• 1. C语言程序设计大学计算机公共基础课
  • 2. 课程介绍课程性质:大学计算机公共基础课(必修) 课程内容 C语言 程序设计的基本方法和技巧 预备知识 《计算机基础》 教材 《C语言程序设计教程》 汪同庆 张 华 杨先娣 主编 机械工业出版社 《C语言程序设计实验教程》 汪同庆 关焕梅 汤 洁 主编 机械工业出版社C语言程序设计2
  • 3. 授课计划C语言程序设计周次理论内容实验内容1第1讲 C语言概述与理论课对应2第2讲 C语言快速入门与理论课对应3第3讲 基本数据类型与理论课对应4第4讲 输入输出和库函数与理论课对应5第5讲 运算符和表达式与理论课对应6第6讲 语句和算法与理论课对应7第7讲 选择结构与理论课对应8第8讲 循环结构与理论课对应9第9讲 函数与理论课对应10第10讲 作用范围和存储类别与理论课对应11第11讲 数组与理论课对应12第11讲 数组与理论课对应13第12讲 指针与理论课对应14第13讲 字符串与理论课对应15第14讲 结构体与理论课对应16第15讲 编译预处理与理论课对应17第16讲 文件与理论课对应18*第17讲 高级数据结构(选讲)与理论课对应3
  • 4. 考试与考核办法学分:3学分 学时:72(讲课36+实验36) 总评成绩 = 平时成绩×#% + 期末考试成绩×#% 平时成绩:(实验,单元测验) 期末考试成绩C语言程序设计4
  • 5. 第一讲 C语言概述C 语言程序设计
  • 6. 第一讲 C语言概述预备知识 计算机组成和工作原理 计算机语言和程序 C语言的简史 为什么学习和使用C语言 C语言程序的开发过程 C语言编程环境 C语言编程实验环境C语言程序设计6
  • 7. 计算机计算机(Computer) 能进行计算和逻辑判断的设备 在一组指令(instructions)——程序(programs)的控制下处理数据 硬件(Hardware) 组成计算机的各种物理部件 键盘、鼠标、显示器、内存、硬盘、光驱等等 软件(Software) 计算机中运行的程序和数据C语言概述7
  • 8. 计算机的组成计算机包括六大部件 输入单元(Input unit) 从输入设备(键盘、鼠标)获得数据 输出单元(Output unit) 把数据输出到输出设备(显示器、打印机等) 内存(Memory unit) 临时存储正在处理的数据和运行的程序 可以快速地访问 容量不大C语言概述8
  • 9. 计算机的组成中央处理单元(Central processing unit, CPU) 算术逻辑单元(Arithmetic and logic unit, ALU) 进行算术运算和逻辑判断 控制器(Control unit) 管理和协调计算机的其它部件 辅助存储器(Secondary storage unit) 保存需长期存储的数据和非活动的程序 保存时间长 价格便宜、容量大 例如硬盘、光盘、磁带C语言概述9
  • 10. 计算机的工作原理六大部件紧密协作 C语言概述10
  • 11. 计算机的工作原理CPU负责执行程序 从内存中读取一条指令 执行该指令 从内存中读取下一条指令并执行 …… 一个主频为1GHz的CPU可以在一秒钟内进行大约一亿次这样的操作 CPU有自己的工作区:包括若干个寄存器(Registers) CPU只能理解有限的指令(CPU的指令集) 指令能够完成的任务是具体的 例如,把一个数从内存移动到某一个寄存器C语言概述11
  • 12. 计算机程序和语言存储在计算机中的一切内容都是二进制的数字形式 数据:数、字符、图片、声音、动画等等 指令 计算机程序 确切告诉计算机如何完成某项任务的指令序列就是一套程序 计算机语言 用来编写程序C语言概述#include #include void main() { float a,b,c,p,disc,q,x1,x2; scanf("%f%f%f",&a,&b,&c); p=-b/(2*a); disc=b*b-4*a*c; q=sqrt(disc)/(2*a); x1=p+q; x2=p-q; printf("x1=%f,x2=%f",x1,x2); }0110001100000110 0110010111001100 0000110000011011 0110001100000110 0110010111001100 000011000001101112
  • 13. 机器语言数字形式的指令码就是机器语言(Machine language) 用机器语言编写的程序能够被计算机直接理解和执行 但是,编写程序非常费力 例如,编写程序完成两个数相加的任务需要以下几步 把地址为2000的内存单元中的数复制到寄存器1; 把地址为2004的内存单元中的数复制到寄存器2; 把寄存器2中的数与寄存器1中的数相加,结果保留在寄存器1中; 把寄存器1中的数复制到地址为2008的内存单元中。C语言概述0110001100000110 0110010111001100 000011000001101113
  • 14. 汇编语言符号化的指令码就是汇编语言(Assembly language) 例如,完成两个数相加的汇编语言程序 汇编语言程序必须被翻译成机器语言程序才能被执行 汇编程序(Assembler)充当“翻译”C语言概述ldreg n1, r1 把变量n1的值复制到寄存器1(r1); ldreg n2, r2 把变量n2的值复制到寄存器2(r2); add r1, r2 把r2中的数与r1中的数相加,结果保留在r1中; store r1, sum 把r1中的数复制到变量sum。14
  • 15. 高级语言高级语言(High-level language)使用数学符号和类似英语的单词 例如,完成两个数相加的C语言程序 从多方面提高了编程效率 不必考虑CPU的指令集 不必考虑CPU实现特定任务的精确步骤 采用接近人类思考问题的方式去书写程序 高级语言程序必须被编译程序(Compiler)翻译成机器语言程序才能被执行C语言概述sum = n1 + n2;15
  • 16. C语言的简史C语言的起源 美国贝尔实验室的Dennis Ritchie在1972年开发的 用于设计UNIX操作系统 其前身是B语言(Ken Thompson于1970年为第一个UNIX系统开发的语言) C语言的标准 美国国家标准化组织(ANSI)于1983年成立了一个委员会(X3J11),以确定C语言的标准。 该标准(ANSI C)于1989年被正式采用,即C89。 最新的标准是C99标准。 目前,大多数C语言编译器没有完全实现C99的所有修改。C语言概述16
  • 17. 为什么学习和使用C语言C语言的特点 C语言是一种强大而灵活的语言,可以用来编写任意复杂的程序。 C语言简洁、紧凑,使用方便、灵活。 C语言程序效率高、运行速度快。 C语言是可移植的。 C语言很适合结构化程序设计,因而要求用户以功能模块的方式来思考问题。 C语言具有自我扩展能力。C语言概述17
  • 18. 为什么学习和使用C语言C与C++(C加加,see-plus-plus) 贝尔实验室于二十世纪八十年代在C语言的基础上开发的。 C++是C语言的超集,包含了C语言的所有内容,同时增加了面向对象编程方面的内容。 C与Java(爪哇) Sun公司于1995年发布的面向对象编程语言。 Java也是基于C语言的。 C与C#(see-sharp) 微软在2000年6月与.NET平台一同推出。 C#也是从C语言派生的一种面向对象语言。C语言概述18
  • 19. C语言程序的开发过程一般分成五个步骤 定义程序目标 设计程序 编写代码 编译 运行、测试和调试程序C语言概述19
  • 20. C语言程序的开发过程定义程序目标 在开始解决问题之前,程序的开发人员必须彻底了解问题的是什么,对即将要创建的程序要做的事情有个清晰的想法。 这需要仔细考虑以下问题 程序需要什么信息 程序需要执行哪些计算和操作 程序应该报告什么信息 例如,计算任意一个圆环的面积 提出问题,明确目标 圆环是什么?如何计算?计算精度?C语言概述20
  • 21. C语言程序的开发过程设计程序 决定程序如何去解决问题。 需要考虑 如何给程序提供必要的数据 程序中如何表示数据 用什么方法来处理数据 程序如何组织 程序怎样报告结果 完成这个程序需要多长时间 应该用一般的概念考虑设计中的问题,而不是考虑具体的代码。 C语言概述21
  • 22. C语言程序的开发过程编写代码 编写代码来实现前面的设计,即用某一门语言来表示程序设计。 需要开发人员熟练掌握该语言的知识。 可以在草稿纸上勾画自己的想法或书写代码,但最终必须将代码输入计算机。 输入代码所采用的机制则取决于具体的编程环境。 一般来说,需要使用文本编辑器(例如Windows的记事本程序)来创建一种文件,称为源文件 源文件包含程序设计的C语言表示形式(称为源程序或源代码)C语言概述22
  • 23. C语言程序的开发过程编译 把C语言源程序转换成机器语言表示的程序。 这种程序可以直接被计算机理解和执行,所以称之为可执行程序,放在可执行文件中。 编译的具体细节取决于使用的编程环境。 C语言分两步完成这一工作:编译和链接。 编译器将源代码转换为目标代码,并存在目标文件中。 链接器将目标代码与其他代码结合起来生成可执行文件。 这种把编译和链接分开来做的方法便于程序的模块化。 可以分别编译程序的各个模块,然后用链接器把编译过的模块结合起来。 这样,如果需要改变一个模块,则不需要重新编译所有其他模块。C语言概述23
  • 24. C语言程序的开发过程运行、测试和调试程序 运行包含可执行程序的文件,观察运行的结果。 在不同的系统中运行程序的方式可能不同。 例如,Windows的控制台和资源管理器。 应该对程序进行仔细的检查,看程序是否在做该做的事。 比较好的做法是为验证程序的正确性设计一个测试计划。 越早做越好,因为它有助于理清程序员的思路。 程序中的错误被称为bug,调试(Debug)就是要发现并修正错误。C语言概述24
  • 25. C语言的编程环境开发程序的具体操作取决于使用的编程环境 因为C语言是可移植的,所以它在许多环境中都是可用的。 例如UNIX、Linux、Windows和MS-DOS。 C语言编程环境包括一系列程序。 编辑器 编译器 链接器 运行和调试环境C语言概述Hello.cHello.objHello.exe编辑器源代码编译器目标代码链接器可执行代码……启动代码库代码执行如果有错如果有错如果有错25
  • 26. C语言的编程环境编辑器 使用一个文本编辑器程序输入源代码,并将代码保存在源文件中。 一般,C程序的源文件名称的扩展名是.c 例如welcometoyou.c和Hello.c。 该名称应该遵循特定的操作系统的命名规则。 例如,MS-DOS要求基本名包含的字符数不能大于8,所以welcometoyou.c不是合法的DOS文件名。 Windows允许长文件名,所以welcometoyou.c是合法的Windows文件名。C语言概述Hello.c编辑器源代码26
  • 27. C语言的编程环境编译器 编译器接收源文件,生成目标文件,扩展名为.obj或.o。 编译器还会检查输入的程序是否是有效的C语言程序。 如果编译器发现错误,就会报告出错,且不能生成可执行程序。 这时就必须修改错误,然后再编译。 显然,为了能迅速找到错误,理解特定编译器的报错信息是一项有用的技能。C语言概述Hello.cHello.obj编辑器源代码编译器目标代码如果有错27
  • 28. C语言的编程环境链接器 目标代码虽然已经是机器代码,但还不能被执行。 缺少下面两个东西 启动代码:相当于程序和操作系统之间的接口。 库函数的代码:库文件中包含许多函数的目标代码。 链接器的作用就是将这三个元素(目标代码、启动代码和库代码)结合起来,并将它们放在一个文件中,即可执行文件,扩展名为.exe或.out。C语言概述Hello.objHello.exe目标代码链接器可执行代码……启动代码库代码如果有错28
  • 29. Windows的C语言编程环境Windows的C语言编程环境 Windows并不包含C编译器,所以需要获得并安装一个C编译器。 许多现代的编译器都是集成开发环境(或叫IDE)的一部分。 包括一个编辑器、编译器、链接器和包括一个符号调试程序在内的运行支持系统。 许多软件厂商都提供了基于Windows的集成开发环境 Microsoft公司的Visual C/C++ Borland公司的C/C++ Builder Metrowerks公司的Code Warrior 目前,大多数IDE把C和C++编译器结合在一起。C语言概述29
  • 30. DOS的C语言编程环境DOS的C语言编程环境 基于DOS的IDE Borland公司的Turbo C Borland公司的Borland C++ 许多Windows上的IDE也提供了在DOS命令行环境中编程的命令行工具。C语言概述30
  • 31. C语言编程实验环境简介上机实验环境简介 举例演示Visual C++ 6.0的基本操作 启动 创建源文件 构建工程(编译、链接) 运行程序C语言概述31
  • 32. 小结C是一种通用的程序设计语言,具有许多突出的优点。 程序设计的初学者应该严格按照C语言程序的开发过程进行训练,以便尽快掌握程序设计的基本原则。 C是一种需要编译的高级语言。 进行C语言编程的具体操作依赖于所使用的编程环境。 程序设计是一门实践性很强的课程,在学习过程中必须十分重视实验环节。C语言概述32
  • 33. 第二讲 C语言快速入门
  • 34. 第二讲 C语言快速入门C语言程序的基本结构 C语言的字符集和关键字 Visual C++ 6.0编程环境C语言程序设计34
  • 35. C语言程序的基本结构一个简单的C语言程序 问题:在屏幕上显示一行文字:“Hello World!” 源程序 运行结果C语言快速入门/* 在屏幕上显示Hello World! */ #include void main() /* 定义主函数 */ { printf("Hello World!\n"); }Hello World!35
  • 36. C语言程序的基本结构一个简单的C语言程序 程序结构分析C语言快速入门/* 在屏幕上显示Hello World! */ #include void main() /* 定义主函数 */ { printf("Hello World!\n"); } 注释部分 以增加程序的可读性。 不被执行。36
  • 37. C语言程序的基本结构一个简单的C语言程序 程序结构分析C语言快速入门/* 在屏幕上显示Hello World! */ #include void main() /* 定义主函数 */ { printf("Hello World!\n"); } 预处理命令 程序中包含某一文件内容。 “stdio.h”为标准输入输出函数头文件名。37
  • 38. C语言程序的基本结构一个简单的C语言程序 程序结构分析C语言快速入门/* 在屏幕上显示Hello World! */ #include void main() /* 定义主函数 */ { printf("Hello World!\n"); } 函数 C程序由函数组成。 一个C程序有且仅有一个主函数(main函数)。38
  • 39. C语言程序的基本结构一个简单的C语言程序 程序结构分析C语言快速入门/* 在屏幕上显示Hello World! */ #include void main() /* 定义主函数 */ { printf("Hello World!\n"); } 函数说明 说明函数类型、函数名和函数参数。39
  • 40. C语言程序的基本结构一个简单的C语言程序 程序结构分析C语言快速入门/* 在屏幕上显示Hello World! */ #include void main() /* 定义主函数 */ { printf("Hello World!\n"); } 函数体 包括变量说明和语句执行部分。40
  • 41. C语言程序的基本结构一个简单的C语言程序 程序结构分析C语言快速入门/* 在屏幕上显示Hello World! */ #include void main() /* 定义主函数 */ { printf("Hello World!\n"); } 函数调用语句 格式输出函数printf( )。 输出“Hello world!”。Hello World!41
  • 42. C语言程序的基本结构含有算术运算的C语言程序 问题:求方程 的根(设 )。 源程序C语言快速入门#include #include void main() { float a,b,c,p,disc,q,x1,x2; scanf("%f%f%f",&a,&b,&c); p=-b/(2*a); disc=b*b-4*a*c; q=sqrt(disc)/(2*a); x1=p+q; x2=p-q; printf("x1=%f,x2=%f",x1,x2); }42
  • 43. C语言程序的基本结构含有算术运算的C语言程序 程序结构分析C语言快速入门#include #include void main() { float a,b,c,p,disc,q,x1,x2; scanf("%f%f%f",&a,&b,&c); p=-b/(2*a); disc=b*b-4*a*c; q=sqrt(disc)/(2*a); x1=p+q; x2=p-q; printf("x1=%f,x2=%f",x1,x2); } 预处理命令43
  • 44. C语言程序的基本结构含有算术运算的C语言程序 程序结构分析C语言快速入门#include #include void main() { float a,b,c,p,disc,q,x1,x2; scanf("%f%f%f",&a,&b,&c); p=-b/(2*a); disc=b*b-4*a*c; q=sqrt(disc)/(2*a); x1=p+q; x2=p-q; printf("x1=%f,x2=%f",x1,x2); } 函数 一个主函数(main函数)。44
  • 45. C语言程序的基本结构含有算术运算的C语言程序 程序结构分析C语言快速入门#include #include void main() { float a,b,c,p,disc,q,x1,x2; scanf("%f%f%f",&a,&b,&c); p=-b/(2*a); disc=b*b-4*a*c; q=sqrt(disc)/(2*a); x1=p+q; x2=p-q; printf("x1=%f,x2=%f",x1,x2); } 函数说明部分45
  • 46. C语言程序的基本结构含有算术运算的C语言程序 程序结构分析C语言快速入门#include #include void main() { float a,b,c,p,disc,q,x1,x2; scanf("%f%f%f",&a,&b,&c); p=-b/(2*a); disc=b*b-4*a*c; q=sqrt(disc)/(2*a); x1=p+q; x2=p-q; printf("x1=%f,x2=%f",x1,x2); } 函数体 变量说明部分 执行部分46
  • 47. C语言程序的基本结构含有算术运算的C语言程序 程序结构分析C语言快速入门#include #include void main() { float a,b,c,p,disc,q,x1,x2; scanf("%f%f%f",&a,&b,&c); p=-b/(2*a); disc=b*b-4*a*c; q=sqrt(disc)/(2*a); x1=p+q; x2=p-q; printf("x1=%f,x2=%f",x1,x2); } 运行程序 键盘输入a、b、c的值; 屏幕显示x1和x2的值。1 0 -4 x1=2.000000,x2=-2.00000047
  • 48. C语言程序的基本结构包含多个函数的C语言程序 问题:求|a|+|b|的值。 源程序C语言快速入门#include float absv(float x) { float y; y=x>=0?x:-x; return y; } void main() { float a,b,c; printf("input a,b:\n"); scanf("%f%f",&a,&b); c=absv(a)+absv(b); printf("|a|+|b|=%f\n",c); }48
  • 49. C语言程序的基本结构包含多个函数的C语言程序 程序结构分析C语言快速入门#include float absv(float x) { float y; y=x>=0?x:-x; return y; } void main() { float a,b,c; printf("input a,b:\n"); scanf("%f%f",&a,&b); c=absv(a)+absv(b); printf("|a|+|b|=%f\n",c); } 程序由两个函数组成: absv函数 main函数49
  • 50. C语言程序的基本结构包含多个函数的C语言程序 程序结构分析C语言快速入门#include float absv(float x) { float y; y=x>=0?x:-x; return y; } void main() { float a,b,c; printf("input a,b:\n"); scanf("%f%f",&a,&b); c=absv(a)+absv(b); printf("|a|+|b|=%f\n",c); } 调用absv函数求绝对值。50
  • 51. C语言程序的基本结构包含多个函数的C语言程序 程序结构分析C语言快速入门#include float absv(float x) { float y; y=x>=0?x:-x; return y; } void main() { float a,b,c; printf("input a,b:\n"); scanf("%f%f",&a,&b); c=absv(a)+absv(b); printf("|a|+|b|=%f\n",c); } 运行程序 屏幕显示提示信息; 键盘输入a、b的值; 屏幕显示a、b绝对值之和。input a,b: -5 13 |a|+|b|=18.00000051
  • 52. C语言程序的基本结构C语言程序的基本结构小结 C语言快速入门⑴一个C程序由一个或多个函数组成,但有且仅有一个主函数(main函数);main函数是程序执行的入口,可置于程序的任何位置。 ⑵程序中可以有预处理命令(如:include 命令)。预处理命令通常放在程序的最前面。 ⑶每个语句以分号结尾;预处理命令、函数头和花括号“}”之后不能加分号。 ⑷ 函数包括函数的说明部分和函数体;函数体包括变量说明部分和执行部分;函数体由一对花括号“{ }”括起来。 ⑸括在/*…*/内的文本为注释。 ⑹一行可以写几个语句,一个语句也可以写在多行上。 ⑺程序区分大小写字母。一般变量、语句等用小写字母书写;符号常量、宏名等用大写字母书写。 ⑻标识符和保留字之间须加空格以示分隔。52
  • 53. C语言的字符集C语言源程序中使用的字符来自C语言的字符集。 字符集(Character set)——来自ASCII表 52个大小写字母(Letters) A B C D E F G H I J K L M N O P Q R S T U V W X Y Z a b c d e f g h i j k l m n o p q r s t u v w x y z 10个数字(Digits) 0 1 2 3 4 5 6 7 8 9 空白符(Blanks) 空格符、制表符、回车符、换行符 图形符号(Graphic characters) ! # % ^ & * ( _ ) - + = ~ [ ] ' | \ ; : " {} , . < > / ?C语言快速入门53
  • 54. C语言的关键字关键字(Keywords)是C语言中的词汇。 也称为保留字(Reserved words) 类型说明 int、long、short、float、double、char、unsigned、signed、const、void、volatile、enum 、struct、union 语句定义 if、else、goto、switch、case、do、while、for、continue、break、return、default、typedef 存储类别说明 auto、register、extern、static 长度运算符 sizeofC语言快速入门54
  • 55. C语言的标识符标识符(Identifiers)是程序中引用对象的名称。 用来标识变量、符号常量、数组、函数、结构体、共用体、自定义类型等。 命名规则 只能包括大小写字母、数字和下划线; 首字符必须是字母或下划线; 一般内部标识符的前31个字符有效,而外部标识符的前6个字符有效;(视具体编译器的规定) 不能与关键字相同。C语言快速入门count,student_name,sum,test13,_number,Sum M.John,$123,hi!,12xyz,void55
  • 56. Visual C++ 6.0编程环境举例演示Visual C++ 6.0的基本操作 创建工作区 创建工程(程序)并添加到工作区 创建源文件并添加到工程 构建工程(编译、链接) 运行程序C语言快速入门56
  • 57. 程序的测试与调试程序错误 语法错误 未遵循C的语法规则。 编译器可以检查。 运行时错误 在程序运行时执行了无法处理的操作。 逻辑错误 程序不能按照预期的方式运行,不能产生预期的结果。C语言快速入门57
  • 58. 程序的测试与调试程序测试 审查和运行程序,检测程序是否存在错误。 测试步骤 在编译前仔细检查程序的源代码,看看是否有明显的错误。 查看编译器发现的语法错误。 对于运行时错误和逻辑错误可以采用正确输出测试。 想找出所有错误是不切实际的。C语言快速入门58
  • 59. 程序的测试与调试程序调试 隔离和修正错误。 调试方法 采用手工方式来跟踪程序。 在程序中的关键点放置输出语句来显示变量的值。 使用调试器。C语言快速入门59
  • 60. 小结C程序的基本结构 C语言程序是由一个或多个函数组成的。函数可以是标准库函数和自定义的函数。 C语言的语句可以包含一个表达式,也可以包含一个函数调用,以一个分号作为结束标志。 C语言中的变量在使用之前必须先声明,通过声明语句中为变量指定一个名字和数据类型。可以通过赋值运算改变变量的值。 C语言可以使用scanf()函数和printf()函数输入和输出数据。C语言快速入门60
  • 61. 小结编译器可以发现程序中的语法错误,但是程序中的运行时错误和逻辑错误只有在程序运行时才表现出来。检测运行时错误和逻辑错误的一种简单实用的方法是跟踪程序的运行状态,即程序每执行一步之后所有或主要变量的值。C语言快速入门61
  • 62. 第三讲 基本数据类型
  • 63. 第三讲 基本数据类型引例 变量与常量 预备知识 内储存器的组织 数据类型 基本数据类型 整数类型 字符 浮点类型C语言程序设计63
  • 64. 引例引例 问题 计算任意一个圆的面积。 源程序(cw02-01.c)基本数据类型#include void main() { float r, area; //定义变量 scanf(“%f”,&r); //读取输入的半径 area = 3.14*r*r; //计算圆的面积 printf(“area=%f”,area); //输出 }1 area=3.140000areararea = Лr264
  • 65. 变量与常量数据(Data) 程序需要使用数据。 数据是信息的载体。 数据有多种形式:数、字符、图片等。 常量(Constants) 在程序运行之前可以预先设定,并在整个运行过程中没有变化的数据。 例如引例中的圆周率3.14。 变量(Variables) 在程序运行过程中可能变化或被赋值的数据。 例如引例中的半径和面积。基本数据类型65
  • 66. 数据类型数据类型的作用 决定数据的存储方式和占用的存储空间的大小。 决定可以进行的操作。 C语言的数据类型 基本类型 整型(integer),字符型(character),浮点型(floating-point),枚举类型(enumeration) 构造类型 结构体(structure),共用体(union),数组(array) 指针类型(pointer) 空类型(void)基本数据类型66
  • 67. 位、字节和字内存储器的组织 基本数据类型位(bit):最小的存储单位,可以容纳两个值之一,即0或1。200020012002字节(Byte):基本的存储单位,8位。字(word):自然的存储单位,包含若干个字节。例如32位机的一个字就是32位。地址:以字节为单位从0开始编号。67
  • 68. 整数的存储方式有符号的正整数 在内存中以二进制补码形式存放。 正整数的补码与原码相同。 例如:10基本数据类型符号位0 0 0 0 0 0 0 00 0 0 0 1 0 1 00000000000001010RAM数轴0215-1=32767可表示的数的范围-215=-3276868
  • 69. 整数的存储方式有符号的负整数 在内存中以二进制补码的形式存放。 例如:-10基本数据类型符号位1 1 1 1 1 1 1 11 1 1 1 0 1 1 01111111111110110RAM-10的原码1 0 0 0 0 0 0 00 0 0 0 1 0 1 01 1 1 1 1 1 1 11 1 1 1 0 1 0 11 1 1 1 1 1 1 11 1 1 1 0 1 1 0按位取反加一后得到 -10的补码符号位不变69
  • 70. 整数的存储方式无符号整数 所有二进制位都存放数值。 例如:65535基本数据类型1 1 1 1 1 1 1 11 1 1 1 1 1 1 11111111111111111RAM0216-1=65535可表示的数的范围70
  • 71. 整数的类型C语言提供多种整数类型 为程序员提供了针对不同用途的多种选择。基本数据类型类型名称类型说明符字节数数值范围基本整型[signed] int4-2147483648~ 2147483647短整型[signed] short [int]2-32768~32767长整型[signed] long [int]4-2147483648~ 2147483647无符号基本整型unsigned [int]40~4294967295无符号短整型unsigned short [int]20~65535无符号长整型unsigned long [int]40~4294967295C标准只规定:short ≤ int ≤ long 最大最小值参考仅供参考,实际值与所使用的操作系统、编译系统、机器有关。71
  • 72. 整型常量整型常量有三种形式: 十进制(decimal)整数 12 65 65535 八进制(octal)整数:带前缀 0(zero) 014 0101 0177777 十六进制(hexadecimal)整数:带前缀 0x 或 0X 0xc 0x41 0xffff 默认类型是int,即有符号的基本整型。 可以加上后缀 u 或 U 表示无符号整数,或者 l 或 L 表示长整数。 0xb5Lu基本数据类型72
  • 73. RAM整型变量声明变量(Declaration) 变量在使用之前必须被声明。 声明语句的格式: 举例基本数据类型<类型说明符> <变量名>[,<变量名>[,...]];int counter; int width, height; short x, y; long number;变量声明创建了变量:为变量分配了存储空间。height73
  • 74. 整型变量初始化(Initialize)变量 为变量赋一个初始值。 可以在声明语句中初始化变量。 举例 变量获得值的方法 直接赋值 输入 初始化基本数据类型int counter = 0; int width = 352, height = 288;初始化式RAM288height74
  • 75. 整型变量输出变量的值 可以使用printf()函数。 与int类型对应的格式说明符是%d。 举例(cw02-02a.c)基本数据类型#include void main() { int a, b; a=32767; b=-32768; printf("a=%d,b=%d\n", a, b); }a=32767,b=-3276875
  • 76. 整数的溢出溢出 整数太大,超出了整数类型的数值范围。 使用printf() 时与unsigned int类型对应的格式说明符是%u。 举例(cw02-02b.c)基本数据类型#include void main() { int a, b; unsigned c, d; a = 2147483647; b = a+1; c = 4294967295; d = c+1; printf("a=%d,b=%d\n", a, b); printf("c=%u,d=%u", c, d); }a=2147483647,b=-2147483648 c=4294967295,d=0???76
  • 77. 整数的溢出溢出 结果分析 结论 a+1 的结果(2147483648) 超出了基本整型变量 b 所能容纳(表示)的数值范围(-2147483648~2147483647)。 请分析d的结果。基本数据类型0 1 1 1 1 1 1 1...1 1 1 1 1 1 11 0 0 0 0 0 0 0...0 0 0 0 0 0 0a(2147483647)1 0 0 0 0 0 0 0...0 0 0 0 0 0 0a+1(2147483648)b(-2147483648)12加正数减正数77
  • 78. 字符的存储方式字符编码 计算机使用一种数字编码(整数)来表示字符,每一个字符都对应一个特定的整数。 常用的编码是ASCII(美国信息交换用标准码)。 7位二进制数,十进制码值范围从0到127。 一般用一个字节保存,最高位为0。 字符的存储方式与整数相同 举例 字母A的ASCII码值为65, 那么在内存中以65的二进制形式存储, 且占一个字节。基本数据类型0100000101000001RAM78
  • 79. 字符的类型和字符变量C语言的字符类型:char 占一个字节; 可视为一个有符号的整数。 举例(cw02-03.c)基本数据类型#include void main() { char c1, c2; //声明字符变量 c1 = 97; //把一个整数赋值给字符变量 c2 = c1-32; //字符变量可以进行算术运算 printf("c1=%c,c2=%c\n", c1, c2); printf("c1=%d,c2=%d\n", c1, c2); }c1=a,c2=A c1=97,c2=6579
  • 80. 字符常量字符常量 用单引号括起来的一个字符。 'x' '9' '+‘ C语言将字符常量视为int类型。 举例(cw02-04.c) 如果int类型为16位,char类型为8位, 那么对于’bc’,将把’b’和’c’的ASCII码值存储在两个字节中,并把’c’赋值给变量c2。 注意:不同系统处理方式不同,结果不同。基本数据类型char c1, c2; c1=‘a’; c2=‘bc’;0 1 1 0 0 0 0 1c10 1 1 0 0 0 1 1c20 1 1 0 0 0 1 10 1 1 0 0 0 1 0‘bc’LH9799999880
  • 81. 字符常量转义字符(escape character) 指代一些特殊的字符。(打印不出来的字符) 举例(cw02-05.c)基本数据类型\a 警报 \\ 反斜杠(\) \b 退格 \? 问号(?) \f 走纸 \‘ 单引号(’) \n 换行 \“ 双引号(”) \r 回车 \ooo 八进制值(o表示一个八进制数字) \t 水平制表符 \xhh 十六进制值(h表示一个十六进制数字) \v 垂直制表符#include void main() { printf("a\tb\nc\bd\100\x40\n"); }ab d@@ 81
  • 82. 浮点数的存储方式浮点数 浮点型数据在内存中按指数形式存放。 例如:314.15 = 3.1415×102基本数据类型0 (3.14159)10 0 (2)10 数符 尾数部分 阶符 阶码 + 3.14159 × 10+2由此可见,尾数部分的宽度决定了有效数字的个数(即精度),阶码部分的宽度决定了数值范围。RAM科学计数法允许使用少量的数字表示很大范围的数和很小的数。82
  • 83. 浮点数的类型浮点数也有多种类型 类型名称及典型大小基本数据类型类型名称类型说明符字节数有效数字数值范围单精度float46~7(s)10-37~1038双精度double815~16(s)10-307~10308长双精度long double1018~19(s)10-4931~104932S = ±1 参考仅供参考,实际值与所使用的操作系统、编译系统、机器有关。数轴0可表示的正数可表示的负数83
  • 84. 浮点型常量浮点型常量有两种形式: 十进制形式 12.3 .65 0. 指数形式:< 小数 > < e | E > < 整数 > 1.2e-2 .1E5 7E0 1.2×10-2 0.1×105 7.0×100 默认类型是double。 可以加上后缀 f 或 F 表示float类型,或者 l 或 L 表示long double类型,否则该常量是double类型。 2.3f 1.2L .1E5f基本数据类型84
  • 85. 浮点型变量浮点型变量的声明和初始化 举例基本数据类型float radius; double x = 0.0, y = 0.0;不能写成: double x = y =0.0;85
  • 86. 浮点型变量浮点数的输出 使用printf()函数 float和double对应的格式说明符为%f、%e。 举例(cw02-06.c)基本数据类型#include void main() { float f; double d; f=33333.33333f; d=33333.3333333333; printf("f=%f\nd=%f", f, d); }f=33333.332031 d=33333.333333有效数字位数是有限的,在可表示的有效位之外的数字被舍去。因此可能会产生误差。86
  • 87. 浮点数的舍入误差浮点数的舍入误差 举例(cw02-07.c)基本数据类型#include void main() { float a, b; a=123456.789e5; b=a+20; printf("a=%f\nb=%f", a, b); }a=12345678848.000000 b=12345678848.000000???87
  • 88. 浮点数的舍入误差浮点数的舍入误差 结果分析基本数据类型a=123456.789e5; b=a+20;a+20的理论值应该是:12345678920 但是,一个实型变量能够保证的有效数字是7位,后面的数字将被舍去,是没有意义的。 因此,最后得到 b=12345678848.000000应当避免一个很大的数和一个很小的数直接相加或相减,否则就会“丢失”较小的数。88
  • 89. 浮点数的溢出上溢 举例(cw02-08.c) 若某系统中的最大float值为3.4e38,进行如下操作 得到结果 下溢 举例:假设-10是最小的指数,能够保留四位有效数字 如果把数0.1234e-10除以10,将得到结果0.0123e-10,但损失了一位有效数字。基本数据类型float toobig = 3.4e38 * 100.0f; printf(“toobig=%f", toobig);…inf…无穷大(infinity)89
  • 90. 小结C语言有多种数据类型。 基本的数据类型包括两大类: 整数类型 浮点类型 开发程序时,应当注意所需变量及其类型的选择。 一般使用int和float表示数,用char表示字符。 在使用变量的函数的可执行语句之前声明该变量,并为它选择有意义的名字。 初始化变量使用的常量应当与变量的类型相匹配。基本数据类型90
  • 91. 第四讲 输入输出和库函数
  • 92. 第四讲 输入输出和库函数引例 字符串常量 符号常量 格式化输入输出 字符输入输出 库函数C语言程序设计92
  • 93. 引例引例 问题:计算任意一个球体的体积。 源程序(cw03-01.c)输入输出和库函数#include #include #define PI 3.14159 //定义符号常量 void main() { double r, volume; printf("Please input the radius of a ball: "); scanf("%lf", &r); volume = 4.0/3.0*PI*pow(r, 3.0); //使用数学函数 printf(“Volume = %.5f\n”, volume); //按格式输出 }Please input the radius of a ball: 1 Volume = 4.18879volume = 4/3Лr393
  • 94. 字符串常量字符串常量 用双引号括起来的字符序列。 "this is a string" "x" "12345" 字符串常量的存储方式 字符串中的字符存放在相邻的存储单元中,每个字符占用一个单元,在最后加上一个空字符(\0)作为结束标识符。 举例输入输出和库函数thisisastring\0每个字符占一个字节结束标记94
  • 95. 字符串常量字符串常量和字符常量的区别 字符‘x’是基本型数据。 字符串”x”是构造型数据。 占用的存储空间大小不同。输入输出和库函数‘x’x在内存中占一个字节“x”x在内存中占两个字节\095
  • 96. 符号常量符号常量 符号常量是用一个标识符表示的常量。 定义规则 #define <符号常量名> <字符串> 举例输入输出和库函数#define MAXSIZE 20 #define WELCOME "Welcome to you!" #define PI 3.14编译预处理器将会用3.14替换以后出现的PI标识符。96
  • 97. 符号常量为什么需要符号常量 名字比数字包含的信息多。 便于维护 如果需要在多个地方用到同一个常量,而且必须改变它的值,则只需要修改其符号常量的定义。 另一种定义符号常量的方法 使用const把一个变量声明转换成常量声明。 举例 输入输出和库函数const int MAX_NUMBER = 30;MAX_NUMBER是只读的。97
  • 98. 输入输出输入输出是以计算机主机为主体而言的。 输出(Output):从主机向外部输出设备输出数据。 输入(Input):从外部通过输入设备向主机输入数据。输入输出和库函数主 机键盘 磁盘 光盘 扫描仪 ……显示器 打印机 磁盘……输入输出98
  • 99. 输入输出流流(stream) 按直线排列的字符序列 每个序列包括0个或多个字符,并用换行符结尾 ANSI C支持至少254个字符的序列 所有的输入/输出都是用流来进行的。输入输出和库函数S@!ci’[]源目的99
  • 100. 标准输入输出流开始执行程序时,3个流自动连接到程序上 标准输入流(standard input)对应的源端设备:keyboard 标准输出流(standard output)对应的目的端设备:screen 标准错误流(standard error)对应的目的端设备:screen 操作系统允许这些流重定向到其它设备输入输出和库函数type myprog.c > prn输出重定向符myprog < data.txt输入重定向符100
  • 101. 输入输出函数C语言中数据的输入、输出是由函数来实现的。 字符输入函数:getchar() 字符输出函数:putchar() 格式输入函数:scanf() 格式输出函数:printf() 它们都属于标准输入输出库函数,其原型(prototype)都在stdio.h头文件(header file)中。输入输出和库函数101
  • 102. 字符输入输出putchar()函数 getchar()函数 使用说明 程序中必须包含 stdio.h 头文件输入输出和库函数#include 102
  • 103. 字符输出putchar 使用形式 putchar(character); 向标准输出流输出一个字符character 使用说明 character可以是以下数据: 字符型数据 整型数据 转义字符输入输出和库函数103
  • 104. 字符输出举例(cw0302.c)输入输出和库函数#include void main() { char a; int b; a=‘B’; b=79; putchar(a); putchar(‘\n’); putchar(b); putchar(‘\n’); putchar(‘Y’); }B O Y104
  • 105. 字符输入getchar 使用形式 variable = getchar(); 从标准输入流读取一个字符。 使用说明 只读取一个字符。 该字符可赋给字符变量、整型变量或作为表达式的一部分。输入输出和库函数105
  • 106. 字符输入举例(cw0303.c)输入输出和库函数#include void main() { char c; c = getchar(); putchar(c); }a aabc a98 9123106
  • 107. 格式输入输出printf()函数 scanf()函数 使用说明 程序中可以不明确指定包含 stdio.h 头文件输入输出和库函数#include 可以省略此命令107
  • 108. 格式输出printf 使用形式 printf(<格式控制字符串>, <输出列表>); 按格式控制字符串规定的格式,向指定的输出设备输出输出列表中的输出项。 使用说明 格式控制字符串 由双引号括起来的字符串,用于指定输出格式 输出列表 需要输出的数据列表,彼此间用逗号分隔 输出项可以是任意合法的表达式输入输出和库函数108
  • 109. 格式输出格式控制字符串 举例输入输出和库函数printf(“n=%5d,f=%5.2f\n”, 3, 6.235)普通字符 原样输出 格式说明符(转换规则) % [修饰符] 格式字符 指定数据的输出格式n= 3,f= 6.24109
  • 110. 格式输出格式字符输入输出和库函数格式字符功 能d, i以十进制有符号形式输出整数(正数不输出符号)o以八进制无符号形式输出整数(不输出前缀)x, X以十六进制无符号形式输出整数(不输出前缀)u以十进制无符号形式输出整数f以小数形式输出单、双精度实数e, E以指数形式输出单、双精度实数g, G选用%f和%e格式中输出宽度较短的一种,不输出无意义的零c以字符形式输出,输出一个字符s输出字符串110
  • 111. 格式输出格式修饰符输入输出和库函数修饰符意 义l用于长整型和长双精度实型数据,可加在格式字符 d、o、x、u、f 前面。m(正整数)数据最小宽度。.n(正整数)对于实数,表示输出n位小数; 对于字符串,表示截取的字符个数。-输出的数字或字符在域内向左靠,右边填空格。#当整数以八进制或十六进制形式输出时,输出前缀。 可加在格式字符o、x前面。111
  • 112. 格式输出格式说明符小结输入输出和库函数格式字符形 式d, i%d,%md,%-md,%ld,%mld,%-mld,%i,……o%o,%mo,%-mo,%lo,%mlo,%-mlo,%#o,……x, X%x,%mx,%-mx,%lx,%mlx,%-mlx,%#x,……u%u,%mu,%-mu,%lu,%mlu,%-mluf%f,%mf,%m.nf,%lf,……e, E%e,%me,%m.ne,……g, G%gc%cs%s,%ms,%m.ns,……要输出%就用%%112
  • 113. 格式输出举例(cw0304.c)输入输出和库函数void main() { int a=97,b=-1; float f=123.4; printf(“%d,%c\n”,a,a); printf(“%d,%o,%x,%u\n”,b,b,b,b); printf(“%f,%e,%gEND”,f,f,f); }97,a -1,177777,ffff,65535 123.400002,1.234000e+02,123.4END1 1 1 1 1 1 1 11 1 1 1 1 1 1 1BC113
  • 114. 格式输出举例(cw0305.c)输入输出和库函数void main() { int a=1,b=2,c=3; printf(“%d,%d,%d,%d\n”,a,b,c); printf(“%d,%d,%d\n”,a,b,c,a+b+c); }1,2,3,898 1,2,3格式说明符和输出项在数量和类型上应该一一对应。114
  • 115. 格式输入scanf 使用形式 scanf(<格式控制字符串>, <地址列表>); 按格式控制字符串规定的格式,从指定的输入设备读入数据,并存放到地址列表中的各地址项指定的变量中 使用说明 格式控制字符串 由双引号括起来的字符串,用于指定输入格式 地址列表 由若干个变量的地址组成输入输出和库函数115
  • 116. 格式输入地址列表 取地址运算符:& &<变量> 得到变量在内存中的地址。输入输出和库函数scanf(“%d,%d”, &a, &b)FF00FF02ab116
  • 117. 格式输入格式字符输入输出和库函数格式字符功 能d, i以十进制形式输入有符号整数o以八进制形式输入无符号整数x, X以十六进制形式输入无符号整数u以十进制形式输入无符号整数f以小数形式或指数形式输入实数e, E, g, G同f,它们之间可以互换c输入单个字符s输入字符串117
  • 118. 格式输入格式修饰符输入输出和库函数修饰符意 义l用于输入长整型和双精度实型数据,可加在格式字符 d、o、x、u、f、e 前面。h用于输入短整型数据。m(正整数)域宽,指定输入数据所占的宽度。*表示本输入项读入后不赋给任何变量,即跳过该输入值。118
  • 119. 格式输入格式说明符小结输入输出和库函数格式字符形 式d, i%d,%md,%ld,%mld,%i,……o%o,%mo,%lo,%mlox, X%x,%mx,%lx,%mlxu%u,%mu,%lu,%mluf%f,%mf,%lf,%mlfe, E, g, G%e,%me,%le,%mle,%g,……c%cs%s,%ms119
  • 120. 格式输入举例(cw0306.c)输入输出和库函数void main() { char a,b,c; scanf(“%c%c%c”,&a,&b,&c); printf(“a=%c,b=%c,c=%c",a,b,c); }abc a=a,b=b,c=ca b c a=a,b= ,c=ba bc a=a,b= ,c=ba b a=a,b= ,c=b1234用 c 格式字符输入字符时,若格式控制字符串中无普通字符,那么认为所有输入的字符(包括空格、制表符、换行符)均为有效字符。120
  • 121. 格式输入输入过程分析输入输出和库函数ab2,3.4输入缓冲区scanf(); getchar(); …输入流输入流121
  • 122. 格式输入用c格式字符输入字符时,若格式控制字符串中加入空格作为分隔符,那么输入时各数据之间可以加任意多个空格、制表符或回车符)。 例:scanf(“%c %c %c”,&a,&b,&c); 输入:a b c↙ 则 a=a,b=b,c=c 在下列情况下可认为数据输入结束: 遇到空格、回车、制表符 按指定的宽度结束 遇非法输入输入输出和库函数122
  • 123. 数学库函数使用标准库中的数学函数 包含数学函数库的头文件 #include 常用的数学函数的原型 int abs(int n) double fabs(double x) double pow(double x, double y) double sqrt(double x) 使用举例 int n = abs(-21); double v = pow(r, 3.0);输入输出和库函数123
  • 124. 小结C语言中的字符串常量 C语言中的符号常量 C语言中常用的输入输出函数 使用标准库中的数学函数输入输出和库函数124
  • 125. 第五讲 运算符和表达式
  • 126. 第五讲 运算符和表达式引例 算术运算符 运算符的优先级和结合性 类型转换 自增自减运算符 逗号运算符C语言程序设计126
  • 127. 引例引例 问题 输入一个四位的正整数,反序输出该四位数的四个数字字符。 设计 用一个无符号整型变量number保存输入的四位正整数。 依次分解出个位数字、十位数字、百位数字和千位数字,并分别放到字符变量c1、c2、c3和c4中。 怎么分解? 利用除法、取模等运算。 顺序输出变量c1、c2、c3和c4中的字符。运算符和表达式127
  • 128. 引例引例 源程序(cw04-01.c)运算符和表达式#include void main() { unsigned number; char c1, c2, c3, c4; printf("请输入一个四位的正整数:"); scanf("%u", &number); c1 = number%10 + '0'; //得到个位数字 c2 = number/10%10 + '0'; //得到十位数字 c3 = number/100%10 + '0'; //得到百位数字 c4 = number/1000 + '0'; //得到千位数字 printf("\n%c%c%c%c\n", c1, c2, c3, c4); }请输入一个四位的正整数: 1234 4321128
  • 129. 算术运算符常用的算术运算及运算符 说明 取模运算(%),结果是两个整数相除的余数。 例如,7%5 = 2,-2%3 = -2。运算符和表达式运算符名称算术运算符代数表达式C语言表达式适用的数据类型正号++ a+ a整数、字符、浮点数负号-- b- b整数、字符、浮点数加+f + 7f + 7整数、字符、浮点数减-p – cp – c整数、字符、浮点数乘*bmb * m整数、字符、浮点数除/x / yx / y整数、字符、浮点数取模%r mod sr % s整数、字符129
  • 130. 算术运算符的优先级和结合性运算符的优先级 某些运算符先于其他运算符被执行。 例如,x + y * 4,先乘除后加减。 必要时可以用圆括号()改变计算顺序。 例如,求三个数的平均值。 错误的写法:a + b + c / 3 正确的写法:(a + b + c ) / 3 运算符的结合性 当出现并列的运算优先级别相同的运算符时,由运算符的结合性决定计算的次序。 例如,x * y / z运算符和表达式130
  • 131. 算术运算符的优先级和结合性常用的算术运算符的优先级和结合性 运算符和表达式运算符运算符名称优先级结合性()圆括号1从左往右+ -正号和负号2从右往左* / %乘、除、取模3从左往右+ -加和减4从左往右131
  • 132. 算术表达式算术表达式 算术表达式,是由算术运算符和括号将运算对象(也称为操作数)连接起来的式子。 运算对象包括常量、变量、函数等。 举例运算符和表达式7/5 x+y+z (f1*2)/f2+0.5 sqrt(a)+sqrt(b)表达式有自己的值(计算结果),其值的类型就是表达式的类型。132
  • 133. 算术表达式举例(cw0402.c) 运算符和表达式#include void main() { double f1; double f2; f1=1/3; f2=1.0/3.0; printf("f1=%f\nf2=%f\n", f1, f2); }f1=0.000000 f2=0.333333整数相除结果还是整数。133
  • 134. 类型转换在算术表达式中允许不同类型的数据参与运算。 例如,10+‘a’+1.5-65.4*’b’ 在进行运算时,不同类型的数据要先转换成同一类型,然后进行运算。 转换方式有两种: 自动转换:数据类型自动由低级向高级转换。 强制转换:将表达式的值强制转换成指定的数据类型。运算符和表达式134
  • 135. 自动类型转换这种类型转换由编译系统自动完成。 转换规则:运算符和表达式floatdoublelongunsignedintChar short低高说明: 1、横向的箭头表示必做的转换。 2、纵向的箭头表示当运算对象类型不同时转换的方向。135
  • 136. 强制类型转换类型转换运算符: ( ) (<类型说明符>)[(]<表达式>[)] 将一个表达式的值转换成指定的类型。 属单目运算符。 优先级:2。 结合性:从右往左。 举例 (double)a (int)(x+y) ≠(int)x+y (float)(5/3) =1.0运算符和表达式136
  • 137. 类型转换下面表达式的类型是什么? 10+‘a’+1.5-87.1*’b’ 举例(cw0403.c) 运算符和表达式#include void main() { double f; int i; f=3.6; i=(int)f; printf(“f=%f\ni=%d\n",f,i); }f=3.600000 i=3f 的类型并未因此而改变!137
  • 138. 赋值运算赋值运算符 简单赋值运算符:= 复合赋值运算符:+=,-=,*=,/=,%= 优先级:14 结合性:从右往左 赋值表达式 <变量> <赋值运算符> <表达式> d = 23 作用:将表达式的值赋给变量。 赋值表达式的值就是被赋值的变量的值。运算符和表达式138
  • 139. 简单赋值运算简单赋值运算符:= 举例 类型转换 如果赋值运算符右边表达式的类型与左边变量的类型不一致,则把右边表达式值的类型按左边变量的类型进行转换,然后再赋值。 例如,int n = 3.6; n的值为3。运算符和表达式c=a+b a=b=c=d=10 x=(a=5)+(b=8)a=(a+b) a=(b=(c=(d=10))) a=5, b=8, x=a+b139
  • 140. 复合赋值运算复合赋值运算符:+=, -=, *=, /=, %= 简化了赋值表达式 = 由下面的表达式简化而来 = 举例运算符和表达式a+=5 x*=y+7 x+=x-=x*=xa=a+5 x=x*(y+7) x=x+(x=x-(x=x*x))≡140
  • 141. 自增和自减运算符自增和自减运算符 自增运算符:++ 将操作数的值增一。 自减运算符:-- 将操作数的值减一。 操作数必须是整型和字符型变量。 单目运算符。 优先级:2。 结合性:从右往左。运算符和表达式141
  • 142. 自增和自减表达式表达式形式 前缀形式:++<操作数>或--<操作数> 先改变操作数的值,然后执行表达式中其它运算,并得到表达式的值。 举例 ++c 的值为 c+1 --c 的值为 c-1 后缀形式:<操作数>++或<操作数>-- 先不改变操作数的值,执行表达式中其它运算,并得到表达式的值,然后改变操作数的值。 举例 c++ 的值为 c c-- 的值为 c运算符和表达式142
  • 143. 自增和自减运算举例(cw0404.c)运算符和表达式#include void main() { int i=6, a, b; printf("%d\n", ++i); printf("%d\n", i++); a=--i; printf("%d\n", a); b=i--; printf("%d\n", b); printf("%d\n", -i++); printf("i=%d\n", i); }7 7 7 7 -6i = ?143
  • 144. 自增和自减运算举例(cw0405.c)运算符和表达式#include void main() { int i=5, j=5, p, q; p=(i++)+(i++); q=(++j)+(++j); printf("p=%d,i=%d\n", p, i); printf("q=%d,j=%d\n", q, j); }p=10,i=7 q=14,j=7144
  • 145. 自增和自减运算举例(cw0406.c)运算符和表达式#include void main() { int i, j, k; i=1; j=1; k=i+++j; printf(“i=%d\nj=%d\nk=%d\n",i,j,k); }i=2 j=1 k=2k=(i++)+j;145
  • 146. 逗号运算符逗号运算符:, 优先级:15。 结合性:从左往右。 逗号表达式 <表达式1>, <表达式2>, …, <表达式n> x=1, (a=b+1), i++ 求解过程:先求表达式1的值,再求表达式2的值,···,直至求出表达式n的值。 表达式的值为表达式n的值。运算符和表达式146
  • 147. 逗号运算符举例(cw0407.c)运算符和表达式#include void main() { int a=2, b=4, c=6; int x, y; y=((x=a+b),(b+c)); printf(“x=%d\ny=%d\n", x, y); }x=6 y=10这里的逗号只是分隔符。147
  • 148. 小结常用的算术运算符 运算符的优先级和结合性 类型转换 赋值运算符 自增自减运算符 逗号运算符运算符和表达式148
  • 149. 第六讲 语句和算法
  • 150. 第六讲 语句和算法常用语句 算法及其表示 结构化程序设计 程序设计举例C语言程序设计150
  • 151. 语句语句是构成程序的基本成分。 C语言程序常用的语句: 声明语句 <类型说明符> <变量名>; <类型说明符> <函数名>(<参数表>); 表达式语句 <表达式>; 函数调用语句 <函数名>(<参数表>); 空语句 ;语句和算法151
  • 152. 语句C语言程序常用的语句: 复合语句语句和算法{} { int x, y; x=y=10; printf(“x=%d,y=%d\n”, x, y); }152
  • 153. 语句C语言程序常用的语句: 流程控制语句: 条件判断语句 if if-else switch 循环执行语句 while do-while for 跳转语句 goto语句和算法构造选择结构构造循环结构153
  • 154. 程序设计和算法程序设计 程序 = 数据结构 + 算法 任何计算问题的解决方案包括 按照特定顺序去执行一系列动作。 算法(Algorithm) 为解决某个特定的问题而采用的确定且有限的步骤。 例如 “早晨上学准备算法”: 起床-穿衣-洗漱-吃早餐-上学语句和算法算法是程序的灵魂。 Algorithm is the spirit of a program.154
  • 155. 算法的表示算法的表示方法 自然语言 伪代码 流程图 计算机语言 举例 问题 计算1+2+3+…+100 数据结构 定义整型变量 i 保存加数 定义整型变量 sum 保存每次累加的和语句和算法155
  • 156. 算法的自然语言表示问题 计算1+2+3+…+100 算法的自然语言表示 语句和算法步骤1:i=1,sum=0。 步骤2:如果i不大于100,顺序执行步骤3;否则,执行步骤5。 步骤3: sum加上i,相加后的值仍放在sum中,即:sum = sum+i。 步骤4:使i的值增一得到下一个加数,即i=i+1; 执行步骤2。 步骤5:变量sum中的值就是要得到的结果;输出结果,算法结束。156
  • 157. 算法的伪代码表示问题 计算1+2+3+…+100 伪代码表示语句和算法i=1 sum=0 while i<=100 do sum=sum+i i=i+1 end while print sum人为的、非正式的语言 与日常用语类似 帮助程序员在写程序之前“设想出”程序 很容易被转换成C程序 一般只包括可执行语句157
  • 158. 算法的流程图表示流程图表示语句和算法starti=1 sum=0i<=100sum=sum+i i=i+1Out: sumendYN程序的开始和结束动作流程线判断输入/输出连接158
  • 159. 算法的计算机语言表示C语言表示语句和算法#include void main() { int i, sum; //定义数据结构 i=1; sum=0; //变量初始化 while (i<=100) //循环累加100次 { sum=sum+i; i=i+1; } printf(“1+2+3+...+100=%d”,sum); //输出结果 }159
  • 160. 程序的控制结构程序控制 计算机程序中指定语句执行的顺序。 顺序执行 程序中的语句按照它们的书写顺序一句接一句地执行。 控制转移 把待执行的下一个语句指定为不是书写顺序中的下一个语句。 goto语句带来很多问题。 程序结构不清晰、可读性差、不利于维护。语句和算法160
  • 161. 结构化技术结构化技术(1970s):所有的程序只用3种结构就可以写出来。 顺序结构(Sequence structures) Built into C Programs executed sequentially by default 选择结构(Selection structures) C has three types: if, if-else, and switch 循环结构(Repetition structures) C has three types: while, do/while and for语句和算法161
  • 162. 结构化程序3种控制结构都是单入/单出控制结构。 结构化程序由3种结构通过以下方式组合而成: 堆叠(stacking) 嵌套(nesting)语句和算法sum=sum+ii=i+1sequence structurestackingnesting162
  • 163. 顺序结构顺序结构 按语句书写的顺序执行。语句和算法statement 1statement 2Sequence Structure163
  • 164. 程序设计举例问题: 把十进制整数407转换成八进制形式并输出。 分析与设计 转换规则:除8取余法。 用三个变量分别保存八进制形式的三位数,即三次除法运算所得的余数。语句和算法4078508680627164
  • 165. #include void main() { int n1, n2, n3; printf(“407 = "); n1 = 407%8; n2 = 407/8%8; n3 = 407/8/8%8; printf(“0%d%d%d\n“, n3, n2, n1); }程序设计举例源代码(cw0501.c) 语句和算法407 = 06274078508680627165
  • 166. 小结语句是组成程序的基本成分。 C语言的常用语句 算法 概念 与程序设计的关系 表示方法 结构化程序设计语句和算法166
  • 167. 第七讲 选择结构
  • 168. 第七讲 选择结构关系运算 if语句 if-else语句 问号运算符 逻辑运算 switch语句 程序设计举例C语言程序设计168
  • 169. 引例引例 问题:根据输入的x值,计算y值。 分析 情况一:条件x≤2.5满足时 情况二:条件x>2.5满足时选择结构y =x2+1 (x≤2.5)X2-1 (x>2.5)169
  • 170. 引例引例 源代码(cw0601.c)选择结构#include void main() { float x, y; scanf("%f", &x); if (x <= 2.5) y = x*x+1; else y = x*x-1; printf("y = %.1f\n", y); }x≤2.5y=x2+1y=x2-1是否170
  • 171. 关系运算关系运算(比较运算) 比较两个值,结果为“真”或“假”。 举例:a>3 如果a=8,则结果为“真” 如果a=1,则结果为“假” 用关系运算进行条件判断 关系表达式表示一个条件,根据其结果判断条件是否满足。 举例:a>3 如果a=8,结果为“真”,即条件满足 如果a=1,结果为“假”,即条件不满足选择结构171
  • 172. 关系运算符关系运算符 选择结构运算符含 义优先级结合性<小于6左结合<=小于或等于>大于>=大于或等于==等于7!=不等于172
  • 173. 关系表达式关系表达式 <表达式1> <关系运算符> <表达式2> 表达式1和表达式2可以是任何表达式。 举例选择结构a>b m+n<=20 c!=‘y’ (x>z)==(y>z)173
  • 174. 关系表达式关系表达式的值是一个逻辑值:“真”或“假” C语言没有逻辑类型,因此 用整数 1 表示“真” 用整数 0 表示“假” 即关系表达式的值是整数 1 或 0 。 举例 若a=1,b=2,c=3选择结构a>b a+b<=c (a>c)==(b>c) ‘Y’!=‘y’0 1 1 1174
  • 175. 关系表达式的使用举例:写出下面各条件的关系表达式 x为不小于 -5 的整数 x为非零的数 x的平方大于m与n的和选择结构x>=-5 x!=0 x*x>(m+n)175
  • 176. if选择结构if选择结构 用于在可选择的几个操作之间做出决策。 伪代码语句举例: … if student’s grade is greater than or equal to 60 Print “Passed” … 如果条件满足(学生的课程成绩大于或等于60) 那么就打印 “Passed”,然后程序按顺序执行下一条伪代码语句 否则,即条件不满足(学生的课程成绩小于60) 就忽略打印操作,顺序执行下一条伪代码语句选择结构176
  • 177. if选择结构if选择结构是单入单出的 举例选择结构FalsePrint “Passed”grade >= 60Trueif 选择结构if (grade>=60) printf(“Passed”);条件表达式 可以是任何表达式。 0(zero):假 非0(nonzero):真圆括号不能省略。if子句只能是单条语句。177
  • 178. if-else选择结构if 选择结构 只有在条件满足时,才会执行一个操作; 否则就会跳过这个操作。 if-else 选择结构 条件满足时所执行的操作与不满足时所执行的操作不同。 伪代码语句举例: … if student’s grade is greater than or equal to 60 Print “Passed” else Print “Failed” …选择结构178
  • 179. if-else选择结构if 选择结构 举例选择结构Print “Passed”TruePrint “Failed”grade >= 60Falseif-else 选择结构if (grade>=60) printf(“Passed”); else printf(“Failed”);if子句else子句179
  • 180. 条件运算符条件运算符:? : 三目运算符 右结合 条件表达式 <表达式1>?<表达式2>:<表达式3> 举例选择结构printf(“%s”,(grade>=60)? “Passed”:“Failed”);(grade>=60)?printf(“Passed”):printf(“Failed”);180
  • 181. 嵌套的选择结构嵌套的if-else结构 对多种情况进行测试 伪代码语句举例: 根据学生的课程成绩在不同的分数段内,打印正确的等级名称 90~ A 80~89 B 70~79 C 60~69 D ~59 E选择结构181
  • 182. 嵌套的选择结构嵌套的if-else结构举例 if 学生的分数大于等于 90 打印 “A” else if 学生的分数大于等于80 Print “B” else if 学生的分数大于等于70 Print “C” else if 学生的分数大于等于60 Print “D” else Print “E”选择结构注意这里隐含的情况: 分数小于90182
  • 183. 嵌套的选择结构嵌套的if-else结构 举例选择结构if (grade>=90) printf(“A”); else if (grade>=80) printf(“B”); else if (grade>=80) printf(“C”); else if (grade>=60) printf(“D”); else printf(“E”);if (grade>=90) printf(“A”); else if (grade>=80) printf(“B”); else if (grade>=80) printf(“C”); else if (grade>=60) printf(“D”); else printf(“E”); 清楚183
  • 184. 嵌套的选择结构举例:嵌套的if-else结构 读下面的程序,说出其功能。选择结构if (a>0) if (b>0) printf(“%d,%d”, a, b);if (a>0) if (b>0) printf(“%d,%d”, a, b); else printf(“%d”, a); else if (b>0) printf(“%d”, b);如果a和b的值都是正数,则输出a和b的值。输出a和b中的正数。if语句之间可以嵌套,不能交叉。 else总是与在它前面最接近的if匹配。184
  • 185. 选择结构中的复合语句选择结构的if子句和else子句可以是复合语句 举例选择结构if (grade>=60) printf(“Passed”); else { printf(“Failed\n”); printf(“You must take the course again!”); }不需要分号了一条复合语句就是一条语句,块语句。185
  • 186. 选择结构中的复合语句举例 读下面的程序,写出结果。选择结构void main() { int a=2, b=1, c=2; if (a) if (b<0) c = 0; else c++; printf(“%d”, c); }3void main() { int a=2, b=1, c=2; if (a) { if (b<0) c = 0; } else c++; printf(“%d”, c); }2186
  • 187. 关系运算的不足关系表达式只能表达一些简单的条件 grade>=60 total>1000 每个判断只是对一个条件进行测试。 如果要对多个条件进行测试就要在独立的语句或嵌套的if或if-else结构中进行测试。 例1:10>y>5 例2:x<-10或者x>0选择结构if (y>5) if (y<10) …;if (x<-10) …; if (x>0) …;187
  • 188. 逻辑运算逻辑运算 通过逻辑运算符把简单的条件组合起来,能够形成更加复杂的条件。 例1:10>y>5 的逻辑表达式 (y>5)&&(y<10) 例2:x<-10或者x>0 的逻辑表达式 (x<-10)||(x>0) 逻辑运算符 !(逻辑非) &&(逻辑与) ||(逻辑或)选择结构188
  • 189. 逻辑表达式逻辑表达式 [<表达式1>] <逻辑运算符> <表达式2> 表达式1和表达式2可以是任何表达式。 举例选择结构(x>5)||(x<-5) !(ac)&&(a+c>b)&&(b+c>a)逻辑表达式的值 1:真 0:假 整数189
  • 190. 逻辑运算逻辑运算真值表 选择结构a!a1001aba&&b111100010000aba||b111101011000!&&||190
  • 191. 逻辑运算符与其他运算符常用的运算符优先级小结 选择结构算术运算关系运算逻辑运算与、或赋值运算逻辑非从高到低191
  • 192. 逻辑表达式的使用将下面的条件用C语言的逻辑表达式表示 例1:1≤x≤10且x≠7 x>=1&&x<=10&&x!=7 例2:y能被4整除,但不能被100整除 (y%4==0)&&(y%100!=0) 逻辑与和逻辑非具有短路能力 例1:gender==‘F’&&age>=65 如果gender不等于’F’,整个表达式的值就是假,求值过程结束。 例2:average>=90||finalExam>=90 如果average大于等于90,整个表达式的值就是真,求值过程结束。选择结构192
  • 193. 程序设计举例举例(cw0602.c) 问题:任意输入一个字符,判断它是字母还是数字。选择结构#include void main() { char c; c=getchar(); if ((c>=‘A’&&c<=‘Z’)||(c>=‘a’&&c<=‘z’)) printf(“%c is a letter.”, c); else if (c>=‘0’&&c<=‘9’) printf(“%c is a digit.”, c); else printf(“%c is neither a letter nor a digit.”, c); } 193
  • 194. switch多重选择结构switch 在一个表达式可能的一组固定数量的值中进行选择,并采取不同的动作。选择结构switch(<条件表达式>) { case <常量表达式_1>: <动作_1>; [break;] case <常量表达式_2>: <动作_2>; [break;] …… case <常量表达式_n>: <动作_n>; [break;] [default: <缺省的动作>; [break;]] }条件表达式:整型/字符型表达式(或枚举表达式) 常量表达式:整型/字符型常量表达式194
  • 195. switch语句举例 根据考试成绩的等级打印相应的分数段。选择结构… grade=getchar(); switch(grade) { case ‘A’: printf(“85~100\n”); case ‘B’: printf(“70~84\n”); case ‘C’: printf(“60~69\n”); case ‘D’: printf(“<60\n”); default: printf(“error\n”); } …B 70~84 60~69 <60 error?195
  • 196. switch语句举例 结果分析 流程图选择结构print “60~69”print “70~84”print “85~100”print “<60”print “error”‘A’‘B’‘C’‘D’defaultgrade196
  • 197. switch语句举例 正确的流程图选择结构print “60~69”print “70~84”print “85~100”print “<60”print “error”‘A’‘B’‘C’‘D’defaultgrade197
  • 198. … grade=getchar(); switch(grade) { case ‘A’: printf(“85~100\n”); break; case ‘B’: printf(“70~84\n”); break; case ‘C’: printf(“60~69\n”); break; case ‘D’: printf(“<60\n”); break; default: printf(“error\n”); } …switch语句中的break举例 得到正确结果选择结构B 70~84break语句 跳转至switch语句之后的第一条语句OK198
  • 199. 程序设计举例举例 问题:任意输入两个数,和一个运算符,输出运算结果。选择结构print op1*op2print op1-op2print op1+op2print op1/op2print “error”‘+’‘-’‘*’‘/’defaultoperator199
  • 200. 程序设计举例举例 源代码(cw0604a.c)选择结构#include void main() { int op1, op2, oper; scanf("%d%d", &op1, &op2); oper = getchar(); switch(oper) { case ‘+’: printf(“=%d”, op1+op2); break; case ‘-’: printf(“=%d”, op1-op2); break; case ‘*’: printf(“=%d”, op1*op2); break; case ‘/’: printf(“=%d”, op1/op2); break; default: printf(“operator is invalid!”); } }200
  • 201. 程序设计举例举例:改进代码(cw0604b.c) 选择结构#include void main() { int op1, op2, oper, result, done=1; scanf("%d%d", &op1, &op2); oper=getchar(); switch(oper) { case ‘+’: result=op1+op2; break; case ‘-’: result=op1-op2; break; case ‘*’: result=op1*op2; break; case ‘/’: result=op1/op2; break; default: done=0; } if (done) printf(“%d%c%d=%d”, op1, oper, op2, result); else printf(“operator is invalid!”); }标记变量(flag)201
  • 202. 程序设计举例举例(cw0606.c) 问题:根据输入的月份,给出当月的天数。选择结构#include void main() { int m, d; scanf("%d", &m); switch (m) { case 1: case 3: case 5: case 7: case 8: case 10: case 12: d=31; break; case 4: case 6: case 9: case 11: d=30; break; case 2: d=28; break; default: d=0; } d>0 ? printf("%d", d) : printf("invalid input!"); }202
  • 203. 小结构造选择结构的流程控制语句 if if-else switch 智能的一个体现方面是根据环境调节反应的能力,所以构造选择结构的语句是开发具有智能行为程序的基础。选择结构203
  • 204. 小结判断条件 if和if-else语句使用一个判断条件来决定执行哪条语句。 任何非零值被视为“真”,零值被视为“假”。 判断包括关系表达式和逻辑表达式。 如果要判断两个条件,应该使用逻辑运算符将两个完整的判断表达式连接起来。 例如,if (1010 && x<20) …;选择结构204
  • 205. 小结switch switch语句实现了从一系列以整数值作为标签的语句中进行选择。 如果紧跟在switch关键字后的判断条件的整数值与某标签相匹配,程序控制就定位到由该标签定位的语句。 然后程序控制继续完成紧跟在该标签语句后的语句,直到遇到一个break语句。 break语句 跳转语句。 导致程序跳转到紧跟在包含它的switch语句末尾的下一条语句。选择结构205
  • 206. 第八讲 循环结构
  • 207. 第八讲 循环结构while语句 do-while语句 for语句 跳转语句 break语句 continue语句 goto语句 程序设计举例C语言程序设计207
  • 208. while循环while循环 当某个条件一直为真时重复执行某个动作。 举例:购物 … while 购物单上还有商品 购买下一件商品,并且把它从清单上划掉 … 如果条件“购物单上还有商品”成立时,就执行动作“购买下一件商品,并且把它从清单上划掉”; 如果该条件一直成立,这个动作就会重复执行; 最终(该买的商品都买到),即该条件不成立,循环过程就会终止,程序将执行这个循环结构之后的第一条语句。循环结构208
  • 209. While循环结构while语句 while (<条件表达式>) <循环体>; C语言代码举例:循环结构int product=2; while (product<=10) product=2*product;product<=10product=2*productYN可以是两种语句 单语句 复合语句while 循环结构是单入/单出结构209
  • 210. 计数器控制的循环计数器控制的循环 循环重复执行,直到计数器达到一个特定的值。 确定循环,即循环执行的次数是确定的。 举例 问题 1个班有10个学生,参加了一次考试,成绩(0~100内的整数)已知。计算这次考试的班级平均成绩。 分析与设计 输入每个学生的成绩,累计总成绩,计算平均分,显示结果。 采用计数器控制的循环,重复执行操作: 输入一个学生的成绩。 就进行累加。循环结构210
  • 211. 计数器控制的循环设计方法:自上而下,逐步求精 算法的顶部(描述程序整体功能) 计算本次考试的班级平均分 划分成一系列较小的任务 初始化变量 输入考试的分数,求分数总和,并计数(统计分数个数) 计算出最后结果并显示班级平均分循环结构211
  • 212. 计数器控制的循环进一步细化算法 初始化 输入考试的分数,求分数总和,并计数 计算出最后结果并显示班级平均分循环结构设置总分(total)为0 设置分数计数器(counter)为0While 分数计数器 < 10 输入下一个分数(grade) 把该分数加到总分上 分数计数器增1班级平均分(average)为总分除以10 显示班级平均分212
  • 213. 计数器控制的循环源代码分析(cw0701.c) 循环结构#include void main() { int counter, grade, total, average; total=0; counter=0; while (counter < 10) { printf("Enter grade:"); scanf("%d", &grade); total+=grade; counter++; } average=total/10; printf("Class average is %d\n", average); }初始化 (initialization) 执行循环 (execute loop) 输入 累加 结束 (termination) 最后的计算 输出结果213
  • 214. 计数器控制的循环运行(cw0701.c) 一次运行结果循环结构Enter grade:60 Enter grade:70 Enter grade:65 Enter grade:76 Enter grade:73 Enter grade:56 Enter grade:78 Enter grade:64 Enter grade:89 Enter grade:76 Class average is 70214
  • 215. 标记控制的循环举例 问题:编写一个班级平均分计算程序,每次执行该程序,都能够处理任意数量的学生分数。 思考:学生人数未知,何时结束输入? 标记控制的循环 使用标记(flag)指定“数据输入的结束” 即当用户输入标记值时,循环结束 其他名称:信号值(signal value),哨兵(sentinel) 标记值不应与正常的用户输入混淆 本例可以采用 -1 但同时标记值也是合法的输入 不确定的循环,即循环次数未知。循环结构215
  • 216. 标记控制的循环算法设计 算法的顶部 计算本次考试的班级平均分 细化 初始化变量 输入考试的分数,求分数总和,并计数(统计分数个数) 计算并显示班级平均分循环结构216
  • 217. 标记控制的循环进一步细化 初始化变量 输入考试的分数,求分数总和,并计数(计算分数个数)循环结构设置总分(total)为0 设置计数器(counter)(分数的个数)为0输入第一个分数(grade)(可能是标记值) While 输入的分数不是标记值 把当前分数加到总分中 计数器加1 输入下一个分数(可能是标记值)217
  • 218. 标记控制的循环进一步细化(续) 计算并显示班级平均分循环结构被 0 除 “致命”错误 会导致程序“崩溃”If 计数器不等于0 总分除以计数器的结果就是班级平均分 显示班级平均分 Else 显示“没有输入成绩”218
  • 219. 标记控制的循环源代码分析(cw0702.c) 循环结构#include void main() { float average; int counter, grade, total; total = 0; counter = 0; printf("Enter grade, -1 to end:"); scanf("%d", &grade); while (grade != -1) { total += grade; counter++; printf("Enter grade, -1 to end:"); scanf("%d", &grade); }初始化处理结束判断219
  • 220. 标记控制的循环源代码分析(续) 循环结构 if (counter != 0) { average = (float)total / counter; printf("Class average is %.2f\n", average); } else printf("No grades were entered\n"); }Enter grade, -1 to end:70 Enter grade, -1 to end:60 Enter grade, -1 to end:65 Enter grade, -1 to end:-1 Class average is 65.00结束220
  • 221. 嵌套的控制结构举例 现有10个学生期末考试结果的清单。 用1表示通过,2表示没有通过。 编写一程序对结果进行统计分析。 如果有8个以上的学生通过,显示“优秀班级”。 分析 程序每次执行处理10个考试结果。 采用计数器控制的循环。 使用两个统计计数器。 一个统计通过的人数,另一个统计没通过的人数。 每一个考试结果要么是1,要么是2。 只对1进行精确测试,不是1的其他数就假定为2。循环结构221
  • 222. 嵌套的控制结构设计 算法的顶部 分析考试结果,判断是否评为优秀班级 细化 初始化变量 输入10个考试结果,并计算通过考试和未通过考试的学生数目 显示考试结果的汇总,并判断是否应该评为优秀班级 进一步细化 初始化变量循环结构通过数初始化为0 未通过数初始化为0 循环控制计数器初始化为0222
  • 223. 嵌套的控制结构设计 进一步细化(续) 输入10个考试结果,并计算通过考试和未通过考试的学生数目循环结构While 循环控制计数器的值 < 10 输入一个考试结果 if 考试结果为1 通过计数器加1 else 未通过计数器加1 循环控制计数器加1223
  • 224. 嵌套的控制结构设计 进一步细化(续) 显示考试结果的汇总,并判断是否应该评为优秀班级循环结构显示通过考试的学生数目 显示未通过考试的学生数目 If 通过考试的学生数目大于8 显示“优秀班级”224
  • 225. 嵌套的控制结构源代码分析(cw0703.c) 循环结构#include void main() { int passed=0, failed=0, counter=0, result; while (counter<10) { printf("Enter result (1=pass, 2=fail):"); scanf("%d", &result); if (result==1) passed++; else failed++; counter++; }225
  • 226. 嵌套的控制结构源代码分析(续) 循环结构 printf("Passed %d\n", passed); printf("Failed %d\n", failed); if (passed>8) printf("Excellent Class\n"); }Enter result (1=pass, 2=fail):1 Enter result (1=pass, 2=fail):1 Enter result (1=pass, 2=fail):1 Enter result (1=pass, 2=fail):2 Enter result (1=pass, 2=fail):1 Enter result (1=pass, 2=fail):1 Enter result (1=pass, 2=fail):1 Enter result (1=pass, 2=fail):1 Enter result (1=pass, 2=fail):1 Enter result (1=pass, 2=fail):1 Passed 9 Failed 1 Excellent Class226
  • 227. do-while循环结构do-while 语句 do <循环体>; while (<条件表达式>);循环结构do-while 循环结构是单入/单出结构<条件表达式><循环体>YN先执行循环体语句,再判断循环条件227
  • 228. do-while语句举例(cw0704.c) 问题:计算1+2+…+100循环结构#include void main() { int s, i; s=0; i=1; do { s = s + i; i++; } while (i<=100); printf("1+2+...+100=%d\n", s); }既是循环控制计数器,也是加数228
  • 229. do-while语句比较do-while语句与while语句(cw0705.c) 循环结构main() { int s=0, n; scanf(“%d”, &n); while (n<=2) { s += n; n++; } printf(“s=%d,n=%d”, s, n); }main() { int s=0, n; scanf(“%d”, &n); do { s += n; n++; } while (n<=2); printf(“s=%d,n=%d”, s, n); }1 s=3,n=33 s=0,n=31 s=3,n=33 s=3,n=412do-while的循环体至少被执行一次229
  • 230. for循环结构for语句 for (<表达式1>; <表达式2>; <表达式3>) <循环体>;循环结构分号作为表达式的分隔符<表达式2><循环体>YN<表达式3><表达式1>只计算一次230
  • 231. for语句for语句能够自动处理计数器控制的循环的细节 举例:打印 1~10。(cw0706.c)循环结构#include void main() { int counter; for (counter = 1; counter <= 10; counter++) printf("%d ", counter); }循环条件控制变量增1对控制变量进行初始化1 2 3 4 5 6 7 8 9 10231
  • 232. for语句for 语句使用说明 for ([<表达式1>]; [<表达式2>]; [<表达式3>]) <循环体>; 说明: 三个表达式都是可选的(都可以为空),但分号不能少 如果表达式2为空,那么就假定该循环条件为真 则创建一个无限循环循环结构for (i=0;;i++) printf(“%2d”,i);for (i=0;1;i++) printf(“%2d”,i);=< Ctrl + C > 强行终止无限循环,结束程序的执行232
  • 233. for语句for 语句使用说明 for ([<表达式1>]; [<表达式2>]; [<表达式3>]) <循环体>; 说明: 表达式1和表达式3可以是任何合法的表达式 常用逗号表达式循环结构for (s=0,i=1;i<=100;i++) s+=i;for (s=0,i=1;i<=100;s+=i,i++); 对多个变量初始化修改多个变量的值233
  • 234. break语句break语句 break; 当在while、do-while、for或switch结构中执行break语句时,break语句会造成程序从该结构中退出,程序接着执行该结构之后的第一条语句。 注意: 执行break语句后,for语句的表达式3不被执行 常规应用: 提前从循环结构中退出 跳过switch结构的剩余部分循环结构234
  • 235. break语句举例(cw0707.c) 循环结构#include void main() { int x; for (x = 1; x <= 10; x++) { if (x == 5) break; printf("%d ", x); } printf(“\n当 x == %d 时跳出循环。\n", x); }1 2 3 4 当 x == 5 时跳出循环。与if配合使用235
  • 236. break语句在switch和for语句中的使用 循环结构for (i=1;i<=3;i++) { switch(i) { case 1: printf(“*\n”); break; case 2: printf(“**\n”); break; case 3: printf(“***\n”); break; } }* ** ***for (i=1;i<=3;i++) { if (i==1) {printf(“*\n”); break;} if (i==2) {printf(“**\n”); break;} if (i==3) {printf(“***\n”); break;} } * 236
  • 237. continue语句continue 语句 continue; 当在while、do-while或for结构中执行continue语句时,continue语句能够跳过该结构中剩余语句,执行下一个循环过程。 注意: 在while和do-while结构中 continue语句被执行之后,立即进行循环条件的测试 在for结构中 表达式3被执行之后,然后进行循环条件的测试循环结构237
  • 238. continue语句continue 语句举例(cw0708.c) 循环结构#include void main() { int x; x = 1; while (x <= 10) { if (x == 5) continue; printf("%d ", x); x++; } }1 2 3 4#include void main() { int x; for (x = 1; x <= 10; x++) { if (x == 5) continue; printf("%d ", x); } }1 2 3 4 6 7 8 9 10死循环238
  • 239. goto语句goto语句 goto <语句标号>; 立即执行语句标号指向的语句。 合理的使用: 与if语句一起构成循环结构 从循环体内跳到循环体外 特别是从多层嵌套循环的内部跳到外层循环,或者直接跳出循环结构语句标号 是一个标识符 放在可执行语句前面结构化编程技术限制使用 goto 语句 滥用goto语句使得程序无结构可言,可读性差,调试和维护困难。239
  • 240. goto语句goto语句举例(cw0709.c) 循环结构#include void main() { int s=0, i=1; Loop: s = s + i; i++; if (i<=100) goto Loop; printf("1+2+...+100=%d\n", s); }1+2+…+100=5050语句标号240
  • 241. C语言的控制结构C语言的控制结构(一) 循环结构顺序结构TFTFTFbreakTFbreakTFbreak选择结构switchifif-else241
  • 242. C语言的控制结构C语言的控制结构(二) 循环结构TF循环结构whileTFdo-whileforTF242
  • 243. 结构化程序设计的规则规则1和2 循环结构规则 1:从一个最简单的流程图开始。规则 2:任何一个矩形框都可以被两个顺序相连的矩形框替换。规则 2规则 2规则 2243
  • 244. 结构化程序设计的规则规则3:任何一个矩形框都可以被任何控制结构替换。 循环结构Rule 3Rule 3Rule 3244
  • 245. 非结构化程序非结构化的程序 循环结构satckingnestingoverlapinggoto245
  • 246. 结构化程序设计小结任何程序都是由3种结构组成的。 三种结构: 顺序结构 选择结构 循环结构 两种组合方式: 堆叠 嵌套 任何选择结构可以用if语句重写。 任何循环结构可以用while语句重写。 循环结构246
  • 247. 程序设计举例问题 求下面公式的前n项之和 分析 累加求和: s = s + ai 关键在于写出 ai 的表达式 用计数器控制的循环实现循环结构初始化累加和变量 s 为0 初始化计数器变量 i 为1 输入要累加的项数,放入变量 n While i <= n 计算第i项的值,结果放入ai 累加:s = s + ai 修改计数器变量:i++ 输出结果 s不确定量的数据来源: 输入获得 计算获得n247
  • 248. 程序设计举例第一种实现的源代码分析(cw0710A.c) 循环结构#include #include void main() { int i=1, n; float s=0, ai; scanf(“%d”, &n); while (i<=n) { ai=pow(-1,i-1)/(2*i-1); s+=ai; i++; } printf(“sum=%.3f\n”, s); }不划算1 sum=1.0002 sum=0.6675 sum=0.8350 sum=0.000仔细设计测试数据,检验程序正确性248
  • 249. 程序设计举例第二种实现的源代码分析(cw0710B.c) 循环结构#include void main() { int i=1, n, sign=1; float s=0, ai; scanf(“%d”, &n); while (i<=n) { ai=1.0/(2*i-1); s+=sign*ai; sign*=-1; i++; } printf(“sum=%.3f\n”, s); }1 sum=1.0002 sum=0.6675 sum=0.8350 sum=0.000现在ai表示每一项的绝对值249
  • 250. 程序设计举例问题 用下面的公式求π的近似值(直到最后一项的绝对值小于10-6为止): 分析 先计算等式右边的和 累加求和: s = s + ai 当|ai|<10-6时,停止累加 用标记控制的循环实现循环结构初始化累加和变量 s 为0 初始化计数器变量 i 为1 第1项的值 ai = 1 While |ai| >= 10-6 累加:s = s + ai i增一 计算第i项(下一项)的值,结果放入ai 计算π=s*4 输出结果 π250
  • 251. 程序设计举例第一种实现的源代码分析(cw0711A.c) 循环结构#include void main() { int i=1, n, sign=1; double s=0, ai; ai=1; while (!(ai<1e-6)) { s+=sign*ai; i++; sign*=-1; ai=1.0/(2*i-1); } printf("pi=%.8lf\n", s*4); }pi=3.14153162ai 保存的是第i项的绝对值251
  • 252. 程序设计举例第二种实现的源代码分析(cw0711B.c) 循环结构#include void main() { int i=1, n, sign=1; double s=0, an; for (;;) { an=1.0/(2*i-1); if (an<1e-6) break; s+=sign*an; sign*=-1; i++; } printf(“pi=%.8lf\n", s*4); }pi=3.14153162TFTF使用 break 语句违背了结构化程序设计的原则 continue语句也有类似的问题252
  • 253. 程序设计举例问题 任意输入10个数,找出最大数和最小数 分析 数的范围无法确定 把输入的第一个数作为最大数和最小数 将其余的数与最大数、最小数分别比较 每次根据比较的结果更新最大数和最小数 用计数器控制的循环实现循环结构372175482975461118493737最大数最小数971253
  • 254. 程序设计举例源代码分析(cw0712.c) 循环结构#include void main() { int i, d, max, min; printf("Input 10 integers:\n"); scanf("%d", &d); max=min=d; for (i=2;i<=10;i++) { scanf("%d", &d); if (d>max) {max=d; continue;} if (d
  • 255. 程序设计举例问题 判断整数 m 是否素数 源代码分析(cw0713.c)循环结构#include #include void main() { int m, k, i; scanf("%d", &m); k = m-1; for (i=2;i<=k;i++) if (m%i==0) break; if (i>k) printf("%d is a prime number.\n", m); else printf("%d is not a prime number.\n", m); }如果break被执行了,则该数不是素数,那么i<=k255
  • 256. 程序设计举例问题 找出1~100之间的全部素数 源代码分析(cw0714.c)循环结构#include #include void main() { int m, k, i; for (m=1;m<=100;m++) { k = m-1; for (i=2;i<=k;i++) if (m%i==0) break; if (i>k) printf("%4d", m); } }m是否是素数嵌套for(m=1;m<=100;m++)256
  • 257. 程序设计举例问题 输出图形 分析 共有10行 第n行有n个星号 用计数器控制的循环实现循环结构* ** *** **** ***** ****** ******* ******** ********* **********257
  • 258. 程序设计举例源代码分析(cw0715.c) 循环结构#include void main() { int m, n; for (n=1;n<=10;n++) { for (m=1;m<=n;m++) printf("*"); printf("\n"); } }* ** *** **** ***** ****** ******* ******** ********* **********258
  • 259. 程序设计举例问题 输出图形 分析 共有10行 第n行先输出n-1个空格,再输出10-(n-1)个星号 用计数器控制的循环实现循环结构********** ********* ******** ******* ****** ***** **** *** ** *259
  • 260. 程序设计举例源代码分析(cw0716.c) 循环结构#include void main() { int m, n; for (n=1;n<=10;n++) { for (m=1;m<=n-1;m++) printf(" "); for (;m<=10;m++) printf("*"); printf("\n"); } }********** ********* ******** ******* ****** ***** **** *** ** *260
  • 261. 程序设计举例问题 输出下面的图形循环结构 * *** ***** ******* ********* * *** ***** ******* ********* ******* ***** *** *图1图2261
  • 262. 程序设计举例源代码分析 图1 (cw0717.c)循环结构#include void main() { int m, n; for (n=1;n<=5;n++) { for (m=1;m<=5-n;m++) printf(" "); for (m=1;m<=2*n-1;m++) printf("*"); printf("\n"); } }262
  • 263. 程序设计举例源代码分析 图2 (cw0718.c)循环结构#include void main() { int m, n; for (n=1;n<=5;n++) { for (m=1;m<=5-n;m++) printf(" "); for (m=1;m<=2*n-1;m++) printf("*"); printf("\n"); } for (n=4;n>=1;n--) { for (m=1;m<=5-n;m++) printf(" "); for (m=1;m<=2*n-1;m++) printf("*"); printf("\n"); } }263
  • 264. 程序设计举例问题 用二分法求下面方程在区间[-10,10]上的根: 2x3-4x2+3x-6=0 二分法循环结构f(x)=2x3-4x2+3x-6x1x2f(x1)f(x2)x0x0f(x0)x0=(x1+x2)/2近似计算264
  • 265. 程序设计举例二分法 确定有根的区间 指定一个区间[x1,x2],如果函数f(x)在此区间是单调变化的,则可以根据f(x1)和f(x2)是否同号来确定方程f(x)=0在区间[x1,x2]内是否有一个实根。 若f(x1)和f(x2)不同号,则f(x)=0在区间[x1,x2]内有一个(且只有一个)实根。 如果f(x1)和f(x2)同号,则f(x)=0在区间[x1,x2]内没有实根,要重新改变x1和x2的值。 把区间一分为二 当确定f(x)=0在区间[x1,x2]内有实根后,可采用二分法将[x1,x2]区间一分为二,再判断在哪一个小区间中有实根。 循环 如此不断进行下去,直到小区间足够小为止,即该区间的中点是f(x)=0的近似根。循环结构265
  • 266. 程序设计举例算法设计 输入并确定有根的初始区间 输入两个数放入 x1 和 x2 ,表示区间[x1 x2] 判断区间内是否有根 若无根,则重复上面两步,直到得到合法的区间 二分法求近似根 计算当前近似根 x0 = (x1+x2)/2 把当前区间一分为二 [x1 x0]和[x0 x2] 判断根在哪一个小区间 更新当前区间[x1 x2] 判断当前近似根是否达到精度要求 否则,重复上述步骤,直到满足要求 输出近似根循环结构266
  • 267. 程序设计举例算法设计(细化) 输入并确定有根的初始区间 输入两个数放入 x1 和 x2 ,表示区间[x1 x2] 计算 fx1 和 fx2 判断区间内是否有根,即 fx1 和 fx2 是否异号 若同号,则重复上面步骤,直到得到合法的区间 二分法求近似根 计算当前近似根 x0 = (x1+x2)/2 计算 fx0 把当前区间一分为二 [x1 x0]和[x0 x2] 判断根在哪一个小区间 更新当前区间[x1 x2],和相应的 fx# 判断当前近似根是否达到精度要求 否则,重复上述步骤,直到满足要求 输出近似根循环结构 do-while do-whileif-elseprocessoutputinput267
  • 268. 程序设计举例源代码分析(cw0719.c) 循环结构#include void main() { float x0,x1,x2,fx0,fx1,fx2; do { printf("Input x1 , x2:"); scanf("%f,%f", &x1, &x2); fx1 = x1*(x1*(2*x1-4)+3)-6; fx2 = x2*(x2*(2*x2-4)+3)-6; } while (fx1*fx2>0); do-while do-whileif-elseprocessoutputinput268
  • 269. 程序设计举例源代码分析 循环结构 do { x0=(x1+x2)/2; fx0=x0*(x0*(2*x0-4)+3)-6; if (fx0*fx1<0) { x2=x0; fx2=fx0; } else { x1=x0; fx1=fx0; } } while (fabs(fx0)>=1e-5); printf("x=%.2f", x0); }x1 x0 x2x1 x0 x2 do-while do-whileif-elseprocessoutputinput269
  • 270. 小结建立循环时特别注意三个方面: 明确定义结束循环的条件 确保在循环判断中使用的值在第一次使用之前已经初始化 确保循环在每个周期中更新了判断值 构造循环结构的流程控制语句 while do-while for 其中, while和for提供入口条件循环 do-while提供退出条件循环循环结构270
  • 271. 小结跳转语句 break语句 continue语句 goto语句 结构化程序设计的规则循环结构271
  • 272. 第九讲 函数
  • 273. 第九讲 函数程序的模块化 函数 定义 原型 调用 参数传递 函数的嵌套调用 递归函数 程序设计举例C语言程序设计273
  • 274. 简介分而治之与程序的模块化 把一个规模较大的问题分解成若干个较小的相对独立的部分,对每一个部分使用一个较小的程序段,即程序模块(module)来处理。 从较小的程序段或组件来构建程序。 这些小片段或组件比原始程序更容易实现和管理。 这些小组件可以被重复使用。函数274
  • 275. C语言的函数在C语言中,函数(function)是构成程序的基本模块。 一个C程序由一个或多个函数组成,有且仅有一个主函数,即main()函数。 每个函数完成一个相对独立的且功能明确的任务。 由主函数调用其他函数,其他函数也可以互相调用。 同一个函数可以被一个或多个函数调用任意多次。函数mainfun_afun_bfun_cfun_cfun_dfun_efun_ffun_d275
  • 276. C语言的函数C语言的函数有两大类: 标准库函数 提供了丰富的函数。 例如 数学计算:sqrt(),abs() 输入/输出:scanf(),printf() 自定义函数 程序员可以编写函数来完成特定的任务。 应该熟悉C系统中的标准函数库。 应该避免从零开始构建一切。函数276
  • 277. 为什么使用函数函数使程序模块化。 程序采用模块化结构的好处: 分而治之 提高程序开发的效率。 使程序易于管理。 代码重用 使用现有的函数作为构件来创建程序。 函数可以被重复使用。 抽象 隐藏了实现的细节。 例如 使用库函数(printf()),但并不知道它的具体实现(没有影响使用)。函数277
  • 278. 案例分析:一个简单的函数编写和使用一个简单的函数(cw0801.c) 定义一个函数square,用来计算任意整数的平方。 然后,使用该函数计算从1到10所有整数的平方。函数#include int square(int); void main() { int x; for (x=1; x<=10; x++) printf("%d ", square(x)); } int square(int y) { return y*y; }声明函数使用函数定义函数1 4 9 16 25 36 49 64 81 100278
  • 279. 函数的定义定义函数的格式 <函数类型> <函数名>(<参数表>) { <函数体语句> } 函数名:一个有效的标识符。 函数类型:返回值的类型说明符。 如果不指定,即缺省,就是 int。 void :表示函数不返回任何值。 参数表:声明参数,多个参数用逗号分隔。 接收传递进来的数据。 必须为每个参数指定数据类型。 但 int 可以省略。函数函数头函数体279
  • 280. 函数的定义定义函数的格式 <函数类型> <函数名>(<参数表>) { <函数体> } 函数体:包括声明语句和可执行语句。 在函数体内可以声明变量。 不能定义函数,即函数的定义不允许嵌套。 控制返回:结束执行,把程序的控制交还主调函数,也可以用return返回一个数值。 } return; return <表达式>;函数无返回值有返回值280
  • 281. 案例分析:函数的定义函数的定义(cw0802.c) 定义函数找出三个数中的大数。函数#include int maximum(int, int, int); void main() { int a, b, c; printf(“Input three integers: "); scanf("%d%d%d", &a, &b, &c); printf("Maximum is: %d\n", maximum(a, b, c)); } int maximum(int x, int y, int z) { int max = x; if (y>max) max = y; if (z>max) max = z; return max; }maximumintintintint函数原型接口(interface)281
  • 282. 函数原型函数原型 <函数类型> <函数名>(<参数表>); 用来对函数进行声明。 编译器使用函数原型来检查函数调用的合法性。 注意:函数原型要与函数的定义一致。 例如 int maximum(int a, int b, int c); int maximum(int, int, int);函数282
  • 283. 函数原型函数原型在程序文件中的位置不同,作用范围不同。 在所有函数的外面 在函数内部函数main() { … } void funcA() { int funcB(int); … } int funcB(int) { … }283
  • 284. 函数原型如果程序中没有包含函数原型。 编译程序会使用该函数第一次出现的情形来形成自己的函数原型。 函数的定义 函数的调用 默认情况下,编译程序假定函数返回int型的结果,但不会对参数进行任何假定。 如果传递给函数的参数不正确,编译程序不会检查到这些错误。函数284
  • 285. 函数原型函数原型强迫参数采用正确的数据类型。 举例 printf(“%.3f”, sqrt(4) ); 函数原型使编译程序把整数值4转换为double型的值4.0 没有与函数原型中的参数类型完全对应的参数值会在调用函数之前被转换成合适的数据类型。 遵守C语言的提升规则。函数double sqrt(double);285
  • 286. 函数原型与头文件头文件 每个标准库函数都有对应的头文件。 包含了标准库中所有函数的函数原型, 以及那些函数所需的数据类型和常量的定义。 使用#include命令把头文件包含到程序文件中: #include <文件名> 例如,#include 程序员可以创建自己的头文件 使用.h扩展名。 使用下面的命令格式包含头文件: #include “文件名” 例如,#include “abc.h”函数286
  • 287. 函数调用函数调用 使用函数,也称为调用函数。函数main() { int a, b, c; scanf("%d%d", &a, &b); c = max(a, b); printf("the larger is %d", c); }126int max(int a, int b) { int c; c=a>=b?a:b; return c; }3451、…… 2、主调函数暂停,保存现场。 3、把实参的值拷贝给形参,控制权交给函数 max 。 4、……5、被调函数执行结束,把函数值返回给主调函数,同时把控制权还给主调函数。 6、恢复现场,主调函数继续执行。 ……287
  • 288. 参数传递函数间的数据传递方式: 参数 返回值函数int max(int a, int b) { …… return c; } main() { …… c=max(a, b); }max()abcmain()参数传递返回值传递调用288
  • 289. 实参和形参实参和形参 函数int max(int a, int b) { int c=a>=b?a:b; return c; } main() { int a, b, c; scanf(“%d%d”, &a, &b); c=max(a, b); }形式参数 简称“形参”。在函数定义时表示可以接受传递过来的值。实际参数 简称“实参”。在函数调用时给出。289
  • 290. 形参形参 函数int max(int a, int b) { int c=a>=b?a:b; return c; } main() { int a, b, c; scanf(“%d%d”, &a, &b); c=max(a, b); }只有在函数被调用、启动后,才临时为其分配存储单元,并接受主调函数传来的数据。 在函数调用结束后,形参所占存储单元被释放。290
  • 291. 实参实参 函数int max(int a, int b) { int c=a>=b?a:b; return c; } main() { int a, b, c; scanf(“%d%d”, &a, &b); c=max(a, b); }实参是函数调用时主调函数传送给被调函数的形式参数的实际值。 实参可以是常量、变量和表达式。 实参必须有确定的值。291
  • 292. 参数传递参数传递的顺序。 函数int max(int a, int b) { int c=a>=b?a:b; return c; } main() { int x=6, y; y=max(x, x++); printf(“%d”, y); }当实参表列中有多个实参时,对实参的求值顺序并不确定。 VC和BC是按从右往左的顺序求值。b=x++; a=x;7在参数传递时292
  • 293. 参数传递参数传递的影响。 函数int max(int a, int b) { int c=a>=b?a:b; a++; b++; return c; } main() { int a=6, b=5, c; c=max(a, b); printf(“%d,%d,%d”,a,b,c); }实参与形参不共用存储单元。 参数传递时,把实参的值复制一份给形参。 形参值的变化不影响实参的值。 所以,形参和实参可以同名。6,5,6max6a5b6a5bmain293
  • 294. 值传递和引用传递函数间参数的传递有两种类型: 值传递 主调函数把实参的值的副本传递给被调函数的形参。 在被调函数内改变形参的值不会改变主调函数中实参的值。 如果函数不需要修改参数的值,就采用这种调用方式。 引用传递 主调把实参“自身”传递给被调函数的形参。 在被调函数内改变形参的值将改变主调函数中实参的值。 用于可信的函数。 在C语言中,所有参数传递都采用值传递。函数294
  • 295. 参数传递实参和形参的类型应该相同或赋值兼容。 函数int max(int a, int b) { int c=a>=b?a:b; return c; } main() { int x=6, y=5, z; z=max(x, y); printf(“%d”, z); }如果x, y是整型,则结果正确; 如果x, y是字符型,则自动进行类型转换,结果正确; 如果x, y是短整型,则自动进行类型转换,结果正确; 如果x或y是实数,则自动进行类型转换,有数据丢失,结果可能不正确。b=y; a=x;在参数传递时295
  • 296. 函数的返回值函数返回值的类型应该与函数的类型一致。 如果不一致,采用函数的类型,对返回值进行类型转换。函数int max(float a, float b) { float c=a>=b?a:b; return c; } main() { float x=6.5, y=5.6, z; z = 2*max(x, y); printf(“%f”, z); }c 的类型? 返回值的类型?max(x,y) 的类型? 2*max(x,y) 的类型?296
  • 297. 函数的嵌套调用嵌套调用 在调用一个函数的过程中又调用另一个函数函数主函数调用函数 1函数 1调用函数 2函数 2123456789297
  • 298. 案例分析:函数的嵌套调用计算圆环的面积 分析 圆环的面积 = 外圆的面积 – 内圆的面积 可以定义两个函数 circleArea 计算圆的面积 ringArea 计算圆环的面积函数r1r2circleAreadoubledoubleringAreadoubledoubledoubledouble circleArea(double r); double ringArea(double r1, double r2);298
  • 299. 案例分析:函数的嵌套调用计算圆环的面积 源代码(cw0804.c) 函数#include #define PI 3.14 double circleArea(double r); double ringArea(double r1, double r2); void main() { double r1, r2, s; printf("\tInput r1, r2: "); scanf("%lf%lf", &r1, &r2); s = ringArea(r1, r2); printf("\n\tThe area is:%.2lf\n", s); }299
  • 300. 案例分析:函数的嵌套调用计算圆环的面积 源代码(续) 函数double circleArea(double r) { return PI*r*r; } double ringArea(double r1, double r2) { if (r1<=r2) return circleArea(r2)-circleArea(r1); else return circleArea(r1)-circleArea(r2); }Input r1, r2: 1 2 The area is: 9.42300
  • 301. 程序设计举例:掷骰子掷骰子 问题 每个玩家掷两个骰子。每个骰子都有6个面。这些面中包含了1点、2点、3点、4点、5点和6点。当骰子静止下来之后,计算两个朝上的面中的点数和(本次投掷的结果)。 如果第一次投掷的结果是7 或11,那么这个玩家就获胜。 如果第一次投掷的结果是2、3或12,那么这个玩家就输了(即庄家获胜)。 如果第一次投掷的结果是4、5、6、8、9或10,那么这个结果就是该玩家的“点数”。 为了获胜,玩家必须继续掷骰子,直到“掷出了点数”。在掷出点数之前,如果玩家掷出了7,那么玩家就输了。函数301
  • 302. 程序设计举例:掷骰子掷骰子 初始设计 定义一个函数 rollDice,用来模拟掷一次骰子 产生两个随机数(1..6),返回它们的和(点数)函数掷第一次胜输掷一次胜输?302
  • 303. 程序设计举例:掷骰子掷骰子 细化设计 定义一个变量保存游戏进展的状态 gamestatus 0:继续 1:胜利(游戏结束) 2:失败(游戏结束)函数否rollDice结束?rollDice结束?否是是赢或输303
  • 304. 程序设计举例:掷骰子掷骰子 实现(cw0807.c)函数#include #include #include int rollDice(void); void main() { int gameStatus, sum, myPoint; srand(time(NULL)); sum = rollDice();掷第一次304
  • 305. 程序设计举例:掷骰子掷骰子 实现函数 switch(sum) { case 7: case 11: gameStatus = 1; break; case 2: case 3: case 12: gameStatus = 2; break; default: gameStatus = 0; myPoint = sum; printf("Point is %d\n", myPoint); break; }掷第一次之后的结果305
  • 306. 程序设计举例:掷骰子掷骰子 实现函数 while (gameStatus == 0) { sum = rollDice(); if (sum == myPoint) gameStatus = 1; else if (sum == 7) gameStatus = 2; } if (gameStatus == 1) printf("You wins!"); else printf("You loses!"); }继续游戏胜负判断306
  • 307. 程序设计举例:掷骰子掷骰子 实现函数int rollDice() { int dice1, dice2, sum; dice1 = rand()%6 + 1; //第一个骰子的点数 dice2 = rand()%6 + 1; //第二个骰子的点数 sum = dice1 + dice2; printf("You rolled %d + %d = %d\n", dice1, dice2, sum); return sum; }307
  • 308. 递归函数递归函数 直接或间接调用自己的函数。函数函数 1调用函数 1函数 1调用函数 2函数 2调用函数 1308
  • 309. 案例分析:递归函数用递归方法计算n!。 分析 5! = 5 * 4 * 3 * 2 * 1 5! = 5 * 4! 4! = 4 * 3!... 递归公式函数n ! =1 (n=0或1)n*(n-1)! (n>1)基本情形简化问题s=1; n=1; while (n<=20) s = n * s;迭代n! = n * (n-1)! 309
  • 310. 案例分析:递归函数举例:用递归方法计算n!。 源代码(cw0805.c)函数#include long fac(int n) { long f; if (n==0||n==1) f=1; else f=n*fac(n-1); printf("\t%d!=%ld\n", n, f); return f; } void main( ) { printf("\n\t5!=%ld\n", fac(5)); }1!=1 2!=2 3!=6 4!=24 5!=120 5!=120递归调用310
  • 311. 案例分析:递归函数举例:用递归方法计算n!。 分析函数5!5*4!4*3!3*2!2*1!11205*244*63*22*11递归调用从递归调用返回值311
  • 312. 用递归方案解决问题小结用递归(函数)方案解决问题 递归函数只知道如何去解最简单的情形(基本情形) 简单的返回一个值 把复杂的问题分成两部分: 函数知道如何去做的部分 函数不知道如何去做的部分 这一部分与原问题相似,且更简单 函数可以调用自己的新形式来解决这个更小的问题(递归调用) 最终遇到基本情形 函数识别出基本情形,将结果返回给前一个情形 一系列的结果按顺序返回 直到把最终结果返回给原始的调用者(可能是main()函数)函数312
  • 313. 案例分析:递归方案使用递归方法计算斐波拉契数列。 0 1 1 2 3 5 8 … 分析 从第三个数开始,每个数是前两个数的和。 第一、二个数是确定的。 递归公式 fib(n) = fib(n-1) + fib(n-2) fib(1) = 0 fib(2) = 1函数313
  • 314. 递归与迭代递归与迭代的比较 循环 迭代:明确使用了循环结构 递归:重复调用递归函数 终止条件 迭代:循环条件不满足 递归:遇到基本情形 都有可能出现无限循环 如何选择 迭代:性能好 递归:可读性好函数314
  • 315. 程序设计举例:汉诺塔问题汉诺塔问题 问题 假设有三个分别命名为X,Y和Z的塔座,在塔座X上插有n个直径大小各不相同、依从小到大编号为1,2,…,n的圆盘。现要求将X轴上的n个圆盘移到塔座Z上,并按同样的顺序叠放。 移动时必须遵循以下规则: 每次只能移动一个圆盘; 圆盘可以插在X,Y和Z中的任一塔座上; 任何时候都不能将一个较大的圆盘压在较小的圆盘之上。函数XYZ315
  • 316. 程序设计举例:汉诺塔问题汉诺塔问题 分析 n=1时 将圆盘1从塔座X移到塔座Z。函数XYZXYZ基本情形316
  • 317. 程序设计举例:汉诺塔问题汉诺塔问题 分析 n>1时 函数1. 利用塔座Z为辅助塔座,将压在圆盘n之上的n-1个盘从塔座X移到塔座Y; (——与原问题类似) 2. 将圆盘n从塔座X移到塔座Z; 3. 利用塔座X为辅助塔座,将塔座Y上的n-1个圆盘移动到塔座Z。 (——与原问题类似)XYZXYZXYZXYZ123317
  • 318. 程序设计举例:汉诺塔问题汉诺塔问题 设计 move 函数:移动一个盘 把盘 n 从 s 塔移到 d 塔 hanoi 函数:移动n个盘的汉诺塔问题 把 n 个盘从 x 塔移到 z 塔,y 塔作为辅助塔函数void move(int n, char s, char d); void hanoi(int n, char x, char y, char z);318
  • 319. 程序设计举例:汉诺塔问题汉诺塔问题 实现(cw0806.c)函数#include void move(int n, char s, char d); void hanoi(int n, char x, char y, char z); void main() { int n; printf("\t Input the number of disks:"); scanf("%d", &n); hanoi(n, 'X', 'Y', 'Z'); }319
  • 320. 程序设计举例:汉诺塔问题汉诺塔问题 实现函数void hanoi(int n, char x, char y, char z) { if (n==1) move(n, x, z); else { hanoi(n-1, x, z, y); move(n, x, z); hanoi(n-1, y, x, z); } } void move(int n, char s, char d) { printf("\t%d\t%c-->%c\n", n, s, d); }320
  • 321. 小结函数可以作为大型程序的组成模块。 每个函数应该实现某个明确的功能。 使用参数可以向函数传递数据,通过return让函数返回一个数据。 使用函数原型声明函数,以便编译器检查函数调用时所传递的参数个数及类型是否正确。 函数可以调用自身,这种调用方式称为递归。有些问题使用递归解决方案,但是递归会在内存使用和时间花费方面效率低下。函数321
  • 322. 第十讲 作用范围和存储类别
  • 323. 第十讲 作用范围和存储类别作用范围 存储类别 包含多个源文件的程序C语言程序设计323
  • 324. 简介变量定义的完整格式: <存储类别> <数据类型> <变量名> 数据类型 占据存储空间的大小 取值范围 存储类别 在内存中持续的时间(生存期) 在硬件中存放的位置 其它属性 作用范围 可以被引用的程序部分(可见性)作用范围和存储类别324
  • 325. 作用范围程序中的所有标识符构成了名字空间。 标识符的作用范围 能够引用该标识符的程序部分。 四种作用范围: 文件作用范围 函数作用范围 程序块作用范围 函数原型作用范围作用范围和存储类别325
  • 326. 作用范围文件作用范围 在函数外声明的标识符,可以在所有函数中被引用。 包括:全局变量,函数定义,函数原型。作用范围和存储类别int total; int max(int, int); void main() { … } int limit; int max(int x, int y) { … }作用范围 从声明的位置开始,到文件的末尾。326
  • 327. 作用范围函数作用范围 在函数体内定义的标识符,只能在函数体内被引用。 包括:语句标号作用范围和存储类别void main() { … loop: … goto loop; }327
  • 328. 作用范围程序块作用范围 在程序块内声明的变量,在程序块内被引用。 包括:程序块内的变量(局部变量),函数的参数。作用范围和存储类别int max(int x, int y) { … } void main() { int a; … { int a; … } … }作用范围 从声明的位置开始,到程序块的右大括号覆盖 同名变量,内部变量“覆盖了”外部变量328
  • 329. 作用范围函数原型作用范围 函数原型中的参数。作用范围和存储类别int max(int x, int y); void main() { … } int max(int x, int y) { … }329
  • 330. 关于声明和定义定义声明 定义一个标识符,同时也是声明。 对于变量,定义意味着创建,即为其分配内存。 引用声明 只是声明一个需要引用的标识符。作用范围和存储类别330
  • 331. 存储类别四种存储类别说明符 auto register extern static 两种存储时期 自动存储时期 auto register 静态存储时期 extern static作用范围和存储类别331
  • 332. 自动存储类别自动存储 程序执行到变量所在的程序块内时创建它,退出时销毁。 不会被自动初始化。 auto(自动变量):局部变量的缺省类别。 程序块中声明的变量,函数的参数。 register(寄存器变量):建议编译器把变量放进高速的寄存器。 只适用于自动变量。作用范围和存储类别auto int a, b;register int count = 1;332
  • 333. 静态存储类别静态存储 程序开始执行时创建,在程序执行期间,变量一直存在。 自动被初始化,缺省初值:0 或 ‘\0’。 static(静态变量):可以用于在程序块中定义的变量。 退出程序块后依然存在,并保留值。 但不改变其作用范围,即只能在所在的程序块内被使用。 extern(外部变量):用来声明在“外部”定义的全局变量。 仅用于声明变量。作用范围和存储类别static int a, b;extern int total;333
  • 334. 案例分析:存储类别存储类别 读下面的程序,写出结果。(cw0901.c)作用范围和存储类别void try(int a) { auto int b=10; static int c=10; b++; c++; printf("a=%d\tb=%d\tc=%d\n", a, b, c); } void main() { int i; for (i=1;i<=3;i++) try(i); }a=1 b=11 c=11 a=2 b=11 c=12 a=3 b=11 c=13334
  • 335. 案例分析:存储类别存储类别 结果分析作用范围和存储类别cababab123动态存储区静态存储区FF00220822062206220422042202a=1 b=11 c=11 a=2 b=11 c=12 a=3 b=11 c=13335
  • 336. C语言程序的内存映像C语言程序的内存映像作用范围和存储类别包含了代码中的字面值常量。存放全局变量和标明为静态类的局部变量。栈:保存函数调用时的返回地址、函数的形参、局部变量,以及CPU的当前状态。 堆:自由内存区域。程序代码静态存储变量堆栈程序可以访问的内存区域。数据段代码段动态存储区用户区静态存储区336
  • 337. 案例分析:外部变量外部变量(cw0902.c)作用范围和存储类别#include void main() { extern int a; int c=3; printf("a = %d\n", a); { extern int c; c = 4; printf("inner c = %d\n", c); } printf("c = %d\n", c); } int a=1; int c=5;a = 1 inner c = 4 c = 3337
  • 338. 案例分析:作用范围与存储类别作用范围与存储类别(cw0903.c)作用范围和存储类别#include void a(void); /*function prototype*/ void b(void); /*function prototype*/ void c(void); /*function prototype*/ int x = 1; /*global variable*/ void main() { int x = 5; printf("local x in outer scope of main is %d\n", x); { /*start new scope*/ int x = 7; printf("local x in inner scope of main is %d\n", x); } /*end new scope*/ printf("\nlocal x in outer scope of main is %d\n", x);338
  • 339. 案例分析:作用范围与存储类别作用范围与存储类别作用范围和存储类别 a(); b(); c(); a(); b(); c(); printf("local x in main is %d\n", x); } void a() { int x = 25; /*initialized each time a is called*/ printf("\nlocal x in a is %d after entering\n", x); x++; printf("local x in a is %d before exiting\n", x); }339
  • 340. 案例分析:作用范围与存储类别作用范围与存储类别作用范围和存储类别void b() { static int x = 50; /*static initialization only*/ /*first time b is called*/ printf("\nlocal x in b is %d after entering\n", x); x++; printf("local x in b is %d before exiting\n", x); } void c() { printf("\nglobal x is %d on entering c\n", x); x*=10; printf("global x is %d on exiting c\n", x); }340
  • 341. 案例分析:作用范围与存储类别作用范围与存储类别作用范围和存储类别local x in outer scope of main is 5 local x in inner scope of main is 7 local x in outer scope of main is 5 local x in a is 25 after entering a local x in a is 26 before exiting a local x in b is 50 after entering b local x in b is 51 before exiting b global x is 1 on entering c global x is 10 on exiting c341
  • 342. 案例分析:作用范围与存储类别作用范围与存储类别作用范围和存储类别local x in a is 25 after entering a local x in a is 26 before exiting a local x in b is 51 after entering b local x in b is 52 before exiting b global x is 10 on entering c global x is 100 on exiting c local x in main is 5342
  • 343. 包含多个源文件的程序包含多个源文件的程序 每个函数的定义必须在一个文件内,不能被分割。 全局变量可以被同一文件内的函数访问。 如果需要被其他文件内的函数访问,则必须在其他文件内声明。 extern 表示变量myGlobal是在另一个文件内定义的。 一个文件内定义的函数,也可以被其他文件内的函数调用。 在每个文件内加入该函数的原型(声明为外部函数)。 函数的原型可以不需要 extern。作用范围和存储类别int myGlobal;extern int myGlobal;a.cb.c343
  • 344. 包含多个源文件的程序包含多个源文件的程序 举例作用范围和存储类别int a, b; extern int max(void); void main() { scanf("%d%d", &a, &b); printf("%d", max()); }extern int a, b; int max() { return (a>b?a:b); } ABcw0904a.ccw0904b.c344
  • 345. 包含多个源文件的程序包含多个源文件的程序 static 限制全局变量只能被同一文件内的函数访问。 限制函数只能被同一文件内的函数调用。作用范围和存储类别static int myGlobal; static void myFunc() { … }345
  • 346. 编译包含多个源文件的程序编译包含多个源文件的程序 每个源文件必须被编译,然后链接成一个可执行文件。 如果有一个文件作了改动,则必须重新编译所有相关的文件。 一般会提供 make 工具用来管理和编译多源文件的程序。 创建 makefile 文件,记录编译规则。 自动查找必须编译的源文件。 可以创建工程(project)文件来管理多源文件的程序。作用范围和存储类别346
  • 347. 小结用于存储程序数据的内存可用存储时期和作用范围来表征。 存储时期可以是静态的或自动的。 如果是静态的,变量所用内存在程序开始执行时被分配,并在程序执行时一直存在。 如果是自动的,变量所用内存在程序执行到该变量定义所在的代码块时开始分配,在退出代码块时释放。 作用范围决定了哪一部分程序可以访问某个数据。 C语言程序可以包含多个源文件,在编译时,逐一编译各文件,然后通过链接器链接所有的目标代码构成可执行文件。作用范围和存储类别347
  • 348. 第十一讲 数组
  • 349. 第十一讲 数组一维数组 概念 定义和声明 初始化 使用 二维数组 查找和排序 程序设计举例C语言程序设计349
  • 350. 简介引例 问题:保存n个学生的成绩,按从高到低的顺序排序。数组6572837997877957…78657283799787795778…scores怎么处理 排序 查找?score1 score2 … scorenscores[1] scores[2] … scores[n]用一个名字和编号去引用这些变量。350
  • 351. 概念数组 由一系列类型相同的元素构成。 数组元素都属于同一个数据类型。 数组元素的数目是固定且有限的。 在内存中数组元素是连续存放的。 引用数组元素的格式: <数组名>[<位置编号>] 下标运算符:[] 1级,左结合。 第一个位置编号(下标)是 0。 数组 c 的 n 个元素如图所示。数组-45 6 0 72 1543 -89 0 62 -3 1 6453 78 c[6] c[0] c[1] c[2] c[3] c[11] c[10] c[9] c[8] c[7] c[5] c[4] c数组名数组元素引用名数组元素351
  • 352. 概念数组元素 数组元素可视为普通变量。 下标可以是整型常量或整型表达式。数组-45 6 0 72 1543 -89 0 62 -3 1 6453 78 c[6] c[0] c[1] c[2] c[3] c[11] c[10] c[9] c[8] c[7] c[5] c[4] cc[0] = -45; scanf(“%d”, &c[1]); printf(“%d”, c[1]);若 x = 3, 那么 c[5-2] == c[3] == c[x]352
  • 353. 定义数组格式 <类型说明符> <数组名>[<整型常量表达式>] 数组类型 数组名 元素个数(数组长度)数组#define MAX 200; int c[12]; float f[1000]; int a[MAX], b[MAX*10];int n, m=5, x[m]; scanf(“%d”,&n); { int y[n]; }数组长度必须是一个整型常量或整型常量表达式数组长度不能是不确定的值353
  • 354. 数组的初始化数组的初始化 <类型说明符> <数组名>[<整型常量表达式>] = {<初值表>}; 为每一个数组元素指定初值。 举例 如果初值表中的数据太多,产生语法错误(syntax error) 剩下的元素的初值是 0 数组的长度就是初值表中数值的个数数组int a[5] = {1, 2, 3, 4, 5};int a[5] = {1};int a[ ] = {1, 2, 3, 4, 5};354
  • 355. 数组的初始化数组的初始化说明 初值表不能为空 没有初始化的数组,其元素的值不确定数组int a[5] = { };int a[5]={1}; for (i=0;i<5;i++) printf(“ %d”,a[i]);int a[5]; for (i=0;i<5;i++) printf(“ %d”,a[i]); 1 0 0 0 0 872 0 1492 4160 186不可预知355
  • 356. 案例分析一:使用数组问题 从一个数组中读取数据,并以直方图的形式显示信息。 源代码(cw1001.c)数组#include #define SIZE 10 void main() { int n[SIZE] = {19, 2, 15, 7, 11, 9, 13, 5, 17, 1}; int i ,j;用一系列 ‘*’ 来代替长方形************** *** *******356
  • 357. 案例分析一:使用数组源代码 数组 printf("%s%13s%17s\n", "Element", "Value", "Histogram"); for (i=0; i<=SIZE-1; i++) { printf("%7d%13d", i, n[i]); for (j=1; j<=n[i]; j++) printf("%c", '*'); printf("\n"); } }Element Value Histogram 0 19 ******************* 1 3 *** 2 15 *************** 3 7 ******* 4 11 *********** 5 9 ********* 6 13 ************* 7 5 ***** 8 17 ***************** 9 1 * 357
  • 358. 案例分析二:使用数组问题 统计全班32名同学某门功课的平均成绩,找出最高分。 把学生成绩保存到数组 score 源代码(cw1002a.c)数组#include #define MAX 32 void main() { float score[MAX], sum, best; int i; printf("Input %d scores:\n",MAX); for (i=0; i
  • 359. 案例分析二:使用数组源代码数组 sum=best=score[0]; for (i=1;i
  • 360. 案例分析三:使用数组问题 统计全班32名同学某门功课的平均成绩,找出最高分。 使用位置指针变量 源代码(cw1002b.c)数组#include #define MAX 32 void main() { float score[MAX], sum; int i, best; printf("Input %d scores:\n",MAX); for (i=0; i
  • 361. 案例分析三:使用数组源代码数组 sum=score[0]; best=0; for (i=1; i
  • 362. 案例分析四:使用数组问题 把一个六面的骰子掷6000次,统计每一面出现的次数。 用frequency[7]数组保存六个面出现的次数。 源代码(cw1003.c)数组#include #include #include #define SIZE 7 void main() { int face, roll, frequency[SIZE]={0}; srand(time(NULL));frequency[1], …, frequency[6]362
  • 363. 案例分析四:使用数组源代码数组 for (roll=1; roll<=6000; roll++) { face = rand()%6+1; frequency[face]++; } printf("%s%17s\n", "Face", "Frequency"); for (face=1; face<=SIZE-1; face++) printf("%4d%17d\n", face, frequency[face]); }363
  • 364. 案例分析五:使用数组问题 把一个任意的正整数 N 转换成 d 进制数。 用数组 remainder[MAX+1] 保存每一步的余数数组NN/8N%8134816841682102125202printremainder[len]remainder[1]remainder[2]remainder[3]remainder[4]用 N 除以 d 如果商不为 0,继续用商除以 d,直到商为 0364
  • 365. 案例分析五:使用数组源代码(cw1004.c) 数组#include #define MAX 10 void main() { int N, d, len; int remainder[MAX+1]; printf("Input a decimal integer and a base:"); scanf("%d%d", &N, &d); printf("Converting...\n");估计最大位数实际位数365
  • 366. 案例分析五:使用数组源代码 数组 len=0; while (N) { len++; remainder[len]=N%d; N=N/d; } printf("equals "); for (;len>0;len--) printf("%d",remainder[len]); }366
  • 367. 把数组传递给函数把数组作为参数传递给函数 实参用数组名 通常要把数组的长度传递给函数 对应的函数定义 实参与形参的类型相同 可以不指定形参数组长度 函数原型中数组参数的形式数组int myArray[20]; myFunction(myArray, 20);void myFunction(int array[], int arraySize) { }void myFunction(int[], int);可以有空格长度在这儿不起作用通过另一个参数传递长度367
  • 368. 把数组传递给函数把数组作为参数传递给函数 数组的传递模拟了引用传递数组void bubble_sort(int array[ ], int len) {…} main() { int list[10]; … bubble_sort(list, 10); }list[0] list[1] list[2] … list[9]FF00FF00listFF00array数组名是数组第一个元素的地址 (内存单元编号)。所以,依然是按值传递(传递的是地址)368
  • 369. 把数组传递给函数把数组作为实参传递给函数 数组名是一个指针变量,其值是数组第一个元素的地址。数组#include void main() { int array[10]; printf("array = %p\n" "&array[0] = %p\n", array, &array[0]); }array = 19b8 &array[0] = 19b8%p 输出地址的格式说明字符。printf的格式控制字符串可以分多行写,但每一行必须用“”括起来。369
  • 370. 把数组传递给函数把数组元素作为参数传递给函数 数组元素就是一个普通的变量。 采用值传递。 举例 源代码(cw1006.c)数组#include void modifyArray(int [], int); void modifyElement(int); void main() { int a[] = {0, 1, 2, 3, 4}, i;370
  • 371. 把数组传递给函数举例 源代码数组 printf("\nThe values of the original array are:\n"); for (i=0;i
  • 372. 把数组传递给函数举例 源代码数组void modifyArray(int array[], int size) { int j; for (j=0;j
  • 373. 把数组传递给函数举例 运行结果数组The values of the original array are: 0 1 2 3 4 The values of the modified array are: 0 2 4 6 8 The value of a[3] is 6 Value in modifyElement is 12 The value of a[3] is 6373
  • 374. 二维数组二维数组 可以看作是一个有行号和列号的数据表。 例如,矩阵或行列式。 举例 int a[3][4];数组Row 0 Row 1 Row 2 Column 0 Column 1 Column 2 Column 3 a[ 0 ][ 0 ] a[ 1 ][ 0 ] a[ 2 ][ 0 ] a[ 0 ][ 1 ] a[ 1 ][ 1 ] a[ 2 ][ 1 ] a[ 0 ][ 2 ] a[ 1 ][ 2 ] a[ 2 ][ 2 ] a[ 0 ][ 3 ] a[ 1 ][ 3 ] a[ 2 ][ 3 ] Row subscript Array name Column subscript 374
  • 375. 二维数组定义二维数组 <数组类型> <数组名>[<行数>][<列数>]; 举例数组#define MAXNUM 30 #define MAXLEN 20 #define MAXLES 5 int a[3][3], b[30][3]; char name[MAXNUM][MAXLEN]; float score[MAXNUM][MAXLES];a[3][3] 二维数组可以看作是由一维数组组成的数组,即数组元素是一维数组的一维数组。a[0]a[0][0]a[0][1]a[0][2]a[1]a[1][0]a[1][1]a[1][2]a[2]a[2][0]a[2][1]a[2][2]375
  • 376. 二维数组引用二维数组的元素 <数组名>[<行下标>][<列下标>] 举例 遍历二维数组中的元素数组int a[3][4]; for (m=0;m<3;m++) for (n=0;n<4;n++) a[m][n] = m*n;常用嵌套的for循环# # # # # # # # # # # #376
  • 377. 二维数组二维数组的存储形式 二维数组的元素是按行顺序存放的。 举例 int a[2][3];数组a[0][0] a[0][1] a[0][2] a[1][0] a[1][1] a[1][2]12 32 -1 87 0 53 a[0][0] a[0][1] a[0][2] a[1][0] a[1][1] a[1][2]a数组 a 的存储形式377
  • 378. 二维数组二维数组的初始化 四种方式: 给全部元素赋初值 给部分元素赋初值 给全部元素赋初值时,不指定第一维的长度,但要指定第二维的长度 给部分元素赋初值时,不指定第一维的长度,但要指定第二维的长度数组378
  • 379. 二维数组二维数组的初始化 给全部元素赋初值 数组int a[2][3]={{10,11,12},{13,14,15}}; int a[2][3]={10,11,12,13,14,15};a[0][0]a[0][1]a[0][2]101112a[1][0]a[1][1]a[1][2]131415用括号按行分组379
  • 380. 二维数组二维数组的初始化 给部份元素赋初值 数组int a[2][3]={{10,11},{13}};a[0][0]a[0][1]a[0][2]10110a[1][0]a[1][1]a[1][2]1300int a[2][3]={10,11,13};a[0][0]a[0][1]a[0][2]101113a[1][0]a[1][1]a[1][2]000没有明确指定初值的元素被初始化为缺省初值380
  • 381. 二维数组二维数组的初始化 给全部元素赋初值时,不指定第一维的长度,但要指定第二维的长度. 数组int a[ ][3]={{10,11,12},{13,14,15}}; int a[ ][3]={10,11,12,13,14,15};a[0][0]a[0][1]a[0][2]101112a[1][0]a[1][1]a[1][2]131415编译器: 我来算吧。381
  • 382. 二维数组二维数组的初始化 给部分元素赋初值时,不指定第一维的长度,但要指定第二维的长度。 数组int a[ ][3]={{10,11},{13}};a[0][0]a[0][1]a[0][2]10110a[1][0]a[1][1]a[1][2]1300382
  • 383. 案例分析:二维数组的应用问题 求矩阵a的转置矩阵b。 分析 用二维数组来保存矩阵。 数组b的第i行第j列的元素 等于 a的第j行第i列的元素for(i=0;i<3;i++) for(j=0;j<2;j++) b[i][j] = a[j][i]383
  • 384. 案例分析:二维数组的应用源代码(cw1007.c) 数组#include void main() { int a[2][3]={1,2,3,4,5,6}, b[3][2]; int i, j; printf("matrix a is:\n"); for (i=0; i<2; i++) { for (j=0; j<3; j++) { printf("%3d", a[i][j]); } printf("\n"); }384
  • 385. 案例分析:二维数组的应用源代码 数组 printf("matrix b is:\n"); for (i=0; i<3; i++) { for (j=0; j<2; j++) { b[i][j] = a[j][i]; printf("%3d", b[i][j]); } printf("\n"); } }matrix a is: 1 2 3 4 5 6 matrix b is: 1 4 2 5 3 6385
  • 386. 把二维数组传递给函数把二维数组传递给函数 数组元素作为函数的实参 值传递 二维数组名作为函数的实参 模拟引用传递数组void exchange(int a[][3], int b[][2], int m, int n); void main() { int a[2][3], b[3][2]; … exchange(a, b, 2, 3); … }第1个括号可以为空,其他下标必须指定。386
  • 387. 查找查找 根据指定的关键字查找数组中的特定元素。 常用方法 顺序查找 折半查找数组387
  • 388. 顺序查找顺序查找 适用于小型和(或)没有排序的数组。 用关键字与数组的元素依次进行比较。 平均而言,要与数组的一半元素进行比较数组6572837997877957917887查找表关键字388
  • 389. #define N 10 void main() { int list[N+1]={0,65,72,83,79,97,87,75,57,91,78}; int key,i; printf("Input search key:"); scanf("%d",&key); for (i=1;(list[i]!=key)&&(i<=N);i++); if (i>N) printf("Not found!"); else printf("Success! The position is %d.",i); }顺序查找顺序查找举例(cw1009.c) 数组65728379978779579178389
  • 390. 折半查找折半查找 适用于已经排好序的数组。 用关键字与数组的中间元素比较 如果相等,则查找结束——找到 如果keymiddle,则继续在后半部分查找 如果没有可查找的部分,则查找结束——没有找到数组5765727578798387919783lowmidhigh390
  • 391. 折半查找折半查找举例(cw1010.c) 数组#include #define N 10 void main() { int i, low, mid, high, key, found; int list[N+1]={0,57,65,72,75,78,79,83,87,91,97}; printf("Sorted list:\n"); for (i=1;i<=N;i++) printf("%-4d", list[i]); printf("\n"); printf("Input search key:"); scanf("%d", &key);391
  • 392. 折半查找折半查找举例 数组 low=1; high=N; found=0; while ((low<=high) && (! found)) { mid=(low+high)/2; if (key>list[mid]) low=mid+1; else if (key==list[mid]) found=1; else high=mid-1; } if (found) printf("Success! The position is %d.", mid); else printf("Not found!"); }考虑不要found变量392
  • 393. 排序排序 按特定的顺序来安排数据。 常用方法 直接插入排序 简单选择排序 冒泡排序数组393
  • 394. 数据插入问题 把一个数据插入到已排好序的有序表中,从而得到一个新的、长度增1的有序表。数组57657275787987919783576572757879838791975765727578798791971.找到插入点2.腾出位置3.插入数据394
  • 395. 数据插入数据插入(cw1011.c) 把一个数据插入到一个有序表中。数组#include #define N 20 void main() { int i, j, x, len=9; int list[N]={57,65,72,75,78,79,87,91,97}; printf("Sorted list:\n"); for (i=0;i
  • 396. 数据插入数据插入 续数组 for (i=0;(x>list[i])&&(ii;j--) list[j]=list[j-1]; list[i]=x; len++; printf("The new list:\n"); for (i=0;i
  • 397. 直接插入排序直接插入排序 数组(78) 45 25 31 13 66 92 8初始状态(45 78) 25 31 13 66 92 8插入第2个数(25 45 78) 31 13 66 92 8插入第3个数(8 13 25 31 45 66 78 92)插入最后一个数……397
  • 398. 直接插入排序直接插入排序(cw1012.c) 输入任意个数,按从小到大的顺序对它们进行排序。数组#include #define N 10 void main() { int i, j, k, len; int list[N], x; printf("Input several integers to construct a list\n"); printf(“How many?(<%d)”, N); scanf("%d", &len); printf(“Please input them:”); for (i=0;i
  • 399. 直接插入排序直接插入排序 续数组 printf("\nTo sort...\n"); for (i=1;ilist[j])&&(jj;k--) list[k]=list[k-1]; list[j]=x; } printf("Finished! The list has been sorted:\n"); for (i=0;i
  • 400. 简单选择排序简单选择排序 数组78 45 25 31 13 66 92 8初始状态(8) 78 45 31 25 66 92 13找到最小数(8 13) 78 45 31 66 92 25找到第二小的数(8 13 25 31 45 66 78 92)最大数被找到……一趟简单选择排序的操作:通过n-i次数据间的比较,从n-i+1个记录中选出最小的数,并和第i个数交换。400
  • 401. 简单选择排序简单选择排序(cw1013.c) 输入任意个数,按从小到大的顺序对它们进行排序。数组#include #define N 10 void main() { int i, j, len, min; int list[N], tmp; printf("Input several integers to construct a list.\n"); printf(“How many?(<%d)”, N); scanf("%d", &len); printf(“Please input them:”); for (i=0;i
  • 402. 简单选择排序简单选择排序 续数组 printf("\nTo sort...\n"); for (i=0;ilist[j]) min=j; tmp=list[i]; list[i]=list[min]; list[min]=tmp; } printf("Finished! The list has been sorted:\n"); for (i=0;i
  • 403. 冒泡排序冒泡排序 将相邻两个数比较,把小的调到前面,大数放到后面。数组78 45 32 10 29 80 92 8 61 5545 78 32 10 29 80 92 8 61 5545 32 78 10 29 80 92 8 61 5545 32 10 78 29 80 92 8 61 5545 32 10 29 78 80 92 8 61 5545 32 10 29 78 80 92 8 61 5545 32 10 29 78 80 92 8 61 5545 32 10 29 78 80 8 92 61 5545 32 10 29 78 80 8 61 92 5545 32 10 29 78 80 8 61 55 9232 10 29 45 78 8 61 55 80 9210 29 32 45 8 61 55 78 80 92…8 10 29 32 45 55 61 78 80 92小数大数N-1 趟403
  • 404. 冒泡排序冒泡排序(cw1014.c) 输入任意个数,按从小到大的顺序对它们进行排序。数组#include #define N 10 void main() { int i, j, len; int list[N], tmp; printf("Input several integers to construct a list.\n"); printf(“How many?(<%d)”, N); scanf("%d", &len); printf(“Please input them:”); for (i=0;i
  • 405. 冒泡排序冒泡排序 续数组 printf("\nTo sort...\n"); for (i=0;ilist[j+1]) { tmp=list[j]; list[j]=list[j+1]; list[j+1]=tmp; } printf("Finished! The list has been sorted:\n"); for (i=0;i
  • 406. 小结数组是由同一种数据类型的元素序列构成的。 数组元素按顺序存储在内存中。 通过使用整数索引来访问数组元素。 程序员要能正确地使用数组索引,因为编译器和程序运行时都不检查索引是否合法。 C不支持把整个数组作为函数的参数进行传递,但是可以传递数组的地址。然后函数可以利用该地址来处理原始数组。 二维数组是数组的数组。 查找和排序是两类常用的应用。数组406
  • 407. 第十二讲 指针
  • 408. 第十二讲 指针指针的概念 指针变量的定义和初始化 指针运算符 指针作为函数参数 指针与数组 指针与字符串 程序设计举例C语言程序设计408
  • 409. 简介指针 功能强大,不易掌握。 用来模拟引用传递。 与数组和字符串关系密切。 可以创建和操作动态的数据结构: 链表 队列 栈 树指针409
  • 410. 指针的概念指针就是内存对象的地址。 内存对象包括:变量,数组,函数等。 C语言允许直接通过地址来处理数据。指针int x; x=23;内存单元的地址 一个无符号的整数,就是指向变量的指针。变量名 其实就是内存单元的符号化名称。内存单元的内容 就是变量的值。FF00x23直接引用 Direct reference 通过变量名直接引用变量占用的内存单元。410
  • 411. 内存对象的地址变量的地址 用取地址运算符(&)获得变量在内存中的地址。 数组的地址 即第一个元素的地址,用数组名表示。 函数的地址 用函数名表示。 指针int var; scanf(“%d”, &var);vararrayfunctionint array[3];int function(int x);411
  • 412. 指针变量指针变量就是保存内存地址的变量。 指针int x=23; int *x_pointer; x_pointer=&x;FF00x23FFF0x_pointerFF00指针变量 保存变量的地址。 变量 x_pointer 的值是变量 x 的地址(指针)。 目前,指针 x_pointer 指向变量 x 。间接引用 Indirect reference 通过指针变量间接引用变量占用的内存单元。412
  • 413. 指针变量指针变量的声明 <类型说明符> * <指针变量名>; * 表示 x_pointer 是一个指针变量 x_pointer 是 int* 类型的指针,读作: 指向 int 型数据的指针 指向整型对象的指针 指针可以声明为指向任何数据类型的对象 声明多个指针时,每个变量前都必须有 *指针int *x_pointer;int *x_pointer, *y_pointer; char *charPtr;注意 所有指针变量保存的数据的类型是相同的,即一个内存单元的地址,但是,它们指向的数据的类型可以不同。413
  • 414. 指针变量指针变量的初始化 在声明语句中为指针变量指定初值。 指针变量可以被初始化为 0 ,NULL 或 一个地址量。 0 和 NULL 是等价的(用NULL更好) NULL 是在(和几个其他头文件)中定义的符号常量指针int x, *p=&x;int x, *p=0xffe;int x, *p=(int *)0xffe;强制类型转换int x, *p=NULL;空指针:不指向任何对象Warning: Nonportable pointer conversion414
  • 415. 取地址运算符取地址运算符:& 返回变量在内存中的地址。指针int y, *yPtr; y = 5; yPtr = &y;5yPtrfff0yff00yPtry5y的地址是yPtr的值yPtr “指向” yff00415
  • 416. 指针运算符指针运算符:* 返回指针变量所指向的对象的别名。 *yPtr 就是 y ,因为 yPtr 指向 y。 可以用在赋值语句中。指针int y=5, *yPtr; yPtr = &y;int y, *yPtr; yPtr = &y; *yPtr = 5;416
  • 417. 案例分析:指针运算符指针运算符 指针#include void main() { int a, *aPtr; a = 7; aPtr = &a; printf("The address of a is %p" "\nThe value of aPtr is %p", &a, aPtr); printf("\n\nThe value of a is %d" "\nThe value of *aPtr is %d", a, *aPtr); printf("\n\nShowing that * and & are inverses of each other." "\n&*aPtr = %p" "\n*&aPtr = %p", &*aPtr, *&aPtr); }417
  • 418. 案例分析:指针运算符指针运算符 运行结果指针The address of a is 1A58 The value of aPtr is 1A58 The value of a is 7 The value of *aPtr is 7 Showing that * and & are inverses of each other. &*aPtr = 1A58 *&aPtr = 1A58* 和 & 是互反的418
  • 419. 指针作为函数的参数指针作为函数的参数用来模拟引用传递。 用指针变量作为函数的形式参数。 调用函数时,用 & 运算符把实际参数的地址传递给函数。 在被调用函数中,用 * 运算符间接引用实际参数。指针int myFunc( int *ptr );myFunc( &var );int myFunc( int *ptr ) { *ptr = … }419
  • 420. 案例分析:指针参数问题:计算任意整数的立方。 值传递:callByValue()指针#include int callByValue(int); /*返回参数的立方*/ void main() { int number = 5; printf("The original value of number is %d", number); number = callByValue(number); printf("\nThe new value of number is %d", number); } int callByValue(int n) { return n*n*n; }The original value of number is 5 The new value of number is 125420
  • 421. 案例分析:指针参数问题:计算任意整数的立方。 值传递:callByReference()指针#include void callByReference(int*); void main() { int number = 5; printf("The original value of number is %d", number); callByReference(&number); printf("\nThe new value of number is %d", number); } void callByReference(int *nPtr) { *nPtr = *nPtr**nPtr**nPtr; }The original value of number is 5 The new value of number is 125421
  • 422. 案例分析:指针参数问题:计算任意整数的立方。 结果分析指针void main() { int number = 5; callByReference(&number); }void callByReference(int *nPtr) { *nPtr = *nPtr**nPtr**nPtr; }void main() { int number = 5; callByReference(&number); }void callByReference(int *nPtr) { *nPtr = *nPtr**nPtr**nPtr; }void main() { int number = 5; callByReference(&number); }void callByReference(int *nPtr) { *nPtr = *nPtr**nPtr**nPtr; }nPtr5numbernPtr125numbernPtr125number调用前调用时调用后422
  • 423. 案例分析:指针参数问题:交换两个变量的值。 哪一种实现是正确的?指针void swap(int x, int y) { int tmp; tmp=x; x=y; y=tmp; } void main() { int a=4, b=6; swap(a, b); printf("a=%d,b=%d",a,b); }a=4,b=6void swap(int *x, int *y) { int tmp; tmp=*x; *x=*y; *y=tmp; } void main() { int a=4, b=6; swap(&a, &b); printf("a=%d,b=%d",a,b); }a=6,b=4423
  • 424. 指针运算指针可以参与以下运算: 赋值运算 给指针变量赋值 关系运算 两个指针之间的比较 算术运算 加(减)一个整数 两个指针相减指针424
  • 425. 指针运算指针的赋值运算 可以把指针赋给同类型的指针变量。指针int x=10, *p, *q; p = &x; q = p; printf(“*q=%d", *q);*q=1010x2000p2000q2000425
  • 426. 指针运算指针的赋值运算 把指针赋值给类型不同的指针变量时要进行类型转换。 但 void * 类型的指针是一个例外。指针int a, *intPtr=&a; char *charPtr; charPtr = (char*)intPtr;int a, *intPtr=&a; char *charPtr; void *voidPtr; voidPtr = intPtr; charPtr = voidPtr;void* 通用指针 代表任何指针类型 不能间接引用426
  • 427. 指针运算指针的赋值运算 比较两个指针的值。指针p1 < p2 p1 <= p2 p1 > p2 p1 >= p2 p1 == p2 p1 != p22000p12002p2char *charPtr; … charPtr >= (char*)2000与同类型的指针常量比较 常常与 0 比较427
  • 428. 指针运算指针的算术运算 自增自减(++,--) 加上一个整数(+,+=,-,-=) 两个指针相减指针428
  • 429. 指针运算指针的算术运算 举例指针int *vP1, *vP2; vP1 = (int*)2000; vP2 = vP1 + 2;2000200220042006 数组 v vP12000 + 2 = 20022000 + 2*2 = 2004vP2所指向的内存对象的长度?vP1 + 2429
  • 430. 指针运算指针的算术运算 举例指针int *vP1, *vP2; vP1 = (int*)2000; vP2 = (int*)2004;2000200220042006 数组 v vP1vP2vP2和vP1之间内存对象的个数?vP2 - vP1 =2指针的算术运算在数组上使用才有意义430
  • 431. 指针与数组数组和指针关系密切。 数组名是一个指针常量。 数组指针:指向数组的指针。 数组指针可以用来完成任何涉及数组下标的操作。 将 bPtr 的值置为数组 b 中的第一个元素的地址 bPtr = b; 等价于 bPtr = &b[0];指针int b[5]; int *bPtr;431
  • 432. 指针与数组数组和指针关系密切。 引用数组元素的表达式 数组元素 b[3] 可以用 *(bPtr + 3) 来引用 3是偏移量 这种表示法称为指针偏移量表示法 还可以用 bPtr[3] 来引用 称为指针下标表示法 与 b[3] 相同 还可以用 *(b + 3) 来引用指针432
  • 433. 指针与数组数组和指针关系密切。 引用数组元素的表达式指针main() { int i, a[5]={1,2,3,4,5}; for (i=0; i<5; i++) printf("%2d",a[i]); }下标法main() { int i, a[5]={1,2,3,4,5}; for (i=0; i<5; i++) printf("%2d",*(a+i)); }地址法main() { int a[5]={1,2,3,4,5}, *p; for (p=a; p<(a+5); p++) printf("%2d",*p); }指针法433
  • 434. 指针与数组数组和指针互换使用时的注意事项 数组名是一个指针常量。指针因为a是数组名,即数组的首地址,它的值在程序运行期间是固定不变的! 是一个常量。错main() { int a[5]={1,2,3,4,5}, *p; for ( p=a; a<(p+5); a++ ) printf("%2d",*a); }434
  • 435. 指针与数组数组和指针互换使用时的注意事项 注意指针变量的值。指针要注意指针变量的当前值。ppa[0]a[1]a[2]a[3]a[4]数组amain() { int i, a[5], *p; p=a; for ( i=0; i<5; i++ ) scanf( "%d", p++ ); for ( i=0; i<5; i++, p++ ) printf( "%d ", *p ); }错p = a;435
  • 436. 指针与数组数组和指针互换使用时的注意事项 注意运算符的优先级。指针* aPtr ++* (aPtr ++)等价考虑: *++aPtr 与 *(++aPtr)int a=0, *aPtr=&a; printf("a=%d,aPtr=%p\n", a, aPtr); printf("%d\n", *aPtr++); printf("a=%d,aPtr=%p\n", a, aPtr);a=0,aPtr=1BCC 0 a=0,aPtr=1BCE436
  • 437. 指针与数组数组参数的值传递 形参数组是一个指针变量。指针void sort( int *x , int n) { … } void main() { int a[10]; … … sort( a , 10); … }a, xa[0]a[1]a[2]a[3]a[4]a[5]a[6]a[7]a[8]a[9]地址void sort(int x[], int n) { … }等价437
  • 438. 指针数组数组元素是指针的数组 <类型说明符> *<数组名>[<常量表达式>]; 常用来构造字符串数组。 注意: 字符串并不在 suit 数组中。 suit 数组只包含指向字符串的指针。指针char *suit[4] = {“Hearts”, “Diamonds”, “Clubs”, “Spades”};suit[3] suit[2] suit[1] suit[0] ’H’ ’e’ ’a’ ’r’ ’t’ ’s’ ’\0’ ’D’ ’i’ ’a’ ’m’ ’o’ ’n’ ’d’ ’s’ ’\0’ ’C’ ’l’ ’u’ ’b’ ’s’ ’\0’ ’S’ ’p’ ’a’ ’d’ ’e’ ’s’ ’\0’ suit 的长度是固定的,但却可以访问任意长度的字符串438
  • 439. 案例分析:指针数组问题:洗牌和发牌的模拟 定义数据结构 字符串(指针)数组 suit 保存牌的花色名 字符串(指针)数组 face 保存牌的号码 二维数组 deck 表示一副牌,行对应花色,列对应号码 保存洗牌后牌的序号指针deck[ 2 ][ 12 ] represents the King of ClubsHeartsDiamondsClubsSpades0 1 2 3 Ace Two Three Four Five Six Seven Eight Nine Ten Jack Queen King 0 1 2 3 4 5 6 7 8 9 10 11 12 Clubs King 439
  • 440. 案例分析:指针数组问题:洗牌和发牌的模拟 设计算法 算法的顶部 第一次细化指针对52张牌进行洗牌和发牌初始化 suit 数组 初始化 face 数组 初始化 deck 数组 对 52 张牌洗牌 对 52 张牌发牌440
  • 441. 案例分析:指针数组问题:洗牌和发牌的模拟 设计算法 第二次细化指针初始化 suit 数组 初始化 face 数组 初始化 deck 数组 对 52 张牌中的每张 在随机选择的纸牌空位上放置纸牌序号 对 52 张牌中的每张 查找 deck 数组中纸牌的序号,并显示纸牌的花色和号码441
  • 442. 案例分析:指针数组问题:洗牌和发牌的模拟 设计算法 第三次细化指针初始化 suit 数组 初始化 face 数组 初始化 deck 数组 对 52 张牌中的每张 随机选择纸牌位置 当所选纸牌位置已经被选过(有序号) 随机选择纸牌位置 在所选纸牌位置中放置纸牌序号 对 52 张牌中的每张 对于 deck 数组的每个位置 如果该位置包含期望的纸牌序号 显示纸牌的花色和号码442
  • 443. 案例分析:指针数组问题:洗牌和发牌的模拟 源代码指针#include #include #include void shuffle(int[][13]); void deal(int[][13], char*[], char*[]); void main() { char *suit[]={"Hearts", "Diamonds", "Clubs", "Spades"}; char *face[]={"Ace","Two","Three", "Four","Five","Six", "Seven","Eight","Nine", "Ten","Jack","Queen","King"}; int deck[4][13]={0};443
  • 444. 案例分析:指针数组问题:洗牌和发牌的模拟 源代码指针 srand(time(0)); shuffle(deck); deal(deck, face, suit); }444
  • 445. 案例分析:指针数组问题:洗牌和发牌的模拟 源代码指针void shuffle(int wDeck[][13]) { int row, column, card; for (card=1; card<=52; card++) { do { row = rand()%4; column = rand()%13; } while(wDeck[row][column]!=0); wDeck[row][column]=card; } }445
  • 446. 案例分析:指针数组问题:洗牌和发牌的模拟 源代码指针void deal(int wDeck[][13], char *wFace[], char *wSuit[]) { int card, row, column; for (card=1; card<=52; card++) for (row=0; row<4; row++) for (column=0; column<13; column++) if (wDeck[row][column]==card) printf("%5s of %-8s%c", wFace[column], wSuit[row], card%2==0?'\n':'\t'); }446
  • 447. 小结指针指针447
  • 448. 第十三讲 字符串
  • 449. 第十三讲 字符串字符串与字符数组 字符数组的定义和初始化 字符串的输入与输出 指针与字符串 程序设计举例C语言程序设计449
  • 450. 简介字符串与字符数组 在C语言中,字符串是存储在字符数组中并用空字符(’\0’)结束的字符序列。字符串char name[20]; char names[30][20];…name字符串名,即字符数组名,就是该字符数组第一个元素的地址name[0]name[1]name[19]450
  • 451. 字符数组的初始化给字符数组的全部元素赋初值 字符串char a[5]={‘C’,’h’,’i’,’n’,’a’};a[0]a[1]a[2]a[3]a[4]‘C’‘h’‘i’‘n’‘a’451
  • 452. 字符数组的初始化给字符数组的部分元素赋初值 第十一讲 字符串char a[5]={‘C’,’h’,’i’};a[0]a[1]a[2]a[3]a[4]‘C’‘h’‘i’‘\0’‘\0’缺省的初值为 ‘\0’452
  • 453. 字符数组的初始化给全部元素赋初值时,可以不指定长度 字符串char a[ ]={‘C’,’h’,’i’,’n’,’a’};a[0]a[1]a[2]a[3]a[4]‘C’‘h’‘i’‘n’‘a’char a[5]={‘C’,’h’,’i’,’n’,’a’};453
  • 454. 字符数组的初始化用字符串常量给字符数组赋初值 字符串char a[ ] = {“China”};char a[ ] = “China”;char a[6] = {“China”};char a[6] = “China”;a[0]a[1]a[2]a[3]a[4]a[5]‘C’‘h’‘i’‘n’‘a’‘\0’来自字符串常量的 结束字符 ‘\0’ 。454
  • 455. 字符串的输入输出字符串的输入输出有几种方式 用格式输入输出函数逐个字符输入输出 %c 用格式输入输出函数作为整体输入输出 %s 用字符串输入输出函数作为整体输入输出 gets() puts()字符串455
  • 456. 字符串的输入输出用%c逐个字符的输入输出 举例(cw1101.c)字符串#include #define MAX 5 void main() { int n; char c[MAX]; printf("Input %d characters:\n",MAX); for (n=0;n
  • 457. 字符串的输入输出用%s实现字符串作为整体一次性的输入输出 举例(cw1102.c)字符串#include #define MAX 5 void main() { char c[MAX]; printf("Input c[%d]:\n",MAX); scanf("%s",c); printf("Show c:\n"); printf("%s",c); }输入和输出项都是数组名 在输入时,空格、制表符和换行符都被作为字符串的分隔符,不被读入 在输出时,一旦遇到 ‘\0’ 就结束457
  • 458. 字符串的输入输出用标准库提供的字符串输入输出函数 举例(cw1103.c)字符串#include #define MAX 5 void main() { char c[MAX]; printf("Input c[%d]:\n",MAX); gets(c); printf("Show c:\n"); puts(c); }在输入时,换行符被作为数据的分隔符,不被读入 但是,空格和制表符可以被读入 puts()输出字符串后自动换行458
  • 459. 指针与字符串字符串指针 指向字符串(字符数组)字符指针。指针char str[20]=“China”, *p=str; printf(“%s”, p);China\0char *p=“China”; printf(“%s”, p);C语言对字符串常量是按字符数组处理的。char *p; p=“China”; printf(“%s”, p);China\0p459
  • 460. 指针与字符串字符数组和指针 比较指针char str[5]= “wuhan”;char *p=“wuhan”;char *p; p=“wuhan”;char str[5]; str= “wuhan”;=≠错对460
  • 461. 指针与字符串字符数组和指针 比较指针char *p; scanf(“%s”, p);char str[5], *p; p=str; scanf(“%s”, p);char str[5]; scanf(“%s”, str);错对对在编译时为字符串数组分配内存单元,有确定的地址 虽然为字符指针变量分配了内存单元,但其值(地址)是不确定的,不可以使用461
  • 462. 指针与字符串案例分析 问题:字符串的拷贝。指针China\0Chinese\0afrmbtoChina\0e\0bvoid strCopy(char *frm, char *to) { while((*to=*frm)!='\0') { frm++; to++; } } void main() { char *a="China"; char *b="Chinese"; strCopy(a, b); printf("%s", b); }China462
  • 463. 字符和字符串标准库函数字符函数: 字符串函数: 常用的函数 strcat(字符数组名1, 字符串2) strcpy(字符数组名1, 字符串2) strcmp(字符串1, 字符串2) strlen(字符串) strlwr(字符数组名) strupr(字符数组名) 字符串转换库: 把数字字符串转换成整数和浮点数字符串字符串:字符串数组名或者字符串常量463
  • 464. 案例分析(一)问题 输入一行字符,统计其中有多少个单词,单词之间用空格隔开。 分析 逐一检测字符,遇到单词的第一个字符就计数。 单词的第一个字符是一个非空格字符,且其前一个字符是空格。 定义一个标记变量。 记住当前字符的前一个字符(最近检测的)是否是空格。字符串Iamfrom…\0464
  • 465. 案例分析(一)设计 定义标记变量 word 0 最近检测的字符是空格 期待新单词出现 1 最近检测的字符不是空格 正在读单词 期待单词的结束字符串i=0 word=0 num=0(c=s[i])!=‘\0’c==‘ ’word=0i++word==0word=1 num++NYNYNY465
  • 466. 案例分析(一)源代码(cw1104.c)字符串#include void main() { char c, s[81]; int i, num, word; printf("Input a sentence:\n"); gets(s);466
  • 467. 案例分析(一)源代码字符串 num=word=i=0; while ((c=s[i])!='\0') { if (c==' ') word=0; else if (word==0) { word=1; num++; } i++; } printf("There are %d words in this sentence.\n", num); }扩展: 输出这些单词 统计每个单词出现的次数467
  • 468. 案例分析(二)问题 编程实现strcat()函数的功能。 任意输入两个字符串,将它们连接成一个字符串。 分析 字符串xyz\0abc\0str1str2\0468
  • 469. 案例分析(二)源代码(cw1105.c) 字符串#include void myStrcat(char str1[], char str2[]) { int i, j; for(i=0;str1[i]!='\0';i++); for(j=0;str2[j]!='\0';i++,j++) str[i]=str2[j]; str[i]='\0'; } void main() { char str1[161], str2[81]; printf("Input two strings:\n"); gets(str1); gets(str2); myStrcat(str1, str2); printf("\nNow, you get a new string:\n%s\n", str1); }str1的长度要足够大469
  • 470. 案例分析(三)问题 输入一字符串,再输入一个字符,要求程序将字符串中该字符删去。 分析字符串wuhanda\0wuhanda\0读写例:删去空格wuhanda\0读写470
  • 471. 案例分析(三)源代码(cw1106.c) 字符串#include void main() { void delLetter(char str[], char c); char str[81], c; printf("Input a string:\n"); gets(str); printf("Input a letter:\n"); c=getchar(); delLetter(str, c); printf("Delete all \'%c\' from the string.\n", c); printf("The string:\n"); puts(str); }471
  • 472. 案例分析(三)源代码 字符串void delLetter(char str[], char c) { int i, j; for (i=j=0;str[i]!='\0';i++) if (str[i]!=c) { str[j]=str[i]; j++; } str[j]='\0'; }i是读指针 j是写指针472
  • 473. 案例分析(四):TicTacToe问题:“井”字棋游戏(一担挑) 游戏规则: 双方轮流下子 每次一方只能在某一个空格处下一颗棋子 胜负判断: 若棋盘的某一行,或某一列,抑或某一对角线上的三个格子被某一方的棋子占据,则该方胜利; 否则,为平局字符串473
  • 474. 案例分析(四):TicTacToe分析 游戏过程字符串玩家胜利计算机胜利初始化棋盘玩家下一颗棋子胜利?计算机下一颗棋子胜利?YYNN怎么表示?怎么判断?计算机如何决策?474
  • 475. 案例分析(四):TicTacToe设计 功能分解字符串玩家胜利计算机胜利初始化棋盘玩家下一颗棋子胜利?计算机下一颗棋子胜利?YYNN初始化棋盘显示棋盘玩家走一步判断胜负计算机走一步判断胜负475
  • 476. 案例分析(四):TicTacToe设计 功能模块字符串初始化棋盘: init_matrix()显示棋盘: disp_matrix()玩家走一步: get_player_move()判断胜负: check()计算机走一步: get_computer_move()main476
  • 477. 案例分析(四):TicTacToe设计 数据结构字符串3*3的二维字符数组: char matrix[3][3]; 全局变量。玩家的棋子用‘X’表示; 计算机的棋子用‘O’表示; ‘ ’表示空。一步棋 确定棋子的位置:坐标(x,y)477
  • 478. 案例分析(四):TicTacToe实现(cw1107.c) 源代码分析字符串char matrix[3][3]; /* the ticktacktoe matrix */ void main(void) { char done; printf("This is the game of ticktacktoe.\n"); printf("You will be playing against the computer.\n"); done = ' '; init_matrix();done: 保存当前游戏的状态和结果 ‘ ’ 游戏未结束,继续玩 ‘X’ 游戏结束,玩家胜 ‘O’ 游戏结束,计算机胜478
  • 479. 案例分析(四):TicTacToe实现 源代码分析字符串 do { disp_matrix(); get_player_move(); done = check(); /* see if winner */ if (done != ' ') break; /* winner! */ get_computer_move(); done = check(); /* see if winner */ } while (done == ' '); if (done == 'X') printf("You won!\n"); else printf("I won!!!!\n"); disp_matrix(); /* show final positions */ }479
  • 480. 案例分析(四):TicTacToe实现 源代码分析字符串/* Initialize the matrix. */ void init_matrix(void) { int i, j; for (i=0; i<3; i++) for (j=0; j<3; j++) matrix[i][j] = ' '; }480
  • 481. 案例分析(四):TicTacToe实现 源代码分析字符串/* Display the matrix on the screen. */ void disp_matrix(void) { int t; for (t=0; t<3; t++) { printf(" %c | %c | %c", matrix[t][0], matrix[t][1], matrix[t][2]); if (t != 2) printf("\n---|---|---\n"); } printf("\n"); }481
  • 482. 案例分析(四):TicTacToe实现 源代码分析字符串/* See if there is a winner. */ char check(void) { int i; for (i=0; i<3; i++) /* check rows */ if (matrix[i][0] == matrix[i][1] && matrix[i][0] == matrix[i][2]) return matrix[i][0]; for (i=0; i<3; i++) /* check colums */ if (matrix[0][i] == matrix[1][i] && matrix[0][i] == matrix[2][i]) return matrix[0][i];482
  • 483. 案例分析(四):TicTacToe实现 源代码分析字符串 /* test disgonals */ if (matrix[0][0] == matrix[1][1] && matrix[1][1] == matrix[2][2]) return matrix[0][0]; if (matrix[0][2] == matrix[1][1] && matrix[1][1] == matrix[2][0]) return matrix[0][2]; return ' '; }483
  • 484. 案例分析(四):TicTacToe实现 源代码分析字符串/* Get a player's move. */ void get_player_move(void) { int x, y; printf("Enter X, Y coordinates for your move: "); scanf("%d%*c%d", &x, &y); x--; y--; if (matrix[x][y] != ' ') { printf("Invalid move, try again.\n"); get_player_move(); } else matrix[x][y] = 'X'; }484
  • 485. 案例分析(四):TicTacToe实现 字符串/* Get a move from the computer. */ void get_computer_move(void) { int i, j; for (i=0; i<3; i++) { for (j=0; j<3; j++) if (matrix[i][j] == ' ') break; if (matrix[i][j] == ' ') break; } if (i*j == 9) { printf("draw\n"); exit(0); } else matrix[i][j] = 'O'; }人工智能485
  • 486. 小结C语言用字符数组来保存字符串。 C系统提供了较多的字符和字符串标准库函数。字符串486
  • 487. 第十四讲 结构体
  • 488. 第十四讲 结构体结构体的概念 结构体类型的定义 结构体变量 定义和声明 初始化 使用 结构体作函数的参数 自定义类型 程序设计举例C语言程序设计488
  • 489. 引例问题 图书包括书号、作者、出版商、出版日期等属性。 怎么定义数据结构?结构体如何表示图书?如何表示多本图书?用多个独立的数据。用多个并列数组。用单个数据。用一个数组。489
  • 490. 简介结构体 相关数据的集合。 数据的类型可以不相同。 用来定义保存在文件中的记录。 与指针一起创建动态的数据结构: 链表 队列 栈 树结构体490
  • 491. 结构体类型的定义结构体是派生的数据类型 使用其他类型的对象来构造结构体。 结构体定义举例 struct:引入结构体定义。 card:结构体的名称,必须与 struct 一起使用。 struct card 结构体包含两个 char * 类型的成员: face suit结构体/*表示纸牌*/ struct card { char *face; char *suit; };为程序创建了一个新的数据类型 struct card491
  • 492. 结构体类型的定义结构体定义说明 同一个结构体内不可以有同名的成员。 不同结构体的成员名可以相同,不互相冲突。结构体struct date { int year,month,day; }; struct Book { char title[50],writer[20],publisher[50]; int year,month; }; int year,month,day; 492
  • 493. 结构体类型的定义结构体定义说明 结构体的成员可以是基本类型和构造类型(数组和其他结构体)。结构体struct date { int year,month,day; }; struct StuRec { int num; char name[20]; struct date birthday; };493
  • 494. 结构体类型的定义结构体定义说明 结构体不能包含自身的实例。 但可以包含指向自身的指针。结构体struct student { char name[20]; char gender; float scores[4]; struct student next; /*error*/ struct student *nextPtr; /*correct*/ };494
  • 495. 结构体变量结构体定义说明 只是创建了新的数据类型,并不能保留内存空间。 必须定义结构体变量来获得内存空间。 定义声明结构体变量 定义结构体类型后,像声明普通变量一样声明结构体变量。结构体struct date { int year,month,day; }; struct date birth;year month dayFF00 FF02 FF04birthbirth 的存储形式495
  • 496. 结构体变量定义声明结构体变量 在定义结构体类型的同时,声明结构体变量 直接(只)声明结构体变量结构体struct date { int year,month,day; } birth, days[4], *bPtr;struct { int year,month,day; } birth, days[4], *bPtr;没有结构体名,无法再次使用。496
  • 497. 结构体的操作在结构体(变量)上可以执行的操作 将结构体变量赋给相同类型的结构体变量。 得到结构体变量的地址。 访问结构体变量的成员。 使用 sizeof 确定结构体变量的大小。结构体497
  • 498. 结构体变量的初始化初始化结构体变量 给全部成员赋初值。结构体struct StuRec { int num; char name[20]; struct date { int year,month,day; } birthday; float score; } student={101, “WangHai”, 1982, 5, 21, 80};num (2B)name (20B)birthday(6B)score (4B)yearmonthday101WangHai198252180.0498
  • 499. 结构体变量的初始化初始化结构体变量 给部分成员赋初值。结构体struct StuRec { int num; char name[20]; struct date { int year,month,day; } birthday; float score; } student={101, “WangHai”};num (2B)name (20B)birthday(6B)score (4B)yearmonthday101WangHai0000.0499
  • 500. 结构体变量的成员访问结构体成员的两种方式 结构体成员运算符:. 用于结构体变量 结构体指针运算符:-> 用于指向结构体的指针 等价于 (*cardPtr).face结构体struct card myCard; printf(“%s”, myCard.face);struct card *cardPtr; printf(“%s”, cardPtr->face);struct card { char *face; char *suit; };500
  • 501. 案例分析:结构体变量的成员问题:访问结构体变量的成员。 实现(cw1301.c)结构体#include struct card { char *face; char *suit; }; void main() { struct card a, *aPtr; a.face = "Ace"; a.suit = "Spades"; aPtr = &a;与数组的不同: 结构变量名不是指针501
  • 502. 案例分析:结构体变量的成员问题 访问结构体变量的成员。结构体 printf("%s%s%s\n%s%s%s\n%s%s%s\n", a.face, " of ", a.suit, aPtr->face, " of ", aPtr->suit, (*aPtr).face, " of ", (*aPtr).suit); }Ace of Spades Ace of Spades Ace of Spades注意: 结构不能作为整体输入输出。 必须逐个成员输入输出。502
  • 503. 结构体作为函数的参数把结构体的单个成员传递给函数 值传递。 被调用函数不能修改调用函数中的结构体成员。 把整个结构体传递给函数 值传递。 被调用函数不能修改调用函数中的结构体。 把结构体指针传递给函数 模拟引用传递。 被调用函数能修改调用函数中的结构体。结构体503
  • 504. 案例分析:结构体作为函数的参数问题:编写函数实现结构体的复制。 实现(cw1302.c)结构体#include struct date { int year, month, day; }; void show(char *, struct date); void copy(struct date, struct date); void clone(struct date, struct date *); void main() { struct date d1, d2, d3, d4; d1.year = 2004; d1.month = 5; d1.day = 1; show("d1", d1);504
  • 505. 案例分析:结构体作为函数的参数源代码 (续)结构体 d2 = d1; show("d2", d2); copy(d1, d3); show("d3", d3); clone(d1, &d4); show("d4", d4); } void show(char *name, struct date d) { printf("%s: %d-%d-%d\n", name, d.year, d.month, d.day); }505
  • 506. 案例分析:结构体作为函数的参数源代码 (续)结构体void copy(struct date s, struct date d) { d = s; } void clone(struct date s, struct date *dPtr) { *dPtr = s; }d1: 2004-5-1 d2: 2004-5-1 d3: 0-0-24 d4: 2004-5-1506
  • 507. 在函数中使用结构体把整个结构体作为单个数据返回 因为结构体变量之间可以赋值。 按值传递把数组传递给函数 把数组作为结构体的成员,然后把结构体传递给函数。 被调用函数不能修改调用函数中的数组。 结构体507
  • 508. 自定义类型typedef 为已经定义的数据类型创建一个别名。 举例 创建了一个新的类型名 Date ,它是 struct date 的别名。 注意:并没有创建新的类型。 可以简化程序代码,提高程序的可移植性。结构体typedef struct date Date;void show(char *, Date d); void copy(Date s, Date d);typedef int Integer;508
  • 509. 结构体数组结构体数组 数组的元素是结构体变量。 常用结构体来表示记录,那么结构体数组就可以表示一组记录。 案例分析 全班 N 个学生,每个学生有学号、姓名、四门课的成绩。结构体学号姓名成绩1成绩2成绩3成绩4101WangHai80787681102ZhaoFei68667175………………………………130LiRui82768184509
  • 510. 结构体数组结构体数组 案例分析 那么,可以定义结构体数组来保存 N 个学生的数据。 这样,每个学生的数据就对应一个结构体变量(一条记录),便于编程处理。结构体struct student { int num; char name[20]; float scores[4]; }; struct student students[30];510
  • 511. 案例分析:结构体数组高性能的洗牌和发牌程序 数据结构 用一个纸牌结构体数组保存一副牌。 纸牌的花色和号码名依然保存在字符串数组中。 这样,数组中的纸牌俨然已有一个顺序了,则可以改进算法。 洗牌:随机打乱纸牌在数组中的位置。 不存在无限延期。 发牌:按纸牌在数组中的顺序显示输出。 数组遍历一次。结构体511
  • 512. 案例分析:结构体数组高性能的洗牌和发牌程序 实现(cw1303.c)结构体#include #include #include struct card { char *face; char *suit; }; typedef struct card Card; void fillDeck(Card*, char*[], char*[]); void shuffle(Card*); void deal(Card*);512
  • 513. 案例分析:结构体数组高性能的洗牌和发牌程序 实现结构体void main() { Card deck[52]; char *face[] = {"Ace","Deuce","Three", "Four","Five", "Six","Seven","Eight", "Nine","Ten", "Jack","Queen","King"}; char *suit[] = {"Hearts","Diamonds","Clubs","Spades"}; srand(time(NULL)); fillDeck(deck, face, suit); shuffle(deck); deal(deck); }513
  • 514. 案例分析:结构体数组高性能的洗牌和发牌程序 实现结构体void fillDeck(Card *wDeck, char *wFace[], char *wSuit[]) { int i; for (i=0; i<=51; i++) { wDeck[i].face = wFace[i%13]; wDeck[i].suit = wSuit[i/13]; } }514
  • 515. 案例分析:结构体数组高性能的洗牌和发牌程序 实现结构体void shuffle(Card *wDeck) { int i, j; Card temp; for (i=0; i<=51; i++) { j = rand()%52; temp = wDeck[i]; wDeck[i] = wDeck[j]; wDeck[j] = temp; } }515
  • 516. 案例分析:结构体数组高性能的洗牌和发牌程序 实现结构体void deal(Card *wDeck) { int i; for (i=0; i<=51; i++) { printf("%5s of %-8s%c", wDeck[i].face, wDeck[i].suit, (i+1)%2 ? '\t' : '\n'); } }516
  • 517. 案例分析:结构体数组检索 问题: 某班有 n 个学生,每个学生的数据包括学号、姓名、年龄和性别。 要求给定任意一个学号,程序能输出检索的结果,并显示对应的学生的数据。 分析 定义结构体表示学生 用结构体数组保存学生数据 采用顺序查找法结构体输入学生信息输入查询条件查找学生信息,报告结果517
  • 518. 案例分析:结构体数组检索 实现(cw1304.c)结构体#include #define MAX 20 void main() { struct StuRec { int num; char name[20]; char gender; int age; } student[MAX]; int i, N, num;518
  • 519. 案例分析:结构体数组检索 实现结构体 printf("\tInput a integer as the number of students:"); scanf("%d", &N); printf("\tInput %d students' information:\n",N); printf("\n\tNo.\tName\tGender\tAge\n"); for (i=0;i
  • 520. 案例分析:结构体数组检索 实现结构体 printf("\n\tInput a number:"); scanf("%d", &num); printf("\n\tPlease wait. Searching...\n"); for (i=0;i
  • 521. 案例分析:结构体数组检索 问题: 可以多次查找。结构体输入学生信息输入查询条件查找学生信息,报告结果是否继续查询YN人机交互521
  • 522. 案例分析:结构体数组检索 实现(cw1304m.c) 部分代码结构体 do { scanf("%d", &num); for (i=0;iy522
  • 523. 案例分析:结构体数组点票程序 问题 有三个候选人,N个选举人,每次输入一个得票的候选人的名字,要求最后输出各人的得票结果。 定义数据结构结构体struct candidate { char name[20]; /*姓名*/ int count; /*得票数*/ } cand[3];523
  • 524. 案例分析:结构体数组点票程序 算法结构体输入候选人信息输入选票查找得票人信息找到否?得票人的票数增一输出点票结果点完否?YNNY524
  • 525. 案例分析:结构体数组点票程序 实现(cw1305.c)结构体…… do { printf("Vote:\t"); gets(name); for (i=0;i
  • 526. 案例分析:结构体数组改进点票程序 问题: 假设选举人都是候选人 分析点票过程中数组的变化结构体litaolitao wanghai litao zhaofei ……litaowanghailitaowanghaizhaofei526
  • 527. 案例分析:结构体数组改进点票程序 数据结构和算法结构体struct candidate { char name[20]; int count; } cand[M];输入选票查找得票人信息找到否?得票人的票数增一输出点票结果点完否?YNNY加入新的候选人; 其得票数为1。527
  • 528. 案例分析:结构体数组改进点票程序 实现(cw1306.c)结构体。。。 printf("Vote:\t"); gets(name); found=0; for (i=0;i
  • 529. 案例分析:结构体数组增强点票程序 问题 要求按候选人得票数 从高到低顺序输出结果。 修改算法结构体输入选票查找得票人信息找到否?得票人的票数增一输出点票结果点完否?YNNY加入新的候选人; 其得票数赋值“1”。按得票数进行排序529
  • 530. 案例分析:结构体数组增强点票程序 实现(cw1307.c) 排序部分结构体… for (i=0;i
  • 531. 案例分析:结构体数组优化点票程序 问题 如果候选人的信息较多,为了提高排序过程中数据交换的性能,增设一数组order,用来保存排序结果。 分析结构体01234530514220Wanghai M 43 P …12Zhaofei F 41 P …6Lilan F 38 N …35Huangjin M 52 P …9Wuma M 29 N …15Hecheng M 36 P …order的初态order的末态从高到低0 1 2 3 4 5531
  • 532. 案例分析:结构体数组优化点票程序 实现(cw1308.c)结构体 for (i=0;i
  • 533. 案例分析:结构体数组优化点票程序 实现结构体 for (i=0;i
  • 534. 小结结构体结构体534
  • 535. 第十五讲 编译预处理
  • 536. 第十五讲 编译预处理编译预处理 文件包含 宏定义 无参 有参 条件编译 程序设计举例C语言程序设计536
  • 537. 简介预处理(preprocessing) 在实施代码转换之前进行。 包括: 包含其它文件 定义宏 有条件的编译程序代码 预处理命令(preprocessor directive)的格式 以#开头的一行(可以占多行) #前面只能出现空白符编译预处理537
  • 538. 文件包含#include 把所包含的文件的内容放到指令所在的地方。 两种形式: #include 只在指定存放头文件的目录下(IDE的include子目录下)查找该文件。 #include "filename“ 首先在使用文件包含命令的源文件所在目录下查找该文件; 若未找到,再到指定存放头文件的目录下去查找。 好处: 当许多程序中需要用到一些共同的常量、数据等资料时,可以把这些共同的东西写在以.h作为扩展名的头文件中; 如果哪个程序需要用时,就可用文件包含命令把它们包含进来,省去了重复定义的麻烦。编译预处理538
  • 539. 文件包含#include 使用说明: 一个#include命令只能指定一个被包含文件,若有多个文件要包含,则需要用多个#include命令。 文件包含允许嵌套,即在一个被包含文件中又可以包含另一个文件。 文件包含命令通常包含的文件是头文件,即后缀是.h的文件,也可以包含其他的源文件,例如,可包含.c文件。 使用文件包含命令可使多个源文件合并成一个源程序后进行编译和运行。编译预处理539
  • 540. 宏定义#define 用一个标识符来表示一个字符串,称为“宏”。 被定义为“宏”的标识符称为“宏名”。 在编译预处理时,对程序中所有出现的“宏名”,都用宏定义中的字符串去代换,这称为“宏代换”或“宏展开”。 包括: 不带参数的宏定义 带参数的宏定义编译预处理540
  • 541. 宏定义无参数的宏定义 格式: #define <宏名> <字符串> 举例: 说明: 宏定义一般写在程序的开头。 宏名的命名规则同变量名,一般习惯用大写字母,以便与变量区别,但也允许用小写字母。宏名的前后应有空格,以便准确地辨认宏名。 宏定义必须写在函数之外,宏名的有效范围是从宏定义开始到本源程序文件结束,或遇到预处理命令#undef时止。编译预处理#define PI 3.14541
  • 542. 宏定义无参数的宏定义 说明: 宏定义不但可以定义常量,还可以定义C语句和表达式等。 宏定义允许嵌套。 宏代换只是指定字符串替换宏名的简单替换,不做任何语法检查。如有错误,只能在编译已被宏展开后的源程序时发现。 宏定义是专门用于预处理命令的一个专用名词,只作字符替换,不分配内存空间。 当宏定义在一行中写不下,需要在下一行继续时,只需在最后一个字符后紧接着加一个反斜杠“\”。 可用宏定义表示数据类型,使书写方便。编译预处理542
  • 543. 宏定义有参数的宏定义 格式: #define 宏名(形参表) 字符串 举例: 说明: 实参个数与形参个数相同,但没有类型要求。 宏名和形参表之间不能有空格出现。 如宏定义包含“##”,则宏替换时将“##”去掉,并将其前后字符串合在一起。编译预处理#define M(a,b) a*b543
  • 544. 条件编译条件编译 目的和作用 条件编译是指对源程序中某段程序通过条件来控制是否参加编译。 根据条件来选取需要的代码进行编译,以便生成不同的应用程序,供不同用户使用。 此外,条件编译还可以方便程序的逐段调试,简化程序调试工作。 指令: #if #ifdef #ifndef #else #endif编译预处理544
  • 545. 小结编译预处理功能是C语言特有的功能,它是在对源程序正式编译前由预处理程序完成的。程序员在程序中用预处理命令来调用这些功能。预处理命令若有变动,必须对源程序重新编译和连接。 使用预处理功能便于程序修改、阅读、移植和调试,便于实现模块化程序设计。编译预处理545
  • 546. 第十六讲 文件
  • 547. 第十六讲 文件文件的概念 文件指针 文件的打开与关闭 文件的读写 文件的定位 程序设计举例C语言程序设计547
  • 548. 简介文件 存储在外存储器上的数据集合。文件内存硬盘写 读保存在变量和数组中的数据是暂时的大量的数据保存到文件实现永久存储548
  • 549. 简介数据的层次结构 文件001000111001001111010110011011100100100100111101101011101001011WangHai21M788983LiFei21F768885ZhaoXin21M818679010111001………… ………… ……文件字段字节位记录549
  • 550. 简介C语言的文件处理 C语言把文件看作是一个有序的字节或字符流。文件550
  • 551. 简介文件的分类(按存储形式分) 文本文件 数据被作为字符,并以字符的二进制编码形式保存。 举例文件1234‘1’‘2’‘3’‘4’整数把4个字符的二进制编码写入文件中00110001 00110010 00110011 00110100551
  • 552. 简介文件的分类(按存储形式分) 二进制文件 以数据在内存中的二进制形式保存。 举例文件1234整数00000100 11010010把值1234的二进制编码写入文件中552
  • 553. 简介文件的读写 缓冲文件系统 系统在内存中开辟一块缓冲区以便慢速的外设与其成块地进行数据交换。 向磁盘(文件)输出数据: 数据→缓冲区,装满缓冲区后→磁盘文件。 从磁盘(文件)读入数据: 先一次性从磁盘文件将一批数据输入到缓冲区, 然后再从缓冲区逐个数据读入到变量。文件553
  • 554. 文件指针文件指针(指向文件的指针) 缓冲文件系统为每个使用的文件在内存中开辟一存储区存放其相关信息,这些信息用一个结构体变量保存,该结构体类型的名字是 FILE。 程序通过 FILE类型指针建立和文件的联系,进行关于文件的一切操作。 定义形式: FILE *指针变量名; 举例: 定义了一个结构体FILE类型的指针变量:fp。文件用于存放文件的缓冲区首地址,即指向一个文件。FILE *fp;554
  • 555. 文件的打开打开文件 方法: 为了打开一个文件,首先要为该文件定义一个文件类型指针; 然后用C语言提供的fopen()函数打开文件。 fopen函数有两个参数: “文件名”与“文件使用方式”。 它们均是字符串。 一般形式:文件FILE *文件指针名; … 文件指针名=fopen("文件名","文件使用方式"); 555
  • 556. 文件的打开打开文件 文件使用方式 文件符号意义r(read)以只读方式打开一个文本文件,若文件不存在,则返回空指针w(write)以只写方式打开或创建一个文本文件,并覆盖原有数据a(append)以追加方式打开或创建一个文本文件,并从文件末尾写数据t(text)文本文件,可省略不写b(binary)二进制文件+读和写556
  • 557. 文件的打开打开文件 举例 其意义是在当前目录下打开文件filea.dat,文件的使用方式为“只读”,并使fp指向该文件。 举例 其意义是打开c盘根目录下的文件fileabc.dat,这是一个二进制文件,只允许按二进制方式进行读操作。 两个反斜线“\\”中的第一个表示转义字符,第二个表示根目录。 文件FILE *fp; fp = fopen("filea.dat", "r"); FILE *fpabc; fpabc = fopen(“c:\\fileabc.dat", "rb"); 557
  • 558. 文件的关闭关闭文件 使用fclose()函数。 一般形式: fclose(文件指针); 举例 将fp指向的缓冲区中的数据存放到外存储器的文件中,然后释放该缓冲区,使文件指针变量不再指向与该文件对应的FILE结构,从而断开与文件的关联。 操作正常返回0,否则返回EOF(是在stdio.h文件中定义的符号常量,值为-1)。文件FILE *fp; … fclose(fp); 如果不关闭文件,将会丢失数据,并且一个C程序能同时打开的文件数有限,应该养成在使用完文件后关闭文件的习惯。558
  • 559. 文件操作文件操作的一般步骤 文件/* 定义声明文件指针 */ FILE *fp; /* 打开文件 */ fp = fopen("文件名", "r"); /* 判断是否打开文件 */ if (fp == NULL) { /* 当文件打开失败时的处理 */ } /* 对文件进行读写操作 */ /* 关闭文件 */ fclose(fp);559
  • 560. 文件的读写C语言支持两种读写文件的方式 顺序读写 从文件中一个接一个地读写数据,仅当读写了前一个数据后,才能读写下一个数据。 在读写数据后,文件位置指针自动跳到下一个读写位置。 标准输入输出只能进行顺序读写。 随机读写 从文件中读写数据时可不考虑数据在文件中的位置,可以在文件的任何位置读写。 在读写数据前,需要将文件位置指针移到读写位置。 当文件以读或写方式打开,文件位置指针设置在文件头,若文件以附加方式打开,位置设置在文件末尾。文件560
  • 561. 字符的读写读字符 fgetc()函数 从指定文件读入一个字符,该文件必须是以读或读写方式打开。 返回值: 调用成功时返回读入的字符; 文件结束时返回文件结束符EOF。 举例 表示从文件指针变量fp指向的文件中读出一个字符,赋给字符变量ch文件ch = fgetc(fp);561
  • 562. 字符的读写文件位置指针 文件型数据结构中,用文件位置指针指向当前对文件进行读写操作的位置。 在文件打开时,该指针总是指向文件的第一个字节。读写文件时,每读写一个字符后,该位置指针的值会自动加1,指向下一个字符。 改变位置指针的值,也就改变了下一次读写操作在文件中执行时的位置。文件文件位置指针562
  • 563. 字符的读写文件结束 问题 使用fgetc()函数,文件结束时返回文件结束符EOF,其值为-1。这在对文本文件操作时不会产生问题。但对二进制文件进行读操作时,由于-1是二进制数的合法值,故将影响文件数据的读取。 为解决此问题,ANSI C提供了专门判断文件结束的函数feof()。 feof()函数 feof(文件指针); 判断文件位置指针是否已至文件尾 返回值: 函数返回值为1时表示已至文件尾部; 为0时则还未到文件结束处。文件563
  • 564. 字符的读写写字符 fputc()函数 fputc(字符量,文件指针); 将一个字符写到磁盘文件上去 返回值: 调用成功时返回输出的字符; 失败时返回EOF。 举例 表示向文件指针变量fp指向的文件写入一个字符’a’。文件fputc('a', fp);564
  • 565. 案例分析:字符的读写问题 打开“c:\infile.c”文件,然后将其复制到“c:\outfile.c”。 源代码文件#include "stdio.h" #include "stdlib.h" void main() { FILE *infp,*outfp; char ch; if ((infp = fopen("c:\\infile.c", "r")) == NULL) { printf("Cannot open infile.c.\n"); exit(0); }565
  • 566. 案例分析:字符的读写源代码文件 if ((outfp = fopen("c:\\outfile.c", "w")) == NULL) { printf("Cannot open outfile.c.\n"); exit(0); } while(!feof(infp)) if ((ch = fgetc(infp)) != EOF)    fputc(ch,outfp); fclose(infp); fclose(outfp); }566
  • 567. 字符串的读写读字符串 fgets()函数 fgets(字符数组名, n, 文件指针); 从指定文件读入n-1个字符到字符数组,最后加一个‘\0’结束符。在读完n-1个字符之前遇到换行符或文件结束符EOF,读入即结束。 返回值:字符数组的地址。 举例 从fp所指的文件中读出n-1个字符送入字符数组str中。文件fgets(str, n, fp);567
  • 568. 字符串的读写写字符串 fputs()函数 fputs(字符数组名, 文件指针); 向指定的文件输出一个字符串,‘\0’结束符不输出。其中字符串可以是字符串常量、字符数组名或指针变量。 返回值: 调用成功时,返回0; 否则返回EOF。 举例 其意义是把字符串“abcd”写入fp所指的文件之中。文件fputs(“abcd”, fp);568
  • 569. 案例分析:字符串的读写问题 从键盘上输入一串字符,写入文本文件“abc.c”中,再将文本文件的内容读出,显示在屏幕上。 源代码文件#include "stdio.h" #include "stdlib.h" void main() { FILE *fp; char str[100], ch; if((fp = fopen("c:\\abc.c", "w")) == NULL)  {  printf("file open error.\n"); exit(0);  }569
  • 570. 案例分析:字符串的读写源代码文件  printf("请输入一串字符:\n");   gets(str);   fputs(str,fp);   fclose(fp);     if((fp=fopen("c:\\abc.c", "r")) == NULL)   {  printf("file open error.\n");   exit(0);   }   while(!feof(fp))   if ((ch = fgetc(fp)) != EOF) putchar(ch);   fclose(fp); }570
  • 571. 格式化读写格式化读 fscanf()函数 fscanf(文件指针, ”格式控制字符串”, 地址表列); 从指定的文件中格式化读数据。 这个函数与格式输入函数scanf( )很相似,它们的区别在于,scanf( )函数是从键盘输入数据,而fscanf( )函数是从文件读入数据,因此在fscanf( )函数参数中多了一个文件指针,用于指出从哪个文件读入数据。 举例 表示从磁盘文件中的读取一个浮点数和一个整数,分别存入变量f和d。文件fscanf(fp, “%f%d”, &f, &d);571
  • 572. 格式化读写格式化写 fprintf()函数 fprintf(文件指针, ”格式控制字符串”, 输出表列); 格式化写数据到指定的文件中。 这个函数与printf( )函数很相似,它们的区别在于,printf()函数是将数据输出到屏幕上,而fprintf()函数是将数据输出到文件中,因此在fprintf()函数参数中多了一个文件指针,用于指出将数据输出到哪个文件中。 举例 表示把变量f和d按指定的格式写入磁盘文件。文件fprintf(fp, “f=%f, d=%d\n”, f, d);572
  • 573. 数据块的读写以数据块方式读写文件 fscanf()函数和fprintf()函数对磁盘文件读写,使用方便,易理解。但输入输出需要转化。 因此,在内存与磁盘频繁交换数据的情况下,最好用fread()和fwrite()函数。 fread(内存地址,数据项字节数,数据项个数,文件指针); fwrite(内存地址,数据项字节数,数据项个数,文件指针); 通常用于对二进制文件的读写操作。 举例 表示从已打开的一个文件中读入5个整数,依次送入整型数组a中。文件int a[5]; fread(a, sizeof(int), 5, fp);573
  • 574. 文件的随机读写文件内部的位置 按要求移动位置指针,是实现随机读写的关键。 rewind()函数 rewind(文件指针); 使位置指针重新返回到文件的开头,函数无返回值。 ftell()函数 ftell(); 得到当前文件指针位置。 返回值: 调用成功时,返回long int 数值,表示文件指针位置; 出错返回-1L。文件574
  • 575. 文件的随机读写文件内部的位置 fseek()函数 rewind(文件指针, 位移量, 起始点); 改变文件的位置指针。 其中, 文件指针:是文件打开时返回的文件指针。 位移量:是指以起始点为基点,向前向后移动的字节数,可以为负值。大多数C语言版本要求位移量为long型数。 起始点:表示从何处开始计算位移量,规定的起始点有: 返回值:操作成功返回0,否则返回非0。文件起始点名字数字代号文件开始SEEK_SET 0 当前位置SEEK_CUR1文件末尾SEEK_END2575
  • 576. 案例分析:文件的随机读写问题 设文件“alphabet.c”中存放了字母表“A┅Z”,现在打开这个文件,用从尾部倒着读的方式将其信息读出并送屏幕显示。 源代码 文件#include #include void main() { FILE *fp; long i; if((fp=fopen(“c:\\alphabet.c”, “rb”))== NULL) { printf(“file open error.\n”); exit(0); }576
  • 577. 案例分析:文件的随机读写源代码 文件 for(i=1;i<=26;i++) { fseek(fp,-i,SEEK_END); /* i=1时,定位于字母Z */ putchar(fgetc(fp)); /* 显示读出的字符,位置指针+1 */ } fclose(fp); }577
  • 578. 案例分析问题 建立一个文件,向其中写入一组学生的姓名和成绩,然后从该文件中读出成绩大于80分的学生信息,并显示在屏幕上。 分析 先用fopen()函数建立文件,再用fwrite()函数写入学生记录。 用rewind()函数定位于文件开头,用fread()函数从文件中顺序读出一个个记录,并判断成绩是否大于80分。若是,则输出。 在读记录之前,要用fseek()函数定位在正确的位置上。文件578
  • 579. 案例分析源代码 文件#include #include void main( ) { FILE *fp; int i; struct student { char name[10]; int score; } s, stud[]={{“张雄”,90}, {“李平”,72}, {“孙兵”,80}, {“刘军”,88}, {“王伟”,92}};579
  • 580. 案例分析源代码 文件 if((fp=fopen(“stud.bin”,”wb+”))==NULL) { printf(“不能建立文件stud.bin\n”); exit(0); } for(i=0;i<5;i++) if(fwrite(&stud[i],sizeof(struct student),1,fp)!=1) printf(“写文件错误”);580
  • 581. 案例分析源代码 文件 rewind(fp); printf(“姓名 成绩\n”); printf(“----------------\n”); for(i=0;i<5;i++) { fseek(fp,i*sizeof(struct student),SEEK_SET); fread(&s, sizeof(struct student),1,fp); if (s.score>80) printf(“%s %d\n”,s.name,s.score); } fclose(fp); }581
  • 582. 小结文件文件582
  • 583. 第十七讲 高级数据结构
  • 584. 第十七讲 高级数据结构动态数据结构 自引用的结构体 动态内存分配 链表 程序设计举例C语言程序设计584
  • 585. 动态数据结构Dynamic data structures(动态数据结构) 在程序的执行过程中可以增长和缩短的数据结构。 Linked lists(链表) 在任何地方进行插入和删除。 Stacks(栈) 在栈的顶端进行插入和删除。 Queues(对列) 在队列的末尾删除,在队列的前面插入。高级数据结构585
  • 586. 自引用的结构体自引用的结构体 包含指向相同结构类型的指针成员 可以链接到一起,构成有用的数据结构: 链表 栈 队列 树高级数据结构struct node { int data; struct node *nextPtr; };2042数据成员指针结点(node)空指针链头指针586
  • 587. 动态内存分配动态内存分配 在程序执行期间获取或释放内存 相关的函数(stdlib.h) malloc() free() 相关的运算符 sizeof高级数据结构587
  • 588. 动态内存分配malloc()函数 void* malloc(size_t size) 参数size :表示要分配的内存的字节数 size_t为sizeof返回的整数类型 一般用 sizeof 运算符确定对象的大小 返回值:指向分配内存的 void* 类型的指针 void* 类型的指针可以被赋值给任何类型的指针变量 如果没有可用内存,则返回 NULL高级数据结构int *array, n=30; array = (int*)malloc(n*sizeof(int));struct node *sPtr; sPtr = malloc(sizeof(struct node));588
  • 589. 动态内存分配free()函数 void free(void* aPtr) 释放aPtr指向的内存空间 aPtr指向的应该是用 malloc 分配的内存空间 如果不调用free释放array指向的空间,则程序结束后array消失了,但其所指的该段内存仍旧存在且不能被使用(其地址丢失了),即出现内存泄露。高级数据结构int *array, n=30; array = (int*)malloc(n*sizeof(int)); … free(array);589
  • 590. 链表链表 用指针链接的自引用结构的线性集合,这些结构称为结点。 通过指向链表的第一个结点的指针访问链表。 后续的结点通过存储在每个结点中的链接指针成员来访问。 链表中最后一个结点的链接指针被设置为NULL,以标志链表的结尾。 什么时候用链表替代数组 数据成员的数目无法事先确定。 对数据进行快速的排序。高级数据结构203542590
  • 591. 案例分析:链表链表 问题 输入任意个整数创建链表。 实现常用的操作:遍历,插入,删除,空表判断。 实现(cw1311.c)高级数据结构#include #include struct listNode { int data; struct listNode *nextPtr; }; typedef struct listNode ListNode; typedef ListNode* ListNodePtr;591
  • 592. 案例分析:链表链表 实现高级数据结构void insert(ListNodePtr*, int); int delete(ListNodePtr*, int); int isEmpty(ListNodePtr); void printList(ListNodePtr); void menu(void);592
  • 593. 案例分析:链表链表 实现高级数据结构void menu(void) { printf("Enter your choice:\n" "1 to insert an element into the list.\n" "2 to delete an element from the list.\n" "3 to ent.\n"); }593
  • 594. 案例分析:链表链表 实现高级数据结构void main() { ListNodePtr listPtr = NULL; int choice; int item; menu(); printf("? "); scanf("%d", &choice);594
  • 595. 案例分析:链表链表 实现高级数据结构 while (choice != 3) { switch (choice) { case 1: printf("Enter an integer: "); scanf("%d", &item); insert(&listPtr, item); printList(listPtr); break;595
  • 596. 案例分析:链表链表 实现高级数据结构 case 2: if (!isEmpty(listPtr)) { printf("Enter an integer to be deleted: "); scanf("%d", &item); if (delete(&listPtr, item)) { printf("%d deleted.\n", item); printList(listPtr); } else printf("%d not found.\n", item); } else printf("List is Empty!\n"); break;596
  • 597. 案例分析:链表链表 实现高级数据结构 default: printf("Invalid choice.\n\n"); menu(); break; } printf("? "); scanf("%d", &choice); } printf("Bye-bye!\n"); }597
  • 598. 案例分析:链表链表 插入结点的函数高级数据结构2042listPtr35newPtrprePtrcurPtr598
  • 599. 案例分析:链表链表 插入结点的函数高级数据结构void insert(ListNodePtr *sPtr, int data) { ListNodePtr newPtr, prePtr, curPtr; newPtr = malloc(sizeof(ListNode)); if (newPtr != NULL) { newPtr->data = data; newPtr->nextPtr = NULL; prePtr = NULL; curPtr = *sPtr; while (curPtr != NULL && data > curPtr->data) { prePtr = curPtr; curPtr = curPtr->nextPtr; }599
  • 600. 案例分析:链表链表 插入结点的函数高级数据结构 if (prePtr == NULL) { newPtr->nextPtr = curPtr; *sPtr = newPtr; } else { prePtr->nextPtr = newPtr; newPtr->nextPtr = curPtr; } } else printf("%d not inserted. No memory available.\n", data); }如果新结点是第一个节点,则必须调整链表的头指针600
  • 601. 案例分析:链表链表 删除结点的函数高级数据结构tempPtrprePtrcurPtr2042listPtr35601
  • 602. 案例分析:链表链表 删除结点的函数高级数据结构int delete(ListNodePtr *sPtr, int data) { ListNodePtr prePtr, curPtr, tempPtr; if (data == (*sPtr)->data) { tempPtr = *sPtr; *sPtr = (*sPtr)->nextPtr; free(tempPtr); return 1; } else { prePtr = *sPtr; curPtr = (*sPtr)->nextPtr;如果删除的是第一个节点,必须调整链表的头指针602
  • 603. 案例分析:链表链表 删除结点的函数高级数据结构 while (curPtr != NULL && curPtr->data != data) { prePtr = curPtr; curPtr = curPtr->nextPtr; } if (curPtr != NULL) { tempPtr = curPtr; prePtr->nextPtr = curPtr->nextPtr; free(tempPtr); return 1; } } return 0; }603
  • 604. 案例分析:链表链表 判空和输出链表的函数高级数据结构int isEmpty(ListNodePtr listPtr) { return listPtr == NULL; } void printList(ListNodePtr curPtr) { if (curPtr == NULL) printf("List is Empty.\n\n"); else { printf("The list is:\n"); while (curPtr != NULL) { printf("%d --> ", curPtr->data); curPtr = curPtr->nextPtr; } printf("NULL\n\n"); } }604
  • 605. 小结动态数据结构 自引用的结构体 动态内存分配 链表高级数据结构605