表达式(二)

1.算术运算符

这里写图片描述
表中的运算符的优先级是由高到低,它们都满足左结合律,意味着当优先级相同时,按照从左向右的顺序进行结合。不如果不做说明,那么算术运算符都能作用于任何算术类型,或者是可以转化 为算术类型的类型。算术类型的对算对象和运算结果都是右值。一元运算符、加法运算符、减法运算符都能应用于指针。当一元正号运算符作用于一个指针或者是算术值时,返回运算对象的一个(提升后的)副本;一元负号运算符对运算对象值取负后,返回其(提升后的)副本。布尔值不应该参与运算。例如:

int i=1024;
int k=-i; //k为-1024
bool b=true;
bool b2=-b; //很显然,b2仍然为true

两个整数相除结果还是整数,如果商有小数,直接摒除。运算符%称为取余或者是取模运算符,负责记录两数相除所得的余数,要求参加取余运算的运算对象必须是整数类型。如果两个运算对象负号相同,那么商为正数,否则为负数。在早期的版本允许结果为负值的商向上或者向下取整,C++11标准 则 规定商一律向0取整。
根据取余运算的定义,如果m和n是整数且n非0,则表达式(m/n)*n+m%n的 求值结果与m相等。这里面隐含的意思就是,如果m%n不等于0,则它的符号和m相同。C++语言的早期版本运行m%n的符号匹配n的符号,而且商向负无穷一侧取数;但是在新标准中,除了-m导致溢出的特殊情况,其他时候(-m)/n和m/(-n)都等于-(m/n),m%(-n)等于m%n,(-m)%n等于-(m%n)。例如:

21%6; /*结果为3*/    21/6; /*结果为3*/
21%7; /*结果为0*/    21/7;  /*结果为3*/
-21%-8;  /*结果为-5*/  -21/-8; /*结果为2*/
21%-5;  /*结果为1*/   21/-5; /*结果为-4*/

2.逻辑和关系运算符
关系运算符作用域算术类型或是指针类型,逻辑运算符作用于任意能够转换成布尔型的类型。逻辑运算符和关系运算符的返回值都是布尔类型。对于这两类运算符来说,运算对象和求值结果都是右值。

逻辑与与逻辑或运算符
逻辑与运算符和逻辑或运算符都是先求 左侧对象的值再求右侧运算对象的值,当且仅当左侧运算对象无法确定表达式的结果时候才会计算右侧运算对象的值。这种策略称为短路求值对于逻辑与运算符,当且仅当左侧运算对象为真时才对右侧运算对象求值;对于逻辑或运算符,当且仅当左侧运算对象为假时才对右侧运算对象求值。下面 看一个逻辑或的使用案例:

vector<string> svec;
//s是 对常量的引用,既不会拷贝元素,也不会改变元素大小
for(const auto &s:svec)
{
   cout<<s<<endl; //打印出字符串
   if(s.empty() || s[s.size()-1]=='.')
   {
       cout<<endl; //如果字符串为空,或者是以句号为结尾的字符串换行
   }else
   {
     cout<<" "; //其余的用空格隔开
   }
}

逻辑非运算符
逻辑运算符(!)将运算对象的值取反后返回,例如:

vector<int> vec;
if(vec.empty())
{
    cout<<vec[0]<<endl;
} 

关系运算符
关系运算符比较运算对象的大小并返回布尔值。关系运算符都满足做结合律。如果将几个关系运算符连写,会发生不一样的结果。例如:if(i

int i=0,j=0,k=0; //初始化而非赋值
const int ci=i;  //初始化而非赋值

则下面的赋值语句都是非法的:

1024=k; //字面值是右值
k=i+j; //算是表达式是右值
ci=i; //ci是不可修改的左值,只能在初始化的时候给值

**赋值运算的结果是它左侧与运算对象,并且是一个左值,结果的类型就是左侧运算对象的类型。**C++11新标准运行使用花括号括起来的初始化列表作为赋值语句的右侧运算对象。例如:

vector<int> vec;
vec={1,2,3,4,5,6,7,8};

如果左侧运算对象是内置类型,那么初始化列表最多只能包含一个值,而是即使是该值被转化的话,它所占空间也不应该大于目标类型的空间。对于类类型而言,赋值运算的细节由类本身决定。无论左侧运算的类型是什么,初始化列表都可以为空,此时编译器创建一个值初始化的临时量并将其赋值给左侧运算对象。

赋值运算满足右结合律
赋值运算符满足右结合律,这一点和其他二目运算符都不一样。例如:

int ival,jval;
ival=jval=0; //因为满足右结合律,所以靠右的赋值运算jval=0作为靠左的赋值运算符的右侧运算对象。对于多重赋值语句中的每一个对象,它的类型或者与右边对象的类型相同,或者可以由右侧对象的类型转换得到。例如:

int ival,*pval;
ival=pval=0; //错误无法将指针赋值给一个整型变量,正确应该这么写:ival=*pval=0;
string s1,s2;
s1=s2=”Ok”; //正确,之前介绍过字符串字面值可以转化为string对象

赋值运算符优先级较低
赋值语句经常出现在条件当中。因为赋值运算的优先级相对较低,所以通常需要给赋值部分加上括号。例如:

int i;
while((i=getValue())!=42)
{
   //做一些其他处理
}

切勿混淆相对运算符和赋值运算符
如果要判断两个数是否相等,我们应该使用相对运算符而不是赋值运算符。例如:

if(i=j) //只要当j大于0,条件就为真
if(i==j) //判断i和j是否相等

这是在程序中经常容易发生的错误。

复合赋值运算符
任何一种复合运算符都完全等价于a=a op b。和普通的运算符相比,唯一的区别就是左侧对象的求值次数:使用复合运算符只求值一次,使用普通的运算符则需要求值两次。这两次包括:一次作为右边表达式的一部分求值,另一次作为赋值运算符的左侧运算对象求值。

4.递增和递减运算符
递增和递减运算符除了可以使对象加1或者减1操作之外,还可以用于迭代器。因为很多 迭代器本身并不提供算术运算,所以递增和递增运算符除了简洁外,还是必须的。
递增和递减运算符有前置版本和后置版本。前置递增运算符首先将运算对象加1,然后将改变后的对象作为求值结果;后置递增运算符也会将运算对象加上1,但是求值结果是运算对象改变之前那个值的副本。两种运算符必须作用于左值运算对象,前置版本将对象本身作为左值返回,后置版本则将对象原始值的副本作为右值返回。除非必须使用后置版本,一般都使用递增或者递减运算符的前置版本。

在一条语句中混用解引用和 递增运算符
如果想在一条复合表达式中既将变量加1或者减去1又能够使用它原来的值,这个时候可以使用递增和递减运算符的后置版本。例如循环输出一个vector对象内容直至遇到第一个负值为止。

vector<int> ivec;
auto pbegin=ivec.begin();
while(pbegin!=ivec.end() && *pbegin>=0)
{
    cout<<*pbegin++; //先指向下一个元素,然后把pbegin的初始值的副本作为其求值结果
}

其实上面循环中的语句还可以这么写:

cout<<*pbegin;
pbegin++;

但是相比而言,上面的表达方式更加简洁,出错也更少,再加上C++程序追求简洁,所以我们应该习惯上一张书写方式。

运算对象可按任意顺序求值
大多数运算符都没有规定运算对象的求值顺序,但是如果一条子表达式改变了某个运算对象的值,另一条子表达式又要使用该值的话,运算对象的求值顺序将非常关键。因为递增运算符和递减运算符会改变运算对象的值,所以要提防表达式中错用这两个运算符。下面我们看一个while循环:

//该循环的行为是未定义的
while(beg!=s.end() &&!isspace(*beg))
{
   *beg=toupper(*beg++);
}

明显可以看出,赋值运算符的两侧都用到了beg,在赋值运算符的右侧还修改了beg的值,因为这个赋值语句是未定义的。

5.成员访问运算符
点运算符和 箭头运算符都可以用作访问成员,例如:

string s="hellp",*p=&s;
auto n=s.size();
n=(*p).size();
n=p->size(); //等价于(*p).size()

因为解引用运算符的优先级低于点运算符,所以执行解引用运算的子表达式两端必须加上括号,例如:

*p.size(); //错误p时一个指针,它并没有成员size

箭头运算符作用于一个指针类型的运算对象,结果是一个左值。点运算符分成两种情况:如果成员所属的对象是左值,那么结果是左值;如果成员所属的对象是右值,那么结果是右值。

6.条件运算符
条件运算符(?:)允许我们把简单的if-else嵌入到单个表达式当中,按照如下形式使用:cond?expr1:expr2。如果条件为真,对expr1进行运算并返回值,为假,则对expr2进行运算并返回值。

嵌套条件运算符
允许在条件运算符的内部嵌套另外一个条件运算符,也就是表达式可以作为另一个条件运算符的cond和expr。例如:

//首先判断是否不小于60,然后判断是否大于90
finalgrade=(grade>90)?"high pass":grade(<60)?"fail":"pass";

条件运算符满足右结合律,意味着运算对象一般按照从右向左的顺序组合。

在输出表达式中使用条件运算符
条件运算符的优先级非常低,因此当一条表达式中嵌套了条件运算子表达式时,通常需要在它两端加上括号。例如:

cout<<((grade<60)?"fail":"pass"); //输出fail或者pass
cout<<(grade<60)?"fail":"pass"; //输出0或1
cout<<grade<60?"fail":"pass"; //错误 非法的定义

7.位运算符
位运算符作用于整数类型的运算对象,并把运算对象看成是二进制位的集合。位运算符提供检查和设置二进制位的功能,有一种名为bitset的标准库类型也可以表示任意大小的二进制位集合,所以位运算符同样能用于bitset类型。
这里写图片描述
一般情况下,如果运算对象是“小整数”,那么它的值会被自动提升成较大的整数类型。运算对象可以是带符号的,也可以是无符号的。如果运算对象是带符号的而且它的值为负,那么位运算符如何处理运算对象的“符号位”依赖于机器。此时的左移操作可能会改变符号位的值,因此是一种未定义的行为。关于符号位如何处理没有明确规定,所以强烈建议仅将位运算符用于处理无符号类型。

移位运算符
标准IO定义的<<和>>运算符的内置含义就是对其运算对象基于二进制位的移动操作,首先让左侧运算对象按照运算符右侧对象的要求移动相应的次数,然后将移动过的左侧运算对象的拷贝作为运算结果。其中,右侧的运算对象一定不能为负,并且小于结果的位数,否则将产生未定义的行为 。二进制 或向左或向右移,移出边界之外的位将被舍弃。

8.sizeof运算符
sizeof运算符返回一个表达式或者一个类型名字所占空间的大小。sizeof运算符满足右结合律,其所得的值是一个size_t类型的常量表达式。运算符的运算对象有两种:sizeof(type)、sizeof expr。第二种形式中,sizeof返回的是表达式结果类型的大小。不同的是,sizeof并不实际计算其运算对象的值。下面看几个例子:

Sales_data data,*p;
sizeof(Sales_data); //Sales_data类型对象所占的空间大小
siezof data; //等价于sizeof(Sales_data)
sizeof p; //指针p所占的空间
sizeof *p; //等价于sizeof(Sales_data)
sizeof data.revenue; //Sales_data的成员revenue所占的空间
sizeof Sales_data::revenue; //等价于sizeof data.revenue
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值