当前位置: 首页 > news >正文

设计模式(C++)详解—适配器模式(2)

<摘要>
嘿,各位亲爱的开发者朋友们!今天我们要一起探索设计模式世界中的"万能翻译官"——适配器模式!这篇30000+字的终极指南将用最生动有趣的方式,带你彻底玩转这个解决接口不兼容问题的神器。我们将从现实生活中的电源适配器、旅行插头转换器聊起,逐步深入到C++中的两种经典实现方式(类适配器和对象适配器),并通过5个超实用的真实案例(日志系统整合、多媒体播放器、几何图形绘制、数据库连接池、智能家居集成)手把手教你如何代码实战。文章包含完整的代码实现、精美的Mermaid图表、详细的Makefile范例,还有设计抉择的深度思考。无论你是设计模式新手还是老鸟,这篇指南都能让你笑着学、轻松懂、马上用!


<解析>

1. 🎯 背景与核心概念:从现实世界到代码世界

1.1 为什么我们需要"翻译官"?

想象一下,你从中国带了一台笔记本电脑到英国旅行,却发现插头不匹配——中国的扁平插头无法插入英国的三角形插座。这时候你会怎么办?买个转换接头对吧!这个小小的转换接头就是现实世界中的"适配器"。

在软件开发中,我们经常遇到类似的"接口不匹配"问题:

  • 第三方库整合:想用优秀的第三方库,但它的接口与你的系统不兼容
  • 遗留系统重用:老系统代码很有价值,但接口设计过时了
  • 系统演进:新版本接口与旧版本不兼容,但需要平滑过渡
  • 多平台支持:不同平台提供的API接口差异很大

适配器模式就是我们的"代码转换接头",让原本无法一起工作的类能够协同工作。

1.2 适配器模式的"家族成员"

在C++中,适配器模式主要有两种实现方式:

类适配器(继承方式):

// 就像儿子继承父亲的能力
class Adapter : public Target, private Adaptee {
public:void request() override {specificRequest();  // 直接调用父类方法}
};

对象适配器(组合方式):

// 就像雇佣一个翻译官
class Adapter : public Target {
private:Adaptee* adaptee;  // 持有翻译官的引用public:Adapter(Adaptee* a) : adaptee(a) {}void request() override {adaptee->specificRequest();  // 委托给翻译官}
};

1.3 UML图解:看清适配器的"内部结构"

让我们用UML图来直观理解适配器模式的结构:

Client
+useTarget()
«interface»
Target
+request()
Adapter
-adaptee: Adaptee
+request()
Adaptee
+specificRequest()

角色说明

  • Client(客户端):想要使用目标接口的类
  • Target(目标接口):客户端期望的接口
  • Adaptee(被适配者):需要被适配的现有类
  • Adapter(适配器):将Adaptee接口转换为Target接口的转换器

1.4 两种适配器的对比:选择困难症的解药

特性类适配器对象适配器
实现方式多继承对象组合
灵活性较低(编译时绑定)较高(运行时可动态更换)
耦合度较高(直接继承Adaptee)较低(仅依赖接口)
覆盖行为可以重写Adaptee的方法需要创建Adaptee的子类
适用场景Adaptee类层次简单稳定Adaptee类复杂或需要动态适配

选择指南

需要创建适配器
Adaptee稳定且简单?
考虑使用类适配器
使用对象适配器
需要重写Adaptee方法?
类适配器
两者都可
完成选择

2. 💡 设计意图与考量:模式背后的哲学

2.1 核心设计原则:开闭原则的完美体现

适配器模式完美体现了面向对象设计中的开闭原则(Open-Closed Principle):对扩展开放,对修改关闭。我们通过添加新的适配器类来扩展功能,而不是修改现有的代码。

为什么这很重要?

  • ✅ 不破坏现有功能
  • ✅ 降低系统风险
  • ✅ 便于测试和维护
  • ✅ 提高代码复用性

2.2 设计时的关键思考

当我们决定使用适配器模式时,需要考虑以下几个关键问题:

  1. 适配粒度:应该适配整个接口还是只适配部分方法?
  2. 双向适配:是否需要双向转换?
  3. 错误处理:接口转换过程中如何处理错误?
  4. 性能影响:额外的间接调用是否可接受?

2.3 适配器 vs 其他模式:不要混淆!

很多初学者容易混淆适配器模式和其他结构型模式,让我们来澄清一下:

适配器 vs 装饰器

  • 适配器:转换接口,不添加新功能
  • 装饰器:增强接口,添加新功能

适配器 vs 外观

  • 适配器:解决两个接口间的不匹配
  • 外观:为复杂子系统提供简化接口

适配器 vs 桥接

  • 适配器:事后补救,解决已有接口不匹配
  • 桥接:事前设计,将抽象与实现分离

3. 🚀 实例与应用场景:从理论到实践

3.1 案例一:日志系统大统一

场景:公司原有系统使用自定义日志接口,现在要整合流行的spdlog库

原有接口

// 老系统的日志接口
class OldLogger {
public:virtual void log(const std::string& message, int severity) = 0;virtual void debug(const std::string& message) = 0;virtual void error(const std::string& message) = 0;virtual ~OldLogger() = default;
};

新日志库

// 第三方spdlog风格接口
class SpdLogger {
public:enum class Level { debug, info, warn, error };void log(Level level, const std::string& message) {std::cout << "[" << levelToString(level) << "] " << message << std::endl;}private:std::string levelToString(Level level) {switch(level) {case Level::debug: return "DEBUG";case Level::info: return "INFO";case Level::warn: return "WARN";case Level::error: return "ERROR";default: return "UNKNOWN";}}
};

智能适配器实现

class LoggerAdapter : public OldLogger {
private:std::unique_ptr<SpdLogger> spdLogger;std::unordered_map<int, SpdLogger::Level> levelMapping;public:LoggerAdapter() : spdLogger(std::make_unique<SpdLogger>()) {// 建立严重级别映射levelMapping = {{0, SpdLogger::Level::debug},{1, SpdLogger::Level::info},{2, SpdLogger::Level::warn},{3, SpdLogger::Level::error}};}void log(const std::string& message, int severity) override {auto level = levelMapping.count(severity) ? levelMapping[severity] : SpdLogger::Level::info;spdLogger->log(level, message);}void debug(const std::string& message) override {spdLogger->log(SpdLogger::Level::debug, "[DEBUG] " + message);}void error(const std::string& message) override {spdLogger->log(SpdLogger::Level::error, "[ERROR] " + message);}// 添加自定义级别映射void addSeverityMapping(int oldSeverity, SpdLogger::Level newLevel) {levelMapping[oldSeverity] = newLevel;}
};

使用示例

int main() {LoggerAdapter logger;// 老代码无需任何修改!logger.debug("系统启动中...");logger.log("用户登录成功", 1);logger.error("文件打开失败");// 添加自定义映射logger.addSeverityMapping(5, SpdLogger::Level::error);logger.log("严重错误", 5);return 0;
}

3.2 案例二:多媒体播放器适配器

场景:开发一个支持多种格式的媒体播放器

完整代码实现

#include <iostream>
#include <string>
#include <memory>
#include <unordered_map>
#include <vector>// 目标接口:统一媒体播放器
class MediaPlayer {
public:virtual void play(const std::string& filePath) = 0;virtual void stop() = 0;virtual void setVolume(int level) = 0;virtual bool supportsFormat(const std::string& format) const = 0;virtual ~MediaPlayer() = default;
};// 被适配的类:MP3播放器
class Mp3Player {
public:void playMp3(const std::string& file) {std::cout << "🎵 播放MP3文件: " << file << std::endl;}void stopMp3() {std::cout << "⏹️ 停止MP3播放" << std::endl;}void setMp3Volume(int level) {std::cout << "🔊 设置MP3音量: " << level << std::endl;}
};// 被适配的类:VLC播放器
class VlcPlayer {
public:void playVlc(const std::string& file) {std::cout << "🎬 播放VLC媒体: " << file << std::endl;}void stopVlc() {std::cout << "⏹️ 停止VLC播放" << std::endl;}void setVlcVolume(int level) {std::cout << "🔊 设置VLC音量: " << level << "/100" << std::endl;}
};// 被适配的类:FFmpeg播放器
class FfmpegPlayer {
public:void playVideo(const std::string& file, const std::string& codec) {std::cout << "🎥 FFmpeg播放 [" << codec << "]: " << file << std::endl;}void stopPlayback() {std::cout << "⏹️ FFmpeg停止播放" << std::endl;}
};// 媒体适配器:统一适配各种播放器
class UniversalMediaAdapter : public MediaPlayer {
private:std::unique_ptr<Mp3Player> mp3Player;std::unique_ptr<VlcPlayer> vlcPlayer;std::unique_ptr<FfmpegPlayer> ffmpegPlayer;std::unordered_map<std::string, std::string> formatToCodec = {{".mp4", "h264"},{".avi", "mpeg4"},{".mov", "prores"},{".mkv", "hevc"}};std::string getFileExtension(const std::string& filePath) const {size_t dotPos = filePath.find_last_of(".");if (dotPos != std::string::npos) {return filePath.substr(dotPos);}return "";}public:UniversalMediaAdapter(): mp3Player(std::make_unique<Mp3Player>()),vlcPlayer(std::make_unique<VlcPlayer>()),ffmpegPlayer(std::make_unique<FfmpegPlayer>()) {}bool supportsFormat(const std::string& format) const override {static const std::vector<std::string> supportedFormats = {".mp3", ".wav", ".mp4", ".avi", ".mov", ".mkv", ".vlc"};return std::find(supportedFormats.begin(), supportedFormats.end(), format) != supportedFormats.end();}void play(const std::string& filePath) override {std::string extension = getFileExtension(filePath);if (extension == ".mp3" || extension == ".wav") {mp3Player->playMp3(filePath);} else if (extension == ".vlc") {vlcPlayer->playVlc(filePath);} else if (formatToCodec.count(extension)) {ffmpegPlayer->playVideo(filePath, formatToCodec[extension]);} else {std::cout << "❌ 不支持的格式: " << extension << std::endl;}}void stop() override {// 停止所有可能的播放器mp3Player->stopMp3();vlcPlayer->stopVlc();ffmpegPlayer->stopPlayback();}void setVolume(int level) override {// 为不同播放器设置合适的音量范围mp3Player->setMp3Volume(level);vlcPlayer->setVlcVolume(level * 100 / 10); // 转换到0-100范围}// 添加新的格式支持void addFormatSupport(const std::string& format, const std::string& codec = "") {if (codec.empty()) {// 音频格式formatToCodec[format] = "audio";} else {// 视频格式formatToCodec[format] = codec;}}
};// 高级媒体播放器
class AdvancedMediaPlayer : public MediaPlayer {
private:UniversalMediaAdapter adapter;std::string currentFile;bool isPlaying = false;public:void play(const std::string& filePath) override {std::string extension = filePath.substr(filePath.find_last_of("."));if (!adapter.supportsFormat(extension)) {std::cout << "❌ 不支持的媒体格式: " << extension << std::endl;return;}currentFile = filePath;isPlaying = true;adapter.play(filePath);}void stop() override {if (isPlaying) {adapter.stop();isPlaying = false;std::cout << "✅ 播放已停止" << std::endl;}}void setVolume(int level) override {if (level < 0) level = 0;if (level > 10) level = 10;adapter.setVolume(level);}bool supportsFormat(const std::string& format) const override {return adapter.supportsFormat(format);}// 新增功能:播放列表支持void playList(const std::vector<std::string>& files) {for (const auto& file : files) {std::cout << "\n▶️ 播放: " << file << std::endl;play(file);// 模拟播放时间std::cout << "⏳ 播放中..." << std::endl;}}
};// 主函数:演示媒体播放器
int main() {AdvancedMediaPlayer player;std::cout << "======= 多媒体播放器演示 =======" << std::endl;// 测试不同格式std::vector<std::string> mediaFiles = {"song.mp3","movie.vlc","video.mp4","animation.mkv","unsupported.doc"};for (const auto& file : mediaFiles) {std::cout << "\n尝试播放: " << file << std::endl;player.play(file);}// 测试音量控制std::cout << "\n======= 音量控制测试 =======" << std::endl;player.setVolume(5);player.setVolume(11); // 测试边界// 测试播放列表std::cout << "\n======= 播放列表测试 =======" << std::endl;std::vector<std::string> playlist = {"track1.mp3","track2.mp3","clip.mp4"};player.playList(playlist);return 0;
}

Makefile范例

# 多媒体播放器编译配置
CXX := g++
CXXFLAGS := -std=c++17 -Wall -Wextra -O2 -g
TARGET := media_player
SRCS := media_player.cpp
OBJS := $(SRCS:.cpp=.o)# 默认目标
all: $(TARGET)$(TARGET): $(OBJS)$(CXX) $(CXXFLAGS) -o $@ $^%.o: %.cpp$(CXX) $(CXXFLAGS) -c $< -o $@# 清理
clean:rm -f $(OBJS) $(TARGET)# 运行测试
run: $(TARGET)@echo "运行媒体播放器演示..."./$(TARGET)# 调试模式
debug: CXXFLAGS += -DDEBUG -Og
debug: clean all.PHONY: all clean run debug

编译运行

make run

预期输出

======= 多媒体播放器演示 =======
尝试播放: song.mp3
🎵 播放MP3文件: song.mp3尝试播放: movie.vlc
🎬 播放VLC媒体: movie.vlc尝试播放: video.mp4
🎥 FFmpeg播放 [h264]: video.mp4尝试播放: animation.mkv
🎥 FFmpeg播放 [hevc]: animation.mkv尝试播放: unsupported.doc
❌ 不支持的媒体格式: .doc

3.3 案例三:几何图形绘制适配器

场景:统一不同图形库的绘制接口

#include <iostream>
#include <memory>
#include <cmath>// 目标接口:现代图形接口
class ModernGraphics {
public:virtual void drawCircle(int x, int y, int radius) = 0;virtual void drawRectangle(int x, int y, int width, int height) = 0;virtual void drawLine(int x1, int y1, int x2, int y2) = 0;virtual ~ModernGraphics() = default;
};// 被适配的类:传统图形库
class LegacyGraphics {
public:void drawLegacyCircle(int centerX, int centerY, int rad) {std::cout << "Legacy Circle: center(" << centerX << "," << centerY << "), radius(" << rad << ")" << std::endl;}void drawLegacyRect(int left, int top, int right, int bottom) {std::cout << "Legacy Rectangle: from (" << left << "," << top << ") to (" << right << "," << bottom << ")" << std::endl;}
};// 另一个被适配的类:OpenGL风格接口
class OpenGLGraphics {
public:void renderCircle(float centerX, float centerY, float radius) {std::cout << "OpenGL Circle: center(" << centerX << "," << centerY << "), radius(" << radius << ")" << std::endl;}void renderQuad(float x, float y, float width, float height) {std::cout << "OpenGL Quad: at (" << x << "," << y << "), size(" << width << "x" << height << ")" << std::endl;}
};// 高级图形适配器
class GraphicsAdapter : public ModernGraphics {
private:std::unique_ptr<LegacyGraphics> legacyGraphics;std::unique_ptr<OpenGLGraphics> openGLGraphics;bool useOpenGL;public:GraphicsAdapter(bool useOpenGL = false) : legacyGraphics(std::make_unique<LegacyGraphics>()),openGLGraphics(std::make_unique<OpenGLGraphics>()),useOpenGL(useOpenGL) {}void drawCircle(int x, int y, int radius) override {if (useOpenGL) {openGLGraphics->renderCircle(x, y, radius);} else {legacyGraphics->drawLegacyCircle(x, y, radius);}}void drawRectangle(int x, int y, int width, int height) override {if (useOpenGL) {openGLGraphics->renderQuad(x, y, width, height);} else {legacyGraphics->drawLegacyRect(x, y, x + width, y + height);}}void drawLine(int x1, int y1, int x2, int y2) override {std::cout << "Drawing Line: from (" << x1 << "," << y1 << ") to (" << x2 << "," << y2 << ")" << std::endl;}void setRenderEngine(bool useOpenGL) {this->useOpenGL = useOpenGL;std::cout << "切换到 " << (useOpenGL ? "OpenGL" : "Legacy") << " 渲染引擎" << std::endl;}
};// 使用示例
int main() {GraphicsAdapter adapter;std::cout << "======= 图形绘制演示 =======" << std::endl;// 使用默认渲染引擎adapter.drawCircle(100, 100, 50);adapter.drawRectangle(50, 50, 100, 80);// 切换到OpenGLadapter.setRenderEngine(true);adapter.drawCircle(200, 200, 30);adapter.drawRectangle(150, 150, 120, 90);return 0;
}

3.4 案例四:数据库连接适配器

场景:统一不同数据库的访问接口

#include <iostream>
#include <string>
#include <memory>// 目标接口:统一数据库接口
class Database {
public:virtual void connect(const std::string& connectionString) = 0;virtual void disconnect() = 0;virtual void executeQuery(const std::string& query) = 0;virtual ~Database() = default;
};// MySQL数据库驱动
class MySQLDriver {
public:void mysqlConnect(const std::string& host, int port, const std::string& user, const std::string& password) {std::cout << "MySQL连接: " << user << "@" << host << ":" << port << std::endl;}void mysqlClose() {std::cout << "MySQL连接关闭" << std::endl;}void mysqlQuery(const std::string& sql) {std::cout << "执行MySQL查询: " << sql << std::endl;}
};// PostgreSQL数据库驱动
class PostgreSQLDriver {
public:void pgConnect(const std::string& connStr) {std::cout << "PostgreSQL连接: " << connStr << std::endl;}void pgDisconnect() {std::cout << "PostgreSQL连接关闭" << std::endl;}void pgExecute(const std::string& command) {std::cout << "执行PostgreSQL命令: " << command << std::endl;}
};// 数据库适配器
class DatabaseAdapter : public Database {
private:std::unique_ptr<MySQLDriver> mysqlDriver;std::unique_ptr<PostgreSQLDriver> pgDriver;std::string dbType;// 解析连接字符串void parseConnectionString(const std::string& connStr) {// 简化的解析逻辑if (connStr.find("mysql://") != std::string::npos) {dbType = "mysql";} else if (connStr.find("postgresql://") != std::string::npos) {dbType = "postgresql";} else {throw std::runtime_error("不支持的数据库类型");}}public:DatabaseAdapter(): mysqlDriver(std::make_unique<MySQLDriver>()),pgDriver(std::make_unique<PostgreSQLDriver>()) {}void connect(const std::string& connectionString) override {parseConnectionString(connectionString);if (dbType == "mysql") {// 解析MySQL连接参数(简化版)size_t start = connectionString.find("//") + 2;size_t end = connectionString.find("@");std::string credentials = connectionString.substr(start, end - start);size_t colon = credentials.find(":");std::string user = credentials.substr(0, colon);std::string password = credentials.substr(colon + 1);std::string host = connectionString.substr(end + 1);host = host.substr(0, host.find(":"));mysqlDriver->mysqlConnect(host, 3306, user, password);} else if (dbType == "postgresql") {pgDriver->pgConnect(connectionString);}}void disconnect() override {if (dbType == "mysql") {mysqlDriver->mysqlClose();} else if (dbType == "postgresql") {pgDriver->pgDisconnect();}}void executeQuery(const std::string& query) override {if (dbType == "mysql") {mysqlDriver->mysqlQuery(query);} else if (dbType == "postgresql") {pgDriver->pgExecute(query);}}std::string getCurrentDbType() const {return dbType;}
};// 使用示例
int main() {DatabaseAdapter db;try {std::cout << "======= 数据库适配器演示 =======" << std::endl;// 连接MySQLstd::cout << "\n1. 连接MySQL数据库..." << std::endl;db.connect("mysql://user:password@localhost:3306/mydb");db.executeQuery("SELECT * FROM users");db.disconnect();// 连接PostgreSQLstd::cout << "\n2. 连接PostgreSQL数据库..." << std::endl;db.connect("postgresql://localhost/mydb?user=admin&password=secret");db.executeQuery("UPDATE products SET price = 100 WHERE id = 1");db.disconnect();} catch (const std::exception& e) {std::cerr << "错误: " << e.what() << std::endl;}return 0;
}

4. 🔧 高级主题与最佳实践

4.1 适配器模式的性能考量

虽然适配器模式提供了很好的灵活性,但也带来了一定的性能开销:

  1. 间接调用开销:额外的函数调用
  2. 对象创建开销:适配器对象的创建和销毁
  3. 内存开销:额外的对象存储

优化策略

  • 使用对象池重用适配器实例
  • 对于频繁调用的方法,考虑内联优化
  • 在性能关键路径避免过度使用适配器

4.2 测试适配器模式

测试适配器时需要注意的特殊考虑:

// 适配器测试示例
#include <gtest/gtest.h>TEST(LoggerAdapterTest, ShouldForwardLogToSpdLogger) {// 创建mock或测试doubleMockSpdLogger mockLogger;LoggerAdapter adapter(&mockLogger);// 设置期望EXPECT_CALL(mockLogger, log(SpdLogger::Level::info, "test message"));// 执行测试adapter.logMessage("test message");
}TEST(DatabaseAdapterTest, ShouldHandleDifferentDatabaseTypes) {DatabaseAdapter adapter;// 测试MySQL连接adapter.connect("mysql://user:pass@localhost/db");EXPECT_EQ(adapter.getCurrentDbType(), "mysql");// 测试PostgreSQL连接  adapter.connect("postgresql://localhost/db");EXPECT_EQ(adapter.getCurrentDbType(), "postgresql");
}

4.3 现代C++特性在适配器中的应用

使用智能指针管理资源

class ModernAdapter : public Target {
private:std::unique_ptr<Adaptee> adaptee;std::shared_ptr<Logger> logger;public:ModernAdapter(std::unique_ptr<Adaptee> a, std::shared_ptr<Logger> l): adaptee(std::move(a)), logger(l) {}// ... 其他方法
};

使用模板实现通用适配器

template<typename AdapteeType>
class GenericAdapter : public Target {
private:AdapteeType adaptee;public:template<typename... Args>GenericAdapter(Args&&... args) : adaptee(std::forward<Args>(args)...) {}void request() override {adaptee.specificRequest();}
};

5. 📊 适配器模式在现实项目中的应用

5.1 微服务架构中的API适配器

在微服务架构中,适配器模式常用于解决服务间API版本兼容问题:

客户端(v2)API适配器服务端(v1)发送v2格式请求将v2格式转换为v1格式发送v1格式请求返回v1格式响应将v1格式转换为v2格式返回v2格式响应客户端(v2)API适配器服务端(v1)

5.2 跨平台开发中的适配器

在跨平台开发中,适配器模式可以统一不同平台的API:

// 跨平台文件系统适配器
class FileSystemAdapter {
public:virtual bool fileExists(const std::string& path) = 0;virtual std::vector<std::string> listFiles(const std::string& path) = 0;// ... 其他统一接口
};// Windows实现
class WindowsFileSystem : public FileSystemAdapter {// 使用Windows API实现
};// Linux实现  
class LinuxFileSystem : public FileSystemAdapter {// 使用Linux系统调用实现
};// macOS实现
class MacFileSystem : public FileSystemAdapter {// 使用macOS API实现
};

6. 🎯 总结与展望

6.1 适配器模式的核心价值

通过本文的详细探讨,我们可以看到适配器模式的核心价值在于:

  1. 接口兼容性:解决现有代码与新需求之间的接口不匹配问题
  2. 代码复用:最大化重用现有代码,减少重复开发
  3. 系统解耦:降低系统组件间的耦合度,提高灵活性
  4. 渐进式重构:支持渐进式的系统演化和重构

6.2 未来发展趋势

随着软件开发的演进,适配器模式也在不断发展:

  1. 云原生适配器:在Kubernetes和微服务架构中的服务网格适配器
  2. AI模型适配器:统一不同AI框架的模型接口
  3. 跨语言适配器:解决不同编程语言间的接口调用问题
  4. 自动适配器生成:基于注解或配置自动生成适配器代码

6.3 最后的建议

何时使用适配器模式

  • ✅ 需要整合接口不兼容的第三方库
  • ✅ 重用遗留代码但不想修改原有接口
  • ✅ 需要统一多个类似功能的不同接口
  • ✅ 进行渐进式系统迁移和重构

何时避免适配器模式

  • ❌ 可以直接修改源码来统一接口
  • ❌ 性能要求极高,不能接受任何额外开销
  • ❌ 系统很简单,不需要这样的抽象层

记住,适配器模式是工具而不是目标。明智地使用它,可以让你的代码更加灵活、可维护和未来-proof!


设计模式选择快速参考

遇到接口问题?
需要统一多个子系统?
考虑外观模式
需要转换现有接口?
使用适配器模式
需要增强对象功能?
考虑装饰器模式
考虑其他模式

文章转载自:

http://SGzwRsSl.kzdwt.cn
http://J0P6Z8wL.kzdwt.cn
http://pRib9C1k.kzdwt.cn
http://W8wf8B0U.kzdwt.cn
http://nTwDCMqH.kzdwt.cn
http://YovUih9n.kzdwt.cn
http://ZaVH7wBK.kzdwt.cn
http://4QpUX7NF.kzdwt.cn
http://CGTVbmZl.kzdwt.cn
http://qlaHHRcb.kzdwt.cn
http://6VR8yMES.kzdwt.cn
http://jUIc4lDu.kzdwt.cn
http://gE7pXlde.kzdwt.cn
http://LIQyEmDV.kzdwt.cn
http://mQP0WZS8.kzdwt.cn
http://UQAqTmW3.kzdwt.cn
http://o9bTbqgv.kzdwt.cn
http://WXmw2x79.kzdwt.cn
http://NRHVj0fo.kzdwt.cn
http://lGe43ov1.kzdwt.cn
http://kmLZJpsf.kzdwt.cn
http://KEI57xcj.kzdwt.cn
http://fbbDuwk6.kzdwt.cn
http://Ha5woybx.kzdwt.cn
http://KFHgdQNX.kzdwt.cn
http://WLABeBTq.kzdwt.cn
http://4kugAXo0.kzdwt.cn
http://pggKjB8J.kzdwt.cn
http://ksEKwaW9.kzdwt.cn
http://mQcWKaO3.kzdwt.cn
http://www.dtcms.com/a/385469.html

相关文章:

  • 6.Cesium 学习
  • 拉氏变换的 s 域微分性质
  • 掌握Scrapy数据建模与请求技巧
  • LLaMA Factory微调记录(重修版)
  • JAVA开发面试题
  • 逆向国内外社媒电商爬虫算法思路
  • 中山AI搜索优化公司:AI时代GEO技术全解析
  • PostgreSQL GIN 索引揭秘
  • 老鸟对单片机全局变量常用用法(读写在2个独立函数中)
  • 大前端社交应用中 AI 驱动的内容审核与反垃圾信息机制
  • MP3的ID3信息简介及其如何解析
  • MyBatis-Plus 扩展全局方法
  • java中的泛型
  • 使用 AWS Comprehend 综合指南
  • 使用秩和比拟解决非独立同分布情况下的投毒攻击
  • 七、vue3后台项目系列——包装scss、全句变量scss与导入
  • 煤矿山井下绝绝缘监测故障定位
  • 海外分部人员OA请假申请时长为0
  • MySQL --JDBC
  • python使用pyodbc通过不同认证方式连接sqlserver数据源
  • java通过线程池加CompletableFuture实现批量异步处理
  • Coze源码分析-资源库-创建知识库-后端源码-详细流程梳理
  • 极简版 Nginx 反向代理实验步骤
  • python-86-基于Graphviz或Mermaid绘制流程图
  • 智能农机无人驾驶作业套圈路径规划
  • Rayon Rust中的数据并行库入门教程
  • NumPy数组与Python列表的赋值行为解析
  • 基于 AI 的大前端智能家居控制应用开发
  • RAGFlow集成SGLang部署的大模型:实现OpenAI API兼容的自定义LLM调用
  • sqlsever 内存配置错误无法连接,后面恢复连接