网站首页 综合导航 全能搜索 网站地图 联系我们

首页 > 教程 > 模板知识

模板基础知识之函数模板

  • 分类 : 模板知识
  • 编辑 : 大宝123
  • 发布 : 10-27
  • 阅读 :86

一、简介

该篇主要内容为介绍函数模板以及函数模板的基本用法。

二、编程环境

系统:Windows11
IDE:Visual Studio2022
项目名字:funcTemp

三、内容

1.基本范例

1.1函数模板的作用

举例说明:编程过程中,数据类型千奇百怪,仅表示数字的常见的就有int,float,double等多种类型。这些数字在数学中,都可以进行相加,相减,相乘,相除,多次方运算等等多种运算模式。但是在计算机中,并不能一概而论,每种不同的数据类型,计算机都很较真的做了区分,int和int进行计算,double和double进行计算。拿减法来说,就会产生以下的代码。

int Sub(int a, int b)
{
    return a - b;
}
double Sub(double a, double b)
{
    return a - b;
}

如上面代码所示,一个减法,却要写出两个函数,如果是更复杂的运算,函数内容更多,几十行甚至上百行代码,达成同一个目的,仅仅因为类型不同,就需要重写一遍函数,就会造成代码冗余,不好看,使得工程变得非常庞大。为了解决这个问题所以我们需要模板。

1.2函数模板的举例

接着上面的例子,减法运算,我们可以用一个函数解决

template <typename T>
T Sub(T a, T b)
{
    return a - b;
}

调用时,类型T会被编译器自动的,识别为所给参数的类型。
调用举例

int y = Sub(5, 3);

调用验证,使用VS自带的工具,搜索Developer Command Prompt for vs2022,打开后进入命令行,进入到工程编译的文件夹路径下。会生成一个funcTemp.obj的文件。使用dumpbin命令来验证调用过程中,编译器自动识别类型T的正确性

dumpbin /all funcTemp.obj > funcTemp.txt

就会在该文件夹下生成一个funcTemp.txt文件。查看funcTemp.txt文件。搜索函数名,就可以看到,sub()函数,自动的把T类型识别为了int类型

COMDAT; sym= "int __cdecl Sub<int>(int,int)" (??$Sub@H@@YAHHH@Z)

1.3函数模板说明

1.template :表示函数模板的开始,是C++中的关键字
2.typename:typename修饰的是类型T,语法规定的写法。也可以用class代替,不过我的习惯是函数模板中用typename,在类模板中用class。
3.T:T表示一个类型,可以是任意的,也可以是X,Y,L等等
4.函数体中,与普通函数类似,只需要主要如果是T类型的,就一定写T就好。

template <typename T>                       
T Sub(T a, T b)
{
    return a - b;
}

1.4多个类型的函数模板使用

上面介绍的内容中,T要么是指代了int要么是指代了double,如果是int-double呢,或者int+double+int呢。所以函数模板也可以“传入多个T类型”。简单的例子如下。

template <typename T,typename U,typename K>                       
T Sub(T a, U b, K c)
{
    return a - b - c;
}

2.模板函数的重载

普通函数可以重载,只要参数数量不同,参数类型不同,即可完成重载。与函数重载非常类似。下面用代码说明。

template<typename T>
void myFunc(T a)
{
    std::cout << "myFunc(T a) is done" << std::endl;
}

template<typename T>
void myFunc(T* a)
{
    std::cout << "myFunc(T* a) is done" << std::endl;
}

上面模板函数完成了重载,第一个传入的是T类型的参数,第二个传入的是T*类型参数。参数类型不同,就是一个重载的(模板)函数。

3.特化

3.1.全特化

模板函数属于是泛化,具有不确定性,广泛性的模式,与之相反的,全特化,就是确定的,指定的模式。
写一个模板函数

template<typename T,typename U>
void myFunc(T* a , U& b)
{
    std::cout << "myFunc(T* a , U& b) is done" << std::endl;
}

这里的T与U都是不确定的,在调用的时候,编译器自动编译确定类型。
下面写一个全特化版本

template<>
void myFunc(char* a , int& b)
{
    std::cout << "myFunc(char* a , int& b) is done" << std::endl;
}

这里注意看,<>里面是空的!,函数体里确定了,第一个参数是char*类型,第二个参数是int& 类型。但是要知道的是,看起来挺像重载,但记住,全特化不等于重载。全特化只是实例化了一个函数模板。

3.2.偏特化

在模板函数中,偏特化并不方便实现,只能通过重载的方式来实现,但是很少会在实际开发中使用到,这里不过多说明(真的用不到,类里面才会比较方便使用)。

四、注意点

1.不要让模板函数有不确定的类型

注意看以下代码。

template <typename T,typename U,typename V>
V Add(T a, U b)
{
    return a + b;
}
int main()
{
    std::cout << Add(2 , 3.5) << std::endl;
    return 0;
}

编译时会报错,原因是返回类型V并不能被编译器推断出来。改进的方法有两种,我比较倾向于第二种。
方法一:指定返回值类型,并且将返回值类型放到第一位,注意对比上面的代码,V被放到了第一位,调用的时候也显式的指出了V的类型。

template <typename V,typename T,typename U>
V Add(T a, U b)
{
    return a + b;
}
int main()
{
    std::cout << Add<double>(2 , 3.5) << std::endl;
    return 0;
}

方法二:使用auto关键字。这样返回值类型就不用管传入的类型,让编译器自己去推断。会更加方便,我比较倾向于这种方式。

template <typename T,typename U>
auto Add(T a, U b)
{
    return a + b;
}
int main()
{
    std::cout << Add(2 , 3.5) << std::endl;
    return 0;
}

2.使用隐式转换,改变传入的参数

在一些开发过程中,传入的参数可能不是我们想要的参数,总会多一些烦人的小数点,我们可以用隐式转换,改变传入的参数类型,下面函数,传入的类型为double类型,我们不想要2.3,我们只要2,就必须显式的指出传入类型。

template < typename U>
auto MySquare(U a)
{
    return a * a;
}
int main()
{
    std::cout << MySquare(2.3) << std::endl;
    std::cout << MySquare<int>(2.3) << std::endl;
    return 0;
}

3.函数调用的优先级

一个程序中,可以有函数模板,可以特化,可以有功能相同的普通函数,那么编译器会优先调用哪一个类型的函数呢?

//模板函数
template <typename T>
void Temp(T a)
{
     std::cout << "temp  is run" << std::endl;
}
//全特化
template <>
void Temp(int a)
{
     std::cout << "spacial temp is run" << std::endl;
}
//功能相同的普通函数
void Temp(int a)
{
     std::cout << "general func  is run" << std::endl;
}

int main()
{
    Temp(8);
    return 0;
}

看结果,是普通函数被调用了,我们把普通函数屏蔽了之后,是特化的模板函数被调用了。所以可以得出结论,函数调用的顺序是普通函数 > 特化的模板函数 > 泛化的模板函数

五、总结

模板函数的基础使用方法,基本上就这么多了,如果有错误欢迎指正。这是一些看王建伟老师编写的《C++新经典 模板与泛型编程》*一书过程中的理解。欢迎交流。



作者:唐予清
链接:https://www.jianshu.com/p/6335ed2ff0a4
来源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。