C++的表达式部分

基础部分

左值和右值

    C++的表达式要么是左值,要么是右值。C语言中,左值可以位于赋值语句左侧,右值则不能,为了方便记忆我们在C++中延续这一叫法,但是二者的区别就没有这么简单了
    归纳:
  1、当一个对象被当成左值应用时,我们使用的是该对象的内容,是它的值本身;
  2、当一个对象被当成右值使用时,我们看重的是该对象的身份,也就是它的地址,内存中的位置。
    记住以下的原则:当需要用到右值时可以用左值代替,但不可以吧右值当成左值使用(即反之不成立)。
    “翻译”一下以上的原则,即:我们想用到那个对象的值的时候可以使用其在内存中的位置(地址)来代替表示,但是当我们想访问地址时不可以用地址中的值来代替表示。实际上很好理解,我们可以将它想象成一种映射关系:地址到值的映射是唯一的,但是值到地址的映射却是不唯一的(多个地址可能存放同一个值,所以你很难通过值来寻找它的地址)。因此说可以在要用到右值的时候使用左值,但不可以在要用到左值的时候使用右值代替。

求值顺序、优先级、结合律

    优先级说明的是运算对象的组合方式,但是对于运算对象按照什么顺序求值,并没有进行明确的规定。

int i = f1()*f2();
对于这个表达式,我们仅仅知道在乘法发生之前,f1()f2()都会被调用并返回值,但具体谁先被调用也不得而知。
int i = 0;
cout << i << " " << ++i << endl;//错误,编译不通过
对于以上情形,由于"<<"运算符没有明确规定何时以及如何对运算对象求值,所以说编译器可能先对i求值或先对++i求值。
这样就造成了在输出i之前可能就对i进行了改变,可能输出 0 1 也有可能是 1 1 由于情况的未知性,我们说这种写法是未定义的

以下的这个例子我们结合来看

f() + g() * h() + j();//表达式

1、根据优先级:g() 和 h()的返回值相乘
2、根据结合律:f()的返回值和 g() 与 h() 之积相加,所得结果与 j()的返回值相加
3、以上函数的调用顺序未知,没有明确规定

    注意在1、和2、中我没有使用“率先”“最后”等顺序相关的词语,因为以上的结合律和优先级都是决定表达式的结合方式的,而非运算的先后,这一点特别注意,要打破之前的传统思维!!!
    对于3、如果这几个函数是无关的函数,则函数的调用顺序是不受限制的(无所谓谁先谁后),但如果其中某几个函数影响了同一个对象,那么以上的运算值将会受到调用顺序的影响,而调用顺序是没有明确规定的,所以是错误的表达式,产生未定义的行为,编译不会通过。

运算符

算数运算符

int i_val_1 = 21/6;//无论如何,结果一律向 0 取整(即直接切除小数部分)
double d_val = 3.14;
int i_val_2 = 22%d_val;//错误:运算对象必须是整型

逻辑运算符 和 关系运算符

    短路求值:对于&&运算符,先对算符左侧进行运算,当为真时继续计算右侧运算对象;对于||运算符,先对算符左侧进行运算,当为假时继续对计算右侧运算对象。

if(i < j < k){/*...*/}

    关系运算符都服从左结合律,以上表达式将i、j比较得到的bool值和k进行比较,即当k>1时总为真。

赋值运算符

    要求赋值运算符的左侧运算对象必须是一个可修改的左值。注意分清赋值和初始化。

int i = 0, j = 0, k = 0;//是初始化不是赋值
i = 10;//赋值
const int ci = i;//是初始化,这是允许的
1024 = k;//错误:字面值1024是右值
i + j = k;//错误:算术表达式是右值
ci = j;//错误:ci是const对象,左值不可修改
k = 3.1416;//k被初始化为3
k = {3.14};//不被允许:窄化转换
vector<int> vi;//初始化空vector
vi = {1,2,3,4,5};
vi = {2,2,2,2,2,3,4,4,5};
vi = {2,5};//以上任何形式的的赋值都是可以被允许的,不一定非得push_back()
赋值运算符满足右结合律
int i_val, jval;
i_val = j_val = 33;//是合法的:字面值33先被赋值给j_val,之后再将j_val的值赋给i_val。

递增和递减运算符

    Warning:为什么使用前置递增运算符(++i)而不是后置递增运算符(i++)?后置运算符返回的当前值,++之后的值不被返回,由于需要使用到当前值,而对象又要储存++之后的值,所以必须拷贝一个当前值的副本用于返回,这样就造成了空间和时间的浪费。但前置版本就不会出现拷贝等操作,直接返回++之后的值,所以说如果仅仅是想使用++之后的值,那么请尽量养成使用前置递增/递减运算操作。

条件运算符(唯一的三目运算符)

关于需要注意的优先级
cout << grade < 60 ? "fail" : "pass";//程序会先将grade输出,并试图比较cout和60
cout << (grade < 60) ? "fail" : "pass";//程序会输出 0 或 1(60和grade的比较结果)
cout << ((grade < 60) ? "fail" : "pass");//程序输出"fail"或"pass",正常执行
final_grade = (grade > 90) ? "high_pass" : (grade < 60) ? "pass" : "fail";
主要是注意一下常用到的嵌套形式,但嵌套层数增多,常常会使可读性下降,故尽量使用switch case语句或if else等。

位运算符

int i_val = -100;
i_val >>= 3;//右移三位

    对于左移很好理解。对于右移,有符号位的移动规律需要说明:其左侧插入的是符号位的副本或者0,对于我们使用的VS2015,就是插入符号位的副本,对于负数就是插入1。

    另外这个“<<”、”>>“运算符又叫IO运算符,无论用作什么操作,都满足左结合律。

sizeof运算符

    注意这是个运算符,它不是函数,以下各类运用。

string data, *p;
sizeof(string);//整个类占用内存空间的字节数
sizeof data;//这个类型(string)的数据占空间字节数(效果同上)
sizeof p;//指针所占空间的字节数
sizeof *p;//等同于sizeof(string)
sizeof data.begin();//成员变量大小(字节数)
sizeof string::iterator;//因为data.begin()的类型就是string::iterator,故这条语句效用同上。

     sizeof运算符的相关规则
1、对引用类型执行 sizeof 运算符,得到的是被引用对象所占空间大小(字节数)。
2、对指针类型执行 sizeof 运算符,得到的是指针本身所占空间的大小。
3、对解引用指针类型执行 sizeof 运算符,得到的指针指向的对象所占空间的大小,注意这里的指针不需要有效。
4、对数组执行sizeof,得到的是整个数组所占空间的大小,等价于对所有数组中的元素执行sizeof之后求和。注意针对以上第2、条,这里输入数组名称,数组名并不会被转化成指针。
5、对string和vector执行sizeof,那么得到的是其已经有的固定部分的大小。

一种得到数组中元素个数的方法:
sizeof(array)/sizeof(*array)

逗号运算符

    含有两个运算对象,按照从左到右的顺序依次求值即可。

只写一个示例,理解即可
vector<int>::size_type cnt = ivec.size();
for(vector<int>::size_type ix = 0; ix!= ivec.size(); ix++, cnt--)
	ivec[ix] = cnt;

类型转换

隐式转换

int i_val = 3.14 +3;

    就像以上的操作,C++不会将不同类型的字面值进行相加,而是先进行转换(统一类型)。以上的转换是自动执行的,而不需要程序员介入(甚至不需要了解其转换过程),因此称之为隐式转换。以下是一些情况下会发生的隐式转换:
1、表达式中占空间较小的整型(比int小)会自动提升到较大的整型(int类型)。
2、条件语句中,非bool类型转换成bool类型
3、初始化语句中的字面初值将转换成变量的类型;赋值语句中,右侧的运算对象将被转化成左侧的对象类型。
4、算数运算、关系运算,表达式中的不同类型需转化成同一类型。
(5、关于函数调用类型的转换将在之后的章节介绍)

算数转换

1、算数表达式中,运算符的运算对象将转换成最宽的类型(比如一个是long double类型,则另一个运算对象也转换成long double类型)
2、表达式中存在浮点类型的对象,则均转换成相应的浮点类型。

整型提升属于以上1、中提到的内容,对于以下类型:
bool 
char
signed char
unsigned char
short
unsigned short
只要它们所有可能的值都能存在 int 里,则它们会被提升成int类型,否则提升成unsigned int类型

较大的char类型 wchar_t char16_t char32_t将提升为
int 
unsigned int 
long
unsigned long 
long long
unsigned long long 
中的一种(当然是其中最小的一种,前提是可以容纳转换前的所有可能的值)
无符号和带符号

    分两种情况考虑:1、无符号数大于等于带符号数,则带符号数转化为无符号数,带符号数是复数的情况给一个示例:-1转化成unsigned char类型,变为255;2、无符号数小于带符号数:分两种情况:a)若无符号数的所有值可以通过带符号类型完整表示出,则无符号类型转换成带符号类型;b)否则带符号类型转换成无符号类型。

进行举例说明:

显式转换

    转换的具体形式如下:

cast-name<type>(expression)

static_cast

    最一般的类型转换,只要不改动对象的底层const属性用这个进行显式转换都可以。不过多说明

const_cast

    很有问题,需要进一步解答,这部分待续

	const int sss = 0;
	const int* ps = &sss;
	int* p = const_cast<int*>(ps);//?!正确,但通过p写值的行为是未定义的
	//*p = 1;

reinterpret_cast 和 dynamic_cast

    这部分咱不进行介绍,之后的可能开辟章节介绍
(待续)

待解决问题

1、赋值运算符第三个代码段,为什么第二个语句发生错误???怎么理解
2、while(cin>>s)//while条件部分将cin转换成bool值,但是cin何时才为false呢?这个语句何时才会因为不满足while条件而断出呢?
3、关于const_cast的一些疑问:书中说通过p写值的行为是未定义的,我不清楚自己是不是没有理解它的意思,但是我通过p解引用来修改底层对象的值不正是我使用const_cast的本意吗?他的本意就是想解除底层const的属性,但是为啥又说这种行为是未定义的?!再有就是在VS2015上验证,这种写法是允许的比如说*p = 7;但是经过修改,直接访问对象值却发现没有改变,直接通过指针解引用得到的却是修改后的值!最重要的是我查看二者的地址,是相同的(指针值和对象取地址的值)。
4、关于constexpr表达式以及何为constexpr函数,它们的定义时什么,怎样去理解,如何区分和const限定符的区别。


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值