C语言——文件相关操作

2.什么是文件 3.文件的打开和关闭 4.文件的顺序读写 5.文件的随机读写 6.文本文件和二进制文件 7.文件读取结束的判定 8.文件缓冲区 

一、文件相关介绍

1、为什么使用文件

文件用于永久存储数据。通过使用文件,我们可以在程序关闭后保存数据,以便将来可以重新访问和修改。文件是数据存储和交换的重要手段。它们用于日志记录,数据分析,保存用户配置,程序间的数据传输等。

2、什么是文件

文件是存储在某种长期存储设备上的一系列数据的集合,如硬盘、SSD、光盘或USB驱动器。计算机中的文件可以包含文本、图像、音频、视频数据或任何其他形式的信息,并且通常通过文件系统来组织和访问。

在程序设计方面,我们一般讨论两种文件:程序文件和数据文件(从文件功能角度分析)。

1)程序文件:

程序文件包含了可执行代码或者源代码,它们被用来指导计算机进行特定的操作。可执行文件(如.exe在Windows系统中或无扩展名的可执行文件在Unix系统中)是由源代码编译而成,可以直接被操作系统加载到内存中执行。源代码文件(如.c.java.py文件)包含了用高级编程语言书写的指令,它们需要通过编译器或解释器转换为机器语言才能被计算机执行。

2)数据文件:

数据文件用于存储由程序创建或处理的数据。这些数据可能是输入数据,如配置文件、用户输入或外部来源数据;也可能是输出数据,如程序运行结果、日志文件或报告。数据文件通常不包含执行指令,而是存储信息,程序通过读写数据文件来维护状态、记录历史或与其他程序交互。

3)文件名和文件路径

文件名和文件路径是用于标识和定位计算机文件系统中文件的重要元素。

文件名: 文件名是分配给文件的唯一标识符。它允许用户和系统识别和引用文件。文件名通常包括文件主名称和文件扩展名两部分。文件扩展名通常由一个点(.)与主名称分隔,并用于指示文件的类型,例如 .txt 表示文本文件,.jpg 表示JPEG图像文件。操作系统通常会根据文件扩展名来决定如何处理或打开该文件。

文件路径: 文件路径是描述文件在文件系统中位置的字符串。它提供了从根目录或其他起始点到达文件所需遍历的目录(文件夹)序列。文件路径可以是绝对的或相对的。

  • 绝对路径 定义了文件在文件系统中的确切位置,从根目录(在Windows中是驱动器号,如C:\;在UNIX-like系统中是/)开始。例如,C:\Users\Example\Documents\file.txt 是一个绝对路径,它详细描述了如何从C盘根目录开始找到file.txt

  • 相对路径 相对于当前工作目录来定义文件的位置。它不从根目录开始,而是使用.(表示当前目录)或..(表示上级目录)等符号来导航。例如,如果当前工作目录是C:\Users\Example,则相对路径Documents\file.txt引用的是C:\Users\Example\Documents\file.txt

二、文件的相关操作

1、通过什么对文件操作

1)文件指针

缓冲文件操作系统中,关键的概念是“文件类型指针”,简称“文件指针”。
每个被使用的文件都在内存中开辟了一个相应的文件信息区,用来存放文件的相关信息(如文件的名字,文件状态及文件当前的位置等)。这些信息是保存在一个结构体变量中的。该结构体类型是有系统声明的,取名FILE

例如,一个典型的FILE结构体可能包含以下信息(注意,这不是标准定义,仅供参考,不同编译器的实现可能不同,但大同小异):

typedef struct _iobuf {char* _ptr;        // 当前缓冲区的位置指针int   _cnt;        // 缓冲区中剩余的字符数char* _base;       // 指向缓冲区基址的指针int   _flag;       // 文件状态标志int   _file;       // 文件描述符int   _charbuf;    // 一个单独的字符(用于ungetc)int   _bufsiz;     // 缓冲区大小char* _tmpfname;   // 临时文件名
} FILE;

但比较新的编译器已经将这些信息封装了,对于用户来说是不可见的,对FILE使用转到定义可能会看到这样的定义:

typedef struct _iobuf
{void* _Placeholder;
} FILE;

通常情况下,我们只要通过标准的文件操作函数来使用FILE指针,就不需要关心它内部的具体实现细节。

2)创建一个文件指针变量

定义pf是一个指向FILE类型数据的指针变量。可以使pf指向某个文件的文件信息区(是一个结构体变量)。通过该文件信息区中的信息就可以访问到该文件。也就是说,通过文件指针变量可以访问到与它关联的文件

	FILE* pf;

3)文件指针的作用

在打开文件后,会产生一个FILE结构体变量,我们可以通过FILE*指针变量,来维护这个文件信息区,也就是这个结构体变量,这个结构体变量有与文件相关联,所以可以通过这个文件指针来对文件来进行操作。

2、文件操作相关函数

在打开文件后,使用文件,在使用文件完成后,要将文件关闭。在C语言中,打开文件的函数是fopen,关闭文件的函数是fclose。

1)打开文件fopen

在使用fopen函数打开一个文件成功后,fopen函数会返回一个文件指针,通过这个文件指针我们就得到了对文件的引用,就可以对文件进行操作了。该函数声明在 <stdio.h> 头文件中。

函数原型:

函数参数:

filename 参数是一个指向字符串的指针,代表你想要打开的文件名。

mode 参数定义了文件被打开的模式,即文件是用来读取、写入、追加等。

模式字符串包含文件访问模式,可以是:

  • "r" 读取:用于输入操作的打开文件。文件必须存在。
  • "w" 写入:为输出操作创建一个空文件。如果一个同名文件已经存在,它的内容会被丢弃,文件被当作一个新的空文件处理。
  • "a" 追加:在文件末尾打开文件进行输出。输出操作总是在文件的末尾写入数据,扩展文件。文件定位操作(fseek、fsetpos、rewind)会被忽略。如果文件不存在,则创建文件。
  • "r+" 读取/更新:打开文件进行更新(输入和输出均可)。文件必须存在。
  • "w+" 写入/更新:创建一个空文件,并将其打开以进行更新(输入和输出均可)。如果一个同名文件已经存在,它的内容会被丢弃,文件被当作一个新的空文件处理。
  • "a+" 追加/更新:打开文件以进行更新(输入和输出均可),所有输出操作都在文件的末尾写入数据。文件定位操作(fseek、fsetpos、rewind)会影响下一个输入操作,但输出操作会将位置移回文件末尾。如果文件不存在,则创建文件。

以上模式指定文件作为文本文件打开。为了将文件作为二进制文件打开,在模式字符串中必须包含一个“b”字符。这个额外的“b”字符可以附加在字符串的末尾(从而形成以下复合模式:“rb”、“wb”、“ab”、“r+b”、“w+b”、“a+b”),或者插入到字母和“+”号之间的混合模式中(“rb+”、“wb+”、“ab+”)。

C2011新的C标准(不包括C++)增加了一个新的标准子指示符(“x”),可以附加到任何“w”指定符上(形成“wx”、“wbx”、“w+x”或“w+bx”/“wb+x”)。这个子指示符强制函数在文件存在时失败,而不是覆盖它。

如果序列后跟随其他字符,行为取决于库的实现:一些实现可能会忽略额外的字符,所以例如额外的“t”(有时用来明确指出一个文本文件)被接受。

在某些库实现中,以更新模式打开或创建文本文件可能会将流视为二进制文件。

文本文件包含文本行序列。根据应用程序运行的环境,文本模式下的输入/输出操作可能会发生一些特殊字符转换,以适应系统特定的文本文件格式。尽管在某些环境中没有转换发生,文本文件和二进制文件被以同样方式对待,使用适当的模式可以提高可移植性。

对于打开以进行更新的文件(包括一个“+”号),允许进行输入和输出操作,在写操作之后的读操作之前,流应该被刷新(fflush)或重新定位(fseek、fsetpos、rewind)。在读操作之后的写操作之前,如果该操作没有到达文件末尾,流应该被重新定位(fseek、fsetpos、rewind)。

函数返回值:

如果文件成功打开,fopen 函数会返回一个指向 FILE 结构的指针。这个指针后续用于其他的文件操作函数。如果文件打开失败,例如mode为“r”时文件不存在,fopen 会返回 NULL

由于这里的fopen函数可能返回NULL指针,所以要在使用fopen函数之后对返回值进行检查:

	FILE* pf = fopen("test.txt", "w");if (pf == NULL){printf("%s\n", strerror(errno));return EXIT_FAILURE;}//打开成功的操作//...

2)关闭文件fclose

fclose 函数用于关闭一个已打开的文件。该函数也声明在 <stdio.h> 头文件中。

函数原型:

函数参数:

stream 参数是一个指向 FILE 结构的指针,代表了一个已打开的文件流。

函数返回值:

fclose 函数的作用是关闭文件流并释放所有相关资源。如果操作成功,函数返回0;如果失败,则返回EOF

一般在文件打开、操作完成后,都需要调用 fclose 函数来关闭文件。这是个良好的编程习惯,能够防止资源泄露等问题。

使用示例:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>int main()
{FILE* fp = fopen("example.txt", "r");if (fp == NULL){printf("%s\n", strerror(errno));return EXIT_FAILURE;}// 进行各种文件操作...if (fclose(fp) != 0){printf("%s\n", strerror(errno));return EXIT_FAILURE;}fp = NULL;return 0;
}

三、对文件内容的操作

1、fputc

fputc 函数用于将一个字符写入到指定的文件流中。

函数原型:

函数参数:

char 是一个整数,它表示要写入的字符。尽管参数是 int 类型,但实际上只写入这个整数的最低有效字节(即一个字符)。

stream 是目标文件流的指针,之前必须已使用 fopen 等函数打开,并且文件需要以写入模式打开。

这里的文件流包含stdout。你可以使用 fputc 将一个字符写入到标准输出:

int ch = 'A';
fputc(ch, stdout); // 将字符 'A' 写入标准输出,通常是控制台

函数返回值:

fputc 函数返回写入的字符。如果发生错误,则返回 EOF

使用示例:

#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <stdlib.h>int main()
{FILE* pf = fopen("test.txt", "w");if (pf == NULL){printf("%s\n", strerror(errno));return EXIT_FAILURE;}//写入a-zchar ch = 'a';for (ch = 'a'; ch <= 'z'; ch++){fputc(ch, pf);}fclose(pf);pf = NULL;return 0;
}

运行结果:

可以发现a-z已经被写入文件中。

2、fgetc

fgetc 函数用于从指定的文件流中读取下一个字符。

函数原型:

函数参数:

stream 是源文件流的指针,之前必须已使用 fopen 等函数打开,并且文件需要以读取模式打开。

函数返回值:

fgetc 函数返回读取的字符。如果达到文件末尾或发生读取错误,则返回 EOF

使用示例:

如果一个文件中有a-z这26个字母:

#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <stdlib.h>int main()
{FILE* pf = fopen("test.txt", "r");//mode应当是r读取模式if (pf == NULL)//打开失败的情况{printf("%s\n", strerror(errno));return EXIT_FAILURE;}//读取文件内容char ch = 0;while ((ch = fgetc(pf)) != EOF){printf("%c ", ch);}printf("\n");fclose(pf);pf = NULL;return 0;
}

在这段代码中,while 循环会一直执行,直到 fgetc 返回 EOF,即文件结束标志。每次循环迭代,fgetc 都从文件流 pf 读取下一个字符,存储到变量 ch,然后 printf 打印该字符。当读取到文件末尾时,fgetc 返回 EOF,循环终止。

运行结果:

3、fputs

fputs 函数用于把一个字符串写入到指定的文件流中。

函数原型:

函数参数:

str 是一个指向以空字符 '\0' 结尾的字符串的指针。

stream 是目标文件流的指针,之前必须已使用 fopen 等函数打开,并且文件需要以写入模式打开。

函数返回值:

fputs 函数返回一个非负数。如果发生错误,则返回 EOF

使用示例:

#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <stdlib.h>int main()
{FILE* pf = fopen("test.txt", "w");if (pf == NULL)//打开失败的情况{printf("%s\n", strerror(errno));return EXIT_FAILURE;}//向文件中写入内容fputs("Hello World!", pf);fclose(pf);pf = NULL;return 0;
}

运行结果:

4、fgets

fgets 是一个在 C 标准库中提供的函数,它用于从文件流中读取字符串。fgets 函数的原型定义在 <stdio.h> 头文件中,并且它的行为是读取一行或者直到达到缓冲区大小的限制。

函数原型:

函数参数:

str:指向一个元素类型为 char 的数组的首元素的指针,用于接收从文件流读取的字符串。

num:指定要读取的最大字符数,包括最后的空字符(\0)。fgets 会读取不超过 num-1 个字符,以确保总是有空间放置空字符。所以实际上fgets 读取的有效字符个数是 num-1 个。

stream:指向 FILE 对象的指针,代表一个已打开的文件流,该文件流应以读模式打开。

函数返回值:

成功读取:当读取成功时,fgets 返回 str 指针。

文件结束或错误:如果读取到文件末尾或发生错误,将返回 NULL

行为细节:

fgets 会从当前文件流的位置读取字符,直到发生以下情况之一:

  1. 读取了 num-1 个字符。
  2. 读到了换行符(\n),换行符也会被读取并存储到 str 指向的数组里。
  3. 到达了文件末尾。

无论在哪种情况下停止读取,fgets 都会在读取的字符串后添加一个空字符(\0),以确保结果是一个合法的C字符串。

如果在读取任何字符之前就遇到了文件末尾,fgets 将返回 NULL。如果在读取了至少一个字符后到达文件末尾或发生错误,fgets 仍然会返回 str 指针,并且缓冲区中的数据将是直到目前为止读取的字符。

使用示例:

文件中的内容是:

#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <stdlib.h>int main()
{FILE* pf = fopen("test.txt", "r");//mode应当是r读取模式if (pf == NULL)//打开失败的情况{printf("%s\n", strerror(errno));return EXIT_FAILURE;}//向文件中读取内容char str[20];fgets(str, 6, pf);printf("%s\n", str);fclose(pf);pf = NULL;return 0;
}

运行结果:

标准错误报告函数perror

perror 是 C 语言中的标准错误报告函数,定义在 <stdio.h> 头文件中。它用于在标准错误输出 stderr 上打印一个描述性错误消息,通常用于报告系统调用失败的原因。这个错误消息是根据全局变量 errno 的当前值,来查找相应的错误描述。

全局变量 errno 是在 <errno.h> 头文件中定义的,它在发生系统调用和某些库函数调用失败时被设置为一个错误代码。每个错误代码对应着一个特定的错误情况。

函数原型:

函数参数:

string:一个指向以空字符终止的字符串的指针。这个字符串通常包含了发生错误时正在尝试执行的操作的名称,它会被 perror 输出作为错误消息的前缀。如果 string 不是 NULL,并且指向的字符串长度不是零,那么 perror 会在打印操作系统提供的错误消息前先打印这个前缀,后面跟着一个冒号和一个空格。如果 string 是 NULL 或指向的字符串长度为零,perror 只打印错误消息,不包括前缀。

函数行为:

当你调用 perror 时,它会查找 errno 当前值对应的错误描述字符串,并将其输出到标准错误输出 stderr。输出格式通常是:string: error_description,其中 string 是你传入的前缀字符串,而 error_description 是系统提供的错误描述。

使用示例:

#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <stdlib.h>int main()
{FILE* pf = fopen("noexistfile.txt", "r");if (pf == NULL)//打开失败的情况{perror("fopen");return EXIT_FAILURE;}fclose(pf);pf = NULL;return 0;
}

由于在r模式下,文件必须存在,所以这里fopen函数会返回NULL指针。

运行结果:

5、fprintf

fprintf函数用于向文件进行格式化输出。它的原型定义在stdio.h头文件中。

函数原型:

函数参数:

FILE *stream:是目标文件流的指针,之前必须已使用 fopen 等函数打开,并且文件需要以写入模式打开。

const char *format:一个格式化字符串,就像printf使用的那样,它规定了后续参数的输出格式。

...:一个可变数量的参数,这些参数将按照format字符串中的格式被输出到stream指向的文件中。

函数返回值:

fprintf函数会根据format字符串格式化其后的参数,并将结果输出到stream所指向的文件中。如果输出成功,它会返回写入的字符总数,出错则返回一个负数。

与printf比较:

printf函数实际上是fprintf函数的一个特例,其中fprintf函数文件流stream参数被固定为标准输出流stdout就变成了printf

使用示例:

#include <stdio.h>
#include <string.h>
#include <stdlib.h>int main()
{FILE* pf = fopen("test.txt", "w");if (pf == NULL)//打开失败的情况{perror("fopen");return EXIT_FAILURE;}//格式化写入文件内容char* name[3] = { "Bob","Alice","Frank" };for (int i = 1; i <= 3; i++){fprintf(pf, "%03d %s\n", i, name[i - 1]);}fclose(pf);pf = NULL;return 0;
}

运行结果:

6、fscanf

fscanf函数用于从文件中进行格式化输入。它的原型也定义在stdio.h头文件中。

函数原型:

函数参数:

FILE *stream:是目标文件流的指针,之前必须已使用 fopen 等函数打开,并且文件需要以读取模式打开。

const char *format:一个格式化字符串,规定了输入数据需要匹配的格式。

...:一个可变数量的参数,提供了一系列指针,用以存放fscanf从文件中读取并按照format格式化后的数据。

函数返回值:

fscanf函数会尝试从stream所指向的文件中读取内容,并根据format字符串解析内容,将解析出的数据存储在其后的指针指向的变量中。如果成功,函数返回成功匹配并赋值的输入项个数,读取失败或者到达文件结束返回EOF。

与scanf比较:

scanf函数实际上是fscanf函数的一个特例,其中fscanf函数文件流stream参数被固定为标准输出流stdoin就变成了scanf

使用示例:

一个文件内容为:

#include <stdio.h>
#include <string.h>
#include <stdlib.h>typedef struct Stu {int id;char name[10];
}Student;int main()
{FILE* pf = fopen("test.txt", "r");if (pf == NULL)//打开失败的情况{perror("fopen");return EXIT_FAILURE;}//格式化读取文件内容Student s = { 0,0 };for (int i = 1; i <= 3; i++){fscanf(pf, "%03d %s", &(s.id), s.name);printf("%03d %s\n", s.id, s.name);}fclose(pf);pf = NULL;return 0;
}

运行结果:

7、fwrite

fwrite 函数用于向文件流中写入数据块。

函数原型:

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

函数参数:

const void *ptr:指向要写入文件的数据块的指针。

size_t size:每个元素的大小,以字节为单位。

size_t nmemb:要写入的元素数量。

FILE *stream:指向FILE对象的指针,代表一个打开的文件流。

函数返回值:

fwrite 会从 ptr 指向的内存地址开始,向 stream 指向的文件写入 nmemb 个数据块,每个块的大小为 size 字节。函数返回成功写入的元素数量。如果返回的元素个数小于请求的数量,可能是因为发生了错误。

使用示例:

#include <stdio.h>
#include <string.h>
#include <stdlib.h>typedef struct Stu {int id;char name[10];
}Student;int main()
{FILE* pf = fopen("test.bin", "wb");if (pf == NULL)//打开失败的情况{perror("fopen");return EXIT_FAILURE;}//二进制写入文件Student student[2] = { {1,"Bob"},{2,"Alice"} };fwrite(student, sizeof(Student), 2, pf);fclose(pf);pf = NULL;return 0;
}

运行结果:

由于是使用二进制形式写入的,我们使用二进制编辑器打开文件查看:

8、fread

fread 函数用于从文件流中读取数据块。

函数原型:

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

函数参数:

void *ptr:指向一个内存块的指针,从文件读取的数据将被存储在这个内存块中。

size_t size:每个元素的大小,以字节为单位。

size_t nmemb:要读取的元素数量。

FILE *stream:指向FILE对象的指针,代表一个打开的文件流。

函数返回值:

fread 会尝试从 stream 指向的文件中读取 nmemb 个数据块,每个块的大小为 size 字节,保存到 ptr 指向的内存地址。函数返回成功读取的元素数量。如果返回的元素个数小于请求的数量,可能是遇到了文件结尾或发生了错误。

使用示例:

一个二进制文件的内容是:

#include <stdio.h>
#include <string.h>
#include <stdlib.h>typedef struct Stu {int id;char name[10];
}Student;int main()
{FILE* pf = fopen("test.bin", "rb");if (pf == NULL)//打开失败的情况{perror("fopen");return EXIT_FAILURE;}//二进制读取文件内容Student student[2] = { 0 };fread(student, sizeof(Student), 2, pf);for (int i = 0; i < 2; i++){printf("%03d %s\n", student[i].id, student[i].name);}fclose(pf);pf = NULL;return 0;
}

运行结果:

stdin、stdout和stderr

在前面我们提到了 stdin 和 stdout 也是文件流,对于 scanf 和 printf 就是使用 stdin 和 stdout 文件流,那为什么我们在使用 scanf 和 printf 时不需要打开这两个流呢(就像我们上面使用文件时要打开文件才能使用)?

实际上对于一个标准的C程序,在运行时会自动打开三个预定义的流,它们分别是标准输入流(stdin)、标准输出流(stdout)和标准错误流(stderr)。这三个流是在程序启动时由运行时环境自动打开的,因此程序员可以直接使用它们进行输入和输出操作,而无需手动开启。

  • stdin 是标准输入流,通常代表键盘输入或其他程序的输出。
  • stdout 是标准输出流,通常代表屏幕输出。
  • stderr 是标准错误流,它用于输出错误信息,即使标准输出被重定向到一个文件或者其他地方,标准错误通常也会显示在屏幕上。

在C语言中,这三个流被定义为 FILE 类型的指针,它们分别定义在 <stdio.h> 头文件中。

总结:

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

printf、fprintf、sprintf

1)sprintf介绍:

sprintf 函数用于将格式化的数据写入一个字符串中。它根据指定的格式将若干个变量的内容组合、格式化后,存放在第一个参数指定的字符串数组中。

函数原型:

函数参数:

buffer: 要存储输出结果的字符串数组。

format: 格式化字符串,指定输出的格式。

...: 可变参数列表,包含了需要格式化的数据。

函数返回值:

返回写入数组的字符数(不包括终止的 null 字符)。

使用示例:

#include <stdio.h>
#include <string.h>
#include <stdlib.h>typedef struct Stu {int id;char name[10];
}Student;int main()
{Student student[3] = { {1,"Bob"},{2,"Alice"},{3,"Frank"} };char buffer[20] = { 0 };//将数据格式化写入buffer中for(int i = 0;i < 3;i++){sprintf(buffer, "%03d %s\n", student[i].id, student[i].name);//打印buffer中的字符串printf("%s", buffer);}return 0;
}

运行结果:

函数用途:

sprintf 函数的功能是将格式化的数据写入到一个字符串缓冲区中,这在嵌入式编程中非常有用,因为它允许开发者创建具有特定格式的消息,然后可以将这些消息通过串口发送给其他设备,或者用于显示在屏幕上,或者记录到日志中。

例如,如果需要将温度读数和时间戳发送给电脑或其他设备,你可能会使用sprintf来将这些信息格式化为一个字符串,然后通过串口发送。

char message[50];
int temperature = 25;
unsigned long timestamp = 1714656717;sprintf(message, "Temperature: %dC Time: %lu", temperature, timestamp);
// 现在 message 包含了 "Temperature: 25C Time: 1714656717"

然而,需要注意的是,在嵌入式系统中使用 sprintf 或任何其他格式化函数时,必须确保目标缓冲区足够大,以避免溢出,这是一个常见的安全问题。此外,对于资源受限的嵌入式系统,snprintf 函数可能是一个更安全的选择,因为它允许指定缓冲区的最大大小,从而减少溢出的风险。

2)对比三者:

  • printf 是针对标准输出流的格式化输出函数。
  • fprintf 是针对所有输出流的格式化输出函数。
  • sprintf 用于将格式化的数据写入一个字符串中。

scanf、fscanf、sscanf

1)sscanf介绍:

sscanf 函数用于从一个字符串中读取数据,根据指定的格式解析字符串,将数据存储在其余参数中提供的位置。

函数原型:

函数参数:

buffer: 包含了要解析的数据的字符串。

format: 格式化字符串,指定输入数据应当如何被解析。

...: 可变参数列表,包括指向存储数据的变量的指针。

函数返回值:

返回成功匹配并赋值的输入项数目。

使用示例:

#include <stdio.h>
#include <string.h>
#include <stdlib.h>typedef struct Stu {int id;char name[10];
}Student;int main()
{char buffer[20] = "1 Bob";Student student = { 0 };//将buffer中的数据格式化读取到提供的参数中sscanf(buffer, "%d %s", &(student.id), student.name);printf("%03d %s\n", student.id, student.name);return 0;
}

运行结果:

2)对比三者:

  • scanf 是针对标准输入流的格式化输入函数。
  • fscanf 是针对所有输入流的格式化输入函数。
  • sscanf 用于将字符串解析成格式化的数据。

四、对文件的位置指针的操作(对文件的随机访问)

在以 r 或 w 等模式打开文件时,文件位置指针默认在文件最开头,在每次进行文件读取或写入后,会更新到最后读取或写入的位置后面。

#include <stdio.h>
#include <string.h>
#include <stdlib.h>int main()
{FILE* pf = fopen("test.txt", "r");if (pf == NULL){perror("fopen");return EXIT_FAILURE;}char ch = 0;ch = fgetc(pf);printf("%c\n", ch);ch = fgetc(pf);printf("%c\n", ch);fclose(pf);pf = NULL;return 0;
}

运行结果:

具体图解:

如果需要在文件中移动到不同的位置进行读写,我们就需要使用 fseek, ftellrewind 这些函数来手动操作文件位置指针。

1、ftell

ftell 函数用于获取当前在文件流中的读写位置。

函数原型:

函数参数:

stream:指向 FILE 对象的指针,该对象代表一个打开的文件流。

函数返回值:

成功时,返回当前的位置,相对于文件开头的字节偏移。失败时,返回 -1L 并设置错误标志。

使用示例:

使用上面的例子,

这里的位置是2,相对于开头的位置是2。

#include <stdio.h>
#include <string.h>
#include <stdlib.h>int main()
{FILE* pf = fopen("test.txt", "r");if (pf == NULL){perror("fopen");return EXIT_FAILURE;}char ch = 0;ch = fgetc(pf);printf("%c\n", ch);ch = fgetc(pf);printf("%c\n", ch);long position = ftell(pf);if (position == -1){perror("ftell");fclose(pf);return EXIT_FAILURE;}printf("Current position after reading: %ld\n", position);fclose(pf);pf = NULL;return 0;
}

运行结果:

2、fseek

fseek 函数用于移动文件流的读写位置到给定的位置。

函数原型:

函数参数:

stream:指向 FILE 对象的指针,该 FILE 对象标识了流。

offset:相对于 origin 的偏移量,以字节为单位。

origin:这是一个起始点,决定从文件的哪里开始偏移。它通常有三个值:

  • SEEK_SET:文件的开头。
  • SEEK_CUR:当前的读写位置。
  • SEEK_END:文件的末尾。
#define SEEK_CUR    1
#define SEEK_END    2
#define SEEK_SET    0

对于以二进制模式打开的流,新位置由 origin 位置加上偏移量(offset)来定义。

在文本模式下打开文件时,移植性问题变得重要。因为文本模式下的文件可能会进行换行符的转换(如在Windows系统中,\n可能会被转换成\r\n),所以随机访问就变得复杂。
如果流以文本模式打开,偏移量(offset)支持的值是零(适用于任何起点 origin SEEK_SETSEEK_CURSEEK_END)或者早先通过对同一文件关联的流调用ftell得到的返回值(只适用于SEEK_SET起点)。

如果此函数调用时使用了其他值作为这些参数,其支持依赖于特定的系统和库实现(可能没有报错或警告,但不具移植性)。

函数返回值:

成功时返回 0。失败时返回非零值,并设置错误标志。

使用示例:

#include <stdio.h>
#include <string.h>
#include <stdlib.h>int main()
{//打开文件FILE* pf = fopen("test.txt", "r");if (pf == NULL){perror("fopen");return EXIT_FAILURE;}//读取文件内容char ch = 0;ch = fgetc(pf);printf("%c\n", ch);ch = fgetc(pf);printf("%c\n", ch);//保存当前的读取位置long position = ftell(pf);if (position == -1){perror("ftell");fclose(pf);return EXIT_FAILURE;}//关闭文件fclose(pf);pf = NULL;//再次打开文件pf = fopen("test.txt", "r");if (pf == NULL){perror("fopen");return EXIT_FAILURE;}//使用fseek定位到上次访问的位置if(fseek(pf,position,SEEK_SET) != 0){perror("fseek");fclose(pf);return EXIT_FAILURE;}//接着上次的位置继续读取文件内容ch = fgetc(pf);printf("%c\n", ch);ch = fgetc(pf);printf("%c\n", ch);//关闭文件fclose(pf);pf = NULL;return 0;
}

运行结果:

3、rewind

rewind 函数用于将文件流的位置指针重置到文件的开头,它相当于 fseek(stream, 0, SEEK_SET),但不返回值,并且清除错误标志。

“rewind” 意为“倒带”,就是指使影像制品的进程倒退到自己需要的区域的过程,一般是将磁带上的数据回滚至开始的位置。在计算机领域,"rewind" 一词被借用来描述一个类似的过程,即将文件的读写指针移回到文件的起始位置。

函数原型:

函数参数:

stream:指向 FILE 对象的指针,标识需要操作的流。

使用示例:

一个文件的内容是:

#include <stdio.h>
#include <string.h>
#include <stdlib.h>int main()
{FILE* pf = fopen("test.txt", "r");if (pf == NULL){perror("fopen");return EXIT_FAILURE;}//读取数据char ch = 0;for (int i = 0; i < 6; i++){ch = fgetc(pf);printf("%c ", ch);}printf("\n");//将位置重置为开头rewind(pf);//再次读取数据for (int i = 0; i < 6; i++){ch = fgetc(pf);printf("%c ", ch);}printf("\n");fclose(pf);pf = NULL;return 0;
}

运行结果:

五、通讯录改进

通过对文件的操作来将通讯录改进成为在程序退出后可以保存通讯录的内容。

contacts_list.c/cpp

#include "contacts_list.h"void menu()
{printf("*************************************\n");printf("*****  1.add            2.del    ****\n");printf("*****  3.search         4.modify ****\n");printf("*****  5.display        6.sort   ****\n");printf("*****  0.exit                    ****\n");printf("*************************************\n");printf("please choose a option>:");
}int InitContact(Contacts_list* list)
{list->capacity = DEFUALT_CONTACT;//容量设为DEFUALT_CONTACTlist->num = 0;//联系人个数设为0list->dataPtr = (PeopleInfo*)malloc(DEFUALT_CONTACT * sizeof(PeopleInfo));//开辟DEFUALT_CONTACT个联系人大小的内存块if (list->dataPtr == NULL)//分配失败{printf("InitContact:%s\n", strerror(errno));//抛出错误信息return 1;}//分配成功return 0;
}int IncreaseCapacity(Contacts_list* list, int number)
{PeopleInfo* temp = (PeopleInfo*)realloc(list->dataPtr, (list->capacity + number) * sizeof(PeopleInfo));if (temp == NULL)//分配失败{printf("IncreaseCapacity:%s\n", strerror(errno));//抛出错误信息return 1;}else//分配成功{list->dataPtr = temp;//更新内存块的指针temp = NULL;//将临时指针置空list->capacity += number;//更新容量}return 0;
}void AddContact(Contacts_list* list)
{//增容if (list->num == list->capacity){IncreaseCapacity(list, DEFUALT_ALLOC_CONTACT);//默认增容DEFUALT_ALLOC_CONTACT大小}//录入信息printf("please type in name(maximum 20)>:");scanf("%s", list->dataPtr[list->num].name);printf("please type in gender(maximum 8)>:");scanf("%s", list->dataPtr[list->num].gender);printf("please type in age>:");scanf("%d", &(list->dataPtr[list->num].age));list->num++;printf("AddContact successfully\n");
}void DisplayContact(const Contacts_list* list)
{int i = 0;printf("------------------------------------------------\n");printf("%-20s%-8s%-s\n", "name", "gender", "age");for (i = 0; i < list->num; i++){printf("%-20s%-8s%d\n", list->dataPtr[i].name, list->dataPtr[i].gender, list->dataPtr[i].age);}printf("------------------------------------------------\n");printf("display successfully\n");
}void DestroyContact(Contacts_list* list)
{free(list->dataPtr);//释放开辟的空间list->dataPtr = NULL;//将指向内存块指针置空
}void DeleteContact(Contacts_list* list)
{char name[21];if (list->num == 0){printf("there is no contact can be deleted\n");//联系人为零,无需删除return;}printf("please type in the name who you want to delete>:");scanf("%s", name);int res = SearchContact(list, name);if (res != -1){int i = 0;for (i = res; i < list->num - 1; i++){list->dataPtr[i] = list->dataPtr[i + 1];}list->num--;printf("delete successfully\n");}else{printf("cannot find\n");//没找到要删除的联系人return;}
}int SearchContact(Contacts_list* list, const char* string)
{int i = 0;for (i = 0; i < list->num; i++){if (strcmp(list->dataPtr[i].name, string) == 0){return i;//查找到返回下标}}return -1;//未查找到返回-1
}void ModifyContact(Contacts_list* list, const char* string)
{int res = SearchContact(list, string);if (res == -1){printf("cannot find\n");return;}printf("please type in name(maximum 20)>:");scanf("%s", list->dataPtr[res].name);printf("please type in gender(maximum 8)>:");scanf("%s", list->dataPtr[res].gender);printf("please type in age>:");scanf("%d", &(list->dataPtr[res].age));printf("modification successfully\n");
}int cmp_by_name(const void* element1, const void* element2)
{PeopleInfo* ele1 = (PeopleInfo*)element1;//强制转换为联系人结构体类型,方便访问name变量PeopleInfo* ele2 = (PeopleInfo*)element2;return strcmp(ele1->name, ele2->name);
}int cmp_by_age(const void* element1, const void* element2)
{int res = ((PeopleInfo*)element1)->age - ((PeopleInfo*)element2)->age;return ((-res < 0) - (res < 0));
}void SortContact(Contacts_list* list, int num)
{switch (num){case 1:qsort(list->dataPtr, list->num, sizeof(PeopleInfo), cmp_by_name);printf("sort successfully\n");break;case 2:qsort(list->dataPtr, list->num, sizeof(PeopleInfo), cmp_by_age);printf("sort successfully\n");break;default:printf("error\n");break;}
}void update_contact_file(Contacts_list* list)
{FILE* contact_file = fopen("contact_data.txt", "w");if (contact_file == NULL){perror("FUNC(update_contact_file)");return;}for(int i = 0;i < list->num;i++){fprintf(contact_file, "%s %s %d\n", list->dataPtr[i].name, list->dataPtr[i].gender, list->dataPtr[i].age);}fclose(contact_file);contact_file = NULL;
}void load_contact_from_file(Contacts_list* list)
{FILE* contact_file = fopen("contact_data.txt", "r");if (contact_file == NULL){perror("FUNC(load_contact_from_file)");return;}int i = 0;while (fscanf(contact_file, "%s %s %d", list->dataPtr[i].name, list->dataPtr[i].gender, &(list->dataPtr[i].age)) != EOF){list->num++;if (list->num == list->capacity){IncreaseCapacity(list, DEFUALT_ALLOC_CONTACT);}i++;}fclose(contact_file);contact_file = NULL;
}

contacts_list.h

#pragma once
#include <stdio.h>
#include <stdlib.h>
#include <string.h>//默认联系人个数
#define DEFUALT_CONTACT 3
//联系人空间不够时,默认分配空间个数
#define DEFUALT_ALLOC_CONTACT 3//打印菜单函数
void menu();//声明联系人信息结构体变量
typedef struct PeopleInfo
{char name[21];char gender[9];int age;
}PeopleInfo;//声明通讯录结构体变量
typedef struct Contacts_list
{PeopleInfo* dataPtr;//存放联系人数据,指向内存块的指针int num;//记录通讯录联系人个数int capacity;//记录通讯录容量
}Contacts_list;//初始化函数,默认初始化DEFUALT_CONTACT个联系人的空间,分配成功返回0,失败返回1
int InitContact(Contacts_list* list);//增容函数,增加number个大小,成功返回0,失败返回1
int IncreaseCapacity(Contacts_list* list, int number);//添加联系人,如果空间不足,默认再开辟DEFUALT_ALLOC_CONTACT个联系人的空间,分配成功返回0,失败返回1
void AddContact(Contacts_list* list);//摧毁通讯录,释放开辟的空间
void DestroyContact(Contacts_list* list);//显示通讯录
void DisplayContact(const Contacts_list* list);//删除联系人
void DeleteContact(Contacts_list* list);//查找联系人
int SearchContact(Contacts_list* list, const char* string);//修改联系人
void ModifyContact(Contacts_list* list, const char* string);//排序联系人
void SortContact(Contacts_list* list, int num);//按名字排序
int cmp_by_name(const void* element1, const void* element2);//按年龄排序
int cmp_by_age(const void* element1, const void* element2);//更新通讯录文件内容
void update_contact_file(Contacts_list* list);//从通讯录文件中读取数据
void load_contact_from_file(Contacts_list* list);

test.c/cpp

#include "contacts_list.h"int main()
{int status = 0;Contacts_list contacts_list;//通讯录结构体变量if (InitContact(&contacts_list))//初始化失败会返回1,如果初始化失败则程序以1返回{printf("Initialization Error\n");return 1;}load_contact_from_file(&contacts_list);//从文件中加载通讯录数据int res = 0;int num = 0;do{menu();//打印菜单scanf("%d", &status);system("cls");switch (status){case 1:AddContact(&contacts_list);break;case 2:DeleteContact(&contacts_list);break;case 3:char name1[21];printf("please type in the name>:");scanf("%s", name1);res = SearchContact(&contacts_list, name1);if (res != -1){printf("%d\n", res);}else{printf("cannot find\n");}break;case 4:char name2[21];printf("please type in the name>:");scanf("%s", name2);ModifyContact(&contacts_list, name2);break;case 5:DisplayContact(&contacts_list);break;case 6:printf("1.sort by name\n");printf("2.sort by age\n");scanf("%d", &num);SortContact(&contacts_list, num);break;case 0:update_contact_file(&contacts_list);//将通讯录数据保存到文件中DestroyContact(&contacts_list);//释放空间,摧毁通讯录printf("exit\n");break;default:printf("error\n");break;}} while (status);return 0;
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/web/6354.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

手机恢复出厂设置ip地址会变吗

当我们对手机进行恢复出厂设置时&#xff0c;很多人会担心手机的IP地址是否会发生变化。IP地址对于手机的网络连接至关重要&#xff0c;它决定了手机在网络中的身份和位置。那么&#xff0c;手机恢复出厂设置后&#xff0c;IP地址到底会不会发生变化呢&#xff1f;虎观代理小二…

AI-数学-高中53-离散型随机变量的均值与方差

原作者视频&#xff1a;【随机变量】【一数辞典】3离散型随机变量的均值与方差_哔哩哔哩_bilibili 标准差 方差开根

加州大学欧文分校英语中级语法专项课程03:Tricky English Grammar 学习笔记

Tricky English Grammar Course Certificate Course Intro 本文是学习 https://www.coursera.org/learn/tricky-english-grammar?specializationintermediate-grammar 这门课的学习笔记 文章目录 Tricky English GrammarWeek 01: Nouns, Articles, and QuantifiersLearning …

17、ESP32 SPI

SPI 概述 SPI&#xff08;Serial Peripheral Interface&#xff09;协议&#xff0c;即串行外围设备接口&#xff0c;采用一主多从的全双工通信总线。被广泛使用在要求通讯速率较高的场合。 SPI 通信需要四条信号线&#xff1a; MISO&#xff1a; 主器件数据输入&#xff0c;从…

基于SSM SpringBoot vue教务排课系统

基于SSM SpringBoot vue教务排课系统 系统功能 登录 个人中心 学生信息管理 教师信息管理 课室信息管理 班级信息管理 系别信息管理 专业信息管理 课程信息管理 选课信息管理 课表信息管理 开发环境和技术 开发语言&#xff1a;Java 使用框架: SSM(Spring SpringMVC Myba…

【网络原理】UDP协议 | UDP报文格式 | 校验和 | UDP的特点 | 应用层的自定义格式

文章目录 一、UDP协议1.UDP的传输流程发送方接收方 2.UDP协议报文格式&#xff1a;长度受限校验和如何校验&#xff1a;CRC算法&#xff1a;循环冗余算法md5算法&#xff1a; 2.UDP的特点 二、开发中常见的自定义格式1.xml&#xff08;古老&#xff09;2.json&#xff08;最流行…

力扣763. 划分字母区间

Problem: 763. 划分字母区间 文章目录 题目描述思路复杂度Code 题目描述 思路 1.创建一个名为 last 的数组&#xff0c;用于存储每个字母在字符串 s 中最后出现的位置。然后&#xff0c;获取字符串 s 的长度 len。 2.计算每个字母的最后位置&#xff1a;遍历字符串 s&#xff0…

Unity开发微信小游戏(2)分享

目录 1.概述 2.代码 3.示例 4.个人作品 1.概述 这里我们能做有两件事&#xff1a; 1&#xff09;主动发起分享 2&#xff09;监听右上角分享&#xff08;...按钮&#xff0c;发朋友圈也在这里&#xff09; API&#xff1a;官方文档 2.代码 1&#xff09;主动发起分享&…

在家连学校的服务器

在家连接学校的服务器。 Step1: 首先下载一个vscode的插件 Visual Studio Code - Code Editing. Redefined 我的服务区是ubuntu20.04&#xff0c;x64的&#xff0c;所以下载这个。 Step2: 下载到本地之后&#xff0c;想办法将这个文件拷贝到你的服务器上。 Step3: 解压该包…

U盘到底要格式化成什么格式比较好?

前言 前段时间有小伙伴问我&#xff1a;U盘为啥无法粘贴超过4GB的压缩包。 相信这个问题很多人都会遇到&#xff0c;无论是压缩包、镜像文件还是电影&#xff0c;都会有超过4GB的时候。 如果文件超过了4GB&#xff0c;那么就会小伙伴遇到电脑提示&#xff1a;无法粘贴超过4G…

基于Springboot的校园食堂订餐系统(有报告)。Javaee项目,springboot项目。

演示视频&#xff1a; 基于Springboot的校园食堂订餐系统&#xff08;有报告&#xff09;。Javaee项目&#xff0c;springboot项目。 项目介绍&#xff1a; 采用M&#xff08;model&#xff09;V&#xff08;view&#xff09;C&#xff08;controller&#xff09;三层体系结构…

R语言数据探索和分析7-使用随机森林模型对中国GDP及其影响因素分析

一、研究背景和意义 国内生产总值&#xff08;GDP&#xff09;是宏观经济领域中最为关注的经济统计数据之一&#xff0c;它反映了一个国家或地区在一定时期内所创造的所有最终商品和服务的总价值。GDP的增长率不仅仅是一个国家经济健康状况的关键指标&#xff0c;还直接关系到…

服务器数据恢复—异常断电导致RAID模块故障的数据恢复案例

服务器数据恢复环境&#xff1a; 某品牌ProLiant DL380系列服务器&#xff0c;服务器中有一组由6块SAS硬盘组建的RAID5阵列&#xff0c;WINDOWS SERVER操作系统&#xff0c;作为企业内部文件服务器使用。 服务器故障&#xff1a; 机房供电几次意外中断&#xff0c;服务器出现故…

AI代理架构的发展:从单一到多代理系统的演进及其影响分析

每周跟踪AI热点新闻动向和震撼发展 想要探索生成式人工智能的前沿进展吗&#xff1f;订阅我们的简报&#xff0c;深入解析最新的技术突破、实际应用案例和未来的趋势。与全球数同行一同&#xff0c;从行业内部的深度分析和实用指南中受益。不要错过这个机会&#xff0c;成为AI领…

数据结构与算法---线性表

线性表 1.顺序表 需求分析 /*创建顺序表具体功能&#xff1a;初始化顺序表销毁顺序表获取顺序表元素个数输出顺序表中的内容自动扩容增 --- 插入数据&#xff08;包含了尾部添加功能&#xff09;删 --- 删除数据&#xff08;包含了尾部删除功能&#xff09;改 --- 修改数据查…

Android使用kts上传aar到JitPack仓库

Android使用kts上传aar到JitPack 之前做过sdk开发&#xff0c;需要将仓库上传到maven、JitPack或JCenter,但是JCenter已停止维护&#xff0c;本文是讲解上传到JitPack的方式,使用KTS语法&#xff0c;记录使用过程中遇到的一些坑. 1.创建项目(library方式) 由于之前用鸿神的w…

外网禅道配置

exportfs -avrf 修改代码&#xff0c;避免启动太慢&#xff1a;vi /opt/zbox/bin/zbox.php 启动和停止 /opt/zbox/zbox start /opt/zbox/zbox stop

YOLOv5手势物体识别(附代码)

之前是做的yolov3手势物体识别&#xff0c;最近几天我将该项目进行了重新的整理和升级&#xff0c;实现了yolov5手势物体识别&#xff0c;同时为了方便更多的人直接拿来应用&#xff0c;我生成了支持windows系统的应用小程序&#xff0c;即便你电脑上没有安装pytorch,没有安装c…

4 Spring AOP

目录 AOP 简介 传统开发模式 先来看一个需求 解决方案 AOP 图示 Spring 启用 AspectJ 基于 xml 配置 创建 pom.xml 创建 UserService 借口和 UserServiceImpl实现类 创建 LogAdvice 日志通知 创建 log4j.properties 重点&#xff1a;创建 spring-context-xml.xml 配…

MYSQL从入门到精通(二)

1、MYSQL高级概述 【1】架构概述 【2】索引优化 【3】查询截取 【4】mysql锁机制 【5】主从复制 2、MYSQL概述 【1】mysql内核 【2】sql优化工程师 【3】mysql服务器的优化 【4】各种参数常量设定 【5】查询语句优化 【6】主从复制 【7】软硬件升级 【8】容灾百分 【9】sql编…