14.文件操作【C语言】

1.为什么用文件

使用文件我们可以将数据直接存放在电脑的硬盘上,做到了数据的持久化

2.什么是文件

磁盘上的文件是文件。

程序文件

包括源程序文件(后缀为.c),目标文件(windows环境后缀为.obj),可执行程序(windows环境后缀为.exe)。

数据文件

文件的内容不一定是程序,而是程序运行时读写的数据,比如程序运行需要从中读取数据的文件,或者输出内容的文件。

image-20220221143216269

文件名

文件名包含3部分:文件路径+文件名主干+文件后缀

c:\code\test.txt

3.文件打开&关闭

文件指针

每个被使用的文件都在内存中开辟了一个相应的文件信息区,用来存放文件的相关信息(如文件的名字,文件状态及文件当前的位置等)。这些信息是保存在一个结构体变量中的。该结构体类型是由系统声明的,取名FILE.

struct _iobuf 
{
    char *_ptr;
    int   _cnt;
    char *_base;
    int   _flag;
    int   _file;
    int   _charbuf;
    int   _bufsiz;
    char *_tmpfname;
};
typedef struct _iobuf FILE;
//创建一个FILE*的指针变量来维护这个FILE结构的变量
FILE* pf;//文件指针变量

image-20220221144323649

打开关闭

fopen&fclose

FILE *fopen( const char *filename, const char *mode );
Return Value

Each of these functions returns a pointer to the open file. A null pointer value indicates an error. 

"r"

Opens for reading. If the file does not exist or cannot be found, the fopen call fails.

"w"

Opens an empty file for writing. If the given file exists, its contents are destroyed.

"a"

Opens for writing at the end of the file (appending) without removing the EOF marker before writing new data to the file; creates the file first if it doesn’t exist.

"r+"

Opens for both reading and writing. (The file must exist.)

"w+"

Opens an empty file for both reading and writing. If the given file exists, its contents are destroyed.
#include <stdio.h>
#include <stdlib.h>
int main(int argc, char const *argv[])
{
    //打开文件
    FILE* pf = fopen("test.txt","w");//注意是""
    if (pf == nullptr)
    {
        printf("fopen fail\n");
        exit(-1);
    }
    //写文件
    //...

    //关闭文件
    fclose(pf);
    pf = nullptr;
    return 0;
}
//打开文件
FILE* pf = fopen("tesst.txt","r");//注意是""
if (pf == nullptr)
{
    //printf("fopen fail\n");
    printf("%s\n",strerror(errno));//No such file or directory
    exit(-1);
}
参数解读

“r”:以只读的形式打开文本文件(不存在则出错)
“w”:以只写的形式打开文本文件(若不存在则新建,反之,则从文件起始位置写,覆盖原内容)
“a”:以追加的形式打开文本文件(若不存在,则新建;反之,在原文件后追加)
“r+”:以读写的形式打开文本文件(读时,从头开始;写时,新数据只覆盖所占的空间)
“wb”:以只写的形式打开二进制文件
“rb”:以只读的形式打开二进制文件
“ab”:以追加的形式打开一个二进制文件
“rb+”:以读写的形式打开二进制文件。
“w+”:首先建立一个新文件,进行写操作,然后从头开始读(若文件存在,原内容将全部消失)
“a+”:功能与”a”相同。只是在文件尾部追加数据后,可以从头开始读
“wb+”:功能与”w+”相同。只是在读写时,可以由位置函数设置读和写的起始位置
“ab+”:功能与”a+”相同。只是在文件尾部追加数据之后,可以由位置函数设置开始读的起始位置

相关库函数

  1. 文件的打开
    fopen():打开文件

  2. 文件的关闭
    fclose():关闭文件

  3. 文件的读写
    fgetc():读取一个字符 适用于所有输入流
    fputc():写入一个字符
    fgets():读取一个字符串
    fputs():写入一个字符串
    fprintf():写入格式化数据
    fscanf():格式化读取数据
    fread():读取数据
    fwrite():写入数据

  4. 文件状态检查
    feof():文件是否结束
    ferror():文件读/写是否出错
    clearerr():清除文件错误标志
    ftell():文件指针的当前位置

  5. 文件指针定位
    rewind():把文件指针移到开始处
    fseek():重定位文件指针

为什么把信息输出到屏幕上时不用先打开屏幕再关闭屏幕?

任何c语言程序,只要运行起来就默认打开3个流

  1. stdin 标准输入流
  2. stdout 标准输出流
  3. stderr 标准错误流

他们也都是FILE* 类型

注意: f系列的输入输出函数都是作用于所有流的的

4.文件顺序读写

fgetc&fputc

int main(int argc, char const *argv[])
{
    //打开文件
    FILE* pf = fopen("data.txt","w");//注意是""
    if (pf == nullptr)
    {
        printf("%s\n",strerror(errno));//No such file or directory
        exit(-1);
    }
    //写文件
    char ch = 0;
    for (ch  = 'a'; ch < 'z'; ch++)
    {
        fputc(ch,stdout);//写到屏幕上了 abcdefghijklmnopqrstuvwxy
    }

    //关闭文件
    fclose(pf);
    pf = nullptr;
    return 0;
}
for (ch  = 'a'; ch < 'z'; ch++)
{
    fputc(ch,pf);//写到文件里面 abcdefghijklmnopqrstuvwxy
}
//读文件
int ch = 0;//用int既能接受ASCII 也能接受EOF
while ((ch = fgetc(pf)) != EOF)
{
    printf("%c ",ch);
}

fputs&fgets

//写一行
fputs("hello world\n",pf);
fputs("hello\n",pf);
//读文件 读一行
char buf[1000] = {0};
fgets(buf, 1000, pf); //实际读999个
printf("%s",buf);

文件拷贝

//将data.txt拷贝一份 生成data2.txt
int main(int argc, char const *argv[])
{
    FILE *pr = fopen("data.txt", "r");
    if (pr == NULL)
    {
        printf("open for reading::%s", strerror(errno));
        return 0;
    }
    FILE *pw = fopen("data2.txt", "w");
    if (pw == NULL)
    {
        printf("open for writing::%s", strerror(errno));
        // pw开辟失败
        fclose(pr);
        pr = NULL;
        return 0;
    }
    //拷贝文件
    int ch = 0;
    while ((ch = fgetc(pr)) != EOF)
    {
        fputc(ch, pw); //读一个写一个
    }

    //关闭文件
    fclose(pr);
    pr = NULL;
    fclose(pw);
    pw = NULL;
    system("pause");
    return 0;
}

fprintf&fscanf

struct Stu
{
	char name[20];
	int age;
	double d;
};
int main()
{
	struct Stu s = { "张三", 20, 95.5 };
	FILE* pf = fopen("data.txt", "w");
	if (pf == NULL)
	{
		printf("%s\n", strerror(errno));
		return 0;
	}
	//写格式化的数据
	fprintf(pf, "%s %d %lf", s.name, s.age, s.d);

	fclose(pf);
	pf = NULL;

	return 0;
}
fprintf(stdout, "%s %d %lf", s.name, s.age, s.d);
//输出到屏幕上也行
int main()
{
	struct Stu s = { 0 };
	FILE* pf = fopen("data.txt", "r");
	if (pf == NULL)
	{
		printf("%s\n", strerror(errno));
		return 0;
	}
	//读格式化的数据 从data.txt中读
	fscanf(pf, "%s %d %lf", s.name, &(s.age), &(s.d));

	printf("%s %d %lf\n", s.name, s.age, s.d);

	fclose(pf);
	pf = NULL;

	return 0;
}

fwrite&fread

//二进制的写
int main()
{
    struct Stu s[2] = {{"张三", 20, 96}, {"李四", 20, 86}};
    FILE *pf = fopen("data.txt", "wb");
    if (pf == NULL)
    {
        printf("%s\n", strerror(errno));
        return 0;
    }
    //按照二进制的方式写入
    //数组名本身就是地址
    fwrite(s,sizeof(struct Stu),2,pf);//写2组
    //注意二进制的方式写进去后,文本方式打开的是乱码,需要通过二进制编辑器打开
    fclose(pf);
    pf = NULL;
    return 0;
}
//二进制的读
int main()
{
    struct Stu s[2] = {0};
    FILE *pf = fopen("data.txt", "rb");
    if (pf == NULL)
    {
        printf("%s\n", strerror(errno));
        return 0;
    }
    //按照二进制的方式读出来
    fread(s,sizeof(struct Stu),2,pf);//写2组
    printf("%s %d %lf\n",s[0].name,s[0].age,s[0].d);    
    fclose(pf);
    pf = NULL;
    return 0;
}

文件版Contact

void LoadContact(Contact *pc)
{
	//从文件加载数据
	FILE *pf = fopen("contact.txt", "rb");
	if (pf == nullptr)
	{
		printf("InitContact:: open for reading :: %s\n", strerror(errno));
	}
	//读取文件
	Peoinfo buf = {0};
	while (fread(&buf, sizeof(Peoinfo), 1, pf))
	{
		//放进去前要检测容量是否足够
		check_capacity(pc);
		pc->data[pc->sz] = buf;
		pc->sz++;
	}
}
void SaveContact(Contact *pc)
{
	//打开文件
	FILE *pf = fopen("contact.txt", "wb");
	if (pf == nullptr)
	{
		printf("SaveContact::%s\n", strerror(errno));
		exit(-1);
	}
	//写文件
	int i = 0;
	for (i = 0; i < pc->sz; i++)
	{
		//一次写1组,data指向一块连续空间,每次写入的地址要增加
		fwrite(pc->data + i, sizeof(Peoinfo), 1, pf);
	}

	//关闭文件
	fclose(pf);
	pf = nullptr;
}

sscanf&sprintf

scanf 从标准输入流(stdin)上进行格式化输入
printf 想标准输出流(stdout)上进行格式化的输出

fscanf 从标准输入流/指定的文件流上读取格式化的数据
fprintf 把数据按照格式化的方式输出到标准输出流/指定的文件流

sscanf 从一个字符串中提取(转化)出格式化的数据
sprintf 吧一个格式化的数据转换成字符串

int main(int argc, char const *argv[])
{
    struct Stu s = {"Tom", 20, 98};
    struct Stu tmp = {0};
    char buf[100] = {0};
    sprintf(buf,"%s %d %lf",s.name,s.age,s.d);
    printf("%s\n",buf);//Tom 20 98.000000

    sscanf(buf,"%s %d %lf",s.name,&(s.age),&(s.d));
    printf(buf,"%s %d %lf",tmp.name,tmp.age,tmp.d);//Tom 20 98.000000
    return 0;
}

5.文件的随机读写

fssek

根据文件指针的位置和偏移量来定位文件指针

int fseek ( FILE * stream, long int offset, int origin );
int main(int argc, char const *argv[])
{
    FILE *pf = fopen("test.txt", "r");//abcdef
    if (pf == nullptr)
    {
        cout << strerror(errno) << endl;
        return 0;
    }

    //读文件
    char ch = fgetc(pf);
    cout << ch << endl;
    ch = fgetc(pf);
    cout << ch << endl;
    //定位文件指针
    //fseek(pf, 3, SEEK_CUR);
    //fseek(pf,5,SEEK_SET);//SET表示从起始位置开始偏移
    fseek(pf, -1, SEEK_END);//END从结束位置开始偏移,偏移-1就指向end
    ch = fgetc(pf);
    cout << ch << endl;//f
    fclose(pf);
    pf = nullptr;
    system("pause");
    return 0;
}
int main(int argc, char const *argv[])
{
    FILE *pf = fopen("test.txt", "w");//abcdef
    if (pf == nullptr)
    {
        cout << strerror(errno) << endl;
        return 0;
    }
    //写文件
    int ch = 0;
    for (ch = 'a'; ch <= 'z'; ch++)
    {
        fputc(ch,pf);
    }
    
    //读文件
    fseek(pf, -1, SEEK_END);//END从结束位置开始偏移,偏移-1就指向end
    fputc('@',pf);//把z改成#
    fclose(pf);
    pf = nullptr;
    system("pause");
    return 0;
}

ftell

返回文件指针相对于起始位置的偏移量

long int ftell ( FILE * stream );
int main(int argc, char const *argv[])
{
    FILE *pf = fopen("test.txt", "r");//abcdef
    if (pf == nullptr)
    {
        cout << strerror(errno) << endl;
        return 0;
    }

    //读文件
    char ch = fgetc(pf);
    cout << ch << endl;//a
    ch = fgetc(pf);
    cout << ch << endl;//b
    
    //返回偏移量
    cout << ftell(pf) << endl;//2
    fclose(pf);
    pf = nullptr;
    system("pause");
    return 0;
}

rewind

让文件指针的位置回到文件的起始位置

void rewind ( FILE * stream );
int main(int argc, char const *argv[])
{
    FILE *pf = fopen("test.txt", "r");//abcdef
    if (pf == nullptr)
    {
        cout << strerror(errno) << endl;
        return 0;
    }

    //读文件
    char ch = fgetc(pf);
    cout << ch << endl;//a
    ch = fgetc(pf);
    cout << ch << endl;//b
    
    //返回偏移量
    cout << ftell(pf) << endl;//2

    rewind(pf);
    //fseek(pf, 0, SEEK_SET); 也有这样的效果
    cout << ftell(pf) << endl;//0
    fclose(pf);
    pf = nullptr;
    system("pause");
    return 0;
}

6.文本文件和二进制文件

数据在内存中以二进制的形式存储,如果不加转换的输出到外存,就是二进制文件

如果要求在外存上以ASCII码的形式存储,则需要在存储前转换。以ASCII字符的形式存储的文件就是文本文件。

一个数据在内存中是怎么存储的呢?

字符一律以ASCII形式存储,数值型数据既可以用ASCII形式存储,也可以使用二进制形式存储。

如有整数10000,如果以ASCII码的形式输出到磁盘,则磁盘中占用5个字节(每个字符一个字节),而二进制形式输出,则在磁盘上只占4个字节(VS2013测试)。

image-20220223194503644

#include <stdio.h>
int main()
{
    int a = 10000;
    FILE* pf = fopen("test.txt", "wb");
    fwrite(&a, 4, 1, pf);//二进制的形式写到文件中
    fclose(pf);
    pf = NULL;
    return 0; 
}
0000 0000 0000 0000 0010 0111 0001 0000
00 00 27 10
    //用二进制编辑器打开test.txt看到的就是10 27 00 00  小端存储

7.文件结束判定

feof

feof不是用来判断文件结束的,而是应用于当文件读取结束的时候,判断是读取失败结束,还是遇到文件尾结束

文件结束标志

文本文件

fgetc判断是否为EOF

fgets判断返回值是否为NULL

int main(void) 
{
    int c; // 注意:int,非char,要求处理EOF
    FILE* fp = fopen("test.txt", "r");
    if(!fp) 
    {
        perror("File opening failed");
        return EXIT_FAILURE;
    }
    //fgetc 当读取失败的时候或者遇到文件结束的时候,都会返回EOF
    while ((c = fgetc(fp)) != EOF) // 标准C I/O读取文件循环
    { 
        putchar(c);
    }
    //判断是什么原因结束的
    if (ferror(fp))
        puts("I/O error when reading");
    else if (feof(fp))
        puts("End of file reached successfully");
    fclose(fp);
}

二进制文件

fread判断返回值是否小于实际要读的个数

size_t fread( void *buffer, size_t size, size_t count, FILE *stream );
#include <stdio.h>
enum { SIZE = 5 };
int main(void) 
{
    double a[SIZE] = {1.0,2.0,3.0,4.0,5.0};
    FILE *fp = fopen("test.bin", "wb"); // 必须用二进制模式
    fwrite(a, sizeof(*a), SIZE, fp); // 写 double 的数组
    fclose(fp);
    double b[SIZE];
    fp = fopen("test.bin","rb");
    size_t ret_code = fread(b, sizeof *b, SIZE, fp); // 读 double 的数组
    if(ret_code == SIZE) 
    {
        puts("Array read successfully, contents: ");
        for(int n = 0; n < SIZE; ++n) 
            printf("%f ", b[n]);
        putchar('\n');
    } 
    else 
    { 
        // error handling
        if (feof(fp))
            printf("Error reading test.bin: unexpected end of file\n");
        else if (ferror(fp)) 
        {
            perror("Error reading test.bin");
        }
    }
    fclose(fp);

ferror

int ferror( FILE *stream );

Tests for an error on a stream.

8.文件缓冲区

ANSIC 标准采用**“缓冲文件系统”处理数据文件,也就是指系统自动地在内存中为程序中每一个正在使用的文件开辟一块“文件缓冲区”。**
从内存向磁盘输出数据会先送到内存中的缓冲区,装满缓冲区后才一起送到磁盘上。
如果从磁盘向计算机读入数据,则从磁盘文件中读取数据输入到内存缓冲区(充满缓冲区),然后再从缓冲区逐个地将数据送到程序数据区(程序变量等)

不同编译器缓冲区实现机制不一样

image-20220223201308293

C程序代码--->调用printf--->printf调用系统调用api--->在屏幕输出相应信息

//VS2013 WIN10环境测试
int main()
{
    FILE*pf = fopen("test.txt", "w");
    fputs("abcdef", pf);//先将代码放在输出缓冲区
    printf("睡眠10秒-已经写数据了,打开test.txt文件,发现文件没有内容\n");
    Sleep(10000);
    printf("刷新缓冲区\n");
    fflush(pf);//刷新缓冲区时,才将输出缓冲区的数据写到文件(磁盘)
    //注:fflush 在高版本的VS上不能使用了
    printf("再睡眠10秒-此时,再次打开test.txt文件,文件有内容了\n");
    Sleep(10000);
    fclose(pf);
    //注:fclose在关闭文件的时候,也会刷新缓冲区
    pf = NULL;
    return 0; 
}
//刷新缓冲区就是把缓冲区d

要么手动刷新缓冲区立刻把数据写到文件,要么等缓存区满了再写到文件

Linux环境下,读取到\n就会刷新缓冲区

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值