文章目录
- 一、继承
- (一)概念
- (二)语法格式
- (三)通过子类访问父类中的成员
- 1. 类内
- 2. 类外
- (四)继承中的特殊成员函数
- 1. 构造函数
- 2. 析构函数
- 3. 拷贝构造函数
- 4. 拷贝赋值函数
- 二、多重继承
- (一)概念
- 1. 多重继承
- 2. 语法格式
- (二)使用示例
- 三、链式继承
- 四、扇形继承
- 五、菱形继承(钻石继承)和虚继承
- (一)概念
- (二)语法格式
- (三)使用示例
- (四)虚继承
一、继承
(一)概念
继承是C++的一种机制,用已知的类封装出的新的类,继承描述类与类之间的关系
作用:
为了代码的复用
为了实现多态的必备条件之一
继承中的访问控制权限:public private protected
A类继承B类:
A类叫做子类(派生类)
B类叫做父类(基类)
(二)语法格式
继承的语法格式
class 基类{};
class 子类:继承的权限 基类{};
- 注:
- 私有成员也可以被继承过来,但是私有成员被继承过来之后不能访问
- 公有继承:访问权限不变,只有私有的成员访问不到
私有继承:会将公有权限和受保护权限变为私有的,私有成员仍是私有成员,但是私有成员访问不到
受保护继承:会将公有权限和受保护权限变为受保护的,私有成员继承过来还是不可访问 - 一般继承方式都是
public
继承
私有成员和受保护成员是访问不到的,如果想要访问私有成员和受保护成员可以借助基类的公有成员的接口 - 如果不加继承控制权限,默认是
private
私有继承权限 - 通过子类访问父类中的成员:
在子类中访问父类中的成员:
父类名::成员变量名;
父类名::成员函数名;
在类外通过子类访问父类中的成员:
子类对象名.父类名::成员变量名;
子类对象名.父类名::成员函数名;
- 如果子类和父类中出现同名的成员变量名,不会起冲突;
如果在子类中访问同名成员变量,什么都不加,默认是通过this指针
(三)通过子类访问父类中的成员
1. 类内
Father :: Father_show();
2. 类外
son.Father::Father_show();
#include <iostream>
using namespace std;
class Father{
public:int a=10;
};
class son:public Father{
public:void show(void){cout<<"a="<<Father::a<<endl;}
};int main()
{son son1;son1.show();return 0;
}
(四)继承中的特殊成员函数
构造函数、析构函数、拷贝构造函数、拷贝赋值函数
1. 构造函数
构造函数在继承关系中不会被继承
如果在子类的构造函数中没有显式的调用父类的构造函数,编译器会默认先调用父类的无参的构造函数
如果在子类的构造函数中显式地调用父类的构造函数,会先调用父类的构造函数,再调用子类的构造函数
#include <iostream>
using namespace std;
//基类
class Base
{
public:Base(int a): value(a) {cout<<"base"<<endl;}int value = 100;~Base(){cout<<"~base"<<endl;}
};
//子类
class Son1: Base //说明不写访问控制权限,默认是private
{
public:Son1(int a): Base(a) {cout<<"son1"<<endl;}~Son1(){cout<<"~son1"<<endl;}
};
int main()
{Son1 son(10); //实例化对象son2return 0;
}
2. 析构函数
1.析构函数在继承关系中不会被继承
2.如果子类中没有显式的调用析构函数,编译器会使用子类的析构函数;
编译器也会默认调用父类的析构函数;
3.如果在子类中重写了子类的析构函数,父类中重写父类的析构函数
先调用子类的析构函数----再调用父类的析构函数
3. 拷贝构造函数
1.如果在子类中不显式的调用父类的拷贝构造函数,
会调用编译器提供的缺省的拷贝构造函数来完成对成员的拷贝操作;
提供的缺省的拷贝构造函数是一个浅拷贝;
2.如果在子类中显式的调用父类的拷贝构造函数,会调用自己写的拷贝构造函数;
想要调用父类的拷贝构造函数需要在子类的拷贝构造函数的初始化列表中显式的调用父类的拷贝构造函数;
eg:
//父类的指针或者是引用指向子类的对象—多态中讲
子类类名(const 子类类名 &obj): 父类类名(传参){}
3.如果在类中没有指针类型的成员,可以使用编译器提供的缺省的拷贝构造函数; //浅拷贝
如果在类中有指针类型的成员,需要自己实现深拷贝构造函数
#include <iostream>using namespace std;
class Base
{
public:Base(int a): data(a){cout << "Base(int a):data(a)" << endl;}~Base(void){cout << " ~Base(void)" << endl;}//拷贝构造函数Base(const Base &obj){cout << "Base(const Base &obj)" << endl;data = obj.data;}
private:int data;
};
class Son: public Base
{
public:Son(int a): Base(a){cout << "Son(int a):Base(a)" << endl;}~Son(void){cout << " ~Son(void)" << endl;}//子类的拷贝构造函数//父类的指针或者是引用指向子类的对象(多态中讲)Son(const Son &obj): Base(obj){cout << " Son(const Son &obj):Base(obj)" << endl;}
private:
};int main()
{//实例化对象//Son son1; //调用无参构造函数Son son1(10); //调用有参构造函数Son son2 = son1; // 调用拷贝构造函数return 0;
}
4. 拷贝赋值函数
1.如果在子类中不显式的调用父类的拷贝赋值函数,会调用编译器提供的缺省的拷贝赋值函数 来完成对成员的拷贝赋值操作;
默认提供的拷贝赋值函数是一个浅拷贝;
2.如果在子类中想要显式的调用父类的拷贝赋值函数,需要在子类的拷贝赋值函数中使用调用父类的拷贝赋值函数
格式:
父类::成员函数(传递参数); //需要在子类的拷贝赋值函数中写
3.如果在类中没有指针类型的成员,可以使用编译器提供的默认缺省的拷贝赋值函数
如果在类中有指针类型的成员,需要自己实现深拷贝赋值函数
#include <iostream>using namespace std;
class Base
{
public:Base(int a): data(a){cout << "Base(int a):data(a)" << endl;}~Base(void){cout << " ~Base(void)" << endl;}//拷贝赋值函数Base &operator =(const Base &obj){cout << "Base &operator =(const Base &obj)" << endl;if(this != &obj) {//拷贝赋值操作data = obj.data;}return *this;}
private:int data;
};
class Son: public Base
{
public:Son(int a): Base(a){cout << "Son(int a):Base(a)" << endl;}~Son(void){cout << " ~Son(void)" << endl;}//拷贝赋值函数Son &operator =(const Son &obj){cout << "Son &operator =(const Son &obj)" << endl;if(this != &obj) {//拷贝赋值函数//想要显式的使用父类的拷贝赋值函数//调用父类的拷贝赋值函数//父类的指针或者是引用指向子类对象(多态中讲)Base::operator =(obj);}return *this;}
private:
};int main()
{//实例化对象//Son son1; //调用无参构造函数Son son1(10); //调用有参构造函数Son son3(11);son1 = son3; //调用拷贝赋值函数return 0;
}
二、多重继承
(一)概念
1. 多重继承
一个子类继承于多个父类叫做多重继承
2. 语法格式
class 基类1{};
class 基类2{};
class 子类:public 基类1,public 基类2{};
实际开发中不建议使用多重继承,因为在不同的基类中包含相同名字的成员,容易出现歧义,如果不想出现歧义,需要通过子类访问父类中的成员;
格式:子类对象名.父类名::成员;
(二)使用示例
#include <iostream>
using namespace std;
class Base1{
public:int a=10;
};
class Base2{
public:int a=200;
};class son:public Base1,public Base2{
};int main()
{son son1;cout<<"Base1::a="<<son1.Base1::a<<" Base2::a="<<son1.Base2::a<<endl;return 0;
}
输出结果:
注意
如果此时直接调用son1中的a会报错,因为son1从base1和base2中都继承了a,所以当直接调用时,会产生歧义,导致编译器无法确定是打印从哪个基类继承的值
三、链式继承
链式继承中构造函数的初始化列表只需要关注自己继承的基类即可
#include <iostream>
using namespace std;
class Grandfather{
public:Grandfather(int aa):a(aa){cout<<"Grandfather 构造函数"<<endl;}~Grandfather(void){cout<<"Grandfather 析构函数"<<endl;}int a=10;
};class Father:public Grandfather{
public:Father(int aa,int bb):b(bb),Grandfather(aa){cout<<"Father 构造函数"<<endl;}~Father(void){cout<<"Father 析构函数"<<endl;}int b=20;
};class son:public Father{
public:son(int aa,int bb,int cc):c(cc),Father(aa,bb){cout<<"son 构造函数"<<endl;}~son(void){cout<<"son 析构函数"<<endl;}int c=30;
};int main()
{son son1(100,200,300);return 0;
}
输出结果:
四、扇形继承
扇形继承中子类的初始化列表需要把所有基类都考虑进去
#include <iostream>
using namespace std;
class Mother{
public:Mother(int aa):a(aa){cout<<"Mother 构造函数"<<endl;}~Mother(void){cout<<"Mother 析构函数"<<endl;}int a=10;
};class Father{
public:Father(int bb):b(bb){cout<<"Father 构造函数"<<endl;}~Father(void){cout<<"Father 析构函数"<<endl;}int b=20;
};class son:public Mother,public Father{
public:son(int aa,int bb,int cc):c(cc),Mother(aa),Father(bb){cout<<"son 构造函数"<<endl;}~son(void){cout<<"son 析构函数"<<endl;}int c=30;
};int main()
{son son1(100,200,300);return 0;
}
输出结果:
五、菱形继承(钻石继承)和虚继承
(一)概念
虚继承是一种机制,作用是就是为了解决钻石继承中继承多份父类数据的问题
虚继承共享同一份数据,虚继承会间接或是直接影响基类或者子类的数据
(二)语法格式
class 子类类名: virtual 继承方式 基类类名{}
(三)使用示例
#include <iostream>
using namespace std;
class Base{
public:Base(int b):base(b){cout<<"Base 构造函数"<<endl;}~Base(void){cout<<"Base 析构函数"<<endl;}int base=10000;
};class Mother:public Base{
public:Mother(int aa,int base):Base(base),a(aa){cout<<"Mother 构造函数"<<endl;}~Mother(void){cout<<"Mother 析构函数"<<endl;}int a=10;
};class Father:public Base{
public:Father(int bb,int base):Base(base),b(bb){cout<<"Father 构造函数"<<endl;}~Father(void){cout<<"Father 析构函数"<<endl;}int b=20;
};class son:public Mother,public Father{
public:son(int aa,int bb,int cc,int base):Mother(aa,base),Father(bb,base),c(cc){cout<<"son 构造函数"<<endl;}~son(void){cout<<"son 析构函数"<<endl;}int c=30;
};int main()
{son son1(100,200,300,1);return 0;
}
输出结果:
但是如果此时想要调用son1.base就会产生歧义,因为son1从两个父类中都分别继承了一个base,继承类继承了基类多次,从而产生了歧义
(四)虚继承
#include <iostream>
using namespace std;
//虚基类
class Base{
public:Base(int b):base(b){cout<<"Base 构造函数"<<endl;}~Base(void){cout<<"Base 析构函数"<<endl;}int base=10000;
};
//加上关键字virtual声明成虚基类
class Mother:virtual public Base{
public:Mother(int aa,int base):Base(base),a(aa){cout<<"Mother 构造函数"<<endl;}~Mother(void){cout<<"Mother 析构函数"<<endl;}int a=10;
};
//加上关键字virtual声明成虚基类
class Father:virtual public Base{
public:Father(int bb,int base):Base(base),b(bb){cout<<"Father 构造函数"<<endl;}~Father(void){cout<<"Father 析构函数"<<endl;}int b=20;
};//汇聚子类
class son:public Mother,public Father{
public:son(int aa,int bb,int cc,int base):Base(base),Mother(aa,base),Father(bb,base),c(cc){//虚基类的构造函数是由最底层的派生类直接调用的,而不是由每个直接派生类调用。 cout<<"son 构造函数"<<endl;}~son(void){cout<<"son 析构函数"<<endl;}int c=30;
};int main()
{son son1(100,200,300,1);cout<<son1.a<<endl;cout<<son1.b<<endl;cout<<son1.c<<endl;cout<<son1.base<<endl;return 0;
}
- 注:
- 此时基类Base被称作虚基类,子类son1称为汇聚子类
- 虚基类的构造函数是由最底层的派生类直接调用的,而不是由每个直接派生类调用。