<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 才知道未來遇到的時候要怎麼辦

之後的內容就會是我自己開發的小遊戲!
敬啟期待!