牛骨文教育服务平台(让学习变的简单)
博文笔记

C语言文件的输入/输出

创建时间:2016-08-27 投稿人: 浏览次数:1674

    对文件的输入/输出学习是C语言中的一块重要内容,因为当你的程序变得复杂时,难免要处理一些文件,涉及到文件的读取和写入。C语言提供了强大的文件输入/输出功能,其标准I/O包中包含了很多专用的函数,可以很方便地读取和写入文件,我将在下面介绍几种常用的文件I/O函数,并最后给出一个模拟压缩文件的例子。

    在介绍函数之前,首先要说明C语言处理输入和输出,是采用“流“的形式,而且常常会有缓冲区,缓冲区的存在可以提高输入/输出处理的高效性,举个简答的例子,如果没有缓冲,下面这个代码的执行效果会很尴尬!

while((ch=getchar())!="
")
	putchar(ch);
putchar("
");


    当你输入“abc[回车的时候]”,屏幕显示的是"aabbcc",因为没有缓冲的存在,一输入马上得到输出。那么如何理解文件流的形式,可以这么理解,字符的输入就像注入一段水流,这些调用函数就在岸边去抽取自己想要的输入,然后显示,我以一个常见的小错误代码来说明

char ch;
int num1, num2;

while((ch=getchar())!="
"){
	putchar(ch);
	scanf("%d%d", &num1, &num2);
	printf("%d %d", num1, num2);
}
putchar("
");

    当你输入“a 42 24[回车]”的时候,这段字符序列“a【空格】42【空格】24【回车】”就向水流一样流进缓冲区,其中a被ch=getchar()捕获,然后进入循环,由于scanf()自动跳过空白字符,所以捕获两个整型量42和24,遇到【回车】会刷新缓冲区,所以屏幕输出“a42 24”,然后就结束循环了,因为[回车]被ch=getchar()捕获,不满足循环条件,跳出循环。

    所以大家在分析输入输出的时候也可以照着这种方法去分析,其实良好的输入输出功能是一个是需要程序员很细心的去处理,涉及到很多细节。

    接下来,介绍三个标准文件:stdin,stdout,stderr。可以将这3个文件理解成C语言自带的用于处理键盘输入,屏幕显示,标准错误的立即显示。stdin表示标准输入,通常指键盘输入,stdout表示标准输出,通常指屏幕显示,stderr表示标准错误,不经过缓冲区,直接输出到屏幕,举几个简单例子:

   

fprintf(stdout, "Hello World!
");
printf("Hello World!
");

fgets(char_array, MAX_SIZE, stdin);

    上述fprintf()语句和printf()功能相同,都是向屏幕输出"Hello World!", fgets()函数则是将键盘输入的字符串放到容量为MAX_SIZE大小的字符数组中。

    通过上述描述,初学者大概对输入输出流有了一个基本的概念,也了解了C语言的3个标准文件。其实大家可以发现C语言本质是把键盘输入,屏幕输出当成文件来处理的,这和UNIX和Linux的理念很像,一切设备均被视为文件。因为C语言的两位创始人Dennis M. Ritchie和Brain W. Kernighan也是UNIX的创始人啊,真的是佩服的五体投地,大家感兴趣的话可以查一查UNIX和C语言的历史,Linux又和UNIX很有渊源呢!

    标准I/O包中含有很多个函数,这里介绍常用的几个:

    fopen()函数,第一个参数是要打开的文件名(包含该文件名的字符串的地址),第二个参数是用于指定文件打开模式的一个字符串。模式字符串包括:“r”,“w”,“a”,“r+”,“w+”,“a+”,其中r表示只读,w表示写入,a表示追加,若带有+则表示即可读取也可写入,尤其要注意w参数,是将指定文件的原有内容清空,重新写入,若是希望对原有文件进行修改(保留原有文件内容),需要使用“a”参数;该函数的返回值为FILE类型的指针。

    fclose()函数,参数只有一个,即FILE类型的指针指定的文件,其功能为关闭文件,并刷新缓冲区,文件成功关闭返回0,否则返回EOF。

    fseek()定位文件当前的位置,参数为3个,第1个为表示文件的FILE类型指针,第2个为偏移量(long类型,正数表示向前偏移,负数表示向后偏移),第3个为起始点模式(可以称之为基准,共有3个,SEEK_SET表示文件的开始,SEEK_CUR表示文件当前位置,SEEK_END表示文件的结尾)

    fwrite()函数将二进制数据写入文件,其函数原型为size_t fwrite(const void * restrict ptr, size_t size, size_t nmemb, FILE * restrict fp); 举例来说:要写入10个double类型的数据,可以这样做:

    double number[10];

    fwrite(number, sizeof(double), 10, fp);

    与fwrite()函数对应,fread()函数从文件中读取相应的数据到制定目标中,其函数原型为: size_t fread(const void * restrict ptr, size_t size, size_t nmemb, FILE * restrict fp);用法类似与fread(),只不过是从文件中读取相应的数据。

    接下来,我以一个模拟压缩的程序来说明文件的I/O读取。压缩是一个很高深的学问,我这里只是提一个最最简单的压缩思路:如果有一串重复性很高的序列,比如0000000001111110000001111,那么我将其表示成09160614,其中9表示0的个数,6表示1的个数,6表示0的个数,4表示1的个数,这样是不是就可以实现压缩了呢。我给出这个模拟压缩程序的代码如下:

/*================================================

 # Author:      Joker@HIT    NolanRobot@163.com
 # Filetype:    C source code
 # Environment: Linux & Ubuntu 14.04
 # Tool:        Vim & Gcc
 # Date:        Wed Aug 24 2016 
 # Descprition: condense the image!

================================================*/

#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#include<time.h>

#define COMMAND_SIZE 80

FILE * create_image(char *);
void get_command(char [], char *, char *);
void condense_image(FILE *, FILE *);

int main(int argc, char *argv[])
{
	if(argc!=3){
		printf("Input error! The usage: %s source_file condensed_file
", argv[0]);
		exit(EXIT_FAILURE);
	}

	char command[COMMAND_SIZE] = "ls -l $(pwd) | egrep ";
	get_command(command, argv[1], argv[2]);

	FILE *source_file, *condensed_file;
	
	source_file = create_image(argv[1]);
	condensed_file = fopen(argv[2], "w");

	condense_image(source_file, condensed_file);

	fclose(source_file);
	fclose(condensed_file);

	system(command);
	printf("Bye!
");

	return 0;
}

FILE * create_image(char *filename)
{
	FILE *fp;
	int lsize, wsize, i, j;

	printf("Enter the image size (length * width): ");
	scanf("%d%d", &lsize, &wsize);

	char image[lsize][wsize];

	srand((unsigned int)time(NULL));
	for(i=0;i<lsize;i++)
		for(j=0;j<wsize;j++)
			image[i][j] = rand() % 2 + "0";
	
	fp = fopen(filename, "w+");
	if(fp==NULL){
		printf("Create %s error!
", filename);
		exit(EXIT_FAILURE);
	}

	for(i=0;i<lsize;i++){
		fwrite(image[i], sizeof(char), wsize, fp);
		putc("
", fp);
	}

	return fp;
}

void get_command(char command[COMMAND_SIZE], char *str1, char *str2)
{
	strcat(command, """);
	strcat(command, str1);
	strcat(command, "|");
	strcat(command, str2);
	strcat(command, """);
	
	return;
}

void condense_image(FILE *source, FILE *target)
{
	int ch, prev, count;

	count = 1;
	fseek(source, 0L, SEEK_SET);
	
	prev = getc(source);
	while((ch=getc(source))!=EOF){
		if(ch=="
"){
			putc("
",target);
			continue;
		}

		if(ch!=prev){
			putc(prev, target);
			fprintf(target, "%d", count);
			count = 1;
		}
		else
			count++;
		prev = ch;
	}
	fseek(target, -1L, SEEK_END);
	putc(prev, target);
	fprintf(target, "%d", count);

	return;
}

    上述程序的功能是用户通过命令行参数输入两个文件名,执行程序时,先建立模拟数字图像的文件,全为01序列,图像大小由用户输入,然后将该文件通过上述思路压缩,另存为压缩文件,最后程序在屏幕上列出新建两个文件的信息。

    注意上述程序中使用到了C语言的命令行参数,常规main(void),若要使用命令行参数,则为main(int argc, char *argv[]),其中argc表示参数的个数,argv为指向字符的指针数组,另外为了确保已经产生了两个新建文件,调用了system()函数,可以执行Linux的系统命令,列出两个新建的文件。

    运行结果如下所示:

    用Vim同时打开这两个文件,显示如下:


    如果读者对代码有什么意见或建议,欢迎邮箱或者留言探讨!


声明:该文观点仅代表作者本人,牛骨文系教育信息发布平台,牛骨文仅提供信息存储空间服务。