對繼承的總結
一,繼承的基本概念
1.類與類之間的關係
• has-A,包含關係,用以描述一個類由多個“部件類”構成,實現has-A關係用類的成員屬性表示,即一個類的成員屬性是另一個已經定義好的類。
• use-A,一個類使用另一個類,通過類之間的成員函式相互聯絡,定義友元或者通過傳遞引數的方式來實現。
• is-A,即繼承關係,關係具有傳遞性。
2.繼承的相關概念
萬事萬物皆有繼承這個現象,所謂的繼承就是一個類繼承了另一個類的屬性和方法,這個新的類包含了上一個類的屬性和方法,被稱為子類或者派生類,被繼承的類稱為父類或者基類。
3.繼承的特點
• 子類擁有父類的所有屬性和方法(除了建構函式和解構函式)。
• 子類可以擁有父類沒有的屬性和方法。
• 子類是一種特殊的父類,可以用子類來代替父類。
• 子類物件可以當做父類物件使用。
4.繼承的語法
二,繼承中的訪問控制
1.繼承的訪問控制方式
我們在一個類中可以對成員變數和成員函式進行訪問控制,通過C++提供的三種許可權修飾符實現。在子類繼承父類時,C++提供的三種許可權修飾符也可以在繼承的時候使用,分別為公有繼承,保護繼承和私有繼承。這三種不同的繼承方式會改變子類對父類屬性和方法的訪問。
2.三種繼承方式對子類訪問的影響
• public繼承:父類成員在子類中保持原有的訪問級別(子類可以訪問public和protected)。
• private繼承:父類成員在子類中變為private成員(雖然此時父類的成員在子類中體現為private修飾,但是父類的public和protected是允許訪問的,因為是繼承後改為private)。
• protected繼承
o 父類中的public成員會變為protected級別。
o 父類中的protected成員依然為protected級別。
o 父類中的private成員依然為private級別。
• 注意:父類中的private成員依然存在於子類中,但是卻無法訪問到。不論何種方式繼承父類,子類都無法直接使用父類中的private成員。
3.父類如何設定子類的訪問
• 需要被外界訪問的成員設定為public。
• 只能在當前類中訪問設定為private。
• 只能在當前類和子類中訪問,設定為protected。
三,繼承中的構造和解構函式
1.父類的構造和析構
當建立一個物件和銷燬一個物件時,物件的建構函式和解構函式會相應的被C++編譯器呼叫。當在繼承中,父類的構造和解構函式是如何在子類中進行呼叫的呢,C++規定我們在子類物件構造時,需要呼叫父類的建構函式完成對對繼承而來的成員進行初始化,同理,在析構子類物件時,需要呼叫父類的解構函式對其繼承而來的成員進行析構。
2.父類中的構造和析構執行順序
• 子類物件在建立時,會先呼叫父類的建構函式,如果父類還存在父類,則先呼叫父類的父類的建構函式,依次往上推理即可。
• 父類建構函式執行結束後,執行子類的建構函式。
• 當父類的建構函式不是C++預設提供的,則需要在子類的每一個建構函式上使用初始化列表的方式呼叫父類的建構函式。
• 解構函式的呼叫順序和建構函式的順序相反。
3.繼承中的建構函式示例
# include<iostream>
using namespace std;
class Parent
{
protected:
char * str;
public:
Parent(char * str)
{
if (str != NULL)
{
this->str = new char[strlen(str) + 1];
strcpy(this->str, str);
}
else {
this->str = NULL;
}
cout << "父類建構函式..." << endl;
}
~Parent()
{
if (this->str != NULL)
{
delete[] this->str;
this->str = NULL;
}
cout << "父類解構函式..." << endl;
}
};
class Child:public Parent
{
private:
char * name;
int age;
public:
/* 在構造子類物件時,呼叫父類的建構函式 */
Child():Parent(NULL)
{
this->name = NULL;
this->age = 0;
cout << "子類無參建構函式..." << endl;
}
/* 在構造子類物件時,呼叫父類的建構函式 */
Child(char * name,int age):Parent(name)
{
this->name = new char[strlen(name) + 1];
strcpy(this->name, name);
cout << "子類有參建構函式..." << endl;
}
~Child()
{
if (this->name != NULL)
{
delete[] this->name;
this->name = NULL;
}
cout << "子類解構函式..." << endl;
}
};
int main()
{
Child c1;
Child c2("王剛",22);
return 0;
}
輸出結果:
4.繼承與組合情況混搭下的構造解構函式呼叫順序
• 建構函式:先呼叫父類的建構函式,再呼叫成員變數的建構函式,最後呼叫自己的建構函式。
• 解構函式:先呼叫自己的解構函式,再呼叫成員變數的解構函式,最後呼叫父類的解構函式。
5.繼承和組合情況混搭情況下的程式碼演示
# include<iostream>
using namespace std;
/* 定義父類 */
class Parent
{
protected:
char * str;
public:
Parent(char * str)
{
if (str != NULL)
{
this->str = new char[strlen(str) + 1];
strcpy(this->str, str);
}
else {
this->str = NULL;
}
cout << "父類建構函式..." << endl;
}
~Parent()
{
if (this->str != NULL)
{
delete[] this->str;
this->str = NULL;
}
cout << "父類解構函式..." << endl;
}
};
/* 定義Object類 */
class Object
{
private:
int i;
public:
Object(int i)
{
this->i = i;
cout << "Object的建構函式..." << endl;
}
~Object()
{
cout << "Object的解構函式..." << endl;
}
};
/* 定義子類 */
class Child:public Parent
{
private:
/* 定義類成員變數 */
Object obj;
char * name;
int age;
public:
/* 在構造子類物件時,呼叫父類的建構函式和呼叫成員變數的建構函式 */
Child():Parent(NULL),obj(10)
{
this->name = NULL;
this->age = 0;
cout << "子類無參建構函式..." << endl;
}
/* 在構造子類物件時,呼叫父類的建構函式和呼叫成員變數的建構函式 */
Child(char * name,int age):Parent(name),obj(age)
{
this->name = new char[strlen(name) + 1];
strcpy(this->name, name);
cout << "子類有參建構函式..." << endl;
}
~Child()
{
if (this->name != NULL)
{
delete[] this->name;
this->name = NULL;
}
cout << "子類解構函式..." << endl;
}
};
int main()
{
Child c1;
Child c2("王剛",22);
return 0;
}
輸出結果:
四,繼承中的型別相容性原則
1.繼承中的同名成員
當在繼承中,如果父類的成員和子類的成員屬性名稱相同,我們可以通過作用域操作符來顯式的使用父類的成員,如果我們不使用作用域操作符,預設使用的是子類的成員屬性。
2.繼承中的同名成員演示
# include<iostream>
using namespace std;
class PP
{
public:
int i;
};
class CC:public PP
{
public:
int i;
public:
void test()
{
/* 使用父類的同名成員 */
PP::i = 10;
/* 使用子類的同名成員 */
i = 100;
}
void print()
{
cout << "父類:" << PP::i << "," << "子類:" << i << endl;
}
};
int main()
{
CC cc;
cc.test();
cc.print();
return 0;
}
3.型別相容性原則
型別相容性原則是指在需要父類物件的所有地方,都可以用公有繼承的子類物件來替代。通過公有繼承,子類獲得了父類除構造和析構之外的所有屬性和方法,這樣子類就具有了父類的所有功能,凡是父類可以解決的問題,子類也一定可以解決。
4.型別相容性原則可以替代的情況
• 子類物件可以當做父類物件來使用。
• 子類物件可以直接賦值給父類物件。
• 子類物件可以直接初始化父類物件。
• 父類指標可以直接指向子類物件。
• 父類引用可以直接引用子類物件。
5.型別相容性原則示例
# include<iostream>
using namespace std;
/* 建立父類 */
class MyParent
{
protected:
char * name;
public:
MyParent()
{
name = "HelloWorld";
}
void print()
{
cout << "name = " << name << endl;
}
};
/* 建立子類 */
class MyChild:public MyParent
{
protected:
int i;
public:
MyChild()
{
i = 100;
name = "I am Child";
}
};
void main()
{
/* 定義子類物件 */
MyChild c;
/* 用子類物件當做父類物件使用 */
c.print();
/* 用子類物件初始化父類物件 */
MyParent p1 = c;
p1.print();
/* 父類指標直接指向子類物件 */
MyParent * p2 = &c;
p2->print();
/* 父類物件直接引用子類物件 */
MyParent& p3 = c;
p3.print();
}
6.繼承中的static
• 繼承中的static也遵循三種繼承的基本訪問控制原則。
• 繼承中的static可以通過類名和域作用符的方式訪問,也可以通過物件點的方式訪問。
7.繼承中的static演示
# include<iostream>
using namespace std;
/* 父類 */
class MyP
{
public:
static int i;
};
/* 初始化靜態成員 */
int MyP::i = 10;
/* 子類 */
class MyC:public MyP
{
public:
void test()
{
/* 直接訪問 */
cout << "i = " << i << endl;
/* 通過父類訪問 */
cout << "Myp::i = " << MyP::i << endl;
/* 通過子類訪問 */
cout << "MyC::i = " << MyC::i << endl;
}
void add()
{
i++;
}
};
int main()
{
MyC c;
c.add();
c.test();
MyC c1;
c1.add();
c1.test();
/* 通過子類物件訪問 */
c1.i = 100;
c1.test();
return 0;
}
五,多繼承
1.C++中的多繼承
所謂的多繼承就是指一個子類可以繼承多個父類,子類可以獲取多個父類的屬性和方法。這種繼承方式是不被推薦的,但是C++還是添加了,事實證明,多繼承增加了程式碼的複雜度,而且任何可以通過多繼承解決的問題都可以通過單繼承的方式解決。多繼承和單繼承的基本知識是相同的。不需要再闡述,主要講解下面的不同的地方。
2,多繼承中的構造和析構
和單繼承類似,還是首先執行父類的建構函式,此時有多個建構函式,則按照繼承時的父類順序來執行相應父類的建構函式,解構函式與此相反。
3.多繼承中的二義性
一個類A,它有兩個子類B1和B2,然後類C多繼承自B1和B2,此時如果我們使用類A裡面的屬性,則根據上面的多繼承的構造和析構,發現此時的類A會被創造兩個物件,一個是B1一個是B2,此時使用A裡面的屬性則會出現編譯器無法知道是使用B1的還是B2的。因此C++為我們提供了虛繼承這個概念,即B1和B2虛繼承自A,則在構造A物件的時候,只建立一個A的物件。
4.多繼承的二義性程式碼示例
此時如果去除virtual關鍵字,嘗試一下會報錯。
# include<iostream>
using namespace std;
/* 類B */
class B
{
public:
int a;
};
/* 虛繼承自類B */
class B1 :virtual public B
{
};
/* 虛繼承自類B */
class B2 :virtual public B
{
};
/* 繼承自B1,B2 */
class C :public B1, public B2
{
};
void main()
{
C c;
c.B::a = 100;
cout << c.B::a << endl;
cout << c.B1::a << endl;
cout << c.B2::a << endl;
}
注:以上均為轉載內容。