51工具盒子

依楼听风雨
笑看云卷云舒,淡观潮起潮落

指针和函数

函数和指针在 C/C++ 中常常一起使用,指针可以用于传递函数参数、返回函数结果或者作为函数的返回值。这样做可以实现更灵活和高效的程序设计。

下面是一些常见的函数和指针的使用方式:

  1. 指针做函数参数 {#1-指针做函数参数} =======================

1.1 参数为变量 {#1-1-参数为变量}

可以将指针作为函数的参数,从而在函数内部直接访问并修改指针所指向的变量。这样可以避免在函数调用时进行变量的拷贝,提高程序的运行效率。

需求:编写一个函数实现两个变量之间的值的交换

|---------------------------------------------------------------------------------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | 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 | #include <stdio.h> void swap1(int x, int y) { int tmp; tmp = x; x = y; y = tmp; printf("x = %d, y = %d\n", x, y); } void swap2(int* x, int* y) { int tmp; tmp = *x; *x = *y; *y = tmp; } int main() { int a = 3; int b = 5; swap1(a, b); // 值传递 printf("a = %d, b = %d\n", a, b); a = 3; b = 5; swap2(&a, &b); // 地址传递 printf("a2 = %d, b2 = %d\n", a, b); return 0; } |

程序输出的结果如下:

|---------------|--------------------------------------------------| | 1 2 3 | x = 5, y = 3 a = 3, b = 5 a2 = 5, b2 = 3 |

在上面代码中一共编写了两个交换函数swap1swap2,参数个数相同但是类型不同,通过输出的结果可以看出swap2函数完成了变量之间的数据交换,swap1没有,其主要原因是这样的:

  • swap1函数的参数是数值,参数传递的是值,在传递过程中实参会发生拷贝,此时形参和实参变量对应的内存地址是不同的,在函数体内部交换的是参数的值,而不是外部实参的值
  • swap2函数的参数是指针,参数传递的是地址,形参指针指向的地址和实参变量的地址是相同的,因此在函数体内部通过形参指针就可以交换实参变量内部的数值了。

1.2 参数为数组 {#1-2-参数为数组}

数组名也可以做函数参数,但是数组类型的形参最终会退化为指针:

|---------------------------------------------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | //void printArrary(int a[10], int n) //void printArrary(int a[], int n) void printArrary(int* a, int n) { int i = 0; for (i = 0; i < n; i++) { printf("%d, ", a[i]); } printf("\n"); } int main() { int a[] = { 1, 2, 3, 4, 5, 6, 7, 8, 9 }; int n = sizeof(a) / sizeof(a[0]); //数组名做函数参数 printArrary(a, n); return 0; } |

通过上面的示例代码可以得知,数组做函数参数的时候,对于形参而言有三种写法:

  • 指定为数组类型,并且指定了数组的最大容量,必须和实参容量保持一致
    • void printArrary(int a[10], int n)
  • 指定为数组类型,没有指定数组的最大容量,需要通过实参进行推导
    • void printArrary(int a[], int n)
  • 指定为指针类型,通过形参指针指向实参数组的地址
    • void printArrary(int* a, int n)

由于数组类型的形参最终会退化为指针,所以有一个细节需要额外注意:在函数体内部不能通过运算符 sizeof(形参) 的方式对数组进行内存大小的计算,因为这样计算出的是指针自身所占用的内存大小(得到的结果是4字节或者8字节),而我们想要知道的是指针指向的内存的大小。所以在进行数组传递的时候,都会给函数添加一个额外的整形参数用于标记当前这个数组的容量。

  1. 指针做函数返回值 {#2-指针做函数返回值} =========================

函数可以返回指针,这样可以在函数外部获取指针所指向的数据。需要额外注意返回的指针指向的内存的生命周期。

2.1 返回局部数据 {#2-1-返回局部数据}

局部数据是在函数内部定义的变量,它们的作用范围被限制在所在的函数块内。局部数据只在函数执行期间存在,并且在函数执行结束时会自动被销毁,释放相关的内存空间。

以下是一个示例,展示了局部数据的使用:

|---------------------------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | #include <stdio.h> int* getValue(int num) { int number = num * 2 - 66; return &number; } int main() { int *val = getValue(66); printf("value = %d\n", *val); return 0; } |

执行上面的程序打印出的结果有两种:正确或者错误。上面的程序有一个隐藏的Bug:getValue 函数内部的 number 是一个局部变量,函数调用结束之后,这个变量对应的存储空间也就被释放了,此时函数调用者通过函数返回值拿到的地址就是非法的,这块地址随时都可能被分配给其它的变量并且被写入新的值,所以我们通过指针取出的数据可能正确也可能错误。

2.2 返回全局数据 {#2-2-返回全局数据}

全局数据是在函数外部定义的变量,它们在整个程序的执行期间都是有效的,其作用范围跨越了多个函数的调用。

以下是一个示例,展示了全局数据的使用:

|------------------------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------| | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | #include <stdio.h> int a = 10; int* getA(int num) { a = num * 3 % 9; return &a; } int main() { int *val = getA(16); printf("val = %d\n", *val); return 0; } |

上面的代码中通过使用全局变量,解决了变量内存生命周期问题,但仔细思考一下会发现这么写没有任何意义可言,全局部变量可以直接使用,完全不需要通过返回值的方式将其传递给函数调用者,在后面的章节中会给大家详细讲解如何动态申请存储空间,这样函数返回指针就变得有意义了。

  1. 函数指针 {#3-函数指针} =================

函数指针是指向函数的指针变量。它们可以用于动态地调用不同的函数,实现程序的灵活性和可扩展性。

以下是一个示例,展示了函数指针的使用:

|------------------------------------------------------------------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | 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 | #include <stdio.h> int add(int a, int b) { return a + b; } int subtract(int a, int b) { return a - b; } int main() { // 定义一个函数指针,指向有两个整型参数和整型返回值的函数 int (*funcPtr)(int, int); // 将函数指针指向 add 函数 funcPtr = &add; // 通过函数指针调用 add 函数 int result = funcPtr(3, 4); printf("Result of addition: %d\n", result); // 将函数指针指向 subtract 函数 funcPtr = &subtract; // 通过函数指针调用 subtract 函数 result = funcPtr(5, 2); printf("Result of subtraction: %d\n", result); return 0; } |

在上述示例中,我们首先定义了一个函数指针 funcPtr,它指向一个具有两个整型参数和整型返回值的函数。然后,我们将 funcPtr 分别指向 addsubtract 函数,并使用函数指针调用相应的函数。

需要注意的是,对于函数指针变量,我们需要显式地使用 & 取得函数的地址。然而,在大多数情况下,可以直接将函数名赋给函数指针,编译器会自动进行地址获取。

函数指针可以用于实现回调机制,使函数能够动态地选择要执行的代码段。此外,函数指针还可以在实现函数库、处理函数指针数组等方面发挥重要作用。需要注意的是,函数指针的类型必须与所指向的函数的类型保持一致,包括参数类型和返回值类型。

函数指针的灵活性使得我们能够编写更加通用和可扩展的代码,动态地适应不同的应用场景。

赞(1)
未经允许不得转载:工具盒子 » 指针和函数