第8 章 函数探幽
本章内容包括:
内联函数。 引用变量。 如何按引用传递函数参数。 默认参数。 函数重载。 函数模板。 函数模板具体化。
通过第 7 章,您了解到很多有关 C++函数的知识,但需要学习的知识还很多。C++还提供许多新的函
数特性,使之有别于 C 语言。新特性包括内联函数、按引用传递变量、默认的参数值、函数重载(多态)
以及模板函数。
本章介绍的 C++在 C 语言基础上新增的特性,比前面各章都多,这是您进入加加(++)领
域的重要一步.
内联函数
内联函数是在编译时将函数的定义直接插入到调用该函数的地方,而不是通过函数调用的方式执行。通过内联函数可以提高程序的执行效率,减少函数调用的开销。
在C++中,使用关键字inline
来声明内联函数。一般情况下,将函数定义放在类定义的头文件中,或者在函数定义前加上inline
关键字。
例如:
inline int add(int a, int b) { return a + b; }
内联函数在编译时会被替换为函数体的实际代码,而不是通过函数调用执行。这样可以避免函数调用的开销,提高程序的执行效率。然而,内联函数的展开也会增加代码的体积,所以内联函数适用于函数体较小且被频繁调用的情况。
需要注意的是,对于递归函数、含有循环或复杂控制流的函数以及包含静态变量的函数,通常不适合声明为内联函数。此外,编译器并不一定会完全遵守内联函数的声明,它可能会根据自己的优化策略选择是否内联某个函数。
总之,内联函数是一种用于提高程序性能的机制,它通过将函数的代码插入到调用处,减少了函数调用的开销,但也会增加代码的体积。我们需要根据实际情况来选择使用内联函数。
// inline.cpp -- using an inline function #include <iostream> // 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"; // cin.get(); return 0; }
内联函数是在编译时展开的函数,它通常用于简单的函数,以提高程序的执行效率。在C++中,使用关键字inline
来声明内联函数。
在上面的代码示例中,square()
函数被声明为内联函数。它的定义如下:
inline double square(double x) { return x * x; }
square()
函数接受一个double
类型的参数,然后返回该参数的平方。
在main()
函数中,我们可以通过调用square()
函数来计算不同值的平方,并将结果打印出来。例如:
a = square(5.0);
这里,5.0
作为参数传递给square()
函数,并将返回值赋给变量a
。
另外,内联函数还支持传递表达式作为参数。例如:
b = square(4.5 + 7.5);
这里,表达式4.5 + 7.5
的值作为参数传递给square()
函数。
此外,内联函数的展开发生在编译时,因此在代码中多次调用同一个内联函数时,每次调用都会被替换为函数体的复制。在上述代码中,可以看到square(c++)
的调用,其中c++
是一个后缀递增操作。由于内联函数的展开发生在编译时,所以c++
只会在整个表达式计算之前递增一次,而不是在每次复制的调用中递增。
综上所述,内联函数是一种用于提高程序执行效率的特殊函数,它将函数的调用替换为函数体的复制,适用于简单的函数和频繁调用的函数。
当我们在代码中使用内联函数时,编译器会尝试将函数的定义直接嵌入到调用该函数的地方。这样可以减少函数调用带来的额外开销,提高程序的执行效率。
以下是一些关于内联函数的注意事项:
- 小函数:内联函数适合用于短小且频繁调用的函数。因为直接插入函数代码而不是通过函数调用,所以函数体越小,插入到调用处的冗余代码就越少,性能提升也越明显。
- 头文件中的定义:为了使内联函数在每个调用点都可见,通常将内联函数的定义放在头文件中。这样,在每个使用该内联函数的源文件编译时,都可以将函数的定义嵌入。否则,在链接阶段会出现无法解析的符号错误。
- 函数声明与定义的一致性:内联函数的声明和定义必须一致,即函数签名(参数类型,返回类型)和函数体需一致。如果函数的定义与声明不一致,编译器可能会忽略内联修饰符。
- 递归函数和循环:递归函数通常不适合用作内联函数,因为递归函数的执行需要保持调用栈的状态。而内联函数的工作方式不适合保持递归调用的状态。
- 编译器优化:内联函数的使用并不保证编译器一定会进行内联展开。编译器可能会根据自身的优化策略和设置来决定是否内联函数。我们可以使用编译器的优化选项来指示编译器进行内联展开。
内联函数是一种在编译时将函数调用替换为函数本体的方式,以提高程序执行效率。内联函数适用于短小且频繁调用的函数,但并不能保证编译器一定会进行内联展开。我们需要根据实际情况来选择使用内联函数,并注意遵循内联函数的声明和定义规则。
当我们在编程中使用内联函数时,有几点需要注意:
- 使用内联函数可以减少函数调用的开销,但这并不意味着每个函数都应该声明为内联函数。编译器根据一系列规则和优化策略来决定是否将函数展开为内联代码。通常,适合用于内联的函数包括简短的、频繁调用的函数。
- 内联函数的定义通常放在头文件中。因为内联函数的定义需要在每个调用点都可见,所以将其放在头文件中可以确保在所有使用的源文件中都能访问到内联函数的定义。否则,在链接阶段会出现找不到函数定义的错误。
- 内联函数的声明和定义必须一致,包括参数类型、返回类型和函数体。如果声明和定义不一致,编译器可能会忽略内联修饰符。
- 内联函数不适合复杂的函数体、包含循环或递归调用的函数以及包含静态变量的函数。由于内联函数会在每个调用点直接插入代码,复杂的函数体会导致代码重复,并且可能会影响性能。
- 内联函数的展开也会增加代码的体积,因此在某些情况下,过度使用内联函数可能会导致代码膨胀,影响可执行文件的大小。
- 编译器并不一定会完全遵循内联函数的声明。编译器会根据自身的优化策略和设置来决定是否将函数展开为内联代码。我们可以使用编译器提供的优化选项来指示编译器进行内联展开。
引用变量
在编程中,引用变量是一个存储内存地址的变量,它指向另一个变量或对象的位置。通过引用变量,我们可以间接地访问和修改相应的数据。
在不同的编程语言中,引用变量可能具有不同的表达方式。例如,在C++中,引用变量使用&
符号声明,并在声明时与另一个变量绑定;在Java中,引用变量直接声明为对象类型;在Python中,变量实际上就是一个引用,直接通过赋值来引用其他对象。
引用变量的主要作用是允许多个变量同时指向同一个对象,从而实现数据共享和传递。通过修改引用变量的值,可以改变所指向对象的状态。这在处理大型复杂数据结构时非常有用,可以减少内存复制和提高性能。
然而,需要注意的是,引用变量并不总是指向有效的内存地址。如果引用变量没有初始化或者指向已释放的内存,那么访问该引用变量可能导致错误或者未定义行为。因此,在使用引用变量时,需要谨慎处理,确保引用的对象是有效的。
/ firstref.cpp -- defining and using a reference #include <iostream> 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; // cin.get(); return 0; }
这是一个C++的示例代码,演示了引用变量的用法。下面是对代码的解释:
#include <iostream>
引入iostream库,用于输入输出操作。
int main() { using namespace std; int rats = 101; int & rodents = rats; // rodents is a reference cout << "rats = " << rats; cout << ", rodents = " << rodents << endl;
在主函数中,声明一个整型变量rats
并赋值为101。然后,通过引用变量rodents
将rats
进行引用(绑定)。rodents
成为rats
的引用,它们指向同一个内存地址。
接下来,使用cout
语句输出rats
和rodents
的值。
rodents++; cout << "rats = " << rats; cout << ", rodents = " << rodents << endl;
通过修改rodents
的值,也会同时修改rats
的值。这里对rodents
进行自增操作,并再次输出rats
和rodents
的值。
cout << "rats address = " << &rats; cout << ", rodents address = " << &rodents << endl;
最后,使用&
运算符获取rats
和rodents
的地址,并输出。请注意,两者在内存中的地址是相同的,因为rodents
是rats
的引用。
return 0; }
程序结束,返回0表示正常退出。
这个示例代码展示了引用变量的使用。通过引用变量,我们可以创建别名并直接访问和修改原始变量的值,实现数据共享和传递。
// secref.cpp -- defining and using a reference #include <iostream> int main() { using namespace std; int rats = 101; int & rodents = rats; // rodents is a reference cout << "rats = " << rats; cout << ", rodents = " << rodents << endl; cout << "rats address = " << &rats; cout << ", rodents address = " << &rodents << endl; int bunnies = 50; rodents = bunnies; // can we change the reference? cout << "bunnies = " << bunnies; cout << ", rats = " << rats; cout << ", rodents = " << rodents << endl; cout << "bunnies address = " << &bunnies; cout << ", rodents address = " << &rodents << endl; // cin.get(); return 0; }
这是另一个C++的示例代码,展示了引用变量的一些特性。下面是对代码的解释:
#include <iostream> • 1
引入iostream库,用于输入输出操作。
int main() { using namespace std; int rats = 101; int & rodents = rats; // rodents is a reference cout << "rats = " << rats; cout << ", rodents = " << rodents << endl;
在主函数中,声明一个整型变量rats
并赋值为101。然后,通过引用变量rodents
将rats
进行引用(绑定)。
接下来,使用cout
语句输出rats
和rodents
的值。
cout << "rats address = " << &rats; cout << ", rodents address = " << &rodents << endl;
使用&
运算符获取rats
和rodents
的地址,并输出。请注意,两者在内存中的地址是相同的,因为rodents
是rats
的引用。
int bunnies = 50; rodents = bunnies; // can we change the reference? cout << "bunnies = " << bunnies; cout << ", rats = " << rats; cout << ", rodents = " << rodents << endl;
在此代码段中,声明了一个整型变量bunnies
并赋值为50。然后,将bunnies
赋值给rodents
,即修改了rodents
所引用的值。请注意,这里并没有修改rodents
的引用目标,仅修改了其所引用对象的值。
接下来,使用cout
语句输出bunnies
、rats
和rodents
的值。
cout << "bunnies address = " << &bunnies; cout << ", rodents address = " << &rodents << endl;
最后,使用&
运算符获取bunnies
和rodents
的地址,并输出。请注意,bunnies
和rodents
在内存中的地址是不同的,因为它们是两个独立的变量。
return 0; }
程序结束,返回0表示正常退出。
这个示例代码演示了引用变量的一些特性。通过引用变量,我们可以创建别名并直接访问原始变量的值。在赋值操作中,引用变量会更改其所引用对象的值,而不是修改引用本身。引用变量与原始变量共享相同的内存地址。
当我们使用引用变量时,可以进行以下操作:
- 声明引用变量:在编程语言中,可以使用特定的语法来声明引用变量。具体的语法可能因编程语言而异,但通常使用符号或关键字表示。例如,在C++中,可以使用
&
符号来声明引用变量。 - 初始化引用变量:引用变量必须在声明时被初始化,即要将其绑定到另一个变量或对象。这样引用变量就指向了相应的内存地址。初始化后,引用变量将成为该变量或对象的别名,并且可以通过该引用变量来访问和修改其值。
- 传递引用变量:可以将引用变量作为参数传递给函数或方法。通过这种方式,函数可以直接操作原始变量的值,而不需要进行额外的内存复制。这样可以提高性能并减少内存消耗。
- 修改引用变量:通过引用变量,可以修改所指向的变量或对象的值。这种修改是直接的,会反映在原始变量上。这对于在函数内部修改传入的变量非常有用。
- 注意引用的有效性:在使用引用变量时,需要确保引用的对象是有效的。如果引用变量指向已释放的内存或未初始化的变量,访问该引用变量可能导致错误或未定义行为。因此,在使用引用变量时,需要进行适当的检查和管理,以确保引用的正确性。
需要注意的是,具体的引用变量用法和语法可能因编程语言而异。在使用引用变量时,应该查阅相应编程语言的文档和规范,了解具体的用法和语法规则。