模板
- 模板是将一个事物的结构规律予以固定化、标准化的成果,它体现的是结构形式的标准化。
C++与模板
- 利用模板的思想是泛型编程—即不考虑具体数据类型的编程方式,即功能相同时,不需要重复编写代码
- 模板是建立通用的与数据类型无关的算法的重要手段。C++国际标准ISO 14882将模板正式引入标准库,要求用模板类取代传统C++中定义的类。
- c++集合了过程式语言,通用语言,面向对象语言的众多特点。模板是通用语言的特性,模板又叫参数化类型(parametrized types)。
看一个例子:
#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
using namespace std;
//模板函数
template<typename T>
void M_add(T x1, T x2) {
cout << x1 << " + " << x2 << " = " << num1 + num2 << endl;
}
//模板类
template<typename T>
class M_Class {
public:
static void multi(T x1, T x2) {
cout << x1 << " + " << x2 << " = " << num1 * num2 << endl;
}
};
int main() {
//整型
int x1 = 1;
int x2 = 2;
M_add<int>(x1, x2); //模板函数实例
M_Class<int>::multi(x1, x2); //模板类实例
//浮点型
double num3 = 3.1;
double num4 = 4.2;
M_add<double>(num3, num4); //模板函数实例
M_Class<double>::multi(num3, num4); //模板类实例
return 0;
}
其中的void M_add(T x1, T x2) 为模板函数,class M_Class为模板类
C++中的函数模板
- 函数模板是一组函数的抽象描述,它不是一个实实在在的函数,函数模板 不会编译成任何目标代码。函数模板必须先实例化成模板函数,这些模板函 数再程序运行时会进行编译和链接,然后产生相应的目标代码。简单来说,函数模板就是实现代码的重用,图个省事。
- 函数模板的关键字为template
- 模板的定义。以下是模板定义的一般格式:
template <class any>//class 也可以换成typename,后者是新来的。
void swap(any &a,any &b)
{
......
}
调用时,就像调用普通函数那样调用模板函数,之后的工作编译器会为你摆平。比如:swap(x,y);
- 在使用函数模板时有两种方式
- 自动类型推到调用 Swap(a, b)
- 具体类型显示调用 Swap(a, b)
template < class T>
void max(T& x,T& y) //函数模板
{
cout <<((x>y)?x:y);
}
void func(int i,int j)
{
max(i,j); //自动推到调用
max<int>(i,j); //具体类型显示调用
}
- 函数模板的错误使用
template < class T>
void max(T& x,T& y) //函数模板
{
cout<<((x>y)?x:y);
}
void func(int i,char j) //正常的函数
{
max(i,i); //实例化的模板函数,正确调用
max(i,j); //实例化的模板函数,但错误调用
}
上面例子中第二个实例化的模板函数出现了错误:
这是因为在实例化的过程中要按照声明时的要求进行,函数模板声明了两个形参是同一类型,第二个函数的参数i为int类型而j为char类型,所以编译产生错误,函数模板是不允许隐式类型转换的,调用时类型必须严格匹配
- 多参数调用
函数模板还可以定义任意多个不同的类型参数,但是对于多参数函数模板:- 编译器是无法自动推导返回值类型的
- 可以从左向右部分指定类型参数
#include <iostream>
using namespace std;
template <typename T1, typename T2, typename T3>
T1 add(T2 a, T3 b)
{
T1 ret;
ret = static_cast<T1>(a + b);
return ret;
}
void main()
{
int c = 12;
float d = 23.4;
//cout << add(c, d) << endl; //error,无法自动推导函数返回值
cout << add<float>(c, d) << endl; //返回值在第一个类型参数中指定
cout << add<int, int, float>(c, d) << endl;
system("pause");
}
- 函数模板跟普通函数一样,也可以被重载
- C++编译器优先考虑普通函数
- 如果函数模板可以产生一个更好的匹配,那么就选择函数模板
- 也可以通过空模板实参列表<>限定编译器只匹配函数模板
限定用法:func<>(x,y); (使用时)
#include <iostream>
using namespace std;
//函数模板
template < class T>
void max(T& x,T& y) //函数模板
{
cout <<((x>y)?x:y);
}
//函数模板的重载
template < class T>
void max(T x,T y) //函数模板
{
cout << x;
}
int main(){
max((int*)0,(int*)0) ;
max((int)0,(int)0);
return 0;
}
函数的特化
使用模板时会遇到一些特殊的类型需要特殊处理,不能直接使用当前的模板函数,所以此时我们就需要对该类型特化出一个模板函数(就是写出一个模板函数专门给该类型使用)
template<class T>
bool Isequal(T& p1, T& p2){
return p1 == p2;
}
这个模板函数在遇到字符型数据就用不成了,所以特化一个函数模板
template<> // 此处不添加类型模板,直接使用空即可
bool Isequal<char*>(char*& p1, char*& p2){
return strcmp(p1, p2) == 0;
}
注意:
- 使用模板特化时,必须要先有基础的模板函数(就是上面第一个模板函数)
- 使用特换模板函数时格式有要求:
1.template 后直接跟<> 里面不用写类型
2.函数名<特化类型>(特化类型 参数1, 特化类型 参数2 , …) 在函数名后跟<>其中写要特化的类型 - 特化的函数的函数名,参数列表要和原基础的模板函数想相同,避免不必要的错误
C++中的类模板
- c++中的类模板的写法如下
template <class类塑参数1, class类型参数2, ...>
class 类模板名{
成员函数和成员变量
};
- 类模板中的成员函数放到类模板定义外面写时的语法如下:
template <类型参数表>
返回值类型 类模板名<类型参数名列表>::成员函数名(参数表)
{
...
}
- 用类模板定义对象的写法如下:
有参数:
类模板名<真实类型参数表> 对象名(构造函数实际参数表);
无参数:
类模板名 <真实类型参数表> 对象名;
例子:
#include <iostream>
#include <string>
using namespace std;
template <class T1,class T2>
class Pair
{
public:
T1 key; //关键字
T2 value; //值
Pair(T1 k,T2 v):key(k),value(v) { };
bool operator < (const Pair<T1,T2> & p) const;
};
template<class T1,class T2>
bool Pair<T1,T2>::operator < (const Pair<T1,T2> & p) const
//Pair的成员函数 operator <
{ //"小"的意思就是关键字小
return key < p.key;
}
int main()
{
Pair<string,int> student("Tom",19); //实例化出一个类 Pair<string,int>
cout << student.key << " " << student.value;
return 0;
}
- 函数模板作为类模板成员
类模板中的成员函数还可以是一个函数模板。成员函数模板只有在被调用时才会被实例化。例如下面的程序:
#include <iostream>
using namespace std;
template <class T>
class A
{
public:
template <class T2>
void Func(T2 t) { cout << t; } //成员函数模板
};
int main()
{
A<int> a;
a.Func('K'); //成员函数模板Func被实例化
a.Func("hello");
return 0;
}
类的特化
前面提到函数模板能被重载,而类模板不可以,我们可以通过特化来实现相似的效果,从而可以透明地获得具有更高效率的代码。
特化也分为了两种,全特化和偏特化:
- 全特化
全特化即将所有的模板类型都进行特化,全特化是模板的一个唯一特例,指定的模板实参列表必须和相应的模板参数列表一一对应。
我们不能用一个非类型值来替换模类型参数。但是如果模板参数具有缺省模板实参,那么用来题还实参就是可选的。
template <class T1, class T2>
class Test
{
}
//全特化
template <> //此处同函数模板的特化一样不用写内容
class Test<int , char>{
}
- 偏特化
偏特化感觉像是介于普通模板和全特化之间,只存在部分的类型明确化,而非将模板唯一化。
是一种部分特化:就是只对函数模板的一部分模板类型进行特化
再次划重点 函数模板不能被偏特化。
#include <iostream>
using namespace std;
template<typename T1, typename T2>
class A
{
public:
void function(T1 value1, T2 value2)
{
cout<<"value1 = "<<value1<<endl;
cout<<"value2 = "<<value2<<endl;
}
};
template<typename T>
class A<T, double>
{ // 部分类型明确化,为偏特化类
public:
void function(T value1, double value2)
{
cout<<"Value = "<<value1<<endl;
cout<<"doubleValue = "<<value2<<endl;
}
};
int main()
{
A<char, double> a;
a.function('a', 12.3);
return 0;
}
类的特化优先级
对主版本模板类、全特化类、偏特化类的调用优先级从高到低进行排序是:
全特化类>偏特化类>主版本模板类
这样的优先级顺序对性能也是最好的。
C++类模板中的静态成员
类模板中可以定义静态成员,从该类模板实例化得到的所有类都包含同样的静态成员。
#include <iostream>
using namespace std;
template <class T>
class A
{
private:
static int count;
public:
A() { count ++; }
~A() { count -- ; };
A( A & ) { count ++ ; }
static void PrintCount() { cout << count << endl; }
};
template<> int A<int>::count = 0;
template<> int A<double>::count = 0;
int main()
{
A<int> ia; //模板类ia
A<double> da; //模板类da
ia.PrintCount(); //只创建一个模板类对象,名为ia
da.PrintCount(); //只创建一个模板类对象,名为da。
return 0;
}
不同类型的类模板共享的静态成员变量不是同一块。
类模板下运算符重载的两种用法
1.
template <typename T>
class Complex
{
friend ostream &operator <<<T>(ostream &out,const Complex &c);
private:
T m_a;
T m_b;
public:
Complex(T a,T b);
void print();
};
friend ostream &operator <<(ostream &out,const Complex &c);
这句的放在运算符<<后是因为该模板类中的成员变量都为T类;
2.
template <typename T>
class Myarray
{
private:
int m_len;
T *m_data;
public:
//Myarray();
Myarray(int l);
Myarray(const Myarray &a);
~Myarray();
template <typename U>
friend ostream &operator <<(ostream &out,const Myarray<U> &a);
T &operator [](int index);
};
friend ostream &operator <<(ostream &out,const Myarray &a);
而此句的类模板中有一个成员变量是int型已确定,因此将放入参数Myarray后。