当前位置: 首页 > news >正文

linux系统编程05-标准IO1

目录
  • 介绍
  • fopen
  • fclose
  • fgetc\fputc
  • fgets\fputs
  • fread\fwrite

介绍

IO是一切实现的基础

stdio :标准io

sysio :系统调用io(文件io)

  • 关系:标准io是用系统调用io实现的
  • 使用原则:能用标准io就用标准io(移植性好、可以加速)

Untitled

标准IO:

FILE 类型贯穿始终

fopen();
fclose();fgetc();
fputc();
fgets();
fputs();
fread();
fwrite();printf();
scanf();fseek();
ftell();
rewind();fflush();getline();临时文件:
tmpnam();
tmpfile();

man手册:1章基本命令;2章系统调用;3章标准io;7章机制(epoll/tcp/socket) man 7 epoll

fopen

##include<stdio.h>FILE *fopen(const char *pathname, const char *mode);
FILE *fopen(int fd, const char *mode);
FILE *fopen(const char *pathname, const char *mode, FILE *stream);

目前只介绍第一个原型

  • 返回值:成功返回 FILE * ,失败返回 errno
    • perror("提示信息")
    • strerror(errno)
  • 参数: 文件路径 pathname ; 打开方式 mode

示例代码:

##include<stdio.h>
##include<stdlib.h>
##include<errno.h>
##include<string.h>int main()
{FILE *fp;fp = fopen("tmp", "r");if(fp == NULL){//#include<errno.h>fprintf(stderr, "fopen() failed! errno = %d\n", errno);perror("fopen()");//#include<string.h>fprintf(stderr, "fopen()%s\n:", strerror(errno));exit(1);}puts("OK");flcose();exit(0);
}

运行结果:

Untitled

关于 mode 的要点:

  • 权限描述:

    r 只读;文件指针在开头;无不创建,报错
    r+ 读并写;文件指针在开头;无不创建,报错
    w 写;文件指针在开头;有则清空,无则创建
    w+ 读并写;文件指针在开头;有则清空,无则创建
    a 读并写;文件指针末尾的下一个;有则添加,无则创建
    a+ 读并写;文件指针看情况;有则添加,无则创建
  • mode is a string begining with one of above sequences 所以 fopen("tmp", "readwrite") 不会报错,而是会被识别为 fopen("tmp", "r")

  • 是否加 b :表示二进制:在windows环境下,stream分为”文本stream”和”二进制stream”,但是在linux环境下统一为stream,所以可以不添加

  • 关于 errno

    errno 在一开始是一个 全局 的整型变量,不同的值对应不同的错误。

    问题在于共用,所以如果不立刻打印就会被其他进程占据。

    现在的 errno 是一个私有的宏,映射到私有空间,不会产生数据冲突。

    //验证代码
    #include<errno.h>
    errno;
    

    终端执行: gcc -E errno.c 其中 -E 表示预处理

    运行结果:

    Untitled

  • mode 手册内容

    Untitled

  • fopen 创建文件时,被创建文件的权限

    linux中,默认文件创建权限为 0666 (0表示八进制),文件夹创建权限为 0777

    系统中有一个掩码 umask ,一般为 0002 ,文件创建权限为 0666 & ~umask 【或简单理解为 0666 - umask

    tmp权限为0664 = 0666-0002

    tmp权限为0664 = 0666-0002

fclose

fopen 的返回的指针存放在哪里:栈、静态区、堆

  • 如果存放在栈中,那么 fopen 执行结束后栈的空间会被销毁,那么必然拿不到指针,所以不会在栈中
  • 如果存放在静态区中 static FILE* ,那么由于静态区的变量只会被创建一次,那么打开多个文件时,就会使用同一个 FILE * ,这也不合理,所以不会放在静态区中
  • 放在堆中,可以解决上述问题,但是需要手动 mallocfree , mallocfopen 中,那么 free 必然是在 fclose

*fclose 一般不会失败,所以不检查返回值*

是资源一定有上限, fopen 最大能打开的文件数量也有限

测试代码:

##include<stdio.h>
##include<stdlib.h>
##include<errno.h>int main()
{FILE *fp = NULL;int cnt = 0;while(1){   fp = fopen("tmp","w");if(fp == NULL){perror("fopen()");break;}cnt++;}   printf("cnt = %d\n", cnt);exit(0);
}

执行结果:

Untitled

由于进程产生时默认打开三个stream, stdin stdout stderr ,所以最大打开的流数目是1024个

可以通过 ulimit -a 查看,通过 ulimit -n xxx 修改

Untitled

fgetc\fputc

man fgetcman fputc 可知:

getchar = getc(stdin) = fgetc(stdin)

putchar = putc(c,stdout) = fputc(c,stdout)

并且根据man手册,getc和fgetc,putc和fputc的函数原型是一样的,那么二者有什么区别呢,实际上没有区别,只不过最早的时候getc是作为宏,而fgetc是作为函数。

宏和函数什么区别?宏占用编译时间,函数占用调用时间,内核中的链表大多使用宏和内联函数,就是为了节省调用时间。

函数原型:

int fgetc(FILE *stream);
int fputc(int ch, FILE *stream); 

Untitled

Untitled

注意,虽然读的是字符,但是为了防止出错,会被转换为 int 类型,所以需要用 int 类型接受 fgetc 的返回值,以及传给 fputc

例子1:编写 mycopy.c 实现 cp 的功能

示例代码:

##include<stdio.h>
##include<stdlib.h>int main(char argc, char **argv)
{FILE *fps, *fpd;int ch; if(argc < 3){   fprintf(stderr, "usage: %s <sourc_file> <dest_file>\n", 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()");exit(1);}while(1){ch = fgetc(fps);if(ch == EOF)break;fputc(ch, fpd);}fclose(fpd);fclose(fps);exit(0);
}

运行结果:

Untitled

例子2:编写一个程序,计算一个文件中的有效字符

示例代码:

##include<stdio.h>                                                                                                                          
##include<stdlib.h>int main(char argc, char **argv)
{   if(argc < 2){   fprintf(stderr, "Usage: %s <file_name>", argv[0]);exit(1);}FILE *fp;int cnt = 0;fp = fopen(argv[1], "r");if(fp == NULL){   perror("fopen()");exit(1);}   while(fgetc(fp) != EOF)cnt++;printf("cnt = %d\n", cnt);fclose(fp);exit(0);
}

运行结果:

Untitled

fgets\fputs

函数原型:

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

fgets()

  • 参数:缓冲区指针,缓冲区大小,读取的流
  • 返回值:成功返回读取到的字符串,失败返回 NULL

fputs()

  • 参数:待写入的字符串、流
  • 返回值:成功返回一个非负数,失败返回 EOF 【一般不检查】

fgets()gets 的关系 : char *gets() 没有指定缓冲区大小,所以会出现缓冲区溢出等问题, fgets() 通过设定缓冲区大小解决了这一点。

fputs()puts() 的关系: puts(char *s) 无法输出流,默认 stdoutfputs() 可以

fgets() 正常结束有两种情况:

  1. 读取到 size-1 个字符,最后添加 \0
  2. 读取到 \n

缓冲区大小为5时,abcd实际上要读两次

缓冲区大小为5时,abcd实际上要读两次

例子:用 fgets()fputs() 实现指令 cp

示例代码:

##include<stdio.h>                                                                                                                                             
##include<stdlib.h>
##define BUFSIZE 1024int main(char argc, char **argv)
{       FILE *fps, *fpd;char buf[BUFSIZE];if(argc < 3){fprintf(stderr, "usage: %s <sourc_file> <dest_file>\n", 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()");exit(1);}while(fgets(buf, BUFSIZE, fps) != NULL)fputs(buf, fpd);fclose(fpd);fclose(fps);exit(0);
}

运行结果:

Untitled

fread\fwrite

函数原型:数据块读写

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);

fread 从stream中读取数据到ptr,每次读取的大小为size,读取nmemb个数据块

fwrite 将ptr中的数据写入到stream中,每次写入的大小为size,写入nmemb个数据块

返回值:成功读取或写入的 数据块 的个数

举例说明:

Untitled

  • 对于 fread(buf, 1, 10, fp)

数据量足够时返回10, 表示成功读取10个数据块,即读取10字节

数据量不足时返回成功读取的数据块个数x,即读取x个字节

  • 对于 fread(buf, 10, 1, fp)

数据量足够时返回1, 表示成功读取1个数据块,即读取10个字节

数据量不足时返回0, 此时读取0个字节

结论:一般场景下,推荐1个字节一个字节地读取,当作 fgetc() 来用,来保证读入文件的所有内容。

例子:用 fread()fwrite() 实现指令 cp

示例代码:

while((n = fread(buf, 1, BUFSIZE, fps)) > 0)fwrite(buf,1,n,fpd);

执行结果:

Untitled

其他的话:

在linux中io很重要:一切皆文件,所有的一切都抽象为文件进行控制,而程序员控制文件的方式就是io。比如socket传的是fd,fd可以通过 fdopen() 转换为 FILE,从而传唤为一个stream,然后程序员通过对stream的io来操控socket。

http://www.wxhsa.cn/company.asp?id=6131

相关文章:

  • linux系统编程07-文件IO\系统调用IO
  • linux系统编程06-标准IO2
  • linux系统编程08-高级IO
  • 第03周 预习、实验与作业:面向对象入门2与类的识别
  • 第8篇、Kafka 监控与调优实战指南
  • linux系统编程02-进程基本知识
  • linux系统编程03-并发:信号
  • linux系统编程04-并发:线程
  • 新手高效制作PPT的3个步骤:告别逻辑混乱,从构思到完成!
  • Avalonia:用 ReactiveUI 的方法绑定数据、事件和命令
  • 【pyQT 专栏】程序设置 windows 任务栏缩略图(.ico)教程
  • Say 题选记(9.14 - 9.20)
  • vm的配置
  • 力扣72题 编辑距离
  • 数学基本结构框架
  • 2025.9.16总结
  • 在 Tailscale 中禁用 DNS
  • 软件工程实践一:Git 使用教程(含分支与 Gitee)
  • 【青少年低空飞行玩意】设计图以及项目概况
  • C++ 多态
  • Python实现对比两个Excel表某个范围的内容并提取出差异
  • 我用AI给自己做了一整套专属表情包!攻略
  • 20250916 之所思 - 人生如梦
  • Vue3项目开发专题精讲【左扬精讲】—— 在线教育网站系统(基于 Vue3+TypeScript+Vite 的在线教育网站系统系统设计与实现)
  • 20250915
  • Python Socket网络编程(4)
  • 今日学习 dos命令和Java基础语法
  • Photoshop 2025 v26.0软件下载免费版安装教程(含 Photoshop 软件一键安装包免费下载渠道)
  • 课前问题列表
  • switch中初始化变量