导图社区 C语言程序设计
这是一篇关于C语言程序设计的思维导图,主要内容包括:第1章 程序设计和C语言,第2章 算法——程序的灵魂,第3章 顺序程序设计(最简单),第8章 善于利用指针,第9章 用户自己建立数据类型,第10章 对文件的输入输出。
编辑于2025-12-14 18:50:18C语言程序设计
第1章 程序设计和C语言
计算机程序
程序就是一组计算机能识别和执行的指令
计算机的一切操作都是由程序控制的
C语言特点
(1)语言简洁、紧凑,使用方便、灵活
一共有37个关键字、9种控制语句
(2)运算符丰富
(3)数据类型丰富
整型
浮点型
字符型
数组类型
指针类型
共用体类型
(4)具有结构化的控制语句
(5)语法限制不太严格,程序设计自由度大
(6)C语言允许直接访问物理地址
(7)用C语言编写的程序可移植性好
(8)生成目标代码质量高,程序执行效率高
C语言程序结构特点
(1)一个程序由一个或多个源程序文件组成
在一个源程序文件中可以包括3个部分
预处理指令
全局声明
函数定义(如main函数)
(2)函数是C程序的主要组成部分
函数是C程序的基本单位
一个C语言程序是由一个或多个函数组成的,有且仅有一个main函数
一个小程序只包含一个源程序文件
函数既可以是库函数,也可以是用户自定义函数
(3)函数的两个部分
①函数首部
包括函数名、函数类型、函数属性、函数参数(形式参数)名、参数类型
②函数体
函数首部下面的花括号内的部分
包括声明部分和执行部 分
(4)程序总是从main函数开始执行的
(5)程序中对计算机的操作是由函数中的C语句完成的
(6)在每个数据声明和语句的最后必须有一个分号
(7)C语言本身不提供输入输出语句
(8)程序应当包含注释
c语言允许注释的方式
//
/* */
运行C程序的步骤与方法
C程序运行步骤图如图1-1所示。其中实线表示操作流程,虚线表示文件的输入输出
1、上机输入和编辑源程序
2、对源程序进行编译
3、进行连接处理
4、运行可执行程序,得到运行结果
第2章 算法——程序的灵魂
一、程序=算法+数据结构
对数据的描述,即数据结构
二、算法概述
定义
广义地说,为解决一个问题而采取的方法和步骤,就称为“算法
分类
(1)数值运算算法,数值运算的目的是求数值解
(2)非数值运算算法
特性
有穷性
一个算法应包含有限的操作步骤,而不能是无限的
确定性
每个步骤应当是确定的
有零个或多个输入
有一个或多个输出
算法的目的是为了求解,“解”是指输出
有效性
算法中的每一个步骤都应当能有效地执行,并得到确定的结果
表示算法的方式
一、 用自然语言表示算法
自然语言是指人们日常使用的语言
二、 用流程图表示算法
流程图
元素
①菱形框
菱形框的作用是对一个给定的条件进行判断,根据给定的条件是否成立决定如何 执行其后的操作。它有一个入口,两个出口,如图2-2所示。
②连接点
连接点(小圆圈)是用于将画在不同地方的流程线连接起来,如图2-3所示。
③注释框
注释框不是流程图中必要的部分,不反映流程和操作,只是为了对流程图中某些 框的操作作必要的补充说明,以帮助阅读流程图的人更好地理解流程图的作用。
组成
①表示相应操作的框
②带箭头的流程线
③框内外必要的文字说明
三种基本结构
顺序结构
如图2-4所示,虚线框内是一个顺序结构。其中A和B两个框是顺序执行的。顺序结 构是最简单的一种基本结构。
选择结构
选择结构又称选取结构或分支结构,如图2-5所示。虚线框内是一个选择结构,此 结构中必包含一个判断框,根据给定的条件P是否成立而选择执行A框或B框。
循环结构
又称重复结构,即反复执行某一部分的操作,有两类循环结构
三种结构共同特点
只有一个入口
只有一个出口
结构内的每一部分都有机会被执行到
结构内不存在“死循环”
基本结构并不一定只限于上面3种,只要具有上述4个特点的都可以作为基本结构
do…while循环结构(二分支选择结构)
switch选择结构(多分支选择结构)
三、 用N-S流程图表示算法
N-S流程图符号
①顺序结构
顺序结构用图2-9形式表示。A和B两个框组成一个顺序结构。
②选择结构
p为判断条件
③循环结构
当型循环结构(while),当p1条件成立时反复执行A操作,直到p1条件不 成立为止
直到型循环结构(until),先执行A操作,然后判断p1条件是否成立,如果p1不成立,反复执行A,只当p1条件成立才停止循环
四、 用伪代码表示算法
五、 用计算机语言表示算法
用流程图或伪代码描述一个算法后,还要将它转换成计算机语言程序。用计算机 语言表示的算法是计算机能够执行的算法,其必须严格遵循所用语言的语法规则
三、结构化程序设计
定义
一种以模块化为核心,遵循自顶向下、逐步求精的原则的编程方法
方法
遵循三种基本控制结构
顺序结构
选择结构
循环结构
模块化功能分解
自顶向下、逐步细化
结构化编码
编码
将已设计好的算法用计算机语言来表示,即根据已经细化的算法正确写出计算机程序
第3章 顺序程序设计(最简单)
一、数据的表现形式
常量和变量
(一) 常量
①整型常量
无小数点
10.0不是
②实型常量(浮点型常量)
十进制小数形式
由数字和小数点组成
12.0
指数形式
12,34e3(代表12.34*10的3次方)
用字母e、E代表以10为底的指数
注意
e、E之前必须有数字,且e、E后必须为整数
③字符常量
普通字符
用单撇号括起来''
表示一个字符
存储的不是字符(如a)本身,而是对应的代码(ASCII代码)
转义字符
以字符"\"开头的字符序列,将\后的字符转化为另外的意思
'\56'表示八进制转义字符
转义字符表
④字符串常量
双撇号“”中的全部字符
⑤符号常量
用#define指令,指定用一个符号名称代表一个常量
如#define PI 3.1416
为与变量名区分,通常大写
不占内存,预编译后,符号常量已全部变成字面常量(3.1416),就不存在了
(二) 变量
代表一个有名字(a)、具有特定属性(int)的一个存储单元
a为变量名,代表一个存储地址
具有两个属性
数据类型
数据的存储类别
自动的(auto)
静态的(static)
寄存器(register)
外部的(extern)
必须先定义,后使用
定义位置
①在函数的开头定义;
②在函数内的复合语句内定义;
③在函数的外部定义
把建立存储空间的声明称定义
把不需要建立存储空间的声明称为声明
局部变量、全局变量声明存储类别,即非定义性声明
分类
按照作用域
局部变量
定义
在函数内定义的变量
作用域
在函数内定义的变量
①只在所定义的函数中有效;
②不同函数可以使用相同变量名;
③形式参数也是局部变量
只能在本函数内使用,其他函数不能直接引用该函数的形参
在函数的复合语句中的变量作用仅限于该语句
这种复合语句也称为“分程序”或“程序块”
存储类别
自动变量(auto变量)
即动态局部变量,属于动态存储类别,函数调用后释放
在函数调用结束时就自动释放这些存储空间(如b=0调用结束后,下一次调用仍为0)
关键字auto可以省略,不写auto则隐含指定为“自动存储类别”
静态局部变量(static局部变量)
属于静态存储类别
占用的存储单元不释放,在下一次调用函数时,该变量已有值(上一次调用函数结束时的值)
如c=1,调用后为2,下次调用则为c=2
寄存器变量(register变量)
提高执行效率
允许将局部变量的值放在CPU中的寄存器中,需要时直接从寄存器中取出参加运算,寄存器的存取速度远高于对内存的存取速度
注意
三种局部变量的存储位置是不同的
自动变量
动态存储区
静态局部变量
静态存储区
寄存器
CPU中的寄存器
在定义变量的基础上加上这些关键字,而不能单独使用
static int a
局部变量声明存储类别作用
指定变量存储的区域以及由此产生的生存期
全局变量(外部变量)
定义
在函数之外定义的变量
作用域
从变量定义处开始,到本程序文件的末尾
存储类别
静态外部变量(static声明)
将外部变量的作用域限制在本文件中
可以在函数内部定义全局变量
extern声明
1、在一个文件内扩展外部变量的作用域
2、将外部变量的作用域扩展到其他文件
全局变量声明存储类别作用
都是存储在静态存储区域中
变量作用域的扩展问题
区别
定义和作用域
从变量存在的时间(生存期)
存储方式决定存储位置和生存期
动态存储
在调用函数时临时分配单元
静态存储
程序整个运行时间都存在
储存位置
(三) 常变量
是有名字的不变量,而且在变量存在期间其值不能改变
前面加一个关键词const
占用存储单元
(四) 标识符
只能由字母、数字和下划线3种字符组成,不能以数字开头
编译系统将大写字母和小写字母认为是两个不同的字符
注意
关键字是标识符,但却不能作为用户标识符
如printf
数据类型
类型
对数据分配存储单元的安排,包括存储单元的长度(占多少字节)以及数据的存储形式
整型类型
字符型数据
表3-2 常用的字符与字符代码
字符变量
用类型符char定义字符变量
字符类型型也属于整型,也可以用signed 和unsigned修饰符
输出字符值的方式
选择十进制整数型输出%d
字符形式输出%c
浮点型数据
用来表示具有小数点的实数
把实数称为浮点数的原因
实数在C语言中以指数形式存放在存储单元,如3.14159可以表示为3.14159e0、0.314159e1,小数点位置可以浮动,所以实数的指数形式为浮点数
数组类型
一维数组
定义—初始化—引用
定义
一般形式
类型符 数组名[常量表达式];
①数组名
内存首地址,是地址常量
命名规则和变量名相同
遵循标识符命名规则
②常量表达式
表示元素的个数,即数组长度(数组大小)
可以包括常量和符号常量,不能包含变量
定义时必须指定大小或者初始化确定大小,不能用变量定义数组大小
存储形式
a[10]
下标从0开始,不存在a[10]
内存=数组大小*每元素字节
初始化
(1)在定义数组时对全部数组元素赋予初值
int a[5]={0,2,4,6,8};
(2)给数组中的一部分元素赋值
int a[10]={0,1,2,3,4,5}
未赋值元素的为0
(3)数据的个数已经确定,可不指定数组长度
int a[ ]={1,2,3,4,5}
引用
只能引用数组元素,不能一次整体调用整个数组全部元素的值
表示形式
数组名[下标]
下标可以是整型常量或整型表达式
a[0]=a[5]+a[7]-a[2*3]
t=a[6]
程序举例
一位数组a有10个元素,要求将a中元素按从小到大排列。
本例用“起泡法排序”,“起泡法”的原理如 下: (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
二维数组
定义—初始化—引用
定义
一般形式
类型说明符 数组名[常量表达式][常量表达式];
元素个数=行数*列数
二维数组名a表示第0行的首地址,a[i]代表第i行的首地址
存放方式
先放第1 行元素,再放第2行元素
a[3][4]
各元素是连续存放的,不是二维的,是线性的
假设数组a存放在从1000字节开始的一段内存单元中,一个元素占4个字节,则其存放方式如图
初始化
(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)提供全部初始数据,行数可以省略,列数不可省
int a[ ][3]={1,2,3,4,5}
引用
元素
读取元素写法
下标法
数组名[下标][下标]
a[i][j]中i,j不能越界,否则引发逻辑错误
数组元素可以出现在表达式中,也可以被赋值
b[1][2]=a[2][3]/2;
指针法
1、指向一维数组的指针
2、使用一维指针遍历(将二维数组视为连续内存)
多维数组
字符数组
定义—初始化—引用
定义字符数组的方式
类型说明符 数组名[常量表达式];
char a[10]
初始化
(1)将各个字符依次赋给数组中各元素
char c[8]={'s','h','e','n','g','c','a','i'};
(2)如果初值个数小于数组长度,则只将这些字符赋给数组中前面那些元素
其余的元素自动定为空字符(即'\0')
char c[6]={'c','h','i','n','a'};
(3)初值个数与预定的数组长度相同,在定义时可以省略数组长度
char c[ ]={'c','h','i','n','a'};
(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';
字符串
(1)字符串结束标志
结束符'\0'
自动加一个'\0',使其前面的字符组成一个字符串
(2)字符串给字符数组赋值
①若数组没有指定大小,则数组最后由系统自动加上一个'\0';
char c[ ]={"I am happy"};
此时数组长度为11,因为系统自动加上'\0'
等价于
char c[ ]={'I',' ','a','m',' ','h','a','p','p','y','\0'};
②若数组指定大小,则未被赋值的元素会自动赋值为'\0',如char c[10]= {"China"}; (花括号可不加),其存储情况如图
引用
输入输出
(1)方法分类
①逐个字符输入输出
格式符“%c”
②将整个字符串一次
用“%s”格式符
输入输出是字符数组名(c),而不是数组元素名(c[0])
printf ("%s",c);
scanf("%s",c),不用加&
(2)输入
输入的字符串应短于已定义的字符数组长度
系统会在字符串后面自动加'\0'
利用scanf函数输入多个字符串,则应当在输入时以空格分隔
正确范例
错误范例
(3)输出
输出的字符中不包括结束符'\0'
输出到遇'\0'结束
如果一个字符数组中包含一个以上'\0',则遇第一个'\0'时输出就结束
处理字符串函数
注意
在使用字符串处理函数时,应当在程序文件的开头用#include<string.h>把 “string.h”文件包含到本文件中
(1)puts函数
输出字符串
一般形式
puts(字符数组);
其作用是将一个字符串(以'\0'结束的字符序列)输出到终端
输出的字符串可包含转义字符
将结束标志符'\0'转换成'\n',即输出完字符后换行
(2)gets函数
输入字符串
一般形式
gets(字符数组);
其作用是从终端输入一个字符串到字符数组,并得到一个函数值,该函数值是字符数组的起始地址
先定义字符数组str
gets(str);
从键盘输入以一个回车结束的字符串放入字符数组
scanf和gets不要混合使用
puts和gets函数只能输出或输入一个字符串
错误
puts(str1,str2);
gets(str1,str2);
(3)strcat函数
字符串连接
一般形式
strcat(字符数组1,字符数组2);
其作用是把两个字符数组中的字符串连接起来,把字符串2接到字符串1的后面, 结果放在字符数组1中,函数调用后得到一个函数值,即字符数组1的地址
注意
①字符数组1必须足够大
②连接前两个字符串的后面都有'\0',连接时将字符串1后面的'\0'取消,只在新串最后保留'\0'
(4)strcpy和strncpy函数
字符串复制
strcpy一般形式
strcpy(字符数组1,字符串2);
作用是将字符串2复制到字符数组1中去
注意
①字符数组1必须定义得足够大
②“字符数组1”必须写成数组名形式(如str1),“字符串2”可以是字符数组名,也可以是一个字符串常量
strncpy
strncpy(str1,str2,2);
将字符串2中前面n个字符复制到字符数组1中,取代数组1中原有最前面n个字符
注意
n不得对于str1中原有的字符(不包括'\0')
(5)strcmp函数
字符串比较
一般形式
strcmp(字符串1,字符串2);
1||| 比较规则
将两个字符串自左至右逐个字符相比(按ASCII码值大小比较),直到出现不同的字符或遇到'\0'为止
a.如全部字符相同,则认为两个字符串相等
b.若出现不相同的字符,则以第1对不相同的字符的比较结果为准
2||| 比较结果
a.如果字符串1=字符串2,则函数值为0
b.如果字符串1>字符串2,则函数值为一个正整数
c.如果字符串1<字符串2,则函数值为一个负整数
返回值为int型整数,其值为ASCII码差值
注意
对两个字符串比较,必须用strcmp
(6)strlen函数
测字符串长度
strlen(字符数组);
函数的值为字符串中的实际长度(不包括'\0'在内)
包括转义字符,如\x69算成一个字符
遇到'\0'停止
(7)strlwr函数
转换为小写
strlwr(字符串);
(8)strupr函数
转换为大写
strupr(字符串);
不存在逻辑型
二、运算符和表达式
C运算符
①算术运算符(+ - * / % ++ --)
自左至右
左结合性
自增、自减运算符
++i,--i
在使用i之前,先使i的值加(减)1
i=3;
printf(“%d\n”,++i);输出4
i++,i--
在使用i之后,使i的值加(减)1
i=3;
printf(“%d\n”,i++);输出3
注意
两个整数相除的结果为整数
5/3=1,结果舍去小数部分
两个实数相除的结果是双精度实数
%的运算对象必须为整数,结果也为整数,如8%3=2
除%以外的运算符的操作数都可以是任何算数类型
算术表达式
C算术表达式是指将运算对象通过算术运算符和括号连接起来的、符合C语法规则的表达式
运算对象包括常量、变量、函数等
不同类型数据的混合运算
规律
1、+、-、*、/运算的两个数float、double——结果为double
系统先将float转为double
2、int、floate——double
3、char、int——把字符的ASCII代码与整数运算
'A'+65=77
4、char 、double——将字符的ASCII代码转为double进行运算
②关系运算符(> < == >= <= !=)
关系表达式
值是一个逻辑值,即“真”(1)或“假”(0)
注意
不能连续判断
x>y>z>k ´
③逻辑运算符(! && ||)
“&&”和“||”是双目(元)运算符,它要求有两个运算对象(操作数),如 (a>b)&&(x>y),(a>b)||(x>y)。 “!”是一目(元)运算符,只要求有一个运算对象, 如!(a>b)。
在C语言中不能在程序中直接用AND、OR、NOT作为运算符
优先级
对象可以是任何基本数据类型
逻辑表达式
值
非零为真“1”,零为假“0”
逻辑的短路特性
在逻辑表达式的求解中,并不是所有的逻辑运算符都被执行,只是在必须执行下一个逻辑运算符才能求出表达式的解时,才执行该运算符
(一) a && b && c,只有a 为真(非0),才继续进行右面的运算
(m=a>b)&&(n=c>d),a=1.b=2.c=3,d=4
因为m=0,无需进行下面的步骤,直接推结果:m=0,n=1
(二) a|| b || c,只要a 为真,就不必判断b和c
④位运算符(<< >> ~ | ∧ &)
⑤赋值运算符(=及其扩展赋值运算符)
自右至左
右结合性
max=(a>b) ? a:b
a>b ? printf("%d",a) : printf("%d",b)
⑥条件运算符(?:)
要求有3个操作对象,称为 三目(元)运算符,它是C语言中唯一的一个三目运算符
条件表达式
一般形式
表达式1 ? 表达式2 : 表达式3
值取决于条件的真假
⑦逗号运算符(,)
逗号表达式
从左至右顺序求解,整个逗号表达式的值为最右边表达式的值
for(i=1;i<=100;i++,i++)sum=sum+i
等价于for(i=1;i<=100;i=i+2)sum=sum+i
⑧指针运算符(*和&)
⑨求字节数运算符(sizeof)
⑩强制类型转换运算符((类型))
(类型名)(表达式)
(double)a——将a转换为double
(int)(x+y)——将x+y的值转换成int
注意
(int)x+y——只将x 转换成int型,与y相加
a=(int)x——注意x的值和类型都没变,只不过是把强制转换后的临时值赋给a
⑪成员运算符(. ->)
⑫下标运算符([ ])
⑬其他(如函数调用运算符())
运算符的优先级与结合性
【总结】运算符优先级(一般规律:单目、双目、三目、赋值)
三、C语句
表达式加分号为语句,不加分号为表达式
分类
①控制语句
定义一个分支结构
选择判断语句(条件语句)
if()...else...(两分支的选择结构)
一般形式
if (表达式) 语句1 [else 语句2]
将一般形式写成3种不同的形式
(1) if (表达式) 语句1
没有else子句部分
(2) if (表达式) 语句1 else 语句2
有else的子句部分
(3) if (表达式1) 语句1 else if(表达式2) 语句2 else if(表达式3) 语句3 else if(表达式m) 语句m else 语句m+1
在else部分又嵌套了多层的if语句
; 不能少
语句多于2,{ }
不管有多少分号,都是if语句
(4)
if语句中又包含一个或多个if语句称为if语句的嵌套
应当注意if与else的配对关系
else总与它上面的最近的未配对的if配对
可以用{ }来确定配对关系
注意缩进不代表配对关系
if(x) 语句3
表达式是变量,如果x不等于0,则条件判断的结果为真,执行语句3
switch-case语句(多分支选择结构)
作用是根据表达式的值,使流程跳转到不同的语句
一般形式
switch(表达式)——————表达式值类型为整数型(包括字符型) { case 常量1: 语句1;break; —————各个case标号出现次序不影响执行结果(可先default,再case),每一个case常量必须互不相同 case 常量2: 语句2;break; case标号只起标记的作用,case子句不必用花括号括起来,多个case标号可以共用一组执行语句 ⁝ ⁝ case 常量n: 语句n;break; default : 语句n+1; ——————至多可以有一个default标号 }——————花括号内是一个复合语句
注意
在执行一个case子句后,应当用break语句使流程跳出switch结构
无break时会继续执行下一个case(穿透)
default子句不必加break,因为流程已经到switch的结束处
循环执行语句
for( )...
一般形式
for(表达式1 ; 表达式2 ;表达式3) ——后面无; 语句
表达式1
设置初始条件,只执行一次
可以是与循环变量无关的表达式
可以是逗号表达式,即包含一个以上简单表达式
for(sum=0,i=1;i<=100;i++) sum=sum+i
表达式2
循环条件表达式,用来判定是否继续循环,每次循环前执行
表达式3
作为循环的调整,每次在执行完循环体后进行的
可以是逗号表达式,即包含一个以上简单表达式
常用
for(循环变量赋值初始;循环条件;循环变量增值)
等价于while语句
表达式1 while 表达式2 { 语句 表达式3 }
可省略
表达式1
表达式2
表达式3
表达式1和表达式3
都省略
还可以用于循环次数不确定而只给出循环结束条件的情况
执行过程
四、用for语句实现循环
3 详细说明
(1)“表达式1”可以省略,但“表达式1”后的分号不能省略;
(2)“表达式2”也可以省略,即不设置和检查循环的条件;
(3)“表达式3”也可以省略,但此时程序设计者应另外设法保证循环能正常结束;
(4)“表达式1”和“表达式3”可以包含一个以上的简单表达式,中间用逗号间隔。
while( )...
一般形式
while(表达式)语句
语句就是循环体
当型循环结构
特点
先判断条件表达式,后执行循环体语句,只要当循环条件表达式为真,就执行循环体
do...while( )
一般形式
do 语句 while(表达式);
;必加
多语句时,do后加{ },只有一个语句时,可不加{ }
直到型循环结构
特点
先无条件执行循环体,然后判断循环条件是否成立,直到表达式的值为0(“假”)为止,此时循环结束
注意
while和do-while的区别
do-while循环体至少执行一次
当while后面的表达式第一次的值为“真”,两种循环得到的结果相同,否则,结果不相同
循环的嵌套
一个循环体内又包含另一个完整的循环结构
内层循环要完全包含在外层循环的循环体中,不能出现循环范围交叉的情形
3种循环(while循环、do…while循环和for循环)可以互相嵌套
循环嵌套执行次数
外层循环次数*内层循环次数
跳转控制语句
break
中止执行switch
提前中止循环语句
不能单独使用
一般形式
break;
continue
提前结束本次循环
continue;
跳过尚未执行的语句,接着执行for语句中的“表达式3”,然后进行下一次是否执行循环的判定
break语句和continue语句的区别
(1)continue语句只结束本次循环,而接着执行下次循环
(2)break语句则是结束整个循环过程,不再判断执行循环的条件是否成立
goto(转向语句,在结构化程序中基本不用goto语句)
()表示“判别条件”,...表示“内嵌的语句”
②函数调用语句
函数调用语句由一个函数调用加一个分号构成
如printf(“this is a C statment.”);
输入输出语句
数据输入输出的概念
(1)输入输出是以计算机主机为主体
输入
从输入设备(如键盘、关盘、扫描仪等)向计算机输入数据
输出
从计算机向输出设备(如显示器)输出数据
(2)C语言本身不提供输入输出语句
输入和输出操作是由C标准函数库中的函数来实现的
printf和scanf不是关键字,而是库函数的名字
(3)在使用系统库函数时,要在程序文件的开头用预处理指令#include<stdio.h>
输入函数
格式输入
scanf()
一般形式
scanf(格式控制,地址表列);
①“格式控制”
含义同printf函数
格式声明
格式字符
格式附加字符
②“地址表列”
可以是变量的地址,或字符串的首地址
注意
是变量地址,不是变量名
scanf("%f",&a);&a而不是a
对于一个数值型数组,是不能用数组名输出他的全部元素
只能逐个输出
注意
位置对应
用“%c”格式声明输入字符时,空格字符和“转义字符”中的字符都作为有效字符输入
在输入数据时,如输入空格、回车、tab键或者遇非法字符(不属于数值的字符),认为该数据结束
字符输入
一般形式
getchar()
注意
必须连续输入BOY并按Enter键
只读一个字符
字符串输入
gets()
输出函数
格式输出
printf()
一般格式
printf(格式控制,输出表列);
①格式控制
称为格式控制字符串
a.格式声明
"%+格式字符"
"%d"
%c
%f
%e
除了X、E、G,其他格式字符必须用小写
在%和格式字符间可以插入附加符号(又 称修饰符)
指定数据宽度和小数位数
%m.nf
向右对齐
%-m.nf
向左对齐
想输出%
应该在格式控制字符串中用“连续两个%”,如printf("%f%%\n",1.0/3);输出0.333333%
b.普通字符
""的逗号、空格、换行符
②输出表列
可以是常量、变量或表达式
printf("%s","CHINA")
输出"CHUNA"(不包括双引号)
printf("%c",'a')
可以只有控制项,没有输出项
字符输出函数
一般形式
putchar(c)
c可以是字符常量(包括转义字符)、整型常量、字符变量、整型变量
字符串输出函数
puts()
用函数实现模块化程序设计
函数的概述
概念
函数是指功能
函数的名字应反映其代表的功能
分类
(1)从用户使用的角度划分
①库函数
库函数是由系统提供的,用户不必自己定义,可直接使用它们
只须用#include指令把有关的头文件包含到本文件模块中即可
②用户自己定义的函数
(2)从函数的形式划分
①无参函数
在调用无参函数时,主调函数不向被调用函数传递数据
②有参函数
主调函数在调用被调用函数时,通过参数向被调用函数传递数据
C语言中用到的所有函数,必须“先定义,后使用”
定义函数
主要内容
①指定函数的名字
②指定函数的类型,即函数返回值的类型
③指定函数的参数的名字和类型
④指定函数的功能
定义方法
(1)定义无参函数
类型名 函数名(void){ 函数体 }
函数体包含声明部分和语句部分
void 表示"空",即函数没有参数
(2)定义有参函数
类型名 函数名(形式参数表列){ 函数体 }
int max (int x,int y)
形参必须指定类型,不能是常量或表达式
(3)定义空函数
类型名 函数名() { }
注意
所有函数是平行的,不能嵌套定义
调用函数
被调用函数的条件
(1)已经定义的函数
(2)如果使用库函数,#include进行调用
(3)在主调函数中,进行声明
函数调用的形式
(1)一般形式
函数名(实参表列);
若有参数,则用逗号隔开;若无参数,则括号中为空
print_star();
c=max(a,b);
(2)函数调用方式
①函数调用语句
把函数调用单独作为一个语句
print_star();
②函数表达式
函数调用出现在另一个表达式中,带回一个确定的值以参加表达式运算
c=2*max(a,b);
③函数参数
函数调用作为另一个函数调用时的实参
m=max(a,max(b,c));
注意
函数调用本身不必有分号,只有作为函数调用语句时才需要分号
调用有参函数时的数据传递
形参
定义函数时括号中的参数
可以是一维数组、多维数组、指针变量
数组可以不用指定大小
float average(float array[ ])
等价于 float average(float *array )
该指针变量用来接收从实参数组传过来的地址
二维不能省略列下标
实参
调用该函数括号中的参数
可以是常量、变量或表达式,如max(3,a+b),但要求有明确的值
数组元素可以用作函数实参,不能用作形参
向形参传递的是数组元素的值
一维数组名、二维数组名
向形参(数组 名或指针变量)传递的是数组首元素的地址
形参数组中各元素的值发生变化,实参数组元素的值随之变化
实参向形参的数据传递是“值传递”,称为”虚实结合“,单向传递,只能由实参传给形参,而不能由形参传给实参
如果实参为float—3.5,形参为int,则先将实参转为int—3,再送到形参
函数调用过程如图
return 与函数返回值
函数的返回值(函数值)
通过函数调用使主函数能得到一个确定的值
有返回值
return语句
针对函数指定类型与return表达式类型不一致时自动转换
函数类型决定返回值类型
一个函数可以有多个return,一个return只能返回一个值
return可以是一个表达式
return(x>y?x:y);
注意
main函数的返回值用于告知操作系统的运行状态
return 0
表示正常退出
return 1
表示异常退出
无返回值
定义函数为void类型
void函数可不用return或写return
嵌套调用
嵌套即在调用一个函数的过程中,又调用另一个函数,如图
递归调用
定义
函数直接或间接调用自身,速度比较慢,且必须有终止条件
分类
(1)直接调用本函数
在定义函数f的过程中,又要调用f函数,这是直接调用本函数,如图
(2)间接调用本函数
如果在调用f1函数过程中要调用f2函数,而在调用f2函数过程中又要调用f1函数, 这是间接调用本函数,如图
内部函数和外部函数
内部函数
一个函数只能被本文件中其他函数所调用
又称静态函数,因为它是用static声明的,可以使函数的作用域只局限于所在文件
定义内部函数+static
static 类型名 函数名(形参表);
外部函数
(1)定义
定义函数时,在函数首部的最左端加关键字extern,可供其他文件调用
在定义函数时省略extern,则默认为外部函数
(2)调用
在需要调用此函数的其他文件中,需要对此要加关键字extern函数作声明,表示该函数“是在其他文件中定义的外部函数”
③表达式语句
表达式语句由一个表达式加一个分号构成
如a=3;
最基本的语句——赋值语句
(1)赋值运算符
“=”
(2)复合的赋值运算符
+=,-=,*=,/=,%=
a+=3
a=a+3
x*=y+8
x=x*(y+8)
x%=3
x=x%3
(3)赋值表达式
(左值)变量 赋值运算符 表达式(右值)
b=5
连续赋值
a=b=c=5
注意
赋值语句不能连续赋值,如int a=b=c=3;是不对的
可以出现在输出语句、循环语句
printf("%d,\n",a=b)
(4)赋值过程中的类型转换
(float、double)赋给int
对float、double取整,即舍弃小数部分
int赋给float、double
int数值不变,但以浮点形式存储变量中
double赋给float,注意数值范围
char赋给int
将字符的ASCII代码赋值给整型变量
将一个占字节多的整型数据赋值给一个占字节少的整型数据或字符变量
只能将其他字节原封不动地送到被赋值的变量(即发生“截断”)
(5)变量赋初值
可以用赋值语句对变量赋值,也可以在定义变量时对变量赋以初值。
对几个变量赋予同一个初值,应写成
int a=3,b=3,c=3;
④空语句
只有一个分号
;
⑤复合语句(语句块)
用{}把一些语句和声明括起来成为复合语句
常用在if语句或者循环语句中
最后一个语句末尾的分号不能省略
{float pi=3.14159,r=2.5,area; area=pi*r*r;printf("area=%f\n",area);}
第8章 善于利用指针
指针和指针变量
对变量的访问都是通过地址进行的
访问方式
(1)直接访问
直接按变量名进行的访问
(2)间接访问
将变量i的地址存放在另一个变量中,然后通过该变量来找到变量i的地址,从而访问变量
指针
定义
一个变量的地址称为该变量的指针
指针变量
概念
地址变量,用来存放地址
不要将一个整数赋值给一个指针变量
值是地址(即指针)
定义指针变量
(基类型)类型名 *指针变量名;
用来指定此指针变量可以指向的变量的类型
如何表示指针类型
指向整型数据的指针类型为int *,读作“指向int的指针”或“int 指针”
描述指针变量
如int *a,a 是指向整型数据的指针变量
引用指针变量
(1)引用指针变量的不同情况
①给指针变量赋值
int a,*p ; p=&a ;
②引用指针变量指向的变量
如果已执行“p=&a”,printf("%d", *p);
*p=1,将整数1赋值给p当前所指向的变量,如果p指向a,则相当于1赋值给a
③引用指针变量的值
printf("%o", p) ;
以八进制形式输出指针变量p的值,如果p指向了a,即输出a的地址&a
(2)&和*的区别
①“&”是取地址运算符。&a是变量a的地址;
②“*”是指针运算符(或称间接访问运算符),*p代表指针变量P指向的对象
作为函数参数
作用是将一个变量的地址传递到另一个函数中
对函数swap的声明
void swap(int *p1,int *p2);
形参
调用swap函数
swap(p1,p2);
实参为指针变量名
通过指针引用数组
在引用数组元素时
数组元素的指针
数组元素的地址
令p=&a[0],即指针p指向数组首元素的地址
指针运算分类
①加一个整数(用+或+=),如p+1
加上一个数组元素所占用的字节数
②减一个整数(用-或-=),如p-1
减去一个数组元素所占用的字节数
如果p的初值为&a[0],则p+i和a+i是指数组元素a[i]的地址
*(p+i)或*(a+i)是p+i或a+i所指向的数组元素,即a[i]
③自加运算,如p++,++p
④自减运算,如p--,--p
⑤p1、p2都指向同一数组,两个指针相减
如p1-p2
结果是p2-p1的值 (两个地址之差)除以数组元素的长度
引用数组元素的形式
①下标法,如a[i]
②指针法,如*(a+i)或*(p+i),p=a
其中a是数组名(地址常量),p是指向数组元素的指针变量
用数组名作函数参数
变量名与数组名做函数参数的比较
引用多维数组
多维数组元素的地址
a 代表的是首行(0行)的首地址
a+1与a+2 分别为 1行和2行
a[0]代表一维数组a[0]第0列元素地址,即&a[0][0]
a[0]+1
行0,列1的元素地址,即&a[0][1]
a[1]
代表&a[1][0],行1列0
地址
a[1][2]的值不是地址
二维数组a的有关指针
(2)指向多维数组元素的指针变量
①指向数组元素的指针变量
令p=a[0],则p指向第0行的首列元素,而a[2][3]为数组第11个元素(从0开始计数)
易错:行、列、计算从0开始
用p+11表示a[2][3]的地址
②指向由m个元素组成的一维数组的指针变量
若p指向a[0],即p=&a[0],则p+1指向a[1]
p的增值以一维数组的长度为单位
通过指针引用字符串
字符串的引用方式
用字符指针变量指向一个字符串常量
char *string="I love China!";
把字符串第一个字符的地址赋给string
等价于
char & string;
string ="I love China!";
字符指针变量和字符数组的比较
变量值可改变指针变量存储的地址可以改变,而数组名只能是数组的首地址, 不能改变
指向函数的指针
函数名就是函数的指针,它代表函数的起始地址
定义指向函数的指针变量
(1)一般形式
类型名( *指针变量名)(函数参数表列);
(2)详细说明
①指向函数的指针变量只能指向定义时指定的类型函数,在一个程序中可以先后 指向同类型的不同函数
②如果要用指针调用函数,必须先使指针变量指向该函数。
③在给函数指针变量赋值时,只须给出函数名而不必给出参数。
调用函数的方式
1、通过函数名调用
2、通过指向函数的指针变量调用
int max(int,int);
函数声明
int(*p)(int,int);
定义指向函数的指针变量p
c=(*p)(a,b);
通过指针变量调用函数max
用(*p)代替函数名即可
用指向函数的指针作函数参数
设函数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函 数。
七、指针数组和多重指针
指针数组
一个数组,若其元素均为指针类型数据,则称为指针数组,即指针数组中的每一 个元素都存放一个地址,相当于一个指针变量,其一般形式为:
类型名 *数组名[数组长度];
【说明】
在“*数组名”两侧没有括号,即不是表示指向一维数组的指针,因此“数组 名[数组长度]”表示一个一维数组,而“类型名 *”表示该一维数组中的元素都为指 针,且指向该类型。
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。
八、动态内存分配与指向它的指针变量
内存的动态分配
全局变量是分配在内存中的静态存储区的,非静态的局部变量(包括形参)是分 配在内存中的动态存储区的,这个存储区是一个称为栈(stack)的区域。
而C语言还允许建立内存动态分配区域,不必声明和定义,随时需要随时开辟,不 需要时随时释放,这个特别的自由存储区称为堆(heap)区。
怎样建立内存的动态分配
(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 用结构体变量和结构体变量的指针作函数参数
结构体作函数参数分类有三种:
①用结构体变量的成员作参数
②用结构体变量作实参
③用指向结构体变量(或数组元素)的指针作实参
四、用指针处理链表
链表
定义
链表是一种常见的重要的数据结构。不同于数组的固定长度,链表根据需要开辟内存单元,它是动态地进行存储分配的一种结构
结构
简单的单向链表结构如图所示,链表中每一个元素称为结点,每个结点都应包 括两个部分: ①用户需要用的实际数据; ②下一个结点的地址。
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,以便再进行下一次的检测。