1. 程式人生 > >C語言再學習 -- 詳解C++/C 面試題 1

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)

或者 if ( fabs (x) <= EPSINON)   // fabs (x) 取 x 的絕對值,其中EPSINON是允許的誤差(即精度)。 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下的32C++程式,請計算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

3const有什麼用途?(請至少說明兩種)

( 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 的字串庫函式,請編寫函式 strcpy
char *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分
}
2strcpy 能把 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++,理解這四個函式,就夠了!