派生,为什么要派生?
派生的目的:
当新问题出现,原有的程序无法解决或者无法完全解决时,需要对原有的程序进行改造。
单继承时派生的定义:语法:class 派生类名:继承方式 基类名
继承方式:
继承方式存在三种,分别是公有继承,私有继承和保护继承。
公有继承:基类的public和protected成员访问属性在派生类中保持不变,基类的private成员不可直接访问。
私有继承:基类的public和protected成员以private身份出现在派生类中。
保护继承:基类的public和protected成员以protected身份出现在派生类中,基类的private成员不可直接访问。派生类中的成员函数可以直接访问基类中的public和protected成员,但不能访问基类的private成员。通过派生类的对象不能直接访问从基类继承的任何成员。
类型转换
公有派生类对象可以被当做基类的对象使用,反之不可。因为公有派生类继承了基类的东西,即派生类的内容比基类只多不少,基类的对象访问接口和派生类的一样,因此可以直接当做基类的对象使用。
派生类的对象可以隐含转化为基类对象
派生类的对象可以初始化基类的引用
派生类的指针可以隐含转化为基类的指针
通过基类的对象名,指针只能使用从基类继承的成员
派生类的构造函数
默认情况下:基类的构造函数不能被继承,派生类需要自己定义构造函数。当基类有默认构造函数时,派生类构造函数可以不向基类传递参数,创建派生类的对象时,基类的默认构造函数被调用。而派生类继承的需要初始参数的基类成员函数会由派生类传递参数给基类,再由基类的构造函数初始化这些成员函数,而派生类新增部分的初始化由函数体和初始化列表完成。
(c++11可以使用using语句继承基类的构造函数,但只能初始化从基类继承的成员:
using 基类名 ::基类构造函数名)
单继承时派生类构造函数语法:
派生类名::派生类名(基类初始化所需的形参,本类成员所需的形参):基类名(参数表),
本类成员初始化列表
{
其他初始化
}
如下Base2中的构造函数:
#include<iostream>
using namespace std;
class Base1{
public:
Base1(){
b=0;
cout<<"Base1 is constructed..."<<endl;
}
Base1(int i){
b=i;
cout<<"Base1 is constructed..."<<endl;
}
~Base1(){
cout<<"Base1 is destructed..."<<endl;
}
void print() const{
cout<<b<<endl;
}
private:
int b;
};
class Base2:public Base1{
public:
Base2(){
c=0;
cout<<"Base2 is constructed..."<<endl;
}
Base2(int i,int j):Base1(i),c(j){
cout<<"Base2 is constructed..."<<endl;
}
~Base2(){
cout<<"Base2 is destructed..."<<endl;
}
void print() const{
Base1::print();
cout<<c<<endl;
}
private:
int c;
};
int main(){
Base2 b(1,2);
b.print();
return 0;
}
继承时派生类将基类所需的初始化参数传递给基类进行初始化,然后初始化自己的列表。上述程序过程中,生成Base2的对象,由于有两个参数,因此调用有两个参数的构造函数Base2(int i,int j):Base1(i),c(j){},这个函数要向Base1的构造函数传递参数,因此先进行Base1的构造,运行结果如下:
由上述可知派生类构造函数的执行顺序:
1.调用基类的构造函数,顺序按照它们被继承时声明的顺序,先声明先构造。
2.对初始化列表中的成员进行初始化,顺序按照它们在类中定义的顺序,先定义先构造。
对象成员初始化时自动调用其所属类的构造函数,由初始化列表提供参数。
3.初始化派生类的构造函数体中的内容。
以下程序进行构造顺序的知识点加强:
#include<iostream>
using namespace std;
class Base1{
public:
Base1(int a){
cout<<"Base1 is constructed..."<<a<<endl;
}
};
class Base2{
public:
Base2(int a){
cout<<"Base2 is constructed..."<<a<<endl;
}
};
class Base3{
public:
Base3(){
cout<<"Base3 is constructed..."<<endl;
}
};
class Depre:public Base1,public Base2,public Base3{
public:
Depre(int a,int b,int c,int d):Base1(a),Base2(b),number1(c),number2(d){}
private:
Base1 number1;
Base2 number2;
Base3 number3;
};
int main(){
Depre d(5,2,6,7);
return 0;
}
运行结果:
派生类的析构函数
析构函数也不能被继承,派生类如果需要,则需要自行声明析构函数,声明方式与无继承关系时的析构函数相同。(不用显示调用基类的析构函数,系统会自动隐式调用)
析构的顺序与构造时正好相反。
构造时先构造基类继承过来成员,再构造自身函数体的内容,而析构时先析构本类自己的成员,然后再析构继承过来的成员(析构自己的成员时最后被构造的最先析构,析构继承的成员时,最后继承的最先被析构)
#include<iostream>
using namespace std;
class Base1{
public:
Base1(int a){
cout<<"Base1 is constructed..."<<a<<endl;
}
~Base1(){
cout<<"Base1 is destructed..."<<endl;
}
};
class Base2{
public:
Base2(int a){
cout<<"Base2 is constructed..."<<a<<endl;
}
~Base2(){
cout<<"Base2 is destructed..."<<endl;
}
};
class Base3{
public:
Base3(){
cout<<"Base3 is constructed..."<<endl;
}
~Base3(){
cout<<"Base3 is destructed..."<<endl;
}
};
class Depre:public Base1,public Base2,public Base3{
public:
Depre(int a,int b,int c,int d):Base1(a),Base2(b),number1(c),number2(d){}
private:
Base1 number1;
Base2 number2;
Base3 number3;
};
int main(){
Depre d(5,2,6,7);
return 0;
}
运行结果如下:
如上所示,先析构了自己本类的成员,最后构造的最先析构,按number3,number2,number1的顺序,再析构继承过来的成员,最后继承的最先析构,按Base3,Base2,Base1的顺序。
派生类的访问
当派生类中与基类有相同成员时:
若未特别限定,则通过派生类对象使用的是派生类中的同名成员(同名隐藏规则),即默认访问多个同名函数中所属派生类的那个。若要通过派生类对象访问基类中被隐藏的同名函数,可以使用基类名加作用域操作符::来限定。比如以下 d.Base1::fun();,实现调用基类被隐藏的fun()函数。
#include<iostream>
using namespace std;
class Base1{
public:
int var;
void fun(){
cout<<"number of Base1"<<endl;
}
};
class Base2{
public:
int var;
void fun(){
cout<<"number of Base2"<<endl;
}
};
class Derived:public Base1,public Base2{
public:
int var;
void fun(){
cout<<"number of Derived"<<endl;
}
};
int main(){
Derived d;
d.fun();//同名隐藏原则,默认调用派生类的同名函数
d.Base1::fun();
}
运行结果如下:
当派生类中无与基类有相同成员时:
同时从不同基类继承了同名成员时访问成员存在二义性问题,即继承之后,这些同名函数如何区分,如何访问成了问题。(可以采用用类名限定来解决二义性的问题)
class A{
public:
void q();
};
class B{
public:
void q();
void w();
};
class C:public A,public B{
public:
void w();
void e();
};
如果此时定义一个C c;,那么c.q();就存在二义性问题,而c.w()则无二义性(同名隐藏原则)。
编写程序的时候应该要避免出现二义性和冗余问题(同时多次继承同一个类出现数据重复等等诸类情况)。