C++ primer Plus 第八章:函数探幽

1.为什么要使用内联函数?

执行函数调用指令的时候,程序将在函数调用后立即储存该指令的内存地址,并将函数参数复制到堆栈,跳到标记函数起点的内存单元,执行函数代码,然后条回到保存的那条指令的内存地址。这意味着执行函数要来回的跳跃位置,所以执行函数需要一定的开销。

而内联函数,编译器会将它的函数代码替换相关的函数调用。(这一过程在编译的时候实现,并不使用程序运行时候的时间,因此提高了函数的效率)

inline double fun(int a)
{
   return a*a;
}

int main()
{
   int i=2;
   cout<<fun(i)<<endl;
   return 0;
}
//若使用内联函数
cout<<fun(i)<<endl;就会变成cout<<i*i<<endl;

2.如何使用内联函数

在函数前面加上inline。

在类的成员函数可以直接在类的内部给出函数体,不用加inline就可以成为内联函数。

这里注意一点,inline函数不区分声明和定义。

并不是所有函数都可以成为内联函数,如果一个函数含有循环 switch等语句编译器将不会将其解释为内联函数,即使你使用了inline。

3.内联函数与宏

#define square(x) x*x

a= square(5);  会被解释成 5*5 是对的
b= square(1+2); 会被解释成 1+2*1+2 出错了
c= square(b++); 会被解释成 b++*b++

使用宏可能会出错,使用内联函数则不会。

其次,内联函数还有一个优点,它有返回类型,也就是说,编译器可以通过检查返回类型来检查是否出错,而宏定义没有返回类型。

4.引用变量

语法:

int a=1;

int & i=a;

引用只能指向一个变量,一旦指向后就不可更改。

可以通过改变i来改变a,其实引用是用指针实现的,int &i = a可以理解为,int const *i=&a;一个指向int变量的常指针,C++的设计者,简化了这些符号,使用了&。

5.将引用作为函数参数

函数参数如果不是指针的话,程序会为其创建一个副本,将副本传过去,这就意味着需要花费时间和内存,使用指针可以优化这个过程,直接传一个地址过去,让函数直接访问这个地址里的数据,减少了时间开销。上面提到引用时常指针,因此用引用也可以起到减少时间开销的作用。

函数参数为引用的话,不能传一个表达式过去 例如 fun(x +3)

如果实参与引用参数不匹配但可以转换成正确的类型或者实参类型正确,但不是左值,C++会生成临时变量。

仅当参数为const引用时,C++才允许这样做。

#include<iostream>
using namespace std;
void print(const int &a)
{
  cout<<a<<endl;
}

int main()
{
  long a,b;
  a=1,b=2;
  print(a+3);
  return 0;
}

个人认为,const是用来规范的,意思就是你类型不对,程序会创建一个正确类型的副本,并使形参引用他,那么即使你修改了形参,也不会改变主函数里的数据。所以一般这种情况,加一个const,相当于告诉编译器让它来帮忙检查程序员是否犯了这个错误。

当函数的返回类型为引用时候,不要返回一个临时变量的引用

6.关于对象的引用

对于基类和派生类来说,基类的引用可以指向派生类的引用,而且无需进行类型转换。

7.使用引用的原因

(1)程序员能够修改调用函数里的数据对象

(2)通过传递引用而不是整个数据对象,可以提高程序的运行速度。

8.引用、指针、值的使用情况

数据对象比较小,采用按值传递。

对象是数组或者结构,采用指针。

类对象,采用引用。

9.默认参数

C++允许函数使用默认参数,例如fun(int a,int b=0);

这样意味着你可以这样调用fun函数:fun(1),b被默认初始化为0了,如果你不想使用默认的b值,则可以这样fun(1,2);

注意事项:必须从右往左添加默认值,例如 fun(int a,int b=1,int c=2)

对于 fun2(int a=1,int b=2,int c=3)

不允许这样调用 fun2(1,,4);

10.函数重载

函数重载的关键在于函数的参数列表。也称为函数的特征标,特征标包含 函数的参数数目,类型,参数的排列顺序。

只有当函数的特征标不同时候,才可以实现函数重载

类型引用和类型本身视为同一个特征标

就是说print(int &i)和print(int i)无法重载,编译器无法决定使用哪一个。

函数的特征标相同但函数的返回类型不同的函数也无法重载

如long fun(int i)和 int fun(int i)

函数重载的实现:C++编译器执行神奇的操作——名称修饰,会根据函数原型中指定的形参类型对每个函数名进行加密,也就是说即使函数名相同,如果函数特征标不同的话,编译器编译出来的函数们,它们的名称是不同的,这样在调用的时候,程序会根据参数类型去找正确的函数。

11.函数模板

如果需要多个将同一种算法应用于不同种类型的函数,请使用模板

语法:template<class T>

函数类型 函数名 函数参数

例如:

template<class T>

void swap(T &a,T&b)

{

  T  t=a;

  a=b;

  b=t;

}

程序执行时候会根据函数模板和给定参数类型去生成一个给定参数类型的函数。

显示具体化

当编译器找到与函数调用匹配的具体化定义时,将使用该定义,而不再寻找模板。

什么时候用显示具体化呢,举个例子,当我们在使用上述swap函数模板交换结构体的时候,我们希望几交换结构体中的某个数据对象,而不交换其他数据对象,这时候就可以使用显示具体化。

struct job
{
    string name;
    double salary;
    int floor;
};

template<>
void Swap<job>(job &j1,job &j2)//只交换薪水
{
    double t1;
    t1=j1.salary;
    j1.salary=j2.salary;
    j2.salary=t1;
}

还有一个叫显示实例化的东西,就是说你告诉编译器,让编译器提前生成一个类型的函数

如 template void swap<int >(int,int );这是个声明

所谓隐式实例化就是程序根据参数类型来生成函数。

显示实例化 隐式实例化 显示具体化统称为具体化

编译器选择使用哪一个函数版本

第一步:创建候选函数列表。其中包含与被调函数名称相同的函数和模板函数

第二步:使用候选函数列表创建可执行函数。

第三步:确定是否有最佳的可行函数。

从最佳到最差的顺序为:

1.完全匹配

2.提升转换(如 shorts 转换为 int ,float转换为double)

3.标准转换(如char 转换为int,long转换为double)

4.用户定义的转换(如类声明里定义的转换)

发布了37 篇原创文章 · 获赞 3 · 访问量 2383

猜你喜欢

转载自blog.csdn.net/Stillboring/article/details/105164027
今日推荐