继承
- 无论是那种继承方式,基类继承的私有属性都无法访问
- 不论父类中的属性被啥修饰,都会被子类全盘接收
- public,protected,private继承
- private 继承和protected 继承都是类中可以访问,类外无法访问,这有什么区别呐?
- 继承的内存布局
- 当出现多继承时,子类构建对象时,按照继承顺序,依次调用基类的构造函数
- 派生类创建对象时,每次都会默认调用基类的构造函数
- 在两个类(一个继承一个)中,如果出现相同的静态变量,虽然它俩都属于整个类,名字相同,但它们不会出现覆盖的情况,它们各种的作用域在各种的类中。
- 不想让子类继承的化话可以加final关键字
- 如果基类和子类有相同名称函数,那么在子类中,父类的该函数就会被同名隐藏起来了,
- 想调用的话,就必须加上作用域
- 就算是参数列表不同,也不能构成函数重载,必须显式的去调用
- Y形问题:
- 菱形问题
- 先组合,再继承
- 先继承,再组合,同时有自己的私有属性
- 继承时虚表的情况
- 通过指针的转换调用虚函数
- 派生类继承基类的虚函数时,会将基类中的虚函数的地址原封不动的继承下来
- 当派生类重写基类中的方法后,派生类的虚表中对应的该函数将被重写,其它的函数不变
- 当派生类有虚函数时,会把自己的虚函数加入到从基类继承的虚函数的后面
- 当多继承时,派生类会把自己的虚函数加到第一个类的虚表指针后面
- 当基类里面有三个相同名称的虚函数,当派生类继承并改写时,会把三个虚表中的虚函数全部改了
- 公有继承:是一个
- 基类只会继承父类的构造函数,不会继承拷贝构造
- 同时,赋值重载也也一样,不过我们可以通过指针强制来解决
- 隐藏
- 注意
继承:类型层次设计中提高代码的复用程度的重要手段。
派生:保持原有的属性上进行扩展,增加新属性和方法,以形成新的类型
继承权限和访问权限一样,都有三个: public protected private
派生类时可以分四步
1 .吸收基类的成员 不论是数据成员,还是函数成员,除构造函数外全盘接收
2.改造基类成员 当父类有virtual,声明一个与基类成员同名,同参,同返回类型(返回基类指针除外)的新成员。当父类无virtual,申明一个与基类同名的函数,进行隐藏。
3.发展新成员 派生类新成员必须与基类成员不同名,它的加入保证派生类在功能上有所发展,独特的新成员才是继承与派生的核心特征
4.析构函数
无论是那种继承方式,基类继承的私有属性都无法访问
不论父类中的属性被啥修饰,都会被子类全盘接收
public,protected,private继承
1.通过public继承,基类里面public继承过来的,还是public,基类里面是protected,还是protected
2.通过protected继承,把基类中的public变成了protected
3.通过private继承,把基类汇总的public 和 protected 变成了private
private 继承和protected 继承都是类中可以访问,类外无法访问,这有什么区别呐?
通过private继承,可以把基类中的属性全部变成private,从而当子类继承时,自己类中从父类继承的属性全部无法访问
继承的内存布局
H类的内存布局
当出现多继承时,子类构建对象时,按照继承顺序,依次调用基类的构造函数
派生类创建对象时,每次都会默认调用基类的构造函数
在两个类(一个继承一个)中,如果出现相同的静态变量,虽然它俩都属于整个类,名字相同,但它们不会出现覆盖的情况,它们各种的作用域在各种的类中。
不想让子类继承的化话可以加final关键字
如果基类和子类有相同名称函数,那么在子类中,父类的该函数就会被同名隐藏起来了,
想调用的话,就必须加上作用域
就算是参数列表不同,也不能构成函数重载,必须显式的去调用
Y形问题:
调用的时候会出现二义性
显示的调用
菱形问题
调用的时候还会出现二义性
这样我们使用虚继承就可以了
当创建SofoBad对象时,先调用其默认的构造函数,因为它继承了其它的类,所以先调用基类构造函数,因为Sofo在Bad前面,所以先调用Sofo的构造函数,Sofo同样先调用Furuiture的构造函数,接着构造Furuiture的对象,然后构造出Sofo的对象,然后调用Bad的构造函数,同样也会先调用Furuiture的构造函数,不过因为之前已经调用过,所以不需要再次调用。
先组合,再继承
它的对象创建顺序,先按照父类对象成员属性中设计的顺序依次构造,接着构造父类对象,接着构造派生类对象。
先继承,再组合,同时有自己的私有属性
它的对象创建顺序,先按继承顺序构建父类对象,然后创建派生对象时,按照其成员属性的定义顺序创建对象。
继承时虚表的情况
如果一个类中有虚函数,则生成的该对象中会有一个虚指针,该指针指向一个虚表,该虚表中存储着所有虚函数的入口地址(此时这个表中没有存da函数的地址,因为它不是虚函数)
通过指针的转换调用虚函数
派生类继承基类的虚函数时,会将基类中的虚函数的地址原封不动的继承下来
当派生类重写基类中的方法后,派生类的虚表中对应的该函数将被重写,其它的函数不变
当派生类有虚函数时,会把自己的虚函数加入到从基类继承的虚函数的后面
当多继承时,派生类会把自己的虚函数加到第一个类的虚表指针后面
D从3个有虚函数的类继承下来,所以它里面有3个虚指针,
D类中的虚函数添加到了从A类继承下来的虚表中了
当基类里面有三个相同名称的虚函数,当派生类继承并改写时,会把三个虚表中的虚函数全部改了
公有继承:是一个
1.派生类的对象可以赋值给基类的对象(切片),这时是把派生类中从基类继承过来的隐藏对象赋值给基类对象,反过来不行,因为派生类的新成员无值可赋
2.可以将一个派生类的对象的地址赋值给基类对象,不过只能通过这个指针访问派生类中由基类继承过来的隐藏对象,不能访问派生类中的新成员。
3.派生类对象可以可以初始话基类的引用,但这个别名只能包含派生类对象中由基类继承过来的隐藏对象
基类只会继承父类的构造函数,不会继承拷贝构造
当使用基类对象拷贝构造另一个对象时,当创建父类时,并不会调用父类的拷贝构造,而是调用构造函数,当我们想要调用父类的拷贝构造时,需要自己显示调用
这种和上面的不一样,这种也是调用基类的构造,在构成作用域中,创建了一个基类匿名对象,然后析构了
同时,赋值重载也也一样,不过我们可以通过指针强制来解决
class Base
{
private:
int b_value;
public:
Base(int value = 0) :b_value(value) { std::cout << "Create Base" << std::endl; }
Base(const Base& base) :b_value(base.b_value) { std::cout << "Copy Base" << std::endl; }
Base& operator =(const Base& base)
{
if (this != &base)
{
b_value = base.b_value;
}
}
};
class Child :public Base
{private:
int c_value;
public:
Child(int value = 0) :c_value(value) { std::cout << "Create Chile" << std::endl; }
Child(const Child& child) :c_value(child.c_value)
{
Base(child);
std::cout << "Copy Child" << std::endl;
}
Child& operator =(const Child& child)
{
if (this != &child)
{
*(Base*)this = child;
c_value = child.c_value;
}
}
};
隐藏
非虚函数:只要子类与父类中的函数名相同
虚函数:子类中的函数与父类中函数的名字相同,参数不同,当子类中的函数与父类中函数名字,参数,返回值相同时则为重写。
注意
1.当派生类继承基类中的纯虚函数时,派生类只继承了基类中的函数名字,函数具体的实现是由派生类来实现的
2.假如给虚函数返回值写 return 0.0f;它实现了一部分,因此也不会强制派生类实现这个函数
3.不要同名隐藏基类中的非虚方法