第八章 函数探幽
8.1 C++内联函数
提出的目的:为了提高程序运行速度。
内联函数和普通函数的区别:
-
编译方式:
- 内联函数在编译时会被直接替换到调用处,而不是像普通函数那样通过函数调用的方式执行。这样可以减少函数调用的开销,提高程序执行效率。
- 普通函数则是通过函数调用的方式执行,会涉及函数栈的压栈和出栈操作。
-
代码复制:
- 内联函数会在每个调用处直接插入函数代码,因此可能会导致代码冗余增加,尤其对于较大的函数来说。
- 普通函数只在内存中存储一份代码,多次调用时共享这一份代码。
-
适用场景:
- 内联函数适合用于简单的、频繁调用的函数,可以减少函数调用带来的开销。
- 普通函数适合用于复杂的、功能复用性强的函数,可以提高代码的可读性和维护性。
-
代码大小:
- 使用内联函数可能会增加最终生成的可执行文件的大小,因为函数代码会被复制到每个调用处。
- 普通函数会减少可执行文件的大小,因为函数代码只需要存储一份。
总的来说,内联函数可以提高程序的执行效率,但可能会增加代码的大小,空间换时间;普通函数更适合用于复杂的功能或需要多次重复调用的场景。在实际编程中,要根据具体情况选择使用内联函数还是普通函数。
下面是一个内联函数的代码例子:
#include <iostream>
// 内联函数定义
inline int add(int a, int b) {
return a + b;
}
int main() {
int x = 5;
int y = 10;
int result = add(x, y); // 内联函数调用
std::cout << "Result: " << result << std::endl;
return 0;
}
在上述代码中,我们定义了一个内联函数 add
,用于计算两个整数的和。在 main
函数中,我们通过 add
函数进行求和,并输出结果。
内联函数的关键字 inline
告诉编译器将该函数内联展开,而不是通过函数调用的方式执行。这样可以减少函数调用的开销,提高程序的执行效率。需要注意的是,编译器会根据具体情况决定是否真正内联展开函数代码,因此并非所有带有 inline
关键字的函数都会被内联展开。
请注意,内联函数适用于简单的、频繁调用的函数。在实际编程中,应根据需求和具体情况来决定是否使用内联函数。
如果使用C语言的宏执行了类似的函数定义功能,应该考虑替换为c++内联函数。
8.2 引用变量
引用时已定义的变量的别名,其主要用途时用作函数的形参。
8.2.1 创建引用变量
下面是demo
#include <iostream>
int main() {
int num = 10; // 定义一个整数变量
int& ref = num; // 创建一个对整数变量的引用
std::cout << "num: " << num << std::endl; // 输出 num 的值
std::cout << "ref: " << ref << std::endl; // 输出引用 ref 的值
ref = 20; // 通过引用修改原变量的值
std::cout << "num after modification: " << num << std::endl; // 输出修改后的 num 的值
std::cout << "ref after modification: " << ref << std::endl; // 输出修改后的引用 ref 的值
return 0;
}
注意: 引用必须在声明引用的时候就将其初始化,而指针是可以先声明,后赋值。
8.2.2 将引用用作函数参数
当使用按引用传递时,被调用的函数可以通过引用参数访问和修改调用函数中的变量。
下面是一个相关的例子:
#include <iostream>
// 按引用传递的函数
void multiplyByTwo(int &num) {
num *= 2; // 通过引用修改调用函数中的变量
}
int main() {
int number = 5;
std::cout << "Original value: " << number << std::endl;
multiplyByTwo(number); // 通过引用传递调用函数中的变量
std::cout << "Value after function call: " << number << std::endl;
return 0;
}
在这个示例中,我们定义了一个名为 multiplyByTwo
的函数,它以引用方式接收一个整数参数,并将该参数乘以2。在 main
函数中,我们创建了一个整数变量 number
,然后调用 multiplyByTwo
函数并将 number
作为参数传递。
由于 multiplyByTwo
函数的参数是按引用传递的,它可以直接修改 main
函数中的 number
变量的值。因此,在输出 "Value after function call" 时,我们会看到 number
的值已经被修改为原来的两倍。
这个例子展示了按引用传递允许被调用的函数访问和修改调用函数中的变量的情况。当然上述情况也能用指针解决。
8.2.3 引用的属性和特别之处
左值参数:
左值参数有两种,一是指可以出现在赋值操作符的左边的表达式的常规变量,它们是可以被修改的。
二是const 变量,不可被修改。
如果函数的目的不是为了修改传递的值,可使用const。
8.2.7 何时使用引用参数
使用原因:
1.能够修改调用函数中的数据对象。
2.通过传递引用而不是整个数据对象,可以提高效率。
什么时候应使用引用、什么时候应使用指针呢?什么时候应按值传递呢?
下面是一些指导原则:
对于使用传递的值而不作修改的函数。
1.如果数据对象很小,如内置数据类型或小型结构,则按值传递。
2.如果数据对象是数组,则使用指针,因为这是唯一的选择,并将指针声明为指向const的指针。
3.如果数据对象是较大的结构,则使用const指针或const引用,以提高程序的效率。这样可以节省复制结构所需的时间和空间。
4.如果数据对象是类对象,则使用const引用。类设计的语义常常要求使用引用,这是C++新增这项特性的主要原因。因此,传递类对象参数的标准方式是按引用传递。
对于修改调用函数中数据的函数:
如果数据对象是内置数据类型,则使用指针。如果看到诸如 fxit(&x)这样的代码(其中x是 imnt),则很明显,该函数将修改 x。
如果数据对象是数组,则只能使用指针。
如果数据对象是结构,则使用引用或指针。
如果数据对象是类对象,则使用引用。
8.3 默认参数
默认参数是指当函数调用时少了实参时自动使用的一个值。
如何设置: 必须使用函数原型
char * left(const char * str,int n = 1);如果不对n赋值时,n的值默认为1
注意:对于带参数列表的函数,必须从右往左添加默认值
8.4 函数重载
函数重载和函数多态是一回事儿。
函数重载是指在同一个作用域内,可以定义多个函数名相同但参数列表不同的函数。通过函数重载,可以根据函数参数的不同来调用不同版本的函数,从而提高代码的灵活性和可复用性。
下面是一个简单的示例来说明函数重载:
#include <iostream>
// 函数重载示例
void printNumber(int num) {
std::cout << "Integer number: " << num << std::endl;
}
void printNumber(double num) {
std::cout << "Double number: " << num << std::endl;
}
int main() {
int a = 10;
double b = 3.14;
printNumber(a); // 调用第一个 printNumber 函数,传入整数参数
printNumber(b); // 调用第二个 printNumber 函数,传入双精度浮点数参数
return 0;
}
在上面的示例中,定义了两个名为 printNumber
的函数,一个接受 int
类型的参数,另一个接受 double
类型的参数。这两个函数具有相同的函数名但参数列表不同,因此它们构成了函数重载。
在 main
函数中,分别传入整数和双精度浮点数参数来调用 printNumber
函数。由于参数类型不同,编译器可以根据传入参数的类型选择调用相应版本的函数,并输出不同类型的数字信息。
通过函数重载,我们可以根据不同的参数类型或参数个数来区分函数的行为,使代码更加灵活,同时避免在命名上进行过多的差异化,提高代码的可读性和维护性。
何时用:仅当函数基本上执行相同的任务,但使用不同形式的数据时,才采用函数重载。
8.5 函数模板(重要)
函数模板是一种通用的函数定义,通过引入类型参数,可以实现多个类型的数据进行相同的操作。
#include <iostream>
// 函数模板定义
template <typename T> // 常用为
template <class T>
T getMax(T a, T b) {
return (a > b) ? a : b;
}
int main() {
int num1 = 10, num2 = 20;
double num3 = 3.14, num4 = 2.7;
// 调用函数模板
int maxInt = getMax(num1, num2);
double maxDouble = getMax(num3, num4);
std::cout << "Max integer: " << maxInt << std::endl;
std::cout << "Max double: " << maxDouble << std::endl;
return 0;
}
8.5.1 重载的模板
当函数模板和函数重载结合时,可以实现更加灵活的代码设计。下面是一个示例,演示了同时使用函数模板和函数重载的情况:
#include <iostream>
// 函数模板重载示例
template <typename T>
T getMax(T a, T b) {
return (a > b) ? a : b;
}
// 函数重载:针对 char* 类型的特殊处理
const char* getMax(const char* a, const char* b) {
return strcmp(a, b) > 0 ? a : b;
}
int main() {
int num1 = 10, num2 = 20;
double num3 = 3.14, num4 = 2.7;
const char* str1 = "apple";
const char* str2 = "banana";
// 调用函数模板
int maxInt = getMax(num1, num2);
double maxDouble = getMax(num3, num4);
// 调用函数重载
const char* maxStr = getMax(str1, str2);
std::cout << "Max integer: " << maxInt << std::endl;
std::cout << "Max double: " << maxDouble << std::endl;
std::cout << "Max string: " << maxStr << std::endl;
return 0;
}
在上面的示例中,我们首先定义了一个函数模板 getMax
,用于比较两个相同类型的数据并返回较大值。然后,我们又定义了一个针对 const char*
类型的函数重载版本,用于比较字符串的大小并返回较大的字符串。
在 main
函数中,我们声明了几个变量,包括整数、双精度浮点数和字符串。然后分别调用 getMax
函数模板和函数重载版本,根据参数的类型来选择合适的函数进行处理。
8.5.3 显式具体化
当处理一些特殊结构的特殊操作时候,可能无法使用模板函数,例如下面的结构体job,若定义job a,job b,如果要交换a,b里面的floor值则无法正常使用模板函数,此时提供了显式具体化的办法实现。
struct job{
char name[40];
double salary;
int floor;
}
//模板化
template<class T>
void Swap(T &a, T &b);
//对job类型的显示具体化
template <> void Swap<job>(job &a,job &b);
int main(){
double u,v;
Swap(u,v);
job s,w;
Swap(s,w);
}
8.5.4 实例化和具体化
实例化:是生成一个具体的函数,无论调不调用都存在
具体化:指开辟一个特殊的模板通道,使用时不走普通模板,走此特殊通道。
8.6 总结
C++扩展了C语言的函数功能。通过将imnline 关键字用于函数定义,并在首次调用该函数前提供其函数定义,可以使得C+编译器将该函数视为内联函数。也就是说,编译器不是让程序联到独立的代要受以执行函数,而是用相应的代码替换函数调用。只有在函数很短时才能采用内联方式。
引用变量是一种伪装指针,它允许为变量创建别名(另一个名称)。引用变量主要被用作处理结构和类对象的函数的参数。通常,被声明为特定类型引用的标识符只能指向这种类型的数据;然而,如果一个类(如ofstream)是从另一个类(如ostream)派生出来的,则基类引用可以指向派生类对象。
C++原型让您能够定义参数的默认值。如果函数调用省略了相应的参数,则程序将使用默认值:如果函数调用提供了参数值,则程序将使用这个值(而不是默认值)。只能在参数列表中从右到左提供默认参数。因此,如果为某个参数提供了默认值,则必须为该参数右边所有的参数提供默认值。
函数的特征标是其参数列表。程序员可以定义两个同名函数,只要其特征标不同。这被称为函数多态或函数重载。通常,通过重载函数来为不同的数据类型提供相同的服务。函数模板自动完成重载函数的过程。只需使用泛型和具体算法来定义函数,编译器将为程序中使用的特定参数类型生成正确的函数定