亚洲乱码中文字幕综合,中国熟女仑乱hd,亚洲精品乱拍国产一区二区三区,一本大道卡一卡二卡三乱码全集资源,又粗又黄又硬又爽的免费视频

C++語(yǔ)義copy and swap示例詳解

 更新時(shí)間:2022年11月02日 10:31:12   作者:庖丁解牛  
這篇文章主要為大家介紹了C++語(yǔ)義copy and swap示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪

class對(duì)象的初始化

我們有一個(gè)class Data, 里面有一個(gè)int m_d 變量,存儲(chǔ)一個(gè)整數(shù)。

class Data
{
    int m_i;
    public:
    void print()
    {
        std::cout << m_i << std::endl;
    }
};

我們?nèi)绻枰粋€(gè)Data類(lèi)的對(duì)象的話,可以這樣寫(xiě):

void test()
{
    Data d;
    d.print(); // 打印內(nèi)部的變量 m_i
}

看到這里,應(yīng)該能發(fā)現(xiàn)問(wèn)題,雖然 d 變量已經(jīng)實(shí)例化了,但是,我們好像沒(méi)有在初始化的時(shí)候指定內(nèi)部m_i到底是什么值。

有沒(méi)有一種可能性,我們并沒(méi)有將 d 所引用的內(nèi)存變成一個(gè)可以使用的狀態(tài)。

比如說(shuō),這里提一個(gè)業(yè)務(wù)需求,內(nèi)部的m_i只能是奇數(shù)。

而上述代碼中的變量d所引用的內(nèi)存中的m_i到底是什么數(shù),是未知的,有可能你的編譯器將m_i的初始值設(shè)置成了0,但這是于事無(wú)補(bǔ)的,因?yàn)槲覀兊臉I(yè)務(wù)需求是:

  • m_i 必須是奇數(shù)

所有用到d的地方,都會(huì)有這個(gè)假設(shè),所以如果在初始化d的時(shí)候,沒(méi)有保證這個(gè)m_i是奇數(shù)的話,那么后續(xù)的所有業(yè)務(wù)邏輯全部都會(huì)崩潰。

說(shuō)了這么多,實(shí)際上就是想道明一句話:

  • 想要使用一個(gè)類(lèi)對(duì)象,先進(jìn)行初始化,這個(gè)對(duì)象的內(nèi)存變成一個(gè)合法的狀態(tài)。

合法的狀態(tài)大部分跟業(yè)務(wù)邏輯相關(guān),比如上面的m_i必須是奇數(shù)

constructor 構(gòu)造器

對(duì)象在實(shí)例化的時(shí)候,大抵有這么兩步:

  • 分配內(nèi)存:這里分棧和堆,又叫自動(dòng)分配內(nèi)存(函數(shù)棧自動(dòng)張開(kāi))和手動(dòng)(使用new操作符在堆上申請(qǐng))
  • 填充內(nèi)存

分配好的內(nèi)存,幾乎都是混沌的,完全不知道里面存的數(shù)據(jù)是什么,所以需要第二步填充內(nèi)存,使得這塊內(nèi)存變成合法的

而 constructor 的最大職責(zé)就是這個(gè)。(打開(kāi)文件,打開(kāi)數(shù)據(jù)庫(kù),或者網(wǎng)絡(luò)連接也能在這里面干)

這意思就是,constructor 執(zhí)行的時(shí)機(jī)一定是在內(nèi)存已經(jīng)準(zhǔn)備好了的時(shí)候。

拿上面的例子,我們這樣來(lái)確保一個(gè)合法的m_i:

class Data
{
    int m_i;
    public:
    Data(int i): m_i{i} // 變量m_i初始化
    {}
};
void test()
{
    Data d{3};// 這里確保了變量 m_i 為 3
}

也許不想在初始化的非要想一個(gè)合法值傳給m_i,我們可以搞一個(gè)默認(rèn)constructor:

class Data
{
    int m_i;
    public:
    Data():m_i{1}
    {}
};
void test()
{
    Data d{}; // 這里不用填參數(shù)
}

constructor overload 構(gòu)造器重載

constructor的形式有很多,但是它本質(zhì)上就是一個(gè)函數(shù),在初始化的時(shí)候會(huì)調(diào)用而已。

只要是函數(shù),那么就可以按照一般的函數(shù)的重載規(guī)則進(jìn)行重載。

上面的例子已經(jīng)說(shuō)明了這個(gè)用法

    Data() : m_i{1}        // 不帶參數(shù)
    Data(int i) : m_i{i}   // 帶了一個(gè)int參數(shù) i

所以一個(gè)類(lèi)該有什么樣的constructor,由業(yè)務(wù)邏輯自己決定。

copy constructor 拷貝構(gòu)造器

還是上面的Data的例子:

void test
{
    Data d1{5};   調(diào)用 Data(int i) 進(jìn)行初始化
    Data d2{d1}; // 這個(gè)是啥?????
}

從寫(xiě)法上來(lái)看,我們可以猜測(cè)到,d2.m_i 應(yīng)該拷貝自 d1.m_i, 所以最后的結(jié)果是 5。

這沒(méi)問(wèn)題的,但是我們前面說(shuō)了,初始化一定是調(diào)用了某個(gè)constructor,那么這里是調(diào)用的哪個(gè)constructor呢?

答案是:

Data(const Data& other);

形如這樣的參數(shù)是這樣的constructor,還特意起了個(gè)名字:copy constructor, 也就是拷貝構(gòu)造器。

這個(gè)函數(shù)接受一個(gè)參數(shù),我們起了個(gè)名叫other,所以一看就明白了,這個(gè)other就是我們想要拷貝的對(duì)象。

這個(gè)constructor,我們并沒(méi)有手動(dòng)提供,所以這是編譯器自動(dòng)給我們加上去的。

你可能會(huì)問(wèn),編譯器怎么知道這個(gè)函數(shù)內(nèi)部應(yīng)該怎樣實(shí)現(xiàn)?

對(duì)啊,編譯器不知道,他對(duì)我們的業(yè)務(wù)邏輯以及合法性一無(wú)所知,所以,編譯器只能提供一個(gè)比較基礎(chǔ)的功能:

  • 逐個(gè)成員變量拷貝

Data類(lèi)里只有一個(gè)m_i, 所以這里編譯器提供的這個(gè)constructor,就是做了大概這樣的事情:

class Data
{
    int m_i;
    public:
    Data(const Data& other):m_i{other.m_i}
    {}
};

像m_i這種基礎(chǔ)類(lèi)型,就是直接拷貝了。那如果Data類(lèi)內(nèi)部有class類(lèi)型的變量呢:

class Foo
{
    int m_i;
};
class Data
{
    Foo m_f;
};

從形式上看,編譯器給我們提供的默認(rèn)的拷貝構(gòu)造器,應(yīng)該是這樣的:

class Data
{
    Foo m_f;
    public:
    Data(const Data& other):m_f{other.m_f}
    {}
};

雖然m_f不是基本類(lèi)型的變量,但是形式上來(lái)看,和基本變量是一致的。

有必要提一下:

m_f{other.m_f}

這句,實(shí)際上繼續(xù)調(diào)用了Foo類(lèi)的拷貝構(gòu)造,所以到這里,那就是Foo類(lèi)的事情了,與Data類(lèi)無(wú)關(guān)了。

總之:

  • 拷貝構(gòu)造器,就是一個(gè)普通的構(gòu)造器,接收一個(gè)參數(shù)const T &
  • 拷貝構(gòu)造器,可以讓我們新產(chǎn)生的對(duì)象去拷貝一個(gè)已有的老對(duì)象,進(jìn)行初始化
  • 如果我們不提供一個(gè)拷貝構(gòu)造器,那么編譯器會(huì)給我們搞一個(gè)默認(rèn)的,逐個(gè)成員拷貝的,拷貝構(gòu)造器

拷貝構(gòu)造器的調(diào)用時(shí)機(jī)

上面已經(jīng)說(shuō)過(guò)一種:

Data d1{};
Data d2{d1} // 這里會(huì)調(diào)用拷貝構(gòu)造器

事實(shí)上,還有別的時(shí)候,拷貝構(gòu)造器會(huì)被調(diào)用,那就是函數(shù)的傳參,和返回值。

class Data{}; // 內(nèi)部省略
void foo(Data d)
{
    // 一些邏輯
}
void test()
{
    Data d1{};
    foo(d1); // 這一句調(diào)用了拷貝構(gòu)造器
}

函數(shù)傳參的時(shí)候,如果是值類(lèi)型參數(shù),那么會(huì)調(diào)用拷貝構(gòu)造器。

再來(lái)看看函數(shù)返回值:

class Data{}; // 內(nèi)部省略
Data getData()
{
    Data d1{};
    return d1; // 這里也是調(diào)用拷貝構(gòu)造器
}
void test()
{
    Data d{getData()}; // 這里依然調(diào)用了拷貝構(gòu)造器
}

從理論上來(lái)看,上面的 Data d{getData()} 這一句應(yīng)該調(diào)用兩次拷貝構(gòu)造

  • 第一次是函數(shù)getData內(nèi)部的一個(gè)局部d1,拷貝給了一個(gè)臨時(shí)匿名變量
  • 第二次是這個(gè)臨時(shí)匿名變量拷貝給了變量d

但是如果你在拷貝構(gòu)造器里加上打印,你會(huì)發(fā)現(xiàn),沒(méi)有任何東西會(huì)打印出來(lái),也就是說(shuō),壓根就沒(méi)有調(diào)用到拷貝構(gòu)造器。

這不代表上面關(guān)于函數(shù)的說(shuō)法是錯(cuò)的,這只是編譯器的優(yōu)化而已,因?yàn)閬?lái)來(lái)回回的拷貝,實(shí)在是沒(méi)有必要,所以在某些編譯器認(rèn)為可以的情況下,編譯器就直接省了。這個(gè)不重要,就不具體往里面細(xì)說(shuō)規(guī)則了。

自定義拷貝構(gòu)造器

大部分時(shí)候,編譯器生成的這個(gè)拷貝構(gòu)造器就滿足需求了。

但是,如果我們的class包含了動(dòng)態(tài)資源,比如說(shuō)一個(gè)堆上動(dòng)態(tài)的int數(shù)組, 默認(rèn)的拷貝構(gòu)造器就沒(méi)那么好用了:

class Data
{
    int m_size; // 數(shù)組的元素個(gè)數(shù)
    int* m_ptr; // 指向數(shù)組首元素的指針
    public:
    Data(int size):m_size{size}
    {
        if (size > 0)
        {
            m_ptr = new int[size]{};
        }
    }
    ~Data()
    {
        delete[] m_ptr;
    }
};

由于這個(gè)Data類(lèi),擁有一個(gè)動(dòng)態(tài)的數(shù)組,所以我們提供了一個(gè)析構(gòu)函數(shù),省的這塊內(nèi)存不會(huì)被回收。

然后,我們沒(méi)有提供一個(gè)拷貝構(gòu)造器,所以編譯器就給我們添加了一個(gè):

class Data
{
    // 忽略別的代碼,現(xiàn)在只關(guān)注拷貝構(gòu)造器
    Data(const Data& other):m_size{other.m_size}, m_ptr{other.m_ptr}
    {}
};
void test()
{
    Data d1{10}; // 第一句
    Data d2{d1}; // 第二句
}

沒(méi)什么懸念,就是按照成員,逐個(gè)拷貝,注意,連指針也是直接拷貝。

所以上述test函數(shù)中,第二句執(zhí)行了之后,整個(gè)內(nèi)存應(yīng)該是這樣的:

image.png

這有問(wèn)題嗎?

有很大的問(wèn)題,考慮一下test函數(shù)執(zhí)行完畢前,是不是需要對(duì)這兩個(gè)變量 d1,d2d1, d2d1,d2 進(jìn)行析構(gòu)。

你會(huì)發(fā)現(xiàn),兩次析構(gòu),delete 的資源是一份?。?!

一份資源,被delete兩次,這就是所謂double free問(wèn)題。

還有別的問(wèn)題嗎?

有??紤]下面的代碼:

void foo(Data d)
{
    // 一些邏輯
}
void test()
{
    Data d1{10};
    foo(d1);
    //
}

上面代碼里,foo執(zhí)行完之前,會(huì)析構(gòu)這個(gè)局部變量d!導(dǎo)致資源已經(jīng)被delete!

而外面d1和里面的d,指向的是同一份資源,也就是說(shuō),foo執(zhí)行完之后,d1.m_ptr 成為了一個(gè)懸掛指針!

沒(méi)辦法了,只能靠自己定義拷貝構(gòu)造器,來(lái)解決上面的問(wèn)題了:

class Data
{
    int m_size; // 動(dòng)態(tài)數(shù)組的元素個(gè)數(shù)
    int* m_ptr; // 指向數(shù)據(jù)的指針
    public:
    Data(const Data& other){
        if(other.m_ptr)
        {
            auto temp_ptr { new int[other.m_size]};
            std::copy(other.m_ptr, other.m_ptr + other.m_size, temp_ptr);
            m_ptr = temp_ptr;
            m_size = other.m_size;
        }
        else
        {
            m_ptr = nullptr;
        }
    }
};

上面的拷貝構(gòu)造器,才是真正的拷貝,這種拷貝一般稱(chēng)之為深拷貝

進(jìn)行深拷貝之后,新對(duì)象和老對(duì)象,各自都有一份資源,不會(huì)再有任何粘連了。

拷貝賦值,copy assignment

想要完成深拷貝,到現(xiàn)在只進(jìn)行了一半。

剩下的一般就是重載一個(gè)操作符,operator=,這是用來(lái)解決如下形式的拷貝:

Data d1{10};
Data d2{2};
///
d2 = d1;

這里,兩個(gè)變量 d1,d2d1, d2d1,d2 都自己進(jìn)行了初始化,在經(jīng)過(guò)一堆代碼邏輯之后,此時(shí)我們的需求是:

  • 清除 d2 的數(shù)據(jù)
  • 將 d1 完整的拷貝給 d2

兩個(gè)類(lèi)對(duì)象之間用賦值操作符,其實(shí)是調(diào)用了一個(gè)成員函數(shù):operator=。

對(duì),這玩意雖然是操作符,但是操作符本質(zhì)上也還是函數(shù),這個(gè)函數(shù)的名字就是operator=

還是一樣的,如果我們不提供一個(gè)自定義的operator=, 那么編譯器會(huì)給我們添加一個(gè)如下的:

class Data
{
    int m_size;
    int* m_ptr;
    public:
    Data(int size):m_size{size} // 普通構(gòu)造器
    {
        if (size > 0)
        {
            m_ptr = new int[size]{};
        }
    }
    Data(const Data& other) // 拷貝構(gòu)造器
    {
        if(other.m_ptr)
        {
            auto temp_ptr { new int[other.m_size]};
            std::copy(other.m_ptr, other.m_ptr + other.m_size, temp_ptr);
            m_ptr = temp_ptr;
            m_size = other.m_size;
        }
        else
        {
            m_ptr = nullptr;
        }
    }
    ~Data()               // 析構(gòu)
    {
        delete[] m_ptr;
    }
    ///////// 編譯器自動(dòng)添加的 operator=
    Data& operator=(const Data& other)
    {
        m_size = other.m_size;
        m_ptr = other.m_ptr;
        return *this;
    }
};

看這個(gè)編譯器自動(dòng)添加的operator=, 是顯而易見(jiàn)能發(fā)現(xiàn)問(wèn)題的:

  • 自身的m_ptr指向的內(nèi)存永遠(yuǎn)無(wú)法回收了

自定義 operator=

還是得靠自己來(lái)編寫(xiě) operator=

前方警告,終于要點(diǎn)題了,copy and swap 即將出現(xiàn)。

先按照我們的思路來(lái)寫(xiě)一個(gè):

Data& operator=(const Data& other)
{
    // 1. 首先清除本身的資源
    delete[] m_ptr;
    // 2. 拷貝other的資源
    m_size = other.m_size;
    if (other.m_ptr)
    {
        m_ptr = new int[m_size];
        std::copy(other.m_ptr, other.m_ptr+m_size, m_ptr);
    }
    return *this;
}

如果按照上面的代碼,來(lái)看下面的test函數(shù),會(huì)發(fā)生什么問(wèn)題:

void test()
{
    Data d1{10};
    d1 = d1; // 自己賦值給自己
}

我們?cè)?code>operator=里面看見(jiàn),上來(lái)直接把整個(gè)資源刪除了,GG!

我們要加一個(gè)判斷:

Data& operator=(const Data& other)
{
    if (this == &other) // 加了一個(gè)判斷
    {
        return *this;
    }
    // 1. 首先清除本身的資源
    delete[] m_ptr;
    // 2. 拷貝other的資源
    m_size = other.m_size;
    if (other.m_ptr)
    {
        m_ptr = new int[m_size]; // 這句有可能異常
        std::copy(other.m_ptr, other.m_ptr+m_size, m_ptr);
    }
    return *this;
}

關(guān)于這里加不加判斷,很多大師級(jí)人物也認(rèn)為不該加:

  • 誰(shuí)會(huì)寫(xiě)出這種 d1 = d1; 這種代碼???加了判斷,徒增煩惱而已。

再來(lái)看上面注釋那個(gè), new 在申請(qǐng)新的內(nèi)存的時(shí)候,可能會(huì)發(fā)生異常,此時(shí)出現(xiàn)了一個(gè)問(wèn)題,在文章開(kāi)頭提及的:

  • 內(nèi)存合法性

m_size 已經(jīng)拷貝過(guò)來(lái)了
而真正的數(shù)據(jù)沒(méi)有拷貝過(guò)來(lái),導(dǎo)致這兩個(gè)變量,不滿足我們的業(yè)務(wù)合法性。

所以再改改:

Data& operator=(const Data& other)
{
    // 1. 首先清除本身的資源
    delete[] m_ptr;
    m_ptr = nullptr;
    // 2. 拷貝other的資源
    auto temp_size {other.m_size};
    if (other.m_ptr)
    {
        m_ptr = new int[temp_size];
        std::copy(other.m_ptr, other.m_ptr+temp_size, m_ptr);
        m_size = temp_size;
    }
    return *this;
}

此時(shí)此刻,這個(gè)代碼已經(jīng)沒(méi)啥大問(wèn)題了,除了一樣:

  • 代碼重復(fù)了,我們發(fā)現(xiàn)在拷貝other的數(shù)據(jù)的時(shí)候,邏輯是和拷貝構(gòu)造器是一模一樣的

c++里有一個(gè)原則:DRY: Do not Repeat Yourself。

別寫(xiě)重復(fù)的代碼!

所以接著往下,copy-and-swap正式出場(chǎng):

copy-and-swap 語(yǔ)義

  • 首先copy就是指拷貝構(gòu)造器

我們先來(lái)講講swap是個(gè)啥。

就是說(shuō),我們需要寫(xiě)一個(gè)函數(shù)swap,如下:

class Data
{
    // 其余部分省略,將重點(diǎn)放在swap函數(shù)
    friend void swap(Data &left, Data& right)
    {
        std::swap(left.m_size, right.m_size);
        std::swap(left.m_ptr, right.m_ptr);
    }
};

這個(gè)swap函數(shù)很簡(jiǎn)單,就是交換兩個(gè)已有的Data對(duì)象的內(nèi)部數(shù)據(jù),僅此而已。

現(xiàn)在,

  • copy有了
  • swap有了

讓我們寫(xiě)出最終極的operator=:

Data& operator=(Data other)
{
    swap(*this, other);
    return *this;
}

是不是驚呆了,就這么兩句,就行了!

仔細(xì)領(lǐng)略一下這個(gè)寫(xiě)法的高深之處:

  • 函數(shù)傳參,用的值傳參,而非引用,所以此時(shí)會(huì)調(diào)用拷貝構(gòu)造器(copy)
  • 函數(shù)內(nèi)部,交換了當(dāng)前對(duì)象,和局部臨時(shí)變量other的數(shù)據(jù)(swap)

你可能會(huì)問(wèn),沒(méi)有清除自身的資源?????

注意,other 是一個(gè)局部臨時(shí)變量,這個(gè)函數(shù)結(jié)束之前,會(huì)進(jìn)行析構(gòu),而析構(gòu)的時(shí)候,other身上已經(jīng)是被交換過(guò)的了,所以other被析構(gòu)的時(shí)候,就是自身資源清除的時(shí)候。

妙,妙,妙!!

用如此短的代碼實(shí)現(xiàn)了operator=, 實(shí)在是妙~

以上就是C++語(yǔ)義copy and swap示例詳解的詳細(xì)內(nèi)容,更多關(guān)于C++語(yǔ)義copy and swap的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

  • Qt5 串口類(lèi)QSerialPort的實(shí)現(xiàn)

    Qt5 串口類(lèi)QSerialPort的實(shí)現(xiàn)

    在Qt5以上提供了QtSerialPort模塊,方便編程人員快速的開(kāi)發(fā)應(yīng)用串口的應(yīng)用程序。本文主要介紹了Qt5 串口類(lèi)QSerialPort的實(shí)現(xiàn),,感興趣的可以了解一下
    2022-05-05
  • C++性能剖析教程之循環(huán)展開(kāi)

    C++性能剖析教程之循環(huán)展開(kāi)

    這篇文章主要給大家介紹了關(guān)于C++性能剖析教程之循環(huán)展開(kāi)的相關(guān)資料,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2018-06-06
  • MFC程序中使用QT開(kāi)發(fā)界面的實(shí)現(xiàn)步驟

    MFC程序中使用QT開(kāi)發(fā)界面的實(shí)現(xiàn)步驟

    本文主要介紹了MFC程序中使用QT開(kāi)發(fā)界面的實(shí)現(xiàn)步驟,文中通過(guò)示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2021-12-12
  • C++11新特性std::make_tuple的使用

    C++11新特性std::make_tuple的使用

    這篇文章主要介紹了C++11新特性std::make_tuple的使用,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2020-10-10
  • 循環(huán)隊(duì)列詳解及隊(duì)列的順序表示和實(shí)現(xiàn)

    循環(huán)隊(duì)列詳解及隊(duì)列的順序表示和實(shí)現(xiàn)

    這篇文章主要介紹了循環(huán)隊(duì)列詳解及隊(duì)列的順序表示和實(shí)現(xiàn)的相關(guān)資料,需要的朋友可以參考下
    2016-12-12
  • C語(yǔ)言之?dāng)?shù)組名與數(shù)組起始地址的關(guān)系解析

    C語(yǔ)言之?dāng)?shù)組名與數(shù)組起始地址的關(guān)系解析

    這篇文章主要介紹了C語(yǔ)言之?dāng)?shù)組名與數(shù)組起始地址的關(guān)系,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2022-07-07
  • C++11基于范圍的for循環(huán)代碼示例

    C++11基于范圍的for循環(huán)代碼示例

    這篇文章主要給大家介紹了關(guān)于C++11基于范圍的for循環(huán)的相關(guān)資料,范圍for循環(huán)(也稱(chēng)為C++11的基于范圍的for循環(huán))是一種簡(jiǎn)化迭代容器(如數(shù)組、向量、列表等)元素的方式,文中通過(guò)代碼介紹的非常詳細(xì),需要的朋友可以參考下
    2024-06-06
  • ipv6實(shí)現(xiàn)tcp編程示例

    ipv6實(shí)現(xiàn)tcp編程示例

    這篇文章主要介紹了ipv6實(shí)現(xiàn)tcp編程示例,需要的朋友可以參考下
    2014-03-03
  • C++按位異或運(yùn)算符的使用介紹

    C++按位異或運(yùn)算符的使用介紹

    本篇文章對(duì)C++按位異或運(yùn)算符的使用進(jìn)行了詳細(xì)的分析介紹。需要的朋友參考下
    2013-05-05
  • C++如何實(shí)現(xiàn)DNS域名解析

    C++如何實(shí)現(xiàn)DNS域名解析

    這片文章介紹了C++如何實(shí)現(xiàn)DNS域名解析,還有對(duì)相關(guān)技術(shù)的介紹,代碼很詳細(xì),需要的朋友可以參考下
    2015-07-07

最新評(píng)論