之前做的一个项目,需要用c++写一个服务去访问和控制硬件。这个服务是同事写的,今年年中离职了,很自然地,轮到我接手。
一、认知
我捣鼓了几天,勉强读懂一点原来的代码,并在原来基础上,做了一些修改,计有
1)增加从数据库中读取新数据;
2)修改原有功能;
3)重构部分代码
总的感觉,或者说是收获,就是
1、语法比较怪异
比如,对象的属性或方法,,要用对象->属性或对象->方法()来表示
LIMClass* lim_cls = mstId_LIMClass_map_mbs_ptr->at(master_id);
lim_cls->stop();
这样的话,如果c++支持lambda表达式(还真支持),岂不容易混淆?
然后有命名空间与其中的方法、变量或类
std::string str_master_id = std::to_string(mstId);
2、指针泛滥
也许C语言最大的特色就是指针了,而C++的指针与C基本一致。我过去的印象就是,这是一个难点。总的来说,指针指向对象的内存地址。这就相当于c#或java里的引用类型变量。
以下是一个定义指针并使用的例子:
//千万不能写成 int *ptr = 10;错误!
int value = 10;
int *ptr = &value;//定义一个名为ptr的指针,指向变量value的内存地址
std::cout << *ptr << std::endl; //输出指针指向的地址所存储的内容:结果是输出10
std::cout << ptr << std::endl; //输出指针指向的地址:结果是输出0x1234
我认为容易令人感到困惑。定义指针的时候,需要带上星号(*),但指针名称本身其实是不带星号的,如果指针前面带上星号,代表的不是指针,而是指针指向地址所存储的内容,也就是值!仔细看看上面的例子就知道了。当然,定义时,星号可以挨近指针名称,也可以挨近类型,意思是一样的。例如以下例子中,ptr1和ptr2是等价的:
int value = 10;
int* ptr1 = &value;
int *ptr2 = &value;
不过,紧挨类型的话,有时可能会让人误解:
int* ptr1, ptr2; // ptr1是int*类型,ptr2是int类型
int *ptr1, *ptr2; // ptr1和ptr2都是int*类型
令人发指的是,还可以来一个指向指针的指针(指针本身也有内存地址):
int value = 10; // 定义一个int类型的变量
int *ptr1 = &value; // ptr1指向value
int **ptr2 = &ptr1; // ptr2指向ptr1
// 输出各个值和地址
std::cout << "value: " << value << std::endl; // 输出10
std::cout << "ptr1: " << ptr1 << std::endl; // 输出value的地址
std::cout << "*ptr1: " << *ptr1 << std::endl; // 输出10
std::cout << "ptr2: " << ptr2 << std::endl; // 输出ptr1的地址
std::cout << "*ptr2: " << *ptr2 << std::endl; // 输出ptr1的值(即value的地址)
std::cout << "**ptr2: " << **ptr2 << std::endl; // 输出10
3、特别喜欢操作内存
从指针就可以看出来,C++特别喜欢直接操作内存。这可能是它所谓执行效率非常高的一个原因。但凡事有利弊,直接操作内存,一不小心就内存泄漏。
以下是一个通过静态键值实现全局共享哈希表的例子:
#include <iostream>
#include <unordered_map>
#include <string>
class GlobalData {
public:
// 静态成员变量,全局共享的哈希表
static std::unordered_map<int, std::string> sharedMap;
// 添加数据到哈希表
static void addData(int key, const std::string& value) {
sharedMap[key] = value;
}
// 从哈希表中获取数据
static std::string getData(int key) {
auto it = sharedMap.find(key);
if (it != sharedMap.end()) {
return it->second;
} else {
return "Key not found";
}
}
// 删除哈希表中的数据
static void removeData(int key) {
sharedMap.erase(key);
}
// 打印哈希表中的所有数据
static void printData() {
for (const auto& pair : sharedMap) {
std::cout << "Key: " << pair.first << ", Value: " << pair.second << std::endl;
}
}
};
// 静态成员变量的初始化
std::unordered_map<int, std::string> GlobalData::sharedMap;
int main() {
// 添加数据
GlobalData::addData(1, "Value1");
GlobalData::addData(2, "Value2");
GlobalData::addData(3, "Value3");
// 获取数据
std::cout << "Data for key 1: " << GlobalData::getData(1) << std::endl;
std::cout << "Data for key 2: " << GlobalData::getData(2) << std::endl;
std::cout << "Data for key 3: " << GlobalData::getData(3) << std::endl;
// 打印所有数据
std::cout << "All data in the shared map:" << std::endl;
GlobalData::printData();
// 删除数据
GlobalData::removeData(2);
// 再次打印所有数据
std::cout << "All data in the shared map after removal:" << std::endl;
GlobalData::printData();
return 0;
}
GlobalData 类包含一个静态成员变量 sharedMap,这是一个 std::unordered_map<int, std::string> 类型的哈希表,用于存储全局共享的数据。由于其是静态的,因此调用的地方无须构造实例,就能使用,存入或读取其中的数据。
4、访问硬件
c++这种语言从语法上看,没有什么优势,学习成本还高,不用来开发一般的应用程序,其强项在于直接与底层和硬件打交道。
比如某电源支持编写上位机软件进行访问和控制。
1)这是它的控制指令和控制后返回消息格式:
2)其中设置电源控制模式指令为:
3)相关代码
#define READ_CMD 0x03 //读指令
#define WRITE_CMD 0x06 //写指令
#define READ_ERROR 0x83 //读取错误
#define WRITE_ERROR 0x86 //写入错误
/*
* 电源控制模式:本地/远程
*/
bool MyPower::setControlMode(int mode) {
this->ctrl_mode = mode;
unsigned char cmd[] = { this->address, WRITE_CMD, 0x01, 0x13, 0x00, mode, 0x00, 0x00 };//控制指令
setCrcValue(cmd, 6);//生成CRC校验码
unsigned char recv_data[32];//用于接收返回结果
this->send_and_recv_data(cmd, recv_data, 8, 8);
return recv_data[1] != WRITE_ERROR;
}
void MyPower::send_and_recv_data(unsigned char* sendData, unsigned char* recv_data, int send_len, int recv_len) {
std::lock_guard<std::mutex> lock(this->socket_mtx);
bool is_happen_error = false;
while (true) {
if (is_happen_error) {
this->init_socket();
is_happen_error = false;
}
unsigned char sendData_[32];
memcpy(sendData_, sendData, send_len);
int bytesSent = send(this->clientSocket, reinterpret_cast<char*>(sendData_), send_len, 0);
if (bytesSent == SOCKET_ERROR) {
char out_buf[64];
strcpy(out_buf, this->ipaddr);
strcat(out_buf, " Failed to send data.");
strcat(out_buf, "\n");
printf(PREFIX "%s", out_buf);
closesocket(this->clientSocket);
WSACleanup();
is_happen_error = true;
continue;
}
unsigned char recvData[0x60];
int bytesReceived = 0;
bytesReceived = recv(this->clientSocket, reinterpret_cast<char*>(recvData), recv_len, 0);
if (bytesReceived == SOCKET_ERROR) {
int errCode = WSAGetLastError();
/*
调用 WSAGetLastError() 获取具体的错误码。常见的错误码及其含义:
10060 (WSAETIMEDOUT):接收超时,可能是服务器未能在指定时间内返回数据。
10054 (WSAECONNRESET):连接被对方重置,可能是服务器关闭了连接。
10053 (WSAECONNABORTED):连接中止,表明连接被意外关闭。
*/
std::cerr << PREFIX << "Failed to receive data. Error code: " << errCode << std::endl;
closesocket(this->clientSocket);
WSACleanup();
is_happen_error = true;
continue;
}
else {
memcpy(recv_data, recvData, recv_len);
break;
}
}
}
值得一提的是,指令cmd在定义时是一个数组,但当它作为参数传递,无论是在setCrcValue()还是在
send_and_recv_data(),都以指针的形式出现。这是因为,
**在C++中,当数组作为函数参数传递时,数组会退化为指向其第一个元素的指针。**数组名在大多数情况下会被解释为指向其第一个元素的指针。这种行为在以下情况下发生:
作为函数参数传递。
用于指针运算(如 cmd + 1)。
用于 sizeof 运算符以外的其他上下文。
二、小结
我只在读书的时候写过c++,当时c++作为一门课程,是为了讲述面向对象。在此之前,我们还学过pascal,一种面向过程语言。教材之所以选pascal,据说是因为其风格较为优美、规范,特别利于编程语言教学。当然了,据说后来的delphi语法跟pascal保持一致,不过我没有用过。那时候,我们计算机系布置的一些上机作业,比如图形学、密码学、计算方法、操作系统等,都用c++(borland c++)来编码。当时还是ms-dos时代,win95刚出来不太久,我们编码,相当一部分时间是花在写界面上,比如下拉菜单、支持鼠标点击等等。
出来工作以后,就再没有用过c++了。我曾经想学一下visual c++,但感到十分艰深晦涩,加上与工作没有关系,进展如蜗牛,形同放弃。那时java刚兴起,与高薪挂钩,但我不知道怎么想的,也没有涉足。工作第一年用的是visual foxpro,一种与数据库关系比较紧密的工具,整天拖控件,捣鼓增删改查。人啊,糊涂,懒惰。大约这就是命吧。
时间过得真快,一眨眼,20多年就过去了。其实像我这么老的,没有捞到管理岗位,至少也应该精通汇编、C、C++,能普通程序员所不能,筑起一条护城河才对,但我就没有,仍然每天捣鼓增删改查。总之没有技术含量。可替代性特别强。
喜嘎嘎,波兰喜嘎嘎,咦吔。