标签(空格分隔): C/C++编程
泛型基础
所谓泛型编程就是类型参数化
- 首先我们需要声明一个模板
template
template
在声明参数类别上,两者完全一致,根据个人喜好选择即可,在使用嵌套类型上只能使用typename
//告诉编译器下面紧跟着的代码里如果出现了T类型时,不要编译报错,T是一个通用的类型
template
void swap(T &a, T &b) {
T temp;
temp = a;
a = b;
b = temp;
}
//下面是错误的,T不识别,注意泛型的声明之后,是紧跟着的,此处的已经不能用了
/*
void swap2(T &a, T &b) {
T temp;
temp = a;
a = b;
b = temp;
}
*/
int main(){
int a = 10, b = 20;
char c = 'c';
//调用的时候,会发生自动类型推导
swap(a, b); //正确
//编译错误,因为编译器在自动类型推导时,需要一个swap(int a,char c);
//但是却没有发现这个函数声明或者定义,因此,编译失败
swap(a,c);
}
当编译器在调用模板函数时,会进行自动类型推导,你应该让编译器能正确的推导出类型,否则将会编译不通过。
当然我们在调用时,可以为泛型显式的指定类型如下:
int main(){
int a = 10, b = 20;
char c = 'c';
//显式的指定泛型为int类型
swap(a, b);
swap(a,c);//编译错误
}
小结一下: 我们在调用泛型函数时,在编译时,必须要让编译器知道,这个泛型到底是什么类型。
不管是编译器自己可以推导出,还是程序员调用时显式指定,必须要让编译器正确的知道,因为函数在调用时,编译器需要为这个类型分配内存空间。
我们来看下面一个例子
void func() {
}
void func() {
}
当我同时定义了两个一样的函数时,此时编译器是编译不通过的,因为在函数调用时,编译无法区分用户到底应该调用哪一个函数。
加上泛型会如何呢?
void func() {
std::cout << "非泛型函数" << std::endl;
}
template
void func() {
std::cout << "泛型函数" << std::endl;
}
结果可以编译通过,根据之前未加泛型时无法编译通过的原因,我们可以肯定,加上了泛型,使编译器可以区分两个函数是两个不同的函数,在调用时,有区分的依据可以让用户区分两个函数。
如何区分泛型调用呢
我们来看看调用
func(); //调用的是非泛型
//需要任意指定一个泛型类型参数
func()//调用的是泛型函数
有人就要问了,因为两个函数名字相同,我需要加上泛型参数区分,那么如果名字不同是不是就可以了呢?我们来看看
template
void func() {
std::cout << "泛型函数" << std::endl;
}
int main(){
func();//编译失败,类型不匹配
func(); //成功
}
我们看到我们似乎必须要显式的加上泛型参数似乎才能调用成功,但是对于我们开篇的例子并没有显式的指定也可以调用成功啊,是这样的吗?
我们在看一个例子
template
void func(int x,int y) {
std::cout << "泛型函数" << std::endl;
}
int main(){
int a = 10 , b = 10;
func(a,b);//编译失败,类型不匹配
func(a,b); //成功
}
结论:我们在调用泛型函数时,必须要让编译器知道我们的泛型是什么类型,因为在编译时,我们需要为其分配内存空间,当函数在传递参数时,函数可以隐式的推导出泛型的类型,我们不需要指定,但是当无法隐式推导出时我们必须要指定其泛型的参数类型,虽然函数参数没有使用泛型类型,但是函数实现却可能使用,因此编译器要保证一切可能的发生情况.
建议:我们在使用泛型函数的时候,最好都显式指定泛型类型
函数参数的隐式类型转换
int plus1(int a, int b) {
return a + b;
}
int plus2(int &a, int &b) {
return a + b;
}
void test(){
int a = 10;
char b = 'c'
//发生了隐式类型转换
int sum = plus1(a,b); //正确,结果是109
int sum2 = plus2(a , b);// 错误,引用不会发生类型转换
}
从上面的案例我们可以知道C++
的所谓的隐式类型转换,实际上是利用了下面的原理
char c = 'c';
int a = c ; //可以直接转换
int b = 500;
char = b; //截取低8位
因此函数参数的隐式转换实质上与赋值操作类似,而引用却不能进行转换.
泛型函数可以发生隐式转换吗
template
int plus(T a, T b) {
std::cout << "泛型函数调用" << std::endl;
return a + b;
}
void test(){
int a = 10;
char b = 'b'
plus(a,b)//编译无法通过
plus(a ,b)//正确
}
结论:对于隐式调用的函数,由于其首先进行类型推断,因此不会发生隐式转化,当我们显式指定其泛型类型时,其可以发生隐式转换.其实当我们显式的指定了泛型之后,就可以按着普通函数的语法进行判断了.
函数重载遇上泛型函数
template
int plus(T a, T b) {
std::cout << "泛型函数调用" << std::endl;
return a + b;
}
int plus(int a, int b) {
std::cout << "普通函数调用" << std::endl;
return a + b;
}
void test(){
int a = 10 , b = 20;
char c = `c`;
plus(a,b); //普通函数调用
plus(a,b)//泛型函数调用(建议)
plus<>(a,b)//泛型函数调用,不必指明任何函数类型,但是明确告诉编译器我要泛型函数调用
}
结论:当泛型函数遇上函数重载时,如果我们没有显式的指明泛型调用,则调用的是普通函数,如果我们显式的指明了泛型调用,则调用的是泛型函数.我们可以这样理解,编译器总是选择最合适的函数调用,没有指明泛型的类型,编译还要做隐式推导,显然这个是不明智的,既然已经有了一个合适的函数可用,为什么要多此一举呢,但是当我们指定了泛型之后,其实就是明确的告诉编译器,我要调用的是泛型函数,这时编译器就没有其他的选择了,只能调用泛型函数,这里我们可以看出,显式的指定泛型类型,也有明确告诉编译器我要调用的泛型函数的意思.
如果函数模板可以产生更好的匹配优先选择函数模板
template
int plus(T a, T b) {
std::cout << "泛型函数调用" << std::endl;
return a + b;
}
int plus(int a, int b) {
std::cout << "普通函数调用" << std::endl;
return a + b;
}
void test(){
int a = 10 , b = 20;
char c = `c`;
char d = 'd';
plus(c,d);//泛型函数调用,不会调用普通函数
}
之所以选择泛型函数调用,原因有有点:1. 泛型函数可以自动推导出泛型的参数类型是char
类型. 2. 如果选择普通函数调用,则会发生参数的隐式转换,因此,泛型函数能够给出最优的调用.
模板函数的实现机制
- 编译器并不是把函数模板处理成能够处理任何类型的函数
- 函数模板通过具体类型产生不同的函数
- 两次编译
两次编译
第一次编译:首先编译器会对函数模板声明的地方进行第一次编译.
第二次编译:在调用的地方对参数替换后的代码进行第二次编译.