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

C++ 的类型排序

0.前言

在 C++ 中,我编写了一个 tuple-like 模板,这个模板能容纳任意多且可重复的类型:

template<typename... Ts>
struct TypeList {};// usage:
using List1 = TypeList<int, double, char, double>;
using List2 = TypeList<>;

因为 C++ 的模板系统是图灵完备的,所以我可以判断某个类型是否存在某个列表中:

#include <type_traits>template<typename T, typename TpList>
struct Belong;
template<typename T>
struct Belong<T, TypeList<>> : std::false_type {};
template<typename T, typename Head, typename... Tail>
struct Belong<T, TypeList<Head, Tail...>>: std::conditional<std::is_same<T, Head>::value, std::true_type, Belong<T, TypeList<Tail...>>>::type {};static_assert( Belong<int, TypeList<double, void, int>>::value, "true" );
static_assert( !Belong<long long, TypeList<double, void, int>>::value, "false" );

类似的,我也可以为这个 TypeList 添加一系列用于增删查改的模板:

// 在一个 TypeList 首部插入一个元素
template<typename TpList, typename T>
struct PushFront;
template<typename TpList, typename T>
using PushFront_t = typename PushFront<TpList, T>::type;template<typename... Ts, typename T>
struct PushFront<TypeList<Ts...>, T> {using type = TypeList<T, Ts...>;
};// 提取第 I 个类型
template<std::size_t Pos, typename... Ts>
struct TypeAt {static_assert( static_cast<bool>( Pos < sizeof...( Ts ) ), "Position overflow" );
};
template<std::size_t Pos, typename... Ts>
using TypeAt_t = typename TypeAt<Pos, Ts...>::type;
// C++26 后可以用 Ts...[Pos] 取代这里的递归遍历template<std::size_t Pos, typename T, typename... Ts>
struct TypeAt<Pos, T, Ts...> : TypeAt<Pos - 1, Ts...> {};
template<typename T, typename... Ts>
struct TypeAt<0, T, Ts...> {using type = T;
};

很显然,因为 TypeList 是一个线性容器,从中查找某个元素时需要从头遍历整个列表。

但可以注意到:当我们使用 std::is_base_of 检查某个类型是否是一个类型的基类时,这个操作的结果可以经由类型协变直接返回,所以我们可以据此编写一个 TypeSet

template<typename... Ts>
struct TypeSet : TypeList<Ts>... {};
// 这里用 TypeList 包一下,是因为类似 int、double 的基本数据类型不允许被继承template<typename T, typename TpSet>
struct Exist;
template<typename T, typename... Ts>
struct Exist<T, TypeSet<Ts...>> : std::is_base_of<TypeList<T>, TypeSet<Ts...>> {};static_assert( Exist<char, TypeSet<double, char, float>>::value, "true" );
static_assert( !Exist<int, TypeSet<double, char, float>>::value, "false" );

C++ 不允许在多继承中重复继承多个相同的基类,所以如果 TypeSet 包含了重复元素就会直接触发一次编译硬错误。

不幸的是,这个硬错误无法被运用在 SFINAE 中,以检查多个类型列表是否包含重复元素。

在不涉及模板实例化的场景下使用一个明显包含重复元素的 TypeSet 并不会立即导致编译错误,

只有抵达了 std::is_base_of 之类需要展开继承关系的场景才会触发,

这种错误不能被 SFINAE 忽略,因为这个错误是由模板实例化尝试导致的。

而且因为不能使用 SFINAE 拒绝重复元素模板,所以如果想从 TypeList 构造一个 TypeSet,就需要提前检查前者是否包含重复元素。

一个朴素的查重实现可以由递归遍历操作得到:

template<typename TpList>
struct Duplicated;
template<>
struct Duplicated<TypeList<>> : std::false_type {};
template<typename Head, typename... Tail>
struct Duplicated<TypeList<Head, Tail...>>: std::conditional<Belong<Head, TypeList<Tail...>>::value, std::true_type, Duplicated<TypeList<Tail...>>>::type {};

这个实现过于朴素,以至于它的复杂度达到了 O ( n 2 ) O(n^{2}) O(n2)

正常来说,如果想要有一个更优的复杂度实现,我们会首先排序待查找集合中的所有元素,然后线性查找重复的相邻元素,这能做到 O ( n log ⁡ n + n ) O(n\log{n}+n) O(nlogn+n)

那么我们能否对 C++ 的类型进行排序?

1.指针标记

只要我们能给每一个类型赋予一个可比较的类型 ID,那么自然的,整个类型集合就是可以被排序的。

虽然在 C++ 里,只要是重载了比较运算符的类型都可以被比较,但因为我们要对类型进行运算,所以我们要把这个操作限定在编译期。

编译期可用的类型包括算术类型和指针类型,很容易想到的是,我们可以使用一个指向某个实例化模板的指针实现类型 ID:

using ID = typename std::add_pointer<void() noexcept>::type;template<typename T>
struct TypeID {
private:static constexpr void id() noexcept {}public:static constexpr ID value = &id;
};template<ID... Is>
struct IDList {};using IDs = IDList<TypeID<int>::value, TypeID<double>::value>;

很不幸的是,在尝试使用这种方法时,编译器(或者说标准)会抱怨:对指针的偏序比较操作不属于编译期常量表达式。

error: '(TypeID<int>::id < TypeID<double>::id)' is not a constant expression| constexpr bool value = TypeID<int>::value < TypeID<double>::value;|                        ~~~~~~~~~~~~~~~~~~~^~~~~~~~~~~~~~~~~~~~~~~

不过判等比较是没问题的。

出现这种错误的主要原因在于:执行比较时我们还处在编译期,代码生成没有完全结束,此时拿到的指针值纯粹是一个不可比较的类型标记,就和类型本身一样。

相同的,对指针做类似 reinterpret_cast 的转换也不属于一个编译期表达式。

指针这条路走不通,那么我们只能将目标转移到整数类型上了。

在下一节前,特别需要指出的是:

尽管 typeid() 产生的 std::type_info 类型提供了一个返回整数的 hash_code() 方法,

std::type_info 对象的生成会(被故意)延迟到运行期,所以 hash_code() 也不属于一个常量表达式。

2.整数标记

在 GCC、Clang 和 MSVC 中,编译器为我们提供了以下两个特殊的扩展宏:

// GCC、Clang:
__PRETTY_FUNCTION__// MSVC:
__FUNCSIG__

这两个宏的作用是以字符数组的形式返回当前函数的名称。

与 C++ 标准的 __func__ 变量不同,这两个宏带有更多与函数参数类型有关的信息,而我们可以借助它附带的类型信息将每一个类型映射为独一无二的整数值。

#include <iostream>template<typename T>
void foo()
{
#if defined( _MSC_VER )std::cout << __FUNCSIG__ << std::endl;
#elsestd::cout << __PRETTY_FUNCTION__ << std::endl;
#endif
}int main()
{foo<int>();foo<double>();foo<std::string>();
}

上述代码的输出为:

// gcc:
void foo() [with T = int]
void foo() [with T = double]
void foo() [with T = std::__cxx11::basic_string<char>]// clang:
void foo() [T = int]
void foo() [T = double]
void foo() [T = std::basic_string<char>]// msvc:
void __cdecl foo<int>(void)
void __cdecl foo<double>(void)
void __cdecl foo<class std::basic_string<char,struct std::char_traits<char>,class std::allocator<char> >>(void)

在 C++20 中,标准库引入了一个名为 std::source_location 的组件,这个组件可以在一定程度上获取当前源码上下文的信息。

特别是 std::source_location::function_name() 方法,标准称这个方法会返回一个由实现而异的、能表示当前函数名称的字符串,这个方法在 GCC、Clang 和 MSVC 上的实现与上述两个宏是相同的。

所以在 GCC、Clang、MSVC,亦或者 C++20 之后的 C++ 环境下,我们可以利用函数名字符串为每个类型计算得到一个独特 ID:

#include <cstdint>
#if defined( __cpp_lib_source_location )
# include <source_location>
#endif#if defined( __cpp_lib_source_location ) || defined( __GNUC__ ) || defined( __clang__ ) || defined( _MSC_VER )
using ID = std::uint64_t;template<typename T>
struct TypeID {
private:static constexpr ID avalanche( ID hash ) noexcept{ // 进行后处理是因为 fnv1a 对尾部字符差异不敏感,而 GCC 和 Clang 的函数字符串差异主要体现在尾部return ( ( ( ( hash ^ ( hash >> 33 ) ) * 0xFF51AFD7ED558CCDull )^ ( ( ( hash ^ ( hash >> 33 ) ) * 0xFF51AFD7ED558CCDull ) >> 33 ) )* 0xC4CEB9FE1A85EC53ull )^ ( ( ( ( ( hash ^ ( hash >> 33 ) ) * 0xFF51AFD7ED558CCDull )^ ( ( ( hash ^ ( hash >> 33 ) ) * 0xFF51AFD7ED558CCDull ) >> 33 ) )* 0xC4CEB9FE1A85EC53ull )>> 33 );}static constexpr ID fnv1a( const char* type_name, ID hash = 14695981039346656037ull ) noexcept{ // 因为函数签名是固定的,变动的只有模板参数名,所以这里也可以简单地累加 type_name 中的每个字符// 虽然累加得到的整数值会比较接近return *type_name == '\0'? avalanche( hash ): fnv1a( type_name + 1, ( hash ^ static_cast<ID>( *type_name ) ) * 1099511628211ull );}template<typename U>static constexpr ID id() noexcept{
# if defined( __cpp_lib_source_location )const auto location = std::source_location::current();return fnv1a( location.function_name() );
# elif defined( _MSC_VER )return fnv1a( __FUNCSIG__ );
# elsereturn fnv1a( __PRETTY_FUNCTION__ );
# endif}public:static constexpr ID value = id<typename std::remove_cv<typename std::remove_reference<T>::type>::type>();
};
#endif

现在我们拥有了将不同类型映射为一个唯一的整数 ID 的方法:

#include <iostream>int main()
{std::cout << TypeID<int>::value << std::endl<< TypeID<double>::value << std::endl<< TypeID<TypeID<int>>::value << std::endl<< TypeID<std::string>::value << std::endl<< TypeID<std::ostream>::value << std::endl;
}

以上代码的输出见此。

3.类型排序

因为类型是可以通过 std::is_same 直接判等的,所以我们可以直接排序一个 TypeList 而不需要单独创建 IDList

排序算法可以随便选择一个,我用的是归并排序:

template<typename TpList>
struct Split;
template<typename TpList>
using Split_l = typename Split<TpList>::left;
template<typename TpList>
using Split_r = typename Split<TpList>::right;template<>
struct Split<TypeList<>> {using left  = TypeList<>;using right = TypeList<>;
};
template<typename T, typename... Ts>
struct Split<TypeList<T, Ts...>> {
private:static constexpr std::size_t N = 1 + sizeof...( Ts );static constexpr std::size_t H = N / 2;template<std::size_t... I>static constexpr TypeList<TypeAt_t<I, T, Ts...>...> make_first( std::index_sequence<I...> );template<std::size_t... I>static constexpr TypeList<TypeAt_t<I + H, T, Ts...>...> make_second( std::index_sequence<I...> );public:using left  = decltype( make_first( std::make_index_sequence<H> {} ) );using right = decltype( make_second( std::make_index_sequence<N - H> {} ) );
};template<typename TpList>
struct MergeSort;
template<typename TpList>
using MergeSort_t = typename MergeSort<TpList>::type;template<>
struct MergeSort<TypeList<>> {using type = TypeList<>;
};
template<typename T>
struct MergeSort<TypeList<T>> {using type = TypeList<T>;
};
template<typename... Ts>
struct MergeSort<TypeList<Ts...>> {
private:template<typename LeftList, typename RightList>struct Conquer;template<typename LeftList, typename RightList>using Conquer_t = typename Conquer<LeftList, RightList>::type;template<typename... Us>struct Conquer<TypeList<>, TypeList<Us...>> {using type = TypeList<Us...>;};template<typename... Us>struct Conquer<TypeList<Us...>, TypeList<>> {using type = TypeList<Us...>;};// 因为是按哈希 ID 排序,所以这里升降序没有意义,也就不提供模板谓词了template<typename U, typename... Us, typename V, typename... Vs>struct Conquer<TypeList<U, Us...>, TypeList<V, Vs...>>: std::conditional<( TypeID<U>::value < TypeID<V>::value ),PushFront<Conquer_t<TypeList<Us...>, TypeList<V, Vs...>>, U>,PushFront<Conquer_t<TypeList<U, Us...>, TypeList<Vs...>>, V>>::type {};public:using type =Conquer_t<MergeSort_t<Split_l<TypeList<Ts...>>>, MergeSort_t<Split_r<TypeList<Ts...>>>>;
};using List       = TypeList<char, unsigned char, double, int, float>;
using SortedList = MergeSort_t<TypeList<char, unsigned char, double, int, float>>;// 一般来说两个列表元素顺序是不同的,但这取决于具体的编译器实现
static_assert( !std::is_same<List, SortedList>::value, "false" );

4.类型查重

经过排序后的 TypeList 中,重复的元素(类型)会位于相邻的位置上,所以查重模板 Duplicated 可以简化为以下的线性查找实现:

template<typename TpList>
struct Duplicated;
template<>
struct Duplicated<TypeList<>> : std::false_type {};
template<typename TpList>
struct Duplicated {
private:template<typename Types>struct Helper;template<typename T>struct Helper<TypeList<T>> : std::false_type {};template<typename T1, typename T2, typename... Ts>struct Helper<TypeList<T1, T2, Ts...>>: std::conditional<std::is_same<T1, T2>::value, std::true_type, Helper<TypeList<T2, Ts...>>>::type {};public:static constexpr bool value = Helper<MergeSort_t<TpList>>::value;
};

现在可以用它检查一个 TypeList 是否包含重复元素,并且能否转换为一个 TypeSet 了。

static_assert( !Duplicated<TypeList<int, double, short, char>>::value, "false" );
static_assert( Duplicated<TypeList<int, double, int, float>>::value, "true" );

5.兼容性处理

由于 TypeID 基于特定的编译器宏、亦或者 C++20 的 std::srouce_location 实现了类型哈希,所以以上代码只适用于使用 GCC、Clang、MSVC 或者 C++20 的环境;在这些环境之外,只能提供一个朴素实现的 fallback。

实际上,如果能通过静态反射拿到一些可以排序的类型元数据的话,这里的实现可以更简单。

所以wo静态反射ne?

完整的类型查重实现代码为:

#include <cstdint>
#if defined( __cpp_lib_source_location )
# include <source_location>
#endif#if defined( __cpp_lib_source_location ) || defined( __GNUC__ ) || defined( __clang__ ) || defined( _MSC_VER )
using ID = std::uint64_t;template<typename T>
struct TypeID {
private:static constexpr ID avalanche( ID hash ) noexcept{return ( ( ( ( hash ^ ( hash >> 33 ) ) * 0xFF51AFD7ED558CCDull )^ ( ( ( hash ^ ( hash >> 33 ) ) * 0xFF51AFD7ED558CCDull ) >> 33 ) )* 0xC4CEB9FE1A85EC53ull )^ ( ( ( ( ( hash ^ ( hash >> 33 ) ) * 0xFF51AFD7ED558CCDull )^ ( ( ( hash ^ ( hash >> 33 ) ) * 0xFF51AFD7ED558CCDull ) >> 33 ) )* 0xC4CEB9FE1A85EC53ull )>> 33 );}static constexpr ID fnv1a( const char* type_name, ID hash = 14695981039346656037ull ) noexcept{ // 因为函数签名是固定的,变动的只有模板参数名,所以这里也可以简单地累加 type_name 中的每个字符// 虽然累加得到的整数值会比较接近return *type_name == '\0'? avalanche( hash ): fnv1a( type_name + 1, ( hash ^ static_cast<ID>( *type_name ) ) * 1099511628211ull );}template<typename U>static constexpr ID id() noexcept{
# if defined( __cpp_lib_source_location )const auto location = std::source_location::current();return fnv1a( location.function_name() );
# elif defined( _MSC_VER )return fnv1a( __FUNCSIG__ );
# elsereturn fnv1a( __PRETTY_FUNCTION__ );
# endif}public:static constexpr ID value = id<typename std::remove_cv<typename std::remove_reference<T>::type>::type>();
};template<typename TpList>
struct Split;
template<typename TpList>
using Split_l = typename Split<TpList>::left;
template<typename TpList>
using Split_r = typename Split<TpList>::right;template<>
struct Split<TypeList<>> {using left  = TypeList<>;using right = TypeList<>;
};
template<typename T, typename... Ts>
struct Split<TypeList<T, Ts...>> {
private:static constexpr std::size_t N = 1 + sizeof...( Ts );static constexpr std::size_t H = N / 2;template<std::size_t... I>static constexpr TypeList<TypeAt_t<I, T, Ts...>...> make_first( std::index_sequence<I...> );template<std::size_t... I>static constexpr TypeList<TypeAt_t<I + H, T, Ts...>...> make_second( std::index_sequence<I...> );public:using left  = decltype( make_first( std::make_index_sequence<H> {} ) );using right = decltype( make_second( std::make_index_sequence<N - H> {} ) );
};template<typename TpList>
struct MergeSort;
template<typename TpList>
using MergeSort_t = typename MergeSort<TpList>::type;template<>
struct MergeSort<TypeList<>> {using type = TypeList<>;
};
template<typename T>
struct MergeSort<TypeList<T>> {using type = TypeList<T>;
};
template<typename... Ts>
struct MergeSort<TypeList<Ts...>> {
private:template<typename LeftList, typename RightList>struct Conquer;template<typename LeftList, typename RightList>using Conquer_t = typename Conquer<LeftList, RightList>::type;template<typename... Us>struct Conquer<TypeList<>, TypeList<Us...>> {using type = TypeList<Us...>;};template<typename... Us>struct Conquer<TypeList<Us...>, TypeList<>> {using type = TypeList<Us...>;};template<typename U, typename... Us, typename V, typename... Vs>struct Conquer<TypeList<U, Us...>, TypeList<V, Vs...>>: std::conditional<( TypeID<U>::value < TypeID<V>::value ),PushFront<Conquer_t<TypeList<Us...>, TypeList<V, Vs...>>, U>,PushFront<Conquer_t<TypeList<U, Us...>, TypeList<Vs...>>, V>>::type {};public:using type =Conquer_t<MergeSort_t<Split_l<TypeList<Ts...>>>, MergeSort_t<Split_r<TypeList<Ts...>>>>;
};template<typename TpList>
struct Duplicated;
template<>
struct Duplicated<TypeList<>> : std::false_type {};
template<typename TpList>
struct Duplicated {
private:template<typename Types>struct Helper;template<typename T>struct Helper<TypeList<T>> : std::false_type {};template<typename T1, typename T2, typename... Ts>struct Helper<TypeList<T1, T2, Ts...>>: std::conditional<std::is_same<T1, T2>::value, std::true_type, Helper<TypeList<T2, Ts...>>>::type {};public:static constexpr bool value = Helper<MergeSort_t<TpList>>::value;
};
#else
template<typename TpList>
struct Duplicated;
template<>
struct Duplicated<TypeList<>> : std::false_type {};
template<typename Head, typename... Tail>
struct Duplicated<TypeList<Head, Tail...>>: std::conditional<Belong<Head, TypeList<Tail...>>::value, std::true_type, Duplicated<TypeList<Tail...>>>::type {};
#endif

6.总结展望

实际上,由于我们已经实现了一个排序算法,所以只要有一个能够表示不同类型之间偏序关系的谓词,就可以将这个排序操作作用在任何概念上。

为此我们需要泛化一下先前的归并排序模板:

template<typename TpList, template<typename, typename> class Cmp>
struct MergeSort;
template<typename TpList, template<typename, typename> class Cmp>
using MergeSort_t = typename MergeSort<TpList, Cmp>::type;template<template<typename, typename> class Cmp>
struct MergeSort<TypeList<>, Cmp> {using type = TypeList<>;
};
template<typename T, template<typename, typename> class Cmp>
struct MergeSort<TypeList<T>, Cmp> {using type = TypeList<T>;
};
template<typename... Ts, template<typename, typename> class Cmp>
struct MergeSort<TypeList<Ts...>, Cmp> {
private:template<typename LeftList, typename RightList>struct Conquer;template<typename LeftList, typename RightList>using Conquer_t = typename Conquer<LeftList, RightList>::type;template<typename... Us>struct Conquer<TypeList<>, TypeList<Us...>> {using type = TypeList<Us...>;};template<typename... Us>struct Conquer<TypeList<Us...>, TypeList<>> {using type = TypeList<Us...>;};template<typename U, typename... Us, typename V, typename... Vs>struct Conquer<TypeList<U, Us...>, TypeList<V, Vs...>>: std::conditional<Cmp<U, V>::value,PushFront<Conquer_t<TypeList<Us...>, TypeList<V, Vs...>>, U>,PushFront<Conquer_t<TypeList<U, Us...>, TypeList<Vs...>>, V>>::type {};public:using type =Conquer_t<MergeSort_t<Split_l<TypeList<Ts...>>, Cmp>, MergeSort_t<Split_r<TypeList<Ts...>>, Cmp>>;
};

然后添加一个谓词模板:

// 按 ID 升序
template<typename A, typename B>
struct IDLess : std::integral_constant<bool, ( TypeID<A>::value < TypeID<B>::value )> {};

最后修改 Duplicated 的实现:

template<typename TpList>
struct Duplicated;
template<>
struct Duplicated<TypeList<>> : std::false_type {};
template<typename TpList>
struct Duplicated {
private:template<typename Types>struct Helper;template<typename T>struct Helper<TypeList<T>> : std::false_type {};template<typename T1, typename T2, typename... Ts>struct Helper<TypeList<T1, T2, Ts...>>: std::conditional<std::is_same<T1, T2>::value, std::true_type, Helper<TypeList<T2, Ts...>>>::type {};public:static constexpr bool value = Helper<MergeSort_t<TpList, IDLess>>::value;
};

我们已经将谓词模板暴露了出来,所以现在我们还能用这个排序算法实现一些更有意思的操作。

在 C++ 里,struct/class 的成员会受到不同的对齐字节要求,导致最终的 struct/class 类型的大小可能比实际成员大小之和略大;这在 std::tuple 中体现的极为明显:

#include <tuple>using tup = std::tuple<bool, void*, char, int>;
static_assert( sizeof( tup ) != ( sizeof( bool ) + sizeof( void* ) + sizeof( char ) + sizeof( int ) ),"false" );

如果我们定义一个基于类型大小降序的谓词模板,那么就可以利用排序算法重排列 std::tuple 的类型列表:

降序是因为在 std::tuple 的主流实现中,更靠左的类型参数会位于类型成员的“更高处”。

template<typename A, typename B>
struct SizeGreater : std::integral_constant<bool, ( sizeof( A ) > sizeof( B ) )> {};

为了协调 TypeListstd::tuple,还需要为它们编写一个类型转换模板:

// 不需要为 std::tuple -> TypeList 编写
// 因为这个类型转换可以利用模板特化匹配完成
template<typename TpList>
struct List2Tuple;
template<typename TpList>
using List2Tuple_t = typename List2Tuple<TpList>;template<typename... Ts>
struct List2Tuple<TypeList<Ts...>> {using type = std::tuple<Ts...>;
};

因为重排 std::tuple 会导致不同类型根据它们的实现大小被调换到不同的位置上,导致通过下标访问成员变得比较没什么意义。

这里可以用上之前的查重模板 Duplicated,确保传入和生成的 std::tuple 不存在重复类型,使得可以仅通过类型就查找到对应成员:

template<typename Tuple>
struct ReorderTuple;
template<typename Tuple>
using ReorderTuple_t = typename ReorderTuple<Tuple>::type;template<typename... Ts>
struct ReorderTuple<std::tuple<Ts...>> {static_assert( !Duplicated<TypeList<Ts...>>::value, "Duplicate types are not allowed" );using type = List2Tuple_t<MergeSort_t<TypeList<Ts...>, SizeGreater>>;
};

简单编写一个 std::tuple 就可以看到结果:

using Tup       = std::tuple<bool, void*, char, long long>;
using Reordered = ReorderTuple_t<Tup>;
static_assert( !std::is_same<Tup, Reordered>::value, "false" );
static_assert( sizeof( Tup ) > sizeof( Reordered ), "false" );

本文使用到的所有代码均可以在此找到。

相关文章:

C++ 的类型排序

0.前言 在 C 中&#xff0c;我编写了一个 tuple-like 模板&#xff0c;这个模板能容纳任意多且可重复的类型&#xff1a; template<typename... Ts> struct TypeList {};// usage: using List1 TypeList<int, double, char, double>; using List2 TypeList<…...

[计算机网络]拓扑结构

拓扑结构一般会在计网教材或课程的第一章计网的分类那里接触到&#xff0c;但实际上计网的拓扑结构并不只是第一章提到的总线型、星型、树型、网状、混合型那几种类型那么简单&#xff0c;学完了后面的数链层以后对拓扑结构会有新的体会&#xff0c;所以特别单独总结成一篇博客…...

C#方法返回值全解析:从基础语法到实战技巧

摘要&#xff1a;方法返回值是C#编程的核心概念之一。本文将带你彻底掌握返回值声明、void方法特性&#xff0c;以及如何通过返回值实现优雅的流程控制&#xff08;文末附完整示例代码&#xff09;。 返回值的基础法则 类型声明原则 有返回值&#xff1a;必须在方法名前声明…...

修复笔记:SkyReels-V2 项目中的 torch.cuda.amp.autocast 警告和错误

#工作记录 一、问题描述 在运行项目时&#xff0c;出现以下警告和错误&#xff1a; FutureWarning: torch.cuda.amp.autocast(args...) is deprecated. Please use torch.amp.autocast(cuda, args...) instead.with torch.cuda.amp.autocast(dtypepipe.transformer.dtype), …...

【TF-BERT】基于张量的融合BERT多模态情感分析

不足&#xff1a;1. 传统跨模态transformer只能处理2种模态&#xff0c;所以现有方法需要分阶段融合3模态&#xff0c;引发信息丢失。2. 直接拼接多模态特征到BERT中&#xff0c;缺乏动态互补机制&#xff0c;无法有效整合非文本模态信息 改进方法&#xff1a;1. 基于张量的跨模…...

SONiC-OTN代码详解(具体内容待续)

SONiC-OTN代码详解 &#xff08;具体内容待续&#xff09; 基于AI的源代码解析工具的产生使得代码阅读和解析变得越来越高效和简洁&#xff0c;计划通过这样的工具对SONiC在OTN领域的应用做一个全自动的解析&#xff0c;大部分内容会基于AI工具的自动解析结果。这样做的目的是…...

牛客周赛90 C题- Tk的构造数组 题解

原题链接 https://ac.nowcoder.com/acm/contest/107500/C 题目描述 解题思路 数组a是不可以动的&#xff0c;所以我们可以把a[i]*b[i]*i分成两组&#xff0c;分别为a[i]*i以及b[i] 然后策略就很明显了&#xff0c;让更大的b[i]匹配更大的a[i]*i 详细实现见代码。 代码&am…...

[ML]通过50个Python案例了解深度学习和神经网络

通过50个Python案例了解深度学习和神经网络 摘要:机器学习 (Machine Learning, ML)、深度学习 (Deep Learning, DL) 和神经网络 (Neural Networks, NN) 是人工智能领域的核心技术。Python 是学习和实践这些技术的首选语言,因为它提供了丰富的库(如 scikit-learn、Te…...

vue3 - keepAlive缓存组件

在Vue 3中&#xff0c;<keep-alive>组件用于缓存动态组件或路由组件的状态&#xff0c;避免重复渲染&#xff0c;提升性能。 我们新建两个组件&#xff0c;在每一个组件里面写一个input&#xff0c;在默认情况下当组件切换的时候&#xff0c;数据会被清空&#xff0c;但…...

自由学习记录(58)

Why you were able to complete the SpringBoot MyBatisPlus task smoothly: Clear logic flow: Database → Entity → Service → Controller → API → JSON response. Errors are explicit, results are verifiable — you know what’s broken and what’s fixed. Sta…...

短信侠 - 自建手机短信转发到电脑上并无感识别复制验证码,和找手机输验证码说再见!

自建手机短信转发到电脑上并无感识别复制验证码 一、前言 项目开发语言&#xff1a;本项目使用PythonRedisC#开发 你是否也遇到过这样的场景&#xff1a; 正在电脑上操作某个网站&#xff0c;需要输入短信验证码手机不在身边&#xff0c;或者在打字时来回切换设备很麻烦验证码…...

课程10. 聚类问题

课程10. 聚类问题 聚类此类表述的难点K 均值法让我们推广到几个集群的情况如果我们选择其他起始近似值会怎样&#xff1f; 结论在 sklearn 中的实现 如何处理已发现的问题&#xff1f;层次聚类Lance-Williams 算法Lance-Williams 公式在Scipy中实现 示例DBSCANDBSCAN 算法 聚类…...

深度学习中的数据增强:提升食物图像分类模型性能的关键策略

深度学习中的数据增强&#xff1a;提升食物图像分类模型性能的关键策略 在深度学习领域&#xff0c;数据是模型训练的基石&#xff0c;数据的数量和质量直接影响着模型的性能表现。然而&#xff0c;在实际项目中&#xff0c;获取大量高质量的数据往往面临诸多困难&#xff0c;…...

QT设计权限管理系统

Qt能够简单实现系统的权限设计 首先我们需要一个登陆界面 例如这样 然后一级权限&#xff0c;可以看到所有的内容&#xff0c;不设置菜单栏的隐藏。 然后其他权限&#xff0c;根据登陆者的身份进行菜单栏不同的展示。 菜单栏的隐藏代码如下&#xff1a; ui->actionuser-…...

从上帝视角看文件操作

1.为什么使用文件? 如果没有文件,我们写的程序中的数据是存储在电脑的内存中,当程序退出时,内存被回收后,数据就丢失了,等下次运行程序,是无法看到上次程序的数据的。(比如我们在程序中写通讯录时,联系人的相关数据都是放在内存中的,当程序退出时,这些数据也会随之消…...

【51单片机6位数码管显示时间与秒表】2022-5-8

缘由数码管 keil proteus 为什么出现这种情况呢&#xff1f;-编程语言-CSDN问答 #include "reg52.h" unsigned char code smgduan[]{0x3f,0x06,0x5b,0x4f,0x66,0x6d,0x7d,0x07,0x7f,0x6f,0x77,0x7c,0x39,0x5e,0x79,0x71,0,64}; //共阴0~F消隐减号 unsigned char cod…...

从头训练小模型: 4 lora 微调

1. LoRA (Low-Rank Adaptation) LoRA是一种高效的参数高效微调&#xff08;Parameter-Efficient Fine-Tuning, PEFT&#xff09;方法&#xff0c;原理是通过低秩分解的方式对预训练模型进行微调。 相比于全参数微调&#xff08;Full Fine-Tuning&#xff09;&#xff0c;LoRA…...

前端开发,文件在镜像服务器上不存在问题:Downloading binary from...Cannot download...

问题与处理策略 问题描述 在 Vue 项目中&#xff0c;执行 npm i 下载依赖时&#xff0c;报如下错误 Downloading binary from https://npm.taobao.org/mirrors/node-sass//v4.14.1/win32-x64-72_binding.node Cannot download "https://npm.taobao.org/mirrors/node-sa…...

Debezium Binlog协议与事件转换详解

Debezium Binlog协议与事件转换详解 1. MySQL Binlog通信机制 1.1 连接建立流程 #mermaid-svg-eE88YFqcTG9kUWaZ {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-eE88YFqcTG9kUWaZ .error-icon{fill:#552222;}#mer…...

【PostgreSQL数据分析实战:从数据清洗到可视化全流程】4.1 日期时间标准化(时区转换/格式统一)

&#x1f449; 点击关注不迷路 &#x1f449; 点击关注不迷路 &#x1f449; 点击关注不迷路 文章大纲 PostgreSQL数据分析实战&#xff1a;数据清洗之日期时间标准化&#xff08;时区转换/格式统一&#xff09;4.1 日期时间标准化&#xff1a;从混乱到有序4.1.1 数据乱象&…...

基于Hive + Spark离线数仓大数据实战项目(视频+课件+代码+资料+笔记)

精品推荐&#xff1a;基于Hive Spark离线数仓大数据实战项目&#xff0c;共23节课&#xff0c;供学习参考。 项目介绍项目中 docker 使用项目环境搭建项目数仓分层项目业务分析sqoop 数据采集python 数据采集项目 ODS 层创建DWD 层构建DWS 层构建项目回顾&#xff08;一&…...

【深入浅出MySQL】之数据类型介绍

【深入浅出MySQL】之数据类型介绍 MySQL中常见的数据类型一览为什么需要如此多的数据类型数值类型BIT&#xff08;M&#xff09;类型INT类型TINYINT类型BIGINT类型浮点数类型float类型DECIMAL(M,D)类型区别总结 字符串类型CHAR类型VARCHAR(M)类型 日期和时间类型enum和set类型 …...

从入门到登峰-嵌入式Tracker定位算法全景之旅 Part 4 |IMU 死算与校正:惯性导航在资源受限环境的落地

Part 4 |IMU 死算与校正:惯性导航在资源受限环境的落地 本章聚焦 ESP32-S3 平台上如何利用 LSM6DS3 IMU 实现 死算(Dead Reckoning),并结合 零速更新(ZUPT) 或 磁力计辅助 进行 漂移校正,最终通过 EKF/UKF 融合提升定位精度。 一、传感器简介与校准 LSM6DS3 主要参数 加速…...

【iOS】 方法交换

【iOS】 方法交换 method-swizzling 文章目录 【iOS】 方法交换 method-swizzling前言什么是method-swizzling相关API方法交换的风险在load方法中保证只加载一次要在当前类的方法中进行交换如果方法依赖于cmd 方法交换的应用 前言 之前看过有关于消息转发的内容,这里我们可以简…...

PostgreSQL 的 ANALYZE 命令

PostgreSQL 的 ANALYZE 命令 ANALYZE 是 PostgreSQL 中用于收集数据库对象统计信息的关键命令&#xff0c;这些统计信息对于查询优化器生成高效执行计划至关重要。 一 ANALYZE 命令 1.1 基本语法 ANALYZE [ ( option [, ...] ) ] [ table_and_columns [, ...] ] ANALYZE [ …...

初识 iOS 开发中的证书固定

引言 在移动应用安全领域&#xff0c;HTTPS/TLS 是数据传输的第一道防线&#xff0c;但仅依赖系统默认的证书验证仍有被中间人&#xff08;MITM&#xff09;攻击的风险。Certificate Pinning&#xff08;证书固定&#xff09;通过将客户端信任“钉”在指定的服务器证书或公钥上…...

2025 年如何使用 Pycharm、Vscode 进行树莓派 Respberry Pi Pico 编程开发详细教程(更新中)

micropython 概述 micropython 官方网站&#xff1a;https://www.micropython.org/ 安装 Micropython 支持固件 树莓派 Pico 安装 Micropython 支持固件 下载地址&#xff1a;https://www.raspberrypi.com/documentation/microcontrollers/ 选择 MicroPython 下载 RPI_PIC…...

设计模式每日硬核训练 Day 17:中介者模式(Mediator Pattern)完整讲解与实战应用

&#x1f504; 回顾 Day 16&#xff1a;责任链模式小结 在 Day 16 中&#xff0c;我们学习了责任链模式&#xff08;Chain of Responsibility Pattern&#xff09;&#xff1a; 将请求沿链传递&#xff0c;节点可选择处理或传递下一节点。实现了请求发送者与多个处理者的解耦…...

文章记单词 | 第63篇(六级)

一&#xff0c;单词释义 vegetable [ˈvedʒtəbl] n. 蔬菜&#xff1b;植物人&#xff1b;生活单调乏味的人&#xff1b;adj. 蔬菜的&#xff1b;植物的faint [feɪnt] adj. 模糊的&#xff1b;微弱的&#xff1b;虚弱的&#xff1b;v. 昏倒&#xff0c;昏厥&#xff1b;n. 昏…...

ES类的索引轮换

通过以下请求方法创建一个名为 “tiered-storage-policy” 的 ISM policy&#xff1a; PUT _plugins/_ism/policies/tiered-storage-policy {"policy": {"description": "Changes replica count and deletes.","schema_version": 1,…...

小白机器人假想:分布式关节控制——机器人运动的未来模式?

引言 在机器人技术快速发展的今天&#xff0c;控制架构的创新往往能带来突破性进展。作为一名机器人爱好者&#xff0c;我最近思考了一个大胆的设想&#xff1a;如果机器人的每个关节都配备独立的动作存储器和处理器&#xff0c;并通过高速光纤网络与中央"驱动总脑"…...

LangChain4j +DeepSeek大模型应用开发——9 优化硅谷小鹿

1.预约业务的实现 这部分我们实现硅谷小鹿的查询订单、预约订单、取消订单的功能 创建MySQL数据库表 CREATE DATABASE xiaolu; USE xiaolu; -- 创建预约表 appointment CREATE TABLE appointment (id BIGINT NOT NULL AUTO_INCREMENT COMMENT 主键ID&#xff0c;自增, -- 主…...

Oracle VirtualBox 在 macOS 上的详细安装步骤

Oracle VirtualBox 在 macOS 上的详细安装步骤 一、准备工作1. 系统要求2. 下载安装包二、安装 VirtualBox1. 挂载安装镜像2. 运行安装程序3. 处理安全限制(仅限首次安装)三、安装扩展包(增强功能)四、配置第一个虚拟机1. 创建新虚拟机2. 分配内存3. 创建虚拟硬盘4. 加载系…...

Day110 | 灵神 | 二叉树 | 根到叶路径上的不足节点

Day110 | 灵神 | 二叉树 | 根到叶路径上的不足节点 1080.根到叶路径上的不足节点 1080. 根到叶路径上的不足节点 - 力扣&#xff08;LeetCode&#xff09; 思路&#xff1a; 笔者一开始没看懂&#xff0c;只能通过部分的例子&#xff0c;原因是把路径和小于limit的都给删了…...

超详细讲解C语言转义字符\a \b \r \t \? \n等等

转义字符 C语言有一组字符很特殊&#xff0c;叫做转义字符&#xff0c;顾名思义&#xff0c;改变原来的意思的字符。 1 \? ??)是一个三字母词&#xff0c;在以前的编译器它会被编译为] (??会被编译为[ 因此在以前输入(are you ok ??)就会被编译为are you ok ] 解决这个…...

TensorFlow 多卡训练 tf多卡训练

目录 export TF_GPU_ALLOCATORcuda_malloc_async &#x1f527; 具体作用 优势 &#x1f9e9; 依赖条件 ✅ 设置方式&#xff08;Linux/macOS&#xff09; export TF_GPU_ALLOCATORcuda_malloc_async 是设置 TensorFlow 使用 CUDA 异步内存分配器 的环境变量。这个设置可…...

数据结构--树状数组

树状数组&#xff08;Fenwick Tree&#xff09; 概述 树状数组是一种用于高效处理动态数组中前缀和查询的数据结构。它能够在 O ( l o g n ) O(log n) O(logn) 时间复杂度内完成以下操作&#xff1a; 更新数组中的元素O(logn)查询数组前缀和O(logn) 数组&#xff1a; O(1)…...

如何使用python保存字典

在Python中&#xff0c;可以通过多种方式将字典&#xff08;dict&#xff09;保存到文件中&#xff0c;并能够随时读取恢复。以下是几种常见的方法&#xff1a; 1. 使用 json 模块&#xff08;推荐&#xff09; 适用场景&#xff1a;需要人类可读的文件格式&#xff0c;且数据不…...

C和指针——预处理

预处理是编译前的过程&#xff0c;主要对define&#xff0c;include以及一些编译器定义的内容进行替换 #define的本质就是替换 1、例子 #define FOREVER for(;;) 2、例子 #define TEMPD "1231231231\ 123123123" \\如果太长了&#xff0c;可以用\换行 3、例子——可…...

windows python ta-lib安装

https://github.com/TA-Lib/ta-lib/releases windows安装ta-lib指令 pip install --no-cache-dir https://github.com/cgohlke/talib-build/releases/download/v0.6.3/ta_lib-0.6.3-cp310-cp310-win_amd64.whl...

机器学习+多目标优化的算法如何设计?

一、核心问题与设计思路 机器学习&#xff08;ML&#xff09;与多目标优化&#xff08;MOO&#xff09;的结合旨在解决两类核心问题&#xff1a; 利用ML提升MOO算法的性能&#xff1a;通过机器学习模型预测解的质量、优化搜索方向或加速收敛&#xff1b;利用MOO优化ML模型的多…...

爬虫管理平台-最新版本发布

TaskPyro 是什么&#xff1f; TaskPyro 是一个轻量级的 Python 任务调度平台&#xff0c;专注于提供简单易用的任务管理和爬虫调度解决方案。它能够帮助您轻松管理和调度 Python 任务&#xff0c;特别适合需要定时执行的爬虫任务和数据处理任务。 官方文档&#xff1a;https:/…...

SpringCloud教程 — 无废话从0到1逐步学习

目录 什么是微服务&#xff1f;​ 微服务与单体架构的区别 微服务主要用法概念 远程调用 服务注册/发现&注册中心 配置中心 服务熔断&服务降级 1&#xff09;服务熔断 2&#xff09;服务降级 API 网关 环境准备 Nacos OpenFeign Gateway Sentinel Sea…...

Webug4.0通关笔记12- 第17关 文件上传之前端拦截(3种方法)

目录 一、文件上传前端拦截原理 二、第17关 文件上传(前端拦截) 1.打开靶场 2.构造php脚本 3.源码分析 &#xff08;1&#xff09;js源码 &#xff08;2&#xff09;服务器源码 &#xff08;3&#xff09;总结 4.渗透实战 &#xff08;1&#xff09;禁用js法 &#…...

使用synchronized关键字同步Java线程

问题 在Java多线程编程中&#xff0c;你需要保护某些数据&#xff0c;防止多个线程同时访问导致数据不一致或程序错误。 解决方案 在需要保护的方法或代码段上使用synchronized关键字。 讨论 synchronized关键字是Java提供的同步机制&#xff0c;用于确保在同一时刻只有一…...

从头训练小模型: 2 监督微调SFT

简介 从头训练小模型是我个人对大语言模型(LLM)学习中的重要部分。 通过对一个小规模模型的最小化复现实践&#xff0c;我不仅能够深入理解模型训练的基本流程&#xff0c;还可以系统地学习其中的核心原理和实际运行机制。这种实践性的学习方法让我能够直观地感受模型训练的每…...

【QT】QT中http协议和json数据的解析-http获取天气预报

QT中http协议和json数据的解析 1.http协议的原理2.QT中http协议的通信流程2.1 方法步骤 3.使用http协议&#xff08;通过http下载图片和获取天气预报信息&#xff09;3.1 http下载网络上的图片(下载小文件)3.1.1 示例代码3.1.2 现象 3.2 获取网络上天气预报3.2.1 免费的天气预报…...

PiscTrace针对YOLO深度适配:从v8到v12

一、YOLO简介&#xff1a;目标检测的核心技术 YOLO&#xff08;You Only Look Once&#xff09;是近年来最为流行的目标检测模型&#xff0c;凭借其实时性与高精度&#xff0c;广泛应用于自动驾驶、视频监控、安防检测等多个领域。YOLO系列模型自v1问世以来&#xff0c;经过不…...

前端面试每日三题 - Day 24

这是我为准备前端/全栈开发工程师面试整理的第24天每日三题练习&#xff0c;涵盖了&#xff1a; JavaScript 中的 Promise.all()、Promise.race() 和 Promise.allSettled() 的实际应用和性能差异React 中的 Concurrent Rendering 和 useTransition API如何设计一个高并发的在线…...

正态分布习题集 · 题目篇

正态分布习题集 题目篇 全面覆盖单变量正态、多变量正态、参数估计、假设检验、变换以及应用&#xff0c;共 20 题&#xff0c;从基础到进阶。完成后请移步《答案与解析篇》。 1. 基础定义与性质&#xff08;5题&#xff09; 1.1 密度函数 写出正态分布 N ( μ , σ 2 ) N(…...