第4单元 表达式本单元教学目标介绍C++的表达式和表达式语句。
学习要求熟练掌握C++的各种表达式,特别是赋值表达式及其他有副作用的表达式。
授课内容在任何高级程序设计语言中,表达式都是最基本的的组成部分。形式化的表达式定义比较复杂,需要用到递归定义的概念。简单说来,表达式是由运算符将运算对象 (如常数,变量和函数等) 连接起来的具有合法语义的式子。在C++中,由于运算符比较丰富(达数十种之多),加之引入了赋值等有副作用的运算符,因而可以构成灵活多样的表达式。这些表达式的应用一方面可以使程序编写得短小简洁,另一方面还可以完成某些在其他高级程序设计语言中较难实现的运算功能。
学习C++的表达式时应注意以下几个方面:
(1) 运算符的正确书写方法。C++的许多运算符与通常在数学公式中所见到的符号有很大差别,例如,整除求余(%),相等(= =),逻辑运算与(&&)等等。
(2) 运算符的确切含义和功能。C++语言中有一些比较特殊的运算符,有些运算符还有所谓“副作用”,这些都给我们的学习带来了一定的困难。
(3) 运算符与运算对象的关系。C++的运算符可以分为单目运算符 (仅对一个运算对象进行操作)、双目运算符(需要2个运算对象),甚至还有复合表达式,其中的两个运算符对三个或者更多个运算对象进行操作。
(4)运算符具有优先级和结合方向。如果一个运算对象的两边有不同的运算符,首先执行优先级别较高的运算。如果一个运算对象两边的运算符级别相同,则应按由左向右的方向顺序处理。各运算符的优先顺序可以参看表4-3:“运算符的优先级别和结合方向”。如果编程序时对运算符的优先顺序没有把握,可以通过使用括号来明确其运算顺序。
4.1 算术运算符和算术表达式
C++的算术运算符有:
+ (加),( (减),* (乘),/ (除),% (整除求余)
其中“/”为除法运算符。如果除数和被除数均为整型数据,则结果也是整数。例如,5/3的结果为1。“%”为整除求余运算符。“%”运算符两侧均应为整型数据,其运算结果为两个运算对象作除法运算的余数。例如5%3的结果为2。
在C++中,不允许两个算术运算符紧挨在一起,也不能象在数学运算式中那样,任意省略乘号,以及用中圆点“·,代替乘号等。如果遇到这些情况,应该使用括号将连续的算术运算符隔开,或者在适当的位置上加上乘法运算符。例如
x*(y 应写成 x*((y)
(x+y)(x(y) 应写成 (x+y)*(x(y)
4.2 逻辑运算符和逻辑表达式
C++中有6种比较运算符:
> (大于), < (小于), == (等于),
>= (大于等于), <= (小于等于), != (不等于)
如果比较运算的结果成立,比较表达式取值true(真),否则比较表达式的值为false(假)。在C++中,使用逻辑数据类型(bool)表示逻辑运算的结果。
注意,算术运算符的优先级高于比较运算符。
C++中有3种逻辑运算符:
! (逻辑非) && (逻辑与) || (逻辑或)
在逻辑运算符中,逻辑与“&&”的优先级高于逻辑或“||”的优先级,而所有的比较运算符的优先级均高于以上两个逻辑运算符。至于逻辑非运算符“!”,由于这是一个单目运算符,所以和其他单目运算符 (例如用于作正、负号的“+”和“-”)一样,优先级高于包括算术运算符在内的所有双目运算符。例如表达式:
x*y>z && x*y<100 || -x*y>0 && !isgreat(z)
的运算顺序为:
计算 x*y // 算术运算优先于比较运算
计算 x*y>z // 比较运算优先于逻辑运算
计算 x*y<100 // 比较运算优先于逻辑与运算
计算 x*y>z && x*y<100 // 逻辑与运算优先于逻辑或运算
计算 (x // 单目运算优先于双目运算
计算 (x*y // 算术运算优先于比较运算
计算 (x*y>0 // 比较运算优先于逻辑运算
计算 isgreat(z) // 计算函数值优先于任何运算符
计算 !isgreat(z) // 单目运算优先于双目运算
计算 (x*y>0 && !isgreat(z) // 逻辑与运算优先于逻辑或运算
计算 x*y>z && x*y<100 || (x*y>0 && !isgreat(z) 
4.3 赋值运算符和赋值表达式
C++将赋值作为一个运算符处理。赋值运算符为“=”,用于构造赋值表达式。赋值表达式的格式为:
V = e
其中V表示变量,e表示一个表达式。赋值表达式的值等于赋值运算符右边的表达式的值。其实,赋值表达式的价值主要体现在其副作用上,即赋值运算符可以改变作为运算对象的变量V的值。赋值表达式的副作用就是将计算出来的表达式e的值存入变量V。
和其他表达式一样,赋值表达式也可以作为更复杂的表达式的组成部分。例如
i = j = m*n;
由于赋值运算符的优先级较低(仅比逗号运算符高,见自学部分)。并列的赋值运算符之间的结合方向为从右向左,所以上述语句的执行顺序是,首先计算出表达式m*n的值,然后再处理表达式j = m*n,该表达式的值就是m*n的值,其副作用为将该值存入变量j。最后,处理表达式i = j = m*n,其值即第一个赋值运算符右面的整个表达式的值,因此也就是m*n的值 (最后计算出的这一赋值表达式的值并没有使用),其副作用为将第一个赋值运算符右面整个表达式的值存入变量i。因此,上述表达式语句的作用是将m*n的值赋给变量i和j。整个运算过程如下 (设m的值为2,n的值为3):
计算 m*n 的值: 2*3 等于 6;
计算 j = m*n 的值, j = 6 的值等于6,其副作用为将6存入变量j;
计算 i = j = m*n 的值: i = 6 的值等于6,其副作用为将6存入变量i。
4.4 自增运算符和自减运算符
C++中有两个很有特色的运算符,自增运算符“++”和自减运算符“((”。这两个运算符也是C++程序中最常用的运算符,以致于它们几乎成为C++程序的象征。
“++”和“((”运算符都是单目运算符,且其运算对象只能是整型变量或指针变量。这两个运算符既可以放在作为运算对象的变量之前,也可以放在变量之后。这四种表达式的值分别为:
i++ 的值和i的值相同;
i(( 的值和i的值相同;
++i 的值为i+1;
((i 的值为i-1。
然而,“++”和“((”这两个运算符真正的价值在于它们和赋值运算符类似,在参加运算的同时还改变了作为运算对象的变量的值。++i和i++会使变量i 的值增大1; 类似地,((i和i((会使变量i的值减1。因此,考虑到副作用以后,“++”和“((”构成的4种表达式的含义见表4-1(设i为一整型变量)。
表4-1 自增运算符和自减运算符的用法表达式
表达式的值
副作用
i++
++i
i--
--i
i
i+1
i
i-1
i的值增大1
i的值增大1
i的值减少1
i的值减少1
++”表达式和“((”表达式既可以单独使用,也可以出现于更复杂的表达式中。例如
i++; // i增加1
((i; // i减少1
x = array[++i]; // 将array[i+1]的值赋给x,并使i增加1
s1[i++] = s2[j++]; // 将s2[j]赋给s1[i],然后分别使i和j增加1
作为运算符来说,“++”和“((”的优先级较高,高于所有算术运算符和逻辑运算符。但在使用这两个运算符时要注意它们的运算对象只能是变量,不能是其他表达式。例如,(i+j)++就是一个错误的表达式。
引入含有“++”、“((”以及赋值运算符这类有副作用的表达式的目的在于简化程序的编写。例如,表达式语句 i = j = m*n; 的作用和
j = m*n;
i = j;
完全一样; 而表达式语句 s1[i++] = s2[j++]; 其实正是下列语句的简化表达方式:
s1[i] = s2[j];
i = i+1;
j = j+1;
[例4-1] 字符串连接。
算 法,所谓字符串连接,就是将两个字符串合并成一个新的字符串。函数mstrcat()可以实现字符串连接的功能,其具体做法是将第二个字符串的内容复制到第一个字符串的尾部去。
程 序
// Example 4-1:连接两个字符串
void mstrcat(char destin[],char source[])
{
int i = strlen(destin),j = 0;
while(source[j]!=0)
destin[i++] = source[j++];
destin[i] = 0;
}
分 析,函数mstrcat()将字符串source的内容复制在字符串destin的后面。显然,字符型数组destin的大小一定要能够容纳得下连接后的新字符串才行。在复制工作开始之前,首先要确定在destin中的复制位置,这是通过库函数strlen()完成的。
自学内容
4.5 其他具有副作用的运算符除了“++”和“((”以外,在C++中还有其他一些有副作用的复合运算符,它们都是以称为赋值运算符“=”为基础构成的。例如算术复合赋值运算符“+=”,“(=”,“*=”,“/=”以及“%=”; 另外还有用位运算符和赋值运算符复合而成的复合赋值运算符(见4.8:“位运算表达式”)。算术复合运算符的形式为:
V += e 的含义为将表达式e的值加在变量V上;
V (= e 的含义为将表达式e的值从变量V中减去;
V *= e 的含义为将变量V与表达式e的乘积存入变量V中;
V /= e 的含义为将变量V和表达式e的商存入变量V中;
V %= e 的含义为将变量V和表达式e的余数存入变量V中。
C++引入了一些有副作用的表达式,一方面丰富了程序的表达方式,使得C++程序的形式简洁、干练; 生成的目标代码的效率也比较高。但另一方面这些表达式比较复杂,难于理解和调试,有时还会因为不同的C++编译程序对计算顺序的规定不同而产生二义性的解释。因此在编程时要慎重使用。为了确保实现自己所要求的计算顺序,可以通过加括号的方法加以明确; 甚至可以将由多个有副作用的表达式组成的复杂表达式语句分解成几个比较简单的表达式语句处理。
4.6 问号表达式和逗号表达式
C++中还提供了一种比较复杂的表达式,即问号表达式,又称条件表达式。问号表达式使用两个运算符 (? 和,) 对三个运算对象进行操作,格式为:
<表达式1>?<表达式2>:<表达式3>
问号表达式的值是这样确定的,如果<表达式1>的值为非零值,则问号表达式的值就是<表达式2>的值; 如果<表达式1>的值等于0,则问号表达式的值为<表达式3>的值。利用问号表达式可以简化某些选择结构的编程。例如,分支语句
if(x>y)
z = x;
else
z = y;
等价于语句
z = x>y? x,y;
[例4-2] 编写一个求绝对值的函数。
程 序,
// Example 4-2:求双精度类型量的绝对值
double dabs(double x)
{
return x>0?x:-x;
}
在C++中可以使用逗号 (,) 将几个表达式连接起来,构成逗号表达式。逗号表达式的格式为:
<表达式1>,<表达式2>,...,<表达式n>
在程序执行时,按从左到右的顺序执行组成逗号表达式的各表达式,而将最后一个表达式 (即表达式n) 的值作为逗号表达式的值。
逗号表达式常用于简化程序的编写。例如,如下程序结构
if(x>y)
{
t = x;
x = y;
y = t;
}
可以利用逗号表达式简化为:
if(x>y)
t = x,x = y,y = t;
4.7 表达式中各运算符的运算顺序大家知道,四则运算的运算顺序可以归纳为“先乘、除,后加、减”,也就是说乘、除运算的优先级别比加减运算的优先级别要高。C语言中有几十种运算符,仅用一句“先乘、除,后加、减”是无法表示各种运算符之间的优先关系的,因此必须有更严格的确定各运算符优先关系的规则。 表4-3列出了各种运算符的优先级别和同级别运算符的运算顺序(结合方向)。
表4-3 运算符的优先级别和结合方向优先级别
运算符
运算形式
结合方向
名称或含义
1
()
[]
.
->
(e)
a[e]
x.y
p->x
自左至右
圆括号数组下标结构体成员用指针访问结构体成员
2
+
++ --
!
~
(t)
*
&
sizeof
-e
++x或x++
!e
~e
(t)e
*p
&x
sizeof(t)
自右至左
负号和正号自增运算和自减运算逻辑非按位取反类型转换由地址求内容求变量的地址求某类型变量的长度
3
* / %
e1*e2
自左至右
乘、除和求余
4
+ -
e1+e2
自左至右
加和减
5
<< >>
e1<<d2
自左至右
左移和右移
6
< <= > >=
e1<e2
自左至右
关系运算(比较)
7
== !=
e1==e2
自左至右
等于和不等于比较
8
&
e1&e2
自左至右
按位与
9
^
e1^e2
自左至右
按位异或
10
|
e1|e2
自左至右
按位或
11
&&
e1&&e2
自左至右
逻辑与(并且)
续表4-3
优先级别
运算符
运算形式
结合方向
名称或含义
12
||
e1||e2
自左至右
逻辑(或者)
13
,
e1?e2:e3
自右至左
条件运算
14
=
+= -= *=
/= %= >>=
<<= &= ^=
|=
自右至左
赋值运算
复合赋值运算
15
,
e1,e2
自左至右
顺序求值运算
说明:运算形式一栏中各字母的含义如下:a ( 数组,e ( 表达式,p ( 指针,t ( 类型,x,y ( 变量由表4-3可以看出,运算优先级的数字越大,优先级别越低。优先级别最高的是括号,所以如果我们要改变混合运算中的运算次序,或者对运算次序把握不准时,可以使用括号来明确规定运算的顺序。
运算符的结合方向是对级别相同的运算符而言的,说明了在几个并列的级别相同的运算符中运算的次序。大部分运算符的结合方向都是“自左至右”,例如表达式x*y/3,运算次序就是先计算x*y,然后将其结果除以3。也有些运算符的结合顺序与此相反,是“自右至左”,例如赋值运算符。表达式i = j = 0的计算顺序是首先将0赋给变量j,然后再将表达式j = 0的值(仍为0)赋给变量i。
4.8 类型不同的数据之间的混合算术运算大多数运算符对运算对象的类型有严格的要求。例如,%运算符只能用于两个整型数据的运算,所有的位运算符也只适用于整型数据。但是算术四则运算符适用于所有的整型 (包括char、int和long)、浮点型 (float) 和双精度型 (double) 数据,因此存在一个问题,不同类型的数据的运算结果的类型怎样确定?
C++规定,不同类型的数据在参加运算之前会自动转换成相同的类型,然后再进行运算。运算结果的类型也就是转换的类型。转换的规则为:
(1) 转换的原则是级别低的类型转换为级别高的类型。各类型按级别由低到高的顺序为char,int,unsigned,long,unsigned long,float,double。
因此,即使是两个char型的数据运算,也要先转换为int型,运算的结果也是int型; 两个float型数据要先转换为double型,然后参加运算,其结果为double型。不同类型的数据之间也是这样,如一个char类型的数据和一个int类型的数据运算,结果为int型; 一个int型的数据和一个float型数据的运算结果的类型为double型。
另外,C++规定,有符号类型数据和无符号类型的数据进行混合运算,结果为无符号类型。例如,int型数据和unsigned类型数据的运算结果为unsigned型。
对于赋值运算来说,如果赋值运算符右边的表达式的类型与赋值运算符左边的变量的类型不一致,则赋值时会首先将赋值运算符右边的表达式按赋值运算符左边的变量的类型进行转换,然后将转换后的表达式的值赋给赋值运算左边的变量。整个赋值表达式的值及其类型也是这个经过转换后的值及其类型。例如:
float x;
int i;
x = i = 3.1416;
则变量i的值为3,并且赋值表达式i = 3.1416的类型为int,值也是3。因此尽管变量x的类型为float,但对其赋值的结果是x的值为3.0而不是3.1416。上述赋值表达式语句实际上完全相当于
i = 3.1416;
x = i;
这两个赋值表达式语句的效果。
(3) 可以使用强制类型转换。在程序中使用强制类型转换操作符可以明确地控制类型转换。强制类型转换操作符由一个放在括号中的类型名组成,置于表达式之前,其结果是表达式的类型被转换为由强制类型转换操作符所标明的类型。例如,如果i的类型为int,表达式 (float)i将i强制转换为float类型。
算术表达式的强制类型转换的最主要的用途是防止丢失整数除法结果中的小数部分。例如
int i1 = 100,i2 = 40;
float f1;
f1 = i1/i2;
这段程序的结果是float类型的变量f的内容被赋值为2.00,虽然100/40求值应为2.5。为什么? 原因是表达式i1/i2 包含了两个int类型的变量,该表达式的类型当然也应该是int类型。因此,它只能表示整数部分,结果中的小数部分就丢失了。虽然将i1/i2 的结果赋值给了一个浮点类型的变量,但这已经太晚了:结果中的小数部分已经被丢掉了。
为了防止这种误差,其中一个int 类型的变量必须强制转换为float 类型:
f1 = (float)i1/i2;
在这种情况下,另一个变量被自动地被转换为float 类型,并且整个表达式的类型也是float。结果的小数部分就会被保留。
调试技术
4.9 运行错误的判断与调试通常所说的运行错误有两种,一种是逻辑错误,即程序的实际运行结果和我们对程序结果的期望不符; 另一种仍是程序设计上的错误,但是躲过了编译程序和连接程序的检查,通常表现为突然死机、自行热启动或者输出信息混乱。
相对于编译和连接错误来说,运行错误的查找和判断更为困难。编译和连接错误分别由编译程序和连接程序检查,尽管有时它们报告的出错信息和错误的实际原因之间有一些差距,但总还可以作为查错时的一种参考。而运行错误就不同了,很少或根本没有提示信息,只能靠程序员的经验来判断错误的性质和位置。下面我们简单地介绍一些常见运行错误的调试方法。
逻辑错误,一种逻辑错误是由于在设计程序的算法时考虑欠周引起的,例如对边界和极端条件未作处理等。例如以下循环:
while(count)
{
… …
count = count(1;
}
程序员的构思是进行count次循环。但是,如果count中原来的值为负数时,此循环就成了一个“死循环”而导致无法停机,显然是错误的。但是编译程序无法查出这类错误,只有到了程序运行之后才有可能发现。再如,在利用海伦公式计算三角型面积时,首先应该确认给出的三条边长确实可以构成一个三角形,否则计算结果是没有意义的; 而在编写求解一般实系数一元二次方程的程序时,必须在程序中设计处理复根情况的程序段,以免对负数求平方根。通常在手算时不用事先考虑这些问题,可以在确实发生了问题以后再提出解决的办法。但是程序是为计算机设计的,而计算机并没有自行应变的能力,程序员必须事先将一切可能遇到的情况统统考虑周全,尤其是对于那些受用户委托设计或者作为商品出售的软件更是如此。
另一种常见的逻辑错误是由于程序输入时的打字错误造成的,例如将判断条件中的“>=”误输入为“>”,将相等判断“==”误输入为赋值号“=”等。含有这类错误的程序在运行时出现的现象多种多样,而且通常很难与错误的原因联系起来。
4.10 基本调试手段程序的基本调试手段有以下几种,标准数据检验、程序跟踪、边界检查和简化循环次数等。下面我们分别介绍之。
标准数据检验,在程序编译、连接通过以后,就进入了运行调试阶段。运行调试的第一步就是用若干组已知结果的标准数据对程序进行检验。标准数据的选择非常重要,一是要有代表性,接近实际数据; 二是要比较简洁,容易对其结果的正确性进行分析。另外,对重要的临界数据也必须进行检验。
程序跟踪,对于比较复杂的大型程序来说,上述标准数据检验一次就完全通过的可能性很小。通常程序中总是存在许多各种各样的错误 (就好象出错是程序的基本特性,一个错误也没有的程序反倒是罕见的意外),还需要对程序进行细致的调试工作。
程序跟踪则是最重要的调试手段。程序跟踪的基本原理是让程序一句一句地执行,通过观察和分析程序执行的过程中数据和程序执行流程的变化来查找错误。就Visual C++而言,程序跟踪可以采用两种方法,一种是直接利用Developer Studio中的分步执行、断点设置、变量内容显示等功能对程序进行跟踪,这种方法我们留在第6单元的编程与调试部分介绍; 另一种是传统的方法,通过在程序中直接设置断点、打印重要变量内容等来掌握程序的运行情况。例如,我们可以在程序中的关键部位插入这样的代码段:
// 调试代码段
cout << "Break Point 10,line 120 --- count > 100" << endl;
cout << "Variable count =,<< count <<,,x =,<< x <<,,sum =,<< sum << endl;
exit(0);
// 调试代码段结束其中的变量可以根据程序的实际情况进行设计。使用exit ( ) 函数的目的是要程序在执行到这一行时暂时停下来,从而可以看清楚调试代码段所显示的信息。然后选择是否让程序继续执行。如果到这一断点时尚未发现错误,则可以按下任何一个键让程序继续运行到下一个断点; 否则可以使用组合键Ctrl+Break键来中断程序,再使用编辑器对程序进行修改。在程序中的所有的问题都解决了之后,再将程序中所有的调试代码段统统删去 (如果利用第1单元中介绍的条件编译来处理就更好)。
边界检查,在设计检查用的数据时,要重点检查边界和特殊情况。例如,对于循环:
while(count<1000)
{
…...
}
就应该设计数据检验count等于999、等于1000、等于0 或者负数等情况。如果程序中有由if-else语句、switch语句等组成的分支结构,也应该设计相应的数据,使得分支中的每一条路径都要通过检验。
简化,在调试时,有时可以通过对程序进行某种简化来加快调试速度。例如减少循环次数、缩小数组规模、屏蔽某些次要程序段 (如一些用于显示提示信息的子程序) 等。但在进行简化工作时,一定要注意这种简化不能太过分,以致于无法代表原来程序的真实情况。例如,对于一个求解N元一次方程组的程序来说,仅将N等于2的情况调通是不够的,还不能保证该程序对于较大的方程组也能给出正确的结果。如果对于N=3或4的情况该程序也能正常工作,则在该程序中因为矩阵规模而出错的可能性就大大减少了(但这并不说明该程序就一定没有错误了,例如如果该程序使用某种消元法,则还要考虑主对角线元素的绝对值过小或者为0的情况)。
4.11 条件编译条件编译是编译预处理命令的一种,用于对源程序的内容进行选择性编译。例如,在调试程序期间,常常希望记录输出一些调试用的信息,而在调试完成后,就不再需要这些输出信息了。要解决这个问题,一种办法是逐一从源程序中删去这些输出这些调试信息的程序段落,或者将这些程序段落用注释标记括起来。显然这样做要花费相当大的工作量,而且今后再要进行调试时,恢复这些调试程序段也是一件挺麻烦的事情。另一种方法就是使用编译预处理中的条件编译命令。条件编译命令的格式为:
#if <条件1>
<程序段落1>
#elif <条件2>
<程序段落2>
...,..
#elif <条件n>
<程序段落n>
#else
<省缺程序段落>
#endif
条件编译中所使用的条件只能是由常数构成的表达式。如果该常数表达式的值不为0,就表示条件成立,否则表示条件不成立。一般情况下,在#if中都是使用由#define指令产生的符号常数进行测试。
编译预处理命令的一个常见的用途是将调试代码插入应用程序中。程序员可以定义一个叫做DEBUG的符号常数,值可以定义为1或者0。在程序中的任何地方,都可用以下方式插入调试信息:
#if DEBUG == 1
<此处为调试代码>
#endif
在开发程序时,将DEBUG定义为1,则插入的调试代码被编译进目标程序,可以输出一些帮助跟踪错误的信息。一旦程序工作正常,就可以将DEBUG重新定义为0后再次编译程序,即可从目标程序中去掉调试代码。
初看起来,条件编译的用法和C++的if语句的用法非常相似。那么能否用下面的结构实现调试代码的选择输出呢?
int debug = 1;
...,..
if(debug)
<调试代码>
endif
...,..
在调试时,全局变量debug被赋值1,这时<调试代码>起作用,输出调试信息。待调试结束后,将赋给debug的值改为0,则不在输出调试信息。初看起来,这种用法与使用条件编译差不多,但实际上这两种结构有着本质上的区别,if语句控制着某些语句是否执行,而#if指令控制着某个程序段落是否被编译。因此,上述使用C++条件语句的结构在调试成功后仍然被编译成目标代码,只是不再执行,成了一段废码;而使用条件编译的调试结构在正式编译时根本不会被编译,目标程序中没有废码,所以目标码占用的存储较少。
C++中还有两个和#if类似的条件编译命令,#ifdef和#ifndef。例如
#ifdef DEBUG
则只要定义了DEBUG(无论将DEBUG定义为什么值,包括0),甚至什么具体值也不是:
#define DEBUG
则上述条件成立。而在前面没有定义DEBUG时
#ifndef DEBUG
成立。
程序设计举例
[例4-3] 计算1!+2!+3!+4!+......+20!,即。
算 法:该级数的通项ti =i! = i((i-1)! = ti-1(i。
程 序:
// Example 4-4:求级数和
#include <iostream.h>
void main()
{
long sum = 0,t = 1;
for(int i=1; i<=20; i++)
{
t = t*i;
sum = sum+t;
}
cout<<"sum = "<<sum<<endl;
}
输 出:sum = 268040729
分 析:阶乘的增长速度很快,由于int类型的表示范围很小(-32768~2767),所以应选用long型变量存放计算结果。
[例4-4] 编写程序制作九九乘法表。
程 序:
// Example 4-5:制作乘法表
#include <iostream.h>
void main()
{
int i,j;
for(i=1; i<10; i++)
{
for(j=1; j<=i; j++)
cout << i*j << "\t";
cout << endl;
}
}
输 出:
1
2 4
3 6 9
4 8 12 16
...,..
9 18 27 36 45 54 63 72 81
说 明:"\t"是C++中的转义字符,输出语句中的“\t”表示后面的输出位置将在下一个制表位。
[例4-5] 根据三边长求三角形面积。
算 法:利用海伦公式:,其中。
程 序:
// Example 4-6:求三角形面积
#include "math.h"
#include "iostream.h"
void main()
{
double a,b,c,s,area;
cout << "please input a,b,c =?";
cin >> a >> b >> c;
s = (a+b+c)/2;
area = sqrt(s*(s(a)*(s(b)*(s(c));
cout << "area = " << area;
}
输 入:3 4 5
输 出:area = 6
说 明:为简单起见,程序未考虑对数据的检验,即未检查输入的三边长是否能构成一个三角形。实际上,数据检验是程序的重要组成部分,应预以足够的重视。我们将为本程序添加数据检验的任务留作上机作业(第1题)。
[例4-6] 求一元二次方程ax2+bx+c=0的根,其中系数a,b,c为实数,由键盘输入。
算 法:称 为一元二次方程的判别式,若,则方程有两个实根,计算公式为:

如果则方程有重根:

如果则方程有两个共轭复根:

据此,可设计如下计算步骤:首先计算判别式的值以及,然后根据判别式生成根的值。
程 序:
// Example 4-7:解一元二次方程
#include "math.h"
#include "iostream.h"
void main()
{
double a,b,c,delta,p,q;
cout << "please intput a,b,c =?";
cin >> a >> b >> c;
delta = b*b(4*a*c;
p = (b/(2*a);
q = sqrt(fabs(delta))/(2*a);
if(delta >= 0)
cout << endl << "x1 = " << p+q << endl << "x2 = " << p(q << endl;
else
{
cout << endl << "x1 = " << p << " + j" << q;
cout<< endl << "x2 = " << p << "( j" << q <<endl;
}
}
输 入:1 3 2
输 出:x1 = (1
x2 = (2
[例4-7] 求 ( 的近似值。
算 法:利用公式:

计算,直到最后一项的绝对值小于10(4为止。
程 序:
// Example 4-8:求π的近似值
#include <math.h>
#include <iostream.h>
void main()
{
int s = 1;
double n = 1.0,t = 1.0,pi = 0.0;
while(fabs(t)>=1e(4)
{
pi = pi+t;
n = n+2;
s = (s;
t = s/n;
}
cout << "pi = " << 4*pi << endl;
}
输 出:pi = 3.14139
说 明:注意程序中符号位的生成方法。
单元上机练习题目
1.为例4-5添加数据检验部分。给出三边长,检验其是否能构成一个三角形的方法是检查是否任意两边和均大于第三边。如果检验不合格,输出信息“Error Data!”。
2.求a+aa+aaa+aaaa+...,..+aa...a(n个),其中a为1~9之间的整数。
例如:当a = 1,n = 3时,求1+11+111之和;
当a = 5,n = 7时,求5+55+555+5555+55555+5555555之和。
3.找出2~10000之内的所有完全数。所谓完全数,即其各因子之和正好等于本身的数。如6=1+2+3,28=1+2+4+7+14,所以6,28都是完全数。
4.有一分数序列

(即后一项的分母为前一项的分子,后项的分子为前一项分子与分母之和),求其前n项之和。