C++:STL標準入門彙總

C++:STL標準入門彙總

 一、STL簡介

STLStandard Template Library,標準模板庫)是惠普實驗室開發的一系列軟件的統稱。它是由Alexander StepanovMeng LeeDavid R Musser在惠普實驗室工作時所開發出來的。現在雖說它主要出現在C++中,但在被引入C++之前該技術就已經存在了很長的一段時間。

 STLStandard Template Library),即標準模板庫,是一個具有工業強度的,高效的C++程序庫。它被容納於C++標準程序庫(C++ Standard Library)中,是ANSI/ISO C++標準中最新的也是極具革命性的一部分。該庫包含了諸多在計算機科學領域裏所常用的基本數據結構和基本算法。爲廣大C++程序員們提供了一個可擴展的應用框架,高度體現了軟件的可複用性。

 從邏輯層次來看,在STL中體現了泛型化程序設計的思想(generic programming),引入了諸多新的名詞,比如像需求(requirements),概念(concept),模型(model),容器(container),算法(algorithmn),迭代子(iterator)等。與OOPobject-oriented programming)中的多態(polymorphism)一樣,泛型也是一種軟件的複用技術;

       從實現層次看,整個STL是以一種類型參數化(type parameterized)的方式實現的,這種方式基於一個在早先C++標準中沒有出現的語言特性--模板(template)。如果查閱任何一個版本的STL源代碼,你就會發現,模板作爲構成整個STL的基石是一件千真萬確的事情。除此之外,還有許多C++的新特性爲STL的實現提供了方便;

STL的代碼從廣義上講分爲三類:algorithm(算法)、container(容器)和iterator(迭代器),幾乎所有的代碼都採用了模板類和模版函數的方式,這相比於傳統的由函數和類

 

組成的庫來說提供了更好的代碼重用機會。在C++標準中,STL被組織爲下面的13個頭文件<algorithm><deque><functional><iterator><vector><list><map>

<memory><numeric><queue><set><stack><utility>

二、STL的六大組件

· 容器(Container),是一種數據結構,如listvector,和deques ,以模板類的方法提供。爲了訪問容器中的數據,可以使用由容器類輸出的迭代器;

· 迭代器(Iterator),提供了訪問容器中對象的方法。例如,可以使用一對迭代器指定listvector中的一定範圍的對象。迭代器就如同一個指針。事實上,C++的指針也是一種迭代器。但是,迭代器也可以是那些定義了operator*()以及其他類似於指針的操作符地方法的類對象;

· 算法(Algorithm),是用來操作容器中的數據的模板函數。例如,STLsort()來對一個vector中的數據進行排序,用find()來搜索一個list中的對象,函數本身與他們操作的數據的結構和類型無關,因此他們可以在從簡單數組到高度複雜容器的任何數據結構上使用;

· 仿函數(Function object,仿函數(functor)又稱之爲函數對象(function object),其實就是重載了()操作符的struct,沒有什麼特別的地方

· 迭代適配器(Adaptor

· 空間配製器(allocator)其中主要工作包括兩部分1.對象的創建與銷燬    2.內存的獲取與釋放

1.STL容器

在實際的開發過程中,數據結構本身的重要性不會遜於操作於數據結構的算法的重要性,當程序中存在着對時間要求很高的部分時,數據結構的選擇就顯得更加重要。

經典的數據結構數量有限,但是我們常常重複着一些爲了實現向量、鏈表等結構而編寫的代碼,這些代碼都十分相似,只是爲了適應不同數據的變化而在細節上有所出入。STL容器

就爲我們提供了這樣的方便,它允許我們重複利用已有的實現構造自己的特定類型下的數據結構,通過設置一些模版類,STL容器對最常用的數據結構提供了支持,這些模板的參數

允許我們指定容器中元素的數據類型,可以將我們許多重複而乏味的工作簡化。

 

容器部分主要由頭文件<vector>,<list>,<deque>,<set>,<map>,<stack><queue>組成。對於常用的一些容器和容器適配器(可以看作由其它容器實現的容器),可以通過下表總結一下它們和相應頭文件的對應關係。

 

向量(vector) 連續存儲的元素<vector>

列表(list)       由節點組成的雙向鏈表,每個結點包含着一個元素<list>

雙隊列(deque) 連續存儲的指向不同元素的指針所組成的數組<deque>

集合(set) 由節點組成的紅黑樹,每個節點都包含着一個元素,節點之間以某種作用於元素對的謂詞排列,沒有兩個不同的元素能夠擁有相同的次序 <set>

多重集合(multiset) 允許存在兩個次序相等的元素的集合 <set>

(stack) 後進先出的值的排列 <stack>

隊列(queue) 先進先出的執的排列 <queue>

優先隊列(priority_queue) 元素的次序是由作用於所存儲的值對上的某種謂詞決定的的一種隊列<queue>

映射(map) {鍵,值}對組成的集合,以某種作用於鍵對上的謂詞排列 <map>

多重映射(multimap) 允許鍵對有相等的次序的映射 <map>

 

1)序列式容器(Sequence containers),每個元素都有固定位置--取決於插入時機和地點,和元素值無關,vectordequelist

   Vectors將元素置於一個動態數組中加以管理,可以隨機存取元素(用索引直接存取),數組尾部添加或移除元素非常快速。但是在中部或頭部安插元素比較費時;

   Deques:是“double-ended queue”的縮寫,可以隨機存取元素(用索引直接存取),數組頭部和尾部添加或移除元素都非常快速。但是在中部或頭部安插元素比較費時;

   Lists雙向鏈表,不提供隨機存取(按順序走到需存取的元素,O(n)),在任何位置上執行插入或刪除動作都非常迅速,內部只需調整一下指針;

2)關聯式容器(Associated containers),元素位置取決於特定的排序準則,和插入順序無關,setmultisetmapmultimap

   Sets/Multisets:內部的元素依據其值自動排序,Set內的相同數值的元素只能出現一次,Multisets內可包含多個數值相同的元素,內部由二叉樹實現(實際上基於紅黑樹(RB-tree)實現),便於查找;

   Maps/MultimapsMap的元素是成對的鍵值/實值,內部的元素依據其值自動排序,Map內的相同數值的元素只能出現一次,Multimaps內可包含多個數值相同的元素,內部由二叉樹實現(實際上基於紅黑樹(RB-tree)實現),便於查找;

另外有其他容器hash_map,hash_set,hash_multiset,hash_multimap

  容器的比較:

wKioL1cDtM2gaJ1UAACZRu_FKao752.jpg


2.STL迭代器 

Iterator(迭代器)模式又稱Cursor(遊標)模式,用於提供一種方法順序訪問一個聚合對象中各個元素
而又不需暴露該對象的內部表示。或者這樣說可能更容易理解:Iterator模式是運用於聚合對象的一種模式,通過運用該模式,使得我們可以在不知道對象內部表示的情況下,按照一定順序(由iterator提供的方法)訪問聚合對象中的各個元素。

迭代器的作用:能夠讓迭代器與算法不干擾的相互發展,最後又能無間隙的粘合起來,重載了*,++,==,!=,=運算符。用以操作複雜的數據結構,容器提供迭代器,算法使用迭代器;

常見的一些迭代器類型:iteratorconst_iteratorreverse_iteratorconst_reverse_iterator

迭代器一般聲明使用示例

vector<T>::iterator it;

list<T>::iterator it;

deque<T>::iterator it

wKiom1cDtP6SKa-hAABBvv3lYmM333.jpg

      input               output

              \            /  

                forward

                     |

                bidirectional

                     |

               random access            

要注意,上面這圖表並不是表明它們之間的繼承關係:而只是描述了迭代器的種類和接口。處於圖表下層的迭代器都是相對於處於圖表上層迭代器的擴張集。例如:forward迭代器不但擁有input和output迭代器的所有功能,還擁有更多的功能。

各個迭代器的功能如下:

迭代器類別說明

輸入  從容器中讀取元素。輸入迭代器只能一次讀入一個元素向前移動,輸入迭代器只支持一               遍算法,同一個輸入迭代器不能兩遍遍歷一個序列

輸出  向容器中寫入元素。輸出迭代器只能一次一個元素向前移動。輸出迭代器只支持一遍算              法,統一輸出迭代器不能兩次遍歷一個序列

正向  組合輸入迭代器和輸出迭代器的功能,並保留在容器中的位置

雙向  組合正向迭代器和逆向迭代器的功能,支持多遍算法

隨機訪問  組合雙向迭代器的功能與直接訪問容器中任何元素的功能,即可向前向後跳過任意個              元素

3.STL算法

STL算法部分主要由頭文件<algorithm>,<numeric>,<functional>組成。要使用 STL中的算法函數必須包含頭文件<algorithm>,對於數值算法須包含<numeric><functional>中則定義了一些模板類,用來聲明函數對象。
STL中算法大致分爲四類:
1)、非可變序列算法:指不直接修改其所操作的容器內容的算法。
2)、可變序列算法:指可以修改它們所操作的容器內容的算法。
3)、排序算法:包括對序列進行排序和合並的算法、搜索算法以及有序序列上的集合操作。
4)、數值算法:對容器內容進行數值計算。

 

以下對所有算法進行細緻分類並標明功能:
    <>查找算法(13)判斷容器中是否包含某個值
    adjacent_find:            iterator對標識元素範圍內,查找一對相鄰重複元素,找到則返回指向這對元素的第一個元素的ForwardIterator。否則返回last。重載版本使用輸入的二元操作符代替相等的判斷。
    binary_search:            在有序序列中查找value,找到返回true。重載的版本實用指定的比較函數對象或函數指針來判斷相等。
    count:                    利用等於操作符,把標誌範圍內的元素與輸入值比較,返回相等元素個數。
    count_if:                 利用輸入的操作符,對標誌範圍內的元素進行操作,返回結果爲true的個數。
    equal_range:              功能類似equal,返回一對iterator,第一個表示lower_bound,第二個表示upper_bound
    find:                     利用底層元素的等於操作符,對指定範圍內的元素與輸入值進行比較。當匹配時,結束搜索,返回該元素的一個InputIterator
    find_end:                 在指定範圍內查找"由輸入的另外一對iterator標誌的第二個序列"的最後一次出現。找到則返回最後一對的第一個ForwardIterator,否則返回輸入的"另外一對"的第一個ForwardIterator。重載版本使用用戶輸入的操作符代替等於操作。
    find_first_of:            在指定範圍內查找"由輸入的另外一對iterator標誌的第二個序列"中任意一個元素的第一次出現。重載版本中使用了用戶自定義操作符。
    find_if:                  使用輸入的函數代替等於操作符執行find
    lower_bound:              返回一個ForwardIterator,指向在有序序列範圍內的可以插入指定值而不破壞容器順序的第一個位置。重載函數使用自定義比較操作。
    upper_bound:              返回一個ForwardIterator,指向在有序序列範圍內插入value而不破壞容器順序的最後一個位置,該位置標誌一個大於value的值。重載函數使用自定義比較操作。
    search:                   給出兩個範圍,返回一個ForwardIterator,查找成功指向第一個範圍內第一次出現子序列(第二個範圍)的位置,查找失敗指向last1。重載版本使用自定義的比較操作。
    search_n:                 在指定範圍內查找val出現n次的子序列。重載版本使用自定義的比較操作。
 
    <>排序和通用算法(14)提供元素排序策略
    inplace_merge:            合併兩個有序序列,結果序列覆蓋兩端範圍。重載版本使用輸入的操作進行排序。
    merge:                    合併兩個有序序列,存放到另一個序列。重載版本使用自定義的比較。
    nth_element:              將範圍內的序列重新排序,使所有小於第n個元素的元素都出現在它前面,而大於它的都出現在後面。重載版本使用自定義的比較操作。
    partial_sort:             對序列做部分排序,被排序元素個數正好可以被放到範圍內。重載版本使用自定義的比較操作。
    partial_sort_copy:        partial_sort類似,不過將經過排序的序列複製到另一個容器。
    partition:                對指定範圍內元素重新排序,使用輸入的函數,把結果爲true的元素放在結果爲false的元素之前。
    random_shuffle:           對指定範圍內的元素隨機調整次序。重載版本輸入一個隨機數產生操作。
    reverse:                  將指定範圍內元素重新反序排序。
    reverse_copy:             reverse類似,不過將結果寫入另一個容器。
    rotate:                   將指定範圍內元素移到容器末尾,由middle指向的元素成爲容器第一個元素。
    rotate_copy:              rotate類似,不過將結果寫入另一個容器。
    sort:                     以升序重新排列指定範圍內的元素。重載版本使用自定義的比較操作。
    stable_sort:              sort類似,不過保留相等元素之間的順序關係。
    stable_partition:         partition類似,不過不保證保留容器中的相對順序。
 
    <>刪除和替換算法(15)
    copy:                     複製序列
    copy_backward:            copy相同,不過元素是以相反順序被拷貝。
    iter_swap:                交換兩個ForwardIterator的值。
    remove:                   刪除指定範圍內所有等於指定元素的元素。注意,該函數不是真正刪除函數。內置函數不適合使用removeremove_if函數。
    remove_copy:              將所有不匹配元素複製到一個制定容器,返回OutputIterator指向被拷貝的末元素的下一個位置。
    remove_if:                刪除指定範圍內輸入操作結果爲true的所有元素。
    remove_copy_if:           將所有不匹配元素拷貝到一個指定容器。
    replace:                  將指定範圍內所有等於vold的元素都用vnew代替。
    replace_copy:             replace類似,不過將結果寫入另一個容器。
    replace_if:               將指定範圍內所有操作結果爲true的元素用新值代替。
    replace_copy_if:          replace_if,不過將結果寫入另一個容器。
    swap:                     交換存儲在兩個對象中的值。
    swap_range:               將指定範圍內的元素與另一個序列元素值進行交換。
    unique:                   清除序列中重複元素,和remove類似,它也不能真正刪除元素。重載版本使用自定義比較操作。
    unique_copy:              unique類似,不過把結果輸出到另一個容器。
 
    <>排列組合算法(2)提供計算給定集合按一定順序的所有可能排列組合
    next_permutation:         取出當前範圍內的排列,並重新排序爲下一個排列。重載版本使用自定義的比較操作。
    prev_permutation:         取出指定範圍內的序列並將它重新排序爲上一個序列。如果不存在上一個序列則返回false。重載版本使用自定義的比較操作。
 

    <>算術算法(4)
    accumulate:               iterator對標識的序列段元素之和,加到一個由val指定的初始值上。重載版本不再做加法,而是傳進來的二元操作符被應用到元素上。
    partial_sum:              創建一個新序列,其中每個元素值代表指定範圍內該位置前所有元素之和。重載版本使用自定義操作代替加法。
    inner_product:            對兩個序列做內積(對應元素相乘,再求和)並將內積加到一個輸入的初始值上。重載版本使用用戶定義的操作。
    adjacent_difference:      創建一個新序列,新序列中每個新值代表當前元素與上一個元素的差。重載版本用指定二元操作計算相鄰元素的差。
 
    <>生成和異變算法(6)
    fill:                     將輸入值賦給標誌範圍內的所有元素。
    fill_n:                   將輸入值賦給firstfirst+n範圍內的所有元素。
    for_each:                 用指定函數依次對指定範圍內所有元素進行迭代訪問,返回所指定的函數類型。該函數不得修改序列中的元素。
    generate:                 連續調用輸入的函數來填充指定的範圍。
    generate_n:               generate函數類似,填充從指定iterator開始的n個元素。
    transform:                將輸入的操作作用與指定範圍內的每個元素,併產生一個新的序列。重載版本將操作作用在一對元素上,另外一個元素來自輸入的另外一個序列。結果輸出到指定容器。
 

    <>關係算法(8)
    equal:                    如果兩個序列在標誌範圍內元素都相等,返回true。重載版本使用輸入的操作符代替默認的等於操作符。
    includes:                 判斷第一個指定範圍內的所有元素是否都被第二個範圍包含,使用底層元素的<操作符,成功返回true。重載版本使用用戶輸入的函數。
    lexicographical_compare:  比較兩個序列。重載版本使用用戶自定義比較操作。
    max:                      返回兩個元素中較大一個。重載版本使用自定義比較操作。
    max_element:              返回一個ForwardIterator,指出序列中最大的元素。重載版本使用自定義比較操作。
    min:                      返回兩個元素中較小一個。重載版本使用自定義比較操作。
    min_element:              返回一個ForwardIterator,指出序列中最小的元素。重載版本使用自定義比較操作。
    mismatch:                 並行比較兩個序列,指出第一個不匹配的位置,返回一對iterator,標誌第一個不匹配元素位置。如果都匹配,返回每個容器的last。重載版本使用自定義的比較操作。
 

    <>集合算法(4)
    set_union:                構造一個有序序列,包含兩個序列中所有的不重複元素。重載版本使用自定義的比較操作。
    set_intersection:         構造一個有序序列,其中元素在兩個序列中都存在。重載版本使用自定義的比較操作。
    set_difference:           構造一個有序序列,該序列僅保留第一個序列中存在的而第二個中不存在的元素。重載版本使用自定義的比較操作。
    set_symmetric_difference: 構造一個有序序列,該序列取兩個序列的對稱差集(並集-交集)
 

   <>堆算法(4)
    make_heap:                把指定範圍內的元素生成一個堆。重載版本使用自定義比較操作。
    pop_heap:                 並不真正把最大元素從堆中彈出,而是重新排序堆。它把firstlast-1交換,然後重新生成一個堆。可使用容器的back來訪問被"彈出"的元素或者使用pop_back進行真正的刪除。重載版本使用自定義的比較操作。
    push_heap:                假設firstlast-1是一個有效堆,要被加入到堆的元素存放在位置last-1,重新生成堆。在指向該函數前,必須先把元素插入容器後。重載版本使用指定的比較操作。
    sort_heap:                對指定範圍內的序列重新排序,它假設該序列是個有序堆。重載版本使用自定義比較操作。

 

 

4.適配器

STL提供了三個容器適配器:queue、priority_queue、stack。這些適配器都是包裝了vector、list、deque中某個順序容器的包裝器。注意:適配器沒有提供迭代器,也不能同時插入或刪除多個元素。

適配器(Adaptor)是提供接口映射的模板類。適配器基於其他類來實現新的功能,成員函數可以被添加、隱藏,也可合併以得到新的功能。

STL提供了三個容器適配器:queue、priority_queue、stack。

這些適配器都是包裝了vector、list、deque中某個順序容器的包裝器。注意:適配器沒有提供迭代器,也不能同時插入或刪除多個元素。

隊列(queue)

先進先出(FIFO)的數據結構

可供選擇的容器只有deque和list。對大多數用途使用默認的deque。

1、要用到頭文件#include<queue>

2、常用函數

  • queue<int> Q 聲明一個int的空隊列Q;

  • push() 將一個新元素接到隊列的末端;

  • pop() 彈出隊列中的第一個元素,返回的是一個void;

  • front() \ back() 存取隊列中的第一個元素和最後一個元素,返回的是一個引用;

  • empty() 測試隊列是否爲空;

  • size() 獲得隊列中元素的個數;

堆棧(stack)

先進後出(FILO)的數據結構

可以使用三個標準順序容器vector、deque、list中的任何一個作爲stack的底層模型。

1、要用到頭文件#include<stack>

2、常用函數

  • stack<int> Q 聲明一個int的空棧Q;

  • push() 將一個新元素接到棧的末端;

  • pop() 彈出棧中的末端元素,返回的是一個void;

  • top() 存取棧中的最後一個元素,返回的是一個引用;

  • empty() 測試棧是否爲空;

  • size() 獲得棧中元素的個數;

<priority_queue> 內部實現: 堆

priority_queue<T, Sequence, Compare>

支持操作:

push() O(logn)

pop() O(logn)

top() O(1)存取隊列中的第一個元素,與queue不同

See also: push_heap(), pop_heap() … in <algorithm>

priority_queue用法舉例

#include <queue>
#include <vector>
using namespace std;
priority_queue <int> maxheap;      //int最大堆
struct cmp{
    bool operator()(int a,int b) 
    {return a > b;}
};
priority_queue <int,vector<int>,cmp> minheap;  //int最小堆

priority_queue的比較函數

優先級的比較函數,可以使用缺省默認,也可以重載自己編寫的比較函數。

1) 假設優先隊列中的元素爲結構體,要對其中某個變量排序。此時可以在結構體中重載操作符:

priority_queue<int, vector<int>, cmp> Q;
struct cmp{
        int seq;                //重載運算符號
        bool operator < (const Test &a) const {
                return seq < a.seq;     // seq大的優先級高
        }
};

2)如果是根據與優先隊列中某個元素相關的變量排序,且此變量不在結構體中,則可參考greater<int>() 的方式去重載(),具體做法如下:

priority_queue<int, vector<int>, cmp> Q;
struct cmp {
      bool operator()(int a, int b) { // d[a]小的優先級高
              return d[a] > d[b];
        }
};

怎樣學習 C++ STL?

來自知乎飯後溫柔的回答

樓上推薦< STL源碼剖析>的挺多.其實我覺得這本書並不適合STL入門.
我自己初期認識上的一個盲點,就是以爲STL幾乎等同於模板編程.
STL是嘗試爲計算過程及數據操作整理出一套普遍的接口.設計原則上符合這套接口約定的,都可以稱爲某種STL吧.
模板則是出於對類型操作的需求而誕生的.
STL天生跟模板搭配的很好.因爲提供一個普遍的數據操作接口,,應該會遇到一個普遍的數據表示這個需求才會顯得合理優雅.而模板提供的類型操作能力,很好的滿足了普遍的數據表示這個要求.

STL源碼剖析主要集中於模板,及一些具體實現(如內存分配器)的分析.而STL的目的,卻正是企圖抽象出一種公共的數據操作與計算過程接口.學習STL急着扎進細節中看實現,似乎與它的初衷背道而馳.
這本書我前面看的泛泛,但內存分配器那部分着實好,爲當時的我,初學者,展示了一個精密的程序組件應有的思考與決斷.雖然它其實與STL的目的可以說並無關係的.STL的分配器是C++追求精確控制與提供用戶自主選擇這一原則才引入的.是現實的痛點.

學習STL,得先假設你有以下的知識背景:
簡單的與c重疊的那部分基礎語法.
對簡單數據結構數組,鏈表,堆棧等的理解,並能夠使用.
操作符重載的概念和寫法.
對模板的使用.理解到類型替換這個層面就足夠了.就是教材首先教的那部分.但越多越好.
仿函數體的概念.前期基本不需要.

其實我覺得以上知識背景你都擁有的話,學STL的使用不成問題了. 這部分對很多人來說比較簡單,所以他們直接去看STL源碼剖析.以下我嘗試列出幾個點,你結合搜索引擎擴展開去,應該能夠建立一些脈絡.(以下公共這個詞,你可以用普遍,泛型這2個詞代替.以下算法這個詞,專指對容器中的數據進行計算.)
1 爲什麼公共的數據操作表示,都需要一個iterator(迭代器)?
因爲數據結構都有取出,存入的操作.操作下一個或上一個數據的需求.
因爲迭代器代表了某個數據的在容器位置這麼一個概念.我們無須關心指針這種低級硬件概念.
iterator(迭代器)翻譯爲遊標,你是否覺得更合適?
爲什麼STL的最初設計者這麼叼,能夠想到公共的數據操作就要有公共的數據位置表示概念?
2 一個公共的算法,其數據輸入應該是什麼?是容器嗎?
理想上,輸入的應是數據的位置範圍,而不應該關心它是什麼容器.甚至無須關心其在容器中的具體    位置.只需知道是A-B位置即可.所以iterator這個概念是很有必要的.
3 iterator真正代表的是什麼?不同容器的iterator通常不兼容.既然STL的初衷是設計公共的數據操作      接口,爲什麼會特別區別不同容器的iterator?瞭解下什麼是非侵入式設計,它是如何體現在公共算法    對迭代器的獲取上的?
4 確實會有人設計通用的iterator適配不同容器(需要一些模板知識).你覺得有必要嗎?如何實現?
5 爲什麼大多數的公共算法,需要我們輸入一個仿函數體?什麼是謂語?
排序算法可以有多種.而比較大小這一步計算,原則上是可以抽象出來的.
6 以上幾點,是否能讓你感覺到強烈需要模板操作類型的能力?
7 vector長度增長時,其內存如何增長?幾種容器的內存分配是如何的?
8 容器的賦值拷貝代價?複製容器存在哪些方法或接口減少拷貝代價?
9 容器循環遍歷刪除會遇到什麼情況?如何避免?這個問題是普遍存在的還是只限於STL?(參考非侵      入設計.)

STL最強大的是它所設計的這一套公共數據操作接口.它爲什麼有這些接口?其目的,考量,決斷是非常值得學習的.就算你能盲打紅黑樹,腳寫tokenizer什麼的...至少在接口設計方面是能夠提供一些思路的.



作者:飯後溫柔
鏈接:https://www.zhihu.com/question/24068436/answer/26602153
來源:知乎
著作權歸作者所有。商業轉載請聯繫作者獲得授權,非商業轉載請註明出處。


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章