第四章 表达式
表达式是由运算对象构成的集合,在计算过程中将得到结果。基本的表达式包括常量与变量
4.1基础
4.1.1基本概念
组合运算符合运算对象
在处理复杂的算术表达式时, 必须明确各运算符的应用顺序及其相互关系, 并确保正确理解和应用它们
5+10*20/2
运算对象转换
尽管类型转换看似复杂但实际操作却相当合理
左值和右值
C++中的表达式分为left-value和right-value两种类型。当一个object被用作right-value时,则是基于该object的内容;而当该object被用作left-value时,则基于其在内存中的位置。
在需要右值的地方可以用左值来替换,但是不能把右值当成左值使用。
赋值运算符需要一个左值作为其左侧运算,得到结果也是一个左值。
取地址符用于操作一个左值运算对象,并生成指向该运算对象的指针;这个指针成为右值。
内置于内存中的引用操作对于处理数据结构至关重要。在编程语言中,默认情况下字符串(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.拿不准的时候,最好用括号来强制让表达式的组合关系。
如果修改了某个操作数的值,则在表达式中的其他位置不应再次引用该操作数
4.2算数运算符
算数运算符适用于任意算术类型以及可转换为算术类型的类型,并且其运算操作是右值。
int i = 1024;
int k = -i;
%运算,负责计算两个整数相除取的余数,参与运算的对象必须为整数类型。
C++11规定商一律向0取整,即切除掉小数部分。
m%n = m%(-n); 21%-5 = 1
4.3逻辑和关系运算符
关系运算符的操作域包括算术类型以及指针类型;而逻辑运算符则适用于任何能够转换为布尔类型的变量。
逻辑运算符及其相关运算符在执行操作时会生成布尔类型的返回结果;这两个运算对象其结果为right-hand-side value.
逻辑与和逻辑或
AND运算和OR运算是依次先计算左边的运算对象、再计算右边的对象;只有在左边的运算对象无法确定整个表达式的值时才会继续计算右边的对象;这种处理方式被称作短路评估机制。
逻辑与当且仅当左侧运算对象为真是才对右侧进行求值
逻辑或当前仅当左侧运算对象为假时才对右侧进行求值
逻辑非
逻辑非运算符将运算对象值取反后返回。
关系运算符
关系运算符比较运算对象大小并返回布尔值。
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;
后缀递增运算符处于更高的优先等级 than 解引用运算符。由此可见, *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位运算符
位运算符应用于整数类型的运算对象;当这些运算对象为短整型时,则该数值会自动提升至更大的数据类型。
左移运算符<<在其右侧添加补零后的二进制数。右移运算符>>的行为则取决于其左侧操作数的数据类型:若该操作数为无符号型,在其左侧填充补零后的二进制位;若该操作数为有符号型,则在其左侧补充与其数值相等的符号位副本或补零后的二进制数(具体情况由环境决定)。
移位运算符满足左结合律
移位运算符的优先级属于中等水平,在算术运算法则中排位较低;其计算次序低于关系运算法则以及赋值运算法则;但高于条件运算法则
cout<<42+10;//输出求和结果
cout<<(10<42);//输出 1
cout<<10<42;//错误,试图比较cout和42
4.9sizeof运算符
运算符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.对解引用指针,得到指针指向对象所占空间大小,指针不需要有效。
对给定的数组而言,在计算其整体内存占用总量时,这与将每个元素逐一进行sizeof操作后的结果之和是完全一致的。
对于string或vector类型的对象而言,在获取其内存占用时,默认不计入对象元素所占用的空间
4.10逗号类型
按照从左到右的顺序一次求值,先求左侧的表达式丢掉,然后求右侧的。
4.11类型转换
何时发生隐式类型转换
1.大多数表达式中,比int小的整型值都会提升到整型。
2.在条件中,非布尔值转换为布尔值。
- 在初始过程中,在将初始值转为变量的类型时,在赋值语句中,在将右侧运算对象转换为左侧的对象的过程中
4.算数和关系运算中有多种类型,需转化为同一类型。
5.函数调用也会发生类型转换。
4.11.1算数转换
整型提升
这些数据类型包括布尔型、字符型及其变体:bool,char,signed char,unsigned char ,short和unsigned short。这些类型中一旦当前数值能够被整型变量容纳,则建议将其提升为整型(int);如果无法容纳则提升为无符号整型(unsigned int)。
将wchar_t 、char16_t 、char32_t 转换为int及其变体中的最低一种类型,并通过位操作判断所需精度。
4.11.2显示转换
命名的强制类型转换
cast-name
目标类型为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本质上基于机器;为了确保安全使用, 必须清楚理解对象所涉及的数据类型以及如何处理编译器的转换过程。
