- 類 & 物件
- 封裝
- 訪問許可權
- 類的建構函式&解構函式
- 建構函式的分類及呼叫
- 複製建構函式的呼叫時機
- 建構函式呼叫規則
- 深複製與淺複製
- 初始化列表
- 類物件作為類成員
- 靜態成員
- 建構函式的分類及呼叫
- C++物件模型和this指標
- 成員變數和成員函式分開儲存
- this指標概念
- 空指標訪問成員函式
- const修飾成員函式
- 友元
- 全域性函式做友元
- 類做友元
- 成員函式做友元
- 運算子過載
- 加號運算子過載
- 左移運算子過載
- 遞增 / 遞減運算子過載
- 賦值運算子過載
- 關係運算子過載
- 函式呼叫運算子過載
- 繼承
- 基本語法
- 繼承方式
- 構造和析構順序
- 繼承同名成員處理方式
- 多繼承
- 菱形繼承
- 多型
- 多型的基本概念
- 多型案例(一)——計算器類
- 純虛擬函式和抽象類
- 多型案例(二)——飲品類
- 虛析構和純虛析構
- 多型案例(三)—— 電腦組裝
- 封裝
類 & 物件
c++物件導向的三大特徵為:封裝、繼承、多型
封裝
封裝的意義:
- 將屬性和行為作為一個整體,表現生活中的事物(資料成員和方法)
- 將屬性和行為加以許可權控制(訪問修飾符)
類中的屬性和行為我們一般稱為成員,屬性為成員屬性,行為是成員方法
這裡以盒子為例,定義一個類,並宣告兩個物件
class Box
{
//訪問許可權
//公共許可權
public:
//資料成員
double lenth; //長
double breadth; //寬
double height; //高
//方法
//求盒子體積
double cal_Volume()
{
return lenth * breadth * height;
}
};
int main()
{
Box box1; // 宣告 box1,型別為 Box
Box box2; // 宣告 box2,型別為 Box
//物件box1和box2都有他們各自的成員
}
訪問許可權
類在設計時,可以把屬性和行為放在不同的許可權下來加以控制
訪問許可權有三種:
-
public 公共許可權
其成員類內可以訪問,類外可以訪問
-
protected 保護許可權
其成員類內可以訪問,類外不可以訪問,但在子類(派生類)中可以訪問
-
private(預設) 私有許可權
其成員類內可以訪問,類外不可以訪問,不可檢視
class Person
{
public:
String name;
protected:
int age;
int id_card;//預設為private
public:
func()
{
name = "alen";
age = 26;
id_card = 114514;
}
};
int main()
{
Person p;
p.name = "walker";
p.age = 23;//報錯,類外不能訪問
p.id_card = 1919810;//報錯,類外不能訪問
}
struct和class區別
兩者的唯一區別在於預設的訪問許可權:
-
struct預設許可權為公共
-
class預設許可權為私有
-
class C1 { int a; //預設是私有許可權 private }; struct C2 { int a; //預設是公共許可權 public };
成員屬性設定為私有
優點:
- 將所有成員屬性設定為私有,可以自己控制讀寫許可權
- 對於寫許可權,我們可以檢測資料的有效性
class Person
{
public:
//設定名字
void setName(string name)
{
m_Name = name;
}
//設定偶像
void setIdol(string idol)
{
m_Idol = idol;
}
//設定年齡
void setAge(int age)
{
if (age < 0 || age > 150)
{
cout<<"年齡"<<age<<"輸入有誤"<<endl;
return;
}
m_Age = age;
}
string getName()
{
return m_Name;
}
int getAge()
{
return m_Age;
}
private:
string m_Name;//姓名 可讀可寫
int m_Age = 18;//年齡 只讀 (當可寫的時候,年齡限制在0-150之間)
string m_Idol;//偶像 只寫
};
類的建構函式&解構函式
c++中的建構函式和解構函式的作用是對物件的初始化和清理
物件的初始化和清理工作是強制的,如果我們沒有提供構造和解構函式的話,編譯器將自動提供空實現的構造和解構函式
- 建構函式:主要作用在於建立物件時為物件的成員屬性賦值,建構函式由編譯器自動呼叫,無需手動呼叫
- 解構函式:主要作用在於物件銷燬前系統自動呼叫,執行一些清理工作
構建函式語法(內聯):類名( ) { }
- 沒有返回值,也不用寫void
- 函式名稱與類名相同
- 建構函式可以有引數,因此可以發生過載
- 程式在呼叫物件的時候會自動呼叫構造,無須手動呼叫,而且只會呼叫一次
解構函式語法(內聯):~類名( ) { }
- 沒有返回值,也不用寫void
- 函式名稱與類名相同,在名稱前加上符號 ~
- 解構函式不可以有引數,因此不可以發生過載
- 程式在物件銷燬前會自動呼叫析構,無須手動呼叫,而且只會呼叫一次
class Person
{
public:
//建構函式 進行初始化操作
Person()
{
cout<<"Person建構函式的呼叫"<<endl;
}
~Person()
{
cout<<"Person解構函式的呼叫"<<endl;
}
};
void test01()
{
Person p; //在棧上的資料,p建立時執行建構函式,test01執行完畢後釋放這個物件,物件的解構函式被呼叫
}
int main()
{
test01();
return 0;
}
建構函式的分類及呼叫
兩種分類方式:
- 按引數分為:有參構造和無參構造(預設)
- 按型別分為:普通構造和複製構造
三種呼叫方式:
- 括號法
- 顯示法
- 隱式轉換法
class Person
{
public:
//普通構造
Person()
{
cout<<"Person無參函式的呼叫"<<endl;
}
Person(int a)
{
age = a;
cout<<"Person有參函式的呼叫"<<endl;
}
//複製構造
Person(const Person &p)
{
//將傳入物件的所有屬性,複製到當前物件上
age = p.age;
}
~Person()
{
cout<<"Person解構函式的呼叫"<<endl;
}
private:
int age;
};
void test01()
{
//括號法
Person p1;//預設建構函式呼叫
Person p2(10);//有參建構函式
Person p3(p2);//複製建構函式
/*
注意事項:
呼叫預設建構函式的時候,不要加()
因為這句程式碼“ Person p1(); ”編譯器會認為是一個函式宣告,不會認為是在建立物件
*/
//顯示法
Person p4;
Person p5 = Person(10); //有參
Person p6 = Person(p5); //複製
Person(10); //匿名物件 特點:當前行執行結束後,系統會立即回收掉匿名物件
/*
注意事項2:
不要用複製建構函式初始化匿名物件
Person(p3) 會被視為物件p3的重定義
*/
//隱式轉換法
Person p7 = 10; //有參 相當於 Person p7 = Person(10)
Person p8 = p7; //複製
}
int main()
{
test01();
return 0;
}
複製建構函式的呼叫時機
c++中複製建構函式呼叫實機通常有三種情況
- 使用一個已經建立完畢的物件來初始化一個新物件
- 值傳遞的方式給函式引數傳值(值傳遞本質是複製一個臨時的副本來傳遞,因此會呼叫複製建構函式)
- 以值方式返回區域性物件(同上)
建構函式呼叫規則
預設情況下,c++編譯器至少給一個類新增3個函式
- 預設建構函式(無參,函式體為空)
- 預設解構函式(無參,函式體為空)
- 預設複製函式,對屬性進行值複製
建構函式呼叫規則如下:
- 如果使用者定義有參建構函式,c++不再提供預設無參構造,但是會提供預設複製構造
- 如果使用者定義複製建構函式,c++不會再提供其他建構函式
深複製與淺複製
淺複製(預設):簡單的賦值複製操作
深複製:在堆區重新申請空間,進行複製操作
淺複製可能帶來的問題是堆區的記憶體重複釋放
class Person
{
Person(const Person &p)
{
m_Age = p.m_Age;
m_Height = p.m_Height; //編譯器預設的淺複製(單純的值複製)
}
Person(const Person &p)
{
m_Age = p.m_Age;
m_Height = new int(*p.m_Height); //深複製操作,另外開闢一片堆記憶體進行複製
}
};
初始化列表
作用:c++提供了初始化列表語法,用來初始化屬性
語法:建構函式( ) : 屬性(值1), 屬性(值2)...{ }
class Person
{
//傳統初始化操作
Person(int a, int b, int c)
{
m_A = a;
m_B = b;
m_C = c;
}
//初始化列表初始化屬性
Person(int a, int b, int c) :m_A(a), m_B(b), m_C(c)
{
}
private:
int m_A;
int m_B;
int m_C;
}
類物件作為類成員
c++類中的成員可以是另一個類的物件,我們稱該成員為 物件成員
class A
{
};
class B
{
A a;
}
B類中有物件A作為成員,A
靜態成員
靜態成員就是在成員變數和成員函式前加上關鍵字static,稱為靜態成員
靜態成員分為:
-
靜態成員變數
- 所有物件共享同一份資料
- 在編譯階段分配記憶體
- 類內宣告,類外初始化
-
靜態成員函式
- 所有物件共享一個函式
- 靜態成員函式只能訪問靜態成員變數
class Person
{
public:
//靜態成員變數
//編譯階段就分配記憶體
static int m_A;
private:
static int m_B;//靜態成員變數也有訪問許可權
};
//類內宣告,類外初始化操作
int Person:: m_A = 100;
void test01()
{
Person p;
cout<< p.m_A <<endl;//100
Person p2;
p2.m_A = 200;
cout<< p.m_A <<endl;//200
//所有物件都共享同一份資料
}
void test02()
{
//靜態成員變數不專屬於某個物件,所有物件都共享同一份資料
//因此靜態成員變數有兩種訪問方式:
//1.透過物件進行訪問
Person p;
cout<< p.m_A <<endl;
//2.透過類名進行訪問
cout<< Person::m_A<<endl;
}
class Person
{
public:
static void func()
{
cout <<"靜態成員函式呼叫"<< endl;
}
};
void test01()
{
//兩種訪問方式
//透過物件訪問
Person p;
p.func();
//透過類名訪問
Person::func();
}
C++物件模型和this指標
成員變數和成員函式分開儲存
在c++中,類內的成員變數和成員函式分開儲存
只有非靜態成員變數才屬於類的物件上
class Person
{
};
class Human
{
public:
int m_A;//非靜態成員變數 屬於類的物件上的資料
static int m_B;//靜態成員變數 不屬於類物件上
void func(){}//非靜態成員函式 不屬於類物件上
static void func2(){}//靜態成員函式 不屬於類物件上
};
void test01()
{
Person p;
//空物件佔用記憶體空間為:1 (byte)
//C++編譯器會給每個空物件也分配一個位元組空間,是為了區分空物件佔記憶體的位置
//每個空物件也應該有一個獨一無二的記憶體地址
cout << "size of p = " << sizeof(p) << endl;
}
void test02()
{
Human p1;
//佔4 (byte) -> 非靜態成員變數(int)所佔的記憶體
//因為靜態成員變數、非靜態成員函式、靜態成員函式不在類的物件上所以分開儲存
cout << "size of p = " << sizeof(p1) << endl;
}
this指標概念
c++中成員變數和成員函式是分開儲存的
每一個非靜態成員函式只會產生一份函式例項,也就是說多個同型別的物件會公用同一份程式碼
那麼問題是:這一塊程式碼是如何區分哪個物件呼叫自己的呢?
c++透過提供特殊的物件指標,this指標來解決上述問題。this指標指向被呼叫的成員函式所屬的物件
this指標是隱含每一個非靜態成員函式內的一種指標
this指標不需要定義,直接使用即可
this指標的本質是指標常量,指標的指向是不可修改的
this指標的用途:
- 當形參和成員變數同名時,可用this指標來區分
- 在類的非靜態成員函式中返回物件本身,可使用 return *this
class Person
{
public:
Person(int age)
{
age = age;//形參和成員屬性名字相同,編譯器誤以為他倆是同一個東西
//this指標指向的是被呼叫的成員函式(這裡呼叫的是建構函式)所屬的物件(這裡的物件是p1)
this->age = age;//使用this指標可解決名稱衝突
}
//要返回本體的話須加引用
Person& PersonAddAge(Person &p)
{
this->age += p.age;
//this指向p2的指標,而*this指向的就是p2的本身
return *this;
}
int age;//應該使用別的規範名稱,如m_Age
};
//1.解決名稱衝突
void test01()
{
Person p1(18);
cout << "p1的年齡是" << p1.age << endl;//並非18
}
//2.返回物件本身用*this
void test02()
{
Person p1(10);
Person p2(10);
//返回值為物件本身,即p2 = p2.PersonAddAge(p1)
//鏈式程式設計思想
p2.PersonAddAge(p1).PersonAddAge(p1).PersonAddAge(p1);
cout << "p2的年齡是" << p2.age << endl;//
}
空指標訪問成員函式
c++中空指標也是可以呼叫成員函式的,但是也要注意有沒有用到this指標
如果用到this指標,需要加以判斷保證程式碼的健壯性
//空指標呼叫成員函式
class Person
{
public:
void showClassName()
{
cout << "this is Person class" << endl;
}
void showPersonAge()
{
//報錯的原因是傳入的指標為NULL
/*解決措施:
if (this == NULL)
{
return;
}
*/
cout << "age = " << this->m_Age << endl;
}
int m_Age;
};
void test01
{
Person * p = null;
p->showClassName();//不報錯
p->showPersonAge();//報錯,因為呼叫了成員屬性,但指標為空指標
}
const修飾成員函式
常函式:
- 成員函式後加const後我們稱這個函式為常函式
- 常函式內不可以修改成員屬性
- 成員屬性宣告時加關鍵字mutable後,在常函式中依然可以修改
常物件:
- 宣告物件前加const稱該物件為常物件
- 常物件只能呼叫常函式
//常函式
class Person
{
public:
//在成員函式後面加const,修飾的是this指向,讓this指標指向的值也無法修改
void showPerson() const
{
//this->m_A = 100; 不可修改
this->m_B = 100;
}
int m_A;
mutable int m_B;//特殊變數,即使在常函式中,也可以修改這個變數
}
void test02()
{
const Person p; //在物件前加const,變為常物件
//p.m_A = 100; 報錯
p.m_B = 100; //m_B是特殊值,在常物件中可修改
//常物件只能呼叫常函式
p.showPerson();
//p.func(); //報錯 常物件不可以呼叫普通成員函式,因為普通成員函式可以修改屬性
}
友元
生活中,家裡(class)有客廳(public)也有臥室(private),客廳所有客人都能進,但是臥室除了好友之外的普通客人不能進
在程式裡,有些私有屬性也想讓類外特殊的一些函式或者類進行訪問,就需要用到友元的技術
友元的目的就是讓一個函式或者類訪問另一個類中私有成員
友元的關鍵詞為friend
友元的三種實現
- 全域性函式做友元
- 類做友元
- 成員函式做友元
全域性函式做友元
class Building
{
//友元宣告
friend void goodFriend(Building &building);
public:
Building()
{
m_SittingRoom = "客廳";
m_BedRoom = "臥室";
}
public:
string m_SittingRoom;
private:
string m_BedRoom;
};
//全域性函式
void goodFriend(Building &building)
{
cout << "猴米全域性函式正在訪問:"<< building->m_BedRoom/*private*/ <<endl;
}
void test01()
{
Building building;
goodFriend(&building);//成功訪問
}
類做友元
class Building;
class GoodFriend
{
public:
GoodFriend();
void visit(); //參觀函式 訪問Building類中元素
Building * building;
};
class Building
{
//友元宣告
friend class GoodFriend;
public:
Building();
public:
string m_SittingRoom;
private:
string m_BedRoom;
};
//類外寫成員函式
Building::Building()
{
m_SittingRoom = "客廳";
m_BedRoom = "臥室";
}
GoodFriend::GoodFriend()
{
//建立建築物物件
building = new Building;
}
void GoodFriend::visit()
{
cout << "猴米類正在訪問:" << building->m_BedRoom << endl;
}
void test01()
{
GoodFriend gf;
gf.visit();//呼叫成功
}
成員函式做友元
class Building;
class GoodFriend
{
public:
GoodFriend();
void visit01(); //讓visit01函式可以訪問Building類的私有成員
void visit02(); //讓visit02函式不可以訪問Building類的私有成員
Building * building;
};
class Building
{
//友元宣告
friend void GoodFriend::visit01();
public:
Building();
public:
string m_SittingRoom;
private:
string m_BedRoom;
};
//類外寫成員函式
Building::Building()
{
m_SittingRoom = "客廳";
m_BedRoom = "臥室";
}
GoodFriend::GoodFriend()
{
//建立建築物物件
building = new Building;
}
void GoodFriend::visit01()
{
cout << "猴米類正在訪問:" << building->m_BedRoom << endl;
}
void GoodFriend::visit01()
{
cout << "猴米類正在訪問:" << building->m_BedRoom << endl;//報錯
}
運算子過載
運算子過載的概念:對已有的運算子重新進行定義,賦予其另一種功能,以適應不同的資料型別
加號運算子過載
作用:實現兩個自定義資料型別相加的運算
Tips:
- 對於內建的資料型別的表示式的運算子是不可能改變的
- 不要濫用運算子過載
class Person
{
public:
//成員函式過載+號
Person operator+(Person &p)
{
Person temp;
temp.m_A = this->m_A + p.m_A;
temp.m_B = this->m_B + p.m_B;
return temp;
}
int m_A;
int m_B;
};
//全域性函式過載+號
Person operator+(Person &p1, Person &p2)
{
Person temp;
temp.m_A = this->m_A + p.m_A;
temp.m_B = this->m_B + p.m_B;
return temp;
}
void test01
{
Person p1;
p1.m_A = 10;
p1.m_B = 10;
Person P2;
p2.m_A = 10;
p2.m_B = 10;
//成員函式本質呼叫
//Person p3 = p1.operator+(p2);
Person p3 = p1 + p2;//簡化
cout << "p3.m_A = " << p3.m_A << endl;
cout << "p3.m_B = " << p3.m_B << endl;
}
左移運算子過載
作用:可以輸出自定義資料型別
class Person
{
friend ostream& operator<<(ostream &cout,Person &p);
public:
Person(int a, int b)
{
m_A = a;
m_B = b;
}
private:
//不會利用成員函式過載<<運算子,因為無法實現cout在左側
int m_A;
int m_B;
};
//只能利用全域性函式過載左移運算子
ostream& operator<<(ostream &cout,Person &p)
{
cout << "m_A = " << p.m_A << " m_B = " << p.m_B;
return cout;
}
void test01()
{
Person p(10,10);
cout << p << endl;
}
int main() {
cout << "Hello, World!" << endl;
test01();
return 0;
}
遞增 / 遞減運算子過載
作用:透過過載遞增 / 遞減運算子,實現自己的整形資料
//過載遞增運算子
//自定義整型
class MyInteger
{
friend ostream & operator<<(ostream& cout, MyInteger myint);
public:
MyInteger()
{
m_Num = 0;
}
//過載前置++運算子
//返回引用是為了一直對一個資料進行遞增操作
MyInteger& operator++()
{
//先進行++運算
m_Num++;
//再將自身返回
return *this;
}
//過載後置++運算子
//int代表佔位引數,可用於區分前置和後置遞增
MyInteger operator++(int)
{
//先 記錄當時結果
MyInteger temp = *this;
//後 遞增
m_Num++;
//最後將記錄結果做返回
return temp;
}
//過載前置--運算子
//返回引用是為了一直對一個資料進行遞減操作
MyInteger& operator--()
{
//先進行--運算
m_Num--;
//再將自身返回
return *this;
}
//過載後置--運算子
//int代表佔位引數,可用於區分前置和後置遞減
MyInteger operator--(int)
{
//先 記錄當時結果
MyInteger temp = *this;
//後 遞減
m_Num--;
//最後將記錄結果做返回
return temp;
}
private:
int m_Num;
};
ostream & operator<<(ostream& cout, MyInteger myint)
{
cout << myint.m_Num;
return cout;
}
void test01()
{
MyInteger myint; //0
cout << ++myint << endl; //1
cout << myint++ << endl; //1
cout << myint << endl; //2
cout << --myint << endl; //1
cout << myint-- << endl; //1
cout << myint << endl; //0
}
賦值運算子過載
c++編譯器至少給一個類新增4個函式
- 預設建構函式(無參,函式體為空)
- 預設解構函式(無參,函式體為空)
- 預設複製建構函式,對屬性進行值複製
- 賦值運算子operator=,對屬性進行值複製
如果類中有屬性指向堆區,做賦值操作時也會出現深淺複製問題
//賦值運算子過載
class Person
{
public:
Person(int age)
{
m_Age = new int(age);
}
~Person()
{
if (m_Age != NULL)
{
delete m_Age;
m_Age = NULL;
}
}
//過載 賦值運算子
Person& operator=(Person &p)
{
//應該先判斷是否有屬性在堆區,如有,應先釋放乾淨再進行深複製
if (m_Age != NULL)
{
delete m_Age;
m_Age = NULL;
}
//深複製
m_Age = new int(*p.m_Age);
//返回物件本身
return *this;
}
int *m_Age;
};
void test01()
{
Person p1(10);
Person p2(20);
Person p3(30);
p3 = p2 = p1; //賦值操作
cout << "p1's age = " << *p1.m_Age << endl;
cout << "p2's age = " << *p2.m_Age << endl;
cout << "p3's age = " << *p3.m_Age << endl;
}
關係運算子過載
作用:過載關係運算子,可以讓兩個自定義型別物件進行對比操作
//關係運算子過載
class Person
{
public:
Person(string name, int age)
{
m_Name = name;
m_Age = age;
}
//過載==號
bool operator==(Person &p)
{
if (this->m_Name == p.m_Name)
{
return true;
}
return false;
}
//過載!=號
bool operator!=(Person &p)
{
if (this->m_Name != p.m_Name)
{
return true;
}
return false;
}
string m_Name;
int m_Age;
};
void test01()
{
Person p1("Tom",18);
Person p2("Tom",18);
if (p1 == p2)
{
cout << "p1 == p2" << endl;
} else{
cout << "p1 != p2" << endl;
}
}
函式呼叫運算子過載
- 函式呼叫運算子()也可以過載
- 與哦於過載後使用的方式非常像函式的呼叫,因此稱為仿函式
- 仿函式沒有固定寫法,非常靈活
//函式呼叫運算子過載
class MyPrint
{
public:
//過載函式呼叫運算子
void operator()(string test)
{
cout << test <<endl;
}
};
class MyAdd
{
public:
int operator()(int num1, int num2)
{
return num1 + num2;
}
};
void test01()
{
MyPrint myPrint;
myPrint("hello world");
//匿名物件呼叫
int a = MyAdd()(10,20);
cout << "a = " << a << endl;
}
繼承
繼承是物件導向三大特性之一
繼承允許我們依據另一個類來定義一個類,有重用程式碼功能和提高執行效率的效果。
當建立一個類時,不必重新編寫新的資料成員和成員函式,只需指定新建的類繼承了一個已有的類的成員即可。這個已有的類稱為基類,新建的類稱為派生類。也可以稱為父類與子類
基本語法
class 子類 : 繼承方式 父類
class A : public B
以上圖為例
// 基類
class Animal {
public:
void eat()
{
cout << "吃!" << endl;
}
void sleep()
{
cout << "zzzZZZ" << endl;
}
string name;
int age;
};
//派生類
class Dog : public Animal {
public:
void bark()
{
cout << "汪汪汪!" << endl;
}
};
子類中的成員,包含兩大部分:
- 從父類繼承過來的
- 自己增加的成員
從父類繼承過來的表現其共性,而新增的成員體現了其個性。
一個子類繼承了所有的基類方法,但下列情況除外:
- 父類的構造函式、解構函式和複製構造函式。
- 父類的過載運算子。
- 父類的友元函式。
繼承方式
繼承方式一共有三種:
- 公共繼承(public):當一個類派生自公有基類時,基類的公有成員也是派生類的公有成員,基類的保護成員也是派生類的保護成員,基類的私有成員不能直接被派生類訪問,但是可以透過呼叫基類的公有和保護成員來訪問。
- 保護繼承(protected):當一個類派生自保護基類時,基類的公有和保護成員將成為派生類的保護成員。
- 私有繼承(private):當一個類派生自私有基類時,基類的公有和保護成員將成為派生類的私有成員。
我們幾乎不使用 protected 或 private 繼承,通常使用 public 繼承。
構造和析構順序
在繼承後,派生類也會繼承基類的建構函式與解構函式
那麼他們之間的呼叫順序如下:
父類構造——子類構造——子類析構——父類析構
繼承同名成員處理方式
當子類與父類出現同名的成員時:
- 如訪問子類同名成員,直接訪問即可
- 如訪問父類同名成員,需要加作用域(類名::)
class Base{
public:
Base()
{
m_A = 100;
}
void func()
{
cout << "Base" << endl;
}
int m_A;
};
class Son:public Base{
public:
Son()
{
m_A = 200;
}
void func()
{
cout << "Son" << endl;
}
int m_A;
};
//同名資料成員處理
void test01()
{
Son s;
cout << "Son下 m_A = " << s.m_A << endl;//200
cout << "Base下 m_A = " << s.Base::m_A << endl;//100
}
//同名成員函式處理
void test02()
{
Son s;
s.func();//Son
s.Base::func();//Base
}
總結:
- 子類物件可以直接訪問到子類中同名成員
- 子類物件加作用域可以訪問到父類同名函式
- 當子類和父類擁有同名的成員函式,子類會隱藏父類中同名成員函式,但加作用域可以訪問到
多繼承
多繼承即一個子類可以有多個父類,它繼承了多個父類的特性。
C++ 類可以從多個類繼承成員,語法如下:
class <派生類名>:<繼承方式1><基類名1>,<繼承方式2><基類名2>,…
{
<派生類類體>
};
c++實際開發中不建議使用多繼承,因為可能會引發多個父類中有同名成員導致作用域管理混亂
示例如下:
// 形狀基類 Shape
class Shape
{
public:
void setWidth(int w)
{
width = w;
}
void setHeight(int h)
{
height = h;
}
protected:
int width;
int height;
};
// 刷漆基類 PaintCost
class PaintCost
{
public:
int getCost(int area)
{
return area * 70;
}
};
// 派生類
class Rectangle: public Shape, public PaintCost
{
public:
int getArea()
{
return (width * height);
}
};
int main(void)
{
Rectangle Rect;
int area;
Rect.setWidth(5);
Rect.setHeight(7);
area = Rect.getArea();
// 輸出物件的面積
cout << "Total area: " << Rect.getArea() << endl;
// 輸出總花費
cout << "Total paint cost: $" << Rect.getCost(area) << endl;
return 0;
}
菱形繼承
菱形繼承概念:
- 兩個派生類繼承同一個基類
- 又有某個類同時繼承著兩個派生類
- 這種繼承被稱為菱形繼承,或者鑽石繼承
典型的菱形繼承案例:
菱形繼承問題:
- 老虎繼承了動物的資料,獅子同樣也繼承了動物的資料;當獅虎獸使用資料時,就會產生二義性
- 當菱形繼承,兩個父類擁有相同資料時,需要加以作用域區分
- 獅虎獸繼承自動物的資料繼承了兩份,但我們應該清楚,這份資料我們只需要一份即可,導致資源浪費
- 利用虛繼承來解決(在繼承方式前加上關鍵字virtual:class Tiger : virtual public Animal,基類Animal變成虛基類)
多型
多型的基本概念
多型是c++物件導向的三大特性之一
多型按字面的意思就是多種形態。當類之間存在層次結構,並且類之間是透過繼承關聯時,就會用到多型。呼叫成員函式時,會根據呼叫函式的物件的型別來執行不同的函式。
多型分為兩類:
- 靜態多型:函式過載和運算子過載屬於靜態多型,服用函式名
- 動態多型:派生類和虛擬函式來實現執行時的多型
靜態多型和動態多型區別:
- 靜態多型的函式地址早繫結 —— 編譯階段確定函式地址
- 動態多型的函式地址晚繫結 —— 執行階段確定函式地址
class Animal
{
public:
void speak()
{
cout<<"動物在說話"<<endl;
}
};
class Cat :public Animal
{
public:
void speak()
{
cout<<"meow,meow,meow"<<endl;
}
};
//執行說話的函式
//地址早繫結 在編譯階段確定函式地址
//如果想執行讓貓說話,那麼這個函式地址就不能提前繫結,需要晚繫結
void doSpeak(Animal &animal)
{
animal.speak();
}
void test01()
{
Cat cat;
doSpeak(cat);//動物在說話
}
class Animal
{
public:
//虛擬函式 實現晚繫結
virtual void speak()
{
cout<<"動物在說話"<<endl;
}
};
class Cat :public Animal
{
public:
//重寫 函式返回值型別、函式名、引數列表完全相同
void speak()
{
cout<<"meow,meow,meow"<<endl;
}
};
//執行說話的函式
void doSpeak(Animal &animal)
{
animal.speak();
}
void test01()
{
Cat cat;
doSpeak(cat);//meow
}
總結:
動態多型滿足條件:
- 有繼承關係
- 子類重寫父類的虛擬函式
多型使用條件
- 父類指標或引用指向子類物件
多型案例(一)——計算器類
案例描述:
分別用普通寫法和多型技術,設計實現兩個運算元進行的計算器類
多型的優點:
- 程式碼組織結構清晰
- 可讀性強
- 利於前期和後期的擴充套件以及維護
//普通實現
class Calculator
{
public:
Calculator(int num1, int num2)
{
m_Num1 = num1;
m_Num2 = num2;
}
int getResult(string oper)
{
if (oper == "+") return m_Num1 + m_Num2;
else if (oper == "-") return m_Num1 - m_Num2;
else if (oper == "*") return m_Num1 * m_Num2;
//(普通寫法下)如果想擴充套件新功能,就需要修改原始碼
//在真實開發中,我們提倡開閉原則:對擴充套件進行開放,對修改進行關閉
}
int m_Num1;//運算元1
int m_Num2;//運算元2
};
void test01()
{
//建立一個計算器物件
Calculator c(10,10);
cout << c.m_Num1 << "+" << c.m_Num2 << "=" << c.getResult("+") << endl;
cout << c.m_Num1 << "-" << c.m_Num2 << "=" << c.getResult("-") << endl;
cout << c.m_Num1 << "*" << c.m_Num2 << "=" << c.getResult("*") << endl;
}
//利用多型實現計算器
//實現計算器抽象類
class AbstractCalculator
{
public:
virtual int getResult()
{
return 0;
}
int m_Num1;//運算元1
int m_Num2;//運算元2
};
//加法計算器類
class AddCaculator :public AbstractCalculator
{
public:
virtual int getResult()
{
return m_Num1 + m_Num2;
}
};
//減法計算器類
class SubCaculator :public AbstractCalculator
{
public:
virtual int getResult()
{
return m_Num1 - m_Num2;
}
};
//乘法計算器類
class MulCaculator :public AbstractCalculator
{
public:
virtual int getResult()
{
return m_Num1 * m_Num2;
}
};
void test02()
{
//多型使用條件:父類指標/引用指向子類物件
//加法運算
AbstractCalculator * abc = new AddCaculator;//父類指標
abc->m_Num1 = 100;
abc->m_Num2 = 100;
cout << abc->m_Num1 << "+" << abc->m_Num2 << "=" << abc->getResult() << endl;
//用完後記得銷燬
delete abc;
//減法運算
abc = new SubCaculator;
abc->m_Num1 = 100;
abc->m_Num2 = 100;
cout << abc->m_Num1 << "-" << abc->m_Num2 << "=" << abc->getResult() << endl;
//用完後記得銷燬
delete abc;
//乘法運算
abc = new MulCaculator;
abc->m_Num1 = 100;
abc->m_Num2 = 100;
cout << abc->m_Num1 << "*" << abc->m_Num2 << "=" << abc->getResult() << endl;
//用完後記得銷燬
delete abc;
}
純虛擬函式和抽象類
在多型中,通常父類中虛擬函式的實現是無意義的,主要是呼叫子類重寫的內容(例如計算器類案例中的抽象計算器類)
因此可以將虛擬函式改為純虛擬函式
純虛擬函式語法:virtual 返回值型別 函式名(引數列表)= 0;
當類中有了純虛擬函式,這個類也成為抽象類
抽象類特點:
- 無法例項化物件
- 子類必須重寫抽象類中的純虛擬函式,否則也屬於抽象類
class AbstractCalculator//有純虛擬函式的類就是抽象類
{
public:
//純虛擬函式
virtual int getResult() = 0;
int m_Num1;//運算元1
int m_Num2;//運算元2
};
//加法計算器類
class AddCalculator : public AbstractCalculator
{
public:
virtual int getResult()
{
return m_Num1 + m_Num2;
}
};
void test01()
{
//多型使用條件:父類指標/引用指向子類物件
//加法運算
AbstractCalculator * abc = new AddCalculator;//父類指標
abc->m_Num1 = 100;
abc->m_Num2 = 100;
cout << abc->m_Num1 << "+" << abc->m_Num2 << "=" << abc->getResult() << endl;
//用完後記得銷燬
delete abc;
AbstractCalculator abc;//報錯 抽象類無法例項化物件
new AbstractCalculator;//報錯 堆區或棧區都不行
}
多型案例(二)——飲品類
案例描述:
製作飲品的大致流程為:煮水 - 沖泡 - 倒入杯中 - 加入輔料
利用多型技術實現本案例,提供抽象製作飲品基類,提供子類製作咖啡和茶葉
class AbstractDrinking
{
public:
//煮水
virtual void Boil() = 0;
//沖泡
virtual void Brew() = 0;
//倒入杯中
virtual void PourInCup() = 0;
//加入輔料
virtual void PutSth() = 0;
//製作飲品
void makeDrink()
{
Boil();
Brew();
PourInCup();
PutSth();
}
};
//製作咖啡
class Coffee :public AbstractDrinking
{
//煮水
virtual void Boil(){
cout << "用阿拉斯加山脈泉水煮水" << endl;
}
//沖泡
virtual void Brew(){
cout << "沖泡咖啡" << endl;
}
//倒入杯中
virtual void PourInCup(){
cout << "倒入咖啡杯中" << endl;
}
//加入輔料
virtual void PutSth(){
cout << "加入方糖和牛奶" << endl;
}
};
//製作茶葉
class Tea :public AbstractDrinking
{
//煮水
virtual void Boil(){
cout << "用長白山泉水煮水" << endl;
}
//沖泡
virtual void Brew(){
cout << "沖泡茶葉" << endl;
}
//倒入杯中
virtual void PourInCup(){
cout << "倒入茶杯中" << endl;
}
//加入輔料
virtual void PutSth(){
cout << "加入茉莉花" << endl;
}
};
//製作函式
void doWork(AbstractDrinking * abs)
{
abs->makeDrink();
delete abs; //釋放堆區資料
}
void test01()
{
//製作咖啡
doWork(new Coffee);
cout << "----------------" << endl;
//製作茶葉
doWork(new Tea);
}
虛析構和純虛析構
多型使用時,如果子類中有屬性開闢到堆區,那麼父類指標在釋放時無法呼叫到子類的析構程式碼
解決方式:將父類中的解構函式改為虛析構或者純虛析構
虛析構或者純虛析構共性:
- 可以解決父類指標釋放子類物件
- 都需要有具體的函式實現
虛析構或者純虛析構區別:
- 如果是純虛析構,該類屬於抽象類,無法例項化物件
虛析構語法:
virtual ~類名( ) { }
純虛析構語法:
virtual ~類名( ) = 0 ;
類名::~類名(){ }
總結:
- 虛析構或純虛析構就是用來解決父類指標釋放子類物件
- 如果子類中沒有堆區資料,可以不寫為虛析構或純虛析構
- 擁有純虛解構函式的類也屬於抽象類
多型案例(三)—— 電腦組裝
案例描述:
電腦主要組成部分為CPU(計算)、顯示卡(顯示)和記憶體條(儲存)
將每個零件封裝成抽象基類,並且提供不同的廠商生產的不同的零件,例如Intel和AMD廠商
建立電腦類提供讓電腦工作的函式,並且呼叫每個零件工作的介面
測試時組裝三臺不同的電腦進行工作
//抽象類
//CPU
class CPU
{
public:
//抽象計算函式
virtual void Calculate() = 0;
};
//GPU
class GPU
{
public:
//抽象顯示函式
virtual void Display() = 0;
};
//記憶體條
class Memory
{
public:
//抽象儲存函式
virtual void Storage() = 0;
};
//電腦類
class Computer
{
public:
Computer(CPU * cpu, GPU * gpu, Memory * mem)
{
m_cpu = cpu;
m_gpu = gpu;
m_mem = mem;
}
//提供工作的函式
void work()
{
//呼叫零件介面
m_cpu->Calculate();
m_gpu->Display();
m_mem->Storage();
}
//提供解構函式來釋放3個建立在堆區的電腦零件
~Computer()
{
if (m_cpu != NULL)
{
delete m_cpu;
m_cpu = NULL;
}
if (m_gpu != NULL)
{
delete m_gpu;
m_gpu = NULL;
}
if (m_mem != NULL)
{
delete m_mem;
m_mem = NULL;
}
}
private:
CPU * m_cpu;//CPU的零件指標
GPU * m_gpu;//GPU的零件指標
Memory * m_mem;//記憶體條的零件指標
};
//具體廠商零件
//Intel
class IntelCPU :public CPU
{
public:
virtual void Calculate()
{
cout << "Intel CPU is working!" << endl;
}
};
class IntelGPU :public GPU
{
public:
virtual void Display()
{
cout << "Intel GPU is working!" << endl;
}
};
class IntelMemory :public Memory
{
public:
virtual void Storage()
{
cout << "Intel Memory is working!" << endl;
}
};
//AMD
class AMDCPU :public CPU
{
public:
virtual void Calculate()
{
cout << "AMD CPU is working!" << endl;
}
};
class AMDGPU :public GPU
{
public:
virtual void Display()
{
cout << "AMD GPU is working!" << endl;
}
};
class AMDMemory :public Memory
{
public:
virtual void Storage()
{
cout << "AMD Memory is working!" << endl;
}
};
void test01()
{
//no.1 pc
CPU * intelCpu = new IntelCPU;
GPU * intelGpu = new IntelGPU;
Memory * intelMem = new IntelMemory;
Computer * pc1 = new Computer(intelCpu,intelGpu,intelMem);
pc1->work();
delete pc1;
//no.2 pc
CPU * amdCpu = new AMDCPU;
GPU * amdGpu = new AMDGPU;
Memory * amdMem = new AMDMemory;
Computer * pc2 = new Computer(amdCpu,amdGpu,amdMem);
pc1->work();
delete pc2;
}