Linux笔记 --- 标准IO

devtools/2024/9/24 11:10:12/

        系统IO的最大特点一个是更具通用性,不管是普通文件、管道文件、设备节点文件、接字文件等等都可以使用,另一个是他的简约性,对文件内数据的读写在任何情况下都是带任何格式的,而且数据的读写也都没有经过任何缓冲处理,这样做的理由是尽量精简内API,而更加丰富的功能应该交给第三方库去进一步完善。

        标准C库是最常用的第三方库,而标准IO就是标准C库中的一部分接口,这一部分口实际上是系统IO的封装,他提供了更加丰富的读写方式,比如可以按格式读写、按SCII码字符读写、按二进制读写、按行读写、按数据块读写等等,还可以提供数据读写冲功能,极大提高程序读写效率。

        所有的系统IO函数都是围绕所谓的“文件描述符”进行的,这个文件描符由函数open()获取,而所有的标准IO都是围绕所谓的“文件指针”进的,这个文件指针则是由fopen()获取的,他是第一个需要掌握的标准IO函数:

        打开/关闭

返回值的文件指针是一种指向FILE{}的结构体,在标准IO中被定义

 

可以看到,使用标准IO函数处理文件的最大特点是,数据将会先存储在一个标准IO缓冲区中,而后在一定条件下才被一并flush(冲洗,或称刷新)至内核缓冲区,而不是像系统IO那样,数据直接被 flush至内核。

注意到,标准IO函数 fopen()实质上是系统IO函数 open()的封装,他们是一一对应的,每一次fopen()都会导致系统分配一个file{}结构体和一个FILE{}来保存维护该文件的读写信息,每一次的打开和操作都可以不一样,是相对独立的,因此可以在多线程或者多进程中多次打开同一个文件,再利用文件空洞技术进行多点读写。

另外由上一节得知,标准输入输出设备是默认被打开的,在标准IO中也是一样,他们在程序的一开始就已经拥有相应的文件指针了:

跟fopen()配合使用的是fclose函数

 读写

标准IO的读写函数接非常多,下面列出最常用的函数集合

第一组:读写一个字符的函数        

需要注意的几点:

1,fgec()、getc()和getchar()返回值是int,而不是char,原因是因为他们在出 错或者读到文件末尾的时候需要返回一个值为—1的EOF标记,而char型数据有可能因为系统的差异而无法表示负整数。

2,当fgec()、getc()和getchar()返回EOF时,有可能是发生了错误,也有可能是读到了文件末尾,这是要用以下两个函数接口来进一步加以判断:

3,getchar()缺省从标准输入设备读取一个字符。

4,putchar()缺省从标准输出设备输出一个字符。

5,fgetc()和fputc()是函数,getc()和putc()是宏定义。

6,两组输入输出函数一般成对地使用,fgetc()和fputc(),getc()和putc(), getchar()和 putchar().

一个例子将这些函数的使用方法综合:

#include <stdio.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
#include <strings.h>
#include <stdlib.h>
#include <stdbool.h>
#include <errno.h>int main(int argc, char const *argv[])
{FILE *fp1 = fopen("in.txt","w");FILE *fp2 = fopen("out.txt","r");if(fp1 == NULL || fp2 == NULL){perror("fopen()");exit(1);}int c,total = 0;while (1){c = fgetc(fp2);if(c == EOF && feof(fp2)){printf("copy compeleted,%d was been copied\n",total);break;}else if(ferror(fp2)){perror("fgetc()");break;}fputc(c,fp1);total++;}fclose(fp1);fclose(fp2);return 0;
}

 

第二组:每次一行的读写函数 

值得注意的有以下几点:

1,fgets()跟fgetc()一样,当其返回NULL时并不能确定究竟是达到文件末尾还是碰到错误,需要用 feof()/ferror()来进一步判断。

2,fgets()每次读取至多不超过size个字节的一行,所谓“一行”即数据至多包含一个换行符\n′。

3,gets()是一个已经过时的接口,因为他没有指定自定义缓冲区s的大小,这样很容易造成缓冲区溢出,导致程序段访问错误。

4,fgets()和fputs(),gets()和puts()一般成对使用,鉴于gets()的不安全性, 一般建议使用前者。

下面是使用该组函数实现的普通文件拷贝示例代码:

#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>struct node //栈节点结构体
{int data;struct node *next;
};struct linked_stack //管理结构体
{int size;struct node *top;   //指向栈顶节点
};struct linked_stack *init_stack (void)
{struct linked_stack *s;s = malloc(sizeof(struct linked_stack));if(s != NULL){s->size = 0;s->top = NULL;}return s;
}//创建新节点
struct node *new_node(int data)
{struct node *new;new = malloc(sizeof(struct node));if(new != NULL){new->data = data;new->next = NULL;}return new;
}//压栈
bool push(struct linked_stack *s,struct node *new)
{if (s == NULL || new == NULL)return false;new->next = s->top;s->top = new;s->size++;return true;
}bool stack_empty(struct linked_stack *s)
{return s->size == 0;
}bool pop(struct linked_stack *s,struct node **p)
{if(s == NULL || p == NULL || stack_empty(s))return false;*p = s->top;s->top = s->top->next;(*p)->next = NULL;s->size--;return true;
}void show(struct linked_stack *A,struct linked_stack *B,struct linked_stack *C)
{FILE *fp; /*建立文件指针*/fp = fopen("/mnt/e/GZ2112/程序入门/test_txt/hano.txt","a");int maxlen,len;maxlen = A->size > B->size ? A->size : B->size;maxlen = maxlen > C->size ? maxlen : C->size;len = maxlen;struct node *t1 = A->top;struct node *t2 = B->top;struct node *t3 = C->top;int i;for (i = 0; i < maxlen; i++){if(t1 != NULL && len <= A->size){fprintf(fp,"%d",t1->data);t1 = t1->next;}fprintf(fp,"\t");if(t2 != NULL && len <= B->size){fprintf(fp,"%d",t2->data);t2 = t2->next;}fprintf(fp,"\t");if(t3 != NULL && len <= C->size){fprintf(fp,"%d",t3->data);t3 = t3->next;}fprintf(fp,"\t");fprintf(fp,"\n");len--;}fprintf(fp,"A\tB\tC\n");fprintf(fp,"-----------------\n");fclose(fp);
}void hano(struct linked_stack *A,struct linked_stack *B,struct linked_stack *C,int n)
{if(n <= 0)return;struct node *tmp;hano(A,C,B,n-1);    //将n-1块从A借助C移向Bgetchar();          //每回车一次进行一步show(A,B,C);pop(A,&tmp);push(C,tmp);        //将A最下面一块移动到Chano(B,A,C,n-1);    //将n-1块从B借助A移向C
}int main(int argc, char const *argv[])
{struct linked_stack *A = init_stack();struct linked_stack *B = init_stack();struct linked_stack *C = init_stack();int hanois = 0;scanf("%d",&hanois);int i;for (i = 0; i < hanois; i++){struct node *new = new_node(hanois-i);  //将汉诺塔中的块放入栈A(柱A)push(A,new);}hano(A,B,C,hanois);show(A,B,C);return 0;
}

第三组:每次读写若干数据块的标准IO接口函数

这一组标准IO函数被称为“直接IO函数”或者“二进制IO函数”,因为他们对数据的读写严格按照规定的数据块数和数据块的大小来处理,而不会对数据格式做任何处理,而且当数据块中出现特殊字符,比如换行符”\n、字符串结束标记”\0等时不会受到影响。需要注意的几点:

1,如果fread()返回值小于nmemb时,则可能已达末尾,或者遇到错误,需要借助于feof()/ferror()来加以进一步判断。

2,当发生上述第1种情况时,其返回值并不能真正反映其读取或者写入的数据块数,而只是一个所谓的“截短值”,比如正常读取5个数据块,每个数据块100个字节,在执行成功的情况下返回值是5,表示读到5个数据块总共500个字节,但是如果只读到499个数据块,那么返回值就变成4,而如果读到99个字节,那么fread()会返回0。因此当发生返回值小于nmemb时,需要仔细确定究竟读取了几个字节,而不能直接从返回值确定。

第四组:获取或设置文件当前位置偏移量

 

注意:

        1.fseek函数的使用基本与lseek函数相同
        2.rewind(fp)相当于fseek(fp,0L,SEEK_SET);

利用上面两组函数再次实现读写功能:

#include <stdio.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
#include <strings.h>
#include <stdlib.h>
#include <stdbool.h>
#include <errno.h>#define BUFSIZE 100
#define NMEMB 5int main(int argc, char const *argv[])
{FILE *fp1 = fopen("in.txt","w");FILE *fp2 = fopen("out.txt","r");if(fp1 == NULL || fp2 == NULL){perror("fopen()");exit(1);}char buf[BUFSIZE];int total = 0;long pos1 , pos2;while (1){bzero(buf,BUFSIZE);pos1 = ftell(fp2);if (fread(buf,BUFSIZE,NMEMB,fp2) < NMEMB){if(feof(fp2)){//将未满NMENB的数据写入pos2 = ftell(fp2);fwrite(buf,pos2 - pos1,1,fp1);total += pos2-pos1; printf("copy compeleted,%d was been copied\n",total);break;}else if(ferror(fp2)){perror("fgetc()");break;}}fwrite(buf,BUFSIZE,NMEMB,fp1);total += BUFSIZE * NMEMB;}fclose(fp1);fclose(fp2);return 0;
}

第五组:标准格式化IO函数

格式化IO函数中最常用的莫过于printf()和scanf()了,但从上表中可以看到,他们其实各自都有一些功能类似的兄弟函数可用,使用这些函数需要注意以下几点:
        1,fprintf()不仅可以像printf()一样向标准输出设备输出信息,也可以向由stream指定的任何有相应权限的文件写入数据。
        2,sprintf()和snprintf()都是向一块自定义缓冲区写入数据,不同的是后者第二个参数提供了这块缓冲区的大小,避免缓冲区溢出,因此应尽量使用后者,放弃使用前者。
        3,fscanf()不仅可以像scanf()一样从标准输入设备读入信息,也可以从由stream指定的任何有相应权限的文件读入数据。
        4,sscanf()从一块由s指定的自定义缓冲区中读入数据。
        5,最重要的一条:这些函数的读写都是带格式的,这些所谓的格式由下表规定:

 这一组函数与之前最大的区别在于带有格式控制,因此适用于有格式的文件处理,比如学生信息等等此类数据。


http://www.ppmy.cn/devtools/94256.html

相关文章

C语言问答进阶--5、基本表达式和基本语句

赋值表达式 表达式是什么&#xff1f;表达式是由运算符和操作数组成的式子。 如下的代码 #include "iostream.h" int main() { int a1,b2,sum; cout<<(sumab)<<endl; return 0; } 那么如下的呢&#xff1f; #include "iostream.h" int mai…

前端 JS 经典:防抖和节流函数

1. 防抖函数 防抖函数&#xff08;Debounce Function&#xff09;用于处理频繁触发的事件&#xff0c;确保在事件触发后的一段特定时间内&#xff0c;如果再次触发则重新计时&#xff0c;只有在这段时间内没有再次触发时&#xff0c;才真正执行相应的函数。 以下是一个简单的…

Git的基本操作

1.创建用户信息 打开终端cmd git -v 查看版本可以确认是否已经安装成功git config --global user.name [用户名] //如果用户名存在空格就需要打“ ”git config --global user.email [邮箱]git config --global credential.helper store //保存用户信息git config --global -…

C# Unity 面向对象补全计划 七大原则 之 合成/聚合复用原则( CARP)难度:☆☆☆☆ 总结:在类中使用类,而不是继承类

本文仅作学习笔记与交流&#xff0c;不作任何商业用途&#xff0c;作者能力有限&#xff0c;如有不足还请斧正 本系列作为七大原则和设计模式的进阶知识&#xff0c;看不懂没关系 请看专栏&#xff1a;http://t.csdnimg.cn/mIitr&#xff0c;查漏补缺 1.合成/聚合复用原则&…

MySQL基础练习题34-游戏玩法分析4

目录 题目 准备数据 分析数据 总结 题目 报告在首次登录的第二天再次登录的玩家的 比率&#xff0c;四舍五入到小数点后两位。换句话说&#xff0c;你需要计算从首次登录日期开始至少连续两天登录的玩家的数量&#xff0c;然后除以玩家总数。 准备数据 ## 创建库 create…

MySQL基础练习题36-各赛事的用户注册率

目录 题目 准备数据 分析数据 题目 统计出各赛事的用户注册百分率&#xff0c;保留两位小数。 返回的结果表按 percentage 的 降序 排序&#xff0c;若相同则按 contest_id 的 升序 排序。 准备数据 ## 创建库 create database db; use db;## 创建Users表 Create table …

偏导数的可视化

偏导数的可视化 flyfish 函数 f ( x , y ) sin ⁡ ( x ) ⋅ cos ⁡ ( y ) f(x, y) \sin(x) \cdot \cos(y) f(x,y)sin(x)⋅cos(y) import numpy as np from sympy import lambdify, sin, cos from sympy.abc import x, y import matplotlib.pyplot as plt from mpl_toolk…

解决ONENOTE复制文字到外部为图片(Ditto)

默认情况下&#xff0c;在ONENOTE中记录的文字&#xff0c;在复制粘贴到外部时&#xff0c;会成为一张图片格式 如下图这段文字&#xff0c;粘贴到QQ中变为了图片 解决办法&#xff1a;安装Ditto Ditto下载链接 点击Download下载 双击安装.exe&#xff0c;选择安装路径后&…