《程序員面試寶典(第4版)》的筆記(作者: 歐立奇; 劉洋; 段韜)

程序員面試寶典(第4版)

歐立奇; 劉洋; 段韜
2.1 簡歷注意事項
2016-05-31
簡歷要儘量短。我們做過一個計算,一份中文簡歷壓縮在2頁左右就可以把所有的內容突出了。清楚、完整地把你的經歷和取得的成績表現出來。不要壓縮版面,不要把字體縮小到別人難以閱讀的程度。當你寫履歷時,試着問自己:“這些陳述會讓我得到面試的機會嗎?”然後,僅僅保留那些回答“是”的信息。
2016-05-31
求職簡歷一定要按照實際情況填寫,任何虛假的內容都不要寫。即使有的人靠含有水分的簡歷得到面試的機會,面試時也會露出馬腳的。千萬不要爲了得到一次面試機會就編寫虛假簡歷。被招聘方發現後,你幾乎就再也沒有機會進入這家公司了。而且對於應屆生來說,出現這種情況後,還有可能影響到同校的其他同學。
2016-05-31
求職簡歷上一定要註明求職的職位。每份簡歷都要根據你所申請的職位來設計,突出你在這方面的優點,不能把自己說成是一個全才,任何職位都適合。不要只准備一份簡歷,要根據工作性質有側重地表現自己。如果你認爲一家單位有兩個職位都適合你,可以向該單位同時投兩份簡歷。
2016-05-31
作爲求職的開始,我們要編寫一份或者幾份有針對性的簡歷,也就是按照對方的要求突出自己相關的經歷。只要你的優勢與招聘方的需要吻合,並且比其他應聘者突出的話,你就勝利了。
2016-05-31
簡歷上寫上對工資的要求要冒很大的風險,最好不寫。如果薪水要求太高,會讓企業感覺僱不起你;如果要求太低,會讓企業感覺你無足輕重。對於剛出校門的大學生來說,第一份工作的薪水不重要,不要在這方面費太多腦筋。
9.不要寫太多個人情況不要把個人資料寫得非常詳細,姓名、電話是必需的,出生年月可有可無。如果應聘國家機關、事業單位,應該寫政治面貌。如果到外企求職,這一項也可省去,其他都可不寫。
10.不要用怪字怪體我見過一份簡歷,用中空字體,還有斜體字。這些都是很忌諱的。試想一個HR挑了一天的簡歷,很累了,還要歪着頭看你的簡歷。你想你的勝算能有多大?其實用簡單的宋體5號字就很好了,不用標新立異。
11.仔細措辭體現個人業績有時候,簡歷上的幾個字就足以“激怒”HR,使其停止閱讀你的簡歷。招聘經理與招聘人員心中都有一張討厭措辭表。他們厭煩那些在簡歷中儘可能多地堆砌動詞、形容詞或副詞的人。“非常出色”、“做出很大的貢獻”這些措詞都是不合適的。最好能夠改成“我完成了多少銷售業績,聯繫了多少家公司”。如果數字過於敏感不適宜表達,可以用百分比或企業的表彰來表達,還可以寫上獲得的證書。
2.2 簡歷模板
2016-06-01
下面是一份簡歷模板。
求職簡歷個人介紹:
姓名:柯小平性別:男
出生日期:1985/07/03學校及專業:西北大學計算機系軟件與理論專業學歷:碩士
移動電話:13096964884電子郵件:[email protected]
IT &&英語技能:1.軟件結構設計,需求分析能力
2.精通C/C++、C#,精通SQL3.熟悉Windows開發平臺,精通.NET體系
4.熟悉Delphi開發工具,熟悉UML統一建模語言5.深入理解面向對象的思想,並能熟練地應用於具體的軟件設計開發工作中
6.英語水平:國家六級項目經驗(近期):
2010/1—2010/7與實驗室人員合作,在基於ASP.NET+SQL 2005的平臺下開發西北大學網絡選課系統程序。負責幫助客戶現場鑑定並解決有關網絡技術及安全問題,保證客戶網絡暢通;整體安全服務解決方案項目的設計;網絡培訓課程的開發、設計;學校管理信息技術客戶培訓。
2009/9—2010/1與實驗室人員合作,在基於Delphi+SQL 2005的平臺下開發西北大學人事管理系統程序。該項目是西北大學基金項目,目的爲完成西北大學教職員工信息的統一規範化管理。系統分爲教師科、勞資科、人事科、人才交流中心等幾部分,實現了各個部門之間的信息統一化協調管理。系統由Delphi、Power Designer、MS SQL開發完成。
工作經驗:2009/9—2009/12
北大青鳥ACCP培訓師2007/7—至今
就讀於西北大學計算機系,在校期間,與實驗室小組合作完成網上選課系統(ASP.NET+SQL 2000)和人事管理系統(Delphi+SQL 2000)的研發工作,以及教務管理系統(PowerBuilder 8+SQL 2000)的測試工作。應聘西北工業大學計算機三級網絡任課老師,教授國家計算機三級相關內容。
獎學金:中國石油獎學金優秀學生一等獎。
其他特長:文學和美術功底較好,擅長網頁製作,Photoshop和Dreamweaver水平較好。擅長表述,能夠勝任教學工作。
個人評價:我無法掩飾對這份工作的渴望——一份有科研挑戰的職位。
我一向認爲理想分爲兩類,一類是實現自己的理想,另一類理想則通過自身得到實現。理想之於我則兩者兼而有之,並稍稍傾向後者。作爲老師,我喜歡傳道、授業、解惑,形成一套自己的理論並潛移默化我的學生;同樣,作爲科研工作者,我也被C++的華貴多彩而吸引,那是真正的邏輯之美。此外,很多時候爲了項目的完滿,必須具備一種不破樓蘭終不還的決心和不積跬步無以至千里的恆心。最後,我謙和、謹慎,富於團隊精神。希望您能給我這樣一個機會展示自己。謝謝。
柯小平2010.7.7
3.2 電話面試
2016-06-01
由於模式的限制,電話面試時間不會很長。在這個環節中,一定要表現得自信、禮貌、認真、嚴肅,這樣會在聲音上給對方一個良好的印象。如果聲音慵懶、語氣生硬,除非是技術題目及英文方面表現得足夠好,否則很難予以平衡。
3.3 面試
2016-06-01
應聘初級職位,會針對你的編程能力和以往的項目經驗進行重點的考查。如果面試官針對你做的某個項目反覆提問,那麼你就需要注意了,要麼面試官在這個方面特別精通,要麼就是未來的職位需要用到這方面的技術。我們應該抱着一種誠懇的態度來回答,對熟悉的技術點可以詳細闡述,對於不熟悉的部分可以誠實地告訴面試官,千萬不要不懂裝懂。不過,我們認爲可以引導與面試官的談話,把他儘量引導到我們所擅長的領域。在SPSS公司面試時,在回答完面試官單鏈表逆置和複製構造函數問題之後,我把話題引入了我所擅長的設計模式方面,這是一種談話的藝術。
應聘中級職位,不但會考查代碼編寫,而且會對軟件架構或相關行業知識方面進行考查。代碼編寫方面,主要以考查某種編程技巧來判斷你對代碼的駕馭能力。比如某國際知名軟件公司經常會讓面試者編寫malloc或atoi函數。越是簡單的函數越能考驗應聘者的編碼能力。你不但要實現功能,而且還要對可能出現的錯誤編寫防禦性代碼,這些經驗都需要在實際編程過程中積累。應聘高級職位,應聘者肯定對技術或某個行業有相當程度的瞭解,這時主要是看你與職位的契合程度、企業文化的配比性(即將人力資源及成本配比作爲服務體系的重要組成部分,將公司企業文化中核心理念及價值觀作爲客戶服務的重要媒介)及整體感覺。應聘管理職位的話,考查的更多是管理技巧、溝通技巧和性格因素。架構師一般會考查行業背景與軟件架構方面的知識,比如UML或建模工具的使用等;技術專家的職位則會針對相關技術進行深度考查,而不會再考查一般性的編碼能力。
2016-06-01
建議準備一個日程本,記錄每一次宣講會、筆試和麪試的時間,這樣一旦公司打電話來預約面試,可以馬上查找日程本上的空閒時間,不至於發生時間上的衝突。每投一份簡歷,記錄下公司的職位和要求,如果一段時間以後(1個月或更長)有面試機會,可以翻出來看看,有所準備。根據不同的公司,準備不同的簡歷,千萬不要一概而論,不同的公司care(在意)的東西不一樣。每參加完一次筆試或面試,把題目回憶一下,覈對一下答案,不會做的題目更要好好弄懂。同學們之間信息共享,總有人有你沒有的信息。如果投了很多份簡歷,一點兒迴音都沒有,你得好好看看簡歷是否有問題,增加一些吸引HR眼球的東西。
3.4 簽約
2016-06-01
這是簽約前必然要談的部分。這裏面的內容非常多。待遇主要包括工資、獎金、補貼、福利、股票(期權)、保險、公積金。以下具體介紹各部分應注意的細節。● 工資:一定要問清楚是稅前還是稅後,這點不用多說。另外,還要問清楚,發多少個月。例如,稅前工資7000,發13個月,則年收入7000×13=91000。很多單位有年底雙薪,還有一些單位會發14~16個月不等。
● 獎金:很多單位的獎金佔收入的很大一部分。例如,聯想、百度、中航信都有季度獎、年終獎,另外還有項目獎,華爲也有項目獎、年終獎,瞬聯就沒有獎金。不同的單位情況不同,獎金的數額也不一樣,通常幾千至數萬不等,所以關於這一點,一定要問清楚,而且要問確定能拿到的獎金,取最低數。● 補貼:有些單位會有各種補貼,如通信補貼、住房補貼、伙食補貼等。例如,華爲有800~1000的餐補。有些單位的補貼加在一起非常可觀,也要問清楚。
● 福利:對於一些國企和事業單位來說,往往會有一些福利。例如,過節費、防暑降溫費、取暖費、購物券、電影票、生活用品,等等。● 股票:對於很多公司來說,股票是他們提供的非常有誘惑力的福利。一般來說,已經上市的公司提供股票的可能性不大,反倒是一些即將上市的公司提供股票的可能性很大。對此,一定要看準機遇,不要輕易錯過。
● 保險、公積金:即常說的“五險一金”。五險指的是養老保險,醫療保險,失業保險,人身意外傷害保險,生育保險,一金指的是住房公積金。這些是國家規定的,企業不得以任何理由拒絕爲你繳納,而且個人和企業出的比例是有規定的(但是也有一些企業不繳納公積金的例子)。這裏要注意的是繳費基數。很多單位在這上面做文章。例如,你的工資是5000,他們以2000爲繳費基數,也就是說,用它去乘固定的比例給你繳納五險一金,對此,一定要注意問清楚繳費基數。有些單位公積金比例上得非常高,所以你工資扣得也很多,那意味着公司交的錢更多,而一旦買房時,這些錢都是你自己的,所以,這部分收入不能忽視。此外,有些單位還會向你提供補充醫療保險、補充養老保險、補充意外保險、住房無息貸款或經濟適用房等,也要問清楚。把這些收入加起來,得到年收入。然後再考慮工作地的工資水平和消費水平。例如,年薪8萬在西安,無疑是比年薪10萬在上海要高多了。● 年假:即每年除了法定節假日之外可以休息的天數,這個自然是高校最多(有寒、暑假),研究所、外企可能會少一些,比如PPFORM公司一年是15~20天年假,30天探親假(不可以同時休);Nortel是第一年12天年假,然後每年遞增,直到21天爲止;華爲沒有年假,要靠每月最後一天週六加班來攢假期作爲自己的年假。不上班的時候覺得假期無足輕重,上了班就會覺得假期彌足珍貴。
5.3 編程風格
2016-06-02
C中printf計算參數時是從右到左壓棧的。
5.4 類型轉換
2016-06-02
C++定義了一組內置類型對象之間的標準轉換,在必要時它們被編譯器隱式地應用到對象上。
隱式類型轉換髮生在下列這些典型情況下。1.在混合類型的算術表達式中
在這種情況下最寬的數據類型成爲目標轉換類型,這也被稱爲算術轉換(Arithmetic Conversion),例如:2.用一種類型的表達式賦值給另一種類型的對象
在這種情況下目標轉換類型是被賦值對象的類型。例如在下面第一個賦值中文字常量0的類型是int。它被轉換成int*型的指針表示空地址。在第二個賦值中double型的值被截取成int型的值。3.把一個表達式傳遞給一個函數,調用表達式的類型與形式參數的類型不相同在這種情況下目標轉換類型是形式參數的類型。例如:
4.從一個函數返回一個表達式的類型與返回類型不相同在這種情況下返回的表達式類型自動轉換成函數類型。例如:
算術轉換保證了二元操作符,如加法或乘法的兩個操作數被提升爲共同的類型,然後再用它表示結果的類型。兩個通用的指導原則如下:(1)爲防止精度損失,如果必要的話,類型總是被提升爲較寬的類型。
(2)所有含有小於整型的有序類型的算術表達式在計算之前其類型都會被轉換成整型。規則的定義如上面所述,這些規則定義了一個類型轉換層次結構。我們從最寬的類型long double開始。
如果一個操作數的類型是long double,那麼另一個操作數無論是什麼類型都將被轉換成long doubless。例如在下面的表達式中,字符常量小寫字母a將被提升爲long double,它的ASC碼值爲97,然後再被加到long double型的文字常量上:如果兩個操作數都不是long double型,那麼若其中一個操作數的類型是double型,則另一個就將被轉換成double型。例如:
類似地,如果兩個操作數都不是double型而其中一個操作數是float型,則另一個被轉換成float型。例如:否則如果兩個操作數都不是3種浮點類型之一,它們一定是某種整值類型。在確定共同的目標提升類型之前,編譯器將在所有小於int的整值類型上施加一個被稱爲整值提升(integral promotion)的過程。在進行整值提升時類型char、signed char、unsigned char和short int都被提升爲類型int。如果機器上的類型空間足夠表示所有unsigned short型的值,這通常發生在short用半個字而int用一個字表示的情況下,則unsigned short int也被轉換成int,否則它會被提升爲unsigned int。wchar_t和枚舉類型被提升爲能夠表示其底層類型(underlying type)所有值的最小整數類型。例如已知如下枚舉類型:
相關聯的值是0和1。這兩個值可以但不是必須存放在char類型的表示中。當這些值實際上被作爲char類型來存儲時,char代表了枚舉的底層類型,然後status的整值提升將它的底層類型轉換爲int。在下列表達式中:
在確定兩個操作數被提升的公共類型之前,cval found和mval都被提升爲int類型。一旦整值提升執行完畢,類型比較就又一次開始。如果一個操作數是unsigned long型,則第二個也被轉換成unsigned long型。在上面的例子中所有被加到ulong上的3個對象都被提升爲unsigned long型。如果兩個操作數的類型都不是unsigned long而其中一個操作數是long型,則另一個也被轉換成long型。例如:
long類型的一般轉換有一個例外。如果一個操作數是long型而另一個是unsigned int型,那麼只有機器上的long型的長度足以存放unsigned int的所有值時(一般來說,在32位操作系統中long型和int型都用一個字長表示,所以不滿足這裏的假設條件),unsigned int纔會被轉換爲long型,否則兩個操作數都被提升爲unsigned long型。若兩個操作數都不是long型而其中一個是unsigned int型,則另一個也被轉換成unsigned int型,否則兩個操作數一定都是int型。儘管算術轉換的這些規則帶給你的困惑可能多於啓發,但是一般的思想是儘可能地保留多類型表達式中涉及的值的精度。這正是通過把不同的類型提升到當前出現的最寬的類型來實現的。
5.6 a、b交換與比較
2016-06-04
x&y是取相同的位與,這個的結果是x和y相同位的一半,x^y是取x和y的不同位,右移相當於除以2
5.7 C和C++的關係
2016-06-06
C++語言支持函數重載,C語言不支持函數重載。函數被C++編譯後在庫中的名字與C語言的不同。假設某個函數的原型爲void foo(int x, int y)。該函數被C編譯器編譯後在庫中的名字爲_foo,而C++編譯器則會產生像_foo_int_int之類的名字。
C++提供了C連接交換指定符號extern "C"解決名字匹配問題。
2016-06-06
C是一種結構化語言,重點在於算法和數據結構。C程序的設計首先考慮的是如何通過一個過程,對輸入(或環境條件)進行運算處理得到輸出(或實現過程(事務)控制)。而對於C++,首先考慮的是如何構造一個對象模型,讓這個模型能夠契合與之對應的問題域,這樣就可以通過獲取對象的狀態信息得到輸出或實現過程(事務)控制。
對於大規模數值運算,C/C++和Java/.NET之間沒有明顯的性能差異。不過,如果運算設計向量計算、矩陣運算,可以使用FORTRAN或者MATLAB編寫計算組件(如COM)。大規模用戶界面相關的軟件可以考慮使用.NET進行開發(Windows環境下),而且.NET同COM之間的互操作十分容易,同時.NET對數據庫訪問的支持也相當好。
6.2 const
2016-06-07
1)先看情況1。
如果const位於星號的左側,則const就是用來修飾指針所指向的變量,即指針指向爲常量;如果const位於星號的右側,const就是修飾指針本身,即指針本身是常量。
2016-06-07
const與#define相比有什麼不同?
答案:C++語言可以用const定義常量,也可以用#define定義常量,但是前者比後者有更多的優點:● const常量有數據類型,而宏常量沒有數據類型。編譯器可以對前者進行類型安全檢查,而對後者只進行字符替換,沒有類型安全檢查,並且在字符替換中可能會產生意料不到的錯誤(邊際效應)。
● 有些集成化的調試工具可以對const常量進行調試,但是不能對宏常量進行調試。在C++程序中只使用const常量而不使用宏常量,即const常量完全取代宏常量。
2016-06-07
默認const是外部連接的,C++默認const是內部連接的,這樣,如果在C++中想完成與C中同樣的事情,必須用extern把內部連接改成外部連接
6.3 sizeof
2016-06-07
在const成員函數中,用mutable修飾成員變量名後,就可以修改類的成員變量了。
2016-06-07
在VC中,我們可以用pack預處理指令來禁止對齊調整。例如,下面的代碼將使得結構尺寸更加緊湊,不會出現對齊到4字節問題:
對於這個pack指令的含義,大家可以查詢MSDN。請注意,除非你覺得必須,否則不要輕易做這樣的調整,因爲這將降低程序的性能。目前比較常見的用法有兩種,一是這個結構需要被直接寫入文件;二是這個結構需要通過網絡傳給其他程序。
2016-06-08
通過對sizeof與strlen的深入理解,得出兩者區別如下:
(1)sizeof操作符的結果類型是size_t,它在頭文件中的typedef爲unsigned int類型。該類型保證能容納實現所建立的最大對象的字節大小。(2)sizeof是運算符,strlen是函數。
(3)sizeof可以用類型做參數,strlen只能用char*做參數,且必須是以“\0”結尾的。sizeof還可以用函數做參數,比如:輸出的結果是sizeof(short),即2。
(4)數組做sizeof的參數不退化,傳遞給strlen就退化爲指針。(5)大部分編譯程序在編譯的時候就把sizeof計算過了,是類型或是變量的長度。這就是sizeof(x)可以用來定義數組維數的原因:
(6)strlen的結果要在運行的時候才能計算出來,用來計算字符串的長度,而不是類型佔內存的大小。(7)sizeof後如果是類型必須加括號,如果是變量名可以不加括號。這是因爲sizeof是個操作符而不是個函數。
(8)當使用了一個結構類型或變量時,sizeof返回實際的大小。當使用一靜態的空間數組時,sizeof返回全部數組的尺寸。sizeof操作符不能返回被動態分配的數組或外部的數組的尺寸。(9)數組作爲參數傳給函數時傳的是指針而不是數組,傳遞的是數組的首地址,如fun(char [8])、fun(char [])都等價於fun(char *)。在C++裏傳遞數組永遠都是傳遞指向數組首元素的指針,編譯器不知道數組的大小。如果想在函數內知道數組的大小,需要這樣做:進入函數後用memcpy將數組複製出來,長度由另一個形參傳進去。代碼如下:
(10)計算結構變量的大小就必須討論數據對齊問題。爲了使CPU存取的速度最快(這同CPU取數操作有關,詳細的介紹可以參考一些計算機原理方面的書),C++在處理數據時經常把結構變量中的成員的大小按照4或8的倍數計算,這就叫數據對齊(data alignment)。這樣做可能會浪費一些內存,但在理論上CPU速度快了。當然,這樣的設置會在讀寫一些別的應用程序生成的數據文件或交換數據時帶來不便。MS VC++中的對齊設定,有時候sizeof得到的與實際不等。一般在VC++中加上#pragma pack(n)的設定即可。或者如果要按字節存儲,而不進行數據對齊,可以在Options對話框中修改Advanced Compiler選項卡中的“Data Alignment”爲按字節對齊。(11)sizeof操作符不能用於函數類型、不完全類型或位字段。不完全類型指具有未知存儲大小數據的數據類型,如未知存儲大小的數組類型、未知內容的結構或聯合類型、void類型等。
6.4 內聯函數和宏定義
2016-06-08
內聯函數和宏的差別是什麼?
答案:內聯函數和普通函數相比可以加快程序運行的速度,因爲不需要中斷調用,在編譯的時候內聯函數可以直接被鑲嵌到目標代碼中。而宏只是一個簡單的替換。內聯函數要做參數類型檢查,這是內聯函數跟宏相比的優勢。
inline是指嵌入代碼,就是在調用函數的地方不是跳轉,而是把代碼直接寫到那裏去。對於短小的代碼來說inline增加空間消耗換來的是效率提高,這方面和宏是一模一樣的,但是inline在和宏相比沒有付出任何額外代價的情況下更安全。至於是否需要inline函數,就需要根據實際情況來取捨了。inline一般只用於如下情況:
(1)一個函數不斷被重複調用。(2)函數只有簡單的幾行,且函數內不包含for、while、switch語句。
2016-06-08
一般來說,我們寫小程序沒有必要定義成inline,但是如果要完成一個工程項目,當一個簡單函數被調用多次時,則應該考慮用inline。
宏在C語言裏極其重要,而在C++裏用得就少多了。關於宏的第一規則是絕不應該去使用它,除非你不得不這樣做。幾乎每個宏都表明了程序設計語言裏、程序裏或者程序員的一個缺陷,因爲它將在編譯器看到程序的正文之前重新擺佈這些正文。宏也是許多程序設計工具的主要麻煩。所以,如果你使用了宏,就應該準備只能從各種工具(如排錯系統、交叉引用系統、輪廓程序等)中得到較少的服務。宏是在代碼處不加任何驗證的簡單替代,而內聯函數是將代碼直接插入調用處,而減少了普通函數調用時的資源消耗。
宏不是函數,只是在編譯前(編譯預處理階段)將程序中有關字符串替換成宏體。關鍵字inline必須與函數定義體放在一起才能使函數成爲內聯,僅將inline放在函數聲明前面不起任何作用。如下風格的函數Foo不能成爲內聯函數:
而如下風格的函數Foo則成爲內聯函數:所以說,inline是一種“用於實現的關鍵字”,而不是一種“用於聲明的關鍵字”。內聯能提高函數的執行效率,至於爲什麼不把所有的函數都定義成內聯函數?如果所有的函數都是內聯函數,還用得着“內聯”這個關鍵字嗎?內聯是以代碼膨脹(複製)爲代價,僅僅省去了函數調用的開銷,從而提高函數的執行效率。如果執行函數體內代碼的時間,相比於函數調用的開銷較大,那麼效率的收穫會很少。另一方面,每一處內聯函數的調用都要複製代碼,將使程序的總代碼量增大,消耗更多的內存空間。
以下情況不宜使用內聯:1 如果函數體內的代碼比較長,使用內聯將導致內存消耗代價較高。2 如果函數體內出現循環,那麼執行函數體內代碼的時間要比函數調用的開銷大。類的構造函數和析構函數容易讓人誤解成使用內聯更有效。要當心構造函數和析構函數可能會隱藏一些行爲,如“偷偷地”執行了基類或成員對象的構造函數和析構函數。所以不要隨便地將構造函數和析構函數的定義體放在類聲明中。一個好的編譯器將會根據函數的定義體,自動地取消不值得的內聯(這進一步說明了inline不應該出現在函數的聲明中)。
7.1 指針基本問題
2016-06-12
指針和引用的差別?
答案:(1)非空區別。在任何情況下都不能使用指向空值的引用。一個引用必須總是指向某些對象。因此如果你使用一個變量並讓它指向一個對象,但是該變量在某些時候也可能不指向任何對象,這時你應該把變量聲明爲指針,因爲這樣你可以賦空值給該變量。相反,如果變量肯定指向一個對象,例如你的設計不允許變量爲空,這時你就可以把變量聲明爲引用。不存在指向空值的引用這個事實意味着使用引用的代碼效率比使用指針要高。
(2)合法性區別。在使用引用之前不需要測試它的合法性。相反,指針則應該總是被測試,防止其爲空。(3)可修改區別。指針與引用的另一個重要的區別是指針可以被重新賦值以指向另一個不同的對象。但是引用則總是指向在初始化時被指定的對象,以後不能改變,但是指定的對象其內容可以改變。
(4)應用區別。總的來說,在以下情況下應該使用指針:一是考慮到存在不指向任何對象的可能(在這種情況下,能夠設置指針爲空),二是需要能夠在不同的時刻指向不同的對象(在這種情況下,你能改變指針的指向)。如果總是指向一個對象並且一旦指向一個對象後就不會改變指向,那麼應該使用引用。
7.2 傳遞動態內存
2016-06-12
首先要搞清楚char *str和char str[]:
是分配一個局部數組:是分配一個全局數組:
局部數組是局部變量,它所對應的是內存中的棧。全局數組是全局變量,它所對應的是內存中的全局區域。字符串常量保存在只讀的數據段,而不是像全局變量那樣保存在普通數據段(靜態存儲區),如:c佔用一個存儲區域,但是局部區的數據是可以修改的:
這裏c不佔存儲空間。
7.5 迷途指針
2016-06-13
malloc與free是C++/C語言的標準庫函數,new/delete是C++的運算符。它們都可用於申請動態內存和釋放內存。
對於非內部數據類型的對象而言,只用malloc/free無法滿足動態對象的要求。對象在創建的同時要自動執行構造函數,對象在消亡之前要自動執行析構函數。由於malloc/free是庫函數而不是運算符,不在編譯器控制權限之內,不能夠把執行構造函數和析構函數的任務強加於malloc/free。
2016-06-13
因此C++語言需要一個能完成動態內存分配和初始化工作的運算符new,以及一個能完成清理與釋放內存工作的運算符delete。new/delete不是庫函數,而是運算符。
7.6 指針和句柄
2016-06-14
句柄是一個32位的整數,實際上是Windows在內存中維護的一個對象(窗口等)內存物理地址列表的整數索引。因爲Windows的內存管理經常會將當前空閒對象的內存釋放掉,當需要時訪問再重新提交到物理內存,所以對象的物理地址是變化的,不允許程序直接通過物理地址來訪問對象。程序將想訪問的對象的句柄傳遞給系統,系統根據句柄檢索自己維護的對象列表就能知道程序想訪問的對象及其物理地址了。
句柄是一種指向指針的指針。我們知道,所謂指針是一種內存地址。應用程序啓動後,組成這個程序的各對象是駐留在內存的。如果簡單地理解,似乎我們只要獲知這個內存的首地址,那麼就可以隨時用這個地址訪問對象。但是,如果真的這樣認爲,那麼就大錯特錯了。我們知道,Windows是一個以虛擬內存爲基礎的操作系統。在這種系統環境下,Windows內存管理器經常在內存中來回移動對象,以此來滿足各種應用程序的內存需要。對象被移動意味着它的地址變化了。如果地址總是如此變化,我們該到哪裏去找該對象呢?爲了解決這個問題,Windows操作系統爲各應用程序騰出一些內存地址,用來專門登記各應用對象在內存中的地址變化,而這個地址(存儲單元的位置)本身是不變的。Windows內存管理器移動對象在內存中的位置後,把對象新的地址告知這個句柄地址來保存。這樣我們只需記住這個句柄地址就可以間接地知道對象具體在內存中的哪個位置。這個地址是在對象裝載(Load)時由系統分配的,當系統卸載時(Unload)又釋放給系統。句柄地址(穩定)→記載着對象在內存中的地址→對象在內存中的地址(不穩定)→實際對象。但是,必須注意的是,程序每次重新啓動,系統不能保證分配給這個程序的句柄還是原來的那個句柄,而且絕大多數情況下的確不一樣。假如我們把進入電影院看電影看成是一個應用程序的啓動運行,那麼系統給應用程序分配的句柄總是不一樣,這和每次電影院售給我們的門票總是不同的座位是一樣的道理。HDC是設備描述表句柄。CDC是設備描述表類。用GetSafeHwnd和FromHandle可以互相轉換。
答案:句柄和指針其實是兩個截然不同的概念。Windows系統用句柄標記系統資源,隱藏系統的信息。你只要知道有這個東西,然後去調用就行了,它是個32bit的uint。指針則標記某個物理內存地址,兩者是不同的概念。
7.7 this指針
2016-06-14
關於This指針,有這樣一段描述:當你進入一個房子後,你可以看見桌子、椅子、地板等,但是房子你是看不到全貌了。對於一個類的實例來說,你可以看到它的成員函數、成員變量,但是實例本身呢?this指針是這樣一個指針,它時時刻刻指向這個實例本身。
this指針易混的幾個問題如下。(1)This指針本質是一個函數參數,只是編譯器隱藏起形式的,語法層面上的參數。
this只能在成員函數中使用,全局函數、靜態函數都不能使用this。實際上,成員函數默認第一個參數爲T* const this。
如:其中,func的原型在編譯器看來應該是:(2)this在成員函數的開始前構造,在成員的結束後清除。這個生命週期同任何一個函數的參數是一樣的,沒有任何區別。當調用一個類的成員函數時,編譯器將類的指針作爲函數的this參數傳遞進去。如:此處,編譯器將會編譯成:
看起來和靜態函數沒差別,不過,區別還是有的。編譯器通常會對this指針做一些優化,因此,this指針的傳遞效率比較高,如VC通常是通過ecx寄存器傳遞this參數的。
2016-06-14
(3)this指針並不佔用對象的空間。
this相當於非靜態成員函數的一個隱函的參數,不佔對象的空間。它跟對象之間沒有包含關係,只是當前調用函數的對象被它指向而已。所有成員函數的參數,不管是不是隱含的,都不會佔用對象的空間,只會佔用參數傳遞時的棧空間,或者直接佔用一個寄存器。
(4)this指針是什麼時候創建的?this在成員函數的開始執行前構造,在成員的執行結束後清除。
但是如果class或者struct裏面沒有方法的話,它們是沒有構造函數的,只能當做C的struct使用。採用TYPE xx的方式定義的話,在棧裏分配內存,這時候this指針的值就是這塊內存的地址。採用new方式創建對象的話,在堆裏分配內存,new操作符通過eax返回分配的地址,然後設置給指針變量。之後去調用構造函數(如果有構造函數的話),這時將這個內存塊的地址傳給ecx。(5)this指針存放在何處?堆、棧、還是其他?this指針會因編譯器不同而有不同的放置位置。可能是堆、棧,也可能是寄存器。
C++是一種靜態的語言,那麼對C++的分析應該從語法層面和實現層面兩個方面進行。語法上,this是個指向對象的“常指針”,因此無法改變。它是一個指向相應對象的指針。所有對象共用的成員函數利用這個指針區別不同變量,也就是說,this是“不同對象共享相同成員函數”的保證。
而在實際應用的時候,this應該是個寄存器參數。這個不是語言規定的,而是“調用約定”,C++的默認調用約定是__cdecl,也就是C風格的調用約定。該約定規定參數自右向左入棧,由調用方負責平衡堆棧。對於成員函數,將對象的指針(即this指針)存入ecx中(有的書將這一點單獨分開,叫做thiscall,但是這的確是cdecl的一部分)。因爲這只是一個調用約定,不是語言的組成部分,不同編譯器自然可以自由發揮。但是現在的主流編譯器都是這麼做的。(6)this指針是如何傳遞給類中的函數的?綁定?還是在函數參數的首參數就是this指針?那麼,this指針又是如何找到“類實例後函數”的?
大多數編譯器通過ecx寄存器傳遞this指針。事實上,這也是一個潛規則。一般來說,不同編譯器都會遵從一致的傳參規則,否則不同編譯器產生的obj就無法匹配了。(7)我們只有獲得一個對象後,才能通過對象使用this指針。如果我們知道一個對象this指針的位置,可以直接使用嗎?this指針只有在成員函數中才有定義。因此,你獲得一個對象後,也不能通過對象使用this指針。所以,我們無法知道一個對象的this指針的位置(只有在成員函數裏纔有this指針的位置)。當然,在成員函數裏,你是可以知道this指針的位置的(可以通過&this獲得),也可以直接使用它。
第9章 STL模板與容器
2016-06-15
STL有以下優點:
● 可以方便、容易地實現搜索數據或對數據排序等一系列的算法。● 調試程序時更加安全和方便。
● 即使是人們用STL在UNIX平臺下寫的代碼,也可以很容易地理解(因爲STL是跨平臺的)。STL中一些基礎概念的定義如下。
● 模板(Template):類(及結構等各種數據類型和函數)的宏(macro)。有時叫做甜餅切割機(cookie cutter),正規的名稱應叫做泛型(generic)。一個類的模板叫做泛型類(generic class),而一個函數的模板也自然而然地被叫做泛型函數(generic function)。● STL標準模板庫:一些聰明人寫的一些模板,現在已成爲每個人所使用的標準C++語言中的一部分。
● 容器(Container):可容納一些數據的模板類。STL中有vector、set、map、multimap和deque等容器。● 向量(Vector):基本數組模板,這是一個容器。
● 遊標(Iterator):這是一個奇特的東西,它是一個指針,用來指向STL容器中的元素,也可以指向其他的元素。
9.1 向量容器
2016-06-15
:C++的一個新特性就是採用了標準模板庫(STL)。所有主要編譯器銷售商現在都把標準模板庫作爲編譯器的一部分進行提供。標準模板庫是一個基於模板的容器類庫,包括鏈表、列表、隊列和堆棧。標準模板庫還包含許多常用的算法,包括排序和查找。
標準模板庫的目的是提供對常用需求重新開發的一種替代方法。標準模板庫已經經過測試和調試,具有很高的性能並且是免費的。最重要的是,標準模板庫是可重用的。當你知道如何使用一個標準模板庫的容器以後,就可以在所有的程序中使用它而不需要重新開發了。容器是包容其他對象的對象。標準C++庫提供了一系列的容器類,它們都是強有力的工具,可以幫助C++開發人員處理一些常見的編程任務。標準模板庫容器類有兩種類型,分別爲順序和關聯。順序容器可以提供對其成員的順序訪問和隨機訪問。關聯容器則經過優化關鍵值訪問它們的元素。標準模板庫在不同操作系統間是可移植的。所有標準模板庫容器類都在namespace std中定義。
9.2 泛型編程
2016-06-15
泛型編程是一種基於發現高效算法的最抽象表示的編程方法。也就是說,以算法爲起點並尋找能使其工作且有效率工作的最一般的必要條件集。令人驚訝的是,很多不同的算法都需要相同的必要條件集,並且這些必要條件有多種不同的實現方式。類似的事實在數學裏也可以看到。大多數不同的定理都依賴於同一套公理,並且對於同樣的公理存在多種不同的模型。泛型編程假定有某些基本的法則在支配軟件組件的行爲,並且基於這些法則有可能設計可互操作的模塊,甚至還有可以使用此法則去指導我們的軟件設計。STL就是一個泛型編程的例子。C++是我可以實現令人信服的例子的語言。
9.3 模板
2016-06-15
數據結構本身十分重要。當程序中存在着對時間要求很高的部分時,數據結構的選擇就顯得更加重要。
經典的數據結構數量有限,但是我們常常重複着一些爲了實現向量、鏈表等結構而編寫的代碼。這些代碼都十分相似,只是爲了適應不同數據的變化而在細節上有所不同。STL容器就爲我們提供了這樣的方便,它允許我們重複利用已有的實現構造自己的特定類型下的數據結構。通過設置一些模板類,STL容器對最常用的數據結構提供了支持。這些模板的參數允許我們指定容器中元素的數據類型,可以將許多重複而乏味的工作簡化。容器部分主要由頭文件<vector>、<list>、<deque>、<set>、<map>、<stack>和<queue>組成。對於常用的一些容器和容器適配器(可以看做由其他容器實現的容器),可以通過下表總結一下它們和相應頭文件的對應關係。
10.3 成員變量
2016-06-15
必須使用靜態成員變量在一個類的所有實例間共享數據。如果想限制對靜態成員變量的訪問,則必須把它們聲明爲保護型或私有型。不允許用靜態成員變量去存放某一個對象的數據。靜態成員數據是在這個類的所有對象間共享的。
10.4 構造函數和析構函數
2016-06-15
由於在生成CChild對象c時,實際上在調用CChild類的構造函數之前必須首先調用其基類CBase的構造函數,所以當撤銷c時,也會在調用CChild類析構函數之後,調用CBase類的析構函數(析構函數調用順序與構造函數相反)。也就是說,無論析構函數是不是虛函數,派生類對象被撤銷時,肯定會依次上調其基類的析構函數。
那麼爲什麼CObject類要搞一個虛的析構函數呢?因爲多態的存在。
仍以上面的代碼爲例,如果main()中有如下代碼:那麼在pBase指針被撤銷時,調用的是CBase的析構函數還是CChild的呢?顯然是CBase的(靜態聯編)析構函數。但如果把CBase類的析構函數改成virtual型,當pBase指針被撤銷時,就會先調用CChild類構造函數,再調用CBase類構造函數。
答案:在這個例子裏,所有對象都存在於棧框中,當離開其所處的作用域時,該對象會被自動撤銷,似乎看不出什麼大問題。但是試想,如果CChild類的構造函數在堆中分配了內存,而其析構函數又不是virtual型的,那麼撤銷pBase時,將不會調用CChild::~CChild(),從而不會釋放CChild::CChild()佔據的內存,造成內存泄露。將CObject的析構函數設爲virtual型,則所有CObject類的派生類的析構函數都將自動變爲virtual型,這保證了在任何情況下,不會出現由於析構函數未被調用而導致的內存泄露。這纔是MFC將CObject::~CObject()設爲virtual型的真正原因。
2016-06-15
虛函數採用一種虛調用的辦法。虛調用是一種可以在只有部分信息的情況下工作的機制,特別允許我們調用一個只知道接口而不知道其準確對象類型的函數。但是如果要創建一個對象,你勢必要知道對象的準確類型,因此構造函數不能爲虛。
2016-06-15
:如果虛函數是非常有效的,我們是否可以把每個函數都聲名爲虛函數?
答案:不行,這是因爲虛函數是有代價的:由於每個虛函數的對象都必須維護一個v表,因此在使用虛函數的時候都會產生一個系統開銷。如果僅是一個很小的類,且不想派生其他類,那麼根本沒必要使用虛函數。
10.6 多態的概念
2016-06-15
什麼是多態?
答案:開門,開窗戶,開電視。在這裏的“開”就是多態!
多態性可以簡單地概括爲“一個接口,多種方法”,在程序運行的過程中才決定調用的函數。多態性是面向對象編程領域的核心概念。多態(Polymorphisn),按字面的意思就是“多種形狀”。多態性是允許你將父對象設置成爲和它的一個或更多的子對象相等的技術,賦值之後,父對象就可以根據當前賦值給它的子對象的特性以不同的方式運作。簡單地說就是一句話,允許將子類類型的指針賦值給父類類型的指針。多態性在Object Pascal和C++中都是通過虛函數(Virtual Function)實現的。
2016-06-15
虛函數就是允許被其子類重新定義的成員函數。而子類重新定義父類虛函數的做法,稱爲“覆蓋”(override),或者稱爲“重寫”。這裏有一個初學者經常混淆的概念。上面說了覆蓋(override)和重載(overload)。覆蓋是指子類重新定義父類的虛函數的做法。而重載,是指允許存在多個同名函數,而這些函數的參數表不同(或許參數個數不同,或許參數類型不同,或許兩者都不同)。其實,重載的概念並不屬於“面向對象編程”。重載的實現是編譯器根據函數不同的參數表,對同名函數的名稱做修飾,然後這些同名函數就成了不同的函數(至少對於編譯器來說是這樣的)。如,有兩個同名函數function func(p:integer):integer;和function func(p:string):integer;。那麼編譯器做過修飾後的函數名稱可能是int_func,str_func。對於這兩個函數的調用,在編譯器間就已經確定了,是靜態的(記住:是靜態)。也就是說,它們的地址在編譯期就綁定了(早綁定),因此,重載和多態無關。真正與多態相關的是“覆蓋”。當子類重新定義了父類的虛函數後,父類指針根據賦給它的不同的子類指針,動態(記住:是動態)地調用屬於子類的該函數,這樣的函數調用在編譯期間是無法確定的(調用的子類的虛函數的地址無法給出)。因此,這樣的函數地址是在運行期綁定的(晚綁定)。結論就是重載只是一種語言特性,與多態無關,與面向對象也無關。
引用一句Bruce Eckel的話:“不要犯傻,如果它不是晚綁定,它就不是多態。”那麼,多態的作用是什麼呢?我們知道,封裝可以隱藏實現細節,使得代碼模塊化;繼承可以擴展已存在的代碼模塊(類);它們的目的都是爲了代碼重用。而多態則是爲了實現另一個目的—接口重用!而且現實往往是,要有效重用代碼很難,而真正最具有價值的重用是接口重用,因爲“接口是公司最有價值的資源。設計接口比用一堆類來實現這個接口更費時間,而且接口需要耗費更昂貴的人力和時間”。其實,繼承爲重用代碼而存在的理由已經越來越薄弱,因爲“組合”可以很好地取代繼承的擴展現有代碼的功能,而且“組合”的表現更好(至少可以防止“類爆炸”)。因此筆者個人認爲,繼承的存在很大程度上是作爲“多態”的基礎而非擴展現有代碼的方式。
10.7 友元
2016-06-15
類具有封裝和信息隱藏的特性。只有類的成員函數才能訪問類的私有成員,程序中的其他函數是無法訪問私有成員的。非成員函數可以訪問類中的公有成員,但是如果將數據成員都定義爲公有的,這又破壞了隱藏的特性。另外,應該看到在某些情況下,特別是在對某些成員函數多次調用時,由於參數傳遞、類型檢查和安全性檢查等都需要時間開銷,而影響程序的運行效率。
爲了解決上述問題,提出一種使用友元的方案。友元是一種定義在類外部的普通函數,但它需要在類體內進行說明,爲了與該類的成員函數加以區別,在說明時前面加以關鍵字friend。友元不是成員函數,但是它可以訪問類中的私有成員。友元的作用在於提高程序的運行效率,但是,它破壞了類的封裝性和隱藏性,使得非成員函數可以訪問類的私有成員。友元可以是一個函數,該函數被稱爲友元函數;友元也可以是一個類,該類被稱爲友元類。
11.2 私有繼承
2016-06-16
一個私有的或保護的派生類不是子類,因爲非公共的派生類不能做基類能做的所有的事。
2016-06-16
公有繼承(public)、私有繼承(private)和保護繼承(protected)是常用的3種繼承方式。
1.公有繼承方式基類成員對其對象的可見性與一般類及其對象的可見性相同,公有成員可見,其他成員不可見。這裏保護成員與私有成員相同。
基類成員對派生類的可見性對派生類來說,基類的公有成員和保護成員可見,基類的公有成員和保護成員作爲派生類的成員時,它們都保持原有的狀態;基類的私有成員不可見,基類的私有成員仍然是私有的,派生類不可訪問基類中的私有成員。基類成員對派生類對象的可見性對派生類對象來說,基類的公有成員是可見的,其他成員是不可見的。
所以,在公有繼承時,派生類的對象可以訪問基類中的公有成員,派生類的成員函數可以訪問基類中的公有成員和保護成員。2.私有繼承方式
基類成員對其對象的可見性與一般類及其對象的可見性相同,公有成員可見,其他成員不可見。基類成員對派生類的可見性對派生類來說,基類的公有成員和保護成員是可見的,基類的公有成員和保護成員都作爲派生類的私有成員,並且不能被這個派生類的子類所訪問;基類的私有成員是不可見的,派生類不可訪問基類中的私有成員。
基類成員對派生類對象的可見性對派生類對象來說,基類的所有成員都是不可見的。所以,在私有繼承時,基類的成員只能由直接派生類訪問,而無法再往下繼承。
3.保護繼承方式這種繼承方式與私有繼承方式的情況相同。兩者的區別僅在於對派生類的成員而言,基類成員對其對象的可見性與一般類及其對象的可見性相同,公有成員可見,其他成員不可見。
基類成員對派生類的可見性對派生類來說,基類的公有成員和保護成員是可見的,基類的公有成員和保護成員都作爲派生類的保護成員,並且不能被這個派生類的子類所訪問;基類的私有成員是不可見的,派生類不可訪問基類中的私有成員。基類成員對派生類對象的可見性對派生類對象來說,基類的所有成員都是不可見的。
所以,在保護繼承時,基類的成員也只能由直接派生類訪問,而無法再往下繼承。C++支持多重繼承,從而大大增強了面向對象程序設計的能力。多重繼承是一個類從多個基類派生而來的能力。派生類實際上獲取了所有基類的特性。當一個類是兩個或多個基類的派生類時,必須在派生類名和冒號之後,列出所有基類的類名,基類間用逗號隔開。派生類的構造函數必須激活所有基類的構造函數,並把相應的參數傳遞給它們。派生類可以是另一個類的基類,這樣,相當於形成了一個繼承鏈。當派生類的構造函數被激活時,它的所有基類的構造函數也都會被激活。在面向對象的程序設計中,繼承和多重繼承一般指公共繼承。在無繼承的類中,protected和private控制符是沒有差別的。在繼承中,基類的private對所有的外界都屏蔽(包括自己的派生類),基類的protected控制符對應用程序是屏蔽的,但對其派生類是可訪問的。
保護繼承和私有繼承只是在技術上討論時有其一席之地。
11.4 多重繼承
2016-06-16
多重繼承在語言上並沒有什麼很嚴重的問題,但是標準本身只對語義做了規定,而對編譯器的細節沒有做規定。所以在使用時(即使是繼承),最好不要對內存佈局等有什麼假設。此類的問題還有虛析構函數等。爲了避免由此帶來的複雜性,通常推薦使用複合。但是,在《C++設計新思維》(Andrei Alexandrescu)一書中對多重繼承和模板有極爲精彩的運用。
(1)多重繼承本身並沒有問題,如果運用得當可以收到事半功倍的效果。不過大多數系統的類層次往往有一個公共的基類,就像MFC中的Cobject,Java中的Object。而這樣的結構如果使用多重繼承,稍有不慎,將會出現一個嚴重現象——菱形繼承,這樣的繼承方式會使得類的訪問結構非常複雜。但並非不可處理,可以用virtual繼承(並非唯一的方法)及Loki庫中的多繼承框架來掩蓋這些複雜性。(2)從哲學上來說,C++多重繼承必須要存在,這個世界本來就不是單根的。從實際用途上來說,多重繼承不是必需的,但這個世界上有多少東西是必需的呢?對象不過是一組有意義的數據集合及其上的一組有意義的操作,虛函數(晚期綁定)也不過是一堆函數入口表,重載也不過是函數名擴展,這些東西都不是必需的,而且對它們的不當使用都會帶來問題。但是沒有這些東西行嗎?很顯然,不行。
(3)多重繼承在面向對象理論中並非是必要的——因爲它不提供新的語義,可以通過單繼承與複合結構來取代。而Java則放棄了多重繼承,使用簡單的interface取代。多重繼承是把雙刃劍,應該正確地對待。況且,它不像goto,不破壞面向對象語義。跟其他任何威力強大的東西一樣,用好了會帶來代碼的極大精簡,用壞了那就不用說了。C++是爲實用而設計的,在語言裏有很多東西存在着各種各樣的“缺陷”。所以,對於這種有“缺陷”的東西,它的優劣就要看使用它的人。C++不迴避問題,它只是把問題留給使用者,從而給大家更多的自由。像Ada、Pascal這類定義嚴格的語言,從語法上回避了問題,但並不是真正解決了問題,而使人做很多事時束手束腳(當然,習慣了就好了)。
(4)多重繼承本身並不複雜,對象佈局也不混亂,語言中都有明確的定義。真正複雜的是使用了運行時多態(virtual)的多重繼承(因爲語言對於多態的實現沒有明確的定義)。爲什麼非要說多重繼承不好呢?如果這樣的話,指針不是更容易出錯,運行時多態不是更不好理解嗎?因爲C++中沒有interface這個關鍵字,所以不存在所謂的“接口”技術。但是C++可以很輕鬆地做到這樣的模擬,因爲C++中的不定義屬性的抽象類就是接口。
(5)要了解C++,就要明白有很多概念是C++試圖考慮但是最終放棄的設計。你會發現很多Java、C#中的東西都是C++考慮後放棄的。不是說這些東西不好,而是在C++中它將破壞C++作爲一個整體的和諧性,或者C++並不需要這樣的東西。舉一個例子來說明,C#中有一個關鍵字base用來表示該類的父類,C++卻沒有對應的關鍵字。爲什麼沒有?其實C++中曾經有人提議用一個類似的關鍵字inherited,來表示被繼承的類,即父類。這樣一個好的建議爲什麼沒有被採納呢?這本書中說得很明確,因爲這樣的關鍵字既不必須又不充分。不必須是因爲C++有一個typedef * inherited,不充分是因爲有多個基類,你不可能知道inherited指的是哪個基類。很多其他語言中存在的時髦的東西在C++中都沒有,這之中有的是待改進的地方,有的是不需要,我們不能一概而論,需要具體問題具體分析。
2016-06-16
比如,C繼承自A和B,如果出現了相同的函數foo(),那麼C.A::foo(),C.B::foo()就分別代表從A類中繼承的foo函數和從B類中繼承的foo函數。
11.5 檢測並修改不適合的繼承
2016-06-16
如果不指定public,C++默認的是私有繼承。私有繼承是無法繼承並使用父類函數中的公有變量的。
11.6 純虛函數
2016-06-16
因爲Shape類中的Draw函數是一個純虛函數,所以Shape類是不能實例化一個對象的。Shape s1;是不可以的,解決方法是把Draw函數修改成一般的虛函數。
2016-06-16
C++中如何阻止一個類被實例化?
2.一般在什麼時候構造函數被聲明成private呢?3.什麼時候編譯器會生成默認的copy constructor呢?
4.如果你已經寫了一個構造函數,編譯器還會生成copy constructor嗎?[英國某著名計算機圖形圖像公司面試題]答案:
1.使用抽象類,或者構造函數被聲明成private。2.比如要阻止編譯器生成默認的copy constructor的時候。
3.只要自己沒寫,而程序中需要,都會生成。4.會生成。
11.7 運算符重載與RTTI
2016-06-16
C++引入的額外開銷體現在以下兩方面。
1.編譯時開銷模板、類層次結構、強類型檢查等新特性,以及大量使用了這些新特性的C++模板、算法庫都明顯地增加了C++編譯器的負擔。但是應當看到,這些新機能在不增加程序執行效率的前提下,明顯降低了廣大C++程序員的工作量。
2.運行時開銷運行時開銷恐怕是程序員最關心的問題之一了。相對於傳統C程序而言,C++中有可能引入額外運行時開銷特性包括:
● 虛基類。● 虛函數。
● RTTI(dynamic_cast和typeid)。● 異常。
● 對象的構造和析構。虛基類,從直接虛繼承的子類中訪問虛基類的數據成員或其虛函數時,將增加兩次指針引用(大部分情況下可以優化爲一次)和一次整型加法的時間開銷。定義一個虛基類表,定義若干虛基類表指針的空間開銷。虛函數的運行開銷有進行整型加法和指針引用的時間開銷。定義一個虛表,定義若干個(大部分情況下是一個)虛表指針的空間開銷。
RTTI的運行開銷主要有進行整型比較和取址操作(可能還會有一兩次整形加法)所增加的時間開銷。定義一個type_info對象(包括類型ID和類名稱)的空間開銷。"dynamic_cast"用於在類層次結構中漫遊,對指針或引用進行自由的向上、向下或交叉轉化。"typeid"則用於獲取一個對象或引用的確切類型。一般地講,能用虛函數解決的問題就不要用"dynamic_cast",能夠用"dynamic_cast"解決的就不要用"typeid"。關於異常,對於幾乎所有編譯器來說,在正常情況(未拋出異常)下,try塊中的代碼執行效率和普通代碼一樣高,而且由於不再需要使用傳統上通過返回值或函數調用來判斷錯誤的方式,代碼的實際執行效率還會進一步提高。拋出和捕捉異常的開銷也只是在某些情況下會高於函數返回和函數調用的開銷。
關於構造和析構,開銷也不總是存在的。對於不需要初始化/銷燬的類型,並沒有構造和析構的開銷,相反對於那些需要初始化/銷燬的類型來說,即使用傳統的C方式實現,也至少需要與之相當的開銷。實事求是地講,RTTI是有用的。但因爲一些理論上及方法論上的原因,它破壞了面向對象的純潔性。
首先,它破壞了抽象,使一些本來不應該被使用的方法和屬性被不正確地使用。其次,因爲運行時類型的不確定性,它把程序變得更脆弱。第三點,也是最重要的一點,它使程序缺乏擴展性。當加入了一個新的類型時,你也許需要仔細閱讀你的dynamic_cast或instanceof的代碼,必要時改動它們,以保證這個新的類型的加入不會導致問題。而在這個過程中,編譯器將不會給你任何幫助。很多人一提到RTTI,總是側重於它的運行時的開銷。但是,相比於方法論上的缺點,這點運行時的開銷真是無足輕重的。
總的來說,RTTI因爲它的方法論上的一些缺點,它必須被非常謹慎地使用。今天面嚮對象語言的類型系統中的很多東西就是產生於避免RTTI的各種努力。
2016-06-16
RTTI是Runtime Type Information的縮寫,從字面上來理解就是執行時期的類型信息,其重要作用就是動態判別執行時期的類型。有的讀者會認爲設計類時使用虛函數就已經足夠了,可是虛函數有本身的侷限性,當涉及類別階層時,需要判斷某個對象所屬的類別,而因爲類別設計中大量使用了虛函數,所以使得這一工作難以實現,但又極其重要,於是使用RTTI的typeid運算符能使程序員確定對象的動態類型。
12.1 位制轉換
2016-06-16
首先參數5爲int型,32位平臺中爲4字節,因此在stack中分配4字節的內存,用於存放參數5。
然後printf根據說明符“%f”,認爲參數應該是個double型(在printf函數中,float會自動轉換成double),因此從stack中讀了8個字節。很顯然,內存訪問越界,會發生什麼情況不可預料。如果在printf或者scanf中指定了“%f”,那麼在後面的參數列表中也應該指定一個浮點數,或者一個指向浮點變量的指針,否則不應加載支持浮點數的函數。
於是("%f",5)有問題,而("%f",5.0)則可行。
2016-06-16
“int z:33;”定義整型變量z爲33位,也就是超過了4字節。這是不合法的,會造成越界,所以程序會報錯。
2016-06-16
In which system(進制) expression 13*16=244 is true?(下面哪個進制能表述13*16=244是正確的?)[中國臺灣某計算機硬件公司V2010年5月面試題]
A.5B.7
C.9D.11
解析:13如果是一個十進制的話,它可以用13=1*101+3*100來表示。現在我們不知道13是幾進制,那我們姑且稱其X進制。X進制下的13轉化爲十進制可以用13=1*X1+3*X0;表示;X進制下的16轉化爲十進制可以用16=1*X1+6*X0;表示;X進制下的244轉化爲十進制可以用244=2*X2+4*X1+4*X0;表示;因此X進制下的13*16=244可以轉化爲十進制下的等式:(1*X1+3*X0)* (1*X1+6*X0)=2*X2+4*X1+4*X0。整理得X*X+6*X+3*X+3*6=2*X*X+4*X+4;最後得出一元二次方程X*X-5*X-14=0。答案X=-2或者X=7。X=-2不合題意捨棄,所以X=7。
2016-06-17
C++有4個類型轉換操作符,這4個操作符是static_cast、const_cast、dynamic_cast和reinterpret_cast。
例如,假設你想把一個int轉換成double,以便讓包含int類型變量的表達式產生出浮點數值的結果。你應該這樣寫:這樣的類型轉換不論是對人工還是對程序都很容易識別。在C++中,static_cast在功能上相對C語言來說有所限制。如不能用static_cast像用C風格的類型轉換一樣把struct轉換成int類型,或者把double類型轉換成指針類型;另外,static_cast不能從表達式中去除const屬性,因爲另一個新的類型轉換操作符const_cast有這樣的功能。
其他C++類型轉換操作符被用在需要更多限制的地方。const_cast最普通的用途就是轉換掉對象的const屬性。通過使用const_cast,讓編譯器知道通過類型轉換想做的只是改變一些東西的constness或者volatileness屬性。這個含義被編譯器所約束。如果你試圖使用const_cast來完成修改constness或者volatileness屬性之外的事情,你的類型轉換將被拒絕。下面是一個const_cast的例子:static_cast和reinterpret_cast操作符修改了操作數類型。它們不是互逆的;static_cast在編譯時使用類型信息執行轉換,在轉換執行必要的檢測(諸如指針越界計算,類型檢查),其操作數相對是安全的。另一方面,reinterpret_cast僅僅是重新解釋了給出的對象的比特模型而沒有進行二進制轉換,編譯器隱式執行任何類型轉換都可由static_cast顯示完成,reinterpret_cast通常爲操作數的位模式提供較低層的重新解釋。例子如下:上面的例子中,我們將一個變量從int轉換到double。這些類型的二進制表達式是不同的。要將整數9轉換到雙精度整數9,static_cast需要正確地爲雙精度整數d補足比特位。其結果爲9.0。而reinterpret_cast的行爲卻不同:
在進行計算以後,d包含無用值。這是因爲reinterpret_cast僅僅是複製n的比特位到d,沒有進行必要的分析。reinterpret_cast這個操作符被用於的類型轉換的轉換結果幾乎都是實現時定義(implementation-defined)。因此,使用reinterpret_casts的代碼很難移植。轉換函數指針的代碼是不可移植的,(C++不保證所有的函數指針都被用一樣的方法表示),在一些情況下這樣的轉換會產生不正確的結果。所以應該避免轉換函數指針類型,按照C++新思維的話來說,reinterpret_cast是爲了映射到一個完全不同類型的意思,這個關鍵詞在我們需要把類型映射回原有類型時用到它。我們映射到的類型僅僅是爲了故弄玄虛和其他目的,這是所有映射中最危險的。reinterpret_cast就是一把銳利無比的雙刃劍,除非你處於背水一戰和火燒眉毛的危急時刻,否則絕不能使用。對於dynamic_cast要注意以下4點:
● dynamic_cast是在運行時檢查的,dynamic_cast用於在繼承體系中進行安全的向下轉換downcast(當然也可以向上轉換,但是沒必要,因爲完全可以用虛函數實現),即基類指針/引用到派生類指針/引用的轉換。如果源和目標類型沒有繼承/被繼承關係,編譯器會報錯;否則必須在代碼裏判斷返回值是否爲NULL來確認轉換是否成功。● dynamic_cast不是擴展C++中style轉換的功能,而是提供了類型安全性。你無法用dynamic_cast進行一些“無理”的轉換。
● dynamic_cast是4個轉換中唯一的RTTI操作符,提供運行時類型檢查。● dynamic_cast不是強制轉換,而是帶有某種“諮詢”性質的。如果不能轉換,dynamic_cast會返回NULL,表示不成功。這是強制轉換做不到的。
下面是一個例子:在本題中,MyItem與IGlyph是繼承關係,可以適用dynamic_cast類型轉換,而因爲我們不知道確定的IWidgetSelector::Selection()返回的具體類型是什麼,所以應用dynamic_cast“試探性”地進行類型轉換是十分必要的。
2016-06-17
給定一個整型變量a,寫兩段代碼,第一個設置a的bit 3,第二個清除a的bit 3。在以上兩個操作中,要保持其他位不變。
2016-06-17
面試官希望看到的幾個要點爲說明常數、“|=”和“&=~”操作。
12.2 嵌入式編程
2016-06-17
volatile問題。
當一個對象的值可能會在編譯器的控制或監測之外被改變時,例如一個被系統時鐘更新的變量,那麼該對象應該聲明成volatile。因此編譯器執行的某些例行優化行爲不能應用在已指定爲volatile的對象上。volatile限定修飾符的用法與const非常相似——都是作爲類型的附加修飾符。例如:display_register是一個int型的volatile對象;curr_task是一個指向volatile的Task類對象的指針;ixa是一個volatile的整型數組,數組的每個元素都被認爲是volatile的;bitmap_buf是一個volatile的Screen類對象,它的每個數據成員都被視爲volatile的。
volatile修飾符的主要目的是提示編譯器該對象的值可能在編譯器未監測到的情況下被改變,因此編譯器不能武斷地對引用這些對象的代碼做優化處理。答案:
volatile的語法與const是一樣的,但是volatie的意思是“在編譯器認識的範圍外,這個數據可以被改變”。不知什麼原因,環境正在改變數據(可能通過多任務處理),所以,volatile告訴編譯器不要擅自做出有關數據的任何假定——在優化期間這是特別重要的。如果編譯器說:“我已經把數據讀進寄存器,而且再沒有與寄存器接觸。”在一般情況下,它不需要再讀這個數據。但是,如果數據是volatile修飾的,編譯器則不能做出這樣的假定,因爲數據可能被其他進程改變了,編譯器必須重讀這個數據而不是優化這個代碼。就像建立const對象一樣,程序員也可以建立volatile對象,甚至還可以建立const volatile對象。這個對象不能被程序員改變,但可通過外面的工具改變。
2016-06-17
關鍵字const的作用是爲讀你代碼的人傳達非常有用的信息。實際上,聲明一個參數爲常量是爲了告訴用戶這個參數的應用目的。如果你曾花很多時間清理其他人留下的垃圾,你就會很快學會感謝這點兒多餘的信息。當然,懂得用const的程序員很少會留下垃圾讓別人來清理。通過給優化器一些附加的信息,使用關鍵字const也許能產生更緊湊的代碼。
合理地使用關鍵字const可以使編譯器很自然地保護那些不希望被改變的參數,防止其被無意的代碼修改。簡而言之,這樣可以減少bug的出現。
2016-06-17
一個定義爲volatile的變量是說這變量可能會被意想不到地改變,這樣,編譯器就不會去假設這個變量的值了。精確地說就是,優化器在用到這個變量時必須每次都小心地重新讀取這個變量的值,而不是使用保存在寄存器裏的備份。下面是volatile變量的幾個例子:
● 並行設備的硬件寄存器(如狀態寄存器)。● 一箇中斷服務子程序中會訪問到的非自動變量(Non-automatic variables)。
● 多線程應用中被幾個任務共享的變量。
12.3 static
2016-06-17
在C語言中,static關鍵字至少有下列幾個作用:
● 函數體內static變量的作用範圍爲該函數體,不同於auto變量,該變量的內存只被分配一次,因此其值在下次調用時仍維持上次的值。● 在模塊內的static全局變量可以被模塊內所有函數訪問,但不能被模塊外其他函數訪問。
● 在模塊內的static函數只可被這一模塊內的其他函數調用,這個函數的使用範圍被限制在聲明它的模塊內。● 在類中的static成員變量屬於整個類所擁有,對類的所有對象只有一份拷貝。
● 在類中的static成員函數屬於整個類所擁有,這個函數不接收this指針,因而只能訪問類的static成員變量。
13.6 堆
2016-06-17
進行C/C++編程時,需要程序員對內存的瞭解比較精準。經常需要操作的內存可分爲以下幾個類別。
● 棧區(stack):由編譯器自動分配和釋放,存放函數的參數值、局部變量的值等。其操作方式類似於數據結構中的棧。● 堆區(heap):一般由程序員分配和釋放,若程序員不釋放,程序節束時可能由操作系統回收。注意它與數據結構中的堆是兩回事,分配方式倒是類似於鏈表。
● 全局區(靜態區)(static):全局變量和靜態變量的存儲是放在一塊的,初始化的全局變量和靜態變量在一塊區域,未初始化的全局變量和未初始化的靜態變量在相鄰的另一塊區域。程序節束後由系統釋放。● 文字常量區:常量字符串就是放在這裏的。程序節束後由系統釋放。
● 程序代碼區:存放函數體的二進制代碼。以下是一段實際說明的程序代碼:堆和棧的理論知識如下。
1.申請方式棧:由系統自動分配。例如,聲明在函數中的一個局部變量int b,系統自動在棧中爲b開闢空間。
堆:需要程序員自己申請,並指明大小,在C中用malloc函數。如:
在C++中用new運算符,如:但是注意p1、p2本身是在棧中的。
2.申請後系統的響應棧:只要棧的剩餘空間大於所申請空間,系統將爲程序提供內存,否則將報異常提示棧溢出。
堆:首先應該知道操作系統有一個記錄空閒內存地址的鏈表,當系統收到程序的申請時,會遍歷該鏈表,尋找第一個空間大於所申請空間的堆節點,然後將該節點從空閒節點鏈表中刪除,並將該節點的空間分配給程序。對於大多數系統,會在這塊內存空間中的首地址處記錄本次分配的大小,這樣,代碼中的delete語句才能正確地釋放本內存空間。另外,由於找到的堆節點的大小不一定正好等於申請的大小,系統會自動地將多餘的那部分重新放入空閒鏈表中。3.申請大小的限制
棧:在Windows下,棧是向低地址擴展的數據結構,是一塊連續的內存的區域。這句話的意思是棧頂的地址和棧的最大容量是系統預先規定好的,在Windows下,棧的大小是2MB(也有的說是1MB,總之是一個編譯時就確定的常數),如果申請的空間超過棧的剩餘空間,將提示overflow。因此,能從棧獲得的空間較小。堆:堆是向高地址擴展的數據結構,是不連續的內存區域。這是由於系統是用鏈表存儲空閒內存地址的,自然是不連續的。而鏈表的遍歷方向是由低地址向高地址,堆的大小受限於計算機系統中有效的虛擬內存。由此可見,堆獲得的空間比較靈活,也比較大。4.申請效率的比較
棧:由系統自動分配,速度較快。但程序員無法控制。堆:是由new分配的內存,一般速度比較慢,而且容易產生內存碎片,不過用起來最方便。
另外,在Windows下,最好的方式是用VirtualAlloc分配內存。不是在堆,也不是在棧,而是直接在進程的地址空間中保留一塊內存,雖然用起來最不方便,但是速度最快,也最靈活。5.堆和棧中的存儲內容
棧:在函數調用時,第一個進棧的是主函數中的下一條指令(函數調用語句的下一條可執行語句)的地址,然後是函數的各個參數。在大多數的C編譯器中,參數是由右往左入棧的,然後是函數中的局部變量。注意靜態變量是不入棧的。當本次函數調用節束後,局部變量先出棧,然後是參數,最後棧頂指針指向最開始存的地址,也就是主函數中的下一條指令,程序由該點繼續運行。
堆:一般是在堆的頭部用一個字節存放堆的大小。堆中的具體內容由程序員安排。6.存取效率的比較
aaaaaaaaaaa是在運行時刻賦值的,而bbbbbbbbbbb是在編譯時就確定的。但是,在以後的存取中,在棧上的數組比指針所指向的字符串(例如堆)快。比如:
對應的彙編代碼如下:
2016-06-17
第一種在讀取時直接就把字符串中的元素讀到寄存器cl中,而第二種則要先把edx指中,再根據edx讀取字符,顯然慢了。
7.小節堆和棧的區別可以用如下的比喻來描述。
使用棧就像我們去飯館裏吃飯,只管點菜(發出申請)、付錢和吃(使用),吃飽了就走,不必理會切菜、洗菜等準備工作和洗碗、刷鍋等掃尾工作。好處是快捷,但是自由度小。使用堆就像是自己動手做喜歡吃的菜餚,比較麻煩,但是比較符合自己的口味,而且自由度大。數據結構方面的堆和棧,這些都是不同的概念。這裏的堆實際上指的就是(滿足堆性質的)優先隊列的一種數據結構,第一個元素有最高的優先權;棧實際上就是滿足先進後出的性質的數學或數據結構。雖然“堆棧”的說法是連起來叫,但是它們還是有很大區別的。
2016-06-17
heap是堆,stack是棧。
stack的空間由操作系統自動分配/釋放,heap上的空間手動分配/釋放。stack空間有限,heap是很大的自由存儲區。
C中的malloc函數分配的內存空間即在堆上,C++中對應的是new操作符。程序在編譯期對變量和函數分配內存都在棧上進行,且程序運行過程中函數調用時參數的傳遞也在棧上進行。
2016-06-17
接觸過編程的人都知道,高級語言都能通過變量名訪問內存中的數據。那麼這些變量在內存中是如何存放的呢?程序又是如何使用這些變量的呢?
首先,來了解一下C語言的變量是如何在內存分佈的。C語言有全局變量(Global)、本地變量(Local)、靜態變量(Static)和寄存器變量(Register)。每種變量都有不同的分配方式。先來看下面這段代碼:編譯後的執行結果是:
輸出的結果就是變量的內存地址。其中v1、v2、v3是本地變量,g1、g2、g3是全局變量,s1、s2、s3是靜態變量。你可以看到這些變量在內存中是連續分佈的,但是本地變量和全局變量分配的內存地址差了十萬八千里,而全局變量和靜態變量分配的內存是連續的。這是因爲本地變量和全局/靜態變量是分配在不同類型的內存區域中的結果。對於一個進程的內存空間而言,可以在邏輯上分成3個部分:代碼區、靜態數據區和動態數據區。動態數據區一般就是“堆棧”。“棧(stack)”和“堆(heap)”是兩種不同的動態數據區。棧是一種線性節構,堆是一種鏈式節構。進程的每個線程都有私有的“棧”,所以每個線程雖然代碼一樣,但本地變量的數據都是互不干擾的。一個堆棧可以通過“基地址”和“棧頂”地址來描述。全局變量和靜態變量分配在靜態數據區,本地變量分配在動態數據區,即堆棧中。程序通過堆棧的基地址和偏移量來訪問本地變量,如下圖所示。堆棧是一個先進後出的數據結構,棧頂地址總是小於等於棧的基地址。我們可以先了解一下函數調用的過程,以便對堆棧在程序中的作用有更深入的瞭解。不同的語言有不同的函數調用規定,這些因素有參數的壓入規則和堆棧的平衡。Windows API的調用規則和ANSI C的函數調用規則是不一樣的,前者由被調函數調整堆棧,後者由調用者調整堆棧。兩者通過“_stdcall”和“_cdecl”前綴區分。先看下面這段代碼:
編譯後的執行結果是:下面詳細解釋函數調用的過程中堆棧的分佈:
在堆棧中分佈變量是從高地址向低地址分佈,EBP指向棧底指針,ESP是指向棧頂的指針,根據__stdcall調用約定,參數從右向左入棧,所以首先,3個參數以從右到左的次序壓入堆棧,先壓“param3”,再壓“param2”,最後壓入“param1”。棧內分佈如下圖所示。然後函數的返回地址入棧,棧內分佈如下圖所示。通過跳轉指令進入函數。函數地址入棧後,EBP入棧,然後把當前的ESP的值給EBP,彙編下指令爲:
此時棧底指針和棧頂指針指向同一位置,棧內分佈爲如下圖所示。然後是int var1=param1;int var2=param2;int var3=param3;也就是變量var1,var2,var3的初始化(從左向右的順序入棧),按聲明順序依次存儲到EBP-4,EBP-8,EBP-12位置,棧內分佈如下圖所示。
2016-06-17
Windows下的動態數據除了可存放在棧中,還可以存放在堆中。瞭解C++的朋友都知道,C++可以使用new關鍵字來動態分配內存
13.8 排序
2016-06-18
按平均時間將排序分爲以下4類。
● 平方階(O(n2))排序:一般稱爲簡單排序,例如直接插入、直接選擇和冒泡排序。● 線性對數階(O(nlgn))排序:如快速、堆和歸併排序。
● O(n1+£)階排序:£是介於0和1之間的常數,即0<£<1,如希爾排序。● 線性階(O(n))排序:如桶、箱和基數排序。
簡單排序中直接插入排序最好,快速排序最快。當文件爲正序時,直接插入排序和冒泡排序均最佳。1.影響排序效果的因素因爲不同的排序方法適應不同的應用環境和要求,所以選擇合適的排序方法應綜合考慮下列因素:
● 待排序的記錄數目n。● 記錄的大小(規模)。● 關鍵字的節構及其初始狀態。
● 對穩定性的要求。● 語言工具的條件。
● 存儲節構。● 時間和輔助空間複雜度等。
2.不同條件下排序方法的選擇(1)若n較小(如n≤50),可採用直接插入或直接選擇排序。
當記錄規模較小時,直接插入排序較好。否則因爲直接選擇移動的記錄數少於直接插入,應選直接選擇排序爲宜。(2)若文件初始狀態基本有序(指正序),則應選用直接插入排序、冒泡排序或隨機的快速排序爲宜。
(3)若n較大,則應採用時間複雜度爲O(nlgn)的排序方法(快速排序、堆排序或歸併排序)。快速排序被認爲是目前基於比較的內部排序中最好的方法。當待排序的關鍵字隨機分佈時,快速排序的平均時間最短。
堆排序所需的輔助空間少於快速排序,並且不會出現快速排序可能出現的最壞情況。這兩種排序都是不穩定的。若要求排序穩定,則可選用歸併排序。但本章介紹的從單個記錄起進行兩兩歸併的排序算法並不值得提倡,通常可以將它和直接插入排序節合在一起使用。先利用直接插入排序求得較長的有序子文件,然後再兩兩歸併之。因爲直接插入排序是穩定的,所以改進後的歸併排序仍是穩定的。
14.1 整數字符串轉化
2016-06-18
整數轉化成字符串,可以採用加'0',再逆序的辦法,整數加'0'就會隱性轉化成char類型的數。
16.1 進程
2016-06-21
作業:用戶在一次解題或一個事務處理過程中要求計算機系統所做工作的集合。它包括用戶程序、所需要的數據及控制命令等。作業是由一系列有序的步驟組成的。
進程:一個程序在一個數據集合上的一次運行過程。所以一個程序在不同數據集合上運行,乃至一個程序在同樣數據集合上的多次運行都是不同的進程。線程:線程是進程中的一個實體,是被系統獨立調度和執行的基本單位。
管程:管程實際上是定義了一個數據結構和在該數據結構上的能爲併發進程所執行的一組操作,這組操作能同步進程和改變管程中的數據。
2016-06-21
現在最常用的進程間通信的方式有信號、信號量、消息隊列、共享內存。所謂進程通信,就是不同進程之間進行一些“接觸”。這種接觸有簡單,也有複雜。機制不同,複雜度也不一樣。通信是一個廣義上的意義,不僅僅指傳遞一些message。它們的使用方法是基本相同的,所以只要掌握了一種使用方法,然後記住其他的使用方法就可以了。信號和信號量是不同的,它們雖然都可用來實現同步和互斥,但前者是使用信號處理器來進行的,後者是使用P、V操作來實現的。消息隊列是比較高級的一種進程間通信方法,因爲它真的可以在進程間傳送message,連傳送一個“I seek you”都可以。
一個消息隊列可以被多個進程所共享(IPC就是在這個基礎上進行的);如果一個進程的消息太多,一個消息隊列放不下,也可以用多於一個的消息隊列(不過可能管理會比較複雜)。共享消息隊列的進程所發送的消息中除了message本身外還有一個標誌,這個標誌可以指明該消息將由哪個進程或者是哪類進程接受。每一個共享消息隊列的進程針對這個隊列也有自己的標誌,可以用來聲明自己的身份。
2016-06-21
在Windows編程中互斥器(mutex)的作用和臨界區(critical section)類似,請說一下二者間的主要區別。[中國臺灣某著名殺毒軟件公司2005年面試題]
解析:多線程編程問題。答案:兩者的區別是mutex可以用於進程之間互斥,critical section是線程之間的互斥。
2016-06-21
所謂deadlocks(死鎖)是指兩個或兩個以上的進程在執行過程中,因爭奪資源而造成的一種互相等待的現象,若無外力作用,它們都將無法推進下去。此時稱系統處於死鎖狀態或系統產生了死鎖。
產生死鎖的4個必要條件如下。● 互斥條件:一個資源每次只能被一個進程使用。
● 請求與保持條件:一個進程因請求資源而阻塞時,對已獲得的資源保持不放。● 不剝奪條件:進程已獲得的資源,在末使用完之前,不能強行剝奪。
● 循環等待條件:若干進程之間形成一種頭尾相接的循環等待資源關係。這4個條件是死鎖的必要條件,只要系統發生死鎖,這些條件必然成立,而只要上述條件之一不滿足,就不會發生死鎖。
死鎖的解除與預防方法如下:理解了死鎖的原因,尤其是產生死鎖的4個必要條件,就可以最大可能地避免、預防和解除死鎖。所以,在系統設計、進程調度等方面注意如何不讓這4個必要條件成立,如何確定資源的合理分配算法,避免進程永久佔據系統資源。此外,也要防止進程在處於等待狀態的情況下佔用資源,在系統運行過程中,對進程發出的每一個系統能夠滿足的資源申請進行動態檢查,並根據檢查結果決定是否分配資源,若分配後系統可能發生死鎖,則不予分配,否則予以分配。因此,對資源的分配要給予合理的規劃。
根據產生死鎖的4個必要條件,只要使其中之一不能成立,死鎖就不會出現。爲此,可以採取下列3種預防措施:● 採用資源靜態分配策略,破壞“部分分配”條件。
● 允許進程剝奪使用其他進程佔有的資源,從而破壞“不可剝奪”條件。● 採用資源有序分配法,破壞“環路”條件。
這裏注意一點:互斥條件無法被破壞。死鎖的避免不嚴格地限制死鎖的必要條件的存在,而是系統在系統運行過程中小心地避免死鎖的最終發生。避免死鎖算法中最有代表性的算法是Dijkstra E.W於1968年提出的銀行家算法,該算法需要檢查申請者對資源的最大需求量,如果系統現存的各類資源可以滿足申請者的請求,就滿足申請者的請求。這樣申請者就可很快完成其計算,然後釋放它佔用的資源,從而保證了系統中的所有進程都能完成,所以可避免死鎖的發生。
2016-06-21
進程在運行中不斷地改變其運行狀態。通常,一個運行進程必須具有以下三種基本狀態。1>就緒(Ready)狀態:當進程已分配到除CPU以外的所有必要的資源,只要獲得處理機便可立即執行,這時的進程狀態稱爲就緒狀態。2>執行(Running)狀態:當進程已獲得處理機,其程序正在處理機上執行,此時的進程狀態稱爲執行狀態。3>阻塞(Blocked)狀態:正在執行的進程,由於等待某個事件發生而無法執行時,便放棄處理機而處於阻塞狀態。引起進程阻塞的事件可以有多種,例如,等待I/O完成、申請緩衝區不能滿足、等待信件(信號)等。
16.2 線程
2016-06-21
進程是程序的一次執行。線程可以理解爲進程中執行的一段程序片段。在一個多任務環境中下面的概念可以幫助我們理解兩者間的差別。
進程間是獨立的,這表現在內存空間、上下文環境上;線程運行在進程空間內。一般來講(不使用特殊技術),進程無法突破進程邊界存取其他進程內的存儲空間;而線程由於處於進程空間內,所以同一進程所產生的線程共享同一內存空間。同一進程中的兩段代碼不能夠同時執行,除非引入線程。
線程是屬於進程的,當進程退出時該進程所產生的線程都會被強制退出並清除。線程佔用的資源要少於進程所佔用的資源。進程和線程都可以有優先級。進程間可以通過IPC通信,但線程不可以。
2016-06-21
PE文件被稱爲可移植的執行體是Portable Execute的全稱,常見的EXE、DLL、OCX、SYS、COM都是PE文件,PE文件是微軟Windows操作系統上的程序文件(可能是間接被執行,如DLL)
2016-06-21
目前以lib爲後綴的庫有兩種,一種爲靜態鏈接庫(Static Libary),另一種爲動態鏈接庫(DLL)的導入庫(Import Libary,以下簡稱“導入庫”)。雖然靜態鏈接庫和動態庫的導入庫都是.lib文件,但是區別很大,它們實質是不一樣的東西。靜態庫本身就包含了實際執行代碼、地址符號表等等,而對於導入庫而言,其實際的執行代碼位於動態庫中,導入庫只包含了地址符號表等,確保程序找到對應函數的一些基本地址信息。
靜態鏈接庫是一個或者多個obj文件的打包,所以有人乾脆把從obj文件生成lib的過程稱爲Archive,即合併到一起。比如你鏈接一個靜態庫,如果其中有錯,它會準確地找到是哪個obj有錯,即靜態lib只是殼子。當我們的應用工程在使用靜態鏈接庫的時候,靜態鏈接庫要參與編譯,在生成執行文件之前的鏈接過程中,將靜態鏈接庫的全部指令直接鏈接入可執行文件中,故而,在可執行文件生成以後,靜態鏈接庫.lib文件即可以棄之不用。動態鏈接庫(dll)是作爲共享函數庫的可執行文件。動態鏈接提供了一種方法,使進程可以調用不屬於其可執行代碼的函數。函數的可執行代碼位於一個.dll文件中,該dll包含一個或多個已被編譯、鏈接並與使用它們的進程分開存儲的函數。dll還有助於共享數據和資源。多個應用程序可同時訪問內存中單個dll副本的內容。使用動態鏈接代替靜態鏈接有若干優點。dll節省內存,減少交換操作,節省磁盤空間,更易於升級(不需要重鏈接和重編譯),提供售後支持,提供擴展MFC庫類的機制,支持多語言程序。
靜態鏈接庫與動態鏈接庫都是共享代碼的方式,如果採用靜態鏈接庫,lib中的指令都全部被直接包含在最終生成的exe文件中了。但是若使用dll(即動態鏈接庫),該dll不必被包含在最終exe文件中,exe文件執行時可以“動態”地引用和卸載這個與exe獨立的dll文件。靜態鏈接庫和動態鏈接庫的另外一個區別在於靜態鏈接庫中不能再包含其他的動態鏈接庫或者靜態庫,而在動態鏈接庫中還可以再包含其他的動態或靜態鏈接庫。動態鏈接庫與靜態鏈接庫的使用不同之處在於它允許可執行模塊(.dll文件或.exe文件)僅包含在運行時定位dll函數的可執行代碼所需的信息。在靜態鏈接庫的使用中,鏈接器從靜態鏈接庫獲取所有被引用的函數,並將庫同代碼一起放到可執行文件中。
16.3 內存管理
2016-06-21
Windows內存管理方式主要分爲:頁式管理、段式管理、段頁式管理。
頁式管理的基本原理是將各進程的虛擬空間劃分成若干個長度相等的頁(page);頁式管理把內存空間按頁的大小劃分成片或者頁面,然後把頁式虛擬地址與內存地址建立一一對應的頁表;並用相應的硬件地址變換機構來解決離散地址變換問題。頁式管理採用請求調頁或預調頁技術來實現內外存存儲器的統一管理。其優點是沒有外碎片,每個內碎片不超過頁的大小。缺點:程序全部裝入內存,要求有相應的硬件支持。例如地址變換機構缺頁中斷的產生和選擇淘汰頁面等都要求有相應的硬件支持。這增加了機器成本,增加了系統開銷。段式管理的基本思想就是把程序按內容或過程函數關係分成段,每段有自己的名字。一個用戶作業或進程所包含的段對應一個二維線形虛擬空間,也就是一個二維虛擬存儲器。段式管理程序以段爲單位分配內存 然後通過地址影射機構把段式虛擬地址轉換爲實際內存物理地址。其優點是可以分別編寫和編譯,可以針對不同類型的段採取不同的保護,可以按段爲單位來進行共享,包括通過動態鏈接進行代碼共享。缺點是會產生碎片。
段頁式管理:爲了實現段頁式管理,系統必須爲每個作業或進程建立一張段表以管理內存分配與釋放、缺段處理等。另外由於一個段又被劃分成了若干頁。每個段又必須建立一張頁表以把段中的虛頁變換成內存中的實際頁面。顯然與頁式管理時相同,頁表中也要有相應的實現缺頁中斷處理和頁面保護等功能的表項。段頁式管理是段式管理與頁式管理方案結合而成的 所以具有它們兩者的優點。但反過來說,由於管理軟件的增加,複雜性和開銷也就隨之增加了。另外需要的硬件以及佔用的內存也有所增加。使得執行速度下降。
2016-06-21
銀行家算法是用來避免死鎖的,該方法將系統的狀態分爲安全狀態和不安全狀態,只要使系統處於安全狀態,便可避免死鎖的發生。
17.1 數據庫理論
2016-06-21
數據庫模式的4個範式問題。
1NF:第一範式。如果關係模式R的所有屬性的值域中每一個值都是不可再分解的值,則稱R屬於第一範式模式。如果某個數據庫模式都是第一範式的,則稱該數據庫模式屬於第一範式的數據庫模式。第一範式的模式要求屬性值不可再分裂成更小部分,即屬性項不能是屬性組合或由組屬性組成。
2NF:第二範式。如果關係模式R爲第一範式,並且R中每一個非主屬性完全函數依賴於R的某個候選鍵,則稱R爲第二範式模式。如果某個數據庫模式中每個關係模式都是第二範式的,則稱該數據庫模式屬於第二範式的數據庫模式。(注:如果A是關係模式R的候選鍵的一個屬性,則稱A是R的主屬性,否則稱A是R的非主屬性。)3NF:第三範式。如果關係模式R是第二範式,且每個非主屬性都不傳遞依賴於R的候選鍵,則稱R是第三範式的模式。如果某個數據庫模式中的每個關係模式都是第三範式,則稱R爲3NF的數據庫模式。
BCNF:BC範式。如果關係模式R是第一範式,且每個屬性都不傳遞依賴於R的候選鍵,那麼稱R爲BCNF的模式。4NF:第四範式。設R是一個關係模式,D是R上的多值依賴集合。如果D中成立非平凡多值依賴X→→Y時,X必是R的超鍵,那麼稱R是第四範式的模式。
2016-06-21
存儲過程是用戶定義的一系列SQL語句的集合,涉及特定表或其他對象的任務,用戶可以調用存儲過程。而函數通常是數據庫已定義的方法,它接收參數並返回某種類型的值,並且不涉及特定用戶表。
2016-06-21
數據庫事務是指作爲單個邏輯工作單元執行的一系列操作,這些操作要麼全做要麼全不做,是一個不可分割的工作單位。
事務的開始與結束可以由用戶顯式控制。如果用戶沒有顯式地定義事務,則由DBMS按默認規定自動劃分事務。事務具有原子性、一致性、獨立性及持久性等特點。● 事務的原子性是指一個事務要麼全部執行,要麼不執行。也就是說一個事務不可能只執行了一半就停止了。比如你從銀行取錢,這個事務可以分成兩個步驟(1)存摺減款,(2)拿到現金。不可能存摺錢少了,而錢卻沒拿出來。這兩步必須同時完成,要麼就都不完成。
● 事務的一致性是指事務的運行並不改變數據庫中數據的一致性。例如,完整性約束了a+b=10,一個事務改變了a,那麼b也應該隨之改變。● 事務的獨立性是指兩個以上的事務不會出現交錯執行的狀態。因爲這樣可能會導致數據不一致。● 事務的持久性是指事務運行成功以後,就係統的更新是永久的。不會無緣無故的回滾。
2016-06-21
遊標用於定位結果集的行。通過判斷全局變量@@FETCH_ STATUS可以判斷其是否到了最後。通常此變量不等於0表示出錯或到了最後。
2016-06-21
事前觸發器運行於觸發事件發生之前,而事後觸發器運行於觸發事件發生之後。語句級觸發器可以在語句執行前或後執行,而行級觸發在觸發器所影響的每一行觸發一次。
2016-06-21
所謂SQL注入式攻擊,就是攻擊者把SQL命令插入到Web表單的輸入域或頁面請求的查詢字符串中,欺騙服務器執行惡意的SQL命令。在某些表單中,用戶輸入的內容直接用來構造(或者影響)動態SQL命令,或作爲存儲過程的輸入參數,這類表單特別容易受到SQL注入式攻擊。
防範SQL注入式攻擊闖入並不是一件特別困難的事情,只要在利用表單輸入的內容構造SQL命令之前,把所有輸入內容過濾一番就可以了。過濾輸入內容可以按多種方式進行。● 替換單引號,即把所有單獨出現的單引號改成兩個單引號,防止攻擊者修改SQL命令的含義。
● 刪除用戶輸入內容中的所有連字符,防止攻擊者順利獲得訪問權限。● 對於用來執行查詢的數據庫賬戶,限制其權限。用不同的用戶賬戶執行查詢、插入、更新、刪除操作。由於隔離了不同賬戶可執行的操作,因而也就防止了原本用於執行SELECT命令的地方卻被用於執行INSERT、UPDATE或DELETE命令。● 用存儲過程來執行所有的查詢。SQL參數的傳遞方式將防止攻擊者利用單引號和連字符實施攻擊。此外,它還使得數據庫權限可以被限制到只允許特定的存儲過程執行,所有的用戶輸入必須遵從被調用的存儲過程的安全上下文,這樣就很難再發生注入式攻擊了。
● 檢查用戶輸入的合法性,確信輸入的內容只包含合法的數據。數據檢查應當在客戶端和服務器端都執行。之所以要執行服務器端驗證,是爲了彌補客戶端驗證機制脆弱的安全性。在客戶端,攻擊者完全有可能獲得網頁的源代碼,修改驗證合法性的腳本(或者直接刪除腳本),然後將非法內容通過修改後的表單提交給服務器。因此,要保證驗證操作確實已經執行,唯一的辦法就是在服務器端也執行驗證。● 將用戶登錄名稱、密碼等數據加密保存。加密用戶輸入的數據,然後再將它與數據庫中保存的數據比較,這相當於對用戶輸入的數據進行了“消毒”處理。用戶輸入的數據不再對數據庫有任何特殊的意義,從而也就防止了攻擊者注入SQL命令。
● 檢查提取數據的查詢所返回的記錄數量。如果程序只要求返回一個記錄,但實際返回的記錄卻超過一行,那就當做出錯處理。
17.2 SQL語言
2016-06-21
臨時表要在表名前面加“#”。
17.4 SQL語言主觀題
2016-06-21
sp_renamedb命令可以更改數據庫的名稱。
語法如下:參數:
是數據庫的當前名稱。old_name爲sysname類型,無默認值。是數據庫的新名稱。new_name必須遵循標識符規則。new_name爲sysname類型,無默認值。
返回代碼值:0(成功)或非零數字(失敗)權限:
只有sysadmin和dbcreator固定服務器角色的成員才能執行sp_renamedb。答案:sp_renamedb命令。
18.1 網絡結構
2016-06-21
OSI參考模型有7層,其分層原則如下:
● 根據不同層次的抽象分層。● 每層應當有一個定義明確的功能。
● 每層功能的選擇應該有助於制定網絡協議的國際標準。● 各層邊界的選擇應儘量節省跨過接口的通信量。
● 層數應足夠多,以避免不同的功能混雜在同一層中,但也不能太多,否則體系結構會過於龐大。根據以上標準,OSI參考模型分爲物理層、數據鏈路層、網絡層、傳輸層、會話層、表示層、應用層。
物理層涉及在信道上傳輸的原始比特流。數據鏈路層的主要任務是加強物理層傳輸原始比特流的功能,使之對應的網絡層顯現爲一條無錯線路。發送包把輸入數據封裝在數據幀,按順序傳送出去並處理接收方回送的確認幀。
網絡層關係到子網的運行控制,其中一個關鍵問題是確認從源端到目的端如何選擇路由。傳輸層的基本功能是從會話層接收數據而且把其分成較小的單元傳遞給網絡層。
會話層允許不同機器上的用戶建立會話關係。表示層用來完成某些特定的功能。
應用層包含着大量人們普遍需要的協議。
18.2 網絡協議問題
2016-06-21
TCP是傳輸控制協議,提供的是面向連接、可靠的字節流服務。當客戶和服務器彼此交換數據前,必須先在雙方之間建立一個TCP連接,之後才能傳輸數據。TCP提供超時重發、丟棄重複數據、檢驗數據、流量控制等功能,保證數據能從一端傳到另一端。
UDP是用戶數據報協議,是一個簡單的面向數據報的運輸層協議。UDP不提供可靠性,它只是把應用程序傳給IP層的數據報發送出去,但是並不保證它們能到達目的地。由於UDP在傳輸數據報前不用在客戶和服務器之間建立一個連接,且沒有超時重發等機制,故而傳輸速度很快。
2016-06-21
服務器端程序編寫:
(1)調用ServerSocket(int port)創建一個服務器端套接字,並綁定到指定端口上。(2)調用accept(),監聽連接請求,則接收連接,返回通信套接字。
(3)調用Socket類的getOutStream()和getInputStream獲取輸出流和輸入流,開始網絡數據的發送和接收。(4)關閉通信套接字.Socket.close()。
客戶端程序編寫:(1)調用Socket()創建一個流套接字,並連接到服務器端。
(2)調用Socket類的getOutputStream()和fetInputStream獲取輸出流和輸入流,開始網絡數據的發送和接收。(3)關閉通信套接字.Socket.close()。
18.3 網絡安全問題
2016-06-21
防火牆的優點:它能增強機構內部網絡的安全性,用於加強網絡間的訪問控制,防止外部用戶非法使用內部網的資源,保護內部網絡的設備不被破壞,防止內部網絡的敏感數據被竊取。防火牆系統決定了哪些內部服務可以被外界訪問;外界的哪些人可以訪問內部的哪些服務,以及哪些外部服務可以被內部人員訪問。
防火牆的缺點:對於發生在內網的攻擊無能爲力;對於部分攻擊,可以繞過防火牆,防火牆發現不了;防火牆的策略是靜態的,不能實施動態防禦;等等。入侵檢測的優勢:入侵監測系統掃描當前網絡的活動,監視和記錄網絡的流量,根據定義好的規則來過濾從主機網卡到網線上的流量,提供實時報警。大多數的入侵監測系統可以提供關於網絡流量非常詳盡的分析。它們可以監視任何定義好的流量。很多系統對FTP、HTTP和Telnet流量都有默認的設置,還有其他的流量,如NetBus、本地和遠程登錄失敗,等等。也可以自己定製策略。如果定義了策略和規則,便可以獲得FTP、SMTP、Telnet和任何其他的流量。這種規則有助於追查該連接和確定網絡上發生過什麼,以及現在正在發生什麼。這些程序在需要確定網絡中策略實施的一致性情況時是非常有效的工具。
入侵檢測的缺點:目前入侵檢測技術的方法主要停留在異常檢測統計方法和誤用檢測方法上,這兩種方法都還存在這樣或那樣的問題。網絡入侵技術在不斷地發展,入侵的行爲表現出不確定性、多樣性等特點。網絡應用的發展又帶來新的安全問題。如高速網絡技術出現流量大的特點,那麼基於網絡的入侵檢測系統如何適應這種情況?基於主機審計數據怎樣做到既減少數據量,又能有效地檢測到入侵?入侵檢測研究領域急需其他學科知識提供新的入侵檢測解決方法。入侵檢測只是僅僅試圖發現計算機網絡中的安全問題,要解決網絡安全的問題還需要其他的網絡安全技術。另外,入侵檢測系統本身還存在安全問題。入侵檢測系統也可能會受到攻擊。終上所述,其實防火牆和入侵檢測各有優劣。打個比方,防火牆就相當於一棟大樓外的門衛系統,而入侵檢測就相當於大樓內的監控系統,兩者缺一不可。應該將入侵檢測系統與防火牆聯動起來,當入侵檢測系統發現到有入侵行爲時,應及時報告防火牆,以阻斷入侵。
2016-06-22
在網絡技術中,端口(Port)大致有兩種意思:一是物理意義上的端口,比如,ADSL MODEM、集線器、交換機、路由器用於連接其他網絡設備的接口,如RJ-45端口、SC端口,等等;二是邏輯意義上的端口,一般是指TCP/IP協議中的端口,端口號的範圍爲0~65535,比如用於瀏覽網頁服務的80端口,用於FTP服務的21端口,等等。我們這裏將要介紹的就是邏輯意義上的端口。
邏輯意義上的端口有多種分類標準,下面將介紹兩種常見的分類。1)按端口號分佈劃分
①知名端口(Well-Known Ports)知名端口即衆所周知的端口號,範圍爲0~1023,這些端口號一般固定分配給一些服務。比如21端口分配給FTP服務,25端口分配給SMTP(簡單郵件傳輸協議)服務,80端口分配給HTTP服務,135端口分配給RPC(遠程過程調用)服務,等等。
②動態端口(Dynamic Ports)動態端口的範圍爲1024~65535,這些端口號一般不固定分配給某個服務,也就是說許多服務都可以使用這些端口。只要運行的程序向系統提出訪問網絡的申請,那麼系統就可以從這些端口號中分配一個供該程序使用。比如1024端口就是分配給第一個向系統發出申請的程序。在關閉程序進程後,就會釋放所佔用的端口號。
不過,動態端口也常常被病毒木馬程序所利用,如冰河默認連接端口是7626,WAY 2.4是8011,Netspy 3.0是7306,YAI病毒是1024,等等。2)按協議類型劃分
按協議類型劃分,可以分爲TCP、UDP、IP和ICMP(Internet控制消息協議)等端口。下面主要介紹TCP和UDP端口。①TCP端口
TCP端口,即傳輸控制協議端口,需要在客戶端和服務器之間建立連接,這樣可以提供可靠的數據傳輸。常見的包括FTP服務的21端口,Telnet服務的23端口,SMTP服務的25端口,以及HTTP服務的80端口,等等。②UDP端口
UDP端口,即用戶數據包協議端口,無須在客戶端和服務器之間建立連接,安全性得不到保障。常見的有DNS服務的53端口,SNMP(簡單網絡管理協議)服務的161端口,QQ使用的8000和4000端口,等等。
21.1 數字規律類題目
2016-06-22
數字規律題目一般包含以下10種情況:
(1)等差關係類;(2)等比關係類;(3)前項求和/差關係類;
(4)前項求積/商關係類;(5)隔項規律類;
(6)分組規律類;(7)平方規律類;
(8)質數規律;(9)整數+小數類;
(10)組合類。
21.4 應用數學類題目
2016-06-22
應用數學類題目包括如下幾類:1.比例問題,2.路程問題,3.工程問題,4.植樹問題,5.解方程問題,6.排列組合問題,7.利潤問題,8.概率問題。
發佈了69 篇原創文章 · 獲贊 7 · 訪問量 10萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章