《C++ primer》学习笔记(第四章)——表达式

表达式

注:本章的sizeof相关内容会拎出来单独记录,并且补充一下关于c++内存对齐的相关知识
本章内容较基础但是容易出错,尤其是各种各样运算符的运算。本章只对个别知识点进行总结。
左值与右值的概念
关于左值与右值的概念,参考自这篇博客
c++11中的所有值,要么是左值,要么就是右值。简单判断左值和右值的方法就是:有名字,可以取地址的就是左值,而没有名字不能取地址的就是右值。还有一个方法判断方法就是:左值能够出现在赋值语句的左侧和右侧,而右值只能位于赋值语句的右侧(不严谨)。比如:int a=b+c;a就是左值,可以对a取地址,而b+c就是右值,不能进行&(b+c)取地址操作。

右值又分为纯右值将亡值。纯右值包括:①不跟对象关联的字面值,如true,22,"hello"等;②表达式,如:a+b;③函数返回值,如:int func()。将亡值是跟右值引用相关的表达式。如返回右值引用T&&的函数返回值、std::move的返回值,或者转换为T&&的类型转换函数的返回值。将亡值可以理解为通过“盗取”其他变量内存空间的方式获取到的值。在确保其他变量不再被使用、或即将被销毁时,通过“盗取”的方式可以避免内存空间的释放和分配,能够延长变量值的生命期。

相应的左值引用右值引用就是对左值和右值的引用。无论是左值引用还是右值引用都是引用类型,因此都需要初始化。非常量左值引用只能使用非常量左值对其初始化,而常量左值引用可以使用非常量左值,常量左值,右值对其初始化(详见第二章)。

const int &p=5;//使用右值对常量左值引用初始化

而右值引用一般不能绑定到任何左值上,若需要将一个左值绑定到一个右值引用,则需要使用std::move()将左值强制转换为右值。

int c=10;
int &&p=std::move(c);//将左值转化为右值,然后绑定到右值引用上

4.1 基础
求值顺序
c++中有4种运算符规定了运算对象的求值顺序:
①:逻辑与运算符(&&):当且仅当左侧为真时,才继续对右侧进行计算
②:逻辑或运算符(||):当且仅当左侧为假时,才继续对右侧进行计算
③:条件运算符(?:):当条件为真时,对前一个表达式计算,否则计算后一个表达式
④:逗号运算符(,):先对左侧进行计算,如何对右侧进行结算,并且最终返回右侧的值,一般出现在for循环语句中。

4.2 算数运算符
%运算符表示取余或取模运算符。注意:m%n得到的结果的符号与m的符号相同,与n的符号无关,即有:m%(-n)=m%n;-m%n=-(m%n)。而m/n的结果与m和n的符号都有关。

4.3 逻辑与关系运算符
进行比较运算时,除非比较对象是布尔类型,否则不要使用true或false,因为true或false会自动转换为该比较类型,比较麻烦

int i=3;
if(i==true);//true会转化为1
if(i==1);//直接与1进行比较

4.4 赋值运算符
赋值运算符的左侧对象必须是可以修改的左值

const int i=10;
i=20;//错误对象是常量不能修改
a+b=20;//错误,对象是右值

赋值运算符满足右结合律

int i,j,k;
i=j=k=10;//正确。

4.5 递增和递减运算符
前置递增和递减运算符是返回对象改变后的值;
后置递增和递减则是先返回对象改变前的值(返回原始值的副本)。

int i=0;
int a=++i;//a=1,i=1;
int b=i++;//b=1,i=2;

后置运算符的优先级别高于解引用运算符,因此*iter++,就相当于*(iter++),由于iter++是后置运算符,因此将iter先向前移动,然后返回移动前的对象,因此*iter++可以理解为:先取iter的内容,然后在将iter向前移动一位。而(*iter)++表示将iter所指向的内容加1,但是返回的是加1前的内容。

vecotr<int> v={1,2,3,4,5};
auto iter=v.begin();
cout<<*iter++<<endl;//得到的结果为1,但是iter此时已经指向2
cout<<(*iter)++<<endl;//得到2
cout<<*iter<<endl;  得到3

另外值得注意的是:关于书上第133页的一段描述,文中举的例子仿佛不太恰当,如下:

string s="hello";
auto iter=s.begin();
*iter=toupper(*iter++);//文中指出此处“错误,该赋值语句未定义”。实际上经过测试,
                                  //发现该语句没有错误,而且结果总是:"hHello"。

4.6 成员访问运算符
表达式:p->size()等价于(*p).size().p为指针,由于点运算符的优先级要高于解运算符,因此注意添加括号。

4.7 条件运算符
条件运算符前文以及提及过,值得一提的是条件运算符优先级别较低,在输出表达式中所用条件运算符需要使用括号

cout<<(H<180?"low":"heigh")<<endl;//正确
cout<<H<180?:"low":"heigh"<<endl;//错误,实际上是先cout<<H,然后返回的cout再进行cout<180,发生错误

4.8 位运算符
有:&,|,^,<<,>>
对于位运算符的对象如果是小整型,则会自动提升为大整型。另外位运算符的结果为原始对象移动后的拷贝,但是原始对象并没有发生变化

int i=1;
int j=i<<1;
cout<<i<<j<<endl;//此时i仍然为1,而j为2.

对于左移运算符(<<),在右侧插入0,若左侧移除边界之外则舍弃掉;
对于右移运算符(>>),右侧的直接舍弃,对于无符号类型左侧补0,对于有符号类型左侧可能是符号位也可能是0(结果测试本人的机子补的是符号位),

int i=-2;
int j=i>>1;
cout<<i<<j<<endl;//i=-2,j=-1.注计算机存储的是二进制的补码(反码加一)自己用小本本推算一遍就了解了

4.9 sizeof运算符

关于sizeof运算符准备单独拎出来学一学,这里简单讲一下sizeof的基础知识,顺便了解一下c++的内存对齐的知识。
sizeof返回的是一条表达式或者一个类型名字的所占的字节数(返回类型为常量表达式)。但是sizeof实际并不计算运算对象的值(类似于decltype)。

class A;
 A a, *p;
 sizeof (A);
 sizeof a;
 sizeof p;//指针p所占的空间大小
 sizeof *p;//p所值类型的空间大小,但是并没有求运算对象的值,因此即使p是一个未初始化的指针,sizeof不需要真正解该指针。

使用sizeof运算符注意一下几点:
①、对char或者类型为char的表达式执行sizeof结果为1;

sizeof(char);//结果为1

②、对引用类型执行sizeof得到的是该引用所绑定对象所占空间的大小;

int i=10, &a=i;
sizeof(a);//结果为4,int类型所占用的字节数

③、对指针进行sizeof运算得到指针本身所占空间的大小;

char i = 10, *p = &i;
cout<<sizeof (i)<<""<<sizeof(p)<<endl;//结果为1和4,指针本身占用4个字节

④、对解指针进行sizeof运算得到指针所指向对象所占空间的大小,此时指针不需要有效;

char i = 10, *p = &i;
	cout<<sizeof (*p)<<""<<sizeof(p)<<endl;//结果为1和4

⑤、对数组进行sieof运算得到整个数组所占空间的大小,相当于对数组所有元素各执行sizeof运算后结果的和。注意:sizeof运算并不会将数组转化为指针

int a[5] = { 1,2,3,4,5 };
cout << sizeof(a) << endl;//结果为20,即4*5.

⑥、对string和vector进行sizeof运算,只返回该类型固定部分的大小,不会计算对象中的元素所占用的空间

vector<int> v={1,2,3,4```};
sizeof(v);//无论v是否会增加还是删除元素个数,sizeof(v)的值一直都是16.

关于第6条我将会在下一次有关sizeof运算的章节进行解释。

4.10 逗号运算符
前已述及
4.11 类型转换
类型转换分为隐式类型转换显式类型转换
以下几种情况会发生隐式类型转换:
①、在大多数表达式中,比int小的整型会被首先提升为较大的整型;
②、在条件中,非布尔类型会转换成布尔类型;
③、初始化过程中以及负责语句中,右侧对象类型会转换为左侧对象类型
④、如果算数运算或关系运算的运算对象存在多种类型,需要转换为同一种类型
另外还包括其他几种特殊情况:
⑤、数组转换成指针

int a[5]={1,2,3,4,5};
int *p=a;//隐式类型转换。

但是如果使用decltype,&(取地址运算符),sizeof时,上述转换不会发生
⑥、指针的转换
常量0或者字面值nullptr能转换成任意指针,指向任意非常量的指针能够转换成void*,指向任意对象(包括非常量)的指针可以转换为const void*。
⑦、另外还包括非常量对象转换成相应类型的常量引用等(详见第二章)。

显式类型转换
在某些情况下,如果不进行显式类型转换,可能会得到错误的结果,如:

int j=5,i=2;
doouble d=j/i;//结果为2.0而不是2.5
d=static_cast<double>(j)/i;//通过显式类型转换后结果为2.5

显式类型转换有四种方式:static_cast,dynamic_cast,const_cast,reinterpret_cast。使用形式统一如下:

以static_cast为例;
static_cast<需要转换的类型>(表达式或变量);

①、dynamic_cast:该方法支持运行时类型识别,在后面章节在学习吧
②、static_cast:比较常用的一种方法,除了底层const以外,任何具有明确定义的类型都可以使用这种方法,如将一个较大的类型转换为一个较小的类型

int i=10;
double d=static_cast<double>(i);//d=10.0
void *v=&a;
int *p=static_cast<int*>(v);//将void*类型装换为int*类型指针
const int* a=i;
int *p=static_cast<int*>(a);//错误,static_cast不能改变底层const,只能使用const_cast.

③、const_cast:只改变常量属性,且只能改变底层const,且<>里面只能是指针或者引用。使用这种方法将一个常量转换成非常量后,如果试图执行写操作就会产生未定义的结果

const int* a=i;
int *p=const_cast<int*>(a);//正确,常量转换为非常量
int j=10;
double d=const_cast<double>(j);//错误,const_cast类型必须为指针或者引用**加粗样式**

④、reinterpret_cast:意思为“重新解释”,且同样只能用于指针或引用之间,可以用于处理无关类型之间的转换。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值