第八章 - 函数探幽

一,内联函数

1.1,什么是内联函数?

内联函数是C++为了提高程序的运行速度所做出的一项改进。常规函数与内联函数的主要的区别不在于函数的编写方式,而在于C++编译器如何把他们整合到代码中。要了解两者之间的区别,必须要先了解函数调用的底层实现。

1.2,函数调用过程

代码编译过程的最终的产品是可执行指令–由一组机器语言指令组成。运行程序时,操作系统将这些指令载入到内存中。计算机随后将逐条执行这些指令。执行函数调用指令时,程序在该函数调用后立即存储该指令的内存地址,并将函数的参数复制到堆栈中,跳到存储被调函数二进制代码的内存单元,执行函数代码,然后跳回到被保存的指令处。来回跳跃并记录之前函数调用点的地址,需要一定的开销。

1.3,内敛函数的优缺点

优点:

内联函数的编译代码与其他的程序”内联”起来,也就是说在程序编译时,编译器将程序中出现的内联函数的调用用内联函数的函数体进行替代。我们可以看出,inline的原理,是用空间换取时间的做法,是以代码膨胀(复制)为代价,省去了函数调用的开销,从而提高函数的执行效率。

缺点:

需要占用更多的内存。例如:如果一个程序在10个不同的位置调用同一个内联函数,则该程序将包含该函数代码的10个副本。

1.4,如何决定是否把一个函数作为内联函数?

  1. 如果执行函数代码的时间比处理函数调用机制的事件长,把这个函数作为内联函数,则节省的时间只占整个过程的一小部分,因此,这个函数不适合作为内联函数。
  2. 如果代码执行的时间很短,把这个函数作为内联函数,则可以节省函数调用机制占用的大部分的时间。另一方面,由于这个过程很快,因此尽管节省了该过程的大部分的时间,但节省的时间的绝对值并不大。

1.5,使用inline关键字把一个函数声明为内联函数

在函数原型与函数声明前面都要加上inline关键字。程序员请求将一个函数作为内联函数时,编译器并不一定会满足这个要求。它可能认为该函数过大或注意到函数调用到了自己(内联函数不能递归调用)或者这个函数的虚函数,因此不将该函数作为内联函数。

    #include <iostream>
    using namespace std;

    inline void display();

    int main(){
        display();
        return 0;
    }

    void display(){
        cout<<"hello world."<<endl;
    }

1.6,内联函数与宏定义的区别

宏定义实现的函数不是通过传递参数实现的,而是通过文本替换来实现的

include <iostream>
using namespace std;

#define square(x) x * x

int main(){
    int a = square(4);  //is replaced by 4 * 4
    int b = square(5 + 5);  //is replaced by 5 + 5 * 5 + 5
    cout<<"a: "<<a<<endl;
    cout<<"b: "<<b<<endl;
    return 0;
}

二,函数重载

2.1,什么是函数重载?

  1. C++中的函数重载也就是函数多态,是编译时多态性的体现。可以通过函数重载来设计一系列的函数,他们具有相同的函数名,但是具有不同的函数特征标。
  2. 函数重载的关键是函数的参数列表(也称为函数特征标)。如果两个函数的参数数目与类型相同,同时参数的排列顺序也相同,则他们的特征标相同。C++允许定义名称相同的函数,条件是他们的特征标不相同。

2.2,函数重载应注意的问题

  1. 编译器在检查函数特征标时,将把类型引用与类型本身视为同一个特征标。
  2. 两个函数如果只是参数的常量性不同,可以被重载。
  3. 函数的返回值不能作为特征标。

2.3,如何实现函数重载?

C++支持函数重载,而C则不支持。函数被C++编译器编译后在符号表中的名字与C语言的不同。例如,假设某个函数的原型为:

void foo(int a, int b);

该函数被C编译器编译后在符号表中的名字为_foo,而C++编译器则会产生像_foo_int_int之类的名字。_foo_int_int这样的名字包含了函数名和函数的特征标,C++就是使用这种机制来实现函数重载的。

三,函数模板

3.1,什么是函数模板?

函数模板是通用的函数描述,如果需要将同一种算法用于不同类型的函数,可以使用模板。下面是一个函数模板的定义:

#include <iostream>
using namespace std;

template <typename Type>
void Swap(Type &a, Type &b){
    Type temp = a;
    a = b;
    b = temp;
}

int main(){
    int a = 10, b= 20;
    Swap(a, b);
    cout<<"a: "<<a<<endl;
    cout<<"b: "<<b<<endl;
    return 0;
}

输出结果:

a: 20
b: 10

Process returned 0 (0x0)   execution time : 0.007 s
Press any key to continue.

3.2,重载函数模板

可以像重载常规函数定义那样重载函数模板定义,和常规重载一样,被重载的模板的函数特征标必须不同。

template <typename Type>
void Swap(Type &a, Type &b){
    Type temp = a;
    a = b;
    b = temp;
}

template <typename Type>
void Swap(Type *a, Type *b, int n){
    Type temp;
    for(int i = 0; i < n; i ++){
        temp = a[i];
        a[i] = b[i];
        b[i] = temp;
    }
}

3.3,模本具体化

在代码中包含模板本身并不会生成函数定义,它只是一个用于生成函数定义的方案。隐式实例化,显示实例化和显示具体化统称为具体化。例如:

template <typename Type>
void Swap(Type &a, Type &b){
    Type temp = a;
    a = b;
    b = temp;
}

3.3.1,隐式实例化

函数调用Swap(a, b)导致编译器生成Swap()的一个实例,该实例使用int型。模板并非函数定义,但是使用int的模板实例是函数定义,这种实例化方式成为隐式实例化。编译器之所以知道要进行函数定义,是由于程序调用Swap()函数时提供了int参数。

3.3.2,显示实例化

最初编译器只能通过隐式实例化,来生成模板函数的定义,但是现在C++还允许显示实例化,这意味着可以直接命令编译器创建特定的实例。其语法是,声明所需的类型–使用<>符号指示类型,并在声明的前面加上template关键字。如下:template void Swap(int, int)。

3.3.3,显示具体化

struct Student{
    string name;
    int age;
};

在程序中定义了上面的结构体,可以使用前面提供的Swap函数来交换两个结构体的内容。然而假如我们只想交换name成员变量,而不交换age成员变量则需要使用不同的代码,但是Swap的参数将保持不变,因此无法使用模板重载来提供其他的代码。可以提供一个具体化函数定义–成为显示具体化,其中包含所需的代码。当编译器找到与函数调用匹配的具体化定义时,将使用该定义,而不再寻找模板。具体化机制随着C++的演变而不断发生变化,下面介绍C++标准定义的形式:

  1. 对于给定的函数名,可以有非模板函数、模板函数与显示具体化模板函数以及他们的重载的版本。
  2. 显示具体化的原型和定义应以template<>开头,并通过名称来指定类型。
  3. 具体化优先与常规模板,而非模板函数优于具体化与常规模板。
#include <iostream>
using namespace std;

struct Student{
    string name;
    int age;
    Student(string name, int age):name(name),age(age){}
};

template <typename Type>
void Swap(Type &a, Type &b){
    Type temp = a;
    a = b;
    b = temp;
}

template <> void Swap<Student>(Student &a, Student &b){
    string name = a.name;
    a.name = b.name;
    b.name = name;
}

int main(){
    Student a("zhangsan", 20);
    Student b("lisi", 25);
    Swap(a, b);
    cout<<"a.name: "<<a.name<<endl;
    cout<<"b.name: "<<b.name<<endl;
    return 0;
}

输出结果:

a.name: lisi
b.name: zhangsan

Process returned 0 (0x0)   execution time : 0.007 s
Press any key to continue.

3.4,实例化与具体化的区别

3.4.1,实例化

实例化语法,声明所需的类型–用<>符号指示类型,并在声明前面加上关键字template。

template  void  Swap(int, int);

上面声明的意思是,使用Swap模板生成int类型的函数定义。

3.4.2,具体化

与上面实例化不同的是,显示具体化使用下面两个等价的声明之一:

template  <>  void  Swap<int>(int, int);
template  <>  void  Swap(int, int);

与上面实例化的区别是,上面这些声明的意思是,不要使用Swap模板来生成函数定义,而是用专门为int类型显示定义的函数定义。这些原型必须有自己的函数定义。显示具体化声明在关键字template后面包含<>,而显示实例化没有。

注意:

试图在同一个文件中使用同一种类型的显示实例和显示具体化将出错。

猜你喜欢

转载自blog.csdn.net/cloud323/article/details/80895949