C++快速讲解(七):继承和多态
前言:主要讲解了继承和多态的用法。
1.继承入门
#include <iostream>
#include <string>
using namespace std;
//父类
class Father{
public:
string name;
int age;
};
//子类
class Son: public Father{
};
int main() {
//子类虽然没有声明name 和 age ,但是继承了Father类,等同于自己定义的效果一样
Son s;
s.name = "张三";
s.age = 18;
cout << s.name << " = " << s.age << endl;
return 0;
}
2.public、protected、private继承
-
public:表示公有成员,该成员不仅可以在类内可以被访问,在类外也是可以被访问的,是类对外提供的可访问接口;
-
private:表示私有成员,该成员仅在类内可以被访问,在类的外面无法访问;
-
protected:表示保护成员,保护成员在类的外面同样是隐藏状态,无法访问。但是可以在子类中访问。
3.构造和析构
3.1 继承状态
-
子类对象在创建时会首先调用父类的构造函数;
-
父类构造函数执行完毕后,执行子类的构造函数;
-
当父类的构造函数中有参数时,必须在子类的初始化列表中显示调用;
-
析构函数执行的顺序是先调用子类的析构函数,再调用父类的析构函数
#include <iostream>
#include <string>
//构造和析构 继承状态
using namespace std;
class Father{
public:
string name;
int age;
Father(){
cout<<"调用了父类构造"<<endl;
}
~Father(){
cout<<"调用了父类析构"<<endl;
}
};
class Son: public Father{
public:
Son(){
cout<<"调用了子类构造"<<endl;
}
~Son(){
cout<<"调用了子类析构"<<endl;
}
};
int main() {
Son s;
return 0;
}
3.2 继承和组合
- 先调用父类的构造函数,再调用组合对象的构造函数,最后调用自己的构造函数;
- 先调用自己的析构函数,再调用组合对象的析构函数,最后调用父类的析构函数。
#include <iostream>
#include <string>
//构造和析构 继承和组合
using namespace std;
class Father{
public:
string name;
int age;
Father(){
cout<<"调用了父类构造"<<endl;
}
~Father(){
cout<<"调用了父类析构"<<endl;
}
};
class Other{
public:
Other(){
cout<<"调用了Other构造"<<endl;
}
~Other(){
cout<<"调用了Other析构"<<endl;
}
};
class Son: public Father{
public:
Son(){
cout<<"调用了子类构造"<<endl;
}
~Son(){
cout<<"调用了子类析构"<<endl;
}
Other o;
};
int main() {
Son s;
return 0;
}
3.3 调用父类有参构造
- 继承关系下,子类的默认构造函数会隐式调用父类的默认构造函数,假设父类没有默认的无参构造函数,那么子类需要使用参数初始化列表方式手动调用父类有参构造函数。
- 一般来说在创建子类对象前,就必须完成父类对象的创建工作,也就是在执行子类构造函数之前,必须先执行父类的构造函数。
#include <iostream>
#include <string>
//构造和析构 调用父类有参构造
using namespace std;
class Father{
private:
string name;
int age;
public:
Father(int age,string name){
cout<<"调用了父类构造"<<endl;
this->name = name;
this->age = age;
}
};
class Son: public Father{
//子类只能使用初始化列表的方式来访问父类构造
public:
Son(int age,string name):Father(age,name){
cout<<"调用了子类构造"<<endl;
}
};
int main() {
Son s(20,"fly");
return 0;
}
3.4 初始化列表
常量和引用的情况
#include <iostream>
#include <string>
//常量和引用的情况
using namespace std;
class Father{
public:
const string name;
int &age;
Father(string name,int age):name(name),age(age){
cout<<"调用了构造"<<endl;
}
};
int main() {
Father f("fly",20);
cout << f.name << " = " << f.age << endl;
return 0;
}
初始化对象成员
#include <iostream>
#include <string>
using namespace std;
class Father{
public:
int age;
Father(int age):age(age){
cout<<"调用了父类构造"<<endl;
}
};
class Son{
public:
Father f;
Son():f(10){
cout<<"调用了Son类构造"<<endl;
}
};
int main() {
Son s;
cout<<s.f.age<<endl;
return 0;
}
4.重写父类同名函数
#include <iostream>
#include <string>
//重写父类同名函数
using namespace std;
class Father{
public:
void smoke(){
cout<<"抽普通烟"<<endl;
}
};
class Son: public Father{
public:
void smoke(){
Father::smoke();
cout<<"抽高级烟"<<endl;
}
};
int main() {
Son s;
s.smoke();
return 0;
}
5.多重继承
#include <iostream>
#include <string>
//多重继承
using namespace std;
class Father{
public:
void makeMoeny(){
cout << "赚钱" << endl;
}
};
class Mother{
public:
void makeHomeWork(){
cout << "做家务活" << endl;
}
};
class Son:public Father , public Mother{
};
int main(){
Son s ;
s.makeMoeny();
s.makeHomeWork();
return 0 ;
}
6.类的前置声明
#include <iostream>
#include <string>
//类的前置声明
using namespace std;
class father; //所有前置声明的类,在某个类中定义的时候,只能定义成引用或者指针。
class son{
public:
//father f0; //因为这行代码,单独拿出来说,会执行B类的无参构造,
//但是编译器到此处的时候,还不知道B这个类的构造长什么样。
father &f1;
father *f2;
son(father &f1 , father *f2):f1(f1),f2(f2){
}
};
class father{
};
int main(){
// father b; //---> 执行B的构造函数。
father f1;
father f2;
son s(f1 ,&f2);
return 0 ;
}
7.多态
7.1 静态多态
静态多态是编译器在编译期间完成的,编译器会根据实参类型来选择调用合适的函数,如果有合适的函数可以调用就调,没有的话就会发出警告或者报错 。
#include <iostream>
#include <string>
//静态多态
using namespace std;
int Add(int m, int n){
return m+n;
}
double Add(double m, double n){
return m+n;
}
int main() {
Add(10,20);
Add(10.2,20.2);
return 0;
}
7.2 动态多态
它是在程序运行时根据父类的引用(指针)指向的对象来确定自己具体该调用哪一个类的虚函数。
#include <iostream>
#include <string>
using namespace std;
class Father{
public:
virtual void smoke(){ //加上virtual
cout << "father smoke" << endl;
}
};
class Son : public Father{
public:
void smoke(){
cout << "son smoke" << endl;
}
};
int main() {
Father f;
f.smoke(); //父亲的抽烟
Son s ;
s.smoke(); //孩子的抽烟
Father *f2 = new Son();//父类指针指向子类对象
f2->smoke();
return 0;
}
8.联编机制
8.1 静态类型和动态类型
- 静态类型:不需要运行,编译状态下即可知晓具体的类型
- 动态类型:只有真正运行代码了,才能知晓具体的类型
8.2 父类指针指向子类对象
#include<iostream>
using namespace std;
class father{
public:
void show(){
cout << "father show" << endl;
}
};
class children : public father{
public:
void show(){
cout << "children show" << endl;
}
};
int main(){
father f = children();
f.show(); // 打印father show
return 0 ;
}
8.3 静态联编和动态联编
#include<iostream>
using namespace std;
class WashMachine{
public:
void wash(){
cout << "洗衣机在洗衣服" << endl;
}
};
class SmartWashMachine : public WashMachine{
public:
void wash(){
cout << "智能洗衣机在洗衣服" << endl;
}
};
int main(){
WashMachine *w1= new WashMachine(); //父类指针指向父类对象 打印:洗衣机在洗衣服
w1->wash();
SmartWashMachine *s = new SmartWashMachine(); //子类指针指向子类对象 打印: 智能洗衣机...
s->wash();
WashMachine *w2 = new SmartWashMachine(); //父类指针指向子类对象 打印..洗衣机在洗衣服
w2->wash();
return 0 ;
}
9.虚函数
9.1 虚函数入门
#include <iostream>
#include <string>
//虚函数
using namespace std;
class WashMachine{
public:
virtual void wash(){
cout << "洗衣机在洗衣服" << endl;
}
};
class SmartWashMachine : public WashMachine{
public:
virtual void wash(){
cout << "智能洗衣机在洗衣服" << endl;
}
};
int main(){
WashMachine *w2 = new SmartWashMachine(); //父类指针指向子类对象 打印..洗衣机在洗衣服
w2->wash();
return 0 ;
}
9.2 虚函数的工作原理
- 通常情况下,编译器处理虚函数的方法是: 给每一个对象添加一个隐藏指针成员,它指向一个数组,数组里面存放着对象中所有函数的地址。这个数组称之为虚函数表(virtual function table ) 。表中存储着类对象的虚函数地址。
- 父类对象包含的指针,指向父类的虚函数表地址,子类对象包含的指针,指向子类的虚函数表地址。
- 如果子类重新定义了父类的函数,那么函数表中存放的是新的地址,如果子类没有重新定义,那么表中存放的是父类的函数地址。
9.3 构造函数不可以是虚函数
构造函数不能为虚函数 , 因为虚函数的调用,需要虚函数表(指针),而该指针存在于对象开辟的空间中,而对象的空间开辟依赖构造函数的执行。
9.4 析构函数可以是虚函数
#include <iostream>
#include <string>
using namespace std;
class father{
public:
virtual ~father(){
cout << "执行父类析构函数" << endl;
}
};
class son : public father{
~son(){
cout << "执行子类析构函数" << endl;
}
};
int main(){
father *f = new son(); //父类指针指向子类对象
//创建的是子类对象,理应执行子类的析构函数
delete f;
return 0 ;
}
10.override 关键字
#include <iostream>
#include <string>
using namespace std;
class father{
public:
virtual void run(){
cout << "父亲在跑步" << endl;
}
};
class son : public father{
public:
virtual void run() override{ //表示重写父类的函数
cout << "孩子在跑步" << endl;
}
};
int main(){
father *f = new son(); //父类指针指向子类对象
return 0 ;
}
11.final 关键字
#include <iostream>
#include <string>
using namespace std;
class person final{ //表示该类是最终类,无法被继承
virtual void run() final{ //表示该方法时最终方法,无法被重写
}
};
//编译错误,无法被继承。
class student : public person{
//编译错误,方法无法被重写。
void run(){
}
};
int main(){
return 0 ;
}
12.=delete 和 =default
#include <iostream>
#include <string>
using namespace std;
class stu{
string name;
int age;
public:
stu(string name , int age):name(name) , age(age){
cout << "执行stu的构造函数" << endl;
}
//表示禁止调用拷贝构造函数
stu(stu & s) =delete;
~stu(){
cout << "执行stu的析构函数" << endl;
}
};
int main(){
stu s("张三",18) ;
//编译错误。
stu s1= s;
return 0 ;
}
#include <iostream>
#include <string>
using namespace std;
class stu{
string name;
int age;
public:
stu() = default;
stu(string name , int age):name(name) , age(age){
cout << "执行stu的构造函数" << endl;
}
~stu(){
cout << "执行stu的析构函数" << endl;
}
};
int main(){
stu s("张三",18) ;
//编译错误。
stu s1 = s;
return 0 ;
}
13.纯虚函数
#include <iostream>
#include <string>
//纯虚函数
using namespace std;
class WashMachine{
public:
//没有函数体,表示洗衣机能洗衣服,但是具体怎么洗,每个品牌不一样
virtual void wash() = 0;
};
class HaierMachine:public WashMachine{
public :
virtual void wash(){
cout << "海尔牌洗衣机在洗衣服" << endl;
}
};
class LittleSwanMachine:public WashMachine{
public :
virtual void wash(){
cout << "小天鹅洗衣机在洗衣服" << endl;
}
};
int main(){
//WashMachine w; 错误,抽象类无法创建对象
WashMachine *w1 = new HaierMachine() ;
WashMachine *w2 = new LittleSwanMachine() ;
return 0 ;
}
14.抽象类和接口
所谓接口,其实就是用于描述行为和功能,并不会给出具体的实现。C++中没有提供类似interface 这样的关键字来定义接口 , 纯虚函数往往承担起了这部分功能,可以看成是对子类的一种约束。
例如:
class Person{
Person() =default; // 可以用于初始化成员函数
virtual ~Person()=default; //防止子类析构函数无法被调用问题
//每个人吃什么,做什么都不一样,,即可声明为纯虚函数
virtual void eat() = 0 ;
virtual void work() = 0 ;
//...
};
结束!!!