Tips for C++ Primer Chapter 4 表达式
2018-06-17 21:52:00来源:未知 阅读 ()
第4章 表达式
左值与右值:不同运算符对运算对象的要求、及其返回值类型
赋值运算符:需要一个(非常量)左值作为其左侧运算对象,得到的结果仍然是一个左值。
取地址符:作用于一个左值运算对象,返回一个右值(它是一个指向左值运算对象的指针)。
解引用运算符、下标运算符:求值结果是左值。
内置类型和迭代器的递增递减运算符:作用于左值运算对象,其前置版本所得结果是左值。
当一个对象被用作右值的时候,用的是对象的值(内容);当对象被用作左值的时候,用的是对象的身份(在内存中的位置)。
注意:使用关键字decltype时:
如果表达式的求值结果是左值,decltype作用于该表达式(不是变量)会得到一个引用类型。
例如:
假定p的类型是int*,因为解引用运算符生成左值,所以decltype(*p)的结果是int&。
另一方面,因为取地址运算符生成右值,所以decltype(&p)的结果是int**(指向整型指针的指针)。
整数的除法与取余运算
(假设m>=0,n>0)
m/n 两整数相除,结果为负值的情况
C++11之前:允许该负值(小数)向上或向下取整。
C++11:一律向0取整(即直接切除小数部分)。(-m)/n和m/(-n)都等于-(m/n)。
m%n 取余运算
C++11之前:允许m%n的符号匹配n的符号。
C++11:m%n的符号匹配m的符号。(-m)%n等于-(m%n),(-m)%(-n)等于-(m%n);m%(-n)等于m%n。
逻辑运算符与关系运算符
短路求值
&&:当左侧运算对象为真时才会对右侧运算对象求值。
||:当左侧运算对象为假时才会对右侧运算对象求值。
关系运算符满足左结合律,返回bool值
if(i<j<k) //首先运算(i<j)结果是一个bool值(0或1),再判断该bool值是否小于k
if(i<j && j<k) //这才是我们的目的
相等性测试与布尔值字面值
int val = 2;
if(val) cout<<"yes"; //将会输出"yes";因为val被转换成bool类型,转换结果是true
if(val == true) cout<<"yes"; //不会输出"yes";因为bool型字面值true被转换成int,转换结果是1,而val!=1
注:除非比较对象都是布尔类型,否则不要使用布尔值字面值(true/false)作为运算对象。
运算符优先级
算术运算>关系运算>逻辑运算
例如:i!=j<k+l 相当于 i!=(j<(k+l))
总结:括号 > 后置递增递减运算符 > 前置递增递减运算符 > 其它单目运算符 > 算数运算 > 移位运算 > 关系运算 > 位运算(& > ^ > |) > 逻辑运算(&& > ||) > 条件运算(? :) > 赋值运算 > 复合赋值运算
总结:复合赋值运算、赋值运算、条件运算、单目运算是右结合,其余运算符一般是左结合。
赋值运算符
赋值运算的结果是它的左侧运算对象,并且是一个(可修改的)左值。
与其它二元运算符不同的是:赋值运算满足右结合律。
int a = 0, b = 1;
a=b=2; //a的值为2,b的值为2(先把2赋给b,再把b的值赋给a)
(a=b)=2; //a的值为2,b的值为1(先把b的值赋给a,再把2赋给a)
注:那什么是不可修改的左值呢?例如:
const int ci = 0; //ci是一个左值,但它是个常量,所以它是个常量左值。
复合赋值运算符
a operator= b 相当于 a = a operator b
注意:二者在程序性能上有区别:
使用复合赋值运算时,只对a求值一次;
使用赋值运算时,会对a求值两次(一次是作为右侧表达式的一部分求值,另一次是作为赋值运算符的左侧运算对象求值)。
递增和递减运算符
前置版本:首先将运算对象加1(或减1),然后将改变后的对象作为求值结果(返回左值)。
后置版本:首先将运算对象加1(或减1),但求值结果是运算对象改变之前的那个值的副本(返回右值)。
注:除非需要,尽量使用前置版本。
前置版本把值加1或减1后就直接返回了该运算对象。而后置版本因为要返回对象未修改之前的值,所以要将原始值拷贝存储一个副本,因而时间和空间上都有额外的开销。
混用解引用符与递增递减运算符
vector<int> v{4,5,6};
auto it = v.begin(); //开始时迭代器指向v的首元素
*(++it) 与 *++it 与 *(it+1) 此三者等价(都输出5),这容易理解。
注解:前两者先将迭代器自身加1(返回左值,值改变后的it本身),然后对迭代器解引用;
后者对“值为迭代器加1的值的内存中的未命名迭代器对象”(返回右值,一个新的迭代器)解引用;
但是 *it++ 与 *(it++) 二者等价(都输出4),这似乎难以理解。
原因是:后置递增运算符的优先级高于解引用运算符,并且后置版本的递增运算符返回的是运算对象改变之前的那个值。
因此,在先后顺序上来看,的确是先让迭代器加1了,但是解引用符的运算对象却是迭代器未加1之前的值(的副本)。
注:你可能会试着这样理解:*it++相当于先*it,取得了it的值,再令it++;而 *(it++)相当于先it++(假设记其为it2),再*(it2);事实上,这两种理解均是错误的,原因已经解释。
求值顺序与优先级、结合律
运算对象的求值顺序与优先级和结合律无关。
例如:对一条形如 f() + g()*h() + j() 的表达式,
优先级规定:先进行g()*h()
结合律规定:先将f()与g()*h()的结果相加,再与j()相加
但是,若这些函数是无关函数,则这些函数的实际调用顺序是不确定的;如果其中的几个函数影响同一对象,将产生未定义行为。
注:只有&&、||、条件运算符、逗号运算符4种运算符明确规定了求值顺序。
条件运算符
当条件运算符的两个表达式都是左值并且是同类型的左值,运算结果是左值。
例如:
int a = 0, b = 0;
( 0<1 ? a : b ) = 10; //最后a的值是10,b的值是0
相当于:
if(0<1)
a = 10;
else
b = 10;
当条件运算符的两个表达式都是左值,但是不同类型的左值,则编译出错。
例如:上例改成“int a = 0; double b = 0;”则编译错误。
当条件运算符的两个表达式其一是左值,另一是右值,则编译出错。
当条件运算符的两个表达式都是右值,运算结果是右值。
例如:
int c = 0<1 ? 2 : 3; //c的值是2
在输出表达式中使用条件运算符
条件运算符的优先级非常低(前面已讨论到),因此尽量对条件表达式加括号。
例如:
cout << grade < 60 ? "fail" : "pass";
它的实际意义是:
cout << (grade < 60); //输出0或1
cout ? "fail" : "pass"; //根据cout的值产生对应的字面值
正确写法:
cout << ( grade < 60 ? "fail" : "pass" );
位运算符
当位运算对象是“小整型”(char、short等),它的值会被自动提升。(通常是int)
当位运算对象是带符号的,且它的值为负,那么位运算符如何处理“符号位”依赖于机器。而且此时的左移操作可能会改变符号位的值,带符号整数的左移是一种未定义行为。
注:建议仅将位运算用于处理无符号类型。
移位运算符
<<:右侧插入0
>>:对无符号类型,左侧插入0;对带符号类型,左侧插入0,或者插入1(符号位的副本)
位求反运算符
~:每个二进制位0变成1,1变成0
位与、位或、位异或运算符
对二进制的每一位:
&:若对应位两个都是1,则结果为1;否则为0
|:若对应位至少有一个1,则结果为1;否则为0
^:若对应位有且只有一个1,则结果为1;否则为0
sizeof运算符
sizeof运算符返回一条表达式或一个类型名字在当前机器所占的字节数。返回值类型是 constexpr size_t。
sizeof只是推断表达式的字节数,而不会实际执行表达式的计算。
sizeof(引用) 得到引用所指对象的字节数。
sizeof(指针) 得到指针本身所占字节数。
sizeof(数组名) 不会把数组名转换成指针,而是得到整个数组所占字节数。
逗号运算符
首先对左侧的表达式求值,然后将求值结果丢弃掉。逗号运算符的最后结果是右侧表达式的值。
如果右侧运算对象是左值,那么最终求值结果也是左值。
int a = 0, b = 0;
(++a, b) = 10;; //a的值是1,b的值是10
int c = (a, b); //c的值是10
显式转换
强制类型转换形式:
cast-name<type>(expression);
type是转换的目标类型,如果type是引用类型,则结果是左值;
expression是待转换的表达式;
cast-name指定了转换规则,可以是static_cast、const_cast等。(此外还有dynamic_cast、reinterpret_cast这里不讨论)
static_cast
只要表达式不包含底层const,都可以使用static_cast。
例如:
int i = 1, j = 2;
double d = static_cast<double>(i) / j; //将int强制转换成double;d的值是0.5
void *p = &d;
double *dp = static_cast<double*>(p); //将void*强制转换成double*
const_cast
const_cast只能改变运算对象的底层const。
(PS:具有底层const特性:不能通过指针修改其所指对象的值;具有顶层const特性:指针本身的值不能被修改)
const char *pc;
char *p = const_cast<char*>(pc); //合法;但是通过p写值是未定义行为
常量对象pc转换成了非常量对象p,此类行为被称为“去掉const性质(cast away the const)”。
一旦我们去掉某个对象的const性质,编译器就不在阻止我们对该对象进行写操作了。
如果对象本身不是一个常量,那么使用const_cast后是合法的行为;
然而如果对象是一个常量,再使用const_cast执行写操作会产生未定义的后果。
例如:
char c = 'a';
const char *pc = &c;
char *p = const_cast<char*>(pc);
*pc = 'b'; //非法;不能通过pc修改c的值
*p = 'b'; //合法;可以通过p修改c的值(*p的值和c的值都是'b'),因为c本身不是一个常量,使用const_cast获得对c的写权限是合法的
注意:若c是一个常量,即:第一行是 “const char c = 'a';” ,那么执行 “*p = 'b';” 会产生未定义的后果。
(在我的机器上的实验结果是:编译器不会发出error或warning,程序运行,最后*p的值是'b',c的值是'a')
标签:
版权申明:本站文章部分自网络,如有侵权,请联系:west999com@outlook.com
特别注意:本站所有转载文章言论不代表本站观点,本站所提供的摄影照片,插画,设计作品,如需使用,请与原作者联系,版权归原作者所有
- C++ 转换函数搭配友元函数 2020-06-10
- C++ 自动转换和强制类型转换(用户自定义类类型) 2020-06-10
- C++ rand函数 2020-06-10
- C++ 友元函数 2020-06-10
- C++ 运算符重载 2020-06-10
IDC资讯: 主机资讯 注册资讯 托管资讯 vps资讯 网站建设
网站运营: 建站经验 策划盈利 搜索优化 网站推广 免费资源
网络编程: Asp.Net编程 Asp编程 Php编程 Xml编程 Access Mssql Mysql 其它
服务器技术: Web服务器 Ftp服务器 Mail服务器 Dns服务器 安全防护
软件技巧: 其它软件 Word Excel Powerpoint Ghost Vista QQ空间 QQ FlashGet 迅雷
网页制作: FrontPages Dreamweaver Javascript css photoshop fireworks Flash