OOC源码(三)

        《ooc》第三章针对动态链接举例,解析并计算算术表达式。《数据结构》中有关于这一块的描述,即算术表达式是某个二叉树的中序序列,示例中是将该算术表达式(中序序列)还原为一个二叉树,然后进行计算。当然该例的目的不是让我们重温数据结构,而是让我们通过该示例进一步加深对“动态链接”的理解。


        首先从二叉树着手,抽象出结点类型,具体包括分支结点(包括根结点)、叶子;分支结点对应于二元操作符(+ - * /),叶子结点对应于数值或一元操作符(负值运算符);现在对于我们需要定义的类型有了大致的了解,我们需要针对所有类型定义计算方法,用于计算以该结点为根结点的树的值。和第二章类似,我们还需要针对对象资源的分配和回收作些文章。首先声明结点类型、树(对象)管理的方法:

/*
 *	node types
 */

const void * Minus;
const void * Value;
const void * Mult;
const void * Div;
const void * Add;
const void * Sub;

/*
 *	tree management
 */

void * new (const void * type, ...);
void process (const void * tree);
void delete (void * tree);

        类型描述符定义:

struct Type {
	void * (* new) (va_list ap);
	double (* exec) (const void * tree);
	void (* delete) (void * tree);
};

        对象管理相关方法定义:

void * new (const void * type, ...)
{	va_list ap;
	void * result;

	assert(type && ((struct Type *) type) -> new);

	va_start(ap, type);
	result = ((struct Type *) type) -> new(ap);
	* (const struct Type **) result = type;
	va_end(ap);
	return result;
}

// 树值计算
static double exec (const void * tree)
{
	assert(tree && * (struct Type **) tree
		&& (* (struct Type **) tree) -> exec);

	return (* (struct Type **) tree) -> exec(tree);
}

void process (const void * tree)
{
	printf("\t%g\n", exec(tree));
}

void delete (void * tree)
{
	assert(tree && * (struct Type **) tree
		&& (* (struct Type **) tree) -> delete);

	(* (struct Type **) tree) -> delete(tree);
}

        数值结点类型定义

struct Val {
	const void * type;
	double value;
};

static void * mkVal (va_list ap)
{	struct Val * node = malloc(sizeof(struct Val));

	assert(node);
	node -> value = va_arg(ap, double);
	return node;
}

static double doVal (const void * tree)
{
	return ((struct Val *) tree) -> value;
}

static struct Type _Value = { mkVal, doVal, free };
const void * Value = & _Value;

        一元操作符及负值运算类型结点定义:

struct Un {
	const void * type;
	void * arg;
};

static void * mkUn (va_list ap)
{	struct Un * node = malloc(sizeof(struct Un));

	assert(node);
	node -> arg = va_arg(ap, void *);
	return node;
}

static double doMinus (const void * tree)
{
	return - exec(((struct Un *) tree) -> arg);
}

static void freeUn (void * tree)
{
	delete(((struct Un *) tree) -> arg);
	free(tree);
}

static struct Type _Minus = { mkUn, doMinus, freeUn };
const void * Minus = & _Minus;

        二元操作符类型定义:

struct Bin {
	const void * type;
	void * left, * right;
};

static void * mkBin (va_list ap)
{	struct Bin * node = malloc(sizeof(struct Bin));

	assert(node);
	node -> left = va_arg(ap, void *);
	node -> right = va_arg(ap, void *);
	return node;
}

static double doAdd (const void * tree)
{
	return exec(((struct Bin *) tree) -> left) +
			exec(((struct Bin *) tree) -> right);
}

static double doSub (const void * tree)
{
	return exec(((struct Bin *) tree) -> left) -
		exec(((struct Bin *) tree) -> right);
}

static double doMult (const void * tree)
{
	return exec(((struct Bin *) tree) -> left) *
		exec(((struct Bin *) tree) -> right);
}

static double doDiv (const void * tree)
{	double left = exec(((struct Bin *) tree) -> left);
	double right = exec(((struct Bin *) tree) -> right);

	if (right == 0.0)
		error("division by zero");
	return left / right;
}

static void freeBin (void * tree)
{
	delete(((struct Bin *) tree) -> left);
	delete(((struct Bin *) tree) -> right);
	free(tree);
}

static struct Type _Add = { mkBin, doAdd, freeBin };
static struct Type _Sub = { mkBin, doSub, freeBin };
static struct Type _Mult = { mkBin, doMult, freeBin };
static struct Type _Div = { mkBin, doDiv, freeBin };

const void * Add = & _Add;
const void * Sub = & _Sub;
const void * Mult = & _Mult;
const void * Div = & _Div;

        这样,我们就将所需结点类型描述完毕了。下面主要叙述表达式解析,这部分在《数据结构》中已经晒了很多。

        我们在针对某个表达式解析时,需要判断当前的symbols是空格、数值还是字符(运算符、括号等);处理时首先忽略掉空格,这就需要标记当前的东东是数值还是字符,我们用枚举值NUMBER表示数值:

enum tokens {				/* must not clash with operators */
	NUMBER = 'n'			/* literal constant */
};

        使用token变量标记是字符还是数值,如果是数值便将数值存入number变量中:

static enum tokens token;	/* current input symbol */
static double number;		/* if NUMBER: numerical value */

        下面给出字符和数值的提取方法:

static enum tokens scan (const char * buf)
					/* return token = next input symbol */
{	static const char * bp;

	if (buf)
		bp = buf;			/* new input line */

	while (isspace(* bp & 0xff))
		++ bp;
	if (isdigit(* bp & 0xff) || * bp == '.')
	{	errno = 0;
		token = NUMBER, number = strtod(bp, (char **) & bp);
		if (errno == ERANGE)
			error("bad value: %s", strerror(errno));
	}
	else
	{
		token = * bp ? * bp ++ : 0;
	}
	return token;
}

        下面三个方法factor、product、sum是用来生成树的,内部使用递归,不方便多讲;factor主要负责创建一元操作符结点(负值)和数值(对象),就是终端节点(树叶);product创建二元操作符结点(* /),即子树;sum负责创建二元操作符结点(+ -),即树的主干。

/*
 *	factor : + factor
 *			 - factor
 *			 NUMBER
 *			 ( sum )
 */

static void * sum (void);

static void * factor (void)
{	
	void * result;

	switch (token) {
	case '+':
		scan(0);
		return factor();
	case '-':
		scan(0);
		return new(Minus, factor());
	default:
		error("bad factor: '%c' 0x%x", token, token);
	case NUMBER:
		result = new(Value, number);
		break;
	case '(':
		scan(0);
		result = sum();
		if (token != ')')
			error("expecting )");
	}
	scan(0);
	return result;
}

/*
 *	product : factor { *|/ factor }...
 */

static void * product (void)
{	
	void * result = factor();
	const void * type;

	for (;;)
	{	switch (token) {
		case '*':
			type = Mult;
			break;
		case '/':
			type = Div;
			break;
		default:
			return result;
		}
		scan(0);
		result = new(type, result, factor());
	}
}

/*
 *	sum : product { +|- product }...
 */

static void * sum (void)
{
	void * result = product();
	const void * type;

	for (;;)
	{	switch (token) {
		case '+':
			type = Add;
			break;
		case '-':
			type = Sub;
			break;
		default:
			return result;
		}
		scan(0);
		result = new(type, result, product());
	}
}

        此外,还提供了异常处理程序,增强程序健壮性

static jmp_buf onError;

void error (const char * fmt, ...)
{	va_list ap;

	va_start(ap, fmt);
	vfprintf(stderr, fmt, ap), putc('\n', stderr);
	va_end(ap);
	longjmp(onError, 1);
}


       主函数:
int main (void)
{	volatile int errors = 0;
	char buf [BUFSIZ];

	if (setjmp(onError))			// 异常恢复断点
		++ errors;

	while (fgets(buf, sizeof buf, stdin))
		if (scan(buf))
		{
			void * e = sum();		// 创建树

			if (token)
				error("trash after sum");
			process(e);				// 计算树值
			delete(e);				// 释放树
		}

	return errors > 0;
}





你可能感兴趣的:(面向对象,OOC)