该系列文章总纲链接:android 系统核心机制基础 系列文章目录


本章关键点总结 & 说明:

以上是本模块的导图,整体概括了智能指针的几个要点,引用计数,弱转强,flag标志意义以及LightRefBase解读。

RefBase、sp和wp解读:
RefBase是Android中所有对象的始祖,类似于Java中的Object。在Android 中,RefBase结合sp和wp,实现了一套通过引用计数的方法来控制对象生命周期的机制。sp和wp的定义:sp是strong pointer,wp是weak pointer。sp和wp的目的,就是为了帮助健忘的程序员回收new出来的内存。 接下来这里以使用实例进行说明与分析。

1 影子对象

//类A从RefBase派生,RefBase是万物的始祖
class A:public RefBase
{//A没有任何自己的功能
}
int main()
{A* pA =new A;{//注意我们的sp,wp对象是在{}中创建的,下面的代码先创建sp,然后创建wpsp<A> spA(A);wp<A> wpA(spA);...//正常其他的使用spA和wpA操作//大括号结束前,先析构wp,再析构sp}
}

@1 这里分析RefBase构造函数。代码如下:

RefBase::RefBase(): mRefs(new weakref_impl(this))
{//mRefs是RefBase的成员变量,类型是weakref_impl,我们暂且叫它影子对象,所以A有一个影子对象
}

mRefs是引用计数管理的关键类,需要进去观察。它是从RefBase的内部类weakref_type中派生出来的。声明如下:

//RefBase.h中weakref_type的声明
typedef typename RefBase::weakref_type weakref_type;
//RefBase.cpp中定义的派生关系
class RefBase::weakref_impl : public RefBase::weakref_type    //从RefBase的内部类weakref_type派生

C++的内部类和Java内部类相似,不同点是它需要一个显示的成员指向外部类对象。weakref_impl构造实现如下所示:

weakref_impl(RefBase* base): mStrong(INITIAL_STRONG_VALUE) //强引用计数,初始值为0x1000000,即(1<<28), mWeak(0)//弱引用计数,初始值为0, mBase(base))//该影子对象所指向的实际对象, mFlags(0){}

new了一个A对象后,其实还new了一个RefBase::weakref_impl内部类对象,这里称它为影子对象,A为实际对象。影子对象成员中有两个引用计数:一个强引用,一个弱引用。在构造一个实际对象的同时还会悄悄地构造一个影子对象。
@2 程序继续运行,现在到了

sp<A> spA(A);

sp的构造函数,它的代码如下所示(sp是一个模板类,这里列举最关键sp摸板函数):

template<typename T> //根据程序特点,运行的是这个构造器
sp<T>::sp(T* other)  //这里的other就是刚才创建的pA
: m_ptr(other)       // sp保存了pA的指针{if (other) other->incStrong(this);//调用pA的incStrong}
...//其他构造器略过

继续分析RefBase的incStrong中,它的代码如下所示:

void RefBase::incStrong(const void* id) const
{weakref_impl* const refs = mRefs;//mRefs就是刚才RefBase构造函数中new出来的内部影子对象refs->incWeak(id);refs->addStrongRef(id);//这句不考虑,Passconst int32_t c = android_atomic_inc(&refs->mStrong);//原子加1操作,并返回旧值。所以c=0x1000000,而mStrong变为0x1000001ALOG_ASSERT(c > 0, "incStrong() called on %p after last strong ref", refs);if (c != INITIAL_STRONG_VALUE)  { //如果c不是初始值,则表明这个对象已经被强引用过一次了,所以直接返回return;}android_atomic_add(-INITIAL_STRONG_VALUE, &refs->mStrong);//原子加操作,相当于执行refs->mStrong +(-0x1000000),最终mStrong=1refs->mBase->onFirstRef();    //如果是第一次引用,则调用onFirstRef,这个函数很重要,派生类可以重载这个函数,完成一些初始化工作。
}

这里继续分析refs->incWeak的方法,如下所示:

void RefBase::weakref_type::incWeak(const void* id)
{weakref_impl* const impl = static_cast<weakref_impl*>(this);impl->addWeakRef(id);//这句不考虑,Passconst int32_t c __unused = android_atomic_inc(&impl->mWeak);//原子操作,影子对象的弱引用计数加1ALOG_ASSERT(c >= 0, "incWeak called on %p after last weak ref", this);
}

sp构造初始化完成后RefBase中影子对象的强引用计数变为1,弱引用计数也变为1。
特殊说明:

  1. 影子对象的强弱引用计数的值,这是彻底理解sp和wp的关键。
  2. android_atomic_xxx是Android平台提供的原子操作函数,是多线程编程中的常见函数。
  3. 因为这是release版走的分支。debug的代码是给创造RefBase、 sp,以及wp的人调试用的,因此分析流程时不考虑debug相关代码。以下几个函数不用考虑,仅为调试人员使用。

碰到它们,可以直接跳过,不影响流程的分析:

void addWeakRef(const void* /*id*/) { }
void addStrongRef(const void* /*id*/) { }
void removeStrongRef(const void* /*id*/) { }
void addWeakRef(const void* /*id*/) { }
void removeWeakRef(const void* /*id*/) { }
void printRefs() const { }
void trackMe(bool, bool) { }

@3 程序继续运行,现在到了

wp<A> wpA(spA)

wp有好几个构造函数,这里仅列出最关键的,如下所示:

template<typename T>wp<T>::wp(T* other): m_ptr(other)//wp的成员变量m_ptr指向实际对象
{if (other) m_refs = other->createWeak(this);//调用pA的createWeak,并且保存返回值到成员变量m_refs中
}
...//其他构造器略过

继续分析creatWeak,代码如下:

RefBase::weakref_type* RefBase::createWeak(const void* id) const
{mRefs->incWeak(id);//影子对象的弱引用计数增加1return mRefs;      //返回影子对象
}

wp构造初始化完成后影子对象的弱引用计数将增加1。
@4 构造初始化结束总结:

  1. 现在弱引用计数为2,而强引用计数仍为1。
  2. wp中有两个成员变量,一个保存实际对象,另一个保存影子对象。
  3. sp只有一个成员变量用来保存实际对象,但这个实际对象内部已包含了对应的影子对象
  4. 正常使用该sp化和wp化是引用计数不变的,当销毁对象时,进入到析构方法时才会发生变化。

接下来开始进行析构的流程
@5 wp的析构,析构方法如下所示:

template<typename T>
wp<T>::~wp()
{if (m_ptr) m_refs->decWeak(this);
}

继续分析decWeak,代码如下:

void RefBase::weakref_type::decWeak(const void* id)
{//把基类指针转换成子类(影子对象)的类型,这种做法有些违背面向对象编程的思想weakref_impl* const impl = static_cast<weakref_impl*>(this);impl->removeWeakRef(id);//这句不考虑,Passconst int32_t c = android_atomic_dec(&impl->mWeak);  //原子减1,返回旧值,c=2,而弱引用计数从2变为1ALOG_ASSERT(c >= 1, "decWeak called on %p too many times", this);if (c != 1) return;//c=2,直接返回//如果c为1,则弱引用计数为0,这说明没有弱引用指向实际对象,需要考虑是否释放内存// OBJECT_LIFETIME_XXX和生命周期有关系,1.3中会对此做详细解释//这里OBJECT_LIFETIME_WEAK=0,OBJECT_LIFETIME_STRONG=1if ((impl->mFlags&OBJECT_LIFETIME_WEAK) == OBJECT_LIFETIME_STRONG) {if (impl->mStrong == INITIAL_STRONG_VALUE) {delete impl->mBase;} else {delete impl;}} else {impl->mBase->onLastWeakRef(id);if ((impl->mFlags&OBJECT_LIFETIME_MASK) == OBJECT_LIFETIME_WEAK) {delete impl->mBase;}}
}

这里因为之前sp和wp分别执行构造器,所以,弱引用计数为2。wp析构后,弱引用计数减1。但由于此时强引用计数和弱引用计数仍为1,所以没有对象被干掉,即没有释放实际对象和影子对象占据的内存。
@6 sp的析构,析构方法如下所示:

template<typename T>
sp<T>::~sp()
{if (m_ptr) m_ptr->decStrong(this);//调用实际对象的decStrong。由RefBase实现
}

继续分析decStrong,代码如下:

void RefBase::decStrong(const void* id) const
{weakref_impl* const refs = mRefs;refs->removeStrongRef(id);//这句不考虑,Passconst int32_t c = android_atomic_dec(&refs->mStrong);//注意,此时强弱引用计数都是1,下面函数调用的结果是c=1,强引用计数为0ALOG_ASSERT(c >= 1, "decStrong() called on %p too many times", refs);if (c == 1) {//对于我们的例子, c为1refs->mBase->onLastStrongRef(id);//调用onLastStrongRef,表明强引用计数减为0,对象有可能被delete//mFlags为0,所以会通过delete this把自己干掉。注意,此时弱引用计数仍为1// OBJECT_LIFETIME_XXX和生命周期有关系,1.3中会对此做详细解释if ((refs->mFlags&OBJECT_LIFETIME_MASK) == OBJECT_LIFETIME_STRONG) {delete this;}}//注意,实际数据对象已经被干掉了,所以mRefs也没有用了,但是decStrong刚进来时就保存mRefs到refs了,所以这里的refs指向影子对象refs->decWeak(id);//调用影子对象decWeak
}

先看delete this的处理,它会导致A的析构函数被调用,A的析构函数,代码如下所示:

//A是继承RefBase的,因此,A的析构直接导致进入RefBase的析构。
RefBase::~RefBase()
{if (mRefs->mStrong == INITIAL_STRONG_VALUE) {delete mRefs;} else {if ((mRefs->mFlags & OBJECT_LIFETIME_MASK) != OBJECT_LIFETIME_STRONG) {if (mRefs->mWeak == 0) { //这里弱引用计数不为0,而是1delete mRefs;}}}const_cast<weakref_impl*&>(mRefs) = NULL;
}

RefBase的delete this自杀行为没有把影子对象销毁,但还在decStrong中,可接着从delete this往下看,到影子对象的decWeak方法,代码如下:

void RefBase::weakref_type::decWeak(const void* id)
{weakref_impl* const impl = static_cast<weakref_impl*>(this);impl->removeWeakRef(id);//调用前影子对象的弱引用计数为1,强引用计数为0,调用结束后c=1,弱引用计数为0const int32_t c = android_atomic_dec(&impl->mWeak);ALOG_ASSERT(c >= 1, "decWeak called on %p too many times", this);    if (c != 1) return;// OBJECT_LIFETIME_XXX和生命周期有关系,1.3中会对此做详细解释//这次弱引用计数终于变为0,并且mFlags为0, mStrong也为0。if ((impl->mFlags&OBJECT_LIFETIME_WEAK) == OBJECT_LIFETIME_STRONG) {if (impl->mStrong == INITIAL_STRONG_VALUE) {delete impl->mBase;} else {delete impl;//impl就是this,把影子对象自己干掉}} else {impl->mBase->onLastWeakRef(id);if ((impl->mFlags&OBJECT_LIFETIME_MASK) == OBJECT_LIFETIME_WEAK) {delete impl->mBase;}}
}

@7 总结

  1. RefBase中有一个隐含的影子对象weakRefImpl,该影子对象内部有强弱引用计数。
  2. sp化后,强弱引用计数各增加1,sp析构后,强弱引用计数各减1。
  3. wp化后,弱引用计数增加1,wp析构后,弱引用计数减1。

完全彻底地消灭RefBase对象,包括让实际对象和影子对象灭亡,这些都是由强弱引用计数控制的,另外还要考虑flag的取值情况。当flag为0时,可得出如下结论:

  1. 强引用为0将导致实际对象被delete。
  2. 弱引用为0将导致影子对象被delete。

2 弱引用变成强引用

这里以使用实例进行说明与分析:

int main()
{A *pA =new A();wp<A> wpA(A);sp<A> spA = wpA.promote();//通过promote函数,得到一个sp
}

wp化后弱引用计数加1,所以此处wp化的结果是:影子对象的弱引用计数为1,强引用计数仍然是初始值0x1000000,wpA的promote函数是从一个弱对象产生一个强对象的重要函数,试看:

@1 由弱引用变成强引用的方法解析

template<typename T>sp<T> wp<T>::promote() const
{sp<T> result;if (m_ptr && m_refs->attemptIncStrong(&result)) {result.set_pointer(m_ptr);//调用sp的构造函数}return result;
}

这里分析set_pointer,代码如下:

template<typename T>
void sp<T>::set_pointer(T* ptr) {m_ptr = ptr;
}

@2 由弱引用变成强引用的关键函数是attemptIncStrong,它的代码如下所示:

bool RefBase::weakref_type::attemptIncStrong(const void* id)
{incWeak(id);//增加弱引用计数,此时弱引用计数变为2weakref_impl* const impl = static_cast<weakref_impl*>(this);int32_t curCount = impl->mStrong;//这个仍是初始值ALOG_ASSERT(curCount >= 0,"attemptIncStrong called on %p after underflow", this);//该循环在多线程操作同一个对象时可能会循环多次。这里可以不去管它,目的就是使强引用计数增加1while (curCount > 0 && curCount != INITIAL_STRONG_VALUE) {if (android_atomic_cmpxchg(curCount, curCount+1, &impl->mStrong) == 0) {break;}curCount = impl->mStrong;}// OBJECT_LIFETIME_XXX和生命周期有关系,1.3中会对此做详细解释if (curCount <= 0 || curCount == INITIAL_STRONG_VALUE) {if ((impl->mFlags&OBJECT_LIFETIME_WEAK) == OBJECT_LIFETIME_STRONG) {if (curCount <= 0) {decWeak(id);    //弱引用-1return false;}while (curCount > 0) {if (android_atomic_cmpxchg(curCount, curCount + 1,&impl->mStrong) == 0) {break;}curCount = impl->mStrong;}if (curCount <= 0) {decWeak(id);    //弱引用-1并返回false,表示强引用计数增加失败return false;}} else {if (!impl->mBase->onIncStrongAttempted(FIRST_INC_STRONG, id)) {decWeak(id);    //弱引用-1并返回false,表示强引用计数增加失败return false;}curCount = android_atomic_inc(&impl->mStrong);}if (curCount > 0 && curCount < INITIAL_STRONG_VALUE) {impl->mBase->onLastStrongRef(id);//交给派生类处理}}impl->addStrongRef(id);//这句不考虑,PasscurCount = impl->mStrong;while (curCount >= INITIAL_STRONG_VALUE) {ALOG_ASSERT(curCount > INITIAL_STRONG_VALUE,"attemptIncStrong in %p underflowed to INITIAL_STRONG_VALUE",this);if (android_atomic_cmpxchg(curCount, curCount-INITIAL_STRONG_VALUE,&impl->mStrong) == 0) {break;}curCount = impl->mStrong;}return true;
}

这里对android_atomic_cmpxchg进行说明:android_atomic_cmpxchg(oldValue, newValue,&impl->mstrong);

逻辑解析:如果impl->mstrong == oldValue,则impl->mstrong == newValue,返回1;否则返回0;        
总结:promote完成后,相当于增加了一个强引用。由弱生强成功后,强弱引用计数均增加1。当前影子对象的强引用计数为1,弱引用计数为2。

同时这里对RefBase的extendObjectLifetime方法解读(解释OBJECT_LIFETIME_WEAK和OBJECT_LIFETIME_STRONG),RefBase中定义了一个关键枚举和一个方法extendObjectLifetime:

 //! Flags for extendObjectLifetime()enum {OBJECT_LIFETIME_STRONG  = 0x0000,OBJECT_LIFETIME_WEAK    = 0x0001,OBJECT_LIFETIME_MASK    = 0x0001};

观察flags为OBJECT_LIFETIME_WEAK的情况,见实例代码:

class A:public RefBase
{publicA(){extendObjectLifetime(OBJECT_LIFETIME_WEAK);//在构造函数中调用}
}int main()
{A *pA =new A();wp<A> wpA(A);//弱引用计数加1{sp<A>spA(pA) //sp后,结果是强引用计数为1,弱引用计数为2}
....
}

@@2.1 sp的析构将直接调用RefBase的decStrong,它的代码如下所示:

void RefBase::decStrong(const void* id) const
{weakref_impl* const refs = mRefs;refs->removeStrongRef(id);//这句不考虑,Passconst int32_t c = android_atomic_dec(&refs->mStrong);//注意,此时强引用计数都是1,弱引用计数是2,下面函数调用的结果是c=1,强引用计数为0ALOG_ASSERT(c >= 1, "decStrong() called on %p too many times", refs);if (c == 1) {//对于我们的例子, c为1refs->mBase->onLastStrongRef(id);//调用onLastStrongRef,表明强引用计数减为0,对象有可能被delete//mFlags为0,所以会通过delete this把自己干掉。注意,此时弱引用计数仍为1//注意这句话。如果flags不是WEAK,将直接delete数据对象,现在我们的flags是WEAK,所以不会delete它if ((refs->mFlags&OBJECT_LIFETIME_MASK) == OBJECT_LIFETIME_STRONG) {delete this;}}refs->decWeak(id);//调用影子对象decWeak,调用前引用计数为2
}

@@2.2 调用影子对象的decWeak。再来看它的处理,代码如下所示:

void RefBase::weakref_type::decWeak(const void* id)
{weakref_impl* const impl = static_cast<weakref_impl*>(this);impl->removeWeakRef(id);//调用前影子对象的弱引用计数为1,强引用计数为0,调用结束后c=1,弱引用计数为0const int32_t c = android_atomic_dec(&impl->mWeak);ALOG_ASSERT(c >= 1, "decWeak called on %p too many times", this);if (c != 1) return;    //c为2,弱引用计数为1,直接返回。//若到了wp析构之处,也会调用decWeak,调用上边的原子减操作后c=1,弱引用计数变为0,此时会继续往下运行。//由于mFlags为WEAK ,所以不满足if的条件if ((impl->mFlags&OBJECT_LIFETIME_WEAK) == OBJECT_LIFETIME_STRONG) {if (impl->mStrong == INITIAL_STRONG_VALUE) {delete impl->mBase;} else {delete impl;//impl就是this,把影子对象自己干掉}} else {//会走这里impl->mBase->onLastWeakRef(id);//由于flags值满足下面这个条件,所以实际对象会被delete,实际对象的delete会检查影子对象的弱引用计数,若它为0,则会把影子对象也delete掉。//由于影子对象的弱引用计数此时已经为0,所以影子对象也会被delete。if ((impl->mFlags&OBJECT_LIFETIME_MASK) == OBJECT_LIFETIME_WEAK) {delete impl->mBase;}}
}

@3 总结
@@3.1 flags为LIFETIME_STRONG(即0),强引用计数控制实际对象的生命周期,弱引用计数控制影子对象的生命周期。强引用计数为0后,实际对象被delete。所以对于这种情况,应记住的是,使用wp时要由弱生强,以免收到segment fault信号。
@@3.2 flags为LIFETIME_WEAK(即1)强引用计数为0,弱引用计数不为0时,实际对象不会被delete。当弱引用计数减为0时,实际对象和影子对象会同时被delete。

3 轻量级的引用计数控制LightRefBase
RefBase是一个重量级的引用计数控制类。Android为我们提供了一个轻量级的RefBase,即LightRefBase。
@1 LightRefBase的实现,查看其代码:

template <class T>
class LightRefBase
{
public:inline LightRefBase() : mCount(0) { }inline void incStrong(__attribute__((unused)) const void* id) const {//LightRefBase只有一个引用计数控制量mCount。incStrong的时候使它增加1android_atomic_inc(&mCount);}inline void decStrong(__attribute__((unused)) const void* id) const {//decStrong的时候减1,当引用计数变为零的时候,delete掉自己if (android_atomic_dec(&mCount) == 1) {delete static_cast<const T*>(this);}}//! DEBUGGING ONLY: Get current strong ref count.inline int32_t getStrongCount() const {return mCount;}typedef LightRefBase<T> basetype;protected:inline ~LightRefBase() { }private:friend class ReferenceMover;inline static void renameRefs(size_t n, const ReferenceRenamer& renamer) { }inline static void renameRefId(T* ref,const void* old_id, const void* new_id) { }private:mutable volatile int32_t mCount;//引用计数控制变量
};

@2 使用实例(LightRefBase是一个模板类,其中类A是从LightRefBase派生,写法如下:

class A:public LightRefBase<A> //注意派生的时候要指明是LightRefBase<A>
{
public:
A(){};
~A(){};
};

从LightRefBase的定义中可以知道,它支持sp控制,因为它只有incStrong和decStrong。

android系统核心机制 基础(01)智能指针wp sp相关推荐

  1. 【C++】Android (Light)RefBase-sp-wp引用计数-智能指针源码分析

    文章目录 1.RefBase简介 2.RefBase源码分析 3.RefBase使用注意事项 4.总结 1.RefBase简介 什么是RefBase?RefBase是Android中的一个C++类,用 ...

  2. VTK修炼之道80:VTK开发基础_智能指针与引用计数

    1.引用计数 VTK经过多年的开发与维护,已经形成了一套稳定的框架和开发规则.因此,了解这些规则和框架是定制VTK类的基础,这其中用到了大量面向对象的设计模式,例如对象工程模式.观察者/命令模式:还有 ...

  3. android ndk r8 mac,c – 智能指针不适用于Android NDK r8

    我不知道如何在我的 Android项目中使用共享指针.我在Mac OS X上使用最新的Eclipse ADT与Android NDK r8d. 这是我的Android.mk文件中的内容: LOCAL_ ...

  4. Android系统核心机制之APP启动的程序入口ActivityThread的简单介绍

    Android中为什么主线程不会因为Looper.loop()里的死循环阻塞? 标题是伪命题 参考资料 Android中为什么主线程不会因为Looper.loop()里的死循环卡死? 知乎 之前对这个 ...

  5. C++入门基础01:指针与引用

    C++入门基础:指针与引用 指针 #include <iostream> //系统定义头文件一般是尖括号 #include<fstream> #include<strin ...

  6. Android 智能指针 视频,Android系统智能指针中轻量级指针

    lp.sp.wp在Android Native层中被大量使用,所以非常有必要学习它们的实现原理.lp是Light Pointer的缩写,表示轻量级指针,sp是Strong Pointer的缩写,表示强 ...

  7. Android智能指针——读书笔记

    目录结构 目录结构 参考资料 概述 背景知识 GC经典问题 轻量级指针 实现原理分析 构造函数 析构函数 应用实例分析 强指针和弱指针 强指针的实现原理分析 增加对象的弱引用计数 增加对象的强引用计数 ...

  8. Android系统原理性问题分析 - RefBase、sp、wp 分析

    声明 在Android系统中经常会遇到一些系统原理性的问题,在此专栏中集中来讨论下. 接触Android系统,遇到很多sp.wp相关问题,此篇分析Android系统内的智能指针问题. 此篇参考一些博客 ...

  9. C++智能指针详解【C++智能指针】

    自动内存管理 智能指针 什么是 RAII 原理 智能指针的模板(template)实现 auto_ptr auto_ptr 使用 重载函数 operator-> / *语法格式 自实现 auto ...

最新文章

  1. KMM 搭建环境,并运行安卓和ios
  2. Windbg/KD驱动调试点滴–将平时调试的一些小方法共享给大家 --------- 转
  3. 华为ipd产品开发流程_华为集成产品开发(IPD)流程的解读
  4. python tkinter计算器实例_python -Tkinter 实现一个小计算器功能
  5. MongoDB在Linux下常用优化设置
  6. 光伏项目用地政策解析
  7. 【计算机图形学】画线算法——中点画线算法
  8. 网页版模仿Excel
  9. 一个简单的姓名生成器
  10. 软工网络15团队作业4——Alpha阶段敏捷冲刺之Scrum 冲刺博客(Day6)
  11. 面试技巧---白话文
  12. 微信公众号运营助手,可以在手机上回复粉丝留言
  13. 一部分使用CNES后处理BIA产品的PPP-AR结果
  14. 用java实现从txt文本文件批量导入数据至数据库
  15. Android5.0以上系统的移动网络开关
  16. Linux:系统进程---->查看命令【ps:静态查看进程】【top:动态查看进程】
  17. 软件测试-制定测试策略
  18. OpenAI-ChatGPT最新官方接口《审核机制》全网最详细中英文实用指南和教程,助你零基础快速轻松掌握全新技术(七)(附源码)
  19. 设计模式(一) 工厂模式 五种写法总结
  20. 软件工程——软件设计总结

热门文章

  1. python怎么批量下载年报_如何用Python写一个抓取新浪财经网指定企业年报的脚本...
  2. QQ思维导图--消息模块
  3. 【C++】购物街中的商品价格竞猜
  4. CODESOFT软件报错 無法開啓文件
  5. ToB创业,要借什么势?
  6. shell 编程 declare 命令
  7. Java顺丰同城接口开发
  8. 学生成绩管理系统mysql课程设计_数据库课程设计(极其简单的学生成绩管理系统)...
  9. java 3gp 转mp3_java实现音频转换
  10. C++沉思录上提到的一道练习题及其源码实现