為了便於溫故而知新,特於此整理 C/C++ 方面相關面試題。分享,共勉。
(備注:各題的重要程度與先后順序無關。不斷更新中......歡迎補充)
(1)分析下面程序的輸出(* 與 -- 運算符優先級問題)
程序1:原題程序
1 #include<iostream> 2 using namespace std; 3 4 void main() 5 { 6 static int a[5] = {1, 2, 3, 4, 5}, b = 6; 7 int *p = (int *)(&a + 1); 8 cout << "*(a + 1) :: " << *(a + 1) << endl; 9 cout << "*p :: " << *p << endl; 10 cout << "*p-- :: " << *p-- << endl; 11 cout << "*p---b :: " << *p---b << endl; 12 cout << "*p :: " << *p << endl; 13 cout << "*p-- :: " << *p-- << endl; 14 15 system("pause"); 16 } 17 // run out 18 /* 19 *(a + 1) :: 2 20 *p :: 6 21 *p-- :: 6 22 *p---b :: -1 23 *p :: 4 24 *p-- :: 4 25 請按任意鍵繼續. . . 26 */
總結:參考《C++操作符的優先級》
程序2:對比分析
1 #include<iostream> 2 using namespace std; 3 4 void main() 5 { 6 static int ar[5] = {1, 2, 3, 4, 5}; 7 int cr = 6; 8 int *pr = (int *)(&ar + 1); 9 cout << "*(ar + 1) :: " << *(ar + 1) << endl; 10 cout << "*pr :: " << *pr << endl; 11 cout << "*pr-- :: " << *pr-- << endl; 12 cout << "*pr :: " << *pr << endl; 13 cout << "*pr---cr :: " << *pr---cr << endl; 14 cout << "*pr :: " << *pr << endl; 15 cout << "*pr-- :: " << *pr-- << endl; 16 17 system("pause"); 18 } 19 20 // run out 21 /* 22 *(ar + 1) :: 2 23 *pr :: 0 24 *pr-- :: 0 25 *pr :: 5 26 *pr---cr :: -1 27 *pr :: 4 28 *pr-- :: 4 29 請按任意鍵繼續. . . 30 */
(2)指針函數與函數指針的區別
指針函數是指一個返回指針類型的函數。函數指針是指一個指向函數的指針。
(3)函數模板與模板函數的區別
函數模板指由關鍵字template、typename(或class)定義的通用函數體。
將類型形參實例化的參數成為模板實參。模板函數指用模板實參實例化的函數。
即模板函數指將函數模板的類型形參實例化的過程。
(4)static 在 C/C++ 語言中的作用
1、static在C語言中的作用
第一、修飾局部變量。static修飾的局部變量只執行一次初始化,且延長了局部變量的生命周期,直到程序運行結束以后才釋放。
static修飾的局部變量存放在全局數據區的靜態變量區。初始化的時默認值為0;
第二、修飾全局變量。static修飾的全局變量只能在本文件中訪問,不能在其它文件中訪問,即便是加extern外部聲明也不可以。
第三、修飾函數。若static修飾一個函數,則這個函數的只能在本文件中調用,不能在其他文件被調用。即具有文件作用域。
2、static在C++語言中的作用
第一、類中靜態成員分為 靜態成員變量 和 靜態成員函數。
第二、靜態成員變量的名字在類的作用域中,可以避免命名沖突。
第三、靜態成員變量獨立於該類的任何對象而存在。
第四、靜態成員變量可以聲明為任意類型:常量、引用、數組、類本身類型等。
第五、靜態成員變量必須在類的定義體外部初始化值。
第六、靜態成員函數與一般普通成員函數最大的區別在於不存在this指針。因為這個函數是與類相關的函數,而不是與某一個對象相關。
第七、聲明靜態成員函數時在前面加關鍵字static,當在類外實現這個函數時,不允許加關鍵字。
第八、可以通過作用域操作符直接調用static靜態成員函數。或通過類的對象、引用或指向類對象的指針間接的調用static靜態成員函數。
第九、static靜態成員函數不是任何對象的組成部分,所以static成員函數不能被聲明為const。
(函數聲明為const是對函數this指針的進一步限定,而static成員函數本身就不存在this指針,所以再加const是沒有意義。)
第十、static靜態成員函數不可以被聲明為虛函數。虛函數是為實現多態的一種特殊成員函數,因為static函數沒有this指針,因此是沒有意義的。
總結以上內容:

3、參見隨筆《static關鍵字(1)》
4、參見隨筆《static關鍵字(C/C++中的作用)》
(5)相比於C函數,C++函數有哪些特色?
C++函數增加重載、內聯、const、virtual四種新機制。
重載和內聯機制既可以用於全局函數也可以用於類的成員函數。
const和virtual機制僅用於類的成員函數。
(6)重載、覆蓋、隱藏的區別
重載是指在同一個類中,同名函數,參數不同。重載的核心在於參數,參數有三個基本點:1、類型 2、數量 3、順序。(切記與返回值無關)。
注意:第一、作用域(全局函數與類中成員函數同名不算重載)。第二、類中成員函數加const修飾也算作重載范疇。
覆蓋是指派生類重寫(遵循三同原則)基類的虛函數。
隱藏分兩種情況:
一則:派生類與基類的函數同名,但參數不同,與關鍵字virtual無關;(不同於重載)
二則:派生類與基類的函數同名,並且同參,但無關鍵字virtual;(不同於覆蓋)
具體代碼可參見隨筆《 重載 覆蓋 隱藏 》
(7)函數strlen與操作符sizeof的區別:參見隨筆《strlen與sizeof》
(8)函數聲明。聲明函數原型時,函數每個形參的名稱可省略,主要聲明清楚每個參數的類型和返回值類型就可以了。
(9)表達式。所有的表達式都有值。
(10)編譯。程序的編譯是以文件為單位的,因此將程序分到多個文件中可以減少每次對程序修改后再編譯所帶來的工作量。
(11)靜態成員變量。類的靜態數據成員變量需要在類定義外進行初始化。
(12)友元。當將一個類S定義為另一個類A的友元類時,類S的所有成員函數都可以直接訪問類A的所有成員(成員變量和成員函數)。
(13)C/C++內存分配區別:
1、malloc / free是C語言標准的庫函數;new / delete是C++中的運算符。
2、都是在堆(heap)上進行動態的內存操作。
3、用malloc函數需要指定內存分配的字節數並且不能初始化對象;new 會自動調用對象的構造函數。
4、delete會調用對象的destructor,而free不會調用對象的destructor。

(14)如果創建的是靜態局部或全局對象,則對象的位模式全部為0,否則默認值將會是隨機的。
(15)析構函數是特殊的類成員函數,它沒有返回類型、沒有參數、不能隨意調用、也沒有重載,只有在類對象的生命期結束時,由系統自動調用。
(16)類中成員對象的構造是按照在類中定義的順序進行的,而不是按照構造函數冒號后的初始化列表順序進行構造的(這點尤其需要注意)。
(17)explicit關鍵字。普通構造函數可以被隱式調用,而被關鍵字explicit修飾的構造函數只能被顯式調用。
(18)拷貝構造函數使用情況:
1、一個對象以值傳遞的方式傳入函數體。
2、一個對象以值傳遞的方式從函數返回。
3、一個對象需要通過另外一個對象進行初始化。
(19)在ANSI C 語言中用什么來定義常量呢?答案是enum類型和#define宏,這兩個都可以用來定義常量。而在C++的類中實現常量,需要考慮使用枚舉。
(20)一個類的構造和析構函數都不能用const修飾。
(21)const修飾成員函數。const在類中對成員函數的三種作用(1、參數;2、返回值;3、函數體)。
(22)枚舉詳解
1、枚舉是一種類型。
2、默認的,第一個枚舉成員賦值為0,后面的每個枚舉成員的值比前面的大1。
3、枚舉成員本身是一個常量表達式,不可以改變其值。
4、可以顯式的定義枚舉成員的值,當隨機指定其中某個成員值后,位於其前的成員值仍為默認值,位於其后的成員值比前面的大一。
(23)引用與指針的區別
1、初始化要求不同。前者必須要初始化。
2、可修改性不同。前者一旦被初始化,它就不能被另一個對象引用,而指針在任何時候都可以指向另一個對象。
3、不存在NULL引用。引用必須要確定具體引用的是某個對象。
4、測試的區別。引用不會指向空值,使用引用前不需要測試它的合法性。指針可能為空值,使用前需要進行測試,避免空指針導致程序崩潰。
5、應用的區別。如果指向一個對象后就不會再改變指向,那么選擇使用引用。如果在不同的時刻需要指向不同的對象,應該使用指針。
(24)內聯是以代碼膨脹為代價的,僅僅省去了函數調用的開銷,從而提高了函數的執行效率,一般適合於使用頻率高,並且代碼量簡單的函數。
(25)內聯與宏的區別:
內聯函數在編譯時展開,宏在預編譯時展開。
在編譯時內聯函數可以直接被嵌入到目標代碼中,而宏在預處理是只是僅僅的文本替換。
內聯函數可以完成類型匹配,語句正確的判斷,而宏不具有。
宏不屬於函數,內聯函數屬於函數。
宏在定義時要小心處理宏參數,以免出現二義性。
(26)頭文件中的ifndef/define/endif干什么用?防止頭文件被重復引用。
(27)#include <filename.h> 和 #include “filename.h” 有什么區別?
對於#include <filename.h> ,編譯器從標准庫路徑開始搜索 filename.h
對於#include “filename.h”,編譯器從用戶的工作路徑開始搜索 filename.h
(28)const 有什么用途?
1、const即“只讀”。可以定義 const 常量。
2、const可以修飾成員函數的參數、返回值,甚至函數的定義體。
被const 修飾的東西都受到強制保護,可以預防意外的變動,能提高程序的健壯性。
3、參見隨筆《const》
(29)在C++ 程序中調用被 C 編譯器編譯后的函數,為什么要加 extern “C”?
C++語言支持函數重載,C 語言不支持函數重載。函數被C++編譯后在庫中的名字與C 語言的不同。假設某個函數的原型為:
void foo(int x, int y);該函數被C 編譯器編譯后在庫中的名字為_foo ,而C++編譯器則會產生像_foo_int_int 之類的名字。
C++提供了C 連接交換指定符號extern“C”來解決名字匹配問題。
(30)內存思考題1,代碼如下:
1 void GetMemory(char *p) 2 { 3 p = (char *)malloc(100); 4 } 5 6 void Test() 7 { 8 char *str = NULL; 9 GetMemory(str); 10 strcpy(str, "hello world"); 11 printf(str); 12 }
請問運行Test 函數會有什么樣的結果?
程序崩潰。因為GetMemory 並不能傳遞動態內存,
Test 函數中的 str 一直都是 NULL。
執行strcpy(str, "hello world"); 將使程序崩潰。
(31)內存思考題2,代碼如下:
1 char *GetMemory(void) 2 { 3 char p[] = "hello world"; 4 return p; 5 } 6 7 void Test(void) 8 { 9 char *str = NULL; 10 str = GetMemory(); 11 printf(str); 12 }
請問運行Test 函數會有什么樣的結果?
可能是亂碼。
因為GetMemory 返回的是指向“棧內存”的指針,該指針的地址不是 NULL,
但其原來的內容已經被清除,新內容不可知。
(32)內存思考題3,代碼如下:
1 void GetMemory(char **p, int num) 2 { 3 *p = (char *)malloc(num); 4 } 5 6 void Test(void) 7 { 8 char *str = NULL; 9 GetMemory(&str, 100); 10 strcpy(str, "hello"); 11 printf(str); 12 }
請問運行Test 函數會有什么樣的結果?
(1)能夠輸出hello
(2)內存泄漏
(33)內存思考題4,代碼如下:
1 void Test(void) 2 { 3 char *str = (char *) malloc(100); 4 strcpy(str, "hello"); 5 free(str); 6 if(str != NULL) 7 { 8 strcpy(str, "world"); 9 printf(str); 10 } 11 }
請問運行Test 函數會有什么樣的結果?
篡改動態內存區的內容,后果難以預料,非常危險。
因為free(str);之后,str 成為野指針,if (str != NULL)判斷語句不起作用。
(34)全局變量和局部變量有什么區別?怎么實現的?操作系統和編譯器是怎么知道的?
全局變量的生命周期是整個程序運行期間,而局部變量的生命周期則是局部函數或過程調用的時間段。
其實現是由編譯器在編譯時采用不同內存分配方法。
全局變量在main函數調用前,就開始分配,如果是靜態變量則是在main函數前就已經初始化了。而局部變量則是在用戶棧中動態分配的。
(35)C語言文件讀寫程序
1 #include "stdio.h" 2 #include "stdlib.h" 3 4 void main() 5 { 6 FILE *fp = NULL; 7 char filename[10] = "test.txt"; 8 scanf("%s", filename); 9 if ((fp = fopen(filename, "w")) == NULL) 10 { 11 printf("cann't open file\n"); 12 exit(0); 13 } 14 char ch = getchar(); 15 while (ch != '#') 16 { 17 fputc(ch, fp); 18 putchar(ch); 19 ch = getchar(); 20 } 21 22 fclose(fp); 23 }
(36)數組越界問題
下面這個程序執行后會有什么錯誤或者效果:
1 #define MAX 255 2 3 void main() 4 { 5 unsigned char A[MAX], i; 6 for (i = 0; i <= MAX; i++) 7 A[i] = i; 8 }
解答:
MAX = 255,數組A的下標范圍為: 0..MAX-1,這是其一。
其二,當i循環到255時,循環內執行: A[255] = 255;
這句本身沒有問題,但是返回for (i = 0; i <= MAX; i++) 語句時,
由於unsigned char的取值范圍在(0..255),i++以后i又為0了,無限循環下去。
備注:char類型為一個字節,取值范圍是[-128,127],unsigned char [0 ,255]
(37)C 和 C++ 中的 struct 有什么不同?
C 和 C++中struct的主要區別是 C 中的struct不可以含有成員函數,而 C++ 中的struct可以。
(38)C++中的struct 與 class 有什么不同?
C++中的struct 和 class的區別是默認成員訪問權限不同。struct的默認訪問權限是public,而class的默認訪問權限是private。
(39)Heap與Stack的區別:
Heap是堆,Stack是棧。
Stack的空間由操作系統自動分配/釋放,Heap上的空間手動分配/釋放。
Stack空間有限,Heap是很大的自由存儲區
C中的malloc函數分配的內存空間即在堆上。C++中對應的是new操作符。
程序在編譯期對變量和函數分配內存都在棧上進行,且程序運行過程中函數調用時參數的傳遞也在棧上進行。
(40)請定義一個宏,比較兩個數的a、b的大小,不能使用大於、小於、if語句。
1 #include<iostream> 2 using namespace std; 3 4 #define max0(a, b) (((a)-(b)) & (1<<31)) == 1 ? (a) : (b); 5 #define max1(a, b) ((((a)-(b)) & (1<<31)) ? (b) : (a)) 6 #define max2(a, b) ((((long)((a)-(b))) & 0x80000000) ? (b) : (a)) 7 #define max3(a, b) (((abs((a)-(b))) == ((a)-(b))) ? (a) : (b)) 8 9 void main() 10 { 11 int n0 = max0(5, 6); 12 cout << n0 << endl; 13 14 int n1 = max1(10, 26); 15 cout << n1 << endl; 16 17 int n2 = max2(5, 1); 18 cout << n2 << endl; 19 20 int n3 = max3(100, 47); 21 cout << n3 << endl; 22 23 system("pause"); 24 } 25 // run out 26 /* 27 6 28 26 29 5 30 100 31 請按任意鍵繼續. . . 32 */
(41)交換兩個數的方法。參見隨筆《交換兩個數的方法》
(42)設置或者清除某位。參見隨筆《面試題(1)》
(43)求最大字段和。參見隨筆《面試題(1)》
(44)字節對齊。參見隨筆《面試題(1)》
(45)大小端判斷。參見隨筆《面試題(1)》
(46)查找數組中的最小和次小項。參見隨筆《面試題(1)》
(47)用預處理指令#define 聲明一個常數,用以表明1年中有多少秒(忽略閏年問題)。
1 #define SECONDS_PER_YEAR (60 * 60 * 24 * 365)UL
分析以下幾點:
1、 #define 語法的基本知識(例如:不能以分號結束,括號的使用等等)。
2、 懂得預處理器將為你計算常數表達式的值,因此,直接寫出你是如何計算一年中有多少秒而不是計算出實際的值,是更清晰而沒有代價的。
3、 意識到這個表達式將使一個16位機的整型數溢出-因此要用到長整型符號L,告訴編譯器這個常數是的長整型數。
4、 如果你在你的表達式中用到UL(表示無符號長整型),那么你有了一個好的起點。記住,第一印象很重要。
(48)用變量a給出下面的定義
a) 一個整型數(An integer)
b) 一個指向整型數的指針(A pointer to an integer)
c) 一個指向指針的的指針,它指向的指針是指向一個整型數(A pointer to a pointer to an integer)
d) 一個有10個整型數的數組(An array of 10 integers)
e) 一個有10個指針的數組,該指針是指向一個整型數的(An array of 10 pointers to integers)
f) 一個指向有10個整型數數組的指針(A pointer to an array of 10 integers)
g) 一個指向函數的指針,該函數有一個整型參數並返回一個整型數
(A pointer to a function that takes an integer as an argument and returns an integer)
h) 一個有10個指針的數組,該指針指向一個函數,該函數有一個整型參數並返回一個整型數
( An array of ten pointers to functions that take an integer argument and return an integer )
1 a) int a; // An integer 2 b) int *a; // A pointer to an integer 3 c) int **a; // A pointer to a pointer to an integer 4 d) int a[10]; // An array of 10 integers 5 e) int *a[10]; // An array of 10 pointers to integers 6 f) int (*a)[10]; // A pointer to an array of 10 integers 7 g) int (*a)(int); 8 // A pointer to a function a that takes an integer argument and returns an integer 9 h) int (*a[10])(int); 10 // An array of 10 pointers to functions that take an integer argument and return an integer
(49)關鍵字typedef 與 宏的區別。參見隨筆《typedef關鍵字》
(50)如何引用一個已經定義過的全局變量?
可以用引用頭文件的方式,或者可以用extern關鍵字。
如果用引用頭文件方式來引用某個在頭文件中聲明的全局變量,假定你將那個變量寫錯了,那么在編譯期間會報錯。
如果你用extern方式引用時,假定你犯了同樣的錯誤,那么在編譯期間不會報錯,而在鏈接期間報錯。
(51)全局變量和局部變量在內存中有什么區別?
全局變量儲存在靜態數據區,局部變量在堆棧中。
(52)零值比較

(53)sizeof的使用

(54)堆棧溢出一般是由什么原因導致的?
沒有回收垃圾資源。
(55)什么函數不能聲明為虛函數?
constructor構造函數
(56)不能做switch()的參數類型是:
switch的參數不能為實型。
(57)局部變量能否和全局變量重名?
能,局部會屏蔽全局。要用全局變量,需要使用"::"
局部變量可以與全局變量同名,在函數內引用這個變量時,會用到同名的局部變量,而不會用到全局變量。
對於有些編譯器而言,在同一個函數內可以定義多個同名的局部變量,比如在兩個循環體內都定義一個同名的局部變量,而那個局部變量的作用域就在那個循環體內。
(58)全局變量可不可以定義在可被多個.C文件包含的頭文件中?為什么?
可以。在不同的C文件中以static形式來聲明同名全局變量。
可以在不同的C文件中聲明同名的全局變量,前提是其中只能有一個C文件中對此變量賦初值,此時鏈接不會出錯。
(59)do……while和while……do有什么區別?
前一個循環一遍再判斷,后一個判斷以后再循環。
(60)語句for( ;1 ;)有什么問題?它是什么意思?
無限循環,和while(1)相同。
(61)寫出下面程序的輸出內容
1 #include <stdio.h> 2 #include <stdlib.h> 3 void main() 4 { 5 int a,b,c,d; 6 a = 10; 7 b = a++; 8 c = ++a; 9 d = 10 * a++; 10 printf("b,c,d:%d,%d,%d\n", b, c, d); 11 system("pause"); 12 } 13 //run out: 14 /* 15 b,c,d:10,12,120 16 請按任意鍵繼續. . . 17 */
(62)寫出下列代碼的輸出內容
1 #include <stdio.h> 2 #include <stdlib.h> 3 4 typedef union 5 { 6 long i; 7 int k[5]; 8 char c; 9 } DATE; 10 11 struct data 12 { 13 int cat; 14 DATE cow; 15 double dog; 16 }too; 17 18 void main() 19 { 20 DATE max; 21 printf("%d, %d\n", sizeof(long), (sizeof(struct data) + sizeof(max))); 22 system("pause"); 23 } 24 // run out: 25 /* 26 4, 52 27 請按任意鍵繼續. . . 28 */
分析:DATE是一個union, 變量共用空間,里面最大的變量類型是int[5], 占用20個字節,所以它的大小是20。
data是一個struct, 每個變量分開占用空間. 依次為int(4)+ DATE(20)+ double(8) = 32
所以結果是 20 + 32 = 52
(63)寫出下列代碼的輸出內容
1 #include <stdio.h> 2 #include <stdlib.h> 3 4 int inc(int a) 5 { 6 return (++a); 7 } 8 int multi(int* a, int* b, int* c) 9 { 10 return (*c = *a * *b); 11 } 12 13 typedef int(*FUNC1) (int in); 14 typedef int(*FUNC2) (int*, int*, int*); 15 16 void show(FUNC2 fun, int arg1, int*arg2) 17 { 18 FUNC1 p = &inc; 19 int temp = p(arg1); 20 fun(&temp, &arg1, arg2); 21 printf("%d\n", *arg2); 22 } 23 24 void main() 25 { 26 int a; 27 show(multi, 10, &a); 28 system("pause"); 29 } 30 // run out: 31 /* 32 110 33 請按任意鍵繼續. . . 34 */
(64)找出下面代碼中的所以錯誤(下面為正常標准程序)
說明:以下代碼是把一個字符串倒序,如“abcd”倒序后變為“dcba”
1 #include <stdio.h> 2 #include <stdlib.h> 3 #include <string.h> 4 5 void main() 6 { 7 char* src = "hello,world"; 8 int len = strlen(src); 9 char* dest = (char*)malloc(len + 1); // 要為\0分配一個空間 10 char* d = dest; 11 char* s = &src[len-1]; // 指向最后一個字符 12 while ( len-- != 0 ) 13 *d++ = *s--; 14 *d = 0; // 尾部要加\0 15 printf("%s\n", dest); 16 free(dest);// 使用完,應當釋放空間,以免造成內存泄露 17 18 system("pause"); 19 }
(65)對於一個頻繁使用的短小函數,在C語言中應用什么實現,在C++中應用什么實現?
C語言用宏定義,C++用inline實現。
(66)下面的程序是否有錯誤,如果有錯,請說明原因。
1 char* const pszHelp = "hello"; 2 pszHelp[0] = 'a';
因為pszHelp指向一個常量字符串,所以根本不允許修改字符串內容。除非使用一個字符數組。
(67)什么是抽象類
包含抽象函數的類是抽象類,滿足virtual fun() = 0; 的語法的函數是抽象函數。主要用於提供接口。
(68)什么時候需要使用虛析構函數
一般情況下,類的析構函數都定義成虛函數,主要是考慮在使用基類指針操作派生類對象時保證類的析構順序。
(69)請指出下面代碼存在的潛在問題
1 class CC 2 { 3 int* m_pCount; 4 5 public: 6 void clear() 7 { 8 if ( m_pCount ) 9 delete m_pCount; 10 } 11 12 CC() 13 { 14 m_pCount = new int; 15 } 16 17 ~CC() 18 { 19 clear(); 20 } 21 };
主要存在的問題是clear函數在delete m_pCount; 后並沒有置指針為空( m_pCount = NULL),這樣當第二次調用 clear 時,會出現問題。
(70)請寫出下列程序運行的結果
1 #include <iostream> 2 using namespace std; 3 4 class A 5 { 6 public: 7 virtual void func() { cout << "I am in base" << endl; }; 8 }; 9 10 class B : public A 11 { 12 public: 13 virtual void func() { cout << "I am in derived" << endl; } 14 }; 15 16 void main() 17 { 18 B* bb = new B; 19 bb->func(); 20 A* aa = (A*)bb; 21 aa->func(); 22 system("pause"); 23 } 24 // run out 25 /* 26 I am in derived 27 I am in derived 28 請按任意鍵繼續. . . 29 */
(71)Debug版本中經常使用ASSERT進行斷言,在Release版本中有一個起同樣作用的函數,請說明。
VERIFY,而且要注意ASSERT中的語句在Release版本中會忽略。
(72)描述內存分配方式以及它們的區別?
1) 從靜態存儲區域分配。內存在程序編譯的時候就已經分配好,這塊內存在程序的整個運行期間都存在。例如全局變量,static 變量。
2) 在棧上創建。在執行函數時,函數內局部變量的存儲單元都可以在棧上創建,函數執行結束時這些存儲單元自動被釋放。棧內存分配運算內置於處理器的指令集。
3) 從堆上分配,亦稱動態內存分配。程序在運行的時候用malloc 或new 申請任意多少的內存,程序員自己負責在何時用free 或delete 釋放內存。
動態內存的生存期由程序員決定,使用非常靈活。
(73)C++四種類型轉換方式
1、static_cast 2、const_cast 3、dynamic_cast 4、reinterpret_cast.
關於各種方式區別參見隨筆《C++類型轉換》
(74)類成員函數的重載、覆蓋和隱藏區別?
a、成員函數被重載的特征:
1、相同的范圍(在同一個類中);
2、函數名字相同;
3、參數不同;
4、virtual 關鍵字可有可無。
b、覆蓋是指派生類函數覆蓋基類函數,特征是:
1、不同的范圍(分別位於派生類與基類);
2、函數名字相同;
3、參數相同;
4、基類函數必須有virtual 關鍵字。
c、“隱藏”是指派生類的函數屏蔽了與其同名的基類函數,規則如下:
1、如果派生類的函數與基類的函數同名,但是參數不同。此時,不論有無virtual關鍵字,基類的函數將被隱藏(注意別與重載混淆)。
2、如果派生類的函數與基類的函數同名,並且參數也相同,但是基類函數沒有virtual關鍵字。此時,基類的函數被隱藏(注意別與覆蓋混淆)。
三者關系總結:

具體代碼可參見隨筆《 重載 覆蓋 隱藏 》
(75)如何打印出當前源文件的文件名以及源文件的當前行號?
cout << __FILE__ ;
cout << __LINE__ ;
__FILE__和__LINE__是系統預定義宏,這種宏並不是在某個文件中定義的,而是由編譯器定義的。
(76)main 函數執行以前,還會執行什么代碼?
全局對象的構造函數會在main 函數之前執行。
(77)main 主函數執行完畢后,是否可能會再執行一段代碼,給出說明?
可以,可以用_onexit 注冊一個函數,它會在main 之后執行。示例代碼如下:
1 #include <stdio.h> 2 #include <stdlib.h> 3 4 int fn1() 5 { 6 printf( "next. " ); 7 system("pause"); 8 return 0; 9 } 10 11 int fn2() 12 { 13 printf( "executed " ); 14 return 0; 15 } 16 17 int fn3() 18 { 19 printf( "is " ); 20 return 0; 21 } 22 23 int fn4() 24 { 25 printf( "This " ); 26 return 0; 27 } 28 29 void main() 30 { 31 _onexit( fn1 ); 32 _onexit( fn2 ); 33 _onexit( fn3 ); 34 _onexit( fn4 ); 35 printf( "This is executed first. " ); 36 system("pause"); 37 } 38 // run out 39 /* 40 This is executed first. 請按任意鍵繼續. . . 41 This is executed next. 請按任意鍵繼續. . . 42 */
(78)如何判斷一段程序是由C 編譯程序還是由C++編譯程序編譯的?
1 #ifdef __cplusplus 2 cout << "c++"; 3 #else 4 cout << "c"; 5 #endif
(79)寫出下列程序的輸出內容
1 #include <stdio.h> 2 #include <stdlib.h> 3 void main() 4 { 5 int x = 3, y, z; 6 x *= (y = z = 4); 7 printf("x = %d\n", x); 8 z = 2; 9 x = (y = z); 10 printf("x = %d\n", x); 11 x = (y == z); 12 printf("x = %d\n", x); 13 system("pause"); 14 } 15 16 //Out put : 17 /* 18 x = 12 19 x = 2 20 x = 1 21 請按任意鍵繼續. . . 22 */
(80)寫出下列程序的輸出內容
1 #include<iostream> 2 using namespace std; 3 4 class Test 5 { 6 private: 7 int value; 8 9 public: 10 Test(int x = 0) : value(x) 11 {} 12 ~Test() 13 {} 14 operator int() 15 { 16 return value; 17 } 18 operator bool() 19 { 20 return value > 0; 21 } 22 }; 23 24 void main() 25 { 26 Test t1(1); 27 28 if (t1) 29 { 30 cout << "if(t1)" << endl; 31 } 32 33 switch (t1.operator int()) 34 { 35 case 1: 36 cout << "switch(t1)" << endl; 37 break; 38 }; 39 40 system("pause"); 41 } 42 43 //run out: 44 /* 45 if(t1) 46 switch(t1) 47 請按任意鍵繼續. . . 48 */
(81)寫出下列程序的輸出內容
1 #include<iostream> 2 #include<cstring> 3 using namespace std; 4 5 void main() 6 { 7 char ch[] = {"abcd"}; 8 char *r = ch + 1; 9 ch[0] = (*r++) + 2; 10 cout << "第一部分結果:" << endl; 11 cout << sizeof(ch) << endl; 12 cout << sizeof(r) <<endl; 13 cout << sizeof(*r) <<endl; 14 cout << ch << endl; 15 cout << *r << endl; 16 cout << ch[0] << endl; 17 cout << endl; 18 19 20 char *ptr = "abcdef"; 21 cout << ptr[3] << endl << endl; 22 23 cout << "第二部分結果:" << endl; 24 // A B C D 25 char *str[] = {"Welcome","To","Fortemedia","Nanjing"}; 26 char **p = str + 1; 27 //執行結束 p指向B 28 str[0] = (*p++) + 2; 29 //執行結束 p指向C 但是str[0]指向D后面的元素,因此內容為空 30 str[1] = *(p+1); 31 //執行結束 p指向C不變 str[1]指向p后一個元素的地址,即就是D 32 str[2] = p[1] + 3; 33 //p是二級指針,此時p[1]指向D p[1]+3即就是字符串元素的第四個字符,即‘j’ str[2]='jing' 34 str[3] = p[0] + (str[2] - str[1]); 35 //str[2] - str[1] = 3, 而p[0]指向的是‘j’的地址,所以str[4] = 'g' 36 cout << sizeof(str) << endl; // 16 37 cout << p << " :: " << sizeof(p) << endl; // 4 38 cout << *p << " :: " << sizeof(*p) << endl; // jing::4 39 cout << **p << " :: " << sizeof(**p) << endl; // j::1 40 cout << str[0] << endl; 41 cout << str[1] << endl; // Nanjing 42 cout << str[2] << endl; // jing 43 cout << str[3] << endl; // g 44 45 cout << str << endl; 46 system("pause"); 47 } 48 //run out: 49 /* 50 第一部分結果: 51 5 52 4 53 1 54 dbcd 55 c 56 d 57 58 d 59 60 第二部分結果: 61 16 62 0018F83C :: 4 63 jing :: 4 64 j :: 1 65 66 Nanjing 67 jing 68 g 69 0018F834 70 請按任意鍵繼續. . . 71 */
(82)C++中虛函數和純虛函數的區別?
1、聲明的差異。純虛函數的聲明除過像虛函數加關鍵字virtual而外,還必須加 = 0;
聲明為虛函數的作用是為了能讓這個函數在它的派生類里面被覆蓋或隱藏,這樣編譯器就可以使用后期綁定來達到多態性。
聲明純虛函數,有一種接口的規范作用。派生類必須且只能原樣實現。
2、定義的差異。虛函數在基類中必須實現(哪怕空操作),派生類里面也可以不覆蓋或隱藏。但純虛函數必須在派生類里面去實現。
3、虛基類或抽象類。帶純虛函數的類叫做虛基類,或抽象類。這種基類不能直接聲明對象,只能被繼承,並且只有在實現其純虛函數后才能使用。
(83)什么時候需要“引用”?
流操作符<< 和 >>、賦值操作符=的返回值、拷貝構造函數的參數、賦值操作符=的參數、其它情況都推薦使用引用。
(84)結構與聯合的區別?
1. 結構和聯合都是由多個不同的數據類型成員組成。
但在任何同一時刻, 聯合中只存放了一個被選中的成員(所有成員共用一塊地址空間), 而結構的所有成員都存在(不同成員的存放地址不同)。
2. 對於聯合的不同成員賦值, 將會對其它成員重寫, 原來成員的值就不存在了, 而對於結構的不同成員賦值是互不影響的。
(85)下列關於聯合的程序輸出
1 #include <stdio.h> 2 #include <stdlib.h> 3 4 union 5 { 6 int i; 7 char x[2]; 8 } a; 9 10 void main() 11 { 12 a.x[0] = 10; 13 a.x[1] = 1; 14 printf("%d", a.i); // 266 15 system("pause"); 16 }
低位低地址,高位高地址,內存占用情況是Ox010A

(86)New delete 與 malloc free 的聯系與區別
都是在堆(heap)上進行動態的內存操作。用malloc函數需要指定內存分配的字節數並且不能初始化對象,new 會自動調用對象的構造函數。
delete 會調用對象的destructor,而free不會調用對象的destructor。
關於new 和 delete詳情內容請參見隨筆《new 與 delete》
(87)C++是不是類型安全的?
不是。兩個不同類型的指針之間可以強制轉換(用reinterpret cast)。C#是類型安全的。
(88)當一個類A中沒有生命任何成員變量與成員函數,這時sizeof(A)的值是多少,如果不是零,請解釋一下編譯器為什么沒有讓它為零。
肯定不是零。舉個反例,如果是零的話,聲明一個class A[10]對象數組,而每一個對象占用的空間是零,這時就沒辦法區分A[0],A[1]…了。
(89)簡述數組與指針的區別?
數組要么在靜態存儲區被創建(如全局數組),要么在棧上被創建。指針可以隨時指向任意類型的內存塊。
(90)strcpy與memcpy的區別
1、復制的內容不同。strcpy僅僅復制字符串,而memcpy可以復制任何類型的數據內容
2、復制的方法不同。strcpy不需要指定長度,它遇到字符串結束符"\0"便結束。memcpy則是根據其第三個參數決定復制的長度。
3、用途不同。通常在復制字符串時用strcpy,而復制其它類型數據時一般用memcpy()
(91)寫出下列程序的輸出內容:
1 #include<iostream> 2 using namespace std; 3 4 void main() 5 { 6 unsigned short int i = 0; 7 unsigned char ii = 255; 8 int j = 8, p, q; 9 10 i = i - 1; 11 ii = ii + 1; 12 p = j << 1; 13 q = j >> 1; 14 15 cout << i << endl; // 65535 16 cout << ii << endl; 17 cout << p << endl; // 16 18 cout << q << endl; // 4 19 20 system("pause"); 21 }
(92)寫出下列程序的輸出內容:
1 #include<iostream> 2 using namespace std; 3 4 class Base 5 { 6 public: 7 virtual void fun1() 8 { 9 cout << "Base :: fun1()" << endl; 10 } 11 virtual void fun2() 12 { 13 cout << "Base :: fun2()" << endl; 14 } 15 virtual void fun3() 16 { 17 cout << "Base :: fun3()" << endl; 18 } 19 20 private: 21 int num1; 22 int num2; 23 }; 24 25 typedef void (*Fun)(); 26 27 void main() 28 { 29 Base b; 30 Fun pFun; 31 pFun = (Fun)*((int *)*(int *)(&b) + 0); 32 pFun(); 33 pFun = (Fun)*((int *)*(int *)(&b) + 1); 34 pFun(); 35 pFun = (Fun)*((int *)*(int *)(&b) + 2); 36 pFun(); 37 38 system("pause"); 39 } 40 // run out: 41 /* 42 Base :: fun1() 43 Base :: fun2() 44 Base :: fun3() 45 請按任意鍵繼續. . . 46 */
分析:
typedef void( *Fun )( void ); // Fun定義為一個沒有參數,返回void類型的函數指針
針對 *( ( int* ) * ( int* )( &b ) + i ); 這一段:
(int*)* 相當於沒有進行任何操作,所以等同於:
*( ( int* )( &b ) + i )
這里先取b的地址,然后把地址轉換成int*,之后+i是指針算術,也就是在b的地址上加一個int的長度。
最后,前面的*是解指針,這段最后返回的是“b的地址 + i個int長度”的地址值。
最前面的(Fun)是強制把“b的地址 + i個int長度”的地址值轉換為一個“沒有參數,返回void類型的函數指針”,
所以pFun就是一個函數指針,其指向的位置從一開始的b的地址,每次循環加一個int類型的長度。
然后,我們來看實際情況:
( Fun )*( ( int* ) * ( int* )( &b ) + i );
這里*( ( int* ) * ( int* )( &b ) + i )最前面的*是其前面結果進行解指針,也就取b的地址+i個int長度的這個地址的值。
並把它轉換為Fun類型,也就是一個沒有參數,返回void類型的函數指針,所以最后得到的就是一個函數指針。
再來看循環,循環3次,pFun變量分別被賦了3次值,每次都是一個函數指針。
由於Base類型中有virtual虛函數,所以b的地址指向的是b的vtbl(虛函數表),vtbl虛函數表可以看作是一個保存了函數指針的數組,
每個元素就是一個int長度,在vtbl虛函數表中Base::fun1, Base::fun2, Base::fun3是按照如上順序排列的,所以第一次循環指向Base::fun1。
那么后兩次就指向Base::fun2 和 Base::fun3了。編碼以明志,調試而致遠。調試結果如下:

至於為什么是按照這樣的順序排列的,因為其聲明順序。
(93)寫出下列程序的輸出內容
1 #include <iostream> 2 using namespace std; 3 4 class A 5 { 6 virtual void g() 7 { 8 cout << "A::g" << endl; 9 } 10 private: 11 virtual void f() 12 { 13 cout << "A::f" << endl; 14 } 15 }; 16 17 class B : public A 18 { 19 void g() 20 { 21 cout << "B::g" << endl; 22 } 23 virtual void h() 24 { 25 cout << "B::h" << endl; 26 } 27 }; 28 29 typedef void( *Fun )( void ); 30 31 int main() 32 { 33 B b; 34 Fun pFun; 35 for (int i = 0 ; i < 3; ++i) 36 { 37 pFun = ( Fun )*( ( int* ) * ( int* )( &b ) + i ); 38 pFun(); 39 } 40 41 system("pause"); 42 } 43 */ 44 // run out: 45 /* 46 B::g 47 A::f 48 B::h 49 */
(94)在什么時候需要使用“常引用”?
如果既要利用引用提高程序的效率,又要保護傳遞給函數的數據不在函數中被改變,就應使用常引用。
常引用聲明方式:const 類型標識符 & 引用名 = 目標變量名;
(95)流操作符重載返回值申明為“引用”的作用:
流操作符< <和>>,這兩個操作符常常希望被連續使用,例如:cout << "hello" << endl; 因此這兩個操作符的返回值應該是一個仍然支持這兩個操作符的流引用。
可選的其它方案包括:返回一個流對象和返回一個流對象指針。
但是對於返回一個流對象,程序必須重新(拷貝)構造一個新的流對象,也就是說,連續的兩個<<操作符實際上是針對不同對象的!
這無法讓人接受。對於返回一個流指針則不能連續使用<<操作符。因此,返回一個流對象引用是惟一選擇。
這個唯一選擇很關鍵,它說明了引用的重要性以及無可替代性,也許這就是C++語言中引入引用這個概念的原因吧。賦值操作符=。
這個操作符象流操作符一樣,是可以連續使用的,例如:x = j = 10; 或者 (x = 10) = 100;賦值操作符的返回值必須是一個左值,以便可以被繼續賦值。
因此引用成了這個操作符的惟一返回值選擇。
(96)系統為變量賦的默認值打印結果:(系統:win32 + VS2010)
1 #include <iostream> 2 using namespace std; 3 4 static int g_sn; 5 int g_n; 6 7 void func() 8 { 9 static int inner_sn; 10 int inner_n; 11 12 cout << "inner_sn :: " << inner_sn << endl; 13 // cout << inner_n << endl; 打印崩潰!系統不會自動為局部變量賦初值。 14 } 15 16 class A 17 { 18 private: 19 char m_ch; 20 int m_n; 21 bool m_b; 22 short m_sh; 23 long m_lg; 24 float m_fl; 25 double m_d; 26 int* m_pN; 27 28 public: 29 void printDefaultValue() 30 { 31 cout << "char :: " << m_ch << endl; 32 cout << "int :: " << m_n << endl; 33 cout << "bool :: " << m_b << endl; 34 cout << "short :: " << m_sh << endl; 35 cout << "long :: " << m_lg << endl; 36 cout << "float :: " << m_fl << endl; 37 cout << "double :: " << m_d << endl; 38 cout << "int* :: " << m_pN << endl; 39 } 40 }; 41 42 void main() 43 { 44 cout << "g_sn :: " << g_sn << endl; 45 cout << "g_n :: " << g_n << endl; 46 func(); 47 A objA; 48 objA.printDefaultValue(); 49 system("pause"); 50 } 51 52 // run out: 53 /* 54 g_sn :: 0 55 g_n :: 0 56 inner_sn :: 0 57 char :: 58 int :: -858993460 59 bool :: 204 60 short :: -13108 61 long :: -858993460 62 float :: -1.07374e+008 63 double :: -9.25596e+061 64 int* :: CCCCCCCC 65 請按任意鍵繼續. . . 66 */
注意:系統不會為局部變量賦默認值。因此直接打印局部變量的默認值導致崩潰!
(97)有哪幾種情況只能用intialization list 而不能用assignment?
類中含有const、reference 成員變量;基類的構造函數都需要初始化表。
(98)面向對象的三個基本特征,並簡單敘述之?
1、封裝:類。將客觀事物抽象成類,每個類對自身的數據和方法實行受權限訪問protection (private, protected, public)。
2、繼承:廣義的繼承有三種實現形式:
實現繼承(指使用基類的屬性和方法而無需額外編碼的能力)、
可視繼承(子窗體使用父窗體的外觀和實現代碼)、
接口繼承(僅使用屬性和方法,實現滯后到子類實現)。前兩種(類繼承)和后一種(對象組合 => 接口繼承以及純虛函數)構成了功能復用的兩種方式。
3、多態:是將父對象設置成為和一個或更多的他的子對象相等的技術,賦值之后,父對象就可以根據當前賦值給它的子對象的特性以不同的方式運作。
簡單的說,就是一句話:允許將子類類型的指針賦值給父類類型的指針。
(99)找出下列代碼的問題:
1 void test() 2 { 3 char string[10], str1[10]; 4 int i; 5 for (i = 0; i < 10; i++) 6 { 7 str1[i] = 'a'; 8 } 9 strcpy(string, str1); 10 }
如果面試者指出字符數組str1不能在數組內結束可以給3分;
如果面試者指出strcpy(string, str1)調用使得從str1內存起復制到string內存起所復制的字節數具有不確定性可以給7分,在此基礎上指出庫函數strcpy工作方式的給10 分;
str1不能在數組內結束:因為str1的存儲為:{a, a, a, a, a, a, a, a, a, a},沒有'\0'(字符串結束符),所以不能結束。
strcpy( char *s1, char *s2)他的工作原理:
掃描s2指向的內存,逐個字符付到s1所指向的內存,直到碰到'\0',因為str1結尾沒有'\0'。所以具有不確定性,不知道他后面還會賦值什么東東。
正確程序如下:
1 void test() 2 { 3 char string[10], str1[10]; 4 int i; 5 for (i = 0; i < 9; i++) 6 { 7 str1[i] = 'a' + i; // 把abcdefghi賦值給字符數組 8 } 9 str1[i] = '\0'; // 加上結束符 10 strcpy(string, str1); 11 }
(100)C++中為什么用模板類?
1、可用來創建動態增長和減小的數據結構。
2、它是類型無關的,因此具有很高的可復用性。
3、它在編譯時而不是運行時檢查數據類型,保證了類型安全。
4、它是平台無關的,可移植性。
5、可用於基本數據類型。
(101)MFC中CString是類型安全類么?
不是,其它數據類型轉換到CString可以使用CString的成員函數Format來轉換
(102)函數模板與類模板有什么區別?
函數模板的實例化是由編譯程序在處理函數調用時自動完成的,而類模板的實例化必須由程序員在程序中顯式地指定。
(103)動態連接庫的兩種方式?
調用一個DLL中的函數有兩種方法:
1、載入時動態鏈接(load-time dynamic linking),模塊非常明確調用某個導出函數,使得他們就像本地函數一樣。
這需要鏈接時鏈接那些函數所在DLL的導入庫,導入庫向系統提供了載入DLL時所需的信息及DLL函數定位。
2、運行時動態鏈接(run-time dynamic linking),運行時可以通過LoadLibrary或LoadLibraryEx函數載入DLL。
DLL載入后,模塊可以通過調用GetProcAddress獲取DLL函數的出口地址,然后就可以通過返回的函數指針調用DLL函數了。
(104)類中函數詳解(構造函數、析構函數、拷貝構造函數、賦值構造函數)。請參見隨筆《類中函數》
(105)寫出下列程序編譯錯誤問題的原因
程序代碼如下:
1 #define charPtr char * 2 3 void main() 4 { 5 typedef char* charPtrDef; 6 char str[4] = {"abc"}; 7 const char* p1 = str; 8 const charPtr p2 = str; 9 const charPtrDef p3 = str; 10 char * const p4 = str; 11 const char * const p5 = str; 12 p1++; 13 p2++; // OK 宏定義僅僅只是文本替換,其本質與p1相同。可以編譯通過 14 // *p2 = 'A'; // error! const 修飾的是p2指向的內容,只讀不允許修改 15 // p3++; // error! const修飾的是p3指針,只讀不允許修改 16 *p3 = 'B'; // OK 因此指針指向的內容可以修改 17 // p4++; // error! const修飾的是p4指針,只讀不允許修改 18 *p4 = 'C'; // OK 因此指針指向的內容可以修改 19 // p5++; // error! *號后的const修飾的是p5指針,只讀不允許修改 20 // *p5 = 'D'; // error! *號前的const修飾的是p5指針指向的內容,只讀不允許修改 21 }
注釋為原因分析。
(106)“引用”與多態的關系?
引用是除指針外另一個可以產生多態效果的手段。這意味着,一個基類的引用可以指向它的派生類實例。
(107)多態的作用?
主要是兩個:
1、 隱藏實現細節,使得代碼能夠模塊化;擴展代碼模塊,實現代碼重用;
2、 接口重用:為了類在繼承和派生的時候,保證使用家族中任一類實例的某一屬性時的正確調用。
(108)STL中容器map,map有幾種賦值方式?每種方式有何區別?
詳情請參見隨筆《STL容器之map 》
(109)STL中容器list,使用list容器的merge方法需要注意什么?
詳情請參見隨筆《STL容器之list 》
(110)介於vector和list之間的一種容器deque,請參見隨筆《STL容器之deque》
(111)C++語言中如何訪問一個類私有成員變量?
兩種方式。第一,接口,利用訪問成員變量相應的set/get公共成員函數;第二,友元,友元類和友元函數。
(112)由於本文篇幅太長,編輯后保存太慢。下面續《 C++筆試題2(基礎題) 》
Good Good Study, Day Day Up.
順序 選擇 循環 總結
