> 文章列表 > C++模板的使用

C++模板的使用

C++模板的使用

在平时的工作和学习过程中,经常会用到泛型,这里对泛型和模板进行一下梳理,以便理解和使用。

模板关键字

	template。

为什么要使用模板?

假如设计一个两个参数的函数,用来求两个对象的乘积,在实践中我们可能需要定义n多个函数

int multiplication(int a,int b){return a+b;}
char multiplication(char a,char b){return a+b;}
float multiplication(float a,float b){return a+b;}
...

这些函数几乎相同,唯一的区别就是形参类型不同,在这种情况下,不必定义多个函数,只需要在模板中定义一次即可。
在调用函数时系统会根据实参的类型来取代模板中的虚拟类型,从而实现不同的函数功能。

模板与泛型

模板是泛型编程的基础,泛型编程是指独立与任何类型的代码编程。
模板是一种对类型进行参数化的工具,有两种形式:函数模板和类模板。

函数模板

函数模板 针对仅参数类型不同的函数
定义格式

template<typename Type>
Type funName(Type val)
{//Code
}

模板形参表使用typename或者class定义都可以,没有任何区别,为了区分类的定义,一般使用typename.
//对于任意类型的两个对象相加,的函数模板

template<typename T>
T multiplication(T a,T b)
{return a * b;
}

函数模板调用
对于函数模板,有两种调用方式
显示类型调用 需要在函数调用处的函数名后面加上类型参数
multiplication(2,4);
自动类型推导 根据参数的类型进行推导,但是两个参数的类型必须一致,否则会报错。
multiplication(‘a’,‘c’);
那么需要传两个不一样的参数要怎么做呢?写两个模板类型即可:

template <typename T,typename U>
auto multiplication(T a,U b)
{return a * b;
}

函数模板和普通函数

函数模板:不提供隐式类型转换,必须是严格匹配。
普通函数:提供隐式类型转换。

 template<typename T>
void multiplication(T a,T b)
{cout<<"模板函数"<< a * b <<endl;
}
show('A',65);		//“void showSum(T,T)”: 未能从“char”为“T”推导 模板 参数
show<int>('A',65);	//显示指定模板类型后,‘A’可以转换到int

函数模板和普通函数构成重载时,调用规则:

template<typename T>
void multiplication(T a,T b)
{cout<<"模板函数(2)"<<a*b<<endl;
}
template<typename T>
void sum(T a,T b,T c)
{cout<<"模板函数(3)"<<a*b*c<<endl;
}
void sum(int a,int c)
{cout<<"普通函数"<<a*c<<endl;
}
void Test()
{multiplication(1,2);			//当函数模板和普通函数参数都符合时,优先选择普通函数multiplication<>(1,2);			//若显示使用模板函数,则使用<>类型列表multiplication(3.0,4.2);		//如果函数模板产生更好的匹配,则使用函数模板multiplication(5.0,6.5,8.2);	//只有一个合适multiplication('a',12);		//调用普通函数,可以隐式类型转换}

类模板

类模板与函数模板的定义和使用类似。 有时,有两个或多个类,其功能是相同的,仅仅是数据类型不同,如下面语句声明了一个类:
//模板的参数类型定义写在类的定义之前,在类里的任意位置都可以使用

template<typename T>
class Factory
{
public:Factory(T val) :_value(val){}void Factory(){cout << _value << endl;}
private:T _value;
};

类模板用于实现类所需数据的类型参数化
类模板在表示如数组、表、图等数据结构显得特别重要,这些数据结构的表示和算法不受所包含的元素类型的影响。

单个类模板语法

定义一个类模板非常简单,重要的是如何去用类模板定义对象

如果没有指定模板的参数列表,编译器是会报错的

Factory d(20);			//error C155: “Factory”: 使用 类 模板 需要 模板 参数列表

指定参数列表只需要在类名的后面加上<类型>即可

Factory<string> d("aaa");
Factory<string> d1(string("bbb"));

实际上,类模板的使用就是将类模板实例化成一个具体的类
类模板不代表一个具体的、实际的类,而代表一类类。

只有那些被调用的成员函数,才会产生这些函数的实例化代码。对于类模板,成员函数只有在被使用的时候才会被实例化。显然,这样可以节省空间和时间;

如果类模板中含有静态成员,那么用来实例化的每种类型,都会实例化这些静态成员。

来个示例

#include<iostream>
#include<string>
using namespace std;template<typename T>
class Object
{
public:Object(int size) :_capacity(size),_size(0),_base(nullptr){if (_capacity == 0)_capacity = 1;_base = new T[_capacity]{T()};}T& operator[](int index){if (index < 0 || index >= _capacity){//return T(); //不能返回临时对象的引用,对于int() 是一个0throw std::out_of_range("Object 越界");	//抛异常是最合适的}return _base[index];}
private:T* _base;int _size;int _capacity;
};
int main()
{Object<int> arr(10);for (size_t i = 0; i < 10; i++){cout << arr[i] << " ";}cout << endl;Object<string> arr1(10);arr1[0] = string("cdef");for (size_t i = 0; i < 10; i++){cout << arr1[i] << " ";}return 0;
}

继承中的类模板——类模板派生普通类

子类从模板类继承的时候,需要让编译器知道,父类的数据类型具体是什么(数据类型的本质:如何分配内存空间)

template<typename T>
class Factory 
{
public:Factory(T val) :_value(val){}void factory(){cout << _value << endl;}
protected:T _value;
};
class A:public Factory<int>	//指定具体类型
{
public:using Factory<int>::Factory;void show(){cout << "A" <<" "<<_value<< endl;}
};

继承中的类模板——类模板派生模板

template<typename T>
class Factory
{
public:Factory(T val) :_value(val){}void factory(){cout << _value << endl;}
protected:T _value;
};
template<typename U>
class A:public Factory<U>
{
public:using Factory<U>::Factory;void show(){cout << "A" <<" "<<_value<< endl;}
};

代码看起来没有问题,但是在子类中使用父类的成员,会提示找不到标识符

void show()
{cout << "A" <<" "<<_value<< endl;		//error C2065: “_value”: 未声明的标识符
}

解决办法
1,通过this指针访问:this->_value
2,通过父类访问: Factory< U >::_value

模板特化

提到特化这个概念,就想到泛化的概念。模板函数的T参数只能传入类类型的参数;特化函数的参数只能传入对应的参数类型。

函数模板特化

假设有一个比较两个对象的模板函数

template<typename T>
int compare(T a, T b)
{cout << "T" << endl;return a == b ? 0 : (a > b ? 1 : -1);
}

对于支持operator== 和 operator>操作的类型, 包括基本的int,float,double等类型,是完全没有问题的,但是它不能用来比较字符串(char*),因为这个函数比较的是串指针,而不是字符串本身。

cout << compare("A", "a") << endl;	//类型是const char*,比较的是地址,需要做特化版本才能比较

特化版本:

template<>	//必须写,不然就是重载函数,而不是函数模板,特化版本了
inline  int compare(const char* str1, const char* str2)
{cout << "const char *" << endl;return strcmp(str1, str2);
}或者//test.h
template<>
int compare(const char* str1, const char* str2);
//test.cpp
template<>
int compare(const char* str1, const char* str2)
{cout << "特化 const char *" << endl;return strcmp(str1, str2);
}

这样,就能正确比较字符串了。

类模板特化

全特化:所有类型模板参数都用具体类型代表,特化版本模板参数列表为空 template<>

template<typename T,typename U>
struct Test
{void show(){cout<<"非特化版本"<<endl;}
};
//全特化版本
template<>
struct Test<int,int>
{void show(){cout<<"int,int特化版本"<<endl;}
};
//特化版本可以有任意多个
template<>
struct Test<double,string>
{void show(){cout<<"double,string特化版本"<<endl;}
};
//测试
int main()
{Test<int,int> t;t.show();					//int,int特化版本Test<double, string> t1;	t1.show();					//double,string特化版本Test<char, char> t2;t2.show();					//非特化版本return 0;
}

局部特化(偏特化):指定一部分模板参数用具体类型代替

从模板参数数量上

//从模板参数数量上
template<typename T,typename U>
struct Test
{void show(){cout<<"非特化版本"<<endl;}
};
//局部特化
template<typename U>
struct Test<double,U>
{void show(){cout<<"非特化版本"<<endl;}
};
//测试
int main()
{Test<char,string> tt;tt.show();				//局部特化版本return 0;
}

从模板参数范围上(int -> int&)

//从模板参数范围上
template<typename T>
struct Test
{void show(){cout<<"非特化版本"<<endl;}
};
//const T
template<typename T>
struct Test<T&>
{void show(){cout<<"T&特化版本"<<endl;}
};
//T*
template<typename T>
struct Test<T*>
{void show(){cout<<"T*特化版本"<<endl;}
};
//测试
int main()
{Test<int> test;test.show();			//非特化版本Test<int*> test1;test1.show();			//T*特化版本Test<int&> test2;test2.show();			//T&特化版本return 0;
}

总结

泛型编程和面向对象编程,都依赖与某种形式的多态。面向对象编程的多态性在运行时应用于存在继承关系的类。在泛型编程中,编写的代码可以用作多种类型的对象。面向对象编程所依赖的多态性称为运行时多态性,而泛型编程所依赖的多态性称为编译时多态性或参数式多态性。
通过上面这些介绍,相信会对模板与泛型理解的更深入。