普通文件(Ordinary Files)
普通文件,代指储存在硬盘中或外部媒体文件中的有序数据集。源文件(source file),工程文件(object file),可执行文件(executable file),乃至一组被处理的原始输入数据和输出结果均为普通文件。其中,源文件,工程文件等称之为程序文件,而对于输入输出的数据,则被称之为数据文件。
设备文件(Device Files)
顾名思义,设备文件是指与计算机主机相关联的各种外部设备。例如显示器,键盘等等。在操作系统中,其外部设备通常也会被认为是一个文件,而这些外部设备的输入输出也等同于对硬盘文件操作的读和写。
通常我们将显示器定义为标准输出文件,在显示屏上显示的信息便是标准输出文件的输出结果。
键盘的输入则是标准输入文件,从键盘输入数据等同于标准输入文件的输入。
对于标准输入输出文件,是由系统打开的,用户可直接进行使用。
C语言文件存储格式
C语言中,最主要的文件存储格式有两种:文本文件(text file)与二进制文件(binary file)。
文本文件同时也是ASCII文件,当该文件在磁盘中存放时,每一个字符都对应一个字节,用于存放其对应的ASCII码值。ASCII文件能够在屏幕上以字符样式显示。由于其内容可通过字符形式呈现,因此ASCII文件是能够进行阅读的。
二进制文件是按二进制的编码方式来存放文件的。二进制文件也能够通过屏幕进行显示,但由于是按机器码方式进行呈现,因此也是不可阅读的。
在处理这些文件时,C系统并不区分其类型,而是将其看成字符流,按字节进行处理。输入输出的字符流控制并不由物理符号控制(例如换行符等),而是由其程序进行控制。因此,这种类型的文件也称之为“流式文件”。
文件指针与文件打开关闭
FILE指针
C语言中,无法直接对文件进行操作,其各种修改操作必须借由其指针进行。
//定义一个文件指针
FILE* your_pointer_name;
//例如
FILE* fp;
以上语句定义了一个指向文件的指针变量。
文件指针可对其指向的文件进行文件操作。FILE是一个结构体类型,因此其定义的指针也是指向结构体的,其中包含了文件名,文件状态,当前方位指针等等数据。
只要用户不重新分配,文件指针的值是不会改变的,一直指向该文件。
这里不去深究其中的细节,只需要了解FILE是一个结构体类型,以及如何定义一个指向文件的指针既可。
fopen()打开文件
fopen函数包含于stdio.h文件中,用于打开文件,以及选择用户想要的操作方式,为接下来一系列的文件操作打下基础。其函数原型如下:
fopen(const char* file_name,const char* open_mode)
const char* file_name :指向一个常量字符串,即要打开文件的名称(可包含路径)。
const char* open_mode:指向一个常量字符串,既要通过什么方式打开文件(只读/只写等)。
关于返回值:
fopen返回一个指向FILE结构体的指针,该指针用于后续的文件操作。若打开失败,则会返回NULL。因此在打开文件同时应当对其返回值进行检测,以确保文件被正确打开,避免后续操作中出现错误。
//fopen()用于打开文件. fopen命令格式
fp = fopen("file_name","open_mode");
//例如:以只写方式打开当前目录下名为DataBase.txt的文件
fp = fopen("DataBase.txt", "wt");
通过路径打开文件:
//也有如下格式。以二进制文件只写方式打开D盘下的test文件
FILE* fphzk;
//fphzk = fopen("Your_File_Path","mode");
fphzk = fopen("D:\\test", "wb");
以下为打开文件的完整步骤:
FILE* fp = fopen("Database.bin", "wb"); //以二进制只写方式打开该目录下Database.bin文件
if (fp == NULL) { //检测返回值
perror("Oops!Something wrong happened");
return -1;
}
文件打开模式
文件的打开模式有如下12种方式:
1.rt 2.wt 3.at 4.rb 5.wb 6.ab 7.rt+ 8.wt+ 9.at+
10.rb+ 11.wb+ 12.ab+
其中:
r : read 可读
w : write 可写
a : append 追加
text : (ASCII文本文件)
b : binary(二进制)
+ : read and write 可读可写
接下来以二进制文件(文本文件同理)为例来解释一下各个模式:
rb (read binary) : 以只读的方式打开一个二进制文件,该文件必须存在!用户可以读取该文件中的数据,但是不能向其中写入数据。
wb (write binary) : 以只写的方式打开一个二进制文件,若该文件不存在,则在指定目录新建一个同名文件。若该文件存在,则格式化原文件。用户不能从其中读取出数据,但是可以向其中写入。
ab (append binary) : 以追加的方式打开一个二进制文件,若该文件不存在则被创建。新写入的数据会被追加在文件末尾。(注意:追加方式文件可以从头读取文件,但是只能在文件尾端写入数据,而不能在除尾端之外的任何地方覆盖原文件数据或写入数据)
rb+(read and write binary) :以读写方式打开二进制文件,文件必须存在。
wb+(write and read binary) : 以读写方式打开二进制文件,若文件不存在则被创建,若文件存在则格式化原文件。
ab+(append and read binary) : 以读和追加的方式打开二进制文件,若文件不存在则被创建,该文件将会从头被读取,但是写入文件只能在文件末尾。
ASCII文件的打开模式与上相同。
fclose()关闭文件
当文件操作结束之后,可以用fclose()函数关闭文件,防止出现数据丢失等错误情况。
//函数原型
fclose(file_pointer);
//关闭fp指向的文件
fclose(fp);
关于返回值:
当文件正确被关闭时,将会返回0值。如果函数返回了一个非0值,则代表关闭出现了错误,文件未正常关闭。
if(fclose(fp) != 0){
perror("Error closing the file");
return;
}
文件的读与写
字符的读与写
C语言文件操作中可以对单个字符进行读写操作。单字符的读写操作基于一个字节进行读取,其中fgetc()函数用于从文件中获取一个字符,fputc()用于向文件中写入一个字符。
上述函数的每一次调用都将读取或写入一个字符到文件。
fgetc()读取字符
fgetc函数用于从文件中读取一个字符,其函数原型如下:
character variable = fgetc(file_pointer);
word.txt
adcdefg
--------------------------------------------
//打开文件word.txt,读取一个字符并打印输出
FILE *fp = fopen("word.txt","rt");
char ch = fgetc(fp);
printf("%c",ch);
--------------------------------------------
输出结果: a
可以利用循环,从文件中读取多个字符。
word.txt
adcdefg
--------------------------------------------
FILE *fp = fopen("word.txt","rt");
char ch;
while((ch = fgetc(fp)) != EOF)
{
printf("%c",ch);
}
--------------------------------------------
输出结果: adcdefg
每个文件都有一个文件末尾标志符(EOF),当使用fgetc进行读取时,每读取一个字符,文件的内部指针都会向前移动一个字符,当读取到达文件末尾时,指针便指向EOF。
由于是读取字符,所以使用该函数时打开的文件应包含读取权限。
该函数可以在不给读取的数值分配字符变量时使用。
fputc()写入字符
与fgetc()相反,fputc()函数用于向文件中写入单个字符,其函数原型如下:
fputc(character,file_pointer);
character :传入要写入的字符,可以是字符常量,也可以是变量。
file_pointer :指向要写入文件的文件指针。
word.txt
adcdefg
--------------------------------------------
//将大写E写入word.txt文件
FILE *fp = fopen("word.txt","wt");//注意此处使用的是write模式,并非append模式
fputc('E',fp);
fclose(fp);
--------------------------------------------
执行命令后的word.txt
E
由于是写入操作,所以打开的文件应当包含对应的写入权限。
每一次的字符写入,内部指针都会自行移动一个字符的位置,指向下一个即将写入的位置。
关于返回值:
若字符被正确写入,则返回被写入的字符,若未被正确写入,则返回EOF。可以用来检查数据是否被正确写入。
FILE *fp = fopen("word.txt","wt");
char ch;
if((ch=fputc('E',fp)) != EOF)
{
printf("%c",ch); //若正确写入,则输出其返回值
}
fclose(fp);
------------------------------------------------
运行结果:
E
可用循环配合字符数组一次性向文件中写入多个字符。
word.txt:
E
---------------------------------------------------------------
//用循环配合字符数组来向文件中写入多个字符
FILE *fp = fopen("word.txt","at");
char ch[5] = {'a','b','c','d'};
int i;
for(i=0;i<4;i++)
{
if(fputc(ch[i],fp) == EOF)
{
printf("Oops!Something wrong happened\n");
continue;
}
}
printf("Successfully writing\n");
fclose(fp);
---------------------------------------------------------------
输出结果:
Successfully writing
---------------------------------------------------------------
word.txt:
Eabcd
getchar()读取字符
getchar()函数也用于从文件中读取一个字符,但与fgetc()不同,该函数是从标准输入文件(通常是键盘)读取一个字符。当输入的字符被成功读取时,getchar将返回输入的字符,若遇到错误则返回EOF。
通常该函数可用于清空输入缓冲区和从键盘读入字符数据。
该函数通常是一个宏定义,等价于fgetc(stdin);
printf("Please enter a char\n");
char ch = getchar();
if(ch != EOF)
{
printf("Character from standard input:%c\n",ch);
}
------------------------------------------------------------
输出结果:
Please enter a char
A
Character from standard input:A
putchar()写入字符
同getchar,putchar用于直接向标准输出文件(通常为显示屏)写入数据。需要一个字符常量或变量作为参数。与fputc一样,当写入成功时返回写入的字符,若发生错误则返回EOF。
通常也是一个宏定义,等价于:
fputc(character,stdout);
int result = putchar('B');
if(result != EOF)
{
printf("\nThe Character B has been written to standard output\n");
}
else
{
perror("Oops!Failed to write to standard output\n");
}
-----------------------------------------------------------------------
输出结果:
B
The Character B has been written to standard output
字符串的读与写
fgets()读取字符串
fgets用于从文件中读取字符串并存储到一个字符数组中。其函数原型如下:
char *fgets(char* str,int num,FILE *stream);
str : 指向用于存储读取字符串的缓冲区指针。
num : 要读取的最大字符数(包含空字符与换行符)。注意,是从文件中读取num-1个字符,最后一位将会自动补上'\0'。
stream : 指向FILE结构体的指针,表示要读取的文件。
word.txt
Eabcde
ABCD
------------------------------------------
FILE *fp = fopen("word.txt","rt");
char str[6];
fgets(str,sizeof(str),fp);
printf("%s\n",str);
fclose(fp);
-------------------------------------------
输出结果:
Eabcd
如上述代码所示,以只读方式打开了文件word.txt,定义了一个6字节大小的字符数组str,并从文件word.txt中读取该字符数组大小的字符。而这会将第一行Eabcd五个字符读取,并在最后留空的一位补上'\0',因此屏幕上会打印Eabcd而并没有e。
现在我们将str数组大小改为11,并重新执行上述代码。
char str[11];
得到如下结果:
并未如我们预期一样恰好读取完word.txt中所有内容。
原因是fgets函数在读取时并不会关注格式,而会在读取到换行符或达到指定的最大字符数时停止读取。(注:换行符也会被包含在读取的字符串中)
关于返回值:
fgets函数也具有返回值,其返回的是字符数组的首地址。
FILE *fp = fopen("word.txt","rt");
char str[5];
printf("%p\n",str);//打印str地址
char *ptr;
ptr = fgets(str,sizeof(str),fp);
printf("%p\n",ptr); //打印返回值ptr指向的地址
fclose(fp);
----------------------------------------------------
输出结果:
000000000061FDDB
000000000061FDDB
fputs()写入字符串
fputs()函数用于将一个字符串写入到指定文件中,该函数不会自动添加换行符,因此若需换行的话需在字符串中显式包含换行符。函数原型如下:
int fputs(const char* str,FILE* stream);
str:指向要写入字符串的指针。
stream:FILE的结构体指针,指向要写入的文件。
关于返回值:
当写入成功时,将返回一个非负值,若写入失败,则返回EOF。
word.txt
Eabcde
ABCD
-------------------------------------------------------------------
FILE* fp;
if((fp=fopen("word.txt","at")) == NULL) //判断是否正确打开文件
{
perror("Error opening the file");
exit(EXIT_FAILURE);
}
if(fputs("Hello",fp) != EOF) //写入文件并判断是否写入成功
{
printf("Successful writing\n");
}
else
{
perror("Failed write to the file");
exit(EXIT_FAILURE);
}
fclose(fp);
-------------------------------------------------------------------
运行结果:
Successful writing
-------------------------------------------------------------------
word.txt
Eabcde
ABCDHello
上述示例代码运用fputs()函数,向word.txt文件中追加了字符串常量"Hello"。现在,我们对将上述代码进行修改如下:
FILE* fp;
char str[20] = "This si C\n Word"; //定义一个大小为20的字符数组,并在其中显式包含换行
if((fp=fopen("word.txt","at")) == NULL)
{
perror("Error opening the file");
exit(EXIT_FAILURE);
}
if(fputs(str,fp) != EOF) //将定义的字符数组名作为参数传入
{
printf("Successful writing\n");
}
else
{
perror("Failed write to the file");
exit(EXIT_FAILURE);
}
fclose(fp);
-------------------------------------------------------------------
运行结果:
Successful writing
-------------------------------------------------------------------
word.txt
Eabcde
ABCDHelloThis si C
Word
修改后的代码定义了一个字符数组,并在其中做了格式调整后将数组名(数组首元素地址)传入fputs进行写操作。
fputs()函数的第一个参数,既可以是字符常量,也可以是字符数组的名称,亦或是一个指针变量。
区块数据的读与写
fread()块数据的读取
在前面,我们了解了fgetc可以从文件中读取单字符,而fgets可以从文件中读取字符串。但有时,对于一些特殊的数据类型(例如结构体)的处理,往往需要对一块数据整体进行读写处理,这就需要用到fread与fwrite功能函数了。
fread()函数原型如下:
size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream);
ptr:也可叫做buffer,是一个指向一块内存的指针。该内存用作缓冲区来暂存从文件中读取过来的一块数据,或要写入文件的一块数据。
size:每块数据项的大小(单位为字节)。
nmemb:要读取数据块的数量。
stream:FILE结构体指针,指向要操作的文件。
换句话说,fread即从用户指定的文件流中,读取nmemb块数据文件,每块数据的大小均为size个字节,并将这些读取到的文件存储在ptr指向的内存块中,并返回成功读取的数据项数量(即如果全部成功读取将返回nmemb)。
若返回值小于nmemb,则表示出现了读取错误或达到了文件结尾。
示例代码如下:
word.txt
ABCDHelloThis si C
------------------------------------------------------
FILE * fp = fopen("word.txt","rt");
int i;
char arr[2][3]; //定义二维数组,将读取的数据分为两块,每块3字节
//进行两次循环,每次向一个块中从文件读取3个字符
for(i=0;i<2;i++)
{
size_t BytesRead = fread(arr[i],3,1,fp);
if(BytesRead != 1) //对返回值进行检查
{
if(feof(fp))
{
printf("Reach the end of file\n");
}
else
{
perror("Error reading file");
fclose(fp);
return; //结束
}
}
}
//打印输出两块数据
for(i=0;i<2;i++)
{
for(int k=0;k<3;k++)
{
printf("%c",arr[i][k]);
}
printf("\t");
}
fclose(fp); //记得关闭文件
------------------------------------------------------
输出结果:
ABC DHe
fwrite()块数据的写入
同fread,fwrite用于对整块数据进行写入。其函数原型如下:
size_t fwrite(const void *ptr, size_t size, size_t nmemb, FILE *stream);
ptr:也可叫做buffer,是一个指向一块内存的指针。该内存用作缓冲区来暂存从文件中读取过来的一块数据,或要写入文件的一块数据。
size:每块数据项的大小(单位为字节)。
nmemb:要读取数据块的数量。
stream:FILE结构体指针,指向要操作的文件。
fwrite函数从ptr所指向的内存块中,读取nmemb块数据,每块数据大小为size个字节。写入成功后返回成功写入的数据(若全部写入成功将返回nmemb)。如果返回值小于nmemb,则说明写入出错。
int i;
int arr[5] = {1, 2, 3, 4, 5};
FILE *fp = fopen("data.bin", "wb"); //二进制方式创建一个文件并打开
if (fp == NULL)
{
perror("Error opening the file");
return;
}
size_t BytesWrite = fwrite(arr,sizeof(int),5,fp); //写入数组arr中的数据,并记录返回值
if(BytesWrite != 5) //通过返回值检查是否写入成功
{
perror("Error write to the file");
}
else
{
printf("Successful write\n");
}
fclose(fp); //关闭文件
------------------------------------------------------
输出结果:
Successful write
执行上述代码,用十六进制打开创建的data文件,可以看到
说明已经写入成功。
区块数据读写操作示例代码
/*自定义一个学生类,由用于输入学生数据后,将数据写入到文件保存并且读取出来打印到
显示器上*/
#include <stdio.h>
#include <process.h>
#include <windows.h>
#include <string.h>
#define ARR_RANGE 3 //宏定义,学生类型数组大小
//定义一个学生类型结构体
typedef struct Student
{
char Name[20];
int Age;
int ID;
}student;
//函数声明
void studentInput(struct Student* ptr);
void studentRead(struct Student* ptr);
void studentWrite(struct Student* ptr);
void studentDisplay(struct Student* ptr);
void clearInputBuff();
int main(int argc,const char* argv[])
{
SetConsoleOutputCP(CP_UTF8);
student stu[ARR_RANGE]; //学生类型结构体数组
studentInput(stu); //输入学生数据
studentWrite(stu); //写入学生数据
studentRead(stu); //读取学生数据
studentDisplay(stu); //显示学生数据
system("pause");
system("cls");
return 0;
}
void studentInput(struct Student* ptr) //输入数据
{
char _name[20];
int _age;
int _id;
for(int i=1;i<ARR_RANGE+1;i++)
{
system("cls");
printf("请输入%d号学生信息\n",i);
printf("请输入姓名\n");
fgets(_name,sizeof(_name),stdin); //从键盘读取数据存入字符数组
_name[strcspn(_name,"\n")] = '\0'; //剔除掉输入数据中的换行符
printf("请输入年龄\n");
scanf("%d",&_age);
printf("请输入学号\n");
scanf("%d",&_id);
clearInputBuff();
//为结构体类变量赋值
strcpy(ptr->Name,_name);
ptr->ID = _id;
ptr->Age = _age;
ptr++;
}
system("cls");
printf("Successful inputing\n");
Sleep(1000);
ptr=NULL;
}
void studentRead(struct Student* ptr) //读取数据
{
system("cls");
FILE *fp = fopen("Database.bin","rb");
if(fp == NULL)
{
perror("Error opining the file");
return;
}
size_t BytesRead = fread(ptr,sizeof(student),ARR_RANGE,fp);
if(BytesRead != ARR_RANGE) //对返回值进行检查,确保正确读取
{
perror("Error read the file");
exit(1);
}
else
{
printf("Successful reading\n");
Sleep(1000);
}
ptr=NULL;
fclose(fp);
}
void studentWrite(struct Student* ptr) //写入数据
{
system("cls");
FILE *fp = fopen("Database.bin","wb"); //二进制方式创建并打开文件Database.bin
if(fp == NULL)
{
perror("Error opening the file");
return;
}
size_t BytesWrite = 0;
//从ptr指向的内存块中写入数据
while(fwrite(ptr,sizeof(student),1,fp) == 1){
BytesWrite++;
ptr++;
if(BytesWrite == ARR_RANGE)
{
break;
}
}
printf("Successful Writing\n");
Sleep(1000); //延时1秒
ptr=NULL;
fclose(fp);
}
void studentDisplay(struct Student* ptr) //显示数据
{
for(int i=0;i<ARR_RANGE;i++)
{
printf("Name:%s\n",ptr->Name);
printf("Age:%d\n",ptr->Age);
printf("Student ID:%d\n\n\n",ptr->ID);
ptr++;
}
ptr=NULL;
}
void clearInputBuff()
{
char ch;
while(ch != EOF && (ch=getchar()) != '\n'){}
}
运行结果如下:
标签:fp,文件,搞定,一文,字符,写入,C语言,ptr,读取 From: https://blog.csdn.net/R6bandito/article/details/141187126