7.6 设计程序
前面介绍了C语言中一个比较难的部分,现在运用学过的内容编写一个应用程序。本节将依循惯例,先分析、设计,然后一步步编写代码。这是本章最后一个程序。
7.6.1 问题
要处理的问题是用一些新的特性重写第3章的计算器程序,但这次要使用指针。主要改进如下:
● 允许使用有符号的小数,包含带–或+符号的小数和有符号的整数。
● 允许表达式组合多个运算式,如2.5+3.7–6/6。
● 添加运算符^,计算幂,因此2^3会得到8。
● 允许使用前一个运算的结果。如果前一个运算的结果是2.5,那么=*2+7会得到12。任何以赋值运算符开头的输入行都自动假设左操作数是前一个运算的结果。
不考虑运算符的优先级,只简单地从左到右计算输入的表达式,将每个运算符应用于前一个结果和右操作数。所以下面的表达式:
1 + 2*3–4*–5
会以如下方式计算:
((1 + 2)*3 - 4)*(-5)
7.6.2 分析
我们事先并不知道表达式有多长或有多少个操作数。用户会输入一个完整的字符串,然后分析它,确定它包含什么数值和运算符。只要一个运算符有左右操作数,就计算其中间结果。
步骤如下:
(1) 读入用户输入的字符串,如果它是quit,就退出。
(2) 检查=运算符,如果有一个=运算符,就查找第一个操作数。
(3) 寻找跟在操作数后的运算符,依次执行每一个运算符,直到输入字符串结束为止。
(4) 显示结果,并回到步骤1。
7.6.3 解决方案
本节列出解决问题的步骤。
1. 步骤1
如本章前面所述,scanf()函数不允许读入含有空格的完整字符串,而是在第一个空白字符处停止读取。因此,这里使用在头文件<stdio.h>中声明的gets()函数读取输入的表达式。这个函数会读入整行输入,包含空格。可以将输入和整个程序的循环结合在一起,如下:
/* Program 7.15 An improved calculator */
#include <stdio.h> /* Standard input/output */
#include <string.h> /* For string functions */
#define BUFFER_LEN 256 /* Length of input buffer */
int main(void)
{
char input[BUFFER_LEN]; /* Input expression */
while(strcmp(fgets(input, BUFFER_LEN, stdin), "quit\n") != 0)
{
/* Code to implement the calculator */
}
return 0;
}
可以这么做,是因为函数strcmp()的参数是一个指向字符串的指针,而函数fgets()会返回一个指向用户输入的字符串的指针,在这个例子中是&input[0]。strcmp()函数会比较输入的字符串和"quit\n",如果它们相等,就返回0。然后,结束循环。
设定输入的字符串长度是256。这应该足够了,因为大多数计算机的键盘缓冲区是255个字符(这也是按下回车键之前,所能输入的最大字符数)。
有了字符串,就可以开始分析它,但最好从字符串中删除空格。因为输入的字符串是定义好的,不需要用空格分隔运算符和操作数。下面在while循环中添加代码,删除空格:
/* Program 7.15 An improved calculator */
#include <stdio.h> /* Standard input/output */
#include <string.h> /* For string functions */
#define BUFFER_LEN 256 /* Length of input buffer */
int main(void)
{
char input[BUFFER_LEN]; /* Input expression */
unsigned int index = 0; /* Index of the current character in input */
unsigned int to = 0; /* To index for copying input to itself */
size_t input_length = 0; /* Length of the string in input */
while(strcmp(fgets(input, BUFFER_LEN, stdin), "quit\n") != 0)
{
input_length = strlen(input); /* Get the input string length */
input[--input_length] = '\0'; /* Remove newline at the end */
/* Remove all spaces from the input by copy the string to itself */
/* including the string terminating character */
for(to = 0, index = 0 ; index<=input_length ; index++)
if(*(input+index) != ' ') /* If it is not a space */
*(input+to++) = *(input+index); /* Copy the character */
input_length = strlen(input); /* Get the new string length */
index = 0; /* Start at the first character */
/* Code to implement the calculator */
}
return 0;
}
上述代码声明了一些额外的变量。变量input_length声明为类型size_t,是为了和strlen()函数返回的类型兼容,以避免编译器产生警告信息。
按下回车键结束输入行后,fgets()函数会存储一个换行符。分析这个字符串的代码不希望看到换行符,所以用'\0\'覆盖它。input数组的索引表达式递减strlen()函数返回的长度值,使用其结果引用包含换行符的元素。
将存储在input中的字符串复制到它本身,以删除空格。在复制循环中需要跟踪两个索引:一个是下一个要复制的非空格字符在input中的位置,另一个是下一个要复制的字符的位置。在这个循环中不复制空格;只递增index,以移动到下一个字符。索引to只有在复制字符时才递增。进入循环后,把新字符串长度存储到input_length中,并重置索引,以引用input的第一个字符。
可以用数组表示法来编写这个循环:
for(to = 0, index = 0 ; index<=input_length ; index++)
if(input[index] != ' ') /* If it is not a space */
input[to++] = input[index]; /* Copy the character */
使用数组表示法比较整洁,但下面使用指针表示法来实践。
2. 步骤2
输入的表达式有两种可能的形式。它可以由一个赋值运算符开始,表示最后的结果要用作左操作数,它也可以由一个有符号或无符号的数值开始。可以先寻找“=”字符来区分这两种情况。如果找到一个“=”字符,左操作数就是前一个运算的结果。
下面添加到while循环中的代码将查找“=”字符,如果没找到,就寻找一个数值子字符串,它应该是左操作数:
/* Program 7.15 An improved calculator */
#include <stdio.h> /* Standard input/output */
#include <string.h> /* For string functions */
#include <ctype.h> /* For classifying characters */
#include <stdlib.h> /* For converting strings to numeric values */
#define BUFFER_LEN 256 /* Length of input buffer */
int main(void)
{
char input[BUFFER_LEN]; /* Input expression */
char number_string[30]; /* Stores a number string from input */
unsigned int index = 0; /* Index of the current character in input */
unsigned int to = 0; /* To index for copying input to itself */
size_t input_length = 0; /* Length of the string in input */
unsigned int number_length = 0;
/* Length of the string in number_string */
double result = 0.0; /* The result of an operation */
while(strcmp(fgets(input, BUFFER_LEN, stdin), "quit\n") != 0)
{
input_length = strlen(input); /* Get the input string length */
input[--input_length] = '\0'; /* Remove newline at the end */
/* Remove all spaces from the input by copying the string to itself */
/* including the string terminating character */
for(to = 0, index = 0 ; index<=input_length ; index++)
if(*(input+index) != ' ') /* If it is not a space */
*(input+to++) = *(input+index); /* Copy the character */
input_length = strlen(input); /* Get the new string length */
index = 0; /* Start at the first character */
if(input[index]== '=') /* Is there =? */
index++; /* Yes so skip over it */
else
{ /* No - look for the left operand */
/* Look for a number that is the left operand for */
/* the first operator */
/* Check for sign and copy it */
number_length = 0; /* Initialize length */
if(input[index]=='+' || input[index]=='-') /* Is it + or -? */
*(number_string+number_length++) = *(input+index++);
/* Yes so copy it */
/* Copy all following digits */
for( ; isdigit(*(input+index)) ; index++) /* Is it a digit? */
*(number_string+number_length++) = *(input+index);
/* Yes - Copy it */
/* copy any fractional part */
if(*(input+index)=='.') /* Is it decimal point? */
{ /* Yes so copy the decimal point and the following digits */
*(number_string+number_length++) = *(input+index++);
/* Copy point */
for( ; isdigit(*(input+index)) ; index++) /* For each digit */
*(number_string+number_length++) = *(input+index); /* copy it */
}
*(number_string+number_length) = '\0'; /* Append string terminator */
/* If we have a left operand, the length of number_string */
/* will be > 0. In this case convert to a double so we */
/* can use it in the calculation */
if(number_length>0)
result = atof(number_string); /* Store first number as result */
}
/* Code to analyze the operator and right operand */
/* and produce the result */
}
return 0;
}
为字符分析函数包含<ctype.h>头文件,为使用atof()函数包含<stdlib.h>头文件,atof()函数将一个字符串参数转换成浮点值。这里添加了相当多的代码,但它们都是由一些容易理解的步骤组成。
if语句检查“=”是否为input中的第一个字符:
if(input[index]== '=') /* Is there =? */
index++; /* Yes so skip over it */
如果找到一个“=”,就递增index,跳过它,直接找寻操作数。否则,就执行else,查找数值型的左操作数。
把组成这个数的所有字符复制到number_string数组中。这个数可能由一元运算符'–'或'+'开头,所以在else块中首先检查第一个字符。如果找到该一元运算符,就用以下的语句把它复制到number_string中:
if(input[index]=='+' || input[index]=='-') /* Is it + or -? */
*(number_string+number_length++) = *(input+index++); /* Yes so copy it */
如果没有找到符号,index值就保持不变,index值记录了当前要在input中分析的字符。如果找到符号,就将它复制到number_string中,然后递增index的值,指向下一个字符。
接下来应有一个或多个数字,所以用一个for循环将所有的数字复制到number_string中:
for( ; isdigit(*(input+index)) ; index++) /* Is it a digit? */
*(number_string+number_length++) = *(input+index); /* Yes - Copy it */
这将复制整数的所有数字,随后相应递增index的值。当然,如果没有数字,index的值不会改变。
这个数可能不是整数。在这种情形下,下一个必定是小数点,它可能后跟许多数字。if语句检查小数点。如果有一个小数点,就复制这个小数点和其后的数字:
if(*(input+index)=='.') /* Is it decimal point? */
{ /* Yes so copy the decimal point and the following digits */
*(number_string+number_length++) = *(input+index++); /* Copy point */
for( ; isdigit(*(input+index)) ; index++) /* For each digit */
*(number_string+number_length++) = *(input+index); /* copy it */
}
现在复制完表示第一个操作数的字符串,所以给number_string附加一个字符串终止字符:
*(number_string+number_length) = '\0'; /* Append string terminator */
也可能没有找到数值,如果在number_string中复制了代表一个数值的字符串,number_length的值就必然是正的,因为它至少要有一个数字。因此,将number_length的值作为有一个数值的指示器:
if(number_length>0)
result = atof(number_string); /* Store first number as result */
atof()函数把这个字符串转换成double类型的浮点数。注意,字符串的值存储到result中。后面会使用这个变量存储运算的结果。这可确保result始终含有运算的结果,包含计算完整个字符串的结果。如果不在这里存储值,由于没有左操作数,result将含有前一个输入字符串的值。
3. 步骤3
此时,输入字符串后的内容已定义得非常清楚。它必然是一个运算符跟一个数值。这个运算符把之前找到的数或前一个的result用作左操作数。这个运算符和数值(op-number)的组合也可能后跟另一个运算符和数值组合,所以可能会有连续的运算符和数值组合,直到字符串结束。可以用一个循环查找这些组合:
/* Program 7.15 An improved calculator */
#include <stdio.h> /* Standard input/output */
#include <string.h> /* For string functions */
#include <ctype.h> /* For classifying characters */
#include <stdlib.h> /* For converting strings to numeric values */
#define BUFFER_LEN 256 /* Length of input buffer */
int main(void)
{
char input[BUFFER_LEN]; /* Input expression */
char number_string[30]; /* Stores a number string from input */
char op = 0; /* Stores an operator */
unsigned int index = 0; /* Index of the current character in input */
unsigned int to = 0; /* To index for copying input to itself */
size_t input_length = 0; /* Length of the string in input */
unsigned int number_length = 0;
/* Length of the string in number_string */
double result = 0.0; /* The result of an operation */
double number = 0.0; /* Stores the value of number_string */
while(strcmp(fgets(input, BUFFER_LEN, stdin), "quit\n") != 0)
{
input_length = strlen(input); /* Get the input string length */
input[--input_length] = '\0'; /* Remove newline at the end */
/* Remove all spaces from the input by copying the string to itself */
/* including the string terminating character */
/* Code to remove spaces as before... */
/* Code to check for '=' and analyze & store the left operand as before */
/* Now look for 'op number' combinations */
for(;index < input_length;)
{
op = *(input+index++); /* Get the operator */
/* Copy the next operand and store it in number */
number_length = 0; /* Initialize the length */
/* Check for sign and copy it */
if(input[index]=='+' || input[index]=='-') /* Is it + or -? */
*(number_string+number_length++) = *(input+index++);
/* Yes - copy it. */
/* Copy all following digits */
for( ; isdigit(*(input+index)) ; index++) /* For each digit */
*(number_string+number_length++) = *(input+index); /* copy it. */
/* copy any fractional part */
if(*(input+index)=='.') /* Is it a decimal point? */
{ /* Copy the decimal point and the following digits */
*(number_string+number_length++) = *(input+index++); /* Copy point */
for( ; isdigit(*(input+index)) ; index++) /* For each digit */
*(number_string+number_length++) = *(input+index); /* copy it. */
}
*(number_string+number_length) = '\0'; /* terminate string */
/* Convert to a double so we can use it in the calculation */
number = atof(number_string);
}
/* code to produce result */
}
return 0;
}
这里没有重复相同的代码,而是用一些注释指出在哪里添加上述代码。下次给程序添加代码时,将列出完整的代码。
for循环会持续到到达输入串的末尾为止,此时index等于input_length。循环在每次迭代时,都把运算符存储到char类型变量op中:
op = *(input+index++); /* Get the operator */
执行这个运算符后,提取组成下一个数的字符,这是运算符的右操作数。这里没有验证这个运算符是否有效,所以此时程序不立即断定它是无效的运算符。
从字符串中提取右操作数的过程和提取左操作数完全相同。重复相同的代码。然而这次,操作数的double值存储到number中:
number = atof(number_string);
现在左操作数存储在result中,运算符存储在op中,右操作数存储在number中。下面准备执行这个计算式:
Resule=(result op number]
为此添加代码后,这个程序就完整了。
4. 步骤4
用一个switch语句根据操作数选择要执行的运算。这和之前的计算器代码相同。显示输出,并且程序开头添加一个提示,说明如何使用这个计算器。下面是这个程序的完整代码,新加的代码用粗体字显示:
/* Program 7.15 An improved calculator */
#include <stdio.h> /* Standard input/output */
#include <string.h> /* For string functions */
#include <ctype.h> /* For classifying characters */
#include <stdlib.h> /* For converting strings to numeric values */
#include <math.h> /* For power() function */
#define BUFFER_LEN 256 /* Length of input buffer */
int main(void)
{
char input[BUFFER_LEN]; /* Input expression */
char number_string[30]; /* Stores a number string from input */
char op = 0; /* Stores an operator */
unsigned int index = 0; /* Index of the current character in input */
unsigned int to = 0; /* To index for copying input to itself */
size_t input_length = 0; /* Length of the string in input */
unsigned int number_length = 0; /* Length of the string in number_string */
double result = 0.0; /* The result of an operation */
double number = 0.0; /* Stores the value of number_string */
printf("\nTo use this calculator, enter any expression with"
" or without spaces");
printf("\nAn expression may include the operators:");
printf("\n +, -, *, /, %%, or ^(raise to a power).");
printf("\nUse = at the beginning of a line to operate on ");
printf("\nthe result of the previous calculation.");
printf("\nUse quit by itself to stop the calculator.\n\n");
/* The main calculator loop */
while(strcmp(fgets(input, BUFFER_LEN, stdin), "quit\n") != 0)
{
input_length = strlen(input); /* Get the input string length */
input[--input_length] = '\0'; /* Remove newline at the end */
/* Remove all spaces from the input by copying the string to itself */
/* including the string terminating character */
for(to = 0, index = 0 ; index<=input_length ; index++)
if(*(input+index) != ' ') /* If it is not a space */
*(input+to++) = *(input+index); /* Copy the character */
input_length = strlen(input); /* Get the new string length */
index = 0; /* Start at the first character */
if(input[index]== '=') /* Is there =? */
index++; /* Yes so skip over it */
else
{ /* No - look for the left operand */
/* Look for a number that is the left operand for the 1st operator */
/* Check for sign and copy it */
number_length = 0; /* Initialize length */
if(input[index]=='+' || input[index]=='-') /* Is it + or -? */
*(number_string+number_length++) = *(input+index++);
/* Yes so copy it */
/* Copy all following digits */
for( ; isdigit(*(input+index)) ; index++) /* Is it a digit? */
*(number_string+number_length++) = *(input+index); /* Yes - Copy it */
/* copy any fractional part */
if(*(input+index)=='.') /* Is it decimal point? */
{ /* Yes so copy the decimal point and the following digits */
*(number_string+number_length++) = *(input+index++); /* Copy point */
for( ; isdigit(*(input+index)) ; index++) /* For each digit */
*(number_string+number_length++) = *(input+index); /* copy it */
}
*(number_string+number_length) = '\0'; /* Append string terminator */
/* If we have a left operand, the length of number_string */
/* will be > 0. In this case convert to a double so we */
/* can use it in the calculation */
if(number_length>0)
result = atof(number_string); /* Store first number as result */
}
/* Now look for 'op number' combinations */
for(;index < input_length;)
{
op = *(input+index++); /* Get the operator */
/* Copy the next operand and store it in number */
number_length = 0; /* Initialize the length */
/* Check for sign and copy it */
if(input[index]=='+' || input[index]=='-') /* Is it + or -? */
*(number_string+number_length++) = *(input+index++);
/* Yes - copy it. */
/* Copy all following digits */
for( ; isdigit(*(input+index)) ; index++) /* For each digit */
*(number_string+number_length++) = *(input+index); /* copy it. */
/* copy any fractional part */
if(*(input+index)=='.') /* Is it a decimal point? */
{ /* Copy the decimal point and the following digits */
/* Copy point */
*(number_string+number_length++) = *(input+index++);
for( ; isdigit(*(input+index)) ; index++) /* For each digit */
*(number_string+number_length++) = *(input+index); /* copy it. */
}
*(number_string+number_length) = '\0'; /* terminate string */
/* Convert to a double so we can use it in the calculation */
number = atof(number_string);
/* Execute operation, as 'result op= number' */
switch(op)
{
case '+': /* Addition */
result += number;
break;
case '-': /* Subtraction */
result -= number;
break;
case '*': /* Multiplication */
result *= number;
break;
case '/': /* Division */
/* Check second operand for zero */
if(number == 0)
printf("\n\n\aDivision by zero error!\n");
else
result /= number;
break;
case '%': /* Modulus operator - remainder */
/* Check second operand for zero */
if((long)number == 0)
printf("\n\n\aDivision by zero error!\n");
else
result = (double)((long)result % (long)number);
break;
case '^': /* Raise to a power */
result = pow(result, number);
break;
default: /* Invalid operation or bad input */
printf("\n\n\aIllegal operation!\n");
break;
}
}
printf("= %f\n", result); /* Output the result */
}
return 0;
}
switch语句和前一个计算器程序相同,但多了一些case。因为这个程序用幂函数pow()计算resultnumber,所以必须使用#include指令包含头文件<math.h>。这个计算器程序的输出如下:
To use this calculator, enter any expression with or without spaces
An expression may include the operators:
+, -, *, /, %, or ^(raise to a power).
Use = at the beginning of a line to operate on
the result of the previous calculation.
Use quit by itself to stop the calculator.
2.5+3.3/2
= 2.900000
= *3
= 8.700000
= ^4
= 5728.976100
1.3+2.4-3.5+-7.8
= -7.600000
=*-2
= 15.200000
= *-2
= -30.400000
= +2
= -28.400000
quit





