1. 程式人生 > 實用技巧 >C++——繼承

C++——繼承

繼承

繼承是面向物件三大特性之一

有些類與類之間存在特殊的關係,例如下圖中:

我們發現,定義這些類時,下級別的成員除了擁有上一級的共性,還有自己的特性。

這個時候我們就可以考慮利用繼承的技術,減少重複程式碼

1 繼承的基本語法

例如我們看到很多網站中,都有公共的頭部,公共的底部,甚至公共的左側列表,只有中心內容不同

接下來我們分別利用普通寫法和繼承的寫法來實現網頁中的內容,看一下繼承存在的意義以及好處

普通實現:

//Java頁面
class Java 
{
public:
	void header()
	{
		cout << "首頁、公開課、登入、註冊...(公共頭部)" << endl;
	}
	void footer()
	{
		cout << "幫助中心、交流合作、站內地圖...(公共底部)" << endl;
	}
	void left()
	{
		cout << "Java,Python,C++...(公共分類列表)" << endl;
	}
	void content()
	{
		cout << "JAVA學科視訊" << endl;
	}
};
//Python頁面
class Python
{
public:
	void header()
	{
		cout << "首頁、公開課、登入、註冊...(公共頭部)" << endl;
	}
	void footer()
	{
		cout << "幫助中心、交流合作、站內地圖...(公共底部)" << endl;
	}
	void left()
	{
		cout << "Java,Python,C++...(公共分類列表)" << endl;
	}
	void content()
	{
		cout << "Python學科視訊" << endl;
	}
};
//C++頁面
class CPP 
{
public:
	void header()
	{
		cout << "首頁、公開課、登入、註冊...(公共頭部)" << endl;
	}
	void footer()
	{
		cout << "幫助中心、交流合作、站內地圖...(公共底部)" << endl;
	}
	void left()
	{
		cout << "Java,Python,C++...(公共分類列表)" << endl;
	}
	void content()
	{
		cout << "C++學科視訊" << endl;
	}
};

void test01()
{
	//Java頁面
	cout << "Java下載視訊頁面如下: " << endl;
	Java ja;
	ja.header();
	ja.footer();
	ja.left();
	ja.content();
	cout << "--------------------" << endl;

	//Python頁面
	cout << "Python下載視訊頁面如下: " << endl;
	Python py;
	py.header();
	py.footer();
	py.left();
	py.content();
	cout << "--------------------" << endl;

	//C++頁面
	cout << "C++下載視訊頁面如下: " << endl;
	CPP cp;
	cp.header();
	cp.footer();
	cp.left();
	cp.content();

}

int main() {

	test01();

	system("pause");

	return 0;
}

繼承實現:

//公共頁面
class BasePage
{
public:
	void header()
	{
		cout << "首頁、公開課、登入、註冊...(公共頭部)" << endl;
	}

	void footer()
	{
		cout << "幫助中心、交流合作、站內地圖...(公共底部)" << endl;
	}
	void left()
	{
		cout << "Java,Python,C++...(公共分類列表)" << endl;
	}

};

//Java頁面
class Java : public BasePage
{
public:
	void content()
	{
		cout << "JAVA學科視訊" << endl;
	}
};
//Python頁面
class Python : public BasePage
{
public:
	void content()
	{
		cout << "Python學科視訊" << endl;
	}
};
//C++頁面
class CPP : public BasePage
{
public:
	void content()
	{
		cout << "C++學科視訊" << endl;
	}
};

void test01()
{
	//Java頁面
	cout << "Java下載視訊頁面如下: " << endl;
	Java ja;
	ja.header();
	ja.footer();
	ja.left();
	ja.content();
	cout << "--------------------" << endl;

	//Python頁面
	cout << "Python下載視訊頁面如下: " << endl;
	Python py;
	py.header();
	py.footer();
	py.left();
	py.content();
	cout << "--------------------" << endl;

	//C++頁面
	cout << "C++下載視訊頁面如下: " << endl;
	CPP cp;
	cp.header();
	cp.footer();
	cp.left();
	cp.content();


}

int main() {

	test01();

	system("pause");

	return 0;
}

總結:

繼承的好處:可以減少重複的程式碼

class A : public B;

A 類稱為子類 或 派生類

B 類稱為父類 或 基類

派生類中的成員,包含兩大部分

一類是從基類繼承過來的,一類是自己增加的成員。

從基類繼承過過來的表現其共性,而新增的成員體現了其個性。

2 繼承方式

繼承的語法:class 子類 : 繼承方式 父類

繼承方式一共有三種:

  • 公共繼承
  • 保護繼承
  • 私有繼承

示例:

class Base1
{
public: 
	int m_A;
protected:
	int m_B;
private:
	int m_C;
};

//公共繼承
class Son1 :public Base1
{
public:
	void func()
	{
		m_A; //可訪問 public許可權
		m_B; //可訪問 protected許可權
		//m_C; //不可訪問
	}
};

void myClass()
{
	Son1 s1;
	s1.m_A; //其他類只能訪問到公共許可權
}

//保護繼承
class Base2
{
public:
	int m_A;
protected:
	int m_B;
private:
	int m_C;
};
class Son2:protected Base2
{
public:
	void func()
	{
		m_A; //可訪問 protected許可權
		m_B; //可訪問 protected許可權
		//m_C; //不可訪問
	}
};
void myClass2()
{
	Son2 s;
	//s.m_A; //不可訪問
}

//私有繼承
class Base3
{
public:
	int m_A;
protected:
	int m_B;
private:
	int m_C;
};
class Son3:private Base3
{
public:
	void func()
	{
		m_A; //可訪問 private許可權
		m_B; //可訪問 private許可權
		//m_C; //不可訪問
	}
};
class GrandSon3 :public Son3
{
public:
	void func()
	{
		//Son3是私有繼承,所以繼承Son3的屬性在GrandSon3中都無法訪問到
		//m_A;
		//m_B;
		//m_C;
	}
};

3 繼承中的物件模型

問題:從父類繼承過來的成員,哪些屬於子類物件中?

示例:

class Base
{
public:
	int m_A;
protected:
	int m_B;
private:
	int m_C; //私有成員只是被隱藏了,但是還是會繼承下去
};

//公共繼承
class Son :public Base
{
public:
	int m_D;
};

void test01()
{
    //父類中所以非靜態成員屬性都會被子類繼承下去
    //父類中私有成員屬性是被編譯器給隱藏,因此訪問不到
    //但是確實被繼承下去了
	cout << "sizeof Son = " << sizeof(Son) << endl;
}

int main() {

	test01();

	system("pause");

	return 0;
}

利用工具檢視:

開啟工具視窗後,定位到當前CPP檔案的碟符

然後輸入: cl /d1 reportSingleClassLayout檢視的類名 所屬檔名

效果如下圖:

結論: 父類中私有成員也是被子類繼承下去了,只是由編譯器給隱藏後訪問不到

4 繼承中構造和析構順序

子類繼承父類後,當建立子類物件,也會呼叫父類的建構函式

問題:父類和子類的構造和析構順序是誰先誰後?

示例:

class Base 
{
public:
	Base()
	{
		cout << "Base建構函式!" << endl;
	}
	~Base()
	{
		cout << "Base解構函式!" << endl;
	}
};

class Son : public Base
{
public:
	Son()
	{
		cout << "Son建構函式!" << endl;
	}
	~Son()
	{
		cout << "Son解構函式!" << endl;
	}

};


void test01()
{
	//繼承中 先呼叫父類建構函式,再呼叫子類建構函式,析構順序與構造相反
	Son s;
}

int main() {

	test01();

	system("pause");

	return 0;
}

總結:繼承中 先呼叫父類建構函式,再呼叫子類建構函式,析構順序與構造相反

5 繼承同名成員處理方式

問題:當子類與父類出現同名的成員,如何通過子類物件,訪問到子類或父類中同名的資料呢?

  • 訪問子類同名成員 直接訪問即可
  • 訪問父類同名成員 需要加作用域

示例:

class Base {
public:
	Base()
	{
		m_A = 100;
	}

	void func()
	{
		cout << "Base - func()呼叫" << endl;
	}

	void func(int a)
	{
		cout << "Base - func(int a)呼叫" << endl;
	}

public:
	int m_A;
};


class Son : public Base {
public:
	Son()
	{
		m_A = 200;
	}

	//當子類與父類擁有同名的成員函式,子類會隱藏父類中所有版本的同名成員函式
	//如果想訪問父類中被隱藏的同名成員函式,需要加父類的作用域
	void func()
	{
		cout << "Son - func()呼叫" << endl;
	}
public:
	int m_A;
};

void test01()
{
	Son s;

	cout << "Son下的m_A = " << s.m_A << endl;
	cout << "Base下的m_A = " << s.Base::m_A << endl;

	s.func();
	s.Base::func();
	s.Base::func(10);

}
int main() {

	test01();

	system("pause");
	return EXIT_SUCCESS;
}

總結:

  1. 子類物件可以直接訪問到子類中同名成員
  2. 子類物件加作用域可以訪問到父類同名成員
  3. 當子類與父類擁有同名的成員函式,子類會隱藏父類中同名成員函式,加作用域可以訪問到父類中同名函式

6 繼承同名靜態成員處理方式

問題:繼承中同名的靜態成員在子類物件上如何進行訪問?

靜態成員和非靜態成員出現同名,處理方式一致

  • 訪問子類同名成員 直接訪問即可
  • 訪問父類同名成員 需要加作用域

示例:

class Base {
public:
	static void func()
	{
		cout << "Base - static void func()" << endl;
	}
	static void func(int a)
	{
		cout << "Base - static void func(int a)" << endl;
	}

	static int m_A;
};

int Base::m_A = 100;

class Son : public Base {
public:
	static void func()
	{
		cout << "Son - static void func()" << endl;
	}
	static int m_A;
};

int Son::m_A = 200;

//同名成員屬性
void test01()
{
	//通過物件訪問
	cout << "通過物件訪問: " << endl;
	Son s;
	cout << "Son  下 m_A = " << s.m_A << endl;
	cout << "Base 下 m_A = " << s.Base::m_A << endl;

	//通過類名訪問
	cout << "通過類名訪問: " << endl;
	cout << "Son  下 m_A = " << Son::m_A << endl;
	cout << "Base 下 m_A = " << Son::Base::m_A << endl;
}

//同名成員函式
void test02()
{
	//通過物件訪問
	cout << "通過物件訪問: " << endl;
	Son s;
	s.func();
	s.Base::func();

	cout << "通過類名訪問: " << endl;
	Son::func();
	Son::Base::func();
	//出現同名,子類會隱藏掉父類中所有同名成員函式,需要加作作用域訪問
	Son::Base::func(100);
}
int main() {

	//test01();
	test02();

	system("pause");

	return 0;
}

總結:同名靜態成員處理方式和非靜態處理方式一樣,只不過有兩種訪問的方式(通過物件 和 通過類名)

7 多繼承語法

C++允許一個類繼承多個類

語法: class 子類 :繼承方式 父類1 , 繼承方式 父類2...

多繼承可能會引發父類中有同名成員出現,需要加作用域區分

C++實際開發中不建議用多繼承

示例:

class Base1 {
public:
	Base1()
	{
		m_A = 100;
	}
public:
	int m_A;
};

class Base2 {
public:
	Base2()
	{
		m_A = 200;  //開始是m_B 不會出問題,但是改為mA就會出現不明確
	}
public:
	int m_A;
};

//語法:class 子類:繼承方式 父類1 ,繼承方式 父類2 
class Son : public Base2, public Base1 
{
public:
	Son()
	{
		m_C = 300;
		m_D = 400;
	}
public:
	int m_C;
	int m_D;
};


//多繼承容易產生成員同名的情況
//通過使用類名作用域可以區分呼叫哪一個基類的成員
void test01()
{
	Son s;
	cout << "sizeof Son = " << sizeof(s) << endl;
	cout << s.Base1::m_A << endl;
	cout << s.Base2::m_A << endl;
}

int main() {

	test01();

	system("pause");

	return 0;
}

總結: 多繼承中如果父類中出現了同名情況,子類使用時候要加作用域

8 菱形繼承

菱形繼承概念:

​ 兩個派生類繼承同一個基類

​ 又有某個類同時繼承者兩個派生類

​ 這種繼承被稱為菱形繼承,或者鑽石繼承

典型的菱形繼承案例:

菱形繼承問題:

  1. 羊繼承了動物的資料,駝同樣繼承了動物的資料,當草泥馬使用資料時,就會產生二義性。
    
  2. 草泥馬繼承自動物的資料繼承了兩份,其實我們應該清楚,這份資料我們只需要一份就可以。
    

示例:

class Animal
{
public:
	int m_Age;
};

//繼承前加virtual關鍵字後,變為虛繼承
//此時公共的父類Animal稱為虛基類
class Sheep : virtual public Animal {};
class Tuo   : virtual public Animal {};
class SheepTuo : public Sheep, public Tuo {};

void test01()
{
	SheepTuo st;
	st.Sheep::m_Age = 100;
	st.Tuo::m_Age = 200;

	cout << "st.Sheep::m_Age = " << st.Sheep::m_Age << endl;
	cout << "st.Tuo::m_Age = " <<  st.Tuo::m_Age << endl;
	cout << "st.m_Age = " << st.m_Age << endl;
}


int main() {

	test01();

	system("pause");

	return 0;
}

總結:

  • 菱形繼承帶來的主要問題是子類繼承兩份相同的資料,導致資源浪費以及毫無意義
  • 利用虛繼承可以解決菱形繼承問題