C++(15):模板(Template)
阿新 • • 發佈:2019-02-13
引言
還是那句話。為了更多的相容性。為了寫更少的程式碼。
有了模(mu,二聲)板。模板不是類。
模板分函式模板,類模板。以下分別總結。
函式模板
基本寫法
template <typename T>
void swap1(T & t1, T & t2){
T temp;
temp = t1;
t1 = t2;
t2 = temp;
}
x1 = 1, x2 = 3int x1 = 1; int x2 = 3; cout<<"x1 = "<<x1<<", x2 = "<<x2<<endl; swap1(x1,x2); cout<<"x1 = "<<x1<<", x2 = "<<x2<<endl;
x1 = 3, x2 = 1
因為是引用傳遞,所以確實交換了值。 注意。 1. 要寫關鍵字template以示這是模板,之後寫<模板引數>; 2. 儘量不要寫class T,class U,不要寫“class”,這樣具有誤導性。儘管java就是這麼寫。儘量寫“typename”; 3. 之後寫函式體。 4. 編譯器在編譯的時候,會推斷出這些T、U究竟是什麼型別,進而例項化一個特定版本的函式; 5. 上面可以寫swap1(x1,x2),但是不能寫swap(1,3)。編譯器報錯,說不是int型變數… 6. 不能寫swap(i,d),其中i是int型,d是double型。因為這個函式模板宣告的時候,兩個形參都是T,實參在傳引數的時候,型別要一致! 第5條的解決方案。函式形參加const,實參就可以是常量了
此時,呼叫這個函式時,實參可以是同一型別的常數,也可以是同一型別的變數。template <typename T> bool compare(const T & v1,const T & v2) { return (v1<=v2); }
cout<<compare(2.3,3.4)<<endl;
cout<<compare(x1,x2)<<endl;
1
0第6條的解決方案。模板引數列表中,引數寫兩個不同的。同時函式形參,也寫兩個不同的,就支援不同型別的變量了。
template <typename T1,typename T2>
bool compare(const T1 & v1,const T2 & v2) {
return (v1<=v2);
}
越來越相容了……。 甚至還可以寫成:compare(const T1 & t1, int i)這種樣子…cout<<compare(x1,0.3)<<endl;
函式模板的過載
Can overload function templates only when each version takes a different argument list– allow compiler to distinguish
template <typename T>
T FindMax(T x, T y) {
T max = x;
if (y > max)
max = y;
return max;
}
template <typename T>
T FindMax(
T x, T y, T z) {
T max = x;
if (y > max)
max = y;
else if (z > max)
max = z;
return max;
}
【經典案例】 用模板實現從3個學生中,找出最高分的同學。 連返回的資料型別都可以寫成模板!太靈活了。
#include <iostream>
using namespace std;
// 三個數找大小都不會了 -_-!
// 先假設第一個數是最大的,然後跟第二個比,再跟第3個比!
// 【嚴重警告】 最基本的查詢、排序還是要會的啊!
// 【嚴重警告】 最基本的三種資料結構必須要會啊!
template <typename T>
T findMax(T & a, T & b, T & c) {
T maxT = a;
if (maxT<b)
{
maxT = b;
}
if (maxT<c)
{
maxT = c;
}
return maxT;
}
// overloading function findMax
template <typename T>
T findMax(T & a, T & b) {
return (a<b)?b:a;
}
class CStu
{
/* friend function */
friend ostream & operator<<(ostream & os, CStu & cStu);
// friend bool operator<(CStu & o1, CStu & o2); // overloading operator <
public:
CStu(double score_para):score(score_para){};
~CStu(){};
// overloading operator < as a member function
bool operator<(CStu & o2);
void setScore(double score) {
this->score = score;
}
double getScore() const {
return this->score;
}
private:
double score;
};
/* friend function 需要在類外面定義 */
ostream & operator<<(ostream & os, CStu & cStu){
os<<cStu.getScore();
return os;
}
// bool operator<(CStu & o1, CStu & o2) {
// return (o1.getScore()<o2.getScore()) ? true : false;
// }
bool CStu::operator<(CStu & o2) {
return (this->getScore()<o2.getScore()) ? true : false;
}
int main () {
CStu cStu1(88.6);
CStu cStu2(77.4);
CStu cStu3(66.3);
CStu cStuMax = findMax(cStu1,cStu2,cStu3);
cout<<"最高分:"<<cStuMax<<endl;
CStu cStuMax2 = findMax(cStu2,cStu3);
cout<<"最高分:"<<cStuMax2<<endl;
system("pause");
return 0;
}
執行結果。
最高分:88.6最高分:77.4
幾點注意。 1. 3個數,找出最大的。先假設第一個數是最大的,然後跟第二個比,再跟第3個比! 2. 現在形參確實是T & ,是一個類了!所以要對<運算子進行過載!注意啊,"<"也是一個函式!返回值是bool型別的! 3. 過載可以通過成員函式過載,也可以用友元函式進行過載。 Review:運算子過載
顯式地指定型別
When calling a template function, the arguments dictate the types to be usedTo override a deduced type:
someFunction<char>(someArgument);
- useful when at least one of the types you need to generate in the function is not an argument
【經典案例】
#include <iostream>
#include <string>
using namespace std;
// 在呼叫時顯示地指定模板的型別
template <class T>
T doubleVal(T val) {
val *= 2;
return val;
}
template <class T, class U>
T tripleVal(U val) {
T temp = val * 3;
return temp;
}
template <class T, class U>
U tripleVal2(T val) {
T temp = val * 3;
return temp;
}
template <class U, class T>
T tripleVal3(U val) {
T temp = val * 3;
return temp;
}
int main () {
int a = 4;
double b = 8.8;
cout <<a<<" & "<<doubleVal(a)<<"\n";
cout <<b<<" & "<<doubleVal(b)<<"\n";
cout <<b<<" & "<<doubleVal<int>(b)<<"\n"; // 這個<int>的意思是,把T直接指定int型
// 因此,輸入的時候,double就直接被轉成了int型
cout << tripleVal<int>(a) << "\n"; // 把模板裡第一種型別,T,指定為int --> 輸出12
cout << tripleVal<int>(b) << "\n"; // 把模板裡的第一種型別,T,指定為int --> 8.8*3 = 26.4 --> 轉int 輸出26
cout << tripleVal<int,double>(b)<< "\n"; // --> 把第一種型別,T,指定為int,把第二種型別,U,指定為double --> 8.8*4=26.4 --> 轉int // 輸出26
cout << tripleVal<int, int>(b) <<"\n"; // 把 T 和 U 都指定為 int型, --> 8.8變成8, 24
cout << tripleVal2<int,double>(b)<< "\n"; // 第一種型別,還是T!
cout << tripleVal2<int, int>(b) <<"\n"; //
cout << tripleVal3<int,double>(b)<< "\n";
cout << tripleVal3<int, int>(b) <<"\n";
system("pause");
return 0;
}
執行結果。
4 & 88.8 & 17.6
8.8 & 16
12
26
26
24
24
24
24
24 注意,模板的顯式指定,是從template <typename T1, typename T2, ...> 這裡開始,從左往右,顯式指定!
類模板
寫法
A class template defines a family of classes– serve as a class outline to generate many classes --> 在編譯時會根據你的程式碼,推測生成哪些類。
– specific classes are generated during compile time --> 注意,是在編譯時! Class templates promote code reusability --> 類模板提高了程式碼的可重複利用性
– reduce program development time
– used for a need to create several similar classes at least one type is generic (parameterized) 當有一個或幾個引數都是比較泛化的情況時,可以考慮用類模板。
Terms “class template” and “template class” are used interchangeably.
/************************************************************************/
/*
類模板。
qcy
2016年11月26日14:31:11
*
/************************************************************************/
#include <iostream>
using namespace std;
// 類中含有暫未指定型別的成員,就是類模板。
template <class T>
class CPerson
{
public:
CPerson() {}
CPerson(T num):number(num) {}
~CPerson() {}
void setNumber(T num) {
this->number = num;
}
T getNumber() {
return this->number;
}
private:
T number;
};
int main () {
// 在例項化的時候,就要指明是用什麼型別的了。
double d = 1.3;
// CPerson cPerson2(d); // 錯誤。缺少模板的引數列表
CPerson<double> cPerson1;
cPerson1.setNumber(19.5);
cout<<"cPerson的number:"<<cPerson1.getNumber()<<endl;
// 連宣告一個空指標,都要指定用什麼型別。
CPerson<double> * cPersonPt1 = nullptr;
CPerson<char> cPerson3(97);
cout<<"cPerson的number:"<<cPerson3.getNumber()<<endl;
CPerson<int> cPerson4('A');
cout<<"cPerson的number:"<<cPerson4.getNumber()<<endl;
CPerson<char> cPerson5('b');
cout<<"cPerson的number:"<<cPerson5.getNumber()<<endl;
system("pause");
return 0;
}
結果。
cPerson的number:19.5cPerson的number:a
cPerson的number:65
cPerson的number:b 注意。 1. 在例項化的時候,就要指明是用什麼型別的了。
2. 連宣告一個空指標,都要指定用什麼型別。
模板引數
3 forms of template parameters– type parameters
– non-type parameters
– template parameters
1. A type parameter defines a type identifier – when instantiating a template class, a specific datatype listed in the argument list substitute for the type identifier
– either class or typname must precede a template type parameter
2. A non-type parameter can be – integral types: int, char, and bool
– enumeration type
– reference to object or function
– pointer to object, function or member
A non-type parameter cannot be
– floating types: float and double
– user-defined class type
– type void
Good e.g. template <int A, char B, bool C> // A也就是一種型別了。在這裡,就是int型了。
class G1 { //... };
template <float* D, double& E> // D從此以後,就是float * 型別
class G2 { //... };
Bad e.g. template <double F>
class B1 { //... }; //cannot be double
template <PhoneCall P>
class B2 { //... }; //cannot be class
3. A template parameter may have a default argument.
e.g. template <class T=int, int n=10>
class C3 { //... };
C3< > a; // 預設把T當int型,但是要加“<>”
C3 b; //error: missing < >
C3<double,50> c;
C3<char> d;
C3<20> e; //error: missing template argument!要指定T的型別,這裡的20只是說int n的n要是20。編譯器沒有這麼聰明……
自己用模板寫一個Stack
【非常重要的一個例子】/************************************************************************/
/*
我自己寫的一個stack
qcy
2016年12月25日10:21:43
*/
/************************************************************************/
#include <iostream>
using namespace std;
template<typename T,int MAX_SIZE>
class MyStack
{
public:
MyStack();
~MyStack();
bool full() {
return (top == MAX_SIZE);
}
bool empty() {
return (top == 0);
}
bool push(T obj) {
bool result;
if (full()) {
cout<<"stack full"<<endl;
result = false;
}
else {
elements[top] = obj;
top++ ;
result = true;
}
return result;
}
T pop() {
if (empty()) {
cout<<"stack empty"<<endl;
exit(-1); // 如果不想退出呢?
}
else {
top--;
T temp = elements[top];
return temp;
}
}
private:
int top;
T elements[MAX_SIZE];
};
template<typename T,int MAX_SIZE>
// 【注意】 在例項化的時候,這裡不是寫int,是寫MAX_SIZE
MyStack<T,MAX_SIZE>::MyStack()
{
top = 0;
}
template<typename T,int MAX_SIZE>
MyStack<T,MAX_SIZE>::~MyStack()
{
}
int main () {
MyStack<double,5> stack;
int i = 0;
while (!stack.full())
{
stack.push(i);
i++;
}
while (!stack.empty())
{
cout<<stack.pop()<<" ";
}
cout<<"\n";
MyStack<char,10> stack2;
char c = 'a';
while (!stack2.full())
{
stack2.push(c);
c++;
}
while (!stack2.empty())
{
cout<<stack2.pop()<<" ";
}
cout<<"\n";
system("pause");
return 0;
}
執行結果。
4 3 2 1 0j i h g f e d c b a
(first in, last out.) 注意。 1. 建構函式如果在類的外面寫,一定要指定模板引數列表! // 【注意】 在例項化的時候,這裡不是寫int,是寫MAX_SIZE template<typename T,int MAX_SIZE>
MyStack<T,MAX_SIZE>::MyStack() // 要統一。T是一種型別,MAX_SIZE也是一種型別了…
{
// ...
}
2. template<typename T,int MAX_SIZE> // 這裡的MAX_SIZE也是int型了。後面例項化的時候,竟然可以傳一個常數進來!
class MyStack
{ // ... } 3. 不能把建構函式寫到另一個CPP檔案單獨裡面去…!好像暫時我發現是這樣……
模板中的友元和繼承
Friend functions can be used with template classes– same as with ordinary classes
– simply requires proper type parameters
– common to have friends of template classes, especially for operator overloading Nothing new for inheritance Derived template classes
– can derive from template or non-template class
– derived class is naturally a template class
示例。 1. 定義一個基類模板-->注意,這是模板!一個basic的class template。基礎的類模板。
template <class T>
class TBase {
private:
T x, y;
public:
TBase() {}
TBase(T a, T b) : x(a), y(b) {}
~TBase() {}
T getX();
T getY();
};
template <class T>
T TBase<T>::getX() const { return x; }
template <class T>
T TBase<T>::getY() const { return y; }
2. 從基礎類模板,派生一個(非模板的)類(注意,此時派生的東西,不是模板了!已經成為一個類了!)
Derive non-class template from class template --> easy to understand
– behave like normal classes
把所有的T啊、U啊這些模板引數,在程式碼裡都指定型別了。現在,TDerived1就不是一個類模板了!而是一個類!
class TDerived1: public TBase<int> {
private:
int z;
public:
TDerived1(int a, int b, int c):
TBase<int>(a,b), z(c) {}
int getZ() { return z; }
};
3. 從基礎類模板,派生一個類模板(注意,此時派生的東西還是模板!)
Derive class template from class template– same as the normal class inheritance 只要還有模板引數沒有指定型別,就還是類模板,而不是類。 這就像,只要還沒有把抽象基類基類所有的純虛擬函式都override實現一遍的話,就還是抽象基類。
template <class T>
class TDerived2 : public TBase<T> {
private:
T z;
public:
TDerived2(T a, T b, T c):
TBase<T>(a,b), z(c) {}
T getZ() { return z; }
};
4. 從基礎類,派生一個類模板(由類生出模板。使用要小心!)template <class T>
class TDerived3 : public TDerived1 {
private:
T w;
public:
TDerived3(int a, int b, int c, T d): TDerived1(a,b,c), w(d) {}
T getW() { return w; }
};
call TDerived1 constructor with known datatypes for parameters!5. 主函式這樣呼叫。
TBase<int> c1(0,1);
cout << "TBase: x=" << c1.getX() << " y=" <<
c1.getY() << endl;
TDerived1 c2(1,3,5);
cout << "TDerived1: x=" << c2.getX() << " y=" <<
c2.getY() << " z=" << c2.getZ() << endl;
TDerived2<double> c3(2.2, 4.4, 6.6);
cout << "TDerived2: x=" << c3.getX() << " y=" <<
c3.getY() << " z=" << c3.getZ() << endl;
TDerived3<int> c4(3.5, 6.5, 9.5, 12.5);
cout << "TDerived3: x=" << c4.getX() << " y=" <<
c4.getY() << " z=" << c4.getZ() << " w=" <<
c4.getW() << endl;
結果。
TBase: x=0 y=1 // 類模板的例項化(指定用int型)TDerived1: x=1 y=3 z=5 // 例項化一個類!已經是類了。
TDerived2: x=2.2 y=4.4 z=6.6 // 例項化一個類模板!(還是模板!)指定用double型!
TDerived3: x=3 y=6 z=9 w=12 // 例項化一個由類派生的類模板!注意,是例項化的模板!所以要指定型別!
// 此時,指定的是int型,所以, // template <typename T1, ... > 的第一個模板引數 T1,在編譯時就已經變成了int! // 因此,後面的12.5也會被轉為12