• C++ 中的模板函数简介



    前言

    这一章主要讲模板函数和泛型的定义,主要用法和作用,简单写了一下和普通函数的异同。
    本文主要为学习心得笔记,如有纰漏,欢迎指正

    一、函数模板(模板函数)是什么,有什么用,怎么用?

    当你写了一个交换函数,交换传入参数的值,例如:

    void mySwap(int &a, int &b)
    {
    	int temp = a;
    	a = b;
    	b = temp;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    你会发现两个问题:

    • 这个函数只适用与该指定数据类型的运算。
    • 无论是重新实现函数,或者重载函数,或者重载运算符,都需要大量的重复代码,且不易维护。
      比如你要实现double类型的比较,就需要重新使用double类型来重新实现一遍这个函数,很不方便。
      函数模板的作用:
    • 将数据类型抽象化<泛型>,可以传入多种不同的数据类型,并实现逻辑运算,提高代码的复用性。

    声明和定义:

    //声明创建模板 
    //函数返回值 函数名(参数列表) { 函数主体 }
    
    template<typename T>
    void func()
    { // 代码块}
    
    //或:
    template<class T>
    void func()
    { // 代码块}
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 上面的代码中,大写字母 T 指代了一个数据类型,这个数据类型需要手动来指定,这个数据也被称为泛型

    函数模板的调用方式

    1. 自动类型推导,可以直接传入指定参数,编译器会自动识别
    2. 指定数据类型调用,编译器会根据你给出的类型引用(或强制类型转换)
    	//第一种方式:
    	函数名(参数列表);
    
    	//第二种方式:
    	函数名<指定数据类型>(参数列表);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 在下面重新给交换函数做模板函数实现,这个时候就可以传入其他类型的数据了:
    template<typename T>
    void mySwap(T &a, T &b)
    {
    	T temp = a;
    	a = b;
    	b = temp;
    }
    
    void test1()
    {
    	long a = 10;
    	long b = 20;
    	//两种方式使用函数模板
    	//第一种方式:
    	mySwap(a, b);
    
    	//第二种方式:
    	//mySwap(a, b);
    
    	cout << "a = " << a << endl
    		<< "b = " << b << endl;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22

    注意:

    1. 自动类型推导,必须导出一致的数据类型
      上面的例字中,如果使用自动类型推导方式传参,是无法进行隐式类型转换的;如果传入参数不一致,则需要在调用时强制类型转换,或者使用指定数据类型的方式进行调用。
    2. 模板必须要有确定(推导)出的数据类型,才可以使用
      当你写了一个模板函数,但函数体内不含有任何泛型时,调用也必须指定泛型,否则就会报错,如下:
    template<typename T>
    void print()
    {
    	cout << "打印函数" << endl;
    }
    
    void test2()
    {
    	print<int>();	//如果模板函数无法判断泛型类型,则必须写上类型
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    二、模板函数与普通函数的异同

    1.关于隐式类型转换

    • 普通函数调用时可以发生隐式类型转换;
    • 函数模板在调用时,只有通过指定数据类型的方式调用才能进行隐式类型转换
    • 使用自动类型推导的方式调用无法发生隐式类型转换
    template<typename T>
    void myPrint(T a, T b)	//不要传引用
    {
    	cout << a << endl;
    	cout << b << endl;
    }
    
    void test1()
    {
    	int a = 10;
    	char b = 'a';
    	// 这样写是错误的,编译器无法确定转化的数据类型
    	//mySwap(a, b);
    
    	// 这种写法可以使两个变量发生隐式类型转换
    	myPrint<int>(a, b);
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    注:

    • 如需使用隐式类型转换,则不能传入地址(引用),不论任何函数都是一样的。

    2.关于重载和调用优先级

    1. 函数模板可以重载
    2. 如果函数模板和普通函数都可以调用,则优先调用普通函数
    3. 如果函数模板可以产生更好的匹配,则优先调用函数模板(优先调用不需要类型转化的)
    4. 可以通过空模板参数列表,强制调用函数模板
    void myPrint(int a, int b)
    {
    	cout << "普通函数" << endl;
    }
    
    template<typename T>
    void myPrint(T a, T b)
    {
    	cout << "模板函数" << endl;
    }
    
    template<typename T>
    void myPrint(T a, T b, T c)
    {
    	cout << "重载模板函数" << endl;
    }
    
    void test3()
    {
    	int a = 1;
    	int b = 1; 
    	
    	// 优先调用普通函数
    	myPrint(a, b);			//	=> 普通函数
    
    	//强制调用模板函数,写不写数据类型都可以
    	myPrint<int>(a, b);		//	=> 模板函数
    	myPrint<>(a, b);		//	=> 模板函数
    
    	//重载
    	myPrint(a, b, 10);		//	=> 重载模板函数
    
    	//发生更好的匹配时,优先调用不需要类型转化的
    	myPrint('a', 'b');		//	=> 模板函数
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36

    注:

    • 如果提供函数模板,则最好不要提供普通函数重载,否则容易出现二义性

    三、关于可能的问题和局限性

    1.问题

    例:当你想实现两个数值的交换,但是传进来一个数组(各种不符合这个函数功能的数据);
    这个时候用原有的方式无法对这种情况做处理,很容易出问题。

    2.解决方式:

    • 使用具体化的方式做特殊实现,使用具体化的模板,解决自定义类型的通用化。(类似重载,但不是重载)
    
    class Person
    {
    public:
    	string name;
    	int age;
    	Person(string name, int age)
    	{
    		this->name = name;
    		this->age = age;
    	}
    };
    
    //模板函数
    template<typename T>
    bool myCmp(T& a, T& b)
    {
    	return a == b;
    }
    
    //Person版本具体化实现
    template<>
    bool myCmp(Person& a, Person& b)
    {
    	return (a.name == b.name && a.age == b.age);
    }
    
    void test4()
    {
    	Person p1("gzy", 10);
    	Person p2(p1);
    	Person p3(p1);
    	p3.name = "张利伟";
    	cout << (myCmp(p1, p2) ? "相等" : "不相等") << endl;	// => 相等
    	cout << (myCmp(p1, p3) ? "相等" : "不相等") << endl;	// => 不相等
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36

    总结

    函数模板的学习一个目的是学习模板的使用编写,更大的作用是理解stl库的容器使用,为后面的学习做铺垫。

  • 相关阅读:
    漏洞修复:Content-Security-Policy header missing
    Bootstrap Blazor 开源UI库介绍-Table 虚拟滚动行
    【服务器数据恢复】IBM服务器RAID控制器出错的数据恢复案例
    原型设计的坑
    MIT课程分布式系统学习04——PrimaryBackup Replication for Fault Tolerance
    亚马逊哪些因素会影响转化率,如何才能做得更好(测评)
    C#界面里的winform BackColor和BackgroundImage属性
    精通Nginx(14)-配置HTTPS
    DAST 黑盒漏洞扫描器 第三篇:无害化
    【重识云原生】第四章云网络4.9.5.1节下一代智能网卡——DPU综述
  • 原文地址:https://blog.csdn.net/KamikazePilot/article/details/126736180