表达式是由一个或多个运算对象组成,对表达式求值将得到一个结果。字面值和变量是最简单的表达式。
4.1基础
4.1.1基本概念
组合运算符合运算对象
对于含有多个运算符的复杂表达式,要理解运算符的优先级和结合律,以及运算符的求值顺序。
5+10*20/2
运算对象转换
类型转换虽然复杂,但合理。例如整形和浮点型互相转换,但是指针不能转为浮点数。小整形bool short char将被提升为int
左值和右值
c++表达式要不是左值就是右值。当一个对象被用作右值时,用的是对象的值(内容);当对象被用作左值的时候,用的是对象身份(在内存中的位置)。
在需要右值的地方可以用左值来替换,但是不能把右值当成左值使用。
赋值运算符需要一个左值作为其左侧运算,得到结果也是一个左值。
取地址符作用于一个左值运算对象,返回指向该运算对象指针,这个指针是右值。
内置解引用,下标运算符,迭代器解引用,string和vector的下标运算符都是左值。
假定p 为int*,decltype(*p) 的结果是int&. decltype(&p)的结果为int **
4.1.2优先级和结合律
复合表达式是指含有两个或者多个运算符的表达式。求复合表达式式需要将运算符和运算对象合理组合在一起。高优先级运算符的运算对象要比低优先级运算符的运算对象更为紧密的结合在一起。
括号无视优先级和结合律
括号无视普通的组合规则,表达式中括号括起来的部分被当做一个单元来求值。
4.1.3求值顺序
优先级规定了运算对象的组合方式,但没有说明运算对象的求值顺序。
int i = f1()+f2();
f1和f2谁先调用没法确定。
对于没有指定运算顺序的运算符来说,如果表达式指向并修改了同一个对象,将引发错误。
int i = 0;
cout<< i<<" " <<i++<<endl;
有4种运算符明确规定了运算对象和求值顺序。分别为&& 、||、?: 和逗号运算符。
运算对象的求值顺序和优先级、结合律无关
f() + g() *h()+j()
优先级规定: g()与*和()相乘
结合律:f()与 g和f的乘积相加再与j相加
对于这些函数的调用顺序没有规定。
注意:1.拿不准的时候,最好用括号来强制让表达式的组合关系。
2.如果改变了某个运算对象的值,在表达式的其他地方不要再使用这个运算对象。
4.2算数运算符
算数运算符能作用于任意算数类型以及能转化为算数的类型,算数运算符的运算对象和求值结果都是右值。
int i = 1024;
int k = -i;
%运算,负责计算两个整数相除取的余数,参与运算的对象必须为整数类型。
C++11规定商一律向0取整,即切除掉小数部分。
m%n = m%(-n); 21%-5 = 1
4.3逻辑和关系运算符
关系运算符作用域算数类型或指针类型,逻辑运算符作用于任意能转化为布尔类型的类型。
逻辑运算符和关系运算符的返回值都是布尔类型。这两个运算对象结果为右值
逻辑与和逻辑或
逻辑与和逻辑或都是先求左侧运算对象,在求右侧的运算对象,当且仅当左侧运算对象无法确定表达式结果才计算右侧的运算对象。这种策略叫短路求值。
逻辑与当且仅当左侧运算对象为真是才对右侧进行求值
逻辑或当前仅当左侧运算对象为假时才对右侧进行求值
逻辑非
逻辑非运算符将运算对象值取反后返回。
关系运算符
关系运算符比较运算对象大小并返回布尔值。
if(i<j<k) 这样不对
相等性测试和布尔字面值
如果想测试算数对象和指针对象的真值,直接将其作为if的条件。
如果写成if(val == true)//太长且如果val不是bool则失去了原来的意义
val不是布尔值,则true会转化为1,相当于判断val 是否等于1了。
4.4 赋值运算符
赋值运算符左侧运算对象必须是一个可修改的左值。结果是它的左侧运算对象,并且是一个左值。如果赋值运算符左右两个运算对象类型不同,则右侧运算对象将转换成左侧运算对象的类型。C++11允许使用花括号括起来的初始值列表
k= {3.14};
vector<int> vi;
vi = {0,1,2,3,4,5,6,7,8,9};
如果左侧运算对象是内置类型,那么初始值列表最多只能包含一个值。
赋值运算满足右结合律
int ival,jval;
ival = jval = 0;
多重赋值语句中的每个对象,他的类型或者与右边对象类型相同,或者可由右边对象转换得到。
int i;
while((i=get_value()) != 42){
}
4.5递增和递减运算符
在一条语句中混用解引用和递增运算符
auto pbeg= v.begin();
while(pbeg != v.end() && *pbeg >=0)
cout << *pbeg++<<endl;
后置递增运算符的优先级高于解引用运算符,因此*pbeg++等价于*(pbeg++),最后这条语句输出pbeg开始指向的那个元素,并将指针向前移动一个位置。
4.6成员访问运算符
ptr->mem 等价于(*ptr).mem
string s1 = "a string ",*p = &s1;
auto n = s1.size();
n = p->size();
4.7条件运算符
嵌套的条件运算符
finalgrade = (grade>90)?"high pass":(grade >60)?"fail":"pass";
条件运算符,满足右结合律,意味着运算对象按照从右往左的顺序组合。
在输出表达式中使用条件运算符
cout<<((grade <60)?"fail":"pass"); //输出pass或者fail
cout<<(grade<60)?"fail":"pass";//输出1或者0!
cout<<grade<60?"fail":"pass";//错误,试图比较cout和60
4.8位运算符
位运算符作用于整数类型的运算对象。如果运算对象是“小整型”,则它的值会被自动提升成较大的整数类型。
左移运算符<<在右侧插入值为0的二进制。右移运算符>>的行为则依赖其左侧运算对象的类型:如果该运算对象是无符号类型,在左侧插入值为0的二进制位,如果该运算对象是带符号类型,在左侧插入符号位的副本或值为0的二进制,视环境而定。
移位运算符满足左结合律
移位运算符的优先级不高不低,比算数运算符优先级低,比关系运算符、赋值运算符和条件运算符的优先级高。
cout<<42+10;//输出求和结果
cout<<(10<42);//输出 1
cout<<10<42;//错误,试图比较cout和42
4.9sizeof运算符
sizeof运算符返回一条表达式或一个类型名字所占的字节数。sizeof运算符满足右结合律,所得结果为size_t类型。
sizeof(type);
sizeof expr
第二种返回的是表达式结果类型的大小。不同的是,sizeof并不实际计算其运算对象的值。
Sales_data data,*p;
sizeof( Sales_data);
sizeof data;
sizeof p;
sizeof *p;
sizeof data.revenue;
sizeof Sales_data::revenue;
因为sizeof不计算实际值,即使p是一个无效的指针,也不会有什么影响。同样,sizeof 无需提供一个具体的对象。
1.对char或者类型为char的表达式执行sizeof运算,结果为1
2.对引用类型,得到被引用对象所占空间大小。
3.对指针,得到指针本身所占空间大小。
4.对解引用指针,得到指针指向对象所占空间大小,指针不需要有效。
5.对数组,得到整个数组所占空间大小,等价于对数组中所有元素各执行一次sizeof运算的结果合。
6.对string或vector对象,返回该类型固定部分的大小,不会计算对象的元素占用了多少空间。
4.10逗号类型
按照从左到右的顺序一次求值,先求左侧的表达式丢掉,然后求右侧的。
4.11类型转换
何时发生隐式类型转换
1.大多数表达式中,比int小的整型值都会提升到整型。
2.在条件中,非布尔值转换为布尔值。
3.初始过程中,初始值转为变量的类型,赋值语句中,右侧运算对象转换为左侧的对象。
4.算数和关系运算中有多种类型,需转化为同一类型。
5.函数调用也会发生类型转换。
4.11.1算数转换
整型提升
bool,char,signed char,unsigned char ,short和unsigned short 只要int能保存了提升到int;否则提升到unsigned int.
wchar_t ,char16_t,char32_t提升成int、unsigned int,long,long long 和unsinged long long中最小的一种类型
4.11.2显示转换
命名的强制类型转换
cast-name<type>(expression);
type是要转换的目标类型,expression是要转换的值。如果type是引用类型,结果是左值。
cast-name 是static_cast、dynamic_cast、const_cast和reinterpret_cast的一种。
static_cast
只要不包含底层const,都可用static_cast.
double slop = static_cast<double> (j)/i;
当需要将较大的算数类型赋值给较小的类型时,非常有用,告诉编译器不在乎损失。
对于void* 可以强制转换为对应的类型。
void *p =&d;
double *dp = static_cast<double*>(p)
const_cast
const_cast只能改变运算对象的底层const
const char *pc;
char *p = const_cast<char *>(pc);
reinterpret_cast
通常为运算对象的位模式提供较低层次上的重新解释,
int *ip;
char *pc = reinterpret_cast<char*>(ip);
reinterpret_cast本质上依赖于机器,要想安全使用,需要对象涉及的类型和编译器实现转换过程都非常了解。