std::ratio 简单使用举例
author: hjjdebug
date: 2025年 06月 09日 星期一 14:28:40 CST
descrip: std::ratio 简单使用举例
文章目录
- 1. 先看一个简单的例子 1/2+/1/3=5/6
- 2 std::ratio 的手册页
- 3. std::ratio_add 到底是什么呢?
- 4. 代码注释
- 5. 加深理解.
- 6. 自定义的std::ratio 与 std::ratio_add
std::ratio 代表一个比值,代表一个分数, 是一种类型, 这容易理解, 因为它有自己的类成员num,den
std::ratio 还能参与运算,例如进行加,减,乘,除, 而且是编译期预算,这下就感觉有点意思了.
c++ 的编译期的分数运算.
1. 先看一个简单的例子 1/2+/1/3=5/6
$ cat main.cpp
// ratio example
#include <ratio>
#include <stdio.h>int main (void)
{using one_third= std::ratio<1,3> ; //类型using one_second = std::ratio<1,2> ; //类型using sum = std::ratio_add<one_third,one_second> ; //类型,由一种类型可以转变成另一种类型printf("one_third=%ld/%ld\n",one_third::num,one_third::den);printf("one_second=%ld/%ld\n",one_second::num,one_second::den);double r=(double)sum::num/sum::den;printf("sum=%ld/%ld (which is:%f)\n",sum::num,sum::den,r);sum obj1;r=(double)obj1.num/obj1.den;printf("sum=%ld/%ld (which is:%f)\n",obj1.num,obj1.den,r);return 0;
}
执行结果:
$ ./tt
one_third=1/3
one_second=1/2
sum=5/6 (which is:0.833333)
sum=5/6 (which is:0.833333)
初见这个例子,还是觉得非常的奇怪的, 这种写法是什么意思?怎么工作的?
std::ratio<1,3> 是一种类型.
std::ratio<1,2> 是另一种类型
它们都是模板std::ratio 的实例化类型. std::ratio 叫类模板. 这2个实例可以叫模板类,是具体的类型.
2 std::ratio 的手册页
$ man std::ratio
有如下描述:
template<intmax_t _Num, intmax_t _Den = 1>
struct std::ratio< _Num, _Den >" Provides compile-time rational arithmetic. //提供编译期有理数算术
它的两个输入参数是非类型参数.
非类型参数不是类型,而是具体的值,通常是整数值或整数常量
std::ratio_add 是另一个类模板,它接受2种类型做输入参数.
当我们查询它的手册页时, 竟然没有条目.
$ man std::ratio_add
没有 std::ratio_add 的手册页条目
好吧,顺便看看std::ratio 相关的有几个条目?
$ man -w std::ratio
/usr/share/man/man3/std::ratio.3cxx.gz
找到std::rario手册页位置后, 例如相关帮助. 由此看出是 3条,另2条是equal, not_equal
$ ls -l /usr/share/man/man3/std::ratio*
-rw-r–r-- 1 root root 546 10月 24 2022 /usr/share/man/man3/std::ratio.3cxx.gz
-rw-r–r-- 1 root root 493 10月 24 2022 /usr/share/man/man3/std::ratio_equal.3cxx.gz
-rw-r–r-- 1 root root 484 10月 24 2022 /usr/share/man/man3/std::ratio_not_equal.3cxx.gz
3. std::ratio_add 到底是什么呢?
可以对编译进行一下预处理, 然后找到std::ratio_add, 为:
template<typename _R1, typename _R2>
using ratio_add = typename __ratio_add<_R1, _R2>::type;
从这里,我们知道ratio_add 是一种类型别名就可以了.
至于这个类型到底是什么?包含了什么成员,
那要继续追查 __ratio_add 重点关注__ratio_add::type
template<typename _R1, typename _R2>
struct __ratio_add
{
typedef typename __ratio_add_impl<_R1, _R2>::type type;
static constexpr intmax_t num = type::num;
static constexpr intmax_t den = type::den;
};
__ratio_add 有两个常量num,den, 这就是我们用printf 打印的内容,
这两个值的计算依赖于__ratio_add_impl<_R1, _R2>::type
这里是说, __ratio_add::type 是 __radio_add_imple::type的别名
其中 __radio_add_imple<_R1,_R2> 的type 是如下定义的, 看其中的一种形式定义
private:
typedef typename __ratio_add_impl<
ratio<_R1::num, _R1::den>,
ratio<_R2::num, _R2::den> >::type __t;
public:
typedef ratio<-__t::num, __t::den> type;
从这个定义中看出,将类型R1,类型R2共同构建一个复合类型,__ratio_add_impl<…> ,
复合类型里边的type类型,把它叫别名 __t, 剧透一下,实际上这个__t也是std::ratio 类型.
我这里没有继续追下去到此为止了.
我们把__t的num,den做为参数创建 std::ratio类型,把它叫__ratio_add_impl的type 类型
其下面具体的运算过程 __ratio_add_impl 比较复杂,需要考虑分子,分母符号问题, 分母通分,分子相加等.
就不copy代码也不再分析了.
绕了一大圈,实际上 template std::ratio_add ==>bla bla bla… ==> ratio<_num,_den>
总之,它这里面引入了诸多的类型.
类型是可以有变量的, (有的变量属于类型而不属于对象)
类型可以以类型为输入参数,(模板参数有2种类型: 类型参数,非类型参数)
类型内可以重新构建其它类型 (例如上面由num,den构成的ratio<__t::num,__t::den>类型)
可以定义类型的小名. (例如将上面长的称谓std::ration<__t::num,__t::den>用type 来称谓.
这些关系构成面向类型的编程.
这里所说的面向类型强调的是编译期的数据类型, 并非是独立的编程范式.
这里的代码,是一种解释性语言,消费者是gcc, gcc负责解释并执行模板代码. gcc 能够记录和推导类型.
4. 代码注释
one_third 是std::ratio 的一种实例化类型
one_second 是std::ratio 的一种实例化类型
sum 也是 std::ratio 的一种实例化类型, 它的分子,分母是由2个输入参数one_third,one_second来决定的.
在编译期间进行计算和推导.
本博没有给出分子,分母的推导过程,只说明了ratio_add 是一种std::radio类型
5. 加深理解.
从代码上去推导sum 的类型还是比较费劲的, 所以我们不如直接去看最后的推导结果,
反正这对gcc 是一件小事. 为了看sum 的类型, 我们为sum类型声明一个变量obj1
就如同代码种所示的那样.
然后用gdb 来打印obj1的类型,如下:
(gdb) ptype obj1
type = struct std::ratio<5, 6> {
static const intmax_t num;
static const intmax_t den;
}
6. 自定义的std::ratio 与 std::ratio_add
鉴于标准库中的模板库 std::ratio_add 很难阅读,
不如实现一个简单的自定义的std::ratio 及 std::ratio_add模板
以进一步理解模板编程.
下面代码已经是很精简的了,慢慢体会其中之妙吧.
简单来说,求最大公约数是重点和难点,求最小公倍数.分数加法运算(通分母,分子相加)
测试代码:
$ cat main.cpp
#include <stdio.h>
#include <stdint.h> // for intmax_t// 编译期计算最大公约数 (GCD) Greatest common divider, 辗转相除法
template <intmax_t A, intmax_t B>
struct gcd {static constexpr intmax_t value = gcd<B, A % B>::value;
};//特化gcd
template <intmax_t A>
struct gcd<A, 0> {static constexpr intmax_t value = A;
};// 自定义 ratio 结构
template <intmax_t Num, intmax_t Den = 1>
struct ratio {static_assert(Den != 0, "Denominator cannot be zero");static constexpr intmax_t num = Num / gcd<Num, Den>::value;static constexpr intmax_t den = Den / gcd<Num, Den>::value;
};// 编译期计算最小公倍数 (LCM) Least common multiple
template <intmax_t A, intmax_t B>
struct lcm {static constexpr intmax_t value = (A / gcd<A, B>::value) * B;
};// 自定义 ratio_add
template <typename R1, typename R2>
struct ratio_add {
private:static constexpr intmax_t lcm_val = lcm<R1::den, R2::den>::value; //求公分母static constexpr intmax_t num_sum = R1::num * (lcm_val / R1::den) + R2::num * (lcm_val / R2::den); //求分子
public:using type = ratio<num_sum, lcm_val>;
};int main() {using R1 = ratio<1, 3>; // 1/3using R2 = ratio<2, 5>; // 2/5using Result = ratio_add<R1, R2>::type;printf("Result:%ld/%ld\n",Result::num, Result::den); // 输出 11/15
}
执行结果:
$ ./tt
Result:11/15