高位为0代表这个数为正,否则为负,将这一位取反,则取反后这个数在无符号表示下的值相当于给取反前带符号值加上这一位的权值。两个数同时对最高位取反,前后两数大小关系不变,这就把带符号类型映射到无符号类型上去了,而且这个操作的成本非常低(取反操作用异或实现,而且操作数中有一个是常量,总共只需一句汇编语句)。
对于无符号类型,不需要也不能将最高位取反。那么问题来了,这个函数对象的类肯定是一个模板类,如何知道其模板类型参数是带符号还是无符号类型呢?你当然可以写个声明然后对每一个内置整数类型去特化,但这么暴力的方法我是不允许出现在我的博客里的。给g++加上参数“-std=c++17 -fconcepts”(不要带引号),给MSVC开启最新标准,我们来体验一把C++20中 concept 。请看代码:
1 #include <type_traits>
2
3 template <typename T>
4 class IntegerRadixBase
5 {
6 public:
7 static constexpr int bits = 4;
8 static constexpr int radix = 1 << bits;
9 static constexpr int pass = sizeof(T) * 8 / bits;
10 };
11
12 template <typename T>
13 class IntegerRadix;
14
15 template <typename T> requires std::is_signed_v<T>
16 class IntegerRadix<T> : public IntegerRadixBase<T>
17 {
18 public:
19 using IntegerRadixBase<T>::bits;
20 unsigned operator()(T _value, int _pass)
21 {
22 return ((_value ^ 1 << (sizeof(T) * 8 - 1)) >> (_pass * bits)) & ((1 << bits) - 1);
23 }
24 };
25
26 template <typename T> requires std::is_unsigned_v<T>
27 class IntegerRadix<T> : public IntegerRadixBase<T>
28 {
29 public:
30 using IntegerRadixBase<T>::bits;
31 unsigned operator()(T _value, int _pass)
32 {
33 return (_value >> (_pass * bits)) & ((1 << bits) - 1);
34 }
35 };
第15行(第26行同理), template <typename T> requires std::is_signed_v<T> 是对 class IntegerRadix 的特化,并且约束模板参数 T 要使 std::is_signed_v<T> 为 true 。 is_signed_v<T>相当于 is_signed<T>::value ,前者需要C++17(这下我把C++11/14/17/20都凑齐了),后者需要C++11,同样都在 <type_traits> 中定义。
虽然代码里没有出现 concept 这个关键字,但 requires 是和 concept 一起在新标准中加入的,所以上面这段代码算是用上了 concept 吧。
其实 concept 只是语法糖, requires 子句都可以用 std::enable_if_t 代替,比如 template <typename T> requires std::is_signed_v<T> 可以写为 template <typename T, typename = std::enable_if_t<std::is_signed_v<T>>> ,但是,很好看吗???尖括号都数不清了!
更多关于 concept 的内容,也许我以后会开一篇专门讲。
回到算法本身。在 return ((_value ^ 1 << (sizeof(T) * 8 - 1)) >> (_pass * bits)) & ((1 << bits) - 1); 这一句中(运算符优先级:乘除>移位>加减>位运算), sizeof(T) * 8 得到 T 类型的长度, 1 << (sizeof(T) * 8 - 1) 得到最高位为1其余位为0的数字, _value ^ 1 << (sizeof(T) * 8 - 1) 得到 _value 最高位取反的结果, (_value ^ 1 << (sizeof(T) * 8 - 1)) >> (_pass * bits) 将这个数右移使这一次循环所需要的4位在最低的4位上, (1 << bits) - 1 得到一个bit mask,此处值为 0b1111 ,最后 ((_value ^ 1 << (sizeof(T) * 8 - 1)) >> (_pass * bits)) & ((1 << bits) - 1) 获得这4位。无符号版的没有最高位取反这一步,其余相同。
在需要调用基数排序函数的地方,代码应该写成:
1 std::vector<int> data;
2 radix_sort(data.begin(), data.end(), IntegerRadix<int>::radix, IntegerRadix<int>(), IntegerRadix<int>::pass);
可以把上面代码中的 int 换成任意内置整数类型。
这个基数排序函数就到此为止了吗?没有。前面说过,可以转化为整数的,或者可以表示成有限个有限范围的整数的类的对象,都可以用基数排序。
假设我们有一个从C代码