C++之std::tuple(二) : 揭秘底层实现原理

相关系列文章

C++之std::tuple(二) : 揭秘底层实现原理

C++三剑客之std::any(一) : 使用

C++之std::tuple(一) : 使用精讲(全)

C++三剑客之std::variant(一) : 使用

C++三剑客之std::variant(二):深入剖析

深入理解可变参数(va_list、std::initializer_list和可变参数模版)

std::apply源码分析

目录

1.std::tuple存储设计

2.std::tuple构造

3.std::tuple_size

4.std::get<>访问值

5.operator=

6._Equals

7.总结


之前的章节中讲解std::tuple的使用和一些注意事项,接下来我们就以vs2019的std::tuple的实现来讲解它的底层实现原理。

1.std::tuple存储设计

std::tuple存储的递归写法基于这样的思想:一个包含N(N>0)个元素的元组可以存储为一个元素(第1个元素,或者说是列表的头部)加上一个包含N-1个元素的元组(尾部),而包含0个元素的元组是单独的特殊情况。下面看一下示例:

std::tuple<bool, int, double, std::string>  a(true, 1, 3.0, "1112222");

因此,一个包含4个元素的元组a的递归构造如下:

1) 第一层递归。准备存储a的第一个元素true,剩下的是 std:tuple<int,double,std::string> 对象。

2) 第二层递归。准备存储a的第二个元素1,剩下的是 std::tuple<double,std::string> 对象。

3) 第三层递归。准备存储a第三个元素3.0, 剩下的是 std::tuple<std::string> 对象。

4) 第四层递归。准备存储a第四个元素"1112222",剩余的是std::tuple<>,遇到std::tuple<>递归结束。此时a的才开始存储元素,并且存储元素是从"1112222"开始,然后递归开始返回。

5) 返回到第3步的递归,存储duble类型的3.0。

6) 返回到第2步的递归,存储第int类型的1。

7) 返回到第1步的递归,存储第bool类型的true。

也就是说,std::tuple 的构造函数中,最后一个传入的元素最先构造,最先传入的元素最后一个构造,符合递归顺序,即入栈顺序。下面从源码角度,具体来看看 std::tuple 的实现。

template <class _This, class... _Rest>
class tuple<_This, _Rest...> : private tuple<_Rest...> { // recursive tuple definition
public:
    using _This_type = _This; //当前元素
    using _Mybase    = tuple<_Rest...>; //余下的元素

    //以下都是构造函数,罗列了部分
    template <class _Tag, class _This2, class... _Rest2, enable_if_t<is_same_v<_Tag, _STD _Exact_args_t>, int> = 0>
    constexpr tuple(_Tag, _This2&& _This_arg, _Rest2&&... _Rest_arg)
        : _Mybase(_Exact_args_t{}, _STD forward<_Rest2>(_Rest_arg)...), _Myfirst(_STD forward<_This2>(_This_arg)) {}

    template <class _Tag, class _Tpl, size_t... _Indices, enable_if_t<is_same_v<_Tag, _STD _Unpack_tuple_t>, int> = 0>
    constexpr tuple(_Tag, _Tpl&& _Right, index_sequence<_Indices...>);

    template <class _Tag, class _Tpl, enable_if_t<is_same_v<_Tag, _STD _Unpack_tuple_t>, int> = 0>
    constexpr tuple(_Tag, _Tpl&& _Right)
        : tuple(_Unpack_tuple_t{}, _STD forward<_Tpl>(_Right),
            make_index_sequence<tuple_size_v<remove_reference_t<_Tpl>>>{}) {}
    ...


    //获取余下的元素类,不带const
    constexpr _Mybase& _Get_rest() noexcept { // get reference to rest of elements
        return *this;
    }

    //获取余下的元素类,带const
    constexpr const _Mybase& _Get_rest() const noexcept { // get const reference to rest         of elements
        return *this;
    }

    ...

    //
    template <size_t _Index, class... _Types>
    friend constexpr tuple_element_t<_Index, tuple<_Types...>>& get(tuple<_Types...>& _Tuple) noexcept;

    template <size_t _Index, class... _Types>
    friend constexpr const tuple_element_t<_Index, tuple<_Types...>>& get(const tuple<_Types...>& _Tuple) noexcept;

    template <size_t _Index, class... _Types>
    friend constexpr tuple_element_t<_Index, tuple<_Types...>>&& get(tuple<_Types...>&& _Tuple) noexcept;

    template <size_t _Index, class... _Types>
    friend constexpr const tuple_element_t<_Index, tuple<_Types...>>&& get(const tuple<_Types...>&& _Tuple) noexcept;

    template <size_t _Index, class... _Types>
    friend constexpr auto&& _Tuple_get(tuple<_Types...>&& _Tuple) noexcept;

    template <class _Ty, class... _Types>
    friend constexpr _Ty& get(tuple<_Types...>& _Tuple) noexcept;

    template <class _Ty, class... _Types>
    friend constexpr const _Ty& get(const tuple<_Types...>& _Tuple) noexcept;

    template <class _Ty, class... _Types>
    friend constexpr _Ty&& get(tuple<_Types...>&& _Tuple) noexcept;

    template <class _Ty, class... _Types>
    friend constexpr const _Ty&& get(const tuple<_Types...>&& _Tuple) noexcept;
    
    //包装的当前的元素类,
    _Tuple_val<_This> _Myfirst; // the stored element    
};

        从上面的代码看到,std::tuple的递归继承用到了private继承,说明各个元素都是独立的,互相没有关系。std::tuple的元素之间是一种组合的关系。另外一个是private继承可以造成empty base最优化,这对致力于“对象尺寸最小化”的程序开发者而言,可能很重要。

        那么,tuple是如何存储其中的元素呢?

_Tuple_val<_This> _Myfirst; // the stored element

原来,它有个成员叫_Myfirst,它就是用来存储_This类型的变量的。你会看到_Myfirst的类型不是_This而是_Tuple_val<_This>,其实,_Tuple_val又是一个类模板,它的代码这里就不展开了,简而言之,它的作用是存储一个tuple中的变量。_Myfirst._Val才是真正的元素。从_Tuple_val的定义可以看出:

template <class _Ty>
struct _Tuple_val { // stores each value in a tuple
    constexpr _Tuple_val() : _Val() {}

    template <class _Other>
    constexpr _Tuple_val(_Other&& _Arg) : _Val(_STD forward<_Other>(_Arg)) {}

    template <class _Alloc, class... _Other, enable_if_t<!uses_allocator_v<_Ty, _Alloc>, int> = 0>
    constexpr _Tuple_val(const _Alloc&, allocator_arg_t, _Other&&... _Arg) : _Val(_STD forward<_Other>(_Arg)...) {}

    template <class _Alloc, class... _Other,
        enable_if_t<conjunction_v<_STD uses_allocator<_Ty, _Alloc>,
                        _STD is_constructible<_Ty, _STD allocator_arg_t, const _Alloc&, _Other...>>,
            int> = 0>
    constexpr _Tuple_val(const _Alloc& _Al, allocator_arg_t, _Other&&... _Arg)
        : _Val(allocator_arg, _Al, _STD forward<_Other>(_Arg)...) {}

    template <class _Alloc, class... _Other,
        enable_if_t<conjunction_v<_STD uses_allocator<_Ty, _Alloc>,
                        _STD negation<_STD is_constructible<_Ty, _STD allocator_arg_t, const _Alloc&, _Other...>>>,
            int> = 0>
    constexpr _Tuple_val(const _Alloc& _Al, allocator_arg_t, _Other&&... _Arg)
        : _Val(_STD forward<_Other>(_Arg)..., _Al) {}

    _Ty _Val;
};

在std::tuple类中定义_Myfirst的权限是public的,所以对外面而言是直接访问元素值的。

通过上面的分析,a中定义的类和类的继承关系图如下所示:

class std::tuple<>;
class std::tuple<std::string>;
class std::tuple<double, std::string>;
class std::tuple<int, double, std::string>;
class std::tuple<bool, int, double, std::string>;

内存的分布图如下:

2.std::tuple构造

tuple的构造函数就是初始化_Myfirst和_MyBase,当然,_MyBase也要进行么一个过程,直到tuple<>。

//分类构造
template <class _Tag, class _This2, class... _Rest2, enable_if_t<is_same_v<_Tag, _STD _Exact_args_t>, int> = 0>
    constexpr tuple(_Tag, _This2&& _This_arg, _Rest2&&... _Rest_arg)
        : _Mybase(_Exact_args_t{}, _STD forward<_Rest2>(_Rest_arg)...), _Myfirst(_STD forward<_This2>(_This_arg)) {}

    template <class _Tag, class _Tpl, size_t... _Indices, enable_if_t<is_same_v<_Tag, _STD _Unpack_tuple_t>, int> = 0>
    constexpr tuple(_Tag, _Tpl&& _Right, index_sequence<_Indices...>);

    template <class _Tag, class _Tpl, enable_if_t<is_same_v<_Tag, _STD _Unpack_tuple_t>, int> = 0>
    constexpr tuple(_Tag, _Tpl&& _Right)
        : tuple(_Unpack_tuple_t{}, _STD forward<_Tpl>(_Right),
            make_index_sequence<tuple_size_v<remove_reference_t<_Tpl>>>{}) {}

    template <class _Tag, class _Alloc, class _This2, class... _Rest2,
        enable_if_t<is_same_v<_Tag, _STD _Alloc_exact_args_t>, int> = 0>
    constexpr tuple(_Tag, const _Alloc& _Al, _This2&& _This_arg, _Rest2&&... _Rest_arg)
        : _Mybase(_Alloc_exact_args_t{}, _Al, _STD forward<_Rest2>(_Rest_arg)...),
          _Myfirst(_Al, allocator_arg, _STD forward<_This2>(_This_arg)) {}

    template <class _Tag, class _Alloc, class _Tpl, size_t... _Indices,
        enable_if_t<is_same_v<_Tag, _STD _Alloc_unpack_tuple_t>, int> = 0>
    constexpr tuple(_Tag, const _Alloc& _Al, _Tpl&& _Right, index_sequence<_Indices...>);

    template <class _Tag, class _Alloc, class _Tpl, enable_if_t<is_same_v<_Tag, _STD _Alloc_unpack_tuple_t>, int> = 0>
    constexpr tuple(_Tag, const _Alloc& _Al, _Tpl&& _Right)
        : tuple(_Alloc_unpack_tuple_t{}, _Al, _STD forward<_Tpl>(_Right),
            make_index_sequence<tuple_size_v<remove_reference_t<_Tpl>>>{}) {}

//根据入口参数的不同分派到不同的构造函数
template <class _This2, class... _Rest2,
        enable_if_t<conjunction_v<_STD _Tuple_perfect_val<tuple, _This2, _Rest2...>,
                        _STD _Tuple_constructible_val<tuple, _This2, _Rest2...>>,
            int> = 0>
    constexpr explicit(_Tuple_conditional_explicit_v<tuple, _This2, _Rest2...>) tuple(_This2&& _This_arg,
        _Rest2&&... _Rest_arg) noexcept(_Tuple_nothrow_constructible_v<tuple, _This2, _Rest2...>) // strengthened
        : tuple(_Exact_args_t{}, _STD forward<_This2>(_This_arg), _STD forward<_Rest2>(_Rest_arg)...) {}


tuple(const tuple&) = default;
tuple(tuple&&)      = default;

tuple& operator=(const volatile tuple&) = delete;

        它还提供了默认拷贝构造函数和移动构造函数(移动语义是C++11中新增的特性,可以参考C++之std::move(移动语义)-CSDN博客)。其实,它还有很多构造函数,写起来挺热闹,无非就是用不同的方式为它赋初值,故省略。

        上面的构造函数是根据_tag的不同被分派到不同的构造函数,它叫标签派发。源码中定义了如下的标签:

struct _Exact_args_t {
    explicit _Exact_args_t() = default;
}; // tag type to disambiguate construction (from one arg per element)

struct _Unpack_tuple_t {
    explicit _Unpack_tuple_t() = default;
}; // tag type to disambiguate construction (from unpacking a tuple/pair)

struct _Alloc_exact_args_t {
    explicit _Alloc_exact_args_t() = default;
}; // tag type to disambiguate construction (from an allocator and one arg per element)

struct _Alloc_unpack_tuple_t {
    explicit _Alloc_unpack_tuple_t() = default;
}; // tag type to disambiguate construction (from an allocator and unpacking a tuple/pair)

3.std::tuple_size

先看一下源码:

template <class _Ty, _Ty _Val>
struct integral_constant {
    static constexpr _Ty value = _Val;

    using value_type = _Ty;
    using type       = integral_constant;

    constexpr operator value_type() const noexcept {
        return value;
    }

    _NODISCARD constexpr value_type operator()() const noexcept {
        return value;
    }
};

// TUPLE INTERFACE TO tuple
template <class... _Types>
struct tuple_size<tuple<_Types...>> : integral_constant<size_t, sizeof...(_Types)> {}; // size of tuple

template <class _Ty1, class _Ty2>
struct tuple_size<pair<_Ty1, _Ty2>> : integral_constant<size_t, 2> {}; // size of pair

std::integral_constant 包装特定类型的静态常量。它是 C++ 类型特征的基类。

std::tuple_size利用sizeof...求得可变参数的个数。

4.std::get<>访问值

先看一下tuple_element,tuple_element是获取tuple的元素,包括_Myfirst和_MyBase,源码:

//tuple<>
template <size_t _Index>
struct _MSVC_KNOWN_SEMANTICS tuple_element<_Index, tuple<>> { // enforce bounds checking
    static_assert(_Always_false<integral_constant<size_t, _Index>>, "tuple index out of bounds");
};

//tuple的_index == 0
template <class _This, class... _Rest>
struct _MSVC_KNOWN_SEMANTICS tuple_element<0, tuple<_This, _Rest...>> { // select first element
    using type = _This;
    // MSVC assumes the meaning of _Ttype; remove or rename, but do not change semantics
    using _Ttype = tuple<_This, _Rest...>;
};

//tuple的_index > 0
template <size_t _Index, class _This, class... _Rest>
struct _MSVC_KNOWN_SEMANTICS tuple_element<_Index, tuple<_This, _Rest...>>
    : tuple_element<_Index - 1, tuple<_Rest...>> {}; // recursive tuple_element definition

//pair
template <size_t _Idx, class _Ty1, class _Ty2>
struct _MSVC_KNOWN_SEMANTICS tuple_element<_Idx, pair<_Ty1, _Ty2>> {
    static_assert(_Idx < 2, "pair index out of bounds");
    using type = conditional_t<_Idx == 0, _Ty1, _Ty2>;
};

template <size_t _Index, class _Tuple>
using tuple_element_t = typename tuple_element<_Index, _Tuple>::type;

通过_Index获取tuple元素:

template <size_t _Index, class... _Types>
_NODISCARD constexpr tuple_element_t<_Index, tuple<_Types...>>& get(tuple<_Types...>& _Tuple) noexcept {
    using _Ttype = typename tuple_element<_Index, tuple<_Types...>>::_Ttype;
    return static_cast<_Ttype&>(_Tuple)._Myfirst._Val;
}

template <size_t _Index, class... _Types>
_NODISCARD constexpr const tuple_element_t<_Index, tuple<_Types...>>& get(const tuple<_Types...>& _Tuple) noexcept {
    using _Ttype = typename tuple_element<_Index, tuple<_Types...>>::_Ttype;
    return static_cast<const _Ttype&>(_Tuple)._Myfirst._Val;
}

template <size_t _Index, class... _Types>
_NODISCARD constexpr tuple_element_t<_Index, tuple<_Types...>>&& get(tuple<_Types...>&& _Tuple) noexcept {
    using _Ty    = tuple_element_t<_Index, tuple<_Types...>>;
    using _Ttype = typename tuple_element<_Index, tuple<_Types...>>::_Ttype;
    return static_cast<_Ty&&>(static_cast<_Ttype&>(_Tuple)._Myfirst._Val);
}

template <size_t _Index, class... _Types>
_NODISCARD constexpr const tuple_element_t<_Index, tuple<_Types...>>&& get(const tuple<_Types...>&& _Tuple) noexcept {
    using _Ty    = tuple_element_t<_Index, tuple<_Types...>>;
    using _Ttype = typename tuple_element<_Index, tuple<_Types...>>::_Ttype;
    return static_cast<const _Ty&&>(static_cast<const _Ttype&>(_Tuple)._Myfirst._Val);
}

上述代码分析get<index>的流程:

1)通过_Index递归构造出类 tuple_element_t

2)   获取当前元素 _MyFirst.Val

通过_Ty获取tuple元素:

//辅助类
template <class _Ty, class _Tuple>
struct _Tuple_element {}; // backstop _Tuple_element definition

template <class _This, class... _Rest>
struct _Tuple_element<_This, tuple<_This, _Rest...>> { // select first element
    static_assert(!_Is_any_of_v<_This, _Rest...>, "duplicate type T in get<T>(tuple)");
    using _Ttype = tuple<_This, _Rest...>;
};

template <class _Ty, class _This, class... _Rest>
struct _Tuple_element<_Ty, tuple<_This, _Rest...>> { // recursive _Tuple_element definition
    using _Ttype = typename _Tuple_element<_Ty, tuple<_Rest...>>::_Ttype;
};

//通过_Ty  get
template <class _Ty, class... _Types>
_NODISCARD constexpr _Ty& get(tuple<_Types...>& _Tuple) noexcept {
    using _Ttype = typename _Tuple_element<_Ty, tuple<_Types...>>::_Ttype;
    return static_cast<_Ttype&>(_Tuple)._Myfirst._Val;
}

template <class _Ty, class... _Types>
_NODISCARD constexpr const _Ty& get(const tuple<_Types...>& _Tuple) noexcept {
    using _Ttype = typename _Tuple_element<_Ty, tuple<_Types...>>::_Ttype;
    return static_cast<const _Ttype&>(_Tuple)._Myfirst._Val;
}

template <class _Ty, class... _Types>
_NODISCARD constexpr _Ty&& get(tuple<_Types...>&& _Tuple) noexcept {
    using _Ttype = typename _Tuple_element<_Ty, tuple<_Types...>>::_Ttype;
    return static_cast<_Ty&&>(static_cast<_Ttype&>(_Tuple)._Myfirst._Val);
}

template <class _Ty, class... _Types>
_NODISCARD constexpr const _Ty&& get(const tuple<_Types...>&& _Tuple) noexcept {
    using _Ttype = typename _Tuple_element<_Ty, tuple<_Types...>>::_Ttype;
    return static_cast<const _Ty&&>(static_cast<const _Ttype&>(_Tuple)._Myfirst._Val);
}

上述代码分析get<_Ty>的流程:

1)通过_Ty递归构造出类 _Tuple_element

2)   获取当前元素 _MyFirst.Val

5.operator=

tuple重载了赋值符号(=),这样,tuple之间是可以赋值的。

    tuple& operator=(const volatile tuple&) = delete;

    //以下是特殊情况是可以赋值的
    template <class _Myself = tuple, class _This2 = _This,
        enable_if_t<conjunction_v<_STD _Is_copy_assignable_no_precondition_check<_This2>,
                        _STD _Is_copy_assignable_no_precondition_check<_Rest>...>,
            int> = 0>
    _CONSTEXPR20 tuple& operator=(_Identity_t<const _Myself&> _Right) noexcept(
        conjunction_v<is_nothrow_copy_assignable<_This2>, is_nothrow_copy_assignable<_Rest>...>) /* strengthened */ {
        _Myfirst._Val = _Right._Myfirst._Val;
        _Get_rest()   = _Right._Get_rest();
        return *this;
    }

    template <class _Myself = tuple, class _This2 = _This,
        enable_if_t<conjunction_v<_STD _Is_move_assignable_no_precondition_check<_This2>,
                        _STD _Is_move_assignable_no_precondition_check<_Rest>...>,
            int> = 0>
    _CONSTEXPR20 tuple& operator=(_Identity_t<_Myself&&> _Right) noexcept(
        conjunction_v<is_nothrow_move_assignable<_This2>, is_nothrow_move_assignable<_Rest>...>) {
        _Myfirst._Val = _STD forward<_This>(_Right._Myfirst._Val);
        _Get_rest()   = _STD forward<_Mybase>(_Right._Get_rest());
        return *this;
    }

    template <class... _Other, enable_if_t<conjunction_v<_STD negation<_STD is_same<tuple, _STD tuple<_Other...>>>,
                                               _STD _Tuple_assignable_val<tuple, const _Other&...>>,
                                   int> = 0>
    _CONSTEXPR20 tuple& operator=(const tuple<_Other...>& _Right) noexcept(
        _Tuple_nothrow_assignable_v<tuple, const _Other&...>) /* strengthened */ {
        _Myfirst._Val = _Right._Myfirst._Val;
        _Get_rest()   = _Right._Get_rest();
        return *this;
    }

    template <class... _Other, enable_if_t<conjunction_v<_STD negation<_STD is_same<tuple, _STD tuple<_Other...>>>,
                                               _STD _Tuple_assignable_val<tuple, _Other...>>,
                                   int> = 0>
    _CONSTEXPR20 tuple& operator=(tuple<_Other...>&& _Right) noexcept(
        _Tuple_nothrow_assignable_v<tuple, _Other...>) /* strengthened */ {
        _Myfirst._Val = _STD forward<typename tuple<_Other...>::_This_type>(_Right._Myfirst._Val);
        _Get_rest()   = _STD forward<typename tuple<_Other...>::_Mybase>(_Right._Get_rest());
        return *this;
    }

    template <class _First, class _Second,
        enable_if_t<_Tuple_assignable_v<tuple, const _First&, const _Second&>, int> = 0>
    _CONSTEXPR20 tuple& operator=(const pair<_First, _Second>& _Right) noexcept(
        _Tuple_nothrow_assignable_v<tuple, const _First&, const _Second&>) /* strengthened */ {
        _Myfirst._Val             = _Right.first;
        _Get_rest()._Myfirst._Val = _Right.second;
        return *this;
    }

赋值符号返回左边的引用,这种行为和C++的内置类型是一致的。_Get_rest是tuple的成员函数,作用是把除了_Myfirst之外的那些元素拿出来。

6._Equals

    //tuple内置函数
    template <class... _Other>
    constexpr bool _Equals(const tuple<_Other...>& _Right) const {
        return _Myfirst._Val == _Right._Myfirst._Val && _Mybase::_Equals(_Right._Get_rest());
    }

    //重载operator==
    template <class... _Types1, class... _Types2>
_NODISCARD constexpr bool operator==(const tuple<_Types1...>& _Left, const tuple<_Types2...>& _Right) {
    static_assert(sizeof...(_Types1) == sizeof...(_Types2), "cannot compare tuples of different sizes");
    return _Left._Equals(_Right);
}

其中进行了静态断言,如果两个tuple的元素个数不相同,会引发一个编译时的错误。如果对应的类型不能用==进行比较,在模板特化时也会引发编译期的错误,例如,tuple<std::string, int>不能和tuple<int, char>比较,因为std::string和int是不能用==进行比较的。

7.总结

终于写完了,也是想了好久才写的,涉及到的知识点主要是模版推导。写的不好的地方,欢迎批评指正;如果觉得好,点个赞,收藏关注!

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

xss靶场实战(xss-labs-master靶场)

xss-labs-master靶场链接&#xff1a;https://pan.baidu.com/s/1X_uZLF3CWw2Cmt3UnZ1bTw?pwdgk9c 提取码&#xff1a;gk9c xss-labs level 1 修改 url 地址中的name<script>alert(1)</script>&#xff0c;便可以通关 level 2 在搜索框中输入的 JS 代码无法执行 …

Programming Abstractions in C阅读笔记:p293-p302

《Programming Abstractions in C》学习第73天&#xff0c;p293-p302总结&#xff0c;总计10页。 一、技术总结 1.时间复杂度 (1)quadratic time(二次时间) p293, Algorithms like selection sort that exhibit O(N^2) performance are said to run in quadratic time。 2…

windows实现ip1:port1转发至ip2:port2教程

第一步&#xff1a;创建虚拟网卡(如果ip1为本机127.0.0.1跳过此步骤)&#xff0c;虚拟网卡的IPV4属性设置ip1 1> 创建虚拟网卡参见 Windows系统如何添加虚拟网卡&#xff08;环回网络适配器&#xff09;_windows添加虚拟网卡-CSDN博客​​​​​​ 2> 设置虚拟网卡使用…

C++——类和对象(1)

1. 类 我们之前提及过C语言是面向过程的语言&#xff0c;其解决问题的方式是关注问题过程&#xff0c;然后逐步解决。而C是面向对象编程&#xff0c;聚焦于对象&#xff0c;依靠多个对象之间的交互关系解决问题。而类这个概念的引入则是面向对象的最深刻体现。 1.1 C中的结构体…

32单片机基础:OLED调试工具的使用

下面会介绍OLED显示屏的驱动函数模块&#xff0c;先学会如何使用&#xff0c;至于OLED屏幕的原理和代码编写&#xff0c; 我们之后会再写一篇。 现在我们就是用OLED当一个调试的显示屏&#xff0c;方便我们调试程序。 为什么要调试呢&#xff0c;是为了方便我们看现象&#…

人工智能 — 点云模型

目录 一、点云模型1、三维图像2、点云1、概念2、内容 3、点云处理的三个层次1、低层次处理方法2、中层次处理方法3、高层次处理方法 二、Spin image 一、点云模型 1、三维图像 三维图像是一种特殊的信息表达形式&#xff0c;其特征是表达的空间中三个维度的数据。 和二维图像…

涵盖5大领域的机器学习工具介绍

随着数据的产生及其使用量的不断增加&#xff0c;对机器学习模型的需求也在成倍增加。由于ML系统包含了算法和丰富的ML库&#xff0c;它有助于分析数据和做出决策。难怪机器学习的知名度越来越高&#xff0c;因为ML应用几乎主导了现代世界的每一个方面。随着企业对这项技术的探…

Mockito单元测试Mockito对Service层的测试案例

前言 以下是关于Mockito的API使用文档 官网&#xff1a;http://mockito.org/ 官网英文API文档&#xff1a;https://javadoc.io/static/org.mockito/mockito-core/5.10.0/help-doc.html#index 非官方中文API文档&#xff1a;https://gitee.com/wnboy/mockito-doc-zh#mockito-%E…

软件运维维保方案-套用文档

软件运维维保方案 项目情况 1.1 项目背景 简述项目的来源、目的和重要性。 说明项目的规模、预算和预期目标。 1.2 项目现状 分析当前系统/软件的运行状态、存在的问题和潜在风险。 提供最近一次的维护报告或相关统计数据。服务简述 2.1 服务内容 明确运维服务的具体内容&…

JSON(javaScript Object Notation,Js对象标记)—我耀学IT

Json是一种轻量级的数据交换格式&#xff0c;目前使用非常广泛&#xff0c;是一种轻量级的数据交换格式。易于人阅读和编写&#xff0c;可以在多种语言之间进行数据交换 。同时也易于机器解析和生成 1.1json的值: 值可以是对象、数组、数字、字符串或者三个字面值(false、nul…

【数据分析之Numpy基础003】数组形状大变身!轻松掌握改变数组形状的技巧

处理数组的一项重要工作就是改变数组的维度&#xff0c;包括提高数组的维度和降低数组的维度&#xff0c;还包括数组的转置、拼接、分隔等。 Numpy为大家提供了大量的API可以很轻松的完成这些数组的操作。 1、改变数组的维度 如上篇文章使用到的reshape方法&#xff0c;将一维…

各国的通胀是多少?央行又使用那些指标?昂首资本1分钟分享

各国的通胀是多少&#xff1f;央行又使用哪些指标&#xff1f;今天昂首资本1分钟快速分享 在美国和欧盟&#xff0c;作为一个中期通胀目标&#xff0c;使用了一个目标指标&#xff0c;通常是为长达两年的前景计算的。在美国和欧盟&#xff0c;中期通胀目标是2%。在俄罗斯&…

【NCom】:通过高温气相合成调节Pt-CeO2相互作用以提高晶格氧的还原性

摘要&#xff1a;在这项工作中&#xff0c;我们比较了通过两种方法制备的 Pt 单原子催化剂&#xff08;SAC&#xff09;的 CO 氧化性能&#xff1a;&#xff08;1&#xff09;传统的湿化学合成&#xff08;强静电吸附strong electrostatic adsorption–SEA&#xff09;&#xf…

Mybatis总结--传参

MyBatis 传递参数&#xff1a;从 java 代码中把参数传递到 mapper.xml 文件 六、一个简单参数&#xff1a; Dao 接口中方法的参数只有一个简单类型&#xff08; java 基本类型和 String &#xff09;&#xff0c; 占位符 #{ 任意字符 } &#xff0c;和方法的参数名无关…

电脑msvcp100.dll丢失了怎么办?msvcp100.dll丢失的5种解决方法

当计算机系统中无法找到msvcp100.dll文件&#xff0c;或者遭遇msvcp100.dll丢失的情况时&#xff0c;可能会引发一系列运行问题和功能障碍。msvcp100.dll是Microsoft Visual C Redistributable Package的一部分&#xff0c;这是一个至关重要的动态链接库文件&#xff0c;对于许…

如何利用EXCEL批量插入图片

目录 1.excel打开目标表格&#xff1b; 2.点开视图-宏-录制宏&#xff0c;可以改宏的名字或者选择默认&#xff1b; 3.然后点开视图-宏-查看宏 4.点编辑进去 5.修改代码&#xff1a; &#xff08;1&#xff09;打开之后会显示有一堆代码 &#xff08;2&#xff09;将这个…

【C++进阶】STL容器--list底层剖析(迭代器封装)

目录 前言 list的结构与框架 list迭代器 list的插入和删除 insert erase list析构函数和拷贝构造 析构函数 拷贝构造 赋值重载 迭代器拷贝构造、析构函数实现问题 const迭代器 思考 总结 前言 前边我们了解了list的一些使用及其注意事项&#xff0c;今天我们进一步深入…

132 Linux 系统编程9 ,IO操作,lseek 函数,truncate函数,查看文件的表示形式

一 lseek 函数 函数说明&#xff1a;此函数用于文件偏移 Linux中可使用系统函数lseek来修改文件偏移量(读写位置) 每个打开的文件都记录着当前读写位置&#xff0c;打开文件时读写位置是0&#xff0c;表示文件开头&#xff0c;通常读写多少个字节就会将读写位置往后移多少个字…

PixPin:一键搞定截图、长截图、贴图、GIF

名人说&#xff1a;莫道桑榆晚&#xff0c;为霞尚满天。——刘禹锡&#xff08;刘梦得&#xff0c;诗豪&#xff09; 创作者&#xff1a;Code_流苏(CSDN)&#xff08;一个喜欢古诗词和编程的Coder&#x1f60a;&#xff09; 目录 一、什么是PixPin&#xff1f;①PixPin②核心功…

C语言每日一题(61)盛最多水的容器

题目链接 力扣 11 盛最多水的容器 题目描述 给定一个长度为 n 的整数数组 height 。有 n 条垂线&#xff0c;第 i 条线的两个端点是 (i, 0) 和 (i, height[i]) 。 找出其中的两条线&#xff0c;使得它们与 x 轴共同构成的容器可以容纳最多的水。 返回容器可以储存的最大水…