C++学习:六个月从基础到就业——C++基础语法回顾:运算符与表达式

C++学习:六个月从基础到就业——C++基础语法回顾:运算符与表达式

本文是我C++学习之旅系列的第二篇技术文章,主要回顾C++中的运算符和表达式,包括优先级规则、类型转换和常见陷阱。查看完整系列目录了解更多内容。

引言

在C++中,运算符是执行特定操作的符号,而表达式则是由运算符和操作数组成的序列,用于计算值。理解运算符的语义、优先级和结合性对于编写正确的C++代码至关重要。本文将详细介绍C++中的各类运算符、表达式求值规则以及一些常见的陷阱和最佳实践。

基本运算符类型

C++中的运算符可以按功能大致分为以下几类:

算术运算符

算术运算符用于执行基本的数学运算:

运算符描述示例
+加法a + b
-减法a - b
*乘法a * b
/除法a / b
%取模(整数除法的余数)a % b
++自增++aa++
--自减--aa--

需要注意的是,自增(++)和自减(--)运算符有前缀和后缀两种形式,它们的行为略有不同:

int a = 5;
int b = ++a;  // 前缀形式:先增加a,再将a的值赋给b(结果:a=6, b=6)
int c = 5;
int d = c++;  // 后缀形式:先将c的值赋给d,再增加c(结果:c=6, d=5)

整数除法会截断小数部分:

int result = 5 / 2;  // 结果为2,而不是2.5

要获取浮点结果,至少一个操作数必须是浮点类型:

double result = 5.0 / 2;  // 结果为2.5

关系运算符

关系运算符用于比较两个值:

运算符描述示例
==等于a == b
!=不等于a != b
>大于a > b
<小于a < b
>=大于等于a >= b
<=小于等于a <= b

关系运算符的结果是布尔值(truefalse):

int a = 5, b = 10;
bool result = a < b;  // 结果为true

逻辑运算符

逻辑运算符用于组合布尔表达式:

运算符描述示例
&&逻辑与a && b
||逻辑或a || b
!逻辑非!a

逻辑运算符遵循短路求值规则:

bool a = false, b = true;
bool result1 = a && b;  // 结果为false
bool result2 = a || b;  // 结果为true// 短路求值示例
int x = 5;
if (x != 0 && 10 / x > 1) {  // 安全:如果x为0,第二部分不会被求值// ...
}

位运算符

位运算符对整数的二进制位进行操作:

运算符描述示例
&按位与a & b
|按位或a | b
^按位异或a ^ b
~按位取反~a
<<左移a << b
>>右移a >> b

位运算符的例子:

unsigned char a = 0x5A;  // 二进制:01011010
unsigned char b = 0x3F;  // 二进制:00111111unsigned char c = a & b;   // 结果:00011010 (0x1A)
unsigned char d = a | b;   // 结果:01111111 (0x7F)
unsigned char e = a ^ b;   // 结果:01100101 (0x65)
unsigned char f = ~a;      // 结果:10100101 (0xA5)
unsigned char g = a << 2;  // 结果:01101000 (0x68)
unsigned char h = a >> 2;  // 结果:00010110 (0x16)

位运算符常用于:

  • 设置、清除或检查特定位
  • 高效的乘除运算(左移右移)
  • 位掩码操作
  • 位域处理

赋值运算符

赋值运算符用于将值赋给变量:

运算符描述等价形式
=简单赋值a = b
+=加法赋值a = a + b
-=减法赋值a = a - b
*=乘法赋值a = a * b
/=除法赋值a = a / b
%=取模赋值a = a % b
&=按位与赋值a = a & b
|=按位或赋值a = a | b
^=按位异或赋值a = a ^ b
<<=左移赋值a = a << b
>>=右移赋值a = a >> b

复合赋值运算符的优势在于更简洁的代码和可能的性能优化:

int x = 10;
x += 5;  // 等价于 x = x + 5;// 在复杂表达式中尤其有用
some_long_variable_name += another_long_variable_name;

成员访问运算符

成员访问运算符用于访问类、结构或联合的成员:

运算符描述示例
.直接成员访问object.member
->通过指针的成员访问pointer->member
.*通过对象访问成员指针object.*memberPtr
->*通过指针访问成员指针pointer->*memberPtr

示例:

struct Point {int x, y;void print() { std::cout << "(" << x << ", " << y << ")" << std::endl; }
};Point p{10, 20};
p.x = 15;       // 直接访问成员变量
p.print();      // 调用成员函数Point* ptr = &p;
ptr->y = 25;    // 通过指针访问成员
ptr->print();   // 通过指针调用成员函数// 成员指针示例
void (Point::*funcPtr)() = &Point::print;
(p.*funcPtr)();     // 通过对象调用成员函数指针
(ptr->*funcPtr)();  // 通过指针调用成员函数指针

其他重要运算符

运算符描述示例
?:条件运算符(三元运算符)condition ? expr1 : expr2
,逗号运算符expr1, expr2
sizeof获取类型或变量的大小sizeof(type)sizeof expr
new动态分配内存new Type
new[]动态分配数组new Type[size]
delete释放动态分配的内存delete ptr
delete[]释放动态分配的数组delete[] array
()函数调用func(args)
[]数组下标访问array[index]
typeid获取类型信息typeid(expr)
static_cast静态类型转换static_cast<Type>(expr)
dynamic_cast动态类型转换dynamic_cast<Type>(expr)
const_cast常量转换const_cast<Type>(expr)
reinterpret_cast重解释转换reinterpret_cast<Type>(expr)
条件运算符(三元运算符)

条件运算符是唯一的三元运算符,常用于简单的条件赋值:

int max_value = (a > b) ? a : b;  // 如果a大于b,返回a,否则返回b
逗号运算符

逗号运算符按顺序求值左侧和右侧的表达式,整个表达式的结果是右侧表达式的值:

int a = 1, b = 2;
int c = (a++, b++, a + b);  // a=2, b=3, c=5

注意:不要将逗号运算符与其他上下文中的逗号分隔符混淆,如函数参数列表或变量声明中的逗号。

运算符优先级与结合性

运算符的优先级决定了复合表达式中的运算顺序,而结合性则决定了相同优先级运算符的求值顺序(从左到右或从右到左)。

以下是C++运算符按优先级从高到低的大致排序(简化版):

优先级运算符结合性
1作用域解析 ::左到右
2后缀递增/递减 a++ a--
函数调用 ()
数组下标 []
成员访问 . ->
左到右
3前缀递增/递减 ++a --a
一元运算符 + - ! ~
类型转换
new delete
sizeof
指针操作 * &
右到左
4乘除法 * / %左到右
5加减法 + -左到右
6移位 << >>左到右
7关系 < <= > >=左到右
8相等 == !=左到右
9按位与 &左到右
10按位异或 ^左到右
11按位或 |左到右
12逻辑与 &&左到右
13逻辑或 ||左到右
14条件 ?:右到左
15赋值 = += -=右到左
16逗号 ,左到右

复杂表达式示例:

int a = 5, b = 2, c = 3, d;
d = a + b * c;  // 结果:d = 5 + (2 * 3) = 5 + 6 = 11

为了提高代码可读性,建议使用括号明确表达运算顺序,尤其是在涉及多种运算符的复杂表达式中:

d = a + (b * c);  // 更加清晰

类型转换与表达式求值

隐式类型转换

当运算符作用于不同类型的操作数时,C++会执行隐式类型转换,遵循以下基本规则:

  1. 如果一个操作数是布尔型,另一个不是,则布尔型会被提升为整型
  2. 如果一个操作数是整型,另一个是浮点型,则整型会被转换为浮点型
  3. 较小的整型(如char、short)在计算前会被提升为至少int大小
  4. 在二元运算中,较低级别的类型会被提升为较高级别的类型
char a = 'A';   // ASCII值为65
int b = a + 1;  // a被提升为int,结果为66
double c = b / 2;  // 整型除法导致结果为33,然后被转换为33.0

算术类型提升规则

整数提升:小于int大小的整型(如bool、char、short)在表达式中会被提升为int或unsigned int。

一般算术转换:在涉及二元运算符的表达式中,如果操作数具有不同的类型,则会按照"转换为最宽类型"的原则进行转换。

转换等级从低到高:

bool → char → short → int → long → long long → float → double → long double

常见陷阱

整数除法

整数除法会截断小数部分,这是C++继承自C语言的行为:

int a = 5, b = 2;
int result = a / b;  // 结果为2,小数部分被截断

正确处理方法:

double result = static_cast<double>(a) / b;  // 结果为2.5
无符号数下溢

无符号整数减法导致结果小于零时,会发生环绕:

unsigned int a = 3;
unsigned int b = 5;
unsigned int result = a - b;  // 结果不是-2,而是一个很大的正数(UINT_MAX - 1)
运算符优先级混淆

位运算符与逻辑运算符的优先级常常被混淆:

int a = 0x05;  // 00000101
int b = 0x0A;  // 00001010
bool result = a & b == 0;  // 等同于 a & (b == 0),而不是 (a & b) == 0

正确做法应该使用括号明确意图:

bool result = (a & b) == 0;  // 清楚表明先执行按位与操作,再比较结果
自增/自减运算符副作用

当在同一个表达式中多次修改同一个变量时,会产生未定义行为:

int i = 5;
int j = i++ + i++;  // 未定义行为!

运算符重载

C++允许自定义类型重载大多数运算符,使其具有特殊的语义:

class Complex {
private:double real, imag;
public:Complex(double r = 0, double i = 0) : real(r), imag(i) {}// 重载+运算符(成员函数)Complex operator+(const Complex& rhs) const {return Complex(real + rhs.real, imag + rhs.imag);}// 重载输出流运算符(友元函数)friend std::ostream& operator<<(std::ostream& os, const Complex& c) {os << c.real;if (c.imag >= 0) os << "+";os << c.imag << "i";return os;}
};int main() {Complex a(1, 2), b(3, 4);Complex c = a + b;  // 使用重载的+运算符std::cout << c;     // 使用重载的<<运算符,输出"4+6i"return 0;
}

运算符重载的规则和限制:

  • 不能改变运算符的优先级、结合性或操作数数量
  • 不能重载不存在的运算符(如**等)
  • 某些运算符不能重载,如.::.*?:
  • 至少有一个操作数必须是用户定义类型

表达式的左值和右值

在C++中,表达式可以分为左值(lvalue)和右值(rvalue)。理解这个概念对于深入理解C++的语法和语义非常重要。

  • 左值:可以出现在赋值运算符左侧的表达式,通常代表存储在内存中的对象。
  • 右值:只能出现在赋值运算符右侧的表达式,通常是临时的、即将消亡的值。

例如:

int x = 5;  // x是左值,5是右值
int& ref = x;  // 可以绑定引用到左值
// int& ref2 = 5;  // 错误:不能将非常量引用绑定到右值
const int& cref = 5;  // 正确:可以将常量引用绑定到右值

C++11引入了右值引用(&&),使我们能够更好地处理右值:

void process(int& x) { std::cout << "处理左值引用" << std::endl; }
void process(int&& x) { std::cout << "处理右值引用" << std::endl; }int main() {int a = 5;process(a);    // 调用左值引用版本process(5);    // 调用右值引用版本process(a+2);  // 调用右值引用版本return 0;
}

位运算技巧

位运算是C++中的强大工具,尤其在需要性能优化的场景下。以下是一些常用技巧:

检查特定位

bool isBitSet(int value, int position) {return (value & (1 << position)) != 0;
}

设置特定位

int setBit(int value, int position) {return value | (1 << position);
}

清除特定位

int clearBit(int value, int position) {return value & ~(1 << position);
}

切换特定位

int toggleBit(int value, int position) {return value ^ (1 << position);
}

高效乘除

int multiplyByTwo(int value) {return value << 1;  // 等同于value * 2
}int divideByTwo(int value) {return value >> 1;  // 等同于value / 2(对于正数)
}

检查奇偶性

bool isEven(int value) {return (value & 1) == 0;
}

实际应用示例

位域与按位操作

下面的例子展示了如何使用位运算操作FLAG:

// 使用枚举定义标志位
enum Flags {READ = 1 << 0,    // 0001WRITE = 1 << 1,   // 0010EXECUTE = 1 << 2, // 0100HIDDEN = 1 << 3   // 1000
};// 设置权限
int permissions = 0;
permissions |= READ | WRITE;  // 添加读写权限// 检查权限
bool canRead = (permissions & READ) != 0;
bool canExecute = (permissions & EXECUTE) != 0;// 删除权限
permissions &= ~WRITE;  // 移除写权限// 切换权限
permissions ^= HIDDEN;  // 如果隐藏,则显示;如果显示,则隐藏

优化条件语句

使用三元运算符可以简化一些条件语句:

// 传统if-else
int max;
if (a > b) {max = a;
} else {max = b;
}// 使用三元运算符
int max = (a > b) ? a : b;

复合赋值与运算符优先级

理解复合赋值和运算符优先级对于编写简洁、正确的代码至关重要:

int a = 5, b = 3, c = 2;// 错误的理解
a = b + c;  // a = 5
a += b + c;  // 很多人误以为等同于 a = a + b + c = 5 + 3 + 2 = 10// 正确的理解
// a += b + c 等同于 a = a + (b + c) = 5 + (3 + 2) = 5 + 5 = 10

最佳实践与建议

  1. 使用括号明确优先级:即使你对优先级规则非常熟悉,使用括号也能让代码更易读。

  2. 避免在一行中多次修改同一变量:这可能导致未定义行为。

    // 不好的做法
    int i = 0;
    int j = ++i + ++i;  // 未定义行为// 好的做法
    int i = 0;
    ++i;
    int j = i + i;
    
  3. 小心隐式类型转换:尤其是在混合有符号和无符号类型时。

    unsigned int a = 10;
    int b = -5;
    if (b < a) {  // b被转换为无符号类型,比较结果可能不符合预期// ...
    }
    
  4. 使用显式类型转换:优先使用现代C++的类型转换运算符,而不是C风格的类型转换。

    // 不推荐
    int i = (int)3.14;// 推荐
    int i = static_cast<int>(3.14);
    
  5. 理解自增/自减运算符的行为:尤其是前缀和后缀形式的区别。

    int i = 5;
    int j = ++i;  // i=6, j=6
    int k = i++;  // i=7, k=6
    
  6. 避免过度依赖运算符优先级:即使你确定优先级正确,过于复杂的表达式也会增加代码的维护难度。

  7. 使用const防止意外修改:尤其在表达式中使用变量时。

    const int MAX_SIZE = 100;
    int array[MAX_SIZE];  // 确保MAX_SIZE不会被意外修改
    
  8. 考虑使用命名函数代替复杂表达式:这能提高代码的可读性和可维护性。

    // 不易理解的表达式
    if ((x & (x-1)) == 0 && x != 0) {// ...
    }// 更易理解的命名函数
    bool isPowerOfTwo(int x) {return x != 0 && (x & (x-1)) == 0;
    }if (isPowerOfTwo(x)) {// ...
    }
    

总结

本文回顾了C++中的运算符和表达式,包括各类运算符的使用、优先级规则、类型转换和常见陷阱。正确理解和使用这些基本概念,对于编写高效、无错的C++代码至关重要。在后续的文章中,我们将继续探索C++的其他基础特性,包括控制流结构、函数和类的定义等。

希望这篇文章对你有所帮助。如果有任何问题或建议,欢迎在评论区留言讨论!

参考资料

  1. Bjarne Stroustrup. The C++ Programming Language (4th Edition)
  2. Scott Meyers. Effective Modern C++
  3. cppreference.com - 运算符优先级
  4. C++ Core Guidelines - 表达式与语句

查看完整系列目录了解更多内容。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/38762.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

资金管理策略思路

详细描述了完整交易策略的实现细节&#xff0c;主要包括输入参数、变量定义、趋势判断、入场与出场条件、止损与止盈设置等多个方面。 输入参数&#xff08;Input&#xff09;&#xff1a; EntryFrL (.6)&#xff1a;多头入场的前一日波动范围的倍数。 EntryFrS (.3)&#xff1…

体育直播视频源格式解析:M3U8 vs FLV

在体育直播领域&#xff0c;视频源的格式选择直接影响着直播的流畅度、画质以及兼容性。目前&#xff0c;M3U8 和 FLV 是两种最为常见的视频流格式&#xff0c;它们各有优劣&#xff0c;适用于不同的场景。本文将从技术原理、优缺点以及应用场景等方面对 M3U8 和 FLV 进行详细解…

【动态规划】下降路径最小和

跟之前不同由于可能取到最右上角值&#xff0c;则左右各加一列&#xff0c;并且由于求最小值&#xff0c;则加的列须设置为正无穷大&#xff1b; class Solution { public:int minFallingPathSum(vector<vector<int>>& matrix) {int nmatrix.size();vector<…

07_GRU模型

GRU模型 双向GRU笔记:https://blog.csdn.net/weixin_44579176/article/details/146459952 概念 GRU&#xff08;Gated Recurrent Unit&#xff09;也称为门控循环单元&#xff0c;是一种改进版的RNN。与LSTM一样能够有效捕捉长序列之间的语义关联&#xff0c;通过引入两个&qu…

VScode

由于centos停止了维护 ,后面使用ubuntu 在Ubuntu中用vscode 充当记事本的作用 替代了centos中vim的作用 后面使用vscode编辑 vscode中继续使用makefile , xshell中的cgdb进行debug (半图形写 ,半命令行debug&&运行) 官网下载地址&#xff1a;https://code.visuals…

【行驶证识别】批量咕嘎OCR识别行驶证照片复印件图片里的文字信息保存表格或改名字,基于QT和腾讯云api_ocr的实现方式

项目背景 在许多业务场景中,如物流管理、车辆租赁、保险理赔等,常常需要处理大量的行驶证照片复印件。手动录入行驶证上的文字信息,像车主姓名、车辆型号、车牌号码等,不仅效率低下,还容易出现人为错误。借助 OCR(光学字符识别)技术,能够自动识别行驶证图片中的文字信…

异步编程与流水线架构:从理论到高并发

目录 一、异步编程核心机制解析 1.1 同步与异步的本质区别 1.1.1 控制流模型 1.1.2 资源利用对比 1.2 阻塞与非阻塞的技术实现 1.2.1 阻塞I/O模型 1.2.2 非阻塞I/O模型 1.3 异步编程关键技术 1.3.1 事件循环机制 1.3.2 Future/Promise模式 1.3.3 协程&#xff08;Cor…

python-selenium 爬虫 由易到难

本质 python第三方库 selenium 控制 浏览器驱动 浏览器驱动控制浏览器 推荐 edge 浏览器驱动&#xff08;不容易遇到版本或者兼容性的问题&#xff09; 驱动下载网址&#xff1a;链接: link 1、实战1 &#xff08;1&#xff09;安装 selenium 库 pip install selenium&#…

前端OOM内存泄漏如何排查?

前言 现代前端开发中&#xff0c;随着应用的复杂性和交互性的增加&#xff0c;OOM&#xff08;Out Of Memory&#xff0c;内存不足&#xff09;问题和内存泄漏逐渐成为影响用户体验和应用性能的关键挑战。排查和解决这些问题需要开发人员具备良好的调试技巧和优化策略。 造成…

C++20:玩转 string 的 starts_with 和 ends_with

文章目录 一、背景与动机二、string::starts_with 和 string::ends_with&#xff08;一&#xff09;语法与功能&#xff08;二&#xff09;使用示例1\. 判断字符串开头2\. 判断字符串结尾 &#xff08;三&#xff09;优势 三、string_view::starts_with 和 string_view::ends_w…

Redis、Memcached应用场景对比

环境 Redis官方网站&#xff1a; Redis - The Real-time Data Platform Redis社区版本下载地址&#xff1a;Install Redis | Docs Memcached官方网站&#xff1a;memcached - a distributed memory object caching system Memcached下载地址&#xff1a;memcached - a dis…

【MySQL】日志

目录 基本概念错误日志二进制日志查询日记慢查询日志 基本概念 日志&#xff08;Log&#xff09;是系统、软件或设备在运行过程中对发生的事件、操作或状态变化所做的记录。这些记录通常包含时间戳、事件类型、相关数据等信息&#xff0c;用于跟踪运行过程、排查故障、审计操作…

ArkUI-List组件

列表是一个复杂的容器&#xff0c;当列表项达到一定数量&#xff0c;使得列表内容超出其范围的时候&#xff0c;就会自动变为可以滚动。列表适合用来展现同类数据类型。 List组件支持使用&#xff0c;条件渲染&#xff0c;循环渲染&#xff0c;懒加载等渲染控制方式生成子组件…

Word限定仅搜索中文或英文引号

在Word中&#xff0c;按下CtrlF键&#xff0c;左侧会弹出导航搜索栏&#xff1b; 点击放大镜旁边的下拉栏&#xff0c;选择高级查找 在查找内容处输入英文状态下的"&#xff0c;然后选择更多->使用通配符&#xff0c;就可以仅查找英文状态下的" 同理&#xff…

智能飞鸟监测 守护高压线安全

飞鸟检测新纪元&#xff1a;视觉分析技术的革新应用 在现代化社会中&#xff0c;飞鸟检测成为了多个领域不可忽视的重要环节。无论是高压线下的安全监测、工厂内的生产秩序维护&#xff0c;还是农业区的作物保护&#xff0c;飞鸟检测都扮演着至关重要的角色。传统的人工检测方…

React初学分享 事件绑定 组价通信 useState useEffect

React初学 React介绍快速搭建React项目JSXJSX的本质优势&#xff1a;JSX中使用JS表达式JSX中的列表渲染JSX实现简单条件渲染JSX实现复杂条件渲染 React中的事件绑定React基础事件绑定传递自定义参数同时传递事件对象和自定义参数 React中的组件useState修改状态的规则状态不可变…

【实战】deepseek数据分类用户评论数据

在平时的工作中&#xff0c;我们会遇到数据分类的情况&#xff0c;比如将一些文本划分为各个标签。如果人工分类这块的工作量将是非常大&#xff0c;而且分类数据的准确性也不高。我们需要用到一些工具来实现。提高效率的同时也提高准确率。 1.示例数据 用户ID 时间戳 评论场…

git tag以及git

git tag 以及git 一、先说收获吧 1. git bash 在windows上 类似于linux的bash提供的shell命令行窗口&#xff0c;可以执行很多linux命令&#xff0c;cd pwd ls vim cat touch mkdir&#xff0c;还可以用正则匹配查看标签。相当于在windows上装了一个小的linux。git init myproj…

[动手学习深度学习]28. 批量归一化

当前所有的深度学习网络&#xff0c;或多或少都用了批归一化操作 批归一化的思想不新&#xff0c;但是这个特定的层是16年左右出现的&#xff0c;在这之后&#xff0c;发现他对深度学习算法性能的提升非常有效 概念理解 这是一个网络的结构&#xff1a; 当数据很深的时候&am…

AI比人脑更强,因为被植入思维模型【17】万物联系思维模型

万物联系,万物,并不孤立。 定义 万物联系思维模型是一种强调世界上所有事物都相互关联、相互影响的思维方式。它认为任何事物都不是孤立存在的,而是与周围的环境、其他事物以及整个宇宙构成一个有机的整体。这种联系不仅包括直接的因果关系,还涵盖了间接的、潜在的、动态的…