C++primer 第四章:表达式学习笔记

重载运算符

当C++运算符作用于类类型的运算对象时,用户可以通过自定义为已存在的运算符赋予另外一层含义,所以称为重载运算符;

左值或右值

左值:左值是一个对象或变量,可以代表着一个固定地址。

int i = 1//此时,i是个变量,本质和对象一样,是一块内存区域,代表着一个固定的地址。

右值:不能作为左值的都是右值,要么是一个常量,要么就是一个使用一次立即被销毁的临时变量或临时对象,右值没有固定的地址,使用完立即被释放。

int i = 1 ; //1 是一个右值,没有固定地址,虽然它也占内存,但用完就被立即释放

当一个对象被用作右值的时侯,用的是对象的值(内容),当对象被用作左值时,用的是对象的身份(在内存中的位置);
一个重要原则:需要右值的地方可以用左值来代替,但不能把右值当成左值使用,当一个左值被当成右值使用时,实际使用的是它的内容。

几点总结:

  1. 赋值运算符需要一个(非常量)左值作为其左侧运算对象,得到的结果也仍然是一个左值;
  2. 取地址符作用于一个左值运算对象,返回一个指向该运算对象的指针,这个指针是一个右值;
  3. 内置解引用运算符加粗样式、下标运算符、迭代器解引用运算符、string和vector下标运算符的求值结果都是左值;
  4. 内置类型和迭代器的递增递减运算符作用于左值运算对象,其前置版本也是左值;

注意:使用关键字decltype时,左值和右值有所不同;如果表达式的求值结果是左值,decltype作用于该表达式得到一个引用类型,如假定p的类型是int*,因为解引用运算符生成左值,所以decltype*(*p)的结果是int&;另一方面,因为取地址运算符生成右值,所以decltype(&p)的结果,结果是一个指向整型指针的指针;

优先级与结合律

复合表达式是指含有两个或多个运算符的表达式,优先级和结合律决定了运算对象的组合方式,括号无视上述规则;
高优先级运算符的运算对象比低优先级运算符的运算对象更为紧密地组合在一起;如果优先级相同,组合规律由结合律决定;
举个例子:
6 + 3 * 4 / 2 + 2
此式运算结果为14,等价于下述表达式:
( ( 6 + ( ( 3 * 4 ) / 2 ) ) + 2 )

括号无视优先级和结合律
表达式中括号括起来的部分被当成一个单元来求值,然后再与其他部分一起按照优先级组合;
优先级与结合律有何影响

求值顺序
4种运算符明确规定了运算对象的求值顺序:
逻辑运算符(&&)规定先求左侧运算对象的值,只有当左侧为真时才继续求右侧运算对象的值,另外三种是逻辑或(||)、条件运算符(?😃、逗号运算符(,)。

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

算术运算符

  • 一元正号      + expr 
    
  •  一元负号      - expr
    
  •  加法         expr + expr
    

一元运算符的级别最高,接下来是乘法和除法,优先级最低的是加法和减法;
算术运算符的运算对象和求值结果都是右值;
对于大多数运算符来说,布尔类型的运算对象将被提升为int类型;
参与取余运算的运算对象必须为整数类型;
除法运算中,如果两个运算对象符号相同则商为正,否则为负,C++11标准规定商一律向0取整;
21 / -5结果是-4;

逻辑和关系运算符

关系运算符作用于算术类型或指针类型,逻辑运算符作用于任意能转换成布尔值的类型,两者都返回布尔类型;两类运算符的运算对象和求值结果都是右值;

  1. 右 ! 逻辑非 !expr
  2. 左 < 小于 exp1<expr2
  3. 左 <= 小于等于 exp1<=expr2
  4. 左 > 大于 exp1>expr2
  5. 左 >= 大于等于 exp1>=expr2
  6. 左 == 相等 exp1==expr2
  7. 左 != 不相等 exp1!=expr2
  8. 左 && 逻辑与 exp1&&expr2
  9. 左 || 逻辑或 exp1||expr2
    逻辑与和逻辑或都是先求左侧运算对象的值,当且仅当左侧运算对象无法确定表达式的结果时才会计算右侧运算对象的值,这种策略称为短路求值
    关系运算符
    都满足左结合律,用来比较运算对象的大小并返回布尔值;
if(i<j<k) //若k大于1则为真

if语句的条件部分首先把i、j和第一个<运算符组合在一起,其返回的布尔值再作为第二个<运算符的左侧运算对象;
可以将上述语句改为下面的形式来实现目的:

if(i < j && j < k) //当i小于j并且j小于k时条件为真

相等性测试与布尔字面值

if(val) //如果val是任意非0值,条件为真
if(!val) //如果val是0,条件为真

进行比较运算时除非比较的对象时布尔类型,否则不要使用布尔字面值true和false作为运算对象;
赋值运算符
赋值运算符的左侧运算对象必须是一个可修改的左值

1024 = k; //错误:字面值是右值
i+j = k; //错误:算术表达式是右值
const int ci = i; //初始化而非赋值
ci = k; //错误:ci是常量

结果的类型就是左侧运算对象的类型,如果赋值运算符的左右两个运算对象类型不同,则右侧运算对象将转换成左侧运算对象的类型

k=0;//类型是int,值是0
k=3.14159;//类型是int,值是3

赋值运算满足右结合律

int ival,jval;
ival=jval=0;//正确:都被赋值为0

靠右的赋值运算jval=0作为靠左的赋值运算符的右侧运算对象;
赋值运算优先级较低
因为赋值运算符的优先级低于关系运算符的优先级,所以在条件语句中,赋值部分通常应加上括号;
切勿混淆相等运算符和赋值运算符
C++允许用赋值运算作为条件,但会带来意想不到的后果;
复合赋值运算符
+= -= *= /= %= 算术运算符
<<= >>== &= ^= |= 位运算符
等价于 a=a op b
区别在于左侧运算的求值次数:使用复合运算符只求值一次,使用普通的运算符则求值两次,一次是作为右边子表达式的一部分求值,另一次是作为赋值运算符的左侧运算对象求值;

递增和递减运算符

递增运算符(++)和递减运算符(–)为对象的加1和减1操作提供了简洁的书写形式;
两种形式:前置版本和后置版本;
前置版本首先将运算对象加1(减1),然后将改变后的对象作为求值结果;
后置版本先作为求值结果,然后在加1(减1);
建议:一般不用后置版本;
在一条语句中混用解引用和递增运算符
*pbeg++的写法等价于:pbeg++把pbeg的值加1,然后返回pbeg的初始值的副本作为其求值结果,此时解引用运算符的运算对象是pbeg未增加之前的值
运算对象可以任意顺序求值
如果一条子表达式改变了某个运算对象的值,另一条子表达式又要使用该值的话又要使用该值的话,运算对象的求值顺序就关键了;

成员访问运算符

点运算符获取类对象的一个成员,箭头运算符与点运算符有关,如ptr->mem等价于(*ptr).mem;
箭头运算符的运算对象是左值,点运算符的运算对象可左值也可右值;

条件运算符

cond ? expr1 : expr2
首先求cond的值,如果条件为真对expr1求值并返回该值,否则对expr2求值并返回该值;
嵌套条件运算符
条件表达式可以作为另一个条件运算符的cond或expr;

类型转换

如果两种类型可以相互转换,那么它们就是关联的;
如果类型转换是自动执行的,无须程序员的介入,被称为隐式转换
何时发生隐式类型转换

  • 大多数表达式中,比int类型小的整型值首先提升为较大的整数类型;
  • 在条件中,非布尔值转换成布尔类型;
  • 初始化时,初始值转换成变量的类型;赋值语句中,右侧运算对象转换成左侧运算对象的类型;
  • 如果算术运算或关系运算的运算对象有多种类型,需转换成同一种类型;
    算术转换
    把一种算术类型转换成另一种算术类型,运算符的运算对象将转换成最宽的类型;如一个运算对象的类型是long double,那么不论另一个运算对象的类型是什么都会转换成long double;
    整形提升
    负责把小整数类型转换成较大的整数类型;
    对于bool、char、signed char、unsigned char、short和unsigned short等类型来说,只要它们所有的值都能存在int里,它们就会提升成int类型;否则,提升成unsigned int类型;
    较大的char类型(wchar_t、char16_t、char_t)提升成int、unsigned int、long、unsigned long、long long和unsigned long long中最小的一种类型;
    无符号类型的运算对象
    首先进行整形提升,如果结果类型匹配,无须进行进一步转换,如果两个运算对象的类型要么都是带符号的、要么都是无符号的,则小类型转换为较大类型;
    如果一个运算对象是无符号类型、另一个运算对象是带符号类型:

1.如果无符号类型不小于带符号类型,那么带符号的运算对象转换成无符号的;
2.如果带符号类型大于无符号类型,转换结果依赖于机器;如果此时无符号类型的所有值都能存在该带符号类型中,则无符号类型的运算对象转换成带符号类型,如果不能,那么带符号类型的运算对象转换为无符号类型;
其他隐式类型转换

  • 数组转换成指针:数组自动转换成指向数组首元素的指针;
  • 转换成布尔类型:如果指针或算术类型的值为0,转换结果为false,否则为true;
  • 转换成常量:允许将指向非常量类型的指针转换成指向相应的常量类型的指针;
  • 类类型定义的转换:能定义由编译器自动执行的转换,不过编译器每次只能执行一种类类型的转换;

显式转换
显式地将对象强制转换成另外一种类型;
命名的强制类型转换
cast-name(expression);
type是转换的目标类型,expression是要转换的值,如果type是引用类型,则结果是左值;
cast-name是static_cast、dynamic_cast、const_cast和reinterpret_cast中的一种;

double slope = static_cast<double>(j)/i;

static_cast常用于把一个较大的算术类型赋值给较小的类型;对编译器无法自动执行的类型转换也非常有用;如用static_cast找回存在于void*中的值:

void* p = &d;
double *dp = static_cast<double*>(p);//将void*转换回初始的指针类型,强制转换的结果将与原始的地址值相等

const_cast
只能用于改变对象的底层const;

const char *pc;
char *p = const_cast<char*>(pc);//正确,但是通过p写值是未定义的行为

对于将常量对象转换成非常量对象的行为,称为“去掉const性质”;如果对象本身不是一个常量,使用强制类型转换获得写权限是合法的行为;如果对象是一个常量,使用const_cast执行写操作就会产生未定义的后果;
reinterpret_cast
通常为运算对象提供较低层次上的重新解释;

int *ip;
char *pc = reinterpret_cast<char*>(ip);

牢记pc所指的真实对象是一个int而非字符;
这个尽量不使用;
旧式的强制类型转换
type (expr);//函数形式的强制
(type) expr;//c语言风格

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值