Linux环境编程——标准IO(一)

标准IO的引入

缓存大小和执行时间的关系

最优缓存大小:
在这里插入图片描述
分别为用户CPU时间和内核CPU时间

引入标准IO库的目的是为了提高IO的效率,避免频繁的进行read/write系统调用,而系统调用会消耗较多的资源。标准I/O在系统调用的上一层多加了一个缓冲区,通过缓冲机制减少了系统调用,实现更高的效率。

一个关于缓存的实验现象:

#include <stdio.h>
#include <unistd.h>

int main()
{
    printf("hello world");

    for (int i = 0; i < 10; ++i) {
        sleep(2);
    }
}

点击执行,屏幕上并没有先显示出“hello world” 而是在20s之后或者程序退出之后才显示。
在这里插入图片描述
在这里插入图片描述
当我们修改一下代码,在hello world 后面加上一个“\n”时:

#include <stdio.h>
#include <unistd.h>

int main()
{
    printf("hello world\n");

    for (int i = 0; i < 10; ++i) {
        sleep(2);
    }
}

在这里插入图片描述
可以看到,此时是立刻先输出了hello world。

为什么产生这种现象,原因是什么呢?
这是由于缓存的原因,printf不是直接系统调用函数,它在直接写的时候并没有写给系统调用write()函数(底层函数只有wirte才能写),而是写入一个缓存区,当缓存区达到一定条件后才会触发系统调用write()函数,才会在屏幕上显示。

标准IO是在不同系统调用之前加入一个缓存,让所有操作不是直接访问,而是现在缓存中存一存,等确实有必要的时候才进行系统调用,这样避免了频繁进行系统调用,提高了IO效率

标准IO的特点

  • 不仅在UNIX系统,在很多操作系统上都实现了标准IO库
  • 标准IO库由ANSIC标准说明
  • 标准IO库处理很多细节,如缓存分配、以优化长度执行IO等,这样用户不必关心如何选择合适的块长度
  • 标准IO在系统调用函数基础上构造的,它便于用户使用
  • 标准IO库及其头文件stdio.h为底层IO系统调用提供了一个用户接口

文件指针

  • FILE指针:每个被使用的文件都在内存中开辟一个区域,用来存放文件的有关信息,这些信息是保存在一个结构体类型的变量中,该结构体类型是由系统定义的,取名为FILE。
  • 标准IO库的所有操作都是围绕流(stream)来进行的。在标准IO中,流用FILE*来描述
  • 标准IO库是由Dennis Ritchie在1975年左右编写的

在这里插入图片描述

缓存文件系统(高级磁盘IO)

  • 目的:尽量减少使用read/write的调用
  • 定义:系统自动的在内存中为每一个正在使用的文件开辟一个缓冲区,从内存向磁盘输出数据必须先送到内存缓冲区,装满缓冲区在一起送到磁盘中区。从磁盘读取数据,则一次从磁盘文件将一批数据读入到内存缓冲区,然后再从缓冲区逐个的将数据送到程序的数据区

分类:全缓存、行缓存、不缓存

全缓存:缓存区满了
行缓存:“/n”
不缓存:绕过缓冲区直接操作

非缓冲文件系统(低级磁盘IO)

定义:依靠于操作系统,通过对操作系统的功能对文件进行读写,是系统级的输入输出。

标准IO提供的三种缓存类型

  • 全缓存(针对所有文件)
    • 当填满IO缓存后才进行实际IO操作,或者满足一定条件后,系统通过调用malloc()来获得所需要的缓冲区域,默认值。
    • 当缓冲区满了,或者满足一定的条件后,就会执行刷新操作
  • 行缓存
    • 当在输入和输出中遇到新行符“\n”时,进行IO操作
    • 当流遇到一个终端时,典型的行缓存(如敲键盘之类:敲完之后回车)
  • 不带缓存
    • 标准IO库不对字符进行缓冲,例如stderr
    • 很多的人机交互界面要求不可全缓存(如手机屏幕,点击实时反应)
    • 标准出错绝不会是全缓存的
  • 在任何时刻,可以使用fflush强刷新一个数据流

printf、fprintf、sprintf

printf(const char *format,…) 函数用于格式化输出数据,将输出结果到标准输出设备,直到出现字符串结束“\0”为止。
fprintf(FILE * stream, const char * format,… ) 函数会根据参数format字符串并转换格式化数据,然后将结果输出到参数stream指定的文件中,直到出现字符串结束(\0)为止。
sprintf(char *str, const char * format, …) 函数会根据format字符串来转换并格式化数据,然后将结果复制到参数str所指的字符串数组,直到出现“\0”为止。

案例

  • 编写程序向标准输出输出 “stdout: hello world”
  • 编写程序向错误输出输出 “stderr: 123”
  • 控制标准的标准输出输出,使程序仅输出标准输出字符
  • 控制标准的输出错误输出,使程序仅输出错误输出字符
#include <stdio.h>
#include <unistd.h>

int main()
{
    fprintf(stdout,"hello world");
    fprintf(stderr,"123");

    for (int i = 0; i <5; ++i) {
        sleep(1);
    }
}

实验现象:
在这里插入图片描述
在这里插入图片描述
实验现象:先出123,在程序退出时才显示hello world,而代码中是先写的输出hello world,再输出123。
原因:因为fprintf(stdout,“hello world”);是行缓存,如果在末尾没有加’\n"就不会立刻输出

这里的fprintf(stdout,“hello world”);等价于平时使用的printf()

输出重定向:

在liunx命令行中: > 表示标准输出重定向
2> 表示输出错误重定向
执行以下代码:

#include <stdio.h>
#include <unistd.h>

int main()
{
    fprintf(stdout,"hello world\n");
    fprintf(stderr,"123");
}

执行命令:./ClionProject > a.txt
在这里插入图片描述
可以看到,执行>命令后屏幕上没有输出结果了,输出结果被重定向到a.txt 中了
在这里插入图片描述
但是a.txt中只有hello world,没有123

再次执行命令:./ClionProject 2> a.txt
在这里插入图片描述
屏幕上出现了hello world
打开a.txt:
在这里插入图片描述
123被重定向了

如果想把标准输出和标准错误同时重定向:“&>”
再次执行命令:./ClionProject &> a.txt
在这里插入图片描述
总结:“>” 为标准输出重定向,"2>“为标准错误重定向,”&>"同时标准输出和错误重定向

用处:在工作中,编写程序时,打印重要信息单独摘抄出来。

用于打开一个标准IO流的三个函数:

FILE *foen(const char *path, const char *mode);
FILE *freopen(const char *parthname, const char *type, FILE *fp);
FILE *fdopen(int filedes, const char *type);
在这里插入图片描述

注:fopen需要和fclose成对出现,因为fopen返回的地址空间是堆,而堆空间是必须让程序员手动释放
在这里插入图片描述

实验——缓存文件的创建

#include <stdio.h>
#include <sys/stat.h>
#include <sys/types.h>

#define NEW_FILE_NAME "/tmp/temp/tmp_new.txt"

/*
 * 标准IO将文件描述符、缓存等数据结构抽象成FILE,使用一个FILE *指向这些对象
 * 标准IO使用fopen实现这些结构的申请,申请内容在堆上,所以在不使用这些结构时,一定要释放,使用fclose函数
 *
 * 文件不存在,创建新文件 : "w",这个选项会清空原内容
 * 不改变文件原内容的话,使用"r"方式
 * "+"符号指扩展标记,w就扩展r,r就扩展w,相当于一定是读写方式
 * 如果文件不存在就创建,存在就保留原文件,使用"a"标签,a只能向后添加
 * */
int lesson1(const char *filename) {
    FILE *fp;

    // 如果目录不存在就创建
    mkdir("/tmp/temp/",0777);
    fp = fopen(filename, "w");
    if (fp == NULL) {
        perror("fopen:");
        return -1;
    }
    for(int i =0;i<12;i++){
        int month = i+1;
        int year = 2023;
        fprintf(fp,"%d-%02d\n",year,month);
    }
    fclose(fp);
}

int main() {
    lesson1(NEW_FILE_NAME);

}

实验结果:
在这里插入图片描述
在这里插入图片描述
实际工程开发中,不允许使用的函数:strcpy、gets(容易越界、内存泄露)
fgets(buf, sizeof(buf), stdin);
用strncpy、fgets(作为输入,不用scanf)

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Asita_c

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值