C++主題年技巧積累#2——我被static撞了一下腰

51CTO旗下水之真諦( [url]http://liutiemeng.blog.51cto.com[/url])出品
 
前傳:

         剛剛參加博文視點出版社三週年慶典回來,興奮之餘想到今天尚未更新Blog,因而跑上來更新一下——我儘可能「好好學習,每天上博」。哎呀,今天見到好多名人啊!先是在金戈老師旁邊坐下,而後又去問候了久仰大名的孟巖老師,在孟老師的幫助下又找到《Beginning C# Object》的譯者,也是本次晚會的攝像師——韓磊老師,你問韓磊唱歌沒?沒有!歌被歐陽璟(《程序員》的老編,大帥哥!仍是本次年會的攝影師)給唱了。晚會的精彩那沒的說,你們等着看視頻吧,估計CSDN上不久就會放出來。會間在楊福川的帶領下,先是見到了梁晶編輯,而後又見到了親自爲我斧正譯文的方舟老師。方舟老師人真不錯,幾分鐘裏還抓緊時間教我應該怎麼譯技術文章,怎麼理順段落、句型,如何重組定語、去掉沒必要要的連詞……在這裏,我深深地向您鞠上一躬,道一聲:謝謝!同時也感謝梁編輯的不懈努力!接下來,我有幸見到了博文視點的周筠老師和CSDN的總裁蔣濤先生。過程見還見到了不少平時「只見MSN不見人」的朋友,其中包括帥哥佘廣。最後還有機會見到技術專家金旭亮老師。
         慶典參加後,感受博文視點出版社和CSDN(包括《程序員》雜誌)真是兩個充滿激情、活力四射的團隊!衷心地祝福博文視點出版社出版更多優秀的技術書籍——就像席間韓磊老師說的:出一本是一本,不糟踏書,人家買書,是真想學東西啊!也祝CSDN和《程序員》雜誌越辦越紅火!
打油詩一首,贈予辛勤工做在出版一線的朋友們,祝大家的2007年萬事如意!詩名《日出》,意思是但願博文視點出版社能「每天出書,每天出好書」——夫,日出,日日出,又日出!
日出
    學海茫茫尋師苦,
    博文樂把衆人渡。
    書香滿載齊揮槳,
    破浪乘風向日出!
 
小序:

         咳咳,收心啦收心啦!熱鬧是人家的,知識是本身的。仍是收回心來寫本身的技術心得吧。話說「由儉入奢易,由奢入儉難」啊……這話一點不假,連寫程序都是這樣。像我,之前用慣了語法舒服流暢的C#,再回過頭來學C++就深入地體驗到了這一點:C#之因此用起來簡潔舒服,是由於它把不少應該由程序員本身作的事情(好比分配和釋放內存)都替程序員作了。換句話說,C#和C/C++的設計理念是不同的,C/C++假設程序作的一切事情(包括某些事情不去作)都是正確的,相信程序員是聰明人,決不爲程序員多作任何事情;C#正好反過來,認爲程序員應該不去關心底層這些東西、由程序員關注底層是容易出錯的、程序員總會忘記在該分配內存的地方分配內存或者在該釋放內存的地方釋放內存(微軟是懷疑程序員都是傻瓜仍是打算把程序員都培養成傻瓜?),總之,程序員應該更多地去關心架構和實現,而不是這些細枝末節的東西……
         因而,我被培養成了一個徹徹底底的「傻瓜型」程序員。因此,今天也就理所固然地被static關鍵字撞了一下腰、絆了一個跤。
 
正文:

         一切都源於我在寫練習程序時那一瞬間的妄想……
         static關鍵字?小菜,C#和C++裏都有,原理是同樣的,會了C#還寫不出C++的來?看着!咱這就同樣寫一個出來!
         先來C#的!
//----------------水之真諦----------------//
//  [url]http://blog.csdn.net/FantasiaX[/url]
//-----------------------------------------------//
using System;
class Student
{
    public static void Report() // C#中成員方法的聲明和定義合二爲一
    {
        Console.WriteLine("I am a C# student.");
    }
}
class Program
{
    static void Main(string[] args)
    {
        Student.Report(); // OK
    }
}
 
         再來C++的!
#include <iostream>
class Student
{
public:
         static void Report()    // C++成員函數的聲明和定義亦可合二爲一,但決不是上策!
         {
                   std::cout<<"I am a C++ student."<<std::endl;
         }
};
int main(int argc, char *argv[])
{
         Student::Report(); // OK
         return 0;
}
 
         哈哈!一次編譯經過!我說嗎,會C#就會C++,沒問題的,只是語法結構上稍有不一樣。
         接下來再來兩個靜態成員變量的例子!仍是先寫C#的
//----------------水之真諦----------------//
//  [url]http://blog.csdn.net/FantasiaX[/url]
//-----------------------------------------------//
using System;
class Student
{
    public static string Name; // 猜猜在這裏C#都作了些什麼?
}
class Program
{
    static void Main(string[] args)
    {
        Student.Name = "Tim";  // OK
        Console.WriteLine(Student.Name);
    }
}
 
         乘勝追擊,再來C++的!
#include <iostream>
#include <string>
class Student
{
public:
         static std::string Name;  //猜猜C++在這裏作什麼了?
};
int main(int argc, char *argv[])
{
         Student::Name = "Tim";  //Error!!!   狂汗~~~ >_<
         std::cout<<Student::Name<<std::endl;
         return 0;
}
         啊~~~居然有錯誤!這是怎麼回事呢?不該該啊~~~C#裏明明能行的~~~
         百思不得其解後,拿出《C++ Primer 第四版(中文)》來查閱……果真找到不少有用信息。
l         P.399 Row4-5.         ……static數據成員獨立於該類的任意對象而存在……
l         P.399 Row5-6.         ……每一個static數據成員是與類關聯的對象,並不與該類的對象關聯……
l         P.401 Row1.             ……static數據成員必須在類定義體的外部定義(正好一次)。
註解:數據成員(Data Member)其實就是成員變量。
         上面最有用的信息就是第三條了:原來還須要在類的外部定義一次!因而修改代碼爲:
#include <iostream>
#include <string>
class Student
{
public:
         static std::string Name;  //猜猜C++在這裏作什麼了?
};
std::string Student::Name = std::string();  // 類外定義,這句是新添加的。
int main(int argc, char *argv[])
{
         Student::Name = "Tim";  // OK
         std::cout<<Student::Name<<std::endl;
         return 0;
}
 
         問題雖然解決了,但決不能輕易放過這個問題——爲何會這樣呢?
 
深挖:

         我把目光移到在《C++ Primer》裏找到的前兩條消息上。第二條與C#一致,說明不了什麼問題,第一條卻是讓人眼前一亮——static數據成員獨立於該類的任意對象而存在。果然是這樣嗎?我得本身動手驗證一下:
         先寫一個類:
class Student
{
public:
         int Age;
};
         而後執行 std::cout<<sizeof(Student)<<std::endl; 獲得的結果是4。
         把類改寫成這樣:
class Student
{
public:
         int Age;
         unsigned int StudentID;
};
         結果是8——預料之中。
         再改寫成:
class Student
{
public:
         int Age;
         unsigned int StudentID;
         double Score;
};
         結果是16——能夠理解。
         而後加一個static成員試試——
class Student
{
public:
         int Age;
         unsigned int StudentID;
         double Score;
         static int Amount;
};
         結果仍是16,沒有變!原來static的成員數據真的是獨立於類空間以外的!
         問題解決了嗎?No!不但沒有解決,反而增多了,一個問題變成了兩個問題——
1.         類所佔的空間本質是什麼?
2.         類的static數據成員究竟在哪裏?
第一個問題彷佛比較好回答:說「類所佔的空間」其實欠妥,應該是「類的實例佔內存的大小」。
拿上面這個類來講,它的每個實例中都將包含三個子數據成員(int Age, double Score, double Score),因此會佔去16個字節。而這16個字節將會由new操做符在內存中分配出來,並能夠在Student類的構造函數去初始化它們——咱們沒有顯式地初始化它們,因此它們的值是一個由上帝擲骰子得出來的值(使用VC執行的時候若是彈出錯誤警告,請按Ignore,我獲得的Age值是-858993460,陰壽乎?)。
問題回答完了嗎?沒有!
提問:分配了16個字節的內存說明了什麼問題?
回答:說明每一個非static成員變量都有本身的內存,疊加起來佔16字節。
提問:每一個非static成員變量都有本身的內存說明了什麼問題?
回答:說明爲每一個變量都分配了內存(哪兒來的磚??!!)
提問:爲變量分配了內存說明了什麼?
回答:說明每一個非static變量不但已經被聲明,並且已經被定義了!由於聲明變量並不分配內存,只有定義變量的時候才分配內存!
         這纔是第一個問題的本質的答案!
 
         第二個問題在《C++ Primer》這本書裏就找不到答案了——畢竟是Primer,不是Advanced。因而又祭出寶卷《深刻探索C++對象模型》。一番查閱後,在這裏找到半個答案:
l         P. 95 Static Data Members節        Static data members,按其字面意義,被編譯器提出於class以外……並被視爲一個global變量……每個static data member只有一個實體,存放在程序的data segment之中……
My God! 多麼精彩的描述!!第二個問題的答案就是:類的static數據成員會像一個全局(global)變量同樣被放在程序的數據段裏(誰說大學開的彙編語言課沒用來着??),而不佔類實例的內存。
         基於此,咱們能夠推敲出至少兩點:
1.         類的static數據成員不佔類(的實例)的內存,所以這裏只是個聲明、沒有定義。
2.         「被視爲」global變量僅僅是「被視爲」,並不像聲明並定義了一個真正的global變量。
因此,不管怎麼說,咱們都欠它一次「定義」——問題完全解決了!
 
多作之過?What’s under the C#’s hood?

         也許你會問:爲了一個小小的static,至於嗎?我會堅決地告訴你——至於!不只僅是由於它讓我閃了腰,更由於咱們做爲程序員,要有嚴謹的態度和鑽研的精神!
         最後收拾一把C#——爲何它的static成員變量就能夠直接使用?
         其實上面已經說過,C#的設計理念是把程序員從時時刻刻記着與內存打交道的繁枝縟節中解放出來,所以C#類中的一句public static string Name;不只僅是一個聲明,順便連定義也作了。
         C#替程序員作的還遠不止一個變量的定義。當你試圖輸出一個沒有顯式初始化的C#類數據成員(不管是static的仍是非static的),都會獲得一個默認的「零」值。在MSDN裏有張表格——Default Values Table (C# Reference),你們能夠參閱。簡摘以下:
Value type
 Default value
 
bool
 false
 
byte
 0
 
char
 '\0'
 
decimal
 0.0M
 
double
 0.0D
 
enum
 The value produced by the expression (E)0, where E is the enum identifier.
 
float
 0.0F
 
int
 0
 
long
 0L
 
sbyte
 0
 
short
 0
 
struct
 The value produced by setting all value-type fields to their default values and all reference-type fields to null.
 
uint
 0
 
ulong
 0
 
ushort
 0
 
         那麼這個「零」值是怎麼獲得的呢?這又得感謝C#的「多作」了——對於C#類的非static數據成員,C#編譯器會在自動添加的實例構造函數中給它們賦上「零」值;而對於C#類的static數據成員,C#編譯器會在自動添加的類構造函數中爲它們賦「零」值。(注意:最後這一段描述來源於個人記憶,具體出處已無從考證,也許來自某帖子?也許是劉德華上次遭槍殺時那張報紙裏的內容?唉……是本身夢中杜撰出來的也說不定,因此請你們多加當心咯)
 
         夜已經很深了……睡覺了。今天的入眠曲是When You Know和The Shadow of Your Smile。

         她……還好嗎?
 
法律聲明:本文章受到知識產權法保護,任何單位或我的若須要轉載此文,必需保證文章的完整性(未經做者許可的任何刪節或改動將視爲侵權行爲)。若您須要轉載,請務必註明文章出處爲51cto和CSDN以保障網站的權益;請務必註明文章做者爲劉鐵猛( [url]http://liutiemeng.blog.51cto.com[/url]),並向 [email]liutm@beyondsoft.com[/email]發送郵件,標明文章位置及用途。轉載時請將此法律聲明一併轉載,謝謝!
相關文章
相關標籤/搜索