# 继承 Inheritance
看什么视频,还是文档来得快
一些说法:
派生类 / 子类 / 子类 --("is-a" relationship)-> 基类 / 父类 / 超类。
子类继承父类,父类泛化子类。
派生类继承基类之后还可以添加属性与方法,不可删除基类属性与方法。子类包含父类中大部分成员。
基类的 ctor, dtor, cp ctor, friend 无法被继承。
- C++11 使用
using A::A;
继承类 A 所有 (cp) ctor,除了派生类中 显式定义过的 同参的 构造函数。 - 若基类的 ctor 未被显式调用,基类的默认构造函数会被调用。
- C++11 使用
C++11 引入
final
标识符,可以使类不能被继承。- 例如:
class B final{}; class D : public B {};
报错。
- 例如:
语法:
Class 派生类名 : (访问修饰符1) 基类1, (访问修饰符2) 基类2 ... { 派生类体 };
实例:
#include <iostream> | |
using namespace std; | |
class A | |
{ | |
public: | |
int d; | |
A(int d = 233) | |
{ | |
this -> d = d; | |
cout << "ctor_A" << endl; | |
} | |
}; | |
class B : public A | |
{ | |
public: | |
using A::A; | |
}; | |
class C : public A | |
{ | |
public: | |
int e{123}; | |
C(int y) : e{y} // 默认先调用 A () | |
{ | |
cout << "ctor_C" << endl; | |
} | |
}; | |
int main() | |
{ | |
B b; //ctor_A | |
cout << B{466}.d << endl; //ctor_A 466 | |
cout << b.d << endl; // 233 | |
C c{233};//ctor_A ctor_C | |
return 0; | |
} |
# 访问属性与派生方式
访问属性:
权限 | public | protected | private |
---|---|---|---|
类内 / 友元 | true | true | true |
派生类 | true | true | false |
外部 | true | false | false |
继承:
继承方式 | 基类成员 | 派生类成员函数 | 外部 |
---|---|---|---|
公有继承 | 在派生类访问属性不变 | 不能访问基类的私有成员 | 可以访问派生类公有成员 |
私有继承 | 派生类中都变成私有 | 不能访问基类的私有成员 | 不可访问派生类继承的成员 |
保护继承 | 公有变成保护,私有不变 | 不能访问基类的私有成员 | 不可访问派生类继承的成员 |
# 构造链与析构链
构造函数调用从父亲到儿子,析构函数调用从儿子到父亲。
实例:
#include <iostream> | |
using namespace std; | |
class A | |
{ | |
public: | |
A(){ cout << "ctor_A" << endl; } | |
~A(){ cout << "dtor_A" << endl; } | |
}; | |
class B : public A | |
{ | |
public: | |
B(){ cout << "ctor_B" << endl; } | |
~B(){ cout << "dtor_B" << endl; } | |
}; | |
class C : public B | |
{ | |
public: | |
C() { cout << "ctor_C" << endl; } | |
~C() { cout << "dtor_C" << endl; } | |
}; | |
int main() | |
{ | |
C c; | |
return 0; // 结束自动销毁 c | |
} | |
/* | |
ctor_A | |
ctor_B | |
ctor_C | |
dtor_C | |
dtor_B | |
dtor_A | |
*/ |
# 继承中同名相关问题
# 名字隐藏
派生类的函数会隐藏基类的同名函数。理解为作用域小的变量优先。
使用 using ParentName::ParentFunction
引入同名函数重载。
class P { | |
public: | |
void f() {} | |
}; | |
class C :public P { | |
public: | |
void f(int x) {} | |
using P::f; // 不加这句会在下面报错 | |
}; | |
int main() { | |
C c; | |
c.f(); // 就是这里 qwq | |
// 下面两种不用 using 的写法 | |
c.P::f(); | |
static_cast<P>(c).f(); | |
return 0; | |
} |
# 重定义函数
派生类中定义与基类 同名同参的函数,直接替代。
若要访问基类函数,前加 ParentName::
范围限定符。
#include <iostream> | |
using namespace std; | |
class P { | |
public: | |
void f() { cout << "P" << endl;} | |
}; | |
class C :public P { | |
public: | |
void f() { cout << "C" << endl;} | |
}; | |
int main() { | |
C c; | |
c.f(); // C | |
c.P::f(); // P | |
return 0; | |
} |
# 多态 Polymorphism
肽是 α-氨基酸以肽键连接在一起而形成的化合物,是蛋白质水解的中间产物。由三个或三个以上氨基酸分子组成的肽叫多肽。
不同类型的对象 对同一消息的 不同响应。
- 重载多态
- 子类型多态(重定义函数)
实现多态:通过联编 Binding :确定具有多态性的语句调用哪个函数的过程。
- 静态联编:编译时确定。
- 通过 派生类对象 / 基类对象指针 访问同名函数。(访问指针类型)
- 动态联编:运行时确定。(C++ 中多态主要是这个)
- 通过 基类对象的指针或引用 访问同名虚函数。(访问所指类型)
实例:
有对象 ABC,若使用 print () 输出信息,需要写三个重载函数。
# 虚函数
virtal
关键字,添加至正常函数之前变成虚函数,传参变成传递指针或引用。
运行时检查 指针所指的对象或所引用对象的类型(取决于对象真正的类而不是指针的类型) 决定调用哪个同名虚函数。用途可以使用父类指针访问子类对象成员。
可由继承传递,即基类添加 virtal 后派生类同名函数也自动变成虚函数。
当前类找不到虚函数时会沿继承链一直向父类寻找。
#include <iostream> | |
using namespace std; | |
class A | |
{ | |
public: | |
virtual string toString() { return "A"; } // 不加 virtual 则下方全是 A | |
}; | |
class B : public A | |
{ | |
public: | |
string toString() { return "B"; } // 写错函数名的话(即找不到虚函数),下方会输出 A | |
}; | |
class C : public B | |
{ | |
public: | |
string toString() { return "C"; } | |
}; | |
void print(A *o) // 等价于使用引用的写法 | |
{ | |
cout << o -> toString() << endl; | |
} | |
int main() | |
{ | |
A a; B b; C c; | |
print(&a); // A | |
print(&b); // B | |
print(&c); // C | |
return 0; | |
} |
# override 和 final
C++11 override
关键字,表示该函数为基类函数的复写,当函数名或参数与基类不同时会报错。
例如类 B 中: string toString() override {...}
。
override
只能在类内使用。
final
关键字 例如: string toString() {...} final;
阻止继续复写。( final
还可用于阻止继承)
# 抽象类与纯虚函数
沿继承链移动,基类越来越抽象,派生类越来越具体。
类太抽象以至于无法实例化(即无法创建对象)叫抽象类。
例如:在某个基类中声明一个函数(抽象函数 / 纯虚函数),只有在派生类中将其实现 才可以创建派生对象。
暂时不知道有多大用处,实例鸽了
# 动态类型转换
回收:Runoob_C++ 强制转换运算符
dynamic_cast
运算符:沿继承层级向上向下或侧向转换到类的指针和引用。
这里假定 Shape 类 是 Circle 类 的基类,比如现在有个
Shape *p = &s;
,其实它是个 Circle 。转指针
Circle *c = dynamic_cast<Circle*>(p);
失败返回 nullptr。转引用
Circle &c = dynamic_cast<Circle&>(s);
失败抛出异常。
向上转换可采用隐式转换,向下转换必须显式执行。派生类可截断,基类不能加长。
内存模型:
<img src = "https://s3.ax1x.com/2021/02/04/y3SxJ0.png" width = "500px">
# typeid 查询所属类
#include <iostream> | |
#include <typeinfo> | |
using namespace std; | |
class A{}; | |
int main() | |
{ | |
A a; | |
auto& t1 = typeid(a); // 自动推断要加上 & amp; | |
cout << t1.name() << endl; // 1A | |
return 0; | |
} |