C++ SerialPort库char*赋值疑问及用户指定端口实现咨询
问题背景
参考YouTube教程使用第三方SerialPort库开发C++串口通信程序,实现消息发送与响应监听。教程中直接用char* port = "\\\\.\\COM10";初始化SerialPort对象可正常运行,但尝试让用户输入端口号,通过std::string拼接后赋值给char*时,VS Studio提示“无法从string转换为char*”错误。有两个疑问:
- 为何直接将字符串字面量赋值给
char*可行? - 如何实现让用户指定端口并赋值给
char*以初始化串口对象?
用户原始代码
#include<iostream> #include<string> #include<stdlib.h> using namespace std; #include"SerialPort.h" char output[MAX_DATA_LENGTH]; char incomingData[MAX_DATA_LENGTH]; char* port; int main() { string data; string portone; cout << "输入端口号: " << endl; cin >> portone; string porttwo = "\\\\.\\COM" + portone; port = porttwo; SerialPort arduino(port); if (arduino.isConnected()) { cout << "已连接" << endl << endl; } else { cout << "端口未占用" << endl << endl; } if (arduino.isConnected()) { cout << "输入发送内容: " << endl; cin >> data; char* charArray = new char[data.size() + 1]; copy(data.begin(), data.end(), charArray); charArray[data.size()] = '\n'; arduino.writeSerialPort(charArray, MAX_DATA_LENGTH); arduino.readSerialPort(output, MAX_DATA_LENGTH); cout << "响应内容: " << output << endl; delete[] charArray; } cout << "程序结束" << endl << endl; system("PAUSE"); return 0; }
SerialPort类实现代码
#include "SerialPort.h" SerialPort::SerialPort(char *portName) { this->connected = false; this->handler = CreateFileA(static_cast<LPCSTR>(portName), GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); if (this->handler == INVALID_HANDLE_VALUE){ if (GetLastError() == ERROR_FILE_NOT_FOUND){ printf("ERROR: 句柄未关联,原因: %s 不可用\n", portName); } else { printf("ERROR!!!"); } } else { DCB dcbSerialParameters = {0}; if (!GetCommState(this->handler, &dcbSerialParameters)) { printf("获取当前串口参数失败"); } else { dcbSerialParameters.BaudRate = CBR_9600; dcbSerialParameters.ByteSize = 8; dcbSerialParameters.StopBits = ONESTOPBIT; dcbSerialParameters.Parity = NOPARITY; dcbSerialParameters.fDtrControl = DTR_CONTROL_ENABLE; if (!SetCommState(handler, &dcbSerialParameters)) { printf("警告: 无法设置串口参数\n"); } else { this->connected = true; PurgeComm(this->handler, PURGE_RXCLEAR | PURGE_TXCLEAR); Sleep(ARDUINO_WAIT_TIME); } } } } SerialPort::~SerialPort() { if (this->connected){ this->connected = false; CloseHandle(this->handler); } } int SerialPort::readSerialPort(char *buffer, unsigned int buf_size) { DWORD bytesRead; unsigned int toRead = 0; ClearCommError(this->handler, &this->errors, &this->status); if (this->status.cbInQue > 0){ if (this->status.cbInQue > buf_size){ toRead = buf_size; } else toRead = this->status.cbInQue; } if (ReadFile(this->handler, buffer, toRead, &bytesRead, NULL)) return bytesRead; return 0; } bool SerialPort::writeSerialPort(char *buffer, unsigned int buf_size) { DWORD bytesSend; if (!WriteFile(this->handler, (void*) buffer, buf_size, &bytesSend, 0)){ ClearCommError(this->handler, &this->errors, &this->status); return false; } else return true; } bool SerialPort::isConnected() { return this->connected; }
问题解答
1. 为何字符串字面量可直接赋值给char*?
字符串字面量(比如"\\\\.\\COM10")在C中是**const char[]**类型,早期C标准允许将其隐式转换为char*(为兼容旧代码),但这是不安全的——因为字符串字面量存储在只读内存区,修改它会导致未定义行为。现代C标准(C11及以后)已经弃用了这种隐式转换,不过部分编译器(比如VS)为了兼容旧代码仍会允许,但会给出警告。
而std::string是一个C++类,它的内部存储是动态分配的,无法直接将std::string对象赋值给char*,因为两者类型不匹配,没有隐式转换规则支持这种操作。
2. 实现用户指定端口的正确方式
有两种常用的解决方案:
方案一:使用std::string的c_str()或data()方法
std::string提供了c_str()和data()方法(C++11后两者功能一致),返回指向内部字符数组的const char*指针。由于SerialPort构造函数接收的是char*,而CreateFileA实际接收的是LPCSTR(即const char*),可以用const_cast<char*>临时转换(注意:不要修改转换后的指针指向的内容)。
修改后的main函数关键部分:
int main() { string data; string portone; cout << "输入端口号: " << endl; cin >> portone; string porttwo = "\\\\.\\COM" + portone; // 使用c_str()获取const char*,并用const_cast转换为char* SerialPort arduino(const_cast<char*>(porttwo.c_str())); // ... 后续代码不变 }
方案二:修改SerialPort构造函数参数为const char*
更推荐的做法是修改SerialPort类的构造函数,将参数改为const char*,因为串口端口名不需要修改,这样更符合const正确性:
修改SerialPort.h中的构造函数声明:
class SerialPort { // ... 其他成员 public: SerialPort(const char *portName); // 改为const char* // ... 其他成员 };
然后修改SerialPort.cpp中的构造函数实现:
SerialPort::SerialPort(const char *portName) { this->connected = false; this->handler = CreateFileA(portName, // 无需static_cast,因为portName已经是const char* GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); // ... 后续代码不变 }
此时main函数中直接使用porttwo.c_str()即可:
SerialPort arduino(porttwo.c_str());
完整修改后的main函数代码
#include<iostream> #include<string> #include<stdlib.h> using namespace std; #include"SerialPort.h" char output[MAX_DATA_LENGTH]; char incomingData[MAX_DATA_LENGTH]; int main() { string data; string portone; cout << "输入端口号: " << endl; cin >> portone; string porttwo = "\\\\.\\COM" + portone; // 假设SerialPort构造函数已改为const char*参数 SerialPort arduino(porttwo.c_str()); if (arduino.isConnected()) { cout << "已连接" << endl << endl; } else { cout << "端口不可用" << endl << endl; } if (arduino.isConnected()) { cout << "输入发送内容: " << endl; cin >> data; char* charArray = new char[data.size() + 2]; // 多留一个位置存'\n'和'\0' copy(data.begin(), data.end(), charArray); charArray[data.size()] = '\n'; charArray[data.size() + 1] = '\0'; // 确保字符串以'\0'结尾 arduino.writeSerialPort(charArray, data.size() + 1); // 只发送有效长度,而非MAX_DATA_LENGTH int bytesRead = arduino.readSerialPort(output, MAX_DATA_LENGTH); output[bytesRead] = '\0'; // 手动添加结束符,避免乱码 cout << "响应内容: " << output << endl; delete[] charArray; } cout << "程序结束" << endl << endl; system("PAUSE"); return 0; }
内容的提问来源于stack exchange,提问作者CheeseGoblin44

