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

            2011年8月10日

            本篇摘要

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

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

            準備工作

              由于本文涉及到交換兩個用戶自定義類型的變量,為了舉例方便,本文定義如下的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);

            posted @ 2011-08-10 11:29 二狗子_五哥 閱讀(3510) | 評論 (4)編輯 收藏

            2011年8月9日

            摘要:

            Sizeof的作用非常簡單:求對象或者類型的大小。然而sizeof又非常復雜,它涉及到很多特殊情況,本篇把這些情況分門別類,總結出了sizeof的10個特性:

            (0)sizeof是運算符,不是函數;

            (1)sizeof不能求得void類型的長度;

            (2)sizeof能求得void類型的指針的長度;

            (3)sizeof能求得靜態分配內存的數組的長度!

            (4)sizeof不能求得動態分配的內存的大小!

            (5)sizeof不能對不完整的數組求長度;

            (6)當表達式作為sizeof的操作數時,它返回表達式的計算結果的類型大小,但是它不對表達式求值!

            (7)sizeof可以對函數調用求大小,并且求得的大小等于返回類型的大小,但是不執行函數體!

            (8)sizeof求得的結構體(及其對象)的大小并不等于各個數據成員對象的大小之和!

            (9)sizeof不能用于求結構體的位域成員的大小,但是可以求得包含位域成員的結構體的大小!

            概述:

            Sizeof是C/C++中的關鍵字,它是一個運算符,其作用是取得一個對象(數據類型或者數據對象)的長度(即占用內存的大小,byte為單位)。其中類型包含基本數據類型(不包括void)、用戶自定義類型(結構體、類)、函數類型。數據對象是指用前面提到的類型定義的普通變量和指針變量(包含void指針)。不同類型的數據的大小在不同的平臺下有所區別,但是c標準規定所有編譯平臺都應該保證sizeof(char)等于1。關于sizeof的更多概述你可以在msdn總輸入sizeof進行查詢。

            看了上面這些,或許你看了沒有多少感覺。沒關系,下面我將詳細列出sizeof的諸多特性,這些特性是造成sizeof是一個較刁鉆的關鍵字的原因:

            十大特性:

            特性0:sizeof是運算符,不是函數

            這個特性是sizeof的最基本特性,后面的很多特性都是受到這個特性的影響,正因為sizeof不是函數,因此我們不把它所要求得長度的對象叫做參數,我本人習慣上叫做操作數(這不嚴謹,但是有助于我記住sizeof是個操作符)。

            特性1:sizeof不能求得void類型的長度

            是的,你不能用sizeof(void),這將導致編譯錯誤:illegal sizeof operand。事實上你根本就無法聲明void類型的變量,不信你就試試void a;這樣的語句,編譯器同樣會報錯:illegal use of type 'void'。或許你要問為什么,很好,學東西不能只知其然,還要知其所以然。我們知道聲明變量的一個重要作用就是告訴編譯器該變量需要多少存儲空間。然而,void是“空類型”,什么是空類型呢,你可以理解成不知道存儲空間大小的類型。既然編譯器無法確定void類型的變量的存儲大小,那么它自然不讓你聲明這樣的變量。當然了,聲明void類型的指針是可以的!這就是特性2的內容。

            特性2:sizeof能求得void類型的指針的長度

            在特性1中說過,可以申明void類型的指針,也就是說編譯器可以確定void類型的指針所占用的存儲空間。事實上確實如此,目前,幾乎所有平臺上的所有版本的編譯器都把指針的大小看做4byte,不信你試試sizeof(int*);sizeof(void*);sizeof(double*);sizeof(Person*);等等,它們都等于4!為什么呢?問得好,我將盡全力對此作出解釋:其實指針也是變量,只不過這個變量很特殊,它是存放其他變量的地址的變量。又由于目前32位計算機平臺上的程序段的尋址范圍都是4GB,尋址的最小單元是byte,4GB等于232Byte,這么多的內存其地址如果編碼呢,只需要用32個bit就行了,而32bit = 32/8 = 4byte,也就是說只需要4byte就能存儲這些內存的地址了。因此對任何類型的指針變量進行sizeof運算其結果就是4!

            特性3:sizeof能求得靜態分配內存的數組的長度!

            Int a[10];int n = sizeof(a);假設sizeof(int)等于4,則n= 10*4=40;特別要注意:char ch[]=”abc”;sizeof(ch);結果為4,注意字符串數組末尾有’\0’!通常我們可以利用sizeof來計算數組中包含的元素個數,其做法是:int n = sizeof(a)/sizeof(a[0]);

            非常需要注意的是對函數的形參數組使用sizeof的情況。舉例來說,假設有如下的函數:

            void fun(int array[10])

            {

                     int n = sizeof(array);

            }

            你會覺得在fun內,n的值為多少呢?如果你回答40的話,那么我很遺憾的告訴你,你又錯了。這里n等于4,事實上,不管形參是int的型數組,還是float型數組,或者其他任何用戶自定義類型的數組,也不管數組包含多少個元素,這里的n都是4!為什么呢?原因是在函數參數傳遞時,數組被轉化成指針了,或許你要問為什么要轉化成指針,原因可以在很多書上找到,我簡單說一下:假如直接傳遞整個數組的話,那么必然涉及到數組元素的拷貝(實參到形參的拷貝),當數組非常大時,這會導致函數執行效率極低!而只傳遞數組的地址(即指針)那么只需要拷貝4byte

            特性4:sizeof不能求得動態分配的內存的大小!

            假如有如下語句:int* a = new int[10];int n = sizeof(a);那么n的值是多少呢?是40嗎?答案是否定的!其實n等于4,因為a是指針,在特性2中講過:在32位平臺下,所有指針的大小都是4byte!切記,這里的a與特性3中的a并不一樣!很多人(甚至一些老師)都認為數組名就是指針,其實不然,二者有很多區別的,要知詳情,請看《c專家編程》。通過特性3和特性4,我們看到了數組和指針有著千絲萬縷的關系,這些關系也是導致程序潛在錯誤的一大因素,關于指針與數組的關系問題我將在《C/C++刁鉆問題各個擊破之指針與數組的秘密》一文中進行詳細介紹。

            特性3指出sizeof能求靜態分配的數組的大小,而特性4說明sizeof不能求的動態分配的內存的大小。于是有人認為sizeof是編譯時進行求值的,并給出理由:語句int array[sizeof(int)*10];能編譯通過,而很多書上都說過數組大小是編譯時就確定下來的,既然前面的語句能編譯通過,所以認為sizeof是編譯時進行求值的。經過進一步測試我發現這個結論有些武斷!至少是有些不嚴謹!因為在實現了c99標準的編譯器(如DEV C++)中可以定義動態數組,即:語句:int num;cin>>num; int arrary[num];是對的(注意在vc6.0中是錯的)。因此我就在DEV C++中對剛才的array利用語句int n =sizeof(array);cout<<n<<endl來求大小,結果編譯通過,運行時輸入num的值10之后,輸出n等于40!在這里很明顯num的值是運行時才輸入的,因此sizeof不可能在編譯時就求得array的大小!這樣一來sizeof又變成是運行時求值的了。

            那么到底sizeof是編譯時求值還是運行時求值呢?最開初c標準規定sizeof只能編譯時求值,后來c99又補充規定sizeof可以運行時求值。但值得注意的是,即便是在實現了c99標準的DEV C++中仍然不能用sizeof求得動態分配的內存的大小!

            特性5:sizeof不能對不完整的數組求長度!

            在闡述該特性之前,我們假設有兩個源文件:file1.cpp和file2.cpp,其中file1.cpp中有如下的定義:

            int arrayA[10] = {1,2,3,4,5,6,7,8,9,10};

            int arrayB[10] = {11,12,13,14,15,16,17,18,19,20};

            file2.cpp包含如下幾個語句:

                     extern arrayA[];

                     extern arrayB[10];

                     cout<<sizeof(arrayA)<<endl;            //編譯出錯!!

                     cout<<sizeof(arrayB)<<endl;

            在file2.cpp中第三條語句編譯出錯,而第條語句正確,并且能輸出40!為什么呢?原因就是sizeof(arrayA)試圖求不完整數組的大小。這里的不完整的數組是指數組大小沒有確定的數組!sizeof運算符的功能就是求某種對象的大小,然而聲明:extern int arrayA[]只是告訴編譯器arrayA是一個整型數組,但是并沒告訴編譯器它包含多少個元素,因此對file2.cpp中的sizeof來說它無法求出arrayA的大小,所以編譯器干脆不讓你通過編譯。

            那為什么sizeof(arrayB)又可以得到arraryB的大小呢?關鍵就在于在file2.cpp中其聲明時使用extern int arrayB[10]明確地告訴編譯器arrayB是一個包含10個元素的整型數組,因此大小是確定的。

            到此本特性講解差不多要結束了。其實本問題還能引申出連接和編譯等知識點,但是目前我暫時還沒自信對這兩個知識點進行詳細的,徹底的講解,因此不便在此班門弄斧,不久的將來我會在本系列中加上相關問題的闡述。

            特性6:當表達式作為sizeof的操作數時,它返回表達式的計算結果的類型大小,但是它不對表達式求值!

            為了說明這個問題,我們來看如下的程序語句:

            char ch = 1;

                     int num=1;

                     int n1 = sizeof(ch+num);

                     int n2 = sizeof(ch = ch+num);

            假設char占用1byte,int占用4byte,那么執行上面的程序之后,n1,n2,ch的值是多少呢?我相信有不少人會認為n1與n2相等,也有不少人認為ch等于2,事實這些人都錯了。事實上n1等于4,n2等于1,ch等于1,為什么呢?請看分析:

            由于默認類型轉換的原因,表達式ch+num的計算結果的類型是int,因此n1的值為4!而表達式ch=ch+num;的結果的類型是char,記住雖然在計算ch+num時,結果為int,但是當把結果賦值給ch時又進行了類型轉換,因此表達式的最終類型還是char,所以n2等于1。n1,n2的值分別為4和1,其原因正是因為sizeof返回的是表達式計算結果的類型大小,而不是表達式中占用最大內存的變量的類型大小!

            對于n2=sizeof(ch =ch+num);乍一看該程序貌似實現了讓ch加上num并賦值給ch的功能,事實并非如此!由于sizeof只關心類型大小,所以它自然不應該對表達式求值,否則有畫蛇添足之嫌了。但是,在支持變長數組定義的(即實現了c99標準的)編譯器(dev C++)中執行了int len = 3;cout<<sizeof(int [++len])<<”,”<<len;輸出是多少呢?答案是16,4!這里的++len卻執行了!很不可理喻吧?這到底是為什么呢?我翻閱了《The New C Standard》一書,這主要是由于可變長度的數組的長度需要在其長度表達式求值之后才能確定大小,因此上述情況下,sizeof中的++len執行了。

            正是因為sizeof的操作數中的某些表達式會被執行,而有些表達式不會被執行,這里告誡各位,盡量不要在sizeof中直接對表達式求大小,以免出現錯誤,你可以將sizeof(ch = ch+num);改寫成 ch = ch +num;sizeof(ch);雖然多了一條語句,看似冗余了,其實好處多多:首先更加清晰明了,其次不會出現ch等于1這樣的錯誤(假設程序的邏輯本身就是要執行ch = ch +num;)。

            特性7:sizeof可以對函數調用求大小,并且求得的大小等于返回類型的大小,但是不執行函數體!

            假設有如下函數(是一個寫得很不好的函數,但是能很好的說明需要闡述的問題):

            int fun(int& num,const int& inc)

            {

                     float div = 2.0;

                     double ret =0;

                     num = num+inc;

                     ret = num/div;

                     return ret;

            }那么語句:

            int a = 3;

                     int b = 5;

                     cout<<sizeof(fun(a,b))<<endl;

                     cout<<a<<endl;輸出多少呢?不同的人會給出不同的答案,我將對sizeof(fun(a,b))的值和a的值分別進行討論:

            首先sizeof(fun(a,b))的值:其正確是4,因為用sizeof求函數調用的大小時,它得到的是函數返回類型的大小,而fun(a,b)的返回類型是int,sizeof(int)等于4。很多人把函數的返回類型返回值的類型弄混淆了,認為sizeof(fun(a,b))的值是8,因為函數返回值是ret,而ret被定義成double,sizeof(doube)等于8。注意,雖然函數返回值類型是double,但是在函數返回時,將該值進行了類型轉換(這里的轉換不安全)。也有人錯誤的認為sizeof(fun(a,b))的值是12,它們的理由是:fun內部定義了兩個局部變量,一個是float一個是double,而sizeof(float)+sizeof(doube)= 4+8=12。這樣的答案看似很合理,其實他們是錯誤地認為這里的sizeof是在求函數內部的變量的大小了。這當然是錯誤的。

            接下來看a的值:其正確答案是3!還記得特性6嗎?這里很類似,sizeof的操作對象是函數調用時,它不執行函數體!為此,建議大家不要把函數體放在sizeof后面的括號里,這樣容易讓人誤以為函數執行了,其實它根本沒執行。

            既然對函數條用使用sizeof得到的是函數返回類型的大小,那么很自然能得出這樣的結論:不能對返回類型為void的函數使用sizeof求其大小!原因請參考特性1。同理,對返回類型是任何類型的指針的函數調用使用sizeof求得的大小都為4,原因請參考特性2。

            最后我們來看看這樣的語句:cout<<sizeof(fun);其答案是多少呢?其實它得不到答案,原因是編譯就通不過!最開始,我以為能輸出答案4,因為我認為fun是函數名,而我知道函數名就是函數的地址,地址就是指針,于是我認為sizeof(fun)其實就是對一個指針求大小,根據特性2,任何指針的大小都是4。可是當我去驗證時,編譯器根本不讓我通過!這個是為什么呢?我一時半會想不到,所以還請朋友們補充!

            特性8:sizeof求得的結構體(及其對象)的大小并不等于各個數據成員對象的大小之和!

            結構體的大小跟結構體成員對齊有密切關系,而并非簡單地等于各個成員的大小之和!比如對如下結構體兩個結構體A、B使用sizeof的結果分別是:16,24。可以看出sizeof(B)并不等于sizeof(int)+sizeof(double)+sizeof(int)=16。

            struct A{

                     int num1;

                     int num2;

                     double num3;

            };

            struct B{

                     int num1;

                     double num3;

                     int num2;

            };

            如果您不了解結構體的成員對齊,你會感到非常驚訝:結構體A和B中包含的成員都一樣,只不過順序不同而已,為什么其大小不一樣呢?要解釋這個問題,就要了解結構體成員對齊的規則,由于結構體成員對齊非常復雜,我將用專題——C/C++刁鉆問題各個擊破之位域和成員對齊——進行講解,這里我只簡單地介紹其規則:

            1、 結構體的大小等于結構體內最大成員大小的整數倍

            2、 結構體內的成員的首地址相對于結構體首地址的偏移量是其類型大小的整數倍,比如說double型成員相對于結構體的首地址的地址偏移量應該是8的倍數。

            3、 為了滿足規則1和2編譯器會在結構體成員之后進行字節填充!

             

            基于上面三個規則我們來看看為什么sizeof(B)等于24:首先假設結構體的首地址為0,第一個成員num1的首地址是0(滿足規則2,前面無須字節填充,事實上結構體絕對不會在第一個數據成員前面進行字節填充),它的類型是int,因此它占用地址空間0——3。第二個成員num3是double類型,它占用8個字節,由于之前的num1只占用了4個字節,為了滿足規則2,需要使用規則3在num1后面填充4個字節(4——7),使得num3的起始地址偏移量為8,因此num3占用的地址空間是:8——15。第三個成員num2是int型,其大小為4,由于num1和num3一共占用了16個字節,此時無須任何填充就能滿足規則2。因此num2占用的地址空間是16——19。那么是不是結構體的總大小就是0——19共20個字節呢?請注意,別忘了規則1!由于結構體內最大成員是double占用8個字節,因此最后還需要在num2后面填充4個字節,使得結構體總體大小為24。

            按照上面的三個規則和分析過程,你可以很容易地知道為什么sizeof(A)等于16。特別需要說明的是,我這里給出了三個結論性的規則,而沒有闡述為什么要這樣。你或許有很多疑問:為什么要結構體成員對齊,為什么要定義規則1等。如果你有這樣的疑問,并嘗試去弄清楚的話,那么我敢斷言,不久的將來你必定會有大成就,至少在學習c++上是這樣。前面說過,我會再寫一篇專題:C/C++刁鉆問題各個擊破之位域和成員對齊來詳細回答這些問題,如果你急于要弄明白,那么你可以參考其他資料,比如說《高質量c++程序設計指南》。

            最后再提醒一點,在進行設計時,最好仔細安排結構體中各個成員的順序,因為你已經看到了上面的結構體B與結構體A包含的成員相同,只不過順序略有差異,最終就導致了B比A多消耗了50%的空間,假如在工程中需要定義該結構體的數組,多消耗的空降將是巨大的。即使將來內存降價為白菜價格,你也不要忽視這個問題,勤儉節約是中國人民的優良傳統,我們應該繼承和保持!

            特性9:sizeof不能用于求結構體的位域成員的大小,但是可以求得包含位域成員的結構體的大小!

            首先解釋一下什么是位域:類型的大小都是以字節(byte)為基本單位的,比如sizeof(char)為1byte,sizeof(int)為4byte等。我們知道某個類型的大小確定了該類型所能定義的變量的范圍,比如sizeof(char)為1byte,而1byte等于8bit,所以char類型的變量范圍是-128——127,或者0——255(unsigned char),總之它只能定義28=256個數!然而,要命的是bool類型只取值true和false,按理所只用1bit(即1/8byte)就夠了,但事實上sizeof(bool)等于1。因此我們可以認為bool變量浪費了87.5%的存儲空間!這在某些存儲空間有限的設備(比如嵌入式設備)上是不合適的,為此需要提供一種能對變量的存儲空間精打細算的機制,這就是位域。簡單來說,在結構體的成員變量后面跟上的一個冒號+一個整數,就代表位域,請看如下的結構體:

            Struct A

            {

                     Bool b:1;

                     char ch1:4;

                     char ch2:4;

            }item; 其中b,ch1,ch2都是位域成員,而i是普通成員。該結構體的試圖讓bool類型的變量b只占用1個bit,讓ch1和ch2分別只占用4個bit,以此來達到對內存精打細算的功能(事實上使用位域對內存精打細算有時候能成功,有時候卻未必,我將《C/C++刁鉆問題各個擊破之位域和成員對齊》進行論述)。另外需要特別注意的是:c語言規定位域只能用于int,signed int或者unsigned int類型,C++又補充了char和long類型!你不能這樣使用位域:float f:8;這是不能通過編譯的。并且位域變量不能在函數或者全局區定義,只能在結構體,自定義類,聯合(union)中使用!

            基于上面的結構體,語句sizeof(item.b)和sizeof(item.ch1)等對位域成員求大小的語句均不能通過編譯。其原因能再本篇的概論中找到:sizeof以byte為單位返回操作數的大小!

            那么愛學好問的你可能要問,sizeof(A)能否通過編譯呢?如何能,其結果又是多少呢?這是兩給非常好的問題,事實上我之前沒有看到任何關于這方面的論述(可能是我看的資料不足),我正是在看到sizeof(item.b)不能通過編譯時想到了這兩個問題,然后通過驗證得出了后面的結論:對包含位域的結構體是可以使用sizeof求其大小的,但其求值規則比較復雜,不僅涉及到成員對齊,還與具體編譯環境有關!在這里你只需要知道可以對包含位域的結構體使用sizeof求其大小,對于sizeof是根據什么規則來求這個大小的問題,我將會在專題:《C/C++刁鉆問題各個擊破之位域和成員對齊》中進行詳細闡述

            后記:

            至此,本專題差不多該結束了,需要說明的是,這里并沒有包含所有關于sizeof的知識點,但是也幾乎包含了所有的容易出錯的特性。為了完成該文,我花了斷斷續續3天半時間,想想效率實在是底下。由于是本系列的第一個專題,我格外慎重,深怕講錯了誤導大家。即便如此,也難免錯誤或不妥之處,還請各位朋友指正!

            另外,我有幾句話要對大學生朋友們說:教科書通常只是教授很基礎的知識,要想深入學習,還需要翻閱其他資料,比如論文、網絡資料、論壇博文,最重要的一點是要在學習時經常總結、記錄、歸納,積少成多,這樣堅持下來一定受益匪淺。 @import url(http://www.shnenglu.com/CuteSoft_Client/CuteEditor/Load.ashx?type=style&file=SyntaxHighlighter.css);@import url(/css/cuteeditor.css);

            posted @ 2011-08-09 10:31 二狗子_五哥 閱讀(6071) | 評論 (16)編輯 收藏

            久久人人爽人人爽人人片AV东京热| 亚洲va国产va天堂va久久| 国产毛片欧美毛片久久久| 久久久久无码中| 久久人人爽人人爽人人片AV麻豆| 久久综合中文字幕| aaa级精品久久久国产片| 99久久国语露脸精品国产| 国产Av激情久久无码天堂| 久久久久99精品成人片欧美| 亚洲国产精品久久久天堂| 色诱久久久久综合网ywww| 久久香蕉超碰97国产精品| 久久精品国产亚洲精品2020| 国产精品美女久久久久久2018| 国产人久久人人人人爽| 青草影院天堂男人久久| 久久久人妻精品无码一区| 久久综合鬼色88久久精品综合自在自线噜噜| 少妇久久久久久被弄到高潮| 无码人妻久久一区二区三区蜜桃| 亚洲精品乱码久久久久久按摩| 国产精品久久久久久久久| 久久电影网| 日韩精品久久久久久久电影蜜臀| 久久久噜噜噜久久熟女AA片| 品成人欧美大片久久国产欧美| 中文字幕亚洲综合久久菠萝蜜| 久久久久AV综合网成人| 国产激情久久久久影院老熟女免费| 一本久久a久久精品综合香蕉| 久久精品国产久精国产思思| 久久精品成人免费国产片小草| 99久久精品国产一区二区| 88久久精品无码一区二区毛片| 久久久精品人妻一区二区三区蜜桃| 久久精品国产亚洲AV高清热| 国产精品美女久久福利网站| 久久最近最新中文字幕大全| 久久久久亚洲av成人网人人软件| 国产A级毛片久久久精品毛片|