IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> C++知识库 -> C++函数模板 -> 正文阅读

[C++知识库]C++函数模板

C++函数模板

第一节-函数模板

  • 模板的定义是以template关键字开头;
  • 类型模板参数T前面用typename来修饰,所以,遇到typename就该知道其后面跟的是一个类型。typename可以被class取代,但此处的class并没有“类”;
  • 类型模板参数T(代表是一个类型)以前前面的修饰符typename/class都用<>括起来
  • T这个名字可以换成任意其他标识符,对程序没有影响。用T只是一种编程习惯

例子:

namespace _nmsp1
{
	int Sub(int tv1, int tv2)
	{
		return tv1 - tv2;
	}

	float Sub(float tv1, float tv2)
	{
		return tv1 - tv2;
	}
}

使用函数模板:

namespace _nmsp1
{
	template <typename T>  //T:称为 类型 模板参数,代表的是一个类型。
	// template <class T>  //class 可以取代typename,但是这里的class并没有“类”,只是表示后面的T是一							  //个类型,T这个名字可以任意起
	T Sub(T tv1, T tv2)    //给进去的T必须支持减法操作,编译器会做检查
	{
		return tv1 - tv2;
	}
}

实例化

实例化:在编译器编译的时候,用具体的类型来代替”类型模板参数“的过程(有人也叫代码生成器)

VS上面通过dumpbin命令查看.obj文件可以查看到

.obj文件的格式一般会被认为是一种COFF----通用对象文件格式(Common Object File Format)。

Linux可以看.o文件

int  Sub<int>(int,int)
double Sub<double>(double,double)
实例化之后的函数名分别叫做Sub<int>和Sub<double>

通过函数模板实例化之后的函数名包含三部分:

  • a)模板名;
  • b)后面跟着一对<>;
  • c)<>中间是一个具体类型。

? 编译期间->实例化具体的sub函数

? 在编译阶段,编译器就会查看函数模板的函数体部分,来确定能否针对该类型string进行Sub函数模板的实例化。

在编译阶段,编译器需要能够找到函数模板的函数体部分

在工程中,需要将函数模板包括头和体都要包含在头文件中;

模板参数的推断

  • 常见的参数推断

    namespace _nmsp1
    {
    	//-------------------------------------------------
    	template <typename T,typename U,typename V>
        
    	V Add(T tv1, U tv2)
    	{
    		return tv1 + tv2;
    	}
        
        template <typename V,typename T,typename U>
        
        V Add1(T tv1, U tv2) 
        {
            return tv1 + tv2;
        }
        
        template <typename T, typename U>
        
        auto Add2(T tv1, U tv2) // auto用于表达式推导返回类型的含义
        {
            return tv1 + tv2;
        }
        
         
        template <typename T, typename U>
        
        auto Add3(T tv1, U tv2)-> decltype(tv1 + tv2) // 返回类型后置语法,这里的auto只是返回类型
        {                                             // 后置语法的一部分,并没有类型推导的含义
            return tv1 + tv2;
        }
        
    }
    
    int main()
    {
    	cout << _nmsp1::Add(15, 17,8) << endl;
        // error: _nmsp1::Add未能找到匹配的重载函数
        // 未能为"V"推导模板参数,没有能提推断出返回值
        // 怎么改呢?
        // cout << _nmsp1::Add<...,...double>(15, 17.8)没有这种语法
        cout << _nmsp1::Add<int, double, double>(15, 17.8); // ok麻烦可可以值指定一部分模板参数嘛?
        // 方法二:
        cout << _nmsp1::Add1<double>(15, 17.8); // // 通过<>可以只指定一部分模板参数的类型,另外一部分模板参数的类型可以通过调用时给的实参来推断。但是一旦开始了推断就必须让它都是自动推断
        // 方法三:
        cout << _nmsp1::Add2(15, 17.8) << endl; // 使用auto是可以的,C++17里面增强auto的功能
        // decltype,可以与auto结合使用来构成返回类型后置语法。
        // 这种后置语法其实也就是使用auto和decltype结合来完成返回值类型的推导。
        cout << _nmsp1::Add3(15, 17.8) << endl;
        return 0;
    }
    
  • 各种推断的比较以及空模板参数列表的推断

    • 自动推断
    • 指定类型模板参数,优先级比自动推断高
    • 指定空模板参数列表<>:作用就是请调用mydouble函数模板而不是调用普通的mydouble函数。
namespace _nmsp1
{
	template <typename T>
	T mydouble(T tmpvalue)
	{
		return tmpvalue * 2;
	}

	double mydouble(double tmpvalue)
	{
		return tmpvalue * 2;
	}
}

int main()
{
    cout << _nmsp1::mydouble(15) << endl;        // 可以推断出T类型是int类型
    int result2 = _nmsp1::mydouble<int>(16.9);   // 显示指定了类型有警告double->int
    auto result3 = _nmsp1::mydouble<>(16.9)      // 这种写法编译器是通过16.9推导是double类型 
    											 // 此种场合下,空的<>没有用处,但语法上允许
    auto result4 = _nmsp1::mydouble(16.9);       // 在模板和普通函数都适合的情况下,编译器会优先调													// 用普通函数
    auto result5 = _nmsp1::mydouble<>(16.9);     // 告诉编译器调用模板函数
    reutrn 0;
}

重载

  • 函数(函数模板)名字相同,但是参数数量或者参数类型上不同

  • 函数模板和函数也可以同时存在,此时可以把函数看成是一种重载,当普通函数和函数模板都比较合适的时候,编译器会优先选择普通函数来执行

  • 如果选择最合适(最特殊)的函数模板/函数,编译器内部有比较复杂的排序规则,规则也在不断更新。

namespace _nmsp1
{
	template<typename T>
	void myfunc(T tmpvalue)
	{
		cout << "myfunc(T tmpvalue)执行了" << endl;
	}

	template<typename T>
	void myfunc(T* tmpvalue)
	{
		cout << "myfunc(T* tmpvalue)执行了" << endl;
	}

	void myfunc(int tmpvalue)
	{
		cout << "myfunc(int tmpvalue)执行了" << endl;
	}

}

int main() 
{
    _nmsp1::myfunc(12); // int,优先普通函数版本
    char*p = nullptr;
    _nmsp1::myfunc(p); // T char* char
    _nmsp1::mufunc(12.1); //T double
}

特化

泛化(泛化版本):大众化的,常规的。常规情况下,写的函数模板都是泛化的函数模板

特化(特化版本):往往代表着从泛化版本中抽出来的一组子集

实例化:tfunc<const char*, int>

namespace _nmsp2
{
	//泛化版本
	template <typename T,typename U>  //T = const char *;U = int
	
    void tfunc(T& tmprv, U& tmprv2)   //tmprv = const char * &,tmprv2 = int &
	{
		cout << "tfunc泛化版本" << endl;
		cout << tmprv << endl;
		cout << tmprv2 << endl;
	}

}

int main()
{
    const char* p = "I love China!";
    int i = 12;
    _nmsp2::tfunc(p, i);  // 输出泛化版本
    return 0;
}
  • 全特化:就是把tfunc这个泛化版本中的所有模板参数都用具体的类型来代替构成的一个特殊的版本(全特化版本)

? 全特化实际上等价于实例化一个函数模板,并不等价于一个函数重载

 void tfunc<int ,double>(int& tmprv, double& tmprv2){......} // 全特化的样子
 void tfunc(int& tmprv, double& tmprv2) { ...... }           // 重载函数的样子

? 编译器考虑的顺序:优先选择普通函数,然后才会考虑函数模板的特化版本,最后才会考虑函数模板的泛化版本

namespace _nmsp2
{
	//泛化版本
	template <typename T,typename U>  // T = const char *;U = int
	void tfunc(T& tmprv, U& tmprv2)   // tmprv = const char * &,tmprv2 = int &
	{
		cout << "tfunc泛化版本" << endl;
		cout << tmprv << endl;
		cout << tmprv2 << endl;
	}

	//全特化版本
	template <> // 全特化<>里面为空
	void tfunc<int ,double>(int& tmprv, double& tmprv2)// <int, double>可以省略,因为根据实参可以完全推导出T和U的类型。
	//void tfunc(int& tmprv, double& tmprv2)  
	{
		cout << "---------------begin------------" << endl;
		cout << "tfunc<int,double>特化版本" << endl;
		cout << tmprv << endl;
		cout << tmprv2 << endl;
		cout << "---------------end------------" << endl;
	}

	//重载函数
	void tfunc(int& tmprv, double& tmprv2)
	{
		cout << "---------------begin------------" << endl;
		cout << "tfunc普通函数" << endl;		
		cout << "---------------end------------" << endl;
	}
}

int main()
{
    int k = 12;
    double db = 15.8;
    _nmsp2::tfunc(k, db); // 执行特化版本,普通函数版本优先
    return 0;
}
  • 偏特化(局部特化)

    从两方面来说:一个是模板参数数量上的偏特化,一个是模板参数范围上的偏特化

    • 模板参数数量上的偏特化:比如针对tfunc函数模板,第一个模板参数类型为double,第二个模板参数不特化;实际上,从模板参数数量上来讲,函数模板不能偏特化。否则会导致编译错误。

    • 模板参数范围上的偏特化:范围上:int->const int,类型变小; T->T*,T->T&,T->T&&针对T类型,从类型范围上都变小了。实际上,对于函数模板来讲,也不存在模板参数范围上的偏特化。因为这种所谓模板参数范围上的偏特化,实际上是函数模板的重载。

    • 通过重载实现模板参数数量上的偏特化

namespace _nmsp2
{
	//泛化版本
	template <typename T,typename U>  //T = const char *;U = int
	void tfunc(T& tmprv, U& tmprv2)   //tmprv = const char * &,tmprv2 = int &
	{
		cout << "tfunc泛化版本" << endl;
		cout << tmprv << endl;
		cout << tmprv2 << endl;
	}

	从模板参数数量上的偏特化
	//template <typename U> // error编译问题,参数数量不能偏特化 
	//void tfunc<double, U>(double& tmprv, U& tmprv2)
	//{
	//	//.......
	//}
	template <typename U>
	void tfunc(double& tmprv, U& tmprv2)
	{
		cout << "---------------begin------------" << endl;
		cout << "类似于tfunc<double, U>偏特化的tfunc重载版本" << endl;
		cout << tmprv << endl;
		cout << tmprv2 << endl;
		cout << "---------------end------------" << endl;
	}


	template <typename T, typename U>
	void tfunc(const T& tmprv, U& tmprv2)
	{
		cout << "tfunc(const T& tmprv, U& tmprv2)重载版本" << endl;
	}
}

int main()
{
    const int k2 = 12;
    _nmsp2::tfunc(k2, db); // tfunc(const T& tmprv, U& tmprv2)重载版本
    return 0;
}
  • 结论:函数模板不能偏特化

后续讲解类模板时,对于类模板,还是存在模板参数范围上的偏特化以及数量上的偏特化,后续会详细讲解。

缺省参数

namespace _nmsp1
{			
	//普通函数
	int mf(int tmp1, int tmp2)
	{
		return 1;
	}
    
	int mf2(int tmp1, int tmp2)
	{
		return 10;
	}
    
	typedef int(*FunType)(int, int); //函数指针类型定义
	
    template <typename T,typename F = FunType>	// FunType类型
	void testfunc(T i, T j, F funcpoint = mf) // 缺省值mf 
	{
		cout << funcpoint(i, j) << endl; // call mf函数
	}

	//------------------
	template <typename T = int, typename U>
	void testfunc2(U m)
	{
		T tmpvalue = m;
		cout << tmpvalue << endl;
	}	
}

int main()
{
    _nmsp1::testfunc(15, 16, _nmsp1::mf2);
    _nmsp1::testfunc2(12); // 12
     
    return 0;
}

非类型模板参数

  • 基本概念

    • 前面的函数模板涉及到 模板参数都是 “类型 模板参数”需要用typename(class)来修饰

    • 模板参数还可以是 “非类型模板参数(普通的参数)”

    • c++17开始,支持非类型模板参数为auto类型。

    指定非类型模板参数的值时,一般给的都是常量

    因为编译器在编译的时候就要能够确定非类型模板参数的值。并不是任何类型的参数都可以作为非类型模板参数。

    int类型可以,但double,float或者类类型string等等可能就不可以,*不过double 这种指针类型可以

    一般允许做非类型模板参数的类型如下:可能不断增加

    • 整型或者枚举型
    • 指针类型
    • 左值引用类型
    • auto或者decltype(auto);
    • 可能还有其他类型,请自行总结;
    namespace _nmsp2
    {
    	template < typename T, typename U ,int val = 100> // 非类型模板参数 int val=100
    	//template < typename T, typename U, auto val = 100> // auto C++17
    	auto Add(T tv1,U tv2)
    	{
    		return tv1 + tv2 + val;
    	}
    
    	//-------------------
    	template <double *p>
    	
        void mft()
    	{
    		cout << "mft()执行了" << endl;
    	}
    	
        double g_d = 1.3; //全局量
    
    	//-------------------
    	//template <typename T,int value>
    	//template <typename, int>
    	template <typename T, typename int value>
    	
        auto Add2()
    	{
    		return 100;
    	}
    }
    
    int main()
    {
        cout << _nmsp2::Add<float, float>(22.3f, 11.8f) << endl;  // 134.1
        cout << _nmsp2::Add<float, float, 800>(22.3f, 11.8f) << endl; // 834.1
        return 0;
    }
    
    
  • 比较奇怪的语法

    • 不管类型还是非类型模板参数,如果代码中没有用到,则参数名可以省略
    • 类型前面可以增加一个typename修饰以明确标识一个类型,一般跟模板有关的类型名前面是需要typename
namespace _nmsp2
{
	//template <typename T,int value>
	//template <typename, int>
	template <typename T, typename int value> // 第一个typename 修饰类型模板参数,第二个		                                               // typename表示后面修饰一个类型
	
    auto Add2()
	{
		return 100;
	}
}
  C++知识库 最新文章
【C++】友元、嵌套类、异常、RTTI、类型转换
通讯录的思路与实现(C语言)
C++PrimerPlus 第七章 函数-C++的编程模块(
Problem C: 算法9-9~9-12:平衡二叉树的基本
MSVC C++ UTF-8编程
C++进阶 多态原理
简单string类c++实现
我的年度总结
【C语言】以深厚地基筑伟岸高楼-基础篇(六
c语言常见错误合集
上一篇文章      下一篇文章      查看所有文章
加:2021-09-13 09:05:31  更:2021-09-13 09:05:48 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年12日历 -2024/12/28 13:39:27-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码
数据统计