【C语言】有关“文件操作”的总结

	按照c语言学习的顺序,我总结了有关文件操作的函数和技巧,在此篇博客中我列写了一些主
要的文件操作函数及其相关技巧,以及文件相关的概念和定义,与此同时,还有对部分函数的简单
使用。

文章目录

      • 一、文件指针
      • 二、fopen()
      • 三、fclose()
      • 四、fprintf()
      • 五、fscanf()
      • 六、fgetc(),getc()
      • 七、fputc(),putc()
      • 八、fgets()
      • 九、fputs()
      • 十、fwrite()
      • 十一、fread()
      • 十二、feof()
      • 十三、fseek()
      • 十四、ftell()
      • 十五、rewind()
      • 十六、ferror()
      • 十七、clearerr()

一、文件指针

FILE *fp; 定义了一个文件指针fp

C 语言提供了一个 FILE 类型的数据结构,定义在头文件stdio.h中,所有文件操作函数都要通过这个数据结构,获取文件信息。

开始操作一个文件之前,就要定义一个指向该文件的 FILE 指针,相当于获取一块内存区域,用来保存文件信息。

二、fopen()

功能:使用指定模式打开并操作文件,它名字的第一个字符f,就代表file

函数原型:FILE *fopen(char *filename, char *mode);

参数:第一个参数是文件名(可以包含路径);第二个参数是模式字符串,指定对文件执行的操作

返回值:返回一个 FILE 类型指针,其他函数可以用这个指针操作文件。如果无法打开文件(比如文件不存在或没有权限),会返回空指针 NULL

fp = fopen("hello.txt", "r");
if (fp == NULL) {
  printf("Can't open file!\n");
  exit(EXIT_FAILURE);
}
模式 描述
r 读模式,只用来读取数据。如果文件不存在,返回 NULL 指针。
w 写模式,只用来写入数据。如果文件存在,文件长度会被截为0,然后再写入;如果文件不存在,则创建该文件。
a 写模式,只用来在文件尾部追加数据。如果文件不存在,则创建该文件。
r+ 读写模式。如果文件存在,指针指向文件开始处,可以在文件头部添加数据。如果文件不存在,返回 NULL 指针。
w+ 读写模式。如果文件存在,文件长度会被截为0,然后再写入数据。这种模式实际上读不到数据,反而会擦除数据。如果文件不存在,则创建该文件。
a+ 读写模式。如果文件存在,指针指向文件结尾,可以在现有文件末尾添加内容。如果文件不存在,则创建该文件。

比较一下,以上的模式有+相比之前产生了什么变化?

fopen()函数会为打开的文件创建一个缓冲区。C 语言通过缓存区,以流的形式,向文件读写数据。

  • 读模式下,创建的是读缓存区;
  • 写模式下,创建的是写缓存区;
  • 读写模式下,会同时创建两个缓冲区。

数据在文件里面,都是以二进制形式存储

  • 以原本的二进制形式解读,叫做“二进制流”;
  • 将二进制数据转成文本,以文本形式解读,叫做“文本流”。
  • 写入操作也是如此,分成以二进制写入和以文本写入,后者会多一个文本转二进制的步骤。

模式字符串的后缀有b和x

  • 以上的模式字符串**,默认是以文本流读写。如果添加b后缀(表示 binary),就会以“二进制流”进行读写。**比如,rb是读取二进制数据模式,wb是写入二进制数据模式。

  • 模式字符串还有一个**x后缀**,表示独占模式(exclusive)。**如果文件已经存在,则打开文件失败;如果文件不存在,则新建文件,打开后不再允许其他程序或线程访问当前文件。**比如,wx表示以独占模式写入文件,如果文件已经存在,就会打开失败

  • 文本文件:每个字符都使用字符编码(如ASCII或Unicode)进行表示的文件

  • 二进制文件:使用01机器码表示的文件

  • 标准流

    Linux 系统默认提供三个已经打开的文件,它们的文件指针如下。

    • stdin(标准输入):默认来源为键盘,文件指针编号0
    • stdout(标准输出):默认目的地为显示器,文件指针编号1
    • stderr(标准错误):默认目的地为显示器,文件指针编号2

    Linux 系统的文件,不一定是数据文件,也可以是设备文件,即文件代表一个可以读或写的设备。文件指针stdin默认是把键盘看作一个文件,读取这个文件,就能获取用户的键盘输入。同理,stdoutstderr默认是把显示器看作一个文件,将程序的运行结果写入这个文件,用户就能看到运行结果了。它们的区别是,stdout写入的是程序的正常运行结果,stderr写入的是程序的报错信息。

三、fclose()

功能:关闭 已接受文件指针 指向的文件

函数原型:int fclose(FILE *stream);

参数:文件指针

返回值:如果成功关闭文件,返回整数0;若操作失败(比如磁盘已满,或者出现 I/O 错误),则返回一个特殊值 EOF(一般为-1)。

  • 有关EOF

    C 语言的文件操作函数的设计是,如果遇到文件结尾,就返回一个特殊值。程序接收到这个特殊值,就知道已经到达文件结尾了。

    头文件stdio.h为这个特殊值定义了一个**宏EOF(end of file 的缩写),它的值一般是-1。**这是因为从文件读取的二进制值,不管作为无符号数字解释,还是作为 ASCII 码解释,都不可能是负值,所以可以很安全地返回-1,不会跟文件本身的数据相冲突。

    需要注意的是,不像字符串结尾真的存储了\0这个值,EOF并不存储在文件结尾,文件中并不存在这个值,完全是文件操作函数发现到达了文件结尾,而返回这个值。

注意:

  1. 一般来说,系统对同时打开的文件数量有限制,及时关闭文件可以避免超过这个限制。
  2. 对于文件,有打开就有关闭,这样才能及时回收内存
#include 

int main(void) {
  FILE* fp;
  char c;
  fp = fopen("hello.txt", "r");
  if (fp == NULL) {
    return -1;
  }
  c = fgetc(fp);
  printf("%c\n", c);
  fclose(fp);
  return 0;
}

新建文件指针fp以后,依次使用了下面三个文件操作函数,分成三个步骤。其他的文件操作,大致上也是这样的步骤。

第一步,使用fopen()打开指定文件,返回一个 FILE 指针。如果出错,返回 NULL。

它相当于将指定文件的信息与新建的文件指针fp相关联,在 FILE 结构内部记录了这样一些信息:文件内部的当前读写位置、读写报错的记录、文件结尾指示器、缓冲区开始位置的指针、文件标识符、一个计数器(统计拷贝进缓冲区的字节数)等等。后继的操作就可以使用这个指针(而不是文件名)来处理指定文件。

同时,它还为文件建立一个缓存区。由于存在缓存区,也可以说fopen()函数“打开一个了流”,后继的读写文件都是流模式。

第二步,使用读写函数,从文件读取数据,或者向文件写入数据。上例使用了fgetc()函数,从已经打开的文件里面,读取一个字符。

fgetc()一调用,文件的数据块先拷贝到缓冲区。不同的计算机有不同的缓冲区大小,一般是512字节或是它的倍数,如4096或16384。随着计算机硬盘容量越来越大,缓冲区也越来越大。

fgetc()从缓冲区读取数据,同时将文件指针内部的读写位置指示器,指向所读取字符的下一个字符。所有的文件读取函数都使用相同的缓冲区,后面再调用任何一个读取函数,都将从指示器指向的位置,即上一次读取函数停止的位置开始读取。

当读取函数发现已读完缓冲区里面的所有字符时,会请求把下一个缓冲区大小的数据块,从文件拷贝到缓冲区中。读取函数就以这种方式,读完文件的所有内容,直到文件结尾。不过,上例是只从缓存区读取一个字符。当函数在缓冲区里面,读完文件的最后一个字符时,就把 FILE 结构里面的文件结尾指示器设置为真。于是,下一次再调用读取函数时,会返回常量 EOF。

第三步,fclose()关闭文件,同时清空缓存区。

上面是文件读取的过程,文件写入也是类似的方式,先把数据写入缓冲区,当缓冲区填满后,缓存区的数据将被转移到文件中。

四、fprintf()

功能:fprintf()用于向文件写入格式化字符串,用法与printf()类似。区别是printf()总是写入stdout,而fprintf()则是写入指定的文件。

函数原型:int fprintf(FILE* stream, const char* format, ...)

参数:第一个参数必须是文件指针,其余的用法和printf一样

返回值:返回值也和printf一样

// fprintf()可以替代printf()。
printf("Hello, world!\n");
fprintf(stdout, "Hello, world!\n");

五、fscanf()

功能:按照给定的模式,从文件中读取内容,用法跟scanf()类似。区别是scanf()总是从stdin读取数据,而fscanf()是从文件读入数据

函数原型:int fscanf(FILE* stream, const char* format, ...)

参数:第一个参数必须是文件指针,其余用法和scanf一样

返回值:fscanf()的返回值和scanf一样,是赋值成功的变量数量,如果赋值失败会返回 EOF。

注意:

  1. **使用fscanf()的前提是知道文件的结构,它的占位符解析规则与scanf()完全一致。**由于fscanf()可以连续读取,直到读到文件尾,或者发生错误(读取失败、匹配失败),才会停止读取,所以fscanf()通常放在循环里面。

    while(fscanf(fp, "%s", words) == 1) 
      puts(words); // fscanf()依次读取文件的每个词,将它们一行打印一个,直到文件结束。
    
  2. 一定情况下,fscanf和scanf是等价的

    // 等价scanf("%d", &a)
    fscanf(stdin, "%d", &a);
    

六、fgetc(),getc()

功能:fgetc()和getc()用于从文件读取一个字符。

函数原型:int fgetc(FILE *stream); int getc(FILE *stream);

返回值:返回读取的字符,读取失败的情况下,它们会返回 EOF(-1)。

参数:都只有文件指针一个参数。

注意:

  1. 两者的区别是,getc()一般用宏来实现,而fgetc()是函数实现,所以前者的性能可能更好一些。
  2. 读取的文件必须是以读或者读写的方式打开的
  3. 使用一次该函数,文件指针的位置就会向后移动一个字节
  • 使用getc完成文件中字符出现次数的统计

    #include 
    #include  // for `exit`
    #include  // for `isalpha`
    
    int main(int argc, char *argv[]){
        int ch, get_ch;   // for accident EOF (-1)
        FILE *fp;
        unsigned long count = 0;
    
        if (argc != 3 && isalpha(argv[2][0])){
            printf("Usage %s file letter\n", argv[0]);
            exit(EXIT_FAILURE);
        }
    		
    		// receive charachter, DO NOT WRITE ERROR NUMBER SUCH AS 3 OR 1
    		get_ch = argv[2][0]; 
    
        if ((fp = fopen(argv[1], "r")) == NULL){
            printf("can't open %s\n", argv[1]);
            exit(EXIT_FAILURE);
        }
    
        while ((ch = getc(fp)) != EOF){
            if (ch == get_ch){
    					count++;
    				} 
        }
    
        fclose(fp);
        printf("The file `%s` have  `%2lu` `%c`.\n", argv[1], count, get_ch);
        return 0;
    }
    

七、fputc(),putc()

功能:fputc()和putc()用于向文件写入一个字符。

函数原型:int fputc(int char, FILE *stream); int putc(int char, FILE *stream);

返回值:返回写入的字符,若是写入失败,返回 EOF(-1)。

参数:第一个参数是待写入的字符,第二个参数是文件指针。

注意:

  1. **putc()通常是使用宏来实现,而fputc()只作为函数来实现,所以理论上,putc()的性能会好一点。**
  2. 被写入的文件可以用写、读写、追加的方式打开
  3. 每写入一个字符,文件指针的位置就会向后移动一个字节。
  • 使用putc和getc实现文件复制

    FILE *fp1, *fp2;
    char ch; // 临时变量
    
    // 将fp1... 复制到 fp2...
    while ((ch = getc(fp1)) != EOF){ // 核心代码
        putc(ch, fp2);
    }
    
  • 使用putc和getc实现输出文件内容的同时并记录文件的字符长度

    #include 
    #include  // for `exit`
    
    int main(int argc, char *argv[]){
        int ch;   // for accident EOF (-1)
        FILE *fp;
        unsigned long count = 0;
    
        if (argc != 2){
            printf("Usage %s letter\n", argv[0]);
            exit(EXIT_FAILURE);
        }
    
        if ((fp = fopen(argv[1], "r")) == NULL){
            printf("can't open %s\n", argv[1]);
            exit(EXIT_FAILURE);
        }
    
        while ((ch = getc(fp)) != EOF){
            putc(ch, stdout);
            count++;
        }
    
        fclose(fp);
        printf("\nThe file `%s` have  `%2lu` Bytes length.\n", argv[1], count);
        return 0;
    }
    
  • 使用fputc和fgetc向文件中输入一句话

    // 其实截至条件就是 ENTER,若是改成其他的可以无限输入
    while ((fputc(fgetc(stdin), fp)) != '\n')
    		continue;
    

八、fgets()

功能:fgets()用于从文件读取指定长度的字符串。

函数原型:char *fgets(char *str, int STRLEN, File *fp);

参数:它的第一个参数str是一个字符串指针,用于存放读取的内容。第二个参数STRLEN指定读取的长度,第三个参数是一个 FILE 指针,指向要读取的文件。

返回值:返回指向第一个参数的指针,读取失败则返回空指针 NULL。

注意:

  1. fgets()读取 STRLEN - 1 个字符之后,或者遇到换行符与文件结尾,就会停止读取,然后在已经读取的内容末尾添加一个空字符\0,使之成为一个字符串。注意,fgets()会将换行符(\n)存储进字符串。
  • 读取文件的每一行

    #include 
    
    int main(void) {
      FILE* fp;
      char s[1024];  // 数组必须足够大,足以放下一行
      int linecount = 0;
    
      fp = fopen("hello.txt", "r");
    
      while (fgets(s, sizeof(s), fp) != NULL)
        printf("%d: %s", ++linecount, s); // 输出该行和行号
    
      fclose(fp);
    }
    
    
  • 循环读取用户的输入。

    // 如果用户输入的字符串大于9个字符,fgets()会多次读取。直到遇到q + 回车键,才会退出循环。
    char words[10];
    
    puts("Enter strings (q to quit):");
    
    // 字符串地址保持不变
    // 内容会不断变化,直至条件为真跳出循环
    while (fgets(words, 10, stdin) != NULL) {
      if (words[0] == 'q' && words[1] == '\n')
        break;
    
      puts(words);
    }
    
    puts("Done.");
    
  • 读取输入行,删除存储在字符串中的换行符,如果没有换行符,则丢弃数组装不下的字符。

    #include 
    
    int main(){
    	char s[1024];
    	while(s_gets(s, 1024) != NULL && s[0] != '\0'){ // '\n' -> '\0'
    		puts(s);
    		printf("Enter string: ");
    	}
    	return 0;
    }
    
    char *s_gets(char *str, int length){
    	char *ret_val = fgets(str, length, stdin); // 从键盘获取输入
    	int i = 0;
    
    	if (ret_val){ // 若输入字符串成功, 对字符串进行二次处理
    		while(str[i] != '\0' && str[i] != '\n')
    			i++;
    		/*
    		然而,如果输入的字符串长度超过了定义的length参数,fgets函数会将多
    余的字符留在输入缓冲区中,导致下一次循环时仍然可以读取到这些字符,从而
    陷入死循环。要解决这个问题,您可以在清空缓冲区之前,先判断输入缓冲区中
    是否还有剩余字符,如果有的话就使用一个循环来读取并丢弃这些字符。
    		*/
    
    		if (str[i] == '\n'){
    			str[i] = '\0'; // 彻底成为字符串,末尾是'\0'而不是fgets自带的'\n'
    		} else { // else 换成if判断的话,出现的问题!!
    			while (getchar() != '\n') // 目的是清空缓存区
    				continue; 
    		}
    	}
    	return ret_val;
    }
    

九、fputs()

功能:fputs()函数用于向文件写入字符串

函数原型:int fputs(const char *str, FILE *stream);

返回值:返回一个非负整数,写入失败则返回 EOF(-1)。

参数:它接受两个参数,第一个参数是字符串指针,第二个参数是要写入的文件指针。

注意:

  1. puts()函数只有一点不同,**那就是它不会在字符串末尾添加换行符。**这是因为fgets()保留了换行符,所以fputs()就不添加了。fputs()函数通常与fgets()配对使用。

十、fwrite()

功能:fwrite()用来一次性写入较大的数据块,主要用途是将数组数据一次性写入文件,适合写入二进制数据。它的原型定义在头文件stdio.h

函数原型:

size_t fwrite( const void *ptr, // 数组指针。 size_t size, // 每次输入内存的字节大小。 size_t nmemb, // 输入多少个内存。 FILE *fp // 要写入的文件指针。 );
参数:fwrite()原型的第一个参数类型是void*,这是一个无类型指针,编译器会自动将参数指针转成void*类型。正是由于fwrite()不知道数组成员的类型,所以才需要知道每个成员的大小(第二个参数)和成员数量(第三个参数)。

返回值:fwrite()函数的返回值是成功写入的数组成员的数量(注意不是字节数)。正常情况下,该返回值就是第三个参数nmemb,但如果出现写入错误,只写入了一部分成员,返回值会比nmemb小。

注意:

  1. fwrite()实际上可以写入任何类型的数据,而不仅仅是数组。比如,fwrite()可以将一个 struct 结构写入文件保存。

    fwrite(&s, sizeof(s), 1, fp);
    

    上面示例中,s是一个 struct 结构指针,可以看成是一个成员的数组。注意,如果s的属性包含指针,存储时需要小心,因为保存指针可能没意义,还原出来的时候,并不能保证指针指向的数据还存在。

  • 写入二进制文件

    #include 
    
    int main(void) {
      FILE* fp;
      unsigned char bytes[] = {5, 37, 0, 88, 255, 12};
    
      fp = fopen("output.bin", "wb"); // wb 二进制写入
      fwrite(bytes, sizeof(char), sizeof(bytes) / sizeof(char), fp);
      fclose(fp);
      return 0;
    }
    
    

    上面例子写入的文件output.bin,使用十六进制编辑器打开,05 25 00 58 ff 0c。

十一、fread()

功能:fread()函数用于一次性从文件读取较大的数据块,主要用途是将文件内容读入一个数组,适合读取二进制数据。它的原型定义在头文件stdio.h

函数原型:

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

参数:fread()原型的第一个参数类型是void*,这是一个无类型指针,编译器会自动将参数指针转成void*类型。正是由于fread()不知道数组成员的类型,所以才需要知道每个成员的大小(第二个参数)和成员数量(第三个参数)。

返回值:读取的内存的数量。正常情况下,该返回值就是第三个参数nmemb,但如果出现读取错误或读到文件结尾,该返回值就会比nmemb小。所以,检查fread()的返回值是非常重要的。

注意:

  1. fread()fwrite()可以配合使用。在程序终止之前,使用fwrite()将数据保存进文件,下次运行时再用fread()将数据还原进入内存。
  2. 二进制数据可能包含空字符\0,这是 C 语言的字符串结尾标记,所以读写二进制文件,不适合使用文本读写函数(比如fprintf()等)
  • 生成二进制文件

    #include 
    
    int main(void) {
      FILE* fp;
      unsigned char c;
    
      fp = fopen("output.bin", "rb");
    
      while (fread(&c, sizeof(char), 1, fp) > 0) 
        printf("%d\n", c);
      return 0;
    }
    
  • 使用fwrite和fread实现文件复制

    FILE *fp1, *fp2;
    char buffer[1024];
    
    size_t readBytes;
    // 将fp1指向文件中的内容 复制到 fp2指向文件中
    // 利用每次的返回值都为第三个参数的特性来不断循环,直至完全复制成功
    while ((readBytes = fread(buffer, sizeof(char), sizeof(buffer)/sizeof(char), fp1)) > 0){
    		fwrite(buffer, sizeof(char), readBytes, fp2);
    }
    

十二、feof()

功能:判断文件的内部指针是否指向文件结尾

函数原型:int feof(FILE *fp);

参数:文件指针

返回值:如果已经到达文件结尾,会返回一个非零值(表示 true);否则返回0(表示 false)

注意:

  1. 文件操作函数若返回 EOF,有两种可能,一种可能是已读取到文件结尾,另一种可能是出现读取错误。feof()可以用来判断到底是那一种情况。

    // 通过循环判断feof()是否读到文件结尾,从而实现读出整个文件内容
    
    int num;
    char name[50];
    
    FILE *cfPtr = fopen("clients.txt", "r");
    
    // 要求文件内容有一定格式
    while (!feof(cfPtr)) {
      fscanf(cfPtr, "%d%s\n", &num, name);
      printf("%d %s\n", num, name);
    }
    
    fclose(cfPtr);
    
  2. feof()为真时,表明文件内部的指针已经到达文件结尾,可以通过fseek()、rewind()、fsetpos()函数改变文件内部读写位置的指示器,从而清除这个函数的状态。

  3. 每个文件指针都有一个内部指示器(内部指针),记录当前打开的文件的读写位置(file position),即下一次读写从哪里开始。文件操作函数(比如getc()fgets()fscanf()fread()等)都从这个指示器指定的位置开始按顺序读写文件。

十三、fseek()

功能:fseek()可以改变文件内部指针的位置。

函数原型:int fseek(FILE *stream, long int offset, int whence);

参数:

  1. stream:文件指针。

  2. offset:移动字节数。类型为 long int**(后缀加L)**,可以为正值(向文件末尾移动)、负值(向文件开始处移动)或 0(保持不动)。3.

  3. whence:位置基准,用来确定计算起点。

    它的值是以下三个宏(定义在stdio.h):SEEK_SET(文件开始处)、SEEK_CUR (内部指针的当前位置)、SEEK_END(文件末尾)

返回值:正常情况下,fseek()的返回值为0。如果发生错误(如移动的距离超出文件的范围),返回值为非零值(比如-1)。

注意

  1. fseek()最好只用来操作二进制文件,不要用来读取文本文件。因为文本文件的字符有不同的编码,某个位置的准确字节位置不容易确定。

    // 定位到文件开始处
    fseek(fp, 0L, SEEK_SET);
    
    // 定位到文件末尾
    fseek(fp, 0L, SEEK_END);
    
    // 从当前位置后移2个字节
    fseek(fp, 2L, SEEK_CUR);
    
    // 定位到文件第10个字节
    fseek(fp, 10L, SEEK_SET);
    
    // 定位到文件倒数第10个字节
    fseek(fp, -10L, SEEK_END);
    
  • 逆向输出文件的所有字节,即逆序输出文件内容

    for (count = 1L; count <= size; count++) {
      fseek(fp, -count, SEEK_END);
    	putchar(getc(fp));
    }
    
    

十四、ftell()

功能:确定文件内部指示器的当前位置。

函数原型:long int ftell(FILE *stream);

参数:一个文件指针

返回值:返回文件内部指示器从开头到当前位置的字节数。0表示文件开始处。如果发生错误,ftell()返回-1L

十五、rewind()

功能:让文件的内部指示器回到文件开始处。

函数原型:void rewind(file *stream);

参数:一个文件指针

无返回值

注意:

  • rewind(fp)基本等价于fseek(fp, 0L, seek_set),唯一的区别是rewind()没有返回值,而且会清除当前文件的错误指示器。

十六、ferror()

所有的文件操作函数如果执行失败,都会在文件指针里面记录错误状态。后面的操作只要读取错误指示器,就知道前面的操作出错了。

功能:ferror()函数用来返回错误指示器的状态。可以通过这个函数,判断前面的文件操作是否成功。

函数原型:int ferror(FILE *stream);

参数:一个文件指针。

返回值:如果前面的操作出现错误,ferror()就会返回一个非零整数(表示 true),否则返回0

十七、clearerr()

功能:clearerr()函数用来重置出错指示器。它的原型定义在头文件stdio.h

函数原型:void clearerr(FILE* fp);

参数:一个文件指针

没有返回值。

下面是一个例子。

FILE* fp = fopen("file.txt", "w");
char c = fgetc(fp);

if (ferror(fp)) {
  printf("读取文件:file.txt 时发生错误\\n");
}

clearerr(fp);

上面示例中,fgetc()尝试读取一个以”写模式“打开的文件,读取失败就会返回 EOF。这时调用ferror()就可以知道上一步操作出错了。处理完以后,再用clearerr()清除出错状态。

文件操作函数如果正常执行,ferror()feof()都会返回零。如果执行不正常,就要判断到底是哪里出了问题。

if (fscanf(fp, "%d", &n) != 1) {
  if (ferror(fp)) {
    printf("io error\\n");
  }
  if (feof(fp)) {
    printf("end of file\\n");
  }

  clearerr(fp);

  fclose(fp);
}

上面示例中,当fscanf()函数报错时,通过检查ferror()feof(),确定到底发生什么问题。这两个指示器改变状态后,会保持不变,所以要用clearerr()清除它们,clearerr()可以同时清除两个指示器。

你可能感兴趣的:(C语言基础学习,C)