• C++基础——模板讲解


    目录

    一. 泛型编程

    二. 函数模板

    1.格式:

    2.定义:

    1.隐式实例化

    2.显式实例化 

     3.解决方法3:使用多个T类型

    4.在C++中编译器允许非模板函数和模板函数同时存在


    一. 泛型编程

            先来看一段代码:

    1. void Swap(int& p1, int& p2) {
    2. int tmp = p1;
    3. p1 = p2;
    4. p2 = tmp;
    5. }
    6. int main(){
    7. int a = 1, b = 2;
    8. cout << "a:" << a << " b:" << b << endl;
    9. Swap(a, b);
    10. cout << "a:" << a <<" b:"<
    11. return 0;
    12. }

            若是我们需要对浮点型数据进行交换函数,还得再去写一个Swap函数 :

    1. void Swap(double& p1, double& p2) {
    2. double tmp = p1;
    3. p1 = p2;
    4. p2 = tmp;
    5. }
    6. void Swap(char& p1, char& p2) {
    7. char tmp = p1;
    8. p1 = p2;
    9. p2 = tmp;
    10. }
    11. int main() {
    12. int a = 1, b = 2;
    13. cout << "a:" << a << " b:" << b << endl;
    14. Swap(a, b);
    15. cout << "a:" << a <<" b:"<
    16. cout << "——————————————————————————————" << endl;
    17. double c = 10.5, d = 20.8;
    18. cout << "c:" << c << " d:" << d << endl;
    19. Swap(c, d);
    20. cout << "c:" << c << " d:" << d << endl;
    21. cout << "——————————————————————————————" << endl;
    22. char e = 'a', f ='h';
    23. cout << "e:" << e << " f:" << f << endl;
    24. Swap(e, f);
    25. cout << "e:" << e << " f:" << f << endl;
    26. return 0;
    27. }

              这就说明了一个问题:若有多个变量之间的数据交换,就要频繁去写各种类型的函数,函数体大致是一样的,只是参数类型不同,导致其中含有大量重复代码,代码耦合度高,更容易出bug。

            所以我们需要一个模板,模板是一个蓝图,它本身并不是函数,是编译器用使用方式产生特定具体类型函数的模具。所以其实模板就是将本来应该我们做的重复的事情交给了编译器。

    二. 函数模板

    1.格式:

    1. templatetypename T2,......>
    2. 返回值类型 函数名(参数列表){}

            class用于定义类,在c++引入模板后,最初定义模板的方法为:template,这里class关键字表明T是一个广泛的类型。后来为了避免class在这两个地方的使用可能给人带来混淆,所以引入了typename这个关键字。它的作用同class一样表明后面的符号为一个类型。这样在定义模板的时候就能够使用以下的方式了: template.在模板定义语法中关键字class与typename的作用全然一样。 T就是泛型——广泛类型,它里面包含内置类型和你所创建的自定义类型。

    2.定义:


            一个类模板(也称为类属类或类生成类)同意用户为类定义一种模式。使得类中的某些数据成员、默写成员函数的參数、某些成员函数的返回值,能够取随意类型(包含系统提前定义的和用户自己定义的)。
            假设一个类中数据成员的数据类型不能确定。或者是某个成员函数的參数或返回值的类型不能确定。就必须将此类声明为模板,它的存在不是代表一个详细的、实际的类,而是代表着      (一类)类。

    例:        

    1. template <typename T>
    2. void Swap(T& left, T& right) {
    3. T tmp = left;
    4. left = right;
    5. right = tmp;
    6. }
    1. int main() {
    2. int a = 10, b = 20;
    3. Swap(a, b);
    4. cout << "a:" << a << " b:" << b << endl;
    5. double c = 33.3, d = 66.6;
    6. Swap(c, d);
    7. cout << "c:" << c << " d:" << d << endl;
    8. char e = 'y', f = 'r';
    9. Swap(e, f);
    10. cout << "e:" << e << " f:" << f << endl;
    11. return 0;
    12. }

            编译器在指向模板函数的调用时,会先进行类型推演,它会把接收到的实参类型推演生成对应类型的函数以供调用。

            例如:当double类型数据被作为实参去调用函数模板时,编译器通过实参的double类型进行推演,将T确定为double类型,然后专门产生一份处理double类型的代码。

            其次需要注意的是:使用函数模板时,其函数会实例化出对象,在上面的代码中,共有三种类型数据进行三次函数调用,那么就会实例化出三个对象。但若是同种类型的多次调用,编译器只会生成一个对象:

    1. int a = 10, b = 20;
    2. Swap(a, b);
    3. cout << "a:" << a << " b:" << b << endl;
    4. int x = 500, y = 1220;
    5. Swap(x, y);
    6. cout << "a:" << a << " b:" << b << endl;
    7. int r=63,t=79;
    8. Swap(r, t);
    9. cout << "r:" << r << " t:" << t << endl;

            如上图,这也是进行了三次模板函数的调用,但只实例化出一个对象,这是因为模板函数对于同种类型的多次调用,也只会实例化出一个对象。

            用不同类型的参数使用函数模板时,称为函数模板的实例化。模板参数实例化分为:隐式实例化和显式实例化。

    1.隐式实例化

            看代码:

    1. template<class T>
    2. T Add(const T& l, const T& r) {
    3. return l + r;
    4. }
    5. int main() {
    6. int a1= 1,a2=2;
    7. double b = 5.6,b2=7.8;
    8. Add(a1,a1);
    9. Add(b1,b2);
    10. //两种不同类型的数据相加
    11. Add(a1,b1);
    12. return 0;
    13. }

            前两个Add函数是两个同类型间相加,第三个是int和double类型相加,这不会通过编译,在编译期间编译器看到模板函数调用时,会实例化出对象,需要推演其实参的类型,通过参数一将T推演成int,通过参数二将T推演成double,但模板只有一个T,无法确定T是int还是double,因此报错。注:编译器可不会因为两种类型的不同而进行类型转换操作!!! 

    所以解决方法1:就是在编译器推演实参类型前,就把两个不同类型的转换为同一类型

    1. Add(a, (int)c);
    2. Add((double)a,c);

            这种方式称为隐式实例化,第一个Add函数先将变量c转换为int类型,这样就可以进行推演了,T最终会变成int类型;第二个Add函数 将变量a转换为double类型,T最终变成double型。

    2.显式实例化 

            在函数名后加一对尖括号,里面填写你最终要转换出的类型,这也是T的最终类型。

    1. int main() {
    2. int a = 10;
    3. double b = 20.3;
    4. Add<int>(a, b);
    5. cout << "Add:" <<Add<int>(a,b) << endl;
    6. Add<double>(a, b);
    7. cout << "Add:" << Add<double>(a, b) << endl;
    8. return 0;
    9. }

     3.解决方法3:使用多个T类型

    1. template <class T1, class T2>
    2. T1 Add2(const T1& left, const T2& right) {
    3. return left + right;
    4. }
    5. int main() {
    6. int a1 = 10, a2 = 20;
    7. double b1 = 10.1, b2 = 20.2;
    8. cout << Add2(a1, a2) << endl;
    9. cout << Add2(b1, b2) << endl;
    10. cout << Add2(a1, b2) << endl;
    11. cout << Add2(b1, a2) << endl; //最终结果的类型是根据第一个参数类型所决定
    12. return 0;
    13. }

            直接写出两个T类型,这样即使不用隐式和显式转换也可以进行不同类型间的数据相加,最终结果的类型可以设置为int,也可以是double。


    4.在C++中编译器允许非模板函数和模板函数同时存在

    例:

    1. template <class T1, class T2>
    2. T1 Add( T1& left,T2& right) {
    3. return left + right;
    4. }
    5. int Add(int& left, int& right) {
    6. return left + right;
    7. }
    8. int main() {
    9. int a = 15, b = 30;
    10. double c = 33.3, d = 99.9;
    11. Add(a, b);
    12. Add(c, d);
    13. return 0;
    14. }

    结果: 

            对于非模板函数和同名函数模板,如果其他条件都相同,编译器会优先调用非模板函数,而模板函数可以生成更加匹配的版本。所以第一个Add函数内部的数据都是int,那么编译器肯定会选择现有的int Add函数去调用,其实编译器也很懒的,哈哈哈~。

  • 相关阅读:
    《羊了个羊》一夜爆红?产品运营带来的巨大红利
    Layui合计自定义列
    IDEA找不到Maven窗口
    hadoop的日志知识点
    使用Blazor WebAssembly整合PocketBase的基础项目模板
    2023年哪些渲染器更好用?3D新手适合的渲染器汇总
    Python之前端的学习
    leetcode 85. 最大矩形
    电脑字体怎么改?4个方法快速更改字体!
    《C语言》22-23第一学期后十周教学计划(谭浩强第五版)
  • 原文地址:https://blog.csdn.net/weixin_69283129/article/details/127845086