專案建置[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 大致能用了
但接下來還會有更多細節及技巧
不管是專案還是物件導向
敬請期待