我们在C语言的常规编程工作中,经常会遇到因为形参数据类型,而定义多个函数。比如功能交换A和B的值
//int 类型数据交换
void MySwap(int &a, int &b)
{
int temp = a;
a = b;
b = temp;
}
//double 类型数据交换
void MySwap(double &a, double &b)
{
double temp = a;
a = b;
b = temp;
}
只要A和B 这两个数据的类型不同,我就要重定义一个新函数,而这两个函数除了形参数据类型不一样,其他的逻辑都是一样的。这样就造成了代码的重复,增加维护成本。
为了解决这个问题,c++引入了 模板。
1、函数模板的基本语法函数模板使形参类型化,实现定义的时候不关心具体的数据类型,只关心功能的实现。
编译器为了与普通函数区分,使用关键字template。
template
template
template
#include#include #include using namespace std; //template // template //这两种方式都可以 //如果需要多个类型参数的时候,可以增加定义 //template //使用template模板函数的时候,当出现template关键字的时候,只对紧接着出现的函数名生效 void MySwap(T &a, T &b) { T temp = a; a = b; b = temp; } //上面template的作用域消失 int main(void) { int a = 10, b = 20; cout <<"a="<(c, d); cout <<"c="< #include #include #include using namespace std; template T MyAdd(T a, T b) { cout<<"函数模板"< 3、函数模板和普通函数在一起调用的规则 (1)、函数模板可以像普通函数那样被重载
(2)、c++编译器优先考虑普通函数
MySwap(T a, T b); MySwap(int a, int b); 当形参都是int时,优先考虑普通。
(3)、如果函数模板可以产生一个更好的匹配,那么选择模板
(4)、可以通过空模板实参列表的语法限定编译器只能通过模板匹配
MySwap<>(a, b);限定只调用函数模板
4、c++编译模板机制剖析当我们需要编译一个test.cpp的时候,他的一个编译过程大概是这样的。首先预编译器会先将宏定义进行展开,生成test.i文件,此时的这个 .i 文件我们还能看得懂。(通过编辑器打开,可以在最下面发现 宏定义展开后的代码)
g++ -E test.cpp -o test.i之后编译器会将.i文件,进行翻译编译,生成汇编文件test.s,这个时候已经不太能 看得懂了,当然ABCD的字母还是看得懂。
g++ -S test.i -o test.s在之后汇编器,会将.s文件变成二进制文件,也就是目标文件,也就是test.o文件。(在windows下是.obj文件)
g++ -c test.s -o test.o最后到链接器,将很多.o文件和链接库文件,最后都合在一起,生成可执行文件。(linux可执行文件,文件名可自定义。windows下是.exe文件)。
g++ test.s -o a.out函数模板的机制,其实是编译器在编译阶段,对函数模板进行了翻译。将整个工程中,被调用的函数模板翻译成了普通函数。调用了N种数据类型的模板,就会生成N个不同名称的普通函数。
下面我们来验证一下
#include#include #include using namespace std; template //注意没有分号 T MyAdd(T a, T b) { return (a+b); } int main(void) { int nVal1 = 10, nVal2 = 20; char chVal1 = 30, chVal2 = 40; float fVal1 = 6.66, fVal2 = 9.99; MyAdd(nVal1, nVal2); MyAdd(chVal1, chVal1); MyAdd(fVal1, fVal2); MyAdd(nVal1, nVal1); return 0; } 上面这个例子一共调用了四次MyAdd模板,我们对源文件直接进行编译g++ -S test.cpp -o test.s,生成 .s 文件。然后直接打开.s文件进行分析
.s文件还是能让我们稍微看懂一点,但是对与理解原来有很大帮助。我们看到里面有一个熟悉的main函数,在这里面call了四次 MyAdd,我们在.s里也发现了熟悉的MyAdd,只不过名字不太一样,多了一点后缀。
分别叫 细心点可以发现,里面不同的i、c、f、i就是 对应的数据类型的缩写,int、char、float、int。跟我们实际的调用情况也吻合。
5、函数模板案例char,int,float数据排序#include#include #include using namespace std; template void MySort(T *a, int len) { int i=0, j=0; cout<<"排序之前的数据:"< 6、类模板 类模板和函数模板很相似,有一点区别。函数模板在调用的时候,可以自动类型推导。但是类模板必须显示的指定类型。
要注意在类模板派生普通类的时候,子类的定义也要指定数据类型。
类模板派生类模板的时候,如果不指定类型。那么需要再使用template关键字,定义模板类型。
#include#include #include using namespace std; template //注意没有分号 //类模板 class people { public: people(T age) { this->m_Age = age; } public: T m_Age; }; //类模板派生普通类 class student : public people { public: }; //类模板派生类模板 template //注意没有分号 class adult : public people { public: //类模板的,类内实现,需要指定模板类people adult(T age):people (age) { } void show(); }; //类模板的,类外实现,也需要指定模板类people template void adult ::show() { //在类模板中使用成员变量,必须要加上this,或者指定类模板作用域,否则编译报错 // adult ::m_Age cout<<"i am adult, my age is "< m_Age< a1(30); a1.show(); adult a2("30岁"); a2.show(); return 0; } 这里一定要注意,使用类模板的时候,类内定义实现和类外定义实现的区别。其实如果对上面编译器的编译机制理解的比较好的话,就知道在分配内存的时候必要要指定类型,来区分内存大小。
当他是模板的时候,类型不明确。当变量不指定类型或者模板的时候,就更不知道具体的数据大小了。
7、类模板重载操作符



