帶你深入理解STL之迭代器和Traits技法

在開始講迭代器之前,先列舉幾個例子,由淺入深的來理解一下爲什麼要設計迭代器。

//對於int類的求和函數
int sum(int *a , int n)
{
    int sum = 0 ;
    for (int i = 0 ; i < n ; i++) {
        sum += *a++;
    }
    return sum;
}
//對於listNode類的求和函數
struct ListNode {
    int val;
    ListNode * next;
};
int sum(ListNode * head) {
    int sum = 0;
    ListNode *p = head;
    while (p != NULL) {
        sum += p->val;
        p=p->next;
    }
    return sum;
}

針對如上兩個函數,我們來談談這種設計的缺陷所在:

  • 遍歷int數組和單鏈表listNode時,需要設計兩份不一樣的sum求和函數,對於STL這樣含有大量容器的代碼庫,針對每一種容器都設計sum的話,過於冗雜
  • 在sum函數中暴露了太多設計細節,如ListNode的節點值類型int,和指向下一個節點的指針next
  • 對於int數組來說,還必須知道數組的大小,以免越界訪問
  • 算法的設計過多的依賴容器,容器的改動會造成大量算法函數的隨之改動

那麼,如何設計才能使得算法擺脫特定容器?如何讓算法和容器各自獨立設計,互不影響又能統一到一起?本篇博客就帶你一窺STL的迭代器設計。

迭代器概述

迭代器是一種抽象的設計概念,在設計模式中是這麼定義迭代器模式的,即提供一種方法,使之能夠巡訪某個聚合物所含的每一個元素,而無需暴露該聚合物的內部表述方式。

不論是泛型思維或STL的實際運用,迭代器都扮演着重要的角色,STL的中心思想就在於:將數據容器和算法分開,彼此獨立設計,最後再以一帖膠着劑將它們撮合在一起。

談到迭代器需要遍歷容器就想到指針,的確,迭代器就是一種類似指針的對象,而指針的各種行爲中最常見也最重要的就是內容提領(dereference)和成員訪問(member access),因此,迭代器最重要的編程工作就是對operator*和operator->進行重載工作。

Traits編程技法

在介紹STL迭代器之前,先來看看Traits編程技法,通過它你能夠更好的理解迭代器設計。

template參數推導機制

我們先回過頭去看看sum函數,在sum函數中,我們必須知道容器的元素類型,這關係到函數返回值。既然迭代器設計中不能暴露容器的實現細節,那麼我們在算法中是不可能知道容器元素的類型,因此,必須設計出一個機制,能夠提取出容器中元素的類型。看看如下示例代碼:

#include <stdio.h>
#include <iostream>

using namespace std;

//此函數中並不知道iter所指的元素型別,而是通過模板T來獲取的
template <class I, class T1 ,class T>
T sum_impl(I iter ,T1 n , T t) {
    T sum = 0;//通過模板的特性,可以獲取I所指之物的型別,此處爲int

    //這裏做func應該做的工作
    for(int i = 0 ; i < n ;i++){
        sum+=*iter++;
    } 
    return sum;
}

template <class I , class T1>
inline T1 sum(I iter , T1 n) {//此處暴露了template參數推導機制的缺陷,在型別用於返回值時便束手無策
    return sum_impl(iter , n ,*iter);
}

int main() {
    int a[5] = {1,2,3,4,5};
    int sum1 = sum(a , 5);
    cout<<sum1<<endl;
}

上例中通過模板的參數推導機制推導出了iter所指之物的型別(int),於是可以定義T sum變量。

然而,迭代器所指之物的型別並非只是”迭代器所指對象的型別“,最常用的迭代器型別有五種,並非任何情況下任何一種都可利用上述的template參數推導機制來獲取,而且對於返回值類型,template參數推導機制也束手無策,因此,Traits技法應運而生,解決了這一難題!

內嵌型別機制

Traits技法採用內嵌型別來實現獲取迭代器型別這一功能需求,具體怎麼實現的呢?我們看下面的代碼:

#include <stdio.h>
#include <iostream>

using namespace std;
//定義一個簡單的iterator
template <class T>
struct MyIter{
    typedef T value_type; // 內嵌型別聲明
    T* ptr;
    MyIter(T* p =0):ptr(p){}
    T& operator*() const {return *ptr;}
};

template <class I>
typename I::value_type // func返回值型別
func(I iter){
    return *iter;
}

int main(){
    MyIter<int> iter(new int(8));
    cout<<func(iter)<<endl;
}

注意,func()函數的返回值型別必須加上關鍵詞typename,因爲T是一個template參數,在它被編譯器具現化之前,編譯器對其一無所知,關鍵詞typename的用意在於告訴編譯器這是一個型別,如此才能通過編譯。

內嵌型別看起來不錯,可以很順利的提取出迭代器所指型別並克服了template參數推導機制不能用於函數返回值的缺陷。可是,並不是所有的迭代器都是class type,原聲指針就不是!如果不是class type,那麼就無法聲明內嵌型別。但是STL絕對必須接受原生指針作爲一個迭代器。因此,必須另行它法!

iterator_traits萃取機

針對原生指針這類特殊情況,我們很容易想到利用模板偏特化的機制來實現特殊聲明,在泛化設計中提供一個特化版本。偏特化的定義是:針對任何template參數更進一步的條件限制所設計出來的一個特化版本。這裏,針對上面的MyIter設計出一個適合原生指針的特化版本,如下:

template <class T>
struct MyIter <T*>{   //T*代表T爲原生指針,這便是T爲任意型別的一個更進一步的條件限制
    typedef T value_type; // 內嵌型別聲明
    T* ptr;
    MyIter(T* p =0):ptr(p){}
    T& operator*() const {return *ptr;}
};

有了上述的介紹,包括template參數推導,內嵌型別,模板偏特化等,下面STL的真正主角要登場了,STL專門設計了一個iterator_traits模板類來”萃取“迭代器的特性。其定義如下:

// 用於traits出迭代其所指對象的型別
template <class I>
struct iterator_traits
{
  typedef typename I::value_type        value_type;
};

這個類如何完成迭代器的型別萃取呢?我們繼續往下看:

template <class I>
typename iterator_traits<I>::value_type // 通過iterator_traits類萃取I的型別
func(I iter){
    return *iter;
}

從表面上來看,這麼做只是多了一層間接性,但是帶來的好處是極大的!iterator_traits類可以擁有特化版本,如下:

//原生指針特化版本
template <class T>
struct iterator_traits <T*>
{
  typedef T  value_type;
}
//const指針特化版本
template <class T>
struct iterator_traits <const T*>
{
  typedef T  value_type;
}

於是,原生指針int*雖然不是一種class type,也可以通過traits取其value,到這裏traits的思想就基本明瞭了!下面就來看看STL只能中”萃取機“的源碼

// 用於traits出迭代其所指對象的型別
template <class Iterator>
struct iterator_traits
{
  // 迭代器類型, STL提供五種迭代器
  typedef typename Iterator::iterator_category iterator_category;

  // 迭代器所指對象的型別
  // 如果想與STL算法兼容, 那麼在類內需要提供value_type定義
  typedef typename Iterator::value_type        value_type;

  // 這個是用於處理兩個迭代器間距離的類型
  typedef typename Iterator::difference_type   difference_type;

  // 直接指向對象的原生指針類型
  typedef typename Iterator::pointer           pointer;

  // 這個是對象的引用類型
  typedef typename Iterator::reference         reference;
};
// 針對指針提供特化版本
template <class T>
struct iterator_traits<T*>
{
  typedef random_access_iterator_tag iterator_category;
  typedef T                          value_type;
  typedef ptrdiff_t                  difference_type;
  typedef T*                         pointer;
  typedef T&                         reference;
};

// 針對指向常對象的指針提供特化
template <class T>
struct iterator_traits<const T*>
{
  typedef random_access_iterator_tag iterator_category;
  typedef T                          value_type;
  typedef ptrdiff_t                  difference_type;
  typedef const T*                   pointer;
  typedef const T&                   reference;
};

對於源碼中的五種迭代器型別在下一小節中會有詳細說明。

迭代器設計

迭代器型別

value_type

所謂value_type,是指迭代器所指對象的型別,在之前的示例中已經介紹得很清楚了,這裏就不再贅述。

difference_type

difference_type用來表示兩個迭代器之間的距離,因此它也可以用來表示一個容器的最大容量。下面以STL裏面的計數功能函數count()爲例,來介紹一下difference_type的用法。

template <class I,class T>
typename iterator_traits<I>::difference_type   //返回值類型
count(I first , I end , const T& value){
    typename iterator_traits<I>::difference_type n = 0;  
    for( ; first!=end ; ++first){
        if(*first == value) ++n;
    }
    return n;
}

針對原生指針和原生的const指針,iterator_traits的difference_type爲ptrdiff_t(long int),特化版本依然可以採用iterator_traits::difference_type來獲取型別。

reference_type

從“迭代器所指之物的內容是否可以改變”的角度可以將迭代器分爲兩種:

  • const迭代器:不允許改變“所指對象之內容”,例如const int* p
  • mutable迭代器:允許改變“所指對象之內容”,例如int* p

當我們要對一個mutable迭代器進行提領(reference)操作時,獲得的不應該是一個右值(右值不允許賦值操作),而應該是一個左值,左值才允許賦值操作。

故:
+ 當p是一個mutable迭代器時,如果其value type是T,那麼*p的型別應該爲T&;
+ 當p是一個const迭代器時,*p的型別爲const T&

pointer type

迭代器可以傳回一個指針,指向迭代器所指之物。再迭代器源碼中,可以找到如下關於指針和引用的實現:

Reference operator*() const { return *value; }
pointer operator->() const { return &(operator*()); }

在iterator_traits結構體中需要加入其型別,如下:

template <class Iterator>
struct iterator_traits
{
  typedef typename Iterator::pointer           pointer;
  typedef typename Iterator::reference         reference;
}
//針對原生指針的特化版本
template <class T>
struct iterator_traits<T*>
{
  typedef T*                         pointer;
  typedef T&                         reference;
};
//針對原生const指針的特化版本
template <class T>
struct iterator_traits<const T*>
{
  typedef const T*                   pointer;
  typedef const T&                   reference;
};

iterator_category

這一型別代表迭代器的類別,一般分爲五類:

  • Input Iterator:只讀(read only)
  • Output Iterator:只寫(write only)
  • Forward Iterator:允許“寫入型”算法在此迭代器所形成的區間上進行讀寫操作
  • Bidirectional Iterator:可雙向移動的迭代器
  • Random Access Iterator:前四種迭代器都只供應一部分指針的算數能力(前三種支持operator++,第四種支持operator–),第五種則涵蓋所有指針的算數能力,包括p+n,p-n,p[n],p1-p2,p1
// 用於標記迭代器類型
struct input_iterator_tag {};
struct output_iterator_tag {};
struct forward_iterator_tag : public input_iterator_tag {};
struct bidirectional_iterator_tag : public forward_iterator_tag {};
struct random_access_iterator_tag : public bidirectional_iterator_tag {};

迭代器的保證

爲了符合規範,任何迭代器都應該提供五個內嵌型別,以利於Traits萃取,否則就自別與整個STL架構,可能無法與其他STL組件順利搭配。STL提供了一份iterator class如下:

//爲避免掛一漏萬,每個新設計的迭代器都必須繼承自它
template <class Category, class T, class Distance = ptrdiff_t,
          class Pointer = T*, class Reference = T&>
struct iterator {
  typedef Category  iterator_category;
  typedef T         value_type;
  typedef Distance  difference_type;
  typedef Pointer   pointer;
  typedef Reference reference;
};

迭代器源碼(完整版)

/**
 * 用於標記迭代器類型
 */
struct input_iterator_tag {};
struct output_iterator_tag {};
struct forward_iterator_tag : public input_iterator_tag {};
struct bidirectional_iterator_tag : public forward_iterator_tag {};
struct random_access_iterator_tag : public bidirectional_iterator_tag {};

/**
 * 用於traits出迭代其所指對象的型別
 */
template <class Iterator>
struct iterator_traits
{
  // 迭代器類型, STL提供五種迭代器
  typedef typename Iterator::iterator_category iterator_category;

  // 迭代器所指對象的型別
  // 如果想與STL算法兼容, 那麼在類內需要提供value_type定義
  typedef typename Iterator::value_type        value_type;

  // 這個是用於處理兩個迭代器間距離的類型
  typedef typename Iterator::difference_type   difference_type;

  // 直接指向對象的原生指針類型
  typedef typename Iterator::pointer           pointer;

  // 這個是對象的引用類型
  typedef typename Iterator::reference         reference;
};

/**
 * 針對指針提供特化版本
 */
template <class T>
struct iterator_traits<T*>
{
  typedef random_access_iterator_tag iterator_category;
  typedef T                          value_type;
  typedef ptrdiff_t                  difference_type;
  typedef T*                         pointer;
  typedef T&                         reference;
};
/**
 * 針對指向常對象的指針提供特化
 */
template <class T>
struct iterator_traits<const T*>
{
  typedef random_access_iterator_tag iterator_category;
  typedef T                          value_type;
  typedef ptrdiff_t                  difference_type;
  typedef const T*                   pointer;
  typedef const T&                   reference;
};
/**
 *  返回迭代器類別
 */
template <class Iterator>
inline typename iterator_traits<Iterator>::iterator_category
iterator_category(const Iterator&)
{
  typedef typename iterator_traits<Iterator>::iterator_category category;
  return category();
}
/**
 * 返回表示迭代器距離的類型
 */
template <class Iterator>
inline typename iterator_traits<Iterator>::difference_type*
distance_type(const Iterator&)
{
  return static_cast<typename iterator_traits<Iterator>::difference_type*>(0);
}
/**
 * 返回迭代器所指對象的類型
 */
template <class Iterator>
inline typename iterator_traits<Iterator>::value_type*
value_type(const Iterator&)
{
  return static_cast<typename iterator_traits<Iterator>::value_type*>(0);
}

迭代器相關函數設計

distance函數

distance函數用來計算兩個迭代器之前的距離。

  • 針對Input Iterator,Output Iterator,Forward Iterator,Bidirectional Iterator來說,必須逐一累加計算距離
  • 針對random_access_iterator來說,支持兩個迭代器相減,所以直接相減就能得到距離

其具體實現如下:

/**
 * 針對Input Iterator,Output Iterator,Forward Iterator,Bidirectional Iterator
 * 這四種函數,需要逐一累加來計算距離
 */
template <class InputIterator>
inline iterator_traits<InputIterator>::difference_type
__distance(InputIterator first, InputIterator last, input_iterator_tag)
{
  iterator_traits<InputIterator>::difference_type n = 0;
  while (first != last) {
    ++first; ++n;
  }
  return n;
}
/**
 * 針對random_access_iterator,可直接相減來計算差距
 */
template <class RandomAccessIterator>
inline iterator_traits<RandomAccessIterator>::difference_type
__distance(RandomAccessIterator first, RandomAccessIterator last,
           random_access_iterator_tag)
{
  return last - first;
}
// 入口函數,先判斷迭代器類型iterator_category,然後調用特定函數
template <class InputIterator>
inline iterator_traits<InputIterator>::difference_type
distance(InputIterator first, InputIterator last)
{
  typedef typename iterator_traits<InputIterator>::iterator_category category;
  return __distance(first, last, category());
}

Advance函數

Advance函數有兩個參數,迭代器p和n,函數內部將p前進n次。針對不同類型的迭代器有如下實現:

  • 針對input_iterator和forward_iterator,單向,逐一前進
  • 針對bidirectional_iterator,雙向,可以前進和後退,n>0和n<0
  • 針對random_access_iterator,支持p+n,可直接計算

其代碼實現如下:

/**
 * 針對input_iterator和forward_iterator版本
 */
template <class InputIterator, class Distance>
inline void __advance(InputIterator& i, Distance n, input_iterator_tag)
{
  while (n--) ++i;
}
/**
 * 針對bidirectional_iterator版本
 */
template <class BidirectionalIterator, class Distance>
inline void __advance(BidirectionalIterator& i, Distance n,
                      bidirectional_iterator_tag)
{
  if (n >= 0)
    while (n--) ++i;
  else
    while (n++) --i;
}
/**
 * 針對random_access_iterator版本
 */
template <class RandomAccessIterator, class Distance>
inline void __advance(RandomAccessIterator& i, Distance n,
                      random_access_iterator_tag)
{
  i += n;
}
/**
 * 入口函數,先判斷迭代器的類型
 */
template <class InputIterator, class Distance>
inline void advance(InputIterator& i, Distance n)
{
  __advance(i, n, iterator_category(i));
}

附加:type_traits設計

iterator_traits負責萃取迭代器的特性;type_traits負責萃取型別的特性。

帶你深入理解STL之空間配置器(思維導圖+源碼)一篇博客中,講到需要根據對象構造函數和析構函數的trivial和non-trivial特性來採用最有效的措施,例如:如果構造函數是trivial的,那麼可以直接採用如malloc()和memcpy()等函數,來提高效率。

例如:如果拷貝一個未知類型的數組,如果其具有trivial拷貝構造函數,那麼可以直接利用memcpy()來拷貝,反之則要調用該類型的拷貝構造函數。

type_traits的源代碼實現如下:

/**
 * 用來標識真/假對象,利用type_traits響應結果來進行參數推導,
 * 而編譯器只有面對class object形式的參數纔會做參數推導,
 * 這兩個空白class不會帶來額外負擔
 */
struct __true_type{};
struct __false_type{};

/**
 * type_traits結構體設計
 */
template <class type>
struct __type_traits
{
    // 不要移除這個成員
    // 它通知能自動特化__type_traits的編譯器, 現在這個__type_traits template是特化的
    // 這是爲了確保萬一編譯器使用了__type_traits而與此處無任何關聯的模板時
    // 一切也能順利運作
   typedef __true_type     this_dummy_member_must_be_first;

   // 以下條款應當被遵守, 因爲編譯器有可能自動生成類型的特化版本
   //   - 你可以重新安排的成員次序
   //   - 你可以移除你想移除的成員
   //   - 一定不可以修改下列成員名稱, 卻沒有修改編譯器中的相應名稱
   //   - 新加入的成員被當作一般成員, 除非編譯器提供特殊支持

   typedef __false_type    has_trivial_default_constructor;
   typedef __false_type    has_trivial_copy_constructor;
   typedef __false_type    has_trivial_assignment_operator;
   typedef __false_type    has_trivial_destructor;
   typedef __false_type    is_POD_type;
};
// 特化類型:
//         char, signed char, unsigned char,
//         short, unsigned short
//         int, unsigned int
//         long, unsigned long
//         float, double, long double
/**
 * 以下針對C++內置的基本數據類型提供特化版本, 
 * 使其具有trivial default constructor,
 * copy constructor, assignment operator, destructor並標記其爲POD類型
 */
__STL_TEMPLATE_NULL struct __type_traits<char>
{
   typedef __true_type    has_trivial_default_constructor;
   typedef __true_type    has_trivial_copy_constructor;
   typedef __true_type    has_trivial_assignment_operator;
   typedef __true_type    has_trivial_destructor;
   typedef __true_type    is_POD_type;
};
//針對char的特化版本
__STL_TEMPLATE_NULL struct __type_traits<signed char>
{
   typedef __true_type    has_trivial_default_constructor;
   typedef __true_type    has_trivial_copy_constructor;
   typedef __true_type    has_trivial_assignment_operator;
   typedef __true_type    has_trivial_destructor;
   typedef __true_type    is_POD_type;
};
//針對unsigned char的特化版本
__STL_TEMPLATE_NULL struct __type_traits<unsigned char>
{
   typedef __true_type    has_trivial_default_constructor;
   typedef __true_type    has_trivial_copy_constructor;
   typedef __true_type    has_trivial_assignment_operator;
   typedef __true_type    has_trivial_destructor;
   typedef __true_type    is_POD_type;
};
//針對short的特化版本
__STL_TEMPLATE_NULL struct __type_traits<short>
{
   typedef __true_type    has_trivial_default_constructor;
   typedef __true_type    has_trivial_copy_constructor;
   typedef __true_type    has_trivial_assignment_operator;
   typedef __true_type    has_trivial_destructor;
   typedef __true_type    is_POD_type;
};
//針對unsigned short的特化版本
__STL_TEMPLATE_NULL struct __type_traits<unsigned short>
{
   typedef __true_type    has_trivial_default_constructor;
   typedef __true_type    has_trivial_copy_constructor;
   typedef __true_type    has_trivial_assignment_operator;
   typedef __true_type    has_trivial_destructor;
   typedef __true_type    is_POD_type;
};
//針對int的特化版本
__STL_TEMPLATE_NULL struct __type_traits<int>
{
   typedef __true_type    has_trivial_default_constructor;
   typedef __true_type    has_trivial_copy_constructor;
   typedef __true_type    has_trivial_assignment_operator;
   typedef __true_type    has_trivial_destructor;
   typedef __true_type    is_POD_type;
};
//針對unsigned int的特化版本
__STL_TEMPLATE_NULL struct __type_traits<unsigned int>
{
   typedef __true_type    has_trivial_default_constructor;
   typedef __true_type    has_trivial_copy_constructor;
   typedef __true_type    has_trivial_assignment_operator;
   typedef __true_type    has_trivial_destructor;
   typedef __true_type    is_POD_type;
};
//針對long的特化版本
__STL_TEMPLATE_NULL struct __type_traits<long>
{
   typedef __true_type    has_trivial_default_constructor;
   typedef __true_type    has_trivial_copy_constructor;
   typedef __true_type    has_trivial_assignment_operator;
   typedef __true_type    has_trivial_destructor;
   typedef __true_type    is_POD_type;
};
//針對unsigned long的特化版本
__STL_TEMPLATE_NULL struct __type_traits<unsigned long>
{
   typedef __true_type    has_trivial_default_constructor;
   typedef __true_type    has_trivial_copy_constructor;
   typedef __true_type    has_trivial_assignment_operator;
   typedef __true_type    has_trivial_destructor;
   typedef __true_type    is_POD_type;
};
//針對float的特化版本
__STL_TEMPLATE_NULL struct __type_traits<float>
{
   typedef __true_type    has_trivial_default_constructor;
   typedef __true_type    has_trivial_copy_constructor;
   typedef __true_type    has_trivial_assignment_operator;
   typedef __true_type    has_trivial_destructor;
   typedef __true_type    is_POD_type;
};
//針對double的特化版本
__STL_TEMPLATE_NULL struct __type_traits<double>
{
   typedef __true_type    has_trivial_default_constructor;
   typedef __true_type    has_trivial_copy_constructor;
   typedef __true_type    has_trivial_assignment_operator;
   typedef __true_type    has_trivial_destructor;
   typedef __true_type    is_POD_type;
};
//針對long double的特化版本
__STL_TEMPLATE_NULL struct __type_traits<long double>
{
   typedef __true_type    has_trivial_default_constructor;
   typedef __true_type    has_trivial_copy_constructor;
   typedef __true_type    has_trivial_assignment_operator;
   typedef __true_type    has_trivial_destructor;
   typedef __true_type    is_POD_type;
};

// 針對指針提供特化
template <class T>
struct __type_traits<T*>
{
   typedef __true_type    has_trivial_default_constructor;
   typedef __true_type    has_trivial_copy_constructor;
   typedef __true_type    has_trivial_assignment_operator;
   typedef __true_type    has_trivial_destructor;
   typedef __true_type    is_POD_type;
};

// 針對char *, signed char *, unsigned char *提供特化

struct __type_traits<char*>
{
   typedef __true_type    has_trivial_default_constructor;
   typedef __true_type    has_trivial_copy_constructor;
   typedef __true_type    has_trivial_assignment_operator;
   typedef __true_type    has_trivial_destructor;
   typedef __true_type    is_POD_type;
};

struct __type_traits<signed char*>
{
   typedef __true_type    has_trivial_default_constructor;
   typedef __true_type    has_trivial_copy_constructor;
   typedef __true_type    has_trivial_assignment_operator;
   typedef __true_type    has_trivial_destructor;
   typedef __true_type    is_POD_type;
};

struct __type_traits<unsigned char*>
{
   typedef __true_type    has_trivial_default_constructor;
   typedef __true_type    has_trivial_copy_constructor;
   typedef __true_type    has_trivial_assignment_operator;
   typedef __true_type    has_trivial_destructor;
   typedef __true_type    is_POD_type;
};

後記

本篇博客比較枯燥,STL的迭代器中有很多優秀的值得學習的設計方式,如萃取機制,用類繼承來定義迭代器類型等,通篇代碼比較多,圖片講解較少,只能說迭代器的設計中基本上都是較爲繁瑣的型別聲明和定義,認真看下去的話會收穫很多!若有疑惑可以在博文下方留言,我看到會及時幫大家解答!

參考:
+ C++ STL源碼剖析
+ STL源碼剖析——迭代器

發佈了241 篇原創文章 · 獲贊 52 · 訪問量 57萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章