C++模板编程与泛型编程之函数模板(一)

C++模板编程与泛型编程之函数模板

  • 引例:

    • 写一个两数相减的函数
int Sub(int tv1, int tv2)
{
	return tv1 - tv2;
}

float Sub(float tv1, float tv2)
{
	return tv1 - tv2;
}
  • 不难发现,函数体相同(算法相同),只是参数类型不同
//template <typename T> 
template <class T>      
                        
T Sub(T tv1, T tv2)
{
	return tv1 - tv2;
}
//调用
int subv = Sub(3, 5); 
int subv = Sub(6.3, 5.9);
  • T:称为类型模板参数,代表的是一个类型
  • class 可以取代typename但是这里的class并没有“类”
  • T这个名字可以任意起
  • 定义:

    • 模板的定义是以template关键字开头。
    • 类型模板参数T前面用typename来修饰,所以,遇到typename就该知道其后面跟的是一个类型。
      • typename可以被class取代,但此处的class并没有“类”
    • 类型模板参数T(代表是一个类型)以前前面的修饰符typename/class都用<>括起来
    • T这个名字可以换成任意其他标识符,对程序没有 影响。用T只是一种编程习惯。
  • 如果对上述模板采用如下调用
string a("abc"), b("def");
string addresult = Sub(a, b);
  • 编译器会报错,string类型不支持减法。
  • 可以发现模板在实例化编译阶段编译器就能根据类型判断是否支持减法操作。

实例化

  • 因为有调用的情况下,就会对模板进行实例化
  • 实例化:编译时,用具体的“类型”代替“类型模板参数”的过程就叫做实例化(有人也叫代码生成器)。
int subv = Sub(3, 5); 
int subv = Sub(6.3, 5.9);
  • 上述调用,会生成两份subv代码。
  • 查看生成的两份代码,打开VS开发工具

  • 找到源程序编译生成的.obj文件(工程Debug目录下),通过开发工具进入到这个目录下。
    • .obj文件在编译完成后就会产生
    • .obj文件的格式一般会被认为是一种COFF——通用对象文件格式(Common Object File Format)。
  • 执行dumpbin  /all   xxx.obj   >   xxx.txt

  • 将生成的.txt文件拖到VS中,然后Ctrl + F 搜索Sub

  • 实例化之后的函数名分别叫做Sub<int>和Sub<double>
  • 通过函数模板实例化之后的函数名包含三部分
    • 模板名;
    • 后面跟着一对<>;
    • <>中间是一个具体类型
  • 注意到
string a("abc"), b("def");
string addresult = Sub(a, b);
  • 如果函数模板函数体是减法,这段代码报错。当把函数模板改为加法再次编译就不报错。
template <class T>      
                        
T Sub(T tv1, T tv2)
{
	return tv1 + tv2;
}
  • 编译阶段编译器就会查看函数模板的函数体部分,来确定能否针对该类型string进行Sub函数模板的实例化
  • 上述实参类型相同,因为模板形参定义都是T类型的,如果传入的两个实参类型不相同,编译器就会报错。
  • 编译器进行函数模板类型推断过程中是不能进行自动类型转换的,要求精确匹配
  • 在推导前强制类型转换,编译不报错
double subv = Sub(double(3), 5.9);
  • 或者直接指定类型,编译器将3视为double
double subv = Sub<double>(3, 5.9);

 

模板参数的推断

template <typename T,typename U,typename V>

V Add(T tv1, U tv2)
{
    return tv1 + tv2;
}
//调用
cout << Add(15, 17.8) << endl;
  • 编译器报未匹配到重载函数的错误。
  • 编译器能推导出T,U,但是V是函数体内执行后的返回类型,编译器没法根据运行时的代码结果来推断出类型
  • 推断不出来,我们可以通过尖括号进行指定
  • 编译器不能跳过T,U直接指定V。下述代码是错误的。
cout <<Add<...,...,double>(15, 17.8) << endl;
  • 正确的如下
cout << Add<int,double,double>(15, 17.8) << endl
  • 或者做如下尝试,改变类型参数顺序,只指定一部分模板参数的类型,另外一部分模板参数的类型可以通过调用时给的实参来推断。
template < typename V,typename T, typename U>
V Add(T tv1, U tv2)
{
	return tv1 + tv2;
}

cout << Add<double>(15, 17.8) << endl;
  • auto代替函数模板返回类型,利用auto特性
template <typename T, typename U>
auto Add(T tv1, U tv2) //auto用于表达推导返回值类型的含义。
{
	return tv1 + tv2;
}

cout << Add(15, 17.8) << endl
  • decltype,可以与auto结合使用来构成返回类型后置语法。这种后置语法其实也就是使用auto和decltype结合来完成返回值类型的推导。
  • 返回类型后置语法,这里的auto只是返回类型后置语法的组成部分,并没有类型推导的含义
template <typename T, typename U>
auto Add(T tv1, U tv2) -> decltype(tv1 + tv2) 
{
	return tv1 + tv2;
}
  • 各种推断的比较以及空模板参数列表的推断
template <typename T>
T mydouble(T tmpvalue)
{
	return tmpvalue * 2;
}
  • 自动推断
cout << mydouble(15) << endl; //可以推断出T类型是int
  • 指定类型模板参数,优先级比自动推断高
int result2 = mydouble<int>(16.9)  //32
  • 指定空模板参数列表<>:
    • 编译器依旧自行推断,下面推断出类型为double。
    • 此种场合下,空的<>没有用处,但语法上允许
auto result3 = _nmsp1::mydouble<>(16.9);  //33.8
cout << result3 << endl;
  • 既然空的<>没有用处,意义何在?
  • 如果还有一个函数如下:
double mydouble(double tmpvalue)
{
	return tmpvalue * 2;
}
  • 如下函数调用的普通函数,不是去调用函数模板
auto result4 = mydouble(16.9); 
  • 对于编译器,模板也合适,普通也合适,编译器会优先调用普通函数
  • 如果想调用函数模板,怎么办?
    • 这时候就使用<>,<>作用就是告诉编译器,调用mydouble函数模板
auto result3 = mydouble<>(16.9); 

猜你喜欢

转载自blog.csdn.net/baidu_41388533/article/details/106865436