分享
 
 
 

PALM开发教程-第十四章 程序的可移植性

王朝other·作者佚名  2006-01-09
窄屏简体版  字體: |||超大  

作者: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()。

 
 
 
免责声明:本文为网络用户发布,其观点仅代表作者个人观点,与本站无关,本站仅提供信息存储服务。文中陈述内容未经本站证实,其真实性、完整性、及时性本站不作任何保证或承诺,请读者仅作参考,并请自行核实相关内容。
2023年上半年GDP全球前十五强
 百态   2023-10-24
美众议院议长启动对拜登的弹劾调查
 百态   2023-09-13
上海、济南、武汉等多地出现不明坠落物
 探索   2023-09-06
印度或要将国名改为“巴拉特”
 百态   2023-09-06
男子为女友送行,买票不登机被捕
 百态   2023-08-20
手机地震预警功能怎么开?
 干货   2023-08-06
女子4年卖2套房花700多万做美容:不但没变美脸,面部还出现变形
 百态   2023-08-04
住户一楼被水淹 还冲来8头猪
 百态   2023-07-31
女子体内爬出大量瓜子状活虫
 百态   2023-07-25
地球连续35年收到神秘规律性信号,网友:不要回答!
 探索   2023-07-21
全球镓价格本周大涨27%
 探索   2023-07-09
钱都流向了那些不缺钱的人,苦都留给了能吃苦的人
 探索   2023-07-02
倩女手游刀客魅者强控制(强混乱强眩晕强睡眠)和对应控制抗性的关系
 百态   2020-08-20
美国5月9日最新疫情:美国确诊人数突破131万
 百态   2020-05-09
荷兰政府宣布将集体辞职
 干货   2020-04-30
倩女幽魂手游师徒任务情义春秋猜成语答案逍遥观:鹏程万里
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案神机营:射石饮羽
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案昆仑山:拔刀相助
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案天工阁:鬼斧神工
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案丝路古道:单枪匹马
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:与虎谋皮
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:李代桃僵
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:指鹿为马
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案金陵:小鸟依人
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案金陵:千金买邻
 干货   2019-11-12
 
推荐阅读
 
 
 
>>返回首頁<<
 
靜靜地坐在廢墟上,四周的荒凉一望無際,忽然覺得,淒涼也很美
© 2005- 王朝網路 版權所有