首先,先来谈谈为什么会有权限控制这一东西?看如下代码
struct Test
{
int a;
int b;
};
int main()
{
Test test;
test.a = 2;
test.b = 3;
test.a = 4;
system("pause");
}这段代码是不是写起来特别爽,一直写一直爽。好了,此时假设对于这个Test结构体,你在你的项目中已经可能有上千处的地方直接用到了其成员a和b,然后有一天,你需要修改你的变量名,如下:
struct Test
{
int m_a;
int m_b;
};你可能会想,我不可能会改着东西,这个你是站在你一人开发的角度上思考问题,可能平常很多时候都是多人协作开发的哦,也许这个结构体是他人提供的,这时候也只能随着改。
那么此时对变量进行引用的地方都得随之修改,你可能会想到直接查找替换,但是不管咋样,对于项目而已,只要是大面积的修改代码肯定是有风险在滴,所以一般来说对于这些公开的来说,不太会随意改动。
所以权限控制就来了,C++提供了两个关键字
public:
这个成员哪里都可以用,不用担心被修改,所以,一旦发布成public的成员,是不能够随意改名字的
private:
这个成员只用于内部使用,不要在其他的地方使用(编译器检测)
public/private可以修饰成员变量,也可以修饰成员函数下面我们改进代码:
struct Test
{
private: //表示以下为私有权限
int m_a;
int m_b;
};
int main()
{
Test test;
//test.m_a = 2; // err 成员 "Test::m_a" (已声明 所在行数 : 41) 不可访问
//test.m_b = 3;// err 成员 "Test::m_b" (已声明 所在行数 : 41) 不可访问
system("pause");
}此时在外部直接使用成员变量会直接报错,这里编译器帮我们限制了访问,那么此时疑问可能又来了,定义了结构体的成员变量为私有,那么我们既不能直接获取数据,也不能修改数据,有啥子用?
别急,我们不可以直接访问或者修改,那可以间接进行操作,可以给出两个对应的访问和修改的公有函数(接口)供他人修改,看如下代码:
struct Test
{
private:
int m_a;
int m_b;
public: //以下成员函数声明
int getA();
void setA(int a);
int getB();
void setB(int b);
};
// 下面为成员函数的实现
// 函数名前面需要加作用域,用于区分普通全局函数
//获取变量m_a数据
int Test::getA()
{
return m_a; //private 内部可以自由访问
}
//设置变量m_a数据
void Test::setA(int a)
{
m_a = a; //private 内部可以自由访问
}
//获取变量m_b数据
int Test::getB()
{
return m_b;
}
//设置变量m_a数据
void Test::setB(int b)
{
m_b = b;
}
int main()
{
Test test;
test.setA(1);
test.setB(2);
cout << "m_a: " << test.getA() << endl;
cout << "m_b: " << test.getB() << endl;
system("pause");
}发现其代码多了一倍之多,哈哈,但是好处也是不少滴,现在如果修改成员变量的名字,那么其实最终修改的地方只有get和set所对应的四个函数,而mian里面的代码不用任何变化(实际main里面的调用可能会很多哦)。
除此之外,成员变量设置私有还有其他好处么,毕竟成员变量改名的情况实际是比较少的
下面,我们再来考虑一种需求,假设上面的成员变量m_a要求只能在1到100的范围内,如果此时m_a是公开的,那么可以直接这样写
Test test;
test.m_a = 101;此时数据是合法(遵守语法,编译器OK),但是不合理(情理,违反了需求),但是使用了private后,我们就可以进行验证啦,使得对数据具有控制权,这也是封装的思想
void Test::setA(int a)
{
//验证需求数据
if (a >= 1 && a <= 100)
{
m_a = a;
}
}对于C++的权限控制来说,其实还有个protected,这个权限的话我们在讲继承的时候在来看。
下面我们先来扩展下另一个知识点,结合之前的const语法,看如下代码
int Test::getA()
{
return m_a;
}
int main()
{
const Test test;
//int a = test.getA(); //err 不能将“this”指针从“const Test”转换为“Test &”
system("pause");
}分析下上面的代码,我把Test定义为const类型,调用getA函数编译器报错了,回顾下C++的第一篇博客,里面有详细的介绍了this指针,this指针的类型其实可以这样表示
Type * const this;可以修改其指向的数据,但不能替换this指针值那么现在我们调用getA函数当然会出错喽,不过这个很不符合常理,为啥呢,虽然我的test是个常理,但是getA函数也没有去修改其值,为何不能调用呢?
所以C++就出了个新语法,常成员函数,看如下代码
struct Test
{
private:
int m_a;
int m_b;
public: //以下成员函数声明
int getA() const;
void setA(int a);
int getB() const;
void setB(int b);
};
// 下面为成员函数的实现
// 函数名前面需要加作用域,用于区分普通全局函数
int Test::getA() const
{
return m_a;
}
int Test::getB() const
{
return m_b;
}
//....所谓的常成员函数语法就是在函数后面加上 const,现在重新调用编译器就不会报错了,那么我们其实可以猜想下,之前不是常成员函数,会报类型转换报错,现在OK了,说明此时this指针的类型可能发生了变化,我们验证下:
int Test::getA() const
{
//char *t = this; //err "const Test *" 类型的值不能用于初始化 "char *" 类型的实体
return m_a;
}哈哈,果然,this指针的类型为
const Type * const this;自己和指向的数据都不能修改这样子就保证了const其内容一定不会发生改变了,所以其实常成员函数的本质就是修改了this指针的类型
下面再思考一个问题,常成员函数可以和成员函数发生重载么
int Test::getA() const
{
return m_a;
}
int Test::getA()
{
return m_a;
}这里的话之前说过,this指针是编译器帮我们偷偷传递进去的,那么常成员函数和成员函数的this指针的类型都是不一致的,所以是构成重载的。
当然,常成员函数还有一说明性的作用,就是让调用者放心调,不会修改数据,自己(开发类的人)也省心(编译器会检测)
OK,下面回到我们的权限控制,思考一个问题,私有数据真的私有么?可以直接访问么
下面先来看一下下面的代码
struct Test
{
private:
int x;
public:
int y;
Test(int x, int y)
{
this->x = x;
this->y = y;
}
};
int main()
{
Test test(3,4);
//test.x; err 如何获取?
system("pause");
}首先我们先使用sizeof来打印下对象占用的大小
cout << sizeof(test) <<endl; // 8结果是8,说明成员x和y都占用后,其字节大小之和也是8,该有的空间都有,下面来观察下内存分布
使用VS的内存窗口,输入&test回车

其实不管私有还是公有,在内存中,完全是一样的,之所以不让访问,是编译器不让访问,并不是这个数据没有或者在其他地方。我们可以跟一下Test构造的汇编代码,观察其赋值
Test test(3,4);
0115220E 6A 04 push 4
01152210 6A 03 push 3
01152212 8D 4D F4 lea ecx,[test] ;取test首地址,也就是this
01152215 E8 F1 F1 FF FF call Test::Test (0115140Bh)
Test(int x, int y)
01151DC0 55 push ebp
01151DC1 8B EC mov ebp,esp
01151DC3 81 EC CC 00 00 00 sub esp,0CCh
01151DC9 53 push ebx
01151DCA 56 push esi
01151DCB 57 push edi
01151DCC 51 push ecx
01151DCD 8D BD 34 FF FF FF lea edi,[ebp-0CCh]
01151DD3 B9 33 00 00 00 mov ecx,33h
01151DD8 B8 CC CC CC CC mov eax,0CCCCCCCCh
01151DDD F3 AB rep stos dword ptr es:[edi]
01151DDF 59 pop ecx
01151DE0 89 4D F8 mov dword ptr [this],ecx ;this指针,也是上面传进来的test首地址
{
this->x = x;
01151DE3 8B 45 F8 mov eax,dword ptr [this]
01151DE6 8B 4D 08 mov ecx,dword ptr [x]
01151DE9 89 08 mov dword ptr [eax],ecx ;给x赋值
this->y = y;
01151DEB 8B 45 F8 mov eax,dword ptr [this]
01151DEE 8B 4D 0C mov ecx,dword ptr [y]
01151DF1 89 48 04 mov dword ptr [eax+4],ecx ;偏移+4,给y赋值
}
01151DF4 8B 45 F8 mov eax,dword ptr [this]
01151DF7 5F pop edi
01151DF8 5E pop esi
01151DF9 5B pop ebx
01151DFA 8B E5 mov esp,ebp
01151DFC 5D pop ebp
01151DFD C2 08 00 ret 8 根据其反汇编可以看出,其实给x和y赋值,就是纯粹计算对应内存数据的偏移然后进行赋值操作。
那么如何获取到私有变量的值呢?那就得用指针了
int main()
{
Test test(3,4);
int *xAddr = (int*)&test; //取出x的地址,也是对象首地址
cout << "x的值为: " << *xAddr <<endl;
system("pause");
}
这里就可以得出结论啦,其实不管成员是public还是private的,并不是放到其他地方找不到而不能访问,在底层存储上,没有任何的区别,只是编译器不让我们用而已,所以我们通过指针仍然可以访问私有数据。
OK,再来看下面这个问题。之前为了让大家知道更好的理解类是这么来的,所以之前的博客也都是用struct来演示,C++ 里面有个关键字 class,其实这个class和之前用的struct本质上是完全一样的,但是有一个细节上的区别,看如下代码:
struct Test
{
int m_a;
int m_b;
};
class Test2
{
int m_a;
int m_b;
};
int main()
{
Test test;
test.m_a = 1; //ok
Test2 test2;
//test2.m_a; // err 成员 "Test2::m_a" (已声明 所在行数 : 46) 不可访问
system("pause");
}使用struct时,其成员m_a是可以访问的,但是class是不让访问的,所以这个细微的唯一差别就是权限不一样
编译器默认class中的成员为private 而struct中的成员为public
所以,学习C++,从C的角度,底层的角度弄清楚怎么回事,这样子就不用记很多的概念。