有時我們希望某些常量只在類中有效。由於#define定義的宏常量是全域的,不能達到目的,於是想當然地覺得應該用const修飾資料成員來實現。const資料成員的確是存在的,但其含義卻不是我們所期望的。const資料成員只在某個物件存留期內是常量,而對於整個類而言卻是可變的,因為類可以建立多個對象,不同的對象其const資料成員的值可以不同。
不能在類聲明中初始化const資料成員。以下用法是錯誤的,因為類的對象未被建立時,編譯器不知道SIZE的值是什麼。
class A
{…
const int SIZE = 100; // 錯誤,企圖在類聲明中初始化const資料成員
int array[SIZE]; // 錯誤,未知的SIZE
};
const資料成員的初始化只能在類建構函式的初始化表中進行,例如
class A
{…
A(int size); // 建構函式
const int SIZE ;
};
A::A(int size) : SIZE(size) // 建構函式的初始化表
{
…
}
A a(100); // 對象 a 的SIZE值為100
A b(200); // 對象 b 的SIZE值為200
1.new、delete、malloc、free關係
delete會調用對象的解構函式,和new對應free只會釋放記憶體,new調用建構函式。malloc與free是C++/C語言的標準庫函數,new/delete是C++的運算子。它們都可用於申請動態記憶體和釋放記憶體。對於非內部資料類型的對象而言,光用maloc/free無法滿足動態對象的要求。對象在建立的同時要自動執行建構函式,對象在消亡之前要自動執行解構函式。
2.與 delete []區別
delete只會調用一次解構函式,而delete[]會調用每一個成員的解構函式。
3.有哪些性質(物件導向特點)
封裝,繼承和多態。
4.子類析構時要調用父類的解構函式嗎?
解構函式調用的次序是先衍生類別的析構後基類的析構,也就是說在基類的的析構調用的時候,衍生類別的資訊已經全部銷毀了。定義一個對象時先調用基類的建構函式、然後調用衍生類別的建構函式;析構的時候恰好相反:先調用衍生類別的解構函式、然後調用基類的解構函式
16. C++中的class和struct的區別
從文法上,在C++中(只討論C++中)。class和struct做類型定義時只有兩點區別:
(一)預設繼承許可權。如果不明確指定,來自class的繼承按照private繼承處理,來自struct的繼承按照public繼承處理;
(二)成員的預設存取權限。class的成員預設是private許可權,struct預設是public許可權。
除了這兩點,class和struct基本就是一個東西。文法上沒有任何其它區別。
求下面函數的傳回值(微軟)
int func(x)
{
int countx = 0;
while(x)
{
countx ++;
x = x&(x-1);
}
return countx;
}
假定x = 9999。 答案:8
思路:將x轉化為2進位,看含有的1的個數。
5:什麼是“引用”?申明和使用“引用”要注意哪些問題?
引用就是某個目標變數的“別名”(alias),對應用的操作與對變數直接操作效果完全相同。申明一個引用的時候,切記要對其進行初始化。聲明一個引用,不是新定義了一個變數,它只表示該引用名是目標變數名的一個別名,它本身不是一種資料類型,因此引用本身不佔儲存單元,系統也不給引用分配儲存單元。
6:“引用”與多態的關係?
引用是除指標外另一個可以產生多態效果的手段。這意味著,一個基類的引用可以指向它的衍生類別執行個體
a)
#include <stdio.h>
union
{
int i;
char x[2];
}a;
void main()
{
a.x[0] = 10; //2進位就是0000 1010(0AH)
a.x[1] = 1 ; //2進位就是 0000 0001(01H)
printf("%d",a.i); //結果就是 0000 0001 0000 1010=266
}
答案:266 (低位低地址,高位高地址,記憶體佔用情況是Ox010A)
b)
main()
{
union
{ /*定義一個聯合*/
int i;
struct
{ /*在聯合中定義一個結構*/
char first;
char second;
}half;
}number;
number.i=0x4241; /*聯合成員賦值*/
printf("%c%cn", number.half.first, mumber.half.second);
number.half.first='a'; /*聯合中結構成員賦值*/
number.half.second='b';
printf("%xn", number.i);
getch();
}
答案: AB (0x41對應'A',是低位;Ox42對應'B',是高位)
6261 (number.i和number.half共用一塊地址空間)
9:多態的作用?
主要是兩個:
1. 隱藏實現細節,使得代碼能夠模組化;擴充代碼模組,實現代碼重用;
2. 介面重用:為了類在繼承和派生的時候,保證使用家族中任一類的執行個體的某一屬性時的正確調用。
12. 描述記憶體配置方式以及它們的區別?
1) 從靜態儲存地區分配。記憶體在程式編譯的時候就已經分配好,這塊記憶體在程式的整個運行期間都存在。例如全域變數,static 變數。
2) 在棧上建立。在執行函數時,函數內局部變數的儲存單元都可以在棧上建立,函數執行結束時這些儲存單元自動被釋放。棧記憶體配置運算內建於處理器的指令集。
3) 從堆上分配,亦稱動態記憶體分配。程式在啟動並執行時候用malloc 或new 申請任意多少的記憶體,程式員自己負責在何時用free 或delete 釋放記憶體。動態記憶體的生存期由程式員決定,使用非常靈活,但問題也最多。
BOOL : if ( !a ) or if(a)
int : if ( a == 0)
float : if ( a < 0.00001&& a >-0.00001)
pointer : if ( a != NULL) or if(a == NULL)
14:請說出const與#define 相比,有何優點?
答案:
Const作用:定義常量、修飾函數參數、修飾函數傳回值三個作用。被Const修飾的東西都受到強制保護,可以預防意外的變動,能提高程式的健壯性。
1) const 常量有資料類型,而宏常量沒有資料類型。編譯器可以對前者進行型別安全檢查。而對後者只進行字元替換,沒有型別安全檢查,並且在字元替換可能會產生意料不到的錯誤。
2) 有些整合化的調試工具可以對const 常量進行調試,但是不能對宏常量進行調試。
15:簡述數組與指標的區別?
數組要麼在靜態儲存區被建立(如全域數組),要麼在棧上被建立。指標可以隨時指向任意類型的記憶體塊。
16.類成員函數的重載、覆蓋和隱藏區別?
答案:a.成員函數被重載的特徵:
(1)相同的範圍(在同一個類中);
(2)函數名字相同;
(3)參數不同;
(4)virtual 關鍵字可有可無。
b.覆蓋是指衍生類別函數覆蓋基類函數,特徵是:
(1)不同的範圍(分別位於衍生類別與基類);
(2)函數名字相同;
(3)參數相同;
(4)基類函數必須有virtual 關鍵字。
c.“隱藏”是指衍生類別的函數屏蔽了與其同名的基類函數,規則如下:
(1)如果衍生類別的函數與基類的函數同名,但是參數不同。此時,不論有無virtual關鍵字,基類的函數將被隱藏(注意別與重載混淆)。
(2)如果衍生類別的函數與基類的函數同名,並且參數也相同,但是基類函數沒有virtual 關鍵字。此時,基類的函數被隱藏(注意別與覆蓋混淆)
17.求出兩個數中的較大值
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
19.如何判斷一段程式是由C 編譯器還是由C++編譯器編譯的?
答案:
#ifdef __cplusplus
cout<<"c++";
#else
cout<<"c";
#endif
20:堆疊溢位一般是由什麼原因導致的?
答 、1.沒有回收垃圾資源
2.層次太深的遞迴調用
21:什麼函數不能聲明為虛函數?
答 、constructor
22.用兩個棧實現一個隊列的功能?要求給出演算法和思路!
答 、設2個棧為A,B, 一開始均為空白.
入隊:
將新元素push入棧A;
出隊:
(1)判斷棧B是否為空白;
(2)如果不為空白,則將棧A中所有元素依次pop出並push到棧B;
(3)將棧B的棧頂元素pop出;
23.寫一個“標準”宏MIN,這個宏輸入兩個參數並返回較小的一個。
#define MIN(A,B) ((A) <= (B)? (A) : (B))
24.交換兩個數的宏定義
交換兩個參數值的宏定義為:. #define SWAP(a,b) (a)=(a)+(b);(b)=(a)-(b);(a)=(a)-(b);
25.用變數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 )
答案是:
a) int a; // An integer
b) int *a; // A pointer to an integer
c) int **a; // A pointer to a pointer to an integer
d) int a[10]; // An array of 10 integers
e) int *a[10]; // An array of 10 pointers to integers
f) int (*a)[10]; // A pointer to an array of 10 integers
g) int (*a)(int); // A pointer to a function a that takes an integer argument and returns an integer
h) int (*a[10])(int); // An array of 10 pointers to functions that take an integer argument and return an integer
26:關鍵字static的作用是什嗎?
這個簡單的問題很少有人能回答完全。在C語言中,關鍵字static有三個明顯的作用:
1). 在函數體,一個被聲明為靜態變數在這一函數被調用過程中維持其值不變。
2). 在模組內(但在函數體外),一個被聲明為靜態變數可以被模組內所用函數訪問,但不能被模組外其它函數訪問。它是一個本地的全域變數。
3). 在模組內,一個被聲明為靜態函數只可被這一模組內的其它函數調用。那就是,這個函數被限制在聲明它的模組的本地範圍內使用。
27:.下面的代碼輸出是什麼,為什嗎?
void foo(void)
{
unsigned int a = 6;
int b = -20;
(a+b > 6) puts("> 6") : puts("<= 6");
}
這個問題測試你是否懂得C語言中的整數自動轉換原則,我發現有些開發人員懂得極少這些東西。不管如何,這無符號整型問題的答案是輸出是“>6”。原因是當運算式中存在有符號類型和無符號類型時所有的運算元都自動轉換為無符號類型。因此-20變成了一個非常大的正整數,所以該運算式計算出的結果大於6
28:.C語言同意一些令人震驚的結構,下面的結構是合法的嗎,如果是它做些什嗎?
int a = 5, b = 7, c;
c = a+++b;
因此, 這段代碼持行後a = 6, b = 7, c = 12。
29:.用遞迴演算法判斷數組a[N]是否為一個遞增數組。
遞迴的方法,記錄當前最大的,並且判斷當前的是否比這個還大,大則繼續,否則返回false結束:
bool fun( int a[], int n )
{
if( n= =1 )
return true;
if( n= =2 )
return a[n-1] >= a[n-2];
return fun( a,n-1) && ( a[n-1] >= a[n-2] );
}
18.如何列印出當前源檔案的檔案名稱以及源檔案的當前行號?
答案:
cout << __FILE__ ;
cout<<__LINE__ ;
__FILE__和__LINE__是系統預定義宏,這種宏並不是在某個檔案中定義的,而是由編譯器定義的。
char a[] = "hello world";
char *p = a;
cout<< sizeof(a) << endl; // 12 位元組,結束符號'/0'也要計算在裡面
cout<< sizeof(p) << endl; // 4 位元組
計算數組和指標的記憶體容量
注意當數組作為函數的參數進行傳遞時,該數組自動退化為同類型的指標。
void Func(char a[100])
{
cout<< sizeof(a) << endl; // 4 位元組而不是100 位元組
}
19:分別寫出BOOL,int,float,指標類型的變數a 與“零”的比較語句。20:引用”與指標的區別是什嗎?
指標通過某個指標變數指向一個對象後,對它所指向的變數間接操作。程式中使用指標,程式的可讀性差;而引用本身就是目標變數的別名,對引用的操作就是對目標變數的操作。
21.結構與聯合有和區別?
(1). 結構和聯合都是由多個不同的資料類型成員組成, 但在任何同一時刻, 聯合中只存放了一個被選中的成員(所有成員共用一塊地址空間), 而結構的所有成員都存在(不同成員的存放地址不同)。
(2). 對於聯合的不同成員賦值, 將會對其它成員重寫, 原來成員的值就不存在了, 而對於結構的不同成員賦值是互不影響的。
22. 解構函式和虛函數的用法和作用
解構函式是在物件存留期結束時自動調用的函數,用來釋放在建構函式分配的記憶體。
虛函數是指被關鍵字virtual說明的函數,作用是使用C++語言的多態特性
23: Windows程式的入口是哪裡?寫出Windows訊息機制的流程
Windows程式的入口是WinMain()函數。
Windows應用程式訊息處理機制:
A. 作業系統接收應用程式的視窗訊息,將訊息投遞到該應用程式的訊息佇列中
B. 應用程式在訊息迴圈中調用GetMessage函數從訊息佇列中取出一條一條的訊息,取出訊息後,應用程式可以對訊息進行一些預先處理。
C. 應用程式調用DispatchMessage,將訊息回傳給作業系統。
D. 系統利用WNDCLASS結構體的lpfnWndProc成員儲存的視窗過程函數的指標調用視窗過程,對訊息進行處理。
宏與內嵌函式的區別
內嵌函式和宏都是在程式出現的地方展開,內嵌函式不是通過函數調用實現的,是在調用該函數的程式處將它展開(在編譯期間完成的);宏同樣是;
不同的是:內嵌函式可以在編譯期間完成諸如類型檢測,語句是否正確等編譯功能;宏就不具有這樣的功能,而且宏展開的時間和內嵌函式也是不同的(在運行期間展開)
47.如何判斷一個單鏈表是有環的?(注意不能用標誌位,最多隻能用兩個額外指標)
struct node { char val; node* next;}
bool check(const node* head) {} //return false : 無環;true: 有環一種O(n)的辦法就是(搞兩個指標,一個每次遞增一步,一個每次遞增兩步,如果有環的話兩者必然重合,反之亦然):
bool check(const node* head)
{
if(head==NULL) return false;
node *low=head, *fast=head->next;
while(fast!=NULL && fast->next!=NULL)
{
low=low->next;
fast=fast->next->next;
if(low==fast) return true;
}
return false;
}
48.指標找錯題
分析這些面試題,本身包含很強的趣味性;而作為一名研發人員,通過對這些面試題的深入剖析則可進一步增強自身的內功。
2.找錯題
試題1:
以下是引用片段:
void test1() //數組越界
{
char string[10];
char* str1 = "0123456789";
strcpy( string, str1 );
}
試題2:
以下是引用片段:
void test2()
{
char string[10], str1[10];
int i;
for(i=0; i<10; i++)
{
str1= 'a';
}
strcpy( string, str1 );
}
試題3:
以下是引用片段:
void test3(char* str1)
{
char string[10];
if( strlen( str1 ) <= 10 )
{
strcpy( string, str1 );
}
}
解答:
試題1字串str1需要11個位元組才能存放下(包括末尾的’/0’),而string只有10個位元組的空間,strcpy會導致數組越界;對試題2,如果面試者指出字元數組str1不能在數組內結束可以給3分;如果面試者指出strcpy(string,str1)調用使得從 str1記憶體起複製到string記憶體起所複製的位元組數具有不確定性可以給7分,在此基礎上指出庫函數strcpy工作方式的給10分;
對試題3,if(strlen(str1) <= 10)應改為if(strlen(str1) <10),因為strlen的結果未統計’/0’所佔用的1個位元組。剖析:考查對基本功的掌握
(1)字串以’/0’結尾;
(2)對數組越界把握的敏感度;
(3)庫函數strcpy的工作方式,
試題4: void GetMemory( char *p )
{
p = (char *) malloc( 100 );
}
void Test( void )
{
char *str = NULL;
GetMemory( str );
strcpy( str, "hello world" );
printf( str );
}
試題5: char *GetMemory( void )
{
char p[] = "hello world";
return p;
}
void Test( void )
{
char *str = NULL;
str = GetMemory();
printf( str );
}
試題6:void GetMemory( char **p, int num )
{
*p = (char *) malloc( num );
}
void Test( void )
{
char *str = NULL;
GetMemory( &str, 100 );
strcpy( str, "hello" );
printf( str );
}
試題7:以下是引用片段:
void Test( void )
{
char *str = (char *) malloc( 100 );
strcpy( str, "hello" );
free( str );
... //省略的其它語句
}
解答:試題4傳入中GetMemory( char *p )函數的形參為字串指標,在函數內部修改形參並不能真正的改變傳入形參的值,執行完
char *str = NULL;
GetMemory( str );
後的str仍然為NULL;試題5中
char p[] = "hello world";
return p;
的p[]數組為函數內的局部自動變數,在函數返回後,記憶體已經被釋放。這是許多程式員常犯的錯誤,其根源在於不理解變數的生存期。
試題6的GetMemory避免了試題4的問題,傳入GetMemory的參數為字串指標的指標,但是在GetMemory中執行申請記憶體及指派陳述式 tiffanybracelets
*p = (char *) malloc( num );
後未判斷記憶體是否申請成功,應加上:
if ( *p == NULL )
{
...//進行申請記憶體失敗處理
}
試題7存在與試題6同樣的問題,在執行
char *str = (char *) malloc(100);
後未進行記憶體是否申請成功的判斷;另外,在free(str)後未置str為空白,導致可能變成一個“野”指標,應加上:
str = NULL;
試題6的Test函數中也未對malloc的記憶體進行釋放。
剖析:
試題4~7考查面試者對記憶體操作的理解程度,基本功紮實的面試者一般都能正確的回答其中50~60的錯誤。但是要完全解答正確,卻也絕非易事。
軟體開發網 www.mscto.com
對記憶體操作的考查主要集中在:
(1)指標的理解;
(2)變數的生存期及作用範圍;
(3)良好的動態記憶體申請和釋放習慣。
再看看下面的一段程式有什麼錯誤:
以下是引用片段:
swap( int* p1,int* p2 )
{
int *p;
*p = *p1;
*p1 = *p2;
*p2 = *p;
}
在swap函數中,p是一個“野”指標,有可能指向系統區,導致程式啟動並執行崩潰。在VC++中DEBUG運行時提示錯誤“Access Violation”。該程式應該改為
以下是引用片段:
swap( int* p1,int* p2 )
{
int p;
p = *p1;
*p1 = *p2;
*p2 = p;
}
101.用遞迴演算法判斷數組a[N]是否為一個遞增數組。
遞迴的方法,記錄當前最大的,並且判斷當前的是否比這個還大,大則繼續,否則返回false結束:
bool fun( int a[], int n )
{
if( n= =1 )
return true;
if( n= =2 )
return a[n-1] >= a[n-2];
return fun( a,n-1) && ( a[n-1] >= a[n-2] );
}
如何定義和實現一個類的成員函數為回呼函數?
typedef void (*FunPtr)(void);
class A
{
public :
static void callBackFun(void )
{
cout<<"callBackFun "<<endl;
}
};
void Funtype( FunPtr p)
{
p();
}
void main(void)
{
Funtype(A::callBackFun);
}