std::unordered_multiset

本文详细介绍了C++标准库中的unordered_multiset容器,包括其构造函数、元素访问、迭代器、容量管理、修改器(如插入、删除)、查找方法、桶接口以及哈希策略。通过实例演示展示了如何使用和调整unordered_multiset的各种特性。

std::unordered_multiset

介绍

成员函数

非成员函数

介绍

// unordered_multiset 模板定义
template<
    class Key,
    class Hash = std::hash<Key>,
    class KeyEqual = std::equal_to<Key>,
    class Allocator = std::allocator<Key>> class unordered_multiset;
namespace pmr {
    template <class Key,
              class Hash = std::hash<Key>,
              class Pred = std::equal_to<Key>>
    using unordered_multiset = std::unordered_multiset<Key, Hash, Pred, std::pmr::polymorphic_allocator<Key>>
}
  • std::unordered_multiset 介绍摘选自 cppreference.com 中文网 std::unordered_multiset 介绍
  • unordered_multiset 是关联容器,含有可能非唯一 Key 类型对象的集合。搜索、插入和移除拥有平均常数时间复杂度
  • 元素在内部并不以任何顺序排序,只是被组织到桶中。元素被放入哪个桶完全依赖其值的哈希
  • 这允许快速访问单独的元素,因为一旦计算哈希,它就指代放置该元素的准确的桶
  • 不要求此容器的迭代顺序稳定(故例如 std::equal 不能用于比较二个 std::unordered_multiset )
  • 了关键比较等价(以 key_eq() 为比较器比较相等)的每组元素组成迭代顺序中的相接子范围,它可用 equal_range() 访问
  • unordered_multiset可以存储多个相同的数值,unordered_set只能存储一个相同的数值
  • 无序容器,值存储的位置可能会变化,所以直接访问元素

成员函数

构造析构
#include <QCoreApplication>
#include <algorithm>
#include <iostream>
#include <string>
#include <unordered_set>

auto Print(const std::string &msg,
           const std::unordered_multiset<int, std::hash<int>, std::less<int>,
                                         std::allocator<int>> &lst) {
  std::cout << msg << " : ";
  for (const auto &pa : lst) {
    std::cout << pa << "\t";
  }
  std::cout << "\n";
}

auto Print(const std::string &msg, const std::unordered_multiset<int> &lst) {
  std::cout << msg << " : ";
  for (const auto &pa : lst) {
    std::cout << pa << "\t";
  }
  std::cout << "\n";
}

int main(int argc, char *argv[]) {
  QCoreApplication a(argc, argv);

  using uset_int = std::unordered_multiset<int>;
  uset_int s1; //默认构造
  Print("s1", s1);

  uset_int s2(10); //设置桶数量
  Print("s2", s2);
  uset_int s3(10, std::hash<int>()); //设置桶数量,哈希散列
  Print("s3", s3);
  std::unordered_multiset<int, std::hash<int>, std::less<int>> s4(
      10, std::hash<int>(), std::less<int>{}); //设置桶数量,哈希散列,比较器
  Print("s4", s4);

  std::unordered_multiset<int, std::hash<int>, std::less<int>,
                          std::allocator<int>>
      s5(10, std::hash<int>(), std::less<int>(),
         std::allocator<int>()); //设置桶数量,哈希散列,比较器,分配器
  Print("s5", s5);

  uset_int s6(10, std::allocator<int>()); //设置桶数量,分配器
  Print("s6", s6);

  uset_int s7{1, 2, 3, 4, 5, 6, 5}; //初始化列表
  Print("s7", s7);

  uset_int s8(s7.begin(), s7.end()); //迭代器初始化
  Print("s8", s8);

  uset_int s9(s8); //拷贝构造
  Print("s9", s9);
  uset_int s10(s9, std::allocator<int>());
  Print("s10", s10);

  uset_int s11(std::move(s10)); //移动构造
  Print("s11", s11);
  uset_int s12(std::move(s11), std::allocator<int>());
  Print("s12", s12);

  //析构函数默认
  return 0; // a.exec();
}

输出结果:
s1 :
s2 :
s3 :
s4 :
s5 :
s6 :
s7 : 1 2 3 4 5 5 6
s8 : 1 2 3 4 5 5 6
s9 : 1 2 3 4 5 5 6
s10 : 1 2 3 4 5 5 6
s11 : 1 2 3 4 5 5 6
s12 : 1 2 3 4 5 5 6

元素访问
int main(int argc, char *argv[]) {
  QCoreApplication a(argc, argv);

  using uset_int = std::unordered_multiset<int>;
  uset_int s1{1, 2, 3, 4, 5, 6, 5};
  std::cout << "s1 : ";
  for (const auto &val : s1) {
    std::cout << val << "\t";
  }
  std::cout << "\n";
  return 0; // a.exec();
}

输出结果:
s1 : 1 2 3 4 5 5 6

迭代器
int main(int argc, char *argv[]) {
  QCoreApplication a(argc, argv);

  using uset_int = std::unordered_multiset<int>;
  uset_int s1{1, 2, 3, 4, 5, 6, 5};
  uset_int::iterator iter = s1.begin();
  std::cout << "s1 : ";
  for (; iter != s1.end(); ++iter) {
    std::cout << *iter << "\t";
  }
  std::cout << "\n";

  uset_int::const_iterator citer = s1.cbegin();
  std::cout << "s1 : ";
  for (; citer != s1.cend(); ++citer) {
    std::cout << *citer << "\t";
  }
  std::cout << "\n";
  return 0; // a.exec();
}

输出结果:
s1 : 1 2 3 4 5 5 6
s1 : 1 2 3 4 5 5 6

容量
auto Print(const std::string &msg, const std::unordered_multiset<int> &lst) {
  std::cout << msg << " : ";
  for (const auto &pa : lst) {
    std::cout << pa << "\t";
  }
  std::cout << "\n";
}

int main(int argc, char *argv[]) {
  QCoreApplication a(argc, argv);

  using uset_int = std::unordered_multiset<int>;
  uset_int s1{1, 2, 3, 4, 5, 6, 5};
  Print("s1", s1);

  //检查容器是否为空
  std::cout << std::boolalpha << "s1.empty() : " << s1.empty() << std::endl;

  std::cout << "s1.size() : " << s1.size() << std::endl; //返回容纳的元素数
  std::cout << "s1.max_size() : " << s1.max_size()
            << std::endl; //返回可容纳的最大元素数,和平台有关
  return 0;               // a.exec();
}

输出结果:
s1 : 1 2 3 4 5 5 6
s1.empty() : false
s1.size() : 7
s1.max_size() : 768614336404564650

修改器
auto Print(const std::string &msg, const std::unordered_multiset<int> &lst) {
  std::cout << msg << " : ";
  for (const auto &pa : lst) {
    std::cout << pa << "\t";
  }
  std::cout << "\n";
}

int main(int argc, char *argv[]) {
  QCoreApplication a(argc, argv);

  using uset_int = std::unordered_multiset<int>;
  uset_int s1{1, 2, 3, 4, 5, 6, 5};
  Print("s1", s1);

  s1.clear(); //  清除内容
  Print("s1", s1);
  s1.insert(20); //插入元素或结点
  Print("s1", s1);
  s1.emplace(30); //原位构造元素
  Print("s1", s1);

  s1.emplace_hint(s1.end(), 40); //使用提示原位构造元素
  Print("s1", s1);

  s1.erase(s1.begin()); //擦除元素
  Print("s1", s1);

  uset_int s2;
  s2.swap(s1);
  Print("s1", s1);
  Print("s2", s2);
  return 0; // a.exec();
}

输出结果:
s1 : 1 2 3 4 5 5 6
s1 :
s1 : 20
s1 : 20 30
s1 : 20 30 40
s1 : 30 40
s1 :
s2 : 30 40

查找
auto Print(const std::string &msg, const std::unordered_multiset<int> &lst) {
  std::cout << msg << " : ";
  for (const auto &pa : lst) {
    std::cout << pa << "\t";
  }
  std::cout << "\n";
}

int main(int argc, char *argv[]) {
  QCoreApplication a(argc, argv);

  using uset_int = std::unordered_multiset<int>;
  uset_int s1{1, 2, 3, 4, 5, 6, 5, 6};
  Print("s1", s1);

  // 返回匹配特定键的元素数量
  std::cout << "s1.count(1) : " << s1.count(1) << std::endl;
  std::cout << "s1.count(5) : " << s1.count(5) << std::endl;
  std::cout << "s1.count(6) : " << s1.count(6) << std::endl;

  // 寻找带有特定键的元素,返回 key 对应的任意一个值的迭代器
  uset_int::const_iterator iter = s1.find(5);
  if (iter != s1.cend()) {
    std::cout << "s1.find(5) : " << *iter << std::endl;
  } else {
    std::cout << "s1.find(5) : s1.cend()" << std::endl;
  }

  // 返回容器中所有键等于 key 的元素范围
  // 返回容器中所有键等于 key
  // 的元素范围。范围以二个迭代器定义,第一个指向所需范围的首元素,而第二个指向范围的尾后一位元素。
  // 返回含有容器中所有键等价于 x 的元素的范围。此重载只有在有限定标识
  // Hash::is_transparent 与 KeyEqual::is_transparent
  // 均合法并指代类型时才参与重载决议。这假设能用 K 和 Key 类型一起调用这种 Hash
  // 还有 KeyEqual 是通透的,进而允许不用构造 Key 的实例就调用此函数
  std::pair<uset_int::iterator, uset_int::iterator> pa = s1.equal_range(5);
  if (pa.first == s1.end())
    std::cout << "pa.first is empty" << std::endl;
  else
    std::cout << "s1.equal_range(5) first : " << *pa.first << std::endl;
  if (pa.second == s1.end())
    std::cout << "pa.second is empty" << std::endl;
  else
    std::cout << "s1.equal_range(5) second : " << *pa.second << std::endl;
  return 0; // a.exec();
}

输出结果:
s1 : 1 2 3 4 5 5 6 6
s1.count(1) : 1
s1.count(5) : 2
s1.count(6) : 2
s1.find(5) : 5
s1.equal_range(5) first : 5
s1.equal_range(5) second : 6

非成员函数

auto Print(const std::string &msg, const std::unordered_multiset<int> &lst) {
  std::cout << msg << " : ";
  for (const auto &pa : lst) {
    std::cout << pa << "\t";
  }
  std::cout << "\n";
}

int main(int argc, char *argv[]) {
  QCoreApplication a(argc, argv);

  using uset_int = std::unordered_multiset<int>;
  uset_int s1{1, 2, 3, 4, 5, 6, 5};
  uset_int s2{1, 2, 3, 4, 5, 6, 5};
  Print("s1", s1);
  Print("s2", s2);

  std::cout << std::boolalpha << "s1 == s2 : " << (s1 == s2) << std::endl;

  uset_int s3;
  std::swap(s3, s2);
  Print("s2", s2);
  Print("s3", s3);
  return 0; // a.exec();
}

输出结果:
s1 : 1 2 3 4 5 5 6
s2 : 1 2 3 4 5 5 6
s1 == s2 : true
s2 :
s3 : 1 2 3 4 5 5 6

桶接口
auto Print(const std::string &msg, const std::unordered_multiset<int> &lst) {
  std::cout << msg << " : ";
  for (const auto &pa : lst) {
    std::cout << pa << "\t";
  }
  std::cout << "\n";
}

int main(int argc, char *argv[]) {
  QCoreApplication a(argc, argv);

  using uset_int = std::unordered_multiset<int>;
  uset_int s1;
  s1.emplace(20);
  s1.emplace(5);
  s1.emplace(66);
  s1.emplace(99);
  s1.emplace(66);
  s1.emplace(100);
  Print("s1", s1);

  //返回一个迭代器,指向指定的桶的开始
  for (int i = 0; i < s1.bucket_count(); ++i) {
    auto iter = s1.begin(i);
    if (iter == s1.end(i)) {
      std::cout << "s1.end(" << i << ")"
                << "\t";
    } else {
      for (; iter != s1.end(i); ++iter) {
        std::cout << "s1.begin(" << i << ") : " << *iter << "\t";
      }
    }
  }
  std::cout << "\n";

  //返回一个迭代器,指向指定的桶的开始
  for (int i = 0; i < s1.bucket_count(); ++i) {
    auto iter = s1.cbegin(i);
    if (iter == s1.cend(i)) {
      std::cout << "s1.cend(" << i << ")"
                << "\t";
    } else {
      for (; iter != s1.cend(i); ++iter) {
        std::cout << "s1.cbegin(" << i << ") : " << *iter << "\t";
      }
    }
  }
  std::cout << "\n";

  //返回桶数
  std::cout << "s1.bucket_count() : " << s1.bucket_count() << std::endl;
  //返回桶的最大数量
  std::cout << "s1.max_bucket_count() : " << s1.max_bucket_count() << std::endl;
  //返回在特定的桶中的元素数量
  for (int i = 0; i < s1.bucket_count(); ++i) {
    std::cout << "s1.bucket_size(" << i << ") : " << s1.bucket_size(i) << "\t";
  }
  std::cout << "\n";

  //返回带有特定键的桶,返回关键 key 的桶的下标。始终会在此桶中找到关键等于 key
  //的元素(若存在)。返回值仅对 bucket_count() 返回相同值的容器实例合法。
  //若 bucket_count() 为零则行为未定义
  std::cout << "s1.bucket(1) : " << s1.bucket(1) << std::endl;
  std::cout << "s1.bucket(100) : " << s1.bucket(100) << std::endl;

  // 键不存在,若存在会分配当前下标的桶
  std::cout << "s1.bucket(5000) : " << s1.bucket(5000) << std::endl;
  std::cout << "s1.bucket(2) : " << s1.bucket(2) << std::endl;
  return 0; // a.exec();
}

输出结果:
s1 : 100 20 5 66 66 99
s1.begin(0) : 5 s1.begin(1) : 100 s1.begin(1) : 20 s1.end(2) s1.end(3) s1.end(4) s1.end(5) s1.begin(6) : 99 s1.begin(7) : 66 s1.begin(7) : 66
s1.cbegin(0) : 5 s1.cbegin(1) : 100 s1.cbegin(1) : 20 s1.cend(2) s1.cend(3) s1.cend(4) s1.cend(5) s1.cbegin(6) : 99 s1.cbegin(7) : 66 s1.cbegin(7) : 66
s1.bucket_count() : 8
s1.max_bucket_count() : 1152921504606846975
s1.bucket_size(0) : 1 s1.bucket_size(1) : 2 s1.bucket_size(2) : 0 s1.bucket_size(3) : 0 s1.bucket_size(4) : 0 s1.bucket_size(5) : 0 s1.bucket_size(6) : 1 s1.bucket_size(7) : 2
s1.bucket(1) : 4
s1.bucket(100) : 1
s1.bucket(5000) : 4
s1.bucket(2) : 7

哈希策略
auto Print(const std::string &msg, const std::unordered_multiset<int> &lst) {
  std::cout << msg << " : ";
  for (const auto &pa : lst) {
    std::cout << pa << "\t";
  }
  std::cout << "\n";
}

int main(int argc, char *argv[]) {
  QCoreApplication a(argc, argv);

  using uset_int = std::unordered_multiset<int>;
  uset_int s1;
  s1.emplace(20);
  s1.emplace(5);
  s1.emplace(66);
  s1.emplace(99);
  s1.emplace(66);
  s1.emplace(100);
  Print("s1", s1);

  for (int i = 0; i < s1.bucket_count(); ++i) {
    auto iter = s1.begin(i);
    if (iter == s1.end(i)) {
      std::cout << "s1.end(" << i << ")"
                << "\t";
    } else {
      for (; iter != s1.end(i); ++iter) {
        std::cout << "s1.begin(" << i << ") : " << *iter << "\t";
      }
    }
  }
  std::cout << "\n";

  //返回每个桶的平均元素数量
  std::cout << "s1.load_factor() : " << s1.load_factor() << std::endl;

  for (int i = 0; i < s1.bucket_count(); ++i) {
  }

  //管理每个桶的平均元素数量的最大值
  //管理最大加载因子(每个桶的平均元素数)。若加载因子超出此阈值,则容器自动增加桶数
  //返回最大加载因子
  std::cout << "s1.max_load_factor() : " << s1.max_load_factor() << std::endl;
  //设置最大加载因子
  s1.max_load_factor(0.9);
  std::cout << "s1.max_load_factor() : " << s1.max_load_factor() << std::endl;

  //为至少为指定数量的桶预留存储空间并重新生成散列表
  s1.rehash(16);
  for (int i = 0; i < s1.bucket_count(); ++i) {
    auto iter = s1.begin(i);
    if (iter == s1.end(i)) {
      std::cout << "s1.end(" << i << ")"
                << "\t";
    } else {
      for (; iter != s1.end(i); ++iter) {
        std::cout << "s1.begin(" << i << ") : " << *iter << "\t";
      }
    }
  }
  std::cout << "\n";

  //为至少为指定数量的元素预留存储空间并重新生成哈希表
  s1.reserve(20);
  for (int i = 0; i < s1.bucket_count(); ++i) {
    auto iter = s1.begin(i);
    if (iter == s1.end(i)) {
      std::cout << "s1.end(" << i << ")"
                << "\t";
    } else {
      for (; iter != s1.end(i); ++iter) {
        std::cout << "s1.begin(" << i << ") : " << *iter << "\t";
      }
    }
  }
  std::cout << "\n";
  return 0; // a.exec();
}

输出结果:
s1 : 100 20 5 66 66 99
s1.begin(0) : 5 s1.begin(1) : 100 s1.begin(1) : 20 s1.end(2) s1.end(3) s1.end(4) s1.end(5) s1.begin(6) : 99 s1.begin(7) : 66 s1.begin(7) : 66
s1.load_factor() : 0.75
s1.max_load_factor() : 1
s1.max_load_factor() : 0.9
s1.begin(0) : 5 s1.begin(1) : 20 s1.begin(1) : 100 s1.end(2) s1.end(3) s1.end(4) s1.end(5) s1.begin(6) : 99 s1.begin(7) : 66 s1.begin(7) : 66 s1.end(8) s1.end(9) s1.end(10) s1.end(11) s1.end(12) s1.end(13) s1.end(14) s1.end(15)
s1.begin(0) : 5 s1.begin(1) : 20 s1.end(2) s1.end(3) s1.end(4) s1.end(5) s1.begin(6) : 99 s1.end(7) s1.end(8) s1.end(9) s1.end(10) s1.end(11) s1.end(12) s1.end(13) s1.end(14) s1.end(15) s1.end(16) s1.begin(17) : 100 s1.end(18) s1.end(19) s1.end(20) s1.end(21) s1.end(22) s1.begin(23) : 66 s1.begin(23) : 66 s1.end(24) s1.end(25) s1.end(26) s1.end(27) s1.end(28) s1.end(29) s1.end(30) s1.end(31)

起始

<think>我们正在讨论C++中的std::unordered_set容器。根据之前的对话,用户已经知道std::set不能存储重复元素,并且了解了std::multiset作为替代方案。现在用户想了解std::unordered_set的使用方法。参考引用资料,特别是引用[1]、[3]、[4]中提供了std::unordered_set的相关信息。目标:提供std::unordered_set的使用教程,包括基本操作、特性和示例代码。结构:1.介绍std::unordered_set:无序、唯一元素、基于哈希表实现。2.基本操作:包含头文件、声明、插入、删除、查找、遍历、大小等。3.自定义哈希函数和相等比较函数(如果需要存储自定义类型)。4.与std::set的简单对比(根据之前问题,用户可能想知道)。5.示例代码演示。6.注意事项(如哈希冲突的影响,性能等)。注意:用户要求使用教程,因此以实用为主。另外,根据指令,需要生成相关问题。</think> ### C++ `std::unordered_set` 使用教程 `std::unordered_set` 是 C++ 标准库中的无序关联容器,基于哈希表实现。它存储**唯一元素**(不允许重复),支持快速插入、删除和查找操作,平均时间复杂度为 $O(1)$。与 `std::set` 不同,它不保证元素顺序[^2][^4]。以下是完整使用指南: --- #### 1. 基本操作 **头文件与声明** ```cpp #include <unordered_set> // 声明存储整数的 unordered_set std::unordered_set<int> numSet; ``` **插入元素** ```cpp numSet.insert(10); // 单元素插入 numSet.insert({20, 30, 40}); // 初始化列表插入 numSet.emplace(50); // 直接构造元素(高效) ``` **删除元素** ```cpp numSet.erase(30); // 删除值为30的元素 auto it = numSet.find(20); if (it != numSet.end()) { // 安全删除(避免未定义行为) numSet.erase(it); } numSet.clear(); // 清空所有元素 ``` **查找与判断** ```cpp // 查找元素(返回迭代器) auto pos = numSet.find(40); if (pos != numSet.end()) { std::cout << "Found: " << *pos << std::endl; } // 检查元素存在性 if (numSet.contains(10)) { // C++20 语法 std::cout << "10 exists\n"; } // 统计元素出现次数(始终为0或1,因元素唯一) std::cout << "Count of 50: " << numSet.count(50) << std::endl; ``` **遍历与容量** ```cpp // 遍历(顺序不固定) for (const auto& num : numSet) { std::cout << num << " "; } // 容量信息 std::cout << "\nSize: " << numSet.size() << "\nBucket count: " << numSet.bucket_count() << "\nLoad factor: " << numSet.load_factor() << std::endl; ``` --- #### 2. 自定义类型处理 若需存储自定义类型(如结构体),必须提供**哈希函数**和**相等比较函数**: ```cpp struct Point { int x, y; bool operator==(const Point& p) const { // 相等比较 return x == p.x && y == p.y; } }; // 自定义哈希函数(需特化 std::hash) struct PointHash { size_t operator()(const Point& p) const { return std::hash<int>()(p.x) ^ (std::hash<int>()(p.y) << 1); } }; // 声明存储 Point 的 unordered_set std::unordered_set<Point, PointHash> pointSet; pointSet.insert({1, 2}); pointSet.insert({3, 4}); ``` > **注意**:哈希函数设计需满足: > - 相同输入产生相同哈希值 > - 不同输入尽量产生不同哈希值(减少冲突) > - 可通过 `bucket()` 方法观察元素存储位置,例如: > ```cpp > Point p{1, 2}; > std::cout << "Bucket for (1,2): " << pointSet.bucket(p); > ``` --- #### 3. 性能优化 - **调整桶数量** 预分配桶以减少重建哈希表的开销: ```cpp std::unordered_set<std::string> wordSet; wordSet.reserve(1000); // 预分配1000个桶 ``` - **控制负载因子** 负载因子 = 元素数 / 桶数。建议保持 ≤0.75: ```cpp wordSet.max_load_factor(0.75); // 设置最大负载因子 wordSet.rehash(2000); // 重建哈希表(桶数≥2000) ``` --- #### 4. 示例场景 ```cpp #include <iostream> #include <unordered_set> #include <string> int main() { std::unordered_set<std::string> cities = {"Tokyo", "Delhi", "New York"}; // 插入新城市(重复值被忽略) cities.insert("Tokyo"); cities.emplace("Paris"); // 检查存在性 std::cout << "Contains Paris? " << cities.contains("Paris") << "\n"; // 输出:1 // 遍历输出(无序) for (const auto& city : cities) { std::cout << city << ", "; // 可能输出:Paris, New York, Delhi, Tokyo } return 0; } ``` **输出示例**(顺序可能不同): ``` Contains Paris? 1 Paris, New York, Delhi, Tokyo, ``` --- #### 5. 与 `std::set` 对比 | 特性 | `std::unordered_set` | `std::set` | |---------------------|----------------------------|-------------------------| | **底层结构** | 哈希表 | 红黑树(平衡二叉搜索树)| | **元素顺序** | 无序 | 有序(按比较函数排序) | | **平均时间复杂度** | $O(1)$(插入/查找/删除) | $O(\log n)$ | | **自定义类型要求** | 需哈希函数 + `operator==` | 需 `operator<` 或比较函数| | **内存占用** | 通常更低(无指针开销) | 更高(存储子节点指针) | > **何时选择**: > - 需要快速查找且不关心顺序 → `std::unordered_set`[^1] > - 需要有序遍历或范围查询 → `std::set`[^2] --- #### 注意事项 1. 迭代器稳定性: 插入操作可能导致迭代器失效(哈希表重建时)。 2. 哈希冲突: 设计低碰撞哈希函数以保持 $O(1)$ 性能[^4]。 3. C++11 支持: 最低需支持 C++11 标准(早期版本如 C++03 不可用)[^3]。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值