0


c语言文件操作

为什么使用文件

以前运行程序的时候,会输入或输出一些数据。大家发现没有,每一次运行,我们都得烦不胜烦地重新输入数据。这是因为,此时的数据是存放在内存中的,只要程序一结束,这些数据就会被销毁。

那如果我们想把数据记录下来呢?不管是输入数据亦或是输出数据。

这就要用到我今天介绍的内容了——文件。使用文件,我们能将数据直接存放在电脑的硬盘上,做到了数据的持久化。

什么是文件

磁盘上的文件是文件。就是我们平时说的C盘,D盘之类的。

以文件功能的角度来分类的话,文件分为:程序文件、数据文件。本篇博客专注于介绍数据文件。

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

对于数据文件,我们可以把信息输出到磁盘上,当需要的时候再从磁盘上把数据读取到内存中使用,这里处理的就是磁盘上文件。

文件名

一个文件要有唯一的文件名

文件名包括文件路径+文件名主干+文件后缀

文件的后缀名决定了一个文件的默认打开方式,但文件不一定有后缀。

文件的打开和关闭

每个被使用的文件都在内存中开辟了一个相应的文件信息区,用来存放文件的相关信息(如文件的名 字,文件状态及文件当前的位置等)。这些信息是保存在一个结构体变量中的。该结构体类型是由系统声明的,取名FILE。不同的C编译器的FILE类型存在些许差异,但也大同小异。但其实我们也无需关心里面的具体内容,只要会用就可以了。

一般情况下,可以通过创建一个FILE*的指针变量,如pf。

FILE* pf ; //文件指针变量

pf指向某个文件的文件信息区(是一个结构体变量)。通过该文件信息区中的信息就能够访问该文件。也就是说,通过文件指针变量能够找到与它关联的文件。

类比于使用一瓶墨水,我们要经历拧开墨水盖,蘸取墨水,最后把盖子拧回去的过程。同样的,访问文件,我们也要经历,打开文件,读写文件,最后是关闭文件的过程。

ANSIC 规定使用fopen函数来打开文件,fclose来关闭文件。

//打开文件

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

//关闭文件

int fclose ( FILE * stream );

fopen函数,第二个参数mode指的是打开的方式,一定要使用" "才行。具体内容,见下文:
文件使用方式含义如果指定文件不存在"r" (只读)为了输入数据,打开一个已经存在的文本文件报错"w"(只写)为了输出数据,打开一个文本文件建立一个新的文件"a" (追加)向文本文件尾添加数据建立一个新的文件“rb”(只读)为了输入数据,打开一个二进制文件报错“wb”(只写)为了输出数据,打开一个二进制文件建立一个新的文件“ab”(追加)向一个二进制文件尾添加数据出错

#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>

int main() {
    //打开文件
    //相对路经:如果文件不存在
    //则会在该项目存储的地方创建一个新的文件
    FILE* pf = fopen("text.txt", "w");
    if (NULL == pf) {
        perror("fopen");
        return 1;
    }

    //写文件
    //具体内容见下文
    
    //关闭文件
    fclose(pf);
    pf = NULL;
    return 0;
}

//而绝对路径就是指定地方创建文件,比如:
//FILE* pf = fopen("C:\\code\\text.txt", "w");

文件的顺序读写

功能函数名适用于字符输出函数fputc所有输出流字符输入函数fgetc所有输入流文本行输出函数fputs所有输出流文本行输入函数fgets所有输入流格式化输出函数fprintf所有输出流格式化输入函数fscanf所有输入流二进制输入函数fread文件二进制输出函数fwrite文件

**字符输出函数fputc:**
int fputc ( int character, FILE * stream ); 
//写文件
    int i = 0;
    for (i = 0; i < 26; i++)
    {
        fputc('A' + i, pf);
        fputc(' ', pf);
    }

字符输入函数fgetc:

int fgetc ( FILE * stream );
//前面打开文件的第二个参数要改成"r
//"FILE* pf = fopen("text.txt", "r");
//读取文件
int ch = 0;
    while ((ch = fgetc(pf)) != EOF) {
        printf("%c", ch);
    }

文本行输出函数:

int fputs ( const char * str, FILE * stream );
    //一行一行地写文件
    fputs("Hello!\n", pf);
    fputs("What is your name?", pf);

** 文本行输入函数fgets:**

char * fgets ( char * str, int num, FILE * stream );
//读取文件:一行一行地读
    char arr[12] = "##########";//10*#
    fgets(arr, 12, pf);
    printf("%s", arr);
    fgets(arr, 12, pf);
    printf("%s", arr);

可以看到,读取的第一行"hello!",六个字符外加一个'\0',arr多余的#没有读取。第二行"What is your name?",18个字符外加一个'\0',但arr的大小只有12个字节,因此读取11个字符的同时,留一个字符给'\0'。

** 格式化输出函数fprintf:**

int fprintf ( FILE * stream, const char * format, ... );

对比以下printf函数的声明

int printf ( const char * format, ... );
//创建一个结构体
struct People {
    char name[20];
    int age;
    char ID[10];
};

struct People pp = { "Christy",18,"1234567" };
//将结构体的信息存入文件中
fprintf(pf, "%s %d %s", pp.name, pp.age, pp.ID);

结构化输入函数fscanf:

int fscanf ( FILE * stream, const char * format, ... );

对比scanf函数的声明

int scanf ( const char * format, ... );
//读取时,同样需要创建一个结构体
struct People {
    char name[20];
    int age;
    char ID[10];
};

struct People pp = { 0 };
//从文件中读取结构体信息
fscanf(pf, "%s %d %s", pp.name, &pp.age, pp.ID);
printf("%s %d %s", pp.name, pp.age, pp.ID);

二进制输出函数fwrite:

size_t fwrite ( const void * ptr, size_t size, size_t count, FILE * stream );
//FILE* pf = fopen("text.txt", "rw");
//以二进制的方式,将一个结构体信息写入文件中
struct People pp = { "Christy 貌美如花",18,"88888888"};
fwrite(&pp, sizeof(pp), 1, pf);

二进制输入函数fread:

size_t fread ( void * ptr, size_t size, size_t count, FILE * stream );
//FILE* pf = fopen("text.txt", "rb");
//读取一个用二进制书写的文件
struct People pp = { 0};
fread(&pp, sizeof(pp), 1, pf);
printf("%s %d %s", pp.name, pp.age, pp.ID);

到这里,文件的顺序读写函数介绍的差不多了。那我们就利用上面讲过的知识,来做一点知识上延申吧。

请说出下面两组函数的差异?

scanf / fscanf / sscanf

printf / fprintf / sprintf

这两组函数中,我们最熟悉的就是scanf函数(按照一定的格式从键盘输入数据)和printf函数(按照一定的格式把数据打印到屏幕上),这是一对适用于标准输入/输出流的格式化输入/输出语句。今日学了fscanf(按照一定的格式从输入流(文件)输入数据)及fprintf(按照一定的格式将数据输出到输出流(文件)中),这是一对适用于所有输入/输出流的格式化输入/输出语句。那sscanf和sprintf又是啥呢?

通过查询cplusplus网站,我们可以找到以下声明

int sscanf ( const char * s, const char * format, ...);

Read formatted data from string:把字符串按照一定的格式读取成格式化数据

int sprintf ( char * str, const char * format, ... );

Write formatted data to string :把格式化的数据按照一定的格式转换成字符串

int main() {
    //将一个结构体的内容写成字符串
    struct People pp = { "Christy!" ,22,"12234553" };
    char buff[50] = { 0 };
    sprintf(buff, "%s %d %s", pp.name, pp.age, pp.ID);
    printf("%s\n", buff);

    //将buff里的内容还原成一个结构体形式
    struct People temp = { 0 };
    sscanf(buff, "%s %d %s", temp.name, &(temp.age), temp.ID);
    printf("%s %d %s", temp.name, temp.age, temp.ID);//以结构体的形式打印
    return 0;
}

文件的随机读写

**fseek函数 **

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

int fseek ( FILE * stream, long int offset, int origin );

Constant Reference positionSEEK_SETBeginning of fileSEEK_CURCurrent position of the file pointerSEEK_ENDEnd of file *

#include<stdio.h>
int main() {
    FILE* pf = fopen("example.txt", "w");
    if (pf == NULL)
        return 1;
    fputs("What is your name?", pf);
    fseek(pf, 13, SEEK_SET);
    fputs("JOB??", pf);
    fclose(pf);
    pf = NULL;
    return 0;
}

#include<stdio.h>
int main() {
    FILE* pf = fopen("example.txt", "w");
    if (pf == NULL)
        return 1;
    fputs("What is your name?", pf);
    fseek(pf, -13, SEEK_END);
    fputs("do you do for your living?", pf);
    fclose(pf);
    pf = NULL;
    return 0;
}

** ftell函数**

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

long int ftell ( FILE * stream );
fseek(pf, 5, SEEK_SET);
    long ret = ftell(pf);
    printf("%ld", ret);

输出 5

rewind函数

让文件指针回到起始位置

void rewind ( FILE * stream );
rewind(pf);

文本文件和二进制文件

数据文件有文本文件和二进制文件之分。数据在内存中以二进制的形式存储,如果不加转换的输出到外存,就是二进制文件。如果要求在外存上以ASCII码的形式存储,则需要在存储前转换。以ASCII字符的形式存储的文件就是文本文件。

拿11111(1万1千1百1十1)举例子。

#include<stdio.h>
int main() {
    FILE* pf = fopen("text.txt", "wb");
    if (pf == NULL)
        return 1;
    int a = 11111;
    fwrite(&a, 4, 1, pf);
    fclose(pf);
    pf = NULL;
    return 0;
}

此时的文件代开,是些看不懂的东西:

但如果使用二进制编辑器,就可以看懂:

文件读取结束的判定

文本文件读取是否结束,判断返回值是否为 EOF( fgetc),或者NULL(fgets)。

二进制文件的读取结束判断,判断返回值是否小于实际要读的个数。如,fread函数判断返回值是否小于实际要读的个数。

所以实际上,并不能使用feof函数的返回值来直接判断文件是否结束。这个函数另有用途,用于当文件读取结束的时候,判断是读取失败结束,还是遇到文件尾结束。

文件缓冲区

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

#include <stdio.h>
#include <windows.h>
//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;
}

因为有缓冲区的存在,C语言在操作文件的时候,需要做刷新缓冲区或者在文件操作结束的时候关闭文件,这一行为也会刷新缓冲区。

标签: c语言 开发语言

本文转载自: https://blog.csdn.net/qq_41233305/article/details/127241053
版权归原作者 是Christy的博客呀 所有, 如有侵权,请联系我们删除。

“c语言文件操作”的评论:

还没有评论