目录
一、泛型编程
1、通用版交换函数的实现:
2、模板的引入
二、函数模板
1、函数模板的定义和使用
2、函数模板的实例化
三、类模板
1、类模板的定义和实例化
模板是C++的一项强大特性,犹如中国古代四大发明中的活字印刷术与造纸术融为一体一般,给予C++编程强大的复用能力,允许我们编写与类型无关的通用代码,极大地提高了代码的复用性和可维护性。本文将介绍C++模板编程的基础知识,包括函数模板和类模板。
一、泛型编程
泛型编程的核心思想就是编写出一串与类型无关的代码。C++中的模板就提供了这样一种方法,让我们可以定义通用的算法和数据结构。
1、通用版交换函数的实现:
在学习C语言的时候我们经常会自己命名使用到一种交换函数,swap。
//C语言中交换函数的实现与使用void Swap(int* a, int* b)
{int c = *a;*a = *b;*b = c;
}int main()
{int a = 10, b = 20;Swap(&a, &b);printf("%d %d", a, b);return 0;
}
但是不知道大家是否发现了这一方式的缺陷,没错,就是不能泛用,只能对两个int类型的数据进行交换。倘若我们想要交换两个浮点数类型的数据,又该怎么办呢?
通过前面我们了解了C++不同于C,他支持了函数重载,那我们就多写几个相同内容,相同名字,却只有参数的类型不相同的函数??
传统的函数重载虽然可以实现不同类型的交换:(例如)
void Swap(int* a, int* b)
{int c = *a;*a = *b;*b = c;
}
void Swap(double* a, double* b)
{double c = *a;*a = *b;*b = c;
}
void Swap(char* a, char* b)
{char c = *a;*a = *b;*b = c;
}
可以看见,重载的函数仅仅只是类型不同,代码的复用率极地,只要有新的类型出现,就需要用户自己增加对应的函数。并且代码的可维护性比较低,一个出错可能导致所有的重载出错。
2、模板的引入
那么什么又是模板呢?
我们想到,打造铁器时通常都是将铁水倒进一个提前准备好的模具里,这样冷却下来的形状就会是模具的形状。
如果在C++中,也能够存在一个这样的模具,通过给这个模具填充不同的材料(数据类型),来获得不同的形状材料(也就是具体类型的代码),那就会节省很多时间与空间,根据这个思想,模板就出现了!
二、函数模板
1、函数模板的定义和使用
函数模板是一种用于定义一组相关函数的蓝图。这些函数模板在使用时会根据实参类型生成具体的函数版本。
以swap为例,我们要写的模板就是:
template <typename T>
void Swap(T& a, T& b)
{T c = a;a = b;b = c;
}
(typename是用来定义模板参数的关键字,也能用class代替)
在编译时,编译器会根据传入的实参类型生成具体类型的函数。例如:
int a = 5, b = 10;
Swap(a, b); // 编译器生成处理int类型的Swap函数double x = 5.5, y = 10.5;
Swap(x, y); // 编译器生成处理double类型的Swap函数
函数模板只是一个蓝图,它本身并不是一个函数,而是编译器用特定方式产生具体类型函数的模具,所以其实模具就是将本来应该由我们做的重复的事情,交给了编译器。
2、函数模板的实例化
用不同类型参数使用函数模板时,称为函数模板的实例化。模板参数实例化分为:隐式实例化和显式实例化。
隐式实例化:编译器根据实参推演模板参数的实际类型。
template<typename T>
T Add(const T& left, const T& right)
{return left + right;
}int main()
{int a = 10, b = 20;double x = 10.5, y = 20.5;Add(a, b); // 实例化为int类型的Add函数Add(x, y); // 实例化为double类型的Add函数return 0;
}
但如果交换的x与b呢?
这样编译就不会通过,因为在编译期间,当编译器看见该实例化时,需要推演其实参类型,通过实参将T推演为int或者double,但此时模板参数列表只有一个T,编译器无法确定将T转换为int还是double而报错。在模板中,编译器一般不会进行类型转换操作,因为一旦转换出现问题,编译器就得背黑锅,只有普通函数允许自动类型转换。
此时有两种处理方式,第一种时用户强制转换类型,二就是使用显示实例化。
显式实例化:在函数名后的尖括号中指定模板参数的实际类型。
int a = 10;
double b = 20.5;
Add<int>(a, b); // 显式实例化为int类型的Add函数
//如果类型不匹配,编译器会尝试进行隐式类型转换,如果无法转换成功编译器将会报错。
对于非模板函数和同名函数模板,如果其他条件相同,在调动时会优先调用非模板参数而不会从该模板中产生一个实例。如果模板可以产生一个具有更好匹配的函数,那么才会调用模板进行实例化。
三、类模板
类模板是用于定义一组相关类的蓝图,实例化时根据模板参数生成具体类。
1、类模板的定义和实例化
以一个简单的顺序表为例,
template<typename T>
class Vector
{
public:Vector(size_t capacity = 10): _pData(new T[capacity]), _size(0), _capacity(capacity){}~Vector() {delete[] _pData;}void PushBack(const T& data) {if (_size < _capacity) {_pData[_size++] = data;}}void PopBack() {if (_size > 0) {--_size;}}size_t Size() const {return _size; }T& operator[](size_t pos) {assert(pos < _size);return _pData[pos];}private:T* _pData;size_t _size;size_t _capacity;
};
实例化类模板:
int main()
{Vector<int> vecInt;vecInt.PushBack(10);vecInt.PushBack(20);Vector<double> vecDouble;vecDouble.PushBack(10.5);vecDouble.PushBack(20.5);return 0;
}