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

在C++上实现反射用法

0. 简介

最近看很多端到端的工作,发现大多数都是基于mmdet3d来做的,而这个里面用的比较多的形式就是反射机制,这样其实可以比较好的通过类似plugin的形式完成模型模块的插入。当然我们这里不是来分析python的反射机制的。我们这篇文章主要来介绍C++上实现反射。

1. 反射的用途

一般来说就是序列化反序列化啦。比如说你想通过网络传递一个实例,或者把它保存到文件里,以后再取出来放到程序里,这就需要反射

反射其实还能细分为静态反射和动态反射

  1. 静态反射,就是在编译期生成反射信息
  2. 动态反射,就是在运行时生成反射信息
  3. 动态反射,显然需要一套强大的运行时和动态类型系统,也是显然的很复杂

在来,还有侵入式非侵入式之分。非侵入式的反射允许不对源码进行修改就能实现反射;侵入式呢就得对源码动动手脚了

第一种:实现思路,是在源码里加入大量的反射信息,手动注册反射。 这种库的代表是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方法会被调用,我们可以在这里生成反射信息并注册类。下面我们对上面代码中主要部分进行详细解释:

  1. ReflectionGenerator类:这是一个继承自MatchFinder::MatchCallback的自定义类,用于处理匹配到的AST节点。在run方法中,根据匹配到的C++类定义,获取类名并输出到控制台,同时遍历类的字段并输出字段名。

  2. main函数

    • 创建CommonOptionsParser对象和ClangTool对象,用于解析命令行参数和运行Clang工具。
    • 创建ReflectionGenerator对象和MatchFinder对象,用于注册匹配规则和处理匹配结果。
    • 通过Tool.run方法运行Clang工具,并传入匹配规则和处理结果的工厂对象。
  3. cxxRecordDecl matcher:使用Finder.addMatcher添加了一个匹配规则,用于匹配C++类的定义。当匹配到符合规则的AST节点时,会调用ReflectionGeneratorrun方法进行处理。

  4. 反射信息生成:在ReflectionGeneratorrun方法中,获取到类名和字段名后,输出到控制台。这里展示了获取类名和字段名的基本操作,您可以根据需求进一步扩展生成反射信息的逻辑。

此外这里我们可以通过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;
}
  1. FieldInfo 和 TypeInfo 结构体:这些结构体用于存储字段和类型的元信息。
  2. Reflection 类:这个类负责注册和存储类型元信息,并提供查询接口。
  3. REFLECTABLE 宏:这个宏用于简化类型元信息的注册过程。它声明一个友元函数,这个函数可以访问类的私有成员,并在编译期生成字段的名字和类型信息。
  4. registerType 和 getTypeInfo 方法
    • registerType 方法在编译期处理传入的字段名称,生成对应的字段信息并存储在一个哈希表中。
    • getTypeInfo 方法在运行时查询并返回存储的类型信息。
  5. MyClass 类:这是一个简单的示例类,通过 REFLECTABLE 宏声明它是可反射的。
  6. main 函数:在 main 函数中,我们创建一个 MyClass 的实例并注册它的类型信息,然后查询并输出这些信息。

这个例子展示了如何在 C++ 中利用模板和宏实现一个简单的反射机制。这个机制允许你在运行时访问类型的元信息,从而实现各种动态操作,例如序列化和反序列化、类型检查和动态调用等等。

5. 调试器的运行时信息形成反射

使用调试器的运行时信息来生成反射代码是一种非常高级的技术,通常需要结合调试器提供的 API 和脚本语言(例如 Python)进行自动化处理。这个过程大致可以分为以下几个步骤:

  1. 使用调试器获取目标程序的运行时信息
  2. 解析并提取元信息
  3. 生成对应的反射代码

首先,编写一个简单的 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. 简介 最近看很多端到端的工作&#xff0c;发现大多数都是基于mmdet3d来做的&#xff0c;而这个里面用的比较多的形式就是反射机制&#xff0c;这样其实可以比较好的通过类似plugin的形式完成模型模块的插入。当然我们这里不是来分析python的反射机制的。我们这篇文章主要来…...

【学术会议介绍,SPIE 出版】第四届计算机图形学、人工智能与数据处理国际学术会议 (ICCAID 2024,12月13-15日)

第四届计算机图形学、人工智能与数据处理国际学术会议 2024 4th International Conference on Computer Graphics, Artificial Intelligence and Data Processing (ICCAID 2024) 重要信息 大会官网&#xff1a;www.iccaid.net 大会时间&#xff1a;2024年12月13-15日 大会地…...

网络百问百答(一)

什么是链接&#xff1f; 链接是指两个设备之间的连接&#xff0c;它包括用于一个设备能够与另一个设备通信的电缆类型和协议。OSI参考模型的层次是什么&#xff1f; 有7个OSI层&#xff1a;物理层&#xff0c;数据链路层&#xff0c;网络层&#xff0c;传输层&#xff0c;会话层…...

【深圳大学】数据结构A+攻略(计软版)

1. 考试 1.1 形式 分为平时&#xff0c;笔试&#xff0c;机试三部分。其中&#xff1a; 平时占30%&#xff0c;包含平时OJ测验和课堂练习&#xff0c;注意这个可能会因老师的不同和课题组的新策略而改变。笔试占60%&#xff0c;是分值占比的主要部分。机试占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 的瑞士军刀&#xff1a;Requests 库第一部分&#xff1a;背景介绍第二部分&#xff1a;Requests 库是什么&#xff1f;第三部分&#xff1a;如何安装 Requests 库&#xff1f;第四部分&#xff1a;Requests 库的基本函数使用方法第五部分&#xff1a…...

PostgreSQL 页损坏如何修复

PostgreSQL 错误&#xff1a;关系 base/46501/52712 中的块 480 存在无效的页。 当我们在使用 PostgreSQL 数据库的时候&#xff0c;如果服务器发生 CRASH 或者断电等异常情况的时候&#xff0c;有可能会遇到上面的这个报错信息。那么我们如何去修复这个数据呢&#xff0c;以及…...

Leetcode 75 Sort colors

题意&#xff1a;荷兰国旗问题&#xff0c;给一个数组[0,0,2,1,0]&#xff0c;构造成[0,0,0,1,2]的形式&#xff0c;分成三块 https://leetcode.com/problems/sort-colors/description/ 题解&#xff1a; 在任意时刻&#xff0c;i 左边的数都是 0&#xff0c;k 右边的数都是 …...

如何进行数据库连接池的参数优化?

以下是进行数据库连接池参数优化的一些方法&#xff1a; 一、确定合适的初始连接数&#xff1a; 考虑因素&#xff1a;数据库的规模、应用程序的启动需求以及预期的初始负载。如果数据库规模较小且应用程序启动时对数据库的即时访问需求不高&#xff0c;可以将初始连接数设置…...

有了miniconda,再也不用担心python、nodejs、go的版本问题了

哈喽&#xff0c;大家好&#xff01;我是「励志前端小黑哥」&#xff0c;我带着最新发布的文章又来了&#xff01; 专注前端领域10年&#xff0c;专门分享那些没用的前端知识&#xff01; 今天要分享的内容&#xff0c;是一个免费的环境管理工具&#xff0c;它叫Miniconda&…...

openresty入门教程:init_by_lua_block

init_by_lua_block 是 Nginx 配置中用于在 Nginx 启动时执行 Lua 脚本的一个指令。这个指令通常用于初始化全局变量、设置共享内存&#xff0c;或者执行一些需要在服务器启动时完成的准备工作。 以下是一个简单的 init_by_lua_block 使用示例&#xff1a; 1. 安装 Nginx 和 L…...

sol机器人pump机器人如何实现盈利的?什么是Pump 扫链机器人?

什么是Pump 扫链机器人&#xff0c;它的盈利逻辑优化策略是什么&#xff1f; Pump 扫链机器人&#xff0c;通过智能化、自动化的买卖操作帮助投资者实现快速盈利。在此基础上&#xff0c;我们对该机器人的盈利逻辑进行了深度优化&#xff0c;涵盖了买入策略和止盈策略的各个方面…...

Spring-boot 后端java配置接口返回jsp页面

Spring-boot 后端java配置接口返回jsp页面 spring boot 基于spring MVC的基础上进行了改进&#xff0c; 将Controller 与ResponseBody 进行了合并成一个新的注解 RestController。 当用户请求时&#xff0c;需要有视图渲染的&#xff0c;与请求数据的请求分别使用 1.在appli…...

LabVIEW车辆侧翻预警系统

在工业和实验室环境中&#xff0c;搬运车辆、叉车和特种作业车辆经常在负载和高速转弯过程中发生侧翻事故&#xff0c;导致设备损坏和人员伤害。为提高工作环境的安全性&#xff0c;开发了一种基于LabVIEW的工业车辆侧翻预警系统&#xff0c;能够实时监测车辆状态并发出预警&am…...

亲测有效:Maven3.8.1使用Tomcat8插件启动项目

我本地maven的settings.xml文件中的配置&#xff1a; <mirror><id>aliyunmaven</id><mirrorOf>central</mirrorOf><name>阿里云公共仓库</name><url>https://maven.aliyun.com/repository/public</url> </mirror>…...

Find My电子体温计|苹果Find My技术与体温计结合,智能防丢,全球定位

电子体温计由温度传感器&#xff0c;液晶显示器&#xff0c;纽扣电池&#xff0c;专用集成电路及其他电子元器件组成。能快速准确地测量人体体温&#xff0c;与传统的水银玻璃体温计相比&#xff0c;具有读数方便&#xff0c;测量时间短&#xff0c;测量精度高&#xff0c;能记…...

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新增多媒体标签详解&#xff1a;视频标签与音频标签视频标签<video>音频标签<audio>代码案例 HTML5新增多媒体标签详解&#xff1a;视频标签与音频标签 HTML5引入了多项新特性&#xff0c;其中多媒体标签的引入为网页开发带来了革命性的变化。这些标…...

51c自动驾驶~合集10

我自己的原文哦~ https://blog.51cto.com/whaosoft/11638131 #端到端任务 说起端到端&#xff0c;每个从业者可能都觉得会是下一代自动驾驶量产方案绕不开的点&#xff01;特斯拉率先吹响了方案更新的号角&#xff0c;无论是完全端到端&#xff0c;还是专注于planner的模型&a…...

JAVA学习日记(十五) 数据结构

一、数据结构概述 数据结构是计算机底层存储、组织数据的方式。 数据结构是指数据相互之间以什么方式排列在一起的。 数据结构是为了更加方便的管理和使用数据&#xff0c;需要结合具体的业务场景来进行选择。 二、常见的数据结构 &#xff08;一&#xff09;栈 特点&…...

室内定位论文精华-无人机与机器人在地下与室内环境中的自主导航与定位新技术

天文导航算法在低成本视觉系统中的应用 关键词 天文导航;自主无人机;GNSS拒止环境;稳定成像系统;星图识别;姿态估计;位置估算 研究问题 现代无人驾驶飞行器(UAV)中,很少使用天文学导航技术。传统的天文学导航依赖于稳定的成像系统,这不仅体积大且重量重,难以满足…...

Java 中如何自定义一个类加载器,加载自己指定的类?

文章目录 为什么要自定义类加载器&#xff1f;类加载器的基本原理自定义类加载器的步骤1. 继承 ClassLoader 类2. 编写 findClass 方法 代码示例代码解释 使用自定义类加载器加载类执行结果注意事项总结推荐阅读文章 在 Java 中&#xff0c;类加载器&#xff08;ClassLoader&am…...

LeetCode【0037】解数独

本文目录 1 中文题目2 求解方法&#xff1a;递归回溯法2.1 方法思路2.2 Python代码2.3 复杂度分析 3 题目总结 1 中文题目 编写一个程序&#xff0c;通过填充空格来解决数独问题。数独的解法需 遵循如下规则&#xff1a; 数字 1-9 在每一行只能出现一次。数字 1-9 在每一列只…...

计算机视觉 ---常见图像文件格式及其特点

常见的图像文件格式及其特点如下&#xff1a; JPEG&#xff08;Joint Photographic Experts Group&#xff09; 特点&#xff1a; 有损压缩&#xff1a;通过丢弃一些图像数据来实现高压缩比&#xff0c;能显著减小文件大小&#xff0c;适合用于存储照片等色彩丰富的图像。但过…...

Cent OS-7的Apache服务配置

WWW是什么&#xff1f; WWW&#xff08;World Wide Web&#xff0c;万维网&#xff09;是一个全球性的信息空间&#xff0c;其中的文档和其他资源通过URL标识&#xff0c;并通过HTTP或其他协议访问。万维网是互联网的一个重要组成部分&#xff0c;但它并不是互联网的全部。互联…...

mysql每日一题(上升的温度,date数据的计算)

日期之间的运算 日期类型的加法运算 data_add(now_data,interval 1 month) select date_add(now(), interval 1 day); -- 加1天 select date_add(now(), interval 1 hour); -- 加1小时 select date_add(now(), interval 1 minute); -- 加1分钟 select date_add(now(), inter…...

前端人之网络通信概述

前端人之网络通信概述 介绍网络七层模型物理层链路层网络层传输层应用层 介绍 互联网的核心技术就是一系列协议&#xff0c;总称“互联网协议”&#xff0c;对电脑如何连接和组网作出详细的规定&#xff0c;理解了这些协议就理解了互联网的原理。 网络七层模型 互联网完成数…...

Python从0到100(七十二):Python OpenCV-OpenCV实现手势音量控制(文末送书)

前言&#xff1a; 零基础学Python&#xff1a;Python从0到100最新最全教程。 想做这件事情很久了&#xff0c;这次我更新了自己所写过的所有博客&#xff0c;汇集成了Python从0到100&#xff0c;共一百节课&#xff0c;帮助大家一个月时间里从零基础到学习Python基础语法、Pyth…...

【云原生开发】K8S多集群管理系统成果展示

✨✨ 欢迎大家来到景天科技苑✨✨ &#x1f388;&#x1f388; 养成好习惯&#xff0c;先赞后看哦~&#x1f388;&#x1f388; &#x1f3c6; 作者简介&#xff1a;景天科技苑 &#x1f3c6;《头衔》&#xff1a;大厂架构师&#xff0c;华为云开发者社区专家博主&#xff0c;…...

spring boot项目打成war包部署

1.修改pom.xml 在 pom.xml 里设置 <packaging>war</packaging>2.移除嵌入式tomcat插件 在 pom.xml 里找到spring-boot-starter-web依赖&#xff0c;在其中添加如下代码&#xff0c; <dependency><groupId>org.springframework.boot</groupId>&l…...