C Primer Plus (第五版) 

第十三章 文件输入/输出  

编程练习


  1. 修改程序清单13.1,使之不采用命令行参数,而是请求用户输入文件名并读入用户的响应 

#include <stdio.h>
#include <stdlib.h>
int main(void)
{
	int ch;
	char fname[40];
	FILE * fp;
	long count = 0;

	puts("input filename:");
	gets(fname);
	if ((fp = fopen(fname, "r")) == NULL)
	{
		printf("Can't open %s\n", fname);
		exit(EXIT_FAILURE);
	}
	while ((ch = getc(fp)) != EOF)
	{
		putc(ch, stdout);
		count++;
	}
	fclose(fp);
	printf("File %s has %ld characters\n", fname, count);
	return 0;
}


2.编写一个文件复制程序,程序需要从命令行获得源文件和目的文件名。尽可能使用标准I/O和二进制模式

#include <stdio.h>
#include <stdlib.h>
#define BUFSIZE 1024

int main(int argc, char * argv[])
{
	FILE * fs, *fc;
	char temp[BUFSIZE];
	size_t bytes;

	if (3 != argc)
	{
		fprintf(stderr, "命令行参数错误\n");
		exit(EXIT_FAILURE);
	}
	if ((fs = fopen(argv[1], "rb")) == NULL)
	{//打开源文件流
		fprintf(stderr, "%s文件打开失败\n", argv[1]);
		exit(EXIT_FAILURE);
	}
	if (setvbuf(fs, NULL, _IOFBF, BUFSIZE) != 0)
	{//创建缓冲区与源文件流关联
		fputs("输出缓冲区创建失败\n", stderr);
		exit(EXIT_FAILURE);
	}
	if ((fc = fopen(argv[2], "wb")) == NULL)
	{//打开目标文件流
		fprintf(stderr, "%s文件打开失败", argv[1]);
		exit(EXIT_FAILURE);
	}
	if (setvbuf(fc, NULL, _IOFBF, BUFSIZE) != 0)
	{//创建缓冲区与目标文件流关联
		fputs("输出缓冲区创建失败\n", stderr);
		exit(EXIT_FAILURE);
	}
	while ((bytes = fread(temp, sizeof(char), BUFSIZE, fs)) > 0)
		fwrite(temp, sizeof(char), bytes, fc);
	if (ferror(fs) != 0)
	{
		fprintf(stderr, "读取%s出错。\n", argv[1]);
		exit(EXIT_FAILURE);
	}		
	if (ferror(fc) != 0)
	{
		fprintf(stderr, "写入%s出错。\n", argv[2]);
		exit(EXIT_FAILURE);
	}	
	fclose(fs);
	fclose(fc);
	fprintf(stdout, "文件复制成功。\n");

	return 0;
}


3.编写一个文件复制程序,提示用户输入源文件名和输出文件名。在向输出文件写入时,程序应当使用ctype.h中定义的toupper()函数将所有的文本转换成大写。使用标准I/O和文本模式

#include <stdio.h>
#include <stdlib.h>
#include <ctype.h>

int main(void)
{
	char filenames[40], filenamec[40];
	char ch;
	FILE * fs, *fc;

	printf("请输入源文件名:");
	gets(filenames);
	printf("请输入目标文件名:");
	gets(filenamec);
	if ((fs = fopen(filenames, "r")) == NULL)
	{
		fprintf(stderr, "%s打开失败\n", filenames);
		exit(EXIT_FAILURE);
	}
	if ((fc = fopen(filenamec, "w")) == NULL)
	{
		fprintf(stderr, "%s打开失败\n", filenames);
		exit(EXIT_FAILURE);
	}
	while ((ch = getc(fs)) != EOF)
		putc(ch, fc);
	fclose(fs);
	fclose(fc);

	return EXIT_SUCCESS;
}


4.编写一段程序,依次在屏幕上显示命令行中列出的全部文件。使用argc控制循环

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


int main(int argc, char * argv[])
{

	FILE * fp;
	int i;
	char ch;

	if (2 > argc)
	{
		fprintf(stderr, "命令参数不符\n");
		exit(EXIT_FAILURE);
	}
	for (i = 1; i < argc; i++)
	{
		if ((fp = fopen(argv[i], "r")) == NULL)
		{
			fprintf(stderr, "%s打开失败。\n", argv[i]);
		}
		else
		{
			printf("\n%s内容如下:\n",argv[i]);
			while ((ch = getc(fp)) != EOF)
				putc(ch, stdout);
			fclose(fp);
		}
	}

	return EXIT_SUCCESS;
}


5.修改程序清单13.6中的程序,使用命令行参数(而不是交互式界面)获得文件名。

# include <stdio.h>
# include <stdlib.h>
# include <string.h>
# define BUFSIZE 1024
# define SLEN 81

void append(FILE * source, FILE * dest);

int main(int argc, char * argv[])
{
	FILE * fa, *fs;			
	int files = 0;			
	int i;

	if (3 > argc)
	{
		fprintf(stderr, "Error.\n");
		exit(EXIT_FAILURE);
	}
	if ((fa = fopen(argv[1], "a")) == NULL)
	{
		fprintf(stderr, "Can't open %s\n", argv[1]);
		exit(2);
	}
	if (setvbuf(fa, NULL, _IOFBF, BUFSIZE) != 0)
	{
		fputs("Can't create output buffer\n", stderr);
		exit(3);
	}
	for (i = 2; i < argc; i++)
	{
		if (strcmp(argv[1], argv[i]) == 0)
			fputs("Can't append file to itself\n", stderr);
		else if ((fs = fopen(argv[i], "r")) == NULL)
			fprintf(stderr, "Can't open %s\n", argv[i]);
		else
		{
			if (setvbuf(fs, NULL, _IOFBF, BUFSIZE) != 0)
			{
				fputs("Can't create input buffer\n", stderr);
				continue;
			}
			append(fs, fa);
			if (ferror(fs) != 0)
				fprintf(stderr, "Error in reading file %s.\n", argv[i]);
			if (ferror(fa) != 0)
				fprintf(stderr, "Error in writing file %s.\n", argv[1]);
			fclose(fs);
			files++;
			printf("File %s appended.\n", argv[i]);
		}
	}
	printf("Done. %d files appended.\n", files);
	fclose(fa);

	return 0;
}


void append(FILE * source, FILE * dest)
{
	size_t bytes;
	static char temp[BUFSIZE]; 
	while ((bytes = fread(temp, sizeof(char), BUFSIZE, source)) > 0)
		fwrite(temp, sizeof(char), bytes, dest);
}


6.使用命令行参数的程序要求用户记住正确的使用方法。重写程序清单13.2中的程序,不使用命令行参数,而是提示用记键入所需的信息。

# include <stdio.h>
# include <stdlib.h>
# include <string.h>
# define LEN 40

int main(void)
{
	FILE *in, *out;		
	int ch;
	char namein[LEN];
	char nameout[LEN];		
	int count = 0;

	printf("请输入源文件名:");
	gets(namein);

	if ((in = fopen(namein, "r")) == NULL)	
	{
		fprintf(stderr, "I couldn't open the file \"%S\"\n",namein);
		exit(EXIT_FAILURE);
	}


	int i = 0;
	while (namein[i] != '.' && namein[i] != '\0')
		i++;
	namein[i] = '\0';
	strcpy(nameout, namein);
	strcat(nameout, ".red");		
	if ((out = fopen(nameout, "w")) == NULL)
	{
		fprintf(stderr, "Can't create output file.\n");
		exit(EXIT_FAILURE);
	}

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


	if (fclose(in) != 0 || fclose(out) != 0)
		fprintf(stderr, "Error in closing fille\n");
	printf("文件已压缩到%s\n", nameout);
	return EXIT_FAILURE;
}


7.编写一个打开两个文件的程序。可以用命令行参数或者请求用户输入来获得文件名。

a.让程序打印第一个文件的第一行、第二个文件的第一行,第一个文件的第二行,第二个文件的第二行。依次类推,直到打印完行数较多的文件的最后一行。

b.修改程序,把行号相同的行打印到同一行上。

//pe13-7a
#include <stdio.h>
#include <stdlib.h>
#define LEN 40

void fun_a(FILE * fp1, FILE * fp2);

int main(void)
{
	char fname1[LEN], fname2[LEN];
	FILE * fp1, *fp2;

	printf("请输入第一个文件名:");
	gets(fname1);
	printf("请输入第二个文件名:");
	gets(fname2);
	if ((fp1 = fopen(fname1, "r")) == NULL)
	{
		fprintf(stderr,"%s文件打开失败\n", fname1);
		exit(EXIT_FAILURE);
	}
	if ((fp2 = fopen(fname2, "r")) == NULL)
	{
		fprintf(stderr, "%s文件打开失败\n", fname2);
		exit(EXIT_FAILURE);
	}
	
	fun_a(fp1, fp2);
	fclose(fp1);
	fclose(fp2);

	return 0;
}

void fun_a(FILE * fp1, FILE * fp2)
{
	char ch1 = getc(fp1);
	char ch2 = getc(fp2);
	while (ch1 != EOF || ch2 != EOF)
	{
		while (ch1 != EOF && ch1 != '\n')
		{
			putchar(ch1);
			ch1 = getc(fp1);
		}
		if (ch1 != EOF)
		{
			putchar('\n');
			ch1 = getc(fp1);
		}
		while (ch2 != EOF && ch2 != '\n')
		{
			putchar(ch2);
			ch2 = getc(fp2);
		}
		if (ch2 != EOF)
		{
			putchar('\n');
			ch2 = getc(fp2);
		}
	}
}
//pe13-7b
#include <stdio.h>
#include <stdlib.h>
#define LEN 40

void fun_b(FILE * fp1, FILE * fp2);

int main(void)
{
	char fname1[LEN], fname2[LEN];
	FILE * fp1, *fp2;

	printf("请输入第一个文件名:");
	gets(fname1);
	printf("请输入第二个文件名:");
	gets(fname2);
	if ((fp1 = fopen(fname1, "r")) == NULL)
	{
		fprintf(stderr,"%s文件打开失败\n", fname1);
		exit(EXIT_FAILURE);
	}
	if ((fp2 = fopen(fname2, "r")) == NULL)
	{
		fprintf(stderr, "%s文件打开失败\n", fname2);
		exit(EXIT_FAILURE);
	}

	fun_b(fp1, fp2);
	fclose(fp1);
	fclose(fp2);

	return 0;
}


void fun_b(FILE * fp1, FILE * fp2)
{
	char ch1 = getc(fp1);
	char ch2 = getc(fp2);
	while (ch1 != EOF || ch2 != EOF)
	{
		while (ch1 != EOF && ch1 != '\n')
		{
			putchar(ch1);
			ch1 = getc(fp1);
		}
		if (ch1 != EOF)
		{
			putchar(' ');
			ch1 = getc(fp1);
		}
		else
			putchar('\n');
		while (ch2 != EOF && ch2 != '\n')
		{
			putchar(ch2);
			ch2 = getc(fp2);
		}
		if (ch2 != EOF)
		{
			putchar('\n');
			ch2 = getc(fp2);
		}
	}
}


8.编写一段程序,将一个字符、零个或多个文件名作为命令行参数。如果字符后没有参跟随,程序读取标准输入文件。否则,程序依次打开每个文件,然后报告每个文件中该字符的出现次数。文件名和字符本身也与计算值一起报告。程序中包括错误检查,以确定参数数目是否正确和是否能打开文件。如果不能打开文件,程序要报告这一情况然后继续处理下一文件。

#include <stdio.h>
#include <stdlib.h>
#define LEN 40
int main(int argc, char * argv[])
{
	char ch1, ch2;
	FILE * fp;
	int count;

	if (argc < 2)
	{
		fprintf(stderr, "参数不符\n");
		exit(EXIT_FAILURE);
	}
	ch1 = argv[1][0];
	if (argc == 2)
	{
		char filename[LEN];
		printf("请输入文件名:");
		while (gets(filename))
		{
			if ((fp = fopen(filename, "r")) == NULL)
			{
				fprintf(stderr, "文件%s打开失败!\n", filename);
				continue;
			}
			count = 0;
			while ((ch2 = getc(fp)) != EOF)
			{
				if (ch1 == ch2)
					count++;
			}
			printf("字符 %c 在文件 %s 中出现 %d 次.\n", ch1, filename, count);
			fclose(fp);
		}
	}
	else
	{
		int i;
		for (i = 2; i < argc; i++)
		{
			if ((fp = fopen(argv[i], "r")) == NULL)
			{
				fprintf(stderr, "文件%s打开失败!\n", argv[i]);
				continue;
			}
			count = 0;
			while ((ch2 = getc(fp)) != EOF)
			{
				if (ch1 == ch2)
					count++;
			}
			printf("字符 %c 在文件 %s 中出现 %d 次.\n", ch1, argv[i], count);
			fclose(fp);
		}
	}
	return 0;
}


9.修改程序清单13.3 中的程序,从1开始,根据加入列表的顺序为每个单词编号。当再次运行程序时,确保新的单词号接着前面的编号开始。

# include <stdio.h>
# include <stdlib.h>
# define MAX 8

int main(void)
{
	FILE * fp;
	char words[MAX];
	int i = 0;
	char ch;

	if ((fp = fopen("f:\\test\\words.txt", "a+")) == NULL)
	{
		fprintf(stdout, "Can't open \"words\" file.\n");
		exit(EXIT_FAILURE);
	}

	puts("Enter words to add to the file: press the Enter");
	puts("key at the beginning lf a line to terminate.");
	while ((ch = getc(fp)) != EOF)
		if(ch == '\n') i++;
	while (gets(words) != NULL && words[0] != '\0')
		fprintf(fp, "%d. %s\n", ++i, words); 
		
	puts("File contentes: ");
	rewind(fp);	
	while (fgets(words, MAX, fp) != NULL)
	//当一行超过8个字符时下一次会接着读取剩下的内容,所以会显示整行而不是一行的前7个字符
		fputs(words,stdout);
	if (fclose(fp) != 0)
		fprintf(stderr, "Error closing file\n");

	return 0;
}


10. 编写一个程序,打开一个文本文件,文件名通过交互方式获得。建立一个循环,请求用户输入一个文件位置。然后程序打印文件中从该位置开始到下一换行符之间的部分。用户通过输入非数字字符来终止输入循环。

#include <stdio.h>
#include <stdlib.h>
#define LEN 40

int main(void)
{
	char filename[LEN];
	FILE * fp;
	char ch;
	long int cur, end;

	printf("请输入文件名:");
	gets(filename);
	if ((fp = fopen(filename, "r")) == NULL)
	{
		fprintf(stderr, "%s打开失败\n");
		exit(EXIT_FAILURE);
	}
	fseek(fp, 0L, SEEK_END);
	end = ftell(fp);
	printf("请输入一个位置:");
	while (scanf("%d", &cur) == 1 )
	{	
		if (cur < end && cur >= 0)
		{
			fseek(fp, cur, SEEK_SET);
			while ((ch = getc(fp)) != '\n')
			{
				if (ch != EOF)
					putchar(ch);
			}
			putchar(ch);
		}
		else
			printf("位置超出文件范围。\n");
		
		printf("请输入下一个位置(输入q退出):");
	}
	
	return EXIT_SUCCESS;
}


11. 编写一个程序,接受两个命令行参数。第一个参数为一个字符串;第二个为文件名。程序打印文件中包含该字符串的的所有行。因为这一任务是面向行而不是面向字符的,所以要使用fgets()而不是getc()。使用标准C库strstr()(在第11章的练习7中简要描述过)在每一行中搜索这一字符串。

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define LEN 256

int main(int argc, char * argv[])
{
	char string[LEN];
	FILE * fp;

	if (argc != 3)
	{
		fprintf(stderr, "参数错误!\n");
		exit(EXIT_FAILURE);
	}
	if ((fp = fopen(argv[2], "r")) == NULL)
	{
		fprintf(stderr, "%s打开失败!\n", argv[2]);
		exit(EXIT_FAILURE);
	}
	while (fgets(string, LEN - 1, fp))
	{
		if (strstr(string, argv[1]))
			fputs(string, stdout);
	}
	fclose(fp);

	return 0;
}


12.创建一个包含20行,每行30个整数的文本文件。整数在0到9之间,用空格分开。该文件是一个图片的数字表示,从0到9的值代表逐渐增加的灰度。编写一个程序,将文件的内容读入到一个20*30的int数组中。一种将这种数字表示转化成图片的粗略方法就是让程序使用数组中的数值来初始化一个20*31的字符阵列。0对应空格。1对应句号字符,依此类推,较大的值对应占用空间较多的字符。比如。可以使用#代表9.每行的最后一个字符(第31个)为空字符,这样数组将包含20个字符串。然后程序显示结果图片(即打印这些字符串),并将结果存入一个文本文件中。例如,如果开始的数据为:

0 0 9 0 0 0 0 0 0 0 0 0 5 8 9 9 8 5 2 0 0 0 0 0 0 0 0 0 0 0

0 0 0 0 9 0 0 0 0 0 0 0 5 8 9 9 8 5 5 2 0 0 0 0 0 0 0 0 0 0

0 0 0 0 0 0 0 0 0 0 0 0 5 8 1 9 8 5 4 5 2 0 0 0 0 0 0 0 0 0

0 0 0 0 9 0 0 0 0 0 0 0 5 8 9 9 8 5 0 4 5 2 0 0 0 0 0 0 0 0

0 0 9 0 0 0 0 0 0 0 0 0 5 8 9 9 8 5 0 0 4 5 2 0 0 0 0 0 0 0

0 0 0 0 0 0 0 0 0 0 0 0 5 8 9 1 8 5 0 0 0 4 5 2 0 0 0 0 0 0

0 0 0 0 0 0 0 0 0 0 0 0 5 8 9 9 8 5 0 0 0 0 4 5 2 0 0 0 0 0

5 5 5 5 5 5 5 5 5 5 5 5 5 8 9 9 8 5 5 5 5 5 5 5 5 5 5 5 5 5

8 8 8 8 8 8 8 8 8 8 8 8 5 8 9 9 8 5 8 8 8 8 8 8 8 8 8 8 8 8

9 9 9 9 0 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 3 9 9 9 9 9 9 9

8 8 8 8 8 8 8 8 8 8 8 8 5 8 9 9 8 5 8 8 8 8 8 8 8 8 8 8 8 8

5 5 5 5 5 5 5 5 5 5 5 5 5 8 9 9 8 5 5 5 5 5 5 5 5 5 5 5 5 5

0 0 0 0 0 0 0 0 0 0 0 0 5 8 9 9 8 5 0 0 0 0 0 0 0 0 0 0 0 0

0 0 0 0 0 0 0 0 0 0 0 0 5 8 9 9 8 5 0 0 0 0 6 6 0 0 0 0 0 0

0 0 0 0 2 2 0 0 0 0 0 0 5 8 9 9 8 5 0 0 5 6 0 0 6 5 0 0 0 0

0 0 0 0 3 3 0 0 0 0 0 0 5 8 9 9 8 5 0 5 6 1 1 1 1 6 5 0 0 0

0 0 0 0 4 4 0 0 0 0 0 0 5 8 9 9 8 5 0 0 5 6 0 0 6 5 0 0 0 0

0 0 0 0 5 5 0 0 0 0 0 0 5 8 9 9 8 5 0 0 0 0 6 6 0 0 0 0 0 0

0 0 0 0 0 0 0 0 0 0 0 0 5 8 9 9 8 5 0 0 0 0 0 0 0 0 0 0 0 0

0 0 0 0 0 0 0 0 0 0 0 0 5 8 9 9 8 5 0 0 0 0 0 0 0 0 0 0 0 0

对于一种特定的输出字符选择,输出是这样的:

/*此处为题目的一部分,因为排版不对称所以用代码来显示
  #         *%##%*'
    #       *%##%**'
            *%.#%*~*'
    #       *%##%* ~*'
  #         *%##%*  ~*'
            *%#.%*   ~*'
            *%##%*    ~*'
*************%##%*************
%%%%%%%%%%%%*%##%*%%%%%%%%%%%%
#### #################:#######
%%%%%%%%%%%%*%##%*%%%%%%%%%%%%
*************%##%*************
            *%##%*
            *%##%*    ==
    ''      *%##%*  *=  =*
    ::      *%##%* *=....=*
    ~~      *%##%*  *=  =*
    **      *%##%*    ==
            *%##%*
            *%##%*
*/
#include <stdio.h>
#include <stdlib.h>
#define FNLEN 40

int main(void)
{
	char fname[FNLEN];
	FILE * fp;
	char ch;

	printf("请输入文件名:");
	gets(fname);

	if ((fp = fopen(fname, "r")) == NULL)
	{
		fprintf(stderr, "%s打开失败!\n", fname);
		exit(EXIT_FAILURE);
	}
	while ((ch = getc(fp)) != EOF)
	{
		switch (ch)
		{
		case '0': putchar(' '); break;
		case '1': putchar('.'); break;
		case '2': putchar('\''); break;
		case '3': putchar(':'); break;
		case '4': putchar('~'); break;
		case '5': putchar('*'); break;
		case '6': putchar('='); break;
		case '7': putchar('+'); break;
		case '8': putchar('%'); break;
		case '9': putchar('#'); break;
		case ' ':  break;
		default:putchar(ch);
		}
	}
	printf("\n");
	return 0;
}


13.数字图像,尤其是从宇宙飞船发回的数字图像可能会包含尖峰脉冲。为第12道编程练习题添加消除尖峰脉冲的函数。该函数应该将每一个值和它上下左右的相邻值比较,如果该值与它周围每个值的差都大于1,就用所有相邻值的平均值(取与其最接近的整数)取代这个值。注意到边界上的点的相邻点少于4个,所以它们需要特殊处理。

#include <stdio.h>
#include <stdlib.h>
#define FNLEN 40
#define ROWS 22
#define COLS 32

int main(void)
{
	char fname[FNLEN];
	int array[ROWS][COLS] = { 0 };
	FILE * fp;
	int i, j;
	printf("请输入文件名:");
	gets(fname);

	if ((fp = fopen(fname, "rb")) == NULL)
	{
		fprintf(stderr, "%s打开失败!\n", fname);
		exit(EXIT_FAILURE);
	}
	for (i = 1; i < ROWS - 1; i++)
	for (j = 1; j < COLS - 1; j++)
		fscanf(fp, "%d", &array[i][j]);

	for (i = 1; i < ROWS - 1; i++)
	for (j = 1; j < COLS - 1; j++)
	{
		int a = array[i][j] - array[i - 1][j];
		int b = array[i][j] - array[i + 1][j];
		int c = array[i][j] - array[i][j - 1];
		int d = array[i][j] - array[i][j + 1];
		if (a > 1 && b > 1 && c > 1 && d > 1)
		{	//当前数与每个相邻数的差值都大于1时,改变当前值为所有相邻值的平均值 
			int e = array[i - 1][j] + array[i - 1][j] + array[i - 1][j] + array[i - 1][j];
			if (e)
			{
				if ((i == 1 && j == 1) || (i == 1 && j == 30) || (i == 20 && j == 1) || (i == 20 && j == 30))
					array[i][j] = e / 2;	//四个角相邻数只有两个
				else if (i == 1 || i == 20 || j == 1 || j == 30)
					array[i][j] = e / 3;	//四个边相邻数只有三个
				else
					array[i][j] = e / 4;
			}
			else
				array[i][j] = e;	//与每个相邻的数差值都大于1,且每个相邻数都为0
		}
	}

	for (i = 1; i < ROWS - 1; i++)
	{
		for (j = 1; j < COLS - 1; j++)
		{
			switch (array[i][j])
			{
			case 0: putchar(' '); break;
			case 1: putchar('.'); break;
			case 2: putchar('\''); break;
			case 3: putchar(':'); break;
			case 4: putchar('~'); break;
			case 5: putchar('*'); break;
			case 6: putchar('='); break;
			case 7: putchar('+'); break;
			case 8: putchar('%'); break;
			case 9: putchar('#'); break;
			default:printf("Error!");	//当出现与期望值不符时用default输出内容来提醒。 
			}
		}
		printf("\n");
	}
	printf("结束!\n");
	return 0;
}