C++泛型编程——模板(初识)

简介: C++泛型编程——模板(初识)

C++泛型编程——模板(初识)



本章思维导图:

注:本章思维导图对应的 xmind.png文件都已同步导入至 资源


1. 泛型编程的概念

在C++中,如果我们不借助库函数,要实现两个数据的交换函数swap,由于要考虑到数据类型的多样性,我们难免要将swap函数重载很多次,例如:

void swap(int& num1, int& num2)
{
  int temp = num1;
  num1 = num2;
  num2 = temp;
}
void swap(char& num1, char& num2)
{
  int temp = num1;
  num1 = num2;
  num2 = temp;
}
k'j
void swap(double& num1, double& num2)
{
  int temp = num1;
  num1 = num2;
  num2 = temp;
}
//…………………………

swap函数的函数体基本相同,只有交换数据的类型不同,但就是由于这个小小的不同迫使我们产生很多冗余代码,使生产效率变得低下

为了解决这一问题,C++就支持了泛型编程这一概念:

允许我们用一个通用的代码模板来处理不同类型数据

在C++中,泛型编程就是靠模板来实现的

2. 模板

2.1 模板格式

基本格式为:

template<typename T1, typename T2,......,typename Tn>

  • template为模板的关键字
  • <>里面的即模板参数,代表一个数据类型
  • typename可以用class替代

2.2 函数模板

基本格式为:

template<typename T1, typename T2,......,typename Tn>

返回值类型 函数名(参数列表){}

例如:

//定义一个函数模板swap
//T泛指所有类型
template <typename T>
void swap(T& num1, T& num2)
{
  T temp = num1;
  num1 = num2;
  num2 = temp;
}

注意:

当模板函数的声明和定义不能分布在不同的文件

2.3 函数模板的实例化

用不同的类型使用函数模版生成一个具体的函数这一过程叫做函数模板的实例化,函数模板的实例化有以下两种方法:

2.3.1 隐式(推演)实例化

推演实例化——让编译器根据实参推演模板参数的实际类型

例如:

template <typename T>
void swap(T& num1, T& num2)
{
  T temp = num1;
  num1 = num2;
  num2 = temp;
}
int main()
{
  int a = 1, b = 2;
  double a1 = 1.0, b1 = 2.0;
  swap(a, b); //实例化函数模板为swap(int& num1, int& num2),并进行调用
  swap(a1, b1); //实例化函数模板为swap(double& num1, double& num2),并进行调用
  return 0;
}

需要注意:

如果该函数模板的模板参数只有一个,那么进行推演实例化时,传入的实参的类型就只能有一个(即模板不允许自动类型转换

例如:

template <typename T>
void swap(T& num1, T& num2)
{
  T temp = num1;
  num1 = num2;
  num2 = temp;
}
int main()
{
  int a = 1;
  double b = 2.0;
  swap(a, b);
  return 0;
}
//会报错:
// “swap”: 未找到匹配的重载函数
// “void swap(T &,T &)”: 模板 参数“T”不明确

为了避免这个问题,一种解决方式就是增多模板参数

template <typename T1, typename T2>
void swap(T1& num1, T2& num2)
{
  T1 temp = num1;
  num1 = num2;
  num2 = temp;
}
int main()
{
  int a = 1;
  double b = 2.0;
  swap(a, b);
  return 0;
}

一种方法是使用强制类型转换,使传入的类型相同。但这个方法也有一个需要注意的点:如果函数模板的形参为引用类型,但是没有被const修饰,那么就不能用强制类型转换来实现推演实例化:

template <typename T>
void swap(T& num1, T& num2)
{
  T temp = num1;
  num1 = num2;
  num2 = temp;
}
int main()
{
  int a = 1;
  double b = 2.0;
  swap(a, (int)b);
 //(int)b涉及数据类型的转换,因此产生了一个临时变量,而临时变量具有常性(const),
 //被const修饰的引用权限不能被放大,因此无法转换为没有被const修饰的形参T& num
  return 0;
}

还有一种常用的方法就是使用显式实例化

2.3.2 显式实例化

显式实例化——在函数名后的<>中指定模板参数的实际类型

注意:使用显式实例化同样需要注意引用权限不能提升的问题

例如:

template <typename T>
T Add(const T& num1, const T& num2)
{
  return num1 + num2;
}
int main()
{
  int a1 = 1, b1 = 2;
  double a2 = 2.0, b2 = 4.99;
  int ret1 = Add<int>(a1, a2);
  double ret2 = Add<double>(b1, b2);
  return 0;
}

2.3 类模板

基本格式

template<typename T1, typename T2,......,typename Tn>

class 类模板名 {};

例如:

//定义一个类模板stack
//该stack可以存储所有类型
template <class T>
class stack
{
public:
  stack(int capacity = 3)
  {
    _a = new T[capacity];
    _capacity = capacity;
    _top = 0;
  }
  void push(T val) {}
  //…………
private:
  T* _a;
  int _capacity;
  int _top;
};

类模板只能显示实例化,其基本格式为:

类模板名 <数据类型> 对象名

例如对于上面的类模板stack

stack<int> st1;
stack<double> st2;

成员函数声明和定义分离

template <class T>
class stack
{
public:
  stack(int capacity = 3)
  {
    _a = new T[capacity];
    _capacity = capacity;
    _top = 0;
  }
  void push(T val);
  //…………
private:
  T* _a;
  int _capacity;
  int _top;
};
//当声明和定义分离时,需要制定成员函数所在类的类型
//类模板的类模板名不是类名,类模板名<数据类型>才是类类型
//同样,成员函数的声明和定义不能分在两个不同的文件
template <class T>
void stack<T>::push(T val) {}

3. 模板的本质

  • 和类实例化对象类似,我们不能将类看成是一个具体的对象,它只是一个不占据空间的蓝图
  • 同样,我们也不能将函数模板和类模板看成是一个具体的函数和类,他们也只是实例化一个具体函数和类的模具
  • 只有我们使用一个或多个具体的数据类型用模板实例化时,才会形成一个具体的函数和类。

如上图所示, 在编译器编译阶段,对于模板函数的使用,编译器需要根据传入的实参类型来推演生成对应类型的函数以供调用。因此实际上,我们没写的冗余代码,编译器都替我们完成了,是编译器在替我们负重前行。

相关文章
|
1月前
|
存储 算法 C++
C++ STL 初探:打开标准模板库的大门
C++ STL 初探:打开标准模板库的大门
94 10
|
1月前
|
存储 C++ UED
【实战指南】4步实现C++插件化编程,轻松实现功能定制与扩展
本文介绍了如何通过四步实现C++插件化编程,实现功能定制与扩展。主要内容包括引言、概述、需求分析、设计方案、详细设计、验证和总结。通过动态加载功能模块,实现软件的高度灵活性和可扩展性,支持快速定制和市场变化响应。具体步骤涉及配置文件构建、模块编译、动态库入口实现和主程序加载。验证部分展示了模块加载成功的日志和配置信息。总结中强调了插件化编程的优势及其在多个方面的应用。
232 64
|
1月前
|
安全 程序员 编译器
【实战经验】17个C++编程常见错误及其解决方案
想必不少程序员都有类似的经历:辛苦敲完项目代码,内心满是对作品品质的自信,然而当静态扫描工具登场时,却揭示出诸多隐藏的警告问题。为了让自己的编程之路更加顺畅,也为了持续精进技艺,我想借此机会汇总分享那些常被我们无意间忽视却又导致警告的编程小细节,以此作为对未来的自我警示和提升。
91 5
|
1月前
|
存储 搜索推荐 C++
【C++篇】深度剖析C++ STL:玩转 list 容器,解锁高效编程的秘密武器2
【C++篇】深度剖析C++ STL:玩转 list 容器,解锁高效编程的秘密武器
48 2
【C++篇】深度剖析C++ STL:玩转 list 容器,解锁高效编程的秘密武器2
|
1月前
|
编译器 程序员 C++
【C++打怪之路Lv7】-- 模板初阶
【C++打怪之路Lv7】-- 模板初阶
16 1
|
1月前
|
安全 程序员 编译器
【C++篇】继承之韵:解构编程奥义,领略面向对象的至高法则
【C++篇】继承之韵:解构编程奥义,领略面向对象的至高法则
81 11
|
1月前
|
存储 C++ 容器
【C++篇】深度剖析C++ STL:玩转 list 容器,解锁高效编程的秘密武器1
【C++篇】深度剖析C++ STL:玩转 list 容器,解锁高效编程的秘密武器
52 5
|
1月前
|
编译器 C语言 C++
C++入门6——模板(泛型编程、函数模板、类模板)
C++入门6——模板(泛型编程、函数模板、类模板)
41 0
C++入门6——模板(泛型编程、函数模板、类模板)
|
1月前
|
算法 编译器 C++
【C++篇】领略模板编程的进阶之美:参数巧思与编译的智慧
【C++篇】领略模板编程的进阶之美:参数巧思与编译的智慧
79 2
|
1月前
|
存储 编译器 C++
【C++篇】引领C++模板初体验:泛型编程的力量与妙用
【C++篇】引领C++模板初体验:泛型编程的力量与妙用
38 2