您的位置:首页 > 编程语言 > C语言/C++

《C++ primer》英文第五版阅读笔记(十三)——表达式基本知识

2015-11-18 21:52 459 查看
Chapter4,Section4.1——Fundamentals

基本原理

对于如何计算表达式的值有许多基本的概念,在这里先简短进行介绍,后面会进行详细的介绍。

(一)基本概念

操作符有一元的,二元的,还有三元的。

一元的操作符只有一个操作数(例如取地址符&,解引用符*等)。

二元的操作符有两个操作数(例如等于符号==,乘法符号*等)。

三元的操作符有三个操作数和一个操作符。函数调用对于操作数的个数没有限制。

有些操作符,比如*,既有一元的含义,又有二元的含义。要具体看它代表的含义。

但是它的使用是独立的,可以将它当做两个不同的操作符来看待。

操作符和操作数的组合

要想理解含有多个操作符的表达式,要理解操作符之间的优先级和结合性,有时候表达式的值也可能取决于计算操作数的顺序。

操作数的类型转换

当计算一个表达式时,操作数可能要经常进行类型转换。

我们可以把一个整型转换成浮点型,反之亦然,但是我们不能把一个指针类型转换为浮点型。

通常小整型类型(比如short,char,bool等)会被提升为更大一点的整型,最典型的是int。

重载操作符

在C++中定义了当操作符被用在内置类型和复合类型中的具体的含义。对于在类类型中使用的操作数,我们也可以定义它们中大多数的含义。因为这种定义方式可以对已经存在的操作符提供可选择的含义,因此把这种方式叫做“重载操作符”。IO库里面的<<和>>操作符,以及我们在strings,vectors,iterators中使用的操作符都是重载操作符。

当我们使用一个重载操作符时,操作符的含义——包括它的操作数的类型和计算的结果都取决于这个操作符是如何进行定义的。然而,操作数的数量、操作符的优先级和结合性是不能被更改的。

左值和右值

C++中的每个表达式,不是一个左值,就是一个右值。这些名字是从C中继承过来的,并且最初有一个简单的助记方法:左值能放在赋值语句的左边,但是右值不能。

在C++里面,左值和右值的区别很简单。一个左值产生一个对象或函数。然而有些左值(比如一个const对象),可能不能作为赋值当中的左操作数。还有一些表达式产生了对象,但是将它们作为右值返回,而不是左值。概括地说,当我们将一个对象作为右值使用时,我们使用的是这个对象的值(它的内容);当我们将一个对象作为左值来使用时,我们使用的是这个对象实体(它在内存中的位置)。

操作符在关于到底是需要左值操作数还是右值操作数是不同的,在它们到底是返回左值还是右值也是不同的。但是重要的一点是(有一个例外后面介绍),当需要右值时,可以使

用左值;但是当左值需要时,不能使用右值。当用左值替代右值时,使用的是左值对象的值。我们已经使用了许多包含左值的操作符。

1.赋值运算需要一个非const的左值作为它的左操作数,并且将它的左操作数作为一个左值。

2.地址符需要一个左值操作数并且把它的操作数返回一个指针,这个指针被当做右值。

3.内置的解引用和下标操作符,iterator解引用和vector与string的下标操作符都产生左值。

4.内置的和iterator的自增和自减操作符需要的是左值操作数,并且前缀形式的自增和自减操作符也产生左值。

当使用decltype时,左值和右值也是不同的。当我们在表达式中使用decltype时(而不是在变量中),如果表达式产生一个左值的话,结果是一个引用类型。

例如:假设p是一个int*类型的指针。由于解引用产生一个左值,decltype(*p)的类型是int&。由于地址符产生的是右值,decltype(&p)的类型是int**,也就是指向int类型指针的指针。

(二)优先级和结合性

含有两个或者更多操作符的表达式是一个复合类型的表达式。计算复合类型表达式的值需要对操作符的操作数进行组织。优先级和结合性决定了操作数是如何被组合的。也就是说,它们决定了表达式中的哪一部分是每一个操作符的操作数。编程者可以不管这些规则通过在复合表达式中使用括号来限制一个特定的组合。

通常,一个表达式的值取决于子表达式是如何进行组合的。优先级高的操作符的操作数比优先级低的操作符的操作数组合的更紧密。结合性决定了相同优先级的操作符的操作数是

如何进行组合的。例如,乘法和除法运算符的优先级相同,但是它们都比加法运算符的优先级高。所以乘法和除法的操作数比加法和减法的操作数先进行组合。算术操作符是左结合的,这就

意味着相同优先级的操作符从左向右进行组合。

括号可以不管优先级和结合性

通过使用括号,可以不用去管正常的组合。含有括号的表达式在计算时,把每一个括号子表达式当成一个单元,其余的部分使用正常的优先级规则。

IO操作符是左结合的,所以我们可以在一行中写许多IO操作符。

(三)计算的顺序

优先级决定了操作数是怎样进行组合的,但是并不能代表操作数被计算的顺序。在大多数情况下,计算的顺序是未知的。

在表达式中如果出现了未指明计算顺序的操作符,其结果是未知的。

例如:int i=0;

cout << i << " " << ++i << endl; //undefined

这段程序的结果是未知的。编译器可能在计算i之前计算++i,输出会是1 1。编译器也可能先计算i,再计算++i,结果是0 1。编译器也可能完全做一些别的事。由于这个表达式的具体行为是未知的,无论编译器生成什么样的代码这段程序都是有错的。

有四个操作符能够保证操作数的计算顺序,分别是&&,||,?:,与逗号(,)。

操作数被计算的顺序是独立于优先级和结合性的。

例如:表达式f()+g()*h()+j()

优先级能够保证g()和h()进行乘法运算。

结合性保证了f()会和g()*h()的结果相加,相加之后的结果会和j()进行相加。

但是无法确定这些函数的调用顺序,哪个先被调用,哪个后被调用。

如果这四个函数是相互独立的函数,并不会影响一个相同的对象或进行IO操作,那个它们被调用的顺序就是未知的。如果它们作用于相同的对象,那么这个表达式就会出错,其结果是未知的。

当使用复合表达式时,记住下面的两个规则:

1.括号会改变运算的逻辑。

2.当改变了一个操作数的值时,不要在同一个表达式里面再使用那个操作数。

第二条规则有一个例外:当改变操作数的子表达式它自己本身也是另一个表达式的操作数时。(例如*++iter)
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: