C语言文件操作

v@[toc]

准确来说是一个卧底去偷取信息然后将信息传回。然后总部就可以根据这些信息操作了。

  对计算机来说,一切皆数据,超女的信息是数据、C语言源代码文件是数据、编译后的可执行程序也是数据,数据的存放方式有很多种,如内存、文件、数据库等,文件是极其重要的一种。

  平时我们基本上都是从电脑的界面操作文件,那么是否可以在编程的时候就可以操作文件呢,比如打开文件,读写文件,关闭文件。编程时能操作文件,对于数据的输入输出或者保存就很方便了。这节课将学习如何操作文件。根据文件中数据组织形式的不同,可以把文件分为文本文件和二进制文件,C语言源代码是文本文件,编译后的可执行程序是二进制文件。

一、文本数据和二进制

补充:
文本数据由字符串组成,字符串由字符组成

每个字符占一个字节,按文本格式存放数据的文件称为文本文件或ASCII文件

什么叫做文本格式

就是将数据拆分成一个一个字符

然后将每个字符的ASCII的二进制保存到文件中

比如文本“123”

将文本拆分成1、2、3

1的ASCII值为49,转换为二进制为00110001

2的ASCII值为50,转换为二进制为00110010

就是将它们的ASCII的二进制保存在文件中

如果用vi打开,就显示这些二进制对应的ASCII对应的单字符

接下来就是二进制数据

它不拆分成单字符,而是直接将数据转换成二进制,省掉了拆分成一个一个字符然后再将每个字符的ASCII转换成二进制

如果二进制数据,用vi打开,就会出错

因为vi显示的是文件中的数字对应的字符

比如我存储123用二进制数据方式存储,那么文件中保存的就是01111011

用vi打开

则就显示01111011对应的字符,这串数字对应的ASCII值为123,123对应的是大括号{

所以你看到的是大括号,而不是123

事与愿违

其实区分点就是转不转ASCII值再转二进制

二进制文本就是要直接写读写二进制数,所以要取地址,找到存放这串二进制数的地址。或者要找一个地址去存放取出来的。
写要找到存放数据的二进制串的地址,把它找到,因为写进去之前,内存中已经有它了,就是你之前已经定义了。比如你定义了一个超女结构体,并且你也已经初始化(赋值),那么要写进文件时,你只要找到他们就行——找结构体地址。
取出来的话,就是找一个容器去装。比如你定义一个结构体去装取出来的数据,那么把结构体的地址给取数据的函数

1、文本数据

  文本数据由字符串组成,存放了每个字符的 ASCII码值,每个字符占一个字节,每个字节存放一个字符。

例如数字 123,如果用文本格式存放,数据内容是’1’、‘2’、'3’三个字符,占三个字节,如下表所示。

字符’1’’2’’3’
ASCII(十进制)495051
ASCII(二进制)001100010011001000110011

2、二进制数据

  二进制数据是字节序列,数字123的二进制表示是01111011,如果用二进制格式形式存储,字符、短整型、短整型、长整型都可以存储123,存储方式分别如下:

1)字符型一个字节

01111011

2)短整型2个字节

00000000 01111011

3)整型4个字节

00000000 00000000 00000000 01111011

4)长整型8个字节

00000000 00000000 00000000 00000000 00000000 00000000 00000000 01111011

3、文本文件和二进制文件

  按文本格式存放数据的文件称为文本文件或ASCII文件,文件可以用vi和记事本打开,看到的都是ASCII字符。

按二进制格式存放数据的文件称为二进制文件,如果用vi打开二进制文件,看到的是乱码,没有意义。

二、打开文件

  C 语言对文件进行操作必须先“打开”文件,操作(读和写)完成后,再“关闭”文件。

1、文件指针

  打开一个房间,需要房间的钥匙和需要知道房间的位置。大家都住过酒店,当你想住进你预定的房间,前台登记后就会给你一张房卡,房卡就是钥匙上面还有预定的房间的位置。打开文件也是一样,需要打开文件的钥匙以及知道文件的位置。

  打开文件的时候,C语言为打开的文件分配一个文件信息区,该信息区中包含文件描述信息、缓冲区位置、缓冲区大小、文件读写到的位置等基本信息,这些信息保存在一个结构体类型变量中struct_IO_FILE),这个结构体有一个别名FILE(typedef struct _IO_FILE FILE),FILE结构体和对文件操作的库函数在 stdio.h 头文件中声明的。(在C语言中结构结构体可以包含多种类型的信息,所以用结构体)

  打开文件的时候,调用fopen函数时会动态分配一个FILE结构体,并把FILE结构体的地址作为函数的返回值,程序中用FILE结构体指针存放这个地址。调用关闭文件的函数fclose时候,除了关闭文件,还会释放文件指针占用的内存空间。

FILE结构体指针习惯称为文件指针。

2、打开文件

我们可以使用 C语言提供的库函数fopen(这个就是钥匙)来创建一个新的文件或者打开一个已存的文件,调用fopen函数成功后,返回一个文件指针( FILE *),函数的原型如下:

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

参数filename 是字符串(拿钥匙打开房间要知道房间号),表示需要打开的文件名,可以包含目录名,如果不包含路径就表示程序运行的当前目录。实际开发中,采用文件的全路径。

参数mode也是字符串,表示打开文件的方式(模式),打开方式可以是下列值中的一个。(打开房间要干嘛)

方式含 义说 明
r只读文件必须存在,否则打开失败。
w只写如果文件存在,则清除原文件内容;如果文件不存在,则新建文件。
a追加只写如果文件存在,则打开文件,如果文件不存在,则新建文件。
r+读写文件必须存在。在只读 r 的基础上加 ‘+’ 表示增加可写的功能。
w+读写在只写w的方式上增加可读的功能。
a+读写在追加只写a的方式上增加可读的功能。

英文单词:read简写r、write简写w、append简写a。

注意了,不同教材中对文件打开的方式有不同的说法。

有的说打开文本文件的方式要用"rt"、“wt”、“at”、“rt+”、“wt+”、“at+”,"t"是text的简写,"t"可以省略不写。

有的说打开二进制文件的方式要用"rb"、“wb”、“ab”、“rb+”、“wb+”、“ab+”,"b"是binary的简写。

准确的说,在Linux平台下,打开文本文件和二进制文件的方式没有区别。

在windows平台下,如果以“文本”方式打开文件,当读取文件的时候,系统会将所有的"/r/n"转换成"/n";当写入文件的时候,系统会将"/n"转换成"/r/n"写入, 如果以"二进制"方式打开文件,则读和写都不会进行这样的转换,真是罗嗦。

3、关闭文件

fclose库函数用于关闭文件,函数的原型:

int fclose(FILE *fp);

fp为fopen函数返回的文件指针。

示例(book108.c)

/*
 * 程序名:book108.c,此程序用于演示文件打开和关闭
 * 作者:C语言技术网(www.freecplus.net) 日期:20190525
*/
#include <stdio.h>

int main()
{
  FILE *fp=0;     // 定义文件指针变量fp

  // 以只读的方式打开文件/home/wucz/demo/book1.c
  if ( (fp=fopen("/home/wucz/demo/book1.c","r")) == 0 )
  {
    printf("打开文件/home/wucz/demo/book.c失败。\n"); return -1;
  }

  /* 上代码等同于以下代码
  fp=fopen("/oracle/c/book1.c","r");
  if (fp==0)
  {
    printf("打开文件/home/wucz/demo/book.c失败。\n"); return -1;
  }
  */
  /* 不信用这个代码来测试
  printf("fp=%p\n",(fp=fopen("/home/wucz/demo/book1.c","r")));
  printf("fp=%p\n",fp);
  */
  
  // 关闭文件
  fclose(fp);
}

对初学者来说,以下代码可能难以理解。

  if ( (fp=fopen("/home/wucz/demo/book1.c","r")) == 0 )

其实(fp=fopen("/home/wucz/demo/book1.c","r"))表达式的值就是fp,我在讲if分支语句的时候就讨论过了,估计大家都没把它放在心上,我们可以用代码来测试它。

如果还不理解,就这么抄吧,抄多了就熟了。

4、注意事项

1)调用fopen打开文件的时候,一定要判断返回值,如果文件不存在、或没有权限、或磁盘空间满了,都有可能造成打开文件失败。

2)文件指针是调用fopen的时候,系统动态分配了内存空间,函数返回或程序退出之前,必须用fclose关闭文件指针,释放内存,否则后果严重。

3)如果文件指针是空的,用fclose关闭它相当于操作空指针,后果严重。

三、文本文件的读写

在实际开发中,文本文件以行的形式存放字符串,如C程序的源代码,一段文字等,所以一般是按行写入和读取数据。

1、向文件中写入数据

C语言向文件中写入数据库函数有fputc、fputs、fprintf,在实际开发中,fputc和fputs没什么用,只介绍fprintf就可以了。fprintf函数的声明如下:

int fprintf(FILE *fp, const char *format, ...);

fprintf函数的用法与printf相同,只是多了第一个参数文件指针,表示把数据输出到文件。

程序员不必关心fprintf函数的返回值。

示例(book111.c)

/*
 * 程序名:book111.c,此程序用于演示向文件中写入文本数据
 * 作者:C语言技术网(www.freecplus.net) 日期:20190525
*/
#include <stdio.h>

int main()
{
  int   ii=0;
  FILE *fp=0;     // 定义文件指针变量fp

  // 以只写的方式打开文件/tmp/test1.txt
  if ( (fp=fopen("/tmp/test1.txt","w")) == 0) 
  {
    printf("fopen(/tmp/test1.txt) failed.\n"); return -1;
  }

  for (ii=0;ii<3;ii++) // 往文件中写入3行
  {
    fprintf(fp,"这是第%d条数数据。\n",ii+1);
  }
  
  // 关闭文件
  fclose(fp);
}

编译book111.c程序并执行,采用cat命令查看/tmp/test1.txt的内容,如下:

在这里插入图片描述

可以看到/tmp/test1.txt中有3行记录,程序book111不管执行多少次,文件/tmp/test1.txt的记录都是3行记录,因为文件打开的方式是"w",每次打开文件的时候都会清空原文件中的记录。

大家可以试一下把文件打开方式设置为"a",看看程序执行的效果。

2、从文件中读取数据

C语言从文件中读取数据的库函数有fgetc、fgets、fscanf,在实际开发中,fgetc和fscanf没什么用,只介绍fgets就可以了。fgets函数的原型如下:

char *fgets(char *buf, int size, FILE *fp);

fgets的功能是从文件中读取一行。

参数buf是一个字符串,用于保存从文件中读到的数据。

参数size是打算读取内容的长度。

参数fp是待读取文件的文件指针。

如果文件中将要读取的这一行的内容的长度小于size,fgets函数就读取一行,如果这一行的内容大于等于size,fgets函数就读取size-1字节的内容。

调用fgets函数如果成功的读取到内容,函数返回buf,如果读取错误或文件已结束,返回空,即0。如果fgets返回空,可以认为是文件结束而不是发生了错误,因为发生错误的情况极少出现。

示例(book113.c)

/*
 * 程序名:book113.c,此程序用于演示从文本文件中读取数据
 * 作者:C语言技术网(www.freecplus.net) 日期:20190525
*/
#include <stdio.h>
#include <string.h>

int main()
{
  FILE *fp=0;        // 定义文件指针变量fp
  char strbuf[301];  // 存放从文件中读取到的一行的内容

  // 以只读的方式打开文件/tmp/test1.txt
  if ( (fp=fopen("/tmp/test1.txt","r")) == 0) 
  {
    printf("fopen(/tmp/test1.txt) failed.\n"); return -1;
  }

  // 逐行读取文件的内容,输出到屏幕
  while (1)
  {
    memset(strbuf,0,sizeof(strbuf));
    if (fgets(strbuf,301,fp)==0) break;
    printf("%s",strbuf);
  }
  
  // 关闭文件
  fclose(fp);
}

运行效果

在这里插入图片描述

需要重点说明的是,在读取到 size-1个字符之前如果出现了换行,或者读到了文件末尾,则读取结束。

不管 size 的值多大,fgets函只读取一行数据,不能跨行。

在实际开发中,可以将 size 的值设置地足够大,确保每次都能读取到一行完整的数据。

四、二进制文件的读写

二进制文件没有行的概念,没有字符串的概念。

我们把内存中的数据结构直接写入二进制文件,读取的时候,也是从文件中读取数据结构的大小一块数据,直接保存到数据结构中。注意,这里所说的数据结构不只是结构体,是任意数据类型。

1、向文件中写入数据

fwrite函数用来向文件中写入数据块,它的原型为:

size_t fwrite(const void *ptr, size_t size, size_t nmemb, FILE *stream);

写什么数据块进去,写多少写多大的数据块,写到哪里。
写什么数据类型,那些数据现在在哪,找出来然后写到目的地——在哪要用到取地址符&+数据类型
写多大——sizeof(数据类型)
写到哪里——文件中(fp)

参数的说明:

ptr:为内存区块的指针,存放了要写入的数据的地址,它可以是数组、变量、结构体等。

size:固定填1。

nmemb:表示打算写入数据的字节数。

fp:表示文件指针。

函数的返回值是本次成功写入数据的字节数,一般情况下,程序员不必关心fwrite函数的返回值。

示例(book115.c)

/*
 * 程序名:book115.c,此程序用于演示向文件中写入二进制数据
 * 作者:C语言技术网(www.freecplus.net) 日期:20190525
*/
#include <stdio.h>
#include <string.h>

struct st_girl
{
  char name[50];     // 姓名
  int  age;          // 年龄
  int  height;       // 身高,单位:厘米cm
  char sc[30];       // 身材,火辣;普通;飞机场。
  char yz[30];       // 颜值,漂亮;一般;歪瓜裂枣。
};

int main()
{
  struct st_girl stgirl;  // 定义超女数据结构变量
  FILE *fp=0;     // 定义文件指针变量fp

  // 以只写的方式打开文件/tmp/test1.dat
  if ( (fp=fopen("/tmp/test1.dat","w")) == 0) 
  {
    printf("fopen(/tmp/test1.dat) failed.\n"); return -1;
  }

  strcpy(stgirl.name,"西施"); stgirl.age=18; stgirl.height=170;
  strcpy(stgirl.sc,"火辣"); strcpy(stgirl.yz,"漂亮");
  fwrite(&stgirl,1,sizeof(stgirl),fp);
  
  strcpy(stgirl.name,"芙蓉妹妹"); stgirl.age=38; stgirl.height=166;
  strcpy(stgirl.sc,"膘肥体壮"); strcpy(stgirl.yz,"让人终生不忘");
  fwrite(&stgirl,1,sizeof(stgirl),fp);
  
  // 关闭文件
  fclose(fp);
}

编译并运行程序,得到数据文件/tmp/test.dat,用vi命令打开文件,显示如下:

在这里插入图片描述

可以看到很多乱码,其实并不是文件的内容乱,而是vi无法识别文件的格式,把内容当成ASCII码显示,如果内容刚好是ASCII码,就能正确显示,如果不是ASCII码(如年龄和身高是整数),就无法正常显示了。

2、从文件中读取数据

fread函数用来从文件中读取数据块,它的原型为:

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

读什么类型的数据,在哪里,读多少,在哪里读
读的数据是什么类型,读出来要放在哪里——&(在哪里取地址符)数据类型
读多少——sizeof(数据类型)
在哪里读——文件中(fp)

ptr:用于存放从文件中读取数据的变量地址,它可以是数组、变量、结构体等。

size:固定填1。

nmemb:表示打算读取的数据的字节数。

fp:表示文件指针。

调用fread函数如果成功的读取到内容,函数返回读取到的内容的字节数,如果读取错误或文件已结束,返回空,即0。如果fread返回空,可以认为是文件结束而不是发生了错误,因为发生错误的情况极少出现。

示例(book117.c)

/*
 * 程序名:book117.c,此程序用于演示从文件中读取二进制数据
 * 作者:C语言技术网(www.freecplus.net) 日期:20190525
*/
#include <stdio.h>
#include <string.h>

struct st_girl
{
  char name[50];     // 姓名
  int  age;          // 年龄
  int  height;       // 身高,单位:厘米cm
  char sc[30];       // 身材,火辣;普通;飞机场。
  char yz[30];       // 颜值,漂亮;一般;歪瓜裂枣。
};

int main()
{
  struct st_girl stgirl;  // 定义超女数据结构变量
  FILE *fp=0;     // 定义文件指针变量fp

  // 以只读的方式打开文件/tmp/test1.dat
  if ( (fp=fopen("/tmp/test1.dat","rb")) == 0) 
  {
    printf("fopen(/tmp/test1.dat) failed.\n"); return -1;
  }

  while (1)
  {
    // 从文件中读取数据,存入超女数据结构变量中
    if (fread(&stgirl,1,sizeof(struct st_girl),fp)==0) break;
    // 显示超女数据结构变量的值
    printf("name=%s,age=%d,height=%d,sc=%s,yz=%s\n",\
          stgirl.name,stgirl.age,stgirl.height,stgirl.sc,stgirl.yz);
  }
  
  // 关闭文件
  fclose(fp);
}

运行效果

在这里插入图片描述

3、注意事项

1)我对fread和fwrite函数的size和nmemb以及它们的返回值的解释是不准确的,这么做的原因是为了方便大家的学习,正确的解释会把大家搞晕,等您功力提升之候,我们再讨论它的准确含义。

2)fwrite和fread函数也可以写入和读取文本文件,但是没有换行的概念,不管是换行符或其它的特殊字符,无区别对待。

3)一般来说,二进制文件有约定的数据格式,程序必须按约定的格式写入/读取数据,book115.c写入的是超女结构体,book117.c就要用超女结构体来存放读取到的数据。这道理就像图片查看软件无法打开音频文件,音频播放软件也无法打开图片文件,因为音频文件和图片文件的格式不同。

五、文件定位

在文件内部有一个位置指针,用来指向当前读写的位置。在文件打开时,如果打开方式是r和w,位置指针指向文件的第一个字节,如果打开方式是a,位置指针指向文件的尾部。每当从文件里读n个字节或文件里写入n个字节之后位置指针也会向后移动n个字节。

文件位置指针与C语言中的指针不是一回事。位置指针仅仅是一个标志,表示文件读写到的位置,不是变量的地址。文件每读写一次,位置指针就会移动一次,它不需要您在程序中定义和赋值,而是由系统自动设置,对程序员来说是隐藏的。

在实际开发中,偶尔需要移动位置指针,实现对指定位置数据的读写。我们把移动位置指针称为文件定位。

C语言提供了ftell、rewind和fseek三个库函数来实现文件定位功能。

1、ftell函数

ftell函数用来返回当前文件位置指针的值,这个值是当前位置相对于文件开始位置的字节数。它的声明如下:

long ftell(FILE *fp);

2、rewind函数

rewind函数用来将位置指针移动到文件开头,它的声明如下:

void rewind ( FILE *fp );

3、fseek函数

fseek() 用来将位置指针移动到任意位置,它的声明如下:

int fseek ( FILE *fp, long offset, int origin );

参数说明:

1)fp 为文件指针,也就是被移动的文件。

2)offset 为偏移量,也就是要移动的字节数。之所以为 long 类型,是希望移动的范围更大,能处理的文件更大。offset 为正时,向后移动;offset 为负时,向前移动。

3)origin 为起始位置,也就是从何处开始计算偏移量。C语言规定的起始位置有三种,分别为:0-文件开头;1-当前位置;2-文件末尾。

fseek(fp,100,0);     // 从文件的开始位置计算,向后移动100字节。
fseek(fp,100,1);     // 从文件的当前位置计算,向后移动100字节。
fseek(fp,-100,2);    // 从文件的尾部位置计算,向前移动100字节。

4、注意事项

当offset是向文件尾方向偏移的时候,无论偏移量是否超出文件尾,fseek都是返回0,当偏移量没有超出文件尾的时候,文件指针式指向正常的偏移地址的,当偏移量超出文件尾的时候,文件指针是指向文件尾的,不会返回偏移出错-1值。

当offset是向文件头方向偏移的时候,如果offset没有超出文件头,是正常偏移,文件指针指向正确的偏移地址,fseek返回值为0,当offset超出文件头时,fseek返回出错-1值,文件指针还是处于原来的位置。

文件指针是不是可以配合读写函数使用。如果和读函数使用可以读取到特定的内容,和写函数使用可以将内容写到特定的位置。

六、文件缓冲区

在操作系统中,存在一个内存缓冲区,当调用fprintf、fwrite等函数往文件写入数据的时候,数据并不会立即写入磁盘文件,而是先写入缓冲区,等缓冲区的数据满了之后才写入文件。还有一种情况就是程序调用了fclose时也会把缓冲区的数据写入文件。

在实际开发中,如果程序员想把缓冲区的数据立即写入文件,可以调用fflush库函数,它的声明如下:

int fflush(FILE *fp);

函数的参数只有一个,即文件指针,返回0成功,其它失败,程序员一般不关心它的返回值。

七、标准输入、标准输出和标准错误

Linux操作系统为每个程序默认打开三个文件,即标准输入stdin、标准输出stdout和标准错误输出stderr,其中0就是stdin,表示输入流,指从键盘输入,1代表stdout,2代表stderr,1,2默认是显示器。

printf("Hello world.\n");

等同于

fprintf(stdout,"Hello world.\n");

这几个文件指针没什么用,让大家了解一下就行。在实际开发中,我们一般会关闭这几个文件指针。

八、课后作业

在实际开发中,文件操作极其重要,本章节的课后作业一定要认真完成。

1)编写示例程序,从界面上输入五名超女的数据,存放在struct st_girl结构体数组中,然后把结构体数组以二进制的方式写入文件。

2)编写示例程序,把上一题写入的数据从二进制文件中读取出来,存入struct st_girl结构体中,然后在界面上显示出来。

3)编写示例程序,从界面上输入五名超女的数据,存放在struct st_girl结构体数组中,然后把结构体数组以xml字符串的方式写入文本文件。文件内容的格式如下:

<name>西施</name><age>20</age><height>166</height><sc>一般</sc><yz>漂亮</yz>
<name>王昭君</name><age>18</age><height>160</height><sc>火辣</sc><yz>一般</yz>
<name>杨玉环</name><age>22</age><height>177</height><sc>一般</sc><yz>漂亮</yz>
<name>陈圆圆</name><age>26</age><height>159</height><sc>火辣</sc><yz>不行</yz>

4)编写示例程序,把上一题写入的数据从文本文件中读取出来,并解析xml,存入struct st_girl结构体中,然后在界面上显示出来。

5)编写示例程序,实现文件复制的功能,文本文件用fget和fprintf读写?二进制文件用fread和fwrite读写?用fread和fwrite读写文本文件是什么效果?

6)编写示例程序,测试文件定位函数ftell、rewind和fseek的使用。

7)编写示例程序,测试文件缓冲函数fflush的使用。

九、版权声明

C语言技术网原创文章,转载请说明文章的来源、作者和原文的链接。
来源:C语言技术网(www.freecplus.net)
作者:码农有道

如果这篇文章对您有帮助,请点赞支持,或在您的博客中转发我的文章,谢谢!!!
如果文章有错别字,或者内容有错误,或其他的建议和意见,请您留言指正,非常感谢!!!

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值