友情链接:C/C++系列系统学习目录
知识总结顺序参考C Primer Plus(第六版)和谭浩强老师的C程序设计(第五版)等,内容以书中为标准,同时参考其它各类书籍以及优质文章,以至减少知识点上的错误,同时方便本人的基础复习,也希望能帮助到大家
最好的好人,都是犯过错误的过来人;一个人往往因为有一点小小的缺点,将来会变得更好。如有错漏之处,敬请指正,有更好的方法,也希望不吝提出。最好的生活方式就是和努力的大家,一起奔跑在路上
🚀字符输入/输出和输入验证
⛳一、单字符I/O:getchar()和putchar()
getchar()和 putchar()每次只处理一个字符。你可能认为这种方法实在太笨拙了,毕竟与我们的阅读方式相差甚远。但是,这种方法很适合计算机。而且,这是绝大多数文本(即,普通文字)处理程序所用的核心方法。
自从ANSI C标准发布以后,C就把stdio.h头文件与使用getchar()和 putchar()相关联,这就是为什么程序中要包含这个头文件的原因(其实, getchar()和 putchar()都不是真正的函数,它们被定义为供预处理器使用的宏,
🎈(一)getchar()
getchar函数是从标准的输入设备(如键盘)上输入一个字符,不带任何参数,格式为:getchar();
getchar函数将输入的第一个字符作为函数的返回值,通常使用这个函数时,将函数的返回值赋予一个字符变量或者整型变量。查看帮助手册可知,实际上getchar()函数实际返回值的类型是 int
#include <stdio.h> //getchar是C语言的标准库函数
int main()
{ char ch;
ch = getchar();
printf("%c %d\n",ch,ch);
printf("%c %d\n",ch-32,ch-32);
return 0;
}
程序运行时输入 abc。得到结果应该为a
🎈(二)putchar()
putchar函数是向标准输出设备(屏幕)上输出一个字符的C语言标准函数,格式为:putchar(ch);
ch可以是变量、常量,转义字符或表达式。数据类型可以是字符型或整型。当ch是整型数据时,输出的是与其相对应的ASCII码值。
#include <stdio.h>
int main()
{ int p;
char q; //此时定义的是字符变量,不是字符串,所以只能将一个字符的值赋予q
p=98;
q='a';
putchar(p);
printf("\n");
putchar(q);
return 0;
}
执行结果输出:
b
a
⛳二、缓冲区
🎈(一)缓冲输入和无缓冲输入
无缓冲输入:即正在等待的程序可立即使用输入的字符
缓冲输入:用户输入的字符被收集并储存在一个被称为缓冲区(buffer)的临时存储区,按下Enter键后,程序才可使用用户输入的字符
为什么要有缓冲区:首先,把若干字符作为一个块进行传输比逐个发送这些字符节约时间。其次,如果用户打错字符,可以直接通过键盘修正错误。当最后按下Enter键时,传输的是正确的输入。
拓展:
1.虽然缓冲输入好处很多,但是某些交互式程序也需要无缓冲输入。例如,在游戏中,你希望按下一个键就执行相应的指令。因此,缓冲输入和无缓冲输入都有用武之地。
2.ANSI C和后续的C标准都规定输入是缓冲的
3.许多IBM PC兼容机的编译器都为支持无缓冲输入提供一系列特殊的函数,其原型都在conio.h头文件中。这些函数包括用于回显无缓冲输入的getche()函数和用于无回显无缓冲输入的getch()函数(回显输入意味着用户输入的字符直接显示在屏幕上,无回显输入意味着击键后对应的字符不显示)
4.UNIX系统使用另一种不同的方式控制缓冲。在 UNIX系统中,可以使用ioctl()函数(该函数属于UNIX库,但是不属于C标准)指定待输入的类型,然后用getchar()执行相应的操作
5.在ANSI C中,用 setbuf()和setvbuf()函数控制缓冲,但是受限于一些系统的内部设置,这些函数可能不起作用。总之,ANSI没有提供调用无缓冲输入的标准方式,这意味着是否能进行无缓冲输入取决于计算机系统。
🎈(二)使用缓冲输入
缓冲输入用起来比较方便,因为在把输入发送给程序之前,用户可以编辑输入。但是,在使用输入的字符时,它也会给程序员带来麻烦。缓冲输入要求用户按下Enter键发送输入。这一动作也传送了换行符,程序必须妥善处理这个麻烦的换行符。
🎈(三)完全缓冲I/O和行缓冲I/O
缓冲分为两类:完全缓冲I/O和行缓冲I/O。
完全缓冲I/O:当缓冲区被填满时才刷新缓冲区(内容被发送至目的地),通常出现在文件输入中。缓冲区的大小取决于系统,常见的大小是 512 字节和 4096字节。
行缓冲I/O:在出现换行符时刷新缓冲区。键盘输入通常是行缓冲输入,所以在按下Enter键后才刷新缓冲区。
⛳三、结束键盘输入
🎈(一)文件、流和键盘输入
文件:文件(file)是存储器中储存信息的区域。通常,文件都保存在某种永久存储器中(如,硬盘、U盘或DVD等)。毫无疑问,文件对于计算机系统相当重要。例如,你编写的C程序就保存在文件中,用来编译C程序的程序也保存在文件中
C 是一门强大、灵活的语言,有许多用于打开、读取、写入和关闭文件的库函数,C可以使用主机操作系统的基本文件工具直接处理文件,这些直接调用操作系统的函数被称为底层 I/O (low-level I/O)。 由于计算机系统各不相同,所以不可能为普通的底层I/O函数创建标准库, ANSI C也不打算这样做
然而从较高层面上,C还可以通过标准I/O包 (standard I/O package)来处理文件。这涉及创建用于处理文件的标准模型和一套标准I/O函数。在这一层面上,具体的C实现负责处理不同系统的差异,以便用户使用统一的界面。
上面讨论的差异指的是什么?例如,不同的系统储存文件的方式不同。 有些系统把文件的内容储存在一处,而文件相关的信息储存在另一处;有些系统在文件中创建一份文件描述。在处理文件方面,有些系统使用单个换行符标记行末尾,而其他系统可能使用回车符和换行符的组合来表示行末尾。 有些系统用最小字节来衡量文件的大小,有些系统则以字节块的大小来衡量。
如果使用标准 I/O 包,就不用考虑这些差异。因此,可以用 if (ch == ‘\n’)检查换行符。即使系统实际用的是回车符和换行符的组合来标记行末尾,I/O函数会在两种表示法之间相互转换。
流:从概念上看,C程序处理的是流而不是直接处理文件。流(stream)是一个实际输入或输出映射的理想化数据流。这意味着不同属性和不同种类的输入,由属性更统一的流来表示。于是,打开文件的过程就是把流与文件相关联,而且读写都通过流来完成。
键盘输入:绝大部分系统(不是全部)都有办法通过键盘模拟文件结尾条件,C把输入和输出设备视为存储设备上的普通文件,尤其是把键盘和显示设备视为每个C程序自动打开的文件。stdin流表示键盘输入,stdout流表示屏幕输出。getchar()、putchar()、 printf()和scanf()函数都是标准I/O包的成员,用来处理这两个流。
🎈(二)文件结尾
以上讨论的内容说明,可以用处理文件的方式来处理键盘输入。例如, 程序读文件时要能检测文件的末尾才知道应在何处停止。因此,C 的输入函数内置了文件结尾检测器。既然可以把键盘输入视为文件,那么也应该能使用文件结尾检测器结束键盘输入。下面我们从文件开始,学习如何结束文件。计算机操作系统要以某种方式判断文件的开始和结束。
1.检测文件结尾
检测文件结尾的一种方法是,在文件末尾放一个特殊的字符标记文件结尾
CP/M、IBM-DOS和MS-DOS的文本文件曾经用过这种方法。如今,这些操作系统可以使用内嵌的Ctrl+Z字符来标记文件结尾。这曾经是操作系统使用的唯一标记, 不过现在有一些其他的选择,例如记录文件的大小。所以现代的文本文件不一定有嵌入的Ctrl+Z,但是如果有,该操作系统会将其视为一个文件结尾标记
操作系统使用的另一种方法是储存文件大小的信息。
如果文件有3000字节,程序在读到3000字节时便达到文件的末尾。MS-DOS 及其相关系统使用这种方法处理二进制文件,因为用这种方法可以在文件中储存所有的字符, 包括Ctrl+Z。新版的DOS也使用这种方法处理文本文件。UNIX使用这种方法处理所有的文件。
2.返回值EOF
无论操作系统实际使用何种方法检测文件结尾,在C语言中,用getchar()读取文件检测到文件结尾时将返回一个特殊的值,即EOF(end of file的缩写)。scanf()函数检测到文件结尾时也返回EOF
通常, EOF定义在stdio.h文件中:#define EOF (-1)
为什么是-1?因为getchar()函数的返回值通常都介于0~127,这些值对应标准字符集。但是,如果系统能识别扩展字符集,该函数的返回值可能在 0~255之间。无论哪种情况,-1都不对应任何字符,所以,该值可用于标记文件结尾。
某些系统也许把EOF定义为-1以外的值,但是定义的值一定与输入字符所产生的返回值不同。如果包含stdio.h文件,并使用EOF符号,就不必担心EOF值不同的问题。这里关键要理解EOF是一个值,标志着检测到文件结尾,并不是在文件中找得到的符号。
如何在程序中使用EOF?
把getchar()的返回值和EOF作比较。如果两值不同,就说明没有到达文件结尾。也就是说,可以使用下面这样的表达式:
int ch;
while ((ch = getchar()) != EOF)
- 不用定义EOF,因为stdio.h中已经定义过了。
- 不用担心EOF的实际值,因为EOF在stdio.h中用#define预处理指令定义,可直接使用,不必再编写代码假定EOF为某值。
- 变量ch的类型从char变为int,因为char类型的变量只能表示0~255的无符号整数,但是EOF的值是-1,还好,getchar()函数实际返回值的类型是 int,所以它可以读取EOF字符。如果实现使用有符号的char类型,也可以把 ch声明为char类型,但最好还是用更通用的形式。
- 由于getchar()函数的返回类型是int,如果把getchar()的返回值赋给char类型的变量,一些编译器会警告可能丢失数据。
- 使用该程序进行键盘输入,要设法输入EOF字符。不能只输入字符EOF,也不能只输入-1(输入-1会传送两个字符:一个连字符和一个数字 1)。正确的方法是,必须找出当前系统的要求。例如,在大多数UNIX和 Linux系统中,在一行开始处按下Ctrl+D会传输文件结尾信号。许多微型计算机系统都把一行开始处的Ctrl+Z识别为文件结尾信号,一些系统把任意位置的Ctrl+Z解释成文件结尾信号。
⛳四、重定向连接程序和文件
程序可以通过两种方式使用文件。
第 1 种方法是,显式使用特定的函数打开文件、关闭文件、读取文件、写入文件,诸如此类。
第 2 种方法是,设计能与键盘和屏幕互动的程序,通过不同的渠道重定向输入至文件和从文件输出。
换言之,把stdin流重新赋给文件。继续使用getchar()函数从输入流中获取数据,但它并不关心从流的什么位置获取数据。虽然这种重定向的方法在某些方面有些限制,但是用起来比较简单,而且能让读者熟悉普通的文件处理技术。
重定向的一个主要问题与操作系统有关,与C无关。尽管如此,许多 C 环境中(包括UNIX、Linux和Windows命令提示模式)都有重定向特性,而且一些C实现还在某些缺乏重定向特性的系统中模拟它。
🎈(一)重定向输入
在默认情况下,C程序使用标准I/O包查找标准输入作为输入源。这就是前面介绍过的stdin流,它是把数据读入计算机的常用方式。它可以是一个过时的设备,如磁带、穿孔卡或电传打印机,或者(假设)是键盘,甚至是一 些先进技术,如语音输入。然而,现代计算机非常灵活,可以让它到别处查找输入。尤其是,可以让一个程序从文件中查找输入,而不是从键盘。
UNIX(运行命令行模式时)、Linux(ditto)和Window命令行提示(模仿旧式DOS命令行环境)都能重定向输入、输出
重定向输入让程序使用文件而不是键盘来输入,例如:
echo_eof < words
对于UNIX、Linux和Windows命令提示,<两侧的空格是可选的。一些系统,如AmigaDOS(那些喜欢怀旧的人使用的系统),支持重定向,但是在重定向符号和文件名之间不允许有空格。
<符号是UNIX和DOS/Windows的重定向运算符。该运算符使words文件与stdin流相关联,把文件中的内容导入echo_eof程序。echo_eof程序本身并不知道(或不关心)输入的内容是来自文件还是键盘,它只知道这是需要导入的字符流,所以它读取这些内容并把字符逐个打印在屏幕上,直至读到文件结尾。因为C把文件和I/O设备放在一个层面,所以文件就是现在的I/O设备
🎈(二)重定向输出
重定向输出让程序输出至文件而不是屏幕,例如:
echo_eof > mywords
>符号是第2个重定向运算符。它创建了一个名为mywords的新文件,然后把echo_eof的输出(即,你输入字符的副本)重定向至该文件中。重定向把stdout从显示设备(即,显示器)赋给mywords文件。如果已经有一个名为 mywords的文件,通常会擦除该文件的内容,然后替换新的内容(但是,许多操作系统有保护现有文件的选项,使其成为只读文件)。所有出现在屏幕的字母都是你刚才输入的,其副本储存在文件中。在下一行的开始处按下 Ctrl+D(UNIX)或Ctrl+Z(DOS)即可结束该程序
🎈(三)组合重定向
假设希望制作一份mywords文件的副本,并命名为savewords。 只需输入以下命令即可:
echo_eof < mywords > savewords
//或
echo_eof > savewords < mywords
//错误用法:在一条命令中,输入文件名和输出文件名不能相同。
echo_eof < mywords > mywords....
//原因是> mywords在输入之前已导致原mywords的长度被截断为0。
在UNIX、Linux或Windows/DOS系统中使用两个重定向运算符 (<和>)时,要遵循以下原则:
- 重定向运算符连接一个可执行程序(包括标准操作系统命令)和一个数据文件,不能用于连接一个数据文件和另一个数据文件,也不能用于连接一个程序和另一个程序。
- 使用重定向运算符不能读取多个文件的输入,也不能把输出定向至多个文件。
- 通常,文件名和运算符之间的空格不是必须的,除非是偶尔在UNIX shell、Linux shell或Windows命令行提示模式中使用的有特殊含义的字符。
- 命令与重定向运算符的顺序无关
拓展:
重定向是一个命令行概念,因为我们要在命令行输入特殊的符号发出指令
如果不使用命令行环境,也可以使用重定向。首先,一些集成开发环境提供了菜单选项,让用户指定重定向。其次,对于 Windows系统,可以打开命令提示窗口,并在命令行运行可执行文件。
UNIX、Linux或Windows/DOS 还有>>运算符,该运算符可以把数据添加 到现有文件的末尾,而 | 运算符能把一个文件的输出连接到另一个文件的输入。
⛳五、输入输出中常遇到的问题
🎈(一)缓冲输入带来的问题
混合使用 getchar()和 scanf()时,如果在调用 getchar()之前,scanf()在输入行留下一个换行符,会导致一些问题。不过,意识到这个问题就可以在程序中妥善处理
🎈(二)混合数值和字符输入
假设程序要求用 getchar()处理字符输入,用 scanf()处理数值输入,这两个函数都能很好地完成任务,但是不能把它们混用。因为 getchar()读取每个字符,包括空格、制表符和换行符;而 scanf()在读取数字时则会跳过空格、 制表符和换行符。
⛳六、输入验证(防御性编程)
在实际应用中,用户不一定会按照程序的指令行事。用户的输入和程序期望的输入不匹配时常发生,这会导致程序运行失败。作为程序员,除了完成编程的本职工作,还要事先预料一些可能的输入错误,这样才能编写出能检测并处理这些问题的程序。
常用的方法:
- 使用关系表达式来排除
- 检查scanf()的返回值
- 常需要涉及if选择结构
⛳七、C语言实现菜单浏览
许多计算机程序都把菜单作为用户界面的一部分。菜单给用户提供方便的同时,却给程序员带来了一些麻烦。
同样也是第五点中提到的问题
🚀文件输入/输出
⛳一、C文件基本知识
关于文件的基本知识,以及使用重定向输出到文件中等等已经在上面字符输入/输出和输入验证部分讲解,就不再总结了,这里补充未提到的知识点
🎈(一)文本模式和二进制模式
C提供两种文件模式:文本模式和二进制模式。
1.文本模式
如果要求在外存上以ASCII代码形式存储,则需要在存储前进行转换。ASCII文件又称文本文件(text file),每一个字节存放一个字符的ASCII代码。
2.二进制模式
数据在内存中是以二进制形式存储的,如果不加转换地输出到外存,就是二进制文件
一个数据在磁盘上怎样存储呢?
字符一律以ASCII形式存储,数值型数据既可以用ASCII形式存储,也可以用二进制形式存储。如有整数10000,如果用ASCII码形式输出到磁盘,则在磁盘中占5个字节(每一个字符占一个字节),而用二进制形式输出,则在磁盘上只占4个字节(用Visual C++时),见图10.1。
🎈(二)文件缓冲区
和之前讲的缓冲区的概率一样,ANSIC标准采用“缓冲文件系统”处理数据文件,所谓缓冲文件系统是指系统自动地在内存区为程序中每一个正在使用的文件开辟一个文件缓冲区。从内存向磁盘输出数据必须先送到内存中的缓冲区,装满缓冲区后才一起送到磁盘去。如果从磁盘向计算机读入数据,则一次从磁盘文件将一批数据输入到内存缓冲区(充满缓冲区),然后再从缓冲区逐个地将数据送到程序数据区(给程序变量),见图10.2。这样做是为了节省存取时间,提高效率,缓冲区的大小由各个具体的C编译系统确定。
🎈(三)文件类型指针
缓冲文件系统中,关键的概念是“文件类型指针”,简称“文件指针”。每个被使用的文件都在内存中开辟一个相应的文件信息区,用来存放文件的有关信息(如文件的名字、文件状态及文件当前位置等)。这些信息是保存在一个结构体变量中的。该结构体类型是由系统声明的取名为FILE。例如有一种C编译环境提供的stdio. h头文件中有以下的文件类型声明:
typedef struct{
short level; //缓冲区“满”或“空”的程度
unsigned flags; //文件状态标志
char fd; //文件描述符
unsigned char hold; //如缓冲区无内容不读取字符
short bsize; //缓冲区的大小
unsigned char * buffer; //数据缓冲区的位置
unsigned char * curp; //文件位置标记指针当前的指向
unsigned istemp; //临时文件指示器
short token //用于有效性检查
}FILE;
//不同的C编译系统的FILE类型包含的内容不完全相同,但大同小异。对以上结构体中的成员及其含义可不深究,只须知道其中存放文件的有关信息即可。
例如:
//定义一个FILE类型的变量
FILE f1;
定义了一个结构体变量fl,用它来存放一个文件的有关信息。这些信息是在打开一个文件时由系统根据文件的情况自动放入的,在读写文件时需要用到这些信息,也会修改某些信息。例如在读一个字符后,文件信息区中的位置标记指针的指向就要改变。
//定义一个指向文件型数据的指针变量:
FILE *FP;
定义fp是一个指向FILE类型数据的指针变量。可以使fp 指向某一个文件的文件信息区(是一个结构体变量),通过该文件信息区中的信息就能够访问该文件。也就是说,通过文件指针变量能够找到与它关联的文件。
stdio.h头文件把3个文件指针与3个标准文件相关联,C程序会自动打开这3个标准文件。
这些文件指针都是指向FILE的指针,所以它们可用作标准I/O函数的参数,如fclose(fp)中的fp
⛳二、标准I/O
标准I/O中有许多专门对文件操作的I/O函数,我们称为文件I/O,常以f开头,如fopen()、fgets()等
Linux中提供了一系列由系统调用的用来输入输出的函数,我们也称为文件I/O,注意与这里讲的文件I/O做区分,这里只是简单将专门用于文件输入输出的I/O归一下类。
🎈(一)打开和关闭文件
1.fopen()
ANSI C规定了用标准输入输出函数fopen来实现打开文件。
调用方法:
fopen(文件名,使用文件方式);
例如:
fopen("a1","r");
表示要打开名字为a1的文件,使用文件方式为“读入”(r代表read,即读入)。fopen函数的返回值是指向a1文件的指针(即al文件信息区的起始地址)。通常将fopen函数的返回值赋给一个指向文件的指针变量。如:
FILE* fp;
//定义一个指向文件的指针变量fp
fp=fopen("a1","r");
//将fopen函数的返回值赋给指针变量fp
2.fclose()
在使用完一个文件后应该关闭它,以防止它再被误用。
调用方法:
fclose(文件指针);
例如:
fclose(fp);
如果不关闭文件就结束程序运行将会丢失数据。因为,在向文件写数据时,是先将数据输出到缓冲区,待缓冲区充满后才正式输出给文件。如果当数据未充满缓冲区时程序结束运行,就有可能使缓冲区中的数据丢失。用fclose函数关闭文件时﹐先把缓冲区中的数据输出到磁盘文件,然后才撤销文件信息区。有的编译系统在程序结束前会自动先将缓冲区中的数据写到文件﹐从而避免了这个问题,但还是应当养成在程序终止之前关闭所有文件的习惯。
fclose函数也带回一个值,当成功地执行了关闭操作,则返回值为o﹔否则返回EOF(—1)。
🎈(二)顺序读写文件
1.getc()、putc()、fgetc()和fputc()
(1)getc()和putc()
getc()和putc()函数与getchar()和putchar()函数类似。所不同的是,要告诉 getc()和putc()函数使用哪一个文件。例如:
//从标准输入中获取一个字符
ch = getchar();
//从fp指定的文件中获取一个字符
ch = getc(fp);
在putc()函数的参数列表中,第1个参数是待写入的字符,第2个参数是文件指针。stdout作为与标准输出相关联的文件指针,定义在stdio.h中,所以putc(ch, stdout)与putchar(ch)的作用相同。
//把字符ch放入FILE指针fpout指定的文件中
putc(ch, fpout);
//把stdout作为putc()的第2个参数
putc(ch, stdout);
(2)fgetc()和fputc()
fgetc()和fputc()与getc()和putc()功能一样,都是从指定的流中获取字符和将指定的字符输入到指定的流中:
//函数原型:
int fgetc(FILE *stream);
int getc(FILE *stream);
int fPutc(int c,FILE *stream); //c代表字符对应的ASCII码值
int Putc(int c,FILE *stream);
(3)区别
从表面上看,fgetc和getc的功能相同,fputc和putc的功能相同,那他们的区别在哪里呢?
注意:fgetc和fputc本质上函数,而getc和putc本质上是宏!
- 调用宏(getc、putc)时,宏的参数不能是有副作用的表达式。有副作用的表达式,指的是表达式执行后,会改变表达式中某些变量的值,如++i。
- 函数可以获取其地址,并将其赋值给函数指针,而宏不可以获取地址。
- 调用宏的效率要高于调用函数。
2.fprintf()、fscanf()、fgets()和fputs()
(1)fprintf()和fscanf()
文件I/O函数fprintf()和fscanf()函数的工作方式与printf()和scanf()类似, 区别在于前者需要用第1个参数指定待处理的文件。
调用方式:
fprintf(文件指针,格式字符串,输出表列);
fscanf(文件指针,格式字符串,输入表列)﹔
//例如:
fprintf(fp,"%d,%6.2f",i,f);
fscanf(fp,"%d,%f",&i,&f);
拓展:
用fprint和 fscanf 函数对磁盘文件读写,使用方便,容易理解,但由于在输入时要将文件中的ASCII码转换为二进制形式再保存在内存变量中,在输出时又要将内存中的二进制形式转换成字符,要花费较多时间。因此,在内存与磁盘频繁交换数据的情况下,最好不用fprintf 和 fscanf 函数,而用下面介绍的fread和fwrite函数进行二进制的读写。
C语言允许用 fread 函数从文件中读一个数据块﹐用fwrite函数向文件写一个数据块。在读写时是以二进制形式进行的。在向磁盘写数据时,直接将内存中一组数据原封不动、不加转换地复制到磁盘文件上,在读入时也是将磁盘文件中若干字节的内容一批读入内存。
注意要在打开文件时指定用二进制文件,这样就可以用fread和fwrite函数读写任何类型的信息
调用方式:
fread(buffer,size,count,fp) ; fwrite(buffer,size,count,fp); /* buffer:是一个地址。对fread来说,它是用来存放从文件读入的数据的存储区的地址。对fwrite来说,是要把此地址开始的存储区中的数据向文件输出(以上指的是起始地址)。 size:要读写的字节数。 count :要读写多少个数据项(每个数据项长度为size)。 fp:FILE类型指针。 */ //例如: fread(f,4,10,fp) ; //其中,f是一个float型数组名(代表数组首元素地址)。这个函数从fp所指向的文件读入10个4个字节的数据,存储到数组f中。
fwrite()函数和fread()函数都返回size_t类型,size_t是根据标准C类型定义的类型,它是sizeof运算符返回的类型,通常是unsigned int,但是实现可以选择使用其他类型。实际上fwrite()函数返回成功写入项的数量。fread()函数返回成功读取项的数量。
(2)fgets()和fputs()
//函数原型:
char *fgets(char *str,int n,FILE *fp);
int fput(char *str,FILE *fp);
fgets()函数读取输入直到第 1 个换行符的后面,或读到文件结尾,或者读取STLEN-1 个字符(以上面的 fgets()为例)。然后,fgets()在末尾添加一个空字符使之成为一个字符串。
如果fgets()在读到字符上限之前已读完一整行,它会把表示行结尾的换行符放在空字符前面。
fgets()函数在遇到EOF时将返回NULL值,可以利用这一机制检查是否到达文件结尾;如果未遇到EOF则之前返回传给它的地址。
fputs()函数接受两个参数:第1个是字符串的地址;第2个是文件指针。 该函数根据传入地址找到的字符串写入指定的文件中。和 puts()函数不同, fputs()在打印字符串时不会在其末尾添加换行符。由于fgets()保留了换行符,fputs()就不会再添加换行符,它们配合得非常好。
🎈(三)随机读写文件
1.文件位置标记
为了对读写进行控制,系统为每个文件设置了一个文件读写位置标记(简称文件位置标记或文件标记),用来指示“接下来要读写的下一个字符的位置”。
一般情况下,在对字符文件进行顺序读写时,文件位置标记指向文件开头,这时如果对文件进行读的操作,就读第1个字符,然后文件位置标记向后移一个位置,在下一次执行读的操作时,就将位置标记指向的第⒉个字符读入。依此类推,遇到文件尾结束。顺序写文件也一样
所谓随机读写,是指读写完上一个字符(字节)后,并不一定要读写其后续的字符(字节),而可以读写文件中任意位置上所需要的字符(字节)。即对文件读写数据的顺序和数据在文件中的物理顺序一般是不一致的。可以在任何位置写入数据﹐在任何位置读取数据。
2.fseek()和tell()
同样,需要以二进制模式打开文件
(1)我们可以使用fseek()函数强制使文件位置标记指向人们指定的位置。
调用形式:
fseek(文件类型指针,位移量,起始点)
//例如:
fseek (fp,100L,0); //将文件位置标记向前移到离文件开头 100个字节处
fseek (fp,50L,1); //将文件位置标记向前移到离当前位置50个字节处
fseek (fp,-10L,2); //将文件位置标记从文件末尾处向后退10个字节
- “起始点”用0,1或2代替,0代表“文件开始位置”,1为“当前位置”,2为“文件末尾位置”。C标准指定的名字如表10.4所示。
- “位移量”指以“起始点”为基点,向前移动的字节数。位移量应是long型数据(在数字的末尾加一个字母L,就表示是long 型)。
- fseek()返回一个int类型的值,如果一切正常,返回0,如果出现错误(如试图移动的距离超出文件的范围),其返回值为-1
拓展:
rewind()函数
rewind 函数的作用是使文件位置标记重新返回文件的开头,此函数没有返回值。参数为一个文件指针
(2)用ftell函数测定文件位置标记的当前位置
调用方式:
ftell(文件类型指针);
ftell()函数返回一个long类型的值,表示文件中的当前位置(距文件开始处的字节数),如果调用函数时出错(如不存在fp指向的文件),ftell 函数返回值为一1L。
3.文件读写的出错检测
(1)ferror函数
在调用各种输入输出函数(如 putc,getc,fread和fwrite等)时,如果出现错误,除了函数返回值有所反映外,还可以用ferror函数检查。它的一般调用形式为
ferror(fp);
如果ferror返回值为o(假),表示未出错;如果返回一个非零值,表示出错。
拓展:
feof()函数:
如果标准输入函数返回 EOF,则通常表明函数已到达文件结尾。然而,出现读取错误时,函数也会返回EOF。feof()和ferror()函数用于区分这两种情况。当上一次输入调用检测到文件结尾时,feof()函数返回一个非零值,否则返回0。当读或写出现错误,ferror()函数返回一个非零值,否则返回0。
(2)clearerrh函数
只要出现文件读写出错标志﹐它就一直保留﹐直到对同一文件调用clearerr函数或rewind函数,或任何其他一个输入输出函数。
clearerr的作用是使文件出错标志和文件结束标志置为0。假设在调用一个输入输出函数时出现错误, ferror函数值为一个非零值。应该立即调用clearerr(fp),使ferror(fp)的值变成0,以便再进行下一次的检测。
⛳三、其它标准I/O函数
(一)fgetpos()函数和fsetpos()函数
fseek()和 ftell()潜在的问题是,它们都把文件大小限制在 long 类型能表示的范围内。但是随着存储设备的容量迅猛增长,文件也越来越大。鉴于此,ANSI C新增了两个处理较大文件的新定 位函数:fgetpos()和 fsetpos()。
这两个函数不使用 long 类型的值表示位置, 它们使用一种新类型:fpos_t(代表file position type,文件定位类型)。 fpos_t类型不是基本类型,它根据其他类型来定义。fpos_t 类型的变量或数据对象可以在文件中指定一个位置,它不能是数组类型,除此之外,没有其他限制。
函数原型:
int fgetpos(FILE * restrict stream, fpos_t * restrict pos);
调用该函数时,它把fpos_t类型的值放在pos指向的位置上,该值描述了文件中的一个位置。如果成功,fgetpos()函数返回0;如果失败,返回非0。
int fsetpos(FILE *stream, const fpos_t *pos);
调用该函数时,使用pos指向位置上的fpos_t类型值来设置文件指针指向该值指定的位置。如果成功,fsetpos()函数返回0;如果失败,则返回非0。 fpos_t类型的值应通过之前调用fgetpos()获得。
*(二)int ungetc(int c,FILE fp)函数
int ungetc()函数把c指定的字符放回输入流中。如果把一个字符放回输入流,下次调用标准输入函数时将读取该字符
(三)int fflush()函数
函数原型:
int fflush(FILE *fp);
调用fflush()函数引起输出缓冲区中所有的未写入数据被发送到fp指定的输出文件。这个过程称为刷新缓冲区。如果 fp是空指针,所有输出缓冲区 都被刷新。
(四)int setvbuf()函数
函数原型:
int setvbuf(FILE * restrict fp, char * restrict buf, int mode, size_t size);
setvbuf()函数创建了一个供标准I/O函数替换使用的缓冲区。在打开文件后且未对流进行其他操作之前,调用该函数。
- 指针fp识别待处理的流,
- buf 指向待使用的存储区。如果buf的值不是NULL,则必须创建一个缓冲区。如果把 NULL作为buf的值,该函数会为自己分配一个缓冲区。
- 变量size告诉setvbuf() 数组的大小
- mode的选择如 下:_IOFBF表示完全缓冲(在缓冲区满时刷新);_IOLBF表示行缓冲(在缓冲区满时或写入一个换行符时);_IONBF表示无缓冲。如果操作成功, 函数返回0,否则返回一个非零值。
⛳四、二进制随机访问例子
标注I/O的机理;
第一步:调用fopen()打开文件(前面介绍过,C程序会自动打开3种标准文件)
fopen()函数不仅打开一个文件,还创建了一个缓冲区(在读写模式下会创建两个缓冲区)以及一个包含文件和缓冲区数据的结构。另外,fopen()返回一个指向该结构的指针,以便其他函数知道如何找到该结构。假设把该指针赋给一个指针变量fp,我们说fopen()函数“打 开一个流”。如果以文本模式打开该文件,就获得一个文本流;如果以二进 制模式打开该文件,就获得一个二进制流。
第二步:调用一个定义在 stdio.h中的输入函数
由于 stdio.h系列的所有输入函数都使用相同的缓冲区,所以调用任何一个函数都将从上一次函数停止调用的位置开始。
第三步:输出函数以类似的方式把数据写入缓冲区。当缓冲区被填满时,数据将被拷贝至文件中。
【例10.6】在磁盘文件上存有10个学生的数据。要求将第1,3,5,7,9个学生数据输入计算机,并在屏幕上显示出来。
- 按“二进制只读”的方式打开指定的磁盘文件,准备从磁盘文件中读取学生数据。
- 将文件位置标记指向文件的开头,然后从磁盘文件读入一个学生的信息,并把它显示在屏幕上。
- 再将文件位置标记指向文件中第3,5,7,9个学生的数据区的开头﹐从磁盘文件读入相应学生的信息,并把它显示在屏幕上。
- 关闭文件。
#include<stdio.h>
#include <stdlib.h>
//学生数据类型
struct Student_type
{
char name[10];
int num;
int age;
char addr[15];
}stud[10];
int main()
{
int i;
FILE* fp;
//以只读方式打开二进制文件
if((fp=fopen("stu. dat","rb"))==NULL)
{
printf("can not open file\n") ;
exit(O);
}
for(i=o;i<10;i+=2)
{
fseek(fp, i * sizeof(struct Student_type),0); //移动文件位置标记
fread(&.stud[i],sizeof(struct Student_type),1,fp); //读一个数据块到结构体变量
printf("%-10s %4d %4d %-15s\n",stud[i].name,stud[i].num,stud[i].age,stud[i].addr);//在屏幕输出
}
fclose(fp);
return 0;
}