C++提供许多新的函数特性,使之有别于C语言。包括内联函数、按引用传递变量、默认参数值、函数重载(多态)以及模板函数。
内联函数
内联函数定义
内联函数是C++为了提高程序运行速度而做出的一项改进。内联函数与常规函数的主要区别不在于编写方式,而在于C++编译器如何把它们组合到程序中。
常规函数调用会使程序立即跳到一个地址(函数的地址),并在函数结束时返回。执行函数调用时,程序在函数调用后立即存储该指令的内存地址,并且把函数参数复制到堆栈,跳到标记函数起点的地址,然而执行函数代码,最后跳回之前保存的指令地址处。来回跳跃并记录跳跃位置意味着使用常规函数,需要一定的内存开销。
内联函数提供了另一种选择。内联函数的代码编译与其他程序代码“内联”起来了。也就是说,对于内联函数代码,程序无需跳到一个位置处理函数,再跳回来。因此内联函数的执行速度会稍快,但是代价是更多的内存。
应有选择地使用内联函数。如果执行函数代码的时间比函数调用机制的时间长,则节省的时间只是占整个过程的很小一部分。如果代码执行时间短,则内联函数调用机制可以节省大部分时间。但是另一方面,虽然节省了大部分时间,但实际上节省的时间也不长,除非该函数会被反复调用多次。
实现方法
使用这项特性,采取以下方法中的一种:
- 在函数声明前加上关键字inline
- 在函数定义前加上关键字inline
编译器不一定会满足inline
的要求。它有可能认为函数调用了自己(内联函数不允许递归),或者有的编译器没有这种特性。
1 |
|
- 函数定义都放在了一行之中,(不是非得这样做,但是请保证函数尽量简短)然而如果函数定义占多行,则将其作为内联函数就不太合适。
- tips1:对于函数语句内有while、switch如果内联函数有这些语句,则编译将该函数视同普通函数那样产生函数调用代码,递归函数(自己调用自己的函数)是不能被用来做内联函数的。
- tips2:对一个含有许多语句的大函数,函数调用和返回的开销相对来说微不足道,所以也没有必要用内联函数实现。
内联扩展是用来消除函数调用时的时间开销。它通常用于频繁执行的函数。 一个小内存空间的函数非常受益。
注意到,内联函数和常规函数一样,也是按值传递参数的。如果参数为表达式,如4.5+7.5,则内联函数能传递表达式的值(C语言中实参可以是表达式,形参不能是表达式)。这使得C++的内联功能胜过C语言的宏定义。
与宏定义的比较
内联函数的功能和预处理宏的功能相似。相信大家都用过预处理宏,我们会经常定义一些宏,如
1 | #define SQUARE(X) X*X |
宏定义最重要的性质是它通过替换字符串来实现功能。所以如果a=SQUARE(5)
,能得到正确结果25,但是如果b=SQRARE(4.5+7.5)
,结果会是4.5+7.5*4.5+7.5
,不会是我们要的结果144。
所以在写宏定义的时候一般都是这么写:
1 | #define SQUARE(X) ((X)*(X)) |
tips:所有用到参数的地方加上左右括号,最后再用左右括号把整个定义括起来。
但仍然存在问题:不能按值传递。所以当遇到SQRARE(c++)这样的情况,会把C递增两次。但如果依照上面的内联函数写法,会求出C的平方值后,只递增一次。
而且事实上我们可以用内联函数完全取代预处理宏。
内联函数和宏的区别在于,宏是由预处理器对宏进行替代,而内联函数是通过编译器控制来实现的。而且内联函数是真正的函数,只是在需要用到的时候,内联函数像宏一样的展开,所以取消了函数的参数压栈,减少了调用的开销。你可以象调用函数一样来调用内联函数,而不必担心会产生于处理宏的一些问题。
引用
C++ 新增了一种符合类型——引用变量。引用,是已定义的变量的别名。例如将a作为已经定义的b变量的引用,则可以交替使用a和b来表示改变量。
这有何作用呢?
使用引用变量
C 和 C++使用&
来表示变量的地址。C++给&
赋予了另一个意义,可以用来声明引用。
1 | int a = 101; |
那么 b
就是 a
的一个别名。
其中,&
不是地址运算符,而且类型标示符的一部分。就像 char *
指的是指向 char 的指针一样,int &
就是指向 int 的引用。
引用:支持已定义变量和其引用变量互换
- 他们代表相同的值,指向相同的内存单元。
- 将其中一个变量改变会影响两个变量。
- 声明一个引用,不是新定义了一个变量,它只表示该引用名是目标变量名的一个别名,它本身不是一种数据类型,因此引用本身不占存储单元,系统也不给引用分配存储单元。
引用与指针、地址的关系
初始引用变量,我们很容易想到原本C语言中就有的指针和地址。
那么,具体区别如下:
表达式b和*c都可以和a互换。
&b和c都可以和&a互换。
1 | int a = 1; |
在上段代码中,各种表达式的意义如下:
b | &b | c | *c | a | &a |
---|---|---|---|---|---|
引用 | 引用的地址 | 指针 | 间接访问 | 变量 | 变量的地址 |
引用看起来很像伪装表示的指针。实际上,引用还是很不同于指针的。除了表示法不同以外,还有其他的差别,例如差别之一是必须在声明引用的同时初始化,不能像指针那样先声明,后赋值。
1 | int a = 1; |
编译器会报错:引用变量“b”需要初始值设定项。
引用有一种const指针的味道——必须在声明的时候初始化,一旦初始化,就和某个变量关联起来,而且一直效忠于它。
引用作为真实对象的别名,必须进行初始化,除非满足下列条件之一:
- 引用变量被声明为外部的,它可以在任何地方初始化
- 引用变量作为类的成员,在构造函数里对它进行初始化
- 引用变量作为函数声明的形参,在函数调用时,用调用者的实参来进行初始化
声明一个引用,不是新定义了一个变量,它只表示该引用名是目标变量名的一个别名,它本身不是一种数据类型,因此引用本身不占存储单元,系统也不给引用分配存储单元。故:对引用求地址,就是对目标变量求地址。如果ra
是a
的引用,则&ra
与&a
相等。
可以建立数组的引用,C++中解决数组做形参的降价问题就是通过使用数组的引用。
不能建立引用的引用,不能建立指向引用的指针。
1 | int |
可以建立指针的引用
1 | int *p; |
引用作为函数参数
下面看看swap函数的三种写法:按值传参数、传指针做参数(地址)、按引用传递。
不用我说也知道:按值传递不能成功交换两个值,而后面传地址or传引用都是可以成功交换的。
1 | void swapr(int &a, int &b);//a,b are aliases for ints; |
引用常常被作为参数来传递,使得函数中的变量名是程序中变量的别名。这种传递方法叫做按引用传递。按引用传递可以允许被调用函数访问变量。C语言只能按值传递,调用函数使用的是程序中变量值的拷贝。所以C语言函数为了修改变量,得避开按值传递的限制,使用修改地址的方法。
像这个交换两个值的函数,我们知道函数swapv不起作用的原因是函数交换的是原变量的副本值,而不是交换变量。但传递引用,就可以使得函数使用原始数据。
如果a和b分别是w1和w2的引用,调用swapr(a,b)和swapv(a,b)。因为a和b是变量的别名,所以swapr交换两个引用的值,自然相当于交换变量a和b的值。但在swapv中,变量a和b只是复制了w1和w2的值的新变量,因此交换两个变量的值并不会影响w1和w2。
引用的最多用处就是作为函数的参数(常见于结构体和对象),通过引用变量作参数可以是函数使用原始数据,而不是拷贝。
引用就是给变量取别名,那别名究竟是什么意思?
引用么,如同给你取了一个别名。如你真名叫张三,你去参加了一档真人秀节目,在这个真人秀节目中,你有个昵称叫做张三疯,然后在真人秀节目中,有人打了张三疯一拳,你觉得本名叫张三的你会不会感觉到疼?真人秀 -> 函数。张三 -> 对象原名。张三疯 -> 引用传递的参数名。打了张三疯一拳 -> 改变对象的值。试试不就知道了。
作者:蓝色
链接:https://www.zhihu.com/question/54151133/answer/138137011
来源:知乎
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
与按值传递的区别
1 |
|
运行结果为:
refcube(x)
函数修改了 main 里的 x 的值,而 cube(x)
没有。
这提醒我们为何通常选择按值传递。变量 a 位于 cube()中,它被初始化为 x 的值,但修改 a 并不会影响 x。但由于 refcube() 使用了引用参数,因此修改 ra 实际上就是修改 x。
如果程序员的意图是让函数使用参数,但不对参数进行修改,同时又想使用按引用传递,则应该使用按常量引用。例如:
1 | double refcube(const double &ra); |
如有const,检测到函数要修改ra,会生成报错信息。
何时使用引用参数?
主要原因有两个:
• 程序员要修改调用函数中的数据对象
• 通过传递引用,而不是整个数据对象,提高运行速度
下面有一些指导原则
如果数据对象
• 很小,选择按值传递。
• 是数组,选用指针,这是唯一的选择,如果不修改数组内容,用const
• 是较大的结构,使用const指针或者const引用。
• 是类对象,const引用。类设计的语义常常需要使用引用。传递类对象参数的标准方式是按引用传递
对于修改调用函数中的数据的函数:
如果数据对象
• 是内置数据类型,则使用指针。如果看到类似func(int &x)这样的代码,很明显函数会修改x的。
• 是数组,则只能使用指针。不能有const
• 结构,使用指针或引用
• 类对象,只能使用引用
默认参数
下面介绍C++的一项新内容——默认参数。默认参数是指当函数调用时,省略了实参时自动使用的一个值。
例如有函数func(int n),设置n的默认值为1,则函数调用func()相当于func(1),这极大提高了使用函数的灵活性。
设置默认参数
设置默认值,必须通过函数原型。1
char *left(const char * str, int n = 1);
例如,left函数它有两个参数,字符串str和int型整数n,它的作用是返回字符串的前n个字符。准确的说,这个函数返回一个char型指针,指针指向由原始字符串中被选中的部分组成的字符串。
如果调用left(“theory”,3),则会返回指向新的字符串”the”的指针。假设第二个参数n的默认值被设定成了1,那么3会覆盖默认值。但是调用函数left(“theory”)将不会出错,它认为第二个参数是默认值1,会返回新的字符串”t”的指针。
如果说,这个程序经常需要抽取一个字符,偶尔需要抽取很长的字符,那么这种默认值的设置会很有帮助。
!默认参数定义的顺序为自右到左
对于带参数列表的函数,必须从右向左添加默认值,也就是说,为某个参数设置了默认值,则必须为它右边的所有参数都设置默认值。1
int func(int n, int m = 1, int j);//invalid
实参必须按照从左至右的顺序依次赋给相应的形参,而不能跳过任何参数。
1 | void mal(int a, int b=3, int c=5); //默认参数 |
1 | void mal(int a=8, int b=3, int c=5); //默认参数 |
实际上,默认函数并非编程方面的重大突破,而是提供了一种便捷的方式。在设计类的时候,通过默认函数,可以减少析构函数、方法、方法重载的数量。