這個東西有些參考價值,和同學討論一下發現還是有些錯誤, 1.已知strcpy函數的原型是: char * strcpy(char * strDest,const char * strSrc); 1.不調用庫函數,實現strcpy函數。 2.解釋為什麼要返回char *。 解說: 1.strcpy的實現代碼 char * strcpy(char * strDest,const char * strSrc) { if ((strDest==NULL)||(strSrc==NULL)) //[1] throw "Invalid argument(s)"; //[2] char * strDestCopy=strDest; //[3] while ((*strDest++=*strSrc++)!='/0'); //[4] return strDestCopy; } 錯誤的做法: [1] (A)不檢查指標的有效性,說明答題者不注重代碼的健壯性。 (B)檢查指標的有效性時使用((!strDest)||(!strSrc))或(!(strDest&&strSrc)),說明答題者對C語言中類型的隱式轉換沒有深刻認識。在本例中char *轉換為bool即是類型隱式轉換,這種功能雖然靈活,但更多的是導致出錯機率增大和維護成本升高。所以C++專門增加了bool、true、false三個關鍵字以提供更安全的條件運算式。 (C)檢查指標的有效性時使用((strDest==0)||(strSrc==0)),說明答題者不知道使用常量的好處。直接使用字面常量(如本例中的0)會減少程式的可維護性。0雖然簡單,但程式中可能出現很多處對指標的檢查,萬一出現筆誤,編譯器不能發現,產生的程式內含邏輯錯誤,很難排除。而使用NULL代替0,如果出現拼字錯誤,編譯器就會檢查出來。 [2] (A)return new string("Invalid argument(s)");,說明答題者根本不知道傳回值的用途,並且他對記憶體流失也沒有警惕心。從函數中返回函數體內分配的記憶體是十分危險的做法,他把釋放記憶體的義務拋給不知情的調用者,絕大多數情況下,調用者不會釋放記憶體,這導致記憶體流失。 (B)return 0;,說明答題者沒有掌握異常機制。調用者有可能忘記檢查傳回值,調用者還可能無法檢查傳回值(見後面的鏈式運算式)。妄想讓傳回值肩負返回正確值和異常值的雙重功能,其結果往往是兩種功能都失效。應該以拋出異常來代替傳回值,這樣可以減輕調用者的負擔、使錯誤不會被忽略、增強程式的可維護性。 [3] (A)忘記儲存原始的strDest值,說明答題者邏輯思維不嚴密。 [4] (A)迴圈寫成while (*strDest++=*strSrc++);,同[1](B)。 (B)迴圈寫成while (*strSrc!='/0') *strDest++=*strSrc++;,說明答題者對邊界條件的檢查不力。迴圈體結束後,strDest字串的末尾沒有正確地加上'/0'。 2.返回strDest的原始值使函數能夠支援鏈式運算式,增加了函數的“附加值”。同樣功能的函數,如果能合理地提高的可用性,自然就更加理想。 鏈式運算式的形式如: int iLength=strlen(strcpy(strA,strB)); 又如: char * strA=strcpy(new char[10],strB); 返回strSrc的原始值是錯誤的。其一,源字串肯定是已知的,返回它沒有意義。其二,不能支援形如第二例的運算式。其三,為了保護源字串,形參用const限定strSrc所指的內容,把const char *作為char *返回,類型不符,編譯報錯。 參考文獻:林銳,《高品質程式設計指南--C/C++語言》,北京,電子工業出版社,2002.6。 2.已知類String 的原型為: class String { public: String(const char *str = NULL); // 普通建構函式 String(const String &other); // 拷貝建構函式 ~ String(void); // 解構函式 String & operate =(const String &other); // 賦值函數 private: char *m_data; // 用於儲存字串 }; 請編寫String 的上述4 個函數。 答案: String::String(const char *str) { if ( str == NULL ) //strlen在參數為NULL時會拋異常才會有這步判斷 { m_data = new char[1] ; m_data[0] = '' ; } else { m_data = new char[strlen(str) + 1]; strcpy(m_data,str); } } String::String(const String &other) { m_data = new char[strlen(other.m_data) + 1]; strcpy(m_data,other.m_data); } String & String::operator =(const String &other) { if ( this == &other) return *this ; delete []m_data; m_data = new char[strlen(other.m_data) + 1]; strcpy(m_data,other.m_data); return *this ; } String::~ String(void) { delete []m_data ; }3.簡答 3.1 標頭檔中的ifndef/define/endif 幹什麼用? 答:防止該標頭檔被重複引用。 3.2#i nclude <filename.h> 和#i nclude “filename.h” 有什麼區別? 答:對於#i nclude <filename.h> ,編譯器從標準庫路徑開始搜尋filename.h 對於#i nclude “filename.h”,編譯器從使用者的工作路徑開始搜尋filename.h 3.3 在C++ 程式中調用被C 編譯器編譯後的函數,為什麼要加extern “C”? 答:C++語言支援函數重載,C 語言不支援函數重載。函數被C++編譯後在庫中的名字與C 語言的不同。假設某個函數的原型為: void foo(int x, int y); 該函數被C 編譯器編譯後在庫中的名字為_foo , 而C++ 編譯器則會產生像_foo_int_int 之類的名字。 C++提供了C 串連交換指定符號extern“C”來解決名字匹配問題。 3.4 一個類有基類、內部有一個其他類的成員對象,建構函式的執行順序是怎樣的。(Autodesk) 答:先執行基類的(如果基類當中有虛基類,要先執行虛基類的,其他基類則按照聲明衍生類別時的順序依次執行),再執行成員對象的,最後執行自己的。 3.5 請描述一個你熟悉的設計模式(Autodesk) 3.6 在UML 中,彙總(aggregation)和組合(composition)有什麼區別 Autodesk) 答案:彙總關係更強,類似於pages 和book 的關係;組合關係要弱,類似於books和bookshelf 的關係。 3.7C#和C++除了文法上的差別以外,有什麼不同的地方?(Autodesk,Microsoft) 答案:(C#我只是瞭解,不是很精通) (1) c#有垃圾自動回收機制,程式員不用擔心對象的回收。(2)c#嚴禁使用指標,只能處理對象。如果希望使用指標,則僅可在unsafe 程式塊中能使用指標。(3)c#只能單繼承。(4)必須通過類名訪問靜態成員。不能像C++中那樣,通過對象訪問靜態成員。(5)在子類中覆蓋父類的虛函數時必須用關鍵字override,覆蓋父類的方法要用關鍵字new 3.8ADO.net 和ADO 的區別? 答案:實際上除了“能夠讓應用程式處理儲存於DBMS 中的資料“這一基本相似點外,兩者沒有太多共同之處。但是ADO 使用OLE DB 介面並基於微軟的COM 技術,而ADO.NET 擁有自己的ADO.NET 介面並且基於微軟的.NET 體系架構。眾所周知.NET 體系不同於COM 體系,ADO.NET 介面也就完全不同於ADO和OLE DB 介面,這也就是說ADO.NET 和ADO是兩種資料訪問方式。ADO.net 提供對XML 的支援。 3.9 New delete 與malloc free 的區別 ( Autodesk) 答案:用malloc 函數不能初始化對象,new 會調用對象的建構函式。Delete 會調用對象的destructor,而free 不會調用對象的destructor. 3.10 #define DOUBLE(x) x+x (Autodesk) i = 5*DOUBLE(10); i 是多少?正確的聲明是什嗎? 答案:i 為60。正確的聲明是#define DOUBLE(x) (x+x) 3.11 有哪幾種情況只能用intialization list 而不能用assignment? (Autodesk) 答案:當類中含有const、reference 成員變數;基類的建構函式都需要參數;類中含有其他類的成員對象,而該類的建構函式都需要參數。 3.11 C++是不是型別安全的? (Autodesk) 答案:不是。兩個不同類型的指標之間可以強制轉換。C#是型別安全的。 3.12 main 函數執行以前,還會執行什麼代碼? (Autodesk) 答案:全域對象的建構函式會在main 函數之前執行。 3.13 描述記憶體配置方式以及它們的區別。 (Autodesk , Microsoft) 答案:1) 從靜態儲存地區分配。記憶體在程式編譯的時候就已經分配好,這塊記憶體在程式的整個運行期間都存在。例如全域變數,static 變數。 (2) 在棧上建立。在執行函數時,函數內局部變數的儲存單元都可以在棧上建立,函數執行結束時這些儲存單元自動被釋放。棧記憶體配置運算內建於處理器的指令集。 (3) 從堆上分配,亦稱動態記憶體分配。程式在啟動並執行時候用malloc 或new 申請任意多少的記憶體,程式員自己負責在何時用free 或delete 釋放記憶體。動態記憶體的生存期由我們決定,使用非常靈活,但問題也最多。 3.14 什麼是虛擬儲存空間?virtual memory 怎樣映射到physical memory?頁面替換演算法有哪些? (Microsoft) 見作業系統 p238 頁。掌握的頁面替換演算法NRU,FIFO,第二次機會頁面替換演算法,LRU 3.15 有四個同樣的容器,裡面裝滿了粒數相同的藥丸,正常藥丸的品質為m,變質藥丸的品質為m+1,現在已知這四個容器中,有一個裝的全是變質藥丸,用電子秤只稱一次,找出哪個容器裝的是變質藥丸 (Microsoft) 答案:把四個容器依次編號為1、2、3、4,然後從中分別取出1、2、3、4 粒藥丸,稱這10 粒藥丸的品質,如果品質為10m+1,則說明第一個容器裝的是變質藥丸,如果為10m+2 則說明第二個裝的變質藥丸,依次類推。 3.16 比較一下C++中static_cast 和 dynamic_cast 的區別。 (Autodesk) dynamic_casts在協助你瀏覽繼承層次上是有限制的。它不能被用於缺乏虛函數的類型上,它被用於安全地沿著類的繼承關係向下進行類型轉換。如你想在沒有繼承關係的類型中進行轉換,你可能想到static_cast 3.17 Struct 和class 的區別 (Autodesk) 答案:struct 中成員變數和成員函數預設存取權限是public,class 是private 3.18 當一個類A 中沒有生命任何成員變數與成員函數,這時sizeof(A)的值是多少,如果不是零,請解釋一下編譯器為什麼沒有讓它為零。(Autodesk) 答案:肯定不是零。我舉個反例,如果是零的話,聲明一個class A[10]對象數組,而每一個對象佔用的空間是零,這時就沒辦法區分A[0],A[1]…了 3.19 在8086 彙編下,邏輯地址和物理地址是怎樣轉換的?(Intel) 答案:通用寄存器給出的地址,是段內位移地址,相應段寄存器地址*10H+通用寄存器內地址,就得到了真正要訪問的地址。 3.20 描述一下C++的多態 (microsoft) 答案:C++的多態表現在兩個部分,一個是靜態連編下的函數重載,運算子多載;動態連編下的虛函數、純虛函數(抽象類別) 4.寫出BOOL,int,float,指標類型的變數a 與零的比較語句。 答案: BOOL : if ( !a ) int : if ( a == 0) float : const EXPRESSION EXP = 0.000001 if ( a < EXP && a >-EXP) pointer : if ( a != NULL) 5.請說出const 與#define 相比優點 答案: (1) const 常量有資料類型,而宏常量沒有資料類型。編譯器可以對前者進行型別安全檢查。而對後者只進行字元替換,沒有型別安全檢查,並且在字元替換可能會產生意料不到的錯誤。 (2) 有些整合化的調試工具可以對const 常量進行調試,但是不能對宏常量進行調試。 6.簡述數組與指標的區別 數組要麼在靜態儲存區被建立(如全域數組),要麼在棧上被建立。指標可以隨時指向任意類型的記憶體塊。 (1)修改內容上的差別 char a[] = “hello”; a[0] = ‘X’; char *p = “world”; // 注意p 指向常量字串 p[0] = ‘X’; // 編譯器不能發現該錯誤,執行階段錯誤 (2) 用運算子sizeof 可以計算出數組的容量(位元組數)。sizeof(p),p 為指標得到的是一個指標變數的位元組數,而不是p 所指的記憶體容量。C++/C 語言沒有辦法知道指標所指的記憶體容量,除非在申請記憶體時記住它。注意當數組作為函數的參數進行傳遞時,該數組自動退化為同類型的指標。 char a[] = "hello world"; char *p = a; cout<< sizeof(a) << endl; // 12 位元組 cout<< sizeof(p) << endl; // 4 位元組 計算數組和指標的記憶體容量 void Func(char a[100]) { cout<< sizeof(a) << endl; // 4 位元組而不是100 位元組 } 7.類成員函數的重載、覆蓋和隱藏區別 答案: 成員函數被重載的特徵: (1)相同的範圍(在同一個類中); (2)函數名字相同; (3)參數不同; (4)virtual 關鍵字可有可無。 覆蓋是指衍生類別函數覆蓋基類函數,特徵是: (1)不同的範圍(分別位於衍生類別與基類); (2)函數名字相同; (3)參數相同; (4)基類函數必須有virtual 關鍵字。 “隱藏”是指衍生類別的函數屏蔽了與其同名的基類函數,規則如下: (1)如果衍生類別的函數與基類的函數同名,但是參數不同。此時,不論有無virtual關鍵字,基類的函數將被隱藏(注意別與重載混淆)。 (2)如果衍生類別的函數與基類的函數同名,並且參數也相同,但是基類函數沒有virtual 關鍵字。此時,基類的函數被隱藏(注意別與覆蓋混淆) 8.There are two int variables: a and b, don’t use “if”, “? :”, “switch” or other judgement statements, find out the biggest one of the two numbers. 答案:( ( a + b ) + abs( a – b ) ) / 2 9.如何列印出當前源檔案的檔案名稱以及源檔案的當前行號? 答案: cout << __FILE__ ; cout<<__LINE__ ; __FILE__和__LINE__是系統預定義宏,這種宏並不是在某個檔案中定義的,而是由編譯器定義的。 10.main 主函數執行完畢後,是否可能會再執行一段代碼,給出說明? 答案:可以,可以用_onexit 註冊一個函數,它會在main 之後執行int fn1(void), fn2(void), fn3(void), fn4 (void); void main( void ) { String str("zhanglin"); _onexit( fn1 ); _onexit( fn2 ); _onexit( fn3 ); _onexit( fn4 ); printf( "This is executed first./n" ); } int fn1() { printf( "next./n" ); return 0; } int fn2() { printf( "executed " ); return 0; } int fn3() { printf( "is " ); return 0; } int fn4() { printf( "This " ); return 0; } The _onexit function is passed the address of a function (func) to be called when the program terminates normally. Successive calls to _onexit create a register of functions that are executed in LIFO (last-in-first-out) order. The functions passed to _onexit cannot take parameters. 11.如何判斷一段程式是由C 編譯器還是由C++編譯器編譯的? 答案: #ifdef __cplusplus cout<<"c++"; #else cout<<"c"; #endif 12.檔案中有一組整數,要求排序後輸出到另一個檔案中 答案: void Order(vector<int> &data) //起泡排序 { int count = data.size() ; int tag = false ; for ( int i = 0 ; i < count ; i++) { for ( int j = 0 ; j < count - i - 1 ; j++) { if ( data[j] > data[j+1]) { tag = true ; int temp = data[j] ; data[j] = data[j+1] ; data[j+1] = temp ; } } if ( !tag ) break ; } } void main( void ) { vector<int>data; ifstream in("c://data.txt"); if ( !in) { cout<<"file error!"; exit(1); } int temp; while (!in.eof()) { in>>temp; data.push_back(temp); } in.close(); Order(data); ofstream out("c://result.txt"); if ( !out) { cout<<"file error!"; exit(1); } for ( i = 0 ; i < data.size() ; i++) out<<data[i]<<" "; out.close(); } 13.排序方法比較 (intel) 排序方法 平均時間 最壞時間 輔助儲存 直接插入排序 起泡排序 快速排序 簡單選擇排序 堆排序 歸併排序 基數排序 14.一個鏈表的結點結構 struct Node { int data ; Node *next ; }; typedef struct Node Node ; (1)已知鏈表的頭結點head,寫一個函數把這個鏈表逆序 ( Intel) Node * ReverseList(Node *head) //鏈表逆序 { if ( head == NULL || head->next == NULL ) return head; Node *p1 = head ; Node *p2 = p1->next ; Node *p3 = p2->next ; p1->next = NULL ; while ( p3 != NULL ) { p2->next = p1 ; p1 = p2 ; p2 = p3 ; p3 = p3->next ; } p2->next = p1 ; head = p2 ; return head ; } (2)已知兩個鏈表head1 和head2 各自有序,請把它們合并成一個鏈表依然有序。 Node * Merge(Node *head1 , Node *head2) { if ( head1 == NULL) return head2 ; if ( head2 == NULL) return head1 ; Node *head = NULL ; Node *p1 = NULL; Node *p2 = NULL; if ( head1->data < head2->data ) { head = head1 ; p1 = head1->next; p2 = head2 ; } else { head = head2 ; p2 = head2->next ; p1 = head1 ; } Node *pcurrent = head ; while ( p1 != NULL && p2 != NULL) { if ( p1->data <= p2->data ) { pcurrent->next = p1 ; pcurrent = p1 ; p1 = p1->next ; } else { pcurrent->next = p2 ; pcurrent = p2 ; p2 = p2->next ; } } if ( p1 != NULL ) pcurrent->next = p1 ; if ( p2 != NULL ) pcurrent->next = p2 ; return head ; } (2)已知兩個鏈表head1 和head2 各自有序,請把它們合并成一個鏈表依然有序,這次要求用遞迴方法進行。 ( Autodesk) 答案: Node * MergeRecursive(Node *head1 , Node *head2) { if ( head1 == NULL ) return head2 ; if ( head2 == NULL) return head1 ; Node *head = NULL ; if ( head1->data < head2->data ) { head = head1 ; head->next = MergeRecursive(head1->next,head2); } else { head = head2 ; head->next = MergeRecursive(head1,head2->next); } return head ; } 15.分析一下這段程式的輸出 (Autodesk) class B { public: B() { cout<<"default constructor"<<endl; } ~B() { cout<<"destructed"<<endl; } B(int i):data(i) { cout<<"constructed by parameter" << data <<endl; } private: int data; }; B Play( B b) { return b ; } int main(int argc, char* argv[]) { B temp = Play(5); return 0; } 請自己執行一下看看。 16.寫一個函數找出一個整數數組中,第二大的數 (microsoft) 答案: const int MINNUMBER = -32767 ; int find_sec_max( int data[] , int count) //類似於1 4 4 4這樣的序列將認為1是第二大數 { int maxnumber = data[0] ; int sec_max = MINNUMBER ; for ( int i = 1 ; i < count ; i++) { if ( data[i] > maxnumber ) { sec_max = maxnumber ; maxnumber = data[i] ; } else { if ( data[i] > sec_max ) sec_max = data[i] ; } } return sec_max ; } 17 寫一個在一個字串中尋找一個子串第一個位置的函數 這個題目的一般演算法比較簡單我就不給出了,如果要求高效率的話請參見資料結構中的KMP 演算法,不過在筆試時間有限情況下,寫出那個演算法還是挺難的。 英文題目 1. Introduce yourself in English 2. What is your great advantage you think of yourself? 3. What is your drawback you think of yourself? Maybe I will feel very tense if I make a speech in front of a lot of people. 4. How do you feel shanghai?
|