---
title: 'C++ 物件 & 類別'
disqus: kyleAlien
---
C++ 物件 & 類別
===
## OverView of Content
如有引用參考請詳註出處,感謝 :smile:
C++ 以類別為中心
[TOC]
## 程序式 & 物件導向
程序式 : **先思考程序** (演算法為重),再思考如何表達
物件導向設計(OOP) : **先思考資料** (資料為重),再思考如何表達
:::success
* **OOP 特性**
1. 抽象化
2. 封裝
3. 同名宣告,不同定義
4. 繼承
5. 程式在利用
:::
## 抽象化 & 類別
* **抽象化** : 抽象化是以 **使用者介面定義**,它是表示介面的資訊,**取出基本操作的特性,並以特性表示方法 (Function)**,可看作 **==方法的宣告==**
* **類別 Class** : C++ 將**抽象化轉為使用者自定義的工具**,它結合 **==資料成員、型態==** & 處理資料的 **==成員函數==**
```cpp=
#include <string>
class PersonInfo {
private :
// 資料成員
std::string name;
int age;
int idNum;
public :
// 抽象
void setInfo(std::string name, int age, int id);
std::string getInfo();
std::string getName();
int getAge();
int getIdNum();
};
```
### 物件型態
* 型態可以以 `struct`、`enum`、`union`、`class` ... 表示出來,其**主要作用在於三點**
1. **多少的記憶體** : 該資料需要記憶體的總量
2. **如何去解釋** : 每個記憶體讀取出來後要如何解釋
3. **如何操作** : 取出的資料可以如何操作,就像是指標不能拿來做乘法
下面範例新增一個 **struct MyStruct 型態**,內部放置資料、指標、short、short、int
```cpp=
#include <iostream>
using namespace std;
union MyUnion {
short Word;
struct {
char Low : 8;
char Height : 8;
} Byte ;
};
enum MyEnum {
Hi,
Alien,
Welcom
};
struct MyStruct {
char *name;
short number;
short age;
int id;
};
int main() {
union MyUnion *myUnion = new union MyUnion;
myUnion->Byte.Height = 0x01;
myUnion->Byte.Low = 0x01;
cout << "MyUnion: " << myUnion->Word << endl;
enum MyEnum *myEnum = new enum MyEnum;
cout << "MyEnum: " << myEnum << endl;
struct MyStruct *myStruct = new struct MyStruct;
myStruct->age = 100;
cout << "MyStruct: " << myStruct->age << endl;
return 0;
}
```
> 
MyStruct 總共需要空間為,`4 (ptr)` + `2 (short)` + `2 (short)` + `4 (int)` = `12 Byte`
> 解釋、操作則按照 MyStruct 型態
> 
### 類別 class 封裝
* C++ 的關鍵字 `class` 標示該程式碼定義為類別,將 **==資料成員==、==成員函數聯繫 (binding)==**,成立一個獨立單元 **就可稱為類別 class**
* **將資料放置在一起併與抽象化 (不定義函數體) 分離稱為封裝**
```cpp=
#include <iostream>
#include <string>
using namespace std;
class PersonInfo {
private :
// 資料成員
string name;
int age;
int idNumber;
public :
// 抽象方法 "3. "
void setInfo(string name, int age, int id);
string getInfo();
string getName();
int getAge();
int getIdNum();
};
```
1. 可把 `PersonInfo` 稱為 **物件 object** or **實體 instance**
2. **可直接宣告後定義方法**,C++ 會轉換回內嵌函數 (inline method),會面會在提到 inline 函數
3. **也可只宣告函數原型**,定義由其他檔案去完成
**--封裝--**
> **資料 & 宣告分離**
>
> 
:::success
* `struct` & `class` 差異
| 關鍵字 | 存取權限 | 使用 |
| -------- | -------- | -------- |
| struct | **public** | 純粹作為資料 |
| class | **private** (可控) | 作為抽象宣告函數、並可包含資料成員 |
:::
### 存取控制權
* 存取控制權 : 可以看到上面程式碼中有 `public`、`private`、`protect` 關鍵字,**class 類別用於==隔絕==使用者直接控制資料成員** (符合 OOP 原則),避免使用者直接存取的**功能 private 又稱為 資料隱藏 (data hiding) ==作用在於保持資料完整性==**
* **預設控制權為 private**,大部分會寫出來,寫出來可以**強調資料特性**,**存取 private 資料的==唯一方式==是透過函數**
```cpp=
class PersonInfo {
std::string name; // default is private
int age; // default is private
int idNum; // default is private
public :
// 抽象
void setInfo(std::string name, int age, int id);
std::string getInfo();
std::string getName();
int getAge();
int getIdNum();
};
```
:::info
class 後面記得要加 `;` 符號
:::
### 實作 class 抽象函數
* 抽象函數可存取 private 的成員變量,**若非成員函數要存取私有變量,編譯器會 Error,但除了 ==friend 函數例外==**
* 實作函數時要使用 ==**範疇運算子 `::`**==,**標示函數所屬的類別**,類似於 namespace 指定變數
1. **Class 類定義**:定義抽象方法、成員,並定義一個私有方法
```cpp=
#ifndef CLASS__TESTCLASS_H_
#define CLASS__TESTCLASS_H_
#include <string>
#include <iostream>
using namespace std;
class PersonInfo {
private :
// 資料內容
string name;
int age;
int idNum;
string info() {
string str;
str = "name: " + name + ", age: " + to_string(age) + ", id:" + to_string(idNum);
return str;
}
void show() {
cout << info() << endl;
}
public :
void setInfo(string name, int age, int id);
string getInfo();
string getName();
int getAge();
int getIdNum();
};
#endif /* CLASS__TESTCLASS_H_ */
```
2. **Class 類定義**:
```cpp=
#include "TestClass.h"
void PersonInfo::setInfo(string name, int age, int id) {
this->name = name;
this->age = age;
this->idNum = id;
}
//"1. "
string PersonInfo::getInfo() {
return this->info(); //"2. " Occur error
}
string PersonInfo::getName() {
return this->name;
}
int PersonInfo::getAge() {
return this->age;
}
int PersonInfo::getIdNum() {
return this->idNum;
}
```
1. 有 **使用範疇運算子** 的稱為 **標示名稱**、而沒有使用的稱為 **未標示名稱**,Ex: public 函數會使用 private 函數
2. **`this` 成員函數可以使用 `private` 的成員變量、函式,而 ++外部呼叫者不可以使用++,保護資料成員的安全性**
**--實作--**
> 
### inline 成員函數
* 在 class 內有 **共同使用到的函數**,就可直接在 class **宣告時就定義出來,這會被編譯器==自動轉為內嵌 inline 函數==**
* 當然也可以不再宣告時就定義,可分開做,但是要遵照 inline **特別規則**
> 定義在使用它們的每個檔案中,也就是說**多少檔案 include,就要實作多少次**
1. **Class 抽象定義**
```cpp=
/**
* TestClass.h 封裝 PersonInfo
*/
class PersonInfo {
private :
// 資料內容
...
void show() { //"1. "
std::cout << info() << std::endl;
}
public :
// 抽象
...
void hello(); // 新增 private 方法
};
```
* 自動翻譯成 inline 函數,inline 可有可無 (限於功能來說,但 inline 不等於一般函數)
:::success
* inline 更像是把函數複製到指定呼叫位置,不會出入 Stack
:::
2. **Class 實作**:其實這樣很不方便,所以大部分共用程式都會在宣告時就定義
```cpp=
/**
* TestClass.cpp 實作 PersonInfo
*/
inline void PersonInfo::hello() { //"2. "
std::cout << "Hello " << std::endl;
}
```
:::warning
如果定義在 private 中,則不能在不同檔案中定義
:::
3. main 直接內聯函數
```cpp=
int main() {
PersonInfo info;
info.setInfo("Alien", 20, 1234);
cout << info.getInfo() << endl;
info.hello();
return 0;
}
```
> 
### class 函數 & 資料關係
* **每一個新建的物件都有==自己==的內部變數** (各自的變數儲存空間 in memory),但是**宣告實作函數是共用的**
:::info
* 在 OOP 中呼叫共用函數的動作稱為 `傳遞訊息 (sending Message)`
:::
1. 原型 Header
```cpp=
/**
* 原型 P.h
*/
#include <iostream>
class P {
public:
int test = 0;
void show();
}
```
2. 實做類 Cpp source
```cpp=
/**
* 實作
*/
#include "P.h"
void P::show() {
std::cout << "value: " << test << std::endl;
}
/**
* 使用者
*/
#include "P.h"
int main() {
P p1, p2;
std::cout << &p1.test << "\n";
std::cout << &p2.test << "\n";
return 0;
}
```
物件創建概念圖
> 
**--實作--**
> 可看出創建兩個物件後,其參數是各自放置不同位子的
>
> 
## 建構函數、解構函數
* **建構函數** : 與類別同名且無型態,並無回傳值,**在宣告類別時==自動==呼叫建構函數**,不管是靜態、動態創建
> 宣告方式:`MyClass()`
* **解構 (析構) 函數** : Func 前有一個反向符號,與類別同名且無型態,也無回傳值,**在類別離開範疇、或是 delete 時==自動==呼叫析構函數**
> 宣告方式:`~MyClass()`
:::success
* **Java & C++ 解構**
Java 中就沒有解構函數,不過它有個類似的函數 `finalize`,不過由於 Java 會自動回收內存,所以不用考慮太多解構
:::
1. Class 抽象宣告
```cpp=
/**
* MyClass.h
*/
#include <iostream>
class MyClass {
public:
MyClass();
~MyClass();
};
```
2. Class 定義建構、解構函數
```cpp=
/**
* MyClass.cpp
*/
#include "MyClass.h"
MyClass::MyClass() {
std::cout << "MyClass 建構函數" << std::endl;
}
MyClass::~MyClass() {
std::cout << "MyClass 解構(析購)函數" << std::endl;
}
```
3. 靜態對象建構
```cpp=
/**
* main.c
*/
#include <iostream>
#include "./MyClass/MyClass.h"
int main() {
MyClass m1;
return 0;
}
```
**--實做--**
> 
### 建構函數 - 初始化 (預設參數)
* 可 **使用預設引數,減少類別的初始化**;在宣告時定義預設引數,**定義時不可使用預設引數**,定義函數內部還是 **要指定到內部成員**
1. Class 抽象宣告
```cpp=
/**
* MyClass.h
*/
#include <iostream>
#include <string>
using namespace std;
class MyClass {
private:
int age;
string name;
public:
MyClass();
MyClass(string name, int age);
~MyClass();
};
```
2. Class 定義預設有引數的建構
```cpp=
/**
* MyClass.cpp
*/
#include "MyClass.h"
MyClass::MyClass() {
cout << "MyClass 建構函數" << endl;
}
MyClass::MyClass(string name, int age) { // 1. 定義時不用再寫出預設函數
cout << "MyClass 建構函數" << endl;
//2. 定義時要把預設引數只定到內部成員
this->name = name;
this->age = age;
cout << "name: " << this->name
<< ", age = " << this->age << endl;
}
MyClass::~MyClass() {
cout << "MyClass 解構(析購)函數" << endl;
}
int main() {
MyClass m1;
MyClass m2("Alien", 12);
return 0;
}
```
> 
### 預設建構函數
* 如果一個 Class 類沒有建構函數,系統會用預設建構函數
```cpp=
MyClass::MyClass() { }
```
* 假設有自己的建構函數並且有要求的引數,但在使用時沒有給予,編譯器會錯誤,有兩種辦法可以解決
```cpp=
// 宣告
MyClass(string name, int age);
//定義
MyClass::MyClass(string name, int age) {
}
// 顯式使用
MyClass MyClass(); // 因沒有引數 name 編譯器會報錯
```
1. **函數重載**,多個建構函數
> MyClass(); // 重載另一個建構函數
>
> MyClass(string name = "Alien", int age = 10);
2. 每個建構函數的**引數都有預設值**
> MyClass(string name = "Alien", int age = 10);
### 建構函數 - 類初始化 (建構) 方法
* **靜態建構**有兩種方法,**顯式、隱式**,兩種方法都有相同效果
```cpp=
// 顯式
MyClass m1 = MyClass(str);
// 隱式
MyClass m2(str, 12345, 67890);
```
* 動態建構是使用關鍵字 `new`,並使用 **回傳型態指標**
```cpp=
// 動態
MyClass *m3 = new MyClass(str);
```
* 隱式使用 **如果沒有擴號,就一定是使用預設的建構函數**
```cpp=
// 隱式
MyClass m4; // 沒括號
MyClass *m5 = new MyClass; // 沒括號
```
* 物件陣列,也**可指定建構函數**
```cpp=
// 預設 建構函數
MyClass infos_1[4];
// 指定 建構函數
MyClass infos_2[] = {
MyClass(str, 60),
MyClass(str2, 30)
};
```
* **C++11 可以使用 ==初始化列表 `{}`==**
```cpp=
// 初始化列表 {}
std::string str2("Pan");
MyClass m4{str2};
MyClass m5{str2, 5432, 1};
MyClass m6 = {str2, 333, 444};
MyClass *m7 = new MyClass{str2, 111, 222};
```
**--實做--**
> 
### const 成員函數
* 我們知道 `const` 可拿來規定該物件不可該改,但是如果**只規定物件 ++部分不可修改++ 則應該在被呼叫的 function 後加關鍵字 const**
* **const 用來確保呼叫該 function 時,這個物件不會被更改** ==(++表示該函式保證不會修改呼叫的物件++)==
> 下面會宣告兩個物件,該物件內部有一個 const_show() 被 const 修飾,show 並沒有被 const 修飾
1. **Class 抽象宣告**
```cpp=
/**
* MyClass.h 宣告物件
*/
#include <iostream>
#include <string>
class MyClass {
private:
std::string name;
int id;
int phone;
public:
MyClass();
MyClass(std::string name, int id = 999, int phone = 999);
void show();
void const_show() const; //"3. "
~MyClass();
};
```
* 這個宣告整個被 const 包覆,導致 show 這個普通 function 也無法修改 name 變數;**const 修飾 `const_show` 方法,表示調用該方法時,這個物件的成員變數不會被該改**,變成了 **唯讀(`read-only`)**
2. **Class 定義**
```cpp=
/**
* MyClass.cpp 定義物件
*/
#include "MyClass.h"
MyClass::MyClass() {
std::cout << "隱式預設,MyClass 建構函數" << std::endl;
}
MyClass::MyClass(std::string name, int id, int phone) {
std::cout << "MyClass 建構函數" << std::endl;
this->name = name;
this->id = id;
this->phone = phone;
}
MyClass::~MyClass() {
std::cout << "MyClass 解構(析購)函數" << std::endl;
}
void MyClass::show() {
name = "Hello, " + name;
std::cout << "name: " << this->name
<< ", id = " << this->id
<< ", phone = " << this->phone << std::endl;
}
void MyClass::const_show() const {
//name = "Hello const, " + name; // Occur Error
std::cout << "name: " << this->name
<< ", id = " << this->id
<< ", phone = " << this->phone << std::endl;
}
```
* `show` & `const_show` 內部都有修改成員變數 name,差別在 **const_show 有被 const 修飾,所以 ++無法修改 name 變數++**
3. 測試呼叫
```cpp=
/**
* main.cpp 客戶端使用物件
*/
int main() {
std::string str("Alien");
MyClass m1 = MyClass(str);
m1.show();
m1.const_show();
const MyClass m2 = MyClass(str);
//m2.show();
return 0;
}
```
**--實做--**
> 
## this 指標
**this 指標指向呼叫成員函數** 的物件本身,並且 **==this 做為 隱藏 的引數,傳入成員函數中==**,這也就是為何上面我們在定義方法時,可以使用 `this`
```cpp=
MyClass::MyClass(string name, int age) {
cout << "MyClass 建構函數" << endl;
this->name = name; // this 使用
this->age = age;
}
```
* **this 的指標是被 const 指示**,所以不能被修改指向其它地方,**使用 間接運算子 `*` 可以取得 const Object 物件**
```cpp=
// 概念程式 (this 等同 `MyClass * const this`)
MyClass::MyClass(MyClass * const this, string name, int age) {
cout << "MyClass 建構函數" << endl;
this = new MyClass("Bob", 1234);
this->name = name;
this->age = age;
}
```
> 如果要對 this 改變指向,則會錯誤
>
> 
### this、ref 差異
* **引用 ref & this 理解**:
ref 原型是不可修改內容的指標,其實也就是透過 const 修飾內容 & 指標指向的物件 (read-onlt)
> 
:::info
* **`ref` 應該思考為進一步的抽象**
**它將指標給隱藏,讓使用者直接使用「符號」來訪問參數**,許多更高階的語言都使用引用來讓開發者使用
> 這也就是為啥有人說 C++ 不算完全的高級語言,因為它仍保有底層(assemble)的操作概念(底層需要知道 **記憶體位置、數值**,才可以操作參數)
:::
```cpp=
/**
* MyClass.h 宣告物件
*/
class MyClass {
private:
std::string name;
int score;
public:
MyClass(std::string name, int score);
const MyClass& getHighScore(const MyClass & m) const;
void show();
void const_show() const;
~MyClass();
};
/**
* MyClass.cpp 定義物件
*/
#include "MyClass.h"
MyClass::MyClass(std::string name, int score) {
//std::cout << "MyClass 建構函數" << std::endl;
this->name = name;
this->score = score;
}
MyClass::~MyClass() {
//std::cout << "MyClass 解構(析購)函數" << std::endl;
}
void MyClass::show() {
std::cout << "name: " << name << ", "
<<"score: " << score << std::endl;
}
void MyClass::const_show() const {
std::cout << "name: " << name << ", "
<<"score: " << score << std::endl;
}
const MyClass& MyClass::getHighScore(const MyClass & m) const {
//this->score += 10; //"4. " **`this` is read-only**
if(m.score > score) {
return m;
} else {
return *this;
}
}
/**
* main.cpp 客戶端使用物件
*/
int main() {
std::string str("Alien");
std::string str2("Pan");
MyClass m1 = MyClass(str, 60);
MyClass m2 = MyClass(str2, 30);
MyClass result1 = m1.getHighScore(m2);
result1.show();
//m2.getHighScore(m1).show(); // "2. " fail
//MyClass result2 = m2.getHighScore(m1); // "3. " ok
//result2.show();
m2.getHighScore(m1).const_show(); // "1. " ok
return 0;
}
```
**--實做--**
> 
## 類別範疇 - Class Scope
* **屬於類別範疇的項目 (資料成員、成員函數) ==只能在類別內使用==**
> **類別範疇** : **不能直接從外部呼叫類別內的成員**,儘管是 public 的成員仍然 **必須透過==類別名==才能訪問**
* **++class 函數名稱是全域範疇++,不可轉為區域範疇 (不可 static)**
> 
```cpp=
// 宣告
class P {
private:
int a;
public:
int b;
void show();
}
// ------------------------------------------------------------------
// 定義 必須透過範疇運算子 ::
void P::show() {
// TODO:
}
// ------------------------------------------------------------------
// 客戶訪問
P.a = 10; // Err : private 不可訪問
b = 10; // Err : 不可直接訪問,必須透過 class P
P.b = 10; // ok
show(); // Err : 不可直接訪問,必須透過 class P
P.show(); // ok
```
可透過以下幾種方法訪問
| 訪問符號 | 使用 | 範例 |
| -------- | -------- | - |
| **==.==** | class 宣告後呼叫 | P.b |
| **==->==** | 方法中指標使用 | this->b |
| **==::==** | **class 定義函數**、**靜態變數** | P::show() |
### 類別內常數 - Static Field
* **類別宣告是描述物件的樣子 (也就是 Class)**,不是產生物件,所以 **const 常數無法做為初始化成員變數**,也就是說 const 描述的成員變量,**要在產生物件時才有記憶體空間**
* 但仍有以下兩種方法可以達到目的
1. 使用 ==**static const**== 關鍵字,**將物件存在靜態區塊記憶體**
2. 使用 ==**enum**== 關鍵字,宣告列舉,**enum 具有類別範疇**
```cpp=
class P {
private:
static const int Months = 12; // 所有物件共用
int Birthday_1[Months];
enum {
Monthd = 12 // 所有物件共用 (類似 Static)
};
int Birthday_2[Months];
};
```
### 範圍列舉 enum
* 過去 enum 是靜態全域儲存,所有常常有名稱衝突的問題
```cpp=
enum Dinner {
One, Two, Three
};
enum Lunch { // 衝突 ~~~~~~ !
One, Two, Three // (內容物相同)
};
```
1. **enum 可以透過 struct、class 包裝後重新使用**
```cpp=
#include <iostream>
using namespace std;
enum Dinner {
One, Two, Three
};
struct MyStruct{
enum Lunch {
One, Two, Three
};
};
class PP {
public:
enum Breakfast {
One, Two, Three
};
};
namespace ABC {
enum Hello {
One, Two, Three
};
}
int main() {
enum Dinner d = One; // ok (原來的宣告)
cout << d << endl;
MyStruct::Lunch l = MyStruct::One; // ok (宣告在 Struct 內部)
cout << l << endl;
MyStruct::Lunch l2 = MyStruct::Two; // ok
cout << "l: " << l << ", l2: " << l2 << endl;
PP::Breakfast b = PP::One; // ok (宣告在 Class 內部)
cout << b << endl;
ABC::Hello h2 = ABC::Hello::Two; // ok
cout << "h: " << h << ", h2: " << h2 << endl;
return 0;
}
```
>
2. **enum 包裝 class、struct,稱為==範圍列舉==**
```cpp=
enum class Food_1 {
Rice, Noodle, Friut
};
enum struct Food_2 {
Rice, Noodle, Friut
};
int main() {
Food_1 f1 = Food_1::Rice;
Food_2 f2 = Food_2::Friut;
return 0;
}
```
> 
* **C++11 對範圍列舉 的安全性更加嚴警**,**傳統 enum 會被自動轉型為 int,但範圍 enum 並不會轉型為 int** (除了強制轉型)
```cpp=
#include <iostream>
enum Dinner {
One, Two, Three
};
struct MyStruct{
enum Lunch {
One, Two, Three
};
};
class PP {
public:
enum Breakfast {
One, Two, Three
};
};
enum class Food_1 {
Rice, Noodle, Friut
};
int main() {
enum Dinner d = One; // ok
MyStruct::Lunch l = MyStruct::Two; // ok
PP::Breakfast b = PP::Three; // ok
Food_1 f = Food_1::Rice;
if(l > d) {
std::cout<< "Dinner < Lunch" << std::endl;
}
// not allow
//
// "1. " 對於範圍列舉來說是不可以的,但 可以透過強制轉型 int(f),轉換就可以比對
if(l < f) {
std::cout<< "Food_1 < Lunch" << std::endl;
}
return 0;
}
```
**--實做--**
> 
* enum 預設使用的型態是 int,**可使用 ==:== 轉換不同型態 (必須整數型態)**
```cpp=
// 基礎單位轉為 short 更省空間
enum class : short Size {Small, Medium, Large};
```
## Appendix & FAQ
:::info
enum 轉換預設單位好像不行 (:short)???
:::
###### tags: `C++` `this` `enum`