本 Wiki 开启了 HTTPS。但由于同 IP 的 Blog 也开启了 HTTPS,因此本站必须要支持 SNI 的浏览器才能浏览。为了兼容一部分浏览器,本站保留了 HTTP 作为兼容。如果您的浏览器支持 SNI,请尽量通过 HTTPS 访问本站,谢谢!
这是本文档旧的修订版!
C++ Primer 笔记 第四章
我的笔记均包含大量个人理解内容,存在一定偏差。如果您发现错误,请留言提出,谢谢!
运算符分为一元运算符(unary operator )和二元运算符 (Binary operator)。“元”代表有几个operands。每个运算符的运算优先级(precedence) 和结合律(associativity)由运算符自身决定。 我们在对包含多个运算符的表达式进行计算的时候,常常会遇到一些需要考虑的问题:比如运算优先级,operands的转化,运算符的重载等等。要讨论这些规则之前,我们必须先明白两个概念:左值(Lvalues)和右值(Rvalues)
每个表达式都有至少一个左值或者右值来充当自己的operand。 那什么叫左值?我们可以顾名思义,左值当然是能放到左边的了值了。放到什么左边?放到“=”的左边。不过左值不能这么来定义,因为这么定义会有一些例外。比如 const 对象,这是一个左值,但是不能放到“=”左边。书中讲了一个简单的判定规则:当使用左值的时候,我们是使用的对象在内存里的位置;当使用右值的时候,我们使用的是对象的值。看看“=”左边的值,是不是都能取地址,都能赋值?
判断左值的具体方法可以参考:
关于C++左值和右值区别有没有什么简单明了的规则可以一眼辨别? 看轮子哥的解答。
了解了定义以后,有几个表达式的规则就根据左右值的区分来制订了:
关于运算符对operand的转化后面会详细说到详细的规则。
对于运算符的重载,要注意的是运算符不管怎么重载,operand的类型都是由重载的定义来决定的。但是,重载并不能改变运算符本身的运算优先级和结合律。
优先级和结合律并不能保证运算对象的求值顺序,operand的运算顺序在大多数情况下是未知的;而有时候这种未知的操作顺序会带来很严重的后果,比如:
int i = 0;
cout << i << ++i << endl;
在这里我们根本就不知道i是多少。如果前面的i先运算,那么我们打印的是0, 1;但如果后面的++i先运算,我们打印的就是1,1了,这显然是不合理的。f() + g() * h() + j();
我们知道这几个function我们不知道谁先进行运算,因此如果他们改变同一个对象,我们又要得到undefined的值了。但是如果他们操作的是不同的对象呢?对于一些运算符,operand运算的顺序是指定了的。比如逻辑运算符(&& / || ),条件运算符(? :),逗号运算符(,)。
算术运算符的优先级可以同样可以参考P166。有几点需要注意的是:
算术运算的异常通常有几种情况:
short si = 32767;
si += 1; //error, overflows.
注意:这样的错误编译器很可能不会给出异常,因为在有些系统里是有值得出的,比如上述计算得出si的值为-32768(我的系统)。
对于整数的除法,现行的新标准已经禁止商像负无穷方向取整了。
对于余数的计算 M%N,有:
逻辑运算符都是先求左边operand的值,根据左边结果来判定是不是要求右边的值,这种求值方式我们称为short-circuit evaluation. && / || / !都是比较常见的运算符,主要用于条件判断。对于默认的条件判断来说,括号里的值只要非负,就为真。比如:
while(a); // true if a is any non-zero value.
这里牵涉到类型的隐式转换,后面会cover相关内容。
关系运算符用于鉴别operand的大小,所以也是返回bool类型的值。
因为关系运算是有结合律的,所以不能进行连续的关系运算,会导致结果错误。一般都与逻辑运算符混用,达到不同的条件判断。
注意:不进行关系运算的时候,不要用true / false来进行运算。(进行比较后会有隐式转换)
赋值运算符有几个要点:
赋值运算的结合律是从右到左的。比如:
int ival, jval;
ival = jval = 0; // expression will do jval = 0 first; then do ival = jval
注意:在多重赋值中,所有的对象类型必须一致,或者可以通过类型转换达到一致。
假设用自增运算符(自减也是相同的)对变量 i 进行操作,那么我们会得到两种情况:++i 和 i++.
简单的说来:
从结果来说:
从程序效率上来说:
对于一些迭代器运算,++i的效率要高于i++的。详情可以参考:
在程序开发中,++i 与 i++的区别在哪里? 叶王的回答。
有时候我们会希望使用变量的值,然后再对其加1,这时候我们可以用*iter++这样的表达式。比如我们要遍历打印一个vector:
如果我们用一般的方法,我们需要做两部:
auto pbeg = v.begin();
while (pbeg != v.end()) {
cout << *pbeg << endl; // print current value;
++pbeg; // iterator move to the next position;
}
但现在有了 *iter++这种形式,语句就可以更简洁了:
auto pbeg = v.begin();
while (pbeg != v.end())
*pbeg++; // print current value then move to the next position.
而对于 *pbeg++ 来说,解引用运算的优先级高于自增。所以这个表达式也等价于*(pbeg++)。