模板(template)
1. 重载,不能解决的问题
从 C 语言刚学 C++ 重载的时候,只有一个想法:重载好阿,重载妙啊,想用重载大展拳脚。
🌰一个残酷对比奉上:
// 是从 C 开始就被我们写烂的 Swap 函数
/****************************** 重载 ****************************/
void Swap(int& left, int& right)
{
int tmp = left;
left = right;
right = tmp;
}
void Swap(double& left, double& right)
{
double tmp = left;
left = right;
right = tmp;
}
void Swap(char& left, char& right)
{
double tmp = left;
left = right;
right = tmp;
}
/****************************** 模板 ****************************/
template<class T>
void Swap(T& x, T& y)
{
T tmp = x;
x = y;
y = tmp;
}
(在你盘算重载参数有没有写齐全的同时,模板已经写出三份了~
-
不可否认的是:
- 重载的函数仅仅是类型不同,代码复用率比较低,只要有新类型出现时,就需要用户自己增加对应的函数。
- 代码的可维护性比较低,一个出错可能所有的重载均出错。
为解决这个问题,我们引入 泛型编程 的概念,
泛型编程,是编写与类型无关的通用代码,是代码复用的一种手段。模板 是泛型编程的基础。
模板分为 函数模板 和 类模板。
2. 模板的原理
如图所示,使用函数模板看似只写了一个函数,实际上该有的重载函数一个不少,只是不再需要用户去反复编写,而是通过模板实例化自动生成。
⭕注意:
-
模板的声明或定义只能在 全局,命名空间 或 类范围内 进行。即不能在局部范围,函数内进行,比如不能在main函数中声明或定义一个模板。
-
如果定义和声名分开写,两份都需要声明 template
-
类模板的定义和声明尽量放在一个文件下,可以分开方式但会很麻烦。(具体方式见下篇)
-
类模板的名称:类名 <参数>
3. 函数模板
函数模板,定义时,简化了函数参数,使其与具体类型无关,在使用时,根据实参类型被实例化,产生函数的特定类型版本以供使用。
写法:
template<class T1, class T2> // T 为自定义名,class 也可写作 typedef
函数...
🌰举个例子:
- 👣 定义模板:
template<class T>
T Add(T a, T b)
{
return a + b;
}
- 👣 使用模板
-
使用模板函数有两种方式:
-
☝️隐式实例化,传参过程中,自动推演模板类型,
实参前面可加强制类型转化。 -
✌️显式实例化,直接声明参数类型,不需要推演,
传参的过程中,参数会视情况进行隐式类型转换。
/*********************** 测试及结果如下 ***********************/
void testAdd()
{
int a1 = 10, a2 = 20;
double d1 = 1.88, d2 = 2.77;
// 隐式实例化
cout << Add(a1, a2) << endl; // 30
cout << Add(d1, d2) << endl; // 4.65
cout << Add(a1, (int)d1) << endl; // 11
cout << Add((double)a1, d1) << endl; // 11.88
//cout << Add(a1, d1) << endl; // 报错,因为类型与模板不一致,这里可以回忆一下 auto 的使用规则
// 显式实例化
cout << Add<int>(a1, d1) << endl; // 11
cout << Add<double>(a1, d1) << endl; // 11.88
}
-----------
测试结果:
30
4.65
11
11.88
11
11.88
- 👣 函数模板 与 函数 同时存在
如果用户定义了相应参数类型的函数,会调用用户定义的!!一是用户可以 个性化定制,二是 执行效率更高,不需要模板实例化。
ps:如果在有用户定义的情况下,还想用模板,可以用模板显式实例化。
// 将下面这个函数放入上面的代码段,仍然调用 testAdd() 进行测试
int Add(int a, int b)
{
cout << "int Add() = ";
return a + b;
}
/*********************** 测试及结果如下 ***********************/
int Add = 30
4.65
int Add = 11
11.88
11
11.88
4. 类模板
函数模板 解决了 函数重载 的限制问题,类模板 将解决 typedef 的限制问题,针对仅数据成员和成员函数类型不同的类。
🤔问:typedef 差在哪儿啦?
typedef int STDataType;
class Stack
{
//...
private:
STDataType* _a;
size_t _top;
size_t _capacity;
};
/**** 要求,创建两个对象,其中成员_a的类型分别为 int* 和 double* ****/
int main()
{
Stack st1; // int
Stack st2; // ...还是int
return 0;
}
//(typedef 内心 os:写完 main 函数是我最后的倔强
答:无法创建多类型的对象成员!!
🌰类模板的写法和函数模板异曲同工,直接上代码:
template<class T>
class Stack
{
public:
Stack(int capacity = 4)
{
_a = new T[capacity]; // try catch 暂时不考虑
_top = 0;
_capacity = 0;
}
~Stack(); // 类中声明,类外定义
private:
T* _a;
size_t _top;
size_t _capacity;
};
template<class T> // 需要再次 template
Stack<T>::~Stack() // 类模板类型名称:类名<参数>
{
delete[] _a; // 方括号!!!!匹配使用,一定注意检查
_top = 0;
_capacity = 0;
}
int main()
{
Stack<int> st1;
Stack<double> st1;
return 0;
}
这是一段 不学模板 看不懂的代码:
vector<int> v; // 模板、类对象
for(int i = 0; i < v.size(); ++i)
{
cout << v[i] << " "; // 运算符重载
}
5. 模板 Ⅰ 总结
-
<class / typedef T> 两种写法都可取,T 是自定义名称 通常大写,多个参数时,用逗号间隔。
-
使用模板函数时可以将 参数类型 显式实例化。
-
函数模板和同名函数可以同时存在,优先调用用户定义的 同名函数。
(有同名函数时,可以显式实例化模板) -
模板的 声明或定义 只能在 全局、命名空间、类 范围内进行。即不能在局部范围,函数内进行,比如不能在 main 函数中声明或定义一个模板。
-
如果定义和声名分开写,声明 也需要标记 template。
-
模板的 定义 和 声明 尽量写在 一个文件中,可以分开方式但会很麻烦。(具体方式见下篇)
-
类模板的名称:类名 <参数>
-
多多使用为益!!
接下篇 🔗进阶,特殊情况的处理🔗
👉【C++】模板 template Ⅱ:模板的非类型模板参数、模板特化(函数模板、类模板)、为什么模板不支持分离编译
文末,私心放一些正儿八经的优质文,都是本篇提及到的相关内容,欢迎点赞收藏~🥰
🔗函数重载、auto
🔗运算符重载
🔗内存管理函数 new / delete