--- tags: 課程影片筆記 --- # 物件導向程式設計 Lecturer : 温宏斌 ## Lec 01 C/C++ Overview and OOP by Example - 3-steps in building a C++ program. - preprocessing - recognize meta-information about the code. - meta-information:compiling environment, operating system...etc - compiling - translate source code into machine-dependent object code. - linking - link together all individual object files into an application. - Preprocessor - **Directives** starting with "**#**" character. ```cpp #include <iostream> ``` - Common Directives - #include <[file]> - #define [key] [value] - replace the key with the value everywhere. - can be used for a macro (similar to function) ```cpp #define f(x) x*x+2*x+4 int main() { int k = 0; cout << f(k) << endl; //cout << k*k+2*k+4 << endl; // 注意 macro 做的是直接地取代,所以可能會造成下列錯誤 cout << f(k+1) << endl; //cout << k+1*k+1+2*k+1+4 // 修正(i) cout << f((k+1)) << endl; // 修正(ii):更改 macro,改成(x)*(x)+2*(x)+4 return 0; } ``` - (#ifdef [key] / #ifndef [key]) + #endif - 用來避免循環引入 ```cpp //in A.h #ifndef A_H // 如果沒有定義過A_H,就執行到 #endif 前 #define A_H #include <B.h> ...processing... #endif // 結束定義 ``` ```cpp //in B.h #ifndef B_H #define B_H #include <A.h> ...processing... #endif ``` ```cpp //in main.cpp #include <A.h> #include <B.h> ... processing ... ``` - Namespaces - Namespace solve the naming conflicts between different pieces of code. - OOP - OOP is just programming paradigm based on obejects. - features - (i) encapsulation - (ii) inheritance - (iii) polymorphism ## Lec 02 Storage, Pointer/Reference, and Dynamic Memory ### Storage Class in C/C++ - A variable is formally declared as : ```storage_class var_data_type var_name``` - Ex: ```auto int x=5;``` - 5 storage classes for variables : - auto : 自動判別變數型別。 - register : 將變數儲存在 register (暫存器)以提高讀取的速度。 - extern : 表示在某個 scope 內所宣告的變數並非是 local 而是來自於 scope 外 (可能來自 global 或其他程式)。 - static : 使變數在離開 scope 仍保持存在,生存範圍延伸至 global 但 visibility 仍為 local。 - mutable : 使 class 或 struct 內的 const 仍然能做變更。 ### Pointer and Reference - void pointer - 無形別指標 ```cpp void *p1; int *p2, x = 3; p1 = p2 = &x; //p1 and p2 both point to x int y = *p1 //ERROR! p1 cannot be directly derefeerenced! int y = *(int *)p1; //Legal. ``` - void pointer to function ```cpp //fun.cpp // {void (回傳型態)} {(*fp) (函式名稱)} {(void *) (傳進的變數型態)} void fun1(void (*fp)(void *), void *q){ fp(q); //使用時不需解參考 } void fun2(void *r) { int a = * (int *) r; cout << a << endl; } ``` ```cpp //main() block in main.cpp int var = 22; void *p = &var; fun1(fun2, p); //傳入時 fun2 不用加&來取得位址,同理而言,使用 function pointer 當函式時也不需要解參考。 ``` - constant references - 使傳入的參考在函式不會被改變 ```cpp void fun(const int &cref) { cout << cref/15; //no probelm cref++; //error! cannot modify it. } ``` - return reference by function - 傳參考也可以擺在等號左邊,若function前沒有加&,則無法擺在等號左邊。 ```cpp int &fun(int *a, int i) { if(i>0 && i<5) return a[i]; else exit(0); } //這是一個通用 assign 陣列的 function //iary is an integer array for (int idx=0; idx<5; idx++) { fun(iary. idx) = idx*2; //作assign cout << fun(idary, idx); //回傳的是一個(array內的)整數值 } ``` - funtion pointer - 括號的重要 ```cpp int *fun1(); //回傳型態為 int pointer 的函式 int (*fun1)(); //一個指向回傳型態為 int 的函式的pointer ``` - 4 types of constant modifier (訣竅:從右往左讀) - a reference to a consant (a constant reference) - A read-only alias - 參考本身為常數,無法作改變,但被參考的變數本身仍然可改變。 ```cpp int x = 8; const int & xref = x; // a ref to a const. x = 33; cout << xreft; xref = 15; //error! cannot modify xref x = 50; //OK ``` - a pointer to a constant (右到左為 : non-constant pointer -> constant int) - 指標本身指到的位址可以改變,但指標指到的內容為常數,無法改變(解參考)。 ```cpp int x = 4; y = 7; const int *pt = &x // a pointer to a const. cout << *pt; //print 4 pt = &y; cout << *pt //print 7 *pt = 11; //error! cannot modify the dereference. ``` - a constant pointer (右到左為 : const pointer -> non-constant int) - 不能改變指標指到的位址,但指標指到的內容可以改變。 ```cpp int var1 = 3, var2 = 5; int * const cpt = &var1; //a const. pointer to a declared variable. *cpt = 8; //change the value cpt points to. cout << var1; //print 8 cpt = &var2; //error! cannot modifiy the pointer. ``` - a constant pointer to a constant (右到左為 : const pointer -> constant int) - 一個用來指向常數的指標。 - 指標所指到的內容不能改變。 - 指標所指到的位址(變數)不能變更。 ```cpp int var1 = 11, var2 =22; const int const *cptc = &var1; //a const. pointer to a constant. *cptc = 33; //error! cannot modify cout << var1; //print 11 cpt = &var2; //error! cannot modify cout << *cptc; //print 11 ``` ### Memory Allocation and Dynamic Array - Dynamic Memory Allocation - 在 run-time 時才配置記憶體。 - 使用 Heap (a.k.a freestore) 的記憶體 - 配置記憶體的方法: - C : malloc(), calloc(), realloc(), free() - C++ : new, delete - Memory Leaking - 常發生於當不斷地用指標去要一塊新的記憶體但沒有回收舊的記憶體時。 ```cpp int *ptr = new int; //allocate 1st block *ptr = 15; //access 1st block ptr = new int; //allocate 2nd block *ptr = 7; //access 2nd block //1st block can't not be accessed anymore. ``` - Dynamic Array - Create Dynamic Arrays : with "new" operator ```cpp int iSize = 0; cin >> iSize; typedef double* DoublePtr; DoublePtr d; d = new double[iSize]; //要一塊記憶體來創建 array ``` - Delete Dynamic Arrays : with "delete" operator ```cpp delete [] d; //delete array that d points to. d = NULL; ``` - Create Multi-dimensional Arrays ```cpp typedef int* = IntArrayPtr; IntArrayPtr* m = new IntArrayPtr[3];//create a 2-D array that comtains 3 1-D arrays. for (int idx=0; idx < 3; idx++) m[idx] = new int[4]; //建立一個 3*4 的2-D矩陣 ``` - Delete Dynamic Arrays ```cpp int **Mat = new int *[5]; //create 5 rows for (int r=0; r<5; r++) Mat[r] = new int [9]; //create 9 columns; ...... //some processing for (int r=0; r<5; r++) delete [] Mat[r]; //clean column delete []Mat; //clean rows Mat = NULL; ``` - Expand Dynamic Arrays ```cpp int n = 0; int *ivec = new int [MAX]; while (cin >> ivec[n]) { n++; if (n >= MAX) { MAX *= 2; int *temp = new int [MAX]; for (int j=0; j<n; j++) temp[j] = ivec[j]; delete []ivec; ivec = tmp } } ``` - Shallow copy (copy-by-address) vs. Deep copy (copy-by-value) - Pass Arrays to Function - 當我們傳遞陣列給 function 時,我們傳遞的是該陣列第一個 index 的記憶體位址。 ```cpp int FindMax(int *val, int size); ...... int max = FindMax(array, size); ``` - Return Array from Function ```cpp int *somefun(); //一個回傳整數指標的function int *ptr = somefun(); ``` ## Lec 03 Classes (I) - Basics, Constructors and Destructors ### Class - 簡介 - 類別 Class 是 OOP 中的基礎 - 將 data 和 function 綁在一起 - 一個 class 宣告出的實體稱為 objects - Class 是抽象的 (takes no memory) 且由一系列物件所組成。 - 每個物件 Object 擁有自己的獨特性、狀態和行為 - states : data fields - behaviors : a set of functions - Class Declaration - 類別的名稱必須是合法的識別字(通常以 C or T 開頭來命名) - 類別的 body 包含很多 "data members" (variables) and "member functions" (methods) ```cpp class class_name { public : //public variables and function protected : //protected variables and function private : //private variables and function }; ``` - Declare Classes - (i)先宣告類別,再定義物件。 ```cpp class someclass { ...//implement data and functions }; someclass obj1, obj2, ... ,objn; ``` - (ii)宣告完類別馬上定義物件。 ```cpp class somecalss { ...//implement data and functions } obj1, obj2, ..., objn; ``` - Access Members in Objects - (i) object name and dot(.) operator ```cpp CScore stu1; cout << stu1.name; stu1.computeAVG(); ``` - (ii) a pointer to the object and arrow(->) ```cpp CScore *pStu = &stu1; cout < pStu -> name; pStu->computeAVG(); ``` - (iii) a reference to the object and dot(.) operator - Declare Functions in Class - 定義在 class body 內 : ```cpp class CScore { private: char name[20]; int subj[3]; public: double computeAverage(); { return (subj[0]+subj[1]+subj[2])/3.0; } void SetName(char *inName) { strcpy(name, inName, 20); } }; ``` - 定義在 class body 外 : 可以隱藏程式碼 ```cpp class CScore { private: char name[20]; int subj[3]; public: double computeAverage(); void SetName(char *inName) //不像一般的 function , member function 的 parameter 的名字需要寫出來。 }; inline double CScore::computeAverage() // field qualifier (::) 來表示此函式屬於何種類別。 { return (subj[0]+subj[1]+subj[2])/3.0; }//預設不會是 inline 需要自己加。 ``` - Preventing Multiple Declarations - 若在 head1.h 中包含 circle.h 且 testHead.h 中包含 head1.h 和 circle.h , 則可能會發生重複宣告的問題。 - 此時使用 Preprocessor directive 來解決這問題。 ```cpp #ifndef CIRCLE_H #define CIRCLE_H //original class declaration in circle.h class CCircle { ... }; #endif ``` - Accessor and Mutator Functions - 若想存取或變更類別內 private 的資料,在 public 區域中需要有一個 "get" 函式作為 accessor function 或一個 "set" 函式做為 mutator 函式。 ```cpp class CScore { private: char name[20]; int subj[3]; public: void setName(char *inName) //mutator { strcpy(name, inName, 20); } char *getName() //accessor { return name; } }; ``` ### Constructors and Destructors - Constructors - 特性: - 用來初始化類別。 - 建構函式可以有多種參數,用來重載。 - 其名稱與類別相同。 - 可以有預設值。 - 不能有回傳型態。 - Example: ```cpp CScore::CScore(char* str, double* dary) { strncpy(name, str, 20); subj[0] = dary[0]; subj[1] = dary[1]; subj[2] = dary[2]; } CScore::CScore(char* str) { strncpy(name, str, 20); } CScore::CScore(double* dary) { subj[0] = dary[0]; subj[1] = dary[1]; subj[2] = dary[2]; } CScore::CScore(){;} //若沒有定義任何 constructer ,編譯器會自動生成一個沒有參數的預設建構函式。 CScore::CScore(const CStr &old) { name = new char [strlen(old.line)+1]; strcpy(line, name); subj[0] = old.subj[0]; subj[1] = old.subj[1]; subj[2] = old.subj[2]; } //複製 constructar ``` - Destrctors - 特性: - 用來清除物件。 - 不像 constructor 一樣有很多個,只能有一個。 - 若沒有宣告,則編譯器會自動生成一個。 - 解構函式名稱:" ~ + class name "。 - 不能有回傳型式及任何參數。 - 當脫離生存範圍 (scope) 時,程式會自動呼叫解構函式。 - Example 1: ```cpp //宣告和定義類別 class CStr { private: char * line; public: CStr() { line = NULL; } //A CStr(char *cline) { line = cline; } //B ~CStr() {} //Destructor }; ``` ```cpp int main(){ char *p = "Peter Pan"; CStr one(p); //call B delete [] p; one.~SCtr(); //call destructor return 0; } ``` - Example 2:改用 deep copy ```cpp //宣告和定義類別 class CStr { private: char * line; public: CStr() { line = NULL; } //A CStr(char *cline) { line = new char [strlen(cline)+1]; strcpy(line, cline); } //B ~CStr() { if (line) delete [] line; line = NULL; cout << "done" << endl; } //Destructor //因為在建立時有配置動態記憶,所以解構函式內需要回收這些配置出的空間。 }; ``` ### Composition - Composition - 一個類別中包含了另一個類別。 - 較 top-level 的類別稱為 composed classes,而被包含的類別稱為 member object。 - Example: ```cpp class CPoint { int x, y; //沒寫 modifier 預設為 privtate public: CPoint() { x=0; y=0; } CPoint(int a, int b) { x=a; y=b; } void set(int a, int b) { x=a; y=b; } void move(int a, int b) { x+=a; y+=b } }; //member object class CRect { CPoint p1, p2; //give two points Public: CRect() { p1.set(0, 0); p2.set(0, 0); } CRect(int a, int b, int c, int d) { p1.set(a, b); p2.set(c, d); } CRect(const CPoint &q1, const CPoint &q2) { p1 = q1; p2 = q2; } }; //composed class ``` - 錯誤的初始化方法 (i) ```cpp class CRect { CPoint p1(0, 0); CPoint p2(0, 0); } //illegal! 類別的宣告僅是型態並非實體,在實體 p1 和 p2 尚未產生時不能賦值。 ``` - 錯誤的初始化方法 (ii) ```cpp CRect::CRect (int a, int b, int c, int d) { p1(a, b); p2(c, d); ... } //illegal! 僅有在宣告時能使用建構子來初始化。 //E.g. int a; a(4); //illegal! ``` - 另一個合法的初始化:使用 ":" 來初始化 ```cpp //in class CRect CRect (int a, int b, int c, int d) : p1(a,b), p2(a,b) {} CRect (const CPoint &q1, const CPoint &q2) : p1(q1), p2(q2) {} //用 : 可以在初始化 CRect 時順便用 CPoint 的 constructor 來初始化。 ``` ## Lec 04 Classes (II) - Advanced Topics - Object Array - 可以用 class 來建立陣列: ```cpp CScore StuAry[3]; CScore[0] = ("Adam", {43, 62, 85}); CScore[1] = ("Hank", {73, 65, 38}); CScore[2] = ("Peter", {23, 54, 87}); ``` - Pointer to Member Function - 格式:<rtn type> (<CName> :: *<pFunc>) (<para_list>); ```cpp double (*pF) (); ... pF = &Fun; //or pF = Fun; double result = (*pF)(); ``` - class 會影響 return type ```cpp pF = &StuAry[0].computeAverage; // compilation error! // "double" is not equal to CScore::double ``` 修正: ```cpp double (CScore::* pF) (); ... pF = &CScore::computeAverage; ``` - 使用 pointer to the member function: ```cpp CScore * p1 = &(StuAry[2]); double (CScore:: * p2) () = &CScore::computeAverage(); cout << (p1->*p2)(); //等價於cout << p1->computeAverage(); ``` - Pointer to Data Member - 格式: ```cpp <datatype> (<CName> :: *<pName>); <pName> = &<CName>::<data_member>; ``` - Example: ```cpp class X { public: int a; void f(int b) { cout << b << endl;} } int X::*pa = &X::a; X obj1; obj1.*pa = 10; cout << "value a =" << obj1.*pa; ``` - this pointer - 特性: - 當我們去呼叫某個物件時,用來存放物件位址的 pointer 。 - 通常不會呈現在程式碼裡,編譯器會自行處理。 - 但也可以主動用 this -> [member] 來取得資料。 - 當函式內有同名的參數是也可使用 this。 - 實際應用:可以 return class 的東西 ```cpp class Cpoint { public: Cpoint offset(int diff) { x += diff; y += diff; return *this; } }; //做點的偏移 int main() { CPoint p, q; p.setPt(a, b); q = p.offset(3); // p 和 q 都是(6,8) } ``` ### Different Kinds of Members - Static - Static Data Member - 僅能在 class 外作初始化。(C++ 11以前) - 加上 static 後,不受 access modifier 影響。 - 不屬於某個 object ,而屬於整個 class,即宣告出的所有物件共享這個變數。 ```cpp class X { private: //declacre a static data variable static int count; }; //initialize the static member int X::count = 0; ``` - Static Function - 僅能用來處理 Static Data Member,不能呼叫非 Static 的變數。 - Const - Constant Data Member - 常與 static 一起使用 ```cpp class CScore { protected: //declare a static constant static const int max; } const int CScore::max = 100; ``` - 可用在 & 但須在 constructor 內被初始化。 ```cpp class CScore { public: //declare a constant reference //constant reference should be initialized in construtor. const int & sc; } ``` - Constant Methods - const 物件或 const 物件的參考只有 constant methods 能呼叫。 ```cpp class CScore { public: char* getName() const { return name; } void setName(const char *uname) { strcpy(name, uname); } }; ``` ```cpp CScore S1(); const CScore & S3 = S1; S1.setName("Bob"); //legal S3.getName("Bob"); //illegal, setName() is not a const method. S3.getName(); //legal ``` - Muatable - Mutable Data Member - 在 constant function 中不能變動任何資料成員,為了能變動資料成員,會加上 mutable 使這些 data member 能被變更。 ```cpp class CScore { public: mutable int sCount = 0; } ``` ```cpp void CScore::showName() const { cout << sCount++ << ":" << name << endl; } // 一個 constant method ``` - 若宣告一個 constant object ,但想讓內部的 data member 做變更,則可以用 mutable 來修飾。 ```cpp const COne { public: mutable int x; int y; } ``` ``` const COne m; m.x = 10; //illegal m.y = 20; //legal ``` - 不能用來修飾 methods. - Explicit - 用來規定 constructor 的呼叫方法。 - explicit call : CScore one(value); - implicit call : CScore one = value; ```cpp explicit CScore::CScore(const char* cstr) { ... } CScore one = "TOM"; //illgal CScore one("TOM"); //legal ``` ### Friend - Friend Function - 特性 - 放置在類別的宣告內,前面會加 friend。 - 在類別外面做定義,並非 member function 。 - 如果這個 friend function 存在於兩個類別內,則規定在宣告時必須有 prototype。 - friend function 不能被繼承。 - friend function 可以存取 class 內的 data member。 - Friend Class - 特性 - 當某個類別內所有的 member function 需要用到別的類別的 data member 時使用。 ## Lec 05 Understanding Friends and Overloading Operators - w ## Lec 06 Streams and File I/O - Stream is a sequence of bytes. ### I/O stream - Two level of I/O - Low-level I/O - 不管資料的型態,全都以記憶體來進行資料的傳遞。 - for 高速度、大檔案的資料處理 - High-level I/o - byte 根據資料型態被分為具有意義的 group,ex:double, int.... - Stream Class - istream class - 包含 extraction 運算元 \>\> - cin 是 istream 中的一個 member 或 object - 裡面包含各種 member function - e.g. get(), getline(), ignore() - ostream class - 包含 insertion 運算元 \<\< - 裡面包含各種 member function - e.g. setf(), unsetf(), precision(), width() - istream 跟 ostream 都被包含在 iostream 這個函式庫內 - Member function of istream - get() - prototype: ```cpp istream& get(char &); ``` - cin.get() 可以抓取任何字元,也可以不傳任何參數進去,若沒有傳參數這預設回傳型態為 int (得到字符的 ascii code) ```cpp int c = cin.get(); ``` - 用 get() 抓取多個字元 - protoype: ```cpp istream& get(char *str, int len, char c = '\n'); //return cin 本身 ``` - 第一個參數為存放字串的 address - 第二個參數為字串的長度 - 第三個參數為抓取到哪個字元為止 (預設為換行符號) - getline() - Prototype: ```cpp istream& getline(char *str, int len, char c='\n') ``` - 與 get() 第三種相同,會抓取到指定的字元長度或特定的字元為止。 - 輸入的 cstring 需要先分配記憶體,否則會出現 segmentation fault - ignore() - 用來跳過從 stream 讀入的字元。 - Prototype ```cpp istream& ignore(int length = 1, char c = '\n'); ``` - length 參數代表著最大忽略的字元數量。 - Member function of ostream - setf(), unsetf() - 決定著 cout 物件的狀態 flag (這些 flag 被儲存由 bit 表示),所有狀態皆在 ios:: 底下。 - ios::left - ios::right - ios::dec - 設定輸出為十進位制 - ios::showpos - 將正號顯示出來 - ios::showpoint - 顯示小數點後至第六位 - Example ```cpp cout.setf(ios::showpos | ios::dec | ios::showpoint); cout << 4.0; //輸出結果為 +4.000000 cout.unsetf(ios::showpos); cout << 4.0; //輸出結果為 4.000000 ``` - width() - 調整輸出的寬度 - width()屬於揮發性設定,只會套用在下一次的輸出中。 - Example ```cpp cout.width(5); cout << 12; //輸出結果為 [][][]12 ``` - precision() - 設定輸出的位數精確度。 - Example ```cpp cout.precision(4); cout << 12.98765; //輸出結果為 12.99 ``` - ostream/ifstream class - 利用 open() 來連結外部檔案與 ofstream 物件,可利用下面的函式調整開啟狀態。 - 利用 close() 來關閉已使用完的檔案。 - ios::in - open the file for input - ios::out (default) - open the file for output - ios::app - append the file (rather than re-create it) - ios::ate - open an existing file (either input or output) and seeks the end. - ios::binary - open the file for binary I/O - 可利用 good() 或 bad() 來檢查檔案是否開啟成功。 - Example ```cpp #include <iostream> #include <fstream> #include <iomanip> using namespace std; int main() { fstream myFile; myFile.open("test.dat", ios::in); if (myFile.good()) cout << "File opened!" << endl; else cout << "Cannot open file!" << endl; return 0; } ``` - 利用 operator overloading 改變讀寫物件 ```cpp class CStu { int sid; string name; double gpa; friend ostream& operator<<(ostream&, CStu); friend istream& operator>>(istream&, CStu&); } ostream& operator<<(ostream& out, CStu s) { out << s.sid << " " << s.name << " " << s.gpa << endl; return out; } istream& operator>>(istream& in, CStu& s) { in >> s.sid >> s.name >> s.gpa; return in; } ``` ```cpp #include <iostream> #include <fstream> using namespace std; int main() { CStu a; ofstream out; out.open("text.txt"); cout << "Enter id, name, and gpa\n"; while (cin >> a) { out << a << endl; cout << "Enter id, name, and gpa\n"; } out.close(); return 0; } ``` - istringstream / ostringstream class - 將字串串流化。 - 利用 str(),將字串轉成 stringstream,也可以反過來將 stringstream 轉成字串。 - 若想重複使用同一個 stringstream ,需要利用 clear() 及 str("") 來清除 buffer 內的資料。 - Example ```cpp string buffer; getline(cin, buffer); stringstream ss; ss.str(buffer); long value = 0; double data = 0.0; ss >> value >> data; ss.str(""); ss.clear(); ``` ## Lec 07 Inheritance (I) - Basics & Single Inheritance - Fundamentals of Inheritance - Code reusability is the key to enable object-oriented programming - Inheritance is a relationship between classes. - one class inherits the properties of another class - it is an ***is-a*** relationship - Example ![owo](https://i.imgur.com/P4BtnIT.png) - Base (super, parent) class v.s. Derived (sub, child) class - base class is the class to be inherited - derived class is the new class on top of a base class - A base class can have multiple derived classes - Declaration of Inheritance - General form of a derived class : ```cpp class <Derived Class>: <Access Specifier 1><Base Class 1>, <Access Specifier 2><Base Class 1>, { //specify properties of its own }; ``` - only one ":" - if >= 2 base classes -> multiple inheritance - default as private access - Example - Class CCircle ```cpp class CCircle { protected: double radius; public: CCircle (double r= 1.0): radius(r) {;} void setR(double r = 1.0) { radius = r;} double calVol() const { return (PI*radius*radius); } void showVol() const { cout << "radius=" << radius << endl; cout << "volume=" << calVol() << endl; } } ``` - (derived) Class CCylinder ```cpp class CCylinder: public CCircle { protected: double length; //add one data member public: CCylinder(double r=1.0, double l=1.0): CCircle(r), length(l) {;} void setRL(double r=1.0, double l=1.0) { radius = r; length = l;} double calVol() const { //compute volume return (CCircle::calVol()*length); //因為同一個名字所以須標示出是不是繼承來的函數 //此稱為函數的 override } //inherit showVol() from CCircle void displayVol() const { cout << "d radius=" << radius << endl; cout << "d volume=" << calVol() << endl; } } ``` - 編譯器具有向上兼容的功能,所以可以將 derived class assign 給 base class ```cpp class CCircle {}; class CCylinder : public CCircle {}; CCircle cr1, cr2; CCylinder cy1, cy2; cr1 = cy1; //legal cy1 = cr1; //illegal ``` - Derive Classes - A derived class can change - 增加新的 member - overload or overide (redefine) member function in the base class - 改變存取性質 (public、private、protected) - 無法被繼承的資料 - 建構子、解構子 (constructor / destructor) - overloading assignment - 友元函數 (friend function) - static member - Access Specifier - public inheritance - 保持(不改變) base class 裡面的 accessibility - derived class 的 ***member function*** 可以存取 base class 中的 public 和 protected member - derived class 不可以存取 base class 中的 private member - derived class 所宣告的 ***object*** 只能存取 base class 中的 public member ![owo](https://i.imgur.com/mSbInrF.png) - private inheritance - 將 base class 的存取性改成 private - derived class 的 ***member function*** 仍然可以存取 base class 中的 public 和 protected member - derived class 不可以存取 base class 中的 private member - derived class 所宣告的 ***object*** 不能存取 base class 中的***任何*** member - 如果想直接用物件來呼叫 base class 的 member function,可以 override 該 function ![owo](https://i.imgur.com/uOZBQap.png) - protected inheritance - 將 base class 內的 public 與 protected member 轉換成 protected ![owo](https://i.imgur.com/ktvXEkQ.png) - Protected Member - 對於 base class 外來說,protected member 可視為 private member - 對於 derived class 來說,protected member 可視為 public member - 對於 base class 所宣告的 object 而言,protected member 不能被存取 - Constructors of Derived Class - 若沒有在初始化時同時呼叫 base class 的建構子,則會自動呼叫不帶參數的建構子。 - 定義 derived class 的建構子的步驟: - (i) 呼叫 base class 的建構子 (處理繼承的 data member) - (ii) 處理 derived class 後來新增的 data member - Example ```cpp class CPsn //base class { public: CPsn(string sname, char s) { name = sname; sex = s; } ~CPsn() {} string GetName() { return name; } char GetSex() { return sex; } }; class CStu : public CPsn //derived class { int age; string addr; public: CStu (string sname, char s, int a, string ad): CPsn(sname, s) { age = a; addr = ad; } } ``` - Destructors of Derived Class - derived class 會**自動**呼叫 base class 的 destructors - 先呼叫 derived class 的 destructor,再呼叫 base class 的 destructor,再回到 derived class 的 desturctor。 - Assignment and Copy Constructors - 重載後的 assignment 跟 copy constructor 皆不能被繼承。 - 必須分別使用 base class 的 overloading assignment 跟 copy constructor 來協助。 - Conversion of Derived/Base Class - 可以將 derived class assign 給 base class - 可以用 derived class 將 base class 初始化 - 上述對於物件的處理不可以反向。 - 但兩種 class 的 pointer 可以互指。 - Example ```cpp class B0 { public: void Show() { cout << "B0::Show()" << endl; } }; class B1 : public B0 { public: void Show() { cout << "B1::Show()" << endl; } }; class D2 : public B1 { public: void Show() { cout << "D2::Show()" << endl; } }; void fun(B0* ptr) { ptr->Show(); }; ``` ```cpp int main() { B0 r0; B1 r1; D2 r2; B0 *p; p = &r0; //B0 pointer to B0 object fun(p); //output: B0::Show() p = &r1; //B0 pointer to B1 object fun(p); //output: B0::Show() p = &r2; //B0 pointer to B2 object fun(p) //output: B0::Show() //雖然能互指,但只能使用原來 pointer 型態的 function return 0; } ``` ## Lec 08 Inheritance (II) - Multiple Inheritance & Virtual Base Class ### Multiple Inheritance - Declaration - 在宣告類別時如同單一繼承,一個一個列出來。 - Example ```cpp class Sofa { public: void sit() { cout << "sit!" << endl; } }; class Bed { public: void lie() { cout << "lie!" << endl; } }; class Sofabed : public Sofa, public Bed { ... }; ``` - Properties - 多重繼承其實並非必要,皆可透過單一繼承完成。 - 若兩個 base classes 具有相同名字的 member,則需使用 resolution operator (::) - Derived class 的建構子必須提供用來給 base class 建構子使用的 argument。 - base class 的建構子呼叫順序需依照繼承的順序呼叫 (left to right) - Example 1 ```cpp class B1 { int x; protected: int GetX() { return x; } public: void SetX(int a=1) { x=a; } }; class B2 { int y; public: int GetY() { return y; } void SetY(int a=1) { y=a; } }; class B3 { int z; public: int GetZ() { return z; } void SetZ(int a=1) { z=a; } }; class D4 : public B1, public B2, public B3 { int w; public: void SetW(int a) { w=a; } void ShowVal() { cout << GetX() << GetY() << GetZ() << endl; } }; ``` - Example 2 如果具有同名的 member function ```cpp class Sofabed: public Sofa, public Bed { public: void fold() { cout << "fold!" << endl; } }; int main() { Sofabed myfun; myfun.SetWeight(100); //不知道 call 哪個 return 0; } ``` - Ambiguity in Multiple Inheritance - Case 1:不同的 parent classes 具有相同名稱的 member - 透過 scope resolution - Example ```cpp class Sofabed: public Sofa, public Bed { public: void fold() { cout << "fold!" << endl; } void ShowWeight(); }; Sofabed myfur; myfur.Sofa::ShowWeight(200); myfur.Bed::ShowWeight(300); myfur.ShowWeight(500); //Derived Class 的 ShowWeight ``` - 副作用 1:base class 中所有同名異式皆會被覆蓋。 - 副作用 2:若是用 pointer 來呼叫 member function,則編譯器會去尋找原來 pointer 呼叫類別中的 member。 ```cpp SofaBed obj; obj.Sofa::SetWeight(25); Bed *ptr; ptr = new Bed; ptr->SetWeight(70); ptr->ShowWeight(); ptr = &obj; ptr->ShowWeight(); //雖然指向 SofaBed,但會呼叫 Bed 的 ShowWeight() ``` - Case 2:若 derived class 多重繼承的 base classes 同時繼承了同一個 class。(CB、CC 同時繼承 CA,CD 又繼承 CB 和 CC) - 需透過 Virtual base class 來解決重複繼承的 data member。 - 若沒有使用 virtual base class 會導致複製到兩分 common base class 的 member。 - Virtual Base Class - 使 derived class 只會繼承(複製)一次 common base class。 - 使用時機:定義 common base class 的 derived class 時 - 使用後 common base class 中的 data member/function 就不會出現 ambiguity - 使用後 derived class (最下層) 的 constructor 需要呼叫 common base class 的 constructor。 - Example ```cpp class CA { public: int x; CA(int a=0) { x=a; } }; class CB : virtual public CA { public: int y; CB(int a=0, int b=0):CA(a) { y=b; } }; class CB : virtual public CA { public: int z; CC(int a=0, int b=0):CA(a) { z=b; } }; class CD: public CB, public CC { public: int w; CD(int a=0, int b=0, int c=0, int d=0, int e=0): CA(a), CB(a,b), CC(c,d) { w=e; } //使用 virtual base class 後需要呼叫 CA 的建構子 void Show Val() { cout <<"x=" << CB::x << " y="<< y; cout <<"x=" << CC::x << " z=" << z; cout <<"w=" << w << "x=" << x << endl; } }; ``` - 在 CD() 裡呼叫 CB() 和 CC() 時就不會再呼叫 CA(a),會被自動忽略 (只呼叫一次)。 - Order of Contructors/Destructors - 建構子呼叫順序: - (i) 依照宣告順序來呼叫 virtual base classes 的建構子(並非初始化順序) - (ii) 再依照宣告順序來呼叫其他 base classes 的建構子 - 建構子會先去尋找有沒有 virtual base class 再開始呼叫建構子。 - 解構子呼叫順序: - 與 constructor 相反。 - Example ```cpp class C1 { public: C1() { cout << "construct C1\n"; } ~C1() { cout << "destruct C1\n"; } }; class C2 { public: C2() { cout << "construct C2\n"; } ~C2() { cout << "destruct C2\n"; } }; class C3 { public: C3() { cout << "construct C3\n"; } ~C3() { cout << "destruct C3\n"; } }; class C4 { public: C4() { cout << "construct C4\n"; } ~C4() { cout << "destruct C4\n"; } }; class CD: public C3, virtual public C4, virtual public C2 //宣告順序 { C1 obj; //use a private C1 object public: CD():obj(), C2(), C3(), C4() //初始化順序 { cout << "construct CD\n"; } ~CD() { cout << "destruct CD\n"; } }; ``` ```cpp int main() { CD dd; cout << "here!\n"; return 0; } /* Output: construct C4 //1st virtual base class construct C2 //2nd virtual base class construct C3 //1st othe base class construct C1 //private member of CD construct CD here destruct CD //反順序 destruct C1 destruct C3 destruct C2 destruct C4 */ ``` ## Lec 09 Polymorphism - Virtual Functions and Abstract Base Class ### Polymorphism 多型 - 已見的多型: - function overloading、operator overloading (在 compile 已知) - function overriding (在 run-time 已知) - Binding - 編譯器保留一塊記憶體,用來儲存 user 定義的函式,並記錄呼叫該函式所需讀取記憶體位址。 - Static binding (early binding) - 在 compile-time 時就已經連結好每次呼叫函式時所對應的記憶體空間。 - Dynamic binding (late binding) - 在 run-time 時才會透過查詢符號表決定呼叫某個函式時該讀取哪塊記憶體。 - **在同一條繼承鍊上,透過 virtual class,使 base class 的指標或參考可以作用在 derived class 的物件上。** - 兩種多型比較 - Compile-time polymorphism - 使用 static binding - 速度較快 - 透過 function overloading 與 operator overloading 實現 - Run-time polymorphism - 使用 dynamic binding - 更具有彈性 - 透過 inheritance + virtual function 實現 - Virtual Function - 將同一個繼承鏈上的 function 進行綁定。 - 可以將 derived class 的參考物件賦值給 base class 。 - 可以將 derived class 的指標物件賦值給 base class 的指標。 - Virtual function 告訴編譯器: - 不知道是哪一個 function 會被執行(同名) - 直到使用的時候才會知道該呼叫誰 (run-time、dynamic binding) - 當使用 virtual function 後,透過指標呼叫函式時,並不是看指標的類型,而是看指標所指到資料的類型。 - 如果 f() 在基礎類別中是虛擬函式,則在這條繼承鍊上剩下的所有衍生類別中皆為虛擬含式。 - f() 會被 redefined,或稱為 overriding。 - virtual function 不可以是 global、static 或 friend。 - 解構子可以是虛擬函式但建構子不可以是虛擬函式。 - Virtual function 所操作的函式必須同名同式(參數同) - Example ```cpp class A { public: virtual void ShowFun() { //virtual cout << "A::ShowFun()"" << end; } }; class C : public A { public: void ShowFun(int i) { //not virtual cout << “C::ShowFun()” << endl; } }; ``` ```cpp // in main() C c; A *pa = &c, &ra = c, a = c; a.ShowFun(); //呼叫 class A 的 pa->ShowFun(); //呼叫 class A 的 ra.ShowFun(); //呼叫 class A 的 ``` - More Complicated Example ```cpp class B { public: void f() { cout << “Bf ”; } virtual void g(){ cout << “Bg ”; } void h() { g(); f(); } virtual void m(){ g(); f(); } }; class D : public B { public: void f() { cout << “Df ”; } void g() { cout << “Dg ”; } void h() { f(); g(); } }; ``` ```cpp //in main() D d; B *pB = &d; pB->f(); pB->g(); pB->h(); pB->m(); //Output: Bf Dg Dg Bf Dg Bf ``` - Virtual Destructors - 當使用 pointer 來呼叫解構子時,會呼叫到 base class 的解構子,此時資料的清除會不完整 (memory leakage)。 - 為避免上述問題,將基礎類別的解構子宣告為虛擬類別是好習慣。 - Example ```cpp class B { public: virtual ~B() { cout << “B::~B()\n”;} }; class D : public B { int * iary; public: D(int i) { iary = new int [i]; } ~D() { delete [] iary; cout << “D::~D()\n”; } }; ``` ```cpp //in main() B *pB = new D(10); delete pB; //Output: D::~D() B::~B() ``` - Pure Virtual Function - 宣告出來的函式不佔有記憶體(無實體)。 - Example ```cpp virtual void ShowFun() = 0; ``` - Abstract Base Classes - 若類別中含有 pure virtual function,則此類別不可以宣告任何物件(實體),只能被繼承。 - Example ```cpp class CFig { protected: double x, y; public: void SetDim(double a=0, double b=0) { x=a; y=b; } virtual void Area()=0; //pure virtual! }; class CRec: public CFig { public: void Area(int i) { cout<<“Rec:”<<x*y<<“\n”; } }; class CTri: public CFig { public: void Area() { cout<<“Tri:”<<x*y/2<<“\n”; } }; ``` - Slicing Problem - w - Downcasting & Dynamic_cast - 可以強迫將資料少的類別的位址賦值給資料多的類別。 - 使用:```dynamic_cast```: ```cpp Pet *ppet; ppet = new Dog; Dog *pdog = dynamic_cast<Dog*>(ppet); //也不會出現資料截斷的問題 ``` - 少用,具有一定程度的危險。 ## Lec 10 Templates Function Templates and Class Templates - Function Template - 功能模板即是使用可變換型態的資料,僅僅是一個非具體存在的模板,在呼叫函式的時候才會實例化(instantiation)出對應資料型態的函式。 - 在每個功能模板中,必須要有一個變數為 generic (或稱為 parameterized),即為可變動資料型態的變數。 - 利用關鍵字 class ,來進行模板的定義,在新的編譯器中也可以使用 typename 來定義模板。 - Example ```cpp template <class T> void swap(T& t1, T& t2) { T tmp = t1; t1 = t2; t2 = tmp; } //T 在後續會被當成資料類型 ``` ```cpp int main() { int a(10), b(5); swap(a, b); } ``` - T 可以用來當作函數回傳型態、變數宣告等等,但需要注意其對應的資料型態的運算子有無定義(>、=、== ......)。 - 大於一種可變參數的模板 - 同樣的,若是有自訂資料型態,須注意有無定義運算子。 ```cpp template <class T, class U> bool ShowCompare(T v1, U v2) { ... } ``` ```cpp in main int a(10), b(5); double c(5.0), d(10.0); ShowCompare(a, b); ShowCompare(c, d); ShowCompare(c, a); ShowCompare(b, d); ShowCompare(b, 'e') //可以同類型也可以不同類型 ``` - 指定呼叫的型態 (Explicitly call function) - 形式:```FunctionName<datatype>(parameter);``` - Example ```cpp template <class T, class U> T tripleVal(U val) { T tmp = val*3; return tmp; } ``` ```cpp int a = 4; double b = 8.8; //前兩個自動透過傳入參數來決定 U 型態。 cout << tripleVal<int>(a) << "\n"; //丟a近來,回傳 4*3 = 12 cout << tripleVal<int>(b) << "\n"; //丟b近來,回傳(int)(8.8*3) = 26 cout << tripleVal<int, double>(b) << "\n"; //丟b近來,回傳 (int)(8.8*3) = 26 cout << tripleVal<int, int>(b) << "\n"; //丟b近來,回傳(int)(8*3) = 24 ``` - Class Template - 一個類別的框架,可產生出**內部**不同資料型態的實體。 - 同樣至少需要一個可變參數 - Example ```cpp template <class T> class Number { T num; public: Number(T val) { num = val; } void ShowNumber() { cout << "Number = " << num << "\n"; } }; //in main() Number<int> a(65); a.ShowNumber(); Number<double> b(8.8); b.ShowNumber(); Number<char> c('D'); c.ShowNumber(); Number<int> d('D'); d.ShowNumber(); Number<char> e(70); e.ShowNumber(); Output: Number = 65; Number = 8.8; Number = D; Number = 68; Number = F; ``` - Template Parameters - 3 種模板參數 - type parameter - non-type parameters - template parameter - Type parameter - Example ```cpp template <class C1, class C2, class C3> class X {//...}; ``` - C1,C2,C3 就是類別參數 - 在類別模板實例化的過程中,必須給予型態。 ```cpp X <int, double> a; X <char, double*> b; ``` - Non-Type Parameters - 非類別參數只能為簡單型態 - int, char, bool - enumeration type - reference or pointer to object or function - 非類別參數不能為 - void - float and double - 自定義型態 - Example ```cpp template <int A, char B, bool C> class G1 {//....}; //good template <float* D, double& E> class G2 {//....}; //good template <double F> class G3 {//....}; //error template <PhoneCall P> class G4 {//....}; //error ``` - 可變型別也可以有預設參數類別。 ```cpp template <class T=int, int n=10> class C3 { //... }; int main() { C3< > a; //good C3 b; //error missing < > C3<double, 50> c; //good C3<char> d; //good,n 預設為 10 C3<20> e; //error : missing template argument C3 <,> f; //error C3 <,20> g; //error C3 <char,> h; //error //p.s. c 與 a 屬於不同類別。 } ``` - Template parameter - 把 template class 當作參數 - Example ```cpp ``` - Friend & Ingeritance in Template - Friend Function - 通常用來實現 operator overloading - 需額外給予參數型態 - Inheritance - 衍生模板類別可以繼承自模板或非模板類別(透過指定其參數類別) - 其自然就是模板類別 - Example ```cpp //Base Class template <class T> class PT2D { private: T x, y; public: PT2D() {} PT2D(T a, T b) : x(a), y(b) {} ~PT2D() {} T getX(); T getY(); }; template <class T> T PT2D<T>::getX() { return x; } template <class T> T PT2D<T>::getY() { return y; } ``` ```cpp //從模板類別繼承的非模板衍生類別 //所有的參數型態已被決定 class PT3C: public PT2D<int> { //這裡直接指定繼承 int 型態 private: int z; public: PT3C(int a, int b, int c): PT2D<int>(a,b), z(c) {} //呼叫指定型態的建構子 int getZ() { return z; } }; //從模板類別繼承的模板衍生類別 template <class T> class PT3D: public PT2D<T> { //沒有指定型態 private: T z; public: PT3D(T a, T b, T c): PT2D<T>(a,b), z(c) {} //呼叫沒有模板建構子 T getZ() { return z; } }; //PT3C不屬於類型模板 PT3D屬於類別模板 ``` ```cpp //從非模板類別繼承的模板衍生類別 //繼承自已知 PT2D 屬於 int 的 PT3C template <class T> class PT4D: public PT3C { private: T w; public: PT4D(int a, int b, int c, T d): PT3C(a,b,c), w(d) {} T getW() { return w; } }; ```