C++基础之模板详解

C++中模板是泛型编程的基础,是将类型作为参数的一种编程方式。因此可以独立与特定的类型。模板顾名思义就是一个模子,举个不太恰当的栗子:做月饼的模子,样子都是一样的,但是可以做出五仁的、豆沙的、蛋黄的等等各种馅的月饼。

C++中模板的应用主要分为两个场景:函数模板和类模板。下面分别介绍一下,能想到的尽量都写一下。

函数模板

定义方式

template <typename T[typename Q]>
return_type func_name(param_list) {
    
    
	//函数体
}

template和typename都是关键字,typename可以替换为class,两者是等价的。T和Q叫模板形参,模板形参可以有一个或多个但是不能为空,多个的情况下用逗号分割,并且每个形参前面都需要加typename或者class。

下面举个例子:

#include <iostream>
template <class T>
T Add(T x, T y) {
    
    
	return x + y;
}

int main() {
    
    
	int a = Add(2, 3); // 这里相当于隐式的生成了一个函数 int Add(int, int),
	std::cout << "a = " << a << std::endl;
	
	float f = Add(3.9, 1.3) //这里会隐式的生成一个函数 float Add(float, float)
	std::cout << "s = " << s << std::endl;

	return 0;
}

还有一种情况是模板的形参不是函数的入参会是什么情况呢?看看下面的例子。

#include <iostream>

template <typename T>
void Div() {
    
    
	T x = 2;
	T y = 5;

	T result = y/x;
	std::cout << "result = " << result << "\n";
	return;
}

int main() {
    
    
	Div(); //会报错的,因为不能够推导出Div中T的类型,需要指定
	Div<int>(); //ok
	Div<float>(); //ok
	return 0;
}

也就是说如果不能够推导出T的类型,需要指定调用时的类型

匹配规则

  1. 如果函数调用可以完全匹配到一个普通函数和函数模板,那么优选选择普通函数
  2. 可以使用模板的空实参列表来强制调用模板函数
  3. 模板函数可以重载
  4. 如果模板函数可以更好的匹配,那么选择模板

函数模板和模板函数

函数模板说明这是一个模板,而模板函数说明这是一个函数。函数模板就是上面讲到的通过template定义的;而模板函数就是在调用的时候生成的函数可能是Add<int> 或Add<float>。

特化

函数模板的特化,个人理解就是当前的模板已经不能够满足所有的类型了,需要定义一个将模板形参指定为特定类型,然后针对该特定类型的实现版本。函数模板的特化只能是全特化,也就是说要指定所有模板形参的类型。
下面看一个例子:

template<class T>
T Max(T t1,T t2){
    
    
    return (t1>t2)?t1:t2;
}
  
typedef const char* CCP;
template<>
CCP Max<CCP>(CCP s1,CCP s2){
    
    
    return (strcmp(s1,s2)>0)?s1:s2;
}
int main(){
    
    
	//调用实例:int Max<int>(int,int)
    int i=Max(10,5);
    //调用显示特化:const char* Max<const char*>(const char*,const char*)
    const char* p=Max<const char*>("very","good");
    cout<<"i:"<<i<<endl;
    cout<<"p:"<<p<<endl;
}

上面的例子中Max函数模板的实现对于char *类型的比较结果并不是我们想要的,它会返回地址的比较而不是内容的比较,这时候就可以通过特化来实现一个针对char*的函数。
当然特化也可以通过重载来实现相同的功能。

typedef const char* CCP;
CCP Max(CCP s1,CCP s2){
    
    
    return (strcmp(s1,s2)>0)?s1:s2;
}

类模板

定义方式

template <class T>
class class_name {
    
    
	//类定义
};

其中的关键字与函数模板相同,就不再重复叙述。
下面举例子说明:

template <class T1, class T2>
class Yuebing {
    
    
public:
	Yuebing(T1 xian, T2 color);
	void Show();

private:
	T1 yuebingxian;
	T2 color;
};

//函数实现
template <class T1, class T2>
Yuebing<T1, T2>::Yuebing(T1 t1, T2 t2) {
    
    
	yuebingxian = t1;
	color = t2;
}

template <class T1, class T2>
void Yuebing<T1, T2>::Show() {
    
    
	std::cout << "yuebingxian = " << yuebingxian << "\n";
	std::cout << "color = " << color << "\n";
	return;
}

int main() {
    
    
	Yuebing<std::string, int> yuebing1("danhuang", 111);
	yuebing1.Show();

	Yuebing<int, char> yuebing2(3333, 'a');
	yuebing2.Show();
	return 0;
}

继承

涉及到模板类的继承需要注意的问题是模板的形参设置的问题,一般的规则是:如果普通类继承模板类的话需要指定模板类的类型;如果模板类继承模板类的话可以使用子类的类型来设置父类类型;如果模板类继承普通类就是正常的继承就可以了;还有一种情况是模板类继承类模板,这个比较绕,实际就是由模板来指定父类。
下面写几个简单的例子哈:

//普通类继承模板类
template <class T>
class Base1 {
    
    
};

class Derived: public Base1<int> {
    
    
};

//类模板继承普通类
class Base2 {
    
    
}template <class T>
class Derived2 : public Base2 {
    
    
};

//类模板继承类模板
template <class T>
class Base3{
    
    
};
template <class T1, class T2>
class Drived3 : public Base3<T2> {
    
    
};

//模板类继承类模板
template <class T>
class Drived4 : public T {
    
    
};
Drived4<Base3>(); //父类是Base3
Drived4<Base1>(); //父类是Base1

类模板和模板类

这两个定义和函数的类似,类模板就是上面定义的Yuebing,模板类就是指定了模板中形参类型,例如:Yuebing<std::string, int>。

特化

类模板的特化比函数模板特化稍微复杂一点,可以分为全特化和偏特化。全特化和函数模板类似,就是指定所有的模板参数的类型;偏特化可以分为几种情况:1.指定部分模板参数的类型,2.只接受T或T&或const T或const T或const T&,3.只接受vector<T>或map<T>或list<T>。
下面写几个例子:

//全特化
template <class T1, class T2>
class MyClass{
    
    }

template <>
class MyClass<int, float> {
    
    }

//偏特化,指定部分参数
template <class T1, class T2>
class MyClass{
    
    }

template <class T2>
class MyClass<int, T2> {
    
    }

//指针、引用、const特化
template <class T1, class T2>
class MyClass{
    
    }

template <class T1, class T2>
class MyClass<T1*, T2*>{
    
    }

//vector特化
template <class T1, class T2>
class MyClass{
    
    }

template <class T1, class T2>
class MyClass<vector<T1>, vector<T2>>{
    
    }

非类型模板参数

直接上栗子:

template <class T, int MAXSIZE>
class Stack{
    
    
private:
	T elements[MAXSIZE];
};

也就是说模板参数不一定是类型,普通值也可以作为参数。但是非类型模板的形参是有限制的,只能是整型,指针或者引用,float,double,String, String **这样的类型是不可以的。但是double &,double *,对象的引用或指针是可以的。还应当注意,在调用的时候该参数必须是一个值或者是一个常量表达式能够在边际阶段就能确定值是多少。

Guess you like

Origin blog.csdn.net/itlilyer/article/details/109823585