目录
1 值传递
2 传递指针给函数
2.1 传地址或指针给函数
2.2 传数组给函数
3 指针函数(返回指针的函数)
3.1 语法格式
3.2 案例:返回静态局部变量
3.3 案例:返回字符串
3.4 案例:返回较长的字符串
3.5 案例:返回静态局部数组
3.6 返回值注意事项
4 函数指针(指向函数的指针)
4.1 函数的内存布局
4.2 语法格式
4.3 案例演示
5 回调函数
5.1 概念
5.2 案例:传入库函数
5.3 案例:传入自定义函数
6 测试题
值传递(也称为按值传递)是函数调用中一种常见的参数传递方式。在这种方式下,当调用函数时,实参(实际传递给函数的值)会被复制一份,然后这份复制的值被传递给形参(函数定义中的参数),形参在函数内部是一个独立的变量,与实参没有直接的关联。这意味着函数内部对形参的任何修改都不会影响到调用者处的实参。
输出结果如下所示:
在 C 语言中,没有直接的 “引用传递” 概念,但可以通过指针实现类似的效果。C 语言中的函数参数传递默认是值传递(即传值),但通过传递指针,可以实现对原始数据的修改,从而达到类似引用传递的效果。
当函数的形参类型是指针类型时,调用该函数时需要传递指针、地址或数组给该形参。
当通过传地址或指针给函数时,函数可以访问和修改调用者处的原始数据。这种方式称为 “指针传递”。这种方式在需要修改调用者处的变量值时非常有用,尤其是在处理大型数据结构时,可以避免不必要的数据复制,提高程序的效率。
输出结果如下所示:
数组作为函数参数传递时,实际上是传递数组的首地址,也就是说,传递的是一个指针。
传递数组给函数时,通常需要显式地传递数组的大小。这是因为当数组作为参数传递给函数时,实际上传递的是数组的首地址,而不是整个数组。在函数内部,数组退化为指针,因此函数无法直接获取数组的大小。如果尝试在函数内部使用 sizeof 操作符来获取数组的大小,实际上获取的是指针的大小,而不是数组的实际大小。如果不传递数组大小,函数内部可能会发生数组越界访问,导致未定义行为或程序崩溃。
输出结果如下所示:
问:如果在 getAverage() 函数中,通过指针修改了数组的值,那么 main 函数中的 balance 数组的值是否会相应变化?
答:如果在 getAverage 函数中通过指针修改了数组的值,那么 main 函数中的 balance 数组的值也会相应变化。这是因为传递给函数的是数组的首地址,函数内部对指针所指向的值的修改会影响到原始数组。简单来说: getVerage 函数中的指针,指向的就是 main 函数中的 balance 数组。如下代码所示:
输出结果如下所示:
指针函数是指返回值类型为指针的函数。换句话说,这类函数的返回值是一个指针,指向某种数据类型(如 int、char、struct 等)。
指针函数的声明需要指定返回值的指针类型和参数列表。语法格式如下:
函数运行结束后会销毁在其内部定义的所有局部数据,包括局部变量、局部数组和形式参数。因此,函数返回的指针不能指向这些数据。如果确实有这样的需求,需要将局部变量定义为静态局部变量。
静态局部变量的生命周期与整个程序相同,即使函数调用结束,静态局部变量仍然存在于内存中。因此,返回静态局部变量的地址是安全的。
输出结果如下所示:
在 C 语言中,字符串常量(如 "Hello, World!")存储在静态数据区,而不是栈上。因此,返回指向字符串常量的指针是安全的。
字符串常量的生命周期与整个程序相同,不会在函数调用结束后被销毁。
输出结果如下所示:
注意:
函数内部定义的局部变量在函数返回后会被销毁,因此返回局部变量的地址是不安全的。然而,对于这个案例:在 getString 函数中定义的 char *str 并不是指向一个局部变量,而是指向一个字符串常量。字符串常量在 C 语言中是存储在静态数据区的,因此返回它的地址是安全的。
使用 strlen 函数可以获取字符串的长度。通过比较两个字符串的长度,可以确定哪个字符串较长。
输出结果如下所示:
为什么这个案例没有传递数组的长度呢?
strlen 函数会遍历字符串,直到遇到 0,从而计算字符串的长度。例如,strlen(str1) 会从 str1 的首地址开始,逐个检查字符,直到遇到 0,返回字符数。
每个字符串数组在初始化时,最后一个元素会被自动设置为 0,除非你手动覆盖它。因此,str1 和 str2 都有明确的终止符,strlen 可以准确地计算字符串的长度。
注意:
函数内部定义的局部变量在函数返回后会被销毁,因此返回局部变量的地址是不安全的。然而,对于这个案例:在 main 函数中定义的字符串数组 str1 和 str2 存储在 main 函数的栈上。当调用 strlong 函数时,传递给 strlong 的参数 str1 和 str2 是 main 函数中数组的地址。这些参数在 strlong 函数调用结束后仍然有效,因为它们指向的是 main 函数中的数组,而不是 strlong 函数内部的局部变量。
编写一个函数 randArr,该函数生成 10 个随机数,范围是 [1,100],并使用数组名作为返回值。由于返回的数组需要在函数调用结束后仍然有效,因此必须将数组声明为静态局部变量。
输出结果如下所示:
返回局部变量:指针函数返回局部变量的地址是不安全的,原因在于局部变量是在栈上分配的,它们的生命周期仅限于定义它们的函数作用域内。当函数调用结束后,栈帧被销毁,局部变量所占用的内存也随之释放,因此返回的指针将指向不确定的内存区域,可能导致未定义行为。
返回字符串常量:指针函数返回指向字符串常量的指针通常是安全的,因为这些字符串常量通常存储在程序的只读数据段(静态数据区)中。其生命周期贯穿整个程序运行期间,因此指向这些常量的指针在程序运行时始终有效。然而,需要注意的是,不应尝试修改字符串常量内容,因为这可能会导致程序崩溃或未定义行为。
返回传递的参数:指针函数如果函数返回的是传递参数的地址(假设这些参数本身不是局部变量或临时变量),这通常是安全的。因为这些参数要么来自调用者的作用域(对于值传递的参数,其地址实际上指向调用者的变量),要么是通过指针或引用传递的,在调用者上下文中保持有效。不过,如果参数本身是局部变量且已被销毁(例如,如果参数是通过值传递的局部变量的地址),则返回这样的地址同样是不安全的。
返回静态局部变量或数组:指针函数返回静态局部变量或数组的地址是安全的,因为静态局部变量或数组存储在静态存储区,它们的生命周期与整个程序相同。静态局部变量或数组在程序的整个运行期间都存在,不会被函数调用结束所销毁,因此返回的指针在整个程序运行期间都是有效的。
在 C 和 C++ 编程中,函数总是占用一段连续的内存区域,并且函数名在表达式中有时会被编译器转换为该函数所在内存区域的首地址(即函数的入口地址)。这种特性使得函数名在某种程度上类似于数组名,因为数组名也代表数组首元素的地址。
利用这一特性,我们可以将函数的入口地址赋予一个指针变量,使该指针变量指向函数所在的内存区域。通过这个指针变量,我们可以间接地找到并调用相应的函数。这种指针被称为函数指针。
- 括号 () 的优先级高于星号 *,因此第一个括号不能省略。如果省略,编译器会将其解析为返回指针的函数(即指针函数),而不是函数指针。
- 在函数指针声明中,参数名是可以省略的,只保留类型即可。
- 函数指针调用时可以省略解引用操作符 *,直接使用:函数指针名(参数列表)。
下面我们实现用函数指针来实现对函数的调用,返回两个整数中的最大值。
输出结果如下所示:
注意:
上述案例在打印函数地址时,使用了 (void*) 进行强制类型转换,以避免潜在的警告或错误。然而,需要强调的是,在标准 C 中,获取和打印函数地址的行为是未定义的(undefined behavior)。尽管许多编译器允许这样做,但并不意味着它是可移植的或安全的。在实际编程中,通常不需要这样做,这里只是为了更好地讲解知识点。
函数指针可以作为参数传递给其他函数,这种通过函数指针调用的函数被称为回调函数。简而言之,回调函数是你在调用另一个函数时传入,并由该函数在适当时候执行的函数。
使用回调函数的方式,给一个整型数组 int arr[10] 赋 10 个随机数和其自定义内容。
输出结果如下所示:
输出结果如下所示:
1. 请写出下面程序的运行结果。
【答案】10 11
【解析】
(1)func 函数的形参 arg1 接收到的是变量 num1的值,接收完值后,两者不再有关系。
(2)func 函数的形参 arg2 接收到的是变量 num2 的地址,所以 arg2 仍然指向 num2。
2. 请写出下面程序的运行结果。
【答案】12
版权声明:
本文来源网络,所有图片文章版权属于原作者,如有侵权,联系删除。
本文网址:https://www.mushiming.com/mjsbk/4077.html