• <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>
            Creative Commons License
            本Blog采用 知識(shí)共享署名-非商業(yè)性使用-禁止演繹 3.0 Unported許可協(xié)議 進(jìn)行許可。 —— Fox <游戲人生>

            游戲人生

            游戲人生 != ( 人生 == 游戲 )
            站點(diǎn)遷移至:http://www.yulefox.com。請(qǐng)訂閱本博的朋友將RSS修改為http://feeds.feedburner.com/yulefox
            posts - 62, comments - 508, trackbacks - 0, articles - 7

            本文同時(shí)發(fā)布在

            近來(lái)在Windows下用WSAEventSelect時(shí),碰到一個(gè)棘手的問(wèn)題,當(dāng)然現(xiàn)在已經(jīng)解決了。

            問(wèn)題描述:

            一個(gè)Server,一個(gè)ClientA,一個(gè)ClientB,Server用WSAEventSelect模型監(jiān)聽(tīng)(只有監(jiān)聽(tīng),沒(méi)有讀寫(xiě)),ClientA在連接Server后,ClientA對(duì)應(yīng)的EventA被觸發(fā),Server的WSAWaitForMultipleEvents等待到EventA,ClientB連接Server時(shí),TCP三次握手成功,ClientB與Server的TCP狀態(tài)被置為ESTABLISHED,然而Server的WSAWaitForMultipleEvents沒(méi)有等待到EventB被觸發(fā)。

            用netstat看了一下,ClientB與Server的狀態(tài)是ESTABLISHED,此時(shí)如果ClientB退出,由于Server無(wú)法正常Close該連接,因此Server的狀態(tài)不是TIME_WAIT而是CLOSE_WAIT(持續(xù)2小時(shí)),Client的狀態(tài)是FIN_WAIT_2(持續(xù)10分鐘)。

            我嘗試將ClientA主動(dòng)關(guān)閉后再次連接Server,Server的WSAWaitForMultipleEvents在wait到EventA之后,EventB此時(shí)也被觸發(fā)。

            開(kāi)始一直以為問(wèn)題的根源在于WSAEventSelect的使用上,畢竟,之前沒(méi)有系統(tǒng)寫(xiě)過(guò)類(lèi)似的代碼,難免懷疑到事件模型的使用上。多方查閱資料,最后還是沒(méi)有發(fā)現(xiàn)類(lèi)似問(wèn)題的解決方案。

            又跟了一上午之后,Kevin開(kāi)始懷疑是多線程使用的問(wèn)題,我看了一下,的確沒(méi)有對(duì)event的多線程操作進(jìn)行處理,但因?yàn)樵诹硪粋€(gè)應(yīng)用中,使用了同樣的模塊,卻沒(méi)有該問(wèn)題。最后考慮必要性時(shí)還是放棄了加臨界資源,無(wú)視多線程同步問(wèn)題。Kevin本來(lái)勸我換個(gè)模型,但我固執(zhí)的認(rèn)為要做就把這事兒做好。因?yàn)橄挛邕€要回學(xué)校一趟,就想盡快搞定,畢竟因?yàn)檫@一塊已經(jīng)把Kevin的進(jìn)度拖了一周了,心下還是過(guò)意不去,而且隱約感覺(jué)到離問(wèn)題的解決越來(lái)越近了。

            問(wèn)題分析:

            在對(duì)著WSAWaitForMultipleEvents思考了半天之后,忽然開(kāi)竅了,如果ThreadA在WSAWaitForMultipleEvents時(shí),只有一個(gè)EventA被WSAEventSelect并set到signaled狀態(tài),則該EventA會(huì)被wait成功,ThreadA處理EventA之后繼續(xù)阻塞在WSAWaitForMultipleEvents。此時(shí),ThreadB通過(guò)WSAEventSelect將EventB初始化為nonsignaled狀態(tài),之后即使EventB被set為signaled狀態(tài),但ThreadA的WSAWaitForMultipleEvents因?yàn)樘幱谧枞麪顟B(tài),不可能刷新事件集,也就不可能wait到EventB,最終導(dǎo)致了ClientB的請(qǐng)求無(wú)法被響應(yīng)。如果EventA被觸發(fā)則會(huì)被ThreadA等待到,WSAWaitForMultipleEvents返回后再次進(jìn)入時(shí)事件集已經(jīng)被刷新,EventB被wait到也就不難理解了。

            問(wèn)題解決:

            說(shuō)到底是因?yàn)楫?dāng)ThreadA阻塞在WSAWaitForMultipleEvents處之時(shí),事件集的變更無(wú)法立即得到體現(xiàn)。如果允許上層應(yīng)用隨時(shí)create或close一些event,則WSAWaitForMultipleEvents就不應(yīng)該無(wú)限阻塞下去。

            因此最后的一個(gè)解決方法就是讓W(xué)SAWaitForMultipleEvents超時(shí)返回并Sleep一段時(shí)間,當(dāng)WSAWaitForMultipleEvents再次進(jìn)入時(shí)事件集得以更新。

            想了一下,另一個(gè)應(yīng)用中之所以沒(méi)出現(xiàn)該問(wèn)題也只是個(gè)巧合,因?yàn)樵搼?yīng)用中ThreadB的兩次WSAEventSelect間隔很短,在ThreadA獲得時(shí)間片之前已經(jīng)確定了事件集。

            說(shuō)白了這也不是一個(gè)什么大問(wèn)題,甚至談不上任何難度,但是因?yàn)橹皩?duì)WSAEventSelect沒(méi)有一個(gè)清晰的概念,因此在發(fā)現(xiàn)和分析問(wèn)題上花費(fèi)了大量時(shí)間,加上在VS2005調(diào)試過(guò)程中,有個(gè)別文件更新時(shí)沒(méi)有被重新編譯,也耗費(fèi)了很多無(wú)謂的時(shí)間,以至于我們都在考慮是不是要放棄IDE,因?yàn)槲覀兇_實(shí)太依賴(lài)IDE了,有些TX為了穩(wěn)妥,每次都是“重新生成整個(gè)解決方案”,如果一個(gè)解決方案有幾千個(gè)文件、幾十萬(wàn)行的代碼,估計(jì)重編一次也要花個(gè)幾分鐘吧。

            總結(jié):

            1. netstat觀察的網(wǎng)絡(luò)連接處于ESTABLISHED狀態(tài)并不意味著邏輯連接被accept,只是表明客戶(hù)端connect的TCP物理連接(三次握手)被服務(wù)器端ack,如果服務(wù)器沒(méi)有accept到該連接,證明網(wǎng)絡(luò)模塊代碼有問(wèn)題;
            2. 多線程怎么都是個(gè)問(wèn)題,線程同步盡量避免,畢竟,用Kevin的話(huà)來(lái)說(shuō),加鎖是丑陋的。但在涉及到同步問(wèn)題時(shí),還是權(quán)衡一下,我這兒之所以最后沒(méi)有加臨界區(qū),是因?yàn)槭录饕窃赥hreadA中處理,ThreadB中只有create操作,而且ThreadA對(duì)事件集的刷新要求不是那么嚴(yán)格,也就不考慮加臨界區(qū)了;
            3. 如果能力和條件允許的話(huà),放棄IDE吧,IDE的確不是個(gè)好東西,我主要是指在編譯鏈接的時(shí)候,如果作為編輯器說(shuō)不定還會(huì)好用:)。

            個(gè)人網(wǎng)站用的主機(jī)最近從據(jù)說(shuō)要黑屏的Windows換成了Debian,還在調(diào)整,估計(jì)明天能弄好,內(nèi)容肯定比Cppblog雜的多,談點(diǎn)技術(shù)的還是會(huì)同步更新到

            posted @ 2008-10-27 23:25 Fox 閱讀(5641) | 評(píng)論 (3)編輯 收藏

            作者:Fox

            本文同時(shí)發(fā)布在http://www.yulefox.comhttp://www.shnenglu.com/fox

            十天之前,在CPPBLOG上寫(xiě)了一篇,有同學(xué)提到該實(shí)現(xiàn)不支持成員函數(shù)。這個(gè)問(wèn)題我也考慮到了,既然被提出來(lái),不妨把實(shí)現(xiàn)提供出來(lái)。

            需要說(shuō)明的是,我本身對(duì)template比較不感冒,不過(guò)對(duì)template感冒,而且寫(xiě)過(guò)關(guān)于成員函數(shù)指針的問(wèn)題,想了很久,如果支持成員函數(shù)指針,不用模板是不行了。

            此處對(duì)成員函數(shù)的支持還不涉及對(duì)函數(shù)參數(shù)的泛化,因?yàn)槲疫@個(gè)消息映射暫時(shí)不需要參數(shù)泛化,下面的代碼應(yīng)該不需要過(guò)多的解釋了。

            #define REG_MSG_FUNC(nMsgType, MsgFunc) \
                CMsgRegister::RegisterCallFunc(nMsgType, MsgFunc);

            #define REG_MSG_MEM_FUNC(nMsgType, Obj, MsgFunc) \
                CMsgRegister::RegisterCallFunc(nMsgType, Obj, MsgFunc);

            class CBaseMessage;

            class CHandler
            {
            public:
                virtual int operator()(CBaseMessage* pMsg) = 0;
            };

            template<typename FuncType>
            class CDefHandler : public CHandler
            {
            public:
                CDefHandler(){}
                CDefHandler(FuncType &Func)
                    : m_Func(Func)
                {
                }

                virtual int operator()(CBaseMessage* pMsg)
                {
                    return m_Func(pMsg);
                }

            protected:
                FuncType    m_Func;
            };

            template<typename ObjType, typename FuncType>
            class CMemHandler : public CHandler
            {
            public:
                CMemHandler(){}
                CMemHandler(ObjType* pObj, FuncType Func)
                    : m_pObj(pObj)
                    , m_Func(Func)
                {
                }

                virtual int operator()(CBaseMessage* pMsg)
                {
                    return (m_pObj->*m_Func)(pMsg);
                }

            protected:
                FuncType    m_Func;
                ObjType*    m_pObj;
            };

            class CFunction
            {
            public:
                CFunction()
                    : m_pHandler(NULL)
                {
                }

                // 封裝(C函數(shù)或靜態(tài)成員函數(shù))
                template<typename FuncType>
                CFunction( FuncType &Func )
                    : m_pHandler(new CDefHandler<FuncType>(Func))
                {
                }

                // 封裝(非靜態(tài)成員函數(shù))
                template<typename ObjType, typename FuncType>
                CFunction( ObjType* pObj, FuncType Func )
                    : m_pHandler(new CMemHandler<ObjType, FuncType>(pObj, Func))
                {
                }

                virtual ~CFunction()
                {
                    DELETE_SAFE(m_pHandler);
                }

                    // 函數(shù)調(diào)用
                int operator()(CBaseMessage* pMsg)
                {
                    return (*m_pHandler)(pMsg);
                }

            private:
                CHandler    *m_pHandler;
            };

            typedef std::map<int, CFunction*> MSG_MAP;
            typedef MSG_MAP::iterator MSG_ITR;

            class CMsgRegister
            {
            public:
                // 注冊(cè)消息函數(shù)(C函數(shù)或靜態(tài)成員函數(shù))
                template <typename FuncType>
                inline static void RegisterCallFunc(int nMsgType, FuncType &Func)
                {
                    CFunction *func = new CFunction(Func);
                    s_MsgMap[nMsgType] = func;
                }

                // 注冊(cè)消息函數(shù)(非靜態(tài)成員函數(shù))
                template <typename ObjType, typename FuncType>
                inline static void RegisterCallFunc(int nMsgType, ObjType* pObj, FuncType Func)
                {
                    CFunction *func = new CFunction(pObj, Func);
                    s_MsgMap[nMsgType] = func;
                }

                // 執(zhí)行消息
                inline static void RunCallFunc(int nMsgType, CBaseMessage* pMsg)
                {
                    MSG_ITR itr = s_MsgMap.find(nMsgType);
                    if( s_MsgMap.end() != itr )
                    {
                        (*itr->second)(pMsg);
                    }
                }

                static void ReleaseMsgMap()                // 釋放消息映射表
                {
                    MSG_ITR itr = s_MsgMap.begin();
                    while( itr != s_MsgMap.end() )
                    {
                        DELETE_SAFE(itr->second);
                        itr = s_MsgMap.erase(itr);
                    }
                }

            protected:
                static MSG_MAP            s_MsgMap;        // 消息映射表
            };

            不可否認(rèn),模板給了你更大的想象空間,很多東西,還是不要一味排斥的好:)。

            posted @ 2008-10-10 10:20 Fox 閱讀(2685) | 評(píng)論 (4)編輯 收藏

            作者:Fox

            本文同時(shí)發(fā)布在http://www.yulefox.comhttp://www.shnenglu.com/fox

            兩個(gè)多月之前,在CPPBLOG上寫(xiě)過(guò)一篇關(guān)于游戲開(kāi)發(fā)中的問(wèn)題,主要該考慮的問(wèn)題都已經(jīng)說(shuō)明,當(dāng)時(shí)沒(méi)有實(shí)現(xiàn)這一塊。在最近一個(gè)模塊中,寫(xiě)了一個(gè)非常簡(jiǎn)單的寫(xiě)日志的接口,接口的聲明大概是:

            void PutoutLog(const char *szFile, const char *szLog, ...);

            記錄的日志格式如下:

            1  2008-10-10-03:30:10.618 | projectpath/srcfile.cpp/function(30) : 哦嚯, 這兒出錯(cuò)了(eno : 0x00100000).

            用到了__FILE__、__LINE__、__FUNCTION__幾個(gè)宏。

            基本滿(mǎn)足需要了,需要改進(jìn)的地方我現(xiàn)在能想到的主要是:

            • 文件名是全路徑,沒(méi)有必要,只記錄文件名稱(chēng)其實(shí)就夠了;
            • 沒(méi)有考慮寫(xiě)日志時(shí)的線程同步問(wèn)題;
            • 系統(tǒng)dump時(shí)的日志還是沒(méi)有辦法記錄;
            • 缺少足夠的、動(dòng)態(tài)的上下文信息:調(diào)用堆棧、函數(shù)參數(shù)、系統(tǒng)運(yùn)行參數(shù);
            • 日志記錄到普通文本中,雖然記錄了時(shí)間、位置,還是不便于系統(tǒng)查看、查找、分析、挖掘。

            說(shuō)白了,這所謂的基本滿(mǎn)足需要只是皮毛,因?yàn)樽罱诖蚶?a title="我的個(gè)人博客站點(diǎn)" target="_blank" rel="tag">我的個(gè)人博客站點(diǎn),有感于網(wǎng)頁(yè)數(shù)據(jù)庫(kù)技術(shù)的博大精深、美妙直觀,如果可以把日志用網(wǎng)頁(yè)數(shù)據(jù)庫(kù)作為讀寫(xiě)的載體,豈不甚妙?

            隱約中感覺(jué)這種想法似曾相識(shí),不識(shí)字只好亂翻書(shū),果然在中發(fā)現(xiàn)有這樣一篇文章:一個(gè)基于HTML的日志和調(diào)試系統(tǒng)。有興趣的同學(xué)自己翻書(shū)吧:)。

            如果將更加豐富的信息寫(xiě)入xml或php文件中,加入到數(shù)據(jù)庫(kù),可以對(duì)數(shù)據(jù)進(jìn)行分析、挖掘,并友好的顯示在瀏覽器中,這對(duì)于枯燥的debug過(guò)程,起碼增添了些許益處。

            然而,這又只是一個(gè)想法,或許在我手頭上的工作稍后告一段落的時(shí)候,我可以花精力研究一下這方面的東西。

            posted @ 2008-10-10 04:18 Fox 閱讀(1829) | 評(píng)論 (8)編輯 收藏

            項(xiàng)目中使用了消息通信機(jī)制,因?yàn)橄㈩?lèi)型非常多,相應(yīng)的,處理消息的地方代碼也非常多。

            自然而然想到MFC中的消息映射:

            創(chuàng)建一個(gè)缺省MFC框架程序的解決方案Test,在Test.h中看到以下內(nèi)容:

            class Ctest_mfcApp : public CWinApp
            {
            public:
                Ctest_mfcApp();

            // 重寫(xiě)
            public:
                virtual BOOL InitInstance();

            // 實(shí)現(xiàn)
                afx_msg void OnAppAbout();
                DECLARE_MESSAGE_MAP()
            };

             

            其中,最緊要的就是DECLARE_MESSAGE_MAP()這個(gè)宏,相關(guān)內(nèi)容展開(kāi)如下:

            struct AFX_MSGMAP_ENTRY
            {
                UINT nMessage;   // windows message
                UINT nCode;      // control code or WM_NOTIFY code
                UINT nID;        // control ID (or 0 for windows messages)
                UINT nLastID;    // used for entries specifying a range of control id's
                UINT_PTR nSig;   // signature type (action) or pointer to message #
                AFX_PMSG pfn;    // routine to call (or special value)
            };

            struct AFX_MSGMAP
            {
                const AFX_MSGMAP* (PASCAL* pfnGetBaseMap)();
                const AFX_MSGMAP_ENTRY* lpEntries;
            };

            #define DECLARE_MESSAGE_MAP() \
            protected: \
                static const AFX_MSGMAP* PASCAL GetThisMessageMap(); \
                virtual const AFX_MSGMAP* GetMessageMap() const; \

            其中AFX_PMSG不再解析下去,我們認(rèn)為這是一個(gè)指向特定消息對(duì)應(yīng)的實(shí)現(xiàn)函數(shù)的函數(shù)指針,這幾個(gè)宏的作用可簡(jiǎn)單理解成為T(mén)est這個(gè)項(xiàng)目定義了一個(gè)靜態(tài)的消息映射表。當(dāng)消息到來(lái)時(shí),從消息隊(duì)列中彈出消息并分發(fā)到具有入口實(shí)現(xiàn)的上層CWnd派生窗口。用戶(hù)只需要注冊(cè)消息,實(shí)現(xiàn)消息入口函數(shù)就夠了,這在MFC中一般放在.cpp文件頭部。Test.cpp中頭部有以下內(nèi)容:

            BEGIN_MESSAGE_MAP(CTest, CWinApp)
                ON_COMMAND(ID_APP_ABOUT, &CTest::OnAppAbout)
                // 基于文件的標(biāo)準(zhǔn)文檔命令
                ON_COMMAND(ID_FILE_NEW, &CWinApp::OnFileNew)
                ON_COMMAND(ID_FILE_OPEN, &CWinApp::OnFileOpen)
                // 標(biāo)準(zhǔn)打印設(shè)置命令
                ON_COMMAND(ID_FILE_PRINT_SETUP, &CWinApp::OnFilePrintSetup)
            END_MESSAGE_MAP()

            這里是為消息枚舉值與消息實(shí)現(xiàn)函數(shù)建立映射,其中涉及到的宏的展開(kāi)如下:

            #define ON_COMMAND(id, memberFxn) \
                { WM_COMMAND, CN_COMMAND, (WORD)id, (WORD)id, AfxSigCmd_v, \
                    static_cast<AFX_PMSG> (memberFxn) },

            #define BEGIN_MESSAGE_MAP(theClass, baseClass) \
                PTM_WARNING_DISABLE \
                const AFX_MSGMAP* theClass::GetMessageMap() const \
                    { return GetThisMessageMap(); } \
                const AFX_MSGMAP* PASCAL theClass::GetThisMessageMap() \
                { \
                    typedef theClass ThisClass;                           \
                    typedef baseClass TheBaseClass;                       \
                    static const AFX_MSGMAP_ENTRY _messageEntries[] =  \
                    {

            #define END_MESSAGE_MAP() \
                    {0, 0, 0, 0, AfxSig_end, (AFX_PMSG)0 } \
                }; \
                    static const AFX_MSGMAP messageMap = \
                    { &TheBaseClass::GetThisMessageMap, &_messageEntries[0] }; \
                    return &messageMap; \
                }                                  \
                PTM_WARNING_RESTORE

            按照上述思路得到的相似代碼如下:

            // Test.h
            typedef void (* funCall)(void*);        // 消息執(zhí)行函數(shù)類(lèi)型

            struct tagMsgEntry                      // 消息入口結(jié)構(gòu)
            {
                int            nMsgType;            // 消息類(lèi)型
                funCall        MsgRun;              // 消息執(zhí)行函數(shù)
            };

            struct tagMsgMap                        // 消息映射表結(jié)構(gòu)
            {
                const tagMsgMap* (__stdcall* funGetBaseMsgMap)();
                const tagMsgEntry* pMsgEntries;     // 消息入口集
            };

            class CMessage
            {
                // ...
            protected:
                static const tagMsgMap* __stdcall GetThisMsgMap();
                virtual const tagMsgMap* GetMsgMap() const;
            };

            // Test.cpp
            const tagMsgMap* CMessage::GetMsgMap() const
            {
                return GetThisMsgMap();
            }

            const tagMsgMap* __stdcall CMessage::GetThisMsgMap()
            {
                static const tagMsgEntry MsgEntries[] =
                {
                    { MSG_SOME_ONE, SomeOneFunc },
                    { MSG_SOME_TWO, SomeTwoFunc },
                    { MSG_NULL, NULL }
                };
                static const tagMsgMap msgMap =
                {
                    &CBaseMessage::GetThisMsgMap,    // 父類(lèi)消息映射表
                    &MsgEntries[0]
                };
                return &msgMap;
            }

            int CMessage::MsgProc(int nMsgType)
            {
                switch( nMsgType )
                {
                case MSG_SOME_ONE:
                    {

                    }
                    break;
                }
                return CBaseMessage::MsgProc(nMsgType);
            }

            這種處理的優(yōu)點(diǎn)在于,子類(lèi)沒(méi)有定義的消息實(shí)現(xiàn)接口,可以使用父類(lèi)接口實(shí)現(xiàn)。不過(guò)在現(xiàn)在的消息處理中,我們一般不需要由基類(lèi)來(lái)完成,因此可以不依賴(lài)基類(lèi)接口,使用宏可以使代碼看上去更加簡(jiǎn)潔。

            ___________________________________________________________

            簡(jiǎn)化版本的消息映射采用以下方式,簡(jiǎn)單清晰:

            // Test.h
            #define REG_MSG_FUNC(nMsgType, MsgFunc) \
                CMessge::RegisterCallFunc(nMsgType, MsgFunc); \

            typedef void (* function)(void*);

            typedef std::map<int, function> MSG_MAP;
            typedef MSG_MAP::const_iterator MSG_CITR;

            class CMessage
            {
                // ...
            public:
                static const MSG_MAP& GetMsgMap();
                static void RegisterCallFunc(int nMsgType, void(* Func)(void *))
                {
                    s_MsgMap[nMsgType] = Func;
                }

                int CMessage::Run(int nMsgType)                // 消息公用執(zhí)行函數(shù)
                {
                    MSG_ITR itr = s_MsgMap.find(nMsgType);
                    if( s_MsgMap.end() != itr )
                    {
                        itr->second(this);
                    }
                }

            protected:
                static MSG_MAP            s_MsgMap;            // 消息映射表
            };

            // UserTest.cpp -- 用戶(hù)在使用時(shí)對(duì)自己關(guān)心的消息予以注冊(cè), 入口函數(shù)予以實(shí)現(xiàn)即可
            REG_MSG_FUNC(MSG_SOME_ONE, SomeOneFunc)

            void SomeOneFunc(CBaseMessage *pMsg)
            {
                return;
            }

            ___________________________________________________________

            最近忙的焦頭爛額,正好寫(xiě)到消息,稍微整理一下,提供些許借鑒。

            posted @ 2008-09-29 18:34 Fox 閱讀(4493) | 評(píng)論 (31)編輯 收藏

            網(wǎng)絡(luò)編程學(xué)習(xí)和實(shí)踐的過(guò)程中,同步(synchronous)/異步(asynchronous)阻塞(blocking)/非阻塞(non-blocking)總是會(huì)迷惑很多人。依然記得我半年之前在記述IOCP時(shí),一句不經(jīng)意的“非阻塞I/O則是致力于提供高效的異步I/O”便引來(lái)一番口水論爭(zhēng)。

            今天在查一些資料的時(shí)候,看到關(guān)于這幾個(gè)詞的論辯竟不是一般的多,細(xì)細(xì)想來(lái),這個(gè)問(wèn)題似乎也確實(shí)有解釋的必要,不在于爭(zhēng)論對(duì)錯(cuò),而在于辨明是非。

            討論之前,先限定討論的范圍:此處之同步/異步僅限于I/O操作,與OS所討論的進(jìn)程/線程中的其他同步/異步沒(méi)有直接關(guān)系;討論的內(nèi)容是:兩對(duì)相似的術(shù)語(yǔ)之間的區(qū)別到底有多大

            • 非常大:

            Douglas C. Schmidt在《C++網(wǎng)絡(luò)編程》中這樣說(shuō)到:

            They are very different, as follows:

            AIO is "asynchronous I/O", i.e., the operation is invoked asynchronously and control returns to the client while the OS kernel processes the I/O request.  When the operation completes there is some mechanism for the client to retrieve the results.

            Non-blocking I/O tries an operation (such as a read() or write()) and if it the operation would block (e.g., due to flow control on a TCP connection or due to lack of data in a socket), the call returns -1 and sets errno to EWOULDBLOCK.

            翻譯如下:

            :例如,操作被異步調(diào)用時(shí),控制權(quán)交給客戶(hù)端,I/O操作請(qǐng)求則交由操作系統(tǒng)內(nèi)核處理,當(dāng)操作完成后,通過(guò)某種機(jī)制將結(jié)果通知客戶(hù)端。

            非阻塞I/O:嘗試調(diào)用某操作,如果操作被阻塞,則調(diào)用返回-1并置錯(cuò)誤值為EWOULDBLOCK。

            從這兩段“very different”的解釋來(lái)看,我的感覺(jué)是并沒(méi)有指出二者的區(qū)別,因?yàn)槲覀儫o(wú)法確定所謂AIO是如何處理的,如果AIO直接“調(diào)用返回-1并置錯(cuò)誤值為EWOULDBLOCK”,實(shí)現(xiàn)“控制權(quán)交給客戶(hù)端”,似乎并無(wú)任何不妥。況且,對(duì)于非阻塞I/O,我們也需要“當(dāng)操作完成后,通過(guò)某種機(jī)制將結(jié)果通知客戶(hù)端”這樣的處理。

            • 無(wú)差別:

            而在Wikipedia上則直接等同二者:Asynchronous I/O, or non-blocking I/O, is a form of input/output processing that permits other processing to continue before the transmission has finished.

            當(dāng)然,對(duì)于recv和send,我們一般會(huì)說(shuō)他們是阻塞起的,而不會(huì)說(shuō)他們是同步起的,但這顯然不是二者的區(qū)別,因?yàn)槲覀兌贾溃?strong>阻塞的原因正是等待同步結(jié)果的返回。

            因此,二者的區(qū)別在于,阻塞/非阻塞是表現(xiàn),同步/異步是原因,我們說(shuō)某某操作是阻塞起的,或者某某線程是阻塞起的,是因?yàn)樵诘却僮鹘Y(jié)果的同步返回;我們說(shuō)某某操作是非阻塞的,是因?yàn)椴僮鹘Y(jié)果會(huì)通過(guò)異步方式返回。

            討論到這兒,再咬文嚼字的爭(zhēng)辯下去似乎已經(jīng)沒(méi)有任何實(shí)際意義。

            ------------------------------------------------------------

            PS:糾結(jié)一些必要的概念是為了加深理解,太過(guò)糾結(jié)了反倒會(huì)滯塞理解。我之前對(duì)于其概念也并非特別清楚,所以才會(huì)再續(xù)一篇特意言明,也算彌補(bǔ)一下自己的過(guò)失。

            posted @ 2008-09-11 01:11 Fox 閱讀(4965) | 評(píng)論 (12)編輯 收藏

            When :  2008.8.8.20:00:00

            Where : National Stadium, Beijing, China.

            Who :    One World.

            What :   One Dream.

            All right, it is just a D-R-E-A-M...

            ____________________________

            但是,我一定會(huì)看,從電視上。

            就像看火炬一定要從電視上才和諧好些,開(kāi)幕式也是。

            網(wǎng)上猜測(cè)李寧點(diǎn)火的人氣很高,之前官方也有透露說(shuō)會(huì)有5.12汶川大地震相關(guān)內(nèi)容,我猜想是這樣的:

            李寧身著Li-Ning牌黑白熊貓運(yùn)動(dòng)服,像功夫熊貓那樣,以體操功夫的糅合動(dòng)作跳進(jìn)8級(jí)地震中的深5.12m的名為汶川主火炬盆,以川劇中的吐火絕技點(diǎn)燃主火炬盆,高喊“我是李書(shū)記,快救我”,連做三個(gè)俯臥撐后,欲火浴火涅磐鳳凰,手提一瓶和諧醬油飛向太空……

            感覺(jué)很靠譜:D。

            posted @ 2008-08-08 14:16 Fox 閱讀(869) | 評(píng)論 (2)編輯 收藏

            0. Introduction

            接觸設(shè)計(jì)模式有兩年時(shí)間了,但一直沒(méi)有系統(tǒng)整理過(guò),為了不至于讓自己的思維被繁瑣的工作一點(diǎn)點(diǎn)禁錮,還是決定總結(jié)一下,為了能夠真正做到有所收獲,整個(gè)系列會(huì)按照GoF的Design Patterns: Elements of Reusable Object-Oriented Software的行文思路,但不會(huì)照本宣科就是了,Wikipedia上關(guān)于23種設(shè)計(jì)模式的介紹非常全面,CSDN上也可以下載中/英文電子檔,因此很多套話(huà)、類(lèi)圖一概省去。

            最早接觸設(shè)計(jì)模式的時(shí)候,難免被各種模式的聯(lián)系和區(qū)別所困擾,從教科書(shū)的分析可以得到模式之間形式上的不同。但這樣對(duì)于領(lǐng)會(huì)設(shè)計(jì)模式意義不大,因?yàn)槲覀冋莆漳J降哪康氖菫榱巳跁?huì)貫通,靈活運(yùn)用,以對(duì)開(kāi)發(fā)有所幫助。

            稍微成規(guī)模的OO程序,會(huì)有大量對(duì)象,其中很多實(shí)體對(duì)象之間存在著父子、兄弟關(guān)系,對(duì)象的創(chuàng)建提升為一種模式。其好處在于設(shè)計(jì)模式本身所宣稱(chēng)的reusable,這就像堆積木蓋房子一樣,堆的好的情況下,換一換門(mén)窗便是另一番風(fēng)景。

            關(guān)于實(shí)現(xiàn),我不會(huì)為了厘清模式間的區(qū)別而刻意使用相似代碼實(shí)現(xiàn),相反,我會(huì)根據(jù)模式本身的適用情況舉例,而且大量代碼基于SourceMaking

            _______________________________

            1. Creational Design Patterns(DP)

            創(chuàng)建型DP抽象了類(lèi)和對(duì)象的創(chuàng)建過(guò)程,GoF給出了5種創(chuàng)建型DPAbstract FactoryBuilderFactory MethodBuilderPrototypeSingleton

            2. Abstract Factory

            意圖:提供一個(gè)創(chuàng)建一系列相關(guān)或相互依賴(lài)對(duì)象的接口,而無(wú)需指定它們具體的類(lèi)。

            1) 只提供了一個(gè)創(chuàng)建接口,其返回值為具體產(chǎn)品:如AbstractProduct *Client::CreateProduct(AbstractFactory &factory);

            2) 接口的參數(shù)是一個(gè)工廠對(duì)象AbstractFactory &factory)的引用,參數(shù)類(lèi)型(AbstractFactory)為抽象基類(lèi),調(diào)用時(shí)根據(jù)需要傳入具體工廠對(duì)象即可;

            3) 接口內(nèi)部實(shí)現(xiàn)了一系列相關(guān)或相互依賴(lài)對(duì)象(抽象產(chǎn)品)的創(chuàng)建:當(dāng)傳入具體工廠時(shí),接口實(shí)現(xiàn)的就是一系列具體產(chǎn)品的創(chuàng)建;

            4) 創(chuàng)建的產(chǎn)品立即返回CreateProduct)。

            參與者:

            • AbstractFactory
            — 聲明一個(gè)創(chuàng)建抽象產(chǎn)品對(duì)象的操作接口。

            • ConcreteFactory
            — 實(shí)現(xiàn)創(chuàng)建具體產(chǎn)品對(duì)象的操作。

            • AbstractProduct
            — 為一類(lèi)產(chǎn)品對(duì)象聲明一個(gè)接口。

            • ConcreteProduct
            — 定義一個(gè)將被相應(yīng)的具體工廠創(chuàng)建的產(chǎn)品對(duì)象。
            — 實(shí)現(xiàn)AbstractProduct接口。

            • Client
            — 僅使用由AbstractFactory和AbstractProduct類(lèi)聲明的接口。

            代碼:

            class AbstractFactory
            {
            public:
                virtual AbstractProduct *MakePartA() = 0;
                virtual AbstractProduct *MakePartB() = 0;
                virtual AbstractProduct *MakePartC() = 0;
                virtual AbstractProduct *AddPart(const AbstractProduct *pPart) = 0;
            };

            AbstractProduct *Client::CreateProduct(AbstractFactory &factory)
            {
                AbstractProduct *pProduct = factory.CreateProduct();
                AbstractProduct *pPartA = factory.MakePartA();
                AbstractProduct *pPartB = factory.MakePartB();
                AbstractProduct *pPartC = factory.MakePartC();
                factory.AddPart(pPartA);
                factory.AddPart(pPartB);
                factory.AddPart(pPartC);
                return pProduct;
            }

            int main(void)
            {
                Client client;           
                ConcreteFactory factory;
                client.CreateProduct(factory);
                return 0;
            }

            3. Builder

            意圖:將一個(gè)復(fù)雜對(duì)象的構(gòu)建與它的表示分離,使得同樣的構(gòu)建過(guò)程可以創(chuàng)建不同的表示。

            1) director提供抽象產(chǎn)品創(chuàng)建接口:如void Director::Construct();

            2) 不同產(chǎn)品使用同一創(chuàng)建過(guò)程,由director指定特定builder以生產(chǎn)不同產(chǎn)品;

            3) 接口內(nèi)部實(shí)現(xiàn)了一個(gè)復(fù)雜對(duì)象(抽象產(chǎn)品)的創(chuàng)建:當(dāng)傳入具體工廠時(shí),接口實(shí)現(xiàn)的是一個(gè)復(fù)雜的具體產(chǎn)品的創(chuàng)建;

            4) 創(chuàng)建的產(chǎn)品并不立即返回創(chuàng)建完畢后返回,或使用接口GetProduct)提取結(jié)果。

            參與者:

            • Builder
            — 為創(chuàng)建一個(gè)Product對(duì)象的各個(gè)部件指定抽象接口。

            • ConcreteBuilder
            — 實(shí)現(xiàn)Builder的接口以構(gòu)造和裝配該產(chǎn)品的各個(gè)部件。
            — 定義并明確它所創(chuàng)建的表示。
            — 提供一個(gè)檢索產(chǎn)品的接口。

            • Director
            — 構(gòu)造一個(gè)使用Builder接口的對(duì)象。

            • Product
            — 表示被構(gòu)造的復(fù)雜對(duì)象。ConcreteBuilder創(chuàng)建該產(chǎn)品的內(nèi)部表示并定義它的裝配過(guò)程。
            — 包含定義組成部件的類(lèi),包括將這些部件裝配成最終產(chǎn)品的接口。

            代碼:

            class Builder
            {
            public:
                virtual void MakePartA() = 0;
                virtual void MakePartB() = 0;
                virtual void MakePartC() = 0;

                Product *GetProduct()    { return _product; }

            protected:
                Product *_product;
            };

            class Director
            {
            public:
                void setBuilder(Builder *b)    { _builder = b; }
                void Construct();

            private:
                Builder *_builder;
            };

            void Director::Construct()
            {
                _builder.MakePartA();
                _builder.MakePartB();
                _builder.MakePartC();
            }

            int main(void) {
                ConcreteBuilderA concreteBuilderA;
                ConcreteBuilderB concreteBuilderB;
                Director director;
                Product *pProduct;

                director.SetBuilder(&concreteBuilderA);
                director.Construct();
                pProduct = concreteBuilderA.GetProduct();
                pProduct->Show();

                director.SetBuilder(&concreteBuilderB);
                director.Construct();
                pProduct = concreteBuilderB.GetProduct();
                pProduct->Show();

                return 0;
            }

            4. Factory Method

            意圖:定義一個(gè)用于創(chuàng)建對(duì)象的接口,讓子類(lèi)決定實(shí)例化哪一個(gè)類(lèi)。Factory Method使一個(gè)類(lèi)的實(shí)例化延遲到其子類(lèi)。

            1) 看得出該模式其實(shí)就是C++的多態(tài)特性,借繼承實(shí)現(xiàn)。因此,其別名為虛構(gòu)造器( Virtual Constructor)

            2) 作為模式與C++多態(tài)特性不同的是,Creator可以定義工廠方法的缺省實(shí)現(xiàn),完成缺省操作,MFC大量使用了這一思想。

            參與者:

            • Product
            — 定義工廠方法所創(chuàng)建的對(duì)象的接口。

            • ConcreteProduct
            — 實(shí)現(xiàn)Product接口。

            • Creator
            — 聲明工廠方法,該方法返回一個(gè)Product類(lèi)型的對(duì)象。Creator也可以定義一個(gè)工廠方法的缺省實(shí)現(xiàn),它返回一個(gè)缺省的ConcreteProduct對(duì)象。
            — 可以調(diào)用工廠方法以創(chuàng)建一個(gè)Product對(duì)象。

            • ConcreteCreator
            — 重定義工廠方法以返回一個(gè)ConcreteProduct實(shí)例。

            代碼:

            ConcreteProduct *ConcreteCreator::FactoryMethod()
            {
                ConcreteProduct *pProduct = new ConcreteProduct;
                return pProduct;
            }

            Product *Creator::FactoryMethod()
            {
                Product *pProduct = new Product;
                return pProduct;
            }

            int main(void) {
                Creator creator;
                ConcreteProduct *pProduct;

                pProduct = creator.FactoryMethod();
                pProduct->Show();

                return 0;
            }

            5. Prototype

            意圖:用原型實(shí)例指定創(chuàng)建對(duì)象的種類(lèi),并且通過(guò)拷貝這些原型創(chuàng)建新的對(duì)象。

            1) 創(chuàng)建不再通過(guò)工廠新類(lèi)繼承(inheritance),而是通過(guò)委托(delegation)

            2) 根通拷貝原型實(shí)例創(chuàng)建新對(duì)象。

            參與者:

            • ProtoType
            — 聲明一個(gè)克隆自身的接口。

            • ConcreteProtoType
            — 實(shí)現(xiàn)一個(gè)克隆自身的操作。

            • Client
            — 讓一個(gè)原型克隆自身從而創(chuàng)建一個(gè)新的對(duì)象。

            代碼:

            class ProtoType
            {
            public:
                virtual void Draw();
                virtual ProtoType *Clone() = 0;
                virtual void Initialize();
            };

            class ProtoTypeA: public ProtoType
            {
            public:
                virtual ProtoType *Clone()
                {
                    return new ProtoTypeA;
                }
            };

            class ProtoTypeB: public ProtoType
            {
            public:
                virtual ProtoType *Clone()
                {
                    return new ProtoTypeB;
                }
            };

            class Client
            {
            public:
                static ProtoType *Clone( int choice );

            private:
                static ProtoType *s_prototypes[3];
            };

            ProtoType* Client::s_prototypes[] = { 0, new ProtoTypeA, new ProtoTypeB };

            ProtoType *Client::Clone(int choice)
            {
                return s_prototypes[choice]->Clone();
            }

             

            6. Singleton

            意圖:保證一個(gè)類(lèi)僅有一個(gè)實(shí)例,并提供一個(gè)訪問(wèn)它的全局訪問(wèn)點(diǎn)。

            1) 用靜態(tài)成員函數(shù)保證上述意圖。

            參與者:

            • Singleton
            — 定義一個(gè)Instance操作,允許客戶(hù)訪問(wèn)它的唯一實(shí)例。Instance是一個(gè)類(lèi)操作(即C++中的一個(gè)靜態(tài)成員函數(shù))。
            — 可能負(fù)責(zé)創(chuàng)建它自己的唯一實(shí)例。

             

            代碼:

            class Singleton
            {
            public:
                static Singleton *GetInstance()
                {
                    if (!s_instance)
                        s_instance = new Singleton;
                    return s_instance;
                }

                void Run()    {}

            private:
                static Singleton *s_instance;
                Singleton()    {}                // Singleton cannot be created outside.
            };

            Singleton *GetSingleton(void)
            {
                return Singleton::GetInstance();
            }

            int main(void)
            {
                GetSingleton()->Run();

                return 0;
            }

            ______________________________________________

            代碼寫(xiě)的都比較簡(jiǎn)單,基本可以將各種模式之間的不同體現(xiàn)出來(lái)了。

            posted @ 2008-08-06 15:43 Fox 閱讀(2229) | 評(píng)論 (3)編輯 收藏

            一、Big-endian & Little-endian

            還是Wikipedia好啊!可惜中文的國(guó)內(nèi)看不了,愚昧啊!實(shí)在覺(jué)得中文有點(diǎn)難懂,看看日本語(yǔ)版本吧:D!

            關(guān)于端(endianness)的介紹,Wikipedia上比較全了:http://en.wikipedia.org/wiki/Endianness

            關(guān)于網(wǎng)絡(luò)字節(jié)序(network byte order)主機(jī)字節(jié)序(host byte order),說(shuō)來(lái)挺無(wú)關(guān)緊要的一點(diǎn)東西,因?yàn)槊看慰偸峭簦悦看味家闷娴目纯?strong>大端(big-endian)和小端(little-endian)

            給定unsigned long型整數(shù)十六進(jìn)制形式:0x0A0B0C0D,其big-endian和little-endian形式分別為:

            1) Big-endian

            Memory
            |
            ...  |  8-bit atomic element size       | ...    |  16-bit atomic element size
            | 0x0A |  a                               | 0x0A0B |  a
            | 0x0B |  a+1                             | 0x0C0D |  a+1
            | 0x0C |  a+2
            | 0x0D |  a+3
            | ...  |

            2) Little-endian(X86)

            Memory
            |
            ...  |  8-bit atomic element size       | ...    |  16-bit atomic element size
            | 0x0D |  a                               | 0x0C0D |  a
            | 0x0C |  a+1                             | 0x0A0B |  a+1
            | 0x0B |  a+2
            | 0x0A |  a+3
            | ...  |

            Mapping registers to memory locations (from Wikipedia)

            為什么X86存儲(chǔ)會(huì)使用little-endian,起初我想對(duì)于位運(yùn)算,尤其是位移運(yùn)算,little-endian很方便,但轉(zhuǎn)念一想,big-endian也方便啊,無(wú)非是左移和右移的區(qū)別而已,但little-endian的優(yōu)勢(shì)在于unsigned char/short/int/long類(lèi)型轉(zhuǎn)換時(shí),存儲(chǔ)位置無(wú)需改變。

            在網(wǎng)絡(luò)傳輸中,采用big-endian序,對(duì)于0x0A0B0C0D,傳輸順序就是0A 0B 0C 0D,因此big-endian作為network byte order,little-endian作為host byte order。

            ________________________________________________

            PS:做雞有什么不好?

            上午跟某同事(為尊重慮,下文以Y稱(chēng)之)躲在犄角旮旯抽煙。以下為場(chǎng)景再現(xiàn):

            (忽然整出來(lái)一句)Y:聽(tīng)過(guò)鷹的故事沒(méi)有?

            (滿(mǎn)臉疑惑)Fox:沒(méi)有。

            Y:一只小鷹掉到雞窩里,#$@%……

            F:我不是鷹,我就是一只雞,做技術(shù)雞有什么不好?

            Y:做技術(shù)沒(méi)有不好啊……

            F:我不是說(shuō)做技術(shù),我說(shuō)做雞,我就是在地上走的,我為什么總是要抬頭看天?

            Y:你要往上看,沒(méi)有人注定不能飛,XX以前也沒(méi)有想過(guò)有一天會(huì)飛起來(lái)。

            F:我不是掉到雞窩里,我本來(lái)就在雞窩里,我也喜歡呆在雞窩里,別人都在地上走,我為什么要飛起來(lái)?

            Y:你總要飛起來(lái)。

            F:我說(shuō)了我喜歡呆在雞窩里,你見(jiàn)過(guò)有那只雞飛起來(lái)了?

            Y:……

            F:我就是一只雞,插了雞翅還是飛不起來(lái),況且,我對(duì)飛起來(lái)也沒(méi)有任何興趣。

            Y:……

            F:做雞有什么不好?

            Y:你看老毛,與人斗其樂(lè)無(wú)窮,他境界多高,與天斗其樂(lè)無(wú)窮,知道吧,他已經(jīng)不屑與人斗了。

            F:我不喜歡與人斗,我也斗不過(guò),做雞有什么不好?

            Y:……

            posted @ 2008-07-30 14:48 Fox 閱讀(2032) | 評(píng)論 (4)編輯 收藏

            原文地址:

            • 規(guī)則之例外

            前面說(shuō)明的編碼習(xí)慣基本是強(qiáng)制性的,但所有優(yōu)秀的規(guī)則都允許例外。

            1. 現(xiàn)有不統(tǒng)一代碼(Existing Non-conformant Code)

            對(duì)于現(xiàn)有不符合既定編程風(fēng)格的代碼可以網(wǎng)開(kāi)一面。

            當(dāng)你修改使用其他風(fēng)格的代碼時(shí),為了與代碼原有風(fēng)格保持一致可以不使用本指南約定。如果不放心可以與代碼原作者或現(xiàn)在的負(fù)責(zé)人員商討,記住,一致性包括原有的一致性。

            1. Windows代碼(Windows Code)

            Windows程序員有自己的編碼習(xí)慣,主要源于Windows的一些頭文件和其他Microsoft代碼。我們希望任何人都可以順利讀懂你的代碼,所以針對(duì)所有平臺(tái)的C++編碼給出一個(gè)單獨(dú)的指導(dǎo)方案。

            如果你一直使用Windows編碼風(fēng)格的,這兒有必要重申一下某些你可能會(huì)忘記的指南(譯者注,我怎么感覺(jué)像在被洗腦:D)

            1) 不要使用匈牙利命名法(Hungarian notation,如定義整型變量為iNum,使用Google命名約定,包括對(duì)源文件使用.cc擴(kuò)展名;

            2) Windows定義了很多原有內(nèi)建類(lèi)型的同義詞(譯者注,這一點(diǎn),我也很反感),如DWORDHANDLE等等,在調(diào)用Windows API時(shí)這是完全可以接受甚至鼓勵(lì)的,但還是盡量使用原來(lái)的C++類(lèi)型,例如,使用const TCHAR *而不是LPCTSTR

            3) 使用Microsoft Visual C++進(jìn)行編譯時(shí),將警告級(jí)別設(shè)置為3或更高,并將所有warnings當(dāng)作errors處理

            4) 不要使用#pragma once;作為包含保護(hù),使用C++標(biāo)準(zhǔn)包含保護(hù)包含保護(hù)的文件路徑包含到項(xiàng)目樹(shù)頂層(譯者注,#include<prj_name/public/tools.h>

            5) 除非萬(wàn)不得已,否則不使用任何不標(biāo)準(zhǔn)的擴(kuò)展,如#pragma__declspec,允許使用__declspec(dllimport)__declspec(dllexport),但必須通過(guò)DLLIMPORTDLLEXPORT等宏,以便其他人在共享使用這些代碼時(shí)容易放棄這些擴(kuò)展。

            在Windows上,只有很少一些偶爾可以不遵守的規(guī)則:

            1) 通常我們禁止使用多重繼承,但在使用COMATL/WTL類(lèi)時(shí)可以使用多重繼承,為了執(zhí)行COMATL/WTL類(lèi)及其接口時(shí)可以使用多重實(shí)現(xiàn)繼承;

            2) 雖然代碼中不應(yīng)使用異常,但在ATL和部分STL(包括Visual C++的STL)中異常被廣泛使用,使用ATL時(shí),應(yīng)定義_ATL_NO_EXCEPTIONS以屏蔽異常,你要研究一下是否也屏蔽掉STL的異常,如果不屏蔽,開(kāi)啟編譯器異常也可以,注意這只是為了編譯STL,自己仍然不要寫(xiě)含異常處理的代碼;

            3) 通常每個(gè)項(xiàng)目的每個(gè)源文件中都包含一個(gè)名為StdAfx.hprecompile.h的頭文件方便頭文件預(yù)編譯,為了使代碼方便與其他項(xiàng)目共享,避免顯式包含此文件(precompile.cc除外),使用編譯器選項(xiàng)/FI以自動(dòng)包含;

            4) 通常名為resource.h、且只包含宏的資源頭文件,不必拘泥于此風(fēng)格指南。

            • 團(tuán)隊(duì)合作

            參考常識(shí),保持一致

            編輯代碼時(shí),花點(diǎn)時(shí)間看看項(xiàng)目中的其他代碼并確定其風(fēng)格,如果其他代碼if語(yǔ)句中使用空格,那么你也要使用。如果其中的注釋用星號(hào)(*)圍成一個(gè)盒子狀,你也這樣做:

            /**********************************
            * Some comments are here.
            * There may be many lines.
            **********************************/
            
            

            編程風(fēng)格指南的使用要點(diǎn)在于提供一個(gè)公共的編碼規(guī)范,所有人可以把精力集中在實(shí)現(xiàn)內(nèi)容而不是表現(xiàn)形式上。我們給出了全局的風(fēng)格規(guī)范,但局部的風(fēng)格也很重要,如果你在一個(gè)文件中新加的代碼和原有代碼風(fēng)格相去甚遠(yuǎn)的話(huà),這就破壞了文件本身的整體美觀也影響閱讀,所以要盡量避免。

            好了,關(guān)于編碼風(fēng)格寫(xiě)的差不多了,代碼本身才是更有趣的,盡情享受吧!

            Benjy Weinberger
            Craig Silverstein
            Gregory Eitzmann
            Mark Mentovai
            Tashana Landray

            ______________________________________

            譯者:終于翻完了,前后歷時(shí)兩周,整個(gè)過(guò)程中,雖因工作關(guān)系偶有懈怠,但總算不是虎頭蛇尾(起碼我的態(tài)度是非常認(rèn)真的:D),無(wú)論是否能對(duì)你有所裨益,對(duì)我而言,至少是溫習(xí)了一些以前知道的知識(shí),也學(xué)到了一些之前不知道的知識(shí)

            剛好這兩天還不是特緊張,趕緊翻完了,要開(kāi)始干活了……

            posted @ 2008-07-23 14:28 Fox 閱讀(3833) | 評(píng)論 (11)編輯 收藏

            原文地址:

            • 格式

            代碼風(fēng)格和格式確實(shí)比較隨意,但一個(gè)項(xiàng)目中所有人遵循同一風(fēng)格是非常容易的,作為個(gè)人未必同意下述格式規(guī)則的每一處,但整個(gè)項(xiàng)目服從統(tǒng)一的編程風(fēng)格是很重要的,這樣做才能讓所有人在閱讀和理解代碼時(shí)更加容易。

            1. 行長(zhǎng)度(Line Length)

            每一行代碼字符數(shù)不超過(guò)80。

            我們也認(rèn)識(shí)到這條規(guī)則是存有爭(zhēng)議的,但如此多的代碼都遵照這一規(guī)則,我們感覺(jué)一致性更重要。

            優(yōu)點(diǎn):提倡該原則的人認(rèn)為強(qiáng)迫他們調(diào)整編輯器窗口大小很野蠻。很多人同時(shí)并排開(kāi)幾個(gè)窗口,根本沒(méi)有多余空間拓寬某個(gè)窗口,人們將窗口最大尺寸加以限定,一致使用80列寬,為什么要改變呢?

            缺點(diǎn):反對(duì)該原則的人則認(rèn)為更寬的代碼行更易閱讀,80列的限制是上個(gè)世紀(jì)60年代的大型機(jī)的古板缺陷;現(xiàn)代設(shè)備具有更寬的顯示屏,很輕松的可以顯示更多代碼。

            結(jié)論:80個(gè)字符是最大值。例外:

            1) 如果一行注釋包含了超過(guò)80字符的命令或URL,出于復(fù)制粘貼的方便可以超過(guò)80字符;

            2) 包含長(zhǎng)路徑的可以超出80列,盡量避免;

            3) 頭文件保護(hù)(防止重復(fù)包含第一篇)可以無(wú)視該原則。

            2. 非ASCII字符(Non-ASCII Characters)

            盡量不使用非ASCII字符,使用時(shí)必須使用UTF-8格式。

            哪怕是英文,也不應(yīng)將用戶(hù)界面的文本硬編碼到源代碼中,因此非ASCII字符要少用。特殊情況下可以適當(dāng)包含此類(lèi)字符,如,代碼分析外部數(shù)據(jù)文件時(shí),可以適當(dāng)硬編碼數(shù)據(jù)文件中作為分隔符的非ASCII字符串;更常用的是(不需要本地化的)單元測(cè)試代碼可能包含非ASCII字符串。此類(lèi)情況下,應(yīng)使用UTF-8格式,因?yàn)楹芏喙ぞ叨伎梢岳斫夂吞幚砥渚幋a,十六進(jìn)制編碼也可以,尤其是在增強(qiáng)可讀性的情況下——如"\xEF\xBB\xBF"是Unicode的zero-width no-break space字符,以UTF-8格式包含在源文件中是不可見(jiàn)的。

            3. 空格還是制表位(Spaces vs. Tabs)

            只使用空格,每次縮進(jìn)2個(gè)空格。

            使用空格進(jìn)行縮進(jìn),不要在代碼中使用tabs,設(shè)定編輯器將tab轉(zhuǎn)為空格。

            譯者注:在前段時(shí)間的關(guān)于Debian開(kāi)發(fā)學(xué)習(xí)日記一文中,曾給出針對(duì)C/C++編碼使用的vim配置。

            4. 函數(shù)聲明與定義(Function Declarations and Definitions)

            返回類(lèi)型和函數(shù)名在同一行,合適的話(huà),參數(shù)也放在同一行。

            函數(shù)看上去像這樣:

            ReturnType ClassName::FunctionName(Type par_name1, Type par_name2) {
              DoSomething();
              ...
            }
            
            

            如果同一行文本較多,容不下所有參數(shù):

            ReturnType ClassName::ReallyLongFunctionName(Type par_name1,
                                                         Type par_name2,
                                                         Type par_name3) {
              DoSomething();
              ...
            }
            
            

            甚至連第一個(gè)參數(shù)都放不下:

            ReturnType LongClassName::ReallyReallyReallyLongFunctionName(
                Type par_name1,  // 4 space indent
                Type par_name2,
                Type par_name3) {
              DoSomething();  // 2 space indent
              ...
            }
            
            

            注意以下幾點(diǎn):

            1) 返回值總是和函數(shù)名在同一行;

            2) 左圓括號(hào)(open parenthesis)總是和函數(shù)名在同一行;

            3) 函數(shù)名和左圓括號(hào)間沒(méi)有空格;

            4) 圓括號(hào)與參數(shù)間沒(méi)有空格;

            5) 左大括號(hào)(open curly brace)總在最后一個(gè)參數(shù)同一行的末尾處;

            6) 右大括號(hào)(close curly brace)總是單獨(dú)位于函數(shù)最后一行;

            7) 右圓括號(hào)(close parenthesis)和左大括號(hào)間總是有一個(gè)空格;

            8) 函數(shù)聲明和實(shí)現(xiàn)處的所有形參名稱(chēng)必須保持一致;

            9) 所有形參應(yīng)盡可能對(duì)齊;

            10) 缺省縮進(jìn)為2個(gè)空格;

            11) 獨(dú)立封裝的參數(shù)保持4個(gè)空格的縮進(jìn)。

            如果函數(shù)為const的,關(guān)鍵字const應(yīng)與最后一個(gè)參數(shù)位于同一行。

            // Everything in this function signature fits on a single line
            ReturnType FunctionName(Type par) const {
              ...
            }
            
            // This function signature requires multiple lines, but
            // the const keyword is on the line with the last parameter.
            ReturnType ReallyLongFunctionName(Type par1,
                                              Type par2) const {
              ...
            }
            
            

            如果有些參數(shù)沒(méi)有用到,在函數(shù)定義處將參數(shù)名注釋起來(lái):

            // Always have named parameters in interfaces.
            class Shape {
             public:
              virtual void Rotate(double radians) = 0;
            }
            
            // Always have named parameters in the declaration.
            class Circle : public Shape {
             public:
              virtual void Rotate(double radians);
            }
            
            // Comment out unused named parameters in definitions.
            void Circle::Rotate(double /*radians*/) {}
            // Bad - if someone wants to implement later, it's not clear what the
            // variable means.
            void Circle::Rotate(double) {}
            
            

            譯者注:關(guān)于UNIX/Linux風(fēng)格為什么要把左大括號(hào)置于行尾(.cc文件的函數(shù)實(shí)現(xiàn)處,左大括號(hào)位于行首),我的理解是代碼看上去比較簡(jiǎn)約,想想行首除了函數(shù)體被一對(duì)大括號(hào)封在一起之外,只有右大括號(hào)的代碼看上去確實(shí)也舒服;Windows風(fēng)格將左大括號(hào)置于行首的優(yōu)點(diǎn)是匹配情況一目了然。

            5. 函數(shù)調(diào)用(Function Calls)

            盡量放在同一行,否則,將實(shí)參封裝在圓括號(hào)中。

            函數(shù)調(diào)用遵循如下形式:

            bool retval = DoSomething(argument1, argument2, argument3);
            
            

            如果同一行放不下,可斷為多行,后面每一行都和第一個(gè)實(shí)參對(duì)齊,左圓括號(hào)后和右圓括號(hào)前不要留空格:

            bool retval = DoSomething(averyveryveryverylongargument1,
                                      argument2, argument3);
            
            

            如果函數(shù)參數(shù)比較多,可以出于可讀性的考慮每行只放一個(gè)參數(shù):

            bool retval = DoSomething(argument1,
                                      argument2,
                                      argument3,
                                      argument4);
            
            

            如果函數(shù)名太長(zhǎng),以至于超過(guò)行最大長(zhǎng)度,可以將所有參數(shù)獨(dú)立成行:

            if (...) {
              ...
              ...
              if (...) {
                DoSomethingThatRequiresALongFunctionName(
                    very_long_argument1,  // 4 space indent
                    argument2,
                    argument3,
                    argument4);
              }
            
            

            6. 條件語(yǔ)句(Conditionals)

            更提倡不在圓括號(hào)中添加空格,關(guān)鍵字else另起一行。

            對(duì)基本條件語(yǔ)句有兩種可以接受的格式,一種在圓括號(hào)和條件之間有空格,一種沒(méi)有。

            最常見(jiàn)的是沒(méi)有空格的格式,那種都可以,還是一致性為主。如果你是在修改一個(gè)文件,參考當(dāng)前已有格式;如果是寫(xiě)新的代碼,參考目錄下或項(xiàng)目中其他文件的格式,還在徘徊的話(huà),就不要加空格了。

            if (condition) {  // no spaces inside parentheses
              ...  // 2 space indent.
            } else {  // The else goes on the same line as the closing brace.
              ...
            }
            
            

            如果你傾向于在圓括號(hào)內(nèi)部加空格:

            if ( condition ) {  // spaces inside parentheses - rare
              ...  // 2 space indent.
            } else {  // The else goes on the same line as the closing brace.
              ...
            }
            
            

            注意所有情況下if和左圓括號(hào)間有個(gè)空格,右圓括號(hào)和左大括號(hào)(如果使用的話(huà))間也要有個(gè)空格:

            if(condition)     // Bad - space missing after IF.
            if (condition){   // Bad - space missing before {.
            if(condition){    // Doubly bad.
            if (condition) {  // Good - proper space after IF and before {.
            
            

            有些條件語(yǔ)句寫(xiě)在同一行以增強(qiáng)可讀性,只有當(dāng)語(yǔ)句簡(jiǎn)單并且沒(méi)有使用else子句時(shí)使用:

            if (x == kFoo) return new Foo();
            if (x == kBar) return new Bar();
            
            

            如果語(yǔ)句有else分支是不允許的:

            // Not allowed - IF statement on one line when there is an ELSE clause
            if (x) DoThis();
            else DoThat();
            
            

            通常,單行語(yǔ)句不需要使用大括號(hào),如果你喜歡也無(wú)可厚非,也有人要求if必須使用大括號(hào):

            if (condition)
              DoSomething();  // 2 space indent.
            
            if (condition) {
              DoSomething();  // 2 space indent.
            }
            
            

            但如果語(yǔ)句中哪一分支使用了大括號(hào)的話(huà),其他部分也必須使用:

            // Not allowed - curly on IF but not ELSE
            if (condition) {
              foo;
            } else
              bar;
            
            // Not allowed - curly on ELSE but not IF
            if (condition)
              foo;
            else {
              bar;
            }
             
            // Curly braces around both IF and ELSE required because
            // one of the clauses used braces.
            if (condition) {
              foo;
            } else {
              bar;
            }
            
            

            7. 循環(huán)和開(kāi)關(guān)選擇語(yǔ)句(Loops and Switch Statements)

            switch語(yǔ)句可以使用大括號(hào)分塊;空循環(huán)體應(yīng)使用{}continue

            switch語(yǔ)句中的case塊可以使用大括號(hào)也可以不用,取決于你的喜好,使用時(shí)要依下文所述。

            如果有不滿(mǎn)足case枚舉條件的值,要總是包含一個(gè)default(如果有輸入值沒(méi)有case去處理,編譯器將報(bào)警)。如果default永不會(huì)執(zhí)行,可以簡(jiǎn)單的使用assert

            switch (var) {
              case 0: {  // 2 space indent
                ...      // 4 space indent
                break;
              }
              case 1: {
                ...
                break;
              }
              default: {
                assert(false);
              }
            }
            
            

            空循環(huán)體應(yīng)使用{}continue,而不是一個(gè)簡(jiǎn)單的分號(hào):

            while (condition) {
              // Repeat test until it returns false.
            }
            for (int i = 0; i < kSomeNumber; ++i) {}  // Good - empty body.
            while (condition) continue;  // Good - continue indicates no logic.
            while (condition);  // Bad - looks like part of do/while loop.
            
            

            8. 指針和引用表達(dá)式(Pointers and Reference Expressions)

            句點(diǎn)(.)或箭頭(->)前后不要有空格,指針/地址操作符(*、&)后不要有空格。

            下面是指針和引用表達(dá)式的正確范例:

            x = *p;
            p = &x;
            x = r.y;
            x = r->y;
            
            

            注意:

            1) 在訪問(wèn)成員時(shí),句點(diǎn)或箭頭前后沒(méi)有空格;

            2) 指針操作符*&后沒(méi)有空格。

            在聲明指針變量或參數(shù)時(shí),星號(hào)與類(lèi)型或變量名緊挨都可以:

            // These are fine, space preceding.
            char *c;
            const string &str;
            
            // These are fine, space following.
            char* c;    // but remember to do "char* c, *d, *e, ...;"!
            const string& str;
            char * c;  // Bad - spaces on both sides of *
            const string & str;  // Bad - spaces on both sides of &
            
            

            同一個(gè)文件(新建或現(xiàn)有)中起碼要保持一致。

            譯者注:個(gè)人比較習(xí)慣與變量緊挨的方式

            9. 布爾表達(dá)式(Boolean Expressions)

            如果一個(gè)布爾表達(dá)式超過(guò)標(biāo)準(zhǔn)行寬(80字符),如果斷行要統(tǒng)一一下。

            下例中,邏輯與(&&)操作符總位于行尾:

            if (this_one_thing > this_other_thing &&
                a_third_thing == a_fourth_thing &&
                yet_another & last_one) {
              ...
            }
            
            

            兩個(gè)邏輯與(&&)操作符都位于行尾,可以考慮額外插入圓括號(hào),合理使用的話(huà)對(duì)增強(qiáng)可讀性是很有幫助的。

            譯者注:個(gè)人比較習(xí)慣邏輯運(yùn)算符位于行首,邏輯關(guān)系一目了然,各人喜好而已,至于加不加圓括號(hào)的問(wèn)題,如果你對(duì)優(yōu)先級(jí)了然于胸的話(huà)可以不加,但可讀性總是差了些

            10. 函數(shù)返回值(Return Values)

            return表達(dá)式中不要使用圓括號(hào)。

            函數(shù)返回時(shí)不要使用圓括號(hào):

            return x;  // not return(x);
            
            

            11. 變量及數(shù)組初始化(Variable and Array Initialization)

            選擇=還是()

            需要做二者之間做出選擇,下面的形式都是正確的:

            int x = 3;
            int x(3);
            string name("Some Name");
            string name = "Some Name";
            
            

            12. 預(yù)處理指令(Preprocessor Directives)

            預(yù)處理指令不要縮進(jìn),從行首開(kāi)始。

            即使預(yù)處理指令位于縮進(jìn)代碼塊中,指令也應(yīng)從行首開(kāi)始。

            // Good - directives at beginning of line
              if (lopsided_score) {
            #if DISASTER_PENDING      // Correct -- Starts at beginning of line
                DropEverything();
            #endif
                BackToNormal();
              }
            // Bad - indented directives
              if (lopsided_score) {
                #if DISASTER_PENDING  // Wrong!  The "#if" should be at beginning of line
                DropEverything();
                #endif                // Wrong!  Do not indent "#endif"
                BackToNormal();
              }
            
            

            13. 類(lèi)格式(Class Format)

            聲明屬性依次序是public:protected:private:,每次縮進(jìn)1個(gè)空格(譯者注,為什么不是兩個(gè)呢?也有人提倡private在前,對(duì)于聲明了哪些數(shù)據(jù)成員一目了然,還有人提倡依邏輯關(guān)系將變量與操作放在一起,都有道理:-)

            類(lèi)聲明(對(duì)類(lèi)注釋不了解的話(huà),參考第六篇中的類(lèi)注釋一節(jié))的基本格式如下:

            class MyClass : public OtherClass {
             public:      // Note the 1 space indent!
              MyClass();  // Regular 2 space indent.
              explicit MyClass(int var);
              ~MyClass() {}
            
              void SomeFunction();
              void SomeFunctionThatDoesNothing() {
              }
            
              void set_some_var(int var) { some_var_ = var; }
              int some_var() const { return some_var_; }
            
             private:
              bool SomeInternalFunction();
            
              int some_var_;
              int some_other_var_;
              DISALLOW_COPY_AND_ASSIGN(MyClass);
            };
            
            

            注意:

            1) 所以基類(lèi)名應(yīng)在80列限制下盡量與子類(lèi)名放在同一行;

            2) 關(guān)鍵詞public:、protected:private:要縮進(jìn)1個(gè)空格(譯者注,MSVC多使用tab縮進(jìn),且這三個(gè)關(guān)鍵詞沒(méi)有縮進(jìn))

            3) 除第一個(gè)關(guān)鍵詞(一般是public)外,其他關(guān)鍵詞前空一行,如果類(lèi)比較小的話(huà)也可以不空;

            4) 這些關(guān)鍵詞后不要空行;

            5) public放在最前面,然后是protectedprivate

            6) 關(guān)于聲明次序參考第三篇聲明次序一節(jié)。

            14. 初始化列表(Initializer Lists)

            構(gòu)造函數(shù)初始化列表放在同一行或按四格縮進(jìn)并排幾行。

            兩種可以接受的初始化列表格式:

            // When it all fits on one line:
            MyClass::MyClass(int var) : some_var_(var), some_other_var_(var + 1) {
            
            

            // When it requires multiple lines, indent 4 spaces, putting the colon on
            // the first initializer line:
            MyClass::MyClass(int var)
                : some_var_(var),             // 4 space indent
                  some_other_var_(var + 1) {  // lined up
              ...
              DoSomething();
              ...
            }
            
            

            15. 命名空間格式化(Namespace Formatting)

            命名空間內(nèi)容不縮進(jìn)。

            命名空間不添加額外縮進(jìn)層次,例如:

            namespace {
            
            void foo() {  // Correct.  No extra indentation within namespace.
              ...
            }
            
            }  // namespace
            
            

            不要縮進(jìn):

            namespace {
            
              // Wrong.  Indented when it should not be.
              void foo() {
                ...
              }
            
            }  // namespace
            
            

            16. 水平留白(Horizontal Whitespace)

            水平留白的使用因地制宜。不要在行尾添加無(wú)謂的留白。

            普通

            void f(bool b) {  // Open braces should always have a space before them.
              ...
            int i = 0;  // Semicolons usually have no space before them.
            int x[] = { 0 };  // Spaces inside braces for array initialization are
            int x[] = {0};    // optional.  If you use them, put them on both sides!
            // Spaces around the colon in inheritance and initializer lists.
            class Foo : public Bar {
             public:
              // For inline function implementations, put spaces between the braces
              // and the implementation itself.
              Foo(int b) : Bar(), baz_(b) {}  // No spaces inside empty braces.
              void Reset() { baz_ = 0; }  // Spaces separating braces from implementation.
              ...
            
            

            添加冗余的留白會(huì)給其他人編輯時(shí)造成額外負(fù)擔(dān),因此,不要加入多余的空格。如果確定一行代碼已經(jīng)修改完畢,將多余的空格去掉;或者在專(zhuān)門(mén)清理空格時(shí)去掉(確信沒(méi)有其他人在使用)。

            循環(huán)和條件語(yǔ)句

            if (b) {          // Space after the keyword in conditions and loops.
            } else {          // Spaces around else.
            }
            while (test) {}   // There is usually no space inside parentheses.
            switch (i) {
            for (int i = 0; i < 5; ++i) {
            switch ( i ) {    // Loops and conditions may have spaces inside
            if ( test ) {     // parentheses, but this is rare.  Be consistent.
            for ( int i = 0; i < 5; ++i ) {
            for ( ; i < 5 ; ++i) {  // For loops always have a space after the
              ...                   // semicolon, and may have a space before the
                                    // semicolon.
            switch (i) {
              case 1:         // No space before colon in a switch case.
                ...
              case 2: break;  // Use a space after a colon if there's code after it.
            
            

            操作符

            x = 0;              // Assignment operators always have spaces around
                                // them.
            x = -5;             // No spaces separating unary operators and their
            ++x;                // arguments.
            if (x && !y)
              ...
            v = w * x + y / z;  // Binary operators usually have spaces around them,
            v = w*x + y/z;      // but it's okay to remove spaces around factors.
            v = w * (x + z);    // Parentheses should have no spaces inside them.
            
            

            模板和轉(zhuǎn)換

            vector<string> x;           // No spaces inside the angle
            y = static_cast<char*>(x);  // brackets (< and >), before
                                        // <, or between >( in a cast.
            vector<char *> x;           // Spaces between type and pointer are
                                        // okay, but be consistent.
            set<list<string> > x;       // C++ requires a space in > >.
            set< list<string> > x;      // You may optionally make use
                                        // symmetric spacing in < <.
            
            

            17. 垂直留白(Vertical Whitespace)

            垂直留白越少越好。

            這不僅僅是規(guī)則而是原則問(wèn)題了:不是非常有必要的話(huà)就不要使用空行。尤其是:不要在兩個(gè)函數(shù)定義之間空超過(guò)2行,函數(shù)體頭、尾不要有空行,函數(shù)體中也不要隨意添加空行。

            基本原則是:同一屏可以顯示越多的代碼,程序的控制流就越容易理解。當(dāng)然,過(guò)于密集的代碼塊和過(guò)于疏松的代碼塊同樣難看,取決于你的判斷,但通常是越少越好。

            函數(shù)頭、尾不要有空行:

            void Function() {
            
              // Unnecessary blank lines before and after
            
            }
            
            

            代碼塊頭、尾不要有空行:

            while (condition) {
              // Unnecessary blank line after
            
            }
            if (condition) {
            
              // Unnecessary blank line before
            }
            
            

            if-else塊之間空一行還可以接受:

            if (condition) {
              // Some lines of code too small to move to another function,
              // followed by a blank line.
            
            } else {
              // Another block of code
            }
            
            

            ______________________________________

            譯者:首先說(shuō)明,對(duì)于代碼格式,因人、因系統(tǒng)各有優(yōu)缺點(diǎn),但同一個(gè)項(xiàng)目中遵循同一標(biāo)準(zhǔn)還是有必要的:

            1. 行寬原則上不超過(guò)80列,把22寸的顯示屏都占完,怎么也說(shuō)不過(guò)去;

            2. 盡量不使用非ASCII字符,如果使用的話(huà),參考UTF-8格式(尤其是UNIX/Linux下,Windows下可以考慮寬字符),盡量不將字符串常量耦合到代碼中,比如獨(dú)立出資源文件,這不僅僅是風(fēng)格問(wèn)題了;

            3. UNIX/Linux下無(wú)條件使用空格,MSVC的話(huà)使用Tab也無(wú)可厚非;

            4. 函數(shù)參數(shù)、邏輯條件、初始化列表:要么所有參數(shù)和函數(shù)名放在同一行,要么所有參數(shù)并排分行;

            5. 除函數(shù)定義的左大括號(hào)可以置于行首外,包括函數(shù)/類(lèi)/結(jié)構(gòu)體/枚舉聲明、各種語(yǔ)句的左大括號(hào)置于行尾,所有右大括號(hào)獨(dú)立成行;

            6. ./->操作符前后不留空格,*/&不要前后都留,一個(gè)就可,靠左靠右依各人喜好;

            7. 預(yù)處理指令/命名空間不使用額外縮進(jìn),類(lèi)/結(jié)構(gòu)體/枚舉/函數(shù)/語(yǔ)句使用縮進(jìn);

            8. 初始化用=還是()依個(gè)人喜好,統(tǒng)一就好;

            9. return不要加();

            10. 水平/垂直留白不要濫用,怎么易讀怎么來(lái)。

            posted @ 2008-07-23 11:43 Fox 閱讀(4390) | 評(píng)論 (7)編輯 收藏

            僅列出標(biāo)題
            共7頁(yè): 1 2 3 4 5 6 7 
            久久66热人妻偷产精品9| 久久99国产精品久久99| 国产综合久久久久| 欧美激情精品久久久久久| 久久久久亚洲av无码专区导航| 久久99亚洲综合精品首页| 亚洲国产精品无码久久一区二区| 国产精品免费久久久久影院| 亚洲中文字幕久久精品无码APP| 精品久久久久久无码免费| 久久国产色AV免费看| 久久人人爽人人人人片av| 久久精品无码免费不卡| 国产精品视频久久| 中文字幕热久久久久久久| 亚洲а∨天堂久久精品| 国产激情久久久久影院小草 | 久久久久久精品久久久久| 久久av免费天堂小草播放| 97久久综合精品久久久综合| 狠狠色狠狠色综合久久| 日韩久久久久中文字幕人妻| 草草久久久无码国产专区| 91精品国产91久久久久福利| 亚洲AV日韩精品久久久久久久| 性高湖久久久久久久久AAAAA| 精品久久久久一区二区三区| 国产精品久久久久久久久免费| 色偷偷88888欧美精品久久久| 99久久国产亚洲综合精品| 亚洲精品国产自在久久| 欧美一级久久久久久久大| 久久免费观看视频| 久久天天躁狠狠躁夜夜av浪潮| 99久久99久久精品国产| 国产激情久久久久影院老熟女| 亚洲国产成人久久综合碰碰动漫3d| 91视频国产91久久久| 国内精品久久久久| 国产精品无码久久四虎| 国产综合精品久久亚洲|