高质量C++编程指南课件_第1页
高质量C++编程指南课件_第2页
高质量C++编程指南课件_第3页
高质量C++编程指南课件_第4页
高质量C++编程指南课件_第5页
已阅读5页,还剩385页未读 继续免费阅读

下载本文档

版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领

文档简介

高質量C++編程指南

第一章、高質量軟體開發之概念1.1軟體品質的基本概念一、詞典的定義:①典型的或本質的特徵②事物固有的或區別於其他事物的特徵或本質③優良或者出色的程度二、軟體成熟度模型CMM(CapabilityMaturityModel)定義:①一個系統或者組件或者過程符合特定需求的程度②一個系統或者組件或者過程符合客戶或者用戶的要求或期望的程度

以上定義很抽象三、從實用角度,10大品質屬性:1、第一大類:功能性屬性①正確性

第一個重要的軟體品質屬性。指軟體按照要求正確執行任務的能力。軟體如果運行不正確,將給用戶造成不便甚至損失。②健壯性

指異常情況下,軟體能夠正常運行的能力。健壯性有2層含義:*容錯能力:發生異常時系統不出錯的能力。對於航太、武器、金融等領域,容錯設計非常重要。容錯是非常健壯的意思,UNIX容錯能力很強。*恢復能力:指軟體發生錯誤(不論死活)後,重新運行時,能否恢復到沒有發生錯誤前的狀態的能力。例如:某人挨了壞蛋一頓拳腳,特別健壯的人一點事沒有,表示有容錯能力;比較健壯的人,雖然被打倒在地,過一會還能爬起來,除了皮肉之痛外倒也不用去醫院,表示恢復能力比較強;而虛弱的人可能短期恢復不過來,得在病床上躺很久。恢復能力是很有價值的。如Microsoft公司早期的操作系統Windows3.x和Windows9x動不動就死機,其容錯性比較差,但它的恢復能力不錯,機器重新啟動後一般都能正常運行,看在這個份上,人們也願意將就著使用.③可靠性

軟體可靠性問題通常是由於設計中沒有料到的異常和測試中沒有暴露的代碼缺陷引起的。可靠性是指在一定的環境下,在一定的時間段內,程式不出現故障的概率,是一個統計量。通常用MTTF(平均無故障時間)衡量。2、第二大類:非功能性屬性④性能

性能通常是指軟體的時間-空間效率,人們總希望軟體的運行速度快些,佔用的資源少些。可以通過優化數據結構、演算法和代碼來提高軟體性能。⑤易用性

是指用戶使用軟體容易的程度。軟體的易用性要讓用戶來評價,如果用戶覺得軟體很難用,開發人員不要有逆反心理,其實不是用戶笨,是你開發的軟體太笨了。⑥清晰性

清晰意味著工作成果易讀、易理解,這個品質屬性表達了人們一種質樸的願望:讓我花錢買它或用它,總得讓我看明白它是什麼東西。開發人員只有在自己思路清晰的時候才能寫出讓別人易讀、易理解的程式和文檔。高水準的人能夠把系統設計得很簡潔。如果軟體系統臃腫不堪,它遲早會出問題。⑦安全性

是指資訊安全,英文是Security而不是Safety。安全性是指防止系統被非法入侵的能力。對於大多數軟體產品來說,杜絕非法入侵既不可能也沒有必要。一般地,如果駭客為非法入侵花費的代價(考慮時間、費用、風險等多種因素)高於得到的好處,那麼這樣的系統認為是安全的⑧可擴展性可擴展性反映了軟體適應變化的能力。如需求、設計變化,演算法改進、程式的變化等。如果軟體的規模很大,問題很複雜,若軟體的可擴展性不好,那麼就有很大的局限性。可擴展性是軟體設計階段重點考慮的問題。⑨相容性是指兩個或者兩個以上的軟體相互交換資訊的能力。如兩個字處理軟體的檔格式相容。相容性的商業規則是:弱者相容強者,否則無容身之地;強者避免被相容,否則市場被瓜分。⑩可移植性

是指軟體不經修改或者稍加修改就可以運行在不同硬體環境(CPU、OS和編譯器)的能力,主要體現為代碼的可移植性。編程語言越低級,用它寫的程式越難移植,反之則越容易。一般地,軟體設計時應將“設備相關程式”與“設備無關程式”分開,將“功能模組”與“用戶介面”分開,這樣可提高可移植性。1.2提高軟體品質的基本方法提高品質就是減少軟體的缺陷。缺陷越少軟體品質越高。Bug是缺陷的形象比喻。錯誤是嚴重的缺陷。沒有錯誤的程式世間難求。品質的最高境界是盡善盡美,即零缺陷。郎中治病的故事:在中國古代,有一家三兄弟全是郎中。其中一人是名醫,人們問他:“你們兄弟三人誰的醫術最高?”他回答說:“我常用猛藥給病危者治病,偶爾有些病危者被我救活,於是我的醫術遠近聞名並成了名醫。我二哥通常在人們剛剛生病時馬上就治癒他們,臨近村莊的人都知道他的醫術。我大哥深知人們生病的原因,所以能夠預防家裏人生病,他的醫術只有我們家裏人知道。消除軟體缺陷有三種基本方式:一、在開發過程中有效地防止工作成果產生缺陷,將高質量內建於開發過程之中。這是預防勝於治療的道理,也是最佳的方式。學習高質量編程的目的就是要一次性編寫出高質量的程式,而不是在程式出錯後才去修補。二、當工作成果剛剛產生時馬上進行品質檢查,及時找出並消除工作成果中的缺陷。這種方式的效果比較好,人們一般都能學會。最常用的是技術評審、測試和品質保證等,已經被企業廣泛採用並取得了成效。三、當軟體交付使用後,用著用著就出錯了,趕快請開發者來補救,這種方式的代價最高。可笑的是:當軟體系統在用戶那裏出故障了,那些現場補救成功的人倒成了英雄,好心用戶甚至寄來感謝信。1.3高質量軟體開發的基本方法一、建立軟體過程規範:若想順利開發高質量的軟體產品,必須有條不紊地組織技術開發活動和專案管理活動,我們把這些活動的組織形式稱為過程模型。典型的成果是1970年提出的提出的瀑布模型。其他還有增量模型、快速原型模型、螺旋模型、迭代模型。瀑布模型的精髓是線性順序地開發軟體。線性化是人們最容易掌握並能熟練應用的思想方法。特別適合企業軟體開發。二、複用複用就是指“利用現成的東西”。是拿來主義的思想。是對前人的成果的利用和繼承。複用是人類智慧的表現。複用有利於提高品質、提高生產率和降低成本。勤勞且聰明的人們應該把把大部分的時間用在小比例的創新工作上,而把小部分的時間用在大比例的成熟工作中,這樣才能把工作做得又快又好。軟體複用簡化了軟體開發過程,減少了總的開發工作量與維護代價。據統計世界上已有一千多億行程序,無數功能被重寫了成千上萬次,真是浪費啊。三、分而治之是把一個複雜的問題分解成若干個簡單問題,然後逐個解決。軟體分而治之說起來容易,做起來好難。應該著重考慮:複雜問題分解後,每個問題能否用程式實現?所有程式能否最終集成為一個軟體系統並有效解決原始的複雜問題?四、優化與折中優化是指優化軟體的各個品質屬性。如提高運行速度,提高對內存的利用率,使用戶的介面更加友好,使三維圖形的真實感更強等。軟體的折中是指協調各個品質屬性,實現整體品質最優。折中是在保證其他品質屬性不差的前提下,使某些重要品質屬性變的更好。優化與折中——魚與熊掌可兼得

假設魚每公斤10元,熊掌每公斤10000元,有個強脾氣的人只有20元,非得要吃上1公斤美妙的“熊掌燒魚”,怎麼辦?解決方法:花9元9角9分錢買999克魚肉,花10元錢買1克熊掌肉,可以做一道“熊掌戲魚”菜,剩下的那一分錢還可以建立基金,用於推廣優化與折中方法。五、技術評審TR(TechnicalReview):目的是儘早發現工作成果中的缺陷,並幫助開發人員及時消除缺陷,技術評審的主要好處:通過消除工作成果中的缺陷而提高產品品質。越早消除缺陷就越能降低成本。開發人員能及時得到同行專家的幫助和指導。技術評審的2種基本類型:正式技術評審(FTR):FTR比較嚴格,需要舉行評審會議,參加評審會議的人員比較多。非正式技術評審(ITR):ITR的形式比較靈活,通常在同伴之間開展,不必舉行會議,評審人員比較少。六、測試:是通過運行測試用例(TestCase)來找出軟體中的缺陷。測試是一種動態檢查。一個成功的測試在於發現了迄今尚未發現的缺陷。所以怎樣設計出有效的測試是非常重要的。常用的測試:測試階段:單元測試、集成測試、系統測試、驗收測試測試方法:白盒測試、黑盒測試測試內容:功能測試、介面測試、安全測試*

測試能提高軟體的品質,但提高品質不能依賴測試。*測試只能證明缺陷存在,不能證明缺陷不存在,徹底的測試難以實現。*測試的困難是不知道如何進行有效的測試,也不知道什麼時候可以放心的結束測試*2—8原則:80%的缺陷聚集在20%的模組中,經常出錯的模組改錯後還會經常出錯七、品質保證QA(QualityAssurance):目的是提供一種有效的人員組織形式和管理方法。它是一種有計畫的、貫穿於整個產品生命週期的品質管理方法。品質保證的基本方法是通過有計畫地檢查工作過程及工作成果是否符合既定的規範,來監控和改進過程品質和產品品質。建議把技術評審、測試與品質保證三者有機地結合起來,才能提高工作效率和降低成本。八、改錯:就是找出根源,對症下藥。有人問阿凡提:“我肚子痛,應該用什麼藥?”阿凡提說“應該用眼藥水,因為你眼睛不好,吃了髒東西才肚子痛。”由軟體錯誤的症狀找出根源並不是件容易的事,因為:1、症狀和根源可能相隔很遠。2、症狀可能在另一個錯誤被糾正後暫時性消失。3、症狀可能並不是由某個程式錯誤直接引發的,如累積誤差。4、症狀可能是由不太容易跟蹤的人工錯誤引起的。5、症狀可能時隱時現,如記憶體洩漏。6、很難重現“錯誤現場”。7、症狀可能分佈在多個不同任務中,難以跟蹤。尋找錯誤過程稱調試(Debugging)。調試簡單辦法:插入列印語句。1.4有關編程的思考一、有最好的編程語言嗎?cc++visualc++visualbasicdelphijava等哪個最好?能很好地解決問題的語言就是好語言根據實際情況選擇自己擅長的語言,才能保證有較好的品質。不要發誓忠於某某語言而自尋煩惱。二、編程時應該多使用技巧嗎?技巧的優點在於另闢蹊徑地解決問題。缺點是技巧不為人熟知。程式中使用太多的技巧,可能會遺留下錯誤隱患,別人也難以理解。建議用自然的方式編程,不要濫用技巧。《賣油翁》的故事又告訴我們“熟能生巧”,表明技巧是自然而然產生的,不是賣弄出來的。三、換更快的電腦還是換更快演算法?如果開發軟體的目的是為了學習或是研究,那麼應該設計一種更快的演算法。如果該軟體已經用於商業,則需謹慎考慮。如果更換一臺電腦能解決問題,則是最快的解決方案。類似問題:是買現成的程式?還是徹底有自己開發?四、錯誤是否應該分等級?微軟的一些開發小組把錯誤分為4個等級:一級嚴重:導致軟體崩潰。二級嚴重:導致一個特性不能運行,並且沒有替代方案。三級嚴重:導致一個特性不能運行,但有替代方案。四級嚴重:是表面化的或是微小的。觀點:該分類帶有較重的技術傾向,並不是普遍適用的。

假設某個財務軟體有2個錯誤:錯誤A使該軟體死掉,錯誤B導致工資計算錯誤。按上述分類,錯誤A屬一級錯誤,錯誤B屬二級錯誤。例如航空軟體操作手冊寫錯了,按上述分類屬四級錯誤。但這樣的錯誤可能導致機毀人亡,難道還算微小嗎?編程人員應該意識到:所有的錯誤都是嚴重的,不存在微不足道的錯誤,只有這樣才能少犯錯誤。小結

軟體品質屬性之間並非完全獨立的,而是相互交織、相互影響的。因此要同時兼顧多個軟體品質屬性,使程式達到整體最優。軟體工程的觀念、方法和規範都是樸實無華的,平凡之人皆可領會,但只有實實在在地用起來才有價值。第二章、C++檔結構和程式版式2.1程式檔的目錄結構

一個正在開發程式不僅包括源代碼還包含許多資源檔,數據檔、庫檔等等.典型的C++程式工程結構如圖所示。圖中目錄的用途如下:1、include目錄存放程式的頭檔2、source目錄存放程式的原始檔案3、shared目錄存放一些共用檔4、resource目錄存放各種資源檔,包括圖片、圖示、游標、對話框5、debug目錄存放應用程式調試版本生成的目標檔、lib檔、dll檔及可執行檔等。6、release目錄存放應用程式發行版本生成的目標檔、lib檔、dll檔及可執行檔等。7、bin目錄存放程式員自己創建的lib檔。提示:要分清楚編譯時相對路徑和運行時相對路徑的不同。例如:#include“.\include\abc.h”其相對路徑是當前工程檔的路徑.而下麵這行代碼:

OpenFile(“..\abc.ini”);其相對路徑是在運行時可執行檔的路徑.2.2程式檔的結構C++根源程式一般有2類檔,一類為保存程式聲明的頭檔,尾碼為.h;另一類為保存程式實現的原始檔案,尾碼為.cpp原始檔案一般要用#include包含頭檔一、頭檔的用途和結構1、頭檔用途:(1)、通過頭檔來調用庫功能。在一些場合,源代碼不便向用戶公開,只向用戶提供頭檔和二進位即可。編譯器會從庫中提取相應的代碼。(2)、能加強類型安全檢查。如果某個介面被實現或被使用時的方式與頭檔的聲明不一致,編譯器會指出錯誤。(3)、頭檔可以提高程式的可讀性2、頭檔結構:頭檔中的元素比較多,其結構一般安排:①、版權、版本資訊。②、編譯預處理塊,如:#ifndef/#define/#endif結構。③、檔包含。有#include<>及””。④、常量定義及初始化。⑤、全局變數的聲明。⑥、數據類型定義。typedefstructclass⑦、函數原型。⑧、內聯函數定義。二、版權和版本資訊版權和版本的聲明位於頭檔和定義檔的開頭(見例1-1),主要內容有:(1)版權資訊。(2)檔案名稱,識別字,摘要。(3)當前版本,作者/修改者,完成日期。(4)版本歷史資訊。這些資訊實際上是注釋資訊,程式不會執行的【規則1】為了防止頭檔被重複引用,應當用ifndef/define/endif結構產生預處理塊。【規則2】用#include<filename.h>格式來引用標準庫的頭檔(編譯器將從標準庫目錄開始搜索)。【規則3】用#include“filename.h”格式來引用非標準庫的頭檔(編譯器將從用戶的工作目錄開始搜索)。【建議1】頭檔中只存放“聲明”而不存放“定義”在C++語法中,類的成員函數可以在聲明的同時被定義,並且自動成為內聯函數。這雖然會帶來書寫上的方便,但卻造成了風格不一致,弊大於利。建議將成員函數的定義與聲明分開,不論該函數體有多麼小。二、

代碼行及行內空格【規則1】一行代碼只做一件事情,如只定義一個變數,或只寫一條語句。這樣的代碼容易閱讀,並且方便於寫注釋。【規則2】if、for、while、do等語句自占一行,執行語句不得緊跟其後。不論執行語句有多少都要加{

}。這樣可以防止書寫失誤。示例2(a)為風格良好的代碼行,示例2(b)為風格不良的代碼行。【建議1】盡可能在定義變數的同時初始化該變數(就近原則)如果變數的引用處和其定義處相隔比較遠,變數的初始化很容易被忘記。如果引用了未被初始化的變數,可能會導致程式錯誤。本建議可以減少隱患。【規則3】關鍵字之後要留空格。象const、virtual、inline、case等關鍵字之後至少要留一個空格,否則無法辨析關鍵字。象if、for、while等關鍵字之後應留一個空格再跟左括弧‘(’,以突出關鍵字。【規則4】函數名之後不要留空格,緊跟左括弧‘(’,以與關鍵字區別。【規則5】‘(’向後緊跟,‘)’、‘,’、‘;’向前緊跟,緊跟處不留空格。【規則6】‘,’之後要留空格,如Function(x,y,z)。如果‘;’不是一行的結束符號,其後要留空格,如for(initialization;condition;update)。【規則7】賦值操作符、比較操作符、算術操作符、邏輯操作符、位域操作符,如“=”、“+=”“>=”、“<=”、“+”、“*”、“%”、“&&”、“||”、“<<”,“^”等二元操作符的前後應當加空格。【規則8】一元操作符如“!”、“~”、“++”、“--”、“&”(地址運算符)等前後不加空格。【規則9】象“[]”、“.”、“->”這類操作符前後不加空格。【建議2】對於運算式比較長的for語句和if語句,為了緊湊起見可以適當地去掉一些空格,如for(i=0;i<10;i++)和if((a<=b)&&(c<=d))三、對齊與縮進【規則1】程式的分界符‘{’和‘}’應獨佔一行並且位於同一列,同時與引用它們的語句左對齊。【規則2】{

}之內的代碼塊在‘{’右邊數格處左對齊。示例4(a)為風格良好的對齊,示例4(b)為風格不良的對齊。四、長行拆分:【規則1】代碼行最大長度宜控制在70至80個字元以內。代碼行不要過長,否則眼睛看不過來,也不便於列印。【規則2】長運算式要在低優先順序操作符處拆分成新行,操作符放在新行之首(以便突出操作符)。拆分出的新行要進行適當的縮進,使排版整齊,語句可讀。五、修飾符的位置修飾符*和&應該靠近數據類型還是靠近變數名,是個有爭議的話題。若將修飾符*靠近數據類型,例如:int*x;從語義上講此寫法比較直觀,即x是int類型的指針。上述寫法的弊端是容易引起誤解,例如:int*x,y;此處y容易被誤解為指針變數。雖然將x和y分行定義可以避免誤解,但並不是人人都願意這樣做。【建議1】應當將修飾符*和&緊靠變數名或者用typedef做個類型映射。例如:char*name;int*x,y; //此處y不會被誤解為指針六、注釋的風格

C語言的注釋符為“/*…*/”。C++語言中,程式塊的注釋常採用“/*…*/”,行注釋一般採用“//…”。注釋通常用於:(1)版本、版權聲明;(2)函數介面說明;(3)重要的代碼行或段落提示。雖然注釋有助於理解代碼,但注意不可過多地使用注釋。

【規則1】注釋是對代碼的“提示”,而不是文檔。程式中的注釋不可喧賓奪主,注釋太多了會讓人眼花繚亂。注釋的花樣要少【規則2】如果代碼本來就是清楚的,則不必加注釋。否則多此一舉,令人厭煩。例如:i++; //i加1,多餘的注釋【規則3】邊寫代碼邊注釋,修改代碼同時修改相應的注釋,以保證注釋與代碼的一致性。不再有用的注釋要刪除。【規則4】注釋應當準確、易懂,防止注釋有二義性。錯誤的注釋不但無益反而有害【規則5】儘量避免在注釋中使用縮寫,特別是不常用縮寫。【規則6】注釋的位置應與被描述的代碼相鄰,可以放在代碼的上方或右方,不可放在下方。【規則7】當代碼比較長,特別是有多重嵌套時,應當在一些段落的結束處加注釋,便於閱讀。七、類的版式類可以將數據和函數封裝在一起,其中函數表示了類的行為(或稱服務)。類提供關鍵字public、protected和private,分別用於聲明哪些數據和函數是公有的、受保護的或者是私有的。這樣可以達到資訊隱藏的目的,即讓類僅僅公開必須要讓外界知道的內容,而隱藏其他一切內容。我們不可以濫用類的封裝功能,不要把它當成火鍋,什麼東西都往裏扔。類的版式主要有兩種方式:(1)將private類型的數據寫在前面,而將public類型的函數寫在後面,如示例6(a)。採用這種版式的程式員主張類的設計“以數據為中心”,重點關注類的內部結構。(2)將public類型的函數寫在前面,而將private類型的數據寫在後面,如示例6(b)採用這種版式的程式員主張類的設計“以行為為中心”,重點關注的是類應該提供什麼樣的介面(或服務)。

很多C++教課書受到BiarneStroustrup第一本著作的影響,不知不覺地採用了“以數據為中心”的書寫方式,並不見得有多少道理。

建議讀者採用“以行為為中心”的書寫方式,即首先考慮類應該提供什麼樣的函數。這是很多人的經驗——“這樣做不僅讓自己在設計類時思路清晰,而且方便別人閱讀。因為用戶最關心的是介面,誰願意先看到一堆私有數據成員!

應試實例:

美国某著名计算机嵌入公司面试:

下麵程式有2中寫法,你青睞哪種?為什麼?

A:寫法1:if(‘A’==a){

a++;

}

写法2:if(a==‘A’){

a++;

}

B:写法1:for(i=0;i<8;i++){

X=i+Y+J*7;

printf(“%d”,X);

}

写法2:S=Y+J*7;

for(i=0;i<8;i++){

printf(“%d”,i+S);

}第三章、C++程式設計基礎3.1C++程式的基本概念一、不可缺少的主函數main():

1、

C++程式的可執行部分都是由函數組成的,main()就是其中必須有且只有一個的函數,叫主函數。

C++標準對main()函數有不同一般函數的限制:*不能重載。*不能內聯。*不能定義為靜態的。*不能取其地址。*不能由用戶直接調用。2、main()的命令行參數和返回值可以為main()定義任何類型和個數的形參,但前2個參數為int和char*[]或與之相容的類型。main()函數常見的原型為:voidmain(void);intmain(void);intmain(intargc,char*argv[]);intmain(intargc,char*argv[],char*env[]);char*argv[]用於接收命令行參數char*env[]用於接收系統環境變數返回值為int時,不同值有不同含義返回0,表示程式正常結束。返回非0,表示錯誤或非正常退出。3、內部名稱:

C++編譯器會按照特定的規則把用戶定義的識別字轉換為相應的內部名稱。structexample1{intcount;charname;};structexample2{intcount;char*name;};C++允許用戶定義擁有同名成員的多個構造類型如何區分它們的同名成員?

對語言實現來講,這兩個同名成員代表兩塊不同的記憶體,名字是引用這兩塊記憶體的別名,如果不加以區分,就會連接時導致二義性。二、類型轉換:程式中某個地方所需要的數據類型並非你提供的類型時,通常要進行數據類型轉換。本質上說,C++不會直接對兩個不同的運算元進行運算,除非進行隱式的類型轉換或者進行強制轉換,再就是定義運算符重載函數。類型轉換並不是改變原來的類型和值,而是生成了新的臨時變元,其類型為目標類型。1、隱式轉換就是編譯器在背後幫程式員做的類型轉換,程式員往往察覺不到。這種類型轉換必須具有足夠的安全性。凡是不安全的類型轉換,編譯器都能夠檢查出來並給出錯誤或警告資訊,而不會隱式進行。基本數據類型存在如下的相容關係:

charis-aintintis-alonglongis-afloatfloatis-adouble一個低級的數據類型對象總是優先轉換為能夠容納得下它的值的最小的高級類型對象。例如:有2個重載函數:voidfun(longm);voidfun(doublen);如果存在調用fun(100),會調用其中的哪個函數呢?會調用voidfun(longm)。再有:doubled1=100;inti=100;doubled2=i;

編譯器隱式地將100和i提升為double的一個臨時變數,然後才分別賦給d1和d2,當編譯器認為這些臨時變數不再需要時就會適時地把它們銷毀。派生類和基類之間存在is-a關係,因此可以直接將派生類對象轉換為基類對象。雖然會發生記憶體截斷,但無論從記憶體訪問還是從轉換結果來說都是安全的。classbase{private:intm_a,m_b;};classderived:publicbase{intm_c;};derivedobj1;baseobj2=obj1;derived*p1=&obj1;base*p2=p1;2、強制轉換*強制轉換可能導致安全問題.例如:doubled=1.28e+20;inti=(int)d;按照從浮點數到整數的轉換語義,結果應該是截去浮點數的小數部分而保留其整數部分,因為d的整數部分遠遠超出了一個int所能表示的範圍,結果當然不是我們所期望的(實際上為0)。*基本數據類型之間的指針強制轉換會改變編譯器對指針所指的記憶體單元的解釋方式,結果會導致錯誤。下麵的例子會導致記憶體訪問的截斷或擴張。結果顯然也不是你所期望的。doubled=1000.25;int*iptr=(int*)&d;inti=1000;double*dptr=(double*)&i;

C++基類和派生類之間的指針強制轉換同樣存在安全隱患。例如:baseobj;derived*dptr=(derived*)&obj;

通過dptr能夠訪問的記憶體擴張了4個位元組。如果訪問m_c就可能引發運行時錯誤,因為dptr指向的對象根本就沒有成員m_c的空間。提示1:不可以直接把基類對象轉換為派生類對象,這是不自然的。對於基本類型的強制轉換要區分值的截斷和記憶體的截斷的不同。

如果確信需要數據類型轉換請儘量使用顯式的強制類型轉換。提示2:儘量避免做違反編譯器類型安全原則和數據保護原則的事情,如在有符號和無符號數之間的轉換,或者把const對象的地址指派給非const對象指針等。面試實例:

中國臺灣著名CPU生產公司:

下麵程式的結果是什麼?

charfoo(void)

{

unsignedinta=6;

intb=-20;

charc;

(a+b)>6?(c=1):(c=0);

returnc;

}

答案:1三、運算式運算式就是使用運算符和識別字(運算元)按照語法規則連接起來的算式,任何運算式都是有值的。提示1:不要把數學中的運算式與電腦語言支持的運算式相混淆。如:(a<b<c)是數學運算式不是程式運算式。if(a<b<c)並不表示:if((a<b)&&(b<c))而是令人費解的if((a<b)<c)提示2:在使用運算符&&的運算式中,要儘量把最有可能為false的運算式放在&&的左邊;同樣在使用運算符||的運算式中,要儘量把最有可能為true的運算式放在||的左邊;因為C++對邏輯運算式的判斷採取突然死亡法;如果&&的左邊子運算式計算結果為0,則整個運算式計算結果就為false,後面的子運算式沒有必要再計算。這樣可以提高程式的執行效率。提示3:不要編寫太複雜的複合運算式。如

i=a>=b&&c+f<=g+h四、基本控制語句任何程式只用3種控制語句就可以實現:

順序結構、選擇結構、迴圈結構。1、選擇(判斷)結構C++有3種基本的選擇結構:單選結構:if雙重選擇結構:if…else多重選擇結構:switch…case建議1:在if…else結構中,要儘量把為TRUE概率較高的條件判斷置於前面,這樣可以提高該段程式的性能。(1)布爾變數與零值的比較:設flag為布爾變數的名字,它與零值比較的標準if語句如下:if(flag)//表示flag為真if(!flag)//表示flag為假下麵的if語句都屬於不良風格:if(flag!=TRUE)//錯誤的用法if(flag==TRUE)//錯誤的用法if(flag!=1)//錯誤的用法if(flag==1)//錯誤的用法因為假為0,真為非0,不同系統真值沒有統一值if(flag!=0)//不良的用法if(flag==0)//不良的用法讓人誤認為flag為整數。(2)整型變數與零值的比較假設整型變數為value,它與零值比較的標準if語句如下:if(value==0)if(value!=0)不可模仿布爾變數的書寫風格:if(value)或if(!value)讓人誤認為value是布爾變數。(3)浮點變數與零值的比較電腦表示浮點數float或double都有精度限制,超出精度的數,精度之外會截斷.如:10.222222225與10.222222229在32位電腦中它們就是相等的。提示1:如果兩個浮點數的絕對值小於或等於某個可接受的誤差,就認為它們是相等的,否則就是不相等的.因此不要直接用==或!=對兩個浮點數進行比較.

假設有兩個浮點變數x和y,精度定義為EPS=1e-6;則錯誤的比較方式為:if(x==y)//隱含錯誤的比較if(x!=y)//隱含錯誤的比較正確的比較方式為:if(abs(x-y)<=eps)

//x等於yif(abs(x-y)>eps)

//x不等於y浮點數x與零值比較的正確方式為:if(abs(x)<=eps)

//x等於0if(abs(x)>eps)

//x不等於0(4)指針變數與零值的比較指針變數的零值是空值,記為:NULL即不指向任何對象.儘管NULL的值與0相同,但兩者意義不同:#defineNULL((void*)0)假設指針變數的名字為p,它與零值比較的標準if語句如下:if(p==NULL)//p與NULL顯式比較if(p!=NULL)強調p是指針變數不可寫成:

if(p==0)//讓人誤認為p是整型變數

if(p!=0)

或者寫成:if(p)//讓人誤認為p是布爾變數

if(!p)補充:有時候會看到if(NULL==p)這樣的古怪格式,不是程式寫錯了,而是程式員為了防止將if(p==NULL)誤寫成:if(p=NULL),有意這樣寫的,編譯器認為是合法的,類似有if(100==i).提示:switch結構沒有自動跳出的功能,每個case子句的結尾不要忘記了加上break,否則導致多個分支重疊,也不要忘了最後的default子句,即使程式真的不需要default處理,也應該保留語句default:break;這樣做為了防止別人誤以為你忘了default處理.也是出於清晰性和對稱性考慮的.2、迴圈(重複)結構

C++提供了3種迴圈結構:do…while,while,for結構.即確定迴圈,不確定迴圈和無限迴圈.提示:標準C++允許在for語句中聲明變數,並且它的作用範圍為該for語句塊內,不過VisualC++語言的實現沒有遵循這個語意,而是把這樣聲明的變數挪到了for語句塊的外面,因此在for語句塊結束後仍然有效.

不要使用浮點數做計數器來控制迴圈,因為它是不精確的.建議1:如果計數器從0開始計數,建議for語句的迴圈控制變數取值採用前閉後開區間寫法.要防止出現差1錯誤.如:for(inti=0;i<N;i++){……}前閉後開for(intj=0;j<=N-1;j++){……}閉區間建議2:在多重嵌套的迴圈中,如有可能應當將最長迴圈放在最內層,最短迴圈放在最外層,這樣可以減少CPU跨切迴圈層的次數,從而優化程式的性能.例如:for(row=0;row<100;row++){for(col=0;col<6;col++){sum=sum+array[row][col];}}這段程式效率比下麵的要低for(col=0;col<6;col++){for(row=0;row<100;row++){sum=sum+array[row][col];}}建議3:如果迴圈記憶體在邏輯判斷,且迴圈次數很大,宜將邏輯判斷移到循環體的外面.例如:for(i=0;i<N;i++){if(condition)dosomething();elsedootherthing();}//N很大時效率低應該這樣寫會好些:if(condition){for(i=0;i<N;i++)dosomething();}else{for(i=0;i<N;i++)dootherthing();}如果N很小,兩者效率差別不明顯,第一種寫法比較好,因為程式更簡潔.五、C++常量常量是一種識別字,它的值在運行期間恒定不變。常用常量有字面常量、符號常量、布爾常量和枚舉常量等。1、字面常量使用最多的常量。字面常量只能引用,不能修改。所以語言實現一般把它保存在程式的符號表裏。2、符號常量

有2種:一個是用#define定義的宏常量,另一個用const定義的常量.提示1:由於#define是預編譯偽指令,它定義的宏常量在進入編譯階段前就已經替換為所代表的字面常量,本質上是字面常量.

你可以取一個const符號常量的地址。

對於基本數據類型的const常量,編譯器會重新在內存中創建它的一個拷貝,你通過其地址訪問的是它的拷貝而非原始符號常量。

對於構造類型的const常量,它成為編譯時不允許修改的變數,如果你能繞過編譯器的靜態類型安全檢查機制,就可以在運行時修改其記憶體單元。例1:constlongm=10;//定義常量long*p=(long*)&m;//取常量地址*p=1000;//迂回修改

cout<<*p<<endl;//1000cout<<m<<endl;//10,原始常量沒有變例2:

classsample{public:longm;};constsamples;s.m=10;sample*p=(sample*)&s;p->m=1000;cout<<s.m<<endl;//迂回修改成功

cout<<p->m<<endl;提示2:理論上說,只要你手中握有一個對象指針,你就可以設法繞過編譯器隨意修改它的內容,除非該記憶體受到操作系統保護。,也就是說C++沒有提供對指針有效性的靜態檢查。【規則1】需要對外公開的常量放在頭檔中,不需要對外公開的常量放在定義檔的頭部。為便於管理,可以把不同模組的常量集中存放在一個公共的頭檔中。【規則2】如果某一常量與其它常量密切相關,應在定義中包含這種關係,而不應給出一些孤立的值。例如:constfloatRADIUS=100;constfloatDIAMETER=RADIUS*2;3、const與#define的比較

C++語言可以用const來定義常量,也可以用#define來定義常量。但是前者比後者有更多的優點:(1)、const常量有數據類型,而宏常量沒有數據類型。編譯器可以對前者進行類型安全檢查。而對後者只進行字元替換,沒有類型安全檢查,並且在字元替換可能會產生意料不到的錯誤(邊際效應)。(2)、有些集成化的調試工具可以對const常量進行調試,但是不能對宏常量進行調試。【規則1】在C++程式中儘量使用const常量來定義符號常量。【規則2】const是constant的縮寫,有恆定不變的意思,被const修飾的變數受到C++語言實現的靜態類型安全檢查機制的強制保護,可以預防意外修改,提高健壯性4、類中的常量

有時我們希望某些常量只在類中有效。由於#define定義的宏常量是全局的,不能達到目的,於是想當然地覺得應該用const修飾數據成員來實現。const數據成員的確是存在的,但其含義卻不是我們所期望的。const數據成員只在某個對象生存期內是常量,而對於整個類而言卻是可變的,因為類可以創建多個對象,不同的對象其const數據成員的值可以不同。

不能在類聲明中初始化const數據成員。以下用法是錯誤的,因為類的對象未創建時,編譯器不知道SIZE的值是什麼。classA {…//錯誤,企圖在類聲明中constintSIZE=100;//初始化const數據成員intarray[SIZE]; //錯誤,未知的SIZE };const數據成員的初始化只能在類構造函數的初始化表中進行,例如

classA {… A(intsize); //構造函數

constintSIZE; };

A::A(intsize):SIZE(size) {//構造函數的初始化表

… } Aa(100); //對象a的SIZE值為100 Ab(200); //對象b的SIZE值為200

怎樣才能建立在整個類中都恒定的常量呢?別指望const數據成員了,應該用類中的枚舉常量來實現。例如classA {…enum{SIZE1=100,SIZE2=200};//枚舉常量intarray1[SIZE1]; intarray2[SIZE2]; };

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

C++編譯預處理

編譯預處理器對編譯偽指令進行處理生成中間檔作為編譯器的輸入。編譯偽指令一般以#打頭。它不是C++語句,可以出現在程式的任何地方。

常用的預編譯偽指令有檔包含、宏定義、條件編譯等。一、檔包含#include偽指令用於包含一個頭檔,預編譯器掃描到該偽指令後就用對應的文本內容替換它。常用有2種語法形式:#include<頭檔案名>#include“頭檔案名”使用該偽指令時,頭檔前面可以加路徑(相對路徑或者絕對路徑)。例如:#include“.\include\my.h”#include“d:\proj\test\sour\include\a.h”二、宏定義

可以使用#define(或#undef)偽指令來定義(或取消)宏。有不帶參數的宏和帶參數的宏。宏在它定義後面的任何地方都可以引用。宏的特點和注意事項如下:1、宏定義不是C++語句,因此不需要使用語句結束符“;”,否則它也被看作宏體的一部分。如:#defineout(word)cout<<#word<<endl;使用:out(verygood!)替換結果:cout<<“verygood!”<<endl;2、宏不可調試,即使替換後出現語法錯誤,編譯器也會將錯誤定位到根源程式中而不是定位到具體的某個宏定義。3、帶參數的宏體和各個形參應該分別用括弧括起來,以免造成意想不到的錯誤。#definesquare(x)x*x//不好#definesquare(x)((x)*(x))4、不要在引用宏定義的參數列表中使用增量和減量運算符,否則將導致變數的多次求值。如:inta=5;intx=spuare(a++);其結果不是期望的25而是30。5、帶參數的宏定義不是函數,沒有調用函數的開銷,但其每一次擴展都會生成重複的代碼,結果可使執行代碼的體積增大。6、inline函數不可能完全取代宏,各有各的好處,用宏構造一些重複的、數據和函數混合的、功能較特殊的代碼段的時候,其優點就顯示出來了。例:#definePi3.1415926#definecircle(r,L,S,V)L=2*Pi*r;\S=Pi*r*r;V=4.0/3.0*Pi*r*r*r調用:floatr=10.0,l,s,v;circle(r,l,s,v);7、當我們不再使用某一個宏時,可以用#undef來取消其定義。簡單地刪除宏定義會帶來許多編譯錯誤。如#undefPi#undefcircle(r,L,S,V)

建議:不要定義很複雜的宏,應該簡單而清晰。不要使用宏定義來定義新的類型名,應該使用typedef,否則容易造成錯誤。三、條件編譯條件編譯為程式的移植和調試帶來很大的方便。條件編譯可以出現在程式代碼的任何地方。1、#if、#elif和#else

可以用來暫時放棄編譯一段代碼。如:#if0……/*……*///希望禁止編譯的代碼段……/*……*///希望禁止編譯的代碼段#endif如果要使這段代碼生效,只要把0該位1。2、#ifdef和#ifndef#ifdefxyz等價於#ifdefined(xyz)此處xyz稱為調試宏。如果前面曾經用#define定義過宏xyz,那麼#ifdefxyz條件為真,否則條件為假。如:#define

xyz

//如果不想讓……

//Dosomething();語句#ifdefxyz//被編譯,那麼刪除Dosomething();//#define

xyz,或者#endif//在其後用#undefxyz取消該宏。#ifndefxyz預編譯偽指令等價於#if!defined(xyz)。為了防止頭檔被重複引用,應當用ifndef/define/endif結構產生預處理塊,這是C++約定俗成做法。3、#error

編譯偽指令#error用於輸出與平臺、環境等有關的資訊。#ifndefwin32#errorERROR:OnlyWin32plattformsurpported!#endif當預處理器發現應用程式中沒有定義宏win32時,那麼把#error後面的字元序列輸出到螢幕後就終止。程式不會進入編譯階段4、#pragma

編譯偽指令#pragma用於執行語言實現所定義的動作。VC++中使用比較複雜。#pragma(push,8)

//對象成員對齊8位元組#pragmawarning(disable:4069)

//不要產生第4069號編譯警告。#pragmacomment(lib,“user32.lib”)//當鏈接時在當前目錄下搜索庫檔user32.lib,找不到再在系統目錄下找。具體參看語言的幫助檔。5、#和##運算符

(1)構串操作符#只能修飾帶參數的宏的形參,它將實參的字元序列(而不是實參代表的值)轉換成字串常量。如定義宏#defineSTRING(x)#x#x#x#defineTEXT(x)“class”#x#”info”那麼引用宏:intabc=100;//儘管abc是整型量STRING(abc);

//結果abcabcabcTEXT(abc);

//結果classabcinfo(2)合併操作符##將出現在其左右的字元序列合併成一個識別字。注意不是字串。如:#definePTR(name)typedefname*P\##name;#defineREF(name)typedefname&R\##name;#defineDEF(name)classname;\PTR(name)\REF(name)有:DEF(TImage)

則展開後為classTImage;typedefTImage*PTImage;typedefTImage&RTImage;注意:使用合併操作符##時,產生的識別字必須預先定義,否則編譯器會報“識別字未定義”的編譯錯誤。面試實例:

美國某著名網路開發公司:

1、有2個變數a和b,不用if、?:switch或其他判斷語句,找出兩個數中比較大的數。

答案:intmax=((a+b)+abs(a-b))/2;

2、如何将变量a和b的值進行交換,並且不使用任何中間變數?

答案:a=a+b;b=a-b;a=a-b;

缺点:a和b比較大時,a+b會超界。

用異或就比較好:

a=a^b;b=a^b;a=a^b;函數原型是為了編譯器對函數調用語句執行靜態類型安全檢查,即檢查實參是否與形參的個數、類型、及順序匹配。函數原型的格式如下:[作用域]返回類型函數名(類型1[形參名1],類型2[形參名2],類型3[形參名3],……);形參:在函數原型或定義及catch語句的參數列表中被聲明的對象或指針、宏定義中的參數、範本定義中的類型參數等。實參:函數調用語句中以逗號分隔的參數列表中的運算式、throw語句的運算元等。二、函數的堆疊

一般函數都支持三種調用方式:過程調用、嵌套調用及遞歸調用。函數堆疊實際上使用的是程式的堆疊段記憶體空間。雖然程式的堆疊段是系統為程式分配的一種靜態數據區,函數堆疊是在調用它的時候才動態分配的。這是因為:無法在編譯時確定一個函數運行時所需的堆疊大小。如果在函數調用完後還要為它保留堆疊,則會很浪費記憶體空間。

堆疊是自動管理的。也就是說局部變數的創建和銷毀,堆疊的釋放都是函數自動完成的,不需要程式員干涉。

函數堆疊重要有三個用途:進入函數前保存環境變數和返回地址;進入函數時保存實參的拷貝;在函數體內保存局部變數。函數調用規範:

函數調用規範決定了函數調用的實參壓入堆疊、退出堆疊及堆疊釋放的方式。Windows環境下常用的調用規範有:1、_cdecl:這是C++函數默認的調用規範,參數從右向左依次傳遞並壓入堆疊,由調用函數負責堆疊的清退,因此這種方式有利於傳遞個數可變的參數給被調用函數。如printf()函數。2、_stdcall:這是WinAPI函數使用的調用規範。參數從右向左依次傳遞並壓入堆疊,

由被調用函數負責堆疊的清退。該規範生成的函數代碼比_cdecl更小,但當函數有可變個數的參數時會轉為_cdecl規範。在Windows中,宏WINAPI、CALLBACK都定義為_stdcall。3、_thiscall:C++非靜態成員函數的默認調用規範,不能使用個數可變的參數。當調用非靜態成員函數的時候,this指針直接保存在ECX寄存器中而非壓入函數堆疊。其他方面與_stdcall相同。4、_fastcall:該規範所修飾的函數的實參將被直接傳遞到CPU寄存器而不是記憶體堆疊中(這就是”快速調用”的含義)。堆疊的清退由被調用函數負責。該規範不能用於成員函數。

一般情況下我們不需要顯式地指定函數的調用規範。三、參數的傳遞規則:函數是C++程式的基本功能單元,其重要性不言而喻。函數設計的細微缺點很容易導致該函數被錯用,所以光使函數的功能正確是不夠的。這裏重點論述函數介面設計和內部實現的規則。函數介面的兩個要素是參數和返回值。C++語言中,函數的參數和返回值的傳遞方式有三種:值傳遞(passbyvalue)、指針傳遞(passbypointer)和引用傳遞(passbyreference)。【規則1】參數的書寫要完整,不要貪圖省事只寫參數的類型而省略參數名字。如果函數沒有參數,則用void填充。例如:voidSetValue(intw,inth); //良好的風格

voidSetValue(int,int);//不良的風格floatGetValue(void); //良好的風格floatGetValue(); //不良的風格【規則2】參數命名要恰當,輸入參數和輸出參數的順序要合理。例如編寫字串拷貝函數StringCopy,它有兩個參數。如果把參數名字起為str1和str2,則voidStringCopy(char*str1,char*str2);我們很難搞清楚究竟是把str1拷貝到str2中,還是剛好倒過來。可以把參數名字起得更有意義,如叫strSource和strDestination。這樣從名字上就可以看出應該把strSource拷貝到strDestination。還有一個問題,這兩個參數那一個放在前那一個放在後?參數的順序要遵循程式員的習慣。一般地,應將目的參數放在前面,源參數放在後面。如voidStringCopy(char*strSource,char*strDestination);//這樣不好別人在使用時可能會不假思索地寫成如下形式:charstr[20];StringCopy(str,“HelloWorld”);//參數順序顛倒【規則3】如果參數是指針,且僅作輸入用,則應在類型前加const,以防止該指針在函數體內被意外修改。例如:voidStringCopy(char*strDestination,constchar*strSource);

如果輸入參數以值傳遞的方式傳遞對象,則宜改用“const&”方式來傳遞,這樣可以省去臨時對象的構造和析構過程,從而提高效率。【建議】避免函數有太多的參數,參數個數儘量控制在5個以內。如果參數太多,在使用時容易將參數類型或順序搞錯。此時可以將這些參數封裝為一個對象並儘量採用地址傳遞或引用傳遞。

儘量不要使用類型和數目不確定的參數列表。C標準庫函數printf是採用不確定參數的典型代表,其原型為:intprintf(constchat*format[,argument]…);這種風格的函數在編譯時喪失了嚴格的靜態類型安全檢查。四、返回值的規則:【規則1】不要省略返回值的類型。C語言中,凡不加類型說明的函數,一律自動按整型處理。這樣做不會有什麼好處,卻容易被誤解為void類型。C++語言有很嚴格的類型安全檢查,不允許上述情況發生。由於C++程式可以調用C函數,為了避免混亂,規定任何C++/C函數都必須有類型。如果函數沒有返回值,那麼應聲明為void類型。【規則2】函數名字與返回值類型在語義上不可衝突。違反這條規則的典型代表是C標準庫函數getchar。如:charc;c=getchar();if(c==EOF)…按照getchar名字的意思,將變數c聲明為char類型是很自然的事情。但不幸的是getchar的確不是char類型,而是int類型,其原型如下:

intgetchar(void);由於c是char類型,取值範圍是[-128,127],如果宏EOF的值在char的取值範圍之外,那麼if語句將總是失敗,這種“危險”人們一般哪里料得到!導致本例錯誤的責任並不在用戶,是函數getchar誤導了使用者。【建議1】不要將正常值和錯誤標誌混在一起返回。正常值用輸出參數獲得,而錯誤標誌用return語句返回。

C標準庫函數為什麼要將getchar聲明為令人迷糊的int類型呢?在正常情況下,getchar的確返回單個字元。但如果getchar碰到檔結束標誌或發生讀錯誤,它必須返回一個標誌EOF。為了區別於正常的字元,只好將EOF定義為負數(通常為負1)。因此函數getchar就成了int類型。為了避免出現誤解,我們應該將正常值和錯誤標誌分開。即:正常值用輸出參數獲得,而錯誤標誌用return語句返回。函數getchar可以寫成BOOLGetChar(char*c);【建議2】有時候函數原本不需要返回值,但為了增加靈活性如支持鏈式表達,可以附加返回值。例如字串拷貝函數strcpy的原型:char*strcpy(char*strDest,constchar*strSrc);strcpy函數將strSrc拷貝至輸出參數strDest中,同時函數的返回值又是strDest。這樣做並非多此一舉,可以獲得如下靈活性:charstr[20];intlength=strlen(strcpy(str,“HelloWorld”));

【建議3】如果函數的返回值是一個對象,有些場合用“引用傳遞”替換“值傳遞”可以提高效率。而有些場合只能用“值傳遞”而不能用“引用傳遞”,否則會出錯。例如:classString{…String&operate=(constString&other);//賦值函數//相加函數,如果沒有friend修飾則只許有一個右側參數friendStringoperate+(constString&s1,constString&s2);private:char*m_data;}String的賦值函數operate=的實現如下:String&String::operate=(constString&other){ if(this==&other) return*this; deletem_data; m_data=newchar[strlen(other.data)+1]; strcpy(m_data,other.data); return*this; //返回的是*this的引用,無需拷貝過程}

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

String的相加函數operate+的實現如下:Stringoperate+(constString&s1,constString&s2){ Stringtemp; deletetemp.data; //temp.data是僅含‘\0’的字串

temp.data=newchar[strlen(s1.data)+strlen(s2.data)+1]; strcpy(temp.data,s1.data); strcat(temp.data,s2.data); returntemp; }

對於相加函數,應當用“值傳遞”的方式返回String對象。如果改用“引用傳遞”,那麼函數返回值是一個指向局部對象temp的“引用”。由於temp在函數結束時被自動銷毀,將導致返回的“引用”無效。五、函數內部實現的規則:不同功能的函數其內部實現各不相同,看起來似乎無法就“內部實現”達成一致的觀點。但根據經驗,我們可以在函數體的“入口處”和“出口處”從嚴把關,從而提高函數的品質。【規則1】在函數體的“入口處”,對參數的有效性進行檢查。很多程式錯誤是由非法參數引起的,我們應該充分理解並正確使用“斷言”(assert)來防止此類錯誤。

程式一般分為Debug版本和Release版本,Debug版本用於內部調試,Release版本發行給用戶使用。斷言assert是僅在Debug版本起作用的宏,它用於檢查“不應該”發生的情況。在運行過程中,如果assert的參數為假,那麼程式就會中止(一般地還會出現提示對話,說明在什麼地方引發了assert)。如果程式在assert處終止了,並不是說含有該assert的函數有錯誤,而是調用者出了差錯,assert可以幫助我們找到發生錯誤的原因。

void*memcpy(void*pvTo,constvoid*pvFrom,size_tsize){assert((pvTo!=NULL)&&(pvFrom!=NULL)); //使用斷言,指針為空退出。byte*pbTo=(byte*)pvTo; //防止改變pvTo的地址byte*pbFrom=(byte*)pvFrom; //防止改變pvFrom的地址

while(size-->0) *pbTo++=*pbFrom++; returnpvTo;}【規則2】在函數體的“出口處”,對return語句的正確性和效率進行檢查。 如果函數有返回值,那麼函數的“出口處”是return語句。如果return語句寫得不好,函數要麼出錯,要麼效率低下。注意事項如下:(1)return語句不可返回指向“棧記憶體”的“指針”或者“引用”,因為該記憶體在函數體結束時被自動銷毀。例如char*Func(void) {charstr[]=“helloworld”;//str的記憶體位於棧上

…returnstr; //將導致錯誤

}(2)要搞清楚返回的究竟是“值”、“指針”還是“引用”。(3)如果函數返回值是一個對象,要考慮return語句的效率。例如

returnString(s1+s2);這是臨時對象的語法,表示“創建一個臨時對象並返回它”。不要以為它與“先創建一個局部對象temp並返回它的結果”是等價的,如Stringtemp(s1+s2);returntemp;上述代碼將發生三件事。首先temp對象被創建,同時完成初始化;然後拷貝構造函數把temp拷貝到保存返回值的外部存儲單元中;最後temp在函數結束時被銷毀(調用析構函數)。而“創建一個臨時對象並返回它”的過程是不同的,編譯器直接把臨時對象創建並初始化在外部存儲單元中,省去了拷貝和析構的花費,提高了效率。類似地,我們不要將returnint(x+y); //創建一個臨時變數並返回它寫成inttemp=x+y;returntemp;由於內部數據類型如int,float,double的變數不存在構造函數與析構函數,雖然該“臨時變數的語法”不會提高多少效率,但是程式更加簡潔易讀。五、其他建議:【建議1】函數的功能要單一,不要設計多用途的函數。【建議2】函數體規模要小,儘量控制在50行代碼之內。【建議3】儘量避免函數帶有“記憶”功能。相同的輸入應當產生相同的輸出。帶有“記憶”功能的函數,其行為可能是不可預測的,因為它的行為可能取決於某種“記憶狀態”。這樣的函數既不易理解又不利於測試和維護。在C++語言中,函數的static局部變數是函數的“

温馨提示

  • 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
  • 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
  • 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
  • 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
  • 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
  • 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
  • 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。

评论

0/150

提交评论