Boost开发指南

第二章:计时器timer库  和日期时间库 date_time

第三章:资源管理 ——智能指针(原始指针的代理类)smart_ptr库

基本思想:用对象管理资源  RALL机制

C++标准库中的auto_ptr:复制使用权转移(不支持引用计数) 不能用作STL容器元素

Boost的smart_ptr库:(6个指针代理类)

1、scoped_ptr:与auto_ptr类似,不能复制或者赋值操作(使用权不会发生转移),不能用作STL容器元素。

2、scoped_array:与scoped_ptr类似,包装了new[],用于动态数组,(尽量用vector代替动态数组)

3、shared_ptr:基于引用计数,可自由拷贝、赋值、可安全共享,可安全作为STL容器元素,支持基本线程安全。和weak_ptr一起,已收入TR1(新标准库)中。

4、shared_array:

5、weak_ptr:

6、intrusive_ptr:

 

7、工厂模式:工厂函数:产生shared_ptr的自由工厂函数:

Template<T,class….>

shared_ptr<T> make_shared(Args&& …);

 

8、shared_ptr常用于实现工厂模式:工厂函数(或对象)在内部用new在堆上创建目标对象,再返回其指针(返回原始指针是危险的),故一般返回shared_ptr< >.

 

9、shared_ptr用于定制删除器:shared_ptr的一个构造函数 shared_ptr(Y* P,D d);p为被管理的原始指针,d为定制的删除器(一个函数 或者函数对象,d(p):用于替代Delete的作用,可理解为d的默认值就是delete),这样shared_ptr就可用来管理任何资源,而不仅仅是内存资源,如:shared_ptr<FILE> fp(fopen(“./1.txt”,”r”),fclose); 可以自动管理文件资源,在shared_ptr离开其作用域时,自动调用删除器关闭文件资源。

10、shared_ptr<void>:能够存储void*型指针,void*型指针可以指向任意类型,shared_ptr<void>就像一个泛型指针容器,可以接纳任意类型。但是void*也丧失了原来的类型信息。

11、shared_ptr的功能已经远远超过智能指针的范围,可用于:包装成员函数、延时释放、、、、

 

Shared_array:

1、 类似于shared_ptr,包装了new[],delete[]操作,为在堆上动态分配数组提供了一个代理,使用引用计数机制。 接口与功能与shared_ptr类似。

2、如scoped_array,重载了operator[],但不提供数组的索引范围查找,使用时要注意索引范围。

3、Shared_array能力有限,可选择使用shared_ptr<std::vector>或者std::vector<shared_ptr>来代替。付出代价几乎可以忽略,而具有更好的安全性和灵活性。

 

Weak_ptr:

1、weak_ptr是为配合shared_ptr工作的一种“智能指针”,是shared_ptr的工作助手,而不具有像普通指针的行为,没有重载operator*和operator->.

2、作为一个静静的观察者,weak_ptr没有共享原始指针,不能操作资源(但其成员函数lock()可从被观测的shared_ptr获得一个shared_ptr对象,从而操作资源),它的构造和析构都不会引起引用计数的改变。

3、成员函数expired(),用于判断引用计数是否为0,为0时表示被观测的资源已失效。

4、weak_ptr的一个重要用途:获取this指针的shared_ptr,使对象能够产生sahred_pt管理自己:用weak_ptr的lock()在需要的时候返回一个this指针的代理shared_ptr共外界使用。(这要被管理的对象继承于boost库中定义的一个助手类即可)

 

Intrusive_ptr: 是一个侵入式的引用计数指针。由于shared_ptr的功能已经足够强大,故几乎不用intrusive_ptr.

 

 

3.8pool库概述:

内存池预先分配一块大的内存空间,然后在其中使用某种算法实现高效快速的自定制内存分配。Boost.pool库不仅能管理大量对象,还能用作STL的内存分配器。分为四个组成部分

1、简单的pool: 最简单的内存池类。注意:pool<>只能作为普通数据类型的内存池,不能用于复杂的自定义对象或类,因为它只分配内存,不调用构造函数。

2、object_pool:相对于pool<>,object_pool是用于实例对象的内存池。

3、singleton_pool:  与pool接口完全一致,可分配简单数据类型的内存指针,但是一个单件。

4、pool_alloc可用于STL容器模板参数的内存分配器。   除非有特别的要求,我们应该总是使用STL实现自带的内存分配器。

 

第四章:实用工具     (boost中非常有用的类或者宏

noncopyable: 允许程序轻松的实现一个禁止复制的类。

1、当不明确需要类进行复制构造或赋值操作时,应避免编译器生成的复制构造函数和赋值操作符。一般可把定义为显示调用或者private实现。

2、Noncopyable可避免大量的这样定义操作。为实现不可拷贝的类,只需要从boost::noncopyable派生即可。(直接默认私有派生即可,实现的是HAS-A关系)。 这能明确无误的表达设计者的意图,对用户也更加友好。

3、注意在使用时应该总是使用boost::noncopyabled的名字空间限定域形式,而不是使用using语句,避免在头文件打开boost名字空间。

 

 

Typeof:  支持C++内置类型和STL的大多数类型

1、Typeof库使用宏模拟C++0X新增的typeof和auto关键字,可减轻书写繁琐的变量类型声明工作,简化代码。(typeof和auto关键字用于自动获取表达式或表达式值得类型)

Typeof(2) a=3;//声明a为int    而auto更简洁: auto a=3;  //    在编译器自动推到类型

2、C++是一种静态强类型语言,所有变量在使用之前都需要声明其类型,这使得C++运行速度快,代码规范,但有时这也带来麻烦,如:声明

Std::map<std::string,std::string>::iteratorp=….;//这是很繁琐的,尽管用using打开名字空间可以适当简化,但还是不能根本解决。

简化:auto p=A.begin();//A为Std::map<std::string,std::string>的一个对象即可

3、使用:头文件<boost/ytpeof.hpp>中定义了两个宏:BOOST_TYPEOF   BOOST_AUTO,完全模拟C++0X中的typeof  auto.

如:BOOST_TYPEOF(2.0*3)X=2.0*3;//推到类型为double  也可用于STL容器的相关类型等等

BOOST_AUTOX=2.0*3;//推到类型为double  

4、typeof库不能支持所有类型,如果需要用于自定义的类型,需要向typeof库注册自定义类

Optional:

Optional库(模板类)使用“容器”语义,包装了“可能产生无效值”的对象,实现了为初始化的概念。可理解为一个只包含一个元素的容器,其用法可类似于指针。

1、背景:函数不是总返回有效的值(正确运行也会返回异常值),如求倒数函数。通过把函数返回值放入optional容器中,通过判断optional来处理这种“无效值”问题。而不用再出现无效值是抛出异常。 注意:optional和STL容器一样,只提供基本的异常保证,它自身不会抛出任何异常,但不会超过所包装的对象T。

 

Assign:

1、背景:例如对STL容器的初始化或者赋值,需要填入大量的数据,这是一个麻烦的过程,这正是assign出现的理由。

2、assign重载了“+= ,和()”操作符。 在使用时必须用usingnamespace boost::assign; 指示符 在作用域类生效

3、使用操作符+=向容器增加元素(此种方法只能用于STL容器):using namespace boost::assign;

                             Vector<int>v;    v+=1,2,3,4,5,6*6;  

4、使用操作符()向容器增加元素:使用assign库提供的三个辅助函数insert(),push_back(),push_front().可用于拥有同名函数的容器,以容器变量作为参数,返回一个代理类,该代理类实现了填充数据的功能。如:

Using namespace boost::assidn;

Int main(){  using namespaceboost::assign;

     Vector<int>v;

Push_back(v)(1) (2) (3) (8-4);

Map<int,string> m;

Insert(m) (1,”one”)(2,”two”);//此种方法可以直接在括号中接受多个参数(+=操作符时需//要用make_pair(1,”one”)先生成pair)

}

 

5、逗号操作符陪和操作符()使用:

Int main()

using namespace boost::assign;

     Vector<int>v;

Push_back(v),1,2,3,4,8-3;

}

注意:vector 和list拥有push_back(),push_front();而set,map只能使用assign::insert();

 

初始化容器元素:

C++内建的数组和string支持这样做。Assign库中的list_of(),map_list_of()/pair_list_of(),和tuple_list_of()三个函数解决了这个问题。

1、list_of():用法与前insert(),push_back()等类似,返回一个匿名列表,可以赋值给任意容器

   Vector<int>v=list_of(1) (2) (3);//v=[1] [2] [3]

2、list_of()用于map时如同括号操作符一样不方便,故有list_of(),map_list_of()/pair_list_of()

,二者完全相同。map_list_of()接受两个参数,自动构造std::pair对象插入map容器中。

Ru:map<int,int>m=map_list_of(1,2)(3,4)(5,6);

3、tuple_list_of:用与初始化tuple容器(boost引入的新容器 数据结构)

 

 

减少重复输入:

在填充数据时需要输入大量重复数据。Assign库的repeat(),repeat_fun(),range()三个函数可用于减轻工作量

1、repeat(size_t s,value_type u);//把u重复赋值s次

2、range(p1,p2)//接受两个迭代器 区间赋值   (1、2相当于STL容器的部分赋值assign()成员函数)

3、repeat_fun(sinze_t s, fun);//第一个参数是要插入的元素个数,第二个是无参数函数或者函数对象,其返回值作为要插入的元素。

如:vector<int> v;

   Insert(v).repeat_fun(5,&rand).reap(2,1),10;//X  x x  x  1  1  10

 

高级用法:

1、list_of的嵌套使用:

list_of可以就地创建匿名列表,可以嵌套使用,创建复杂数据结构

如 初始化二位数组:

Vector<vector<int> > v=list_of(list_of(1) (2) )(list_of(3)(4));//初始化

v+=list_of(5)(6),list_of(7)(8);//插入元素

 

 

4.5 swap

Boost::swap 是对标准库std::swap的增强和泛化,为交换两个变量(int等内置数据 或者类实例、容器等)的值提供便捷的方法。  对于自定义的数据结构可以通过重载 或者特化模板提高效率。  应该尽量用boost::swap而不是std::swap,前者提供了更好的优化策略。

1、交换数组(已被收入C++新标准):要求两个数组有相同长度

Int a[10];  int b[10];  boost::swap(a,b);

 

4.6 singleton 即单件模式   这种实现模式的类在程序的生命周期里有且仅有一个实例。

(boost中还没有专门的单件库,而是在其他库中有并不完善的描述)

定义: 确保一个类有且仅有一个实例,并提供在全局的为唯一访问点

      把一个类设计成自己管理自己的一个单独实例,避免其他类自行生成实例(故构造函数声明为protect或者 parivate)

      提供全局访问点:public函数

 

4.7 tribool

1、Boost.tribool类似于C++内置的bool类型,但基于三态的:true fanlse以及indeterminate状态(未知、不确定)tribool定义了各种逻辑和比较运算,可与任意bool值混合运算。由于有不确定状态,故运算遵循三态布尔逻辑。

2、与optional<bool>的区别:optional<bool>功能类似于tribool,一个未初始化的optional<bool>即可表示tribool的不确定状态。(optional存在一个隐式的bool转换)

Optional<bool>b; if(!b) {cout<<”indeterminate”;} //b未初始化,(为indeterminate态,)转为false值

4.8   operators

1、Boost.operators库提供了类似于 std::rel_ops的实现手法,允许用户在自己的类中仅定义少量的操作符(如<),就可以自动的生成其他的相应操作符重载(如> <= >=),而保证其正确的语义。

2、使用时 需要继承与Boost.operators库中的相应类即可,可以用多重继承实现多种操作符。

3、基类链 技术:operators库使用泛型编程的“基类链”解决多重继承问题,通过模板把多重继承转换为链式的单继承。

4、复合运算概念

5、相等与等价:

相等是基于==,即x==y;等价是基于<,即!(x<y)&&!(x>y),两者在语义上有很大差别。对于简单数据类型二者一样。但对于大多复杂类型和自定义类型,由于==和<操作符比较原则可能不同,从而导致两者有不同意义。

STL关联容器的排序算法使用的是等价关系,而各种查找算法是基于相等关系的。

6、解引用操作符

 

 

4.9 exception

异常是C++错误处理的重要机制,改变了传统的使用错误返回值的处理模式。C++98标准定义了标准异常类std::exception 以及一系列子类,是整个C++语言错误处理的基础。 Boost.exception 库针对标准库异常类的缺陷进行了强化,提供<<操作符重载,可向异常传入任意数据,有助于增加异常的信息和表达力。

1、标准库中的异常:

c++标准异常类别

语言本身或标准程序库所抛出的所有异常,都派生自基类exception。所有这些标准异常可分为三组:(1)语言本身支持的异常;(2)c++标准程序库发出的异常;(3)程序作用域(scope of a program)之外发生的异常。

1. 语言本身支持的异常

此类异常用以支撑某些语言特性。

(1)new操作失败,会抛出bad_alloc异常(new的nothrow版本另当别论)。

(2)执行期间,当一个作用于reference身上的“动态型别转换操作”失败时,dynamic_cast会抛出bad_cast异常。

(3)执行期型别辨识(RTTI)过程中,如果交给typeid的参数为零或空指针,typeid操作符会抛出bad_typeid异常。

(4)如果发生非预期的异常(函数抛出异常规格(exception specification)以外的异常),bad_exception异常会接手处理,bad_exception会调用unexpected(),后者通常会唤起terminate()终止程序。

如果异常规格罗列了bad_exception,那么任何未列于规格的异常,都将在函数unexpected()中重新抛出(rethrows)bad_exception。

2. c++标准程序库发出的异常

c++标准程序库异常总是派生自logic_error。

(1)invalid_argument表示无效参数,例如将bitset(array of bits)以char而非“0”或“1”进行初始化。

(2)length_error指出某个行为“可能超越了最大极限”,例如对某个字符串附加太多字符。

(3)out_of_range指出参数值“不在预期范围内”,例如在处理容器或string中采用一个错误索引。

(4)domain_error指出专业领域范畴内的错误。

此外,标准程序库的IO部分提供一个名为ios_base::failure的特殊异常,当数据流由于错误或者到达文件末尾而发生状态改变时,就可能抛出这个异常。

3. 程序作用域(scope of a program)之外发生的异常

派生自runtime_error的异常,用来指出“不在程序范围内,且不容易回避”的事件。c++标准程序库针对执行期错误提供以下三个class:

(1)range_error指出内部计算时发生区间错误。

(2)overflow_error指出算术运算发生上溢位。

(3)underflow_error指出算术运算发生下溢位。

基础类别exception和badexception定义于<exception>。bad_alloc定义于<new>。bad_cast和bad_typeid定义于<typeinfo>。ios_base::failure定义于<ios>。其他异常类别定义于<stdexcept>。

 

Boost::exception库提供两个类:exception和 error_info。

1、其中exception为异常基类(抽象类),所需要实际的异常类须由虚继承与excption,而error_info为信息储存类,用于向exception异常类添加异常信息。(通过eception的友元函<<,实现信息添加error_info到异常类,再通过自有函数get_error_info()随时取出,返回一个存储数据的指针)

2、exception和 error_info被设计为配合std::exception工作,自定义的异常类可以安全的从exception和std::exception多重继承,从而获得二者能力。

3、为简化error_info的使用,库中typedef了一些常用的异常信息类型

4、包装标准异常(自动转化为boost::exception类):

boost::exception库提供了一个模板函数enable_error_info<T>(T&e);其中T是标准异常类或其他自定义类型,通过包装T,产生从boost::exception和T派生的类,从而在不修改原异常处理体系的情况下获得boost::exception的所有好处。如果T已经是boost::exception的子类,则返回e的一个拷贝。注意:在catch(boost::exception&e)//为了使用boost::exception存储的信息,这里必须使用boost::exception来捕获。

5、使用函数抛出异常: exception库提供了throw_exception()函数来简化enable_error_info()的调用,用它抛出异常会自动调用enable_error_info()来包装异常对象:

即:throw_exception(e)  等价于thow(boost::enable_error_info(e))

6、获取更多的调试信息: 自由函数diagnostic_information();可以获取比get_error_info()更全面的信息。

7、对异常信息打包:exception支持使用boost::tuple对异常信息

 

uuid库 :全球唯一标示符,一个128位(16字节)(的数字,不需要中央机构认证就能创建一个全球唯一的标示符,位于boost::uuids名域中。其使用可以看做一个固定容量为16、元素类型为unsigned cahr 的字符容器。

1、config库:一般config库主是提供给开发者而非用户使用的,不过其中有几个可用的小工具:

BOOST_STRINGIZE宏:可以把任意字面值转换为字符串。(宏:意味着它仅能用在编译器,而不支持运行时转换)如:int x=255;cout<<BOOST_STRINGIZE(x)<<endl;  //结果输出 x,而不是255,因为宏不能处理运行时变量。

2、BOOST_STATIC_CONSTANGT宏:C++98标准允许直接在类声明中为静态整型变量赋初值,但不是所用编译器都支持该特性,如果,那么可以用enum来变通实现。 可以利用该宏,无须担心编译器限制,而更具移植能力。如:

Struct static_int

{

 Static cons tint v1=10; //整型变量直接复制

Enum{V2=20}; //枚举得到编译期整数值

BOOST_STATIC_CONSTANGT(int,v1=20);//使用宏改写

BOOST_STATIC_CONSTANGT(int,v2=10);

};

3、禁止编译器警告:

头文件<boost/config/waring_disable.hpp>可用于禁止特定的编译器(如VC)警告,如:C4996警告(确信函数调用完全正确,但还是出现的大量的C4996),以避免有用的讲稿被其淹没。

注意:仅仅用于禁止C4996这条无聊的警告,而不能用于禁止任意警告!!(使用时需要保证该头文件出现在所有头文件之前)

4、BOOST_BINARY宏:便于二进制数值的书写方法。

5、BOOST_CURRENT_FUNTION宏能够输出函数名称字符串,被exception库使用。

 

 

 

第五章:字符串与文本处理

注意:boost的很多字符处理都需要标准库的<locale>头文件,否则好很多功能无法使用。

 

5.1、lexical_cast库:

1、进行“字面值”的转换。类似于X中的atoi()函数。但是C中提供的转换函数不堆成的,如没有itoa()反转函数。lexical_cast使用类似于    C++98标准操作符(xxx_cast)的形式给出了一套通用、一致、可理解的语法。 只用时只需要在模板参数中指定转换的目标类型即可。

如: 字符串与数字之间相互转换: longx=lexical_cast<long>(“2000”);

                                String str= lexical_cast<string>(456);

2、异常 bad_ lexical_cast:

当lexical_cast无法执行转换操作时就会抛出异常bad_lexical_cast,这是std::bad_cast的派生类。应该用try/catch来保护转换。

(标准库中的异常类都有what()成员函数,可以用来获取具体的异常信息)

还可用来验证数字字符串的合法性。

3、对转换对象的要求: 虽然lexical_cast用法像是转型操作,但实际上市一个模板函数,在其内部使用了标准库的流操作,要求:

起点对象:是可流输出的,即定义了 operator<<;

终点对象时可流输入的,及定义了 operator>>,且必须是可缺省构造和拷贝构造的。

C++的内建类型(int double 等)和string满足以上条件。是常用的搭档,对与STL容器以及自定义类型一般都不满足以上条件,一般都不可用。

4、应用与自己的类:

如要用于自定义的类,把类转换为可理解的字符描述,只要满足lexical_cast条件即可,即定义 operator<<;

 

5.2 format

Boost.format库扬弃了C的printf,实现了类似于prientf()的格式化对象,可以把参数格式化到一个字符串,而且是完全类型安全的。其语法类似于prientf().

 

5.3  string_algo

#include<boost/algorithm/string.hpp>

Using namespaceboost;

 

1、C++98提供的std::string提供的成员函数,由于其符合STL容器定义,因此可看做元素类型为char的序列容器,可用标准算法来处理,但由于标准算法不是为字符串处理定做的,故不方便。   

String_algo库是一个全面字符换算法库,能在不使用正则表达式的情况下处理字符串。如大小无关比较、修建、特定的子串查找等等。   并且,String_algo库中都是泛型算法,因此可用于容器上,而不仅是std::string字符串!

2、String_algo提供的算法分为五类:

大小写转换    判断式与分类   修剪   查找与替换   分割与合并

 

注意命名规则:

前缀i:表明大小写不敏感,反之不敏感

后缀_copy:表明不变动输入,处理的是输入的拷贝,否则是原地处理,输入即输出。

后缀:_if:表明算法需要一个判断式的谓词函数对象,否则使用默认判断规则。

 

3、大小写转换:  具有带和不带_copy的两个版本:

template<typenameT> void to_upper(T& input);// 以及带_copy

template<typenameT> void to_lower(T& input);// 以及带_copy

4、判断式(算法): 可用于检测两个字符串之间的关系。

5、判断式(函数对象):都提供了大小写无关、有关两个版本

Is_epual    is_less is|_not_greater 

{

 String str1(“Samus”),str2(“samus”);

Assert(!is_equal()(str1,str2) );

Assert(is_less()(str1,str2));

//注意函数对象名称后面的两个括号:第一个函数对象构造函数,产生一个临时对象,第二个括号调用操//作符 operator();

}

6、分类:  string_algo提供一组分类函数,可用于检测一个字符是否符合某种特性,

主要用于搭配其他算法。    

如:is_space:是否为空格 is_alpha:是否为字母   is_print:是否为可打印字符 ……..等,在使用时这些可以用或运算复合判断。

当这些不能满足时,自己定义一个返回值为bool的函数对象即可。

 注意:这些函数并不正真的检测字符,而是一个工厂函数,返回一个其他正真用于自动检测的函数对象。

7、修剪: 提供了3个修剪算法:

Trim_left    trim_right  trim: 默认为删去开头或结尾的空格,他们都有_if  和_copy两种后缀,故一共有四种版本。

{

 String sr1=”2010 Happy new Year!!!!”;

Trim_left_copy_if(str1,is_digit());//删除str1的拷贝的最开头的数字  Happy new Year!!!!

Trim_copy_if(str1,is_punct()||is_digit()||is_space());//删除两端的标点、数字、和空格

}

8、查找:

String_algo的查找算法提供与std::search()类似的功能,但接口不一样,它不是返回一个迭代器,而是返回查找到的整个区间,相当于容器的一个子区间,可以像原容器一样使用。

9、替换与删除:  这些算法与查找非常接近。在在查找到结果后在对字符串进行处理。

10、分割:String_algo提供了两个字符串分割算法:find_all和split,可以使用某种策略把字符串分割成若干部分,并把分割后的字符串拷贝存入指定容器。

11、合并jion:是分割的逆运算,把存储在容器中中的字符串连接成一个新的串并可以指定连接的分隔符。

 

5.4 tokenizer

Tokenizer库是一个专门用于分词(token)的字符串处理库,可以使用简单易用的方法把一个字符串分解成若干个单词。与分割算法很类似,但也有很多区别。

1、Tokenizer是一个模板类,三个模板参数,第一个为TokenizerFunc:这是Tokenizer库专门的分词函数对象,默认是使用空格和标点进行分词。

2、分词函数对象: 这是Tokenizer的最关键的功能所在。 Tokenizer库提供了预定义好的四个分词对象:   char_deliliters_separator:  使用标点符号分词,这是默认的分词方式,但已被废弃,尽量不用。

       Char_sepatator:它支持一个字符集作为分隔符

       Escaped_list_separator:用于CSV(逗号分隔)的分词

       Offset_separator:使用偏移量分词。

3、缺陷:Tokenizer库只支持使用单个字符进行分隔;

 

5.5  xpressive:

正则表达式是处理文本的强有力工具,它使用一套复杂的语法规则,能偶文本处理领域的绝大数问题,如验证、匹配、查找、替换等等。比常用的字符串算法好的多。 许多语言都提供了内置的正则表达式支持,如Perl,python,java,但C++98标准没有。   Xpressive提供了全面的正则表达式的支持,比原正则表达式库boost.regex要好的是它不需要编译,速度快。

1、Xpressive有两种用法:静态 和动态用法(其动态用法与传统的正则表达式、boost.regex(C++11引入)很接近),一下主要为动态用法。

2、基础   正则表达式语法基础知识: 正则表达式中大部分字符都是匹配自己,是由少量的被用于特殊的模式匹配:. ^$ () * + ? {}[]/|

3、Xpressive库提供了3个重要的类:basic_regex、match_results’和 sub_match.其中basic_regex是xperessive的核心。

 

 

 

第六章  正确性 与 测试

C/C++提供了很有限的正确性、测试支持——assert宏。 C++98标准中的std::exception能够处理运行时异常,但不能检查代码的逻辑。  而boost.assert库增强了原运行时assert宏,static_assert库提供了静态断言(编译期诊断),而boost.test库则构建了完整的单元测试框架。

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值