• <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>

            堅持學習/暴露問題/不斷提升

            c++/設計模式/算法結構/系統
            posts - 2, comments - 20, trackbacks - 0, articles - 0

            本篇摘要

              交換兩個變量是非常古老的話題了,然而本文絕對保證給你新鮮的感覺!本文涉及到最簡單的“不用臨時變量交換兩個整數”還涉及到如果利用異或來實現兩個指針、兩個浮點數的交換,要知道指針的浮點數是不允許直接異或運算的哦;同時本文還闡述了如何交換用戶自定義類型及其指針。

            本文完全是個人自由發揮之作,歡迎廣大磚家來拍磚,我個人感覺文中必然有很多不足,甚至錯誤之處,這并非我謙虛,事實上我寫本文的目的就是希望在挨磚板中成長!新人或許看不懂很多東西,如果你看不懂,那么就不要隨便膜拜,因為看不懂的或許原本就是錯的,高手看完后如果本文寫得還可以,那么請留下好評,以供新手參考本文是否有閱讀本文的必要,如果覺得本文完全是垃圾,那么請不要客氣,您可以赤裸地,露骨地指出本文的錯誤和不足,讓本人在批評中進步,但請不要進行人身攻擊,謝謝!

            準備工作

              由于本文涉及到交換兩個用戶自定義類型的變量,為了舉例方便,本文定義如下的Person類(其中省略了拷貝構造函數的重寫,因為本文不用到它):

            class Person
            {
            public:
                     Person(
            int age ,const char* name ):m_Age(age)
                     {
                               
            int len = strlen(name);
                               
            this->m_Name = new char[len+1];
                               strcpy(
            this->m_Name,name);
                     }
                     Person()
                     {
                               
            this->m_Age = -1;
                               
            this->m_Name = 0;
                     }
                     
            void PrintSelf()
                     {
                               cout
            <<this->m_Name<<":"<<this->m_Age<<endl;
                     }
                     Person
            & operator= (const Person& other)
                     {
                               
            if (this == &other)
                               {
                                        
            return *this;
                               }
                               
            else
                               {
                                        
            this->m_Age = other.m_Age;
                                        delete 
            this->m_Name;
                                        
            int len = strlen(other.m_Name);
                                        
            this->m_Name = new char[len+1];
                                        strcpy(
            this->m_Name,other.m_Name);
                                        
            return *this;
                               }
                     }
                     
            ~Person()
                     {
                               delete 
            this->m_Name;
                     }
            private:
                     
            int m_Age;
                     
            char* m_Name;
            };

              為了后文表述方便,這里再定義Person類的兩個對象和兩個指針,定義如下:

            Person youngMan(18,” young man”);

            Person oldMan(81,” old man”);

            Person* pYoungMan = &youngMan;

            Person* pOldMan = &oldMan;

            最常見的交換兩個對象的方法:GeneralSwap

            通常,我們為了交換兩個變量都采取下面的方法來實現,它需要一個臨時變量:

            template<class T>
            void GeneralSwap(T& a,T& b)
            {
                     T temp;
                     temp 
            = a;
                     a 
            = b;
                     b 
            = temp;
            }

                顯然人人都知道這個寫法,但是我仍然覺得有必要重點申明幾點:1、注意函數的參數是引用(也可指針),為什么我就不解釋了;2、這個交換函數基本上是最簡單、最通用的,簡單到人人都會寫,通用到它幾乎可適用于任何數據類型:char , int , long , float, double等各種系統自定義數學類型(無符號的,帶符號的),用戶自定義數據類型(需要有默認構造函數,否則語句T temp;會報錯),以及各種指針(系統自定義類型的指針,和用戶自定義類型的指針)。當然用戶自定義類型中如果包含了指針數據成員,那么需要重載賦值運算符,事實上這樣的用戶自定義類,你都應該自己重寫賦值運算符、拷貝構造函數,否則不但不能使用GeneralSwap,其他涉及到拷貝和賦值的操作都可能導致出錯!

            利用GeneralSwap交換兩個用戶自定義對象

                下面深入探討一下關于用戶自定義對象的交換問題:針對準備工作中的Person類的兩個對象youngMan和oldMan語句GeneralSwap(youngMan,oldMan);能實現他們的交換。短短一行代碼就能實現將一個18歲的花季少男跟一個81歲的老頭子掉包,這像不像是耍魔術啊,呵呵。要注意了,該交換代碼雖短,但涉及到默認構造函數的調用(GeneralSwap中的T temp;語句)和賦值運算符重載函數的調用(GeneralSwap中的三個賦值語句)。

            或許您很少這么用吧,事實上在我寫本文之前,我都沒真正交換過兩個自定義的對象,通常我們都不愿意這么交換兩個自定義對象。原因是效率太低!或許你要問,萬一有的應用就是需要交換兩個自定義的對象怎么辦?好辦,用指針啊!對,指針的好處就是效率高,為什么C++比java效率高,原因之一就是java取消了指針。下面的第一行代碼就是交換兩個Person類的指針:

            GeneralSwap(pYoungMan,pOldMan);

            //GeneralSwap(*pYoungMan,* pOldMan);     //效率低

                為什么使用指針就效率高了呢?原因是指針就是地址,地址就是整數,于是問題等價于交換兩個整數,因此它不調用賦值運算符重載函數!只要你在應用程序中始終通過指向對象的指針來訪問對象,那么交換兩個指針就能達到交換對象的目的。注意被注釋掉的第二行代碼,它是正確的,但是它又回到了交換兩個實際對象,其效率低,最好不要這么用!

            對于這個最常見、最簡單的GeneralSwap我都廢話了一大堆,而且還扯出了一個沒多少用的關于用戶自定義對象的交換問題,這實屬個人思維散射,請磚家們狠狠地拍。

            在進行下一個方法之前,再次強調一點,這個方法的特點是簡單、通用!后面的方法都將與之做比較。

            利用加減法實現兩個數的交換

                幾乎人人都知道還可以利用加減法來實現兩個數的交換,其代碼也異常簡單:

            template<class T>
            void Add_Sub_Swap_1(T& a, T& b)
            {
                    a 
            = a+b;
                    b 
            = a-b;
                     a 
            = a-b;
            }

                Add_Sub_Swap_1可以用于交換兩個整數,但由于涉及加減法,因此有數據溢出的危險;也可以用于交換浮點數,但是有可能由于舍入誤差導致結果不準確。

            Add_Sub_Swap_1不能用于交換兩個用戶自定義的對象,下面的語句編譯就通過不,編譯器告訴你Person類沒有定義operator +等符號:

            Add_Sub_Swap_1(youngMan,oldMan);//編譯通不過!

                Add_Sub_Swap_1不能用于交換兩個指針,語句Add_Sub_Swap_1(pYoungMan,pOldMan);編譯時將報錯:error C2110: cannot add two pointers,是的,兩個指針不能直接做加法運算(減法是可以的)。那么是不是就不能利用加減法實現兩個指針的交換呢?答案是:“可以!”,接下來我將闡述如何實現。

            利用加減法交換兩個指針

                Add_Sub_Swap_1不能用于交換兩個指針,前面我說可以用加減法來實現兩個指針的交換,這是有根據的:指針仍然是變量,只不過它是存儲普通變量的地址的變量。只要我們把指針“看作”變量,那么就能實現加法。那么如何把指針“看作”變量呢?答案是:“通過強制類型轉換”!指針表示變量的地址,在32位平臺上它是一個無符號的整數,因此可以將指針強制轉換為無符號類型的整數。我對上面的Add_Sub_Swap_1進行了改進:

            template<class T>
            void Add_Sub_Swap_2(T& a, T& b)
            {
                     
            *(( unsigned*)(&a)) = *(( unsigned*)(&a)) + *(( unsigned*)(&b));
                     
            *(( unsigned*)(&b)) = *(( unsigned*)(&a)) - *(( unsigned*)(&b));
                     
            *(( unsigned*)(&a)) = *(( unsigned*)(&a)) - *(( unsigned*)(&b));
            }

                利用Add_Sub_Swap_2既可以交換兩個普通的整數、浮點數同時它可以交換兩個任意類型的指針(包含系統預定義類型和用戶自定義類型的指針,其實本質上所有指針都屬于同一種類型:32位無符號整數類型)。不信您試試Add_Sub_Swap_2(pYoungMan,pOldMan);它能得到正確答案。

            雖然Add_Sub_Swap_2解決了Add_Sub_Swap_1無法交換兩個指針的問題,但是它仍然無法交換兩個用戶自定義類型的變量,原因是用戶自定義類型沒有加減法運算。看來要想用加減法實現兩個用戶定義類型的交換是不可能的了(除非用戶自定義的operator+和operator-能滿足交換兩個對象的目的,這很難,除非是非常簡單的用戶自定義類型,比如你不使用系統類型int非要定義一個MyInt類)。

            利用異或實現兩個整數的交換

                同樣地,幾乎人人都知道利用異或來交換兩個數,其實現也非常簡單:

            template <class T>
            void Xor_Swap_1(T& a,T& b)
            {
                     a 
            = a^b;
                     b 
            = a^b;
                     a 
            = a^b;
            }

                上面的函數的實用性非常有限,它只能交換兩個整數(包含char,int,long),要想交換兩個浮點數是不行的,因為浮點數不能參與位運算,要想交換兩個指針也是不行的,編譯器不允許你把兩個指針拿來做位運算,要想交換兩個用戶自定義對象也是不行的,因為它仍然不能參與位運算。那么是不是利用異或交換兩個變量就沒法用于浮點數、指針和用戶自定義的對象了呢?答案是“能”!后面幾節我將闡述這些問題。

            利用異或實現兩個float和指針的交換

                前面的Xor_Swap_1無法實現兩個浮點數和指針的交換,其原因是浮點數和指針均不直接支持位運算。那么如何才能利用異或來交換兩個浮點數和指針呢?方法仍然是“強制類型轉換”!因為浮點數在內存中仍然是用一串二進制bit來表示的嘛,只要把浮點數看作(強制類型轉換)二進制bit構成的整數,那么就能進行位運算了,至于指針嘛,處理方法完全相同。具體如何做呢,其實現大概是這樣的:

            template <class T>
            void Xor_Swap_2(T& a,T& b)
            {
                     
            *((unsigned*)(&a)) = *((unsigned*)(&a)) ^ *((unsigned*)(&b));
                     
            *((unsigned*)(&b)) = *((unsigned*)(&a)) ^ *((unsigned*)(&b));
                     
            *((unsigned*)(&a)) = *((unsigned*)(&a)) ^ *((unsigned*)(&b));
            }

                利用這個函數可以交換兩個float類型的變量,也可以交換任意類型的指針!非常值得注意的是:用它交換兩個double類型數據或者兩個Person類的對象(youngMan,oldMan)均能編譯通過,但是其結果卻是錯的。至于為什么,以及如何解決,這將是我下一節要闡述的內容。

            利用異或實現兩個double類型變量和用戶自定義變量的交換

                 Xor_Swap_2解決了利用異或不能交換兩個float數據和指針的問題,然而它卻不能正確地交換兩個double數據和兩個Person類對象。這是為什么呢?原因是函數內部是把參數強制類型轉換成unsigned類型的,而sizeof(float)和sizeof(pointor)的值都等于sizeof(unsigned),但是sizeof(double)卻不等于sizeof(unsigned),也就是說把double強制轉換成unsigned類型時,發生了“位截斷”(在概念是區別與數據截斷),那么得到的結果肯定就不對了。至于無法交換兩個Person類對象,其原因也相同。

            這里我要深入分析一下強制類型轉換是如何發生位截斷的,首先看看以下測試的輸出結果,注意代碼中的注釋,為了節約篇幅,我把值得注意的地方都放在注釋中了:

            Double a = 1.0,b=2.0;

            Xor_Swap_2(a,b);//交換兩個double數據

            Cout<<a<<b;//輸出仍然是1.0和2.0,a,b的值并未改變

            Xor_Swap_2(youngMan,oldMan);//交換兩個用戶自定義對象

            youngMan.PrintSelf();//輸出young man:81

            oldMan.PrintSelf();//輸出old man:18

                可以看出兩個double數據并沒被交換,而兩個Person對象在交換之后發生了怪異現象:產生了81歲的年輕人和18歲的老年人!這一點正好說明強制類型轉換時發生了位截斷,由于Person類的第一個數據成員m_Age正好是int型,在Xor_Swap_2內部做強制類型轉換時正好取得了兩個對象的m_Age成員,于是出現了兩個對象被部分交換的情況,那么又如何解釋兩個double數據沒有變法呢?事實上兩個double數據仍然發生了部分交換,因為這里的兩個double數(a,b)的前4個字節正好相同,因此看不出部分交換。

            既然我們知道了Xor_Swap_2為什么不能用于交換兩個double類型的數據和兩個用戶自定義的數據,那么就有辦法對它進行改進。具體改進的思想就是把參數按照一個byte一個byte地分別異或,按照這個思路我實現了如下的函數:

            template <class T>
            void Xor_Swap_3(T& a,T& b)
            {
                     
            int size = sizeof(T);
                     
            for (int i = 0;i<size;i++)
                     {
                               
            *((unsigned char*)(&a)+i) = (*((unsigned char*)(&a)+i)) ^ (*((unsigned char*)(&b)+i));
                               
            *((unsigned char*)(&b)+i) = (*((unsigned char*)(&a)+i)) ^ (*((unsigned char*)(&b)+i));
                               
            *((unsigned char*)(&a)+i) = (*((unsigned char*)(&a)+i)) ^ (*((unsigned char*)(&b)+i));
                     }
            }

                 這個版本的函數不僅能交換兩個整數、任何指針、float數和double數,更牛逼的是它能交換兩個用戶定義類型的變量!事實上它基本上是在內存一級上操作數據,而任何類型的數據對象最終都表現為內存對象。這其實就是通過內存拷貝實現兩個對象的交換的一個版本吧,當然還有利用memcpy等手段進行內存拷貝來實現兩個變量的交換的,這里我就不贅述了。

            結束語

                本篇到此寫完了,有種不好的感覺,因為文中大量使用“強制類型轉換”而這個東西是C++中容易出錯的地方,而我再寫本文時,并沒有去復習關于強制類型轉換的相關知識,因此擔心很多地方有潛在的出錯可能,還請各位磚家指正!

            @import url(http://www.shnenglu.com/CuteSoft_Client/CuteEditor/Load.ashx?type=style&file=SyntaxHighlighter.css);@import url(/css/cuteeditor.css);

            Feedback

            # re: 絕對深入剖析各種方法實現兩個變量的交換[未登錄]  回復  更多評論   

            2011-08-10 13:11 by Chipset
            這不是4位機和8位機的時代了,這種交換容易出錯。如果兩個數相同這種交換是不是會出錯?大的數據結構交換用move語義,C++0x,此時依賴大量數據拷貝的交換永遠快不起來。

            # re: 絕對深入剖析各種方法實現兩個變量的交換  回復  更多評論   

            2011-08-10 17:22 by 他她女鞋
            好好了解學習一下。

            # re: 絕對深入剖析各種方法實現兩個變量的交換  回復  更多評論   

            2011-08-10 21:33 by 瘋狂的面包
            還是 ^ 操作 本質還是玩這個。 我看不到有什么新意。還要注意自己和自己交換。

            # re: 絕對深入剖析各種方法實現兩個變量的交換  回復  更多評論   

            2011-08-11 13:33 by right
            雖然這種探索的意義不大,樓主的探索精神還是值得表揚的。
            提一個函數,我沒仔細看,可能樓主用的上
            float IntBitsToFloat(int x)
            {
            union
            {
            int n;
            float f;
            } m;
            m.n = x;
            return m.f;
            }
            int FloatToIntBits(float x)
            {
            union
            {
            float f;
            int n;
            } m;
            m.f = x;
            return m.n;
            }
            亚洲午夜精品久久久久久app| 亚洲国产精品无码久久一线| 国产精品亚洲美女久久久| 日韩欧美亚洲国产精品字幕久久久 | 国产99久久精品一区二区| 久久亚洲国产午夜精品理论片| 久久久亚洲精品蜜桃臀| 久久精品亚洲日本波多野结衣| 99久久国产综合精品女同图片| 国产成年无码久久久久毛片| 中文字幕久久精品无码| 老男人久久青草av高清| 国产69精品久久久久APP下载| 日韩一区二区三区视频久久| 久久久久久亚洲精品无码| 国内精品久久久久久久久电影网| 日本福利片国产午夜久久| 国产亚洲成人久久| 国内精品欧美久久精品| 午夜肉伦伦影院久久精品免费看国产一区二区三区 | 色综合久久中文字幕无码| 亚洲精品无码久久久影院相关影片| 久久亚洲AV无码精品色午夜麻豆| 久久精品人人做人人爽电影| 久久久久久综合网天天| 久久综合给合久久狠狠狠97色 | 久久Av无码精品人妻系列| 国产精品久久久久久吹潮| 18岁日韩内射颜射午夜久久成人| 久久久99精品成人片中文字幕| 伊人久久五月天| 国产精品99久久99久久久| 久久AAAA片一区二区| 久久精品国产亚洲αv忘忧草| 一本久道久久综合狠狠爱| 久久er热视频在这里精品| 亚洲精品美女久久久久99小说| 国内精品伊人久久久久777| 国产精品美女久久久久| 久久国产免费| 91精品国产高清91久久久久久|