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].
- 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.
- 3. Revise minprintf to handle more of the other facilities of printf.
- 4. Write a private version of scanf analogous to minprintf from the previous section.
- 5. Rewrite the postfix calculator of Chapter 4 to use scanf and/or sscanf to do the input and number conversion.
- 6. Write a program to compare two files, printing the first line where they differ.
- 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?
- 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.
- ?9. Functions like isupper can be implemented to save space or to save time. Explore both possibilities.
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。
输入类型有:
- 操作数,用 %f 格式扫描
- 操作符,用%c
操作符不用 %s,防止操作符和后面字符没有空格隔开。如果扫描到不是操作符,如果是需要将该字符放回缓冲区,以防是数学函数字符串首字母。这里用 ungetc (在头文件 stdio.h 中) 函数放回。 - 数学函数,用 %s 格式扫描
- 其余均为非法
另注意:
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')
}