知识点#
7.1 函数的基本概念和定义#
- @ 函数基本概念图
- ![[白板/P15 函数基本概念.canvas|P15 函数基本概念]]
7.1.1 函数的基本概念#
- @ 函数: 代表一段可以复用的代码,减少重复代码,实现相对独立的功能
- % C 程序结构: 一个主函数(
main)+ 若干个其他函数 - !
main函数是程序执行的入口,由系统调用,名字固定 - ! 函数之间可以互相调用,但不要调用
main函数 - ! 函数不能嵌套定义(不能在函数内部定义另一个函数)
函数分类#
- % 库函数: 如
printf,直接使用,不需要自己定义 - % 自定义函数: 开发者自己写的函数
7.1.2 函数的定义和返回值#
[!tip] 函数定义
- 一般形式:
返回类型 函数名(形式参数列表) { 一条或多条语句 return 返回值; }
- 圆括号内的参数叫形式参数(形参)
函数定义范例#
无返回类型无形参:
void printhello() {
printf("hello world");
return; // 可以没有,因为无返回类型
}
有返回值有形参:
int addtwoshu(int a, int b) {
int sum = a + b;
return sum; // 返回变量 sum 的值
}
函数定义要点#
- ! 函数定义的第一行末尾没有分号
- ! 调用函数时为形参分配内存,调用结束后释放,形参只能在函数内部使用
- ! 实参可以是常量、变量、表达式
- ! 实参和形参的数量、类型要一致
- ! 参数传递是单向值传递:只由实参传给形参,不能由形参传给实参
int result = addtwoshu(3, 4); // 常量作实参
int result = addtwoshu(1+2, 2+2); // 表达式作实参
int result = addtwoshu(i, j); // 变量作实参
return 语句的多种形式#
// 范例1: 返回表达式的值
int addtwoshu(int a, int b) {
return a + b;
}
// 范例2: 根据条件有多个 return
int whichmax(int a, int b) {
if(a > b)
return a;
return b;
}
// 范例3: 返回类型不一致时系统自动转换(不建议)
int testf() {
return 3.45F; // 实际返回3,不建议
}
7.2 函数调用方式和嵌套调用#
- @ 函数调用图
- ![[白板/P16 函数调用.canvas|P16 函数调用]]
7.2.1 函数调用的一般形式#
- 函数调用一般形式:
函数名(实参列表); - ! 无形参时圆括号不能省略
- ! 实参和形参个数相等、类型一致、按顺序对应
7.2.2 函数调用的方式#
| 调用方式 | 示例 | 说明 |
|---|---|---|
| 作为语句 | printhello(); | 末尾加分号 |
| 在表达式中 | int result = addtwoshu(3,4) * 100; | 需要带回确定值 |
| 作为另一个函数的参数 | whichmax(44, whichmax(12,19)) | 先计算内层参数 |
函数声明#
[!tip] 函数声明
- @ 函数声明: 在调用函数之前声明该函数,使编译器知道函数的参数类型和返回值类型
- 一般形式:
返回类型 函数名(形式参数列表);- ! 函数声明末尾有分号,函数定义末尾没有分号
// 函数定义(无分号,有函数体)
void printhello() {
printf("hello, how are you!\n");
}
// 函数声明(有分号,无函数体)
void printhello();
// 函数调用
printhello();
- ! 可以把函数声明写在
.h头文件中,用#include包含 - ! 严格区分函数定义和函数声明:定义包含函数体,声明不包含
7.2.3 函数的嵌套调用#
- ! C 语言不允许嵌套定义函数(不能在函数内定义另一个函数)
- ! C 语言允许嵌套调用函数(在函数内调用另一个函数)
void qtfunc1() {
printf("qtfunc1开始执行\n");
qtfunc2(); // 嵌套调用 qtfunc2
printf("qtfunc1结束执行\n");
}
void qtfunc2() {
printf("qtfunc2开始执行\n");
qtfunc3(); // 嵌套调用 qtfunc3
printf("qtfunc2结束执行\n");
}
void qtfunc3() {
printf("qtfunc3开始执行\n");
printf("qtfunc3结束执行\n");
}
7.3 函数递归调用精彩演绎#
- @ 递归调用图
- ![[白板/P17 递归调用.canvas|P17 递归调用]]
7.3.1 函数递归调用的定义#
[!tip] 递归调用
- @ 递归调用: 函数自己调用自己,属于特殊的嵌套调用
- ! 递归必须有出口条件,否则会无限调用导致内存耗尽、程序崩溃
// 错误示范:无出口条件的递归,会导致程序崩溃
void diguifunc() {
printf("diguifunc()函数执行\n");
diguifunc(); // 自己调用自己,无出口!
}
- ! 每次调用函数都会占用内存(局部变量、函数参数、调用关系等),嵌套层次越深内存越大
- ! 系统分配的内存是有限的,超过限制程序就会崩溃
7.3.2 递归的两种形式#
- % 直接递归: 函数直接调用自己
- % 间接递归: 函数 A 调用函数 B,函数 B 又调用函数 A
7.3.3 递归的实际运用#
- ! 递归的两个出口条件:一是判断条件是否满足,二是是否有剩余资源
// 伪代码:战棋游戏寻路(递归实现)
void pf_zhanqiFindPath(int posx, int posy, int shengyuyidongli) {
if(上方图块存在) {
判断是否能走;
如果能走 {
保存位置,扣除移动力;
pf_zhanqiFindPath(); // 递归调用
}
}
// 下方、左边、右边同理...
}
7.4 数组作为函数参数#
- @ 数组参数图
- ![[白板/P18 数组参数.canvas|P18 数组参数]]
7.4.1 数组元素作为函数实参#
- 数组元素可以当作普通变量使用,作为实参调用函数
- ! 依旧是值传递
int whichmax(int x, int y) {
if(x > y) return x;
return y;
}
int main() {
int a[10];
a[1] = 5; a[4] = 7;
int b = whichmax(a[1], a[4]); // 数组元素作实参,值传递
return 0;
}
7.4.2 数组名作为函数实参#
[!tip] 数组名作参数
- 数组名代表数组的首地址
- ! 数组名作参数时是地址传递,不是值传递
- ! 形参数组元素改变,实参数组元素也跟着改变
void changevalue(int ba[]) { // 形参为数组名
ba[3] = 27; // 修改形参数组,实参数组也改变!
ba[4] = 36;
return;
}
int main() {
int a[5] = {85, 70, 68, 92, 78};
changevalue(a); // 数组名作实参,传递首地址
// a[3] 变为 27,a[4] 变为 36
return 0;
}
数组名作参数要点#
- ! 实参为数组名,形参也应为数组名
- ! 实参数组与形参数组类型必须一致
- ! 形参数组大小可以不指定(C 编译器不做检查)
- ! 超过实参数组大小的部分不要引用,否则程序崩溃
7.4.3 用多维数组作为函数实参#
- 形参数组可以省略第一维大小,但不能省略第二维大小
- ! 实参是多少行多少列,形参尽量跟实参一样
void changevalue2(int ba[5][8]) {
ba[0][2] = 15;
return;
}
int main() {
int a[5][8];
a[0][2] = 12;
changevalue2(a);
return 0;
}
7.5 局部变量和全局变量#
- @ 变量作用域图
- ![[白板/P19 变量作用域.canvas|P19 变量作用域]]
7.5.1 局部变量#
[!tip] 局部变量
- @ 局部变量: 在函数内部定义的变量,只在本函数范围内有效
- ! 不同函数中可以使用相同变量名,互不干扰
- ! 形参也是局部变量
- ! 复合语句
{}中定义的变量只在该复合语句内有效
void funcl(int tmpvalue) {
// 无法使用 main 中的 m, n, k
int x, y; // 只在 funcl 内有效
}
int main() {
int m, n;
int k = 4;
{
int c; // 只在本复合语句内有效
c = a + b;
}
// c 在这里无效,内存已释放
return 0;
}
7.5.2 全局变量#
[!tip] 全局变量
- @ 全局变量(外部变量): 在函数外定义的变量,可为本文件中其他函数共用
- 有效范围: 从定义位置开始到本源程序文件结束
int p = 1, q = 5; // 全局变量,从这行开始有效
int f1(int a) {
// 可以使用 p, q
// 不能使用 c1, c2(定义在下面)
}
char c1, c2; // 全局变量,从这行开始有效
char f2(int x, int y) {
// 可以使用 p, q, c1, c2
}
int main() {
// 可以使用 p, q, c1, c2
}
全局变量的优缺点#
| 优点 | 缺点 |
|---|---|
| 增加函数间数据联系通道 | 程序执行期间一直占用内存 |
| 相当于能返回多个值 | 降低函数通用性 |
| 不需要通过形参传递 | 降低程序清晰性和可读性 |
- ! 要限制使用全局变量
extern 关键字#
- ! 如果函数想引用在它后面定义的全局变量,用
extern做外部变量说明 - ! 外部变量说明不能给初值,全局变量定义时可以给初值
extern int cl, c2; // 外部变量说明,不能写成 extern int cl=0, c2=1
void lookvalue() {
cl = 5; // 因为前面用了 extern,所以可以使用
c2 = 8;
}
int cl, c2; // 这里才是全局变量定义的地方
- ! 全局变量定义只能有一次,外部变量说明可以有多次
- ! 同一文件中全局变量和局部变量同名时,在局部变量作用域内全局变量不起作用
int a = 4, b = 5; // 全局变量
void lookvalue(int a, int b) {
a = 123; // 修改的是局部变量,不影响全局变量
b = 456;
}
int main() {
lookvalue(i, j);
printf("a=%d\n", a); // a=4,全局变量不变
printf("b=%d\n", b); // b=5,全局变量不变
}
跨文件引用全局变量#
- ! 在其他源程序文件中用
extern说明即可引用
// MyProject2.cpp 中定义
int g_a = 6;
// MyProject.cpp 中引用
extern int g_a; // 外部变量说明
7.6 变量的存储和引用与内部和外部函数#
- @ 变量存储与函数图
- ![[白板/P20 变量存储与函数.canvas|P20 变量存储与函数]]
7.6.1 变量的存储类别#
| 存储方式 | 说明 | 存储区 |
|---|---|---|
| 静态存储方式 | 程序运行期间分配固定存储空间 | 静态存储区 |
| 动态存储方式 | 程序运行期间动态分配和释放存储空间 | 动态存储区 |
- % 静态存储区: 存放全局变量,程序开始执行时分配,执行完毕后释放
- % 动态存储区: 存放函数形参、局部变量、函数调用现场数据等
7.6.2 局部变量的存储方式#
传统情形#
- 函数中的局部变量一般动态分配存储空间
- 函数调用时分配,执行完成后自动释放
特殊情形:static 局部变量#
- ! 用
static修饰的局部变量保存在静态存储区 - ! 函数调用结束后不释放,下次调用时保持上次的值
- ! 如果不给初值,自动初始化为
0
// 普通局部变量:每次调用都重新初始化
void funcTest() {
int c = 4; // 每次调用 c 都是 4
printf("c=%d\n", c);
c++;
}
// 输出: c=4, c=4, c=4
// static 局部变量:保持上次离开时的值
void funcTest() {
static int c = 4; // 只在第一次调用时初始化
printf("c=%d\n", c);
c++;
}
// 输出: c=4, c=5, c=6
7.6.3 全局变量的跨文件引用#
- ! 用
extern在其他源程序文件开头说明即可引用 - ! 用
static修饰全局变量,则该变量只能在本文件中使用
// MyProject2.cpp
static int g_a = 6; // 只能在本文件中使用,其他文件无法 extern 引用
- ! 不同文件中定义同名全局变量时,用
static修饰可以避免冲突
7.6.4 函数的跨文件调用#
| 函数类型 | 定义方式 | 说明 |
|---|---|---|
| 内部函数 | static 返回类型 函数名(形参表){...} | 只能被本文件调用 |
| 外部函数 | 返回类型 函数名(形参表){...} | 可被其他文件调用 |
- ! 外部函数在其他文件中调用时,只需增加函数声明即可
- ! 建议将函数声明统一放在
.h头文件中
// MyProject2.cpp 中定义外部函数
void g_otherfunc() {
printf("外部函数g_otherfunc()\n");
}
// MyProject.cpp 中声明并调用
void g_otherfunc(); // 函数声明
int main() {
g_otherfunc(); // 调用
}
7.6.5 static 关键字用法总结#
| 用法 | 示例 | 说明 |
|---|---|---|
局部变量前加 static | static int tmpvalue = 1; | 保存在静态存储区,保持上次值 |
全局变量前加 static | static int g_a = 6; | 只能在本文件中使用 |
函数定义前加 static | static void g_func(){...} | 只能在本文件中调用 |

