【c ++ primer 笔记】第4章 表达式

简介: **运算对象转换**- `小整数类型(如bool、char、short等)通常会被提升成较大的整数类型,主要是 int 型`- 一般在进行二元运算符时,即使俩个数类型不一样,只要他们可以相互准换为同一个类型就可以。- 运算符作用于类类型的运算对象时,用户可以自行定义其含义。这种做法称之为`重载运算符`

⛄️第4章 表达式

☔️4.1 基础

❄️4.1.1 基本概念

运算对象转换

  • 小整数类型(如bool、char、short等)通常会被提升成较大的整数类型,主要是 int 型
  • 一般在进行二元运算符时,即使俩个数类型不一样,只要他们可以相互准换为同一个类型就可以。
  • 运算符作用于类类型的运算对象时,用户可以自行定义其含义。这种做法称之为重载运算符

左值和右值

  • c中左值可以位于赋值语句的左侧,而右值不行
  • c ++ 表达式要么是左值,要么是右值
  • 左值:当一个对象被用作左值时,用的是对象的身份(在内存中的位置)
  • 右值:当一个对象被用作右值时,用的是对象的值(内容)

需要右值的地方可以被左值代替,但是不能把右值当成左值(也就是位置)使用。

  • 使用关键字 decltype 时,如果表达式的求值结果是左值,decltype 作用于该表达式(不是变量)得到一个引用类型。
例如
int *p;
decltype(*p)的结果是int&;
  • 运算符对于作用对象是左值还是右值会有要求,比如赋值运算符的左侧运算对象必须是左值。

❄️4.1.2 优先级与结合律

  • 左结合律:如果运算符优先级相同,按照从左向右的顺序组合运算对象。
  • 大部分二元运算符满足左结合律,赋值运算符满足右结合律。

❄️4.1.3 求值顺序

  • 优先级规定了运算对象的组合方式,但是并没有规定运算对象按照什么顺序求值。
int i = f1() * f2();
函数f1和函数f2一定会在乘法之前调用,但是到底是f1()在f2()之前调用还是,之后调用,不确定
  • 对于没有指定执行顺序的运算符,如果表达式指向并修改了同一个对象,那么将会发生错误
cout << i << ""<< ++i << end;//错误!未定义的行为,不知道先求 i 还是先求 ++i

处理复合语句

  1. 不确定优先级与结合律的情况下,强制用括号
  2. 如果改变了某个运算对象的值,在同一表达式中不要再使用该运算对象。

☔️4.2 算术运算符

算术运算符(左结合律),有三种

  1. +、- :一元正号与一元负号
  2. *、/、% :乘法、除法、求余
  3. +、- :加法、减法
  • 上面的所有运算符,都满足左结合律,意味着优先级相同,按照从左向右的顺序进行组合。
  • 整数除法的结果是向0取整
  • 求余运算的运算对象必须是整数,运算结果始终与被除数符号相同
-21 % -8 = -5
21 % -5 = 1

☔️4.3 逻辑和关系运算符

  • 逻辑运算符:!、&&、||
  • 关系运算符:<, <=, >, >=, !=, ==
  • 逻辑运算符与关系运算符的求值结果都是布尔值
  • 短路求值:逻辑与运算符和逻辑或运算符都是先求左侧运算对象的值再求右侧运算对象的值,当且仅当左侧运算对象无法确定表达式的结果时才会计算右侧运算对象的值。先左再右
  • 声明为引用类型可以避免对元素的拷贝,如下,如string特别大时可以节省大量时间。
vector<string> text;
for(const auto &s: text){
  cout << s;
}

注意:如果想要测试一个算术对象或者指针对象的真值,最直接的方法是将其作为 if 语句的条件,不要与布尔值进行比较。

if(a);//正确   只要a 是 正数,则为真
if(a == true);//错误:会将 true 先转换为 int 再比较,比较结果是不相等(只有a = 1时才为真)

☔️4.4 赋值运算符

  • 赋值运算的返回结果时它的左侧运算对象,且是一个左值。类型也就是左侧对象的类型。
  • 如果赋值运算的左右侧运算对象类型不同,则右侧运算对象将转换成左侧运算对象的类型。
  • 赋值运算符满足右结合律
int i, j;
i = j = 1;//正确,j 被赋值为 1,随后i 被赋值为 j 的值。
等价于
i = (j = 1);
  • 赋值运算优先级比较低,在条件语句中,赋值部分通常应该加上括号
  • 复合赋值运算符,复合运算符只求值一次,普通运算符求值两次。
  • 位运算也可以使用赋值运算符。
+=; -=; *=; /=; %=; <<=; >>=; &=; ^=; |=;

例如a = a + 1; 需要进行俩次运算
而  a += 1; 只需要进行一次运算

☔️4.5 递增和递减运算符

递增和递减运算符有俩种形式:

  • 前置版本:++i ,得到递增之后的值。(先 +1,在运算)
  • 后置版本:i++,得到递增之前的值。(先运算,在 +1)

前置版本将对象本身作为左值返回,后置版本将对象的原始值的副本返回。
非必须,不要使用后置版本原因有俩点:

  1. 前置版本直接返回改变了的运算对象,后置版本需要将原始值保存下来以便于返回,是一种浪费。
  2. 后置版本对于整数和指针来说影响不大,但是对于迭代器而言消耗巨大。

在一条语句中混用解引用和递增运算符

pbeg++ 等价于 *(pbeg++)

  • pbeg++,把pbeg的值加1,然后返回pbeg的初始值的副本作为其求值结果。
  • 递增运算符优先级高于解引用,所以不用加括号
  • 建议这样写,比较简约

auto pbeg = v.begin();
while(pbeg != v.end())
    cout << *pbeg++ << endl;

☔️4.6 成员访问

  • 点运算符箭头运算符都可以访问成员。
  • .运算符的优先级大于*,所以要记得加括号
  • ptr->mem等价于(*ptr).mem

☔️4.7 条件运算符

  • 条件运算符(?:),允许我们把简单的的if-else逻辑嵌入到单个表达式当中,条件运算符的格式为cond ? expr1: expr2;
  • 可以使用嵌套条件运算符
  • 条件运算符优先级比较低,一般要加括号
int final = (grade > 90) ? "high pass":(grade < 60)? "fail":"pass" 

☔️4.8 位运算符

位运算符作用于整数类型的对象,并把运算符对象看成是二进制位的集合。

运算符 功能
~ 位求反
<< 左移
>> 右移
& 位与
^ 位异或
! 位或
  • 如果运算对象是“小整型”,值会被自动提升为较大的整数类型。
  • 运算对象可以带符号,也可以不带符号。不带符号的运算结果是固定的,带符号的运算结果依赖于机器。
  • 左移操作可能会改变符号位的值,因此在c++中建议仅用位运算来处理无符号类型

移位运算符

  • 使用移位运算符,移动的位数必须严格小于结果的位数。否则产生未定义的行为。
  • >> 右移运算符处理无符号数时,相当于在左侧插入0,右侧移出边界的值舍弃
  • << 左移运算符处理无符号数时,相当于在右侧插入0,左侧移出边界的值舍弃

对于有符号位:

  • <<左移运算符,相当于在右侧插入值为0的二进制位
  • >>右移运算符:如果该运算对象是无符号类型,相当于在左侧插入值为0的二进制位。如果有符号类型,在左侧插入符号的副本或值为0的二进制位,具体看环境
移位运算符满足左结合律

cout << a << b << endl;  
((cout << a) <<b ) << endl;

☔️4.9 sizeof运算符

  • sizeof返回一条表达式或一个类型名字所占的字节数,返回值是size_t类型
  • sizeof 满足右结合律
  • sizeof并不实际计算其运算对象的值

sizeof有俩种形式:

  • sizeof (type),给出类型名
  • sizeof expr,给出表达式
Sales_data data *p;
sizeof p;          //指针所占空间大小
sizeof *p          //P所指类型的大小,例如int * p ;此时p的大小就是int 类型的大小
  • 对数组执行 sizeof 运算符得到的是整个数组所占空间的大小。不会把数组转换为指针来处理。
  • 但是对指针执行 sizeof 运算符得到的是指针类型的大小,也就是 8。
  • 对 string 或 vector 对象执行 sizeof 运算只返回该类型固定部分的大小,不会计算对象中的元素占了多少空间。
  • 可以用 sizeof 获得数组中元素的个数:
int ia[10];
// sizeof(ia)返回整个数组所占空间的大小
// sizeof(ia)/sizeof(*ia)返回数组的大小
constexpr size_t sz = sizeof(ia)/sizeof(*ia);
int arr[sz];

☔️4.10 逗号运算符

  • 按照从左向右顺序依次求值。
  • 左侧求值结果丢弃,逗号运算符结果是右侧表达式的值。
  • 在 for 循环中可以用逗号分隔两个不同的条件
for(int i=0; i!=n; i++,j++)

注意不要在判断条件那里使用逗号分隔不同的条件,那样只会返回逗号分隔的最后一个表达式的值。

☔️4.11 类型转换

在c++中,如果参加运算的俩个对象是不同类型的。则会通过类型转换把,把俩个对象统一一下类型,在进行计算

隐式类型转换

  • 比 int 类型小的整型值首先提升为较大的整数类型
  • 在条件里,把非布尔值转换成布尔值
  • 初始化中,初始值转换成变量的类型。
  • 赋值时,右侧运算对象转换成左侧类型
  • 算数运算或关系运算的运算对象有多种类型,转换成一种。
  • 函数调用时也会有转换。

❄️4.11.1 算术转换

将运算符中的运算对象,转换为最宽的类型。例如:当表达式中既有浮点数也有整数时。整数值将准换成相应的浮点类型

double c = 3 + 'a';//先将'a'提升成 int,然后把 int 转换成 double

整数提升

整数提升负责把小整数转换成为较大的整数类型。
例如:(bool, short, char等),只要他们所有的可能的值都能存在int 里,他们就会提升成 int类型

无符号类型的运算对象

  • 如果一个是无符号一个带符号。如果无符号类型不小于带符号类型(比如都是 4 字节),则带符号转换为无符号
  • 如果无符号类型小于带符号,转换结果依赖机器。尽量不使用。

❄️4.11.2 其他隐式类型转换

数组转换为指针

  • 大多数情况下数组会自动转换成指向数组首元素的指针。(decltype关键字参数、取地址符(&)、sizeof、typeid 都不会发生这种转换)

指针的转换

  • 整数0 nullptr 都能转换成任意指针类型。
  • 指向非常量的指针能转换成 void。指向所有对象的指针都能转换成 const void

转换成常量
允许将指向非常量类型的指针转换成指向相应的常量类型指针。

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

类类型定义的转换

string s = "value";//将 c 风格字符串字面值转换为 string类型
while(cin>>s);     //将 cin 转换为 bool 值

❄️4.11.3 显示转换

显示准换就是强制转换

强制转换的具体格式:

castname<type>(expression);

castname有四种:

  • static_cast
  • dynamic_cast
  • const_cast
  • reinterpret_cast

static_cast

  • 任何类型转换,只要不包含底层 const,都可以用 static_cast
double slope = static_cast<double>(j)/i; //将 j 转换成 double 以便执行浮点数除法
  • 当把较大的类型转换为较小的类型时,static_cast 很有用。这时它告诉读者和编译器:我们知道且不在乎精度损失。平时编译器会给警告,显式转换后就不警告了。

const_cast

  • const_cast 只能改变对象的底层 const。可以去掉或增加 const 性质。
  • 只有 const_cast 能改变表达式的常量属性,其他都不行。
  • cosnt_cast 常用于有函数重载的上下文中。
string& s;
const_cast <const string&> (s);// 将 s 转换为常量引用
const_cast <string&> (s);// 将 s 转换回非常量引用

reinterpret_cast

不要用它。

旧式的强制类型转换

int(a);// 函数形式的强制类型转换
(int)a;// c 语言风格的强制类型转换
  • 旧式的强制类型转换本质上采用 const_cast、static_cast 或 reinterpret_cast 的一种。
  • 旧式与新式相比没那么清晰明了,如果出现问题,追踪困难。

☔️4.12 运算符优先级(省略p147)

目录
相关文章
|
1月前
|
算法 编译器 C++
【C++11】lambda表达式
C++11 引入了 Lambda 表达式,这是一种定义匿名函数的方式,极大提升了代码的简洁性和可维护性。本文详细介绍了 Lambda 表达式的语法、捕获机制及应用场景,包括在标准算法、排序和事件回调中的使用,以及高级特性如捕获 `this` 指针和可变 Lambda 表达式。通过这些内容,读者可以全面掌握 Lambda 表达式,提升 C++ 编程技能。
73 3
|
6月前
|
编译器 C++
《Effective C++ 改善程序与设计的55个具体做法》 第一章 笔记
《Effective C++ 改善程序与设计的55个具体做法》 第一章 笔记
|
3月前
|
算法 编译器 程序员
C++ 11新特性之Lambda表达式
C++ 11新特性之Lambda表达式
20 0
|
4月前
|
C++ 容器
【C/C++笔记】迭代器
【C/C++笔记】迭代器
30 1
|
4月前
|
存储 安全 程序员
【C/C++笔记】迭代器范围
【C/C++笔记】迭代器范围
73 0
|
5月前
|
C++ Windows
FFmpeg开发笔记(三十九)给Visual Studio的C++工程集成FFmpeg
在Windows上使用Visual Studio 2022进行FFmpeg和SDL2集成开发,首先安装FFmpeg至E:\msys64\usr\local\ffmpeg,然后新建C++控制台项目。在项目属性中,添加FFmpeg和SDL2的头文件及库文件目录。接着配置链接器的附加依赖项,包括多个FFmpeg及SDL2的lib文件。在代码中引入FFmpeg的`av_log`函数输出"Hello World",编译并运行,若看到"Hello World",即表示集成成功。详细步骤可参考《FFmpeg开发实战:从零基础到短视频上线》。
241 0
FFmpeg开发笔记(三十九)给Visual Studio的C++工程集成FFmpeg
|
5月前
|
安全 编译器 C++
C++一分钟之-泛型Lambda表达式
【7月更文挑战第16天】C++14引入泛型lambda,允许lambda接受任意类型参数,如`[](auto a, auto b) { return a + b; }`。但这也带来类型推导失败、隐式转换和模板参数推导等问题。要避免这些问题,可以明确类型约束、限制隐式转换或显式指定模板参数。示例中,`safeAdd` lambda使用`static_assert`确保只对算术类型执行,展示了一种安全使用泛型lambda的方法。
73 1
|
6月前
|
算法 编译器 C++
C++一分钟之—Lambda表达式初探
【6月更文挑战第22天】C++的Lambda表达式是匿名函数的快捷方式,增强函数式编程能力。基本语法:`[capture](params) -&gt; ret_type { body }`。例如,简单的加法lambda:`[](int a, int b) { return a + b; }`。Lambda可用于捕获外部变量(值/引用),作为函数参数,如在`std::sort`中定制比较。注意点包括正确使用捕获列表、`mutable`关键字和返回类型推导。通过实践和理解这些概念,可以写出更简洁高效的C++代码。
57 13
|
6月前
|
C++
C++语言的lambda表达式
C++从函数对象到lambda表达式以及操作参数化
|
6月前
|
C++
C++一分钟之-理解C++的运算符与表达式
【6月更文挑战第18C++的运算符和表达式构成了编程的基础,涉及数学计算、逻辑判断、对象操作和内存管理。算术、关系、逻辑、位、赋值运算符各有用途,如`+`、`-`做加减,`==`、`!=`做比较。理解运算符优先级和结合律至关重要。常见错误包括优先级混淆、整数除法截断、逻辑运算符误用和位运算误解。解决策略包括明确优先级、确保浮点数除法、正确使用逻辑运算符和谨慎进行位运算。通过实例代码学习,如 `(a &gt; b) ? &quot;greater&quot; : &quot;not greater&quot;`,能够帮助更好地理解和应用这些概念。掌握这些基础知识是编写高效、清晰C++代码的关键。
45 3