C++中的单例模式及具体应用示例
AI 摘要
本文深入探讨了C++中的单例模式及其在机器人自主导航中的应用,特别是如何通过单例模式来管理地图数据。文章详细介绍了单例模式的基本结构、优缺点以及在多线程环境中的应用,强调了其在保证数据一致性和资源管理中的重要性。
接着,文章通过一个实际的路径规划系统示例,展示了如何结合单例模式设计地图管理模块。该系统使用了广度优先搜索(BFS)算法来进行路径规划,并通过多线程实现地图更新和路径规划的并行处理。主要的代码模块包括地图管理类
MapManager
、路径规划类BFSPlanner
、以及ProjectNode
管理多线程操作。通过 CMake
构建系统,用户可以方便地配置和编译该项目。文章还提供了具体的实现代码,涵盖了地图更新、路径规划、C++单例模式的使用,以及多线程编程的技巧。通过这种方式,读者不仅能掌握 C++
单例模式的实现,还能学到如何将其应用于实际的机器人导航与路径规划系统中。最后,文章提供了如何在 Ubuntu 系统中构建和运行该示例项目的详细步骤,包括项目的编译、执行以及展示了系统的运行效果。
C++中的单例模式是一种设计模式,确保一个类只有一个实例,并提供一个全局访问点。单例模式在很多情况下非常有用,例如在管理全局配置、日志系统、数据库连接等场景中。
一、C++单例模式的简单介绍
–
1、单例模式的结构
单例模式通常包含以下几个核心部分:
- 私有构造函数:构造函数私有化,防止外部创建实例。
- 静态私有实例:提供一个静态成员函数来创建或返回类的唯一实例。
- 公共静态方法:提供全局访问点,通常命名为
getInstance()
,用于获取单例对象。 - 删除复制构造函数和赋值运算符:防止复制或赋值,以确保只有一个实例存在。
2、优点
- 控制实例数量:确保类只有一个实例,节省资源。
- 全局访问:提供一个全局访问点,方便管理状态或数据。
- 延迟初始化:可以在第一次使用时才创建实例(懒加载)。
3、 缺点
- 隐藏依赖关系:由于全局访问点,可能导致代码难以测试和维护,因为依赖关系不明显。
- 多线程问题:在多线程环境下需要特别小心,确保线程安全。
- 难以扩展:由于构造函数私有化,无法直接从其他类派生出子类。
4、总结
C++中的单例模式是一种非常实用的设计模式,特别是在需要共享全局状态或资源时。通过确保只有一个实例存在,单例模式提供了一种有效的管理方法。在多线程环境中使用单例模式时,务必考虑线程安全性。
5、标志性的代码结构
class Singleton {
private:// 1. 私有化构造函数,禁止外部创建Singleton() {}// 2. 禁止拷贝构造和赋值Singleton(const Singleton&) = delete;Singleton& operator=(const Singleton&) = delete;public:// 3. 静态方法,提供全局唯一的访问点static Singleton& getInstance() {static Singleton instance; // 4. 静态实例,延迟初始化,线程安全return instance;}
};
二、C++实现单例模式的具体应用示例
–
1、应用示例——总体介绍
在机器人的自主导航中,机器人经常需要访问二维栅格地图(Occupancy Grid),包括随时间更新的障碍物信息、路径规划和定位模块对地图的查询。如果不同模块频繁读取地图信息而不加管理,可能会导致数据不一致或资源竞争问题。通过单例模式实现一个全局的地图管理类,可以确保数据一致性并简化代码、节省资源。
在我设计的应用示例中包含单例模式的地图管理、地图更新线程、规划线程三部分内容,再加上主节点,完整的文件路径结构如下:
C++单例模式应用示例——BFS规划和地图更新/
│
├── include/
│ ├── bfs_planner.h
│ ├── map_manager.h
│ └── node.h
│
├── src/
│ ├── map/
│ │ └── map_manager.cpp
│ ├── node/
│ │ ├── node_map.cpp
│ │ ├── node_planner.cpp
│ │ └── node.cpp
│ └── planner/
│ └── bfs_planner.cpp
│
├── CMakeLists.txt
以下是对每个文件的概括总结:
include
文件夹
-
bfs_planner.h
- 该文件声明了
BFSPlanner
类,负责实现基于广度优先搜索(BFS)算法的路径规划。主要功能包括计算路径、打印路径、以及显示带路径和障碍物的地图。
- 该文件声明了
-
map_manager.h
- 该文件声明了
MapManager
类,用于管理地图数据。它提供获取当前地图、更新地图、打印地图及获取地图尺寸的功能,并通过互斥锁保证线程安全。
- 该文件声明了
-
node.h
- 该文件声明了
ProjectNode
类,负责管理项目的核心功能。它启动了两个线程:一个线程负责地图的更新,另一个线程负责路径规划。
- 该文件声明了
src
文件夹
map
文件夹
map_manager.cpp
- 该文件实现了
MapManager
类的方法。包括地图数据的随机更新(生成障碍物)、打印地图以及获取地图尺寸。通过线程安全的方式管理地图的更新和访问。
- 该文件实现了
node
文件夹
-
node_map.cpp
- 该文件实现了与地图更新相关的线程。它模拟地图的随机更新,并定期打印更新后的地图。
-
node_planner.cpp
- 该文件实现了路径规划的线程。它从
MapManager
获取地图数据,使用BFSPlanner
进行路径计算,并打印计算结果。它循环执行路径规划,每两秒进行一次更新。
- 该文件实现了路径规划的线程。它从
-
node.cpp
- 该文件实现了
ProjectNode
类的构造和析构方法。它初始化了BFSPlanner
实例并启动了地图更新线程和路径规划线程。
- 该文件实现了
planner
文件夹
bfs_planner.cpp
- 该文件实现了
BFSPlanner
类的路径规划功能。通过广度优先搜索算法计算从起点到终点的路径,并提供路径打印和带路径显示的地图输出。
- 该文件实现了
其他
-
CMakeLists.txt
- 该文件是 CMake 构建系统的配置文件,定义了项目的构建规则。它指定了项目名称、C++标准版本、使用的线程库等,并列出了所有源文件以生成可执行文件。
总的来说,这个应用示例项目实现了一个基于 BFS 算法的路径规划系统。它通过多个模块协同工作,MapManager
模拟并更新地图,BFSPlanner
负责路径规划,而 ProjectNode
类管理整个流程,包括启动地图更新和路径规划的线程。整个系统通过多线程来模拟地图更新并实时计算路径,展示路径规划的过程。
2、应用示例——主节点
node.h
和 node.cpp
这两个文件定义并实现了 ProjectNode
类的功能。ProjectNode
类的作用是管理路径规划和地图更新的多线程操作。
node.h :
#ifndef NODE_H
#define NODE_H#include "map_manager.h"
#include "bfs_planner.h"
#include <iostream>
#include <thread>
#include <chrono>
#include <memory> // 引入 std::shared_ptrnamespace Project {class ProjectNode {
public:ProjectNode(); ~ProjectNode(); // Threadsvoid mapThread();void plannerThread();std::thread map_thread_;std::thread planner_thread_;private:std::shared_ptr<BFSPlanner> bfsplanner_; // BFSPlanner 的共享指针
};} // namespace Project#endif // NODE_H
node.cpp :
#include "../../include/node.h"namespace Project {ProjectNode::ProjectNode() {bfsplanner_.reset(new BFSPlanner()); // 使用 reset() 创建 BFSPlanner 实例map_thread_ = std::thread(&ProjectNode::mapThread, this);planner_thread_ = std::thread(&ProjectNode::plannerThread,this);
}
ProjectNode::~ProjectNode() {map_thread_.join();planner_thread_.join();
}} // namespace Projectint main()
{Project::ProjectNode Node;std::cout << "Starting Map Manager Simulation..." << std::endl;return 0;
}
node.h
node.h
文件定义了 ProjectNode
类的接口,主要功能是启动和管理两个线程:一个用于更新地图,另一个用于执行路径规划。
主要功能:
-
ProjectNode
构造函数:- 构造函数初始化了
bfsplanner_
作为BFSPlanner
类的共享指针。BFSPlanner
是用于路径规划的核心类。 - 同时,构造函数启动了两个线程:
map_thread_
用于地图更新,planner_thread_
用于路径规划。
- 构造函数初始化了
-
ProjectNode
析构函数:- 析构函数确保两个线程在对象销毁前都能正确地完成任务。通过调用
join()
方法等待线程结束,保证线程安全退出。
- 析构函数确保两个线程在对象销毁前都能正确地完成任务。通过调用
-
mapThread
和plannerThread
:- 这两个函数定义了每个线程的行为,但在
node.cpp
中具体实现。
- 这两个函数定义了每个线程的行为,但在
-
成员变量:
bfsplanner_
:一个std::shared_ptr<BFSPlanner>
,用于存储BFSPlanner
类的实例,这个实例负责路径规划。map_thread_
和planner_thread_
:两个std::thread
类型的成员变量,分别表示用于地图更新和路径规划的线程。
node.cpp
node.cpp
文件实现了 ProjectNode
类的构造函数和析构函数,并创建了两个线程来分别执行地图更新和路径规划任务。
主要实现:
-
ProjectNode
构造函数:bfsplanner_.reset(new BFSPlanner())
:这行代码创建了一个新的BFSPlanner
实例,并将其指针赋给bfsplanner_
。reset()
方法用来保证指针指向一个新的实例。map_thread_ = std::thread(&ProjectNode::mapThread, this)
:创建并启动地图更新线程,线程将调用ProjectNode
类的mapThread()
方法。planner_thread_ = std::thread(&ProjectNode::plannerThread, this)
:创建并启动路径规划线程,线程将调用ProjectNode
类的plannerThread()
方法。
-
ProjectNode
析构函数:- 在析构函数中,调用
map_thread_.join()
和planner_thread_.join()
等待两个线程完成任务后再退出。这保证了在ProjectNode
对象销毁之前,所有线程的执行会正确结束。
- 在析构函数中,调用
-
main
函数:main()
函数创建了一个ProjectNode
对象并启动整个模拟过程。输出 “Starting Map Manager Simulation…” 用于标识模拟开始。
mapThread
和 plannerThread
虽然这两个线程函数在 node.h
中声明了,分别在 node_map.cpp
和 node_planner.cpp
中进行具体实现。
mapThread
负责定期调用MapManager
来更新地图并打印更新后的地图。plannerThread
负责定期获取地图信息,并使用BFSPlanner
来计算路径。
这两个线程循环执行,模拟一个实时更新的环境,其中一个线程在更新地图,而另一个线程在计算路径,确保整个过程并行进行。
总的来说, node.h
:定义了 ProjectNode
类,它启动并管理两个线程(地图更新线程和路径规划线程),并包含了与路径规划和地图管理相关的必要成员。 node.cpp
:实现了 ProjectNode
的构造和析构函数,启动了两个线程,并确保它们在对象销毁时正确地退出。这两个文件的主要功能是通过多线程实现地图更新和路径规划的并行处理,从而模拟一个动态环境中的路径规划过程。
3、单例模式的地图管理模块
结合第一部分的介绍,容易写出类似于如下结构的单例模式的地图管理类MapManager,除了单例模式的必要框架,还增加了获取当前地图数据的接口函数getMap()、获取地图大小的接口函数getMapSize(),方便规划模块调用,增加了随机更新地图的接口函数updateMapRandom()、打印地图的接口函数printMap(),方便地图更新模块对地图进行维护更新(真实应用时应该根据实时的传感器信息对地图进行更新维护,这里为了简化采用了随机更新)
map_manager.h :
#ifndef MAP_MANAGER_H
#define MAP_MANAGER_H#include <vector>
#include <mutex>
#include <random>
#include <iostream>namespace Project {class MapManager {
public:// 获取单例实例static MapManager& getInstance();// 获取当前地图数据std::vector<int> getMap();// 模拟地图的更新void updateMapRandom();// 打印地图void printMap();// 获取地图尺寸std::pair<int,int> getMapSize();private:// 私有构造函数,禁止外部实例化MapManager();// 禁用拷贝构造和赋值运算符MapManager(const MapManager&) = delete;MapManager& operator=(const MapManager&) = delete;// 地图数据std::vector<int> current_map_;// 线程安全的互斥锁std::mutex map_mutex_;// 地图尺寸int MAP_WIDTH_;int MAP_HEIGHT_;
};} // namespace Project#endif // MAP_MANAGER_H
函数定义在map_manager.cpp中,如下所示:
#include "../../include/map_manager.h"namespace Project {// 静态方法:获取 MapManager 的唯一实例
MapManager& MapManager::getInstance() {static MapManager instance; // C++11 保证静态局部变量线程安全return instance;
}// 私有构造函数:初始化地图数据和尺寸
MapManager::MapManager() : MAP_WIDTH_(10), MAP_HEIGHT_(10) { // 初始化地图尺寸current_map_.resize(MAP_WIDTH_ * MAP_HEIGHT_, 0); // 初始化为 10x10 的全 0 栅格地图
}// 获取当前地图数据(加锁以确保线程安全)
std::vector<int> MapManager::getMap() {std::lock_guard<std::mutex> lock(map_mutex_);return current_map_;
}// 获取地图尺寸
std::pair<int, int> MapManager::getMapSize() {return {MAP_HEIGHT_, MAP_WIDTH_};
}// 模拟地图更新(随机生成障碍物)
void MapManager::updateMapRandom() {std::lock_guard<std::mutex> lock(map_mutex_); // 确保线程安全std::random_device rd;std::mt19937 gen(rd());std::uniform_int_distribution<> dis(1, 10); // 生成范围为 [1, 10]// 随机更新地图中的障碍物信息for (int& cell : current_map_) {// 1-7 为无障碍物点(70%),8-10 为障碍物点(30%)cell = (dis(gen) <= 7) ? 0 : 1;}std::cout << "Map updated!" << std::endl;
}// 打印地图数据(使用 ANSI 转义序列实现彩色输出)
void MapManager::printMap() {std::lock_guard<std::mutex> lock(map_mutex_); // 确保线程安全std::cout << std::endl;std::cout << std::endl;std::cout << "Current Map:" << std::endl;std::cout << std::endl;for (int i = 0; i < MAP_HEIGHT_; ++i) {for (int j = 0; j < MAP_WIDTH_; ++j) {int value = current_map_[i * MAP_WIDTH_ + j];if (value == 1) {// 红色表示障碍物std::cout << "\033[31mX \033[0m"; // 红色障碍物} else {// 绿色表示可通行区域std::cout << "\033[32m. \033[0m"; // 绿色可通行区域}}std::cout << std::endl;}std::cout << std::endl;std::cout << std::endl;
}} // namespace Project
–
map_manager.h
和 map_manager.cpp
是该项目中用于管理地图的文件。下面将详细介绍这两个文件中的程序。
map_manager.h
map_manager.h
是 MapManager
类的头文件,声明了该类的接口。
主要功能:
-
getInstance
:- 这是一个静态方法,用于获取
MapManager
类的唯一实例。它通过静态局部变量实现单例模式,确保在整个程序运行过程中只有一个MapManager
实例。
- 这是一个静态方法,用于获取
-
getMap
:- 返回当前的地图数据,类型为
std::vector<int>
,它表示一个一维数组来存储地图的信息。这个方法使用了互斥锁 (std::mutex
) 来确保在多线程环境中线程安全。
- 返回当前的地图数据,类型为
-
updateMapRandom
:- 模拟地图的更新,随机生成障碍物。更新过程通过使用
std::random_device
和std::uniform_int_distribution
随机决定地图上的每个位置是障碍物还是通路。该方法也使用互斥锁确保线程安全。
- 模拟地图的更新,随机生成障碍物。更新过程通过使用
-
printMap
:- 打印地图数据。该方法根据
current_map_
的值打印地图,使用 ANSI 转义序列来着色输出。障碍物用红色 (X
) 表示,通行区域用绿色 (.
) 表示。
- 打印地图数据。该方法根据
-
getMapSize
:- 获取地图的尺寸(高度和宽度)。该方法返回一个
std::pair<int, int>
,分别表示地图的高度和宽度。
- 获取地图的尺寸(高度和宽度)。该方法返回一个
私有成员:
-
current_map_
:- 一个
std::vector<int>
,存储当前地图的数据,大小为MAP_WIDTH_ * MAP_HEIGHT_
。
- 一个
-
map_mutex_
:- 一个
std::mutex
,用于确保对地图数据的访问是线程安全的。
- 一个
-
MAP_WIDTH_
和MAP_HEIGHT_
:- 地图的宽度和高度,默认初始化为 10。
map_manager.cpp
map_manager.cpp
是 MapManager
类的实现文件,定义了该类的方法。
主要实现:
-
getInstance
:- 使用静态局部变量
instance
实现单例模式,这样即使在多线程环境下,MapManager
类的实例也能确保唯一性和线程安全。
- 使用静态局部变量
-
MapManager
构造函数:- 构造函数初始化地图尺寸为 10x10,并将
current_map_
初始化为全 0,表示一个没有障碍物的地图。current_map_
被扩展为std::vector<int>
,其大小为MAP_WIDTH_ * MAP_HEIGHT_
。
- 构造函数初始化地图尺寸为 10x10,并将
-
getMap
:- 使用
std::lock_guard<std::mutex>
进行线程安全操作,确保对地图数据的访问不会受到其他线程干扰。返回当前地图的数据。
- 使用
-
updateMapRandom
:- 随机生成一个地图,模拟障碍物的更新。每个地图单元的值要么是 0(表示通行),要么是 1(表示障碍物)。通过使用
std::random_device
和std::mt19937
随机生成值,70% 的概率是通行区域(0),30% 的概率是障碍物(1)。每次更新后,打印 “Map updated!”。
- 随机生成一个地图,模拟障碍物的更新。每个地图单元的值要么是 0(表示通行),要么是 1(表示障碍物)。通过使用
-
printMap
:- 打印地图到控制台。使用 ANSI 转义序列打印不同颜色的字符:
X
表示障碍物,红色显示。.
表示通行区域,绿色显示。
- 打印时每一行表示地图的一行,使用
MAP_WIDTH_
和MAP_HEIGHT_
计算并显示二维地图。
- 打印地图到控制台。使用 ANSI 转义序列打印不同颜色的字符:
线程安全
所有访问和修改地图的操作都使用 std::mutex
加锁,这确保了在多线程环境下,访问地图数据时不会发生竞争条件。
总的来说MapManager
类的作用是负责管理和操作地图数据。它提供了获取地图、更新地图和打印地图的方法,并且能够确保这些操作在多线程环境下是安全的。通过 getInstance
实现单例模式,确保地图管理器在整个程序中只有一个实例,避免了重复创建和内存浪费。updateMapRandom
方法模拟了一个动态更新的地图,每次调用都会随机生成新的障碍物。
4、地图更新线程
目前的地图更新线程较简单,直接调用地图管理模块提供的随机更新接口即可,node_map.cpp
文件定义了 ProjectNode
类的 mapThread
方法,主要用于模拟读取地图数据和更新地图的过程。下
node_map.cpp:
#include "../../include/node.h"namespace Project {// 模拟模块 A:读取地图数据
void ProjectNode::mapThread() {while (true) {MapManager::getInstance().updateMapRandom();MapManager::getInstance().printMap();std::this_thread::sleep_for(std::chrono::seconds(5));}
}} // namespace Project
mapThread
方法
mapThread
是 ProjectNode
类中的一个线程执行函数,功能是周期性地更新地图并打印更新后的地图。
功能与实现:
-
循环执行:
while (true)
使得该线程不断地执行,形成一个无限循环,直到线程被外部停止。这个线程每隔一段时间(5秒)执行一次地图更新。
-
地图更新:
MapManager::getInstance().updateMapRandom()
:调用MapManager
类的updateMapRandom
方法来随机更新地图中的障碍物。此方法模拟了动态的环境,其中地图上的障碍物会在每个周期内随机变化。
-
地图打印:
MapManager::getInstance().printMap()
:在更新地图后,调用printMap
方法打印当前地图的状态。地图会被打印在控制台上,其中障碍物使用红色(X
)表示,通行区域使用绿色(.
)表示。
-
线程休眠:
std::this_thread::sleep_for(std::chrono::seconds(5))
:在每次更新地图和打印地图之后,线程会休眠 5 秒,模拟一个定时更新地图的过程。这使得地图的更新不至于过于频繁,模拟环境变化的周期性。
总的来说mapThread
方法通过一个无限循环不断更新地图,打印地图,并在每次更新之间休眠 5 秒。这模拟了一个动态变化的地图环境,可以用于测试和验证路径规划算法。在多线程程序中,mapThread
会在一个独立的线程中运行,确保地图更新过程不会阻塞主线程或其他任务的执行。
5、规划线程
规划线程plannerThread 方法通过调用 BFSPlanner 的 planPath 方法实现路径规划,定期计算从起点到终点的路径。每次路径规划之后,打印路径规划结果,并每 2 秒休眠一次。目前仅在bfs_planner.cpp中提供了BFSPlanner 一种简单的规划器,感兴趣的小伙伴可自行添加其他规划器。BFSPlanner 类 使用广度优先搜索(BFS)算法来计算路径,并能打印出路径及包含路径和障碍物的地图。通过 isValid 确保搜索过程中只访问有效且可通行的区域。
node_planner.cpp
#include "../../include/node.h"namespace Project {void ProjectNode::plannerThread() {while (true) {// 获取当前地图auto flat_map = MapManager::getInstance().getMap();std::pair<int, int> mapsize= MapManager::getInstance().getMapSize();std::vector<std::vector<int>> grid(mapsize.first, std::vector<int>(mapsize.second));// 将一维地图转换为二维地图for (int i = 0; i < mapsize.first; ++i) {for (int j = 0; j < mapsize.second; ++j) {grid[i][j] = flat_map[i * mapsize.second + j];}}std::cout << "[Path Planning Module] Calculating path..." << std::endl;// 调用 planPath() 函数规划路径auto path = bfsplanner_ -> planPath(grid, {0, 0}, {mapsize.second-1, mapsize.first-1});if (!path.empty()) {// 规划成功,打印成功日志std::cout << "[Path Planning Module] Path successfully found!" << std::endl;// 打印带路径的地图bfsplanner_ -> printMapWithPath(grid, path, {0, 0}, { mapsize.second - 1, mapsize.first - 1 });} else {// 规划失败,打印失败日志std::cout << "[Path Planning Module] Path planning failed. No path found!" << std::endl;}// 线程休眠 2 秒std::this_thread::sleep_for(std::chrono::seconds(2));}
}} // namespace Project
bfs_planner.cpp
#include "../../include/bfs_planner.h"namespace Project {// 判断给定的坐标是否在地图范围内且可通行bool BFSPlanner::isValid(const std::vector<std::vector<int>>& map, int x, int y) {return (x >= 0 && x < MAP_WIDTH_&& y >= 0 && y < MAP_HEIGHT_&& map[x][y] == 0);}// 打印路径void BFSPlanner::printPath(const std::vector<std::pair<int, int>>& path) {std::cout << "[Path] ";for (const auto& coord : path) {int x = coord.first;int y = coord.second;std::cout << "(" << x << ", " << y << ") ";}std::cout << std::endl;}// 打印包含路径和障碍物的二维地图void BFSPlanner::printMapWithPath(const std::vector<std::vector<int>>& map,const std::vector<std::pair<int, int>>& path,std::pair<int, int> start,std::pair<int, int> goal){std::cout << std::endl;std::cout << std::endl;std::cout << "Map with Path:" << std::endl;std::cout << std::endl;// 创建一个副本地图用于标记路径std::vector<std::vector<char>> displayMap(MAP_HEIGHT_, std::vector<char>(MAP_WIDTH_, ' '));// 将障碍物标记为 'X'for (int i = 0; i < MAP_HEIGHT_; ++i) {for (int j = 0; j < MAP_WIDTH_; ++j) {if (map[i][j] == 1) {displayMap[i][j] = 'X';}}}// 将路径点标记为 '.'for (const auto& coord : path) {int x = coord.first;int y = coord.second;displayMap[x][y] = '.';}// 标记起点为 'S' 和终点为 'G'displayMap[start.first][start.second] = 'S';displayMap[goal.first][goal.second] = 'G';// 打印地图for (const auto& row : displayMap) {for (const auto& cell : row) {switch (cell) {case 'X':std::cout << "\033[31m" << cell << " \033[0m"; // 红色障碍物break;case '.':std::cout << "\033[32m" << cell << " \033[0m"; // 绿色路径点break;case 'S':std::cout << "\033[33m" << cell << " \033[0m"; // 黄色起点break;case 'G':std::cout << "\033[36m" << cell << " \033[0m"; // 青色终点break;default:std::cout << " "; // 空格break;}}std::cout << std::endl;}std::cout << std::endl;std::cout << std::endl;}// 使用 BFS 计算从起点到目标点的路径std::vector<std::pair<int, int>> BFSPlanner::planPath(const std::vector<std::vector<int>>& map,std::pair<int, int> start,std::pair<int, int> goal){MAP_WIDTH_ = map[0].size();MAP_HEIGHT_ = map.size();std::vector<std::pair<int, int>> path;const std::vector<std::pair<int, int>> directions = {{0, 1}, {1, 0}, {0, -1}, {-1, 0}};std::vector<std::vector<bool>> visited(MAP_HEIGHT_, std::vector<bool>(MAP_WIDTH_, false));std::queue<std::pair<std::pair<int, int>, std::vector<std::pair<int, int>>>> q;// 初始化队列,从起点开始q.push(std::make_pair(start, std::vector<std::pair<int, int>>{start}));visited[start.first][start.second] = true;while (!q.empty()) {auto front = q.front();q.pop();auto current = front.first;auto currentPath = front.second;// 如果找到目标点,则返回路径if (current == goal) {return currentPath;}// 遍历四个方向for (const auto& direction : directions) {int dx = direction.first;int dy = direction.second;int nx = current.first + dx;int ny = current.second + dy;if (isValid(map, nx, ny) && !visited[nx][ny]) {visited[nx][ny] = true;auto newPath = currentPath;newPath.push_back(std::make_pair(nx, ny));q.push(std::make_pair(std::make_pair(nx, ny), newPath));}}}std::cout << "No path found!" << std::endl;return {};}} // namespace Project
node_planner.cpp
node_planner.cpp
文件实现了 ProjectNode
类的 plannerThread
方法,该方法用于路径规划。它主要完成了以下几个步骤:
功能与实现:
-
获取当前地图:
auto flat_map = MapManager::getInstance().getMap();
- 从
MapManager
中获取当前地图数据。flat_map
是一个一维的整数数组,表示地图的各个格子(0 表示通行区域,1 表示障碍物)。
-
获取地图尺寸:
std::pair<int, int> mapsize = MapManager::getInstance().getMapSize();
- 获取地图的宽度和高度,分别用于后续二维数组的创建。
-
将一维地图转换为二维地图:
- 创建一个二维数组
grid
,大小为mapsize.first
行和mapsize.second
列。 - 通过循环将一维的
flat_map
转换为二维的grid
,每个元素表示地图中的一个格子。
- 创建一个二维数组
-
调用路径规划:
auto path = bfsplanner_->planPath(grid, {0, 0}, {mapsize.second - 1, mapsize.first - 1});
- 使用
bfsplanner_
(BFSPlanner
类的实例)调用planPath
方法进行路径规划。规划的起点是(0, 0)
,终点是地图的右下角(mapsize.second - 1, mapsize.first - 1)
。
-
打印路径规划结果:
- 如果找到路径,打印成功消息,并调用
bfsplanner_->printMapWithPath
打印带路径的地图。 - 如果没有找到路径,打印失败消息。
- 如果找到路径,打印成功消息,并调用
-
线程休眠:
std::this_thread::sleep_for(std::chrono::seconds(2));
- 每次计算路径后,线程会休眠 2 秒,再进行下一次路径规划。这模拟了一个实时的路径规划过程。
bfs_planner.cpp
bfs_planner.cpp
文件实现了 BFSPlanner
类,它负责执行广度优先搜索(BFS)来计算路径,并打印包含路径的地图。
功能与实现:
-
isValid
方法:- 用于判断某个坐标是否在地图范围内并且该位置是否为通行区域(值为 0)。
- 如果坐标有效且地图上该位置是通行的,返回
true
,否则返回false
。
-
printPath
方法:- 打印路径中的每个坐标点。
- 每个路径点格式为
(x, y)
,路径中的所有点将被打印出来,形成一条路径。
-
printMapWithPath
方法:- 打印包含路径和障碍物的地图。方法通过将障碍物标记为
'X'
、路径点标记为'.'
,起点标记为'S'
,终点标记为'G'
来显示地图。 - 使用颜色输出(通过 ANSI 转义序列),红色表示障碍物,绿色表示路径,黄色表示起点,青色表示终点。
- 打印包含路径和障碍物的地图。方法通过将障碍物标记为
-
planPath
方法:- 执行广度优先搜索(BFS)来寻找从起点到终点的路径。
- 使用队列
q
存储待处理的坐标及其路径。每个元素是一个包含坐标和路径的二元组。 - 从起点开始,探索四个方向(上、下、左、右),将有效的且未被访问过的坐标加入队列。
- 如果找到目标点,则返回从起点到目标点的路径。
- 如果队列为空且未找到目标点,则表示没有路径可达,返回空路径。
BFS 算法细节:
- 队列存储路径: 广度优先搜索使用队列来确保按层次逐步扩展路径。每次扩展一个新的节点时,都会将当前路径加入队列继续搜索。
- 路径返回: 一旦找到目标点,当前路径被返回。
- 路径输出: 找到的路径通过
printPath
和printMapWithPath
方法显示在控制台上。
总的来说, plannerThread
方法通过调用 BFSPlanner
的 planPath
方法实现路径规划,定期计算从起点到终点的路径。每次路径规划之后,打印路径规划结果,并每 2 秒休眠一次。BFSPlanner
类 使用广度优先搜索(BFS)算法来计算路径,并能打印出路径及包含路径和障碍物的地图。通过 isValid
确保搜索过程中只访问有效且可通行的区域。整个过程通过两个线程实现并行操作:一个线程负责地图更新,另一个线程负责路径规划,模拟了一个实时的动态环境和路径规划系统。
6、CMakeLists.txt 文件
CMakeLists.txt
文件是 CMake 构建系统的配置文件,指定了如何编译和链接项目中的源代码文件。下面是文件的详细内容及介绍
CMakeLists.txt :
# 最低 CMake 版本要求
cmake_minimum_required(VERSION 3.10)# 项目名称和版本
project(MapManagerProject VERSION 1.0 LANGUAGES CXX)# 设置 C++ 标准为 C++11
set(CMAKE_CXX_STANDARD 11)
set(CMAKE_CXX_STANDARD_REQUIRED True)# 启用多线程支持
find_package(Threads REQUIRED)# 包含头文件目录
include_directories(include)# 添加所有的源文件
set(SOURCESsrc/map/map_manager.cppsrc/planner/bfs_planner.cppsrc/node/node.cppsrc/node/node_map.cppsrc/node/node_planner.cpp
)# 生成可执行文件 map_simulation
add_executable(map_simulation ${SOURCES})# 链接线程库
target_link_libraries(map_simulation PRIVATE Threads::Threads)
CMakeLists.txt 文件解析
-
cmake_minimum_required(VERSION 3.10)
- 指定了 CMake 的最低版本要求为 3.10。CMake 版本 3.10 或更高版本是必需的才能正确地处理构建。
-
project(MapManagerProject VERSION 1.0 LANGUAGES CXX)
- 定义了项目的名称(
MapManagerProject
)和版本(1.0)。 LANGUAGES CXX
表示项目是使用 C++ 编写的。
- 定义了项目的名称(
-
set(CMAKE_CXX_STANDARD 11)
- 设置项目使用的 C++ 标准为 C++11。通过
CMAKE_CXX_STANDARD
变量设置 C++ 编译标准为 11。
- 设置项目使用的 C++ 标准为 C++11。通过
-
set(CMAKE_CXX_STANDARD_REQUIRED True)
- 强制要求 CMake 使用 C++11 标准进行编译。如果 C++11 编译器不可用,CMake 会报错。
-
find_package(Threads REQUIRED)
- 查找并配置线程库。这使得 CMake 能够处理多线程支持,并链接适当的线程库。在本项目中,CMake 需要找到线程库来支持多线程编程。
-
include_directories(include)
- 指定
include
目录为头文件搜索路径。CMake 会在这个目录下查找项目中的头文件。
- 指定
-
set(SOURCES ...)
- 使用
set()
命令定义了一个名为SOURCES
的变量,包含所有的源文件路径。项目中的所有源文件(例如map_manager.cpp
、bfs_planner.cpp
、node.cpp
等)都被列出并包含在此变量中。 - 这些源文件都在
src
目录下的子目录中,分别属于map
、planner
和node
模块。
- 使用
-
add_executable(map_simulation ${SOURCES})
- 创建一个名为
map_simulation
的可执行文件,使用SOURCES
变量中列出的所有源文件进行编译。 - CMake 会将这些源文件编译并链接成一个名为
map_simulation
的可执行文件。
- 创建一个名为
-
target_link_libraries(map_simulation PRIVATE Threads::Threads)
- 将线程库
Threads::Threads
链接到map_simulation
可执行文件。PRIVATE
关键字意味着只有目标文件(即map_simulation
)会链接到线程库,不会将其传播到其他目标。
- 将线程库
总的来说,CMakeLists.txt
文件配置了该项目的构建过程,主要步骤包括:- 设置项目名称、版本和 C++ 编译标准(C++11)。- 查找并链接线程库,以支持多线程。- 指定头文件搜索路径和源文件路径。- 编译源文件并生成一个名为 map_simulation
的可执行文件。- 确保多线程库能够在程序中正确链接。此配置文件能够有效地管理项目的构建过程,确保所有源文件被正确编译并链接,支持多线程操作。
三、具体应用示例的运行演示
可以按照以下步骤在Ubuntu系统中运行该示例项目
步骤 1: 克隆/复制项目文件
将完整的项目文件复制到 Ubuntu 系统上的某个目录。本文中以目录/home/gly/test/bfsmap为例
步骤 2: 创建构建目录并构建项目
在项目目录下执行以下命令来构建项目:
-
进入项目根目录:(具体路径根据个人实际情况修改)
cd /home/gly/test/bfsmap
-
创建构建目录:
通常建议在项目目录中创建一个build
目录来存放构建文件:mkdir build cd build
-
运行 CMake 配置项目:
使用 CMake 指定项目的根目录进行配置:cmake ..

-
编译项目:
使用make
编译项目:make
如果一切正常,CMake 会自动生成 Makefile 并使用它来编译源代码,生成可执行文件
map_simulation
。

步骤 3: 运行项目
编译完成后,可以运行生成的可执行文件。
- 运行可执行文件:
./map_simulation
运行示例如下所示:
bfsmap运行效果
相关文章:
C++中的单例模式及具体应用示例
AI 摘要 本文深入探讨了C中的单例模式及其在机器人自主导航中的应用,特别是如何通过单例模式来管理地图数据。文章详细介绍了单例模式的基本结构、优缺点以及在多线程环境中的应用,强调了其在保证数据一致性和资源管理中的重要性。 接着,文章…...
网络编程——套接字、创建服务器、创建客户端
一、套接字 1.1什么是套接字 套接字文件,原本就是一个和管道文件类似,用来实现进程间通信的一个文件 既然有了管道文件,当时为什么还要开发套接字文件,去实现进程的通信 因为管道文件是半双工模式的 套接字文件是全双工模式的…...
【设计模式】3W 学习法深入剖析创建型模式:原理、实战与开源框架应用(含 Java 代码)
3W 学习法总结创建型模式(附 Java 代码实战及开源框架应用) 创建型模式主要关注 对象的创建,旨在提高代码的可复用性、可扩展性和灵活性。本文采用 3W 学习法(What、Why、How),深入分析 五大创建型模式&am…...
软考系统架构师考试目录(2023新版)
论文 2023下半年 开发:论面向对象设计的应用与实现大数据:论多数据源集成的应用与实现测试:论软件可靠性评价的设计与实现运维:论边云协同的设计与实现 2024上半年 大数据:Lambda架构,分层批处理层、加…...
Apifox Helper 自动生成API接口文档
在我们开发过程中我们在编写请求地址和编写请求参数的时候特别花费时间耗费了我们很多时间,作为一个程序员,更应该把精力时间集中在开发上, Apifox Helper 是 Apifox 团队针对 IntelliJ IDEA 环境所推出的插件,可以在 IDEA 环境中…...
MySQL开发陷阱与最佳实践:第1章:MySQL开发基础概述-1.1 MySQL简介与应用场景
👉 点击关注不迷路 👉 点击关注不迷路 👉 点击关注不迷路 文章大纲 MySQL开发陷阱与最佳实践:第1章:MySQL开发基础概述-1.1 MySQL简介与应用场景1.1.1 MySQL的发展历程与市场地位1.1.2 MySQL的核心特性与技术优势1.1.2…...
电鱼智能EFISH-RK3576-SBC工控板已适配Android 14系统
EFISH-RK3576-SBC工控板此前已提供了Linux 6.1.57系统,为了满足更多客户的需求,电鱼智能近日又为其成功适配了Android 14系统——硬件性能卓越的核心板与Android 14的深度组合,将为用户带来更加流畅、开放、智能的使用体验。 一、高性能处理器…...
C++ 语法之函数和函数指针
在上一章中 C 语法之 指针的一些应用说明-CSDN博客 我们了解了指针变量,int *p;取变量a的地址这些。 那么函数同样也有个地址,直接输出函数名就可以得到地址,如下: #include<iostream> using namespace std; void fun() …...
LabVIEW生成EXE文件错误提示
在LabVIEW生成EXE时弹出 “The build is missing one or more source files or items the source files reference on disk”,表明项目中引用的某些文件(如VI、子模块、依赖库或配置文件)未被正确包含或路径丢失。以下是具体原因及解决方案&a…...
HTML,CSS,JavaScript
HTML:负责网页的结构(页面元素和内容)。 CSS:负责网页的表现(页面元素的外观、位置等页面样式,如:颜色、大小等)。 Javascript:负责网页的行为(交互效果)。 MDN前端开发文档(MDN Web Docs) HTML HTML(HyperText Markup Language):超文本标记语言超文本:超越了文本的…...
SpringCloud 学习笔记2(Nacos)
Nacos Nacos 下载 Nacos Server 下载 | Nacos 官网 下载、解压、打开文件: 更改 Nacos 的启动方式 Nacos 的启动模式默认是集群模式。在学习时需要把他改为单机模式。 把 cluster 改为 standalone,记得保存! 启动startup.cmd Ubuntu 启动…...
Qt5.15.2实现Qt for WebAssembly与示例
目录 1.什么是Qt for WebAssembly? 1.1 什么是 WebAssembly? 1.2 WebAssembly 的优势 1.3 什么是 Qt for WebAssembly? 1.4 Qt for WebAssembly 的特点 1.5 编译过程 1.6 运行时环境 注意!!!注意&am…...
荣耀手机怎么录制屏幕?屏幕录制后为视频加水印更有“安全感”
在数字时代,屏幕录制已经成为记录和分享信息的重要方式之一。无论是记录游戏的高光时刻,还是制作教学视频,亦或是保存重要的线上会议内容,屏幕录制都能轻松搞定。 荣耀手机作为一款功能强大的设备,自然也提供了便捷的…...
3DXML 与 SOLIDWORKS 格式转换:技术协同及迪威模型方案
一、引言 在产品设计的前沿领域,3DXML 与 SOLIDWORKS 作为主流格式,虽各有所长,但因格式差异,常成为数据流通与协作的阻碍。对于技术人员和学生党而言,掌握二者间的转换技术,不仅能提升设计效率࿰…...
CH347使用笔记:CH347结合STM32CubeIDE实现单片机下载与调试
目录 基于 STM32CubeIDE的 CH347 JTAG/SWD调试器使用说明1. CH347驱动安装与配置2. STM32CubeIDE调试器配置2.1 打开相关工程后,进行以下操作2.2 openocd.exe替换2.3 脚本添加2.4 更改调试器选择 3. 下载程序4. 使用过程中可能遇到的问题4.1 CH347未插入4.2 Openocd…...
JS—基本数据类型和引用数据类型:1分钟掌握两者的区别
个人博客:haichenyi.com。感谢关注 一. 目录 一–目录二–分类三–核心区别四–实际场景中的问题五–总结对比 二. 分类 前面说过这么判断数据类型,今天来说说基本数据类型和引用数据类型的区别。 基本数据类型引用数据类型StringObjectNumberFunct…...
使用 CryptoJS 实现 AES 解密:动态数据解密示例
在现代加密应用中,AES(高级加密标准)是一种广泛使用的对称加密算法。它的安全性高、效率好,适合用于各种加密任务。今天,我们将通过一个实际的示例,展示如何使用 CryptoJS 实现 AES 解密,解密动态数据。CryptoJS 是一个基于 JavaScript 的加密库,它支持 AES、DES 等多种…...
[设计模式与源码]1_Spring三级缓存中的单例模式
欢迎来到啾啾的博客🐱,一个致力于构建完善的Java程序员知识体系的博客📚,记录学习的点滴,分享工作的思考、实用的技巧,偶尔分享一些杂谈💬。 欢迎评论交流,感谢您的阅读😄…...
使用React和google gemini api 打造一个google gemini应用
实现一个简单的聊天应用,用户可以通过输入问题或点击“Surprise me”按钮获取随机问题,并从后端API获取回答。 import { useState } from "react"; function App() {const [ value, setValue] useState(""); // 存储用户输入的问题…...
为什么Django能有效防御CSRF攻击?
在当今这个互联网高度发达的时代,Web安全问题层出不穷,其中跨站请求伪造(CSRF,Cross-Site Request Forgery)就是一个比较常见的威胁。攻击者利用用户的身份信息,发送恶意请求,改变用户的属性或执…...
Oracle常见系统函数
一、字符类函数 1,ASCII(c)和CHR(i)字符串和ascii码互转换 SQL> select ascii(Z) ,ascii(H),ascii( A) from dual;ASCII(Z) ASCII(H) ASCII(A) ---------- ---------- ----------90 72 32SQL> select chr(90),chr(72),chr(65) from dual;C…...
【Visio使用教程】
Visio使用教程 1. Visio 的基本介绍1.1 Visio 是什么?核心特点: 1.2 主要功能与应用场景典型用途:行业应用: 1.3 版本与兼容性1.4 Visio下载1.5 安装 2. Visio 的界面与基础操作2.1 界面布局详解2.2 创建新文档与模板选择2.3 形状…...
蓝桥杯 修剪灌木
问题描述 爱丽丝要完成一项修剪灌木的工作。 有 N 棵灌木整齐的从左到右排成一排。爱丽丝在每天傍晩会修剪一棵灌 木, 让灌木的高度变为 0 厘米。爱丽丝修剪灌木的顺序是从最左侧的灌木开始, 每天向右修剪一棵灌木。当修剪了最右侧的灌木后, 她会调转方向, 下一天开 始向左修…...
HTML中滚动加载的实现
设置div的overflow属性,可以使得该div具有滚动效果,下面以div中包含的是table来举例。 当table的元素较多,以至于超出div的显示范围的话,观察下该div元素的以下3个属性: clientHeight是div的显示高度,scrol…...
bbbbb
import java.util.ArrayList; import java.util.List; public class KthPermutation { public static String getPermutation(int n, int k) { // 计算阶乘 int[] factorial new int[n]; factorial[0] 1; for (int i 1; i < n; i) …...
Linux文件
1.Open函数 高频使用的Linux系统调用:open write read close Linux自带的工具:man手册: man 1是普通的shell命令,比如ls man 2是系统调用函数,比如open,write说明 在Linux系统库的定义: int o…...
kafka指北
为自己总结一下kafka指北,会持续更新。创作不易,转载请注明出处。 目录 集群controller选举过程broker启动流程 主题创建副本分布ISRleader副本选举机制LEO 生产数据流程同步发送和异步发送 分区策略ack应答生产者发送消息的幂等性跨分区幂等性问题&…...
Linux安装部署Elasticsearch8 全过程记录
一、安装 Elasticsearch8 1、下载 访问 Elasticsearch 官方网站(Download Elasticsearch | Elastic)。 在下载页面找到 Elasticsearch 8 的 Linux 版本(.tar.gz 格式)下载链接,点击下载。 下载Elasticsearch8&…...
ESP32(3)UDP通信
对于 lwIP 的 Socket 的使用方式,它与文件操作非常相似。在文件操作中,我们首先打开文件,然后进行读/写操作,最后关闭文件。在TCP/IP网络通信中,也存在着相同的操作流程,但所使用的接口不再是文件描述符或 …...
汽车机械钥匙升级一键启动的优点
汽车机械钥匙升级一键启动的优点主要包括: 便捷性:一键启动功能的引入极大地提升了用车便捷性。车主无需翻找钥匙,只需在车辆感应范围内轻触启动键,即可轻松发动汽车。 安全性:移动管家专车专用一键启动系统配备了防…...
【matlab例程】三维下的TDOA定位和EKF轨迹滤波例程,TDOA的锚点数量可自定义(订阅专栏后可获得完整代码)
本文所述的MATLAB例程实现了TDOA定位和扩展卡尔曼滤波(EKF)来提高位置估计的准确性,并通过可视化结果进行分析。 文章目录 运行结果MATLAB代码程序讲解关键步骤和功能步骤解释注意事项总结运行结果 三维轨迹: 三维误差曲线: RMSE曲线: 命令行输出内容:...
个人blog系统 前后端分离 前端js后端go
系统设计: 1.使用语言:前端使用vue,并使用axios向后端发送数据。后端使用的是go的gin框架,并使用grom连接数据库实现数据存储读取。 2.设计结构: 最终展示:仅展示添加模块,其他模块基本相似 前…...
OSG简介
OSG OpenSceneGraph (简称 OSG) 是一个开源的高性能3D图形库。 作用 它为开发者提供了一个强大的API,处理和渲染复杂的3D图形。 特点 OSG基于OpenGL构建,提供了对现代图形技术的支持,如着色器、纹理映射、光照模型等高级特性。 跨平台支…...
社区版Uos20.9从源码编译QT5.15.2
主要是在这个文章上学的究极保姆式教你如何在Ubuntu上源码安装Qt5.15.2_ubuntu安装qt5.15.2-CSDN博客 但原文上在环境变量的配置上真用在 uso上好像不行,要加一些引号和$号。原文的测试编译代码也有些问题,include上少了类。略作修改,在UOS社…...
AI学习第二天--大模型压缩(量化、剪枝、蒸馏、低秩分解)
目录 1. 量化:压缩大象的“脂肪” 比喻 技术逻辑 2. 剪枝:修剪大象的“无效毛发” 比喻 技术逻辑 3. 知识蒸馏:让大象“师从巨象” 比喻 技术逻辑 4. 低秩分解:把大象“折叠成纸偶” 比喻 技术逻辑 5. 推理优化&#…...
C++ —— 线程同步(互斥锁)
C —— 线程同步(互斥锁) 线程同步互斥锁(互斥量)测试代码mutex互斥锁 线程同步 线程同步:多线程协同工作,协商如何使用共享资源。 C11线程同步包含三部分内容: 互斥锁(互斥量&…...
相对路径跳转和绝对路径跳转有什么区别?
在 Vue 3 中使用路由跳转时,相对路径跳转和绝对路径跳转在使用方式、适用场景等方面存在明显区别,以下为你详细介绍: 定义 绝对路径跳转:指的是使用完整的路径来进行路由导航,路径以 / 开头,无论当前处于…...
Flume详解——介绍、部署与使用
1. Flume 简介 Apache Flume 是一个专门用于高效地 收集、聚合、传输 大量日志数据的 分布式、可靠 的系统。它特别擅长将数据从各种数据源(如日志文件、消息队列等)传输到 HDFS、HBase、Kafka 等大数据存储系统。 特点: 可扩展࿱…...
笔记类AI应用体验
笔记类AI应用体验 叮当好记视频一键转笔记, 祝你学习效率起飞 IMAGet笔记印象笔记(Evernote):Notion:Trilium Notes:二、开始搭建三、搭建步骤四、创建博客 Obsidian:案例让ai帮我执行大模型学习…...
Mysql篇——SQL优化
本篇将带领各位了解一些常见的sql优化方法,学到就是赚到,一起跟着练习吧~ SQL优化 准备工作 准备的话我们肯定是需要一张表的,什么表都可以,这里先给出我的表结构(表名:userinfo) 通过sql查看…...
【css酷炫效果】纯CSS实现故障文字特效
【css酷炫效果】纯CSS实现故障文字特效 缘创作背景html结构css样式完整代码基础版进阶版(3D效果) 效果图 想直接拿走的老板,链接放在这里:https://download.csdn.net/download/u011561335/90492053 缘 创作随缘,不定时更新。 创作背景 刚…...
【Java】链表(LinkedList)(图文版)
本博客总结了Java当中链表的实现,以及相关方法的使用,在最后附带了一些常见链表相关处理技巧,希望对你有帮助! ps:可拷贝到IDEA上自行测试,代码全部完成测试。 一.链表概述 1.什么是链表? 链…...
审批工作流系统xFlow
WorkFlow-审批流程系统 该项目为完全开源免费项目 可用于学习或搭建初始化审批流程系统 希望有用的小伙伴记得点个免费的star gitee仓库地址 仿钉钉飞书工作审批流系统 介绍 前端技术栈: vue3 ts vite arcodesign eslint 后端技术栈:springbootspring mvc mybatis mavenmysq…...
UNION,UNION ALL 的详细用法
目录 一、基本概念 二、核心区别 三、语法使用规则 四、代码实演示 4.1 两张表字段相同,字段顺序也相同 4.2 两张表字段相同。但字段顺序不同 4.3 两张表存在相同字段,但一张表字段多,一张表字段少 一、基本概念 操作符功能描述去重处…...
Java 集合遍历过程中修改数据触发 Fail-Fast 机制 ,导致报ConcurrentModificationException异常
Java Fail-Fast 机制 Fail-Fast 机制是 Java 集合框架中的一种错误检测机制,用于在遍历集合时检测结构修改。如果在迭代器创建之后,集合被修改(例如添加或删除元素),并且这种修改不是通过迭代器自身的 remove() 方法进…...
Javascript 日期相关计算
1、获取当前日期的前一天 // 获取当前日期let today new Date();today.setDate(today.getDate() - 1);// 转换为本地日期字符串格式let yesterdayStr today.toISOString().slice(0, 10);console.log(yesterdayStr); // 例如: "2023-04-03" (格式取决于地区设置) 2…...
自动驾驶背后的数学:特征提取中的线性变换与非线性激活
在上一篇博客「自动驾驶背后的数学:从传感器数据到控制指令的函数嵌套」—— 揭秘人工智能中的线性函数、ReLU 与复合函数中,我们初步探讨了自动驾驶技术中从传感器数据到控制指令的函数嵌套流程,其中提到了特征提取模块对传感器数据进行线性…...
DNS解析查询工具
dig命令 1 常用命令 命令:dig 您的域名(示例:dig www.baidu.com) 2、根据解析记录查询,比如MX,CNAME,NS,PTR等,只需将类型加在命令后面即可。 示例:dig bai…...
【eNSP实战】(续)一个AC多个VAP的实现—将隧道转发改成直接转发
在 一个AC多个VAP的实现—CAPWAP隧道转发 此篇文章配置的基础上,将隧道转发改成直接转发 一、改成直接转发需要改动的配置 (一)将连接AP的接口改成trunk口,并允许vlan100、101、102通过 [AC1]interface GigabitEthernet 0/0/8 …...
解决远程卡在下载vscode-server的问题,一键安装脚本
vscode-server 下载与安装脚本 vscode-server一键安装脚本 简介 此脚本用于下载并安装指定提交 ID 和架构的 VS Code Server。用户可以选择不同的架构,并输入对应的 VS Code 提交 ID 来下载和安装 vscode-server。VS Code提交ID可以在VS Code界面“帮助>关于…...