基础
左值和右值
左值:可以放到等式左边的值
右值:不可以放到等式左边的值
建议:复合表达式
拿不准的时候最好用括号来强制让表达式的组合关系符合程序逻辑的要求
如果改变了某个运算对象的值,在表达式的其他地方不要再使用这个运算对象
第 2 条规则有一个重要例外,当改变运算对象的子表达式本身就是另外一个子表达式的运算对象时该规则无效。例如在表达式 *++iter 中,递增运算符改变 iter 的值,iter(已经改变)的值又是解引用运算符的运算对象。此时(或类似的情况下),求值的顺序不会成为问题,因为递增运算(即改变运算对象的子表达式)必须先求值,然后才轮到解引用运算,显然这是一种很常见的用法,不会造成什么问题
算数运算符
算数运算符(左结合律)
运算符 功能 用法
+ 一元正号 + expr
- 一元负号 - expr
--------------------------------------
* 乘法 expr * expr
/ 除法 expr / expr
% 求余 expr % expr
--------------------------------------
+ 加法 expr + expr
- 减法 expr - expr
一元负号运算符对运算对象值取负后,返回其(提升后)的副本
int i = 1024 ;
int k = -i;
bool b = true ;
bool b2 = -b;
C++ 语言的早期版本允许结果为负值的商向上或向下取整,C++11 新标准则规定商一律向 0 取整(即直接切除小数部分)
根据取余运算的定义,如果 m 和 n 是整数且 n 非 0,则表达式 (m / n) * n + m % n 的求值结果与 m 相等。隐含的意思是,如果 m % n 不等于 0,则它的符号和 m 相同。除了 -m 导致溢出的特殊情况,其他时候 (-m) / n 和 m / (-n) 都等于 -(m / n), m % (-n) 等于 m % n, (-m) % n 等于 -(m % n)。
21 % 6 = 3 21 / 6 = 3
21 % 7 = 0 21 / 7 = 3
-21 % -8 = -5 -21 / -8 = 2
21 % -5 = 1 21 / -5 = -4
逻辑和关系运算符
结合律 运算符 功能 用法
右 ! 逻辑非 !expr
---------------------------------------------
左 < 小于 expr < expr
左 <= 小于等于 expr <= expr
左 > 大于 expr > expr
左 >= 大于等于 expr >= expr
---------------------------------------------
左 == 相等 expr == expr
左 != 不相等 expr != expr
---------------------------------------------
左 && 逻辑与 expr && expr
-------------------------------------------------
左 || 逻辑或 expr || expr
短路求值:逻辑与运算符和逻辑或运算符都是先求左侧运算对象的值再求右侧运算对象的值,当且仅当左侧运算对象无法确定表达式的结果时才会计算右侧运算对象的值。
将变量声明成引用可以避免对元素的拷贝
因为关系运算符的求值结果是布尔值,所以将几个关系运算符连写在一起会产生意想不到的结果
if (i < j < k)
if (i < j && j < k)
进行比较运算时除非比较的对象是布尔类型,否则不要使用布尔字面值 true 和 false 作为运算对象
赋值运算符
赋值运算符的左侧对象必须是一个可修改的左值
C++11 新标准允许使用花括号括起来的初始化列表作为赋值语句的右侧运算对象
窄化转换
从浮点数转换为整数
从取值范围大的浮点数转换为取值范围小的浮点数(在编译期可以计算并且不会溢出的表达式除外)
从整数转换为浮点数(在编译期可以计算并且转换之后值不变的表达式除外)
从取值范围大的整数转换为取值范围小的整数(在编译期可以计算并且不会溢出的表达式除外)
int k = 0 ;
k = {3.14 }
vector<int > vi;
vi = {0 , 1 , 2 , 3 , 4 , 5 , 6 , 7 , 8 , 9 };
赋值运算符满足右结合律
int ival, jval;
ival = jval = 0 ;
对于多重赋值语句中的每一个对象,它的类型或者与右边对象的类型相同、或者可由右边对象的类型转换得到
int ival, *pval;
ival = pval = 0 ;
string s1, s2;
s1 = s2 = "OK" ;
赋值运算优先级较低
int i = get_value ();
while (i != 42 ) {
i = get_value ();
}
int i;
while ((i = get_value ()) != 42 ) {
}
因为赋值运算符的优先级低于关系运算符的优先级,所以在条件语句中,赋值部分通常应该加上括号
复合赋值运算符
使用例如(a += b 和 a = a + b)唯一的区别是左侧运算对象的求值次数
递增和递减运算符
前置版本:将改变后的对象作为求值结果
后置版本:求值结果是运算对象改变之前那个值的副本
int i = 0 , j;
j = ++ i;
j = i ++;
解引用和递增运算符
auto pbeg = v.begin ();
while (pbeg != v.end () && *pbeg >= 0 )
cout << *pbeg ++ << endl;
运算对象可按任意顺序求值
while (beg != s.end () && !isspace (*beg))
*beg = toupper (*beg ++);
问题在于:赋值运算符左右两端的运算对象都用到了 beg,并且右侧的运算对象还改变了 beg 的值,所以该赋值语句是未定义的。
编译器可能按照下面的任意一种思路处理该表达式:
*beg = toupper (*beg);
*(beg + 1 ) = toupper (*beg);
成员访问运算符
点运算符:获取类对象的一个成员
箭头运算符:与点运算符有关, ptr -> mem
等价于 (*ptr).mem
条件运算符
形式 cond ? expr1 : expr2
执行过程:首先求 cond 的值,如果条件为真对 expr1 求值并返回该值,否则对 expr2 求值并返回该值。
嵌套条件运算符
finalgrade = (grade > 90 ) ? "high pass" : (grade > 60 ) ? "fail" : "pass" ;
随着条件运算符嵌套层数的增加,代码可读性急剧下降。因此,条件运算的嵌套最好别超过两到三层
cout << ((grade < 60 ) ? "fail" : "pass" );
cout << (grade < 60 ) ? "fail" : "pass" ;
cout << grade < 60 ? "fail" : "pass" ;
位运算符
运算符 功能 用法
~ 位求反 ~expr
--------------------------------------------------
<< 左移 expr1 << expr2
>> 右移 expr1 >> expr2
--------------------------------------------------
& 位与 expr & expr
--------------------------------------------------
^ 位异或 expr ^ expr
--------------------------------------------------
| 位或 expr | expr
以为运算符的优先级不高不低,介于中间;比算术运算符的优先级低,但比关系运算符、赋值运算符和条件运算符的优先级高。
sizeof 运算符
在第二种形式中,sizeof 返回的是表达式结果类型的大小。与众不同的一点是,sizeof 并不实际计算其运算对象的值
Sales_data data, *p;
sizeof (Salse_data);
sizeof data;
sizeof p;
sizeof *p;
sizeof data.revenue;
sizeof Salse_data::revenue;
对 char 或者类型为 char 的表达式执行 sizeof 运算,结果得 1
对引用类型执行 sizeof 运算得到被引用对象所占空间的大小
对指针执行 sizeof 运算得到指针本身所占空间的大小
对解引用指针执行 sizeof 运算得到指针指向的对象所占空间的大小,指针不需有效
对数组执行 sizeof 运算得到整个数组所占空间的大小,等价于对数组中所有的元素各执行一次 sizeof 运算并将所得结果求和。注意,sizeof 运算不会把数组转换成指针来处理
对 string 对象或 vector 对象执行 sizeof 运算只返回该类型固定部分的大小,不会计算对象中的元素占用了多少空间
利用 sizeof 求出数组中元素的个数
constexpr size_t sz = sizeof (ia) / sizeof (*ia);
int arr2[sz];
逗号运算符
对于逗号运算符来说,首先对左侧的表达式求值,然后将求值结果丢弃掉,逗号运算符真正的结果是右侧表达式的值。如果右侧运算符对象是左值,那么最终的求值结果也是左值。
类型转换
int a = 3.5 + 3.6 ;
cout << a << endl;
int b = 3.5 + 3 ;
cout << b << endl;
现根据类型转换规则设法将运算对象的类型统一再求值,上述的类型转换是自动执行的,无需程序员的介入,被称作隐式转换
在上面的例子中,隐式转换被设计得尽可能避免丢失精度, 3 会变为 double,再进行加法,初始化得时候 double 转为 int
何时发生隐式类型转换
在大多数表达式中,比 int 类型小的整数值首先提升为较大的整数类型
在条件中,非布尔值转换成布尔类型
初始化过程中,初始值转换成变量的类型,在赋值语句中,右侧运算对象转换成左侧运算对象的类型
如果算数运算或关系运算的运算对象有多种类型,需要转换成同一种类型
函数调用时也会发生类型转换
无符号类型的运算对象
类型匹配:无需进行进一步转换
无符号不小于带符号,那么带符号的运算对象转换成无符号
带符号大于无符号,如果无符号类型的所有值都能存在该带符号类型中,则无符号转带符号,否则带符号转无符号
其他隐式类型转换(P143)
数组转换成指针
转换成布尔类型
转换成常量
类类型定义的转换
显式转换
命名的强制类型转换具有如下形式 cast-name<type>(expression)
type 时转换的目标类型,expression 是要转换的值,如果 type 是引用类型,则结果是左值。 cast-name 是 static_cast
dynamic_cast
const_cast
reinterpret_cast
static_cast
任何具有明确定义的类型转换,只要不包含底层 const,都可以使用 static_cast
const_cast
const_cast 只能改变运算对象的底层 const
reinterpret_cast
reinterpret_cast 通常为运算对象的位模式提供较低层次上的重新解释
运算符优先级(P147)
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 【自荐】一款简洁、开源的在线白板工具 Drawnix
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY
· Docker 太简单,K8s 太复杂?w7panel 让容器管理更轻松!