C語言再學習 -- 詳解C++/C 面試題 1
對這篇文章記憶猶新,因為之前找工作面試的時候,遇到過一家公司就是用的這套面試題。現在就結合考查的知識點和我總結完 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)
#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
解答:
在 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();
}
優點:迴圈的效率高缺點:程式不簡潔
解答:
#ifndef 識別符號
程式段1
#else
程式段2
#endif
它的作用是,若識別符號未被定義則編譯程式段1,否則編譯程式段2。
一般地,當某檔案包含幾個標頭檔案,而且每個標頭檔案都可能定義了相同的巨集,使用#ifndef可以防止該巨集重複定義。
//標頭檔案衛士
#ifndef __THINGS_H__
#define __THINGS_H__
#endif
#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"
const 修飾型別
1、const 修飾一般常量
2、const修飾指標、陣列
3、const 修飾函式的形參和返回值
4、const 修飾常物件
5、const 修飾常引用
6、const 修飾類的成員變數
7、const 修飾類的成員函式
const 作用
1)可以定義 const 常量,具有不可變性。
2)便於進行型別檢查,使編譯器對處理內容有更多瞭解,消除一些隱患。
3)可以避免意義模糊的數字出現,同樣可以很方便進行引數的調整和修改。同巨集定義一樣,可以做到不變則已,一變都變。
4)可以保護被修改的東西,防止意外的修改,增強程式的健壯性。
5)可以節省空間,避免不必要的記憶體分配。
6)為函式過載提供了一個參考
7)提高效率
C 程式中,不允許出現型別不同的同名變數。而C++程式中 卻允許出現過載。過載的定義:同一個作用域,函式名相同,引數表不同的函式構成過載關係。因此會造成連結時找不到對應函式的情況,此時C函式就需要用extern “C”進行連結指定,來解決名字匹配問題。簡單來說就是,extern “C”這個宣告的真實目的是為了實現C++與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 釋放記憶體。動態記憶體的生存期由我們決定,使用非常靈活,但問題也最多。
1、出現 段錯誤(核心已轉儲)
#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 world
3、段錯誤(核心已轉儲)//迴圈多次執行後
#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”) );
解答:
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++,理解這四個函式,就夠了!