[No0000F8]override和new的区别

本文详细介绍了C#中override和new关键字的区别及其应用场景,包括如何重写基类方法和隐藏基类方法,以及在派生类中如何正确使用这两个关键字。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

 override

1. override是派生类用来重写(或覆盖)基类中方法的;

2. override不能重写非虚方法和静态方法

3. override只能重写用virtual、abstract、override修饰的方法;

4. 不能使用修饰符 new、static、virtual 或 abstract 来修改 override 方法。

new

1. new是派生类用来隐藏基类中的方法的;也就是说在派生类中“看不到”基类中的方法;

2. 如果要在派生类中隐藏(不是重写)基类中的方法,而没有使用new关键字,编译时会出现一个警告,提示如果是要隐藏基类中的方法,请使用new关键字;

3. 派生类可以隐藏基类中的虚方法,也可以隐藏基类中的普通方法

4. 如果在派生类中用private来修饰new 方法,那么只在该派生类中隐藏了基类中的方法,在该派生类之外,相当于没有隐藏基类中的方法;

5. 如果在派生类中隐藏了基类中的方法,在该派生类的派生类中,将延续对该派生类对基类方法的隐藏。

using System;

class A
{
    public virtual void Func() // 注意virtual,表明这是一个虚拟函数 
    {
        Console.WriteLine("Func In A");
    }
}
class B : A // 注意B是从A类继承,所以A是父类,B是子类 
{
    public override void Func() // 注意override ,表明重新实现了虚函数 
    {
        Console.WriteLine("Func In B");
    }
}
class C : B // 注意C是从B类继承,所以B是父类,C是子类 
{
}
class D : A // 注意D是从A类继承,所以A是父类,D是子类 
{
    public new void Func() // 注意new,表明覆盖父类里的同名类,而不是重新实现 
    {
        Console.WriteLine("Func In D");
    }
}
class E : D // 注意E是从D类继承,所以D是父类,E是子类 
{

}
class F : A
{
    private new void Func() //注意new关键字前有private修饰符,故该隐藏只在F类内有效
    {
        Console.WriteLine("Func In F");
    }

    public void Func2()
    {
        Func(); //在F类内隐藏了基类的Func方法,故此处调用的private new void Func()
    }
}

class Program
{
    static void Main(string[] args)
    {
        A a; // 定义一个a这个A类的对象.这个A就是a的申明类 
        A b; // 定义一个b这个A类的对象.这个A就是b的申明类 
        A c; // 定义一个c这个A类的对象.这个A就是c的申明类 
        A d; // 定义一个d这个A类的对象.这个A就是d的申明类 
        A e; // 定义一个e这个A类的对象.这个A就是e的申明类 
        A f; // 定义一个f这个A类的对象.这个A就是f的申明类 
        a = new A(); // 实例化a对象,A是a的实例类 
        b = new B(); // 实例化b对象,B是b的实例类 
        c = new C(); // 实例化c对象,C是c的实例类 
        d = new D(); // 实例化d对象,D是d的实例类 
        e = new E(); // 实例化e对象,E是e的实例类
        f = new F(); // 实例化f对象,F是f的实例类
        Console.WriteLine("a.Func();");
        a.Func(); // 执行a.Func:1.先检查申明类A 2.检查到是虚拟方法 3.转去检查实例类A,就为本身 4.执行实例类A中的方法 5.输出结果 Func In A 
        Console.WriteLine("b.Func();");
        b.Func(); // 执行b.Func:1.先检查申明类A 2.检查到是虚拟方法 3.转去检查实例类B,有重载的 4.执行实例类B中的方法 5.输出结果 Func In B 
        Console.WriteLine("c.Func();");
        c.Func(); // 执行c.Func:1.先检查申明类A 2.检查到是虚拟方法 3.转去检查实例类C,无重载的 4.转去检查类C的父类B,有重载的 5.执行父类B中的Func方法 5.输出结果 Func In B 
        Console.WriteLine("d.Func();");
        d.Func(); // 执行d.Func:1.先检查申明类A 2.检查到是虚拟方法 3.转去检查实例类D,无重载的(这个地方要注意了,虽然D里有实现Func(),但没有使用override关键字,所以不会被认为是重载) 4.转去检查类D的父类A,就为本身 5.执行父类A中的Func方法 5.输出结果 Func In A 
        Console.WriteLine("e.Func();");
        e.Func(); // 执行e.Func:E继承D,E.Func没有重写父类中的方法,相当于执行父类D中的Func方法,输出结果 Func In A 
        Console.WriteLine("f.Func();");
        f.Func(); // 执行f.Func:F类中虽然隐藏了基类中的Func方法,但是有private修饰符,该隐藏只在F类范围内有效。执行f.Func相当于执行其基类中的Func方法,输出结果 Func In A 

        D d1 = new D();
        Console.WriteLine("d1.Func();");
        d1.Func(); // 执行D类里的Func(),输出结果 Func In D 

        E e1 = new E();
        Console.WriteLine("e1.Func();");
        e1.Func(); // 执行E类里的Func(),输出结果 Func In D

        F f1 = new F();
        Console.WriteLine("f1.Func();");
        f1.Func(); // 执行F类里的Func(),输出结果 Func In A
        Console.WriteLine("f1.Func2();");
        f1.Func2(); // 执行F类里的Func2(),输出结果 Func In F

        Console.ReadLine();
    }
}

默认情况下,C# 方法不是虚方法 -- 如果将一种方法声明为虚方法,则继承该方法的任何类都可以实现其自己的版本。若要使方法成为虚方法,必须在基类的方法声明中使用 virtual 修饰符。然后,派生类可以使用 override 关键字重写基虚方法,或使用 new 关键字隐藏基类中的虚方法。如果 override 关键字和 new 关键字均未指定,编译器将发出警告,并且派生类中的方法将隐藏基类中的方法。

为了在实践中演示上述情况,我们暂时假定公司 A 创建了一个名为 GraphicsClass 的类,您的程序使用该类。GraphicsClass 类似如下:

class GraphicsClass
{
    public virtual void DrawLine() { }
    public virtual void DrawPoint() { }
}

您的公司使用此类,并且您在添加新方法时将其用来派生自己的类:

class YourDerivedGraphicsClass : GraphicsClass
{
    public void DrawRectangle() { }
}

您在应用程序的使用过程中没有遇到任何问题,直到公司 A 发布了 GraphicsClass 的新版本,该新版本类似如下:

class GraphicsClass
{
    public virtual void DrawLine() { }
    public virtual void DrawPoint() { }
    public virtual void DrawRectangle() { }
}

现在,GraphicsClass 的新版本中包含了一个称为 DrawRectangle 的方法。最初,一切正常。新版本仍与旧版本二进制兼容 -- 即使在计算机系统中安装新类,部署的所有软件仍将继续工作。在您的派生类中,对方法 DrawRectangle 的任何现有调用将继续引用您的版本。

但是,一旦使用 GraphicsClass 的新版本重新编译应用程序,您将收到来自编译器的警告。

此警告提示您需要考虑您的 DrawRectangle 方法在应用程序中的工作方式。

如果想用您的方法重写新的基类方法,请使用 override 关键字,如下所示:

class YourDerivedGraphicsClass : GraphicsClass
{
    public override void DrawRectangle() { }
}

override 关键字可确保派生自 YourDerivedGraphicsClass 的任何对象都将使用 DrawRectangle 的派生类版本。派生自 YourDerivedGraphicsClass的对象仍可以使用 base 关键字访问 DrawRectangle 的基类版本,如下所示:

base.DrawRectangle();

 

 如果不想用您的方法重写新的基类方法,则应注意下面的事项。为避免在两种方法之间引起混淆,可以重命名您的方法。重命名方法可能很耗时且容易出错,而且在某些情况下并不实用。但是,如果您的项目相对较小,则可以使用 Visual Studio 的重构选项来重命名方法。或者,也可以通过在派生类定义中使用关键字 new 来防止出现该警告,如下所示:

class YourDerivedGraphicsClass : GraphicsClass
{
    new public void DrawRectangle() { }
}

 

上述一般调整关键字顺序为:

class YourDerivedGraphicsClass : GraphicsClass
{
    public new void DrawRectangle() { }
}

 

使用 new 关键字告诉编译器您的定义将隐藏基类中包含的定义。这是默认行为。

  new 应该还有约束作用。是指泛型类声明中任意参数类型都要有无参构造函数,当与其他约束一起使用时,new约束必须在最后指定

总结:声明的什么类,先去调用的就是哪个类的方法,override不能重写普通方法,(new可以,用于隐藏):

 

 

转载于:https://www.cnblogs.com/Chary/p/No0000F8.html

package demo1; import javax.swing.*; public class Main { public static void main(String[] args) { SwingUtilities.invokeLater(() -> { TemperatureMonitorGUI gui = new TemperatureMonitorGUI(); gui.setVisible(true); }); } } package demo1; public class ModbusRequestBuilder { public static byte[] buildReadRequest(byte slaveId, int registerAddr) { return buildRequest(slaveId, 0x03, registerAddr, 0x0001); } public static byte[] buildWriteRequest(byte slaveId, int registerAddr, int value) { return new byte[]{ slaveId, 0x06, (byte) (registerAddr >> 8), (byte) registerAddr, (byte) (value >> 8), (byte) value }; } private static byte[] buildRequest(byte slaveId, int functionCode, int registerAddr, int wordCount) { return new byte[]{ slaveId, (byte) functionCode, (byte) (registerAddr >> 8), (byte) registerAddr, (byte) (wordCount >> 8), (byte) wordCount }; } public static byte[] calculateCRC(byte[] data) { int crc = 0xFFFF; for (byte b : data) { crc ^= (b & 0xFF); for (int i = 0; i < 8; i++) { if ((crc & 0x0001) != 0) { crc >>= 1; crc ^= 0xA001; } else { crc >>= 1; } } } return new byte[]{(byte) crc, (byte) (crc >> 8)}; } public static String bytesToHex(byte[] bytes) { StringBuilder sb = new StringBuilder(); for (byte b : bytes) { sb.append(String.format("%02X ", b)); } return sb.toString().trim(); } } package demo1; public class RegisterAddress { // 测量值类 public static final int TEMP_MEASURED = 0x0000; public static final int HUMIDITY_MEASURED = 0x0001; public static final int RPM_MEASURED = 0x000D; public static final int CO2_MEASURED = 0x0012; public static final int PRESSURE_MEASURED = 0x0013; public static final int O2_MEASURED = 0x00E8; // 控制设定类 public static final int RUN_CONTROL = 0x0002; public static final int TEMP_SETPOINT = 0x0003; public static final int TIMER_SETPOINT = 0x0006; public static final int HUMIDITY_SETPOINT = 0x0019; public static final int RPM_SETPOINT = 0x0018; public static final int LIGHT_GROUP1 = 0x001A; public static final int LIGHT_GROUP2 = 0x001E; public static final int LIGHT_GROUP3 = 0x001F; public static final int CO2_SETPOINT = 0x001B; public static final int O2_SETPOINT = 0x001C; public static final int PRESSURE_SETPOINT = 0x001D; // 状态与报警类 public static final int ALARM_STATUS = 0x000A; public static final int DEVICE_STATUS = 0x000B; // 连续读取 public static final int CONTINUOUS_READ = 0x00F8; } package demo1; import com.fazecast.jSerialComm.SerialPort; import com.fazecast.jSerialComm.SerialPortDataListener; import com.fazecast.jSerialComm.SerialPortEvent; public class SerialPortDataListenerImpl implements SerialPortDataListener { private final TemperatureMonitorGUI gui; private final TemperatureController controller; private final SerialPort serialPort; private final byte[] buffer = new byte[256]; private int bufferIndex = 0; public SerialPortDataListenerImpl(SerialPort serialPort, TemperatureMonitorGUI gui, TemperatureController controller) { this.serialPort = serialPort; this.gui = gui; this.controller = controller; } @Override public int getListeningEvents() { return SerialPort.LISTENING_EVENT_DATA_AVAILABLE; } @Override public void serialEvent(SerialPortEvent event) { if (event.getEventType() == SerialPort.LISTENING_EVENT_DATA_AVAILABLE) { byte[] newData = new byte[serialPort.bytesAvailable()]; int numRead = serialPort.readBytes(newData, newData.length); System.arraycopy(newData, 0, buffer, bufferIndex, numRead); bufferIndex += numRead; processBuffer(); } } private void processBuffer() { while (bufferIndex >= 5) { // 至少一个完整的MODBUS头 int functionCode = buffer[1] & 0xFF; if ((functionCode & 0x80) != 0) { handleError(); removeProcessedBytes(5); continue; } switch (functionCode) { case 0x03: handleReadResponse(); break; case 0x06: handleWriteResponse(); break; case 0x08: handleLoopbackResponse(); break; default: removeProcessedBytes(1); // 忽略未知功能码 } } } private void handleReadResponse() { if (bufferIndex < 3) return; int byteCount = buffer[2] & 0xFF; int frameLength = 3 + byteCount + 2; // 包含CRC if (bufferIndex < frameLength) return; if (!verifyCRC(buffer, frameLength)) { gui.appendLog("❌ CRC校验失败"); removeProcessedBytes(frameLength); return; } int registerAddr = ((buffer[3] & 0xFF) << 8) | (buffer[4] & 0xFF); switch (registerAddr) { case RegisterAddress.TEMP_MEASURED: int tempValue = ((buffer[5] & 0xFF) << 8) | (buffer[6] & 0xFF); double temperature = tempValue / 10.0; controller.updateTemperature(temperature); gui.appendLog(String.format("✅ 读取温度: %.1f°C", temperature)); break; case RegisterAddress.HUMIDITY_MEASURED: int humidity = ((buffer[5] & 0xFF) << 8) | (buffer[6] & 0xFF); controller.updateHumidity(humidity); gui.appendLog(String.format("💧 读取湿度: %d%%", humidity)); break; case RegisterAddress.CONTINUOUS_READ: handleContinuousReadResponse(); break; default: gui.appendLog("📦 未知寄存器地址: " + Integer.toHexString(registerAddr)); } removeProcessedBytes(frameLength); } private void handleContinuousReadResponse() { int frameLength = 3 + 29 + 2; // 固定长度+CRC if (bufferIndex < frameLength) return; if (!verifyCRC(buffer, frameLength)) { gui.appendLog("❌ 连续读取CRC校验失败"); removeProcessedBytes(frameLength); return; } int index = 3; // 跳过MODBUS头部 int tempMeasured = ((buffer[index++] & 0xFF) << 8) | (buffer[index++] & 0xFF); int tempSetpoint = ((buffer[index++] & 0xFF) << 8) | (buffer[index++] & 0xFF); int humidityMeasured = ((buffer[index++] & 0xFF) << 8) | (buffer[index++] & 0xFF); int humiditySetpoint = ((buffer[index++] & 0xFF) << 8) | (buffer[index++] & 0xFF); controller.updateTemperature(tempMeasured / 10.0); controller.updateHumidity(humidityMeasured); gui.appendLog(String.format("🔁 连续读取:温度=%.1f°C, 设定=%.1f°C, 湿度=%d%%", tempMeasured / 10.0, tempSetpoint / 10.0, humidityMeasured)); removeProcessedBytes(frameLength); } private void handleWriteResponse() { int frameLength = 8; if (bufferIndex < frameLength) return; if (!verifyCRC(buffer, frameLength)) { gui.appendLog("❌ 写响应CRC校验失败"); removeProcessedBytes(frameLength); return; } int registerAddr = ((buffer[2] & 0xFF) << 8) | (buffer[3] & 0xFF); int value = ((buffer[4] & 0xFF) << 8) | (buffer[5] & 0xFF); gui.appendLog(String.format("✅ 寄存器写入成功: 地址=0x%04X, 值=0x%04X", registerAddr, value)); gui.setStatus("寄存器写入成功"); removeProcessedBytes(frameLength); } private void handleLoopbackResponse() { int frameLength = 8; if (bufferIndex < frameLength) return; if (!verifyCRC(buffer, frameLength)) { gui.appendLog("❌ 回路侦测CRC校验失败"); removeProcessedBytes(frameLength); return; } int subFunction = ((buffer[2] & 0xFF) << 8) | (buffer[3] & 0xFF); int data = ((buffer[4] & 0xFF) << 8) | (buffer[5] & 0xFF); gui.appendLog(String.format("🔁 回路测试成功: 子功能=0x%04X, 数据=0x%04X", subFunction, data)); gui.setStatus("回路侦测成功"); removeProcessedBytes(frameLength); } private void handleError() { if (bufferIndex < 3) return; int errorCode = buffer[2] & 0xFF; String errorMessage = "❌ 错误响应: "; switch (errorCode) { case 0x01: errorMessage += "功能码错误"; break; case 0x02: errorMessage += "寄存器地址错误"; break; case 0x03: errorMessage += "资料内容值错误"; break; case 0x04: errorMessage += "控制器无法处理"; break; case 0x09: errorMessage += "CRC校验错误"; break; case 0x0A: errorMessage += "奇偶校验错误"; break; case 0x0C: errorMessage += "接收数据低于规定长度"; break; case 0x0D: errorMessage += "接收数据超过规定长度"; break; default: errorMessage += "未知错误码: " + errorCode; } gui.appendLog(errorMessage); gui.setStatus(errorMessage); removeProcessedBytes(5); } private boolean verifyCRC(byte[] data, int length) { byte[] frameData = new byte[length - 2]; System.arraycopy(data, 0, frameData, 0, length - 2); byte[] receivedCRC = new byte[]{data[length - 2], data[length - 1]}; byte[] calculatedCRC = ModbusRequestBuilder.calculateCRC(frameData); return receivedCRC[0] == calculatedCRC[0] && receivedCRC[1] == calculatedCRC[1]; } private void removeProcessedBytes(int count) { if (count > bufferIndex) count = bufferIndex; System.arraycopy(buffer, count, buffer, 0, bufferIndex - count); bufferIndex -= count; } } package demo1; class SerialRequest { public byte[] data; public boolean isManual; public SerialRequest(byte[] data, boolean isManual) { this.data = data; this.isManual = isManual; } } package demo1; import com.fazecast.jSerialComm.SerialPort; import static demo1.ModbusRequestBuilder.buildReadRequest; import static demo1.ModbusRequestBuilder.buildWriteRequest; import java.util.concurrent.*; public class TemperatureController { private final TemperatureMonitorGUI gui; private final String portName; private SerialPort serialPort; private volatile boolean running = false; // 请求队列 private final BlockingQueue<SerialRequest> requestQueue = new LinkedBlockingQueue<>(); private ScheduledExecutorService queueExecutor; public TemperatureController(TemperatureMonitorGUI gui, String portName) { this.gui = gui; this.portName = portName; } public void start() { new Thread(this::connectSerialPort).start(); } private void connectSerialPort() { serialPort = SerialPort.getCommPort(portName); serialPort.setComPortParameters(9600, 8, SerialPort.ONE_STOP_BIT, SerialPort.NO_PARITY); serialPort.setComPortTimeouts(SerialPort.TIMEOUT_READ_SEMI_BLOCKING, 1000, 0); if (serialPort.openPort()) { System.out.println("✅ 成功打开串口"); gui.setStatus("已连接到 " + portName); serialPort.addDataListener(new SerialPortDataListenerImpl(serialPort, gui, this)); queueExecutor = Executors.newScheduledThreadPool(1); queueExecutor.submit(this::consumeRequests); queueExecutor.scheduleAtFixedRate(() -> enqueueRequest(buildReadRequest((byte) 0x01, RegisterAddress.TEMP_MEASURED), false), 0, 1, TimeUnit.SECONDS); } else { System.err.println("❌ 无法打开串口"); gui.setStatus("无法打开串口:" + portName); } } private void consumeRequests() { while (!Thread.interrupted()) { try { SerialRequest request = requestQueue.take(); sendRequest(request.data); if (request.isManual) { Thread.sleep(500); } } catch (InterruptedException e) { Thread.currentThread().interrupt(); } } } public void readRegister(byte slaveId, int registerAddr) { enqueueRequest(buildReadRequest(slaveId, registerAddr), true); } public void writeRegister(byte slaveId, int registerAddr, int value) { enqueueRequest(buildWriteRequest(slaveId, registerAddr, value), true); } public void loopbackTest(byte slaveId) { enqueueRequest(new byte[]{slaveId, 0x08, 0x00, 0x00, 0x12, 0x34}, true); } public void continuousRead(byte slaveId) { enqueueRequest(new byte[]{slaveId, 0x03, 0x00, (byte) 0xF8, 0x00, 0x14}, true); } private void enqueueRequest(byte[] data, boolean isManual) { requestQueue.offer(new SerialRequest(data, isManual)); } public void sendRequest(byte[] data) { if (serialPort == null || !serialPort.isOpen()) { System.err.println("串口未打开,无法发送数据"); return; } byte[] crc = ModbusRequestBuilder.calculateCRC(data); byte[] fullRequest = new byte[data.length + 2]; System.arraycopy(data, 0, fullRequest, 0, data.length); fullRequest[data.length] = crc[0]; // CRC低字节 fullRequest[data.length + 1] = crc[1]; // CRC高字节 serialPort.writeBytes(fullRequest, fullRequest.length); System.out.println("发送请求: " + ModbusRequestBuilder.bytesToHex(fullRequest)); } public void updateTemperature(double temperature) { gui.updateTemperature(temperature); } public void updateHumidity(int humidity) { gui.updateHumidity(humidity); } } package demo1; import javax.swing.*; import java.awt.*; import java.awt.event.*; import java.util.Objects; public class TemperatureMonitorGUI extends JFrame { private JLabel temperatureLabel; private JLabel humidityLabel; private JLabel statusLabel; private JButton connectButton; private JButton readButton; private JButton writeButton; private JButton loopbackButton; private JButton continuousReadButton; private JComboBox<String> portComboBox; private TemperatureController controller; // 新增字段 private JTextField slaveIdField = new JTextField("01"); private JComboBox<String> functionCodeCombo = new JComboBox<>(new String[]{"03H: 读寄存器", "06H: 写寄存器", "08H: 回路侦测"}); private JTextField registerAddrField = new JTextField("0000"); private JTextField valueField = new JTextField("0000"); private JTextField floatTemperatureField = new JTextField("20.5"); private JComboBox<String> inputTypeComboBox = new JComboBox<>(new String[]{"DEC", "HEX", "FLOAT"}); private JTextArea logArea = new JTextArea(5, 40); // 用于包裹动态组件的容器 private JPanel dynamicInputPanel; public TemperatureMonitorGUI() { setTitle("MODBUS RTU 温控系统"); setSize(800, 700); setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); setLayout(new BorderLayout(10, 10)); // 主面板 JPanel mainPanel = new JPanel(); mainPanel.setLayout(new BoxLayout(mainPanel, BoxLayout.Y_AXIS)); JScrollPane scrollPane = new JScrollPane(mainPanel); // 表单面板 JPanel formPanel = new JPanel(new GridLayout(0, 2, 5, 5)); // 动态输入面板(包裹 valueField floatTemperatureField) dynamicInputPanel = new JPanel(new GridLayout(0, 2, 5, 5)); // 串口选择 addFormRow(formPanel, "串口:", createPortSelection()); // 地址码输入 addFormRow(formPanel, "地址码 (HEX):", slaveIdField); // 功能码选择 addFormRow(formPanel, "功能码:", functionCodeCombo); // 寄存器地址 addFormRow(formPanel, "寄存器地址 (HEX):", registerAddrField); registerAddrField.addActionListener(e -> updateInputFieldsVisibility()); // 输入类型选择 addFormRow(formPanel, "输入类型:", inputTypeComboBox); inputTypeComboBox.addActionListener(e -> updateInputFieldsVisibility()); // 值输入框 addFormRow(dynamicInputPanel, "值 (HEX):", valueField); // 浮点温度输入框 addFormRow(dynamicInputPanel, "浮点温度 (FLOAT):", floatTemperatureField); // 将动态面板加入主表单 mainPanel.add(formPanel); mainPanel.add(dynamicInputPanel); // 按钮面板 JPanel buttonPanel = new JPanel(new GridLayout(1, 5, 5, 5)); connectButton = new JButton("连接串口"); readButton = new JButton("读取"); writeButton = new JButton("写入"); loopbackButton = new JButton("回路测试"); continuousReadButton = new JButton("连续读取"); buttonPanel.add(connectButton); buttonPanel.add(readButton); buttonPanel.add(writeButton); buttonPanel.add(loopbackButton); buttonPanel.add(continuousReadButton); // 状态栏 statusLabel = new JLabel("状态: 未连接", SwingConstants.CENTER); // 当前温度显示 temperatureLabel = new JLabel("当前温度: --°C", SwingConstants.CENTER); temperatureLabel.setFont(new Font("Arial", Font.BOLD, 24)); // 湿度显示 humidityLabel = new JLabel("当前湿度: --%", SwingConstants.CENTER); humidityLabel.setFont(new Font("Arial", Font.BOLD, 24)); // 日志区域 logArea.setEditable(false); JScrollPane logScrollPane = new JScrollPane(logArea); // 组装主界面 mainPanel.add(buttonPanel); mainPanel.add(statusLabel); mainPanel.add(temperatureLabel); mainPanel.add(humidityLabel); mainPanel.add(logScrollPane); // 初始化控制器 controller = null; // 事件绑定 connectButton.addActionListener(e -> { String selectedPort = (String) portComboBox.getSelectedItem(); if (selectedPort != null && controller == null) { controller = new TemperatureController(this, selectedPort); controller.start(); } }); readButton.addActionListener(e -> { if (controller != null) { try { int slaveId = Integer.parseInt(slaveIdField.getText(), 16); int registerAddr = Integer.parseInt(registerAddrField.getText(), 16); controller.readRegister((byte) slaveId, registerAddr); } catch (NumberFormatException ex) { JOptionPane.showMessageDialog(this, "请输入有效的十六进制数字!"); } } }); writeButton.addActionListener(e -> { if (controller != null) { try { int slaveId = Integer.parseInt(slaveIdField.getText(), 16); int registerAddr = Integer.parseInt(registerAddrField.getText(), 16); String inputType = (String) inputTypeComboBox.getSelectedItem(); if (registerAddr == RegisterAddress.TEMP_SETPOINT && "FLOAT".equals(inputType)) { // 浮点写入 float floatValue = Float.parseFloat(floatTemperatureField.getText()); int intBits = Float.floatToIntBits(floatValue); int highWord = (intBits >> 16) & 0xFFFF; int lowWord = intBits & 0xFFFF; controller.writeRegister((byte) slaveId, registerAddr, highWord); controller.writeRegister((byte) slaveId, registerAddr + 1, lowWord); appendLog(String.format("写入浮点温度 %.2f -> %04X %04X", floatValue, highWord, lowWord)); } else { int value = Integer.parseInt(valueField.getText(), 16); controller.writeRegister((byte) slaveId, registerAddr, value); appendLog("写入寄存器 0x" + Integer.toHexString(registerAddr).toUpperCase() + " 成功,值:" + valueField.getText()); } // 写入后自动读取一次 controller.readRegister((byte) slaveId, registerAddr); } catch (NumberFormatException ex) { JOptionPane.showMessageDialog(this, "请输入有效的数值!"); } } }); loopbackButton.addActionListener(e -> { if (controller != null) { try { int slaveId = Integer.parseInt(slaveIdField.getText(), 16); controller.loopbackTest((byte) slaveId); } catch (NumberFormatException ex) { JOptionPane.showMessageDialog(this, "请输入有效的十六进制数字!"); } } }); continuousReadButton.addActionListener(e -> { if (controller != null) { try { int slaveId = Integer.parseInt(slaveIdField.getText(), 16); controller.continuousRead((byte) slaveId); } catch (NumberFormatException ex) { JOptionPane.showMessageDialog(this, "请输入有效的十六进制数字!"); } } }); // 初始更新一次 updateInputFieldsVisibility(); } private void addFormRow(JPanel panel, String label, Component component) { panel.add(new JLabel(label)); panel.add(component); } private JComboBox<String> createPortSelection() { portComboBox = new JComboBox<>(); refreshPortList(); return portComboBox; } // 控制输入框显示/隐藏 private void updateInputFieldsVisibility() { try { int registerAddr = Integer.parseInt(registerAddrField.getText(), 16); boolean isFloatTemp = registerAddr == RegisterAddress.TEMP_SETPOINT && "FLOAT".equals(inputTypeComboBox.getSelectedItem()); floatTemperatureField.setVisible(isFloatTemp); valueField.setVisible(!isFloatTemp); // 修复:对动态面板调用 revalidate repaint dynamicInputPanel.revalidate(); dynamicInputPanel.repaint(); } catch (NumberFormatException ignored) {} } public void refreshPortList() { portComboBox.removeAllItems(); for (com.fazecast.jSerialComm.SerialPort port : com.fazecast.jSerialComm.SerialPort.getCommPorts()) { portComboBox.addItem(port.getSystemPortName()); } } public void updateTemperature(double temperature) { temperatureLabel.setText(String.format("当前温度: %.1f°C", temperature)); } public void updateHumidity(int humidity) { humidityLabel.setText(String.format("当前湿度: %d%%", humidity)); } public void setStatus(String status) { statusLabel.setText("状态: " + status); } public void appendLog(String message) { SwingUtilities.invokeLater(() -> logArea.append(message + "\n")); } } MODBUS_RTU通讯协议(客户) 1.字元结构 1.1 10―bit字元框(FOR ASCII) 资料格式 8. N .1 START BIT 0 1 2 3 4 5 6 7 STOP BIT 8-data bits 10-bits character fram 1.2 11―bit字元框(FOR RTU) 资料格式 8. N .2 START BIT 0 1 2 3 4 5 6 7 STOP BIT STOP BIT 8-data bits 11-bits character fram 资料格式 8. E .1 START BIT 0 1 2 3 4 5 6 7 Even Parity STOP BIT 8-data bits 11-bits character fram 资料格式 8. O. 1 START BIT 0 1 2 3 4 5 6 7 Odd Parity STOP BIT 8-data bits 11-bits character fram 波特率:1200,2400,4800,9600,19200 2.通信资料格式 RTU模式 START 保持无输入讯号≧ 20ms Adress 通信位址:8-bit 二进制位址。00H为广播地址,使用广播地址时只能接一台控制器。 Function 功能码:8-bit 二进制位址 DATA(n-1) 资料内容: n8-bit 资料内容 …… DATA 0 CRC CHK Low CRC 检查码: 由2个8-bit二进制码组成 CRC CHK High END Hi 保持无输入讯号≧20ms 2.3功能码: 03H:读出暂存器内容 06H:写入一个WORD至寄存器 08H:回路侦测 2.3.1功能码08H:回路侦测。 RTU 模式:询问格式: 回应格式: Address 01H Address 01H Function 08H Function 08H Sub-Func-Hi 00H (任意) Sub-Func-Hi 00H Sub-Func-Lo 00H (任意) Sub-Func-Lo 00H Data Content 12H (任意) Data Content 12H 34H (任意) 34H CRC Lo EDH CRC Lo EDH CRC Hi 7CH CRC Hi 7CH 2.3.2功能码03H:读出暂存器内容。 例如:从起始暂存器(位址0000)读出2个连续资料内容,假设寄存器(0000)=0100H,(0001)=00F0H。 RTU模式:询问格式: 回应格式 Address 01H Address 01H Function 03H Function 03H Data Addr 00H Number of data (count by byte) 04H 00H Number of data (count by word) 00H Content of data (Address 0000) 01H 02H 00H CRC Low C4H Content of data (Address 0001) 00H CRC Hight 0BH F0H CRC CHK Low FBH CRC CHK Hight 8BH 2.3.3功能码06H:写入一个WORD至暂存器。 例如:对驱动器位址01H,写入03E8H到参数0010H。 询问格式: 回应格式: Address 01H Address 01H Function 06H Function 06H Data Addr 00H Data Addr 00H 10H 10H Data Content 03H Data Content 03H E8H E8H CRC Low 88H CRC CHK Low 88H CRC Hight B1H CRC CHK Hight B1H 2.4.错误通讯时的额外回应: 当控制器做通信连接时,如果产生错误,此时控制器会回应错误码且将Function code AND 80H回应给主控系统,让主控系统知道有错误产生。参考错误通信时错误码的意义 RTU模式: Address 01H Function 86H Except code 02H CRC CHR Low C3H CRC CHR Hight A1H 错误码的意义: 错误码 说明 01 功能码错误; 控制器可以辨识的功能码为03H,06H,08H 02 寄存器地址错误; 资料的位址控制器无法辨识 03 资料内容值错误 资料内容值太大或者太小,不是控制器所能辨识的内容值 04 控制器无法处理; 控制器对此命令,无法执行 09 CRC或者LRC校验错误 10 奇偶校验错误 12 接收数据低于规定长度 13 接收数据超过规定长度 其中将原功能号AND 80H后返回。并在Except code中返回错误码(见右上表格) 2.5 RTU模式的检查码(CRC Check) 检查码由Address到Data content结束。其运算规则如下: 步骤1:令16-bit暂存器(CRC暂存器)=FFFFH。 步骤2:Exclusive OR第一个8-bite byte的讯息指令与低位元16-bite CRC暂存器,做Exclusive OR,将结果存入CRC暂存器内。 步骤3:右移位CRC暂存器,将0填入高位元处。 步骤4:检查右移的值,如果是0,将步骤3的新值存入CRC暂存器内,否则Exclusive OR A001H与CRC暂存器,将结果存入CRC暂存器内。 步骤5:重复步骤3~步骤4,将8-bit全部运算完成。 步骤6:重复步骤2~步骤5,取下一个8-bit的讯息指令,直到所有讯息指令运算完成。最后,得到的CRC暂存器的值,即是CRC的检查码。值得注意的是CRC的检查码必须交换放置於讯息指令的检查码中。 以下为用c语言所写的crc检查码运算范例: unsigned char *data; unsigned char length; unsigned int crc_chk(unsigned char *data,unsigned char length) { int j;unsigned int reg_crc=0xffff; while(length--){ reg_crc^=*data++; for(j=0;j<8;j++){ if(reg_crc&0x01){ reg_crc=(reg_crc>>1)^0xa001;} else{ reg_crc=reg_crc>>1; } } } return reg_crc; } 3:通讯参数修改:同时按住SET键加上移位键4秒以上,出现LK代码,按数字键使LK数值为118,再:按SET键进入通讯参数设置(SET键即功能键)。 意义 参数名 说 明 通讯地址 Ad 0-32 0:广播地址 通讯波特率 bA 2:4800 3:9600 4:19200 通讯格式 Fo 3:MODBUS-RTU(8,N,2) 4:MODBUS-RTU(8,E,1) 5:MODBUS-RTU(8,O,1)6: MODBUS-RTU(8,N,1) 4.参数位址定义: 0x0000: 温度测量值 只能读 0x0001: 湿度测量值 只能读 0x0002: 读:剩余运行时间。 写:写入0,关闭手动输出;写入1,打开手动输出。比如照明,循环。 0x0003/0x0004:温度设定值 可读可写。 0x0006: 定时设定值 可读可写。 0x000a: 读:.0 超过允许最高温度报警 .1温度上偏差报警 .2温度传感器故障 .6 温度下偏差报警 .15 总报警 写:写入1 在鸣叫静音之间切换。 0x000b: 读:0x000b.11=1运行 =0停止 写:写入1,启动或停止运行 读:0x000b.8=1 手动输出打开 =0 手动输出关闭。 0x000d: 转速测量值 只读 0x0012: CO2浓度测量值 只读 0x0013: 压力测量值 只读 0x0018: 转速设定值 可读可写 0x0019: 湿度设定值 可读可写 0x001a: 第一组光照设定值 可读可写 0x001b: CO2设定值 可读可写 0x001c: O2设定值 可读可写 0x001d: 压力设定值 可读可写 0x001e: 第二组光照设定值 可读可写 0x001f: 第三组光照设定值 可读可写 0x00e8 O2氧浓度测量值 只读 0x00f8: 连读 上位机发送:通讯地址+03H+00H+F8H+00H+14H+CRC(L)+CRC(H)。此处0x00F8必须优先。 控制器返回:通讯地址+03H+28H+运行时间高位+运行时间低位+设定时间高位+设定时间低位+测量温度高位+测量温度低位+设定温度高位+设定温度低位+测量湿度高位+测量湿度低位+设定湿度高位+设定湿度低位+第二组光照级数+第一组光照级数+第三组光照级数+第一组光照级数+测量转速高位+测量转速低位+设定转速高位+设定转速低位+测量CO2浓度高位+测量CO2浓度低位+设定CO2浓度高位+设定CO2浓度低位+测量O2浓度高位+测量O2浓度低位+设定O2浓度高位+设定O2浓度低位+测量压力高位+测量压力低位+设定压力高位+设定压力低位+备份1高位+备份1低位+备份2高位+备份2低位+备份3高位+备份3低位+备份4高位+备份4低位+ CRC(L)+CRC(H). 根据协议实现功能,将多余代码删除
07-16
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值