跳至主要內容

函数模板与模板函数

张威大约 6 分钟c/c++模板

函数模板与模板函数

为什么要定义模板?

  1. 简化程序,少写代码,维持结构的清晰,大大提高程序的效率。

  2. 解决强类型语言的严格性灵活性之间的冲突。

    1. 带参数的宏定义(原样替换)
    2. 函数重载(函数名字相同,参数不同)
    3. 模板(将数据类型作为参数)
  3. 强类型语言程序设计:C/C++/Java等,有严格的类型检查,如int a = 10,在编译时候明确变量的类型,如果有问题就可以在编译时发现错误,安全,但是不够灵活

  4. 弱类型程序语言设计:js/python等,虽然也有类型,但是在使用的时候直接使用let/var number,不知道变量具体类型,由编译器解释变量类型,属于解释型语言。如果有错,到运行时才发现,虽然灵活,但是不安全。

模板的定义

template <class T,...>
或者
template <typename T,...>

注意:classtypename在此,完全一致。class出现的时间比较早,通用性更好一些,typename是后来才添加了,对于早期的编译器可能识别不了typename关键字。

模板的类型

函数模板类模板。通过参数实例化构造出具体的函数或者类,称为模板函数或者模板类。

函数模板

template <模板参数列表>  //模板参数列表,此处模板参数列表不能为空
返回类型 函数名(参数列表)
 { //函数体  }
template <typename T>//模板参数列表
T add(T x, T y)
{
    cout << "T add(T, T)" << endl;
    return x + y;
}

意义:一种特殊的函数可用,看起来和普通函数很相似,区别是类型可被参数化。

语法规则: template 关键字用于typename 关键字用于 **模板实现原理: 。**但我们实际使用时并不用关注它

注意:

  1. 只用写一套代码实现逻辑,,编译器会从原模版实例化出来。

  2. 一般来说,模板代码====。模板代码调用之前,一定要看到模板定义的地方,这样模板才能够正常实例化。

    1. 分开可以编译,但是在的。

实例化:隐式实例化与显示实例化

//函数的调用点
cout << "add(ia, ib) = " << add(ia, ib) << endl;//隐式实例化,没有明确说明类型,靠
编译器推导
 cout << "add(da, db) = " << add<double>(da, db) << endl;//显示实例化,编译器无序
推导
image-20240411140728256
image-20240411140728256

模板函数:用模板实现的函数

template<typename T>//定义一个模板参数列表 class也可以替换typename
bool compare(T a, T b)//compare 是一个函数模板
{
	cout << "template compare" << endl;
	return a > b;
}
int main()
{
	//函数的调用点
	compare<int>(10, 20);
	compare<double>(10.5, 20.5);
	return 0;
}

//实例化的函数模板,即模板函数
bool compare<int>(int a, int b)
{
	return a > b;
}
bool compare<double>(double a, double b)
{
	return a > b;
}

函数模板、普通函数间的关系

  1. 于函数模板执行

  2. 之间也是

template<typename T>//定义一个模板参数列表 class也可以替换typename
bool compare(T a, T b)//compare 是一个函数模板
{
	cout << "template compare" << endl;
	return a > b;
}
//针对compare函数模板,提供const char*类型的特例化版本
template<>
bool compare<const char*>(const char *a, const char *b)
{
	cout << "compare<const char*>" <<endl;
	return strcmp(a, b) > 0;
}
//普通函数(非模板函数)
bool compare(const char *a, const char *b)
{
	cout << "normal compare" <<endl;
	return strcmp(a,b) > 0;
}
  • 优先调用普通函数,没有的话才去找函数模板,若有模板特例化,优先使用特例化

模板的特化:偏特化与全特化

。针对某些类型,以来编译器自己针对这些类型的模板实例化已经不满足代码的逻辑要求了,我们就针对该该类型提供自己的特例化版本。不是编译器提供的,而是用户提供的。

全特化全部特化出来就是全特化

偏特化只特殊化几个参数或者一定的参数范围

全特化

比较两个字符串的时候使用a>b,只是比较两个字符串地址谁大谁小,没有意义。我们应该比较为它们在ASCII表中的顺序大小。实际应该这样比较:

template<typename T>//定义一个模板参数列表 class也可以替换typename
bool compare(T a, T b)//compare 是一个函数模板
{
	cout << "template compare" << endl;
	return a > b;
}
//针对compare函数模板,提供const char*类型的特例化版本
template<>
bool compare<const char*>(const char *a, const char *b)
{
	cout << "compare<const char*>" <<endl;
	return strcmp(a, b) > 0;
}

int main()
{
	//函数的调用点
	compare<int>(10, 20);
	compare<double>(10.5, 20.5);

	compare(10, 20);
	compare<int>(30, 40.5);
	compare("aaa,=", "ccc");

	return 0;
}

template<>中为空,代表所有类型都在下面特殊化处理,其他类型依然是泛化版本。

/* cout << "add(ia, db) = " << add(ia, db) << endl;//函数模板必须进行严格的
推导,如果没有普通函数形式,这就话就error */

偏特化:

image-20240411151958707
image-20240411151958707

注意区分偏特化和重载

template <typename T>
void tfunc(T& a, double& b)

这个叫做重载。 下面的才是偏特化

template <typename T>
void tfunc<T,double >(T& a, double& b) //error

函数模板的参数类型

类型参数,class T 这种就是类型参数

非类型参数 ,

template <typename T = int, short kMin = 10>
T multiply(T x, T y)
{
    return x * y * kMin;
}
void test()
{
    int ia = 3, ib = 4;
    double da = 3.3, db = 4.4;
    cout << "multiply(ia, ib) = " << multiply(ia, ib) << endl;
    cout << "multiply(ia, ib) = " << multiply<int, 4>(ia, ib) << endl;
    cout << "multiply(ia, ib) = " << multiply<double, 4>(da, db) << endl;
}
$./a.out 
multiply(ia, ib) = 120
multiply(ia, ib) = 48
multiply(ia, ib) = 58.08

成员函数模板

就是类的成员函数也可以设置为模板,可以写例子看看。

class Point
{
public:
    //.............
    //成员函数模板,成员函数模板也是可以设置默认值
    template <typename T = int>
    T func()
    {
        return (T)_dx;
    }
private:
    double _dx;
    double _dy;
};
void test()
{
    Point pt(1.1, 2.2);
    cout << "pt.func() = " << pt.func<int>() << endl;
    cout << "pt.func() = " << pt.func<double>() << endl;
    cout << "pt.func() = " << pt.func() << endl;
}