C++内联函数
什么是内联函数?
内联函数是C++为提高程序运行速度所做的一项改进,常规函数和内联函数的主要区别不在于编写方式。而在于编译器如何将它们组合到程序中。
程序的运行过程
编译过程的最终产物是可执行程序——由一组机器语言指令组成。运行程序时,操作系统将这些指令载入到计算机内存中,因此每条指令都有特定的内存地址。计算机随后将逐步执行这些指令。有时(例如循环/分支语句),将跳过一些指令,向前或向后跳到特定地址。常规函数调用也使程序跳到另一个地址(函数的地址),并在函数结束后返回。下面更详细地介绍这一过程。执行到函数调用指令时,程序将在函数调用后立即存储该指令的内存地址,并将函数复制到堆栈(为此保留的内存块),跳到标记函数起点的内存单元,执行函数代码(也许还需将返回值放入到寄存器中),然后跳回到地址被保存的指令处(这与阅读文章时停下来看脚注,并在阅读完脚注后返回以前阅读的地方类似)。来回跳跃并记录跳跃位置意味着以前使用函数时,需要一定的开销。
内联函数
C++内联函数提供了另一种选择。内联函数的编译代码与其他程序代码“内联”起来了。也就是说,编译器将使用相应的函数代码替代函数调用。对于内联代码,程序无需跳到另一个位置处执行代码,再跳回来。因此,内联函数的运行速度比常规函数稍快,但代价是需要占用更多内存。如果程序在10个不同的地方调用同一个内联函数,则该程序将包含该函数代码的10个副本。
应有选择地使用内联函数。如果执行函数代码地时间比处理函数调用机制的时间长,则节省的时间将只占整个过程中很小的一部分。如果代码执行时间很短,那么内联调用就可以节省非内联调用使用的大部分时间。另一方面,由于这个过程相当快,因此尽管节省了该过程的大部分时间,但节省的时间绝对值并不大,除非其经常被调用。
要使用这项特性,必须采取下述措施之一:
- 在函数声明前加上关键字inline;
- 在函数定义前加上关键字inline。
通常的做法是省略原型,将整个定义(即函数头和所有函数代码)放在本应提供原型的地方。
当然,内联函数只是对编译器的一种建议,编译器完全可以忽略你的建议。如果提供的函数过大或是调用了自己(内联函数不能递归),编译器可能不会将其作为内联函数。
下面是一个实例:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20// inline.cpp -- using an inline function
// an inline function definition
inline double square(double x) { return x * x; }
int main()
{
using namespace std;
double a, b;
double c = 13.0;
a = square(5.0);
b = square(4.5 + 7.5); // can pass expressions
cout << "a = " << a << ", b = " << b << "\n";
cout << "c = " << c;
cout << ", c squared = " << square(c++) << "\n";
cout << "Now c = " << c << "\n";
return 0;
}
它通过内联函数square()(计算参数的平方)演示了内联技术。注意到整个函数定义都放在一行中,但不一定非要这样做。然而,如果函数定义占用多行(假定没有使用冗长的标识符),则将其作为内联函数就不大合适。
下面是该程序的输出:1
2
3a = 25, b = 144
c = 13, c squared = 169
Now c = 14
输出表明,内联函数与常规函数一样,也是按值来传递参数的。如果参数为表达式,如上面的4.5+7.5,则函数将传递表达式的值(这里为12)。这使得C++的内联功能远远胜过C语言的宏定义。
尽管函数没有提供独立原型,但C++原型特性仍在起作用。这是因为在函数首次使用前出现的整个函数定义充当了原型。这意味着可以给square()传递int或long值,将值传递给函数前,程序自动将这个值强制转换为double类型。
内联与宏
inline工具是C++新增的特性。C语言使用预处理器语句#define来提供宏——内敛代码的原始实现。例如,下面是一个计算平方的宏:1
这并不是通过传递参数实现的,而是通过文本替换实现的——X是“参数的符号标记”。1
2
3a = SQUARE(5.0); is replaced by a = 5.0 * 5.0;
b = SQUARE(4.5+7.5); is replaced by b = 4.5 + 7.5 * 4.5 + 7.5;
d = SQUARE(c++); is replaced by d = c++ * c++;
上述示例只有第一个能正常工作。可以通过括号来改进:1
但仍然存在这样的问题,即宏不能按值传递。即使使用新的定义,SQUARE(C++)仍将c递增2次,但是之前的inline.cpp中的内联函数square()计算c的结果,传递它,以计算其平方值,然后将c递增一次。
这里的目的不是演示如何编写C宏,而是要指出,如果使用C语言的宏执行了类似函数的功能,应考虑将它们转换为C++内联函数。
引用变量
引用
C++新增了一种复合类型——引用变量。引用是一顶一的变量的别名(另一个名称)。例如,如果将twain作为element变量的引用,则可以交替使用twain和element来表示该变量。那么,这种别名有何作用呢?它的主要作用是用作函数的形参,通过引用变量作为参数,函数将使用原始数据,而不是其副本。这样,除指针以外,引用也为函数处理大型结构提供了一种非常方便的途径,同时对于设计类来说,引用也是必不可少的。
创建引用变量
C和C++使用&符号来表示变量的地址。C++给&符号赋予了另一个含义,将其用来声明引用。例如,要将rodents作为rats变量的别名,可以这样做:1
2int rats;
int & rodents = rats; // makes rodents an alias for rats
其中,&不是地址运算符,而是类型标识符的一部分。就像声明中的char*指的是指向char的指针一样,int &指的是指向int的引用。上述引用声明允许将rats和rodents呼唤——它们指向相同的值和内存单元,参看以下程序:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19// firstref.cpp -- defining and using a reference
int main()
{
using namespace std;
int rats = 101;
int & rodents = rats; // rodents is a reference
cout << "rats = " << rats;
cout << ", rodents = " << rodents << endl;
rodents++;
cout << "rats = " << rats;
cout << ", rodents = " << rodents << endl;
// some implementations require type casting the following
// addresses to type unsigned
cout << "rats address = " << &rats;
cout << ", rodents address = " << &rodents << endl;
return 0;
}
需要注意的是,下述语句中的&运算符不是地址运算符,而是将rodents的类型声明为int &,即指向int变量的引用:1
int & rodents = rats;
不过,下述语句中的&则是地址运算符,其中&rodents表示rodents引用的变量的地址:1
cout << ", rodents address = " << &rodents << endl;
下面是上述程序的输出:1
2
3rats = 101, rodents = 101
rats = 102, rodents = 102
rats address = 0x0065fd48, rodents address = 0x0065fd48
从中可知,rats和rodents的值和地址都相同(具体的地址和显示格式随系统而异)。将rodents加1将影响这两个变量。更准确地说,rodents++操作将一个有两个名称的变量加1。
将引用作为函数参数
引用经常被用作函数参数,使得函数中的变量名成为调用程序中的变量的别名。这种传递参数的方法成为按引用传递。按引用传递允许被调用的函数能够访问调用函数中的变量。C++新增的这项特性是对C语言的超越,C语言只能按值传递。按值传递导致被调用函数使用调用程序的值的拷贝。当然,C语言也允许避开按值传递的限制,采用按指针传递的方式。
参看下列程序代码,它演示了几种交换两个变量的值的函数:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50// swaps.cpp -- swapping with references and with pointers
void swapr(int &a, int &b); // a, b are aliases for ints
void swapp(int *p, int *q); // p, q are addresses of ints
void swapv(int a, int b); // a,b are new variables
int main(void)
{
using namespace std;
int wallet1 = 300;
int wallet2 = 350;
cout << "wallet1 = $" << wallet1;
cout << " wallet2= $" << wallet2 << endl;
cout << "Using reference to swap contents:\n";
swapr(wallet1, wallet2);
cout << "wallet1 = $" << wallet1;
cout << " wallet2= $" << wallet2 << endl;
cout << "Using pointers to swap contents again:\n";
swapp(&wallet1, &wallet2);
cout << "wallet1 = $" << wallet1;
cout << " wallet2= $" << wallet2 << endl;
cout << "Trying to use passing by value:\n";
swapv(wallet1, wallet2);
cout << "wallet1 = $" << wallet1;
cout << " wallet2= $" << wallet2 << endl;
return 0;
}
void swapr(int &a, int &b) // use references
{
int temp;
temp = a; // use a, b for values of variables
a = b;
b = temp;
}
void swapp(int *p, int *q) // use pointers
{
int temp;
temp = *p; // use *p, *q for values of variables
*p = *q;
*q = temp;
}
void swapv(int a, int b) // try using values
{
int temp;
temp = a;
a = b;
b = temp;
}
下面是程序的输出:1
2
3
4
5
6
7wallet1 = $300 wallet2 = $350 << original values
Using references to swap contents:
wallet1 = $350 wallet2 = $300 << values swapped
Using pointers to swap contents:
wallet1 = $300 wallet2 = $350 << values swapped again
Trying to use passing by value:
wallet1 = $300 wallet2 = $350 << swap failed
正如预想那样,指针和引用方法都成功地交换了两个钱夹(wallet)中的内容,而按值传递的方法没能完成此项任务。
引用的其他使用方法我们将在后续学习。
