嵌入式——标准IO

1.标准IO介绍:

文件基础:

概念:

一组相关数据的有序集合

文件类型:

常规文件 r

目录文件 d linux中把目录也看作是文件

字符设备文件 c

块设备文件 b 优盘

管道文件 p

套接字文件 s

符号链接文件 l 相当于通向真正文件的门牌入口

Unix基础知识:系统调用和库函数

用户代码调用系统给我们提供的接口使显卡驱动输出(比如printf函数),一般通过库函数实现

为什么不直接调用内核?

不同系统给我们提供的接口是不一样的,比如linux\windows系统,提供的接口不一样,但如果想实现同样的输出,那就需要改动大量的代码,移植性差,这时候引入库函数,充当翻译官,将写的代码翻译成该系统下的系统调用,比如在windows系统下写好的代码移植到linux下,因为接口是不一样的,所以不能直接调用,而通过库函数则可以翻译成linux下的系统调用,再去调用输出驱动,库函数printf就是一个标准I/O

具体可参考该文件:

==系统调用与库函数的关系==:

库函数其实是别人写好的一些代码供我们使用,使用时只用包含头文件即可,库函数是一些系统调用的封装,几乎每一个操作系统都会有C库,C库相当于翻译官,代码在不同操作系统之间移植时,可以被C库翻译为不同操作系统下的系统调用,去请求内核的服务,进而访问硬件,因此移植性比较好,而直接使用系统调用,一是移植性差,二是缺乏安全性

系统调用与库函数的关系_为什么要用库函数代替系统调用-CSDN博客

标准IO介绍:

由ANSI C标准定义

主流操作系统上都实现了C库,标准IO通过缓冲机制减少系统调用,实现高效

流:

FILE指针:每个被使用的文件都在内存中开辟一个区域,用来存放文件的有关信息,这些信息是保存在一个结构体类型的变量中,该结构体类型是由系统定义的,取名为FILE。

标准I/O库的所有操作都是围绕流(stream)来进行的,在标准I/O中,流用FILE *来描述。

标准IO预定义三个流,程序运行时自动打开:

流(FILE),将文件看作资源,C语言中的文件都是以二进制性质存放的,当这些文件执行被调用移动等操作时,成为流(可类比为水流,水为资源),流分为文本流和二进制流,其实就是是用结构体封装好的文本信息,比如int fclose(FILE *stream);,参数为流指针也即是结构体指针或者看作文件信息指针,标准IO所有操作都是围绕流进行的

2.缓冲区实验

==那么为什么要有缓冲区呢?==

当计算机的高速部件与低速部件通讯时,必须将高速部件的输出暂存到某处,以保证高速部件与低速部件相吻合。通常情况下,就是为了高效的处理我们的cpu和i/o设备之间的交互,因为我们知道cpu处理速度是很快的。举个例子,电脑的cpu通常情况下要处理很多事务,而我们从键盘敲下的文字相对于cpu的处理是很慢的,cpu不能老等着我们,它可以这时候去处理别的事务。所以当我们敲小的文字被先放到了缓冲区,等待cpu最后的统一处理。这样就让计算机的cpu变得高效起来。

==那么缓冲区又有哪些分类呢?==

我们将缓冲区分为全缓冲、行缓冲和无缓冲。

全缓冲:我们缓存在缓冲区的东西在缓冲区满的时候,才写入磁盘或者我们调用fflush刷新缓冲区才能写入到磁盘。对于全缓冲,如果我们缓冲区没满,或者我们没有手动刷新缓存,那么缓存区的内容是不能写入到磁盘的。

行缓冲:我们标准输入、标准输出都是采用的行缓存,也就是遇到换行符的时候,才会将缓存区的东西写入到磁盘。

无缓存:有的时候,我们希望一些内容在第一时间写入磁盘或者显示出来,比如我们显示错误信息的时候,这时候典型的例子比如标准出错,它就是直接显示出错信息,而不会先放入缓存 ———————————————— 版权声明:本文为CSDN博主「小C博客」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。 原文链接:全缓冲、行缓冲、无缓冲_全缓冲 行缓冲 无缓冲-CSDN博客

man 2 查看系统调用函数

man 3 查看库函数

验证行缓冲:

代码如下:

#include <stdio.h>
​
#include <unistd.h>
​
​
int main()
​
{
​
  /*
​
  int i;
​
​
•    for(i=0;i<1023;i++)
​
  {
•      printf("a");
  }
  
  */
​
  printf("hello world!");
​
// printf("bc");
​
  /*while(1)
​
  {
•     sleep(1);
  }*/
​
  return 0;
}

执行结果如下:

造成上面结果的原因是:程序运行结束后也会输出结果,下面我们加上while(1)死循环

#include <stdio.h>
​
#include <unistd.h>
​
​
int main()
{
​
  /*
​
  int i;
  
•    for(i=0;i<1023;i++)
​
  {
•      printf("a");
  }
​
  */
​
  printf("hello world!");
​
// printf("bc");
​
  while(1)
  {
•     sleep(1);
  }
​
  return 0;
}

程序运行结果:

代码中while死循环里的sleep函数作用是使循环空间休眠,不会占用CPU

可见此时行缓冲方式并未输出结果。

下面改为printf("hello world\n");结果如下:

输出打印到终端,原因是行缓冲方式遇到\n输出

全缓冲的缓冲区:

#include <stdio.h>
​
#include <unistd.h>
​
​
​
int main()
​
{
​
  int i;
​
​
  for(i=0;i<100;i++)
​
  {
​
•      printf("a");
​
  }
​
  printf("hello world!");
​
​
​
  while(1)
​
  {
​
•     sleep(1);
​
  }
​
}

printf("a");中没有加'\n',根据程序结果可知:将a打印100次并没有输出

下面改为1000次,结果如下:

仍然没有输出,再将a改为打印2000次,结果如下:

那么行缓冲区大小是多少呢?根据上面的运行结果可知一共是1024个a,也就是是1k字节,下面进行验证:

将循环改为执行1023次

#include <stdio.h>
​
#include <unistd.h>
​
​
int main()
​
{
​
  int i;
​
​
​
  for(i=0;i<1023;i++)
​
  {
​
•      printf("a");
​
  }
​
// printf("hello world!");
​
  //printf("b");
​
  while(1)
​
  {
•     sleep(1);
  }
​
}

结果如下:

再将循环改为1024次,结果如下:

这样不太直观,下面将代码改为:

#include <stdio.h>
​
#include <unistd.h>
​
int main()
{
  int i;
​
  for(i=0;i<1023;i++)
​
  {
•      printf("a");
  }
​
// printf("hello world!");
​
  printf("b");
​
  while(1)
​
  {
•     sleep(1);
  }
}

执行结果为:

将输出'b'的打印语句改为 printf("bc");,输出结果为:

可见输出结果中最后输出了第1024位的b字符,对比前面结果(缓冲区中有1024个字节的字符时并没有输出),可见缓冲区大小为1024字节。

全缓冲与行缓冲:

==一般指向终端的都是行缓冲,其它则是全缓冲==,error一般为无缓冲直接显示到终端上。即每一次使用fopen打开文件时就是全缓冲,输出到终端就是行缓冲

https://www.baidu.com/link?url=TG-UENxa6LXw2elnWQQeKUfWMtRIp-jamyeH03sg3Jg-iVDSbLykeijYRJF0yxlpM2FZpC5FvbvRV-GZ3dPirb67LjoemRbY1HGmdoTTckW&wd=&eqid=d3012ce00001cad30000000463eb4120

行缓冲,全缓冲,无缓冲的详细介绍

摘自CSDN

一.标准IO缓冲区

1.1缓冲区的类型

行缓存:和终端相关的缓冲区就是行缓存 stdin stdout

全缓存: 和文件相关的就是全缓存 fopen打开的文件

不缓存:没有缓冲区 stderr

二.缓冲区的大小

2.2.1行缓存:大小1024 #include <head.h>

int main(int argc, const char* argv[]) { int ch; 对于标准输入行缓存大小验证的时候,必须有输入才有数据 scanf("%d", &ch); printf("size = %ld\n", stdin->IO_buf_end - stdin->IO_read_base); 1024

printf("size = %ld\n", stdout->_IO_buf_end - stdout->_IO_read_base);
 1024
 
fprintf(stderr,"q121231qweqweqweqweqweqweqwe2");
printf("size = %ld\n", stderr->_IO_buf_end - stderr->_IO_read_base);
0
while(1);
 
return 0;

}

2.2.2全缓存:大小4096 #include <head.h>

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

if ((fp = fopen("./tim.txt", "a+")) == NULL)
    PRINT_ERR("fopen error");
 
fprintf(fp,"q121231qweqweqweqweqweqweqwe2");
printf("size = %ld\n",fp->_IO_buf_end - fp->_IO_buf_base);
//4096
return 0;

} 2.2.3无缓冲: 为0

三.缓冲区的刷新时机

3.1行缓存的刷新时机

#include <head.h>

int main(int argc,const char * argv[]) { 1.行缓存遇到换行符的时候会刷新缓冲区 printf("hello world\n"); while(1);

2程序结束的时候行缓存也会被刷新
 printf("helloworld");
 
3.当输入和输出切换的时候也会刷新缓冲区
 int ch;
 printf("helloworld");
 scanf("%d",&ch);
 while(1);
 
4.当关闭文件描述符的时候也会刷新缓冲区
 printf("helloworld");
 fclose(stdout);
 while(1);
 
5.调用fflush主动刷新缓冲区
 printf("helloworld");
 fflush(stdout);
 while(1);
 
6.当缓冲区满也会刷新缓冲区
int i;
for(i=0;i<1028;i++){
    fputc('a',stdout);
}
while(1);
return 0;

}

3.2全缓存的刷新时机:

#include <head.h>

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

if ((fp = fopen("./hello.txt", "w")) == NULL)
    PRINT_ERR("fopen error");
 
1.换行符不会刷新全缓存
fprintf(fp,"hello world\n");
 while(1);
​
2.程序结束的时候全缓存也会被刷新
 fprintf(fp,"hello world");
 
3.当输入和输出切换的时候也会刷新全缓冲区
 int ch;
 fprintf(fp,"hello world\n");
 ch = fgetc(fp);
 while(1);
 
4.当关闭文件描述符的时候也会刷新全缓冲区
 printf(fp,"hello world\n");
 fclose(fp);
 while(1);
 
5.调用fflush主动刷新全缓冲区
 fprintf(fp,"hello world\n");
 fflush(fp);
 while(1);
 
6.当缓冲区满也会刷新全缓冲区
int i;
for(i=0;i<4097;i++){
    fputc('a',fp);
}
while(1);
return 0;

}

———————————————— 版权声明:本文为CSDN博主「向着太阳迎着光_」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。 原文链接:行缓冲,全缓冲,无缓冲的详细介绍_行缓冲和全缓冲-CSDN博客

3.文件的打开和关闭

文件的打开:占用资源

文件的关闭:释放资源

文件的打开函数:

FILE *fopen(const char *path,const char *mode);

失败返回NULL

path:普通文件当前路径不需要加目录,其他文件要使用完整的路径

mode:

参数:(重点)

r 只读打开,条件是文件必须存在,若不存在还读则会出错返回NULL

r+ 读写方式打开,条件同上,并且写的时候将数据从头开始写入

w 只写打开,若文件存在,则将文件里的数据清空,若文件不存在则重新创建

w+读写打开,其他同w

a 只写打开,文件不存在则创建,存在则将要写入的内容追加到文件末尾

a+ 读写打开,其他同a

文件打开模式区别和辨析:

w+ 打开文件并读写 1. 文件存在,则清空(也即写入空); 2. 文件不存在,则创建文件 ; 3. 文件流定位到开始位置, 所以read() 会得到空。

r+ 打开文件并读写 1. 文件存在,打开文件,文件指针定位到文件开始位置; 2. 文件不存在, 则报错文件不存在。

a+ 打开文件并读添 1. 文件存在,打开文件,文件指针定位到文件开始位置,但不清空;2. 文件不存在,创建文件; 3. 打开后读取时,在文件开头位置,4. 写入时,添加到文章末尾,并且指针位于添加后的末尾,所以再次读取会乱码。

另外,

\1. w 打开文件写入,也会清空文件,如果使用read(),则报错;a 打开文件添加,数据流添加到文件末尾,而不是w模式的清空后,添加到文件末尾。

\2. b可以附加到上述的字母后,形成rb, rb+, wb等等模式,针对二进制文件,比如exe, elf, jpeg格式的文件,进行文件操作; 在unix 类型的系统上,text格式与二进制的处理相同,但是非unix类型的系统上,换行格式不同,所以需要用加b模式来在指定是否是二进制。

返回值:出错时返回NULL指针,成功返回FILE*类型的流指针,使用fopen函数时必须判断是否为空指针

错误信息:

原型:void perror(const char *s);

perror("fopen"); 库函数 需要包含头文件 <stdio.h>

原型:char *strerror(int errno);

strerror(errno); 库函数 需要包含头文件 <errno.h> <string.h>

printf("fopen:%s\n",strerror(errno));

文件关闭:

int fclose(FILE *stream);

成功返回0,失败返回EOF(-1),并设置errno

流关闭后自动刷新缓冲区数据,释放缓冲区,比如:常规文件缓冲区写入磁盘

程序正常终止时,所有的流均被关闭

另外fclose函数参数不为NULL,否则会出现段错误


标准IO的读写:

按字符输入:

为什么返回值用int 呢? 因为char、unsigned char 的数据范围是-128-+127、0-255,而int的数据范围是-32768-+32767,数据范围足够大,足以容纳任何返回值

另外stdin也是FILE *的指针,是系统定义好的(标准输入--键盘)

打开文件后读取,读取操作从头进行,读完一个读写指针后移。

getchar()标准输入函数执行时会阻塞,等待用户输入

按字符输出:

三种字符输出函数的返回值都是int型

putc(c,stdout); stdout相当于FILE *,是系统已经定义好的标准输出(显示器)

int fputc(int c,FILE *stream);

调用fputc函数

#include <stdio.h>
​
​
int main()
​
{
   FILE *fp;
​
   int rec;
​
   fp = fopen("1.txt","w+");
​
​
   if(fp == NULL)
   {
•      perror("fopen");
​
•      return 0;
   }
​
   /*
​
   rec = fgetc(fp);
​
   if(rec == -1)
​
   {
•      perror("fgetc");
​
•      fclose(fp);
​
•      return 0;
   }
​
   printf("get char:%c\n",rec);
​
   rec = getchar();
​
   printf("get stdin:%c\n",rec);
​
   fclose(fp);
​
   fp = fopen("1.txt","r");
​
​
   if(fp == NULL)
​
   {
•      perror("fopen");
​
•      return 0;
   }
​
​
   rec = fgetc(fp);
​
   printf("get char:%c\n",rec);
​
   rec = fgetc(fp);
​
   printf("get char:%c\n",rec);
​
​
   rec = fgetc(fp);
​
   printf("get char:%c\n",rec);
​
  */
​
   int wrc = 'w';
​
   rec = fputc(wrc,fp);
​
   if(rec == -1)
   {
•     perror("fputc");
​
•     fclose(fp);
​
•     return 0;
   }
​
   printf("fputc:%c\n",rec);
​
   fclose(fp);
​
   return 0;
}

调用后需要对返回值进行出错判断,标准的出错判断代码为上面标红部分

按行读取字符:

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

注意返回值为char *类型的指针 形参为:缓存区、输入字符的大小、文件流指针(文件)

若定义输入的字符数为size,那么当从指定的文件中实际输入的字符数大于等于size-1时,会输入size-1个用户字符+‘\0’结束符到缓存区中(如果恰好输入size-1个字符,那么直接将在这些字符后加上'\0'输入到缓存区);若用户实际输入的字符数小于size-1,则直接将用户输入的字符原样输入到缓存区内存空间,后面跟上'\n'

如果程序中连续调用fgets函数会怎样?

下面进行验证:

源码如下:

#include <stdio.h>
​
​
​
int main()
{
   FILE *fp;
​
   char buff[100];
​
   char *ret;
​
   int retn;
​
   fp = fopen("1.txt","r");
​
   if(fp == NULL)
​
   {
•      perror("fopen");
​
•      return 0;
   }
​
​
   ret = fgets(buff,5,fp);
​
   if(ret == NULL)
​
   {
•      perror("fgets");
​
•      fclose(fp);
​
•      return 0;
   }
​
   printf("buff:%s\n",buff);
​
 
   ret = fgets(buff,5,fp);
​
   if(ret == NULL)
​
   {
•      perror("fgets");
​
•      fclose(fp);
​
•      return 0;
   }
​
   printf("buff:%s\n",buff);
​
   ret = fgets(buff,5,fp);
​
   if(ret == NULL)
​
   {
•      perror("fgets");
​
•      fclose(fp);
​
•      return 0;
   }
​
   printf("buff:%s\n",buff);
​
   fclose(fp);
​
   return 0;
​
}

如果程序执行前,流指针指向的文件1.txt内容为(size-1为4):

那么这三次分别读进缓存区的内容为:

aaaa'\0'

aa'\n'

wewe'\0'

结果如下:

输出aa时,将aa'\n' 完整输出后,因输出语句printf为行缓冲,遇到'\n'结束释放缓存,故再加一个回车

拓展:

函数原型:

char* fgets ( char* str, int size, FILE* stream)

*str: 字符型指针,用来存储所得数据的地址。字符数组。

size: 整型数据,要复制到str中的字符串的长度,包含终止NULL。

*stream:文件结构体指针,将要读取的文件流。

意义:从stream所指向的文件中读取size-1个字符送入字符串数组str中。

功能:从文件中读取字符串,每次只读取一行。

注意:

\1. fgets每次最多只能读取n-1个字符,第n个为NULL。

\2. 当遇到换行符或者EOF时,即使当前位置在n-1之前也读出结束。

\3. 若函数返回成功,则返回 字符串数组str的首地址

————————————————

版权声明:本文为CSDN博主「噗噗酱」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。

原文链接:C++之 fgets函数_c++ fgets-CSDN博客

按行输出函数:

课堂例程代码:

#include <stdio.h>
​
​
int main()
{
   FILE *fp;
​
   char buff[100];
​
   char *ret;
​
   int retn;
​
   fp = fopen("1.txt","r+");
​
​
   if(fp == NULL)
​
   {
•      perror("fopen");
​
•      return 0;
   }
​
   /*
​
   ret = fgets(buff,5,fp);
​
   if(ret == NULL)
​
   {
•      perror("fgets");
​
•      fclose(fp);
​
•      return 0;
   }
​
   ret = fgets(buff,5,fp);
​
   if(ret == NULL)
​
   {
•      perror("fgets");
​
•      fclose(fp);
​
•      return 0;
   }
​
   ret = fgets(buff,5,fp);
​
   if(ret == NULL)
​
   {
•      perror("fgets");
​
•      fclose(fp);
​
•      return 0;
   }
​
​
   printf("buff:%s\n",buff);
​
   */
​
   //puts("hello world");
​
​
   retn = fputs("hello world",fp);
​
​
   if(retn == -1)
​
   {
•      perror("fputs");
​
•      fclose(fp);
​
•      return 0;
   }
​
   printf("hahaha\n");
​
   fclose(fp);
​
   return 0;
​
}

假如代码执行前1.txt文件是这样的(注意打开文件模式是读写)

那么按照代码执行结果是:

假如原1.txt文件首行内容少,比如这样:

那么再来看下程序执行后的结果:

说明采用r+模式,原内容会被覆盖掉,从结果可以看出,原来首行的'\n'也被吃掉了,下一行接到了上面

若采用a+追加模式,内容会被直接追加到文件后面:

二进制读写:

文件读写过程:磁盘— 文件缓冲区 — 应用程序的内存空间

文本文件和二进制文件:

文本文件:文本文件只能存储文本,是一种特殊的二进制文件(解释格式确定--ASCII码或Unicode码)

二进制文件:除了文本文件都是二进制文件,绝大部分文件都是二进制文件,比如图形、图像、声音文件等

文本文件是基于字符编码的文件,常见的有ASCII、Unicode等,二进制文件是基于值编码的文件,可以看成是变长编码,你可以根据自己的需要,决定多少个比特代表一个值。

如果是需要频繁的保存和访问数据,那么应该采取二进制文件进行存放,这样可以节省存储空间和转换时间。

如果需要频繁的向终端显示数据或从终端读入数据,那么应该采用文本文件进行存放,这样可以节省转换时间。

文本文件的打开方式和二进制文件打开方式的区别:

(1)文本模式中回车被当成一个字符’\n’,在文件中如果读到0x1B,文本模式会认为这是文件结束符,会按照一定方式对数据做相应的转换。

(2)二进制模式中’\n’会被认为是两个字符0x0D,0x0A;在读到0x1B时,二进制模式不会对文件进行处理。

文本工具打开一个文件的过程是怎样的呢?拿记事本来说,它首先读取文件物理上所对应的二进制比特流,然后按照你所选择的解码方式来解释这个流,然后将解释结果显示出来。一般来说,你选取的解码方式会是ASCII码形式(ASCII码的一个字符是8个比特),接下来,它8个比特8个比特地来解释这个文件流。例如对于这么一个文件流”01000000_01000001_01000010_01000011”(下划线”_”,为了增强可读性手动添加的),第一个8比特”01000000”按ASCII码来解码的话,所对应的字符是字符”A”,同理其它3个8比特可分别解码为”BCD”,即这个文件流可解释成“ABCD”,然后记事本就将这个“ABCD”显示在屏幕上。

用记事本打开二进制文件与上面的情况类似。记事本无论打开什么文件都按既定的字符编码工作(如ASCII码),所以当他打开二进制文件时,出现乱码也是很必然的一件事情了,解码和译码不对应嘛。例如文件流”00000000_00000000_00000000_00000001”可能在二进制文件中对应的是一个四字节的整数int 1,在记事本里解释就变成了”NULL_NULL_NULL_SOH”这四个控制符。

————————————————

版权声明:本文为CSDN博主「Cacra」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。

原文链接:二进制文件、文本文件_用二进制文件和文本文件存储整型数-8765-CSDN博客

读写函数:

size_t fread(void *ptr,size_t size,size_t n,FILE * fp);

void *ptr,读取内容放的位置指针

size_t size,读取的块的大小

size_t n ,读取的个数

FILE *fp, 读取的文件流指针

size_t fwrite(const void *ptr,size_t size,size_t n,FILE *fp);

void *ptr,写文件内容的位置指针

size_t size,写的块的大小

size_t n ,写的个数

FILE *fp, 要写的文件流指针

读文本:

size_t fread(void *ptr,size_t size,size_t n,FILE * fp);

fread(从文件流读取数据)

表头文件 #include<stdio.h>

定义函数 size_t fread(void * ptr,size_t size,size_t count,FILE * stream);

函数说明 fread()用来从文件流中读取数据。参数stream为已打开的文件指针,参数ptr 指向欲存放读取进来的数据空间,读取的字节数以参数size*count来决定。 Fread()会返回实际读取到的count数目,如果此值比参数count来得小,则代表可能读到了文件尾了或者有错误发生(前者几率大),这时必须用feof()或ferror()来决定发生什么情况。

返回值 返回实际读取到的count数目。 ———————————————— 版权声明:本文为CSDN博主「fisher」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。 原文链接:C中fread()函数的返回值_c语言中fread返回值-CSDN博客

代码思路:

代码一开始先是定义一个文件流指针fp、接收返回值变量ret、指向内存的指针buff,fp用于接收打开文件函数返回的文件指针,ret接收读写函数的返回值,buff用于接收申请内存后的返回指针

接着调用文件打开函数:fopen,并进行打开成功失败的判断,然后申请一块内存并强制类型转换为char *类型并进行申请成功与否的判断

调用fread函数并使ret接收其返回值,并进行成功与否的判断,该函数的功能是将fp指向的文件的内容按照读一块,块大小为10字节的规则读取,放入用户申请得到的buff内存空间中,最后将内存空间的字符打印出来

源码如下:

#include <stdio.h>
​
#include <stdlib.h>
​
int main()
{
   FILE *fp;
​
   int ret;
​
   char *buff;
​
   if((fp = fopen("1.txt","r")) == NULL)
   {
•       perror("fopen");
​
•       return 0;
   }
​
​
   if((buff = (char *)malloc(100)) == NULL)
   {
•       perror("malloc");
​
•       fclose(fp);
​
•       return 0;
   }
​
​
   ret = fread(buff,10,1,fp);
​
​
   if(ret == -1)
​
   {
•     perror("fread");
​
•     goto end;
   }
​
​
   printf("buff:%s\n",buff);
​
end:
​
   free(buff);
​
   fclose(fp);
​
   return 0;
​
}

结果图:

要读取的文本:

读取到buff内存:

可见读了6个a,1个'\n',和下面一行'wew'共10个字符,另外不要忘了程序最后要释放内存、关闭文件

写读二进制:

思路:

1.首先定义一个结构体,并取一个别名STU,结构体成员分别为:name[]\age\sex[],分别用来存放学生的名字、年龄、性别,main函数中定义几个变量,fp用于接收文件打开函数的返回值,ret用来接收读写文件函数的返回值,以及定义了两个结构体类型的变量stu,stu2

2.调用fopen函数,并判断返回值

3.结构体成员初始化赋值

4.调用fwrite函数,并用ret接收其返回值,并对返回值进行判断,其中fwrite函数功能是将stu结构体内存里的内容,写入fp指向的文件,写入规则为:写1个块,块大小为结构体大小

5.若写入成功,打印"write succeed",注意先将文件关闭再打开,再调用fread函数进行读取,最后打印结构体成员值

另外要注意:要额外定义一个结构体变量stu2,用来存放从文件读入的内容,如果还用原来的stu,因为写入操作后stu指针移动到了最后,读取的话是从1.bin文件中去内容放到指针位置处,那么打印时就从指针处开始打印

#include <stdio.h>
​
#include <string.h>
​
​
​
typedef struct student
​
{
•    char name[20];
​
•    int  age;
​
•    char sex[10];
​
}STU;
​
​
int main()
​
{
•    FILE *fp;
​
•    int ret;
​
•    STU stu,stu2;
​
​
•    if((fp = fopen("1.bin","w+")) == NULL)
•    {
​
•        perror("fopen");
​
•        return 0;
​
•    }
​
•    strcpy(stu.name,"zhangsan");
​
•    stu.age = 49;
​
•    strcpy(stu.sex,"male");
​
   
•    ret = fwrite(&stu,sizeof(stu),1,fp);
​
•    if(ret == -1)
​
•    {
​
•       perror("fwrite");
​
•       goto end;
​
•    }
​
​
•    else
​
•    {
​
•       printf("write succeed\n");
​
•    }
​
•    fclose(fp);//重要
​
​
•    if((fp = fopen("1.bin","r")) == NULL)
​
•    {
​
•        perror("fopen");
​
•        return 0;
​
•    }
​
•    ret = fread(&stu2,sizeof(stu2),1,fp);
​
•    if(ret == -1)
​
•    {
•       perror("fread");
​
•       goto end;
•    }
​
•    printf("%s %d %s\n",stu2.name,stu2.age,stu2.sex);
​
end:
•    fclose(fp);    
​
•    return 0;
}

如果直接用VI编辑器打开,会出现下面的结果,原因是该文件为二进制文件,而VI编辑器可以正常打开文本文件,打开二进制文件时,会以字符的形式显示字符内容,这是由于文本显示是根据ASCII码显示的,VI编辑器相当于一个翻译器,将二进制数据按字节为单位以ASCII对应的字符显示出来,而有的无法显示(比如控制符)。而且,name[20]数组只赋值了8个字符,后面的空缺部分会随机显示,sex数组同理。

另外一定要注意:代码中标红部分为先关闭fp指向的文件,再打开。因为经过fwrite写入操作时,fp指针已经移动到了最后,如果直接读取,会什么也读不到。

注意:

文件写完后,文件流位置指针移到了末尾,如果直接就开始读,读不出来内容

解决方法:

移动位置指针(后面讲解)

关闭后重新打开,流指针会自动跳到开头


流刷新定位、格式化输入输出

流刷新定位:

流刷新:

若输出到显示器使用:fflush(stdout);

流的定位:

注意事项:

==打开模式a下,fseek无效==

rewind(fp) 相当于fseek(fp,0,SEEK_SET);

这三个函数只适用于2G以下的文件

获取文件大小:

==手动向1.txt文件中输入字符时,会自动追加\0==,这时如果求取文件大小会比用fwrite函数只写字符时要多一个,比如:

源码:

#include <stdio.h>
​
​
int main()
​
{
•    FILE *fp;
​
•    long n;
​
•    if((fp = fopen("1.txt","r")) == NULL)
​
•    {
•       perror("fopen");
​
•       return 0;
•    }
​
​
//    fwrite("qhc",3,1,fp);
​
•    fseek(fp,0,SEEK_END);
​
•    n = ftell(fp);
​
•    printf("%d\n",(int)n);
​
​
•    fclose(fp);
​
•    return 0;
}

假如文件注释,向文件1.txt中手动写入qhc,输出结果为4,此时文件指针定位到文件末尾即\0后面

假如将文件中的注释解除,则输出结果为3,文件指针定位到文件末尾,在字母c后面

格式化输入输出:

格式化输出:

格式化输入:

重点掌握sprintf函数和sscanf函数

标准IO练习:

源码:

思路:

①:类型变量定义:

FILE *fp; 用于接收文件打开函数返回的流指针,打开文件失败接收NULL

time_t ctime; 用于接收系统时钟调用函数time()返回的秒数,存放到类型为time_t的内存中

struct tm*cltime; 用于接收本地时间函数localtime()返回的结构体指针,即它指向秒数转化为本地时间后所存放的结构体

int line = 0; 初始行数为0

char buff[32]; 字符数组,用于接收从fp指向的文件中读取的字符串

②:以a+模式打开文件1.txt,fp接受返回后的指针

③:计算文件行号基数

④:循环体,每隔一秒向1.txt文件中写入系统时间转化后的本地时间,并递增显示行号

#include <stdio.h>
​
#include <time.h>
​
#include <unistd.h>
​
#include <string.h>
​
​
int main()
​
{
•    FILE *fp;
​
•    time_t ctime;
​
•    struct tm*cltime;
​
•    int line = 0;
​
•    char buff[32] = 0;
​
​
•    if((fp = fopen("1.txt","a+")) == NULL)
•    {
•        perror("fopen");
​
•        return 0;
•    }
​
​
•    while(fgets(buff,32,fp) != NULL)
​
•    {
•        if(buff[strlen(buff)-1] == '\n')
​
•        {
​
•          line++;
​
•        }
•    }
​
•    while(1)
​
•    {
​
•        ctime = time(NULL);  //参数传为NULL即可(如果不为空,秒数也会存放到参数里的指针指向的内存)
​
•        cltime = localtime(&ctime);  //注意参数是秒数存放的内存的地址(指针)
​
•        printf("%04d-%02d-%02d %02d:%02d:%02d\n",cltime->tm_year+1900,cltime->tm_mon+1,cltime->tm_mday,cltime->tm_hour+12,cltime->tm_min,cltime->tm_sec);
​
•        fprintf(fp,"%d %04d-%02d-%02d %02d:%02d:%02d\n",line,cltime->tm_year+1900,cltime->tm_mon+1,cltime->tm_mday,cltime->tm_hour+12,cltime->tm_min,cltime->tm_sec);//换行符不会刷新全缓冲的缓冲区
​
​
•        fflush(fp);
​
​
•        line++;
​
​
•        sleep(1);  //系统休眠函数,头文件为“unistd.h”;
•    }
​
​
•    fclose(fp);
​
•    return 0;
}

int tm_year 获取的数加1是正确的月份

int tm_mon 获取的数加上1900是正确的年份

计算文件内行总数:

while(fgets(buff,32,fp) != NULL)

{

if(buff[strlen(buff)-1] == '\n') //判断是否读到了行结束位置【文件中的输出每一行最后都有回车(fprintf格式化输出 秒后跟了一个'\n')】。若是,行号加1

{

line++;

}

}

注意写文件最后每一次循环都要加上fflush,将缓存区内容写到磁盘里面。

关于fprintf之后,内容没有写入到文件中:

https://www.baidu.com/link?url=5MhKDbJaqDu__NR0qvZlzGLDC628TzdKNgnCdTzbHcWtmUQ4hoVls_rYWio4hMB0Snbk5PEpp6dQ1UkCqT_o8KWWyTrwozBhRgt0D-2b-7u&wd=&eqid=928ab2b9000073830000000463eb39dd

文件IO

什么是文件IO?

又称为系统IO,系统调用,是操作系统提供的接口

是Posix定义的一组函数

不提供缓冲机制,每次读写都引起系统调用

文件IO的API:

open、close、read、write

文件IO的文件描述符:

是0-1023的一组数字(非负整数),代表文件

0 ,1, 2 分别表示 : 标准输入、标准输出、标准错误

而且实际打开文件的文件描述符从3开始,因为程序运行时会自动分配0,1,2三个默认的文件描述符,这三个位置已被占据。

文件IO操作通过文件描述符进行

文件的打开和关闭:

文件打开函数open:

只有两个参数的open函数只用来打开文件,有三个参数的open函数用于创建不存在的文件

成功时返回文件描述符,错误时返回EOF,需要进行判断

参数mode :创建文件时open函数的第三个参数指定新文件的权限,实际权限为:mode-umask

参数flags:

标准IO读写参数与文件IO读写参数对比:

下面为使用open函数创建一个文件1.txt:

#include <stdio.h>
​
#include <sys/types.h>
​
#include <sys/stat.h>
​
#include <fcntl.h>
​
​
int main()
​
{
•    int fd;
​
•    fd = open("1.txt",O_WRONLY | O_CREAT | O_TRUNC, 0664);
​
•    if(fd < 0)
•    {
•        printf("open failed\n");
​
•        return -1;
•    }
​
•    printf("succeed\n");
​
•    return 0;
}

注意使用open函数要包含三个头文件,且要对返回的fd文件描述符进行判断

文件的读取 写入 和定位:

读文件:

返回值:读取成功返回实际读取的字节数,出错返回-1,如果调用read函数之前已经到达文件末尾,则这次read返回0,另外count若为0,则read返回0

注意:read函数读完文件字符才返回字符数,如果使用while循环调用,循环体只能执行两次,若字符未达到文件尾,当读完时返回一个实际读取的字符数,再次调用read函数,read到达文件尾,返回0,可参考后面的代码,而且遇到\0结束,read为阻塞函数

==拓展:==

摘自man 手册:

返回值:

On success, the number of bytes read is returned (zero indicates end of
​
•       file), and the file position is advanced by this number.  It is not  an
​
•       error  if  this  number  is smaller than the number of bytes requested;
​
•       this may happen for example because fewer bytes are actually  available
​
•       right  now  (maybe  because we were close to end-of-file, or because we
​
•       are reading from a pipe, or from a terminal),  or  because  read()  was
​
•       interrupted  by  a  signal.  On error, -1 is returned, and errno is set
​
•       appropriately.  In this case it is left unspecified  whether  the  file
​
•       position (if any) changes.

read的返回值一共有三种情况:

\1. 大于0:成功读取的字节数;

\2. 等于0:到达文件尾;

\3. -1:发生错误,通过errno确定具体错误值。

Note:本次讨论只限于阻塞的fd,不讨论非阻塞情况。

通过这个man的介绍,看似read的应用很简单,但真的是这样吗?莫忘了Linux中文件是一个很common的概念。它可以是一个真实的文件,也可以是一个socket,一个设备,等等。对于真实的文件,文件尾EOF是一个确定的情况。

1.那么如果是一个socket,它的返回值何时为0呢?

所以无论是 本端还是对端调用shutdown或者close时,都有可能导致本端的read返回为0。这里之所以说可能导致,是因为shutdown时可以指定 shutdown的行为,是关闭发送还是接收。

2.还有,在read的过程中,如果被信号中断,究竟是返回-1,还是返回一个正值或者0呢?

如果已读取了一些字节,那么就返回copied即已读取了的字节数——小于我们要去读取的字节数。如果没有读取任何字节,那么就返回-1,且errno为 EINTR。

3.当对端关闭后,是否socket还可以读取对端关闭socket前发送的数据呢?

即使对端关闭socket,本端仍可以读取对端在关闭之前发送的数 据。

https://www.cnblogs.com/jingzhishen/p/4393155.html

写文件:

与read类似,可参考read函数

定位(fd):

可以参考fseek函数,参数几乎一样

注意:

与fseek一样,标准IO的fseek在fopen的a、a+模式下对写无效,对读有效;

而lseek同理,在open的O_WRONLY | O_CREAT | O_APPEND 和 O_RDWR | O_CREAT | O_APPEND

模式下对写无效,对读有效

作业:

下面是使用文件IO实现“每隔1秒向文件1.txt写入当前系统时间,行号递增”:

(本程序是修改过的,源码有问题,收录在错题集)

#include <stdio.h>
​
#include <unistd.h>
​
#include <sys/types.h>
​
#include <sys/stat.h>
​
#include <fcntl.h>
​
#include <time.h>
​
​
int main()
​
{
•    int fd;
​
•    int ret;
​
•    time_t ctime;
​
•    struct tm *cltime;
​
•    char buff[1024] = {0};
​
•    char buff1[1024]= {0};
​
•    int line  = 0;
​
​
•    if((fd = open("1.txt",O_RDWR | O_CREAT | O_APPEND, 0666)) == -1)
​
•    {
•        perror("open");
​
•        return -1;
•    }
​
//    printf("succeed fd:%d\n",fd);
​
•    ret = read(fd,buff1,sizeof(buff1));
​
//    printf("ret= %d\n",ret);
​
​
•    line = ret/23;
​
//    printf("line= %d\n",line);
​
​
•    while(1)
​
•    {
•        ctime = time(NULL);
​
​
•        cltime = localtime(&ctime);
​
//        printf("%04d-%02d-%02d %02d:%02d:%02d\n",cltime->tm_year + 1900,cltime->tm_mon + 1,cltime->tm_mday
​
//                ,cltime->tm_hour,cltime->tm_min,cltime->tm_sec);
​
•        sprintf(buff,"%02d %04d-%02d-%02d %02d:%02d:%02d\n",line,cltime->tm_year + 1900,cltime->tm_mon + 1,cltime->tm_mday
​
•                ,cltime->tm_hour,cltime->tm_min,cltime->tm_sec);
​
•        if(write(fd,buff,23) == -1)
•        {
•            close(fd);
​
•            return -1;
•        }
​
•        line++;
​
•        sleep(1);
•    }
​
•    close(fd);
​
•    return 0;
}


目录操作和库的使用:

opendir打开目录函数:

注意包含头文件<dirent.h>

读目录流内容:

函数readdir:

注意包含头文件<dirent.h>

参数是opendir返回的目录流指针,目录流指针指向打开的目录,readdir操作后,返回下一个目录项的struct dirent*类型的结构体指针,指向下一个目录项(结构体),而且每一个目录项的结构体都包含成员char d_name[256],具体可参见man 手册

closedir

参数为目录流指针dp

修改文件访问权限:

一般用第一个函数

chmod 第二个参数为要修改成的权限

注意如果在共享文件夹下,不方便使用,因为共享文件为linux与windows公用,而有些权限在windows下不可修改

获取文件属性(stat):

该函数使用时要加上头文件:

#include <sys/types.h>

#include <sys/stat.h>

#include <unistd.h>

注意stat函数的参数:第一个为要获取属性的源文件,第二个为获取后属性所存放的结构体的指针

且结构体类型为struct stat

下面是struct stat结构体的介绍:

判断文件类型、获取文件访问权限:

文件类型和获取文件权限均是通过访问成员st_mode得到

下面通过使用stat函数来获取文件属性信息,并打印到命令行:

#include <stdio.h>
​
#include <sys/types.h>
​
#include <sys/stat.h>
​
#include <unistd.h>
​
#include <time.h>
​
​
int main()
​
{
•    struct stat buff;
​
•    int ret;
​
•    int i;
​
•    struct tm* t;
​
​
•    if((ret = stat("chmod.c",&buff)) == -1)  //注意参数为指针,查看man手册
​
•    {
•       perror("stat");
​
•       return -1;
•    }
​
​
•    if(S_ISREG(buff.st_mode))
​
•    {
​
•       printf("-");
​
•    }
​
•    if(S_ISDIR(buff.st_mode))
​
•    {
​
•       printf("d");
​
•    }
​
•    if(S_ISCHR(buff.st_mode))
​
•    {
​
•       printf("c");
​
•    }
​
​
•    if(S_ISBLK(buff.st_mode))
​
•    {
​
•       printf("b");
​
•    }
​
​
•    if(S_ISFIFO(buff.st_mode))
​
•    {
​
•       printf("p");
​
•    }
​
​
•    if(S_ISSOCK(buff.st_mode))
​
•    {
​
•       printf("s");
​
•    }
​
​
​
•    for(i=8;i>=0;i--)
​
•    {
​
•       if(buff.st_mode & (1<<i))   //二进制移位操作,与运算
​
•       {
​
•         switch(i%3)
​
•         {
​
•         case 2:
​
•             printf("r");
​
•             break;
​
•         case 1:
​
•             printf("w");
​
•             break;
​
•         case 0:
​
•             printf("x");
​
•             break;
​
•         }
​
•       }
​
​
​
•       else
​
•       {
​
•           printf("-");
​
•       }
​
•    }
​
•    printf(" %d ",(int)buff.st_size);
​
​
​
•    t = localtime(&buff.st_ctime);
​
​
•    printf("%04d-%02d-%02d %02d:%02d:%02d",t->tm_year+1900,t->tm_mon+1,t->tm_mday,t->tm_hour,t->tm_min,t->tm_sec);
​
​
•    printf(" chmod.c\n");
​
•    return 0;
​
}

附:linux下的文件属性查看:

前面d开头的文件为目录文件,横线开头的为常规文件


静态库的使用:

可执行文件中必须有main函数

如果调用库文件里的内容,编译时就把相关代码和main函数合并,生成可执行文件

创建静态库:

①:编写库文件代码,编译生成.o目标文件

②:ar命令创建 libxxx.a 文件

ar -rsv libxxx.a xxx.o

注意:libxxx.a 静态库文件以lib为前缀,.a为后缀

没有mian函数的.c文件无法生成可执行文件,否则执行结果如下:

链接错误:

test.c:(.text+0x7): undefined reference to `hello'

collect2: error: ld returned 1 exit status

含义:表示hello函数在编译的源码内没有找到实现

解决方法:实现代码或者找到hello函数对应的库并链接它

链接静态库:

gcc -o 可执行文件 源.c文件 -L路径 -lxxx

-L 跟上库所在的路径

-l 跟上库的名称

动态库的使用(也叫共享库):

1.生成位置无关代码的目标文件(带有全局偏移表的.o文件):

gcc -c -fPIC xxx.c xxx.c.......

注意必须加上-fPIC(小写亦可),编译后生成的.o文件中用 nm 命令查看会有全局偏移表(不加-fPIC没有):

例如:

2.生成动态库:

将上一步所生成的目标文件用gcc 命令生成后缀为.so的动态库文件

gcc -shared -o libxxxx.so xxx.o xxx.o.......

3.链接动态库文件

gcc -o test test.c -L. -lmyheby

使test.c文件与上一步生成的共享库文件进行链接,生成可执行文件

执行动态库的可执行文件错误:

./test: error while loading shared libraries: libmyheby.so: cannot open shared object file: No such file or directory

含义:找不到可执行文件所需的共享库文件(程序运行加载动态库时不会在-L文件下查看,而是去/usr/lib或者/lib下找动态库)

解决方法:

①:找到共享库,放到/usr/lib里面

②:或者使用命令:

export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:你的共享库文件目录

将它添加在~/.bashrc文件的最后一行,使用source ~/.bashrc生效,其实就是每次执行可执行 文件时,它会自己跑到这个文件里面,查找用户定义的共享库文件目录里是否存在所需要的共享库文件

查看可执行文件的动态库:

ldd : ldd 可执行文件名

例如:

 linux@linux:~$ ldd test
​
•    linux-gate.so.1 =>  (0xb77bf000)
​
•    libmyheby.so => /home/linux/IO/libmyheby.so (0xb77b9000)
​
•    libc.so.6 => /lib/i386-linux-gnu/libc.so.6 (0xb75f6000)
​
•    /lib/ld-linux.so.2 (0xb77c0000)

如果没找到需要的动态库:

 linux@linux:~$ ldd test
​
•    linux-gate.so.1 =>  (0xb773f000)
​
•    libmyheby.so => not found
​
•    libc.so.6 => /lib/i386-linux-gnu/libc.so.6 (0xb7579000)
​
•    /lib/ld-linux.so.2 (0xb7740000)

静态库与动态库的区别:

动态库和静态库的区别_静态库与动态库区别-CSDN博客

静态库和动态库的区别-星雨-的博客-CSDN博客静态库和动态库的区别](静态库和动态库的区别-CSDN博客)

标准IO与文件IO的区别:

其实标准IO就是库函数,文件IO(系统IO)就是系统调用

①: 文件IO与标准IO遵循的标准不同:

文件I/O 又称为低级磁盘I/O,遵循POSIX相关标准。任何兼容POSIX标准的操作系统上都支持文件I/O。标准I/O被称为高级磁盘I/O,遵循ANSI C相关标准。只要开发环境中有标准I/O库,标准I/O就可以使用。(Linux 中使用的是GLIBC,它是标准C库的超集。不仅包含ANSI C中定义的函数,还包括POSIX标准中定义的函数。因此,Linux 下既可以使用标准I/O,也可以使用文件I/O)。

②:标准IO有缓冲机制,文件IO则没有

通过文件I/O读写文件时,每次操作都会执行相关系统调用。这样处理的好处是直接读写实际文件,坏处是频繁的系统调用会增加系统开销,标准I/O可以看成是在文件I/O的基础上封装了缓冲机制。先读写缓冲区,必要时再访问实际文件,从而减少了系统调用的次数。

③:操作的文件句柄(入口)不一样

文件I/O中用文件描述符表现一个打开的文件,可以访问不同类型的文件如普通文件、设备文件和管道文件等。而标准I/O中用FILE(流)表示一个打开的文件,通常只用来访问普通文件。

④:来源不同

标准IO来自标准C库,而文件IO来自操作系统内核

具体可参考:

文件I/O和标准I/O的区别_fp6886-CSDN博客

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值