本文已参与「新人创作礼」活动,一起开启掘金创作之路。
目录
1.继承的简介
类的继承是在现有类的基础之上,创建新类的机制。
称已经有的类为基类,新建立的类为派生类。
新建立的类的特点:
- 新的类继承了基类的属性和方法
- 新的类还可以增加新的属性和方法
2.单继承和多继承
继承分为单继承和多继承。
单继承格式:
class<派生类名>:[继承方式]<基类名>
{ 派生类成员声明; };
多继承格式:
class<派生类名>:[继承方式]<基类名> ,[继承方式]<基类名>......
{ 派生类成员声明; };
代码示例:
#include<bits/stdc++.h>
using namespace std;
class people
{
private:
int age;
public:
people(int a=0):age(a){}
};
class student:public people//公有继承
{
private:
int id;
public:
student(int a,int i):people(a),id(i){}
};
int main()
{
return 0;
}
上述代码中,student类继承了people类,所有来自people的数据都继承过来了,另外自己还可以声明其它的数据成员。
派生类成员指除了从基类继承来的所有成员(除构造函数和析构函数)之外,新增加的数据和函数成员。这些新增的成员正是派生类不同于基类的关键所在,是派生类对基类的发展。
下面是关于单继承的解释:
3.继承方式
三种继承方式:
- 公有继承public
- 私有继承private
- 保护继承protected
继承方式指定派生类成员以及类外对象对继承来的成员的访问权限。
派生类继承了基类的全部数据成员和除了构造、析构函数之外的全部函数成员,但是这些成员的访问属性在派生的过程中是可以调整的。从基类继承的成员,其访问属性由继承方式控制。
3.1.公有继承
- 基类的public和protected成员的访问属性在派生类中保持不变,但基类的private成员不可访问。
- 派生类中的成员函数可以直接访问基类中的public和protected成员,但不能访问基类的private成员。
- 派生类的对象只能访问基类的public成员。
- 基类的私有数据成员 不能在派生类中直接访问 但派生类对象建立私有数据空间
用图片来说明一下:
#include <bits/stdc++.h>
using namespace std;
class Base
{
public:
Base(int i) {id = i;}
int GetId() {id++;cout<< id<<endl;return id;}
protected:
int GetNum() {cout<< 0 <<endl;return 0;}
private:
int id;
};
class Child : public Base
{
public:
Child() : Base(7) {}
int GetCId() {return GetId();} //新增成员可以访问公有成员
int GetCNum() {return GetNum();} //新增成员可以访问保护成员
//无法访问基类的私有成员
protected:
int y;
private:
int x;
};
int main()
{
Child child;
child.GetId(); //派生类的对象可以访问派生类继承下来的公有成员
//child.GetNum(); //无法访问继承下来的保护成员GetNum()
child.GetCId();
child.GetCNum(); //派生类对象可以访问派生类的公有成员
//child.x;
//child.y; //无法访问派生类的保护成员y和私有成员x
return 0;
}
3.2.私有继承
- 基类的public和protected成员都以private身份出现在派生类中,但基类的private成员不可直接访问。
- 派生类中的成员函数可以直接访问基类中的public和protected成员,但不能访问基类的private成员。
- 派生类的对象不能访问基类中的任何成员。
class Child : private Base
{
public:
Child() : Base(7) {;}
int GetCId() {return GetId();} //可以访问基类的公有成员和保护成员
int GetCNum() {return GetNum();}
protected:
int y;
private:
int x;
};
int main()
{
Child child;
//child.GetId();//派生类对象访问不了继承的公有成员,因为此时私有继承时GetId()已经为 private类型
//child.GetNum(); //派生类对象访问不了继承的保护成员,而且此时私有继承时GetNum()已经为 private类型
child.GetCId();
child.GetCNum();
return 0;
}
3.3.保护继承
- 基类的public和protected成员都以protected身份出现在派生类中,但基类的private成员不可访问。
- 派生类中的成员函数可以直接访问基类中的public和protected成员,但不能访问基类的private成员。
- 派生类的对象不能访问基类中的任何成员.
class Child : protected Base
{
public:
Child() : Base(7) {}
int GetCId() {return GetId();} //可以访问基类的公有成员和保护成员
int GetCNum() {return GetNum();}
protected:
int y;
private:
int x;
};
int main()
{
Child child;
//child.GetId();//派生类对象访问不了继承的公有成员,因为此时保护继承时GetId()已经为 protected类型
//child.GetNum(); //这个也访问不了
child.GetCId();
child.GetCNum();
return 0;
}
4.派生类的构造函数和析构函数
4.1.构造函数
- 基类的构造函数不被继承,派生类中需要声明自己的构造函数。
- 声明构造函数时,只需要对本类中新增成员进行初始化,对继承来的基类成员的初始化,自动调用基类构造函数完成。
- 派生类的构造函数需要给基类的构造函数传递参数
4.2.析构函数
- 析构函数也不被继承,派生类自行声明
- 声明方法与一般(无继承关系时)类的析构函数相同。
- 不需要显式地调用基类的析构函数,系统会自动隐式调用。
- 析构函数的调用次序与构造函数相反。
#include<bits/stdc++.h>
using namespace std;
class student
{
private:
int age;
int id;
public:
student(int a,int i):age(a),id(i){}
};
class student1:public student
{
private:
string name;
public:
//使用初始化器对基类的成员变量进行初始化
student1(int a,int i,string n):student(a,i)
{
name = n;//派生类的变量的初始化
}
};
int main()
{
return 0;
}
4.3.有子对象的派生类的初始化
类的数据成员中可以包含类的对象,即对象中的对象,称为子对象(subobject),也称为组合类。
派生类构造函数的任务应该包括3个部分:
- 对基类数据成员初始化;
- 对子对象数据成员初始化;
- 对派生类数据成员初始化。
定义有子对象的派生类构造函数的一般形式为
派生类构造函数名(总参数表列): 基类构造函数名(参数表列),子对象名(参数表列)
{派生类中新增数成员据成员初始化语句}
执行派生类构造函数的顺序是:
① 调用基类构造函数,对基类数据成员初始化;
② 调用子对象构造函数,对子对象数据成员初始化;
③ 再执行派生类构造函数本身,对派生类数据成员初始化。
- 注意:
派生类构造函数中的基类构造函数和子对象的参数列表的次序可以是任意的,编译系统会根据参数名来确立它们的传递关系的。
#include<bits/stdc++.h>
using namespace std;
class student
{
protected:
int id;
int age;
public:
student(int i,int a):id(i),age(a){}
void print() {cout<<"id:"<<id<<' '<<"age:"<<age<<"\n\n";}
};
class student1:public student
{
protected:
string name;
student moniter;//定义子对象
public:
student1(int a,int i,string n):student(i,a),moniter(i,a){name = n;}
void display(){cout<<"name:"<<name<<"\nid:"<<id<<"\nage:"<<age<<'\n';}
void show_moniter()
{
moniter.print();//输出子对象的数据
}
};
int main()
{
student1 s(19,1,"xingmaqi");
s.print();
s.display();
s.show_moniter();
return 0;
}
5.多继承
- 构造函数调用的顺序是先执行基类的构造函数,再执行派生类自己的构造函数的函数体。
- 析构函数顺序和构造函数调用顺序相反
- 调用基类的构造函数的顺序并不是由初始化的书写顺序决定的,而是由定义派生类时的派生顺序决定的
5.1.多继承的二义性问题
当一个派生类有多个基类时,可能有些基类的成员会出现同名的成员,那么访问这些成员就会出现二义性的问题。
5.1.1.使用类名对成员加以限定
例如:
C1.A::f(); 或者C1.B::f();
c1继承的类中有多个同名成员,访问时添加类名限定,指明它是那个基类的,便可以成功访问。
5.1.2.使用虚基类
为解决二义性问题,将共同基类设置为虚基类,创建派生类对象时,虚基类的构造函数只会调用一次,虚基类的成员在第三层派生类对象中就只有一份拷贝,不会再引起二义性问题。
- 语法格式
class 派生类名: virtual 继承方式 基类名
{ //…… }
添加虚基类的情况:
class A
{
public:
void f();
private:
int a;
};
class B: virtual public A
{
protected:
int b;
};
class C: virtual public A
{
protected:
int c;
};
class D: public B, public C
{
public:
int g();
private:
int d;
};
示意图:
注意:
- 在多继承情况下,虚基类关键字的作用范围和继承方式关键字相同,只对紧随其后的基类起作用。
- 在多继承类结构中,说明虚基类之后,虚基类的成员在派生类中将不会因继承关系对虚基类的多次继承而形成多份拷贝,只为最远的派生类提供唯一的基类成员,消除了多继承结构中的二义性问题。
- 需要注意的是在第一级继承时就要将共同基类设计为虚基类。