【C++继承与派生】

继承与派生

(一)类的三种访问(控制)方式

  • private(class默认访问方式):仅定义该成员的类内部可以访问
  • protected:派生类可以访问,类外部不可以访问
  • public(struct默认访问方式):均可访问

(二)派生类的继承方式与访问控制:

派生类可以通过访问方式对基类成员的访问方式进行改造:

1)private(私有继承):

只有定义该成员的类内部可以访问,不允许派生类访问(不论是什么继承方式)。

2)protected(保护继承):

基类除了private成员,在派生类中的访问方式均为protected。

3)public(公有继承):

基类除了private成员,在派生类中的访问控制与基类一致。

(三)派生类生成的过程:

1)吸收基类成员

派生类会除了基类的构造函数、析构函数以外的所有数据成员和函数成员。

2)改造基类成员

通过访问方式,数据和函数成员的覆盖对基类成员进行改造。

3)添加新成员

尤其是添加构造函数和析构函数

(四)派生类的构造和析构函数

  1. 派生类没有继承基类的构造和析构函数
  2. 派生类必须对新增的成员初始化,还需要初始化基类的成员

1)派生类的构造函数定义格式:

派生类名(参数表):需要初始化的基类名(参数表),新增成员初始化列表 { 构造函数体 }

【PS】:若没有在派生类构造函数中显示初始化基类,则默认调用基类的无参构造函数(此时需基类有无参构造函数)

2)派生类构造函数的执行顺序:

1、调用基类的构造函数(按继承时说明从左到右的顺序,与初始化列表无关)
2、(如果有的话)调用子对象的构造函数(按类中说明的顺序)
3、执行派生类构造函数的函数体

执行析构函数的顺序与此正好相反

代码示例:

#include <iostream>
using namespace std;

class Base {
private:
    int bi; //私有成员
public:
    Base() {
        bi=0;
        cout<<"执行基类Base的构造函数\n";
    }
    ~Base() {
        cout<<"执行基类Base["<<bi<<"]的析构函数\n";
    }
};

class Base1 {
private:
    int bi1; //私有成员
public:
    Base1(int x) {
        bi1=x;
        cout<<"执行基类Base1的构造函数\n";
    }
    ~Base1() {
        cout<<"执行基类Base1["<<bi1<<"]的析构函数\n";
    }
};

class Base2 {
private:
    int bi2; //私有成员
public:
    Base2(int x) {
        bi2=x;
        cout<<"执行基类Base2的构造函数\n";
    }
    ~Base2() {
        cout<<"执行基类Base2["<<bi2<<"]的析构函数\n";
    }
};

class Myclass {
private:
    int mi; //私有成员
public:
    Myclass(int x) {
        mi=x;
        cout<<"执行普通类Myclass的构造函数\n";
    }
    ~Myclass() {
        cout<<"执行普通类Myclass["<<mi<<"]的析构函数\n";
    }
};

class Derived:public Base2,Base1,protected Base
{//注意:Base1(缺省)为私有继承,而不是公有继承!
private:
    int di;      //普通私有成员
    Myclass obj; //私有(对象)成员
public:
    Derived(int a,int b,int c,int d)
            :obj(b),Base1(c),Base2(d) {
        //Base没有在初始化表列出,所以自动调用Base的无参构造函数
        //如果没有,Base2(d)将导致错误(因为类Base2没有无参构造函数)
        di=a;
        cout<<"执行派生类Derived的构造函数\n";
    }
    ~Derived() {
        cout<<"执行派生类Derived["<<di<<"]的析构函数\n";
    }
};

int main()
{
    Derived dObj(1,2,3,4);
    cout<<endl;
}

/*
  该程序的输出为:
执行基类Base2的构造函数
执行基类Base1的构造函数
执行基类Base的构造函数
执行普通类Myclass的构造函数
执行派生类Derived的构造函数

执行派生类Derived[1]的析构函数
执行普通类Myclass[2]的析构函数
执行基类Base[0]的析构函数
执行基类Base1[3]的析构函数
执行基类Base2[4]的析构函数
*/

(五)派生类的拷贝构造函数

//派生类的拷贝构造函数的一般格式如下:
Derived(const Derived &d):Base(d)
{
  //用派生类的引用去初始化基类的引用合法!  //正常的派生类的拷贝构造函数体
}//Base:基类,Derived:派生类

(六)基类和派生类指针

  1. 派生类的成员函数将覆盖基类的所有同名成员函数(含重载的同名函数)。

    【注意1】:覆盖不是重载,可以通过**Base::show()**来限定。

    【注意2】:若是通过基类指针指向派生类,调用同名函数,则执行的基类的函数。(基类指针不知道派生类新增成员的存在)。

    • 这也引出了一个问题——【为什么要有虚函数?】:

      正是由于【注意点2】所说,基类指针只能调用基类成员函数,而不能调用派生类中的新增成员函数,如果使用虚函数,就能够实现用基类指针访问派生类的成员函数,基于这一点,派生类中的这个成员函数和基类的虚函数的形式要完全相同,并且在派生类中实现重写(一个接口多种方法)。

  2. 不能通过基类的对象(或指针)访问派生类新增的(共有)成员。

    【例1】:

    #include <iostream>
    using namespace std;
    
    class Base
    {
    	public:
    		void print(int a=0)
    		{
    			cout<<"Print in Base"<<endl;
    		}
    		
    		void showBase()
    		{
    			cout<<"Show in Base"<<endl;
    		} 
    };
    
    class Derived:public Base
    {
    	public:
    		void print()
    		{
    			cout<<"Print in Derived"<<endl;
    		}	
    		void showDerived()
    		{
    			cout<<"Show in Derived"<<endl;
    		}
    };
    
    int  main()
    {
    	Base *bp,bObj;
    	Derived *dp,dObj;
    	//分别用基类和派生类的对象调用各自的成员函数 
    	bObj.print();
    	//基类的print被派生类的覆盖 
    	dObj.print();
    	//dObj.print(1); 会报错(No Matching Function),派生类的同名函数会覆盖基类的函数(包括重载的函数)
    	dObj.showBase();	//在派生类中找不到,则调用基类的函数show 
    	
    	//用基类指针指向派生类
    	bp=&dObj;
    	bp->print();	//执行基类的print,基类指针并不知道派生类中的新增函数 
    	//bp->showDerived();	 报错,理由同上、
    	
    } 
    
    //输出结果如下:
    /* 
    Print in Base
    Print in Derived
    Show in Base
    Print in Base 
    
    */ 
    

  3. 派生类指针不能直接指向基类对象(编译报错),否则可能会使用派生类中的新增成员(在基类中是未定义的),即使是强制转化成基类指针也是调用派生类的函数。

    总之,派生类指针无法访问基类成员。

  4. 基类指针指向派生类是安全的,但是只能用来调用基类的成员。

    若要访问派生类的新增成员,需要进行强制转化。

    ((Derived *)bp)->print();	
    //调用的是派生的print(强制转化后,相当于是一个派生类指针,可以访问派生类成员函数)
    //外面的括号不能省略!!(优先级问题!!)
    
    

【例2】派生类和基类指针的相互转化

//派生类和基类指针的相互转化
#include <iostream>
using namespace std;

class Base
{
	public:
		void print()
		{
			cout<<"Print in Base"<<endl;
		}
		
};

class Derived:public Base
{
	public:
		void print()
		{
			cout<<"Print in Derived"<<endl;
		}	
		
		void show()
		{
			cout<<"Show in Derived"<<endl; 
		}
};

int  main()
{
	Base *bp,bObj;
	Derived *dp,dObj;
	//基类指针指向派生类:
	bp=&dObj;
	dp=(Derived *)bp;	//将基类指针强制转化为派生类指针 
	dp->print(); 		 
	
	//派生类指针指向基类---报错 
//	dp=&bObj; 	
    //将基类地址赋给派生类需要进行强制转化!!	
	dp=(Derived *)&bObj;
  	dp->print(); 
  	dp->show();
} 

/*
Print in Derived
Print in Derived
Show in Derived
*/

(七)虚基类

1)概念与声明方式

虚基类(在访问方式前加virtual)

在多层次继承中,若直接基类派生自同一个间接基类,会有两份的间接基类成员。

在虚基类机制下,虽然被一个派生类间接地多次继承,但派生类只继承一份该基类的成员。

2)虚基类下构造函数的执行顺序:

1、直接基类中的虚基类的构造函数在非虚基类之前调用
2、多个虚基类按继承时说明从左到右的顺序执行
3、若虚基类由非虚基类派生而来,则仍先调用基类构造函数,再按派生类中的构造函数的执行顺序调用
4、执行派生类构造函数的函数体

执行析构函数的顺序与此正好相反

3)初始化派生类

  • 对于非虚基类,在派生类的构造函数中初始化间接基类是不允许的;
  • 而对于虚基类,则必须在派生类中对虚基类初始化。

例如person是teacher和student的虚基类,则在派生类assistant的构造函数一定要初始化虚基类。

若person不是teacher和student的虚基类,则不允许派生类assistant初始化间接基类person。

注意:虚基类并不是在声明基类时声明的,而是在声明派生类是,指定继承方式时声明的。因为一个基类可以在生成一个派生类作为虚基类,而在生成另一个派生类时不作为虚基类。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值