我曾经自学过C++,现在回想起来,当时是什么都不懂。说不上能使用C++,倒是被C++牵着鼻子走了。高中搞NOIP并不允许使用STL库,比赛中C++面向对象的机制基本没有什么用武之地,所以高中搞NOIP名为用C++,其实是c加上了cout和cin。

  前几天看韩老师的《老码识途》,里面记录了一些C++面向对象机制的探索,又勾起了我的兴趣。而这个学期自学了汇编,又给了我自己动手探索提供了能力基础,自己上手以后,从一个更加底层的视角看C++机制的实现,让我在黑暗中摸到了驯服C++的缰绳。

  引用:

  本质上是指针,这一点即使大家没有看反汇编应该也是猜到了。

  对象在内存上的布局:

1: class Father
2: {
3:     int iA_;
4:     int iB_;
5:
6:     void FuncA();
7:     void FuncB();
8: };
9:
10: class Child : Father
11: {
12:     int iC_;
13:     void FuncC();
14: };

  一个Father对象里只包含 (低地址 –> 高地址) : iA_,iB_。也是一个Father对象的大小是8个字节,函数并不会占用内存空间。

  为什么不会?

  其实类的成员函数可以看做本质上与普通函数相同。

  编译器在编译的时候知道函数的位置,所以调用普通函数的时候会直接 call 函数地址(偏移)。也是被硬编码了,函数的地址是固定的( 不考虑重定位之类的情况 )。

  而成员函数的调用也是如此,只是编译器还多做了一件事情,是判断这个对象有没有调用这个函数的“权限”(函数不是你声明的,当然无权调用),“权限”不够会报错,告诉那个对象类型没有这个方法。

  所以,类对象的大小与这个类的方法数多少是没关系的。成员函数和普通函数本质上一样,实现这个机制,要靠编译器来做工作。

  this指针:

  成员函数与普通函数不同之处之一是访问对象的数据。

  要访问一个对象的元素,说白了是要找到这个元素所在的内存位置,也是要有指针。

  我们没有看到传递this指针,因为这件事又是编译器帮我们做了。

  反汇编会看到对象调用一个方法的时候,会将这个对象的首部地址赋值给ecx寄存器,通过寄存器来传递this指针。

  我们在成员函数里可以不需明写this指针地调用对象元素,还是因为编译器帮我们多做了一步“翻译”。

  私有化:

  不多说,是编译器在编译阶段通过源码来判断某个元素是不是能够被访问,某个方法是不是能够被调用,运行的时候并不会有访问限制。看代码:

1: #include <stdio.h>
2:
3: class Exp
4: {
5:     int iA_;
6:     int iB_;
7:
8: public:
9:     Exp()
10:     {
11:         iA_ = iB_ = 0;
12:     }
13:     void Out()
14:     {
15:         printf("%d %d ",iA_,iB_);
16:     }
17: };
18:
19: int main()
20: {
21:     Exp oA;
22:     void *pC = &oA;
23:
24:     oA.Out();
25:     *(int*)pC = 1;
26:     *(int*)((int)pC+4) = 2;
27:     oA.Out();
28:
29:     return 0;
30: }

  结果是: 0    0
       1    2

  虽然 iA_,iB_是私有的,但是还是被外界修改了。因为编译器无法知道我干了这事(显式的 oA.iA_ = 1 被发现了哈)