Objective-C 小记(10)__weak
本文使用的 runtime 版本为 objc4-706。
__weak
修饰的指针最重要的特性是其指向的对象销毁后,会自动置为 nil
,这个特性的实现完全是依靠运行时的。实现思路是非常简单的,对于下面的语句来说:
id __weak weakObj = strongObj;
便是用 strongObj
当作 key,weakObj
当作 value 存入一个表里。当 strongObj
销毁时,从表里找到所有的 __weak
引用,将其置为 nil
。
当然,实际的实现肯定是要比这要充斥着更多的细节。
变量的创建和销毁
还是上面那个例子,实际上编译器会进行一些变动:
{
id __weak weakObj = strongObj;
}
// 会变成
{
id __weak weakObj;
objc_initWeak(&weakObj, strongObj);
// 离开变量的范围,进行销毁
objc_destroyWeak(&weakObj);
}
objc_initWeak
和 objc_destroyWeak
都可以在 NSObject.mm
文件中找到:
id
objc_initWeak(id *location, id newObj)
{
if (!newObj) {
*location = nil;
return nil;
}
return storeWeak<false/*old*/, true/*new*/, true/*crash*/>
(location, (objc_object*)newObj);
}
void
objc_destroyWeak(id *location)
{
(void)storeWeak<true/*old*/, false/*new*/, false/*crash*/>
(location, nil);
}
可以看到都是对 storeWeak
函数模板的调用(为什么要使用模板呢?会更快吗?C++ 小白内心的问题…… )。
赋值
当已有的 __weak
变量被重新赋值时会怎么样呢?
weakObj = anotherStrongObj;
// 会变成下面这样
objc_storeWeak(&weakObj, anotherStrongObj);
它的实现如下:
id
objc_storeWeak(id *location, id newObj)
{
return storeWeak<true/*old*/, true/*new*/, true/*crash*/>
(location, (objc_object *)newObj);
}
但实际上也还是对 storeWeak
函数模板的封装。
storeWeak
storeWeak
的实现还是有点长的,一点一点来分析:
// Update a weak variable.
// If HaveOld is true, the variable has an existing value
// that needs to be cleaned up. This value might be nil.
// If HaveNew is true, there is a new value that needs to be
// assigned into the variable. This value might be nil.
// If CrashIfDeallocating is true, the process is halted if newObj is
// deallocating or newObj's class does not support weak references.
// If CrashIfDeallocating is false, nil is stored instead.
template <bool HaveOld, bool HaveNew, bool CrashIfDeallocating>
static id
storeWeak(id *location, objc_object *newObj)
{
assert(HaveOld || HaveNew);
if (!HaveNew) assert(newObj == nil);
Class previouslyInitializedClass = nil;
id oldObj;
SideTable *oldTable;
SideTable *newTable;
函数前的注释表明了三个模板参数的作用,当然在后面的代码里也能直观的看到。函数一开始进行了变量的声明,可以注意到 SideTable
这个类型,SideTable
是现在的运行时中用来存放引用计数和弱引用的结构体,它的结构是这样的(省略了结构体函数):
struct SideTable {
spinlock_t slock;
RefcountMap refcnts;
weak_table_t weak_table;
}
其中 slock
是一个自旋锁,用来对 SideTable
实例进行操作时的加锁。refcnts
则是存放引用计数的地方。weak_table
则是存放弱引用的地方(后面将详细分析 weak_table_t
)。
回到 storeWeak
函数:
// Acquire locks for old and new values.
// Order by lock address to prevent lock ordering problems.
// Retry if the old value changes underneath us.
retry:
if (HaveOld) {
oldObj = *location;
oldTable = &SideTables()[oldObj];
} else {
oldTable = nil;
}
if (HaveNew) {
newTable = &SideTables()[newObj];
} else {
newTable = nil;
}
SideTable::lockTwo<HaveOld, HaveNew>(oldTable, newTable);
if (HaveOld && *location != oldObj) {
SideTable::unlockTwo<HaveOld, HaveNew>(oldTable, newTable);
goto retry;
}
这一段即获取 oldObj
、oldTable
和 newTable
,并将获取的两个表上锁。注意到获取 oldTable
和 newTable
时,其实是用对象的地址当作 key 从 SideTables
获取的,SideTables
返回的就是一个哈希表,存储着若干个 SideTable
,一般是 64 个。
// Prevent a deadlock between the weak reference machinery
// and the +initialize machinery by ensuring that no
// weakly-referenced object has an un-+initialized isa.
if (HaveNew && newObj) {
Class cls = newObj->getIsa();
if (cls != previouslyInitializedClass &&
!((objc_class *)cls)->isInitialized())
{
SideTable::unlockTwo<HaveOld, HaveNew>(oldTable, newTable);
_class_initialize(_class_getNonMetaClass(cls, (id)newObj));
// If this class is finished with +initialize then we're good.
// If this class is still running +initialize on this thread
// (i.e. +initialize called storeWeak on an instance of itself)
// then we may proceed but it will appear initializing and
// not yet initialized to the check above.
// Instead set previouslyInitializedClass to recognize it on retry.
previouslyInitializedClass = cls;
goto retry;
}
}
上面这一段代码也有着很好的注释,就是要确保对象的类已经走过 +initialize
流程了。
// Clean up old value, if any.
if (HaveOld) {
weak_unregister_no_lock(&oldTable->weak_table, oldObj, location);
}
// Assign new value, if any.
if (HaveNew) {
newObj = (objc_object *)weak_register_no_lock(&newTable->weak_table,
(id)newObj, location,
CrashIfDeallocating);
// weak_register_no_lock returns nil if weak store should be rejected
// Set is-weakly-referenced bit in refcount table.
if (newObj && !newObj->isTaggedPointer()) {
newObj->setWeaklyReferenced_nolock();
}
// Do not set *location anywhere else. That would introduce a race.
*location = (id)newObj;
}
else {
// No new value. The storage is not changed.
}
SideTable::unlockTwo<HaveOld, HaveNew>(oldTable, newTable);
return (id)newObj;
}
最后一段的逻辑也是很清晰的。首先,如果有旧的值(HaveOld
),则使用 weak_unregister_no_lock
函数将其从 oldTable
的 weak_table
中移除。其次,如果有新的值(HaveNew
),则使用 weak_register_no_lock
函数将其注册到 newTable
的 weak_table
中,并使用 setWeaklyReferenced_nolock
函数将对象标记为被弱引用过。
storeWeak
的实现就告一段落了,其重点就在 weak_register_no_lock
和 weak_unregister_no_lock
函数上。
weak_table_t
在分析这两个函数之前,先看看 weak_table_t
是一个怎么样的结构:
/**
* The global weak references table. Stores object ids as keys,
* and weak_entry_t structs as their values.
*/
struct weak_table_t {
weak_entry_t *weak_entries;
size_t num_entries;
uintptr_t mask;
uintptr_t max_hash_displacement;
};
weak_entries
便是存放弱引用的数组;num_entries
是存放的weak_entry_t
条目的数量;mask
则是动态申请的弱引用数组weak_entries
长度减 1 的值,用来对哈希后的值取余和记录数组大小;max_hash_displacement
则是哈希碰撞后最大的位移值。
其实 weak_table_t
就是一个动态增长的哈希表。
继续看看其相关的操作,首先是对整个表的扩大:
#define TABLE_SIZE(entry) (entry->mask ? entry->mask + 1 : 0)
// Grow the given zone's table of weak references if it is full.
static void weak_grow_maybe(weak_table_t *weak_table)
{
size_t old_size = TABLE_SIZE(weak_table);
// Grow if at least 3/4 full.
if (weak_table->num_entries >= old_size * 3 / 4) {
weak_resize(weak_table, old_size ? old_size*2 : 64);
}
}
可以看到,当 weak_table
里的弱引用条目达到它容量的四分之三时,便会将容量拓展为两倍。值得注意的是第一次拓展也就是是 mask
为 0 的情况,初始值是 64。实际对弱引用表大小的操作则交给了 weak_resize
函数。
除了扩大,当然也还有缩小:
// Shrink the table if it is mostly empty.
static void weak_compact_maybe(weak_table_t *weak_table)
{
size_t old_size = TABLE_SIZE(weak_table);
// Shrink if larger than 1024 buckets and at most 1/16 full.
if (old_size >= 1024 && old_size / 16 >= weak_table->num_entries) {
weak_resize(weak_table, old_size / 8);
// leaves new table no more than 1/2 full
}
}
缩小的话则是需要表本身大于等于 1024 并且存放了不足十六分之一的条目时,直接缩小 8 倍。实际工作也是交给了 weak_resize
函数:
static void weak_resize(weak_table_t *weak_table, size_t new_size)
{
size_t old_size = TABLE_SIZE(weak_table);
weak_entry_t *old_entries = weak_table->weak_entries;
weak_entry_t *new_entries = (weak_entry_t *)
calloc(new_size, sizeof(weak_entry_t));
weak_table->mask = new_size - 1;
weak_table->weak_entries = new_entries;
weak_table->max_hash_displacement = 0;
weak_table->num_entries = 0; // restored by weak_entry_insert below
if (old_entries) {
weak_entry_t *entry;
weak_entry_t *end = old_entries + old_size;
for (entry = old_entries; entry < end; entry++) {
if (entry->referent) {
weak_entry_insert(weak_table, entry);
}
}
free(old_entries);
}
}
weak_resize
函数的过程就是新建一个数组,将老数组里的值使用 weak_entry_insert
函数添加进去,注意到代码中间 mask
在这里被赋值为新数组的大小减去 1,max_hash_displacement
和 num_entries
也都清零了,因为 weak_entry_insert
函数会对这两个值进行操作。接着对 weak_entry_insert
函数进行分析:
/**
* Add new_entry to the object's table of weak references.
* Does not check whether the referent is already in the table.
*/
static void weak_entry_insert(weak_table_t *weak_table, weak_entry_t *new_entry)
{
weak_entry_t *weak_entries = weak_table->weak_entries;
assert(weak_entries != nil);
size_t begin = hash_pointer(new_entry->referent) & (weak_table->mask);
size_t index = begin;
size_t hash_displacement = 0;
while (weak_entries[index].referent != nil) {
index = (index+1) & weak_table->mask;
if (index == begin) bad_weak_table(weak_entries);
hash_displacement++;
}
weak_entries[index] = *new_entry;
weak_table->num_entries++;
if (hash_displacement > weak_table->max_hash_displacement) {
weak_table->max_hash_displacement = hash_displacement;
}
}
这个函数就是个很正常的哈希表插入的过程,hash_pointer
函数是对指针地址进行哈希,哈希后的值之所以要和 mask
进行 &
操作,是因为弱引用表的大小永远是 2 的幂(一开始是 64,之后不断乘以 2),mask
则是大小减去 1 即为一个 0b111...11
这么一个数,和它进行 &
运算相当于取余。hash_displacement
则是记录了哈希相撞后偏移的大小。
既然有插入,也就有删除:
/**
* Remove entry from the zone's table of weak references.
*/
static void weak_entry_remove(weak_table_t *weak_table, weak_entry_t *entry)
{
// remove entry
if (entry->out_of_line()) free(entry->referrers);
bzero(entry, sizeof(*entry));
weak_table->num_entries--;
weak_compact_maybe(weak_table);
}
很直接的清零 entry
,并给 weak_table
的 num_entries
减 1,最后检查看是否需要缩小。
最后还有一个根据指定对象查找存在条目的函数:
/**
* Return the weak reference table entry for the given referent.
* If there is no entry for referent, return NULL.
* Performs a lookup.
*
* @param weak_table
* @param referent The object. Must not be nil.
*
* @return The table of weak referrers to this object.
*/
static weak_entry_t *
weak_entry_for_referent(weak_table_t *weak_table, objc_object *referent)
{
assert(referent);
weak_entry_t *weak_entries = weak_table->weak_entries;
if (!weak_entries) return nil;
size_t begin = hash_pointer(referent) & weak_table->mask;
size_t index = begin;
size_t hash_displacement = 0;
while (weak_table->weak_entries[index].referent != referent) {
index = (index+1) & weak_table->mask;
if (index == begin) bad_weak_table(weak_table->weak_entries);
hash_displacement++;
if (hash_displacement > weak_table->max_hash_displacement) {
return nil;
}
}
return &weak_table->weak_entries[index];
}
也是很正常的哈希表套路。
weak_entry_t
那弱引用是怎么存储的呢,继续分析 weak_entry_t
:
#define WEAK_INLINE_COUNT 4
#define REFERRERS_OUT_OF_LINE 2
struct weak_entry_t {
DisguisedPtr<objc_object> referent;
union {
struct {
weak_referrer_t *referrers;
uintptr_t out_of_line_ness : 2;
uintptr_t num_refs : PTR_MINUS_2;
uintptr_t mask;
uintptr_t max_hash_displacement;
};
struct {
// out_of_line_ness field is low bits of inline_referrers[1]
weak_referrer_t inline_referrers[WEAK_INLINE_COUNT];
};
};
bool out_of_line() {
return (out_of_line_ness == REFERRERS_OUT_OF_LINE);
}
weak_entry_t& operator=(const weak_entry_t& other) {
memcpy(this, &other, sizeof(other));
return *this;
}
weak_entry_t(objc_object *newReferent, objc_object **newReferrer)
: referent(newReferent)
{
inline_referrers[0] = newReferrer;
for (int i = 1; i < WEAK_INLINE_COUNT; i++) {
inline_referrers[i] = nil;
}
}
};
首先 DisguisedPtr<T>
类型和 T*
的行为是一模一样的,这个类型存在的目的是为了躲过内存泄漏工具的检查(注释原文:「DisguisedPtr<T>
acts like pointer type T*
, except the stored value is disguised to hide it from tools like leaks
.」)。所以 DisguisedPtr<objc_object> referent
可以看作是 objc_object *referent
。
referent
这个指针记录的便是被弱引用的对象。接下来的联合里有两种结构体,先分析第一种:
referrers
:referrers
是一个weak_referrer_t
类型的数组,用来存放弱引用变量的地址,weak_referrer_t
的定义是这样的:typedef DisguisedPtr<objc_object *> weak_referrer_t;
;out_of_line_ness
:2 bit 标记位,用来确定联合里的内存是第一个结构体还是第二个结构体;num_refs
:PTR_MINUS_2
便是字长减去 2 位,和out_of_line_ness
一起组成一个字长,用来存储referrers
的大小;mask
和max_hash_displacement
:和前面分析的一样,做哈希表用到的东西。
可以发现第一种结构体也是一个哈希表,第二种结构体则是一个和第一种结构体一样大的数组,所谓的 inline 存储。存放思路则是首先 inline 存储,当超过 WEAK_INLINE_COUNT
也就是 4 时,再变成第一种的动态哈希表存储。代码下方的构造函数便体现了这个思路。
可以注意到 weak_entry_t
重载了赋值操作符,将赋值变成了一个拷贝内存的操作。
相关操作也是和上面 weak_table_t
的类似,只不过加上了 inline 存储情况的变化,就不详细分析了。
weak_register_no_lock
开始分析 weak_register_no_lock
函数:
/**
* Registers a new (object, weak pointer) pair. Creates a new weak
* object entry if it does not exist.
*
* @param weak_table The global weak table.
* @param referent The object pointed to by the weak reference.
* @param referrer The weak pointer address.
*/
id
weak_register_no_lock(weak_table_t *weak_table, id referent_id,
id *referrer_id, bool crashIfDeallocating)
{
objc_object *referent = (objc_object *)referent_id;
objc_object **referrer = (objc_object **)referrer_id;
if (!referent || referent->isTaggedPointer()) return referent_id;
第一段,约等于什么都没干。referent
是被弱引用的对象,referrer
则是弱引用变量的地址。
// ensure that the referenced object is viable
bool deallocating;
if (!referent->ISA()->hasCustomRR()) {
deallocating = referent->rootIsDeallocating();
}
else {
BOOL (*allowsWeakReference)(objc_object *, SEL) =
(BOOL(*)(objc_object *, SEL))
object_getMethodImplementation((id)referent,
SEL_allowsWeakReference);
if ((IMP)allowsWeakReference == _objc_msgForward) {
return nil;
}
deallocating =
! (*allowsWeakReference)(referent, SEL_allowsWeakReference);
}
这一段很有意思,如果对象没有自定义的内存管理方法(hasCustomRR
),则将 deallocating
变量赋值为 rootIsDeallocating
也就是是否正在销毁。但是如果有自定义的内存管理方法的话,发送的是
allowsWeakReference
这个消息,即是否允许弱引用。不管怎么样,我们得到了一个 deallocating
变量。
if (deallocating) {
if (crashIfDeallocating) {
_objc_fatal("Cannot form weak reference to instance (%p) of "
"class %s. It is possible that this object was "
"over-released, or is in the process of deallocation.",
(void*)referent, object_getClassName((id)referent));
} else {
return nil;
}
}
从上面一段可以知道,deallocating
为 true
的话肯定是有问题的,所以这一段处理一下。
// now remember it and where it is being stored
weak_entry_t *entry;
if ((entry = weak_entry_for_referent(weak_table, referent))) {
append_referrer(entry, referrer);
}
else {
weak_entry_t new_entry(referent, referrer);
weak_grow_maybe(weak_table);
weak_entry_insert(weak_table, &new_entry);
}
// Do not set *referrer. objc_storeWeak() requires that the
// value not change.
return referent_id;
}
最后一段终于做了正事了!首先先用 weak_entry_for_referent
函数搜索对象是否已经有了 weak_entry_t
类型的条目,有的话则使用 append_referrer
添加一个变量位置进去,没有的话则新建一个 weak_entry_t
条目,使用 weak_grow_maybe
函数扩大(如果需要的话)弱引用表的大小,并使用 weak_entry_insert
将弱引用插入表中。
weak_unregister_no_lock
接下来是 weak_unregister_no_lock
函数:
void
weak_unregister_no_lock(weak_table_t *weak_table, id referent_id,
id *referrer_id)
{
objc_object *referent = (objc_object *)referent_id;
objc_object **referrer = (objc_object **)referrer_id;
weak_entry_t *entry;
if (!referent) return;
if ((entry = weak_entry_for_referent(weak_table, referent))) {
remove_referrer(entry, referrer);
bool empty = true;
if (entry->out_of_line() && entry->num_refs != 0) {
empty = false;
}
else {
for (size_t i = 0; i < WEAK_INLINE_COUNT; i++) {
if (entry->inline_referrers[i]) {
empty = false;
break;
}
}
}
if (empty) {
weak_entry_remove(weak_table, entry);
}
}
// Do not set *referrer = nil. objc_storeWeak() requires that the
// value not change.
}
主要功能实现思路很简单,使用 weak_entry_for_referent
函数找到对应的弱引用条目,并用 remove_referrer
将对应的弱引用变量位置从中移除。最后判断条目是否为空,为空则使用 weak_entry_remove
将其从弱引用表中移除。
自动置为 nil
对象销毁后,弱引用变量被置为 nil
是因为在对象 dealloc
的过程中调用了 weak_clear_no_lock
函数:
/**
* Called by dealloc; nils out all weak pointers that point to the
* provided object so that they can no longer be used.
*
* @param weak_table
* @param referent The object being deallocated.
*/
void
weak_clear_no_lock(weak_table_t *weak_table, id referent_id)
{
objc_object *referent = (objc_object *)referent_id;
weak_entry_t *entry = weak_entry_for_referent(weak_table, referent);
if (entry == nil) {
/// XXX shouldn't happen, but does with mismatched CF/objc
//printf("XXX no entry for clear deallocating %p\n", referent);
return;
}
首先初始化一下,获取到弱引用条目,顺便处理没有弱引用的情况。
// zero out references
weak_referrer_t *referrers;
size_t count;
if (entry->out_of_line()) {
referrers = entry->referrers;
count = TABLE_SIZE(entry);
}
else {
referrers = entry->inline_referrers;
count = WEAK_INLINE_COUNT;
}
获取弱引用变量位置数组和个数。
for (size_t i = 0; i < count; ++i) {
objc_object **referrer = referrers[i];
if (referrer) {
if (*referrer == referent) {
*referrer = nil;
}
else if (*referrer) {
_objc_inform("__weak variable at %p holds %p instead of %p. "
"This is probably incorrect use of "
"objc_storeWeak() and objc_loadWeak(). "
"Break on objc_weak_error to debug.\n",
referrer, (void*)*referrer, (void*)referent);
objc_weak_error();
}
}
}
weak_entry_remove(weak_table, entry);
}
循环将它们置为 nil
,最后移除整个弱引用条目。
访问弱引用
在访问一个弱引用时,ARC 会对其进行一些操作:
obj = weakObj;
// 会变成
objc_loadWeakRetained(&weakObj);
obj = weakObj;
objc_release(weakObj);
objc_loadWeakRetained
函数的主要作用就是调用了 rootTryRetain
函数:
ALWAYS_INLINE bool
objc_object::rootTryRetain()
{
return rootRetain(true, false) ? true : false;
}
实际上就是尝试对引用计数加 1,让弱引用对象在使用时不会被释放掉。
有关
rootRetain
的实现:《Objective-C 小记(7)retain & release》
总结
存放一个弱引用还真是哈希了很多次:
SideTable
哈希一次,这里分开来应该是为了性能原因;weak_table_t
哈希一次;weak_entry_t
哈希一次。
对于开销,直观感受上也并没有什么很大开销,想用就用呗……
作者:KylinRoc
链接:https://www.jianshu.com/p/eff6b9443800
來源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
Objective-C 小记(10)__weak的更多相关文章
- 计算缓存文件大小、清除缓存的Cell
计算缓存文件大小 - (void)getCacheSize { // 总大小 unsigned long long size = 0; // 获得缓存文件夹路径 NSString *cachesPat ...
- iOS开发 Masonry的简单使用
首先,在正式使用Masonry之前,我们先来看看在xib中我们是如何使用AutoLayout 从图中我们可以看出,只要设置相应得局限,控制好父视图与子视图之间的关系就应该很ok的拖出你需要的需 ...
- iOS开发 ReactiveCocoa入门教程 第二部分
ReactiveCocoa 是一个框架,它允许你在你的iOS程序中使用函数响应式(FRP)技术.加上第一部分的讲解,你将会学会如何使用信号量(对事件发出数据流)如何替代标准的动作和事件处理逻辑.你也会 ...
- Day 3 @ RSA Conference Asia Pacific & Japan 2016 (morning)
09.00 – 09.45 hrs Tracks Cloud, Mobile, & IoT Security A New Security Paradigm for IoT (Inter ...
- Automake
Automake是用来根据Makefile.am生成Makefile.in的工具 标准Makefile目标 'make all' Build programs, libraries, document ...
- ReactiveCocoa入门教程--第二部分
翻译自:http://www.raywenderlich.com/62796/reactivecocoa-tutorial-pt2 ReactiveCocoa 是一个框架,它允许你在你的iOS程序中使 ...
- 国内A股16家上市银行的財务数据与股价的因子分析报告(1)(工具:R)
分析人:BUPT_LX 研究目的 用某些算法对2014年12月份的16家国内A股上市的商业银行当中11项財务数据(资产总计.负债合计.股本.营业收入.流通股A.少数股东权益.净利润.经营活动的现金流量 ...
- # iOS Block的本质(三)
iOS Block的本质(三) 上一篇文章iOS Block的本质(二)中已经介绍过block变量的捕获,本文继续探寻block的本质. 1. block对对象变量的捕获,ARC 环境 block一般 ...
- 2017/7/26 SCJP英语学习
1 Declarations and Access Control ............... 1 Java Refresher . . . . . . . . . . . . . . . . . ...
- 【Python代码】混合整数规划MIP/线性规划LP+python(ortool库)实现
目录 相关知识点 LP线性规划问题 MIP混合整数规划 MIP的Python实现(Ortool库) assert MIP的Python实现(docplex库) 相关知识点 LP线性规划问题 Linea ...
随机推荐
- Ubuntu16.04+GTX 1080Ti+CUDA 8.0+cuDNN+Tesnorflow1.0深度学习服务器安装之路
0.安装背景 系统:ubuntu 16.04 内核:4.4.0-140-generic GPU:GTX 1080Ti nvidia驱动版本: 384.111 cuda: CUDA 8.0 深度学习库c ...
- ajax的cache缓存的使用方法
ajax中cache缓存的使用: 问题描述: 在IE.360浏览器上提交表单后,保存后的内容不回显(依然显示空或者之前的内容). 原因: 回显内容是使用ajax的get方式的请求查询数据,ajax的c ...
- winform控件命名规范对照表
WinForm Control 命名规范 数据类型 数据类型简写 标准命名举例 Label lbl lblMessage LinkLabel llbl llblToday Button btn btn ...
- python 3.x 学习笔记15(多线程)
1.线程进程进程:程序并不能单独运行,只有将程序装载到内存中,系统为它分配资源才能运行,而这种执行的程序就称之为进程,不具备执行感念,只是程序各种资源集合 线程:线程是操作系统能够进行运算调度的最小单 ...
- TLCL
参考阅读:http://billie66.github.io/TLCL/book/chap04.html 绝对路径 An absolute pathname begins with the root ...
- 大数相乘(牛客网ac通过)
2019-05-172019-05-17 大数相乘基本思想: 相乘相加,只不过大于10先不进位到计算完后统一进位 #include <iostream> #include <stri ...
- html5学习之第一步:认识标签,了解布局
图1. Acme United的网页的规划 Header区的例子包含了页面标题和副标题,< header>标签被用来创建页面的Header区的内容.除了网页本身之外,< header ...
- JavaScript函数练习
1. 判断一个数是否是素数 function isSushu (n) { n = n || 0; var isSu = true; for (var i = 2; i <= Math.sqrt( ...
- c traps and pitfalls reading notes(2)
1.运算符优先级,这个我是肯定记不住,每次遇到的时候都纠结下,然后去查下,或者直接括号,但是括号太多,你懂得,要用notepad才能理清各种层次.这里啦个下来,留着参考.
- ERROR in xxxx.js from UglifyJS——配置版本混杂版
常规解决套路可以参考这篇:https://segmentfault.com/a/11... 我采用了上面的做法,依然没法解决.我采用的是vue-cli脚手架自动生成的项目结构: vue-cli版本 2 ...