基础复习

先上个对 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);
}

浅谈 C++ 模板 & 泛化 (妈妈再也不用担心我不会用 std::sort 了)的更多相关文章

  1. 妈妈再也不用担心别人问我是否真正用过redis了

    1. Memcache与Redis的区别 1.1. 存储方式不同 1.2. 数据支持类型 1.3. 使用底层模型不同 2. Redis支持的数据类型 3. Redis的回收策略 4. Redis小命令 ...

  2. 有了 tldr,妈妈再也不用担心我记不住命令了

    引言 有一次我在培训时说「程序员要善于使用 Terminal 以提高开发效率」,一位程序员反驳道:「这是 21 世纪,我们为什么要用落后的命令行,而不是先进的 GUI?」 是的,在一些人眼里,这个黑黑 ...

  3. 妈妈再也不用担心我使用git了

    妈妈再也不用担心我使用git了 Dec 29, 2014 git git由于其灵活,速度快,离线工作等特点而倍受青睐,下面一步步来总结下git的基本命令和常用操作. 安装msysgit 下载地址:ms ...

  4. 利用CH341A编程器刷新BIOS,恢复BIOS,妈妈再也不用担心BIOS刷坏了

    前几天,修电脑主析就捣鼓刷BIOS,结果刷完黑屏开不了机,立刻意识到完了,BIOS刷错了.就从网上查资料,各种方法试了个遍,什么用处都没有.终于功夫不负有心人,找到了编码器,知道了怎么用.下面看看具体 ...

  5. python爬虫07 | 有了 BeautifulSoup ,妈妈再也不用担心我的正则表达式了

    我们上次做了 你的第一个爬虫,爬取当当网 Top 500 本五星好评书籍 有些朋友觉得 利用正则表达式去提取信息 太特么麻烦了 有没有什么别的方式 更方便过滤我们想要的内容啊 emmmm 你还别说 还 ...

  6. 锋利的js之妈妈再也不用担心我找错钱了

    用js实现收银功能. <!DOCTYPE html> <html xmlns="http://www.w3.org/1999/xhtml"> <hea ...

  7. 有了这套模板,女朋友再也不用担心我刷不动 LeetCode 了

    全文包含 12000+ 字.30 张高清图片,预计阅读时间为 40 分钟,强烈建议先收藏再仔细阅读. 作者 | 李威 整理 | 公众号:五分钟学算法 个人博客 | https://www.cxyxia ...

  8. 有了jsRender,妈妈再也不用担心我用jq拼接DOM拼接的一团糟了、页面整齐了、其他伙伴读代码也不那么费劲了

    写在前面 说来也很巧, 下午再做一个页面,再普通不过的分页列表,我还是像往常一样,基于MVC环境下,我正常用PagedList.MVC AJAX做无刷新分页,这时候问题就来了,列表数据中有个轮播图用到 ...

  9. 【C#】妈妈再也不用担心自定义控件如何给特殊类型的属性添加默认值了,附自定义GroupBox一枚

    ------------------更新:201411190903------------------ 经过思考和实践,发现套路中的第1条是不必要的,就是完全可以不用定义一个名为Default+属性名 ...

  10. 妈妈再也不用担心我的移动端了:网易和淘宝的rem方案剖析

    从博主学习前端一路过来的经历了解到,前端移动开发是大部分从PC端转战移动端的小伙伴都非常头疼的一个问题,这边博主就根据一篇自己看过的移动开发文章来剖析一下网易和淘宝的rem解决方案,希望能够帮助到一些 ...

随机推荐

  1. 220722 T1 分树 (模拟)

    dfs一遍求出以每个节点为根的子树大小,然后枚举n的约数,对于每个约数i,统计sz[ ]是i的倍数的有多少个(开桶统计),如果有n/i个则答案+1. 这道题也就是个结论题,画图分析一下.复杂度O(n* ...

  2. 2022年最新最详细在IDEA中配置Tomcat(含有详细图解过程)、建立使用IEDA建立一个Web项目的案例

    1.首先已经成功安装过tomcat 如果没有成功安装,参考这篇tomcat安装教程(安装成功可忽略):https://blog.csdn.net/weixin_43304253/article/det ...

  3. 【MySQL】01_运算符、函数

    运算符 运算符是保留字或主要用于 SQL 语句的 WHERE 子句 中的字符,用于执行操作,例如:比较和算术运算. 这些运算符用于指定 SQL 语句中的条件,并用作语句中多个条件的连词. 常见运算符有 ...

  4. C# 8.0 中的 Disposable ref structs(可处置的 ref 结构)

    官方文档中的解释:   用 ref 修饰符声明的 struct 可能无法实现任何接口,因此无法实现 IDisposable. 因此,要能够处理 ref struct,它必须有一个可访问的 void D ...

  5. 十一、Pod的健康检查-探针

    Pod 的健康检查-探针 一.Pod 的健康检查-探针 1.1.探针基本概念 ​探针是由 kubelet 对容器执行的定期诊断.要执行诊断,kubelet 调用由容器实现的 Handler 有三种类型 ...

  6. 【FAQ】关于华为地图服务定位存在偏差的原因及解决办法

    一. 问题描述: 华为地图服务"我的位置"能力,在中国大陆地区,向用户展示他们在地图上的当前位置与用户的实际位置存在较大的偏差. 具体差别可以查看下方的图片: 二. 偏差较大的原因 ...

  7. CF815D

    模拟赛遇到的题目. 看各位大佬的做法都不是很懂,于是自己一通乱搞搞出来了. 题意 翻译清楚的不能再清楚了 做法 为了方便叙述,我们将每个给出的三元组表示成 \((a_i,b_i,c_i)\),所选的三 ...

  8. python基础类型,字符串

    python基本类型小结 # str,可以用索引取值,但是不能通过索引改变值, # a = "123" a[0]=10,直接TypeError因为字符串是不可变类型 # list, ...

  9. Codeforces Global Round 18 B. And It's Non-Zero(按位前缀和)

    题目大意:求一段数(l到r)的按位与结果不为零需要删除中间元素的最小个数 思路:按位与使得结果不为0只要有某一位全是1即可,所以只要统计每一位1的个数,用总个数减去1的个数就是某一位0的个数 删除包含 ...

  10. 系统启动后bond配置不生效问题定位

    背景描述 为了适配新功能,裸金属服务的磁盘镜像中做了如下修改: dracut添加network, iscsi模块 grub添加rd.iscsi.firmware=1参数 删除网卡配置文件/etc/sy ...