OC對(duì)象底層探索(本質(zhì)驻襟、創(chuàng)建流程夺艰、內(nèi)存對(duì)齊及空間大小)

目錄

作為一個(gè)開(kāi)發(fā)者沉衣,有一個(gè)學(xué)習(xí)的氛圍跟一個(gè)交流圈子特別重要郁副,這是一個(gè)我的iOS交流群:196800191,加群密碼:112233豌习,不管你是小白還是大牛歡迎入駐 霞势,分享BAT,阿里面試題、面試經(jīng)驗(yàn)斑鸦,討論技術(shù), 大家一起交流學(xué)習(xí)成長(zhǎng)草雕!

1. 概述

每個(gè)iOS開(kāi)始人員對(duì)OC語(yǔ)言并不陌生巷屿,雖然現(xiàn)在蘋(píng)果提倡swift開(kāi)發(fā),但是OC還是入門(mén)的必修課墩虹,平時(shí)開(kāi)發(fā)的時(shí)候嘱巾,我們通常就是調(diào)用各種API,很少探究其底層的原理诫钓,蘋(píng)果是如何在底層進(jìn)行封裝的呢旬昭?作為入行幾年的開(kāi)發(fā)者,還是有必要一探究竟菌湃。
OC是面向?qū)ο蟮恼Z(yǔ)言问拘,在代碼中最常見(jiàn)的就是創(chuàng)建一個(gè)對(duì)象了,那么對(duì)象是什么惧所,底層的結(jié)構(gòu)又是什么呢骤坐,是如何創(chuàng)建出來(lái)的呢?帶著這些問(wèn)題下愈,我們來(lái)開(kāi)始分析纽绍。

2. 對(duì)象是什么

對(duì)象在底層到底是個(gè)什么樣子呢?
在項(xiàng)目中創(chuàng)建一個(gè)GYMPerson類(lèi)势似,里面定義個(gè)name屬性和一個(gè)成員變量hobby拌夏,如下:

@interface GYMPerson : NSObject{
    NSString *hobby;
}
@property (nonatomic, copy) NSString *name;
@end

然后通過(guò)命令行將main.m文件轉(zhuǎn)成c++文件main.cpp.

clang -rewrite-objc main.m -o main.cpp

轉(zhuǎn)換完成后打開(kāi)main.cpp文件僧著,此時(shí)找到了:

struct GYMPerson_IMPL {
    struct NSObject_IMPL NSObject_IVARS;
    NSString *hobby;
    NSString *_name;
};

這個(gè)就是GYMPerson在底層的形式,一個(gè)結(jié)構(gòu)體障簿,并且繼承了父類(lèi)的所有屬性盹愚。
另外我們注意到hobby沒(méi)有下劃線,而name則有下劃線卷谈,我們都知道成員變量在底層保持不變杯拐,不會(huì)生成一個(gè)帶下劃線的成員變量的,而name是一個(gè)屬性世蔗,在底層是會(huì)生成一個(gè)帶下劃線的成員變量的端逼,而且還會(huì)增加getter和setter方法,如下:

static NSString * _I_GYMPerson_name(GYMPerson * self, SEL _cmd) { return (*(NSString **)((char *)self + OBJC_IVAR_$_GYMPerson$_name)); }
static void _I_GYMPerson_setName_(GYMPerson * self, SEL _cmd, NSString *name) { objc_setProperty (self, _cmd, __OFFSETOFIVAR__(struct GYMPerson, _name), (id)name, 0, 1); }

所以對(duì)象的本質(zhì)是什么污淋?毫無(wú)疑問(wèn)顶滩,結(jié)構(gòu)體。

3. 對(duì)象創(chuàng)建流程

我們?cè)诖a中調(diào)用alloc方法創(chuàng)建對(duì)象的時(shí)候寸爆,通常會(huì)經(jīng)歷以下幾個(gè)步驟礁鲁,簡(jiǎn)易圖如下:



當(dāng)在代碼中調(diào)用alloc的時(shí)候,例如[GYMPerson alloc]赁豆,那么在底層仅醇,代碼會(huì)先去哪里呢?
毫無(wú)疑問(wèn)魔种,當(dāng)?shù)谝淮蝿?chuàng)建GYMPerson對(duì)象的時(shí)候析二,是會(huì)來(lái)到下面這個(gè)方法的:

// Calls [cls alloc].
id
objc_alloc(Class cls)
{
    return callAlloc(cls, true/*checkNil*/, false/*allocWithZone*/);
}

其實(shí)這個(gè)方法沒(méi)什么,那么再來(lái)看看callAlloc這個(gè)方法

static ALWAYS_INLINE id
callAlloc(Class cls, bool checkNil, bool allocWithZone=false)
{
    if (slowpath(checkNil && !cls)) return nil;

#if __OBJC2__
    if (fastpath(!cls->ISA()->hasCustomAWZ())) {
        // No alloc/allocWithZone implementation. Go straight to the allocator.
        // fixme store hasCustomAWZ in the non-meta class and 
        // add it to canAllocFast's summary
        if (fastpath(cls->canAllocFast())) {
            // No ctors, raw isa, etc. Go straight to the metal.
            bool dtor = cls->hasCxxDtor();
            id obj = (id)calloc(1, cls->bits.fastInstanceSize());
            if (slowpath(!obj)) return callBadAllocHandler(cls);
            obj->initInstanceIsa(cls, dtor);
            return obj;
        }
        else {
            // Has ctor or raw isa or something. Use the slower path.
            id obj = class_createInstance(cls, 0);
            if (slowpath(!obj)) return callBadAllocHandler(cls);
            return obj;
        }
    }
#endif

    // No shortcuts available.
    if (allocWithZone) return [cls allocWithZone:nil];
    return [cls alloc];
}

在這個(gè)方法中有個(gè)OBJC2判斷节预,現(xiàn)在底層的代碼全都是objc2的了叶摄,宏定義的值為1.
隨后遇到 fastpath(!cls->ISA()->hasCustomAWZ()) 判斷,因?yàn)檫@個(gè)類(lèi)是第一次創(chuàng)建對(duì)象安拟,類(lèi)還沒(méi)有初始化(懶加載)蛤吓,因此無(wú)法判斷該類(lèi)是否實(shí)現(xiàn)了allocWithZone方法,因而判斷也不成立糠赦,所以直接跳到下面allocWithZone的判斷会傲,但是callAlloc在調(diào)用的時(shí)候,傳入的allocWithZone是false拙泽,因此直接走到return唆铐,調(diào)用 [cls alloc] 。

+ (id)alloc {
    return _objc_rootAlloc(self);
}

這一看奔滑,這也沒(méi)什么啊艾岂,別著急,繼續(xù)往下看

id _objc_rootAlloc(Class cls)
{
    return callAlloc(cls, false/*checkNil*/, true/*allocWithZone*/);
}

感覺(jué)被欺騙了朋其,怎么又回來(lái)了王浴?請(qǐng)注意脆炎,參數(shù)值不一樣了,此時(shí)的allocWithZone是true了氓辣。
此時(shí)如果沒(méi)有實(shí)現(xiàn)allocWithZone方法秒裕,那么 fastpath(!cls->ISA()->hasCustomAWZ()) 判斷則成立,進(jìn)入內(nèi)部的 fastpath(cls->canAllocFast()) 判斷钞啸,而這個(gè)判斷永遠(yuǎn)是false几蜻,如下:

bool canAllocFast() {
     assert(!isFuture());
     return bits.canAllocFast();
}
bool canAllocFast() {
     return false;
}

因此代碼會(huì)走到 id obj = class_createInstance(cls, 0) 創(chuàng)建對(duì)象。
那么如果用戶實(shí)現(xiàn)了allocWithZone方法体斩,第二次調(diào)用callAlloc傳入的allocWithZone參數(shù)為true梭稚,此時(shí) fastpath(!cls->ISA()->hasCustomAWZ()) 判斷不成立,代碼直接走到 [cls allocWithZone:nil] 方法中絮吵,然后調(diào)用allocWithZone方法弧烤,隨后調(diào)用 _class_createInstanceFromZone 方法。

上面我們提到了一個(gè)創(chuàng)建對(duì)象的方法class_createInstance(cls, 0)蹬敲,那我們看看這個(gè)方法:

id class_createInstance(Class cls, size_t extraBytes)
{
    return _class_createInstanceFromZone(cls, extraBytes, nil);
}

真是萬(wàn)變不離其中啊暇昂,最終又回到了我們實(shí)現(xiàn)allocWithZone方法后,底層調(diào)用的統(tǒng)一方法 _class_createInstanceFromZone伴嗡,下面我們來(lái)看一下這個(gè)方法急波。

static __attribute__((always_inline)) 
id
_class_createInstanceFromZone(Class cls, size_t extraBytes, void *zone, 
                              bool cxxConstruct = true, 
                              size_t *outAllocatedSize = nil)
{
    if (!cls) return nil;

    assert(cls->isRealized());

    // Read class's info bits all at once for performance
    bool hasCxxCtor = cls->hasCxxCtor();
    bool hasCxxDtor = cls->hasCxxDtor();
    bool fast = cls->canAllocNonpointer();

    size_t size = cls->instanceSize(extraBytes);
    if (outAllocatedSize) *outAllocatedSize = size;

    id obj;
    if (!zone  &&  fast) {
        obj = (id)calloc(1, size);
        if (!obj) return nil;
        obj->initInstanceIsa(cls, hasCxxDtor);
    } 
    else {
        if (zone) {
            obj = (id)malloc_zone_calloc ((malloc_zone_t *)zone, 1, size);
        } else {
            obj = (id)calloc(1, size);
        }
        if (!obj) return nil;

        // Use raw pointer isa on the assumption that they might be 
        // doing something weird with the zone or RR.
        obj->initIsa(cls);
    }

    if (cxxConstruct && hasCxxCtor) {
        obj = _objc_constructOrFree(obj, cls);
    }

    return obj;
}

這個(gè)方法中zone為nil, fast經(jīng)判斷后是true,因此代碼會(huì)走到下面的代碼中:

if (!zone  &&  fast) {
        obj = (id)calloc(1, size);
        if (!obj) return nil;
        obj->initInstanceIsa(cls, hasCxxDtor);
    }

這個(gè)if分支中就做了兩件事情瘪校,第一:在內(nèi)存中給這個(gè)對(duì)象開(kāi)辟空間幔崖,相當(dāng)于在小區(qū)里面申請(qǐng)了一套房子;第二:初始化isa渣淤,綁定對(duì)應(yīng)的類(lèi)信息,相當(dāng)于給這套房子弄個(gè)房本吉嫩,里面有具體的信息价认。
至于calloc和isa以后會(huì)講到,還有一個(gè)很重要的方法 cls->instanceSize(extraBytes) 馬上就會(huì)講到自娩,繼續(xù)往下看哦用踩!

以上則是一個(gè)對(duì)象的初始化過(guò)程,現(xiàn)在我們將上面的簡(jiǎn)易圖復(fù)雜化一下:


4. 對(duì)象空間大小及內(nèi)存對(duì)齊

上面我們主要探究了對(duì)象創(chuàng)建的流程忙迁,現(xiàn)在我們說(shuō)一下對(duì)象所需要空間的大小脐彩,以及字節(jié)對(duì)齊問(wèn)題。
還記得剛才說(shuō)過(guò)的方法嗎姊扔?

size_t instanceSize(size_t extraBytes) {
        size_t size = alignedInstanceSize() + extraBytes;
        // CF requires all objects be at least 16 bytes.
        if (size < 16) size = 16;
        return size;
    }

這個(gè)方法主要計(jì)算對(duì)象所需要的內(nèi)存空間的大小惠奸,在了解如何計(jì)算之前,我們先看一下內(nèi)存對(duì)齊原則:

  1. 結(jié)構(gòu)(struct)(或聯(lián)合(union))的數(shù)據(jù)成員恰梢,第?個(gè)數(shù)據(jù)成員放在位置為0的地?佛南,以后每個(gè)數(shù)據(jù)成員存儲(chǔ)的起始位置要從該成員??或者成員的?成員??(只要該成員有?成員梗掰,?如說(shuō)是數(shù)組,結(jié)構(gòu)體等)的整數(shù)倍開(kāi)始(?如int為4字節(jié),則要從4的整數(shù)倍地址開(kāi)始)存儲(chǔ)嗅回。

  2. 如果?個(gè)結(jié)構(gòu)?有某些結(jié)構(gòu)體成員,則結(jié)構(gòu)體成員要從其內(nèi)部最?元素??的整數(shù)倍地址開(kāi)始存儲(chǔ).(struct a?存有struct b,b?有char,int ,double等元素,那b應(yīng)該從8的整數(shù)倍開(kāi)始儲(chǔ))及穗。

  3. 結(jié)構(gòu)體的總??,必須是其內(nèi)部最?成員的整數(shù)倍.不?的要補(bǔ)齊绵载。
    聽(tīng)起來(lái)是不是有些亂呢埂陆,來(lái),還是看代碼理解吧娃豹,下面有四個(gè)結(jié)構(gòu)體:

struct Struct1 {
    char a;
    int b;
    double c;
    short d;
} Struct1;

struct Struct2 {
    double ;
    int b;
    char c;
    short d;
} Struct2;

struct Struct3 {
    int a;
    double b;
    char c;
    short d;
    struct Struct2 e;
} Struct3;

struct Struct4 {
    int a;
    double b;
    char c;
    short d;
    struct Struct1 e;
} Struct4;

- (void)instanceSizeFunction {
    NSLog(@"Struct1 size = %lu", sizeof(Struct1));
    NSLog(@"Struct2 size = %lu", sizeof(Struct2));
    NSLog(@"Struct3 size = %lu", sizeof(Struct3));
    NSLog(@"Struct4 size = %lu", sizeof(Struct4));
}

我們調(diào)用instanceSizeFunction方法查看一下結(jié)果:

GYMDemo[41131:3568504] Struct1 size = 24
GYMDemo[41131:3568504] Struct2 size = 16
GYMDemo[41131:3568504] Struct3 size = 40
GYMDemo[41131:3568504] Struct4 size = 48

首先我們分析一下原則1焚虱,將其簡(jiǎn)化成一個(gè)公式:min(position, size),如果是存儲(chǔ)第一個(gè)元素培愁,那么直接放到0的位置著摔,從第二個(gè)元素開(kāi)始,采用這個(gè)公式定续,position是第二個(gè)及以后元素存儲(chǔ)的最小開(kāi)始位置谍咆,size則是元素的大小(比如int為4字節(jié))私股,公式的原理就是取position是size的最小整數(shù)倍的值作為存儲(chǔ)某一元素的開(kāi)始位置摹察。
我們看Struct1結(jié)構(gòu)體:

struct Struct1 {
    char a;  // 1 字節(jié)
    int b;   // 4 字節(jié)
    double c;// 8 字節(jié)
    short d; // 2 字節(jié)
} Struct1;

將a存入0位置的時(shí)候,只占用了1個(gè)字節(jié)倡鲸,此時(shí)position指向下一個(gè)可存儲(chǔ)的起始位置供嚎,也就是1,而下一個(gè)元素b是4字節(jié)峭状,那么position就往后移動(dòng)克滴,當(dāng)為4的時(shí)候(4為int(4字節(jié))的整數(shù)倍),存入b优床,則b存在4 5 6 7四個(gè)位置劝赔,此時(shí)position為8,下一個(gè)元素c胆敞,8個(gè)字節(jié)着帽,position正好是整數(shù)倍,于是開(kāi)始存c移层,則c存在8 9 10 11 12 13 14 15八個(gè)位置仍翰,此時(shí)position為16,正好是元素d(2字節(jié)的整數(shù)倍)观话,則d存在16 17兩個(gè)位置予借,這么一算結(jié)構(gòu)體Struct1一共占用了18個(gè)字符,但是別忘了原則3,結(jié)構(gòu)體整體大小是其內(nèi)部最大元素大小的整數(shù)倍蕾羊,iOS64位下最大的數(shù)據(jù)類(lèi)型占8字節(jié)喧笔,所以結(jié)構(gòu)體總大小應(yīng)是8的整數(shù)倍,那么比18大的最小整數(shù)倍即為24龟再,所以Struct1的總大小為24字節(jié)书闸。
如下入所示:



Struct2內(nèi)存對(duì)齊如下:

struct Struct2 {
    double a;  // 8 字節(jié)
    int b;     // 4 字節(jié)
    char c;    // 1 字節(jié)
    short d;   // 2 字節(jié)
} Struct2;

Struct3內(nèi)存對(duì)齊:

struct Struct3 {
    int a;      // 4 字節(jié)
    double b;   // 8 字節(jié)
    char c;     // 1 字節(jié)
    short d;    // 2 字節(jié) 
    struct Struct2 e; // 16 字節(jié)
} Struct3;

由結(jié)構(gòu)體定義可知,Struct3中有個(gè)結(jié)構(gòu)體成員e利凑,這涉及到了內(nèi)存對(duì)齊的第二個(gè)原則浆劲。
其內(nèi)存對(duì)齊如下圖:



至于Struct4,感興趣的朋友可以自己算一算哀澈。

上面說(shuō)完了內(nèi)存對(duì)齊的原則以及結(jié)構(gòu)體內(nèi)存對(duì)齊牌借,下面回過(guò)頭看看創(chuàng)建對(duì)象時(shí)內(nèi)存大小是如何計(jì)算的。在創(chuàng)建對(duì)象的過(guò)程中割按,最后在calloc方法之前膨报,調(diào)用了 instanceSize(size_t extraBytes) 方法計(jì)算了對(duì)象申請(qǐng)的內(nèi)存空間大小,見(jiàn)下面的方法:

/**
方法中則調(diào)用 **alignedInstanceSize()** 方法進(jìn)行計(jì)算适荣,另外請(qǐng)注意下面還有個(gè)if判斷现柠,如果計(jì)算出來(lái)的size<16,那么size就為16.
*/
size_t instanceSize(size_t extraBytes) {
    size_t size = alignedInstanceSize() + extraBytes;
    // CF requires all objects be at least 16 bytes.
    if (size < 16) size = 16;
    return size;
}
/**
方法中將未內(nèi)存對(duì)齊的類(lèi)的屬性的總大小傳入 **word_align()** 方法中進(jìn)行對(duì)齊計(jì)算弛矛。
*/
uint32_t alignedInstanceSize() {
    return word_align(unalignedInstanceSize());
}
// 返回類(lèi)的ivar中所有屬性的總大小够吩。
uint32_t unalignedInstanceSize() {
    assert(isRealized());
    return data()->ro->instanceSize;
}
// 計(jì)算內(nèi)存對(duì)齊后對(duì)象需要的空間大小,詳見(jiàn)下面講解:
static inline uint32_t word_align(uint32_t x) {
    return (x + WORD_MASK) & ~WORD_MASK;
}

方法解析:

我們知道NSObject對(duì)象有一個(gè)屬性丈氓,那就是isa周循,是一個(gè)指針類(lèi)型,所占空間大小為8字節(jié)万俗,如果創(chuàng)建一個(gè)NSObject湾笛,我們看看這個(gè)方法如何計(jì)算的。
首先看一下宏定義:
#define WORD_MASK 7UL
很顯然在64位下闰歪,WORD_MASK為7.
當(dāng)傳入的x為8的時(shí)候嚎研,那么x + WORD_MASK為15,其二進(jìn)制為:0000 1111
WORD_MASK的二進(jìn)制為:0000 0111课竣, 那么~WORD_MASK的二進(jìn)制為:1111 1000
那么15 & ~7的計(jì)算為:
    0000 1111
&   1111 1000
=   0000 1000
0000 1000的十進(jìn)制結(jié)果為8,那么當(dāng)傳入x值為8的時(shí)候置媳,經(jīng)過(guò)計(jì)算后得到的結(jié)果為8字節(jié)于樟。

是不是感覺(jué)有些巧合,都是8拇囊,好迂曲,那么假設(shè)傳入x=9,我們?cè)谟?jì)算一遍寥袭。

當(dāng)傳入的x為9的時(shí)候路捧,那么x + WORD_MASK為16关霸,其二進(jìn)制為:0001 0000
那么16 & ~7的計(jì)算為:
    0001 0000
&   1111 1000
=   0001 0000
0001 0000的十進(jìn)制結(jié)果為16,由此可知杰扫,類(lèi)的屬性總空間大小為9队寇,經(jīng)過(guò)對(duì)齊后需要的空間為16.

由上面的分析可以,對(duì)象申請(qǐng)內(nèi)存空間的大小是8字節(jié)對(duì)齊計(jì)算的章姓。

經(jīng)過(guò)上面這一波計(jì)算佳遣,我們得到的內(nèi)存對(duì)齊后的數(shù)值就是對(duì)象創(chuàng)建的時(shí)候,向內(nèi)存申請(qǐng)的空間大小凡伊,那么計(jì)算機(jī)真的是按照這個(gè)數(shù)值開(kāi)辟的空間嗎零渐?請(qǐng)看下面章節(jié)。

5. 系統(tǒng)開(kāi)辟空間大小

計(jì)算機(jī)系統(tǒng)真的是按照對(duì)象申請(qǐng)的空間大小來(lái)開(kāi)辟空間嗎系忙?
答案:不是诵盼。
系統(tǒng)在calloc方法中,對(duì)于開(kāi)辟多大的空間银还,有自己的算法风宁。在探索calloc底層源碼的時(shí)候,有一個(gè)很重要的方法见剩,如下:

segregated_size_to_fit(nanozone_t *nanozone, size_t size, size_t *pKey)
{
    size_t k, slot_bytes;
    if (0 == size) {
        size = NANO_REGIME_QUANTA_SIZE; // Historical behavior
    }
    k = (size + NANO_REGIME_QUANTA_SIZE - 1) >> SHIFT_NANO_QUANTUM; // round up and shift for number of quanta
    slot_bytes = k << SHIFT_NANO_QUANTUM;                           // multiply by power of two quanta size
    *pKey = k - 1;                                                  // Zero-based!

    return slot_bytes;
}

還有兩個(gè)關(guān)鍵的宏定義:

#define SHIFT_NANO_QUANTUM      4
#define NANO_REGIME_QUANTA_SIZE (1 << SHIFT_NANO_QUANTUM) // 16

方法解析:

假如方法中傳入的size為24(對(duì)象經(jīng)過(guò)內(nèi)存對(duì)齊后申請(qǐng)空間的大猩迸础),我們看一下這行:
k = (size + NANO_REGIME_QUANTA_SIZE - 1) >> SHIFT_NANO_QUANTUM;
將宏替換掉后:
k = (24 + 16 - 1) >> 4;
即:k = 39 >> 4, 二進(jìn)制表示為:0010 0111 >> 4 = 0000 0010 = 2
再k計(jì)算完后苍苞,又進(jìn)行了:
slot_bytes = k << SHIFT_NANO_QUANTUM; 
即 slot_bytes = k << 4固翰,二進(jìn)制表示為:0000 0010 << 4 = 0010 0000 = 32
最終得到的slot_bytes為32,也就是系統(tǒng)為這個(gè)對(duì)象開(kāi)辟的實(shí)際空間的大小羹呵。

由上可知骂际,在callloc底層,系統(tǒng)將傳入的size右移4位冈欢,再左移4位歉铝,也就是16字節(jié)對(duì)齊。

下面舉個(gè)例子:
定義一個(gè)GYMDeveloper類(lèi)凑耻,繼承GYMPerson太示,GYMPerson

@interface GYMPerson : NSObject

@end
@interface GYMDeveloper : GYMPerson
// isa // 8字節(jié)
@property (nonatomic, copy) NSString *name; // 8字節(jié)
@property (nonatomic, assign) int age; //4字節(jié)
@property (nonatomic, assign) long height;  // 8字節(jié)
@property (nonatomic, copy) NSString *selfIntroduce; // 8字節(jié)
@end

對(duì)于GYMDeveloper,如果要?jiǎng)?chuàng)建一個(gè)GYMDeveloper的實(shí)例對(duì)象香浩,很容易就會(huì)算出該對(duì)象所需要的空間大小类缤,即40字節(jié),不要忘了老祖宗NSObject還有isa指針邻吭,占8字節(jié)呢餐弱。
我們通過(guò)下面的方法測(cè)試一下:

- (void)instanceSizeFunction {
    GYMDeveloper *developer = [GYMDeveloper alloc];
    NSLog(@"對(duì)象申請(qǐng)的空間是:%lu字節(jié), 系統(tǒng)開(kāi)辟的空間是:%lu字節(jié)", class_getInstanceSize([developer class]), malloc_size((__bridge const void *)(developer)));
}

輸出結(jié)果為:

GYMDemo[51386:4021321] 對(duì)象申請(qǐng)的空間是:40字節(jié), 系統(tǒng)開(kāi)辟的空間是:48字節(jié)

綜上所述:對(duì)象申請(qǐng)空間的大小是8字節(jié)對(duì)齊計(jì)算的,而系統(tǒng)為對(duì)象開(kāi)辟空間是16字節(jié)對(duì)齊計(jì)算的。

寫(xiě)在最后:寫(xiě)文章不容易膏蚓,如果您覺(jué)得好就給個(gè)贊瓢谢,如果文章有問(wèn)題還請(qǐng)指正,轉(zhuǎn)載的話驮瞧,請(qǐng)標(biāo)注原文地址哦氓扛!

原文作者:Daniel_Coder

原文地址:https://blog.csdn.net/guoyongming925/article/details/108859202

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市剧董,隨后出現(xiàn)的幾起案子幢尚,更是在濱河造成了極大的恐慌,老刑警劉巖翅楼,帶你破解...
    沈念sama閱讀 219,188評(píng)論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件尉剩,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡毅臊,警方通過(guò)查閱死者的電腦和手機(jī)理茎,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,464評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)管嬉,“玉大人皂林,你說(shuō)我怎么就攤上這事◎橇茫” “怎么了础倍?”我有些...
    開(kāi)封第一講書(shū)人閱讀 165,562評(píng)論 0 356
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)胎挎。 經(jīng)常有香客問(wèn)我沟启,道長(zhǎng),這世上最難降的妖魔是什么犹菇? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,893評(píng)論 1 295
  • 正文 為了忘掉前任德迹,我火速辦了婚禮,結(jié)果婚禮上揭芍,老公的妹妹穿的比我還像新娘胳搞。我一直安慰自己,他們只是感情好称杨,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,917評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布肌毅。 她就那樣靜靜地躺著,像睡著了一般姑原。 火紅的嫁衣襯著肌膚如雪悬而。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 51,708評(píng)論 1 305
  • 那天页衙,我揣著相機(jī)與錄音摊滔,去河邊找鬼。 笑死店乐,一個(gè)胖子當(dāng)著我的面吹牛艰躺,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播眨八,決...
    沈念sama閱讀 40,430評(píng)論 3 420
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼腺兴,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了廉侧?” 一聲冷哼從身側(cè)響起页响,我...
    開(kāi)封第一講書(shū)人閱讀 39,342評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎段誊,沒(méi)想到半個(gè)月后闰蚕,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,801評(píng)論 1 317
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡连舍,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,976評(píng)論 3 337
  • 正文 我和宋清朗相戀三年没陡,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片索赏。...
    茶點(diǎn)故事閱讀 40,115評(píng)論 1 351
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡盼玄,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出潜腻,到底是詐尸還是另有隱情埃儿,我是刑警寧澤,帶...
    沈念sama閱讀 35,804評(píng)論 5 346
  • 正文 年R本政府宣布融涣,位于F島的核電站童番,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏暴心。R本人自食惡果不足惜妓盲,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,458評(píng)論 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望专普。 院中可真熱鬧悯衬,春花似錦、人聲如沸檀夹。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 32,008評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)炸渡。三九已至娜亿,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間蚌堵,已是汗流浹背买决。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,135評(píng)論 1 272
  • 我被黑心中介騙來(lái)泰國(guó)打工沛婴, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人督赤。 一個(gè)月前我還...
    沈念sama閱讀 48,365評(píng)論 3 373
  • 正文 我出身青樓嘁灯,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親躲舌。 傳聞我的和親對(duì)象是個(gè)殘疾皇子丑婿,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,055評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容