【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時,編譯器就傻了,它不知道該呼叫誰,所以直接給你報錯。
因此可以得出結論,在實際使用中,儘量不要使用多重繼承,萬一成員名重複就很麻煩了。