在做ACM题时, 经常都会遇到一些比较大的整数。而常用的内置整数类型常常显得太小了:其中long和int范围是[-2^31,2^31),即-2147483648~2147483647。而unsigned范围是[0,2^32),即0~4294967295。也就 是说,常规的32位整数只能够处理40亿以下 的数。 那遇 到比40亿要大的数怎么办呢?这时就要用到C++的64位扩展 了。不同的编译器对64位整数的扩展有所不同。基于ACM的需 要,下面仅介绍VC6.0与g++编译器的 扩展。 VC的64位整数 分别叫做__int64与unsigned __int64,其范 围分别是[-2^63, 2^63)与[0,2^64),即-9223372036854775808~9223372036854775807与0~18446744073709551615(约1800亿亿)。对64位整数 的运算与32位整数基本相同,都支持四则运算与位运算等。当进行64位与32位的混 合运算时,32位整数会被隐式转换成64位整 数。但是,VC的输入输出与__int64的兼容 就不是很好了,如果你写下这样一段代码: 1__int64 a; 2cin>>a; 3cout<<a; 那么,在 第2行会收到“error C2679: binary '>>' : no operator defined which takes a right-hand operand of type '__int64' (or there is no acceptable conversion)”的错 误;在第3行会收到“error C2593: 'operator <<' is ambiguous”的错误。那是不是就不能进行输入输出呢?当然不是,你可以使用C的写 法: scanf("%I64d",&a); printf("%I64d",a); 就可以正确输入输出了。当使用unsigned __int64时,把"I64d"改为"I64u"就可以 了。 OJ通常使 用g++编译器。其64位扩展方 式与VC有所不同,它们分别叫做long long与unsigned long long。处理规 模与除输入输出外的使用方法同上。对于输入输出,它的扩展比VC好。既可以使用 1long long a; 2cin>>a; 3cout<<a; 也可以使用 scanf("%lld",&a); printf("%lld",a); 使用无符号数时,将"%lld"改成"%llu"即可。 最后我补充一点:作为一个特例,如果你使用的 是Dev-C++的g++编译器,它使用的是"%I64d"而非"%lld"。
Allocator是C++语言标准库中最神秘的部分之一。它们很少被显式使用,标准也没有明确出它们应该在什么时候被使用。今天的allocator与最初的STL建议非常不同,在此过程中还存在着另外两个设计--这两个都依赖于语言的一些特性,而直到最近才在很少的几个编译器上可用。对allocator的功能,标准似乎在一些方面追加了承诺,而在另外一些方面撤销了承诺。
这篇专栏文章将讨论你能用allocator来做什么以及如何定义一个自己的版本。我只会讨论C++标准所定义的allocator:引入准标准时代的设计,或试图绕过有缺陷的编译器,只会增加混乱。 什么时候不使用Allocator
C++标准中的Allocator分成两块:一个通用需求集(描述于§ 20.1.5(表 32)),和叫std::allocator的class(描述于§20.4.1)。如果一个class满足表32的需求,我们就称它为一个allocator。std::allocator类满足那些需求,因此它是一个allocator。它是标准程序库中的唯一一个预先定义allocator类。
每个 C++程序员都已经知道动态内存分配:写下new X来分配内存和创建一个X类型的新对象,写下delete p来销毁p所指的对象并归还其内存。你有理由认为allocator会使用new和delete--但它们没有。(C++标准将::operator new()描述为“allocation function”,但很奇怪,allocator并不是这样的。)
有关allocator的最重要的事实是它们只是为了一个目的:封装STL容器在内存管理上的低层细节。你不应该在自己的代码中直接调用allocator的成员函数,除非正在写一个自己的STL容器。你不应该试图使用allocator来实现operator new[];这不是allocator该做的。 如果你不确定是否需要使用allocator,那就不要用。
allocator是一个类,有着叫allocate()和deallocate()成员函数(相当于malloc和free)。它还有用于维护所分配的内存的辅助函数和指示如何使用这些内存的typedef(指针或引用类型的名字)。如果一个STL容器使用用户提供的allocator来分配它所需的所有内存(预定义的STL容器全都能这么做;他们都有一个模板参数,其默认值是std::allocator),你就能通过提供自己的allocator来控制它的内存管理。
这种柔性是有限制的:仍然由容器自己决定它将要申请多少内存以及如何使用它们。在容器申请更多的内存时,你能控制它调用那个低层函数,但是你不能通过使用allocator来让一个vector行动起来像一个deque一样。虽然如此,有时候,这个受限的柔性也很有用。比如,假设你有一个特殊的fast_allocator,能快速分配和释放内存(也许通过放弃线程安全性,或使用一个小的局部堆),你能通过写下std::list<T, fast_allocator<T> >而不是简单的std::list<T>来让标准的list使用它。
如果这看起来对你很陌生,那就对了。没有理由在常规代码中使用allocator的。 定义一个Allocator
关于allocator的这一点你已经看到了:它们是模板。Allocator,和容器一样,有value_type,而且allocator的value_type必须要匹配于使用它的容器的value_type。这有时会比较丑陋:map的value_type相当复杂,所以显式调用allocator的map看起来象这样的,std::map<K,V, fast_allocator<std::pair<const K, V> > >。(像往常一样,typedef会对此有帮助。)
以一个简单的例子开始。根据C++标准,std::allocator构建在::operator new()上。如果你正在使用一个自动化内存使用跟踪工具,让std::allocator更简单些会更方便。我们可以用malloc()代替::operator new(),而且我们也不考虑(在好的std::allocator实作中可以找到的)复杂的性能优化措施。我们将这个简单的allocator叫作malloc_allocator 。
既然malloc_allocator的内存管理很简单,我们就能将重点集中在所有STL的allocator所共有的样板上。首先,一些类型:allocator是一个类模板,它的实例专为某个类型T分配内存。我们提供了一序列的typedef,以描述该如何使用此类型的对象:value_type指T本身,其它的则是有各种修饰字的指针和引用。 template <class T> class malloc_allocator
{
public:
typedef T value_type;
typedef value_type* pointer;
typedef const value_type* const_pointer;
typedef value_type& reference;
typedef const value_type& const_reference;
typedef std::size_t size_type;
typedef std::ptrdiff_t difference_type;
...
};
这些类型与STL容器中的很相似,这不是巧合:容器类常常直接从它的allocator提取这些类型。 为 什么有这么多的typedef?你可能认为pointer是多余的:它就是value_type *。绝大部份时候这是对的,但你可能有时候想定义非传统的allocator,它的pointer是一个pointer-like的class,或非标的 厂商特定类型value_type __far *;allocator是为非标扩展提供的标准hook。不寻常的pointer类型也是存在address()成员函数的理由,它在 malloc_allocator中只是operator &()的另外一种写法: 现在我们能开始真正的工 作:allocate()和deallocate()。它们很简单,但并不十分象malloc()和free()。我们传给allocate()两个参 数:我们正在为其分派空间的对象的数目(max_size返回可能成功的最大请求值),以及可选的一个地址值(可以被用作一个位置提示)。象 malloc_allocator这样的简单的allocator没有利用那个提示,但为高性能而设计的allocator可能会利用它。返回值是一个指 向内存块的指针,它足以容纳n个value_type类型的对象并有正确的对齐。 我 们也传给deallocate()两个参数:当然一个是指针,但同样还有一个元素计数值。容器必须自己掌握大小信息;传给allocate和 deallocate的大小参数必须匹配。同样,这个额外的参数是为效率而存在的,而同样,malloc_allocator不使用它。 template <class T> class malloc_allocator
{
public:
pointer allocate(size_type n, const_pointer = 0) {
void* p = std::malloc(n * sizeof(T));
if (!p)
throw std::bad_alloc();
return static_cast<pointer>(p);
}
void deallocate(pointer p, size_type) {
std::free(p);
}
size_type max_size() const {
return static_cast<size_type>(-1) / sizeof(value_type);
}
...
};
allocate()和 deallocate()成员函数处理的是未初始化的内存,它们不构造和销毁对象。语句a.allocate(1)更象 malloc(sizeof(int))而不是new int。在使用从allocate()获得的内存前,你必须在这块内存上创建对象;在通过deallocate()归还内存前,你需要销毁那些对象。 C++ 语言提供一个机制以在特定的内存位置创建对象:placement new。如果你写下new(p) T(a, b),那么你正在调用T的构造函数产生一个新的对象,一如你写的new T(a, b)或 T t(a, b)。区别是当你写new(p) T(a, b),你指定了对象被创建的位置:p所指向的地址。(自然,p必须指向一块足够大的内存,而且必须是未被使用的内存;你不能在相同的地址构建两个不同的对 象。)。你也可以调用对象的析构函数,而不释放内存,只要写p->~T()。 这 些特性很少被使用,因为通常内存的分配和初始化是一起进行的:使用未初始化的内存是不方便的和危险的。你需要如此低层的技巧的很少几处之一就是你在写一个 容器类,于是allocator将内存的分配与初始化解耦。成员函数construct()调用placement new,而且成员函数destory()调用析构函数。 template <class T> class malloc_allocator
{
public:
void construct(pointer p, const value_type& x) {
new(p) value_type(x);
}
void destroy(pointer p) { p->~value_type(); }
...
};
(为什么allocator有那些成员 函数,什么时候容器可以直接使用placement new?一个理由是要隐藏笨拙的语法,而另一个是如果写一个更复杂的allocator时你可能想在构造和销毁对象时construct()和 destroy()还有其它一些副作用。比如,allocator可能维护一个所有当前活动对象的日志。) 这 些成员函数没有一个是static的,因此,容器在使用allocator前做的第一件事就是创建一个allocator对象--也就是说我们应该定义一 些构造函数。但是,我们不需要赋值运算:一旦容器创建了它的allocator,这个allocator就从没想过会被改变。表32中的对 allocator的需求没有包括赋值。只是基于安全,为了确保没人偶然使用了赋值运算,我们将禁止掉这个可能自动生成的函数。 template <class T> class malloc_allocator
{
public:
malloc_allocator() {}
malloc_allocator(const malloc_allocator&) {}
~malloc_allocator() {}
private:
void operator=(const malloc_allocator&);
...
};
这些构造函数实际上没有做任何事,因为这个allocator不需要初始化任何成员变量。基于同样的理由,任意两个malloc_allocator都是 可互换的;如果a1和a2的类型都是malloc_allocator<int>,我们可以自由地通过a1来allocate()内存然后通 过a2来deallocate()它。我们因此定义一个比较操作以表明所有的malloc_allocator对象是等价的: template <class T>
inline bool operator==(const malloc_allocator<T>&,
const malloc_allocator<T>&) {
return true;
}
template <class T>
inline bool operator!=(const malloc_allocator<T>&,
const malloc_allocator<T>&) {
return false;
}
你会期望一个allocator,它的不 同对象是不可替换的吗?当然--但很难提供一个简单而有用的例子。一种明显的可能性是内存池。它对大型的C程序很常见,从几个不同的位置(“池”)分配内 存,而不是什么东西都直接使用malloc()。这样做有几个好处,其一是it only takes a single function call to reclaim all of the memory associated with a particular phase of the program。 使用内存池的程序可能定义诸如mempool_Alloc和mempool_Free这样的工具函数,mempol_Alloc(n, p)从池p中分配n个字节。很容易写出一个mmepool_alocator以匹配这样的架构:每个mempool_allocator对象有一个成员变 量以指明它绑定在哪个池上,而mempool_allocator::allocate()将调用mempool_Alloc()从相应的池中获取内存。 [注1] 最 后,我们到了allocator的定义体中一个微妙的部份:在不同的类型之间映射。问题是,一个allocator类,比如 malloc_allocator<int>,全部是围绕着单个value_type构建 的:malloc_allocator<int>::pointer是int*,malloc_allocator<int> ().allocate(1)返回足够容纳一个int对象的内存,等等。然而,通常,容器类使用malloc_allocator可能必须处理超过一个类 型。比如,一个list类,不分配int对象;实际上,它分配list node对象。(我们将在下一段落研究细节。)于是,当你创建一个std::list<int, malloc_allocator<int> >时,list必须将malloc_allocator<int>转变成为处理list_node类型的 malloc_allocator。 这 个机制称为重绑定,它有二个部份。首先,对于给定的一个value_type是X1的allocator类型A1,你必须能够写出一个allocator 类型A2,它与A1完全相同,除了value_type是X2。其次,对于给定的A1类型的对象a1,你必须能够创建一个等价的A2类型对象a2。这两部 分都使用了成员模板,这也就是allocator不能被老的编译器支持,或支持得很差的原因。
一般类的定义和实现组织方式:将定义放在.h文件中,实现放在.cpp文件中。 C++中的模板是C++在发展过程中新添的新生力量,template的声明和实现不同于一般的类定义与实现和函数的声明与定义,故在含有模板的C++程序的组织方式就不同于一般的定义与实现相分离的方式。 我曾经遇到的问题: 当在GUN中编译时老是无法通过,错误信息大概是无法找到函数。然后将程序搬到VC6下编译,同样出现连接错误。 问题原因: C++标准中说明:在使用模板时C++支持两种程序组织方式:包含模式与分离模式。包含模式也就是将类的定义与实现同放在.h 文件中,分离模式也就是将定义与实现分离,也就是我采用的方式。但很多编译器不支持分离模式,只支持包含模式。 通常采用的解决方法: 1. 干脆直接使用包含模式,即将模板类的定义与实现同写在.h文件中。(我不太喜欢这种写法) 2. 使用分离模式,但是在使用时不引用模板类的头文件,而是引用模板类的实现文件。(不太符合一般习惯) 3. 使用分离模式,在模板类头文件中引用实现文件。(这个方法我在VC6和Dev C++下都没有成功,不知道是哪里出了问题) 4. 使用分离模式,在模板类中头文件中实例化一个你需要对象。(十分笨拙的方法,不利于使用) 5. 使用分离模式,但是另外定义.h文件,在这个文件中引用模板的头文件和实现文件,在使用时引用这个另外定义的.h文件。(觉得这个方法还不错,不过也不知道有什么缺点)
C++引用优于指针
(转载请注明来源于金庆的专栏)
在KOK3服务器的崩溃错误中, 十有八九是由空指针引起的.
在C语言中, 空指针确实是错误的一大来源, 到处是空指针判断, 可还是会有漏网的.
在C++中, 空指针错误可以大大减少, 方法就是尽量使用C++的引用代替指针.
void foo(A* pA) { BOOST_ASSERT(pA);
// act on pA... } 应该改为
void foo(A& rA) { // act on rA... }
如果输入参数为const, 那更是无疑的应该使用引用作为参数.
除了参数可以转成引用, 临时变量也尽量使用引用. 例如:
A* pA = getA(); if (pA) { // act on pA... }
可以改为
A* pA = getA(); if (pA) { A& rA = *pA; // act on rA... }
如果getA()不会返回NULL, 就将getA()改为返回引用, 而不是指针.
C++的指针知识可为及其重要的基础,如果不懂得指针的用法,那看C++的源码程序就太难了。C++的指针知识是个难点,必须克服的难点。 指针声明: 1.每个数据类型都有指针类型 int* i char* c 2.指针本来也是一种类型,指针可以对应着指针类型即二级指针变量 int** i 即 (int*)* i【int指针类型的指针i】 3.一个*只能修饰一个指针 int* i,p 【i为指针,p为int变量】 要想一下声明多个指针,则可以:int* i,*p 指针赋值: 1.指针也可以初始化,赋值或初始化的值是同类型实体(即实体变量)的地址 int i=10; int* ip=&i (&表示实体的地址) int* ipt=&10 是不可以的,10是实体,而不认可具有空间地址(所以说是实体变量地址才有效) 2.指针直接赋实体值 int i=10; int* ip=&i; int* ipt=10 不可以,指针初始化必须是实体地址 *ip=20 可以,指针间访操作 这里的*ip为什么能直接给实体值呢? 因为*ip是所指向实体地址的间访操作,也就是指针指向实体的时候是可以读写操作的。其实这里还是赋值的地址。 3.空指针不能赋实体值 int* ip; *ip=10 不可以 这里的*ip为什么又不能给实体值了呢? 因为这里的指针*ip没有经过初始化,*ip没有指向实体,为空指针,则就不能读写了。 指针间的操作 当指针指向具体实体后,指针也就有了具体的实体值和具体的空间地址,这时候就可以被二级指针所操作 int i=10; int* ip=&10; int** iip=&ip; 理解了上面的指针赋值,那么其余的指针间操作就好操作了,必须理解清楚指针在各种情况下的表示。 给指针赋值,必须是个地址,各种形式的赋值只是表现出的形式不一样,实际指针只能指向实体的地址。指针在赋值时必须是与指针类型相同的类型实体地址。但实 际的存储空间的实体都是以二进制数进行存储的,那么当一个float的实体地址被一个int指针指向时,这个float实体就会被强制转换为int实体, 那么数据就出现变动。这是危险的。我理解的是,指针不能在类型不同时进行转换。 今天就到此了,明天还要继续。天,有点想发狂的冲动,还有指针运算、限定、引用,函数指针。头都要大了...
在C/C++中,对字符串的操作有很多值得注意的问题,同样,C/C++对时间的操作也有许多值得大家注意的地方。下面,在这篇文章中,笔者将主要介绍在C/C++中时间和日期的使用方法. 1.概念 通过学习许多C/C++库,你可以有很多操作、使用时间的方法。但在这之前你需要了解一些“时间”和“日期”的概念,主要有以下几个:
Coordinated Universal Time(UTC):协调世界时,又称为世界标准时间,也就是大家所熟知的格林威治标准时间(Greenwich Mean Time,GMT)。比如,中国内地的时间与UTC的时差为+8,也就是UTC+8。美国是UTC-5。
Calendar Time:日历时间,是用“从一个标准时间点到此时的时间经过的秒数”来表示的时间。这个标准时间点对不同的编译器来说会有所不同,但对一个编译系统来说,这个标准时间点是不变的,该编译系统中的时间对应的日历时间都通过该标准时间点来衡量,所以可以说日历时间是“相对时间”,但是无论你在哪一个时区,在同一时刻对同一个标准时间点来说,日历时间都是一样的。
epoch:时间点。时间点在标准C/C++中是一个整数,它用此时的时间和标准时间点相差的秒数(即日历时间)来表示。
clock tick:时钟计时单元(而不把它叫做时钟滴答次数),一个时钟计时单元的时间长短是由CPU控制的。一个clock tick不是CPU的一个时钟周期,而是C/C++的一个基本计时单位。
我们可以使用ANSI标准库中的time.h头文件。这个头文件中定义的时间和日期所使用的方法,无论是在结构定义,还是命名,都具有明显的C语言风格。下面,我将说明在C/C++中怎样使用日期的时间功能。
2. 计时
C/C++中的计时函数是clock(),而与其相关的数据类型是clock_t。在MSDN中,查得对clock函数定义如下:
clock_t clock( void );
这个函数返回从“开启这个程序进程”到“程序中调用clock()函数”时之间的CPU时钟计时单元(clock tick)数,在MSDN中称之为挂钟时间(wal-clock)。其中clock_t是用来保存时间的数据类型,在time.h文件中,我们可以找到对它的定义:
#ifndef _CLOCK_T_DEFINED typedef long clock_t; #define _CLOCK_T_DEFINED #endif
很明显,clock_t是一个长整形数。在time.h文件中,还定义了一个常量CLOCKS_PER_SEC,它用来表示一秒钟会有多少个时钟计时单元,其定义如下:
#define CLOCKS_PER_SEC ((clock_t)1000)
可以看到每过千分之一秒(1毫秒),调用clock()函数返回的值就加1。下面举个例子,你可以使用公式clock()/CLOCKS_PER_SEC来计算一个进程自身的运行时间:
void elapsed_time() { printf("Elapsed time:%u secs.\n",clock()/CLOCKS_PER_SEC); }
当然,你也可以用clock函数来计算你的机器运行一个循环或者处理其它事件到底花了多少时间:
#include “stdio.h” #include “stdlib.h” #include “time.h”
int main( void ) { long i = 10000000L; clock_t start, finish; double duration; /* 测量一个事件持续的时间*/ printf( "Time to do %ld empty loops is ", i ); start = clock(); while( i-- ) ; finish = clock(); duration = (double)(finish - start) / CLOCKS_PER_SEC; printf( "%f seconds\n", duration ); system("pause"); }
在笔者的机器上,运行结果如下:
Time to do 10000000 empty loops is 0.03000 seconds
上面我们看到时钟计时单元的长度为1毫秒,那么计时的精度也为1毫秒,那么我们可不可以通过改变CLOCKS_PER_SEC的定义,通过把它定义的大一些,从而使计时精度更高呢?通过尝试,你会发现这样是不行的。在标准C/C++中,最小的计时单位是一毫秒。
3.与日期和时间相关的数据结构
在标准C/C++中,我们可通过tm结构来获得日期和时间,tm结构在time.h中的定义如下:
#ifndef _TM_DEFINED struct tm { int tm_sec; /* 秒 – 取值区间为[0,59] */ int tm_min; /* 分 - 取值区间为[0,59] */ int tm_hour; /* 时 - 取值区间为[0,23] */ int tm_mday; /* 一个月中的日期 - 取值区间为[1,31] */ int tm_mon; /* 月份(从一月开始,0代表一月) - 取值区间为[0,11] */ int tm_year; /* 年份,其值等于实际年份减去1900 */ int tm_wday; /* 星期 – 取值区间为[0,6],其中0代表星期天,1代表星期一,以此类推 */ int tm_yday; /* 从每年的1月1日开始的天数 – 取值区间为[0,365],其中0代表1月1日,1代表1月2日,以此类推 */ int tm_isdst; /* 夏令时标识符,实行夏令时的时候,tm_isdst为正。不实行夏令时的进候,tm_isdst为0;不了解情况时,tm_isdst()为负。*/ }; #define _TM_DEFINED #endif
ANSI C标准称使用tm结构的这种时间表示为分解时间(broken-down time)。
而日历时间(Calendar Time)是通过time_t数据类型来表示的,用time_t表示的时间(日历时间)是从一个时间点(例如:1970年1月1日0时0分0秒)到此时的秒数。在time.h中,我们也可以看到time_t是一个长整型数:
#ifndef _TIME_T_DEFINED typedef long time_t; /* 时间值 */ #define _TIME_T_DEFINED /* 避免重复定义 time_t */ #endif
大家可能会产生疑问:既然time_t实际上是长整型,到未来的某一天,从一个时间点(一般是1970年1月1日0时0分0秒)到那时的秒数(即日历时间)超出了长整形所能表示的数的范围怎么办?对time_t数据类型的值来说,它所表示的时间不能晚于2038年1月18日19时14分07秒。为了能够表示更久远的时间,一些编译器厂商引入了64位甚至更长的整形数来保存日历时间。比如微软在Visual C++中采用了__time64_t数据类型来保存日历时间,并通过_time64()函数来获得日历时间(而不是通过使用32位字的time()函数),这样就可以通过该数据类型保存3001年1月1日0时0分0秒(不包括该时间点)之前的时间。
在time.h头文件中,我们还可以看到一些函数,它们都是以time_t为参数类型或返回值类型的函数:
double difftime(time_t time1, time_t time0); time_t mktime(struct tm * timeptr); time_t time(time_t * timer); char * asctime(const struct tm * timeptr); char * ctime(const time_t *timer);
此外,time.h还提供了两种不同的函数将日历时间(一个用time_t表示的整数)转换为我们平时看到的把年月日时分秒分开显示的时间格式tm:
struct tm * gmtime(const time_t *timer); struct tm * localtime(const time_t * timer);
通过查阅MSDN,我们可以知道Microsoft C/C++ 7.0中时间点的值(time_t对象的值)是从1899年12月31日0时0分0秒到该时间点所经过的秒数,而其它各种版本的Microsoft C/C++和所有不同版本的Visual C++都是计算的从1970年1月1日0时0分0秒到该时间点所经过的秒数。
4.与日期和时间相关的函数及应用 在本节,我将向大家展示怎样利用time.h中声明的函数对时间进行操作。这些操作包括取当前时间、计算时间间隔、以不同的形式显示时间等内容。
4.1 获得日历时间
我们可以通过time()函数来获得日历时间(Calendar Time),其原型为:
time_t time(time_t * timer);
如果你已经声明了参数timer,你可以从参数timer返回现在的日历时间,同时也可以通过返回值返回现在的日历时间,即从一个时间点(例如:1970年1月1日0时0分0秒)到现在此时的秒数。如果参数为空(NUL),函数将只通过返回值返回现在的日历时间,比如下面这个例子用来显示当前的日历时间:
#include "time.h" #include "stdio.h" int main(void) { struct tm *ptr; time_t lt; lt =time(NUL); printf("The Calendar Time now is %d\n",lt); return 0; }
运行的结果与当时的时间有关,我当时运行的结果是:
The Calendar Time now is 1122707619
其中1122707619就是我运行程序时的日历时间。即从1970年1月1日0时0分0秒到此时的秒数。
4.2 获得日期和时间
这里说的日期和时间就是我们平时所说的年、月、日、时、分、秒等信息。从第2节我们已经知道这些信息都保存在一个名为tm的结构体中,那么如何将一个日历时间保存为一个tm结构的对象呢?
其中可以使用的函数是gmtime()和localtime(),这两个函数的原型为:
struct tm * gmtime(const time_t *timer); struct tm * localtime(const time_t * timer);
其中gmtime()函数是将日历时间转化为世界标准时间(即格林尼治时间),并返回一个tm结构体来保存这个时间,而localtime()函数是将日历时间转化为本地时间。比如现在用gmtime()函数获得的世界标准时间是2005年7月30日7点18分20秒,那么我用localtime()函数在中国地区获得的本地时间会比世界标准时间晚8个小时,即2005年7月30日15点18分20秒。下面是个例子:
#include "time.h" #include "stdio.h" int main(void) { struct tm *local; time_t t; t=time(NUL); local=localtime(&t); printf("Local hour is: %d\n",local->tm_hour); local=gmtime(&t); printf("UTC hour is: %d\n",local->tm_hour); return 0; }
运行结果是:
Local hour is: 15 UTC hour is: 7
4.3 固定的时间格式
我们可以通过asctime()函数和ctime()函数将时间以固定的格式显示出来,两者的返回值都是char*型的字符串。返回的时间格式为:
星期几 月份 日期 时:分:秒 年\n\0 例如:Wed Jan 02 02:03:55 1980\n\0
其中\n是一个换行符,\0是一个空字符,表示字符串结束。下面是两个函数的原型:
char * asctime(const struct tm * timeptr); char * ctime(const time_t *timer);
其中asctime()函数是通过tm结构来生成具有固定格式的保存时间信息的字符串,而ctime()是通过日历时间来生成时间字符串。这样的话,asctime()函数只是把tm结构对象中的各个域填到时间字符串的相应位置就行了,而ctime()函数需要先参照本地的时间设置,把日历时间转化为本地时间,然后再生成格式化后的字符串。在下面,如果t是一个非空的time_t变量的话,那么:
printf(ctime(&t));
等价于:
struct tm *ptr; ptr=localtime(&t); printf(asctime(ptr));
那么,下面这个程序的两条printf语句输出的结果就是不同的了(除非你将本地时区设为世界标准时间所在的时区):
#include "time.h" #include "stdio.h" int main(void) { struct tm *ptr; time_t lt; lt =time(NUL); ptr=gmtime(<); printf(asctime(ptr)); printf(ctime(<)); return 0; }
运行结果:
Sat Jul 30 08:43:03 2005 Sat Jul 30 16:43:03 2005
4.4 自定义时间格式
我们可以使用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小时制显示,就象下面这段程序:
#include “time.h” #include “stdio.h” int main(void) { struct tm *ptr; time_t lt; char str[80]; lt=time(NUL); ptr=localtime(<); strftime(str,100,"It is now %I %p",ptr); printf(str); return 0; }
其运行结果为: It is now 4PM
而下面的程序则显示当前的完整日期:
#include <stdio.h> #include <time.h>
void main( void ) { struct tm *newtime; char tmpbuf[128]; time_t lt1; time( <1 ); newtime=localtime(<1); strftime( tmpbuf, 128, "Today is %A, day %d of %B in the year %Y.\n", newtime); printf(tmpbuf); }
运行结果:
Today is Saturday, day 30 of July in the year 2005.
4.5 计算持续时间的长度
有时候在实际应用中要计算一个事件持续的时间长度,比如计算打字速度。在第1节计时部分中,我已经用clock函数举了一个例子。Clock()函数可以精确到毫秒级。同时,我们也可以使用difftime()函数,但它只能精确到秒。该函数的定义如下:
double difftime(time_t time1, time_t time0);
虽然该函数返回的以秒计算的时间间隔是double类型的,但这并不说明该时间具有同double一样的精确度,这是由它的参数觉得的(time_t是以秒为单位计算的)。比如下面一段程序:
#include "time.h" #include "stdio.h" #include "stdlib.h" int main(void) { time_t start,end; start = time(NUL); system("pause"); end = time(NUL); printf("The pause used %f seconds.\n",difftime(end,start));//<- system("pause"); return 0; }
运行结果为: 请按任意键继续. . . The pause used 2.000000 seconds. 请按任意键继续. . .
可以想像,暂停的时间并不那么巧是整整2秒钟。其实,你将上面程序的带有“//<-”注释的一行用下面的一行代码替换:
printf("The pause used %f seconds.\n",end-start);
其运行结果是一样的。
4.6 分解时间转化为日历时间
这里说的分解时间就是以年、月、日、时、分、秒等分量保存的时间结构,在C/C++中是tm结构。我们可以使用mktime()函数将用tm结构表示的时间转化为日历时间。其函数原型如下:
time_t mktime(struct tm * timeptr);
其返回值就是转化后的日历时间。这样我们就可以先制定一个分解时间,然后对这个时间进行操作了,下面的例子可以计算出1997年7月1日是星期几:
#include "time.h" #include "stdio.h" #include "stdlib.h" int main(void) { struct tm t; time_t t_of_day; t.tm_year=1997-1900; t.tm_mon=6; t.tm_mday=1; t.tm_hour=0; t.tm_min=0; t.tm_sec=1; t.tm_isdst=0; t_of_day=mktime(&t); printf(ctime(&t_of_day)); return 0; }
运行结果:
Tue Jul 01 00:00:01 1997
现在注意了,有了mktime()函数,是不是我们可以操作现在之前的任何时间呢?你可以通过这种办法算出1945年8月15号是星期几吗?答案是否定的。因为这个时间在1970年1月1日之前,所以在大多数编译器中,这样的程序虽然可以编译通过,但运行时会异常终止。
从今天决定重温C,C++,准备从<<21天学通C语言>>入手,虽不是每天一课程,但每次也要完成一天的课程.并在这里写出感想.来更进一步! NO1 C是什么? C的特点? 灵活,可移植,模块化,关键字少 C,C++区别 C++是C语言的超集,包括了C++的所有内容,同时增加了面对对象编程方面的内容. 创建C的步骤? 确定程序的目标----用什么样的方法实现它----实现程序----运行程序 程序开发周期
1. c++中string到int的转换 1) 在C标准库里面,使用atoi: #include <cstdlib> #include <string> std::string text = "152"; int number = std::atoi( text.c_str() ); if (errno == ERANGE) //可能是std::errno { //number可能由于过大或过小而不能完全存储 } else if (errno == ????) //可能是EINVAL { //不能转换成一个数字 } 2) 在C++标准库里面,使用stringstream:(stringstream 可以用于各种数据类型之间的转换) #include <sstream> #include <string> std::string text = "152"; int number; std::stringstream ss; ss << text;//可以是其他数据类型 ss >> number; //string -> int if (! ss.good()) { //错误发生 } ss << number;// int->string string str = ss.str(); if (! ss.good()) { //错误发生 } 3) 在Boost库里面,使用lexical_cast: #include <boost/lexical_cast.hpp> #include <string> try { std::string text = "152"; int number = boost::lexical_cast< int >( text ); } catch( const boost::bad_lexical_cast & ) { //转换失败 } 2.string 转 CString CString.format(”%s”, string.c_str()); 用c_str()确实比data()要好; 3.char 转 CString CString.format(”%s”, char*); 4.char 转 string string s(char *); 只能初始化,在不是初始化的地方最好还是用assign(). 5.string 转 char * char *p = string.c_str(); 6.CString 转 string string s(CString.GetBuffer()); GetBuffer()后一定要ReleaseBuffer(),否则就没有释放缓冲区所占的空间.
7.字符串的内容转换为字符数组和C—string (1) data(),返回没有”\0“的字符串数组 (2) c_str(),返回有”\0“的字符串数组 (3) copy() 8.CString与int、char*、char[100]之间的转换 (1) CString互转int 将字符转换为整数,可以使用atoi、_atoi64或atol。而将数字转换为CString变量,可以使用CString的Format函数。如 CString s; int i = 64; s.Format(”%d”, i) Format函数的功能很强,值得你研究一下。 void CStrDlg::OnButton1() { CString ss=”1212.12″; int temp=atoi(ss); CString aa; aa.Format(”%d”,temp); AfxMessageBox(”var is ” + aa); } (2) CString互转char* ///char * TO cstring CString strtest; char * charpoint; charpoint=”give string a value”; //? strtest=charpoint; ///cstring TO char * charpoint=strtest.GetBuffer(strtest.GetLength()); (3) 标准C里没有string,char *==char []==string, 可以用CString.Format(”%s”,char *)这个方法来将char *转成CString。 要把CString转成char *,用操作符(LPCSTR)CString就可以了。 CString转换 char[100] char a[100]; CString str(”aaaaaa”); strncpy(a,(LPCTSTR)str,sizeof(a));
C++广泛应用在不同领域,使用者以数百万计。根据近十年的调查,C++的流行程度约稳定排行第3位(于C/Java之后)。 C++经历长期的实践和演化,才成为今日的样貌。1998年,C++标准委员会排除万难,使C++成为ISO标准(俗称C++98),当中含非常强大的标准模版库(standard template library, STL)。之后委员会在2005年提交了有关标准库的第一个技术报告(简称TR1),并为下一个标准C++0x而努力。可惜C++0x并不能在200x年完成,各界希望新标准能于2011年内出台。 流行的C++编译器中,微软Visual C++ 2010已实现部分C++0x语法并加入TR1扩充库,而gcc对C++0x语法和库的支持比VC2010更多。 应否选择C++ 哪些程序适宜使用C++? C++并非万能丹,我按经验举出一些C++的适用时机。 - C++适合构造程序中需求较稳定的部分,需求变化较大的部分可使用脚本语言;
- 程序须尽量发挥硬件的最高性能,且性能瓶颈在于CPU和内存;
- 程序须频繁地与操作系统或硬件沟通;
- 程序必须使用C++框架/库,如大部分游戏引擎(如Unreal/Source)及中间件(如Havok/FMOD),虽然有些C++库提供其他语言的绑定,但通常原生的API性能最好、最新;
- 项目中某个目标平台只提供C++编译器的支持。
按应用领域来说,C++适用于开发服务器软件、桌面应用、游戏、实时系统、高性能计算、嵌入式系统等。 使用C++还是C? C++和C的设计哲学并不一样,两者取舍不同,所以不同的程序员和软件项目会有不同选择,难以一概而论。与C++相比,C具备编译速度快、容易学 习、显式描述程序细节、较少更新标准(后两者也可同时视为缺点)等优点。在语言层面上,C++包含绝大部分C语言的功能(例外之一,C++没有C99的变长数组VLA),且提供OOP和GP的特性。但其实用C也可实现OOP思想,亦可利用宏去实现某程度的GP,只不过C++的语法能较简洁、自动地实现OOP/GP。C++的RAII(resource acquisition is initialization,资源获取就是初始化)特性比较独特,C/C#/Java没有相应功能。回顾历史,Stroustrup开发的早期C++编译器Cpre/Cfront是把C++源代码翻译为C,再用C编译器编译的。由此可知,C++编写的程序,都能用等效的C程序代替,但C++在语言层面上提供了OOP/GP语法、更严格的类型检查系统、大量额外的语言特性(如异常、RTTI等), 并且C++标准库也较丰富。有时候C++的语法可使程序更简洁,如运算符重载、隐式转换。但另一方面,C语言的API通常比C++简洁,能较容易供其他语 言程序调用。因此,一些C++库会提供C的API封装,同时也可供C程序调用。相反,有时候也会把C的API封装成C++形式,以支持RAII和其他 C++库整合等。 为何C++性能可优于其他语言? 相对运行于虚拟机语言(如C#/Java),C/C++直接以静态形式把源程序编译为目标平台的机器码。一般而言,C/C++程序在编译及链接时可 进行的优化最丰富,启动时的速度最快,运行时的额外内存开销最少。而C/C++相对动态语言(如Python/Lua)也减少了运行时的动态类型检测。此 外,C/C++的运行行为是确定的,且不会有额外行为(例如C#/Java必然会初始化变量),也不会有如垃圾收集(GC)而造成的不确定性延迟,而且C /C++的数据结构在内存中的布局也是确定的。有时C++的一些功能会使程序性能优于C,当中以内联和模版最为突出,这两项功能使C++标准库的 sort()通常比C标准库的qsort()快多倍(C可用宏或人手编码去解决此问题)。另一方面,C/C++能直接映射机器码,之间没有另一层中间语言,因此可以做底层优化,例如使用内部(intrinsic)函数和嵌入汇编语言。然而,许多C++的性能优点并非免费午餐,代价包括较长的编译链接时间和较易出错,因而增加开发时间和成本,这点稍后补充。 我进行了一个简单全局渲染性能测试(512x512像素,每像素10000个采样),C++ 1小时36分、Java 3小时18分、Python约18天、Ruby约351天。评测方式和其他语言的结果详见博文。 C++常见问题 C++源代码跨平台吗? C++有不错的跨平台能力,但由于直接映射硬件,因性能优化的关系,跨平台能力不及Java及多数脚本语言。然而,实践跨平台的C++软件还是可行的,但须注意以下问题: - C++标准没有规定原始数据类型(如int)的大小,需要特定大小的类型时,可自订类型(如int32_t),同时对任何类型使用sizeof()而不假设其大小;
- 字节序(byte order)按CPU有所不同,特别要注意二进制输入输出、reinterpret_cast法;
- 原始数据和结构类型的地址对齐有差异;
- 编译器提供的一些编译器或平台专用扩充指令;
- 避免作应用二进制接口(application binary interface, ABI)的假设,例如调用函数时参数的取值顺序在C/C++中没定义,在C++中也不可随便假设RTTI/虚表等实现方式。
总括而言,跨平台C++软件可在头文件中用宏检测编译器和平台,再用宏、typedef、自定平台相关实现等方法去实践跨平台,C++标准不会提供这类帮助。 C++程序容易崩溃? 和许多语言相比,C/C++提供不安全的功能以最优化性能,有可能造成崩溃。但要注意,很多运行时错误,如向空指针/引用解引用、数组越界、堆栈溢 出等,其他语言也会报错或抛出异常,这些都是程序问题,而不是语言本身的问题。有些意见认为,出现这类运行时错误,应该尽量写入日志并立即崩溃,不该让程 序继续运行,以免造成更大的影响(例如程序继续把内存中错误的数据覆写文件)。若要容错,可按业务把程序分割为多进程,像Chrome或使用fork()的形式。然而,C++有许多机制可以减少错误,例如以string代替C字符串;以vector或array(TR1)代替原始数组(有些实现可在调试模式检测越界);使用智能指针也能减少一些原始指针的问题。另外,我最常遇到的Bug,就是没有初始化成员变量,有时会导致崩溃,而且调试版和发行版的行为可能不同。 C++要手动做内存管理? C++同时提供在堆栈上的自动局部变量,以及从自由存储(free store)分配的对象。对于后者,程序员需手动释放,或使用不同的容器和智能指针。 C++程序员经常进一步优化内存,自定义内存分配策略以提升效能,例如使用对象池、自定义的单向/双向堆栈区等。虽然C++0x还没加入GC功能,但也可 以自行编写或使用现成库。此外,C/C++也可以直接使用操作系统提供的内存相关功能,例如内存映射文件、共享内存等。 使用C++常要重造轮子? 我曾参与的C++项目,都会重造不少标准库已提供的功能,此情况在其他语言中较少出现。我试图分析个中原因。首先,C++标准库相对很多语言来说是 贫乏的,各开发者便会重复地制造自订库。从另一个角度看,C++标准库是用C++编写的(很多其他语言不用自身而是用C/C++去编写库),在能力和性能 上,自订库和标准库并无本质差别;另外,标准库为通用而设,对不同平台及多种使用需求作取舍,性能上有所影响,例如EA公司就曾发表自制的EASTL规 格,描述游戏开发方面对STL的性能及功能需求的特点;此外,多个C++库一起使用,经常会因规范不同而引起冲突,又或功能重叠,所以项目可能须自行开 发,或引入其他库的概念或实现(如Boost/TR1/Loki),改写以符合项目规范。 C++编译速度很慢? 错,是非常慢。我认为C++可能是实用程序语言中编译速度最慢的。此问题涉及C++沿用C的编译链接方式,又加入了复杂的类/泛型声明和内联机制,使编译时间倍增。在C++对编译方法改革之前(如module提案),可使用以下技巧改善:第一,使用pimpl手法,因性能损耗应用于调用次数不多的类;第二,仅包含必要头文件,并尽量使用及提供前置声明版本的头文件(如iosfwd);第三采用基于接口的设计,但须注意虚函数调用成本;第四,采用unity build,即把多个cpp文件结合在一个编译单元进行编译;第五,采用分布式生成系统如IncrediBuild。 C++缺乏什么功能? 虽然C++已经非常复杂,但仍缺少很多常见功能。 C++0x作出了不少改善,例如语言方面加入Lambda函数、闭包、类型推导声明等,而库方面则加入正则表达式、采用哈希表的 unordered_set/unordered_map、引用计数智能指针shared_ptr/weak_ptr等。但最值得留意的是C++0x引入 多线程的语法和库功能,这是C++演进的一大步。然而,模组、GC、反射机制等功能虽有提案,却未加进C++0x。 C++使用建议 为应用挑选特性集 我同意Stroustrup关于使用C++各种技术的回应:“你可以做,不意味着你必须这么做。(Just because you can do it, doesn't mean that you have to.)” C++充满丰富的特性,但同时带来不同问题,例如过分复杂、编译及运行性能的损耗。一般可考虑是否使用多重继承、异常、RTTI,并调节使用模版及模版元 编程的程度。使用过分复杂的设计和功能,可能会令部分团队成员更难理解和维护。 为团队建立编程规范 C++的编码自由度很高,容易编写风格迥异的代码,C++本身也没有定义一些标准规范。而且,C++的源文件物理构成,较许多语言复杂。因此,除了决定特性集,每个团队应建立一套编程规范,包括源文件格式(可使用文件模版)、花括号风格。 尽量使用C++风格而非C风格 由于C++有对C兼容的包袱,一些功能可以使用C风格实现,但最好使用C++提供的新功能。最基本的是尽量以具名常量、内联函数和泛型取代宏,只把 宏用在条件式编译及特殊情况。旧式的C要求局部变量声明在作用域开端,C++则无此限制,应把变量声明尽量置于邻近其使用的地方,for()的循环变量声 明可置于for的括号内。 C++中能加强类型安全的功能应尽量使用,例如避免“万能”指针void *,而使用个别或泛型类型;用bool而非int表示布尔值;选用4种C++ cast关键字代替简单的强制转换。 结合其他语言 如前文所述,C++并非适合所有应用情境,有时可以混合其他语言使用,包括用C++扩展其他语言,或在C++程序中嵌入脚本语言引擎。对于后者,除了使用各种脚本语言的专门API,还可使用Boost或SWIG作整合。 C++学习建议 C++缺点之一,是相对许多语言复杂,而且难学难精。许多人说学习C语言只需一本K&R《C程序设计语言》即可,但C++书籍却是多不胜数。我是从C进入C++,皆是靠阅读自学。在此分享一点学习心得。个人认为,学习C++可分为4个层次: - 第一层次,C++基础:挑选一本入门书籍,如《C++ Primer》、《C++大学教程》、或Stroustrup撰写的经典《C++程序设计语言》或他一年半前的新作《C++程序设计原理与实践》,而一般C++课程也止于此,另外《C++ 标准程序库》及《The C++ Standard Library Extensions》可供参考;
- 第二层次,正确高效地使用C++:此层次开始必须自修,阅读过《(More)Effective C++》、《(More)Exceptional C++》、《Effective STL》及《C++编程规范》等,才适宜踏入专业C++开发之路;
- 第三层次,深入了解C++:关于全局问题可读《深入探索C++对象模型》、《Imperfect C++》、《C++沉思录》、《STL源码剖析》,要挑战智商,可看关于模版及模版元编程的书籍如《C++ Templates》、《C++设计新思维》、《C++模版元编程》;
- 第四层次,研究C++:阅读《C++语言的设计和演化》、《编程的本质》(含STL设计背后的数学根基)、C++标准文件《ISO/IEC 14882:2003》、C++标准委员会的提案书和报告书、关于C++的学术文献。
由于我主要是应用C++,大约只停留于第二、三个层次。然而,C++只是软件开发的一环而已,单凭语言并不能应付业务和工程上的问题。建议读者不要 强求几年内“彻底学会C++的知识”,到达第二层左右便从工作实战中汲取经验,有兴趣才慢慢继续学习更高层次的知识。虽然学习C++有难度,但也是相当有 趣且有满足感的。
|
|
|
| 日 | 一 | 二 | 三 | 四 | 五 | 六 |
---|
30 | 31 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 |
|
导航
统计
常用链接
留言簿(1)
随笔档案
搜索
最新评论

阅读排行榜
评论排行榜
|
|