本文主要介绍了C语言中有关文件的基础知识与基本操作,对涉及到的操作函数进行了详细解释,同时以笔者自己的理解,对函数的命名进行了一些探究,并以通讯录为例,做了打开存储等演示。
一、文件操作
1、文件指针FILE
亦称“文件类型指针”,由此可知,和int,char等类型一样,FILE也是一种数据类型,其中保存着有关文件的相关信息,文件名、状态、存储位置等信息,在vs2022中查看FILE定义可得如下信息:在此我们得知,FILE类型也是一种结构体类型,既然vs2022中没找到具体定义,我们度娘可知: 在vs2013,FILE结构定义如下:
#ifndef _FILE_DEFINED
struct _iobuf {
char *_ptr; //文件输入的下一个位置。
int _cnt; //当前缓冲区的相对位置。
char *_base; //指针的基础位置(即是文件的起始位置)。
int _flag; //文件标志。
int _file; //文件的有效性验证。
int _charbuf; //检查缓冲区状况,如果无缓冲区则不读取。
int _bufsiz; //缓冲区的大小。
char *_tmpfname; //临时文件名。
};
typedef struct _iobuf FILE;
#define _FILE_DEFINED
每打开一个文件,系统便会根据文件的情况创建一个对应的FILE类型的数据,定义一个指针指向这个数据,便可以通过这个数据访问文件中的信息,这就是文件指针。
2、文件的打开和关闭
文件读写前需要打开,访问信息,使用结束后应该关闭,存储信息; 在打开文件的同时,函数会返回一个FILE*的指针,从而建立指针与文件的关系;
a、打开函数 fopen
function of open,打开功能。 fopen会在成功打开文件时,返回指向该文件信息区的指针,打开失败则会返回NULL;
- 功能 打开文件;
- 使用 fopen在打开失败时会返回NULL,因此在使用中应该对fopen的返回值进行判断,如下:
int main()
{
FILE* pf;//定义文件类型指针
pf = fopen("test.txt", "r");//以"r"模式打开文件pf
//判断是否打开成功
if(pf == NULL)
{
perror("fopen::");
return 1;
}
return 0;
}
perror会打印对应的错误信息,而其中的fopen可以帮助我们定位到错误出现的位置,如下:由于不存在test.txt文件,所以函数报错。
打开模式
由于打开文件的目的不同,有时需要读,有时需要写,因而打开文件也有不同的模式;
-
只读模式 read读,打开文件读,若文件不存在或找不到,fopen调用失败;
-
只写模式 write写,打开一个空文件写入,如果文件已存在,其中的内容会被销毁;
-
追加模式 append追加,在文件末尾打开写入,在将新数据写入文件之前不删除 EOF 标记(End Of File,文件结束标志);若文件不存在,会创建一个新文件。
-
三种不同的读写模式 打开文件读写(文件必须存在); 打开一个空文件读写,若文件存在,则其中的内容会被清除; 打开文件读和追加,追加操作会在将新数据写入文件之前删除 EOF 标记,并在写入完成后恢复 EOF 标记;如果文件不存在,则会创建一个文件;
-
打开二进制文件 只需在对应的字符后加b即可: "rb" "wb" "ab" "rb+" "wb+" "ab+",便是对应的操作。
b、关闭函数 fclose
**function of close,关闭功能。**fclose会关闭文件,成功则返回0,失败则返回EOF;同时会刷新缓冲区,保存修改。
3、文件的读写
a、顺序读写
- 字符输入函数 fgetc function of get a character,获取一个字符的功能。 fget格式化会从流中读取一个字符,并返回其对应的ASCII码值,将其中参数换为stdin时,fgetc会从缓冲区中获取一个字符。 例如: 输入abcd时,缓冲中有四个字符a、b、c、d,而调用了三次fopen,每次拿取一个,分别给到a、b、c;
- 字符输出函数 fputc function of put a character,放置一个字符的功能。fputc会将一个字符放到流中,成功则返回此字符的ASCII码值,失败则返回EOF。 如输入成功时: 以写模式创建了test.txt文件,并以fputc往其中放入了c字符,fputc返回了c的ASCII码值,在屏幕上打印了出来; 输入失败时:以"r"只读模式打开,不可写入,因此fgetc的返回值为EOF,无任何打印。 值得注意的是,若将此时代码中的pf换为stdout,则会在屏幕上打印出c字符,并成功将其赋值给a,并再次打印,从而出现两个c字符,如下:
- 文本行输入函数 fgets function of get a string,获取一个字符串的功能。fgets会从流中获取n-1个字符,在结尾放置 '\0' 存储在string中,并返回string的起始地址,若调取失败,则会返回NULL; 如下:失败时此时test.txt中无数据,所以调取失败,a没有被修改,fgets返回值为NULL,并赋值给b; 成功时:此时test.txt中数据为abcdef,调取了4-1个字符,给a,同时添加了‘\0’置于末尾,并返回地址,赋值给b; 此时a中数据为: abc\0xxxxx\0,如下,
- 文本行输出函数 fputs function of put a string, 放置一个字符串的功能。 fputs会将一个字符string写到流中,如果写入成功,函数会返回一个非负值,如果失败则会返回EOF; 如下:成功时,先以fopen创建了test.txt文件,后将字符串a以fputs放置到了pf指针指向的test.txt的文件中,同时将fputs的返回值赋值给了b。由输出可知,b为非负值,判断此次赋值成功,打开test.txt,验证如实: 失败时, 以只读模式打开文件test.txt,此时test.txt中数据已被手动清除,fputs返回值赋值给b,b值为-1,由此得,此次赋值未成功打开test,其中仍无数据; 值得注意的是,字符串string必须以'\0'结尾,否则fputs在复制完字符串后还会复制随机个字符,直到遇到'\0'。如下:
- 格式化输入函数 fscanf fscanf的操作方式与scanf很相似,只是多了一个文件指针用来确定从何处获取数据。formatted scan function,格式化的扫描功能。fscanf会从流中获取数据,并返回成功分配的字段数,返回0表示未分配任何字段,若发生错误,或在未获取数据前便到达了文件流的末尾,则返回值为EOF。如下: 现在test.txt中手动输入数据,再以只读模式打开test.txt,以fscanf获取其中的数据,并将返回值赋值给b;可得,返回值为3,因为成功获取了3组数据; 若获取失败,返回EOF,值为-1,如下: 此时,test.txt中无数据,结构体a中数据仍未初始给的值,并未成功赋值。返回值为-1,因为未开始赋值便遇到了文件末尾;同样在以只读模式打开时,一样会返回-1.
- 格式化输出函数 fprintf 同样,fprintf的功能也和printf相似,只是多了文件指针;formatted print function,格式化的打印函数。fprintf会在流中打印数据,并返回打印成功的字节数,若发生错误则返回负值。 如下,成功时:字符串"zhangsan"9个字节,因为结尾有'\0',int类型的20,4个字节,double类型的80.0,8个字节,加在一起21个字节,正好是b的值。由此得以验证,打开test.txt:失败时:返回值为-1,表示此次打印失败,打开test.txt验证:
b、随机读写
- fseek 此函数笔者愚钝,未能想到可以自洽的方式以解释其命名。将文件指针移到指定的位置; 在解释前需要首先介绍几个文件类型中定义的宏:SEEK_CUR,文件指针当前位置,SEEK_END,文件末尾,SEEK_SET,文件起始位置。 我们使用如下代码,test.txt中的数据为 abcdefg ,我们逐句代码分析,以代码注释说明:
int main()
{
FILE* pf;//定义文件类型指针
pf = fopen("test.txt", "r");//以"r"模式打开文件pf
//判断是否打开成功
if (pf == NULL)
{
perror("fopen::");
return 1;
}
//打开文件,其文件指针当前位置应指向a
fseek(pf, 1, SEEK_CUR);//向后偏移1位,指向b
int x = fgetc(pf);//获取b,将其ASCII码值传给x,同时指针再次后移指向c
printf("%c\n", x);//打印x,应为b
fgetc(pf);//获取c,同时指针再次后移指向d
x = fgetc(pf);//获取d,将其ASCII码值传给x,同时指针再次后移指向e
printf("%c\n", x);//打印x,应为d
fseek(pf, 2, SEEK_CUR);//再次往后偏移2位,应指向g
x = fgetc(pf);//获取g,将其ASCII码值传给x,同时指针再次后移指向结束标志
printf("%c\n", x);//打印x,应为g
return 0;
}
分析正确。
-
ftell 获取文件指针的位置。ftell会返回文件指针相对起始位置的偏移量,如下:
-
rewind rewind会将文件指针返回起始位置,无返回值。使用如下: 经过此次rewind,文件指针返回起始位置,此时再往后偏移2位,便指向了c。
二、通讯录的修改
1、初始化的修改
创建contact.txt,将通讯录的信息保存在其中,同时初始化需要读取其中数据;
// 扩容
void Enlarge(Contact* con)
{
con->capacity += 2;
Peo* tmp = NULL;
tmp = (Peo*)realloc(con->data, (con->capacity) * sizeof(Peo));
if (tmp != NULL)
{
con->data = tmp;
printf("扩容成功\n");
}
else
{
printf("扩容失败\n");
con->capacity -= 2;
}
}
//加载文件
void Load_Contact(Contact* con, FILE* pf)
{
Peo tmp = { 0 };
while (EOF != fscanf(pf, "%s %s %d %s %s ", tmp.name, tmp.sex, &(tmp.age), tmp.tel, tmp.addr))
{
if (con->sz == con->capacity)
{
//扩容
Enlarge(con);
}
con->data[con->sz] = tmp;
con->sz++;
}
}
//初始化
void Init_Contact(Contact* con)
{
assert(con);
FILE* pf = NULL;
pf = fopen("contact.txt", "r");
if (pf == NULL)
{
perror("Init_Contact::fopen");
return;
}
con->sz = 0;
con->capacity = 3;
con->data = (Peo*)malloc(sizeof(Peo) * con->capacity);
Load_Contact(con, pf);
fclose(pf);
pf = NULL;
}
往fscanf读到文件末尾时,会返回EOF,由此判断是否完成文件的读取;
2、退出的修改
增加保存通讯录函数:
void Save_Contact(Contact* con)
{
FILE* pf;
pf = fopen("contact.txt", "w");
if (pf == NULL)
{
perror("Save_Contact::fopen::");
return;
}
int i = 0;
for (i = 0; i < con->sz; i++)
{
fprintf(pf, "%s %s %d %s %s ", (con->data + i )->name, (con->data + i)->sex, (con->data + i)->age, (con->data + i)->tel, (con->data + i) ->addr);
}
fclose(pf);
pf = NULL;
}
增加文件的修改和保存,便对通讯录的加载提供了有效的效率提升; 文件代码已上传到Gitee——>通讯录代码,大家如果有需要可以前往查看,如果文章对您有帮助,希望可以得到您的一个赞,*^____^*。
标签:文件,指针,详解,pf,通讯录,test,txt,con From: https://blog.51cto.com/u_15423682/6142178