2016.02.22 - 04.05
1 练习
02.22
函数与程序结构
练习4-1
编写函数strrindex(s, t),它返回字符串t在s中最右边出现的位置、如果s中不包含t,则返回-1。
[1] 打算
简单打算如下:
[2] 实现
简单实现[1]中打算如下:
/* p4_1.c
* 编写函数strrindex(s, t) -
* 它返回字符串t在s中最右边出现的位置。如果s中不包含t,则返回-1。
*/
#include <string.h> //直接用strlen获取字符串长度
int strrindex(char s[], char t[])
{
int i, j, k;
size_t slen, tlen;
slen = strlen(s);
tlen = strlen(t);
for (i = slen - 1; i >= tlen - 1; --i) { //O(n^2)
k = i;
for (j = tlen - 1; j >= 0; --j) {
if (s[k--] != t[j]) break;
}
if (j < 0) return i - (tlen - 1);
}
return -1;
}
#include <stdio.h>
/* 简单验证strrindex(s, t) */
int main()
{
char s[] = "hellohellohellohello";
char t1[] = "lo";
char t2[] = "ho";
printf("%d %d\n", strrindex(s, t1), strrindex(s, t2));
return 0;
}
在shell交互界面下简单测试p4_1.c程序:
练习4-2
对atof函数(P.60)进行扩充,使它可以处理形如123.45e-6的科学表示法,其中,浮点数后面可能会紧跟一个e或E以及一个指数(可能有正负号)。
根据stof函数原有的思路简单扩充atof如下:
/* p4_2.c
* 扩展Page.60中的atof函数,使其可以处理形如123.45e-6的科学表示法,
* 其中,浮点数后面可能会紧跟一个e或E以及一个指数(可能有正负号)
*/
#include <ctype.h>
#include <stdio.h>
double atof(char s[])
{
double val, power;
int i, ep, es, sign;
for (i = 0; isspace(s[i]); ++i)
NULL;
sign = (s[i] == '-') ? -1 : 1;
if (s[i] == '+' || s[i] == '-')
++i;
for (val = 0.0; isdigit(s[i]); ++i)
val = 10.0 * val + (s[i] - '0');
if (s[i] == '.')
++i;
for (power = 1.0; isdigit(s[i]); ++i) {
val = 10.0 * val + (s[i] - '0');
power *= 10.0;
}
if (s[i] == 'e' || s[i] == 'E')
++i;
if (s[i] == '+' || s[i] == '-') {
++i;
es = (s[i] == '+') ? 1 : -1;
}
for (ep = 0; isdigit(s[i]); ++i)
ep = 10 * ep + (s[i] - '0');
for (i = 0; i < ep; ++i) {
if (es > 0) power /= 10.0;
else power *= 10.0;
}
return sign * val / power;
}
/* 简单测试stof(s) */
int main()
{
char s[] = "123.45e-6";
printf("%f\n", atof(s));
return 0;
}
在shell程序下简单的测试p4_2.c程序如下:
02.23
4-3 ~ 4-10与书中P.63 ~ P.67的程序相关。
整理P.63 ~ P.67上代码如下:
main.c
/* main.c
* 计算器程序入口程序
*/
#include <stdio.h>
#include <stdlib.h>
#include "share.h"
#define MAXOP 100 /* 操作数的最大长度 */
int getop(char s[]);
void push(double op);
double pop(void);
/* 逆波兰计算器 */
int main()
{
int type;
double op2;
char s[MAXOP];
while ((type = getop(s)) != EOF) {
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 '\n':
printf("\t%.8g\n", pop());
break;
default:
printf("error: unknown command %s\n", s);
break;
}
}
return 0;
}
getop.c
/* getop.c
* 获取操作数或运算符
*/
#include <ctype.h>
#include <stdio.h>
#include "share.h"
int getch(void);
void ungetch(int);
/* getop函数:获取下一个运算符或操作数 */
int getop(char s[])
{
int i, c;
while ((s[0] = c = getch()) == ' ' || c == '\t')
;
s[1] = '\0';
if(!isdigit(c) && c != '.')
return c;
i = 0;
if (isdigit(c))
while (isdigit(s[++i] = c = getch()))
;
if (c == '.')
while (isdigit(s[++i] = c = getch()))
;
s[i] = '\0';
if (c != EOF)
ungetch(c);
return NUMBER;
}
statck.c
/* statck.c
* 保存操作数的栈和栈操作函数
* 本文件中的全局变量对于其它文件来说是隐藏的
*/
#include <stdio.h>
#define MAXVAL 100 /* 栈val的最大深度 */
static int sp = 0; /* 栈中下一个空闲位置 */
static double val[MAXVAL]; /* 栈空间 */
/* push函数:把f压入到栈中 */
void push(double f)
{
if (sp < MAXVAL)
val[sp++] = f;
else
printf("error: stack full, can't push %g\n", f);
}
/* pop函数:弹出并返回栈顶的值 */
double pop(void)
{
if (sp > 0)
return val[--sp];
else {
printf("error: stack empty\n");
return 0.0;
}
}
un_getch.c
/* un_getch.c
* 用缓冲区保存运算符
* 本文件中的全局变量对于其它文件来说是隐藏的
*/
#include <stdio.h>
#define BUFSIZE 100
static char buf[BUFSIZE]; /* 用于ungetch函数的缓冲区 */
static int bufp = 0; /* buf中下一个空闲位置 */
/* getch函数:取一个字符(可能是压回到缓冲区的运算符) */
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;
}
share.h
/* share.h
* 两个文件共享的声明文件
*/
#define NUMBER '0' /* 用于标识是一个数字 */
在shell下简单测试以上程序:
- 栈以及保存运算符及其相关的内存通过全局数组变量(多个函数共用。注 - 少/慎用全局变量)来实现,且这些变量的作用域都只在定义它的文件中(static),其它文件不得擅自访问(在其它文件中被隐藏了起来)。
- ungetch()函数的作用其实是用来保存已读入的运算符(+、-、*、/)。
后面的几个程序写得很差,无稳定性之言。
练习4-3
在有了基本框架后,对计算器程序进行扩充就比较简单了。在该程序中加入取模(%)运算符,并注意考虑负数的情况。
[1] 打算
简单打算如下:
在冷静时思考;用笔画画有助于分析。
[2] 实现打算
简单实现打算。
扩充取模运算。
main.c
...
int main()
{
...
while ((type = getop(s)) != EOF) {
switch (type) {
...
case '%':
op2 = pop();
if (op2 != 0.0)
push((double)((int)pop() % (int)op2));
else
printf("error: zero mod\n");
break;
case '\n':
printf("\t%.8g\n", pop());
break;
default:
printf("error: unknown command %s\n", s);
break;
}
}
return 0;
}
在shell程序下简单的测试取模运算扩充:
由于取模运算只能用于整型,程序中直接将浮点数强制转换为了整型。
02.24
考虑负数。
getop.c
/* getop.c
* 获取操作数或运算符
*/
#include <ctype.h>
#include <stdio.h>
#include "share.h"
int getch(void);
void ungetch(int);
/* getop函数:获取下一个运算符或操作数 */
int getop(char s[])
{
int i, c, cn;
while ((s[0] = c = getch()) == ' ' || c == '\t')
;
s[1] = '\0';
if (!isdigit(c) && c != '.' && c != '+' && c != '-') //非法表达式
return c;
if ((c == '+' || c == '-') && !isdigit(cn = getch())) { //+或-运算符
ungetch(cn);
return c;
}
i = 0;
if (isdigit(c)) { //不带+、-符号的数
while (isdigit(s[++i] = c = getch()))
;
} else { //带正负符号的数
s[++i] = cn;
while (isdigit(s[++i] = c = getch()))
;
}
if (c == '.')
while (isdigit(s[++i] = c = getch()))
;
s[i] = '\0';
if (c != EOF)
ungetch(c);
return NUMBER;
}
在shell程序中测试getop.c程序:
练习4-4
在栈操作中添加几个命令,分别用于在不弹出元素的情况下打印栈顶元素;复制栈顶元素;交换栈顶两个元素的值。另外增加一个命令用于清空栈。
[1] 分析
简单分析如下:
[2] 实现
简单实现分析如下:
stack.c
/* statck.c
* 保存操作数的栈和栈操作函数
* 本文件中的全局变量对于其它文件来说是隐藏的
*/
#include <stdio.h>
#define MAXVAL 100 /* 栈val的最大深度 */
static int sp = 0; /* 栈中下一个空闲位置 */
static double val[MAXVAL]; /* 栈空间 */
...
/* get_top_val函数:不弹出栈顶元素值,获取val栈顶的值 */
double get_top_val(void)
{
if (sp > 0)
return val[sp - 1];
else {
printf("error: stack empty\n");
return -1;
}
}
/* cp_top_val函数:复制栈顶元素到d中
* 返回0复制成功,-1则失败 */
int cp_top_val(double *d)
{
if (!d || sp < 1)
return -1;
*d = get_top_val();
return 0;
}
/* swap_top_val函数:交换两个栈顶元素 */
int swap_top_val(void)
{
double tv1, tv2;
if (sp >= 2) {
tv1 = pop();
tv2 = pop();
push(tv1);
push(tv2);
return 0;
}
return -1;
}
/* clr_stack函数:清空栈 */
void cr_stack(void)
{
sp = 0;
}
练习4-5
给计算器程序增加访问sin、exp与pow等库函数的操作。有关这些库函数的详细信息,参见附录B.4节中的头文件< math.h>。
[1] 分析
简单分析如下:
[2] 实现
简单实现分析如下:
main.c
/* main.c
* 计算器程序入口程序
*/
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include "share.h"
#define MAXOP 100 /* 操作数的最大长度 */
#define MAXCMMD 10 /* 命令的最大长度 */
....
void get_rp_cal_cmmd(void);
void match_cmmd(char cmmd[]);
/* 逆波兰计算器 */
int main()
{
get_rp_cal_cmmd();
return 0;
}
/* get_cp_cal_cmmd函数:解析输入的命令 */
void get_rp_cal_cmmd(void)
{
int i;
int type;
char op[MAXOP];
char cmmd[MAXCMMD];
double op2;
i = 0;
while ((type = getop(op)) != EOF) {
switch (type) {
....
case '\n':
if (i == 0)
printf("\t%.8g\n", pop());
else {
cmmd[i] = '\0';
i = 0;
match_cmmd(cmmd);
}
break;
default:
if (i < MAXCMMD)
cmmd[i++] = type;
else {
cmmd[i] = '\0';
i = 0;
printf("error: command %s...too long\n", cmmd);
}
break;
}
}
}
/* match_cmmd函数:匹配输入命令是否为库函数名 */
void match_cmmd(char cmmd[])
{
double op2;
if (!strcmp(cmmd, "sin")) {
printf("\t%.8g\n", sin(pop()));
} else if (!strcmp(cmmd, "exp")) {
printf("\t%.8g\n", exp(pop()));
} else if (!strcmp(cmmd, "pow")) {
op2 = pop();
printf("\t%.8g\n", pow(pop(), op2));
}/*else if...*/else {
printf("error: command not known\n");
}
}
在shell程序中简单的测试该程序:
02.25
练习4-6
给计算器程序增加处理变量的命令(提供26个具有单个英文字母变量名的变量很容易)。增加一个变量存放最近打印的值。
[1] 分析
简单分析/打算如下:
[2] 打算
简单实现分析/打算如下:
main.c
/* main.c
* 计算器程序入口程序
*/
...
int is_stack_empty(void);
/* 逆波兰计算器 */
int main()
{
get_rp_cal_cmmd();
return 0;
}
/* get_cp_cal_cmmd函数:解析输入的命令 */
void get_rp_cal_cmmd(void)
{
int i, sv; /* sv用来保存'='前的字符 */
int type;
...
double v, op2; /* lv用来保存最近所打印的值 */
double var[26]; /* 用'A' ~ 'Z'单个英文字母作为计算器的内部变量 */
v = 0.0;
for (i = 0; i < 26; ++i)
var[i] = 0.0;
i = 0;
while ((type = getop(op)) != EOF) {
switch (type) {
...
case '=':
if (isupper(sv)) {
pop(); /* 弹出变量对应的值 */
var[sv - 'A'] = pop(); /* 给变量对应的内存赋值 */
} else
printf("error: no varible\n");
break;
case '\n':
if (i == 0) {
if (!is_stack_empty()) {
v = pop();
printf("\t%.8g\n", v);
}
} else {
cmmd[i] = '\0';
i = 0;
match_cmmd(cmmd);
}
break