车间里有台注塑机,PLC 每秒往上位机推一个 byte 类型的状态字。
你打开数据,看到的是 0b10110010——8个二进制位,每一位代表一个报警状态。
领导问:"哪几个报警同时触发了?"
你盯着这串数字,脑子里一片空白。
这不是 PLC 的事,也不是通信协议的事。
你缺的,是位运算。
今天这节课,把算术、逻辑、位运算三类运算符一次讲清楚,工控程序里的数据处理,你会顺多了。
「上一节我们学了类型转换,掌握了隐式转换、显式转换和 Convert 类的使用方法。
今天在这个基础上,我们进一步学习运算符——数据有了,怎么算、怎么判断、怎么拆位,全靠它。」
算术运算符是最基础的一类,和数学里的加减乘除基本一样。
| 运算符 | 含义 | 示例 | 结果 |
|---|---|---|---|
+ | 加法 | 3 + 5 | 8 |
- | 减法 | 10 - 4 | 6 |
* | 乘法 | 6 * 7 | 42 |
/ | 除法 | 10 / 3 | 3(整数除法) |
% | 取余 | 10 % 3 | 1 |
「注意:两个 int 相除,结果还是 int,小数部分直接丢掉,不是四舍五入。」
工业类比:生产线上统计每班产量时,% 取余运算非常常用。
比如每箱装 24 件,生产了 100 件,100 % 24 = 4,说明最后一箱还差 20 件才装满。
比较运算符(也叫关系运算符)用来判断两个值的大小关系,结果只有两种:true 或 false。
| 运算符 | 含义 | 示例 |
|---|---|---|
| == | 等于 | temp == 85 |
!= | 不等于 | status != 0 |
> / < | 大于 / 小于 | count > 100 |
>= / <= | 大于等于 / 小于等于 | voltage <= 380 |
逻辑运算符用来组合多个条件,这在工控报警里极其常见。
| 运算符 | 含义 | 说明 |
|---|---|---|
&& | 与(AND) | 两个条件都成立才为 true |
|| | 或(OR) | 有一个成立就为 true |
! | 非(NOT) | 取反 |
工业类比:把 && 想成 PLC 梯形图里的串联触点,|| 想成并联触点,! 就是常闭触点。
⚠️ 注意区分 =(赋值)和 ==(比较)。
if (temp = 85)是赋值,不是判断,编译器会报错。这是新手最常犯的错之一。
位运算(Bitwise Operation)是直接对二进制位进行操作的运算。
在工业开发里,PLC 状态字、Modbus 寄存器、设备 I/O 状态,全都是按位打包的数据。
不懂位运算,就读不懂这些数据。
常用位运算符一览:
| 运算符 | 名称 | 作用 |
|---|---|---|
& | 按位与 | 提取指定位(掩码操作) |
| | 按位或 | 强制置位某一位 |
^ | 按位异或 | 翻转指定位 |
~ | 按位取反 | 所有位取反 |
<< | 左移 | 相当于乘以 2 的 N 次方 |
>> | 右移 | 相当于除以 2 的 N 次方 |
最常用的是 &(按位与)——用来提取某一位的状态。
工业类比:把一个 byte 想成一排 8 个开关,每个开关控制一个报警灯。
用 & 加上一个"掩码",就像拿一张只开了一个孔的纸盖上去,只看你想看的那一位。
状态字: 1011 0010 掩码: & 0000 0010 (只看第1位) 结果: 0000 0010 (第1位是1,说明该报警触发了)
「掩码(Mask):一个专门用来提取或屏蔽特定位的二进制数,工控程序里随处可见。」
C# 支持复合赋值运算符,写法更简洁,逻辑更清晰。
| 写法 | 等价于 |
|---|---|
count += 1 | count = count + 1 |
total -= loss | total = total - loss |
flags |= 0x04 | flags = flags | 0x04(置位第2位) |
flags &= ~0x04 | flags = flags & ~0x04(清除第2位) |
flags |= 0x04 和 flags &= ~0x04 这两句是工控代码里置位和清位的标准写法,记住它。
Step 1:新建控制台项目
打开 VS2026,选择 文件 > 新建 > 项目,搜索"控制台应用",选择 C# 控制台应用(.NET 10),项目名填 OperatorDemo,点击创建。
VS2026 Copilot 提示:创建完成后,Copilot 会自动识别到这是一个新项目,右侧 Copilot 面板会弹出"是否需要添加常用命名空间"的建议,直接点"接受"即可。
Step 2:打开 Program.cs,清空默认代码
VS2026 默认生成 Hello, World! 模板代码。全选删除,从第一行开始写你自己的逻辑。
Step 3:输入代码,使用 Copilot 自动补全
当你输入 // 提取设备状态字的第3位报警 这行注释后,停顿 1 秒,Copilot 会自动给出位运算的补全建议,按 Tab 接受即可。
Step 4:运行调试
按 F5 启动调试,在"输出"窗口查看打印结果。如果要单步调试,在关键行按 F9 打断点,再按 F5 运行,鼠标悬停变量名可以直接看当前值。
如果你想让 Copilot 直接帮你生成位运算的工业场景代码,可以这样写 Prompt:
用 C# 14 写一个方法,输入参数是一个 byte 类型的设备状态字, 输出每一位对应的报警名称(共8个报警,名称我会在代码里定义), 使用位运算提取每一位,注释要详细,变量名用工业语义命名。
Copilot 会直接生成带注释的完整方法,你只需要把报警名称替换成实际设备的定义。

这段代码演示了三类运算符在工业场景中的典型用法:温度超限判断、产量统计取余、以及设备状态字的位提取。
csharp// OperatorDemo - 运算符工业场景综合演示
// 运行环境:VS2026 + .NET 10 + C# 14
using System;
class OperatorDemo
{
static void Main()
{
// ===== 1. 算术运算符:产量统计 =====
int totalProduction = 1583; // 总产量(件)
int boxCapacity = 24; // 每箱容量(件)
// 计算完整箱数和散件数量
int fullBoxCount = totalProduction / boxCapacity;
int remainingParts = totalProduction % boxCapacity;
Console.WriteLine($"总产量:{totalProduction} 件");
Console.WriteLine($"完整箱数:{fullBoxCount} 箱");
Console.WriteLine($"散件数量:{remainingParts} 件");
Console.WriteLine();
// ===== 2. 比较与逻辑运算符:温度报警判断 =====
double deviceTemp = 92.5; // 当前设备温度(°C)
double alarmThreshold = 90.0; // 报警阈值(°C)
double criticalThreshold = 100.0; // 紧急停机阈值(°C)
bool isCoolingSystemOk = true; // 冷却系统是否正常
// 判断是否需要触发报警
bool isOverheat = deviceTemp > alarmThreshold;
// 判断是否需要紧急停机:温度超临界值 且 冷却系统故障
bool needEmergencyStop = deviceTemp >= criticalThreshold
&& !isCoolingSystemOk;
Console.WriteLine($"当前温度:{deviceTemp}°C");
Console.WriteLine($"超温报警:{isOverheat}");
Console.WriteLine($"紧急停机:{needEmergencyStop}");
Console.WriteLine();
// ===== 3. 位运算符:解析设备状态字 =====
// 模拟从 Modbus 寄存器读取的设备状态字(1 byte)
// 每一位含义(从低位到高位):
// Bit0 = 电机过载 Bit1 = 温度超限
// Bit2 = 压力异常 Bit3 = 急停触发
// Bit4~7 = 保留位
byte deviceStatusByte = 0b00001011; // 二进制:00001011
// 定义各报警位的掩码
const byte MASK_MOTOR_OVERLOAD = 0b00000001; // Bit0
const byte MASK_TEMP_ALARM = 0b00000010; // Bit1
const byte MASK_PRESSURE_ALARM = 0b00000100; // Bit2
const byte MASK_EMERGENCY_STOP = 0b00001000; // Bit3
// 用按位与提取每一位的状态
bool motorOverload = (deviceStatusByte & MASK_MOTOR_OVERLOAD) != 0;
bool tempAlarm = (deviceStatusByte & MASK_TEMP_ALARM) != 0;
bool pressureAlarm = (deviceStatusByte & MASK_PRESSURE_ALARM) != 0;
bool emergencyStop = (deviceStatusByte & MASK_EMERGENCY_STOP) != 0;
Console.WriteLine($"状态字原始值:0x{deviceStatusByte:X2}(二进制:{Convert.ToString(deviceStatusByte, 2).PadLeft(8, '0')})");
Console.WriteLine($"电机过载:{motorOverload}");
Console.WriteLine($"温度超限:{tempAlarm}");
Console.WriteLine($"压力异常:{pressureAlarm}");
Console.WriteLine($"急停触发:{emergencyStop}");
Console.WriteLine();
// ===== 4. 复合赋值运算符:动态置位与清位 =====
byte controlFlags = 0b00000000;
// 置位第2位(压力异常报警)
controlFlags |= MASK_PRESSURE_ALARM;
Console.WriteLine($"置位后:{Convert.ToString(controlFlags, 2).PadLeft(8, '0')}");
// 清除第2位(报警复位)
controlFlags &= unchecked((byte)~MASK_PRESSURE_ALARM);
Console.WriteLine($"清位后:{Convert.ToString(controlFlags, 2).PadLeft(8, '0')}");
}
}

运行后,控制台会依次打印产量统计结果、温度报警状态、设备状态字的每一位解析结果,以及置位/清位前后的二进制变化。
你可以把 deviceStatusByte 的值改成任意数字,观察哪些报警位被触发,这就是真实上位机解析 PLC 数据的核心逻辑。
场景: 冲压车间上位机每 500ms 读取一次压力机的状态字(ushort 类型,16位),需要判断第 4 位(模具到位信号)和第 7 位(冲压完成信号)是否同时为 1,才允许触发下一次冲压动作。
思路拆解:
& 提取这两位的状态&& 判断两个条件是否同时成立true 时,触发下一次冲压指令csharp// 冲压机状态字解析 - 判断是否允许触发下一次冲压
ushort stampingStatusWord = 0b0000000010010000; // 模拟读取的状态字
// 掩码定义(ushort 16位)
const ushort MASK_DIE_IN_PLACE = 1 << 4; // 第4位:模具到位
const ushort MASK_STAMP_COMPLETE = 1 << 7; // 第7位:冲压完成
// 提取两个信号位
bool dieInPlace = (stampingStatusWord & MASK_DIE_IN_PLACE) != 0;
bool stampComplete = (stampingStatusWord & MASK_STAMP_COMPLETE) != 0;
// 两个条件同时满足,才允许触发下一次冲压
bool allowNextStamp = dieInPlace && stampComplete;
Console.WriteLine($"模具到位:{dieInPlace},冲压完成:{stampComplete}");
Console.WriteLine($"允许触发下一次冲压:{allowNextStamp}");

运行后输出 允许触发下一次冲压:True,说明两个信号均有效,上位机可以发送下一次冲压指令。把 stampingStatusWord 改成其他值,你会看到条件不满足时输出 False,这就是真实冲压机联锁逻辑的软件侧实现。
坑1:整数除法丢精度
❌ double ratio = 7 / 2;(结果是 3.0,不是 3.5)
✅ double ratio = 7.0 / 2; 或 (double)7 / 2;
📌 原因:两个 int 相除,C# 直接做整数除法,小数部分截断,不会自动转成 double。
坑2:& 和 && 混用
❌ if (deviceTemp > 90 & isCoolingSystemOk) (单 & 是位运算,用在 bool 上虽然能跑,但语义不对,且不会短路求值)
✅ if (deviceTemp > 90 && isCoolingSystemOk) (双 && 才是逻辑与,有短路特性)
📌 原因:&& 有短路求值——左边为 false 时右边直接跳过,避免不必要的运算甚至空引用异常。
坑3:位运算结果类型提升
❌ byte result = deviceStatusByte & MASK_TEMP_ALARM;(编译报错)
✅ byte result = (byte)(deviceStatusByte & MASK_TEMP_ALARM);
📌 原因:C# 对 byte 做位运算时,会自动提升为 int 类型,结果也是 int,赋值回 byte 必须显式强转。
「学完本节,你掌握了:」
算术运算符的整数除法陷阱和取余在产量统计中的实用技巧;比较运算符与逻辑运算符的组合用法,能写出多条件的设备报警判断;位运算的核心原理,可以用掩码从 PLC 状态字里精准提取任意一位的信号;复合赋值运算符的置位与清位写法,这是工控代码里的标准套路。
运算符这关过了,后面写数据处理逻辑会顺很多。
📖 本文是《C# 工业数字化应用开发专家》系列第 012 节
上一节:【类型转换:隐式、显式与 Convert 类】
下一节:【字符串操作:拼接、格式化、插值表达式】(明天更新)
💬 你在工作中遇到过解析设备状态字的需求吗?
欢迎在评论区说说你们设备的状态字是怎么定义的,也许下一篇案例就来自你的留言。
🔔 还没关注的同学记得点击关注,系列课程持续更新,学完这 420 节,从工厂小白到工业软件开发专家,我们一起走。
#C#编程 #工业数字化 #工控软件开发 #编程入门 #制造业数字化转型
本文作者:技术老小子
本文链接:
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!