亚洲av成人无遮挡网站在线观看,少妇性bbb搡bbb爽爽爽,亚洲av日韩精品久久久久久,兔费看少妇性l交大片免费,无码少妇一区二区三区

  免費注冊 查看新帖 |

Chinaunix

  平臺 論壇 博客 文庫
12下一頁
最近訪問板塊 發(fā)新帖
查看: 4360 | 回復(fù): 13
打印 上一主題 下一主題

高質(zhì)量C++/C編程指南 [復(fù)制鏈接]

論壇徽章:
0
跳轉(zhuǎn)到指定樓層
1 [收藏(0)] [報告]
發(fā)表于 2008-04-18 23:21 |只看該作者 |倒序瀏覽
前 言
      

軟件質(zhì)量是被大多數(shù)程序員掛在嘴上而不是放在心上的東西!

    除了完全外行和真正的編程高手外,初讀本書,你最先的感受將是驚慌:“哇!我以前捏造的C++/C程序怎么會有那么多的毛?”

    別難過,作者只不過比你早幾年、多幾次驚慌而已。

    請花一兩個小時認(rèn)真閱讀這本百頁經(jīng)書,你將會獲益匪淺,這是前面N-1個讀者的建議。



一、編程老手與高手的誤區(qū)



自從計算機(jī)問世以來,程序設(shè)計就成了令人羨慕的職業(yè),程序員在受人寵愛之后容易發(fā)展成為毛病特多卻常能自我臭美的群體。

如今在Internet上流傳的“真正”的程序員據(jù)說是這樣的:

(1)    真正的程序員沒有進(jìn)度表,只有討好領(lǐng)導(dǎo)的馬屁精才有進(jìn)度表,真正的程序員會讓領(lǐng)導(dǎo)提心吊膽。

(2)    真正的程序員不寫使用說明書,用戶應(yīng)當(dāng)自己去猜想程序的功能。

(3)    真正的程序員幾乎不寫代碼的注釋,如果注釋很難寫,它理所當(dāng)然也很難讀。

(4)    真正的程序員不畫流程圖,原始人和文盲才會干這事。

(5)    真正的程序員不看參考手冊,新手和膽小鬼才會看。

(6)    真正的程序員不寫文檔也不需要文檔,只有看不懂程序的笨蛋才用文檔。

(7)    真正的程序員認(rèn)為自己比用戶更明白用戶需要什么。

(    真正的程序員不接受團(tuán)隊開發(fā)的理念,除非他自己是頭頭。

(9)    真正的程序員的程序不會在第一次就正確運行,但是他們愿意守著機(jī)器進(jìn)行若干個30小時的調(diào)試改錯。

(10)真正的程序員不會在上午9:00到下午5:00之間工作,如果你看到他在上午9:00工作,這表明他從昨晚一直干到現(xiàn)在。

……

具備上述特征越多,越顯得水平高,資格老。所以別奇怪,程序員的很多缺點竟然可以被當(dāng)作優(yōu)點來欣賞。就象在武俠小說中,那些獨來獨往、不受約束且?guī)c邪氣的高手最令人崇拜。我曾經(jīng)也這樣信奉,并且希望自己成為那樣的“真正”的程序員,結(jié)果沒有得到好下場。



我從讀大學(xué)到博士畢業(yè)十年來一直勤奮好學(xué),累計編寫了數(shù)十萬行C++/C代碼。有這樣的苦勞和疲勞,我應(yīng)該稱得上是編程老手了吧?

我開發(fā)的軟件都與科研相關(guān)(集成電路CAD和3D圖形學(xué)領(lǐng)域),動輒數(shù)萬行程序,技術(shù)復(fù)雜,難度頗高。這些軟件頻頻獲獎,有一個軟件獲得首屆中國大學(xué)生電腦大賽軟件展示一等獎。在1995年開發(fā)的一套圖形軟件庫到2000年還有人買。羅列出這些“業(yè)績”,可以說明我算得上是編程高手了吧?

可惜這種個人感覺不等于事實。



讀博期間我曾用一年時間開發(fā)了一個近10萬行C++代碼的3D圖形軟件產(chǎn)品,我內(nèi)心得意表面謙虛地向一位真正的軟件高手請教。他雖然從未涉足過3D圖形領(lǐng)域,卻在幾十分鐘內(nèi)指出該軟件多處重大設(shè)計錯誤。讓人感覺那套軟件是用紙糊的華麗衣服,扯一下掉一塊,戳一下破個洞。我目瞪口呆地意識到這套軟件毫無實用價值,一年的心血白化了,并且害死了自己的軟件公司。



人的頓悟通常發(fā)生在最心痛的時刻,在沮喪和心痛之后,我作了深刻反省,“面壁”半年,重新溫習(xí)軟件設(shè)計的基礎(chǔ)知識。補修“內(nèi)功”之后,又覺得腰板硬了起來。博士畢業(yè)前半年,我曾到微軟中國研究院找工作,接受微軟公司一位資深軟件工程師的面試。他讓我寫函數(shù)strcpy的代碼。

太容易了吧?

錯!

這么一個小不點的函數(shù),他從三個方面考查:

(1)編程風(fēng)格;

(2)出錯處理;

(3)算法復(fù)雜度分析(用于提高性能)。

在大學(xué)里從來沒有人如此嚴(yán)格地考查過我的程序。我化了半個小時,修改了數(shù)次,他還不盡滿意,讓我回家好好琢磨。我精神抖擻地進(jìn)“考場”,大汗淋漓地出“考場”。這“高手”當(dāng)?shù)靡蔡C囊了。我又好好地反省了一次。



我把反省后的心得體會寫成文章放在網(wǎng)上傳閱,引起了不少軟件開發(fā)人員的共鳴。我因此有幸和國產(chǎn)大型IT企業(yè)如華為、上海貝爾、中興等公司的同志們廣泛交流。大家認(rèn)為提高質(zhì)量與生產(chǎn)率是軟件工程要解決的核心問題。高質(zhì)量程序設(shè)計是非常重要的環(huán)節(jié),畢竟軟件是靠編程來實現(xiàn)的。

我們心目中的老手們和高手們能否編寫出高質(zhì)量的程序來?

不見得都能!



就我的經(jīng)歷與閱歷來看,國內(nèi)大學(xué)的計算機(jī)教育壓根就沒有灌輸高質(zhì)量程序設(shè)計的觀念,教師們和學(xué)生們也很少自覺關(guān)心軟件的質(zhì)量。勤奮好學(xué)的程序員長期在低質(zhì)量的程序堆中滾爬,吃盡苦頭之后才有一些心得體會,長進(jìn)極慢,我就是一例。

現(xiàn)在國內(nèi)IT企業(yè)擁有學(xué)士、碩士、博士文憑的軟件開發(fā)人員比比皆是,但他們在接受大學(xué)教育時就“先天不足”,豈能一到企業(yè)就突然實現(xiàn)質(zhì)的飛躍。試問有多少軟件開發(fā)人員對正確性、健壯性、可靠性、效率、易用性、可讀性(可理解性)、可擴(kuò)展性、可復(fù)用性、兼容性、可移植性等質(zhì)量屬性了如指掌?并且能在實踐中運用自如?!案哔|(zhì)量”可不是干活小心點就能實現(xiàn)的!



我們有充分的理由疑慮:

(1)編程老手可能會長期用隱含錯誤的方式編程(習(xí)慣成自然),發(fā)現(xiàn)毛病后都不愿相信那是真的!

(2)編程高手可以在某一領(lǐng)域?qū)懗鰳O有水平的代碼,但未必能從全局把握軟件質(zhì)量的方方面面。



       事實證明如此。我到上海貝爾工作一年來,陸續(xù)面試或測試過近百名“新”“老”程序員的編程技能,質(zhì)量合格率大約是10%。很少有人能夠?qū)懗鐾耆腺|(zhì)量要求的if語句,很多程序員對指針、內(nèi)存管理一知半解,……。

領(lǐng)導(dǎo)們不敢相信這是真的。我做過現(xiàn)場試驗:有一次部門新進(jìn)14名碩士生,在開歡迎會之前對他們進(jìn)行“C++/C編程技能”摸底考試。我問大家試題難不難?所有的人都回答不難。結(jié)果沒有一個人及格,有半數(shù)人得零分。競爭對手公司的朋友們也做過試驗,同樣一敗涂地。



真的不是我“心狠手辣”或者要求過高,而是很多軟件開發(fā)人員對自己的要求不夠高。

要知道華為、上海貝爾、中興等公司的員工素質(zhì)在國內(nèi)IT企業(yè)中是比較前列的,倘若他們的編程質(zhì)量都如此差的話,我們怎么敢期望中小公司拿出高質(zhì)量的軟件呢?連程序都編不好,還談什么振興民族軟件產(chǎn)業(yè),豈不胡扯。



我打算定義編程老手和編程高手,請您別見笑。

定義1:能長期穩(wěn)定地編寫出高質(zhì)量程序的程序員稱為編程老手。

定義2:能長期穩(wěn)定地編寫出高難度、高質(zhì)量程序的程序員稱為編程高手。

根據(jù)上述定義,馬上得到第一推論:我既不是高手也算不上是老手。



在寫此書前,我閱讀了不少程序設(shè)計方面的英文著作,越看越羞慚。因為發(fā)現(xiàn)自己連編程基本技能都未能全面掌握,頂多算是二流水平,還好意思談什么老手和高手。希望和我一樣在國內(nèi)土生土長的程序員朋友們能夠做到:

(1)知錯就改;

(2)經(jīng)常溫故而知新;

(3)堅持學(xué)習(xí),天天向上。



二、本書導(dǎo)讀



    首先請做附錄B的C++/C試題(不要看答案),考查自己的編程質(zhì)量究竟如何。然后參照答案嚴(yán)格打分。

(1)如果你只得了幾十分,請不要聲張,也不要太難過。編程質(zhì)量差往往是由于不良習(xí)慣造成的,與人的智力、能力沒有多大關(guān)系,還是有藥可救的。成績越差,可以進(jìn)步的空間就越大,中國不就是在落后中趕超發(fā)達(dá)資本主義國家嗎?只要你能下決心改掉不良的編程習(xí)慣,第二次考試就能及格了。

(2)如果你考及格了,表明你的技術(shù)基礎(chǔ)不錯,希望你能虛心學(xué)習(xí)、不斷進(jìn)步。如果你還沒有找到合適的工作單位,不妨到上海貝爾試一試。

(3)如果你考出85分以上的好成績,你有義務(wù)和資格為你所在的團(tuán)隊作“C++/C編程”培訓(xùn)。希望你能和我們多多交流、相互促進(jìn)。半年前我曾經(jīng)發(fā)現(xiàn)一顆好苗子,就把他挖到我們小組來。

(4)如果你在沒有任何提示的情況下考了滿分,希望你能收我做你的徒弟。



    編程考試結(jié)束后,請閱讀本書的正文。

    本書第一章至第六章主要論述C++/C編程風(fēng)格。難度不高,但是細(xì)節(jié)比較多。別小看了,提高質(zhì)量就是要從這些點點滴滴做起。世上不存在最好的編程風(fēng)格,一切因需求而定。團(tuán)隊開發(fā)講究風(fēng)格一致,如果制定了大家認(rèn)可的編程風(fēng)格,那么所有組員都要遵守。如果讀者覺得本書的編程風(fēng)格比較合你的工作,那么就采用它,不要只看不做。人在小時候說話發(fā)音不準(zhǔn),寫字潦草,如果不改正,總有后悔的時候。編程也是同樣道理。

    第七章至第十一章是專題論述,技術(shù)難度比較高,看書時要積極思考。特別是第七章“內(nèi)存管理”,讀了并不表示懂了,懂了并不表示就能正確使用。有一位同事看了第七章后覺得“野指針”寫得不錯,與我切磋了一把?墒沁^了兩周,他告訴我,他忙了兩天追查出一個Bug,想不到又是“野指針”出問題,只好重讀第七章。

光看本書對提高編程質(zhì)量是有限的,建議大家閱讀本書的參考文獻(xiàn),那些都是經(jīng)典名著。



       如果你的編程質(zhì)量已經(jīng)過關(guān)了,不要就此滿足。如果你想成為優(yōu)秀的軟件開發(fā)人員,建議你閱讀并按照CMMI規(guī)范做事,讓自己的綜合水平上升一個臺階。上海貝爾的員工可以向網(wǎng)絡(luò)應(yīng)用事業(yè)部軟件工程研究小組索取CMMI有關(guān)資料,最好能參加培訓(xùn)。



三、版權(quán)聲明



       本書的大部分內(nèi)容取材于作者一年前的書籍手稿(尚未出版),現(xiàn)整理匯編成為上海貝爾網(wǎng)絡(luò)應(yīng)用事業(yè)部的一個規(guī)范化文件,同時作為培訓(xùn)教材。

       由于C++/C編程是眾所周知的技術(shù),沒有秘密可言。編程的好經(jīng)驗應(yīng)該大家共享,我們自己也是這么學(xué)來的。作者愿意公開本書的電子文檔。

       版權(quán)聲明如下:

(1)讀者可以任意拷貝、修改本書的內(nèi)容,但不可以篡改作者及所屬單位。

(2)未經(jīng)作者許可,不得出版或大量印發(fā)本書。

(3)如果競爭對手公司的員工得到本書,請勿公開使用,以免發(fā)生糾紛。

       預(yù)計到2002年7月,我們將建立切合中國國情的CMMI 3級解決方案。屆時,包括本書在內(nèi)的約1000頁規(guī)范將嚴(yán)格受控。



       歡迎讀者對本書提出批評建議。





林銳,2001年7月

論壇徽章:
0
2 [報告]
發(fā)表于 2008-04-18 23:21 |只看該作者
第1章 文件結(jié)構(gòu)
每個C++/C程序通常分為兩個文件。一個文件用于保存程序的聲明(declaration),稱為頭文件。另一個文件用于保存程序的實現(xiàn)(implementation),稱為定義(definition)文件。

C++/C程序的頭文件以“.h”為后綴,C程序的定義文件以“.c”為后綴,C++程序的定義文件通常以“.cpp”為后綴(也有一些系統(tǒng)以“.cc”或“.cxx”為后綴)。

1.1 版權(quán)和版本的聲明
版權(quán)和版本的聲明位于頭文件和定義文件的開頭(參見示例1-1),主要內(nèi)容有:

(1)版權(quán)信息。

(2)文件名稱,標(biāo)識符,摘要。

(3)當(dāng)前版本號,作者/修改者,完成日期。

(4)版本歷史信息。



/*

* Copyright (c) 2001,上海貝爾有限公司網(wǎng)絡(luò)應(yīng)用事業(yè)部

* All rights reserved.

*

* 文件名稱:filename.h

* 文件標(biāo)識:見配置管理計劃書

* 摘    要:簡要描述本文件的內(nèi)容

*

* 當(dāng)前版本:1.1

* 作    者:輸入作者(或修改者)名字

* 完成日期:2001年7月20日

*

* 取代版本:1.0

* 原作者  :輸入原作者(或修改者)名字

* 完成日期:2001年5月10日

*/




示例1-1 版權(quán)和版本的聲明

1.2 頭文件的結(jié)構(gòu)
頭文件由三部分內(nèi)容組成:

(1)頭文件開頭處的版權(quán)和版本聲明(參見示例1-1)。

(2)預(yù)處理塊。

(3)函數(shù)和類結(jié)構(gòu)聲明等。

假設(shè)頭文件名稱為 graphics.h,頭文件的結(jié)構(gòu)參見示例1-2。



l         【規(guī)則1-2-1】為了防止頭文件被重復(fù)引用,應(yīng)當(dāng)用ifndef/define/endif結(jié)構(gòu)產(chǎn)生預(yù)處理塊。

l         【規(guī)則1-2-2】用 #include <filename.h> 格式來引用標(biāo)準(zhǔn)庫的頭文件(編譯器將從標(biāo)準(zhǔn)庫目錄開始搜索)。

l         【規(guī)則1-2-3】用 #include “filename.h” 格式來引用非標(biāo)準(zhǔn)庫的頭文件(編譯器將從用戶的工作目錄開始搜索)。

&sup2;        【建議1-2-1】頭文件中只存放“聲明”而不存放“定義”

在C++ 語法中,類的成員函數(shù)可以在聲明的同時被定義,并且自動成為內(nèi)聯(lián)函數(shù)。這雖然會帶來書寫上的方便,但卻造成了風(fēng)格不一致,弊大于利。建議將成員函數(shù)的定義與聲明分開,不論該函數(shù)體有多么小。

&sup2;        【建議1-2-2】不提倡使用全局變量,盡量不要在頭文件中出現(xiàn)象extern int value 這類聲明。



// 版權(quán)和版本聲明見示例1-1,此處省略。



#ifndef   GRAPHICS_H  // 防止graphics.h被重復(fù)引用

#define   GRAPHICS_H



#include <math.h>     // 引用標(biāo)準(zhǔn)庫的頭文件



#include “myheader.h”   // 引用非標(biāo)準(zhǔn)庫的頭文件



void Function1(…);   // 全局函數(shù)聲明



class Box             // 類結(jié)構(gòu)聲明

{



};

#endif


示例1-2 C++/C頭文件的結(jié)構(gòu)



1.3 定義文件的結(jié)構(gòu)
定義文件有三部分內(nèi)容:

(1)       定義文件開頭處的版權(quán)和版本聲明(參見示例1-1)。

(2)       對一些頭文件的引用。

(3)       程序的實現(xiàn)體(包括數(shù)據(jù)和代碼)。

假設(shè)定義文件的名稱為 graphics.cpp,定義文件的結(jié)構(gòu)參見示例1-3。



// 版權(quán)和版本聲明見示例1-1,此處省略。



#include “graphics.h”     // 引用頭文件





// 全局函數(shù)的實現(xiàn)體

void Function1(…)

{



}



// 類成員函數(shù)的實現(xiàn)體

void Box:raw(…)

{



}


示例1-3 C++/C定義文件的結(jié)構(gòu)

1.4 頭文件的作用
早期的編程語言如Basic、Fortran沒有頭文件的概念,C++/C語言的初學(xué)者雖然會用使用頭文件,但常常不明其理。這里對頭文件的作用略作解釋:

(1)通過頭文件來調(diào)用庫功能。在很多場合,源代碼不便(或不準(zhǔn))向用戶公布,只要向用戶提供頭文件和二進(jìn)制的庫即可。用戶只需要按照頭文件中的接口聲明來調(diào)用庫功能,而不必關(guān)心接口怎么實現(xiàn)的。編譯器會從庫中提取相應(yīng)的代碼。

(2)頭文件能加強類型安全檢查。如果某個接口被實現(xiàn)或被使用時,其方式與頭文件中的聲明不一致,編譯器就會指出錯誤,這一簡單的規(guī)則能大大減輕程序員調(diào)試、改錯的負(fù)擔(dān)。

1.5 目錄結(jié)構(gòu)
如果一個軟件的頭文件數(shù)目比較多(如超過十個),通常應(yīng)將頭文件和定義文件分別保存于不同的目錄,以便于維護(hù)。

例如可將頭文件保存于include目錄,將定義文件保存于source目錄(可以是多級目錄)。

如果某些頭文件是私有的,它不會被用戶的程序直接引用,則沒有必要公開其“聲明”。為了加強信息隱藏,這些私有的頭文件可以和定義文件存放于同一個目錄。

論壇徽章:
0
3 [報告]
發(fā)表于 2008-04-18 23:22 |只看該作者
第2章 程序的版式
       版式雖然不會影響程序的功能,但會影響可讀性。程序的版式追求清晰、美觀,是程序風(fēng)格的重要構(gòu)成因素。

可以把程序的版式比喻為“書法”。好的“書法”可讓人對程序一目了然,看得興致勃勃。差的程序“書法”如螃蟹爬行,讓人看得索然無味,更令維護(hù)者煩惱有加。請程序員們學(xué)習(xí)程序的“書法”,彌補大學(xué)計算機(jī)教育的漏洞,實在很有必要。

2.1 空行
空行起著分隔程序段落的作用?招械皿w(不過多也不過少)將使程序的布局更加清晰?招胁粫速M內(nèi)存,雖然打印含有空行的程序是會多消耗一些紙張,但是值得。所以不要舍不得用空行。



l         【規(guī)則2-1-1】在每個類聲明之后、每個函數(shù)定義結(jié)束之后都要加空行。參見示例2-1(a)

l         【規(guī)則2-1-2】在一個函數(shù)體內(nèi),邏揖上密切相關(guān)的語句之間不加空行,其它地方應(yīng)加空行分隔。參見示例2-1(b )



// 空行

void Function1(…)

{

  …

}

// 空行

void Function2(…)

{

  …

}

// 空行

void Function3(…)

{

  …

}


// 空行

while (condition)

{

  statement1;

  // 空行

  if (condition)

  {

     statement2;

  }

  else

  {

     statement3;

  }

// 空行

  statement4;

}  


示例2-1(a) 函數(shù)之間的空行                   示例2-1(b) 函數(shù)內(nèi)部的空行



2.2 代碼行
l         【規(guī)則2-2-1】一行代碼只做一件事情,如只定義一個變量,或只寫一條語句。這樣的代碼容易閱讀,并且方便于寫注釋。

l         【規(guī)則2-2-2】if、for、while、do等語句自占一行,執(zhí)行語句不得緊跟其后。不論執(zhí)行語句有多少都要加{}。這樣可以防止書寫失誤。



示例2-2(a)為風(fēng)格良好的代碼行,示例2-2(b)為風(fēng)格不良的代碼行。





int width;    // 寬度

int height;   // 高度

int depth;    // 深度
  

int width, height, depth; // 寬度高度深度



x = a + b;

y = c + d;

z = e + f;
X = a + b;   y = c + d;  z = e + f;



if (width < height)

{

dosomething();

}
if (width < height) dosomething();

for (initialization; condition; update)

{

dosomething();

}

// 空行

other();


for (initialization; condition; update)

     dosomething();

other();






示例2-2(a) 風(fēng)格良好的代碼行                 示例2-2(b) 風(fēng)格不良的代碼行



&sup2;        【建議2-2-1】盡可能在定義變量的同時初始化該變量(就近原則)

如果變量的引用處和其定義處相隔比較遠(yuǎn),變量的初始化很容易被忘記。如果引用了未被初始化的變量,可能會導(dǎo)致程序錯誤。本建議可以減少隱患。例如

int width = 10;     // 定義并初紿化width

int height = 10; // 定義并初紿化height

int depth = 10;     // 定義并初紿化depth



2.3 代碼行內(nèi)的空格
l         【規(guī)則2-3-1】關(guān)鍵字之后要留空格。象const、virtual、inline、case 等關(guān)鍵字之后至少要留一個空格,否則無法辨析關(guān)鍵字。象if、for、while等關(guān)鍵字之后應(yīng)留一個空格再跟左括號‘(’,以突出關(guān)鍵字。

l         【規(guī)則2-3-2】函數(shù)名之后不要留空格,緊跟左括號‘(’,以與關(guān)鍵字區(qū)別。

l         【規(guī)則2-3-3】‘(’向后緊跟,‘)’、‘,’、‘;’向前緊跟,緊跟處不留空格。

l         【規(guī)則2-3-4】‘,’之后要留空格,如Function(x, y, z)。如果‘;’不是一行的結(jié)束符號,其后要留空格,如for (initialization; condition; update)。

l         【規(guī)則2-3-5】賦值操作符、比較操作符、算術(shù)操作符、邏輯操作符、位域操作符,如“=”、“+=” “>=”、“<=”、“+”、“*”、“%”、“&&”、“||”、“<<”,“^”等二元操作符的前后應(yīng)當(dāng)加空格。

l         【規(guī)則2-3-6】一元操作符如“!”、“~”、“++”、“--”、“&”(地址運算符)等前后不加空格。

l         【規(guī)則2-3-7】象“[]”、“.”、“->”這類操作符前后不加空格。

&sup2;        【建議2-3-1】對于表達(dá)式比較長的for語句和if語句,為了緊湊起見可以適當(dāng)?shù)厝サ粢恍┛崭瘢鏵or (i=0; i<10; i++)和if ((a<=b) && (c<=d))



void Func1(int x, int y, int z);          // 良好的風(fēng)格

void Func1 (int x,int y,int z);           // 不良的風(fēng)格

if (year >= 2000)                         // 良好的風(fēng)格

if(year>=2000)                            // 不良的風(fēng)格

if ((a>=b) && (c<=d))                     // 良好的風(fēng)格

if(a>=b&&c<=d)                            // 不良的風(fēng)格

for (i=0; i<10; i++)                      // 良好的風(fēng)格

for(i=0;i<10;i++)                         // 不良的風(fēng)格

for (i = 0; I < 10; i ++)                 // 過多的空格

x = a < b ? a : b;                        // 良好的風(fēng)格

x=a<b?a:b;                                // 不好的風(fēng)格

int *x = &y;                              // 良好的風(fēng)格  

int * x = & y;                            // 不良的風(fēng)格  

array[5] = 0;                             // 不要寫成 array [ 5 ] = 0;

a.Function();                             // 不要寫成 a . Function();

b->Function();                            // 不要寫成 b -> Function();




示例2-3 代碼行內(nèi)的空格



2.4 對齊
l         【規(guī)則2-4-1】程序的分界符‘{’和‘}’應(yīng)獨占一行并且位于同一列,同時與引用它們的語句左對齊。

l         【規(guī)則2-4-2】{ }之內(nèi)的代碼塊在‘{’右邊數(shù)格處左對齊。



示例2-4(a)為風(fēng)格良好的對齊,示例2-4(b)為風(fēng)格不良的對齊。





void Function(int x)

{

… // program code

}
  

void Function(int x){

… // program code

}



if (condition)

{

… // program code

}

else

{

… // program code

}
if (condition){

… // program code

}

else {

… // program code

}

for (initialization; condition; update)

{

… // program code

}
for (initialization; condition; update){

… // program code

}

While (condition)

{

… // program code

}
while (condition){

… // program code

}

如果出現(xiàn)嵌套的{},則使用縮進(jìn)對齊,如:

     {

        …

          {

            …

          }

       …

}
  


示例2-4(a) 風(fēng)格良好的對齊                       示例2-4(b) 風(fēng)格不良的對齊



2.5 長行拆分
l         【規(guī)則2-5-1】代碼行最大長度宜控制在70至80個字符以內(nèi)。代碼行不要過長,否則眼睛看不過來,也不便于打印。

l         【規(guī)則2-5-2】長表達(dá)式要在低優(yōu)先級操作符處拆分成新行,操作符放在新行之首(以便突出操作符)。拆分出的新行要進(jìn)行適當(dāng)?shù)目s進(jìn),使排版整齊,語句可讀。



if ((very_longer_variable1 >= very_longer_variable12)

&& (very_longer_variable3 <= very_longer_variable14)

&& (very_longer_variable5 <= very_longer_variable16))

{

    dosomething();

}

virtual CMatrix CMultiplyMatrix (CMatrix leftMatrix,

                                 CMatrix rightMatrix);



for (very_longer_initialization;

     very_longer_condition;

     very_longer_update)

{

    dosomething();

}


示例2-5 長行的拆分

2.6 修飾符的位置
修飾符 * 和 & 應(yīng)該靠近數(shù)據(jù)類型還是該靠近變量名,是個有爭議的活題。

若將修飾符 * 靠近數(shù)據(jù)類型,例如:int*  x; 從語義上講此寫法比較直觀,即x是int 類型的指針。

上述寫法的弊端是容易引起誤解,例如:int*  x, y; 此處y容易被誤解為指針變量。雖然將x和y分行定義可以避免誤解,但并不是人人都愿意這樣做。



l         【規(guī)則2-6-1】應(yīng)當(dāng)將修飾符 * 和 & 緊靠變量名

例如:

char  *name;

    int   *x, y;  // 此處y不會被誤解為指針

2.7 注釋
C語言的注釋符為“/*…*/”。C++語言中,程序塊的注釋常采用“/*…*/”,行注釋一般采用“//…”。注釋通常用于:

(1)版本、版權(quán)聲明;

(2)函數(shù)接口說明;

(3)重要的代碼行或段落提示。

雖然注釋有助于理解代碼,但注意不可過多地使用注釋。參見示例2-6。



l         【規(guī)則2-7-1】注釋是對代碼的“提示”,而不是文檔。程序中的注釋不可喧賓奪主,注釋太多了會讓人眼花繚亂。注釋的花樣要少。

l         【規(guī)則2-7-2】如果代碼本來就是清楚的,則不必加注釋。否則多此一舉,令人厭煩。例如

i++;     // i 加 1,多余的注釋

l         【規(guī)則2-7-3】邊寫代碼邊注釋,修改代碼同時修改相應(yīng)的注釋,以保證注釋與代碼的一致性。不再有用的注釋要刪除。

l         【規(guī)則2-7-4】注釋應(yīng)當(dāng)準(zhǔn)確、易懂,防止注釋有二義性。錯誤的注釋不但無益反而有害。

l         【規(guī)則2-7-5】盡量避免在注釋中使用縮寫,特別是不常用縮寫。

l         【規(guī)則2-7-6】注釋的位置應(yīng)與被描述的代碼相鄰,可以放在代碼的上方或右方,不可放在下方。

l         【規(guī)則2-7-8】當(dāng)代碼比較長,特別是有多重嵌套時,應(yīng)當(dāng)在一些段落的結(jié)束處加注釋,便于閱讀。





/*

* 函數(shù)介紹:

* 輸入?yún)?shù):

* 輸出參數(shù):

* 返回值  :

*/

void Function(float x, float y, float z)

{

  …

}
  

if (…)

{

  …

  while (…)

  {



} // end of while



} // end of if

示例2-6 程序的注釋



2.8 類的版式
類可以將數(shù)據(jù)和函數(shù)封裝在一起,其中函數(shù)表示了類的行為(或稱服務(wù))。類提供關(guān)鍵字public、protected和private,分別用于聲明哪些數(shù)據(jù)和函數(shù)是公有的、受保護(hù)的或者是私有的。這樣可以達(dá)到信息隱藏的目的,即讓類僅僅公開必須要讓外界知道的內(nèi)容,而隱藏其它一切內(nèi)容。我們不可以濫用類的封裝功能,不要把它當(dāng)成火鍋,什么東西都往里扔。

類的版式主要有兩種方式:

(1)將private類型的數(shù)據(jù)寫在前面,而將public類型的函數(shù)寫在后面,如示例8-3(a)。采用這種版式的程序員主張類的設(shè)計“以數(shù)據(jù)為中心”,重點關(guān)注類的內(nèi)部結(jié)構(gòu)。

(2)將public類型的函數(shù)寫在前面,而將private類型的數(shù)據(jù)寫在后面,如示例8.3(b)采用這種版式的程序員主張類的設(shè)計“以行為為中心”,重點關(guān)注的是類應(yīng)該提供什么樣的接口(或服務(wù))。

很多C++教課書受到Biarne Stroustrup第一本著作的影響,不知不覺地采用了“以數(shù)據(jù)為中心”的書寫方式,并不見得有多少道理。

我建議讀者采用“以行為為中心”的書寫方式,即首先考慮類應(yīng)該提供什么樣的函數(shù)。這是很多人的經(jīng)驗——“這樣做不僅讓自己在設(shè)計類時思路清晰,而且方便別人閱讀。因為用戶最關(guān)心的是接口,誰愿意先看到一堆私有數(shù)據(jù)成員!”



class A

{

  private:

int    i, j;

float  x, y;

    …

  public:

void Func1(void);

void Func2(void);



}
class A

{

  public:

void Func1(void);

void Func2(void);



  private:

int    i, j;

float  x, y;

    …

}


示例8.3(a) 以數(shù)據(jù)為中心版式                 示例8.3(b) 以行為為中心的版式

論壇徽章:
0
4 [報告]
發(fā)表于 2008-04-18 23:22 |只看該作者
第3章 命名規(guī)則
比較著名的命名規(guī)則當(dāng)推Microsoft公司的“匈牙利”法,該命名規(guī)則的主要思想是“在變量和函數(shù)名中加入前綴以增進(jìn)人們對程序的理解”。例如所有的字符變量均以ch為前綴,若是指針變量則追加前綴p。如果一個變量由ppch開頭,則表明它是指向字符指針的指針。

“匈牙利”法最大的缺點是煩瑣,例如

int    i,  j,  k;  

float  x,  y,  z;

倘若采用“匈牙利”命名規(guī)則,則應(yīng)當(dāng)寫成

int    iI,  iJ,  ik;  // 前綴 i表示int類型

float  fX,  fY,  fZ;  // 前綴 f表示float類型

如此煩瑣的程序會讓絕大多數(shù)程序員無法忍受。

據(jù)考察,沒有一種命名規(guī)則可以讓所有的程序員贊同,程序設(shè)計教科書一般都不指定命名規(guī)則。命名規(guī)則對軟件產(chǎn)品而言并不是“成敗悠關(guān)”的事,我們不要化太多精力試圖發(fā)明世界上最好的命名規(guī)則,而應(yīng)當(dāng)制定一種令大多數(shù)項目成員滿意的命名規(guī)則,并在項目中貫徹實施。

3.1 共性規(guī)則
       本節(jié)論述的共性規(guī)則是被大多數(shù)程序員采納的,我們應(yīng)當(dāng)在遵循這些共性規(guī)則的前提下,再擴(kuò)充特定的規(guī)則,如3.2節(jié)。



l         【規(guī)則3-1-1】標(biāo)識符應(yīng)當(dāng)直觀且可以拼讀,可望文知意,不必進(jìn)行“解碼”。

標(biāo)識符最好采用英文單詞或其組合,便于記憶和閱讀。切忌使用漢語拼音來命名。程序中的英文單詞一般不會太復(fù)雜,用詞應(yīng)當(dāng)準(zhǔn)確。例如不要把CurrentValue寫成NowValue。



l         【規(guī)則3-1-2】標(biāo)識符的長度應(yīng)當(dāng)符合“min-length && max-information”原則。

幾十年前老ANSI C規(guī)定名字不準(zhǔn)超過6個字符,現(xiàn)今的C++/C不再有此限制。一般來說,長名字能更好地表達(dá)含義,所以函數(shù)名、變量名、類名長達(dá)十幾個字符不足為怪。那么名字是否越長約好?不見得! 例如變量名maxval就比maxValueUntilOverflow好用。單字符的名字也是有用的,常見的如i,j,k,m,n,x,y,z等,它們通?捎米骱瘮(shù)內(nèi)的局部變量。



l         【規(guī)則3-1-3】命名規(guī)則盡量與所采用的操作系統(tǒng)或開發(fā)工具的風(fēng)格保持一致。

例如Windows應(yīng)用程序的標(biāo)識符通常采用“大小寫”混排的方式,如AddChild。而Unix應(yīng)用程序的標(biāo)識符通常采用“小寫加下劃線”的方式,如add_child。別把這兩類風(fēng)格混在一起用。



l         【規(guī)則3-1-4】程序中不要出現(xiàn)僅靠大小寫區(qū)分的相似的標(biāo)識符。

例如:

int  x,  X;      // 變量x 與 X 容易混淆

void foo(int x);    // 函數(shù)foo 與FOO容易混淆

void FOO(float x);



l         【規(guī)則3-1-5】程序中不要出現(xiàn)標(biāo)識符完全相同的局部變量和全局變量,盡管兩者的作用域不同而不會發(fā)生語法錯誤,但會使人誤解。



l         【規(guī)則3-1-6】變量的名字應(yīng)當(dāng)使用“名詞”或者“形容詞+名詞”。

例如:

float  value;

float  oldValue;

float  newValue;



l         【規(guī)則3-1-7】全局函數(shù)的名字應(yīng)當(dāng)使用“動詞”或者“動詞+名詞”(動賓詞組)。類的成員函數(shù)應(yīng)當(dāng)只使用“動詞”,被省略掉的名詞就是對象本身。

例如:

DrawBox();              // 全局函數(shù)

              box->Draw();        // 類的成員函數(shù)



l         【規(guī)則3-1-8】用正確的反義詞組命名具有互斥意義的變量或相反動作的函數(shù)等。

例如:

int      minValue;

int      maxValue;



int      SetValue(…);

int      GetValue(…);



&sup2;        【建議3-1-1】盡量避免名字中出現(xiàn)數(shù)字編號,如Value1,Value2等,除非邏輯上的確需要編號。這是為了防止程序員偷懶,不肯為命名動腦筋而導(dǎo)致產(chǎn)生無意義的名字(因為用數(shù)字編號最省事)。

3.2 簡單的Windows應(yīng)用程序命名規(guī)則
       作者對“匈牙利”命名規(guī)則做了合理的簡化,下述的命名規(guī)則簡單易用,比較適合于Windows應(yīng)用軟件的開發(fā)。



l         【規(guī)則3-2-1】類名和函數(shù)名用大寫字母開頭的單詞組合而成。

例如:

  class Node;              // 類名

  class LeafNode;           // 類名

  void  Draw(void);     // 函數(shù)名

  void  SetValue(int value);  // 函數(shù)名



l         【規(guī)則3-2-2】變量和參數(shù)用小寫字母開頭的單詞組合而成。

例如:

    BOOL flag;

    int  drawMode;



l         【規(guī)則3-2-3】常量全用大寫的字母,用下劃線分割單詞。

例如:

    const int MAX = 100;

    const int MAX_LENGTH = 100;



l         【規(guī)則3-2-4】靜態(tài)變量加前綴s_(表示static)。

例如:

void Init(…)

{

       static int s_initValue;       // 靜態(tài)變量

       …

}



l         【規(guī)則3-2-5】如果不得已需要全局變量,則使全局變量加前綴g_(表示global)。

例如:

int g_howManyPeople;       // 全局變量

int g_howMuchMoney;       // 全局變量



l         【規(guī)則3-2-6】類的數(shù)據(jù)成員加前綴m_(表示member),這樣可以避免數(shù)據(jù)成員與成員函數(shù)的參數(shù)同名。

例如:

    void Object::SetValue(int width, int height)

    {

        m_width = width;

m_height = height;

}



l         【規(guī)則3-2-7】為了防止某一軟件庫中的一些標(biāo)識符和其它軟件庫中的沖突,可以為各種標(biāo)識符加上能反映軟件性質(zhì)的前綴。例如三維圖形標(biāo)準(zhǔn)OpenGL的所有庫函數(shù)均以gl開頭,所有常量(或宏定義)均以GL開頭。

3.3 簡單的Unix應(yīng)用程序命名規(guī)則


第4章 表達(dá)式和基本語句
讀者可能懷疑:連if、for、while、goto、switch這樣簡單的東西也要探討編程風(fēng)格,是不是小題大做?

我真的發(fā)覺很多程序員用隱含錯誤的方式寫表達(dá)式和基本語句,我自己也犯過類似的錯誤。

表達(dá)式和語句都屬于C++/C的短語結(jié)構(gòu)語法。它們看似簡單,但使用時隱患比較多。本章歸納了正確使用表達(dá)式和語句的一些規(guī)則與建議。

4.1 運算符的優(yōu)先級
       C++/C語言的運算符有數(shù)十個,運算符的優(yōu)先級與結(jié)合律如表4-1所示。注意一元運算符 +  -  * 的優(yōu)先級高于對應(yīng)的二元運算符。



優(yōu)先級
運算符
結(jié)合律


























( )  [ ]  ->  .
從左至右

!  ~  ++  --  (類型) sizeof

+  -  *  &
從右至左



*  /  %
從左至右

+  -
從左至右

<<  >>
從左至右

<   <=   >  >=
從左至右

==  !=
從左至右

&
從左至右

^
從左至右

|
從左至右

&&
從左至右

||
從右至左

?:
從右至左

=  +=  -=  *=  /=  %=  &=  ^=

|=  <<=  >>=
從左至右


表4-1 運算符的優(yōu)先級與結(jié)合律



l         【規(guī)則4-1-1】如果代碼行中的運算符比較多,用括號確定表達(dá)式的操作順序,避免使用默認(rèn)的優(yōu)先級。

由于將表4-1熟記是比較困難的,為了防止產(chǎn)生歧義并提高可讀性,應(yīng)當(dāng)用括號確定表達(dá)式的操作順序。例如:

word = (high << | low

if ((a | b) && (a & c))   

4.2 復(fù)合表達(dá)式
如 a = b = c = 0這樣的表達(dá)式稱為復(fù)合表達(dá)式。允許復(fù)合表達(dá)式存在的理由是:(1)書寫簡潔;(2)可以提高編譯效率。但要防止濫用復(fù)合表達(dá)式。



l         【規(guī)則4-2-1】不要編寫太復(fù)雜的復(fù)合表達(dá)式。

例如:

      i = a >= b && c < d && c + f <= g + h ;   // 復(fù)合表達(dá)式過于復(fù)雜



l         【規(guī)則4-2-2】不要有多用途的復(fù)合表達(dá)式。

例如:

d = (a = b + c) + r ;

該表達(dá)式既求a值又求d值。應(yīng)該拆分為兩個獨立的語句:

a = b + c;

d = a + r;



l         【規(guī)則4-2-3】不要把程序中的復(fù)合表達(dá)式與“真正的數(shù)學(xué)表達(dá)式”混淆。

例如:  

if (a < b < c)            // a < b < c是數(shù)學(xué)表達(dá)式而不是程序表達(dá)式

并不表示      

if ((a<b) && (b<c))

而是成了令人費解的

if ( (a<b)<c )

4.3 if 語句
    if語句是C++/C語言中最簡單、最常用的語句,然而很多程序員用隱含錯誤的方式寫if語句。本節(jié)以“與零值比較”為例,展開討論。



4.3.1 布爾變量與零值比較

l         【規(guī)則4-3-1】不可將布爾變量直接與TRUE、FALSE或者1、0進(jìn)行比較。

根據(jù)布爾類型的語義,零值為“假”(記為FALSE),任何非零值都是“真”(記為TRUE)。TRUE的值究竟是什么并沒有統(tǒng)一的標(biāo)準(zhǔn)。例如Visual C++ 將TRUE定義為1,而Visual Basic則將TRUE定義為-1。

假設(shè)布爾變量名字為flag,它與零值比較的標(biāo)準(zhǔn)if語句如下:

if (flag)    // 表示flag為真

if (!flag)    // 表示flag為假

其它的用法都屬于不良風(fēng)格,例如:

    if (flag == TRUE)   

    if (flag == 1 )     

    if (flag == FALSE)  

    if (flag == 0)      



4.3.2 整型變量與零值比較

l         【規(guī)則4-3-2】應(yīng)當(dāng)將整型變量用“==”或“!=”直接與0比較。

    假設(shè)整型變量的名字為value,它與零值比較的標(biāo)準(zhǔn)if語句如下:

if (value == 0)  

if (value != 0)

不可模仿布爾變量的風(fēng)格而寫成

if (value)    // 會讓人誤解 value是布爾變量

if (!value)



4.3.3 浮點變量與零值比較

l         【規(guī)則4-3-3】不可將浮點變量用“==”或“!=”與任何數(shù)字比較。

    千萬要留意,無論是float還是double類型的變量,都有精度限制。所以一定要避免將浮點變量用“==”或“!=”與數(shù)字比較,應(yīng)該設(shè)法轉(zhuǎn)化成“>=”或“<=”形式。

    假設(shè)浮點變量的名字為x,應(yīng)當(dāng)將   

if (x == 0.0)     // 隱含錯誤的比較

轉(zhuǎn)化為

if ((x>=-EPSINON) && (x<=EPSINON))

其中EPSINON是允許的誤差(即精度)。



4.3.4 指針變量與零值比較

l         【規(guī)則4-3-4】應(yīng)當(dāng)將指針變量用“==”或“!=”與NULL比較。

    指針變量的零值是“空”(記為NULL)。盡管NULL的值與0相同,但是兩者意義不同。假設(shè)指針變量的名字為p,它與零值比較的標(biāo)準(zhǔn)if語句如下:

        if (p == NULL)    // p與NULL顯式比較,強調(diào)p是指針變量

        if (p != NULL)

不要寫成

        if (p == 0)   // 容易讓人誤解p是整型變量

        if (p != 0)   

    或者

if (p)            // 容易讓人誤解p是布爾變量

    if (!p)            



4.3.5 對if語句的補充說明

有時候我們可能會看到 if (NULL == p) 這樣古怪的格式。不是程序?qū)戝e了,是程序員為了防止將 if (p == NULL) 誤寫成 if (p = NULL),而有意把p和NULL顛倒。編譯器認(rèn)為 if (p = NULL) 是合法的,但是會指出 if (NULL = p)是錯誤的,因為NULL不能被賦值。

程序中有時會遇到if/else/return的組合,應(yīng)該將如下不良風(fēng)格的程序

    if (condition)

        return x;

    return y;

改寫為

    if (condition)

    {

        return x;

    }

    else

    {

return y;

}

或者改寫成更加簡練的

return (condition ? x : y);

4.4 循環(huán)語句的效率
    C++/C循環(huán)語句中,for語句使用頻率最高,while語句其次,do語句很少用。本節(jié)重點論述循環(huán)體的效率。提高循環(huán)體效率的基本辦法是降低循環(huán)體的復(fù)雜性。



l         【建議4-4-1】在多重循環(huán)中,如果有可能,應(yīng)當(dāng)將最長的循環(huán)放在最內(nèi)層,最短的循環(huán)放在最外層,以減少CPU跨切循環(huán)層的次數(shù)。例如示例4-4(b)的效率比示例4-4(a)的高。



for (row=0; row<100; row++)

{

for ( col=0; col<5; col++ )

{

sum = sum + a[row][col];

}

}
for (col=0; col<5; col++ )

{

for (row=0; row<100; row++)

{

    sum = sum + a[row][col];

}

}


示例4-4(a) 低效率:長循環(huán)在最外層           示例4-4(b) 高效率:長循環(huán)在最內(nèi)層



l         【建議4-4-2】如果循環(huán)體內(nèi)存在邏輯判斷,并且循環(huán)次數(shù)很大,宜將邏輯判斷移到循環(huán)體的外面。示例4-4(c)的程序比示例4-4(d)多執(zhí)行了N-1次邏輯判斷。并且由于前者老要進(jìn)行邏輯判斷,打斷了循環(huán)“流水線”作業(yè),使得編譯器不能對循環(huán)進(jìn)行優(yōu)化處理,降低了效率。如果N非常大,最好采用示例4-4(d)的寫法,可以提高效率。如果N非常小,兩者效率差別并不明顯,采用示例4-4(c)的寫法比較好,因為程序更加簡潔。



for (i=0; i<N; i++)

{

if (condition)

    DoSomething();

else

    DoOtherthing();

}
if (condition)

{

for (i=0; i<N; i++)

    DoSomething();

}

else

{

    for (i=0; i<N; i++)

    DoOtherthing();

}


表4-4(c) 效率低但程序簡潔                表4-4(d) 效率高但程序不簡潔

4.5 for 語句的循環(huán)控制變量
l         【規(guī)則4-5-1】不可在for 循環(huán)體內(nèi)修改循環(huán)變量,防止for 循環(huán)失去控制。



l         【建議4-5-1】建議for語句的循環(huán)控制變量的取值采用“半開半閉區(qū)間”寫法。

示例4-5(a)中的x值屬于半開半閉區(qū)間“0 =< x < N”,起點到終點的間隔為N,循環(huán)次數(shù)為N。

示例4-5(b)中的x值屬于閉區(qū)間“0 =< x <= N-1”,起點到終點的間隔為N-1,循環(huán)次數(shù)為N。

相比之下,示例4-5(a)的寫法更加直觀,盡管兩者的功能是相同的。



for (int x=0; x<N; x++)

{



}
for (int x=0; x<=N-1; x++)

{



}


示例4-5(a) 循環(huán)變量屬于半開半閉區(qū)間           示例4-5(b) 循環(huán)變量屬于閉區(qū)間

4.6 switch語句
    有了if語句為什么還要switch語句?

switch是多分支選擇語句,而if語句只有兩個分支可供選擇。雖然可以用嵌套的if語句來實現(xiàn)多分支選擇,但那樣的程序冗長難讀。這是switch語句存在的理由。

    switch語句的基本格式是:

switch (variable)

{

case value1 :   …

break;

case value2 :   …

break;

    …

default :    …

break;

}



l         【規(guī)則4-6-1】每個case語句的結(jié)尾不要忘了加break,否則將導(dǎo)致多個分支重疊(除非有意使多個分支重疊)。

l         【規(guī)則4-6-2】不要忘記最后那個default分支。即使程序真的不需要default處理,也應(yīng)該保留語句    default : break; 這樣做并非多此一舉,而是為了防止別人誤以為你忘了default處理。

4.7 goto語句
    自從提倡結(jié)構(gòu)化設(shè)計以來,goto就成了有爭議的語句。首先,由于goto語句可以靈活跳轉(zhuǎn),如果不加限制,它的確會破壞結(jié)構(gòu)化設(shè)計風(fēng)格。其次,goto語句經(jīng)常帶來錯誤或隱患。它可能跳過了某些對象的構(gòu)造、變量的初始化、重要的計算等語句,例如:

goto state;

String s1, s2; // 被goto跳過

int sum = 0;  // 被goto跳過



state:



如果編譯器不能發(fā)覺此類錯誤,每用一次goto語句都可能留下隱患。

    很多人建議廢除C++/C的goto語句,以絕后患。但實事求是地說,錯誤是程序員自己造成的,不是goto的過錯。goto 語句至少有一處可顯神通,它能從多重循環(huán)體中咻地一下子跳到外面,用不著寫很多次的break語句; 例如

  { …

      { …

        { …

            goto error;

        }

      }

  }

  error:

  …

就象樓房著火了,來不及從樓梯一級一級往下走,可從窗口跳出火坑。所以我們主張少用、慎用goto語句,而不是禁用。

論壇徽章:
0
5 [報告]
發(fā)表于 2008-04-18 23:24 |只看該作者
第5章 常量
    常量是一種標(biāo)識符,它的值在運行期間恒定不變。C語言用 #define來定義常量(稱為宏常量)。C++ 語言除了 #define外還可以用const來定義常量(稱為const常量)。

5.1 為什么需要常量
如果不使用常量,直接在程序中填寫數(shù)字或字符串,將會有什么麻煩?

(1)       程序的可讀性(可理解性)變差。程序員自己會忘記那些數(shù)字或字符串是什么意思,用戶則更加不知它們從何處來、表示什么。

(2)       在程序的很多地方輸入同樣的數(shù)字或字符串,難保不發(fā)生書寫錯誤。

(3)       如果要修改數(shù)字或字符串,則會在很多地方改動,既麻煩又容易出錯。



l         【規(guī)則5-1-1】 盡量使用含義直觀的常量來表示那些將在程序中多次出現(xiàn)的數(shù)字或字符串。

例如:

    #define            MAX   100     /*  C語言的宏常量  */

const int          MAX = 100;        //  C++ 語言的const常量

const float     PI = 3.14159;    //  C++ 語言的const常量

5.2 const 與 #define的比較
    C++ 語言可以用const來定義常量,也可以用 #define來定義常量。但是前者比后者有更多的優(yōu)點:

(1)       const常量有數(shù)據(jù)類型,而宏常量沒有數(shù)據(jù)類型。編譯器可以對前者進(jìn)行類型安全檢查。而對后者只進(jìn)行字符替換,沒有類型安全檢查,并且在字符替換可能會產(chǎn)生意料不到的錯誤(邊際效應(yīng))。

(2)       有些集成化的調(diào)試工具可以對const常量進(jìn)行調(diào)試,但是不能對宏常量進(jìn)行調(diào)試。



l         【規(guī)則5-2-1】在C++ 程序中只使用const常量而不使用宏常量,即const常量完全取代宏常量。

5.3 常量定義規(guī)則
l         【規(guī)則5-3-1】需要對外公開的常量放在頭文件中,不需要對外公開的常量放在定義文件的頭部。為便于管理,可以把不同模塊的常量集中存放在一個公共的頭文件中。

l         【規(guī)則5-3-2】如果某一常量與其它常量密切相關(guān),應(yīng)在定義中包含這種關(guān)系,而不應(yīng)給出一些孤立的值。

例如:

const  float   RADIUS = 100;

const  float   DIAMETER = RADIUS * 2;

5.4 類中的常量
有時我們希望某些常量只在類中有效。由于#define定義的宏常量是全局的,不能達(dá)到目的,于是想當(dāng)然地覺得應(yīng)該用const修飾數(shù)據(jù)成員來實現(xiàn)。const數(shù)據(jù)成員的確是存在的,但其含義卻不是我們所期望的。const數(shù)據(jù)成員只在某個對象生存期內(nèi)是常量,而對于整個類而言卻是可變的,因為類可以創(chuàng)建多個對象,不同的對象其const數(shù)據(jù)成員的值可以不同。

    不能在類聲明中初始化const數(shù)據(jù)成員。以下用法是錯誤的,因為類的對象未被創(chuàng)建時,編譯器不知道SIZE的值是什么。

    class A

    {…

        const int SIZE = 100;     // 錯誤,企圖在類聲明中初始化const數(shù)據(jù)成員

        int array[SIZE];        // 錯誤,未知的SIZE

    };



const數(shù)據(jù)成員的初始化只能在類構(gòu)造函數(shù)的初始化表中進(jìn)行,例如

    class A

    {…

        A(int size);      // 構(gòu)造函數(shù)

        const int SIZE ;

    };

    A::A(int size) : SIZE(size)    // 構(gòu)造函數(shù)的初始化表

    {

      …

    }

    A  a(100); // 對象 a 的SIZE值為100

    A  b(200); // 對象 b 的SIZE值為200



    怎樣才能建立在整個類中都恒定的常量呢?別指望const數(shù)據(jù)成員了,應(yīng)該用類中的枚舉常量來實現(xiàn)。例如

    class A

    {…

        enum { SIZE1 = 100, SIZE2 = 200}; // 枚舉常量

        int array1[SIZE1];  

        int array2[SIZE2];

    };

    枚舉常量不會占用對象的存儲空間,它們在編譯時被全部求值。枚舉常量的缺點是:它的隱含數(shù)據(jù)類型是整數(shù),其最大值有限,且不能表示浮點數(shù)(如PI=3.14159)。







第6章 函數(shù)設(shè)計
函數(shù)是C++/C程序的基本功能單元,其重要性不言而喻。函數(shù)設(shè)計的細(xì)微缺點很容易導(dǎo)致該函數(shù)被錯用,所以光使函數(shù)的功能正確是不夠的。本章重點論述函數(shù)的接口設(shè)計和內(nèi)部實現(xiàn)的一些規(guī)則。

函數(shù)接口的兩個要素是參數(shù)和返回值。C語言中,函數(shù)的參數(shù)和返回值的傳遞方式有兩種:值傳遞(pass by value)和指針傳遞(pass by pointer)。C++ 語言中多了引用傳遞(pass by reference)。由于引用傳遞的性質(zhì)象指針傳遞,而使用方式卻象值傳遞,初學(xué)者常常迷惑不解,容易引起混亂,請先閱讀6.6節(jié)“引用與指針的比較”。

6.1 參數(shù)的規(guī)則
l         【規(guī)則6-1-1】參數(shù)的書寫要完整,不要貪圖省事只寫參數(shù)的類型而省略參數(shù)名字。如果函數(shù)沒有參數(shù),則用void填充。

例如:

void SetValue(int width, int height);   // 良好的風(fēng)格

void SetValue(int, int);            // 不良的風(fēng)格

float GetValue(void);    // 良好的風(fēng)格

float GetValue();       // 不良的風(fēng)格



l         【規(guī)則6-1-2】參數(shù)命名要恰當(dāng),順序要合理。

例如編寫字符串拷貝函數(shù)StringCopy,它有兩個參數(shù)。如果把參數(shù)名字起為str1和str2,例如

void StringCopy(char *str1, char *str2);

那么我們很難搞清楚究竟是把str1拷貝到str2中,還是剛好倒過來。

可以把參數(shù)名字起得更有意義,如叫strSource和strDestination。這樣從名字上就可以看出應(yīng)該把strSource拷貝到strDestination。

還有一個問題,這兩個參數(shù)那一個該在前那一個該在后?參數(shù)的順序要遵循程序員的習(xí)慣。一般地,應(yīng)將目的參數(shù)放在前面,源參數(shù)放在后面。

如果將函數(shù)聲明為:

void StringCopy(char *strSource, char *strDestination);

別人在使用時可能會不假思索地寫成如下形式:

char str[20];

StringCopy(str, “Hello World”);   // 參數(shù)順序顛倒



l         【規(guī)則6-1-3】如果參數(shù)是指針,且僅作輸入用,則應(yīng)在類型前加const,以防止該指針在函數(shù)體內(nèi)被意外修改。

例如:

void StringCopy(char *strDestination,const char *strSource);



l         【規(guī)則6-1-4】如果輸入?yún)?shù)以值傳遞的方式傳遞對象,則宜改用“const &”方式來傳遞,這樣可以省去臨時對象的構(gòu)造和析構(gòu)過程,從而提高效率。



&sup2;        【建議6-1-1】避免函數(shù)有太多的參數(shù),參數(shù)個數(shù)盡量控制在5個以內(nèi)。如果參數(shù)太多,在使用時容易將參數(shù)類型或順序搞錯。



&sup2;        【建議6-1-2】盡量不要使用類型和數(shù)目不確定的參數(shù)。

C標(biāo)準(zhǔn)庫函數(shù)printf是采用不確定參數(shù)的典型代表,其原型為:

int printf(const chat *format[, argument]…);

這種風(fēng)格的函數(shù)在編譯時喪失了嚴(yán)格的類型安全檢查。

6.2 返回值的規(guī)則
l         【規(guī)則6-2-1】不要省略返回值的類型。

C語言中,凡不加類型說明的函數(shù),一律自動按整型處理。這樣做不會有什么好處,卻容易被誤解為void類型。

C++語言有很嚴(yán)格的類型安全檢查,不允許上述情況發(fā)生。由于C++程序可以調(diào)用C函數(shù),為了避免混亂,規(guī)定任何C++/ C函數(shù)都必須有類型。如果函數(shù)沒有返回值,那么應(yīng)聲明為void類型。



l         【規(guī)則6-2-2】函數(shù)名字與返回值類型在語義上不可沖突。

違反這條規(guī)則的典型代表是C標(biāo)準(zhǔn)庫函數(shù)getchar。

例如:

char c;

c = getchar();

if (c == EOF)



按照getchar名字的意思,將變量c聲明為char類型是很自然的事情。但不幸的是getchar的確不是char類型,而是int類型,其原型如下:

        int getchar(void);

由于c是char類型,取值范圍是[-128,127],如果宏EOF的值在char的取值范圍之外,那么if語句將總是失敗,這種“危險”人們一般哪里料得到!導(dǎo)致本例錯誤的責(zé)任并不在用戶,是函數(shù)getchar誤導(dǎo)了使用者。



l         【規(guī)則6-2-3】不要將正常值和錯誤標(biāo)志混在一起返回。正常值用輸出參數(shù)獲得,而錯誤標(biāo)志用return語句返回。

回顧上例,C標(biāo)準(zhǔn)庫函數(shù)的設(shè)計者為什么要將getchar聲明為令人迷糊的int類型呢?他會那么傻嗎?

在正常情況下,getchar的確返回單個字符。但如果getchar碰到文件結(jié)束標(biāo)志或發(fā)生讀錯誤,它必須返回一個標(biāo)志EOF。為了區(qū)別于正常的字符,只好將EOF定義為負(fù)數(shù)(通常為負(fù)1)。因此函數(shù)getchar就成了int類型。

我們在實際工作中,經(jīng)常會碰到上述令人為難的問題。為了避免出現(xiàn)誤解,我們應(yīng)該將正常值和錯誤標(biāo)志分開。即:正常值用輸出參數(shù)獲得,而錯誤標(biāo)志用return語句返回。

函數(shù)getchar可以改寫成 BOOL GetChar(char *c);

雖然gechar比GetChar靈活,例如 putchar(getchar()); 但是如果getchar用錯了,它的靈活性又有什么用呢?



&sup2;        【建議6-2-1】有時候函數(shù)原本不需要返回值,但為了增加靈活性如支持鏈?zhǔn)奖磉_(dá),可以附加返回值。

例如字符串拷貝函數(shù)strcpy的原型:

char *strcpy(char *strDest,const char *strSrc);

strcpy函數(shù)將strSrc拷貝至輸出參數(shù)strDest中,同時函數(shù)的返回值又是strDest。這樣做并非多此一舉,可以獲得如下靈活性:

    char str[20];

    int  length = strlen( strcpy(str, “Hello World”) );



&sup2;        【建議6-2-2】如果函數(shù)的返回值是一個對象,有些場合用“引用傳遞”替換“值傳遞”可以提高效率。而有些場合只能用“值傳遞”而不能用“引用傳遞”,否則會出錯。

例如:

class String

{…

    // 賦值函數(shù)

    String & operate=(const String &other);   

// 相加函數(shù),如果沒有friend修飾則只許有一個右側(cè)參數(shù)

friend    String   operate+( const String &s1, const String &s2);

private:

    char *m_data;

}



       String的賦值函數(shù)operate = 的實現(xiàn)如下:

String & String:perate=(const String &other)

{

    if (this == &other)

        return *this;

    delete m_data;

    m_data = new char[strlen(other.data)+1];

    strcpy(m_data, other.data);

    return *this;    // 返回的是 *this的引用,無需拷貝過程

}



對于賦值函數(shù),應(yīng)當(dāng)用“引用傳遞”的方式返回String對象。如果用“值傳遞”的方式,雖然功能仍然正確,但由于return語句要把 *this拷貝到保存返回值的外部存儲單元之中,增加了不必要的開銷,降低了賦值函數(shù)的效率。例如:

  String a,b,c;

  …

  a = b;     // 如果用“值傳遞”,將產(chǎn)生一次 *this 拷貝

  a = b = c;   // 如果用“值傳遞”,將產(chǎn)生兩次 *this 拷貝



       String的相加函數(shù)operate + 的實現(xiàn)如下:

String  operate+(const String &s1, const String &s2)  

{

    String temp;

    delete temp.data;    // temp.data是僅含‘\0’的字符串

        temp.data = new char[strlen(s1.data) + strlen(s2.data) +1];

        strcpy(temp.data, s1.data);

        strcat(temp.data, s2.data);

        return temp;

    }



對于相加函數(shù),應(yīng)當(dāng)用“值傳遞”的方式返回String對象。如果改用“引用傳遞”,那么函數(shù)返回值是一個指向局部對象temp的“引用”。由于temp在函數(shù)結(jié)束時被自動銷毀,將導(dǎo)致返回的“引用”無效。例如:

    c = a + b;

此時 a + b 并不返回期望值,c什么也得不到,流下了隱患。

6.3 函數(shù)內(nèi)部實現(xiàn)的規(guī)則
不同功能的函數(shù)其內(nèi)部實現(xiàn)各不相同,看起來似乎無法就“內(nèi)部實現(xiàn)”達(dá)成一致的觀點。但根據(jù)經(jīng)驗,我們可以在函數(shù)體的“入口處”和“出口處”從嚴(yán)把關(guān),從而提高函數(shù)的質(zhì)量。



l         【規(guī)則6-3-1】在函數(shù)體的“入口處”,對參數(shù)的有效性進(jìn)行檢查。

很多程序錯誤是由非法參數(shù)引起的,我們應(yīng)該充分理解并正確使用“斷言”(assert)來防止此類錯誤。詳見6.5節(jié)“使用斷言”。



l         【規(guī)則6-3-2】在函數(shù)體的“出口處”,對return語句的正確性和效率進(jìn)行檢查。

     如果函數(shù)有返回值,那么函數(shù)的“出口處”是return語句。我們不要輕視return語句。如果return語句寫得不好,函數(shù)要么出錯,要么效率低下。

注意事項如下:

(1)return語句不可返回指向“棧內(nèi)存”的“指針”或者“引用”,因為該內(nèi)存在函數(shù)體結(jié)束時被自動銷毀。例如

    char * Func(void)

    {

        char str[] = “hello world”;    // str的內(nèi)存位于棧上

        …

        return str;     // 將導(dǎo)致錯誤

    }

(2)要搞清楚返回的究竟是“值”、“指針”還是“引用”。

(3)如果函數(shù)返回值是一個對象,要考慮return語句的效率。例如   

              return String(s1 + s2);

這是臨時對象的語法,表示“創(chuàng)建一個臨時對象并返回它”。不要以為它與“先創(chuàng)建一個局部對象temp并返回它的結(jié)果”是等價的,如

String temp(s1 + s2);

return temp;

實質(zhì)不然,上述代碼將發(fā)生三件事。首先,temp對象被創(chuàng)建,同時完成初始化;然后拷貝構(gòu)造函數(shù)把temp拷貝到保存返回值的外部存儲單元中;最后,temp在函數(shù)結(jié)束時被銷毀(調(diào)用析構(gòu)函數(shù))。然而“創(chuàng)建一個臨時對象并返回它”的過程是不同的,編譯器直接把臨時對象創(chuàng)建并初始化在外部存儲單元中,省去了拷貝和析構(gòu)的化費,提高了效率。

類似地,我們不要將  

return int(x + y); // 創(chuàng)建一個臨時變量并返回它

寫成

int temp = x + y;

return temp;

由于內(nèi)部數(shù)據(jù)類型如int,float,double的變量不存在構(gòu)造函數(shù)與析構(gòu)函數(shù),雖然該“臨時變量的語法”不會提高多少效率,但是程序更加簡潔易讀。

6.4 其它建議
&sup2;        【建議6-4-1】函數(shù)的功能要單一,不要設(shè)計多用途的函數(shù)。

&sup2;        【建議6-4-2】函數(shù)體的規(guī)模要小,盡量控制在50行代碼之內(nèi)。

&sup2;        【建議6-4-3】盡量避免函數(shù)帶有“記憶”功能。相同的輸入應(yīng)當(dāng)產(chǎn)生相同的輸出。

帶有“記憶”功能的函數(shù),其行為可能是不可預(yù)測的,因為它的行為可能取決于某種“記憶狀態(tài)”。這樣的函數(shù)既不易理解又不利于測試和維護(hù)。在C/C++語言中,函數(shù)的static局部變量是函數(shù)的“記憶”存儲器。建議盡量少用static局部變量,除非必需。

&sup2;        【建議6-4-4】不僅要檢查輸入?yún)?shù)的有效性,還要檢查通過其它途徑進(jìn)入函數(shù)體內(nèi)的變量的有效性,例如全局變量、文件句柄等。

&sup2;        【建議6-4-5】用于出錯處理的返回值一定要清楚,讓使用者不容易忽視或誤解錯誤情況。

6.5 使用斷言
程序一般分為Debug版本和Release版本,Debug版本用于內(nèi)部調(diào)試,Release版本發(fā)行給用戶使用。

斷言assert是僅在Debug版本起作用的宏,它用于檢查“不應(yīng)該”發(fā)生的情況。示例6-5是一個內(nèi)存復(fù)制函數(shù)。在運行過程中,如果assert的參數(shù)為假,那么程序就會中止(一般地還會出現(xiàn)提示對話,說明在什么地方引發(fā)了assert)。



         void  *memcpy(void *pvTo, const void *pvFrom, size_t size)

{

        assert((pvTo != NULL) && (pvFrom != NULL));     // 使用斷言

        byte *pbTo = (byte *) pvTo;     // 防止改變pvTo的地址

        byte *pbFrom = (byte *) pvFrom; // 防止改變pvFrom的地址

        while(size -- > 0 )

            *pbTo ++ = *pbFrom ++ ;

        return pvTo;

}


示例6-5 復(fù)制不重疊的內(nèi)存塊



assert不是一個倉促拼湊起來的宏。為了不在程序的Debug版本和Release版本引起差別,assert不應(yīng)該產(chǎn)生任何副作用。所以assert不是函數(shù),而是宏。程序員可以把assert看成一個在任何系統(tǒng)狀態(tài)下都可以安全使用的無害測試手段。如果程序在assert處終止了,并不是說含有該assert的函數(shù)有錯誤,而是調(diào)用者出了差錯,assert可以幫助我們找到發(fā)生錯誤的原因。

很少有比跟蹤到程序的斷言,卻不知道該斷言的作用更讓人沮喪的事了。你化了很多時間,不是為了排除錯誤,而只是為了弄清楚這個錯誤到底是什么。有的時候,程序員偶爾還會設(shè)計出有錯誤的斷言。所以如果搞不清楚斷言檢查的是什么,就很難判斷錯誤是出現(xiàn)在程序中,還是出現(xiàn)在斷言中。幸運的是這個問題很好解決,只要加上清晰的注釋即可。這本是顯而易見的事情,可是很少有程序員這樣做。這好比一個人在森林里,看到樹上釘著一塊“危險”的大牌子。但危險到底是什么?樹要倒?有廢井?有野獸?除非告訴人們“危險”是什么,否則這個警告牌難以起到積極有效的作用。難以理解的斷言常常被程序員忽略,甚至被刪除。[Maguire, p8-p30]



l         【規(guī)則6-5-1】使用斷言捕捉不應(yīng)該發(fā)生的非法情況。不要混淆非法情況與錯誤情況之間的區(qū)別,后者是必然存在的并且是一定要作出處理的。

l         【規(guī)則6-5-2】在函數(shù)的入口處,使用斷言檢查參數(shù)的有效性(合法性)。

l         【建議6-5-1】在編寫函數(shù)時,要進(jìn)行反復(fù)的考查,并且自問:“我打算做哪些假定?”一旦確定了的假定,就要使用斷言對假定進(jìn)行檢查。

l         【建議6-5-2】一般教科書都鼓勵程序員們進(jìn)行防錯設(shè)計,但要記住這種編程風(fēng)格可能會隱瞞錯誤。當(dāng)進(jìn)行防錯設(shè)計時,如果“不可能發(fā)生”的事情的確發(fā)生了,則要使用斷言進(jìn)行報警。

6.6 引用與指針的比較
引用是C++中的概念,初學(xué)者容易把引用和指針混淆一起。一下程序中,n是m的一個引用(reference),m是被引用物(referent)。

    int m;

    int &n = m;

n相當(dāng)于m的別名(綽號),對n的任何操作就是對m的操作。例如有人名叫王小毛,他的綽號是“三毛”。說“三毛”怎么怎么的,其實就是對王小毛說三道四。所以n既不是m的拷貝,也不是指向m的指針,其實n就是m它自己。

引用的一些規(guī)則如下:

(1)引用被創(chuàng)建的同時必須被初始化(指針則可以在任何時候被初始化)。

(2)不能有NULL引用,引用必須與合法的存儲單元關(guān)聯(lián)(指針則可以是NULL)。

(3)一旦引用被初始化,就不能改變引用的關(guān)系(指針則可以隨時改變所指的對象)。

    以下示例程序中,k被初始化為i的引用。語句k = j并不能將k修改成為j的引用,只是把k的值改變成為6。由于k是i的引用,所以i的值也變成了6。

    int i = 5;

    int j = 6;

    int &k = i;

    k = j;    // k和i的值都變成了6;

    上面的程序看起來象在玩文字游戲,沒有體現(xiàn)出引用的價值。引用的主要功能是傳遞函數(shù)的參數(shù)和返回值。C++語言中,函數(shù)的參數(shù)和返回值的傳遞方式有三種:值傳遞、指針傳遞和引用傳遞。

    以下是“值傳遞”的示例程序。由于Func1函數(shù)體內(nèi)的x是外部變量n的一份拷貝,改變x的值不會影響n, 所以n的值仍然是0。

    void Func1(int x)

{

    x = x + 10;

}



int n = 0;

    Func1(n);

    cout << “n = ” << n << endl;    // n = 0

   

以下是“指針傳遞”的示例程序。由于Func2函數(shù)體內(nèi)的x是指向外部變量n的指針,改變該指針的內(nèi)容將導(dǎo)致n的值改變,所以n的值成為10。

    void Func2(int *x)

{

    (* x) = (* x) + 10;

}



int n = 0;

    Func2(&n);

    cout << “n = ” << n << endl;        // n = 10



    以下是“引用傳遞”的示例程序。由于Func3函數(shù)體內(nèi)的x是外部變量n的引用,x和n是同一個東西,改變x等于改變n,所以n的值成為10。

    void Func3(int &x)

{

    x = x + 10;

}



int n = 0;

    Func3(n);

    cout << “n = ” << n << endl;      // n = 10



    對比上述三個示例程序,會發(fā)現(xiàn)“引用傳遞”的性質(zhì)象“指針傳遞”,而書寫方式象“值傳遞”。實際上“引用”可以做的任何事情“指針”也都能夠做,為什么還要“引用”這東西?

答案是“用適當(dāng)?shù)墓ぞ咦銮∪缙浞值墓ぷ鳌薄?br />
    指針能夠毫無約束地操作內(nèi)存中的如何東西,盡管指針功能強大,但是非常危險。就象一把刀,它可以用來砍樹、裁紙、修指甲、理發(fā)等等,誰敢這樣用?

如果的確只需要借用一下某個對象的“別名”,那么就用“引用”,而不要用“指針”,以免發(fā)生意外。比如說,某人需要一份證明,本來在文件上蓋上公章的印子就行了,如果把取公章的鑰匙交給他,那么他就獲得了不該有的權(quán)利。

論壇徽章:
0
6 [報告]
發(fā)表于 2008-04-18 23:27 |只看該作者
第7章 內(nèi)存管理
    歡迎進(jìn)入內(nèi)存這片雷區(qū)。偉大的Bill Gates 曾經(jīng)失言:

640K ought to be enough for everybody

— Bill Gates 1981

程序員們經(jīng)常編寫內(nèi)存管理程序,往往提心吊膽。如果不想觸雷,唯一的解決辦法就是發(fā)現(xiàn)所有潛伏的地雷并且排除它們,躲是躲不了的。本章的內(nèi)容比一般教科書的要深入得多,讀者需細(xì)心閱讀,做到真正地通曉內(nèi)存管理。

7.1內(nèi)存分配方式
內(nèi)存分配方式有三種:

(1)       從靜態(tài)存儲區(qū)域分配。內(nèi)存在程序編譯的時候就已經(jīng)分配好,這塊內(nèi)存在程序的整個運行期間都存在。例如全局變量,static變量。

(2)       在棧上創(chuàng)建。在執(zhí)行函數(shù)時,函數(shù)內(nèi)局部變量的存儲單元都可以在棧上創(chuàng)建,函數(shù)執(zhí)行結(jié)束時這些存儲單元自動被釋放。棧內(nèi)存分配運算內(nèi)置于處理器的指令集中,效率很高,但是分配的內(nèi)存容量有限。

(3)       從堆上分配,亦稱動態(tài)內(nèi)存分配。程序在運行的時候用malloc或new申請任意多少的內(nèi)存,程序員自己負(fù)責(zé)在何時用free或delete釋放內(nèi)存。動態(tài)內(nèi)存的生存期由我們決定,使用非常靈活,但問題也最多。

7.2常見的內(nèi)存錯誤及其對策
       發(fā)生內(nèi)存錯誤是件非常麻煩的事情。編譯器不能自動發(fā)現(xiàn)這些錯誤,通常是在程序運行時才能捕捉到。而這些錯誤大多沒有明顯的癥狀,時隱時現(xiàn),增加了改錯的難度。有時用戶怒氣沖沖地把你找來,程序卻沒有發(fā)生任何問題,你一走,錯誤又發(fā)作了。

常見的內(nèi)存錯誤及其對策如下:

u       內(nèi)存分配未成功,卻使用了它。

編程新手常犯這種錯誤,因為他們沒有意識到內(nèi)存分配會不成功。常用解決辦法是,在使用內(nèi)存之前檢查指針是否為NULL。如果指針p是函數(shù)的參數(shù),那么在函數(shù)的入口處用assert(p!=NULL)進(jìn)行檢查。如果是用malloc或new來申請內(nèi)存,應(yīng)該用if(p==NULL) 或if(p!=NULL)進(jìn)行防錯處理。



u       內(nèi)存分配雖然成功,但是尚未初始化就引用它。

犯這種錯誤主要有兩個起因:一是沒有初始化的觀念;二是誤以為內(nèi)存的缺省初值全為零,導(dǎo)致引用初值錯誤(例如數(shù)組)。

內(nèi)存的缺省初值究竟是什么并沒有統(tǒng)一的標(biāo)準(zhǔn),盡管有些時候為零值,我們寧可信其無不可信其有。所以無論用何種方式創(chuàng)建數(shù)組,都別忘了賦初值,即便是賦零值也不可省略,不要嫌麻煩。



u       內(nèi)存分配成功并且已經(jīng)初始化,但操作越過了內(nèi)存的邊界。

例如在使用數(shù)組時經(jīng)常發(fā)生下標(biāo)“多1”或者“少1”的操作。特別是在for循環(huán)語句中,循環(huán)次數(shù)很容易搞錯,導(dǎo)致數(shù)組操作越界。



u       忘記了釋放內(nèi)存,造成內(nèi)存泄露。

含有這種錯誤的函數(shù)每被調(diào)用一次就丟失一塊內(nèi)存。剛開始時系統(tǒng)的內(nèi)存充足,你看不到錯誤。終有一次程序突然死掉,系統(tǒng)出現(xiàn)提示:內(nèi)存耗盡。

動態(tài)內(nèi)存的申請與釋放必須配對,程序中malloc與free的使用次數(shù)一定要相同,否則肯定有錯誤(new/delete同理)。



u       釋放了內(nèi)存卻繼續(xù)使用它。

有三種情況:

(1)程序中的對象調(diào)用關(guān)系過于復(fù)雜,實在難以搞清楚某個對象究竟是否已經(jīng)釋放了內(nèi)存,此時應(yīng)該重新設(shè)計數(shù)據(jù)結(jié)構(gòu),從根本上解決對象管理的混亂局面。

(2)函數(shù)的return語句寫錯了,注意不要返回指向“棧內(nèi)存”的“指針”或者“引用”,因為該內(nèi)存在函數(shù)體結(jié)束時被自動銷毀。

(3)使用free或delete釋放了內(nèi)存后,沒有將指針設(shè)置為NULL。導(dǎo)致產(chǎn)生“野指針”。



l         【規(guī)則7-2-1】用malloc或new申請內(nèi)存之后,應(yīng)該立即檢查指針值是否為NULL。防止使用指針值為NULL的內(nèi)存。

l         【規(guī)則7-2-2】不要忘記為數(shù)組和動態(tài)內(nèi)存賦初值。防止將未被初始化的內(nèi)存作為右值使用。

l         【規(guī)則7-2-3】避免數(shù)組或指針的下標(biāo)越界,特別要當(dāng)心發(fā)生“多1”或者“少1”操作。

l         【規(guī)則7-2-4】動態(tài)內(nèi)存的申請與釋放必須配對,防止內(nèi)存泄漏。

l         【規(guī)則7-2-5】用free或delete釋放了內(nèi)存之后,立即將指針設(shè)置為NULL,防止產(chǎn)生“野指針”。

7.3指針與數(shù)組的對比
       C++/C程序中,指針和數(shù)組在不少地方可以相互替換著用,讓人產(chǎn)生一種錯覺,以為兩者是等價的。

       數(shù)組要么在靜態(tài)存儲區(qū)被創(chuàng)建(如全局?jǐn)?shù)組),要么在棧上被創(chuàng)建。數(shù)組名對應(yīng)著(而不是指向)一塊內(nèi)存,其地址與容量在生命期內(nèi)保持不變,只有數(shù)組的內(nèi)容可以改變。

指針可以隨時指向任意類型的內(nèi)存塊,它的特征是“可變”,所以我們常用指針來操作動態(tài)內(nèi)存。指針遠(yuǎn)比數(shù)組靈活,但也更危險。

下面以字符串為例比較指針與數(shù)組的特性。



7.3.1 修改內(nèi)容

       示例7-3-1中,字符數(shù)組a的容量是6個字符,其內(nèi)容為hello\0。a的內(nèi)容可以改變,如a[0]= ‘X’。指針p指向常量字符串“world”(位于靜態(tài)存儲區(qū),內(nèi)容為world\0),常量字符串的內(nèi)容是不可以被修改的。從語法上看,編譯器并不覺得語句p[0]= ‘X’有什么不妥,但是該語句企圖修改常量字符串的內(nèi)容而導(dǎo)致運行錯誤。



char a[] = “hello”;

a[0] = ‘X’;

cout << a << endl;

char *p = “world”;     // 注意p指向常量字符串

p[0] = ‘X’;             // 編譯器不能發(fā)現(xiàn)該錯誤

cout << p << endl;


示例7-3-1 修改數(shù)組和指針的內(nèi)容



7.3.2 內(nèi)容復(fù)制與比較

    不能對數(shù)組名進(jìn)行直接復(fù)制與比較。示例7-3-2中,若想把數(shù)組a的內(nèi)容復(fù)制給數(shù)組b,不能用語句 b = a ,否則將產(chǎn)生編譯錯誤。應(yīng)該用標(biāo)準(zhǔn)庫函數(shù)strcpy進(jìn)行復(fù)制。同理,比較b和a的內(nèi)容是否相同,不能用if(b==a) 來判斷,應(yīng)該用標(biāo)準(zhǔn)庫函數(shù)strcmp進(jìn)行比較。

    語句p = a 并不能把a的內(nèi)容復(fù)制指針p,而是把a的地址賦給了p。要想復(fù)制a的內(nèi)容,可以先用庫函數(shù)malloc為p申請一塊容量為strlen(a)+1個字符的內(nèi)存,再用strcpy進(jìn)行字符串復(fù)制。同理,語句if(p==a) 比較的不是內(nèi)容而是地址,應(yīng)該用庫函數(shù)strcmp來比較。



    // 數(shù)組…

    char a[] = "hello";

    char b[10];

    strcpy(b, a);           // 不能用   b = a;

    if(strcmp(b, a) == 0)   // 不能用  if (b == a)



    // 指針…

    int len = strlen(a);

    char *p = (char *)malloc(sizeof(char)*(len+1));

    strcpy(p,a);            // 不要用 p = a;

    if(strcmp(p, a) == 0)   // 不要用 if (p == a)




示例7-3-2 數(shù)組和指針的內(nèi)容復(fù)制與比較





7.3.3 計算內(nèi)存容量

    用運算符sizeof可以計算出數(shù)組的容量(字節(jié)數(shù))。示例7-3-3(a)中,sizeof(a)的值是12(注意別忘了’\0’)。指針p指向a,但是sizeof(p)的值卻是4。這是因為sizeof(p)得到的是一個指針變量的字節(jié)數(shù),相當(dāng)于sizeof(char*),而不是p所指的內(nèi)存容量。C++/C語言沒有辦法知道指針?biāo)傅膬?nèi)存容量,除非在申請內(nèi)存時記住它。

注意當(dāng)數(shù)組作為函數(shù)的參數(shù)進(jìn)行傳遞時,該數(shù)組自動退化為同類型的指針。示例7-3-3(b)中,不論數(shù)組a的容量是多少,sizeof(a)始終等于sizeof(char *)。



    char a[] = "hello world";

    char *p  = a;

    cout<< sizeof(a) << endl;   // 12字節(jié)

    cout<< sizeof(p) << endl;   // 4字節(jié)


示例7-3-3(a) 計算數(shù)組和指針的內(nèi)存容量

      

    void Func(char a[100])

    {

        cout<< sizeof(a) << endl;   // 4字節(jié)而不是100字節(jié)

}


示例7-3-3(b) 數(shù)組退化為指針

7.4指針參數(shù)是如何傳遞內(nèi)存的?
       如果函數(shù)的參數(shù)是一個指針,不要指望用該指針去申請動態(tài)內(nèi)存。示例7-4-1中,Test函數(shù)的語句GetMemory(str, 200)并沒有使str獲得期望的內(nèi)存,str依舊是NULL,為什么?



void GetMemory(char *p, int num)

{

    p = (char *)malloc(sizeof(char) * num);

}

void Test(void)

{

    char *str = NULL;

    GetMemory(str, 100);    // str 仍然為 NULL  

    strcpy(str, "hello");   // 運行錯誤

}


示例7-4-1 試圖用指針參數(shù)申請動態(tài)內(nèi)存



毛病出在函數(shù)GetMemory中。編譯器總是要為函數(shù)的每個參數(shù)制作臨時副本,指針參數(shù)p的副本是 _p,編譯器使 _p = p。如果函數(shù)體內(nèi)的程序修改了_p的內(nèi)容,就導(dǎo)致參數(shù)p的內(nèi)容作相應(yīng)的修改。這就是指針可以用作輸出參數(shù)的原因。在本例中,_p申請了新的內(nèi)存,只是把_p所指的內(nèi)存地址改變了,但是p絲毫未變。所以函數(shù)GetMemory并不能輸出任何東西。事實上,每執(zhí)行一次GetMemory就會泄露一塊內(nèi)存,因為沒有用free釋放內(nèi)存。

如果非得要用指針參數(shù)去申請內(nèi)存,那么應(yīng)該改用“指向指針的指針”,見示例7-4-2。



void GetMemory2(char **p, int num)

{

    *p = (char *)malloc(sizeof(char) * num);

}

void Test2(void)

{

    char *str = NULL;

    GetMemory2(&str, 100);  // 注意參數(shù)是 &str,而不是str

    strcpy(str, "hello");   

    cout<< str << endl;

    free(str);  

}


示例7-4-2用指向指針的指針申請動態(tài)內(nèi)存



由于“指向指針的指針”這個概念不容易理解,我們可以用函數(shù)返回值來傳遞動態(tài)內(nèi)存。這種方法更加簡單,見示例7-4-3。



char *GetMemory3(int num)

{

    char *p = (char *)malloc(sizeof(char) * num);

    return p;

}

void Test3(void)

{

    char *str = NULL;

    str = GetMemory3(100);  

    strcpy(str, "hello");

    cout<< str << endl;

    free(str);  

}


示例7-4-3 用函數(shù)返回值來傳遞動態(tài)內(nèi)存



用函數(shù)返回值來傳遞動態(tài)內(nèi)存這種方法雖然好用,但是常常有人把return語句用錯了。這里強調(diào)不要用return語句返回指向“棧內(nèi)存”的指針,因為該內(nèi)存在函數(shù)結(jié)束時自動消亡,見示例7-4-4。



char *GetString(void)

{

    char p[] = "hello world";

    return p;   // 編譯器將提出警告

}

void Test4(void)

{

char *str = NULL;

str = GetString();  // str 的內(nèi)容是垃圾

cout<< str << endl;

}


示例7-4-4 return語句返回指向“棧內(nèi)存”的指針



用調(diào)試器逐步跟蹤Test4,發(fā)現(xiàn)執(zhí)行str = GetString語句后str不再是NULL指針,但是str的內(nèi)容不是“hello world”而是垃圾。

如果把示例7-4-4改寫成示例7-4-5,會怎么樣?



char *GetString2(void)

{

    char *p = "hello world";

    return p;

}

void Test5(void)

{

    char *str = NULL;

    str = GetString2();

    cout<< str << endl;

}


示例7-4-5 return語句返回常量字符串



函數(shù)Test5運行雖然不會出錯,但是函數(shù)GetString2的設(shè)計概念卻是錯誤的。因為GetString2內(nèi)的“hello world”是常量字符串,位于靜態(tài)存儲區(qū),它在程序生命期內(nèi)恒定不變。無論什么時候調(diào)用GetString2,它返回的始終是同一個“只讀”的內(nèi)存塊。



7.5 free和delete把指針怎么啦?
別看free和delete的名字惡狠狠的(尤其是delete),它們只是把指針?biāo)傅膬?nèi)存給釋放掉,但并沒有把指針本身干掉。

用調(diào)試器跟蹤示例7-5,發(fā)現(xiàn)指針p被free以后其地址仍然不變(非NULL),只是該地址對應(yīng)的內(nèi)存是垃圾,p成了“野指針”。如果此時不把p設(shè)置為NULL,會讓人誤以為p是個合法的指針。

如果程序比較長,我們有時記不住p所指的內(nèi)存是否已經(jīng)被釋放,在繼續(xù)使用p之前,通常會用語句if (p != NULL)進(jìn)行防錯處理。很遺憾,此時if語句起不到防錯作用,因為即便p不是NULL指針,它也不指向合法的內(nèi)存塊。



    char *p = (char *) malloc(100);

    strcpy(p, “hello”);

    free(p);        // p 所指的內(nèi)存被釋放,但是p所指的地址仍然不變

    …

    if(p != NULL)   // 沒有起到防錯作用

    {

       strcpy(p, “world”);  // 出錯

}


示例7-5  p成為野指針

7.6 動態(tài)內(nèi)存會被自動釋放嗎?
       函數(shù)體內(nèi)的局部變量在函數(shù)結(jié)束時自動消亡。很多人誤以為示例7-6是正確的。理由是p是局部的指針變量,它消亡的時候會讓它所指的動態(tài)內(nèi)存一起完蛋。這是錯覺!



    void Func(void)

{

    char *p = (char *) malloc(100); // 動態(tài)內(nèi)存會自動釋放嗎?

}


示例7-6 試圖讓動態(tài)內(nèi)存自動釋放



    我們發(fā)現(xiàn)指針有一些“似是而非”的特征:

(1)指針消亡了,并不表示它所指的內(nèi)存會被自動釋放。

(2)內(nèi)存被釋放了,并不表示指針會消亡或者成了NULL指針。

這表明釋放內(nèi)存并不是一件可以草率對待的事。也許有人不服氣,一定要找出可以草率行事的理由:

    如果程序終止了運行,一切指針都會消亡,動態(tài)內(nèi)存會被操作系統(tǒng)回收。既然如此,在程序臨終前,就可以不必釋放內(nèi)存、不必將指針設(shè)置為NULL了。終于可以偷懶而不會發(fā)生錯誤了吧%

論壇徽章:
0
7 [報告]
發(fā)表于 2008-04-18 23:29 |只看該作者
第8章 C++函數(shù)的高級特性
對比于C語言的函數(shù),C++增加了重載(overloaded)、內(nèi)聯(lián)(inline)、const和virtual四種新機(jī)制。其中重載和內(nèi)聯(lián)機(jī)制既可用于全局函數(shù)也可用于類的成員函數(shù),const與virtual機(jī)制僅用于類的成員函數(shù)。

       重載和內(nèi)聯(lián)肯定有其好處才會被C++語言采納,但是不可以當(dāng)成免費的午餐而濫用。本章將探究重載和內(nèi)聯(lián)的優(yōu)點與局限性,說明什么情況下應(yīng)該采用、不該采用以及要警惕錯用。

8.1 函數(shù)重載的概念
8.1.1 重載的起源

    自然語言中,一個詞可以有許多不同的含義,即該詞被重載了。人們可以通過上下文來判斷該詞到底是哪種含義!霸~的重載”可以使語言更加簡練。例如“吃飯”的含義十分廣泛,人們沒有必要每次非得說清楚具體吃什么不可。別迂腐得象孔已己,說茴香豆的茴字有四種寫法。

    在C++程序中,可以將語義、功能相似的幾個函數(shù)用同一個名字表示,即函數(shù)重載。這樣便于記憶,提高了函數(shù)的易用性,這是C++語言采用重載機(jī)制的一個理由。例如示例8-1-1中的函數(shù)EatBeef,EatFish,EatChicken可以用同一個函數(shù)名Eat表示,用不同類型的參數(shù)加以區(qū)別。





void EatBeef(…);       // 可以改為     void Eat(Beef …);

void EatFish(…);       // 可以改為     void Eat(Fish …);

void EatChicken(…);    // 可以改為     void Eat(Chicken …);




示例8-1-1 重載函數(shù)Eat



    C++語言采用重載機(jī)制的另一個理由是:類的構(gòu)造函數(shù)需要重載機(jī)制。因為C++規(guī)定構(gòu)造函數(shù)與類同名(請參見第9章),構(gòu)造函數(shù)只能有一個名字。如果想用幾種不同的方法創(chuàng)建對象該怎么辦?別無選擇,只能用重載機(jī)制來實現(xiàn)。所以類可以有多個同名的構(gòu)造函數(shù)。



8.1.2 重載是如何實現(xiàn)的?

    幾個同名的重載函數(shù)仍然是不同的函數(shù),它們是如何區(qū)分的呢?我們自然想到函數(shù)接口的兩個要素:參數(shù)與返回值。

如果同名函數(shù)的參數(shù)不同(包括類型、順序不同),那么容易區(qū)別出它們是不同的函數(shù)。

如果同名函數(shù)僅僅是返回值類型不同,有時可以區(qū)分,有時卻不能。例如:

void Function(void);

int  Function (void);

上述兩個函數(shù),第一個沒有返回值,第二個的返回值是int類型。如果這樣調(diào)用函數(shù):

    int  x = Function ();

則可以判斷出Function是第二個函數(shù)。問題是在C++/C程序中,我們可以忽略函數(shù)的返回值。在這種情況下,編譯器和程序員都不知道哪個Function函數(shù)被調(diào)用。

    所以只能靠參數(shù)而不能靠返回值類型的不同來區(qū)分重載函數(shù)。編譯器根據(jù)參數(shù)為每個重載函數(shù)產(chǎn)生不同的內(nèi)部標(biāo)識符。例如編譯器為示例8-1-1中的三個Eat函數(shù)產(chǎn)生象_eat_beef、_eat_fish、_eat_chicken之類的內(nèi)部標(biāo)識符(不同的編譯器可能產(chǎn)生不同風(fēng)格的內(nèi)部標(biāo)識符)。



如果C++程序要調(diào)用已經(jīng)被編譯后的C函數(shù),該怎么辦?

假設(shè)某個C函數(shù)的聲明如下:

void foo(int x, int y);

該函數(shù)被C編譯器編譯后在庫中的名字為_foo,而C++編譯器則會產(chǎn)生像_foo_int_int之類的名字用來支持函數(shù)重載和類型安全連接。由于編譯后的名字不同,C++程序不能直接調(diào)用C函數(shù)。C++提供了一個C連接交換指定符號extern“C”來解決這個問題。例如:

extern “C”

{

   void foo(int x, int y);

   … // 其它函數(shù)

}

或者寫成

extern “C”

{

   #include “myheader.h”

   … // 其它C頭文件

}

這就告訴C++編譯譯器,函數(shù)foo是個C連接,應(yīng)該到庫中找名字_foo而不是找_foo_int_int。C++編譯器開發(fā)商已經(jīng)對C標(biāo)準(zhǔn)庫的頭文件作了extern“C”處理,所以我們可以用#include 直接引用這些頭文件。



    注意并不是兩個函數(shù)的名字相同就能構(gòu)成重載。全局函數(shù)和類的成員函數(shù)同名不算重載,因為函數(shù)的作用域不同。例如:

    void Print(…);     // 全局函數(shù)

    class A

    {…

        void Print(…);    // 成員函數(shù)

    }

    不論兩個Print函數(shù)的參數(shù)是否不同,如果類的某個成員函數(shù)要調(diào)用全局函數(shù)Print,為了與成員函數(shù)Print區(qū)別,全局函數(shù)被調(diào)用時應(yīng)加‘::’標(biāo)志。如

    :rint(…);    // 表示Print是全局函數(shù)而非成員函數(shù)



8.1.3 當(dāng)心隱式類型轉(zhuǎn)換導(dǎo)致重載函數(shù)產(chǎn)生二義性

    示例8-1-3中,第一個output函數(shù)的參數(shù)是int類型,第二個output函數(shù)的參數(shù)是float類型。由于數(shù)字本身沒有類型,將數(shù)字當(dāng)作參數(shù)時將自動進(jìn)行類型轉(zhuǎn)換(稱為隱式類型轉(zhuǎn)換)。語句output(0.5)將產(chǎn)生編譯錯誤,因為編譯器不知道該將0.5轉(zhuǎn)換成int還是float類型的參數(shù)。隱式類型轉(zhuǎn)換在很多地方可以簡化程序的書寫,但是也可能留下隱患。



# include <iostream.h>

void output( int x);    // 函數(shù)聲明

void output( float x);  // 函數(shù)聲明



void output( int x)

{

    cout << " output int " << x << endl ;

}



void output( float x)

{

    cout << " output float " << x << endl ;

}



void main(void)

{

    int   x = 1;

    float y = 1.0;

    output(x);          // output int 1

    output(y);          // output float 1

    output(1);          // output int 1

//  output(0.5);        // error! ambiguous call, 因為自動類型轉(zhuǎn)換

    output(int(0.5));   // output int 0

    output(float(0.5)); // output float 0.5

}


示例8-1-3 隱式類型轉(zhuǎn)換導(dǎo)致重載函數(shù)產(chǎn)生二義性



8.2 成員函數(shù)的重載、覆蓋與隱藏
    成員函數(shù)的重載、覆蓋(override)與隱藏很容易混淆,C++程序員必須要搞清楚概念,否則錯誤將防不勝防。



8.2.1 重載與覆蓋

    成員函數(shù)被重載的特征:

(1)相同的范圍(在同一個類中);

(2)函數(shù)名字相同;

(3)參數(shù)不同;

(4)virtual關(guān)鍵字可有可無。

    覆蓋是指派生類函數(shù)覆蓋基類函數(shù),特征是:

(1)不同的范圍(分別位于派生類與基類);

(2)函數(shù)名字相同;

(3)參數(shù)相同;

(4)基類函數(shù)必須有virtual關(guān)鍵字。

    示例8-2-1中,函數(shù)Base::f(int)與Base::f(float)相互重載,而Base::g(void)被Derived::g(void)覆蓋。



#include <iostream.h>

    class Base

{

public:

              void f(int x){ cout << "Base::f(int) " << x << endl; }

void f(float x){ cout << "Base::f(float) " << x << endl; }

      virtual void g(void){ cout << "Base::g(void)" << endl;}

};



    class Derived : public Base

{

public:

      virtual void g(void){ cout << "Derived::g(void)" << endl;}

};



    void main(void)

    {

      Derived  d;

      Base *pb = &d;

      pb->f(42);        // Base::f(int) 42

      pb->f(3.14f);     // Base::f(float) 3.14

      pb->g();          // Derived::g(void)

}


示例8-2-1成員函數(shù)的重載和覆蓋

   

8.2.2 令人迷惑的隱藏規(guī)則

    本來僅僅區(qū)別重載與覆蓋并不算困難,但是C++的隱藏規(guī)則使問題復(fù)雜性陡然增加。這里“隱藏”是指派生類的函數(shù)屏蔽了與其同名的基類函數(shù),規(guī)則如下:

(1)如果派生類的函數(shù)與基類的函數(shù)同名,但是參數(shù)不同。此時,不論有無virtual關(guān)鍵字,基類的函數(shù)將被隱藏(注意別與重載混淆)。

(2)如果派生類的函數(shù)與基類的函數(shù)同名,并且參數(shù)也相同,但是基類函數(shù)沒有virtual關(guān)鍵字。此時,基類的函數(shù)被隱藏(注意別與覆蓋混淆)。

    示例程序8-2-2(a)中:

(1)函數(shù)Derived::f(float)覆蓋了Base::f(float)。

(2)函數(shù)Derived::g(int)隱藏了Base::g(float),而不是重載。

(3)函數(shù)Derived::h(float)隱藏了Base::h(float),而不是覆蓋。



#include <iostream.h>

    class Base

{

public:

    virtual void f(float x){ cout << "Base::f(float) " << x << endl; }

void g(float x){ cout << "Base::g(float) " << x << endl; }

            void h(float x){ cout << "Base::h(float) " << x << endl; }

};

    class Derived : public Base

{

public:

    virtual void f(float x){ cout << "Derived::f(float) " << x << endl; }

void g(int x){ cout << "Derived::g(int) " << x << endl; }

            void h(float x){ cout << "Derived::h(float) " << x << endl; }

};


示例8-2-2(a)成員函數(shù)的重載、覆蓋和隱藏



    據(jù)作者考察,很多C++程序員沒有意識到有“隱藏”這回事。由于認(rèn)識不夠深刻,“隱藏”的發(fā)生可謂神出鬼沒,常常產(chǎn)生令人迷惑的結(jié)果。

示例8-2-2(b)中,bp和dp指向同一地址,按理說運行結(jié)果應(yīng)該是相同的,可事實并非這樣。



void main(void)

{

Derived  d;

Base *pb = &d;

Derived *pd = &d;

// Good : behavior depends solely on type of the object

pb->f(3.14f); // Derived::f(float) 3.14

pd->f(3.14f); // Derived::f(float) 3.14



// Bad : behavior depends on type of the pointer

pb->g(3.14f); // Base::g(float) 3.14

pd->g(3.14f); // Derived::g(int) 3        (surprise!)



// Bad : behavior depends on type of the pointer

pb->h(3.14f); // Base::h(float) 3.14      (surprise!)

pd->h(3.14f); // Derived::h(float) 3.14

}


示例8-2-2(b) 重載、覆蓋和隱藏的比較

8.2.3 擺脫隱藏

    隱藏規(guī)則引起了不少麻煩。示例8-2-3程序中,語句pd->f(10)的本意是想調(diào)用函數(shù)Base::f(int),但是Base::f(int)不幸被Derived::f(char *)隱藏了。由于數(shù)字10不能被隱式地轉(zhuǎn)化為字符串,所以在編譯時出錯。



class Base

{

public:

void f(int x);

};

class Derived : public Base

{

public:

void f(char *str);

};

void Test(void)

{

Derived *pd = new Derived;

pd->f(10);    // error

}


示例8-2-3 由于隱藏而導(dǎo)致錯誤



    從示例8-2-3看來,隱藏規(guī)則似乎很愚蠢。但是隱藏規(guī)則至少有兩個存在的理由:

u       寫語句pd->f(10)的人可能真的想調(diào)用Derived::f(char *)函數(shù),只是他誤將參數(shù)寫錯了。有了隱藏規(guī)則,編譯器就可以明確指出錯誤,這未必不是好事。否則,編譯器會靜悄悄地將錯就錯,程序員將很難發(fā)現(xiàn)這個錯誤,流下禍根。

u       假如類Derived有多個基類(多重繼承),有時搞不清楚哪些基類定義了函數(shù)f。如果沒有隱藏規(guī)則,那么pd->f(10)可能會調(diào)用一個出乎意料的基類函數(shù)f。盡管隱藏規(guī)則看起來不怎么有道理,但它的確能消滅這些意外。



示例8-2-3中,如果語句pd->f(10)一定要調(diào)用函數(shù)Base::f(int),那么將類Derived修改為如下即可。

class Derived : public Base

{

public:

void f(char *str);

void f(int x) { Base::f(x); }

};

8.3 參數(shù)的缺省值
有一些參數(shù)的值在每次函數(shù)調(diào)用時都相同,書寫這樣的語句會使人厭煩。C++語言采用參數(shù)的缺省值使書寫變得簡潔(在編譯時,缺省值由編譯器自動插入)。

    參數(shù)缺省值的使用規(guī)則:

l         【規(guī)則8-3-1】參數(shù)缺省值只能出現(xiàn)在函數(shù)的聲明中,而不能出現(xiàn)在定義體中。

例如:

    void Foo(int x=0, int y=0);    // 正確,缺省值出現(xiàn)在函數(shù)的聲明中



    void Foo(int x=0, int y=0)        // 錯誤,缺省值出現(xiàn)在函數(shù)的定義體中

    {



    }

為什么會這樣?我想是有兩個原因:一是函數(shù)的實現(xiàn)(定義)本來就與參數(shù)是否有缺省值無關(guān),所以沒有必要讓缺省值出現(xiàn)在函數(shù)的定義體中。二是參數(shù)的缺省值可能會改動,顯然修改函數(shù)的聲明比修改函數(shù)的定義要方便。



l         【規(guī)則8-3-2】如果函數(shù)有多個參數(shù),參數(shù)只能從后向前挨個兒缺省,否則將導(dǎo)致函數(shù)調(diào)用語句怪模怪樣。

正確的示例如下:

void Foo(int x, int y=0, int z=0);

錯誤的示例如下:

void Foo(int x=0, int y, int z=0);   



要注意,使用參數(shù)的缺省值并沒有賦予函數(shù)新的功能,僅僅是使書寫變得簡潔一些。它可能會提高函數(shù)的易用性,但是也可能會降低函數(shù)的可理解性。所以我們只能適當(dāng)?shù)厥褂脜?shù)的缺省值,要防止使用不當(dāng)產(chǎn)生負(fù)面效果。示例8-3-2中,不合理地使用參數(shù)的缺省值將導(dǎo)致重載函數(shù)output產(chǎn)生二義性。



#include <iostream.h>

void output( int x);

void output( int x, float y=0.0);



void output( int x)

{

    cout << " output int " << x << endl ;

}



void output( int x, float y)

{

    cout << " output int " << x << " and float " << y << endl ;

}



void main(void)

{

    int x=1;

    float y=0.5;

//  output(x);          // error! ambiguous call

    output(x,y);        // output int 1 and float 0.5

}




示例8-3-2  參數(shù)的缺省值將導(dǎo)致重載函數(shù)產(chǎn)生二義性

8.4 運算符重載
8.4.1 概念

    在C++語言中,可以用關(guān)鍵字operator加上運算符來表示函數(shù),叫做運算符重載。例如兩個復(fù)數(shù)相加函數(shù):

    Complex Add(const Complex &a, const Complex &b);

可以用運算符重載來表示:

    Complex operator +(const Complex &a, const Complex &b);

    運算符與普通函數(shù)在調(diào)用時的不同之處是:對于普通函數(shù),參數(shù)出現(xiàn)在圓括號內(nèi);而對于運算符,參數(shù)出現(xiàn)在其左、右側(cè)。例如

   Complex a, b, c;

    …

    c = Add(a, b); // 用普通函數(shù)

    c = a + b;        // 用運算符 +

    如果運算符被重載為全局函數(shù),那么只有一個參數(shù)的運算符叫做一元運算符,有兩個參數(shù)的運算符叫做二元運算符。

    如果運算符被重載為類的成員函數(shù),那么一元運算符沒有參數(shù),二元運算符只有一個右側(cè)參數(shù),因為對象自己成了左側(cè)參數(shù)。

    從語法上講,運算符既可以定義為全局函數(shù),也可以定義為成員函數(shù)。文獻(xiàn)[Murray , p44-p47]對此問題作了較多的闡述,并總結(jié)了表8-4-1的規(guī)則。



運算符
規(guī)則

所有的一元運算符
建議重載為成員函數(shù)

= () [] ->
只能重載為成員函數(shù)

+= -= /= *= &= |= ~= %= >>= <<=
建議重載為成員函數(shù)

所有其它運算符
建議重載為全局函數(shù)


表8-4-1 運算符的重載規(guī)則



由于C++語言支持函數(shù)重載,才能將運算符當(dāng)成函數(shù)來用,C語言就不行。我們要以平常心來對待運算符重載:

(1)不要過分擔(dān)心自己不會用,它的本質(zhì)仍然是程序員們熟悉的函數(shù)。

(2)不要過分熱心地使用,如果它不能使代碼變得更加易讀易寫,那就別用,否則會自找麻煩。



8.4.2 不能被重載的運算符

    在C++運算符集合中,有一些運算符是不允許被重載的。這種限制是出于安全方面的考慮,可防止錯誤和混亂。

(1)不能改變C++內(nèi)部數(shù)據(jù)類型(如int,float等)的運算符。

(2)不能重載‘.’,因為‘.’在類中對任何成員都有意義,已經(jīng)成為標(biāo)準(zhǔn)用法。

(3)不能重載目前C++運算符集合中沒有的符號,如#,@,$等。原因有兩點,一是難以理解,二是難以確定優(yōu)先級。

(4)對已經(jīng)存在的運算符進(jìn)行重載時,不能改變優(yōu)先級規(guī)則,否則將引起混亂。

8.5 函數(shù)內(nèi)聯(lián)
8.5.1 用內(nèi)聯(lián)取代宏代碼

    C++ 語言支持函數(shù)內(nèi)聯(lián),其目的是為了提高函數(shù)的執(zhí)行效率(速度)。

    在C程序中,可以用宏代碼提高執(zhí)行效率。宏代碼本身不是函數(shù),但使用起來象函數(shù)。預(yù)處理器用復(fù)制宏代碼的方式代替函數(shù)調(diào)用,省去了參數(shù)壓棧、生成匯編語言的CALL調(diào)用、返回參數(shù)、執(zhí)行return等過程,從而提高了速度。使用宏代碼最大的缺點是容易出錯,預(yù)處理器在復(fù)制宏代碼時常常產(chǎn)生意想不到的邊際效應(yīng)。例如

    #define MAX(a, b)       (a) > (b) ? (a) : (b)

語句

result = MAX(i, j) + 2 ;

將被預(yù)處理器解釋為

    result = (i) > (j) ? (i) : (j) + 2 ;

由于運算符‘+’比運算符‘:’的優(yōu)先級高,所以上述語句并不等價于期望的

    result = ( (i) > (j) ? (i) : (j) ) + 2 ;

如果把宏代碼改寫為

    #define MAX(a, b)       ( (a) > (b) ? (a) : (b) )

則可以解決由優(yōu)先級引起的錯誤。但是即使使用修改后的宏代碼也不是萬無一失的,例如語句   

result = MAX(i++, j);

將被預(yù)處理器解釋為

    result = (i++) > (j) ? (i++) : (j);

    對于C++ 而言,使用宏代碼還有另一種缺點:無法操作類的私有數(shù)據(jù)成員。



讓我們看看C++ 的“函數(shù)內(nèi)聯(lián)”是如何工作的。對于任何內(nèi)聯(lián)函數(shù),編譯器在符號表里放入函數(shù)的聲明(包括名字、參數(shù)類型、返回值類型)。如果編譯器沒有發(fā)現(xiàn)內(nèi)聯(lián)函數(shù)存在錯誤,那么該函數(shù)的代碼也被放入符號表里。在調(diào)用一個內(nèi)聯(lián)函數(shù)時,編譯器首先檢查調(diào)用是否正確(進(jìn)行類型安全檢查,或者進(jìn)行自動類型轉(zhuǎn)換,當(dāng)然對所有的函數(shù)都一樣)。如果正確,內(nèi)聯(lián)函數(shù)的代碼就會直接替換函數(shù)調(diào)用,于是省去了函數(shù)調(diào)用的開銷。這個過程與預(yù)處理有顯著的不同,因為預(yù)處理器不能進(jìn)行類型安全檢查,或者進(jìn)行自動類型轉(zhuǎn)換。假如內(nèi)聯(lián)函數(shù)是成員函數(shù),對象的地址(this)會被放在合適的地方,這也是預(yù)處理器辦不到的。

C++ 語言的函數(shù)內(nèi)聯(lián)機(jī)制既具備宏代碼的效率,又增加了安全性,而且可以自由操作類的數(shù)據(jù)成員。所以在C++ 程序中,應(yīng)該用內(nèi)聯(lián)函數(shù)取代所有宏代碼,“斷言assert”恐怕是唯一的例外。assert是僅在Debug版本起作用的宏,它用于檢查“不應(yīng)該”發(fā)生的情況。為了不在程序的Debug版本和Release版本引起差別,assert不應(yīng)該產(chǎn)生任何副作用。如果assert是函數(shù),由于函數(shù)調(diào)用會引起內(nèi)存、代碼的變動,那么將導(dǎo)致Debug版本與Release版本存在差異。所以assert不是函數(shù),而是宏。(參見6.5節(jié)“使用斷言”)



8.5.2 內(nèi)聯(lián)函數(shù)的編程風(fēng)格

    關(guān)鍵字inline必須與函數(shù)定義體放在一起才能使函數(shù)成為內(nèi)聯(lián),僅將inline放在函數(shù)聲明前面不起任何作用。如下風(fēng)格的函數(shù)Foo不能成為內(nèi)聯(lián)函數(shù):

    inline void Foo(int x, int y);     // inline僅與函數(shù)聲明放在一起

    void Foo(int x, int y)

    {

        …

    }

而如下風(fēng)格的函數(shù)Foo則成為內(nèi)聯(lián)函數(shù):

    void Foo(int x, int y);     

    inline void Foo(int x, int y)  // inline與函數(shù)定義體放在一起

    {

        …

    }

    所以說,inline是一種“用于實現(xiàn)的關(guān)鍵字”,而不是一種“用于聲明的關(guān)鍵字”。一般地,用戶可以閱讀函數(shù)的聲明,但是看不到函數(shù)的定義。盡管在大多數(shù)教科書中內(nèi)聯(lián)函數(shù)的聲明、定義體前面都加了inline關(guān)鍵字,但我認(rèn)為inline不應(yīng)該出現(xiàn)在函數(shù)的聲明中。這個細(xì)節(jié)雖然不會影響函數(shù)的功能,但是體現(xiàn)了高質(zhì)量C++/C程序設(shè)計風(fēng)格的一個基本原則:聲明與定義不可混為一談,用戶沒有必要、也不應(yīng)該知道函數(shù)是否需要內(nèi)聯(lián)。

    定義在類聲明之中的成員函數(shù)將自動地成為內(nèi)聯(lián)函數(shù),例如

    class A

    {

public:

        void Foo(int x, int y) { … }     // 自動地成為內(nèi)聯(lián)函數(shù)

    }

將成員函數(shù)的定義體放在類聲明之中雖然能帶來書寫上的方便,但不是一種良好的編程風(fēng)格,上例應(yīng)該改成:

    // 頭文件

class A

    {

public:

        void Foo(int x, int y);

    }

    // 定義文件

    inline void A::Foo(int x, int y)

{



}



8.5.3 慎用內(nèi)聯(lián)

    內(nèi)聯(lián)能提高函數(shù)的執(zhí)行效率,為什么不把所有的函數(shù)都定義成內(nèi)聯(lián)函數(shù)?

    如果所有的函數(shù)都是內(nèi)聯(lián)函數(shù),還用得著“內(nèi)聯(lián)”這個關(guān)鍵字嗎?

    內(nèi)聯(lián)是以代碼膨脹(復(fù)制)為代價,僅僅省去了函數(shù)調(diào)用的開銷,從而提高函數(shù)的執(zhí)行效率。如果執(zhí)行函數(shù)體內(nèi)代碼的時間,相比于函數(shù)調(diào)用的開銷較大,那么效率的收獲會很少。另一方面,每一處內(nèi)聯(lián)函數(shù)的調(diào)用都要復(fù)制代碼,將使程序的總代碼量增大,消耗更多的內(nèi)存空間。以下情況不宜使用內(nèi)聯(lián):

(1)如果函數(shù)體內(nèi)的代碼比較長,使用內(nèi)聯(lián)將導(dǎo)致內(nèi)存消耗代價較高。

(2)如果函數(shù)體內(nèi)出現(xiàn)循環(huán),那么執(zhí)行函數(shù)體內(nèi)代碼的時間要比函數(shù)調(diào)用的開銷大。

    類的構(gòu)造函數(shù)和析構(gòu)函數(shù)容易讓人誤解成使用內(nèi)聯(lián)更有效。要當(dāng)心構(gòu)造函數(shù)和析構(gòu)函數(shù)可能會隱藏一些行為,如“偷偷地”執(zhí)行了基類或成員對象的構(gòu)造函數(shù)和析構(gòu)函數(shù)。所以不要隨便地將構(gòu)造函數(shù)和析構(gòu)函數(shù)的定義體放在類聲明中。

一個好的編譯器將會根據(jù)函數(shù)的定義體,自動地取消不值得的內(nèi)聯(lián)(這進(jìn)一步說明了inline不應(yīng)該出現(xiàn)在函數(shù)的聲明中)。

8.6 一些心得體會
    C++ 語言中的重載、內(nèi)聯(lián)、缺省參數(shù)、隱式轉(zhuǎn)換等機(jī)制展現(xiàn)了很多優(yōu)點,但是這些優(yōu)點的背后都隱藏著一些隱患。正如人們的飲食,少食和暴食都不可取,應(yīng)當(dāng)恰到好處。我們要辨證地看待C++的新機(jī)制,應(yīng)該恰如其分地使用它們。雖然這會使我們編程時多費一些心思,少了一些痛快,但這才是編程的藝術(shù)。

論壇徽章:
0
8 [報告]
發(fā)表于 2008-04-18 23:30 |只看該作者
第9章 類的構(gòu)造函數(shù)、析構(gòu)函數(shù)與賦值函數(shù)
構(gòu)造函數(shù)、析構(gòu)函數(shù)與賦值函數(shù)是每個類最基本的函數(shù)。它們太普通以致讓人容易麻痹大意,其實這些貌似簡單的函數(shù)就象沒有頂蓋的下水道那樣危險。

       每個類只有一個析構(gòu)函數(shù)和一個賦值函數(shù),但可以有多個構(gòu)造函數(shù)(包含一個拷貝構(gòu)造函數(shù),其它的稱為普通構(gòu)造函數(shù))。對于任意一個類A,如果不想編寫上述函數(shù),C++編譯器將自動為A產(chǎn)生四個缺省的函數(shù),如

    A(void);                    // 缺省的無參數(shù)構(gòu)造函數(shù)

    A(const A &a);                // 缺省的拷貝構(gòu)造函數(shù)

    ~A(void);                    // 缺省的析構(gòu)函數(shù)

    A & operate =(const A &a);    // 缺省的賦值函數(shù)



這不禁讓人疑惑,既然能自動生成函數(shù),為什么還要程序員編寫?

原因如下:

(1)如果使用“缺省的無參數(shù)構(gòu)造函數(shù)”和“缺省的析構(gòu)函數(shù)”,等于放棄了自主“初始化”和“清除”的機(jī)會,C++發(fā)明人Stroustrup的好心好意白費了。

(2)“缺省的拷貝構(gòu)造函數(shù)”和“缺省的賦值函數(shù)”均采用“位拷貝”而非“值拷貝”的方式來實現(xiàn),倘若類中含有指針變量,這兩個函數(shù)注定將出錯。

      

對于那些沒有吃夠苦頭的C++程序員,如果他說編寫構(gòu)造函數(shù)、析構(gòu)函數(shù)與賦值函數(shù)很容易,可以不用動腦筋,表明他的認(rèn)識還比較膚淺,水平有待于提高。

本章以類String的設(shè)計與實現(xiàn)為例,深入闡述被很多教科書忽視了的道理。String的結(jié)構(gòu)如下:

    class String

    {

      public:

        String(const char *str = NULL);    // 普通構(gòu)造函數(shù)

        String(const String &other);    // 拷貝構(gòu)造函數(shù)

        ~ String(void);                    // 析構(gòu)函數(shù)

        String & operate =(const String &other);    // 賦值函數(shù)

      private:

        char      *m_data;                // 用于保存字符串

    };

9.1 構(gòu)造函數(shù)與析構(gòu)函數(shù)的起源
       作為比C更先進(jìn)的語言,C++提供了更好的機(jī)制來增強程序的安全性。C++編譯器具有嚴(yán)格的類型安全檢查功能,它幾乎能找出程序中所有的語法問題,這的確幫了程序員的大忙。但是程序通過了編譯檢查并不表示錯誤已經(jīng)不存在了,在“錯誤”的大家庭里,“語法錯誤”的地位只能算是小弟弟。級別高的錯誤通常隱藏得很深,就象狡猾的罪犯,想逮住他可不容易。

       根據(jù)經(jīng)驗,不少難以察覺的程序錯誤是由于變量沒有被正確初始化或清除造成的,而初始化和清除工作很容易被人遺忘。Stroustrup在設(shè)計C++語言時充分考慮了這個問題并很好地予以解決:把對象的初始化工作放在構(gòu)造函數(shù)中,把清除工作放在析構(gòu)函數(shù)中。當(dāng)對象被創(chuàng)建時,構(gòu)造函數(shù)被自動執(zhí)行。當(dāng)對象消亡時,析構(gòu)函數(shù)被自動執(zhí)行。這下就不用擔(dān)心忘了對象的初始化和清除工作。

       構(gòu)造函數(shù)與析構(gòu)函數(shù)的名字不能隨便起,必須讓編譯器認(rèn)得出才可以被自動執(zhí)行。Stroustrup的命名方法既簡單又合理:讓構(gòu)造函數(shù)、析構(gòu)函數(shù)與類同名,由于析構(gòu)函數(shù)的目的與構(gòu)造函數(shù)的相反,就加前綴‘~’以示區(qū)別。

除了名字外,構(gòu)造函數(shù)與析構(gòu)函數(shù)的另一個特別之處是沒有返回值類型,這與返回值類型為void的函數(shù)不同。構(gòu)造函數(shù)與析構(gòu)函數(shù)的使命非常明確,就象出生與死亡,光溜溜地來光溜溜地去。如果它們有返回值類型,那么編譯器將不知所措。為了防止節(jié)外生枝,干脆規(guī)定沒有返回值類型。(以上典故參考了文獻(xiàn)[Eekel, p55-p56])

9.2 構(gòu)造函數(shù)的初始化表
       構(gòu)造函數(shù)有個特殊的初始化方式叫“初始化表達(dá)式表”(簡稱初始化表)。初始化表位于函數(shù)參數(shù)表之后,卻在函數(shù)體 {} 之前。這說明該表里的初始化工作發(fā)生在函數(shù)體內(nèi)的任何代碼被執(zhí)行之前。

       構(gòu)造函數(shù)初始化表的使用規(guī)則:

u       如果類存在繼承關(guān)系,派生類必須在其初始化表里調(diào)用基類的構(gòu)造函數(shù)。

例如

    class A

    {…

        A(int x);        // A的構(gòu)造函數(shù)

};  

    class B : public A

    {…

        B(int x, int y);// B的構(gòu)造函數(shù)

    };

    B::B(int x, int y)

     : A(x)             // 在初始化表里調(diào)用A的構(gòu)造函數(shù)

    {

      …

}   

u       類的const常量只能在初始化表里被初始化,因為它不能在函數(shù)體內(nèi)用賦值的方式來初始化(參見5.4節(jié))。

u       類的數(shù)據(jù)成員的初始化可以采用初始化表或函數(shù)體內(nèi)賦值兩種方式,這兩種方式的效率不完全相同。

    非內(nèi)部數(shù)據(jù)類型的成員對象應(yīng)當(dāng)采用第一種方式初始化,以獲取更高的效率。例如

    class A

{…

    A(void);                // 無參數(shù)構(gòu)造函數(shù)

    A(const A &other);        // 拷貝構(gòu)造函數(shù)

    A & operate =( const A &other);    // 賦值函數(shù)

};



    class B

    {

      public:

        B(const A &a);    // B的構(gòu)造函數(shù)

      private:   

        A  m_a;            // 成員對象

};



示例9-2(a)中,類B的構(gòu)造函數(shù)在其初始化表里調(diào)用了類A的拷貝構(gòu)造函數(shù),從而將成員對象m_a初始化。

示例9-2 (b)中,類B的構(gòu)造函數(shù)在函數(shù)體內(nèi)用賦值的方式將成員對象m_a初始化。我們看到的只是一條賦值語句,但實際上B的構(gòu)造函數(shù)干了兩件事:先暗地里創(chuàng)建m_a對象(調(diào)用了A的無參數(shù)構(gòu)造函數(shù)),再調(diào)用類A的賦值函數(shù),將參數(shù)a賦給m_a。



B::B(const A &a)

: m_a(a)           

{

   …

}
B::B(const A &a)

{

m_a = a;



}


示例9-2(a) 成員對象在初始化表中被初始化      示例9-2(b) 成員對象在函數(shù)體內(nèi)被初始化



對于內(nèi)部數(shù)據(jù)類型的數(shù)據(jù)成員而言,兩種初始化方式的效率幾乎沒有區(qū)別,但后者的程序版式似乎更清晰些。若類F的聲明如下:

class F

{

  public:

    F(int x, int y);        // 構(gòu)造函數(shù)

  private:

    int m_x, m_y;

    int m_i, m_j;

}

示例9-2(c)中F的構(gòu)造函數(shù)采用了第一種初始化方式,示例9-2(d)中F的構(gòu)造函數(shù)采用了第二種初始化方式。



F::F(int x, int y)

: m_x(x), m_y(y)           

{

   m_i = 0;

   m_j = 0;

}
F::F(int x, int y)

{

   m_x = x;

   m_y = y;

   m_i = 0;

   m_j = 0;

}


示例9-2(c) 數(shù)據(jù)成員在初始化表中被初始化     示例9-2(d) 數(shù)據(jù)成員在函數(shù)體內(nèi)被初始化

9.3 構(gòu)造和析構(gòu)的次序
       構(gòu)造從類層次的最根處開始,在每一層中,首先調(diào)用基類的構(gòu)造函數(shù),然后調(diào)用成員對象的構(gòu)造函數(shù)。析構(gòu)則嚴(yán)格按照與構(gòu)造相反的次序執(zhí)行,該次序是唯一的,否則編譯器將無法自動執(zhí)行析構(gòu)過程。

一個有趣的現(xiàn)象是,成員對象初始化的次序完全不受它們在初始化表中次序的影響,只由成員對象在類中聲明的次序決定。這是因為類的聲明是唯一的,而類的構(gòu)造函數(shù)可以有多個,因此會有多個不同次序的初始化表。如果成員對象按照初始化表的次序進(jìn)行構(gòu)造,這將導(dǎo)致析構(gòu)函數(shù)無法得到唯一的逆序。[Eckel, p260-261]

9.4 示例:類String的構(gòu)造函數(shù)與析構(gòu)函數(shù)
       // String的普通構(gòu)造函數(shù)

       String::String(const char *str)

{

    if(str==NULL)

    {

        m_data = new char[1];

        *m_data = ‘\0’;

    }   

    else

    {

        int length = strlen(str);

        m_data = new char[length+1];

        strcpy(m_data, str);

    }

}   



// String的析構(gòu)函數(shù)

       String::~String(void)

{

    delete [] m_data;

// 由于m_data是內(nèi)部數(shù)據(jù)類型,也可以寫成 delete m_data;

       }

9.5 不要輕視拷貝構(gòu)造函數(shù)與賦值函數(shù)
       由于并非所有的對象都會使用拷貝構(gòu)造函數(shù)和賦值函數(shù),程序員可能對這兩個函數(shù)有些輕視。請先記住以下的警告,在閱讀正文時就會多心:

u       本章開頭講過,如果不主動編寫拷貝構(gòu)造函數(shù)和賦值函數(shù),編譯器將以“位拷貝”的方式自動生成缺省的函數(shù)。倘若類中含有指針變量,那么這兩個缺省的函數(shù)就隱含了錯誤。以類String的兩個對象a,b為例,假設(shè)a.m_data的內(nèi)容為“hello”,b.m_data的內(nèi)容為“world”。

現(xiàn)將a賦給b,缺省賦值函數(shù)的“位拷貝”意味著執(zhí)行b.m_data = a.m_data。這將造成三個錯誤:一是b.m_data原有的內(nèi)存沒被釋放,造成內(nèi)存泄露;二是b.m_data和a.m_data指向同一塊內(nèi)存,a或b任何一方變動都會影響另一方;三是在對象被析構(gòu)時,m_data被釋放了兩次。



u       拷貝構(gòu)造函數(shù)和賦值函數(shù)非常容易混淆,常導(dǎo)致錯寫、錯用?截悩(gòu)造函數(shù)是在對象被創(chuàng)建時調(diào)用的,而賦值函數(shù)只能被已經(jīng)存在了的對象調(diào)用。以下程序中,第三個語句和第四個語句很相似,你分得清楚哪個調(diào)用了拷貝構(gòu)造函數(shù),哪個調(diào)用了賦值函數(shù)嗎?

String  a(“hello”);

String  b(“world”);

String  c = a;    // 調(diào)用了拷貝構(gòu)造函數(shù),最好寫成 c(a);

c = b; // 調(diào)用了賦值函數(shù)

本例中第三個語句的風(fēng)格較差,宜改寫成String c(a) 以區(qū)別于第四個語句。

9.6 示例:類String的拷貝構(gòu)造函數(shù)與賦值函數(shù)
    // 拷貝構(gòu)造函數(shù)

    String::String(const String &other)

    {   

// 允許操作other的私有成員m_data

    int length = strlen(other.m_data);   

    m_data = new char[length+1];

    strcpy(m_data, other.m_data);

}



// 賦值函數(shù)

    String & String:perate =(const String &other)

    {   

       // (1) 檢查自賦值

        if(this == &other)

            return *this;

        

       // (2) 釋放原有的內(nèi)存資源

        delete [] m_data;

        

        // (3)分配新的內(nèi)存資源,并復(fù)制內(nèi)容

    int length = strlen(other.m_data);   

    m_data = new char[length+1];

        strcpy(m_data, other.m_data);

        

        // (4)返回本對象的引用

        return *this;

}   

   

    類String拷貝構(gòu)造函數(shù)與普通構(gòu)造函數(shù)(參見9.4節(jié))的區(qū)別是:在函數(shù)入口處無需與NULL進(jìn)行比較,這是因為“引用”不可能是NULL,而“指針”可以為NULL。

    類String的賦值函數(shù)比構(gòu)造函數(shù)復(fù)雜得多,分四步實現(xiàn):

(1)第一步,檢查自賦值。你可能會認(rèn)為多此一舉,難道有人會愚蠢到寫出 a = a 這樣的自賦值語句!的確不會。但是間接的自賦值仍有可能出現(xiàn),例如

   

// 內(nèi)容自賦值

b = a;



c = b;



a = c;  
// 地址自賦值

b = &a;



a = *b;




也許有人會說:“即使出現(xiàn)自賦值,我也可以不理睬,大不了化點時間讓對象復(fù)制自己而已,反正不會出錯!”

他真的說錯了?纯吹诙降膁elete,自殺后還能復(fù)制自己嗎?所以,如果發(fā)現(xiàn)自賦值,應(yīng)該馬上終止函數(shù)。注意不要將檢查自賦值的if語句

if(this == &other)

錯寫成為

    if( *this == other)

(2)第二步,用delete釋放原有的內(nèi)存資源。如果現(xiàn)在不釋放,以后就沒機(jī)會了,將造成內(nèi)存泄露。

(3)第三步,分配新的內(nèi)存資源,并復(fù)制字符串。注意函數(shù)strlen返回的是有效字符串長度,不包含結(jié)束符‘\0’。函數(shù)strcpy則連‘\0’一起復(fù)制。

(4)第四步,返回本對象的引用,目的是為了實現(xiàn)象 a = b = c 這樣的鏈?zhǔn)奖磉_(dá)。注意不要將 return *this 錯寫成 return this 。那么能否寫成return other 呢?效果不是一樣嗎?

不可以!因為我們不知道參數(shù)other的生命期。有可能other是個臨時對象,在賦值結(jié)束后它馬上消失,那么return other返回的將是垃圾。

9.7 偷懶的辦法處理拷貝構(gòu)造函數(shù)與賦值函數(shù)
       如果我們實在不想編寫拷貝構(gòu)造函數(shù)和賦值函數(shù),又不允許別人使用編譯器生成的缺省函數(shù),怎么辦?

       偷懶的辦法是:只需將拷貝構(gòu)造函數(shù)和賦值函數(shù)聲明為私有函數(shù),不用編寫代碼。

例如:

    class A

    { …

      private:

        A(const A &a);                // 私有的拷貝構(gòu)造函數(shù)

        A & operate =(const A &a);    // 私有的賦值函數(shù)

    };



如果有人試圖編寫如下程序:

    A  b(a);    // 調(diào)用了私有的拷貝構(gòu)造函數(shù)

    b = a;      // 調(diào)用了私有的賦值函數(shù)

編譯器將指出錯誤,因為外界不可以操作A的私有函數(shù)。

9.8 如何在派生類中實現(xiàn)類的基本函數(shù)
       基類的構(gòu)造函數(shù)、析構(gòu)函數(shù)、賦值函數(shù)都不能被派生類繼承。如果類之間存在繼承關(guān)系,在編寫上述基本函數(shù)時應(yīng)注意以下事項:

u       派生類的構(gòu)造函數(shù)應(yīng)在其初始化表里調(diào)用基類的構(gòu)造函數(shù)。

u       基類與派生類的析構(gòu)函數(shù)應(yīng)該為虛(即加virtual關(guān)鍵字)。例如

#include <iostream.h>

class Base

{

  public:

    virtual ~Base() { cout<< "~Base" << endl ; }

};



class Derived : public Base

{

  public:

    virtual ~Derived() { cout<< "~Derived" << endl ; }

};



void main(void)

{

    Base * pB = new Derived;  // upcast

    delete pB;

}



輸出結(jié)果為:

       ~Derived

       ~Base

如果析構(gòu)函數(shù)不為虛,那么輸出結(jié)果為

       ~Base



u       在編寫派生類的賦值函數(shù)時,注意不要忘記對基類的數(shù)據(jù)成員重新賦值。例如:

class Base

{

  public:



    Base & operate =(const Base &other);    // 類Base的賦值函數(shù)

  private:

    int  m_i, m_j, m_k;

};



class Derived : public Base

{

  public:



    Derived & operate =(const Derived &other);    // 類Derived的賦值函數(shù)

  private:

    int  m_x, m_y, m_z;

};



Derived & Derived:perate =(const Derived &other)

{

    //(1)檢查自賦值

    if(this == &other)

        return *this;



    //(2)對基類的數(shù)據(jù)成員重新賦值

    Base:perate =(other);    // 因為不能直接操作私有數(shù)據(jù)成員



    //(3)對派生類的數(shù)據(jù)成員賦值

    m_x = other.m_x;

    m_y = other.m_y;

    m_z = other.m_z;



    //(4)返回本對象的引用

    return *this;

}



9.9 一些心得體會
有些C++程序設(shè)計書籍稱構(gòu)造函數(shù)、析構(gòu)函數(shù)和賦值函數(shù)是類的“Big-Three”,它們的確是任何類最重要的函數(shù),不容輕視。

也許你認(rèn)為本章的內(nèi)容已經(jīng)夠多了,學(xué)會了就能平安無事,我不能作這個保證。如果你希望吃透“Big-Three”,請好好閱讀參考文獻(xiàn)[Cline] [Meyers] [Murry]。







第10章 類的繼承與組合


對象(Object)是類(Class)的一個實例(Instance)。如果將對象比作房子,那么類就是房子的設(shè)計圖紙。所以面向?qū)ο笤O(shè)計的重點是類的設(shè)計,而不是對象的設(shè)計。

對于C++程序而言,設(shè)計孤立的類是比較容易的,難的是正確設(shè)計基類及其派生類。本章僅僅論述“繼承”(Inheritance)和“組合”(Composition)的概念。

注意,當(dāng)前面向?qū)ο蠹夹g(shù)的應(yīng)用熱點是COM和CORBA,這些內(nèi)容超出了C++教材的范疇,請閱讀COM和CORBA相關(guān)論著。

10.1 繼承
如果A是基類,B是A的派生類,那么B將繼承A的數(shù)據(jù)和函數(shù)。例如:

       class A

{

  public:

              void  Func1(void);

              void  Func2(void);

};



class B : public A

{

  public:

              void  Func3(void);

              void  Func4(void);

};



       main()

{

              B  b;                     

              b.Func1();              // B從A繼承了函數(shù)Func1

              b.Func2();              // B從A繼承了函數(shù)Func2

              b.Func3();

              b.Func4();

}



這個簡單的示例程序說明了一個事實:C++的“繼承”特性可以提高程序的可復(fù)用性。正因為“繼承”太有用、太容易用,才要防止亂用“繼承”。我們應(yīng)當(dāng)給“繼承”立一些使用規(guī)則。



l         【規(guī)則10-1-1】如果類A和類B毫不相關(guān),不可以為了使B的功能更多些而讓B繼承A的功能和屬性。不要覺得“白吃白不吃”,讓一個好端端的健壯青年無緣無故地吃人參補身體。

l         【規(guī)則10-1-2】若在邏輯上B是A的“一種”(a kind of ),則允許B繼承A的功能和屬性。例如男人(Man)是人(Human)的一種,男孩(Boy)是男人的一種。那么類Man可以從類Human派生,類Boy可以從類Man派生。

         class Human

{

                  …

};

         class Man : public Human

{

                  …

};

         class Boy : public Man

{

                  …

};



u       注意事項

【規(guī)則10-1-2】看起來很簡單,但是實際應(yīng)用時可能會有意外,繼承的概念在程序世界與現(xiàn)實世界并不完全相同。

例如從生物學(xué)角度講,鴕鳥(Ostrich)是鳥(Bird)的一種,按理說類Ostrich應(yīng)該可以從類Bird派生。但是鴕鳥不能飛,那么Ostrich::Fly是什么東西?

class Bird

{

public:   

       virtual void Fly(void);



};



class Ostrich : public Bird

{



};



例如從數(shù)學(xué)角度講,圓(Circle)是一種特殊的橢圓(Ellipse),按理說類Circle應(yīng)該可以從類Ellipse派生。但是橢圓有長軸和短軸,如果圓繼承了橢圓的長軸和短軸,豈非畫蛇添足?

       所以更加嚴(yán)格的繼承規(guī)則應(yīng)當(dāng)是:若在邏輯上B是A的“一種”,并且A的所有功能和屬性對B而言都有意義,則允許B繼承A的功能和屬性。

10.2 組合
l         【規(guī)則10-2-1】若在邏輯上A是B的“一部分”(a part of),則不允許B從A派生,而是要用A和其它東西組合出B。

例如眼(Eye)、鼻(Nose)、口(Mouth)、耳(Ear)是頭(Head)的一部分,所以類Head應(yīng)該由類Eye、Nose、Mouth、Ear組合而成,不是派生而成。如示例10-2-1所示。



class Eye

{
  public:

void  Look(void);  

};
class Nose

{
  public:

void  Smell(void);

};

class Mouth

{
  public:

void  Eat(void);     

};
class Ear

{
  public:

void  Listen(void);

};

// 正確的設(shè)計,雖然代碼冗長。

class Head

{

  public:

              void       Look(void)     {  m_eye.Look();  }

              void       Smell(void)     {  m_nose.Smell();  }

              void       Eat(void) {  m_mouth.Eat();  }

              void       Listen(void)    {  m_ear.Listen();  }

  private:

              Eye       m_eye;

              Nose     m_nose;

              Mouth  m_mouth;

              Ear        m_ear;

};


示例10-2-1 Head由Eye、Nose、Mouth、Ear組合而成

      

如果允許Head從Eye、Nose、Mouth、Ear派生而成,那么Head將自動具有Look、 Smell、Eat、Listen這些功能。示例10-2-2十分簡短并且運行正確,但是這種設(shè)計方法卻是不對的。



       // 功能正確并且代碼簡潔,但是設(shè)計方法不對。

class Head : public Eye, public Nose, public Mouth, public Ear

{

};


示例10-2-2  Head從Eye、Nose、Mouth、Ear派生而成



一只公雞使勁地追打一只剛下了蛋的母雞,你知道為什么嗎?

因為母雞下了鴨蛋。

很多程序員經(jīng)不起“繼承”的誘惑而犯下設(shè)計錯誤!斑\行正確”的程序不見得是高質(zhì)量的程序,此處就是一個例證。

論壇徽章:
0
9 [報告]
發(fā)表于 2008-04-18 23:31 |只看該作者
第11章 其它編程經(jīng)驗
11.1 使用const提高函數(shù)的健壯性
看到const關(guān)鍵字,C++程序員首先想到的可能是const常量。這可不是良好的條件反射。如果只知道用const定義常量,那么相當(dāng)于把火藥僅用于制作鞭炮。const更大的魅力是它可以修飾函數(shù)的參數(shù)、返回值,甚至函數(shù)的定義體。

const是constant的縮寫,“恒定不變”的意思。被const修飾的東西都受到強制保護(hù),可以預(yù)防意外的變動,能提高程序的健壯性。所以很多C++程序設(shè)計書籍建議:“Use const whenever you need”。



11.1.1 用const修飾函數(shù)的參數(shù)

如果參數(shù)作輸出用,不論它是什么數(shù)據(jù)類型,也不論它采用“指針傳遞”還是“引用傳遞”,都不能加const修飾,否則該參數(shù)將失去輸出功能。

const只能修飾輸入?yún)?shù):

u       如果輸入?yún)?shù)采用“指針傳遞”,那么加const修飾可以防止意外地改動該指針,起到保護(hù)作用。

例如StringCopy函數(shù):

        void StringCopy(char *strDestination, const char *strSource);

其中strSource是輸入?yún)?shù),strDestination是輸出參數(shù)。給strSource加上const修飾后,如果函數(shù)體內(nèi)的語句試圖改動strSource的內(nèi)容,編譯器將指出錯誤。



u       如果輸入?yún)?shù)采用“值傳遞”,由于函數(shù)將自動產(chǎn)生臨時變量用于復(fù)制該參數(shù),該輸入?yún)?shù)本來就無需保護(hù),所以不要加const修飾。

例如不要將函數(shù)void Func1(int x) 寫成void Func1(const int x)。同理不要將函數(shù)void Func2(A a) 寫成void Func2(const A a)。其中A為用戶自定義的數(shù)據(jù)類型。



u       對于非內(nèi)部數(shù)據(jù)類型的參數(shù)而言,象void Func(A a) 這樣聲明的函數(shù)注定效率比較底。因為函數(shù)體內(nèi)將產(chǎn)生A類型的臨時對象用于復(fù)制參數(shù)a,而臨時對象的構(gòu)造、復(fù)制、析構(gòu)過程都將消耗時間。

為了提高效率,可以將函數(shù)聲明改為void Func(A &a),因為“引用傳遞”僅借用一下參數(shù)的別名而已,不需要產(chǎn)生臨時對象。但是函數(shù)void Func(A &a) 存在一個缺點:“引用傳遞”有可能改變參數(shù)a,這是我們不期望的。解決這個問題很容易,加const修飾即可,因此函數(shù)最終成為void Func(const A &a)。

以此類推,是否應(yīng)將void Func(int x) 改寫為void Func(const int &x),以便提高效率?完全沒有必要,因為內(nèi)部數(shù)據(jù)類型的參數(shù)不存在構(gòu)造、析構(gòu)的過程,而復(fù)制也非常快,“值傳遞”和“引用傳遞”的效率幾乎相當(dāng)。

    問題是如此的纏綿,我只好將“const &”修飾輸入?yún)?shù)的用法總結(jié)一下,如表11-1-1所示。



對于非內(nèi)部數(shù)據(jù)類型的輸入?yún)?shù),應(yīng)該將“值傳遞”的方式改為“const引用傳遞”,目的是提高效率。例如將void Func(A a) 改為void Func(const A &a)。



對于內(nèi)部數(shù)據(jù)類型的輸入?yún)?shù),不要將“值傳遞”的方式改為“const引用傳遞”。否則既達(dá)不到提高效率的目的,又降低了函數(shù)的可理解性。例如void Func(int x) 不應(yīng)該改為void Func(const int &x)。




表11-1-1 “const &”修飾輸入?yún)?shù)的規(guī)則



11.1.2 用const修飾函數(shù)的返回值

u       如果給以“指針傳遞”方式的函數(shù)返回值加const修飾,那么函數(shù)返回值(即指針)的內(nèi)容不能被修改,該返回值只能被賦給加const修飾的同類型指針。

例如函數(shù)

        const char * GetString(void);

如下語句將出現(xiàn)編譯錯誤:

        char *str = GetString();

正確的用法是

        const char *str = GetString();



u       如果函數(shù)返回值采用“值傳遞方式”,由于函數(shù)會把返回值復(fù)制到外部臨時的存儲單元中,加const修飾沒有任何價值。

    例如不要把函數(shù)int GetInt(void) 寫成const int GetInt(void)。

    同理不要把函數(shù)A GetA(void) 寫成const A GetA(void),其中A為用戶自定義的數(shù)據(jù)類型。

    如果返回值不是內(nèi)部數(shù)據(jù)類型,將函數(shù)A GetA(void) 改寫為const A & GetA(void)的確能提高效率。但此時千萬千萬要小心,一定要搞清楚函數(shù)究竟是想返回一個對象的“拷貝”還是僅返回“別名”就可以了,否則程序會出錯。見6.2節(jié)“返回值的規(guī)則”。



u       函數(shù)返回值采用“引用傳遞”的場合并不多,這種方式一般只出現(xiàn)在類的賦值函數(shù)中,目的是為了實現(xiàn)鏈?zhǔn)奖磉_(dá)。

例如

    class A

    {…

        A & operate = (const A &other);    // 賦值函數(shù)

    };

    A a, b, c;         // a, b, c 為A的對象

    …

    a = b = c;            // 正常的鏈?zhǔn)劫x值

    (a = b) = c;      // 不正常的鏈?zhǔn)劫x值,但合法

如果將賦值函數(shù)的返回值加const修飾,那么該返回值的內(nèi)容不允許被改動。上例中,語句 a = b = c仍然正確,但是語句 (a = b) = c 則是非法的。



11.1.3 const成員函數(shù)

    任何不會修改數(shù)據(jù)成員的函數(shù)都應(yīng)該聲明為const類型。如果在編寫const成員函數(shù)時,不慎修改了數(shù)據(jù)成員,或者調(diào)用了其它非const成員函數(shù),編譯器將指出錯誤,這無疑會提高程序的健壯性。

以下程序中,類stack的成員函數(shù)GetCount僅用于計數(shù),從邏輯上講GetCount應(yīng)當(dāng)為const函數(shù)。編譯器將指出GetCount函數(shù)中的錯誤。

    class Stack

{

      public:

        void     Push(int elem);

        int     Pop(void);

        int     GetCount(void)  const;  // const成員函數(shù)

      private:

        int     m_num;

        int     m_data[100];

};



    int Stack::GetCount(void)  const

{
        ++ m_num;  // 編譯錯誤,企圖修改數(shù)據(jù)成員m_num

    Pop();      // 編譯錯誤,企圖調(diào)用非const函數(shù)

    return m_num;

    }

    const成員函數(shù)的聲明看起來怪怪的:const關(guān)鍵字只能放在函數(shù)聲明的尾部,大概是因為其它地方都已經(jīng)被占用了。

11.2 提高程序的效率
程序的時間效率是指運行速度,空間效率是指程序占用內(nèi)存或者外存的狀況。

全局效率是指站在整個系統(tǒng)的角度上考慮的效率,局部效率是指站在模塊或函數(shù)角度上考慮的效率。



l         【規(guī)則11-2-1】不要一味地追求程序的效率,應(yīng)當(dāng)在滿足正確性、可靠性、健壯性、可讀性等質(zhì)量因素的前提下,設(shè)法提高程序的效率。



l         【規(guī)則11-2-2】以提高程序的全局效率為主,提高局部效率為輔。



l         【規(guī)則11-2-3】在優(yōu)化程序的效率時,應(yīng)當(dāng)先找出限制效率的“瓶頸”,不要在無關(guān)緊要之處優(yōu)化。



l         【規(guī)則11-2-4】先優(yōu)化數(shù)據(jù)結(jié)構(gòu)和算法,再優(yōu)化執(zhí)行代碼。



l         【規(guī)則11-2-5】有時候時間效率和空間效率可能對立,此時應(yīng)當(dāng)分析那個更重要,作出適當(dāng)?shù)恼壑。例如多花費一些內(nèi)存來提高性能。



l         【規(guī)則11-2-6】不要追求緊湊的代碼,因為緊湊的代碼并不能產(chǎn)生高效的機(jī)器碼。



11.3 一些有益的建議
&sup2;        【建議11-3-1】當(dāng)心那些視覺上不易分辨的操作符發(fā)生書寫錯誤。

我們經(jīng)常會把“==”誤寫成“=”,象“||”、“&&”、“<=”、“>=”這類符號也很容易發(fā)生“丟1”失誤。然而編譯器卻不一定能自動指出這類錯誤。



&sup2;        【建議11-3-2】變量(指針、數(shù)組)被創(chuàng)建之后應(yīng)當(dāng)及時把它們初始化,以防止把未被初始化的變量當(dāng)成右值使用。



&sup2;        【建議11-3-3】當(dāng)心變量的初值、缺省值錯誤,或者精度不夠。



&sup2;        【建議11-3-4】當(dāng)心數(shù)據(jù)類型轉(zhuǎn)換發(fā)生錯誤。盡量使用顯式的數(shù)據(jù)類型轉(zhuǎn)換(讓人們知道發(fā)生了什么事),避免讓編譯器輕悄悄地進(jìn)行隱式的數(shù)據(jù)類型轉(zhuǎn)換。



&sup2;        【建議11-3-5】當(dāng)心變量發(fā)生上溢或下溢,數(shù)組的下標(biāo)越界。



&sup2;        【建議11-3-6】當(dāng)心忘記編寫錯誤處理程序,當(dāng)心錯誤處理程序本身有誤。



&sup2;        【建議11-3-7】當(dāng)心文件I/O有錯誤。



&sup2;        【建議11-3-8】避免編寫技巧性很高代碼。



&sup2;        【建議11-3-9】不要設(shè)計面面俱到、非常靈活的數(shù)據(jù)結(jié)構(gòu)。



&sup2;        【建議11-3-10】如果原有的代碼質(zhì)量比較好,盡量復(fù)用它。但是不要修補很差勁的代碼,應(yīng)當(dāng)重新編寫。



&sup2;        【建議11-3-11】盡量使用標(biāo)準(zhǔn)庫函數(shù),不要“發(fā)明”已經(jīng)存在的庫函數(shù)。



&sup2;        【建議11-3-12】盡量不要使用與具體硬件或軟件環(huán)境關(guān)系密切的變量。



&sup2;        【建議11-3-13】把編譯器的選擇項設(shè)置為最嚴(yán)格狀態(tài)。



&sup2;        【建議11-3-14】如果可能的話,使用PC-Lint、LogiScope等工具進(jìn)行代碼審查。



完......

論壇徽章:
0
10 [報告]
發(fā)表于 2008-04-19 10:15 |只看該作者
靜下心來,好好讀讀CU的精華帖。
您需要登錄后才可以回帖 登錄 | 注冊

本版積分規(guī)則 發(fā)表回復(fù)

  

北京盛拓優(yōu)訊信息技術(shù)有限公司. 版權(quán)所有 京ICP備16024965號-6 北京市公安局海淀分局網(wǎng)監(jiān)中心備案編號:11010802020122 niuxiaotong@pcpop.com 17352615567
未成年舉報專區(qū)
中國互聯(lián)網(wǎng)協(xié)會會員  聯(lián)系我們:huangweiwei@itpub.net
感謝所有關(guān)心和支持過ChinaUnix的朋友們 轉(zhuǎn)載本站內(nèi)容請注明原作者名及出處

清除 Cookies - ChinaUnix - Archiver - WAP - TOP