<摘要>
单例模式是创建型设计模式中最简单但应用最广泛的模式之一,它确保一个类只有一个实例并提供全局访问点。本文从历史背景和核心概念出发,系统阐述了单例模式的产生缘由和演进脉络,深入剖析了其在资源管理、状态一致性和访问控制方面的设计意图。通过饿汉式、懒汉式、Meyer’s Singleton和线程安全双检锁等四种经典实现方式的对比分析,结合日志系统、配置管理、数据库连接池等实际应用场景,全面展示了单例模式的具体实现技术和适用场景。文章特别针对多线程环境下的线程安全问题进行了深度解析,使用时序图和状态图直观演示了竞态条件的产生与解决方案,最后探讨了单例模式的测试方法和替代方案,为开发者提供了全面而实用的指导。


<解析>

1. 背景与核心概念

1.1 产生背景与发展脉络

单例模式(Singleton Pattern)的诞生源于软件开发中对特定类型对象管理的实际需求。在早期的软件开发实践中,开发者逐渐意识到某些类的实例应该在整个应用程序生命周期中只存在一个,这种需求催生了单例模式的形成。

历史演进阶段

  1. 初期探索阶段(1980年代前):在面向对象编程范式普及之前,开发者通常使用全局变量来实现类似单例的功能。这种方式虽然简单,但带来了命名冲突、初始化顺序不确定和访问控制缺失等问题。

  2. 模式化阶段(1980-1990年代):随着"Gang of Four"(Erich Gamma, Richard Helm, Ralph Johnson, John Vlissides)在1994年出版的《设计模式:可复用面向对象软件的基础》一书中正式提出单例模式,它被系统性地归纳为23种经典设计模式之一,属于创建型模式类别。

  3. 语言特性融合阶段(2000年代至今):随着编程语言的发展,单例模式的实现方式不断演进。C++11标准引入的线程局部存储(thread_local)、原子操作(atomic)和静态变量初始化线程安全等特性,为单例模式提供了更优雅的实现方案。

产生的根本原因

  • 资源共享需求:如数据库连接池、线程池等需要集中管理的资源
  • 状态一致性要求:如配置管理、计数器等需要全局一致状态的对象
  • 性能优化考虑:避免频繁创建销毁重量级对象带来的开销
  • 访问控制需要:集中管控对特定资源的访问,如日志系统

1.2 核心概念与关键术语

单例模式(Singleton Pattern):确保一个类只有一个实例,并提供一个全局访问点来获取该实例的设计模式。

关键特性

  • 唯一实例性(Instance Uniqueness):保证类只有一个实例存在
  • 全局可访问性(Global Accessibility):提供统一的访问入口
  • 延迟初始化(Lazy Initialization):多数实现支持在第一次使用时创建实例
  • 线程安全性(Thread Safety):在多线程环境下保证正确性

基本结构组件

class Singleton {
private:static Singleton* instance;    // 静态私有成员,保存唯一实例Singleton();                   // 私有构造函数,防止外部实例化Singleton(const Singleton&) = delete;            // 删除拷贝构造函数Singleton& operator=(const Singleton&) = delete; // 删除赋值运算符public:static Singleton* getInstance(); // 静态公共方法,提供全局访问点// 其他成员函数...
};

UML表示

Singleton
-static Singleton* instance
-Singleton()
+static getInstance()
+someOperation() : void

图1.1:单例模式基本UML类图

2. 设计意图与考量

2.1 核心设计目标

单例模式的设计旨在解决以下核心问题:

2.1.1 controlled Instance Creation(受控实例创建)
通过将构造函数设为私有,单例模式彻底消除了客户端随意创建类实例的可能性。这种强制性的创建控制确保了实例数量的严格管理,从语言机制层面而非仅仅约定层面保证了单一实例的约束。

2.1.2 Global Access Point(全局访问点)
提供静态方法getInstance()作为获取单例实例的统一入口,解决了全局变量方式的散乱访问问题。这种方法:

  • 明确了职责:清晰标识这是获取实例的正确方式
  • 封装了复杂性:隐藏了实例创建和管理的细节
  • 提供了灵活性:允许在不改变客户端代码的情况下修改实例化策略

2…3 Resource Coordination(资源协调)
对于需要协调共享资源的场景,单例模式提供了自然的设计方案:

  • 避免资源冲突:如多个日志写入器同时写文件可能导致的内容交错
  • 减少资源浪费:如数据库连接的重用而非重复创建
  • 统一管理策略:如缓存的一致性管理和过期策略

2.2 设计考量因素

2.2.1 线程安全性考量
在多线程环境下,单例模式的实现必须考虑竞争条件(Race Condition)问题:

Thread1Thread2Singleton竞态条件发生场景getInstance()检查instance==nullptrgetInstance()检查instance==nullptrnew Singleton()创建实例new Singleton()创建另一个实例两个线程得到不同实例,违反单例原则Thread1Thread2Singleton

图2.1:多线程环境下的竞态条件时序图

解决方案包括:

  • 饿汉式初始化:在程序启动时即创建实例,避免运行时竞争
  • 互斥锁保护:在懒汉式初始化时使用锁机制
  • 双检锁模式:减少锁的使用频率,提高性能
  • 局部静态变量:利用C++11的静态变量线程安全特性

2.2.2 初始化时机权衡

初始化方式优点缺点适用场景
饿汉式实现简单,线程安全可能提前占用资源,启动慢实例小且必定使用
懒汉式资源按需分配,启动快实现复杂,需要线程安全措施实例大或不一定会使用

2.2.3 继承与扩展性
单例类的继承会带来设计上的挑战:

  • 构造函数隐私性:派生类需要访问基类构造函数
  • 实例唯一性:每个派生类是否都应该是单例?
  • 模板方法应用:通过模板元编程实现可复用的单例基类

2.2.4 测试困难性
单例模式对单元测试不友好,主要原因:

  • 全局状态共享:测试用例之间可能相互影响
  • 难以模拟:无法轻松替换为模拟对象进行测试
  • 重置困难:需要额外机制在测试间重置单例状态

2.2.5 生命周期管理
单例实例的生命周期管理需要考虑:

  • 创建时机:何时以及如何创建实例
  • 销毁时机:是否需要显式销毁,如何保证安全销毁
  • 依赖关系:单例之间的依赖关系及初始化顺序

3. 实例与应用场景

3.1 日志系统(Logger)

应用场景
在大多数应用程序中,日志系统需要满足以下要求:

  • 全局唯一:多个模块共享同一个日志实例
  • 线程安全:多线程环境下能安全写入日志
  • 集中配置:统一设置日志级别、输出目标等

实现方案1:Meyer’s Singleton(C++11及以上)

class Logger {
public:static Logger& getInstance() {static Logger instance;  // C++11保证静态局部变量初始化线程安全return instance;}void log(const std::string& message, LogLevel level = LogLevel::INFO) {std::lock_guard<std::mutex> lock(logMutex);// 实际日志记录逻辑std::cout << "[" << getLevelString(level) << "] " << message << std::endl;}void setLogLevel(LogLevel level) { /* 实现 */ }// 删除拷贝构造函数和赋值运算符Logger(const Logger&) = delete;Logger& operator=(const Logger&) = delete;private:std::mutex logMutex;LogLevel currentLevel;Logger() : currentLevel(LogLevel::INFO) {// 初始化逻辑}~Logger() {// 清理逻辑,如关闭文件等}std::string getLevelString(LogLevel level) { /* 实现 */ }
};// 使用示例
Logger::getInstance().log("Application started");
Logger::getInstance().setLogLevel(LogLevel::DEBUG);

实现方案2:带双检锁的懒汉式

class Logger {
public:static Logger* getInstance() {Logger* tmp = instance.load(std::memory_order_acquire);if (tmp == nullptr) {std::lock_guard<std::mutex> lock(mutex);tmp = instance.load(std::memory_order_relaxed);if (tmp == nullptr) {tmp = new Logger();instance.store(tmp, std::memory_order_release);}}return tmp;}// 其他成员函数同上...private:static std::atomic<Logger*> instance;static std::mutex mutex;Logger() { /* 初始化 */ }~Logger() { /* 清理 */ }
};// 静态成员初始化
std::atomic<Logger*> Logger::instance{nullptr};
std::mutex Logger::mutex;

3.2 配置管理器(Configuration Manager)

应用场景
应用程序通常需要读取和管理配置文件,配置管理器应该:

  • 全局唯一:确保所有模块使用相同的配置
  • 懒加载:只在第一次使用时加载配置
  • 线程安全:支持多线程并发读取配置

实现方案:带异常处理的单例

class ConfigManager {
public:static ConfigManager& getInstance() {try {static ConfigManager instance;return instance;} catch (const std::exception& e) {// 处理初始化异常std::cerr << "ConfigManager initialization failed: " << e.what() << std::endl;throw;}}void loadConfig(const std::string& filename) {std::lock_guard<std::mutex> lock(configMutex);// 解析配置文件// 可能抛出异常,如文件不存在或格式错误}std::string getValue(const std::string& key, const std::string& defaultValue = "") const {std::lock_guard<std::mutex> lock(configMutex);auto it = configMap.find(key);return it != configMap.end() ? it->second : defaultValue;}void setValue(const std::string& key, const std::string& value) {std::lock_guard<std::mutex> lock(configMutex);configMap[key] = value;}private:std::mutex configMutex;std::unordered_map<std::string, std::string> configMap;ConfigManager() {// 尝试加载默认配置try {loadConfig("default.conf");} catch (...) {// 使用内置默认值setDefaultValues();}}void setDefaultValues() {configMap["server.host"] = "localhost";configMap["server.port"] = "8080";// 更多默认值...}// 禁止拷贝和赋值ConfigManager(const ConfigManager&) = delete;ConfigManager& operator=(const ConfigManager&) = delete;
};// 使用示例
std::string host = ConfigManager::getInstance().getValue("server.host");
int port = std::stoi(ConfigManager::getInstance().getValue("server.port"));

3.3 数据库连接池(Database Connection Pool)

应用场景
数据库连接是昂贵的资源,连接池需要:

  • 限制连接数量:防止过多连接耗尽数据库资源
  • 重用连接:避免频繁创建和关闭连接
  • 全局管理:所有数据库操作共享同一个连接池

实现方案:带连接管理的单例

class ConnectionPool {
public:static ConnectionPool& getInstance() {static ConnectionPool instance;return instance;}std::shared_ptr<DatabaseConnection> getConnection() {std::unique_lock<std::mutex> lock(poolMutex);// 等待可用连接connectionAvailable.wait(lock, [this]() {return !availableConnections.empty() || currentSize < maxSize;});if (!availableConnections.empty()) {auto conn = availableConnections.front();availableConnections.pop();return std::shared_ptr<DatabaseConnection>(conn, [this](DatabaseConnection* conn) {releaseConnection(conn);});}if (currentSize < maxSize) {auto conn = createConnection();currentSize++;return std::shared_ptr<DatabaseConnection>(conn, [this](DatabaseConnection* conn) {releaseConnection(conn);});}throw std::runtime_error("Unable to get database connection");}void configure(size_t maxConnections, const std::string& connectionString) {std::lock_guard<std::mutex> lock(poolMutex);this->maxSize = maxConnections;this->connectionString = connectionString;// 可选的预创建连接precreateConnections();}private:std::mutex poolMutex;std::condition_variable connectionAvailable;std::queue<DatabaseConnection*> availableConnections;size_t currentSize = 0;size_t maxSize = 10;std::string connectionString;ConnectionPool() = default;~ConnectionPool() {cleanup();}DatabaseConnection* createConnection() {// 实际创建数据库连接的逻辑return new DatabaseConnection(connectionString);}void releaseConnection(DatabaseConnection* conn) {std::lock_guard<std::mutex> lock(poolMutex);if (conn->isValid()) {availableConnections.push(conn);connectionAvailable.notify_one();} else {delete conn;currentSize--;connectionAvailable.notify_one();}}void precreateConnections() {for (size_t i = 0; i < std::min(size_t(3), maxSize); ++i) {availableConnections.push(createConnection());currentSize++;}}void cleanup() {while (!availableConnections.empty()) {delete availableConnections.front();availableConnections.pop();}}// 禁止拷贝和赋值ConnectionPool(const ConnectionPool&) = delete;ConnectionPool& operator=(const ConnectionPool&) = delete;
};// 使用示例
auto& pool = ConnectionPool::getInstance();
pool.configure(20, "host=localhost;dbname=test;user=root");
auto connection = pool.getConnection();
// 使用connection进行数据库操作...

3.4 状态管理器(State Manager)

应用场景
在游戏或复杂应用中,需要管理全局状态:

  • 全局可访问:各个子系统需要访问和修改状态
  • 线程安全:多线程环境下的状态更新
  • 状态持久化:支持状态的保存和恢复

实现方案:观察者模式结合的单例

class GameStateManager {
public:static GameStateManager& getInstance() {static GameStateManager instance;return instance;}// 状态获取和设置int getScore() const {std::shared_lock<std::shared_mutex> lock(stateMutex);return currentState.score;}void setScore(int score) {{std::unique_lock<std::shared_mutex> lock(stateMutex);currentState.score = score;}notifyObservers(StateEvent::SCORE_CHANGED);}// 观察者模式支持void addObserver(StateObserver* observer) {std::lock_guard<std::mutex> lock(observerMutex);observers.push_back(observer);}void removeObserver(StateObserver* observer) {std::lock_guard<std::mutex> lock(observerMutex);observers.erase(std::remove(observers.begin(), observers.end(), observer),observers.end());}// 状态持久化bool saveState(const std::string& filename) const {std::shared_lock<std::shared_mutex> lock(stateMutex);// 序列化状态到文件return true;}bool loadState(const std::string& filename) {GameState newState;// 从文件加载状态{std::unique_lock<std::shared_mutex> lock(stateMutex);currentState = newState;}notifyObservers(StateEvent::STATE_LOADED);return true;}private:mutable std::shared_mutex stateMutex;std::mutex observerMutex;struct GameState {int score = 0;int level = 1;std::string playerName;// 更多状态字段...} currentState;std::vector<StateObserver*> observers;GameStateManager() = default;~GameStateManager() = default;void notifyObservers(StateEvent event) {std::vector<StateObserver*> observersCopy;{std::lock_guard<std::mutex> lock(observerMutex);observersCopy = observers;}for (auto observer : observersCopy) {observer->onStateChanged(event);}}// 禁止拷贝和赋值GameStateManager(const GameStateManager&) = delete;GameStateManager& operator=(const GameStateManager&) = delete;
};// 使用示例
GameStateManager::getInstance().setScore(1000);
int currentScore = GameStateManager::getInstance().getScore();

4. 交互性内容解析

4.1 多线程环境下的交互分析

单例模式在多线程环境下的行为复杂性主要体现在实例化过程中。以下通过时序图详细分析不同实现方式的线程交互:

4.1.1 不安全懒汉式的竞态条件

ThreadAThreadBSingletonClass不安全懒汉式初始化getInstance()检查instance==nullptr确认instance为nullptrgetInstance()检查instance==nullptr确认instance为nullptr(此时还未创建)new Singleton()创建实例赋值给instancenew Singleton()创建另一个实例赋值给instance(覆盖前一个)产生两个实例,内存泄漏ThreadAThreadBSingletonClass

4.1.2 双检锁模式的正确交互

ThreadAThreadBMutexSingletonClassgetInstance()检查instance==nullptr确认instance为nullptrlock()获取互斥锁getInstance()检查instance==nullptr等待(因为instance仍为nullptr)再次检查instance==nullptr(双检)new Singleton()创建实例原子操作赋值给instanceunlock()释放互斥锁lock()获取互斥锁再次检查instance≠nullptr(双检)unlock()释放互斥锁返回已创建的实例ThreadAThreadBMutexSingletonClass

4.2 单例与依赖组件的交互

在实际应用中,单例对象往往需要与其他系统组件进行交互。以下以日志单例为例展示其与文件系统、网络服务的交互:

ClientLoggerSingletonFileSystemNetworkServicelog("Error occurred", ERROR)获取当前时间戳格式化日志消息write(log_file, message)写入成功sendLog(message)发送确认alt[网络日志启用]日志记录完成ClientLoggerSingletonFileSystemNetworkService

5. 高级主题与最佳实践

5.1 单例模式的变体

5.1.1 多例模式(Multiton)
扩展单例概念,允许有限数量的实例,通常按键区分:

template<typename Key, typename Value>
class Multiton {
public:static Value& getInstance(const Key& key) {std::lock_guard<std::mutex> lock(mutex);auto it = instances.find(key);if (it == instances.end()) {it = instances.emplace(key, std::make_unique<Value>()).first;}return *it->second;}// 禁止外部构造和拷贝Multiton() = delete;Multiton(const Multiton&) = delete;Multiton& operator=(const Multiton&) = delete;private:static std::mutex mutex;static std::map<Key, std::unique_ptr<Value>> instances;
};// 使用示例
auto& config1 = Multiton<std::string, ConfigManager>::getInstance("database");
auto& config2 = Multiton<std::string, ConfigManager>::getInstance("application");

5.1.2 线程局部单例(Thread-Local Singleton)
每个线程拥有自己的单例实例:

class ThreadLocalLogger {
public:static ThreadLocalLogger& getInstance() {thread_local ThreadLocalLogger instance;return instance;}void log(const std::string& message) {// 线程安全的日志记录,无需加锁logs.push_back(message);}std::vector<std::string> getLogs() const {return logs;}private:std::vector<std::string> logs;ThreadLocalLogger() = default;~ThreadLocalLogger() = default;// 禁止拷贝和赋值ThreadLocalLogger(const ThreadLocalLogger&) = delete;ThreadLocalLogger& operator=(const ThreadLocalLogger&) = delete;
};

5.2 单例模式的测试策略

由于单例的全局状态特性,对其进行单元测试需要特殊策略:

5.2.1 测试夹具设计

class ConfigManagerTest : public ::testing::Test {
protected:void SetUp() override {// 保存原始实例(如果支持重置)originalInstance = &ConfigManager::getInstance();// 使用测试配置ConfigManager::getInstance().loadConfig("test_config.conf");}void TearDown() override {// 重置单例状态ConfigManager::getInstance().resetToDefaults();}ConfigManager* originalInstance;
};TEST_F(ConfigManagerTest, LoadsConfigurationCorrectly) {auto& config = ConfigManager::getInstance();EXPECT_EQ(config.getValue("test.setting"), "expected_value");
}

5.2.2 可测试单例设计
通过引入依赖注入和接口抽象增强可测试性:

class IConfigManager {
public:virtual ~IConfigManager() = default;virtual std::string getValue(const std::string& key) const = 0;virtual void setValue(const std::string& key, const std::string& value) = 0;
};class ConfigManager : public IConfigManager {
public:static IConfigManager& getInstance() {static ConfigManager instance;return instance;}// 实现接口方法...// 测试支持方法static void setTestInstance(IConfigManager* testInstance) {testInstanceOverride = testInstance;}static void resetInstance() {testInstanceOverride = nullptr;}// 通过此方法访问实例,允许测试替换static IConfigManager& getInstanceInternal() {if (testInstanceOverride != nullptr) {return *testInstanceOverride;}return getInstance();}private:static IConfigManager* testInstanceOverride;ConfigManager() = default;// 其他实现...
};// 在测试中
class MockConfigManager : public IConfigManager {
public:MOCK_METHOD(std::string, getValue, (const std::string&), (const override));MOCK_METHOD(void, setValue, (const std::string&, const std::string&), (override));
};TEST(ConfigDependentTest, UsesConfigManager) {MockConfigManager mockConfig;EXPECT_CALL(mockConfig, getValue("test.key")).WillOnce(Return("mock_value"));ConfigManager::setTestInstance(&mockConfig);// 测试使用ConfigManager::getInstanceInternal()的代码// ...ConfigManager::resetInstance();
}

5.3 单例模式的替代方案

虽然单例模式有用,但并非所有全局访问需求都适合使用单例。考虑以下替代方案:

5.3.1 依赖注入(Dependency Injection)
通过构造函数或方法参数显式传递依赖:

class Application {
public:// 通过构造函数注入依赖explicit Application(ILogger& logger, IConfigManager& config): logger(logger), config(config) {}void run() {logger.log("Application started");std::string setting = config.getValue("some_setting");// ...}private:ILogger& logger;IConfigManager& config;
};// 在组合根中组装对象
int main() {auto& logger = Logger::getInstance();auto& config = ConfigManager::getInstance();Application app(logger, config);app.run();
}

5.3.2 单例服务定位器(Service Locator)
提供全局访问点,但允许替换实现:

class ServiceLocator {
public:static ILogger& getLogger() {ILogger* service = loggerService.load();if (service == nullptr) {// 返回默认实现或抛出异常return defaultLogger;}return *service;}static void registerLogger(ILogger* service) {loggerService.store(service);}static void deregisterLogger() {loggerService.store(nullptr);}private:static std::atomic<ILogger*> loggerService;static DefaultLogger defaultLogger;
};// 使用示例
ServiceLocator::getLogger().log("Message");// 在测试中
TEST(SomeTest, TestWithMockLogger) {MockLogger mockLogger;ServiceLocator::registerLogger(&mockLogger);// 执行测试...ServiceLocator::deregisterLogger();
}

6. 总结与建议

6.1 单例模式适用场景

在以下情况下考虑使用单例模式:

  1. 确需全局唯一实例的场景
  2. 需要严格控制实例数量的资源管理
  3. 需要集中管理全局状态或配置
  4. 频繁访问的重量级对象需要重用

6.2 单例模式实现选择建议

场景推荐实现理由
C++11及以上环境Meyer’s Singleton简单、安全、自动销毁
需要控制初始化时机双检锁模式精确控制初始化时机
性能敏感场景饿汉式无运行时开销,但可能浪费资源
需要参数化初始化带init方法的单例支持初始化参数传递

6.3 注意事项与陷阱

  1. 隐藏的依赖:单例模式会创建隐藏的全局依赖,降低代码可测试性和模块化
  2. 生命周期管理:注意单例的销毁顺序,特别是在静态销毁期访问单例
  3. 过度使用:避免将单例作为全局变量的替代品,导致设计僵化
  4. 线程安全:确保实现正确的线程同步,避免竞态条件

6.4 现代C++中的改进

C++11及以上版本提供了更好的单例实现工具:

  • thread_local:实现线程局部单例
  • std::call_once:保证一次性初始化
  • 原子操作:实现无锁或低锁同步
  • 静态局部变量:线程安全的延迟初始化

单例模式是强大的工具,但需要谨慎使用。正确应用时,它可以提供优雅的解决方案;滥用时,它会导致代码难以维护和测试。始终考虑是否真的需要单例,或者是否有更好的替代设计方案。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/pingmian/96170.shtml
繁体地址,请注明出处:http://hk.pswp.cn/pingmian/96170.shtml
英文地址,请注明出处:http://en.pswp.cn/pingmian/96170.shtml

如若内容造成侵权/违法违规/事实不符,请联系英文站点网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

kafka如何保证消息的顺序性

kafka如何保证消息的顺序性 Kafka只能在分区&#xff08;Partition&#xff09;级别保证消息的顺序性&#xff0c;而不能在主题&#xff08;Topic&#xff09;级别保证全局顺序。 核心原理&#xff1a;分区和偏移量分区&#xff08;Partition&#xff09;是顺序性的基础&#x…

传输层:UDP/TCP协议

网络协议图 一.UDP 特点: 无连接&#xff0c;不可靠&#xff0c;面向数据报&#xff0c;全双工&#xff08;前面网络编程中介绍过&#xff09; 格式: 服务器的端口号一般都是程序员指定的(这样你才能访问到),客户端的端口号是系统自动分配的(如果提前指定好, 可能会与其他程…

A/B测试全解析:原理、流程与实战案例

A/B测试&#xff08;AB Testing&#xff09;原理与实践全解析 在数据驱动的时代&#xff0c;A/B测试几乎是每一个互联网公司都会使用的实验方法。无论是电商平台优化转化率&#xff0c;还是内容平台提升点击率&#xff0c;抑或是游戏公司提升留存&#xff0c;A/B测试都是最常见…

循环神经网络(三):小练习

RNN小练习 要求&#xff1a; 假设有 4 个字 吃 了 没 &#xff1f;&#xff0c;请使用 torch.nn.RNN 完成以下任务 将每个进行 one-hot 编码请使用 吃 了 没 作为输入序列&#xff0c;了 没 &#xff1f; 作为输出序列RNN 的 hidden_size 64请将 RNN 的输出使用全连接转换成 4…

ESPIDF官方文档,启用dhcp会禁用对应的STA或AP的静态IP,我测试STA确实是,但是AP不是,为什么

1. STA 模式下的 DHCP&#xff08;客户端角色&#xff09;ESP32 当 Station&#xff08;STA&#xff09; 时&#xff0c;它的行为就跟你的手机/笔记本连 Wi-Fi 一样&#xff1a;DHCP 客户端 → 去路由器&#xff08;DHCP 服务器&#xff09;要一个 IP。特点启用 DHCP&#xff0…

cocos2d. 3.17.2 c++如何实现下载断点续传zip压缩包带进度条

新建类CurlDown #include “curl/curl.h” #include using namespace std; USING_NS_CC; /** 资源下载curl */ class CurlDown { public: CurlDown(); ~CurlDown(); void StartDownResZip(string downLoadUrl, int64_t totalSize); //下载控制 void downloadControler(); //下…

MySQL 整型数据类型:选对数字类型,让存储效率翻倍

MySQL 整型数据类型&#xff1a;选对数字类型&#xff0c;让存储效率翻倍 在 MySQL 中&#xff0c;整型&#xff08;整数类型&#xff09;是最常用的数据类型之一&#xff0c;从用户 ID 到商品数量&#xff0c;几乎所有涉及数字的场景都离不开它。但你知道吗&#xff1f;选对整…

公司电脑监控软件有哪些?公司电脑监控软件应该怎么选择

大家好呀&#xff0c;电竞直播运营团队常常面临 “直播脚本被抄袭、用户付费数据篡改、主播话术外泄” 的问题&#xff01;尤其是独家直播流程脚本、用户充值记录、主播互动话术库、赛事解说手稿&#xff0c;一旦泄露可能导致竞品跟风、用户信任下降、直播竞争力减弱&#xff5…

ARM裸机开发:链接脚本、进阶Makefile(bsp)、编译过程、beep实验

一、链接脚本的作用&#xff1f;各个段存放什么数据类型&#xff08;一&#xff09;链接脚本内容SECTIONS {. 0x87800000;.text : {obj/start.o*(.text)}.rodata ALIGN(4) : {*(.rodata*)}.data ALIGN(4) : {*(.data)}__bss_start .;.bss ALIGN(4) : {*(.bss) *(COMMON)}__bs…

Linux驱动开发(1)概念、环境与代码框架

一、驱动概念驱动与底层硬件直接打交道&#xff0c;充当了硬件与应用软件中间的桥梁。1、具体任务&#xff08;1&#xff09;读写设备寄存器&#xff08;实现控制的方式&#xff09;&#xff08;2&#xff09;完成设备的轮询、中断处理、DMA通信&#xff08;CPU与外设通信的方式…

计算机视觉(十):ROI

什么是感兴趣区域&#xff08;ROI&#xff09;&#xff1f; 在计算机视觉中&#xff0c;**感兴趣区域&#xff08;ROI&#xff09;**指的是图像中包含我们想要分析、处理或识别的目标或特征的特定子集。就像我们在阅读一本书时会聚焦于某个重要的段落&#xff0c;计算机视觉系统…

Jenkins 构建 Node 项目报错解析与解决——pnpm lockfile 问题实战

在使用 Jenkins 自动化构建 Node.js 项目时&#xff0c;经常会遇到类似报错&#xff1a; ERR_PNPM_OUTDATED_LOCKFILE  Cannot install with "frozen-lockfile" because pnpm-lock.yaml is not up to date with package.json Error: Cannot find module node_module…

Kafka在多环境中安全管理敏感

1. 配置提供者是什么&#xff1f; 配置提供者&#xff08;ConfigProvider&#xff09;是一类按需“拉取配置”的组件&#xff1a;应用读取配置时&#xff0c;按约定的占位符语法去外部来源&#xff08;目录、环境变量、单一 properties 文件、你自定义的来源……&#xff09;取…

编程工具的演进逻辑:从Python IDLE到Arduino IDE的深度剖析

引言:工具进化的本质 在编程学习与开发的道路上,我们总会与各种各样的工具相遇。一个有趣的现象是,无论是初学者的第一款工具Python IDLE,还是硬件爱好者常用的Thonny和Arduino IDE,它们都自称“集成开发环境”(IDE)。这背后隐藏着怎样的逻辑? 本文将带你深入分析这三…

p10k configure执行报错: ~/powerlevel10k/config/p10k-lean.zsh is not readable

[ERROR] p10k configure: ~/powerlevel10k/config/p10k-lean.zsh is not readable 背景 我移动了Powerlevel10k文件夹的位置&#xff0c;导致p10k configure命令找不到powerlevel10k文件夹的位置。 原来Powerlevel10k的位置&#xff1a;~/powerlevel10k 移动后Powerlevel10k的位…

Java 学习笔记(进阶篇3)

1. 美化界面关键逻辑 1&#xff1a;// 相对路径&#xff1a;直接从项目的 src 目录开始写&#xff0c;不包含 D:\ 和个人名字 ImageIcon bg new ImageIcon("src/image/background.png"); JLabel background new JLabel(bg);这两行代码是 Swing 中加载并显示图片的经…

BFD 概述

BFD简介1.BFD:Bidirectional Forwarding Detection,双向转发检查概述&#xff1a;毫秒级链路故障检查&#xff0c;通常结合三层协议&#xff08;如静态路由、vrrp、 ospf、 BGP等&#xff09;实现链路故障快速切换。作用&#xff1a;① 检测二层非直连故障② 加快三层协议收敛底…

【嵌入式DIY实例-ESP32篇】-Flappy Bird游戏

Flappy Bird游戏 文章目录 Flappy Bird游戏 1、游戏介绍 2、硬件准备与接线 3、代码实现 《Flappy Bird》游戏以其引人入胜的玩法和简约的设计风靡全球。本文将探讨如何使用 OLED SSD1306 显示屏和 ESP32 微控制器重现这款经典游戏。这个 DIY 项目不仅充满乐趣,也是学习编程和…

[数据结构——lesson2.顺序表]

目录 学习目标 引言 1.什么是线性表&#xff1f; 2.什么是顺序表&#xff1f; 2.1概念及结构 2.2 接口实现 2.2.1顺序表的功能 1.顺序表的初始化 2.打印数据 3.尾插数据 (1)检查空间 (2)插入数据 4.尾删数据 5.头插数据 6.头删数据 7.数据查找 8.指定位置数据…

ChatGPT大模型训练指南:如何借助动态代理IP提高训练效率

随着人工智能技术的飞速发展&#xff0c;ChatGPT等大型语言模型&#xff08;LLM&#xff09;已成为科技界和产业界关注的焦点。模型的训练过程耗时、耗资源且对网络环境要求极高。尤其是在需要模拟真实用户行为、进行大规模数据爬取或分布式训练的场景下&#xff0c;单一IP地址…