C++:函数————函数重载与函数模板

来源:互联网 发布:千里眼淘宝版下载 编辑:程序博客网 时间:2024/06/05 22:32

一、函数重载


1.概念

C语言中不允许定义多个函数名相同的函数,但是在C++中,只要函数的特征标(即函数的参数列表)不同,即使函数名相同也是可以定义的,这种方式被称为“函数重载”。(C++允许通过函数重载设计一系列的函数——它们完成相同的工作但是使用不同的参数列表。)

2.函数重载的规则

  >1:定义时要注明参数的类型:

void print(const char * str);void print(const double value);void print(const long value);

 >2.如果调用的函数没有对应的原型的时候,C++不会停止调用,而是尝试自动转换

#include <iostream>#include <string>using namespace std;void print(const char * str);void print(const double value);void print(const long value);int main(){    unsigned int year = 2015;    print(year);    return 0;}

在这段代码中,year是无符号整型,它没有对应的函数原型,于是系统将其强制转换。

但是不巧的是,它既可以被强制转换为double型,也能被强制转换为long型,因此编译器判定这是:

error: call of overloaded 'print(unsigned int&)' is ambiguous|(二义性的)

但是如果print(double)与print(long)去掉任意一个,编译器将unsigned int提升转换,编译可以通过。

>3.类型的引用和类型本身视为同一种特征标

void print(const long value);void print(const double & value);
这种情况下无法共存,因为当存在形如print(5.0)的调用时,编译器不知道到底调用的哪个print,同样是二义性的定义错误。

>4.存在const限定符的特征标

匹配函数时,并不区分const与非const变量

void print1(const int value);void print2(int value);int main(){    const int value1 = 50;    int value2 = 30;       print1(value1);    print1(value2);    print2(value1);    print2(value2);    return 0;}
上述代码是可以通过编译的,但是如果const修饰的是指针或者是引用时,上述代码就无法通过。

void print1(const char * value);void print2(char * value);int main(){    const char* value1 = "testone";    char* value2 = "testtwo";    print1(value1);    print1(value2);    print2(value1);    print2(value2);    return 0;}

这种情况下编译器会报错,print2(char*)只能与非const类型匹配,但是print1(const char * )既能与const类型匹配也可以和非const类型的匹配。造成这种不同的原因在于,将非const(指针或者引用)值赋值给const是合法的,但是将const值赋给非const的值是非法的(这个地方有点绕,但的确是合乎规范的)

>5.重载引用参数

类设计和STL中经常用到引用,因此知道不同引用类型的重载很有必要。

二、函数模板

函数的模板是通用函数描述,使用泛型定义一个模板,其中的泛型可以用具体的类型进行替换。下面给出函数模板的创建与使用方法。

1.建立一个模板

template <typename AnyType>void Swap(AnyType &a,AnyType &b){    AnyType temp = a;    a = b;    b = temp;}
这里建立了一个Swap模板,再次强调它不是一个具体的函数,使用这个模板可以交换基本上所有类型的数据。

第一行,声明:

template <typename AnyType>

表示建立一个模板并将类型的名称定义为Anytype,其中template和typename都是关键字必须的。不过更一般的情况下,我们使用下面这种方式。

template<class T> 

那么上述代码可改写为:

template <class T>void Swap(T &a,T &b){    T temp = a;    a = b;    b = temp;}
在程序中,可能有不同类型的数据来调用Swap()这个模板,每当具体的数据类型的变量调用该模板时,编译器自动地生成Swap()函数的相应数据类型的版本

2.函数模板的重载:

同函数重载一样,只要特征标不同即可实现对模板的重载,如Swap()模板,想要对数据进行交换的换,可以重载为以下形式:

template <class T>void Swap(T a[],T b[],int n){    T temp;    for(int i = 0 ; i < n ; ++i){        temp = a[i];        a[i] = b[i];        b[i] = temp;    }}
再次说明,模板的参数可以是明确的数据类型而非泛型,如int n

3.函数模板的显示具体化

之前编写的模板可能无法处理某些类型的数据(比如说比较大小的模板,当比较结构体时可能就会力不从心),我们能想到的是,为特定的类型提供具体化的模板定义,也就是这里所说的显示具体化。当编译器找到与函数调用匹配的具体化定义时,不再寻找模板。

为了更方便的说明,我们举一个例子:

假如说存在一个结构体:

typedef struct{    string name;    string ID;    int grade;}Student;
我们只想交换两个学生的ID数据域,那么可以对template进行如下具体化实现:

template <> void Swap<Student>(Student &a,Student &b){    string temp = a.ID;    a.ID = b.ID;    b.ID = temp;}
其中Swap<Student>中的<Student>是可选的,因为函数的参数类型表明,这是Student类型的一个具体化,所以也可以这样编写:

template <> void Swap(Student &a,Student &b){    string temp = a.ID;    a.ID = b.ID;    b.ID = temp;}
所以,该例的完整程序可如下编写:

#include <iostream>#include <string>using namespace std;typedef struct{    string name;    string ID;    int grade;}Student;template <class T> void Swap(T &a,T &b);//如同函数一样先声明,不定义,在main函数的后面定义,方便定义多个模板template <> void Swap(Student &a,Student &b);int main(){    Student s1 = {"Zhangsan","100000",90};    Student s2 = {"Lisi","200000",70};    Swap(s1,s2);    cout << "s1:" << s1.name << " " << s1.ID << " " << s1.grade << endl;    cout << "s2:" << s2.name << " " << s2.ID << " " << s2.grade << endl;    return 0;}template <class T>void Swap(T &a,T &b){    T temp = a;    a = b;    b = temp;}template <>void Swap(Student &a,Student &b){     string temp = a.ID;     a.ID = b.ID;     b.ID = temp;}
PS:更加推荐这种编写方式,简单规范。
需要强调的是,必须首先定义模板才可以对模板进行显式具体化。

4.函数模板的实例化与具体化

为了更好的理解模板,必须理解术语实例化与具体化。

再次强调(因为很重要要说好几遍!):定义模板的时候并不会生成函数的定义,只是提供一个生成函数定义的方案。

编译器使用模板为特定的数据类型生成函数定义时,得到的是模板实例。

这种借由编译器自动生成函数定义的方式,被称为隐式实例化。(因为没有手动的去给定实例化的规则所以叫隐式)


简而言之,模板+自动匹配的具体的数据类型 = 隐式实例化


除了隐式实例化,C++还提供显式实例化,这意味着可以直接命令编译器创建特定的实例。显式实例化的创建规则如下:

template void Swap<Student>(Student &a,Student &b);
这个格式同显式具体化的格式十分相似:

template <> void Swap(Student &a,Student &b);//或下行template <> void Swap<Student>(Student &a,Student &b);
因此很容易被混淆。

>1.显式实例化与显式具体化的不同
不同之处在于,编译器看到某个具体类型的显式实例化后,将利用模板定义生成Swap()函数的具体类型的版本而不需要为此重新定义Swap()的规则。显式具体化则是为了某些原来模板不能处理的类型重新定义了Swap()的规则。

>2.显式实例化与隐式实例化的不同

那么已经存在隐式实例化的机制下我们为什么要使用显式实例化呢?让编译器自己找不就可以了么?

第一点不同:当程序调用模板函数时,如果存在显式实例化的类型匹配,那么编译器优先遵循显式实例化的规则。因此编译速度稍快一些。

第二点不同:

如果遇到以下情况,那么显式实例化就起到了很大的作用:

template <class T> T Add(T a,T b){    return a + b;}int main(){    int x = 10;    double y = 8.2;    cout << Add<double>(x,y) << endl;    return 0;}
这里的模板实际上与函数调用的Add(x,y)是不匹配的,因为x、y一个为int一个为double,但是模板定义的Add中的两个参数要求类型一样(均为泛型T),此时就可以用Add<double>(x,y)的形式将其强制为double类型实例化。

但是如果对Swap()类型做类似的处理时不行的,即:

template <class T> void Swap(T &a,T &b);int main(){    int x = 10;    double y = 8.2;    cout << Swap<double>(x,y) << endl;    return 0;}
同样强制生成一个double类型的实例,但是这是没有用处的,编译器如下提示:

error: no matching function for call to 'Swap(int&, double&)'

第一个形参的类型为double,不可以指向int变量x(归根结底是因为使用了引用或者指针,Add的那个实例中用的是值传递)

总结:隐式实例化、显式实例化、显式具体化都称为具体化。它们的相同之处在于,均表示的是使用具体类型的函数定义而不是通用的描述。

那么在程序中,有模板有函数又有重载的时候,到底调用的是哪一个?

优先级:常规函数 > 具体化模板函数 > 常规模板函数

且,有以下规则:

    cout << Add(x,y) << endl;//优先使用常规函数    cout << Add<>(x,y) << endl;//优先使用模板函数    cout << Add<int>(x,y) << endl;//这是要求编译器进行显式实例化
____________________________________________________________________________________________________________________________________

0 0