5-1:
在上面的例子中,如果符号+或-的后面紧跟的不是数字,getint函数将把符号视为数字0的有效表达式。修改该函数,讲这种形式的+或-符号重新写会到输入流中
#include <stdio.h>
#include <ctype.h>
#define BUFSIZE 100
int buf[BUFSIZE];
int bufp = 0;
int getch(void);
void ungetch(int);
int getint(int *pn)
{
int c, sign;
int mask = 0;
while(isspace(c = getch()))
;
if(!isdigit(c) && c != EOF && c != '+' && c != '-')
{
ungetch(c);
return 0;
}
sign = (c == '-') ? -1 : 1;
if(c == '+' || c == '-')
{
mask = 1;
c = getch();
}
if(!isdigit(c))
{
ungetch(c);
if(mask)
ungetch((sign == -1) ? '-' : '+');
return 0;
}
for(*pn = 0; isdigit(c); c = getch())
*pn = 10 * *pn + (c - '0');
*pn *= sign;
if(c != EOF)
ungetch(c);
return c;
}
int getch(void)
{
return (bufp == 0) ? getchar() : buf[--bufp];
}
void ungetch(int c)
{
if(bufp >= BUFSIZE)
printf("ungetch: too many characters\n");
else
buf[bufp++] = c;
}
5-2:
模仿函数getint的实现方法,编写一个读取浮点数的函数getfloat。getfloat函数的返回值应该是什么类型
#include <stdio.h>
#include <ctype.h>
#define BUFSIZE 100
int buf[BUFSIZE];
int bufp = 0;
int getfloat(float *pn);
int getch(void);
void ungetch(int);
int main(void)
{
float number = 0.0;
printf("请输入待转换为数字的字符串\n");
while(getfloat(&number))
printf("字符串转换为数字为:%.2f\n", number);//此处只打印2位小数,因为将字符串转换为float类型会出现精度丢失的问题
//例如-151.15,结果是-151.149994,这显然和我们的预期不同,除非将float类型改为double类型,这一问题才消失
return 0;
}
int getfloat(float *pn)
{
int c, sign;
float power = 1.0;
int mask = 0;
while(isspace(c = getch()))
;
if(!isdigit(c) && c != EOF && c != '+' && c != '-' && c != '.')
{
ungetch(c);
return 0;
}
sign = (c == '-') ? -1 : 1;
if(c == '+' || c == '-')
{
mask = 1;
c = getch();
}
if(!isdigit(c) && c != '.')
{
ungetch(c);
if(mask)
ungetch((sign == -1) ? '-' : '+');
return 0;
}
for(*pn = 0.0; isdigit(c); c = getch())
*pn = 10.0 * *pn + (float)(c - '0');
if(c == '.')
{
for(c = getch(); isdigit(c); c = getch())
{
*pn = 10.0 * *pn + (float)(c - '0');
power *= 10.0;
}
}
*pn = (*pn / power) * (float)sign;
if(c != EOF)
ungetch(c);
return c;
}
int getch(void)
{
return (bufp == 0) ? getchar() : buf[--bufp];
}
void ungetch(int c)
{
if(bufp >= BUFSIZE)
printf("ungetch: too many characters\n");
else
buf[bufp++] = c;
}
5-3:
用指针方式实现第2章中的函数strcat,函数strcat(s, t)将t指向的字符串复制到s指向的字符串的尾部
#include <stdio.h>
#define MAXSIZE 100
void strcat(char *s, char *t);
int main(void)
{
char string1[MAXSIZE] = "Hello world!";
char string2[MAXSIZE] = "good morning";
strcat(string1, string2);
printf("%s\n", string1);
return 0;
}
void strcat(char *s, char *t)
{
while(*s++)//此语句等同于while(*s) {s++;} s++;
;
//ASCII码中空字符编码为0,当*s == '\0'时,循环终止,然后s递增,指向下一字符
//也就是说退出上面的循环时,s其实指向的是空字符的下一字符
s--;
while(*s++ = *t++)//此处与上面的语句同理
;
}
5-4:
编写函数strend(s, t)。如果字符串t在字符串s的尾部,函数返回1,否则返回0
#include <stdio.h>
#define MAXSIZE 100
int strend(char *s, char *t);
int main(void)
{
int result;
char string1[MAXSIZE] = "Hello world!";
char string2[MAXSIZE] = "ld!";
char string3[MAXSIZE] = "rld";
result = strend(string1, string2);
printf("%d\n", result);
result = strend(string1, string3);
printf("%d\n", result);
return 0;
}
int strend(char *s, char *t)
{
int t_len = 0;
int s_len = 0;
while(*s)
{
s++;
s_len++;//s_len不包括空字符
}
s--;//s指向的是空字符的前一个字符
while(*t)
{
t++;
t_len++;
}
t--;
if(s_len >= t_len)
{
for(; *s == *t && t_len > 0; t_len--, s--, t--)
;
if(t_len == 0)
return 1;
}
return 0;
}
5-5:
实现库函数strncpy、strncat和strcmp,它们最多对参数字符串中的前n个字符进行操作。例如,函数strncpy(s, t, n),将t中最多前n个字符复制到s中,更详细的说明请参见附录B
#include <stdio.h>
#define MAXSIZE 100
void strncpy(char *s, char *t, int n);
void strncat(char *s, char *t, int n);
int strncmp(char *s, char *t, int n);
int main(void)
{
int number = 4;
char string1[MAXSIZE];
char string2[MAXSIZE] = "Hello";
char string3[MAXSIZE] = "World!";
char string4[MAXSIZE] = "Hel";
strncpy(string1, string2, number);
printf("%s\n", string1);
strncat(string2, string3, number);
printf("%s\n", string2);
printf("%d\n", strncmp(string2, string4, number));
return 0;
}
void strncpy(char *s, char *t, int n)
{
if(n <= 0)
{
printf("please enter a positive number\n");
return;
}
for(; (*s++ = *t++) && n > 0; n--)
;
*--s = '\0';
}
void strncat(char *s, char *t, int n)
{
if(n <= 0)
{
printf("please enter a positive number\n");
return;
}
while(*s++)
;
s--;
for(; (*s++ = *t++) && n > 0; n--)
;
*--s = '\0';
}
int strncmp(char *s, char *t, int n)
{
if(n <= 0)
{
printf("please enter a positive number\n");
return EOF;
}
for(; (*s == *t) && n > 0 && *t != '\0'; n--)
{
s++;
t++;
}
if(n == 0 || *t == '\0' )
return 0;
else if(*s > *t)
return 1;
else
return -1;
}
5-6:
采用指针而非数组索引方式改写前面章节和练习中的某些程序,例如getline(第1、4章),atoi、itoa以及它们的变形形式(第2、3、4章),reserve(第三章),strindex、getop(第4章)等等
#include <stdio.h>
#include <string.h>
#include <ctype.h>
int getline(char *s, int lim)
{
int c;
char *start = s;
while(--lim > 0 && (c = getchar()) != EOF && c != '\n')
{
*s++ = c;
}
if(c == '\n')
*s++ = c;
*s = '\0';
return (s - start);
}
int atoi(char *s)
{
int n, sign;
while(isspace(*s))
s++;
sign = (*s != '-' && *s != '+') ? 1 : ((*s++ == '+') ? 1 : -1);
for(n = 0; isdigit(*s); s++)
n = 10 * n + (*s - '0');
return n * sign;
}
void itoa(int n, char *s)
{
unsigned int u;
char *start = s;
u = (n < 0) ? -n : n;
do{
*s++ = (u % 10) + '0';
}while((u /= 10) > 0);
if(n < 0)
*s++ = '-';
*s = '\0';
reverse(start);
}
void reverse(char *s)
{
int temp;
char *end = s;
while(*end)
end++;
end--;
for(; end - s > 0; end--, s++)
{
temp = *s;
*s = *end;
*end = temp;
}
}
void itob(int n, char *s, int b)
{
unsigned int u;
char *start = s;
u = (n < 0) ? -n : n;
do
{
if(u % b > 9)
*s++ = (u % b) - 10 + 'A';
else
*s++ = (u % b) + '0';
}while((u /= b) > 0);
if(n < 0)
*s++ = '-';
*s = '\0';
reverse(start);
}
double atof(char s[])
{
double val;
double power = 1.0;
double sign;
int mask; //标记e后面的符号
int power_e; //记录指数大小
double result; //结果值
while(isspace(*s))
s++;
sign = (*s != '+' && *s != '-') ? 1 : ((*s++ == '+') ? 1 : -1);
for(val = 0; isdigit(*s); s++)
val = 10.0 * val + (*s - '0');
if(*s == '.')
{
s++;
for(; isdigit(*s); s++)
{
val = 10.0 * val + (*s - '0');
power *= 10.0;
}
}
result = sign * val / power;
if(*s == 'e' || *s == 'E')
{
s++;
if(*s == '-')
mask = 1;
else if(*s == '+' || isdigit(*s))
mask = 0;
else
return result;
if(*s == '-' || *s == '+')
s++;
for(power_e = 0; isdigit(*s); s++)
power_e = 10 * power_e + (*s - '0');
if(mask)//e后面符号为-时,表示除以power_e个10.0
{
while(power_e > 0)
{
result /= 10.0;
power_e--;
}
return result;
}
else//e后面符号为+或没有符号时,表示乘以power_e个10.0
{
while(power_e > 0)
{
result *= 10.0;
power_e--;
}
return result;
}
}
else//数字后面没有e或E
return result;
}
int strindex(char *s, char *t)
{
int length_s = strlen(s) - 1;
int length_t = strlen(t) - 1;
char *end_s = s + length_s - 1;
char *end_t = t + length_t - 1;
char *p, *q;
for(; end_s >= s; end_s--)
{
for(p = end_s, q = end_t; (q >= t) && (*p == *q); p--, q--)
;
if(q < t)
return (p - s + 2);//返回字符出现在字符串中的实际位置
}
return -1;
}
int getop(char s[])
{
int i, c;
while(*s = c = getch()) == ' ' || c == '\t')
;
*(s++) = '\0';
if(!isdigit(c) && c != '.')
return c;
i = 0;
if(isdigit(c))
while(isdigit(*s++ = c = getch()))
;
if(c == '.')
while(isdigit(*s++ = c = getch()))
;
*s = '\0';
if(c != EOF)
ungetch(c);
return NUMBER;
}
5-7:
重写函数readlines,将输入的文本行储存到有main函数提供的一个数组中,而不是存储到调用alloc分配的存储空间中。该函数的运行速度比改写前快多少?
/*两个程序运行时间的差值为调用alloc函数的时间*/
#include <stdio.h>
#define MAXLINES 5000
#define MAXLEN 1000
int readlines(char line[][MAXLEN], int nlines);
int main(void)
{
int nlines;
char lines[MAXLINES][MAXLEN];//最多可存储MAXLINES条长度为MAXLINE的字符串的数组
if((nlines = readlines(lines, MAXLINES)) >= 0)
{
qsort(lines, 0, nlines - 1);
writelines(lines, nlines);
return 0;
}
else
{
printf("error: input too big to sort\n");
return 1;
}
}
int readlines(char lines[][MAXLEN], int maxlines)
{
int len, nlines;
nlines = 0;
while((len = getline(lines[nlines], MAXLEN)) > 0)
{
if(nlines >= maxlines)
return -1;
else
{
lines[nlines][len - 1] = '\0';
}
nlines++;
}
return nlines;
}
5-8:
函数day_of_year和month_day中没有错误检查,请解决该问题
#include <stdio.h>
static char daytab[2][13] = {
{0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31},
{0, 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}
};
int day_of_year(int year, int month, int day);
int month_day(int year, int yearday, int *pmonth, int *pday);
int main(void)
{
int year = 1946;
int yearday = 0;
int month;
int day;
int result;
result = month_day(year, yearday, &month, &day);
if(result == -1)
printf("error!\n");
else
printf("%d年%d天是%d月%d日\n", year, yearday, month, day);
month = 13;
day = 25;
result = day_of_year(year, month, day);
if(result == -1)
printf("error!\n");
else
printf("%d月%d日是%d年%d天\n", month, day, year, result);
return 0;
}
int day_of_year(int year, int month, int day)
{
int i, leap;
if(month < 1 || month > 12 || day < 1 || day > 31)//错误检查应该更加复杂,比如要判断某月的天数在不在范围内,这里简化了
return -1;
leap = year % 4 == 0 && year % 100 != 0 || year % 400 ==0;//前者(即4的倍数并且非100的倍数)是普通闰年,后者为世纪闰年
for(i = 1; i < month; i++)
day += daytab[leap][i];
return day;
}
int month_day(int year, int yearday, int *pmonth, int *pday)
{
int i, leap;
leap = year % 4 == 0 && year % 100 != 0 || year % 400 ==0;
if(yearday < 1 || (!leap && yearday > 365) || (leap && yearday > 366))//闰年天数为366天
return -1;
for(i = 1; yearday > daytab[leap][i]; i++)
yearday -= daytab[leap][i];
*pmonth = i;
*pday = yearday;
}
5-9:
用指针方式代替数组下标方式改写函数day_of_year和month_day
int day_of_year(int year, int month, int day)
{
int i, leap;
if(month < 1 || month > 12 || day < 1 || day > 31)
return -1;
leap = year % 4 == 0 && year % 100 != 0 || year % 400 ==0;//前者(即4的倍数并且非100的倍数)是普通闰年,后者为世纪闰年
for(i = 1; i < month; i++)
{
day += *(*(daytab + leap) + i);//daytab指向的是数组的第一行(即&daytab[0])
//daytab + leap指向的是数组第leap + 1行(即&daytab[leap])
//那么*(daytab + leap)得到daytab[leap][0]的地址,即数组第leap+1行首元素的地址
//*(daytab + leap) + i指向的是数组的第leap+1行的第i + 1个元素(即&daytab[leap][i])
//*(*(daytab + leap) + i)表示获取该元素的值(即daytab[leap][i])
}
return day;
}
int month_day(int year, int yearday, int *pmonth, int *pday)
{
int i, leap;
leap = year % 4 == 0 && year % 100 != 0 || year % 400 ==0;
if(yearday < 1 || (!leap && yearday > 365) || (leap && yearday > 366))//闰年天数为366天
return -1;
for(i = 1; yearday > *(*(daytab + leap) + i); i++)
yearday -= *(*(daytab + leap) + i);
*pmonth = i;
*pday = yearday;
}
5-10:
编写程序expr,以计算从命令行输入的逆波兰表达式的值,其中每个运算符或操作数用一个单独的参数表示。例如,命令
expr 2 3 4 + *
将计算表达式2 * (3 + 4)的值
/*程序的主体部分不需要太大变化,主要是让程序知道什么时候该停止运行,输出计算结果*/
#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];
int getop(char s[], char t[]);
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(int argc, char ** argv)
{
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;
if(argc < 2)
{
printf("error: too few parameters\n");
return -1;
}
while(argc-- > 0)//此程序最重要的一步,假设命令行输入的参数为4个(包括程序名),我们需要让循环迭代4次
//前三次传入命令行除程序名以外的三个参数,第四次需要传入作为计算终止标志的换行符
{
if(argc == 0)//当argc==0是,type='\n',输出计算结果
type = '\n';
else
type = getop(s, *++argv);
switch(type)
{
case NUMBER:
push(atof(s));
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':
push( sin( (pop() / RAD_TO_DEG) ) );
break;
case 'c':
push( cos( (pop() / RAD_TO_DEG) ) );
break;
case 't':
op2 = pop();
if(op2 != 90)
push( tan( (pop() / RAD_TO_DEG) ) );
else
printf("tan90 is invalid value\n");
break;
case 'e':
push( exp( pop() ) );
break;
case 'p':
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 '\n':
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[], char t[])
{
int i, c;
*s++ = c = *t++;//不需要跳过空格符
if(!isdigit(c) && c != '.')
return c;
if(isdigit(c))
while(isdigit(*s++ = c = *t++))
;
if(c == '.')
while(isdigit(*s++ = c = *t++))
;
if(c == '\0')
{
*s = '\0';
return NUMBER;
}
return c;
}
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();
double temp2 = pop();
push(temp1);
push(temp2);
}
void empty_stack(void)
{
sp = 0;
}
5-11:
修改程序entab和detab(第1章练习中编写的函数),使它们接受一组作为参数的制表符停止位。如果启动程序时不带参数,则使用默认的制表符停止位设置
#include <stdio.h>
#include <stdlib.h>
#define MAXLINE 1000
#define Tabsize 8//制表符的长度时固定的,在本系统中其长度为8个字符
int getline(char line[], int maxline);
void detab(char *s, char *t, int tabsize);
void entab(char *s, char *t, int tabsize);
int main(int argc, char *argv[])
{
char origin[MAXLINE];
char result[MAXLINE];
int tabsize;
if(argc < 2)
tabsize = Tabsize;
else if(argc == 2)
{
tabsize = atoi(argv[1]);
if(tabsize < 1)
{
printf("please enter a positive number\n");
return -1;
}
}
else
{
printf("error!\n");
return -1;
}
printf("请输入带制表符的字符串:\n");
while(getline(origin, MAXLINE) > 0)
{
detab(origin, result, tabsize);
printf("detab : \n%s\n", result);
printf("请输入带空格的字符串:\n");
getline(origin, MAXLINE);
entab(origin, result, tabsize);
printf("entab : \n%s\n", result);
printf("继续输入带制表符的字符串\n");
}
return 0;
}
void detab(char *s, char *t, int tabsize)
{
int all, size;//all统计字符数,size计算制表符到制表符终止位占多少字符位
for(all = 0, size = 0; *s; s++)
{
if(*s == '\t')
{
size = tabsize - (all % tabsize);//计算需要多少个空格填充制表符所占字符长度
while(size > 0)
{
*t++ = ' ';
all++;
size--;
}
}
else
{
*t++ = *s;
all++;
}
}
*t = *s;
}
void entab(char *s, char *t, int tabsize)
{
int all, space;//all统计字符数,space记录使用制表符后还需要使用多少个空格
for(all = 0, space = 0; *s; s++)
{
if(*s == ' ')
{
if(all % tabsize != tabsize - 1)
space++;//标记空格数
else if(all % Tabsize == tabsize - 1)//每经过一次制表符的终止位就打印一次制表符
{
space = 0;//将空格数清零
*t++ = '\t';
}
all++;
}
else
{
if(space > 0)//当空格没有超过制表符终止位时,打印空格
{
while(space > 0)
{
*t++ = ' ';
space--;
}
}
*t++ = *s;
all++;
}
}
*t = *s;
}
int getline(char s[], int lim)
{
int c, i;
for (i = 0; i < lim - 1 && (c = getchar()) != EOF && c != '\n'; ++i)
s[i] = c;
if (c == '\n')
{
s[i] = c;
++i;
}
s[i] = '\0';
return i;
}
5-12:
对程序entab和detab的功能做一些扩充,以接受下列缩写的命令:
entab -m +n
表示制表符从m列开始,每隔n列停止。选择(对使用者而言)比较方便的默认行为
/*此题与上一题的区别不大,可以将上一题看做m = 1,即从第1列开始,每隔n列停
止,也就是说此题正在上一题的基础上需要添加一个判断,m列之前的部分不用更改*/
#include <stdio.h>
#include <stdlib.h>
#include <ctype.h>
#define MAXLINE 1000
#define Tabsize 8//制表符的长度时固定的,在本系统中其长度为8个字符
int getline(char line[], int maxline);
void detab(char *s, char *t, int tabsize, int m);
void entab(char *s, char *t, int tabsize, int m);
int main(int argc, char *argv[])
{
char origin[MAXLINE];
char result[MAXLINE];
int tabsize;
int m;
if(argc == 1)
{
m = 1;
tabsize = Tabsize;
}
else if(argc == 3)
{
while(--argc)
{
if(*(*++argv) == '-' && isdigit(*++(*argv)))
m = atoi(*argv);
else if(**argv == '+' && isdigit(*++(*argv)))
tabsize = atoi(*argv);
else
{
printf("parameter error\n");
return -1;
}
}
}
else
{
printf("error!\n");
return -1;
}
printf("请输入带制表符的字符串:\n");
while(getline(origin, MAXLINE) > 0)
{
detab(origin, result, tabsize, m);
printf("detab : \n%s\n", result);
printf("请输入带空格的字符串:\n");
getline(origin, MAXLINE);
entab(origin, result, tabsize, m);
printf("entab : \n%s\n", result);
printf("继续输入带制表符的字符串\n");
}
return 0;
}
void detab(char *s, char *t, int tabsize, int m)
{
int all, size;//all统计字符数,size计算制表符到制表符终止位占多少字符位
for(all = 0, size = 0; *s; s++)
{
if(*s == '\t' && (all >= (m - 1)))
{
size = tabsize - (all % tabsize);//计算需要多少个空格填充制表符所占字符长度
while(size > 0)
{
*t++ = ' ';
all++;
size--;
}
}
else if(*s == '\t')//当制表符出现在m列之前时,保留制表符,同时统计制表符所占字符位
{
size = tabsize - (all % tabsize);
while(size > 0)
{
all++;
size--;
}
*t++ = '\t';
}
else
{
*t++ = *s;
all++;
}
}
*t = *s;
}
void entab(char *s, char *t, int tabsize, int m)
{
int all, space;//all统计字符数,space记录使用制表符后还需要使用多少个空格
for(all = 0, space = 0; *s; s++)
{
if(*s == ' ' && (all >= (m - 1)))
{
if(all % tabsize != tabsize - 1)
space++;//标记空格数
else if(all % Tabsize == tabsize - 1)//每经过一次制表符的终止位就打印一次制表符
{
space = 0;//将空格数清零
*t++ = '\t';
}
all++;
}
else
{
if(space > 0)//当空格没有超过制表符终止位时,打印空格
{
while(space > 0)
{
*t++ = ' ';
space--;
}
}
*t++ = *s;
all++;
}
}
*t = *s;
}
int getline(char s[], int lim)
{
int c, i;
for (i = 0; i < lim - 1 && (c = getchar()) != EOF && c != '\n'; ++i)
s[i] = c;
if (c == '\n')
{
s[i] = c;
++i;
}
s[i] = '\0';
return i;
}
5-13:
编写函数tail,将其输入中的最后n行打印出来,默认情况下,n的值为10,但可以通过一个可选参数改变n的值,因此,命令:
tail -n
将打印其输入的最后n行。无论输入或n的值是否合理,该程序都应该能正常运行。编写的程序要充分地利用存储空间:输入行的存储方式应该同5.6节排序程序的存储方式一样,而不采用固定长度的二维数组
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#define MAXLINES 100
#define MAXLEN 1000
#define ALLOCSIZE 10000
char *lineptr[MAXLINES];
char allocbuf[ALLOCSIZE];
char *allocp = allocbuf;
int readlines(char *lineptr[], int maxlines);
void printlines(char *lineptr[], int n, int nlines);
int getline(char s[], int lim);
char *alloc(int n);
void afree(char *p);
int main(int argc, char *argv[])
{
int n;
int nlines = 0;
if(argc == 2)//合理输入的情况下
{
if(*(*++argv) == '-' && isdigit(*++(*argv)))
n = atoi(*argv);
else if(isdigit(**argv))
n = atoi(*argv);
else
n = 10;
}
else
n = 10;
if((nlines = readlines(lineptr, MAXLINES)) > 0)
{
if(nlines < n)
printlines(lineptr, 0, nlines);
else
printlines(lineptr, n, nlines);
}
else
{
printf("error: input is empty\n");
return -1;
}
afree(allocbuf);
return 0;
}
int readlines(char *lineptr[], int maxlines)
{
int len, nlines;
char *p, line[MAXLEN];
nlines = 0;
while((len = getline(line, MAXLEN)) > 0)
{
if(nlines >= maxlines || (p = alloc(len)) == NULL)
return -1;
else
{
line[len - 1] = '\0';
strcpy(p, line);
lineptr[nlines++] = p;
}
}
return nlines;
}
void printlines(char *lineptr[], int n, int nlines)
{
for(int i = nlines - n; i < nlines; i++)
printf("%s\n", lineptr[i]);
}
int getline(char s[], int lim)
{
int c, i;
for (i = 0; i < lim - 1 && (c = getchar()) != EOF && c != '\n'; ++i)
s[i] = c;
if (c == '\n')
{
s[i] = c;
++i;
}
s[i] = '\0';
return i;
}
char *alloc(int n)
{
if(allocp + n <= allocbuf + ALLOCSIZE)
{
allocp += n;
return allocp - n;
}
else
return NULL;
}
void afree(char *p)
{
if(p >= allocbuf && p < allocbuf + ALLOCSIZE)
allocp = p;
}
5-14、5-15、5-16:
修改排序程序,使它具有以下功能:
添加-r标记,该标记表明,以逆序(递减)方式排序。要保证-r和-n能够组合在一起使用;
添加-f标记,使得排序过程不考虑字母大小写之间的区别。例如,比较a和A时认为它们相等;
添加选项-d(代表目录顺序),该选项表明,只对字母、数字和空格进行比较。要保证该选项可以和-f组合在一起使用;
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <ctype.h>
#define MAXLINES 100
#define MAXLEN 1000
#define ALLOCSIZE 10000
char allocbuf[ALLOCSIZE];
char *lineptr[MAXLINES];
char *allocp = allocbuf;
int readlines(char *lineptr[], int maxlines);
void writelines(char *lineptr[], int nlines);
int getlines(char s[], int lim);
void qsort(void *lineptr[], int left, int right, int (*comp)(const void *, const void *), int sort_order);
int numcmp(const char *s, const char *t);
int charcmp(const char *s, const char *t);//不区分大小写排序
int listcmp(const char *s, const char *t);//按照目录顺序排序
int list_charcmp(const char *s, const char *t);//按照目录顺序,不区分大小写排序
void swap(void *v[], int i, int j);
char *alloc(int n);
void afree(char *p);
int main(int argc, char *argv[])
{
int nlines;
int sort_mode = 0;//排序模式
int sort_order = -1;//排序顺序
if(argc == 1)
{
sort_mode = 0;//字符排序
sort_order = -1;//顺序排序
}
else if(argc == 2)
{
if(!strcmp(argv[1], "-n"))
sort_mode = 1;//数值排序
else if(!strcmp(argv[1], "-r"))
{
sort_mode = 0;
sort_order = 1;//逆序排序
}
else if(!strcmp(argv[1], "-f"))
{
sort_mode = 2;//不区分大小写排序
}
else if(!strcmp(argv[1], "-d"))
{
sort_mode = 3;//按照目录顺序排序
}
else
{
printf("parameter error!\n");
return -1;
}
}
else if(argc == 3)
{
if(!strcmp(argv[1], "-n") && !strcmp(argv[2], "-r"))
{
sort_mode = 1;
sort_order = 1;
}
else if(!strcmp(argv[1], "-f") && !strcmp(argv[2], "-r"))
{
sort_mode = 2;
sort_order = 1;
}
else if(!strcmp(argv[1], "-d") && !strcmp(argv[2], "-f"))
{
sort_mode = 4;//按照目录顺序,不区分大小写排序
}
else
{
printf("parameter error!\n");
return -1;
}
}
else
{
printf("parameter error!\n");
return -1;
}
if((nlines = readlines(lineptr, MAXLINES)) > 0)
{
qsort((void **) lineptr, 0, nlines - 1, (int (*)(const void *, const void *))
((!sort_mode) ? strcmp : ((sort_mode == 1) ? numcmp : ((sort_mode == 2) ? charcmp :
((sort_mode == 3) ? listcmp : list_charcmp)))), sort_order);
writelines(lineptr, nlines);
}
else
{
printf("input is empty!\n");
return -1;
}
afree(allocbuf);
return 0;
}
int readlines(char *lineptr[], int maxlines)
{
int len, nlines;
char *p, line[MAXLEN];
nlines = 0;
while((len = getlines(line, MAXLEN)) > 0)
{
if(nlines >= maxlines || (p = alloc(len)) == NULL)
return -1;
else
{
line[len - 1] = '\0';
strcpy(p, line);
lineptr[nlines++] = p;
}
}
return nlines;
}
char *alloc(int n)
{
if(allocp + n <= allocbuf + ALLOCSIZE)
{
allocp += n;
return allocp - n;
}
else
return NULL;
}
int getlines(char s[], int lim)
{
int c, i;
for(i = 0; i < lim -1 && (c = getchar()) != EOF && c != '\n'; i++)
s[i] = c;
if(c == '\n')
s[i++] = '\n';
s[i] = '\0';
return i;
}
void qsort(void *v[], int left, int right, int (*comp)(const void *, const void *), int sort_order)
{
int i, last;
void swap(void *v[], int, int);
if(left >= right)
return;
swap(v, left, (left + right) / 2);
last = left;
for(i = left + 1; i <= right; i++)
if((*comp) (v[i], v[left]) == sort_order)
swap(v, ++last, i);
swap(v, left, last);
qsort(v, left, last - 1, comp, sort_order);
qsort(v, last + 1, right, comp, sort_order);
}
int numcmp(const char *s, const char *t)
{
double v1, v2;
v1 = atof(s);
v2 = atof(t);
if(v1 < v2)
return -1;
else if(v1 > v2)
return 1;
else
return 0;
}
int charcmp(const char *s, const char *t)
{
for(; *s && *t && tolower(*s) == tolower(*t); s++, t++)
;
if(tolower(*s) < tolower(*t))
return -1;
else if(tolower(*s) > tolower(*t))
return 1;
else
return 0;
}
int listcmp(const char *s, const char *t)
{
int c1, c2;
do
{
while(!isalnum(*s) && *s != ' ' && *s != '\0')
s++;
while(!isalnum(*t) && *t != ' ' && *t != '\0')
t++;
if(*s == '\0' && *t == '\0')
return 0;
c1 = *s;
s++;
c2 = *t;
t++;
}while(c1 == c2);
if(c1 < c2)
return -1;
else
return 1;
}
int list_charcmp(const char *s, const char *t)
{
int c1, c2;
do
{
while(!isalnum(*s) && *s != ' ' && *s != '\0')
s++;
while(!isalnum(*t) && *t != ' ' && *t != '\0')
t++;
if(*s == '\0' && *t == '\0')
return 0;
c1 = tolower(*s);
s++;
c2 = tolower(*t);
t++;
}while(c1 == c2);
if(c1 < c2)
return -1;
else
return 1;
}
void swap(void *v[], int i, int j)
{
void *temp;
temp = v[i];
v[i] = v[j];
v[j] = temp;
}
void writelines(char *lineptr[], int nlines)
{
for(int i = 0; i < nlines; i++)
printf("%s\n", lineptr[i]);
}
void afree(char *p)
{
if(allocbuf <= p && p >= allocbuf + ALLOCSIZE)
allocp = p;
}
5-17:
添加字段处理功能,以使得排序程序可以根据行内的不同字段进行排序,每个字段按照一个单独的选项集合进行排序
/*关于字段处理的部分有点费解,我认为的字段处理是类似于excel表那样,假设有
两行输入,行1为123456781234,行2为567812345678,字段宽度为4,假设按照第一
个字段宽度排序,行1在前,行2在后,假设按照第二个字段宽度排序,行2在前,行1
灾后,假设没有指定按照那个字段排序,那就默认采用第一个字段宽度,而如果输入
行的字段宽度小于指定的字段宽度,即假设按照第四个字段宽度,上面两行都只有三
个字段宽度,此时采用默认的第一个字段宽度
采用结构来实现上面的要求是非常方便的,但是还没学,所以还是跟着书本来
重新写一个,上面的程序再添就有点乱了,改也不好改
*/`
#include <stdio.h>
#include <string.h>
#define MAXLINES 100
#define MAXLEN 1000
#define ALLOCSIZE 10000
#define SIZE 4//定义一个字段宽度为4个字符
char allocbuf[ALLOCSIZE];
char *lineptr[MAXLINES];
char *allocp = allocbuf;
int readlines(char *lineptr[], int maxlines);
void writelines(char *lineptr[], int nlines);
int getlines(char s[], int lim);
void qsort(char *lineptr[], int left, int right, int (*comp)(const char *, const char *, int ), int size);
int sizecmp(const char *s, const char *t, int size);
void swap(char *v[], int i, int j);
char *alloc(int n);
void afree(char *p);
int main(int argc, char *argv[])
{
int nlines;
int size = 1;
if(argc == 2)
{
if(!strcmp(argv[1], "-s"))
{
printf("请输入按照哪个字段宽度排序\n");
scanf("%d", &size);
}
}
if((nlines = readlines(lineptr, MAXLINES)) > 0)
{
qsort(lineptr, 0, nlines - 1, sizecmp, size);
writelines(lineptr, nlines);
}
else
{
printf("input is empty!\n");
return -1;
}
afree(allocbuf);
return 0;
}
int readlines(char *lineptr[], int maxlines)
{
int len, nlines;
char *p, line[MAXLEN];
nlines = 0;
while((len = getlines(line, MAXLEN)) > 0)
{
if(nlines >= maxlines || (p = alloc(len)) == NULL)
return -1;
else
{
line[len - 1] = '\0';
strcpy(p, line);
lineptr[nlines++] = p;
}
}
return nlines;
}
char *alloc(int n)
{
if(allocp + n <= allocbuf + ALLOCSIZE)
{
allocp += n;
return allocp - n;
}
else
return NULL;
}
int getlines(char s[], int lim)
{
int c, i;
for(i = 0; i < lim -1 && (c = getchar()) != EOF && c != '\n'; i++)
s[i] = c;
if(c == '\n')
s[i++] = '\n';
s[i] = '\0';
return i;
}
void qsort(char *v[], int left, int right, int (*comp)(const char *, const char *, int ), int size)
{
int i, last;
void swap(char *v[], int, int);
if(left >= right)
return;
swap(v, left, (left + right) / 2);
last = left;
for(i = left + 1; i <= right; i++)
if((*comp) (v[i], v[left], size) < 0)
swap(v, ++last, i);
swap(v, left, last);
qsort(v, left, last - 1, comp, size);
qsort(v, last + 1, right, comp, size);
}
int sizecmp(const char *s, const char *t, int size)
{
int length = (size - 1) * SIZE;
const char *v1, *v2;
v1 = s;
v2 = t;
if(length == 0)
return strcmp(s, t);
while(length > 0 && *v1 != '\0' && *v2 != '\0')
{
v1++;
v2++;
length--;
}
if(*v1 == '\0' || *v2 == '\0')
{
return strcmp(s, t);
}
else
{
return strcmp(v1, v2);
}
}
void swap(char *v[], int i, int j)
{
char *temp;
temp = v[i];
v[i] = v[j];
v[j] = temp;
}
void writelines(char *lineptr[], int nlines)
{
for(int i = 0; i < nlines; i++)
printf("%s\n", lineptr[i]);
}
void afree(char *p)
{
if(allocbuf <= p && p >= allocbuf + ALLOCSIZE)
allocp = p;
}
//程序比较简陋,但是基本能实现要求
5-18、5-20:
修改dcl程序,使它能处理输入中的错误;
扩展dcl程序的功能,使它能够处理包含其它成分的声明,例如带有函数参数类型的声明、带有类似于const限定符的声明等;
/*这个程序让我头有点大,简单的梳理一下思路吧,我们假设输入行的输入为:
char (*(*x())[])() \n
程序的递归从第一个'('字符开始,dcl递进到dirdcl,再递进到dcl,遇到第二
个'('再次从dcl递进到dirdcl,再递进到dcl,dcl又递进到dirdcl,在dirdcl函数
中字符'x'被存储到name数组中,紧接着循环语句开始执行,读取到'()',out数组存
储" function returning",再读取到')',循环退出,递归开始回归,首先回归到
dcl,执行dirdcl();语句后面的部分,out数组变为" function returning pointer
to",再次回归,到dirdcl函数,执行dcl();语句后面的部分,此时tokentype ==
')',条件判断为假,进入循环部分,读取输入'[]',out数组变为" function
returning pointer to array[] of",循环继续,读取到')',循环退出,递归回
归到dcl,执行dirdcl();语句后面的部分,out数组变为" function returning
pointer to array[] of pointer to", 再次回归到dirdcl,执行dcl();语句后面
的部分,此时tokentype == ')',条件判断为假,进入循环部分,读取输入"()",
out数组变为" function returning pointer to array[] of pointer to
function returning",循环继续,读取到换行符,退出循环,递归回归到dcl函
数,执行dirdcl();语句后面的部分,ns值为0,条件判断为假,递归执行完,回到
主程序,打印输出
很乱.....,递归部分的简单描述如下:
dcl() -> dirdcl() -> dcl() -> dirdcl() -> dcl() -> dirdcl()
'(' "*(" "*x"
上面为递进过程相应函数中gettoken读取的字符或字符串
dcl() <- dirdcl() <- dcl() <- dirdcl() <- dcl() <- dirdcl()
"()\n" "[])" "())"
上面为回归过程相应函数中gettoken读取的字符或字符串
out数组接受拼接字符串发生在递归回归过程中:
dcl() <- dirdcl() <- dcl() <- dirdcl() <- dcl() <- dirdcl()
"func..." "poi..." "array..." "poi..." "func..."
另外需要说明的是,之所以在第一次回归到dcl函数时没有打印两次"pointer to",
是因为此时ns的值为1,并不是2,因为每次进入dcl函数时ns都被重置为0,除非两
个'*'字符是一起输入的,才会打印两次,如果它们之间有其他非空格字符,那么就
会分开打印
关于这两题,以我目前的能力,我不会....,想了很久,发现这两题要检查的条件太
多了,const后可以接数据类型,也可以在指针后面接const,而函数参数除了常见数
据类型外,还有指针、数组、函数指针....改了几遍,发现最后无法正常运行了,心
态崩了,一边学一边改吧
*/
#include <stdio.h>
#include <string.h>
#include <ctype.h>
#define MAXTOKEN 100
#define BUFSIZE 100
#define DATATYPE "int short long char float double void"
enum {NAME, PARENS, BRACKETS};
int tokentype;//最后一次输入的符号类型
char token[MAXTOKEN];//最后一个符号字符串
char name[MAXTOKEN];//标识符名
char datatype[MAXTOKEN];//数据类型,包括char、int等
char out[1000];
char buf[BUFSIZE];
int bufp = 0;
void dcl(void);
void dirdcl(void);
int gettoken(void);
int main(void)
{
int const_mask = 0;
while(gettoken() != EOF)//读取第一个输入的符号
{
if(!strcmp(token, "const") && !const_mask)
{
const_mask = 1;
strcpy(datatype, "const ");
gettoken();
}
if(strstr(DATATYPE, token) != NULL)//程序只能接受单名的数据类型
strcat(datatype, token);//正常情况下声明的第一个符号是数据类型,即int、long、char等,但是为了排除错误,加入一条判断
else
printf("can't identify the \"%s\" datatype\n", token);
out[0] = '\0';
dcl(); //解析声明的其余部分
if(tokentype != '\n')
printf("syntax error\n");
printf("%s: %s %s\n", name, out, datatype);
const_mask = 0;
datatype[0] = '\0';
}
return 0;
}
int gettoken(void)//返回下一个符号
{
int c, getch(void);
void ungetch(int);
char *p = token;//指针p每次进入函数时都是指向token数组的首元素
while((c = getch()) == ' ' || c == '\t')
;
if(c == '(')
{
if((c = getch()) == ')')
{
strcpy(token, "()");//当读取的'('后面紧跟')'时,说明这是一个函数标记
return tokentype = PARENS;
}
else
{
ungetch(c);
return tokentype = '(';
}
}
else if(c == '[')
{
for(*p++ = c; (*p++ = getch()) != ']'; )//从[开始读取,直到读取到]退出循环
;
*p = '\0';
return tokentype = BRACKETS;//返回中括号标记
}
else if(isalpha(c))
{
for(*p++ = c; isalnum(c = getch()); )//读取整个标识符名
*p++ = c;
*p = '\0';
ungetch(c);
return tokentype = NAME;//返回变量名或者类型名标记
}
else
return tokentype = c;
}
void dcl(void)//解析一个声明
{
int ns;
int const_mask = 0;
for(ns = 0; gettoken() == '*' || !strcmp(token, "const"); )//计算*的数量
{
if(tokentype == '*')
{
ns++;
const_mask = 0;
}
else
{
if(ns > 0 && !const_mask)
const_mask = 1;
else
printf("\"const\" location error\n");
}
}
dirdcl();
while(ns-- > 0)
{
if(const_mask)
strcat(out, " const pointer to");
else
strcat(out, " pointer to");
}
}
void dirdcl(void)//解析直接声明符
{
int type;
if(tokentype == '(')
{
dcl();
if(tokentype != ')')
printf("error: missing ) \n");
}
else if(tokentype == NAME)//变量名
strcpy(name, token);
else
printf("error: expected name or (dcl)\n");
while((type = gettoken()) == PARENS || type == BRACKETS)
if(type == PARENS)
strcat(out, " function returning");
else
{
strcat(out, " array");
strcat(out, token);
strcat(out, " of");
}
}
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;
}
5-19:
修改undcl程序,使它在把文字转换为声明的过程中不会生成多余的圆括号
#include <stdio.h>
#include <string.h>
#include <ctype.h>
#define MAXTOKEN 100
#define BUFSIZE 100
#define DATATYPE "int short long char float double void"
enum {NAME, PARENS, BRACKETS};
int tokentype;//最后一次输入的符号类型
char token[MAXTOKEN];//最后一个符号字符串
char name[MAXTOKEN];//标识符名
char datatype[MAXTOKEN];//数据类型,包括char、int等
char out[1000];
char buf[BUFSIZE];
int bufp = 0;
void undcl(void);
int gettoken(void);
void delete_parens(char *s);//删除多余的圆括号
int main(void)
{
int type, prev_type;
char temp[MAXTOKEN];
while(gettoken() != EOF)
{
strcpy(out, token);
prev_type = NAME;
while((type = gettoken()) != '\n')
{
if(type == PARENS ||type == BRACKETS)
strcat(out, token);
else if(type == '*')
{
if(prev_type == '*')
sprintf(temp, "*%s", out);
else
sprintf(temp, "(*%s)", out);
strcpy(out,temp);
}
else if(type == NAME)
{
if(strstr(DATATYPE, token) == NULL)
printf("can't identify \"%s\" datatype\n", token);
if(prev_type == '*')
delete_parens(out);
sprintf(temp, "%s %s", token, out);
strcpy(out, temp);
}
else
printf("invalid input at %s\n", token);
prev_type = type;
}
printf("%s\n", out);
}
return 0;
}
void delete_parens(char *s)
{
if(*s == '(')
{
while(*(s + 1) != '\0')
{
*s++ = *(s + 1);
}
*--s = '\0';
}
}
int gettoken(void)//返回下一个符号
{
int c, getch(void);
void ungetch(int);
char *p = token;//指针p每次进入函数时都是指向token数组的首元素
while((c = getch()) == ' ' || c == '\t')
;
if(c == '(')
{
if((c = getch()) == ')')
{
strcpy(token, "()");//当读取的'('后面紧跟')'时,说明这是一个函数标记
return tokentype = PARENS;
}
else
{
ungetch(c);
return tokentype = '(';
}
}
else if(c == '[')
{
for(*p++ = c; (*p++ = getch()) != ']'; )//从[开始读取,直到读取到]退出循环
;
*p = '\0';
return tokentype = BRACKETS;//返回中括号标记
}
else if(isalpha(c))
{
for(*p++ = c; isalnum(c = getch()); )//读取整个标识符名
*p++ = c;
*p = '\0';
ungetch(c);
return tokentype = NAME;//返回变量名或者类型名标记
}
else
return tokentype = c;
}
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;
}