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;
}
posted @ 2020-03-30 19:11  江南又一春  阅读(450)  评论(0编辑  收藏  举报