面試題1:變量的聲明和定義有什麼區別
爲變量分配地址和存儲空間的稱爲定義,不分配地址的稱爲聲明。一個變量能夠在多個地方聲明,可是隻在一個地方定義。加入extern修飾的是變量的聲明,說明此變量將在文件之外或在文件後面部分定義。
說明:不少時候一個變量,只是聲明不分配內存空間,直到具體使用時才初始化,分配內存空間,如外部變量。node
面試題2:寫出bool 、int、 float、指針變量與「零值」比較的if語句
bool型數據:
if( flag ) { A; } else { B; }
int型數據:
if( 0 != flag ) { A; } else { B; }
指針型數:
if( NULL == flag ) { A; } else { B; }
float型數據:
if ( ( flag >= NORM ) && ( flag <= NORM ) ) { A;}ios
注意:應特別注意在int、指針型變量和「零值」比較的時候,把「零值」放在左邊,這樣當把「==」誤寫成「=」時,編譯器能夠報錯,不然這種邏輯錯誤不容易發現,而且可能致使很嚴重的後果。 程序員
面試題3:sizeof和strlen的區別
sizeof和strlen有如下區別:
sizeof是一個操做符,strlen是庫函數。
sizeof的參數能夠是數據的類型,也能夠是變量,而strlen只能以結尾爲‘\0‘的字符串做參數。 編譯器在編譯時就計算出了sizeof的結果。而strlen函數必須在運行時才能計算出來。而且sizeof
計算的是數據類型佔內存的大小,而strlen計算的是字符串實際的長度。 數組作sizeof的參數不退化,傳遞給strlen就退化爲指針了。
注意:有些是操做符看起來像是函數,而有些函數名看起來又像操做符,這類容易混淆的名稱必定要加以區分,不然遇到數組名這類特殊數據類型做參數時就很容易出錯。最容易混淆爲函數的操做符就是sizeof。 面試
面試題4:C語言的關鍵字 static 和 C++ 的關鍵字 static 有什麼區別
在C中static用來修飾局部靜態變量和外部靜態變量、函數。而C++中除了上述功能外,還用來定義類的成員變量和函數。即靜態成員和靜態成員函數。
注意:編程時static的記憶性,和全局性的特色可讓在不一樣時期調用的函數進行通訊,傳遞信息,而C++的靜態成員則能夠在多個對象實例間進行通訊,傳遞信息。 算法
面試題5:C中的malloc和C++中的new有什麼區別
malloc和new有如下不一樣:
(1)new、delete 是操做符,能夠重載,只能在C++中使用。 (2)malloc、free是函數,能夠覆蓋,C、C++中均可以使用。
(3)new 能夠調用對象的構造函數,對應的delete調用相應的析構函數。 (4)malloc僅僅分配內存,free僅僅回收內存,並不執行構造和析構函數
(5)new、delete返回的是某種數據類型指針,malloc、free返回的是void指針。
注意:malloc申請的內存空間要用free釋放,而new申請的內存空間要用delete釋放,不要混用。由於二者實現的機理不一樣。 編程
面試題6:寫一個「標準」宏MIN
#define min(a,b)((a)<=(b)?(a):(b))
注意:在調用時必定要注意這個宏定義的反作用,以下調用:
((++*p)<=(x)?(++*p):(x)。
p指針就自加了兩次,違背了MIN的本意。數組
面試題7:一個指針能夠是volatile嗎
能夠,由於指針和普通變量同樣,有時也有變化程序的不可控性。常見例:子中斷服務子程序修改一個指向一個buffer的指針時,必須用volatile來修飾這個指針。
說明:指針是一種普通的變量,從訪問上沒有什麼不一樣於其餘變量的特性。其保存的數值是個整型數據,和整型變量不一樣的是,這個整型數據指向的是一段內存地址。 安全
面試題8:a和&a有什麼區別
請寫出如下代碼的打印結果,主要目的是考察a和&a的區別。
#include<stdio.h> void main( void ) { int a[5]={1,2,3,4,5}; int *ptr=(int *)(&a+1); printf("%d,%d",*(a+1),*(ptr-1)); return; }
輸出結果:2,5。
注意:數組名a能夠做數組的首地址,而&a是數組的指針。思考,將原式的int *ptr=(int *)(&a+1);改成int *ptr=(int *)(a+1);時輸出結果將是什麼呢? 數據結構
面試題9:簡述C、C++程序編譯的內存分配狀況
C、C++中內存分配方式能夠分爲三種:
(1)從靜態存儲區域分配:
內存在程序編譯時就已經分配好,這塊內存在程序的整個運行期間都存在。速度快、不容易出錯,由於有系統會善後。例如全局變量,static變量等。
(2)在棧上分配:
在執行函數時,函數內局部變量的存儲單元都在棧上建立,函數執行結束時這些存儲單元自動被釋放。棧內存分配運算內置於處理器的指令集中,效率很高,可是分配的內存容量有限。
(3)從堆上分配:
即動態內存分配。程序在運行的時候用malloc或new申請任意大小的內存,程序員本身負責在什麼時候用free或delete釋放內存。動態內存的生存期由程序員決定,使用很是靈活。若是在堆上分配了空間,就有責任回收它,不然運行的程序會出現內存泄漏,另外頻繁地分配和釋放不一樣大小的堆空間將會產生堆內碎塊。
一個C、C++程序編譯時內存分爲5大存儲區:堆區、棧區、全局區、文字常量區、程序代碼區。函數
面試題10:簡述strcpy、sprintf與memcpy的區別
三者主要有如下不一樣之處:
(1)操做對象不一樣,strcpy的兩個操做對象均爲字符串,sprintf的操做源對象能夠是多種數據類型,目的操做對象是字符串,memcpy 的兩個對象就是兩個任意可操做的內存地址,並不限於何種數據類型。 (2)執行效率不一樣,memcpy最高,strcpy次之,sprintf的效率最低。 (3)實現功能不一樣,strcpy主要實現字符串變量間的拷貝,sprintf主要實現其餘數據類型格式到字符串的轉化,memcpy主要是內存塊間的拷貝。
說明:strcpy、sprintf與memcpy均可以實現拷貝的功能,可是針對的對象不一樣,根據實際需求,來選擇合適的函數實現拷貝功能。
面試題11:設置地址爲0x67a9的整型變量的值爲0xaa66
int *ptr;
ptr = (int *)0x67a9; *ptr = 0xaa66;
說明:這道題就是強制類型轉換的典型例子,不管在什麼平臺地址長度和整型數據的長度是同樣的,即一個整型數據能夠強制轉換成地址指針類型,只要有意義便可。
面試題12:面向對象的三大特徵
面向對象的三大特徵是封裝性、繼承性和多態性:
封裝性:將客觀事物抽象成類,每一個類對自身的數據和方法實行protection(private, protected,public)。 繼承性:廣義的繼承有三種實現形式:實現繼承(使用基類的屬性和方法而無需額外編碼的能力)、可
視繼承(子窗體使用父窗體的外觀和實現代碼)、接口繼承(僅使用屬性和方法,實現滯後到子類實現)。
多態性:是將父類對象設置成爲和一個或更多它的子對象相等的技術。用子類對象給父類對象賦值
以後,父類對象就能夠根據當前賦值給它的子對象的特性以不一樣的方式運做。
說明:面向對象的三個特徵是實現面向對象技術的關鍵,每個特徵的相關技術都很是的複雜,程序員應該多看、多練。
面試題13:C++的空類有哪些成員函數
缺省構造函數。 缺省拷貝構造函數。 缺省析構函數。 缺省賦值運算符。 缺省取址運算符。
缺省取址運算符 const。
注意:有些書上只是簡單的介紹了前四個函數。沒有說起後面這兩個函數。但後面這兩個函數也是空類的默認函數。另外須要注意的是,只有當實際使用這些函數的時候,編譯器纔會去定義它們。
面試題14:談談你對拷貝構造函數和賦值運算符的認識
拷貝構造函數和賦值運算符重載有如下兩個不一樣之處: (1)拷貝構造函數生成新的類對象,而賦值運算符不能。
(2)因爲拷貝構造函數是直接構造一個新的類對象,因此在初始化這個對象以前不用檢驗源對象是否和新建對象相同。而賦值運算符則須要這個操做,另外賦值運算中若是原來的對象中有內存分配要先把內存釋放掉
注意:當有類中有指針類型的成員變量時,必定要重寫拷貝構造函數和賦值運算符,不要使用默認的。
面試題15:用C++設計一個不能被繼承的類
template <typename T>
class A
{
friend T;
private:
A() {}
~A() {}
};
class B : virtual public A<B>
{
public:
B() {}
~B() {}
};
class C : virtual public B
{
public:
C() {}
~C() {}
};
void main( void )
{
B b;
//C c;
return;
}
注意:構造函數是繼承實現的關鍵,每次子類對象構造時,首先調用的是父類的構造函數,而後纔是本身的。
面試題16:訪問基類的私有虛函數
寫出如下程序的輸出結果:
#include <iostream.h>
class A
{
virtual void g(){
cout << "A::g" << endl; }
private:
virtual void f(){
cout << "A::f" << endl; }
};
class B : public A {
void g() {
cout << "B::g" << endl; }
virtual void h(){
cout << "B::h" << endl; }
};
typedef void( *Fun )( void );
void main() {
B b;
Fun pFun;
for(int i = 0 i < 3; i++) {
pFun = ( Fun )*( ( int* ) * ( int* )( &b ) + i );
pFun(); }
}
輸出結果:
B::g A::f B::h
注意:本題主要考察了面試者對虛函數的理解程度。一個對虛函數不瞭解的人很難正確的作出本題。在學習面向對象的多態性時必定要深入理解虛函數表的工做原理。
面試題17:簡述類成員函數的重寫、重載和隱藏的區別
(1)重寫和重載主要有如下幾點不一樣。
範圍的區別:被重寫的和重寫的函數在兩個類中,而重載和被重載的函數在同一個類中。
參數的區別:被重寫函數和重寫函數的參數列表必定相同,而被重載函數和重載函數的參數列表必定不一樣。
virtual的區別:重寫的基類中被重寫的函數必需要有virtual修飾,而重載函數和被重載函數能夠被
virtual修飾,也能夠沒有。
(2)隱藏和重寫、重載有如下幾點不一樣。
與重載的範圍不一樣:和重寫同樣,隱藏函數和被隱藏函數不在同一個類中。
參數的區別:隱藏函數和被隱藏的函數的參數列表能夠相同,也可不一樣,可是函數名確定要相同。
當參數不相同時,不管基類中的參數是否被virtual修飾,基類的函數都是被隱藏,而不是被重寫。 說明:雖然重載和覆蓋都是實現多態的基礎,可是二者實現的技術徹底不相同,達到的目的也是徹底不一樣的,覆蓋是動態態綁定的多態,而重載是靜態綁定的多態。
面試題18:簡述多態實現的原理
編譯器發現一個類中有虛函數,便會當即爲此類生成虛函數表 vtable。虛函數表的各表項爲指向對應虛函數的指針。編譯器還會在此類中隱含插入一個指針vptr(對vc編譯器來講,它插在類的第一個位置上)指向虛函數表。調用此類的構造函數時,在類的構造函數中,編譯器會隱含執行vptr與vtable的關聯代碼,將vptr指向對應的vtable,將類與此類的vtable聯繫了起來。另外在調用類的構造函數時,指向基礎類的指針此時已經變成指向具體的類的this指針,這樣依靠此this指針便可獲得正確的vtable,。如此才能真正與函數體進行鏈接,這就是動態聯編,實現多態的基本原理。
注意:必定要區分虛函數,純虛函數、虛擬繼承的關係和區別。牢記虛函數實現原理,由於多態C++面試的重要考點之一,而虛函數是實現多態的基礎。
面試題19:鏈表和數組有什麼區別
數組和鏈表有如下幾點不一樣:
(1)存儲形式:數組是一塊連續的空間,聲明時就要肯定長度。鏈表是一塊可不連續的動態空間,長度可變,每一個結點要保存相鄰結點指針。
(2)數據查找:數組的線性查找速度快,查找操做直接使用偏移地址。鏈表須要按順序檢索結點,效率低。
(3)數據插入或刪除:鏈表能夠快速插入和刪除結點,而數組則可能須要大量數據移動。
(4)越界問題:鏈表不存在越界問題,數組有越界問題。
說明:在選擇數組或鏈表數據結構時,必定要根據實際須要進行選擇。數組便於查詢,鏈表便於插入刪除。數組節省空間可是長度固定,鏈表雖然變長可是佔了更多的存儲空間。
面試題20:怎樣把一個單鏈表反序
(1)反轉一個鏈表。循環算法。
List reverse(List n) { if(!n) //判斷鏈表是否爲空,爲空即退出。
{ return n; }
list cur = n.next;
//保存頭結點的下個結點 list pre = n; //保存頭結點
list tmp;
pre.next = null; //頭結點的指針指空,轉換後變尾結點 while ( NULL != cur.next ) //循環直到cur.next爲空 { tmp = cur;
//實現如圖10.3—圖10.5所示
tmp.next = pre
pre = tmp;
cur = cur.next; }
return tmp;
//f返回頭指針
}
(2)反轉一個鏈表。遞歸算法。
List *reverse( List *oldList, List *newHead = NULL ) { List *next = oldList-> next; //記錄上次翻轉後的鏈表 oldList-> next = newHead; //將當前結點插入到翻轉後鏈表的開頭 newHead = oldList; //遞歸處理剩餘的鏈表 return ( next==NULL )? newHead: reverse( t, newHead );
}
說明:循環算法就是圖10.2—圖10.5的移動過程,比較好理解和想到。遞歸算法的設計雖有一點難度,可是理解了循環算法,再設計遞歸算法就簡單多了。
面試題 21:簡述隊列和棧的異同
隊列和棧都是線性存儲結構,可是二者的插入和刪除數據的操做不一樣,隊列是「先進先出」,棧是「後進先出」。
注意:區別棧區和堆區。堆區的存取是「順序隨意」,而棧區是「後進先出」。棧由編譯器自動分配釋放 ,存放函數的參數值,局部變量的值等。其操做方式相似於數據結構中的棧。堆通常由程序員分配釋放, 若程序員不釋放,程序結束時可能由OS回收。分配方式相似於鏈表。
它與本題中的堆和棧是兩回事。堆棧只是一種數據結構,而堆區和棧區是程序的不一樣內存存儲區域。
面試題22:可否用兩個棧實現一個隊列的功能
結點結構體:
typedef struct node { int data; node *next; }node,*LinkStack;
建立空棧:
LinkStack CreateNULLStack( LinkStack &S) { S = (LinkStack)malloc( sizeof( node ) ); //申請新結點
if( NULL == S)
{ printf("Fail to malloc a new node.\n");
return NULL;
}
S->data = 0; //初始化新結點
S->next = NULL; return S;
}
棧的插入函數:
LinkStack Push( LinkStack &S, int data) { if( NULL == S) //檢驗棧
{ printf("There no node in stack!"); return NULL;
}
LinkStack p = NULL;
p = (LinkStack)malloc( sizeof( node ) );
//申請新結點
if( NULL == p) { printf("Fail to malloc a new node.\n"); return S; }
if( NULL == S->next) { p->next = NULL; } else { p->next = S->next; }
p->data = data; //初始化新結點 S->next = p; //插入新結點 return S;
}
出棧函數:
node Pop( LinkStack &S) { node temp; temp.data = 0; temp.next = NULL; if( NULL == S) //檢驗棧
{ printf("There no node in stack!"); return temp; }
temp = *S;
if( S->next == NULL ) { printf("The stack is NULL,can't pop!\n"); return temp; }
LinkStack p = S ->next;
//節點出棧
S->next = S->next->next; temp = *p; free( p ); p = NULL; return temp;
}
雙棧實現隊列的入隊函數:
LinkStack StackToQueuPush( LinkStack &S, int data) { node n; LinkStack S1 = NULL; CreateNULLStack( S1 ); //建立空棧
while( NULL != S->next )
//S出棧入S1
{ n = Pop( S ); Push( S1, n.data ); }
Push( S1, data ); //新結點入棧
while( NULL != S1->next )
//S1出棧入S
{ n = Pop( S1 ); Push( S, n.data );
}
return S;
}
說明:用兩個棧可以實現一個隊列的功能,那用兩個隊列可否實現一個隊列的功能呢?結果是否認的,由於棧是先進後出,將兩個棧連在一塊兒,就是先進先出。而隊列是現先進先出,不管多少個連在一塊兒都是先進先出,而沒法實現先進後出。
面試題23:計算一顆二叉樹的深度
深度的計算函數:
int depth(BiTree T) { if(!T) return 0; //判斷當前結點是否爲葉子結點
int d1= depth(T->lchild); //求當前結點的左孩子樹的深度 int d2= depth(T->rchild);
//求當前結點的右孩子樹的深度
return (d1>d2?d1:d2)+1;
}
注意:根據二叉樹的結構特色,不少算法均可以用遞歸算法來實現。
面試題24:編碼實現直接插入排序
直接插入排序編程實現以下:
#include<iostream.h> void main( void ) { int ARRAY[10] = { 0, 6, 3, 2, 7, 5, 4, 9, 1, 8 }; int i,j; for( i = 0; i < 10; i++) { cout<<ARRAY[i]<<" "; } cout<<endl; for( i = 2; i <= 10; i++ ) //將ARRAY[2],…,ARRAY[n]依次按序插入 { if(ARRAY[i] < ARRAY[i-1]) //若是ARRAY[i]大於一切有序的數值, //ARRAY[i]將保持原位不動
{ ARRAY[0] = ARRAY[i];
//將ARRAY[0]看作是哨兵,是ARRAY[i]的副本
j = i - 1;
do{ //從右向左在有序區ARRAY[1..i-1]中 //查找ARRAY[i]的插入位置
ARRAY[j+1] = ARRAY[j];
//將數值大於ARRAY[i]記錄後移 j--
}while( ARRAY[0] < ARRAY[j] ); ARRAY[j+1]=ARRAY[0]; //ARRAY[i]插入到正確的位置上
}
}
for( i = 0; i < 10; i++)
{ cout<<ARRAY[i]<<" "; }
cout<<endl;
}
注意:全部爲簡化邊界條件而引入的附加結點(元素)都可稱爲哨兵。引入哨兵後使得查找循環條件的時間大約減小了一半,對於記錄數較大的文件節約的時間就至關可觀。相似於排序這樣使用頻率很是高的算法,要儘量地減小其運行時間。因此不能把上述算法中的哨兵視爲雕蟲小技。
面試題25:編碼實現冒泡排序
冒泡排序編程實現以下:
#include <stdio.h> #define LEN 10
//數組長度
void main( void ) { int ARRAY[10] = { 0, 6, 3, 2, 7, 5, 4, 9, 1, 8 }; //待排序數組 printf( "\n" );
for( int a = 0; a < LEN; a++ )
//打印數組內容
{ printf( "%d ", ARRAY[a] ); }
int i = 0; int j = 0;
bool isChange; //設定交換標誌
for( i = 1; i < LEN; i++ ) { //最多作LEN-1趟排序
isChange = 0; //本趟排序開始前,交換標誌應爲假
for( j = LEN-1; j >= i; j-- )
//對當前無序區ARRAY[i..LEN]自下向上掃描
{ if( ARRAY[j+1] < ARRAY[j] ) { //交換記錄
ARRAY[0] = ARRAY[j+1]; //ARRAY[0]不是哨兵,僅作暫存單元
ARRAY[j+1] = ARRAY[j]; ARRAY[j] = ARRAY[0];
isChange = 1;
//發生了交換,故將交換標誌置爲真
} }
printf( "\n" );
for( a = 0; a < LEN; a++)
//打印本次排序後數組內容
{ printf( "%d ", ARRAY[a] ); }
if( !isChange )
//本趟排序未發生交換,提早終止算法
{ break; } }
printf( "\n" ); return;
}
面試題26:編碼實現直接選擇排序
#include"stdio.h" #define LEN 9 void main( void ) { int ARRAY[LEN]={ 5, 6, 8, 2, 4, 1, 9, 3, 7 }; //待序數組
printf("Before sorted:\n");
for( int m = 0; m < LEN; m++ ) //打印排序前數組
{ printf( "%d ", ARRAY[m] ); }
for (int i = 1; i <= LEN - 1; i++)
//選擇排序
{ int t = i - 1; int temp = 0; for (int j = i; j < LEN; j++) { if (ARRAY[j] < ARRAY[t]) { t = j; } } if (t != (i - 1)) { temp = ARRAY[i - 1]; ARRAY[i - 1] = ARRAY[t]; ARRAY[t] = temp; } }
printf( "\n" );
printf("After sorted:\n"); for( i = 0; i < LEN; i++ ) //打印排序後數組
{ printf( "%d ", ARRAY[i] ); }
printf( "\n" );
}
注意:在直接選擇排序中,具備相同關鍵碼的對象可能會顛倒次序,於是直接選擇排序算法是一種不穩定的排序方法。在本例中只是例舉了簡單的整形數組排序,確定不會有什麼問題。可是在複雜的數據元素序列組合中,只是根據單一的某一個關鍵值排序,直接選擇排序則不保證其穩定性,這是直接選擇排序的一個弱點。
面試題27:編程實現堆排序
堆排序編程實現:
#include <stdio.h>
void createHeep(int ARRAY[],int sPoint, int Len)
//生成大根堆
{ while( ( 2 * sPoint + 1 ) < Len ) { int mPoint = 2 * sPoint + 1 if( ( 2 * sPoint + 2 ) < Len ) { if(ARRAY[ 2 * sPoint + 1 ] < ARRAY[ 2 * sPoint + 2 ] ) { mPoint = 2*sPoint+2; } } if(ARRAY[ sPoint ] < ARRAY[ mPoint ]) //堆被破壞,須要從新調整 { int tmpData= ARRAY[ sPoint ];
//交換sPoint與mPoint的數據
ARRAY[ sPoint ] = ARRAY[ mPoint ];
ARRAY[ mPoint ] = tmpData;
sPoint = mPoint }
else {
break;
//堆未破壞,再也不須要調整
}
} return; }
void heepSort( int ARRAY[], int Len ) //堆排序
{ int i=0;
for ( i = ( Len / 2 - 1 ); i >= 0; i-- ) //將Hr[0,Lenght-1]建成大根堆
{ createHeep(ARRAY, i, Len); }
for ( i = Len - 1; i > 0; i-- ) { int tmpData = ARRAY[0]; //與最後一個記錄交換
ARRAY[0] = ARRAY[i]; ARRAY[i] = tmpData;
createHeep( ARRAY, 0, i );
//將H.r[0..i]從新調整爲大根堆
} return; }
int main( void )
{ int ARRAY[] ={ 5, 4, 7, 3, 9, 1, 6, 8, 2}; printf("Before sorted:\n");
//打印排序前數組內容
for ( int i = 0; i < 9; i++ ) { printf("%d ", ARRAY[i]); }
printf("\n");
heepSort( ARRAY, 9 ); //堆排序
printf("After sorted:\n");
//打印排序後數組內容
for( i = 0; i < 9; i++ ) { printf( "%d ", ARRAY[i] ); }
printf( "\n" ); return 0;
}
說明:堆排序,雖然實現複雜,可是很是的實用。另外讀者但是本身設計實現小堆排序的算法。雖然和大堆排序的實現過程類似,可是卻能夠加深對堆排序的記憶和理解。
面試題28:編程實現基數排序
#include <stdio.h> #include <malloc.h> #define LEN 8
typedef struct node //隊列結點
{ int data; struct node * next; }node,*QueueNode;
typedef struct Queue
//隊列
{ QueueNode front; QueueNode rear; }Queue,*QueueLink;
QueueLink CreateNullQueue( QueueLink &Q) //建立空隊列
{ Q = NULL;
Q = ( QueueLink )malloc( sizeof( Queue ) ); if( NULL == Q ) { printf("Fail to malloc null queue!\n"); return NULL; }
Q->front = ( QueueNode )malloc( sizeof( node ) ); Q->rear = ( QueueNode )malloc( sizeof( node ) ); if( NULL == Q->front || NULL == Q->rear ) { printf("Fail to malloc a new queue's fornt or rear!\n"); return NULL; } Q->rear = NULL; Q->front->next= Q->rear; return Q; }
int lenData( node data[], int len) //計算隊列中各結點的數據的最大位數 { int m = 0; int temp = 0; int d; for( int i = 0; i < len; i++) { d = data[i].data; while( d > 0) { d /= 10; temp ++; } if( temp > m ) { m = temp; } temp = 0; } return m; }
QueueLink Push( QueueLink &Q , node node ) //將數據壓入隊列
{ QueueNode p1,p;
p =( QueueNode )malloc( sizeof( node ) ); if( NULL == p ) { printf("Fail to malloc a new node!\n"); return NULL; }
p1 = Q->front;
while(p1->next != NULL) { p1 = p1->next; }
p->data = node.data; p1->next = p;
p->next = Q->rear;
return NULL; }
node Pop( QueueLink &Q)
//數據出隊列
{ node temp; temp.data = 0; temp.next = NULL; QueueNode p; p = Q->front->next; if( p != Q->rear ) { temp = *p; Q->front->next = p->next; free( p ); p = NULL; } return temp; }
int IsEmpty( QueueLink Q) { if( Q->front->next == Q->rear ) { return 0; } return 1; }
int main( void ) { int i = 0; int Max = 0; //記錄結點中數據的最大位數
int d = 10; int power = 1; int k = 0;
node Array[LEN] ={{450, NULL}, {32,NULL}, { 781,NULL}, { 57 ,NULL},組 { 145,NULL},{ 613,NULL},{ 401,NULL},{ 594,NULL}};
//隊列結點數
QueueLink Queue[10];
for( i = 0; i < 10; i++) { CreateNullQueue( Queue[i]); //初始化隊列數組
}
for( i = 0; i < LEN; i++) { printf("%d ",Array[i].data); } printf("\n"); Max = lenData( Array, LEN ); //計算數組中關鍵字的最大位數
printf("%d\n",Max);
for(int j = 0; j < Max; j++) //按位排序
{ if(j == 0) power = 1; else power = power *d;
for(i = 0; i < LEN; i++) { k = Array[i].data /power - (Array[i].data/(power * d)) * d; Push( Queue[k], Array[i] ); }
for(int l = 0, k = 0; l < d; l++) //排序後出隊列重入數組 { while( IsEmpty( Queue[l] ) ) { Array[k++] = Pop( Queue[l] ); } }
for( int t = 0; t < LEN; t++) { printf("%d ",Array[t].data); }
printf("\n");
}
return 0;
}
說明:隊列爲基數排序的實現提供了很大的方便,適當的數據機構能夠減小算法的複雜度,讓更多的算法實現更容易。
面試題29:談談你對編程規範的理解或認識
編程規範可總結爲:程序的可行性,可讀性、可移植性以及可測試性。
說明:這是編程規範的總綱目,面試者不必定要去背誦上面給出的那幾個例子,應該去理解這幾個例子說明的問題,想想,本身如何解決可行性、可讀性、可移植性以及可測試性這幾個問題,結合以上幾個例子和本身平時的編程習慣來回答這個問題。
面試題30:short i = 0; i = i + 1L;這兩句有錯嗎
代碼一是錯的,代碼二是正確的。
說明:在數據安全的狀況下大類型的數據向小類型的數據轉換必定要顯示的強制類型轉換。
面試題31:&&和&、||和|有什麼區別
(1)&和|對操做數進行求值運算,&&和||只是判斷邏輯關係。
(2)&&和||在在判斷左側操做數就能肯定結果的狀況下就再也不對右側操做數求值。
注意:在編程的時候有些時候將&&或||替換成&或|沒有出錯,可是其邏輯是錯誤的,可能會致使不可預想的後果(好比當兩個操做數一個是1另外一個是2時)。
面試題32:C++的引用和C語言的指針有什麼區別
指針和引用主要有如下區別:
(1)引用必須被初始化,可是不分配存儲空間。指針不聲明時初始化,在初始化的時候須要分配存儲空間。
(2)引用初始化之後不能被改變,指針能夠改變所指的對象。
(3)不存在指向空值的引用,可是存在指向空值的指針。
注意:引用做爲函數參數時,會引起必定的問題,由於讓引用做參數,目的就是想改變這個引用所指向地址的內容,而函數調用時傳入的是實參,看不出函數的參數是正常變量,仍是引用,所以可能會引起錯誤。因此使用時必定要當心謹慎。
面試題33:在二元樹中找出和爲某一值的全部路徑
輸入一個整數和一棵二元樹。從樹的根結點開始往下訪問,一直到葉結點所通過的全部結點造成一條路徑。打印出和與輸入整數相等的全部路徑。例如,輸入整數9和以下二元樹:
3 / \ 2 6
/ \ 5 4
則打印出兩條路徑:3,6和3,2,4。 【答案】
typedef struct path { BiTNode* tree;
//結點數據成員 struct path* next; //結點指針成員
}PATH,*pPath;
初始化樹的結點棧:
void init_path( pPath* L ) { *L = ( pPath )malloc( sizeof( PATH ) ); //建立空樹
( *L )->next = NULL;
}
樹結點入棧函數:
void push_path(pPath H, pBTree T) { pPath p = H->next; pPath q = H; while( NULL != p )
{ q = p;
p = p->next;
}
p = ( pPath )malloc( sizeof( PATH ) ); //申請新結點
p->next = NULL; //初始化新結點
p->tree = T; q->next = p;
//新結點入棧
}
樹結點打印函數:
void print_path( pPath L ) { pPath p = L->next; while( NULL != p ) //打印當前棧中全部數據
{ printf("%d, ", p->tree->data); p = p->next;
}
}
樹結點出棧函數:
void pop_path( pPath H ) { pPath p = H->next; pPath q = H; if( NULL == p ) //檢驗當前棧是否爲空
{ printf("Stack is null!\n"); return; }
p = p->next;
while( NULL != p ) //出棧
{ q = q->next; p = p->next; }
free( q->next ); //釋放出棧結點空間
q->next = NULL;
}
判斷結點是否爲葉子結點:
int IsLeaf(pBTree T) { return ( T->lchild == NULL )&&( T->rchild==NULL ); }
查找符合條件的路徑:
int find_path(pBTree T, int sum, pPath L)
{ push_path( L, T); record += T->data;
if( ( record == sum ) && ( IsLeaf( T ) ) )
//打印符合條件的當前路徑
{ print_path( L ); printf( "\n" );
}
if( T->lchild != NULL ) //遞歸查找當前節點的左孩子
{ find_path( T->lchild, sum, L); }
if( T->rchild != NULL ) //遞歸查找當前節點的右孩子
{ find_path( T->rchild, sum, L);
}
record -= T->data; pop_path(L); return 0;
}
注意:數據結構必定要活學活用,例如本題,把全部的結點都壓入棧,而不符合條件的結點彈出棧,很容易實現了有效路徑的查找。雖然用鏈表也能夠實現,可是用棧更利於理解這個問題,即適當的數據結構爲更好的算法設計提供了有利的條件。
面試題34:寫一個「標準」宏MIN
寫一個「標準」宏MIN,這個宏輸入兩個參數而且返回較小的一個。 【答案】
#define min(a,b)((a)<=(b)?(a):(b))
注意:在調用時必定要注意這個宏定義的反作用,以下調用:
((++*p)<=(x)?(++*p):(x)。
p指針就自加了兩次,違背了MIN的本意。
面試題35:typedef和define有什麼區別
(1)用法不一樣:typedef用來定義一種數據類型的別名,加強程序的可讀性。define主要用來定義常量,以及書寫複雜使用頻繁的宏。
(2)執行時間不一樣:typedef是編譯過程的一部分,有類型檢查的功能。define是宏定義,是預編譯的部分,其發生在編譯以前,只是簡單的進行字符串的替換,不進行類型的檢查。
(3)做用域不一樣:typedef有做用域限定。define不受做用域約束,只要是在define聲明後的引用都是正確的。
(4)對指針的操做不一樣:typedef和define定義的指針時有很大的區別。
注意:typedef定義是語句,由於句尾要加上分號。而define不是語句,千萬不能在句尾加分號。
面試題36:關鍵字const是什麼
const用來定義一個只讀的變量或對象。主要優勢:便於類型檢查、同宏定義同樣能夠方便地進行參數的修改和調整、節省空間,避免沒必要要的內存分配、可爲函數重載提供參考。
說明:const修飾函數參數,是一種編程規範的要求,便於閱讀,一看即知這個參數不能被改變,實現時不易出錯。
面試題37:static有什麼做用
static在C中主要用於定義全局靜態變量、定義局部靜態變量、定義靜態函數。在C++中新增了兩種做用:定義靜態數據成員、靜態函數成員。
注意:由於static定義的變量分配在靜態區,因此其定義的變量的默認值爲0,普通變量的默認值爲隨機數,在定義指針變量時要特別注意。
面試題38:extern有什麼做用
extern標識的變量或者函數聲明其定義在別的文件中,提示編譯器遇到此變量和函數時在其它模塊中尋找其定義。
面試題39:流操做符重載爲何返回引用
在程序中,流操做符>>和<<常常連續使用。所以這兩個操做符的返回值應該是一個仍舊支持這兩個操做符的流引用。其餘的數據類型都沒法作到這一點。
注意:除了在賦值操做符和流操做符以外的其餘的一些操做符中,如+、-、*、/等卻千萬不能返回引用。由於這四個操做符的對象都是右值,所以,它們必須構造一個對象做爲返回值。
面試題40:簡述指針常量與常量指針區別
指針常量是指定義了一個指針,這個指針的值只能在定義時初始化,其餘地方不能改變。常量指針是指定義了一個指針,這個指針指向一個只讀的對象,不能經過常量指針來改變這個對象的值。
指針常量強調的是指針的不可改變性,而常量指針強調的是指針對其所指對象的不可改變性。 注意:不管是指針常量仍是常量指針,其最大的用途就是做爲函數的形式參數,保證明參在被調用函數中的不可改變特性。
面試題41:數組名和指針的區別
請寫出如下代碼的打印結果:
#include <iostream.h> #include <string.h> void main(void) { char str[13]="Hello world!";
char *pStr="Hello world!";
cout<<sizeof(str)<<endl; cout<<sizeof(pStr)<<endl; cout<<strlen(str)<<endl; cout<<strlen(pStr)<<endl; return;
}
【答案】 打印結果:
13 4 12 12
注意:必定要記得數組名並非真正意義上的指針,它的內涵要比指針豐富的多。可是當數組名當作參數傳遞給函數後,其失去原來的含義,變做普通的指針。另外要注意sizeof不是函數,只是操做符。
面試題42:如何避免「野指針」
「野指針」產生緣由及解決辦法以下:
(1)指針變量聲明時沒有被初始化。解決辦法:指針聲明時初始化,能夠是具體的地址值,也可以讓它指向NULL。
(2)指針 p 被 free 或者 delete 以後,沒有置爲 NULL。解決辦法:指針指向的內存空間被釋放後指針應該指向NULL。
(3)指針操做超越了變量的做用範圍。解決辦法:在變量的做用域結束前釋放掉變量的地址空間而且讓指針指向NULL。
注意:「野指針」的解決方法也是編程規範的基本原則,平時使用指針時必定要避免產生「野指針」,在使用指針前必定要檢驗指針的合法性。
面試題43:常引用有什麼做用
常引用的引入主要是爲了不使用變量的引用時,在不知情的狀況下改變變量的值。常引用主要用於定義一個普通變量的只讀屬性的別名、做爲函數的傳入形參,避免實參在調用函數中被意外的改變。 說明:不少狀況下,須要用常引用作形參,被引用對象等效於常對象,不能在函數中改變實參的值,這樣的好處是有較高的易讀性和較小的出錯率。
面試題44:編碼實現字符串轉化爲數字
編碼實現函數atoi(),設計一個程序,把一個字符串轉化爲一個整型數值。例如數字:「5486321」,轉化成字符:5486321。
【答案】
int myAtoi(const char * str) {
int num = 0; //保存轉換後的數值
int isNegative = 0;
//記錄字符串中是否有負號
int n =0;
char *p = str; if(p == NULL) //判斷指針的合法性
{ return -1; }
while(*p++ != '\0') //計算數字符串度
{ n++; }
p = str;
if(p[0] == '-') //判斷數組是否有負號
{ isNegative = 1;
}
char temp = '0';
for(int i = 0 i < n; i++) { char temp = *p++; if(temp > '9' ||temp < '0') //濾除非數字字符
{ continue; }
if(num !=0 || temp != '0') //濾除字符串開始的0字符 {
temp -= 0x30;
//將數字字符轉換爲數值
num += temp *int( pow(10 , n - 1 -i) ); } }
if(isNegative) //若是字符串中有負號,將數值取反
{ return (0 - num); } else { return num; //返回轉換後的數值
}
}
注意:此段代碼只是實現了十進制字符串到數字的轉化,讀者能夠本身去實現2進制,8進制,10進制,16進制的轉化。
面試題45:簡述strcpy、sprintf與memcpy的區別
三者主要有如下不一樣之處:
(1)操做對象不一樣,strcpy的兩個操做對象均爲字符串,sprintf的操做源對象能夠是多種數據類型,
目的操做對象是字符串,memcpy 的兩個對象就是兩個任意可操做的內存地址,並不限於何種數據類型。
(2)執行效率不一樣,memcpy最高,strcpy次之,sprintf的效率最低。
(3)實現功能不一樣,strcpy主要實現字符串變量間的拷貝,sprintf主要實現其餘數據類型格式到字符串的轉化,memcpy主要是內存塊間的拷貝。
說明:strcpy、sprintf與memcpy均可以實現拷貝的功能,可是針對的對象不一樣,根據實際需求,來選擇合適的函數實現拷貝功能。
面試題46:用C編寫一個死循環程序
while(1) { }
說明:不少種途徑均可實現同一種功能,可是不一樣的方法時間和空間佔用度不一樣,特別是對於嵌入式軟件,處理器速度比較慢,存儲空間較小,因此時間和空間優點是選擇各類方法的首要考慮條件。
面試題47:編碼實現某一變量某位清0或置1
給定一個整型變量a,寫兩段代碼,第一個設置a的bit 3,第二個清a的bit 3,在以上兩個操做中,要保持其餘位不變。
【答案】
#define BIT3 (0x1 << 3 ) Satic int a;
設置a的bit 3:
void set_bit3( void ) { a |= BIT3; //將a第3位置1
}
清a的bit 3
void set_bit3( void ) { a &= ~BIT3; //將a第3位清零
}
說明:在置或清變量或寄存器的某一位時,必定要注意不要影響其餘位。因此用加減法是很難實現的。
面試題48:評論下面這個中斷函數
中斷是嵌入式系統中重要的組成部分,這致使了不少編譯開發商提供一種擴展——讓標準C支持中斷。具體表明事實是,產生了一個新的關鍵字__interrupt。下面的代碼就使用了__interrupt關鍵字去定義一箇中斷服務子程序(ISR),請評論如下這段代碼。
__interrupt double compute_area (double radius) {
double area = PI * radius * radius; printf(" Area = %f", area); return area;
}
【答案】
這段中斷服務程序主要有如下四個問題: (1)ISR 不能返回一個值。
(2)ISR 不能傳遞參數。
(3)在ISR 中作浮點運算是不明智的。 (4)printf()常常有重入和性能上的問題。
注意:本題的第三個和第四個問題雖不是考察的重點,可是若是能提到這兩點可給面試官留下一個好印象。
面試題49:構造函數可否爲虛函數
構造函數不能是虛函數。並且不能在構造函數中調用虛函數,由於那樣實際執行的是父類的對應函數,由於本身尚未構造好。析構函數能夠是虛函數,並且,在一個複雜類結構中,這每每是必須的。析構函數也能夠是純虛函數,但純虛析構函數必須有定義體,由於析構函數的調用是在子類中隱含的。
說明:虛函數的動態綁定特性是實現重載的關鍵技術,動態綁定根據實際的調用狀況查詢相應類的虛函數表,調用相應的虛函數。
面試題50:談談你對面向對象的認識 面向對象能夠理解成對待每個問題,都是首先要肯定這個問題由幾個部分組成,而每個部分其實就是一個對象。而後再分別設計這些對象,最後獲得整個程序。傳統的程序設計可能是基於功能的思想來進行考慮和設計的,而面向對象的程序設計則是基於對象的角度來考慮問題。這樣作可以使得程序更加的簡潔清晰。 說明:編程中接觸最多的「面向對象編程技術」僅僅是面向對象技術中的一個組成部分。發揮面向對象技術的優點是一個綜合的技術問題,不只須要面向對象的分析,設計和編程技術,並且須要藉助必要的建模和開發工具。