C++ 泛型(模板)函数

标签(空格分隔): C/C++编程


泛型基础

所谓泛型编程就是类型参数化

  1. 首先我们需要声明一个模板
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. 如果选择普通函数调用,则会发生参数的隐式转换,因此,泛型函数能够给出最优的调用.

模板函数的实现机制

  • 编译器并不是把函数模板处理成能够处理任何类型的函数
  • 函数模板通过具体类型产生不同的函数
  • 两次编译

两次编译
   第一次编译:首先编译器会对函数模板声明的地方进行第一次编译.
   第二次编译:在调用的地方对参数替换后的代码进行第二次编译.

你可能感兴趣的:(C++ 泛型(模板)函数)