Android智能指针
智能指针的目标
在使用指针的时候容易出现的问题不外乎下面几个。首先,指针在使用之前都必须初始化,这个还算容易解决,在创建指针变量的时候同步初始化就好了;第二个问题就是经常忘记delete,就我的经验来看,这个还是很容易忘记的,在一个大型程序中要是有那么几个地方忘记执行delete,长久来看系统内存肯定会被消耗完;第三个问题就是就算记得delete,但是也不是说delete就delete的,要是还有别的对象在引用这个对象,然后被delete了,那么在其他地方访问这个对象的时候程序肯定会奔溃的。
有了智能指针后,上面的问题就好办了,首先创建智能指针的时候也同时创建需要管理的对象,然后将需要管理的对象委托给智能指针来管理就好了。在智能指针内部管理着一个引用计数值,当有新的智能指针引用这个对象时,引用计数值加一,当本来引用该对象的智能指针指向其他对象时,那么引用计数值就减一,当引用计数值减为0的时候就智能指针会帮忙将对象delete,也就解决了忘记delete以及在错误时刻进行delete的困境了。这样上面出现的问题就都能很好的解决了。
轻量级指针
template <class T> class LightRefBase { public:inline LightRefBase() : mCount(0) { }inline void incStrong(__attribute__((unused)) const void* id) const {android_atomic_inc(&mCount);}inline void decStrong(__attribute__((unused)) const void* id) const {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; };
首先看一下轻量级指针,轻量级指针属于模板类,要想使用轻量级指针的功能只要在创建自身类的时候继承这个类就OK了,然后在这个类内部组织管理着一个引用计数值。这个值的功能和前面说的一样,起到控制对象生命周期的作用。incStrong和decStrong起到减少和增加引用计数值的功能。
因为LightRefBase内没有用来保存待委托对象的指针,所以LightRefBase不算是智能指针,大概算是指针的升级版本。以这个类作为基类的类还需要搭配真正的智能指针才能发挥作用。之后会介绍真正的智能指针sp和wp。
强指针
sp类
sp类相对简单,大家看一下代码的实现基本也能了解,下面会配合Refbase进行简单的分析。
template<typename T> class sp { public:inline sp() : m_ptr(0) { }sp(T* other);sp(const sp<T>& other);template<typename U> sp(U* other);template<typename U> sp(const sp<U>& other);~sp();// Assignmentsp& operator = (T* other);sp& operator = (const sp<T>& other);template<typename U> sp& operator = (const sp<U>& other);template<typename U> sp& operator = (U* other);//! Special optimization for use by ProcessState (and nobody else).void force_set(T* other);// Resetvoid clear();// Accessorsinline T& operator* () const { return *m_ptr; }inline T* operator-> () const { return m_ptr; }inline T* get() const { return m_ptr; }// OperatorsCOMPARE(==)COMPARE(!=)COMPARE(>)COMPARE(<)COMPARE(<=)COMPARE(>=)private: template<typename Y> friend class sp;template<typename Y> friend class wp;void set_pointer(T* ptr);T* m_ptr; };
强指针使用的引用计数类是RefBase,它比LightRefBase复杂得多,所以后者才会被称为轻量级指针。下面看一下RefBase的代码:
class RefBase { public:void incStrong(const void* id) const;void decStrong(const void* id) const;void forceIncStrong(const void* id) const;//! DEBUGGING ONLY: Get current strong ref count.int32_t getStrongCount() const;class weakref_type{public:RefBase* refBase() const;void incWeak(const void* id);void decWeak(const void* id);// acquires a strong reference if there is already one.bool attemptIncStrong(const void* id);// acquires a weak reference if there is already one.// This is not always safe. see ProcessState.cpp and BpBinder.cpp// for proper use.bool attemptIncWeak(const void* id);//! DEBUGGING ONLY: Get current weak ref count.int32_t getWeakCount() const;//! DEBUGGING ONLY: Print references held on object.void printRefs() const;//! DEBUGGING ONLY: Enable tracking for this object.// enable -- enable/disable tracking// retain -- when tracking is enable, if true, then we save a stack trace// for each reference and dereference; when retain == false, we// match up references and dereferences and keep only the // outstanding ones.void trackMe(bool enable, bool retain);};weakref_type* createWeak(const void* id) const;weakref_type* getWeakRefs() const;//! DEBUGGING ONLY: Print references held on object.inline void printRefs() const { getWeakRefs()->printRefs(); }//! DEBUGGING ONLY: Enable tracking of object.inline void trackMe(bool enable, bool retain){ getWeakRefs()->trackMe(enable, retain); }typedef RefBase basetype;protected:RefBase();virtual ~RefBase();//! Flags for extendObjectLifetime()enum {OBJECT_LIFETIME_STRONG = 0x0000,OBJECT_LIFETIME_WEAK = 0x0001,OBJECT_LIFETIME_MASK = 0x0001};void extendObjectLifetime(int32_t mode);//! Flags for onIncStrongAttempted()enum {FIRST_INC_STRONG = 0x0001};virtual void onFirstRef();virtual void onLastStrongRef(const void* id);virtual bool onIncStrongAttempted(uint32_t flags, const void* id);virtual void onLastWeakRef(const void* id);private:friend class weakref_type;class weakref_impl;RefBase(const RefBase& o);RefBase& operator=(const RefBase& o);private:friend class ReferenceMover;static void renameRefs(size_t n, const ReferenceRenamer& renamer);static void renameRefId(weakref_type* ref,const void* old_id, const void* new_id);static void renameRefId(RefBase* ref,const void* old_id, const void* new_id);weakref_impl* const mRefs; };
RefBase和LightRefBase一样提供了incStrong和decStrong成员函数来操作引用计数器;而RefBase和LightRefbase类最大的区别就是它不像LightRefBase那么简单,只提供一个引用计数器,而是提供了一个强引用计数器和一个弱引用计数器。这两种计数器的功能是由weakref_impl类的变量mRefs提供的。
weakref_impl类
RefBase类的成员变量mRefs的类型为weakref_impl指针,这个类的代码在RefBase.cpp文件内,里面的代码看似很复杂,其实细心了解下里面有一个DEBUG_REFS宏,这个宏里面的代码只有在Debug版本下才会去实现,否则为空,所以基本可以不用看。
class RefBase::weakref_impl : public RefBase::weakref_type { public:volatile int32_t mStrong;volatile int32_t mWeak;RefBase* const mBase;volatile int32_t mFlags;#if !DEBUG_REFSweakref_impl(RefBase* base): mStrong(INITIAL_STRONG_VALUE), mWeak(0), mBase(base), mFlags(0){}void addStrongRef(const void* /*id*/) { }void removeStrongRef(const void* /*id*/) { }void renameStrongRefId(const void* /*old_id*/, const void* /*new_id*/) { }void addWeakRef(const void* /*id*/) { }void removeWeakRef(const void* /*id*/) { }void renameWeakRefId(const void* /*old_id*/, const void* /*new_id*/) { }void printRefs() const { }void trackMe(bool, bool) { }#elseweakref_impl(RefBase* base): mStrong(INITIAL_STRONG_VALUE), mWeak(0), mBase(base), mFlags(0), mStrongRefs(NULL), mWeakRefs(NULL), mTrackEnabled(!!DEBUG_REFS_ENABLED_BY_DEFAULT), mRetain(false){}~weakref_impl(){bool dumpStack = false;if (!mRetain && mStrongRefs != NULL) {dumpStack = true;ALOGE("Strong references remain:");ref_entry* refs = mStrongRefs;while (refs) {char inc = refs->ref >= 0 ? '+' : '-';ALOGD("\t%c ID %p (ref %d):", inc, refs->id, refs->ref); #if DEBUG_REFS_CALLSTACK_ENABLEDrefs->stack.log(LOG_TAG); #endifrefs = refs->next;}}if (!mRetain && mWeakRefs != NULL) {dumpStack = true;ALOGE("Weak references remain!");ref_entry* refs = mWeakRefs;while (refs) {char inc = refs->ref >= 0 ? '+' : '-';ALOGD("\t%c ID %p (ref %d):", inc, refs->id, refs->ref); #if DEBUG_REFS_CALLSTACK_ENABLEDrefs->stack.log(LOG_TAG); #endifrefs = refs->next;}}if (dumpStack) {ALOGE("above errors at:");CallStack stack(LOG_TAG);}}void addStrongRef(const void* id) {//ALOGD_IF(mTrackEnabled,// "addStrongRef: RefBase=%p, id=%p", mBase, id);addRef(&mStrongRefs, id, mStrong);}void removeStrongRef(const void* id) {//ALOGD_IF(mTrackEnabled,// "removeStrongRef: RefBase=%p, id=%p", mBase, id);if (!mRetain) {removeRef(&mStrongRefs, id);} else {addRef(&mStrongRefs, id, -mStrong);}}………..void addWeakRef(const void* id) {addRef(&mWeakRefs, id, mWeak);}void removeWeakRef(const void* id) {if (!mRetain) {removeRef(&mWeakRefs, id);} else {addRef(&mWeakRefs, id, -mWeak);}}……………private:struct ref_entry{ref_entry* next;const void* id; #if DEBUG_REFS_CALLSTACK_ENABLEDCallStack stack; #endifint32_t ref;};void addRef(ref_entry** refs, const void* id, int32_t mRef){if (mTrackEnabled) {AutoMutex _l(mMutex);ref_entry* ref = new ref_entry;// Reference count at the time of the snapshot, but before the// update. Positive value means we increment, negative--we// decrement the reference count.ref->ref = mRef;ref->id = id; #if DEBUG_REFS_CALLSTACK_ENABLEDref->stack.update(2); #endifref->next = *refs;*refs = ref;}}void removeRef(ref_entry** refs, const void* id){if (mTrackEnabled) {AutoMutex _l(mMutex);ref_entry* const head = *refs;ref_entry* ref = head;while (ref != NULL) {if (ref->id == id) {*refs = ref->next;delete ref;return;}refs = &ref->next;ref = *refs;}ALOGE("RefBase: removing id %p on RefBase %p""(weakref_type %p) that doesn't exist!",id, mBase, this);ref = head;while (ref) {char inc = ref->ref >= 0 ? '+' : '-';ALOGD("\t%c ID %p (ref %d):", inc, ref->id, ref->ref);ref = ref->next;}CallStack stack(LOG_TAG);}}…..…..mutable Mutex mMutex;ref_entry* mStrongRefs;ref_entry* mWeakRefs;bool mTrackEnabled;// Collect stack traces on addref and removeref, instead of deleting the stack references// on removeref that match the address ones.bool mRetain;#endif };
Weakref_impl类是weakref_base的子类,这个是接口与实现分离的思想。
RefBase的incStrong函数
template<typename T> sp<T>::sp(T* other): m_ptr(other) {if (other)other->incStrong(this); }template<typename T> sp<T>::sp(const sp<T>& other): m_ptr(other.m_ptr) {if (m_ptr)m_ptr->incStrong(this); }
这里的other就是实际的对象,这个对象可以是继承了LightRefBase的对象也可以是继承了RefBase的对象,因为这里主要分析RefBase对象,所以这里以及下面的内容都假设other对象是继承了RefBase的内容的,下面看RefBase的incStrong函数
void RefBase::incStrong(const void* id) const {weakref_impl* const refs = mRefs;refs->incWeak(id);refs->addStrongRef(id);const int32_t c = android_atomic_inc(&refs->mStrong);ALOG_ASSERT(c > 0, "incStrong() called on %p after last strong ref", refs); #if PRINT_REFSALOGD("incStrong of %p from %p: cnt=%d\n", this, id, c); #endifif (c != INITIAL_STRONG_VALUE) {return;}android_atomic_add(-INITIAL_STRONG_VALUE, &refs->mStrong);refs->mBase->onFirstRef(); }
其中mRefs实在RefBase的构造函数中创建的
RefBase::RefBase(): mRefs(new weakref_impl(this)) { }
重新回到incStrong函数中,我们会发现这个函数中其实只是做了三件事情:
- 增加弱引用计数
refs->incWeak(id);
- 增加强引用计数
const int32_t c = android_atomic_inc(&refs->mStrong);
- 如果发现是第一次调用对象的incStrong,那么就会修正mStrong引用计数,然后调用这个对象的onFirstRef函数
android_atomic_add(-INITIAL_STRONG_VALUE, &refs->mStrong); refs->mBase->onFirstRef()
在调用weakref_impl的构造函数的时候会将mStrong的值初始化为INITIAL_STRONG_VALUE=1<<28;那么在执行加1操作后,mStrong就等于1<<28+1;返回的值c等于加1前的值,即1<<28;所以第一次调用incStrong后需要对mStrong的值进行修正,加上-INITIAL_STRONG_VALUE正好。
现在回头看增加弱引用计数的代码,通过调用weakref_impl的incWeak来对弱引用计数进行加1操作,而weakref_impl类的incWeak则是直接从父类weakref_base中继承来的。
void RefBase::weakref_type::incWeak(const void* id) {weakref_impl* const impl = static_cast<weakref_impl*>(this);impl->addWeakRef(id);const int32_t c __unused = android_atomic_inc(&impl->mWeak);ALOG_ASSERT(c >= 0, "incWeak called on %p after last weak ref", this); }
上面代码增加弱引用计数主要是执行android_atomic_inc方法来完成的。
RefBase的decStrong函数
template<typename T> sp<T>::~sp() {if (m_ptr)m_ptr->decStrong(this); }
上面的代码调用RefBase类的decStrong函数
void RefBase::decStrong(const void* id) const {weakref_impl* const refs = mRefs;refs->removeStrongRef(id);const int32_t c = android_atomic_dec(&refs->mStrong); #if PRINT_REFSALOGD("decStrong of %p from %p: cnt=%d\n", this, id, c); #endifALOG_ASSERT(c >= 1, "decStrong() called on %p too many times", refs);if (c == 1) {refs->mBase->onLastStrongRef(id);if ((refs->mFlags&OBJECT_LIFETIME_MASK) == OBJECT_LIFETIME_STRONG) {delete this;}}refs->decWeak(id); }
上面代码先将强引用计数减1,如果发现返回值为1的话,就代表调用此时强引用计数值已经为0了,那么就调用onLastStrongRef函数,这个函数在RefBase中的实现也是为空的,一般是留给子类来实现。然后判断mask是否为OBJECT_LISTTIME_STRONG,如果是,代表该对象的生命周期受强引用计数值控制,当这个对象的强引用计数值为0时,就将这个对象delete掉。
对弱引用计数值的操作则是调用decWeak.
void RefBase::weakref_type::decWeak(const void* id) {weakref_impl* const impl = static_cast<weakref_impl*>(this);impl->removeWeakRef(id);const int32_t c = android_atomic_dec(&impl->mWeak);ALOG_ASSERT(c >= 1, "decWeak called on %p too many times", this);if (c != 1) return;// 如果对象受强引用控制if ((impl->mFlags&OBJECT_LIFETIME_WEAK) == OBJECT_LIFETIME_STRONG) {// This is the regular lifetime case. The object is destroyed// when the last strong reference goes away. Since weakref_impl// outlive the object, it is not destroyed in the dtor, and// we'll have to do it here.// 如果对象没有被强引用过if (impl->mStrong == INITIAL_STRONG_VALUE) {// Special case: we never had a strong reference, so we need to// destroy the object now.// 删除impl该对象delete impl->mBase;} else {// ALOGV("Freeing refs %p of old RefBase %p\n", this, impl->mBase);// 这种时对象被强引用过了,所以只需要直接删除impl即可delete impl;}} else {// less common case: lifetime is OBJECT_LIFETIME_{WEAK|FOREVER}impl->mBase->onLastWeakRef(id);if ((impl->mFlags&OBJECT_LIFETIME_MASK) == OBJECT_LIFETIME_WEAK) {// this is the OBJECT_LIFETIME_WEAK case. The last weak-reference// is gone, we can destroy the object.delete impl->mBase;}} }
在这个函数中,当弱引用计数不为1则直接return,如果为1那么减1后就为0了,需要对对象进行delete操作。而弱引用计数为1又分为两种情况:
第一种情况为对象的生命周期只受强引用控制,而当强引用计数为初始值的时候,就要删除impl->mBase就是删除实际的对象。而RefBase被删除的时候就会调用析构函数,而在析构函数中决定是否需要将mRefs删除。
RefBase::~RefBase() {if (mRefs->mStrong == INITIAL_STRONG_VALUE) {// we never acquired a strong (and/or weak) reference on this object.delete mRefs;} else {// life-time of this object is extended to WEAK or FOREVER, in// which case weakref_impl doesn't out-live the object and we// can free it now.if ((mRefs->mFlags & OBJECT_LIFETIME_MASK) != OBJECT_LIFETIME_STRONG) {// It's possible that the weak count is not 0 if the object// re-acquired a weak reference in its destructorif (mRefs->mWeak == 0) {delete mRefs;}}}// for debugging purposes, clear this.const_cast(mRefs) = NULL; }
当时如果强引用指数不为初始值的时候,就直接调用delete impl,那是因为在decStrong函数中就已经将实际的对象delete掉了。
第二种情况,当对象的生命周期不受强引用控制时,先调用onLastWeakRef函数,然后如果对象的生命周期时由弱引用控制,就直接删除RefBase对象,当然在RefBase的析构函数中也会删除mRefs对象。
弱指针
wp类
template <typename T> class wp { public:typedef typename RefBase::weakref_type weakref_type;inline wp() : m_ptr(0) { }wp(T* other);wp(const wp<T>& other);wp(const sp<T>& other);template<typename U> wp(U* other);template<typename U> wp(const sp<U>& other);template<typename U> wp(const wp<U>& other);~wp();// Assignmentwp& operator = (T* other);wp& operator = (const wp<T>& other);wp& operator = (const sp<T>& other);template<typename U> wp& operator = (U* other);template<typename U> wp& operator = (const wp<U>& other);template<typename U> wp& operator = (const sp<U>& other);void set_object_and_refs(T* other, weakref_type* refs);// promotion to spsp<T> promote() const;// Resetvoid clear();// Accessorsinline weakref_type* get_refs() const { return m_refs; }inline T* unsafe_get() const { return m_ptr; }// OperatorsCOMPARE_WEAK(==)COMPARE_WEAK(!=)COMPARE_WEAK(>)COMPARE_WEAK(<)COMPARE_WEAK(<=)COMPARE_WEAK(>=)inline bool operator == (const wp<T>& o) const {return (m_ptr == o.m_ptr) && (m_refs == o.m_refs);}template<typename U>inline bool operator == (const wp<U>& o) const {return m_ptr == o.m_ptr;}inline bool operator > (const wp<T>& o) const {return (m_ptr == o.m_ptr) ? (m_refs > o.m_refs) : (m_ptr > o.m_ptr);}template<typename U>inline bool operator > (const wp<U>& o) const {return (m_ptr == o.m_ptr) ? (m_refs > o.m_refs) : (m_ptr > o.m_ptr);}inline bool operator < (const wp<T>& o) const {return (m_ptr == o.m_ptr) ? (m_refs < o.m_refs) : (m_ptr < o.m_ptr);}template<typename U>inline bool operator < (const wp<U>& o) const {return (m_ptr == o.m_ptr) ? (m_refs < o.m_refs) : (m_ptr < o.m_ptr);}inline bool operator != (const wp<T>& o) const { return m_refs != o.m_refs; }template<typename U> inline bool operator != (const wp<U>& o) const { return !operator == (o); }inline bool operator <= (const wp<T>& o) const { return !operator > (o); }template<typename U> inline bool operator <= (const wp<U>& o) const { return !operator > (o); }inline bool operator >= (const wp<T>& o) const { return !operator < (o); }template<typename U> inline bool operator >= (const wp<U>& o) const { return !operator < (o); }private:template<typename Y> friend class sp;template<typename Y> friend class wp;T* m_ptr;weakref_type* m_refs; };
与强指针类相比,他们都有一个成员变量m_ptr指向目标对象,但是弱指针还有一个额外的成员变量m_refs,他的类型时weakref_type指针,下面我们分析弱指针的构造函数时再看看他是如何初始化的。这里我们需要关注的是构造函数和析构函数。
wp类的构造函数
先看看wp类的构造函数
template<typename T> wp<T>::wp(T* other): m_ptr(other) {if (other) m_refs = other->createWeak(this); }
这里调用RefBase的createWeak函数,返回值为m_refs。
在RefBase的createWeak函数中,直接调用weakref_impl的incWeak函数,这个函数之前分析过了就是增加弱引用计数的,同时返回mRefs给调用函数。
RefBase::weakref_type* RefBase::createWeak(const void* id) const {mRefs->incWeak(id);return mRefs; }
接下分析析构函数,这里直接调用weakref_impl的decWeak函数,前面分析过这个函数了。在这个函数里面弱引用次数减1,然后决定是否需要delete impl等。
template<typename T> wp<T>::~wp() {if (m_ptr) m_refs->decWeak(this); }
升级为强指针
分析到这里,弱指针还没有分析完成,这里面还有一个非常重要的特性还没有分析,那就是将弱指针升级为强指针的操作,是直接调用其中的promote函数
template<typename T> sp<T> wp<T>::promote() const {sp result;if (m_ptr && m_refs->attemptIncStrong(&result)) {result.set_pointer(m_ptr);}return result; }
http://blog4jimmy.com/2018/01/324.html
Android智能指针相关推荐
- android 智能指针的学习先看邓凡平的书扫盲 再看前面两片博客提升
android 智能指针的学习先看邓凡平的书扫盲 再看前面两片博客提升 转载于:https://www.cnblogs.com/jeanschen/p/3507512.html
- Android智能指针SP WP使用方法介绍
Android手机操作系统既然是开源的操作系统.那么在具体的文件夹中就会存放着各种相关功能的开源代码.我们在使用的时候可以根据这些源代码进行相应的修改就能轻松的完成我们所需的功能.在这里大家就一起来看 ...
- android wp指针使用方法,Android智能指针RefBase、sp、wp解析
[TOC] 在Android系统中,Native层的代码基本都是C++写的,C++跟Java不一样,C++没有垃圾回收机制,C++代码中难于管理new出来对象的释放,稍有不慎就造成内存泄漏.针对此问题 ...
- android 绘指针,Android智能指针
"Yeah It's on. " 前言 在Android系统中,Native层的代码基本都是C++写的,C++跟Java不一样,C++没有垃圾回收机制,C++代码中难于管理new出 ...
- Android 智能指针 视频,Android系统智能指针中轻量级指针
lp.sp.wp在Android Native层中被大量使用,所以非常有必要学习它们的实现原理.lp是Light Pointer的缩写,表示轻量级指针,sp是Strong Pointer的缩写,表示强 ...
- Android智能指针——读书笔记
目录结构 目录结构 参考资料 概述 背景知识 GC经典问题 轻量级指针 实现原理分析 构造函数 析构函数 应用实例分析 强指针和弱指针 强指针的实现原理分析 增加对象的弱引用计数 增加对象的强引用计数 ...
- android 的 sp 智能指针用法介绍 sp wp
1原理 Android中定义了两种智能指针类型,一种是强指针sp(strong pointer),另外一种是弱指针(weak pointer). 其实称之为强引用和弱引用更合适一些.强指针与一般意义的 ...
- Android基础知识之智能指针:强指针和弱指针
Android中定义了两种智能指针类型,一种是强指针sp(strong pointer),另外一种是弱指针(weak pointer).其实称之为强引用和弱引用更合适一些.强指针与一般意义的智能指针概 ...
- ANDROID JNI 智能指针
Android系统的运行时库层代码是用C++来编写的,用C++来写代码最容易出错的地方就是指针了,一旦使用不当,轻则造成内存泄漏,重则造成系统崩溃.不过系统为我们提供了智能指针,避免出现上述问题,本文 ...
最新文章
- Navicat 数据库连接工具连接oracle数据库提示cannot create oci handles问题解决方法
- Unity3D如何有效地组织代码?(转)
- Openstack Python 源代码的路径
- 让shell脚本开机自启动方法
- css元素穿透。 pointer-events: none;
- The library 'SocialSDK_QQZone_2.jar' contains nat
- Python-print学习
- linux 检验md5命令,linux命令行校验工具md5sum
- 播布客里小布老师的全部视频收集
- html的div背景,html div背景到底是什么颜色呢?
- matlab下的彩色图像转换成二值图像并保存
- Banana Pi 消息
- 条件概率、全概率公式和贝叶斯公式
- 【转】中国地理的几个概念
- 北京Uber优步司机奖励政策(12月31日)
- 汽车电子的发展简介和V型开发模式
- 2020ACM俱乐部后备营个人训练赛第七、八、九场
- python汽车类型识别源代码带图形界面,基于PyTorach深度学习
- 地学学术资源(常用数据及其下载地址)
- 计算机基础之网络层的功能和服务
热门文章
- Centos 7 安装NodeJS开发环境
- (转)Python 用hashlib求中文字符串的MD5值
- [译]在CUDA C/C++中如何衡量代码性能
- CUDA程序优化技巧
- 关于操作系统中进程、线程、死锁、同步、进程间通信(IPC)的超详细详解整理
- C++ static与const用法详解
- win10下gnuplot的安装
- 科大星云诗社动态20201208
- Python7:from module import * 和 import module 的区别
- oop中构造函数编写的注意事项