- 目录
- 《C++面向对象语法总结(一)》
- 《C++面向对象语法总结(二)》
二十一、多继承
- C++允许一个类可以有多个父类(不建议使用,会增加程序设计复杂度)
- 在多继承中,会按照继承顺序将父类的成员变量放到子类成员变量的前面
- 多继承体系中,子类调用父类构造函数只需要在初始化列表中调用即可
- 如果子类继承的多个父类都有虚函数,那么子类对象就会产生对应的多张虚表
- 如果子类和父类中都有相同的同名函数,直接调用会调用子类中的函数,调用父类中的同名函数需要显式指定父类名称
- 如果子类和父类都具有同名的成员变量,直接调用会调用子类的成员变量,如果需要调用父类的成员变量,需要显式指定调用的父类
二十二、菱形继承
-
菱形继承指的是两个类继承了一个超类,然后有一个子类又多继承了这两个类,其继承关系图类似于菱形
-
菱形继承的问题
- 最底下的子类从积累继承的成员变量冗余、重复
- 最底下的子类无法访问基类的成员,有二义性
二十三、虚继承
- 虚继承指的是在继承的类前面加上virtual关键字
- 虚继承可以解决菱形继承带来的问题
- Person类被称为虚基类
- 虚继承会增加内存中的数据量,在虚继承中,虚表中一般会包含两个偏移量
- 虚表指针于本类起始的偏移量(一般是0)
- 虚基类第一个成员变量与本类起始的偏移量
- 虚继承的内存图(32位环境)
二十四、静态成员(static)
-
静态成员:被static修饰的成员变量或函数
- 可以通过对象(对象.静态成员)、对象指针(对象指针 -> 静态成员)、类(类::静态成员)来访问静态成员变量或函数
-
静态成员变量
- 存储在数据段(全局区,类似于全局变量),整个程序运行过程中只有一份内存
- 对比全局变量,它可以设定访问权限(public、protected、private),达到局部共享的目的
- 必须初始化,而且必须在类的外面初始化,初始化时不能带static,如果累的声明和实现分离,需要在实现(cpp文件)中初始化
-
静态成员函数
- 内部不能使用this指针(this指针只能用在非静态成员函数内部)
- 不能是虚函数(虚函数只能是非静态成员函数)
- 内部不能访问非静态的成员变量和函数,只能访问静态的成员变量和函数
- 非静态成员函数内部可以访问静态成员变量和函数
- 构造函数、析构函数不能是静态
- 当声明和实现分离时,实现部分不能带static
-
静态成员经典应用——单例模式
二十五、const成员
- const成员:被const修饰的成员变量、非静态成员函数
- const成员变量
- 必须初始化(类内部初始化),可以在声明的时候直接初始化赋值
- 非static的const成员变量还可以再初始化列表中初始化
- const成员函数(非静态)
- const关键字写在参数列表后面,函数的声明和实现都必须带const
- 内部不能修改非static成员变量
- 内部只能调用const成员函数,static成员函数
- 非const成员函数可以调用const成员函数
- const成员函数和非const成员函数构成重载
- 非const对象(指针)优先调用非const成员函数
- const对象(指针)只能调用const成员函数、static成员函数
二十六、引用类型成员
- 引用类型成员变量必须初始化(不考虑static情况)
- 在声明的时候直接初始化
- 通过初始化列表初始化
二十七、拷贝构造函数(Copy Constructor)
- 拷贝构造函数是构造函数的一种
- 当利用已存在的对象创建一个新对象(类似于拷贝),就会调用新对象的拷贝构造函数进行初始化
- 拷贝构造函数格式是固定的,接受一个const引用作为参数
- 类中默认的拷贝构造函数是直接拷贝已有对象的内存,当对象中有指针变量时,也是拷贝的指针变量的值,而不是指针变量指向的对象,所以是浅拷贝
- 继承体系中调用父类的拷贝构造函数直接在初始化列表显式调用,继承体系中默认也会直接拷贝父类对象的内存(因为变量的内存是连续的)
- 拷贝构造函数的使用
- 下面代码中,car2、car3都是通过拷贝构造函数初始化的,car、car4是通过非拷贝构造函数初始化的
- 变量前面有类型是新建对象的操作,没有类型是赋值操作,如car4=car3就是复制操作(默认是浅拷贝),并不会调用拷贝构造函数
二十八、浅拷贝、深拷贝
- 对于指针变量,如果只是拷贝了指针的地址,并没有拷贝指向的具体的值,叫做浅拷贝,如果拷贝了指向的值,就叫做深拷贝
- 编译器默认提供的拷贝是浅拷贝
- 将一个对象中所有成员变量的值拷贝到另一个对象(直接拷贝内存中的值)
- 如果某个成员变量是指针,只会拷贝指针中存储的地址值,并不会拷贝指针指向的内存空间的值
- 可能会导致堆空间多次free的情况,因为浅拷贝拷贝的是地址,在拷贝的不同对象中free内存,实际上释放的是同一个内存空间
- 如果需要实现深拷贝,就需要自定义拷贝构造函数
- 将指针类型的成员变量所指向的内存空间内的值,拷贝到新的内存空间
- 深拷贝示例
二十九、对象型参数和返回值
- 使用对象类型作为函数的参数或者返回值,是进行了对象拷贝的,可能会产生一些不必要的中间对象
- 函数参数默认是拷贝传递,所以作为对象参数,也会拷贝
- 因为函数栈中的对象调用完会释放,所以作为返回值的时候,编译器必须进行拷贝,原来函数中的对象已经释放了
三十、匿名对象(临时对象)
- 匿名对象:没有变量名、没有被指针指向的对象,用完后马上调用析构
- 匿名对象作为实参和返回值时,编译器会做优化,只会产生一个对象,调用一次构造函数
三十一、隐式构造
- c++ 中存在隐式构造的现象:在某些情况下,会隐式调用单参数的构造函数
- 可以通过关键字explicit禁止掉隐式构造
三十二、编译器自动生产构造函数的情况
-
c++的编译器在某些特定的情况下,会给类自动生成无参的构造函数,比如
- 成员变量在声明的同时进行了初始化
- 有定义虚函数
- 继承了其他的类
- 包含了对象类型成员,且这个成员有构造函数(编译器生成或自定义)
- 父类有构造函数(编译器生成或自定义)
-
对象创建后,需要做一些额外的操作时(比如内存操作、函数调用),编译器一般都会为其自动生成无参的构造函数
-
并不是所有情况都会生成无参的构造函数
三十三、内部类
- 如果将类A定义在类C的内部,那么类A就是一个内部类(嵌套类)
- 内部类的特点
- 支持public、protected、private权限
- 成员函数可以直接访问其外部类的所有成员(反过来则不行)
- 成员函数可以直接不带类名、对象名访问其外部类的static成员
- 不会影响外部类的内存布局
- 可以在外部类内部声明,在外部类外面进行定义
- 内部类声明和实现分离的写法
三十四、友元
- 友元包括友元函数和友元类
- 如果将函数A(非成员函数)声明为类C的友元,那么函数A就能直接访问类C对象的所有成员
- 如果将类A声明为类C的友元,那么类A中的所有成员函数都能直接访问类C对象的所有成员
- 友元破坏了面向对象的封装性,但在某些频繁访问成员变量的地方可以提高性能
- 友元的声明可以在类的任何位置
三十五、局部类
- 在一个函数内部定义的类,称为局部类
- 局部类的特点
- 作用域仅限于所在函数内部
- 其所有成员必须定义在类内部,不允许定义static成员变量
- 成员函数不能直接访问函数的局部变量(static变量除外)
后记
个人总结,欢迎转载、评论、批评指正