您的位置:首页 > 编程语言 > C语言/C++

C++(15):模板(Template)

2016-12-25 12:22 441 查看

引言

还是那句话。为了更多的兼容性。为了写更少的代码。

有了模(mu,二声)板。模板不是类。

模板分函数模板,类模板。以下分别总结。

函数模板

基本写法

template <typename T>
void swap1(T & t1, T & t2){
T temp;
temp = t1;
t1 = t2;
t2 = temp;
}
int x1 = 1;
int x2 = 3;
cout<<"x1 = "<<x1<<", x2 = "<<x2<<endl;
swap1(x1,x2);
cout<<"x1 = "<<x1<<", x2 = "<<x2<<endl;

x1 = 1, x2 = 3

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);
}
cout<<compare(x1,0.3)<<endl;


越来越兼容了……。
甚至还可以写成:compare(const T1 & t1, int i)这种样子…

函数模板的重载

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 used

To 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 & 8

8.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.5

cPerson的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 0

j 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

补充:枚举类型

枚举类型……
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  C++ 模板