首页 > 编程语言 >浅谈 C++ 模板 & 泛化 (妈妈再也不用担心我不会用 std::sort 了)

浅谈 C++ 模板 & 泛化 (妈妈再也不用担心我不会用 std::sort 了)

时间:2022-12-25 18:59:07浏览次数:40  
标签:std arr 浅谈 seq int double MyStruct 泛化

基础复习

先上个对 int 类型数组的插入排序:

void insertionSort_01(int* seq, int firstIndex, int lastIndex) {
    for (int j = firstIndex + 1; j <= lastIndex; ++j) {
        int key = seq[j];
        int i = j - 1;
        while (i >= firstIndex && key < seq[i]) {
            seq[i + 1] = seq[i];
            --i;
        }
        seq[i + 1] = key;
    }
}
  • 提出问题: 如果想排 double 类型数组怎么办?

可以重载一个 double 版本:

void insertionSort_01_b(double* seq, int firstIndex, int lastIndex) {
    ...
}

当然, 更好的方式是利用 C++ 的模板泛化元素类型:

template<class ElemType>
void insertionSort_02(ElemType* seq, int firstIndex, int lastIndex) {
    ...
}

步入正题

接着提出两个问题:

  • 1 是否一定要求升序排列
  • 2 类 ElemType 的对象是否一定能使用 operator<

为解决问题 1, 我们可以额外写个降序排列版本:

template<class ElemType>
void insertionSort_02_b(ElemType* seq, int firstIndex, int lastIndex) {
    for (int j = firstIndex + 1; j <= lastIndex; ++j) {
        ElemType key = seq[j];
        int i = j - 1;
        // Change {<} to {>} when comparing {key} and {seq[i]}:
        while (i >= firstIndex && key > seq[i]) {
            seq[i + 1] = seq[i];
            --i;
        }
        seq[i + 1] = key;
    }
}

对于问题 2, 我们举个例子.
现有:

struct MyStruct
{
    int aa;
    int bb;
};

MyStruct arr_MyStruct[4] = { {1,4},{3,1},{9,-1},{12,0} };

要求对 arr_MyStruct 中的元素以 MyStruct::aa 排序.
对于 C++ 新手来说, 这是一个比较难解决的问题, 也是问题 2 聚焦的关键.

对问题 1 的处理中, 我们将 "比较" 这个谓语 (predicate) 从 "operator<" 替换为 "opeartor>";
这给了我们一些提示: 是否可以像我们用模板来泛化元素类型那样泛化谓语?

提出概念: 函数对象 (function object)
定义以下的类:

struct bad_greater {
    // {operator()} should be defined as a const method,
    // in order to make it available to <const bad_greater> instances.
    bool operator()(const MyStruct& a, const MyStruct& b) const { return a.aa > b.bb; }
};

bad_greater 所创建的实例为函数对象, 可以参考以下使用案例:

// Omit the definition of class <MyStruct>.
MyStruct arr_MyStruct[4] = { {1,4},{3,1},{9,-1},{12,0} };
bad_greater compare;
std::cout << compare(arr_MyStruct[0], arr_MyStruct[1]) << std::endl;
// Use anonymous instance:
std::cout << bad_greater()(arr_MyStruct[0], arr_MyStruct[1]) << std::endl;

bad_greater 之所以 bad, 是因为其唯独提供对类 MyStruct 实例的比较.
定义一个模板类 good_less 并对 MyStruct 偏特化以解决这个问题:

// Omit the definition of class <MyStruct>.
template<class T>
struct good_less
{
    bool operator()(const T& a, const T& b) const { return a < b; }
};

template<>
struct good_less<MyStruct>
{
    bool operator()(const MyStruct& a, const MyStruct& b) const { return a.aa < b.bb; }
};

有了函数对象, 我们可以泛化算法中的谓语:

template<class ElemType, class _Pred>
void insertionSort_03(ElemType* seq, int firstIndex, int lastIndex, const _Pred& compare) {
    for (...) {
        ...
        while (... && compare(key, seq[i])) {
            ...;
        }
        ...
    }
}

调用函数 insertionSort_03() 时, 我们要注意, 编译器能直接根据传入参数推断模板的实例化类型; 因此无需提供额外的模板类参数:

// Omit the definition of class <MyStruct>.
// Omit the definition of class <good_less>.
// Omit the definition of class <good_greater>.

MyStruct arr_MyStruct[4] = { {1,4},{3,1},{9,-1},{12,0} };
// Ascending order:
insertionSort_03(arr_MyStruct, 0, 3, bad_less<MyStruct>());
// Descending order:
insertionSort_03(arr_MyStruct, 0, 3, bad_greater<MyStruct>());

// Also works for array with orther types:
double arr_double[4] = { 1,9.1,0.9,-3.1 };
insertionSort_03(arr_MyStruct, 0, 3, good_greater<double>());

std::sort() 的升降序排序

std::sort() 和我们的 insertionSort_03() 一样泛化的谓语, 而且 STL 还提供了类 std::greaterstd::less 等用于定义函数对象.
升降序的使用方法参考以下代码:

#include <algorithm>
#include <functional>

double arr_double[4] = { 1,9.1,0.9,-3.1 };
// Ascending order:
std::sort(arr_double, arr_double + 4);
// Ascending order:
std::sort(arr_double, arr_double + 4, std::less<double>());
// Descending order:
std::sort(arr_double, arr_double + 4, std::greater<double>()));

你可能会问: 为什么第一个例子不用和之前说的一样, 传入一个函数对象?
其实这没什么高深的, 在 C++14 之前, 其实这只是一个函数重载而已. 给个差不多的伪代码出来:

// Pseudocode for definition of std::sort (with only 2 arguments):
std::sort(seq_begin, seq_end){
    std::sort(seq_begin, seq_end, std::less());
}

C++14 之后在谓语类和 std::sort() 的定义上用了点小 trick, 下面给点启发性的例子 (如果不感兴趣, 你可以跳过这段):

template<class T = void>
struct less
{
    template<class ElemType>
    bool operator()(const ElemType& a, const ElemType& b) const { return a < b; }
};
template<class ElemType, class _Pred = less<void>>
void sort(..., const _Pred& compare = {}) {
    ...
}

说简单点, 就是 less 给了一个默认模板实例化类型 void; 而真正进行比较的 operator() 又是一个模板. 调用 sort 时, 不用考虑第三个参数 (函数对像) 具体是什么类型, 反正 operator() 在比较时会自行实例化.
可以参考以下使用案例:

// Under C++ 14 (or later) standard.
#include <algorithm>
#include <functional>

double arr_double[4] = { 1,9.1,0.9,-3.1 };
std::sort(arr_double, arr_double + 4); // std::less<void>
std::sort(arr_double, arr_double + 4, std::less()); // std::less<void>
std::sort(arr_double, arr_double + 4, std::less<double>()); // std::less<double>

int arr_int[4] = { 1,3,4,0 };
std::sort(arr_double, arr_double + 4, std::less()); // std::less<int>

std::sort() 排其他类型实例

如果看懂了前面的内容, 想必你也能够猜出来怎么实现这个问题了.
主义, std::less 之类的谓语类型说到底就是结构体, 和我们上面实现的 good_less 没啥区别. 所以如果我们还是要排序上文提到的 MyStruct 数组:

// Omit the definition of class <MyStruct>.
// Omit the definition of class <good_less>.
// Omit the definition of class <good_greater>.
#include <algorithm>

MyStruct arr_MyStruct[4] = { {1,4},{3,1},{9,-1},{12,0} };
// Ascending order:
std::sort(arr_MyStruct, arr_MyStruct + 4, good_less<MyStruct>());
// Descending order:
std::sort(arr_MyStruct, arr_MyStruct + 4, good_greater<MyStruct>());

统一指针和迭代器

作为一个 STL 使用者, 难免会遇到指针与迭代器不统一的问题. 例如以下例子:

// Use pointer:
int arr_int[] = ...;
std::sort(arr_int, ...);

// Use iterator:
std::vector<int> arr_vector = ...;
std::sort(arr_vector.begin(), ...);

解决方式之一是统一泛化指针类型和迭代器类型, 这里把它们都当作类 _RandIt .
我们还是以最开始的 insertionSort 为例, 给出示范代码.
需要注意的是, 通过迭代器和指针获取元素类型 (用来定义 key 时), decltype 会保留解引用 (dereference) 后会在类型中留下的引用 & (也就是说 decltype(arr_int[0]) 得到的类型不是 int 而是 int& ); 因此需要调用 std::remove_reference 来删除类型中的引用.

using index = long long;


template<class _RandIt, class _Pr = std::less<void>>
void insertionSort(_RandIt seq, index firstIndex, index lastIndex, const _Pr& comp = {}) {
    for (index j = firstIndex + 1; j <= lastIndex; ++j) {
        typename std::remove_reference<decltype(*seq)>::type key = seq[j];
        index i = j - 1;
        while (i >= firstIndex && comp(key, seq[i])) {
            seq[i + 1] = seq[i];
            --i;
        }
        seq[i + 1] = key;
    }
}

再给个归并排序的代码吧! 就说到这里 (计组完全没学, 寄).

using index = long long;

template<class _RandIt, class _Pr>
void merge(_RandIt seq, index subAFirst, index subALast, index subBLast,
    auto MAX, auto MIN, const _Pr& comp) {
    auto END = comp(1, 2) ? MAX : MIN;

    size_t sizeSubA = subALast - subAFirst + 2;
    size_t sizeSubB = subBLast - subALast + 1;

    auto subA = new typename std::remove_reference<decltype(*seq)>::type[sizeSubA];
    std::copy(seq + subAFirst, seq + subALast + 1, subA);
    subA[sizeSubA - 1] = END;

    auto subB = new typename std::remove_reference<decltype(*seq)>::type[sizeSubB];
    std::copy(seq + subALast + 1, seq + subBLast + 1, subB);
    subB[sizeSubB - 1] = END;

    // Merge two subsequences to origin {seq[subAFirst : subBLast]}:
    for (index k = subAFirst, i = 0, j = 0; k <= subBLast; ++k) {
        if (i >= sizeSubA || j >= sizeSubB) return;
        // Merge:
        if (comp(subA[i], subB[j])) {
            seq[k] = subA[i]; ++i;
        } else {
            seq[k] = subB[j]; ++j;
        }
    }

    delete[] subA;
    delete[] subB;
}

template<class _RandIt, class _Pr = std::less<void>>
void mergeSort(_RandIt seq, index firstIndex, index lastIndex,
    auto MAX, auto MIN, const _Pr& comp = {}) {
    if (firstIndex >= lastIndex) return;
    index mid = (firstIndex + lastIndex) / 2;
    mergeSort(seq, firstIndex, mid, MAX, MIN, comp);
    mergeSort(seq, mid + 1, lastIndex, MAX, MIN, comp);
    merge(seq, firstIndex, mid, lastIndex, MAX, MIN, comp);
}

标签:std,arr,浅谈,seq,int,double,MyStruct,泛化
From: https://www.cnblogs.com/jamesnulliu/p/MegumiIWantGeneralization.html

相关文章

  • 优先队列(std_priority_queue)
    title:优先队列(std::priority_queue)date:2022-11-1715:50:12tags:算法本文章遵守知识共享协议CC-BY-NC-SA,转载时须在文章的任一位置附上原文链接和作者署名(ric......
  • 浅谈线段树
    本篇将会记录我的线段树学习时长其实很早就想学了,但由于奇妙的原因咕了好久2021.1.5今天讲讲线段树概念和初始建树线段树的概念线段树是个二叉树线段树的每个区间是......
  • GetStdHandle获取标准设备句柄
     #include<stdio.h>#include<windows.h>intmain(void){TCHARch[]=__TEXT("我是中国人");intlen=lstrlen(ch);//返回字符长度//返回指......
  • C++提取出std::map中的key集合
    std::map<std::string,uint32_t>dictionarystd::set<conststd::string*>keySet;//std::back_inserter(keyVector)std::transform(dictionary.begin(),dictiona......
  • FastDFS客户端与自定义文件存储系统
    本文的前提是已经启动FastDFS的tracker和storage安装安装提供给大家的fdfs_client-py-master.zip到虚拟环境中 pipinstallfdfs_client-py-master.zip 链接:ht......
  • 浅谈电动汽车充电桩建设现状及规划方案
    0引言  随着能源转型步伐的加快,我国经济由高速发展迈向高质量发展。“十三五”以来,全省将节能、削煤、降碳与调结构、治污染、惠民生相结合,实施能耗总量与强度双重控制[1......
  • 浅谈地址,section,vstart
    地址:地址只是数字,描述各种符号在源程序中的位置,它是源代码文件中各符号偏移文件开头的距离。由于指令和变量所占内存大小不同,故他们的偏移量参差不齐。由编译器给各符号编......
  • 浅谈商城项目
         很多同学会说,现在很多培训机构都在做电商这个项目,那我们做这个项目的意义又是什么呢?    1、作为学生而言,刚学完SSM框架,很多基础掌握不牢靠,因此针对初学者而言......
  • C++学习---cstdio的源码学习分析10-改变文件流文件流buffer函数setvbuf
    cstdio中的文件访问函数stdio.h中定义了一系列文件访问函数(fopen,fclose,fflush,freopen,setbuf,setvbuf),接下来我们一起来分析一下setvbuf对应的源码实现。-fopen:打开文件-......
  • C++学习---cstdio的源码学习分析09-设置文件流buffer函数setbuf
    cstdio中的文件访问函数stdio.h中定义了一系列文件访问函数(fopen,fclose,fflush,freopen,setbuf,setvbuf),接下来我们一起来分析一下setvbuf对应的源码实现。-fopen:打开文件-......