樣板、繼承與多型
專案建置[8]
AaW
目錄
- Function Template
- Class Template
- 繼承關係
- 虛擬函數
注意事項:
今天教的內容雖然是物件導向的精髓,但屬於一般高中生都不太會用到的毒瘤語法,盡量聽懂就好
(我們資訊老師也不會繼承)
函式模板
Function Template
什麼是模板(template)
寫程式一定會用到變數
而有時你的變數類型不影響程式邏輯
但是 C++ 卻必須指定變數類型
#include <iostream>
int add_int(int a, int b) {
return a + b;
}
double add_double(double a, double b) {
return a + b;
}
int main() {
std::cout << add_int(2, 3) << '\n'; // 5
std::cout << add_double(4.5, 6.7) << '\n'; // 11.2
return 0;
}
Template 語法
Template 就是讓你使用「萬用型別」的工具
就好像代數一樣
需要用的時候再代入要用的型別就行了
#include <iostream>
template<typename T> // 設 T = 型別名稱
T add(T a, T b) {
return a + b;
}
int main() {
std::cout << add<int>(2, 3) << '\n'; // 5
std::cout << add<double>(4.5, 6.7) << '\n'; // 11.2
return 0;
}
Template 多參數
Template 也可以有兩個以上參數
也就是可以指定多個型別
用在不同的地方上
#include <iostream>
#include <string>
template<typename T, typename U>
void a_and_b(T a, U b) {
std::cout << a << " and " << b << '\n';
}
int main() {
std::cout << a_and_b<int, char>(3, 'G') << '\n'; // 3 and G
std::cout << a_and_b<std::string, float>("Hello", 3.14) // Hello and 3.14
return 0;
}
類別模板
Class Template
類別模板使用情境
Class 當然也可以用 Template
而且應該更有機會用到
C++ 就有一大堆型別都是模板
#include <vector>
#include <pair>
#include <string>
int main() {
std::vector<int> u = {1, 2, 3};
std::vector<double> v = {0.1, 0.2, 0.3};
std::pair<std::string, double> p("Aaron", 12.3);
std::pair<int, std::string> q(42, "Justin");
return 0;
}
稱為 STL(Standard Template Library)
類別模板語法
#include <iostream>
template<typename T>
class Number {
private:
T _value;
public:
Number(T value) : _value(value) {}
~Number() {}
T get_value() { return this->_value; }
void set_value(T new_value) { this->_value = new_value; }
void print() { std::cout << this->_value << '\n'; }
};
int main() {
Number<int> a(42);
a.print(); // 42
a.set_value(13);
a.print() // 13
Number<double> b(3.14);
b.print() // 3.14
return 0;
}
注意!
我們之前一直有強調
寫 class 時要把宣告與實作分開
原因是實作可以先編譯好
要用時再進行連結,以節省編譯時間
但是 class template 沒辦法這樣做
而這跟 template 的原理有關
Template 原理
Template 的本質其實是一種巨集(字串替換)
它在你要使用這個 class 時
才根據你指定的型別臨時生成定義
反過來說,如果這個 class 還沒被拿來用
那它根本就不存在
也不可能被編譯(因為型別不確定)
對專案架構的影響
一個專案最後打包成的函式庫檔案
就是編譯好的實作內容
但當宣告與實作不能分開時
就只能將兩者都放在一個標頭檔裡
而原本的標頭檔與實作檔的副檔名分別為 .h 與 .cpp
那這種包含實作的標頭檔的副檔名就會用 .hpp
朋友
friend
AaW沒有的東西
如果A把B當朋友
B 就可以取得 A 心中的想法private成員
B是什麼?
B可以是一個函數或是一個class
如果是函數,此函數必須在class內進行friend宣告
如果是類別,此類別B必須在A內定義友誼關係
friend function 範例
class Phone{
public:
/*
* 這邊省略,與上面相同,但不包含 equal
*/
friend bool equal(Phone& p1, Phone& p2); // 先定義,不實作
private:
string type; // 隸屬公司
string phone_number;
};
// 我們自己實作 equal
bool equal(Phone& p1, Phone& p2){
if(p1.type == p2.type) return true;
else return false;
}
int main(){
Phone p1("中華電信", "0912345678");
Phone p2("台灣大哥大", "0987654321");
equal(p1, p2);
return 0;
}
friend class 範例
直接在A的public裡面加上這一行
friend class B;
繼承
又回到了class
- 繼承是物件導向重要觀念之一
- 從原本的類別衍生出新的類別
- 這個新的類別會繼承舊類別所有的成員、特性等等
- 繼承體系的優點:
- 維持物件封裝特性
- 不用修改原類別
基礎類別
base class
衍生類別
derived class
這樣畫箭頭比較直觀
但其實這樣畫才是對的
繼承具體來說會繼承什麼?
成員變數與成員函數
全部的
嗎?
衍生類別的建構子、解構子、朋友、 =
運算子要重新定義
繼承的權限表
- 還記得 public、protected和public這三種權限嗎
- base class 在宣告時每個成員都屬於這三種中的一種
- 而繼承時,也分別有三種模式可以選擇
- 衍生類別的成員變數又有三種權限
- 所以......
繼承方式
父類別
成員權限
單一繼承
語法時間
一次只繼承一個父類別
class 衍生類別 : 繼承模式 基礎類別 {
// 類別的定義
};
#include <iostream>
using namespace std;
class car {
public: // 基礎類別中的成員函示宣告為public
void go()
{
cout <<"汽車啟動了!"<< endl;
}
void stop()
{
cout <<"汽車熄火了!"<<endl;
}
};
class freighter: public car
{}; //衍生類別用public繼承
int main() {
freighter ft;
ft.stop();
cout<<"-------------------------------"<<endl;
ft.go();
cout<<"-------------------------------"<<endl;
// ft是freighter類別的一個物件
// 因為繼承關係,可以使用car類別的go()和stop()函數
return 0;
}
public繼承範例
權限會保留為原本樣子
#include<iostream>
using namespace std;
// 宣告類別student
class student
{
private:
int age;
protected:
int lang;
public:
int math;
student() // 建構子
{
age=0;
lang=0;
math=0;
}
void set_age(int a1)
{
age=a1;
}
void show_age()
{
cout << "age=" << age << endl;
}
};
// 以 private型別自類別student繼承成為新的類別s1
class s1:private student
{
public:
void set_lang(int v3)
{
lang=v3; // 可直接設定型別為protected的成員資料lang
}
void set_math(int v4)
{
math=v4; // 可直接設定型別為public的成員資料math
}
void setage(int v5)
{
// 因為無法直接存取private型別的成員資料age
// 必須藉由呼叫set_age成員來設定成員資料age
set_age(v5);
}
void show_data()
{
// 因為無法直接存取private型別的成員資料age,必須
// 藉由呼叫show_age()成員函式來取得age值並在螢幕上顯示
show_age(); // 可直接存取類別student內的protected型別的成員資料lang
// 可直接存取類別student內的public型別的成員資料math
cout << "lang=" << lang << endl;
cout << "math=" << math << endl;
}
};
int main()
{
// 宣告物件obj1
s1 obj1;
// 可藉由呼叫setage成員函式來呼叫類別student內public型別的成員函式set_age()
obj1.setage(35);
// 可直接存取類別student內的protected型別的成員資料lang
obj1.set_lang(100);
// 可直接存取類別student內的public型別的成員資料math
obj1.set_math(95);
obj1.show_data();
return 0;
}
private繼承範例
所有繼承下來的變數外部都無法呼叫
多重繼承
一次繼承很多個乾爹
class 衍生類別 : 繼承模式 基礎類別1, 繼承模式 基礎類別2, 繼承模式 基礎類別3 {
// 類別的定義
};
繼承的實例
c++ stream類型的繼承
所以
cin 是一個 istream 型態的物件
cout 是一個 ostream 型態的物件
更精確來說,cin 的型態是 istream& (他是一個參考變數)
cout 的型態是 ostream& (他是一個參考變數)
知道這個可以幹嘛
我要來補幾百年前挖的坑了
class myClass{
public:
int a;
int b;
int c;
friend std::ostream& operator << (std::ostream&, myClass);
};
// 運算子多載(定義<<運算子左邊為ostream&型態,右邊為myClass型態時要做的事)
std::ostream& operator << (std::ostream& out, const myClass& x) {
out << x.a << " ";
out << x.b << " ";
out << x.c << "\n";
return out; // 將ostream&回傳回去 --> 所以cout可以串連!
}
繼續回到繼承
衍生類別的建構子與解構子
建構時會先呼叫基礎類別的建構子
解構時會先呼叫衍生類別的解構子
#include<iostream>
using namespace std;
// 宣告類別stclass
class stclass
{
public:
stclass() // 建構子
{
cout << "呼叫基礎類別的建構子" << endl;
}
~stclass() // 解構子
{
cout << "呼叫基礎類別的解構子" << endl;
}
};
// 宣告類別student,並以public型別自類別stclass繼承
class student :public stclass
{
public:
student() // 建構子
{
cout << "呼叫衍生類別的建構子" << endl;
}
~student() // 解構子
{
cout << "呼叫衍生類別的解構子" << endl;
}
};
void call()
{
student st1;
}
// 主函式
int main()
{
call();//呼叫宣告物件st1的函數
return 0;
}
衍生類別的建構子與解構子
#include<iostream>
using namespace std;
// 宣告類別stclass
class stclass
{
public:
stclass() // 建構子
{
cout << "呼叫基礎類別的建構子" << endl;
}
~stclass() // 解構子
{
cout << "呼叫基礎類別的解構子" << endl;
}
};
// 宣告類別student,並以public型別自類別stclass繼承
class student :public stclass
{
public:
student() // 建構子
{
cout << "呼叫衍生類別的建構子" << endl;
}
~student() // 解構子
{
cout << "呼叫衍生類別的解構子" << endl;
}
};
void call()
{
student st1;
}
// 主函式
int main()
{
call();//呼叫宣告物件st1的函數
return 0;
}
呼叫基礎類別的建構子
呼叫衍生類別的建構子
呼叫衍生類別的解構子
呼叫基礎類別的解構子
結果:
多形與虛擬函數
先講一個厲害的東西
然後你有一個A型態的指針變數
A* pt;
那pt可以指向所有A的衍生類別
並且用pt->呼叫所有
衍生類別繼承A的成員與方法
class A{
public:
int x;
void fun() {
cout << "A's function\n";
}
};
class B : public A{
void hahaha() {
cout << "B's function\n";
}
};
int main() {
B bbb; // class B 的變數
A* pt = &B; // 合法
cout << pt->x << endl;
pt->fun();
}
所以啥事多形
我們常常會做一件神奇的事
我們會讓基礎類別和衍生類別有一樣名字的函數
這就叫做多形函數
但是他的實作和功能卻不同
因此你用基礎類別指標呼叫衍生類別時,會根據衍生類別的種類有不同種的結果
要怎麼做到多形函數
#include <iostream>
using namespace std;
class A{
public:
void fun() {
cout << "This is fun in class A\n";
}
};
class B: public A{
public:
void fun() {
cout << "This is fun in class B\n";
}
};
int main() {
A* pt = new B;
pt->fun(); // 這樣會輸出啥?
}
This is fun in class A
為什麼?
因為fun()已經被繫結到基礎類別的實作了!
所以我要怎麼做呢?
要把基礎類別裡面的函數宣告為虛擬函數!
虛擬函數的宣告方式:使用virtual關鍵字
運輸工具啟動
運輸工具停止
飛行器啟動
飛行器停止
汽車啟動
汽車停止
#include <iostream>
using namespace std;
class vehicle {
// 宣告基礎類別vehicle
public:
virtual void start() // 成員函數start
{
cout << "運輸工具啟動"<< endl;
}
virtual void stop() // 成員函數stop
{
cout << "運輸工具停止"<< endl ;
}
};
class aircraft: public vehicle {
// 宣告衍生類別aircraft
public:
virtual void start() // 具有和基礎類別相同名稱的成員函數start
{
cout << "飛行器啟動"<< endl;
}
virtual void stop() // 具有和基礎類別相同名稱的成員函數stop
{
cout << "飛行器停止"<< endl;
}
};
class car: public vehicle {
// 宣告衍生類別car
public:
virtual void start() // 具有和基礎類別相同名稱的成員函數start
{
cout << "汽車啟動"<< endl;
}
virtual void stop() // 具有和基礎類別相同名稱的成員函數stop
{
cout << "汽車停止"<< endl;
}
};
int main()
{
vehicle* ve = new vehicle(); // 基礎類別的指標
aircraft af;
car cr;
ve->start(); // 呼叫其成員函數start()
ve->stop(); // 呼叫其成員函數stop()
delete ve;
ve = ⁡ // 將基礎類別指標指向衍生類別aircraft
ve->start(); // 呼叫其成員函數start()
ve->stop(); // 呼叫其成員函數stop()
ve = &cr; // 將基礎類別指標指向衍生類別car
ve->start(); // 呼叫其成員函數start()
ve->stop(); // 呼叫其成員函數stop()
return 0;
}
純虛擬函數與抽象基礎類別
則此函數稱為純虛擬函數
virtual T funName(T1, T2) = 0;
如果有一個函數在基礎類別裡面不定義實作
在使用時,一定要在衍生類別進行定義
否則會出錯
純虛擬函數與抽象基礎類別
則此類別稱為抽象基礎類別
如果有一個基礎類別包含一個純虛擬函數
對於這一些類別一定要使用繼承體系來重新定義(override)虛擬函數
#include <iostream>
using namespace std;
class vehicle {
// 宣告基礎類別vehicle
public:
virtual void start()=0; // 純虛擬函數start
virtual void stop()=0; // 純虛擬函數stop
};
class aircraft: public vehicle {
// 宣告衍生類別aircraft
public:
virtual void start() //宣告同名異式的成員函數start (其virtual關鍵字可省略)
{
cout << "飛行器啟動"<< endl;
}
virtual void stop() //宣告同名異式的成員函數stop (其virtual關鍵字可省略)
{
cout << "飛行器停止"<< endl;
}
};
class car: public vehicle {
// 宣告衍生類別car
public:
virtual void start() //宣告同名異式的成員函數start (其virtual關鍵字可省略)
{
cout << "汽車啟動"<< endl;
}
virtual void stop() //宣告同名異式的成員函數stop (其virtual關鍵字可省略)
{
cout << "汽車停止"<< endl;
}
};
int main()
{
vehicle* ve ;//宣告基礎類別vehicle指標,抽象基礎類別不可實體化
aircraft af;
car cr;
ve = ⁡ // 將基礎類別指標指向衍生類別aircraft
ve->start(); // 呼叫其成員函數start()
ve->stop(); // 呼叫其成員函數stop()
ve = &cr; // 將基礎類別指標指向衍生類別car
ve->start(); // 呼叫其成員函數start()
ve->stop(); // 呼叫其成員函數stop()
return 0;
}
噁心到極致
虛擬基礎類別
vehicle
car
aircraft
aircar
當aircar要呼叫vehicle的東西時
會發生錯誤
稱為模稜兩可(ambiguous)
這時候我們要進行虛擬繼承
class Derive: virtual public Base { };
#include <iostream>
using namespace std;
class vehicle {
// 宣告基礎類別vehicle
public:
void start()
{
cout << "運輸工具啟動"<< endl;
}
void shutdown()
{
cout << "運輸工具熄火"<< endl;
}
};
class aircraft: virtual public vehicle
{
// 宣告虛擬基礎類別aircraft
public:
void fly()
{
cout << "飛行器飛行"<< endl;
}
void land()
{
cout << "飛行器著陸"<< endl;
}
};
class car: virtual public vehicle
{
// 宣告虛擬基礎類別car
public:
void go()
{
cout << "汽車啟動"<< endl;
}
void stop()
{
cout << "汽車熄火"<< endl;
}
};
class aircar: public aircraft, public car {}; // 宣告衍生類別aircar
int main()
{
aircar ac;
ac.start(); // 衍生函數呼叫上兩層基礎類別vehicle的成員函數start
ac.go();
ac.fly();
ac.land();
ac.stop();
ac.shutdown(); // 衍生函數呼叫上兩層基礎類別vehicle的成員函數stop
return 0;
}
嗯,上完了
嗯,上完了
但是還有
嗯,上完了
但是還有
成發啦哈哈
成發的規則:
任何一個c++的程式碼
用到我們上課講過的東西都可以
然後要準備3分鐘左右的介紹