智能指针总结

目录

什么是智能指针

原始指针的问题

引入智能指针的原因

智能指针的原理

share_ptr

weak_ptr

unique_ptr

=default

=delete


  • 什么是智能指针

从比较简单的层面来看,智能指针是RAII(Resource Acquisition Is Initialization,资源获取即初始化)机制对普通指针进行的一层封装。这样使得智能指针的行为动作像一个指针,本质上却是一个对象,这样可以方便管理一个对象的生命周期。

在c++中,智能指针一共定义了4种:

auto_ptr、unique_ptr、shared_ptr 和 weak_ptr。其中,auto_ptr 在 C++11已被摒弃,在C++17中已经移除不可用。

生命周期:每一个实例从创建到销毁的过程

  • 原始指针的问题

原始指针的问题大家都懂,就是如果忘记删除,或者删除的情况没有考虑清楚,容易造成悬挂指针(dangling pointer)或者说野指针(wild pointer)。

野指针:指针指向了一块没有分配给用户使用的内存

  • 引入智能指针的原因

C++中不像java自带垃圾回收机制,必须释放掉分配的内存,否则就会造成内存泄漏。以此c++中引入了智能指针。智能指针是存储指向动态分配(堆)对象指针的类,用于生存期的控制,能够确保在离开指针所在作用域时,自动销毁分配的对象,防止内存泄漏。智能指针的核心实现技术是引用计数,每使用一次内部引用计数加一,每析构一个内部引用次数减一,减为0时,删除原始指针指向的堆区内存,使用智能指针需要引用头文件<memory>

    • 可能会导致内存泄漏出现的问题
  • 异常安全问题。资源申请结束之后,由于异常需要捕获,所以执行流不会按顺序进行,导致资源还没有释放和关闭,就跳转到别的地方执行了。
  • 忘记释放和关闭

以上两种问题会导致内存泄露出现

  • 智能指针的原理
    • RALL思想的理解
  • RAII是一种了利用对象生命周期来控制程序资源(如:内存,文件,套接字,互斥量等)的技术
  • 在对象构造时获得资源,使得堆资源的控制在对象的生命周期内都有效,最后在对象析构的时候释放资源
  • 实际上我们是将资源的管理托管给了一个对象。这样做有两个好处:
    • 不需要显示的释放资源
    • 资源的控制在对象生命周期内都有效

实例:

#pragma once
#include<iostream>
using namespace std;
template<class T>
class SmatrPrt
{
private:
	T* _ptr;
public:
	//将外面申请的资源,托管给类的成员
	SmatrPrt(T* ptr = nullptr)
		:_ptr(ptr)
	{}
 
	//在对象析构时,自动释放资源
	~SmatrPrt()
    {
		if (_ptr)
        {
			cout << "delete ptr" << endl;
			delete _ptr;
		}
 
	}
};


#include "SmartPtr.h"
 
void test()
{
    //将申请的资源托管给了sp对象
    //当对象空间释放会调用析构函数,释放资源
	SmatrPrt<int> sp(new int);
}
 
 
int main()
{
 
	test();
	system("pause");
	return 0;
}

  • share_ptr
  • shared_ptr原理:在类中增加一个成员变量用来计数。每次调用一次拷贝构造和赋值重载函数,即每增加一个管理者,计数加1。每析构一个管理者对象,计数器减1,直到减为0,才真正释放资源
  • shared_ptr内部,给每一份资源维护了一个计数器,用来记录该份资源被几个对象共享
  • 在对象被销毁,也就是调用析构时,说明不在管理该资源,计数器减1
  • 如果引用计数等于0,说明当前对象是最后一个使用该资源的对象,必须释放资源
  • 当引用计数不等于0,说明还有对象在管理资源,不能释放资源
  • 对于shared_ptr的思考:

1.计数器如何设置:

  • 由于当前计数器属于同一资源的对象,可以将计数器设计成一个指针
  • 不能设计成引用,引用要引用外部变量,需要外部管理。也不能设计成静态成员变量,静态成员变量,属于整个类,如果当前类要同时管理其它资源计数器就乱了

2.线程安全问题:

  • 由于引用了一个计数器。计数器自增(++),自减(–)和判断的行为不是原子的。当多个线程进入,会导致线程安全问题。从而导致资源没有释放,造成内存泄漏
  • 为了保证线程安全,可以在shared_ptr类中,再增加一把锁。由于同一份资源的对象要看到同一把锁,所以,锁也需要声明成指针类型

    • 初始化
  • 共享智能指针是指多个智能指针可以同时管理同一块有效的内存,共享智能指针share_ptr是一个模板类,若进行初始化有三种方式如下:
      • 通过构造函数初始化
      • std::make_shared辅助函数
      • reset方法
  • 共享智能指针对象初始化完毕之后就指向了要管理的那块堆区内存,若想要查看有多少个智能指针同时管理着这块内存,可以使用共享智能指针提供的一个成员函数use_count。
  • 构造函数初始化

#include <iostream>
#include<memory>
using namespace std;
 
int main() {
//使用智能指针管理一块int型的堆内存
shared_ptr<int> ptr1(new int(520));
cout << "ptr1管理的内存引用计数" << ptr1.use_count() << endl;
//使用智能指针管理一块字符数组对应的堆内存
shared_ptr<char> ptr2(new char[520]);
cout << "ptr2管理的内存引用计数" << ptr2.use_count() << endl;
shared_ptr<int> ptr3;
cout << "ptr1管理的内存引用计数" << ptr3.use_count() << endl;
//创建智能指针对象,初始化为空
shared_ptr<int> ptr4(nullptr);
cout << "ptr1管理的内存引用计数" << ptr4.use_count() << endl;
return 0;
}

  • 若智能指针被初始化了一块有效内存,那么这块内存的引用次数加一,若智能指针没有被初始化或者初始化为nullptr空指针,引用次数为0,另外,不要使用一个原始指针初始化多个shared_ptr。
  • 拷贝和移动构造函数初始化
      • 当一个智能指针被初始化之后,就可以通过这个智能指针初始化其他新对象。咋创建新对象的时候,对应的拷贝构造函数或者移动构造函数就被调用了。

 #include <iostream>
#include<memory>
using namespace std;
 
int main() {
//构造函数
shared_ptr<int> ptr1(new int(520));
cout << "ptr1管理的内存引用计数" << ptr1.use_count() << endl;
//拷贝构造函数
shared_ptr<int> ptr2(ptr1);
cout << "ptr2管理的内存引用计数" << ptr2.use_count() << endl;
shared_ptr<int> ptr3 = ptr1;
cout << "ptr3管理的内存引用计数" << ptr3.use_count() << endl;
//移动构造函数
shared_ptr<int> ptr4(std::move(ptr1));
cout << "ptr4管理的内存引用计数" << ptr4.use_count() << endl;
std::shared_ptr<int> ptr5 = std::move(ptr2);
cout << "ptr5管理的内存引用次数" << ptr5.use_count() << endl;
return 0;
}

  • std::make_shared初始化
      • 通过c++11提供的std::make_shared()就可以完成内存对象的创建并将其初始化给智能指针

  #include <iostream>
#include<memory>
#include<string>
using namespace std;
 
class Test {
public:
Test() {
cout << "无参构造函数" << endl;
}
Test(int x) {
cout << "int类型构造函数" << x << endl;
}
Test(string str) {
cout << "string类型构造函数" << str << endl;
}
~Test() {
cout << "析构函数" << endl;
}
};
 
int main() {
//使用智能指针管理一块int型的内存内部引用次数为1
shared_ptr<int> ptr1 = make_shared<int>(520);
cout << "ptr1管理的内存引用计数" << ptr1.use_count() << endl;
 
shared_ptr<Test> ptr2 = make_shared<Test>();
cout << "ptr2管理的内存引用计数" << ptr2.use_count() << endl;
 
shared_ptr<Test> ptr3 = make_shared<Test>(520);
cout << "ptr3管理的内存引用计数" << ptr3.use_count() << endl;
 
shared_ptr<Test> ptr4 = make_shared<Test>("QQQQ");
cout << "ptr4管理的内存引用计数" << ptr4.use_count() << endl;
return 0;
}

  • 若使用拷贝的方式初始化共享智能指针,这两个对象会同时管理同一块内存,堆内存对应的引用计数也会增加,若使用移动构造的方式初始化智能指针对象,只是转让了内存的所有权,管理内存的对象不会增加,因此内存引用计数不会增加
  • reset方法初始化

#include <iostream>
#include<memory>
#include<string>
using namespace std;
 
int main() {
//使用智能指针管理一块int型的内存内部引用次数为1
shared_ptr<int> ptr1 = make_shared<int>(520);
shared_ptr<int> ptr2 = ptr1;
shared_ptr<int> ptr3 = ptr1;
shared_ptr<int> ptr4 = ptr1;
cout << "ptr1管理的内存引用计数" << ptr1.use_count() << endl;
cout << "ptr2管理的内存引用计数" << ptr2.use_count() << endl;
cout << "ptr3管理的内存引用计数" << ptr3.use_count() << endl;
cout << "ptr4管理的内存引用计数" << ptr4.use_count() << endl;
cout << endl;
ptr4.reset();
cout << "ptr1管理的内存引用计数" << ptr1.use_count() << endl;
cout << "ptr2管理的内存引用计数" << ptr2.use_count() << endl;
cout << "ptr3管理的内存引用计数" << ptr3.use_count() << endl;
cout << "ptr4管理的内存引用计数" << ptr4.use_count() << endl;
cout << endl;
shared_ptr<int> ptr5;
ptr5.reset(new int(250));
cout << "ptr5管理的内存引用计数" << ptr5.use_count() << endl;
return 0;
}

  • 对于一个未初始化的共享智能指针,可以通过reset方法来初始化,当智能指针中有值的时候,调用reset会引起计数减一
  • 获取原始指针
      • get()函数

 #include <iostream>
#include<memory>
using namespace std;
 
int main() {
shared_ptr<int> p(new int);
*p = 100;
cout << *p.get() << " " << *p << endl;
return 0;
}

  • share_ptr的缺陷
    • 会出现循环引用问题,即当循环引用时,shared_ptr会出现资源没有释放的问题
  • 对于缺陷的解决方法
    • C++11增加了一个weak_ptr专门来解决shared_ptr循环引用的问题
    • 原理是:weak_ptr的成员变量是shared_ptr,不在具有RAII的思想,只是具有指针的功能,也就是,不会释放资源,也不会将计数器计数
    • 使用weak_ptr前提是,知道是循环引用了

  • weak_ptr
  • 弱引用智能指针std::weak_ptr可以看作是share_ptr的助手,它不管理share_ptr内部的指针,std::weak_ptr没有重操作符*和->,因为它不共享指针,不能操作资源,所以它的构造不会增加引用计数,析构也不会减少引用计数,它的主要作用计数作为一个旁观者监视shared_ptr中管理的资源是否存在
    • 初始化

#include <iostream>
#include<memory>
using namespace std;
 
int main() {
shared_ptr<int> sp(new int);
//构造了一个空 weak_ptr 对象
weak_ptr<int> wp1;
//通过一个空 weak_ptr 对象构造了另一个空 weak_ptr 对象
weak_ptr<int> wp2(wp1);
//通过一个 shared_ptr 对象构造了一个可用的 weak_ptr 实例对象
weak_ptr<int> wp3(sp);
//通过一个 shared_ptr 对象构造了一个可用的 weak_ptr 实例对象(这是一个隐式类型转换)
weak_ptr<int> wp4;
wp4 = sp;
//通过一个 weak_ptr 对象构造了一个可用的 weak_ptr 实例对象
weak_ptr<int> wp5;
wp5 = wp3;
return 0;
}

  • use_count()
    • 通过调用std::weak_ptr类提供的use_count()方法可以获得当前所观测资源的引用计数

 #include <iostream>
#include<memory>
using namespace std;
 
int main() {
shared_ptr<int> sp(new int);
//构造了一个空 weak_ptr 对象
weak_ptr<int> wp1;
//通过一个空 weak_ptr 对象构造了另一个空 weak_ptr 对象
weak_ptr<int> wp2(wp1);
//通过一个 shared_ptr 对象构造了一个可用的 weak_ptr 实例对象
weak_ptr<int> wp3(sp);
//通过一个 shared_ptr 对象构造了一个可用的 weak_ptr 实例对象(这是一个隐式类型转换)
weak_ptr<int> wp4;
wp4 = sp;
//通过一个 weak_ptr 对象构造了一个可用的 weak_ptr 实例对象
weak_ptr<int> wp5;
wp5 = wp3;
cout << "use_count:" << endl;
cout << "wp1 " << wp1.use_count() << endl;
cout << "wp2 " << wp2.use_count() << endl;
cout << "wp3 " << wp3.use_count() << endl;
cout << "wp4 " << wp4.use_count() << endl;
cout << "wp5 " << wp5.use_count() << endl;
return 0;
}

  • 通过打印的结果可以知道,虽然弱引用智能指针wp3,wp4,wp5监测的资源是同一个,但是它的引用计数并没有发生任何变化,也进一步证明了weak_ptr只是检测资源,并不是管理资源
  • expired()
    • 通过调用std::weak_ptr类提供的expired()方法来判断观测的资源是否已经被释放

 #include <iostream>
#include<memory>
using namespace std;
 
int main() {
shared_ptr<int> shared(new int(10));
weak_ptr<int> weak(shared);
cout << "1.weak " << (weak.expired() ? "is " : "is not ") << "expired" << endl;
shared.reset();
cout << "2.weak " << (weak.expired() ? "is " : "is not ") << "expired" << endl;
return 0;
}

  • weak_ptr监测的就是shared_ptr管理的资源,当共享智能指针调用shared.reset();之后,管理的资源被释放,因此weak.expired()函数的结果返回true,表示监测的资源已经不存在了。
  • lock()
    • 通过调用std::weak_ptr类提供的lock()方法来获取管理所监视资源的shared_ptr对象

 #include <iostream>
#include<memory>
using namespace std;
 
int main() {
shared_ptr<int> sp1, sp2;
weak_ptr<int> wp;
sp1 = std::make_shared<int>(520);
wp = sp1;
sp2 = wp.lock();
cout << "use_count " << wp.use_count() << endl;
sp1.reset();
cout << "use_count " << wp.use_count() << endl;
sp1 = wp.lock();
cout << "use_count " << wp.use_count() << endl;
cout << "*sp1 " << *sp1 << endl;
cout << "*sp2 " << *sp2 << endl;
return 0;
}

  • sp2 = wp.lock();通过调用lock()方法得到一个用于管理weak_ptr对象所监测的资源的共享智能指针对象,使用这个对象初始化sp2,此时所监测组员的引用计数为2
  • sp1.reset();共享智能指针sp1被重置,weak_ptr对象所监测的资源的引用计数减一
  • sp1 = wp.lock();sp1重新被初始化,并且管理的还是weak_ptr对象所监测的资源,因此引用计数加一
  • 共享智能指针对象sp1和sp2管理的是同一块内存,因此最终打印的内存中的结果都是相同的
  • reset()
    • 通过调用std::weak_ptr类提供的reste()方法来清空对象,使其不监测任何资源

  #include <iostream>
#include<memory>
using namespace std;
 
int main() {
shared_ptr<int> sp(new int(10));
weak_ptr<int> wp(sp);
cout << "1.wp " << (wp.expired() ? "is " : "is not ") << "expired" << endl;
wp.reset();
cout << "2.wp " << (wp.expired() ? "is " : "is not ") << "expired" << endl;
return 0;
}

  • 返回管理thisshare_ptr

#include <iostream>
#include<memory>
using namespace std;
 
struct Test {
shared_ptr<Test> getSharedptr() {
return shared_ptr<Test>(this);
}
~Test() {
cout << "析构函数" << endl;
}
};
 
int main() {
shared_ptr<Test> sp1(new Test);
cout << "引用个数 " << sp1.use_count() << endl;
shared_ptr<Test> sp2 = sp1->getSharedptr();
cout << "引用个数 " << sp1.use_count() << endl;
return 0;
}

  • 通过输出的结果可以看到一个对象被析构了两次,其原因是这样的:在这个例子中使用同一个指针this构造了两个智能指针对象sp1和sp2,这两者之间是没有任何关系的,因为sp2并不是通过sp1初始化得到的对象。在离开作用域之后this将构造的两个智能指针各自析构,导致重复析构的错误

#include <iostream>
#include<memory>
using namespace std;
 
struct Test : public enable_shared_from_this<Test>{
shared_ptr<Test> getSharedptr() {
return shared_from_this();
}
~Test() {
cout << "析构函数" << endl;
}
};
 
int main() {
shared_ptr<Test> sp1(new Test);
cout << "引用个数 " << sp1.use_count() << endl;
shared_ptr<Test> sp2 = sp1->getSharedptr();
cout << "引用个数 " << sp1.use_count() << endl;
return 0;
}

  • 循环引用问题

#include <iostream>
#include<memory>
using namespace std;
 
class B;
 
class A {
public:
shared_ptr<B> bptr;
~A() {
cout << "class TA is disstruct" << endl;
}
};
 
class B {
public:
shared_ptr<A> aptr;
~B() {
cout << "class TB is disstruct" << endl;
}
};
void testPtr() {
shared_ptr<A> ap(new A);
shared_ptr<B> bp(new B);
cout << "A 引用个数 " << ap.use_count() << endl;
cout << "B 引用个数 " << bp.use_count() << endl;
ap->bptr = bp;
bp->aptr = ap;
cout << "A 引用个数 " << ap.use_count() << endl;
cout << "B 引用个数 " << bp.use_count() << endl;
}
 
int main() {
testPtr();
return 0;
}

  • 共享智能指针ap,bp对A,B实例对象的引用计数变为2,在共享智能指针离开作用域之后引用计数只能减为一,这种情况下不会去删除智能指针管理的内存,导致类A,B的实例对象不能被析构,最终造成内存泄漏,通过调用weak_ptr可以解决这个问题,只需要将类A或者类B的任意一个成员改为weak_ptr

#include <iostream>
#include<memory>
using namespace std;
 
class B;
 
class A {
public:
weak_ptr<B> bptr;
~A() {
cout << "A 的析构函数" << endl;
}
};
 
class B {
public:
shared_ptr<A> aptr;
~B() {
cout << "B 的析构函数" << endl;
}
};
void testPtr() {
shared_ptr<A> ap(new A);
shared_ptr<B> bp(new B);
cout << "A 引用个数 " << ap.use_count() << endl;
cout << "B 引用个数 " << bp.use_count() << endl;
ap->bptr = bp;
bp->aptr = ap;
cout << "A 引用个数 " << ap.use_count() << endl;
cout << "B 引用个数 " << bp.use_count() << endl;
}
 
int main() {
testPtr();
return 0;
}

  • 上面程序中,在对类A成员赋值时ap->bptr=bp;由于bptr时weak_ptr类型,这个赋值操作并不会增加引用计数,所以bp的引用计数任然为1,在离开作用域之后bp的引用计数减为0,类B的实例对象会被析构。
  • 在类B的实例对象被析构时,内部的aptr也被析构,其对A对象的管理解除,内存的引用计数减为1,当共享智能指针ap离开作用域之后,对A对象的管理要解除了,内存的引用计数减少为0,类A的实例对象被析构

  • unique_ptr
  • 初始化
  • std::unqiue_ptr是一个独占型的智能指针,它不允许其他的智能指针共享其内部的指针,可以通过它的构造函数初始化一个独占智能指针,但是不允许通过赋值将一个unique_ptr赋值给另一个unique_ptr

#include <iostream>
#include<string>
#include<memory>
using namespace std;
 
int main() {
//通过构造函数初始化对象
unique_ptr<int> ptr1(new int(10));
//报错
unique_ptr<int> ptr2 = ptr1;
return 0;
}

  • unique_ptr不允许被复制,但是可以通过函数返回给其他的unique_ptr,还可以通过std::move()转移给其他的unique_ptr。还是一个unique_ptr独占一个地址。
  • 使用reset方法可以让unique_ptr解除对原始内存的管理,也可以用来初始化一个独占的智能指针

#include <iostream>
#include<string>
#include<memory>
using namespace std;
 
int main() {
unique_ptr<int> ptr1(new int(10));
unique_ptr<int> ptr2;
ptr1.reset();//接触对原始内存的管理
ptr2.reset(new int(250));//重新指定智能指针管理的原始内存
return 0;
}

  • 若想要获取独占智能指针管理的原始内存,可以通过调用get()方法

#include <iostream>
#include<string>
#include<memory>
using namespace std;
 
int main() {
unique_ptr<int> ptr1(new int(10));
unique_ptr<int> ptr2 = move(ptr1);
ptr2.reset(new int(250));
cout << *ptr2.get() << endl;//得到内存地址存储的实际数值 250
return 0;
}

  • =default
  • 可以在类内部修饰满足条件的类函数为显示默认函数,也可以在类定义之外修饰类成员函数为默认函数

class Base {
public:
//指定无参构造为默认函数
Base() = default;
//指定拷贝构造函数为默认函数
Base(const Base& obj) = default;
//指定移动拷贝构造函数为默认函数
Base(Base&& obj) = default;
//指定复制赋值操作符重载函数为默认函数
Base& operator=(const Base& obj) = default;
//指定移动赋值操作符重载函数为默认函数
Base& operator=(Base&& obj) = default;
//指定析构函数为默认函数
~Base() = default;
};

  • 默认函数除了在类定义的内部指定,也可以在类的外部指定

class Base {
public:
//指定无参构造为默认函数
Base();
//指定拷贝构造函数为默认函数
Base(const Base& obj);
//指定移动拷贝构造函数为默认函数
Base(Base&& obj);
//指定复制赋值操作符重载函数为默认函数
Base& operator=(const Base& obj);
//指定移动赋值操作符重载函数为默认函数
Base& operator=(Base&& obj);
//指定析构函数为默认函数
~Base() = default;
};
 
Base::Base() = default;
Base::Base(const Base& obj) = default;
Base::Base(Base&& obj) = default;
Base& Base::operator=(const Base& obj) = default;
Base& Base::operator=(Base&& obj) = default;
Base::~Base() = default;
 
 
class Base {
public:
//指定无参构造为默认函数
Base() = default;
//指定拷贝构造函数为默认函数
Base(const Base& obj) = default;
//指定移动拷贝构造函数为默认函数
Base(Base&& obj) = default;
//指定复制赋值操作符重载函数为默认函数
Base& operator=(const Base& obj) = default;
//指定移动赋值操作符重载函数为默认函数
Base& operator=(Base&& obj) = default;
//指定析构函数为默认函数
~Base() = default;
 
//以下写法全部错误
Base(int a = 0) = default; //自定义带参构造,不允许使用=default修饰(即使有默认参数也不行)
Base(int a, int b) = default; //自定义带参构造,不允许使用=default修饰
void print() = default; //自定义带参构造,不允许使用=default修饰
//下面两行不是移动,复制赋值运算符重载,不容许使用=default修饰
bool operator==(const Base& obj) = default;
bool operator==(const Base& obj) = default;
};

  • 后三行报错如下

  • =delete
  • delete关键字表示显示删除,显示删除可以避免用户使用一些不应该使用的类的成员函数
  • 禁止使用默认生成的函数

class Base {
public:
Base() = default;
Base(const Base& obj) = delete;//禁止拷贝构造函数
Base& operator=(const Base& obj) = delete;//禁用=进行对象复制
};
 
int main() {
Base b;
Base tmp1(b);//报错 拷贝构造函数已被显示删除,无法拷贝对象
Base temp = b;//报错
return 0;
}

  • 禁止使用自定义函数

class Base {
public:
Base(int num) : m_num(num) {}
Base(char c) = delete;//禁止带char类型参数的构造函数,防止隐式转换(char转int)
void print(char c) = delete;//禁止带char类型参数的自定义函数,防止隐式转换(char转int)
void print() {
cout << "num " << m_num << endl;
}
void print(int num) {
cout << "num " << m_num << endl;
}
private:
int m_num;
};
 
int main() {
Base b(97);
Base b1('a');//'a'对应的acscil值为97 报错 对应的构造函数被禁用,因此无法使用该构造函数构造对象
b.print();
b.print(97);
b.print('a');//报错 对应的打印函数被禁用,因此无法给函数传递char类型参数
return 0;
}

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

puzell

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值