最全的李慧芹APUE-标准IO笔记

这篇具有很好参考价值的文章主要介绍了最全的李慧芹APUE-标准IO笔记。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

标准 IO

: 李慧芹老师的视频课程请点这里, 本篇为标准IO一章的笔记, 课上提到过的内容基本都会包含

I/O (Input & Output): 是一切实现的基础

stdio (标准IO)

sysio (系统调用IO / 文件IO)

系统IO是内核接口, 标准IO是C标准库提供的接口, 标准IO内部使用了系统IO

标准IO会合并系统调用, 可移植性好, 因此在两者都可以完成任务的情况下, 优先使用标准IO

stdio 的一系列函数

详细参考man(3); FILE类型贯穿始终, FILE类型是一个结构体

fopen(): 产生FILE

fclose()


fgetc()

fputc()

fgets()

fputs()

fread()

fwrite()


pintf()一族

scanf()一族


fseek()

ftell()

rewind()


fflush()

打开操作

// 打开文件操作, 运行成功时, 返回FILE指针, 失败则返回NULL且设置errno
// params:
// @path: 要打开的文件
// @mode: 打开的权限(如: 只读/读写/只写...)
FILE *fopen(const char *path, const char *mode);

const char *

面试题:

char *ptr = "abc";
ptr[0] = 'x'; // 语句2

问: 能否通过语句2得到值为"xbc"的字符串?

gcc编译会报错(修改常量值), 但Turbo C一类的编译器编译出的程序会运行通过

errno

ubuntu22系统中, 可以执行vim /usr/include/errno.h来查看相关信息

errno曾经是一个全局变量, 但目前已被私有化, 新建test.c:

#include <errno.h>

errno;

执行gcc -E test.ctest.c进行预处理, 会得到:

// MacOS操作系统上的运行结果:
extern int * __error(void);
(*__error());
// Ubuntu22上的运行结果:
(*__errno_location ());

可以看到, errno已经被转化为宏 (而不是int类型全局变量)

再新建测试程序errno.c:

#include <stdlib.h>
#include <stdio.h>
#include <errno.h>

int main(void)
{
    FILE *fp;

    fp = fopen();
    if (fp == NULL)
    {   
        fprintf(stderr, "fopen() failed! errno = %d\n", errno);
        exit(1);
    }   
    puts("OK");

    exit(0);
}

编译并运行该程序, 输出结果:

fopen() failed! errno = 2

标准C中定义的errno类型:

类型 序号 含义
EPERM 1 Operation not permitted
ENOENT 2 No such file or directory
ESRCH 3 No such process
EINTR 4 Interrupted system call
EIO 5 I/O error
ENXIO 6 No such device or address
E2BIG 7 Argument list too long
ENOEXEC 8 Exec format error
EBADF 9 Bad file number
ECHILD 10 No child processes
EAGAIN 11 Try again
ENOMEM 12 Out of memory
EACCES 13 Permission denied
EFAULT 14 Bad address
... ... ...

根据上表中展示的errno类型, 可以得知, 2代表了文件或目录不存在

可以调用perror()strerror()来将errno转化为error message

mode

mode必须以表格中的字符开头

符号 模式
r 以只读形式打开文件, 打开时定位到文件开始处
r+ 读写形式打开文件, 打开时定位到文件开始处
w 写形式打开文件, 有则清空, 无则创建
w+ 读写形式打开文件, 有则清空, 无则创建
a 追加只写的形式打开文件, 如文件不存在, 则创建文件; 打开时定位到文件末尾处 (文件最后一个有效字节的下一个位置)
a+ 追加读写的形式打开文件, 如文件不存在, 则创建文件; 读位置在文件开始处, 而写位置永远在文件末尾处

注意:

  1. rr+要求文件必须存在

  2. mode可以追加字符b, 如rb/r+b, b表示二进制流, 在POSIX环境(包括Linux环境)下, b可以忽略

面试题:

FILE *fp;
fp = fopen("tmp", "r+write"); // 语句2

问: 语句2是否会报错?

并不会, fopen函数只会识别r+, 后面的字符会被忽略

FILE *

fopen返回的FILE结构体指针指向的内存块存在在哪里?

堆上

有逆操作的, 返回指针的函数, 其返回的指针一定指向上某一块空间

如无逆操作, 则有可能指向堆, 也有可能指向静态区

关闭操作

由于fopen返回的指针在堆上, 因此需要有一逆操作释放这一堆上的空间

int fclose(FILE *fp);

小例子

一个进程中, 打开的文件个数的上限?

#include <stdlib.h>
#include <stdio.h>
#include <errno.h>

int main()
{
        FILE *fp;
        int cnt = 0;

        while (1)
        {
                fp = fopen("tmp", "r");
                if (fp == NULL)
                {
                        perror("fopen()");
                        break;
                }
                cnt ++;
        }

        printf("count = %d\n", cnt);

        exit(0);
}

运行结果:

fopen(): Too many open files
count = 1021

在不更改当前默认环境的情况下, 进程默认打开三个流: stdin, stdout, stderr

ulimit -a可以查看当前默认环境的资源限制, 其中包括默认最多打开流的个数:

$ ulimit -a
real-time non-blocking time  (microseconds, -R) unlimited
core file size              (blocks, -c) 0
data seg size               (kbytes, -d) unlimited
scheduling priority                 (-e) 0
file size                   (blocks, -f) unlimited
pending signals                     (-i) 7303
max locked memory           (kbytes, -l) 251856
max memory size             (kbytes, -m) unlimited
open files                          (-n) 1024 # 默认最多1024个流
pipe size                (512 bytes, -p) 8
POSIX message queues         (bytes, -q) 819200
real-time priority                  (-r) 0
stack size                  (kbytes, -s) 8192
cpu time                   (seconds, -t) unlimited
max user processes                  (-u) 7303
virtual memory              (kbytes, -v) unlimited
file locks                          (-x) unlimited

由于最多可以打开1024个stream, 而默认已经打开了三个, 因此程序输出的count的大小就等于1024 - 3 = 1021

文件权限

在上一案例中, 程序打开的tmp文件是由touch命令创造出来的, 其权限为0664, 为什么是0664?

公式: 权限 = 0666 & ~umask

umask的值可以通过umask命令查询, 该值主要用于防止权限过松的文件出现

$ umask
0002

读/写字符操作

  • 读字符
// 以unsigned char转为int的形式返回读到的字符
// 如读到文件末尾, 或发生错误, 返回EOF
int fgetc(FILE *stream); // 函数
int getc(FILE *stream);  // 宏
// getchar()相当于getc(stdin)
  • 写字符
int fputc(int c, FILE *stream);
int putc(int c, FILE *stream);

// 相当于putc(c, stdout)
int putchar(int c);

mycpy

实现复制文件命令mycpy

#include <stdlib.h>
#include <stdio.h>

int main(int argc, char **argv)
{
        FILE *fps, *fpd;
        int ch;

        if (argc < 3)
        {
                fprintf(stderr, "Usage:%s <src_file> <dest_file>", argv[0]);
                exit(1);
        }

        fps = fopen(argv[1], "r");
        if (fps == NULL)
        {
                perror("fopen()");
                exit(1);
        }
        fpd = fopen(argv[2], "w");
        if (fpd == NULL)
        {
                perror("fopen()");
                fclose(fps);
                exit(1);
        }

        while (1)
        {
                ch = fgetc(fps);
                if (ch == EOF)
                        break;
                fputc(ch, fpd);
        }

        // 先关闭依赖别人的流, 再关闭被依赖的文件
        fclose(fpd);
        fclose(fps);
}

编译后执行以下命令:

$ ./mycpy /etc/services ./out
$ diff /etc/services ./out

如果diff命令什么也没有输出, 则说明mycpy命令已正确执行

fsize

查看文件有效字符的个数

#include <stdlib.h>
#include <stdio.h>

int main(int argc, char **argv)
{
        FILE *fp;
        long long cnt = 0;

        if (argc < 2)
        {
                fprintf(stderr, "Usage:%s <file_name>", argv[0]);
                exit(1);
        }

        fp = fopen(argv[1], "r");
        if (fp == NULL)
        {
                perror("fopen()");
                exit(1);
        }

        while (fgetc(fp) != EOF)
                cnt ++;

        printf("%lld\n", cnt);

        fclose(fp);

        exit(0);
}

读写字符串

  • 读字符串:
// params:
// @s: 缓冲区
// @size: 缓冲区大小
char *fgets(char *s, int size, FILE *stream);

fgets有两种正常结束:

  1. 读到size-1个字节 (缓冲区内剩余一个字节需要存放'\0')

  2. 读到了'\n'字符 (文件末尾处默认有换行符)

问题:

假设有一文件:

abcd

问: 用fgets(buff, 5, file)语句读取该文件, 需要几次才能读完?

2次, 第一次读取到"abcd", 第二次读取到"\n"

  • 写字符串:
int fputs(const char *s, FILE *stream);

重写 mycpy

#include <stdlib.h>
#include <stdio.h>

#define BUFSIZE 1024

int main(int argc, char **argv)
{
        FILE *fps, *fpd;
        char buff[BUFSIZE];

        if (argc < 3)
        {
                fprintf(stderr, "Usage:%s <src_file> <dest_file>", argv[0]);
                exit(1);
        }

        fps = fopen(argv[1], "r");
        if (fps == NULL)
        {
                perror("fopen()");
                exit(1);
        }
        fpd = fopen(argv[2], "w");
        if (fpd == NULL)
        {
                perror("fopen()");
                fclose(fps);
                exit(1);
        }

        // === 1 ===
        // 利用读写字符串函数来完成文件复制
        while (fgets(buff, BUFSIZE, fps) != NULL)
        {
                fputs(buff, fpd);
        }
        // ===   ===

        // 先关闭依赖别人的流, 再关闭被依赖的文件
        fclose(fpd);
        fclose(fps);
}

重写后的代码改为利用读写字符串函数来完成复制文件的操作(见1处)

fread & fwrite

fread & fwrite用于二进制流的输入和输出

// 从stream流中读取nmemb个数据
// 每个数据的大小为size
// 读取到的所有数据保存到ptr指向的内存空间
size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream);
// 将ptr指向的数据输出到stream
size_t fwrite(const void *ptr, size_t size, size_t nemeb, FILE *stream);

问题:

要通过fread()从文件中读取字符串, 每次读取10个字符

  1. 假设文件中的有效字符数远大于10, 则2个语句各返回几?
// 语句1
fread(ptr, 1, 10, fp);
// 语句2
fread(ptr, 10, 1, fp);

语句1返回10(读到了10个大小为1的对象)

语句2返回1(读到了1个大小为10的对象)

  1. 假设文件中的有效字符数不足10个(比如5个), 则2个语句各返回几?

语句1返回5, 而语句2返回0

另外, 语句二返回0后, 它究竟读了多少个字符, 也无从得知; 因此, 如果要通过fread()从文件中读取字符串, 则一定要使用语句1的方法!

注意:

fread()fwrite()操作文件, 最好还是只做存取单一大小的数据(例如: 单一类型的结构体数据)的操作; 尽管如此, 这样的操作依然是有风险的, 因为一旦文件中由于各种原因含有了其他的数据, 那么fread()就会彻底失灵

printf & scanf

  • printf一族
int printf(const char *format, ...);
int fprintf(FILE *stream, const char *stream, ...);
// 将format与参数综合的结果, 输入到str中
int sprintf(char *str, const char *format, ...);
// 与sprintf类似, 只是多了对str大小的规定(size)以防止写越界
int snprintf(char *str, size_t size, const char *format, ...);

注意:

尽管printf一族提供了大量的输出函数, 但是这些函数还是不能完全解决问题

sprintfsnprintf中, str不能自行增长, 因此不能解决需要输出长字符串的需求

  • scanf一族
int scanf(const char *format, ...);
int fscanf(FILE *stream, const char *format, ...);
int sscanf(const char *str, const char *format, ...);

注意:

使用scanf一族时, 是不清楚要输入进来的数据有多长的

因此要注意, 输入文本的长度是否大于缓冲区的大小

年-月-日

year-month-day的格式打印日期:

#include <stdlib.h>
#include <stdio.h>

int main()
{
    char buf[1024];
    int year = 2014, month = 5, day = 13;

    sprintf(buf, "%d-%d-%d", year, month, day);
    puts(buf);

    exit(0);
}

文件位置

// 将文件指针定位到文件的某一位置(whence+offset)
// whence有三个选项: SEEK_SET(文件开始位置), SEEK_CUR(当前位置), SEEK_END(文件末尾位置)
// offset的单位为字节int fseek(FILE *stream, long offset, int whence);
long fseek(FILE *stream, long offset, int whence);
// 获得当前文件指针指向的文件位置
long ftell(FILE *stream);
// 相当于: (void) fseek(stream, 0L, SEEK_SET)
void rewind(FILE *stream);

文件位置指针

文件位置指针指向对文件进行操作的位置, 如:

fp = fopen(...);

for (i = 0; i < 10; i ++)
{
    fputc(fp);
}

在上述代码结束后, fp的文件位置指针指向文件第11个字节的位置

为了能将文件位置指针移动到文件内的任意位置上, 有了fseek等函数

flen

将原先的fsize.c复制为flen.c, 并重写为:

#include <stdlib.h>
#include <stdio.h>

int main(int argc, char **argv)
{
        FILE *fp;

        if (argc < 2)
        {
                fprintf(stderr, "Usage:%s <file_name>", argv[0]);
                exit(1);
        }

        fp = fopen(argv[1], "r");
        if (fp == NULL)
        {
                perror("fopen()");
                exit(1);
        }

        fseek(fp, 0, SEEK_END);
        printf("%ld\n", ftell(fp));

        fclose(fp);

        exit(0);
}

fseeko & ftello

由于ftell的返回值的类型为long, 而又不可能为负数, 因此其值域(在64位机器上)为$[0, 2^32 - 1]$, 因此利用ftellfseek一同工作时, 只能定位2G大小的文件

由于ftell有值域限制, 因此有了fseekoftello:

int fseeko(FILE *stream, off_t offset, int whence);
off_t ftello(FILE *stream);

在一些机器上, off_t是32位的, 在定义宏_FILE_OFFSET_BITS值为64后, 可以保证off_t为64位

可以在Makefile中定义CFLAG, 使编译器在编译阶段得知_FILE_OFFSET_BITS被定义为64:

CFLAGS+=-D_FILE_OFFSET_BITS=64

注意:

fseekoftello是POSIX环境的方言, C89和C99标准对其没有定义

刷新缓冲区

printf("Before while()"); // 第1行

while(1);

printf("After while()");

上述代码什么也不会打印, 这是由于标准输出是行缓冲的, 而"Before while()"并非一行内容

可以将第1行代码修改为printf("Before while()\n");或者在第1行代码后增加fflush(stdout);来刷新标准输出的缓冲区

缓冲区的作用: 大多数情况下是好事, 合并系统调用

行缓冲:

换行的时候刷新, 缓冲区满了的时候刷新, 强制刷新

全缓冲:

缓冲区满了的时候刷新, 强制刷新(默认, 只要不是终端设备)

无缓冲:

如stderr, 需要立即输出的内容

可以利用setvbuf修改缓冲模式

读取完整一行

实现了完整读取一行内容的函数:

// 从stream中读取一行内容, 存到lineptr指向的缓冲区
ssize_t getline(char **lineptr, size_t *n, FILE *stream);

使用时, 需要定义宏#define _GNU_SOURCE

可以在Makefile中, 添加CFLAG:

CFLAGS+=-D_GNU_SOURCE

注意:

  1. 不需要自己为缓冲区分配空间, lineptr可为一个值为NULL的指针变量的地址

  2. getline只能在GNU C环境中使用

mygetline

自行实现一个功能为从流中读取一行内容, 且在标准C环境中可以使用的工具:

#ifndef _MY_GETLINE_H__
#define _MY_GETLINE_H__

#define DEFAULT_LINE_BUF_SIZE 120

/*
 * 从stream中读取一行内容, 并将读取到的内容保存在*lineptr指向的缓冲区中
 * n指向缓冲区大小
 *      
 * 返回值:
 *      
 * 返回读取到的文本长度, 如果发生错误或读到文件末尾, 返回-1
 * */   
long long mygetline(char **lineptr, size_t *n, FILE *stream);

/*
 * 释放缓冲区占用的内存空间
 * */
void mygetline_free(char **lineptr);

#endif
#include <stdlib.h>
#include <stdio.h>

#include "mygetline.h"

long long mygetline(char **lineptr, size_t *n, FILE *stream)
{
        size_t buffsize = *n;
        char *linebuff = *lineptr;
        long long idx = 0LL;
        int ch;

        if (*lineptr == NULL || *n <= 0)
        {       
                buffsize = DEFAULT_LINE_BUF_SIZE;
                linebuff = malloc(buffsize * sizeof(char));
        }
        if (linebuff == NULL)
                return -1;

        while ((ch = fgetc(stream)) != EOF)
        {       
                if ((char)ch == '\n')
                        break;

                linebuff[idx ++] = (char)ch;

                if (idx >= buffsize - 1)
                {
                        buffsize += (buffsize >> 1);
                        linebuff = realloc(linebuff, buffsize);
                }
        }

        linebuff[idx] = '\0';

        *lineptr = linebuff;
        *n = buffsize;
        return ch != EOF ? idx : -1;
}


void mygetline_free(char **lineptr)
{
        if (*lineptr != NULL)
                free(*lineptr);
		*lineptr = NULL;
}

临时文件

  1. 如何不冲突地创建临时文件

  2. 及时销毁

可用函数: tmpnam/tmpfile

// 获得一个可用的临时文件名
// 注意: 使用该函数时, 需要:
// 1.首先拿到临时文件名
// 2.用该名称创建临时文件
// 由于这两步不是原子操作, 可能与其他进程产生冲突
// 因此该要谨慎使用该函数
char *tmpnam(char *s);
// 以二进制读写(w+b)模式打开一个临时文件
FILE *tmpfile(void);

下一章: 系统调用IO文章来源地址https://www.toymoban.com/news/detail-709941.html

到了这里,关于最全的李慧芹APUE-标准IO笔记的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • Day 30 标准IO

    标准IO:在c库中定义的一组专门用于输入输出的函数 (1)通过缓冲机制减少系统调用,提高效率 系统调用:内核向上(用户)提供的一组接口 例如:从硬盘读1kb数据每次只能读1b 若将数据先存入缓存区则可以减少硬件到用户层的调用次数 (2)围绕着流(stream)进行操作,流用FIL

    2024年02月21日
    浏览(21)
  • 七、标准IO和管道

    七、标准IO和管道 重定向I/O,为什么要进行重定向?可以帮我们解决什么问题? 使用管道符连接命令 1、I/O输入输出 显示器是Linux系统中创建默认的输出设备 I/O:input和output 重定向:将原本标准设备,替换为我们想要的内容 目的: 在shell脚本中对脚本输出的内容进行处理,

    2024年02月05日
    浏览(58)
  • 标准IO

     标准IO是库函数 是系统调用的封装 文件流:         1.具有方向性(读写)         2.具有连续性         3.具有顺序性 1.1 fopen FILE *fp = NULL; fp = fopen(\\\"m.txt\\\",\\\"r\\\");                  (\\\"要打开的文件名(以字符形式,所以要带引号)\\\", \\\"r/r+/w/w+/a/a+(也要带引号)\\\"); 成功返回   打开

    2024年02月21日
    浏览(30)
  • FPGA中IO电平标准

    FPGA(现场可编程门阵列)的IO电平标准涉及到其输入和输出引脚的电压电平范围,以确保与其他器件和系统的互操作性。这些标准通常由行业组织(如JEDEC、IEEE等)制定,以确保设备之间的电气兼容性和稳定性。 以下是一些常见的FPGA IO电平标准: LVCMOS(低压差分CMOS) :这

    2024年02月06日
    浏览(32)
  • 【玩转Linux】标准io缓冲区的操作

    (꒪ꇴ꒪ ),hello我是 祐言 博客主页:C语言基础,Linux基础,软件配置领域博主🌍 快上🚘,一起学习! 送给读者的一句鸡汤🤔: 集中起来的意志可以击穿顽石! 作者水平很有限,如果发现错误,可在评论区指正,感谢🙏         标准I/O缓冲区是用于提高I/O性能的一种机制,

    2024年02月16日
    浏览(54)
  • Linux标准IO对文件的基本操作(一)

    在Linux标准IO中,就是用 库函数 对文件进行操作。在C语言中有许多库函数方便用户去对文件进行操作。下面谈谈一些简单基本的函数,这些函数都是被包含在头文件 stdio.h 中的。 要操作一个文件,我们肯定先 打开 它,这里打开并不是去右键文件然后左键打开。这里的打开是

    2024年02月15日
    浏览(43)
  • 区块链北大肖老师学习笔记3

    第四节:比特币的共识协议 数字货币和纸质货币区别是可以复制,叫作双花攻击  即double spending attack。 去中心化货币要解决两个问题:①数字货币的发行②怎么验证交易的有效性,防止double spending attack。    答案:①比特币的发行是由挖矿决定的 ②依靠区块链的数据结构 比特

    2024年02月09日
    浏览(29)
  • 区块链北大肖老师学习笔记4

    第五节 比特币系统的实现 区块链是去中心化的账本,比特币使用的是基于交易的这种账本模式(transaction[交易]-based ledger[账本])。系统当中并不会显示每个账户有多少钱。 比特币系统的全节点要维护一个叫UTXO(unspent transaction output)(还没有被花出去的交易的输出)的数据结构。区

    2023年04月19日
    浏览(31)
  • 区块链北大肖老师学习笔记5

    第六节 比特币网络 比特币工作在应用层(application layer:Bitcoin block chain),它的底层是一个网络层(network layer:P2P overlay network)。 比特币的P2P网络是非常简单的,所有节点都是对等的。不像有的P2P网络有所谓的超级节点(super node)、主节点(master node)。 要加入P2P网络首先得知道

    2024年01月17日
    浏览(45)
  • 【C语言趣味教程】(8) 标准 IO 流:输入和输出 | 标准输入 stdin | 标准输出 stdout | 详解 printf 和 scanf | 探讨 scanf 缓冲区问题和安全性问题

        🔗 《C语言趣味教程》👈 猛戳订阅!!! 0x00 引入:I/O 的概念 计算机中的输入和输出,简称 ,其中:  代表 Input,即输入。

    2024年02月09日
    浏览(49)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包