当我们重载运算符时,操作数(operand)的类型和最终结果会被改变。但是!操作数的数量和优先级和组合顺序(precedence and associativity)不会改变!
C++中,lvalue和rvalue的概念是比较麻烦的。lvalue表达式,产生一个对象或函数(object or function);然而,一些lvalue,比如说const类型,不能在赋值语句(assignment)的左边。另外,一些表达式也产生object,但是返回的是rvalue,而非lvalue。大体上,当我们把一个object用作rvalue时,我们使用的是该对象的值(its value,its content);而当我们把object当做lvalue时,我们使用对象本身(its identity,its location in memory)。
另外重要的是:当需要rvalue时,我们可以用lvalue代替;但是,需要lvalue时,不能使用rvalue代替。
lvalue和rvalue很难完全弄明白的,可以根据下面对每个操作符的解读,来深刻体会。
一个表达式,例如:f()+g(),其实我们并不知道计算机是先计算f(),还是先计算g().对操作系统有一定了解的同学就会立刻意识到,f和g这两个函数,不应该对同一个变量改变值,因为可能会发生意想不到的结果。
下面这个例子:
int i=0; cout<<i<<" "<<++i<<endl;由于输出操作符不确定i," ",++i这三个内容中会先执行哪一个;即计算机会先把这三个内容分别计算出来,然后再顺序输出,但是这三个内容哪个先计算哪个后计算是不确定的!因此可能输出:0 1,也可能输出:1 1,我们不得而知。
C++中,只有四个操作符,保证了运算的先后性:"&&","||","?:",和","。其他的操作符都不能保证顺序,因此,我们不应该在那些操作符中改变同一个object。
算术操作符的结果都是rvalue。
在计算时,小的整形会被自动转换成大的整形计算(如short转成int),小的浮点数也被转成大的浮点数(float转成double)。
unary plus(单加符),用来返回原数的一个copy;unary minus(单减符),用来返回原数的负数。
int i =1024; int k = -i;//k=-1024 bool b=true; bool b2 = -b;//b2 = true!
取商和取模
在以前的C++版本中,对于负商,编译器将其四舍五入;在C++ 11中,商永远被向零化整(round toward zero,i.e. truncated)。
对于去模,如果m%n非零,那么就和m的负号相同。
重要:除非-m溢出(这是你应该避免的),否则有:(-m)/n,m/(-n)总和-(m/n)结果相同;m%(-n)和m%n结果相同;(-m)%n和-(m%n)结果相同!
21/6;//3(3.5-->3) -21/-8;//2(2.625-->2) 21/-5;//-4(-4.2-->-4) -21%-8;//-5 21%-5;//1终于不用再纠结取商和取模的结果了!!!赞!!!
这些操作符的操作数都是rvalue,他们的结果也是rvalue。
指针也可以用来做与、或之类的操作。
int *p1 = new int, *p2 = new int; if(p1 && p2)//使用指针的地址来判断!永远为true .... if(*p1 && *p2)//使用指针的值来判断! ...
赋值操作符的左边的操作数,必须是一个可修改的左值(modifiable lvalue)
Assignment经常被直接用在判断条件中:
int i=get(); while(i != 42){ i=get(); ... }更好的方案:
int i; while( (i=get()) != 42){ ... }
这些操作符的操作数必须是lvalue。
前操作符(即加加和减减在前面,prefix operators)返回object本身,是一个lvalue。后操作符(postfix operators)返回object值的一个copy,是一个rvalue。
int i = 0; ++i = 1;//ok i++ = 1;//error!建议:只在必须时使用后操作符(postfix operators)。这是因为,显而易见,前操作符的效率更高。
while(beg != s.end() && !isspace(*beg)) *beg = toupper(*beg++);//error! assignment is undefined!上面例子中,等号两边的内容,需要分别被计算出来,然后再执行赋值操作,因此左边和右边哪个先执行哪个后执行,会影响程序的结果!
条件判断操作符的结果,如果两个表达式都是lvalue,或转换成共同的lvalue type,那么是lvalue;否则,是rvalue。
int *p1,*p2; ( (1>0)?*p1:*p2 ) = 100;//*p1的值为100.注意这里的外圈括号不能少! int *p3; ( (1>0)? p1: p2 ) = p3;//p1指向p3嵌套的条件判断操作符
finalgrade = (grade > 90) ? "high pass" :(grade < 60> ? "fail" : "pass";条件判断操作符是右结合的(right associative)!即从右往左,依次计算内容。
位操作符只对整形(integral type)进行操作。
如果integral是signed并且是负数,那么位操作的结果是不确定的,因此,尽量使用unsigned或确保integral是正数!
sizeof对一个表达式,或一个类型名,返回该表达式结果,或该类型的大小,以占用了几个字节(bytes)来表示。
变量大小操作符是右结合的,其返回值是一个constant expression of type size_t。
有两种用法:
sizeof (type) sizeof expression几个例子:
Sales_data data,*p; sizeof(Sales_data); sizeof data;//和上一个等价 sizeof p;//size of a pointer sizeof *p;//和第一个,第二个等价 sizeof data.revenue; sizeof Sales_data::revenue;//和上一个等价一些值得注意的内容:
sizeof 一个reference type,返回该reference指向的object的size大小
sizeof一个pointer,返回一个pointer自身的大小
sizeof一个dereferenced pointer,返回pointer指向的object的大小,而pointer need not be valid!!
sizeof一个array,返回整个array的大小!sizeof不会把array转换成pointer。
sizeof一个string或vector只返回这个类型本身的大小(即相当于sizeof一个class),其结果是固定的,和string或vector包含的元素无关。sizeof string = 28,sizeof vector = 16。
逗号操作符从左向右,依次计算内容的值。
逗号操作符左边的表达式在计算完后被丢弃,其返回的是右边表达式的计算结果。如果右边表达式是lvalue,那么返回lvalue;否则返回rvalue。
逗号操作符可以有任意多个项,除了最右边的是右边表示,其他都是左边表达式。
默认转换最常出现的情况(Implicit Conversion)
1.比int小的整形会被转换成较大的整形。
2.条件判断中,非bool类型会被转换成bool。
3.初始化时,用来初始化的变量会被转换成接收变量的类型;赋值时,右边表达式被转换成左边表达式的类型。
4.算数和关系运算,所有变量被转换成统一类型。
5.函数调用时。
默认转换中的算数转换
宗旨是将操作数转换成widest type。比如有一个操作数是long double,那么所有其他的操作数都将被转换成long double。如果有浮点数和整形同时出现,那么整形会被转换成适当的浮点数。
默认转换中的unsigned type
其实,转换的规则很复杂,但是大多无关紧要。最最重要的就是:unsigned和signed混合使用时的转换,需要深刻记住!
如果一个unsigned和signed相遇,那么:
1.unsigned的size比signed的size大或相等(the same as or larger than),那么signed就被转换成unsigned!比如unsigned int和int,那么int就被转成unsigned int。
2.unsigned的size比signed的size小。那么结果是不定的。这时候:
2.1unsigned的所有可能的值都包含在signed中,那么unsigned被转成signed,例如long和unsigned int,如果long的bit数更多,那么unsigned int转成long
2.2不满足2.1条件,那么signed被转成unsigned,例如long和unsigned int,如果long和unsigned int的bit数一样多,那么long转成unsigned int
总的来说,就是保证unsigned的精度!
默认转换中的array to pointer
array一般会被转换成pointer。除了:decltype,address of(&),sizeof,typeid。
强制类型转换(Explicit Conversions)
我们使用cast语句来显式转换两个类型。注意:尽量减少类型转换的出现。
强制类型转换共有四种用法:static_cast,dynamic_cast,const_cast,reinterpret_cast.
static_cast
不涉及low-level const的被编译器预先定义好的类型转换,都可以使用static_cast完成。
int j = 0; double p = static_cast<double>(j);
并不是说static_cast的操作符绝对不能是const
const char* p = "aaa"; string s = static_cast<string>(p);//ok!甚至可以有:
const char* p = "aaa"; const string s = static_cast<const string>(p);//ok!top-level const
char *pc = "aaa"; const char* ss = static_cast<const char*>(pc);//ok,low-level const意思是说,static_cast可以添加const,但是它不能去掉const。
只改变操作数的low-level const,且只改变const性质。即只能添加或去除const,但不能改变变量类型。另外,const_cast的操作数和转换类型必须是pointer或reference(这样才能有low-level const)。
const_cast的主要能力是去掉const,如:
const char* s = "aaa"; char* ss = const_cast<char*>(s);但是如果你试图改变ss,那么结果将是危险的!!
const_cast在函数重载时非常有用。
reinterpret_cast
这个类型转换的作用是:重新解释变量的比特类型(bit pattern),但不会改变其二进制的内容。
它能把int*转成double*,甚至能把double*转成int,但是这些变量的值并未改变。
reinterpret_cast需要在深刻理解编译器执行类型转换的实现后,才建议使用!我完全没弄懂这货有啥用!?
dynamic_cast
将在以后介绍。
老版本的强制类型转换
老版本C++中,有两种强制类型转换方法:
type (expr); (type) expr;建议:
尽量不要使用强制类型转换!
const_cast在函数重载时会有用。static_cast和dynamic_cast应该尽量少使用。而reinterpret_cast则非常危险!
如果无法避免,应该尽量限制这个类型转换的作用域(limiting the scope in which the cast value is used),并且标注可能的转换类型(documenting all assumptions about the types involved)。
这张表指明了每个操作符的组合顺序,和优先级,非常实用!