2009年8月13日
2009年8月12日
|
|
一、選擇公司的形式:
普通的有限責(zé)任公司,最低注冊(cè)資金3萬(wàn)元,需要2個(gè)或2個(gè)以上的股東,
從06年1月起新的公司法規(guī)定,允許1個(gè)股東注冊(cè)有限責(zé)任公司,這種特殊的有限責(zé)任公司又稱“一人有限公司”(但公司名稱中不會(huì)有“一人”字樣,執(zhí)照上會(huì)注明“自然人獨(dú)資”),最低注冊(cè)資金10萬(wàn)元。如果只有你一個(gè)人作為股東,則選擇一人有限公司,最低注冊(cè)資金10萬(wàn)元;如果你和朋友、家人合伙投資創(chuàng)業(yè),可選擇普通的有限公司,最低注冊(cè)資金3萬(wàn)元。建議你準(zhǔn)備好注冊(cè)資金3萬(wàn)元。
二、注冊(cè)公司所需的注冊(cè)資料: (1)個(gè)人資料(身份證、法人戶口本復(fù)印件或戶籍證明、居住地址、電話號(hào)碼) (2)注冊(cè)資金 (3)擬訂注冊(cè)公司名稱若干 (4)公司經(jīng)營(yíng)范圍 (5)租房房產(chǎn)證、租賃合同 (6)公司住所 (7)股東名冊(cè)及股東聯(lián)系電話、聯(lián)系地址 (8)公司的機(jī)構(gòu)及其產(chǎn)生辦法、職權(quán)、議事規(guī)則 (9)公司章程
三、注冊(cè)公司的步驟:
1.核名:到工商局去領(lǐng)取一張“企業(yè)(字號(hào))名稱預(yù)先核準(zhǔn)申請(qǐng)表”,填寫(xiě)你準(zhǔn)備取的公司名稱,由工商局上工商局內(nèi)部網(wǎng)檢索是否有重名,如果沒(méi)有重名,就可以使用這個(gè)名稱,就會(huì)核發(fā)一張“企業(yè)(字號(hào))名稱預(yù)先核準(zhǔn)通知書(shū)”。工商名稱核準(zhǔn)費(fèi)是40元,交給工商局。 40元可以幫你檢索5個(gè)名字,很多名字重復(fù),所以一般常見(jiàn)的名字就不用試了,免得花冤枉錢(qián)。
2.租房: 去專門(mén)的寫(xiě)字樓租一間辦公室,如果你自己有廠房或者辦公室也可以,有的地方不允許在居民樓里辦公。 你要交房租給所租辦公室的房東(所有權(quán)人),假設(shè)辦公室的房租是1000元/月,一般起租最少6個(gè)月,6個(gè)月的房租是6000元。
3.簽訂租房合同:你要與你所租的辦公室的房東簽定租房合同,并讓房東提供房產(chǎn)證的復(fù)印件。租房合同打印費(fèi)5份15元,房產(chǎn)證復(fù)印件5張2.5元。
4.買(mǎi)租房的印花稅:你要到稅務(wù)局去買(mǎi)印花稅,按年租金的千分之一的稅率購(gòu)買(mǎi),貼在房租合同的首頁(yè)。例如你的每年房租是1.2萬(wàn)元,那就要買(mǎi)12元錢(qián)的印花稅,后面凡是需要用到房租合同的地方,都需要是貼了印花稅的合同復(fù)印件。
5.編寫(xiě)“公司章程”:可以在工商局網(wǎng)站下載“公司章程”的樣本,修改一下就可以了。章程的最后由所有股東簽名。 假設(shè)章程打印5份(股東2人各2份、工商局1份、銀行1份、會(huì)計(jì)師事務(wù)所1份),章程打印費(fèi)15元、下載公司章程的上網(wǎng)費(fèi)2元。
6.刻私章: 去街上刻章的地方刻一個(gè)私章,給他們講刻法人私章(方形的)。刻章費(fèi)用20元。
7.到會(huì)計(jì)師事務(wù)所領(lǐng)取“銀行詢征函”:聯(lián)系一家會(huì)計(jì)師事務(wù)所,領(lǐng)取一張“銀行詢征函”,必須是原件,會(huì)計(jì)師事務(wù)所蓋鮮章。如果你不清楚,可以看報(bào)紙上的分類(lèi)廣告,有很多會(huì)計(jì)師事務(wù)所的廣告。銀行詢征函10元。
8.去銀行開(kāi)立公司驗(yàn)資戶: 所有股東帶上自己入股的那一部分錢(qián)到銀行,帶上公司章程、工商局發(fā)的核名通知、法人代表的私章、身份證、用于驗(yàn)資的錢(qián)、空白詢征函表格,到銀行去開(kāi)立公司帳戶,你要告訴銀行是開(kāi)驗(yàn)資戶。開(kāi)立好公司帳戶后,各個(gè)股東按自己出資額向公司帳戶中存入相應(yīng)的錢(qián)。 銀行會(huì)發(fā)給每個(gè)股東繳款單、并在詢征函上蓋銀行的章。公司驗(yàn)資戶開(kāi)戶費(fèi)20元。
注意:公司法規(guī)定,注冊(cè)公司時(shí),投資人(股東)必須繳納足額的資本,可以以貸幣形式(也就是人民幣)出資,也可以以實(shí)物(如汽車(chē)、房產(chǎn)、知識(shí)產(chǎn)權(quán)等)出資。到銀行辦的只是貨幣出資這一部分,如果你有實(shí)物、房產(chǎn)等作為出資的,需要到會(huì)計(jì)師事務(wù)所鑒定其價(jià)值后再以其實(shí)際價(jià)值出資,比較麻煩,因此建議你直接拿錢(qián)來(lái)出資,公司法不管你用什么手段拿的錢(qián),自己的也好、借的也好,只要如數(shù)繳足出資款即可。
9.辦理驗(yàn)資報(bào)告:拿著銀行出具的股東繳款單、銀行蓋章后的詢征函,以及公司章程、核名通知、房租合同、房產(chǎn)證復(fù)印件,到會(huì)計(jì)師事務(wù)所辦理驗(yàn)資報(bào)告,會(huì)計(jì)師事務(wù)師驗(yàn)資報(bào)告按注冊(cè)資本收費(fèi)。50萬(wàn)元以下注冊(cè)資金驗(yàn)資費(fèi)500元。
10.注冊(cè)公司:到工商局領(lǐng)取公司設(shè)立登記的各種表格,包括設(shè)立登記申請(qǐng)表、股東(發(fā)起人)名單、董事經(jīng)理監(jiān)理情況、法人代表登記表、指定代表或委托代理人登記表。注冊(cè)登記費(fèi),按注冊(cè)資金的萬(wàn)分之8收取。填好后,連同核名通知、公司章程、房租合同、房產(chǎn)證復(fù)印件、驗(yàn)資報(bào)告一起交給工商局。大概3個(gè)工作日后可領(lǐng)取執(zhí)照。注冊(cè)公司手續(xù)費(fèi)300元。
11.憑營(yíng)業(yè)執(zhí)照,到公安局特行科指定的刻章社,去刻公章、財(cái)務(wù)章。后面步驟中,均需要用到公章或財(cái)務(wù)章。公章50元,財(cái)務(wù)章50元。
12.辦理企業(yè)組織機(jī)構(gòu)代碼證:憑營(yíng)業(yè)執(zhí)照到技術(shù)監(jiān)督局辦理組織機(jī)構(gòu)代碼證,費(fèi)用是80元。辦這個(gè)證需要半個(gè)月,技術(shù)監(jiān)督局會(huì)首先發(fā)一個(gè)預(yù)先受理代碼證明文件,憑這個(gè)文件就可以辦理后面的稅務(wù)登記證、銀行基本戶開(kāi)戶手續(xù)了。
13.去銀行開(kāi)基本戶:憑營(yíng)業(yè)執(zhí)照、組織機(jī)構(gòu)代碼證,去銀行開(kāi)立基本帳號(hào)。最好是在原來(lái)辦理驗(yàn)資時(shí)的那個(gè)銀行的同一網(wǎng)點(diǎn)去辦理,否則,會(huì)多收100元的驗(yàn)資帳戶費(fèi)用。 開(kāi)基本戶需要填很多表,你最好把能帶齊的東西全部帶上,要不然要跑很多趟,包括營(yíng)業(yè)執(zhí)照正本原件、身份證、組織機(jī)構(gòu)代碼證、公財(cái)章、法人章。
開(kāi)基本戶時(shí),還需要購(gòu)買(mǎi)一個(gè)密碼器(從2005年下半年起,大多銀行都有這個(gè)規(guī)定),今后你的公司開(kāi)支票、劃款時(shí),都需要使用密碼器來(lái)生成密碼。公司基本帳號(hào)開(kāi)戶費(fèi)20元,密碼器280元。
14.辦理稅務(wù)登記:領(lǐng)取執(zhí)照后,30日內(nèi)到當(dāng)?shù)囟悇?wù)局申請(qǐng)領(lǐng)取稅務(wù)登記證。一般的公司都需要辦理2種稅務(wù)登記證,即國(guó)稅和地稅。費(fèi)用是各40元,共80元。
15.請(qǐng)兼職會(huì)計(jì):辦理稅務(wù)登記證時(shí),必須有一個(gè)會(huì)計(jì),因?yàn)槎悇?wù)局要求提交的資料其中有一項(xiàng)是會(huì)計(jì)資格證和身份證。你可先請(qǐng)一個(gè)兼職會(huì)計(jì),小公司剛開(kāi)始請(qǐng)的兼職會(huì)計(jì)一般200元工資就可以了。
16.申請(qǐng)領(lǐng)購(gòu)發(fā)票:如果你的公司是銷(xiāo)售商品的,應(yīng)該到國(guó)稅去申請(qǐng)發(fā)票,如果是服務(wù)性質(zhì)的公司,則到地稅申領(lǐng)發(fā)票。 開(kāi)始可先領(lǐng)購(gòu)500元的發(fā)票。
最后就開(kāi)始營(yíng)業(yè)了。
四、注冊(cè)公司的費(fèi)用: 1、工商局工商名稱核準(zhǔn),40元 2、公司辦公室房租6個(gè)月,6000元 3、租房合同打印費(fèi)5份15元,房產(chǎn)證復(fù)印件5張2.5元 4、租房的印花稅12元 5、下載公司章程的上網(wǎng)費(fèi)2元,公司章程打印費(fèi)15元 6、刻法人私章20元 7、會(huì)計(jì)師事務(wù)所的銀行詢征函10元 8、銀行開(kāi)立公司驗(yàn)資戶開(kāi)戶費(fèi)20元 9、會(huì)計(jì)師事務(wù)所辦理驗(yàn)資報(bào)告500元 10、工商局注冊(cè)公司手續(xù)費(fèi)300元,信息卡120元 11、公章2個(gè)120元,財(cái)務(wù)章1個(gè)60元 12、技術(shù)監(jiān)督局辦理組織機(jī)構(gòu)代碼證148元 13、銀行開(kāi)立公司基本帳號(hào)開(kāi)戶費(fèi)20元、密碼器280元 14、國(guó)稅稅務(wù)登記證60元,地稅稅務(wù)登記證60元 15、兼職會(huì)計(jì)工資,200元 16、申請(qǐng)領(lǐng)購(gòu)發(fā)票,500元 合計(jì):8502.5元 如果不算房租、會(huì)計(jì)工資、發(fā)票,則合計(jì)1802.5元。
注冊(cè)資本最少3萬(wàn)元。
注冊(cè)登記費(fèi)按注冊(cè)資本的0.08%(1000萬(wàn)以內(nèi)),0.04%(1000萬(wàn)以上的超過(guò)部分)收取
營(yíng)業(yè)稅:銷(xiāo)售商品的公司,按所開(kāi)發(fā)票額的4%征收增殖稅;提供服務(wù)的公司,按所開(kāi)發(fā)票額的5%征收營(yíng)業(yè)稅。
所得稅:對(duì)企業(yè)的純利潤(rùn)征收18-33%的企業(yè)所得稅。 利潤(rùn)收入在3萬(wàn)元(含3萬(wàn)元)以下的稅率為18%,利潤(rùn)收入在3萬(wàn)元以上10萬(wàn)元(含10萬(wàn)元)的稅率為27%,10萬(wàn)元以上的為33%。
五、注冊(cè)公司的相關(guān)說(shuō)明:
1.注冊(cè)公司會(huì)不到半年時(shí)間,最快需要20天時(shí)間。地區(qū)不同注冊(cè)公司的費(fèi)用也有所不同。
2.要注冊(cè)一個(gè)公司,首先想好經(jīng)營(yíng)什么,怎樣經(jīng)營(yíng)好,再來(lái)注冊(cè)。要不,注冊(cè)了也沒(méi)有用,注冊(cè)了公司是需要很多成本的,不是一件“好玩”的事情。
3.注冊(cè)個(gè)體簡(jiǎn)單易辦;而注冊(cè)公司要有章程、合同,要驗(yàn)資,程序挺多的。 在投入不是太多時(shí),還是注冊(cè)個(gè)體為好。
4.前期可行性分析調(diào)查,建議你自己認(rèn)真的考慮一下
5.公司必須建立健全的會(huì)計(jì)制度,你可能擔(dān)心自己不會(huì),怎么辦?剛開(kāi)始成立的公司,業(yè)務(wù)少,對(duì)會(huì)計(jì)的工作量也非常小,你可以請(qǐng)一個(gè)兼職會(huì)計(jì),每個(gè)月到你的公司幫你建帳,二、三天時(shí)間就夠了,給他200-500左右的工資即可。
6.每個(gè)月1日-10日按時(shí)向稅務(wù)申報(bào)稅,即使沒(méi)有開(kāi)展業(yè)務(wù)不需要繳稅,也要進(jìn)行零申報(bào),否則會(huì)被罰款的。罰款額度超過(guò)一天100元。營(yíng)業(yè)執(zhí)照辦理下來(lái)后一個(gè)月內(nèi)必須辦理稅務(wù)登記。每年3-6月年定時(shí)年檢營(yíng)業(yè)執(zhí)照。
7.對(duì)企業(yè)所得稅,做帳很關(guān)鍵,如果帳面上你的利潤(rùn)很多,那稅率就高。所以,平常的購(gòu)買(mǎi)設(shè)備都要開(kāi)發(fā)票,你吃飯、坐車(chē)的票都留起來(lái),可以做為你的企業(yè)運(yùn)作成本。
8.營(yíng)業(yè)稅是對(duì)營(yíng)業(yè)額征稅,不管你賺沒(méi)有賺錢(qián),只有發(fā)生了交易,開(kāi)了發(fā)票,就要征稅;所得稅,是對(duì)利潤(rùn)征稅,利潤(rùn)就是營(yíng)業(yè)額扣減各種成本后剩余的錢(qián),只有賺了錢(qián),才會(huì)征所得稅。
9.有限責(zé)任公司可以注冊(cè)分公司。
10.開(kāi)辦費(fèi)是指企業(yè)在籌建期間發(fā)生的費(fèi)用,包括籌建期人員工資、辦公費(fèi)、培訓(xùn)費(fèi)、差旅費(fèi)、印刷費(fèi)、注冊(cè)登記費(fèi)以及不計(jì)入固定資產(chǎn)和無(wú)形資產(chǎn)購(gòu)建成本的匯兌損益和利息支出。籌建期是指企業(yè)被批準(zhǔn)籌建之日起至開(kāi)始生產(chǎn)、經(jīng)營(yíng)(包括試生產(chǎn)、試營(yíng)業(yè))之日的期間。
|
2009年7月24日
c++中的explicit關(guān)鍵字用來(lái)修飾類(lèi)的構(gòu)造函數(shù),表明該構(gòu)造函數(shù)是顯式的,既然有"顯式"那么必然就有"隱式",那么什么是顯示而什么又是隱式的呢?
如果c++類(lèi)的構(gòu)造函數(shù)有一個(gè)參數(shù),那么在編譯的時(shí)候就會(huì)有一個(gè)缺省的轉(zhuǎn)換操作:將該構(gòu)造函數(shù)對(duì)應(yīng)數(shù)據(jù)類(lèi)型的數(shù)據(jù)轉(zhuǎn)換為該類(lèi)對(duì)象,如下面所示:
class MyClass
{
public:
MyClass( int num );
}
....
MyClass obj = 10; //ok,convert int to MyClass
在上面的代碼中編譯器自動(dòng)將整型轉(zhuǎn)換為MyClass類(lèi)對(duì)象,實(shí)際上等同于下面的操作:
MyClass temp(10);
MyClass obj = temp;
上面的所有的操作即是所謂的"隱式轉(zhuǎn)換"。
如果要避免這種自動(dòng)轉(zhuǎn)換的功能,我們?cè)撛趺醋瞿兀亢俸龠@就是關(guān)鍵字explicit的作用了,將類(lèi)的構(gòu)造函數(shù)聲明為"顯示",也就是在聲明構(gòu)造函數(shù)的時(shí)候前面添加上explicit即可,這樣就可以防止這種自動(dòng)的轉(zhuǎn)換操作,如果我們修改上面的MyClass類(lèi)的構(gòu)造函數(shù)為顯示的,那么下面的代碼就不能夠編譯通過(guò)了,如下所示:
class MyClass
{
public:
explicit MyClass( int num );
}
....
MyClass obj = 10; //err,can't non-explict convert
class isbn_mismatch:public std::logic_error{
public:
explicit isbn_missmatch(const std::string &s):std:logic_error(s){}
isbn_mismatch(const std::string &s,const std::string &lhs,const std::string &rhs):
std::logic_error(s),left(lhs),right(rhs){}
const std::string left,right;
virtual ~isbn_mismatch() throw(){}
};
Sales_item& operator+(const Sales_item &lhs,const Sales_item rhs)
{
if(!lhs.same_isbn(rhs))
throw isbn_mismatch("isbn missmatch",lhs.book(),rhs.book());
Sales_item ret(lhs);
ret+rhs;
return ret;
}
Sales_item item1,item2,sum;
while(cin>>item1>>item2)
{
try{
sun=item1+item2;
}catch(const isbn_mismatch &e)
{
cerr<<e.what()<<"left isbn is:"<<e.left<<"right isbn is:"<<e.right<<endl;
}
}
用于用戶自定義類(lèi)型的構(gòu)造函數(shù),指定它是默認(rèn)的構(gòu)造函數(shù),不可用于轉(zhuǎn)換構(gòu)造函數(shù).因?yàn)闃?gòu)造函數(shù)有三種:1拷貝構(gòu)造函數(shù)2轉(zhuǎn)換構(gòu)造函數(shù)3一般的構(gòu)造函數(shù)(我自己的術(shù)語(yǔ)^_^)
另:如果一個(gè)類(lèi)或結(jié)構(gòu)存在多個(gè)構(gòu)造函數(shù)時(shí),explicit 修飾的那個(gè)構(gòu)造函數(shù)就是默認(rèn)的
class isbn_mismatch:public std::logic_error{
public:
explicit isbn_missmatch(const std::string &s):std:logic_error(s){}
isbn_mismatch(const std::string &s,const std::string &lhs,const std::string &rhs):
std::logic_error(s),left(lhs),right(rhs){}
const std::string left,right;
virtual ~isbn_mismatch() throw(){}
};
Sales_item& operator+(const Sales_item &lhs,const Sales_item rhs)
{
if(!lhs.same_isbn(rhs))
throw isbn_mismatch("isbn missmatch",lhs.book(),rhs.book());
Sales_item ret(lhs);
ret+rhs;
return ret;
}
Sales_item item1,item2,sum;
while(cin>>item1>>item2)
{
try{
sun=item1+item2;
}catch(const isbn_mismatch &e)
{
cerr<<e.what()<<"left isbn is:"<<e.left<<"right isbn is:"<<e.right<<endl;
}
}
這個(gè) 《ANSI/ISO C++ Professional Programmer's Handbook 》是這樣說(shuō)的
explicit Constructors
A constructor that takes a single argument is, by default, an implicit conversion operator, which converts its argument to
an object of its class (see also Chapter 3, "Operator Overloading"). Examine the following concrete example:
class string
{
private:
int size;
int capacity;
char *buff;
public:
string();
string(int size); // constructor and implicit conversion operator
string(const char *); // constructor and implicit conversion operator
~string();
};
Class string has three constructors: a default constructor, a constructor that takes int, and a constructor that
constructs a string from const char *. The second constructor is used to create an empty string object with an
initial preallocated buffer at the specified size. However, in the case of class string, the automatic conversion is
dubious. Converting an int into a string object doesn't make sense, although this is exactly what this constructor does.
Consider the following:
int main()
{
string s = "hello"; //OK, convert a C-string into a string object
int ns = 0;
s = 1; // 1 oops, programmer intended to write ns = 1,
}
In the expression s= 1;, the programmer simply mistyped the name of the variable ns, typing s instead. Normally,
the compiler detects the incompatible types and issues an error message. However, before ruling it out, the compiler first
searches for a user-defined conversion that allows this expression; indeed, it finds the constructor that takes int.
Consequently, the compiler interprets the expression s= 1; as if the programmer had written
s = string(1);
You might encounter a similar problem when calling a function that takes a string argument. The following example
can either be a cryptic coding style or simply a programmer's typographical error. However, due to the implicit
conversion constructor of class string, it will pass unnoticed:
int f(string s);
int main()
{
f(1); // without a an explicit constructor,
//this call is expanded into: f ( string(1) );
//was that intentional or merely a programmer's typo?
}
'In order to avoid such implicit conversions, a constructor that takes one argument needs to be declared explicit:
class string
{
//...
public:
explicit string(int size); // block implicit conversion
string(const char *); //implicit conversion
~string();
};
An explicit constructor does not behave as an implicit conversion operator, which enables the compiler to catch the
typographical error this time:
int main()
{
string s = "hello"; //OK, convert a C-string into a string object
int ns = 0;
s = 1; // compile time error ; this time the compiler catches the typo
}
Why aren't all constructors automatically declared explicit? Under some conditions, the automatic type conversion is
useful and well behaved. A good example of this is the third constructor of string:
string(const char *);
The implicit type conversion of const char * to a string object enables its users to write the following:
string s;
s = "Hello";
The compiler implicitly transforms this into
string s;
//pseudo C++ code:
s = string ("Hello"); //create a temporary and assign it to s
On the other hand, if you declare this constructor explicit, you have to use explicit type conversion:
class string
{
//...
public:
explicit string(const char *);
};
int main()
{
string s;
s = string("Hello"); //explicit conversion now required
return 0;
}
Extensive amounts of legacy C++ code rely on the implicit conversion of constructors. The C++ Standardization
committee was aware of that. In order to not make existing code break, the implicit conversion was retained. However, a
new keyword, explicit, was introduced to the languageto enable the programmer to block the implicit conversion
when it is undesirable. As a rule, a constructor that can be invoked with a single argument needs to be declared
explicit. When the implicit type conversion is intentional and well behaved, the constructor can be used as an
implicit conversion operator.
網(wǎng)上找的講的最好的貼:
C++ 中 explicit 關(guān)鍵字的作用
在 C++ 中, 如果一個(gè)類(lèi)有只有一個(gè)參數(shù)的構(gòu)造函數(shù),C++ 允許一種特殊的聲明類(lèi)變量的方式。在這種情況下,可以直接將一個(gè)對(duì)應(yīng)于構(gòu)造函數(shù)參數(shù)類(lèi)型的數(shù)據(jù)直接賦值給類(lèi)變量,編譯器在編譯時(shí)會(huì)自動(dòng)進(jìn)行類(lèi)型轉(zhuǎn)換,將對(duì)應(yīng)于構(gòu)造函數(shù)參數(shù)類(lèi)型的數(shù)據(jù)轉(zhuǎn)換為類(lèi)的對(duì)象。 如果在構(gòu)造函數(shù)前加上 explicit 修飾詞, 則會(huì)禁止這種自動(dòng)轉(zhuǎn)換,在這種情況下,即使將對(duì)應(yīng)于構(gòu)造函數(shù)參數(shù)類(lèi)型的數(shù)據(jù)直接賦值給類(lèi)變量,編譯器也會(huì)報(bào)錯(cuò)。
下面以具體實(shí)例來(lái)說(shuō)明。
建立people.cpp 文件,然后輸入下列內(nèi)容:
class People
{
public:
int age;
explicit People (int a)
{
age=a;
}
};
void foo ( void )
{
People p1(10); //方式一
People* p_p2=new People(10); //方式二
People p3=10; //方式三
}
這段 C++ 程序定義了一個(gè)類(lèi) people ,包含一個(gè)構(gòu)造函數(shù), 這個(gè)構(gòu)造函數(shù)只包含一個(gè)整形參數(shù) a ,可用于在構(gòu)造類(lèi)時(shí)初始化 age 變量。
然后定義了一個(gè)函數(shù)foo,在這個(gè)函數(shù)中我們用三種方式分別創(chuàng)建了三個(gè)10歲的“人”。第一種是最一般的類(lèi)變量聲明方式。第二種方式其實(shí)是聲明了一個(gè)people類(lèi)的指針變量,然后在堆中動(dòng)態(tài)創(chuàng)建了一個(gè)people實(shí)例,并把這個(gè)實(shí)例的地址賦值給了p_p2。第三種方式就是我們所說(shuō)的特殊方式,為什么說(shuō)特殊呢?我們都知道,C/C++是一種強(qiáng)類(lèi)型語(yǔ)言,不同的數(shù)據(jù)類(lèi)型是不能隨意轉(zhuǎn)換的,如果要進(jìn)行類(lèi)型轉(zhuǎn)換,必須進(jìn)行顯式強(qiáng)制類(lèi)型轉(zhuǎn)換,而這里,沒(méi)有進(jìn)行任何顯式的轉(zhuǎn)換,直接將一個(gè)整型數(shù)據(jù)賦值給了類(lèi)變量p3。
因此,可以說(shuō),這里進(jìn)行了一次隱式類(lèi)型轉(zhuǎn)換,編譯器自動(dòng)將對(duì)應(yīng)于構(gòu)造函數(shù)參數(shù)類(lèi)型的數(shù)據(jù)轉(zhuǎn)換為了該類(lèi)的對(duì)象,因此方式三經(jīng)編譯器自動(dòng)轉(zhuǎn)換后和方式一最終的實(shí)現(xiàn)方式是一樣的。
不相信? 耳聽(tīng)為虛,眼見(jiàn)為實(shí),讓我們看看底層的實(shí)現(xiàn)方式。
為了更容易比較方式一和方式三的實(shí)現(xiàn)方式,我們對(duì)上面的代碼作一點(diǎn)修改,去除方式二:
void foo ( void )
{
People p1(10); //方式一
People p3=10; //方式三
}
去除方式二的原因是方式二是在堆上動(dòng)態(tài)創(chuàng)建類(lèi)實(shí)例,因此會(huì)有一些額外代碼影響分析。修改完成后,用下列命令編譯 people.cpp
$ gcc -S people.cpp
"-S"選項(xiàng)是GCC輸出匯編代碼。命令執(zhí)行后,默認(rèn)生成people.s。 關(guān)鍵部分內(nèi)容如下:
.globl _Z3foov
.type _Z3foov, @function
_Z3foov:
.LFB5:
pushl %ebp
.LCFI2:
movl %esp, %ebp
.LCFI3:
subl $24, %esp
.LCFI4:
movl $10, 4(%esp)
leal -4(%ebp), %eax
movl %eax, (%esp)
call _ZN6PeopleC1Ei
movl $10, 4(%esp)
leal -8(%ebp), %eax
movl %eax, (%esp)
call _ZN6PeopleC1Ei
leave
ret
看“.LCFI4” 行后面的東西,1-4行和5-8行幾乎一模一樣,1-4行即為方式一的匯編代碼,5-8即為方式三的匯編代碼。 細(xì)心的你可能發(fā)現(xiàn)2和6行有所不同,一個(gè)是 -4(%ebp) 而另一個(gè)一個(gè)是 -8(%ebp) ,這分別為類(lèi)變量P1和P3的地址。
對(duì)于不可隨意進(jìn)行類(lèi)型轉(zhuǎn)換的強(qiáng)類(lèi)型語(yǔ)言C/C++來(lái)說(shuō), 這可以說(shuō)是C++的一個(gè)特性。哦,今天好像不是要說(shuō)C++的特性,而是要知道explicit關(guān)鍵字的作用?
explicit關(guān)鍵字到底是什么作用呢? 它的作用就是禁止這個(gè)特性。如文章一開(kāi)始而言,凡是用explicit關(guān)鍵字修飾的構(gòu)造函數(shù),編譯時(shí)就不會(huì)進(jìn)行自動(dòng)轉(zhuǎn)換,而會(huì)報(bào)錯(cuò)。
讓我們看看吧! 修改代碼:
class People
{
public:
int age;
explicit People (int a)
{
age=a;
}
};
然后再編譯:
$ gcc -S people.cpp
編譯器立馬報(bào)錯(cuò):
people.cpp: In function ‘void foo()’:
people.cpp:23: 錯(cuò)誤:請(qǐng)求從 ‘int’ 轉(zhuǎn)換到非標(biāo)量類(lèi)型 ‘People’
2009年7月22日
軟件設(shè)計(jì)中會(huì)碰到這樣的關(guān)系:一個(gè)對(duì)象依賴于另一個(gè)對(duì)象,必須根據(jù)后者的狀態(tài)更新自己的狀態(tài),可以把后者稱作目標(biāo)對(duì)象,前者稱作觀察者對(duì)象。不但觀察者依賴于目標(biāo),當(dāng)目標(biāo)的狀態(tài)改變時(shí)也要通知觀察者,這就出現(xiàn)了雙向的依賴。兩個(gè)對(duì)象互相依賴的后果是它們必須一起復(fù)用。如果一個(gè)目標(biāo)有多個(gè)觀察者,那么目標(biāo)也依賴所有觀察者,從而目標(biāo)對(duì)象無(wú)法獨(dú)立復(fù)用。如何消除目標(biāo)和觀察者之間的互相依賴呢?觀察者模式幫助我們解決這個(gè)問(wèn)題。
觀察者模式把目標(biāo)對(duì)觀察者的依賴進(jìn)行抽象:使目標(biāo)只知道自己有若干觀察者,但不知道這些觀察者具體是誰(shuí),可能有多少個(gè);當(dāng)目標(biāo)狀態(tài)改變時(shí)只要給這些觀察者一個(gè)通知,不必作更多的事情。這樣目標(biāo)對(duì)觀察者的依賴就達(dá)到了抽象和最小,而目標(biāo)對(duì)具體觀察者的依賴被解除了。
類(lèi)圖如下:


Subject 對(duì)象保存一個(gè)Observer引用的列表,當(dāng)我們讓一個(gè)ConcreteObserver對(duì)象觀察Subject對(duì)象時(shí),調(diào)用后者的Attach()方法,將前者的引用加入該列表中。當(dāng)Subject對(duì)象狀態(tài)改變時(shí),它調(diào)用自身的Notify方法,該方法調(diào)用列表中每一個(gè)Observer的 Update()方法。一個(gè)ConcreteObserver只要重定義Update()就能收到通知,作為對(duì)通知的響應(yīng),Update()調(diào)用 Subject對(duì)象的getStatus()獲取數(shù)據(jù),然后更新自身。當(dāng)不需要繼續(xù)觀察時(shí),ConcreteObserver對(duì)象調(diào)用Subject對(duì)象的Detach()方法,其引用被從列表中移除。
解除目標(biāo)對(duì)具體觀察者的依賴以后,很容易增加新的具體觀察者,因?yàn)椴皇芤蕾嚨姆矫婢涂梢宰杂勺兓欢繕?biāo)也可以獨(dú)立地復(fù)用,因?yàn)闊o(wú)所依賴的方面就可以不受影響。
以上主要考慮了一個(gè)目標(biāo)有多個(gè)觀察者的情況,我們?cè)O(shè)法解除了目標(biāo)對(duì)具體觀察者的依賴,使具體觀察者的種類(lèi)和數(shù)目容易改變。有時(shí)候一個(gè)觀察者觀察多個(gè)目標(biāo)也是有意義的,在前面的類(lèi)圖中,觀察者對(duì)具體目標(biāo)的依賴仍然存在,因此無(wú)法適應(yīng)目標(biāo)方面的變化。怎樣抽象這種依賴呢?使觀察者只知道若干個(gè)目標(biāo)會(huì)向自己發(fā)出通知,而不知道這些目標(biāo)具體是誰(shuí),可能有多少個(gè);在目標(biāo)向觀察者發(fā)送通知時(shí),將一個(gè)自身的引用作為參數(shù),然后觀察者調(diào)用其抽象方法就可以獲得目標(biāo)狀態(tài)。這就使得觀察者對(duì)目標(biāo)的依賴是抽象的,觀察者對(duì)具體目標(biāo)的依賴被解除了。
類(lèi)圖如下:

參考資料:
1.《設(shè)計(jì)模式-可復(fù)用面向?qū)ο筌浖幕A(chǔ)》/Erich Gamma等著,李英軍等譯 機(jī)械工業(yè)出版社
起學(xué)習(xí)
NT/2000方法:
#include <windows.h>
#include <conio.h>
#include <stdio.h>
#define SystemBasicInformation 0
#define SystemPerformanceInformation 2
#define SystemTimeInformation 3
#define Li2Double(x) ((double)((x).HighPart) * 4.294967296E9 (double)((x).LowPart))
typedef struct
{
DWORD dwUnknown1;
ULONG uKeMaximumIncrement;
ULONG uPageSize;
ULONG uMmNumberOfPhysicalPages;
ULONG uMmLowestPhysicalPage;
ULONG uMmHighestPhysicalPage;
ULONG uAllocationGranularity;
PVOID pLowestUserAddress;
PVOID pMmHighestUserAddress;
ULONG uKeActiveProcessors;
BYTE bKeNumberProcessors;
BYTE bUnknown2;
WORD wUnknown3;
} SYSTEM_BASIC_INFORMATION;
typedef struct
{
LARGE_INTEGER liIdleTime;
DWORD dwSpare[76];
} SYSTEM_PERFORMANCE_INFORMATION;
typedef struct
{
LARGE_INTEGER liKeBootTime;
LARGE_INTEGER liKeSystemTime;
LARGE_INTEGER liExpTimeZoneBias;
ULONG uCurrentTimeZoneId;
DWORD dwReserved;
} SYSTEM_TIME_INFORMATION;
// ntdll!NtQuerySystemInformation (NT specific!)
//
// The function copies the system information of the
// specified type into a buffer
//
// NTSYSAPI
// NTSTATUS
// NTAPI
// NtQuerySystemInformation(
// IN UINT SystemInformationClass, // information type
// OUT PVOID SystemInformation, // pointer to buffer
// IN ULONG SystemInformationLength, // buffer size in bytes
// OUT PULONG ReturnLength OPTIONAL // pointer to a 32-bit
// // variable that receives
// // the number of bytes
// // written to the buffer
// );
typedef LONG (WINAPI *PROCNTQSI)(UINT,PVOID,ULONG,PULONG);
PROCNTQSI NtQuerySystemInformation;
void main(void)
{
SYSTEM_PERFORMANCE_INFORMATION SysPerfInfo;
SYSTEM_TIME_INFORMATION SysTimeInfo;
SYSTEM_BASIC_INFORMATION SysBaseInfo;
double dbIdleTime;
double dbSystemTime;
LONG status;
LARGE_INTEGER liOldIdleTime = {0,0};
LARGE_INTEGER liOldSystemTime = {0,0};
NtQuerySystemInformation = (PROCNTQSI)GetProcAddress(
GetModuleHandle("ntdll"),
"NtQuerySystemInformation"
);
if (!NtQuerySystemInformation)
return;
// get number of processors in the system
status = NtQuerySystemInformation(SystemBasicInformation,&SysBaseInfo,sizeof(SysBaseInfo),NULL);
if (status != NO_ERROR)
return;
printf("\nCPU Usage (press any key to exit): ");
while(!_kbhit())
{
// get new system time
status = NtQuerySystemInformation(SystemTimeInformation,&SysTimeInfo,sizeof(SysTimeInfo),0);
if (status!=NO_ERROR)
return;
// get new CPU's idle time
status = NtQuerySystemInformation(SystemPerformanceInformation,&SysPerfInfo,sizeof(SysPerfInfo),NULL);
if (status != NO_ERROR)
return;
// 本文轉(zhuǎn)自 C Builder研究 - http://www.ccrun.com/article.asp?i=424&d=7jw23a// if it's a first call - skip it
if (liOldIdleTime.QuadPart != 0)
{
// CurrentValue = NewValue - OldValue
dbIdleTime = Li2Double(SysPerfInfo.liIdleTime) - Li2Double(liOldIdleTime);
dbSystemTime = Li2Double(SysTimeInfo.liKeSystemTime) - Li2Double(liOldSystemTime);
// CurrentCpuIdle = IdleTime / SystemTime
dbIdleTime = dbIdleTime / dbSystemTime;
// CurrentCpuUsage% = 100 - (CurrentCpuIdle * 100) / NumberOfProcessors
dbIdleTime = 100.0 - dbIdleTime * 100.0 / (double)SysBaseInfo.bKeNumberProcessors 0.5;
printf("\b\b\b\b=%%",(UINT)dbIdleTime);
}
// store new CPU's idle and system time
liOldIdleTime = SysPerfInfo.liIdleTime;
liOldSystemTime = SysTimeInfo.liKeSystemTime;
// wait one second
Sleep(1000);
}
printf("\n");
}
//-------------------------------------------------------------
W9X:
#include <windows.h>
#include <conio.h>
#include <stdio.h>
void main(void)
{
HKEY hkey;
DWORD dwDataSize;
DWORD dwType;
DWORD dwCpuUsage;
// starting the counter
if ( RegOpenKeyEx( HKEY_DYN_DATA,
"PerfStats\\StartStat",
0,KEY_ALL_ACCESS,
&hkey ) != ERROR_SUCCESS)
return;
dwDataSize=sizeof(DWORD);
RegQueryValueEx( hkey,
"KERNEL\\CPUUsage",
NULL,&dwType,
(LPBYTE)&dwCpuUsage,
&dwDataSize );
RegCloseKey(hkey);
// geting current counter's value
if ( RegOpenKeyEx( HKEY_DYN_DATA,
"PerfStats\\StatData",
0,KEY_READ,
&hkey ) != ERROR_SUCCESS)
return;
printf("\nCPU Usage (press any key to exit): ");
while(!_kbhit())
{
dwDataSize=sizeof(DWORD);
RegQueryValueEx( hkey,
"KERNEL\\CPUUsage",
NULL,&dwType,
(LPBYTE)&dwCpuUsage,
&dwDataSize );
Sleep(500);
printf("\b\b\b\b=%%",dwCpuUsage);
}
printf("\n");
RegCloseKey(hkey);
// stoping the counter
if ( RegOpenKeyEx( HKEY_DYN_DATA,
"PerfStats\\StopStat",
0,KEY_ALL_ACCESS,
&hkey ) != ERROR_SUCCESS)
return;
dwDataSize=sizeof(DWORD);
RegQueryValueEx( hkey,
"KERNEL\\CPUUsage",
NULL,&dwType,
(LPBYTE)&dwCpuUsage,
&dwDataSize );
RegCloseKey(hkey);
}
NT/2000方法:
#include <windows.h>
#include <conio.h>
#include <stdio.h>
#define SystemBasicInformation 0
#define SystemPerformanceInformation 2
#define SystemTimeInformation 3
#define Li2Double(x) ((double)((x).HighPart) * 4.294967296E9 (double)((x).LowPart))
typedef struct
{
DWORD dwUnknown1;
ULONG uKeMaximumIncrement;
ULONG uPageSize;
ULONG uMmNumberOfPhysicalPages;
ULONG uMmLowestPhysicalPage;
ULONG uMmHighestPhysicalPage;
ULONG uAllocationGranularity;
PVOID pLowestUserAddress;
PVOID pMmHighestUserAddress;
ULONG uKeActiveProcessors;
BYTE bKeNumberProcessors;
BYTE bUnknown2;
WORD wUnknown3;
} SYSTEM_BASIC_INFORMATION;
typedef struct
{
LARGE_INTEGER liIdleTime;
DWORD dwSpare[76];
} SYSTEM_PERFORMANCE_INFORMATION;
typedef struct
{
LARGE_INTEGER liKeBootTime;
LARGE_INTEGER liKeSystemTime;
LARGE_INTEGER liExpTimeZoneBias;
ULONG uCurrentTimeZoneId;
DWORD dwReserved;
} SYSTEM_TIME_INFORMATION;
// ntdll!NtQuerySystemInformation (NT specific!)
//
// The function copies the system information of the
// specified type into a buffer
//
// NTSYSAPI
// NTSTATUS
// NTAPI
// NtQuerySystemInformation(
// IN UINT SystemInformationClass, // information type
// OUT PVOID SystemInformation, // pointer to buffer
// IN ULONG SystemInformationLength, // buffer size in bytes
// OUT PULONG ReturnLength OPTIONAL // pointer to a 32-bit
// // variable that receives
// // the number of bytes
// // written to the buffer
// );
typedef LONG (WINAPI *PROCNTQSI)(UINT,PVOID,ULONG,PULONG);
PROCNTQSI NtQuerySystemInformation;
void main(void)
{
SYSTEM_PERFORMANCE_INFORMATION SysPerfInfo;
SYSTEM_TIME_INFORMATION SysTimeInfo;
SYSTEM_BASIC_INFORMATION SysBaseInfo;
double dbIdleTime;
double dbSystemTime;
LONG status;
LARGE_INTEGER liOldIdleTime = {0,0};
LARGE_INTEGER liOldSystemTime = {0,0};
NtQuerySystemInformation = (PROCNTQSI)GetProcAddress(
GetModuleHandle("ntdll"),
"NtQuerySystemInformation"
);
if (!NtQuerySystemInformation)
return;
// get number of processors in the system
status = NtQuerySystemInformation(SystemBasicInformation,&SysBaseInfo,sizeof(SysBaseInfo),NULL);
if (status != NO_ERROR)
return;
printf("\nCPU Usage (press any key to exit): ");
while(!_kbhit())
{
// get new system time
status = NtQuerySystemInformation(SystemTimeInformation,&SysTimeInfo,sizeof(SysTimeInfo),0);
if (status!=NO_ERROR)
return;
// get new CPU's idle time
status = NtQuerySystemInformation(SystemPerformanceInformation,&SysPerfInfo,sizeof(SysPerfInfo),NULL);
if (status != NO_ERROR)
return;
// 本文轉(zhuǎn)自 C Builder研究 - http://www.ccrun.com/article.asp?i=424&d=7jw23a// if it's a first call - skip it
if (liOldIdleTime.QuadPart != 0)
{
// CurrentValue = NewValue - OldValue
dbIdleTime = Li2Double(SysPerfInfo.liIdleTime) - Li2Double(liOldIdleTime);
dbSystemTime = Li2Double(SysTimeInfo.liKeSystemTime) - Li2Double(liOldSystemTime);
// CurrentCpuIdle = IdleTime / SystemTime
dbIdleTime = dbIdleTime / dbSystemTime;
// CurrentCpuUsage% = 100 - (CurrentCpuIdle * 100) / NumberOfProcessors
dbIdleTime = 100.0 - dbIdleTime * 100.0 / (double)SysBaseInfo.bKeNumberProcessors 0.5;
printf("\b\b\b\b=%%",(UINT)dbIdleTime);
}
// store new CPU's idle and system time
liOldIdleTime = SysPerfInfo.liIdleTime;
liOldSystemTime = SysTimeInfo.liKeSystemTime;
// wait one second
Sleep(1000);
}
printf("\n");
}
//-------------------------------------------------------------
W9X:
#include <windows.h>
#include <conio.h>
#include <stdio.h>
void main(void)
{
HKEY hkey;
DWORD dwDataSize;
DWORD dwType;
DWORD dwCpuUsage;
// starting the counter
if ( RegOpenKeyEx( HKEY_DYN_DATA,
"PerfStats\\StartStat",
0,KEY_ALL_ACCESS,
&hkey ) != ERROR_SUCCESS)
return;
dwDataSize=sizeof(DWORD);
RegQueryValueEx( hkey,
"KERNEL\\CPUUsage",
NULL,&dwType,
(LPBYTE)&dwCpuUsage,
&dwDataSize );
RegCloseKey(hkey);
// geting current counter's value
if ( RegOpenKeyEx( HKEY_DYN_DATA,
"PerfStats\\StatData",
0,KEY_READ,
&hkey ) != ERROR_SUCCESS)
return;
printf("\nCPU Usage (press any key to exit): ");
while(!_kbhit())
{
dwDataSize=sizeof(DWORD);
RegQueryValueEx( hkey,
"KERNEL\\CPUUsage",
NULL,&dwType,
(LPBYTE)&dwCpuUsage,
&dwDataSize );
Sleep(500);
printf("\b\b\b\b=%%",dwCpuUsage);
}
printf("\n");
RegCloseKey(hkey);
// stoping the counter
if ( RegOpenKeyEx( HKEY_DYN_DATA,
"PerfStats\\StopStat",
0,KEY_ALL_ACCESS,
&hkey ) != ERROR_SUCCESS)
return;
dwDataSize=sizeof(DWORD);
RegQueryValueEx( hkey,
"KERNEL\\CPUUsage",
NULL,&dwType,
(LPBYTE)&dwCpuUsage,
&dwDataSize );
RegCloseKey(hkey);
}
Introduction
One of the interesting features I found in C# is a ?Events and Delegates? concept. The idea is good but not new in Object Oriented Programming, it is one of the most frequently used concepts in programming, sometimes referred to as ?Observer? or ?Document/View? design pattern. Classical formulation of it could be found in ?Design Patterns, Elements of Reusable Object Oriented Software? by Erich Gamma, Richard Helm, Ralph Johnson, and John Vlissides (The Gang of Four).
This concept is used when you want some information stored in one object, called ?model? (subject) to be watched by others, called ?views? (observers). Each time when information is changed in the ?model?, ?views? attached to the model should receive notification and update there states accordingly to the changed ?model?.
Classical implementation described in ?Design Patterns?:

As it is seen from the class diagram, concrete models should be derived from Subject
class and views from Observer
. Any time the state of Subject
is changed, it calls notify
method which notifies all observers attached to the Subject
.
Collapse
Copy Code
void Subject::notify()
{
for(int i=0; i<observes.size(); i++)
observers[i]->update();
}
In many applications, this straightforward implementation is good enough, but things are getting ugly when you have different kinds of changes in the ?subject? and you want to pass different types of parameters to the ?views?.
One of the examples for complex ?Model?/?View? relations is a GUI control attached to its processing function. Each time the control?s state is changed, process function is called with parameters indicating new state of the control.
These kinds of problems are solved in C# by the introduction of ?Events and Delegates? concept. The resolution of the problem is easier in C#, because all classes are inherited from the same ?object? class.
At the beginning, I thought why we do not have this nice ?Events and Delegates? thing in standard C++, but then I came to the conclusion that C++ does not need it.
C++ language is powerful enough to express ?Events? and ?Delegates? concept in terms of already existing primitives. Proposed design makes it possible to "connect" different methods with different number of parameters belonging to unrelated classes to the ?model?.
The keys for this solution are C++ templates (generic types) and pointes to member functions.
Using Code
Suppose we have a class MySubject
that has internal information connected to different views, it produces three different types of events called int_event
, double_event
and triple_event
with different types and numbers of parameters.
Collapse
Copy Code
class MySubject
{
public:
CppEvent1<bool,int> int_event;
CppEvent2<bool,double,int> double_event;
CppEvent3<bool,double,int,const char*> triple_event;
void submit_int()
{
int_event.notify(1);
}
void submit_double()
{
double_event.notify(10.5,100);
}
void submit_triple()
{
triple_event.notify(10.5,100,"Oh ye");
}
};
Views represented by MyListener1
and MyListener2
are unrelated. The only requirement is for callback (delegate) methods to have parameters signature similar to corresponding CppEvent
.
Collapse
Copy Code
class MyListener1
{
public:
bool update_int(int p)
{
Console::WriteLine(S"int update listener 1");
return true;
}
bool update_double(double p,int p1)
{
Console::WriteLine(S"double update listener 1");
return true;
}
bool update_triple(double p,int p1,const char* str)
{
Console::WriteLine(S"triple update listener 1");
return true;
}
};
class MyListener2
{
public:
bool fun(int p)
{
Console::WriteLine(S"int update listener 2");
return true;
}
};
The final step is to create viewers MyListener1
and MyListener2
and connect their member functions to corresponding events in MySubject
model.
Collapse
Copy Code
int main(void)
{
MyListener1* listener1 = new MyListener1;
MyListener2* listener2 = new MyListener2;
MySubject subject;
CppEventHandler h1 = subject.int_event.attach(listener1,
&MyListener1::update_int);
CppEventHandler h2 = subject.int_event.attach(listener2,
&MyListener2::fun);
CppEventHandler h3 = subject.double_event.attach(listener1,
&MyListener1::update_double);
CppEventHandler h4 = subject.triple_event.attach(listener1,
&MyListener1::update_triple);
subject.submit_int();
subject.submit_double();
subject.submit_triple();
subject.int_event.detach(h1);
subject.int_event.detach(h2);
subject.double_event.detach(h3);
subject.triple_event.detach(h4);
return 0;
}
Resulting output is:
Collapse
Copy Code
> int update listener 1
> int update listener 2
> double update listener 1
> triple update listener 1
Implementation
First of all, if we want to attach different types of event handles (member functions with same types of parameters from different classes) to the same event, we should provide common base for them. We use templates to make it generic for any combination of parameter types in ?delegate? or call back method. There are different event types for every number of arguments in callback function.
Collapse
Copy Code
template <typename ReturnT,typename ParamT>
class EventHandlerBase1
{
public:
virtual ReturnT notify(ParamT param) = 0;
};
Specific type of member function pointer within a pointer to the object is stored in the derived class.
Collapse
Copy Code
template <typename ListenerT,typename ReturnT,typename ParamT>
class EventHandler1 : public EventHandlerBase1<ReturnT,ParamT>
{
typedef ReturnT (ListenerT::*PtrMember)(ParamT);
ListenerT* m_object;
PtrMember m_member;
public:
EventHandler1(ListenerT* object, PtrMember member)
: m_object(object), m_member(member)
{}
ReturnT notify(ParamT param)
{
return (m_object->*m_member)(param);
}
};
Event class stores map of event handlers and notifies all of them when notify
method is called. Detach
method is used to release handler from the map.
Collapse
Copy Code
template <typename ReturnT,typename ParamT>
class CppEvent1
{
typedef std::map<int,EventHandlerBase1<ReturnT,ParamT> *> HandlersMap;
HandlersMap m_handlers;
int m_count;
public:
CppEvent1()
: m_count(0) {}
template <typename ListenerT>
CppEventHandler attach(ListenerT* object,ReturnT (ListenerT::*member)(ParamT))
{
typedef ReturnT (ListenerT::*PtrMember)(ParamT);
m_handlers[m_count] = (new EventHandler1<ListenerT,
ReturnT,ParamT>(object,member));
m_count++;
return m_count-1;
}
bool detach(CppEventHandler id)
{
HandlersMap::iterator it = m_handlers.find(id);
if(it == m_handlers.end())
return false;
delete it->second;
m_handlers.erase(it);
return true;
}
ReturnT notify(ParamT param)
{
HandlersMap::iterator it = m_handlers.begin();
for(; it != m_handlers.end(); it++)
{
it->second->notify(param);
}
return true;
}
};
Comments
This implementation is quite similar to those in the article ?Emulating C# delegates in Standard C++?. I found out it after I already wrote the article. Actually, the fact that we have a similar way to deal with the problem means that it?s a very intuitive solution for this kind of problem in C++. An advantage of the current implementation is that it supports different number of arguments, so any member function of any class could be a callback (delegate). Probably to have this thing as a part of standard library is a good thing, but even if it?s not a part of the standard, you can use it as it is. This implementation is restricted to events up to 3 parameters, it can be easily extended to other numbers by just rewriting it with different number of parameters (see code for details).
作用:
定義對(duì)象間的一種一對(duì)多的依賴關(guān)系,當(dāng)一個(gè)對(duì)象的狀態(tài)發(fā)生改變時(shí),所有依賴于它的對(duì)象都得到通知并被自動(dòng)更新。
UML結(jié)構(gòu)圖:

解析:
Observer模式定義的是一種一對(duì)多的關(guān)系,這里的一就是圖中的Subject類(lèi),而多則是Obesrver類(lèi),當(dāng)Subject類(lèi)的狀態(tài)發(fā)生變化的時(shí)候通知與之對(duì)應(yīng)的Obesrver類(lèi)們也去相應(yīng)的更新?tīng)顟B(tài),同時(shí)支持動(dòng)態(tài)的添加和刪除Observer對(duì)象的功能。Obesrver模式的實(shí)現(xiàn)要點(diǎn)是,第一一般subject類(lèi)都是采用鏈表等容器來(lái)存放Observer對(duì)象,第二抽取出Observer對(duì)象的一些公共的屬性形成Observer基類(lèi),而Subject中保存的則是Observer類(lèi)對(duì)象的指針,這樣就使Subject和具體的Observer實(shí)現(xiàn)了解耦,也就是Subject不需要去關(guān)心到底是哪個(gè)Observer對(duì)放進(jìn)了自己的容器中。生活中有很多例子可以看做是Observer模式的運(yùn)用,比方說(shuō),一個(gè)班有一個(gè)班主任(Subject),他管理手下的一幫學(xué)生(Observer),當(dāng)班里有一些事情發(fā)生需要通知學(xué)生的時(shí)候,班主任要做的不是逐個(gè)學(xué)生挨個(gè)的通知而是把學(xué)生召集起來(lái)一起通知,實(shí)現(xiàn)了班主任和具體學(xué)生的關(guān)系解耦。
實(shí)現(xiàn):
1)Observer.h
/**//********************************************************************
created: 2006/07/20
filename: Observer.h
author: 李創(chuàng)
http://www.shnenglu.com/converse/
purpose: Observer模式的演示代碼
*********************************************************************/
#ifndef OBSERVER_H
#define OBSERVER_H
#include <list>
typedef int STATE;
class Observer;
// Subject抽象基類(lèi),只需要知道Observer基類(lèi)的聲明就可以了
class Subject
{
public:
Subject() : m_nSubjectState(-1){}
virtual ~Subject();
void Notify(); // 通知對(duì)象改變狀態(tài)
void Attach(Observer *pObserver); // 新增對(duì)象
void Detach(Observer *pObserver); // 刪除對(duì)象
// 虛函數(shù),提供默認(rèn)的實(shí)現(xiàn),派生類(lèi)可以自己實(shí)現(xiàn)來(lái)覆蓋基類(lèi)的實(shí)現(xiàn)
virtual void SetState(STATE nState); // 設(shè)置狀態(tài)
virtual STATE GetState(); // 得到狀態(tài)
protected:
STATE m_nSubjectState; // 模擬保存Subject狀態(tài)的變量
std::list<Observer*> m_ListObserver; // 保存Observer指針的鏈表
};
// Observer抽象基類(lèi)
class Observer
{
public:
Observer() : m_nObserverState(-1){}
virtual ~Observer(){}
// 純虛函數(shù),各個(gè)派生類(lèi)可能有不同的實(shí)現(xiàn)
// 通知Observer狀態(tài)發(fā)生了變化
virtual void Update(Subject* pSubject) = 0;
protected:
STATE m_nObserverState; // 模擬保存Observer狀態(tài)的變量
};
// ConcreateSubject類(lèi),派生在Subject類(lèi)
class ConcreateSubject
: public Subject
{
public:
ConcreateSubject() : Subject(){}
virtual ~ConcreateSubject(){}
// 派生類(lèi)自己實(shí)現(xiàn)來(lái)覆蓋基類(lèi)的實(shí)現(xiàn)
virtual void SetState(STATE nState); // 設(shè)置狀態(tài)
virtual STATE GetState(); // 得到狀態(tài)
};
// ConcreateObserver類(lèi)派生自O(shè)bserver
class ConcreateObserver
: public Observer
{
public:
ConcreateObserver() : Observer(){}
virtual ~ConcreateObserver(){}
// 虛函數(shù),實(shí)現(xiàn)基類(lèi)提供的接口
virtual void Update(Subject* pSubject);
};
#endif
2)Observer.cpp
/**//********************************************************************
created: 2006/07/20
filename: Observer.cpp
author: 李創(chuàng)
http://www.shnenglu.com/converse/
purpose: Observer模式的演示代碼
*********************************************************************/
#include "Observer.h"
#include <iostream>
#include <algorithm>
/**//* --------------------------------------------------------------------
| Subject類(lèi)成員函數(shù)的實(shí)現(xiàn)
|
----------------------------------------------------------------------*/
void Subject::Attach(Observer *pObserver)
{
std::cout << "Attach an Observern";
m_ListObserver.push_back(pObserver);
}
void Subject::Detach(Observer *pObserver)
{
std::list<Observer*>::iterator iter;
iter = std::find(m_ListObserver.begin(), m_ListObserver.end(), pObserver);
if (m_ListObserver.end() != iter)
{
m_ListObserver.erase(iter);
}
std::cout << "Detach an Observern";
}
void Subject::Notify()
{
std::cout << "Notify Observers''s Staten";
std::list<Observer*>::iterator iter1, iter2;
for (iter1 = m_ListObserver.begin(), iter2 = m_ListObserver.end();
iter1 != iter2;
++iter1)
{
(*iter1)->Update(this);
}
}
void Subject::SetState(STATE nState)
{
std::cout << "SetState By Subjectn";
m_nSubjectState = nState;
}
STATE Subject::GetState()
{
std::cout << "GetState By Subjectn";
return m_nSubjectState;
}
Subject::~Subject()
{
std::list<Observer*>::iterator iter1, iter2, temp;
for (iter1 = m_ListObserver.begin(), iter2 = m_ListObserver.end();
iter1 != iter2;
)
{
temp = iter1;
++iter1;
delete (*temp);
}
m_ListObserver.clear();
}
/**//* --------------------------------------------------------------------
| ConcreateSubject類(lèi)成員函數(shù)的實(shí)現(xiàn)
|
----------------------------------------------------------------------*/
void ConcreateSubject::SetState(STATE nState)
{
std::cout << "SetState By ConcreateSubjectn";
m_nSubjectState = nState;
}
STATE ConcreateSubject::GetState()
{
std::cout << "GetState By ConcreateSubjectn";
return m_nSubjectState;
}
/**//* --------------------------------------------------------------------
| ConcreateObserver類(lèi)成員函數(shù)的實(shí)現(xiàn)
|
----------------------------------------------------------------------*/
void ConcreateObserver::Update(Subject* pSubject)
{
if (NULL == pSubject)
return;
m_nObserverState = pSubject->GetState();
std::cout << "The ObeserverState is " << m_nObserverState << std::endl;
}
3)Main.cpp
/**//********************************************************************
created: 2006/07/21
filename: Main.cpp
author: 李創(chuàng)
http://www.shnenglu.com/converse/
purpose: Observer模式的測(cè)試代碼
*********************************************************************/
#include "Observer.h"
#include <iostream>
int main()
{
Observer *p1 = new ConcreateObserver;
Observer *p2 = new ConcreateObserver;
Subject* p = new ConcreateSubject;
p->Attach(p1);
p->Attach(p2);
p->SetState(4);
p->Notify();
p->Detach(p1);
p->SetState(10);
p->Notify();
delete p;
system("pause");
return 0;
}
2009年7月21日
在許多廣泛應(yīng)用的程序庫(kù),我們會(huì)看到類(lèi)似 #pragma pack(push, 4) 等這樣的標(biāo)示。因?yàn)橛脩魰?huì)任意更改他們的結(jié)構(gòu)成員對(duì)齊選項(xiàng),對(duì)于先于這些內(nèi)容創(chuàng)建的程序庫(kù)來(lái)說(shuō),不能確保一定的內(nèi)存布局將可能在預(yù)先書(shū)寫(xiě)的一些數(shù)據(jù)訪問(wèn)模塊上導(dǎo)致錯(cuò)誤,或者根本不可能實(shí)現(xiàn)。
我在實(shí)現(xiàn)一種 C++ 類(lèi)的實(shí)例的序列化工具時(shí),依賴了內(nèi)存布局。我知道市面上很多“序列化”工具允許更為廣泛的通信用途,但是它們也是用起來(lái)最麻煩的,有很多限制條件。我實(shí)現(xiàn)的序列化工具用意很明顯,為特定運(yùn)行模塊提供便捷高效的持久化存儲(chǔ)能力。
為了提供感性的認(rèn)識(shí),提供了一個(gè)使用這個(gè)序列化工具的類(lèi)型定義。
class StorageDoc
: public SerialOwner
{
public:
Serializable(StorageDoc);
char c;
int i;
SerialString str;
};
它繼承自 SerialOwner,它聲明了 Serializable,隱含著實(shí)現(xiàn)了一些接口,為基類(lèi)訪問(wèn)當(dāng)前類(lèi)型信息提供幫助。這是較早書(shū)寫(xiě)的一種方案,現(xiàn)在我會(huì)改用模板以便在編譯時(shí)建立類(lèi)型信息,不過(guò)原理完全一樣。
現(xiàn)在,StorageDoc 當(dāng)中的內(nèi)存布局需要可確定的,但是用戶會(huì)選擇不同的結(jié)構(gòu)成員對(duì)齊選項(xiàng),為此需要設(shè)定一個(gè)結(jié)構(gòu)成員對(duì)齊的“子域”,完成這項(xiàng)能力的偽指令是 #pragma pack。
#pragma pack( [ show ] | [ push | pop ] [, identifier ] , n )
1)當(dāng)選用 show,則添加一條警告信息,指示當(dāng)前編譯域內(nèi)的對(duì)齊屬性
2)僅僅設(shè)置 n,則重寫(xiě)編譯器選項(xiàng) /Zp,并影響到此聲明以下的同一個(gè)編譯單元內(nèi)的所有結(jié)構(gòu)定義
3)push 以及 pop 管理了一組“子域”堆棧,可以不斷加深嵌套
4)identifier 命名了堆棧上的對(duì)齊項(xiàng),以便在特定需求中彈出合適的項(xiàng)目
以下是使用的注意事項(xiàng):
1)不論何時(shí),#pragma pack() 總是恢復(fù)到 /Zp 的預(yù)設(shè)值,即使處于 push 的“子域”
2)#pragma pack(push) 未指定對(duì)齊值,則不改變
3)#pragma pack(pop) 可指定對(duì)齊值出棧后的設(shè)置值,若不指定則按嵌套等級(jí)還原,直至 /Zp 預(yù)設(shè)值
綜上,#pragma pack(pop) 總是能正確回退到上一個(gè)作用域,不管該作用域通過(guò) #pragma pack(n) 聲明或者 #pragma pack(push, n)。而 #pragma pack() 總是取預(yù)設(shè)值。對(duì)于用戶事先指定了一個(gè)“子域”,并在其中引入了一個(gè)使用 #pragma pack(n) - #pragma pack() 對(duì)而非堆棧形式來(lái)聲明局部結(jié)構(gòu)成員對(duì)齊的頭文件,會(huì)使用戶非常困惑。 就是這樣做的。
當(dāng)我們?yōu)槌绦驇?kù)編譯運(yùn)行時(shí),有一些類(lèi)型要求嚴(yán)格地遵守內(nèi)存布局,比如一些硬件允許我們傳入的數(shù)據(jù)就需要這么做,就可以把它們限定起來(lái):
#pragma pack(push, 8)
#include "Chain.h"
#include "ByteQueue.h"
#include "SerialOwner.h"
#include "SerialUser.h"
#include "SerialString.h"
#include "SerialStream.h"
#pragma pack(pop)
事情再回到序列化上面,用戶會(huì)多次嘗試編譯他們的序列化應(yīng)用模塊,并指望前一次編譯之后運(yùn)行所產(chǎn)生的文件仍然是可用的,所以還需要在用戶文件當(dāng)中明確所選用的對(duì)齊值,并一旦確定就不再更改:
#pragma pack(push, 8)
class StorageDoc
: public SerialOwner
{
public:
Serializable(StorageDoc);
char c;
int i;
SerialString str;
};
#pragma pack(pop)
并使用它們:
StorageDoc doc;
doc.Load(t("doc.bin"));
std::cout << doc.str.Get() << std::endl;
doc.str = ss.str();
std::cout << doc.str.Get() << std::endl;
doc.Save(t("doc.bin"));
這就是全部了,但是正如以上提到的,不僅僅在序列化上,和硬件、鏈接庫(kù)的通信也可能存在嚴(yán)格的內(nèi)存布局的要求,如果你在項(xiàng)目設(shè)計(jì)上遭遇這些困惑,那么現(xiàn)在就可以立即動(dòng)手解決它們。
如果對(duì)本文提到的序列化能力感興趣的話,可以到以下鏈接了解詳情:
http://code.google.com/p/los-lib/source/browse/
目錄是:
svn/trunk/Inc/Los/
文件分別是:
_ISerialUser.h
ByteQueue.h
Chain.h
Serialization.h
SerialOwner.h
SerialStream.h
SerialString.h
SerialUser.h
不過(guò)在本文發(fā)布之時(shí),以上文件所處版本沒(méi)有針對(duì)結(jié)構(gòu)成員對(duì)齊選項(xiàng)進(jìn)行修改,但并不影響閱讀。
* 補(bǔ)充一(2009-1-18 02:41)
聯(lián)合以及結(jié)構(gòu)的結(jié)構(gòu)成員對(duì)齊異常
class Tick
{
static int _StaticID;
__int64 _StartLI; // __alignof(LARGE_INTEGER) != __alignof(__int64)
__int64 _CurrentLI;
__int64 _Frequency;
int _ID;
clock_t _Start;
clock_t _Current;
bool _Stop;
bool _HighPerformance;
...
}
LARGE_INTEGER 是分別對(duì)應(yīng)兩個(gè) 32bit 以及一個(gè) 64bit 類(lèi)型的聯(lián)合,奇怪的是隨著全局對(duì)齊選項(xiàng)的修改,LARGE_INTEGER 類(lèi)型本身的請(qǐng)求對(duì)齊 __alignof(LARGE_INTEGER) 將取聯(lián)合的成員的最大者同全局對(duì)齊選項(xiàng)的最小值,也就是說(shuō),當(dāng) /Zp 設(shè)置為 2,那么 LARGE_INTEGER 也將僅承諾在 2 字節(jié)邊界上對(duì)齊,多么不幸啊。當(dāng)然如果將這個(gè)類(lèi)型納入 #pragma pack 的限定域那就什么問(wèn)題都沒(méi)有了,不管聯(lián)合的對(duì)齊算法多么的古怪,只要保證不修改所需的對(duì)齊值那將總是能獲得確定的內(nèi)存布局。
不過(guò)正如上面的代碼列出的,我使用了 __int64 代替了 LARGE_INTEGER 的工作,并在請(qǐng)求 Win32 API 的接口上強(qiáng)制指針轉(zhuǎn)型,使用的時(shí)候亦如此,但若訪問(wèn)聯(lián)合成員剛好為 __int64 類(lèi)型則直接使用便可。這種方式?jīng)]有獲得額外的好處,算是一種抗議的行為,并且讓后來(lái)的閱讀者有機(jī)會(huì)了解到這個(gè)見(jiàn)不得光的問(wèn)題。
_HighPerformance = ::QueryPerformanceFrequency((LARGE_INTEGER*)&_Frequency) != 0;
當(dāng)然作為嚴(yán)肅的代碼寫(xiě)作者,也許你將在不止一處使用到 LARGE_INTEGER,為此我也不拒絕使用如下格式:
#pragma pack(push, 8)
#include
#pragma pack(pop)
它可保證你萬(wàn)無(wú)一失。
作為對(duì)比,F(xiàn)ILETIME 有如下定義:
typedef struct _FILETIME
{
DWORD dwLowDateTime;
DWORD dwHighDateTime;
} FILETIME;
且不論它所需的可能的最大結(jié)構(gòu)成員對(duì)齊為 4,它也將伴隨著 /Zp 的更改而變動(dòng)。因此,在不同的選項(xiàng)的影響下:
__alignof(LARGE_INTEGER) != __alignof(FILETIME) != __alignof(__int64)
有些人可能要指責(zé)會(huì)發(fā)生這樣的問(wèn)題純粹是用戶在玩弄“結(jié)構(gòu)成員對(duì)齊選項(xiàng)”而導(dǎo)致的,我真希望他能夠讀一讀這篇文章。
* 補(bǔ)充二(2009-1-18 02:41)
D3D 與用戶定義結(jié)構(gòu)的協(xié)調(diào)
class VertexXYZ_N_T1
{
public:
float x, y, z;
float normal_x, normal_y, normal_z;
float u, v;
DeviceBitmap* bitmap;
Material* material;
float temp_val;
static const int FVF = D3DFVF_XYZ | D3DFVF_NORMAL | D3DFVF_TEX1;
};
這是一個(gè)自定義頂點(diǎn)結(jié)構(gòu),它的最大成員字節(jié)數(shù)為 4,所有的成員也都是 4 字節(jié)邊界,不論作何選項(xiàng),始終保持緊湊存儲(chǔ),若其中一個(gè)成員擴(kuò)展為 8 字節(jié),那么伴隨著選項(xiàng)的更改,VertexXYZ_N_T1 要求的對(duì)齊邊界可導(dǎo)致部分空洞,從而同硬件所需的頂點(diǎn)緩存數(shù)據(jù)布局存在出入,我不追究硬件是否使用 double 值,但是現(xiàn)在就應(yīng)當(dāng)使用
#pragma pack(push, 4)
...
#pragma pack(pop)
加以限定。
我還定義了 Matrix, Material, Vector3, Colorf 等類(lèi)型,如果要使得這些數(shù)據(jù)同 D3D, D3DX 的相應(yīng)類(lèi)型在內(nèi)存上兼容的,也是需要限定的。
本文主要包括二個(gè)部分,第一部分重點(diǎn)介紹在VC中,怎么樣采用sizeof來(lái)求結(jié)構(gòu)的大小,以及容易出現(xiàn)的問(wèn)題,并給出解決問(wèn)題的方法,第二部分總結(jié)出VC中sizeof的主要用法。
1、 sizeof應(yīng)用在結(jié)構(gòu)上的情況
請(qǐng)看下面的結(jié)構(gòu):
struct MyStruct
{
double dda1;
char dda;
int type
};
對(duì)結(jié)構(gòu)MyStruct采用sizeof會(huì)出現(xiàn)什么結(jié)果呢?sizeof(MyStruct)為多少呢?也許你會(huì)這樣求:
sizeof(MyStruct)=sizeof(double)+sizeof(char)+sizeof(int)=13
但是當(dāng)在VC中測(cè)試上面結(jié)構(gòu)的大小時(shí),你會(huì)發(fā)現(xiàn)sizeof(MyStruct)為16。你知道為什么在VC中會(huì)得出這樣一個(gè)結(jié)果嗎?
其實(shí),這是VC對(duì)變量存儲(chǔ)的一個(gè)特殊處理。為了提高CPU的存儲(chǔ)速度,VC對(duì)一些變量的起始地址做了“對(duì)齊”處理。在默認(rèn)情況下,VC規(guī)定各成員變量存放的起始地址相對(duì)于結(jié)構(gòu)的起始地址的偏移量必須為該變量的類(lèi)型所占用的字節(jié)數(shù)的倍數(shù)。下面列出常用類(lèi)型的對(duì)齊方式(vc6.0,32位系統(tǒng))。
類(lèi)型
對(duì)齊方式(變量存放的起始地址相對(duì)于結(jié)構(gòu)的起始地址的偏移量)
Char
偏移量必須為sizeof(char)即1的倍數(shù)
int
偏移量必須為sizeof(int)即4的倍數(shù)
float
偏移量必須為sizeof(float)即4的倍數(shù)
double
偏移量必須為sizeof(double)即8的倍數(shù)
Short
偏移量必須為sizeof(short)即2的倍數(shù)
各成員變量在存放的時(shí)候根據(jù)在結(jié)構(gòu)中出現(xiàn)的順序依次申請(qǐng)空間,同時(shí)按照上面的對(duì)齊方式調(diào)整位置,空缺的字節(jié)VC會(huì)自動(dòng)填充。同時(shí)VC為了確保結(jié)構(gòu)的大小為結(jié)構(gòu)的字節(jié)邊界數(shù)(即該結(jié)構(gòu)中占用最大空間的類(lèi)型所占用的字節(jié)數(shù))的倍數(shù),所以在為最后一個(gè)成員變量申請(qǐng)空間后,還會(huì)根據(jù)需要自動(dòng)填充空缺的字節(jié)。
下面用前面的例子來(lái)說(shuō)明VC到底怎么樣來(lái)存放結(jié)構(gòu)的。
struct MyStruct
{
double dda1;
char dda;
int type
};
為上面的結(jié)構(gòu)分配空間的時(shí)候,VC根據(jù)成員變量出現(xiàn)的順序和對(duì)齊方式,先為第一個(gè)成員dda1分配空間,其起始地址跟結(jié)構(gòu)的起始地址相同(剛好偏移量0剛好為sizeof(double)的倍數(shù)),該成員變量占用sizeof(double)=8個(gè)字節(jié);接下來(lái)為第二個(gè)成員dda分配空間,這時(shí)下一個(gè)可以分配的地址對(duì)于結(jié)構(gòu)的起始地址的偏移量為8,是sizeof(char)的倍數(shù),所以把dda存放在偏移量為8的地方滿足對(duì)齊方式,該成員變量占用sizeof(char)=1個(gè)字節(jié);接下來(lái)為第三個(gè)成員type分配空間,這時(shí)下一個(gè)可以分配的地址對(duì)于結(jié)構(gòu)的起始地址的偏移量為9,不是sizeof(int)=4的倍數(shù),為了滿足對(duì)齊方式對(duì)偏移量的約束問(wèn)題,VC自動(dòng)填充3個(gè)字節(jié)(這三個(gè)字節(jié)沒(méi)有放什么東西),這時(shí)下一個(gè)可以分配的地址對(duì)于結(jié)構(gòu)的起始地址的偏移量為12,剛好是sizeof(int)=4的倍數(shù),所以把type存放在偏移量為12的地方,該成員變量占用sizeof(int)=4個(gè)字節(jié);這時(shí)整個(gè)結(jié)構(gòu)的成員變量已經(jīng)都分配了空間,總的占用的空間大小為:8+1+3+4=16,剛好為結(jié)構(gòu)的字節(jié)邊界數(shù)(即結(jié)構(gòu)中占用最大空間的類(lèi)型所占用的字節(jié)數(shù)sizeof(double)=8)的倍數(shù),所以沒(méi)有空缺的字節(jié)需要填充。所以整個(gè)結(jié)構(gòu)的大小為:sizeof(MyStruct)=8+1+3+4=16,其中有3個(gè)字節(jié)是VC自動(dòng)填充的,沒(méi)有放任何有意義的東西。
下面再舉個(gè)例子,交換一下上面的MyStruct的成員變量的位置,使它變成下面的情況:
struct MyStruct
{
char dda;
double dda1;
int type
};
這個(gè)結(jié)構(gòu)占用的空間為多大呢?在VC6.0環(huán)境下,可以得到sizeof(MyStruc)為24。結(jié)合上面提到的分配空間的一些原則,分析下VC怎么樣為上面的結(jié)構(gòu)分配空間的。(簡(jiǎn)單說(shuō)明)
struct MyStruct
{
char dda;//偏移量為0,滿足對(duì)齊方式,dda占用1個(gè)字節(jié);
double dda1;//下一個(gè)可用的地址的偏移量為1,不是sizeof(double)=8
//的倍數(shù),需要補(bǔ)足7個(gè)字節(jié)才能使偏移量變?yōu)?(滿足對(duì)齊
//方式),因此VC自動(dòng)填充7個(gè)字節(jié),dda1存放在偏移量為8
//的地址上,它占用8個(gè)字節(jié)。
int type;//下一個(gè)可用的地址的偏移量為16,是sizeof(int)=4的倍
//數(shù),滿足int的對(duì)齊方式,所以不需要VC自動(dòng)填充,type存
//放在偏移量為16的地址上,它占用4個(gè)字節(jié)。
};//所有成員變量都分配了空間,空間總的大小為1+7+8+4=20,不是結(jié)構(gòu)
//的節(jié)邊界數(shù)(即結(jié)構(gòu)中占用最大空間的類(lèi)型所占用的字節(jié)數(shù)sizeof
//(double)=8)的倍數(shù),所以需要填充4個(gè)字節(jié),以滿足結(jié)構(gòu)的大小為
//sizeof(double)=8的倍數(shù)。
所以該結(jié)構(gòu)總的大小為:sizeof(MyStruc)為1+7+8+4+4=24。其中總的有7+4=11個(gè)字節(jié)是VC自動(dòng)填充的,沒(méi)有放任何有意義的東西。
VC對(duì)結(jié)構(gòu)的存儲(chǔ)的特殊處理確實(shí)提高CPU存儲(chǔ)變量的速度,但是有時(shí)候也帶來(lái)了一些麻煩,我們也屏蔽掉變量默認(rèn)的對(duì)齊方式,自己可以設(shè)定變量的對(duì)齊方式。
VC中提供了#pragma pack(n)來(lái)設(shè)定變量以n字節(jié)對(duì)齊方式。n字節(jié)對(duì)齊就是說(shuō)變量存放的起始地址的偏移量有兩種情況:第一、如果n大于等于該變量所占用的字節(jié)數(shù),那么偏移量必須滿足默認(rèn)的對(duì)齊方式,第二、如果n小于該變量的類(lèi)型所占用的字節(jié)數(shù),那么偏移量為n的倍數(shù),不用滿足默認(rèn)的對(duì)齊方式。結(jié)構(gòu)的總大小也有個(gè)約束條件,分下面兩種情況:如果n大于所有成員變量類(lèi)型所占用的字節(jié)數(shù),那么結(jié)構(gòu)的總大小必須為占用空間最大的變量占用的空間數(shù)的倍數(shù);
否則必須為n的倍數(shù)。下面舉例說(shuō)明其用法。
#pragma pack(push) //保存對(duì)齊狀態(tài)
#pragma pack(4)//設(shè)定為4字節(jié)對(duì)齊
struct test
{
char m1;
double m4;
int m3;
};
#pragma pack(pop)//恢復(fù)對(duì)齊狀態(tài)
以上結(jié)構(gòu)的大小為16,下面分析其存儲(chǔ)情況,首先為m1分配空間,其偏移量為0,滿足我們自己設(shè)定的對(duì)齊方式(4字節(jié)對(duì)齊),m1占用1個(gè)字節(jié)。接著開(kāi)始為m4分配空間,這時(shí)其偏移量為1,需要補(bǔ)足3個(gè)字節(jié),這樣使偏移量滿足為n=4的倍數(shù)(因?yàn)閟izeof(double)大于n),m4占用8個(gè)字節(jié)。接著為m3分配空間,這時(shí)其偏移量為12,滿足為4的倍數(shù),m3占用4個(gè)字節(jié)。這時(shí)已經(jīng)為所有成員變量分配了空間,共分配了16個(gè)字節(jié),滿足為n的倍數(shù)。如果把上面的#pragma pack(4)改為#pragma pack(16),那么我們可以得到結(jié)構(gòu)的大小為24。(請(qǐng)讀者自己分析)
2、 sizeof用法總結(jié)
在VC中,sizeof有著許多的用法,而且很容易引起一些錯(cuò)誤。下面根據(jù)sizeof后面的參數(shù)對(duì)sizeof的用法做個(gè)總結(jié)。
A. 參數(shù)為數(shù)據(jù)類(lèi)型或者為一般變量。例如sizeof(int),sizeof(long)等等。這種情況要注意的是不同系統(tǒng)系統(tǒng)或者不同編譯器得到的結(jié)果可能是不同的。例如int類(lèi)型在16位系統(tǒng)中占2個(gè)字節(jié),在32位系統(tǒng)中占4個(gè)字節(jié)。
B. 參數(shù)為數(shù)組或指針。下面舉例說(shuō)明.
int a[50]; //sizeof(a)=4*50=200; 求數(shù)組所占的空間大小
int *a=new int[50];// sizeof(a)=4; a為一個(gè)指針,sizeof(a)是求指針
//的大小,在32位系統(tǒng)中,當(dāng)然是占4個(gè)字節(jié)。
C. 參數(shù)為結(jié)構(gòu)或類(lèi)。Sizeof應(yīng)用在類(lèi)和結(jié)構(gòu)的處理情況是相同的。但有兩點(diǎn)需要注意,第一、結(jié)構(gòu)或者類(lèi)中的靜態(tài)成員不對(duì)結(jié)構(gòu)或者類(lèi)的大小產(chǎn)生影響,因?yàn)殪o態(tài)變量的存儲(chǔ)位置與結(jié)構(gòu)或者類(lèi)的實(shí)例地址無(wú)關(guān)。
第二、沒(méi)有成員變量的結(jié)構(gòu)或類(lèi)的大小為1,因?yàn)楸仨毐WC結(jié)構(gòu)或類(lèi)的每一
個(gè)實(shí)例在內(nèi)存中都有唯一的地址。
下面舉例說(shuō)明,
Class Test{int a;static double c};//sizeof(Test)=4.
Test *s;//sizeof(s)=4,s為一個(gè)指針。
Class test1{ };//sizeof(test1)=1;
D. 參數(shù)為其他。下面舉例說(shuō)明。
int func(char s[5]);
{
cout<< sizeof(s) << endl;
//數(shù)組參數(shù)在傳遞的時(shí)候系統(tǒng)處理為一個(gè)指針,所
//以sizeof(s)實(shí)際上為求指針的大小。
return 1;
}
sizeof(func(“1234”))=4//因?yàn)閒unc的返回類(lèi)型為int,所以相當(dāng)于
求sizeof(int).
以上為sizeof的基本用法,在實(shí)際的使用中要注意分析VC的分配變量的分配策略,這樣的話可以避免一些錯(cuò)誤。