c++杂乱笔记2

OOP

多态

  • C++多态分类及其实现
    1. 重载多态(Ad-hoc Polymorphism,编译期) 函数重载,运算符重载
    2. 子类型多态(Subtype Polymorphism,运行期) 虚函数
    3. 参数多态性(Parametric PolyMorphism) 编译期 类模板,函数模板
    4. 强制多态(Coerion PolyMorphism 编译期,运行期) 类型转换

静态多态(编译期间,早绑定)

  • 函数重载,模板

    动态多态(运行期间,晚绑定)

  • 虚函数
  • 多态与非多态的实质区别就是函数地址是早绑定还是晚绑定
    tips:
  • 普通函数不可是virtual
  • static 不可为virtual
  • 构造函数不可为virtual,因为在调用构造函数时,虚表指针并没有在对象的内存空间中,必须要构造函数调用完成后才会形成虚表指针
    tips:C++ 隐藏
  • 如果派生类的函数与基类的函数同名,但是参数不同。此时,不论有无virtual关键字,基类的函数将被隐藏(注意别与重载混淆)。
  • 如果派生类的函数与基类的函数同名,并且参数也相同,但是基类函数没有virtual关键字。此时,基类的函数被隐藏(注意别与覆盖混淆).

虚函数指针相关

虚函数指针和虚函数表

  • A b;

    父类虚函数表
  • 包含虚函数的类才会有虚函数表,同属于一个类的对象共享虚函数表, 但是有各自的虚函数指针.虚函数表实质是一个指针数组,里面存的是虚函数的函数指针。
  • 虚表指针放在类成员中的第一个地址(C++的编译器应该是保证虚函数表的指针存在于对象实例中最前面的位置(),可以通过(long)(&b) 访问虚表地址,(long)(long)(&b) 访问第一个第一个虚函数地址。
    1
    2
    3
    pFun = (Fun)*((int*)*(int*)(&b));
    pFun();
    reinterpret_cast<Fun>(*((long *)*(long *)(&d)))();


单继承,不重写虚函数

单继承,重写虚函数

多继承,不重写虚函数

多继承,重写虚函数

virtual ~f()

作用
* 解决based point-> derived object,用based point 删除derived object.
* 如果父类的虚函数是private或是protected的,但这些非public的虚函数同样会存在于虚函数表中,所以,我们同样可以使用访问虚函数表的方式来访问这些non-public的虚函数.

虚继承

虚继承用于解决多继承条件下的菱形继承问题(费存储空间、存在二义性)

* 底层实现原理与编译器相关,一般通过虚基类指针和虚基类表实现,每个虚继承的子类都有一个虚基类指针(占用一个指针的存储空间,4字节)和虚基类表(不占用类对象的存储空间)(需要强调的是,虚基类依旧会在子类里面存在拷贝,只是仅仅最多存在一份而已,并不是不在子类里面了);当虚继承的子类被当做父类继承时,虚基类指针也会被继承。

* 实际上,vbptr 指的是虚基类表指针(virtual base table pointer),该指针指向了一个虚基类表(virtual table),虚表中记录了虚基类与本类的偏移地址;通过偏移地址,这样就找到了虚基类成员,而虚继承也不用像普通多继承那样维持着公共基类(虚基类)的两份同样的拷贝,节省了存储空间。

虚函数和虚继承

  • 都使用了虚指针(占用类的存储空间)和虚表(不占类存储空间)
  • 虚函数表存储的是虚函数地址,虚基类表存储相对直接继承类的位偏移。
  • 虚基类在子类中存在一份拷贝,而虚函数不占用存储空间。

tips:

  • 模板类可以使用虚函数。
  • 但任何类的成员模板函数不能是虚函数。

聚合类

用户可以直接访问其成员,并且具有特殊的初始化语法形式。满足如下特点:
  • 所有成员都是 public
  • 没有定义任何构造函数
  • 没有类内初始化
  • 没有基类,也没有 virtual 函数

内存分配管理

  1. malloc
    申请指定字节内存,初始值不确定。

char *str = (char*) malloc(100);

  1. calloc
    为指定长度的对象,分配能容纳其指定个数的内存。申请到的内存的每一位(bit)都初始化为 0。
  2. realloc
    更改以前分配的内存长度(增加或减少)。当增加长度时,可能需将以前分配区的内容移到另一个足够大的区域,而新增区域内的初始值则不确定。
  3. alloca
    在栈上申请内存。程序在出栈的时候,会自动释放内存。但是需要注意的是,alloca 不具可移植性, 而且在没有传统堆栈的机器上很难实现。alloca 不宜使用在必须广泛移植的程序中。C99 中支持变长数组 (VLA),可以用来替代 alloca。
  • new/new[]先底层调用malloc,然后调用构造函数。
  • delete/delete[]先调用析构函数,然后free,指针置为空。
  • new能自动计算所需字节数,无需像malloc一样手动操作。

pacement new

允许传递额外的地址参数,预先在指定的内存区域创建对象。

1
2
3
4
5
6
new (place_address) type
new (place_address) type (initializers)
new (place_address) type [size]
new (place_address) type [size] { braced initializer list}
//place_address 为 point
//initializers可能为空的初始化列表

tips:

  • delete this 合法前提
    • 之后不再调用this指针(任何调用)
    • this对象由new而不是其他任何方式分配的。
    • 该成员函数是this对象最后调用的的成员函数。
    • 剩下的成员函数(delete this之后的)不接触到this对象,包括调用任何其他成员函数或访问任何数据成员。

只在堆上生成对象的类

方法:将析构函数私有。
  • (C++ 是静态绑定语言,编译器管理栈上对象的生命周期,编译器在为类对象分配栈空间时,会先检查类的析构函数的访问性。若析构函数不可访问,则不能在栈上创建对象。)
  • 在栈上建立一个类对象,是由编译器为对象在栈空间中分配内存,是通过直接移动栈顶指针,挪出适当的空间,然后在这片内存空间上调用构造函数形成一个栈对象。使用这种方法,直接调用类的构造函数。

只在栈上生成对象的类

方法:将new和delete操作符重载为私有。
  • :C++ 是静态绑定语言,编译器管理栈上对象的生命周期,编译器在为类对象分配栈空间时,会先检查类的析构函数的访问性。若析构函数不可访问,则不能在栈上创建对象。
  • 动态建立类对象,是使用new运算符将对象建立在堆空间中。这个过程分为两步,第一步是执行operator new()函数,在堆空间中搜索合适的内存并进行分配;第二步是调用构造函数构造对象,初始化这片内存空间。这种方法,间接调用类的构造函数。
Donate comment here