colorful

zc qq:1337220912

 

关于DBSvr启动时加载所有数据到内存

关于DBSvr启动时加载所有数据到内存
    最近在做这一块,在网上讨论了如何做一个高效的DBSvr, 有人提出这个想法,
    仔细想想有两个问题,
       数据加载到内存,以什么样的形式存贮,像DB里的表一样,还是按游戏逻辑来存,
      如果像表一样,那实现一个像DBMS一样高效的查表功能应该是个问题。
      如果按游戏逻辑来存, 那DBSvr又会跟游戏逻辑层扯上太多联系。
    
看来,读所有数据到内存是不现实的。不知道各个有什么看法,请多多指教

posted @ 2012-07-13 12:13 多彩人生 阅读(221) | 评论 (0)编辑 收藏

游戏服务器架构设计中的一些思考

http://www.cppblog.com/jaxe/archive/2010/04/22/113255.html

 

1、 游戏世界由很多个游戏对象组成(游戏角色、物品、NPC、技能等);

 

2、 一个游戏对象的有效数据主要存放在客户端、游戏服务器和持久性数据库中;

 

3、 游戏对象的处理可划分为与位置有关的和与位置无关的,如公会处理、物品处理等主要行为可以看作是与位置无关的处理,而NPC(AI)、战斗、移动这类的主要行为可以看成是与位置有关的。

 

4、 从客户端的角度来看,游戏行为可分为四类动作:

a)         来自服务器端的动作,如另外一个玩家跳起来。

b)        本地动作。仅仅发生在本地客户端的动作,不需要与服务器端或其他客户端通讯。

c)         先执行后验证的可撤销的动作。客户端先执行,再提交服务器端验证,验证不成功通知客户端将执行的动作撤销。比如玩家控制的游戏角色执行移动处理。

d)        严格服务器端验证的动作。客户端执行动作前必须经过服务器端验证后才能执行。如交易行为、攻击其他玩家/NPC。

 

5、 客户端和服务器,服务器进程之间的相互的通信从逻辑上看就是就是向RemoteObject 发起的远程过程调用(RPC),RPC主要有两种类型:

a)         通知(Notify)。只通知对方,而不关心和需要对方返回结果。

b)        请求(Request)。向对方发起请求,对方处理请求后返回结果,发起请求和返回结果这个过程可以是同步或异步。游戏服务器中绝大部分RPC请求都是异步的。

 

6、 响应延迟主要是由于网络带宽和服务器处理效率引起的。应尽可能的通过一些技巧来隐藏和减少玩家的响应延迟。但不是所有的最新消息都能立刻发送出去(或接收处理到),因此,要在服务器端采用优先队列来减少重要消息的响应时间。延迟也会由客户端产生,如收到消息后的对消息的处理速度。

 

 

7、 服务器负载,除了升级硬件设备外,可以通过一些方式来提高服务器负载。

 

a)         保证足够的网络带宽。

b)        分布式运算,合理的集群式架构。

c)         游戏策划从游戏内容上避免设计高并发,高消耗的游戏行为。

 

 

 

8、 从服务器的可伸缩性,稳定性和高效率方面来考虑,要试着避免所有事情都在一个地方处理,尽量让系统分布式运行,但是过多的划分功能到不同的进程/机器上运行,又会带来数据的大量同步的问题。因此可以将游戏对象的处理主要划分为与位置无关和有关两种。像公会,玩家信息,物品信息,组队,拍卖等等这类与位置无关的但是占用CPU资源较少的处理可以尽可能的放在一个进程中,避免进程间对象同步,而像NPC,寻路,AOI运算,战斗处理等与位置有关的,处理过程中特别关心对象坐标位置的、运算量特别大的,但是进程间对象同步较少的,都可以单独划分成多个进程。

 

每类进程服务的功能尽量单一。负责路由的就尽量只负责网络包转发,而不再承担其他繁重的任务,负责游戏处理的就尽量让网络包流向简单。


大规模应用服务器(不只包含游戏服务器)是否成功主要看架构师对问题的解构能力。
问题是什么?
问题的边界在哪里?
功能粒度划分多细?
解决这些问题都需要经验。

posted @ 2012-07-12 16:06 多彩人生 阅读(153) | 评论 (0)编辑 收藏

stl之map erase方法的正确使用

STL的map表里有一个erase方法用来从一个map中删除掉指令的节点
eg:
map<string,string> mapTest;
typedef map<string,string>::iterator ITER;

ITER iter=mapTest.find(key);
mapTest.erase(iter);

像上面这样只是删除单个节点,map的形为不会出现任务问题,
但是当在一个循环里用的时候,往往会被误用,那是因为使用者没有正确理解iterator的概念.
像下面这样的一个例子就是错误的写法,
eg.
for(ITER iter=mapTest.begin();iter!=mapTest.end();++iter)
{
cout<<iter->first<<":"<<iter->second<<endl;
mapTest.erase(iter);
}

这是一种错误的写法,会导致程序行为不可知.究其原因是map 是关联容器,对于关联容器来说,如果某一个元素已经被删除,那么其对应的迭代器就失效了,不应该再被使用;否则会导致程序无定义的行为。
可以用以下方法解决这问题:
正确的写法
1.使用删除之前的迭代器定位下一个元素。STL建议的使用方式
for(ITER iter=mapTest.begin();iter!=mapTest.end();)
{
cout<<iter->first<<":"<<iter->second<<endl;
mapTest.erase(iter++);
}

2. erase() 成员函数返回下一个元素的迭代器
for(ITER iter=mapTest.begin();iter!=mapTest.end();)
{
cout<<iter->first<<":"<<iter->second<<endl;
iter=mapTest.erase(iter);
}

注意:

map的earse应注意:
map这种容器的下边访问和Vector等容器的下标访问有本质的区别。
对于Vector容器,用aVector[i]访问第i个元素时,如果元素不存在,容器不会增加元素,
而对于map,用aMap[key]
访问键key对应的对象时,如果该键对应的对象存在,则返回该对象(这和Vector一样),但是,当键值为key的元素不存在时,容器会自动的增加一个 pair,键为key,而值则为一个容器定义时指定的类型并默认初始化(即,如果该类型为基本类型,则初始化为0,比如本例中,aMap[1]的使用会产 生一个pair,<1,NULL>,若该类型为类类型,则调用默认构造函数初始化之)

显然,本例中,aMap[1]为NULL,后面的erase()不会执行,使得后面的
插入语句aMap.insert(1,new A())键值冲突

eg:如下代码会导致错误

#include <iostream>
#include <map>

using namespace std;

struct A
{
A(int i)
{
x=i;
}
int x;
};

int main()
{
map<int,A*> amap;
if ( amap[1] != NULL )
amap.erase(1);
amap.insert(make_pair(1,new A(1)));
amap.insert(make_pair(2,new A(2)));
amap.insert(make_pair(3,new A(3)));
return 0;
}

posted @ 2012-07-06 10:20 多彩人生 阅读(231) | 评论 (0)编辑 收藏

c++中的.hpp文件

  hpp,其实质就是将.cpp的实现代码混入.h头文件当中,定义与实现都包含在同一文件,则该类的调用者只需要include该cpp文件即可,无需再 将cpp加入到project中进行编译。而实现代码将直接编译到调用者的obj文件中,不再生成单独的obj,采用hpp将大幅度减少调用 project中的cpp文件数与编译次数,也不用再发布烦人的lib与dll,因此非常适合用来编写公用的开源库。

1、是Header Plus Plus 的简写。

2、与*.h类似,hpp是C++程序头文件 。

3、是VCL 专用的头文件,已预编译。

4、是一般模板类的头文件。

5、一般来说,*.h里面只有声明,没有实现,而*.hpp里声明实现都有,后者可以减 少.cpp的数量。

6、*.h里面可以有using namespace std,而*.hpp里则无。

7、*.hpp要注意的问题有:

      a)不可包含全局对象和全局函数

     由于hpp本质上是作为.h被调用者include,所以当hpp文件中存在全局对象或者全局函数,而该hpp被多个

    调用者include时,将在链接时导致符号重定义错误。要避免这种情况,需要去除全局对象,将全局函数封

    装为类的静态方法。

      b)类之间不可循环调用

      在.h和.cpp的场景中,当两个类或者多个类之间有循环调用关系时,只要预先在头文件做被调用类的声明

    即可,如下:

    class B;

    class A{

    public:

         void someMethod(B b);

    };

    class B{

    public:

         void someMethod(A a);

    };

    在hpp场景中,由于定义与实现都已经存在于一个文件,调用者必需明确知道被调用者的所有定义,而不能等到cpp

    中去编译。因此hpp中必须整理类之间调用关系,不可产生循环调用。同理,对于当两个类A和B分别定义在各自的

    hpp文件中,形如以下的循环调用也将导致编译错误:

    //a.hpp

    #include "b.hpp"

    class A{

    public:

        void someMethod(B b);

    };

    //b.hpp

    #include "a.hpp"

    class B{

    public:

        void someMethod(A a);

    }

      c)不可使用静态成员

      静态成员的使用限制在于如果类含有静态成员,则在hpp中必需加入静态成员初始化代码,当该hpp被多个文档include时,将产生符号重定义错误。唯 一的例外是const static整型成员,因为在vs2003中,该类型允许在定义时初始化,如:

    class A{

     public:

       const static int intValue = 123;

     };

    由于静态成员的使用是很常见的场景,无法强制清除,因此可以考虑以下几种方式(以下示例均为同一类中方法)

   一、类中仅有一个静态成员时,且仅有一个调用者时,可以通过局域静态变量模拟

    //方法模拟获取静态成员

    someType getMember()

    {

       static someType value(xxx);//作用域内静态变量

       return value;

    }

   二、.类中有多个方法需要调用静态成员,而且可能存在多个静态成员时,可以将每个静态成员封装一个模拟方法,供其他方法调用。

    someType getMemberA()

    {

       static someType value(xxx);//作用域内静态变量

       return value;

    }

    someType getMemberB()

    {

       static someType value(xxx);//作用域内静态变量

       return value;

    }

   void accessMemberA()

    {

       someType member = getMemberA();//获取静态成员

     };

    //获取两个静态成员

    void accessStaticMember()

    {

       someType a = getMemberA();//获取静态成员

       someType b = getMemberB();

     };

    三、第二种方法对于大部分情况是通用的,但是当所需的静态成员过多时,编写封装方法的工作量将非常

    巨大,在此种情况下,建议使用Singleton模式,将被调用类定义成普通类,然后使用Singleton将其变为

   全局唯一的对象进行调用。

     如原h+cpp下的定义如下:

     class A{

     public:

        type getMember(){

           return member;

        }

        static type member;//静态成员

    }

    采用singleton方式,实现代码可能如下(singleton实现请自行查阅相关文档)

    //实际实现类

     class Aprovider{

     public:

        type getMember(){

           return member;

        }

       type member;//变为普通成员

    }

    //提供给调用者的接口类

     class A{

     public:

        type getMember(){

           return Singleton<AProvider>::getInstance()->getMember();

        }

    }

posted @ 2012-07-03 17:47 多彩人生 阅读(312) | 评论 (0)编辑 收藏

剖析为什么在多核多线程程序中要慎用volatile关键字?

这篇文章详细剖析了为什么在多核时代进行多线程编程时需要慎用volatile关键字。

主要内容有:
1. C/C++中的volatile关键字
2. Visual Studio对C/C++中volatile关键字的扩展
3. Java/.NET中的volatile关键字
4. Memory Model(内存模型)
5. Volatile使用建议

1. C/C++中的volatile关键字

1.1 传统用途

C/C++作为系统级语言,它们与硬件的联系是很紧密的。volatile的意思是“易变的”,这个关键字最早就是为了针对那些“异常”的内存操作 而准备的。它的效果是让编译器不要对这个变量的读写操作做任何优化,每次读的时候都直接去该变量的内存地址中去读,每次写的时候都直接写到该变量的内存地 址中去,即不做任何缓存优化。它经常用在需要处理中断的嵌入式系统中,其典型的应用有下面几种:

a. 避免用通用寄存器对内存读写的优化。编译器常做的一种优化就是:把常用变量的频繁读写弄到通用寄存器中,最后不用的时候再存回内存中。但是如果某个内存地址中的值是由片外决定的(例如另一个线程或是另一个设备可能更改它),那就需要volatile关键字了。(感谢Kenny老师指正)
b. 硬件寄存器可能被其他设备改变的情况。例如一个嵌入式板子上的某个寄存器直接与一个测试仪器连在一起,这样在这个寄存器的值随时可能被那个测试仪器更改。 在这种情况下如果把该值设为volatile属性的,那么编译器就会每次都直接从内存中去取这个值的最新值,而不是自作聪明的把这个值保留在缓存中而导致 读不到最新的那个被其他设备写入的新值。
c. 同一个物理内存地址M有两个不同的内存地址的情况。例如两个程序同时对同一个物理地址进行读写,那么编译器就不能假设这个地址只会有一个程序访问而做缓存优化,所以程序员在这种情况下也需要把它定义为volatile的。

1.2 多线程程序中的错误用法

看到这里,很多朋友自然会想到:恩,那么如果是两个线程需要同时访问一个共享变量,为了让其中两个线程每次都能读到这个变量的最新值,我们就把它定 义为volatile的就好了嘛!我想这个就是多线程程序中volatile之所以引起那么多争议的最大原因。可惜的是,这个想法是错误的。

举例来说,想用volatile变量来做同步(例如一个flag)?错!为什么?很简单,虽然volatile意味着每次读和写都是直接去内存地址中去操作,但是volatile在C/C++现有标准中即不能保证原子性(Atomicity)也不能保证顺序性(Ordering),所以几乎所有试图用volatile来进行多线程同步的方案都是错的。我之前一篇文章介绍了Sequential Consistency模型(后 面简称SC),它其实就是我们印象中多线程程序应该有的执行顺序。但是,SC最大的问题是性能太低了,因为CPU/编译器完全没有必要严格按代码规定的顺 序(program order)来执行每一条指令。学过体系结构的同学应该知道不管是编译器也好CPU也好,他们最擅长做的事情就是帮你做乱序优化。在串行时代这些乱序优化 对程序员来说都是透明的,封装好了的,你不用关心它们到底给你乱序成啥样了,因为它们会保证优化后的程序的运行结果跟你写程序时预期的结果是一模一样的。 但是进入多核时代之后,CPU和编译器还会继续做那些串行时代的优化,更重要的是这些优化还会打破你多线程程序的SC模型语义,从而使得多线程程序的实际 运行结果与我们所期待的运行结果不一致!

拿X86来说,它的多核内存模型没有严格执行SC,即属于weak ordering(或者叫relax ordering?)。它唯一允许的乱序优化是可以把对不同地址的load操作提到store之前去(即把store x->load y乱序优化成load y -> store x)。而store x -> store y、load x -> load y,以及store x -> load y不允许交换执行顺序。在X86这样的内存模型下,volatile关键字根本就不能保证对不同volatile变量x和y的store x -> load y的操作不会被CPU乱序优化成load y -> store x。

而对多线程读写操作的原子性来说,诸如volatile x=1这样的写操作的原子性其实是由X86硬件保证的,跟volatile没有任何关系。事实上,volatile根本不能保证对没有内存对齐的变量(或者超出机器字长的变量)的读写操作的原子性。

为了有个更直观的理解,我们来看看CPU的乱序优化是如何让volatile在多线程程序中显得如此无力的。下面这个著名的Dekker算法是想用 flag1/2和turn来实现两个线程情况下的临界区互斥访问。这个算法关键就在于对flag1/2和turn的读操作(load)是在其写操作 (store)之后的,因此这个多线程算法能保证dekker1和dekker2中对gSharedCounter++的操作是互斥的,即等于是把 gSharedCounter++放到临界区里去了。但是,多核X86可能会对这个store->load操作做乱序优化,例如dekker1中对 flag2的读操作可能会被提到对flag1和turn的写操作之前,这样就会最终导致临界区的互斥访问失效,而gSharedCounter++也会因 此产生data race从而出现错误的计算结果。那么为什么多核CPU会对多线程程序做这样的乱序优化呢?因为从单线程的视角来看flag2和flag1、turn是没 有依赖关系的,所以CPU当然可以对他们进行乱序优化以便充分利用好CPU里面的流水线(想了解更多细节请参考计算机体系结构相关书籍)。这样的优化虽然 从单线程角度来讲没有错,但是它却违反了我们设计这个多线程算法时所期望的那个多线程语义。(想要解决这个bug就需要自己手动添加memory barrier,或者干脆别去实现这样的算法,而是使用类似pthread_mutex_lock这样的库函数,后面我会再讲到这点)

当然,对不同的CPU来说他们的内存模型是不同的。比如说,如果这个程序是在单核上以多线程的方式执行那么它肯定不会出错,因为单核CPU的内存模 型是符合SC的。而在例如PowerPC,ARM之类的架构上运行结果到底如何就得去翻它们的硬件手册中内存模型是怎么定义的了。

2. Visual Studio对C/C++中volatile关键字的扩展

虽然C/C++中的volatile关键字没有对ordering做任何保证,但是微软从Visual Studio 2005开始就对volatile关键字添加了同步语义(保证ordering),即:对volatile变量的读操作具有acquire语义,对 volatile变量的写操作具有release语义。Acquire和Release语义是来自data-race-free模型的概念。为了理解这个 acquire语义和release语义有什么作用,我们来看看MSDN中的一个例子

例子中的 while (Sentinel) Sleep(0); // volatile spin lock 是对volatile变量的读操作,它具有acquire语义,acquire语义的隐义是当前线程在对sentinel的这个读操作之后的所有的对全局 变量的访问都必须在该操作之后执行;同理,例子中的Sentinel = false; // exit critical section 是对volatile变量的写操作,它具有release语义,release语义的隐义是当前线程在对sentinel这个写操作之前的所有对全局变量 的访问都必须在该操作之前执行完毕。所以ThreadFunc1()读CriticalData时必定已经在ThreadFunc2()执行完 CriticalData++之后,即CriticalData最后输出的值必定为1。建议大家用纸画一下acquire/release来加深理解。一个比较形象的解释就是把acquire当成lock,把release当成unlock,它俩组成了一个临界区,所有临界区外面的操作都只能往这个里面移,但是临界区里面的操作都不能往外移,简单吧?

其实这个程序就相当于用volatile变量的acquire和release语义实现了一个临界区,在临界区内部的代码就是 Sleep(2000); CriticalData++; 或者更贴切点也可以看成是一对pthread_cond_wait和pthread_cond_signal。

这个volatile的acquire和release语义是VS自己的扩展,C/C++标准里是没有的,所以同样的代码用gcc编译执行结果就可 能是错的,因为编译器/CPU可能做违反正确性的乱序优化。Acquire和release语义本质上就是为了保证程序执行时memory order的正确性。但是,虽然这个VS扩展使得volatile变量能保证ordering,它还是不能保证对volatile变量读写的原子性。事 实上,如果我们的程序是跑在X86上面的话,内存对齐了的变量的读写的原子性是由硬件保证的,跟volatile没有任何关系。而像volatile g_nCnt++这样的语句本身就不是原子操作,想要保证这个操作是原子的,就必须使用带LOCK语义的++操作,具体请看我这篇文章

另外,VS生成的volatile变量的汇编代码是否真的调用了memory barrier也得看具体的硬件平台,例如x86上就不需要使用memory barrier也能保证acquire和release语义,因为X86硬件本身就有比较强的memory模型了,但是Itanium上面VS就会生成带 memory barrier的汇编代码。具体可以参考这篇

但是,虽然VS对volatile关键字加入了acquire/release语义,有一种情况还是会出错,即我们之前看到的dekker算法的例子。这 个其实蛮好理解的,因为读操作的acquire语义不允许在其之后的操作往前移,但是允许在其之前的操作往后移;同理,写操作的release语义允许在 其之后的操作往前移,但是不允许在其之前的操作往后移;这样的话对一个volatile变量的读操作(acquire)当然可以放到对另一个 volatile变量的写操作(release)之前了!Bug就是这样产生的!下面这个程序大家拿Visual Studio跑一下就会发现bug了(我试了VS2008和VS2010,都有这个bug)。多线程编程复杂吧?希望大家还没被弄晕,要是晕了的话也很正 常,仔仔细细重新再看一遍吧:)

想解决这个Bug也很简单,直接在dekker1和dekker2中对flag1/flag2/turn赋值操作之后都分别加入full memory barrier就可以了,即保证load一定是在store之后执行即可。具体的我就不详述了。

3. Java/.NET中的volatile关键字

3.1 多线程语义

Java和.NET分别有JVM和CLR这样的虚拟机,保证多线程的语义就容易多了。说简单点,Java和.NET中的volatile关键字也是限制虚拟机做优化,都具有acquire和release语义,而且由虚拟机直接保证了对volatile变量读写操作的原子性。 (volatile 只保证可见性,不保证原子性。java中,对volatile修饰的long和double的读写就不是原子的 (http://java.sun.com/docs/books/jvms/second_edition/html /Threads.doc.html#22244),除此之外的基本类型和引用类型都是原子的。– 多谢liuchangit指正) 这 里需要注意的一点是,Java和.NET里面的volatile没有对应于我们最开始提到的C/C++中对“异常操作”用volatile修饰的传统用 法。原因很简单,Java和.NET的虚拟机对安全性的要求比C/C++高多了,它们才不允许不安全的“异常”访问存在呢。

而且像JVM/.NET这样的程序可移植性都非常好。虽然现在C++1x正在把多线程模型添加到标准中去,但是因为C++本身的性质导致它的硬件平 台依赖性很高,可移植性不是特别好,所以在移植C/C++多线程程序时理解硬件平台的内存模型是非常重要的一件事情,它直接决定你这个程序是否会正确执 行。

至于Java和.NET中是否也存在类似VS 2005那样的bug我没时间去测试,道理其实是相同的,真有需要的同学自己应该能测出来。好像这篇InfoQ的文章中显示Java运行这个dekker算法没有问题,因为JVM给它添加了mfence。另一个臭名昭著的例子就应该是Double-Checked Locking了。

3.2 volatile int与AtomicInteger区别

Java和.NET中这两者还是有些区别的,主要就是后者提供了类似incrementAndGet()这样的方法可以直接调用(保证了原子性), 而如果是volatile x进行++操作则不是原子的。increaseAndGet()的实现调用了类似CAS这样的原子指令,所以能保证原子性,同时又不会像使用 synchronized关键字一样损失很多性能,用来做全局计数器非常合适。

4. Memory Model(内存模型)

说了这么多,还是顺带介绍一下Memory Model吧。就像前面说的,CPU硬件有它自己的内存模型,不同的编程语言也有它自己的内存模型。如果用一句话来介绍什么是内存模型,我会说它就是程序 员,编程语言和硬件之间的一个契约,它保证了共享的内存地址里的值在需要的时候是可见的。下次我会专门详细写一篇关于它的内容。它最大的作用是取得可编程 性与性能优化之间的一个平衡。

5. volatile使用建议

总的来说,volatile关键字有两种用途:一个是ISO C/C++中用来处理“异常”内存行为(此用途只保证不让编译器做任何优化,对多核CPU是否会进行乱序优化没有任何约束力),另一种是在Java /.NET(包括Visual Studio添加的扩展)中用来实现高性能并行算法(此种用途通过使用memory barrier保证了CPU/编译器的ordering,以及通过JVM或者CLR保证了对该volatile变量读写操作的原子性)。

一句话,volatile对多线程编程是非常危险的,使用的时候千万要小心你的代码在多核上到底是不是按你所想的方式执行的,特别是对现在暂时还没 有引入内存模型的C/C++程序更是如此。安全起见,大家还是用Pthreads,Java.util.concurrent,TBB等并行库提供的 lock/spinlock,conditional variable, barrier, Atomic Variable之类的同步方法来干活的好,因为它们的内部实现都调用了相应的memory barrier来保证memory ordering,你只要保证你的多线程程序没有data race,那么它们就能帮你保证你的程序是正确的(是的,Pthreads库也是有它自己的内存模型的,只不过它的内存模型还些缺点,所以把多线程内存模 型直接集成到C/C++中是更好的办法,也是将来的趋势,但是C++1x中将不会像Java/.NET一样给volatile关键字添加acquire和 release语义,而是转而提供另一种具有同步语义的atomic variables,此为后话)。如果你想实现更高性能的lock free算法,或是使用volatile来进行同步,那么你就需要先把CPU和编程语言的memory model搞清楚,然后再时刻注意Atomicity和Ordering是否被保证了。(注 意,用没有acquire/release语义的volatile变量来进行同步是错误的,但是你仍然可以在C/C++中用volatile来修饰一个不 是用来做同步(例如一个event flag)而只是被不同线程读写的共享变量,只不过它的新值什么时候能被另一个线程读到是没有保证的,需要你自己做相应的处理)

Herb Sutter 在他的那篇volatile vs. volatile中对这两种用法做了很仔细的区分,我把其中两张表格链接贴过来供大家参考:

volatile的两种用途
volatile两种用途的异同

最后附上《Java Concurrency in Practice》3.1.4节中对Java语言的volatile关键字的使用建议(不要被英语吓到,这些内容确实对你有用,而且还能顺便帮练练英语,哈哈):

So from a memory visibility perspective, writing a volatile variable is like exiting a synchronized block and reading a volatile variable is like entering a synchronized block. However, we do not recommend relying too heavily on volatile variables for visibility; code that relies on volatile variables for visibility of arbitrary state is more fragile and harder to understand than code that uses locking.

Use volatile variables only when they simplify implementing and verifying your synchronization policy; avoid using volatile variables when veryfing correctness would require subtle reasoning about visibility. Good uses of volatile variables include ensuring the visibility of their own state, that of the object they refer to, or indicating that an important lifecycle event (such as initialization or shutdown) has occurred.

Locking can guarantee both visibility and atomicity; volatile variables can only guarantee visibility.

You can use volatile variables only when all the following criteria are met:
(1) Writes to the variable do not depend on its current value, or you can ensure that only a single thread ever updates the value;
(2) The variable does not participate in invariants with other state variables; and
(3) Locking is not required for any other reason while the variable is being accessed.

参考资料

1. 《Java Concurrency in Practice》3.1.4节
2. volatile vs. volatile(Herb Sutter对volatile的阐述,必看)
3. The “Double-Checked Locking is Broken” Declaration
4. Threading in C#
5. Volatile: Almost Useless for Multi-Threaded Programming
6. Memory Ordering in Modern Microprocessors
7. Memory Ordering @ Wikipedia
8. 内存屏障什么的
9. The memory model of x86
10. VC 下 volatile 变量能否建立 Memory Barrier 或并发锁
11. Sayonara volatile(Concurrent Programming on Windows作者的文章 跟我观点几乎一致)
12. Java 理论与实践: 正确使用 Volatile 变量
13. Java中的Volatile关键字

 

转自:http://www.parallellabs.com/2010/12/04/why-should-we-be-care-of-volatile-keyword-in-multithreaded-applications/

posted @ 2012-07-02 17:03 多彩人生 阅读(439) | 评论 (0)编辑 收藏

关于c++异常 和错误

以前写程序一般是通过返回错误代码或者设置错误标志位为实现,但是这里有个问题,就是你不能保证用户会去检验这个返回代码或者错误标志位,这样的话程序出 错了还继续运行,最终是离出错的地方越来越远。而异常其实就是一个错误信息,如果有异常,而该异常没有被任何程序捕捉的话,程序就会中断。可以更好的让客 户诊断错误。

我想以上只不过是使用异常的一个原因。应该还有其他吧,欢迎ding   。
///////////////////////////////////////////////////////////////////////////////////////
异常有时并不是由于你的程序的问题引起的,如用new   申请一个内存块失败时,数据库操作失败时,就会产生一个异常,异常往往是程序的执行过程中不可预料的。如果不对产生的异常进行处理,程序往往崩溃,会使软件显得很脆弱。

错误而言,不管是语法错误,逻辑错误都是可以通过检查发现的。
/////////////////////////////////////////////////////////////////////////////////////////////

posted @ 2012-06-30 12:01 多彩人生 阅读(236) | 评论 (0)编辑 收藏

c++文件操作

C++ 通过以下几个类支持文件的输入输出:

  • ofstream: 写操作(输出)的文件类 (由ostream引申而来)
  • ifstream: 读操作(输入)的文件类(由istream引申而来)
  • fstream: 可同时读写操作的文件类 (由iostream引申而来)

打开文件(Open a file)

对这些类的一个对象所做的第一个操作通常就是将它和一个真正的文件联系起来,也就是说打开一个文件。被打开的文件在程序中由一个流对象(stream object)来表示 (这些类的一个实例) ,而对这个流对象所做的任何输入输出操作实际就是对该文件所做的操作。

要通过一个流对象打开一个文件,我们使用它的成员函数open():

void open (const char * filename, openmode mode);

这里filename 是一个字符串,代表要打开的文件名,mode 是以下标志符的一个组合:

ios::in 为输入(读)而打开文件
ios::out 为输出(写)而打开文件
ios::ate 初始位置:文件尾
ios::app 所有输出附加在文件末尾
ios::trunc 如果文件已存在则先删除该文件
ios::binary 二进制方式

这些标识符可以被组合使用,中间以”或”操作符(|)间隔。例如,如果我们想要以二进制方式打开文件"example.bin" 来写入一些数据,我们可以通过以下方式调用成员函数open()来实现:

ofstream file;
file.open ("example.bin", ios::out | ios::app | ios::binary);

ofstream, ifstream 和 fstream所有这些类的成员函数open 都包含了一个默认打开文件的方式,这三个类的默认方式各不相同:

参数的默认方式
ofstream ios::out | ios::trunc
ifstream ios::in
fstream ios::in | ios::out

只有当函数被调用时没有声明方式参数的情况下,默认值才会被采用。如果函数被调用时声明了任何参数,默认值将被完全改写,而不会与调用参数组合。

由于对类ofstream, ifstream 和 fstream 的对象所进行的第一个操作通常都是打开文件,这些类都有一个构造函数可以直接调用open 函数,并拥有同样的参数。这样,我们就可以通过以下方式进行与上面同样的定义对象和打开文件的操作:

ofstream file ("example.bin", ios::out | ios::app | ios::binary);

两种打开文件的方式都是正确的。

你可以通过调用成员函数is_open()来检查一个文件是否已经被顺利的打开了:

bool is_open();

它返回一个布尔(bool)值,为真(true)代表文件已经被顺利打开,假( false )则相反。


关闭文件(Closing a file)

当文件读写操作完成之后,我们必须将文件关闭以使文件重新变为可访问的。关闭文件需要调用成员函数close(),它负责将缓存中的数据排放出来并关闭文件。它的格式很简单:

void close ();

这个函数一旦被调用,原先的流对象(stream object)就可以被用来打开其它的文件了,这个文件也就可以重新被其它的进程(process)所有访问了。

为防止流对象被销毁时还联系着打开的文件,析构函数(destructor)将会自动调用关闭函数close。


文本文件(Text mode files)

类ofstream, ifstream 和fstream 是分别从ostream, istream 和iostream 中引申而来的。这就是为什么 fstream 的对象可以使用其父类的成员来访问数据。

一般来说,我们将使用这些类与同控制台(console)交互同样的成员函数(cin 和 cout)来进行输入输出。如下面的例题所示,我们使用重载的插入操作符<<:

    // writing on a text file
#include <fiostream.h>

int main () {
ofstream examplefile ("example.txt");
if (examplefile.is_open()) {
examplefile << "This is a line.\n";
examplefile << "This is another line.\n";
examplefile.close();
}
return 0;
}
file example.txt
This is a line.
This is another line.

从文件中读入数据也可以用与 cin的使用同样的方法:

    // reading a text file
#include <iostream.h>
#include <fstream.h>
#include <stdlib.h>

int main () {
char buffer[256];
ifstream examplefile ("example.txt");
if (! examplefile.is_open())
{ cout << "Error opening file"; exit (1); }
while (! examplefile.eof() ) {
examplefile.getline (buffer,100);
cout << buffer << endl;
}
return 0;
}
This is a line.
This is another line.

上面的例子读入一个文本文件的内容,然后将它打印到屏幕上。注意我们使用了一个新的成员函数叫做eof ,它是ifstream 从类 ios 中继承过来的,当到达文件末尾时返回true 。


状态标志符的验证(Verification of state flags)

除了eof()以外,还有一些验证流的状态的成员函数(所有都返回bool型返回值):

  • bad()

    如果在读写过程中出错,返回 true 。例如:当我们要对一个不是打开为写状态的文件进行写入时,或者我们要写入的设备没有剩余空间的时候。

  • fail()

    除了与bad() 同样的情况下会返回 true 以外,加上格式错误时也返回true ,例如当想要读入一个整数,而获得了一个字母的时候。

  • eof()

    如果读文件到达文件末尾,返回true。

  • good()

    这是最通用的:如果调用以上任何一个函数返回true 的话,此函数返回 false 。

要想重置以上成员函数所检查的状态标志,你可以使用成员函数clear(),没有参数。


获得和设置流指针(get and put stream pointers)

所有输入/输出流对象(i/o streams objects)都有至少一个流指针:

  • ifstream, 类似istream, 有一个被称为get pointer的指针,指向下一个将被读取的元素。
  • ofstream, 类似 ostream, 有一个指针 put pointer ,指向写入下一个元素的位置。
  • fstream, 类似 iostream, 同时继承了get 和 put

我们可以通过使用以下成员函数来读出或配置这些指向流中读写位置的流指针:

  • tellg() 和 tellp()

    这两个成员函数不用传入参数,返回pos_type 类型的值(根据ANSI-C++ 标准) ,就是一个整数,代表当前get 流指针的位置 (用tellg) 或 put 流指针的位置(用tellp).

  • seekg() 和seekp()

    这对函数分别用来改变流指针get 和put的位置。两个函数都被重载为两种不同的原型:

    seekg ( pos_type position );
    seekp ( pos_type position );

    使用这个原型,流指针被改变为指向从文件开始计算的一个绝对位置。要求传入的参数类型与函数 tellg 和tellp 的返回值类型相同。

    seekg ( off_type offset, seekdir direction );
    seekp ( off_type offset, seekdir direction );

    使用这个原型可以指定由参数direction决定的一个具体的指针开始计算的一个位移(offset)。它可以是:

    ios::beg 从流开始位置计算的位移
    ios::cur 从流指针当前位置开始计算的位移
    ios::end 从流末尾处开始计算的位移

流指针 get 和 put 的值对文本文件(text file)和二进制文件(binary file)的计算方法都是不同的,因为文本模式的文件中某些特殊字符可能被修改。由于这个原因,建议对以文本文件模式打开的文件总是使用seekg 和 seekp的第一种原型,而且不要对tellg 或 tellp 的返回值进行修改。对二进制文件,你可以任意使用这些函数,应该不会有任何意外的行为产生。

以下例子使用这些函数来获得一个二进制文件的大小:

    // obtaining file size
#include <iostream.h>
#include <fstream.h>

const char * filename = "example.txt";

int main () {
long l,m;
ifstream file (filename, ios::in|ios::binary);
l = file.tellg();
file.seekg (0, ios::end);
m = file.tellg();
file.close();
cout << "size of " << filename;
cout << " is " << (m-l) << " bytes.\n";
return 0;
}
size of example.txt is 40 bytes.

二进制文件(Binary files)

在二进制文件中,使用<< 和>>,以及函数(如getline)来操作符输入和输出数据,没有什么实际意义,虽然它们是符合语法的。

文件流包括两个为顺序读写数据特殊设计的成员函数:write 和 read。第一个函数 (write) 是ostream 的一个成员函数,都是被ofstream所继承。而read 是istream 的一个成员函数,被ifstream 所继承。类 fstream 的对象同时拥有这两个函数。它们的原型是:

write ( char * buffer, streamsize size );
read ( char * buffer, streamsize size );

这里 buffer 是一块内存的地址,用来存储或读出数据。参数size 是一个整数值,表示要从缓存(buffer)中读出或写入的字符数。

    // reading binary file
#include <iostream>
#include <fstream.h>

const char * filename = "example.txt";

int main () {
char * buffer;
long size;
ifstream file (filename, ios::in|ios::binary|ios::ate);
size = file.tellg();
file.seekg (0, ios::beg);
buffer = new char [size];
file.read (buffer, size);
file.close();

cout << "the complete file is in a buffer";

delete[] buffer;
return 0;
}
The complete file is in a buffer

缓存和同步(Buffers and Synchronization)

当我们对文件流进行操作的时候,它们与一个streambuf 类型的缓存(buffer)联系在一起。这个缓存(buffer)实际是一块内存空间,作为流(stream)和物理文件的媒介。例如,对于一个输出流, 每次成员函数put (写一个单个字符)被调用,这个字符不是直接被写入该输出流所对应的物理文件中的,而是首先被插入到该流的缓存(buffer)中。

当缓存被排放出来(flush)时,它里面的所有数据或者被写入物理媒质中(如果是一个输出流的话),或者简单的被抹掉(如果是一个输入流的话)。这个过程称为同步(synchronization),它会在以下任一情况下发生:

  • 当文件被关闭时: 在文件被关闭之前,所有还没有被完全写出或读取的缓存都将被同步。
  • 当缓存buffer 满时:缓存Buffers 有一定的空间限制。当缓存满时,它会被自动同步。
  • 控制符明确指明:当遇到流中某些特定的控制符时,同步会发生。这些控制符包括:flush 和endl。
  • 明确调用函数sync(): 调用成员函数sync() (无参数)可以引发立即同步。这个函数返回一个int 值,等于-1 表示流没有联系的缓存或操作失败。
  • 在C++中,有一个stream这个类,所有的I/O都以这个“流”类为基础的,包括我们要认识的文件I/O,stream这个类有两个重要的运算符:

    1、插入器(<<)
    向流输出数据。比如说系统有一个默认的标准输出流(cout),一般情况下就是指的显示器,所以,cout<<"Write Stdout"<<'n';就表示把字符串"Write Stdout"和换行字符('n')输出到标准输出流。

    2、析取器(>>)
    从流中输入数据。比如说系统有一个默认的标准输入流(cin),一般情况下就是指的键盘,所以,cin>>x;就表示从标准输入流中读取一个指定类型(即变量x的类型)的数据。

    在C++中,对文件的操作是通过stream的子类fstream(file stream)来实现的,所以,要用这种方式操作文件,就必须加入头文件fstream.h。下面就把此类的文件操作过程一一道来。

    一、打开文件
    在fstream类中,有一个成员函数open(),就是用来打开文件的,其原型是:

    void open(const char* filename,int mode,int access);

    参数:

    filename: 要打开的文件名
    mode: 要打开文件的方式
    access: 打开文件的属性
    打开文件的方式在类ios(是所有流式I/O类的基类)中定义,常用的值如下:

    ios::app: 以追加的方式打开文件
    ios::ate: 文件打开后定位到文件尾,ios:app就包含有此属性
    ios::binary: 以二进制方式打开文件,缺省的方式是文本方式。两种方式的区别见前文
    ios::in: 文件以输入方式打开
    ios::out: 文件以输出方式打开
    ios::nocreate: 不建立文件,所以文件不存在时打开失败
    ios::noreplace:不覆盖文件,所以打开文件时如果文件存在失败
    ios::trunc: 如果文件存在,把文件长度设为0
    可以用“或”把以上属性连接起来,如ios::out|ios::binary

    打开文件的属性取值是:

    0:普通文件,打开访问
    1:只读文件
    2:隐含文件
    4:系统文件
    可以用“或”或者“+”把以上属性连接起来 ,如3或1|2就是以只读和隐含属性打开文件。

    例如:以二进制输入方式打开文件c:config.sys

    fstream file1;
    file1.open("c:config.sys",ios::binary|ios::in,0);

    如果open函数只有文件名一个参数,则是以读/写普通文件打开,即:

    file1.open("c:config.sys");<=>file1.open("c:config.sys",ios::in|ios::out,0);

    另外,fstream还有和open()一样的构造函数,对于上例,在定义的时侯就可以打开文件了:

    fstream file1("c:config.sys");

    特别提出的是,fstream有两个子类:ifstream(input file stream)和ofstream(outpu file stream),ifstream默认以输入方式打开文件,而ofstream默认以输出方式打开文件。

    ifstream file2("c:pdos.def");//以输入方式打开文件
    ofstream file3("c:x.123");//以输出方式打开文件

    所以,在实际应用中,根据需要的不同,选择不同的类来定义:如果想以输入方式打开,就用ifstream来定义;如果想以输出方式打开,就用ofstream来定义;如果想以输入/输出方式来打开,就用fstream来定义。

    二、关闭文件
    打开的文件使用完成后一定要关闭,fstream提供了成员函数close()来完成此操作,如:file1.close();就把file1相连的文件关闭。

    三、读写文件
    读写文件分为文本文件和二进制文件的读取,对于文本文件的读取比较简单,用插入器和析取器就可以了;而对于二进制的读取就要复杂些,下要就详细的介绍这两种方式

    1、文本文件的读写
    文本文件的读写很简单:用插入器(<<)向文件输出;用析取器(>>)从文件输入。假设file1是以输入方式打开,file2以输出打开。示例如下:

    file2<<"I Love You";//向文件写入字符串"I Love You"
    int i;
    file1>>i;//从文件输入一个整数值。

    这种方式还有一种简单的格式化能力,比如可以指定输出为16进制等等,具体的格式有以下一些

    操纵符 功能 输入/输出
    dec 格式化为十进制数值数据 输入和输出
    endl 输出一个换行符并刷新此流 输出
    ends 输出一个空字符 输出
    hex 格式化为十六进制数值数据 输入和输出
    oct 格式化为八进制数值数据 输入和输出
    setpxecision(int p) 设置浮点数的精度位数 输出

    比如要把123当作十六进制输出:file1<<hex<<123;要把3.1415926以5位精度输出:file1<<setpxecision(5)<<3.1415926。

    2、二进制文件的读写
    ①put()
    put()函数向流写入一个字符,其原型是ofstream &put(char ch),使用也比较简单,如file1.put('c');就是向流写一个字符'c'。

    ②get()
    get()函数比较灵活,有3种常用的重载形式:

    一种就是和put()对应的形式:ifstream &get(char &ch);功能是从流中读取一个字符,结果保存在引用ch中,如果到文件尾,返回空字符。如file2.get(x);表示从文件中读取一个字符,并把读取的字符保存在x中。

    另一种重载形式的原型是: int get();这种形式是从流中返回一个字符,如果到达文件尾,返回EOF,如x=file2.get();和上例功能是一样的。

    还 有一种形式的原型是:ifstream &get(char *buf,int num,char delim='n');这种形式把字符读入由 buf 指向的数组,直到读入了 num 个字符或遇到了由 delim 指定的字符,如果没使用 delim 这个参数,将使用缺省值换行符'n'。例如:

    file2.get(str1,127,'A');//从文件中读取字符到字符串str1,当遇到字符'A'或读取了127个字符时终止。

    ③读写数据块
    要读写二进制数据块,使用成员函数read()和write()成员函数,它们原型如下:

    read(unsigned char *buf,int num);
    write(const unsigned char *buf,int num);

    read() 从文件中读取 num 个字符到 buf 指向的缓存中,如果在还未读入 num 个字符时就到了文件尾,可以用成员函数 int gcount();来取得实际读取的字符数;而 write() 从buf 指向的缓存写 num 个字符到文件中,值得注意的是缓存的类型是 unsigned char *,有时可能需要类型转换。

    例:

    unsigned char str1[]="I Love You";
    int n[5];
    ifstream in("xxx.xxx");
    ofstream out("yyy.yyy");
    out.write(str1,strlen(str1));//把字符串str1全部写到yyy.yyy中
    in.read((unsigned char*)n,sizeof(n));//从xxx.xxx中读取指定个整数,注意类型转换
    in.close();out.close();

    四、检测EOF
    成员函数eof()用来检测是否到达文件尾,如果到达文件尾返回非0值,否则返回0。原型是int eof();

    例: if(in.eof())ShowMessage("已经到达文件尾!");

    五、文件定位
    和C的文件操作方式不同的是,C++ I/O系统管理两个与一个文件相联系的指针。一个是读指针,它说明输入操作在文件中的位置;另一个是写指针,它下次写操作的位置。每次执行输入或输出时, 相应的指针自动变化。所以,C++的文件定位分为读位置和写位置的定位,对应的成员函数是 seekg()和 seekp(),seekg()是设置读位置,seekp是设置写位置。它们最通用的形式如下:

    istream &seekg(streamoff offset,seek_dir origin);
    ostream &seekp(streamoff offset,seek_dir origin);

    streamoff定义于 iostream.h 中,定义有偏移量 offset 所能取得的最大值,seek_dir 表示移动的基准位置,是一个有以下值的枚举:

    ios::beg: 文件开头
    ios::cur: 文件当前位置
    ios::end: 文件结尾
    这两个函数一般用于二进制文件,因为文本文件会因为系统对字符的解释而可能与预想的值不同。

    例:

    file1.seekg(1234,ios::cur);//把文件的读指针从当前位置向后移1234个字节
    file2.seekp(1234,ios::beg);//把文件的写指针从文件开头向后移1234个字节

posted @ 2012-06-28 22:36 多彩人生 阅读(354) | 评论 (0)编辑 收藏

C/C++遍历文件夹

 finddata_t的使用

  那么到底如何查找文件呢?我们需要一个结构体和几个大家可能不太熟悉的函数。这些函数和结构体在<io.h>的头文件中,结构体为 struct _finddata_t ,函数为_findfirst、_findnext和_fineclose.具体如何使用,我会慢慢讲来~

  首先讲这个结构体吧~struct _finddata_t ,这个结构体是用来存储文件各种信息的。说实话,这个结构体的具体定义代码,我没有找到,不过还好,文档里面在_find里有比较详细的成员变量介绍。我基本上就把文档翻译过来讲吧:

  unsigned atrrib:文件属性的存储位 置。它存储一个unsigned单元,用于表示文件的属性。文件属性是用位表示的,主要有以下一些:_A_ARCH(存档)、_A_HIDDEN(隐 藏)、_A_NORMAL(正常)、_A_RDONLY(只读)、_A_SUBDIR(文件夹)、_A_SYSTEM(系统)。这些都是 在<io.h>中定义的宏,可以直接使用,而本身的意义其实是一个无符号整型(只不过这个整型应该是2的几次幂,从而保证只有一位为1,而其 他位为0)。既然是位表示,那么当一个文件有多个属性时,它往往是通过位或的方式,来得到几个属性的综合。例如只读+隐藏+系统属性,应该 为:_A_HIDDEN | _A_RDONLY |_A_SYSTEM .

  time_t time_create:这里的time_t是一个变量类型(长整型?相当于long int?),用来存储时间的,我们暂时不用理它,只要知道,这个time_create变量是用来存储文件创建时间的就可以了。

  time_t time_access:文件最后一次被访问的时间。

  time_t time_write:文件最后一次被修改的时间。

  _fsize_t size:文件的大小。这里的_fsize_t应该可以相当于unsigned整型,表示文件的字节数。

  char name[_MAX_FNAME]:文件的文件名。这里的_MAX_FNAME是一个常量宏,它在<stdlib.h>头文件中被定义,表示的是文件名的最大长度。

  以此,我们可以推测出,struct_finddata_t ,大概的定义如下:

  struct _finddata_t

  {

  unsigned attrib;

  time_t time_create;

  time_t time_access;

  time_t time_write;

  _fsize_t size;

  char name[_MAX_FNAME];

  };

  前面也说了,这个结构体是用来存储文件信息的,那么如何把一个硬盘文件的文件信息“存到”这个结构体所表示的内存空间里去呢?这就要靠_findfirst、_findnext和_fineclose三个函数的搭配使用了。

  首先还是对这三个函数一一介绍一番吧……

  long _findfirst( char *filespec, struct _finddata_t *fileinfo );

  返回值:如果查找成功的话,将返回一个long型的唯一的查找用的句柄(就是一个唯一编号)。这个句柄将在_findnext函数中被使用。若失败,则返回-1.

  参数:

  filespec:标明文件的字符串,可支持通配符。比如:*.c,则表示当前文件夹下的所有后缀为C的文件。

  fileinfo :这里就是用来存放文件信息的结构体的指针。这个结构体必须在调用此函数前声明,不过不用初始化,只要分配了内存空间就可以了。函数成功后,函数会把找到的文件的信息放入这个结构体中。

  int _findnext( long handle, struct _finddata_t *fileinfo );

  返回值:若成功返回0,否则返回-1.

  参数:

  handle:即由_findfirst函数返回回来的句柄。

  fileinfo:文件信息结构体的指针。找到文件后,函数将该文件信息放入此结构体中。

  int _findclose( long handle );

  返回值:成功返回0,失败返回-1.

  参数:

  handle :_findfirst函数返回回来的句柄。

  大家看到这里,估计都能猜到个大概了吧?先用_findfirst查找第一个文件,若成功则用返回的句柄调用_findnext函数查找其他的 文件,当查找完毕后用,用_findclose函数结束查找。恩,对,这就是正确思路。下面我们就按照这样的思路来编写一个查找C:\WINDOWS文件 夹下的所有exe可执行文件的程序。

  #include <stdio.h>

  #include <io.h>

  const char *to_search="C:\\WINDOWS\\*.exe";        //欲查找的文件,支持通配符

  int main()

  {

  long handle;                                               //用于查找的句柄

  struct _finddata_t fileinfo;                          //文件信息的结构体

  handle=_findfirst(to_search,&fileinfo);         //第一次查找

  if(-1==handle)return -1;

  printf("%s\n",fileinfo.name);                         //打印出找到的文件的文件名

  while(!_findnext(handle,&fileinfo))               //循环查找其他符合的文件,知道找不到其他的为止

  {

  printf("%s\n",fileinfo.name);

  }

  _findclose(handle);                                      //别忘了关闭句柄

  system("pause");

  return 0;

  }

  当然,这个文件的查找是在指定的路径中进行,如何遍历硬盘,在整个硬盘中查找文件呢?大家可以在网络上搜索文件递归遍历等方法,这里不再做进一步介绍。

  细心的朋友可能会注意到我在程序的末尾用了一个system函数。这个与程序本身并没有影响,和以前介绍给大家的使用getchar()函数的 作用相同,只是为了暂停一下,让我们能看到命令提示符上输出的结果而已。不过system函数本身是一个非常强大的函数。大家可以查查MSDN看看~简单 来说,它是一个C语言与操作系统的相互平台,可以在程序里通过这个函数,向操作系统传递command命令

posted @ 2012-06-28 21:37 多彩人生 阅读(732) | 评论 (0)编辑 收藏

c++ 宏

c/c++ 宏中"#"和"##"的用法
2007年05月14日 星期一 上午 10:19
一、一般用法
我们使用#把宏参数变为一个字符串,用##把两个宏参数贴合在一起.
用法:
#i nclude<cstdio>
#i nclude<climits>
using namespace std;
#define STR(s)      #s
#define CONS(a,b)   int(a##e##b)

int main()
{
     printf(STR(vck));            // 输出字符串"vck"
     printf("%d\n", CONS(2,3));   // 2e3 输出:2000
     return 0;
}

二、当宏参数是另一个宏的时候
需要注意的是凡宏定义里有用'#'或'##'的地方宏参数是不会再展开.

1, 非'#'和'##'的情况
#define TOW       (2)
#define MUL(a,b) (a*b)

printf("%d*%d=%d\n", TOW, TOW, MUL(TOW,TOW));
这行的宏会被展开为:
printf("%d*%d=%d\n", (2), (2), ((2)*(2)));
MUL里的参数TOW会被展开为(2).

2, 当有'#'或'##'的时候
#define A           (2)
#define STR(s)      #s
#define CONS(a,b)   int(a##e##b)

printf("int max: %s\n",   STR(INT_MAX));     // INT_MAX #i nclude<climits>
这行会被展开为:
printf("int max: %s\n", "INT_MAX");

printf("%s\n", CONS(A, A));                // compile error
这一行则是:
printf("%s\n", int(AeA));

INT_MAX和A都不会再被展开, 然而解决这个问题的方法很简单. 加多一层中间转换宏.
加这层宏的用意是把所有宏的参数在这层里全部展开, 那么在转换宏里的那一个宏(_STR)就能得到正确的宏参数.

#define A            (2)
#define _STR(s)      #s
#define STR(s)       _STR(s)           // 转换宏
#define _CONS(a,b)   int(a##e##b)
#define CONS(a,b)    _CONS(a,b)        // 转换宏

printf("int max: %s\n", STR(INT_MAX));           // INT_MAX,int型的最大值,为一个变量 #i nclude<climits>
输出为: int max: 0x7fffffff
STR(INT_MAX) -->   _STR(0x7fffffff) 然后再转换成字符串;

printf("%d\n", CONS(A, A));
输出为:200
CONS(A, A)   -->   _CONS((2), (2))   --> int((2)e(2))

三、'#'和'##'的一些应用特例
1、合并匿名变量名
#define   ___ANONYMOUS1(type, var, line)   type   var##line
#define   __ANONYMOUS0(type, line)   ___ANONYMOUS1(type, _anonymous, line)
#define   ANONYMOUS(type)   __ANONYMOUS0(type, __LINE__)
例:ANONYMOUS(static int);   即: static int _anonymous70;   70表示该行行号;
第一层:ANONYMOUS(static int);   -->   __ANONYMOUS0(static int, __LINE__);
第二层:                         -->   ___ANONYMOUS1(static int, _anonymous, 70);
第三层:                         -->   static int   _anonymous70;
即每次只能解开当前层的宏,所以__LINE__在第二层才能被解开;

2、填充结构
#define   FILL(a)    {a, #a}

enum IDD{OPEN, CLOSE};
typedef struct MSG{
   IDD id;
   const char * msg;
}MSG;

MSG _msg[] = {FILL(OPEN), FILL(CLOSE)};
相当于:
MSG _msg[] = {{OPEN, "OPEN"},
               {CLOSE, "CLOSE"}};

3、记录文件名
#define   _GET_FILE_NAME(f)    #f
#define   GET_FILE_NAME(f)     _GET_FILE_NAME(f)
static char   FILE_NAME[] = GET_FILE_NAME(__FILE__);

4、得到一个数值类型所对应的字符串缓冲大小
#define   _TYPE_BUF_SIZE(type)   sizeof #type
#define   TYPE_BUF_SIZE(type)    _TYPE_BUF_SIZE(type)
char   buf[TYPE_BUF_SIZE(INT_MAX)];
      -->   char   buf[_TYPE_BUF_SIZE(0x7fffffff)];
      -->   char   buf[sizeof "0x7fffffff"];
这里相当于:
char   buf[11];

//////////////////////////////////////////////////////////////////////////////////////////////////////////////

////////////////////////////////////////////////////////////////////////////////////////////////////////////////

c和c++调式 利用宏获得函数名
仅仅为了获取函数名,就在函数体中嵌入硬编码的字符串,这种方法单调乏味还易导致错误,不如看一下怎样使用新的C99特性,在程序运行时获取函数名吧。
   对象反射库、调试工具及代码分析器,经常会需要在运行时访问函数的名称,直到不久前,唯一能完成此 项任务并且可移植的方法,是手工在函数体内嵌入一个带有该函数名的硬编码字符串,不必说,这种方法非常单调无奇,并且轻易导致错误。本文将要演示怎样使用 新的C99特性,在运行时获取函数名。
  那么怎样以编程的方式从当前运行的函数中得到函数名呢?
  答案是:使用__FUNCTION__ 及相关宏。
  引出问题
  通常,在调试中最让人心烦的阶段,是不断地检查是否已调用了特定的函数。对此问题的解决方法,一般是添加一个cout或printf()——假如你使用C语言,如下所示:
void myfunc()
{
cout<<"myfunc()"<<endl;
//其他代码
}
通常在一个典型的工程中,会包含有数千个函数,要在每个函数中都加入一条这样的输出语句,无疑难过上“蜀山”啊,因此,需要有一种机制,可以自动地完成这项操作。
  获取函数名
  作为一个C++程序员,可能经常碰到 __TIME__、__FILE__、__DATE__ ,__LINE__ 这样的宏,它们会在编译时,分别转换为包含编译时间、处理的转换单元名称及当前时间的字符串。
   在最新的ISO C标准中,如大家所知的C99,加入了另一个有用的、类似宏的表达式__func__,其会报告未修饰过的(也就是未裁剪过的)、正在被访问的函数名。请 注重,__func__不是一个宏,因为预处理器对此函数一无所知;相反,它是作为一个隐式声明的常量字符数组实现的:
static const char __func__[] = "function-name";
在function-name处,为实际的函数名。为激活此特性,某些编译器需要使用特定的编译标志,请查看相应的编译器文档,以获取具体的资料。
  有了它,我们可免去大多数通过手工修改,来显示函数名的苦差事,以上的例子可如下所示进行重写:
void myfunc()
{
cout<<"__FUNCTION__"<<endl;
}
官 方C99标准为此目的定义的__func__标识符,确实值得大家关注,然而,ISO C++却不完全支持所有的C99扩展,因此,大多数的编译器提供商都使用 __FUNCTION__ 取而代之,而 __FUNCTION__ 通常是一个定义为 __func__ 的宏,之所以使用这个名字,是因为它已受到了大多数的广泛支持。
  在Visual Studio 2005中,默认情况下,此特性是激活的,但不能与/EP和/P编译选项同时使用。请注重在IDE环境中,不能识别__func__ ,而要用__FUNCTION__ 代替。
  Comeau的用户也应使用 __FUNCTION__ ,而不是 __func__ 。
  C++ BuilderX的用户则应使用稍稍不同的名字:__FUNC__ 。
  GCC 3.0及更高的版本同时支持 __func__ 和__FUNCTION__ 。
  一旦可自动获取当前函数名,你可以定义一个如下所示显示任何函数名的函数:
void show_name(const char * name)
{
cout<<name<<endl;
}
void myfunc()
{
show_name(__FUNCTION__); //输出:myfunc
}
void foo()
{
show_name(__FUNCTION__); //输出:foo
}
因为 __FUNCTION__ 会在函数大括号开始之后就立即初始化,所以,foo()及myfunc()函数可在参数列表中安全地使用它,而不用担心重载。
  签名与修饰名
   __FUNCTION__ 特性最初是为C语言设计的,然而,C++程序员也会经常需要有关他们函数的额外信息,在Visual Studio 2005中,还支持另外两种非标准的扩展特性:__FUNCDNAME__ 与 __FUNCSIG__ ,其分别转译为一个函数的修饰名与签名。函数的修饰名非常有用,例如,在你想要检查两个编译器是否共享同样的ABI时,就可派得上用场,另外,它还能帮助 你破解那些含义模糊的链接错误,甚至还可用它从一个DLL中调用另一个用C++链接的函数。在下例中,show_name()报告了函数的修饰名:
void myfunc()
{
show_name(__FUNCDNAME__); //输出:?myfunc@@YAXXZ
}
一 个函数的签名由函数名、参数列表、返回类型、内含的命名空间组成。假如它是一个成员函数,它的类名和const/volatile限定符也将是签名的 一部分。以下的代码演示了一个独立的函数与一个const成员函数签名间的不同之处,两个函数的名称、返回类型、参数完全相同:
void myfunc()
{
show_name(__FUNCSIG__); // void __cdecl myfunc(void)
}
struct S
{
void myfunc() const
{
show_name(__FUNCSIG__); //void __thiscall S::myfunc(void) const
}
};



posted @ 2012-06-28 16:10 多彩人生 阅读(355) | 评论 (0)编辑 收藏

wxWidgets系列之自定义事件实现、调用6步曲

自定义事件实现步骤有如下几步:

1、定义自定义事件id

enum CustomEventId
{
    ENUM_CUSTOMEVENT_ID_Id1=7000,
    ENUM_CUSTOMEVENT_ID_Id2,
    ENUM_CUSTOMEVENT_ID_Id3
};

 

2、申明自定义事件(.h文件中)

BEGIN_DECLARE_EVENT_TYPES()
    DECLARE_EVENT_TYPE(ENUM_CUSTOMEVENT_NAME1, ENUM_CUSTOMEVENT_ID_Id1)
    DECLARE_EVENT_TYPE(ENUM_CUSTOMEVENT_NAME2, ENUM_CUSTOMEVENT_ID_Id2)
    DECLARE_EVENT_TYPE(ENUM_CUSTOMEVENT_NAME3, ENUM_CUSTOMEVENT_ID_Id3)
END_DECLARE_EVENT_TYPES()

 

3、定义自定义事件(.cpp文件中)

DEFINE_EVENT_TYPE(ENUM_CUSTOMEVENT_NAME1)
DEFINE_EVENT_TYPE(ENUM_CUSTOMEVENT_NAME2)
DEFINE_EVENT_TYPE(ENUM_CUSTOMEVENT_NAME3)

 

4、在BEGIN_EVENT_TABLE与END_EVENT_TABLE()添加事件映射

    EVT_COMMAND(wxID_ANY, ENUM_CUSTOMEVENT_NAME1, Frame::OnSetName1)
    EVT_COMMAND(wxID_ANY, ENUM_CUSTOMEVENT_NAME2, Frame::OnSetName2)
    EVT_COMMAND(wxID_ANY, ENUM_CUSTOMEVENT_NAME3, Frame::OnSetName3)

 

5、在Frame中,申明、实现OnSetName1、OnSetName2、OnSetName3

申明:

    void  OnSetName1(wxCommandEvent& event);

    void  OnSetName2(wxCommandEvent& event);

    void  OnSetName3(wxCommandEvent& event);

实现:代码就不在此列举

6、自定义事件调用

     wxCommandEvent eventCustom(ENUM_CUSTOMEVENT_NAME1);
     wxPostEvent(this->GetParent()->GetEventHandler(), eventCustom); //子窗口

    如果是当前窗口可以写成

   wxPostEvent(this->GetEventHandler(), eventCustom);

posted @ 2012-06-25 15:49 多彩人生 阅读(740) | 评论 (0)编辑 收藏

仅列出标题
共25页: First 11 12 13 14 15 16 17 18 19 Last 

导航

统计

常用链接

留言簿(3)

随笔分类

随笔档案

搜索

最新评论

阅读排行榜

评论排行榜