要学习应用程序设计,首先I/O是肯定要了解的,在这里我们先来了解第一个标准I/O的知识点,从流开始说起。
一.流的含义
标准I/O的核心对象就是流。当用标准I/O打开一个文件时,就会创建一个FILE结构体描述该文件(或者理解为 创建一个FILE结构体和实际打开的文件关联起来)我们把这个FILE结构体形象的称为流。标准I/O函数都基于流来进行各种操作。
标准I/O中的流的缓冲类型有以下3种:
1.全缓冲
在这种情况下,当填满了标准I/O缓冲区后才进行实际的I/O操作。对于存放在磁盘上的普通文件,用标准I/O打开时默认就是全缓冲的,当缓冲区已满或执行flush操作才会进行磁盘操作。
2.行缓冲
在这种情况下,当在输入和输出中遇到换行符时执行I/O操作。标准输入流和标准输出流就是使用行缓冲的典型例子。
3.无缓冲
不对I/O操作进行缓冲,即在对流的读写时会立刻操作在实际的文件。标准出错流是不带缓冲的,这就使得出错的信息可以立刻显示在终端上,而不管输出的内容是否包含换行符。
二.标准I/O编程
这个小点我们要讨论的I/O操作都是基于流的,它符合ANSI C的标准,像(printf() ,scanf()函数等),因此在这里我们仅介绍最常用的函数。
1.流的打开
fopen()
所需的头文件 #include<stdio.h>
函数原型 FILE*fopen(const char* path,const char* mode);
函数参数:
path:包含要打开的文件路径及文件名
mode:文件打开方式
函数的返回值:
成功 返回指向FILE的指针
失败: NULL
fopen里面第二个参数mode说明
mode用于指定打开文件的方式
mode的取值说明
r或rb: 打开只读文件,该文件必须存在
r+或r+b: 打开只写文件,若文件存在则文件的长度为0,即会擦写文件以前的内容,若文件不存在则建立该文件
w或wb: 打开可读写文件,若文件存在则文件的长度为0,即会擦写文件以前的内容;若文件不存在则建立该文件
a或ab: 以附加的方式打开只写文件。若文件不存在,则会建立该文件;如果文件存在,写入的数据会被加到文件尾,即文件原先的内容会被保留。
a+或a+b: 以附加的方式打开可读写文件。若文件不存在,则会建立该文件;如果文件存在,写入的数据会被加到文件尾,即文件原先的内容会被保留。
tips:在每个选项中加入b用来告诉函数库打开的文件为二进制文件,而非纯文本文件。不过在linux系统中会忽略该符号。
2.流的关闭
关闭流的函数为fclose(),该函数将流的缓冲区的数据全部写入文件中,并释放相关资源。
fclose()
头文件:#include<stdio.h>
函数原型 int fclose(FILE* stream)
函数参数: stream 已经打开的流指针
返回值:
成功 0
失败 EOF
3.错误处理
标准I/O函数执行时如果出现错误,会把错误码保存在全局变量errno中,程序员可以通过相应的函数打印错误的信息。
perror()
所需头文件 #include <stdio.h>
函数原型: void perror(const char*s)
函数参数 s 在标准错误流上输出的信息
返回值 没有返回值
光这样说比较空洞,举个例子来看看实际用法
例子:
#include<stdi.h>
int main()
{
FILE*fd; //定义指针流
if(fp=fopen("1.txt","r")==NULL) //NULL是系统定义的宏,其值为0
{
perror("fail to fopen");
return -1;
}
fcolse(fp);
return 0;
}
同学们要实验的话最好自己敲一敲,加深印象,直接复制可能因为中文的标点出错;
在这里如果1.txt不存在,程序执行时会打印如下信息
fail to fopen:No such file or directory
在来一个strerror()
这个函数也是错误处理但是区别就是它是有返回值的
头文件#include <string.h>
#include <errno.h>
函数原型 char*strerror(int errnum);
函数参数 错误码
返回值: 错误码对应的错误的信息
在来一段代码
#include <stdio.h>
#include <string.h>
#include <errno.h>
int main()
{
FILE*fp;
if(fp=fopen("1.txt","r")==NULL)
{
printf("fail to open: %s\n",strerror(errno));
return -1;
}
fclose(fp);
return 0;
}
如果文件1.txt不存在,程序执行时会打印如下信息
fail to fopen: No such file or directory
4.流的读写
(1)按字符(字节)输入/输出
字符输入/输出函数一次仅读写一个字符。字符输入。输出函数语法要点如表1.1和1.2所示
1.字符输入函数语法要点
头文件: #include <stdio.h>
函数原型
int getc(FILE* stream);
int fgetc(FILE *stream);
int getchar(void);
参数: stream 要输入的文件流
返回值
成功 要读取的字符
失败 EOF
表1.1
2. 字符输出函数语法要点
头文件 #include <stdio.h>
函数原型
int putc(int c,FILE* stream);
int fputc(int c,FILE* stream);
int putchar(int c);
返回值
成功 输出的字符c
失败 EOF
表1.2
putc()和fputc()向指定的流输出一个字符(节),putchar()向stdout()输出一个字符(节)
下面这个实际例子结合fputc()和fgetc(),循环从标准输入读取一个任意字符并将一个任意字符输出到标准输出
/*fput.c*/
#include <stdio.h>
int main()
{
int c;
while(1)
{
c=fgetc(stdin);
if((c>='0')&&(c<='9')) fputc(c,stdout);//若输入的是数字,则输出
if(c=='\n') break; //若遇到换行符,跳出循环
}
return 0;
}
运行结果如下:
输入as1asdas3asda8/wasd
输出138
(2)按行输入/输出
行输入/输出函数一次操作一行。行输入/输出函数语法要点如下2.1/2.2
1.行输入函数语法要点
#include <stdio.h>
函数原型:
char* gets(char*s)
char*fgets(char*s,int size,FILE*stream)
函数参数
s:存放输入字符串的缓冲区首地址
size:输入的字符串长度
stream:对应的流
返回值:
成功 s
失败或者没有到达文件末尾 NULL
表2.1
gets 函数容易造成缓冲区溢出,不推荐大家使用
fgets 从指定的流中读取一个字符串,当遇到\n时,会读取\n或读取size-1个字符后返回。注意:fgets不能保证每次都读出一行
2.行输出的函数语法特点
#include <stdio.h>
函数原型
int puts(const* char*s)
int fputs(const* char*s,FILE*stream)
参数:
s:存放输出字符串的缓冲区首地址
stream: 对应的流
成功: s
失败: NULL
表2.2
下面以fgets()给大家举一个例子计算一个文本的文件行数
/*fgets*/
#include<stdio.h>
#include <string.h>
int main(int argc,char*argv[])
{
int line=0;
char buf[128];
FILE*fp;
if(argc<2)
{
printf("Usage: %s<file>\n",argv[0]);
return -1;
}
if(fp=fopen(argv[1],"r")==NULL)
{
perror("fail to fopen");
return -1;
}
while(fgets(buf,128,fp)!=NULL)
{
if(buf[strlen(buf)-1]=='\n')
line++;
}
printf("The line of %s is %d\n",argv[1],line);
return 0;
}
运行该程序,如果如下
./a.out test.txt
The line of test.txt is 64
5.以指定大小为单位读写文件
在文件流被打开之后,可对文件流按指定大小为单位进行读写操作。
(1)fread()函数语法要点
#include <stdio.h>
函数原型
size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream);
参数:
ptr:存放读入记录的缓冲区
size: 读取的每个记录的大小
nmemb:读取的记录数
stream: 要读取的文件流
返回值
成功: 返回实际读取到的nmemb数目
失败: EOF
(2)fwrite()函数语法特点
#include <stdio.h>
函数原型
size_t fwrite(const void *ptr, size_t size, size_t nmemb,FILE *stream);
参数:
ptr:存放写入记录的缓冲区
size:写入的每个记录的大小
nmemb: 要写入的文件流
返回值
成功:返回实际写入的nmemb数目
失败: EOF
fread()和fwite()函数的实际列子
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <strings.h>
struct data
{
int num;
char ch;
char name[20];
};
int main()
{
char buf[64] = {"hello"};
struct data s = {12, '\0', "hjk"};
struct data s2;
FILE *fp = fopen("2.txt", "wb+");
if(fp == NULL)
{
printf("fopen failed\n");
return -1;
}
int ret = fwrite(&s, sizeof(s), 1, fp);
if(ret == 0)
{
printf("fwrite failed\n");
return -1;
}
fclose(fp);
fp = fopen("2.txt", "rb");
ret = fread(&s2, sizeof(s2), 1, fp);
if(ret == 0)
{
if(feof(fp) != 0)
{
printf("end of file\n");
}
else
{
printf("fread failed\n");
return -1;
}
}
printf("%d--%s--%c\n", s2.num, s2.name, s2.ch);
return 0;
}
6.流的定位
每个打开的流内部都有一个当前读写位置。流在打开时,当前读写位置为0,表示文件的开始位置。每读写一次后,当前读写位置自动增加实际读写的大小。在读写流之间可先对流进行定位,即移动到指定的位置再操作。
(1)fseek()函数语法的要点
#include <stdio.h>
函数原型
int fseek(FILE *stream, long offset, int whence);
参数:
stream: 要定位的文件流
offset: 相对于基准值的偏移量
whence: 基准值
SEEK_SET 代表文件起始位置
SEEK_END 代表文件结束位置
SEEK_CUR 代表文件当前读写位置
返回值
成功 0
失败 EOF
(2)ftell()函数的语法要点
功能:获取当前的位置
#include <stdio.h>
函数原型
long ftell(FILE *stream);
参数
stream : 要定位的文件流
返回值
成功 返回当前读写的位置
失败 EOF
下面的例子为获取一个文件的大小
/*ftell*/
#include <stdio.h>
int main(int argc,char*argv)
{
FILE*fp;
if(argc<2)
{
printf("Usage: %s<file>\n",argv[0]);
return -1;
}
if(fp=fopen(argv[1],"r")==NULL)
{
perror("fail to fopen");
return -1;
}
fseek(fp,0,SEEK_END);
printf("The size of %s is %d\n",argc[1],ftell(fp));
return 0;
}
运行结果
./a.out test.txt
The size of test.txt is 305
7.格式化输入/输出
格式化输入/输出函数可以指定输入/输出的具体格式,包括读者已经非常熟悉的printf(),scanf()等函数,以下介绍它们的语法要点
(1)格式化输入函数语法要点
#include <stdio.h>
函数原型
int scanf(const char *format, ...);
int fscanf(FILE *stream, const char *format, ...);
int sscanf(const char *str, const char *format, ...);
传入值
format: 输入的格式
fp: 作为输入的流
buf: 作为输入的缓冲区
返回值
成功 输出字符数(sprintf的返回存入数组中的字符数)
失败 EOF
(2)格式化输出函数语法要点
#include <stdio.h>
int printf(const char *format, ...);
int fprintf(FILE *stream, const char *format, ...);
int dprintf(int fd, const char *format, ...);
int sprintf(char *str, const char *format, ...);
int snprintf(char *str, size_t size, const char *format, ...);
参数
format: 输出的格式
fp: 接收输出的流
buf: 接收输出的缓冲区
返回值
成功 输出字符数(sprintf的返回存入数组中的字符数)
失败 EOF
fprintf和sprintf在应用开发中经常会用到,建议读者查看其帮助信息以掌握其用法。
这个节点重点介绍了标准I/O相关函数,也给大家举了1大量的例子,希望大家能够明白标准I/O的基础知识,谢谢大家阅读,也方便我自己回顾。