C和指针:函数
函数定义
函数体就是一个代码块,它在函数被调用时执行。
类型
函数名(形式参数)
代码块
与函数定义相反,函数声明出现在函数被调用的地方。
函数声明
编译器是如何知道该函数期望接受的是什么类型和多少数量的参数。
原型
int *find_int( int key, int array[1, int len);
原型告诉编译器函数的参数数量和每个参数的类型以及返回值的类型。
编译器见过原型之后,就可以检查该函数的调用,确保参数正确、返回值无误。
C 语言编程中,最好函数原型声明放在头文件中,通过 #include
指令将其包含到多个源文件中,
(1)减少代码冗余,如果每个地方都手动输入函数原型,可能会导致不同地方的原型不一致,进而导致编译错误或运行时错误。
(2)修改方便。当函数的定义发生变化时,如果原型分散在各个地方,则需要在所有地方进行相应的修改,这既耗时又容易出错。将函数原型放在头文件时,函数需要修改,只需在一个地方进行修改,然后重新编译所有引用了该原型的文件。
函数的参数
C 函数的所有参数以"传值调用"方式进行传递,函数将获得参数值的一份拷贝,函数修改这个拷贝值,不会修改调用程序实际传递给它的参数。
如果传入数组,相当于传入一个指针,可以直接修改数组的元素。数组名的值实际上是一个指针,传递给函数的就是这个指针的一份拷贝。下标引用 实际上是 间接访问的另一种形式,可以对指针执行间接访问操作,访问指针指向的内存位置,参数(指针)实际上是一份拷贝,但在这份拷贝上执行间接访问操作所访问的是原先的数组。
ADT和黑盒
C可以使用static限制函数和数据的作用域(黑盒设计)。在设计模块的时候,隐藏的细节比如函数和数据结构如果不想让其他模块访问,可以加上static关键字。使用模块的用户不需要知道模块实现的任何细节,只能访问模块提供的接口。
递归
C通过运行时堆栈支持递归函数的实现。递归函数就是直接或间接调用自身的函数。但是在简单的任务钟,递归的效率不如迭代。比如斐波拉契数列最好使用for循环实现。
可变参数列表
函数可以接受一个不定长的参数列表
stdarg 宏
va_list: 一个类型,用来存储可变参数列表的信息。
va_start: 宏va_start用于初始化 va_list 类型的变量,它接受两个参数:第一个是要初始化的 va_list 类型的变量;第二个是省略号前的最后一个固定参数。
va_arg: 宏va_arg 用于获取下一个可变参数的值。它同样接受两个参数:第一个是 va_list 类型的变量;第二个是参数的类型。
va_end: 宏va_end 用于清理 va_list 类型的变量,确保资源得到释放。
#include <stdarg.h> // 引入 stdarg.h 头文件
#include <stdio.h> // 引入 stdio.h 头文件
// 定义一个打印可变参数列表的函数
void print_varargs(const char *prefix, ...) {
// 定义一个 va_list 类型的变量来保存可变参数列表的信息
va_list args;
// 使用 va_start 初始化 args 变量
va_start(args, prefix); // 第二个参数是省略号前的最后一个参数
// 访问可变参数列表中的每一个参数
while (1) {
// 尝试获取一个 int 类型的参数
int arg = va_arg(args, int);
// 如果取到的参数是 0,那么认为参数列表已经结束(这里假设所有参数非零)
if (arg == 0) {
break;
}
// 打印参数
printf("%s%d\n", prefix, arg);
}
// 使用 va_end 完成对可变参数列表的处理
va_end(args);
}
int main() {
// 调用 print_varargs 函数,传入一个前缀字符串和一些整数参数
print_varargs("Value: ", 10, 20, 30, 0); // 注意,这里的 0 用来标志参数列表的结束
return 0;
}