当前位置: 首页 > 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.zskr.cn/news/6379.html

相关文章:

  • linux系统编程04-并发:线程
  • 新手高效制作PPT的3个步骤:告别逻辑混乱,从构思到完成!
  • 【pyQT 专栏】程序设置 windows 任务栏缩略图(.ico)教程
  • C++ 多态
  • Python Socket网络编程(4)
  • switch中初始化变量
  • C语言结构体中的内存对齐
  • 文件的读取操作
  • 【汇总】Qt常用模块头文件
  • 【IEEE出版、EI检索稳定】第四届云计算、大数据应用与软件工程国际学术会议(CBASE 2025)
  • 97. 交错字符串
  • VRRP实验
  • 25/9/16
  • 25/9/14(补)
  • VSCode + Python 开发踩坑:虚拟环境不在项目根目录导致包无法识别该怎么办
  • 图像与视频编码
  • Python爬虫实战:研究Pandas,构建地理信息资料采集和分析便捷的系统
  • fg/bg/jobs/kill命令--linux
  • 【征文启动】IvorySQL PostgreSQL 迁移实战经验征集:分享你的技术沉淀,赢取专属好礼!
  • ios电脑系统和windows系统
  • lc1029-两地调度
  • Java的运算符
  • HTML打包EXE工具中的WebView2内核更新指南
  • EXE一机一码打包加密大师 - 打包加壳原理
  • 动态修改线程池参数
  • 什么是网络+HTTP详解
  • 黑白世界
  • 【大三下】资料,仅内部学习使用
  • 挖掘PDF生成器中的SSRF漏洞:从发现到利用
  • 做题记录 2