學過c++的人都知道,c++的三大特性:封裝、繼承、多態。
我們今天說的是c++的繼承,那么為什么要引入繼承,它有什么特點呢?
首先,繼承的特點是:使代碼復用,為后面學習多態做鋪墊。
繼承分為:私有繼承(private)、公有繼承(public)、保護繼承(protected)。
分別舉例介紹一下它們各自的特性吧:
一、繼承
私有繼承:
//Inherit.h
#pragma once
#include<iostream>
using namespace std;class Base
{
public:Base(){}void FunTest(){cout<<_pri<<endl;}int _pub;
protected:int _pro;
private:int _pri;
};class Derived:private Base //私有繼承基類Base
{
public:Derived(){}void Function(){cout<<_pub<<endl;}
private:int _d;
};
//Inherit.cpp
#include"Inherit.h"int main()
{Base b;Derived d;d._pri = 10; //errord._pro = 20; //errord._pub = 30; //errord.Function();return 0;
}
通常我們把繼承自基類的類稱為派生類。
運行結果:
由上述結果可知,對于私有繼承下的派生類來說,在基類中的公有成員及成員函數繼承到派生類就變為私有,基類的保護也變為派生的私有,基類的私有則不繼承下去。當然私有繼承下的派生類中的成員及成員函數不能在類外被訪問。
保護繼承:
//Inherit.h
#pragma once
#include<iostream>
using namespace std;class Base
{
public:Base(){}void FunTest(){cout<<_pri<<endl;}int _pub;
protected:int _pro;
private:int _pri;
};class Derived:protected Base
{
public:Derived(){}void Function(){cout<<_pub<<endl;}
private:int _d;
};class Third:protected Derived
{
public:void F(){cout<<_pub<<endl;}
private:int _third;
};
//Inherit.cpp
#include"Inherit.h"int main()
{Base b;Derived d;Third t;d._pri = 10; //errord._pro = 20; //errord._pub = 30; //errord.Function();t.F();return 0;
}
運行結果:
由上述結果可知,對于保護繼承下的派生類來說,在基類的公有成員及成員函數繼承到派生類中就變為私有,基類的保護也變為派生的保護,基類的私有則不繼承下去。此時如果變為保護,則在有繼承關系的類與類之間可以互相訪問,而在類外不可訪問。
公有繼承:
//Inherit.h
#pragma once
#include<iostream>
using namespace std;class Base
{
public:Base(){}void FunTest(){cout<<_pri<<endl;}int _pub;
protected:int _pro;
private:int _pri;
};class Derived:public Base
{
public:Derived(){}void Function(){cout<<_pub<<endl;}
private:int _d;
};class Third:protected Derived
{
public:void F(){cout<<_pub<<endl;cout<<_pro<<endl;}
private:int _third;
};
//Inherit.cpp
#include"Inherit.h"int main()
{Base b;Derived d;Third t;d._pri = 10; //errord._pro = 20; //errord._pub = 30;d.Function();t.F();return 0;
}
運行結果:
由上述結果可知,對于公有繼承下的派生類來說,在基類的公有成員及成員函數繼承到派生類中仍為公有(即在類外也可訪問),基類的保護也變為派生的保護,基類的私有則不繼承下去。
下面拿一個表格來總結一下吧~
注:若類中省略以何種方式繼承時,默認為私有繼承。
二、調用順序
例1:
//Inherit.h
#pragma once
#include<iostream>
using namespace std;
class Base
{
public:Base(){cout<<"Base()"<<endl;}~Base(){cout<<"~Base()"<<endl;}
private:int _b;
};class Derived:public Base
{
public:Derived(){cout<<"Derived()"<<endl;}~Derived(){cout<<"~Derived()"<<endl;}
private:int _d;
};
//Inherit.cpp
#include"Inherit.h"
int main()
{Base b;Derived d;getchar();return 0;
}
運行結果:
調用順序分析:
Base b;創建一個基類的對象b,則調用基類的構造函數;Derived d;創建一個派生類對象d,則調用派生類的構造函數,此時這里沒有直接的調用派生類的構造函數,而是調用基類的構造函數,最后再調用派生類的構造函數。(在派生類對象調用構造函數時,是在派生類對象構造函數的初始化列表中調用了基類的構造函數)
如派生類構造函數原型為:
Derived():Base(){cout<<"Derived()"<<endl;}
例2:
//Inherit.h
#pragma once
#include<iostream>
using namespace std;class Base
{
public:Base(){cout<<"Base()"<<endl;}~Base(){cout<<"~Base()"<<endl;}
private:int _b;
};class C
{
public:C(){cout<<"C()"<<endl;}~C(){cout<<"~C()"<<endl;}
};
class Derived:public Base
{
public:Derived():Base(){cout<<"Derived()"<<endl;}~Derived(){cout<<"~Derived()"<<endl;}
private:C _c;
};
//Inherit.cpp
#include"Inherit.h"
int main()
{Base b;Derived d;getchar();return 0;
}
運行結果:
構造函數的調用次序:
初始化列表的構造函數-->成員變量的構造函數-->派生類的構造函數
三、繼承體系的作用域
要知道,繼承體系中基類和派生類是在兩個不同的作用域中。
類中有很多函數,那么當基類的成員函數和派生類的成員函數同名了怎么辦呢?它會調用哪個函數呢?
例:
//Inherit.h
#pragma once
#include<iostream>
using namespace std;
class Base
{
public:Base(){}void Fun(){cout<<"Base::Fun()"<<endl;}
protected:int _b;
};class Derived
{
public:Derived(){}void Fun(){cout<<"Derived::Fun()"<<endl;}
private:int _d;
};
//Inherit.cpp
#include"Inherit.h"
int main()
{Base b;Derived d;d.Fun();system("pause");return 0;
}
運行結果:
當用派生類的對象調用與基類同名的函數的時候,派生類會調用自己的成員函數。把基類的函數隱藏起來。這被稱作同名隱藏。(只要函數名相同,就會出現同名隱藏)
四、賦值兼容規則
關于賦值,大家再熟悉不過了吧。有同一類型的賦值,有不同類型的賦值(可能會用到強制類型轉換哦)。
這里,我們要掌握的是基類與派生類之間的賦值;
例:
1、派生類對象賦給基類對象;(切割、切片)
2、基類對象不能賦給派生類對象;
3、基類的指針或引用指向派生類對象;
4、派生類的指針或引用不能指向基類的對象。(可以通過強制類型轉換完成)
例:
//Inherit.h
#pragma once
#include<iostream>
using namespace std;
class A
{
public:A(){cout<<"A()"<<endl;}int _a;
};class B:public A
{
public:B(){cout<<"B()"<<endl;}int _b;
};
//Inherit.cpp
#include"Inherit.h"
int main()
{A a;B b;a = b;b = a; //error,無法將基類對象賦給派生類對象A *pA = NULL;B *pB = NULL;pA = pB;pB = pA; //error,無法讓派生類指針給<span style="font-family: Arial, Helvetica, sans-serif;">基</span><span style="font-family: Arial, Helvetica, sans-serif;">類指針</span>
A &aa = b;B &bb = a; //error,無法讓派生類的引用給基類對象return 0;
}
上述錯誤的賦值和指向可以通過強制類型轉化。
改:
五、幾種不能繼承的關系
1、友元關系不能繼承。
2、對于靜態成員及靜態成員函數,則整個繼承體系中只有一個這樣的成員。無論派生出多少個子類,都只有一個這樣的static成員實例。
例1:
//Inherit.h
#pragma once
#include<iostream>
using namespace std;
#include<string>
class Person
{friend void Display(Person &p,Student &s);
public:string p_name;
};
class Student:public Person
{
public:string s_no;
};void Display(Person &p,Student &s)
{cout<<p.p_name<<endl;cout<<s.p_name<<endl;cout<<s.s_no<<endl;
}
//Inherit.cpp
#include"Inherit.h"
int main()
{Person p;Student s;Display(p,s); //error,無法識別Studentreturn 0;
}
這里說明的就是友元關系不能繼承。
例2:
//Inherit.h
#pragma once
#include<iostream>
using namespace std;
#include<string>
class Person
{
public:Person(){cout<<"Person()"<<endl;++_count;}
protected:string _name;
public:static int _count;
};int Person::_count = 0;class Student:public Person
{
protected:int _num;
};class Graduate:public Student
{
protected:string _course;
};
//Inherit.cpp
#include"Inherit.h"
int main()
{Student s;Graduate g;cout<<Person::_count<<endl;cout<<Student::_count<<endl;Student::_count = 0;cout<<Student::_count<<endl;cout<<Person::_count<<endl;return 0;
}
運行結果:
注:友元函數和static成員函數都沒有this指針。
六、單繼承&多繼承
上述講述的都是單繼承的形式,那么多繼承是什么樣子的呢?
例:
class A
{
public:A(){}
protected:int _a;
};class B
{
public:B(){}
protected:int _b;
};class C:public A ,public B //實現多繼承,c既可以訪問A類的公有和保護成員,也可以訪問B類的公有和保護成員。
{
public:C(){}
protected:int _c;
}
七、菱形繼承(也稱鉆石繼承)
形如:
代碼如下:
Inherit.h
#pragma once
#include<iostream>
using namespace std;
class B
{
public:B(){}
protected:int _b;
};
class C1:public B
{
public:C1(){}
protected:int _c1;
};class C2:public B
{
public:C2(){}
protected:int _c2;
};class D:public C1,public C2
{
public:D(){}
private:int _d;
};
Inherit.cpp
#include"Inherit.h"
int main()
{B b;C1 c1;C2 c2;D d;cout<<sizeof(B)<<endl; //打印類B中成員,所以它的size為4cout<<sizeof(C1)<<endl; //打印類C1中的成員,并且繼承了類B,所以它的size為8cout<<sizeof(C2)<<endl; //打印類C2中的成員,并且繼承了類B,所以它的size為8cout<<sizeof(D)<<endl; //打印類D的成員,并且繼承了類C1,C2,所以它的size為20return 0;
}
運行結果:
內存中是這樣的形式出現的:
八、虛繼承
為了解決菱形繼承的二義性問題,又引出了虛繼承的概念。在內存中添加了一個地址,用來存放偏移量。雖然浪費了空間,但是解決了二義性問題。
就如上述菱形繼承一樣,通常,我們在用D d;創建一個對象之后,用派生類的對象調用基類的成員時,不知道到底是C1還是C2繼承下來的成員,這時就會出現二義性。為了解決上述問題的二義性,我們引出了虛繼承。
例:
Inherit.h
class B
{
public:B(){}
protected:int _b;
};class C1:virtual public B
{
public:C1(){}
protected:int _c1;
};class C2:virtual public B
{
public:C2(){}
protected:int _c2;
};class D:public C1,public C2
{
public:D(){}
private:int _d;
};
Inherit.cpp
int main()
{B b;C1 c1;C2 c2;D d;cout<<sizeof(B)<<endl; //只有一個成員,所以size為4cout<<sizeof(C1)<<endl; //類C1自己的成員和繼承自B的成員,還有一個存放偏移量的虛地址,size為12cout<<sizeof(C2)<<endl; //類C2自己的成員和繼承自B的成員,還有一個存放偏移量的虛地址,size為12cout<<sizeof(D)<<endl; //繼承自C1,C2,所以size為24return 0;
}
運行結果:
形式如圖所示:
繼承的內容就先說到這里啦,講的不詳細的還希望大家能給出建議哦。
歡迎大家來訪~~