至今已經直接使用過類別模版很多次了,那麼如何自訂類別模版呢?基本上,類別模版就只是函式模版的概念延伸,如同函式模版實例化後的是各個不同版本的函式,類別模版實例化後的是各個不同的類別,更具體來說,是各種不同的型態。
例如 vector<int>
是個實例化後的型態,vector<char>
是實例化後另一個型態,vector<int>
與 vector<char>
是兩種不同的型態。
因為類別模版實例化後,會是不同的類別、不同的型態,因此定義類別模版時,在傳回型態涉及類別模版本身時,必須包含模版參數,在 ::
範圍解析時也必須包含模版參數。
來看個實例吧!在〈巢狀、區域、匿名類別〉中的 IntLinkedList
,只能用於 int
的元素,可以將之定義為類別模版,適用於各個指定的型態,例如:
#include <iostream>
using namespace std;
template <typename T>
class LinkedList {
class Node {
public:
Node(T value, Node *next) : value(value), next(next) {}
T value;
Node *next;
};
Node *first = nullptr;
public:
LinkedList<T>& append(T value);
T get(int i);
};
template <typename T>
LinkedList<T>& LinkedList<T>::append(T value) {
Node *node = new Node(value, nullptr);
if(first == nullptr) {
this->first = node;
}
else {
Node *last = this->first;
while(last->next != nullptr) {
last = last->next;
}
last->next = node;
}
return *this;
}
template <typename T>
T LinkedList<T>::get(int i) {
Node *last = this->first;
int count = 0;
while(true) {
if(count == i) {
return last->value;
}
last = last->next;
count++;
}
}
int main() {
LinkedList<int> intLt;
intLt.append(1).append(2).append(3);
cout << intLt.get(1) << endl;
LinkedList<char> charLt;
charLt.append('a').append('b').append('c');
cout << charLt.get(2) << endl;
return 0;
}
可以留意到範例中,是如何傳回類別本身型態,以及範圍解析 ::
是怎麼指定的,對於實作於類別之中的成員函式,不用範圍解析 ::
,也不用重複宣告 template
模版參數名稱。例如:
#include <iostream>
using namespace std;
template <typename T>
class LinkedList {
class Node {
public:
Node(T value, Node *next) : value(value), next(next) {}
T value;
Node *next;
};
Node *first = nullptr;
public:
LinkedList<T>& append(T value) {
Node *node = new Node(value, nullptr);
if(first == nullptr) {
this->first = node;
}
else {
Node *last = this->first;
while(last->next != nullptr) {
last = last->next;
}
last->next = node;
}
return *this;
}
T get(int i) {
Node *last = this->first;
int count = 0;
while(true) {
if(count == i) {
return last->value;
}
last = last->next;
count++;
}
}
};
int main() {
LinkedList<int> intLt;
intLt.append(1).append(2).append(3);
cout << intLt.get(1) << endl;
LinkedList<char> charLt;
charLt.append('a').append('b').append('c');
cout << charLt.get(2) << endl;
return 0;
}
如果 static
資料成員是在類別外指定,記得範圍解析時也得加上型態參數,而使用 static
成員時,必須實例化,即使實例化時指定的型態與 static
無關,也是得實例化。例如:
#include <iostream>
using namespace std;
template <typename T>
class Foo {
static int wat;
public:
static int wat10();
};
template <typename T>
int Foo<T>::wat = 10;
template<typename T>
int Foo<T>::wat10() {
return wat * 10;
}
int main() {
cout << Foo<double>::wat10() << endl;
return 0;
}
模版類別中若要宣告 friend
比較麻煩,因為 friend
與類別之間有耦合關係,因此必須事先做好宣告,宣告 friend
時才看得到彼此,例如:
#include <iostream>
using namespace std;
template <typename T>
class Foo;
template <typename T>
void foo(Foo<T> t);
template <typename T>
class Foo {
T t;
public:
Foo(T t) :t(t) {}
friend void foo<T>(Foo<T> t);
};
template <typename T>
void foo(Foo<T> f) {
cout << f.t << endl;
}
int main() {
Foo<int> f(10);
foo(f);
return 0;
}
實例後的類別型態與朋友之間的型態是對應的,例如 Foo<int>
與 void foo(Foo<int>)
才會是朋友,與 void foo(Foo<char>)
不會是朋友。