(转载)你好,C++(16)用表达式表达我们的设计意图——4.1 用操作符对数据进行运算

你好,C++(16)用表达式表达我们的设计意图——4.1 用操作符对数据进行运算

第4章    将语句编织成程序

学过C++中的各种数据类型, 就知道如何使用各种数据类型定义变量来描述现实世界中的各种事物了。现在,我们可以将一个工资统计程序大致写成下面这个样子:

// 工资统计程序
int main()
{
    // 表示员工个数的常量NUM
    const int NUM = 100000;
    // 保存所有工资的数组
    int arrSalary[NUM];

    // 保存平均工资的变量
    float fSalaryAver = 0.0;   

    // 对工资进行处理…

    return 0;
}

但是,我们现在只是知道如何用数据类型定义变量来表示现实世界中的数据,而对于如何处理这些数据以解决问题还一无所知。我们不知道如何方便地输入这100000个工资数据,更不知道如何计算这100000个工资数据的平均工资。程序的两个任务——描述数据和处理数据。现在我们已经完成了第一个任务,用各种类型的变量描述现实世界中的数据。那么接下来,我们就看看在C++中如何完成第二个任务——处理数据,从而获得结果数据最终解决问题。

4.1  用运算符对数据进行运算

对数据的处理最常见的就是对数据进行运算,以获得某个运算结果。就像在现实世界中,我们对1和2 进行加法运算,可以得到运算结果3一样,在C++中,我们同样可以用两个int类型的变量a和b来表示1和2,那么,又如何对a和b进行加法运算得到结果数据3呢?

4.1.1  用表达式表达设计意图

在计算数学题的时候,如果想知道两个数的和,总是先用加法运算符号“+”连接两个加数列出加法算式,然后再计算整个算式得到最终的和值。例如:

1 + 2 = 3

在C++中也是如此,如果想对数据进行处理获得运算结果,就要用一个式子将数据的运算处理过程描述出来,因为这个式子表达了我们对这些数据的处理意图,所以这个式子也被称为表达式。而程序在执行计算一个表达式的时候,会按照这个表达式所描述的运算过程对数据进行运算,最终获得整个表达式的运算结果。

在C++中,一个表达式由操作符、操作数和标点符号(必须是英文的)三部分组成,其作用是描述一个对数据的运算过程。表达式的核心是操作符和操作数。操作数就是要参与运算的数据,它可以是变量表示的数据,也可以是直接表示的常数。而连接这些操作数表达运算意图的各种符号就是操作符了,比如表示加法运算意图的“+”,表示乘法运算意图的“*”等。操作数是操作符的处理对象,而操作符则表达了对所连接的操作数的处理方式。比如,一个加法运算表达式“a + 5”中,变量“a”和常数“5”是这个表达式的操作数,而连接这两个操作数的是加法运算操作符“+”,表示将它所连接的两个操作数“a”和“5”进行加和运算得到结果。如果这个表达式是某个更复杂表达式的一部分,那么这个结果将作为这个表达式的值,继续参与运算,直到最终得出整个表达式的值。例如:

// 定义需要用到的变量
int a,b,c;
// a、5是操作数,=是操作符,它描述的是一个赋值运算:将常数5赋值给变量a
a = 5;
// b、a、5是操作数,=、+都是操作符
// 它描述的计算过程是:先计算变量a和常数5的和,然后将其赋值给变量b
b = a + 5;
// a、b、c是操作数,=、*、-是操作符,()是标点符号
// 它描述的计算过程是:首先计算变量b和变量a的差,然后将其与变量a相乘计算积,
// 最后将积赋值给变量c。运算过程如图4-1所示。
c = a * (b - a);

图4-1 表达式的计算过程

表达式中的操作符决定了整个表达式中操作数的个数。大多数操作符需要两个操作数,比如加法操作符“+”,就需要一个加数和一个被加数。这种需要两个操作数的操作符称为二元操作符,如常见的加、减、乘、除操作符。另外一些操作符需要一个或者三个操作数,相应地被分别称为一元操作符和三元操作符。C++提供了丰富的操作符,用于表达数据之间复杂的运算关系,如有表示加、减、乘、除的算术操作符,也有表示大小关系的关系操作符等。下面就来分别了解一下如何运用这些操作符以实现对数据的处理。

4.1.2  算术操作符

在开发实践中,用得最多的就是用于数学计算的算术操作符了。C++提供的算术操作符有以下几种。

l  +(加):计算两个数的和。

l  “-”(减):计算两个数的差。

l  “*”:(乘):计算两个数的积。

l  /(除):计算两个数的商。

l  %(取余):计算两个数的余。

以上算术操作符跟数学中相应运算符的用法相同,意义也是一致的。运用这些算术操作符可以很方便地表达对数据的算术运算。例如:

1 + 2;    // 对1和2这两个操作数进行加法运算,表达式的值为3
4 * 5;    // 对4和5这两个操作数进行乘法运算,表达式的值为20
10 % 7;   // 对10除以7取余,表达式的值为3

另外,C++程序中对数值数据的加1或减1操作非常普遍,为了提高编码效率,C++还提供了可以快捷完成此操作的“++(自增)”和“--(自减)”操作符。它们都是一元操作符,可以放在单个操作数的前面或后面(相应地,分别被称为前置操作符或后置操作符),执行对操作数的加1 或减1操作。例如,我们通常用它来实现一些递增和递减的操作:

int nIndex = 0;
// …
// nIndex自身加1,其值递增为1,等同于nIndex = nIndex + 1
++nIndex;

最佳实践:使用前置自增操作符代替后置自增操作符

虽然前置和后置自增操作符的意义是相同的,都是对操作数进行加1操作,但当这两种操作符的结果要继续用来参与运算时,它们的效果却是不一样的。观察下面这段代码:

int a = 1;    // 定义整型变量a,并给a赋初始值为1
cout<<++a;    // 利用前置的自增操作符对a加1,输出为2,这时a的值为2
cout<<a++;    // 利用后置的自增操作符对a加1,输出还是2,但是a的值为3

第二条语句的输出为2,这是因为当使用前置自增操作符时,a首先进行自增运算,其数值变为2,然后再输出a的值,自然就是2了。但是大家一定会对第三句输出也为2感到奇怪,为什么a同样执行了自增操作,输出还是2呢?这是因为使用了后置自增操作符,输出语句首先要输出a的当前值2,然后a再进行自增运算,其值变为3。前置操作符是先计算后输出,后置操作符却是先输出后计算,计算顺序的不同才导致了这么奇怪的结果。

既然它们这么容易让人产生困惑,那么,在实际运用中,到底该如何选择呢?可以记住这样一条编程经验:使用前置自增操作符代替后置自增操作符。这样做可以带来如下好处:

1. 前置操作符的效率优于后置操作符

在C++底层,后置操作符是通过前置操作符实现的,实质上,使用后置操作符最终使用的还是前置操作符,并且增加了额外的转换消耗。所以,使用前置操作符可以一定程度上提高代码的执行效率。

2. 前置操作符不易使人产生困惑,增加代码的可读性

后置操作符有时会让人丈二和尚摸不着头脑。例如:

int a = 1;
int b = a++ + 1; // 变量b的值到底是2还是3呢?

这段代码执行完成后,b的值是2,而不是3。这是因为这里使用的是后置的自增操作符,它会先把a的当前值1用于计算得到结果2,然后再将其赋值给b并自己增加1变为2。结果虽然简单,可是却难以让人“一眼就看出来”,有时甚至会让人错误地认为结果是3。而如果使用前置操作符,写成:

b = ++a + 1; // 先执行a的自增运算变为2,然后执行加1运算,结果为3

则一眼就可以看出b的值是3,整个代码结果一目了然。

前置的自增操作符可以提高代码的执行效率,同时又增加了代码的可读性,那自然是优先选择使用前置自增操作符了,前置的自减操作符也是同样的道理。

4.1.3  赋值操作符

有了算术操作符,就可以得到各个数据的算术运算结果。但是有了运算结果,还需要把结果保存下来以备后用。而赋值操作符就是用来将结果数据保存到变量的。在C++中,最简单的赋值操作符就是“=”。它是一个二元操作符,其右侧通常是某个常数或表达式,而左侧是某个变量。它的作用就是将右侧的值(如果是表达式,则先计算表达式的值)保存到左侧的变量中,以此实现数据的保存。例如:

int a, b, c;
// 将1赋值给变量a,a的值变成1,实现数据1的保存
a = 1;
// 连续赋值,首先执行“c = 1”表达式,将c赋值为1,
// 然后“c = 1”这个表达式的值为1,继续赋值给b,
// b的值也变为1,继续赋值给a,最后a、b、c都被赋值为1
a = b = c = 1;
// 先计算“b + 1”表达式的值为2,
// 然后继续将其赋值给a,a的值变成2,实现了表达式计算结果的保存
a = b + 1;         

另外,赋值操作符左侧的变量有时也会参与右侧表达式的运算。这时,将先以左侧变量的当前值参与右侧表达式的运算,然后再将运算结果赋值给左侧变量。例如:

// 先用a的当前值2进行“a+10”的计算,得到结果12
// 然后再将结果数据保存到a,a的值变为12
a = a + 10;
// 先用a的当前值12进行右侧表达式的计算,得到结果24
// 然后再将结果数据保存到a,a的值变为24
a = a * (b + 1);

像这种变量既参与计算又保存结果的赋值操作在C++中非常普遍,为了简化代码,C++还将这些表达式中负责计算的操作符跟赋值操作符结合起来,形成了带计算功能的复合赋值操作符。包括:算术操作符与赋值操作符组合,例如+=、-=、*=、/=、%=;位运算操作符与赋值操作符组合,例如<<=、>>=、&=、^=、|=。

这些复合的赋值操作符同样是二元操作符,它们首先将两侧的操作数按照复合操作符中的算术或位运算操作符进行计算,这时参与计算的是变量的当前值,得到计算结果后再赋值给左侧的操作数,从而在计算的同时完成了赋值,实现了计算和赋值的复合。利用复合赋值操作符,上面的代码就可以简化为:

a += 10;            // 等价于表达式 “a = a + 10”,以此实现对a的递增操作
a *= b + 1;         // 等价于表达式 “a = a * (b + 1)”

原文地址:http://www.cnblogs.com/nihaoCPP/p/4059213.html

时间: 2024-10-28 19:11:45

(转载)你好,C++(16)用表达式表达我们的设计意图——4.1 用操作符对数据进行运算的相关文章

(转载)你好,C++(12)如何管理多个类型相同性质相同的数据?3.6 数组

你好,C++(12)如何管理多个类型相同性质相同的数据?3.6 数组 3.6  数组 学过前面的基本数据类型之后,我们现在可以定义单个变量来表示单个的数据.例如,我们可以用int类型定义变量来表示公交车的216路:可以用float类型定义变量来表示西红柿3.5元一斤.但是,除了单个孤立的数据之外,现实世界中还有一类批量数据.例如,一个公司所有员工的工资,这些数据的数据类型相同(都是int类型),性质相同(都表示员工的工资),数量很多(成千上万员工的工资),并且往往形成一个有意义的数据集合(员工工

(转载)你好,C++(5)如何输出数据到屏幕、从屏幕输入数据与读写文件?

你好,C++(5)如何输出数据到屏幕.从屏幕输入数据与读写文件? 2.2  基本输入/输出流 听过HelloWorld.exe的自我介绍之后,大家已经知道了一个C++程序的任务就是描述数据和处理数据.这两大任务的对象都是数据,可现在的问题是,数据不可能无中生有地产生,C++程序也不可能凭空创造出来数据.那么,C++程序中的数据又从何而来呢? 在现实世界中,国与国之间的交流是通过外交官来完成的.在C++世界中,也有负责应用程序跟外界进行数据交流的外交官,它们的名字就是基本输入/输出流对象(iost

(转载)你好,C++(4)2.1.3 我的父亲母亲:编译器和链接器 2.1.4 C++程序执行背后的故事

你好,C++(4)2.1.3 我的父亲母亲:编译器和链接器 2.1.4 C++程序执行背后的故事 2.1.3  我的父亲母亲:编译器和链接器 从表面上看,我是由Visual Studio创建的,而实际上,真正负责编译源代码创建生成可执行程序HelloWorld.exe的却是Visual Studio中集成的C++编译器cl.exe和链接器link.exe.他们二老,才是我的亲生爹妈. 为了便于人们的编写.阅读和维护,我们的源文件是使用C++这种人们可以理解的高级程序设计语言编写的.然而,计算机却

Spring Security应用开发(16)基于表达式的访问控制

1.1.1. 通用表达式 Spring Security 使用基于Spring EL的表达式来进行访问控制.内置的表达式如下表所示: 表达式 描述 hasRole(role) 当前主体(principal)是否支持role角色.支持则返回true hasAnyRole(role1,role2) 当前主体是否支持role1,role2中的任意一个角色. hasAuthority(authority) 跟hasRole(role)相似. hasAnyAuthority(authority1,auth

(转载)IQ 16.0 SP02起支持从压缩文件直接装载数据到表中

参考文档: http://m.blog.chinaunix.net/uid-16765068-id-4405877.htmlhttp://www.cnblogs.com/lichmama/p/4103048.html 大致过程: /** 创建测试视图 **/ CREATE VIEW BCPVIEW24 AS SELECT TIMEID , SYSTEM_ID , MSISDN , CITY_ID , RECORDTYPE , NETWORKINITIATION , SERVEDIMSI , SE

表达式计算器类的设计4(面向对象的表达式计算器7)

概述 把符号表和变量表中的内容保存到一个文件中,通过IO文件流,来把符号表和变量表存储到文件中.在这之前需要弄明白什么是序列化和反序列化 对象的序列化 序列化:把对象转换为字节序列的过程 反序列化:把字节序列恢复为对象的过程 我们要把SymbolTable类的对象(符号表)和Storage类的对象(变量表)转换成字节序列保存到文件中,这时就可以设置Serializer类来完成这样的功能,同样的设置一个DeSerializer类来完成把保存到文件当中的字节序列恢复为对象的功能.这里要注意的是,所有

(转载)浅谈我对DDD领域驱动设计的理解

原文地址:http://www.cnblogs.com/netfocus/p/5548025.html 从遇到问题开始 当人们要做一个软件系统时,一般总是因为遇到了什么问题,然后希望通过一个软件系统来解决. 比如,我是一家企业,然后我觉得我现在线下销售自己的产品还不够,我希望能够在线上也能销售自己的产品.所以,自然而然就想到要做一个普通电商系统,用于实现在线销售自己企业产品的目的. 再比如,我是一家互联网公司,公司有很多系统对外提供服务,面向很多客户端设备.但是最近由于各种原因,导致服务经常出故

表达式计算器类的设计5(面向对象的表达式计算器8)

计算器的github下载地址:https://github.com/ljian1992/calculator 概述 表达式计算器的类基本已经设计完成了,由于在程序运行的时候总会有这样那样的异常,例如:a +2, a没有初始化,对于异常的管理一般而言是需要自定义异常类.这个自定义异常类也是在继承了系统已经定义好的exception类,然后再重新定义内容. 异常的种类 语法异常---->SyntaxError类 赋值时左操作数不是变量:1 = 2; 缺少括号:1 + (2*2 不认识的函数: 函数缺

表达式计算器类的设计3(面向对象的表达式计算器6)

概述 有了构建语法的类,存储符号的类,现在就可以对表达式进行扫描,解析了.扫描可以抽象出一个Scanner类来完成这一个功能,而解析可以抽象出一个Parser类来完成这一个功能.这两个类存在一定的关系,扫描与解析的互动是这样子的:扫描到一个标识符,然后解析它是什么标识符.由于该表达式计算器是要支持一些命令的,命令的解析和表达式的解析过程完全不一样,所有呢,又要设置一个CommandParser类,来解析命令. Scanner类,Parser类,CommandParser类的设计 Scanner类