【C语言探索之旅】 第二部分第七课:文件读写

来源:互联网 发布:网络粪坑 编辑:程序博客网 时间:2024/05/16 01:51


0



内容简介


1、课程大纲

2、第二部分第七课:文件读写

3、第二部分第八课预告: 动态分配



课程大纲


我们的课程分为四大部分,每一个部分结束后都会有练习题,并会公布答案。还会带大家用C语言编写三个游戏。


C语言编程基础知识


  • 什么是编程?

  • 工欲善其事,必先利其器

  • 你的第一个程序

  • 变量的世界

  • 运算那点事

  • 条件表达式

  • 循环语句

  • 实战:第一个C语言小游戏

  • 函数

  • 练习题

  • 习作:完善第一个C语言小游戏

C语言高级技术

  • 模块化编程

  • 进击的指针,C语言王牌

  • 数组

  • 字符串

  • 预处理

  • 创建你自己的变量类型

  • 文件读写

  • 动态分配

  • 实战:“悬挂小人”游戏

  • 安全的文本输入

  • 练习题

  • 习作:用自己的语言解释指针

用基于C语言的SDL库开发2D游戏

  • 安装SDL

  • 创建窗口和画布

  • 显示图像

  • 事件处理

  • 实战:“超级玛丽推箱子”游戏

  • 掌握时间的使用

  • 用SDL_ttf编辑文字

  • 用FMOD控制声音

  • 实战:可视化的声音谱线

  • 练习题

数据结构

  • 链表

  • 堆,栈和队列

  • 哈希表

  • 练习题




第二部分第七课:文件读写


我们学过了这么多变量的知识,就知道变量实在是很强大的,可以帮助我们实现很多事情。

变量固然强大,还是有缺陷的,最大的缺陷就是:不能永久保存。


因为C语言的变量储存在内存中,在你的程序退出时就被清除了,下次程序启动时就不能找回那个值了。


“蓦然回首,那人不在灯火阑珊处。”


“今天的你我

怎样重复昨天的故事。

这一张旧船票

能否登上你的破船?”


不能够啊,涛声不能依旧啊。


如果这样的话:我们如何在C语言编写的游戏中保存游戏的最高分呢?怎么用C语言写一个退出时依然保存文本的文本编辑器呢?


幸好,C语言中我们可以读写文件。这些文件会储存在我们电脑的硬盘上,就不会在程序退出或电脑关闭时被清除了。


为了实现文件读写,我们就要用到迄今为止我们所学过的知识:指针,结构体,字符串,等等。也算是一个复习吧。




文件的打开和关闭

为了读写文件,我们需要用到定义在stdio.h这个标准库头文件中的一些函数,结构,等。

是的,就是我们所熟知的stdio.h,我们的printf和scanf函数也是定义在这个头文件里。


下面按顺序列出我们打开一个文件,进行读或写操作所必须遵循的一个流程:


  1. 调用“文件打开”函数fopen(f是file,英语“文件”的首字母;open是英语“打开”的意思),返回一个指向该文件的指针。

  2. 检测文件打开是否成功,通过第1步中fopen的返回值(文件指针)来判断。如果指针为NULL,则表示打开失败,我们需要停止操作,并且返回一个错误。

  3. 如果文件打开成功(指针不为NULL),那么我们就可以接着用stdio.h中的函数来读写文件了。

  4. 一旦我们完成了读写操作,我们就要关闭文件,用fclose(close是英语“关闭”的意思)函数。


首先我们来学习如何使用fopen和fclose函数,之后我们再学习如何读写文件。


fopen:打开文件

函数fopen的原型是这样的:


FILE* fopen(const char* fileName, const char* openMode);


不难看出,这个函数接收两个参数:


  1. fileName:文件名。是一个字符串类型,而且是const,意味着不能改变其值。

  2. openMode:打开方式。表明我们打开文件之后要干什么的一个指标。只读、只写、读写。


这个函数的返回值,是 FILE *,也就是一个FILE(file是英语“文件”的意思)指针。FILE定义在stdio.h中。有兴趣的读者可以自己去找一下FILE的定义,我们给出FILE的一般定义:



typedef struct {char *fpos; /* Current position of file pointer (absolute address) */
void *base; /* Pointer to the base of the file */
unsigned short handle; /* File handle */
short flags; /* Flags (see FileFlags) */
short unget; /* 1-byte buffer for ungetc (b15=1 if non-empty) */
unsigned long alloc; /* Number of currently allocated bytes for the file */
unsigned short buffincrement; /* Number of bytes allocated at once */} FILE;



可以看到FILE是一个结构体,里面有7个变量。当然此处我们不必深究FILE的定义,我们只要会使用FILE就好了,而且不同操作系统对于FILE的定义不尽相同。


细心的读者也许会问:“之前不是说结构体的名称最好是首字母大写么,为什么FILE这个结构体每一个字母都是大写呢?怎么和常量的命名方式一样呢?”

好问题。其实我们之前建议的命名方式(对于结构体,首字母大写,例如:StructName)只是一个“规范”(虽然大多数程序员都喜欢遵循),并不是一个强制要求。这只能说明编写stdio.h的前辈并不一定遵循这个“规范”而已。当然,这对我们并没什么影响。所以小编你就是讲废话咯,好吧,好吧...


以下列出几种可供使用的打开方式:


r:只读。r是英语“read”(读)的首字母,这个模式下,我们只能读文件,而不能对文件写入。文件必须已经存在。


w:只写。w是英语“write”(写)的首字母,这个模式下,只能写入,不能读出文件的内容。如果文件不存在,将会被创建。


a:追加。a是英语“append”(追加)的首字母,这个模式下,从文件的末尾开始写入。如果文件不存在,将会被创建。


r+:读和写。这个模式下,可以读和写文件,但文件也必须已经存在。


w+:读,预先会删除文件内容。这个模式下,文件的内容首先会被清空。


a+:读写追加。这个模式下,读写文件都是从文件末尾开始。如果文件不存在,会被创建。


一般来说,“r”,“w”和“r+”用得比较多,“w+”模式要慎用,因为它会首先清空文件内容。当你需要往文件中添加内容时,“a”模式会很有用。


下面的例子程序就以“r+”(读写)的模式打开文件:


int main(int argc, char *argv[])

{

 FILE* file = NULL;


 file = fopen("test.txt", "r+");


 return 0;

}


于是,file成为了指向test.txt文件的一个指针。


你会问:“我们的test.txt文件位于哪里呢?”


text.txt文件和可执行文件位于同一目录下。


文件一定要是.txt结尾的吗?

不是,完全是你决定文件的后缀名。你大可以创建一个文件叫做xxx.level,用于记录游戏的关卡信息。


文件一定要和可执行文件在同一个文件夹下么?

也不是。理论上可以位于当前系统的任意文件夹里,只要在fopen函数的文件名参数里制定文件的路径就好了,例如:


file = fopen("folder/test.txt", "w");


这样,文件test.txt就是位于当前目录的文件夹folder里。这里的 folder/test.txt 称为“相对路径”。


我们也可以这样:


file = fopen("/home/user/folder/test.txt", "w");


这里的/home/user/folder/test.txt 称为“绝对路径”。


测试打开文件

在调用fopen函数尝试打开文件后,我们需要检测fopen的返回值,以判断打开是否成功。检测方法也很简单:如果fopen的返回值为NULL,那么打开失败;如果不是NULL,那么表示打开成功。示例如下:


int main(int argc, char *argv[])

{

 FILE* file = NULL;


 file = fopen("test.txt", "r+");


 if (file != NULL)

 {

     // 读写文件

 }

 else

 {

     // 显示一个错误提示信息

     printf("无法打开 test.txt 文件\n");

 }


 return 0;

}


记得每次使用fopen函数时都要对返回值作判断,因为如果文件不存在或者正被其他程序占用,那可能会使当前程序运行失败。


fclose:关闭文件

如果我们成功地打开了一个文件,那么我们就可以对文件进行读写了(读写的操作我们下一节再详述)。


如果我们对文件的操作已经结束,那么我们应该关闭这个文件,这样做是为了释放占用的文件指针。我们需要调用fclose函数来实现文件的关闭,这个函数可以释放内存,也就是从内存中删除你的文件(指针)。


函数原型:

int fclose(FILE* pointerOnFile);


这个函数只有一个参数:指向文件的指针


函数的返回值(int)有两种情况:


0: 当关闭操作成功时

EOF(一般是-1):如果关闭失败。


示例如下:


int main(int argc, char *argv[])

{

 FILE* file = NULL;


 file = fopen("test.txt", "r+");


 if (file != NULL)

 {

     // 读写文件

   

     // ...

   

     fclose(file); // 关闭我们之前打开的文件

 }


 return 0;

}



读写文件的不同方法

现在,我们既然已经知道怎么打开和关闭文件了,接下来我们就学习如何对文件进行读出和写入吧。


我们首先学习如何 写入 文件(相比读出要简单一些),之后我们再看如何从文件读出。


对文件写入

用于写入文件的函数有好几个,我们可以根据情况选择最适合的函数来使用。


我们来学习三个用于写入的函数:


fputc:在文件中写入一个字符(一次只写一个)


fputs:在文件中写入一个字符串


fprintf:在文件中写入一个格式化过的字符串,用法与printf是几乎相同的,只是多了一个文件指针


fputc

此函数用于在文件中一次写入一个字符。

函数原型:

int fputc(int character, FILE* pointerOnFile);


这个函数包含两个参数:


  1. character:int型变量,表示要写入的字符。我们也可以直接写'A'这样的形式,之前ASCII那节知识点没有忘吧。

  2. pointerOnFile:指向文件的指针。


函数返回int值。如果写入失败,则为EOF;否则,会是另一个值。


示例:


int main(int argc, char *argv[])

{

 FILE* file = NULL;


 file = fopen("test.txt", "w");


 if (file != NULL)

 {

     fputc('A', file); // 写入字符 A

     fclose(file);

 }


 return 0;

}


上面的程序用于向test.txt文件写入字符'A'。



fputs

这个函数和fputc类似,区别是fputc每次是写入一个字符,而fputs每次写入一个字符串。


函数原型:

int fputs(const char* string, FILE* pointerOnFile);


类似地,这个函数也接受两个参数:


  1. string:要写入的字符串。

  2. pointerOnFile:指向文件的指针。


如果出错,函数返回EOF;否则,返回异于EOF的值。


示例:


int main(int argc, char *argv[])

{

 FILE* file = NULL;


 file = fopen("test.txt", "w");


 if (file != NULL)

 {

     fputs("你好朋友\n最近怎么样?", file);

     fclose(file);

 }


 return 0;

}



fprintf

这个函数很有用,因为它不仅可以向文件写入字符串,而且这个字符串是可以自己格式化的。用法其实和printf函数类似,就是多了一个文件指针。


函数原型:

int fprintf(FILE *stream, const char *format, ...)


示例:


int main(int argc, char *argv[])

{

 FILE* file = NULL;

 int age = 0;


 file = fopen("test.txt", "w");


 if (file != NULL)

 {

     // 询问用户的年龄

     printf("您几岁了 ? ");

     scanf("%d", &age);


     // 写入文件

     fprintf(file, "使用者年龄是 %d 岁\n", age);

     fclose(file);

 }


 return 0;

}



从文件中读出

我们可以用与写入文件时类似名字的函数,只是略微修改了一些,也有三个:


fgetc:读出一个字符


fgets:读出一个字符串


fscanf:与scanf的用法类似,只是多了一个文件指针。scanf是从用户输入读取,而fscanf是从文件读取。


这次介绍这三个函数我们会简略一些,因为如果大家掌握好了前面那三个写入的函数,那这三个读出的函数是类似的。只是操作相反了。


fgetc

首先给出函数原型:

int fgetc(FILE* pointerOnFile);


函数返回值是读到的字符。如果不能读到字符,那会返回EOF。


但是如何知道我们从文件的哪个位置读取呢?是第三个字符处,还是第十个字符处呢?

其实,在我们读取文件时,有一个“游标”,会跟随移动。这当然是虚拟的游标,你不会在屏幕上看到它。你可以想象这个游标和你用记事本编辑文件时的闪动的光标类似。这个游标指示你当前在文件中的位置。


之后的小节,我们会学习如何移动这个游标,使其位于文件中特定的位置。可以使开头,也可以是第10个字符处。


fgetc函数每读入一个字符,这个游标就移动一个字符长度。我们就可以用一个循环来读出文件所有的字符。例如:


int main(int argc, char *argv[])

{

  FILE* file = NULL;

  int currentCharacter = 0;


  file = fopen("test.txt", "r");


  if (file != NULL)

  {

      // 循环读取,每次一个字符

      do

      {

          currentCharacter = fgetc(file); // 读取一个字符

          printf("%c", currentCharacter); // 显示读取到的字符

      } while (currentCharacter != EOF); // 我们继续,直到fgetc返回EOF(表示文件结束)为止


      fclose(file);

  }


  return 0;

}



fgets

此函数每次读出一个字符串,这样可以不必每次读一个字符,有时候效率太低。这个函数每次最多读取一行,因为它遇到第一个'\n'(换行符)会结束读取。所以如果我们想要读取多行,需要用循环。


《插入一点回车符和换行符的知识:


关于“回车”(carriage return)和“换行”(line feed)这两个概念的来历和区别。


在计算机还没有出现之前,有一种叫做电传打字机(Teletype Model 33)的玩意,每秒钟可以打10个字符。但是它有一个问题,就是打完一行换行的时候,要用去0.2秒,正好可以打两个字符。要是在这0.2秒里面,又有新的字符传过来,那么这个字符将丢失。

于是,研制人员想了个办法解决这个问题,就是在每行后面加两个表示结束的字符。一个叫做“回车”,告诉打字机把打印头定位在左边界;另一个叫做“换行”,告诉打字机把纸向下移一行。

这就是“换行”和“回车”的来历,从它们的英语名字上也可以看出一二。

后来,计算机发明了,这两个概念也就被般到了计算机上。那时,存储器很贵,一些科学家认为在每行结尾加两个字符太浪费了,加一个就可以。于是,就出现了分歧。

Unix/Linux系统里,每行结尾只有“<换行>”,即“\n”;Windows系统里面,每行结尾是“<换行><回车>”,即“\n\r”;Mac系统里,每行结尾是“<回车>”。一个直接后果是,Unix/Linux/Mac系统下的文件在Windows里打开的话,所有文字会变成一行;而Windows里的文件在Unix/Linux/Mac下打开的话,在每行的结尾可能会多出一个^M符号。


Linux中遇到换行符会进行回车+换行的操作,回车符反而只会作为控制字符显示,不发生回车的操作。而windows中要回车符+换行符才会回车+换行,缺少一个控制符或者顺序不对都不能正确的另起一行。》


函数原型:

char* fgets(char* string, int characterNumberToRead, FILE* pointerOnFile);


示例:


#define MAX_SIZE 1000 // 数组的最大尺寸 1000


int main(int argc, char *argv[])

{

   FILE* file = NULL;

   char string[MAX_SIZE] = ""; // 尺寸为MAX_SIZE的数组,初始为空


   file = fopen("test.txt", "r");


   if (file != NULL)

   {

       fgets(string, MAX_SIZE, file); // 我们读取最多MAX_SIZE个字符的字符串,将其存储在string中

       printf("%s\n", string); // 显示字符串


       fclose(file);

   }


   return 0;

}


这里,我们的MAX_SIZE足够大(1000),保证可以容纳下一行的字符数。所以遇到'\n'我们就停止读取,因此以上代码的作用就是读取文件中的一行字符,并将其输出。


那我们如何能够读取整个文件的内容呢?很简单,加一个循环。


如下:


#define MAX_SIZE 1000 // 数组的最大尺寸 1000


int main(int argc, char *argv[])

{

   FILE* file = NULL;

   char string[MAX_SIZE] = ""; // 尺寸为MAX_SIZE的数组,初始为空


   file = fopen("test.txt", "r");


   if (file != NULL)

   {

       while (fgets(string, MAX_SIZE, file) != NULL) // 我们一行一行的读取文件内容,只要不遇到文件结尾

           printf("%s\n", string); // 显示字符串


       fclose(file);

   }


   return 0;

}



fscanf

此函数的原理和scanf是一样的。负责从文件中读取规定样式的内容。


函数原型:

int fscanf(FILE *stream, const char *format, ...)


示例:


int main(int argc, char *argv[])

{

   FILE* file = NULL;

   int score[3] = {0}; // 包含3个最佳得分的数组


   file = fopen("test.txt", "r");


   if (file != NULL)

   {

       fscanf(file, "%d %d %d", &score[0], &score[1], &score[2]);

       printf("最佳得分是 : %d, %d 和 %d\n", score[0], score[1], score[2]);


       fclose(file);

   }


   return 0;

}


运行输出:


最佳得分是:23, 45, 67




在文件中移动

前面我们提到一个虚拟的“游标”,现在我们仔细地来学习一下。


每当我们打开一个文件的时候,实际上都存在一个游标,标识你当前在文件中所处的位置。你可以类比我们的文本编辑器,每次你在文本编辑器(例如 记事本)里面输入文字的时候,不是有一个游标(光标)可以到处移动么?它指示了你在文件中的位置,也就是你下一次输入会从哪里开始。


总结来说,游标系统使得我们可以在文件中指定位置进行读写操作。


我们介绍三个与文件中游标移动有关的函数:


  1. ftell:告知目前在文件中哪个位置

  2. fseek:移动文件中的游标到指定位置

  3. rewind:将游标重置到文件的开始位置(这和用fseek函数来使游标回到文件开始位置是一个效果)


ftell:指示目前在文件中的游标位置


这个函数使用起来非常简单,它返回一个long型的整数值,标明目前游标所在位置:

long ftell(FILE* pointerOnFile);


其中,pointerOnFile这个指针就是文件指针,指向当前文件。



fseek:使游标移动到指定位置


函数原型为:

int fseek(FILE* pointerOnFile, long move, int origin);


此函数能使游标在文件(pointerOnFile指针所指)中从位置(origin所指)开始移动一定距离(move所指)。


  1. move参数:可以是一个正整数,表明向前移动;0(不移动);或者负整数,表明回退。

  2. origin参数:它的取值可以是以下三个值(define所定义的常量)中的任意:

    SEEK_SET :文件开始处

    SEEK_CUR :游标当前所在位置

    SEEK_END :文件末尾


来看几个具体使用实例吧:


fseek(file, 5, SEEK_SET);

这行代码将游标放置到距离文件开始处5个位置的地方。


fseek(file, -3, SEEK_CUR);

这行代码将游标放置到距离当前位置往后3个位置的地方


fseek(file, 0, SEEK_END);

这行代码将游标放置到文件末尾。



rewind:使游标回到文件开始位置


这个函数的作用就相当于使用fseek来使游标回到0的位置


void rewind(FILE* pointerOnFile);


相信使用难不倒大家吧,看函数原型就一目了然了。和fseek(file, 0, SEEK_SET); 是一个效果。




文件的重命名和删除

我们来学习两个简单的函数,以结束我们这次的课程:


  1. rename函数:重命名一个文件(rename是英语“重命名”的意思)

  2. remove函数:删除一个文件(remove是英语“移除”的意思)


这两个函数的特殊之处就在于,不同于之前的一些文件操作函数,它们不需要文件指针作为参数,只需要传给这两个函数文件的名字就够了。


rename:重命名文件


函数原型:

int rename(const char* oldName, const char* newName);


oldName就是文件的“旧名字”,而newName是文件的“新名字”。


如果函数执行成功,则返回0;否则,返回非零的int型值。


以下是一个使用的例子:


int main(int argc, char *argv[])

{

rename("test.txt", "renamed_test.txt");


return 0;

}


很简单吧。


remove:删除一个文件


函数原型:

int remove(const char* fileToRemove);


fileToRemove就是要删除的文件名


注意:remove函数要慎用,因为它不会提示你是否确认删除文件。文件是直接从硬盘被永久删除了,也不会先移动至垃圾箱。想要再找回被删除的文件就只能借助一些特殊的软件了,但是恢复过程可能没那么容易,也不一定能够成功。


实例:


int main(int argc, char *argv[])

{

remove("test.txt");


return 0;

}




第二部分第八课预告:


今天的课就到这里,一起加油吧。

下一次我们学习:动态分配




程序员联盟社区

目前有一个微信群和一个QQ群(微信群120人以上,QQ群270人以上),凡是对编程感兴趣的朋友都可以加,大家可以交流,学习,互动,讨论写的程序的源代码,编程问答等。


手机上微信里的二维码图片如何“扫描”呢?
小窍门:
在微信里长按图片,选择“识别图中二维码”,就可以了


微信群(程序员联盟),加群请私信我(微信群人数超过100之后,不能通过扫描二维码加入了,只能私信我,谢谢)


QQ群(程序员联盟),群号是 413981577

QQ群共享里有很多编程书籍PDF和其他资料。扫描下面二维码加QQ群:


0

我们还建立了一个公共的百度云盘,2TB容量,已有很多优秀编程资源,大家也可以上传。链接加群之后会发送。


《程序员联盟》的微社区,方便大家提问和互动。可以关注一下。

微社区地址和二维码如下:

http://m.wsq.qq.com/264152148

0


谢谢!



0


程序员联盟 微信公众号*您若觉得本文不错,点击按钮“分享

*新朋友请关注「程序员联盟」微信搜公众号  ProgrammerLeague

小编微信号frogoscar

小编QQ号:  379641629

小编邮箱:  enmingx@gmail.com

程序员联盟QQ群:413981577

程序员联盟微信群:先加我微信


有朋友反映看手机端的文章太累,其实是可以用浏览器网页来看的

方法1. 点击画面右上角的《···》按钮,然后选择“复制链接”,再把链接黏贴到你的浏览器里面或用邮件发送给自己,就可以在电脑的浏览器里打开了


0


方法2. 头条网www.toutiao.com,搜索我的自媒体“程序员联盟”,内有所有文章,也可以直接进这个链接:http://www.toutiao.com/m3750422747/


方法3. 我的51CTO博客和CSDN博客链接(所有文章都在上面)
http://4526621.blog.51cto.com/

http://blog.csdn.net/frogoscar



新朋友如何查看所有文章:

点击“查看公众号”,再点击“查看历史消息”


0


0


程序员联盟”公众号专为程序员,App设计师,各位喜爱编程和热爱分享的小伙伴们推送各样编程相关知识,优秀软件推荐,业界动态等。搜索ProgrammerLeague 加关注~


Dennis Ritchie编著的《C程序设计语言》第二版中文版PDF 百度云盘下载 (可以在手机上点开文件直接看)

2 0
原创粉丝点击