C语言文件操作

简介: C语言文件操作

一.提前说明

说明:我们对文件操作相信并不陌生,但是由于平时课程的关系,文件操作使用也比较少,知识点并不是很多,但容易忘记,忘记了某一天再使用又要重新学,因此我打算写一篇博客,记录C语言文件操作的知识点,巩固知识点,也以便以后回忆。


我们对文件的概念已经非常熟悉了,比如常见的 Word 文档、txt 文件、源文件等。文件是数据源的一种,最主要的作用是保存数据。

二.C语言文件操作

在操作系统中,为了统一对各种硬件的操作,简化接口,不同的硬件设备也都被看成一个文件。对这些文件的操作,等同于对磁盘上普通文件的操作。例如:

  • 通常把显示器称为标准输出文件,printf 就是向这个文件输出数据;
  • 通常把键盘称为标准输入文件,scanf 就是从这个文件读取数据。

1.常见硬件设备对应的文件

在C语言中,常见硬件设备对应的文件包括:


标准输入、输出和错误设备:

标准输入:stdin(通常是键盘)

标准输出:stdout(通常是屏幕)

错误输出:stderr(通常是屏幕)

硬盘、磁盘等存储设备:

文件:用文件名来表示

硬盘:/dev/hd[a-d][1-16] 或 /dev/sd[a-p][1-16]

网络设备:

网络接口卡:/dev/net/tun

串口设备:

串口设备:/dev/ttyS[0-3] (COM1-COM4)

USB串口设备:/dev/ttyUSB[0-3]

并口设备:

并口设备:/dev/lp[0-2]

2.文件操作流程

操作文件的正确流程为:打开文件 --> 读写(操作)文件 --> 关闭文件。文件在进行读写操作之前要先打开,使用完毕要关闭。


所谓打开文件,就是获取文件的有关信息,例如文件名、文件状态、当前读写位置等,这些信息会被保存到一个 FILE 类型的结构体变量中。关闭文件就是断开与文件之间的联系,释放结构体变量,同时禁止再对该文件进行操作。


在C语言中,文件有多种读写方式,可以一个字符一个字符地读取,也可以读取一整行,还可以读取若干个字节。文件的读写位置也非常灵活,可以从文件开头读取,也可以从中间位置读取。

3.文件流

所有的文件(保存在磁盘)都要载入内存才能处理,所有的数据必须写入文件(磁盘)才不会丢失。数据在文件和内存之间传递的过程叫做文件流,类似水从一个地方流动到另一个地方。数据从文件复制到内存的过程叫做输入流,从内存保存到文件的过程叫做输出流。


文件是数据源的一种,除了文件,还有数据库、网络、键盘等;数据传递到内存也就是保存到C语言的变量(例如整数、字符串、数组、缓冲区等)。我们把数据在数据源和程序(内存)之间传递的过程叫做数据流(Data Stream)。相应的,数据从数据源到程序(内存)的过程叫做输入流(Input Stream),从程序(内存)到数据源的过程叫做输出流(Output Stream)。


输入输出(Input output,IO)是指程序(内存)与外部设备(键盘、显示器、磁盘、其他计算机等)进行交互的操作。几乎所有的程序都有输入与输出操作,如从键盘上读取数据,从本地或网络上的文件读取数据或写入数据等。通过输入和输出操作可以从外界接收信息,或者是把信息传递给外界。


我们可以说,打开文件就是打开了一个流。

三.文件操作三剑客

上面已经知道,文件的操作流程为:打开文件 --> 读写(操作)文件 --> 关闭文件。这部分我们就按照这个流程来解释对应的函数。


1.打开文件

在C语言中,操作文件之前必须先打开文件;所谓“打开文件”,就是让程序和文件建立

连接的过程。


fopen() 是一个 C 语言标准库函数,用于打开文件,并返回一个文件指针。其语法如下:

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

其中,filename 参数是要打开的文件名,可以包含路径信息;mode 参数是以哪种方式打开文件,常见的有以下模式:


"r":以只读方式打开文件,该文件必须存在。

"w":以写方式打开文件,如果文件不存在则创建新文件,如果文件已经存在则清空文件内容。

"a":以追加方式打开文件,在文件末尾添加数据,如果文件不存在则创建新文件。

"r+":以读写方式打开文件,该文件必须存在。

"w+":以读写方式打开文件,如果文件不存在则创建新文件,如果文件已经存在则清空文件内容。

"a+":以读写方式打开文件,在文件末尾添加数据,如果文件不存在则创建新文件。

如果打开文件成功,则返回一个指向 FILE 类型结构体的指针;否则返回 NULL。


下面是一个打开文件的例子:

FILE *fp;
if( (fp=fopen("D:\\demo.txt","rb")) == NULL ){
    printf("Fail to open file!\n");
    exit(0);                       //退出程序(结束程序)
}

2.关闭文件

fclose() 函数用于关闭一个打开的文件。它的语法如下:

int fclose(FILE *stream);

其中,stream 是指向 FILE 对象的指针。如果成功退出,则返回 0 ,否则返回 EOF。


3.读写文件

在C语言中,读写文件比较灵活,既可以每次读写一个字符,也可以读写一个字符串,甚至是任意字节的数据(数据块)。本节介绍以字符形式读写文件。以字符形式读写文件时,每次可以从文件中读取一个字符,或者向文件中写入一个字符。主要使用两个函数,分别是 fgetc() 和 fputc()。


fgetc() 是 C 语言标准库中的一个函数,用于从指定的文件流读取一个字符。它的语法如下:

int fgetc(FILE *stream);

其中,stream 参数是一个 FILE 类型的指针,指向要读取的文件流。函数返回值为读取的字符(作为 int 类型返回)或者在遇到文件结束符(EOF)时返回 EOF。

由于它每次读取的都是一个字符,所以我们可以通过以下代码逐个字符读取一个文件:

    //每次读取一个字节,直到读取完毕
    while( (ch=fgetc(fp)) != EOF ){
        putchar(ch);
    }
    putchar('\n');  //输出换行符

fputc() 函数用于将一个字符写入指定的文件流中。其语法如下:

int fputc(int ch, FILE *stream);

其中,ch 表示要写入的字符,stream 表示文件指针。函数返回值为写入的字符,如果发生错误则返回 EOF(-1)。

例如,以下代码将字符 'a' 写入文件:

#include <stdio.h>
int main() {
    char ch = 'a';
    FILE *fp = fopen("file.txt", "w");
    if (fp != NULL) {
        fputc(ch, fp);
        fclose(fp);
    }
    return 0;
}

这个程序创建了一个名为 file.txt 的新文件,并向其中写入了字符 'a'。注意,打开文件时使用的模式为 "w",表示写操作。如果文件已存在,则会被截断为零长度,即清空文件内容。

那么我们可以通过以下代码实现逐个字符输入文件:

    char ch;
    printf("Input a string:\n");
    //每次从键盘读取一个字符并写入文件
    while ( (ch=getchar()) != '\n' ){
        fputc(ch,fp);
    }

4.完整流程实现

#include <stdio.h>
int main() {
    FILE *fp_in, *fp_out;
    int ch;
    // 打开输入文件
    fp_in = fopen("input.txt", "r");
    if (fp_in == NULL) {
        printf("Failed to open input.txt\n");
        return 1;
    }
    // 打开输出文件
    fp_out = fopen("output.txt", "w");
    if (fp_out == NULL) {
        printf("Failed to open output.txt\n");
        fclose(fp_in);
        return 1;
    }
    // 逐个字符地读取输入文件,并将其写入输出文件
    while ((ch = fgetc(fp_in)) != EOF) {
        fputc(ch, fp_out);
    }
    // 关闭文件指针
    fclose(fp_in);
    fclose(fp_out);
    return 0;
}

这个程序将打开名为 input.txt 的输入文件和名为 output.txt 的输出文件,然后逐个字符地从输入文件中读取字符,并将其写入输出文件中,最后关闭文件指针。


需要注意的是,在使用 fgetc() 函数和 fputc() 函数时,如果文件操作失败(例如文件无法打开、读写权限不足等),函数会返回相应的错误代码,此时应该及时处理错误并关闭文件指针。


四.其他读写方式

1.以字符串的形式读写

fgets() 是一个用于从标准输入流读取字符串的 C 语言函数。它的用法如下:

char *fgets(char *str, int n, FILE *stream);

其中,参数 str 是一个指向字符数组的指针,用来存储读取到的字符串;参数 n 是要读取的最大字符数(包括最后的空字符);参数 stream 是要读取的文件指针,通常使用 stdin 表示标准输入流。


fgets() 函数会从给定的文件指针读取字符,直到遇到换行符或者读取了 n-1 个字符(包括空字符)。如果成功读取了字符,则将它们存储在 str 所指向的字符数组中,并在末尾添加一个空字符 ‘\0’,以表示字符串的结束。


如果无法读取任何字符(例如已经到达文件末尾),则返回 NULL,否则返回 str 所指向的字符数组的指针。


注意,读取到的字符串会在末尾自动添加 ‘\0’,n 个字符也包括 ‘\0’。也就是说,实际只读取到了 n-1 个字符,如果希望读取 100 个字符,n 的值应该为 101。


例如:

#define N 101
char str[N];
FILE *fp = fopen("D:\\demo.txt", "r");
fgets(str, N, fp);

表示从 D:\demo.txt 中读取 100 个字符,并保存到字符数组 str 中。


需要重点说明的是,在读取到 n-1 个字符之前如果出现了换行,或者读到了文件末尾,则读取结束。这就意味着,不管 n 的值多大,fgets() 最多只能读取一行数据,不能跨行。在C语言中,没有按行读取文件的函数,我们可以借助 fgets(),将 n 的值设置地足够大,每次就可以读取到一行数据。


fputs() 是一个 C 语言标准库函数,用于将字符串写入到文件中。它的函数声明如下:

int fputs(const char *str, FILE *stream);

其中 str 参数是要写入的字符串,stream 参数是要写入的文件指针。如果成功写入,该函数会返回一个非负整数,否则返回 EOF。


fputs() 函数会将给定的字符串作为一个整体写入到文件中,不包括字符串结尾处的空字符 ‘\0’。与 fprintf() 和 printf() 不同,fputs() 不会自动在输出的字符串末尾添加换行符。


这里要想写入的字符串是从文本末尾开始,就得注意你的文件打开方式。


例如这里以追加写的方式,读取一个文件并编写:

#include<stdio.h>
int main(){
    FILE *fp;
    char str[102] = {0}, strTemp[100];
    if( (fp=fopen("D:\\demo.txt", "at+")) == NULL ){
        puts("Fail to open file!");
        exit(0);
    }
    printf("Input a string:");
    gets(strTemp);
    strcat(str, "\n");
    strcat(str, strTemp);
    fputs(str, fp);
    fclose(fp);
    return 0;
}

2.以数据块的形式读写

fgets() 有局限性,每次最多只能从文件中读取一行内容,因为 fgets() 遇到换行符就结束读取。如果希望读取多行内容,需要使用 fread() 函数;相应地写入函数为 fwrite()。


fread() 是 C 语言标准库中的一个函数,用于从文件中读取数据。它的函数声明如下:

size_t fread(void *ptr, size_t size, size_t count, FILE *stream);

其中各个参数的含义如下:


ptr:指向要读取数据的缓冲区。

size:每个数据块的字节数。

count:要读取的数据块数量。

stream:指向要读取的文件或流。

该函数会尝试从指定的文件中读取 count 个数据块,每个数据块的大小为 size。读取到的数据将存储到缓冲区 ptr 中。成功读取的数据块数量将作为函数返回值返回,如果出现错误或到达了文件末尾,则返回值可能小于 count。


需要特别注意的是,在使用 fread() 函数时,应该确保缓冲区大小足够容纳要读取的数据,以避免发生缓冲区溢出等问题。


fwrite() 是 C 语言标准库中的一个函数,用于向文件中写入数据。它的函数声明如下:

size_t fwrite(const void *ptr, size_t size, size_t count, FILE *stream);

其中各个参数的含义如下:


ptr:指向要写入数据的缓冲区。

size:每个数据块的字节数。

count:要写入的数据块数量。

stream:指向要写入的文件或流。

该函数会尝试向指定的文件中写入 count 个数据块,每个数据块的大小为 size 字节。待写入的数据存储在缓冲区 ptr 中。成功写入的数据块数量将作为函数返回值返回,如果出现错误,则返回值可能小于 count。


需要特别注意的是,在使用 fwrite() 函数时,应该确保缓冲区大小足够容纳要写入的数据,以避免发生缓冲区溢出等问题。


以下是 fread() 和 fwrite() 函数读写文本文件的一个示例:

#include <stdio.h>
#include <stdlib.h>
int main()
{
    FILE *fp = fopen("file.txt", "w");
    if (fp == NULL) {
        perror("Failed to open file");
        return -1;
    }
    const char *str = "Hello, world!";
    fwrite(str, sizeof(char), strlen(str), fp);
    fclose(fp);
    fp = fopen("file.txt", "r");
    if (fp == NULL) {
        perror("Failed to open file");
        return -1;
    }
    fseek(fp, 0L, SEEK_END);
    long int size = ftell(fp);
    rewind(fp);
    char *buffer = malloc(size + 1);
    fread(buffer, sizeof(char), size, fp);
    buffer[size] = '\0';
    printf("%s\n", buffer);
    free(buffer);
    fclose(fp);
    return 0;
}

在这个示例中,我们首先以只写模式打开名为 file.txt 的文本文件,并使用 fwrite() 函数将字符串 “Hello, world!” 写入到文件中。然后,我们再次打开同一个文件,并使用 fread() 函数读取文件内容。最后,我们输出读取到的文件内容并释放缓冲区,关闭文件句柄。

3.格式化读写文件

fscanf()和fprintf()函数是C语言中常用的文件格式化读写函数。

fprintf()函数可以将特定格式的数据写入文件中。它的原型为:

Copy Codeint fprintf(FILE *stream, const char *format, ...)

其中,第一个参数是指向要写入的文件的指针,第二个参数是格式化字符串,后面可以跟上要写入的数据。

例如,下面的代码将字符串和整数分别以特定格式写入文件中:

#include <stdio.h>
int main() {
    FILE *fp = fopen("example.txt", "w");
    char str[] = "Hello World";
    int num = 1234;
    fprintf(fp, "String: %s, Number: %d\n", str, num);
    fclose(fp);
    return 0;
}

而fscanf()函数则可以从文件中读取特定格式的数据。它的原型为:

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

其中,第一个参数是指向要读取的文件的指针,第二个参数是格式化字符串,后面可以跟上要读取的变量名。

例如,下面的代码从文件中读取字符串和整数,并按照特定格式进行解析:

#include <stdio.h>
int main() {
    FILE *fp = fopen("example.txt", "r");
    char str[20];
    int num;
    fscanf(fp, "String: %s, Number: %d", str, &num);
    printf("String: %s, Number: %d\n", str, num);
    fclose(fp);
    return 0;
}

需要注意的是,使用fscanf()和fprintf()函数进行文件格式化读写时,需要保证读写数据的格式和数量与指定的格式字符串一致,否则可能会导致读写错误。


4.随机读写

前面介绍的文件读写函数都是顺序读写,即读写文件只能从头开始,依次读写各个数据。但在实际开发中经常需要读写文件的中间部分,要解决这个问题,就得先移动文件内部的位置指针,再进行读写。这种读写方式称为随机读写,也就是说从文件的任意位置开始读写。


移动文件内部位置指针的函数主要有两个,即 rewind() 和 fseek()。


rewind() 用来将位置指针移动到文件开头,前面已经多次使用过,它的原型为:

void rewind ( FILE *fp );

fseek() 用来将位置指针移动到任意位置,它的原型为:

int fseek ( FILE *fp, long offset, int origin );c

参数说明:


fp 为文件指针,也就是被移动的文件。


offset 为偏移量,也就是要移动的字节数。之所以为 long 类型,是希望移动的范围更大,能处理的文件更大。offset 为正时,向后移动;offset 为负时,向前移动。


origin 为起始位置,也就是从何处开始计算偏移量。C语言规定的起始位置有三种,分别为文件开头、当前位置和文件末尾,每个位置都用对应的常量来表示:

起始点 常量名 常量值
文件开头 SEEK_SET 0
当前位置 SEEK_CUR 1
文件末尾 SEEK_END 2

在移动位置指针之后,就可以用前面介绍的任何一种读写函数进行读写了。

五.补充知识点

1.文本文件与二进制文件

文本文件和二进制文件是两种不同类型的文件,它们之间的主要区别在于数据的存储方式和处理方式。


文本文件是一种只包含文本数据的文件,其中的每个字符都按照某种编码格式(如 ASCII 或 UTF-8)被存储为一个字节序列。文本文件通常用于存储简单的文本信息,例如程序源代码、配置文件、日志文件等。由于文本文件中只包含文本数据,因此可以通过普通的文本编辑器打开和编辑。


二进制文件则是一种包含非文本数据的文件,其中的数据可以是任何形式的二进制数据,包括图像、音频、视频、压缩文件等。二进制文件通常采用特定的格式来存储数据,这些格式往往是由厂商或标准委员会定义的。由于二进制文件中的数据通常不是以文本形式存储的,因此无法使用普通的文本编辑器直接打开和编辑。


由于文本文件和二进制文件的存储方式和处理方式不同,因此在进行文件读写操作时需要使用不同的函数和方法。例如,对于文本文件,可以使用 C 语言中的 fputc() 和 fgets() 等函数进行读写;而对于二进制文件,则需要使用 fread() 和 fwrite() 等函数进行读写。


2.EOF说明

EOF 本来表示文件末尾,意味着读取结束,但是很多函数在读取出错时也返回 EOF,那么当返回 EOF 时,到底是文件读取完毕了还是读取出错了?我们可以借助 stdio.h 中的两个函数来判断,分别是 feof() 和 ferror()。


feof() 函数用来判断文件内部指针是否指向了文件末尾,它的原型是:

int feof ( FILE * fp );

当指向文件末尾时返回非零值,否则返回零值。

ferror() 函数用来判断文件操作是否出错,它的原型是:

int ferror ( FILE *fp );

出错时返回非零值,否则返回零值。

3. strcat()函数

strcat() 是一个 C 语言字符串库函数,用于将两个字符串拼接在一起。它的函数声明如下:

char *strcat(char *dest, const char *src);

其中 dest 参数是目标字符串,也就是要将源字符串 src 添加到其后面的字符串。注意,目标字符串 dest 必须具有足够的空间来容纳源字符串 src。


strcat() 函数会将源字符串 src 中的字符依次添加到目标字符串 dest 的末尾,并在最后一个字符之后添加一个空字符 ‘\0’。如果成功拼接了两个字符串,则返回指向目标字符串 dest 的指针。

4. fread() 和 fwrite() 补充

注意看这个两个函数的语法声明,所以我们不能用int类型变量接收它的返回值。size_t 是在 stdio.h 和 stdlib.h 头文件中使用 typedef 定义的数据类型,表示无符号整数,也即非负数,常用来表示数量。


返回值:返回成功读写的块数,也即 count。如果返回值小于 count:


对于 fwrite() 来说,肯定发生了写入错误,可以用 ferror() 函数检测。

对于 fread() 来说,可能读到了文件末尾,可能发生了错误,可以用 ferror() 或 feof() 检测。

5.获取文件长度

ftell() 函数用来获取文件内部指针(位置指针)距离文件开头的字节数,它的原型为:

long int ftell ( FILE * fp );

注意:fp 要以二进制方式打开,如果以文本方式打开,函数的返回值可能没有意义。

先使用 fseek() 将文件内部指针定位到文件末尾,再使用 ftell() 返回内部指针距离文件开头的字节数,这个返回值就等于文件的大小。请看下面的代码:

long fsize(FILE *fp){    
    fseek(fp, 0, SEEK_END);    
    return ftell(fp);
}

六.总结

相信大家也知道C语言的底层性,相对于其他语言来说,C语言的许多功能需要自定义函数实现,这里我们学习文件操作后,可以自定义封装函数,实现文件复制、插入、删除、更改文件内容等操作。



相关文章
|
11天前
|
存储 程序员 C语言
C语言-文件操作
C语言-文件操作
42 2
|
24天前
|
安全 算法 程序员
【C/C++ 文件操作】深入理解C语言中的文件锁定机制
【C/C++ 文件操作】深入理解C语言中的文件锁定机制
31 0
|
1月前
|
存储 编译器 数据库
【文件操作】C语言
【文件操作】C语言
|
1月前
|
存储 程序员 C语言
【进阶C语言】C语言文件操作
【进阶C语言】C语言文件操作
42 0
|
1月前
|
存储 编译器 C语言
C语言文件操作
C语言文件操作
44 1
|
1月前
|
C语言
C语言---文件操作(1)
C语言---文件操作(1)
25 0
|
2月前
|
存储 编译器 程序员
C语言:文件操作详解
C语言:文件操作详解
|
1月前
|
存储 安全 C语言
在C语言中文件操作
在C语言中文件操作
|
1月前
|
存储 缓存 C语言
C语言从入门到实战——文件操作
C语言中的文件操作是通过使用文件指针来实现的。可以使用标准库中的函数来打开、读取、写入和关闭文件。
53 0
|
29天前
|
C语言
C语言文件操作
C语言文件操作
17 0
C语言文件操作