<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 才知道未来遇到的时候要怎麽办
之後的内容就会是我自己开发的小游戏!
敬启期待!
<<: Day 23 介绍 FactoryBot Rails 及设定
Agenda 资安宣言 测试环境与工具 学习目标 前情提要 技术原理与程序码 References ...
Day33 Golang gRPC介绍与应用 RPC (Remote Procedure Call)...
今天要讲的是期货合约的相关函数。 首先是Contracts函数,就像之前文章里有使用到的一样,透过C...
我现在使用django架构来做LINE Bot,後端如果有新增优惠消息到资料库里面,就会传送优惠消息...
最好的投资,就是投资自己 Investing in yourself is the best ...