c++ 时间类型详解

c++ 时间类型详解(time_t和tm)

原文:http://blog.youkuaiyun.com/love_gaohz/article/details/6637625

Unix时间戳(Unix timestamp),或称Unix时间(Unix time)、POSIX时间(POSIX time),是一种时间表示方式,定义为从格林威治时间1970年01月01日00时00分00秒起至现在的总秒数。Unix时间戳不仅被使用在Unix 系统、类Unix系统中,也在许多其他操作系统中被广告采用。

目前相当一部分操作系统使用32位二进制数字表示时间。此类系统的Unix时间戳最多可以使用到格林威治时间2038年01月19日03时14分07秒(二进制:01111111 11111111 11111111 11111111)。其后一秒,二进制数字会变为10000000 00000000 00000000 00000000,发生溢出错误,造成系统将时间误解为1901年12月13日20时45分52秒。这很可能会引起软件故障,甚至是系统瘫痪。使用64位二进制数字表示时间的系统(最多可以使用到格林威治时间292,277,026,596年12月04日15时30分08秒)则基本不会遇到这类溢出问题。

首先我们了解一下时间的相关概念,以及之间的区别,需要了解的时间概念有:
本地时间(locale time)
格林威治时间(Greenwich Mean Time GMT
时间协调时间 (Universal Time Coordinated UTC


  本地时间,显而易见不用解释了
  先看看时间的标准:
(1)世界时 
世界时是最早的时间标准。在1884年,国际上将1s确定为全年内每日平均长度的1/8.64×104。以此标准形成的时间系统,称为世界是,即UT1。1972年国际上开始使用国际原子时标,从那以后,经过格林威治老天文台本初子午线的时间便被称为世界时,即UT2,或称格林威治时间(GMT),是对地球转速周期性差异进行校正后的世界时。 
(2)原子时 
1967年,人们利用铯原子振荡周期极为规律的特性,研制出了高精度的原子时钟,将铯原子能级跃迁辐射9192631770周所经历的时间定为1s。现在用的时间就是1971年10月定义的国际原子时,是通过世界上大约200多台原子钟进行对比后,再由国际度量衡局时间所进行数据处理,得出的统一的原子时,简称TAI。 
(3)世界协调时 
世界协调时是以地球自转为基础的时间标准。由于地球自转速度并不均匀,并非每天都是精确的86400原子s,因而导致了自转时间与世界时之间存在18个月有1s的误差。为纠正这种误差,国际地球自转研究所根据地球自转的实际情况对格林威治时间进行增减闰s的调整,与国际度量衡局时间所联合向全世界发布标准时间,这就是所谓的世界协调时(UTC:CoordinatdeUniversalTime)。UTC的表示方式为:年(y)、月(m)、日(d)、时(h)、分(min)、秒(s),均用数字表示。

  
GPS 系统中有两种时间区分,一为UTC,另一为LT(地方时)两者的区别为时区不同,UTC就是0时区的时间,地方时为本地时间,如北京为早上八点(东八区),UTC时间就为零点,时间比北京时晚八小时,以此计算即可
  通过上面的了解,我们可以认为格林威治时间就是世界协调时间(GMT=UTC),格林威治时间和UTC时间均用秒数来计算的。

   而在我们平时工作当中看到的计算机日志里面写的时间大多数是用UTC时间来计算的,那么我们该怎么将UTC时间转化为本地时间便于查看日志,那么在作程序开发时又该怎么将本地时间转化为UTC时间呢?
   下面就介绍一个简单而使用的工具,就是使用linux/unix命令date来进行本地时间和local时间的转化。
   大家都知道,在计算机中看到的utc时间都是从(1970年01月01日 0:00:00)开始计算秒数的。所看到的UTC时间那就是从1970年这个时间点起到具体时间共有多少秒。

我们在编程中可能会经常用到时间,比如取得系统的时间(获取系统的年、月、日、时、分、秒,星期等),或者是隔一段时间去做某事,那么我们就用到一些时间函数。


linux下存储时间常见的有两种存储方式,一个是从1970年到现在经过了多少秒,一个是用一个结构来分别存储年月日时分秒的。
time_t 这种类型就是用来存储从1970年到现在经过了多少秒,要想更精确一点,可以用结构struct timeval,它精确到微妙。
struct timeval
{
    long tv_sec; /*秒*/
    long tv_usec; /*微秒*/

};


而直接存储年月日的是一个结构:
struct tm
{
    int tm_sec;  /*秒,正常范围0-59, 但允许至61*/
    int tm_min;  /*分钟,0-59*/
    int tm_hour; /*小时, 0-23*/
    int tm_mday; /*日,即一个月中的第几天,1-31*/
    int tm_mon;  /*月, 从一月算起,0-11*/  1+p->tm_mon;
    int tm_year;  /*年, 从1900至今已经多少年*/  1900+ p->tm_year;
    int tm_wday; /*星期,一周中的第几天, 从星期日算起,0-6*/
    int tm_yday; /*从今年1月1日到目前的天数,范围0-365*/
    int tm_isdst; /*日光节约时间的旗标*/
};

需要特别注意的是,年份是从1900年起至今多少年,而不是直接存储如2011年,月份从0开始的,0表示一月,星期也是从0开始的, 0表示星期日,1表示星期一。


下面介绍一下我们常用的时间函数:
#include <time.h>
char *asctime(const struct tm* timeptr);

将结构中的信息转换为真实世界的时间,以字符串的形式显示


char *ctime(const time_t* timep);

将timep转换为真是世界的时间,以字符串显示,它和asctime不同就在于传入的参数形式不一样


double difftime(time_t time1, time_t time2);

返回两个时间相差的秒数


int gettimeofday(struct timeval* tv,struct timezone* tz);

返回当前距离1970年的秒数和微妙数,后面的tz是时区,一般不用


struct tm* gmtime(const time_t* timep);

将time_t表示的时间转换为没有经过时区转换的UTC时间,是一个struct tm结构指针


struct tm* localtime(const time_t* timep);

和gmtime类似,但是它是经过时区转换的时间。


time_t mktime(struct tm* timeptr);

将struct tm 结构的时间转换为从1970年至今的秒数


time_t time(time_t* t);

取得从1970年1月1日至今的秒数。


上面是简单的介绍,下面通过实战来看看这些函数的用法:
下载: gettime1.c

     
  1. /*gettime1.c*/
  2. #include <time.h>
  3. int main()
  4. {
  5. time_t timep;
  6. time(&timep); /*获取time_t类型的当前时间*/
  7. /*用gmtime将time_t类型的时间转换为struct tm类型的时间,按没有经过时区转换的UTC时间
  8. 然后再用asctime转换为我们常见的格式 Fri Jan 11 17:25:24 2008
  9. */
  10. printf( "%s", asctime(gmtime(&timep)));
  11. return 0;
  12. }
编译并运行:
$gcc -o gettime1 gettime1.c
$./gettime1
Fri Jan 11 17:04:08 2008


下面是直接把time_t类型的转换为我们常见的格式:

下载: gettime2.c

     
  1. /* gettime2.c*/
  2. #include <time.h>
  3. int main()
  4. {
  5. time_t timep;
  6. time(&timep); /*获取time_t类型当前时间*/
  7. /*转换为常见的字符串:Fri Jan 11 17:04:08 2008*/
  8. printf( "%s", ctime(&timep));
  9. return 0;
  10. }
编译并运行:
$gcc -o gettime2 gettime2.c
$./gettime2
Sat Jan 12 01:25:29 2008
我看了一本书上面说的这两个例子如果先后执行的话,两个的结果除了秒上有差别之外(执行程序需要时间),应该是一样的,可是我这里执行却发现差了很长时间按,一个是周五,一个是周六,后来我用 date 命令执行了一遍
$date
六 1月 12 01:25:19 CST 2008

我发现date和gettime2比较一致, 我估计可能gettime1并没有经过时区的转换,它们是有差别的。

(我(非原作者)执行gettime1和gettime2的结果分别是Sun Mar 02 06:53:58 2014    Sun Mar 02 14:53:58 2014,第二个和我本地计算机的时间一样,应该gettime1是没有经过时区的转换,gettime2是获得本地的时间).


下载: gettime3.c


     
  1. /*gettime3.c */
  2. #include <time.h>
  3. int main()
  4. {
  5. char *wday[] = { "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"};
  6. time_t timep;
  7. struct tm *p;
  8. time(&timep); /*获得time_t结构的时间,UTC时间*/
  9. p = gmtime(&timep); /*转换为struct tm结构的UTC时间*/
  10. printf( "%d/%d/%d ", 1900 + p->tm_year, 1 + p->tm_mon, p->tm_mday);
  11. printf( "%s %d:%d:%d\n", wday[p->tm_wday], p->tm_hour,
  12. p->tm_min, p->tm_sec);
  13. return 0;
  14. }
编译并运行:
$gcc -o gettime3 gettime3.c
$./gettime3
2008/1/11 Fri 17:42:54
从这个时间结果上来看,它和gettime1保持一致。


下载: gettime4.c


     
  1. /*gettime4.c*/
  2. #include <time.h>
  3. int main()
  4. {
  5. char *wday[] = { "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"};
  6. time_t timep;
  7. struct tm *p;
  8. time(&timep); /*获得time_t结构的时间,UTC时间*/
  9. p = localtime(&timep); /*转换为struct tm结构的当地时间*/
  10. printf( "%d/%d/%d ", 1900 + p->tm_year, 1 + p->tm_mon, p->tm_mday);
  11. printf( "%s %d:%d:%d\n", wday[p->tm_wday], p->tm_hour, p->tm_min, p->tm_sec);
  12. return 0;
  13. }
编译并运行:
$gcc -o gettime4 gettime4.c
$./gettime4
2008/1/12 Sat 1:49:29
从上面的结果我们可以这样说:

time, gmtime, asctime 所表示的时间都是UTC时间,只是数据类型不一样,

而localtime, ctime 所表示的时间都是经过时区转换后的时间,它和你用系统命令date所表示的CST时间应该保持一致


下载: gettime5.c


     
  1. /*gettime5.c*/
  2. #include <time.h>
  3. int main()
  4. {
  5. time_t timep;
  6. struct tm *p;
  7. time(&timep); /*当前time_t类型UTC时间*/
  8. printf( "time():%d\n",timep);
  9. p = localtime(&timep); /*转换为本地的tm结构的时间按*/
  10. timep = mktime(p); /*重新转换为time_t类型的UTC时间,这里有一个时区的转换*/ 
  11. //by lizp 错误,没有时区转换, 将struct tm 结构的时间转换为从1970年至p的秒数
  12. printf( "time()->localtime()->mktime(): %d\n", timep);
  13. return 0;
  14. }
编译并运行:
$gcc -o gettime5 gettime5.c
$./gettime5
time():1200074913
time()->localtime()->mktime(): 1200074913
这里面把UTC时间按转换为本地时间,然后再把本地时间转换为UTC时间,它们转换的结果保持一致。


下载: gettime6.c


     
  1. /*gettime6.c */
  2. #include <time.h>
  3. int main()
  4. {
  5. time_t timep;
  6. struct tm *p;
  7. time(&timep); /*得到time_t类型的UTC时间*/
  8. printf( "time():%d\n",timep);
  9. p = gmtime(&timep); /*得到tm结构的UTC时间*/
  10. timep = mktime(p); /*转换,这里会有时区的转换*/ //by lizp 错误,没有时区转换, 将struct tm 结构的时间转换为从1970年至p的秒数
  11. printf( "time()->gmtime()->mktime(): %d\n", timep);
  12. return 0;
  13. }
编译并运行:
$gcc -o gettime6 gettime6.c
$./gettime6
time():1200075192
time()->gmtime()->mktime(): 1200046392
从这里面我们可以看出,转换后时间不一致了,计算一下,整整差了8个小时( (1200075192-1200046392)/3600 = 8),说明 mktime会把本地时间转换为UTC时间,这里面本来就是UTC时间,于是再弄个时区转换,结果差了8个小时,用的时候应该注意。

strftime() 函数将时间格式化
我们可以使用strftime()函数将时间格式化为我们想要的格式。它的原型如下:
size_t strftime(
     char *strDest,
     size_t maxsize,
     const char *format,
     const struct tm *timeptr
);
我们可以根据format指向字符串中格式命令把timeptr中保存的时间信息放在strDest指向的字符串中,最多向strDest中存放maxsize个字符。该函数返回向strDest指向的字符串中放置的字符数。
函数strftime()的操作有些类似于sprintf():识别以百分号(%)开始的格式命令集合,格式化输出结果放在一个字符串中。格式化命令说明串 strDest中各种日期和时间信息的确切表示方法。格式串中的其他字符原样放进串中。格式命令列在下面,它们是区分大小写的。
%a 星期几的简写
%A 星期几的全称
%b 月分的简写
%B 月份的全称
%c 标准的日期的时间串
%C 年份的后两位数字
%d 十进制表示的每月的第几天
%D 月/天/年
%e 在两字符域中,十进制表示的每月的第几天
%F 年-月-日
%g 年份的后两位数字,使用基于周的年
%G 年分,使用基于周的年
%h 简写的月份名
%H 24小时制的小时
%I 12小时制的小时
%j 十进制表示的每年的第几天
%m 十进制表示的月份
%M 十时制表示的分钟数
%n 新行符
%p 本地的AM或PM的等价显示
%r 12小时的时间
%R 显示小时和分钟:hh:mm
%S 十进制的秒数
%t 水平制表符
%T 显示时分秒:hh:mm:ss
%u 每周的第几天,星期一为第一天 (值从0到6,星期一为0)
%U 第年的第几周,把星期日做为第一天(值从0到53)
%V 每年的第几周,使用基于周的年
%w 十进制表示的星期几(值从0到6,星期天为0)
%W 每年的第几周,把星期一做为第一天(值从0到53)
%x 标准的日期串
%X 标准的时间串
%y 不带世纪的十进制年份(值从0到99)
%Y 带世纪部分的十制年份
%z,%Z 时区名称,如果不能得到时区名称则返回空字符。
%% 百分号
如果想显示现在是几点了,并以12小时制显示,就象下面这段程序:

     
  1. #include "time.h"
  2. #include "stdio.h"
  3. int main(void)
  4. {
  5. struct tm *ptr;
  6. time_t lt;
  7. char str[ 80];
  8. lt=time( NULL);
  9. ptr=localtime(&lt);
  10. strftime(str, 100, "It is now %I %p",ptr);
  11. printf(str);
  12. return 0;
  13. }
其运行结果为:

It is now 4PM


而下面的程序则显示当前的完整日期:


     
  1. #include<stdio.h>
  2. #include<string.h>
  3. #include<time.h>
  4. int main( void )
  5. {
  6. struct tm *newtime;
  7. char tmpbuf[ 128];
  8. time_t lt1;
  9. time( &lt1 );
  10. newtime=localtime(&lt1);
  11. strftime( tmpbuf, 128, "Today is %A, the %d day of %B in the year %Y.\n", newtime);
  12. printf(tmpbuf);
  13. return 0;
  14. }

其运行结果为:

Today is Sunday, the 02 day of March in the year 2014.




欢迎加入"C/C++梦之队" 学习群:226157456

本文转载自:https://blog.youkuaiyun.com/luoweifu/article/details/20288549




关注【SunLogging】 
这里写图片描述 
长按或扫码二维码,在手机端阅读更多内容

<think>嗯,用户想要了解C++中的变量型,包括详细说明和使用方法。首先,我需要回顾一下C++的基本变量型,以及它们的特点和应用场景。根据提供的引用内容,特别是引用[1]和[2],用户可能希望了解如何选择合适的型以及最佳实践。 首先,基础数据型包括整型、浮点型、字符型、布尔型等。例如,int、short、long这些整型的不同之处在于它们的存储大小和范围。可能需要解释每个型的具体占用字节数,这在不同系统上可能有所不同,但C++标准有最小长度的规定。比如,int通常至少是16位,但实际上在现代系统中通常是32位。 接下来是浮点型,如float和double的区别,它们的精度和适用场景。比如科学计算常用double,而float用于节省内存的情况。还有字符型char,用于存储单个字符,以及布尔型bool,只有true和false两个值。 然后是型修饰符,比如signed、unsigned、short、long。这些修饰符如何改变数据型的范围和存储方式。例如,unsigned int的范围是0到4,294,967,295,而signed int则是-2,147,483,648到2,147,483,647(假设是32位)。需要说明这些修饰符如何影响变量的使用,比如使用unsigned型时要注意避免溢出和下溢。 复合数据型也是重点,比如数组、结构体、枚举、联合体等。数组用于存储相同型的元素集合,结构体允许不同数据型的组合。枚举型可以提高代码可读性,比如用枚举表示状态。联合体则用于共享内存空间的情况,但需要谨慎使用,因为同一时间只能有一个成员有效。 用户可能还关心型转换,包括隐式转换和显式转换。隐式转换在运算时自动发生,比如将int赋值给double。显式转换则需要使用static_cast等操作符,这更安全,避免潜在的错误。需要强调避免不必要的型转换,尤其是在精度丢失的情况下。 在C++11及以后的新特性中,auto关键字和decltype型推导可以简化代码编写,特别是在处理复杂型时。例如,auto可以根据初始化表达式自动推断变量型,这在模板编程中非常有用。另外,nullptr替代了NULL,提供了更明确的空指针表示。 引用型和指针的区别也是一个重要点。引用是变量的别名,必须在声明时初始化,而指针可以重新指向其他地址。需要解释它们的用法和适用场景,比如函数参数传递时使用引用可以避免拷贝开销。 作用域和生命周期部分,局部变量和全局变量的区别,静态变量的生命周期等。静态局部变量在函数调用之间保持其值,而普通局部变量在函数退出时销毁。 关于最佳实践,引用中提到要选择合适的型、避免不必要的转换、合理使用const修饰符。例如,const可以保护变量不被意外修改,提高代码的健壮性。另外,推荐使用新特性如auto和nullptr,以提升代码的现代性和安全性。 用户提供的引用[3]讨论了条件变量的使用,但当前问题主要关注变量型,所以这部分可能暂时不需要涉及。但如果有后续问题涉及多线程中的变量使用,可能需要结合条件变量和互斥锁来讲解变量的线程安全。 在回答结构上,应该先分讲解各种变量型,再讨论型转换、新特性、作用域和最佳实践。每个部分给出代码示例,帮助理解。例如,展示如何声明不同整型变量,使用结构体和枚举,以及auto和decltype的示例。 可能还需要注意用户的实际需求,比如他们可能在实际编程中遇到型选择的问题,或者型转换导致的错误。因此,强调如何根据需求选择合适型,以及如何避免常见错误是很重要的。 最后,相关问题部分需要生成与C++变量型相关的问题,比如型转换的注意事项、新特性的优势、不同整型的选择依据等,帮助用户进一步深入学习。</think># C++ 变量详解及使用教程 ## 一、基础数据型 ### 1. 整型 | 型 | 占用字节 (x64) | 范围 (含符号) | 示例代码 | |-------------|----------------|--------------------------------|--------------------------| | `short` | 2 | $-32768$ 到 $32767$ | `short s = 100;` | | `int` | 4 | $-2^{31}$ 到 $2^{31}-1$ | `int i = -5;` | | `long` | 4/8 | $-2^{31}$ 或 $-2^{63}$ | `long l = 100000L;` | | `long long` | 8 | $-2^{63}$ 到 $2^{63}-1$ | `long long ll = 1e18;` | ### 2. 浮点型 ```cpp float f = 3.14f; // 单精度(6-7位有效数字) double d = 3.1415926; // 双精度(15位有效数字) long double ld = 3.141592653589793238L; // 扩展精度 ``` ### 3. 字符与布尔 ```cpp char c = 'A'; // ASCII字符存储 wchar_t wc = L'中'; // 宽字符(Unicode) bool flag = true; // 逻辑值(true/false) ``` ## 二、型修饰符 ### 符号控制 ```cpp unsigned int ui = 4294967295; // 0到$2^{32}-1$ signed char sc = -128; // $-128$到$127$ ``` ### 长度扩展 ```cpp long double ld = 3.1415926535L; long int li = 2147483647L; ``` ## 三、复合数据型 ### 1. 数组与字符串 ```cpp int arr[5] = {1,2,3,4,5}; // 定长数组 char str[] = "Hello"; // C风格字符串 std::string s = "C++ String"; // C++字符串对象 ``` ### 2. 结构体与联合体 ```cpp struct Point { // 结构体 int x; double y; }; Point p = {5, 3.14}; union Data { // 联合体(共享内存) int i; float f; }; Data d; ``` ### 3. 枚举型 ```cpp enum Color { RED=1, GREEN=2, BLUE=4 }; // 传统枚举 Color c = GREEN; enum class LogLevel : uint8_t { // C++11强型枚举 DEBUG, WARNING, ERROR }; ``` ## 四、型转换 ### 1. 隐式转换 ```cpp double d = 5; // int → double int i = 3.14; // double → int (i=3) ``` ### 2. 显式转换 ```cpp // C风格转换(不推荐) float f = (float)3.1415; // C++转换操作符 int* p = static_cast<int*>(malloc(100)); const_cast<int&>(ci) = 5; // 移除const属性 ``` ## 五、C++11+新特性 ### 型推导 ```cpp auto x = 5; // int auto str = "hello"; // const char* decltype(x) y = x * 2; // 推导为int std::vector<int> vec; auto it = vec.begin(); // 迭代器型自动推导 ``` ### 空指针与型别名 ```cpp int* ptr = nullptr; // 明确空指针(C++11) using StringVector = std::vector<std::string>; // 型别名 StringVector sv; ``` ## 六、作用域与生命周期 ```cpp int global_var; // 全局变量(程序生命周期) void func() { static int count = 0;// 静态局部变量 int local = 5; // 自动存储期 count++; } ``` ## 七、最佳实践 1. **型选择原则** - 整型优先选择`int`,内存敏感时用`short` - 浮点运算首选`double` - 字符处理优先使用`std::string` 2. **型安全** ```cpp const int MAX = 100; // 常量声明 size_t len = str.size(); // 无符号长度型 static_cast<float>(int_var); // 显式转换 ``` 3. **现代C++特性应用** ```cpp auto&& item = getItem(); // 万能引用 constexpr int SIZE = 100; // 编译期常量 ``` ## 八、常见问题解答 **Q1: `unsigned int`和`int`如何选择?** A1: 当确定数值不会为负时使用`unsigned`,如循环索引、位操作。注意避免混合使用导致的隐式转换问题[^2]。 **Q2: C++11的`auto`会降低性能吗?** A2: 不会,`auto`只是编译期型推导工具,生成的机器码与显式声明型完全一致[^1]。 **Q3: 何时使用`long double`?** A3: 需要高精度科学计算时(如天体物理计算),但需注意不同平台精度实现差异。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值