1. 1函数的基本使用
- 问题背景:在编程中,重复编写相同的代码段会导致程序臃肿且难以维护。
- 解决方案:将重复的代码段封装成函数,通过函数调用重复使用这些代码段。
- 好处:实现代码重用,减少冗余,简化代码。
- 一个C源程序可以由一个或多个源文件构成(C文件扩展名是 “.c”),一个源文件是一个编译单位。一个源文件可以由若干个 函数构成,函数之间可以相互调用。也就是说, 函数是C程序基 本的组成单位 。
1.2 函数的分类
- 从程序执行的角度看:
- 主函数:
main()
函数,每个C程序必须有一个且只有一个。 - 子函数:非
main()
函数,可以被main()
函数或其他子函数调用。
- 主函数:
- 从源文件外调用角度看:
- 内部函数:只能被定义它的文件内的函数调用。
- 外部函数:可以被其他文件内的函数调用。
- 从用户使用的角度看:
- 库函数:由C系统提供,如字符串操作、字符操作、时间/日期、数学、IO、内存操作等函数。
- 用户自定义函数:解决用户特定业务需求的函数。
1.3 函数的声明格式
返回值类型 函数名(数据类型1 形参1, 数据类型2 形参2, ..., 数据类型n 形参n) { | |
函数体; | |
} |
//计算两个整数的和,并返回
int add(int m,int n) {
return m + n;
}
//计算两个整数的较大值,并返回
int max(int a, int b){ //定义函数max()
int c;
c = a > b ? a : b; //求a,b两个数的最大值,赋给c
return c; //将最大值返回
}
void printMax(int x,int y){
int z;
z = x > y ? x : y; //求x,y两个数的最大值,赋给z
printf("%d\n",z);
}
- 返回值类型:无返回值的类型 :针对函数无返回值或明确不需返回值的情 况,使用 void (即空类型)表示。 举例:输出函数 void printf(const char *format, ...)。 有返回值的类型 :指明具体的类型。比如, int、float、char 等。如果省略,默认为int类型。 有返回值类型,则需要在函数体内与“ return 返回值 ”搭 配使用。返回值需要与返回值类型一致。 举例:int rand(),调用后返回一个随机整数
- 函数名:遵循标识符命名规则,见名知意。
- 参数列表:无参函数 ,在调用无参函数时,主调函数不向被调用函数传递 数据。但函数名后的()不能省略。 举例:abort():立即终止程序的执行,不接受任何形参。 有参函数 ,在调用函数时,主调函数在调用被调用函数时,通 过参数向被调用函数传递数据。 函数参数为多个参数时,其间用逗号隔开。 举例:add(int m,int n),strcmp(const char *str1, const char *str2)
- 函数体:函数被调用后要执行的代码块。
- return语句的作用:① 结束函数的执行 ②将函数运算的结果返 回。 return语句后面就不能再写其它代码了,否则会报错。(与 break、continue情况类似) 下面分两种情况讨论: 情况1:返回值类型不是void时,函数体中必须保证一定有 return 返回值; 语句,并且要求该返回值结果的类型与声 明的返回值类型一致或兼容。 情况2:返回值类型是void时,函数体中可以没有return语 句。如果要用return语句提前结束函数的执行,那么return后 面不能跟返回值,直接写 return; 就可以。
1.4 声明注意事项
- 函数不能嵌套定义:一个函数内部不能定义另一个函数。
- 函数名唯一:程序中不能有重名的函数(不支持重载)。
1.5 函数的调用
- 调用方式:在函数调用时传入实际的参数值。
- 调用规则:
- 参数个数必须与函数声明中的参数个数一致。
- 函数间可以相互调用,但不能调用
main()
函数。 - 参数和返回值类型会根据需要进行自动类型转换。
void func() {
printf("这是我的第一个函数!\n");
}
int main() {
func();
//func(10); // 报错
//func(10,20); // 报错
return 0; //程序正常结束,默认返回0
}
int func(int x, int y) {
return x + y;
}
int main() {
int sum = func(3, 5);
printf("x+y=%d\n", sum);
return 0;
}
说明: 1、调用时,参数个数必须与函数声明里的参数个数一致,参数 过多或过少都会报错。 2、函数间可以相互调用,但不能调用main函数,因为main函数 是被操作系统调用的,作为程序的启动入口。反之,main() 函数 可以调用其它函数。 3、函数的参数和返回值类型,会根据需要进行自动类型转换。
2. 进一步认识函数
2.1 关于main()
- 作用:程序的入口点,每个C程序必须包含。
- 返回值:
int main()
函数默认返回0表示成功,返回非零值表示错误。 - 参数:可以带有两个参数
int argc, char *argv[]
,用于接收命令行参数。
2.2 关于exit()
- 作用:终止整个程序的运行。
- 参数:可以返回给操作系统一个状态码,表示程序是正常结束还是异常终止。
2.3 函数原型
- 定义:在函数声明时,只包含函数的基本信息(不包括函数体),用于在函数调用前告诉编译器函数的存在。
- 作用:确保函数在使用前已被声明。
3. 参数传递机制
3.1 形参、实参
- 形参:函数定义时的参数,不占用内存,调用时分配内存。
- 实参:函数调用时的参数,传递给形参。
int add(int x, int y) { //x,y是add()中的形参
int z;
z = x + y;
return (z);
}
int main() {
int c;
int a = 10,b = 20;
c = add(a, b); //此时将实参a,b赋值给add()的形参
printf("sum is %d\n", c);
return 0;
}
3.2 值传递
- 定义:将实参的值复制给形参,形参的改变不影响实参。
- 适用类型:基本数据类型、结构体、共用体、枚举类型。
3.3 地址传递
- 定义:将实参的地址传递给形参,形参通过地址修改实参的值。
- 适用类型:指针、数组。
3.4 数组、字符串作为形参
- 数组名:作为形参时,传递的是数组首元素的地址。把数组名传入一个函数,就等同于传入一个指针变量。在函 数内部,就可以通过这个指针变量获得整个数组。
- 字符串:作为形参时,与数组类似,传递的是字符串首字符的地址。
4. 函数的高级应用
4.1 递归函数
- 定义:函数直接或间接调用自身的现象。
// 递归函数,计算n的阶乘
int factorial(int n) {
// 基本情况:当n为0或1时,阶乘为1
if (n == 0 || n == 1) {
return 1;
} else {
// 递归情况:n! = n * (n-1)!
return n * factorial(n - 1);
}
}
int main() {
int n = 5;
// 调用递归函数计算n的阶乘
int result = factorial(n);
printf("%d! = %d\n", n, result);
return 0;
}
- 注意事项:必须有基本情况以结束递归,避免无限递归导致栈溢出。
4.2 可变参数
- 定义:函数参数数量不固定,使用省略号
...
表示。 - 使用:需要包含头文件
<stdarg.h>
,并使用va_list
、va_start
、va_arg
、va_end
宏处理可变
4.3 指针函数(返回值是指针)
- 定义:函数的返回值是一个指针(地址),这样的函数称为指针函数。
- 一般格式:
返回值类型 *函数名(形参列表) {
函数体
}
- 示例:获取两个字符串中较长的那个字符串
#include <stdio.h> #include <string.h> char *maxLengthStr(char *str1, char *str2) { //函数返回 char * (指针) printf("\nstr1的长度%d,str2的长度%d", strlen(str1), strlen(str2)); if (strlen(str1) >= strlen(str2)) { return str1; } else { return str2 } } int main() { char str1[30], str2[30]; printf("请输入第1个字符串:"); gets(str1); printf("请输入第2个字符串:"); gets(str2); char *str; str = maxLengthStr(str1, str2); printf("\nLonger string: %s \n", str); return 0; }
4.4 函数指针(指向函数的指针)
- 定义:函数指针是指向函数的指针,通过它可以调用函数。
- 一般格式:
返回值类型 (*指针变量名)(参数列表);
- 示例:用函数指针来实现对函数的调用,返回两个整数中的最大值。
int max(int a, int b) {
return a > b ? a : b;
}
int main() {
int x, y;
int (*pmax)(int, int) = &max; // 使用函数指针
printf("输入两个整数:");
scanf("%d %d", &x, &y);
int maxVal = (*pmax)(x, y);
printf("较大值为: %d\n", maxVal);
return 0;
}
4.5 回调函数
- 定义:把函数a的地址作为参数传递给函数b,函数b就是回调函数。
- 示例:使用回调函数给数组赋随机值
4.6 函数说明符
- 内部函数(静态函数):使用
static
修饰,只能被定义它的文件调用。 - 外部函数:默认或显式使用
extern
修饰,可被程序中的任何文件调用。
5. 变量
5.1 按声明位置的不同分类
- 局部变量:在函数体内定义的变量或函数的形参,作用域仅限于定义它的函数或代码块内。
- 全局变量:在函数外部定义的变量,作用域默认是整个程序。
5.2 按存储方式的不同分类
5.2.1 动态存储方式
- 自动变量:在函数内部定义的局部变量,默认使用动态存储方式。它们在函数调用时自动分配内存,函数返回时自动释放内存。自动变量可以使用
auto
关键字声明(尽管这通常是隐含的,因为默认就是自动存储类)。
5.2.2 静态存储方式
- 静态局部变量:使用
static
关键字声明的局部变量。它们在函数调用之间保持其值,不会被自动销毁和重新初始化。 - 全局变量和静态全局变量:全局变量默认使用静态存储方式,在整个程序执行期间都保持其值。静态全局变量用
static
修饰,其作用域仅限于定义它的文件。
5.3 其它变量修饰符
5.3.1 寄存器变量
- 寄存器变量:使用
register
关键字声明的变量。编译器会尝试将这些变量存储在CPU的寄存器中,以提高访问速度。然而,现代编译器通常能够自动优化变量存储位置,因此手动声明寄存器变量变得不那么必要。
5.3.2 extern
修饰变量
- 扩展全局变量的作用域:当全局变量不在文件开头定义时,可以使用
extern
关键字在引用点之前声明该变量,以扩展其作用域。 - 跨文件共享全局变量:在多个文件中共享同一个全局变量时,在一个文件中定义该变量,在其他文件中使用
extern
声明它。
5.3.3 const
修饰变量
- 常量变量:使用
const
声明的变量在编译时就被赋予了一个固定的值,并且在程序的执行期间不能被修改。 - 指向常量的指针:
const
可以修饰指针所指向的值,确保这些值不会被修改。 - 常量指针:
const
也可以修饰指针本身,表示指针的值(即地址)不能被修改,但指针指向的值可以被修改(如果指针不是指向常量的话)。 - 指向常量的常量指针:同时修饰指针和指针指向的值,确保两者都不能被修改。