經典C/C++面試題

lostinai發表於2014-06-26

1.介紹一下STL,詳細說明STL如何實現vector。 

     STL (標準模版庫,Standard Template Library)它由容器演算法迭代器組成。 

     STL有以下的一些優點: 

     可以方便容易地實現搜尋資料或對資料排序等一系列的演算法; 

     除錯程式時更加安全和方便; 

     即使是人們用STL在UNIX平臺下寫的程式碼你也可以很容易地理解(因為STL是跨平臺的)。 

     vector實質上就是一個動態陣列,會根據資料的增加,動態的增加陣列空間。 

  
2.如果用VC開發程式,常見這麼幾個錯誤,C2001,c2005,c2011,這些錯誤的原因是什麼。 

  在學習VC++的過程中,遇到的LNK2001錯誤的錯誤訊息主要為: 

  unresolved external symbol “symbol”(不確定的外部“符號”)。 

    如果連線程式不能在所有的庫和目標檔案內找到所引用的函式、變數或標籤,將產生此錯誤訊息。 

     一般來說,發生錯誤的原因有兩個:一是所引用的函式、變數不存在、拼寫不正確或者使用錯誤;其次可能使用了不同版本的連線庫。 

     程式設計中經常能遇到LNK2005錯誤——重複定義錯誤,其實LNK2005錯誤並不是一個很難解決的錯誤. 

  
3.繼承和委派有什麼分別,在決定使用繼承或者委派的時候需要考慮什麼。 

     在OOD,OOP中,組合優於繼承. 

     當然多型的基礎是繼承,沒有繼承多型無從談起。 

     當物件的型別不影響類中函式的行為時,就要使用模板來生成這樣一組類。 

     當物件的型別影響類中函式的行為時,就要使用繼承來得到這樣一組類. 

  
4.指標和引用有什麼分別;如果傳引用比傳指標安全,為什麼?如果我使用常量指標難道不行嗎? 

     (1) 引用在建立的同時必須初始化,即引用到一個有效的物件;而指標在定義的時候不必初始化,可以在定義後面的任何地方重新賦值. 

     (2) 不存在NULL引用,引用必須與合法的儲存單元關聯;而指標則可以是NULL. 

     (3) 引用一旦被初始化為指向一個物件,它就不能被改變為另一個物件的引用;而指標在任何時候都可以改變為指向另一個物件.給引用賦值並不是改變它和原始物件的繫結關係. 

     (4) 引用的建立和銷燬並不會呼叫類的拷貝建構函式 

     (5) 語言層面,引用的用法和物件一樣;在二進位制層面,引用一般都是通過指標來實現的,只不過編譯器幫我們完成了轉換. 

     不存在空引用,並且引用一旦被初始化為指向一個物件,它就不能被改變為另一個物件的引用,顯得很安全。 

     const 指標仍然存在空指標,並且有可能產生野指標. 

     總的來說:引用既具有指標的效率,又具有變數使用的方便性和直觀性. 

 
5.引數傳遞有幾種方式;實現多型引數傳遞採用什麼方式,如果沒有使用某種方式原因是什麼; 

     傳值,傳指標或者引用

6.結合一個專案說明你怎樣應用設計模式的理念。 

     設計模式更多考慮是擴充套件和重用,而這兩方面很多情況下,往往會被忽略。 

     不過,我不建議濫用設計模式,以為它有可能使得簡單問題複雜化. 

  
7.介紹一下你對設計模式的理解。(這個過程中有很多很細節的問題隨機問的) 

     設計模式概念是由建築設計師Christopher Alexander提出:"每一個模式描述了一個在我們周圍不斷重複發生的問題,以及該問題的解決方案的核心.這樣,你就能一次又一次地使用該方案而不必做重複勞動."上述定義是對設計模式的廣義定義.將其應用到物件導向軟體的領域內,就形成了對設計模式的狹義定義. 

     可以簡單的認為:設計模式就是解決某個特定的物件導向軟體問題的特定方法, 並且已經上升到理論程度。 

     框架與設計模式的區別: 

     1,設計模式和框架針對的問題域不同.設計模式針對物件導向的問題域;框架針對特定業務的問題域 

     2,設計模式比框架更為抽象.設計模式在碰到具體問題後,才能產生程式碼;框架已經可以用程式碼表示 

     3,設計模式是比框架更小的體系結構元素.框架中可以包括多個設計模式 

     設計模式就像武術中基本的招式.將這些招式合理地縱組合起來,就形成套路(框架),框架是一種半成品. 

  
8.C++和C定義結構的分別是什麼。 

     C language 的結構僅僅是資料的結合 

     C plus plus的struct 和 class 其實具備幾乎一樣的功能,只是預設的訪問屬性不一樣而已。 

  
9.建構函式可否是虛汗數,為什麼?解構函式呢,可否是純虛的呢? 

     建構函式不能為虛擬函式,要構造一個物件,必須清楚地知道要構造什麼,否則無法構造一個物件。 

     解構函式可以為純虛擬函式。 

  
10.拷貝建構函式相關問題,深拷貝,淺拷貝,臨時物件等。 

     深拷貝意味著拷貝了資源和指標,而淺拷貝只是拷貝了指標,沒有拷貝資源 

     這樣使得兩個指標指向同一份資源,造成對同一份析構兩次,程式崩潰。 

     臨時物件的開銷比區域性物件小些。 

  
11.結合1個你認為比較能體現OOP思想的專案,用UML來描述。(最好這個專案繼承,多型,虛擬函式都有體現)這個問題大概會佔面試時間的一半,並且會問很多問題,一不小心可能會被問住) 



12.基類的有1個虛擬函式,子類還需要申明為virtual嗎?為什麼。 

     不申明沒有關係的。 

     不過,我總是喜歡顯式申明,使得程式碼更加清晰。 

  
13.C也可以通過精心封裝某些函式功能實現重用,那C++的類有什麼優點嗎,難道僅僅是為實現重用。 

     並不僅僅是這樣的。 

     OOD,OOP從根本上改變了程式設計模式和設計思想,具備重大和深遠的意義。 

     類的三大最基本的特徵:封裝,繼承,多型. 

  
14.C++特點是什麼,如何實現多型?畫出基類和子類在記憶體中的相互關係。 

     多型的基礎是繼承,需要虛擬函式的支援,簡單的多型是很簡單的。 

     子類繼承父類大部分的資源,不能繼承的有建構函式,解構函式,拷貝建構函式,operator=函式,友元函式等等 

  
15.為什麼要引入抽象基類和純虛擬函式? 

     主要目的是為了實現一種介面的效果。 

  
16.介紹一下模板和容器。如何實現?(也許會讓你當場舉例實現) 

     模板可以說比較古老了,但是當前的泛型程式設計實質上就是模板程式設計。 

     它體現了一種通用和泛化的思想。 

     STL有7種主要容器:vector,list,deque,map,multimap,set,multiset. 

  
17.你如何理解MVC。簡單舉例來說明其應用。 

     MVC模式是observer 模式的一個特例,典型的有MFC裡面的文件檢視架構。 

  
18.多重繼承如何消除向上繼承的二義性。 

     使用虛擬繼承即可. 

19. 以下三條輸出語句分別輸出什麼?[C易] 

 

  1. char str1[] = "abc";   
  2. char str2[] = "abc";   
  3. const char str3[] = "abc";   
  4. const char str4[] = "abc";   
  5. const char* str5 = "abc";   
  6. const char* str6 = "abc";   
  7. cout << boolalpha << ( str1==str2 ) << endl; // 輸出什麼?   
  8. cout << boolalpha << ( str3==str4 ) << endl; // 輸出什麼?   
  9. cout << boolalpha << ( str5==str6 ) << endl; // 輸出什麼?  
 

 


20. 非C++內建型別 A 和 B,在哪幾種情況下B能隱式轉化為A?[C++中等] 

答: 

a. class B : public A { ……} // B公有繼承自A,可以是間接繼承的 

b. class B { operator A( ); } // B實現了隱式轉化為A的轉化 

c. class A { A( const B& ); } // A實現了non-explicit的引數為B(可以有其他帶預設值的引數)建構函式 

d. A& operator= ( const A& ); // 賦值操作,雖不是正宗的隱式型別轉換,但也可以勉強算一個 


21. 以下程式碼中的兩個sizeof用法有問題嗎?[C易] 

 

22.求下面函式的返回值(微軟) 



假定x = 9999。 答案:8 

思路:將x轉化為2進位制,看含有的1的個數。 


23. 什麼是“引用”?申明和使用“引用”要注意哪些問題? 

答:引用就是某個目標變數的“別名”(alias),對應用的操作與對變數直接操作效果完全相同。申明一個引用的時候,切記要對其進行初始化。引用宣告完畢後,相當於目標變數名有兩個名稱,即該目標原名稱和引用名,不能再把該引用名作為其他變數名的別名。宣告一個引用,不是新定義了一個變數,它只表示該引用名是目標變數名的一個別名,它本身不是一種資料型別,因此引用本身不佔儲存單元,系統也不給引用分配儲存單元。不能建立陣列的引用。 


24. 將“引用”作為函式引數有哪些特點? 

(1)傳遞引用給函式與傳遞指標的效果是一樣的。這時,被調函式的形參就成為原來主調函式中的實參變數或物件的一個別名來使用,所以在被調函式中對形參變數的操作就是對其相應的目標物件(在主調函式中)的操作。 

(2)使用引用傳遞函式的引數,在記憶體中並沒有產生實參的副本,它是直接對實參操作;而使用一般變數傳遞函式的引數,當發生函式呼叫時,需要給形參分配儲存單元,形參變數是實參變數的副本;如果傳遞的是物件,還將呼叫拷貝建構函式。因此,當引數傳遞的資料較大時,用引用比用一般變數傳遞引數的效率和所佔空間都好。 

(3)使用指標作為函式的引數雖然也能達到與使用引用的效果,但是,在被調函式中同樣要給形參分配儲存單元,且需要重複使用"*指標變數名"的形式進行運算,這很容易產生錯誤且程式的閱讀性較差;另一方面,在主調函式的呼叫點處,必須用變數的地址作為實參。而引用更容易使用,更清晰。 


25. 在什麼時候需要使用“常引用”?  

如果既要利用引用提高程式的效率,又要保護傳遞給函式的資料不在函式中被改變,就應使用常引用。常引用宣告方式:const 型別識別符號 &引用名=目標變數名; 

例1 

int a ; 

const int &ra=a; 

ra=1; //錯誤 

a=1; //正確 


例2 

string foo( ); 

void bar(string & s); 

那麼下面的表示式將是非法的: 

bar(foo( )); 

bar("hello world"); 

原因在於foo( )和"hello world"串都會產生一個臨時物件,而在C++中,這些臨時物件都是const型別的。因此上面的表示式就是試圖將一個const型別的物件轉換為非const型別,這是非法的。 

引用型引數應該在能被定義為const的情況下,儘量定義為const 。 


26. 將“引用”作為函式返回值型別的格式、好處和需要遵守的規則? 

格式:型別識別符號 &函式名(形參列表及型別說明){ //函式體 } 

好處:在記憶體中不產生被返回值的副本;(注意:正是因為這點原因,所以返回一個區域性變數的引用是不可取的。因為隨著該區域性變數生存期的結束,相應的引用也會失效,產生runtime error! 

注意事項: 

(1)不能返回區域性變數的引用。這條可以參照Effective C++[1]的Item 31。主要原因是區域性變數會在函式返回後被銷燬,因此被返回的引用就成為了"無所指"的引用,程式會進入未知狀態。

 

(2)不能返回函式內部new分配的記憶體的引用。這條可以參照Effective C++[1]的Item 31。雖然不存在區域性變數的被動銷燬問題,可對於這種情況(返回函式內部new分配記憶體的引用),又面臨其它尷尬局面。例如,被函式返回的引用只是作為一個臨時變數出現,而沒有被賦予一個實際的變數,那麼這個引用所指向的空間(由new分配)就無法釋放,造成memory leak。 

(3)可以返回類成員的引用,但最好是const。這條原則可以參照Effective C++[1]的Item 30。主要原因是當物件的屬性是與某種業務規則(business rule)相關聯的時候,其賦值常常與某些其它屬性或者物件的狀態有關,因此有必要將賦值操作封裝在一個業務規則當中。如果其它物件可以獲得該屬性的非常量引用(或指標),那麼對該屬性的單純賦值就會破壞業務規則的完整性。 

(4)流操作符過載返回值申明為“引用”的作用: 

流操作符<<和>>,這兩個操作符常常希望被連續使用,例如:cout << "hello" << endl; 因此這兩個操作符的返回值應該是一個仍然支援這兩個操作符的流引用。可選的其它方案包括:返回一個流物件和返回一個流物件指標。但是對於返回一個流物件,程式必須重新(拷貝)構造一個新的流物件,也就是說,連續的兩個<<操作符實際上是針對不同物件的!這無法讓人接受。對於返回一個流指標則不能連續使用<<操作符。因此,返回一個流物件引用是惟一選擇。這個唯一選擇很關鍵,它說明了引用的重要性以及無可替代性,也許這就是C++語言中引入引用這個概念的原因吧。賦值操作符=。這個操作符象流操作符一樣,是可以連續使用的,例如:x = j = 10;或者(x=10)=100;賦值操作符的返回值必須是一個左值,以便可以被繼續賦值。因此引用成了這個操作符的惟一返回值選擇。 

例3 



(5)在另外的一些操作符中,卻千萬不能返回引用:+-*/ 四則運算子。它們不能返回引用,Effective C++[1]的Item23詳細的討論了這個問題。主要原因是這四個操作符沒有side effect,因此,它們必須構造一個物件作為返回值,可選的方案包括:返回一個物件、返回一個區域性變數的引用,返回一個new分配的物件的引用、返回一個靜態物件引用。根據前面提到的引用作為返回值的三個規則,第2、3兩個方案都被否決了。靜態物件的引用又因為((a+b) == (c+d))會永遠為true而導致錯誤。所以可選的只剩下返回一個物件了。

 

27.引用與多型的關係? 

引用是除指標外另一個可以產生多型效果的手段。這意味著,一個基類的引用可以指向它的派生類例項。 

例4 

Class A; Class B : Class A{...}; B b; A& ref = b; 


28. 引用與指標的區別是什麼? 

指標通過某個指標變數指向一個物件後,對它所指向的變數間接操作。程式中使用指標,程式的可讀性差;而引用本身就是目標變數的別名,對引用的操作就是對目標變數的操作。此外,就是上面提到的對函式傳ref和pointer的區別。 


29. 什麼時候需要“引用”? 

流操作符<<和>>、賦值操作符=的返回值、拷貝建構函式的引數、賦值操作符=的引數、其它情況都推薦使用引用。

30. 結構與聯合有和區別? 

1. 結構和聯合都是由多個不同的資料型別成員組成, 但在任何同一時刻, 聯合中只存放了一個被選中的成員(所有成員共用一塊地址空間), 而結構的所有成員都存在(不同成員的存放地址不同)。 

2. 對於聯合的不同成員賦值, 將會對其它成員重寫, 原來成員的值就不存在了, 而對於結構的不同成員賦值是互不影響的。 


31. 下面關於“聯合”的題目的輸出? 

a) 



答案:266 (低位低地址,高位高地址,記憶體佔用情況是Ox010A) 

b) 


答案: AB (0x41對應'A',是低位;Ox42對應'B',是高位) 

6261 (number.i和number.half共用一塊地址空間) 


32. 已知strcpy的函式原型:char *strcpy(char *strDest, const char *strSrc)其中strDest 是目的字串,strSrc 是源字串。不呼叫C++/C 的字串庫函式,請編寫函式 strcpy。 

答案: 



33. 已知String類定義如下: 

class String 



public: 

String(const char *str = NULL); // 通用建構函式 

String(const String &another); // 拷貝建構函式 

~ String(); // 解構函式 

String & operater =(const String &rhs); // 賦值函式 

private: 

char *m_data; // 用於儲存字串 

}; 

嘗試寫出類的成員函式實現。 

答案: 
 

 

34. .h標頭檔案中的ifndef/define/endif 的作用? 

答: 

防止該標頭檔案被重複引用。 


35. #include<file.h> 與 #include "file.h"的區別? 

答: 

前者是從Standard Library的路徑尋找和引用file.h,而後者是從當前工作路徑搜尋並引用file.h。 


36.在C++ 程式中呼叫被C 編譯器編譯後的函式,為什麼要加extern “C”? 

首先,作為extern是C/C++語言中表明函式和全域性變數作用範圍(可見性)的關鍵字,該關鍵字告訴編譯器,其宣告的函式和變數可以在本模組或其它模組中使用。 

通常,在模組的標頭檔案中對本模組提供給其它模組引用的函式和全域性變數以關鍵字extern宣告。例如,如果模組B欲引用該模組A中定義的全域性變數和函式時只需包含模組A的標頭檔案即可。這樣,模組B中呼叫模組A中的函式時,在編譯階段,模組B雖然找不到該函式,但是並不會報錯;它會在連線階段中從模組A編譯生成的目的碼中找到此函式 

extern "C"是連線申明(linkage declaration),被extern "C"修飾的變數和函式是按照C語言方式編譯和連線的,來看看C++中對類似C的函式是怎樣編譯的: 

作為一種物件導向的語言,C++支援函式過載,而過程式語言C則不支援。函式被C++編譯後在符號庫中的名字與C語言的不同。例如,假設某個函式的原型為: 

void foo( int x, int y ); 

該函式被C編譯器編譯後在符號庫中的名字為_foo,而C++編譯器則會產生像_foo_int_int之類的名字(不同的編譯器可能生成的名字不同,但是都採用了相同的機制,生成的新名字稱為“mangled name”)。 

_foo_int_int這樣的名字包含了函式名、函式引數數量及型別資訊,C++就是靠這種機制來實現函式過載的。例如,在C++中,函式void foo( int x, int y )與void foo( int x, float y )編譯生成的符號是不相同的,後者為_foo_int_float。 

同樣地,C++中的變數除支援區域性變數外,還支援類成員變數和全域性變數。使用者所編寫程式的類成員變數可能與全域性變數同名,我們以"."來區分。而本質上,編譯器在進行編譯時,與函式的處理相似,也為類中的變數取了一個獨一無二的名字,這個名字與使用者程式中同名的全域性變數名字不同。 

未加extern "C"宣告時的連線方式 

假設在C++中,模組A的標頭檔案如下: 

// 模組A標頭檔案 moduleA.h 

#ifndef MODULE_A_H 

#define MODULE_A_H 

int foo( int x, int y ); 

#endif

在模組B中引用該函式: 

// 模組B實現檔案 moduleB.cpp 

#include "moduleA.h" 

foo(2,3); 

   

實際上,在連線階段,聯結器會從模組A生成的目標檔案moduleA.obj中尋找_foo_int_int這樣的符號! 

加extern "C"宣告後的編譯和連線方式 

加extern "C"宣告後,模組A的標頭檔案變為: 

// 模組A標頭檔案 moduleA.h 

#ifndef MODULE_A_H 

#define MODULE_A_H 

extern "C" int foo( int x, int y ); 

#endif   

在模組B的實現檔案中仍然呼叫foo( 2,3 ),其結果是: 

(1)模組A編譯生成foo的目的碼時,沒有對其名字進行特殊處理,採用了C語言的方式; 

(2)聯結器在為模組B的目的碼尋找foo(2,3)呼叫時,尋找的是未經修改的符號名_foo。 

如果在模組A中函式宣告瞭foo為extern "C"型別,而模組B中包含的是extern int foo( int x, int y ) ,則模組B找不到模組A中的函式;反之亦然。 

所以,可以用一句話概括extern “C”這個宣告的真實目的(任何語言中的任何語法特性的誕生都不是隨意而為的,來源於真實世界的需求驅動。我們在思考問題時,不能只停留在這個語言是怎麼做的,還要問一問它為什麼要這麼做,動機是什麼,這樣我們可以更深入地理解許多問題):實現C++與C及其它語言的混合程式設計。   

明白了C++中extern "C"的設立動機,我們下面來具體分析extern "C"通常的使用技巧: 

extern "C"的慣用法 

(1)在C++中引用C語言中的函式和變數,在包含C語言標頭檔案(假設為cExample.h)時,需進行下列處理: 

extern "C" 



#include "cExample.h" 



而在C語言的標頭檔案中,對其外部函式只能指定為extern型別,C語言中不支援extern "C"宣告,在.c檔案中包含了extern "C"時會出現編譯語法錯誤。 

C++引用C函式例子工程中包含的三個檔案的原始碼如下: 



如果C++呼叫一個C語言編寫的.DLL時,當包括.DLL的標頭檔案或宣告介面函式時,應加extern "C" { }。 

(2)在C中引用C++語言中的函式和變數時,C++的標頭檔案需新增extern "C",但是在C語言中不能直接引用宣告瞭extern "C"的該標頭檔案,應該僅將C檔案中將C++中定義的extern "C"函式宣告為extern型別。 

C引用C++函式例子工程中包含的三個檔案的原始碼如下: 

//C++標頭檔案 cppExample.h 

#ifndef CPP_EXAMPLE_H 

#define CPP_EXAMPLE_H

 

extern "C" int add( int x, int y ); 

#endif 

//C++實現檔案 cppExample.cpp 

#include "cppExample.h" 

int add( int x, int y ) 



return x + y; 




extern int add( int x, int y ); 

int main( int argc, char* argv[] ) 



add( 2, 3 ); 

return 0; 

}

37. 關聯、聚合(Aggregation)以及組合(Composition)的區別? 

涉及到UML中的一些概念:關聯是表示兩個類的一般性聯絡,比如“學生”和“老師”就是一種關聯關係;聚合表示has-a的關係,是一種相對鬆散的關係,聚合類不需要對被聚合類負責,如下圖所示,用空的菱形表示聚合關係: 


從實現的角度講,聚合可以表示為: 

class A {...} class B { A* a; .....} 

而組合表示contains-a的關係,關聯性強於聚合:組合類與被組合類有相同的生命週期,組合類要對被組合類負責,採用實心的菱形表示組合關係: 


實現的形式是: 

class A{...} class B{ A a; ...}

38.物件導向的三個基本特徵,並簡單敘述之? 

1. 封裝:將客觀事物抽象成類,每個類對自身的資料和方法實行protection(private, protected,public) 

2. 繼承:廣義的繼承有三種實現形式:實現繼承(指使用基類的屬性和方法而無需額外編碼的能力)、可視繼承(子窗體使用父窗體的外觀和實現程式碼)、介面繼承(僅使用屬性和方法,實現滯後到子類實現)。前兩種(類繼承)和後一種(物件組合=>介面繼承以及純虛擬函式)構成了功能複用的兩種方式。 

3. 多型:是將父物件設定成為和一個或更多的他的子物件相等的技術,賦值之後,父物件就可以根據當前賦值給它的子物件的特性以不同的方式運作。簡單的說,就是一句話:允許將子類型別的指標賦值給父類型別的指標。 


39. 過載(overload)和重寫(overried,有的書也叫做“覆蓋”)的區別? 

常考的題目。從定義上來說: 

過載:是指允許存在多個同名函式,而這些函式的參數列不同(或許引數個數不同,或許引數型別不同,或許兩者都不同)。 

重寫:是指子類重新定義復類虛擬函式的方法。 

從實現原理上來說: 

過載:編譯器根據函式不同的參數列,對同名函式的名稱做修飾,然後這些同名函式就成了不同的函式(至少對於編譯器來說是這樣的)。如,有兩個同名函式:function func(p:integer):integer;和function func(p:string):integer;。那麼編譯器做過修飾後的函式名稱可能是這樣的:int_func、str_func。對於這兩個函式的呼叫,在編譯器間就已經確定了,是靜態的。也就是說,它們的地址在編譯期就繫結了(早繫結),因此,過載和多型無關! 

重寫:和多型真正相關。當子類重新定義了父類的虛擬函式後,父類指標根據賦給它的不同的子類指標,動態的呼叫屬於子類的該函式,這樣的函式呼叫在編譯期間是無法確定的(呼叫的子類的虛擬函式的地址無法給出)。因此,這樣的函式地址是在執行期繫結的(晚繫結)。

40. 什麼是預編譯,何時需要預編譯:總是使用不經常改動的大型程式碼體。 

程式由多個模組組成,所有模組都使用一組標準的包含檔案和相同的編譯選項。在這種情況下,可以將所有包含檔案預編譯為一個預編譯頭。 


41. char * const p; 

  char const * p 

  const char *p 


  上述三個有什麼區別? 


  char * const p; //常量指標,p的值不可以修改 

  char const * p;//指向常量的指標,指向的常量值不可以改 

  const char *p; //和char const *p 


42. char str1[] = "abc"; 

  char str2[] = "abc"; 


  const char str3[] = "abc"; 

  const char str4[] = "abc"; 


  const char *str5 = "abc"; 

  const char *str6 = "abc"; 


  char *str7 = "abc"; 

  char *str8 = "abc"; 


  cout << ( str1 == str2 ) << endl; 

  cout << ( str3 == str4 ) << endl; 

  cout << ( str5 == str6 ) << endl; 


  cout << ( str7 == str8 ) << endl; 


  結果是:0 0 1 1 


  解答:str1,str2,str3,str4是陣列變數,它們有各自的記憶體空間;而str5,str6,str7,str8是指標,它們指向相同的常量區域。 


43. 以下程式碼中的兩個sizeof用法有問題嗎? 


  void UpperCase( char str[] ) // 將 str 中的小寫字母轉換成大寫字母 

  { 

  for( size_t i=0; i<sizeof(str)/sizeof(str[0]); ++i ) 

 if( 'a'<=str[i] && str[i]<='z' ) 

str[i] -= ('a'-'A' ); 

  } 

  char str[] = "aBcDe"; 

  cout << "str字元長度為: " << sizeof(str)/sizeof(str[0]) << endl; 

  UpperCase( str ); 

  cout << str << endl; 


答:函式內的sizeof有問題。根據語法,sizeof如用於陣列,只能測出靜態陣列的大小,無法檢測動態分配的或外部陣列大小。函式外的str是一個靜態定義的陣列,因此其大小為6,函式內的str實際只是一個指向字串的指標,沒有任何額外的與陣列相關的資訊,因此sizeof作用於上只將其當指標看,一個指標為4個位元組,因此返回4。 


44. 一個32位的機器,該機器的指標是多少位? 


指標是多少位只要看地址匯流排的位數就行了。80386以後的機子都是32的資料匯流排。所以指標的位數就是4個位元組了。 


45. main() 
  { 

   int a[5]={1,2,3,4,5};

int *ptr=(int *)(&a+1); 

   printf("%d,%d",*(a+1),*(ptr-1)); 

  } 


  輸出:2,5 


  *(a+1)就是a[1],*(ptr-1)就是a[4],執行結果是2,5 

  &a+1不是首地址+1,系統會認為加一個a陣列的偏移,是偏移了一個陣列的大小(本例是5個int) 

  int *ptr=(int *)(&a+1); 

  則ptr實際是&(a[5]),也就是a+5 

   

  原因如下: 


  &a是陣列指標,其型別為 int (*)[5]; 

  而指標加1要根據指標型別加上一定的值,不同型別的指標+1之後增加的大小不同。 

  a是長度為5的int陣列指標,所以要加 5*sizeof(int) 

  所以ptr實際是a[5] 

  但是prt與(&a+1)型別是不一樣的(這點很重要) 

  所以prt-1只會減去sizeof(int*) 

  a,&a的地址是一樣的,但意思不一樣,a是陣列首地址,也就是a[0]的地址,&a是物件(陣列)首地址,a+1是陣列下一元素的地址,即a[1],&a+1是下一個物件的地址,即a[5].

46.請問以下程式碼有什麼問題: 
  int main() 

  { 

   char a; 

   char *str=&a; 

   strcpy(str,"hello"); 

   printf(str); 

   return 0; 

  } 


  沒有為str分配記憶體空間,將會發生異常。問題出在將一個字串複製進一個字元變數指標所指地址。雖然可以正確輸出結果,但因為越界進行內在讀寫而導致程式崩潰。 


47.

  char* s="AAA"; 

  printf("%s",s); 

  s[0]='B'; 

  printf("%s",s); 


  有什麼錯? 


  "AAA"是字串常量。s是指標,指向這個字串常量,所以宣告s的時候就有問題。 


  cosnt char* s="AAA"; 


  然後又因為是常量,所以對是s[0]的賦值操作是不合法的。 


48.寫一個“標準”巨集,這個巨集輸入兩個引數並返回較小的一個。 


  .#define Min(X, Y) ((X)>(Y)?(Y):(X))//結尾沒有; 


49.嵌入式系統中經常要用到無限迴圈,你怎麼用C編寫死迴圈。 


  while(1){}或者for(;;)

50. 關鍵字static的作用是什麼? 


  定義靜態變數 


51.關鍵字const有什麼含意? 


  表示常量不可以修改的變數。 


52.關鍵字volatile有什麼含意?並舉出三個不同的例子? 


  提示編譯器物件的值可能在編譯器未監測到的情況下改變。 


53. int (*s[10])(int) 表示的是什麼? 


  int (*s[10])(int) 函式指標陣列,每個指標指向一個int func(int param)的函式。 


54. 有以下表示式: 


  int a=248; b=4; 

  int const c=21; 

  const int *d=&a; 

  int *const e=&b; 

  int const *f const =&a; 


  請問下列表示式哪些會被編譯器禁止?為什麼? 


  *c=32;d=&b;*d=43;e=34;e=&a;f=0x321f; 

  *c 這是個什麼東東,禁止

*d 說了是const, 禁止 

  e = &a 說了是const 禁止 

  const *f const =&a; 禁止 


55. 交換兩個變數的值,不使用第三個變數。即a=3,b=5,交換之後a=5,b=3; 


  有兩種解法, 一種用算術演算法, 一種用^(異或) 

  a = a + b; 

  b = a - b; 

  a = a - b; 

  or 

  a = a^b;// 只能對int,char.. 

  b = a^b; 

  a = a^b; 

  or 

  a ^= b ^= a; 


56.c和c++中的struct有什麼不同? 


  c和c++中struct的主要區別是c中的struct不可以含有成員函式,而c++中的struct可以。c++中struct和class的主要區別在於預設的存取許可權不同,struct預設為public,而class預設為private。 


57.

 

  1. #include <stdio.h>   
  2.  #include <stdlib.h>   
  3.  void getmemory(char *p)   
  4.  {    
  5.   p=(char *) malloc(100);   
  6.   strcpy(p,"hello world");   
  7.  }    
  8.  int main( )   
  9.  {   
  10.   char *str=NULL;   
  11.   getmemory(str);   
  12.   printf("%s/n",str);   
  13.   free(str);   
  14.   return 0;   
  15.  }   
 

 

  程式崩潰,getmemory中的malloc 不能返回動態記憶體, free()對str操作很危險 


58.char szstr[10]; 

  strcpy(szstr,"0123456789"); 

  產生什麼結果?為什麼? 


  長度不一樣,會造成非法的OS 


59. 列舉幾種程式的同步機制,並比較其優缺點。 

  原子操作 

  訊號量機制 

  自旋鎖 

  管程,會合,分散式系統 


60. 程式之間通訊的途徑 


  共享儲存系統 

  訊息傳遞系統 

  管道:以檔案系統為基礎 


61. 程式死鎖的原因 

  資源競爭及程式推進順序非法 


62. 死鎖的4個必要條件 


  互斥、請求保持、不可剝奪、環路 


63.死鎖的處理 


  鴕鳥策略、預防策略、避免策略、檢測與解除死鎖 


64. 作業系統中程式排程策略有哪幾種? 


  FCFS(先來先服務),優先順序,時間片輪轉,多級反饋 


65. 類的靜態成員和非靜態成員有何區別? 


  類的靜態成員每個類只有一個,非靜態成員每個物件一個 


66.純虛擬函式如何定義?使用時應注意什麼? 


  virtual void f()=0; 

  是介面,子類必須要實現 


67. 陣列和連結串列的區別 

陣列:資料順序儲存,固定大小 
連表:資料可以隨機儲存,大小可動態改變

68. ISO的七層模型是什麼?tcp/udp是屬於哪一層?tcp/udp有何優缺點? 

  應用層 

  表示層 

  會話層 

  運輸層 

  網路層 

  物理鏈路層 

  物理層 

  tcp /udp屬於運輸層 

  TCP 服務提供了資料流傳輸、可靠性、有效流控制、全雙工操作和多路複用技術等。

與 TCP 不同, UDP 並不提供對 IP 協議的可靠機制、流控制以及錯誤恢復功能等。由於 UDP 比較簡單, UDP 頭包含很少的位元組,比 TCP 負載消耗少。 

  tcp: 提供穩定的傳輸服務,有流量控制,缺點是包頭大,冗餘性不好 
  udp: 不提供穩定的服務,包頭小,開銷小

69. (void *)ptr 和 (*(void**))ptr的結果是否相同? 

其中ptr為同一個指標(void *)ptr 和 (*(void**))ptr值是相同的 


70. 

 

  1. int main()   
  2. {   
  3.     int x=3;   
  4.     printf("%d",x);   
  5.     return 1;   
  6. }  
 
  問函式既然不會被其它函式呼叫,為什麼要返回1? 

  mian中,c標準認為0表示成功,非0表示錯誤。具體的值是某中具體出錯資訊 


71. 要對絕對地址0x100000賦值,我們可以用(unsigned int*)0x100000 = 1234;那麼要是想讓程式跳轉到絕對地址是0x100000去執行,應該怎麼做? 

  *((void (*)( ))0x100000 ) ( ); 

  首先要將0x100000強制轉換成函式指標,即: 

  (void (*)())0x100000 

  然後再呼叫它: 

  *((void (*)())0x100000)(); 

  用typedef可以看得更直觀些: 

  typedef void(*)() voidFuncPtr; 

  *((voidFuncPtr)0x100000)(); 


72.已知一個陣列table,用一個巨集定義,求出資料的元素個數 

  #define NTBL 

  #define NTBL (sizeof(table)/sizeof(table[0])) 


73. 執行緒與程式的區別和聯絡? 執行緒是否具有相同的堆疊? dll是否有獨立的堆疊? 

  程式是死的,只是一些資源的集合,真正的程式執行都是執行緒來完成的,程式啟動的時候作業系統就幫你建立了一個主執行緒。 

  每個執行緒有自己的堆疊。DLL中有沒有獨立的堆疊? 

  這個問題不好回答,或者說這個問題本身是否有問題。因為DLL中的程式碼是被某些執行緒所執行,只有執行緒擁有堆疊,如果DLL中的程式碼是EXE中的執行緒所呼叫,那麼這個時候是不是說這個DLL沒有自己獨立的堆疊?如果DLL中的程式碼是由DLL自己建立的執行緒所執行,那麼是不是說DLL有獨立的堆疊? 

  以上講的是堆疊,如果對於堆來說,每個DLL有自己的堆,所以如果是從DLL中動態分配的記憶體,最好是從DLL中刪除,如果你從DLL中分配記憶體,然後在EXE中,或者另外一個DLL中刪除,很有可能導致程式崩潰。 


74.unsigned short A = 10; 

  printf("~A = %u\n", ~A); 

  char c=128; 

  printf("c=%d\n",c); 


輸出多少?並分析過程 

  第一題,~A =0xfffffff5,int值 為-11,但輸出的是uint。所以輸出4294967285 


  第二題,c=0x10,輸出的是int,最高位為1,是負數,所以它的值就是0x00的補碼就是128,所以輸出-128。 

這兩道題都是在考察二進位制向int或uint轉換時的最高位處理。 


75.分析下面的程式: 

 

 

  1. void GetMemory(char **p,int num)   
  2. {   
  3.     *p=(char *)malloc(num);   
  4. }    
  5. int main()   
  6. {   
  7.     char *str=NULL;   
  8.     GetMemory(&str,100);   
  9.     strcpy(str,"hello");   
  10.     free(str);   
  11.     if(str!=NULL)   
  12.     {   
  13.         strcpy(str,"world");   
  14.     }    
  15.     printf("\n str is %s",str);  
  16.     getchar();   
  17. }  
  

 

  問輸出結果是什麼? 

  輸出str is world。 

  free 只是釋放的str指向的記憶體空間,它本身的值還是存在的.所以free之後,有一個好的習慣就是將str=NULL. 

此時str指向空間的記憶體已被回收,如果輸出語句之前還存在分配空間的操作的話,這段儲存空間是可能被重新分配給其他變數的, 

儘管這段程式確實是存在大大的問題(上面各位已經說得很清楚了),但是通常會列印出world來。 

這是因為,程式中的記憶體管理一般不是由作業系統完成的,而是由庫函式自己完成的。 

  當你malloc一塊記憶體的時候,管理庫向作業系統申請一塊空間(可能會比你申請的大一些),然後在這塊空間中記錄一些管理資訊(一般是在你申請的記憶體前面一點),並將可用記憶體的地址返回。但是釋放記憶體的時候,管理庫通常都不會將記憶體還給作業系統,因此你是可以繼續訪問這塊地址的。 

  char a[10],strlen(a)為什麼等於15?執行的結果

76.

 

  1. #include "stdio.h"   
  2. #include "string.h"   
  3. void main()   
  4. {   
  5.     char aa[10];   
  6.     printf("%d",strlen(aa));   
  7. }  
 
  sizeof()和初不初始化,沒有關係; 

  strlen()和初始化有關。 


77. char (*str)[20]; 

  char *str[20];  


78. long a=0x801010; 

  a+5=? 

  0x801010用二進位制表示為:“1000 0000 0001 0000 0001 0000”,十進位制的值為8392720,再加上5就是8392725羅 


79. 給定結構 

  struct A 

  { 

 char t:4; 

 char k:4; 

 unsigned short i:8; 

 unsigned long m; 

  }; 


  問sizeof(A) = ? 

   

  給定結構 

  struct A 

  { 

 char t:4; 4位 

 char k:4; 4位 

 unsigned short i:8; 8位  

  unsigned long m; // 偏移2位元組保證4位元組對齊 

  }; // 共8位元組 


80. 下面的函式實現在一個數上加一個數,有什麼錯誤?請改正。 

  int add_n ( int n ) 

  { 

  static int i = 100; 

  i += n; 

  return i; 

  } 


  當你第二次呼叫時得不到正確的結果,難道你寫個函式就是為了呼叫一次?問題就出在 static上? 


81.分析一下 

 

 

  1. #include<iostream.h>   
  2. #include <string.h>   
  3. #include <malloc.h>   
  4. #include <stdio.h>   
  5. #include <stdlib.h>   
  6. #include <memory.h>   
  7. typedef struct AA   
  8. {   
  9.       int b1:5;   
  10.       int b2:2;   
  11. }AA;   
  12. void main()   
  13. {   
  14.     AA aa;   
  15.     char cc[100];   
  16.     strcpy(cc,"0123456789abcdefghijklmnopqrstuvwxyz");  
  17.     memcpy(&aa,cc,sizeof(AA));   
  18.     cout << aa.b1 <<endl;   
  19.     cout << aa.b2 <<endl;   
  20. }   
 

 


  答案是 -16和1 

  首先sizeof(AA)的大小為4,b1和b2分別佔5bit和2bit.經過strcpy和memcpy後,aa的4個位元組所存放的值是: 0,1,2,3的ASC碼,即00110000,00110001,00110010,00110011所以,最後一步:顯示的是這4個位元組的前5位,和之後的2位分別為:10000,和01,因為int是有正負之分   

  所以:答案是-16和1

82.求函式返回值,輸入x=9999; 

 

  1. int func ( x )   
  2.   {   
  3.   int countx = 0;   
  4.    while ( x )   
  5.    {   
  6.    countx ++;   
  7.   x = x&(x-1);   
  8.    }   
  9.    return countx;   
  10.   }  
 


  結果呢? 


  知道了這是統計9999的二進位制數值中有多少個1的函式,且有9999=9×1024+512+256+15 


  9×1024中含有1的個數為2; 

  512中含有1的個數為1; 

  256中含有1的個數為1; 

  15中含有1的個數為4;

 

故共有1的個數為8,結果為8。 

  1000 - 1 = 0111,正好是原數取反。這就是原理。 

  用這種方法來求1的個數是很效率很高的。 

  不必去一個一個地移位。迴圈次數最少。 


  int a,b,c 請寫函式實現C=a+b ,不可以改變資料型別,如將c改為long int,關鍵是如何處理溢位問題 

  bool add (int a, int b,int *c) 

  { 
   *c=a+b; 

   return (a>0 && b>0 &&(*c<a || *c<b) || (a<0 && b<0 &&(*c>a || *c>b))); 
  }

83. 分析: 

  struct bit 

  {  

   int a:3; 

  int b:2; 

  int c:3; 

  }; 

  int main() 

  { 

   bit s; 

  char *c=(char*)&s; 

  cout<<sizeof(bit)<<endl; 

   *c=0x99; 

  cout << s.a <<endl <<s.b<<endl<<s.c<<endl; 

  int a=-1; 

  printf("%x",a); 

   return 0; 

  } 


  輸出為什麼是 

  4 

  1 

  -1 

  -4 

  ffffffff 


因為0x99在記憶體中表示為 100 11 001 , a = 001, b = 11, c = 100。當c為有符合數時, c = 100, 最高1為表示c為負數,負數在計算機用補碼錶示,所以c = -4;同理 b = -1;當c為有符合數時, c = 100,即 c = 4,同理 b = 3。

84.位域:有些資訊在儲存時,並不需要佔用一個完整的位元組, 而只需佔幾個或一個二進位制位。例如在存放一個開關量時,只有0和1 兩種狀態,用一位二進位即可。為了節省儲存空間,並使處理簡便,C語言又提供了一種資料結構,稱為“位域”或“位段”。所謂“位域”是把一個位元組中的二進位劃分為幾個不同的區域,並說明每個區域的位數。每個域有一個域名,允許在程式中按域名進行操作。這樣就可以把幾個不同的物件用一個位元組的二進位制位域來表示。一、位域的定義和位域變數的說明位域定義與結構定義相仿,其形式為:

struct 位域結構名 { 位域列表 }; 其中位域列表的形式為:型別說明符位域名:位域長度 
例如:  
  struct bs  

  {  

   int a:8;  

   int b:2;  

   int c:6;  

  };  


  位域變數的說明與結構變數說明的方式相同。可採用先定義後說明,同時定義說明或者直接說明這三種方式。例如:  

  struct bs  

  {  

   int a:8;  

   int b:2;  

   int c:6;  

  }data;  


  說明data為bs變數,共佔兩個位元組。其中位域a佔8位,位域b佔2位,位域c佔6位。對於位域的定義尚有以下幾點說明:  


  一個位域必須儲存在同一個位元組中,不能跨兩個位元組。如一個位元組所剩空間不夠存放另一位域時,應從下一單元起存放該位域。也可以有意使某位域從下一單元開始。例如:  

   

  struct bs  

  {  

   unsigned a:4  

   unsigned :0  

   unsigned b:4  

   unsigned c:4  

  }  


  在這個位域定義中,a佔第一位元組的4位,後4位填0表示不使用,b從第二位元組開始,佔用4位,c佔用4位。  


  由於位域不允許跨兩個位元組,因此位域的長度不能大於一個位元組的長度,也就是說不能超過8位二進位。  


  位域可以無位域名,這時它只用來作填充或調整位置。無名的位域是不能使用的。例如:  


  struct k  

  {  

   int a:1  

   int :2  

   int b:3  

   int c:2  

  };  


  從以上分析可以看出,位域在本質上就是一種結構型別,不過其成員是按二進位分配的。  


  位域的使用位域的使用和結構成員的使用相同,其一般形式為:位域變數名?位域名位域允許用各種格式輸出。  


  main() 

  {  

   struct bs  

   {  

    unsigned a:1;  

    unsigned b:3;  

    unsigned c:4;  

   } 

   bit,*pbit;  

   bit.a=1;  

   bit.b=7;  

   bit.c=15;  

   pri

85. 改錯: 
  #include <stdio.h> 

  int main(void) 

  { 

    int **p; 

  int arr[100]; 

    p = &arr; 

    return 0; 

  } 

  解答:搞錯了,是指標型別不同,int **p; //二級指標&arr; //得到的是指向第一維為100的陣列的指標 

  #include <stdio.h> 

  int main(void) 

  { 

   int **p, *q; 

   int arr[100]; 

   q = arr; 

   p = &q; 

   return 0; 

  }

相關文章