简介:C++函数模板的作用就是按照程序员的要求生成想要的函数对象。本质上是一种函数声明,在程序运行时依靠指定的参数类型由编译器临时生成函数对象。
1、auto自动类型推导
auto关键字可以取代变量声明时的函数类型,其实实际上会由编译器帮你把auto换成匹配的数据类型。
#include<iostream>
using namespace std;int* get_a_arr(){int* arr= new int(10);return arr;
}int main(){auto a=3;auto b=3.14;auto arr = get_a_arr();cout<<"值是:"<<a<<endl<<"类型是:"<<sizeof(a)<<endl;
}
auto的注意点:
- auto关键字修饰的变量的右值必须是类型确定的表达式(需要根据表达式进行类型推断)
- auto关键字修饰的变量必须初始化(不声明的话不知道类型)
- auto不能修饰函数的形参变量(会出现推断二义性)
- auto不能直接修饰数组变量(会出现推断二义性)
- auto不能修饰静态变量(会出现推断二义性)
auto的正确用法:
- 用于自动推导复杂的数据类型(例如自动推导函数指针类型)
- 用于自动推导函数返回类型
#include<iostream>
using namespace std;int* get_a_arr(){int* arr= new int[10];for(int i=0;i<=10;i++)*(arr+i)=i;return arr;
}int main(){auto pf=get_a_arr;int* arr = pf();for(int i=0;i<=10;i++)cout<<*(arr+i)<<endl;
}
2、函数模板
函数模板将函数所用到的数据类型参数化,形成一个函数声明。可以自定义类型的数据:函数形参的数据类型、函数返回值的数据类型、栈区里面由函数临时生成的数据类型。
具体的函数对象由传递过来的具体数据类型进行确定之后再生成。传递的给模板的数据类型可以手动指定也可以由编译器自动推导(根据auto推导)。由此会产生下面的问题:
- 这个由函数模板生成的函数对象很有可能与普通函数对象长得一样,此时编译器会优先调用普通函数。
- 函数模板会生成多个同名函数,那么这些函数由于参数类型不同、个数不同,从而构成函数重载。
- 当模板和普通函数优先级一样时,可能需要强制调用模板函数;
- 生成函数对象之前,需要明确地知道参数的类型
函数模板的语法形式如下:template<typename anytype1,typename anytype2,……>
#include<iostream>
using namespace std;template <typename T>
void swap(const T &var1,const T &var2){T temp = var1;var2=var1;var1=temp;
}int main(){int i1=1,i2=2;swap(i1,i2);//编译器自动推导类型cout<<i1<<";"<<i2<<endl;float f1=1.1,f2=2.2;swap<float>(f1,f2);//手动指定数据类型cout<<f1<<";"<<f2<<endl;}
3、函数模板的注意事项
3.1 类成员函数可以加上模板
本质就是将普通函数放到类里面。构造函数、成员方法都可以改成模板函数使用,原因就是类就是一个结构体,存在一个函数指针引用着生成的函数对象,这个函数对象可以临时生成。
#include<iostream>
using namespace std;class Cgirl {public:template <typename T>Cgirl(T info) {cout << info << endl;}Cgirl() {}template <typename T>void showinfo(T info) {cout << info << endl;}};int main() {Cgirl c;c.showinfo(18);c.showinfo("mmd");}
3.2 需要明确模板形参类型
不管是自动类型推导,还是手动指定类型,都需要将模板声明中的类型进行赋值,这样才能没有二义性的生成一个函数对象。
情况2:
3.3 函数模板的代码逻辑必须满足传递的数据类型
如果给函数传递的是自定义数据类型,也就是类型对象,那么函数的代码逻辑运算必须满足该自定义数据类型。
3.4 模板函数的自动数据类型转换
由于类型可以自动auto推导也可以手动指定,然而自动推导存在二义性,比如一个字符串可以是char*类型,也可以时string类型,故所有自动推导的函数模板不存在类型自动转换。比如char向int再向float的转换。
但是手动指定模板类型之后,就可以发生自动类型转换。
3.5 模板类型和具体数据类型混用构造重载
模板类型之间是互异的,就构成了参数类型不一致,也就是可以构成重载。
#include<iostream>
using namespace std;template <typename T, typename T2>
void showinfo(T info1, T2 info2) {cout << info1 << endl;cout << info2 << endl;
}template <typename T>
void showinfo(T info1) {cout << info1 << endl;
}template <typename T>
void showinfo(T info1, char* info2) {cout << info1 << endl;cout << info2 << endl;
}int main() {showinfo(10, 12.0);showinfo(10);showinfo(10, "mmd");
}
4、函数模板的具名化
由于自定义数据类型的存在,对对象的操作就比较特殊,可以在声明模板时指定数据类型,使其变得普通函数类似。
#include<iostream>
using namespace std;class Cgirl {public:int rank;
};template <typename T>
void Swap(T &p1, T &p2) {T temp = p1;p1 = p2;p2 = temp;
}template<>
void Swap<Cgirl>(Cgirl &p1, Cgirl &p2) {int temp = p1.rank;p1.rank = p2.rank;p2.rank = temp;
}int main() {Cgirl p1;p1.rank = 1;Cgirl p2;p2.rank = 2;cout << p1.rank << "--" << p2.rank<<endl;swap(p1,p2);cout << p1.rank << "--" << p2.rank;
};
当普通函数、模板函数、具体化模板函数都可以匹配某个函数调用时,优先级为普通函数-->具体化函数-->模板函数。
5、函数模板的分文件编写
目前存在:普通函数、具体化模板函数、模板函数。实际上,普通函数、具体化模板函数是等价的,也就是具体化模板函数就可以看作是普通函数。
那么在头文件当中具体化模板函数和普通函数只能写上声明,具体实现需要在源文件里面书写。
模板函数只是一种声明,他的整段代码都只能写在头文件里面当中一个函数声明。
6、模板函数里面的类型转换
一个函数模板可能会存在多个数据类型参数,当这些数据发生运算时就会生成新的数据类型,如何自动推断这些数据的类型呢?如果需要返回这个新的数据类型数据又该如何操作呢?可以使用decltype自动推导类型。
语法:decltype(expression) var。decltype(expression)的推导结果就是var的数据类型。
- 如果expression是没有用括号括起来的标识符,则var的类型与该标识符的类型相同,包括const等限定符。
#include<iostream>
using namespace std;int main() {
//语法:decltype(expression) var;
//1)如果expression是没有用括号括起来的标识符,则var的类型与该标识符的类型相同,包括const等限定符,
int a=1;
int b=2;
int &ra=a;
decltype(ra) da=b;
cout<<da;
};
- 如果expression是函数调用,则var的类型与函数的返回值类型相同(函数不能返回void,但可以返回void*)。
- 如果expression是左值(能取地址)、或者用括号括起来的标识符,那么var的类型是expression的引用。
- 如果上面的条件都不满足,则var的类型与expression的类型相同。
总结:var要么是引用,要么时expression的类型。而且函数返回类型可以直接使用auto进行推断。