Bootstrap

《深入理解c++对象内存模型》

《c++对象内存模型》读书笔记


这本书大二第一次接触,刚开始由于功力不够不能很好的消化这本书的内功,多读几遍就会对自己的语言思想有很大的提升。这本书出版很久了,但一直没被淘汰。记录自己的学习笔记,温故知新。


C语言不是面向对象的计算机编程语言,它主要由基本数据类型,struct结构体,和函数,数据块和函数是没有关联的,函数就相当于服务指令,你想加工什么数据,就通过调用函数传人数据参数,从而获得该函数的服务。这样的话当工程很大的时候就就会定义很多的函数很多的结构体,这些结构体和函数根据去功能通过.h  .m文件进行划分,成百上千的函数结构体会让头文件的引用变得非常的复杂,从而降低开发效率,和程序设计的难度。


C++是面向对象编程的计算机高级语言,封装,继承,多态。数据块和处理数据块的功能函数就关联起来,以前用文件的形式来划分数据块和函数变成的用类的形式。提高开发效率,用面向对象的程序设计思想来设计程序使得更加的清晰。


对于计算机语言,与其说高级语言牛叉到不如说是编译器牛叉,从本质上来讲,计算机高级语言写出来的源代码都逃不过编译器的掌心,它们都会被编译成对应的CPU指令,对于动态语言无非就是加上一个runtime,既然语言都最后会变成CPU指令,那么计算机语言之间都可以一一对应的关系,那么C++语言编写的程序也无非就是函数的调用,参数的传递,但在语法特性上又存在各自的差异。这就说明了编译器对C++语言做了很多的事。


计算机语言是标准,对应的编译器是标准的实现,编译器越是智能越是强大,它为你做的东西就多,这就为什么java,c#,swift不需要头文件,因为开发环境编译器能动态的检测到你设计的变量和函数,swift甚至不需要main函数,swift是面向对象的语言,如果编译器检测到了函数调用,它就会用这个函数作为main函数作为程序的入口点。


对于不同的书,不同的读者有不同的看法,以下为个人学习记录,不足之处还望见谅。


问题:

1.问什么会有self变量?

2.成员函数和虚函数的区别?

3.为什么虚函数能实现动态调用和多态?


1.c和c++对比

c语言的数据块和功能函数分开
[cpp]  view plain  copy
  1. typedef struct point3d {  
  2.     float a;  
  3.     float b;  
  4.     float c;  
  5.   
  6. }point3d;  
  7.   
  8.   
  9. void printPoint3d(const point3d *pd){  
  10.     printf("a= %g,b= %g,c = %g",pd->a,pd->b,pd->c);  
  11. }  

使用时:新建point3d,调用打印函数,这两部分可以天各一方的声明定义,在使用时应用就行了,编译之后有函数指令地址和数据块地址。
[cpp]  view plain  copy
  1. point3d p;  
  2.     p.a=4;  
  3.     p.b=5;  
  4.     p.c=6;  
  5.       
  6.     printPoint3d(&p);  

在c语言中我们要完成一个功能函数的调用首先要创建需要的参数,把参数传人到函数中。

c++语言来实现:
[cpp]  view plain  copy
  1. class point3d{  
  2. private:  
  3.     float a;  
  4.     float b;  
  5.     float c;  
  6. public:  
  7.     void printPoint3d(){  
  8.         printf("a= %g,b= %g,c = %g",a,b,c);  
  9.     }  
  10. };  

使用时:非虚函数的成员函数在编译时就已经确定它的调用地址,而虚函数的调用是在运行时绑定。
[cpp]  view plain  copy
  1. point3d *p=new point3d();  
  2.     p->printPoint3d();  

在c++中我们想要对数据块进行操作只需要调用和它包装工能函数就行了,成员变量(data member)可以映射为struct数据块,成员函数 (data function)可以映射为打印功能函数。

通过这两部分使用的比较我们知道编译器肯定为我门做了些什么?为什么我们通过一个指针就能达到打印的效果了。

结合一些编译的思想,简单对比一下编译运行之后的内存状态:




通过该例子主要是想说明面向对象变成中编译器为我们做了很多东西,它能够确定的它就帮我们完成,c到c++的封装,如果没有虚函数的话是不会增加数据的负担的,c++经过编译之后就是c的数据结构和函数而已,c++为了实现多态,会增加数据成本。


2.this的由来

下面还会讲到编译器对this的处理,给我们添加虚函数列表,虚函数列表指针,为我们生成默认构造函数。

在此先说一下静态和非静态的成员变量和函数:
类的静态变量和静态函数不属于类的实例,它们属于类,也就是它们的作用域是类而不是实例变量,不能通过this指针使用。
类的成员函数和成员变量属于类的实例变量,可以通过this指针指定。


对于静态和非静态而言,编译器主要就是添加this指针的出来,通过this指针调用成员函数时,编译去会把this指针作为参数传递到调用的函数,成员函数就通过this指针就知道它要处理的this的数据块。


静态函数和非静态函数编译之后都是函数,只是它的调用在编写源代码是被编译器不同的对待,对它们的作用域进行了限制,从而达到封装的效果,实现安全调用。

下面来看一个简单类:
头文件:
[objc]  view plain  copy
  1. #include <stdio.h>  
  2.   
  3. class point3d{  
  4. private:  
  5.     float a;  
  6.     float b;  
  7.     float c;  
  8. public:  
  9.     void printPoint3d();  
  10.       
  11. };  

实现文件:
[cpp]  view plain  copy
  1. #include "Point3d.h"  
  2.   
  3. void point3d::printPoint3d(){  
  4.     this->a =1;  
  5.     this->b =2;  
  6.     this->c =3;  
  7.     printf("a= %g,b= %g,c = %g\n",this->a,this->b,this->c);  
  8. }  

使用:
[cpp]  view plain  copy
  1. #include <iostream>  
  2. #include "Point3d.h"  
  3. int main(int argc, const char * argv[]) {  
  4.      
  5.     point3d *point = new point3d();  
  6.     point->printPoint3d();  
  7.       
  8.     return 0;  
  9. }  




运行结果:

a= 1,b= 2,c = 3

Program ended with exit code: 0


从上面的例子中我们可以看到成员变量中可以通过this指针使用成员变量。

下面我们来一步一步的分解改例子:

该类经过编译之后就得到:



上面的类是没有虚函数的所以它的对象的成员布局为:



3.虚函数

如果我们把printPoint3d声明为虚函数:
[cpp]  view plain  copy
  1. class point3d{  
  2. private:  
  3.     float a;  
  4.     float b;  
  5.     float c;  
  6. public:  
  7.    virtual void printPoint3d();  
  8.       
  9. };  

这样的话编译器就会为我们添加一个成员变量,而这个成员变量的初始化并不是我们给它初始化值的,而是编译器在默认构造函数中给它初始化值的。


类中有虚函数的类编译器又会把它编译成什么样了:




那我们调用虚函数编译器又会对我们进行编译了:

[cpp]  view plain  copy
  1. point->printPoint3d();  
当编译器知道我们调用的是虚函数的时候,它会通过它添加的虚函数列表指针找到虚函数列表(编译时确定的),在虚函数列表中找到调用的虚函数的入口地址,把point当作参数传递给虚函数。它和非虚函数的调用的不同是一个直接,一个间接调用,非虚函数的调用在编译时就已经直接使用,虚函数的掉用是在动态的绑定调用,和为动态,如果现在的point指针指向的是它的子类,那么虚函数列表就是它的子类,这是调用的就不是point的printPoint3d函数了,而是它子类虚函数列表中的printPoint3d函数。这就找到为什么父类指针指向不同的子类对象,调用它的虚函数会去调用不同子类的虚函数,这也是多态的本质所在了。


4.多态

下面我们通过继承point3d来实现这一案例:


class point3d.h
[cpp]  view plain  copy
  1. class point3d{  
  2. public:  
  3.     float a;  
  4.     float b;  
  5.     float c;  
  6. public:  
  7.    virtual void printPoint3d();  
  8. };  

class point3d.m
[cpp]  view plain  copy
  1. void point3d::printPoint3d(){  
  2.     this->a =1;  
  3.     this->b =2;  
  4.     this->c =3;  
  5.     printf("a= %g,b= %g,c = %g\n",this->a,this->b,this->c);  
  6. }  

class point4d.h
[cpp]  view plain  copy
  1. #include <stdio.h>  
  2. #include "Point3d.h"  
  3.   
  4. class Point4d:public point3d{  
  5. private:  
  6.     float d;  
  7. public:  
  8.     void printPoint3d();  
  9. };  

class point4d.m
[cpp]  view plain  copy
  1. void Point4d::printPoint3d(){  
  2.     this->a=1;  
  3.     this->b=2;  
  4.     this->c=3;  
  5.     this->d=4;  
  6.     printf("point 4d print :a= %g,b= %g,c = %g ,d= %g\n",this->a,this->b,this->c,this->d);  
  7. }  

使用:

[cpp]  view plain  copy
  1. #include <iostream>  
  2. #include "Point3d.h"  
  3.   
  4. #include "Point4d.h"  
  5. int main(int argc, const char * argv[]) {  
  6.      
  7.     point3d *point = new point3d();  
  8.     point->printPoint3d();  
  9.       
  10.     point = new Point4d();  
  11.     point->printPoint3d();  
  12.     return 0;  
  13.       
  14.       
  15. }  

运行结果:

[cpp]  view plain  copy
  1. a= 1,b= 2,c = 3  
  2. point 4d print :a= 1,b= 2,c = 3 ,d= 4  
  3. Program ended with exit code: 0  


同一个point指针,指向不同的对象是,调用同一个函数,会得到不同的结果,其中到底发生了什么了,下图分解:




小结:
这本说博大精深,还有很多细节没有说出来,感兴趣的可以去阅读本书,这本书给我最大的就是对语言的理解能力有很大的提升,从c语言到面向对象的语言的过度的理解有很大的帮助,不足之处还忘见谅,谢谢。
;