Smart Pointers - What, Why, Which?- std::auto_ptr

本文介绍智能指针在C++编程中的应用,包括它们如何减少错误、提高效率、确保垃圾回收以及在标准模板库容器中的使用。文章讨论了不同场景下选择合适的智能指针类型,如局部变量、类成员、STL容器和大对象等。

Smart Pointers - What, Why, Which?

Yonat Sharon



What are they?

Smart pointers are objects that look and feel like pointers, but are smarter. What does this mean?

To look and feel like pointers, smart pointers need to have the same interface that pointers do: they need to support pointer operations like dereferencing (operator *) and indirection (operator ->). An object that looks and feels like something else is called a proxy object, or just proxy. The proxy pattern and its many uses are described in the books Design Patterns and Pattern Oriented Software Architecture .

To be smarter than regular pointers, smart pointers need to do things that regular pointers don't. What could these things be? Probably the most common bugs in C++ (and C) are related to pointers and memory management: dangling pointers, memory leaks, allocation failures and other joys. Having a smart pointer take care of these things can save a lot of aspirin...

The simplest example of a smart pointer is auto_ptr, which is included in the standard C++ library. You can find it in the header <memory>, or take a look at Scott Meyers' auto_ptr implementation . Here is part of auto_ptr's implementation, to illustrate what it does:

template
 <class
 T> class
 auto_ptr

{
T* ptr;
public :
explicit auto_ptr (T* p = 0) : ptr(p) {}
~auto_ptr () {delete ptr;}
T& operator* () {return *ptr;}
T* operator-> () {return ptr;}
// ...
};

As you can see, auto_ptr is a simple wrapper around a regular pointer. It forwards all meaningful operations to this pointer (dereferencing and indirection). Its smartness in the destructor: the destructor takes care of deleting the pointer.

For the user of auto_ptr, this means that instead of writing:

void foo
()
{
MyClass * p(new MyClass );
p->DoSomething ();
delete p;
}

You can write:

void foo
()
{
auto_ptr <MyClass > p(new MyClass );
p->DoSomething ();
}

And trust p to cleanup after itself.

What does this buy you? See the next section.

Why would I use them?

Obviously, different smart pointers offer different reasons for use. Here are some common reasons for using smart pointers in C++.

Why: Less bugs

Automatic cleanup. As the code above illustrates, using smart pointers that clean after themselves can save a few lines of code. The importance here is not so much in the keystrokes saved, but in reducing the probability for bugs: you don't need to remember to free the pointer, and so there is no chance you will forget about it.

Automatic initialization. Another nice thing is that you don't need to initialize the auto_ptr to NULL, since the default constructor does that for you. This is one less thing for the programmer to forget.

Dangling pointers. A common pitfall of regular pointers is the dangling pointer: a pointer that points to an object that is already deleted. The following code illustrates this situation:

MyClass
* p(new
 MyClass
);
MyClass * q = p;
delete p;
p->DoSomething (); // Watch out! p is now dangling!
p = NULL; // p is no longer dangling
q->DoSomething (); // Ouch! q is still dangling!

For auto_ptr, this is solved by setting its pointer to NULL when it is copied:

template
 <class
 T>
auto_ptr <T>& auto_ptr <T>::operator= (auto_ptr <T>& rhs)
{
if (this != &rhs) {
delete ptr;
ptr = rhs.ptr;
rhs.ptr = NULL;
}
return *this ;
}

Other smart pointers may do other things when they are copied. Here are some possible strategies for handling the statement q = p, where p and q are smart pointers:

  • Create a new copy of the object pointed by p, and have q point to this copy. This strategy is implemented in copied_ptr.h .
  • Ownership transfer : Let both p and q point to the same object, but transfer the responsibility for cleaning up ("ownership") from p to q. This strategy is implemented in owned_ptr.h .
  • Reference counting : Maintain a count of the smart pointers that point to the same object, and delete the object when this count becomes zero. So the statement q = p causes the count of the object pointed by p to increase by one. This strategy is implemented in counted_ptr.h . Scott Meyers offers another reference counting implementation in his book More Effective C++ .
  • Reference linking : The same as reference counting, only instead of a count, maintain a circular doubly linked list of all smart pointers that point to the same object. This strategy is implemented in linked_ptr.h .
  • Copy on write : Use reference counting or linking as long as the pointed object is not modified. When it is about to be modified, copy it and modify the copy. This strategy is implemented in cow_ptr.h .

All these techniques help in the battle against dangling pointers. Each has each own benefits and liabilities. The Which section of this article discusses the suitability of different smart pointers for various situations.

Why: Exception Safety

Let's take another look at this simple example:

void
 foo
()
{
MyClass * p(new MyClass );
p->DoSomething ();
delete p;
}

What happens if DoSomething() throws an exception? All the lines after it will not get executed and p will never get deleted! If we're lucky, this leads only to memory leaks. However, MyClass may free some other resources in its destructor (file handles, threads, transactions, COM references, mutexes) and so not calling it my cause severe resource locks.

If we use a smart pointer, however, p will be cleaned up whenever it gets out of scope, whether it was during the normal path of execution or during the stack unwinding caused by throwing an exception.

But isn't it possible to write exception safe code with regular pointers? Sure, but it is so painful that I doubt anyone actually does this when there is an alternative. Here is what you would do in this simple case:

void
 foo
()
{
MyClass * p;
try {
p = new MyClass ;
p->DoSomething ();
delete p;
}
catch (...) {
delete p;
throw ;
}
}

Now imagine what would happen if we had some if's and for's in there...

Why: Garbage collection

Since C++ does not provide automatic garbage collection like some other languages, smart pointers can be used for that purpose. The simplest garbage collection scheme is reference counting or reference linking, but it is quite possible to implement more sophisticated garbage collection schemes with smart pointers. For more information see the garbage collection FAQ

.

Why: Efficiency

Smart pointers can be used to make more efficient use of available memory and to shorten allocation and deallocation time.

A common strategy for using memory more efficiently is copy on write (COW). This means that the same object is shared by many COW pointers as long as it is only read and not modified. When some part of the program tries to modify the object ("write"), the COW pointer creates a new copy of the object and modifies this copy instead of the original object. The standard string class is commonly implemented using COW semantics (see the <string> header).

string
 s("Hello"
);

string t = s; // t and s point to the same buffer of characters

t += " there!" ; // a new buffer is allocated for t before
// appending " there!", so s is unchanged.

Optimized allocation schemes are possible when you can make some assumptions about the objects to be allocated or the operating environment. For example, you may know that all the objects will have the same size, or that they will all live in a single thread. Although it is possible to implement optimized allocation schemes using class-specific new and delete operators, smart pointers give you the freedom to choose whether to use the optimized scheme for each object, instead of having the scheme set for all objects of a class. It is therefore possible to match the allocation scheme to different operating environments and applications, without modifying the code for the entire class.

Why: STL containers

The C++ standard library includes a set of containers and algorithms known as the standard template library (STL). STL is designed to be generic (can be used with any kind of object) and efficient (does not incur time overhead compared to alternatives). To achieve these two design goals, STL containers store their objects by value. This means that if you have an STL container that stores objects of class Base, it cannot store of objects of classes derived from Base.

class
 Base
 { /*...*/ 
};
class Derived : public Base { /*...*/ };

Base b;
Derived d;
vector <Base > v;

v.push_back (b); // OK
v.push_back (d); // error

What can you do if you need a collection of objects from different classes? The simplest solution is to have a collection of pointers:

vector
<Base
*> v;

v.push_back (new Base ); // OK
v.push_back (new Derived ); // OK too

// cleanup:
for (vector <Base *>::iterator i = v.begin (); i != v.end (); ++i)
delete *i;

The problem with this solution is that after you're done with the container, you need to manually cleanup the objects stored in it. This is both error prone and not exception safe.

Smart pointers are a possible solution, as illustrated below. (An alternative solution is a smart container, like the one implemented in pointainer.h .)

vector
< linked_ptr
<Base
> > v;
v.push_back (new Base ); // OK
v.push_back (new Derived ); // OK too

// cleanup is automatic

Since the smart pointer automatically cleans up after itself, there is no need to manually delete the pointed objects.

Note: STL containers may copy and delete their elements behind the scenes (for example, when they resize themselves). Therefore, all copies of an element must be equivalent, or the wrong copy may be the one to survive all this copying and deleting. This means that some smart pointers cannot be used within STL containers, specifically the standard auto_ptr and any ownership-transferring pointer. For more info about this issue, see C++ Guru of the Week #25 .

Which one should I use?

Are you confused enough? Well, this summary should help.

Which: Local variables

The standard auto_ptr is the simplest smart pointer, and it is also, well, standard. If there are no special requirements, you should use it. For local variables, it is usually the right choice.

Which: Class members

Although you can use auto_ptr as a class member (and save yourself the trouble of freeing objects in the destructor), copying one object to another will nullify the pointer, as illustrated Below.

class
 MyClass
{
auto_ptr <int > p;
// ...
};

MyClass x;
// do some meaningful things with x
MyClass y = x; // x.p now has a NULL pointer

Using a copied pointer instead of auto_ptr solves this problem: the copied object (y) gets a new copy of the member.

Note that using a reference counted or reference linked pointer means that if y changes the member, this change will also affect x! Therefore, if you want to save memory, you should use a COW pointer and not a simple reference counted/linked pointer.

Which: STL containers

As explained above, using garbage-collected pointers with STL containers lets you store objects from different classes in the same container.

It is important to consider the characteristics of the specific garbage collection scheme used. Specifically, reference counting/linking can leak in the case of circular references (i.e., when the pointed object itself contains a counted pointer, which points to an object that contains the original counted pointer). Its advantage over other schemes is that it is both simple to implement and deterministic. The deterministic behavior may be important in some real time systems, where you cannot allow the system to suddenly wait while the garbage collector performs its housekeeping duties.

Generally speaking, there are two ways to implement reference counting: intrusive and non-intrusive. Intrusive means that the pointed object itself contains the count. Therefore, you cannot use intrusive reference counting with 3-rd party classes that do not already have this feature. You can, however, derive a new class from the 3-rd party class and add the count to it. Non-intrusive reference counting requires an allocation of a count for each counted object. The counted_ptr.h is an example of non-intrusive reference counting.

Intrusive
 reference counting Non-intrusive reference counting
Reference linking does not require any changes to be made to the pointed objects, nor does it require any additional allocations. A reference linked pointer takes a little more space than a reference counted pointer - just enough to store one or two more pointers.Reference 
linking

Both reference counting and reference linking require using locks if the pointers are used by more than one thread of execution.

Which: Explicit ownership transfer

Sometimes, you want to receive a pointer as a function argument, but keep the ownership of this pointer (i.e. the control over its lifetime) to yourself. One way to do this is to use consistent naming-conventions for such cases. Taligent's Guide to Designing Programs recommends using "adopt" to mark that a function adopts ownership of a pointer.

Using an owned pointer as the function argument is an explicit statement that the function is taking ownership of the pointer.

Which: Big objects

If you have objects that take a lot of space, you can save some of this space by using COW pointers. This way, an object will be copied only when necessary, and shared otherwise. The sharing is implemented using some garbage collection scheme, like reference counting or linking.

Which: Summary

For this: Use that:
Local variables auto_ptr
Class members Copied pointer
STL Containers Garbage collected pointer (e.g. reference counting/linking)
Explicit ownership transfer Owned pointer
Big objects Copy on write

Conclusion

Smart pointers are useful tools for writing safe and efficient code in C++. Like any tool, they should be used with appropriate care, thought and knowledge. For a comprehensive and in depth analysis of the issues concerning smart pointers, I recommend reading Andrei Alexandrescu's chapter about smart pointers in his book Modern C++ Design .

Feel free to use my own smart pointers in your code.
The Boost C++ libraries include some smart pointers, which are more rigorously tested and actively maintained. Do try them first, if they are appropriate for your needs.

源码地址: https://pan.quark.cn/s/d1f41682e390 miyoubiAuto 米游社每日米游币自动化Python脚本(务必使用Python3) 8更新:更换cookie的获取地址 注意:禁止在B站、贴吧、或各大论坛大肆传播! 作者已退游,项目不维护了。 如果有能力的可以pr修复。 小引一波 推荐关注几个非常可爱有趣的女孩! 欢迎B站搜索: @嘉然今天吃什么 @向晚大魔王 @乃琳Queen @贝拉kira 第三方库 食用方法 下载源码 在Global.py中设置米游社Cookie 运行myb.py 本地第一次运行时会自动生产一个文件储存cookie,请勿删除 当前仅支持单个账号! 获取Cookie方法 浏览器无痕模式打开 http://user.mihoyo.com/ ,登录账号 按,打开,找到并点击 按刷新页面,按下图复制 Cookie: How to get mys cookie 当触发时,可尝试按关闭,然后再次刷新页面,最后复制 Cookie。 也可以使用另一种方法: 复制代码 浏览器无痕模式打开 http://user.mihoyo.com/ ,登录账号 按,打开,找到并点击 控制台粘贴代码并运行,获得类似的输出信息 部分即为所需复制的 Cookie,点击确定复制 部署方法--腾讯云函数版(推荐! ) 下载项目源码和压缩包 进入项目文件夹打开命令行执行以下命令 xxxxxxx为通过上面方式或取得米游社cookie 一定要用双引号包裹!! 例如: png 复制返回内容(包括括号) 例如: QQ截图20210505031552.png 登录腾讯云函数官网 选择函数服务-新建-自定义创建 函数名称随意-地区随意-运行环境Python3....
<think>我们正在讨论使用宏定义生成智能指针类型,特别是关于一个名为`DEFINE_SMART_POINTERS`的宏的用途或可能遇到的问题。根据引用[1]中提供的宏定义示例,我们可以推断`DEFINE_SMART_POINTERS`可能是一个用于为某个类生成多个智能指针别名的宏,类似于引用[1]中的`__RCLCPP_WEAK_PTR_ALIAS`,但可能更全面(包括shared_ptr, weak_ptr等)。 ### 1. `DEFINE_SMART_POINTERS`宏的典型用途 假设我们有一个类`MyClass`,我们想为它定义以下智能指针别名: - `MyClassPtr`: `std::shared_ptr<MyClass>` - `MyClassConstPtr`: `std::shared_ptr<const MyClass>` - `MyClassWeakPtr`: `std::weak_ptr<MyClass>` - `MyClassConstWeakPtr`: `std::weak_ptr<const MyClass>` - 等等 我们可以定义一个宏`DEFINE_SMART_POINTERS(ClassName)`来一次性生成这些别名。 #### 示例宏定义 ```cpp #define DEFINE_SMART_POINTERS(ClassName) \ using ClassName##Ptr = std::shared_ptr<ClassName>; \ using ClassName##ConstPtr = std::shared_ptr<const ClassName>; \ using ClassName##WeakPtr = std::weak_ptr<ClassName>; \ using ClassName##ConstWeakPtr = std::weak_ptr<const ClassName>; \ using ClassName##UniquePtr = std::unique_ptr<ClassName>; \ using ClassName##UniqueConstPtr = std::unique_ptr<const ClassName> ``` #### 使用示例 ```cpp class MyClass { // ... }; DEFINE_SMART_POINTERS(MyClass); // 为MyClass生成一组智能指针别名 int main() { MyClassPtr shared = std::make_shared<MyClass>(); MyClassWeakPtr weak = shared; MyClassUniquePtr unique = std::make_unique<MyClass>(); } ``` ### 2. 使用宏定义生成智能指针类型可能遇到的问题 #### (1) 宏展开导致的命名冲突 如果宏定义的别名与现有类型冲突,会导致编译错误。例如,如果已经有一个名为`MyClassPtr`的类型,则宏展开会失败。 **解决方案**:采用更独特的命名规则,例如在项目中使用统一的前缀或后缀。 #### (2) 模板类支持 如果`ClassName`是一个模板类,直接使用上述宏会出错,因为模板类需要额外的模板参数。 **解决方案**:使用可变参数宏来支持模板类。 ```cpp #define DEFINE_SMART_POINTERS_TEMPLATE(ClassName, ...) \ template<__VA_ARGS__> \ using ClassName##Ptr = std::shared_ptr<ClassName<__VA_ARGS__>>; \ // ... 其他类似 // 使用示例 template<typename T> class TemplateClass {}; DEFINE_SMART_POINTERS_TEMPLATE(TemplateClass, typename T); // 生成 TemplateClassPtr<T> 等别名 ``` #### (3) 宏的作用域问题 宏定义是全局的,如果在头文件中使用,可能会污染全局命名空间。 **解决方案**:将宏放在命名空间内,并在使用宏之前定义命名空间。 ```cpp namespace MyProject { #define DEFINE_SMART_POINTERS(ClassName) \ ... // 同上 } ``` #### (4) 逗号问题 如果类名本身包含逗号(如模板特化),则宏参数中的逗号会被视为参数分隔符,导致宏参数数量不匹配。 **解决方案**:使用`typedef`或`using`来定义中间类型,或者使用括号将包含逗号的类型括起来(但C++宏不支持,所以通常需要避免)。 #### (5) 调试信息可读性 宏展开的类型别名在调试器中可能显示为原始类型(如`std::shared_ptr<MyClass>`),而不是别名(如`MyClassPtr`),这取决于调试器。这可能会降低代码的可调试性。 #### (6) 跨DLL边界使用 如果智能指针指向的对象在DLL中分配内存,而在另一个DLL中释放,则可能导致问题。因为不同的DLL可能有不同的堆,而默认的`delete`操作可能发生在错误的堆上。 **解决方案**:使用自定义删除器,确保在同一个模块中分配和释放内存。 ### 3. 与工厂函数结合(参考引用[2]) 引用[2]提到`boost::make_shared`和`std::make_shared`可以避免直接调用构造函数。我们可以结合宏定义和工厂函数,为类添加创建智能指针的静态方法。 ```cpp #define DECLARE_CLASS_WITH_SMART_PTRS(ClassName) \ class ClassName; \ DEFINE_SMART_POINTERS(ClassName); \ class ClassName { \ public: \ static ClassName##Ptr Create() { \ return std::make_shared<ClassName>(); \ } \ private: \ ClassName() = default; // 强制使用工厂方法 // 使用示例 DECLARE_CLASS_WITH_SMART_PTRS(MyClass); // 展开后: // class MyClass; // DEFINE_SMART_POINTERS(MyClass); // 生成别名 // class MyClass { ... }; int main() { MyClassPtr obj = MyClass::Create(); } ``` ### 4. 引用[3]的提示:参数转发 引用[3]提到T&&设计用于参数转发。在创建智能指针时,如果需要传递参数给构造函数,可以使用完美转发。 ```cpp #define DECLARE_CLASS_WITH_SMART_PTRS(ClassName) \ ... \ public: \ template<typename... Args> \ static ClassName##Ptr Create(Args&&... args) { \ return std::make_shared<ClassName>(std::forward<Args>(args)...); \ } \ ... // 使用示例 class MyClass { public: MyClass(int a, double b) {} }; DECLARE_CLASS_WITH_SMART_PTRS(MyClass); int main() { MyClassPtr obj = MyClass::Create(10, 3.14); } ``` ### 总结 `DEFINE_SMART_POINTERS`宏的主要用途是简化多个智能指针别名的定义,提高代码可读性和一致性。但在使用时需要注意: 1. 命名冲突 2. 模板类的支持 3. 作用域控制 4. 逗号问题 5. 调试信息 6. 跨模块内存管理 在大型项目中,合理使用这种宏可以显著减少重复代码,但应谨慎设计以避免潜在问题。 §§相关问题§§ 1. 如何为模板类生成智能指针别名? 2. 使用宏定义智能指针别名时如何避免命名冲突? 3. 在跨DLL边界使用智能指针时需要注意什么? 4. 如何结合工厂方法和宏定义来创建对象? 5. 宏定义的智能指针别名在调试器中如何显示?</think>### 使用 `DEFINE_SMART_POINTERS` 宏的用途与常见问题 #### 1. **核心用途** `DEFINE_SMART_POINTERS` 宏主要用于简化智能指针类型别名的声明,通过**一次性生成多个关联类型**,避免重复代码。典型实现如下: ```cpp #define DEFINE_SMART_POINTERS(ClassName) \ using ClassName##Ptr = std::shared_ptr<ClassName>; \ using ClassName##ConstPtr = std::shared_ptr<const ClassName>; \ using ClassName##WeakPtr = std::weak_ptr<ClassName>; \ using ClassName##UniquePtr = std::unique_ptr<ClassName> // [^1] ``` **使用示例**: ```cpp class NetworkConnection; DEFINE_SMART_POINTERS(NetworkConnection); // 生成: // NetworkConnectionPtr // NetworkConnectionWeakPtr等 // 使用生成的类型 NetworkConnectionPtr conn = std::make_shared<NetworkConnection>(); ``` #### 2. **关键优势** 1. **代码简洁性** 将原本需要4-5行的类型声明压缩为单行宏调用 2. **命名一致性** 强制遵循 `ClassName##Ptr` 的统一命名规范 3. **弱指针支持** 自动生成 `WeakPtr` 别名,避免循环引用问题(如引用[1]所示) 4. **工厂模式集成** 结合 `std::make_shared`(如引用[2])实现安全对象创建: ```cpp #define DECLARE_FACTORY(ClassName) \ static ClassName##Ptr Create() { \ return std::make_shared<ClassName>(); \ } ``` #### 3. **常见问题与解决方案** | 问题类型 | 表现 | 解决方案 | |----------|------|----------| | **模板类支持** | 无法处理 `Container<T>` 类 | 使用 `__VA_ARGS__` 宏:<br>`#define DEF_TEMPLATE_PTR(T, ...) using T##Ptr = std::shared_ptr<T<__VA_ARGS__>>` | | **逗号分隔问题** | `std::map<int, string>` 导致宏解析错误 | 用 `typedef` 预定义类型:<br>`typedef std::map<int, string> IntStrMap;`<br>`DEFINE_SMART_POINTERS(IntStrMap);` | | **调试信息混淆** | 调试器中显示 `std::shared_ptr<...>` 而非别名 | 使用 `#pragma` 注入调试信息:<br>`#pragma comment(linker, "/alias:NetworkConnectionPtr=std::shared_ptr<NetworkConnection>")` | | **DLL边界问题** | 跨模块传递智能指针导致未定义行为 | 添加 `__declspec(dllexport)`:<br>`class __declspec(dllexport) NetworkConnection {...}` | | **参数转发限制** | 无法完美转发构造函数参数(如引用[3]的 `T&&`) | 结合可变参数模板:<br>`template<typename... Args>`<br>`static ClassName##Ptr Create(Args&&... args)` | #### 4. **最佳实践** 1. **命名空间隔离** 将宏封装在命名空间内,避免全局污染: ```cpp namespace MyLib { #define DEFINE_SMART_POINTERS(Class) \ using Class##Ptr = std::shared_ptr<Class>; } ``` 2. **前置声明优化** 结合类前置声明减少头文件依赖: ```cpp #define DECLARE_PTR(Class) \ class Class; \ DEFINE_SMART_POINTERS(Class) // [^1] ``` 3. **自定义删除器支持** 扩展宏以支持资源管理(如文件句柄): ```cpp #define DEF_FILE_PTR(ClassName) \ using ClassName##Ptr = std::unique_ptr<ClassName, FileDeleter> struct FileDeleter { void operator()(FILE* f) { if(f) fclose(f); } }; // [^2] ``` ### 典型应用场景 ```cpp // 游戏引擎中的组件管理 DECLARE_PTR(TransformComponent); // 前置声明+智能指针 class GameObject { public: void AddComponent(TransformComponentPtr comp) { m_transform = comp; // shared_ptr m_collisionRef = comp; // weak_ptr } private: TransformComponentPtr m_transform; TransformComponentWeakPtr m_collisionRef; // 避免循环引用[^1] }; ``` > 宏定义在简化智能指针声明方面优势显著,但需警惕其文本替换本质带来的陷阱。对于关键模块,建议结合 `using` 别名和宏定义的优势[^1][^3]。 --- ### 相关问题 1. 如何为模板类(如 `Container<T>`)生成智能指针别名? 2. 跨 DLL 传递智能指针时需要注意哪些特殊问题? 3. 宏定义的智能指针类型如何影响 C++ 的 ADL(参数依赖查找)机制? 4. 如何扩展 `DEFINE_SMART_POINTERS` 以支持自定义分配器? 5. 在调试器中如何正确显示宏定义的智能指针别名?
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值