首页计算机计算机软件C语言程序设计 06 第六章 函数.pptx

06 第六章 函数.pptx

页数:51页文件大小:804.9 KB来源: C语言程序设计ppt课件

本课件讲解C语言的函数与模块化程序设计,先介绍模块化思想、C程序的模块化结构与函数分类,再说明函数定义的两种风格、返回值与函数调用形式、函数原型声明与嵌套调用,然后重点对比值传递与地址传递,最后系统梳理变量的作用域与生存期以及auto、register、static、extern四种存储类型。

第六章函数导览页,列出函数定义、返回值、调用与参数传递等主要知识点
模块化程序设计页,说明自顶向下分而治之思想与函数的黑盒模型
C程序模块化结构页,说明源文件组成与main函数唯一且执行起止的规则
查看本课件的文字版(共 51 页,供检索与复制)

第1页:第六章函数概述

Chapter 6 函数(Functions)

  • 概述:模块化程序设计
  • 函数的定义
  • 函数的返回值
  • 函数的调用
  • 函数参数及其传递方式
  • 变量的存储属性

第2页:模块化程序设计

模块化程序设计

  • 基本思想:将一个大的程序按功能分割成一些小模块
  • 特点:各模块相对独立、功能单一、结构清晰、接口简单;控制程序设计的复杂性;提高元件的可靠性;缩短开发周期;避免重复劳动;易于维护和功能扩充
  • 开发方法:自上向下,逐步分解,分而治之

每个模块可视为一个函数:给定输入、产生输出。

第3页:C程序的模块化结构

C是模块化程序设计语言:一个C程序由若干源文件组成,每个源文件包含预处理命令、数据说明与若干函数(Function1 ~ Function n)。

  • C是函数式语言
  • 必须有且只能有一个名为 main 的主函数
  • 程序执行总是从 main 开始,在 main 中结束
  • 函数调用结构为自顶向下的树形:main 调用 f1、f2,f1 调用 f11,f2 调用 f21、f22

第4页:函数的分类与使用注意

函数分类

  • 从用户角度:标准函数(库函数,由编译系统提供)、用户自定义函数
  • 从函数形式:无参函数、有参函数

使用库函数应注意:1、函数功能;2、函数参数的数目、顺序及各参数意义和类型;3、函数返回值意义和类型;4、需要使用的包含文件。

第5页:函数定义的一般格式

函数定义一般格式

函数类型 函数名(形参类型说明表) { 说明部分 语句部分 }
int max(int x, int y) { int z; z = x>y ? x:y; return(z); } void printstar(void) { printf("**********\n"); }

旧式写法:int max(x, y) int x, y; { ... }。函数类型缺省为 int;空函数写作 void dummy(void){ };C语言不允许函数嵌套定义。

第6页:函数定义举例与合法性判断

例:定义函数 min 求三个整数的最小值

int min(int x, int y, int z) { int t; t = x<y ? x:y; return(t<z ? t:z); }

判断定义合法性:

  • void func1(a,b,c) — 缺形参类型说明,非法
  • float func2(float a, float b){ float a,b,c; ... } — 形参 a、b 在函数体内重复定义,非法

第7页:函数的返回值

返回语句:return(expression); 或 return expression; 或 return;

  • 功能:使程序控制从被调函数返回调用函数,并带回返回值
  • void 型函数无返回值;若无 return 语句,遇 } 时自动返回
  • 函数中可有多个 return 语句
  • 若函数类型与 return 表达式类型不一致,以函数类型为准自动转换(函数调用转换)
int max(float x, float y) { float z; z = x>y ? x:y; return(z); } 

第8页:函数调用的形式

函数调用格式:函数名(实参表)

  • 函数语句:printstar();printf("Hello,World!\n");
  • 函数表达式:m = max(a,b)*2;
  • 函数参数:printf("%d", max(a,b));m = max(a, max(b,c));

第9页:函数原型声明

对被调用函数的要求:必须是已存在的函数;库函数用 #include <*.h>;用户自定义函数需作函数原型说明。

函数原型声明:函数类型 函数名(形参类型[形参名], ...);

  • 作用:告诉编译系统函数类型、参数个数及类型,以便检验
  • 函数定义与函数说明不同:定义只能一次,说明可多次
  • 说明位置:程序的数据说明部分(函数内或函数外)
  • 可不作说明的情况:被调函数定义出现在主调函数之前;函数返值为 int 型

第10页:函数原型声明示例

#include <stdio.h> float add(float x, float y); void main() { float a, b, c; scanf("%f%f", &a, &b); c = add(a, b); printf("sum is %f\n", c); } float add(float x, float y) { float z; z = x + y; return(z); }

第11页:被调函数在前时可省略声明

#include <stdio.h> float add(float x, float y) { float z; z = x + y; return(z); } void main() { float a, b, c; scanf("%f,%f", &a, &b); c = add(a, b); printf("sum is %f", c); }

被调函数定义出现在主调函数之前,可省略原型说明。思考:main 中调用 func(); 而 void func(void){ } 定义在其后,须补写 void func(void); 说明。

第12页:函数的嵌套调用

函数嵌套调用:C语言不允许嵌套定义,但允许嵌套调用。

void main() { ... sum = add(a, b); ... } int add(int x, int y) { int z; z = square(x+y); return z; } int square(int x) { return x*x; }

调用过程:main 调用 add(保存现场)→ add 调用 square → 返回 add → 返回 main。

第13页:嵌套调用求最大最小差

例:输入三个整数,输出最大值与最小值的差

#include <stdio.h> int dif(int x, int y, int z); int max(int x, int y, int z); int min(int x, int y, int z); void main() { int a, b, c, d; scanf("%d%d%d", &a, &b, &c); d = dif(a, b, c); printf("Max-Min=%d\n", d); } int dif(int x, int y, int z) { return max(x,y,z) - min(x,y,z); }

调用关系:main 调 dif,dif 分别调 max 与 min。

第14页:形参与实参

形参与实参

  • 形式参数:定义函数时函数名后面括号中的变量名
  • 实际参数:调用函数时函数名后面括号中的表达式
int max(int x, int y); void main() { ... c = max(a, b); ... } int max(int x, int y) { int z; z = x>y ? x:y; return(z); }

第15页:实参求值顺序

说明

  • 实参必须有确定的值,求值顺序因系统而定(TC、VC 自右向左)
  • 形参与实参个数相等、类型一致、按顺序一一对应
  • 类型不同时自动按形参类型转换
  • 函数调用时才为形参分配内存,调用结束内存释放
p = func(i=3, i); p = func(i, i=3); int func(int a, int b) { if(a>b) c=1; else if(a==b) c=0; else c=-1; return(c); }

第16页:求两整数阶乘之和

#include <stdio.h> int sum(int a, int b); int factorial(int n); void main() { int n1, n2, a; scanf("%d %d", &n1, &n2); a = sum(n1, n2); printf("%d!+%d!=%d", n1, n2, a); } int sum(int a, int b) { int c1, c2; c1 = factorial(a); c2 = factorial(b); return(c1 + c2); } int factorial(int n) { int rtn = 1, i; for (i = 1; i <= n; i++) rtn *= i; return(rtn); }

第17页:值传递方式

参数传递方式——值传递

  • 方式:函数调用时为形参分配单元,并将实参的值复制到形参中;调用结束形参单元被释放,实参单元仍保留并维持原值
  • 特点:形参与实参占用不同的内存单元;单向传递

第18页:值传递交换两数失败

#include <stdio.h> void swap(int x, int y) { int temp; temp = x; x = y; y = temp; } void main() { int a = 7, b = 11; printf("a=%d,b=%d\n", a, b); swap(a, b); printf("swapped:\n"); printf("a=%d,b=%d\n", a, b); }

输出:a=7,b=11 / swapped: a=7,b=11。形参 x、y 得到的是 a、b 的副本,交换只发生在副本上。

第19页:交换两数的内存示意

内存示意:main 中 a(2000)=7、b(2002)=11;swap 中 x(2008)=7、y(200A)=11 为 COPY,temp 交换后 x=11、y=7,但 main 中 a、b 不变,故输出两行均为 a=7, b=11。

第20页:数组元素作函数实参

#include <stdio.h> void swap(int x, int y) { int temp; temp = x; x = y; y = temp; } void main() { int a[2] = {7, 11}; swap(a[0], a[1]); printf("a[0]=%d\na[1]=%d\n", a[0], a[1]); }

数组元素作函数实参仍属于值传递,交换不影响数组元素本身。

第21页:数组元素值传递的内存示意

数组元素 a[0]=7、a[1]=11 作为实参,其值复制到形参 x(2008)、y(200A),temp 交换后 x=11、y=7;main 中数组 a 的内容不变,仍为 7、11。

第22页:地址传递方式

参数传递方式——地址传递

  • 方式:函数调用时,将数据的存储地址作为参数传递给形参
  • 特点:形参与实参对应同样的存储单元;双向传递;实参和形参必须是地址常量或变量
  • 好处:节约内存、提高效率、同步更新

说明:C语言中所有参数传递本质上都是值传递(All argument-passing is strictly by value),地址传递是把地址值传给形参。

第23页:数组名作函数参数

数组名作函数参数——地址传递

  • 数组名作函数参数传递的是数组起始地址,不是整个数组
  • 主调函数与被调函数分别定义数组,且类型应一致
  • 形参数组大小(多维数组第一维)可不指定
  • 形参数组名是地址变量

第24页:数组名作参数交换两数

#include <stdio.h> void swap(int x[2]) { int temp; temp = x[0]; x[0] = x[1]; x[1] = temp; } void main() { int a[2] = {7, 11}; swap(a); printf("a[0]=%d\na[1]=%d\n", a[0], a[1]); }

调用时把数组 a 的首地址 2000 传给形参 x,x 与 a 指向同一段内存,交换后 a[0]=11、a[1]=7。

第25页:地址传递的内存变化

实参 a 的首地址为 2000,形参 x 得到该地址(COPY 的是地址值);x[0]、x[1] 即 a[0]、a[1]。交换后内存中 a[0]=11、a[1]=7,实现双向传递。形参用数组定义 int x[ ] 形式,是地址变量。

第26页:求二维数组各行元素之和

#include <stdio.h> void get_sum_row(int x[][3], int result[], int row, int col) { int i, j; for (i = 0; i < row; i++) { result[i] = 0; for (j = 0; j < col; j++) result[i] += x[i][j]; } } void main() { int a[2][3] = {3, 6, 9, 1, 4, 7}; int sum_row[2], row = 2, col = 3, i; get_sum_row(a, sum_row, row, col); for (i = 0; i < row; i++) printf("The sum of row[%d]=%d\n", i+1, sum_row[i]); }

结果:第1行 18、第2行 12。形参数组最左一维大小可省略,其余各维必须相同(int x[][3])。

第27页:变量的存储属性概述

概述:变量是对程序中数据存储空间的抽象。程序中使用变量名对内存操作;编译或函数调用时为其分配内存单元。

void main() { int a; a = 10; printf("%d", a); } 

第28页:变量的存储方式

变量存储方式

  • 静态存储:程序运行期间分配固定存储空间
  • 动态存储:程序运行期间根据需要动态分配存储空间

内存用户区划分:程序区、静态存储区(永久性变量)、动态存储区(暂时变量)。

第29页:变量的属性

变量的属性

  • 数据类型:变量所持有的数据的性质(操作属性)
  • 存储属性:存储器类型(寄存器、静态存储区、动态存储区)
  • 生存期:静态变量从程序开始执行到结束;动态变量从包含该变量定义的函数开始执行到结束
  • 作用域:局部变量(内部变量)在函数或复合语句内定义,只在本函数或复合语句内有效;全局变量(外部变量)在函数外定义,作用域从定义点到本文件结束

应尽量少使用全局变量:全局变量在程序全部执行过程中占用存储单元;降低函数的通用性、可靠性、可移植性;降低程序清晰性,容易出错。

第30页:变量的存储类型

变量的存储类型

  • auto —— 自动型
  • register —— 寄存器型
  • static —— 静态型
  • extern —— 外部型
[存储类型] 数据类型 变量表; int sum; auto int a, b, c; register int i; static float x, y;

第31页:外部型extern

外部型 extern:extern int a, b;

  • 不是定义变量,是对已定义过的全局变量进行说明
  • 作用:扩展变量作用域
  • 定义与说明不同:次数(定义只能1次、说明可多次)、位置(定义在所有函数之外、说明在函数内或函数外)、分配内存(定义分配内存可初始化,说明不分配内存不可初始化)

错误写法:extern int a = 2;(说明时不能初始化)。

第32页:自动型auto

自动型 auto:auto int a, b, c; 等价于 int a, b, c;

  • 动态变量,存放在动态存储区
  • 默认的局部变量,用于函数内、复合语句和形参
  • 若不赋初值其值不定;每次函数调用时赋初值
  • 不同函数可定义同名变量
  • 若外部变量与局部变量同名,则外部变量被屏蔽

第33页:寄存器型register

寄存器型 register:register int a, b;

  • 动态变量,存放在CPU寄存器
  • 若不赋初值其值不定,每次函数调用时赋初值
  • 变量个数受限(约2个),且不能为 long、double、float 型
  • 局部变量,用于函数内、复合语句和形参
void main() { register int i, sum; for (i = 0, sum = 0; i <= 100; i++) sum += i; printf("Sum=%d\n", sum); }

第34页:静态型static

静态型 static:static int a, b[5];

  • 静态变量,存放在静态存储区
  • 若不赋初值,自动赋 0;编译时赋初值,只赋一次
  • 局部 static:具有全局寿命和局部可见性,值具有继承性
  • 全局 static:作用域从定义点到本文件结束,且不能扩展到其他文件

第35页:变量存储类型小结表

变量存储类型小结
项目静态存储动态存储
存储方式程序整个运行期间分配固定空间函数调用开始至结束
赋初值编译时赋初值,只赋一次每次函数调用时自动赋初值
未赋初值0 或空字符不确定
存储区静态存储区动态区、寄存器
作用域局部变量、外部变量定义变量的函数或复合语句内
存储类别局部 static、外部 static、外部变量auto、register、形参
适用范围本文件或所有文件本函数内

第36页:局部变量的作用域

float f1(int a) { int b, c; ... } char f2(int x, int y) { int i, j; ... } void main() { int m, n; ... } 

main 函数中定义的变量只在 main 中有效,形参也是局部变量,不同函数可使用同名变量。

第37页:全局变量的作用域

int a = 3, b = 5; void main() { void swap(void); printf("a=%d,b=%d\n", a, b); swap(); printf("a=%d,b=%d\n", a, b); } int temp; void swap(void) { temp = a; a = b; b = temp; }

全局变量自动初始化成 0,采用静态存储,作用域可扩展;a、b 与 temp 均为全局,swap 直接操作它们即可完成交换。

第38页:auto变量作用域示例

void main() { int x = 1; void prt(void); { int x = 3; prt(); printf("2nd x=%d\n", x); } printf("1st x=%d\n", x); } void prt(void) { int x = 5; printf("3th x=%d\n", x); }

输出:3th x=5 / 2nd x=3 / 1st x=1。内层复合语句中的 x=3 屏蔽外层 x=1,prt 中的 x=5 又是另一个变量。

第39页:不同函数同名变量

void sub(); void main() { int a, b; a = 3; b = 4; printf("main:a=%d,b=%d\n", a, b); sub(); printf("main:a=%d,b=%d\n", a, b); } void sub() { int a, b; a = 6; b = 7; printf("sub:a=%d,b=%d\n", a, b); }

输出:main:a=3,b=4 / sub:a=6,b=7 / main:a=3,b=4。动态存储区按栈方式工作,各函数局部变量互不干扰。

第40页:局部变量与全局变量同名

int a = 3, b = 5; int max(int a, int b) { int c; c = a>b ? a:b; return(c); } void main() { int a = 8; printf("max=%d\n", max(a, b)); }

输出 max=8。main 中的局部 a=8 屏蔽全局 a=3,b 仍取全局变量 5,故 max(8,5)=8。

第41页:外部变量的副作用

void prt(); int i; void main() { for (i = 0; i < 5; i++) prt(); } void prt(void) { for (i = 0; i < 5; i++) printf("%c", '*'); printf("\n"); }

输出:*****(仅一行)。prt 中的循环把全局 i 修改为 5,main 的循环条件失效,这是外部变量的副作用。

第42页:变量的生存期

 int a; main() { ... f2(); ... f1(); ... } f1() { auto int b; ... f2(); ... } f2() { static int c; ... } 

调用顺序:main → f2 → f1 → f2 → main;a 全程存在,b 随 f1 调用生灭,c 常驻静态区。

第43页:局部静态变量的值继承

void increment(void) { int x = 0; x++; printf("%d\n", x); } void increment(void) { static int x = 0; x++; printf("%d\n", x); } 

static 局部变量在编译时赋初值一次,值具有继承性,适合记录调用次数或累加结果。

第44页:变量作用域综合练习

int a = 10, b = 0; void func1() { int a = 25, b = 50; b /= a + 5; printf("a= %d, b= %d\n", a, b); } void func2() { printf("a= %d, b= %d\n", a, b += 5); } void main() { int b = 10; printf("a+5= %d, b= %d\n", a + 5, b); func1(); func2(); }

运行结果:a+5=15, b=10 / a=25, b=1 / a=10, b=5。func1 内 a、b 为局部,func2 内 a、b 为全局。

第45页:静态变量选择题

int mathfun(int fst) { static int thd = 5; int sec = 0; sec += 1; thd += 1; return(fst + sec + thd); } void main() { int n = 4, num = 0; for (num = 1; num <= 3; num++) printf("%d ~ %d\n", num, mathfun(n)); }

sec 每次调用重置为 1,thd 依次累加为 6、7、8,返回 4+1+thd,故输出 A:1 ~ 11、2 ~ 12、3 ~ 13。

第46页:作用域与生存期综合示例

int i = 1; void main() { static int a; register int b = -10; int c = 0; printf("-----MAIN------\n"); printf("i:%d a:%d b:%d c:%d\n", i, a, b, c); c = c + 8; other(); printf("-----MAIN------\n"); printf("i:%d a:%d b:%d c:%d\n", i, a, b, c); i = i + 10; other(); } void other() { static int a = 2; static int b; int c = 10; a = a + 2; i = i + 32; c = c + 5; printf("-----OTHER------\n"); printf("i:%d a:%d b:%d c:%d\n", i, a, b, c); b = a; }

输出:MAIN i:1 a:0 b:-10 c:0 → OTHER i:33 a:4 b:0 c:15 → MAIN i:33 a:0 b:-10 c:8 → OTHER i:75 a:6 b:4 c:15。

注:原课件输出行中 a、b 之间的分隔符显示为 \b,此处按格式串 i:%d a:%d b:%d c:%d 规范呈现。

第47页:用extern扩展全局变量作用域

int p = 1, q = 5; float f1(int a) { int b, c; ... } int f3() { ... } char c1, c2; char f2(int x, int y) { int i, j; ... } main() { int m, n; ... } extern char c1, c2; 

全局变量自动初始化成 0,采用静态存储;用 extern 说明可扩展其作用域。

第48页:extern说明的输出示例

void main() { void gx(), gy(); extern int x, y; printf("1: x=%d\ty=%d\n", x, y); y = 246; gx(); gy(); } void gx() { extern int x, y; x = 135; printf("2: x=%d\ty=%d\n", x, y); } int x, y; void gy() { printf("3: x=%d\ty=%d\n", x, y); }

输出:1: x=0 y=0 / 2: x=135 y=246 / 3: x=135 y=246。

第49页:外部变量的定义与说明对比

写法一(值传递,交换无效)

void swap(int x, int y) { int t; t = x; x = y; y = t; } void main() { extern int a, b; swap(a, b); printf("a=%d b=%d\n", a, b); } int a = 13, b = -8;

写法二(直接操作全局变量,交换有效)

void swap() { int t; t = a; a = b; b = t; } void main() { extern int a, b; swap(); printf("a=%d b=%d\n", a, b); } int a = 13, b = -8;

第50页:引用其他文件中的外部变量

 int global; extern int global; extern int global; void func1() static int number; static int number; { global = 1; x = 1; } void func2() float x; { number = 2; void func3() global = 2; } { number = 3; global = 3; printf(...); }

主文件中用 extern int global;、extern float x; 说明后再调用 func1、func2、func3,即可引用其他文件定义的外部变量;static 全局变量 number 则不能扩展到其他文件。

第51页:本章小结

本章小结

  • 模块化程序设计思想
  • 函数定义、调用、参数传递、返回值及函数原型
  • 变量的存储类型与存储区划分:程序区、静态存储区(全局变量、外部 static 变量、局部 static 变量)、动态存储区(形参变量、局部动态变量 auto 与 register、函数调用现场保护和返回地址等)
顶部目录