2-3 模版与泛型编程
1. 前言
我们知道声明变量时需要指明变量的类型,类型决定了变量的属性和表现。变量的类型可以是内置的类型,例如int、float、double等;也可以是自定义的类型,例如结构体和类。在2-2章中函数重载一节我们了解了同名函数、不同数量或类型的参数构成了函数重载的条件,编译器在调用重载函数时根据参数类型推导出最适配的函数调用。
但如果我们想实现一个函数,它的参数类型有很多种可能,那么为了适配所有的参数类型,我们需要编写很多很多个重载的函数。
那么能不能不指定函数参数的类型,即参数类型以泛型的型式定义呢?
答案是:能,C++支持编写函数模板和类模板,以支持函数和类的多样性。STL标准库中容器和方法基本都以模板的方式实现。
函数重载和模板均是静态多态的实现方式,此外在2-4章中详细介绍了可变参数模板,读者可在阅读到相应内容时进行呼应。
2. 函数模板
在不使用模板的情况下,我们定义一个函数需要按如下的方式声明:ret_type function_name (parameter list),即 返回值类型 函数名(参数列表)。当定义模板时,需要先声明变量类型的占位符,语法如下:
template <typename T>
或者
template <class T>
ret_type function_name (parameter list)
T是变量类型的占位符,在函数声明前增加模板定义,即可以在这个函数实现的任何位置使用T类型去定义变量,但需要注意的是T作为泛型参数时无法制定默认值(形参不能为空)。 举例,声明泛型T和模板函数,返回值为T类型,参数为两个T类型的const引用,函数实现为两个参数相加:
#include<iostream>
#include<string>
using namespace std;
template <typename T>
T add(const T& a, const T& b)
{
return a + b;
}
int main()
{
int i = 1, j = 2;
cout << "i + j = " << add(i, j) << endl; // i + j = 3
double x = 3.14, y = 5.27;
cout << "x + y = "<< add(x, y) << endl; // x + y = 8.41
string s1 = "hello";
string s2 = "world";
cout << "s1 + s2 = "<<add(s1, s2)<<endl; // s1 + s2 = helloworld
return 0;
}
通过函数模板中占位符的类型在函数实际调用时被指定,从而实现了一个接口根据不同类型的入参实现不同的功能——接口重用。
3. 类模版
与函数模板类似的,在类定义前声明泛型占位符,在类的定义中使用占位符去定义变量。当类在实例化对象时,需要指定模版中占位符的实际类型,例如: 定义一个模板类,它有两个泛型占位符,在类中定义STL中的map容器作为成员变量,并实现向map中插入数据和将map打印的方法。
#include <map>
#include <string>
#include <iostream>
using namespace std;
template <typename T1, typename T2>
class KVMap
{
private:
map<T1, T2> kvMap;
public:
void setValue(const T1 key, const T2 value) // 向map中插入数据
{
kvMap.insert(make_pair(key, value));
}
void printMap() // 打印map的key和value
{
for(auto iter = kvMap.begin(); iter != kvMap.end(); iter++)
{
cout<<iter->first<<"="<<iter->second<<"&";
}
cout<<endl;
}
};
int main()
{
KVMap<string, string> kvParams; // 实例化对象时指明模板的类型
kvParams.setValue("age", "25");
kvParams.setValue("name", "Evila");
kvParams.printMap(); // age=25&name=Evila&
return 0;
}
3.1 类模板继承于类模板
类模板可以作为基类被继承,若派生类也为类模板,那么可以指定基类特定的类型,也可以使用派生类的泛型来指定基类。
// 以上节定义的类模板为基类
template <typename T1, typename T2>
class KVMap;
// 若派生类也为类模板,则可以用派生类的泛型来指定基类
template <typename T1, typename T2>
class KVMapOne : public KVMap<T1, T2>
{
}
3.2 普通类继承于类模板
类模板可以作为基类被继承,若派生类为普通类,那么必须指明当前基类的类型。
// 以上节定义的类模板为基类
template <typename T1, typename T2>
class KVMap;
// 若派生类也为类模板,则可以用派生类的泛型来指定基类
class KVMapTwo : public KVMap<std::string, int>
{
}
3.3 类模板继承于普通类
类模板继承于普通类时,与普通的继承并无区别。类模板作为派生类,根据继承权限获得基类中的成员访问。
4. 模板特化、偏特化与萃取机
4.1 模板全特化
通过上节可以认识到,模板在非特化情况下,占位符的类型是在实例化时指明的。但有时在实现模板类或模板函数时,需要对某个确定的类型进行特殊处理,即实现特定类型下的非通用行为。此时,我们需要对模板进行特化处理,若将所有的占位符特化为绝对类型,则视为模板的全特化。
举个例子: 1、首先定义一个模板类,这里直接引用第二节定义的KVMap模板类。 2、对模板中的两个占位符进行全特化实现,如特化为string和double。实现非通用行为:在打印double的值时,保留3位数字。
template <> // 不能省略,为了说明正在实现该类的特化版本
class KVMap <string, double>
{
private:
map<string, double> kvMap;
public:
void setValue(const string key, const double value) // 向map中插入数据
{
kvMap.insert(make_pair(key, value));
}
void printMap() // 打印map的key和value
{
for(auto iter = kvMap.begin(); iter != kvMap.end(); iter++)
{
cout<<iter->first<<"="<<setprecision(3)<<iter->second<<"&"; // value为double类型时,只输出3位长度
}
cout<<endl;
}
};
int main()
{
KVMap<string, double> kvParams;
kvParams.setValue("age", 25.12345);
kvParams.printMap(); // age=25.1&
r
剩余60%内容,订阅专栏后可继续查看/也可单篇购买
<p> C++工程师面试真题解析! </p> <p> 邀请头部大厂创作者<a href="https://www.nowcoder.com/profile/73627192" target="_blank">@Evila</a> 及牛客教研共同打磨 </p> <p> 助力程序员的求职! </p>