导图社区 C语言程序设计
C语言程序设计:包含第1章 程序设计和C语言,程序就是一组计算机能识别和执行的指令,计算机的一切操作都是由程序控制的,C语言的发展及其特点等等
编辑于2022-05-25 17:24:12C语言程序设计
第1章 程序设计和C语言
一、计算机程序
1 程序就是一组计算机能识别和执行的指令
2 计算机的一切操作都是由程序控制的
二、计算机语言
1 定义 人和计算机交流信息,要解决语言问题。需要创造一种计算机和人都能识别的语 言,这就是计算机语言
1 定义 人和计算机交流信息,要解决语言问题。需要创造一种计算机和人都能识别的语 言,这就是计算机语言
三、C语言的发展及其特点
1 概述 (1)C语言是国际上广泛流行的计算机高级语言。 (2)C语言的祖先是BCPL语言。
2 发展 最初的C语言只是为描述和实现UNIX操作系统提供一种工作语言而设计的,后来 逐步发展为一种高级编程语言,目前C语言使用的标准是1999年修正的C99。
3 特点 (1)语言简洁、紧凑,使用方便、灵活 (2)运算符丰富 (3)数据类型丰富 (4)具有结构化的控制语句 (5)语法限制不太严格,程序设计自由度大 (6)C语言允许直接访问物理地址 (7)用C语言编写的程序可移植性好 (8)生成目标代码质量高,程序执行效率高
四、简单的C语言程序
(1)一个程序由一个或多个源程序文件组成 在一个源程序文件中可以包括3个部分: ①预处理指令 ②全局声明 ③函数定义
(2)函数是C程序的主要组成部分 ①函数是C程序的基本单位。②一个C语言程序是由一个或多个函数组成的,有且仅有一个main函数。 ③一个小程序只包含一个源程序文件。 ④函数既可以是库函数,也可以是用户自定义函数。
(3)函数的两个部分 ①函数首部 函数首部包括函数名、函数类型、函数属性、函数参数(形式参数)名、参数类 型。②函数体 即函数首部下面的花括号内的部分。函数体一般包括两部分:声明部分和执行部 分。
(4)程序总是从main函数开始执行的
(5)程序中对计算机的操作是由函数中的C语句完成的
(6)在每个数据声明和语句的最后必须有一个分号
(7)C语言本身不提供输入输出语句
(8)程序应当包含注释
五、运行C程序的步骤与方法
C程序运行步骤图如图1-1所示。其中实线表示操作流程,虚线表示文件的输入输 出
六、程序设计的任务
程序设计是指从确定任务到得到结果、写出文档的全过程。从确定问题到最后完 成任务,一般经历以下几个工作阶段
1.问题分析 2.设计算法 3.编写程序 4.对源程序进行编辑、编译和连接,得到可执行程序 5.运行程序,分析结果 6.编写程序文档
第2章 算法——程序的灵魂
一、程序=算法+数据结构
一个程序主要包括以下两方面的信息:
(1)对数据的描述,即数据结构(data structure)。
(2)对操作的描述,即算法(algorithm)。
二、算法概述
1 算法定义
广义地说,为解决一个问题而采取的方法和步骤,就称为“算法
2 算法分类
(1)数值运算算法,数值运算的目的是求数值解。
(2)非数值运算算法。
三、算法的特性
1 有穷性
一个算法应包含有限的操作步骤,而不能是无限的。
2 确定性
算法中的每一个步骤都应当是确定的,而不应当是含糊的、模棱两可的。
3 有零个或多个输入
输入是指在执行算法时需要从外界取得必要的信息。
4 有一个或多个输出
算法的目的是为了求解,“解”是指输出。
5 有效性
算法中的每一个步骤都应当能有效地执行,并得到确定的结果。
四、怎样表示一个算法
1 用自然语言表示算法
自然语言是指人们日常使用的语言,其特点是通俗易懂,但语义语法不严格,描 述能力不足。
2 用流程图表示算法
2 用流程图表示算法
(1)概述
美国国家标准化协会ANSI规定了一些常用的流程图符号(如图2-1所示),已为世 界各国程序工作者普遍采用。
(2)流程图元素
①菱形框
菱形框的作用是对一个给定的条件进行判断,根据给定的条件是否成立决定如何 执行其后的操作。它有一个入口,两个出口,如图2-2所示。
②连接点
连接点(小圆圈)是用于将画在不同地方的流程线连接起来,如图2-3所示。
③注释框
注释框不是流程图中必要的部分,不反映流程和操作,只是为了对流程图中某些 框的操作作必要的补充说明,以帮助阅读流程图的人更好地理解流程图的作用。
(3)流程图组成
①表示相应操作的框;
②带箭头的流程线;
③框内外必要的文字说明。
3 三种基本结构和改进的流程图
(1)传统流程图的弊端
传统的流程图用流程线指出各框的执行顺序,对流程线的使用没有严格限制。
(2)三种基本结构
①顺序结构
如图2-4所示,虚线框内是一个顺序结构。其中A和B两个框是顺序执行的。顺序结 构是最简单的一种基本结构。
②选择结构
选择结构又称选取结构或分支结构,如图2-5所示。虚线框内是一个选择结构,此 结构中必包含一个判断框,根据给定的条件P是否成立而选择执行A框或B框。
③循环结构
又称重复结构,即反复执行某一部分的操作,有两类循环结构
(3)三种结构共同特点
①只有一个入口;
②只有一个出口;
③结构内的每一部分都有机会被执行到;
④结构内不存在“死循环”。
基本结构并不一定只限于上面3种,只要具有上述4个特点的都可以作为基本结 构。
do…while循环结构
的switch选择结构
4 用N-S流程图表示算法
(1)概述
在这种流程图中,完全去掉了带箭头的流程线。全部算法写在一个矩形框内,在 该框内还可以包含其他从属于它的框,或者说,由一些基本的框组成一个大的 框,这种流程图又称N-S结构化流程图。
(2)N-S流程图符号
①顺序结构
顺序结构用图2-9形式表示。A和B两个框组成一个顺序结构。
②选择结构
选择结构用图2-10表示,其中p为判断条件。
③循环结构
当型循环结构用图2-11形式表示,当p1条件成立时反复执行A操作,直到p1条件不 成立为止。
直到型循环结构用图2-12形式表示,先执行A操作,然后判断p1条件是否成立,如 果p1不成立,反复执行A,只当p1条件成立才停止循环
5 用伪代码表示算法
(1)概述
伪代码是用介于自然语言和计算机语言之间的文字和符号来描述算法。
(2)特点
①伪代码书写格式比较自由,容易表达出设计者的思想;
②用伪代码写的算法很容易修改;
③用伪代码很容易写出结构化的算法。
6 用计算机语言表示算法
用流程图或伪代码描述一个算法后,还要将它转换成计算机语言程序。用计算机 语言表示的算法是计算机能够执行的算法,其必须严格遵循所用语言的语法规 则
五、结构化程序设计方法
1 基本思路
结构化程序设计方法的基本思路是:把一个复杂问题的求解过程分阶段进行,每 个阶段处理的问题都控制在人们容易理解和处理的范围内。
2 结构化程序设计方法
(1)自顶向下
(2)逐步细化
(3)模块化设计
(4)结构化编码
第3章 最简单的C程序设计——顺序程序设计
一、数据的表现形式及其运算
1 常量和变量
(1)常量
在程序运行过程中,其值不能被改变的量称为常量,数值常量就是数学中的常 数。
常用的常量有以下几类:
①整型常量;
②实型常量,包括十进制小数形式和指数形式;
③字符常量,包括普通字符和转义字符,常见的转义符有换行符'\n'和制表符'\t';
④字符串常量,字符串常量是双撇号中的全部字符;
⑤符号常量,用#define指令,指定用一个符号名称代表一个常量。
(2)变量
变量代表一个有名字的、具有特定属性的一个存储单元,变量必须先定义,后使 用。
(3)常变量
常变量是有名字的不变量,而且在变量存在期间其值不能改变。
(4)标识符
①定义
在计算机高级语言中,用来对变量、符号常量名、函数、数组、类型等命名的有 效字符序列统称为标识符(identifier)。
②组成规则
a.C语言规定标识符只能由字母、数字和下划线3种字符组成,且第1个字符必须 为字母或下划线。
b.编译系统将大写字母和小写字母认为是两个不同的字符。
2 数据类型
C语言允许使用的类型,如图3-1所示,图中有*的是C99所增加的。
3 整型数据
整型数据常见的存储空间和值的范围如表3-1所示。
4 字符型数据
(1)字符与字符代码
常用的字符与字符代码如表3-2所示。
表3-2 常用的字符与字符代码
(2)字符变量
字符变量是用类型符char定义字符变量。字符类型型也属于整型,也可以用signed 和unsigned修饰符。字符型数据的存储空间和值的范围见表3-3
5 浮点型数据
浮点型数据的有关情况
6 怎样确定常量的类型
从常量的表示形式即可以判定其类型。对于字符常量很简单,只要看到由单撇号 括起来的单个字符或转义字符就是字符常量。对于数值常量按以下规律:
(1)整型常量
不带小数点的数值是整型常量,但应注意其有效范围。
(2)浮点型常量
凡以小数形式或指数形式出现的实数,是浮点型常量,在内存中都以指数形式存 储。
二、运算符和表达式
1 C运算符
除了算术运算符外,C还提供其他运算符,共有以下类别: ①算术运算符(+ - * / % ++ --) ②关系运算符(> < == >= <= !=) ③逻辑运算符(! && ||) ④位运算符(<< >> ~ | ∧ &) ⑤赋值运算符(=及其扩展赋值运算符) ⑥条件运算符(?:) ⑦逗号运算符(,) ⑧指针运算符(*和&) ⑨求字节数运算符(sizeof) ⑩强制类型转换运算符((类型)) ⑪成员运算符(. ->) ⑫下标运算符([]) ⑬其他(如函数调用运算符())
2 基本的算术运算符
3 自增、自减运算符
++i,--i:在使用i之前,先使i的值加(减)1; i++,i--:在使用i之后,使i的值加(减)1。
4 算术表达式和运算符的优先级与结合性
C算术表达式是指将运算对象通过算术运算符和括号连接起来的、符合C语法规则 的表达式。其中算术运算符是自左至右,赋值运算符是自右至左的。
5 不同类型数据间的混合运算
(1)int型、float与double型数据进行运算,则全部转化为double类型;
(2)字符(char)型数据与整型数据进行运算,就是把字符的ASCII代码与整型数 据进行运算。
6 强制类型转换运算符
(1)系统自动进行的类型转换;
(2)强制类型转换。
三、C语句
1 C语句的结构和分类
(1)C程序结构可以用图3-2表示。
(2)C语句分为以下5类:
①控制语句
控制语句用于完成一定的控制功能。C只有9种控制语句,它们的形式是:
a.if()…else… (条件语句)
b.for()… (循环语句)
c.while()… (循环语句)
d.do…while() (循环语句)
e.continue (结束本次循环语句)
f.break (中止执行switch或循环语句)
g.switch (多分支选择语句)
h.return (从函数返回语句)
i.goto (转向语句,在结构化程序中基本不用goto语句)
②函数调用语句
函数调用语句由一个函数调用加一个分号构成。
③表达式语句
表达式语句由一个表达式加一个分号构成。
④空语句
⑤复合语句
2 最基本的语句——赋值语句
(1)赋值运算符
赋值符号“=”是指赋值运算符,它的作用是将一个数据赋给一个变量。
(2)复合的赋值运算符
有关算术运算的复合赋值运算符有+=,-=,*=,/=,%=。
(3)赋值表达式
赋值运算符左侧应该是一个可修改的“左值”(leftvalue,简写为lvalue),能出现在 赋值运算符右侧的表达式称为“右值”(rightvalue,简写为rvalue)。
(4)赋值过程中的类型转换
如果赋值运算符两侧的类型不一致,但都是算术类型时,在赋值时要进行类型转 换
(5)变量赋初值
可以用赋值语句对变量赋值,也可以在定义变量时对变量赋以初值。
四、数据的输入输出
1 有关数据输入输出的概念
(1)输入输出是以计算机主机为主体
计算机的输入输出如图3-3所示。
计算机的输入输出如图3-3所示。
(2)输入和输出操作是由C标准函数库中的函数来实现的
(3)在使用系统库函数时,要在程序文件的开头用预处理指令#include<stdio.h>
2 用printf函数输出数据
(1)printf函数的一般格式
printf函数的一般格式为:printf(格式控制,输出表列);
括号内包括两部分
①格式控制
a.格式声明
格式声明总是由“%”字符开始的。
b.普通字符
②输出表列
输出表列是程序需要输出的一些数据,可以是常量、变量或表达式。
(2)格式字符
对不同类型的数据要指定不同的格式声明,而格式声明中最重要的内容是格式字 符,常用的有如表3-6所示。
表3-6 printf函数中用到的格式字符
在格式声明中,在%和上述格式字符间可以插入表3-7中列出的几种附加符号(又 称修饰符)。
表3-7 printf函数中用到的格式附加字符
【说明】
格式附加字符如“printf("%5.2f",f);”即表示输出的f为浮点型数据,其中应保 留两位小数,总宽度为5。
3 用scanf函数输入数据
(1)scanf函数的一般形式
scanf(格式控制,地址表列);
①“格式控制”的含义同printf函数。
②“地址表列”是由若干个地址组成的表列,可以是变量的地址,或字符串的首地 址。
(2)scanf函数中的格式声明
与printf函数中的格式声明相似,以%开始,以一个格式字符结束,中间可以插入 附加的字符。
表3-8和表3-9列出scanf函数所用的格式字符和附加字符。它们的用法和printf函数 中的用法差不多。
表3-8 scanf函数中所用到的格式字符
表3-9 scanf函数中用到的格式附加字符
(3)使用scanf函数时应注意的问题
①scanf函数中的“格式控制”后面应当是变量地址,而不是变量名。
②如果在“格式控制字符串”中除了格式声明以外还有其他字符,则在输入数据时 在对应的位置上应输入与这些字符相同的字符。
③在用“%c”格式声明输入字符时,空格字符和“转义字符”中的字符都作为有效字 符输入。
4 字符输入输出函数
除了可以用printf函数和scanf函数输出和输入字符外,C函数库还提供了一些专门 用于输入和输出字符的函数。
(1)用putchar函数输出一个字符 putchar(c);
(2)用getchar函数输入一个字符 getchar();
第4章 选择结构程序设计
一、选择结构和条件判断
1 概述
由于程序处理问题的需要,在大多数程序中都会包含选择结构,需要在进行下一 个操作之前先进行条件判断。
2 选择语句分类
2 选择语句分类
(1)if语句
用来实现两个分支的选择结构。
(2)switch语句
用来实现多分支的选择结构。
二、用if语句实现选择结构
1 一般形式
if语句的一般形式如下: if(表达式) 语句1 else 语句2
2 常用的三种形式
(1)没有else子句部分
if(表达式) 语句1
(2)有else子句部分
if(表达式) 语句1else 语句2
(3)在else部分又嵌套了多层的if语句
if(表达式1) 语句1 else if(表达式2) 语句2 else if(表达式3) 语句3 … else if(表达式m) 语句m else 语句m+1
三、关系运算符和关系表达式
1 关系运算符及其优先次序
C语言提供的6种关系运算符如图4-1所示。
图4-1 6种关系运算符
【说明】前4种关系运算符(<,<=,>,>=)的优先级别相同,后2种也相 同,前4种高于后2种。
2 关系表达式
(1)用关系运算符将两个数值或数值表达式连接起来的式子,称关系表达式;
(2)关系表达式的值是一个逻辑值,即“真”或“假”。在C的逻辑运算中,以“1”代表 “真”,以“0”代表“假”。
四、逻辑运算符和逻辑表达式
1 逻辑运算符及其优先次序
(1)逻辑运算符
C语言逻辑运算符如表4-1所示。
【说明】“&&”和“||”是双目(元)运算符,它要求有两个运算对象(操作数),如 (a>b)&&(x>y),(a>b)||(x>y)。“!”是一目(元)运算符,只要求有一个运算对象, 如!(a>b)。
(2)优先次序
赋值运算符、逻辑运算符“&&”和“||”、关系运算符、算术运算符、逻辑运算符“!”的 优先级如图4-2所示。
2 逻辑表达式
对于逻辑表达式需要注意的是:
(1)逻辑运算符两侧的运算对象既可以是0和1,也可以是是0和非0的整数,还可 以是字符型、浮点型、枚举型或指针型的纯量型数据。
(2)在逻辑表达式的求解中,并不是所有的逻辑运算符都被执行,只是在必须执 行下一个逻辑运算符才能求出表达式的解时,才执行该运算符。
五、条件运算符和条件表达式
1 条件运算符
条件运算符由两个符号(?和:)组成,必须一起使用。要求有3个操作对象,称为 三目(元)运算符,它是C语言中唯一的一个三目运算符。
2 条件表达式
(1)一般形式
表达式1?表达式2:表达式3
(2)执行过程
【总结】运算符优先级
六、选择结构的嵌套
在if语句中又包含一个或多个if语句称为if语句的嵌套(nest)如图所示
【说明】应当注意if与else的配对关系,else总是与它上面的最近的未配对的if配对。 为了避免混淆,可以将else所要对应的if语句下的内容用“{}”进行限定。
七、用switch语句实现多分支选择结构
1 基本概念
switch语句的作用是根据表达式的值,使流程跳转到不同的语句。switch语句的一 般形式如下:switch(表达式) { case 常量1: 语句1;break; case 常量2: 语句2;break; ⁝ ⁝ ⁝ case 常量n: 语句n;break; default: 语句n+1;}
2 规则说明
①switch后面括号内的“表达式”,其值的类型应为整数类型(包括字符型);
②switch下面的花括号内是一个复合语句;
③至多可以有一个default标号;
④各个case标号出现次序不影响执行结果;
⑤每一个case常量必须互不相同;
⑥case标号只起标记的作用;
⑦case子句不必用花括号括起来;
⑧多个case标号可以共用一组执行语句。
第5章 循环结构程序设计
一、为什么需要循环控制
在日常生活中或是在程序所处理的问题中常常遇到需要重复处理的问题,这时只 用顺序结构和选择结构是不够的,还需要用到循环结构(或称重复结构)。
二、用while语句实现循环
1 一般形式
while(表达式)语句
2 执行过程
while语句执行流程图
三、用do…while语句实现循环
1 一般形式
do 语句 while(表达式);
2 执行过程
先执行循环语句,然后判别表达式,当表达式的值为非零(“真”)时,返回重新执 行循环语句,如此反复,直到表达式的值为0(“假”)为止,此时循环结束,其流 程图如图
3 特点
先无条件地执行循环体,然后判断循环条件是否成立。
四、用for语句实现循环
1 for语句的一般形式
for(表达式1;表达式2;表达式3) 语句
其中3个表达式的主要作用是:
①表达式1:设置初始条件,只执行一次。
②表达式2:是循环条件表达式,用来判定是否继续循环,每次循环前执行。
③表达式3:作为循环的调整,每次在执行完循环体后进行的
2 执行过程
for语句的执行过程如图
3 详细说明
(1)“表达式1”可以省略,但“表达式1”后的分号不能省略;
(2)“表达式2”也可以省略,即不设置和检查循环的条件;
(3)“表达式3”也可以省略,但此时程序设计者应另外设法保证循环能正常结束;
(4)“表达式1”和“表达式3”可以包含一个以上的简单表达式,中间用逗号间隔。
五、循环的嵌套
一个循环体内又包含另一个完整的循环结构,称为循环的嵌套。内嵌的循环中还 可以嵌套循环,这是指多层循环。各种语言中关于循环的嵌套的概念都是一样 的。
3种循环(while循环、do…while循环和for循环)可以互相嵌套。
六、改变循环执行的状态
1 用break语句提前终止循环
break语句的一般形式为:
break;
其作用是使流程跳到循环体之外,接着执行循环体下面的语句,break语句只能用 于循环语句和switch语句之中,而不能单独使用。
2 用continue语句提前结束本次循环
有时并不希望终止整个循环的操作,只希望提前结束本次循环,而接着执行下次 循环,这时可以用continue语句。continue语句的一般形式为:
continue;
其作用为结束本次循环,即跳过循环体中下面尚未执行的语句,转到循环体结束 点之前,接着执行for语句中的“表达式3”,然后进行下一次是否执行循环的判定。
3 break语句和continue语句的区别
(1)continue语句只结束本次循环,而接着执行下次循环,如图
(2)break语句则是结束整个循环过程,不再判断执行循环的条件是否成立,如图
第6章 利用数组处理批量数据
一、定义和引用一维数组
1 定义一维数组
(1)一般形式
类型符 数组名[常量表达式];
(2)注意事项
①数组名的命名规则和变量名相同,遵循标识符命名规则;
②方括号中的常量表达式用来表示元素的个数,即数组长度;
③常量表达式中可以包括常量和符号常量,不能包含变量
(3)存储形式
数组的存储形式如图
2 引用一维数组元素
引用数组元素的表示形式为:
数组名[下标]
3 一维数组的初始化
可以用“初始化列表”方法实现数组的初始化,一般有:
(1)在定义数组时对全部数组元素赋予初值,例如int a[5]={0,2,4,6,8};。
(2)可以只给数组中的一部分元素赋值,未赋值元素的为0。
(3)在对全部数组元素赋初值时,由于数据的个数已经确定,因此可以不指定数 组长度。
4 一维数组程序举例
一位数组a有10个元素,要求将a中元素按从小到大排列。
解题思路:这种问题称为数的排序(sort)。排序的规律有两种:一种是“升序”, 从小到大;另一种是“降序”,从大到小。本例用“起泡法排序”,“起泡法”的原理如 下: (1)比较相邻的元素。如果第一个比第二个大,就交换位置; (2)对每一 对相邻的元素做同样的工作,从开始的第一对到结尾的最后一对。此趟结束最后 一个元素是整个数组中最大的数; (3)从第一个数到上一趟排序的倒数第二个 数,重复上述步骤,直到最后需要排序的个数为1。至此,起泡排序结束。(改 进:若在某一趟排序中,没有任何数字交换位置,则表明当前的序列已经是从小 到大的顺序。故可设置标志flag来记录每一趟是否有数字进行交换,若没有就直接 退出循环。)
举个例子,假设有一数组b[5]={2,6,3,1,4},冒泡排序的过程如下
如果有n个数,则要进行n-1趟比较。在第1趟比较中要进行n-1次两两比较,在 第i趟比较中要进行n-i次两两比较。据此画出流程图
编写程序:
根据流程图写出程序(令设n=10)。
#include<stdio.h> int main() { int a[10]; int i,j,t,flag=1; printf("input 10 numbers:\n"); for(i=0;i<10;i++) scanf("%d",&a[i]); printf("\n"); for(i=0;i<9;i++) //进⾏9次循环,实现9趟⽐较 { if(flag!=1) break; //说明上⼀趟没有元素进⾏交换位 置 flag=0; for(j=0;j<9-i;j++) //在每⼀趟中进⾏9-j次⽐较 if(a[j]>a[j+1]) //相邻两个 数⽐较 { t=a[j]; a[j]=a[j+1]; a[j+1]=t; flag=1; } } printf("t he sorted numbers:\n"); for(i=0;i<10;i++) printf("%d ",a[i]); printf("\n"); return 0;}
运行结果:
input 10 numbers:4 5 3 6 7 2 10 9 1 8 the sorted numbers:1 2 3 4 5 6 7 8 9 10
二、定义和引用二维数组
1 定义二维数组
(1)基本概念
二维数组定义的一般形式为:
类型说明符 数组名[常量表达式][常量表达式];
(2)元素存放方式
C语言中,二维数组中元素排列的顺序是按行存放的,即在内存中先顺序存放第1 行的元素,接着再存放第2行的元素。a[3][4]数组存放的顺序如图
假设数组a存放在从1000字节开始的一段内存单元中,一个元素占4个字节,则其存 放方式如图
(3)多维数组
C语言还允许使用多维数组。例如float a[2][3][4];。
2 引用二维数组的元素
(1)表示形式
数组名[下标][下标]
(2)引用
数组元素可以出现在表达式中,也可以被赋值,例如b[1][2]=a[2][3]/2;。
3 二维数组的初始化
可以用“初始化列表”对二维数组初始化。
(1)分行给二维数组赋初值。
例如: int a[2][3]={{1,2,3},{4,5,6}};
(2)可以将所有数据写在一个花括号内,按数组元素在内存中的排列顺序对各元 素赋初值。例如:
int a[2][3]={1,2,3,4,5,6};
(3)可以对部分元素赋初值。
例如int a[2][3]={{1},{4}},其余元素为0。
(4)如果对全部元素都赋初值(即提供全部初始数据),则定义数组时对第1维的 长度可以不指定,但第2维的长度不能省。
三、字符数组
1 定义字符数组
定义字符数组的方式:
类型说明符 数组名[常量表达式];
例如char a[10];。
2 字符数组的初始化
(1)将各个字符依次赋给数组中各元素。
。例如: char c[8]={'s','h','e','n','g','c','a','i'};
(2)花括号中提供的初值个数(即字符个数)大于数组长度,则出现语法错误。 如果初值个数小于数组长度,则只将这些字符赋给数组中前面那些元素,其余的 元素自动定为空字符(即'\0')。
(3)如果提供的初值个数与预定的数组长度相同,在定义时可以省略数组长度。
(4)给字符数组赋值时只能在初始化时进行,不能对数组名进行重新赋值,因为 数组名是一个地址常量,但可以引用数组的元素进行相应的更改。
下列写法有 误:
char c[8]={'s','h','e','n','g','c','a','i'}; c="cprogram";
下列写法正确:
char c[8]={'s','h','e','n','g','c','a','i'}; c[1]='h';
3 字符串和字符串结束标志
(1)字符串结束标志
C语言在遇到字符'\0'时,表示字符串结束,把它前面的字符组成一个字符串。C系 统在用字符数组存储字符串常量时会自动加一个'\0'作为结束符。
(2)字符串给字符数组赋值
对字符数组初始化的方法补充一种方法,即用字符串常量来使字符数组初始化, 赋值时有以下情况:
①若数组没有指定大小,则数组最后由系统自动加上一个'\0';
②若数组指定大小,则未被赋值的元素会自动赋值为'\0',如char c[10]= {"China"};,其存储情况如图
4 字符数组的输入输出
(1)输入输出方法分类
①逐个字符输入输出
用格式符“%c”输入或输出一个字符。
②将整个字符串一次输入或输出
用“%s”格式符,意思是对字符串(string)的输入输出。
(2)详细说明
①输出的字符中不包括结束符'\0';
②用“%s”格式符输出字符串时,printf函数中的输出项是字符数组名,而不是数组 元素名;
③如果数组长度大于字符串的实际长度,也只输出到遇'\0'结束;
④如果一个字符数组中包含一个以上'\0',则遇第一个'\0'时输出就结束。
5 字符串处理函数
(1)puts函数——输出字符串的函数
puts(字符数组);
其作用是将一个字符串(以'\0'结束的字符序列)输出到终端。
(2)gets函数——输入字符串的函数
gets(字符数组)
(3)strcat函数——字符串连接函数
strcat(字符数组1,字符数组2);
其作用是把两个字符数组中的字符串连接起来,把字符串2接到字符串1的后面, 结果放在字符数组1中,函数调用后得到一个函数值,即字符数组1的地址。
【说明】
①字符数组1必须足够大,以便容纳连接后的新字符串。
②连接前两个字符串的后面都有'\0',连接时将字符串1后面的'\0'取消,只在新串最 后保留'\0'
(4)strcpy和strncpy函数——字符串复制函数
strcpy(字符数组1,字符串2);
它表示“字符串复制函数”,作用是将字符串2复制到字符数组1中去。
【说明】
①字符数组1必须定义得足够大,以便容纳被复制的字符串2
②“字符数组1”必须写成数组名形式(如str1),“字符串2”可以是字符数组名,也 可以是一个字符串常量。
③可以用strncpy函数将字符串2中前面n个字符复制到字符数组1中去。例如: strncpy(str1,str2,2);
(5)strcmp函数——字符串比较函数
①一般形式
strcmp(字符串1,字符串2);
②比较规则
将两个字符串自左至右逐个字符相比(按ASCII码值大小比较),直到出现不同的 字符或遇到'\0'为止。
a.如全部字符相同,则认为两个字符串相等;
b.若出现不相同的字符,则以第1对不相同的字符的比较结果为准。
③比较结果
a.如果字符串1=字符串2,则函数值为0;
b.如果字符串1>字符串2,则函数值为一个正整数;
c.如果字符串1<字符串2,则函数值为一个负整数。
(6)strlen函数——测字符串长度的函数
strlen(字符数组);
函数的值为字符串中的实际长度(不包括'\0'在内)。
(7)strlwr函数——转换为小写的函数
strlwr(字符串);
(8)strupr函数——转换为大写的函数
strupr(字符串);
【说明】
在使用字符串处理函数时,应当在程序文件的开头用#include<string.h>把 “string.h”文件包含到本文件中。
第7章 用函数实现模块化程序设计
一、函数的作用
1 概述
函数是指功能。每一个函数用来实现一个特定的功能,函数的名字应反映其代表 的功能。如图7-1是一个程序中函数调用的示意图。
2 函数分类
(1)从用户使用的角度划分
①库函数
库函数是由系统提供的,用户不必自己定义,可直接使用它们。
②用户自己定义的函数
(2)从函数的形式划分
①无参函数
在调用无参函数时,主调函数不向被调用函数传递数据。
②有参函数
主调函数在调用被调用函数时,通过参数向被调用函数传递数据。
二、定义函数
1 原因
(1)概述
C语言要求,在程序中用到的所有函数,必须“先定义,后使用”。
(2)主要内容
①指定函数的名字;
②指定函数的类型,即函数返回值的类型;
③指定函数的参数的名字和类型;
④指定函数的功能。
(3)库函数
对于C编译系统提供的库函数,只须用#include指令把有关的头文件包含到本文件 模块中即可。
2 定义方法
(1)定义无参函数
类型名 函数名(){ 函数体}
对于有返回值的函数,必须要有相应的返回值(通常是用return语句进行返回)。
(2)定义有参函数
类型名 函数名(形式参数表列){ 函数体}
(3)定义空函数
在程序设计中有时会用到空函数,它的形式为
类型名 函数名(){}
函数体是空的,调用此函数时,什么工作也不做,没有任何实际作用。
三、调用函数
1 函数调用的形式
(1)一般形式
函数名(实参表列);
若有参数,则用逗号隔开;若无参数,则括号中为空。
(2)函数调用方式
①函数调用语句
把函数调用单独作为一个语句。
②函数表达式
函数调用出现在另一个表达式中,这时要求函数带回一个确定的值以参加表达式 的运算。
③函数参数
函数调用作为另一个函数调用时的实参。
2 函数调用时的数据传递
定义函数时括号中的参数为形参,调用该函数括号中的参数为实参,在调用函数 过程中发生的实参与形参间的数据传递,常称为“虚实结合”。实参向形参的数据传 递是“值传递”,单向传递,只能由实参传给形参,而不能由形参传给实参。
3 函数调用的过程
函数调用过程如图
4 函数的返回值
return语句的返回值类型必须和所定义的函数值类型相同,若无返回值则定义函数 为void类型。
四、对被调用函数的声明和函数原型
在一个函数中调用另一个函数(即被调用函数)需要具备如下条件:
(1)首先被调用的函数必须是已经定义的函数。
(2)如果使用库函数,应该在本文件开头用#include指令进行调用。
(3)如果被调函数在主调函数之后,则应进行声明,函数原型格式如下: 类型名 函数名(形参类型1,形参类型2,…);
【说明】
如果已在文件的开头(在所有函数之前),已对本文件中所调用的函数进 行了声明,则在各函数中不必对其所调用的函数再作声明。写在所有函数前面的 外部声明在整个文件范围中有效。
五、函数的嵌套调用
嵌套即在调用一个函数的过程中,又调用另一个函数,如图
六、函数的递归调用
1 定义
在调用一个函数的过程中又出现直接或间接地调用该函数本身,称为函数的递归 调用。
2 分类
(1)直接调用本函数
在定义函数f的过程中,又要调用f函数,这是直接调用本函数,如图
(2)间接调用本函数
如果在调用f1函数过程中要调用f2函数,而在调用f2函数过程中又要调用f1函数, 这是间接调用本函数,如图
【说明】
为了防止出现无终止的调用,应该用相关的控制语句(如if)来终止自身 调用。
七、数组作为函数参数
1 数组元素作函数实参
数组元素可以用作函数实参,不能用作形参。在用数组元素作函数实参时,把实 参的值传给形参,是“值传递”方式。数据传递的方向是从实参传到形参,单向传 递。
2 一维数组名作函数参数
用数组名既可以做形参也可以做实参,当用数组名作函数实参时,向形参(数组 名或指针变量)传递的是数组首元素的地址。
3 多维数组名作函数参数
可以用多维数组名作为函数的实参和形参,在被调用函数中对形参数组定义时可 以指定每一维的大小,也可以省略第一维的大小说明。
八、局部变量和全局变量
1 局部变量
(1)定义
局部变量是指在程序中只在特定过程或函数中可以访问的变量。
(2)定义位置
①在函数的开头定义;
②在函数内的复合语句内定义;
③在函数的外部定义。
(3)作用范围
①只在所定义的函数中有效;
②不同函数可以使用相同变量名;
③形式参数只能在本函数内使用;
④在复合语句中的变量作用仅限于该语句。
2 全局变量
(1)概述
在函数之外定义的变量称为外部变量,外部变量是全局变量(也称全程变量)。 全局变量可以为本文件中其他函数所共用。它的有效范围为从定义变量的位置开 始到本源文件结束。一般将全局变量名的第1个字母用大写表示。
(2)作用
设置全局变量增加了函数间数据联系的渠道。由于同一文件中的所有函数都能引 用全局变量的值,因此如果在一个函数中改变了全局变量的值,就能影响到其他 函数中全局变量的值。
九、变量的存储方式和生存期
1 变量的储存位置
变量按储存位置划分有:
2 变量的生存期
变量按生存期划分有:
一般地所有全局变量以及静态的局部变量存放在静态存储区中。
3 变量的作用域
变量按作用域划分有:
4 关于作用域和生存期的概念,表7-1是变量的作用域和生存期。
十、关于变量的声明和定义
一般为了叙述方便,把建立存储空间的声明称定义,而把不需要建立存储空间的 声明称为声明。显然这里指的声明是狭义的,即非定义性声明。
例如:
int main(){ extern A; //是声明,不是定义。声明将已定义的外部变量A的作⽤域扩展到此 … return 0;}int A; //是定义,定义A为整型外部变量
十一、内部函数和外部函数
1 内部函数
如果一个函数只能被本文件中其他函数所调用,它称为内部函数。在定义内部函 数时,在函数名和函数类型的前面加static,即
static 类型名函数名(形参表);
内部函数又称静态函数,因为它是用static声明的。使用内部函数,可以使函数的 作用域只局限于所在文件。
2 外部函数
(1)定义
如果在定义函数时,在函数首部的最左端加关键字extern,则此函数是外部函数, 可供其他文件调用。C语言规定,如果在定义函数时省略extern,则默认为外部函 数。
(2)调用
在需要调用此函数的其他文件中,需要对此函数作声明。在对此函数作声明时, 要加关键字extern,表示该函数“是在其他文件中定义的外部函数”。
第8章 善于利用指针
一、指针是什么
1 存储地址与存储内容
如图8-1所示的变量的存储地址和存储内容示意图。1000、1004、1008分别为i、j、 k的存储地址,而1、2、3分别为其存储内容
2 访问方式
(1)直接访问
直接按变量名进行的访问,称为直接访问方式,如图(a)所示。
(2)间接访问
将变量i的地址存放在另一个变量中,然后通过该变量来找到变量i的地址,从而访 问变量i,如图(b)所示。
3 指针和指针变量
一个变量的地址称为该变量的指针。如果有一个变量专门用来存放另一变量的地 址(即指针),则称为指针变量。指针变量是指地址变量,用来存放地址,指针 变量的值是地址(即指针)。
二、指针变量
1 定义指针变量
类型名 *指针变量名;
左端的类型名是在定义指针变量时必须指定的基类型,指针变量的基类型用来指 定此指针变量可以指向的变量的类型。
2 引用指针变量
(1)引用指针变量的不同情况
①给指针变量赋值;
②引用指针变量指向的变量;
③引用指针变量的值。
(2)&和*的区别
①“&”是取地址运算符。&a是变量a的地址;
②“*”是指针运算符(或称间接访问运算符),*p代表指针变量P指向的对象。
3 指针变量作为函数参数
函数的参数不仅可以是整型、浮点型、字符型等数据,还可以是指针类型。它的 作用是将一个变量的地址传递到另一个函数中。
三、通过指针引用数组
1 数组元素的指针
如图所示为数组元素的指针。其中令p=&a[0],即指针p指向数组首元素的地 址。
2 在引用数组元素时指针的运算
当指针指向数组元素的时候,允许对指针进行加和减的运算。
(1)运算分类
①加一个整数(用+或+=),如p+1。
②减一个整数(用-或-=),如p-1。
③自加运算,如p++,++p。
④自减运算,如p--,--p。
⑤两个指针相减,如p1-p2(只有p1和p2都指向同一数组中的元素时才有意 义)。
(2)详细说明
①指针变量p+1或p-1是加上或减去一个数组元素所占用的字节数。
②如果p的初值为&a[0],则p+i和a+i是指数组元素a[i]的地址,如图
③*(p+i)或*(a+i)是p+i或a+i所指向的数组元素,即a[i]。
④如果指针变量p1和p2都指向同一数组,则可执行减法计算,结果是p2-p1的值 (两个地址之差)除以数组元素的长度。
3 通过指针引用数组元素
引用数组元素的形式:
①下标法,如a[i]形式;
②指针法,如*(a+i)或*(p+i)。其中a是数组名,p是指向数组元素的指针变 量,其初值p=a。
4 用数组名作函数参数
(1)概述
当用数组名作参数时,实参数组名将数组首元素地址传给形参,因此如果形参数 组中各元素的值发生变化,实参数组元素的值随之变化。
(2)变量名与数组名做函数参数的比较
下面把用变量名作为函数参数和用数组名作为函数参数做一比较,如表
5 通过指针引用多维数组
(1)多维数组元素的地址
如图所示是一个多维数组元素地址示意图。其中a代表的是首行(即序号为0的 行)的首地址、a+1与a+2分别为序号为1和2的行。
如图所示,a[0]为序号为0的行的首列元素地址,即&[0][0],故a[0]+1为行序号 为0、列序号为1的元素地址,即&a[0][1],其他以此类推。
二维数组a的有关指针如表所示。
(2)指向多维数组元素的指针变量
①指向数组元素的指针变量
如图所示,现令p=a[0],则p指向第0行的首列元素,而a[2][3]为数组第11个元 素(从0开始计数),故可以用p+11表示a[2][3]的地址。
②指向由m个元素组成的一维数组的指针变量
若让p指向a[0],即p=&a[0],则p+1指向a[1],p的增值以一维数组的长度为单 位,如图所示。
四、通过指针引用字符串
1 字符串的引用方式
①用字符数组存放一个字符串,可以通过数组名和下标引用字符串中一个字符, 也可以通过数组名和格式声明“%s”输出该字符串;
②用字符指针变量指向一个字符串常量,通过字符指针变量引用字符串常量。
2 字符指针作函数参数
如果想把一个字符串从一个函数传递到另一个函数,可以用地址传递的办法,即 用字符数组名作参数,也可以用字符指针变量作参数。在被调用的函数中可以改 变字符串的内容,在主调函数中可以引用改变后的字符串。
3 使用字符指针变量和字符数组的比较
字符指针变量与字符数组存储及运算字符串的区别如表所示。
【说明】
①变量值可改变指针变量存储的地址可以改变,而数组名只能是数组的首地址, 不能改变;
②指针引用数组元素的前提是指针指向了数组。
五、指向函数的指针
1 定义
如果在程序中定义了一个函数,在编译时,编译系统为函数代码分配一段存储空 间,这段存储空间的起始地址(又称入口地址)称为这个函数的指针。
2 用函数指针变量调用函数
如果想调用一个函数,除了可以通过函数名调用以外,还可以通过指向函数的指 针变量来调用该函数。
3 定义和使用指向函数的指针变量
(1)一般形式
类型名(*指针变量名)(函数参数表列);
(2)详细说明
①指向函数的指针变量只能指向定义时指定的类型函数,在一个程序中可以先后 指向同类型的不同函数
②如果要用指针调用函数,必须先使指针变量指向该函数。
③在给函数指针变量赋值时,只须给出函数名而不必给出参数。
4 用指向函数的指针作函数参数
设函数fun的定义为fun(int (*x1)(int),int (*x2)(int,int)),函数f1和f2的定义分别为 f1(int x)、f2(int x,int y),则用指向函数的指针作函数参数为 fun(f1,f2);
此时x1与x2指向函数f1与f2,如图所示,这样在函数fun中就可以调用f1和f2函 数。
六、返回指针值的函数
一个函数可以返回一个整型值、字符值、实型值等,也可以返回指针型的数据, 即地址。其概念与以前类似,只是返回的值的类型是指针类型而已,其一般形式 为:
类型名 *函数名(参数表列);
【说明】
】在“*函数名”两侧没有括号,即不是表示函数指针,因此“函数名(参数列 表)”表示一个函数形式,而“类型名 *”表示该函数返回的指针所指向的类型。
七、指针数组和多重指针
1 指针数组
一个数组,若其元素均为指针类型数据,则称为指针数组,即指针数组中的每一 个元素都存放一个地址,相当于一个指针变量,其一般形式为:
类型名 *数组名[数组长度];
【说明】
在“*数组名”两侧没有括号,即不是表示指向一维数组的指针,因此“数组 名[数组长度]”表示一个一维数组,而“类型名 *”表示该一维数组中的元素都为指 针,且指向该类型。
2 指向指针数据的指针变量
指向指针数据的指针变量定义为
char **p;
如图所示,p指向name数组元素,而数组元素各自指向一个字符串。
3 指针数组作main函数的形参
(1)概述
C语言程序中main函数也可以有形参,形式为:
int main(int argc,char *argv[])
其中argc表示参数个数,而argv表示一个char*指针数组,其每一个元素指向命令行 的一个字符串。
(2)命令行
在操作命令状态下,实参是和执行文件的命令一起给出的,命令行的一般形式 为:
命令名 参数1 参数2 … 参数n
其中命令名是可执行文件名(包含main函数),命令行参数应当都是字符串,这 些字符串的首地址构成一个指针数组,如图所示,设命令行为file1 China Beijing。
八、动态内存分配与指向它的指针变量
1 内存的动态分配
全局变量是分配在内存中的静态存储区的,非静态的局部变量(包括形参)是分 配在内存中的动态存储区的,这个存储区是一个称为栈(stack)的区域。
而C语言还允许建立内存动态分配区域,不必声明和定义,随时需要随时开辟,不 需要时随时释放,这个特别的自由存储区称为堆(heap)区。
2 怎样建立内存的动态分配
(1)malloc函数
void *malloc(unsigned int size);
【说明】
此函数返回值是所分配区域的第一个字节的地址,指针的基类型为void, 即不指向任何类型的数据,只提供一个地址。如果此函数未能成功地执行(例如 内存空间不足),则返回空指针(NULL)。
(2)calloc函数
void *calloc(unsigned n,unsigned size);
【说明】
其作用是在内存的动态存储区中分配n个长度为size的连续空间,这个空间 一般比较大,足以保存一个数组
(3)free函数
void free(void *p);
【说明】
其作用是释放指针变量p所指向的动态空间,使这部分空间能重新被其他 变量使用。
(4)realloc函数
void *realloc(void *p,unsigned int size);
【说明】如果已经通过malloc函数或calloc函数获得了动态空间,想改变其大小为 size,可以用realloc函数重新分配。
以上4个函数的声明在stdlib.h头文件中,在用到这些函数时应当用 “#include<stdlib.h>”指令把stdlib.h头文件包含到程序文件中。
3 void指针类型
C99允许使用基类型为void的指针类型。可以定义一个基类型为void的指针变量 (即void*型变量),它不指向任何类型的数据。
【说明】
不能把“指向void类型”理解为能指向“任何的类型”的数据,在将它的值赋 给另一指针变量时由系统对它进行类型转换,使之适合于被赋值的变量的类型。
九、各种指针变量类型的总结
各类型的指针变量如表8-4所示
第9章 用户自己建立数据类型
一、定义和使用结构体变量
1 自己建立结构体类型
(1)概述
C语言允许用户自己建立由不同类型数据组成的组合型的数据结构,它称为结构体 (structre)。
(2)结构体类型的声明
struct 结构体名{ 成员表列};
其主要组成为:
①关键字struct,不能省略;
②结构体名,由用户指定,又叫结构体标记;
③成员列表,可以是另外一个结构体类型。
2 定义结构体类型变量
(1)先声明结构体类型,再定义该类型的变量
struct 结构体名 结构体变量名1,结构体变量名2,…,结构体变量名n;
(2)在声明类型的同时定义变量
struct 结构体名{ 成员表列}变量名表列;
(3)不指定类型名而直接定义结构体类型变量
struct{ 成员表列}变量名表列;
无名的结构体类型,显然不能再以此结构体类型去定义其他变量,这种方式用得 不多。
3 结构体变量的初始化和引用
(1)结构体变量初始化
①在定义结构体变量时对所有元素进行初始化,一般形式如下:
struct 结构体名 结构体变量={初始化常量列表};
②在定义结构体变量时对部分元素进行初始化,一般形式如下:
struct 结构体名 结构体变量={.成员名=初始化常量}; //成员运算符“.”必不可少
(2)结构体变量的引用
①结构体变量的一般形式如下:
结构体变量名.成员名
②结构体变量中的成员又是一个结构体变量,则为多级引用,例如:
student1.birthday.month(结构体变量student1中的成员birthday中的成员month)
(3)结构体变量的其他用法
①结构体变量的成员可以像普通变量一样进行各种运算;
②同类的结构体变量可以互相赋值;
③可以引用结构体变量成员的地址,也可以引用结构体变量的地址。
二、使用结构体数组
1 定义结构体数组的两种形式
(1)声明类型的同时定义结构体数组
struct 结构体名{ 成员表列}数组名[数组⻓度];
(2)先声明一个结构体类型,然后再用此类型定义结构体数组:
结构体类型 数组名[数组长度];
2 对结构体数组的初始化
在定义数组的后面加上“={初值表列};”即可。
三、结构体指针
定义
结构体指针是指指向结构体变量的指针,一个结构体变量的起始地址是指这个结 构体变量的指针。
1 指向结构体变量的指针
指向结构体对象的指针变量既可指向结构体变量,也可指向结构体数组中的元 素。指针变量的基类型必须与结构体变量的类型相同。
设结构体指针p指向结构体变量stu,num是其成员,则以下三种用法等价:
①stu.成员名(如stu.num);
②(*p).成员名(如(*p).num);
③p->成员名(如p->num)。
2 指向结构体数组的指针
可以用指针变量指向结构体数组的元素。设有指向结构体数组的指针p,则
①假设p的初值为stu,即指向stu的第1个元素,p加1后,p就指向下一个元素;
②p应该指向指定的结构体类型的对象,而不是指向结构体数组元素中的某一成 员。
3 用结构体变量和结构体变量的指针作函数参数
结构体作函数参数分类有三种:
①用结构体变量的成员作参数
②用结构体变量作实参
③用指向结构体变量(或数组元素)的指针作实参
四、用指针处理链表
1 定义
链表是一种常见的重要的数据结构。不同于数组的固定长度,链表根据需要开辟 内存单元,它是动态地进行存储分配的一种结构。
2 结构
简单的单向链表结构如图所示,链表中每一个元素称为结点,每个结点都应包 括两个部分: ①用户需要用的实际数据; ②下一个结点的地址。
3 建立链表
结构体变量是建立链表最适合的方法,用部分成员来存放用户实际数据、用指针 类型成员来存放下一个结点的位置,设有以下结构体类型:
struct Student{ int nnum; float score; struct Student *next; //next是指针变量,指向结构体变量}
则建立的链表如图所示。
4 输出链表
链表的输出N-S流程图如图所示
五、共用体类型
1 什么是共用体类型
(1)基本概念
有时想用同一段内存单元存放不同类型的变量。在存放变量时会使用覆盖技术, 即新存入的变量会覆盖前一个数据,前面一个数据就不会起作用,这种使几个不 同的变量共享同一段内存的结构,称为“共用体”类型的结构。
(2)定义共用体
(3)与结构体的区别
结构体变量所占内存长度是各成员占的内存长度之和,每个成员分别占有其自己 的内存单元;而共用体变量所占的内存长度等于最长的成员的长度。
2 引用共用体变量的方式
只有先定义了共用体变量才能引用它,引用方式与结构体变量相同,但是不能引 用共用体变量,而只能引用共用体变量中的成员。
3 共用体类型数据的特点
(1)同一个内存段可以用来存放几种不同类型的成员,但在每一瞬时只能存放其 中一个成员,而不是同时存放几个。
(2)可以对共用体变量初始化,但初始化表中只能有一个常量。
(3)共用体变量中起作用的成员是最后一次被赋值的成员,在对共用体变量中的 一个成员赋值后,原有变量存储单元中的值就被取代。
(4)共用体变量的地址和它的各成员的地址都是同一地址。
(5)不能对共用体变量名赋值,也不能企图引用变量名来得到一个值
(6)C99允许用共用体变量作为函数参数。
六、使用枚举类型
如果一个变量只有几种可能的值,则可以定义为枚举(enumeration)类型,“枚举” 是指把可能的值一一列举出来,变量的值只限于列举出来的值的范围内
1 声明枚举类型
声明枚举类型的一般形式为:
enum 枚举名{枚举元素列表};
【说明】C语言将枚举类型元素按常量处理,按顺序默认它们的值为0、1、2、 3…,因此枚举元素可以进行判断比较。
2 定义枚举变量
(1)若已声明了枚举变量,则定义枚举变量的一般形式为:
enum 枚举名 枚举变量1,枚举变量2,…
(2)若没有声明枚举变量,则定义枚举变量的一般形式为:
enum{枚举常量1,枚举常量2,…}枚举常量1,枚举常量2…
七、用typedef声明新类型名
1 简单地用一个新的类型名代替原有的类型名
简单地使用一个新类型名代替原有类型名后,两个类型名的作用是等价的,一般 形式为:
typedef 旧类型名 新类型名;
2 命名一个简单的类型名代替复杂的类型表示方法
(1)命名一个新的类型名代表结构体类型
typedef struct{ int month; int day; int year;}DateDate birthday; //定义结构体类型变量birthday, 前⾯不需要加关键字struct
(2)命名一个新的类型名代表数组类型
typedef int Num[100]; //声明Num为整型数组类型名 Num a; //定义a为整型数组名,它有100个元素
(3)命名一个新的类型名代表指针类型
typedf char * String; //声明String为字符指针类型 String p,s[10]; //定义p为字符指针变量,s为字符指针组
(4)命名一个新的类型名代表指向函数的指针类型
typedef int ( * Pointer)(); //声明Pointer为指向函数的指针类型,该函数返回整型值 Pointer p1,p2; //p1,p2为Pointer类型的指针变量
第10章 对文件的输入输出
一、文件的有关基本知识
1 文件
(1)概述
“文件”一般指存储在外部介质上数据的集合,一批数据是以文件的形式存放在外部 介质(如磁盘)上的。
(2)分类
按存储内容分类有:
①程序文件
这种文件的内容是程序代码,包括源程序文件(后缀为.c)、目标文件(后缀 为.obj)、可执行文件(后缀为.exe)等。
②数据文件
文件的内容不是程序,而是供程序运行时读写的数据。
(3)数据流与流式文件
输入输出是数据传送的过程,常将输入输出形象地称为流(stream),即数据流, 通常为字符流或字节(内容为二进制数据)流。
流式文件的存取是以字符(字节)为单位的,其输入输出数据流的开始和结束仅 受程序控制而不受物理符号(如回车换行符)控制。
2 文件名
一个文件要有一个唯一的文件标识,以便用户识别和引用,一般称为文件名。文 件标识包括3部分:文件路径、文件主干名、文件后缀,如图所示file1.dat文件 存放在D盘中的CC目录下的temp子目录下面。
3 文件的分类
(1)文件分类
根据数据的组织形式,数据文件可分为ASCII文件和二进制文件。
①二进制文件
数据在内存中是以二进制形式存储的,可以认为它是存储在内存的数据的映像, 所以也称之为映像文件(image file)。
②ASCII文件
如果要求在外存上以ASCII代码形式存储,则需要在存储前进行转换。ASCII文件 又称文本文件(text file),每一个字节放一个字符的ASCII代码。
(2)数据存储
根据以上两种文件的分类,整数10000的存储方式有如下两种
①二进制形式:00000000 00000000 00100111 00010000
②ASCII形式:00110001 00110000 00110000 00110000 00110000
4 文件缓冲区
缓冲文件系统如图所示,只有文件缓冲区满后再一起进行输入输出。
5 文件类型指针
(1)概述
文件使用时,文件相关信息会保存在系统声明的结构体变量中,类型用FILE表 示,文件类型指针即指向该结构体类型的指针。
(2)定义文件指针
FILE *fp;
定义fp是一个指向FILE类型数据的指针变量,可以使fp指向某一个文件的文件信息 区,通过该文件信息区中的信息就能够访问该文件。如果有n个文件,应设n个指 针变量,分别指向n个FILE类型变量,以实现对n个文件的访问。
二、打开与关闭文件
1 用fopen函数打开数据文件
(1)概述
fopen函数的调用方式为: fopen(文件名,使用文件方式); 其中包含三个信息:
①需要打开文件的名字;
②使用文件的方式(“读”还是“写”等);
③函数返回值给哪一个指针变量指向被打开的文件。
(2)使用文件方式
使用文件方式,如表所示。
2 用fclose函数关闭数据文件
(1)概述
在使用完一个文件后应该关闭它,“关闭”是指撤销文件信息区和文件缓冲区,使文 件指针变量不再指向该文件。其调用形式为:
fclose(文件指针);
fclose函数也带回一个值,当成功地执行了关闭操作,则返回值为0;否则返回 EOF(-1)。
(2)工作原理
在程序结束前应先关闭文件,这样就先把缓冲区中的数据输出到磁盘文件,然后 撤销文件信息区,从而防止数据丢失的可能。
三、顺序读写数据文件
1 向文件读写字符
对文本文件读入或输出一个字符的函数,如表所示。
2 向文件读写一个字符串
读写一个字符串的函数,如表所示。
3 用格式化的方式读写文本文件
格式化输入输出调用方式为:
fprintf(文件指针,格式字符串,输出表列);
fscanf(文件指针,格式字符串,输入表列);
fscanf(文件指针,格式字符串,输入表列);
其特点是由于需要在二进制内存变量与ASCII码文件之间频繁转换,因此使用 fprintf和fscanf函数会花费较多时间。
4 用二进制方式向文件读写一组数据
(1)概述
C语言允许用fread函数从文件中读一个数据块,用fwrite函数向文件写一个数据 块,在读写时都是以二进制形式进行的。
(2)调用形式
它们的一般调用形式为
fread(buffer,size,count,fp); fwrite(buffer,size,count,fp);
其各参数为:
①buffer,是一个地址。对fread来说,它是用来存放从文件读入数据的存储区地 址。对fwrite来说,是要把此地址开始的存储区数据向文件输出(以上指的是起始 地址)。
②size,数据项的字节数。
③count,要读写多少个数据项(每个数据项长度为size)。
④fp,FILE类型指针。
四、随机读写数据文件
1 文件位置标记及其定位
(1)文件位置标记
为了对读写进行控制,系统为每个文件设置了一个文件读写位置标记(简称文件 位置标记或文件标记),用来指示“接下来要读写的下一个字符的位置”。
随机读写是指读写完上一个字符(字节)后,并不一定要读写其后续的字符(字 节),而可以读写文件中任意位置上所需要的字符(字节)。
(2)文件位置标记的定位
①用rewind函数使文件位置标记指向文件开头,其形式为: rewind(文件类型指针); 该函数无返回值。
②用fseek函数改变文件位置标记 fseek(文件类型指针,位移量,起始点); 其中参数为:
a.“起始点”用0、1或2代替,0代表“文件开始位置”,1为“当前位置”,2为“文件末 尾位置”;
b.“位移量”指以“起始点”为基点,向前移动的字节数,位移量应是long型数据。
③用ftell函数测定文件位置标记的当前位置,其形式为:
ftell(文件类型指针);
其返回值为相对于文件开头的位移量、若出错,则返回-1L。
2 随机读写
通过rewind和fseek函数,再调用顺序读写数据相关函数,就可以实现随机读写。
五、文件读写的出错检测
在调用各种输入输出函数时,因为返回值EOF不仅能够表示文件结束,同时能够表 示输入输出操作中读、写出错及其他一些关联操作的错误状态,因此仅凭EOF并不 能判断文件读写是否出错。
1 ferror函数
ferror(fp);
如果ferror返回值为0(假),表示未出错;如果返回一个非零值,表示出错。 对同一个文件每一次调用输入输出函数,都会产生一个新的ferror函数值,因此, 应当在调用一个输入输出函数后立即检查ferror函数的值,否则信息会丢失。
2 clearerr函数
clearerr(fp);
clearerr的作用是使文件错误标志和文件结束标志置为0,假设在调用一个输入输出 函数时出现错误,ferror函数值为一个非零值。应该立即调用clearerr(fp),使 ferror(fp)的值变成0,以便再进行下一次的检测。