C语言再学习 -- 详解C++/C 面试题 1
參看:《高質量C++ C編程指南》.林銳
對這篇文章記憶猶新,因為之前找工作面試的時候,遇到過一家公司就是用的這套面試題。現在就結合考查的知識點和我總結完 C 語言再學習后的深入理解,來詳細的講講我對這篇文章的總結。
一、請填寫BOOL , float,指針變量 與“零值”比較的if語句。(10分)
提示:這里“零值”可以是0, 0.0 , FALSE或者“空指針”。例如int變量n與“零值”
比較的 if 語句為:
if ( n == 0 )
if ( n != 0 )
以此類推。
1、請寫出BOOL flag與“零值”比較的if語句:
標準答案:
if ( flag )
if ( !flag )
如下寫法均屬不良風格,不得分。
if (flag == TRUE)
if (flag == 1 )
if (flag == FALSE)
if (flag == 0)
2、請寫出 float x與“零值”比較的if語句:
標準答案示例:
const float EPSINON = 0.00001;
if ((x >= - EPSINON) && (x <= EPSINON)
不可將浮點變量用“ ==”或“! =”與數字比較,應該設法轉化成“ >=”或“ <=”此類形式。
如下是錯誤的寫法,不得分。
if (x == 0.0)
if (x != 0.0)
3、請寫出 char *p與“零值”比較的if語句:
標準答案:
if (p == NULL)
if (p != NULL)
如下寫法均屬不良風格,不得分。
if (p == 0)
if (p != 0)
if (p)
if (!)
解答:
1、根據布爾類型的語義,零值為“假”(記為 FALSE),任何非零值都是“真”(記為TRUE)。
/usr/include/curses.h文件/* X/Open and SVr4 specify that curses implements 'bool'. However, C++ may also* implement it. If so, we must use the C++ compiler's type to avoid conflict* with other interfaces.** A further complication is that <stdbool.h> may declare 'bool' to be a* different type, such as an enum which is not necessarily compatible with* C++. If we have <stdbool.h>, make 'bool' a macro, so users may #undef it.* Otherwise, let it remain a typedef to avoid conflicts with other #define's.* In either case, make a typedef for NCURSES_BOOL which can be used if needed* from either C or C++.*/#undef TRUE #define TRUE 1#undef FALSE #define FALSE 0
2、在浮點數比較中不能使用 < 和 >,千萬要留意,無論是 float 還是 double 類型的變量,都有精度限制。所以一定要避免將浮點變量用“==”或“!=”與數字比較,應該設法轉化成“>=”或“<=”形式。?
請寫出?float x?與“零值”比較的?if?語句
const float EPSINON = 0.000001;
if ((x >= - EPSINON) && (x <= EPSINON)
3、參看:C語言再學習 -- NUL和NULL的區別 NULL用于表示什么也不指向,也就是空指針((void *)0)。#include <stdio.h> #include <curses.h>int main (void) {char *p = NULL;if (p != (void*)0){printf ("11111111\n");}printf ("22222222\n");return 0; } 輸出結果: 22222222 程序員為了防止將 if (p == NULL) 誤寫成 if (p = NULL),而有意把 p 和 NULL 顛倒。編譯器認為 if (p = NULL) 是合法的,但是會指出 if (NULL = p)是錯誤的,因為 NULL不能被賦值。
擴展:在表達式中使用無符號數 庫函數 strlen 的原型如下: size_t strlen (char const *string); 注意:strlen 返回一個類型為 size_t 的值。這個類型是在頭文件 stddef.h 中定義的,它是一個無符號整數類型。在表達式中使用無符號數可能導致不可預料的結果。例如下面的表達式:#include <stdio.h> #include <string.h> int main (void) { char ptr1[] = "beijing"; char ptr2[] = "hello world"; if (strlen (ptr1) - strlen (ptr2) >= 0) { printf ("1111111111\n"); } printf ("2222222222\n"); return 0; } 輸出結果: 1111111111 2222222222 但 strlen (ptr1) - strlen (ptr2) 為無符號類型,得不到想要的結果,應該為?if (strlen (ptr1) >= strlen (ptr2))?#include <stdio.h> #include <string.h> int main (void) { char ptr1[] = "beijing"; char ptr2[] = "hello world"; if (strlen (ptr1) >= strlen (ptr2)) { printf ("1111111111\n"); } printf ("2222222222\n"); return 0; } 輸出結果: 2222222222
二、以下為Windows NT下的32位C++程序,請計算sizeof的值(10分)
void Func ( char str[100]){
請計算
sizeof( str ) = 4
}
char str[] = “Hello” ;
char *p = str ;
int n = 10;
請計算
sizeof (str ) = 6
sizeof ( p ) = 4
sizeof ( n ) = 4
void *p = malloc( 100 );
請計算
sizeof ( p ) = 4
解答:
參看:C語言再學習 -- 關鍵字sizeof與strlen記住這兩句話: 在 32 位系統下,不管什么樣的指針類型,其大小都為 4 byte。
參數傳遞數組永遠都是傳遞指向數組首元素的指針。
三、簡答題(25分)
1、頭文件中的ifndef/define/endif干什么用?
答:防止該頭文件被重復引用。2、#include <filename.h>和#include “filename.h”有什么區別?
答:對于#include <filename.h> ,編譯器從標準庫路徑開始搜索 filename.h對于#include “filename.h” ,編譯器從用戶的工作路徑開始搜索 filename.h
3、const有什么用途?(請至少說明兩種)
( 1)可以定義 const 常量( 2) const 可以修飾函數的參數、返回值,甚至函數的定義體。被 const 修飾的東西都受到強制保護,可以預防意外的變動,能提高程序的健壯性。
4、在C++程序中調用被C編譯器編譯后的函數,為什么要加extern “C”聲明?
答: C++語言支持函數重載, C 語言不支持函數重載。函數被 C++編譯后在庫中的名字與 C 語言的不同。假設某個函數的原型為: void foo(int x, int y);該 函 數 被 C 編 譯 器 編 譯 后 在 庫 中 的 名 字 為 _foo, 而 C++編 譯 器 則 會 產 生 像_foo_int_int 之類的名字。C++提供了 C 連接交換指定符號 extern“ C”來解決名字匹配問題。5、請簡述以下兩個for循環的優缺點
// 第一個for (i=0; i<N; i++) { if (condition) DoSomething(); else DoOtherthing(); } 優點:程序簡潔缺點:多執行了 N-1 次邏輯判斷,并且打斷了循環“流水線”作業,使得編譯器不能對循環進行優化處理,降低了效率。
// 第二個if (condition) { for (i=0; i<N; i++) DoSomething(); } else { for (i=0; i<N; i++) DoOtherthing(); } 優點:循環的效率高
缺點:程序不簡潔
解答:
1、參看:C語言再學習 -- C 預處理器#ifndef 標識符 程序段1 #else 程序段2 #endif 它的作用是,若標識符未被定義則編譯程序段1,否則編譯程序段2。
一般地,當某文件包含幾個頭文件,而且每個頭文件都可能定義了相同的宏,使用#ifndef可以防止該宏重復定義。
//頭文件衛士 #ifndef __THINGS_H__ #define __THINGS_H__ #endif
2、?參看:C語言再學習 -- C 預處理器
#include <filename.h> ? ?文件名放在尖括號中
在UNIX系統中,尖括號告訴預處理器在一個或多個標準系統目錄中尋找文件。?
如: #include <stdio.h>
查看:
ls /usr/include
ls kernel/include?
#include "filename.h" ? ?文件名放在雙引號中
在UNIX系統中,雙引號告訴預處理器現在當前目錄(或文件名中指定的其他目錄)中尋找文件,然后在標準位置尋找文件。
如: #include "hot.h" ? ? #include "/usr/buffer/p.h"
3、參看:C語言再學習 -- 關鍵字const
const 修飾類型
1、const 修飾一般常量
2、const修飾指針、數組
3、const 修飾函數的形參和返回值
4、const 修飾常對象
5、const 修飾常引用
6、const 修飾類的成員變量
7、const 修飾類的成員函數
const 作用
1)可以定義 const 常量,具有不可變性。
2)便于進行類型檢查,使編譯器對處理內容有更多了解,消除一些隱患。
3)可以避免意義模糊的數字出現,同樣可以很方便進行參數的調整和修改。同宏定義一樣,可以做到不變則已,一變都變。
4)可以保護被修改的東西,防止意外的修改,增強程序的健壯性。
5)可以節省空間,避免不必要的內存分配。
6)為函數重載提供了一個參考
7)提高效率
4、參看:C語言再學習 -- 存儲類型關鍵字
C 程序中,不允許出現類型不同的同名變量。而C++程序中 卻允許出現重載。重載的定義:同一個作用域,函數名相同,參數表不同的函數構成重載關系。因此會造成鏈接時找不到對應函數的情況,此時C函數就需要用extern “C”進行鏈接指定,來解決名字匹配問題。簡單來說就是,extern “C”這個聲明的真實目的是為了實現C++與C及其它語言的混合編程。
5、參看:C語言再學習 -- 循環語句
第一個程序比第二個程序多執行了 N-1 次邏輯判斷。并且由于前者老要進行邏輯判斷,打斷了循環“流水線”作業,使得編譯器不能對循環進行優化處理,降低了效率。如果 N 非常大,最好采用第二個程序的寫法,可以提高效率。如果 N 非常小,兩者效率差別并不明顯,采用第一個程序的寫法比較好,因為程序更加簡潔。
四、有關內存的思考題(20分)
void GetMemory(char *p) { p = (char *)malloc(100); } void Test(void) { char *str = NULL; GetMemory(str); strcpy(str, "hello world"); printf(str); }請問運行 Test 函數會有什么樣的結果?
答:程序崩潰。
因為 GetMemory 并不能傳遞動態內存,Test 函數中的 str 一直都是 NULL。、
char *GetMemory(void) { char p[] = "hello world"; return p; } void Test(void) { char *str = NULL; str = GetMemory(); printf(str); }請問運行 Test 函數會有什么樣的結果?
答:可能是亂碼。
因為 GetMemory 返回的是指向“棧內存”的指針,該指針的地址不是 NULL,但其原現的內容已經被清除,新內容不可知。
Void GetMemory2(char **p, int num) { *p = (char *)malloc(num); } void Test(void) { char *str = NULL; GetMemory(&str, 100); strcpy(str, "hello"); printf(str); }請問運行 Test 函數會有什么樣的結果?
答:能夠輸出 hello
內存泄漏
void Test(void) { char *str = (char *) malloc(100); strcpy(str, “hello”); free(str); if(str != NULL) { strcpy(str, “world”); printf(str); } }請問運行 Test 函數會有什么樣的結果?
答:篡改動態內存區的內容,后果難以預料,非常危險。
因為 free(str);之后, str 成為野指針,if(str != NULL)語句不起作用。
解答:
主要理解,堆和棧的區別。棧,自動釋放內存,不能跨函數使用存儲區。堆,手動釋放內存,可跨函數使用存儲區。 內存分配方式有三種:(1) 從靜態存儲區域分配。內存在程序編譯的時候就已經分配好,這塊內存在程序的整個運行期間都存在。例如全局變量, static 變量。
(2) 在棧上創建。在執行函數時,函數內局部變量的存儲單元都可以在棧上創建,函數執行結束時這些存儲單元自動被釋放。棧內存分配運算內置于處理器的指令集中,效率很高,但是分配的內存容量有限。
(3) 從堆上分配,亦稱動態內存分配。程序在運行的時候用 malloc 或 new 申請任意多少的內存,程序員自己負責在何時用 free 或 delete 釋放內存。動態內存的生存期由我們決定,使用非常靈活,但問題也最多。
參看:C語言再學習 -- 再論內存管理
1、出現 段錯誤(核心已轉儲)
參看:C語言再學習 -- 再論數組和指針
#include <stdio.h> #include <stdlib.h> #include <string.h>void GetMemory( char *p ) {p = (char *) malloc( 100 ); } void Test( void ) {char *str = NULL;GetMemory( str );strcpy( str, "hello world" );puts (str); }int main (void) {Test ();return 0; } 輸出結果: 段錯誤 (核心已轉儲) 解決方法:第一種方法:使用二級指針作為函數的形式參數,可以讓被調用函數使用其他函數的指針類型存儲區#include <stdio.h> #include <stdlib.h> #include <string.h> void fa(char** p) //主要還是指針的問題 { *p=(char* )malloc(100); if(*p) { return; } } int main() { char* str=NULL;//這塊沒問題的 fa(&str); strcpy(str,"hello"); printf("%s\n",str); free(str); str=NULL; return 0; } 輸出結果: hello第二種方法:使用返回值#include <stdio.h> #include <stdlib.h> #include <string.h> char* fa(char* p) //主要還是指針的問題 { p=(char* )malloc(100); return p; } int main() { char* str=NULL;//這塊沒問題的 str = fa(str); strcpy(str,"hello"); printf("%s\n",str); free(str); str=NULL; return 0; } 輸出結果: hello 2、警告: 函數返回局部變量的地址 [默認啟用]#include <stdio.h> #include <stdlib.h> #include <string.h> char *GetMemory( void ) {char p[] = "hello world";return p; } void Test( void ) {char *str = NULL;str = GetMemory();puts (str); }int main (void) {Test ();return 0; } 輸出結果: 警告: 函數返回局部變量的地址 [默認啟用]解決方法:#include <stdio.h> #include <stdlib.h> #include <string.h> char *GetMemory (char* p_str) {char *p = p_str;p = "hello world";return p; } void Test( void ) {char *str = NULL;str = GetMemory(str);puts (str); }int main (void) {Test ();return 0; } 輸出結果: hello world3、段錯誤(核心已轉儲)//循環多次執行后 #include <stdio.h> #include <stdlib.h> #include <string.h> void GetMemory (char **p, int num) {*p = (char *)malloc(num*sizeof (char)); } void Test (void) {char *str = NULL;GetMemory (&str, 100000);strcpy (str, "hello");puts (str); }int main (void) {while (1){Test ();}return 0; } 輸出結果: hello hello 。。。 hello hello 段錯誤 (核心已轉儲)解決方法:#include <stdio.h> #include <stdlib.h> #include <string.h> void GetMemory (char **p, int num) {*p = (char *)malloc(num*sizeof (char)); } void Test (void) {char *str = NULL;GetMemory (&str, 100000);strcpy (str, "hello");puts (str);free (str); //釋放str = NULL; }int main (void) {while (1){Test ();}return 0; } 輸出結果: hello hello 。。。4、結果難料#include <stdio.h> #include <stdlib.h> #include <string.h> void Test(void) {char *str = (char *) malloc(100);strcpy(str, "hello");puts (str);free(str);if(str != NULL){strcpy (str, "world");puts (str);} }int main (void) {Test ();return 0; } 輸出結果: hello world
五、編寫 strcpy 函數( 10 分)
已知 strcpy 函數的原型是 char *strcpy(char *strDest, const char *strSrc); 其中 strDest 是目的字符串, strSrc 是源字符串。 ( 1)不調用 C++/C 的字符串庫函數,請編寫函數 strcpychar *strcpy(char *strDest, const char *strSrc); { assert((strDest!=NULL) && (strSrc !=NULL)); // 2分 char *address = strDest; // 2分 while( (*strDest++ = * strSrc++) != ‘\0’ ) // 2分 NULL ; return address ; // 2分 } ( 2) strcpy 能把 strSrc 的內容復制到 strDest,為什么還要 char * 類型的返回值?答:為了實現鏈式表達式。 // 2 分 例如 int length = strlen( strcpy( strDest, “hello world”) ); 解答:參看:C語言再學習 -- 字符串和字符串函數1、功能實現函數: char *strcpy(char *dest, const char *src) { char *tmp = dest; while ((*dest++ = *src++) != '\0') /* nothing */; return tmp; } 2、char*類型,它返回的是第一個參數的值,即一個字符的地址#include <stdio.h> #include <string.h> #define WORDS "best" #define SIZE 40 int main (void) { char *orig = WORDS; char copy[SIZE] = "Be the best that you can be."; char *ps; puts (orig); puts (copy); ps = strcpy (copy + 7 , orig); puts (copy); puts (ps); return 0; } 輸出結果: best Be the best that you can be. Be the best best六、編寫類 String 的構造函數、析構函數和賦值函數( 25 分)
已知類 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::~String(void) // 3 分 { delete [] m_data; // 由于 m_data 是內部數據類型,也可以寫成 delete m_data; } // String 的普通構造函數 String::String(const char *str) // 6 分 { if(str==NULL) { m_data = new char[1]; // 若能加 NULL 判斷則更好 *m_data = ‘\0’; } else { int length = strlen(str); m_data = new char[length+1]; // 若能加 NULL 判斷則更好 strcpy(m_data, str); } } // 拷貝構造函數 String::String(const String &other) // 3 分 { int length = strlen(other.m_data); m_data = new char[length+1]; // 若能加 NULL 判斷則更好 strcpy(m_data, other.m_data); } // 賦值函數 String & String::operate =(const String &other) // 13 分 { // (1) 檢查自賦值 // 4 分 if(this == &other) return *this; // (2) 釋放原有的內存資源 // 3 分 delete [] m_data; // ( 3)分配新的內存資源,并復制內容 // 3 分 int length = strlen(other.m_data); m_data = new char[length+1]; // 若能加 NULL 判斷則更好 strcpy(m_data, other.m_data); // ( 4)返回本對象的引用 // 3 分 return *this; } 解答:如果不做C++,理解這四個函數,就夠了!
總結
以上是生活随笔為你收集整理的C语言再学习 -- 详解C++/C 面试题 1的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: C语言再学习 -- 再论数组和指针
- 下一篇: SaaS 通识系列 1:云计算是什么