物件導向初探

專案建置[5]

講師:已經講了三次 class 的溫室蔡

物件導向四大原則

封裝(Encapsulation)

繼承(Inheritance)

 多型(Polymorphism)

抽象化(Abstraction)

抽象化(Abstraction)

把程式裡的東西視為「物件」的動作

你可能會覺得這樣是「具體」

但這裡的抽象是對電腦而言

而對電腦來說最具體的東西

是四則運算、記憶體操作和指令跳轉

封裝(Encapsulation)

將物件的實作細節隱藏起來

同時保護這些細節不被外部任意修改

並提供「介面」讓使用者安全地存取

因而也出現了「成員權限」的概念

道理我都懂,但是為什麼

以上次實作的 LinkedList 為例

成員變數全部都沒被保護

int main() {
    Aaw::LinkedList ls;
    for (int i=0; i<5; i++) {
        ls.insert(i, ls.end());
    }
    ls.print(); // 0 1 2 3 4
    // 以下開始亂搞
    ls.listSize = 0;
    ls.endNode = ls.endNode->prev->prev;
    // 亂搞的後果
    std::cout << ls.empty() << '\n'; // true
    ls.print(); // 0 1 2
    return 0; // 可能產生記憶體洩漏
}

成員權限

C++ 的 struct 跟 class 都有三種等級的權限

權限等級 外部存取 繼承者存取
private
protected
public

C++ 中的 class

在 C++ 中,struct 跟 class 唯一的區別

就是 struct 的成員權限預設是 public

class 則是 private

class Person {
    std::string name;
    double height;
    double weight;
    double bmi;
};

int main() {
    Person p;
    p.name = "Justin"; // Error
    return 0;
}

設定成員權限

在宣告的時候

class Person {
private:
    double height;
    double weight;
    double bmi;
public:
    std::string name;
    Person(std::string name,
           double height,
           double weight) { /*...*/ }
};

int main() {
    Person p("Justin", 165, 62);
    std::cout << p.name << '\n'; // Justin
    std::cout << p.height << '\n'; // Error
    return 0;
}

用權限加冒號

表示以下的成員

都是這個權限

Getter & Setter

由於 private 成員

class Person {
private:
    double _height;
    double _weight;
    double _bmi;
public:
    std::string name;
    Person(/*...*/) { /*...*/ }
    // Getter
    double height() { return this->_height; }
    // Setter
    double set_height(double new_height) {
        this->_height = new_height;
        // ...
    }
};

int main() {
    Person p("Justin", 165, 62);
    std::cout << p._height << '\n'; // Error
    std::cout << p.height() << '\n' // 165
    p._height = 170; // Error
    p.set_height(170);
    std::cout << p.height() << '\n' // 170
    return 0;
}

不能直接被存取

所以要另外寫

Getter 跟 Setter

當作存取的「介面」

快樂實作時間

請各位去上次的 GitHub

用 class 改寫 LinkedList

並設定成員權限

提示:只有宣告區要改

改這個

答案

新語法?!

你有沒有注意到

我們 LinkedList 的 insert 跟 erase 這兩個方法

都出現了一個沒看過的語法

void LinkedList::insert(int val, LinkedList::node* pos) {
    if (pos == this->startNode) {
        throw "ERROR: trying to insert at startNode (a virt. node before .begin()).";
    }
    // ...
}

void LinkedList::erase(LinkedList::node* pos) {
    if (pos == startNode || pos == endNode) {
        throw "ERROR: erasing a virt. node.";
    }
    // ...
}

錯誤處理

在撰寫函式庫的時候

要考量到使用者可能不會正確地使用它們

進而發生錯誤

這時就要仰賴錯誤處理機制

防止繼續錯下去

並提供實用的錯誤訊息

try-throw-catch

C++ 的錯誤處理分成三步驟:

1. 試著(try)做某件事

2. 在做事的過程中,程式拋出(throw)錯誤

3. 把錯誤抓住(catch),並進行應對措施

int main() {
    try {
        throw 錯誤訊息;
    } catch (錯誤型別 錯誤訊息) {
        // 應對措施
    }
    return 0;
}

錯誤處理範例

#include <iostream>

int main() {
    try {
        int x;
        std::cin >> x;
        if (x > 10) {
            throw "Error: Number too big.";
        }
        std::cout << "Your number is " << x << '\n';
    } catch (const char* e) {
        std::cout << e << '\n';
    }
    return 0;
}

throw 不一定要「寫在」try 裡面

#include <iostream>

void get_number() {
    int x;
    std::cin >> x;
    if (x > 10) {
        throw "Error: Number too big.";
    }
    std::cout << "Your number is " << x << '\n';
}

int main() {
    try {
        get_number();
    } catch (const char* e) {
        std::cout << e << '\n';
    }
    return 0;
}

實作時間 II

我們的 LinkedList 有個 find 方法

請修改它,使得找不到指定的值時會拋出錯誤

接著建一個 LinkedList

insert 1~10,但故意少一個

用 find 跟 try-except 找出是缺了哪個數字

恭喜

有沒有大概抓到物件導向的感覺了呢

我們現在的 LinkedList 大致能用了

但接下來還會有更多細節及技巧

不管是專案還是物件導向

敬請期待