1. 程式人生 > >【C++】類的繼承(protected)

【C++】類的繼承(protected)

目錄

類的繼承

函式重寫

虛擬繼承

多重繼承

類的繼承

類的繼承的出現,是由於實際問題的需要。比如定義了一個男人類和女人類,這2個類之間有屬性或者函式的重疊,那程式碼就有點冗餘,所以為了解決這個問題,就將這些共有的屬性和函式抽取出來,形成一個新類(比如這2類都屬於人,就可以抽取人的共有屬性),而這個類的屬性和函式能夠被男人類和女人類繼承,也就是能直接使用這些屬性和函式。

我們稱這個新類為父類,父類中的所有public成員和函式都可以被子類所使用,private成員和函式不能被繼承。

#include "pch.h"
#include <iostream>
#include "string.h"

class Tutorial
{
public:
	char name[16];
	char author[16];
public:
	void ShowInfo()
	{
		printf("name:%s, author:%s \n", name, author);
	}
private:
	int my_private; //這個成員是不能被子類所使用的
};

class VideoTutorial : public Tutorial
{
public:
	void Play();//播放
public:
	char url[128]; //線上觀看的url地址
	int visits; //播放量
};

int main()
{
	VideoTutorial test_inherit;
	strcpy(test_inherit.name, "test_cpp"); //訪問父類的成員和函式
	strcpy(test_inherit.author, "nick");
	test_inherit.ShowInfo();

	strcpy(test_inherit.url, "http://1123.com"); //訪問自己類中的成員


	std::cout << "Hello World!\n"; 
	return 0;
}

看了上面的例子,有沒有發現public和private的功能有一種情況沒有涵蓋到,那就是變數能被繼承,但不能被外部訪問這種情況。那麼為了能滿足這種需求,就新增了一種訪問修飾符protected,制定protected規則如下:

(1)該成員不能被外部訪問,同private;

(2)該成員可以被子類繼承,同public。

#include "pch.h"
#include <iostream>
#include "string.h"

class Tutorial
{
public:
	char name[16];
	char author[16];
public:
	void ShowInfo()
	{
		printf("name:%s, author:%s \n", name, author);
	}
private:
	int my_private; //這個成員是不能被子類所使用的
protected:
	int my_protected = 9; 
};

class VideoTutorial : public Tutorial
{
public:
	void Play()
	{
		printf("my_protected:%d\n", my_protected); //protected能被繼承,但不能被外部訪問
	}//播放
public:
	char url[128]; //線上觀看的url地址
	int visits; //播放量
};

int main()
{
	VideoTutorial test_inherit;
	strcpy(test_inherit.name, "test_cpp"); //訪問父類的成員和函式
	strcpy(test_inherit.author, "nick");
	test_inherit.ShowInfo();

	strcpy(test_inherit.url, "http://1123.com"); //訪問自己類中的成員

	test_inherit.Play();

	std::cout << "Hello World!\n"; 
	return 0;
}

既然public的成員被繼承了,那麼就會在編譯的時候直接編譯,那private既然沒有被繼承,是否在編譯時就不會被編譯呢?

其實private也是會被編譯的,也能出現在記憶體中,只不過編譯器限制了程式不能訪問private成員。

虛擬繼承和虛擬函式

函式重寫

Child類中重寫了父類的Test函式,最終的列印結果就是This is Child.

#include "pch.h"
#include <iostream>
#include "string.h"

class Parent
{
public:
	void Test()
	{
		printf("This is Parent.\n");
	}
};

class Child : public Parent
{
public:
	void Test()
	{
		printf("This is Child.\n");
	}
};

int main()
{
	Child a;
	a.Test();
	std::cout << "Hello World!\n"; 
	return 0;
}

如果我想在父類某個函式的基礎上再增加些功能,該怎麼做呢?

在呼叫父函式的時候前面加上作用域就行了,也就是告訴這個函式你是Parent的。具體情況請看下面的例子。

#include "pch.h"
#include <iostream>
#include "string.h"

class Parent
{
public:
	void Test()
	{
		printf("This is Parent.\n");
	}
};

class Child : public Parent
{
public:
	void Test()
	{
		Parent::Test();
		printf("This is Child.\n");
	}
};

int main()
{
	Child a;
	a.Test();
	std::cout << "Hello World!\n"; 
	return 0;
}

父類指標指向子類物件

可以將一個父類指標指向一個子類的物件,這是完全允許的。例如Tree* p = new AppleTree();

其實在建立子類物件時,是先編譯父類的成員,然後再編譯子類的成員。

#include "pch.h"
#include <iostream>
#include "string.h"

class Parent
{
public:
	void Test()
	{
		printf("This is Parent.\n");
	}
public:
	int a = 1;
	int b = 2;
};

class Child : public Parent
{
public:
	void Test()
	{
		Parent::Test();
		printf("This is Child.\n");
	}
public:
	int x = 3;
	int y = 4;
};

int main()
{
	Child a;
	a.Test();

	Parent* b = &a;
	b->a; //這裡b只能訪問到父類中的成員,子類中的成員他是訪問不到的
	b->Test(); //還是一樣的只能訪問到父類的成員

	std::cout << "Hello World!\n"; 
	return 0;
}

小結:正如上面程式碼所示,Parent* b = &a;該程式碼通過父類指標指向一個子類物件,該父類指標只能訪問父類自己的成員,而子類的成員b是訪問不了的。

從b->Test();中可以看出,它訪問的是父類的Test函式,變相也表明了子類中重寫的Test函式並沒有覆蓋覆蓋的Test函式,只不過在子類中重寫過後,就只能訪問子類的Test函式而已。

虛擬繼承

當一個成員函式需要子類重寫,那麼在父類應該將其申明為virtual。這樣你就可以呼叫子類中的重寫的函數了。

注意:

(1)只需要在父類中將函式宣告為virtual,子類自動地就是virtual了。

(2)即將被重寫的函式新增virtual,是一條應該遵守的編碼習慣。

#include "pch.h"
#include <iostream>
#include "string.h"

class Parent
{
public:
	virtual void Test()
	{
		printf("This is Parent.\n");
	}
public:
	int a = 1;
	int b = 2;
};

class Child : public Parent
{
public:
	void Test()
	{
		printf("This is Child.\n");
	}
public:
	int x = 3;
	int y = 4;
};

int main()
{
	Child a;
	a.Test();

	Parent* b = &a;
	b->a;
	b->Test();

	return 0;
}

結果為:

This is Child.

This is Child.

小結:如果你想被重寫,而且其實是想呼叫重寫的函式,那麼就加virtual。因為加了virtual就只能呼叫子類中重寫的那個函數了。

繼承:構造和析構

(1)總是現有父親,再有兒子。

(2)構造和析構總是相反的。

#include "pch.h"
#include <iostream>
#include "string.h"

class Parent
{
public:
	Parent()
	{
		printf("Parent:建立\n");
	}
	~Parent()
	{
		printf("Parent:銷燬\n");
	}
};

class Child : public Parent
{
public:
	Child()
	{
		printf("Child:建立\n");
	}
	~Child()
	{
		printf("Child:銷燬\n");
	}
};

int main()
{
	{
		Child a;
	}
	return 0;
}

結果為:

當父類有多個建構函式時,可以顯示的呼叫某個建構函式(一個類在被建立物件時,只能選擇一個建構函式和一個解構函式)。比如下面的例子

#include "pch.h"
#include <iostream>
#include "string.h"

class Parent
{
public:
	Parent()
	{
		printf("Parent:建立\n");
	}
	Parent(int x, int y)
	{
		printf("Parent:有引數\n");
		this->x = x;
		this->y = y;
	}
	~Parent()
	{
		printf("Parent:銷燬\n");
	}
private:
	int x, y;
};

class Child : public Parent
{
public:
	Child():Parent(1,2)
	{
		printf("Child:建立\n");
	}
	~Child()
	{
		printf("Child:銷燬\n");
	}
};

int main()
{
	{
		Child a;
	}
	return 0;
}

 結果為:

virtual解構函式

為什麼解構函式需要加virtual呢?

比如Parent* p = new Child(); delete p; //這裡到底呼叫的是誰的解構函式?

#include "pch.h"
#include <iostream>
#include "string.h"

class Parent
{
public:
	Parent()
	{
		printf("Parent:建立\n");
	}
	Parent(int x, int y)
	{
		printf("Parent:有引數\n");
		this->x = x;
		this->y = y;
	}
	virtual ~Parent()
	{
		printf("Parent:銷燬\n");
	}
private:
	int x, y;
};

class Child : public Parent
{
public:
	Child():Parent(1,2)
	{
		printf("Child:建立\n");
	}
	~Child()
	{
		printf("Child:銷燬\n");
	}
};

int main()
{
	Parent* p = new Child();
	delete p;

	return 0;
}

結果為:

小結:如果不加virtual,那麼程式不會報錯。但真實情況是,我真正想呼叫子類中的解構函式,不然程式析構的物件有問題,以後面程式會出現不可預知的錯誤。

類的大小和virtual關鍵字的影響

(1)類的大小由成員變數決定。

類的大小成員函式的個數無關,即使一個類有10000個成員函式,對它所佔的記憶體空間是沒有影響的。

(2)但是,如果一個成員函式被宣告virtual,那類的大小會有些微的變化。(這個變化由編譯器決定,一般是增加4個位元組)

(3)建構函式不能加virtual。

多重繼承

#include "pch.h"
#include <iostream>
#include "string.h"

class Father
{
public:
	int a, b;
	void Test()
	{
		printf("Father");
	}
};

class Mother
{
public:
	int c, d;
	void Test()
	{
		printf("Mother");
	}
};

class Child : public Father, public Mother
{
public:
	int e;
};

int main()
{
	Child my_child;
	my_child.a = 1;
	my_child.b = 2;
	my_child.c = 3;
	my_child.d = 4;
	my_child.e = 5;

	//my_child.Test();// 這裡會直接報錯,因為編譯器不知道該呼叫哪個Test

	return 0;
}

小結:正如上面例子所示,我在Father和Mother類中都寫了Test函式,在主函式中呼叫Test時,編譯器就傻了,它不知道該呼叫誰,所以直接給你報錯。

因此可以得出結論,在實際使用中,儘量不要使用多重繼承,萬一成員名重複就很麻煩了。