1.c++中内存分布的特点:
全局区(静态区):全局变量,static修饰的局部变量和全局变量,编译时期已经确定
常量区:const修饰的全局变量(const修饰的局部变量还是存储在栈区)和一般的常量(比如字符串),虚函数表(编译时期已经确定下来)
代码区:static修饰的成员函数,一般的成员函数,代码,编译时期已经确定
一般的成员变量和虚函数指针一般是分配在堆或栈中。
一般const修饰的局部变量放在栈区,是可以通过指针对这个变量进行修改的。
这里需要注意一点:如果是在编译时期就已经确定,此时还没有产生对象那为什么,类不能直接调用一般的成员函数,必须通过对象才能调用,因为一般的成员函数里面有一个this指针,必须有一个对象往里面传值,这个函数才能使用
-
普通成员函数调用流程(大体)
- 由于函数地址在编译期间已确定,所以直接找到该函数地址
- this指针,作为隐含参数传入该函数
- 注意:如果该函数中,使用了实例的成员变量,由于this指针为null,程序会报错
-
虚函数调用流程(大体)
- 查找this指针(也就是实例)的地址
- 根据this指针,查找虚函数表(函数指针数组)的地址
- 从虚函数表中,取出相应的函数地址
其中,虚函数表是编译时期为每一个类在常量区分配好的一段空间,这个虚函数表实质上是一个函数指针数组,里面存放着该类的虚函数的指针,也就是虚函数的地址。
对于每运行产生的一个对象,为这个对象,且在对象的空间中的第一个位置分配一个指针,这个指针指向虚函数表的第一个位置。
堆区:new一个对象:运行时才能确定
栈区:函数运行分配的空间,栈上分配的对象,也是运行时期确定
2.c++创建一个对象的过程:
1.在栈或者堆中开辟一块空间
2.此时给这块区域填充的是垃圾值
3.根据构造函数给这块区域分配真正的数据
其中,对象初始化有两种方式:1.通过构造函数赋值2.构造函数列表的方式
对于构造函数列表:没有第二步,直接给区域填充正确的值,如果需要填充的变量是由const修饰,只能赋值一次,不可改变。或者就是引用类型,只能赋值一次,不能改变,那么必须使用构造函数列表初始化的方式进行赋值。
(
构造函数的初始化列表
–初始化列表可以让构造函数在调用之前进行初始化工作
–如果类的成员变量是const或引用类型,使用初始化列表是不二选择
)
3.构造函数被私有化:
导致编译器无法调用该类的构造函数进行对象的初始化工作,但是类中的成员函数可以调用类的构造函数,并且这个成员函数应该写成:static的形式,因为需要直接通过类本身来进行调用,并且应该是public的形式,因为需要为外界提供一个接口,如果这个也设置成private的形式,类外部也访问不了,那么这个类的对象就无法创建,可以在该函数里面new一个对象,该函数可以访问构造函数,这样就产生了一个新的对象。主要目的还是不想让外部随便new一个新对象出来。
4.单例模式:
转载原文地址:
https://blog.youkuaiyun.com/lanzhizhuxia/article/details/7920556
5.虚函数和多重继承:
. 多重继承
转载原文地址:
https://www.cnblogs.com/alone-striver/p/7875741.html
2. 虚函数表构造过程
从编译器的角度来说,B的虚函数表很好构造,D的虚函数表构造过程相对复杂。下面给出了构造D的虚函数表的一种方式(仅供参考):这个虚函数表的确定是在编译时期已经完成,并且在常量区为其分配空间
当一个类继承多个类,且多个基类都有虚函数时,子类对象中将包含多个虚函数表的指针(即多个vptr),例:
其中:D自身的虚函数与B基类共用了同一个虚函数表,因此也称B为D的主基类(primary base class)。
虚函数替换过程与前面描述类似,只是多了一个虚函数表,多了一次拷贝和替换的过程。
虚函数的调用过程,与前面描述基本类似,区别在于基类指针指向的位置可能不是派生类对象的起始位置,以如下面的程序为例:
适时把自己“归零”,就会不断重新开始,永葆青春。人生最大的敌人莫过于自己。一个人是否成功,大抵决定于在不断把自己“归零”的过程中战胜自己。
6.虚析构函数的作用
首先,如果A的析构函数是虚函数,那么派生类B即使析构函数不是虚函数,编译器也会默认为其加上virtual关键字,变成虚函数的。
最后,虚函数和普通成员函数的不同: