前言
各位读者朋友们大家好,上期我们讲完了C++的内存管理部分,这一期我们开始初步认识一下模板。
目录
- 前言
- 一. 泛型编程
- 二. 函数模板
- 2.1 函数模板概念
- 2.2 函数模板的格式
- 2.3 函数模板的原理
- 2.4 函数模板的实例化
- 2.5 模板函数匹配规则
- 三. 类模板
- 3.1 类模板的格式
- 3.2 类模板的实例化
- 3.3 类模板的成员函数定义和声明分离
- 结语
一. 泛型编程
如何实现一个交换函数呢?
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.代码的可维护性较低,一个出错可能所有的重载都出错。
我们发现,交换函数里面的逻辑是一样的,只是参数不一样,每次都这样写是不是有点冗余,那能否告诉编译器一个模子,让编译器根据不同的类型利用该模子来生成代码呢?
如果在C++中,也能够存在这样一个模具,通过给这个模具中填充不同材料(类型),来获得不同材料的食品(即生成具体类型的代码),那将会节省许多头发。巧的是前人早已将树栽好,我们只需在此乘凉。
泛型编程:编写与类型无关的普通代码,是代码复用的一种手段。模板是泛型编程的基础。
二. 函数模板
2.1 函数模板概念
函数模板代表了一个和函数家族,该函数模板与类型无关,在使用时被参数化,根据实参类型推测出函数的特定类型版本。
2.2 函数模板的格式
template<typename T1, typename T2,…,typename Tn>
返回值类型 函数名(参数列表){}
template <typename T>
void Swap(T& x, T& y)
{
T tmp = x;
x = y;
y = tmp;
}
注意:typename是用来定义模板参数关键字,也可以使用class(切记:不能使用struct代替class)
虽然两者都是Swap函数,同一个模板,但调用的却不是同一个函数,生成的函数是编译器帮我们生成的。而且上面的Swap函数的参数的类型必须保持一致,因为模板中的参数类型是根据实参推测出来的,如果给x整型,y为double类型,编译就会报错。对于不同类型的参数我们可以写成下面的写法,给模板多参数类型:
template <typename T1,typename T2>
void Swap(T1& x, T2& y)
{
T1 tmp = x;
x = y;
y = tmp;
}
2.3 函数模板的原理
函数模板是一个蓝图,它本身并不是函数,是编译器用使用方式产生特定类型函数的模具。模板就是将本来应该由我们做的事情交给了编译器。
在编译器编译阶段,对于模板函数的使用,编译器需要根据传入的实参来推测生成对以哦那个类型的函数以供使用。比如:当用double类型使用函数模板时,编译器通过对实参类型的推测,将T确定为double类型,然后产生一份专门处理double类型的函数。在使用模板函数的时候一定要声明变量的类型。
2.4 函数模板的实例化
用不同类型的参数使用函数模板时,称为函数模板的实例化。模板实例化分为:隐式实例化和显式实例化。
- 1. 隐式实例化:让编译器根据实参的类型推演出模板参数的类型。
template <typename T>
T& Add(const T& x, const T& y)
{
return x + y;
}
int main()
{
int a = 520, b = 1314;
double m = 520.52, n = 1314.14;
cout << Add(a, b) << endl;
cout << Add(m, n) << endl;
return 0;
}
如果想求两个不同类型参数的和可以走强制类型转换:
也可以走模板的实例化:用函数的模板生成相应的函数
- 2.显式实例化:在函数名后的<>中指定模板参数的实际类型
如果类型不匹配,编译器会尝试进行隐式类型转换,如果无法转换成功编译器将会报错。
像这种没有传模板类型的函数模板,在调用时就一定要显式实例化。
2.5 模板函数匹配规则
+1. 一个非模板函数可以和一个同名的模板函数同时存在,而且该模板函数还可以被实例化为这个非模板函数
template <typename T>
T Add(const T& x, const T& y)
{
return x + y;
}
int Add(const int& x, const int& y)
{
return (x + y)*10;
}
int main()
{
int m = 520, n = 1314;
cout << Add(m, n);
return 0;
}
这种话情况下优先调用非模板函数。
- 2. 对于非模板函数和同名模板函数,如果其他条件都相同,在调用时会优先调用非模板函数而不会从该模板产生出一个实例。如果模板可以产生一个更好匹配的函数,那么就选择模板函数。
第二次调用Add函数,模板可以产生更匹配的,因此使用模板。
三. 类模板
3.1 类模板的格式
template<class T1, class T2, ..., class Tn>
class 类模板名
{
// 类内成员定义
};
template <typename T>
class Stack
{
public:
Stack(int n = 4)
:_array(new T[n])
,_size(0)
,_capacity(n)
{}
~Stack()
{
delete[] _array;
_array = nullptr;
_size = _capacity = 0;
}
void Push(const T& x)
{
if (_size == _capacity)
{
T* tmp = new T[_capacity * 2];
memcpy(tmp, _array, sizeof(Stack) * _size);
delete[] _array;
_array = tmp;
_capacity *= 2;
}
_array[size++] = x;
}
private:
T* _array;
size_t _size;
size_t _capacity;
};
类模板和用typedef数据类型的类在于类模板可以同时产生多种不同类型的类,而用typedef不行。
3.2 类模板的实例化
类模板实例化与函数模板实例化不同,类模板实例化需要在类模板名字后跟<>,然后将实例化的类型放在<>中即可,类模板名字不是真正的类,而实例化的结果才是真正的类。
在调用类模板的时候必须显示实例化
3.3 类模板的成员函数定义和声明分离
声明和定义分离要重新定义模板参数,而且定义和声明不能放到两个文件中。
结语
以上就是关于模板初阶内容的详尽讲解,衷心感谢每一位读者的耐心阅读。希望通过这些内容,能够为大家在学习和使用模板的过程中提供有价值的启示和帮助。模板作为C++中强大的特性之一,其初阶知识的掌握对于后续深入学习和应用至关重要。非常期待大家能够从中受益,并在实际编程中灵活运用。同时,也诚挚地欢迎大家提出宝贵的批评和建议,您的反馈将是我不断进步的动力。再次感谢大家的支持与关注!