C++11中智能指针的介绍和使用

一、什么是智能指针

智能指针是 C++ 中用于管理动态内存的工具,它通过封装裸指针(raw pointer)并自动管理内存的生命周期,解决了手动内存管理中的常见问题,如内存泄漏、悬空指针和双重释放等。智能指针的核心思想是利用 RAII(Resource Acquisition Is Initialization,资源获取即初始化)机制,确保资源在对象生命周期结束时自动释放。

二、为什么要使用智能指针

在 C++ 中,使用智能指针是为了解决手动管理动态内存时常见的问题,如内存泄漏、悬空指针和双重释放等。以下是使用智能指针的主要原因及其优势:

1. 避免内存泄漏
手动使用 new 和 delete 时,如果忘记调用 delete,会导致内存泄漏。使用智能指针在析构时自动释放内存。

void manualMemory() {
    int* ptr = new int(10);  // 手动分配内存
    // 忘记 delete,导致内存泄漏
}

void smartPointer() {
    std::unique_ptr<int> ptr = std::make_unique<int>(10);  // 自动释放内存
}

2. 防止悬空指针
手动释放内存后,指针仍可能被访问,导致未定义行为。使用智能指针在释放内存后会自动置空,避免悬空指针。

void danglingPointer() {
    int* ptr = new int(10);
    delete ptr;  // 手动释放内存
    *ptr = 20;   // 悬空指针,未定义行为
}

void safePointer() {
    std::unique_ptr<int> ptr = std::make_unique<int>(10);
    ptr.reset();  // 释放内存并置空
    if (ptr) {    // 检查指针是否有效
        *ptr = 20;  // 不会执行
    }
}

3. 避免双重释放
同一块内存被多次释放,导致程序崩溃。使用智能指针确保内存只被释放一次。

void doubleFree() {
    int* ptr = new int(10);
    delete ptr;  // 第一次释放
    delete ptr;  // 第二次释放,未定义行为
}

void safeFree() {
    std::unique_ptr<int> ptr = std::make_unique<int>(10);
    ptr.reset();  // 释放内存
    ptr.reset();  // 不会重复释放
}

4. 简化资源管理
手动管理资源(如文件、网络连接)容易出错。使用智能指针结合自定义删除器,自动管理资源。

void fileExample() {
    FILE* file = fopen("data.txt", "r");
    if (!file) {
        std::cerr << "文件打开失败\n";
        return;
    }
    // 手动关闭文件
    fclose(file);
}

void smartFileExample() {
    auto fileDeleter = [](FILE* file) { fclose(file); };
    std::unique_ptr<FILE, decltype(fileDeleter)> file(fopen("data.txt", "r"), fileDeleter);
    if (!file) {
        std::cerr << "文件打开失败\n";
    }
    // 文件自动关闭
}

5. 支持异常安全
在异常发生时,手动管理的内存可能无法正确释放。使用智能指针确保异常发生时资源仍能正确释放。

void unsafeException() {
    int* ptr = new int(10);
    throw std::runtime_error("发生异常");  // 内存泄漏
    delete ptr;
}

void safeException() {
    std::unique_ptr<int> ptr = std::make_unique<int>(10);
    throw std::runtime_error("发生异常");  // 内存自动释放
}

6. 支持多线程安全
手动管理内存时,多线程环境容易引发竞争条件。std::shared_ptr 的引用计数是线程安全的。

void threadSafeExample() {
    auto sharedPtr = std::make_shared<int>(10);
    std::thread t1([sharedPtr]() { (*sharedPtr)++; });
    std::thread t2([sharedPtr]() { (*sharedPtr)++; });
    t1.join(); t2.join();
    std::cout << *sharedPtr << "\n";  // 输出 12
}

三、std::unique_ptr的使用

在 C++ 中,std::unique_ptr 是一种独占所有权的智能指针,用于自动管理动态分配的内存或资源。它遵循 RAII(资源获取即初始化) 原则,确保资源在对象生命周期结束时自动释放。

1. 核心特性

  • 独占所有权:同一时间只能有一个 unique_ptr 指向某个资源,不可复制。
  • 移动语义:支持通过 std::move 转移所有权。
  • 轻量高效:无引用计数开销,性能接近裸指针。
  • 自动释放:析构时自动调用 delete 或自定义删除器。
  • 支持动态数组:可通过 std::unique_ptr<T[]> 管理数组。

2. 基本用法
头文件

#include <memory>

创建 unique_ptr

  • 直接构造:使用 new(不推荐,优先使用 std::make_unique)。
  • 工厂函数:使用 std::make_unique(C++14 起支持,更安全高效)。
// 管理单个对象
std::unique_ptr<int> ptr1(new int(42));        // 直接构造
auto ptr2 = std::make_unique<int>(42);         // 推荐方式(C++14+)

// 管理动态数组(自动调用 delete[])
auto arr1 = std::unique_ptr<int[]>(new int[5]); // 直接构造
auto arr2 = std::make_unique<int[]>(5);         // 推荐方式(C++14+)

3. 所有权转移
std::unique_ptr的设计目标是独占资源所有权,因此它禁用复制构造函数和复制赋值运算符,但提供移动构造函数和移动赋值运算符。这种设计允许所有权的转移,而非复制资源。
unique_ptr 不可复制,但可通过移动语义转移所有权:

auto ptr1 = std::make_unique<int>(42);
// std::unique_ptr<int> ptr2 = ptr1;         // 错误:禁止复制
std::unique_ptr<int> ptr2 = std::move(ptr1);  // 正确:转移所有权

if (!ptr1) {
    std::cout << "ptr1 已失去所有权\n";       // 输出此句
}

4.函数返回 unique_ptr 的机制
当函数返回一个 std::unique_ptr 时,编译器会自动触发移动语义,即使没有显式使用 std::move。这是因为:

  • 局部对象被视为右值:在返回语句中,局部变量会被隐式转换为右值引用(类似于 std::move)。
  • 编译器优化:返回值优化(RVO)或命名返回值优化(NRVO)可能直接构造对象到调用方的内存位置,避免任何拷贝或移动。
  • 自动移动:当返回局部对象时,如果该对象不可复制但可移动,编译器必须优先使用移动操作。
  • 无需显式 std::move:即使返回的是左值(如命名的局部变量),编译器也会隐式应用移动语义。

示例代码:

#include <memory>

// 函数返回 unique_ptr
std::unique_ptr<int> create_ptr() {
    auto ptr = std::make_unique<int>(42);  // 局部对象
    return ptr;  // 隐式调用移动构造函数
}

int main() {
    auto ptr = create_ptr();  // 所有权从函数内部转移到此处
    // ptr 现在有效,原函数内的指针已释放所有权
    return 0;
}

通过调试或日志可以验证所有权的转移过程:

#include <iostream>
#include <memory>

class Resource {
public:
    Resource() { std::cout << "Resource 创建\n"; }
    ~Resource() { std::cout << "Resource 销毁\n"; }
};

std::unique_ptr<Resource> create() {
    auto res = std::make_unique<Resource>();
    return res;  // 所有权转移
}

int main() {
    std::cout << "进入 main\n";
    auto ptr = create();
    std::cout << "离开 main\n";
    return 0;
}

请添加图片描述
可以看出资源在 create() 中创建,在 main() 结束时析构,证明所有权成功转移。

5. 访问资源

  • operator* 和 operator->:访问资源成员。
  • get():返回裸指针(慎用,不推荐长期持有)。
  • release():释放所有权,返回裸指针(需手动释放)。
auto ptr = std::make_unique<std::string>("Hello");
std::cout << *ptr << "\n";                   // 输出 "Hello"
std::cout << ptr->size() << "\n";            // 输出 5

std::string* raw_ptr = ptr.get();            // 获取裸指针(不释放所有权)
std::string* released_ptr = ptr.release();   // 释放所有权,需手动 delete
delete released_ptr;

6. 重置与释放

  • reset():释放当前资源并接管新资源(或置空)。
  • reset(nullptr):显式释放资源。
auto ptr = std::make_unique<int>(42);
ptr.reset(new int(100));  // 释放原资源,管理新资源
ptr.reset();              // 释放资源,ptr 变为空

7. 自定义删除器
unique_ptr 允许自定义资源的释放逻辑(如文件句柄、网络连接)。

#include <cstdio>
#include <memory>

// 自定义删除器:关闭文件
auto FileDeleter = [](FILE* file) {
    if (file) {
        fclose(file);
        std::cout << "文件已关闭\n";
    }
};

int main() {
    std::unique_ptr<FILE, decltype(FileDeleter)> file(
        fopen("data.txt", "r"), FileDeleter
    );
    if (!file) {
        std::cerr << "文件打开失败\n";
    }
    return 0;  // 文件自动关闭
}

示例:管理动态数组

auto arr = std::make_unique<int[]>(5);  // 管理动态数组
arr[0] = 10;                            // 支持下标访问(C++14+)

8. 常见错误与注意事项
避免混合使用裸指针:

int* raw_ptr = new int(10);
std::unique_ptr<int> ptr(raw_ptr);
// 错误:若其他地方再 delete raw_ptr,会导致双重释放

不要返回 unique_ptr 的裸指针:

int* leaky_func() {
    auto ptr = std::make_unique<int>(10);
    return ptr.get();  // 返回后 ptr 析构,资源被释放,返回的指针悬空!
}

四、std::shared_ptr的使用

在 C++ 中,std::shared_ptr 是一种共享所有权的智能指针,通过引用计数机制管理动态分配的资源,允许多个指针共享同一对象的所有权。当最后一个 shared_ptr 被销毁时,资源才会被自动释放。

1. 核心特性

  • 共享所有权:多个 shared_ptr 可以指向同一对象。
  • 引用计数:内部维护一个引用计数器,记录共享该资源的指针数量。
  • 自动释放:当引用计数归零时,调用删除器释放资源。
  • 线程安全:引用计数的增减是原子的,但对象访问需额外同步。
  • 支持自定义删除器:可定义资源释放逻辑(如文件关闭、网络连接释放)。

2. 基本用法
头文件

#include <memory>

创建 shared_ptr
直接构造(不推荐,优先使用 std::make_shared):

std::shared_ptr<int> ptr1(new int(42));

推荐方式(使用 std::make_shared):

auto ptr2 = std::make_shared<int>(42);  // 高效且安全

共享所有权

auto ptr3 = ptr2;  // 引用计数 +1
std::cout << ptr3.use_count() << "\n";  // 输出 2

3. 关键成员函数
use_count():返回当前引用计数(调试用,生产环境慎用)

std::cout << ptr2.use_count() << "\n";  // 输出 2

reset():释放当前资源的所有权,引用计数减 1

ptr3.reset();  // 引用计数 -1,若归零则释放资源

get():返回裸指针(慎用,避免手动管理)

int* raw_ptr = ptr2.get();

4. 自定义删除器
通过构造函数或 std::make_shared 指定删除逻辑:

// 自定义删除器:释放 malloc 分配的内存
auto FreeDeleter = [](int* p) {
    std::free(p);
    std::cout << "内存已释放\n";
};

std::shared_ptr<int> ptr4(static_cast<int*>(std::malloc(sizeof(int))), FreeDeleter);

5. 多线程安全性

  • 引用计数操作是原子的:use_count() 的增减线程安全。
  • 对象访问需同步:对共享资源的读写需加锁。
#include <memory>
#include <mutex>
#include <thread>

std::shared_ptr<int> sharedData = std::make_shared<int>(0);
std::mutex mtx;

void increment() {
    std::lock_guard<std::mutex> lock(mtx);
    (*sharedData)++;
}

int main() {
    std::thread t1(increment);
    std::thread t2(increment);
    t1.join(); t2.join();
    std::cout << *sharedData << "\n";  // 输出 2
    return 0;
}

6.裸指针初始化多个 shared_ptr
在 C++ 中,如果使用同一个原始指针初始化两个独立的 std::shared_ptr,然后将其中一个 shared_ptr 赋值给另一个会导致 未定义行为(Undefined Behavior),例如双重释放(double free)或悬空指针(dangling pointer)。
问题根源:

  • 独立初始化:直接用同一个原始指针初始化两个独立的 shared_ptr,会导致两个 shared_ptr 各自拥有独立的引用计数控制块。
  • 未共享所有权:两个 shared_ptr 的引用计数互相独立,无法感知彼此的释放操作。
  • 双重释放:当其中一个 shared_ptr 析构时释放内存,另一个 shared_ptr 仍持有已释放的指针,导致后续访问或析构时崩溃。

错误示例:

#include <memory>
#include <iostream>

class MyClass {
public:
    MyClass() { std::cout << "MyClass 构造\n"; }
    ~MyClass() { std::cout << "MyClass 析构\n"; }
};

int main() {
    // 错误:用同一个原始指针初始化两个独立的 shared_ptr
    MyClass* raw_ptr = new MyClass();
    std::shared_ptr<MyClass> ptr1(raw_ptr);
    std::shared_ptr<MyClass> ptr2(raw_ptr);  // 危险!

    // 输出引用计数(实际是两个独立的计数)
    std::cout << "ptr1 引用计数: " << ptr1.use_count() << "\n";  // 输出 1
    std::cout << "ptr2 引用计数: " << ptr2.use_count() << "\n";  // 输出 1

    return 0;  // 程序结束时,ptr1 和 ptr2 会分别释放 raw_ptr,导致双重释放!
}

输出:
请添加图片描述
赋值操作的影响:
如果将两个独立的 shared_ptr 互相赋值,会发生以下行为:

  • 目标 shared_ptr 释放原资源:如果目标 shared_ptr 原本管理其他资源,会先减少原资源的引用计数,若归零则释放资源。
  • 接管新资源的所有权:目标 shared_ptr 会指向新资源,并共享新资源的引用计数。
  • 原资源的引用计数增加:新资源的引用计数加 1。

赋值示例:

MyClass* raw_ptr = new MyClass();
std::shared_ptr<MyClass> ptr1(raw_ptr);  // 引用计数 = 1
std::shared_ptr<MyClass> ptr2;           // 空的 shared_ptr

ptr2 = ptr1;  // 正确:ptr2 共享 ptr1 的资源,引用计数 = 2
std::cout << ptr1.use_count() << "\n";  // 输出 2

错误赋值的后果:
如果两个独立的 shared_ptr(指向同一资源但引用计数独立)互相赋值:

MyClass* raw_ptr = new MyClass();
std::shared_ptr<MyClass> ptr1(raw_ptr);  // 引用计数 = 1(控制块 A)
std::shared_ptr<MyClass> ptr2(raw_ptr);  // 引用计数 = 1(控制块 B)

ptr1 = ptr2;  // 危险!
  • ptr1 释放原资源:ptr1 原本管理 raw_ptr(控制块 A),赋值后释放 raw_ptr(因为控制块 A 的引用计数归零)。
  • ptr2 仍管理已释放的 raw_ptr:ptr2 的控制块 B 的引用计数仍为 1,但其管理的 raw_ptr 已被释放。
  • 程序崩溃:当 ptr2 析构时,会再次释放 raw_ptr,导致双重释放。

正确代码示例:

#include <memory>
#include <iostream>

class MyClass {
public:
    MyClass() { std::cout << "MyClass 构造\n"; }
    ~MyClass() { std::cout << "MyClass 析构\n"; }
};

int main() {
    auto ptr1 = std::make_shared<MyClass>();  // 引用计数 = 1
    auto ptr2 = ptr1;                         // 引用计数 = 2
    std::cout << "引用计数: " << ptr1.use_count() << "\n";  // 输出 2
    return 0;  // 资源仅释放一次
}

输出:
请添加图片描述
如果使用同一个对象的两个不同的原始指针分别初始化两个 std::shared_ptr,然后将其中一个 std::shared_ptr 复制给另一个 std::shared_ptr,不会直接导致未定义行为。
场景描述:
假设有两个不同的原始指针 ptrA 和 ptrB,分别初始化两个 std::shared_ptr:

MyClass* ptrA = new MyClass();
MyClass* ptrB = new MyClass();
std::shared_ptr<MyClass> sharedPtrA(ptrA);
std::shared_ptr<MyClass> sharedPtrB(ptrB);

然后将其中一个 std::shared_ptr 复制给另一个:

sharedPtrA = sharedPtrB;  // sharedPtrA 现在管理 ptrB

赋值操作的影响:sharedPtrA = sharedPtrB,sharedPtrA 会释放其原本管理的资源(ptrA),sharedPtrA 会接管 sharedPtrB 的资源(ptrB),并共享其引用计数。ptrA 的引用计数归零,资源被释放,ptrB 的引用计数增加。
结果是:ptrA 被正确释放。ptrB 的引用计数增加,最终,sharedPtrA 和 sharedPtrB 都管理 ptrB,且引用计数正确。

代码示例:

#include <memory>
#include <iostream>

class MyClass {
public:
    MyClass() { std::cout << "MyClass 构造\n"; }
    ~MyClass() { std::cout << "MyClass 析构\n"; }
};

int main() {
    // 创建两个原始指针
    MyClass* ptrA = new MyClass();
    MyClass* ptrB = new MyClass();

    // 分别初始化两个 shared_ptr
    std::shared_ptr<MyClass> sharedPtrA(ptrA);
    std::shared_ptr<MyClass> sharedPtrB(ptrB);

    // 输出初始引用计数
    std::cout << "sharedPtrA 引用计数: " << sharedPtrA.use_count() << "\n";  // 输出 1
    std::cout << "sharedPtrB 引用计数: " << sharedPtrB.use_count() << "\n";  // 输出 1

    // 将一个 shared_ptr 复制给另一个
    sharedPtrA = sharedPtrB;  // sharedPtrA 释放 ptrA,接管 ptrB
    std::cout << "sharedPtrA 引用计数: " << sharedPtrA.use_count() << "\n";  // 输出 2
    std::cout << "sharedPtrB 引用计数: " << sharedPtrB.use_count() << "\n";  // 输出 2

    return 0;  // 程序结束时,ptrB 被释放一次
}

输出:
请添加图片描述
虽然上述场景不会导致未定义行为,但在实际开发中可能会引发以下问题:
如果在赋值操作之前,sharedPtrA 和 sharedPtrB 分别管理不同的资源,且这些资源需要被正确释放,那么赋值操作可能导致资源泄漏。
例如:

MyClass* ptrA = new MyClass();
MyClass* ptrB = new MyClass();
std::shared_ptr<MyClass> sharedPtrA(ptrA);
std::shared_ptr<MyClass> sharedPtrB(ptrB);

sharedPtrA = sharedPtrB;  // ptrA 被释放,ptrB 的引用计数增加
// 此时 ptrA 已被释放,但 ptrB 的引用计数增加

如果程序逻辑依赖于 sharedPtrA 和 sharedPtrB 管理不同的资源,赋值操作会破坏这种逻辑。

void process(std::shared_ptr<MyClass> ptr) {
    // 假设 ptr 是唯一的资源
}

MyClass* ptrA = new MyClass();
MyClass* ptrB = new MyClass();
std::shared_ptr<MyClass> sharedPtrA(ptrA);
std::shared_ptr<MyClass> sharedPtrB(ptrB);

sharedPtrA = sharedPtrB;  // sharedPtrA 和 sharedPtrB 现在管理同一资源
process(sharedPtrA);      // 逻辑错误:sharedPtrA 和 sharedPtrB 管理同一资源

从上面可以看出使用不同原始指针初始化 std::shared_ptr 并相互复制是安全的,不会导致未定义行为。但可能会引发资源泄漏或逻辑错误,尤其是在程序逻辑依赖于 shared_ptr 管理不同资源时。

7.std::shared_ptr的交叉引用
在C++中,std::shared_ptr的交叉引用(循环引用)问题是由于两个或多个对象相互持有对方的shared_ptr,导致引用计数无法归零,从而引发内存泄漏。

#include <memory>
#include <iostream>

class Child;  // 前向声明

class Parent {
public:
    std::shared_ptr<Child> child;
    ~Parent() { std::cout << "Parent 析构\n"; }
};

class Child {
public:
    std::shared_ptr<Parent> parent; 
    ~Child() { std::cout << "Child 析构\n"; }
};

int main() {
    auto parent = std::make_shared<Parent>();
    auto child = std::make_shared<Child>();
    // 循环引用
    parent->child = child;
    child->parent = parent;

    return 0;  // 退出时,child和parent的引用计数仍为1,无法释放!
}

输出结果:无析构输出,内存泄漏。

问题根源:

  • 引用计数机制:shared_ptr通过引用计数管理对象生命周期,每增加一个shared_ptr,计数加1;每析构一个,计数减1。
  • 循环引用:对象A持有对象B的shared_ptr,对象B又持有对象A的shared_ptr,导致两者的引用计数始终为1,无法释放。

解决方案:使用 std::weak_ptr
std::weak_ptr是一种弱引用指针,不会增加引用计数,仅观察资源的存在。通过weak_ptr打破循环引用。
修改后的示例:

#include <memory>
#include <iostream>

class Child;  // 前向声明

class Parent {
public:
    std::weak_ptr<Child> child; // 使用 weak_ptr 避免循环
    ~Parent() { std::cout << "Parent 析构\n"; }
};

class Child {
public:
    std::weak_ptr<Parent> parent;  // 使用 weak_ptr 避免循环
    ~Child() { std::cout << "Child 析构\n"; }
};

int main() {
    auto parent = std::make_shared<Parent>();
    auto child = std::make_shared<Child>();
    parent->child = child;
    child->parent = parent;

    return 0;  // parent 和 child 正确析构
}

输出:
请添加图片描述

五、std::weak_ptr的使用

1. std::weak_ptr 的核心特性

  • 弱引用:不增加引用计数,不拥有资源的所有权。
  • 观察资源:用于观察由 shared_ptr 管理的资源是否存在。
  • 安全访问:通过 lock() 方法获取临时 shared_ptr,以安全访问资源。
  • 解决循环引用:打破 shared_ptr 之间的循环依赖,避免内存泄漏。

2. 基本用法
头文件

#include <memory>

创建 weak_ptr
必须从 shared_ptr 初始化:

auto sharedPtr = std::make_shared<int>(42);
std::weak_ptr<int> weakPtr(sharedPtr);  // 从 shared_ptr 初始化

检查资源是否存在:

if (weakPtr.expired()) {
    std::cout << "资源已释放\n";
} else {
    std::cout << "资源仍存在\n";
}

安全访问资源
通过 lock() 转换为 shared_ptr:

if (auto sharedPtr = weakPtr.lock()) {
    std::cout << "资源值: " << *sharedPtr << "\n";  // 安全访问
} else {
    std::cout << "资源已释放\n";
}

3. 应用场景
观察者模式
观察者持有被观察对象的弱引用,避免延长其生命周期:

class Observer {
public:
    void observe(const std::shared_ptr<int>& subject) {
        subject_ = subject;  // 存储 weak_ptr
    }

    void check() {
        if (auto subject = subject_.lock()) {
            std::cout << "当前值: " << *subject << "\n";
        } else {
            std::cout << "对象已释放\n";
        }
    }

private:
    std::weak_ptr<int> subject_;
};

int main() {
    auto data = std::make_shared<int>(42);
    Observer observer;
    observer.observe(data);
    observer.check();  // 输出 "当前值: 42"

    data.reset();       // 释放数据
    observer.check();   // 输出 "对象已释放"
    return 0;
}

缓存系统
缓存条目使用 weak_ptr,当资源不再被使用时自动释放:

#include <unordered_map>
#include <memory>

class Cache {
public:
    std::shared_ptr<int> get(int key) {
        auto it = cache_.find(key);
        if (it != cache_.end()) {
            if (auto res = it->second.lock()) {
                return res;  // 资源存在,返回共享指针
            } else {
                cache_.erase(it);  // 清理无效条目
            }
        }
        auto res = std::make_shared<int>(key * 10);
        cache_[key] = res;  // 存储 weak_ptr
        return res;
    }

private:
    std::unordered_map<int, std::weak_ptr<int>> cache_;
};

int main() {
    Cache cache;
    auto val1 = cache.get(1);  // 创建并缓存资源
    auto val2 = cache.get(1);  // 返回同一资源,引用计数为2
    val1.reset();              // 引用计数减1
    auto val3 = cache.get(1);  // 资源仍存在,引用计数恢复为2
    return 0;
}

4. 注意事项

  • 不可直接解引用:必须通过 lock() 转换为 shared_ptr 后访问资源。
  • 避免悬垂指针:确保 weak_ptr 的 lock() 结果在使用前有效。
  • 性能开销:lock() 和 expired() 需要检查资源状态,可能影响性能。
  • 线程安全:多线程中需同步访问 weak_ptr 和对应的 shared_ptr。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值