C++ 泛型

泛型

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) ->bool
            {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来存储临时数据。

猜你喜欢

转载自www.cnblogs.com/xunxunxun/p/11440531.html