c++ primer 第五版学习笔记-第四章 表达式

4.1 基础

 

1.表达式由一个或多个运算对象(operand)组成,对表达式求值将得到一个结果(result)。字面值和变量是最简单的表达式(expression),其结果就是字面值和变量的值。把一个运算符(operator)和一个或多个运算对象组合起来可以生成较复杂的表达式。

 

2.C++定义了一元运算符(unary operator)和二元运算符(binary operator)。作用于一个运算对象的运算符是一元运算符,如取地址符(&)和解引用符(*);作用于两个运算对象的运算符是二元运算符,如相等运算符(==)和乘法运算符(*)。除此之外,还有一个作用于三个运算对象的三元运算符。函数调用也是一种特殊的运算符,它对运算对象的数量没有限制。
一些符号既能作为一元运算符也能作为二元运算符。以符号*为例,作为一元运算符时执行解引用操作,作为二元运算符时执行乘法操作。一个符号到底是一元运算符还是二元运算符由它的上下文决定。对于这类符号来说,它的两种用法互不相干,完全可以当成两个不同的符号。

 

3.在表达式求值的过程中,运算对象常常由一种类型转换成另外一种类型。

 

4.C++语言定义了运算符作用于内置类型和复合类型的运算对象时所执行的操作。当运算符作用于类类型的运算对象时,用户可以自行定义其含义。因为这种自定义的过程事实上是为已存在的运算符赋予了另外一层含义,所以称之为重载运算符(overloaded operator)。IO库的>>和<<运算符以及string对象、vector对象和迭代器使用的运算符都是重载的运算符。

 

5.C++的表达式要不然是右值(rvalue,读作"are-value"),要不然就是左值(lvalue,读作"ell-value")。

当一个对象被用作右值的时候,用的是对象的值(内容);当对象被用作左值的时候,用的是对象的身份(在内存中的位置)

(1)赋值运算符需要一个左值作为其左侧运算对象,返回一个指向该运算对象的指针,这个指针是一个右值。

(2)取地址符作用于一个左值运算对象,返回一个指向该运算对象的指针,这个指针是一个右值。

(3)内置解引用运算符、下标运算符、迭代器解引用运算符、string和vector的下标运算符的求值结果都是左值。

(4)内置类型和迭代器的递增递减运算符,作用于左值运算对象,其前置版本所得的结果也是左值。

(5)字面值和算数表达式都是右值。

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

 

使用decltype()时,作用于左值表达式(非变量)得到一个引用类型

int *p=&t;
decltype(*p) y=x;//int &y=x;
  • 1
  • 2

作用于右值表达式,得到一个指针

int j;
decltype(&j) i;//int *(*i); *i=&j;

6.表达式最终的值依赖于其子表达式的组合方式。高优先级运算符的运算对象要比低优先级运算符的运算对象更为紧密地组合在一起。如果优先级相同,则其组合规则由结合律确定。

 

7.以下两条经验准则对书写复合表达式有益:
1.拿不准的时候最好用括号来强制让表达式的组合关系符合程序逻辑的要求。
2.如果改变了某个运算对象的值,在表达式的其他地方不要再使用这个运算对象。

 

4.2 算术运算符

 

1.

 

2.运算符%俗称"取余"或"取模"运算符,负责计算两个整数相除所得的余数,参与取余运算的运算对象必须是整数类型。C++11新标准则规定商一律向0取整(即直接切除小数部分)

除了 m导致溢出的特殊情况,其他时候(-m)/n和m/(-n)都等于-(m/n),m%(-n)等于m%n,(-m)%n等于-(m%n)

 

4.3 逻辑和关系运算符

 

1.

 

2.对于逻辑与运算符来说,当且仅当左侧运算对象为真时才对右侧运算对象求值。
对于逻辑或运算符来说,当且仅当左侧运算对象为假时才对右侧运算对象求值。

 

3.进行比较运算时除非比较的对象是布尔类型,否则不要使用布尔字面值true和false作为运算对象。

 

4.4 赋值运算符

 

1.每种运算符都有相应的复合赋值形式:

+=          -=          *=      /=      %=          //算术运算符  
<<=         >>=         &=      ^=      |=          //位运算符

 

4.5 递增和递减运算符

 

 

1.递增运算符(++)和递减运算符(--)为对象的加1和减1操作提供了一种简洁的书写形式。

    递增递减前置版本将对象本身作为左值返回,后置版本将对象原始值的副本作为右值返回。

 

2.建议:除非必须,否则不用递增递减运算符的后置版本

有C语言背景的读者可能对优先使用前置版本递增运算符有所疑问,其实原因非常简单:前置版本的递增运算符避免了不必要的工作,它把值加1后直接返回改变了的运算对象。与之相比,后置版本需要将原始值存储下来以便于返回这个未修改的内容。如果我们不需要修改前的值,那么后置版本的操作就是一种浪费。
对于整数和指针类型来说,编译器可能对这种额外的工作进行一定的优化;但是对于相对复杂的迭代器类型,这种额外的工作就消耗巨大了。建议养成使用前置版本的习惯,这样不仅不需要担心性能的问题,而且更重要的是写出的代码会更符合编程的初衷。

 

4.6 成员访问运算符

 

1.点运算符和箭头运算符都可用于访问成员,其中,点运算符获取类对象的一个成员;箭头运算符与点运算符有关,表达式ptr->mem等价于(*ptr).mem。

 

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

 

4.7 条件运算符

 

1.条件运算符(? :)允许我们把简单的if-else逻辑嵌入到单个表达式当中,条件运算符按照如下形式使用:
cond ? expr1 : expr2; 
其中cond是判断条件的表达式,而expr1和expr2是两个类型相同或可能转为某个公共类型的表达式。条件运算符的执行过程是:首先求cond的值,如果条件为真对expr1求值并返回该值,否则执行expr2。

 

4.8 位运算符

 

1.位运算符只处理无符号类型

 

2.对于位与运算符(&)来说,如果两个运算对象的对应位置都是1则运算结果中该位为1,否则为0。对于位或运算符(|)来说,如果两个运算对象的对应位置至少有一个为1则运算结果中该位为1,否则为0。对于位异或运算符(^)来说,如果两个运算对象的对应位置有且只有一个为1则运算结果中该位为1,否则为0。

 

4.9 sizeof运算符

 

1.sizeof运算符返回一条表达式或一个类型名字所占的字节数。sizeof运算符满足右结合律,其所得的值是一个

   size_t类型的常量表达式,所以可以声明数组维度

 

2.sizeof运算符的结果部分地依赖于其作用的类型:
对char或者类型为char的表达式执行sizeof运算,结果得1。
对引用类型执行sizeof运算得到被引用对象所占空间的大小。
对指针执行sizeof运算得到指针本身所占空间的大小。
对解引用指针执行sizeof运算得到指针指向的对象所占空间的大小,指针不需有效。在 sizeof 的运算对象中解引用一个无效指针(*p)仍然是安全的,因为指针实际上并没有被真正使用
对数组执行sizeof运算得到整个数组所占空间的大小,等价于对数组中所有的元素各执行一次sizeof运算并将所得结果求和。注意,sizeof运算不会把数组转换成指针来处理。
对string对象或vector对象执行sizeof运算只返回该类型固定部分的大小,不会计算对象中的元素占用了多少空间。

 

4.10 逗号运算符

 

1.逗号运算符(comma operator)含有两个运算对象,按照从左向右的顺序依次求值。和逻辑与、逻辑或以及条件运算符一样,逗号运算符也规定了运算对象求值的顺序。
对于逗号运算符来说,首先对左侧的表达式求值,然后将求值结果丢弃掉。逗号运算符真正的结果是右侧表达式的值。如果右侧运算对象是左值,那么最终的求值结果也是左值。

 

4.11 类型转换

 

1.在下面这些情况下,编译器会自动地转换运算对象的类型:
在大多数表达式中,比int类型小的整型值首先提升为较大的整数类型。
在条件中,非布尔值转换成布尔类型。
初始化过程中,初始值转换成变量的类型;在赋值语句中,右侧运算对象转换成左侧运算对象的类型。
如果算术运算或关系运算的运算对象有多种类型,需要转换成同一种类型。

函数调用时也会发生类型转换。

 

(1)隐式类型转换 
对于bool,char,signed char,unsigned char,short,unsigned short,所有可能值用int可以表示,则提升为int,否则提升为unsigned int 

当有符号与无符号运算时,如果无符号的不小于有符号的则有符号转化为无符号,若为负数则对最大值取模后转化.如果无符号的小于有符号的,如果无符号的所有值都能存在于有符号中,则无符号的转换为有符号的。

①常量整数值0或者字面值nullptr能转换成任意指针类型

②指向任意非常量的指针能转换成void*,指向任意对象的指针能转换成const void*

③将指向非常量类型的指针转换成相应的常量类型的指针,引用也是。相反的转换并不存在,因为它试图删掉底层const。

int i;
const int &j=i; //非常量转换成const int 的引用
const int *p=&i; //非常量的地址转换成const的地址
int &r=j,8q=p;   //错误,不允许const转换成非常量

 

(2)显式类型转化 
static_cast:不包含底层const即可

 

double d=3.14;
void *p=&d;
double *pp=static_cast<double*>(p);
  • 1
  • 2
  • 3

注意:转化后要保证类型相符,否则未定义

const_cast:只改变底层const

const char *p;
char *pp=const_cast<char *>(p);//正确,但通过pp修改值未定义
  • 1
  • 2

注:不能通过const_cast改变表达式类型

 

2.一个命名的强制类型转换具有如下形式:
cast-name<type>(expression); 

其中,type是转换的目标类型而expression是要转换的值。如果type是引用类型,则结果是左值。cast-name是static_cast、dynamic_cast、const_cast和reinterpret_cast中的一种。dynamic_cast支持运行时类型识别。cast-name指定了执行的是哪种转换。

static_cast:任何具有明确定义的类型转换,只要不包含底层const,都可以用static_cast。

void* p=&d;

double *dp=static_cast<double>(p);

3.任何具有明确定义的类型转换,只要不包含底层const,都可以使用static_cast。
const_cast只能改变运算对象的底层const。

reinterpret_cast通常为运算对象的位模式提供较低层次上的重新解释。

   ①.只有 const_cast 能改变表达式的常量属性。常用于有函数重载的上下文中.

//const_cast 只能改变运算对象的底层const
const char *pc;
char *p = const_cast<char*>(pc); //正确:但是通过p写值是未定义的行为

   同样也不能用const_cast改变表达式的类型

const char *p;
const_cast<string> *p;//错误,const_cast只改变常量属性

   ②.避免强制类型转换,因为它干扰了正常的类型检查。尤其是 reinterpret_cast 。

int *ip;
char *pc = reinterpret_cast<char*>(ip); // pc 所指向的对象仍然是 int ,而不是字符。

4.12 运算符优先级表

 

  • 括号,下标,-> 和 .(成员) 最高;
  • 单目比双目高;算术双目比其他双目高;
  • 移位运算 > 关系运算 > 高于位运算 > 逻辑运算 > 条件运算(三目);
  • (复合)赋值运算 > 逗号运算。

 

 

 

 

 

 

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值