跳过正文
P6 函数
  1. S/
  2. C/C++/
  3. C/
  4. C语言程序设计/

P6 函数

Li
作者
Li
往前走,别回头!
目录
C 语言程序设计笔记 - 这篇文章属于一个选集。

知识点
#

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 关键字用法总结
#

用法示例说明
局部变量前加 staticstatic int tmpvalue = 1;保存在静态存储区,保持上次值
全局变量前加 staticstatic int g_a = 6;只能在本文件中使用
函数定义前加 staticstatic void g_func(){...}只能在本文件中调用
Reply by Email
C 语言程序设计笔记 - 这篇文章属于一个选集。

相关文章