chrome-base 如何实现一个BindOnce

news/2025/2/8 13:01:47 标签: c++, 开发语言

考虑一个问题:

    worker_thread.task_runner()->PostDelayedTask(
        FROM_HERE, base::BindOnce(&Ref::Foo, ref, 1), base::Milliseconds(1000));

BindOnce 是如何实现的呢?

翻看源码:base\functional\bind.h

写的 非常简洁

// Bind as OnceCallback.
template <typename Functor, typename... Args>
inline OnceCallback<internal::MakeUnboundRunType<Functor, Args...>> BindOnce(
    Functor&& functor,
    Args&&... args) {
  static_assert(!internal::IsOnceCallback<std::decay_t<Functor>>() ||
                    (std::is_rvalue_reference<Functor&&>() &&
                     !std::is_const<std::remove_reference_t<Functor>>()),
                "BindOnce requires non-const rvalue for OnceCallback binding."
                " I.e.: base::BindOnce(std::move(callback)).");
  static_assert(
      std::conjunction<
          internal::AssertBindArgIsNotBasePassed<std::decay_t<Args>>...>::value,
      "Use std::move() instead of base::Passed() with base::BindOnce()");

  return internal::BindImpl<OnceCallback>(std::forward<Functor>(functor),
                                          std::forward<Args>(args)...);
}

重要的也就是最后一句:构造一个BindImpl,同时完美转发

internal::BindImpl<OnceCallback>(std::forward<Functor>(functor),
                                          std::forward<Args>(args)...)

base\functional\bind_internal.h

template <template <typename> class CallbackT,
          typename Functor,
          typename... Args>
decltype(auto) BindImpl(Functor&& functor, Args&&... args) {
  // This block checks if each |args| matches to the corresponding params of the
  // target function. This check does not affect the behavior of Bind, but its
  // error message should be more readable.
  static constexpr bool kIsOnce = IsOnceCallback<CallbackT<void()>>::value;
  using Helper = BindTypeHelper<Functor, Args...>;
  using FunctorTraits = typename Helper::FunctorTraits;
  using BoundArgsList = typename Helper::BoundArgsList;
  using UnwrappedArgsList =
      MakeUnwrappedTypeList<kIsOnce, FunctorTraits::is_method, Args&&...>;
  using BoundParamsList = typename Helper::BoundParamsList;
  static_assert(
      MakeFunctorTraits<Functor>::is_stateless,
      "Capturing lambdas and stateful lambdas are intentionally not supported. "
      "Please use base::Bind{Once,Repeating} directly to bind arguments.");
  static_assert(
      AssertBindArgsValidity<std::make_index_sequence<Helper::num_bounds>,
                             BoundArgsList, UnwrappedArgsList,
                             BoundParamsList>::ok,
      "The bound args need to be convertible to the target params.");

  using BindState = MakeBindStateType<Functor, Args...>;
  using UnboundRunType = MakeUnboundRunType<Functor, Args...>;
  using Invoker = Invoker<BindState, UnboundRunType>;
  using CallbackType = CallbackT<UnboundRunType>;

  // Store the invoke func into PolymorphicInvoke before casting it to
  // InvokeFuncStorage, so that we can ensure its type matches to
  // PolymorphicInvoke, to which CallbackType will cast back.
  using PolymorphicInvoke = typename CallbackType::PolymorphicInvoke;
  PolymorphicInvoke invoke_func;
  if constexpr (kIsOnce) {
    invoke_func = Invoker::RunOnce;
  } else {
    invoke_func = Invoker::Run;
  }

  using InvokeFuncStorage = BindStateBase::InvokeFuncStorage;
  return CallbackType(BindState::Create(
      reinterpret_cast<InvokeFuncStorage>(invoke_func),
      std::forward<Functor>(functor), std::forward<Args>(args)...));
}

BindState的实现

// This stores all the state passed into Bind().
template <typename Functor, typename... BoundArgs>
struct BindState final : BindStateBase {
  using IsCancellable = std::bool_constant<
      CallbackCancellationTraits<Functor,
                                 std::tuple<BoundArgs...>>::is_cancellable>;
  template <typename ForwardFunctor, typename... ForwardBoundArgs>
  static BindState* Create(BindStateBase::InvokeFuncStorage invoke_func,
                           ForwardFunctor&& functor,
                           ForwardBoundArgs&&... bound_args) {
    // Ban ref counted receivers that were not yet fully constructed to avoid
    // a common pattern of racy situation.
    BanUnconstructedRefCountedReceiver<ForwardFunctor>(bound_args...);

    // IsCancellable is std::false_type if
    // CallbackCancellationTraits<>::IsCancelled returns always false.
    // Otherwise, it's std::true_type.
    return new BindState(IsCancellable{}, invoke_func,
                         std::forward<ForwardFunctor>(functor),
                         std::forward<ForwardBoundArgs>(bound_args)...);
  }

  Functor functor_;
  std::tuple<BoundArgs...> bound_args_;

 private:
  static constexpr bool is_nested_callback =
      MakeFunctorTraits<Functor>::is_callback;

  template <typename ForwardFunctor, typename... ForwardBoundArgs>
  explicit BindState(std::true_type,
                     BindStateBase::InvokeFuncStorage invoke_func,
                     ForwardFunctor&& functor,
                     ForwardBoundArgs&&... bound_args)
      : BindStateBase(invoke_func,
                      &Destroy,
                      &QueryCancellationTraits<BindState>),
        functor_(std::forward<ForwardFunctor>(functor)),
        bound_args_(std::forward<ForwardBoundArgs>(bound_args)...) {
    // We check the validity of nested callbacks (e.g., Bind(callback, ...)) in
    // release builds to avoid null pointers from ending up in posted tasks,
    // causing hard-to-diagnose crashes. Ideally we'd do this for all functors
    // here, but that would have a large binary size impact.
    if (is_nested_callback) {
      CHECK(!IsNull(functor_));
    } else {
      DCHECK(!IsNull(functor_));
    }
  }

  template <typename ForwardFunctor, typename... ForwardBoundArgs>
  explicit BindState(std::false_type,
                     BindStateBase::InvokeFuncStorage invoke_func,
                     ForwardFunctor&& functor,
                     ForwardBoundArgs&&... bound_args)
      : BindStateBase(invoke_func, &Destroy),
        functor_(std::forward<ForwardFunctor>(functor)),
        bound_args_(std::forward<ForwardBoundArgs>(bound_args)...) {
    // See above for CHECK/DCHECK rationale.
    if (is_nested_callback) {
      CHECK(!IsNull(functor_));
    } else {
      DCHECK(!IsNull(functor_));
    }
  }

  ~BindState() = default;

  static void Destroy(const BindStateBase* self) {
    delete static_cast<const BindState*>(self);
  }
};

其中有两个成员变量:

  Functor functor_;
  std::tuple<BoundArgs...> bound_args_;

其中用到了std::tuple ??

为什么chromium不使用std::bind

相对于std::bind, base::Bind能够帮助我们减少生命周期缺陷:

在使用 base::BindOnce() 方法产生一个 base::OnceClosure 的时候,一般会传递一个 base::WeakPrt,而不是一个裸指针。base::WeakPrt能确保所指向的对象销毁时,绑定在对象上的回调能被取消。否则一般会产生一个段错误。

如果声明裸指针则必须使用Unretained符号。

chromium bind出现的更早,且通过BindOnce与BindRepeating 对生命周期做了更加细致的区分,C++ 14之后好像也有了类似的bind?

c++中的元组

元组基本使用

https://en.cppreference.com/w/cpp/utility/tuple/get c11中引入了 std::tuple,并在后续的c14中扩充了使用方式

#include <iostream>
#include <string>
#include <tuple>
 
int main()
{
    auto t = std::make_tuple(1, "Foo", 3.14);
    // index-based access
    std::cout << "(" << std::get<0>(t) << ", " 
              << std::get<1>(t)
              << ", " << std::get<2>(t) << ")\n";
    // type-based access (C++14 or later)
    std::cout << "(" << std::get<int>(t) << ", " 
              << std::get<const char*>(t)
              << ", " << std::get<double>(t) << ")\n";
    // Note: std::tie and structured binding may also be used to decompose a tuple
}

元组用作函数参数

#include <iostream>
#include <string>
#include <tuple>

void foo(int a1, const char* a2, double a3) {
    // Do something
    std::cout << a1 << std::endl;
    std::cout << a2 << std::endl;
    std::cout << a3 << std::endl;
}

int main() {
    auto t = std::make_tuple(1, "Foo", 3.14);
    foo(std::get<0>(t), std::get<1>(t), std::get<2>(t));

    return 0;
}

观察后会发现,配合c++11的可变参数模板,可以写出这样一个函数,整体如下:

https://zhuanlan.zhihu.com/p/465077081

#include <iostream>
#include <string>
#include <tuple>

void foo(int a1, const char* a2, double a3) {
    // Do something
    std::cout << a1 << std::endl;
    std::cout << a2 << std::endl;
    std::cout << a3 << std::endl;
}

template <typename F, typename T, std::size_t... I>
void bar(F f, const T& t) {
  f(std::get<I>(t)...);
}

// 或者这样定义也可以
template <std::size_t... I, typename F, typename T>
void bar2(F f, const T& t) {
  f(std::get<I>(t)...);
}

int main() {
    auto t = std::make_tuple(1, "Foo", 3.14);
    //foo(std::get<0>(t), std::get<1>(t), std::get<2>(t));
    bar<decltype(foo), decltype(t), 0, 1, 2>(foo, t);

    // 少了函数类型和调用类型的模板,这里编译器帮我们做了类型推导
    bar2<0, 1, 2>(foo, t);
    
    return 0;
}

使用std::index_sequence

上述调用方式比较笨拙,不够优雅灵活,还需要手动写模板参数,其实看看0,1,2的序列,其实是从0到N-1的整数序列,c++14中正好提供了生成这种序列的机制:std::index_sequence,这里面有一系列的序列别名,我们这里更简便的使用index_sequence就能解决问题了,具体如下

https://zhuanlan.zhihu.com/p/490967621

  /// Alias template index_sequence
  template<size_t... _Idx>
    using index_sequence = integer_sequence<size_t, _Idx...>;

  /// Alias template make_index_sequence
  template<size_t _Num>
    using make_index_sequence = make_integer_sequence<size_t, _Num>;

  /// Alias template index_sequence_for
  template<typename... _Types>
    using index_sequence_for = make_index_sequence<sizeof...(_Types)>;

有了这个基础设施以后我们就可以这样写代码了:

https://blog.csdn.net/qq_51986723/article/details/127602490 std::index_sequence如何使用

#include <iostream>
#include <string>
#include <tuple>

void foo(int a1, const char* a2, double a3) {
    // Do something
    std::cout << a1 << std::endl;
    std::cout << a2 << std::endl;
    std::cout << a3 << std::endl;
}

template <typename F, typename T, std::size_t... I>
void barImpl(F f, const T& t, std::index_sequence<I...>) {
  f(std::get<I>(t)...);
}

template<typename F, typename T>
void bar(F f, const T& t) {
  barImpl(f, t, std::make_index_sequence<std::tuple_size<T>::value>());
}

int main() {
    auto t = std::make_tuple(1, "Foo", 3.14);

    bar<decltype(foo), decltype(t)>(foo, t);

    return 0;
}

增加返回值

上面的虽然写起来比较简单了,但是没有返回值,我们可以采用decltype特性来解决。

#include <iostream>
#include <string>
#include <tuple>

void foo(int a1, const char* a2, double a3) {
    // Do something
    std::cout << a1 << std::endl;
    std::cout << a2 << std::endl;
    std::cout << a3 << std::endl;
}

template <typename F, typename T, std::size_t... I>
auto barImpl(F f, const T& t, std::index_sequence<I...>) 
 -> decltype(f(std::get<I>(t)...)){
  return f(std::get<I>(t)...);
}

template<typename F, typename T>
auto bar(F f, const T& t) 
 -> decltype(barImpl(f, t, std::make_index_sequence<std::tuple_size<T>::value>())) {
  return barImpl(f, t, std::make_index_sequence<std::tuple_size<T>::value>());
}
int main() {
    auto t = std::make_tuple(1, "Foo", 3.14);

    bar<decltype(foo), decltype(t)>(foo, t);

    return 0;

这样就使用任意的函数调用了,包括带返回值的和不带返回值的。

最终版本

  1. 封装一个万能调用类 +

  2. 直接传参

  3. 完美转发

将调用函数及参数封装进类中,在需要的时候调用Run一下就好了。

#include <iostream>
#include <string>
#include <tuple>

template <typename Functor, typename... BoundArgs>
class OnceCallBack {
private:
  template <typename Functor1, typename TupleType1, std::size_t... I1>
  static auto RunImpl(Functor1 f, const TupleType1& t, std::index_sequence<I1...>) -> decltype(f(std::get<I1>(t)...)) {
    return f(std::get<I1>(t)...);
  }

  Functor* f_;
  std::tuple<BoundArgs...> t_;
public:

  OnceCallBack(Functor&& f, BoundArgs&&... t)
    : f_(std::forward<Functor>(f)), t_(std::forward<BoundArgs>(t)...) {}

  auto Run() -> decltype(RunImpl(f_, t_, std::make_index_sequence<std::tuple_size<std::tuple<BoundArgs...>>::value>())) {
    return RunImpl(f_, t_, std::make_index_sequence<std::tuple_size<std::tuple<BoundArgs...>>::value>());
  }
};

double foo_ret(double a1, char a2, const char* a3) {
  // Do something
  std::cout << a1 << std::endl;
  std::cout << a2 << std::endl;
  std::cout << a3 << std::endl;

  return 1.2;
}

int main() {
  // 函数模板参数类型编译器可以推导,但是类不行,所以这里需要很繁琐的传递参数类型
  OnceCallBack<decltype(foo_ret), decltype(1.0), decltype('c'), 
    decltype("this is my test")> 
            callback(std::move(foo_ret), 1.0, 'c', "this is my test");
  std::cout << callback.Run() << std::endl;
}

到这里,是不是已经很熟悉了。至此,一个最简单的base::Bind 方法就算实现完成了。

Chromium中的base::Bind

chromium中除了OnceCallBack还有RepeatingCallback,另外考虑的各种对象的生命周期问题,这样就涉及到指针传递,所以做的更为复杂,主要涉及的类包括:

  • BindStateBase

  • BindState

  • OnceCallBack

  • RepeatingCallback

  • Invoker

  • UnWrap

看到的也就是开头看到的源码

std::bind

C++11 bind绑定器,是一个函数模板 ,可以自动推演模板类型参数=> 返回的结果还是一个函数对象

bind占位符最多有20个参数

#include <iostream>
#include <typeinfo>
#include <string>
#include <memory>
#include <vector>
#include <functional>
#include <thread>
using namespace std;
using namespace placeholders;
 
/*
C++11 bind绑定器 => 返回的结果还是一个函数对象
*/
 
void hello(string str) { cout << str << endl; }
int sum(int a, int b) { return a + b; }
class Test
{
public:
  int sum(int a, int b) { return a + b; }
};
int main()
{
  //bind是函数模板 可以自动推演模板类型参数
  bind(hello, "hello bind!")();//返回的结果是绑定器,也就是函数对象 最后一个()表示调用函数对象的operator() 
  cout << bind(sum, 10, 20)() << endl;
  cout << bind(&Test::sum, Test(), 20, 30)() << endl;
 
  //参数占位符  绑定器出了语句,无法继续使用
  //只是占位的作用,调用的时候就要传递参数了 
  //书写的时候使用多少个占位符,就是意味着用户调用的时候要传入几个参数
  bind(hello, placeholders::_1)("hello bind 2!");
  cout << bind(sum, placeholders::_1, placeholders::_2)(200, 300) << endl;
 
  //此处把bind返回的绑定器binder就复用起来了
  function<void(string)> func1 = bind(hello, _1);
  func1("hello china!");
  func1("hello shan xi!");
  func1("hello si chuan!");
 
  return 0;
}
  /**
   *  @brief Function template for std::bind.
   *  @ingroup binders
   */
  template<typename _Func, typename... _BoundArgs>
    inline typename
    _Bind_helper<__is_socketlike<_Func>::value, _Func, _BoundArgs...>::type
    bind(_Func&& __f, _BoundArgs&&... __args)
    {
      typedef _Bind_helper<false, _Func, _BoundArgs...> __helper_type;
      return typename __helper_type::type(std::forward<_Func>(__f),
            std::forward<_BoundArgs>(__args)...);
    }
---------------------------------------------------------------------------------------------------------------
  template<bool _SocketLike, typename _Func, typename... _BoundArgs>
    struct _Bind_helper
    : _Bind_check_arity<typename decay<_Func>::type, _BoundArgs...>
    {
      typedef typename decay<_Func>::type __func_type;
      typedef _Bind<__func_type(typename decay<_BoundArgs>::type...)> type;
    };
--------------------------------------------------

   template<typename _Functor, typename... _Bound_args>
    class _Bind<_Functor(_Bound_args...)>
    : public _Weak_result_type<_Functor>
    {
      typedef typename _Build_index_tuple<sizeof...(_Bound_args)>::__type
  _Bound_indexes;

      _Functor _M_f;
      tuple<_Bound_args...> _M_bound_args;

      // Call unqualified
      template<typename _Result, typename... _Args, std::size_t... _Indexes>
  _Result
  __call(tuple<_Args...>&& __args, _Index_tuple<_Indexes...>)
  {
    return std::__invoke(_M_f,
        _Mu<_Bound_args>()(std::get<_Indexes>(_M_bound_args), __args)...
        );
  }

      // Call as const
      template<typename _Result, typename... _Args, std::size_t... _Indexes>
  _Result
  __call_c(tuple<_Args...>&& __args, _Index_tuple<_Indexes...>) const
  {
    return std::__invoke(_M_f,
        _Mu<_Bound_args>()(std::get<_Indexes>(_M_bound_args), __args)...
        );
  }

      // Call as volatile
      template<typename _Result, typename... _Args, std::size_t... _Indexes>
  _Result
  __call_v(tuple<_Args...>&& __args,
     _Index_tuple<_Indexes...>) volatile
  {
    return std::__invoke(_M_f,
        _Mu<_Bound_args>()(__volget<_Indexes>(_M_bound_args), __args)...
        );
  }

      // Call as const volatile
      template<typename _Result, typename... _Args, std::size_t... _Indexes>
  _Result
  __call_c_v(tuple<_Args...>&& __args,
       _Index_tuple<_Indexes...>) const volatile
  {
    return std::__invoke(_M_f,
        _Mu<_Bound_args>()(__volget<_Indexes>(_M_bound_args), __args)...
        );
  }

      template<typename _BoundArg, typename _CallArgs>
  using _Mu_type = decltype(
      _Mu<typename remove_cv<_BoundArg>::type>()(
        std::declval<_BoundArg&>(), std::declval<_CallArgs&>()) );

      template<typename _Fn, typename _CallArgs, typename... _BArgs>
  using _Res_type_impl
    = typename result_of< _Fn&(_Mu_type<_BArgs, _CallArgs>&&...) >::type;

      template<typename _CallArgs>
  using _Res_type = _Res_type_impl<_Functor, _CallArgs, _Bound_args...>;

      template<typename _CallArgs>
  using __dependent = typename
    enable_if<bool(tuple_size<_CallArgs>::value+1), _Functor>::type;

      template<typename _CallArgs, template<class> class __cv_quals>
  using _Res_type_cv = _Res_type_impl<
    typename __cv_quals<__dependent<_CallArgs>>::type,
    _CallArgs,
    typename __cv_quals<_Bound_args>::type...>;

     public:
      template<typename... _Args>
  explicit _Bind(const _Functor& __f, _Args&&... __args)
  : _M_f(__f), _M_bound_args(std::forward<_Args>(__args)...)
  { }

      template<typename... _Args>
  explicit _Bind(_Functor&& __f, _Args&&... __args)
  : _M_f(std::move(__f)), _M_bound_args(std::forward<_Args>(__args)...)
  { }

      _Bind(const _Bind&) = default;

      _Bind(_Bind&& __b)
      : _M_f(std::move(__b._M_f)), _M_bound_args(std::move(__b._M_bound_args))
      { }

      // Call unqualified
      template<typename... _Args,
         typename _Result = _Res_type<tuple<_Args...>>>
  _Result
  operator()(_Args&&... __args)
  {
    return this->__call<_Result>(
        std::forward_as_tuple(std::forward<_Args>(__args)...),
        _Bound_indexes());
  }

      // Call as const
      template<typename... _Args,
         typename _Result = _Res_type_cv<tuple<_Args...>, add_const>>
  _Result
  operator()(_Args&&... __args) const
  {
    return this->__call_c<_Result>(
        std::forward_as_tuple(std::forward<_Args>(__args)...),
        _Bound_indexes());
  }

#if __cplusplus > 201402L
# define _GLIBCXX_DEPR_BIND \
      [[deprecated("std::bind does not support volatile in C++17")]]
#else
# define _GLIBCXX_DEPR_BIND
#endif
      // Call as volatile
      template<typename... _Args,
         typename _Result = _Res_type_cv<tuple<_Args...>, add_volatile>>
  _GLIBCXX_DEPR_BIND
  _Result
  operator()(_Args&&... __args) volatile
  {
    return this->__call_v<_Result>(
        std::forward_as_tuple(std::forward<_Args>(__args)...),
        _Bound_indexes());
  }

      // Call as const volatile
      template<typename... _Args,
         typename _Result = _Res_type_cv<tuple<_Args...>, add_cv>>
  _GLIBCXX_DEPR_BIND
  _Result
  operator()(_Args&&... __args) const volatile
  {
    return this->__call_c_v<_Result>(
        std::forward_as_tuple(std::forward<_Args>(__args)...),
        _Bound_indexes());
  }
    };

placeholders 占位符:最多支持20个


http://www.niftyadmin.cn/n/5844875.html

相关文章

第三个Qt开发实例:利用之前已经开发好的LED驱动在Qt生成的界面中控制LED2的亮和灭

前言 上一篇博文 https://blog.csdn.net/wenhao_ir/article/details/145459006 中&#xff0c;我们是直接利用GPIO子系统控制了LED2的亮和灭&#xff0c;这篇博文中我们利用之前写好的LED驱动程序在Qt的生成的界面中控制LED2的亮和灭。 之前已经在下面两篇博文中实现了LED驱动…

[数据结构] Set的使用与注意事项

目录 Set的说明 常见方法说明 注意事项 TreeSet使用案例 Set的说明 Set与Map主要的不同有两点: Set是继承自Collection的接口类,Set中只存储了Key. 常见方法说明 方法解释boolean add(E e)添加元素,但重复元素不会被添加成功void clear()清空集合boolean contains(Object…

Chrome 浏览器 支持多账号登录和管理的浏览器容器解决方案

根据搜索结果&#xff0c;目前没有直接提到名为“chrometable”的浏览器容器或插件。不过&#xff0c;从功能描述来看&#xff0c;您可能需要的是一个能够支持多账号登录和管理的浏览器容器解决方案。以下是一些可能的实现方式&#xff1a; 1. 使用 Docker 容器化部署 Chrome …

CVPR2021 | VMI-FGSM VNI-FGSM | 通过方差调整增强对抗攻击的可迁移性

Enhancing the Transferability of Adversarial Attacks through Variance Tuning 摘要-Abstract引言-Introduction相关工作-Related Work方法-Methodology动机-Motivation基于方差调整的梯度攻击-Variance Tuning Gradient-based Attacks不同攻击的关系-Relationships among V…

亚博microros小车-原生ubuntu支持系列:24 巡线驾驶

这篇跟之前的颜色识别类似&#xff0c;亚博microros小车-原生ubuntu支持系列&#xff1a;21 颜色追踪-CSDN博客 1、程序功能说明 程序启动后&#xff0c;调整摄像头的俯仰角&#xff0c;把摄像头往下掰动&#xff0c;使得摄像头可以看到线&#xff0c;然后点击图像窗口&#…

golang命令大全13--相关资源与学习路径【完】

1、官方资源 Go 官方文档&#xff1a; https://golang.org/doc/提供详细的语言特性、标准库和工具链的文档。 Go Blog&#xff1a; https://blog.golang.org/包含关于 Go 语言最新特性、优化和实践的文章。 Go Modules 文档&#xff1a; https://golang.org/ref/mod深入解…

JPG、PNG、GIF有什么区别?

JPG、PNG、GIF是三种常见的数字图像格式&#xff0c;它们各自具有不同的特点和适用场景。以下是它们之间的主要区别以及如何进行选择&#xff1a; 一、JPG与PNG、GIF的区别 1. JPG&#xff08;JPEG&#xff09;&#xff1a; 特点&#xff1a;JPG是一种有损压缩的图像格式&…

go结构体详解

结构体简介 Golang 中没有“类”的概念&#xff0c;Golang 中的结构体和其他语言中的类有点相似。和其他面向对象语言中的类相比&#xff0c;Golang 中的结构体具有更高的扩展性和灵活性。 Golang 中的基础数据类型可以表示一些事物的基本属性&#xff0c;但是当我们想表达一…