C++ 泛型


泛型

2019.08

Xun

C++標准算法庫中的各種函數都有很強的適用性。比如其中的std::sort函數,它即可以對std::vector中的元素進行排序,也能對std::deque中的元素進行排序,對於數組中的元素,它也可以正常運行。同時,std::sort函數還可以接受一個函數指針(謂詞),用來指定排序規則。在這篇文章中,我們將模擬標准庫中的std::sort函數,寫一個與其接口相同的排序函數。

這里排序的方法為歸並排序,其主要思想是將兩個已排序的短序列合並為一個更長的已排序序列。一般來說,子序列也是無序的,這就需要在函數中對兩個短序列進行歸並排序。若序列只有一個元素,則它肯定是有序的,遞歸結束。其動圖演示如下:(動圖來自互聯網)

下面參考一下std::sort函數的接口。std::sort函數無返回值(或返回void),接收兩個迭代器指示序列的開始位置和尾后位置,同時還有一個可選參數,用來指定排序規則,其默認使用<來進行比較。

於是我們排序函數的接口可能長成這樣:

template<typename RandIter>
void merge_sort
(
    RandIter iterBeg, RandIter iterEnd, //開始與尾后迭代器
    TYPE comp = ...//排序規則
)

這里的第一行代碼說明下面的RandIter是一個類名,在函數調用時會將其替換為相應的類型,如int*std::vector<int>::iterator等。comp是一個二元謂詞,一旦RandIter確定,comp的類型也就隨之確定了。比如RandIterstd::deque<int>::iterator,其核心數據成員是int類型,謂詞就是為bool (*comp) (int a, int b),即接收兩個int型變量,返回bool型的一個函數指針。然而對於各種不同的迭代器類型,該如何確定其中的TYPE

對於標准庫中的各種順序容器的迭代器類,都封裝了一個value_type用來說明其指向序列中元素的類型,比如存儲着double元素的std::vector的迭代器的value_type,即std::vector<double>::iterator::value_type其實就是double類型。所以,comp的類型TYPE可以寫作typename RandIter::value_type

但是,對於普通的數組,傳入的迭代器是兩個指針,而指針類型是沒有value_type的!因此這樣寫的話是不能處理數組的。

在頭文件type_traits中,標准庫提供了很多的類型轉換函數。對於類型int*,其解引用類型為int。對於更一般的類型T,若U*T是相同的類型,則可以使用type_traits中的std::remove_pointer<T>::type來得到類型U

同樣的romove_pointer只能處理指針,而對於順序容器就束手無策了。

C++中還提供了autodecltype關鍵字來進行類型推斷。在函數接口中,是不能使用auto的。於是,我們的希望就落在了decltype上。

我們需要的是*iterBeg的類型,但是decltype一個解引用會得到引用類型,所以我們仍需借助標准庫頭文件type_traits中的一個remove_reference函數來去除引用性質。於是,函數的接口如下:

template<typename RandIter>
void merge_sort
(
    RandIter iterBeg, RandIter iterEnd, //開始與尾后迭代器
    bool (*comp)(typename std::remove_reference<decltype(*iterBeg)>::type, typename std::remove_reference<decltype(*iterBeg)>::type) = ...//排序規則
)

類型名解決了,下面就是默認參數了。這里,我們將一個lambda表達式傳入comp

bool (*comp)(...) = [](typename std::remove_reference<decltype(*iterBeg)>::type a, typename std::remove_reference<decltype(*iterBeg)>::type b){return a < b;}

將整個接口寫出來,是這樣的:

template<typename RandIter>
void merge_sort
(
    RandIter iterBeg, RandIter iterEnd, //開始與尾后迭代器
    bool (*comp)(typename std::remove_reference<decltype(*iterBeg)>::type, 
        typename std::remove_reference<decltype(*iterBeg)>::type) = []
        (typename std::remove_reference<decltype(*iterBeg)>::type a,
         typename std::remove_reference<decltype(*iterBeg)>::type b)
            {return a < b; }    
)

我之前寫這個函數的時候,參考了Visual Studio所采用的實現版本,發現它使用的是重載而不是默認參數來實現,其接口看起來要清爽不少:

template<typename RandIter>
void merge_sort//這里使用 '<' 來排序
(
    RandIter iterBeg, RandIter iterEnd //開始與尾后迭代器
);

template<typename RandIter, typename Pred>
void merge_sort//這里使用 comp 來排序
(
    RandIter iterBeg, RandIter iterEnd, //開始與尾后迭代器
    Pred comp//排序規則
);

下面是函數的實現部分,與主題的關系不大,可以略過。

template<typename RandIter>
void merge_sort
(
    RandIter iterBeg, RandIter iterEnd, //開始與尾后迭代器
    bool (*comp)(typename std::remove_reference<decltype(*iterBeg)>::type, 
        typename std::remove_reference<decltype(*iterBeg)>::type) = []
        (typename std::remove_reference<decltype(*iterBeg)>::type a,
         typename std::remove_reference<decltype(*iterBeg)>::type b) ->bool
            {return a < b; }    
)
{
    //遞歸終止條件為子序列只有一個元素,一個元素一定是有序的
    //第二個判斷條件是為了防止輸入空序列
    if (iterBeg + 1 == iterEnd || iterBeg == iterEnd)
        return;

    RandIter iterMid = iterBeg + (iterEnd - iterBeg) / 2;//指向序列中間位置
    merge_sort(iterBeg, iterMid, comp);//遞歸
    merge_sort(iterMid, iterEnd, comp);//只有子序列有序才進行合並
    std::vector<std::remove_reference<decltype(*iterBeg)>::type>temp;//用來存儲合並的有序序列
    temp.reserve(iterEnd - iterBeg);//保留空間用於存諸元素,防止空間擴張帶來的額外花銷
    auto it1 = iterBeg, it2 = iterMid;//分別指向兩個子序列的開始位置
    while (it1 != iterMid || it2 != iterEnd)//還有元素沒處理
    {
        if (it1 == iterMid)//序列1的元素已放完
            temp.emplace_back(*it2++);//只能放序列2的元素,后加的優先級高於解引用 (解引用與前加同級)
        else if (it2 == iterEnd)
            temp.emplace_back(*it1++);
        else
            temp.emplace_back(comp(*it1, *it2) ? *it1++ : *it2++);//按謂詞規定的順序放入
    }

    //將排好序的序列復制到原序列中的位置
    typename std::vector<std::remove_reference<decltype(*iterBeg)>::type>::iterator itTemp = temp.begin();//第一個typename說明iterator是一個類型名
    for (RandIter it2 = iterBeg; it2 != iterEnd; ++itTemp, ++it2)
        * it2 = *itTemp;
}

歸並排序不能原位操作,這里使用一個vector來存儲臨時數據。

2019.10新增

  • 從一個指針類型得到其基本類型的算法大致如下(type_traits
template <class _Ty>
struct remove_pointer //如果_Ty不是指針類型,則直接使用原型
{
    using type = _Ty;
};

template <class _Ty>
struct remove_pointer<_Ty*> //如果_Ty是指針類型,則使用指針
{
    using type = _Ty;
};
  • 使用默認參數的版本沒能實現對仿函數的接收
    string s1("12345");
    struct cmp
    {
        bool operator()(char a, char b) { return a > b; }
    };

    merge_sort(s1.begin(), s1.end(), cmp());//不成功
    std::sort(s1.begin(), s1.end(), cmp());//成功


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM