陈圣磊, 王红霞, 刘林源
(南京审计大学 电子商务系,江苏 南京 211815)
C语言程序设计中文件操作部分的教学方法探讨
陈圣磊, 王红霞, 刘林源
(南京审计大学 电子商务系,江苏 南京 211815)
学习C语言,不仅要求学生能够掌握基本语法知识,学会写程序,还应该深入理解库函数提供的功能是如何实现的,以达到与学过的语法知识融会贯通的目的。文章通过阐述在讲解文件操作时如何呼应已学过的知识点,包括结构体、函数和指针,从而加深对文件操作以及相应语法知识点的理解。
程序设计;库函数;C语言;文件操作
C语言程序设计是计算机相关专业的一门学科基础课,承担着培养学生编程思想的重要任务。这门课程涵盖了C语言的所有语法,包括数据类型与变量、流程控制结构、数组、函数、指针和结构体等。学习这些基本语法的目的有两方面:一是学会写程序;二是帮助学生读懂库函数,深入理解库函数提供的功能是如何实现的。第一个目的很容易理解,之所以提出第二个目的,是因为库函数的定义与调用涉及很多基本语法知识,在学习库函数时如果能与这些语法点呼应起来,就能达到融会贯通的目的。
然而,目前的计算机教育对前一方面关注较多[13],而忽视了第二个方面。造成的结果是学生理解了流程控制、数组、函数和指针的用法,也能够编写简单的程序,但是对程序的认识仅限于教材上的例题,对库函数提供的功能,如字符串处理、输入输出处理,理解不够深入,未能把学过的语法知识学以致用,去理解这些函数。
文件操作是在讲解完基本语法后遇到的一个相对独立的知识模块,可以让学生学会使用相关函数来处理文件读写的问题。那么在文件操作部分的教学中,不但要让学生掌握如何使用这些函数,还应该把之前学习的语法贯穿到这些知识点中。
我们一般使用文件类型FILE的方法是声明FILE类型的指针变量,
FILE* fp;
当同学们看到这句声明时,自然会想到FILE应该是一个类型,那么这个类型究竟是怎么构成的呢?
其实教材对文件类型FILE讲得比较清楚[4]333:每个被使用的文件都在内存中开辟一个相应的文件信息区,用来存放文件的有关信息(如文件的名字、文件状态及文件当前位置等)。这些信息被保存在一个结构体变量中,该结构体的类型是由系统声明的,取名为FILE。它的类型声明为:
从文件类型FILE的定义中,我们可以看到:
(1)文件类型FILE是一个结构体类型,由表示缓冲区大小、位置、读写控制等的字段组成,这可以呼应到定义结构体类型的语法。
(2)FILE是用typedef定义的新类型,因此在使用FILE定义指针时不再需要struct关键字,这对应到typedef关键字的用法。
(3)上述定义是教材中给出的,我们还可以在VC++6.0中打开stdio.h文件,找到对FILE的定义:
typedef struct _iobuf FILE;
我们可以看到,在VC++6.0中,先定义了结构体类型_iobuf,再由typedef关键字定义类型FILE。其中,字段_base表示缓冲区的开始位置,_ptr表示指针的指向,_bufsiz表示缓冲区的大小。
因此,通过上述解释,学生更容易理解FILE其实就是结构体类型,这样不仅加深了对FILE类型的理解,也通过这样一个例子加深了对结构体语法的理解。
我们一般使用以下程序打开文件:
其功能是按照只读的方式打开文件,指针变量fp指向被打开的文件。除此之外,我们还可以对fopen函数作以下深层次的挖掘:
(1)使用这个函数需要包含头文件stdio.h。那么在讲函数原型部分时说过,如果使用库函数,应该使用#include指令包含相关的头文件,如果使用用户自定义的函数,应该添加对被调用函数的声明。那么我们可以进一步提出问题:这两种方式之间有关联吗?头文件中究竟是什么内容呢?
这里,我们可以在VC++6.0中打开头文件stdio.h,查找fopen,结果发现:
_CRTIMP FILE * __cdecl fopen(const char *, const char *);
其中,_CRTIMP是一个宏,表示使用动态C运行时库还是静态连接的C运行库,__cdecl表示C语言默认的函数调用方法,可以忽略。其余内容就是函数的声明了,返回值为FILE指针,两个参数都是字符指针类型。
通过查看头文件,我们知道头文件中其实就是函数的声明。所以,所有的函数调用都需要函数声明,只不过库函数的声明集中在头文件中,因此只要用#include包含头文件就可以了。
(2)fopen函数的返回值为FILE指针类型。在函数部分讲过返回指针值的函数,但是比较简略。返回指针值的函数涉及以下3个问题:
① 如果函数返回指针值,那么不能返回这个函数中局部变量的地址,因为局部变量在函数调用结束时就释放了,再引用这个地址就会带来问题。比如:
指针p指向funA函数中局部变量a,a的值为3,但是输出*p的结果为4。这是由于在调用函数funA后,又调用函数funB,变量a的位置被重新分配变量b,b的值为4,所以此时输出*p的结果为4。如果在主函数没有调用funB,即删除funB();这一行,则输出*p的结果为3。由此可以看出,这种返回局部变量地址的做法存在很大的不确定性。其实,上述程序在编译时会产生警告:returning address of local variable or temporary(返回局部变量的地址)。因此,我们在编写程序时应当避免这种情况。
② 被调用函数可以返回主调函数中变量的地址。教材[4]274例题8.25展示了这种情况,代码如下:
从上述代码可以看出,search函数的参数pointer指向主函数中的二维数组score,返回值pt是基于pointer得到的一个地址,仍然指向二维数组score中的某个元素。因此这种情况通过参数传递数组名,返回的是主函数中数组里某个元素的地址,因此在子函数调用结束后不存在地址释放的问题。要注意这种情况和①中程序是有区别的。
③ 函数fopen并没有指针类型的参数,那么为什么它返回指针类型的返回值就没有问题呢?其实我们可以想象,fopen函数中应该声明一个FILE类型的变量,最后返回其地址。如果是普通局部变量,fopen函数返回时肯定会释放的。那么什么样的变量在函数结束时仍然不会释放呢?我们在前面讲过,栈空间变量会自动释放,而堆空间变量是由程序员申请和释放的,因此,这个变量就应该是堆空间中动态分配的变量,由此呼应到动态内存分配的相关函数。
可以想象,fopen函数的定义中应该包含以下代码:
即该函数调用了malloc函数,动态分配了sizeof(FILE)大小的空间,赋值给fp指针,最后返回fp的值。
更进一步,所有动态分配的空间都需要通过free函数释放,那么在哪里调用free函数呢?我们强调在使用fopen函数打开文件进行读写后,还应该调用fclose函数关闭文件。因此可以想见,在fclose函数的定义中应该包含以下代码:
因此,通过上述分析,学生不但掌握了打开关闭文件的方法,更深入理解了函数声明、返回指针值的函数与动态内存分配的相关问题。
C语言允许通过函数fgets和fputs一次读写一个字符串,其中fgets函数的原型为[4]341:
char* fgets(char *str, int n, FILE *fp);
作用是从fp所指向的文件读入一个长度为n-1的字符串,并在最后加一个‘ ’字符,然后把这n个字符存放到字符数组str中。
函数fgets从文件中读取了字符串,用字符指针类型的参数str指向的地址空间来存放读入的字符串。这一点需要特别注意,实际上是从被调函数中传递数据给主调函数。完成这一功能有以下3种方法:
(1)返回值方法。在初次接触函数时,我们知道被调函数可以通过return语句将数据传递给主调函数,这是被调函数向主调函数传递数据最为常见的方式,容易理解,使用方便,其不足之处在于:一个函数仅能返回一个值,如果被调函数需要向主调函数传递多个数据值,这种方法就行不通了。
(2)全局变量法。在讲解全局变量时,我们知道可以在被调函数中给全局变量赋值,这个全局变量在主调函数中也可以访问,同样可以起到向主调函数传递数据的作用,从而突破了函数只能有一个返回值的限制。当然,全局变量也有不足,即全局变量可以在多个函数中访问,违背了函数封装中低耦合的原则,使得程序容易出错。
(3)指针法。在讲到指针后,我们知道指针可以作为函数参数,在被调函数中给参数指针指向的变量赋值,主调函数中就可以访问这个值。如果要想传递多个值给主调函数,只需要设置多个指针类型的参数,因此,使用指针类型的函数参数是函数返回多个值的理想方法。函数fgets就是使用指针法传递数据的一个典型例子。fgets函数中有个两个指针类型的参数,str表示存放从文件中读取的字符串的位置,fp是fgets要读取的文件的指针,很明显,str是实现被调函数向主调函数传递数据的指针参数,而fp是主调函数向被调函数传递的文件指针。
通过与之前学过的数据传递方法的比较,我们可以认清该函数的本质,理解各种方法的优劣,在编写程序时选择合适的方法。
对C语言程序设计中文件操作部分教学方法的探索,能够帮助学生在掌握文件操作方法的同时,加深对相关知识点的理解,达到将所学知识融会贯通的效果。当然,这些教学内容更偏重于操作方法和原理的介绍,我们下一步研究的重点是如何设计文件输入、输出的系列实用案例,如文本文件的格式转换,让学生通过实践进一步加深理解。
[1] 杨加义, 庄丽娟. 学习C语言中的指针类型[J]. 计算机教育, 2010(7): 94-97.
[2] 周百顺. C语言程序设计中的函数分解与函数定义[J]. 计算机教育, 2015(4): 59-62.
[3] 马宪敏, 于延. C语言中循环结构的教学设计探讨[J]. 计算机教育, 2011(5): 87-90.
[4] 谭浩强. C程序设计[M]. 4版. 清华大学出版社, 2010.
(见习编辑:张 勋)
1672-5913(2017)02-0167-04
G642
江苏省高校重点专业建设项目(苏教高[2012]23号);南京审计大学跨境电商交叉学科建设项目支持(南审研发[2016]36号)。
陈圣磊,男,副教授,研究方向为数据挖掘与机器学习,tristan_chen@126.com。