linux 之文件基础 (四)、标准IO的API
1. 刷新缓冲区
#include <stdio.h>
int fflush(FILE *stream);
2. 打开一个流
#include <stdio.h>
FILE *fopen(const char *pathname, const char *mode);
FILE *fdopen(int fd, const char *mode);
FILE *freopen(const char *pathname, const char *mode, FILE *stream);
2.1 fopen
#include <stdio.h>
FILE *fopen(const char *pathname, const char *mode);
FILE *fdopen(int fd, const char *mode);
FILE *freopen(const char *pathname, const char *mode, FILE *stream);
函数功能: 打开一个流。打开文件的权限为0666。
2.1.1 函数参数
(1)path : 路径名
(2)mode : 打开方式,有以下可选项
- r :只读方式。文件必须存在,否则报错。
- r+ :读写方式。文件必须存在,否则报错。
- w :只写方式。文件存在的话清空,不存在的话创建。
- w+ :读写方式。文件存在的话清空,不存在的话创建。
- a :以附加的方式打开(只写)。
- a+ :以附加方式打开(读写)。
2.1.2 函数返回值
- 成功:返回 指向FILE的结构体指针(流指针)
- 失败:返回 NULL
2.2 freopen
函数功能:重定向输入输出流。
2.2.1 函数参数
(1)pathname
重新定向的文件或者是路径
(2) type
文件打开的方式
(3) fp
被改变的流
2.2.2函数返回值
返回一个新的FILE指针。出错返回NULL。
举栗子:
freopen(“file1”, “w+”, stdout); 将stdout 重定向到 file1中。
freopen("/dev/tty", “w+”, stdout); 将stdout 重定向到 当前进程的控制终端中。
2.3 fdopen
2.3.1 函数功能及函数原型
FILE *fdopen(int fd, const char *mode);
将一个文件描述符和一个流结合。一般的fopen 函数只能操作普通文件,而如果想要使用标准IO操作设备文件,则需要使用这个函数。
一般使用场景:如果想用标准IO 操作设备文件,那么可以使用这个函数。即:使用标准IO 来操作设备文件。先获取设备文件的文件描述符,再使用标准IO操作。
2.3.2 函数参数
(1)fd
要操作设备的文件描述符。
(2)mode
与前面的mode一样
2.3.3 函数返回值
返回一个新的FILE指针。出错返回NULL。
3. 关闭一个流
3.1 fclose 函数
#include <stdio.h>
int fclose(FILE *stream);
功能:关闭一个已经打开的流。
注意:
- 关闭一个流之前,会刷下流的缓冲区中的数据,并释放缓冲区资源(也就是它底层调用了 free() 函数)。(因为缓冲区是malloc 来的。)
- 当一个进程终止,或者从main return 时,则所有未写缓冲区的流被刷新,所有打开的流都被关闭。
- 关闭一个流之后,不能再次关闭,否则结果是未知的(类似于两次 free)。
3.1.1 函数参数
要关闭流的指针。
3.1.2 函数返回值
- 关闭成功返回0
- 关闭错误返回EOF 并且设置错误码
4. 从一个流读一个字符
#include <stdio.h>
int getc(FILE *stream);
int fgetc(FILE *stream);
int getchar(void);
注意:
- getchar、getc 是通过宏实现的。fgetc是通过函数实现的。
- getchar() 等价于getc(stdin)
- getc() fgetc() 使用方法和效果是一样的,只是内部的实现细节不同。
4.1 函数参数
- c 输出字符的 ASCII 码
- stream 指定的流
4.2 函数返回值
- 读成功,获取读到字节的ASSII码
- 读失败,返回EOF,并设置error
5. 向一个流写一个字符
#include <stdio.h>
int putc(int c, FILE *stream);
int fputc(int c, FILE *stream);
int putchar(int c);
功能: 将指定的字符写入到指定的流中
注意:
- 从流中读取数据和向流中写数据三个函数一一对应。
- fput 的实现方式是函数。putc 和 putchar 的实现方式是宏。
- putc() fputc() 使用方法和效果是一样的,只是内部的实现细节不同。
5.1 函数参数
- c 输出字符的 ASCII 码
- stream 指定额流
5.2 函数返回值
- 写成功,获取读到字节的ASSII码
- 写失败,返回EOF,并设置error
5.3 区分EOF
5.3.1 区分EOF
读写一个流时,读写失败和到达文件末尾都会返回EOF,那么我们如何区别由于什么原因返回的EOF呢?
在FILE 结构体中,一般有两个标志字段,
- 出错标志
- 文件末尾标志
通过这两个标志,可以区别区别由于什么原因返回的EOF。
5.3.2 获取两种标志的函数
#include <stdio.h>
void clearerr(FILE *stream);
int feof(FILE *stream);
int ferror(FILE *stream);
5.3.2.1 函数描述
- feof : 用于检测文件末尾标志,如果该标志被设置返回非0的值,如果没有被设置返回0
- ferror :用于检测出错标志,如果该标志被设置返回非0的值,如果没有设置返回0
- clearerr :用于清除这两个标志
5.3.2.2用法:
while (!feof(fp) && !ferror(stdin)) {
cTemp = fgetc(fp);
}
解释: 如果没有达到文件末尾,以及没有出错的话,就继续读文件。也就是到达文件末尾以及出错了,都不能再继续读下去。
6. 从流中读一行
char *fgets(char *s, int size, FILE *stream);
char *gets(char *s);
推荐使用fgets。
6.1 函数参数
(1)s:
- 用于存储从制定流的缓冲区中获取的数据的首地址。 这里的缓冲区指的是应用程序的缓冲区,不要和流的缓冲区弄混了。数据会先存到流的缓冲区里,再从流的缓冲区中搬运到应用程序的缓冲区里。
(2)size:
- 缓冲区的长度
(3)stream:
- 指定的流
注意:
- 必须指定缓存的长度n。此函数一直读到下一个新行符为止,但是不超过n-1个字符,读入的字符被送入缓存。该缓存以null字符结尾。
- 如若该行,包括最后一个新行符的字符数超过n-1,则只返回一个不完整的行,而且缓存总是以null字符结尾。对fgets()的下一次调用会继续读该行。
- gets()并不将换行符存入缓存中, gets() 不把换行符加到新的缓冲区中。
- gets()不能从指定的流中获取数据,只能从标准输入流中获取数据。
比如:
- 通过fgets() 由标准输入读取数据,输入1234 然后敲回车,fgets() 函数会将1234和回车都存到缓冲区中。
- 通过gets() 由标准输入读取数据,输入1234 然后敲回车,gets() 函数只会将1234存到缓冲区中,而不会存回车。
6.2 函数返回值
- 读取成功:返回0
- 读取失败,或者到达文件末尾 返回NULL
7. 向流输出一行
int puts(const char *s);
int fputs(const char *s, FILE *stream);
puts函数功能:
向标准输出输出一行
fputs函数功能:
向指定的流里输出一行
函数fputs()将一个以null符终止的字符串写到指定的流,终止符null不写出。
注意
- 这并不一定是每次输出一行,因为它并不要求在null符之前一定是新换行符。
- puts()将一个以null符终止的字符串写到标准输出,终止符不写出。但是,puts()会自动将一个新换行符写到标准输出。
puts 和fputs 区别演示
#include <stdio.h>
int main()
{
char buf[] = {'a','b','c','d','\0','e','f'};
// fputs(buf,stdout);
puts(buf);
return 0;
}
可以看出:
- fputs 读到
\0
后不再向下读 并将读到的数据输出 - puts 读到
\0
后不再向下读,将读入的数据加上一个\n
后输出 - 同时也说明fputs、puts并不一定是每次输出一行,因为它并不要求在null符之前一定是新换行符,也就是它读到一行的
\0
后结束读。
8. 二进制流
疑问:
fputs 和puts 只能读字符串类型 ,且读到\0
就不能继续向下读了。如果想要输出一个结构怎么办?使用fread fwrite 函数。这两个函数常用于从二进制文件中读或写一个结构。
使用二进制读写的情况:
- 两个函数的返回:读或写的对象数
- 对于二进制数据我们更愿意一次读或写整个结构。
- 为了使用getc()或putc()做到这一点,必须循环读取整个结构,一次读或写一个字节。(效率低)
- fputs()在遇到null字节时就停止,而在结构中可能含有null字节,所以不能使用每次一行函数实现这种要求。如果输入数据中包含有null字节或换行符,则fgets()也不能正确工作。(实现限制)
8.1 读写一个二进制文件流
#include <stdio.h>
size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream); // 从一个二进制流读
size_t fwrite(const void *ptr, size_t size, size_t nmemb, FILE *stream);// 向一个二进制流写
8.1.1 函数参数:
- ptr: 缓冲区的首地址
- stream : 流
- nmemb :元素个数(人为指定的)
- size : 每个元素的大小(人为指定)
8.1.2 返回值:
- 成功 :实际读到元素的个数(凑不足一个元素就不算)
- 失败 :-1
8.1.3 使用举栗子
9. 流的格式化输入输出
9.1 由一个流格式化输出
int printf(const char *format, ...); //stdout
int fprintf(FILE *stream, const char *format, ...); //stream
int sprintf(char *str, const char *format, ...); //char *
- fprintf 和printf 的使用方法类似,只是printf 只能向stdout 流输出,但是fprintf 不仅可以向stdout输出,也可以向别的流中输入。
- 我们可以通过sprintf 把我们想要的数据写到字符串中。
9.2 向一个流格式化输入
#include <stdio.h>
int scanf(const char *format, ...); //stdin
int fscanf(FILE *stream, const char *format, ...);//stream 流的格式化输入
int sscanf(const char *str, const char *format, ...);//char *
- scanf 只能从标准输入中获取数据。
- fscanf 从指定的流中获取数据。
- ssanf 从字符串中获取数据。
9.3 举栗子
9.3.1 fprintf
打开一个流,采用流的格式化输出函数,向流中输入 指定的字符
#include <stdio.h>
int main()
{
// step1 读写方式打开一个流
FILE* filePtr = fopen("file.txt","r+");
int a = 10;
fprintf(filePtr,"test %d",a);
return 0;
}
9.3.2 sprintf
将数据格式化输入到字符数组中。
#include <stdio.h>
int main()
{
char buf[10] = "\0";
int a = 10;
sprintf(buf,"test %d",a);
printf("%s",buf);
return 0;
}
9.3.3 fscanf
打开一个流,从流中格式化读入数据,输出到stdout。
#include <stdio.h>
int main()
{
FILE* filePtr = fopen("file.txt","r+");
char my_char[10]= "\0";
int x = 0;
fscanf(filePtr,"%s%d",my_char,&x);
printf("%s %d",my_char,x);
return 0;
}
9.4 sscanf
从一个字符中格式化读入数据,输出到stdout。
#include <stdio.h>
int main()
{
char my_char2[10] = "sss 10";
char my_char[10] = "\0";
int x = 0;
sscanf(my_char2,"%s%d",my_char,&x);
printf("%s %d",my_char,x);
return 0;
}
10. 流的定位
int fseek(FILE *stream, long offset, int whence);
10.1 函数参数
(1)stream
打开的流
(2)offset
相对于基准点偏移量
(3)whence : 基准点
- SEEK_SET:当前位置为文件的开头,新位置为偏移量的大小。
- SEEK_CUR:当前位置为文件指针的位置,新位置为当前位置加上偏移量。
- SEEK_END:当前位置为文件的结尾,新位置为文件的大小加上偏移量的大小。
10.2 返回值
- 成功 返回0
- 失败 返回-1
10.3 获取偏移量
//返回当前流的偏移量. 等价于文件IO中 lseek 函数的返回值
long ftell(FILE *stream);
//rewind()等价于(void)fseek(stream, 0L, SEEK_SET)
void rewind(FILE *stream);
11. 获取系统时间函数
11.1 time函数
#include <time.h>
time_t time(time_t *t); time_t == long
函数功能:
- 返回从1970-1-1 0:0:0到函数调用那一刻的秒数(unix 诞生的时间)
函数参数
- t: 获取后的时间存储的位置。即 如果t非NULL,就将时间填入到t所指向的内存空间
返回值:
- 成功 : 时间
- 失败 :((time_t) -1) 即将 -1 强转成time_t 返回
11.2 localtime 函数
#include <time.h>
struct tm *localtime(const time_t *timep);
函数功能:
- 将秒数转换成具体的时间
函数参数:
- timep : 存秒数的地址
返回值:
tm: 结构指针,结构为:
struct tm {
int tm_sec; /* Seconds (0-60) */
int tm_min; /* Minutes (0-59) */
int tm_hour; /* Hours (0-23) */
int tm_mday; /* Day of the month (1-31) */
int tm_mon; /* Month (0-11) */
int tm_year; /* Year - 1900 */
int tm_wday; /* Day of the week (0-6, Sunday = 0) */
int tm_yday; /* Day in the year (0-365, 1 Jan = 0) */
int tm_isdst; /* Daylight saving time */
};
12. 标准IO 和文件IO 的总结
12.1 文件I/O和标准I/O区别
- 文件I/O是低级I/O,支持POSIX规范,任何支持POSIX标准的操作系统都支持使用文件I/O。(类
UNIX)标准I/O是高级I/O,支持ANSI C相关标准,只要开发环境支持标准C库,标准I/O就可以使用。 - 通过文件I/O读取文件时,每次操作都会执行相应的系统调用。好处是直接读取实际文件,坏处是频繁调用系统调用,增加系统开销,效率低。标准I/O可以看成在文件I/O的基础之上封装了缓冲机制,先读写缓冲区,必要时读写真实文件,从而减少系统调用的次数。
- 文件I/O使用文件描述符,表示一个打开的文件,可以访问不同类型的文件,普通文件,设备文件和管道文件。标准I/O中用FILE(流)来表示一个打开的文件,通常只用来访问普通文件。(如果想要使用标准IO 操作 非普通文件,可以使用fdopen函数。)
12.2 getchar putchar
- 当程序调用getchar时.程序就等着用户按键.用户输入的字符被存放在键盘缓冲区中。直到用户按回车为止(回车字符也放在缓冲区中)。
- 当用户键入回车之后,getchar才开始从stdio流中每次读入一个字符。
- getchar函数的返回值是用户输入的字符的ASCII码,如出错返回-1,且将用户输入的字符回显到屏幕。
- 如用户在按回车之前输入了不止一个字符,其他字符会保留在键盘缓存区中,等待后续getchar调用读取。
- 也就是说,后续的getchar调用不会等待用户按键,而直接读取缓冲区中的字符,直到缓冲区中的字符读完为后,才等待用户按键。
13. 举栗子
13.1 获取一个文件的行数,文件由命令行参数指定
#include <stdio.h>
#include <string.h>
/*
*获取一个文件的行数,文件由命令行参数指定
* */
int main(int argc, const char *argv[])
{
if(argc != 2)
{
perror("参数不准确");
return -1;
}
FILE * filePtr;
// step1 打开流
if((filePtr = fopen(argv[1],"r")) == NULL)
{
perror("open file error");
return -1;
}
char read_buf[1024];
int line = 0;
// 如果fgets 不等NULL 代表没有出错且没有到达文件尾
while(fgets(read_buf,sizeof(read_buf),filePtr) != NULL)
{
//因为一次fgets 不一定读到完整的一行数据,如果一行没有读完,那么line不应该自加。 只有读到了\n 才代表一行读完了。
//而 read_buf 的最后一个字节自动设置为 \0 因此倒数第2个字节应该存的是\n 否则就没有读完一行
if(read_buf[strlen(read_buf)-1] == '\n');
++line;
}
printf("文件共有%d 行\n",line);
return 0;
}
13.2 转换字母大小写
#include <stdio.h>
int main()
{
int read_char;
while((read_char = fgetc(stdin)) != EOF)
{
fputc(read_char,stdin);
if('a' <= read_char && read_char <= 'z')
{
read_char = read_char - 32;
}
else if('A' <= read_char && read_char <= 'Z')
{
read_char = read_char + 32;
}
fputc(read_char,stdin);
printf("%c",read_char);
if(read_char == '\n')
break;
}
return 0;
}