一、泛型编程
为了引出模板,我们来看下面代码,比如要实现不同类型的交换函数,如下:
void Swap(int& a, int& b)
{
int c = a;
a = b;
b = c;
}
void Swap(char& a, char& b)
{
char c = a;
a = b;
b = c;
}
void Swap(double& a, double& b)
{
double c = a;
a = b;
b = c;
}
这样每个类型交换都需要写一个函数重载,其中代码的重复率很高,只有类型不同而逻辑都一样,写起来也非常的繁琐,那我们能不能写一个通用的函数告诉编译器一个模版让编译器根据不同的类型利用该模版来生成代码呢?这就是本章的主题——模板。
泛型编程:编写与类型无关的通用代码,是代码复用的一种手段。模板是泛型编程的基础。
模板分为函数模板和类模板,接下来我们分别来详细学习。
二、函数模版
函数模板的格式:
template<typename T1, typename T2 , ... ... ,typename Tn>
返回类型 函数名(参数列表){}
注意:这里typename可以换为calss,但不能换位struct。
代码示例:
template<typename T>
void Swap(T& a, T& b)
{
T c = a;
a = b;
b = c;
}
这样我们写一个模板就可以省了很多代码,还增加可读性。以上并不是一个函数只是一个模板,需要等到使用这个函数的时候编译器会具体的实例化出对应类型的函数,它和写多个函数重载并没有本质区别,只是说这个工作让编译器帮我们做了而已,在效率上并没有提升。
需要注意的是如果在这里传两个不同的参数的话会编译报错,因为这里模板参数只有一个,而传入两个不同的类型的实参的时候,编译器并不会帮你强制类型转换,因为它并不知道转成那个类型出了问题它可不背锅。那么我们硬要传两个不同的参数的话,可以显示的强制类型转换成两个相同类型,或者调用的时候在函数名后面加<>,尖括号里面加类型名,也就是显示实例化,
如下示例:
这里有人可能会试图去测试Swap模板,结构还是编不过,这是因为不符合引用的语法,我在之前讲过,如下链接:
C++入门基础-CSDN博客
对于这种情况,我们可以在外面进行强制类型转换存入新的变量里面然后再把新的变量代替原来变量做函数参数。
函数模板与模板函数的区别:通过这两个词就可以看出来函数模板的主语是模板,模板函数的主语是函数,那么接下来就好理解了,函数模板就是一个模板如刚才我们写的那些都是函数模板,而模板函数是函数模板经过实例化后生成的函数。
三、类模板
类模板的定义个格式:
template<typename T1, typename T2, ... ... ,typename Tn>
class 类模板名
{}
同样这里typename可以换为calss,但不能换位struct。
对于类模板与函数模板不同,类模板在使用的时候必须显示实例化,在类模板名后面加<>尖括号里面放入类型名。
注意:类模板不是类,要经过实例化后才是类。如下一个Stack类模板的部分:
// 类模版
template<typename T>
class Stack
{
public :
Stack(size_t capacity = 4)
{
_array = new T[capacity];
_capacity = capacity;
_size = 0;
}
void Push(const T& data);
//... ...
private:
T* _array;
size_t _capacity;
size_t _size;
};
// 模版不建议声明和定义分离到两个文件.h 和.cpp会出现链接错误
template<class T>
void Stack<T>::Push(const T& data)
{
// 扩容
//... ...
_array[_size] = data;
++_size;
}
int main()
{
Stack<int> st1;
Stack<double> st2;
return 0;
}
四、模板参数的匹配原则
接下来讲的匹配原则对于类模板和函数模板都是相同的,我们就以函数模板为例。
一个非模板函数是可以和模板函数同名的,而怎么区分编译器会调用那个呢,如下:
template<typename T>
T Add(T a, T b)
{
return a + b;
}
int Add(int a, int b)
{
return a + b;
}
int main()
{
int a1 = 2, b1 = 4;
double a2 = 2.5, b2 = 1.5;
int ret1=Add(a1, b1);
double ret2=Add(a2, b2);
return 0;
}
编译器在做调用(不仅指函数的调用)的时候有一个特点,有现成的就调现成的,没现成的就调用模板实例化一个。如这里第一个Add调用的是非模板函数,第二个Add调用的是模板函数。
总结
**总结**:本篇文章详细讲解了泛型编程及模板(Template)的基本概念和使用方式,主要通过以下几个方面展开:
### 一、泛型编程简介
- **目标**:编写与类型无关的通用代码,是代码复用的一种有效手段。
- **基础**:模板,它是实现泛型编程的核心技术。
- **类型**:模板分为函数模板和类模板两种,分别应用于函数和类的编写中。
### 二、函数模板
- **格式**:使用`template
- **作用**:允许以相同的方式处理多种不同类型的数据,减少代码冗余。
- **示例**:`Swap`函数的函数模板版本展示了如何使用单一模板实现多类型的交换。
- **注意点**:如果传入不同类型的参数而模板只有一个参数,会引发编译错误。可以通过显示指定模板类型来实例化函数调用,或通过强制类型转换匹配模板。
- **区分**:函数模板是模板定义本身,模板函数是由编译器实例化函数模板产生的特定类型的函数。
### 三、类模板
- **格式**:使用与函数模板相同的声明方式,但紧接着是类定义的开始。
- **实例化**:与函数模板不同,类模板必须在使用前通过明确指定类型(类型实例化)后才能作为具体类型的类。
- **用途**:创建适用于任何类型数据结构的通用容器(如Stack),提供代码的通用性和灵活性。
- **注意点**:不建议将模板声明与实现分离到两个文件(`.h`和`.cpp`),否则会导致链接错误。
### 四、模板参数的匹配原则
- **调用**:当有现成的(具体)函数和模板函数共存时,编译器优先调用现成的非模板函数;如无可用具体函数,则通过模板实例化产生相应类型的函数并调用。
- **应用**:这个规则对于类模板同样适用,在编写模板和具体实现时应充分理解这一特点。
通过以上四个部分的阐述,读者可以理解泛型编程及其重要概念模板(函数模板和类模板)在C++中的作用和应用方式,学会如何在不同类型的操作或数据结构中使用模板以减少重复代码并提高代码复用率。