文章目录
1 函数是什么
函数是已命名的、执行特定任务的独立C代码段。
- 函数是已命名的。每个函数都有独一无二的函数名,在程序中使用函数名,可以执行该函数中的语句,称为函数调用;
- 函数执行特定任务。例如pritnf(输出到屏幕)、scanf(读取键盘输入)、strlen(统计字符串长度)、strcmp(比较两字符串大小)等库函数都是实现某一项特定的功能;
- 函数是独立的。函数可独立执行任务,无需程序其他部分干预。
2 函数的分类
C语言中的函数可以分成两类:
- 库函数
- 自定义函数
2.1 库函数
C语言的库函数是指提供给开发人员使用的预定义函数,这些函数通过标准的C语言库提供。C语言的库函数可以加快开发过程,提供常见的功能和算法,以及通过封装底层操作系统接口实现与操作系统的交互。
一些常见的C库函数如下表所示,详情可参考:C语言标准库:
需求:将一个字符串拷贝到另一个字符串中,并打印两个字符串,示例代码如下所示:
#include <stdio.h>
#include <string.h>
int main ()
{
char str1[20] = { 0 };
char str2[] = "hello world";
strcpy(str1, str2);
printf("%s\n", str1);
printf("%s\n", str2);
return 0;
}
2.2 自定义函数
自定义函数是由程序员自己编写的一段具有特定功能的代码块,通过给函数一个名称和参数列表,并在需要时调用该函数来执行特定的任务。
自定义函数通过将一系列的语句组合在一起,形成一个独立的模块,以便在程序中重复使用,提高代码的可维护性和重用性。
自定义函数的使用通常分为两个步骤:
- 函数定义:在程序的任意位置,通过编写函数的定义代码来创建一个自定义函数。
- 函数调用:在需要使用该函数的地方,通过函数名称和参数列表来调用函数,并执行其中的代码。
3 函数定义
自定义函数由函数头和函数体组成:
- 函数头包括函数名、返回类型以及形参列表;
- 函数体是由一对大括号括起来的代码块。
自定义函数的语法格式如下所示:
返回类型 函数名 (形参列表)
{
函数体
}
3.1 函数头
3.1.1 返回类型
函数的返回类型指定了函数返回值的数据类型,函数的返回值相当于函数的输出。
- 函数的返回类型可以是C语言的任意数据类型;
- 函数的返回类型可以是void,不返回任何值给调用程序;
- 函数的返回类型不是void的时候,函数的返回值只能有一个。
3.1.2 函数名
- 函数名的命名规则遵循C语言变量的命名规则;
- 函数名必须唯一,不能与其他函数和变量同名;
- 函数名应反应该函数的功能或用途。
3.1.3 形参列表
函数的形参列表指定了函数可接受的实参的类型和数量,形参相当于函数的输入。
- 函数的形参由形参类型和形参名组成,多个形参之间用逗号隔开;
- 函数的形参可以是C语言的任意数据类型;
- 函数的形参可以是void,不接收调用程序提供的任何实参。
3.2 函数体
- 函数体位于函数头后边的花括号中;
- 函数的功能是在函数体中实现的;
- 调用函数时,从函数体的第一条语句开始执行,直至函数体的最后一条语句或遇到return语句为止。
3.2.1 局部变量
- 函数体中声明的变量是局部变量,局部变量可以是任意类型,可以在声明时初始化局部变量;
- 局部变量属于函数私有,只能在函数内部使用,可以与函数外部的变量同名;
- 函数的形参列表可视为函数的局部变量声明,可在函数内部使用形参列表中的变量;
- 尽管在函数中可以使用全局变量,但是建议尽量不使用全局变量,因为局部变量使得函数的独立性更强,可以在不同的程序中移植或复用。
再次强调:在函数中声明的变量,完全独立于程序其他部分声明的变量,函数内外声明的变量可同名,示例代码如下图所示。
void demo (void)
{
int x = 88;
int y = 99;
printf("Within demo(), x = %d, y = %d\n", x, y);
}
int main()
{
int x = 11;
int y = 22;
printf("Before calling demo(), x = %d, y = %d\n", x, y);
demo();
printf("After calling demo(), x = %d, y = %d\n", x, y);
return 0;
}
示例代码运行结果如下图所示:
实例代码运行结果分析:demo()函数内部的局部变量x和y完全独立于demo()函数外部的全局变量。
在函数中使用变量需遵循以下3条规则:
- 要在函数中使用变量,必须现在函数头或函数体中声明变量(全局变量除外);
- 要在函数中获得调用程序的值,必须将该值作为实参传递给函数;
- 要在调用程序中获得函数的值,必须将该值从函数中显示返回。
3.2.2 函数语句
- 在函数中可以使用任意C语言;
- 在函数中可以调用库函数和自定义函数;
- 在函数中唯一不能做的就是定义其他函数;
- 在结构化程序设计中,每个函数都应该只完成一个较简单的任务。
3.2.3 返回值
return 表达式;
- 关键字关键字右边的表达式可以是任何有效的C表达式;
- 函数使用return关键字将右边表达式的值返回给调用程序;
- 函数执行到return语句时,计算右边表达式的值,将计算结果返回调用程序;
- 函数可以包含多个return语句,只有第一个被执行的return语句有效。
示例1:没有return语句,代码如下图所示:
void print (void)
{
printf("hello world");
}
示例代码分析:上述代码中,print()函数无参数,无返回值,无return语句。
示例2:有多条return语句,代码如下图所示:
int max (int a, int b)
{
if (a > b)
{
return a;
}
else
{
return b;
}
}
示例代码分析:上述代码中有两条return语句,根据形参a和b的大小关系,只有一条return语句会被执行。
4 函数原型
在C语言中,函数原型用于声明函数名称、参数类型以及返回类型。函数原型可以理解为函数的“说明书”,用于编译器在编译时进行函数匹配和类型检查。函数原型的一般格式如下所示:
返回类型 函数名 (形参列表);
- 在形式上:函数原型相当于函数头 末尾加上分号;
- 在功能上:函数原型将函数的基本情况告知编译器,编译器在编译时对函数的参数和返回值进行检查(函数实参的数量和类型是否与函数形参的数量和类型相符、函数返回值是否与返回类型相符);
- 在使用上:建议将程序所有函数的函数原型统一放在一个头文件中,便于更好地组织和管理程序中的函数,使代码更加清晰易懂。
5 函数传参
5.1 实参
实参即实际参数:
- 实参是函数调用程序传递给函数的实际值;
- 实参可以是任何有效的C表达式,例如常量、变量、数据表达式、逻辑表达式和有返回值的函数等;
- 每次调用函数时,传递给函数的实参类型和数量必须相同,但实参的值可以不同
实参是函数返回值示例:
x = half(third(square(half(y)))); // half()、square()和third()都是由返回值的函数
<=>
a = half(y);
b = square(a);
c = third(b);
x = half(c);
5.2 形参
形参即形式参数:
- 形参是只在函数内部有效的局部变量,它在函数被调用时自动创建(分配内存单元),在函数调用完后自动销毁(释放内存单元)。
5.3 传参
- 实参通过形参传入函数;
- 实参的值按照顺序依次复制到对应的形参中去;
- 实参和形参在类型和数量上要完全匹配。
我们知道,变量对应一段内存空间:
- 这段内存空间里存储的值就是变量的值,在程序中直接使用变量其实就是使用变量的值;
- 这段内存空间的起始地址就是变量的地址,在程序中需要用取地址运算符才能得到变量的地址;
5.3.1 传值
传值:
- 把变量的值作为实参传递给形参;
- 形参是实参在内存中的一份临时拷贝;
- 形参和实参完全独立;
- 对形参的操作不会对实参产生任何影响。
写一个函数交换两个变量的内容,代码如下所示:
void Swap(int x, int y)
{
int z = 0;
z = x;
x = y;
y = z;
}
int main()
{
int a = 0;
int b = 0;
//输入
printf("输入a和b的值:>\n");
scanf("%d %d", &a, &b);
//交换前
printf("交换前:a=%d b=%d\n", a, b);
//交换中
Swap(a, b);
//交换后
printf("交换后:a=%d b=%d\n", a, b);
//
printf("\n");
return 0;
}
代码运行结果如下图所示:
函数Swap并未实现交换变量a和b的内容的功能,代码及运行结果分析:
- 实参a和b只是将其值传递给形参x和y;
- 形参x和y只是实参a和b在内存中的一份拷贝;
- 形参x和y与实参a和b完全独立;
- 交换形参x和y的值不会对实参a和b造成任何影响。
5.3.2 传址
传址:
- 把变量的内存地址作为实参传递给形参;
- 传址可以让函数的形参和函数外部的变量之间建立起联系,即函数通过操作形参来操作函数外部的变量。
写一个函数交换两个变量的内容,代码如下所示:
void Swap(int* px, int* py)
{
int z = 0;
z = *px;
*px = *py;
*py = z;
}
int main()
{
int a = 0;
int b = 0;
//输入
printf("输入a和b的值:>\n");
scanf("%d %d", &a, &b);
//交换前
printf("交换前:a=%d b=%d\n", a, b);
//交换中
Swap(&a, &b);
//交换后
printf("交换后:a=%d b=%d\n", a, b);
//
printf("\n");
return 0;
}
代码运行结果如下图所示:
函数Swap能够实现交换变量a和b的内容的功能,代码及运行结果分析:
- 通过传址的方式将变量a和b的地址传递给函数Swap的形参px和py;
- 函数Swap通过操作px和py(即变量a和b的地址)来"远程"改变变量a和b的值。
5.3.3 总结
什么情况下传值?什么情况下传址?这里就只需关注一点,即函数是否需要改变外部变量的值;如果需要,就传址;如果不需要,就传值。
6 函数调用
6.1 嵌套调用
函数之间是可以互相调用的,称为函数的嵌套调用,示例代码如下:
void new_line(void)
{
printf("hello world\n");
}
void three_line(void)
{
int i = 0;
for (i = 0; i < 3; i++)
{
new_line();
}
}
int main()
{
three_line();
return 0;
}
代码运行结果如下:
代码及运行结果分析如下:
- main()函数调用three_line()函数,three_line()函数调用new_line()函数。
特别说明:函数可以嵌套调用,但是不能嵌套定义,即在一个函数的函数体中定义另外一个函数。
6.2 链式访问
一个函数的返回值作为另外一个函数的实参,称为函数的链式访问,示例代码如下所示:
int main()
{
printf("%d\n", strlen("hello world"));
printf("%d\n", printf("%d", printf("%d", 43)));
return 0;
}
代码运行结果如下图所示: