目录

1. 泛型编程

2. 什么是泛型编程

模板(Template)

3. 函数模板

3.1 函数模板概念

3.2 函数模板格式

4. 函数模板原理

“函数生成蓝图”

5. 函数模板实例化

5.1 隐式实例化

类型不一致的问题

5.2 解决方式

方法1:强制类型转换

方法2:显式实例化

6. 模板匹配原则

6.1 普通函数与模板函数可以同时存在

6.2 优先调用普通函数

6.3 显式调用模板函数

6.4 模板可以产生更匹配版本时优先模板

7. 模板函数不允许自动类型转换

类型必须一致

8. 类模板

8.1 类模板格式

8.2 类模板实例化

9. 模板代码为什么一般放在 .h 文件


1. 泛型编程

在学习 C++ 之前,我们如果想实现一个通用的交换函数,往往需要针对不同类型写多个函数。

void Swap(int& left, int& right)
{
	int temp = left;
	left = right;
	right = temp;
}

void Swap(double& left, double& right)
{
	double temp = left;
	left = right;
	right = temp;
}

void Swap(char& left, char& right)
{
	char temp = left;
	left = right;
	right = temp;
}

虽然函数重载可以实现功能,但存在以下问题:

  1. 代码重复率高,仅仅类型不同

  2. 新增类型时,需要继续写新的重载函数

  3. 维护困难,一个地方修改可能全部都要修改

那么有没有一种方式,可以告诉编译器:

“你按照这个模子,根据不同类型自动生成对应代码”

这就是 泛型编程(Generic Programming)


2. 什么是泛型编程

泛型编程:

编写与类型无关的通用代码,是代码复用的一种手段。

而 C++ 中实现泛型编程的核心就是:

模板(Template)

模板相当于一个“代码模具”,编译器会根据我们传入的类型,自动生成对应代码。


3. 函数模板

3.1 函数模板概念

函数模板并不是一个真正的函数。

它代表的是:

一类函数的通用写法。

编译器会根据传入参数类型,自动生成具体函数。


3.2 函数模板格式

函数模板语法:

template<typename T>
返回值类型 函数名(参数列表)
{
}

例如交换函数:

template<typename T>
void Swap(T& left, T& right)
{
	T temp = left;
	left = right;
	right = temp;
}

使用:

int main()
{
	int a = 10;
	int b = 20;

	Swap(a, b);

	double x = 1.1;
	double y = 2.2;

	Swap(x, y);

	return 0;
}

这里:

  • T 是模板参数

  • typename 用于声明模板类型

注意:

typename

也可以写成:

class

例如:

template<class T>

但不能写成:

template<struct T> // 错误

4. 函数模板原理

函数模板本身并不会生成函数。

它只是一个:

“函数生成蓝图”

编译器在编译阶段会根据实参类型推导生成真正的函数。

例如:

Swap(a, b);

编译器会生成:

void Swap(int& left, int& right)

而:

Swap(x, y);

会生成:

void Swap(double& left, double& right)

所以:

模板把原本程序员重复写代码的工作交给了编译器。


5. 函数模板实例化

不同类型使用模板时:

称为模板实例化

模板实例化分为:

  1. 隐式实例化

  2. 显式实例化


5.1 隐式实例化

编译器根据参数类型自动推导。

例如:

template<class T>
T Add(const T& left, const T& right)
{
	return left + right;
}

int main()
{
	int a1 = 10, a2 = 20;
	double d1 = 10.0, d2 = 20.0;

	Add(a1, a2);
	Add(d1, d2);

	return 0;
}

这里:

  • 第一次调用推导 T=int

  • 第二次调用推导 T=double

编译器自动完成实例化。


类型不一致的问题

下面代码会报错:

Add(a1, d1);

原因:

a1 -> int
d1 -> double

编译器无法确定:

T 到底应该是 int 还是 double

因此推导失败。


5.2 解决方式

方法1:强制类型转换

Add(a1, (int)d1);

方法2:显式实例化

手动指定模板类型:

Add<int>(a1, d1);

这里:

T 被明确指定为 int

因此:

double 会隐式转换为 int

6. 模板匹配原则


6.1 普通函数与模板函数可以同时存在

int Add(int left, int right)
{
	return left + right;
}

template<class T>
T Add(T left, T right)
{
	return left + right;
}

6.2 优先调用普通函数

Add(1, 2);

会优先调用:

int Add(int,int)

因为普通函数不需要实例化。


6.3 显式调用模板函数

Add<int>(1, 2);

此时会调用模板函数实例化版本。


6.4 模板可以产生更匹配版本时优先模板

template<class T1, class T2>
T1 Add(T1 left, T2 right)
{
	return left + right;
}

Add(1, 2.0);

普通函数:

int Add(int,int)

需要类型转换。

而模板:

Add(int,double)

匹配更精准。

因此优先调用模板函数。


7. 模板函数不允许自动类型转换

普通函数允许自动类型转换:

int Add(int a, int b);

Add(1, 2.5);

但模板函数通常不允许自动推导转换:

template<class T>
T Add(T a, T b);

Add(1, 2.5); // 错误

因为模板推导要求:

类型必须一致

否则编译器无法确定模板参数。


8. 类模板

除了函数模板,还有类模板


8.1 类模板格式

template<class T>
class 类名
{
};

例如实现栈:

#include<iostream>
using namespace std;

template<typename T>
class Stack
{
public:
	Stack(size_t capacity = 4)
	{
		_array = new T[capacity];
		_capacity = capacity;
		_size = 0;
	}

	void Push(const T& data)
	{
		_array[_size] = data;
		++_size;
	}

private:
	T* _array;
	size_t _capacity;
	size_t _size;
};

8.2 类模板实例化

类模板必须显示指定类型:

Stack<int> st1;
Stack<double> st2;

注意:

Stack

只是模板名,不是真正类型。

真正的类型是:

Stack<int>
Stack<double>

它们是两个完全不同的类。


9. 模板代码为什么一般放在 .h 文件

模板通常不建议:

声明放 .h
定义放 .cpp

否则容易出现:链接错误

原因:

模板是在调用时才实例化。

如果定义在 .cpp 中:

其他文件可能看不到模板定义。

因此编译器无法生成对应实例代码。

所以模板一般声明和定义都放在头文件中

更多推荐