CppCon 2015 学习:REFLECTION TECHNIQUES IN C++

news2025/6/10 13:38:07

关于 Reflection(反射) 这个概念,总结一下:

Reflection(反射)是什么?

  • 反射是对类型的自我检查能力(Introspection)
    可以查看类的成员变量、成员函数等信息。
  • 反射允许枚举类的成员
    比如你可以遍历一个类的字段列表。
  • 不等同于基于谓词的检查
    比如简单地判断一个类是否有某个成员函数,不是完整的反射。
  • 用来表达数据结构为 C++ 类
    反射帮助实现从数据到类的映射,比如序列化和反序列化。
  • C++ 中没有内建反射机制
    C++ 标准目前没有提供像 Java 或 C# 那样的运行时反射。
  • C++ 的反射数据可以存储在编译期
    通过模板元编程、宏或者外部工具,反射信息往往在编译阶段生成或存储。
  • C++ 反射提案正在研究中
    标准化组织(ISO C++)中有小组在推动反射机制的标准化。

总结

C++ 目前没有“真正的”反射,但可以通过编译期技术模拟;而运行时反射是其他语言的常见特性。反射的目标是让程序可以动态查询和操作类型信息,方便泛型编程、序列化、调试工具等场景。

关于 消息序列化到 JSON 和 XML,关键点是:

  • 字段名(field names)被当作
    • 在 JSON 中作为 对象的键(key)
    • 在 XML 中作为 标签名(tag name)
      这意味着:
  • 数据结构的每个字段都映射成了对应的 JSON 键或 XML 标签
  • 序列化后的格式保留字段名信息,方便反序列化或人读
    简单举例:
    假设有一个结构体:
struct Person {
  std::string name;
  int age;
};
  • 序列化成 JSON 可能是:
{
  "name": "Alice",
  "age": 30
}
  • 序列化成 XML 可能是:
<Person>
  <name>Alice</name>
  <age>30</age>
</Person>

关于 对象关系映射(ORM, Object-Relational Mapping)到数据库

  • 需要用注解(Attributes)标注字段,告诉ORM框架:
    • 哪些字段对应数据库的列(columns)
    • 哪些字段是主键(primary key)
    • 字段的数据类型、约束、索引等信息
  • 通过注解,ORM能自动:
    • 把对象属性映射到数据库表的字段
    • 自动生成SQL语句,实现增删改查操作
    • 维护对象和数据库记录之间的同步
      比如在C#中:
public class User
{
  [Key] // 主键
  public int Id { get; set; }
  [Column("user_name")] // 映射到数据库列 user_name
  public string Name { get; set; }
}

ORM框架就知道怎么把 User 类对应到数据库表的结构上。

“General data-driven development”(通用数据驱动开发)理解如下:

  • 数据驱动开发是一种开发范式,核心思想是应用程序的行为、流程和逻辑主要由数据(配置、模型、规则等)来驱动,而不是硬编码在代码里。
  • 代码结构围绕数据的定义和流动设计,实现高度的灵活性和可扩展性。
  • 这可以减少对代码改动的需求,通过修改数据就能调整系统行为,适应业务需求变化。
  • 在工业物联网(IIoT)、流处理、DDS和Rx的背景下,数据驱动开发强调“数据即中心”,各种模块通过数据流(比如DDS Topic)连接,实现异步、响应式、可扩展的系统架构。
    总结:
    数据驱动开发是用数据定义和控制程序行为的开发方法,使系统更灵活、可维护和响应实时变化。

这段代码演示了如何使用**std::tuple(C++11标准库中的元组)来存储不同类型的多个值,并通过at_c**(通常是Boost库中的元组访问函数,或用std::get代替)访问它们。

代码解析

auto stuff = std::make_tuple(1, 'x', "howdy");
  • 创建一个元组,包含3个不同类型的元素:
    • int 1
    • char ‘x’
    • 字符串字面量 "howdy"(会被推断成 const char*
int i = at_c<0>(stuff);
  • 取元组中索引为0的元素,即整数1,赋值给i
char ch = at_c<1>(stuff);
  • 取索引为1的元素,即字符'x',赋值给ch
std::string s = at_c<2>(stuff);
  • 取索引为2的元素,即字符串"howdy",并构造成std::string赋给s

说明

  • std::tuple允许你把多个不同类型的值组合在一起。
  • 访问元素时,C++标准库用的是 std::get<index>(tuple),比如:
    int i = std::get<0>(stuff);
    
  • 你这里的at_c是类似功能的函数,通常来自Boost库boost::tupleboost::hana

总结

  • 元组是一种将多个异类型值聚合到一起的容器。
  • at_cstd::get按索引访问对应元素。
  • 这种结构常用于函数返回多个值,或者临时存储不同类型数据。

这段代码尝试实现一个模板函数 print_xml,用于打印一个集合 t 中的每个元素为 XML 格式:

template<class T>
void print_xml(const T& t)
{
    for_each(t, [](const auto& x)
    {
        std::cout
            << '<' << typeid(x).name() << '>'
            << x
            << "</" << typeid(x).name() << '>';
    });
}

代码解析:

  • template<class T> 表明这是一个模板函数,接受任意类型的 t
  • for_each(t, [](const auto& x) { ... }):对 t 中的每个元素调用 lambda 函数打印。
  • typeid(x).name() 打印元素的类型名字(类型名未必直观,依赖编译器实现)。
  • 输出类似:
    <i>1</i><c>x</c><PKc>howdy</PKc>
    
    这里 iintccharPKcconst char* 类型名字(根据编译器可能不同)。

关键点

  1. for_each 需要支持迭代的容器
    for_each 需要 t 是可迭代的容器或者类型,例如 std::vectorstd::list
    std::tuple 不是可迭代的,因此 for_each(stuff, ...) 不会直接编译。
  2. 如果 t 是 tuple,需要专门写针对 tuple 的遍历
    std::tuple 要遍历一般用递归模板或者 std::apply,而不是普通 for_each
  3. typeid(x).name() 输出是编译器特定的缩写,不适合用作XML标签
    如果想要清晰标签,通常手动写字符串,或用反射技术。
  4. x 需要能直接用 operator<< 输出
    只有支持流输出运算符的类型,才能这样打印。

改进建议

如果你的 stuffstd::tuple,可以用下面示例打印每个元素的值:

#include <iostream>
#include <tuple>
// 递归遍历tuple的辅助模板
template<std::size_t I = 0, typename TupleType>
typename std::enable_if<I == std::tuple_size<TupleType>::value>::type
print_xml(const TupleType& t)
{
    // 递归结束,无操作
}
template<std::size_t I = 0, typename TupleType>
typename std::enable_if<I < std::tuple_size<TupleType>::value>::type
print_xml(const TupleType& t)
{
    auto& x = std::get<I>(t);
    std::cout << "<item>" << x << "</item>\n";
    print_xml<I + 1>(t);
}
int main()
{
    auto stuff = std::make_tuple(1, 'x', "howdy");
    print_xml(stuff);
}

输出:

<item>1</item>
<item>x</item>
<item>howdy</item>

总结

  • 你的代码思想是好的:打印成 XML 标签的形式。
  • for_each 不支持 tuple,需要用递归模板或 std::apply 遍历 tuple。
  • typeid(x).name() 打印的标签名不友好,实际用时一般写固定标签名。
  • 输出内容需要支持 operator<<

这段代码用到了 Boost.Fusion 库的一个宏 BOOST_FUSION_ADAPT_STRUCT,它的作用是:

作用

将普通的 C++ struct 适配成 Boost.Fusion 可识别的“序列”(Sequence)类型。

具体解释

struct person
{
    std::string name;
    int age;
};
BOOST_FUSION_ADAPT_STRUCT(person,
    (std::string, name)
    (int, age)
);
  • struct person 是一个普通的结构体,包含两个字段:name(字符串类型)和 age(整数类型)。
  • BOOST_FUSION_ADAPT_STRUCT 宏告诉 Boost.Fusion 库,person 是一个“结构体序列”,由两个成员组成:
    1. 类型为 std::string,名字为 name
    2. 类型为 int,名字为 age

这样做的好处:

  • person 这个结构体可以被 Boost.Fusion 库的算法和函数当作“序列”来处理,比如:
    • 迭代访问成员
    • 应用元编程算法(如 for_eachtransform
    • 支持序列相关的算法和特性
  • 支持自动序列化、比较、打印等功能(结合其他 Boost 库使用时)

举例

适配后,你可以这样写:

person p{"Alice", 30};
// 使用 Boost.Fusion 的for_each来访问成员
boost::fusion::for_each(p, [](auto& field){
    std::cout << field << "\n";
});

输出:

Alice
30

总结

BOOST_FUSION_ADAPT_STRUCT 是 Boost.Fusion 用来“桥接”普通 struct 和 Fusion 序列的工具,方便在模板元编程和反射式操作中使用 struct 的成员。

这段代码是用 Boost.Fusion 库的宏 BOOST_FUSION_DEFINE_STRUCT 来定义一个结构体 person,它和普通的 struct 定义结合了 Boost.Fusion 的序列特性。

代码解读

BOOST_FUSION_DEFINE_STRUCT((), person,
    (std::string, name)
    (int, age)
);
  • BOOST_FUSION_DEFINE_STRUCT 直接定义了一个结构体,并且同时把它适配成 Boost.Fusion 的序列。
  • () 表示命名空间,这里空代表在全局命名空间定义。
  • person 是结构体名字。
  • 后面括号里的每一项 (类型, 名字) 表示结构体的成员。

效果

  • 等价于写:
struct person
{
    std::string name;
    int age;
};
BOOST_FUSION_ADAPT_STRUCT(person,
    (std::string, name)
    (int, age)
);
  • 但它一步到位,既定义了结构体,也完成了 Fusion 适配。

好处

  • 少写代码,结构体和适配同时完成
  • 方便在 Boost.Fusion 中使用这个结构体作为序列,比如用于序列操作、序列遍历、元编程等

小结

  • BOOST_FUSION_DEFINE_STRUCT 是 Boost.Fusion 提供的宏,用于定义结构体并自动适配 Fusion 序列接口
  • 适合想快速定义可用于 Boost.Fusion 的结构体,避免写两遍代码(定义 + 适配)。

这段代码结合了Boost.Fusion适配的结构体和tuple风格的访问,具体解释如下:

代码分析

person p = { "Tom", 52 };
std::string name = at_c<0>(p);
int age = at_c<1>(p);
print_xml(p);
  • person p = { "Tom", 52 };
    这是直接初始化一个person结构体实例,成员name"Tom"age52
  • std::string name = at_c<0>(p);
    这里at_c<0>(p)利用Boost.Fusion提供的接口访问person结构体的第0个成员,也就是name,并赋值给name变量。
  • int age = at_c<1>(p);
    同理,访问第1个成员age
  • print_xml(p);
    这是你之前写的print_xml函数,遍历结构体成员,打印成XML格式,比如:
<std::string>Tom</std::string><int>52</int>

总结

  • at_c<N>可以像操作tuple一样,访问被Boost.Fusion适配过的结构体成员。
  • 你可以用它避免写p.namep.age,实现更泛化的访问。
  • print_xml函数配合Boost.Fusion,能够自动遍历结构体成员并打印。

这段代码展示了如何在编译时或运行时获取结构体成员的名字,对应于第0和第1个成员:

解释

std::string name = struct_member_name<person, 0>::call();
std::string age = struct_member_name<person, 1>::call();
  • struct_member_name<person, 0>::call() 表示获取 person 结构体第0个成员的名称字符串(比如 "name")。
  • struct_member_name<person, 1>::call() 表示获取第1个成员的名称字符串(比如 "age")。

背景

C++ 本身不支持直接反射(获取成员名),这通常是借助:

  • Boost.Fusion 或 Boost.Hana 等库,通过宏定义或模板元编程实现的成员名映射。
  • 自定义模板或宏,结合结构体适配宏,将成员名存储为字符串字面量,从而能通过模板索引访问。

总结

这两句代码用来:

  • 动态获取结构体成员的名称(字符串),方便做序列化、打印、映射等操作。
  • at_c 获取成员值配合使用,可以实现成员名和值的对应处理。

“将结构体成员的名字和对应的值配对,形成一个类似于tuple的结构”,比如:

std::make_tuple(
  std::make_tuple(name, "name"),
  std::make_tuple(age, "age")
)

这表示:

  • 第一项是 (name的值, "name")
  • 第二项是 (age的值, "age")

理解

这个操作的目的是让数据和字段名关联起来,方便做比如:

  • JSON/XML序列化(键值对)
  • 打印时显示字段名和对应值
  • 数据库映射

结合你的例子

person p = { "Tom", 52 };
auto zipped = std::make_tuple(
  std::make_tuple(p.name, "name"),
  std::make_tuple(p.age, "age")
);

这样 zipped 就是一个包含成员值和成员名的元组集合。

扩展

用Boost.Fusion或模板元编程可以自动实现“字段名和字段值配对”的功能,不用手动写每个字段名。

这个 with_names 模板函数的目的是:

  • 给定一个结构体实例 s
  • 返回一个将结构体成员值和成员名配对的“zip”结构

代码逐步分析:

template<class Struct>
auto with_names(const Struct& s)
{
    // 定义一个整数范围,从0到结构体成员数
    using range = range_c<int, 0, (result_of::size<Struct>())>;
    // names 是成员名组成的列表(用 transform 从索引转换到名字)
    static auto names = transform(range(), [](auto i) -> std::string
    {
        return struct_member_name<Struct, i>::call();
    });
    // 返回成员值和成员名的 zip 结果
    return zip(s, names);
}
  • range_c<int, 0, (result_of::size<Struct>())>:生成从0到成员数的整数序列。
  • transform(range(), lambda):用索引 i 映射到成员名字符串。
  • static auto names:静态存储成员名数组。
  • zip(s, names):把结构体的成员值和对应名字按顺序配对起来。

作用

这个函数让你不用手动写字段名,通过模板元编程自动生成“字段名和字段值”的组合,方便打印、序列化等操作。
如果你有 person p = {"Tom", 52};

auto zipped = with_names(p);

zipped 里就存了:

( ("Tom", "name"), (52, "age") )

这两段模板函数的意思是:

template<class T>
auto get_value(const T& x)
{
    return at_c<0>(x);
}
template<class T>
std::string get_name(const T& x)
{
    return at_c<1>(x);
}

解释:

  • 这里假设 T 是一个二元组(tuple-like),例如 (value, name)
  • get_value:取这个二元组的第0个元素,返回它的值(成员的值)。
  • get_name:取这个二元组的第1个元素,返回它的名字(成员名字符串)。

举例:

如果有

auto x = std::make_tuple(52, std::string("age"));
auto val = get_value(x);  // val == 52
auto name = get_name(x);  // name == "age"

这在结合你之前那个 with_names 函数时特别有用——with_names 生成的每个元素就是 (value, name) 这样的tuple,你用 get_valueget_name 可以方便地访问它们。

这段代码展示了 关联元组(Associative Tuples) 的用法,利用了 Boost Fusion 的 map 类型实现用类型做键来访问结构化数据。

代码详解:

namespace fields
{
    struct name {};  // 定义一个空类型作为键
    struct age  {};  // 另一个空类型作为键
}
// 定义一个 person 类型,它是一个 Boost Fusion map,
// 用 fields::name 类型映射到 std::string,fields::age 映射到 int
typedef boost::fusion::map<
    boost::fusion::pair<fields::name, std::string>,
    boost::fusion::pair<fields::age, int>
> person;

// 创建一个 person 实例,传入键对应的值
person a_person = boost::fusion::make_map<fields::name, fields::age>("Tom", 52);
// 通过键访问对应的值
std::string person_name = boost::fusion::at_key<fields::name>(a_person);  // "Tom"
int person_age = boost::fusion::at_key<fields::age>(a_person);            // 52

核心理解:

  • 类型做键fields::namefields::age 是空结构体,只用作类型标签(key)。
  • boost::fusion::map:可以把一组类型和对应的值绑定起来,形成一个类似于字典/映射的结构,但键是类型,不是运行时的字符串。
  • 通过 at_key<Type>(obj) 访问值:根据类型键获取对应的值。
  • 类型安全,且编译时检查键是否存在。

应用场景:

  • 比较适合元编程风格的数据访问,避免字符串键带来的错误。
  • 代替传统结构体,也可以方便做模板元编程和反射。
  • 在需要键值对集合且键是类型安全时非常有用。

这段代码是用 Boost Fusion 提供的宏 BOOST_FUSION_DEFINE_ASSOC_STRUCT 来定义一个关联序列结构体(Associative Sequence Struct)

代码解释:

BOOST_FUSION_DEFINE_ASSOC_STRUCT((), person,
    (std::string, name, fields::name)
    (int, age, fields::age)
);
  • 这是定义一个名为 person 的结构体,包含两个成员:
    • name,类型是 std::string,关联键是 fields::name
    • age,类型是 int,关联键是 fields::age
  • 关联键(association key) 是用来标识成员的类型标签,类似于前面提到的 fields::namefields::age
  • 这样定义后,person 结构体的成员不仅可以通过成员名访问,也可以通过 Boost Fusion 的关联容器接口,用类型键访问:
person p{"Tom", 52};
// 通过成员名访问
std::string n1 = p.name;
int a1 = p.age;
// 通过 Boost Fusion 关联键访问
std::string n2 = boost::fusion::at_key<fields::name>(p);
int a2 = boost::fusion::at_key<fields::age>(p);

核心作用:

  • 融合了结构体和关联容器的特性,即带有成员变量,又可以通过类型键访问。
  • 让结构体同时支持面向对象的访问方式和元编程的关联式访问方式。
  • 便于用 Boost Fusion 的算法处理结构体数据。

你可以把它理解为:

既有传统的结构体字段访问,也有类似关联容器的按“类型键”访问的能力。

C++ 模板元编程 + 类型系统技巧 的一个小例子,用来检查某个类型是否带有某种“属性”(以继承的方式实现“属性”标记)。它常用于模拟类似于 C# 或 Java 注解(Attributes / Annotations)在 C++ 中的行为。

目标

判断 Key 类型是否“具有”某个属性 Attribute —— 实际上是:Key 类型是否继承自 Attribute 类型。

逐句解释

1. 模板函数定义

template<class Attribute, class Key>
constexpr auto has_attribute(const Key&)
{
    return std::is_base_of<Attribute, Key>();
}
  • 这是一个 constexpr(编译期常量)函数模板。
  • 参数:
    • Attribute: 你想要检查的属性类型(如 primary_key)。
    • Key: 实际的成员标签类型(如 fields::name)。
  • 实现:
    • 使用 std::is_base_of<Attribute, Key>():这个标准库模板会在编译期返回一个 true_typefalse_type,表示 Key 是否继承自 Attribute。

2. 模拟定义一些属性

namespace fields
{
    struct name : primary_key, max_length<250> {};
    struct age {};
}
  • fields::name 继承了两个类型:primary_keymax_length<250>
  • fields::age 没有继承任何东西。
    这些结构就像是给字段打上的“标签”,类似于 C# 的 [PrimaryKey][MaxLength(250)]

3. 使用 has_attribute

bool with_primary_key = has_attribute<primary_key>(fields::name());
bool without_primary_key = has_attribute<primary_key>(fields::age());
  • with_primary_key 会是 true:因为 fields::name 继承了 primary_key
  • without_primary_key 会是 false:因为 fields::age 没有继承 primary_key

总结

作用实现方式
给类型加属性通过继承“标记类型”
检查是否有属性使用 std::is_base_of
编译期检查constexpr 实现

这有什么用?

  • 元编程结构体元数据标注:模拟 ORM 框架、序列化器、验证器等机制。
  • 编译期校验规则:避免运行时错误,提高类型安全。
  • 配合 Boost.Fusion 或其他反射机制:构建智能数据处理框架。

这段代码展示了如何使用 SFINAE(Substitution Failure Is Not An Error)和 enable_if 来根据类型是否具有某个属性来选择不同的函数实现。这是一个典型的 C++11 模板技巧,属于 编译期条件分发(也叫作“类型特化”或“约束式函数重载”)。

一句话总结

你定义了两个重载的 get_max_length 函数:

  • 如果 Key 拥有 max_length_base 属性 ➜ 返回其静态成员值。
  • 如果 Key 没有该属性 ➜ 返回 -1

分步理解

1. REQUIRES 宏定义

#define REQUIRES(...) typename std::enable_if<(__VA_ARGS__), int>::type = 0

这个宏等价于:

template<
  class Key,
  typename std::enable_if<条件表达式为 true, int>::type = 0
>

它用于函数模板中,作为默认模板参数来控制启用/禁用某个重载。

2. 第一个版本:类型有 max_length_base 属性时启用

template<class Key, REQUIRES(has_attribute<max_length_base>(Key()))>
int get_max_length(const Key&)
{
    return Key::max_length_value;
}
  • has_attribute<max_length_base>(Key()) 会检查 Key 是否继承自 max_length_base
  • 如果是,就启用此函数。
  • Key::max_length_value 是一个你要求所有有 max_length_base 属性的类型都要定义的静态成员。

3. 第二个版本:没有属性时启用

template<class Key, REQUIRES(!has_attribute<max_length_base>(Key()))>
int get_max_length(const Key&)
{
    return -1;
}
  • 条件相反:如果没有 max_length_base 属性,就启用此重载。
  • 返回一个默认值 -1

应用示例

假设你有两个字段类型定义如下:

struct max_length_base {};
template<int N>
struct max_length : max_length_base {
    static constexpr int max_length_value = N;
};
struct fields {
    struct name : max_length<255> {};
    struct age {};
};

现在调用:

int name_len = get_max_length(fields::name{}); // 返回 255
int age_len  = get_max_length(fields::age{});  // 返回 -1

总结

技术点说明
std::enable_if用于 SFINAE,启用/禁用模板重载
has_attribute<T>(Key())编译期属性判断
REQUIRES(...)简化 enable_if 写法
静态成员访问Key::max_length_value 假设类型有此静态常量
如果你熟悉 C++20 的 Concepts,这种写法可以更优雅地替换为 requires 表达式。

这段代码的功能是:根据一个结构体对象自动生成 SQL 中的 CREATE TABLE 语句,结合 C++ 模板元编程和 Boost.Fusion 的结构反射功能,完成了“数据结构 ➜ SQL 表结构”的自动转换。

一句话总结

该函数将结构体 x 的字段(名字、类型、属性)映射成 SQL 列定义,并输出完整的 CREATE TABLE 语句。

逐行理解

template<class T>
std::string create_table(const T& x, const std::string& table_name)
  • 模板函数:泛型支持任意结构体 T
  • x: 一个类型为 T 的对象。
  • table_name: 生成 SQL 表的名字。
  • 返回:完整的 SQL CREATE TABLE 字符串。

创建 SQL 开头

std::stringstream ss;
ss << "create table " << table_name;
  • 构建 SQL 语句开头:create table table_name

遍历字段并生成列定义

char delim = '(';
for_each(with_names_and_keys(x), [&](const auto& field)
{
    ss << delim << create_column(field);
    delim = ',';
});

解释

  • with_names_and_keys(x):返回一个 zip 结构,包含字段值、字段名字、字段“键”类型(如 fields::name)。
  • for_each(...):对所有字段应用 lambda。
  • 第一次用 (,之后用 ,,来拼接 SQL 列定义。
  • create_column(field) 是你需要自己实现的一个函数,用来根据字段类型生成列定义,例如:
    name VARCHAR(250)
    age INTEGER
    

结尾与返回

ss << ')';
return ss.str();
  • 补上右括号 ),结束表定义。
  • 返回 SQL 字符串。

示例输入 + 输出

假设结构体:

struct fields {
    struct name : primary_key, max_length<250> {};
    struct age {};
};
BOOST_FUSION_DEFINE_ASSOC_STRUCT((), person,
    (std::string, name, fields::name)
    (int, age, fields::age)
);

调用:

person p = { "Tom", 52 };
std::string sql = create_table(p, "person");

输出可能是:

create table person(name VARCHAR(250) PRIMARY KEY,age INTEGER)

(依赖于 create_column(...) 如何实现)

衍生话题:create_column(...) 可能如何写?

template<typename Field>
std::string create_column(const Field& field) {
    std::stringstream ss;
    auto value = get_value(field);
    auto name  = get_name(field);
    auto key   = get_key(field);  // 假设你还有 get_key()
    ss << name << " ";
    // 推断类型
    if (std::is_same<decltype(value), std::string>::value)
        ss << "VARCHAR(" << get_max_length(key) << ")";
    else if (std::is_same<decltype(value), int>::value)
        ss << "INTEGER";
    // 属性
    if (has_attribute<primary_key>(key))
        ss << " PRIMARY KEY";
    return ss.str();
}

总结

项目内容
功能自动生成 SQL 的 CREATE TABLE 语句
技术模板编程 + Boost.Fusion + 类型反射
亮点字段名、字段值、字段属性打包处理并拼接为 SQL
应用场景自动 ORM、配置导出、元编程实践

后面的扫了一眼没什么意思

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

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

相关文章

【1】跨越技术栈鸿沟:字节跳动开源TRAE AI编程IDE的实战体验

2024年初&#xff0c;人工智能编程工具领域发生了一次静默的变革。当字节跳动宣布退出其TRAE项目&#xff08;一款融合大型语言模型能力的云端AI编程IDE&#xff09;时&#xff0c;技术社区曾短暂叹息。然而这一退场并非终点——通过开源社区的接力&#xff0c;TRAE在WayToAGI等…

高端性能封装正在突破性能壁垒,其芯片集成技术助力人工智能革命。

2024 年&#xff0c;高端封装市场规模为 80 亿美元&#xff0c;预计到 2030 年将超过 280 亿美元&#xff0c;2024-2030 年复合年增长率为 23%。 细分到各个终端市场&#xff0c;最大的高端性能封装市场是“电信和基础设施”&#xff0c;2024 年该市场创造了超过 67% 的收入。…

动态规划-1035.不相交的线-力扣(LeetCode)

一、题目解析 光看题目要求和例图&#xff0c;感觉这题好麻烦&#xff0c;直线不能相交啊&#xff0c;每个数字只属于一条连线啊等等&#xff0c;但我们结合题目所给的信息和例图的内容&#xff0c;这不就是最长公共子序列吗&#xff1f;&#xff0c;我们把最长公共子序列连线起…

网页端 js 读取发票里的二维码信息(图片和PDF格式)

起因 为了实现在报销流程中&#xff0c;发票不能重用的限制&#xff0c;发票上传后&#xff0c;希望能读出发票号&#xff0c;并记录发票号已用&#xff0c;下次不再可用于报销。 基于上面的需求&#xff0c;研究了OCR 的方式和读PDF的方式&#xff0c;实际是可行的&#xff…

MeshGPT 笔记

[2311.15475] MeshGPT: Generating Triangle Meshes with Decoder-Only Transformers https://library.scholarcy.com/try 真正意义上的AI生成三维模型MESHGPT来袭&#xff01;_哔哩哔哩_bilibili GitHub - lucidrains/meshgpt-pytorch: Implementation of MeshGPT, SOTA Me…

Appium下载安装配置保姆教程(图文详解)

目录 一、Appium软件介绍 1.特点 2.工作原理 3.应用场景 二、环境准备 安装 Node.js 安装 Appium 安装 JDK 安装 Android SDK 安装Python及依赖包 三、安装教程 1.Node.js安装 1.1.下载Node 1.2.安装程序 1.3.配置npm仓储和缓存 1.4. 配置环境 1.5.测试Node.j…

qt+vs Generated File下的moc_和ui_文件丢失导致 error LNK2001

qt 5.9.7 vs2013 qt add-in 2.3.2 起因是添加一个新的控件类&#xff0c;直接把源文件拖进VS的项目里&#xff0c;然后VS卡住十秒&#xff0c;然后编译就报一堆 error LNK2001 一看项目的Generated Files下的moc_和ui_文件丢失了一部分&#xff0c;导致编译的时候找不到了。因…

基于stm32F10x 系列微控制器的智能电子琴(附完整项目源码、详细接线及讲解视频)

注&#xff1a;文章末尾网盘链接中自取成品使用演示视频、项目源码、项目文档 所用硬件&#xff1a;STM32F103C8T6、无源蜂鸣器、44矩阵键盘、flash存储模块、OLED显示屏、RGB三色灯、面包板、杜邦线、usb转ttl串口 stm32f103c8t6 面包板 …

高抗扰度汽车光耦合器的特性

晶台光电推出的125℃光耦合器系列产品&#xff08;包括KL357NU、KL3H7U和KL817U&#xff09;&#xff0c;专为高温环境下的汽车应用设计&#xff0c;具备以下核心优势和技术特点&#xff1a; 一、技术特性分析 高温稳定性 采用先进的LED技术和优化的IC设计&#xff0c;确保在…

如何做好一份技术文档?从规划到实践的完整指南

如何做好一份技术文档&#xff1f;从规划到实践的完整指南 &#x1f31f; 嗨&#xff0c;我是IRpickstars&#xff01; &#x1f30c; 总有一行代码&#xff0c;能点亮万千星辰。 &#x1f50d; 在技术的宇宙中&#xff0c;我愿做永不停歇的探索者。 ✨ 用代码丈量世界&…

SQL注入篇-sqlmap的配置和使用

在之前的皮卡丘靶场第五期SQL注入的内容中我们谈到了sqlmap&#xff0c;但是由于很多朋友看不了解命令行格式&#xff0c;所以是纯手动获取数据库信息的 接下来我们就用sqlmap来进行皮卡丘靶场的sql注入学习&#xff0c;链接&#xff1a;https://wwhc.lanzoue.com/ifJY32ybh6vc…

Linux操作系统共享Windows操作系统的文件

目录 一、共享文件 二、挂载 一、共享文件 点击虚拟机选项-设置 点击选项&#xff0c;设置文件夹共享为总是启用&#xff0c;点击添加&#xff0c;可添加需要共享的文件夹 查询是否共享成功 ls /mnt/hgfs 如果显示Download&#xff08;这是我共享的文件夹&#xff09;&…

Redis上篇--知识点总结

Redis上篇–解析 本文大部分知识整理自网上&#xff0c;在正文结束后都会附上参考地址。如果想要深入或者详细学习可以通过文末链接跳转学习。 1. 基本介绍 Redis 是一个开源的、高性能的 内存键值数据库&#xff0c;Redis 的键值对中的 key 就是字符串对象&#xff0c;而 val…

李沐--动手学深度学习--GRU

1.GRU从零开始实现 #9.1.2GRU从零开始实现 import torch from torch import nn from d2l import torch as d2l#首先读取 8.5节中使用的时间机器数据集 batch_size,num_steps 32,35 train_iter,vocab d2l.load_data_time_machine(batch_size,num_steps) #初始化模型参数 def …

EasyRTC音视频实时通话功能在WebRTC与智能硬件整合中的应用与优势

一、WebRTC与智能硬件整合趋势​ 随着物联网和实时通信需求的爆发式增长&#xff0c;WebRTC作为开源实时通信技术&#xff0c;为浏览器与移动应用提供免插件的音视频通信能力&#xff0c;在智能硬件领域的融合应用已成必然趋势。智能硬件不再局限于单一功能&#xff0c;对实时…

【版本控制】GitHub Desktop 入门教程与开源协作全流程解析

目录 0 引言1 GitHub Desktop 入门教程1.1 安装与基础配置1.2 核心功能使用指南仓库管理日常开发流程分支管理 2 GitHub 开源协作流程详解2.1 Fork & Pull Request 模型2.2 完整协作流程步骤步骤 1: Fork&#xff08;创建个人副本&#xff09;步骤 2: Clone&#xff08;克隆…

Android屏幕刷新率与FPS(Frames Per Second) 120hz

Android屏幕刷新率与FPS(Frames Per Second) 120hz 屏幕刷新率是屏幕每秒钟刷新显示内容的次数&#xff0c;单位是赫兹&#xff08;Hz&#xff09;。 60Hz 屏幕&#xff1a;每秒刷新 60 次&#xff0c;每次刷新间隔约 16.67ms 90Hz 屏幕&#xff1a;每秒刷新 90 次&#xff0c;…

【PX4飞控】mavros gps相关话题分析,经纬度海拔获取方法,卫星数锁定状态获取方法

使用 ROS1-Noetic 和 mavros v1.20.1&#xff0c; 携带经纬度海拔的话题主要有三个&#xff1a; /mavros/global_position/raw/fix/mavros/gpsstatus/gps1/raw/mavros/global_position/global 查看 mavros 源码&#xff0c;来分析他们的发布过程。发现前两个话题都对应了同一…

ubuntu中安装conda的后遗症

缘由: 在编译rk3588的sdk时&#xff0c;遇到编译buildroot失败&#xff0c;提示如下&#xff1a; 提示缺失expect&#xff0c;但是实测相关工具是在的&#xff0c;如下显示&#xff1a; 然后查找借助各个ai工具&#xff0c;重新安装相关的工具&#xff0c;依然无解。 解决&am…

【Java多线程从青铜到王者】单例设计模式(八)

wait和sleep的区别 我们的wait也是提供了一个还有超时时间的版本&#xff0c;sleep也是可以指定时间的&#xff0c;也就是说时间一到就会解除阻塞&#xff0c;继续执行 wait和sleep都能被提前唤醒(虽然时间还没有到也可以提前唤醒)&#xff0c;wait能被notify提前唤醒&#xf…