青青草原综合久久大伊人导航_色综合久久天天综合_日日噜噜夜夜狠狠久久丁香五月_热久久这里只有精品

colorful

zc qq:1337220912

 

關(guān)于DBSvr啟動時加載所有數(shù)據(jù)到內(nèi)存

關(guān)于DBSvr啟動時加載所有數(shù)據(jù)到內(nèi)存
    最近在做這一塊,在網(wǎng)上討論了如何做一個高效的DBSvr, 有人提出這個想法,
    仔細(xì)想想有兩個問題,
       數(shù)據(jù)加載到內(nèi)存,以什么樣的形式存貯,像DB里的表一樣,還是按游戲邏輯來存,
      如果像表一樣,那實現(xiàn)一個像DBMS一樣高效的查表功能應(yīng)該是個問題。
      如果按游戲邏輯來存, 那DBSvr又會跟游戲邏輯層扯上太多聯(lián)系。
    
看來,讀所有數(shù)據(jù)到內(nèi)存是不現(xiàn)實的。不知道各個有什么看法,請多多指教

posted @ 2012-07-13 12:13 多彩人生 閱讀(245) | 評論 (0)編輯 收藏

游戲服務(wù)器架構(gòu)設(shè)計中的一些思考

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

 

1、 游戲世界由很多個游戲?qū)ο蠼M成(游戲角色、物品、NPC、技能等);

 

2、 一個游戲?qū)ο蟮挠行?shù)據(jù)主要存放在客戶端、游戲服務(wù)器和持久性數(shù)據(jù)庫中;

 

3、 游戲?qū)ο蟮奶幚砜蓜澐譃榕c位置有關(guān)的和與位置無關(guān)的,如公會處理、物品處理等主要行為可以看作是與位置無關(guān)的處理,而NPC(AI)、戰(zhàn)斗、移動這類的主要行為可以看成是與位置有關(guān)的。

 

4、 從客戶端的角度來看,游戲行為可分為四類動作:

a)         來自服務(wù)器端的動作,如另外一個玩家跳起來。

b)        本地動作。僅僅發(fā)生在本地客戶端的動作,不需要與服務(wù)器端或其他客戶端通訊。

c)         先執(zhí)行后驗證的可撤銷的動作。客戶端先執(zhí)行,再提交服務(wù)器端驗證,驗證不成功通知客戶端將執(zhí)行的動作撤銷。比如玩家控制的游戲角色執(zhí)行移動處理。

d)        嚴(yán)格服務(wù)器端驗證的動作。客戶端執(zhí)行動作前必須經(jīng)過服務(wù)器端驗證后才能執(zhí)行。如交易行為、攻擊其他玩家/NPC。

 

5、 客戶端和服務(wù)器,服務(wù)器進(jìn)程之間的相互的通信從邏輯上看就是就是向RemoteObject 發(fā)起的遠(yuǎn)程過程調(diào)用(RPC),RPC主要有兩種類型:

a)         通知(Notify)。只通知對方,而不關(guān)心和需要對方返回結(jié)果。

b)        請求(Request)。向?qū)Ψ桨l(fā)起請求,對方處理請求后返回結(jié)果,發(fā)起請求和返回結(jié)果這個過程可以是同步或異步。游戲服務(wù)器中絕大部分RPC請求都是異步的。

 

6、 響應(yīng)延遲主要是由于網(wǎng)絡(luò)帶寬和服務(wù)器處理效率引起的。應(yīng)盡可能的通過一些技巧來隱藏和減少玩家的響應(yīng)延遲。但不是所有的最新消息都能立刻發(fā)送出去(或接收處理到),因此,要在服務(wù)器端采用優(yōu)先隊列來減少重要消息的響應(yīng)時間。延遲也會由客戶端產(chǎn)生,如收到消息后的對消息的處理速度。

 

 

7、 服務(wù)器負(fù)載,除了升級硬件設(shè)備外,可以通過一些方式來提高服務(wù)器負(fù)載。

 

a)         保證足夠的網(wǎng)絡(luò)帶寬。

b)        分布式運(yùn)算,合理的集群式架構(gòu)。

c)         游戲策劃從游戲內(nèi)容上避免設(shè)計高并發(fā),高消耗的游戲行為。

 

 

 

8、 從服務(wù)器的可伸縮性,穩(wěn)定性和高效率方面來考慮,要試著避免所有事情都在一個地方處理,盡量讓系統(tǒng)分布式運(yùn)行,但是過多的劃分功能到不同的進(jìn)程/機(jī)器上運(yùn)行,又會帶來數(shù)據(jù)的大量同步的問題。因此可以將游戲?qū)ο蟮奶幚碇饕獎澐譃榕c位置無關(guān)和有關(guān)兩種。像公會,玩家信息,物品信息,組隊,拍賣等等這類與位置無關(guān)的但是占用CPU資源較少的處理可以盡可能的放在一個進(jìn)程中,避免進(jìn)程間對象同步,而像NPC,尋路,AOI運(yùn)算,戰(zhàn)斗處理等與位置有關(guān)的,處理過程中特別關(guān)心對象坐標(biāo)位置的、運(yùn)算量特別大的,但是進(jìn)程間對象同步較少的,都可以單獨劃分成多個進(jìn)程。

 

每類進(jìn)程服務(wù)的功能盡量單一。負(fù)責(zé)路由的就盡量只負(fù)責(zé)網(wǎng)絡(luò)包轉(zhuǎn)發(fā),而不再承擔(dān)其他繁重的任務(wù),負(fù)責(zé)游戲處理的就盡量讓網(wǎng)絡(luò)包流向簡單。


大規(guī)模應(yīng)用服務(wù)器(不只包含游戲服務(wù)器)是否成功主要看架構(gòu)師對問題的解構(gòu)能力。
問題是什么?
問題的邊界在哪里?
功能粒度劃分多細(xì)?
解決這些問題都需要經(jīng)驗。

posted @ 2012-07-12 16:06 多彩人生 閱讀(169) | 評論 (0)編輯 收藏

stl之map erase方法的正確使用

STL的map表里有一個erase方法用來從一個map中刪除掉指令的節(jié)點
eg:
map<string,string> mapTest;
typedef map<string,string>::iterator ITER;

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

像上面這樣只是刪除單個節(jié)點,map的形為不會出現(xiàn)任務(wù)問題,
但是當(dāng)在一個循環(huán)里用的時候,往往會被誤用,那是因為使用者沒有正確理解iterator的概念.
像下面這樣的一個例子就是錯誤的寫法,
eg.
for(ITER iter=mapTest.begin();iter!=mapTest.end();++iter)
{
cout<<iter->first<<":"<<iter->second<<endl;
mapTest.erase(iter);
}

這是一種錯誤的寫法,會導(dǎo)致程序行為不可知.究其原因是map 是關(guān)聯(lián)容器,對于關(guān)聯(lián)容器來說,如果某一個元素已經(jīng)被刪除,那么其對應(yīng)的迭代器就失效了,不應(yīng)該再被使用;否則會導(dǎo)致程序無定義的行為。
可以用以下方法解決這問題:
正確的寫法
1.使用刪除之前的迭代器定位下一個元素。STL建議的使用方式
for(ITER iter=mapTest.begin();iter!=mapTest.end();)
{
cout<<iter->first<<":"<<iter->second<<endl;
mapTest.erase(iter++);
}

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

注意:

map的earse應(yīng)注意:
map這種容器的下邊訪問和Vector等容器的下標(biāo)訪問有本質(zhì)的區(qū)別。
對于Vector容器,用aVector[i]訪問第i個元素時,如果元素不存在,容器不會增加元素,
而對于map,用aMap[key]
訪問鍵key對應(yīng)的對象時,如果該鍵對應(yīng)的對象存在,則返回該對象(這和Vector一樣),但是,當(dāng)鍵值為key的元素不存在時,容器會自動的增加一個 pair,鍵為key,而值則為一個容器定義時指定的類型并默認(rèn)初始化(即,如果該類型為基本類型,則初始化為0,比如本例中,aMap[1]的使用會產(chǎn) 生一個pair,<1,NULL>,若該類型為類類型,則調(diào)用默認(rèn)構(gòu)造函數(shù)初始化之)

顯然,本例中,aMap[1]為NULL,后面的erase()不會執(zhí)行,使得后面的
插入語句aMap.insert(1,new A())鍵值沖突

eg:如下代碼會導(dǎo)致錯誤

#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 多彩人生 閱讀(254) | 評論 (0)編輯 收藏

c++中的.hpp文件

  hpp,其實質(zhì)就是將.cpp的實現(xiàn)代碼混入.h頭文件當(dāng)中,定義與實現(xiàn)都包含在同一文件,則該類的調(diào)用者只需要include該cpp文件即可,無需再 將cpp加入到project中進(jìn)行編譯。而實現(xiàn)代碼將直接編譯到調(diào)用者的obj文件中,不再生成單獨的obj,采用hpp將大幅度減少調(diào)用 project中的cpp文件數(shù)與編譯次數(shù),也不用再發(fā)布煩人的lib與dll,因此非常適合用來編寫公用的開源庫。

1、是Header Plus Plus 的簡寫。

2、與*.h類似,hpp是C++程序頭文件 。

3、是VCL 專用的頭文件,已預(yù)編譯。

4、是一般模板類的頭文件。

5、一般來說,*.h里面只有聲明,沒有實現(xiàn),而*.hpp里聲明實現(xiàn)都有,后者可以減 少.cpp的數(shù)量。

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

7、*.hpp要注意的問題有:

      a)不可包含全局對象和全局函數(shù)

     由于hpp本質(zhì)上是作為.h被調(diào)用者include,所以當(dāng)hpp文件中存在全局對象或者全局函數(shù),而該hpp被多個

    調(diào)用者include時,將在鏈接時導(dǎo)致符號重定義錯誤。要避免這種情況,需要去除全局對象,將全局函數(shù)封

    裝為類的靜態(tài)方法。

      b)類之間不可循環(huán)調(diào)用

      在.h和.cpp的場景中,當(dāng)兩個類或者多個類之間有循環(huán)調(diào)用關(guān)系時,只要預(yù)先在頭文件做被調(diào)用類的聲明

    即可,如下:

    class B;

    class A{

    public:

         void someMethod(B b);

    };

    class B{

    public:

         void someMethod(A a);

    };

    在hpp場景中,由于定義與實現(xiàn)都已經(jīng)存在于一個文件,調(diào)用者必需明確知道被調(diào)用者的所有定義,而不能等到cpp

    中去編譯。因此hpp中必須整理類之間調(diào)用關(guān)系,不可產(chǎn)生循環(huán)調(diào)用。同理,對于當(dāng)兩個類A和B分別定義在各自的

    hpp文件中,形如以下的循環(huán)調(diào)用也將導(dǎo)致編譯錯誤:

    //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)不可使用靜態(tài)成員

      靜態(tài)成員的使用限制在于如果類含有靜態(tài)成員,則在hpp中必需加入靜態(tài)成員初始化代碼,當(dāng)該hpp被多個文檔include時,將產(chǎn)生符號重定義錯誤。唯 一的例外是const static整型成員,因為在vs2003中,該類型允許在定義時初始化,如:

    class A{

     public:

       const static int intValue = 123;

     };

    由于靜態(tài)成員的使用是很常見的場景,無法強(qiáng)制清除,因此可以考慮以下幾種方式(以下示例均為同一類中方法)

   一、類中僅有一個靜態(tài)成員時,且僅有一個調(diào)用者時,可以通過局域靜態(tài)變量模擬

    //方法模擬獲取靜態(tài)成員

    someType getMember()

    {

       static someType value(xxx);//作用域內(nèi)靜態(tài)變量

       return value;

    }

   二、.類中有多個方法需要調(diào)用靜態(tài)成員,而且可能存在多個靜態(tài)成員時,可以將每個靜態(tài)成員封裝一個模擬方法,供其他方法調(diào)用。

    someType getMemberA()

    {

       static someType value(xxx);//作用域內(nèi)靜態(tài)變量

       return value;

    }

    someType getMemberB()

    {

       static someType value(xxx);//作用域內(nèi)靜態(tài)變量

       return value;

    }

   void accessMemberA()

    {

       someType member = getMemberA();//獲取靜態(tài)成員

     };

    //獲取兩個靜態(tài)成員

    void accessStaticMember()

    {

       someType a = getMemberA();//獲取靜態(tài)成員

       someType b = getMemberB();

     };

    三、第二種方法對于大部分情況是通用的,但是當(dāng)所需的靜態(tài)成員過多時,編寫封裝方法的工作量將非常

    巨大,在此種情況下,建議使用Singleton模式,將被調(diào)用類定義成普通類,然后使用Singleton將其變?yōu)?/p>

   全局唯一的對象進(jìn)行調(diào)用。

     如原h(huán)+cpp下的定義如下:

     class A{

     public:

        type getMember(){

           return member;

        }

        static type member;//靜態(tài)成員

    }

    采用singleton方式,實現(xiàn)代碼可能如下(singleton實現(xiàn)請自行查閱相關(guān)文檔)

    //實際實現(xiàn)類

     class Aprovider{

     public:

        type getMember(){

           return member;

        }

       type member;//變?yōu)槠胀ǔ蓡T

    }

    //提供給調(diào)用者的接口類

     class A{

     public:

        type getMember(){

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

        }

    }

posted @ 2012-07-03 17:47 多彩人生 閱讀(334) | 評論 (0)編輯 收藏

剖析為什么在多核多線程程序中要慎用volatile關(guān)鍵字?

這篇文章詳細(xì)剖析了為什么在多核時代進(jìn)行多線程編程時需要慎用volatile關(guān)鍵字。

主要內(nèi)容有:
1. C/C++中的volatile關(guān)鍵字
2. Visual Studio對C/C++中volatile關(guān)鍵字的擴(kuò)展
3. Java/.NET中的volatile關(guān)鍵字
4. Memory Model(內(nèi)存模型)
5. Volatile使用建議

1. C/C++中的volatile關(guān)鍵字

1.1 傳統(tǒng)用途

C/C++作為系統(tǒng)級語言,它們與硬件的聯(lián)系是很緊密的。volatile的意思是“易變的”,這個關(guān)鍵字最早就是為了針對那些“異常”的內(nèi)存操作 而準(zhǔn)備的。它的效果是讓編譯器不要對這個變量的讀寫操作做任何優(yōu)化,每次讀的時候都直接去該變量的內(nèi)存地址中去讀,每次寫的時候都直接寫到該變量的內(nèi)存地 址中去,即不做任何緩存優(yōu)化。它經(jīng)常用在需要處理中斷的嵌入式系統(tǒng)中,其典型的應(yīng)用有下面幾種:

a. 避免用通用寄存器對內(nèi)存讀寫的優(yōu)化。編譯器常做的一種優(yōu)化就是:把常用變量的頻繁讀寫弄到通用寄存器中,最后不用的時候再存回內(nèi)存中。但是如果某個內(nèi)存地址中的值是由片外決定的(例如另一個線程或是另一個設(shè)備可能更改它),那就需要volatile關(guān)鍵字了。(感謝Kenny老師指正)
b. 硬件寄存器可能被其他設(shè)備改變的情況。例如一個嵌入式板子上的某個寄存器直接與一個測試儀器連在一起,這樣在這個寄存器的值隨時可能被那個測試儀器更改。 在這種情況下如果把該值設(shè)為volatile屬性的,那么編譯器就會每次都直接從內(nèi)存中去取這個值的最新值,而不是自作聰明的把這個值保留在緩存中而導(dǎo)致 讀不到最新的那個被其他設(shè)備寫入的新值。
c. 同一個物理內(nèi)存地址M有兩個不同的內(nèi)存地址的情況。例如兩個程序同時對同一個物理地址進(jìn)行讀寫,那么編譯器就不能假設(shè)這個地址只會有一個程序訪問而做緩存優(yōu)化,所以程序員在這種情況下也需要把它定義為volatile的。

1.2 多線程程序中的錯誤用法

看到這里,很多朋友自然會想到:恩,那么如果是兩個線程需要同時訪問一個共享變量,為了讓其中兩個線程每次都能讀到這個變量的最新值,我們就把它定 義為volatile的就好了嘛!我想這個就是多線程程序中volatile之所以引起那么多爭議的最大原因。可惜的是,這個想法是錯誤的。

舉例來說,想用volatile變量來做同步(例如一個flag)?錯!為什么?很簡單,雖然volatile意味著每次讀和寫都是直接去內(nèi)存地址中去操作,但是volatile在C/C++現(xiàn)有標(biāo)準(zhǔn)中即不能保證原子性(Atomicity)也不能保證順序性(Ordering),所以幾乎所有試圖用volatile來進(jìn)行多線程同步的方案都是錯的。我之前一篇文章介紹了Sequential Consistency模型(后 面簡稱SC),它其實就是我們印象中多線程程序應(yīng)該有的執(zhí)行順序。但是,SC最大的問題是性能太低了,因為CPU/編譯器完全沒有必要嚴(yán)格按代碼規(guī)定的順 序(program order)來執(zhí)行每一條指令。學(xué)過體系結(jié)構(gòu)的同學(xué)應(yīng)該知道不管是編譯器也好CPU也好,他們最擅長做的事情就是幫你做亂序優(yōu)化。在串行時代這些亂序優(yōu)化 對程序員來說都是透明的,封裝好了的,你不用關(guān)心它們到底給你亂序成啥樣了,因為它們會保證優(yōu)化后的程序的運(yùn)行結(jié)果跟你寫程序時預(yù)期的結(jié)果是一模一樣的。 但是進(jìn)入多核時代之后,CPU和編譯器還會繼續(xù)做那些串行時代的優(yōu)化,更重要的是這些優(yōu)化還會打破你多線程程序的SC模型語義,從而使得多線程程序的實際 運(yùn)行結(jié)果與我們所期待的運(yùn)行結(jié)果不一致!

拿X86來說,它的多核內(nèi)存模型沒有嚴(yán)格執(zhí)行SC,即屬于weak ordering(或者叫relax ordering?)。它唯一允許的亂序優(yōu)化是可以把對不同地址的load操作提到store之前去(即把store x->load y亂序優(yōu)化成load y -> store x)。而store x -> store y、load x -> load y,以及store x -> load y不允許交換執(zhí)行順序。在X86這樣的內(nèi)存模型下,volatile關(guān)鍵字根本就不能保證對不同volatile變量x和y的store x -> load y的操作不會被CPU亂序優(yōu)化成load y -> store x。

而對多線程讀寫操作的原子性來說,諸如volatile x=1這樣的寫操作的原子性其實是由X86硬件保證的,跟volatile沒有任何關(guān)系。事實上,volatile根本不能保證對沒有內(nèi)存對齊的變量(或者超出機(jī)器字長的變量)的讀寫操作的原子性。

為了有個更直觀的理解,我們來看看CPU的亂序優(yōu)化是如何讓volatile在多線程程序中顯得如此無力的。下面這個著名的Dekker算法是想用 flag1/2和turn來實現(xiàn)兩個線程情況下的臨界區(qū)互斥訪問。這個算法關(guān)鍵就在于對flag1/2和turn的讀操作(load)是在其寫操作 (store)之后的,因此這個多線程算法能保證dekker1和dekker2中對gSharedCounter++的操作是互斥的,即等于是把 gSharedCounter++放到臨界區(qū)里去了。但是,多核X86可能會對這個store->load操作做亂序優(yōu)化,例如dekker1中對 flag2的讀操作可能會被提到對flag1和turn的寫操作之前,這樣就會最終導(dǎo)致臨界區(qū)的互斥訪問失效,而gSharedCounter++也會因 此產(chǎn)生data race從而出現(xiàn)錯誤的計算結(jié)果。那么為什么多核CPU會對多線程程序做這樣的亂序優(yōu)化呢?因為從單線程的視角來看flag2和flag1、turn是沒 有依賴關(guān)系的,所以CPU當(dāng)然可以對他們進(jìn)行亂序優(yōu)化以便充分利用好CPU里面的流水線(想了解更多細(xì)節(jié)請參考計算機(jī)體系結(jié)構(gòu)相關(guān)書籍)。這樣的優(yōu)化雖然 從單線程角度來講沒有錯,但是它卻違反了我們設(shè)計這個多線程算法時所期望的那個多線程語義。(想要解決這個bug就需要自己手動添加memory barrier,或者干脆別去實現(xiàn)這樣的算法,而是使用類似pthread_mutex_lock這樣的庫函數(shù),后面我會再講到這點)

當(dāng)然,對不同的CPU來說他們的內(nèi)存模型是不同的。比如說,如果這個程序是在單核上以多線程的方式執(zhí)行那么它肯定不會出錯,因為單核CPU的內(nèi)存模 型是符合SC的。而在例如PowerPC,ARM之類的架構(gòu)上運(yùn)行結(jié)果到底如何就得去翻它們的硬件手冊中內(nèi)存模型是怎么定義的了。

2. Visual Studio對C/C++中volatile關(guān)鍵字的擴(kuò)展

雖然C/C++中的volatile關(guān)鍵字沒有對ordering做任何保證,但是微軟從Visual Studio 2005開始就對volatile關(guān)鍵字添加了同步語義(保證ordering),即:對volatile變量的讀操作具有acquire語義,對 volatile變量的寫操作具有release語義。Acquire和Release語義是來自data-race-free模型的概念。為了理解這個 acquire語義和release語義有什么作用,我們來看看MSDN中的一個例子

例子中的 while (Sentinel) Sleep(0); // volatile spin lock 是對volatile變量的讀操作,它具有acquire語義,acquire語義的隱義是當(dāng)前線程在對sentinel的這個讀操作之后的所有的對全局 變量的訪問都必須在該操作之后執(zhí)行;同理,例子中的Sentinel = false; // exit critical section 是對volatile變量的寫操作,它具有release語義,release語義的隱義是當(dāng)前線程在對sentinel這個寫操作之前的所有對全局變量 的訪問都必須在該操作之前執(zhí)行完畢。所以ThreadFunc1()讀CriticalData時必定已經(jīng)在ThreadFunc2()執(zhí)行完 CriticalData++之后,即CriticalData最后輸出的值必定為1。建議大家用紙畫一下acquire/release來加深理解。一個比較形象的解釋就是把a(bǔ)cquire當(dāng)成lock,把release當(dāng)成unlock,它倆組成了一個臨界區(qū),所有臨界區(qū)外面的操作都只能往這個里面移,但是臨界區(qū)里面的操作都不能往外移,簡單吧?

其實這個程序就相當(dāng)于用volatile變量的acquire和release語義實現(xiàn)了一個臨界區(qū),在臨界區(qū)內(nèi)部的代碼就是 Sleep(2000); CriticalData++; 或者更貼切點也可以看成是一對pthread_cond_wait和pthread_cond_signal。

這個volatile的acquire和release語義是VS自己的擴(kuò)展,C/C++標(biāo)準(zhǔn)里是沒有的,所以同樣的代碼用gcc編譯執(zhí)行結(jié)果就可 能是錯的,因為編譯器/CPU可能做違反正確性的亂序優(yōu)化。Acquire和release語義本質(zhì)上就是為了保證程序執(zhí)行時memory order的正確性。但是,雖然這個VS擴(kuò)展使得volatile變量能保證ordering,它還是不能保證對volatile變量讀寫的原子性。事 實上,如果我們的程序是跑在X86上面的話,內(nèi)存對齊了的變量的讀寫的原子性是由硬件保證的,跟volatile沒有任何關(guān)系。而像volatile g_nCnt++這樣的語句本身就不是原子操作,想要保證這個操作是原子的,就必須使用帶LOCK語義的++操作,具體請看我這篇文章

另外,VS生成的volatile變量的匯編代碼是否真的調(diào)用了memory barrier也得看具體的硬件平臺,例如x86上就不需要使用memory barrier也能保證acquire和release語義,因為X86硬件本身就有比較強(qiáng)的memory模型了,但是Itanium上面VS就會生成帶 memory barrier的匯編代碼。具體可以參考這篇

但是,雖然VS對volatile關(guān)鍵字加入了acquire/release語義,有一種情況還是會出錯,即我們之前看到的dekker算法的例子。這 個其實蠻好理解的,因為讀操作的acquire語義不允許在其之后的操作往前移,但是允許在其之前的操作往后移;同理,寫操作的release語義允許在 其之后的操作往前移,但是不允許在其之前的操作往后移;這樣的話對一個volatile變量的讀操作(acquire)當(dāng)然可以放到對另一個 volatile變量的寫操作(release)之前了!Bug就是這樣產(chǎn)生的!下面這個程序大家拿Visual Studio跑一下就會發(fā)現(xiàn)bug了(我試了VS2008和VS2010,都有這個bug)。多線程編程復(fù)雜吧?希望大家還沒被弄暈,要是暈了的話也很正 常,仔仔細(xì)細(xì)重新再看一遍吧:)

想解決這個Bug也很簡單,直接在dekker1和dekker2中對flag1/flag2/turn賦值操作之后都分別加入full memory barrier就可以了,即保證load一定是在store之后執(zhí)行即可。具體的我就不詳述了。

3. Java/.NET中的volatile關(guān)鍵字

3.1 多線程語義

Java和.NET分別有JVM和CLR這樣的虛擬機(jī),保證多線程的語義就容易多了。說簡單點,Java和.NET中的volatile關(guān)鍵字也是限制虛擬機(jī)做優(yōu)化,都具有acquire和release語義,而且由虛擬機(jī)直接保證了對volatile變量讀寫操作的原子性。 (volatile 只保證可見性,不保證原子性。java中,對volatile修飾的long和double的讀寫就不是原子的 (http://java.sun.com/docs/books/jvms/second_edition/html /Threads.doc.html#22244),除此之外的基本類型和引用類型都是原子的。– 多謝liuchangit指正) 這 里需要注意的一點是,Java和.NET里面的volatile沒有對應(yīng)于我們最開始提到的C/C++中對“異常操作”用volatile修飾的傳統(tǒng)用 法。原因很簡單,Java和.NET的虛擬機(jī)對安全性的要求比C/C++高多了,它們才不允許不安全的“異常”訪問存在呢。

而且像JVM/.NET這樣的程序可移植性都非常好。雖然現(xiàn)在C++1x正在把多線程模型添加到標(biāo)準(zhǔn)中去,但是因為C++本身的性質(zhì)導(dǎo)致它的硬件平 臺依賴性很高,可移植性不是特別好,所以在移植C/C++多線程程序時理解硬件平臺的內(nèi)存模型是非常重要的一件事情,它直接決定你這個程序是否會正確執(zhí) 行。

至于Java和.NET中是否也存在類似VS 2005那樣的bug我沒時間去測試,道理其實是相同的,真有需要的同學(xué)自己應(yīng)該能測出來。好像這篇InfoQ的文章中顯示Java運(yùn)行這個dekker算法沒有問題,因為JVM給它添加了mfence。另一個臭名昭著的例子就應(yīng)該是Double-Checked Locking了。

3.2 volatile int與AtomicInteger區(qū)別

Java和.NET中這兩者還是有些區(qū)別的,主要就是后者提供了類似incrementAndGet()這樣的方法可以直接調(diào)用(保證了原子性), 而如果是volatile x進(jìn)行++操作則不是原子的。increaseAndGet()的實現(xiàn)調(diào)用了類似CAS這樣的原子指令,所以能保證原子性,同時又不會像使用 synchronized關(guān)鍵字一樣損失很多性能,用來做全局計數(shù)器非常合適。

4. Memory Model(內(nèi)存模型)

說了這么多,還是順帶介紹一下Memory Model吧。就像前面說的,CPU硬件有它自己的內(nèi)存模型,不同的編程語言也有它自己的內(nèi)存模型。如果用一句話來介紹什么是內(nèi)存模型,我會說它就是程序 員,編程語言和硬件之間的一個契約,它保證了共享的內(nèi)存地址里的值在需要的時候是可見的。下次我會專門詳細(xì)寫一篇關(guān)于它的內(nèi)容。它最大的作用是取得可編程 性與性能優(yōu)化之間的一個平衡。

5. volatile使用建議

總的來說,volatile關(guān)鍵字有兩種用途:一個是ISO C/C++中用來處理“異常”內(nèi)存行為(此用途只保證不讓編譯器做任何優(yōu)化,對多核CPU是否會進(jìn)行亂序優(yōu)化沒有任何約束力),另一種是在Java /.NET(包括Visual Studio添加的擴(kuò)展)中用來實現(xiàn)高性能并行算法(此種用途通過使用memory barrier保證了CPU/編譯器的ordering,以及通過JVM或者CLR保證了對該volatile變量讀寫操作的原子性)。

一句話,volatile對多線程編程是非常危險的,使用的時候千萬要小心你的代碼在多核上到底是不是按你所想的方式執(zhí)行的,特別是對現(xiàn)在暫時還沒 有引入內(nèi)存模型的C/C++程序更是如此。安全起見,大家還是用Pthreads,Java.util.concurrent,TBB等并行庫提供的 lock/spinlock,conditional variable, barrier, Atomic Variable之類的同步方法來干活的好,因為它們的內(nèi)部實現(xiàn)都調(diào)用了相應(yīng)的memory barrier來保證memory ordering,你只要保證你的多線程程序沒有data race,那么它們就能幫你保證你的程序是正確的(是的,Pthreads庫也是有它自己的內(nèi)存模型的,只不過它的內(nèi)存模型還些缺點,所以把多線程內(nèi)存模 型直接集成到C/C++中是更好的辦法,也是將來的趨勢,但是C++1x中將不會像Java/.NET一樣給volatile關(guān)鍵字添加acquire和 release語義,而是轉(zhuǎn)而提供另一種具有同步語義的atomic variables,此為后話)。如果你想實現(xiàn)更高性能的lock free算法,或是使用volatile來進(jìn)行同步,那么你就需要先把CPU和編程語言的memory model搞清楚,然后再時刻注意Atomicity和Ordering是否被保證了。(注 意,用沒有acquire/release語義的volatile變量來進(jìn)行同步是錯誤的,但是你仍然可以在C/C++中用volatile來修飾一個不 是用來做同步(例如一個event flag)而只是被不同線程讀寫的共享變量,只不過它的新值什么時候能被另一個線程讀到是沒有保證的,需要你自己做相應(yīng)的處理)

Herb Sutter 在他的那篇volatile vs. volatile中對這兩種用法做了很仔細(xì)的區(qū)分,我把其中兩張表格鏈接貼過來供大家參考:

volatile的兩種用途
volatile兩種用途的異同

最后附上《Java Concurrency in Practice》3.1.4節(jié)中對Java語言的volatile關(guān)鍵字的使用建議(不要被英語嚇到,這些內(nèi)容確實對你有用,而且還能順便幫練練英語,哈哈):

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節(jié)
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. 內(nèi)存屏障什么的
9. The memory model of x86
10. VC 下 volatile 變量能否建立 Memory Barrier 或并發(fā)鎖
11. Sayonara volatile(Concurrent Programming on Windows作者的文章 跟我觀點幾乎一致)
12. Java 理論與實踐: 正確使用 Volatile 變量
13. Java中的Volatile關(guān)鍵字

 

轉(zhuǎn)自:http://www.parallellabs.com/2010/12/04/why-should-we-be-care-of-volatile-keyword-in-multithreaded-applications/

posted @ 2012-07-02 17:03 多彩人生 閱讀(466) | 評論 (0)編輯 收藏

關(guān)于c++異常 和錯誤

以前寫程序一般是通過返回錯誤代碼或者設(shè)置錯誤標(biāo)志位為實現(xiàn),但是這里有個問題,就是你不能保證用戶會去檢驗這個返回代碼或者錯誤標(biāo)志位,這樣的話程序出 錯了還繼續(xù)運(yùn)行,最終是離出錯的地方越來越遠(yuǎn)。而異常其實就是一個錯誤信息,如果有異常,而該異常沒有被任何程序捕捉的話,程序就會中斷。可以更好的讓客 戶診斷錯誤。

我想以上只不過是使用異常的一個原因。應(yīng)該還有其他吧,歡迎ding   。
///////////////////////////////////////////////////////////////////////////////////////
異常有時并不是由于你的程序的問題引起的,如用new   申請一個內(nèi)存塊失敗時,數(shù)據(jù)庫操作失敗時,就會產(chǎn)生一個異常,異常往往是程序的執(zhí)行過程中不可預(yù)料的。如果不對產(chǎn)生的異常進(jìn)行處理,程序往往崩潰,會使軟件顯得很脆弱。

錯誤而言,不管是語法錯誤,邏輯錯誤都是可以通過檢查發(fā)現(xiàn)的。
/////////////////////////////////////////////////////////////////////////////////////////////

posted @ 2012-06-30 12:01 多彩人生 閱讀(263) | 評論 (0)編輯 收藏

c++文件操作

C++ 通過以下幾個類支持文件的輸入輸出:

  • ofstream: 寫操作(輸出)的文件類 (由ostream引申而來)
  • ifstream: 讀操作(輸入)的文件類(由istream引申而來)
  • fstream: 可同時讀寫操作的文件類 (由iostream引申而來)

打開文件(Open a file)

對這些類的一個對象所做的第一個操作通常就是將它和一個真正的文件聯(lián)系起來,也就是說打開一個文件。被打開的文件在程序中由一個流對象(stream object)來表示 (這些類的一個實例) ,而對這個流對象所做的任何輸入輸出操作實際就是對該文件所做的操作。

要通過一個流對象打開一個文件,我們使用它的成員函數(shù)open():

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

這里filename 是一個字符串,代表要打開的文件名,mode 是以下標(biāo)志符的一個組合:

ios::in 為輸入(讀)而打開文件
ios::out 為輸出(寫)而打開文件
ios::ate 初始位置:文件尾
ios::app 所有輸出附加在文件末尾
ios::trunc 如果文件已存在則先刪除該文件
ios::binary 二進(jìn)制方式

這些標(biāo)識符可以被組合使用,中間以”或”操作符(|)間隔。例如,如果我們想要以二進(jìn)制方式打開文件"example.bin" 來寫入一些數(shù)據(jù),我們可以通過以下方式調(diào)用成員函數(shù)open()來實現(xiàn):

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

ofstream, ifstream 和 fstream所有這些類的成員函數(shù)open 都包含了一個默認(rèn)打開文件的方式,這三個類的默認(rèn)方式各不相同:

參數(shù)的默認(rèn)方式
ofstream ios::out | ios::trunc
ifstream ios::in
fstream ios::in | ios::out

只有當(dāng)函數(shù)被調(diào)用時沒有聲明方式參數(shù)的情況下,默認(rèn)值才會被采用。如果函數(shù)被調(diào)用時聲明了任何參數(shù),默認(rèn)值將被完全改寫,而不會與調(diào)用參數(shù)組合。

由于對類ofstream, ifstream 和 fstream 的對象所進(jìn)行的第一個操作通常都是打開文件,這些類都有一個構(gòu)造函數(shù)可以直接調(diào)用open 函數(shù),并擁有同樣的參數(shù)。這樣,我們就可以通過以下方式進(jìn)行與上面同樣的定義對象和打開文件的操作:

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

兩種打開文件的方式都是正確的。

你可以通過調(diào)用成員函數(shù)is_open()來檢查一個文件是否已經(jīng)被順利的打開了:

bool is_open();

它返回一個布爾(bool)值,為真(true)代表文件已經(jīng)被順利打開,假( false )則相反。


關(guān)閉文件(Closing a file)

當(dāng)文件讀寫操作完成之后,我們必須將文件關(guān)閉以使文件重新變?yōu)榭稍L問的。關(guān)閉文件需要調(diào)用成員函數(shù)close(),它負(fù)責(zé)將緩存中的數(shù)據(jù)排放出來并關(guān)閉文件。它的格式很簡單:

void close ();

這個函數(shù)一旦被調(diào)用,原先的流對象(stream object)就可以被用來打開其它的文件了,這個文件也就可以重新被其它的進(jìn)程(process)所有訪問了。

為防止流對象被銷毀時還聯(lián)系著打開的文件,析構(gòu)函數(shù)(destructor)將會自動調(diào)用關(guān)閉函數(shù)close。


文本文件(Text mode files)

類ofstream, ifstream 和fstream 是分別從ostream, istream 和iostream 中引申而來的。這就是為什么 fstream 的對象可以使用其父類的成員來訪問數(shù)據(jù)。

一般來說,我們將使用這些類與同控制臺(console)交互同樣的成員函數(shù)(cin 和 cout)來進(jìn)行輸入輸出。如下面的例題所示,我們使用重載的插入操作符<<:

    // 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.

從文件中讀入數(shù)據(jù)也可以用與 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.

上面的例子讀入一個文本文件的內(nèi)容,然后將它打印到屏幕上。注意我們使用了一個新的成員函數(shù)叫做eof ,它是ifstream 從類 ios 中繼承過來的,當(dāng)?shù)竭_(dá)文件末尾時返回true 。


狀態(tài)標(biāo)志符的驗證(Verification of state flags)

除了eof()以外,還有一些驗證流的狀態(tài)的成員函數(shù)(所有都返回bool型返回值):

  • bad()

    如果在讀寫過程中出錯,返回 true 。例如:當(dāng)我們要對一個不是打開為寫狀態(tài)的文件進(jìn)行寫入時,或者我們要寫入的設(shè)備沒有剩余空間的時候。

  • fail()

    除了與bad() 同樣的情況下會返回 true 以外,加上格式錯誤時也返回true ,例如當(dāng)想要讀入一個整數(shù),而獲得了一個字母的時候。

  • eof()

    如果讀文件到達(dá)文件末尾,返回true。

  • good()

    這是最通用的:如果調(diào)用以上任何一個函數(shù)返回true 的話,此函數(shù)返回 false 。

要想重置以上成員函數(shù)所檢查的狀態(tài)標(biāo)志,你可以使用成員函數(shù)clear(),沒有參數(shù)。


獲得和設(shè)置流指針(get and put stream pointers)

所有輸入/輸出流對象(i/o streams objects)都有至少一個流指針:

  • ifstream, 類似istream, 有一個被稱為get pointer的指針,指向下一個將被讀取的元素。
  • ofstream, 類似 ostream, 有一個指針 put pointer ,指向?qū)懭胂乱粋€元素的位置。
  • fstream, 類似 iostream, 同時繼承了get 和 put

我們可以通過使用以下成員函數(shù)來讀出或配置這些指向流中讀寫位置的流指針:

  • tellg() 和 tellp()

    這兩個成員函數(shù)不用傳入?yún)?shù),返回pos_type 類型的值(根據(jù)ANSI-C++ 標(biāo)準(zhǔn)) ,就是一個整數(shù),代表當(dāng)前get 流指針的位置 (用tellg) 或 put 流指針的位置(用tellp).

  • seekg() 和seekp()

    這對函數(shù)分別用來改變流指針get 和put的位置。兩個函數(shù)都被重載為兩種不同的原型:

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

    使用這個原型,流指針被改變?yōu)橹赶驈奈募_始計算的一個絕對位置。要求傳入的參數(shù)類型與函數(shù) tellg 和tellp 的返回值類型相同。

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

    使用這個原型可以指定由參數(shù)direction決定的一個具體的指針開始計算的一個位移(offset)。它可以是:

    ios::beg 從流開始位置計算的位移
    ios::cur 從流指針當(dāng)前位置開始計算的位移
    ios::end 從流末尾處開始計算的位移

流指針 get 和 put 的值對文本文件(text file)和二進(jìn)制文件(binary file)的計算方法都是不同的,因為文本模式的文件中某些特殊字符可能被修改。由于這個原因,建議對以文本文件模式打開的文件總是使用seekg 和 seekp的第一種原型,而且不要對tellg 或 tellp 的返回值進(jìn)行修改。對二進(jìn)制文件,你可以任意使用這些函數(shù),應(yīng)該不會有任何意外的行為產(chǎn)生。

以下例子使用這些函數(shù)來獲得一個二進(jìn)制文件的大小:

    // 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.

二進(jìn)制文件(Binary files)

在二進(jìn)制文件中,使用<< 和>>,以及函數(shù)(如getline)來操作符輸入和輸出數(shù)據(jù),沒有什么實際意義,雖然它們是符合語法的。

文件流包括兩個為順序讀寫數(shù)據(jù)特殊設(shè)計的成員函數(shù):write 和 read。第一個函數(shù) (write) 是ostream 的一個成員函數(shù),都是被ofstream所繼承。而read 是istream 的一個成員函數(shù),被ifstream 所繼承。類 fstream 的對象同時擁有這兩個函數(shù)。它們的原型是:

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

這里 buffer 是一塊內(nèi)存的地址,用來存儲或讀出數(shù)據(jù)。參數(shù)size 是一個整數(shù)值,表示要從緩存(buffer)中讀出或?qū)懭氲淖址麛?shù)。

    // 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)

當(dāng)我們對文件流進(jìn)行操作的時候,它們與一個streambuf 類型的緩存(buffer)聯(lián)系在一起。這個緩存(buffer)實際是一塊內(nèi)存空間,作為流(stream)和物理文件的媒介。例如,對于一個輸出流, 每次成員函數(shù)put (寫一個單個字符)被調(diào)用,這個字符不是直接被寫入該輸出流所對應(yīng)的物理文件中的,而是首先被插入到該流的緩存(buffer)中。

當(dāng)緩存被排放出來(flush)時,它里面的所有數(shù)據(jù)或者被寫入物理媒質(zhì)中(如果是一個輸出流的話),或者簡單的被抹掉(如果是一個輸入流的話)。這個過程稱為同步(synchronization),它會在以下任一情況下發(fā)生:

  • 當(dāng)文件被關(guān)閉時: 在文件被關(guān)閉之前,所有還沒有被完全寫出或讀取的緩存都將被同步。
  • 當(dāng)緩存buffer 滿時:緩存Buffers 有一定的空間限制。當(dāng)緩存滿時,它會被自動同步。
  • 控制符明確指明:當(dāng)遇到流中某些特定的控制符時,同步會發(fā)生。這些控制符包括:flush 和endl。
  • 明確調(diào)用函數(shù)sync(): 調(diào)用成員函數(shù)sync() (無參數(shù))可以引發(fā)立即同步。這個函數(shù)返回一個int 值,等于-1 表示流沒有聯(lián)系的緩存或操作失敗。
  • 在C++中,有一個stream這個類,所有的I/O都以這個“流”類為基礎(chǔ)的,包括我們要認(rèn)識的文件I/O,stream這個類有兩個重要的運(yùn)算符:

    1、插入器(<<)
    向流輸出數(shù)據(jù)。比如說系統(tǒng)有一個默認(rèn)的標(biāo)準(zhǔn)輸出流(cout),一般情況下就是指的顯示器,所以,cout<<"Write Stdout"<<'n';就表示把字符串"Write Stdout"和換行字符('n')輸出到標(biāo)準(zhǔn)輸出流。

    2、析取器(>>)
    從流中輸入數(shù)據(jù)。比如說系統(tǒng)有一個默認(rèn)的標(biāo)準(zhǔn)輸入流(cin),一般情況下就是指的鍵盤,所以,cin>>x;就表示從標(biāo)準(zhǔn)輸入流中讀取一個指定類型(即變量x的類型)的數(shù)據(jù)。

    在C++中,對文件的操作是通過stream的子類fstream(file stream)來實現(xiàn)的,所以,要用這種方式操作文件,就必須加入頭文件fstream.h。下面就把此類的文件操作過程一一道來。

    一、打開文件
    在fstream類中,有一個成員函數(shù)open(),就是用來打開文件的,其原型是:

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

    參數(shù):

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

    ios::app: 以追加的方式打開文件
    ios::ate: 文件打開后定位到文件尾,ios:app就包含有此屬性
    ios::binary: 以二進(jìn)制方式打開文件,缺省的方式是文本方式。兩種方式的區(qū)別見前文
    ios::in: 文件以輸入方式打開
    ios::out: 文件以輸出方式打開
    ios::nocreate: 不建立文件,所以文件不存在時打開失敗
    ios::noreplace:不覆蓋文件,所以打開文件時如果文件存在失敗
    ios::trunc: 如果文件存在,把文件長度設(shè)為0
    可以用“或”把以上屬性連接起來,如ios::out|ios::binary

    打開文件的屬性取值是:

    0:普通文件,打開訪問
    1:只讀文件
    2:隱含文件
    4:系統(tǒng)文件
    可以用“或”或者“+”把以上屬性連接起來 ,如3或1|2就是以只讀和隱含屬性打開文件。

    例如:以二進(jìn)制輸入方式打開文件c:config.sys

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

    如果open函數(shù)只有文件名一個參數(shù),則是以讀/寫普通文件打開,即:

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

    另外,fstream還有和open()一樣的構(gòu)造函數(shù),對于上例,在定義的時侯就可以打開文件了:

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

    特別提出的是,fstream有兩個子類:ifstream(input file stream)和ofstream(outpu file stream),ifstream默認(rèn)以輸入方式打開文件,而ofstream默認(rèn)以輸出方式打開文件。

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

    所以,在實際應(yīng)用中,根據(jù)需要的不同,選擇不同的類來定義:如果想以輸入方式打開,就用ifstream來定義;如果想以輸出方式打開,就用ofstream來定義;如果想以輸入/輸出方式來打開,就用fstream來定義。

    二、關(guān)閉文件
    打開的文件使用完成后一定要關(guān)閉,fstream提供了成員函數(shù)close()來完成此操作,如:file1.close();就把file1相連的文件關(guān)閉。

    三、讀寫文件
    讀寫文件分為文本文件和二進(jìn)制文件的讀取,對于文本文件的讀取比較簡單,用插入器和析取器就可以了;而對于二進(jìn)制的讀取就要復(fù)雜些,下要就詳細(xì)的介紹這兩種方式

    1、文本文件的讀寫
    文本文件的讀寫很簡單:用插入器(<<)向文件輸出;用析取器(>>)從文件輸入。假設(shè)file1是以輸入方式打開,file2以輸出打開。示例如下:

    file2<<"I Love You";//向文件寫入字符串"I Love You"
    int i;
    file1>>i;//從文件輸入一個整數(shù)值。

    這種方式還有一種簡單的格式化能力,比如可以指定輸出為16進(jìn)制等等,具體的格式有以下一些

    操縱符 功能 輸入/輸出
    dec 格式化為十進(jìn)制數(shù)值數(shù)據(jù) 輸入和輸出
    endl 輸出一個換行符并刷新此流 輸出
    ends 輸出一個空字符 輸出
    hex 格式化為十六進(jìn)制數(shù)值數(shù)據(jù) 輸入和輸出
    oct 格式化為八進(jìn)制數(shù)值數(shù)據(jù) 輸入和輸出
    setpxecision(int p) 設(shè)置浮點數(shù)的精度位數(shù) 輸出

    比如要把123當(dāng)作十六進(jìn)制輸出:file1<<hex<<123;要把3.1415926以5位精度輸出:file1<<setpxecision(5)<<3.1415926。

    2、二進(jìn)制文件的讀寫
    ①put()
    put()函數(shù)向流寫入一個字符,其原型是ofstream &put(char ch),使用也比較簡單,如file1.put('c');就是向流寫一個字符'c'。

    ②get()
    get()函數(shù)比較靈活,有3種常用的重載形式:

    一種就是和put()對應(yīng)的形式:ifstream &get(char &ch);功能是從流中讀取一個字符,結(jié)果保存在引用ch中,如果到文件尾,返回空字符。如file2.get(x);表示從文件中讀取一個字符,并把讀取的字符保存在x中。

    另一種重載形式的原型是: int get();這種形式是從流中返回一個字符,如果到達(dá)文件尾,返回EOF,如x=file2.get();和上例功能是一樣的。

    還 有一種形式的原型是:ifstream &get(char *buf,int num,char delim='n');這種形式把字符讀入由 buf 指向的數(shù)組,直到讀入了 num 個字符或遇到了由 delim 指定的字符,如果沒使用 delim 這個參數(shù),將使用缺省值換行符'n'。例如:

    file2.get(str1,127,'A');//從文件中讀取字符到字符串str1,當(dāng)遇到字符'A'或讀取了127個字符時終止。

    ③讀寫數(shù)據(jù)塊
    要讀寫二進(jìn)制數(shù)據(jù)塊,使用成員函數(shù)read()和write()成員函數(shù),它們原型如下:

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

    read() 從文件中讀取 num 個字符到 buf 指向的緩存中,如果在還未讀入 num 個字符時就到了文件尾,可以用成員函數(shù) int gcount();來取得實際讀取的字符數(shù);而 write() 從buf 指向的緩存寫 num 個字符到文件中,值得注意的是緩存的類型是 unsigned char *,有時可能需要類型轉(zhuǎn)換。

    例:

    unsigned char str1[]="I Love You";
    int n[5];
    ifstream in("xxx.xxx");
    ofstream out("yyy.yyy");
    out.write(str1,strlen(str1));//把字符串str1全部寫到y(tǒng)yy.yyy中
    in.read((unsigned char*)n,sizeof(n));//從xxx.xxx中讀取指定個整數(shù),注意類型轉(zhuǎn)換
    in.close();out.close();

    四、檢測EOF
    成員函數(shù)eof()用來檢測是否到達(dá)文件尾,如果到達(dá)文件尾返回非0值,否則返回0。原型是int eof();

    例: if(in.eof())ShowMessage("已經(jīng)到達(dá)文件尾!");

    五、文件定位
    和C的文件操作方式不同的是,C++ I/O系統(tǒng)管理兩個與一個文件相聯(lián)系的指針。一個是讀指針,它說明輸入操作在文件中的位置;另一個是寫指針,它下次寫操作的位置。每次執(zhí)行輸入或輸出時, 相應(yīng)的指針自動變化。所以,C++的文件定位分為讀位置和寫位置的定位,對應(yīng)的成員函數(shù)是 seekg()和 seekp(),seekg()是設(shè)置讀位置,seekp是設(shè)置寫位置。它們最通用的形式如下:

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

    streamoff定義于 iostream.h 中,定義有偏移量 offset 所能取得的最大值,seek_dir 表示移動的基準(zhǔn)位置,是一個有以下值的枚舉:

    ios::beg: 文件開頭
    ios::cur: 文件當(dāng)前位置
    ios::end: 文件結(jié)尾
    這兩個函數(shù)一般用于二進(jìn)制文件,因為文本文件會因為系統(tǒng)對字符的解釋而可能與預(yù)想的值不同。

    例:

    file1.seekg(1234,ios::cur);//把文件的讀指針從當(dāng)前位置向后移1234個字節(jié)
    file2.seekp(1234,ios::beg);//把文件的寫指針從文件開頭向后移1234個字節(jié)

posted @ 2012-06-28 22:36 多彩人生 閱讀(381) | 評論 (0)編輯 收藏

C/C++遍歷文件夾

 finddata_t的使用

  那么到底如何查找文件呢?我們需要一個結(jié)構(gòu)體和幾個大家可能不太熟悉的函數(shù)。這些函數(shù)和結(jié)構(gòu)體在<io.h>的頭文件中,結(jié)構(gòu)體為 struct _finddata_t ,函數(shù)為_findfirst、_findnext和_fineclose.具體如何使用,我會慢慢講來~

  首先講這個結(jié)構(gòu)體吧~struct _finddata_t ,這個結(jié)構(gòu)體是用來存儲文件各種信息的。說實話,這個結(jié)構(gòu)體的具體定義代碼,我沒有找到,不過還好,文檔里面在_find里有比較詳細(xì)的成員變量介紹。我基本上就把文檔翻譯過來講吧:

  unsigned atrrib:文件屬性的存儲位 置。它存儲一個unsigned單元,用于表示文件的屬性。文件屬性是用位表示的,主要有以下一些:_A_ARCH(存檔)、_A_HIDDEN(隱 藏)、_A_NORMAL(正常)、_A_RDONLY(只讀)、_A_SUBDIR(文件夾)、_A_SYSTEM(系統(tǒng))。這些都是 在<io.h>中定義的宏,可以直接使用,而本身的意義其實是一個無符號整型(只不過這個整型應(yīng)該是2的幾次冪,從而保證只有一位為1,而其 他位為0)。既然是位表示,那么當(dāng)一個文件有多個屬性時,它往往是通過位或的方式,來得到幾個屬性的綜合。例如只讀+隱藏+系統(tǒng)屬性,應(yīng)該 為:_A_HIDDEN | _A_RDONLY |_A_SYSTEM .

  time_t time_create:這里的time_t是一個變量類型(長整型?相當(dāng)于long int?),用來存儲時間的,我們暫時不用理它,只要知道,這個time_create變量是用來存儲文件創(chuàng)建時間的就可以了。

  time_t time_access:文件最后一次被訪問的時間。

  time_t time_write:文件最后一次被修改的時間。

  _fsize_t size:文件的大小。這里的_fsize_t應(yīng)該可以相當(dāng)于unsigned整型,表示文件的字節(jié)數(shù)。

  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];

  };

  前面也說了,這個結(jié)構(gòu)體是用來存儲文件信息的,那么如何把一個硬盤文件的文件信息“存到”這個結(jié)構(gòu)體所表示的內(nèi)存空間里去呢?這就要靠_findfirst、_findnext和_fineclose三個函數(shù)的搭配使用了。

  首先還是對這三個函數(shù)一一介紹一番吧……

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

  返回值:如果查找成功的話,將返回一個long型的唯一的查找用的句柄(就是一個唯一編號)。這個句柄將在_findnext函數(shù)中被使用。若失敗,則返回-1.

  參數(shù):

  filespec:標(biāo)明文件的字符串,可支持通配符。比如:*.c,則表示當(dāng)前文件夾下的所有后綴為C的文件。

  fileinfo :這里就是用來存放文件信息的結(jié)構(gòu)體的指針。這個結(jié)構(gòu)體必須在調(diào)用此函數(shù)前聲明,不過不用初始化,只要分配了內(nèi)存空間就可以了。函數(shù)成功后,函數(shù)會把找到的文件的信息放入這個結(jié)構(gòu)體中。

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

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

  參數(shù):

  handle:即由_findfirst函數(shù)返回回來的句柄。

  fileinfo:文件信息結(jié)構(gòu)體的指針。找到文件后,函數(shù)將該文件信息放入此結(jié)構(gòu)體中。

  int _findclose( long handle );

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

  參數(shù):

  handle :_findfirst函數(shù)返回回來的句柄。

  大家看到這里,估計都能猜到個大概了吧?先用_findfirst查找第一個文件,若成功則用返回的句柄調(diào)用_findnext函數(shù)查找其他的 文件,當(dāng)查找完畢后用,用_findclose函數(shù)結(jié)束查找。恩,對,這就是正確思路。下面我們就按照這樣的思路來編寫一個查找C:\WINDOWS文件 夾下的所有exe可執(zhí)行文件的程序。

  #include <stdio.h>

  #include <io.h>

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

  int main()

  {

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

  struct _finddata_t fileinfo;                          //文件信息的結(jié)構(gòu)體

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

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

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

  while(!_findnext(handle,&fileinfo))               //循環(huán)查找其他符合的文件,知道找不到其他的為止

  {

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

  }

  _findclose(handle);                                      //別忘了關(guān)閉句柄

  system("pause");

  return 0;

  }

  當(dāng)然,這個文件的查找是在指定的路徑中進(jìn)行,如何遍歷硬盤,在整個硬盤中查找文件呢?大家可以在網(wǎng)絡(luò)上搜索文件遞歸遍歷等方法,這里不再做進(jìn)一步介紹。

  細(xì)心的朋友可能會注意到我在程序的末尾用了一個system函數(shù)。這個與程序本身并沒有影響,和以前介紹給大家的使用getchar()函數(shù)的 作用相同,只是為了暫停一下,讓我們能看到命令提示符上輸出的結(jié)果而已。不過system函數(shù)本身是一個非常強(qiáng)大的函數(shù)。大家可以查查MSDN看看~簡單 來說,它是一個C語言與操作系統(tǒng)的相互平臺,可以在程序里通過這個函數(shù),向操作系統(tǒng)傳遞command命令

posted @ 2012-06-28 21:37 多彩人生 閱讀(756) | 評論 (0)編輯 收藏

c++ 宏

c/c++ 宏中"#"和"##"的用法
2007年05月14日 星期一 上午 10:19
一、一般用法
我們使用#把宏參數(shù)變?yōu)橐粋€字符串,用##把兩個宏參數(shù)貼合在一起.
用法:
#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;
}

二、當(dāng)宏參數(shù)是另一個宏的時候
需要注意的是凡宏定義里有用'#'或'##'的地方宏參數(shù)是不會再展開.

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里的參數(shù)TOW會被展開為(2).

2, 當(dāng)有'#'或'##'的時候
#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都不會再被展開, 然而解決這個問題的方法很簡單. 加多一層中間轉(zhuǎn)換宏.
加這層宏的用意是把所有宏的參數(shù)在這層里全部展開, 那么在轉(zhuǎn)換宏里的那一個宏(_STR)就能得到正確的宏參數(shù).

#define A            (2)
#define _STR(s)      #s
#define STR(s)       _STR(s)           // 轉(zhuǎn)換宏
#define _CONS(a,b)   int(a##e##b)
#define CONS(a,b)    _CONS(a,b)        // 轉(zhuǎn)換宏

printf("int max: %s\n", STR(INT_MAX));           // INT_MAX,int型的最大值,為一個變量 #i nclude<climits>
輸出為: int max: 0x7fffffff
STR(INT_MAX) -->   _STR(0x7fffffff) 然后再轉(zhuǎn)換成字符串;

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

三、'#'和'##'的一些應(yīng)用特例
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;
即每次只能解開當(dāng)前層的宏,所以__LINE__在第二層才能被解開;

2、填充結(jié)構(gòu)
#define   FILL(a)    {a, #a}

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

MSG _msg[] = {FILL(OPEN), FILL(CLOSE)};
相當(dāng)于:
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、得到一個數(shù)值類型所對應(yīng)的字符串緩沖大小
#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"];
這里相當(dāng)于:
char   buf[11];

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

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

c和c++調(diào)式 利用宏獲得函數(shù)名
僅僅為了獲取函數(shù)名,就在函數(shù)體中嵌入硬編碼的字符串,這種方法單調(diào)乏味還易導(dǎo)致錯誤,不如看一下怎樣使用新的C99特性,在程序運(yùn)行時獲取函數(shù)名吧。
   對象反射庫、調(diào)試工具及代碼分析器,經(jīng)常會需要在運(yùn)行時訪問函數(shù)的名稱,直到不久前,唯一能完成此 項任務(wù)并且可移植的方法,是手工在函數(shù)體內(nèi)嵌入一個帶有該函數(shù)名的硬編碼字符串,不必說,這種方法非常單調(diào)無奇,并且輕易導(dǎo)致錯誤。本文將要演示怎樣使用 新的C99特性,在運(yùn)行時獲取函數(shù)名。
  那么怎樣以編程的方式從當(dāng)前運(yùn)行的函數(shù)中得到函數(shù)名呢?
  答案是:使用__FUNCTION__ 及相關(guān)宏。
  引出問題
  通常,在調(diào)試中最讓人心煩的階段,是不斷地檢查是否已調(diào)用了特定的函數(shù)。對此問題的解決方法,一般是添加一個cout或printf()——假如你使用C語言,如下所示:
void myfunc()
{
cout<<"myfunc()"<<endl;
//其他代碼
}
通常在一個典型的工程中,會包含有數(shù)千個函數(shù),要在每個函數(shù)中都加入一條這樣的輸出語句,無疑難過上“蜀山”啊,因此,需要有一種機(jī)制,可以自動地完成這項操作。
  獲取函數(shù)名
  作為一個C++程序員,可能經(jīng)常碰到 __TIME__、__FILE__、__DATE__ ,__LINE__ 這樣的宏,它們會在編譯時,分別轉(zhuǎn)換為包含編譯時間、處理的轉(zhuǎn)換單元名稱及當(dāng)前時間的字符串。
   在最新的ISO C標(biāo)準(zhǔn)中,如大家所知的C99,加入了另一個有用的、類似宏的表達(dá)式__func__,其會報告未修飾過的(也就是未裁剪過的)、正在被訪問的函數(shù)名。請 注重,__func__不是一個宏,因為預(yù)處理器對此函數(shù)一無所知;相反,它是作為一個隱式聲明的常量字符數(shù)組實現(xiàn)的:
static const char __func__[] = "function-name";
在function-name處,為實際的函數(shù)名。為激活此特性,某些編譯器需要使用特定的編譯標(biāo)志,請查看相應(yīng)的編譯器文檔,以獲取具體的資料。
  有了它,我們可免去大多數(shù)通過手工修改,來顯示函數(shù)名的苦差事,以上的例子可如下所示進(jìn)行重寫:
void myfunc()
{
cout<<"__FUNCTION__"<<endl;
}
官 方C99標(biāo)準(zhǔn)為此目的定義的__func__標(biāo)識符,確實值得大家關(guān)注,然而,ISO C++卻不完全支持所有的C99擴(kuò)展,因此,大多數(shù)的編譯器提供商都使用 __FUNCTION__ 取而代之,而 __FUNCTION__ 通常是一個定義為 __func__ 的宏,之所以使用這個名字,是因為它已受到了大多數(shù)的廣泛支持。
  在Visual Studio 2005中,默認(rèn)情況下,此特性是激活的,但不能與/EP和/P編譯選項同時使用。請注重在IDE環(huán)境中,不能識別__func__ ,而要用__FUNCTION__ 代替。
  Comeau的用戶也應(yīng)使用 __FUNCTION__ ,而不是 __func__ 。
  C++ BuilderX的用戶則應(yīng)使用稍稍不同的名字:__FUNC__ 。
  GCC 3.0及更高的版本同時支持 __func__ 和__FUNCTION__ 。
  一旦可自動獲取當(dāng)前函數(shù)名,你可以定義一個如下所示顯示任何函數(shù)名的函數(shù):
void show_name(const char * name)
{
cout<<name<<endl;
}
void myfunc()
{
show_name(__FUNCTION__); //輸出:myfunc
}
void foo()
{
show_name(__FUNCTION__); //輸出:foo
}
因為 __FUNCTION__ 會在函數(shù)大括號開始之后就立即初始化,所以,foo()及myfunc()函數(shù)可在參數(shù)列表中安全地使用它,而不用擔(dān)心重載。
  簽名與修飾名
   __FUNCTION__ 特性最初是為C語言設(shè)計的,然而,C++程序員也會經(jīng)常需要有關(guān)他們函數(shù)的額外信息,在Visual Studio 2005中,還支持另外兩種非標(biāo)準(zhǔn)的擴(kuò)展特性:__FUNCDNAME__ 與 __FUNCSIG__ ,其分別轉(zhuǎn)譯為一個函數(shù)的修飾名與簽名。函數(shù)的修飾名非常有用,例如,在你想要檢查兩個編譯器是否共享同樣的ABI時,就可派得上用場,另外,它還能幫助 你破解那些含義模糊的鏈接錯誤,甚至還可用它從一個DLL中調(diào)用另一個用C++鏈接的函數(shù)。在下例中,show_name()報告了函數(shù)的修飾名:
void myfunc()
{
show_name(__FUNCDNAME__); //輸出:?myfunc@@YAXXZ
}
一 個函數(shù)的簽名由函數(shù)名、參數(shù)列表、返回類型、內(nèi)含的命名空間組成。假如它是一個成員函數(shù),它的類名和const/volatile限定符也將是簽名的 一部分。以下的代碼演示了一個獨立的函數(shù)與一個const成員函數(shù)簽名間的不同之處,兩個函數(shù)的名稱、返回類型、參數(shù)完全相同:
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 多彩人生 閱讀(386) | 評論 (0)編輯 收藏

wxWidgets系列之自定義事件實現(xiàn)、調(diào)用6步曲

自定義事件實現(xiàn)步驟有如下幾步:

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中,申明、實現(xiàn)OnSetName1、OnSetName2、OnSetName3

申明:

    void  OnSetName1(wxCommandEvent& event);

    void  OnSetName2(wxCommandEvent& event);

    void  OnSetName3(wxCommandEvent& event);

實現(xiàn):代碼就不在此列舉

6、自定義事件調(diào)用

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

    如果是當(dāng)前窗口可以寫成

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

posted @ 2012-06-25 15:49 多彩人生 閱讀(853) | 評論 (0)編輯 收藏

僅列出標(biāo)題
共25頁: First 11 12 13 14 15 16 17 18 19 Last 

導(dǎo)航

統(tǒng)計

常用鏈接

留言簿(3)

隨筆分類

隨筆檔案

搜索

最新評論

閱讀排行榜

評論排行榜

青青草原综合久久大伊人导航_色综合久久天天综合_日日噜噜夜夜狠狠久久丁香五月_热久久这里只有精品
  • <ins id="pjuwb"></ins>
    <blockquote id="pjuwb"><pre id="pjuwb"></pre></blockquote>
    <noscript id="pjuwb"></noscript>
          <sup id="pjuwb"><pre id="pjuwb"></pre></sup>
            <dd id="pjuwb"></dd>
            <abbr id="pjuwb"></abbr>
            伊人婷婷久久| 一本一道久久综合狠狠老精东影业 | 欧美国产日韩一区二区在线观看 | 久久另类ts人妖一区二区| 欧美在线视频日韩| 毛片一区二区三区| 欧美理论片在线观看| 欧美午夜精品久久久久久孕妇| 国产精品日韩欧美一区二区| 国产欧美日韩免费| 亚洲国语精品自产拍在线观看| 一级日韩一区在线观看| 久久精品一区四区| 亚洲精品日韩欧美| 久久久久久91香蕉国产| 欧美日韩精品免费在线观看视频| 国产嫩草影院久久久久| 亚洲国产第一| 性18欧美另类| 亚洲精品国久久99热| 亚洲自啪免费| 欧美国产激情二区三区| 国产精品亚洲综合色区韩国| 1769国产精品| 午夜欧美理论片| 亚洲精品1区| 久久久91精品国产一区二区三区 | 久久久最新网址| 日韩性生活视频| 麻豆精品一区二区综合av| 国产精品美女主播在线观看纯欲| 亚洲高清不卡| 久久久91精品国产一区二区三区| 一级日韩一区在线观看| 美女视频黄 久久| 国产在线观看91精品一区| 国产精品99久久久久久人| 免费看成人av| 欧美一站二站| 国产精品香蕉在线观看| 国产精品99久久久久久久女警| 欧美第一黄色网| 欧美视频一区二区三区在线观看| 国产综合第一页| 亚洲天堂av电影| 亚洲精品黄网在线观看| 美女主播一区| 136国产福利精品导航网址| 久久精品99国产精品日本| 一本色道精品久久一区二区三区 | 欧美一区网站| 欧美视频在线免费| 妖精成人www高清在线观看| 欧美激情国产精品| 噜噜噜噜噜久久久久久91| 在线电影欧美日韩一区二区私密| 久久久亚洲高清| 久久久国产成人精品| 国产中文一区二区| 久热精品视频| 欧美 日韩 国产 一区| 最新国产拍偷乱拍精品| 亚洲第一中文字幕| 欧美精品在线观看播放| 一区二区三区四区国产精品| 99www免费人成精品| 国产精品久久77777| 午夜精品999| 性欧美xxxx视频在线观看| 狠狠色噜噜狠狠狠狠色吗综合| 另类酷文…触手系列精品集v1小说| 久久夜精品va视频免费观看| 亚洲人人精品| 99精品福利视频| 国产精品主播| 欧美国产第一页| 国产精品xxxxx| 久久伊人免费视频| 欧美极品一区二区三区| 亚洲一区二区三区影院| 午夜精品国产更新| 亚洲国产婷婷| 亚洲免费在线视频| 亚洲高清免费| 一区二区三区视频免费在线观看| 国产深夜精品| 亚洲国产成人久久综合一区| 国产精品嫩草99a| 欧美高清影院| 国产精品色在线| 欧美激情第8页| 国产精品影音先锋| 欧美激情国产日韩| 国产精品自拍三区| 最新国产拍偷乱拍精品| 国产一区在线看| 99国产精品久久久久久久成人热| 国产手机视频一区二区| 亚洲欧洲视频在线| 国产亚洲在线| 9人人澡人人爽人人精品| 国产亚洲欧美aaaa| 一本色道久久综合| 国产在线精品自拍| 国产一区二区三区日韩欧美| 欧美电影免费观看网站| 国产精品一区二区久激情瑜伽| 麻豆9191精品国产| 国产精品美女久久久免费| 最新国产成人av网站网址麻豆| 国产亚洲精品bt天堂精选| 亚洲人成毛片在线播放| 一区视频在线播放| 午夜亚洲福利在线老司机| 亚洲最新在线| 欧美第十八页| 欧美激情aaaa| 在线电影国产精品| 久久av二区| 久久精品国产综合| 国产欧美视频一区二区| 中日韩高清电影网| 一本一本久久a久久精品综合妖精 一本一本久久a久久精品综合麻豆 | 欧美sm极限捆绑bd| 国产婷婷色一区二区三区四区| 日韩亚洲欧美中文三级| 亚洲精品乱码久久久久| 久久综合久久综合这里只有精品| 久久精品最新地址| 国产欧美日韩亚洲一区二区三区| 亚洲午夜一区二区| 欧美一级视频精品观看| 国产精品拍天天在线| 夜夜狂射影院欧美极品| 亚洲一区二区三区影院| 欧美午夜精品久久久久久久| 在线亚洲一区二区| 欧美一区二区成人6969| 国产精自产拍久久久久久蜜| 亚洲男人天堂2024| 久久久91精品国产| 在线欧美日韩精品| 欧美激情视频在线播放| 99re6热只有精品免费观看| 亚洲午夜黄色| 国产一区二区0| 久久午夜精品一区二区| 欧美黄色免费网站| 一区二区三区不卡视频在线观看| 欧美三区美女| 亚洲欧美影院| 欧美国产在线电影| 亚洲一区日韩在线| 国内精品久久久久影院 日本资源 国内精品久久久久伊人av | 亚洲国产裸拍裸体视频在线观看乱了| 久久精品夜色噜噜亚洲a∨| 欧美国产精品中文字幕| 亚洲日本视频| 国产精品欧美一区二区三区奶水| 久久成人资源| 亚洲三级影院| 欧美在线二区| 亚洲欧洲日本一区二区三区| 欧美日韩综合| 久久久av毛片精品| 国产精品永久免费在线| 乱人伦精品视频在线观看| 韩国三级电影久久久久久| 毛片av中文字幕一区二区| av不卡在线| 久久精品综合| 99v久久综合狠狠综合久久| 国产欧美在线视频| 欧美精品久久久久久久| 欧美伊人久久久久久午夜久久久久| 亚洲国产精品美女| 欧美一区二区在线播放| 亚洲人成在线观看| 国产精品一区二区黑丝| 欧美高清自拍一区| 欧美专区中文字幕| 一区二区三区日韩| 亚洲国产日韩一区二区| 麻豆精品网站| 久久夜色精品国产欧美乱极品| 日韩一级裸体免费视频| 美女视频网站黄色亚洲| 欧美一区影院| 亚洲一区二区三区精品视频| 亚洲国产精品高清久久久| 国产精品中文字幕在线观看| 欧美久久久久久久久| 久久久久久久波多野高潮日日 | 亚洲欧洲日夜超级视频| 国产欧美日韩视频一区二区| 欧美日韩久久| 欧美极品一区| 欧美大成色www永久网站婷| 久久亚洲捆绑美女| 久久激情网站|