以下是本人整理的C++基础知识点,内容并不包含全面的C++知识,只是对C++重点内容、特点进行整理和归纳。
4.1 继承和派生的概念
继承和派生(两者表示一个概念)
继承
一个类获得另一个类的成员(变量和函数)
派生
一个类将自己的成员赋予另一个类(变量和函数)
父类和子类
父类(基类)
被继承的类
子类(派生类)
派生的类
继承的语法
class 派生类名:[继承方式] 基类名{
派生类新增加的成员
};
例子:class Student: public People{...};
4.2 C++三种继承方式
继承方式的介绍
限定子类对父类成员的访问的最高权限
三个关键字权限:public > protected > private,默认为 private
一般使用 public 继承
public、protected、private 指定继承方式
三种继承方式
1、public继承的权限变化
基类 public 成员 --> 派生类 public 成员
基类 protected 成员 --> 派生类 protected 成员
基类 private 成员 --> 派生类中不可见
2、protected继承的权限变化
基类 public 成员 --> 派生类 protected 成员
基类 protected 成员 --> 派生类 protected 成员
基类 private 成员 --> 派生类中不可见
3、private 继承的权限变化
基类 public 成员 --> 派生类 private 成员
基类 protected 成员 --> 派生类 private 成员
基类 private 成员 --> 派生类中不可见
基类 private 成员在派生类中的特点
被继承而且占用内存,但在派生类中不可见
访问基类 private 成员的唯一方法:借助基类非 private 成员函数
using 关键字:改变基类成员在派生类中的访问权限
如:public --> private、protected --> public、protected --> public
只能改变基类中 public 和 protected 成员的访问权限,private 成员不能改变
例子
class Student : public People {
public:
using People::m_name; //原是protected,改为public
private:
using People::show; //原是public,改为private
};
4.3 C++继承时的名字遮蔽问题
什么是名字遮蔽
派生类成员和基类成员重名,派生类成员把基类成员遮蔽,实际使用的是派生类成员
被遮蔽的基类成员仍然存在,可以通过类名和域解析符访问
如:stu.People::show();
成员函数:名字遮蔽和函数重载
基类和派生类的成员函数名字一样时会造成遮蔽,不论参数列表是否相同,不会产生函数重载
同一个类内的函数才会构成重载;有继承关系的类之间只会产生名字遮蔽
4.4 C++类继承时的作用域嵌套
作用域包含
类是一种作用域
基类包含派生类
Base > Derived
内层作用域和外层作用域
内层作用域:派生类作用域
外层作用域:基类作用域
编译器对类的名字查找顺序
成员变量
先在派生类中查找,如果找不到就到基类中查找
成员函数
先在派生类中查找,如果找不到就到基类中查找
如果在派生类中找到了,但是函数参数不一致,报错
4.5 C++继承时的对象内存模型
类继承时的内存模型
成员变量
位置:堆区或栈区
顺序:基类成员在前,派生类在后(基类成员在低内存地址)
成员函数
位置:代码区
成员变量遮蔽时的内存分布
被遮蔽的所有基类成员变量都在内存中
基类成员在前,派生类在后,顺序不变
4.6 C++基类和派生类的构造函数
类的构造函数不能被继承
基类的构造函数既不能变成派生类的构造函数,也不能变成普通成员函数
基类成员的初始化
初始化方法
在派生类构造函数的初始化列表中,显式指明调用基类的构造函数
解决基类private成员无法初始化的问题
不显式指明,调用基类默认构造函数
没有默认构造函数,编译失败
派生类构造函数对成员变量的初始化顺序
先调用基类构造函数,再执行其他代码
最外层的基类构造函数最先被调用
派生类构造函数的执行顺序
从最外层基类到派生类
派生类构造函数只能调用直接基类的构造函数,不能调用间接基类的
虚继承的基类构造函数除外
初始化例子
class Student: public People{...}
Student::Student(char *name, int age, float score): People(name, age), m_score(score){ }
4.7 C++基类和派生类的析构函数
析构函数和构造函数一样,不能被继承
析构函数的执行顺序
先执行派生类析构函数,再执行基类析构函数
跟构造函数的执行顺序相反
4.8 C++多继承
多继承介绍
一个派生类拥有至少2个基类
多继承容易让代码逻辑复杂、思路混乱,较少使用
多继承语法
class D: public A, private B, protected C{
//类D新增加的成员
}
多继承下的构造函数
在派生类构造函数的初始化列表中,调用多个基类的构造函数
基类构造函数的调用顺序:和声明派生类时基类出现的顺序相同
写法:D(形参列表): A(实参列表), B(实参列表), C(实参列表){...}
多继承下的命名冲突
发生冲突时,使用类名和域解析符::访问成员
BaseA::show();
4.9 C++多继承时的对象内存模型
Base A、Base B 、Derived C
class C: public A, public B{...}
内存分布(从低到高):A、B 、C,也就是和声明顺序相同
4.10 借助指针访问private、protected属性的成员变量
对象的起始地址 + 偏移
4.11 C++虚继承
多继承的冲突:菱形继承
派生类中保留间接基类的多份同名成员,一般是多余的
访问时会产生歧义和冲突,不知道通过那个继承路径访问
虚继承
虚继承介绍
作用:存在重复继承的间接类时,派生类中只保留一份间接基类的成员,解决多继承时的命名冲突和冗余数据
目的:在继承关系中,让某个类和其它类共享它的基类。其中,这个被共享的基类就称为虚基类
虚继承语法
class A{...}
class B: virtual public A{...}
class C: virtual public A{...}
class D: public B, public C{...}
4.12 C++虚继承时的构造函数
虚继承时的构造函数
存在虚继承时,派生类的构造函数必须显式调用虚基类的构造函数
例子
虚继承【 D::D(...): A(), B(), C(), m_d(){... } 】
A为D的间接基类,被B、C虚继承
一般继承【 D::D(...): B(), C(), m_d(){... } 】
A为D的间接基类,被B、C继承
虚继承时构造函数的执行顺序
先调用虚基类的构造函数
再根据声明顺序,调用其它直接基类的构造函数
4.13 C++虚继承下的 内存模型
内存特点
无论是虚基类的直接派生类还是基类的间接派生类,虚基类的子对象始终位于派生类对象的最后面,也就是位于高地址
虚基类越底层,内存地址越高
编译器对虚继承对象内存的规则
内存分布
分为固定部分和共享部分
固定部分:非虚继承部分
共享部分:虚继承部分
固定部分放在低位,共享部分放在高位
虚继承成员变量的计算
不同编译器没有统一标准
VC编译器解决方案
引入了虚基类表,用一个指针指向虚基类
虚基类表是一个数组,元素存放的是各个虚基类相对于首地址的偏移。
4.14 C++将派生类赋值给基类(向上转型)
数据类型的转换
基本数据类型
编译器知道如何对数据进行取舍
“类”数据类型
只有在基类和派生类之间才有意义
只能将派生类赋值给基类(向上转型)
派生类对象赋值给基类对象
派生类指针赋值给基类指针
派生类引用赋值给基类引用
向上转型和向下转型
向上转型:将派生类赋值给基类,安全
向下转型:将基类赋值给派生类,有风险
编译器不知道如何填充剩下的内存
将派生类对象赋值给基类对象
对象之间的赋值 --> 成员变量的赋值
派生类专属的成员丢弃
不影响成员函数和 this 指针
将派生类指针赋值给基类指针
特点
仅仅是改变了指针的指向
对象的指针必须要指向对象的起始位置
通过基类指针访问派生类的成员
访问派生类的成员变量
能够访问派生类中来自基类的成员变量
基类指针指向派生类的对象,使得隐式指针 this 也指向了 派生类的对象,最终基类指针使用的是派生类对象的成员变量
编译器通过指针来访问成员变量,指向哪个对象就使用哪个对象的数据
访问派生类的成员函数
不能访问派生类的成员函数,只能访问基类的成员函数
编译器通过指针的类型来访问成员函数,指针属于哪个类的类型就使用哪个类的函数
将派生类引用赋值给基类引用
和指针一致