基础IO 一

97 阅读4分钟

携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第18天,点击查看活动详情

写在前面

关于进程,我们还要再往下学习进程间通信、进程信号,但是在这之前,我们先学习基础 IO,这篇文章穿插在进程中并不奇怪,因为它有着承上启下的作用。

  1. 文件的宏观理解

    那么文件在哪呢 ?—— 广义上理解,键盘、显示器等都是文件,因为我们说过 “ Linux 下,一切皆文件 ”,当然我们现在对于这句话的理解是片面的;狭义上理解,文件在磁盘上,磁盘是一种永久存储介质,不会受断电的影响,磁盘也是外设之一,所以对文件的所有操作,都是对外设的输入输出,简称 IO(Input、Output)。

  2. 文件的组成

    当我们在 Windows 下新建一个文本文件,它是否占用磁盘空间 ?—— 虽然它是一个空的文本文件,并且这里显示是 0KB,但是它依旧会占用磁盘空间,因为一个文件新建出来,它有很多数据信息都需要维护,包括文件名、修改日期、类型、大小、权限等。

    在这里插入图片描述

    而当我们对空文本写入字符时,这里可以直观的看到文本的大小由 0KB 到 1KB。

    在这里插入图片描述

    所以说一个文件 = 属性(元数据) + 内容,也就是说我们要学习的所有的文件操作,无外乎就是对文件的属性和内容操作。比如说之前所学的 fread、fwrite、fgets、fputs、fgetc、fputc 是对文件的内容操作;fseek、ftell、rewind 是对文件的属性操作;

  3. 系统看待文件操作

    我们以前写的 fread、fwrite 等对文件操作的 C 程序 ➡ 经过编译形成可执行程序 ➡ 双击或 ./ 运行程序,把程序加载到内存。所以对文件的操作本质就是进程对文件的操作。

    我们在操作文件时所使用到的接口,如 fread、fwrite,这是 C 语言提供的接口,而要操作的文件是在磁盘这个硬件上,同时我们很明确磁盘的管理者是操作系统,用户不可能直接去访问硬件,在计算机体系结构中我们知道用户是通过 C 语言所提供的这些接口来贯穿式的访问硬件(用户 ➡ 库函数 ➡ 系统调用接口 ➡ 操作系统 ➡ 驱动程序 ➡ 硬件)。所以本质并不是 C 语言帮我们把数据写到磁盘文件中,C 语言只提供方便用户使用的接口,真正干活的是操作系统所提供的文件相关的系统调用接口。

所以基础 IO 系列文章中主要学的是进程和系统调用接口这两个角度看待文件的方式。

一、简单复习文件操作

💦 写文件

#include<stdio.h>

int main()
{
	FILE* fp = fopen("./log.txt", "w");//以写的方式打开当前目录下的log.txt文件,没有就新建文件,如果目标文件存在,w写时会清空目标文件	
	//FILE* fp = fopen("log.txt", "w");//没有./,它默认是在当前路径下新建文件
    if(fp == NULL)
    {
        perror("fopen");
        return 1;
    }

    int count = 0;
    while(count < 10)
    {
        fputs("hello DanceBit\n", fp);//往log.txt文件中写数据
        count++;                      
    }

    fclose(fp);//关闭文件

    return 0;
}
  • FILE* fp = fopen("log.txt", "w");

    虽然没有 ./ 指定路径,但是它还是在当前路径下新建文件了,因为每个进程都有一个内置的属性 cwd(可以在 /proc 目录下查找对应进程的属性信息),cwd 可以让进程知道自己当前所处的路径,这也解释了在 VS 中不指明路径,它也能新建对应的文件在对应的路径,换言之,进程在哪个路径运行,文件的新建就哪个路径。

    这里把 ctrl_file 可执行程序移动到上级路径,此时在上级路径下 ./ctrl_file 后,就可以看到新建的文件夹与可执行程序在同一路径。

    在这里插入图片描述

💦 读文件

#include<stdio.h>

int main()
{
    FILE* fp = fopen("./log.txt", "r");//以读的方式打开当前目录下的log.txt文件,没有就报错
    if(fp == NULL)
    {
        perror("fopen");
        return 1;
    }

    int count = 0;
    char buffer[128];
    while(count < 10)
    {
        fgets(buffer, 128, fp);//从log.txt文件中读128个字符到buffer,\n会使fgets停止读取
        printf("%s\n", buffer);
        count++;
    }

    fclose(fp);//关闭文件

    return 0;
}

💦 追加文件

#include<stdio.h>
#include<string.h>

int main()
{
    FILE* fp = fopen("./log.txt", "a");//以追加的打开当前目录下的log.txt文件,没有就新建,如果目标文件存在,a写时不会清空目标文件,在文件内容最后写入
    if(fp == NULL)
    {
        perror("fopen");
        return 1;
    }

    const char* msg = "Hello DanceBit\n";
    //fwrite(msg, strlen(msg) + 1, 1, fp);//乱码
    fwrite(msg, strlen(msg), 1, fp);                                                                               

    fclose(fp);

    return 0;
}
  • size_t fwrite ( const void* ptr, size_t size, size_t count, FILE* stream );

    size 表示你要写入的基本单元是多大(以字节为单位),count 表示你要写入几个这样的基本单元。换言之,最终往文件中写的字节数是 = size * count,比如要写入 10 个字节,那么 size = 1 && count = 10、size = 2 && count = 5,不过一般建议把 size 写大点,count 写小点。

    fwrite 的返回值是成功写入元素的个数,也就是期望写 count 个,每个是 size,那么实际返回的是你实际写入成功了几组 size,比如你期望 size 是 10,count 是 1,大部分情况下都会把这 1 个单元都写入的,写入成功,返回值是 1;这里的你期望写入多少和实际写入多少,好比: 你:爸,我要 10 块钱。(这是你期望的) 爸:我只有 5 块钱,给你 3 块钱吧。(这是实际的) 当然,这里是网络部分才会涉及到的,目前在往磁盘文件中写入时,大部分情况下,硬件设备是能满足你的要求的,所以我们这里不关心 fwrite 的返回值。

  • fwrite(msg, strlen(msg) + 1, 1, fp);

    strlen(msg) + 1 -> 乱码,也就是把 \0 也追加会造成,因为 \0 是 C 的规定,和文件无关。这里 cat log.txt 并没有看到乱码的原因是 \0 是不可见的,所以这里 vim log.txt 才可以看到乱码。

    在这里插入图片描述