7-1:
编写一个程序,根据它自身被调用时存放在argv[0]中的名字,实现将大写字母转换为小写字母或将小写字母转换为大写字母的功能
/*gcc编译时会生成一个名为a.exe的程序,我们以前在命令行参数程序中argv[0]输入的就是a.exe
此处采用gcc的-o命令改变生成的程序文件名,例如以前输入gcc xxx.c生成a.exe程序文件,现在采用-o命令,
输入gcc xxx.c -o u生成u.exe程序文件,那么argv[0][0]的值为u,同样可将程序文件名通过此方法改为
l.exe,那么argv[0][0]的值为l*/
#include <stdio.h>
#include <ctype.h>
int main(int argc, char **argv)
{
int (*pfun)(int);
int ch;
if(argc == 1)
{
if(argv[0][0] == 'u')
{
pfun = toupper;
}
else
{
pfun = tolower;
}
while((ch = getchar()) != EOF)
{
ch = (*pfun)(ch);
putchar(ch);
}
}
else
{
printf("parameter error!\n");
return -1;
}
return 0;
}
7-2:
编写一个程序,以合理的形式打印打印任何输入。该程序至少能够根据用户的习惯以八进制或十六进制打印非图形字符,并截断长文本行
/*我们将ASCII码值为0-31的非打印字符以八进制或者十六进制的数字字符输出,保留换行符不变*/
#include <stdio.h>
#define OCT 8
#define HEX 16
#define MAXLEN 10
int main(void)
{
int n, ch, all;
printf("请输入以什么进制打印非图形字符(o为八进制,h为十六进制):\n");
ch = getchar();
while(getchar() != '\n')
continue;
if(ch == 'o')
n = OCT;
else if(ch == 'h')
n = HEX;
else
n = OCT;
all = 0;
while((ch = getchar()) != EOF)
{
if(ch >= 0 && ch <= 31 && ch != '\n')
{
if(all + 4 <= MAXLEN)
all += 4;
else
putchar('\n');
if(n == OCT)
printf("o%-3o", ch);
else
printf("0x%-2x", ch);
}
else
{
putchar(ch);
all++;
}
if(all % MAXLEN == 0)
{
putchar('\n');
all = 0;
}
}
return 0;
}
7-3:
改写minprintf函数,使它能够完成printf函数的更多功能
#include <stdio.h>
#include <ctype.h>
#include <stdarg.h>
void minprintf(char *fmt, ...);
int main(void)
{
minprintf();//此处自行添加参数,和printf函数的参数一样
return 0;
}
void minprintf(char *fmt, ...)
{
va_list ap;
char pfmt[50];
char *p, *sval;
int ival;
double dval;
unsigned uval;
va_start(ap, fmt);
for(p = fmt; *p; p++)
{
if(*p != '%')
{
putchar(*p);
continue;
}
for(int i = 0; i < 50 && !isalpha(*p); i++, p++)//将%以及其后的非字母字符储存在数组中
pfmt[i] = *p;
pfmt[i++] = *p;//储存第一个字母字符
pfmt[i] = '\0';
//printf函数可以直接接受一个字符数组名,它会将数组名替换为"数组元素"形式,从而打印
switch(*p)
{
case 'i':
case 'd':
case 'c':
ival = va_arg(ap, int);
printf(pfmt, ival);
break;
case 'x':
case 'X':
case 'u':
case 'o':
uval = va_arg(ap, unsigned);
printf(pfmt, uval);
break;
case 'f':
case 'e':
case 'E':
case 'g':
case 'G':
dval = va_arg(ap, double);
printf(pfmt, dval);
break;
case 's':
case 'p':
sval = va_arg(ap, char *);
printf(pfmt, sval);
break;
default:
printf(pfmt);
break;
}
}
va_end(ap);
}
7-4:
类似于上一节中的函数minprintf,编写scanf函数的一个简化版本
#include <stdio.h>
#include <ctype.h>
#include <stdarg.h>
void minscanf(char *fmt, ...);
int main(void)
{
int i;
unsigned u;
char c;
char s[10];
float f;
scanf("%d %c %u %s %f", &i, &c, &u, s, &f);
printf("%d %c %u %s %f\n", i, c, u, s, f);
return 0;
}
void minscanf(char *fmt, ...)
{
va_list ap;
char pfmt[50];
char *p, *sval;
int *ival;
double *dval;
unsigned *uval;
va_start(ap, fmt);
for(p = fmt; *p; p++)
{
if(isspace(*p))
{
continue;
}
if(*p != '%')
{
printf("error: invalid parameter\n");
return;
}
for(int i = 0; i < 50 && !isalpha(*p); i++, p++)//将%以及其后的非字母字符储存在数组中
pfmt[i] = *p;
pfmt[i++] = *p;//储存第一个字母字符
pfmt[i] = '\0';
//printf函数可以直接接受一个字符数组名,它会将数组名替换为"数组元素"形式,从而打印
switch(*p)
{
case 'i':
case 'd':
case 'o':
case 'x':
ival = va_arg(ap, int *);
scanf(pfmt, ival);
break;
case 'u':
uval = va_arg(ap, unsigned *);
scanf(pfmt, uval);
break;
case 'f':
case 'e':
case 'g':
dval = va_arg(ap, double *);
scanf(pfmt, dval);
break;
case 'c':
case 's':
sval = va_arg(ap, char *);
scanf(pfmt, sval);
break;
default:
scanf(pfmt);
break;
}
}
va_end(ap);
}
7-5:
改写第4章的后缀计算器程序,用scanf函数和(或)sscanf函数实现输入以及数的转换
/*该程序是比较符合书本的,但是有一个重要的问题就是输入+号或者-号时要连续输
入两次,因为scanf("%lf", &number)允许输入+或者-,所以第一次输入的+或-会被
第一个scanf丢弃,然后第二个scanf读取其后的字符,同时也不能将if和else if的
顺序调换,因为输入数字时会以字符形式被第一个scanf读取*/
#include <stdio.h>
#include <math.h>
#include <ctype.h>
#include <stdlib.h>
#define RAD_TO_DEG (180 / (4 * atan(1)))//将弧度转换为角度的公式,即1弧度 = 180 / π 度,其中π = 4 * atan(1)
#define MAXOP 100
#define NUMBER '0'
#define MAXVAL 100
#define MAXVAR 26
int sp = 0;
double val[MAXVAL];
void push(double);
double pop(void);
void printf_stack_top(void);
void copy_stack_top(void);
void change_stack_top(void);
void empty_stack(void);
int main(void)
{
int i;
char type;
double number = 0;
char s[MAXOP];
double op2;
double prev; //记录最近打印的值
double var[MAXVAR];//包含26个单个英文字母的变量数组
int print = 0; //记录最近是否有打印的值
int mask = 0; //标记变量是否已赋值
for(i = 0; i < MAXVAR; i++)//对每个变量的值进行初始化
var[i] = 0.0;
while(1)
{
if(scanf("%lf", &number) == 1)
{
push(number);
}
else if(scanf("%c", &type) == 1)
{
switch(type)
{
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((int)pop() % (int)op2);
else
printf("error: zero divisor\n");
break;
case 'd'://打印栈顶元素
printf_stack_top();
break;
case 'f'://复制栈顶元素
copy_stack_top();
break;
case 'j'://交换栈顶两个元素的值
change_stack_top();
break;
case 'q'://清空栈
empty_stack();
break;
case 's'://sin函数,注意sin、cos、tan接受的参数为弧度,不是角度
push( sin( (pop() / RAD_TO_DEG) ) );//将输入的角度转化为弧度
break;
case 'c'://cos函数
push( cos( (pop() / RAD_TO_DEG) ) );
break;
case 't'://tan函数
op2 = pop();
if(op2 != 90)
push( tan( (pop() / RAD_TO_DEG) ) );
else
printf("tan90 is invalid value\n");
break;
case 'e'://exp函数,返回e的指数幂
push( exp( pop() ) );
break;
case 'p'://pow函数,返回参数1的参数2次幂,即假设pow(2, 3),返回2^3的值
op2 = pop();
push( pow( pop(), op2 ) );
break;
case 'v':
if(print)
printf("The most recently printed value is %.8g\n", prev);
else
printf("No recently printed values!\n");
break;
case '=':
prev = pop();
print = 1;
printf("\t%.8g\n", prev);
break;
default:
if(type >= 'A' && type <= 'Z')
{
if(mask == 0)
{
var[type - 'A'] = pop();
mask = 1;
}
else
push(var[type - 'A']);
}
else
{
printf("error: unkown command %c\n", type);
return 0;
}
break;
}
}
else
break;
}
return 0;
}
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.0;
}
}
void printf_stack_top(void)
{
if(sp > 0)
printf("top of stack: %8g\n", val[sp - 1]);
else
printf("stack is empty\n");
}
void copy_stack_top(void)
{
double temp = pop();//先弹出栈顶元素,将其拷贝给临时变量,将该变量压入两次,完成复制工作
push(temp);
push(temp);
printf("Done!\n");
}
void change_stack_top(void)
{
double temp1 = pop();//temp1是栈顶第一个元素
double temp2 = pop();//temp2是栈顶第二个元素
push(temp1);//temp1先压入,temp2后压入,temp1变成栈顶第二个元素,temp2变成栈顶第一个元素
push(temp2);
}
void empty_stack(void)
{
sp = 0;
}
/*这是此程序的第二个版本,但是实际上和我们之前用getline函数替换getch和ungetch的联系本质上是一样的*/
#include <stdio.h>
#include <string.h>
#include <math.h>
#include <ctype.h>
#include <stdlib.h>
#define RAD_TO_DEG (180 / (4 * atan(1)))//将弧度转换为角度的公式,即1弧度 = 180 / π 度,其中π = 4 * atan(1)
#define MAXOP 100
#define NUMBER '0'
#define MAXVAL 100
#define MAXVAR 26
#define NUMBERSIZE 100
int sp = 0;
char number[NUMBERSIZE];//储存输入的数字字符
double val[MAXVAL];
int index = 0; //index为s字符数组的下标,是一个外部变量,可以跨函数使用
int getop(char []);
void push(double);
double pop(void);
void printf_stack_top(void);
void copy_stack_top(void);
void change_stack_top(void);
void empty_stack(void);
int main(void)
{
int type, i;
double op2;
double prev; //记录最近打印的值
char s[MAXOP];
double var[MAXVAR];//包含26个单个英文字母的变量数组
int print = 0; //记录最近是否有打印的值
int mask = 0; //标记变量是否已赋值
for(i = 0; i < MAXVAR; i++)//对每个变量的值进行初始化
var[i] = 0.0;
while((type = getop(s)) != EOF)
{
switch(type)
{
case NUMBER:
push(atof(number));
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((int)pop() % (int)op2);
else
printf("error: zero divisor\n");
break;
case 'd'://打印栈顶元素
printf_stack_top();
break;
case 'f'://复制栈顶元素
copy_stack_top();
break;
case 'j'://交换栈顶两个元素的值
change_stack_top();
break;
case 'q'://清空栈
empty_stack();
break;
case 's'://sin函数,注意sin、cos、tan接受的参数为弧度,不是角度
push( sin( (pop() / RAD_TO_DEG) ) );//将输入的角度转化为弧度
break;
case 'c'://cos函数
push( cos( (pop() / RAD_TO_DEG) ) );
break;
case 't'://tan函数
op2 = pop();
if(op2 != 90)
push( tan( (pop() / RAD_TO_DEG) ) );
else
printf("tan90 is invalid value\n");
break;
case 'e'://exp函数,返回e的指数幂
push( exp( pop() ) );
break;
case 'p'://pow函数,返回参数1的参数2次幂,即假设pow(2, 3),返回2^3的值
op2 = pop();
push( pow( pop(), op2 ) );
break;
case 'v':
if(print)
printf("The most recently printed value is %.8g\n", prev);
else
printf("No recently printed values!\n");
break;
case '=':
prev = pop();
print = 1;
printf("\t%.8g\n", prev);
break;
default:
if(type >= 'A' && type <= 'Z')
{
if(mask == 0)
{
var[type - 'A'] = pop();
mask = 1;
}
else
push(var[type - 'A']);
}
else
printf("error: unkown command %s\n", s);
break;
}
}
return 0;
}
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.0;
}
}
int getop(char s[])
{
int c;
index = 0;
if(scanf("%s", s) != EOF)
{
while(s[index] != '\0')
{
if (s[index] == ' ' || s[index] == '\t')//跳过空格和制表符
index++;
else if (!isdigit(s[index]) && s[index] != '.')//当遇到非数字字符和非.字符返回该字符
{
c = s[index];
index++;//index递增,跳过该字符
return c;
}
else if (isdigit(s[index]))
{
int i = 0;
while(isdigit(s[index]))
{
number[i++] = s[index];
index++;
}
if(s[index] == '.')
{
number[i++] = s[index];
index++;
while(isdigit(s[index]))
{
number[i++] = s[index];
index++;
}
}
number[i] = '\0';//number数组此时存储一个数字的字符形式
return NUMBER;
}
}
}
return EOF;
}
void printf_stack_top(void)
{
if(sp > 0)
printf("top of stack: %8g\n", val[sp - 1]);
else
printf("stack is empty\n");
}
void copy_stack_top(void)
{
double temp = pop();//先弹出栈顶元素,将其拷贝给临时变量,将该变量压入两次,完成复制工作
push(temp);
push(temp);
printf("Done!\n");
}
void change_stack_top(void)
{
double temp1 = pop();//temp1是栈顶第一个元素
double temp2 = pop();//temp2是栈顶第二个元素
push(temp1);//temp1先压入,temp2后压入,temp1变成栈顶第二个元素,temp2变成栈顶第一个元素
push(temp2);
}
void empty_stack(void)
{
sp = 0;
}
7-6:
编写一个程序,比较两个文件并打印它们第一个不相同的行
/*请注意:行内的换行符也会加入比较的范围,也就是说两个文件某行即使其他字符
都相同,但是如果一个有换行符,一个没有换行符,也会被判定为不同*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define MAXLEN 100
int main(void)
{
FILE *fp1, *fp2;
char filename1[MAXLEN], filename2[MAXLEN];
char len1[MAXLEN], len2[MAXLEN];
printf("请输入第一个待打开的文件名\n");
scanf("%s", filename1);
printf("请输入第二个待打开的文件名\n");
scanf("%s", filename2);
if((fp1 = fopen(filename1, "r")) == NULL || (fp2 = fopen(filename2, "r")) == NULL)
{
fprintf(stderr, "error: can't open %s\n", (fp1 == NULL) ? filename1 : filename2);
exit(1);
}
if(ferror(stdout))
{
fprintf(stderr, "error: writing stdout\n");
exit(2);
}
while(1)
{
if(fgets(len1, MAXLEN, fp1) == NULL)//fgets函数会读取换行符
strcpy(len1, "EOF");
if(fgets(len2, MAXLEN, fp2) == NULL)
strcpy(len2, "EOF");
if(strcmp(len1, len2))
break;
if(!strcmp(len1, "EOF") && !strcmp(len2, "EOF"))
break;
}
printf("file1: %s\n", len1);
printf("file2: %s\n", len2);
fclose(fp1);
fclose(fp2);
return 0;
}
7-7:
修改第五章的模式查找程序,使它从一个命名文件的集合中读取输入(有文件名参数时),如果没有文件名参数,则从标准输入中读取输入。当发现一个匹配行时,是否应该将相应的文件名打印出来?
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#define MAXLEN 1000
int main(int argc, char **argv)
{
char line[MAXLEN];
long lineno = 0;//记录行号
FILE *fp;
if(argc < 2)
fprintf(stderr, "error: Too few parameters\n");
else if(argc == 2)
{
while(fgets(line, MAXLEN, stdin) != NULL)
{
lineno++;
if(strstr(line, argv[1]) != NULL)
fprintf(stdout, "%ld: %s", lineno, line);
}
}
else
{
for(int i = 2; i < argc; i++)
{
if((fp = fopen(argv[i], "r")) == NULL)
{
fprintf(stderr, "error: can't open %s\n", argv[i]);
exit(1);
}
if(ferror(stdout))
{
fprintf(stderr, "error: writing stdout\n");
exit(2);
}
lineno = 0;
fprintf(stdout, "file: %s\n", argv[i]);
while(fgets(line, MAXLEN, fp) != NULL)
{
lineno++;
if(strstr(line, argv[1]) != NULL)
fprintf(stdout, "%ld: %s", lineno, line);
}
putchar('\n');
fclose(fp);
}
}
return 0;
}
7-8:
编写一个程序,以打印一个文件集合,每个文件从新的一页开始打印,并且打印每个文件相应的标题和页数
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#define MAXLEN 100
#define LINE_PAGE 10
void print_page(FILE *fp, char *filename);
int main(int argc, char **argv)
{
FILE *fp;
if(argc < 2)//本来准备保留标准输入的,但是它是逐行输入,逐行输出,格式就和我们预想的差别很大,所以删了
{
fprintf(stderr, "error: no file to open\n");
exit(1);
}
else
{
for(int i = 1; i < argc; i++)
{
if((fp = fopen(argv[i], "r")) == NULL)
{
fprintf(stderr, "error: can't open %s\n", argv[i]);
exit(1);
}
if(ferror(stdout))
{
fprintf(stderr, "error: writing stdout\n");
exit(2);
}
print_page(fp, argv[i]);
fclose(fp);
}
}
return 0;
}
void print_page(FILE *fp, char *filename)
{
char line[MAXLEN];
int page = 0;
long lineno = 0;
int page_start = 1;
while(fgets(line, MAXLEN, fp) != NULL)
{
if(page_start)
{
fprintf(stdout, "%40s page %d\n\n", filename, ++page);
page_start = 0;
}
printf("%s", line);
lineno++;
if(lineno % LINE_PAGE == 0)
{
fprintf(stdout, "%80d page\n\f", page);
page_start = 1;
}
}
if(lineno % LINE_PAGE != 0)
fprintf(stdout, "%80d page\n\f", page);
}
7-9:
类似于isupper这样的函数可以通过某种方式实现以达到节省空间或时间的目的。考虑节省空间或时间的实现方式
/*以下答案综合了多位博主的答案,还有百度搜索结果*/
int isupper(int c)
{
return ((_Ctype[(unsigned char)c] & _UP) != 0);
}
//上面的代码是标准库中isupper函数的实现方式
int isupper(int c)
{
return (c >= 'A' && c <= 'Z');
}
//上面的代码是空间最优的实现方式
#define isupper(c) ((c >= 'A' && c <= 'Z') ? 1 : 0;)
//上面的代码是时间最优的实现方式,减少了函数调用,缩短运行时间
/*但是上面的两种实现方式在遇到EBCDIC码表时会失败,因为EBCDIC码表中字母的排
列顺序并不像ASCII码表那样,它的英文字母不是连续地排列,中间出现多次断续*/