1. 程式人生 > 其它 >C++類和物件筆記

C++類和物件筆記

筆記參考C++視訊課程 黑馬C++

C++ 面向物件的三大特性:封裝、繼承、多型

目錄

目錄

一、封裝

1.1 封裝的意義-屬性和行為

封裝是C++ 面向物件的三大特性之一

封裝意義一:1、將屬性和行為作為一個整體,表現生活中的事物、

​ 2、將屬性和行為加以許可權控制

語法: class 類名 {訪問許可權: 屬性 / 行為};

示例: 設計一個圓類

#include <iostream>

const double PI = 3.14;
using namespace std;
class Circle
{
  //訪問許可權
public:
  //屬性
  int r;
  //行為
  double calculateZC()
  {
  	return 2 * PI * r;
  }
};
int main()
{
  Circle c1;	//建立例項
  c1.r = 10;
  cout << "圓的周長為:" << c1.calculateZC() << endl;
  return 0;
}

封裝意義二: 類在設計時,可以把屬性和行為放在不同的許可權下,加以控制

三種許可權

  • public 公共許可權 成員在類內可以訪問,類外可以訪問
  • protected 保護許可權 類內可以訪問,類外不可以訪問,子類可以訪問父類中的保護內容
  • private 私有許可權 類內可以訪問,類外不可以訪問,子類不可以訪問父類中的保護內容

示例:

#include <iostream>

using namespace std;
class Father
{
public:
  string name;
protected:
  string car;
private:
  int password;

public:
  void func()
  {
  	//三種屬性類內皆可以訪問
  	name = "張三";
  	car = "賓士";
  	password = 123456;
  }
};
int main()
{
  Father c1;
  c1.name = "李四";
  //c1.car = "大眾"; //protected許可權內容,不能在類外訪問(子類繼承可訪問)
  //c1.password = 1213; //private許可權內容,不能在類外訪問
  return 0;
}

1.2 struct和class的區別

在C++中struct 和class的唯一區別就在於 預設的訪問許可權不同

  • struct 預設許可權為公共
  • class 預設許可權為私有

示例:

class C
{
	//不加許可權預設為私有
	int age;
};

1.3 成員屬性設定為私有

優點1: 將所有成員屬性設定為私有,可以自己控制讀寫許可權

優點2: 對於寫許可權,可以檢測資料的有效性

示例:

class Father
{
	// 設定可讀可寫
	string m_name;
	//設定 只可寫
	int m_age;
	//設定 只可讀
	string m_password = "12345";
public:
	void setName(string name)
	{
		m_name = name;
	}
	string getName()
	{
		return m_name;
	}
	void setAge(int age)
	{
		//對於寫許可權,可以判斷資料的有效性
		if (age < 0 || age > 150)
		{
			m_age = 0;
			cout << "設定的年齡有誤" << endl;
		}
		m_age = age;
	}
	string getPassword()
	{
		return m_password;
	}
};

【案例】判斷點和圓的關係,在圓內、在圓外、以及在圓上

#include <iostream>
//判斷點和圓的關係,在圓內、在圓外、以及在圓上
using namespace std;
//點類
class Point{
private:
	int m_x;
	int m_y;
public:
	void setXY(int x, int y){
		m_x = x;
		m_y = y;
	}
	int getX(){
		return m_x;
	}
	int getY(){
		return m_y;
	}
};
//圓類
class Circle{
private:
	int R;
	Point c_center;
public:
	void setCenter(Point center){
		c_center = center;
	}
	void setR(int r){
		R = r;
	}
	int getR(){
		return R;
	}
	Point getCenter(){
		return c_center;
	}
};
//判斷點與圓的關係
void isinCircle(Circle &c, Point &a){
	int c_x = c.getCenter().getX();
	int c_y = c.getCenter().getY();
	int R = c.getR();
	int x = a.getX(); 
	int y = a.getY();
	int distance = (x - c_x) * (x - c_x) + (y - c_y) * (y - c_y);
	if (distance == R * R){
		cout << "點在圓上" << endl;
	}
	else if (distance > R * R){
		cout << "點在圓外" << endl;
	}
	else{
		cout << "點在圓內" << endl;
	}
}
int main(){
	Circle c;
	Point c_center;
	c_center.setXY(1, 1); //圓心座標
	c.setCenter(c_center);//設定圓心
	c.setR(1);			  //設定半徑
	Point a;
	a.setXY(0, 1);		  //設定點
	isinCircle(c, a);
	return 0;
}

二、物件的初始化和清理

2.1 建構函式和解構函式

  • 建構函式:主要作用在於建立物件時為物件的成員屬性賦值,建構函式由編譯器自動呼叫,無需手動呼叫
  • 解構函式:主要作用在於物件銷燬前系統自動呼叫,執行一些清理工作

建構函式語法: 類名(){}

  1. 建構函式,沒有返回值也不寫void
  2. 函式名稱與類名相同
  3. 建構函式可以有引數,因此可以發生過載
  4. 程式在呼叫物件時候會自動呼叫構造,無需手動呼叫,而且只會呼叫一次

解構函式語法:~類名(){}

  1. 解構函式,沒有返回值也不寫void
  2. 函式名稱與類名稱相同,在名稱前加~
  3. 解構函式沒有引數,不可以發生過載
  4. 程式在物件銷燬前會自動呼叫解構函式,無需手動呼叫,而且只會呼叫一次

2.2 建構函式的分類及呼叫

分類

  1. 按引數:有參構造和無參構造
  2. 按型別:普通構造和拷貝構造

呼叫方式

  1. 括號法
  2. 顯示法
  3. 隱式轉換法

示例:

#include <iostream>

using namespace std;
class Person
{
public:
	int age;
public:
	//無參構造
	Person()
	{
		cout << "Person 建構函式呼叫" << endl;
	}
	//有參構造
	Person(int a)
	{
		age = a;
	}
	//拷貝構造
	Person(const Person& p)
	{
		age = p.age;
	}
	~Person()
	{
		cout << "Person 解構函式的呼叫" << endl;
	}
};
int main()
{
	// 1、括號法
	Person p1;	//預設建構函式呼叫
	Person p2(10);	//有參建構函式
	Person p3(p2);	//拷貝建構函式
	// 2、顯示法
	Person p4;
	Person p5 = Person(10);
	Person p6 = Person(p2);
	// 3、隱式轉換法
	Person p7 = 10;	//有參構造
	Person p8 = p2;	//拷貝構造
	return 0;
}

2.3 拷貝建構函式呼叫時機

  1. 使用一個已經建立完畢的物件來初始化一個新物件
  2. 值傳遞的方式給函式引數傳值
  3. 值方式返回區域性物件

2.4 建構函式呼叫規則

預設情況下,C++編譯器至少給一個類新增3個函式

  1. 預設建構函式(無參,函式體為空)
  2. 預設解構函式(無參,函式體為空)
  3. 預設拷貝建構函式,對屬性進行值拷貝(預設淺拷貝操作)

建構函式呼叫規則如下:

  • 如果使用者定義有參建構函式,C++不再提供預設無參構造,但是會提供預設拷貝構造
  • 如果使用者定義拷貝建構函式,C++不會再提供其他建構函式

2.5 深拷貝與淺拷貝

深拷貝與淺拷貝是面試經典問題,也是常見的一個坑

  • 淺拷貝:簡單的賦值拷貝操作
  • 深拷貝:在堆區重新申請空間,進行拷貝操作

示例:

#include <iostream>

using namespace std;
class Person
{
public:
	Person(int age, int height)
	{
		cout << "Person的有參建構函式呼叫" << endl;
		Age = age;
		Height = new int(height);
	}
	//更改的拷貝建構函式(Height處進行深拷貝操作)
	Person(const Person& p)
	{
		cout << "自己構造的拷貝函式呼叫" << endl;
		Age = p.Age;
        //此處預設淺拷貝操作為 Height = p.Height;
		Height = new int(*p.Height);
	}
	~Person()
	{
		if (Height != NULL)
		{
			delete Height;
			Height = NULL;
		}
		cout << "Person的解構函式呼叫" << endl;
	}
	int Age;
	int* Height;
};
void test()
{
	Person p1(18, 170);
	cout << "p1的年齡為:" << p1.Age << " 身高為:" << *p1.Height << endl;
	Person p2(p1); //預設淺拷貝
	//如果淺拷貝操作,此處會出現指標重複釋放問題
	cout << "p2的年齡為:" << p2.Age << " 身高為:" << *p2.Height << endl;
}
int main()
{
	test();
	return 0;
}

2.6 初始化列表

作用:C++提供了初始化列表語法,用來初始化屬性

語法: 建構函式(): 屬性1(值1), 屬性2(值2) ... {}

示例:

#include <iostream>

using namespace std;
class Person
{
public:
	Person(int a, int b, int c) :m_A(a), m_B(b), m_C(c){}
	int m_A;
	int m_B;
	int m_C;
};
int main()
{
	Person p(10, 20, 30);
	cout << p.m_A << p.m_B << p.m_C << endl;
	return 0;
}

2.7 類物件作為類成員

C++類中的成員可以是另一個類的物件,我們稱該成員為物件成員

示例:可參考1.3【案例】點與圓的關係

class A{}
class B
{
	A a;
}

2.8 靜態成員

靜態成員就是在成員變數和成員函式前加上關鍵字static,稱靜態成員,可分為:

  • 靜態成員變數
    • 所有物件共享一份資料
    • 在編譯階段分配記憶體
    • 類內宣告,類外初始化

示例:

#include <iostream>

using namespace std;
class Person
{
public:
	static int A;	//在類內定義,類外初始化
private:
	static int B;	//靜態成員也有訪問許可權
};
int Person::A = 100;
int Person::B = 200;

int main()
{
	// 1、通過物件訪問
	Person p1;
	cout << p1.A << endl;
	// 2、通過類名訪問
	cout << Person::A << endl;
	Person p2;
	p2.A = 400;	//所有物件共享一份資料
	cout << p1.A << endl;	//400
	//cout << Person::B << endl;	//私有許可權訪問限制
	return 0;
}
  • 靜態成員函式
    • 所有物件共享一個函式
    • 靜態成員函式只能訪問靜態成員變數

示例:

#include <iostream>

using namespace std;
class Person
{
public:
	static void func()
	{
		A = 100;
		//B = 100;	//靜態成員函式不能訪問非靜態成員變數
		cout << "static void func呼叫" << "A = " << A << endl;
	}
	static int A;
	int B;
private:
	static void func2()
	{
		cout << "靜態成員函式也是有訪問許可權的" << endl;
	}
};
int Person::A = 0;
int main()
{
	//1、通過物件訪問
	Person p;
	p.func();
	//2、通過類名訪問
	Person::func();
	//Person::func2();	//訪問許可權限制
	return 0;
}

三、C++物件模型和this指標

3.1 this指標

C++中成員變數和成員函式是分開儲存的,每一個非靜態成員函式只會誕生一份函式例項,也就是說多個同類型的物件會共用一塊程式碼。那麼,這一塊程式碼是如何區分哪個物件呼叫呢?

C++通過提供特殊的物件指標,this指標,解決上述問題。this指標指向被呼叫的成員函式所屬的物件,this指標是隱含每一個非靜態成員函式內的一種指標,this指標不需要定義,直接使用即可。

  • 當形參和成員變數同名時,可用this指標來區分
  • 在類的非靜態成員函式中返回物件本身,可以用return *this

示例:

#include <iostream>

using namespace std;
// 1. 解決名稱衝突
// 2. 返回物件本身 *this
class Person
{
public:
	int age;
	Person(int age)
	{
		this->age = age;
	}
	Person& addAge(Person& p)
	{
		//返回本體要使用引用的方式返回
		this->age += p.age;
		return *this;
	}
};
int main()
{
	Person p1(18);
	cout << p1.age << endl;
	Person p2(18);
	p2.addAge(p1).addAge(p1);
	cout << "p2.Age = " << p2.age << endl;
	return 0;
}	

3.2 const修飾成員函式

常函式:

  • 成員函式後加const,我們稱這個函式為常函式
  • 常函式內不可以修改成員屬性
  • 成員屬性宣告時加關鍵字mutable後,在常函式中依然可以修改

常物件:

  • 宣告物件前加const稱該物件為常物件
  • 常物件只能呼叫常函式

示例:

#include <iostream> 

using namespace std;
// 常函式
class Person
{
public:
    // this指標的本質 是指標常量 指標的指向是不可以修改的
    // const Person * const this 
    // 在成員函式後面加const,修飾的是this指標,使指標指向的值也不可以修改
    Person()
    {
        return;
    }
    void showPerson() const
    {
        // this->m_A = 100;    //常函式內不可以修改普通成員的屬性
        this->m_B = 100;    //特殊成員變數可以修改
    }
    int m_A;
    mutable int m_B;    //特殊變數,即使在常函式中,也可以修改此值
};
int main()
{
    const Person p; //物件前加const 變為常物件
    // p.m_A = 100;    //常物件中不可以修改普通變數
    p.m_B = 100;    //常物件中可以修改特殊變數
    p.showPerson()  //常物件只能呼叫常函式
}

四、友元

生活中家裡有客廳(Public),有臥室(Private)

客廳所有來的客人都可以進去,但是臥室是私有的,也就是說只有自己可以進去,但是也可以允許好基友進去。

在程式中,有些私有屬性,也想讓類外特殊的一些函式或者類進行訪問,就需要用到友元的技術,友元的目的就是讓一個函式或者類訪問另一個類中私有成員

友元的關鍵字為 friend

友元的三種實現

  • 全域性函式作友元 friend void func(arg);
  • 類作友元 friend class ClassName
  • 成員函式作友元

4.1 全域性函式作友元

#include <iostream>
#include <string>

using namespace std;
class Home
{
    //友元函式宣告
    friend void func(Home *home);
public:
    Home()
    {
        livingRoom = "客廳";
        bedRoom = "臥室";
    }
    string livingRoom;
private:
    string bedRoom;
};
void func(Home *home)
{
    cout << "全域性函式正在訪問 --" << home->livingRoom << endl;
    cout << "全域性函式正在訪問 --" << home->bedRoom << endl;
}
int main()
{
    Home myhome;
    func(&myhome);
    return 0;
}

4.2 類作友元

#include <iostream>

using namespace std;
class Home
{
    // 類作友元
    friend class Myfriend;
public:
    string livingRoom;
    Home()
    {
        livingRoom = "客廳";
        bedRoom = "臥室";
    }
private:
    string bedRoom;
};
class Myfriend
{
public:
    Home *home;
    Myfriend()
    {
        home = new Home;
    }
    void visit()
    {
        cout << "好基友類正在訪問: " << home->livingRoom << endl;
        cout << "好基友類正在訪問: " << home->bedRoom << endl;
    }
};
int main()
{
    Myfriend Tom;
    Tom.visit();
    return  0;
}

4.3 成員函式作友元

#include <iostream>
#include <string>

using namespace std;
class Home;
class Myfriend
{
public:
    Home * home; 
    Myfriend();
    void visit();   // 使該函式可以訪問私有成員
    void visit2();  // 使該函式不可以訪問私有成員
};
class Home
{
    // 成員函式作友元宣告
    friend void Myfriend::visit();
public:
    string livingRoom;
    Home();
private:
    string bedRoom;
};
Home::Home()
{
    livingRoom = "客廳";
    bedRoom = "臥室";
}
Myfriend::Myfriend()
{
    home = new Home;
}
void Myfriend::visit()
{
    cout << "visit函式正在訪問:" << home->livingRoom << endl;
    cout << "visit函式正在訪問:" << home->bedRoom << endl;
}
void Myfriend::visit2()
{
    cout << "visit函式正在訪問:" << home->livingRoom << endl;
}
int main()
{
    Myfriend Tom;
    Tom.visit();
    Tom.visit2();
    return 0;
}

五、運算子過載

運算子過載概念:對已有的運算子重新進行定義,賦予其另一種功能,以適應不同的資料型別

5.1 加號運算子過載 +

作用:實現兩個自定義資料型別相加(重新定義的加法)的運算

#include <iostream>

using namespace std;
class Person
{
public:
    int A;
    int B;
    Person()
    {
        A = 10;
        B = 20;
    }
};
Person operator+(Person &p1, Person &p2)
{
    Person temp;
    //可自定義運算
    temp.A = p1.A + p2.A;   
    temp.B = p1.B - p2.B;
    return temp;
}
int main()
{
    Person p1;
    Person p2;
    Person p3 = p1 + p2;
    cout << p3.A << " " << p3.B << endl;
    return 0;
}

5.2 左移運算子過載 <<

#include <iostream>

using namespace std;
class Person 
{
public:
    int A;
    int B;
    Person()
    {
        A = 10;
        B = 20;
    }
};
ostream & operator<<(ostream &cout, Person &p)
{
    // cout 屬於ostream型別
    cout << "A = " << p.A << " B = " << p.B;
    return cout;
}
int main()
{
    Person p;
    cout << p << endl;
    return 0;
}

過載左移運算子配合友元可以實現輸出自定義資料型別

5.3 遞增運算子過載 ++

作用:通過過載遞增運算子,實現自己的整型資料

#include <iostream>

using namespace std;
class MyIntergeer
{
public:
    int num;
    MyIntergeer()
    {
        num = 0;
    }
    //前置遞增過載
    MyIntergeer &operator++()
    {
        num++;
        return *this;
    }
    //後置遞增過載
    MyIntergeer &operator++(int)    //int代表一個佔位引數
    {
        // 先 記錄當時結果
        MyIntergeer temp = *this;   //記錄當前本身的值,然後讓本身的值加一,但是返回以前的值,達到先返回後++
        // 後 遞增
        num++;
        // 最後將記錄結果做返回
        return temp;    //後置遞增返回的是值
    }
};
// 實現左移運算子過載
ostream &operator<<(ostream &cout, MyIntergeer myint)
{
    cout << myint.num;
    return cout;
}
//前置遞增測試
void test01()
{
    MyIntergeer myint;
    cout << ++myint <<endl;
    cout << myint << endl;
}
//後置遞增測試
void test02()
{
    MyIntergeer myint;
    cout << myint++ << endl;
    cout << myint << endl;
}
int main()
{
    test01();
    test02();
    return 0;
}

5.4 賦值運算子過載 =

C++編譯器至少給一個類新增四個函式

  1. 預設建構函式(無參,函式體為空)
  2. 預設解構函式(無參,函式體為空)
  3. 預設拷貝建構函式,對屬性進行值拷貝
  4. 賦值運算子 operator=,對屬性進行值拷貝

如果類中有屬性指向堆區,做賦值操作時也會出現深淺拷貝問題

示例:

#include <iostream>

using namespace std;
class Person
{
public:
    int *Age;
    Person(int age)
    {
        Age = new int(age);
    }
    ~Person()
    {
        if (Age != NULL)
        {
            delete Age;
            Age = NULL;
        }
    }
    //過載賦值運算子
    Person &operator=(Person &p)
    {
        if (Age != NULL)
        {
            delete Age;
            Age = NULL;
        }
        Age = new int(*p.Age);
        return *this;
    }
};
int main()
{
    Person p1(18);
    Person p2(20);
    Person p3(30);
    p3 = p2 = p1;
    cout << "p1的年齡為:" << *p1.Age << endl;
    cout << "p2的年齡為:" << *p2.Age << endl;
    cout << "p3的年齡為:" << *p3.Age << endl;
}

5.5 關係運算符過載 > < == !=

#include <iostream>
#include <string>

using namespace std;
class Person
{
public:
    string Name;
    int Age;
    Person(string name, int age)
    {
        Name = name;
        Age = age;
    }
    bool operator==(Person &p)
    {
        if(this->Name == p.Name && this->Age == p.Age)
        {
            return true;
        }
        return false;
    }
};
int main()
{
    Person p1("Tom", 18);
    Person p2("Tom", 18);
    if (p1 == p2)
    {
        cout << "p1和p2相等" << endl;
    }
}

4.6 函式呼叫運算子過載 ()

  • 函式呼叫運算子 () 也可以過載
  • 由於過載後使用的方式非常像函式的呼叫,因此稱為仿函式
  • 仿函式沒有固定方法,非常靈活

示例:

#include <iostream>

using namespace std;
class MyPrint
{
public:
    void operator()(string test)
    {
        cout << test << endl;
    }
};
int main()
{
    MyPrint myprint;
    //使用時非常像函式,因此稱為仿函式
    myprint("hello");
    return 0;
}

六、繼承

6.1 繼承的基本語法

繼承是面向物件的三大特性之一,利用繼承可以有效減少重複程式碼

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

示例:

class Sub: public Base{}

繼承方式:

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

6.2 繼承中的物件模型

#include <iostream>

using namespace std;
class A
{
public:
    int A;
protected:
    int B;
private:
    int C;
};
class B: public A
{
public:
    int D;
};
int main()
{
    B b;
    // 子類會將父類的所有成員繼承,包括編譯器隱藏不可訪問的私有成員
    cout << "sizeof b = " << sizeof(b) << endl;  // 16
    return 0;
}

使用開發人員命令提示符 cl /d1 reportSingleClassLayout類名 檔名 可檢視單個類的具體佈局

6.3 繼承中構造和析構順序

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

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

示例:

#include <iostream>

using namespace std;
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 test()
{
    Son s;
}
int main()
{
    test();
    return 0;
}

繼承中的構造和析構順序:先構造父類、後構造子類,析構的順序與構造的順尋相反

6.4 繼承同名成員處理方式

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

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

示例:

s.Base.func()	//加作用域
s.func()	//預設為子類函式

當子類與父類擁有同名的成員函式,子類會隱藏父類中同名成員函式(包括過載同名函式)

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

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

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

示例:

#include <iostream>

using namespace std;
class Base
{
public:
    static int A;
    static void func();
};
int Base::A = 100;
void Base::func()
{
    cout << "Base - func" << endl;
}
class Son :public Base
{
public:
    static int A;
    static void func();
};
int Son::A = 200;
void Son::func()
{
    cout << "Son - func" << endl;
}
int main()
{
    cout << "1. 通過物件訪問" << endl;
    Son s;
    cout << s.A << endl;
    s.func();
    s.Base::func();
    cout << "2. 通過類名訪問" << endl;
    cout << Son::A << endl;
    Son::func();
    Son::Base::func();
    return 0;
}

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

6.5 多繼承語法

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

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

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

【注】C++實際開發中不建議使用多繼承

6.6 菱形繼承

菱形繼承概念:

  • 兩個派生類繼承同一個基類
  • 又有某個類同時繼承著兩個派生類
  • 這種繼承被稱為菱形繼承,或者鑽石繼承

典型的菱形繼承案例:

菱形繼承問題:

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

using namespace std;
class Animal //動物類
{
public:
    int Age;
};
// 利用虛繼承 解決菱形繼承問題
// 繼承之前 加上關鍵字 virtual 變為虛繼承
// Animal類稱為 虛基類
class Sheep :virtual public Animal{};  //羊類
class Camel :virtual public Animal{};  //駝類
class Alpaca :public Sheep, public Camel{};    //羊駝類
int main()
{
    Alpaca alpaca;
    alpaca.Sheep::Age = 18;
    alpaca.Camel::Age = 28; //對於菱形繼承,來自第一個父類的資料會繼承兩份
}

七、多型

7.1 多型的基本概念

多型是C++面向物件的三大特性之一

多型分為兩類

  • 靜態多型:函式過載和運算子過載屬於靜態多型,複用函式名
  • 動態多型:派生類和虛擬函式實現執行時多型

靜態多型和動態多型的區別

  • 靜態多型的函式地址早繫結 - 編譯階段確定函式地址
  • 動態多型的函式地址晚繫結 - 執行階段確定函式地址、

多型的優點

  • 程式碼組織結構清晰
  • 可讀性強
  • 利於前期和後期的擴充套件及維護

7.2 多型案例—計算器類

案例描述:

分別利用普通寫法和多型技術,設計實現兩個運算元進行運算的計算器類

示例:

#include <iostream>

using namespace std;
class AbstractCalculator
{
public:
    virtual int getResult()
    {
        return 0;
    }
    int num1;
    int num2;
};
class AddCalculator :public AbstractCalculator
{
public:
    int getResult()
    {
        return num1 + num2;
    }
};
class SubCalculator :public AbstractCalculator
{
public:
    int getResult()
    {
        return num1 - num2;
    }
};
class MulCalculator :public AbstractCalculator
{
public:
    int getResult()
    {
        return num1 * num2;
    }
};
int main()
{
    //多型使用條件
    //父類指標或者引用指向子類物件
    AbstractCalculator * abc = new AddCalculator;
    abc->num1 = 100;
    abc->num2 = 100;
    cout << abc->num1 << "+" << abc->num2 << " = " << abc->getResult() << endl;
    delete abc; //只是資料消除了,指標的型別沒有變

    abc = new SubCalculator;
    abc->num1 = 100;
    abc->num2 = 100;
    cout << abc->num1 << "-" << abc->num2 << " = " << abc->getResult() << endl;
    return 0;
}

7.3 純虛擬函式和抽象類

在多型中,通常父類函式中的虛擬函式是無意義的,主要是呼叫子類重寫的內容

因此可以將虛擬函式改為純虛擬函式

純虛擬函式語法:virtual 返回值型別 函式名 (引數列表) = 0

當類中有了純虛擬函式,這個類也稱為抽象類

抽象類特點:

  • 無法例項化物件
  • 子類必須重寫抽象類中的純虛擬函式,否則也屬於抽象類

示例:

#include <iostream>

using namespace std;
class Base
{
public:
    //純虛擬函式與抽象類
    virtual void func() = 0;
};
class Son :public Base
{
public:
    virtual void func()
    {
        cout << "func 函式呼叫" << endl;
    }
};
int main()
{
    Base * abc = new Son;
    abc->func();
    return 0;
}

7.4 虛析構和純虛析構

多型使用時,如果子類中有屬性開闢到堆區,那麼父類指標在釋放時無法呼叫到子類的析構程式碼

解決方式:將父類中的解構函式改為虛析構純虛析構

虛析構和純虛析構共性:

  • 可以解決父類指標和釋放子類物件
  • 都需要有具體的函式實現

虛析構和純虛析構的區別:

  • 如果是純虛析構,該類屬於抽象類,無法例項化物件

虛析構語法: virtual ~類名() {}

純虛析構語法:virtual ~類名() = 0;

類名::類名() {}

總結:

  1. 虛析構或純虛析構就是用來解決通過父類指標釋放子類物件
  2. 如果子類中沒有堆區資料,可以不寫為虛析構或純虛析構
  3. 擁有純虛解構函式的類也屬於抽象類