一、I/O流操作一般流程:

(1)为每一个要打开的文件定义一个FILE *类型的指针变量,这个指针变量将指向I/O流使用的FILE结构体。

(2)使用fopen函数打开I/O流。要打开一个I/O流,必须指定要打开的文件(或设备)以及打开后的访问方式(如:只读、只写或读写等)。

(3)按照需要的操作读写文件。

(4)最后,使用fclose函数关闭该I/O流。

在标准流(stdin、stdout和stderr)上进行I/O操作不需要打开和关闭。(stdin、stdout和stderr其实也是执行FILE结构体的指针,它们是由运行时环境提供的)。

I/O函数处理数据的方式分为三种:单字符、文本行和二进制数据。不同的方式使用不同的函数集处理。

二、打开I/O流

1、fopen

FILE *fopen( char const *name, char const *mode );

参数name和mode都是字符串。

参数name是要打开的文件名,参数mode是打开方式(文本打开方式:“r”、“w”、“a”,二进制打开方式:“rb”、“wb”、“ab”)。

函数返回值由之前定义的FILE *类型的变量保存。如果成功,则返回一个指向FILE结构体的指针;如果失败,则返回NULL,并且失败原因代码记录在errno中。

使用“r”方式打开的文件应该已经存在,如果文件不存在则出错。

使用“w”方式打开的文件无论存在还是不存在,都会新建一个name命名的文件(如果存在会先将原来的同名文件删除后再新建)。

使用“a”方式打开的文件如果存在,不删除,打开后位置指针指向移到文件尾,如果文件不存在则新建。

另外,如果在模式字符后加上一个加号“+”,那么无论该模式是只读、只写还是追加,加上加号“+”后都将变为可读写。需要注意的是,当你以可读写的方式打开一个文件进行读写操作时:如果你对该文件进行了读操作,那么在你对该文件开始进行写操作之前必须调用文件定位函数(fseek、fsetpos和rewind);在你对该文件进行了写操作之后,那么在你对该文件开始进行读操作之前必须调用fflush或者文件定位函数。

注意:即便是“r”加上了加号“r+”,虽然由只读变为可读写,但是打开时依然要求文件存在,如果不存在则出错。

提醒:无论何时,只要调用fopen函数,务必要检查其返回值。通常使用fopen的格式如下:

FILE *input;

input = fopen( "data3", "r" );
if( input == NULL )
{
perror( "data3" );
exit( EXIT_FAILURE );
}

2、freopen

FILE *freopen( char const *filename, char const *mode, FILE *stream );

freopen函数用来打开(或重新打开)一个文件上的特定的流。

参数stream是将要被打开的流。它可能是之前fopen返回的,也可能是标准流(stdin、stdout、stderr)。

freopen函数首先会关闭参数stream代表的流,然后使用给定的文件filename和模式mode重新打开这个流。如果成功,则返回参数stream的值;如果失败,则返回NULL。

三、关闭I/O流

int fclose( FILE *f );

对于输出流(以“w”方式打开的流),fclose会在关闭该流之前冲刷缓冲区(也就是将缓冲区中的内容写到磁盘文件中)。

成功,则返回0;失败,则返回EOF。

所有有可能会失败的操作都要进行检查,fclose返回值检查格式如下:

if( fclose( input ) !=  )
{
perror( "fclose" );
exit( EXIT_FAILURE );
}

四、字符I/O

1、字符输入操作使用getchar函数家族中的函数,这些函数原型如下:

int fgetc( FILE *stream );
int getc( FILE *stream );
int getchar( void );

fgetc和getc从指定的流stream中获取要输入的字符,而getchar总是从标准输入中获取要输入的字符。

上面三个函数都是从流中读取下一个字符(当然第一次调用这些函数读的是流中的第一个字符,第二次调用就读第二个字符,以此类推)并且将该字符作为函数返回值。如果流中字符被读取完,后面不再有字符,那么返回EOF。

注意:上述三个函数目的都是读取字符,但是函数返回值不是char而是int,其真正原因是为了使得函数可以报告文件结尾EOF。EOF是被定义为int的,这样EOF就在所有可能的字符集范围之外。

2、字符输出操作使用putchar函数家族中的函数,这些函数原型如下:

int fputc( int character, FILE *stream );
int putc( int character, FILE *stream );
int putchar( int character );

第一个参数是将要打印的字符。上述函数会将int参数截断为unsigned char,然后再打印。

成功的话,上述函数会返回打印的字符;如果出错,上述函数会返回EOF。

3、撤消字符I/O

int ungetc( int character, FILE *stream );

ungetc函数会将character读回到stream中,以便该字符可以被再次读取,并且ungetc将character作为返回值。需要注意的是,已经读取并存储了该字符的变量值不会受到影响。

通过下面的例子可以更直观的理解:

#include <stdio.h>
#include <stdlib.h>
void main()
{
char c1;
char c2;
char c3;
char c4;
char c5;
char c6; c1 = getchar();
c2 = getchar();
c3 = getchar();
c4 = ungetc( c3, stdin );
c5 = getchar();
c6 = getchar(); printf( "c1=%c\n", c1 );
printf( "c2=%c\n", c2 );
printf( "c3=%c\n", c3 );
printf( "c4=%c\n", c4 );
printf( "c5=%c\n", c5 );
printf( "c6=%c\n", c6 );
}

五、非格式化行I/O

面向行的I/O可以有两种处理方式:非格式化和格式化。这两种方式的操作对象都是字符串。区别在于,非格式化行I/O只是简单地读写字符串,而格式化行I/O会对数字和其他变量的内部和外部表示形式进行转换。

gets函数家族和puts函数家族针对字符串进行操作,它们的原型如下:

char *fgets( char *buffer, int buffer_size, FILE *stream );
char *gets( char * buffer ); int fputs( char const *buffer, FILE *stream );
int puts( char const *buffer );

1、fgets

fgets从指定的流stream中读取字符串,并把读到的字符串拷贝到buffer中。读到换行符(newline)并将换行符存储到buffer后停止读取。如果已经读取了buffer_size-1个字符,这时也会停止读取。无论是何种原因导致停止读取,停止读取后都会在buffer末尾添加一个字符串结束符NUL('\0')。

如果什么都还没有读取就读到了文件末尾,那么buffer不会改变,并且fgets会返回指针NULL。否则fgets会返回指向buffer的指针。其返回值经常用来检测是否到达了文件末尾。

2、fputs

传递给fputs的参数buffer必须包含一个字符串,而且这个字符串要以NUL('\0')结尾。这个字符串会被写到参数stream中。对该字符串,fputs是逐个字符进行写入的:如果该字符串中不包含换行符(newline),那么就不会写入换行符;如果该字符串中包含多个换行符,那么这些换行符都会被写入。由此,我们可以看出:fgets是一次尽力去读取一整行;而fputs则可以一次写入一行的一部分、一整行或者多行。

如果发生错误,fputs会返回EOF;否则它会返回一个非负值。

3、gets和puts

gets和puts基本上跟fputs和fgets相同。主要功能上的区别在于:

同fgets相比,gets读取输入流中的一行(以换行符结尾),但是它不会存储换行符(newline):也就是说gets会丢弃换行符,然后再在行尾加上一个NUL('\0')。

同fputs相比,puts在写入字符串时,会在字符串后添加一个换行符(newline)。

注意:gets与fgets的另外一个区别是gets没有buffer_size参数。因此gets无法决定buffer的长度。如果读入的行长度超过了buffer所能容纳的大小,那么将会破坏内存中位于buffer之后的毫不相干的变量。即出现了缓冲区越界,这是很危险的。这是gets函数自身存在的一个漏洞,建议慎重使用该函数。

六、格式化行I/O

1、scanf family

int fscanf( FILE *stream, char const *format, ... );
int scanf( char const *format, ... );
int sscanf( char const *string, char const *format, ... );

简单来说,上面三个输入函数主要区别在于输入源不同:fscanf从stream中输入;scanf从标准输入中输入;sscanf则从字符串string中输入。

当到达格式化字符串format结尾或者读到的输入跟格式化字符串不匹配时,会停止输入。

上述函数会将转换的输入个数作为函数返回值。如果一个输入都还没有转换就读到了文件结尾则返回EOF。

fscanf、scanf以及sscanf都会跳过空白字符(包括空格、Tab和换行等)。

2、printf family

int fprintf( FILE *stream, char const *format, ... );
int printf( char const *format, ... );
int sprintf( char *buffer, char const *format, ... );

简单来讲,printf输出到标准输出;fprintf输出到指定的流stream中;sprintf则将以NUL结尾的字符串输出到指定的buffer中。

七、二进制I/O

fread用来读取二进制数据,fwrite用来写二进制数据。

size_t fread( void *buffer, size_t size, size_t count, FILE *stream );
size_t fwrite( void *buffer, size_t size, size_t count, FILE *stream );

buffer是指向保存二进制数据区域的指针。size表示buffer中每个元素的字节数。count表示要读写多少个这样的元素。stream是要读写的流。

fread和fwrite会返回实际读写的元素个数。这个返回值有可能比count要小(由于读到了文件结尾或者写时出错)。

八、冲刷和定位函数

1、fflush

fflush会强制将输出缓冲区中的内容写入到磁盘文件(或设备)中,即使输出缓冲区还没有满,fflush也会强行将其中的内容冲刷出来到它该去的地方去。

int fflush( FILE *stream );

fflush(NULL) flushes all streams opened for output.

What does fflush(stdin) do?

This function is used to flush any data in output stream. So this will compile but its behavior is undefined by the ANSI C standard. The fflush() function is only meant to be used on streams open for output, not input. Both fflush(stdin) and fflush(NULL), in some C libraries, will flush stdout and stderr, but this is completely unportable! Thus, it should not be used.

2、随机访问I/O

要实现随机访问,首先需要定位要访问的位置。以下两个函数就是用来实现文件中位置定位的:

long ftell( FILE *stream );
int fseek( FILE *stream, long offset, int from );

ftell函数会返回I/O流的当前读写位置,它是相对于文件开头的偏移量,也是下次读写的开始位置。对于二进制文件,这个偏移量是从文件开始到当前位置的字节数。然而,在文本文件中,这个偏移量虽然也表示当前位置,但它可能并不是十分精确的表示从文件开头到当前位置的字符数。这是因为行结尾字符在不同系统中可能有所转换。

不过,无论是二进制文件还是文本文件,使用ftell得到的返回值都可以作为fseek中表示相对于文件开头的偏移量。

fseek可以改变下次读写的文件的位置。该位置由参数offset和from共同决定。

from 将会定位到...
SEEK_SET 相对于文件开头offest字节的地方;offset必须为非负。
SEEK_CUR 相对于当前位置offset字节的地方;offset可正可负。
SEEK_END 相对于文件末尾offset字节的地方;offset可正可负。

注意:

(1)试图定位到文件开头之前的位置会出错。

(2)定位到文件末尾之后的位置并进行写入操作会扩展文件。

(3)定位到文件末尾之后的位置并进行读取操作会返回文件结束符(end-of-file)。

(4)对于二进制文件,不支持从SEEK_END开始定位,也应避免这样做。

(5)对于文本文件,如果参数from为SEEK_CUR或者SEEK_END,那么offset必须为0。如果参数from为SEEK_SET,那么offset必须是ftell的返回值。

使用fseek改变文件读写位置后会有3个副作用:

(1)文件尾指示符被清除。

(2)如果ungetc在fseek之前调用,那么撤消的字符将被丢弃(下一次读不到该字符,而是读该字符后面的字符)。

(3)可以从读模式切换到写模式。还可以更新打开的流(类似于fflush的功能)。

void rewind( FILE *stream );
int fgetpos( FILE *stream, fpos_t *position );
int fsetpos( FILE *stream, fpos_t const *position );

rewind设置读写指针重新回到文件开头。它还会清除该流的错误标记。

fgetpos与ftell功能相同,fsetpos与fseek功能相同。

九、设置缓冲区

void setbuf( FILE *stream, char *buf );
int setvbuf( FILE *stream, char *buf, int mode, size_t size );

上面两个函数只能在特定的流打开以后,但还没有对该流进行任何其他操作的情况下进行调用。

1、setbuf

setbuf函数的功能是:安装一个数组buf来作为流stream的缓冲区。该数组buf大小必须是BUFSIZ(BUFSIZ在stdio.h中定义)。自己为流指定一个缓冲区后,那么会阻止I/O库动态地为该流分配缓冲区。如果调用setbuf时,参数buf为NULL,那么将会关闭该流的所有缓冲区。

2、setvbuf

setvbuf函数更加通用。参数mode用来指示设置何种类型的缓冲区:_IOFBF(全缓冲)、_IONBF(无缓冲)和_IOLBF(行缓冲)。

使用行缓冲的输出流,每当向缓冲区写入换行符(newline)时就冲刷缓冲区buffer。

参数buf和size是用来指定所用的缓冲区的。如果buf为NULL,那么size必须是0。通常情况下,最好使用一个大小为BUFSIZE的数组作为缓冲区。

十、I/O流错误检查函数

int feof( FILE *stream );
int ferror( FILE *stream );
void clearerr( FILE *stream ):

如果流当前读写指针位于文件尾,那么feof会返回真。函数fseek、rewind或者fsetpos会清除文件尾标识。

如果流发生了任何读写错误,那么ferror会返回真。

clearerr用来重置流的错误状态标识。

《C和指针》之ANSI C标准输入输出函数的更多相关文章

  1. C语言——常用标准输入输出函数 scanf(), printf(), gets(), puts(), getchar(), putchar(); 字符串拷贝函数 strcpy(), strncpy(), strchr(), strstr()函数用法特点

    1 首先介绍几个常用到的转义符 (1)     换行符“\n”, ASCII值为10: (2)     回车符“\r”, ASCII值为13: (3)     水平制表符“\t”, ASCII值为 9 ...

  2. <c和指针>学习笔记6输入输出函数

    1 错误报告 (1)perror函数 void perror(char const *message) error是标准库的一个外部整型变量(errno.h),保存错误代码之后就会把这个信息传递给用户 ...

  3. unlocked - 非锁定的标准输入输出函数

    SYNOPSIS 总览 #include <stdio.h> int getc_unlocked(FILE *stream); int getchar_unlocked(void); in ...

  4. 文件输入输出函数fgetc/fputc及fgets/fputs等文件指针位置的变化

    文件打开后才可以对文件进行操作.也就是说,文件必须经历打开-操作-关闭的过程.如前所述,C语言对文件的操作都是通过调用标准I/O库函数来实现的.文件操作实际是指对文件的读写.文件的读操作就是从文件中读 ...

  5. Linux C 程序 输入输出函数(THREE)

    标准输入输出函数#include<stdio.h>stdio 是 standard input & output 的缩写 字符数据输入输出函数: putchar() , getch ...

  6. 【嵌入式开发】C语言 内存分配 地址 指针 数组 参数 实例解析

    . Android源码看的鸭梨大啊, 补一下C语言基础 ... . 作者 : 万境绝尘 转载请注明出处 : http://blog.csdn.net/shulianghan/article/detai ...

  7. C语言 内存分配 地址 指针 数组 参数 实例解析

    . Android源码看的鸭梨大啊, 补一下C语言基础 ... . 作者 : 万境绝尘 转载请注明出处 : http://blog.csdn.net/shulianghan/article/detai ...

  8. C语言-(void*)类型指针

    (void*)类型指针:ANSI新增的:即定义了一个指针,但不指定指向任何类型(即指向抽象的数据类型). 1 通过强制类型转换可将其值赋给另一指针变量 2.1用于动态存储函数的返回型指针 void m ...

  9. C语言采用文本方式和二进制方式打开文件的区别分析

    稍微了解C程序设计的人都知道,文本文件和二进制文件在计算机上面都是以0,1存储的,那么两者怎么还存在差别呢?对于编程人员来说,文本文件和二进制文件就是一个声明,指明了你应该以什么方式(文本方式/二进制 ...

随机推荐

  1. java易混淆概念之类变量、成员变量、局部变量

      类变量.成员变量.局部变量 类变量(也叫静态变量)是类中独立于方法之外的变量,用static 修饰.(static表示“全局的”.“静态的”,用来修饰成员变量和成员方法,或静态代码块(静态代码块独 ...

  2. 利用redis分布式锁的功能来实现定时器的分布式

    文章来源于我的 iteye blog http://ak478288.iteye.com/blog/1898190 以前为部门内部开发过一个定时器程序,这个定时器很简单,就是配置quartz,来实现定 ...

  3. Some_sort_algorithms

    复习下数据结构常用的排序算法,更多内容上wiki 快速排序(不稳定 O(n log n)) 快速排序(Quicksort)是对冒泡排序的一种改进.由C. A. R. Hoare在1962年提出.它的基 ...

  4. [Hive - LanguageManual] Create/Drop/Alter Database Create/Drop/Truncate Table

    Hive Data Definition Language Hive Data Definition Language Overview Create/Drop/Alter Database Crea ...

  5. grep in linux

    1.作用linux系统中grep命令是一种强大的文本搜索工具,它能使用正则表达式搜索文本,并把匹配的行打印出来.grep全称是Global Regular Expression Print,表示全局正 ...

  6. 30 分钟 Java Lambda 入门教程

    Lambda简介 Lambda作为函数式编程中的基础部分,在其他编程语言(例如:Scala)中早就广为使用,但在Java领域中发展较慢,直到java8,才开始支持Lambda. 抛开数学定义不看,直接 ...

  7. JavaSE聊天室

    今天学习了一下简单聊天程序(类似QQ那种)的编写过程,从最初的0.1版本到最后的1.3版本,功能不断地增强,下面对一天的学习内容进行梳理. 版本0.1 我们的需求是显示一个窗体,其他什么也不用做,其他 ...

  8. C#正则表达式判断字符串是否是金钱

    public static bool IsMoney(string input) { string pattern = @"^\-{0,1}[0-9]{0,}\.{0,1}[0-9]{1,} ...

  9. 最新CentOS6.x下redis安装

    1:软件环境: 系统版本:CentOS release 6.5 redis版本:redis-cli 3.0.5 安装目录:"/usr/local/redis" 下载软件:" ...

  10. codeforces 630D Hexagons!

    D. Hexagons! time limit per test 0.5 seconds memory limit per test 64 megabytes input standard input ...