1 物件導向三大特性:封裝、繼承、多型
物件導向程式設計圍繞著三大特性展開:封裝(Encapsulation)把資料與方法包裝起來、限制外部直接存取;繼承(Inheritance)讓子類別重複使用父類別的程式碼;多型(Polymorphism)讓同一介面在不同物件上有不同表現。
| 特性 | 白話比喻 | 解決的問題 |
|---|---|---|
| 封裝 | 藥品包裝盒,只露出使用說明,內部化學成分被包起來不讓人亂動 | 保護資料、限制不當存取,達到資訊隱藏 |
| 繼承 | 子女繼承父母的姓氏與部分特徵,不用從頭生成 | 避免重複撰寫相同的程式碼 |
| 多型 | 同一個「按下播放鍵」動作,CD player跟隨身聽反應不同 | 讓同一介面在不同物件上有各自的實際表現 |
「資訊隱藏」是封裝的作用(把資料與方法包裝起來、限制外部直接存取內部細節),不是多型的功用。多型指的是「同一介面/方法名稱,在不同物件上有不同表現」,統測很愛把這兩者的定義互相調換來出題,一定要分清楚三大特性各自對應的具體功能。
2 class 的基本結構:private/public 存取修飾字
class 若未指定存取修飾字,成員預設是 private(只有類別內部能存取)。private 成員無法在類別外部(例如main())直接存取,只能透過類別提供的 public 成員函式間接操作。
class Counter {
// 這裡沒寫修飾字,預設是 private
int total;
public:
void SetTotal(int t) { total = t; } // public:外部可呼叫
int GetTotal() { return total; }
};
int main() {
Counter c;
// c.total = 5; // ❌ 錯誤!total是private,外部不能直接存取
c.SetTotal(5); // ✅ 正確,透過public成員函式間接設定
return 0;
}
private成員就像鎖起來的抽屜,外人不能直接打開伸手拿東西,只能透過類別特別開的「窗口」(public成員函式)遞交或索取內容物——這正是封裝「限制外部直接存取」的具體實現方式。
3 class vs struct:預設存取權限的差異
C++ 裡 class 與 struct 基本上是同一種機制,唯一的差別在於未指定存取修飾字時的預設值:class 預設是 private;struct 若未指定,成員預設是 public。
class A { int x; }; // x預設private
struct B { int y; }; // y預設public
int main() {
A a; // a.x = 1; // ❌ 編譯錯誤,x是private
B b; b.y = 2; // ✅ 完全合法,y是public
return 0;
}
| 關鍵字 | 未指定修飾字時的預設存取權限 |
|---|---|
class | private(類別外部不能直接存取) |
struct | public(任何取得該變數的地方都能直接存取) |
struct 宣告出來的物件,跟其他任何型態一樣,完全可以拿來當作另一個 class 的成員使用,兩者並沒有互斥的限制。
4 在類別外定義成員函式:範圍解析運算子 ::
在類別外定義成員函式時,必須用「回傳型態 類別名稱::函式名稱(參數){...}」的完整格式,用 ::(範圍解析運算子)明確標示這個函式屬於哪個類別。
class CalculateArea {
private:
double Length, Width;
public:
void SetPara(double, double);
double GetArea();
};
// ✅ 正確:回傳型態 類別名稱::函式名稱(參數){...}
void CalculateArea::SetPara(double L, double W) { Length = L; Width = W; }
double CalculateArea::GetArea() { return Length * Width; }
// ❌ 錯誤示範:
// double GetArea(){...} 缺少 CalculateArea:: 前綴,變成無關的全域函式
// CalculateArea::GetArea(){...} 缺少回傳型態
// double CalculateArea GetArea(){...} :: 位置放錯
如果忘記寫 類別名稱:: 這個前綴,編譯器會把這個定義當成一個跟該類別完全無關的全域函式,而且這個全域函式內也無法直接存取類別的 private 成員——這不只是少寫一個標記那麼簡單,而是整個函式的歸屬都變了。
5 物件 vs 指標:. 與 -> 的選擇(C++版)
跟第9章的struct規則完全一致:手上拿的是「物件」本身,用 .;手上拿的是「指向物件的指標」,用 ->。統測很愛考「T1、T2 明明是物件,卻誤用了 ->」這種陷阱。
class Volume {
public:
void SetOffset(double offs);
double GetParameter();
} T1(1.0, 2.3), T2; // T1、T2 是 Volume 類別的「物件」,不是指標
int main() {
T2.SetOffset(-3.2); // ✅ 正確:物件用 .
// T1 -> SetPara(100.5); // ❌ 錯誤:T1不是指標,不能用 ->
return 0;
}
像 } T1(1.0,2.3), T2; 這種寫在類別定義結尾、直接列出的變數名稱,宣告的就是「物件」本身,不是指標;只有明確寫了 Volume *p1 = &T1; 這種帶星號的宣告,才會是指標,才能使用 ->。
6 static 成員:所有物件共享同一份資料
static 成員是該類別所有物件共享的同一份資料,不是每個物件各自獨立擁有一份。透過任一物件修改,其它物件看到的值都會一起改變。
class Counter {
private:
static int total; // static:所有物件共享同一份
public:
Counter() { total++; }
static int GetTotal() { return total; }
};
int Counter::total = 0; // 類別外初始化 static 成員,須加上 類別名稱::
int main() {
Counter a, b, c;
printf("%d\n", Counter::GetTotal()); // 3(三個物件共享同一份total)
}
static 成員不能只在類別內部宣告就直接使用,必須在類別外部另外寫一行 型態 類別名稱::成員名稱 = 初始值; 來初始化(如上方的 int Counter::total = 0;),這是static成員特有的、額外的一道語法手續。
7 建構子(Constructor)與解構子(Destructor)
建構子(Constructor)在物件建立的當下自動執行,通常用來初始化成員;解構子(Destructor)則在物件生命週期結束時自動執行,常用來釋放資源。
#include <iostream>
class Logger {
public:
Logger() { std::cout << "建構子執行:物件誕生\n"; }
~Logger() { std::cout << "解構子執行:物件銷毀\n"; }
};
int main() {
Logger log1;
std::cout << "main執行中...\n";
return 0; // log1離開作用域時,解構子會自動被呼叫
}
| 語法規則 | 建構子(Constructor) | 解構子(Destructor) |
|---|---|---|
| 函式名稱 | 必須與類別名稱完全相同 | 類別名稱前面加 ~ |
| 回傳型態 | 不可以指定任何回傳型態(連void都不行) | |
| 參數 | 可以有參數(可多載多個版本) | 不可以有參數 |
| 呼叫時機 | 物件建立的當下自動執行 | 物件生命週期結束時自動執行 |
即使只是不小心多寫了 void(看起來像是「沒有回傳值」而合理),對建構子而言依然是不合法的——建構子在C++語法上根本不存在「回傳型態」這個欄位,寫了任何回傳型態(不管是int、double還是void)都會讓編譯器把它當成一個普通的、恰好跟類別同名的函式,而不是建構子。
8 111~115 歷屆統測真題全選項深度剖析(共 6 題)
以下收錄統測專業科目(二)近年全部 6 題物件導向程式設計真題。每題均提供高解析度原始考題掃描圖、互動選項按鈕與所有選項逐項深入解析:
🔍 四個選項逐項深度剖析
class CalculateArea{
private:
double Length, Width, Area;
public:
void SetPara(double, double);
double GetLength();
double GetWidth();
double GetArea();
};
🔍 四個選項逐項深度剖析
void CalculateArea::SetPara(double L, double W){...} 回傳型態void對應原本的宣告、有正確的 CalculateArea:: 範圍解析前綴、參數型態與原型宣告一致,函式本體也正確地把傳入的L、W指定給Length、Width,語法完全正確,選 (A)。CalculateArea:: 這個範圍解析運算子前綴,這樣寫會被編譯器當成一個全新的、跟CalculateArea類別完全無關的全域函式(同時這個新的全域函式內也無法直接存取Length這個屬於CalculateArea類別的private成員),並不是在定義CalculateArea類別的成員函式。CalculateArea:: 前綴,但缺少了函式的回傳型態(原型宣告裡GetWidth()回傳的是double),少寫回傳型態在C++裡是不合法的語法,無法通過編譯。CalculateArea:: 前綴,但函式宣告要回傳double、函式本體內卻完全沒有 return 陳述式,只是計算並指定了Area的值,卻沒有把結果回傳出去,這在C++裡對一個非void函式而言是不完整的。🔍 四個選項逐項深度剖析
enum Item{_FlowRate, _Time };
class Volume {
private:
double FlowRate, Time;
static double Offset;
public:
Volume (){ FlowRate =0.0, Time = 0.0;}
Volume (double In1, double In2)
{ FlowRate = In1; Time = In2; }
void SetOffset(double offs) { Offset = offs; }
double GetParameter(Item item)
{ return (item == _Time) ? Time : FlowRate ;}
void SetPara(double FR,double T)
{ FlowRate =FR; Time = T;}
protected:
double ToatlFlow(){return FlowRate*Time;}
} T1(1.0,2.3), T2;
double Volume :: Offset = 0.0;
🔍 四個選項逐項深度剖析
.(點運算子),不能用 ->(-> 是給指標用的);-> 用在非指標的物件上會導致編譯錯誤。T1 -> SetPara(...) 用了不該用在物件上的 -> 運算子,應該寫成 T1.SetPara(...) 才符合語法。T2.FlowRate,這行會產生存取權限錯誤,無法通過編譯。. 運算子存取是正確的語法;SetOffset是Volume類別的public成員函式,任何外部程式碼都可以透過物件呼叫,T2.SetOffset(-3.2) 語法與存取權限都正確,選 (D)。🔍 四個選項逐項深度剖析
SetOffset(0.5) 修改Offset後,T1(乃至於任何其他Volume物件)看到的Offset值也會一起變成0.5,選 (A)。Volume(1.0,2.3) 這個帶參數的建構子建立的,會把FlowRate設為1.0(不是0.0),只有像T2這樣沒有指定初始值、使用無參數建構子 Volume() 建立的物件,FlowRate才會是預設值0.0;T1與T2的FlowRate初始值並不相同,這句話的敘述錯誤。T1.ToatlFlow(),這行會產生存取權限錯誤,無法通過編譯。(Item)1 會被轉型成enum Item裡數值為1的成員,也就是 _Time(因為 _FlowRate 是0、_Time 是1),GetParameter(_Time) 依函式定義會回傳Time而不是FlowRate,這句話誤以為會拿到FlowRate的值,實際上拿到的是Time的值。🔍 四個選項逐項深度剖析
Student(int wh){...} 函式名稱正確對應類別名稱、沒有多餘的回傳型態,語法完全符合建構子的規則,選 (A)。int 回傳型態,這在C++裡是不合法的——建構子語法上完全不能有任何回傳型態,即使看起來像是「回傳整數」的建構子也不符合規則,這樣寫編譯器會報錯或把它誤判成一個普通的、恰好跟類別同名的函式。double 回傳型態,違反了建構子不能指定回傳型態的規則,即使參數個數、名稱都合理,光是多了回傳型態這一點就不符合建構子的語法。void 回傳型態(建構子不可以有任何回傳型態,包括void);二是參數列 (height, weight) 沒有寫明資料型態(C++要求函式參數必須明確宣告型態,不能只寫參數名稱),兩個問題都會導致編譯失敗。9 高職段考與模擬精選實戰題(5 題全選項解析)
透過以下 5 題精選模擬試題,全面檢驗你對三大特性、存取權限、::、物件存取與建構子語法的掌握度:
🔍 四個選項逐項深度剖析
class A { int x; };
struct B { int y; };
int main() {
A a; a.x = 1; // 行號1
B b; b.y = 2; // 行號2
return 0;
}
🔍 四個選項逐項深度剖析
a.x=1; 會產生存取權限的編譯錯誤。struct B的成員y同樣沒有指定存取修飾字,但struct預設為public,main()可以直接存取,行號2的 b.y=2; 完全合法,不會有編譯錯誤,選 (A)。class Circle {
private:
double radius;
public:
void SetRadius(double r);
double GetArea();
};
/* 以下要放GetArea()的類別外定義 */
🔍 四個選項逐項深度剖析
double Circle::GetArea(){...} 回傳型態double放在最前面、接著是 Circle:: 範圍解析前綴、再接函式名稱GetArea,完全符合語法,選 (A)。Circle:: 前綴,這樣寫會被編譯器當成一個跟Circle類別完全無關的全域函式,而且這個全域函式內也無法存取Circle類別的private成員radius,無法正確運作。:: 範圍解析運算子的位置錯了,應該放在類別名稱「之後」、函式名稱「之前」(即 Circle::GetArea),而不是放在回傳型態跟類別名稱之間,這種寫法不符合C++語法規則。:: 範圍解析運算子,double Circle GetArea(){...} 這樣的寫法不符合C++「回傳型態 類別名稱::函式名稱」的語法規則,無法通過編譯。class Counter {
public:
static int total;
Counter() { total++; }
};
int Counter::total = 0;
int main() {
Counter a, b;
Counter c;
printf("%d", Counter::total);
return 0;
}
🔍 四個選項逐項深度剖析
Counter(){total++;} 各自被呼叫一次,每次呼叫都讓這唯一共享的total加1,三次呼叫後total從0累加到3,選 (A)。total++; 沒有真正生效,但 Counter::total 就是那個唯一、所有物件共享的static成員,讀取到的必定是被三次建構子呼叫累加後的最新值3,不會是初始值0。int Counter::total = 0; 這行寫在類別外部,是static成員必要的初始化語法(static成員必須在類別外部另外初始化一次),這是完全正確、必要的寫法,不會產生編譯錯誤。🔍 四個選項逐項深度剖析
Book(string title, int pages){...} 函式名稱正確對應類別名稱、沒有多餘的回傳型態,完全符合建構子的語法規則,選 (A)。void 這個回傳型態,違反了建構子不能有任何回傳型態(包括void)的規則。Book 當作回傳型態(等於把類別名稱重複寫了兩次),這同樣違反了建構子不能有回傳型態的規則——即使那個型態剛好跟類別同名,也一樣不合法。~Book(...) 這種在函式名稱前面加上波浪符號 ~ 的寫法,是解構子(Destructor)的語法,不是建構子;解構子用來在物件生命週期結束時自動執行清理工作,而且解構子不能帶有任何參數,這個選項不僅用錯了語法(應該是建構子而非解構子),還多寫了不該有的參數。10 考前衝刺 10 秒速記口訣 & 核心知識檢核清單
封裝=資訊隱藏;繼承=重複利用程式碼;多型=同介面不同表現,別互相混淆。
class未寫修飾字預設private;struct未寫修飾字預設public。
回傳型態 類別名稱::函式名稱(參數){...},::前綴與回傳型態缺一不可。
宣告時沒寫星號就是物件,用.存取;寫了星號才是指標,才能用->。
透過任一物件修改static成員,其他物件看到的值都會一起改變;且需類別外初始化一次。
函式名稱=類別名稱、絕對不能有回傳型態(連void都不行);解構子前面加~且不能有參數。
private/protected成員無法在main()等類別外部直接存取,只能透過public介面間接操作。