动态分配结构变量和结构数组
结构变量、结构数组都是可以动态分配存储空间的,如:StudentEx * pStu = new StudentEx;
pStu->ID = 1234;
delete pStu;
pStu = new StudentEx[20];
pStu[0].ID = 1235;
delete [] pStu;
文件读写
既可以从文件中读取数据, 也可以向文件中写入数据。读写文件之前,首先要打开文件。
读写文件结束后,要关闭文件。C/C++提供了一系列库函数,声明于stdio.h中,用于进行文
件操作。这里介绍其中几个常用的文件操作库函数:
用fopen 打开文件
该函数的原型为:
FILE *fopen(const char *filename, const char *mode);
“FILE” 是在 stdio.h中定义的一个结构,用于存放和文件有关的信息,具体内容我
们可以不需要知道。第一个参数是文件名,第二个参数是打开文件的模式。
打开文件的模式主要有以下几种:
“r” :以文本方式打开文件,只进行读操作。
“w”: 以文本方式打开文件,只进行写操作。
“a”: 以文本方式打开文件,只往其末尾添加内容。
“rb” :以二进制方式打开文件,只进行读操作。
“wb”: 以二进制方式打开文件,只进行写操作。
“ab”: 以二进制方式打开文件,只往其末尾添加内容。
“r+” :以文本方式打开文件,既读取其数据,也要往文件中写入数据。
“r+b”:以二进制方式打开文件,既读取其数据,也要往文件中写入数据。
“文本方式”适用于文本文件,即能在“记事本”中打开的,人能够看明白其含义的文
件。“二进制方式”适用于任何文件,包括文本文件,音频文件,视频文件,图像文件,可
执行文件等。只不过文本文件用“文本方式”打开,以后读写会方便一些。
fopen函数返回一个 FILE * 类型的指针,称为文件指针。该指针指向的 FILE 类型变
量中,存放着关于文件的一些信息,比如,文件的“当前位置”(稍后会详述)。文件打开
后,对文件的读写操作就不再使用文件名,而都是通过fopen函数返回的指针进行。
如果试图以只读的方式打开一个并不存在的文件、或因其他原因(比如没有权限)导致
文件打开失败, 则fopen返回NULL指针。如果以读写或只写的方式打开一个不存在的文件,
那么该文件就会被创建出来。
FILE * fp = fopen( “c:\\data\\report.txt”, “r”);
上面的语句以只读方式打开了文件 “c:\\data\\report.txt”。给定文件名的时候也
可以不给路径,那么fopen函数执行时就在当前目录下寻找该文件:
FILE * fp = fopen( “report.txt”, “r”);
如果当前目录下没有report.txt,则fopen函数返回NULL,此后当然不能进行读写操作
了。
用 fclose 关闭文件
打开文件,读写完毕后,一定要调用fclose函数关闭文件。fclose函数的原型是:
int fclose(FILE *stream);
stream即是先前用fopen打开文件时得到的文件指针。
一定要注意,打开文件后,要确保程序执行的每一条路径上都会关闭该文件。一个程序
能同时打开的文件数目是有限的,如果总是打开文件没有关闭,那么文件打开数目到达一定
限度后,就再也不能打开新文件了。一个文件,可以被以只写的方式同时打开很多次,这种
情况也会占用程序能同时打开的文件总数的资源。新手在调程序时常会碰到明明看见文件就
在那里,用fopen函数却总是打不开的情况,很可能就是因为总打开文件而不关闭,导致同
时打开的文件数目达到最大值,从而再也不能打开任何文件。
调用fclose函数时,如果参数stream的值是NULL, 那么很可能会出现程序异常终止的错
误。
用 fscanf 读文件
fscanf函数原型如下;
int fscanf(FILE *stream, const char *format[, address, ...]);
fscanf和scanf函数很象,区别在于多了第一个参数----文件指针stream。scanf函数从
键盘获取输入数据,而fscanf函数从与stream相关联的文件中读取数据。该函数适用于读取
以文本方式打开的文件。如果文件的内容都读完了,那么fscanf函数返回值为 EOF (stdio.h
中定义的一个常量)。
假设有以下文本文件 students.txt 存放在C盘tmp文件夹下:
Tom 08701342 male 1985 11 2 3.47
Jack 08701343 Male 1985 10 28 3.67
Mary 08701344 femal 1984 2 28 2.34
该文件里每行记录了一个学生的信息,依次是:姓名,学号,性别,出生年,月,日,
绩点。下面的程序打开此文件,读取其全部内容并输出。
#include <stdio.h>
int main()
{
FILE * fp;
fp = fopen( "c:\\tmp\\students.txt", "r");
if( fp == NULL ) {
printf( "Failed to open the file.");
return ;
}
char szName[30], szGender[30];
int nId, nBirthYear, nBirthMonth, nBirthDay;
float fGPA;
while( fscanf( fp, "%s%d%s%d%d%d%f", szName, & nId, szGender, & nBirthYear,
& nBirthMonth, & nBirthDay, & fGPA) != EOF) {
printf( "%s %d %s %d %d %d %f\r\n", szName, nId, szGender, nBirthYear,
nBirthMonth, nBirthDay, fGPA);
}
fclose(fp);
return 0;
}
fprintf函数能用于向文件中写入数据,用法和printf、fscanf类似,此处不再赘述。
其原型是:
int fprintf(FILE *stream, const char *format[, argument, ...]);
用 fgetc读文件,用fputc写文件
fgetc函数原型如下:
int fgetc(FILE *stream);
它用于从文件中读取一个字节,返回值即是所读取的字节数。每个字节都被当作一个无
符号的8位(二进制位)数,因此每个被读取字节的取值范围都是0-255。反复调用fgetc函
数可以读取整个文件。如果已经读到文件末尾,无法再读,那么fgetc函数返回EOF(实际上
就是-1)。
fputc函数原型如下:
int fputc(int c, FILE *stream);
它将一个字节写入文件。参数c即是要被写入的字节。虽然c是int类型的,但实际上只
有其低8位才被写入文件。如果写入失败,该函数返回EOF。
下面的程序实现了文件拷贝的功能。如果由该程序生成的可执行文件名叫 MyCopy.exe,
那么在控制台窗口(也称DOS窗口)输入 “MyCopy 文件名1 文件名2”再敲回车,则能进
行文件拷贝操作。比如,如果在DOS窗口输入:
MyCopy c:\tmp\file1.dat d:\tmp2.dat
则本程序的执行结果是将C盘tmp文件夹下的file1.dat文件,复制为到d盘根目录下的
tmp2.dat文件。
#include <stdio.h>
int main(int argc, char * argv[])
{
FILE * fpSrc, * fpDest;
fpSrc = fopen( argv[1], "rb");
if( fpSrc == NULL ) {
printf( "Source file open failure.");
return 0;
}
fpDest = fopen( argv[2], "wb");
if( fpDest == NULL) {
fclose( fpSrc);
printf( "Destination file open failure.");
return 0;
}
int c;
while( ( c = fgetc(fpSrc)) != EOF)
fputc( c, fpDest);
fclose(fpSrc);
fclose(fpDest);
return 0;
}
语句2 中的main 函数比以往多了两个参数argc 和argv,另外在语句5 和语句10 中也
用到了argv 参数,argc 和argv 的作用请看后文的2.18 节“命令行参数”。
语句5 实际上就是以只读方式打开源文件,语句10 是以写方式打开目标文件。
语句17 从源文件读取一个字符。表达式 c = fgetc(fpSrc) 的值实际上就是c 的值,也就
是fgetc 函数的返回值。fgetc 的返回值是EOF,则说明文件已经读完了。
用fgets函数读文件, fputs函数写文件
fgets函数原型如下;
char *fgets(char *s, int n, FILE *stream);
它一次从文件中读取一行,包括换行符,放入字符串s中,并且加上字符串结尾标志
符’\0’。参数n代表缓冲区s中最多能容纳多少个字符(不算结尾标志符’\0’)。
fgets函数的返回值是一个char *类型的指针,和s指向同一个地方。如果再没有数据可
以读取,那么函数的返回值就是NULL。
fputs函数原型如下;
int fputs(const char *s, FILE *stream);
它往文件中写入字符串s。注意,写完s后它并不会再自动向文件中写换行符。
下面的程序将students.txt 内容拷贝到student2.txt
#include <stdio.h>
#define NUM 200
int main()
{
FILE * fpSrc, * fpDest;
fpSrc = fopen( "students.txt", "r");
if( fpSrc == NULL ) {
printf( "Source file open failure.");
return 0 ;
}
fpDest = fopen( "students2.txt", "w");
if( fpDest == NULL) {
fclose( fpSrc);
printf( "Destination file open failure.");
return 0;
}
char szLine[NUM];
while( fgets(szLine, NUM-1, fpSrc)) {
fputs(szLine, fpDest);
}
fclose(fpSrc);
fclose(fpDest);
return 0;
}
调用fgets 时用的参数199改小点,比如150,也是没有问题的,只要能装得下最长的那
一行就行了。
用 fread读文件,用fwrite写文件
#include <stdio.h>
#include <string.h>
struct Student{
char szName[20];
unsigned nId;
short nGender; //性别
short nBirthYear, nBirthMonth, nBirthDay;
float fGPA;
};
int main()
{
FILE * fpSrc, * fpDest;
struct Student Stu;
fpSrc = fopen( "c:\\tmp\\students.txt", "rb");
if( fpSrc == NULL ) {
printf( "Failed to open the file.");
return 0;
}
fpDest = fopen( "students .dat", "wb");
if( fpDest == NULL) {
fclose( fpSrc);
printf( "Destination file open failure.");
return 0;
}
char szName[30], szGender[30];
int nId, nBirthYear, nBirthMonth, nBirthDay;
float fGPA;
while( fscanf( fpSrc, "%s%d%s%d%d%d%f", szName, & nId,
szGender, & nBirthYear, & nBirthMonth, & nBirthDay, & fGPA) != EOF) {
strcpy(Stu.szName, szName);
Stu.nId = nId;
if( szGender[0] == 'f' )
Stu.nGender = 0;
else
Stu.nGender = 1;
Stu.nBirthYear = nBirthYear;
Stu.nBirthMonth = nBirthMonth;
Stu.nBirthDay = nBirthDay;
fwrite( & Stu, sizeof(Stu), 1, fpDest);
}
fclose(fpSrc);
fclose(fpDest);
fpSrc = fopen( "students.dat", "rb");
if( fpSrc == NULL ) {
printf( "Source file open failure.");
return 0;
}
fpDest = fopen( "students2.dat", "wb");
if( fpDest == NULL) {
fclose( fpSrc);
printf( "Destination file open failure.");
return 0;
}
while(fread( & Stu, sizeof(Stu), 1 , fpSrc)) {
if( Stu.nBirthYear >= 1985 )
fwrite( & Stu, sizeof(Stu), 1, fpDest);
}
fclose( fpSrc);
fclose( fpDest);
return 0;
}
我们看到,存放学生信息,可以用 students.txt 文件的格式,也可以用 students.dat
文件的格式。到底哪种比较好呢?应该说使用记录文件更好。记录文件可以按名字或学号等
关键值排序,排序以后可以用折半查找算法快速查找,这样在一个有N个记录的文件中进行
查找,最多只需读取log2N个记录,比较log2N次。而用文本文件的格式存放信息,由于每行
长度都不一样,所以要查找名为“jack”的学生信息,只能从头顺序往下找直到找到为止。
那么平均要读取整个文件的一半,才能找到。
另外,用记录方式保存信息,比用文本方式通常能节省空间。
文本方式中有很多空格、换行符是冗余的,而且像“08701342”这样的学号等数值信息,
用记录方式存放,只需4个字节的unsigned类型就可以,而以文本方式保存,往往4个字节是
无法表示的,因为一个数字就要占用一个字节。
请注意,打开的文件,一定要关闭。因此在语句22在程序返回前,关闭了曾经打开的源
文件。
fread函数原型如下:
unsigned fread(void *ptr, unsigned size, unsigned n, FILE *stream);
该函数从文件中读取n个大小为size字节的数据块, 总计n*size字节,存放到从地址
ptr 开始的内存中。返回值是读取的字节数。如果一个字节也没有读取,返回值就是0。
fwrite函数原型如下:
unsigned fwrite(const void *ptr, unsigned size, unsigned n, FILE *stream);
该函数将内存中从地址 ptr 开始的n*size个字节的内容,写入到文件中去。
这两个函数的返回值,表示成功读取或写入的“项目”数。每个“项目”的大小是size
字节。
其实使用这两个函数时,总是将size置为1,n置为实际要读写的字节数,也是没有问题
的。
fread函数成功读取的字节数,有可能小于期望读取的字节数。比如反复调用fread读取
整个文件,每次读取100个字节,而文件有1250字节,那么显然最后一次读取,只能读取50
字节。
使用fread和fwrite函数读写文件,文件必须用二进制方式打开。
有些文件由一个个“记录”组成,一个记录就对应于C/C++中的一个结构,这样的文件,
71
就适合用fread和fwrite来读写。比如一个记录学生信息的文件students.dat,该文件里每
个“纪录”对应于以下结构:
struct Student{
char szName[20];
unsigned nId;
short nGender; //性别
short nBirthYear, nBirthMonth, nBirthDay;
float fGPA;
};
下面的程序先读取前例提到的students.txt中的学生信息,然后将这些信息写入
students.dat中。接下来再打开students.dat,将出生年份在1985年之后的学生记录提取出
来,写到另一个文件 students2.dat中去。
用 fseek 改变文件当前位置
文件是可以随机读写的,即读写文件并不一定要从头开始,而是直接可以从文件的任意
位置开始读写。比如,可以直接读取文件的第200个字节,而不需将前面的199个字节都读一
遍。同样,也可以直接往文件第1000个字节处写若干字节,覆盖此处原有内容。甚至可以先
在文件的第200字节处读取100字节,然后跳到文件的第1000字节处读取20字节,然后再跳到
文件的第20字节处写入30字节。这就叫“随机读写”。然而,前面提到的那些文件读写函数,
都没有参数能够指明读写是从哪个位置开始,这又是怎么回事呢?
答案是:所有的文件读写函数,都是从文件的“当前位置”开始读写的。文件的“当前
位置”信息保存在文件指针指向的 FILE结构变量中。一个文件在以非 “添加”方式打开,
尚未进行其他操作时,其“当前位置”就是文件的开头;以添加方式打开时,其“当前位置”
在文件的末尾。此后调用读写函数读取或写入了n个字节,“当前位置”就往后移动n个字节。
如果“当前位置”到达了文件的末尾,那么文件读取函数再进行读操作就会失败。
注意,文件开头的“当前位置”值是0,而不是1。
综上所述,要实现随机读写,前提是能够随意改变文件的“当前位置”。fseek函数就
起到这个作用。其原型如下:
int fseek(FILE *stream, long offset, int whence);
该函数将与stream关联的文件的“当前位置”设为距whence处offset字节的地方。whence
可以有以下三种取值,这三种取值都是在stdio.h里定义的标识符:
SEEK_SET: 代表文件开头
SEEK_CUR: 代表执行本函数前文件的当前位置
SEEK_END: 代表文件结尾处
例如,假设 fp是文件指针,那么:
fseek(fp, 200, SEEK_SET);
就将文件的当前位置设为200,即距文件开头200个字节处;
74
fseek(fp, 0, SEEK_SET);
将文件的当前位置设为文件的开头。
fseek(fp, -100, SEEK_END);
将文件的当前位置设为距文件尾部100字节处。
fseek(fp, 100, SEEK_CUR);
将文件的当前位置往后(即往文件尾方向)移动100个字节。
fseek(fp, -100, SEEK_CUR);
将文件的当前位置往前(即往文件开头方向)移动100个字节。
下面的程序,读取文件students.dat中的第4个记录到第10个记录(记录从0开始算),
并将这部分内容写入到第20个记录开始的地方,覆盖原有的内容。
例程 2.16.7.cpp:
#include <stdio.h>
#include <string.h>
#define NUM 10
#define NAME_LEN 20
struct Student{
char szName[NAME_LEN];
unsigned nId;
short nGender; //性别
short nBirthYear, nBirthMonth, nBirthDay;
float fGPA;
};
int main()
{
FILE * fpSrc;
Student aStu[NUM];
fpSrc = fopen( "c:\\tmp\\students4.dat", "r+b");
if( fpSrc == NULL ) {
printf( "Failed to open the file.");
return 0;
}
fseek( fpSrc, sizeof(Student)* 4 , SEEK_SET);
fread( aStu, sizeof(Student), 7, fpSrc);
fseek( fpSrc, sizeof(Student) * 20, SEEK_SET);
fwrite( aStu, sizeof(Student), 7, fpSrc);
fclose( fpSrc);
return 0;
}
这些是对文件常用的操作,以前研究语法时并没有注意这方面的用法,整理。
文件操作与结构化数据管理
本文详细介绍了在C/C++环境中如何动态分配结构变量和结构数组,以及如何使用文件读写函数进行数据的存储与读取。通过实例展示了如何在文件中存储和检索结构化数据,包括使用fopen、fclose、fscanf、fprintf、fgetc、fputc、fgets、fputs、fread、fwrite等函数的操作方法,以及如何利用fseek函数改变文件的当前位置以实现随机读写。文章还对比了文本方式与二进制方式的优缺点,并强调了文件操作过程中的注意事项,如确保文件正确关闭,以避免资源泄漏。
878

被折叠的 条评论
为什么被折叠?



