随机时间点
随机数与时间
main.cpp
#include <iostream>
#include <chrono>
#include <random>
#include <thread>
#include "mclog.h"
#include "rand_int.h"
int main(int argc, char **argv)
{
MCLOG("随机数部分")
// 准备随机算法
// 梅森旋转算法,推荐使用
std::mt19937_64 gen;
// 其他可选算法
// std::mt19937 gen;
// std::minstd_rand gen;
// std::ranlux48 gen;
// 准备伪随机数种子
// 设备提供的随机种子,推荐使用
std::random_device dev;
gen.seed(dev());
// 固定时间种子,每次生成的数据都一样
// gen.seed(44);
// 随机时间种子
// gen.seed(std::chrono::system_clock::now().time_since_epoch().count());
// 随机序列分配器
// 从随机数中分配数据类型 int
// 会生成包括 -3 和 3 的数字
std::uniform_int_distribution<int> unifi(-3, 3);
for (int i = 0; i < 10; i++)
{
int num = unifi(gen);
MCLOG("整数 " << $(num));
}
// 从随机数中分配数据类型 double
std::uniform_real_distribution<double> unifr(0.5, 1.0);
for (int i = 0; i < 5; i++)
{
double num = unifr(gen);
MCLOG("浮点 " << $(num));
}
// 快速使用
rand_int rand(20, 80);
for (int i = 0; i < 5; i++)
{
int num = rand.value();
MCLOG("快速使用 " << $(num));
}
// 常用的分配器
// uniform_int_distribution int 均匀-整数分布
// uniform_real_distribution double 均匀-浮点分布
// bernoulli_distribution bool 均匀-布尔分布
// normal_distribution double 正态-距离中间点扩散概率
// discrete_distribution int 离散-列表中下标出现概率
// poisson_distribution int 泊松-模拟一小时内发生次数分布
// gamma_distribution double 泊松-模拟所有事件发生总长分布
// 时间部分
MCLOG("\n时间部分")
// 开启命名空间
using namespace std::chrono;
// 定义线性时间
steady_clock clock;
// 睡眠 100 ms 时间
time_point<steady_clock> tbegin = clock.now();
std::this_thread::sleep_for(milliseconds(100));
time_point<steady_clock> tend = clock.now();
// 打印时间
auto tspace = tend - tbegin;
MCLOG("纳秒 " $(tspace.count()));
// 转为毫秒
auto ms = duration_cast<milliseconds>(tspace).count();
MCLOG("毫秒 " $(ms));
// 常见的时间单位
// nanoseconds 纳秒
// microseconds 微妙
// milliseconds 毫秒
// seconds 秒
// minutes 分
// hours 时
// 制作一个简单的随机定时任务
MCLOG("\n定时清理任务")
// 任务列表
std::vector<std::string> vec{"洗衣服", "拖地", "去冰箱结块", "清理桌面", "打扫房间", "喝可乐"};
// 假设每个小时生成一次任务,从早上八点到下午五点
rand_int rand_task(0, vec.size() - 1);
auto tbegin_task = clock.now();
for (int time = 8; time < 17; time++)
{
std::string task = vec[rand_task.value()];
MCLOG("你需要执行的任务 " $(task) $(time));
// 休眠一个小时
// std::this_thread::sleep_for(hours(1));
std::this_thread::sleep_for(milliseconds(100));
}
auto tspace_task = duration_cast<milliseconds>(clock.now() - tbegin_task).count();
MCLOG("清理任务总计时间 " $(tspace_task));
return 0;
}
rand_int.h
#ifndef RAND_INT_H
#define RAND_INT_H
#include <random>
// 定义整数随机类
class rand_int
{
public:
// 声明构造函数,未实现
rand_int(int min, int max);
// 声明获取随机数,未实现
int value();
private:
// 分配器
std::uniform_int_distribution<int> _unifi;
// 算法
std::mt19937_64 _gen;
};
#endif // RAND_INT_H
rand_int.cpp
#include "rand_int.h"
// 实现 rand_int 构造函数
rand_int::rand_int(int min, int max) : _unifi(min, max)
{
_gen.seed((std::random_device())());
}
// 实现 value 函数,生成随意数
int rand_int::value()
{
return _unifi(_gen);
}
打印结果
随机数部分 [/home/red/open/github/mcpp/example/14/main.cpp:11]
整数 [num: 0] [/home/red/open/github/mcpp/example/14/main.cpp:40]
整数 [num: -2] [/home/red/open/github/mcpp/example/14/main.cpp:40]
整数 [num: 0] [/home/red/open/github/mcpp/example/14/main.cpp:40]
整数 [num: -3] [/home/red/open/github/mcpp/example/14/main.cpp:40]
整数 [num: 3] [/home/red/open/github/mcpp/example/14/main.cpp:40]
整数 [num: 3] [/home/red/open/github/mcpp/example/14/main.cpp:40]
整数 [num: -2] [/home/red/open/github/mcpp/example/14/main.cpp:40]
整数 [num: 1] [/home/red/open/github/mcpp/example/14/main.cpp:40]
整数 [num: 3] [/home/red/open/github/mcpp/example/14/main.cpp:40]
整数 [num: -2] [/home/red/open/github/mcpp/example/14/main.cpp:40]
浮点 [num: 0.540142] [/home/red/open/github/mcpp/example/14/main.cpp:48]
浮点 [num: 0.668982] [/home/red/open/github/mcpp/example/14/main.cpp:48]
浮点 [num: 0.948659] [/home/red/open/github/mcpp/example/14/main.cpp:48]
浮点 [num: 0.550169] [/home/red/open/github/mcpp/example/14/main.cpp:48]
浮点 [num: 0.587727] [/home/red/open/github/mcpp/example/14/main.cpp:48]
快速使用 [num: 67] [/home/red/open/github/mcpp/example/14/main.cpp:56]
快速使用 [num: 40] [/home/red/open/github/mcpp/example/14/main.cpp:56]
快速使用 [num: 69] [/home/red/open/github/mcpp/example/14/main.cpp:56]
快速使用 [num: 42] [/home/red/open/github/mcpp/example/14/main.cpp:56]
快速使用 [num: 35] [/home/red/open/github/mcpp/example/14/main.cpp:56]
时间部分 [/home/red/open/github/mcpp/example/14/main.cpp:69]
纳秒 [tspace.count(): 100233933] [/home/red/open/github/mcpp/example/14/main.cpp:84]
毫秒 [ms: 100] [/home/red/open/github/mcpp/example/14/main.cpp:88]
定时清理任务 [/home/red/open/github/mcpp/example/14/main.cpp:99]
你需要执行的任务 [task: 清理桌面] [time: 8] [/home/red/open/github/mcpp/example/14/main.cpp:110]
你需要执行的任务 [task: 洗衣服] [time: 9] [/home/red/open/github/mcpp/example/14/main.cpp:110]
你需要执行的任务 [task: 洗衣服] [time: 10] [/home/red/open/github/mcpp/example/14/main.cpp:110]
你需要执行的任务 [task: 喝可乐] [time: 11] [/home/red/open/github/mcpp/example/14/main.cpp:110]
你需要执行的任务 [task: 清理桌面] [time: 12] [/home/red/open/github/mcpp/example/14/main.cpp:110]
你需要执行的任务 [task: 清理桌面] [time: 13] [/home/red/open/github/mcpp/example/14/main.cpp:110]
你需要执行的任务 [task: 拖地] [time: 14] [/home/red/open/github/mcpp/example/14/main.cpp:110]
你需要执行的任务 [task: 拖地] [time: 15] [/home/red/open/github/mcpp/example/14/main.cpp:110]
你需要执行的任务 [task: 拖地] [time: 16] [/home/red/open/github/mcpp/example/14/main.cpp:110]
清理任务总计时间 [tspace_task: 904] [/home/red/open/github/mcpp/example/14/main.cpp:117]
我相信一路看到这里的朋友,都应该已经掌握了大部分的基础知识,是时候编写一些语法之外的东西了
今天带来的是随机数和时间的简单应用,他们是STL的一部分,而是日常代码中非常常见的部分,那我接下来会讲一讲如何使用他们,先看随机数吧
随机数
std::mt19937_64 gen; // 算法
std::random_device dev; // 种子
std::uniform_int_distribution<int> unifi; // 分配器
在C++中使用随机数还挺麻烦的,它需要三个部分组成,算法,种子,分配器,他们组成了一个完整随机数的使用
每一次你都需要分配这三个部分的东西,算法决定了随机数的质量和速度,种子决定了每次使用随机数的重复率,分配器则负责限制随机数的范围
要注意的是,三个部分都是可以替换成其他部分的,通常来讲,算法和种子机会是不需要替换,但是分配器是需要替换的,不同分配器制作出来的数据是完全不一样的,分配器决定了随机数是整数还是浮点数,是正态分布还是均匀分布
可选的部分
// 可选算法
// std::mt19937_64 gen;
// std::mt19937 gen;
// std::minstd_rand gen;
// std::ranlux48 gen;
// 常用的分配器
// uniform_int_distribution int 均匀-整数分布
// uniform_real_distribution double 均匀-浮点分布
// bernoulli_distribution bool 均匀-布尔分布
// normal_distribution double 正态-距离中间点扩散概率
// discrete_distribution int 离散-列表中下标出现概率
// poisson_distribution int 泊松-模拟一小时内发生次数分布
// gamma_distribution double 泊松-模拟所有事件发生总长分布
如果你需要更换组成部分的话,这一段是你可选的部分参考,特别是分配器部分,他们会根据不同类型描述计算随机数,产生的数据是完全不一样的,随机数的引用总是需要根据场景需要进行选择
简化随机数
随机数的使用很繁琐,在 rand_int.h 头文件中提供了一种封装的思路,你可以把常用的随机方式提取成函数或者类来使用 rand_int 类就是如此,里面只包含了固定算法的均匀整数随机值可用
随机设备
在种子上不得不提 std::random_device 虽然是常用的产生随机数的种子,但是时间和固定值也是常用的种子
在C++中时间已经到了纳秒级别,完全可以用来当作随机种子,如果再用C语言中的 srand(time(NULL)); 来生成随机数,那我建议你使用C++的随机数生成方式,因为它可以非常快速的生成随机数,而C语言的方法在循环中会高度重复
固定种子则是直接向算法提供一个固定值,这个值让算法产生的伪随机序列总是一致的,你可以使用这个序列来反复测试随机代码
时间差
steady_clock // 线性时间
system_clock // 系统时间
随机数和时间算是C++STL提供的为数不多的在数据结构与语法之外应用库了,他们作为基础应用上总算还是有一点关联的
在时间上,存在 steady_clock 线性时间和 system_clock 系统时间两种类型,但是系统时间连提供本地时间与格式化年月日都没有,只能说时间不等人,system_clock 类型可以说是毫无作用
那就只有 steady_clock 可用了,他的功能也十分有限,只提供一个线性的纳秒级别增长的时间点,你可以获取这个时间点,如果你在时间点之间加上一些运行代码,用两个时间点相减可以获取代码的运行时间,这个或许就是 steady_clock 位数不多的用处了
时间格式
// 常见的时间单位
// nanoseconds 纳秒
// microseconds 微妙
// milliseconds 毫秒
// seconds 秒
// minutes 分
// hours 时
时间默认的单位是 nanoseconds 纳秒,而且不能直接使用,你需要使用 count 函数转为整数才行,时间单位之间的转换需要使用 duration_cast 来进行,注意这只是一个模板而不是关键字
C++11的时间是不完善的,甚至是没什么可以用的,如果你想用C++提供的时间,那就需要自己实现大量的功能,比如日期,日期时间加减等功能都没有,可以说相当鸡肋,也只是比没有好而已
休眠时间
std::this_thread::sleep_for(milliseconds(100));
sleep_for 函数提供休眠使用,则是C++内提供的,请不要使用 sleep 等系统文件提供的休眠,因为C++可以保证是跨平台的
this_thread 是命名空间而不是一种类型,这个命名空间内放置了一些有关线程的函数,比如休眠线程函数,获取线程名称等
随机任务
auto tbegin_task = clock.now();
auto tspace_task = duration_cast<milliseconds>(clock.now() - tbegin_task).count();
至于C++11提供的时间和随机数,我认为是不完整的,使用他们都需要自己实现一些功能,这不是新手需要过多了解的,这一篇文章只是一个引子,告诉你可以用的部分
我在最后编写了一个简单的 定时清理任务 把随机数和计算运行时间联系在了一起,你应该知道他们有什么作用了吧
在最后获取 tspace_task 变量时,看到长长的一大串代码不知道是否能看懂,则是一个考验,实际上它的功能和 tspace 变量的计算逻辑是一样的
命名空间与自动推倒
你会发现我现在的代码例子也开始出现 using namespace 命名空间和 auto 自动推导关键字,他们是简化代码的关键,虽然我不建议总是使用他们
比如 auto 关键字,当你明确一个类型且这个类型在同一段代码中已经出现时,完全可以使用 auto 代替,因为这时候类型是明确的,正如 main.cpp 代码中,我已经写明了 tbegin 的具体类型,后续的 tend tspace 的类型是可以轻松得知和识别的,这时候就是使用 auto 简化代码的最佳时机
编写可识别类型是 编程规范 的重要一步
当存在多层命名空间时使用 using namespace 打开内部的命名空间也是一个好的选择,我是我建议只在使用的局部打开,而不是全局范围,而且最外层的命名空间通常是不推荐你打开的
或许你看到这里也发现代码中的命名方式开始变得抽象起来,比如 tbegin 这个变量,本意是代表 begin_time 但是被简写了,这不是一个好习惯,这是C++习惯使用下划线命名导致的问题,当命名复杂时就会变的很长,然后习惯简写,最后变得抽象
类与文件分离
声明与定义
你应该也看到 rand_int 这个类是分为 rand_int.h rand_int.cpp 两个文件的,他们是采用文件分离的方式写的,这是C++的普遍做法,请你也保持这个习惯,教学代码例子里面都习惯将声明和定义写在一起,是因为这样可以让代码看起来更简洁和完整
从 rand_int.h 文件可以看到,rand_int 的构造函数和 value 函数都是只是声明,是以分号结尾的,而在 rand_int.cpp 文件中 rand_int::value 函数的写法与普通的函数文件分离不一样,它多了 rand_int:: 作用域,这表明这个函数在 rand_int:: 这个类的中
类作用域与定义
在类的文件分离中,作用域是必须的,这表明一个函数属于什么类,这是因为类中有同名的函数
这种看起来很麻烦的写法其实对于新手来说可能不适应,但是我推荐你怎么写
需要注意的是,类中的成员声明和定义其实不会引起重命名问题,但普通函数的定义写在头文件会引起重定义问题
在C++中一个函数的声明和定义有时候会让新手分不清,其实很简单,一个函数用分号结尾就是声明,函数有大括号的具体实现就是定义,函数的定义不要出现在头文件
如果普通函数的定义出现在头文件中会引起重定义问题,类成员函数和模板函数不会,模板需要声明与定义要一起写在头文件中
上面这一段就是实现函数分离需要注意的事项
项目路径
https://github.com/HellowAmy/mcpp.git