Boolan C++ 笔记 九

算法

STL 算法总览

(以下 质变 栏意指mutating,意思是 会改变其操作对象之内容)

算法名称算法用途质变?所在文件
accumulate元素累计<stl_numeric.h>
adjacent_difference相邻元素的差额是 if in-place<stl_numeric.h>
adjacent_find查找相邻而重复(或符合某条件)的元素<stl_algo.h>
binary_search二分查找<stl_algo.h>
Copy复制是 if in-place<stl_algobase.h>
Copy_backward逆向复制是 if in-place<stl_algobase.h>
count计数<stl_algo.h>
count_if在特定条件下计数<stl_algo.h>
equal判断两个区间相等与否<stl_algobase.h>
equal_range试图在有序区间中寻找某值(返回一个上下限区间)<stl_algo.h>
fill该填元素值<stl_algobase.h>
fill_n该填元素值,n次<stl_algobase.h>
find循序查找
find_if循序查找符合特定条件者
find_end查找某个子序列的最后一次出现点<stl_algo.h>
find_first_of查找某些元素的首次出现点<stl_algo.h>
for_each对区间内的每一个元素施行某操作<stl_algo.h>
generate以特定操作之运算结果填充特定区间内的元素<stl_algo.h>
generate_n以特定操作之运算结果填充n个元素内容<stl_algo.h>
includes是否涵盖于某序列之中<stl_algo.h>
inner_product内积<stl_algo.h>
inplace_merge合并并就地替换(覆写上去)<stl_algo.h>
lexicographical_compare以字典顺序进行比较<stl_numeric.h>
lower_bound“将置顶元素插入区间之内而不影响区间之原本排序”的最低位置<stl_algo.h>
max最大值<stl_algobase.h>
max_element最大值所在位置<stl_algobase.h>
merge合并两个序列是 if-inplace<stl_algo.h>
min最小值<stl_algobase.h>
min_element最小值所在位置<stl_algo.h>
mismatch找出不匹配点<stl_algobase.h>
next_permutation获得下一个排列组合
nth_element重新安排序列中第n个元素的左右两端<stl_algo.h>
partial_sort局部排序<stl_algo.h>
partial_sort_copy局部排序并复制到它处是 if in-place<stl_algo.h>
partial_sum局部求和是 if in-place<stl_numberic.h>
partition分割<stl_algo.h>
prev_permutation获得前一个排列组合<stl_algo.h>
random_shuffle随机重排元素<stl_algo.h>
remove删除某类元素(但不删除)<stl_algo.h>
remove_copy删除某类元素并将结果复制到另一个容器<stl_algo.h>
remove_if有条件地删除某类元素<stl_algo.h>
remove_copy_if有条件地删除某类元素并将结果复制到另一个容器<stl_algo.h>
replace替换某类元素<stl_algo.h>
replace_copy替换某类元素,并将结果复制到另一个容器<stl_algo.h>
replace_if有条件地替换<stl_algo.h>
replace_copy_if有条件地替换,并将结果复制到另一个容器
reverse反转元素次序<stl_algo.h>
reverse_copy反转元素次序并将结果复制到另一个容器<stl_algo.h>
rotate旋转<stl_algo.h>
rotate_copy旋转并将结果复制到另一个容器<stl_algo.h>
search查找某个子序列<stl_algo.h>
search_n查找”连续发生n次”的子序列<stl_algo.h>
set_difference差集是 if in-place<stl_algo.h>
set_intersection交集是 if in-place<stl_algo.h>
set_symmetric_difference对称差集是 if in-place<stl_algo.h>
set_union并集是 if in-place<stl_algo.h>
sort排序<stl_algo.h>
stable_partition分割并保持元素的相对次序<stl_algo.h>
stable_sort排序并保持等值元素的相对次序<stl_algo.h>
swap交换(对调)<stl_algobase.h>
swap_ranges交换(指定区间)<stl_algo.h>
transform以两个序列为基础,交互作用产生第三个序列<stl_algo.h>
unique将重复的元素折叠缩编,使成唯一<stl_algo.h>
unique_copy将重复元素折叠缩编,使成唯一,并复制到他出是 if in-place<stl_algo.h>
upper_bound“将指定元素插入区间之内而不影响区间之原本排序”的最高位置<stl_algo.h>
make_heap制造一个heap<stl_heap.h>
pop_heap从heap取出一个元素<stl_heap.h>
push_heap将一个元素推进heap内<stl_heap.h>
sort_heap对heap排序<stl_heap.h>

STL算法的一般形式

所有泛型算法的前两参数都是一堆迭代器(iterators),通常称为first和last,用以标示算法的操作区间.STL习惯采用前闭后开区间(或称左涵盖区间)表示法,写成[first,last),表示区间涵盖first至last(不含last)之间的所有元素.当first==last时马上述所表现的便是一个空区间
这个[first,last)区间的必要条件是,必须能够经由increment(累加)操作符的反复运用,从first到达last.编译器本身无法强求这一点.如果这个条件不成立,会导致未可预期的结果.
根据行进特性,迭代器可分为五类.每一个STL算法的声明,都表现出它所需要的最低程度的迭代器类型.例如find()需要一个InputIterator,这是它的最低要求,但它可以接受更高类型的迭代器,如ForwardIterator,BidirectionalIterator或RandomAccessIterator,因为他们相对于InputIterator是is-a关系.但如果你交给find()一个OutputIterator,会导致错误.
将无效的迭代器传给某个算法,虽然是一种错误,却不保证能够在编译湿气就被捕捉出来,因为所谓”迭代器类型”并不是真实的型别,它们只是function template的一种型别参数(type parameters).
质变算法通常提供两个版本:一个是in-place版,就地改变其操作对象;另一个是copy版,将操作对象的内容复制一份副本,然后在副本上进行修改并返回该副本.

测试Iterator类型

#include <iostream>     // std::cout
#include <iterator>     // std::iterator_traits
#include <typeinfo>     // typeid
namespace jj33
{
void _display_category(random_access_iterator_tag)
{   cout << "random_access_iterator" << endl; }
void _display_category(bidirectional_iterator_tag)
{   cout << "bidirectional_iterator" << endl; }
void _display_category(forward_iterator_tag)
{   cout << "forward_iterator" << endl;  }
void _display_category(output_iterator_tag)
{   cout << "output_iterator" << endl;   }
void _display_category(input_iterator_tag)
{   cout << "input_iterator" << endl;    }

template<typename I>
void display_category(I itr)
{
   typename iterator_traits<I>::iterator_category cagy;
   _display_category(cagy);

   cout << "typeid(itr).name()= " << typeid(itr).name() << endl << endl;   
       //The output depends on library implementation.
       //The particular representation pointed by the  
       //returned valueis implementation-defined, 
       //and may or may not be different for different types.   
}

void test_iterator_category()
{
    cout << "\ntest_iterator_category().......... \n";

    display_category(array<int,10>::iterator());
    display_category(vector<int>::iterator());
    display_category(list<int>::iterator());    
    display_category(forward_list<int>::iterator());  
    display_category(deque<int>::iterator());

    display_category(set<int>::iterator());
    display_category(map<int,int>::iterator());
    display_category(multiset<int>::iterator());
    display_category(multimap<int,int>::iterator());
    display_category(unordered_set<int>::iterator());
    display_category(unordered_map<int,int>::iterator());
    display_category(unordered_multiset<int>::iterator());
    display_category(unordered_multimap<int,int>::iterator());    

    display_category(istream_iterator<int>());
    display_category(ostream_iterator<int>(cout,""));
}                                                            
}

迭代器与算法

Iterator提供了一种方法,将容器和算法分开独立设计,再用Iterator连接到一起。它最重要的编程工作是对* 和 - 的重载.

由Iterator 的相应类别(associated type) 可以引入了利用function templeate 的参数推导机制,这是traits的prerequisite. 在template类中如果typedef T value_type 便可用这个property作为要生成的变量或函数返回值的类型。但这种方法并不适用于非class type, 非class type没有内嵌类型。“并不是所有迭代器都是class type”。traits技法把所有的associated type封装到一个structure里,通过partial specialization 指定相应偏特化类型的associated type。迭代器有五个associated types,分别是 value type, difference type, reference type, pointer type, 和 iterator_category.

iterator_category 是讨论起来相对最复杂的。迭代器分为5类,input iterator, output iterator, forward iterator, bedirectional iterator, and random access iterator. 设计算法时,我们尽量对某种迭代器提供一个明确的定义,并针对更强化的某种迭代器提供另一种定义,对每个函数确保传入最准确的迭代器。这里的例子是,对每种迭代器类型都有自己structure,并对每个structure定义相应的重载函数,调用函数是传入一个临时实例以出发重载机制,同时,以此可以消除只用来判断类型随后传递调用的函数。

std::iterator是STL规范的保证,如果每个新设计的迭代器都继承自它,就可以保证符合STL规范。总结:设计适当的相应性别(associated type) 是迭代器的责任。设计适当的迭代器是容器的责任。唯容器本身才知道设计出怎样的迭代器来遍历自己,并执行迭代器该有的各种行为。

SGI STL内部使用了__type_traits来萃取type的型别特性,这里关注的有trivial_default_constructor, trivial_copy_constructor, trivial_copy_constructor, trivial_assignment_constructor, trivial_destructor, and POD_type. 对trivial进行最有效率的措施,也就是说直接的内存操作。有显著效率提升。

算法代码剖析 (以下代码来自于www.cplusplus.com)

accumulate

//版本1
template<class InputIterator,class T>
T accumulate(InputIterator first,InputIterator last,T init){
for( ;first!=last;++first)
   init = init + *first;//将每个元素值累加到init身上
return init;
}

//版本2
template<class InputIterator,class T,class BinaryOperation>
T accumulate(InputIterator first,InputIterator last,T init,BinaryOperation binary_op){
for( ;first!=last;++first)
init = binary_op(init,*first);//对每一个元素执行二元操作
return init;
}

for_each

template<class InputIterator, class Function>
  Function for_each(InputIterator first, InputIterator last, Function fn)
{
  while (first!=last) {
    fn (*first);//遍历每次调用fn
    ++first;
  }
  return fn;      // or, since C++11: return move(fn);
}

replace,replace_if,replace_copy

XXX_if :加条件
XXX_copy:复制到别的容器

template <class ForwardIterator, class T>
  void replace (ForwardIterator first, ForwardIterator last,
                const T& old_value, const T& new_value)
{
  while (first!=last) {
    if (*first == old_value) *first=new_value;
    ++first;
  }
}

template < class ForwardIterator, class UnaryPredicate, class T >
  void replace_if (ForwardIterator first, ForwardIterator last,
                   UnaryPredicate pred, const T& new_value)
{
  while (first!=last) {
    if (pred(*first)) *first=new_value;//所有pred()为true都以new_value取代
    ++first;
  }
}

template <class InputIterator, class OutputIterator, class T>
  OutputIterator replace_copy (InputIterator first, InputIterator last,
                               OutputIterator result, const T& old_value, const T& new_value)
{
  while (first!=last) {
    *result = (*first==old_value)? new_value: *first;//没有对容器进行写的操作,只写了result,从代码上可以看出是如果和old_value相等,则把一个new_value赋给result,也就是放入新区间了.
    ++first; ++result;
  }
  return result;
}

count,count_if

template <class InputIterator, class T>
  typename iterator_traits<InputIterator>::difference_type
    count (InputIterator first, InputIterator last, const T& val)
{
  typename iterator_traits<InputIterator>::difference_type ret = 0;
  //遍历 如果元素值相等,计数器加1;
  while (first!=last) {
    if (*first == val) ++ret;
    ++first;
  }
  return ret;
}

template <class InputIterator, class UnaryPredicate>
  typename iterator_traits<InputIterator>::difference_type
    count_if (InputIterator first, InputIterator last, UnaryPredicate pred)
{
  typename iterator_traits<InputIterator>::difference_type ret = 0;
  while (first!=last) {
    if (pred(*first)) ++ret;//这里改了条件,如果pred()为true,计数器加1,所以可以通过pred这个参数改条件
    ++first;
  }
  return ret;
}
/*
容器中不带成员函数的count():
array,vector,list,forward_list,deque,
容器中带有成员函数count():
set/multisset,map/multimap,unordered_set/unordered_multiset,unordered_map/unordered_multimap
*/

find,find_if

template<class InputIterator, class T>
  InputIterator find (InputIterator first, InputIterator last, const T& val)
{
  while (first!=last) {
    if (*first==val) return first;
    ++first;
  }
  return last;
}

template<class InputIterator, class UnaryPredicate>
  InputIterator find_if (InputIterator first, InputIterator last, UnaryPredicate pred)
{
  while (first!=last) {
    if (pred(*first)) return first;
    ++first;
  }
  return last;
}
/*
容器中不带成员函数的find():
array,vector,list,forward_list,deque,
容器中带有成员函数find():
set/multisset,map/multimap,unordered_set/unordered_multiset,unordered_map/unordered_multimap
*/

sort

// sort algorithm example
#include <iostream>     // std::cout
#include <algorithm>    // std::sort
#include <vector>       // std::vector

bool myfunction (int i,int j) { return (i<j); }

struct myclass {
  bool operator() (int i,int j) { return (i<j);}
} myobject;

int main () {
  int myints[] = {32,71,12,45,26,80,53,33};
  std::vector<int> myvector (myints, myints+8);               // 32 71 12 45 26 80 53 33

  // using default comparison (operator <):
  std::sort (myvector.begin(), myvector.begin()+4);           //(12 32 45 71)26 80 53 33

  // using function as comp
  std::sort (myvector.begin()+4, myvector.end(), myfunction); // 12 32 45 71(26 33 53 80)

  // using object as comp
  std::sort (myvector.begin(), myvector.end(), myobject);     //(12 26 32 33 45 53 71 80)

  // print out content:
  std::cout << "myvector contains:";
  for (std::vector<int>::iterator it=myvector.begin(); it!=myvector.end(); ++it)
    std::cout << ' ' << *it;
  std::cout << '\n';

  return 0;
}

//Output:
//myvector contains: 12 26 32 33 45 53 71 80

//只有list,forward_list带成员函数sort()
关于reverse iterator,rbegin(),rend

reverse_iterator
rbegin(){
return reverse_iterator(end());
}
reverse_iterator
rend(){
return reverse__]iterator(begin());
}
//这是iterator adapter

binary_search

binary_search 一定要先排序

template <class ForwardIterator, class T>
  bool binary_search (ForwardIterator first, ForwardIterator last, const T& val)
{
  first = std::lower_bound(first,last,val);//一说应该把!(val<*first)放在这一行前面判断效率会更高,可以省去不必要的lower_bound调用效率更高.
  return (first!=last && !(val<*first));
}


//二分在这里
template <class ForwardIterator, class T>
  ForwardIterator lower_bound (ForwardIterator first, ForwardIterator last, const T& val)
{
  ForwardIterator it;
  iterator_traits<ForwardIterator>::difference_type count, step;
  count = distance(first,last);
  while (count>0)
  {
    it = first; step=count/2; advance (it,step);
    if (*it<val) {                 // or: if (comp(*it,val)), for version (2)
      first=++it;
      count-=step+1;
    }
    else count=step;
  }
  return first;
}

仿函数

存在的意义,要通过它把相应的动作传给算法

算术类(Arithmetic)

template<class T>
struct plus:public binary_function<T,T,T>{
T operator() const T& x,const T& y) const{return x+y;}
};

template<class T>
struct minus:public binary_function<T,T,T>{
T operator() const T& x,const T& y) const{return x-y;}
};

逻辑运算类(Logical)

template<class T>
struct logical_and:public binary_function<T,T,bool>{
bool operator() const T& x,const T& y) const{return x&&y;}
};

相对关系类(Relational)

template<class T>
struct equal_to:public binary_function<T,T,bool>{
bool operator() const T& x,const T& y) const{return x==y;}
};

template<class T>
struct less:public binary_function<T,T,bool>{
bool operator() const T& x,const T& y) const{return x<y;}
};

仿函数functors的可是配条件

//STL规定每个Adaptable Function都应挑选适当者继承之.继承了typedef.函数适配器可能需要用到.要和STL体系融合就要满足该条件

template<class Arg,class Result>
struct unary_function{//一个操作数
typedef Arg argument_type;
typedef Result result_type;
};

template<class Arg1,class Arg2,class Result>
struct binary_function{//两个操作数
typedef Arg1 first_argument_type;
typedef Arg2 second_argument_type;
typedef Result result_type;
};

存在多种 Adapters

配接器(adapters)在STL组件的灵活组合运用功能上,扮演着轴承,转换器的角色.Adapeter这个概念,事实上是一种设计模式:将一个class的接口转换为另一个class接口,使原本因接口不兼容而不能合作的classes,可以一起运作.

分类

应用于容器 container adapters

STL提供两个容器queue和stack,其实都值不够是一种adapter.他们修饰deque的接口而成就出另一种容器的风貌.前面笔记有讲.

应用于迭代器 iterator adapters

STL提供了许多应用于迭代器身上的adapters,包括insert iterators,reverse iterators,iostream iterators.C++ standard规定它们的接口可以藉由获得,SGI STL则将它们实际定义于

Insert Iterators

所谓 insert iterators,可以将一般迭代器的赋值操作转变为插入操作.这样的迭代器包括专司尾端插入操作的back_insert_iterator,专司头端插入操作的front_insert_iterator,以及可以从任意位置执行插入操作的insert_iterator.由于这三个iterator adapters的使用接口不是十分只管,给一般用户带来困扰,因此STL更提供三个相应函数如下表所示,提升使用时的便利性

辅助函数(helper function)实际产生的对象
back_inserter(Container& x);back_insert_iterator(x);
front_inserter(Container& x);front_insert_iterator(x);
inserter(Container& x,Iterator i);insert_iterator(x,Container::iterator(i));
Reverse Iterators

所谓reverse iterators,可以将一般迭代器的行进方向逆转,使原本应该前进的operator++变成后退操作,使原本应该后退的operator–变成了前进操作.这种性质用在”从尾端开始进行”的算法上,有很大的方便性

IOStream iterator

所谓iostream iterators,可以将迭代器绑定到某个iostream对象上.绑定到istream对象(例如std::cin)身上的,称为isstream_iterator,拥有输入功能;绑定到ostream对象(如std::cout)身上的,称为ostream_iterator,拥有输出功能.这种迭代器运用于屏幕输出,非常方便.以它为蓝图,稍加修改,便可适用于任何输出或输入装置上.

下面是一个实例,集上述三种iterator adapters之运用大成:

#include <iostream>
#include <iterator> //for iterator adapters
#include <deque>
#include <algorithm>//for copy()
using namespace std;
int main () {
   //将outite绑定到cout.每次对outite指派一个元素,就后接一个" ".
    ostream_iterator<int> outite(cout," ");
    int ia[] ={0,1,2,3,4,5};
    deque<int> id{ia,ia+6};
    //将所有元素拷贝到outite(那么也就是拷贝到cout)
    copy(id.begin (),id.end (),outite);//输出 0 1 2 3 4 5
    cout<<endl;

    //将ia[]的部分元素拷贝到id内.使用front_insert_iterator
    //注意front_insert_itergtor会将assign操作改为push_front操作
    //vector不支持push_front(),这就是本例不以vector为示范对象的原因.
    copy(ia+1,ia+2,front_inserter(id));
    copy(id.begin (),id.end (),outite);// 1 0 1 2 3 4 5
    cout<<endl;

    //将ia[]的部分元素拷贝到id内,使用back_insert_iterator
    copy (ia+3,ia+4,back_inserter (id));
    copy(id.begin (),id.end (),outite);// 1 0 1 2 3 4 5 3

    //搜寻元素5所在位置
    deque<int>::iterator ite = find(id.begin(),id.end(),5);
    //将ia[]的部分元素拷贝到id内,使用isnert_iterator
    copy (ia+0,ia+3,inserter (id,ite));
    copy(id.begin(),id.end(),outite);//1 0 1 2 3 4 0 1 2 5 3
    cout<<endl;
    //将所有元素你想拷贝到outtite
    //rbegin()和rend()与reverse_iterator有关
    copy (id.rbegin (),id.rend (),outite);//3 5 2 1 0 4 3 2 1 0 1
    cout<<endl;

    //以下,将inite绑定到cin.将元素拷贝到inite,知道eos出现
    istream_iterator<int> inite(cin),eos;//eos:end-of-stream
    copy (inite,eos,insert_iterator(id,id.begin ()));
    //由于很难再键盘上直接输入end-of-stream符号,而且不同系统的eof符号也不尽相同,因此
    //为了让上一行顺利执行.请单独取出此段落为一个独立程序,并准备一个文件例如int.dat,内置
    //32 26 99(自由格式),并在console之下用piping方式执行该程序,如下:
    //c:\>type int.dat | thisprog
    //意思是将type int.dat的结果作为thisprog的输入
    copy(id.begin (),id.end (),outite);//32 26 99 1 0 1 2 3 4 0 1 2 5 3

}
应用于仿函数 functor adapters
//bind2nd &binder2nd
template <class Operation, class T>
  binder2nd<Operation> bind2nd (const Operation& op, const T& x)
{
  return binder2nd<Operation>(op, typename Operation::second_argument_type(x));//typename Operation::second_argument_type(x)这里进行检测参数对不对,把op和typename Operation::second_argument_type(x)传给binder2nd
}

template <class Operation> class binder2nd
  : public unary_function <typename Operation::first_argument_type,
                           typename Operation::result_type>
{
protected:
  Operation op;//内部成员,分别用以记录算式和第二实参
  typename Operation::second_argument_type value;
public:
  binder2nd ( const Operation& x,
              const typename Operation::second_argument_type& y) : op (x), value(y) {}
  typename Operation::result_type
    operator() (const typename Operation::first_argument_type& x) const
    { return op(x,value); }//实际呼叫算式并取value为第二实参. 在这里问询了result_type
};

// bind2nd example
#include <iostream>
#include <functional>
#include <algorithm>
using namespace std;

int main () {
  int numbers[] = {10,-20,-30,40,-50};
  int cx;
  cx = count_if ( numbers, numbers+5, bind2nd(less<int>(),0) );
  cout << "There are " << cx << " negative elements.\n";
  return 0;
}
新型适配器 bind

…\include\c++\backward\backward_warning.h
A list of valid replacements is as follows:

UseInstead of
<sstresm>,basic_stringbuf<sstresm>,strstreambuf
<sstream>,basic_istringstream<strstream>,istrstream
<sstresm>,basic_ostringstream<sstresm>,ostrtream
<sstresm>,basic_stringstream<sstresm>,strstream
<unordered_set>,unordered_set<ext/hash_set>,hash_set
<unordered_set>,unordered_multiset<ext/hash_set>,hash_multiset
<unordered_map>,unordered_map<ext/hash_map>,hash_map
<unordered_map>,unordered_multimap<ext/hash_set>,hash_multimap
<function>,bind<functional>,binder1st
<function>,bind<functional>,binder2nd
<function>,bind<functional>,bind1st
<function>,bind<functional>,bind2nd
`,unique_ptr<memory>,auto_ptr


// bind example
#include <iostream>     // std::cout
#include <functional>   // std::bind

// a function: (also works with function object: std::divides<double> my_divide;)
double my_divide (double x, double y) {return x/y;}

struct MyPair {
  double a,b;
  double multiply() {return a*b;}
};

int main () {
  using namespace std::placeholders;    // adds visibility of _1, _2, _3,...占位符_1 _2 _3

  // binding functions:
  auto fn_five = std::bind (my_divide,10,2);               // returns 10/2
  std::cout << fn_five() << '\n';                          // 5

  auto fn_half = std::bind (my_divide,_1,2);               // returns x/2
  std::cout << fn_half(10) << '\n';                        // 5

  auto fn_invert = std::bind (my_divide,_2,_1);            // returns y/x
  std::cout << fn_invert(10,2) << '\n';                    // 0.2

  auto fn_rounding = std::bind<int> (my_divide,_1,_2);     // returns int(x/y)
  std::cout << fn_rounding(10,3) << '\n';                  // 3

  MyPair ten_two {10,2};

  // binding members:
  auto bound_member_fn = std::bind (&MyPair::multiply,_1); // returns x.multiply()
  std::cout << bound_member_fn(ten_two) << '\n';           // 20

  auto bound_member_data = std::bind (&MyPair::a,ten_two); // returns ten_two.a
  std::cout << bound_member_data() << '\n';                // 10

  return 0;
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值