C# · 12月 20, 2021

C++模板

模板的基本概念与用法。

模板函数

模板函数即就是与类型无关的函数,当需要某种类型的该函数时,编译器就会自动生成这种类型的函数,编写者只需要定义好该函数的处理逻辑即可。

模板函数的格式

template

[返回值] [函数名](…)

{

}

//交换函数

template

void swap(T& a,T& b)

{

T tmp = a;

a = b;

b = tmp;

}

typename是用来定义模板参数的关键字,可以用class替换

模板的原理

在编译器编译阶段,对于模板函数的使用,编译器会根据传入的实参类型来推演生成对应的函数以供调用。

模板的实例化

隐式实例化

隐式实例化即就是编译器根据函数的参数自己去推演应该生成一个什么样的函数。

//隐式实例化的例子

template

T add(T& a,T& b)

{

return a+b;

}

int main()

{

int a = 3;

int b = 5;

int ret = add(a,b);//这就是隐式实例化

return 0;

}

隐式实例化存在的问题:

当推演的参数列表与实际的模板函数的参数列表不匹配时,编译器一般不会进行隐式类型转换操作,所以就会报错。

处理方式:

传参时,用户自行进行强制类型转换,匹配模板函数的参数列表

使用显式实例化

显示实例化

显示实例化就是编写者告诉编译器应该生成一个什么样的函数,不需要编译器再去根据传递的实参的类型进行推演,就是在调用函数处的函数名后面加上指定要实例化出什么样的函数。

//显式实例化的例子

template

T add(T& a,T& b)

{

return a+b;

}

int main()

{

int a = 3;

int b = 5;

int ret = add(3,5);//这就是显式实例化

return 0;

}

显示实例化存在的问题:

当传进函数的参数类型与生成函数的参数类型不匹配时,编译器会尝试进行隐式类型转换,如果无法转换编译器就会报错。

模板函数的匹配规则

一个非模板函数可以和一个同名的函数模板同时存在,而且该模板函数还可以被显式实例化为这个非模板函数。

对于非模板函数和同名函数模板,如果其他条件都相同,在调用时会优先调用非模板函数,而不是用模板函数实例化出一个实例;如果模板函数可以实例化出一个更好匹配的实例,则优先选择模板函数进行实例化。

模板函数不允许隐式类型转换,但普通函数可以进行隐式类型转换

模板类

类模板的定义

template

class [类模板名]

{

}

//类模板定义的例子

template

{

public:

private:

T _data;

}

当在类中的模板函数在类外进行定义时,需要添加模板参数列表

类模板的实例化

类模板与函数模板的实例化不同,可以理解为类模板只可以显式实例化,即就是实例化时需要在类模板名后面加上来表明实例化的类型。类模板的名字不是真正的类,实例化的结果才是真正的类。

非类型模板参数

模板的参数分为两种,一种是类型参数,另一种则是非类型参数。

类型参数:即就是跟在typename/class后代表类型的名称

非类型参数:用一个常量作为类(函数)模板的一个参数,在类(函数)模板中可以当作常量来使用。

浮点数、类对象、字符串不允许作为非模板类型参数

非模板类型参数必须在编译期就能确认结果

模板的特化

函数模板的特化

使用模板可以编写出与类型无关的函数,但某些特殊类型需要使用特殊的处理方法,在这样的场景下我们就可以进行模板函数(类)的特化。

必须要先有一个基础的函数模板

关键字template后加上一对空的尖括号

函数名后加上一堆尖括号括号中指定要特化的类型

函数形参必须要和尖括号中的参数类型完全相同

函数模板的全特化

即指定特定的参数列表进行特化。

//举例说明函数模板的全特化

//编写函数来比较两个对象是否相同,但当两个对象是char*类型的字符串时,这样的比较方式显然是不对的,所以就要对char*类型进行特化

template

bool compare(T a,T b)

{

return a == b;

}

//对的全特化

template

bool compare(char* a,char* b)

{

return strcmp(a,b);

}

函数模板偏(半)特化

函数模板的半特化就是只特化一部分的模板类型;可以看作是对模板类型的进一步限制

//举例说明函数模板的半特化

//编写一个函数来返回两个对象相加的值,但基本模板的返回值是int,如果希望当T1类型为char时,返回值的类型为char则就可以对该模板函数进行偏特化

template

int add(T1 a,T2 b)

{

return a+b;

}

//对模板函数进行偏特化

template

char add@H_418_301@(char a,T b)

{

return a+b;

}

模板类的特化

模板类的全特化

模板类的全特化即就是将模板参数表中的所有参数都确定下来。

//举例说明模板类的全特化

template

class Test

{

test()

{

std::cout << "template" << std::endl;

}

private:

T1 _t1;

T2 _t2;

};

template

class Test

{

public:

test()

{

std::cout << "template" << std::endl;

}

private:

int _t1;

int _t2;

};

int main()

{

Test t1;

Test t2;

return 0;

}

//程序结果:

// template

// template

模板类的偏特化

//举例说明模板类的偏特化

template

class Test

{

test()

{

std::cout << "template" << std::endl;

}

private:

T1 _t1;

T2 _t2;

};

template

class Test

{

test()

{

std::cout << "template" << std::endl;

}

private:

T1 _t1;

T2 _t2;

};

int main()

{

Test t2;

return 0;

}

//执行结果:

// template

// template

模板的分离编译

在写代码的过程中,时常习惯将声明放在xxx.h文件中,而将实现放置在.cpp文件中,但是当我以这样的方式去编写模板代码时,编译器则会报错。

报错原因

一份C++代码生成可执行文件需要经过预处理、编译、汇编、链接四个过程,对于多个文件的项目,编译器会分别对项目中的.cpp文件进行编译并生成目标文件,经过链接之后生成可执行的文件。

在编译阶段,编译器会根据代码进行语法分析、词法分析、语义分析、符号汇总,也就是会对代码中出现的全局变量、静态变量、函数产生对应的符号表。但在编译模板函数的实现文件xxx.cpp时,因为该函数中并没有对模板实例化出实例,所以在进行符号汇总时,并没有产生对应实际存在的模板函数。

此时编译main.cpp,main.cpp中存在对模板函数的实例化以及调用,则在对main.cpp进行符号汇总时,在main.cpp的符号表中一定存在模板函数的实例。

经过上面的过程,模板实现文件xxx.cpp和main.cpp都会生成对应的目标文件,而链接的过程,即就是对目标文件的合并,当在合并main.cpp与模板实现文件xxx.cpp的符号表汇总时,main.cpp中函数模板的实例在模板函数的实现文件xxx.cpp的符号表中并不能找到对应的实例函数,则当然会产生报错,并且错误类型为链接错误。

模板存在的问题

代码膨胀