前言
本篇文章介绍C++类成员的访问控制
关键字
C++的类成员通过使用三个关键字来对成员的访问进行控制,并且只有这三个关键字:
- public
- private
- protected
使用场景
上面的三个关键字会扮演两种角色,也就是说会有两种使用的地方:
- 作为
访问说明符
- 作为
派生运算符
上面的名字比较拗口,看一下例子就明白了,下面的例子是作为访问说明符:
class Base
{// 这里的public、protected、private都是作为访问说明符
public:int a = 0;
protected:int b = 1;
private:int c = 2;
};
注意: class的默认访问说明符是private,struct的默认访问说明符是public
。
下面的例子是作为派生运算符:
class A0:public Base
{
};class B0:private Base
{
};class C0:protected Base
{
};
无论是访问说明符还是派生运算符,最终的目的都是控制对类成员的访问,既然目的明确了,我们就看一下,访问一个类成员的情况有哪些:
- 在当前类访问类的成员
- 在派生类中访问基类的成员
- 作为用户访问当前类的成员
在当前类访问类的成员
这种情况是最简单的,因为上面提到的三个关键字并不会对这种情况有任何影响。也就是说在当前类中可以访问所有类的成员,不管是private、public还是protected。
,看下面的例子:
class Base
{// 这里的public、protected、private都是作为访问说明符
public:int a = 0;void print();
protected:int b = 1;
private:int c = 2;
};void Base::print()
{printf("a = %d, b = %d, c = %d\n",a,b,c);
}
其实关键字的存在并不是为了限制类内对成员的访问,而是为了限制派生类或者使用类对象的用户对类成员的访问
在派生类中访问基类的成员
现在,如果我们创建一个继承基类的派生类,那么,派生类至少得需要确定两件事情:
- 派生类中可以访问哪些成员
- 派生类中可以访问的成员的访问修饰符是什么
还记得文章开始提到的访问说明符和派生运算符吗?
访问说明符决定当前派生类可以访问基类的哪些成员
访问说明符和派生运算符共同决定当前派生类可以访问的成员的访问修饰符是什么
先看第一条,派生类可以访问基类的public成员和protected成员,实际上你可以简单的认为派生类中包含基类的public成员和protected成员,根据在当前类访问类的成员的原则
,我们可以在派生类中访问这些成员。看下面的例子:
class Base
{// 这里的public、protected、private都是作为访问说明符
public:int a = 0;
protected:int b = 1;
private:int c = 2;
};class A0:public Base
{void print();
};
void A0::print()
{printf("a = %d, b = %d, c = %d\n",a,b,c);
}
会报错:
'c' is a private member of 'Base'
但是a和b是可以访问的。
下面再来看第二条,我们既然认为派生了包含了基类的public成员和protected成员,那么这些成员在派生类中是什么访问级别呢?这个得根据派生运算符来决定:
- 如果派生运算符是private(对于class而言,这是默认值,对于struct,默认是public),则基类的public成员和protected成员的访问级别都是private
- 如果派生运算符是protected,则基类的public成员的访问级别变成protected,基类的protected成员的访问级别不变。
- 如果派生运算符是public,则基类的public成员和protected成员的访问级别都不变。
看下面的例子:
#include <iostream>
class Base
{// 这里的public、protected、private都是作为访问说明符
public:int a = 0;
protected:int b = 1;
private:int c = 2;
};class A0:public Base{};
class B0:private Base{};
class C0:protected Base{};
class A00:A0{void print(){printf("a=%d,b=%d",a,b);}};
class B00:B0{void print(){printf("a=%d,b=%d",a,b);}};// 1.这里报错,'a'和'b' is a private member of 'Base'
class C00:C0{void print(){printf("a=%d,b=%d",a,b);}};
int main(int argc, const char * argv[])
{C0 c0;c0.a; // 2. 这里报错'a' is a protected member of 'Base'A0 a0;a0.a; // 3. 没有报错return 0;
}
报错的地方我标记出来了
- 看报错1,因为B0是通过private派生运算符继承的Base,所以在B0中,a,b都被设置为private的访问限制,所以继承的B00中不能使用a和b
- 看报错2,因为C0是通过protected派生运算符继承的Base,所以在C0中,a,b都被设置为protected的访问限制,虽然继承的C00中可以使用a和b,但是如果作为用户使用C0类,还是不能访问a和b。
- 看3为什么没有报错,因为A0是通过public派生运算符继承的Base,所以在A0中,a被设置为public,b依然保持protected,所以对于使用A0的用户来说,依然可以访问a。
作为用户访问当前类的成员
通过上一节我们把基类的成员引入派生类并通过派生运算符来标记访问权限以后,作为用户访问类的成员规则已经很清楚了,因为对于用户来说,只能访问类的public成员,而不能访问protected和private成员
,但是依然有几种情况需要我们注意:
- 如果用户正好就是当前类呢
- 如果用户是当前类的派生类呢
- 用户不能是当前类的基类,可以想想为什么?
看下面的例子:
#include <iostream>
class Base
{// 这里的public、protected、private都是作为访问说明符
public:int a = 0;
protected:int b = 1;
private:int c = 2;
};class A0:public Base
{void print1(Base& val){// 报错:'b' is a protected member of 'Base'printf("a=%d,b=%d",val.a,val.b);}void print2(A0& val){printf("a=%d,b=%d",val.a,val.b);}
};
我们在A0中访问Base对象的protected成员报错
但是我们在A0中访问A0对象的protected成员就没事
先来看为什么在A0中访问A0对象的protected成员就没事,要分析这个问题,先得明白一件事儿,那就是:
类函数不是类对象的一部分,也就是类函数是所有类对象共用的
既然这样,我们为了能在函数中访问类的成员,编译器向类函数添加了一个参数this,请注意,对于A0类来说,这个this正是一个A0对象。既然this能访问自己的protected成员,A0对象也是可以的,不仅如此,在A0类函数中,A0对象也可以访问自己的private成员
,看下面例子:
#include <iostream>
class Base
{// 这里的public、protected、private都是作为访问说明符
public:int a = 0;
protected:int b = 1;
private:int c = 2;
};class A0:public Base
{
private:int aa=0;void print2(A0& val){printf("a=%d,b=%d",val.aa,val.b);}
};
这个执行就没问题
然后看为什么在A0中访问Base对象的protected成员报错,因为如果这种情况允许的话,Base对象的protected成员对用户来说就没有意义了
,我可以随便定义一个继承Base的类,然后在类函数中可以随便修改Base对象的protected成员值。
用比较正式的语言总结一下:
对于派生类来说,不能在派生类中访问一个基类对象的受保护成员,但是可以通过访问一个当前派生类的对象来访问基类的受保护成员
如何改变成员的可访问性
我们可以使用using声明改变派生类成员的可访问性,看下面的例子:
#include <iostream>
class Base
{// 这里的public、protected、private都是作为访问说明符
public:int a = 0;
protected:int b = 1;
private:int c = 2;
};
class A0:private Base
{
public:using Base::a;using Base::b;
protected:
private:int aa=0;void print2(A0& val){printf("a=%d,b=%d",val.aa,val.b);}
};class A00:A0
{void print(){printf("a=%d,b=%d\n",a,b);}
};int main(int argc, const char * argv[])
{A0 a0;a0.b;return 0;
}
在A0类中,我们通过使用
using Base::a;
using Base::b;
使a和b变成了A0的public成员,在使用using声明的时候需要注意:
using声明的成员必须是可以访问的
,例如不能将基类的private成员变成public- using声明所在的访问说明符就是声明成员修改的访问级别。