作者:palmheart 来源:palmheart.net
在这一章中,我们将编写计算器程序运运算的核心部分。我先为核心编制一个ANSI C版本,然后将详细探讨如何使这些代码在Palm OS中运行。
设计计算器核心
在开始写代码之前,我们必须知道到底该做些什么。一个解决这类问题的好方法是建立一个包括程序各个状态并通过箭头相连表示它们之间的关系的状态表。对本程序来说,这也是个很好的办法,因为这个程序比一般的程序包含了更多状态和状态关联。
你也可以将状态表制成一个大纲的形式,这样会使对程序看的更清楚。通常,当我想知道程序该完成哪些工作时,我就先从纸上画出程序的状态表大纲。不管效果如何,你可以先试一下。我建议你在看我的状态表之前,自己先做一个。表14-1是我所做的状态表。
状态:
操作算子――->改变符号―――>完成操作、显示结果、保存―――>回到预备状态
等于号(equal)―――>改变符号―――>完成操作、显示结果―――>回到预备状态
清除号(clear) ―――>清除数字和操作算子―――>回到预备状态
完成号(Done)―――>拷贝当前显示结果到粘贴板―――>退出程序预备状态:显示当前数字
0―――>显示0―――>预备状态
其它数字―――>显示数字―――>插入状态
改变符号―――>显示-0―――>预备状态
小数点―――>显示0. ―――>小数状态
指数―――>出错声―――>预备状态
整数状态:显示一个整数
数字―――>如果是允许输入的最大数字,则发出声音―――>整数状态
改变符号―――>给当前整数取反―――>整数状态
小数点―――>在当前整数后加小数点显示―――>小数状态
指数―――>在当前整数后加e+0显示―――>指数状态
小数状态:显示一个数字和一个小数点
数字―――>如果是允许输入的最大数字就发出报警音―――>把新输入的数字显示在最后―――>小数状态
改变符号―――>给当前显示的数字取反显示―――>小数状态
小数点―――>出错音―――>小数状态
指数―――>在当前显示的数字后加e+0显示―――>指数状态
指数预备状态:带e+0的显示一个数字
0―――>指数预备状态
其它数字―――>显示数字的指数―――>指数输入状态
改变符号―――>改变当前的指数符号为e-0―――>指数预备状态
小数点―――>出错音―――>指数预备状态
指数号―――>出错音―――>指数预备状态
指数输入状态:显示带指数的数字
数字―――>如果是允许输入的最大数字就发出警报音―――>给输入的数字取指数―――>指数输入状态
改变符号―――>改变指数的符号―――>指数输入状态
小数点―――>出错音―――>指数输入状态
指数号―――>出错音―――>指数输入装态
在我开始的状态表中,还有一些其它的状态,例如Clear状态和Enter Second Number状态,但当我画完时,发现这些状态可以很容易的融入到其它的状态中去。在开始编程之前,状态表将是一个很好的参考材料,所以我建议都来使用状态表,况且其它的大多数界面都要比本例子简单和直接。
剩余代码
为了好解释有关将ANSI标准C代码导入Palm OS的知识,我用以下的方法编写了程序。在程序中有一些可移植性的错误,看看你自己在看导入代码那部分之前能不能将它找出来。
新的calc.h
这个calc.h将代替上一章最后创建的“虚”文件,以便我们做进一步的调试。
#ifndef CALC_H
#define CALC_H
//////////////////////////////////////////////////////////////////////////////
// calc.h
// Definitions for the generic calculation routines.
// Copyright (c) 1999, Robert Mykland. All rights reserved.
//////////////////////////////////////////////////////////////////////////////
///////////////////////
// Global Prototypes //
///////////////////////
void calcAdd( void ); // Queue an add operation
void calcAppend( int ); // Append a digit
void calcChangeSign( void ); // Change the sign of the entry
void calcClear( void ); // Clear/reset the calculator
void calcDivide( void ); // Queue a divide operation
void calcEquals( void ); // Finish the current operation
void calcExponent( void ); // Start gathering the exponent
void calcMultiply( void ); // Queue a multiply operation
void calcPoint( void ); // Start gathering the fraction
void calcSubtract( void ); // Queue a subtraction operation
//////////////////////
// Global Constants //
//////////////////////
#define MAX_NUMBER_SIZE 40
#endif // CALC_H
现在头文件已经转换为了原来传统类型的文件。
Calc.c的普通代码
在文件calc.c中实现了calc.h中定义的函数。我们将对这些代码进行简单的描述,因为它们和Palm OS关系不大,只是ANSI中实现计算器功能的普通代码。
//////////////////////////////////////////////////////////////////////////////
// calc.c
// Implements a generic calculator.
// Copyright (c) 1999, Robert Mykland. All rights reserved.
//////////////////////////////////////////////////////////////////////////////
//////////////
// Includes //
//////////////
#include "app.h" // The definitions for this application
#include "calc.h" // The definitions for this module
///////////////////////
// Global Prototypes //
///////////////////////
void calcAdd( void ); // Queue an add operation
void calcAppend( int ); // Append a digit
void calcChangeSign( void ); // Change the sign of the entry
void calcClear( void ); // Clear/reset the calculator
void calcDivide( void ); // Queue a divide operation
void calcEquals( void ); // Finish the current operation
void calcExponent( void ); // Start gathering the exponent
void calcMultiply( void ); // Queue a multiply operation
void calcPoint( void ); // Start gathering the fraction
void calcSubtract( void ); // Queue a subtraction operation
//////////////////////
// Local Prototypes //
//////////////////////
static double ca2n( char* ); // Converts an ascii string to a double
static void n2ca( double, char* ); // Changes a double to a string
/////////////////////
// Local Constants //
/////////////////////
#define MAX_DIGITS 8
#define MAX_EXP_DIGITS 2
enum {
OPERATION_NONE = 0,
OPERATION_ADD,
OPERATION_DIVIDE,
OPERATION_MULTIPLY,
OPERATION_SUBTRACT
};
/////////////////////
// Local Variables //
/////////////////////
static char caNumber[MAX_NUMBER_SIZE] = "+0";
static int iDigitCount;
static int iOperator;
static double nOperand;
static int oExponent;
static int oFraction;
以上是下面讲到函数所用的头文件、函数原型以及变量的声明。
//////////////////////
// Global Functions //
//////////////////////
//----------------------------------------------------------------------------
void calcAdd(
//----------------------------------------------------------------------------
// Queues an add operation.
//----------------------------------------------------------------------------
void )
//----------------------------------------------------------------------------
{
// Resolve any pending operations
calcEquals();
// Queue the operation
iOperator = OPERATION_ADD;
// We're done
return;
}
这个函数解决了“加”的操作。函数calcEquals()处理了所有没有完成的计算,所以我们不需检查“加”到底是“加”还是“被加”的状态。
//----------------------------------------------------------------------------
void calcAppend(
//----------------------------------------------------------------------------
// Appends a digit to the entry.
//----------------------------------------------------------------------------
int iDigit ) // The digit to append
//----------------------------------------------------------------------------
{
char caDigit[2];
// If we are entering the exponent part
if( oExponent )
{
// If the exponent digit count is at maximum, then signal an error
if( iDigitCount >= MAX_EXP_DIGITS )
{
calcSignalError();
return;
}
}
else
// If we are entering the number part
{
// If the digit count is at maximum, then signal an error
if( iDigitCount >= MAX_DIGITS )
{
calcSignalError();
return;
}
}
// Destroy leading zeroes
if( (oFraction == false) && (iDigitCount < 2) && (caNumber[1] == '0') )
caNumber[1] = '\0';
// Append the digit
caDigit[0] = iDigit + 0x30;
caDigit[1] = '\0';
strcat( caNumber, caDigit );
// Increase the digit count if it wasn't a leading zero
if( (oFraction == true) || (iDigitCount == 0) || (caNumber[1] != '0') )
iDigitCount++;
// Display the new number
calcDisplay( caNumber );
return;
}
函数calcAppend()函数是程序中最复杂的函数之一。它将检查我们输入的数字、指数符和操作符是否规范。
//----------------------------------------------------------------------------
void calcChangeSign(
//----------------------------------------------------------------------------
// Changes the sign of the number or exponent.
//----------------------------------------------------------------------------
void )
//----------------------------------------------------------------------------
{
int iPlace;
// Find the last sign in the number
for( iPlace = strlen( caNumber ) - 1; iPlace >= 0; iPlace-- )
{
// If it's a plus, change to minus
if( caNumber[iPlace] == '+' )
{
caNumber[iPlace] = '-';
break;
}
// If it's a minus, change to plus
if( caNumber[iPlace] == '-' )
{
caNumber[iPlace] = '+';
break;
}
}
// Display the new number
calcDisplay( caNumber );
return;
}
函数calcChangeSign()是另一个可以判定我们是在一般数字还是指数状态的函数。这里很简单,只需要将最近的数据符号返回就行了。这也是为什么要将符号保存的原因。
//----------------------------------------------------------------------------
void calcClear(
//----------------------------------------------------------------------------
// Clears/resets the calculator.
//----------------------------------------------------------------------------
void )
//----------------------------------------------------------------------------
{
// Set our local variables in a default state
strcpy( caNumber, "+0" );
iDigitCount = 0;
iOperator = OPERATION_NONE;
nOperand = 0.0;
oExponent = false;
oFraction = false;
// Display the new number
calcDisplay( caNumber );
return;
}
这个函数将程序恢复到最初状态,我们可以首先调试这个函数。
//----------------------------------------------------------------------------
void calcDivide(
//----------------------------------------------------------------------------
// Queues a divide operation.
//----------------------------------------------------------------------------
void )
//----------------------------------------------------------------------------
{
// Resolve any pending operations
calcEquals();
// Queue the operation
iOperator = OPERATION_DIVIDE;
// We're done
return;
}
这个函数和calcAdd()十分相似,实际上所有的计算操作符的函数都是相似的。
//----------------------------------------------------------------------------
void calcEquals(
//----------------------------------------------------------------------------
// Resolves a math operation.
//----------------------------------------------------------------------------
void )
//----------------------------------------------------------------------------
{
double nOperand2;
// If there is an entry
if( iDigitCount > 0 )
// Convert the entry to floating point
nOperand2 = ca2n( caNumber );
else
// If there is no entry
// The entry is the last operand
nOperand2 = nOperand;
// Perform the operation
switch( iOperator )
{
case OPERATION_ADD:
nOperand = nOperand + nOperand2;
break;
case OPERATION_DIVIDE:
nOperand = nOperand / nOperand2;
break;
case OPERATION_MULTIPLY:
nOperand = nOperand * nOperand2;
break;
case OPERATION_SUBTRACT:
nOperand = nOperand - nOperand2;
break;
default:
nOperand = nOperand2;
break;
}
// Clear the operator
iOperator = OPERATION_NONE;
// Convert the result from floating point for display
n2ca( nOperand, caNumber );
// Display the new number
calcDisplay( caNumber );
// Reset the entry
iDigitCount = 0;
strcpy( caNumber, "+0" );
oExponent = false;
oFraction = false;
// We're done
return;
}
这个函数实现了真正的运算操作。
//----------------------------------------------------------------------------
void calcExponent(
//----------------------------------------------------------------------------
// Starts gathering the exponent.
//----------------------------------------------------------------------------
void )
//----------------------------------------------------------------------------
{
// If we're not already doing the exponent
if( (oExponent == false) &&
// and if the number is nonzero
(ca2n( caNumber ) != 0.0) )
{
// Set up the exponent part
oExponent = true;
iDigitCount = 0;
strcat( caNumber, "e+0" );
// Display the new number
calcDisplay( caNumber );
}
else
// This was done in error
calcSignalError();
// We're done
return;
}
这个函数实现了“指数”键的操作,这里定义的变量将影响到calcAppend()函数的操作。
//----------------------------------------------------------------------------
void calcMultiply(
//----------------------------------------------------------------------------
// Queues a multiply operation.
//----------------------------------------------------------------------------
void )
//----------------------------------------------------------------------------
{
// Resolve any pending operations
calcEquals();
// Queue the operation
iOperator = OPERATION_MULTIPLY;
// We're done
return;
}
此函数“加”和“除”操作是类似的。
//----------------------------------------------------------------------------
void calcPoint(
//----------------------------------------------------------------------------
// Appends a decimal point to the entry.
//----------------------------------------------------------------------------
void )
//----------------------------------------------------------------------------
{
// If we are not collecting the fractional part already
if( (oFraction == false) &&
// If we are not doing the exponent
(oExponent == false) &&
// If we are not maxed out on digits
(iDigitCount != MAX_DIGITS) )
{
// If no digit has been entered, enter a zero
if( iDigitCount == 0 )
calcAppend( 0 );
// Now we will have a fractional part
oFraction = true;
// Append the decimal point
strcat( caNumber, "." );
}
else
// This was done in error
calcSignalError();
// Display the new number
calcDisplay( caNumber );
return;
}
这个函数和calcAppend()一样,比较复杂,因为它要解决各种不同情况下,输入数字的状态问题。
//----------------------------------------------------------------------------
void calcSubtract(
//----------------------------------------------------------------------------
// Queues a subtract operation.
//----------------------------------------------------------------------------
void )
//----------------------------------------------------------------------------
{
// Resolve any pending operations
calcEquals();
// Queue the operation
iOperator = OPERATION_SUBTRACT;
// We're done
return;
}
此函数和“加”、“除“、和“乘”函数类似。
/////////////////////
// Local Functions //
/////////////////////
//----------------------------------------------------------------------------
static double ca2n(
//----------------------------------------------------------------------------
// Converts a decimal ascii string to a double.
//----------------------------------------------------------------------------
char* cpNumber ) // The string to convert
//----------------------------------------------------------------------------
{
double nSign;
int oDecimal;
int iDivisor;
int iCount;
char caInt[MAX_DIGITS + 1];
int iNumber;
double nNumber;
// Get any leading sign
nSign = 1.0;
if( *cpNumber == '+' )
cpNumber++;
if( *cpNumber == '-' )
{
nSign = -1.0;
cpNumber++;
}
// Convert to an integer string
oDecimal = false;
iDivisor = 0;
for( iCount = 0; (iCount <= MAX_DIGITS) && *cpNumber &&
(*cpNumber != 'e'); iCount++ )
{
// Do the decimal point thing
if( *cpNumber == '.' )
{
oDecimal = true;
iCount--;
cpNumber++;
continue;
}
// If we are gathering the fraction
if( oDecimal )
iDivisor++;
// Otherwise, copy the digit
caInt[iCount] = *cpNumber++;
}
// Zero delimit the string
caInt[iCount] = '\0';
// Use atoi
iNumber = atoi( caInt );
// Convert to a double
nNumber = nSign * (double)iNumber * pow( 10.0, -(double)iDivisor );
// If there is an exponent
if( *cpNumber == 'e' )
{
cpNumber++;
// Get any leading sign
nSign = 1.0;
if( *cpNumber == '+' )
cpNumber++;
if( *cpNumber == '-' )
{
nSign = -1.0;
cpNumber++;
}
// Convert to an integer string
for( iCount = 0; (iCount <= MAX_EXP_DIGITS) && *cpNumber; iCount++ )
caInt[iCount] = *cpNumber++;
// Zero delimit the string
caInt[iCount] = '\0';
// Use atoi
iNumber = atoi( caInt );
// Multiply the number
nNumber *= pow( 10.0, (double)iNumber );
}
// Return the number
return( nNumber );
}
上面的函数将ASCII字符串转换为双精度浮点数。这绝不是要纯数学性的方法才能完成,相对来说还是比较容易弄懂的。不过,完全搞清这些函数的操作也是不必要的,因为这些对我们以后的课程并不重要。
//----------------------------------------------------------------------------
static void n2ca(
//----------------------------------------------------------------------------
// Converts a double to an ascii string.
//----------------------------------------------------------------------------
double nNumber, // The number to convert
char* cpNumber ) // Storage for the converted number
//----------------------------------------------------------------------------
{
double nExp;
int iExp;
int iNumber;
char caInt[9];
int iZeroes;
// Handle zero
if( nNumber == 0.0 )
{
strcpy( cpNumber, "+0" );
return;
}
// Grab the sign
*cpNumber = '+';
if( nNumber < 0.0 )
{
nNumber = -nNumber;
*cpNumber = '-';
}
cpNumber++;
// Normalize
nExp = log10( nNumber );
iExp = (int)nExp;
if( nExp < 0 )
iExp--;
iExp -= MAX_DIGITS - 1;
nNumber /= pow( 10.0, (double)iExp );
// Convert to an integer
iNumber = (int)(nNumber + 0.5);
// Convert to an integer string
itoa( caInt, iNumber );
// Count trailing zeroes
for( iZeroes = 0; caInt[strlen( caInt ) - 1 - iZeroes] == '0'; iZeroes++ )
;
// Handle decimal notation
if( (iExp <= 0) && (iExp > -MAX_DIGITS) )
{
// Integer part
strncpy( cpNumber, caInt, MAX_DIGITS + iExp );
cpNumber[MAX_DIGITS + iExp] = '\0';
// Decimal point
strcat( cpNumber, "." );
// Mantissa part
strcat( cpNumber, caInt + MAX_DIGITS + iExp );
// Eliminate trailing zeroes
while( cpNumber[strlen( cpNumber ) - 1] == '0' )
cpNumber[strlen( cpNumber ) - 1] = '\0';
}
// Handle decimal notation with leading zeroes
else if( (iExp <= -MAX_DIGITS) && (iExp > -(MAX_DIGITS + iZeroes)) )
{
// Integer part and decimal point
strcpy( cpNumber, "0." );
iExp += MAX_DIGITS;
// Other zeroes
for( ; iExp; iExp++ )
strcat( cpNumber, "0" );
// Eliminate trailing zeroes
while( caInt[strlen( caInt ) - 1] == '0' )
caInt[strlen( caInt ) - 1] = '\0';
// The rest of the number
strcat( cpNumber, caInt );
}
else
// Handle exponential notation
{
// Build the number part
*cpNumber++ = *caInt;
*cpNumber++ = '.';
strcpy( cpNumber, caInt + 1 );
// Convert the exponent to ascii
iExp += MAX_DIGITS - 1;
itoa( caInt, iExp );
// Build the exponent part
strcat( cpNumber, "e" );
if( iExp > 0 )
strcat( cpNumber, "+" );
strcat( cpNumber, caInt );
// Eliminate trailing zeroes
while( cpNumber[strlen( cpNumber ) - 1] == '0' )
cpNumber[strlen( cpNumber ) - 1] = '\0';
}
// We're done
return;
}
上面的函数将一个浮点数转换为ASCII字符串形式。另外说一句,我所用的运算法则要求清晰甚于效率。
可移植性问题
站在可移植性的角度上,上面的代码有两个问题。第一个,数据类型int在代码中的使用,这我已经在前边提到过不要随便使用,因为代码将工作在两个不同的平台上。那么我们就不得不深入函数内部修改数据类型。然而我们不希望修改一些传统的函数。即使我们的确那样做了,但在调用ANSI标准函数如atoi()时,也会用到int。
那数据类型int在代码中真是一个问题吗?如果你观察仔细或单步运行代码的话,就会发现这的确是个问题。是的,一些ANSI函数现在还没有定义。但是如果按我的思路,或者学完本章后再回过头来看一下,你会发现有很多地方,特别是在一些传统函数中,int明显的为32位。问题就出在CodeWarrior编译器是16位的。
工程的修正
幸运的是,我们可以通过改变工程的设置将类型int改为32位。知道这一点对我们很有好处。现在,你可能不敢在程序中随便使用int了吧,但是如果你的确使用了,如16位的int,你可以通过修改设置来修正bug(另一个方法是使用类型保护(type-safe)变量)
运行CodeWarrior IDE并打开“计算器”工程,我假定你已保存了一个比较完整的工程副本,否则你会为调试所有的改变而感到痛苦不堪。下面,选择Edit | Caculator Setting进入Code Generation,68K Processor。选中标有“4 byte ints.”的复选框。这样就在编译器中产生了32位的int类型。
App.h内容的添加
当写完这些代码,第二个可移植性问题出现了,就是Palm OS不支持所有的ANSI 标准C的函数调用!所幸的是,我们已有很多办法来解决这个问题。我们通过引入MathLib取代了那些“坏”函数。我们自己还写了一系列的函数使双精度数和ASCII码可相互转换。下面就是我们将要添加的内容:
////////////////////////////
// Definitions for calc.c //
////////////////////////////
#define atoi StrAToI
#define itoa StrIToA
#define strcat StrCat
#define strcpy StrCopy
#define strlen StrLen
#define strncpy StrNCopy
许多ANSI的函数字符串在Palm OS的字符串处理库中被重新表示。为将它们应用到ANSI标准C中,我们只需像上面那样在开头部分定义就行了。
调试
我建议你将calc.c从CD上拷下来就行了,因为输入这些一般的C代码对学习并没有什么好处。然而,在这里,你将有机会学到如浮点数转换的令人激动的代码。
调试程序,我建议先单步运行calcAppend()和calcExponent()使程序入口能顺利工作。然后调试calEquals(),接着再单步调试传统例程n2ca()和ca2n()。
本文地址:http://com.8s8s.com/it/it32721.htm