C 程序设计语言——第七章练习题

1. Write a program that converts upper case to lower or lower case to upper, depending on the name it is invoked with, as found in argv[0].

#include<stdio.h>
#include<string.h>
#include<ctype.h>
void lower(void);
void upper(void);
int main(int argc, char *argv[])
{
	char s[6];
	if (strstr(argv[0],"lower") != NULL)
		lower();
	else if (strstr(argv[0],"upper") != NULL)
		upper();
	else {
		printf("This is a function for lower or upper\n");
		return -1; 
	}
	return 0;
}
void lower(void)
{
	int c;
	puts("Enter strings, and all characters will be convertd to lower letters:");
	while ((c = getchar()) != EOF)
		putchar(tolower(c));
}
void upper(void)
{
	int c;
	puts("Enter strings, and all characters will be converted to upper letters:");
	while ((c = getchar()) != EOF)
		putchar(toupper(c));
}

输出: 当将程序名字改为 lower时则转换为小写字符,名字为 upper 则转化为 大写字符。

cd-qz@cdqz-KPL-W0X:~/Documents/learnc/course/c-bible/7$ ./lower
Enter strings, and all characters will be convertd to upper letters:
sjdjAJKSJ
sjdjajksj
cd-qz@cdqz-KPL-W0X:~/Documents/learnc/course/c-bible/7$ ./upper
Enter strings, and all characters will be converted to upper letters:
skdja
SKDJA

2. Write a program that will print arbitrary input in a sensible way. As a minimum, it should print non-graphic characters in octal or hexadecimal according to local custom, and break long text lines.

#include<stdio.h>
#include<ctype.h>
#define MAXLEN 20
int main(void)
{
	int c;
	int len = 0;
	while ((c = getchar()) != EOF) {
		if (iscntrl(c) || c == ' ') {
			printf("\\x%03x",c);
			if (c == '\n') {
				putchar('\n');
				len = 0;
			}
			else
				len++;
		}
		else {
			printf("%c", c);
			len++;
		}
		if (len % MAXLEN == 0) {
				putchar('\n');
				len = 0;
		}
	}
	return 0;
}

输出:

kajd sjdk	jsdkj ksdj 
kajd\x020sjdk\x009jsdkj\x020ksdj
\x020\x00a

skjd skdj	jdsk
skjd\x020skdj\x009jdsk\x00a

skjdj dkj
skjdj\x020dkj\x00a

3. Revise minprintf to handle more of the other facilities of printf.

7.3 节,考察可变参数,miniprintf 实现功能和printf 相同。

#include<stdio.h>
#include<stdarg.h>
void miniprintf(char *fmt, ...);
int main(void)
{
	char *a = "Hello World";
	miniprintf("exercise 7.3: %s\n", a);
	return 0;
}
void miniprintf(char *fmt, ...)
{
	va_list ap;
	char *p, *sval;
	int ival;
	double dval;
	va_start(ap, fmt);
	for (p = fmt; *p; p++) {
		if (*p != '%') {
			putchar(*p);
			continue;
		}
		switch(*++p) {
		case 'd':
			ival = va_arg(ap, int);
			printf("%d", ival);
			break;
		case 'f':
			dval = va_arg(ap, double);
			printf("%f", dval);
			break;
		case 's':
			sval = va_arg(ap, char *);
			printf("%s", sval);
			break;
		default:
			putchar(*p);
			break;
		}
	}
	va_end(ap);
}

4. Write a private version of scanf analogous to minprintf from the previous section.

#include<stdio.h>
#include<stdarg.h>
#include<ctype.h>
#include<stdlib.h>
#include<string.h>
void miniscanf(char *fmt, ...);
int main(void)
{
	puts("Please enter day monthname year: ");
	int day;
	char month[10];
	int year;
	miniscanf("%d %s %d\n",&day, month, &year);
	printf("%d %s %d\n",day, month, year);
	return 0;
}
#define BUFSIZE 100
int bufp = 0;
char buf[BUFSIZE];
int getch(void)
{
	return bufp > 0 ? buf[--bufp] : getchar();
}
void ungetch(int c)
{
	if (bufp >= BUFSIZE)
		printf("ungetch: too many characters\n");
	else
		buf[bufp++] = c;
}
void miniscanf(char *fmt,...)
{
	va_list ap;
	char *p;
	va_start(ap, fmt);
	int c, i;
	char s[100];
	int *d;
	char *m;
	int ok = 1;
	int ct;
	for (p = fmt; *p && ok; p++) {
		if (*p == '%') {
			switch (*++p) {
				case 'd':
					while (isspace(c = getch()))
						;
					if (c == EOF) {
						ok = 0;
						break;
					}
					if (!isdigit(c) && c != '-' && c != '+') {
						ungetch(c);
						break;
					}
					i = 0;
					s[i++] = c; 
					ct = (c == '-' || c == '+') ? 0 : 1;//数字的个数
					while ((c = getch()) != EOF && isdigit(c)) {
						ct++;// 计算获取的数字个数,防止只有符号位
						s[i++] = c;
					}
					if (ct == 0) {//肯定有符号位且符号后面没有数字
						ok = 0;//不往后继续扫描
						ungetch(c);
						break;
					}
					s[i] = '\0';
					d = va_arg(ap, int *);
					*d = atoi(s);
					if (c == EOF) {
						ok = 0;
						break;
					}
					else {
						ungetch(c);
						break;
					}
				case 's':
					while (isspace(c = getch()))
						;
					i = 0;
					if ( c == EOF) {
						ok = 0;
						break;
					}
					s[i++] = c;
					while ((c = getch()) != EOF && !isspace(c))
						s[i++] = c;
					s[i] = '\0';
					strcpy(va_arg(ap, char *), s);
					if ( c == EOF) {
						ok = 0;
						break;
					}
					else {
						ungetch(c);
						break;
					}
				default:
					ok = 0;
					break;		
			}
		}
		else if (isspace(*p))
			;
	}
}

输出:

Please enter day monthname year: 
11 November 2019
11 November 2019

5. Rewrite the postfix calculator of Chapter 4 to use scanf and/or sscanf to do the input and number conversion.

分析:写逆波兰计算器程序,用 scanf 或 sscanf 扫描输入字符和实现数字转换。

第四章程序处理输入的函数是 getop,扫描输入字符是通过 getch 逐个字符扫描,数字字符串最后通过函数 atof 转换成数字。

这里需要用 scanf 或 sscanf 来代替 getch,scanf 和 sscanf 都是扫描一个单词,以空白字符或字符串结束符结束一此扫描,遇到非法字符会放回缓冲区。

因为这里要从键盘输入,因此用 scanf 而不是 sscanf。

输入类型有:

扫描二维码关注公众号,回复: 9791172 查看本文章
  1. 操作数,用 %f 格式扫描
  2. 操作符,用%c
    操作符不用 %s,防止操作符和后面字符没有空格隔开。如果扫描到不是操作符,如果是需要将该字符放回缓冲区,以防是数学函数字符串首字母。这里用 ungetc (在头文件 stdio.h 中) 函数放回。
  3. 数学函数,用 %s 格式扫描
  4. 其余均为非法

另注意:
scanf 会跳过空白字符,包括换行符 ‘\n’,那么不能通过换行来输出,改为输入等号 ‘=’ 时输出结果。

而结束程序要通过 EOF。不过每次要输入两次 EOF (ctrl + D)才会结束。因为getop里三扫描了两次,第一次用 scanf 扫描 %f 格式,如果输入 EOF, 不满足,但scanf 只能判断不是浮点数值,不能判断是否是 EOF 输入结束,因此后面继续扫描第二个 scanf %s,如果仍是非法字符,才返回 EOF 退出。

#include<stdio.h>
#include<string.h>
#include<math.h>
#include<stdlib.h>
#define MAXOP 100
#define MATHFUNC 'M'
#define ILLEGAL '?'
#define ANS 'R'
#define NUMBER 'F'
void push(double f);
double pop(void);
void mathfunc(char *s);
int getop(char *s, double *pf);
int main(void)
{
	int type;
	double op2;
	char s[MAXOP];
	double f;
	while ((type = getop(s,&f)) != EOF) {
		switch(type) {
			case NUMBER:
				push(f);
				break;
			case '+':
				push(pop() + pop());
				break;
			case '*':
				push(pop() * pop());
				break;
			case '-':
				op2 = pop();
				push(pop() - op2);
				break;
			case '/':
				op2 = pop();
				if (op2 != 0.0)
					push(pop() / op2);
				else
					printf("Error: zero divisor\n");
				break;
			case '%':
				op2 = pop();
				if (op2 != 0.0)
					push(fmod(pop(), op2));
				else
					printf("Error: zero divisor");
				break;
			case ANS:
				printf("\t%.8g\n",pop());
				break;
			case MATHFUNC:
				mathfunc(s);
				break;
			case ILLEGAL:
				printf("Error: unkonwn command %s\n",s);
				break;
		}
	}
	return 0;
}
#define MAXVAL 100
int sp = 0;
double val[MAXVAL];
void push(double f)
{
	if (sp < MAXVAL)
		val[sp++] = f;
	else
		printf("Error: stack full, can't push %g\n",f);
}
double pop(void)
{
	if (sp > 0)
		return val[--sp];
	else {
		printf("Error: stack empty\n");
		return 0;
	}
}
void mathfunc(char *s)
{
	double op2;
	if (strcmp(s, "sin") == 0)
		push(sin(pop()));
	else if (strcmp(s, "cos") == 0)
		push(cos(pop()));
	else if (strcmp(s, "tan") == 0)
		push(tan(pop()));
	else if (strcmp(s, "pow") == 0) {
		op2 = pop();
		push(pow(pop(), op2));
	}
	else if (strcmp(s, "exp") == 0)
		push(pop());
}
int getop(char *s, double *pf)
{
	int status;
	char  c;
	if ((status = scanf("%lf",pf)) == 1) 
		return NUMBER;
	else if ((status = scanf("%c",&c)) == 1) {
		if (c == '+' || c == '-' || c == '*'\
		|| c == '/' || c == '%') 
		return (int)c;
		else if (c == '=')
			return ANS;
		else {
			ungetc(c, stdin);
			if ((status = scanf("%s", s)) == 1) {
				if (strcmp(s, "sin") == 0 || \
				strcmp(s, "cos") == 0 || strcmp(s, "tan") == 0\
				|| strcmp(s, "pow") == 0 || strcmp(s, "exp") == 0)
					return MATHFUNC;
				else
					return ILLEGAL;
			}
		}
	} 
	else 
		return EOF;
}

输出:

3 4 - sin =
-0.7568025

6. Write a program to compare two files, printing the first line where they differ.

分析:
需要逐行读取文件内容进行比较,输出第一个不相同的行。

如果用 fgets 函数,扫描一行的数据可能不是文件完整的一行。可能扫描第一次内容相同,第二次内容不同,但两次是同一行,这样只输出第二次扫描内容有误。因此用一个指针数组存放被分成不同行的文件的一行。但结束一个循环后要用 free() 释放内存。

#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#define MAXNAME 40
#define MAXLEN 10
#define MAXLINES 100
char *s_gets(char *s, int n);
void Print(char *s[], int n);
void filecmp(FILE *fp1, FILE *fp2);
int main(void)
{
	FILE *fp1, *fp2;
	char fname1[MAXNAME], fname2[MAXNAME];
	printf("Please enter two file name to compare:\n");
	s_gets(fname1, MAXNAME);
	s_gets(fname2, MAXNAME);
	if ((fp1 = fopen(fname1, "r")) == NULL) {
		fprintf(stderr, "Can't open %s\n", fname1);
		exit(EXIT_FAILURE);
	}
	if ((fp2 = fopen(fname2, "r")) == NULL) {
		fprintf(stderr, "Can't open %s\n", fname2);
		exit(EXIT_FAILURE);
	}
	filecmp(fp1, fp2);
	if (fclose(fp1)) {
		fprintf(stderr, "Can't close %s\n", fname1);
		exit(EXIT_FAILURE);
	} 
	if (fclose(fp2)) {
		fprintf(stderr, "Can't close %s\n", fname2);
		exit(EXIT_FAILURE);
	}
	return 0;
}
char *s_gets(char *s, int n)
{
	char *ret, *find;
	ret = fgets(s, n, stdin);
	if (ret) {
		find = strchr(s, '\n');
		if (find)
			*find = '\0';
		else
			while (getchar() != '\n')
				continue;
	}
	return ret;
}
void Print(char *s[], int n)
{
	for (int i = 0; i < n; i++) {
		printf("%s",s[i]);
		free(s[i]);
	}
	putchar('\n');
}
void filecmp(FILE *fp1, FILE *fp2)
{
	char line[MAXLEN];
	char *lineptr1[MAXLINES];
	char *lineptr2[MAXLINES];
	int len;
	int i, j, k;
	int ok = 0, ok1, ok2;
	int find;
	while (ok == 0) {
		ok1 = 0;
		ok2 = 0;
		i = 0;
		j = 0;
	//扫描文件完整的一行
	//注意顺序,先判断 ok1 
		while (ok1 == 0 && fgets(line, MAXLEN, fp1) != NULL) {
			len = strlen(line);
			if (i < MAXLINES) {
				lineptr1[i] = (char *)malloc((len+1) * sizeof(char));
				strcpy(lineptr1[i++], line);
			}
			else {
				printf("Need more space to store a line in file1\n");
				exit(EXIT_FAILURE);
			}
			if (line[len-1] == '\n')
				ok1 = 1;
		}
		while (ok2 == 0 && fgets(line, MAXLEN, fp2) != NULL) {
			len = strlen(line);
			if (j < MAXLINES) {
				lineptr2[j] = (char *)malloc((len+1) * sizeof(char));
				strcpy(lineptr2[j++], line);
			}
			else {
				printf("Need more space to store a line in file2\n");
				exit(EXIT_FAILURE);
			}
			if (line[len-1] == '\n')
				ok2 = 1;
		}
		if (i == 0 && j == 0) {//两个文件都到了末尾
			printf("No different line\n");
			break;
		}
		else if (i == 0 && j != 0) {
			printf("File2 has more lines than file1, the first " 
			"different line in file2:\n");
			Print(lineptr2, j);	
			break;
		}
		else if (i != 0 && j == 0) {
			printf("File1 has more lines than file2, the first " 
			"different line in file1:\n");
			Print(lineptr1, i);	
			break;
		}
		else {
			for (k = 0, find = 0; (find == 0) && k < i && k < j; k++) 
				if (strcmp(lineptr1[k], lineptr2[k]) != 0) 
					find = 1;
			if (find == 0 && k == i && k == j) {
				for (k = 0; k < i; k++) {
					free(lineptr1[k]);
					free(lineptr2[k]);
				}
				continue;
			}
			else {
				printf("The first different line in file1 and file2:\n");
				Print(lineptr1, i);
				Print(lineptr2, j);
				break;
			}
		}
	}
}

程序为了验证文本的行大于设定的一行字符数目时的情况,将 MAXLEN 设为10,测试结果:

Please enter two file name to compare:
1.c
2.c
The first different line in file1 and file2:
#include<string.h> //字符数目大于10,仍然完整显示一行

#include<ctype.h>
Please enter two file name to compare:
1.c
1.c
No different line

7. Modify the pattern finding program of Chapter 5 to take its input from a set of named files or, if no files are named as arguments, from the standard input. Should the file name be printed when a matching line is found?

分析:
程序从命令行识别模式,格式为:
-n
-x
-nx 或 -xn
-n -x 或 -x -n
后面再跟着文件名,如果没有文件名则从键盘输入。

如果遇到:
-xt filename
这种,x 后面有非法字符,那么-x 模式是否生效?是否继续向后识别文件名或终止?
第五章的例题并未将这种情况算作非法,因为且不会继续扫描,结束程序。

但按照书上例题,如果格式为
-xxnn
这种有重复的x或n也算作了合法,因为没有检测有几个x和n。但这种也应该算非法。

程序将例题方案做改进,识别重复x 或 n。

#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#define MAXNAME 40
#define MAXLEN 100
char *s_gets(char *s, int n);
void PrintPattern(FILE *fp, char *p, int x, int n);
int main(int argc, char *argv[])
{
	int c, i, except = 0, number = 0;
	int fnum;
	FILE *fp;
	char *pattern;
	for (i = 1; i < argc && argv[i][0] == '-'; i++) {
		while (c = *++argv[i]) {
			switch (c) {
				case 'x':
					if (except) {
						printf("repeted x, illegal option\n");
						argc = 0;
					}
					else
						except = 1;
					break;
				case 'n':
					if (number) {
						printf("repeated n, illeagal option\n");
						argc = 0;
					}
					else
						number = 1;
					break;
				default:
					printf("find: illeagal option %c\n", c);
					argc = 0;
					break;
			}
		}
	}
	if (argc != 0 && i < argc) {
		pattern = argv[i++];
		if ((fnum = argc - i) > 0) {
			while (fnum--) 
				if ((fp = fopen(argv[i++], "r")) != NULL) {
					printf("file %s:\n",argv[i-1]);
					PrintPattern(fp, pattern, except, number);
				}
				else
					printf("Can't open %s\n",argv[i-1]);
		}
		else {
			printf("No file names, please enter file names (empty line to quit):\n");
			char fname[MAXNAME];
			while (s_gets(fname, MAXNAME) && fname[0] != '\0')
				if ((fp = fopen(fname, "r")) != NULL) {
					printf("file %s:\n", fname);
					PrintPattern(fp, pattern, except, number);
					puts("next file:");
				}
				else
					printf("Can't open %s\n", fname);
		}
	}
	else 
		printf("Usage %s: -x -n filename[s]\n",argv[0]);
	return 0;
}
char *s_gets(char *s, int n)
{
	char *ret, *find;
	ret = fgets(s, n, stdin);
	if (ret) {
		find = strchr(s, '\n');
		if (find)
			*find = '\0';
		else
			while (getchar() != '\n')
				continue;
	}
	return ret;
}
void PrintPattern(FILE *fp, char *p, int x, int n)
{
	char line[MAXLEN];
	long lineno = 0;
	while (fgets(line, MAXLEN, fp) != NULL) {
		lineno++;
		if ((strstr(line, p) != NULL) != x) {
			if (n)
				printf("%ld: ",lineno);
			printf("%s",line);
		}
	} 
	if (fclose(fp))
		printf("Can't close file\n");
}

8. Write a program to print a set of files, starting each new one on a new page, with a title and a running page count for each file.

分析:打印多个文件,每个文件在新的一页打印,且带有文件名和页码。

这样不能用:

while (s_gets(fname, MAXNAME) && fname[0] != '\0')

这种一边输入文件名一边打印文件内容方法不行,只能一次输入所有文件名再打印,可以接受命令行输入,如果没有则键盘输入并通过指针数组保存文件名。

注意: 换页用换页符 '\f’表示,但在屏幕输出并不能体现换页功能,只有连接打印机输出才能换页。

#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#define MAXNAME 40
#define MAXLEN 100
#define MAXFILE 100
char *s_gets(char *s, int n);
void Print(FILE *fp);
int main(int argc, char *argv[])
{
	FILE *fp;
	int i, file = 0, page, len;
	char fname[MAXNAME];
	char *fileptr[MAXFILE];
	//check command-line arguments
	if (argc > 1) {
		for (i = 1, page = 1; i < argc; i++) {
			if ((fp = fopen(argv[i], "r")) != NULL) {
				printf("\ffile: %s	page: %d\n\n", argv[i], page);
				Print(fp);
				page++;
			}
			else 
				printf("\f	Can't open %s\n", argv[i]);
		}
	}
	else {
		printf("Please enter filename (no more than %d, empty line to stop):\n",MAXFILE);
		while (s_gets(fname, MAXNAME) && fname[0] != '\0') {
			len = strlen(fname);
			fileptr[file] = (char *)malloc((len+1) * sizeof(char));
			strcpy(fileptr[file++], fname);
		}
		for (i = 0, page = 1; i < file; i++) {
			if ((fp = fopen(fileptr[i], "r")) != NULL) {
				printf("\ffile: %s	page: %d\n\n",fname, page);
				Print(fp);
				free(fileptr[i]);
				page++;
			}
			else
				printf("\f	Can't open %s\n", fileptr[i]);
		}
	}
	return 0;
}
char *s_gets(char *s, int n)
{
	char *ret, *find;
	ret = fgets(s, n, stdin);
	if (ret) {
		find = strchr(s, '\n');
		if (find)
			*find = '\0';
		else
			while (getchar() != '\n')
				continue;
	}
	return ret;
}
void Print(FILE *fp)
{
	char line[MAXLEN];
	while (fgets(line, MAXLEN, fp) != NULL)
		printf("%s", line);
	fclose(fp);
}

?9. Functions like isupper can be implemented to save space or to save time. Explore both possibilities.

要求:实现 isupper () 函数功能,用两种方法:省空间和省时间

目前不是很理解,stack overflow有问,但答案看不懂。
根据里面回答,对ASCLL 码,省空间方式可以为:

int isupper(char c)
{
	return (c >= 'A' && c <= 'Z')
}
发布了120 篇原创文章 · 获赞 2 · 访问量 5812

猜你喜欢

转载自blog.csdn.net/Lee567/article/details/102996498