1、类的继承:是新的类从已有类那里得到已有的特性,即从已有类产生新类的过程
原有类称为基类和父类,产生的新类称为派生类或子类
派生类的一般定义语法为:
class 派生类名:继承方式 基类1,继承方式 基类2,……,继承方式 基类n
{
派生类成员声明;
};
一个派生类可以同时有多个基类,这种情况称为多继承;只有一个直接基类的情况称为单继承。
在类族中,直接参与派生出某类的基类称为直接基类,基类的基类甚至更高层的基类称间接基类继承方式规定了如何访问从基类继承的成员(如果不显式地给出继承方式关键字,系统的默认值就是私有继承)。
从基类继承来的所有成员外,新增加的数据和函数成员】;在派生过程中构造函数和析构函数都不被继承。
如果派生类声明了一个与某基类成员函数同名的新成员,派生的新成员就隐藏了外层同名成员
2、当为公有继承时,基类的公有和保护成员的访问属性在派生类中不变,而基类的私有成员不可直接访问
当为私有继承时,基类中的公有成员和保护成员都以私有成员身份出现在派生类中,而基类的私有成员在派生类中不可被直接访问。经过私有继承后,所有基类的成员都成为派生类的私有成员或不可直接访问的成员,如果进一步派生,基类的全部成员就无法直接在新的派生类中被访问。因此,私有继承之后,基类的成员再也无法在以后的派生类中直接发挥作用,实际上是相当于终止了基类功能的继续派生。
保护继承中,基类的公有和保护都以保护成员的身份出现在派生类中,而基类的私有成员不可直接访问。
如果B公有继承A,B的成员函数只能通过B的对象访问A中定义的protected成员,而不能通过A的对象访问A的protected成员。
3、类型兼容规则是指在需要基类对象的任何地方,都可以使用公有派生类的对象来替代,替代包括:派生类的对象可以隐含转换为基类对象、派生类的对象可以初始化基类的引用、派生类的指针可以隐含转换为基类的指针。 在代替之后,派生类的对象就可以作为基类的对象使用,但只能使用从基类继承的成员。
例如:
class B{}
class D: public B{}
B b1,*pb1;
D d1;
这时:
(1)派生类对象隐含转换为基类对象,即用派生类对象中从基类继承来的成员,逐个赋值给基类对象的成员:
b1=d1;
(2)派生类的对象也可以初始化基类对象的引用
B &rb=d1;
(3)派生类对象的地址也可以隐含转换为指向基类的指针:
pb1=&d1;
(成员函数名相同时,优先访问基类的公有成员函数)
4、构造派生类的对象时,就要对基类的成员函数和新增成员对象进行初始化。基类的构造函数并没有继承下来,就必须给派生类添加新的构造函数。派生类要完成对基类成员函数的初始化工作,就需要调用基类的构造函数(派生类的构造函数以合适的初值作为参数,一些参数传递给基类的构造函数)
如果对基类初始化,需要调用基类的带有形参表的构造函数,派生类就必须声明构造函数。
在构造派生类的对象时,会首先调用基类的构造函数,然后按照构造函数初始化列表中指定的方式初始化派生类新增的成员对象,最后才执行派生类构造函数的函数体
派生类构造函数的一般语法形式为:
派生类名::派生类名(参数表):基类名1(基类1 初始化参数表),……,基类名n(基类n初始化参数表),成员对象名(成员对象1初始化参数表),……,成员对象名m(成员对象m初始化参数表),基本类型成员初始化
{
派生类构造函数其他初始化操作;
}
派生类构造函数执行的一般顺序为:
(1)调用基类构造函数,调用顺序按照他们被继承时声明的顺序(从左向右)
(2)对派生类新增的成员初始化,初始化顺序按照他们在类中声明的顺序
(3)执行派生类的构造函数中的内容
5、派生类构造函数举例
#include <iostream>
using namespace std;
class Base1{ //基类Base1,构造函数有参数
public:
Base(int i){cout<<Constructing Base1"<<I<<endl;
};
class Base2{ //Base2,构造函数有参数
public:
Base2(int j){cout<<"Constructing Base2"<<j<<endl;
};
class Base3{ //基类Base3,构造函数无参数
public:
Base3(){cout<<"Construting Base3 *"<<endl;
};
class Derived:public Base2,public Base1,public Base3{
//派生新类Derived,注意基类名的顺序
public:
Derived(int a, int b, int C,int d):Base1(a),member2(d),member1(c),Base2(b)
{}
//注意基类名的个数与顺序,注意成员对象名的个数与顺序
private:
Base1 member1;
Base2 member2;
Base3 member3;
};
int main(){
Derived obj(1,2,3,4};
return 0;
}
6、一个类只初始化它的直接基类,同样一个类也只继承直接基类的构造函数,派生类继承基类构造函数的方式时提供了一条注明了(直接)基类名的using声明语句。下面的例子定义了一个Derived类,令其继承Base类的构造函数:
class Derived:public Base{
public:
using Base::Base; //继承Base的构造函数
double d;
}
7、赋值构造函数
假设Derived类是Base类的派生类,则Derived类的复制构造函数形式如下;
Derived::Derived (const Derived &v):Base(v){. }
8、派生类析构函数举例
#include <iostream>
using namespace std;
class Base1{ //基类Base1,构造函数有参数
public:
Base1(int i){cout<<Constructing Base1"<<I<<endl;
~Base1(){cout<<"Destructing Base1"<<endl;
};
class Base2{ //Base2,构造函数有参数
public:
Base2(int j){cout<<"Constructing Base2"<<j<<endl;
~Base2(){cout<<"Destructing Base2"<<endl;
};
class Base3{ //基类Base3,构造函数无参数
public:
Base3(){cout<<"Construting Base3 *"<<endl;
~Base3(){cout<<"Destructing Base3"<<endl;
};
class Derived:public Base2,public Base1,public Base3{
//派生新类Derived,注意基类名的顺序
public:
Derived(int a, int b, int C,int d):Base1(a),member2(d),member1(c),Base2(b)
{}
//注意基类名的个数与顺序,注意成员对象名的个数与顺序
private:
Base1 member1;
Base2 member2;
Base3 member3;
};
int main(){
Derived obj(1,2,3,4};
return 0;
}
8、删除delete构造函数
class Base{
public:
Base()=default;
Base(string_info):info(std::move(_info)){}
Base(Base &)=delete; //删除复制构造函数
Base(Base &&)=delete; //删除移动构造函数
private:
string info;
};
class Derived:public Base{
};
Derived d1; //正确,合成了默认构造函数
Derived d2(d1); //错误,删除了复制构造函数
Derived d3(std::move(d1)); //错误,删除了移动构造函数
9、在派生类中,成员按访问属性分为4类:不可访问的成员(从基类私有成员继承而来)、私有成员、保护成员、公有成员。
如果某派生类的多个基类拥有同名的成员,同时,派生类又新增这样的同名成员,在这种情况下,派生类成员将隐藏所有基类的同名成员。
多继承同名隐藏举例(1):
#include <iostream>
using namespace std;
class Base1{
public:
int var;
void fun(){cout<<"Member of Base2"<<endl;}
};
class Base2{
public:
int var;
void fun(){cout<<"Member of Base2"<<endl;}
};
class Derived:public Base1,public Base2{
public:
int var; //同名数据成员
void fun(){cout<<"Member of Derived"<<endl;}//同名函数成员
};
int main(){
Derived d;
Derived *p=&d;
d.var=1; //对象名.成员名标识
d.fun(); //访问Derived类成员
d.Base1::var=2; //作用域分辨符标识
d.Base1::fun(); //访问Base1基类成员
p->Base::var=3; //作用域分辨符标识
p->Base::fun(); //访问Base2类基类成员
return 0;
}
point:如果希望d.var和d.fun()的用法不产生二义性,可以使用using关键字加以澄清,例如:
using Base1::var;
如果某个派生类的部分或全部直接基类是从另一个共同的基类派生而来,在这些直接基类中,从上一级基类继承来的成员也要使用作用域分辨符来唯一标识,而且必须用直接基类来进行限定
多继承同名隐藏举例(2):
#include <iostream>
using namespace std;
class Base0{
public:
int var0;
void fun0(){cout<<"Member of Bade0"<<endl;}
};
class Base1:pubblic Base0{
public:
int var1;
};
class Base2:public Base0{
public:
int var2;
};
class Derived:public Base1,public Base2{
public:
int var;
void.fun(){cout<<"Member of Derived"<<endl;}
};
int main(){
Derived d;
d.Base1::var0=2;
d.Base1::fun0();
d.Base2::var0=3;
d.Base2::fun0();
return 0;
}
10、我们可以使用作用域分辨符来唯一标识并分别访问他们,也可以将共同基类设置成虚基类,这时从不同路径继承过来的同名数据成员在内存中就只有一个,同一个函数名也只有一个映射。
虚基类的声明是在派生类的定义过程中进行的,语法为:
class 派生类名:vrtual 继承方式 基类名
(虚基类声明只是在类的派生过程中使用了virtual关键字,在程序主函数中,创建了一个派生类的对象d,通过成员名称就可以访问该类成员。)