---
tags: 大學筆記系列
---
# 物件導向程式設計
作者: 410410054 王宥愷
## 第零章 導論
物件導向程式設計的好處:
1. 可以**直接對應**到想設計的物件(ex:賽車),不須透過函式間接達成
2. 可以將**與物件有關的函式寫在一起**,方便管理
3. 透過**公開的互動函式**存取物件,並可將不希望直接更動的變數或函式設定成禁止存取
## 第一章
### 觀念釐清
1. cout<<"Hallo"<<endl; 中的
* << : 是函數
* cout : 是物件
* endl : 是函數
2. int a,b;
float c,d;
a+b 與 c+d 的"+"都是函數,但底層不一樣
3. switch語法 **沒寫break**須注意:
``` C++
#include <bits/stdc++.h>
using namespace std;
int main()
{
int x=0;
switch(x)
{
case 0:
cout<<0<<endl;
case 2:
cout<<1<<endl;
default:
cout<<"default"<<endl;
}
}
```
以上程式碼會同時輸出0 1 default
## 第二章
### C++ 配置記憶體
* 後面會提
### C++ 讀檔
* 後面會提
### "," 運算子
* 回傳最後一個expression所被賦予的值
> result = (first = 2, second = first + 1); //result=3
## 第三章
### 函式的概念
* 函式算是一種Precedural Abstraction(抽象化程序),把許多指令集結起來形成**黑盒子**,只要懂得使用即可
### parameters vs. arguments
* (Formal)parameters: 函示定義時的參數
* (Actual)argument: 函式呼叫時的參數
## 第四章
### Call by value vs. Call by reference
* Call by value 例子
``` C++
#include <bits/stdc++.h>
using namespace std;
void f(int b)
{
b+=1;
}
int main()
{
int a=0;
f(a);
cout<<a<<endl; //輸出0
return 0;
}
```
* 假的 Call by reference 例子
``` C++
#include <bits/stdc++.h>
using namespace std;
void f(int *b)
{
*b+=1;
}
int main()
{
//雖然利用函式成功修改a的值,但還是沒有直接修改到傳入的參數(指向a的指標)
int a=0;
f(&a);
cout<<a<<endl; //輸出1
return 0;
}
```
* 真正的 Call by reference 例子
``` C++
#include <bits/stdc++.h>
using namespace std;
void f(int &b)
{
//利用&符號,表示此參數是以Call by reference的方式傳入
b+=1;
}
int main()
{
int a=0;
f(a);
cout<<a<<endl; //輸出1
return 0;
}
```
### Overloading
* **函式名**、**函式參數組合**只要有其中一個不一樣便可以給過;在函式同名時,系統會自動依據不同的參數組合呼叫對應函式
->換句話說,C++ 允許函式名設定成相同的
* 函式同名時的篩選**規則**
1. 檢查傳入的**參數數量**,數量相同者優先選擇
2. 檢查各個**參數的型態**,型態完全匹配者優先選擇
3. 檢查可行的**型態變換**
* 先檢查具兼容性的型態變換(ex: int->double)
* 再檢查會損失部分資訊的型態變換(ex: double->int)
若以上規則還分不出兩個函式的優先權,則會跳錯
舉例:
* 各函式定義如下
1. f(int a,double b);
2. f(double a,int b);
3. f(int a,int b);
4. f(int a,int b,int c);
* 則函式呼叫時
* f(2,3,5.2) 經過規則1篩選後,只有**第4個函式**滿足條件,故會呼叫該函式
* f(2.2,3) 經過規則1,2篩選後,只有**第2個函式**滿足條件,故會呼叫該函式
* f(2,3) 經過規則1,2篩選後,只有**第3個函式**滿足條件,故會呼叫該函式
* f(2.2,3.3) 經過規則1,2,3篩選後,第1,2個函式滿足條件,故**跳錯**
### Default argument
* 可以給參數初始值,這樣就不用傳全部的參數了
``` C++
#include <bits/stdc++.h>
using namespace std;
int f(int a,int b=10,int c=100)
{
return a+b+c;
}
int main()
{
cout<<f(1)<<endl; //輸出 111
cout<<f(1,2)<<endl; //輸出 103
cout<<f(1,2,3)<<endl; //輸出 6
return 0;
}
```
* 可以結合Overloading的觀念,不衝突
``` C++
#include <bits/stdc++.h>
using namespace std;
void f(int a)
{
cout<<"1"<<endl;
}
void f(int a,int b=1)
{
cout<<"2"<<endl;
}
int main()
{
f(1); //跳錯,因為兩個function都可以選
return 0;
}
```
### debug 工具
* 直接用 cout 印出來
* 使用編譯器自帶的debugger
* assert(條件): 引入\<cassert\>標頭檔即可使用,當條件不滿足時,結束程式
* drivers: debug技巧,分別測試各個函式以找出可能的bug
* Stubs: debug技巧,當函式還沒完成時,可以先回傳一個合理的值,便可先測試後續功能是否正常
## 第五章
### 觀念釐清
1. cout<<a[5]<<endl; 中的 "[" 與 "]" 也是函式
### 連續的記憶體空間
```C++
int a,b,c;
```
此時a,b,c的記憶體空間是連續的,就如陣列一樣
### for(auto x:a) 用法
該語法可以用來遍歷所有元素,須注意以下特性
* 這樣寫不會改到a中的元素
```C++
int a[]={1,2,3};
for(auto x:a)
{
x++;
}
```
* 要這樣寫才行
```C++
int a[]={1,2,3};
for(auto &x:a)
{
x++;
}
```
### array 當參數傳入
```C++
void f(int a[],int Size)
{
}
int main()
{
int a[]={1,2,3};
f(a,3);
}
```
順帶一提,array在函式間的傳遞是假的call by reference
## 第六章
### structure
* 可以在宣告時初始化
```C++
struct Date
{
int y;
int m;
int d;
};
int main()
{
Date a={2023,3,7}
}
```
* C++ struct中可以放function
* C++ struct**預設元素為public**,但可以設成private
### class 簡介
* 新增了許多物件有關概念後,class 從struct中獨立出來自成一種型態
* C++ class**預設元素為private**,但可以設成public
* 外部無法存取private的變數與函式,只能存取public的內容
* 使用 "::" 在class外定義被宣告在class內的function
* 使用 "." 使用被宣告在class內的變數與函式(class型態的變數與函式被稱為物件)
```C++
class Date
{
private:
int y=2023;
int m=3;
int d=7;
public:
void print();
};
void Date::print()
{
cout<<y<<" "<<m<<" "<<d<<endl;
}
int main()
{
Date a;
a.print();
return 0;
}
```
* 是一種abstract data type(抽象資料型態),使用者看不到細節
* encapsulation(封裝)概念: 例如int型態便是一個class,並且含有"+\-*/"等等function
Accessor v.s. Mutator
* 兩者通常都是public的函式
* 不同之處:
* Accessor: 允許使用者讀資料,又稱做get member
* Mutator: 允許使用者透過此函式適當的修改資料
## 第七章
### class constructor
* 目的是在宣告class時自動初始化
* 必須與class同名
* 會回傳anonymous object(不是void)
* 大部分放在public中
* 支援Overloading
``` C++
#include <bits/stdc++.h>
using namespace std;
class Date
{
private:
int month;
int date;
public:
Date(int input1,int input2) //這就是constructor,注意沒有回傳型態
{
month=input1;
date=input2;
}
/*
Date(int input1,int input2) //寫成這樣可達成完全相同的事情
:month(input1),date(input2)
{
}
*/
Print()
{
cout<<month<<" "<<date<<endl;
}
};
int main()
{
Date birthday(9,4);
//Date new_year; //跳錯,自訂義有參數的constructor後,一定要傳參數
birthday.Print();
birthday=Date(1,1); //可以像這樣使用constructor來重新初始化物件(不可使用".")
//其中Date(1,1)是anonymous(匿名)的物件
birthday.Print();
return 0;
}
```
* default constructor
* 若沒有自訂constructor,則會自動創建
* 若有自訂constructor,則不創建
### class destructor
* 在該物件結束的時候會自動執行
* 性質與class constructor 類似,使用時在函式名前加上"~"即可
``` C++
~Date()
{
cout<<"end"<<endl;
}
```
### const的使用
* 結論: 不要改到const本身都沒事
``` C++
#include <bits/stdc++.h>
using namespace std;
class Date
{
private:
const int month;
const int date;
int year=2023;
public:
Date(int input1,int input2) //注意沒有回傳型態
:month(input1),date(input2)
{
}
void Print() const //設定Print為const function
{
//year=2020;
//跳錯,Print已經是const function了
//不可以改變class內的任何變數(class 外的不再此限)
cout<<month<<" "<<date<<endl;
}
void input1(const int& a)
{
cout<<a<<endl;
}
void input2(int a)
{
cout<<a<<endl;
}
};
int main()
{
const int a=10;
Date birthday(9,4);//第一次初始化const 變數是可行的
birthday.Print();
//birthday=Date(1,1); //跳錯,嘗試第二次初始化const 變數會錯
birthday.input1(a);
//如果以call by reference的方式傳入,對應的函數參數必須也要是const
birthday.input2(a);
//如果不以call by reference的方式傳入,因為不會動到const的值,就沒差
return 0;
}
```
### Preprocessor Pitfalls
* 類似函式,但本質上並不是,會做出不符合function的行為
* 可以想像成直接將傳進去的東西**原封不動置換**
* 不可存取class中的private內容,即使在class中定義也不行
``` C++
#include <bits/stdc++.h>
using namespace std;
#define judge(x) ((x)>5 ? (x):0)
//#define judge (x) ((x)>5 ? (x):0) //跳錯,不可有空格
int main()
{
int a=6;
cout<<judge(++a)<<endl; //輸出8
cout<<a<<endl; //輸出8
return 0;
}
```
* 這個例子中,\++a會被原封不動的傳進去#define中,接著在(x)>5及(x)中各被+1後被回傳(main中的++並不起作用)
### inline function
* 保留了Preprocessor Pitfalls的置換特性
* 使用方式與行為與函式一樣
* 適合取代很短的函式
* 使用起來更有效率
``` C++
inline int judge(int x)
{
return (x) > 5 ? (x) : 0; //回傳7
}
```
### Static 語法
* 以Static宣告的變數在離開該區塊後不會被釋放,且多個物件間共享記憶體
* 計算obj物件總數範例
``` C++
#include <iostream>
using namespace std;
class obj
{
private:
static int counter; //所有型態為obj的物件會共用同一個counter
public:
obj()
{
counter++;
}
obj(const obj& ini) //copy constrastor,沒寫的話將物件當作函式的參數時counter不會自動++,函式回傳的物件counter也不會自動++,但結束時counter還是會--,便會出現counter不正確的情況
{
counter++;
}
~obj()
{
counter--;
}
static void print() //在函式前加上static便可以在物件還沒創建之前便使用此函式
{
cout<<counter<<endl;
}
};
int obj::counter=0; //一定要初始化,不然會出錯
int main()
{
obj::print(); //輸出0
obj a,b;
a.print(); //輸出2
return 0;
}
```
## 第八章
### operator overloading
* 目的是讓使用者自定義運算符號,例如兩個class相加
* 使用non-member function的方式
``` C++
#include <iostream>
using namespace std;
class A
{
private:
int val;
public:
A(int input) :val(input) {} //constructor
int get_val() const
{
return val;
}
};
//注意是寫在class外面,屬於non-member function
const A operator - (const A &left,const A &right)
//第一個const是為了確保回傳的anonymous物件不被使用者誤修改;
//simple type(預設就有的型態)才要加
//第二跟第三個const是為了確保傳進去運算的物件不被修改到
{
return A(left.get_val()-right.get_val());
}
bool operator - (const A &temp) //單一參數的自定義operator
{
return -temp.get_val();
}
int main()
{
A temp1(5),temp2(3);
cout<<(temp1-temp2).get_val()<<endl;
//(temp1+temp2)是"+"這個自訂的operator回傳的物件;
//因為是anonymous object,這行結束後就會消失
cout<<-temp1.get_val()<<endl;
return 0;
}
```
* 使用member function的方式(感覺像是a.-b)
``` C++
#include <iostream>
using namespace std;
class A
{
private:
int val;
public:
A(int input) :val(input) {} //constructor
int get_val() const
{
return val;
}
//也可以搬到class中來實作operator
const A operator - (const A &right) const
//第一個const是為了確保回傳的anonymous物件不被使用者誤修改;
//simple type(預設就有的型態)才要加
{
//第二個const是為了確保傳進去運算的物件不被修改到(保護a-b中的b)
//第三個const是為了確保本身運算的物件不被修改到(保護a-b中的a)
return A(val-right.get_val());
}
bool operator - () const //單一參數的自定義operator
{
return -val;
}
};
int main()
{
A temp1(5),temp2(3);
cout<<(temp1-temp2).get_val()<<endl;
//(temp1+temp2)是"+"這個自訂的operator回傳的物件;
//因為是anonymous object,這行結束後就會消失
cout<<-temp1.get_val()<<endl;
return 0;
}
```
* member function 的困境
* 處理class跟simple type的operator是單向的,反過來寫會跳錯
``` C++
#include <iostream>
using namespace std;
class A
{
private:
int val;
public:
A(int input) :val(input) {} //constructor
int get_val() const
{
return val;
}
//也可以搬到class中來實作operator
const A operator - (const A &right) const
{
return A(val-right.get_val());
}
};
int main()
{
A temp1(5);
int temp2=3;
cout<<(temp1-temp2).get_val()<<endl;
//int型態的temp2會自動因為自定義的constructor先轉成A型態
//再透過自定義的operator運算
//cout<<(temp2-temp1).get_val()<<endl;
//這樣寫會跳錯,因為在int型態的operator中
//沒有定義能處理A型態的operator
return 0;
}
```
* 其他operator
* "="、"[]"、"()" :只能使用member function的方式自定義
* "++" :分成prefix(\++a)跟postfix(a\++),沒傳參數的視為prefix
* "()" :可以讓呼叫物件的動作看起來像呼叫函式
### friend
* 設計用來從外部直接存取class private的成員
* 違反物件導向程式設計精神
* 是單向的
* 將friend作用在class與函式之間,便可以使用non-member function的方式模擬member function的效果(不需另外使用Accessor函式存取private成員)
```C++
#include <iostream>
using namespace std;
class A
{
private:
int val;
public:
A(int input) :val(input) {} //constructor
int get_val() const
{
return val;
}
friend const A operator - (const A &left,const A &right);
//讓此函式有權限存取private成員
};
//注意是寫在class外面,屬於non-member function
const A operator - (const A &left,const A &right)
{
return A(left.val-right.val); //可以直接存取private成員
}
int main()
{
A temp1(5);
int temp2=3;
cout<<(temp1-temp2).get_val()<<endl;
cout<<(temp2-temp1).get_val()<<endl; //反過來也能存取了
return 0;
}
```
* friend也可以作用在class之間
### Reference 進階應用
* 除了可以用Reference當函式參數傳入以外,也可以當函式返回值
* 這個技巧可以應用在
* cout<<a<<b<<endl;
* a[0]=3;
```C++
#include <iostream>
using namespace std;
int& f(int &a)
{
return a;
}
int main()
{
int a=10;
f(a)=5;
cout<<a<<endl; //輸出5
return 0;
}
```
## 第九章
### C++ C-string
* 沿用C語言的字串處理系統
* 字串結尾是'\0',不小心改掉的話便不是C-string了
* 由字元陣列實作
* 支援用cin的方式輸入(以空白當作切割點),cout的方式輸出
* 可以使用cin.getline(字元陣列,最大讀的量) 來一次讀一行(會讀入'\n',並替換成'\0'儲存)
```C++
char a[10];
cin.getline(a,10);
```
* 可以使用cin.get(字元) 來一次讀一個字元
* 可以使用cout.put(字元) 來一次輸出一個字元
* 觀念釐清:
1. 只有宣告時可以直接使用"=",之後須使用strcpy或strncpy來重新賦值
> ex: char str[]="abc";
3. strncpy比strcpy好,比較不會有複製超過範圍的問題
4. 沒有"\0"作為字串結尾的話便不是C-string,只是單純的字元陣列
> ex: char[3]={'a','b','c'};
### C++ string
* 想要一次讀一行可以使用getline語法(非member function),同樣會讀掉換行,但不會把換行存進去
``` C++
#include <iostream>
using namespace std;
int main()
{
string a;
getline(cin,a);
cout<<a<<endl;
}
```
* 也可以指定斷點
``` C++
#include <iostream>
using namespace std;
int main()
{
string a;
getline(cin,a,'!'); //指定斷點為'!'
cout<<a<<endl;
}
```
* getline會回傳cin,故可以進行以下操作
```C++
#include <iostream>
using namespace std;
int main()
{
string a;
int b;
getline(cin,a,'!')>>b;
cout<<a<<" "<<b<<endl;
}
```
* getline陷阱
```C++
#include <iostream>
using namespace std;
int main()
{
//輸入:
//54
//apple
int a;
string b;
cin>>a; //讀到54
getline(cin,b); //讀到\n,然後丟掉
cout<<a<<" "<<b<<endl; //b.size()=0
}
```
## 第十章
### C++ pointer
* 相較於不嚴謹的C,C++將指標視為獨立的型態,不可任意轉換
```C++
#include <iostream>
using namespace std;
int main()
{
//以下操作在C中被允許,但被C++擋下來
int* a;
//long int b=a; //錯
//double* b=a; //錯
double* b=(double*)a; //可以過
}
```
### C++ new 與delete
* 取代C中的malloc()
* new 比 malloc() 多做了constructor的部分
* delete 比free() 多做了desstructor的部分
* 觀念釐清:
* 區域變數會從stack開始往下占用
* 動態配置的變數(new,delele)會從Heap開始往上占用

```C++
#include <iostream>
using namespace std;
int main()
{
int* a=new int; //配置一塊整數的記憶體
*a=10;
int* b=new int(20); //也可以用類似constructor的方式初始化
int* c=new int[10]; //配置連續的10個int大小的空間
//動態配置10x5的二維陣列
int** d=new int*[10];
for(int i=0;i<10;i++)
{
d[i]=new int[5];
}
cout<<*a<<" "<<*b<<endl; //輸出10 20
delete a; //刪除該指標所指向的記憶體空間
a=nullptr; //好習慣,重新設成空指標
}
```
### C++ nullptr
* 為了解決無法區分NULL是0還是空指標的問題
* 本質上不是0,但能表現出0的性質
```C++
#include <iostream>
using namespace std;
void f(int a)
{
cout<<"f1"<<endl;
}
void f(int* a)
{
cout<<"f2"<<endl;
}
int main()
{
//f(NULL); //跳錯,因為NULL=0,這樣會導致不知道要呼叫哪個function
f(nullptr); //丟nullptr時,compiler會明確的呼叫第二個函式
if(nullptr==0) cout<<"Yes"<<endl; //會輸出YES
}
```
### "this" 應用
* this就是指向當前物件的指標
```C++
#include <iostream>
using namespace std;
class A
{
private:
int val;
public:
A(int input) :val(input) {} //constructor
int get_val() const
{
return val;
}
//也可以搬到class中來實作operator
const A operator = (const A &right)
{
this->val=right.get_val();
//val=right.get_val(); //與上面那行等價
return *this; //取值並回傳
}
};
int main()
{
A temp1(3),temp2(4),temp3(5);
temp1=temp2=temp3;
//會先執行temp2=temp3,並回傳temp2
//接著執行temp1=temp2,並回傳temp1
cout<<temp1.get_val()<<" "<<temp2.get_val()<<" "<<temp3.get_val()<<endl;
//輸出5 5 5
return 0;
}
```
## 第十一章
### Class separation
* 將程式寫在不同的檔案裡,修改時只需重新compile某部分即可,且可以對使用者隱藏不需知道的細節
* 分成兩類檔案:
* interface files (.h): 給使用者使用方式介紹
* inplementation files (.cpp): 函式的底層定義與實現
* 為了避免.h檔多重定義的問題,可以使用#ifndef()解決,邏輯為"若未定義"

### namespace
* 多人開發的程式可能會有需多重名的class,function等等,為了區分,可以將同名的class,function等等放入不同的namespace下
* 需要用到時則啟用(using)這個namespace(可以只在一個block中生效)
* 或是使用"::"來指定使用哪個namespace中的class,function
* 未寫在其中一個namespace中的class,function會自動的被定義在global namespace 中
* 支援巢狀
```C++
#include <bits/stdc++.h>
using namespace std;
namespace n1
{
void f()
{
cout<<"n1"<<endl;
}
}
namespace n2
{
void f()
{
cout<<"n2"<<endl;
}
}
namespace //無名,只能在這個檔案中使用(local)
{
void debug()
{
cout<<"You have a bug"<<endl;
}
}
int main() //此函式在global namespace 中
{
{
using namespace n1; //使用n1這個namespace
f(); //輸出n1
}
{
using n2::f; //使用n2這個namespace中的f() function
f(); //輸出n2
}
n1::f(); //也可以用"::"指定使用哪個namespace中的fuction (輸出n1)
{
using namespace n1;
using namespace n2;
//f(); //跳錯,f() is ambiguous(使用到此函式時才會從namespace中找)
}
debug(); //可以直接使用無名的namespace
return 0;
}
```
## 第十二章
### stream
* a flow of characters
* 會先將file與stream做連結
* 分成instream,outstream
```cpp
#include <iostream>
#include <fstream>
using namespace std;
int main()
{
ifstream file_In; //創建ifstream物件
//ifstream file_In("test1_input.txt");
//ifstream、ofstream物件內建constructor,可以直接初始化要連結的檔案
ofstream file_out; //創建ofstream物件
file_In.open("test1_input.txt");
//連結file與stream物件,之後程式只要使用此物件即可
file_out.open("test1_output.txt");
int a,b,c,d;
file_In>>a>>b>>c>>d; //從 test1_input.txt中讀取四個變數,並分別存入a,b,c,d中
cout<<a<<" "<<b<<" "<<c<<" "<<d<<endl;
file_out<<a<<b<<c<<d<<endl; //將變數a,b,c,d輸出至test1_output.txt中
file_out.flush(); //刷新資料到硬碟中
file_In.close(); //斷掉物件與檔案的連結
file_out.close();
}
```
## 第十四章
核心精神: 想要定義一個相對完整的架構,但又不想要重複定義太多東西
Reuse的方法分成兩種:
* Composition: 將已有的物件定義在新的class中 (has a)
* inheritance: 定義一個新的class作為已有物件的型態 (is a)
繼承架構:
* base class: "general" class,供其他class繼承使用,通常較為抽象,非現實世界中真實存在的實物
* derived class: 繼承base class,自動擁有base class所定義的變數、函式,在此class中,只需列出自訂的新變數、函式或重新定義繼承的內容即可
觀念釐清:
1. assign operator(例如"="),constructor,destructor,copy constructor不會被繼承,但仍可以使用
2. derived class會繼承base class的private 變數跟函式,但不能存取
### 不被繼承的Constructor
derived class並不會繼承base class的constructor,但可以在定義自己的constructor時使用base class的constructor
注意: 若在derived class中的constructor沒有用到base class的constructor,則會自動呼叫base class的default constructor(沒有傳入任何參數的那個版本),此時,若base class有定義傳入至少一個參數的constructor,則會導致找不到default constructor而跳錯
另外,在建立的時候,會優先建立在上層(base class)定義的內容,才繼續建立在下層定義的內容
錯誤版本:
```cpp
#include <bits/stdc++.h>
using namespace std;
class Employee //base class
{
private:
string Name;
string Sid;
public:
Employee(string name,string sid) :Name(name),Sid(sid) {}
//沒寫default constructor
};
class Hourly_Emplotee:public Employee //derived class
{
private:
int hour_wage;
public:
//呼叫base class的constuctor來幫忙初始化
Hourly_Emplotee(string name,string sid,int money) :Employee(name,sid),hour_wage(money) {}
//Hourly_Emplotee(int money) :hour_wage(money) {} //跳錯
//Hourly_Emplotee() {} //跳錯
//沒寫任何constructor的時候,compiler自動產生的constructor
//反而不會呼叫base class的default constructor
};
int main()
{
}
```

### 不被繼承的destructor
derived class並不會繼承base class的desstructor,與constructor不同的是,在建立derived class的desturctor時,compiler會自動呼叫base class的desstructor,不須手動寫上
另外,在刪除的時候,會優先刪除下層才定義的內容,才繼續刪除在上層(base class)定義的內容
### protected
與public,private同地位,為了解決derived class無法使用base class中的private變數、函式的問題
在base class 中定義在protected區域中的變數與函式
* 對於外部來說,屬於private
* 對於derived class來說,屬於public
```cpp
#include <bits/stdc++.h>
using namespace std;
class Employee //base class
{
protected:
string Name;
string Sid;
public:
Employee(string name,string sid) :Name(name),Sid(sid) {}
Employee() {} //default constructor
};
class Hourly_Emplotee:public Employee //derived class
{
private:
int hour_wage;
public:
Hourly_Emplotee(string name,string sid,int money) :Employee(name,sid),hour_wage(money) {}
Hourly_Emplotee() {} //default constructor
void edit_sid_to_666()
{
Sid="666"; //繼承base class的物件可以正常使用protected中的變數與函式
}
};
int main()
{
Hourly_Emplotee emplotee1("Jason","12345",5000);
emplotee1.edit_sid_to_666(); //正確
//emplotee1.Sid="666"; //跳錯,emplotee1.Sid在protected區域內,外部不可存取
}
```
### Redifition
可以在derived class中重新定義base class中的內容
注意: 跟overloading不一樣的是,Redifition會強行取代base class中的同名函式,不論參數是否一樣
```cpp!
#include <bits/stdc++.h>
using namespace std;
class Employee //base class
{
protected:
string Name;
string Sid;
public:
Employee(string name,string sid) :Name(name),Sid(sid) {}
Employee() {} //default constructor
void print()
{
cout<<"now in Employee class"<<endl;
}
};
class Hourly_Emplotee:public Employee //derived class
{
private:
int hour_wage;
public:
Hourly_Emplotee(string name,string sid,int money) :Employee(name,sid),hour_wage(money) {}
Hourly_Emplotee() {} //default constructor
void print(int temp)
{
cout<<"now in Hourly_Emplotee class"<<endl;
}
};
int main()
{
Hourly_Emplotee emplotee1("Jason","12345",5000);
//emplotee1.print(); //錯誤,從base class中繼承下來的"沒傳任何參數的函式"被取代了
emplotee1.print(1); //正確,輸出"now in Hourly_Emplotee class"
emplotee1.Employee::print(); //正確,輸出"now in Employee class"(base class 中的函式被取代後,仍有方法能被呼叫)
//emplotee1.Sid="666"; //跳錯,emplotee1.Sid在protected區域內,外部不可存取
}
```
### 繼承的種類
分成三種,分別為public,protected,private

* 縱軸是在base class中不同區域的成員,橫軸是繼承的種類
* 綠色字體代表的意義為:
* public: 連外部都可存取
* protected: 只有繼承者可存取
* private: 連繼承者都不可存取
* 例如使用private繼承,則原本在base class中是public的成員,在derived class中變成private的
```cpp
#include <bits/stdc++.h>
using namespace std;
class Employee //base class
{
protected:
string Name;
string Sid;
public:
string company_name="TSMC";
Employee(string name,string sid) :Name(name),Sid(sid) {}
Employee() {} //default constructor
};
class Hourly_Emplotee:private Employee //derived class
{
private:
int hour_wage;
public:
Hourly_Emplotee(string name,string sid,int money) :Employee(name,sid),hour_wage(money) {}
Hourly_Emplotee() {} //default constructor
void Print()
{
//繼承下來的company_name相當於定義在此class的private區域
//本身還是可以存取
cout<<company_name<<endl;
}
};
int main()
{
Hourly_Emplotee emplotee1("Jason","12345",5000);
emplotee1.Print();
//cout<<emplotee1.company_name<<endl; //但對於外部來說,無法存取private成員
}
```
應用場景: 讓外部不可使用繼承下來,但用不到的函式
```cpp
#include <bits/stdc++.h>
using namespace std;
class Pet
{
public:
void Speak() {cout<<"hallo"<<endl;}
void Eat() {cout<<"yummy"<<endl;}
};
class GoldFish: Pet //沒寫代表繼承種類是private
{
public:
Pet::Eat; //重新定義成public函式
};
int main()
{
GoldFish denial;
denial.Eat(); //外部可存取public函式,輸出 "yummy"
//denial.Speak(); //跳錯,外部不可存取private函式
}
```
### 多重繼承
很危險,不要亂用
## 第十五章
### Redifition的困境
假設
Base 是base class,其中包含了Print()函式
Derived 是derived class,其中redifine了Print()函式
```cpp
Derived obj;
Base ptr=&obj; //因為obj也是一種Base型態,顧可以這樣做
ptr->print() //出事了阿伯,沒辦法指定這個函式到底要用Base的還是Derived的
//(compiler總是會選擇最底層(base)的那個)
//ptr->Derived::print() //這樣也行不通,會跳錯
```
另一個完整的例子
```cpp
#include <bits/stdc++.h>
using namespace std;
class Figure
{
public:
void center() {draw();}
void draw() {cout<<"Figure draw"<<endl;}
};
class Circle:public Figure
{
public:
void draw() {cout<<"Circle draw"<<endl;}
};
int main()
{
Circle my_circle;
//呼叫繼承下來的center(),而center()又呼叫draw()
//此時呼叫的draw()只能是Figure的,沒辦法指定要用Circle的draw()
my_circle.center();
}
```
### virtual function
概念: 先用而後定義;在compile的時候不先連結function,而是等到真的使用時再根據物件的型態使用不同的function
註:virtual關鍵字只需寫明一次,以下繼承並重新定義的同名函式會自動賦予virtual特性,多寫了也沒關係
```cpp
#include <bits/stdc++.h>
using namespace std;
class Figure
{
public:
void center() {draw();}
virtual void draw() {cout<<"Figure draw"<<endl;}
};
class Circle:public Figure
{
public:
void draw() {cout<<"Circle draw"<<endl;}
};
int main()
{
Circle my_circle;
//呼叫繼承下來的center(),而center()又呼叫draw()
//此時呼叫的draw()因為是virtual,所以會根據該物件"當前的型態"來決定要用哪個draw
//以這個例子來說,因為物件當前的型態是Circle,故會使用Circle的draw()
my_circle.center();
}
```
比較:
redifined: 重新定義函式,讓原本的被隱藏起來
overridding: virtual的概念
overloading: 透過不同參數來決定使用哪個function
override 關鍵字: 告知使用者此function上層是virtual,而此function overridding 上層的函式
final 關鍵字: 之後繼承此函式的class不能再redifined
virtual缺點:
late binding 是 "on the fly",可以動態調整
但需要更多空間跟時間
注意:
如果destructor沒寫virtual,以下程式碼會出事(只刪掉Figure有的部分)
```cpp
Figure* fig=new Circle;
delete fig;
```
### Pure virtual
概念:
* 使擁有Pure virtual function的class徹底抽象化
* 不可做成物件,但宣告成pointer是可行的
* 如果derived class沒有完成全部的pure virtual function,那該物件仍然不可被直接做出
```cpp
#include <bits/stdc++.h>
using namespace std;
class Figure
{
public:
void center() {draw();}
virtual void draw()=0; //宣告成pure virtual function
};
class Circle:public Figure
{
public:
void draw() {cout<<"Circle draw"<<endl;}
};
int main()
{
Circle my_circle;
my_circle.center();
//Figure fig1; //跳錯
}
```
### Slicing Problem
C++可以用base class型態去接其derived class,但會導致資料流失
可以用指標+virtual function來解決這個問題
```cpp
#include <bits/stdc++.h>
using namespace std;
class Figure
{
public:
int center;
virtual void Print() {cout<<"center= "<<center<<endl;}
};
class Circle:public Figure
{
public:
int radius;
virtual void Print() {cout<<"center= "<<center<<endl<<"radius= "<<radius<<endl;}
};
int main()
{
Figure fig;
Circle my_circle;
my_circle.center=5;
my_circle.radius=2;
fig=my_circle; //此時,關於radius的資訊被丟掉了
cout<<fig.center<<endl; //輸出5
//cout<<fig.radius<<endl; //跳錯,fig不存在radius
Figure* fig_ptr=new Figure;
Circle* my_circle_ptr=new Circle;
my_circle_ptr->center=5;
my_circle_ptr->radius=2;
fig_ptr=my_circle_ptr;
cout<<fig_ptr->center<<endl; //輸出5
//cout<<fig_ptr->radius<<endl; //跳錯,fig_ptr指向的空間雖然存在radius,但指標本身不知道
fig_ptr->Print(); //使用virtual function來存取資訊,順利輸出center,radius的資訊
}
```
### Casting
繼承架構中Base class 跟 derived class 之間的轉型
分成Upcasting跟downcasting
* Upcasting
概念是"derived class 也是一種base class型態"
* downcasting
概念是"當base class的指標實際上指向的是derived class型態的記憶體位址時,可以轉型成derived class型態的指標"
實際例子
```cpp
#include <bits/stdc++.h>
using namespace std;
class Figure
{
public:
int center;
virtual void Print() {cout<<"center= "<<center<<endl;}
};
class Circle:public Figure
{
public:
int radius;
virtual void Print() {cout<<"center= "<<center<<endl<<"radius= "<<radius<<endl;}
};
int main()
{
Figure fig;
Circle my_Circle;
my_Circle.center=5;
my_Circle.radius=2;
fig=my_Circle; //Upcasting,把derived class型態轉型成base class型態
fig=static_cast<Figure> (my_Circle); //另一種Upcasting的寫法
//my_Circle=fig; //反之不成立
Figure* fig_ptr=new Circle; //Upcasting
Circle* my_Circle_ptr=dynamic_cast<Circle*> (fig_ptr); //downcasting
if(my_Circle_ptr==NULL) cout<<"NULL_1"<<endl; //不會進來
Figure* fig_ptr2=new Figure; //Upcasting
//錯誤的downcasting,fig_ptr2指向的記憶體必須要是Circle才可以順利轉換,轉換失敗回傳NULL
Circle* my_Circle_ptr2=dynamic_cast<Circle*> (fig_ptr2);
if(my_Circle_ptr2==NULL) cout<<"NULL_2"<<endl; //輸出NULL_2
//Circle* my_Circle_ptr3=new Figure; //反過來寫會跳錯,因為Figure不是一種Circle
Circle* my_Circle_ptr3=new Circle;
Figure* fig_ptr3=dynamic_cast<Figure*> (my_Circle_ptr3); //dynamic_cast也可用於upcasting
//實體物件用static_cast,指標用dynamic_cast
}
```
## 第十六章
想要實作Stash(不同Stash可以存不同的資料,但同一個Stash內部資料的型態是一致的),可以使用unsigned charactor
* 缺點:
1. 使用者必須非常清楚自己存的資料型態
2. 若資料型態是pointer,在刪除後並未釋放指向的記憶體空間(因為全部資料底層都是unsigned charactor,destructor沒辦法區分)
* 解決方案1: 使用polymorphism(待補)
* 解決方案2: 使用template
template 的概念是將"型態"也視為可變的變數,就像是不同型態function的集合
template 範例:
```C++
#include <bits/stdc++.h>
using namespace std;
class Test
{
int test;
};
template<class T> //寫在會用到該template的函式上面,會自動將T換成收到的型態
void show(T a,T b,int c) //以產生對應的function
{
cout<<a<<" "<<b<<" "<<c<<endl;
}
int main()
{
show(2,3,5);
show(2.2,3.6,9);
//show(2,3.3,7); //錯,找不到對應的function(T的型態需一致)
//Test test1,test2;
//show(test1,test2,9); //錯,沒有對應的operator
}
```
也可以這樣寫,但要注意T1,T2一定都要用到,否則會跳錯
```
template<class T1,class T2>
f(...)
```
注意:template的declear跟definition若寫在不同檔案可能會compile error
-> 結論: 將template的declear跟definition都寫在header.h中,並在main.cpp中使用必對

* case 1: 只有include "header.h",導致main.cpp找不到showStuff(int,char,char)的definition,故出錯
* case 2: 在"header.cpp"中使用了showStuff(int,char,char),故在編譯的時候,該函式的definition已被包含在.o檔中,故main.cpp能正常使用
* case 3: 在"header.cpp"中使用了showStuff(int,char,char),故在編譯的時候,該函式的definition已被包含在.o檔中,但這個並不是main.cpp中使用的那個函式,故出錯
* case 4: "header.h","header.cpp"一起引用,便不會有找不到definition的問題
* case 5: declear 跟 definition 都寫在"header.h",接著被main.cpp引用,便不會有找不到definition的問題
class template 範例:
```C++
#include <bits/stdc++.h>
using namespace std;
template<class T> //template也能用在class上
class MyPair
{
private:
T pair_first;
T pair_second;
public:
MyPair(T input1,T input2); //constructor,definition寫在class外
~MyPair() {} //destructor
T first(); //取得第一個元素,definition寫在class外
T second() {return pair_second;} //取得第二個元素
};
template<class T> //記得加這行
MyPair<T>::MyPair(T input1,T input2) //不可寫成MyPair::MyPair(...)
{
pair_first=input1;
pair_second=input2;
}
template<class T> //記得加這行
T MyPair<T>::first() //最前面的T代表回傳值
{
return pair_first;
}
int main()
{
MyPair<int> pair1(5,7); //用"< >" 來指定T的型態
MyPair<int> pair2(5.3,7.1); //能過,因為前面已經宣告MyPair的T是int型態了
cout<<pair1.first()<<" "<<pair1.second()<<endl;
}
```
* 注意:
1. template中的T可以為自訂型態,但要將operator(例如=,+等等)定義好
2. copy constructor記得處理好
3. 若T是指標,則記得處理destructor
Tip:
class constructor可以作為function的參數使用
```C++
template<class T>
T add(const Pair<T>& the_Pair);
```
## 第十八章
為了在寫code的時候能專注於主線,而非繁瑣的錯誤處理,故有了Exception handleing觀念以解決此問題
* 概念是當發生錯誤時,會產生一個signal,並跳去執行Exception handler,**處理完後不會回到錯誤發生的地方**
* 在function中暫時不知道該怎麼處理錯誤,但又想reuse function的程式碼,即適合使用Exception handleing(將throw跟catch寫在不同function)
語法:
1. try:處理主線情況
2. catch:處理例外情況(最多只能有一個參數)
* 可以有很多個catch,以型態來區分
* catch(...)相當於"switch的default"
4. throw: 在try區塊中,丟出例外給catch
* function可能也會throw exception,並交給當初呼叫function的地方用catch處理(目的是讓不同人呼叫function時,能設定不同的處理方式)
throw list: 告訴使用者這個函式可能會用到那些throw,注意若在函式中寫了throw list,又用到了沒有註冊進throw list的throw,compile會過,但真的用到時會進到unexcept()錯誤處理
簡單的Exception handler範例
```C++
#include <bits/stdc++.h>
using namespace std;
//throw是靠型態來認出要用哪個catch的
//自定義型態便可處理各式情況
class Devide_By_Zero {};
int main()
{
int a=5,b=0;
try //主線情況
{
if(b==0) throw Devide_By_Zero(); //丟出型態為Devide_By_Zero的class以決定要使用哪個Exception handler
cout<<a/b<<endl; //若沒錯的話,才會執行這裡
}
catch(Devide_By_Zero a) //主線情況出錯時的例外處理情況
{
cout<<"you Devide_By_Zero!"<<endl;
}
catch(...) {cout<<"error!"<<endl;} //若沒有符合throw型態的catch,則會執行此catch
}
//本例會輸出 you Devide_By_Zero!
```
Exception handler結合函式的應用(較佳的架構)
```C++
#include <bits/stdc++.h>
using namespace std;
//throw是靠型態來認出要用哪個catch的
//自定義型態便可處理各式情況
class Devide_By_Zero {};
int calculate_1(int a,int b)
throw (Devide_By_Zero) //可寫可不寫,目的是告訴使用者這個函式可能會丟出那些Exception
{
//注意: 下面一行throw後,會回到呼叫函式的地方查找catch,不是在function本身找catch
//在不同地方呼叫同一個函式可能會進到不同的catch中做不同處理
if(b==0) throw Devide_By_Zero(); //丟出型態為Devide_By_Zero的class以決定要使用哪個Exception handler
return a/b;
}
int calculate_2(int a,int b)
throw (Devide_By_Zero) //可寫可不寫,目的是告訴使用者這個函式可能會丟出那些Exception
{
if(b==0) throw Devide_By_Zero(); //丟出型態為Devide_By_Zero的class以決定要使用哪個Exception handler
return a/b;
}
int main()
{
int a=5,b=0;
try //主線情況
{
cout<<calculate_1(a,b)<<endl;
}
catch(Devide_By_Zero a) //主線情況出錯時的例外處理情況
{
cout<<"you Devide_By_Zero in calculate_1!"<<endl;
}
try //主線情況
{
cout<<calculate_2(a,b)<<endl;
}
catch(Devide_By_Zero a) //主線情況出錯時的例外處理情況
{
cout<<"you Devide_By_Zero in calculate_2!"<<endl;
}
}
//本例會輸出
//you Devide_By_Zero in calculate_1!
//you Devide_By_Zero in calculate_2!
```