【c++】模板初阶

[复制链接]
发表于 2025-5-25 12:18:23 | 显示全部楼层 |阅读模式

马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。

您需要 登录 才可以下载或查看,没有账号?立即注册

×
hello~ 很高兴见到各人! 这次带来的是C++中关于模板这部分的一些知识点,如果对你有所帮助的话,可否留下你的三连呢?
个 人 主 页: 默|笙
  


  
一、泛型编程

1.1 什么是泛型编程?

   泛型编程:泛型编程是一种编程范式,它答应在定义函数、类或数据布局时利用范例参数,这些参数可以在利用时被详细范例替换。泛型编程的核心目的是编写通用、可复用的代码,同时保持范例安全。
  

  • 即写代码时先用 “通用符号” 代替详细数据范例(比如用 T 代表恣意范例),必要用到的时候再把这些“通用符号”换成实际范例(比如整数、字符等)。
  • 而模板正是实现泛型编程的基础,是实现它的详细语法工具。
  1.2 为什么要有泛型编程?


  • 在泛型编程出现之前,我们可以通过函数重载为不同范例分别编写交换函数,但无法用同一个函数定义处置处罚多种范例。
   例如对整形之间、浮点型之间以及字符之间实现交换操作的交换函数:
  1. void Swap(int& n1, int& n2)//对整数
  2. {
  3.         int tmp = n1;
  4.         n1 = n2;
  5.         n2 = tmp;
  6. }
  7. void Swap(double& n1, double& n2)//对浮点数
  8. {
  9.         double tmp = n1;
  10.         n1 = n2;
  11.         n2 = tmp;
  12. }
  13. void Swap(char& n1, char& n2)//对字符
  14. {
  15.         char tmp = n1;
  16.         n1 = n2;
  17.         n2 = tmp;
  18. }
复制代码

  • 我们可以或许发现,上面三个重载函数除了范例不同之外,逻辑完全相同。这种重复编码不但繁琐,而且当我们必要修改逻辑时必要对全部重载函数举行修改。那么我们能不能用某个“通用符号”去替换这些范例名称,等到我们要用的时候让编译器代替我们举行替换,从而实现代码复用?固然可以—>模板与泛型编程。
   函数重载的本质是为不同范例创建多个独立的同名函数,这只是语法层面的 “复用”,而非真正的代码复用。我们仍需写重复冗余代码,且维护成本极高(如修改逻辑时必要同步修改所有重载函数)。
  二、模板(template)

   我们从泛型编程那里可以相识到:模板是实现泛型编程的基础,是实现它的详细语法工具。接下来让我们走进模板:
  

  • 模板就像是一个模具,它的外表面是固定的(函数框架),而且它本身不是我们必要的东西,但当我们将材料(详细范例)放入其中生产完成再拿出来时,它就是我们必要的产品(详细函数或类)了。
   就像是做石膏娃娃的模具:

倒入白色的膏体出现的自然是白色的石膏娃娃,同样,如果导入的是其他颜色的膏体出现的自然是不同颜色的石膏娃娃,它们相像但并不相同。
  

  • 模板分为函数模板和类模板。
2.1 函数模板

2.1.1.函数模板概念

   函数模板代表了一个函数家族,该函数模板与详细范例无关,在利用时被参数化,根据实参范例产生函数的特定范例版本。
  

  • 家族->详细成员
  2.1.2函数模板格式

  1. //模板参数声明:
  2. template<typename T1, typename T2, typename T3,......,typename Tn>
  3. 返回类型 函数名(参数列表) {
  4.    // 函数体,使用 T1,T2 等作为类型
  5. }
复制代码
  typename是用来定义模板参数关键字,也可以利用class(切记:不能利用struct代替class)。
  像是之前交换函数的代码,就可以这样写:
  1. template<typename T>
  2. void Swap(T& n1, T& n2)
  3. {
  4.         T tmp = n1;
  5.         n1 = n2;
  6.         n2 = tmp;
  7. }
复制代码
2.1.3函数模板的原理


  • 函数模板它只是一个蓝图,一个空空的框架,它本身并不是函数,是编译器根据详细范例用来产生详细函数的模具,编译器将代替我们去誊写重复代码。
  • 对于模板函数的处置处罚在编译器编译阶段,编译器会根据传入参数的范例推演生成详细的函数。
    如:

2.1.4 函数模板的实例化

   函数模板的实例化是指编译器根据模板定义和实际调用时的范例参数,生成详细函数代码的过程。它分为隐式实例化和显式实例化。
  1. 隐式实例化

   由编译器根据传入实参对推演模板参数的详细范例。
  我们来看接下来的一段错误代码:
  1. template<typename T>
  2. T Add(T& n1, T& n2)
  3. {
  4.         return n1 + n2;
  5. }
  6. int main()
  7. {
  8.         int n1 = 1;
  9.         float n2 = 2.2f;
  10.         cout << Add(n1, n2) << endl;
  11.         return 0;
  12. }
复制代码
发生报错的缘故原由:
   

  • 模板参数推导错误:传入参数第一个是整数范例,第二个是浮点数范例。编译器根据 n1 推导出 T = int,根据 n2 推导出 T = float(模板参数推导是并行,而非顺行),而 T 的值必须唯一,它无法同时匹配 int 和 float,编译失败。
  • 引用绑定限定:即使答应范例转换(如将 float 转换为 int),转换过程中也会产生临时变量(具有常性),而函数参数为非 const 引用(T&),无法绑定临时值(权限不能放大),进一步导致编译错误。"。
  修改方法:

  • 增加模板参数
  1. template<typename T, typename U>//增加模板参数,让 int 和 float 都能匹配上
  2. T Add(T& n1, U& n2)
  3. {
  4.     return n1 + n2;
  5. }
  6. int main()
  7. {
  8.     int n1 = 1;
  9.     float n2 = 2.2f;
  10.     // 自动推导:T=int, U=float
  11.     cout << Add(n1, n2) << endl;
  12.     return 0;
  13. }
复制代码

  • 强制转换范例
  1. template<typename T>
  2. T Add(const T& n1, const T& n2)//加上const用来修饰,如此可绑定临时变量
  3. {
  4.     return n1 + n2;
  5. }
  6. int main()
  7. {
  8.     int n1 = 1;
  9.     float n2 = 2.2f;
  10.     // 自动推导:T=int, U=float
  11.     cout << Add(n1, (int)n2) << endl;//强制转换为 int 类型,形成临时变量
  12.      return 0;
  13. }
复制代码

  • 利用显式实例化(在下面的显式实例化里)。
  • 去掉函数参数里的引用符号,避免绑定题目,固然会产生额外拷贝开销。
2. 显式实例化

   由我们用户主动在函数名后的<>中指定模板参数的实际范例。
  1. template<typename T>
  2. T Add(const T& n1, const T& n2)
  3. {
  4.     return n1 + n2;
  5. }
  6. int main()
  7. {
  8.     int n1 = 1;
  9.     float n2 = 2.2f;
  10.     // 自动推导:T=int, U=float
  11.     cout << Add<int>(n1, n2) << endl;
  12.     return 0;
  13. }
复制代码


  • 倘若 n1, n2 范例不同,编译器会先实验举行隐式范例转换为指定范例,转换失败编译报错。
2.1.5 模板函数的匹配原则


  • 一个非模板函数可以和一个同名的函数模板同时存在,而且该函数模板还可以被实例化为这个非模板函数。
  • 非模板函数答应实参和形参之间隐式范例转换,模板函数则不答应,它要求实参和形参的精准匹配。
  • 对于同名的非模板函数与模板函数,如果非模板函数的参数与调用实参完全匹配(不用范例转换),则会优先调用非模板函数,不会从模板函数里产出一个实例。若必要必要转换才气匹配非模板函数,而模板函数又能产出一个匹配度更高的函数,那么编译器会选择函数模板的实例化版本。
  1. //int类型加法函数
  2. int Add(int n1, int n2)
  3. {
  4.         cout << "int Add(int n1, int n2)" << endl;
  5.         return n1 + n2;
  6. }
  7. //模板加法函数
  8. template<typename T, typename U>
  9. T Add(T n1, U n2)
  10. {
  11.         cout << "T Add(T n1, U n2)" << endl;
  12.         return n1 + n2;
  13. }
  14. int main()
  15. {
  16.         Add(1, 2);//实参完全与非模板函数匹配,不会从模板函数里产出一个实例
  17.         Add(1, 2.2);//第二个实参需要进行隐式转换才能匹配上非模板函数,
  18.         //而模板函数能够产出一个匹配度更高的函数,选择模板函数的实例化版本
  19.         return 0;
  20. }
复制代码

2.2 类模板

2.2.1 类模板格式

  1. template<typename T1, typename T2, ..., typename Tn>
  2. class 类模板名
  3. {
  4.         // 类内成员定义,可以使用T1,T2,...,Tn作为类型
  5. };
复制代码
2.2.2 类模板的实例化

   类模板实例化必须显式实例化,在类模板名字后跟<>,然后将实例化的范例放在<>中即可,类模板名字不是真正的类,而实例化的结果才是真正的类。
  

  • 类模板名<实际范例1, 实际范例2, …, 实际范例n> 对象名;//实际范例一一对应类参数。
  1. template<typename T>
  2. class Stack { /* ... */ };
  3. //对于实例化:Stack只是类模板名,Stack<int>才是具体的类型名
  4. Stack<int> intStack;   // 显式实例化为 int类型的 Stack
复制代码

   今天的分享就到此结束啦,如果对读者朋友们有所帮助的话,可否留下宝贵的三连呢~~
如果可以, 那就让我们共同努力, 一起走下去!
回复

使用道具 举报

登录后关闭弹窗

登录参与点评抽奖  加入IT实名职场社区
去登录
快速回复 返回顶部 返回列表