在C++上实现反射用法
0. 简介
最近看很多端到端的工作,发现大多数都是基于mmdet3d来做的,而这个里面用的比较多的形式就是反射机制,这样其实可以比较好的通过类似plugin的形式完成模型模块的插入。当然我们这里不是来分析python的反射机制的。我们这篇文章主要来介绍C++上实现反射。
1. 反射的用途
一般来说就是序列化反序列化啦。比如说你想通过网络传递一个实例,或者把它保存到文件里,以后再取出来放到程序里,这就需要反射
反射其实还能细分为静态反射和动态反射
- 静态反射,就是在编译期生成反射信息
- 动态反射,就是在运行时生成反射信息
- 动态反射,显然需要一套强大的运行时和动态类型系统,也是显然的很复杂
在来,还有侵入式非侵入式之分。非侵入式的反射允许不对源码进行修改就能实现反射;侵入式呢就得对源码动动手脚了。
第一种:实现思路,是在源码里加入大量的反射信息,手动注册反射。 这种库的代表是rttr
第二种:实现思路是通过parser解析源码,自动生成反射信息。
这种库的代表是QT,UE的反射系统
第三种: 用大量的编译期模板生成元信息,然后构建一套巨抽象的运行时,比如Ubpa/UDRefl
第四种: 利用调试器的运行时信息来生成反射代码,这种想法并非无稽之谈,思考下,lldb,gdb明显能在运行时获取字段,内容,类型
第五种: 绑架编译器! clang提供了插件功能。事实上也有大佬做了,这些都有比较详细的例子。
2. 源码添加,手动注册
这种用的是比较多的,一般的是自定义一个反射类,然后用模板来实现一个模板类管理类名和类构造函数的映射关系,并提供构造对象的接口,每个基类需要初始化一个这样的管理对象。
下面我们提供一个对应的 static 模板函数,用来保存和返回对应的管理对象。并使用模板函数和 new 操作符作为每个类的构造函数。
实现一个简单的 helper 模板类提供作为注册的简单封装,并封装宏实现注册。下面是具体代码:
#ifndef __BASE_H__
#define __BASE_H__
#include <string>
#include <map>
#include <iostream>// 使用模板,每个基类单独生成一个 ClassRegister
// 好处是需要反射的类不需要去继承 Object 对象
// ClassRegister 用来管理类名->类构造函数的映射,对外提供根据类名构造对象对函数
template<typename ClassName>
class ClassRegister {public:typedef ClassName* (*Constructor)(void);private:typedef std::map<std::string, Constructor> ClassMap;ClassMap constructor_map_;public:// 添加新类的构造函数void AddConstructor(const std::string class_name, Constructor constructor) {typename ClassMap::iterator it = constructor_map_.find(class_name);if (it != constructor_map_.end()) {std::cout << "error!";return;}constructor_map_[class_name] = constructor;}// 根据类名构造对象ClassName* CreateObject(const std::string class_name) const {typename ClassMap::const_iterator it = constructor_map_.find(class_name);if (it == constructor_map_.end()) {return nullptr;}return (*(it->second))();}
};// 用来保存每个基类的 ClassRegister static 对象,用于全局调用
template <typename ClassName>
ClassRegister<ClassName>& GetRegister() {static ClassRegister<ClassName> class_register;return class_register;
}// 每个类的构造函数,返回对应的base指针
template <typename BaseClassName, typename SubClassName>
BaseClassName* NewObject() {return new SubClassName();
}// 为每个类反射提供一个 helper,构造时就完成反射函数对注册
template<typename BaseClassName>
class ClassRegisterHelper {public:ClassRegisterHelper(const std::string sub_class_name,typename ClassRegister<BaseClassName>::Constructor constructor) {GetRegister<BaseClassName>().AddConstructor(sub_class_name, constructor);}~ClassRegisterHelper(){}
};// 提供反射类的注册宏,使用时仅提供基类类名和派生类类名
#define RegisterClass(base_class_name, sub_class_name) \static ClassRegisterHelper<base_class_name> \sub_class_name##_register_helper( \#sub_class_name, NewObject<base_class_name, sub_class_name>);// 创建对象的宏
#define CreateObject(base_class_name, sub_class_name_as_string) \GetRegister<base_class_name>().CreateObject(sub_class_name_as_string)#endif
下面是使用的示例:
#include <iostream>
#include <memory>
#include <cstring>
#include "base3.h"
using namespace std;class base
{public:base() {}virtual void test() { std::cout << "I'm base!" << std::endl; }virtual ~base() {}
};class A : public base
{public:A() { cout << " A constructor!" << endl; }virtual void test() { std::cout << "I'm A!" <<std::endl; }~A() { cout << " A destructor!" <<endl; }
};// 注册反射类 A
RegisterClass(base, A);class B : public base
{public :B() { cout << " B constructor!" << endl; }virtual void test() { std::cout << "I'm B!"; }~B() { cout << " B destructor!" <<endl; }
};// 注册反射类 B
RegisterClass(base, B);class base2
{public:base2() {}virtual void test() { std::cout << "I'm base2!" << std::endl; }virtual ~base2() {}
};class C : public base2
{public :C() { cout << " C constructor!" << endl; }virtual void test() { std::cout << "I'm C!" << std::endl; }~C(){ cout << " C destructor!" << endl; }
};// 注册反射类 C
RegisterClass(base2, C);int main()
{// 创建的时候提供基类和反射类的字符串类名base* p1 = CreateObject(base, "A");p1->test();delete p1;p1 = CreateObject(base, "B");p1->test();delete p1;base2* p2 = CreateObject(base2, "C");p2->test();delete p2;return 0;
}
3. parser解析源码,自动生成反射信息
要实现自动生成反射信息的功能,我们需要编写一个代码解析器,用于解析源代码并提取出需要的信息。一般来说,代码解析器会将源码转换为一棵抽象语法树(AST),然后对这棵树进行遍历,提取出需要的信息。
要使用Clang来解析源码并自动生成反射信息,可以借助Clang的AST(Abstract Syntax Tree)来实现。以下是一个简单的示例代码,演示如何使用Clang来解析源码并生成反射信息:
#include <iostream>
#include <string>
#include "clang/Tooling/Tooling.h"
#include "clang/Tooling/CommonOptionsParser.h"
#include "clang/Frontend/FrontendActions.h"
#include "clang/Tooling/Tooling.h"
#include "clang/AST/ASTConsumer.h"
#include "clang/AST/ASTContext.h"
#include "clang/ASTMatchers/ASTMatchers.h"
#include "clang/ASTMatchers/ASTMatchFinder.h"using namespace clang;
using namespace clang::tooling;
using namespace clang::ast_matchers;class ReflectionGenerator : public MatchFinder::MatchCallback {
public:virtual void run(const MatchFinder::MatchResult &Result) {if (const CXXRecordDecl *Record = Result.Nodes.getNodeAs<CXXRecordDecl>("class")) {std::string className = Record->getNameAsString();std::cout << "Registering class: " << className << std::endl;// 在这里可以生成反射信息并注册类// 获取类名,并输出到控制台std::cout << "Class name: " << className << std::endl;// 遍历类的字段,并输出到控制台for (const FieldDecl *Field : Record->fields()) {std::string fieldName = Field->getNameAsString();std::cout << "Field name: " << fieldName << std::endl;}}}
};int main(int argc, const char **argv) {CommonOptionsParser OptionsParser(argc, argv);ClangTool Tool(OptionsParser.getCompilations(), OptionsParser.getSourcePathList());ReflectionGenerator Generator;MatchFinder Finder;Finder.addMatcher(cxxRecordDecl().bind("class"), &Generator);return Tool.run(newFrontendActionFactory(&Finder).get());
}
在这个示例代码中,我们通过Clang的AST来解析源码,并使用AST Matchers来匹配C++类的声明。当匹配到一个类声明时,ReflectionGenerator类的run方法会被调用,我们可以在这里生成反射信息并注册类。下面我们对上面代码中主要部分进行详细解释:
-
ReflectionGenerator类:这是一个继承自
MatchFinder::MatchCallback的自定义类,用于处理匹配到的AST节点。在run方法中,根据匹配到的C++类定义,获取类名并输出到控制台,同时遍历类的字段并输出字段名。 -
main函数:
- 创建
CommonOptionsParser对象和ClangTool对象,用于解析命令行参数和运行Clang工具。 - 创建
ReflectionGenerator对象和MatchFinder对象,用于注册匹配规则和处理匹配结果。 - 通过
Tool.run方法运行Clang工具,并传入匹配规则和处理结果的工厂对象。
- 创建
-
cxxRecordDecl matcher:使用
Finder.addMatcher添加了一个匹配规则,用于匹配C++类的定义。当匹配到符合规则的AST节点时,会调用ReflectionGenerator的run方法进行处理。 -
反射信息生成:在
ReflectionGenerator的run方法中,获取到类名和字段名后,输出到控制台。这里展示了获取类名和字段名的基本操作,您可以根据需求进一步扩展生成反射信息的逻辑。
此外这里我们可以通过attribute((annotate(…))) 来完成相同的操作。__attribute__((annotate(...))) 的意义是为代码中的类、字段、函数等元素添加自定义的元数据信息。这些信息可以用于实现反射、元编程、代码生成等功能。通过注解,我们可以为代码中的各种元素添加描述、标签、类型信息等,使其更具有可读性和可维护性,同时也可以在程序运行时动态地获取这些信息并进行相应的操作。在下面的示例中,我们使用了 __attribute__((annotate("reflect_class", "BarClass")))、__attribute__((annotate("reflect_property", "int foo")))、__attribute__((annotate("reflect_func", "void setFoo(int)")) 等注解来为类、字段和函数添加反射信息。这些注解可以帮助我们在编译时或运行时识别和操作这些元素,实现更高级的功能。
以下是一个完整的示例代码,其中使用了属性拓展和注解来实现反射功能:
#include <iostream>#define RFL_CLASS(...) __attribute__((annotate("reflect_class", #__VA_ARGS__)))
#define RFL_PROPERTY(...) __attribute__((annotate("reflect_property", #__VA_ARGS__)))
#define RFL_FUNC(...) __attribute__((annotate("reflect_func", #__VA_ARGS__)))class RFL_CLASS("BarClass") Bar {
public:RFL_PROPERTY("int foo") int foo;RFL_FUNC("void setFoo(int)") void setFoo(int value) {foo = value;}RFL_FUNC("int getFoo()") int getFoo() {return foo;}
};int main() {Bar bar;bar.setFoo(42);std::cout << "Value of foo: " << bar.getFoo() << std::endl;return 0;
}
- 在
RFL_CLASS宏和类定义中,我们添加了一个字符串参数,用于指定类的名称。这样可以在反射时更准确地标识类。 - 在
RFL_PROPERTY宏和字段定义中,我们添加了一个字符串参数,用于指定字段的类型和名称。这样可以在反射时更准确地标识字段。 - 在
RFL_FUNC宏和成员函数定义中,我们添加了一个字符串参数,用于指定函数的签名。这样可以在反射时更准确地标识函数。
在main函数中,我们创建了一个Bar对象,并使用setFoo和getFoo函数来设置和获取foo字段的值。这些函数的签名和类/字段的信息都被注解添加到了代码中,以便在反射时能够准确地识别和访问它们。
3. 元信息结构反射机制
要在C++中构建一套可以在运行时使用的反射机制,你可以利用模板元编程在编译期生成类型元信息,并在运行时通过这些元信息进行反射操作。
首先,我们需要定义一种数据结构来存储类型的元信息。
#include <iostream>
#include <string>
#include <typeinfo>
#include <typeindex>
#include <unordered_map>
#include <vector>struct FieldInfo {std::string name;std::type_index type;size_t offset;
};struct TypeInfo {std::string name;std::vector<FieldInfo> fields;
};
然后,我们需要一个机制来自动生成这些元信息。这里我们用模板和宏来实现这一点。
#define REFLECTABLE(...) \friend struct Reflection; \static void reflect(Reflection& r) { \r.registerType(typeid(*this), #__VA_ARGS__, __VA_ARGS__); \}class Reflection {
public:template<typename T>void registerType(std::type_index type, const std::string& fieldNames, T& instance) {std::istringstream stream(fieldNames);std::string fieldName;size_t offset = 0;while (std::getline(stream, fieldName, ',')) {trim(fieldName);TypeInfo& typeInfo = typeRegistry[type];typeInfo.name = type.name();typeInfo.fields.push_back({ fieldName, typeid(instance. * (T::*)(T:: *) &fieldName), offset });offset += sizeof(fieldName);}}template<typename T>const TypeInfo& getTypeInfo() {return typeRegistry[std::type_index(typeid(T))];}private:std::unordered_map<std::type_index, TypeInfo> typeRegistry;void trim(std::string& s) {s.erase(0, s.find_first_not_of(' '));s.erase(s.find_last_not_of(' ') + 1);}
};
现在,我们可以定义一个类,并使用宏来使其成为可反射的。
class MyClass {
public:int x;float y;std::string z;REFLECTABLE(x, y, z)
};
4. 使用反射机制
最后,我们可以在运行时使用反射机制来访问类的元信息。
int main() {MyClass obj;Reflection reflection;// 注册类型信息obj.reflect(reflection);// 获取类型信息const TypeInfo& typeInfo = reflection.getTypeInfo<MyClass>();// 输出字段信息std::cout << "Type: " << typeInfo.name << std::endl;for (const auto& field : typeInfo.fields) {std::cout << "Field: " << field.name << ", Type: " << field.type.name() << ", Offset: " << field.offset << std::endl;}return 0;
}
- FieldInfo 和 TypeInfo 结构体:这些结构体用于存储字段和类型的元信息。
- Reflection 类:这个类负责注册和存储类型元信息,并提供查询接口。
- REFLECTABLE 宏:这个宏用于简化类型元信息的注册过程。它声明一个友元函数,这个函数可以访问类的私有成员,并在编译期生成字段的名字和类型信息。
- registerType 和 getTypeInfo 方法:
registerType方法在编译期处理传入的字段名称,生成对应的字段信息并存储在一个哈希表中。getTypeInfo方法在运行时查询并返回存储的类型信息。
- MyClass 类:这是一个简单的示例类,通过
REFLECTABLE宏声明它是可反射的。 - main 函数:在
main函数中,我们创建一个MyClass的实例并注册它的类型信息,然后查询并输出这些信息。
这个例子展示了如何在 C++ 中利用模板和宏实现一个简单的反射机制。这个机制允许你在运行时访问类型的元信息,从而实现各种动态操作,例如序列化和反序列化、类型检查和动态调用等等。
5. 调试器的运行时信息形成反射
使用调试器的运行时信息来生成反射代码是一种非常高级的技术,通常需要结合调试器提供的 API 和脚本语言(例如 Python)进行自动化处理。这个过程大致可以分为以下几个步骤:
- 使用调试器获取目标程序的运行时信息。
- 解析并提取元信息。
- 生成对应的反射代码。
首先,编写一个简单的 C++ 程序:
// example.cpp
#include <iostream>
#include <vector>
#include <string>class MyClass {
public:int x;float y;std::string z;void print() {std::cout << "x: " << x << ", y: " << y << ", z: " << z << std::endl;}
};int main() {MyClass obj;obj.x = 10;obj.y = 20.5f;obj.z = "Hello, World!";obj.print();return 0;
}
编译这个程序:
g++ -g example.cpp -o example
…详情请参照古月居
相关文章:
在C++上实现反射用法
0. 简介 最近看很多端到端的工作,发现大多数都是基于mmdet3d来做的,而这个里面用的比较多的形式就是反射机制,这样其实可以比较好的通过类似plugin的形式完成模型模块的插入。当然我们这里不是来分析python的反射机制的。我们这篇文章主要来…...
【学术会议介绍,SPIE 出版】第四届计算机图形学、人工智能与数据处理国际学术会议 (ICCAID 2024,12月13-15日)
第四届计算机图形学、人工智能与数据处理国际学术会议 2024 4th International Conference on Computer Graphics, Artificial Intelligence and Data Processing (ICCAID 2024) 重要信息 大会官网:www.iccaid.net 大会时间:2024年12月13-15日 大会地…...
网络百问百答(一)
什么是链接? 链接是指两个设备之间的连接,它包括用于一个设备能够与另一个设备通信的电缆类型和协议。OSI参考模型的层次是什么? 有7个OSI层:物理层,数据链路层,网络层,传输层,会话层…...
【深圳大学】数据结构A+攻略(计软版)
1. 考试 1.1 形式 分为平时,笔试,机试三部分。其中: 平时占30%,包含平时OJ测验和课堂练习,注意这个可能会因老师的不同和课题组的新策略而改变。笔试占60%,是分值占比的主要部分。机试占10%。 1.2 题型…...
解读《ARM Cortex-M3 与Cortex-M4 权威指南》——第4章 架构
推荐大佬做的讲解 可以帮助加深理解 ARM架构及汇编 Cortex-M3 和 Cortex-M4 处理器都是基于ARMv7-M架构 需要完成对编程模型、异常(如中断)如何处理、存储器映射、如何使用外设以及如何使用微控制器供应商提供的软件驱动库文件等 Cortex-M3和Cortex-M4处理器有两种操作状态…...
探索 Python HTTP 的瑞士军刀:Requests 库
文章目录 探索 Python HTTP 的瑞士军刀:Requests 库第一部分:背景介绍第二部分:Requests 库是什么?第三部分:如何安装 Requests 库?第四部分:Requests 库的基本函数使用方法第五部分:…...
PostgreSQL 页损坏如何修复
PostgreSQL 错误:关系 base/46501/52712 中的块 480 存在无效的页。 当我们在使用 PostgreSQL 数据库的时候,如果服务器发生 CRASH 或者断电等异常情况的时候,有可能会遇到上面的这个报错信息。那么我们如何去修复这个数据呢,以及…...
Leetcode 75 Sort colors
题意:荷兰国旗问题,给一个数组[0,0,2,1,0],构造成[0,0,0,1,2]的形式,分成三块 https://leetcode.com/problems/sort-colors/description/ 题解: 在任意时刻,i 左边的数都是 0,k 右边的数都是 …...
如何进行数据库连接池的参数优化?
以下是进行数据库连接池参数优化的一些方法: 一、确定合适的初始连接数: 考虑因素:数据库的规模、应用程序的启动需求以及预期的初始负载。如果数据库规模较小且应用程序启动时对数据库的即时访问需求不高,可以将初始连接数设置…...
有了miniconda,再也不用担心python、nodejs、go的版本问题了
哈喽,大家好!我是「励志前端小黑哥」,我带着最新发布的文章又来了! 专注前端领域10年,专门分享那些没用的前端知识! 今天要分享的内容,是一个免费的环境管理工具,它叫Miniconda&…...
openresty入门教程:init_by_lua_block
init_by_lua_block 是 Nginx 配置中用于在 Nginx 启动时执行 Lua 脚本的一个指令。这个指令通常用于初始化全局变量、设置共享内存,或者执行一些需要在服务器启动时完成的准备工作。 以下是一个简单的 init_by_lua_block 使用示例: 1. 安装 Nginx 和 L…...
sol机器人pump机器人如何实现盈利的?什么是Pump 扫链机器人?
什么是Pump 扫链机器人,它的盈利逻辑优化策略是什么? Pump 扫链机器人,通过智能化、自动化的买卖操作帮助投资者实现快速盈利。在此基础上,我们对该机器人的盈利逻辑进行了深度优化,涵盖了买入策略和止盈策略的各个方面…...
Spring-boot 后端java配置接口返回jsp页面
Spring-boot 后端java配置接口返回jsp页面 spring boot 基于spring MVC的基础上进行了改进, 将Controller 与ResponseBody 进行了合并成一个新的注解 RestController。 当用户请求时,需要有视图渲染的,与请求数据的请求分别使用 1.在appli…...
LabVIEW车辆侧翻预警系统
在工业和实验室环境中,搬运车辆、叉车和特种作业车辆经常在负载和高速转弯过程中发生侧翻事故,导致设备损坏和人员伤害。为提高工作环境的安全性,开发了一种基于LabVIEW的工业车辆侧翻预警系统,能够实时监测车辆状态并发出预警&am…...
亲测有效:Maven3.8.1使用Tomcat8插件启动项目
我本地maven的settings.xml文件中的配置: <mirror><id>aliyunmaven</id><mirrorOf>central</mirrorOf><name>阿里云公共仓库</name><url>https://maven.aliyun.com/repository/public</url> </mirror>…...
Find My电子体温计|苹果Find My技术与体温计结合,智能防丢,全球定位
电子体温计由温度传感器,液晶显示器,纽扣电池,专用集成电路及其他电子元器件组成。能快速准确地测量人体体温,与传统的水银玻璃体温计相比,具有读数方便,测量时间短,测量精度高,能记…...
jmeter常用配置元件介绍总结之后置处理器
系列文章目录 安装jmeter jmeter常用配置元件介绍总结之后置处理器 8.后置处理器8.1.CSS/JQuery提取器8.2.JSON JMESPath Extractor8.3.JSON提取器8.4.正则表达式提取器8.5.边界提取器8.5.Debug PostProcessor8.6.XPath2 Extractor8.7.XPath提取器8.8.结果状态处理器 8.后置处理…...
html5多媒体标签
文章目录 HTML5新增多媒体标签详解:视频标签与音频标签视频标签<video>音频标签<audio>代码案例 HTML5新增多媒体标签详解:视频标签与音频标签 HTML5引入了多项新特性,其中多媒体标签的引入为网页开发带来了革命性的变化。这些标…...
51c自动驾驶~合集10
我自己的原文哦~ https://blog.51cto.com/whaosoft/11638131 #端到端任务 说起端到端,每个从业者可能都觉得会是下一代自动驾驶量产方案绕不开的点!特斯拉率先吹响了方案更新的号角,无论是完全端到端,还是专注于planner的模型&a…...
JAVA学习日记(十五) 数据结构
一、数据结构概述 数据结构是计算机底层存储、组织数据的方式。 数据结构是指数据相互之间以什么方式排列在一起的。 数据结构是为了更加方便的管理和使用数据,需要结合具体的业务场景来进行选择。 二、常见的数据结构 (一)栈 特点&…...
未来机器人的大脑:如何用神经网络模拟器实现更智能的决策?
编辑:陈萍萍的公主一点人工一点智能 未来机器人的大脑:如何用神经网络模拟器实现更智能的决策?RWM通过双自回归机制有效解决了复合误差、部分可观测性和随机动力学等关键挑战,在不依赖领域特定归纳偏见的条件下实现了卓越的预测准…...
手游刚开服就被攻击怎么办?如何防御DDoS?
开服初期是手游最脆弱的阶段,极易成为DDoS攻击的目标。一旦遭遇攻击,可能导致服务器瘫痪、玩家流失,甚至造成巨大经济损失。本文为开发者提供一套简洁有效的应急与防御方案,帮助快速应对并构建长期防护体系。 一、遭遇攻击的紧急应…...
条件运算符
C中的三目运算符(也称条件运算符,英文:ternary operator)是一种简洁的条件选择语句,语法如下: 条件表达式 ? 表达式1 : 表达式2• 如果“条件表达式”为true,则整个表达式的结果为“表达式1”…...
服务器硬防的应用场景都有哪些?
服务器硬防是指一种通过硬件设备层面的安全措施来防御服务器系统受到网络攻击的方式,避免服务器受到各种恶意攻击和网络威胁,那么,服务器硬防通常都会应用在哪些场景当中呢? 硬防服务器中一般会配备入侵检测系统和预防系统&#x…...
大模型多显卡多服务器并行计算方法与实践指南
一、分布式训练概述 大规模语言模型的训练通常需要分布式计算技术,以解决单机资源不足的问题。分布式训练主要分为两种模式: 数据并行:将数据分片到不同设备,每个设备拥有完整的模型副本 模型并行:将模型分割到不同设备,每个设备处理部分模型计算 现代大模型训练通常结合…...
html-<abbr> 缩写或首字母缩略词
定义与作用 <abbr> 标签用于表示缩写或首字母缩略词,它可以帮助用户更好地理解缩写的含义,尤其是对于那些不熟悉该缩写的用户。 title 属性的内容提供了缩写的详细说明。当用户将鼠标悬停在缩写上时,会显示一个提示框。 示例&#x…...
视频行为标注工具BehaviLabel(源码+使用介绍+Windows.Exe版本)
前言: 最近在做行为检测相关的模型,用的是时空图卷积网络(STGCN),但原有kinetic-400数据集数据质量较低,需要进行细粒度的标注,同时粗略搜了下已有开源工具基本都集中于图像分割这块,…...
return this;返回的是谁
一个审批系统的示例来演示责任链模式的实现。假设公司需要处理不同金额的采购申请,不同级别的经理有不同的审批权限: // 抽象处理者:审批者 abstract class Approver {protected Approver successor; // 下一个处理者// 设置下一个处理者pub…...
算法:模拟
1.替换所有的问号 1576. 替换所有的问号 - 力扣(LeetCode) 遍历字符串:通过外层循环逐一检查每个字符。遇到 ? 时处理: 内层循环遍历小写字母(a 到 z)。对每个字母检查是否满足: 与…...
面向无人机海岸带生态系统监测的语义分割基准数据集
描述:海岸带生态系统的监测是维护生态平衡和可持续发展的重要任务。语义分割技术在遥感影像中的应用为海岸带生态系统的精准监测提供了有效手段。然而,目前该领域仍面临一个挑战,即缺乏公开的专门面向海岸带生态系统的语义分割基准数据集。受…...
