《Linux应用文件编程(二) — 标准IO》

1. 标准IO

  与文件IO函数相类似,标准IO库中提供的是fopen、fclose、fread、fwrite等面向流对象的IO函数,这些函数在实现时本身就要调用linux的文件IO这些系统调用。

 

2. 标准IO的缓冲机制

   由于IO设备的访问速度与CPU的速度相差好几个数量级,为了协调IO设备与CPU的速度的不匹配,对于块设备,内核使用了页高速缓存,即数据会先被拷贝到操作系统内核的页缓存区中,然后才会从操作系统内核的缓存区拷贝到应用程序的地址空间。

  当应用程序尝试读取某块数据的时候,如果这块数据已经存放在页缓存中,那么这块数据就可以立即返回给应用程序,而不需要经过实际的物理读盘操作。当然,如果数据在应用程序读取之前并未被存放在页缓存中,那么就需要先将数据从磁盘读到页缓存中去。对于写操作来说,应用程序也会将数据先写到页缓存中去,数据是否被立即写到磁盘上去取决于应用程序所采用的写操作机制:如果用户采用的是同步写机制,那么数据会立即被写回到磁 盘上,应用程序会一直等到数据被写完为止;如果用户采用的是延迟写机制,那么应用程序就完全不需要等到数据全部被 写回到磁盘,数据只要被写到页缓存中去 就可以了。在延迟写机制的情况下,操作系统会定期地将放在页缓存中的数据刷到磁盘上。与异步写机制不同的是,延迟写机制在数据完全写到磁盘上得时候不会通 知应用程序,而异步写机制在数据完全写到磁盘上得时候是会返回给应用程序的。

  标准IO提供三种类型的缓冲:

全缓冲:

  一般用于访问真正的磁盘文件。 C 库会为文件访问申请一块内存,只有当文件内容将缓存填满或执行行冲刷函数flush时,C库才会将缓存内容写入内核中。

行缓冲:

  当在输入和输出中遇到换行符时,标准IO库执行IO操作。通常涉及到终端(例如标准输入和标准输出)使用的是行缓冲。

  对于行缓冲有两个限制:

  第一,因为标准IO库用来收集每一行的缓冲区的长度是固定的,所以只要填满了缓冲区,那么即使还没有写一个换行符,也进行IO操作。

  第二,任何时候只要通过标准IO库要求从(a)一个不带缓存的流,或者(b)一个行缓存的流(它要求从内核得到数据)得到输入数据,那么就会造成冲洗所有行缓冲输出流(因为后面读取的数据可能就是前面输出的数据)。其实第二种情况我们会经常遇到,当我们先调用printf输出一串不带换行符的字符时,执行完这条printf语句并不会立刻在屏幕中显示我们输出的数据,当我们接下来调用scanf从标准输入读取数据时,我们才看到前面输出的数据。

不带缓冲:

  标准IO库不对字符进行存储。例如,如果用标准IO函数fputs写15个字符到不带缓冲的流中,则该函数很可能直接调用write系统调用将这些字符立即写到相关的文件中。标准出错流stderr是不带缓冲的,这样为了让出错的信息可以尽快的显示出来。

 

3. 标准IO函数

3.1 fopen函数

#include <stdio.h>

FILE *fopen(const char *path, const char *mode);//mode为操作权限    //打开一个指定的文件
FILE *fdopen(int fd, const char *mode);                           //取一个现存的文件描述符,并使一个标准I/O流与该描述符相结合
FILE *freopen(const char *path, const char *mode, FILE *stream);  //用于重定向输入输出流的函数,将stream中的标准输入、输出、错误或者文件流重定向为filename文件中的内容。linux下需要重定向输出很容易使用 ./程序名 >test (>>test 追加),windows下的输入输出重定向可以使用freopen。
返回值: 成功返回一个FILE文件流指针,失败返回NULL,并且设置errno全局变量。 参数:
   mode: r:以只读方式打开文件,该文件必须存在,返回的文件流指针位于文件开始 r+ :以读/写方式打开文件,该文件必须存在,返回的文件流指针位于文件开始 w:打开只写文件,若文件存在则文件长度清为零,即该文件内容会消失;若文件不存在则创建该文件 w+:打开可读/写文件,若文件存在则文件长度清为零,即该文件内容会消失;若文件不存在则创建该文件 a:以附加的方式打开只写文件。若文件不存在,则会创建该文件;如果文件存在,则写入的数据会被加到文件尾后,即文件原先的内容会被保留。 a+:以附加方式打开可读/写的文件。若文件不存在,则会创建该文件,如果文件存在,则写入的数据会被加到文件尾后,即文件原先的内容会被保留。
          r(read): 只读
          w(write): 只写
          a(append): 追加
          t(text): 文本文件,可省略不写。例如r=rt。以打开一个文本文件
          b(binary): 二进制文件
          +: 读和写

  stream: 文件指针,通常使用标准流文件(stdin/stdout/stderr)

     fdopen常用于由创建管道及网络通信通道函数返回的描述符(我们可能从 open,dup,dup2,fcntl,pipe,socket,socketpair或accept函数得到此文件描述符)。因为这些特殊类型的文件不能用fopen打开,因此必须先调用设备专用函数以获得一个文件描述符,然后再用fdopen使一个标准I/O流与该描述符相关联,形态必须和原先文件描述词读写模式相同。

  对于fdopen函数,mode的参数稍有区别:1.因为该描述符已被打开,所以fdopen为写而打开并不截短该文件。2.不能用于创建文件(因为一个描述符引用一个文件,则该文件一定存在)。

例程:

fopen和fdopen函数

FILE *fp;
int fd;
if ((fp = fopen ("hello.txt", "w+")) == NULL)
    printf("fopen file error\n");
    return 0;}
fprintf(fp , "hello word\n");
fclose(fp);
if ((fd = open("hello.txt", O_RDWR )) == 1) {
    printf("open file fail\n");
    return 0;}
if ((fp = fdopen( fd , "a+")) == NULL)
    printf("fdopen open\n");
    return 0;
}
fprintf(fp , "linux c program");
fclose(fp);

freopen函数

freopen("test.in", "r", stdin);
程序的输入就会从标准输入流stdin转换到从文件"test.in"中输入。

freopen("test.out", "w", stdout);
程序的输出就会从原来的标准输出变成写入文件"test.out"中。

  

3.2 fread函数

#include <stdio.h>
size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream);

返回值:
   成功时fread返回的值与nmemb相等;若小于nmemb但是大于0,则可能是到了文件末尾,不够次数;若返回0,则是文件读取错误,不满一个size的大小。
  所以循环读取文件的时候,while(可以判断是否大于等于0)作为条件,里面再基于feof函数判断是否到了文件末尾而退出循环。 参数: Ptr:读取的数据存储的位置 Size:读取的每个单元的大小 Nmemb:读取的单元数量 Stream:从哪个文件流读取数据

  fread不能区分到达文件尾和错误,因此必须使用feof和ferror函数确定是到达文件尾,还是发生错误。

 例程:

int s[10];
 
if (fread(s, sizeof(int), 10, fp) < 0) {
     perror(“fread”);
     return  -1;
}

 

3.3 fwrite函数

#include <stdio.h>
size_t fwrite(const void *ptr, size_t size, size_t nmemb, FILE *stream);

返回值:
    成功时fwrite返回的值与nmemb相等;若小于nmemb表示出错,可以用perror函数查看错误原因。

参数:
    prt:要写入的数据缓冲区
    size:每次写入的单元大小
    nmemb:写入的单元数
    stream:写入到哪个文件流

 例程1:

struct  student {
       int  no;
       char name[8];
       float  score;
 
} s[] = {{ 1, “zhang”, 97}, {2, “wang”, 95}};
 
fwrite(s, sizeof(struct student), 2, fp);

 例程2:

write.c

#include <stdio.h>

#define PATH  "./1.txt"

typedef struct PEOPLE{
    char name[10];
    int age;
    char addr[10];
}PEOPLE;  //name和addr不能用指针形式,不然用fwrite的时候,用sizeof计算会有问题,导致写入后乱码

int main(void)
{
    FILE *fp;
    PEOPLE Myself;
    int len;

    Myself.age = 24;
    strcpy(Myself.name, "zhuangsan");
    strcpy(Myself.addr, "shenzhen");
        
    fp = fopen(PATH, "w");
    if(fp == NULL)
    {
        perror("fopen:");
     return 0; } //sizeof计算的是已用内存长度,如果是字符串指针,用strlen len
= fwrite(&Myself, sizeof(struct PEOPLE), 1, fp); if(len < 1) { perror("fwrite:");
     return 0; }
return 1; }

 

read.c

#include <stdio.h>

typedef struct PEOPLE{
    char name[10];
    int age;
    char addr[10];
}PEOPLE;

#define PATH   "./1.txt"


int main(void)
{
    PEOPLE Myself;
    FILE *fp;
    int len;

    fp = fopen(PATH, "r");
    if(fp == NULL)
    {
        perror("fopen:");
     return 0; } len
= fread(&Myself, sizeof(struct PEOPLE), 1, fp); if(len < 1) { perror("fread:");
     return 0;  } printf(
"name = %s \r\n", Myself.name); printf("age = %d \r\n", Myself.age); printf("addr = %s \r\n", Myself.addr); return 1; }

 

3.4 fclose函数

#include <stdio.h>

int fclose(FILE *fp);

返回值:
    成功返回0,刷新缓存,失败返回EOF,并且设置errno。

  

3.5 fseek函数

#include <stdio.h>

int fseek(FILE *stream, long offset, int whence);

返回值:
    重定位成功返回0,否则返回非零值

参数:
    stream:文件指针
    offset:指针的偏移量(该指针和文件指针不一样,该指针指的是文件内部数据)
    whence:指针偏移起始位置
        SEEK_SET  即0 文件开头
        SEEK_CUR 即1 文件当前位置
        SEEK_END 即2 文件结尾

fseek(fp,100,SEEK_SET);把fp指针移动到离文件开头100字节处
fseek(fp,100,SEEK_CUR);把fp指针移动到离文件当前位置100字节处
fseek(fp,100,SEEK_END);把fp指针退回到离文件结尾100字节处

  fseek的作用:重定位文件内部的指针。

  需要注意的是该函数不是重定位文件指针,而是重定位文件内部的指针,让指向文件内部数据的指针移到文件中我们感兴趣的数据上,重定位主要是这个目的。

  说明:执行成功,则stream指向以whence为基准,偏移offset个字节的位置。执行失败(比方说offset偏移的位置超出了文件大小),则保留原来stream的位置不变。

 

3.6 fflush函数 

#include<stdio.h>

int fflush(FILE *stream);

参数:
    stream就是由fopen打开的文件流

  fflush函数把文件流里的所有未写出的数据立即写出。调用fclose函数隐含执行了一次fflush操作,所以不必在调用fclose之前调用fflush。

 

3.7 fgetc、getc和getchar函数

#include<stdio.h>

int fgetc(FILE *stream);
int getc(FILE *stream);
int getchar();

  fgetc函数从文件流里取出下一个字节并把它作为一个字符返回。当它到达文件尾或出现错误时返回EOF。

  getc函数作用同fgetc一样。

  getchar函数的作用相当于getc(stdin),从标准输入里读取下一个字符。

fgetc和getc的区别:

  fgetc是一个函数,而getc是一个宏。一般来说宏产生较大的代码,但是避免了函数调用的堆栈操作,所以速度会比较快。getc的参数不应当是具有副作用的表达式。宏都要避免用副作用表示式

  1. 很多RISC处理器(如SUN SPARC)使用寄存器窗口重叠技术,(http://server.chinabyte.com/404/157904_1.shtml)
  2.在寄存器充足的情况下,根本不需要堆栈操作,fgetc函数比getc宏更快
  3. 在多线程的情况下,为了线程同步,getc和fgetc可能是相同的。

什么是副作用的表示式?

  

   在这个程序中,z2我想算8*8,但是传入的参数是5+3,6+2,宏替换之后就变成了5+3*6+2,最后结果就成了25。

3.8 fputc、putc和putchar函数

#include<stdio.h>

int fputc(int c, FILE *stream);
int putc(int c, FILE *stream);
int putchar(int c);

  fputc函数把一个字符写到输出文件流中。返回值是写入的值,失败返回EOF。

  putc函数作用同fputc函数.

  putchar函数相当于putc(c,stdout),将字符c写到标准输出文件流中。

注意:即使fgetc和fputc这些的返回值和参数都是int的,但是读取和写入都是以字符来传递的。

3.9 fgets和gets函数

#include <stdio.h>

char *fgets(char *s, int n, FILE *stream);
char *gets(char *s);
参数:
  • str-- 这是指向一个字符数组的指针,该数组存储了要读取的字符串。
  • n-- 这是要读取的最大字符数(包括最后的空字符)。通常是使用以 str 传递的数组长度。
  • stream-- 这是指向 FILE 对象的指针,该 FILE 对象标识了要从中读取字符的流。

  fgets函数把读到的字符写到s指向的字符串里,直到遇到换行符或者已经读取n-1个字符或者遇到文件尾。它会把遇到的换行符也传递到接收字符串里,再加上一个表示结尾的空字符尾0。

  fgets成功完成时返回一个指向字符串s的指针,如果文件流已经到达文件尾,fgets会设置这个文件流的EOF标识并返回一个空指针。如果出现读错误,fgets返回一个空指针并设置errno。

  与gets相比使用这个好处是:读取指定大小的数据,避免gets函数从stdin接收字符串而不检查它所复制的缓存的容积导致的缓存溢出问题。

  gets函数从标准输入读取数据并丢弃遇到的换行符,并在接收字符串尾部加上一个null字节。警告:gets对传输字符的个数并没有限制,所以它可能会溢出自己的传输缓冲区,所以应该避免使用gets函数。
实例:

#include<string.h>
#include<stdio.h>
 
int main ( void )
{
    FILE*stream;
    char string[]="Thisisatest";
    char msg[20];

    stream=fopen("DUMMY.FIL","w+");

    fwrite(string,strlen(string),1,stream);

    fseek(stream,0,SEEK_SET);

    fgets(msg,strlen(string)+1,stream);

    printf("%s",msg);
    fclose(stream);
    return 0;
}

 

3.10 fputs和puts函数

#include <stdio.h>

int fputs(const char *str, FILE *stream);
int puts(const char *str);

参数:
    str:这是一个数组,包含了要写入的以空字符终止的字符序列
    stream:指向FILE对象的指针,该FILE对象标识了要被写入字符串的流
返回值:
    函数返回值为非负整数,否则返回EOF(符号常量,其值为-1);
    成功写入一个字符串后,文件的位置指针会自动后移

  fputs是一个函数,具有的功能是向指定的文件写入一个字符串(不自动写入字符串结束标记符‘\0’)。

  puts()函数用来向标准输出设备(屏幕)输出字符串并换行,具体为:把字符串输出到标准输出设备,将'\0'转换为回车换行。

fputs和puts的区别:

  1.puts() 只能向标准输出流输出,而 fputs() 可以向任何流输出。

  2.使用 puts() 时,系统会在自动在其后添加换行符;而使用 fputs() 时,系统不会自动添加换行符。

  那么这是不是意味着使用 fputs() 时就要在后面添加一句“printf("\n");”换行呢?看情况!如果输入时使用的是 gets(),那么就要添加 printf 换行;但如果输入时用的是 fgets(),则不需要。

  因为使用 gets() 时,gets() 会将回车读取出来并丢弃,所以换行符不会像 scanf 那样被保留在缓冲区,也不会被 gets() 存储;而使用 fgets() 时,换行符会被 fgets() 读出来并存储在字符数组的最后,这样当这个字符数组被输出时换行符就会被输出并自动换行。

  但是也有例外,比如使用 fgets() 时指定了读取的长度,如只读取 5 个字符,事实上它只能存储 4 个字符,因为最后还要留一个空间给 '\0',而你却从键盘输入了多于 4 个字符,那么此时“敲”回车后换行符就不会被 fgets() 存储。数据都没有地方存放,哪有地方存放换行符呢!此时因为 fgets() 没有存储换行符,所以就不会换行了。

实例:

#include <stdio.h>
#include <conio.h>
int main(void)
{
    int i;
    char string[20];
    for(i=0;i<10;i++)
        string[i]='a';
    string[10]='\0';
    puts(string);
    getch();
    return 0;
}

  puts输出字符串时要遇到'\0’也就是字符结束符才停止,如上面的程序加上一句 string[10]='\0';

 

3.11 printf、fprintf和sprintf函数

#include <stdio.h>

int printf(const char *format, ...);
int sprintf(const *s, const char *format, ...);
int fprintf(FILE *stream, const char *format, ...);

  printf函数将输出送到标准输出流。
  sprintf函数把输出加上一个结尾空字符送到字符串s中。
  fprintf函数把输出输送到指定的文件流中。

  format为输出格式类型,通常有以下几种常用的转换控制符:

  

 

3.12 scanf、fscanf和sscanf函数

#include<stdio.h>

int scanf(const char *format, ...);
int fscanf(FILE *stream, const char *format, ...);
int sscanf(const char *s,const char *format,. ...);

  scanf系列函数使用一个格式字符串来控制输入数据的转化。
  scanf函数从标准输入流中读入数据。fscanf函数从指定的文件流中读入数据。sscanf函数则从指定的字符串中读入数据。

  format为输入格式类型,通常有以下几种常用的转换控制符:

  

   用法:https://blog.csdn.net/respectableking/article/details/76478897

 

3.13 ftell函数、rewind函数

#include <stdio.h>

long ftell(FILE *stream);
void rewind(FILE *stream);

rewind()函数无返回值,fseek成功返回0,ftell成功返回当前偏移量,失败返回-1,并且设置errno。

  

4. 例程

例1:

#include <string.h>
#include <stdio.h>
int main(void)
{
   FILE *stream;
   char msg[] = "this is a test";
   char buf[20];
   if ((stream = fopen("DUMMY.FIL", "w+"))
       == NULL)
   {
      fprintf(stderr,"Cannot open output file.\n");
      return 1;
   }
   /* write some data to the file */
   fwrite(msg, strlen(msg)+1, 1, stream);
   /* seek to the beginning of the file */
   fseek(stream, SEEK_SET, 0);
   /* read the data and display it */
   fread(buf, 1, strlen(msg)+1, stream);
   printf("%s\n", buf);
   fclose(stream);
   return 0;
}

 

例2:

#include  <stdio.h>
#include  <stdlib.h>
int main(int argc,char *argv[])
{
    FILE *fp1, *fp2;       //流指针
    char buf[1024];        //缓冲区
    int n;      //存放fread和fwrite函数的返回值
    if(argc <=2)    //如果参数错误
    {
       printf("请输入正确的参数\n!");   //参数错误
    }
    if ((fp1 = fopen(*(argv+1), "rb")) == NULL)
    //以只读方式打开源文件,读开始位置为文件开头
    {
         printf("读源文件%s发生错误\n",*(argv+1));
         return 1;      //出错退出
      }
    if ((fp2 = fopen(*(argv+2), "wb")) == NULL)
    //以只写方式打开目标文件,写开始位置为文件结尾
    {
         printf("打开目标文件%s失败\n",*(argv+2));
         return 2;       //出错退出
      }
   //开始复制文件,文件可能很大,缓冲一次装不下,所以使用一个循环进行读写*/
    while ((n = fread(buf, sizeof(char), 1024, fp1)) > 0)
    {
        //读源文件,直到将文件内容全部读完*/
        if (fwrite(buf, sizeof(char), n, fp2) == -1)
        {
            //将读出的内容全部写到目标文件中去
            printf("写如文件发生错误\n");
            return 3;    /*出错退出*/
        }
    }
    printf("从源文件%s读数据写入目标文件%s中完成\n",*(argv+1),*(argv+2)); //输出对应的提示
    if(n == -1)
    {
         //如果因为读入字节小于0而跳出循环,则说明出错了*/
         printf("读文件发生错误\n");
         return 4;     /*出错退出*/
    }
    fclose(fp1);   /*操作完毕,关闭源文件和目标文件*/
    fclose(fp2);
    return 0;
}

  

 

关于换行符的一些:https://blog.csdn.net/weixin_44879687/article/details/89739899

 

posted @ 2020-06-08 10:24  一个不知道干嘛的小萌新  阅读(326)  评论(0编辑  收藏  举报