肖恩带你学C语言·文件操作(下)

来咯来咯,让大家久等了,文件操作完结篇~~~

(●’◡’●)

5. 文件的顺序读写

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

是不是眼花缭乱了啊 哈哈哈
对于这些,我刚开始看到也被吓到了,还是有点多的,其实我们只要实际操作操作就和很容易掌握了,那么下面我就给出例子,实践才是检验真理的唯一标准

附:stderr 是一个预定义的文件指针,它指向标准错误流。stderr 通常用于输出错误消息,而不是正常的程序输出(在上一篇文章讲过肖恩带你学C语言·文件操作(上

1.fputc和fgetc

使用 fputc 写入单个字符

#include <stdio.h>  
int main() {  
    FILE *file = fopen("text_file.txt", "w"); // 打开文件以写入  
    if (file == NULL) {  
        perror("Error opening file for writing");  
        return 1;  
    }  
    char ch = 'A';  
    int result = fputc(ch, file); // 写入单个字符  
    if (result == EOF) {  
        fprintf(stderr, "Error writing to file\n");  
    }  
    fclose(file);  
    return 0;  
}

使用 fgetc 读取单个字符

c
复制代码
	#include <stdio.h>  
	int main() {  
	    FILE *file = fopen("text_file.txt", "r"); // 打开文件以读取  
	    if (file == NULL) {  
	        perror("Error opening file for reading");  
	        return 1;  
	    }  
	    int ch = fgetc(file); // 读取单个字符  
	    if (ch == EOF) {  
	        fprintf(stderr, "Error reading from file or end of file reached\n");  
	    } else {  
	        printf("Read character: %c\n", (char)ch); // 输出读取到的字符  
	    }  
	    fclose(file);  
	    return 0;  
	}

2.fputs和fputs

fgets 和 fputs:读取和写入字符串

#include <stdio.h>  
  
int main() {  
    FILE *file = fopen("example.txt", "w"); // 打开文件以写入  
    if (file == NULL) {  
        perror("Error opening file");  
        return 1;  
    }  
  
    char str[] = "Hello, World!\n";  
    fputs(str, file); // 写入字符串  
  
    fclose(file);  
  
    file = fopen("example.txt", "r"); // 打开文件以读取  
    if (file == NULL) {  
        perror("Error opening file");  
        return 1;  
    }  
  
    char read_str[100];  
    fgets(read_str, sizeof(read_str), file); // 读取字符串  
    printf("%s", read_str); // 输出读取到的字符串  
    fclose(file);  
    return 0;  
}

3.fprintf 和 fscanf

fprintf 和 fscanf:格式化写入和读取文本数据

#include <stdio.h>  
  
int main() {  
    FILE *file = fopen("example.txt", "w"); // 打开文件以写入  
    if (file == NULL) {  
        perror("Error opening file");  
        return 1;  
    }  
  
    int a = 10;  
    float b = 20.5;  
    fprintf(file, "Integer: %d, Float: %.2f\n", a, b); // 写入格式化数据  
  
    fclose(file);  
  
    file = fopen("example.txt", "r"); // 打开文件以读取  
    if (file == NULL) {  
        perror("Error opening file");  
        return 1;  
    }  
  
    int read_a;  
    float read_b;  
    fscanf(file, "Integer: %d, Float: %f", &read_a, &read_b); // 读取格式化数据  
  
    printf("Read Integer: %d, Read Float: %.2f\n", read_a, read_b); // 输出读取到的数据  
  
    fclose(file);  
    return 0;  
}

scanf/fscanf/sscanf,printf/fprintf/sprintf对比

scanf、fscanf和sscanf以及printf、fprintf和sprintf是C语言中用于处理格式化输入输出的重要函数,它们之间的主要区别在于输入/输出的来源和目的地。

scanf、fscanf、sscanf对比

  1. scanf
    来源:从标准输入流(通常是键盘)读取数据。
    用途:用于读取用户通过键盘输入的数据,并按照格式字符串解析数据到对应的变量中。
    示例:scanf("%d %f", &num, &floatVal);
  2. fscanf
    来源:从文件流中读取数据。
    用途:用于从文件中读取数据,并按照格式字符串解析数据到对应的变量中。
    示例:FILE *file = fopen("data.txt", "r"); fscanf(file, "%d %f", &num, &floatVal); fclose(file);
  3. sscanf
    来源:从字符串中读取数据。
    用途:用于从给定的字符串中读取数据,并按照格式字符串解析数据到对应的变量中。
    示例:char str[] = "123 4.56"; sscanf(str, "%d %f", &num, &floatVal);

printf、fprintf、sprintf对比

  1. printf
    目的地:将数据输出到标准输出流(通常是屏幕)。
    用途:用于将格式化的数据输出到屏幕上。
    示例:printf("The number is %d and the float is %f\n", num, floatVal);

  2. fprintf
    目的地:将数据输出到文件流。
    用途:用于将格式化的数据写入到文件中。
    示例:FILE *file = fopen("output.txt", "w"); fprintf(file, "The number is %d and the float is %f\n", num, floatVal); fclose(file);

  3. sprintf
    目的地:将数据输出到字符串。
    用途:用于将格式化的数据写入到字符数组中(字符串)。
    示例:char buffer[100]; sprintf(buffer, "The number is %d and the float is %f\n", num, floatVal);

总结

  • 所有的这些函数都使用格式字符串来指定如何解析或格式化数据。

  • scanf系列函数用于从不同来源读取数据,而printf系列函数用于将数据输出到不同目的地。

  • sscanf和sprintf提供了在字符串与变量之间进行转换的能力,这在处理文本数据或构建字符串时特别有用。

在使用这些函数时,需要确保提供正确格式的格式字符串,以及为需要赋值的变量提供正确的地址(对于scanf系列)。此外,还需要注意缓冲区溢出的问题,特别是在使用sprintf时,应确保目标字符数组足够大,以避免数据溢出。在C99及以后的标准中,可以使用snprintf来避免这个问题,因为它允许指定输出字符串的最大长度。

4.fread 和 fwrite

写入二进制数据

#include <stdio.h>  
  
int main() {  
    FILE *file = fopen("binary_data.bin", "wb"); // 打开文件以二进制写入  
    if (file == NULL) {  
        perror("Error opening file for writing");  
        return 1;  
    }  
  
    int data[] = {1, 2, 3, 4, 5};  
    size_t items = sizeof(data) / sizeof(data[0]);  
    size_t result = fwrite(data, sizeof(int), items, file); // 写入整数数组  
  
    if (result != items) {  
        fprintf(stderr, "Error writing to file\n");  
    }  
  
    fclose(file);  
  
    return 0;  
}

读取二进制数据

#include <stdio.h>  
  
int main() {  
    FILE *file = fopen("binary_data.bin", "rb"); // 打开文件以二进制读取  
    if (file == NULL) {  
        perror("Error opening file for reading");  
        return 1;  
    }  
  
    int read_data[5]; // 假设我们知道要读取的整数数量  
    size_t result = fread(read_data, sizeof(int), 5, file); // 读取整数数组  
  
    if (result != 5) {  
        fprintf(stderr, "Error reading from file\n");  
    } else {  
        for (size_t i = 0; i < result; ++i) {  
            printf("%d ", read_data[i]); // 输出读取到的数据  
        }  
    }  
  
    fclose(file);  
  
    return 0;  
}

(.bin为二进制文件)
注:
在上面的示例中,fwrite 函数用于将整数数组写入一个二进制文件。fread 函数用于从同一个文件中读取相同数量的整数。这两个函数都接受一个指向数据的指针、每个数据项的大小、要读取或写入的项数以及一个 FILE 指针。fwrite 返回实际写入的项数,fread 返回实际读取的项数。

请注意,当使用 fread 和 fwrite 进行二进制读写时,你必须确保读取和写入的数据类型、大小和顺序是一致的,否则可能会导致数据损坏或读取错误。此外,由于这些函数处理的是二进制数据,因此它们不执行任何格式转换或文本编码转换。

还值得注意的是,对于 fread,如果读取的项数少于请求的项数,可能是因为文件末尾被提前到达或者发生了其他错误。因此,检查 fread 的返回值是一个好习惯,以确保正确读取了所需数量的数据项。同样,对于 fwrite,如果写入的项数少于提供的项数,也可能是因为磁盘空间不足或其他错误。

6. 文件的随机读写

1. fseek

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

  • SEEK_SET: 文件开始位置。偏移量是从文件开头计算的。
  • SEEK_CUR: 当前文件位置。偏移量是从当前文件指针位置计算的。
  • SEEK_END: 文件结束位置。偏移量是从文件末尾向前计算的(通常用于从文件末尾回溯一定数量的字节)。
int fseek ( FILE * stream, long int offset, int origin );

例子:

#include <stdio.h>  
  
int main() {  
    FILE *file;  
    char buffer[100];  
    long offset = 50; // 要跳转的字节偏移量  
    int origin = SEEK_SET; // 从文件开始处计算偏移量  
  
    // 打开文件  
    file = fopen("example.txt", "r");  
    if (file == NULL) {  
        perror("Error opening file");  
        return 1;  
    }  
  
    // 使用 fseek 跳转到文件的指定位置  
    if (fseek(file, offset, origin) != 0) {  
        perror("Error seeking in file");  
        fclose(file);  
        return 1;  
    }  
  
    // 从当前位置开始读取文件内容  
    if (fgets(buffer, sizeof(buffer), file) != NULL) {  
        printf("Content after seeking: %s", buffer);  
    } else {  
        printf("Failed to read content after seeking.\n");  
    }  
  
    // 关闭文件  
    fclose(file);  
  
    return 0;  
}

我们打开了一个名为 example.txt 的文件,并使用 fseek 函数将文件位置指针移动到从文件开始处起算的第 50 个字节的位置。然后,我们使用 fgets 函数从该位置开始读取文件内容,并将其存储在 buffer 中。最后,我们打印出读取到的内容,并关闭文件。

请注意,这个例子假设 example.txt 文件至少有 50 个字节的内容,否则 fgets 可能会失败,因为它将从文件的当前位置开始读取,而不是从文件的开头。此外,如果文件以文本模式打开,并且平台使用特定的行结束符(如 Windows 上的 \r\n),则 fseek 的行为可能会受到这些行结束符的影响。

2. ftell

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

long int ftell ( FILE * stream );
#include <stdio.h>  
  
int main() {  
    FILE *file;  
    char buffer[100];  
    long int position;  
  
    // 打开文件  
    file = fopen("example.txt", "r");  
    if (file == NULL) {  
        perror("Error opening file");  
        return 1;  
    }  
  
    // 读取一些内容,移动文件位置指示器  
    if (fgets(buffer, sizeof(buffer), file) != NULL) {  
        printf("Read content: %s", buffer);  
    } else {  
        printf("Failed to read content.\n");  
        fclose(file);  
        return 1;  
    }  
  
    // 使用 ftell 获取当前文件位置指示器的值  
    position = ftell(file);  
    if (position == -1) {  
        perror("Error getting file position");  
        fclose(file);  
        return 1;  
    }  
  
    printf("Current file position: %ld bytes\n", position);  
  
    // 关闭文件  
    fclose(file);  
  
    return 0;  
}

我们首先打开一个名为 example.txt 的文件,并使用 fgets 读取一行内容。读取操作之后,文件位置指示器会移动到读取内容的末尾。接着,我们使用 ftell 函数来获取当前文件位置指示器的值,并将其存储在 position 变量中。最后,我们打印出这个位置值,并关闭文件。

请注意,如果 ftell 函数调用失败(例如,文件没有以二进制模式打开,并且文件位置指示器不在文件的某个有效位置),它会返回 -1,并设置全局变量 errno 以指示错误原因。在实际应用中,你应该检查 ftell 的返回值,并适当处理错误情况。

此外,如果你想要使用 ftell 精确地获取文件中的字节位置,你可能需要以二进制模式打开文件,尤其是在跨平台编程时,因为文本模式和二进制模式在处理换行符时可能会有所不同。在 C 中,你可以通过在打开文件时添加 “b” 标志来以二进制模式打开文件,例如 fopen(“example.txt”, “rb”)。

3. rewind

让文件指针的位置回到文件的起始位置
常用于在你读取或写入文件的一部分之后,想要重新开始读取或写入整个文件时

void rewind ( FILE * stream );
#include <stdio.h>  
  
int main() {  
    FILE *file;  
    char buffer[100];  
  
    // 打开文件  
    file = fopen("example.txt", "r");  
    if (file == NULL) {  
        perror("Error opening file");  
        return 1;  
    }  
  
    // 首次读取文件内容  
    if (fgets(buffer, sizeof(buffer), file) != NULL) {  
        printf("First read: %s", buffer);  
    } else {  
        printf("Failed to read content.\n");  
        fclose(file);  
        return 1;  
    }  
  
    // 使用 rewind 将文件位置指示器重置回文件开头  
    rewind(file);  
  
    // 再次读取文件内容,这次应该从文件开头开始  
    if (fgets(buffer, sizeof(buffer), file) != NULL) {  
        printf("Second read (after rewind): %s", buffer);  
    } else {  
        printf("Failed to read content after rewind.\n");  
    }  
  
    // 关闭文件  
    fclose(file);  
  
    return 0;  
}

我们首先打开一个名为 example.txt 的文件,并使用 fgets 读取一行内容。读取操作之后,文件位置指示器会移动到读取内容的末尾。然后,我们调用 rewind 函数将文件位置指示器重置回文件的开头。之后,我们再次使用 fgets 读取文件内容,这次应该会读取到文件的第一行,因为文件位置指示器已经被重置了。

注意,rewind 函数没有返回值,如果调用成功,文件位置指示器会被重置到文件的开头;如果调用失败(例如文件已经关闭或文件指针无效),则结果是不确定的。因此,在调用 rewind 之前,确保文件已经成功打开且未被关闭是很重要的。

此外,尽管 rewind 函数简单易用,但在某些情况下,使用 fseek(file, 0, SEEK_SET) 来达到同样的效果可能更加明确,因为它明确地指出了将文件位置指示器移动到文件的开头(偏移量为 0,从文件开始处计算)。

7. 文件读取结束的判定

被错误使用的feof

在文件读取过程中,不能用eof函数的返回值直接来判断文件的是否结束。
feof 的作用是: 当文件读取结束的时候,判断是读取结束的原因是否是:遇到文件尾结束。

  1. 文本文件读取是否结束,判断返回值是否为 EOF ( fgetc ),或者 NULL ( fgets )
    例如:
    • fgetc 判断是否为 EOF .
    • fgets 判断返回值是否为 NULL .
  2. 二进制文件的读取结束判断,判断返回值是否小于实际要读的个数。
    例如:
    • fread判断返回值是否小于实际要读的个数。

文本文件的例子:

#include <stdio.h>
#include <stdlib.h>
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);
}

二进制文件的例子:

#include <stdio.h>
enum { SIZE = 5 };
int main(void) {
    double a[SIZE] = {1., 2., 3., 4., 5.};
    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);
}

8. 文件缓冲区*

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

文件缓冲区是一个在计算机中用于暂时存放读写期间的文件数据的内存区域。它的主要目的是减少直接从硬盘读取或写入数据的次数,从而提高数据处理的效率。由于CPU与I/O设备(如硬盘)之间的速度不匹配,缓冲区通过暂时存储数据,并在接收到足够的数据后再进行传输,实现了数据的平稳流动。

在计算机中,数据传输的速度往往不一致,比如输入设备和输出设备的速度可能不同,或者数据的处理速度与传输速度不匹配。这时就需要一个缓冲区来协调它们之间的速度差异。缓冲区通过暂时存储数据,当接收到足够的数据时再进行传输,从而实现了数据的平稳流动。在操作系统中,缓冲区常常用于文件输入输出、网络传输等方面。

缓冲区操作包括创建、写入、读取、刷新和清空等步骤,这些操作可以通过特定的函数或API来实现。例如,在C语言中,可以使用malloc()函数来分配内存作为缓冲区,使用fwrite()将数据写入缓冲区,使用fread()从缓冲区读取数据,使用fflush()将缓冲区的数据刷新到文件,以及使用memset()将缓冲区中的数据重置为0。关闭文件也可以刷新缓冲区。在一般情况下,文件关闭时会自动刷新缓冲区,将缓存的数据写入文件中。这是因为在关闭文件之前,系统会隐含地执行刷新操作,确保缓冲区中的数据被写入文件。

需要注意的是,文件缓冲区是计算机内存的一部分,用于存储临时数据。在编程时,合理地使用和管理缓冲区对于提高程序的性能和稳定性至关重要。同时,也需要注意避免缓冲区溢出等安全问题。

因为有缓冲区的存在,C语言在操作文件的时候,需要做刷新缓冲区或者在文件操作结束的时候关闭文件。
如果不做,可能导致读写文件的问题。

那么关于C语言中的文件操作,到这里我们就讲完啦
如果各位佬喜欢肖恩的文章,记得点赞收藏哦!
请添加图片描述

都看到这了,给个赞再走吧,球球了,创作不易,点赞催更!!!
请添加图片描述
今天依旧是花花~~~
在这里插入图片描述

下期预告
编译和链接
(今天之内更新!因为比较短嘿嘿嘿)

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值