GeekBand by 494631002
写在前面:笔记我是跟着视频所讲的内容按照顺序记录的,也不能说是流水账;毕竟对于课程中模糊不清的位置还是添加了一些自己的理解,另外还加了一些自己的实验来验证。最后希望大家能够在互评评论区提交笔记错误之处或者自己的看法和建议,能够帮助我们一同更好的学习!
这个星期主要学习的内容是C++的对象模型,通过对于对象模型的深入了解,知道了虚函数,多态,this指针。 以及增加了new,delete的部分的详细介绍:
一、对象模型(Object Model):
之前的作业中,要求画图分析对象模型,可是当时并不理解。现在看了这个课程之后,才略有感觉。
Object Model当我们创建出A类、B类、C类的对象(分别是a,b,c)时,它们的对象模型如图所示;
我们发现,对象中占空间的不仅仅是一些类中所声明的成员,还包括了虚函数的指针(vptr),一个类中只要有虚函数而无论有多少个,虚函数指针的个数始终为1个;虚函数指向的是虚函数的地址;一个类中可能会出现多个虚函数,所以这些地址会统一存放在一个列表中,称作虚表(vtbl);图中还可以看到,没有重写的虚函数都指向同一个地址;
以下内容中部分概念引用自CSDN:
1.动态绑定:
动态绑定是将一个过程调用与相应代码链接起来的行为。是指与给定的过程调用相关联的代码,只有在运行期才可知的一种绑定,它是多态实现的具体形式。
具体实现:
C++中,通过基类的引用或指针调用虚函数时,发生动态绑定。引用(或指针)既可以指向基类对象也可以指向派生类对象,这一事实是动态绑定的关键。用引用(或指针)调用的虚函数在运行时确定,被调用的函数是引用(或指针)所指对象的实际类型所定义的。
C++中动态绑定是通过虚函数实现的。而虚函数是通过一张虚函数表(virtual table)实现的。这个表中记录了虚函数的地址,解决继承、覆盖的问题,保证动态绑定时能够根据对象的实际类型调用正确的函数。
在C++的标准规格说明书中说到,编译器必需要保证虚函数表的指针存在于对象实例中最前面的位置(这是为了保证正确取到虚函数的偏移量)。这意味着我们通过对象实例的地址得到这张虚函数表,然后就可以遍历其中函数指针,并调用相应的函数。
具体实例:
具体实例我们创造一个容器,里面装的是指针;这些指针全部都是经过了向上转型后的对象;(类型都是指向A类的指针);A*类型的变量,会根据指向不同的类(基类或子类)的实例,根据vptr指向的对应函数地址,执行不同的函数,从而实现多态。
总结:
a.C++中虚函数加上继承是实现多态的手段。
b.符合三个条件的就是动态绑定:
1.必须是通过指针来调用函数;
2.指针必须是向上转型;
3.调用的函数是虚函数;
2.动态绑定中this指针
这是向上造型的又一个例子;按照灰色箭头的路径:在右边的main函数中,先创建一个子类的对象myDoc,然后子类的对象调用的是父类的函数;之前的笔记已经说过,通过对象调用一个函数,那么这个对象的地址就是this Pointer;所以这条语句可以写成底下那种形式(&myDoc就是this Pointer)。这个时候,既然调用的是父类的函数,其实就已经是向上转型了。进入函数后,再执行到虚函数Serialize()的时候,调用函数的方式就入图中左上角所示。其中n是虚函数在虚表中的位置;
二、补充内容:
1.const
之前笔记有说过,const的关键字在程序中起着至关重要的作用,往往在决定一个函数的功能的时候就可以考虑到底要不要在返回类型前加上const了。注意的是,类中的成员函数添加const的位置:
成员函数的const添加在函数()之后非成员函数的const添加在函数名前;
另一方面,补充了const的一些知识点:
试验了一下:
在课件的String类代码中,添加了这个成员函数,返回类型不是const;
但是在使用的时候先创建一个const的对象出来:
创建一个const类型的对象再用const对象来调用一个返回类型为非const的函数,编译不能通过,原因是如之前的表中所解释的那样:
编译器报错如果成员函数加上const,如前面所说注意const的位置:
编译通过这样就通过了,而且得到了正确的结果;
仔细观察图表,其实还有一种情况需要注意:
似乎有点冲突就是说,如果创建的对象是non-const类型,这样如果类中既有const member function又有non-const member function怎么办呢?该调用谁?因为表中明确指出了,这两类函数都可以被non-const对象调用。(这里还要注意,const算作函数的签名)
C++明确指出了:
规则2.new和delete
之前有介绍过,对于new操作,可以分解为以下三个小步骤:
代码引用自简书作者:“不会飞的鸟人”先分配内存大小,再将指针转换成String类型,最后用指针调用构造函数;
注意的是,其中operator new 分为全局的和属于某个类的区分。可以通过重载operator new(operator new[]) 或 operator delete(operator delete[]) 函数来自己控制内存的管理。
重载::operator new是一件危险的事情,因为重载的是全局函数,这可能导致程序中以后每一个new的动作都会调用你自己写的operator new,这样分配空间是不确定的。同样,重载::operator delete也一样,只不过两者传入的参数不同;前者传入的是size_t类型,表示要被分配的空间,后者传入的是一个指针,表示要释放的地址。
可以在类中去重载new和delete;这样做只针对这个类使用自定义的内存管理;
使用new或delete时可以指定使用全局的operator new或operator delete函数
同理还有::operator new[]和member operator new[]的重载,这里就不一一赘述了。