《C Primer Plus》编程练习—第13章

本文详细介绍了《C Primer Plus》第13章的编程练习,涵盖了从用户输入文件名到文件操作,包括文件拷贝、字符计数、行合并、错误检查等多个方面,通过实例代码展示了C语言在处理文件和输入输出时的技巧和注意事项。

《C Primer Plus》编程练习

第13章

1.exercise1.c

修改程序count.c中的程序,要求提示用户输入文件名,并读取用户输入的信息,不使用命令行参数。

//exercise13.1
#include <stdio.h>
#include <stdlib.h> 
#define LEN 50
int main(int argc, char *argv[])
{
    int ch;         // 读取文件时,存储每个字符的地方 
    FILE *fp;       // 文件指针
	unsigned long count = 0; 
    char filename[LEN];
    
    printf("请输入要打开的文件名(带上后缀):\n");
    scanf("%s", filename); 
    if ((fp = fopen(filename, "r")) == NULL)//打开文件,r表示读模式 
    {
        printf("打不开%s文件。\n", filename);
        exit(EXIT_FAILURE);
    }
    while ((ch = getc(fp)) != EOF)
    {
        putc(ch,stdout);  //与putchar(ch)相同 
        count++;
    }
    fclose(fp);//关闭文件 
    printf("%s文件有%lu个字符。\n", filename, count);
    
    return 0;
}

1.txt文件是count.c程序创建的,输出示例:

请输入要打开的文件名(带上后缀):
1.txt
hello
open
1.txt文件有11个字符。

2.exercise2.c

编写一个文件拷贝程序,该程序通过命令行获取原始文件名和拷贝文件名。尽量使用标准I/O和二进制模式。

//exercise13.2
#include <stdio.h>
#include <stdlib.h>
#define BUFSIZE 4096//一次读入缓冲区的大小 
int main(int argc, char *argv[])
{
	FILE * source;//原始文件的文件指针 
	FILE * target;//目标文件的文件指针
	size_t bytes;
    static char temp[BUFSIZE]; //只分配一次,存放目标文件内容的临时数组 
	
	if (argc != 3)
	{
		printf("请输入两个命令行参数。\n");
		exit(EXIT_FAILURE);	
	}
	else
	{
		if ((source = fopen(argv[1], "rb")) == NULL)
		{
			printf("打开原始文件%s失败。\n", argv[1]);
			exit(EXIT_FAILURE);
		}
		else
		{
			if ((target = fopen(argv[2], "wb")) == NULL)
			{
				printf("打开目标文件%s失败。\n", argv[2]);
				exit(EXIT_FAILURE);
			}
			else
			{
				while ((bytes = fread(temp,sizeof(char),BUFSIZE,source)) > 0)
			    //temp是待读取数据块的地址;sizeof(char)表示待写入数据块的大小
				//BUFSIZE表示待写入数据块的数量;source表示待读取的文件
				//即把source指向文件的内容写入temp中,返回值应该是BUFSIZE 
				{ 
					fwrite(temp, sizeof(char), bytes, target);
					//temp是待写入数据块的地址;sizeof(char)表示待写入数据块的大小
					//BUFSIZE表示待写入数据块的数量;target表示待写入的文件
					//即把temp中的数据写入target指向的文件 
			    }
				if (ferror(source) != 0)//返回非零值则读写错误,返回0则读写正确 
				{
					fprintf(stderr,"读原始文件%s错误。\n",argv[1]);				 	
				}
 				if (ferror(target) != 0)
 				{
 					fprintf(stderr,"写目标文件%s错误。\n",argv[2]);
			    }
 				if (fclose(source) != 0)//返回非0值则关闭文件错误 
 				{
 					printf("原始文件关闭错误。\n", argv[1]);
				}
				if (fclose(target) != 0)
 				{
 					printf("目标文件关闭错误。\n", argv[2]);
				}
                
			} 
		}
	}
	
	return 0;
} 

先创建 exercise2source.txt文件,在命令行下运行:

D:\桌面\C\第十三章>exercise2 exercise2source.txt exercise2target.txt
拷贝成功,打开exercise2source.txt查看是否与exercise2target.txt一样。

3.exercise3.c

编写一个文件拷贝程序,提示用户输入文本文件名,并以该文件名作为原始文件名和输出文件名。该程序要使用ctype.h中的toupper()函数,在写入到输出文件时把所有文本转换成大写。使用标准I/O和文本模式。

//exercise13.3
#include <stdio.h>
#include <stdlib.h>
#include <ctype.h>
int main(int argc, char *argv[])
{
	FILE * source;//原始文件的文件指针 
	FILE * target;//目标文件的文件指针
    int ch; 
	
	if (argc != 3)
	{
		printf("请输入两个命令行参数。\n");
		exit(EXIT_FAILURE);	
	}
	else
	{
		if ((source = fopen(argv[1], "rb")) == NULL)
		{
			printf("打开原始文件%s失败。\n", argv[1]);
			exit(EXIT_FAILURE);
		}
		else
		{
			if ((target = fopen(argv[2], "wb")) == NULL)
			{
				printf("打开目标文件%s失败。\n", argv[2]);
				exit(EXIT_FAILURE);
			}
			else
			{
			    while ((ch = getc(source)) != EOF)//把字符一个一个拿出来,直到文件末尾 
			    {
			    	putc(toupper(ch), target);//把字母都变成大写,放入目标文件中 
				}
				if (ferror(source) != 0)//返回非零值则读写错误,返回0则读写正确 
				{
					fprintf(stderr,"读原始文件%s错误。\n",argv[1]);				 	
				}
 				if (ferror(target) != 0)
 				{
 					fprintf(stderr,"写目标文件%s错误。\n",argv[2]);
			    }
 				if (fclose(source) != 0)//返回非0值则关闭文件错误 
 				{
 					printf("原始文件关闭错误。\n", argv[1]);
				}
				if (fclose(target) != 0)
 				{
 					printf("目标文件关闭错误。\n", argv[2]);
				}
				printf("转换成功,打开%s查看是否把%s的字母全部变成大写。\n", argv[2], argv[1]); 
			} 
		}
	}
	
	return 0;
} 

先创建 exercise3source.txt文件,在命令行下运行:

D:\桌面\C\第十三章>exercise3 exercise3source.txt exercise3target.txt
转换成功,打开exercise3target.txt查看是否把exercise3source.txt的字母全部变成大写。

4.exercise4.c

编写一个程序,按顺序在屏幕上显示命令行中列出的所有文件。使用argc控制循环。

//exercise13.4
#include <stdio.h>
#include <stdlib.h> 
int main(int argc, char * argv[])
{
	int i;
	FILE *fp;	
	int ch; 
	
	if (argc == 1)
	{
		printf("请输入命令行参数。\n"); 
	}
	else
	{
		for (i = 1; i < argc; i++)//循环打开每个文件 
		{
			if ((fp = fopen(argv[i], "r")) == NULL)
			{
				fprintf(stderr, "%s打开错误!\n", argv[i]);
				exit(EXIT_FAILURE); 
			}
			printf("这是第%d个文件的内容:\n", i);
			while ((ch = getc(fp)) != EOF)//没到文件结尾 
			{
				putchar(ch);//打印文件的每个字符 
				//putc(ch, stdout);也是一样的效果 
			} 
			printf("\n");
			if (fclose(fp) != 0)
			{
				fprintf(stderr, "%s关闭错误!\n", argv[i]);
				exit(EXIT_FAILURE); 
			}
		}
	} 	
	
	return 0;
} 

假设创建好了exercise4_1.txt,exercise4_2.txt文件,分别存放的是英文和中文,输出示例:

D:\桌面\C\第十三章>exercise4 exercise4_1.txt exercise4_2.txt
这是第1个文件的内容:
It is the first file of exercise4.
这是第2个文件的内容:
杩欐槸exercise4鐨勭浜屼釜鏂囦欢銆

可以看出,中文的是乱码,在C语言中,一个汉字占两个字节,而char占1个字节,所以是乱码。

5.exercise5.c

修改程序append.c中的程序,用命令行界面代替交互式界面。

//exercise13.5
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define BUFSIZE 4096
void append(FILE *source, FILE *target);
int main(int argc, char *argv[])
{
    FILE *source,*target;	//target指向目标文件,source指向源文件 
    int files = 0;  // 附加的文件数量 
    int ch;
    int i; 
    
    if (argc < 3)
	{
		printf("请输入两个及以上命令行参数。\n"); 
		exit(EXIT_FAILURE);
	}   
    if ((target = fopen(argv[1], "a+")) == NULL)
    {
        fprintf(stderr, "目标文件%s打开错误!\n", argv[1]);
        exit(EXIT_FAILURE);
    }
    if (setvbuf(target, NULL, _IOFBF, BUFSIZE) != 0)
    {//setvbuf()创建了一个缓冲区,target是待处理的流;NULL是分配的缓冲区;
	//_IOFBF是指完全缓冲;BUFSIZE是缓冲区的大小 
        fputs("没有成功创建缓冲区。\n", stderr);
        exit(EXIT_FAILURE);
    }
    for (i = 2; i < argc; i++)//循环打开每个文件
    {
    	if (strcmp(argv[1], argv[2]) == 0)
    	{
    		fputs("不能追加文件本身。\n",stderr);
		}
    	if ((source = fopen(argv[i], "r")) == NULL)
	    {
	        fprintf(stderr, "源文件%s打开错误!\n", argv[i]);
	        exit(EXIT_FAILURE);
	    }
	    if (setvbuf(source, NULL, _IOFBF, BUFSIZE) != 0)
	    {//setvbuf()创建了一个缓冲区,source是待处理的流;NULL是分配的缓冲区;
		//_IOFBF是指完全缓冲;BUFSIZE是缓冲区的大小 
	        fputs("没有成功创建缓冲区。\n", stderr);
	        exit(EXIT_FAILURE);
	    }
	    append(source, target); 
	    if (ferror(source) != 0)//返回非零值则读写错误,返回0则读写正确
	    {
	    	fprintf(stderr,"读文件%s错误。\n",argv[i]);
		}
		if (ferror(target) != 0)
		{
			fprintf(stderr,"写入文件%s错误。\n",argv[1]);
		}
		if (fclose(source) != 0)//返回非0值则关闭文件错误 
		{
			printf("源文件关闭错误。\n", argv[i]);
		}
        files++;
        printf("文件%s成功追加。\n", argv[i]);
	}
    printf("完成%d个文件的追加。\n", files);
    rewind(target);//回到目标文件开始处 
    printf("文件%s的内容:\n", argv[1]);
    while ((ch = getc(target)) != EOF)
    {
        putchar(ch);	
	}  
    puts("\n程序结束。");
    if (fclose(target) != 0)
	{
		printf("目标文件关闭错误。\n", argv[1]);
	}
    
    return 0;
}
void append(FILE *source, FILE *target)//源文件内容追加到目标文件中 
{
    size_t bytes;
    static char temp[BUFSIZE]; //只分配一次 
    
    while ((bytes = fread(temp,sizeof(char),BUFSIZE,source)) > 0)
    //temp是待读取数据块的地址;sizeof(char)表示待写入数据块的大小
	//BUFSIZE表示待写入数据块的数量;source表示待读取的文件
	//即把source指向文件的内容写入temp中,返回值应该是BUFSIZE 
	{ 
		fwrite(temp, sizeof(char), bytes, target);
		//temp是待写入数据块的地址;sizeof(char)表示待写入数据块的大小
		//BUFSIZE表示待写入数据块的数量;target表示待写入的文件
		//即把temp中的数据写入target指向的文件 
	}
}

创建两个文件exercise5source1.txt,exercise5source3.txt,输出示例:

D:\桌面\C\第十三章>exercise5 exercise5target.txt exercise5source1.txt exercise5source2.txt
文件exercise5source1.txt成功追加。
文件exercise5source2.txt成功追加。
完成2个文件的追加。
文件exercise5target.txt的内容:
It is the first file of exercise5.
It is the second file of exercise5.

程序结束。

6.exercise6.c

使用命令行参数的程序依赖于用户的内存如何正确地使用它们。重写程序reducto.c中的程序,不使用命令行参数,而是提示用户输入所需信息。

//exercise13.6
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#define LEN 40
char * s_gets(char *arr, int n);
int main(void)
{
    char source[LEN];
    char target[LEN];
	FILE *in; 
    FILE *out; 
    char ch;
    int count = 0;
    
    printf("请输入要压缩1/3的文件的文件名:\n");
    s_gets(source, LEN);
    if ((in = fopen(source, "r")) == NULL)
    {
    	fprintf(stderr, "不能打开文件\"%s\"\n",source);
        exit(EXIT_FAILURE);
	}

    strncpy(target,source, LEN - 5); // 拷贝文件名 
    target[LEN - 5] = '\0';
    strcat(target,".red");            // 在文件名后面添加.add 
    if ((out = fopen(target, "w")) == NULL)
    {
        fprintf(stderr,"创建输出文件失败。\n");
        exit(EXIT_FAILURE);
    }
    // 拷贝数据
    while ((ch = getc(in)) != EOF)
    {
    	if (count++ % 3 == 0)
    	{
    		putc(ch, out);  // 打印3个字符中的第1个字符
		}            
	}
	printf("压缩成功,请打开%s与%s进行比较。\n", target, source);
        
    // 收尾工作,关闭文件 
    if (fclose(in) != 0 || fclose(out) != 0)
    {
    	fprintf(stderr,"关闭文件失败。\n");
	}
        
    return 0;
}
char * s_gets(char *arr, int n)
{
	char * ret_val;
	int i = 0;
	
	ret_val = fgets(arr, n ,stdin);
	if (ret_val != NULL)
	{
		while (arr[i] != '\n' && arr[i] != '\0')
		{
			i++;
		}
		if (arr[i] == '\n')
		{
			arr[i] = '\0';
		}
		else
		{
			while (getchar() != '\n')
			{
				continue;
			}
		}
	}
}

先创建一个exercise6source文件,输出示例:

请输入要压缩1/3的文件的文件名:
exercise6source
压缩成功,请打开exercise6source.red与exercise6source进行比较。

7.exercise7.c

编写一个程序打开两个文件。可以使用命令行参数或提示用户输入文件名。
a.该程序以这样的顺序打印:打印第1个文件的第1行,第2个文件的第1行,第1个文件的第2行,第2个文件的第2行,以此类推,打印到行数较多文件的最后一行。
b.修改该程序,把行号相同的行打印成一行。

//exercise13.7a
#include <stdio.h>
#include <stdlib.h>
#define LEN 40
#define BUF 256//一行字符256个
int main(int argc,char *argv[])
{
	FILE *fp1;//文件指针 
	FILE *fp2;
	static char temp1[BUF];//暂时存放文件1的一行的数组 
	static char temp2[BUF];
	char *st1;
	char *st2;
	int length1;
	int length2;
    
	if (argc == 3)
	{
		if ((fp1 = fopen(argv[1], "r")) == NULL)//打开文件 
		{
			fprintf(stderr, "打开文件%s错误。\n", argv[1]);
			exit(EXIT_FAILURE);
		}
		if ((fp2 = fopen(argv[2], "r")) == NULL)
		{
			fprintf(stderr, "打开文件%s错误。\n", argv[2]);
			exit(EXIT_FAILURE);
		}
		st1 = fgets(temp1, BUF, fp1);//获取文件的一行 
		st2 = fgets(temp2, BUF, fp2);
		while (st1 != NULL || st2 != NULL)//除非两个文件都到结尾 
		{
			length1 = strlen(temp1);//文件某一行的长度 
			length2 = strlen(temp2);
			if (temp1[length1 - 1] == '\n')//文件这一行最后是换行符 
			{
				temp1[length1 - 1] = '\0';//把这一行变成数组存起来 
			}
			if (temp2[length2 - 1] == '\n')
			{
				temp2[length2 - 1] = '\0';
			}
			if (st1 != NULL)//文件1还没读完 
			{
				puts(temp1);//打印一行数据 
			}
			if (st2 != NULL)
			{
				puts(temp2);
			}
			st1 = fgets(temp1, BUF, fp1);
			st2 = fgets(temp2, BUF, fp2);	
		}
		if (ferror(fp1) != 0)//读取文件是否成功 
		{
			fprintf(stderr, "读取文件%s失败。", argv[1]);
		}
		if (ferror(fp2) != 0)
		{
			fprintf(stderr, "读取文件%s失败。", argv[2]);
		}
		if (fclose(fp1) != 0)//关闭文件 
		{
			fprintf(stderr, "关闭文件%s失败。", argv[1]);
		}
		if (fclose(fp2) != 0)
		{
			fprintf(stderr, "关闭文件%s失败。", argv[2]);
		}
	}
	else
    {
		printf("请输入两个命令行参数。\n");
		exit(EXIT_FAILURE);
	}
	
    return 0;
}

先要有两个文件exercise7_1.txt和exercise7_2.txt,输出示例:

D:\桌面\C\第十三章>exercise7a exercise7_1.txt exercise7_2.txt
1111
2222
333
4444
55555
6
7777
9999999

其中单数的都是exercise7_1.txt的内容。如果用编译器的话,输入文件名,但是打不开该文件,可能是检索规则的原因,留待后续解决。

//exercise13.7b
#include <stdio.h>
#include <stdlib.h>
#define LEN 40
#define BUF 256
int main(int argc,char *argv[])
{
	FILE *fp1;//文件指针 
	FILE *fp2;
	static char temp1[BUF];//暂时存放文件1的一行的数组 
	static char temp2[BUF];
	char *st1;
	char *st2;
	int length1;
	int length2;
    
	if (argc == 3)
	{
		if ((fp1 = fopen(argv[1], "r")) == NULL)//打开文件 
		{
			fprintf(stderr, "打开文件%s错误。\n", argv[1]);
			exit(EXIT_FAILURE);
		}
		if ((fp2 = fopen(argv[2], "r")) == NULL)
		{
			fprintf(stderr, "打开文件%s错误。\n", argv[2]);
			exit(EXIT_FAILURE);
		}
		st1 = fgets(temp1, BUF, fp1);//获取文件的一行 
		st2 = fgets(temp2, BUF, fp2);
		while (st1 != NULL || st2 != NULL)//除非两个文件都到结尾 
		{
			length1 = strlen(temp1);//文件某一行的长度 
			length2 = strlen(temp2);
			if (temp1[length1 - 1] == '\n')//文件这一行最后是换行符 
			{
				temp1[length1 - 1] = '\0';//把这一行变成数组存起来 
			}
			if (temp2[length2 - 1] == '\n')
			{
				temp2[length2 - 1] = '\0';
			}
			if (st1 != NULL)//文件1还没读完 
			{
				printf("%s", st1);//打印,但不换行 
			}
			if (st2 != NULL)
			{
				printf("%s", st2); 
			}
			printf("\n"); //换行 
			st1 = fgets(temp1, BUF, fp1);
			st2 = fgets(temp2, BUF, fp2);	
		}
		if (ferror(fp1) != 0)//读取文件是否成功 
		{
			fprintf(stderr, "读取文件%s失败。", argv[1]);
		}
		if (ferror(fp2) != 0)
		{
			fprintf(stderr, "读取文件%s失败。", argv[2]);
		}
		if (fclose(fp1) != 0)//关闭文件 
		{
			fprintf(stderr, "关闭文件%s失败。", argv[1]);
		}
		if (fclose(fp2) != 0)
		{
			fprintf(stderr, "关闭文件%s失败。", argv[2]);
		}
	}
	else
    {
		printf("请输入两个命令行参数。\n");
		exit(EXIT_FAILURE);
	}
	
    return 0;
}

输出示例:

D:\桌面\C\第十三章>exercise7b exercise7_1.txt exercise7_2.txt
11112222
3334444
555556
7777
9999999

8.exercise8.c

编写一个程序,以一个字符和任意文件名作为命令行参数。如果字符后面没有参数,该程序读取标准输入;否则,程序依次打开每个文件并报告每个文件中该字符出现的次数。文件名和字符本身也要一同报告。程序应包含错误检查,以确定参数数量是否正确和是否能打开文件。如果无法打开文件,程序应报告这一情况,然后继续处理下一个文件。

//exercise13.8
#include <stdio.h>
#include <stdlib.h>
int count(char ch, FILE *fp);
int main(int argc, char *argv[])
{
	char ch;
	FILE *fp;
	int total;
	int i;
	
	if (argc == 1) 
	{
		printf("请用命令行参数运行程序。\n"); 
	}
	if (strlen(argv[1]) != 1)//第一个命令行参数不止1个字符 
	{
		printf("第一个命令行参数只允许是一个字符。\n"); 
	}
	if (argc == 2)
	{
		ch = argv[1][0];
		fp = stdin;
		
		printf("请输入一串字符(以Crtl+Z结尾):\n");
		total = count(ch, fp);//标准输入 
		printf("输入的字符串中共有%d个%c。\n", total, ch);
	} 
	if  (argc > 2)
	{
		ch = argv[1][0];//获取想要报告的字符 
		for (i = 2;i < argc; i++)
		{
			if ((fp = fopen(argv[i], "r")) == NULL)//打开文件 
			{
				fprintf(stderr, "打开文件%s错误。\n", argv[i]);
				exit(EXIT_FAILURE);
			}
			total = count(ch, fp);
			printf("文件%s共有%d个%c。\n", argv[i], total, ch);
			if (ferror(fp) != 0)//读取文件是否成功 
			{
				fprintf(stderr, "读取文件%s失败。", argv[i]);
			}
			if (fclose(fp) != 0)//关闭文件 
			{
				fprintf(stderr, "关闭文件%s失败。", argv[i]);
			}
		}
	}
	
	return 0;
}
int count(char ch, FILE *fp)//读取文件中的想要找出的字符的个数 
{
	int total = 0;
	char ch2;
	
	while ((ch2 =getc(fp)) != EOF)
	{
		if (ch == ch2)
		{
			total++;
		}
	}
	
	return total;
}

创建文件exercise8_1.txt和exercise8_2.txt,输出示例:

exercise8 a exercise8_1.txt exercise8_2.tx exercise8_2.txt
文件exercise8_1.txt共有3个a。
打开文件exercise8_2.tx失败。
文件exercise8_2.txt共有3个a。

另一个输出示例:

D:\桌面\C\第十三章>exercise8 a
请输入一串字符(以Crtl+Z结尾):
adult apple awe
amount
^Z
输入的字符串中共有4个a。

9.exercise9.c

修改程序addaword.c中的程序,从1开始,根据加入列表的顺序为每个单词编号。当程序下次运行时,确保新的单词编号接着上次的编号开始。

//exercise13.9
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define MAX 41

int main(void)
{
    FILE *fp;
    char words[MAX];
    int count = 0;
    
    if ((fp = fopen("exercise9.txt", "a+")) == NULL)
    {
        fprintf(stdout,"Can't open \"exercise9.txt\" file.\n");
        exit(EXIT_FAILURE);
    }
    while (fgets(words, MAX, fp) != NULL) 
	{
		count++;//每读一个单词就计数加1 
	} 
	rewind(fp);//返回文件开始处 
    
    puts("Enter words to add to the file; press the #");
    puts("key at the beginning of a line to terminate.");
    while ((fscanf(stdin,"%40s", words) == 1)  && (words[0] != '#'))
    {
    	fprintf(fp, "%d:%s\n", count++, words);
		//单词和序号读入文件 
	}   
    
    puts("File contents:");
    rewind(fp);           /* 返回到文件开始处 */
    while (fscanf(fp,"%s",words) == 1)
        puts(words);
    puts("Done!");
    if (fclose(fp) != 0)
        fprintf(stderr,"Error closing file\n");
    
    return 0;
}

第一次运行:

Enter words to add to the file; press the #
key at the beginning of a line to terminate.
hello can you
#
File contents:
1:hello
2:can
3:you
Done!

第二次运行:

Enter words to add to the file; press the #
key at the beginning of a line to terminate.
read
it
#
File contents:
1:hello
2:can
3:you
4:read
5:it
Done!

10.exercise10.c

编写一个程序打开一个文本文件,通过交互方式获得文件名。通过一个循环,提示用户输入一个文件位置。然后该程序打印从该位置开始到下一个换行符之前的内容。用户输入负数或非数值字符可以结束输入循环。

//exercise13.10
#include <stdio.h>
#include <stdlib.h>
#define LEN 81
int main(void)
{
	char filename[LEN];
	FILE *fp;
	long count; 
	char ch;
	
	printf("请输入要打开的文本文件名(包含后缀):\n");
	scanf("%s", filename);
	
	if ((fp = fopen(filename, "rb")) == NULL)
	{
		fprintf(stdout, "文件%s打开失败。\n", filename);
		exit(EXIT_FAILURE);
	}
	printf("请输入文件位置,程序打印该位置开始到下一个换行符的内容");
	printf("(输入负数或非数值字符退出程序):\n");
	while (scanf("%ld", &count) == 1 && count >= 0)
	{
		fseek(fp, count, SEEK_SET);//指定位置 
		ch = getc(fp);
		if (ch == EOF)//先判断该位置是否超过文件结尾了 
		{
			printf("该位置超过文件范围,请重新输入一个位置:\n");
			continue;
		}
		while (ch != '\n' && ch != EOF)//没到换行符或者文件结尾时 
		{
			putchar(ch);//打印
			ch = getc(fp); 
		}
		printf("\n请输入文件位置,程序打印该位置开始到下一个换行符的内容");
		printf("(输入负数或非数值字符退出程序):\n");
	}	
	if (fclose(fp) != 0)
	{
		fprintf(stderr,"关闭文件%s失败。\n", filename);
	}        
	printf("程序结束。\n");
	
	return 0;
}

输出示例:

请输入要打开的文本文件名(包含后缀):
exercise10.txt
请输入文件位置,程序打印该位置开始到下一个换行符的内容(输入负数或非数值字符退出程序)0
this is exercise10.txt
请输入文件位置,程序打印该位置开始到下一个换行符的内容(输入负数或非数值字符退出程序)99
该位置超过文件范围,请重新输入一个位置:
12
cise10.txt
请输入文件位置,程序打印该位置开始到下一个换行符的内容(输入负数或非数值字符退出程序)-1
程序结束。

11.exercise11.c

编写一个程序,接受两个命令行参数。第1个参数是一个字符串,第2个参数是一个文件名。然后该程序查找该文件,打印文件中包含该字符串的所有行。因为该任务是面向行而不是面向字符的,所以要使用fgets()而不是getc()。使用标准C库函数strstr()(11.5.7节简要介绍过)在每一行中查找指定字符串。假设文件中的所有行都不超过255个字符。

//exercise13.11 
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define LEN 256
int main(int argc, char *argv[])
{
    FILE *fp;
    char temp[LEN];

    if (argc != 3)
    {
        fprintf(stderr, "请用命令行输入两个正确的参数。\n", argv[0]);
        exit(EXIT_FAILURE);
    }
    else
	{
		if ((fp = fopen(argv[2], "r")) == NULL)
	    {
	        fprintf(stderr, "打不开文件%s。", argv[2]);
	        exit(EXIT_FAILURE);
	    }
	    while (fgets(temp, LEN, fp) != NULL)//一行一行读入数组 
	    {
	        if (strstr(temp, argv[1]) != NULL)//如果这一行中包含字符串 
	        {
	            fputs(temp, stdout);//打印这一行 
	        }
	    }
	    if (fclose(fp) != 0)
	    {
	        fprintf(stderr, "无法关闭文件%s。", argv[2]);
	    }	
	}   

    return 0;
}

exercise11.txt:

this is exercise11.txt
if
no error
close

输出示例:

D:\桌面\C\第十三章>exercise11 er exercise11.txt
this is exercise11.txt
no error

12.exercise12.c

创建一个文本文件,内含20行,每行30个整数。这些整数都在0-9之间,用空格分开。该文件是用数字表示一张图片,0-9表示逐渐增加的灰度。编写一个程序,把文件中的内容读入一个20X30的int数组中。一种把这些数字转换为图片的粗略方法是:该程序使用数组中的值初始化一个20X31的字符数组,用值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

根据以上描述选择特定的输出字符,最终输出如下:

  #         *%##%*'
   #        *%##%**'
            *%##%*~*'
   #        *%##%* ~*'
  #         *%##%*  ~*'
            *%##%*   ~*'
            *%##%*    ~*'
*************%##%*************
%%%%%%%%%%%%*%##%*%%%%%%%%%%%%
#### #################:#######
%%%%%%%%%%%%*%##%*%%%%%%%%%%%%
*************%##%*************
            *%##%*
            *%##%*    ==
    ''        *%##%*  *=  =*
    ::      *%##%* *=....=*
    ~~        *%##%*  *=  =*
    **      *%##%*    ==
            *%##%*
            *%##%*
//exercise13.12 
#include <stdio.h>
#include <stdlib.h>
#define LEN 81
#define ROWS 20
#define COLS 30
void change(int start[][COLS], char finish[][COLS], int rows);//整形数组转成字符数组 
int main(void)
{
    int start[ROWS][COLS];//数字读入整型数组 
    char finish[ROWS][COLS];//转换成字符数组 
    char filenamestart[LEN],filenamefinish[LEN];//转换前、后的文件名 
	FILE *fpstart,*fpfinish;//转换前后的文件指针 
	int row,col;//行、列 
	
	
	printf("请输入要转换的文件名:\n");
	scanf("%s", filenamestart);
	if ((fpstart = fopen(filenamestart, "r+")) == NULL)
	{
		fprintf(stderr, "打不开文件%s。\n", filenamestart);
		exit(EXIT_FAILURE);
	}
	for (row = 0; row < ROWS; row++)//循环行 
	{
		for (col = 0; col < COLS; col++)//循环列 
		{
			fscanf(fpstart, "%d", &start[row][col]);//文件的数字读入整型数组 
		}		
	}
	if (ferror(fpstart) != 0)
	{
		fprintf(stderr,"读取文件%s失败。\n",filenamestart);
	}
         
	change(start, finish, ROWS);//调用转换函数 
	
	printf("请输入转换后的文件名:\n");
	scanf("%s", filenamefinish);
	if ((fpfinish = fopen(filenamefinish, "w")) == NULL)
	{
		fprintf(stderr, "打不开文件%s。\n", filenamefinish);
		exit(EXIT_FAILURE);
	} 
	printf("转换后的图片:\n");
	for (row = 0; row < ROWS; row++)
	{
		for (col = 0; col < COLS; col++)
		{
			fprintf(fpfinish, "%c", finish[row][col]);//存在转换后的文件 
			fprintf(stdout, "%c", finish[row][col]);//在控制台输出 
		}
		fprintf(fpfinish, "\n");//打印完一个数组就换行 
		fprintf(stdout, "\n");		
	}
	if (ferror(fpfinish) != 0)
	{
		fprintf(stderr,"写入文件%s失败。\n",filenamefinish);
	}
	
	if (fclose(fpstart) != 0)
	{
		fprintf(stderr, "无法关闭文件%s。", filenamestart);
	}
	if (fclose(fpfinish) != 0)
	{
		fprintf(stderr, "无法关闭文件%s。", filenamefinish);
	}

    return 0;
}
void change(int start[][COLS], char finish[][COLS], int rows)
{
	int row;
	int col;
	char change[10]= " .':~*=&%#";//数字对应的字符 
	
	for (row = 0; row < rows; row++)
	{
		for (col = 0; col < COLS; col++)
		{
			finish[row][col] = change[start[row][col]];
		}		
	}
}

输出示例:

请输入要转换的文件名:
exercise12start.txt
请输入转换后的文件名:
exercise12finish.txt
转换后的图片:
  #         *%##%*'
    #       *%##%**'
            *%.#%*~*'
    #       *%##%* ~*'
  #         *%##%*  ~*'
            *%#.%*   ~*'
            *%##%*    ~*'
*************%##%*************
%%%%%%%%%%%%*%##%*%%%%%%%%%%%%
#### #################:#######
%%%%%%%%%%%%*%##%*%%%%%%%%%%%%
*************%##%*************
            *%##%*
            *%##%*    ==
    ''      *%##%*  *=  =*
    ::      *%##%* *=....=*
    ~~      *%##%*  *=  =*
    **      *%##%*    ==
            *%##%*
            *%##%*

用文本文档打开exercise12finish.txt文件,看上去对应不起来,但是用Notepad++打开是正确结果。可能是在文本文件中展示的效果和在控制台输出的效果不一样。

13.exercise13.c

用变长数组(VLA)代替标准数组,完成编程练习12。

//exercise13.13
#include <stdio.h>
#include <stdlib.h>
#define LEN 81
#define ROWS 20
#define COLS 30
void change(int rows, int cols, int start[rows][cols], char finish[rows][cols]); 
int main(void)
{
    int start[ROWS][COLS];//数字读入整型数组 
    char finish[ROWS][COLS];//转换成字符数组 
    char filenamestart[LEN],filenamefinish[LEN];//转换前、后的文件名 
	FILE *fpstart,*fpfinish;//转换前后的文件指针 
	int row,col;//行、列 
	
	
	printf("请输入要转换的文件名:\n");
	scanf("%s", filenamestart);
	if ((fpstart = fopen(filenamestart, "r+")) == NULL)
	{
		fprintf(stderr, "打不开文件%s。\n", filenamestart);
		exit(EXIT_FAILURE);
	}
	for (row = 0; row < ROWS; row++)//循环行 
	{
		for (col = 0; col < COLS; col++)//循环列 
		{
			fscanf(fpstart, "%d", &start[row][col]);//文件的数字读入整型数组 
		}		
	}
	if (ferror(fpstart) != 0)
	{
		fprintf(stderr,"读取文件%s失败。\n",filenamestart);
	}
         
	change(ROWS, COLS, start, finish);//调用转换函数 
	
	printf("请输入转换后的文件名:\n");
	scanf("%s", filenamefinish);
	if ((fpfinish = fopen(filenamefinish, "w")) == NULL)
	{
		fprintf(stderr, "打不开文件%s。\n", filenamefinish);
		exit(EXIT_FAILURE);
	} 
	printf("转换后的图片:\n");
	for (row = 0; row < ROWS; row++)
	{
		for (col = 0; col < COLS; col++)
		{
			fprintf(fpfinish, "%c", finish[row][col]);//存在转换后的文件 
			fprintf(stdout, "%c", finish[row][col]);//在控制台输出 
		}
		fprintf(fpfinish, "\n");//打印完一个数组就换行 
		fprintf(stdout, "\n");		
	}
	if (ferror(fpfinish) != 0)
	{
		fprintf(stderr,"写入文件%s失败。\n",filenamefinish);
	}
	
	if (fclose(fpstart) != 0)
	{
		fprintf(stderr, "无法关闭文件%s。", filenamestart);
	}
	if (fclose(fpfinish) != 0)
	{
		fprintf(stderr, "无法关闭文件%s。", filenamefinish);
	}

    return 0;
}
void change(int rows, int cols, int start[rows][cols], char finish[rows][cols])
//变长数组 
{
	int row;
	int col;
	char change[10]= " .':~*=&%#";//数字对应的字符 
	
	for (row = 0; row < rows; row++)
	{
		for (col = 0; col < cols; col++)
		{
			finish[row][col] = change[start[row][col]];
		}		
	}
}

只需要改一下变长数组即可。输出示例:

请输入要转换的文件名:
exercise13start.txt
请输入转换后的文件名:
exercise13finish.txt
转换后的图片:
  #         *%##%*'
    #       *%##%**'
            *%.#%*~*'
    #       *%##%* ~*'
  #         *%##%*  ~*'
            *%#.%*   ~*'
            *%##%*    ~*'
*************%##%*************
%%%%%%%%%%%%*%##%*%%%%%%%%%%%%
#### #################:#######
%%%%%%%%%%%%*%##%*%%%%%%%%%%%%
*************%##%*************
            *%##%*
            *%##%*    ==
    ''      *%##%*  *=  =*
    ::      *%##%* *=....=*
    ~~      *%##%*  *=  =*
    **      *%##%*    ==
            *%##%*
            *%##%*

14.exercise14.c

数字图像,尤其是从宇宙飞船发回的数字图像,可能会包含一些失真。为编程练习12添加消除失真的函数。该函数把每个值与它上下左右相邻的值作比较,如果该值与其周围相邻值的差都大于1,则用所有相邻值的平均值(四舍五入为整数)代替该值。注意,与边界上的点相邻的点少于4个,所以做特殊处理。

//exercise13.14 
#include <stdio.h>
#include <stdlib.h>
#define LEN 81
#define ROWS 20
#define COLS 30
void change(int start[][COLS], char finish[][COLS], int rows);
int update(int start[][COLS], int rows, int row, int col);  
int main(void)
{
    int start[ROWS][COLS];//数字读入整型数组 
    char finish[ROWS][COLS];//转换成字符数组 
    char filenamestart[LEN],filenamefinish[LEN];//转换前、后的文件名 
	FILE *fpstart,*fpfinish;//转换前后的文件指针 
	int row,col;//行、列 
	
	
	printf("请输入要转换的文件名:\n");
	scanf("%s", filenamestart);
	if ((fpstart = fopen(filenamestart, "r+")) == NULL)
	{
		fprintf(stderr, "打不开文件%s。\n", filenamestart);
		exit(EXIT_FAILURE);
	}
	for (row = 0; row < ROWS; row++)//循环行 
	{
		for (col = 0; col < COLS; col++)//循环列 
		{
			fscanf(fpstart, "%d", &start[row][col]);//文件的数字读入整型数组 
		}		
	}
	if (ferror(fpstart) != 0)
	{
		fprintf(stderr,"读取文件%s失败。\n",filenamestart);
	}
	for (row = 0; row < ROWS; row++)//循环行 
	{
		for (col = 0; col < COLS; col++)//循环列 
		{			
			start[row][col] = update(start, ROWS, row, col);//消除失真 
		}		
	}    
	change(start, finish, ROWS);//调用转换函数 
	
	printf("请输入转换后的文件名:\n");
	scanf("%s", filenamefinish);
	if ((fpfinish = fopen(filenamefinish, "w")) == NULL)
	{
		fprintf(stderr, "打不开文件%s。\n", filenamefinish);
		exit(EXIT_FAILURE);
	} 
	printf("转换后的图片:\n");
	for (row = 0; row < ROWS; row++)
	{
		for (col = 0; col < COLS; col++)
		{
			fprintf(fpfinish, "%c", finish[row][col]);//存在转换后的文件 
			fprintf(stdout, "%c", finish[row][col]);//在控制台输出 
		}
		fprintf(fpfinish, "\n");//打印完一个数组就换行 
		fprintf(stdout, "\n");		
	}
	if (ferror(fpfinish) != 0)
	{
		fprintf(stderr,"写入文件%s失败。\n",filenamefinish);
	}
	
	if (fclose(fpstart) != 0)
	{
		fprintf(stderr, "无法关闭文件%s。", filenamestart);
	}
	if (fclose(fpfinish) != 0)
	{
		fprintf(stderr, "无法关闭文件%s。", filenamefinish);
	}

    return 0;
}
void change(int start[][COLS], char finish[][COLS], int rows)
{
	int row;
	int col;
	char change[10]= " .':~*=&%#";//数字对应的字符 
	
	for (row = 0; row < rows; row++)
	{
		for (col = 0; col < COLS; col++)
		{
			finish[row][col] = change[start[row][col]];
		}		
	}
}
int update(int start[][COLS], int rows, int row, int col)
{
	int i;
	int total = 0;
	int count = 0;
	int note = 0;
    int change[4][2] = {{0, 1}, {1, 0}, {0, -1}, {-1, 0}};
	//该数组用于表示四周的行列号 
    int change_x;
    int change_y;
    int difference;
    
    for (i = 0; i < 4; ++i)//上下左右的值 
    {
        change_x = row + change[i][0];//i=0时,row不变 
		change_y = col + change[i][1];//i=0时,col变,即右边的值 
        if (change_x < 0 || change_x >= ROWS || change_y < 0 || change_y >= COLS)
		{//边界值 
			continue;//此处无点,下一轮循环 
		}
		note++;//记录周围有几个点 
		difference = start[row][col] - start[change_x][change_y];//该点与周围的差值 
        if (difference > 1 || difference < -1)//差值超过1 
        {
            total += start[change_x][change_y];//该点周围值的总和 
            count++;//记录差值大于1的点的个数 
        }
    }
    if (count != note)//周围的点与该值的差值大于1的点不一致 
	{
		return start[row][col];//该点的值不用变,返回该点的值 
	}
	else
	{
		start[row][col] = 1.0 * total / count + 0.5;//计算周围值的平均值来代替该点 
		return start[row][col];
	}
}

输出示例:

请输入要转换的文件名:
exercise14start.txt
请输入转换后的文件名:
exercise14finish.txt
转换后的图片:
            *%##%*:
            *%##%**:
            *%##%*~*:
            *%##%* ~*:
            *%##%*  ~*:
            *%##%*   ~*:
            *%##%*    ~*:
*************%##%*************
%%%%%%%%%%%%*%##%*%%%%%%%%%%%%
##############################
%%%%%%%%%%%%*%##%*%%%%%%%%%%%%
*************%##%*************
            *%##%*
            *%##%*    ==
    ''      *%##%*  *=  =*
    ::      *%##%* *=....=*
    ~~      *%##%*  *=  =*
    **      *%##%*    ==
            *%##%*
            *%##%*
内容概要:本文详细记录了对一个Android ARM64静态ELF文件中字符串加密机制的逆向分析过程。该ELF文件的所有字符串均被加密,无法通过常规strings命令或IDA直接识别。作者通过分析发现,加密字符串存储在.rodata段,其解密所需信息(包括密文地址、长度和16位密钥)保存在.data.rel.ro段的40字节描述符中。核心解密函数sub_10F408采用自反的双pass流密码算法,结合固定密钥KEY_TERM(由.data段24字节数据计算得出),实现字节级非线性、位置与长度相关的加密。文章还复现了完整的Python解密脚本,并揭示了该保护机制的本质为代码混淆而非强加密,最终成功批量解密全部956条字符串,暴露程序真实行为,如shell命令模板、设备标识篡改、网络重置等操作。此外,文中还提及未启用的自定义壳框架及其反dump设计。; 适合人群:具备逆向工程基础的安全研究人员、二进制分析人员及对ELF保护技术感兴趣的开发者。; 使用场景及目标:①学习ELF二进制中字符串加密的典型实现方式与逆向突破口;②掌握从结构识别、函数追踪到算法还原的完整逆向流程;③理解“绑定二进制”的完整性校验设计及其局限性;④实践编写IDAPython脚本自动化提取与解密敏感数据。; 阅读建议:此资源以实战案例驱动,不仅展示技术细节,更强调逆向思维与验证方法,建议读者结合IDA调试环境,逐步跟随文中步骤进行动态分析与算法验证,深入理解每一步的推理依据。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值