C++ Primer学习 《操作符与类型转换》

操作符与类型转换


运算符重载

当我们重载运算符时,操作数(operand)的类型和最终结果会被改变。但是!操作数的数量和优先级和组合顺序(precedence and associativity)不会改变!


Lvalues和Rvalues(左值和右值)

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。


算数操作符 Arithmetic Operator

算术操作符的结果都是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
终于不用再纠结取商和取模的结果了!!!赞!!!


逻辑和关系操作符(Logical and Relational Operators)

这些操作符的操作数都是rvalue,他们的结果也是rvalue。

指针也可以用来做与、或之类的操作。

int *p1 = new int, *p2 = new int;
if(p1 && p2)//使用指针的地址来判断!永远为true
....
if(*p1 && *p2)//使用指针的值来判断!
...


赋值操作符(Assignment Operator)

赋值操作符的左边的操作数,必须是一个可修改的左值(modifiable lvalue)

Assignment经常被直接用在判断条件中:

int i=get();
while(i != 42){
   i=get();
   ...
}
更好的方案:

int i;
while( (i=get()) != 42){
   ...
}


加加和减减操作符(increment and decrement operators)

这些操作符的操作数必须是lvalue。

前操作符(即加加和减减在前面,prefix operators)返回object本身,是一个lvalue。后操作符(postfix operators)返回object值的一个copy,是一个rvalue。

int i = 0;
++i = 1;//ok
i++ = 1;//error!
建议:只在必须时使用后操作符(postfix operators)。这是因为,显而易见,前操作符的效率更高。
重要建议:赋值语句的两边如果出现同一个object,不要对这个object使用加加和减减操作符!

while(beg != s.end() && !isspace(*beg))
    *beg = toupper(*beg++);//error! assignment is undefined!
上面例子中,等号两边的内容,需要分别被计算出来,然后再执行赋值操作,因此左边和右边哪个先执行哪个后执行,会影响程序的结果!


条件判断操作符(Conditional Operator)

条件判断操作符的结果,如果两个表达式都是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)!即从右往左,依次计算内容。


位操作符(Bitwise Operator)

位操作符只对整形(integral type)进行操作。

如果integral是signed并且是负数,那么位操作的结果是不确定的,因此,尽量使用unsigned或确保integral是正数!


变量大小操作符(Sizeof Operator)

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。


逗号操作符(Comma Operator)

逗号操作符从左向右,依次计算内容的值。

逗号操作符左边的表达式在计算完后被丢弃,其返回的是右边表达式的计算结果。如果右边表达式是lvalue,那么返回lvalue;否则返回rvalue。

逗号操作符可以有任意多个项,除了最右边的是右边表示,其他都是左边表达式。


类型转换(Type Conversion)

默认转换最常出现的情况(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。
const_cast

只改变操作数的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)。


操作符优先表(Operator Precedence Table)

这张表指明了每个操作符的组合顺序,和优先级,非常实用!

C++ Primer学习 《操作符与类型转换》_第1张图片


你可能感兴趣的:(类型转换,操作符)