设计模式(C++)详解—单例模式(2) - 指南

news/2025/9/19 22:55:54/文章来源:https://www.cnblogs.com/tlnshuju/p/19101841

设计模式(C++)详解—单例模式(2) - 指南

2025-09-19 22:51  tlnshuju  阅读(0)  评论(0)    收藏  举报

<摘要>
单例模式是创建型设计模式中最简单但应用最广泛的模式之一,它确保一个类只有一个实例并提供全局访问点。本文从历史背景和核心概念出发,系统阐述了单例模式的产生缘由和演进脉络,深入剖析了其在资源管理、状态一致性和访问控制方面的设计意图。通过饿汉式、懒汉式、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 不安全懒汉式的竞态条件

4.1.2 双检锁模式的正确交互

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.mzph.cn/news/908149.shtml

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

相关文章

使用uv和pycharm搭建python开发环境

uv是一个Rust编写的极速Python包和项目管理工具。官网: https://docs.astral.sh/uv/ , 中文的详细使用文档: https://uv.doczh.com/ 可以用来安装和管理个多版本python,创建管理不同的虚拟环境,所谓虚拟环境是将包…

lc1032-字符流

难度:困难题目描述设计一个算法:接收一个字符流,并检查每个新字符加进来形成的新串,其后缀是否是字符串数组 words 中的一个字符串示例 输入: ["StreamChecker", "query", "query"…

lc1032-字符流

难度:困难题目描述设计一个算法:接收一个字符流,并检查每个新字符加进来形成的新串,其后缀是否是字符串数组 words 中的一个字符串示例 输入: ["StreamChecker", "query", "query"…

八股整理xdsm - 教程

pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; font-family: "Consolas", "Monaco", "Courier New", …

C++小白修仙记_LeetCode刷题_哈希表

哈希表(难度:easy) 217. 存在重复元素 给你一个整数数组 nums 。如果任一值在数组中出现 至少两次 ,返回 true ;如果数组中每个元素互不相同,返回 false 。 示例: 输入:nums = [1,2,3,1] 输出:true 解释: 元…

【F#学习】字符串String

字符串 F#的字符串和其他现代化的语言的字符串差异不大。 let fruit = "Apple"字符串可以通过调用其本身的函数来修改,也可以通过String模块下的函数来修改——但字符串是常量,一旦被创建就不可能发生改变…

US$98 Yanhua Mini ACDP Module4 BMW 35080, 35160DO WT EEPROM Read Write

Yanhua Mini ACDP Module 4 BMW 35080, 35160DO WT EEPROM Read & WriteNo need soldering.Function:Read and write BMW M35080, 35160DO WT etc EEPROM Yanhua Mini ACDP Module 4 Package includes:Item No. Ad…

US$98 Yanhua Mini ACDP Module4 BMW 35080, 35160DO WT EEPROM Read Write

Yanhua Mini ACDP Module 4 BMW 35080, 35160DO WT EEPROM Read & WriteNo need soldering.Function:Read and write BMW M35080, 35160DO WT etc EEPROM Yanhua Mini ACDP Module 4 Package includes:Item No. Ad…

深入解析:K8s学习笔记(二) Pod入门与实战

pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; font-family: "Consolas", "Monaco", "Courier New", …

现代汽车前瞻杯2025牛客暑期多校训练营3

F Flower 题意简化: 有一朵初始有n片花瓣的花,Yuki会按轮次摘花瓣:每轮操作中,她先摘a片花瓣,之后再摘b片花瓣;若剩余花瓣不足,就把剩下的全部摘完。这个过程会持续到所有花瓣被摘完为止。 Yuki的规则是:当且仅…

详细介绍:[新启航]白光干涉仪在微透镜阵列微观 3D 轮廓测量中的应用解析

pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; font-family: "Consolas", "Monaco", "Courier New", …

实用指南:多技术融合提升环境生态水文、土地土壤、农业大气等领域的数据分析与项目科研水平

实用指南:多技术融合提升环境生态水文、土地土壤、农业大气等领域的数据分析与项目科研水平pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !impor…

【F#学习】“变量”?绑定!

绑定 在F#中,给一个值标记上名字的过程叫作绑定(binding)。绑定是不可更改的,就像C#语言中的readonly或者const一样。因此,我们称这样的东西为绑定而非变量。由于F#是静态类型语言,所有的绑定必须在编译期就明确…

2023 CCPC 深圳 F

F. Gift 基环树处理环。 给一棵基环树,要求删掉一条边后还是一棵树,说明只能删掉这棵基环树上的环上的边。 删掉边后还要保证以 \(p\) 作为根节点时,其他节点的儿子数量不超过 \(3\),说明根节点的度数一定是小于等…

完整教程:【算法】双指针(三)[快慢指针]-快乐数

完整教程:【算法】双指针(三)[快慢指针]-快乐数pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; font-family: "Consolas", …

9.19做题资料:哈希表查找时间复杂度分析

好的,我用一个简单的比喻来解释,就像你在学校里找座位一样!1. 哈希表是什么? 想象一个教室里有好多桌子(这些桌子就是哈希表)。每张桌子都有一个编号(比如1号桌、2号桌、3号桌……)。老师规定:每个同学必须坐…

CF2143F Increasing Xor

我咋天天忘。还怎么学 OI 啊。 题意 给定长度为 \(n\) 的序列 \(a\),\(q\) 次询问给定 \(l,r\),每次查询提取出 \([l,r]\) 这段子区间,在这段子区间中你能执行任意次操作,每次操作任选 \(l\le i\le j\le r\) 然后 …

提到链接,你能想到什么

linux中的链接文件和虚拟机的克隆技术一.链接文件 在了解链接文件两种类型之前,必须先了解的知识: 文件在Linux中被分成两部分:数据(data block)和文件元数据(inode) inode与block 每个文件都有一个 inode(索引节…

实用指南:容器逃逸漏洞

实用指南:容器逃逸漏洞pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; font-family: "Consolas", "Monaco", &qu…

实用指南:容器逃逸漏洞

实用指南:容器逃逸漏洞pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; font-family: "Consolas", "Monaco", &qu…