第四章 表达式

4.1 基础

  • 运算对象转换

类型转换大多数都合乎情理,但是小整数类型(如bool,char,short等)通常会被提升成较大的整数类型,主要是int。

  • 重载运算符

当运算符作用于类类型的运算对象时,用户可以自行定义其含义。因为这种自定义的过程事实上是为了已存在的运算符赋予了另外一层含义,所以称之为重载运算符。
我们使用重载运算符时,其包括运算对象的类型和返回值的类型,都是由该运算符定义的,但是运算对象的个数,运算符的优先级和结合律都是无法改变的。

  • 左值和右值

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

  1. 赋值运算符需要一个(非常量)左值作为其左侧运算对象,得到的结果仍然是一个左值。
  2. 取地址符作用于一个左值运算对象,返回一个指向该运算对象的指针,这个指针是一个右值。
  3. 内置解引用运算符、下标运算符、迭代器解引用运算符、string和vector的下标运算符的求值结果都是左值。
  4. 内置类型和迭代器的递增运算符作用于左值运算对象,所得结果也是左值。
    使用关键字decltype的时候,左值和右值也有所不同。如果表达式的求值结果是左值,decltype作用于该表达式得到一个引用类型

假设p的类型是int ,因为解引用运算符生成左值,所以decltype(p)的结果是int&,因为取地址符生成右值,所以decltype(*p)的结果是int **,结果是一个指向整型指针的指针

算数运算符

  • 算数运算符

参与取余运算的对象必须是整数类型。
(-m) % n == - (m % n);
m % (-n) == m % n;

  • 递增和递减运算符

递增和递减运算符有两种形式:前置版本和后置版本
前置版本:首先将运算对象加1(或减1),然后将改变后的对象作为求值结果。
后置版本:也会将运算对象加1(或减1),但是求值结果是运算对象改变之前的值。
除非必须,否则不用递增递减运算符的后置版本!前置版本避免了很多不必要的操作,他把值加一后直接返回改变了的运算对象,后置版本需要将原始值存储下来以便于返回这个未修改的内容,如果不需要修改前的值,那么后置版本操作就是一种浪费。

  • 条件运算符( ?: )

形式: cond ? expr1 : expr2;
cond是判断条件的表达式, expr1和expr2是两个类型相同或可能转换为某个公共类型的表达式。
首先求cond的值,如果条件为真对expr1求值并返回该值,否则对expr2求值并返回该值。
当条件运算符的两个表达式都是左值或者能转换成同一种左值类型时,运算的结果是左值,否则结果是右值。
//在输出表达式中使用条件运算符

//条件运算符的运算优先级非常低,因此当一条长表达式中嵌套了子条件表达式时,通常需要在它的两端加上括号。
cout << ((grade < 60) ? "fail" : "pass";//输出pass或fail
cout << (grade < 60) ? "fail" : "pass"; //输出0或1
cout << grade < 60 ? "fail " : "pass" ; //错误,试图比较cout和60;
//等价于
cout << grade ; //小于运算符的优先级低于移位运算符
cout << 60? "fail" : "pass";
  • 位运算符

位运算符作用于整数类型的运算对象,并把运算对象看成是二进制位的集合。位运算符提供检查和设置二进制位的功能。一般来说,如果运算对象是小整型,则它的值会自动提升为较大的整数类型。关于符号位如何处理没有明确的规定,所以建议仅将位运算符用于处理无符号类型

  • 移位运算符(又叫IO运算符,满足左结合律)

<< 和 >> 运算符的内置含义是对其运算对象执行基于二进制位的移动操作,首先令左侧运算对象的内容按照右侧运算对象的要求移动指定位数,然后将经过移动的左侧运算对象的拷贝结果作为求值结果。右侧对象一定不能为负,而且值必须严格小于结果的位数,否则就会产生未定义的行为


  • sizeof 运算符

sizeof 运算符满足右结合律,其所得的值是一个size_t类型的常量表达式,运算符的运算对象有两种形式:

sizeof (type);
sizeof expr;

sizeof expr返回的是表达式结果类型的大小。sizeof并不实际计算其运算对象的值。


`
在sizeof的运算对象中解引用一个无效指针仍然是一种安全的行为,因为指针实际上并没有被真正使用。sizeof不需要真的解引用指针也能知道它所指对象的类型
通常情况下只有通过类的对象才能访问到类的成员,但是sizeof运算符无需我们提供一个具体的对象,因为要想知道类成员的大小无需真的获取该成员
sizeof 运算符的结果部分地依赖于其作用的类型

  • 对char 或者类型为char的表达式执行 sizeof 运算,结果得1

  • 对引用类型执行sizeof运算得到被引用对象所占空间得大小

  • 对指针执行sizeof运算得到指针本身所占空间的大小

  • 对解引用指针执行sizeof运算得到指针指向的对象所占空间的大小,指针不需要有效。

  • 对数组执行sizeof运算得到整个数组所占空间大小,等价于对数组中所有元素各执行了一次sizeof运算并将所得结果求和。

  • 对string对象或vector对象执行sizeof运算只返回该类型的固定部分的大小,不会计算对象中的元素占了多少空间。

  • 类型转换

如果两种类型可以相互转换,那么他们就是关联的。C++不会直接将两个不同类型的值相加,而是先根据类型转换规则设法将运算对象的类型统一后再求值,上述类型转换是自动执行的,被称作隐式转换
何时发生隐式类型转换:

  • 在大多表达式中,比int类型小的整数值首先提升为较大的整数值。
  • 在条件中,非布尔值转换成bool类型。
  • 初始化过程中,初始值转换成变量的类型;在赋值语句中,右侧运算对象转换成左侧运算对象的类型。
  • 如果算数运算或关系运算的运算对象有多种类型,需要转换成一种类型。
  • 函数调用时也会发生类型转换。

  • 算数转换

整数提升:整数提升负责把小整数类型转换成较大的整数类型。
如果某个运算符的运算对象不一致,这些运算对象将转换成同一类型。

  1. 首先执行整数提升
  2. 如果一个运算对象是无符号类型,另一个运算对象是带符号类型,而且其中的无符号类型不小于带符号类型,那么带符号的运算对象转换成无符号的。比如unsign int和int,
    如果int是负值,那么int转换成的值是无符号类型表示数值总数取模后的余数
  3. 如果带符号类型大于无符号类型,此时转换结果依赖于机器。如果无符号类型的所有值都能存在该带符号类型中,则无符号类型的运算对象转换成带符号类型。如果不能,那么带符号类型的运算对象转换成无符号类型。

  • 其他隐式类型转换

  • 数组转换成指针:在大多数用到数组的表达式中,数组自动转换成指向数组首元素的指针。
int ia[10];
int *ip = ia; //ia转换成指向数组首元素的指针。

当数组被用作decltype关键字的参数,或者作为取地址符(&)、sizeof及typeid等运算符的运算对象时,上述转换不会发生。同样的,如果用引用来初始化一个数组,上述转换也不会发生。int (&arrRef)[10] = arr(引用含有十个整数的数组)

  • 指针的转换:
    常量整数值0或者字面值nullptr能转换成任意指针类型;指向任意非常量的指针能转换成void;指向任意对象的指针能转换成const void
  • 转换成布尔类型:
    存在一种算数类型或指针类型向布尔类型自动转换的机制。如果指针或者算术类型的值为0,转换结果是false,否则转换结果是true。
  • 转换成常量:
    允许将非常量的指针转换成指向相应常量类型的指针,对于引用也是这样。
    不存在相反的转换,因为它试图删掉底层const。
int i;              
const int &j = i;   //非常量转换成const int 引用
const int *p = &i;  //非常量的地址转换成const地址
int &r = j, *q = p; //错误:不允许const转换成常量
  • 类类型定义的转换:
    类类型能定义由编译器自动执行转换, 不过编译器每次只能进行一种类类型的转换,如果同时提出多个转换请求,这些请求将拒绝。
string s, t = "a value";
while(cin >> s)  //while的条件部分把cin转换成bool值

  • 显示转换

`

posted @   Aaaa_aa  阅读(14)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 【自荐】一款简洁、开源的在线白板工具 Drawnix
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY
· Docker 太简单,K8s 太复杂?w7panel 让容器管理更轻松!
点击右上角即可分享
微信分享提示