C---文件输入/输出

本文深入探讨了C语言中的文件操作技术,包括标准I/O函数如fopen(), fread(), fwrite(), fseek()和ftell()的使用,以及如何通过随机访问进行文件读写。通过实例演示了文件压缩、逆序读取文件和二进制文件的随机访问。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

目录

 

标准I/O

exit()

fopen()函数

读和写流

输入函数

输出函数

每次一行I/O

文件结尾

一个简单的文件压缩程序

随机访问: fseek()和ftell()

标准I/O的机理

二进制I/O:fread()和fwrite()

用二进制I/O进行随机访问


标准I/O

count.c演示了如何用标准I/O读取文件和统计文件中的字符数

/* count.c */
#include <stdio.h>
#include <stdlib.h>

int main(int argc, char* argv[])
{
    int ch;
    FILE* fp;
    unsigned long count = 0;
    if (argc != 2)
    {
        printf("Usage: %s filename\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("File %s has %lu characters\n", argv[1], count);

    return 0;

}
mali@mali:~/code/file$ ls -l
total 8
-rw-rw-r-- 1 mali mali 567 8月  10 23:15 count.c
-rw-rw-r-- 1 mali mali 396 8月  10 23:16 test.txt
mali@mali:~/code/file$ gcc count.c -o count
mali@mali:~/code/file$ ./count test.txt 
helloworld
helloworld
helloworld
helloworld
helloworld
helloworld
helloworld
helloworld
helloworld
helloworld
helloworld
helloworld
helloworld
helloworld
helloworld
helloworld
helloworld
helloworld
helloworld
helloworld
helloworld
helloworld
helloworld
helloworld
helloworld
helloworld
helloworld
helloworld
helloworld
helloworld
helloworld
helloworld
helloworld
helloworld
helloworld
helloworld
File test.txt has 396 characters
mali@mali:~/code/file$ ./count test1.txt
can't open test1.txt
mali@mali:~/code/file$ ./count 
Usage: ./count filename

exit()

exit()函数关闭所有打开的文件并结束程序exit()的参数被传递给一些操作系统,包括UNIX、Linux、Windows和MS-DOS,以供其他程序使用。

通常的惯例是:正常结束的程序传递0,异常结束的程序传递非零值。不同的退出值可用于区分程序失败的不同原因。

#define	EXIT_FAILURE	1	/* Failing exit status.  */
#define	EXIT_SUCCESS	0	/* Successful exit status.  */

根据ANSI C的规定,在最初调用的main()中使用return与调用exit()的效果相同。因此,在main(),下面的语句:

return 0;

和下面这条语句的作用相同:

exit(0);

但是要注意,是”最初的调用”。如果main()在一个递归程序中,exit()仍然会终止程序,但是return只会把控制权交给上一级递归,直至最初的一级。然后return结束程序。

return和exit()的另一个区别是:即使在其他函数中(除main()以外)调用exit()也能结束整个程序。

fopen()函数

fopen()的模式字符串
模式字符串含义
"r"

以读模式打开文件

O_RDONLY

"w"

以写模式打开文件,把现有文件的长度截为0,如果文件不存在,则创建一个新文件

O_WRONLY | O_CREAT | O_TRUNC

"a"

以写模式打开文件,在现有文件末尾添加内容,如果文件不存在,则创建一个新文件

O_WRONLY | O_CREAT | O_APPEND

"r+"

以更新模式打开文件(即可以读写文件)

O_RDWR

"w+"

以更新模式打开文件(即,读和写),如果文件存在,则将其长度截为0;如果文件不存在,则创建一个新文件

O_RDWR | O_CREATE | O_TRUNC

"a+"

以更新模式打开文件(即,读和写),在现有文件的末尾添加内容,如果文件不存在,则创建一个新文件;可以读整个文件,但是只能从末尾添加内容

O_RDWR | O_CREATE | O_APPEND

"rb" "wb" "ab" "rb+" "r+b" "wb+" "w+b" "ab+" "a+b"与上一个模式类似,但是以二进制模式而不是文本模式打开文件
"wx" "wbx" "w+x" "wb+x" "w+bx"(C11)类似非x模式,但是如果文件已存在或以独占模式打开文件,则打开文件失败

读和写流

输入函数

#include <stdio.h>
int getc(FILE *fp);
int fgetc(FILE *fp);
int getchar(void);

3个函数的返回值:若成功,返回下一个字符;若已到达文件尾端或出错,返回EOF

getc()和putc()函数与getchar()和putchar()函数类似。所不同的是,要告诉getc()和putc()函数使用哪一个文件

下面这条语句的意思是“从标准输入中获取一个字符”:
ch = getchar();
然而,下面这条语句的意思是"从fp指定的文件中获取一个字符":
ch = getc(fp);

与此类似,下面语句的意思是"把字符ch放入FILE指针fpout指定的文件中":
putc(ch, fpout);
在putc()函数的参数列表中,第1个参数是待写入的字符,第2个参数是文件指针。
然而,下面这条语句的意思是"将一个字符输出到标准输出":
putchar(ch);


getchar等同于getc(stdin).前两个函数的区别是:getc可被实现为宏,而fgetc不能实现为宏。这意味着以下几点:

1.getc的参数不应当是具有副作用的表达式,因为它可能会被计算多次

2.因为fgetc一定是个函数,所以可以得到其地址。这就允许将fgetc的地址作为一个参数传送给另一个函数。

3.调用fgetc所需时间很可能比调用getc要长,因为调用函数所需的时间通常长于调用宏。

这3个函数在返回下一个字符时,将其unsigned char类型转换为int类型。说明为无符号的理由是:如果最高位为1也不会使返回值为负。 要求整形返回值得理由是:这样就可以返回所有可能的字符值再加上一个已出错或已到达文件尾端的指示值。在<stdio.h>中的常量EOF被要求是一个负值,其值经常是-1.这就意味着不能将这3个函数的返回值存放在一个字符变量中,以后还要将这些函数的返回值与常量EOF比较。

注意:不管是出错还是到达文件尾端,这3个函数都返回同样的值。为了区分这两种不同的情况,必须调用ferror或feof。

#include <stdio.h>
int ferror(FILE *fp);

int feof(FILE *fp);
两个函数返回值:若条件为真,返回非0(真),否则,返回0(假)

void clearerr(FILE *fp);

在大多数实现中,为每个流在FILE对象中维护了两个标志:

  • 出错标志
  • 文件结束标志

调用clearerr可以清除这两个标志。

从流中读取数据以后,可以调用ungetc将字符再压回流中。

#include <stdio.h>

int ungetc(int c, FILE *fp);

返回值:若成功,返回c;若出错,返回EOF

输出函数

#include <stdio.h>
int putc(int c, FILE *fp);
int fputc(int c, FILE *fp);
int putchar(int c);

3个函数的返回值:若成功,返回c;若出错,返回EOF

与输入函数一样,putchar(c)等同于putc(c, stdout), putc可被实现为宏,而fputc不能实现为宏。

每次一行I/O

下面两个函数提供每次输入一行的功能。

#include <stdio.h>

char *fgets(char *restrict buf, int n, FILE *restrict fp);

char *gets(char *buf);

两个函数返回值:若成功,返回buf;若已到达文件尾端或出错,返回NULL

这两个函数都指定了缓冲区的地址,读入的行将送入其中。gets从标准输入读,而fgets则从指定的流读。

对于fgets,必须指定缓冲区的长度n.此函数一直读到下一个换行符为止,但是不超过n-1个字符,读入的字符被送入缓冲区。该缓冲区以null字节结尾。如若该行包括最后一个换行符的字符数超过n-1,则fgets只返回一个不完整的行,但是,缓冲区总是以null字节结尾。对fgets的下一次调用会继续读该行。

gets是一个不推荐使用的函数。其问题是调用者在使用gets时不能指定缓冲区的长度。这样就可能造成缓冲区溢出(如若该行长于缓冲区长度),写到缓冲区之后的存储空间中,从而产生不可预料的后果。

gets和fgets的另一个区别是,gets并不会将换行符存入缓冲区中。

fputs和puts提供每次输出一行的功能.

#include <stdio.h>

int fputs(const char *restrict str, FILE *restrict fp);

int puts(const char *str);

两个函数返回值:若成功,返回非负值;若出错,返回EOF

函数fputs将一个以null字节终止的字符串写到指定的流,尾端的终止符null不写出。注意,这并不一定是每次输出一行,因为字符串不需要换行符作为最后一个非null字符。通常,在null字节之前是一个换行符,但并不要求总是如此。

puts将一个以null字节终止的字符串写到标准输出,终止符不写出。但是,puts随后又将一个换行符写到标准输出。

文件结尾

从文件中读取数据的程序在读到文件结尾时要停止。如何告诉程序已经读到文件结尾?

如果getc()函数在读取一个字符时发现是文件结尾,它将返回一个特殊值EOF。

为了避免读到空文件,应该使用入口条件循环(不是do while循环)进行文件输入。

#ifndef EOF
# define EOF (-1)
#endif
//设计范例#1
    int ch;
    FILE* fp;
    fp = fopen("test.txt", "r");
    ch = getc(fp);
    while (ch != EOF)
    {
        putchar(ch);
        ch = getc(fp);
    }
以上代码可简化为:
//设计范例#2
    int ch;
    FILE* fp;
    fp = fopen("test.txt", "r");
    while ((ch = getc(fp)) != EOF)
    {
        putchar(ch);
    }

一个简单的文件压缩程序

下面的程序示例把一个文件中选定的数据拷贝到另一个文件中。该程序同时打开了两个文件,以"r"模式打开第一个,以"w"模式打开另一个。该程序以保留每3个字符中的第1个字符的方式压缩第1个文件的内容。最后,把压缩后的文本存入第2个文件。

第2个文件的名称是第1个文件名加上.red后缀(此处的red代表reduced)。使用命令行参数,同时打开多个文件,以及在原文件名后面加上后缀,都是相当有用的技巧。

/* reducto.c */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#define LEN 40

int main(int argc, char* argv[])
{
    FILE *in, *out;
    int ch;
    char name[LEN];
    int count = 0;

    if (argc < 2)
    {
        fprintf(stderr, "Usage: %s filename\n", argv[0]);
        exit(EXIT_FAILURE);
    }

    if ((in = fopen(argv[1], "r")) == NULL)
    {
        fprintf(stderr, "can't open the file \"%s\"\n",
                argv[1]);
        exit(EXIT_FAILURE);
    }

    strncpy(name, argv[1], LEN - 5);
    name[LEN - 5] = '\0';
    strcat(name, ".red");

    if ((out = fopen(name, "w")) == NULL)
    {
        fprintf(stderr, "can't create output file.\n");
        exit(3);
    }

    while ((ch = getc(in)) != EOF)
    {
        if (count++ % 3 == 0)
        {
            putc(ch, out);
        }
    }

    if (fclose(in) != 0 || fclose(out) != 0)
    {
        fprintf(stderr, "error in closing files.\n");
    }

    return 0;

}
mali@mali:~/code/file$ ls -l
total 28
-rwxrwxr-x 1 mali mali 8912 8月  10 23:17 count
-rw-rw-r-- 1 mali mali  396 8月  11 00:16 count.c
-rw-rw-r-- 1 mali mali   29 8月  11 00:47 eddy
-rw-rw-r-- 1 mali mali  955 8月  11 00:46 reducto.c
-rw-rw-r-- 1 mali mali  396 8月  10 23:16 test.txt
mali@mali:~/code/file$ cat eddy
So even Eddy came oven ready.mali@mali:~/code/file$ gcc reducto.c  -o reducto
mali@mali:~/code/file$ ./reducto eddy
mali@mali:~/code/file$ ls -l
total 44
-rwxrwxr-x 1 mali mali 8912 8月  10 23:17 count
-rw-rw-r-- 1 mali mali  396 8月  11 00:16 count.c
-rw-rw-r-- 1 mali mali   29 8月  11 00:47 eddy
-rw-rw-r-- 1 mali mali   10 8月  11 00:48 eddy.red
-rwxrwxr-x 1 mali mali 9080 8月  11 00:48 reducto
-rw-rw-r-- 1 mali mali  955 8月  11 00:46 reducto.c
-rw-rw-r-- 1 mali mali  396 8月  10 23:16 test.txt
mali@mali:~/code/file$ cat eddy.red 
Send moneymali@mali:~/code/file$ 

随机访问: fseek()和ftell()

/* reverse.c */
#include <stdio.h>
#include <stdlib.h>

#define  SLEN 81
int main(int argc, char* argv[])
{
    char file[SLEN];
    char ch;
    FILE *fp;
    long count, last;

    puts("Enter the name of the file to be processed:");
    scanf("%80s", file);
    if ((fp = fopen(file, "rb")) == NULL)
    {
        printf("reverse can't open %s\n", file);
        exit(EXIT_FAILURE);
    }
    fseek(fp, 0L, SEEK_END);
    last = ftell(fp);
    for (count = 1L; count <= last; count++)
    {
        fseek(fp, -count, SEEK_END);
        ch = getc(fp);
        putchar(ch);
    }
    putchar('\n');
    fclose(fp);
    return 0;

}
Send moneymali@mali:~/code/file$ ls -l
total 48
-rwxrwxr-x 1 mali mali 8912 8月  10 23:17 count
-rw-rw-r-- 1 mali mali  396 8月  11 00:16 count.c
-rw-rw-r-- 1 mali mali   29 8月  11 00:47 eddy
-rw-rw-r-- 1 mali mali   10 8月  11 00:48 eddy.red
-rwxrwxr-x 1 mali mali 9080 8月  11 00:48 reducto
-rw-rw-r-- 1 mali mali  955 8月  11 00:46 reducto.c
-rw-rw-r-- 1 mali mali  634 8月  11 01:07 reverse.c
-rw-rw-r-- 1 mali mali  396 8月  10 23:16 test.txt
mali@mali:~/code/file$ gcc reverse.c -o reverse
mali@mali:~/code/file$ ./reverse 
Enter the name of the file to be processed:
eddy
.ydaer nevo emac yddE neve oS
mali@mali:~/code/file$ cat eddy
So even Eddy came oven ready.mali@mali:~/code/file$ 
文件的起始点模式
模式偏移量的起始点
SEEK_SET文件开始处
SEEK_CUR当前位置
SEEK_END文件末尾
/* Seek to a certain position on STREAM. */
int fseek (FILE *__stream, long int __off, int __whence);

/* Return the current position of STREAM. */
long int ftell (FILE *__stream);

#define SEEK_SET	0	/* Seek from beginning of file.  */
#define SEEK_CUR	1	/* Seek from current position.  */
#define SEEK_END	2	/* Seek from end of file.  */

 

fseek(fp, 0L, SEEK_SET);//定位至文件开始处
fseek(fp, 10L, SEEK_SET);//定位至文件中的第10个字节
fseek(fp, 2L, SEEK_CUR);//从文件当前位置前移2个字节
fseek(fp, 0L, SEEK_END);//定位至文件结尾
fseek(fp, -10L, SEEK_CUR);//从文件结尾处回退10个字节

ftell()函数的返回类型是long,它返回的是参数指向文件的当前位置距文件开始处的字节数。

标准I/O的机理

通常,使用标准I/O的第1步时调用fopen()打开文件。fopen()函数不仅打开一个文件,还创建了一个缓冲区(在读写模式下会创建两个缓冲区)以及一个包含文件和缓冲区数据的结构。另外,fopen()返回一个指向该结构的指针,以便其他函数知道如何找到该结构。

这个结构通常包含一个指定流中当前位置的文件位置指示器。除此之外,它还包含错误和文件结尾的指示器、一个指向缓冲区开始处的指针、一个文件标识符和一个计数(统计实际拷贝进缓冲区的字节数)。

我们主要考虑文件输入。

通常,使用标准I/O的第2步时调用一个定义在stdio.h中的输入函数,如fscanf()、getc()或fgets()。一调用这些函数,文件中的缓冲大小数据块就被拷贝到缓冲区中。缓冲区的大小因实现而异,一般是512字节或是它的倍数,如4096。最初调用函数,除了填充缓冲区外,还要设置fp所指向的结构中的值。尤其要设置流中的当前位置和拷贝进缓冲区的字节数。通常,当前位置从字节0开始

在初始化结构和缓冲区后,输入函数按要求从缓冲区中读取数据。在它读取数据时,文件位置指示器被设置为指向刚读取字符的下一个字符。由于stdio.h系列的所有输入函数都使用相同的缓冲区,所以调用任何一个函数都将从上一次函数停止调用的位置开始。

当输入函数发现已读完缓冲区中的所有字符时,会请求把下一个缓冲大小的数据块从文件拷贝到该缓冲区中。以这种方式,输入函数可以读取文件中的所有内容,直到文件结尾。函数在读取缓冲区的最后一个字符后,把结尾指示器设置为真。于是,下一次被调用的输入函数将返回EOF

输出函数以类似的方式把数据写入缓冲区。当缓冲区被填满时,数据被拷贝至文件中。

二进制I/O:fread()和fwrite()

/* Read chunks of generic data from STREAM. */

 size_t fread (void *__restrict __ptr, size_t __size,
		     size_t __n, FILE *__restrict __stream) __wur;
/* Write chunks of generic data to STREAM. */

size_t fwrite (const void *__restrict __ptr, size_t __size,
		      size_t __n, FILE *__restrict __s);

用二进制I/O进行随机访问

/* randbin.c */
#include <stdio.h>
#include <stdlib.h>

#define ARSIZE 1000
int main(int argc, char* argv[])
{
    double numbers[ARSIZE];
    double value;
    const char *file = "numbers.dat";
    int i;
    long pos;
    FILE *iofile;

    for (i  = 0; i < ARSIZE; i++)
    {
        numbers[i] = 100.0 * i + 1.0 / (i + 1);
    }

    if ((iofile = fopen(file, "wb")) == NULL)
    {
        fprintf(stderr, "could not open %s for output.\n", file);
        exit(EXIT_FAILURE);
    }

    fwrite(numbers, sizeof(double), ARSIZE, iofile);
    fclose(iofile);

    if ((iofile = fopen(file, "rb")) == NULL)
    {
        fprintf(stderr, "could not open %s for random access.\n", file);
        exit(EXIT_FAILURE);
    }

    printf("enter an index in the range 0-%d.\n", ARSIZE - 1);
    while (scanf("%d", &i) == 1 && i >= 0 && i < ARSIZE)
    {
        pos = (long)i * sizeof(double);
        fseek(iofile, pos, SEEK_SET);
        fread(&value, sizeof(double), 1, iofile);
        printf("The value there is %f.\n", value);
        printf("Next index (out of range to quit):\n");
    }

    fclose(iofile);
    puts("Bye!");
    return 0;
}
mali@mali:~/code/file$ ls -l
total 64
-rwxrwxr-x 1 mali mali 8912 8月  10 23:17 count
-rw-rw-r-- 1 mali mali  396 8月  11 00:16 count.c
-rw-rw-r-- 1 mali mali   29 8月  11 00:47 eddy
-rw-rw-r-- 1 mali mali   10 8月  11 00:48 eddy.red
-rw-rw-r-- 1 mali mali 1148 8月  11 02:12 randbin.c
-rwxrwxr-x 1 mali mali 9080 8月  11 00:48 reducto
-rw-rw-r-- 1 mali mali  955 8月  11 00:46 reducto.c
-rwxrwxr-x 1 mali mali 9136 8月  11 01:08 reverse
-rw-rw-r-- 1 mali mali  634 8月  11 01:07 reverse.c
-rw-rw-r-- 1 mali mali  396 8月  10 23:16 test.txt
mali@mali:~/code/file$ gcc randbin.c -o randbin
mali@mali:~/code/file$ ./randbin 
enter an index in the range 0-999.
500
The value there is 50000.001996.
Next index (out of range to quit):
900
The value there is 90000.001110.
Next index (out of range to quit):
0
The value there is 1.000000.
Next index (out of range to quit):
-1
Bye!
mali@mali:~/code/file$ ls -l
total 84
-rwxrwxr-x 1 mali mali 8912 8月  10 23:17 count
-rw-rw-r-- 1 mali mali  396 8月  11 00:16 count.c
-rw-rw-r-- 1 mali mali   29 8月  11 00:47 eddy
-rw-rw-r-- 1 mali mali   10 8月  11 00:48 eddy.red
-rw-rw-r-- 1 mali mali 8000 8月  11 02:14 numbers.dat
-rwxrwxr-x 1 mali mali 9160 8月  11 02:14 randbin
-rw-rw-r-- 1 mali mali 1148 8月  11 02:12 randbin.c
-rwxrwxr-x 1 mali mali 9080 8月  11 00:48 reducto
-rw-rw-r-- 1 mali mali  955 8月  11 00:46 reducto.c
-rwxrwxr-x 1 mali mali 9136 8月  11 01:08 reverse
-rw-rw-r-- 1 mali mali  634 8月  11 01:07 reverse.c
-rw-rw-r-- 1 mali mali  396 8月  10 23:16 test.txt

 

 

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值