天天看點

【OOP】封裝:類與對象

OOP封裝:介紹了【類與對象】的概念,也介紹了其他相關的知識,如:【内聯函數】、【函數重載】、【this指針】等。

目錄

  • 引言
  • 類 & 對象
  • 類的成員
    • A)成員通路權限
    • B)成員變量
      • i.

        auto

      • ii.

        decltype

    • C)成員函數
  • 補充知識
    • 1. 内聯函數
    • 2. 函數重載
    • 3. this 指針

引言

面向對象程式設計(OOP),最核心的概念就是 “對象” 。

簡單地說,資料 + 函數 = 對象。

将一組資料和相關的函數結合起來,構成一個結合體,就是封裝的過程。

回到頂部

類 & 對象

類(class)是一種使用者自定義的類型,包含了資料和函數。

  • 類中的資料 = 成員變量
    • 在類内定義
  • 類中的函數 = 成員函數
    • 在類内聲明
    • 可以在類内 / 類外實作
    class Person {                        // 自定義的類
    public:
        string name;                      // 成員變量
        
        void get_name () {                // 成員函數
            cout << "Name: " << name;
        }
    }
               
  • 定義了類後,将類當成一個資料類型,去定義一個變量,這個變量就是類的對象。
    class Person {
        ......
    }
    
    Person a;                    // a 是 Person 類的對象
    a.name = "Karry";            // 使用對象的成員變量的方法
    a.get_name();                // 調用對象的成員函數的方法
               

回到頂部

通常,為了友善管理,我們會将不同的類放在不同的頭檔案和實作檔案裡:

  • 頭檔案:成員變量、成員函數的聲明
    // 頭檔案 Person.h
    #fndef PERSON
    #define PERSON
    
    class Person {
        ......
    }
    
    #endif
               
  • 實作檔案:部分成員函數的實作
    // 實作檔案 Person.cpp
    #include "Person.h"
    
    ...... // 部分函數實作
               

回到頂部

類的成員

類中的成員主要有:成員變量、成員函數

A)成員通路權限

通路權限分成三種等級:public、private 和 protected。

這裡不詳細說明三者的性質,隻需簡單了解,

public 的成員是可以在類外通路的,但是 private 的成員隻能在類内通路。

class Person {
    int age;           // 預設的通路權限為 private
    
public:
    string name;       // public 通路權限
}

int main() {
    Person a;
    a.name = "Karry";  // name 是 public 的成員變量,可以在類外通路
    // a.age = 23;     // age 是 private 的成員變量,沒有權限在類外通路
}
           

回到頂部

B)成員變量

成員變量的聲明方式和普通的變量相同。

除了常見的資料類型,如:

int

float

char

等,還有

auto

decltype

以下為

auto

decltype

的介紹,可适用于成員變量、成員函數、普通變量、普通函數等。

i.

auto

特點:

  • 必須在編譯期确定類型
  • 編譯器會根據上下文自動确定變量的類型

使用的注意事項:

  • 定義的同時進行初始化
  • 如果是用來定義一連串的資料,這些資料必須為同樣的資料類型
  • 可用于:定義變量(尤其是複雜的類型)、追蹤函數的傳回類型
    // 定義變量
    auto num = 21;
    auto a = "Karry", b = "Wang";
    
    // 追蹤函數的傳回類型
    auto func(int a, int b) -> int {
        return a + b;
    }
               
  • 不可用于:函數的參數

回到頂部

ii.

decltype

特點:

  • 可以對變量 / 表達式的類型進行推導
    struct {
        string name;
        int id;
    } student;
    
    int main() {
        decltype(student) x;        // 推導出 student 的類型,再用類型定義 x
    }
               

使用的方法:

  • 結合

    auto

    decltype

    ,可以自動追蹤函數的傳回類型
    auto func(int a, int b) -> decltype(a + b) {
        return a + b;
    }
    
    auto a = func(6, 9);
               

回到頂部

C)成員函數

成員函數的實作方式有:類内實作 / 類外實作

class Person {
public:
    string name;
    
    // 方法1:類内實作
    void get_name() {
        cout << "Name: " << name << endl;
    }
}

// 方法2:類外實作
void Person::get_name() {
    cout << "Name: " << name << endl;
}
           

定義在類内的成員函數,會被預設為内聯函數,是以大多數情況下,我們會将類的實作和聲明分開,便于管理。

有關内聯函數的更多說明可以在後文的 “補充知識“ 中找到。

關于成員函數,有一些延伸的知識,可以點選後方連結,跳轉到相關部分,如:

  1. 如果需要用到若幹個同名但是功能不同的函數,應該怎麼處理?[函數重載]
  2. 成員函數是如何使用成員變量的?[this 指針]

回到頂部

補充知識

1. 内聯函數

内聯函數(inline function)是一種特别的函數,可以在調用函數的地方生成與函數等價的表達式。例:

inline int min(int a, int b) {
    return a < b ? a : b;
}

cout << min(2, 5) << endl;
           

以上代碼等價于:

cout << (a < b ? a : b) << endl;
           

簡單地說,内聯函數就是将函數的實作直接 ”植入“ 我們調用函數的地方。

雖然,類似的,我們也可以用宏定義(define)來實作這個功能,但是内聯函數和宏定義有着一定的差別。

内聯函數(inline) 宏定義(define)
操作 在函數調用的地方生成等價的表達式 将宏定義的代碼直接拷貝到目的地
功能 可以進行調試、執行檢查 -
安全 不容易出錯 容易出錯

使用内聯函數的注意事項:

  1. 内聯函數的聲明和實作不可以分開
  2. 如果函數包含大段代碼 / 循環 / 複雜結構,避免使用内聯函數
  3. 編譯器有決定權,内聯修飾符隻是一個建議:
    • 如果函數不值得内聯,就會忽略内聯修飾符
    • 如果覺得一些函數需要内聯,會将其自行轉換為内聯函數
  4. 定義在類内的成員函數都被預設為内聯函數
    • 構造函數、析構函數通常都是内聯函數
    • 大段 / 複雜的成員函數建議進行類外定義

回到頂部

2. 函數重載

函數重載:若幹個函數有着同樣的函數名(大機率功能是相似的),但是處理的資料資訊不同。

使用函數重載需要注意的事項:

  1. 函數的參數類型 / 數量需要有變化
  2. 函數的傳回類型 / 函數參數的名稱不能作為函數重載的區分辨別
  3. 如果函數參數有使用預設值,要避免出現二義性
    預設值是函數參數中的預設值,例:
    void print_num(int input = 2) {
        cout << "The number is " << input << endl;
    }
    
    print_num(5);    // The number is 5
    print_num();     // The number is 2
               

    使用預設值時要注意,把參數是預設值的都往後放,避免混淆。

    如果函數參數内有預設值時,要注意避免二義性的使用,例:

    void func(int a, int b = 1) {
        cout << "The sum is " << a + b << endl;
    }
    
    void func(int a) {
        cout << "The number is " << a << endl;
    }
    
    int main() {
        //func(5);     // 這種情況下會導緻函數重載失敗,無法區分兩個函數的使用
        
        return 0;
    }
               

函數重載調用時的操作:

  1. 會優先調用類型比對的函數,例:

    int

    的資料就優先用

    int

    參數的函數 ......
  2. 如果沒有類型比對的函數,會進行内置類型轉換,例:

    int

    的轉去

    float

    ......
    void print_num(int a) {
        cout << "The number is " << a << endl;
    }
    
    print_num(5.2);    // 會進行類型轉換,輸出 The number is 5
               

回到頂部

3. this 指針

類的成員函數,和普通的函數不同的是,它們可以使用類的成員變量。

這是因為,所有成員函數的參數中都有一個隐藏的指針變量

this

,指向目前對象,例:

void Person::get_name() {
    cout << "The person name is " << this->name << endl;
}
           

回到頂部

繼續閱讀