Bootstrap

C++之波兰喜嘎嘎

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

喜嘎嘎,波兰喜嘎嘎,咦吔。

;