二、标准I/O和文件I/O

1. 标准IO介绍及缓冲区

  • Linux IO 进程课程目的:学习编写 linux 应用程序(APP)

  • Linux 文件的种类 :
    常规文件 r
    目录文件 d
    字符文件 c
    块文件 b
    链接文件(相当于 windows 快捷方式)l
    socket文件 s
    管道文件 p

  • IO 的概念
    I input: 输入设备 比如键盘鼠标都是 Input 设备
    O output 输出设备 比如显示器
    优盘,网口,既是输入也是输出

  • 系统调用和库函数
    系统调用就是操作系统提供的接口函数.
    如果我们把系统调用封装成库函数就可以起到隔离的作用,提供程序的可移植性。
    Printf 就是库函数然后调用了系统调用才在显示器上显示字符

  • 流的概念
    就是数据的流,在程序中就是一个结构体。

  • Windows 和 linux 的换行符区别
    Windows 是\r\n
    Linux 是\n

  • 缓冲区的概念
    为了减少操作 IO 设备的次数,提高运行效率,在内存里面设置的缓冲区,
    全缓冲:缓冲区满才输出
    行缓冲:遇到换行符输出

  • 三种标准 IO :


  • 缓冲区概念演示:
    程序正常结束会刷新缓冲区

#include<stdio.h>
int main(int argc, char* argv[]){

    printf("hello world");
    return 0;
}
  • Sleep 函数:是释放 cpu 给其他应用程序使用的库函数。使用的头文件是#include<unistd.h>
    查看头文件方法:man 2 函数 ,或者 man 3 函数
#include<stdio.h>
#include<unistd.h>
int main(int argc, char* argv[]){
    printf("hello world");
    while(1){
      sleep(1);
    }
    return 0;
}

结果无任何输出,因为缓冲区未满也没\n, 程序也没结束。

2. 标准IO: 文件的打开和关闭

文件的打开和关闭概念

打开就是占用资源
关闭就是释放资源

文件的打开

  • 文件的打开函数
    FILE *fopen (const char *path, const char *mode);
    Path: 普通文件当前路径不需要加目录,其他要使用完整的路径
    Mode:
    返回值:出错返回 NULL,所以使用 fopen 函数必须判断是否为空

  • 文件打开的模式(非常重要)


  • 编译错误:
    f_open.c:9:38: error: ‘errno’ undeclared (first use in this function)
    printf("fopen:%s\n",strerror(errno));
    error: ‘errno’ undeclared 表示 errno 变量没有定义
    解决方法:如果是系统变量用 include 头文件,如果是你自己的,自己手动定义。
    f_open.c:10:29: warning: implicit declaration of function ‘strerror’
    [-Wimplicit-function-declaration]
    printf("fopen:%s\n",strerror(errno));
    warning: implicit declaration of function ‘strerror’ 表示 strerror 函数隐示的声明
    解决方法:include 添加对应的头文件。

  • perror 库函数 头文件 stdio.h
    strerror 库函数 头文件 errno.h string.h
    perror 和 strerror 功能:打印系统的错误描述(注意:是系统错误,不是你自己代码错误)

文件的关闭:

函数原型:int fclose(FILE *stream)

  • fclose()调用成功返回 0,失败返回 EOF(-1),并设置 errno
  • 流关闭时自动刷新缓冲中的数据并释放缓冲区,比如:常规文件把缓冲区内容写入磁盘
  • 当一个程序正常终止时,所有打开的流都会被关闭
  • fclose()函数的入参 stream 必须保证为非空,否则出现断错误。
#include<stdio.h>
#include<string.h>
#include<errno.h>

int main(int argc, char * argv[]){
    FILE *fp = fopen("1.txt","r");
    int fret = EOF;
    if(fp == NULL){
      perror("fopen");
      printf("fopen: %s\n",strerror(errno));
    }else{
      printf("fopen: open file success\n");
      fret = fclose(fp);
      if(fret == 1){
         printf("file close success\n");
      }else{
         perror("fclose");
      }
    }
  return 1;
}

3. 标准 IO 的字符输入和输出

字符的输入(读单个字符)

int fgetc(FILE *stream);
int getc(FILE *stream); //宏
int getchar(void);
成功时返回读取的字符;若到文件末尾或出错时返回 EOF(-1),
getchar()等同于 fgetc(stdin)
getc 和 fgetc 区别是一个是宏一个是函数

注意事项:

  1. 函数返回值是 int 类型不是 char 类型,主要是为了扩展返回值的范围。(char范围是-128 -- 127 unsigned char 0 - 255, int就都包含了)
  2. stdin 也是 FILE *的指针,是系统定义好的,指向的是标准输入(键盘输入)
  3. 打开文件后读取,是从文件开头开始读。读完一个后读写指针会后移。读写注意文件位置!
  4. 调用 getchar 会阻塞,等待你的键盘输入

字符的输出(写单个字符):

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

成功时返回写入的字符;出错时返回 EOF
putchar(c)等同于 fputc(c, stdout)

行输入(读取整个行)

char *gets(char *s); 读取标准输入到缓冲区 s
char *fgets(char *s, int size, FILE *stream);
成功时返回 s,到文件末尾或出错时返回 NULL
遇到’\n’或已输入 size-1 个字符时返回,总是包含’\0’

注意事项:

  1. gets 函数已经被淘汰,因为会导致缓冲区溢出
  2. fgets 函数第二个参数,输入的数据超出 size,size-1 个字符会保存到缓冲区,最后添加’\0’,如果输入数据少于 size-1 后面会添加换行符。

行输出(写整行)

int puts(const char *s);
int fputs(const char *s, FILE *stream);

  • 成功时返回非负整数;出错时返回 EOF
  • puts 将缓冲区 s 中的字符串输出到 stdout,并追加’\n’
  • fputs 将缓冲区 s 中的字符串输出到 stream,不追加 ‘\n’

4. 标准IO读写:二进制方式

文本文件和二进制的区别:
存储的格式不同:文本文件只能存储文本。
计算机内码概念:文本符号在计算机内部的编码(计算机内部只能存储数字 0101001....,所以所有符号都要编码)

二进制读写函数格式:
size_t fread(void *ptr, size_t size, size_t n, FILE *fp);
void *ptr 读取内容放的位置指针
size_t size 读取的块大小
size_t n 读取的个数
FILE *fp 读取的文件指针

size_t fwrite(const void *ptr, size_t size, size_t n, FILE *fp);
void *ptr 写文件的内容的位置指针
size_t size 写的块大小
size_t n 写的个数
FILE *fp 要写的文件指针

fread: 从1.txt中读出10个字符

#include<stdio.h>
#include<stdlib.h>
int main(){
    FILE *fp;
    size_t ret;
    char* buff;
    fp = fopen("1.txt","r");
    if(fp == NULL){
       perror("fopen");
       return -1;
    }
    buff = (char *)malloc(100);
    if(buff == NULL){
       perror("buff");
       fclose(fp);
       return -1;
    }

    ret = fread(buff,10,1,fp);
    if(ret == EOF){
       perror("fread");
       goto end;
    }
    printf("ret = %s\n",buff);
end:
    free(buff);
    fclose(fp);
    return 0;
}

fwrite: 往1.bin中写入1个struct Student对象的二进制

#include<stdio.h>
#include<string.h>
struct Student{
  char name[16];
  int age;
  char sex[8];
};
int main(){

    FILE *fp;
    size_t ret;
    fp = fopen("1.bin","w");
    if(fp == NULL){
       perror("fopen");
       return -1;
    }

    struct Student stu;
    strcpy(stu.name,"zhangsan");
    stu.age = 18;
    strcpy(stu.sex,"male");

    ret = fwrite(&stu,sizeof(stu),1,fp);
    if(ret == EOF){
        perror("fwrite");
        goto end;
    }
end:
    fclose(fp);
    return 0;
}
#include<stdio.h>
#include<string.h>
struct Student{
  char name[16];
  int age;
  char sex[8];
};
int main(){

    FILE *fp;
    size_t ret;
    struct Student stu;
    struct Student stu2;
    fp = fopen("1.bin","w");
    if(fp == NULL){
       perror("fopen");
       return -1;
    }

    strcpy(stu.name,"zhangsan");
    stu.age = 18;
    strcpy(stu.sex,"male");

    ret = fwrite(&stu,sizeof(stu),1,fp);
    if(ret == EOF){
        perror("fwrite");
        goto end;
    }else{
      printf("write student success\n");
    }

    ret = fread(&stu2,sizeof(stu2),1,fp);
    if(ret == EOF){
       perror("fread");
       goto end;
    }
    printf("stu2.name=%s,stu2.age=%d,stu2.sex=%s\n",stu2.name,stu2.age,stu2.sex);
end:
    fclose(fp);
    return 0;
}
输出:stu2.name=,stu2.age=0,stu2.sex=

注意事项:

文件写完后,文件指针指向文件末尾,如果这时候读,读不出来内容。
解决办法:移动指针(后面讲解)到文件头;或关闭文件,重新打开

5. 流刷新定位

流的刷新

int fflush(FILE *fp);

  • 成功时返回 0;出错时返回 EOF
  • 将流缓冲区中的数据写入实际的文件
  • Linux 下只能刷新输出缓冲区,输入缓冲区丢弃
  • 如果输出到屏幕使用 fflush(stdout)
#include<stdio.h>
#include<unistd.h>
int main(){
    printf("abcdefghijklmn");
    fflush(stdout); //不加这一行将无法输出 因为缓冲区未满也没\n, 程序也没结束。
    while(1){
      sleep(1);
    }
    return 0;
}
#include<stdio.h>
#include<unistd.h>
int main(){
    FILE *fp;
    size_t ret;
    fp = fopen("fflush.txt","w");
    if(fp == NULL){
        perror("fopen");
        return -1;
    }

    ret = fwrite("abcdefghigklmn",14,1,fp);
    if(ret == EOF){
      perror("fwrite");
    }
    fflush(fp); //不加这一行 也无法将内容写入到文件

    while(1){
      sleep(1);
    }
    return 0;
}

流的定位

include <stdio.h>

long ftell(FILE *stream);
long fseek(FILE *stream, long offset, int whence);
void rewind(FILE *stream);

  • ftell() 成功时返回流的当前读写位置,出错时返回 EOF
  • fseek() 定位一个流,成功时返回 0 ,出错时返回 EOF
  • whence 参数: SEEK_SET/SEEK_CUR/SEEK_END
  • SEEK_SET 从距文件开头 offset 位移量为新的读写位置
  • SEEK_CUR :以目前的读写位置往后增加 offset 个位移量
  • SEEK_END :将读写位置指向文件尾后再增加 offset 个位移量
  • offset 参数:偏移量,可正可负
  • 打开 a 模式 fseek 无效
  • rewind() 将流定位到文件开始位置
  • 读写流时,当前读写位置自动后移
  • 注意事项:
    1.文件的打开使用 a 模式 fseek 无效
    2.rewind(fp) 相当于 fseek(fp,0,SEEK_SET);
    3.这三个函数只适用 2G 以下的文件
#include<stdio.h>

int main(){
    FILE *fp;
    fp = fopen("1.txt","w");
    if(fp == NULL){
        perror("fopen");
        return 0;
    }

    fwrite("abcdefg",7,1,fp);
    printf("current fp=%d\n",(int)ftell(fp));
    rewind(fp);
    printf("after fp=%d\n",(int)ftell(fp));

    return 0;
}

int ferror(FILE *stream);
int feof(FILE *stream);

  • ferror() 返回 1 表示流出错;否则返回 0
  • feof() 返回 1 表示文件已到末尾;否则返回 0

6. 格式化输入输出

格式化输出

int printf(const char *fmt, …);
int fprintf(FILE *stream, const char *fmt, …);
int sprintf(char *s, const char *fmt, …);

  • 成功时返回输出的字符个数;出错时返回 EOF
#include<stdio.h>

int main(){

    FILE *fp;
    size_t ret;
    int year = 2025;
    int month = 8;
    int day = 1;
    fp = fopen("1.txt","w");
    if(fp == NULL){
        perror("fopen");
        return 0;
    }

    ret = fprintf(fp,"%d-%d-%d",year,month,day);
    if(ret == EOF){
        perror("fprintf");
    }
    fclose(fp);
    return 0;
#include<stdio.h>

int main(){
    char buff[20]={0};
    size_t ret;
    int year = 2025;
    int month = 8;
    int day = 1;
    ret = sprintf(buff,"%d-%d-%d",year,month,day);
    if(ret == -1){
        perror("sprintf");
    }
    printf("%ld %s",ret,buff);
    return 0;
}

格式化输入

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

  • 成功时返回输出的字符个数;出错时返回 EOF
#include<stdio.h>

int main(){
    FILE *fp;
    int year, month, day;
    fp = fopen("1.txt","r");
    if(fp == NULL){
        perror("fopen");
        return 0;
    }
    fscanf(fp,"%d-%d-%d",&year,&month,&day);
    printf("%d-%d-%d\n",year, month, day);
    fclose(fp);
    return 0;
}
#include<stdio.h>

int main(){
    char buff[20]={0};
    size_t ret;
    int year = 2025;
    int month = 8;
    int day = 1;
    int syear, smonth, sday;
    ret = sprintf(buff,"%d-%d-%d",year,month,day);
    if(ret == -1){
        perror("sprintf");
    }
    printf("%ld %s\n",ret,buff);
    sscanf(buff,"%d-%d-%d",&syear,&smonth,&sday);
    printf("%d-%d-%d\n",syear,smonth,sday);
    return 0;
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容

  • 标准I/O:数据从内存与标准i/o设备(stdio,stdout,stderr)之间的数据流动 文件I/O:文件的...
    FakeCSer爱去网吧阅读 77评论 0 0
  • 在该章节中讨论的文件描述符的概念。其中包括:打开文件,关闭文件,从文件中读取数据和向文件中写数据。 概述所有执行I...
    Capr1corn阅读 841评论 0 0
  • 1.1 C标准函数与系统函数 C标准是工作在操作系统之上的。比如要执行C标准函数printf函数,printf会调...
    FlyingReganMian阅读 1,053评论 0 0
  • IO是一个比较大的概念,它所涉及到的内容也比较多,比较繁杂,下面就从文件的IO开始说起,因为unix下,一切皆文件...
    飞翃荷兰人阅读 755评论 0 0
  • 所有执行I/O操作的系统调用都以文件描述符(一个非负整数)来指代打开的文件。包括pipe,FIFO,socket,...
    loopppp阅读 686评论 0 0