之前做的一个项目,需要用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++,能普通程序员所不能,筑起一条护城河才对,但我就没有,仍然每天捣鼓增删改查。总之没有技术含量。可替代性特别强。
喜嘎嘎,波兰喜嘎嘎,咦吔。