主讲教师:吴琼 微信群:C语言2016 QQ群:437583061密码scu2016 昵称:“真名+学号” 四川大学计算机学院 四川大学计算机学院 主讲教师:吴琼 微信群:C语言2016 QQ群:437583061密码scu2016 昵称:“真名+学号” 2018/11/15
第1章 基础知识 第2章 表达式 第3章 控制语句 第4章 函数 第5章 数组和指针 第6章 结构类型 第7章 预处理命令 第8章 文件 本书内容 第1章 基础知识 第2章 表达式 第3章 控制语句 第4章 函数 第5章 数组和指针 第6章 结构类型 第7章 预处理命令 第8章 文件 2018/11/15
Undefined Instruction 本章主要内容 1 程序示例 2 相关语法 0x1C 0x18 0x14 0x10 0x0C 0x08 0x04 0x00 FIQ 3 研究部分 IRQ (Reserved) Data Abort 8 习题 Prefetch Abort Software Interrupt Undefined Instruction Reset ARM Vector Table 2018/11/15
第一部分为程序示例:精读和精讲部分,力求人人吃透 。 第二部分为相关语法:自学为宜,不必细讲。 第三部分为研究部分:供学有余力的同学参考 2018/11/15
函数是具有特定功能、有名字的一段独立代码。 本章包括: 4.1 程序示例 函数是C程序的核心概念。 函数是具有特定功能、有名字的一段独立代码。 本章包括: 函数声明、 函数定义、 函数调用、 函数形参、 函数实参、 函数返回值、 递归函数等。 2018/11/15
函数声明 例题4.1计算1~5的阶乘平方和 /* 1. 编译预处理命令和函数声明 */ #include <stdio.h> 4.1.1 函数调用 例题4.1 例题4.1计算1~5的阶乘平方和 /* 1. 编译预处理命令和函数声明 */ #include <stdio.h> unsigned long Factorial (int n); 函数声明 2018/11/15
main()函数 /* 2. 定义main()函数 */ int main( void ) { int i; /* 整型变量 */ unsigned long f ; /* 存放某整数的阶乘 */ unsigned long s = 0; /* 存放累加和 */ for (i = 1; i <= 5; i++) /* 循环求阶乘平方的累加和 */ f = Factorial(i); /* 调用Factorial()函数 */ s = s + f*f; } printf("s=%lu\n", s); /* 输出累加和 */ return 0; /* 返回值0 */ 函数调用,无需再说明返回值和函数参数类型 2018/11/15
Factorial()函数 /* 3. 定义Factorial()函数,求n的阶乘 */ unsigned long Factorial (int n) { unsigned long t = 1; /* 累乘积 */ int i; /* 整型变量 */ for (i = 1; i <= n; i++) t = t * i; /* 求累乘积 */ return t; /* 返回t */ } 函数定义 2018/11/15
例题4.1分为三个部分: 1. 编译预处理命令和函数声明; 2. main( )函数定义; 3. Factorial ( )函数的定义。 2018/11/15
函数定义:函数头和函数体 定义main( )函数 main()函数是自定义函数。 自定义一个函数时,函数头和函数体这两部分缺一不可。 main()函数的函数头就是int main( void ), 它的函数体则是其后一对大括弧及大括弧之间的内容。 函数定义:函数头和函数体 2018/11/15
定义unsigned long Factorial (int n)函数 小括弧()是函数标记, int n是函数的形式参数(形式参数必须有类型,有名字)。 函数包括:函数头和函数体两部分。 自定义一个函数时,函数头和函数体这两部分缺一不可。 2018/11/15
1. 改写例题4.1,求各阶乘的倒数和。(注意数据类型变化)s4_1_1 练习: 1. 改写例题4.1,求各阶乘的倒数和。(注意数据类型变化)s4_1_1 2. 求100以内的所有素数的和。s4_1_2 3. 修改Factorial()函数功能,比如:把一个整数分解成多个素数之积。S_4_1_3 2018/11/15
例题4.2利用递归函数求1+2+3+…+n的累加和。 4.1.2 简单递归函数示例 例题4.2 例题4.2利用递归函数求1+2+3+…+n的累加和。 欲求s(n)得先求出s(n-1)。这种自己调用自己的函数被称为递归函数。只要能求出s(n-1),s(n)自然可得。 而要求s(n-1),就得先求s(n-2),…… 2018/11/15
因为调用在定义之后! 程序分为三个部分: 1. 编译预处理命令; 2. Sum()函数的定义; 3. main( )函数定义。 2018/11/15
关注递归的结束条件 在Sum ()函数的函数体中,通过一个if~else~语句实现了双分支选择。即: 当n>0时,通过return n + Sum( n - 1)语句实现了对Sum( n - 1)的调用,实现了函数自己调用自己,即实现了递归调用; 当n<=0时,通过return 0语句直接返回0值,实现了函数的结束分支。 关注递归的结束条件 2018/11/15
函数调用 返回值 Sum(0) Sum(1) 1+Sum(0), 即1+0 Sum(2) 2+Sum(1), 即2+1+0 Sum(3) Sum(1) 1+Sum(0), 即1+0 Sum(2) 2+Sum(1), 即2+1+0 Sum(3) 3+Sum(2), 即3+2+1+0 Sum(4) 4+Sum(3), 即4+2+1+0 Sum(5) 5+Sum(4), 即5+4+2+1+0 2018/11/15
练习: 1. 请修改例题4.2,对Sum()函数实现每一次进入后和退出前都输出当前的n值。s4_2_1 2. 请修改例题4.2,将Sum()改成非递归实现。S4_2_2 3. 请将例题4.1的Factorial()改为递归实现。S4_2_3 4. 请设计递归函数将字符串“Hello Sichuan University!”按照字符倒序输出。S4_2_4 2018/11/15
重点是思维方法:把最上面n-1个圆盘视为一个圆盘,看看场景是否被大大简化了? 4.1.3 复杂递归函数示例 例题4.3 汉诺塔问题 重点是思维方法:把最上面n-1个圆盘视为一个圆盘,看看场景是否被大大简化了? 2018/11/15
汉诺塔问题 是指有3个分别命名为a、b和c的塔座,在塔座a上插有n个直径大小各不相同、从小到大依次编号为1,2,…,n的圆盘,要求借助塔座b将a塔座上的n个圆盘移至c塔座上,并仍按同样的顺序叠排,圆盘移动时应遵守下列规则: (1)每次只能移动一个圆盘; (2)圆盘可插在a、b和c中任一塔座上; (3)任何时刻都不能将一个较大的圆盘压在较小的圆盘的上面。 2018/11/15
假定用Hanoi(n, a, b, c)表示n阶Hanoi塔问题, 第1个参数表示圆盘的个数, 第2个参数a表示圆盘当前所在的塔座, 第3个参数b表示移动n个圆盘时可以使用的辅助塔座, 第4个参数c表示n个圆盘最终要移动到的目的塔座。 借助递归的思想,显然,可以先利用c作为辅助塔座把a最上面的n-1个圆盘移动到b塔座(把上面n-1个圆盘当成一个圆盘处理);然后直接将a上的第n个圆盘移动到c塔座;最后以a为辅助塔座将b塔座上的n-1个圆盘移动到c塔座第n个圆盘之上。 2018/11/15
分为三个部分: 1. 编译预处理命令和函数声明; 2. main()函数定义; 3. Hanoi ()函数的定义。 声明Hanoi ()函数 2018/11/15
定义Hanoi ()函数 当假定Hanoi(m-1, x, y, z)可以实现时,递归的想法就形成了,一切都会变得很简单。 将n个圆盘视为两部分:第n个圆盘和上面的n-1个圆盘。假定将前面n-1个圆盘视为一个整体(视为一个圆盘),它可以通过Hanoi()实现想要的移动。这样的话只需要考虑两个圆盘情况下的移动就可以了。这是不是变得非常容易了? 而那n-1个圆盘的移动同样可以再分为两个部分,即第n-1个圆盘和更上面的n-2个圆盘(把更上面的n-2个圆盘视为一个整体)。 如此递进,就形成了递归方法。 当假定Hanoi(m-1, x, y, z)可以实现时,递归的想法就形成了,一切都会变得很简单。 2018/11/15
汉诺塔问题的结束条件(即基本情况)如下: 当n==0时,没盘子可移动,什么也不做; 当n==1时,可直接将1号圆盘从a塔座移动到c塔座。汉诺塔问题的递归情况如下: 对n>=2的一般情况可用如下方式递归求解: (1)借助c塔座将1至n-1号园盘从 a 塔座移动至 b塔座,可递归求解Hanoi(n-1, a, c, b); (2)将 n号园盘从 a 塔座移动至 c塔座; (3)借助a塔座将1至n-1号园盘从b塔座移动至c塔座,可递归求解Hanoi(n-1, b, a, c)。 2018/11/15
练习: 1. 查阅资料,了解字符数组的知识。然后使用递归函数将字符串”Hello Sichuan University”按照单词倒序输出。S4_3_1 2. 查阅资料,了解几种利用递归函数简化程序设计的例子,深刻理解递归的妙用,并上机运行它们。 2018/11/15
统计字符个数。 程序分为四个部分: 4.1.4 外部变量示例 例题4.4 1. 编译预处理命令和函数声明; 2.外部变量定义部分; 3. main( )函数定义; 4. CountCharacter( )函数定义部分。 2018/11/15
/. 1. 编译预处理命令和函数声明. / #include <stdio. h> #include <ctype /* 1. 编译预处理命令和函数声明*/ #include <stdio.h> #include <ctype.h> int CountCharacter(int ch); 2018/11/15
/* 2. 定义外部变量并初始化*/ int iUppercase = 0; int iLowercase = 0; int iDigit = 0; int iOther = 0; 2018/11/15
/* 3. 主函数*/ int main (void) { int c; printf("Please input a string:\n"); while(1) c = getchar(); CountCharacter(c); if ( c=='\n') break; } printf(" iUppercase: %d.\n iLowercase: %d.\n iDigit: %d.\n iOther: %d.\n", iUppercase, iLowercase, iDigit, iOther); return 0; 2018/11/15
/* 4. 自定义函数 */ int CountCharacter(int ch) { if ( ch=='\n') ; else if ( isdigit(ch) ) /* 数字isdigit()返回非0 */ iDigit++; else if ( ch>='A' && ch<='A'+26-1 ) iUppercase++; else if ( ch>='a' && ch<='a'+26-1 ) iLowercase++; else iOther++; return ch; } 2018/11/15
4. CountCharacter( )函数定义部分。 程序分为四个部分: 1. 编译预处理命令和函数声明; 2.外部变量定义部分; 3. main( )函数定义; 4. CountCharacter( )函数定义部分。 2018/11/15
#include <ctype.h> 1. 编译预处理命令和函数声明 #include <ctype.h> 还包含一下库函数,可以修改CountCharacter()函数试试isdigit(),isupper(),islower(),tolower(),toupper() int CountCharacter(int ch); 2018/11/15
该部分定义了四个外部变量,分别用于统计大写字母、小写字母、数字和其他字符的个数。 2. 外部变量定义 该部分定义了四个外部变量,分别用于统计大写字母、小写字母、数字和其他字符的个数。 外部变量和函数内部定义的内部变量定义形式相同。但由于定义的位置不同,所以它们有着不同的性质: 内部变量只在函数内部起作用,离开了函数就不起作用了; 外部变量从定义它的位置开始,一直到整个文件结束前都起作用。 思考:内部变量和外部变量可否同名?什么结果? 2018/11/15
while(1)是一个无限循环语句,反复执行while后一对大括弧之间的3个语句。 3. 定义main( )函数 while(1)是一个无限循环语句,反复执行while后一对大括弧之间的3个语句。 第1个语句调用getchar( )函数从输入缓冲队列中获取一个字符并赋值给变量c。 第2个调用值定义函数语句,它判断c的类别,并对相应类别的计数变量实行自增1操作。 第3个语句是if语句,它的意思是当没有取到回车换行符’\n’时继续循环,当取到’\n’时,执行break语句退出while循环。 2018/11/15
4. 定义CountCharacter( )函数 CountCharacter( )函数通过if语句判断字符ch的类别,并对相应的计数变量增加一次计数。 isdigit(ch) 函数的使用。 2018/11/15
1. 查阅资料,设想几个需要通过外部变量在函数间传值的例子,并实现它们。 练习: 1. 查阅资料,设想几个需要通过外部变量在函数间传值的例子,并实现它们。 2018/11/15
4.2 相关语法 建议自修 2018/11/15
完整整体 函数是一个能够完成特定功能的独立程序段(代码段)。 函数是构成C程序的基本单位。 函数必须被作为一个整体看待、调用、执行。 4.2.1 函数 函数是一个能够完成特定功能的独立程序段(代码段)。 函数是构成C程序的基本单位。 函数必须被作为一个整体看待、调用、执行。 站在函数之外看,函数内的代码不可分割。 完整整体 2018/11/15
从形式上,函数分为两部分:即函数头和函数体。 函数头即是提供给函数调用者的调用接口; 函数体就是函数头后封装在一对大括弧之间的所有语句(即函数功能代码)。 函数实际上提供了调用者透明地执行特定功能的一种手段。 程序正是通过调用函数这种形式将不同功能组合在一起形成整体功能,完成某个具体任务。 2018/11/15
调用函数 2018/11/15
函数的定义 函数返回值类型 函数名(函数形式参数列表) { 变量定义部分; 可执行语句部分; } 比如: int Sum(int a, int b) int s = 0; s = a+b; return s; 2018/11/15
函数定义5要素(头和体): 1. 函数返回值类型 2. 函数名 3. 小括弧 4. 形式参数列表 5. 函数体 2018/11/15
函数声明 所谓函数声明,就是将函数头用语句的形式写在函数调用之前。 2018/11/15
出现在主调函数中,其值被传送给被调函数的参数叫实参。 形参和实参的功能是作传送数据,即实参借助于形参把值传递到函数内。 函数参数和返回值 出现在函数定义的函数头中的参数叫形参。 出现在主调函数中,其值被传送给被调函数的参数叫实参。 形参和实参的功能是作传送数据,即实参借助于形参把值传递到函数内。 2018/11/15
(1)形参变量只有在被调用时才分配内存单元,在调用结束时,立即释放所分配的内存单元。 函数的形参和实参具有以下特点: (1)形参变量只有在被调用时才分配内存单元,在调用结束时,立即释放所分配的内存单元。 (2)实参可以是常数、常量、变量、表达式等,无论实参是何种类型的量,在进行函数调用时,必须具有确定的值,以便将这些值传送给形参。 (3)实参和形参在数量上,类型上,顺序上应一致, 否则可能会发生“类型不匹配”的错误。 (4)函数调用中发生的数据传送是单向的。 即只能把实参的值传送给形参,而不能把形参的值反向地传送给实参。在函数调用过程中,形参的值发生改变,而实参中的值不会变化。 2018/11/15
inline void Swap1(int a, int b) { int t; t = a; a = b; b = t; } 内联函数从源代码层看,有函数的结构,而在编译后,却不具备函数的性质。内联函数不是在调用时发生控制转移,而是在编译时将函数体嵌入在每一个调用处。编译时,类似宏替换,使用函数体替换调用处的函数名。 2018/11/15
嵌套调用 2018/11/15
递归是指函数直接或间接对自已进行调用的过程或方式,有递归过程的函数被称为递归函数。 if (<递归结束条件>) { /*递归结束条件成立,结束递归调用 */ 递归结束处理方面的语句; } else { /*递归结束条件不成立,继续进行递归调用 */ 递归调用方面的语句; 2018/11/15
if (<递归调用条件>) { /* 继续进行递归调用 */ 递归调用方面的语句; } else { /* 结束递归调用 */ { /* 继续进行递归调用 */ 递归调用方面的语句; } else { /* 结束递归调用 */ 递归结束处理方面的语句; }】 2018/11/15
4.2.2 外部变量和内部变量 “外部”和“内部” 以函数为分界 。 在所有函数之外定义的变量就是外部变量; 其作用域将覆盖从定义的位置到文件结束,包括该区域内定义的每一个函数的全部内部区域 static修饰:作用域仅在本源文件范围 无static修饰:作用域可扩展到其他源文件,通过extern声明 优点:函数或源文件间通信的方式,作为共享数据在函数间传递; 缺点:破坏程序的独立模块结构 2018/11/15
在任意一个函数之内定义的变量就是内部变量。 其作用域在函数内或定义的块内 static修饰:存放在静态存储区 无static:存放在堆栈区 如果内部变量和外部变量同名了,在内部变量的作用范围内就不能访问外部变量 同一个函数内的内部变量与块内变量同名了,块内内部变量起作用的范围内函数变量不起作用 函数没有内外之分,不能再函数内定义另一函数 2018/11/15
文件范围——其作用域开始于文件开头,结束于文件结尾。 如果在另一文件中使用外部变量,extern声明 外部变量和内部变量的作用域 文件范围——其作用域开始于文件开头,结束于文件结尾。 如果在另一文件中使用外部变量,extern声明 块范围——开始于左大括弧“{”,结束于右大括弧“}”。 函数原型范围——在函数原型中定义的变量只在函数原型内有效。 函数范围——开始于函数体的左大括弧“{”,结束于函数体的右大括弧“}”。 2018/11/15
2018/11/15
{ /* 在{}间的内容就是一个复合语句 */ int i; /* 块内定义了一个变量i */ if (m > 0) { /* 在{}间的内容就是一个复合语句 */ int i; /* 块内定义了一个变量i */ for (i = 0; i < m; i++) ... } 2018/11/15
int x; /* 外部变量x */ int y; /* 外部变量y */ f(double x) /* 形式参数x */ { double y; /* 内部变量y */ } 2018/11/15
4种存储类型: 变量的存储类型 auto(自动型) 、堆栈中;仅用于内部变量,默认类型 register(寄存器型)、CPU中;频繁使用的变量,提高执行速度 static(静态型)、静态存储区; 内部静态变量:作用域在函数内,但生命周期在整个程序运行中,不随函数调用而消失,可作为函数多次调用的信息传递 extern(外部型)、不分配存储空间。变量声明,在别的地方定义是分配空间 2018/11/15
变量的存储类型及其作用域 内部变量 外部变量或函数 register auto static 非static extern 一般主要在栈区分配存储空间 在静态存储区 分配存储空间 不分配存储空间 作用域在块内 或函数内 作用域只限于文件内 作用域可扩展 到其他文件 2018/11/15
这里的外部连接和内部连接是相对于文件而言的。 无连接则只适用于函数内或者块内情况。 变量的连接 三类连接:外部连接、内部连接和无连接。 这里的外部连接和内部连接是相对于文件而言的。 无连接则只适用于函数内或者块内情况。 2018/11/15
未用static限定词修饰的函数和外部变量是外部连接的。 而用static限定词限定的函数或者外部变量是内部连接的。 函数或者块内的内部变量没有连接。 2018/11/15
将待开发的软件系统划分为若干个相互独立的模块。 4.2.3 自顶向下程序设计 从总体入手逐步细化。 将待开发的软件系统划分为若干个相互独立的模块。 自顶向下、逐步求精的程序设计方法从问题本身开始,经过逐步细化,将解决问题的步骤分解为由基本程序结构模块组成的结构化程序框图。 2018/11/15
4.3 研究实例 请根据情况自修。 2018/11/15
3. 请将输入的一个整数输出,要求在每个数字间加上短杆‘-‘。 4.4 习题 1. 查阅资料,了解主要的库函数; 2. 请打印出10000以内的全部水仙花数。 3. 请将输入的一个整数输出,要求在每个数字间加上短杆‘-‘。 水仙花数是指一个 n 位数 ( n≥3 ),它的每个位上的数字的 n 次幂之和等于它本身。(例如:1^3 + 5^3+ 3^3 = 153) 2018/11/15
Thanks! The End 2018/11/15
以下内容为原来的《C语言程序设计(C99版)》第五章的ppt。仅供参考。 2018/11/15
Undefined Instruction 本章主要内容 1 C语言的程序模块 2 函数概念 外部变量、内部变量 及其作用域 3 0x1C 0x18 0x14 0x10 0x0C 0x08 0x04 0x00 FIQ 变量的存储类型 4 IRQ (Reserved) Data Abort 5 变量的初始化、连接 Prefetch Abort Software Interrupt 6 Undefined Instruction 自顶向下程序设计 Reset ARM Vector Table 2018/11/15
大模块分成小模块,每个小模块就可以由一个独立函数来实现。 多个函数构成C程序。 一般一个独立功能就可以设计成一个函数。 本章的基本要求 大任务分成小任务。 大模块分成小模块,每个小模块就可以由一个独立函数来实现。 多个函数构成C程序。 一般一个独立功能就可以设计成一个函数。 2018/11/15
C语言程序由函数组成。 函数是C程序的基本单位。 C语言程序可以由一个或多个源文件构成。 5.1 C语言的程序模块 2018/11/15
多源文件结构 多源文件结构 多源文件结构 函数基本结构 图5.1 C程序的基本组成结构 C程序 源程序文件1 源程序文件2 源程序文件n 预处理命令 外部变量定义 函数1 函数n 外部变量声明 函数首部 { 内部变量定义; 执行语句; } 多源文件结构 多源文件结构 多源文件结构 函数基本结构 2018/11/15
计算器程序 (一个源文件) #include <stdio.h> /* 文件包含预处理命令 */ #include <stdlib.h>/* 文件包含预处理命令 */ double stack[1000]; void Push(double d); int Pop(void); int main(void) {} void Push(double d){} /* 数据压栈操作 */ int Pop(void){} /* 数据出栈操作 */ 2018/11/15
5.2函数概念 函数是完成某一特定功能的独立模块。 函数内的代码对函数来说是私有的。 除了函数本身以外,其他任何语句都不能访问函数内的代码。 满足C语言对函数定义和使用形式要求的就是函数。 私有性、封装性、形式要求 2018/11/15
图5.2 整型堆栈操作 第一个数据的入栈位置 sp bottom 满堆栈的栈底不能够装数据 2018/11/15
为了灵活判断输入的是整数还是字符串,可用gets( )获取输入字符串,然后再用函数将字符串转化为整型数据。 设计考虑 输入字符串为”pop”出栈。 输入字符串为”quit”结束程序。 为了灵活判断输入的是整数还是字符串,可用gets( )获取输入字符串,然后再用函数将字符串转化为整型数据。 atoi( ) strcmp( ) 2018/11/15
压栈Push( )和出栈Pop( )分别设计成独立的函数。 这样,程序中涉及多个函数。 如此设计,编制的程序结构清晰、可以重复使用。 另外进栈、出栈操作都只能在栈顶操作。实际上就是一个操作受限的数组。 2018/11/15
堆栈:后进先出的特殊数据结构 数组设计如下: 数组名为stack,栈顶标记sp和栈底标记bottomSP。 2018/11/15
栈顶装数据的堆栈;空堆栈 在堆栈操作中,sp被改变了。 堆栈中没有数据时,sp=0,此时堆栈没有数据,本书采用“满堆栈”,所以我们假定该单元作为堆栈的栈底不能存放数据。数据入栈时,首先要执行sp++,然后才能将数据入栈。 第一个数据1进栈后,sp指向了数据1所在的内存单元。 注意空堆栈和满堆栈的区别 栈顶装数据的堆栈;空堆栈 2018/11/15
整数出入数组模拟堆栈操作 do { 从键盘获取一个字符串; /* 1.gets( )部分 */ if( 字符串不是“pop”) /* 5.strcmp( )部分 */ /* 3. atoi( )和Push( )部分 */ 不是pop,则将字符串转换为整型数据并压入堆栈; } else 是pop,则将栈顶数据弹出堆栈; /* 4.Pop( ) */ }while(输入的字符串是"quit"吗?是则退出系统);/* 5.strcmp( ) */ strcmp( )、gets( )、以及atoi( )等函数,都已有现成的库函数可以调用,所以没有必要自己设计,我们只需设计好压栈函数Push( )和出栈函数Pop( )就可以了。 2018/11/15
*例题5.1 设计函数演示堆栈操作过程(源程序) void Push(int d) { sp++; if(sp >= (bottomSP+MAXSIZE)) puts("Stack Overflow.\n"); exit(1); } stack[sp] = d; 先修改栈顶 堆栈的最大值 2018/11/15
*例题5.1 设计函数演示堆栈操作过程(源程序) int Pop(void) { if(sp <= bottomSP) puts("Stack Underflow. \n"); exit(1); } return stack[sp--]; 堆栈的栈底 最后修改栈顶 2018/11/15
在函数嵌套调用时,它使系统对函数的内部变量的管理变得简单。 函数调用时,变量在堆栈中的变化过程 堆栈“先进后出”的特点对于理解函数内部变量的生存周期是非常重要的。 生存周期就是变量在内存中存在的时间。对于内部变量则是其在堆栈中的存在时间。 在函数嵌套调用时,它使系统对函数的内部变量的管理变得简单。 2018/11/15
图5.4 变量起作用的范围(变量的作用域) 块内生存周期 2018/11/15
图5.3 程序运行时,变量在堆栈中的动态变化过程 调用函数时,函数的内部变量才在堆栈中存在 注:图中x即为上页图中的cMax。 2018/11/15
5.2.2 函数的现代定义形式 函数定义一般都具有以下形式: 函数返回值类型 函数名([函数形式参数表]) { 变量定义部分; 可执行语句部分; } 函数的返回值类型指定函数返回值的数据类型。 只有void型无返回值 2018/11/15
dummy( ) { } /. 最小的函数,但是函数的返回类型最好明确指定,无参函数加void ,C99必须指出 dummy( ) { } /* 最小的函数,但是函数的返回类型最好明确指定,无参函数加void ,C99必须指出*/ int dummy(void){ } /* 参数表为空函数 */ int dummy(int x, int y, double d){ } /* 正确的函数参数定义形式 */ int dummy(int x, y, double d){ } /* 错误,y必须指定自己的参数类型 */ 2018/11/15
形参定义位置不同 函数的传统定义形式如下: 函数返回值类型 函数名( ) 函数形式参数定义; { 变量定义部分; 可执行语句部分; } 5.2.3 函数的传统定义形式 函数的传统定义形式如下: 函数返回值类型 函数名( ) 函数形式参数定义; { 变量定义部分; 可执行语句部分; } 形参定义位置不同 2018/11/15
5.2.4 函数返回语句return 函数一般都有一个返回值类型。 在C89标准中,如果函数省略了返回类型,那么它返回int型。 但是,最新的C99标准要求任何函数都必须有明确的返回类型。 C99不允许默认返回类型为int的情况出现,C99要求明确指定返回类型。 2018/11/15
程序是由变量和函数组成的。函数与函数之间可以通过函数参数、函数返回值和外部变量进行通信。 返回语句是被调函数向它的调用者返回值的一种机制。可以象以下语句一样返回任何表达式。 return expression; 返回值也可用于传值 2018/11/15
例题5.2 将数字字符串转换为双精度浮点数(源程序)。 5.2.5 函数返回非整数 一个函数可以返回其它类型的值吗? 例题5.2 将数字字符串转换为双精度浮点数(源程序)。 -123.456 首先是前导空格和正负号的处理。 其次以小数点为分界,整数部分和小数部分分别转换。 2018/11/15
5.2.5 函数返回非整数 for (integer = 0.0; isdigit(s[i]); i++) { integer = 10.0 * integer + (s[i] - '0'); } 整数部分处理 2018/11/15
例题5.2 (源程序) for (power = 10, decimal = 0.0; isdigit(s[i]); i++) { decimal = decimal + (s[i] - '0')/power; power *= 10; } 小数部分处理 2018/11/15
Atof( )函数中明确声明了返回值类型。 通过函数声明语句告诉调用程序该函数的返回值类型,函数参数个数、类型及其顺序,比如以下语句: double decimal, Atof(char [ ]); double Atof(char c[ ]); 2018/11/15
C99中 int Func( ); 但是在C99中,这样的声明可能意味着函数具有多个参数或0个参数。 5.2.6 函数原型 int Func( ); 但是在C99中,这样的声明可能意味着函数具有多个参数或0个参数。 在C99中,当一个函数没有参数时,其函数原型参数表必须使用void。 C99中 主讲教师:四川大学计算机学院 陈良银 2018/11/15
函数调用需要耗费部分CPU时间(比如:保存、恢复现场) **5.2.7 内联函数(inline关键字) C99增加了关键字inline,在函数定义体前面增加关键字inline就是告诉编译器对该函数的调用进行内联优化。 内联采用函数插入的方式处理函数调用而不是采用函数调用的方式, 但这只是一个请求,编译器可以忽略这个请求。 函数调用需要耗费部分CPU时间(比如:保存、恢复现场) 主讲教师:四川大学计算机学院 陈良银 2018/11/15
举例 inline void Swap1(int *pa, int *pb) { int t; t = *pa; *pa = *pb; *pb = t; } pa a=5 t=? pb b=8 pa a=5 t=? t=5 pb b=8 pa a=8 a=5 t=5 pb b=8 pa a=8 t=5 pb b=5 b=8 主讲教师:四川大学计算机学院 陈良银 2018/11/15
局部变量和全局变量 外部变量对应“全局变量”,内部变量对应“局部变量”。 外部变量具有全局属性( 全局作用域), 5.3 外部变量、内部变量及其作用域 外部变量对应“全局变量”,内部变量对应“局部变量”。 外部变量具有全局属性( 全局作用域), 内部变量具有局部属性( 局部作用域)。 对于变量本身来说,采用内部变量和外部变量的提法简单易懂,更反映了事物的本质,也便于学习者记忆。 局部变量和全局变量 主讲教师:四川大学计算机学院 陈良银 2018/11/15
没有内部函数! 函数外部定义的变量叫做外部变量,函数内部定义的变量叫做内部变量( 也可认为在块内定义) 。 *5.3.1 外部变量和内部变量 函数外部定义的变量叫做外部变量,函数内部定义的变量叫做内部变量( 也可认为在块内定义) 。 “外部”与“内部”是相对而言的,其属性差别源于其定义位置不同。 变量有内部和外部之分,而函数则没有内部和外部之分, 所有的函数都是外部函数。 没有内部函数! 主讲教师:四川大学计算机学院 陈良银 2018/11/15
所有函数共享、长生不老 缺省情况下,外部变量和函数具有以下性质——通过相同的名字对它们的引用都做相同的事情, 即使在不同的源文件中分别引用同一变量,并各自独立编译所在源文件也是如此。 外部变量提供了又一种在函数之间进行通信的方法 。 注:需要作用域相同的情况下, 同名才会同意义。 如果作用域不同,相同的名字代表不同的意义。 所有函数共享、长生不老 主讲教师:四川大学计算机学院 陈良银 2018/11/15
由于外部变量的作用域范围较大,生命周期与程序运行时间相同,因此外部变量可用于记录一些全局性质的数据。 外部变量的生命周期长,它能够从一个函数调用到另一个函数调用都保持其存储单元。 而内部变量属于自动型变量,只在函数内部起作用,当调用函数时内部变量才在内存中分配内存单元,它们才在内存中存在,当离开函数时它们就会消失,在内存中就不存在了。 主讲教师:四川大学计算机学院 陈良银 2018/11/15
下面通过一个计算器程序来说明外部变量的使用以及多源文件程序的组织结构: 要求完成一个小型计算器程序——提供+、-、*、/等算术运算。 比如:35.1 + 62.7 主讲教师:四川大学计算机学院 陈良银 2018/11/15
35.1 + 62.7 type type type 1.以字符串的方式输入; 2.如何判断操作数已经结束输入; 确定如何获取操作数 35.1 + 62.7 1.以字符串的方式输入; 2.如何判断操作数已经结束输入; 3.如何判断是第1操作数还是第2操作数(在运算符前还是在运算符后输入); 4.字符串结束; 5.如何判断非法输入。 主讲教师:四川大学计算机学院 陈良银 2018/11/15
运算器程序的基本算法流程 主讲教师:四川大学计算机学院 陈良银 while (获取运算符或操作数并判断是否文件结尾?) { switch(type) case: 是操作数 是,则转换为浮点型,压栈保存。 该操作数是第2操作数吗? (是则判断该作什么算术运算) 是,则弹出2个操作数,进行算术运算,并将结果压栈。 不是,则退出,准备接收下一个字符。 case: 是运算符 是则保存运算符,且说明下一个到来的数据是第2操作数。 case: 是'\n'结束符 是则弹出运算结果,并显示。 default:其他字符 提示非法命令。 } 主讲教师:四川大学计算机学院 陈良银 2018/11/15
1.获取运算符和操作数是一个独立的过程,应该由一个函数单独完成; 2.数据压栈和出栈应由独立函数完成; 程序应有功能部分 从上述流程分析: 1.获取运算符和操作数是一个独立的过程,应该由一个函数单独完成; 2.数据压栈和出栈应由独立函数完成; 3.当获取到一个操作数以后,实际上多取一个字符后才能够判断该操作数的结束,需要回退多取的一个字符,所以获取和回退字符由两个函数完成。 4.必不可少的main( )函数。 主讲教师:四川大学计算机学院 陈良银 2018/11/15
下面的组织方法:首先按照功能把程序组织成相对独立的几个模块。 在此基础上可进一步将其分解成多个源文件——得到多源文件的程序组织 函数放在一个文件中组成程序; 也可以放在多个源文件中组成程序。 下面的组织方法:首先按照功能把程序组织成相对独立的几个模块。 在此基础上可进一步将其分解成多个源文件——得到多源文件的程序组织 主讲教师:四川大学计算机学院 陈良银 2018/11/15
看起来,程序分四大部分比较合适 主讲教师:四川大学计算机学院 陈良银 程序的样子看起来可能跟下面的样子差不多: //第一部分、main #includes /* 1.main中用到的各种头文件 */ #defines /* 2.main中用到的宏定义 */ main(void)中用到的函数声明 /* 3.mian中用到的函数声明 */ main(void) { ... } /* 4.main函数定义体 */ //第二部分、堆栈操作 #define /* 5.堆栈操作中用到的宏定义 */ Push( ) 和Pop( )中用到的外部变量声明 /* 6.堆栈操作外部变量声明,从这里开始起作用 */ void Push( double d) { ... } /* 7.Push函数定义体 */ double Pop(void) { ... } /* 8.Pop函数定义体 */ //第三部分、获取运算符和操作数的操作 #include /* 9.GetOP中用到的头文件 */ GetOP中用到的函数声明 /* 10.GetOP中用到的函数声明 */ int GetOP(char s[ ]) { ... } /* 11.GetOP函数定义体获取操作数和运算符 */ //第四部分、获得或回退一个字符的操作 #define /* 12.获得、回退一个字符操作中用到的宏定义 */ Getch、UnGetch中用到的外部变量声明 /* 13.获取、回退字符时用到的外部变量声明 */ int Getch(void); /* 14.Getch函数定义体 */ void UnGetch(int c); /* 15.UnGetch函数定义体 */ 主讲教师:四川大学计算机学院 陈良银 2018/11/15
工程中程序一般由多个源文件组成 1.第一部分主函数的组织 *例题5.3.A 计算器程序的main( )函数(源文件)。 register int type; int operator; /* 记录算术运算符,*/ int secondOP = 0; /* secondOP=0, 不是, 当secondOP=1时,是*/ double op2; /* 第2操作数 */ char s[MAXOP] = {‘\0’ };/* 待操作字符串 */ 工程中程序一般由多个源文件组成 主函数main( )主要就是一个while循环,在while循环中,我们利用了嵌套的switch语句。外层switch判断type的类型,并作相应的操作,当type为+、-、*、/四种运算符时,表明下一次取得的操作数应该是第2操作数(用secondOP=1来标记)。当type=NUMBER,且secondOP=1的时候,说明当前已经取得了第2操作数,自然可以弹出两个操作数,进行相应的算术运算(此时相应的算术运算符已经保存在operator中)。 主讲教师:四川大学计算机学院 陈良银 2018/11/15
case NUMBER: { Push(atof(s)); if (secondOP) secondOP =0; switch(operator) /* 根据type类型运算 */ case '+': Push( Pop( )+ Pop( ) ) break; } case '*': Push( Pop( )*Pop( ) ); case '-': op2 = Pop( ); Push(Pop( )-op2); 可以用: Pop( )- Pop( ) 吗 主讲教师:四川大学计算机学院 陈良银 2018/11/15
printf("error: zero divisor\n"); break; default: case '/': { op2 = Pop( ); if (op2 != 0.0) Push( Pop( )/op2 ); } else printf("error: zero divisor\n"); break; default: } /* 退出switch(operator) */ } /* 退出 if(secondOP) */ } /* 退出case:NUMBER;*/ 主讲教师:四川大学计算机学院 陈良银 2018/11/15
/. 如果遇到 +、-、. 、/等运算符,表明需要接收第2操作数. / case '+': case '-': case ' /* 如果遇到 +、-、*、/等运算符,表明需要接收第2操作数 */ case '+': case '-': case '*': case '/': { operator = type; /* 保存算术运算符 */ secondOP=1; /* =1,下一次是第2操作数 */ break; } case '\n': /* 如果遇到'\n',表示结束,输出结果 */ printf("\t%0.8g\n", Pop( )); /* 弹出最终结果 */ default: /* 其他运算符表示非法运算符 */ printf("error: unknown command %s\n", s); *例题5.3.A 计算器程序的main( )函数(源文件) 主讲教师:四川大学计算机学院 陈良银 2018/11/15
2. 第二部分堆栈操作 主讲教师:四川大学计算机学院 陈良银 2018/11/15
*例题5.3.B Push( )和Pop( )函数 (源文件) 堆栈操作同例题5.1。 #define MAXSIZE 100 int bottomSP = 0; int sp = 0; double stack[MAXSIZE]; 主讲教师:四川大学计算机学院 陈良银 2018/11/15
*例题5.3.B Push( )和Pop( )函数 (源文件) void Push(double d) { sp++; stack[sp] = d; } double Pop(void) return stack[sp--]; 先修改指针 再数据入栈 数据先出栈,后修改指针 主讲教师:四川大学计算机学院 陈良银 2018/11/15
3. 第三部分,取出一个操作数或取出一个运算符。 *例题5.3.C GetOP( )函数获取一个运算数或操作符(源文件) 如何判断一个数已经结束了? 该问题如何解决? 你有更好的办法吗? 主讲教师:四川大学计算机学院 陈良银 2018/11/15
该函数是取出一个运算符或操作数。 如果取出的不是数字,则以单字符的形式取出,存入s中,并以\0作为结尾,同时,函数还需要返回该字符。 例题5.3.C (源文件) 该函数是取出一个运算符或操作数。 如果取出的不是数字,则以单字符的形式取出,存入s中,并以\0作为结尾,同时,函数还需要返回该字符。 比如:当取到‘\n’时,不但返回‘\n’,同时将“\n\0”存入数组中,如果取出的是数字字符串,则将数字字符串存入数组s中,以‘\0’作为字符串的结束符,并返回NUMBER。 NUMBER的作用就是通知main( )函数,此次GetOP( )函数取回了一个数字字符串,并存于s中。NUMBER==0. 主讲教师:四川大学计算机学院 陈良银 2018/11/15
库函数isdigit(c ),判断一个字符是否是数字,如果是数字则返回非0,如果不是数字则返回0。 程序通过Getch( )函数取得一个字符,通过UnGetch( )函数来回退一个字符。 取得一个完整的数字字符串以后,再取得的字符不是EOF,那就需要回退。 主讲教师:四川大学计算机学院 陈良银 2018/11/15
1、消除前导空格 while ((s[0] = c = Getch( )) == ‘ ’ || c == ‘\t’); 2、非数字则获取一个字符即可 if (!isdigit(c) && c != '.') { return c; } 主讲教师:四川大学计算机学院 陈良银 2018/11/15
while (isdigit(s[++i] = c = Getch( ))) { ; } 3、取整数; 4、取小数点和小数部分; while (isdigit(s[++i] = c = Getch( ))) { ; } 5、添加字符串结束符; 6、判断是否回退并执行; if (c != EOF) UnGetch(c); 主讲教师:四川大学计算机学院 陈良银 2018/11/15
获取和回退一个字符,字符是否真的退回到了输入端? 如何实现回退效果? 4. 第四部分,获取和回退一个字符 *例题5.3.D Getch( )和UnGetch( )函数(源文件) 获取和回退一个字符,字符是否真的退回到了输入端? 如何实现回退效果? 主讲教师:四川大学计算机学院 陈良银 2018/11/15
一般直到程序读入了过多的字符以前,程序并不能确定它是否已经读够了字符数。 根据“贪心法”的原则,要读入322+223,程序必须要读入“+”后才会知道数字322应该结束了,而“+”就不属于322里的字符, 因此它是多读的一个字符,这个字符就需要回退给输入端,以便下次阅读,否则就会丢掉“+”运算符,下次读取就不正确。 主讲教师:四川大学计算机学院 陈良银 2018/11/15
我们是否可以考虑将多读的那个字符压回输入端,使余下的代码看起来好像没有读过该字符一样啦? 那如何来解决该问题啦? 我们是否可以考虑将多读的那个字符压回输入端,使余下的代码看起来好像没有读过该字符一样啦? 下一次读取时就从刚才压回输入端的那个字符读取,这样问题不就自然解决了吗? 主讲教师:四川大学计算机学院 陈良银 2018/11/15
设计一个字符数组作为共享缓冲区,UnGetch( )将读出的字符压进共享缓冲区。 Getch( )读取字符时,首先判断共享缓冲区中是否有字符,如果有Getch( )则从缓冲区中读字符,如果缓冲区为空(没有字符),则调用getchar( )从键盘(实际上是从伴随getchar的系统线缓冲区读取)获取一个字符。 主讲教师:四川大学计算机学院 陈良银 2018/11/15
例题5.3.D Getch( )和UnGetch( )函数(源文件) char buf[BUFSIZE]; int bufp = 0; int Getch(void) { return (bufp > 0) ? buf[bufp--] : getchar( ); } void UnGetch(int c) buf[++bufp] = c; 主讲教师:四川大学计算机学院 陈良银 2018/11/15
例题5.3.D Getch( )和UnGetch( )函数(源文件) 上述例题4.3.A、例题4.3.B、例题4.3.C和例题4.3.D程序按照顺序合起来放在一个源文件中,就是一个完整的计算器程序。 主讲教师:四川大学计算机学院 陈良银 2018/11/15
头文件 多源文件组织: calculator.h getch.c getop.c main.c stack.c 多源文件组织 主讲教师:四川大学计算机学院 陈良银 2018/11/15
文件范围——其作用域开始于文件开头,结束于文件结尾。 定义于本文件的外部变量和函数具有文件作用域。 5.3.2 变量的作用域规则 文件范围——其作用域开始于文件开头,结束于文件结尾。 定义于本文件的外部变量和函数具有文件作用域。 具有文件作用域的变量或函数如果在定义时使用了static存储属性限定词,那么该变量或函数的作用域将限定在本文件以内; 如果在定义外部变量和函数时没有使用static限定词,那么其作用域还可以通过extern声明语句扩展到其他文件。 主讲教师:四川大学计算机学院 陈良银 2018/11/15
块范围——开始于左大括弧“{”,结束于右大括弧“}”。在块内定义的内部变量其作用域就在块范围内,包括函数参数。 块、函数、函数原型范围 块范围——开始于左大括弧“{”,结束于右大括弧“}”。在块内定义的内部变量其作用域就在块范围内,包括函数参数。 函数原型范围——在函数原型中定义的变量只在函数原型内有效。 函数范围——开始于函数体的左大括弧“{”,结束于函数体的右大括弧“}”。函数范围只适用于标签,标签只可以作为goto语句的目标,标签只能在同一函数内起作用。 内部变量属于块范围还是函数范围? 主讲教师:四川大学计算机学院 陈良银 2018/11/15
in file1: extern int sp; extern double stack[ ]; void Push(double d) { in file1: extern int sp; extern double stack[ ]; void Push(double d) { ... } double Pop(void) { ... } in file2: int sp = 0; double stack[MAXVAL]; 处于不同的文件中 主讲教师:四川大学计算机学院 陈良银 2018/11/15
*5.3.3 头文件的设计 主讲教师:四川大学计算机学院 陈良银 2018/11/15
头文件 /* calculator.h中的内容 */ /* 第一行相当于#ifndef _CALCULATOR_ (如果还没定义_CALCULATOR_) */ #if !defined(_CALCULATOR_) #define _CALCULATOR_ /* 则定义_CALCULATOR_ */ #define NUMBER '0' /* 0表示发现了一个数字 */ int Getch(void); /* 获取一个字符 */ void UnGetch(int c); /* 回退一个字符 */ int GetOP(char [ ]); /* 获取一个操作数或者运算符 */ void Push(double d); /* 入栈 */ double Pop(void); /* 出栈 */ #endif /* 编译至此结束 */ 主讲教师:四川大学计算机学院 陈良银 2018/11/15
auto(自动型)、static(静态型)、register(寄存器型)和extern(外部型)。 5.4 变量的存储类型 auto(自动型)、static(静态型)、register(寄存器型)和extern(外部型)。 static char buf[BUFSIZE]; static int bufp = 0; int Getch(void) { ... } void UnGetch(int c) { ... } 主讲教师:四川大学计算机学院 陈良银 2018/11/15
5.4.6 变量的存储类型及其作用域总结 内部变量 外部变量或函数 register auto static 非static extern 在栈区分配存储空间 在静态存储区 分配存储空间 不分配存储空间 作用域在块内 或函数内 作用域只限于文件内 作用域可扩展 到其他文件 在寄存器中分配 主讲教师:四川大学计算机学院 陈良银 2018/11/15
初始化方式——通过在其名字后面附带一个名字或一个表达式。 int i = 1; char enter = '\n'; 5.5 变量的初始化 初始化方式——通过在其名字后面附带一个名字或一个表达式。 int i = 1; char enter = '\n'; long secondDay = 60L * 60L * 24L; /* 一天的秒数 */ 外部变量、内部变量、内部静态变量如果不初始化会有不同的初值。 主讲教师:四川大学计算机学院 陈良银 2018/11/15
外部连接和内部连接是针对单个源文件而言的 *5.6 变量的连接 C语言定义了三类连接:外部连接、内部连接和无连接。 一般,函数和外部变量是外部连接,这意味着它们对组成程序的所有文件都是可见的。 用static限定词限定的文件作用域内的对象是内部连接,它们只在定义它们的文件内可见。 外部连接和内部连接是针对单个源文件而言的 主讲教师:四川大学计算机学院 陈良银 2018/11/15
而内部变量和外部变量是针对函数而言的 函数或者块内的内部变量没有连接,它们只在定义它们的块内有效。 *5.6 变量的连接 函数或者块内的内部变量没有连接,它们只在定义它们的块内有效。 未用static限制的,作用域可以扩展到其他文件的外部变量和函数是外部连接的。 而内部变量是无连接的,它们也不需要连接。 而内部变量和外部变量是针对函数而言的 主讲教师:四川大学计算机学院 陈良银 2018/11/15
自顶向下程序设计就是从总体入手逐步细化的结构化程序设计思想。 结构化设计方法是以模块化设计为中心。 *5.7 自顶向下程序设计 自顶向下程序设计就是从总体入手逐步细化的结构化程序设计思想。 结构化设计方法是以模块化设计为中心。 将待开发的软件系统划分为若干个相互独立的模块。 这样使完成每一个模块的工作变单纯而明确,为设计一些较大的软件打下了良好的基础。 主讲教师:四川大学计算机学院 陈良银 2018/11/15
习题 课后习题、作业 习题2 习题6 习题7 主讲教师:四川大学计算机学院 陈良银 2018/11/15
注:任何人不得将该题发在网络上。 C语言编程实践(作为平时成绩) 有一个无线传感器网络,所有10个节点交替工作在休眠和苏醒状态,在苏醒状态节点可以发送和接收数据,在休眠状态节点什么也不干。比如:节点选定工作周期是13,那么节点将每隔13个时隙苏醒1个时隙。网络启动时各节点随机选择一个素数作为其工作周期,并随机选择一个小于其工作周期的值作为其启动时间。假定多个节点同时苏醒时,它们就能够相遇,请你按照你的网络启动时随机选择的值计算一下你的网络的所有节点都与其他节点相遇的最小时隙数。 假定节点的通信半径是100m,启动时这10个节点随机布置在500m*500m的一个正方形广场上,节点以每个时隙移动1m的速度随机选择一个坐标方向运动(不能移出边界),请问最少要多少个时隙这些节点才能够与其他所有节点至少相遇一次。 注:任何人不得将该题发在网络上。 主讲教师:四川大学计算机学院 陈良银 2018/11/15
测试题 http://211.83.120.3在线测试 主讲教师:四川大学计算机学院 陈良银 2018/11/15
Thanks! The End 主讲教师:四川大学计算机学院 陈良银 2018/11/15
入 s = 0; i = 1; i < 5 i < 5 非0 f = Factorial(i); s = s + f*f; 非0 f = Factorial(i); s = s + f*f; i++; 出 主讲教师:四川大学计算机学院 陈良银 2018/11/15
入 t=1; i = 1; i<=n i<=n t = t*i j++; 出 非0 t = t*i j++; 出 主讲教师:四川大学计算机学院 陈良银 2018/11/15