3.时间类的实现-c++ linux编程:从0实现muduo库系列
重点内容
这一讲的内容主要是时间类的实现,但时间类面试很少被问到,所以这里我们主要讲CMakeLists.txt如何生成库文件,以及make install的时候如何把对应的库文件和头文件安装到指定目录。
视频讲解:《C++Linux编程进阶:从0实现muduo C++网络框架系列》-第3讲.时间类的实现
主要改动
首先创建第三节课的目录并复制前1节课的内容:
cp -r lesson2 lesson3
muduo网络库框架:
- 实现:base/Timestamp.h/cc
- 实现:base/Date.h/cc
- 实现:base/TimeZone.h/cc
muduo网络库测试范例
- examples/test_timestamp.cc
- examples/test_date.cc
- examples/test_timezone.cc
CMakeLists.txt
- base/CMakeLists.txt
- examples/CMakeLists.txt
1 CMake生成库文件以及make install安装库文件和头文件
1.1 CMakeLists.txt主要改动
重点掌握
具体细节内容:
1.base/CMakeLists.txt
add_library(mymuduo_base ${base_SRCS}) 生成mymuduo_base静态库
target_link_libraries(mymuduo_base pthread) mymuduo_base依赖pthread库,默认优先依赖动态库
安装文件:
- 安装库文件:install(TARGETS mymuduo_base DESTINATION /usr/local/lib)
- 安装头文件:install(FILES ${base_HEADERS} DESTINATION /usr/local/include/mymuduo/base)
2.examples/CMakeLists.txt
target_link_libraries(test_date mymuduo_base) ,这里链接mymuduo_base这个库文件,可以注释后测试报什么错。
1.2 CMake语法讲解
1.2.1 add_library生成库文件
# 方式1:不指定类型,使用默认行为(静态库) add_library(mylib src1.cpp src2.cpp) # 方式2:明确指定类型 add_library(mylib STATIC src1.cpp src2.cpp) # 静态库 add_library(mylib SHARED src1.cpp src2.cpp) # 动态库 # 方式3:通过变量控制默认行为 set(BUILD_SHARED_LIBS ON) add_library(mylib src1.cpp src2.cpp) # 将生成动态库
1.2.2 target_link_libraries
1. 基本语法解析
target_link_libraries(test_date # 目标程序,这里也可以是库
mymuduo_base) # 需要链接的库
2.实际使用示例
# 1. 基本用法
add_executable(test_date test_date.cc)
target_link_libraries(test_date mymuduo_base)
# 2. 链接多个库
target_link_libraries(test_date
mymuduo_base
pthread
rt
)
3.更复杂的语法 -目前没有必要深究
# 三种链接属性
target_link_libraries(test_date
PRIVATE mymuduo_base # 私有链接:只有test_date使用
PUBLIC other_lib # 公有链接:test_date和链接test_date的目标都能使用
INTERFACE util_lib # 接口链接:只有链接test_date的目标能使用
)
后续遇到再深究。
1.2.3 install安装文件
1.安装库文件
insinstall(TARGETS mymuduo_base #库文件 DESTINATION /usr/local/lib # 安装位置 )
2.安装头文件
# 先选定要安装的头文件
set(base_HEADERS
Version.h
Types.h
copyable.h
noncopyable.h
StringPiece.h
Timestamp.h
Date.h
TimeZone.h
)
install(FILES ${base_HEADERS} # 要安装的头文件
DESTINATION
/usr/local/include/mymuduo/base # 安装位置
)
为什么不用lesson2的file命令?
file(GLOB HEADERS "*.h")
是因为有些头文件只是项目内部使用,可以不用对外提供,但file命令通过 *.h 匹配,则会选中所有的头文件。
2 时间类设计
2.1 涉及的时间函数
我来整理一下这些文件中使用的时间相关函数:
2.1.1 gettimeofday获取当前时间,精确到微秒
// 原型
int gettimeofday(struct timeval *tv, struct timezone *tz);
// 说明:获取当前时间,精确到微秒
struct timeval {
time_t tv_sec; // 秒
suseconds_t tv_usec; // 微秒
};
// 使用示例
struct timeval tv;
gettimeofday(&tv, nullptr);
int64_t seconds = tv.tv_sec;
int64_t microseconds = tv.tv_usec;
2.1.2 gmtime_r将time_t转换为UTC时间的tm结构体
// 原型
struct tm *gmtime_r(const time_t *timep, struct tm *result);
// 说明:将time_t转换为UTC时间的tm结构体,线程安全版本
struct tm {
int tm_sec; // 秒 [0,61]
int tm_min; // 分 [0,59]
int tm_hour; // 时 [0,23]
int tm_mday; // 日 [1,31]
int tm_mon; // 月 [0,11]
int tm_year; // 年 (从1900年开始)
int tm_wday; // 星期 [0,6] (星期日=0)
int tm_yday; // 一年中的第几天 [0,365]
int tm_isdst; // 夏令时标志
};
// 使用示例
time_t seconds = time(nullptr);
struct tm tm_time;
gmtime_r(&seconds, &tm_time);
printf("%d-%02d-%02d %02d:%02d:%02d\n",
tm_time.tm_year + 1900,
tm_time.tm_mon + 1,
tm_time.tm_mday,
tm_time.tm_hour,
tm_time.tm_min,
tm_time.tm_sec);
2.1.3mktime将本地时间tm结构体转换为time_t
// 原型
time_t mktime(struct tm *tm);
// 说明:将本地时间tm结构体转换为time_t
// 使用示例
struct tm local_time = {0};
local_time.tm_year = 2024 - 1900; // 年份从1900开始
local_time.tm_mon = 2 - 1; // 月份从0开始
local_time.tm_mday = 23; // 日期
local_time.tm_hour = 15; // 小时
local_time.tm_min = 30; // 分钟
local_time.tm_sec = 0; // 秒
time_t t = mktime(&local_time);
2.1.4 timegm将UTC时间tm结构体转换为time_t
// 原型
time_t timegm(struct tm *tm);
// 说明:将UTC时间tm结构体转换为time_t(与mktime相对)
// 使用示例
struct tm utc_time = {0};
utc_time.tm_year = 2024 - 1900;
utc_time.tm_mon = 2 - 1;
utc_time.tm_mday = 23;
utc_time.tm_hour = 7; // UTC时间
utc_time.tm_min = 30;
utc_time.tm_sec = 0;
time_t t = timegm(&utc_time);
2.1.5 time获取当前时间戳(秒级)
// 原型 time_t time(time_t *tloc); // 说明:获取当前时间戳(秒级) // 使用示例 time_t now = time(nullptr);
2.2 项目中的实际使用
这些函数在muduo网络库代码中的实际应用:
1.获取当前时间戳(微秒级):
这里返回微秒,后续使用时也可以将其转换为毫秒。
// Timestamp::now()的实现
Timestamp Timestamp::now()
{
struct timeval tv;
gettimeofday(&tv, nullptr);
int64_t seconds = tv.tv_sec;
return Timestamp(seconds * kMicroSecondsPerSecond + tv.tv_usec);
}
在EPollPoller::poll,定时器howMuchTimeFromNow时有使用。
2.时间格式化:
// Timestamp::toFormattedString的实现
std::string Timestamp::toFormattedString(bool showMicroseconds) const
{
char buf[64] = {0};
time_t seconds = static_cast<time_t>(microSecondsSinceEpoch_ / kMicroSecondsPerSecond);
struct tm tm_time;
gmtime_r(&seconds, &tm_time);
snprintf(buf, sizeof(buf), "%4d%02d%02d %02d:%02d:%02d",
tm_time.tm_year + 1900, tm_time.tm_mon + 1, tm_time.tm_mday,
tm_time.tm_hour, tm_time.tm_min, tm_time.tm_sec);
return buf;
}
SystemInspector::overview打印系统信息时有使用 toFormattedString函数。
3.时区转换:
// TimeZone::toLocalTime的实现
struct tm TimeZone::toLocalTime(time_t seconds) const
{
struct tm localTime;
if (!data_)
{
gmtime_r(&seconds, &localTime);
return localTime;
}
const Data::LocalTime* local = data_->findLocalTime(seconds);
time_t localSeconds = seconds + local->gmtOffset;
gmtime_r(&localSeconds, &localTime);
return localTime;
}
日志格式化时 Logger::Impl::formatTime 有使用这些函数。
这些时间函数的使用需要注意:
- 线程安全性:优先使用 _r 后缀的函数(如gmtime_r)
- 时区处理:注意UTC时间和本地时间的转换
- 精度选择:根据需求选择秒级或微秒级函数
- 性能考虑:gettimeofday比time开销大,但精度更高
3. 整体时间类设计
3.1 时间类组件关系
3.2 时间戳处理流程
3.3 日期转换流程
3.4 时区转换流程
3.5 测试用例执行
3.5.1 Timestamp 测试
# 执行时间戳测试 ./bin/test_timestamp # 测试输出示例 === 测试 Timestamp === 当前时间: 2024-03-14 10:30:45.123456 格式化时间: 2024-03-14 10:30:45 时间差: 10.000000 seconds
3.5.2 Date 测试
# 执行日期测试 ./bin/test_date # 测试输出示例 === 测试 Date === 今天: 2024-03-14 星期: Thursday 儒略日: 2460293
3.5.3 TimeZone 测试
# 执行时区测试 ./bin/test_timezone # 测试输出示例 === 测试 TimeZone === UTC: 2024-03-14 02:30:45 北京时间: 2024-03-14 10:30:45
4 章节总结
重点掌握:
使用add_library生成库文件
- add_library(mymuduo_base ${base_SRCS})
使用target_link_libraries链接目标依赖的库文件
- target_link_libraries(mymuduo_base pthread)
使用install安装库文件和头文件
- install(TARGETS mymuduo_base DESTINATION /usr/local/lib)
- install(FILES ${base_HEADERS} DESTINATION /usr/local/include/mymuduo/base)
了解内容:
- 平时用gettimeofday是最多的,可以精确到微秒, 如果你只需要秒级别的函数则可以使用time(time_t *tloc)获取秒。
查看10道真题和解析