<vector>
我們上次寫的 Character 的 class
class Character
{
protected:
static const int EXP_LV = 100;
string name;
int level;
int exp;
int power;
int knowledge;
int luck;
//...
}
在以前,我們在寫完 class 之後,只能改變值的大小,但是卻不能改變 data type。例如,name 就只能用 string
,但不能途中把它改成 int
。
現在我們可以用 Template 實現這個願望了,他有幾個特性:
而只有在這兩個時候允許:
- Warrior<string> w1("Alice", 10);
- Wizard<int> w2(16, 5);
雖然我們可能會像上面傳入兩種不同的 argument,但是我們不需要寫兩種 implementations。
要宣告一個 type parameter,我們需要使用 template
還有 type name
template<typename T>
class TheClassName
{
// T can be treated as a type inside the class definition block
}
如果要 implement 到 member function 上
template<typename T>
T TheClassName<T>::f(Tt)
{
// t 是一個型態為 T 的變數
}
templat<typename T>
void TheClassName<T>::f(int i)
{
// 當不用 T 的時候用這邊的函式
}
這時候就可以這樣用
int main()
{
TheClassName<int> a;
TheClassName<double> b;
TheClassName<AnotherClassName> c;
}
例子:
#include<iostream>
using namespace std;
template<typename T>
void f(T t)
{
cout << t;
}
int main()
{
f<double>(1.2); // 1.2
f<int>(1.2); // 1
return 0;
}
例子:
複數個 parameter
#include<iostream>
using namespace std;
template<typename A, typename B>
void g(A a, B b)
{
cout << a + b << endl;
}
int main()
{
g<double, int>(1.2, 1.7); // 1.2 + 1 = 2.2
return 0;
}
例子:
用在 class 中
template<typename T>
class C
{
public:
T f(T i);
};
template<typename T>
T C<T>::f(T i)
{
return i * 2;
}
int main()
{
C<int> c;
cout << c.f(10) << endl;
return 0;
}
所以回到 Character,我們要怎麼寫?
template <typename KeyType>
class Character
{
protected:
static const int EXP_LV = 100;
KeyType name;
int level;
int exp;
int power;
int knowledge;
int luck;
void levelUp(int pInc, int kInc, int lInc);
public:
Character(KeyType n, int lv, int po, int kn, int lu);
virtual void beatMonster (int exp) = 0;
virtual void print();
KeyType getName
};
在這邊,因為我們只想要改變 name 的 type,所以就只有改 name 的 data type。
template<typename KeyType>
Character<KeyType>::Character(KeyType n, int lv, int po, int kn, int lu)
:name(n), level(lv), exp(pow(lv - 1, 2) * EXP_LV),
power(po), knowledge(kn), luck(lu)
{
}
// 因為 beatmonster 是 pure virtual function -> 所以不用寫
template<typename KeyType>
void Character<KeyType>::print()
{
cout << this->name
<< ": Level " << this - level
<< " (" << this->exp << "/" << pow(this->level, 2) * EXP_LV << "), "
<< this->power << "-" << this->knowledge << "-" << this->luck << "\n";
}
template<typename KeyType>
void Character<KeyType>::levelUp(int pInc, int kInc, int lInc)
{
this->level++;
this->power += pInc;
this->knowledge += kInc;
this->luck += lInc;
}
template<typename KeyType>
KeyType Character<KeyType>::getName()
{
return this->name;
}
這時候就可以對 Warrior 還有 Wizard 做更改
Warrior:
template <typename KeyType>
class Warrior : public Character<KeyType>
{
private:
static const int PO_LV = 10;
static const int KN_LV = 5;
static const int LU_LV = 5;
public:
Warrior(KeyType n, int lv = 0);
void print();
void beatMonster(int exp);
};
template <typename KeyType>
Warrior<KeyType>::Warrior(KeyType n, int lv) : Character<KeyType>(n, lv, lv * PO_LV, lv * KN_LV, lv * LU_LV)
{
}
template <typename KeyType>
void Warrior<KeyType>::print()
{
cout << "Warrior ";
Character<KeyType>::print();
}
template <typename KeyType>
void Warrior<KeyType>::beatMonster(int exp)
{
this->exp += exp;
while(this->exp >= pow(this->level, 2) * Character<KeyType>::EXP_LV) // Why?
this->levelUp(PO_LV, KN_LV, LU_LV);
}
Wizard:
template <typename KeyType>
class Wizard : public Character<KeyType>
{
private:
static const int PO_LV = 4;
static const int KN_LV = 9;
static const int LU_LV = 7;
public:
Wizard(KeyType n, int lv = 0);
void print();
void beatMonster(int exp);
};
template <typename KeyType>
Wizard<KeyType>::Wizard(KeyType n, int lv) : Character<KeyType>(n, lv, lv * PO_LV, lv * KN_LV, lv * LU_LV)
{
}
template <typename KeyType>
void Wizard<KeyType>::print()
{
cout << "Wizard ";
Character<KeyType>::print();
}
template <typename KeyType>
void Wizard<KeyType>::beatMonster(int exp)
{
this->exp += exp;
while(this->exp >= pow(this->level, 2) * Character<KeyType>::EXP_LV) // Why?
this->levelUp(PO_LV, KN_LV, LU_LV);
}
值得注意的一個點是 EXP_LV
我們需要使用 <KeyType>
的原因是因為,他是一個 static variable,我們需要知道他是怎樣的 parent 才能呼叫他(電腦才知道要呼叫誰)。
Team:
template <typename KeyType>
class Team
{
private:
int memberCount;
Character<KeyType>* member[10];
public:
Team();
~Team();
void addWarrior(KeyType name, int lv);
void addWizard(KeyType name, int lv);
void memberBeatMonster(KeyType name, int exp);
void printMember(KeyType name);
};
template <typename KeyType>
Team<KeyType>::Team()
{
this->memberCount = 0;
for(int i = 0; i < 10; i++)
member[i] = nullptr;
}
template <typename KeyType>
Team<KeyType>::~Team()
{
for(int i = 0; i < this->memberCount; i++)
delete this->member[i];
}
template <typename KeyType>
void Team<KeyType>::addWarrior(KeyType name, int lv)
{
if(memberCount < 10)
{
member[memberCount] = new Warrior<KeyType>(name, lv);
memberCount++;
}
}
template <typename KeyType>
void Team<KeyType>::addWizard(KeyType name, int lv)
{
if(memberCount < 10)
{
member[memberCount] = new Wizard<KeyType>(name, lv);
memberCount++;
}
}
template <typename KeyType>
void Team<KeyType>::memberBeatMonster(KeyType name, int exp)
{
for(int i = 0; i < this->memberCount; i++)
{
if(this->member[i]->getName() == name)
{
this->member[i]->beatMonster(exp);
break;
}
}
}
template <typename KeyType>
void Team<KeyType>::printMember(KeyType name)
{
for(int i = 0; i < this->memberCount; i++)
{
if(this->member[i]->getName() == name)
{
this->member[i]->print();
break;
}
}
}
使用:
int main()
{
Team<string> t; // 用名字稱呼的 team
t.addWarrior("Alice", 1);
t.memberBeatMonster("Alice", 10000);
t.addWizard("Bob", 2);
t.printMember("Alice");
Team<int> t2; // 用編號稱呼的 team
t2.addWarrior(1, 1);
t2.memberBeatMonster(1, 10000);
t2.addWizard(2, 2);
t2.printMember(1);
return 0;
}
如果當今天我們使用的 typename
是一個 class,這時候我們可能就要自己寫 operator overloading,才可以做比較。
在 C string ,我們使用的是 char array
而 C++ string 則是用 class 來做 string
我們可以簡單地說, C++ string 就是把 C string 放進一個 class 中,並加入一些好用的 function。
所以同樣的,我們可能也希望把 integer 或是 double 裝入一個 class 中,並加入一些好用的 function。這件事情,可以說是使用 template 的大好時機。
在 C++ standard library (STL, standard template library) 有一個 class 叫做 <vector>
。
他可以做甚麼事情?
vector<int> v1; // integer vector
vector<double> v2;
vector<Warrior> v3;
push_back(), pop_back(), insert(), erase(), swap, =, etc.
[ ], front(), back(), etc.
size(), max_size(), resize(), etc
#include<iostream>
#include<vector>
using namespace std;
void printVector(vector<int> v)
{
for (int i = 0; i < v.size(); i++)
cout << v[i] << " ";
cout << endl;
}
int main()
{
vector<int> v;
cout << v.size() << endl;
cout << v.max_size() << endl;
v.push_back(10);
v.push_back(9);
v.push_back(8);
printVector(v); // 10 9 8
v.pop_back();
v.push_back(5);
printVector(v); // 10 9 5
return 0;
}
#include <iostream>
#include <string>
#include <cmath>
using namespace std;
template <typename KeyType>
class Team
{
private:
vector<Character<KeyType>*> member; // 原本最多只能 10 人
public:
Team();
~Team();
void addWarrior(KeyType name, int lv);
void addWizard(KeyType name, int lv);
void memberBeatMonster(KeyType name, int exp);
void printMember(KeyType name);
};
template <typename KeyType>
Team<KeyType>::Team()
{
}
template <typename KeyType> // 非常重要,因為我們要刪除空間,否則會 memory leak!
Team<KeyType>::~Team()
{
while(this->member.size() > 0)
{
delete this->member.back();
this->member.pop_back();
}
}
template <typename KeyType>
void Team<KeyType>::addWarrior(KeyType name, int lv)
{
Warrior<KeyType>* wPtr = new Warrior<KeyType>(name, lv); // 必須使用動態記憶體配置!
this->member.push_back(wPtr);
}
template <typename KeyType>
void Team<KeyType>::addWizard(KeyType name, int lv)
{
Wizard<KeyType>* wPtr = new Wizard<KeyType>(name, lv);
this->member.push_back(wPtr);
}
template <typename KeyType>
void Team<KeyType>::memberBeatMonster(KeyType name, int exp)
{
for(int i = 0; i < this->member.size(); i++) // 不用記 member 有多少人了
{
if(this->member[i]->getName() == name)
{
this->member[i]->beatMonster(exp);
break;
}
}
}
template <typename KeyType>
void Team<KeyType>::printMember(KeyType name)
{
for(int i = 0; i < this->member.size(); i++)
{
if(this->member[i]->getName() == name)
{
this->member[i]->print();
break;
}
}
}
但是老師給了一個建議:
如果你寫不出 vector,就先不要用它吧!
主要是因為這是比較高階的用法,但是因為我們現在還算是新手階段,所以要盡量的使用基礎一點的用法解決問題,等到我們融會貫通後,vector 的想法也自然可以使用了。
在寫程式的時候,常會發生 run-time error 等問題,像是這樣
#include<iostream>
using namespace std;
void f(int a[], int n)
{
int i = 0;
cin >> i;
a[i] = 1; // run-time error
}
int main()
{
int a[5] = {0};
f(a, 5);
for (int i = 0; i < 5; i++)
cout << a[i] << " ";
return 0;
}
因為很有可能會產生 run-time error ,但是我們不知道何時會發生,這時候可以 check 看看就知道了
#include<iostream>
using namespace std;
bool f(int a[], int n)
{
int i = 0;
cin >> i;
if (i < 0 || i > n)
return false;
a[i] = 1;
return true;
}
int main()
{
int a[5] = {0};
f(a, 5);
for (int i = 0; i < 5; i++)
cout << a[i] << " ";
return 0;
}
但是這樣的 check 方式其實有很多缺點:
因此 C++ 提供一個方式叫做 exception handling,他是
use try & catch
try
{
// statement that may throw exceptions
}
catch(ExceptionClass identifier) // this kind?
{
responses
}
catch(AnotherExceptionClass identifier) // that kind?
{
// other responses
}
程式跑到這邊的時候會 try 你說的 exception,這時候如果他有讀到 exception 就會跳到 catch 那裏面,並做出反應,最後會直接 shut down 程式。
因此這時候要記得宣告 destructor ,否則前面的動態配置不會被 release 掉。
replace()
#include<iostream>
#include<string>
#include<stdexcept>
using namespace std;
void g (string& s, int i)
{
s.replace(i, 1, ".");
}
int main()
{
string s = "12345";
int i = 0;
cin >> i;
g(s, i);
cout << s << endl;
return 0;
}
這時候如果 i 打入 -8,這樣會使程式 error。
所以就必須使用 exception
我們可以在 function 就使用 try & catch
void g(string& s, int i)
{
try
{
s.replace(i, 1, ".");
}
catch (out_of_range e)
{
cout << "...\n";
}
}
也可以在 caller 使用
int main()
{
string s = "12345";
int i = 0;
cin >> i;
try
{
g(s, i);
}
catch (out_of_range e)
{
cout << "...\n";
}
cout << s << endl;
return 0;
}
在 exception 中的 classes 有:
exception
logic_error
domain_error
invalid_argument
length_error
out_of_range
runtime_error
range_error
overflow_error
underflow_error
層級階層大概是長這樣。
#include<iostream>
#include<stdexcept>
using namespace std;
void f(int a[], int n) throw (logic_error) // 寫了這個就代表只能傳出這個 exception
{
int i = 0;
cin >> i;
if (i < 0 || i > n)
throw logic_error("..."); // 丟一個 exception -> 就像是做了一個 object 後往外丟
a[i] = 1;
}
int main()
{
int a[5] = { 0 };
f(a, 5);
for (int i = 0; i < 5; i++)
cout << a[i] << " ";
return 0;
}
在函數後面寫 throw (...error)
,寫了這個就代表只能傳出這個 exception,也就是設計這個 function 的人跟其他人說,他可能傳出甚麼exception。那如果你確定他不會跑出 exception ,這時候就可以在 function 後面加上noexcept
。
我們也可以自己定義自己的 exception
#include<stdexcept>
using namespace std;
class MyException : public exception
{
public:
MyException(const string& msg = "") : exception(msg.c_str()) {}
};
template <typename KeyType>
void Team<KeyType>::addWarrior(KeyType name, int lv) throw (MyException)
{
if (memberCnt < 10)
{
member[memberCnt] = new Warrior<KeyType>(name, lv);
memberCnt++;
}
else
throw MyException("...");
}
今天的內容是小傑老師上的最後一堂課程!
有一種畢業的感覺(並沒有)
但是真的很進階,可能還是要多看一些 code 才知道未來遇到的時候要怎麼辦
之後的內容就會是我自己開發的小遊戲!
敬啟期待!