1.已知strcpy函數的原型是:
char * strcpy(char * strDest,const char * strSrc);
1.不調用庫函數,實作strcpy函數。
2.解釋為什麼要傳回char *。
解說:
1.strcpy的實作代碼
char * strcpy(char * strDest,const char * strSrc)
{
if ((strDest==NULL)||(strSrc==NULL)) //[1]
throw "Invalid argument(s)"; //[2]
char * strDestCopy=strDest; //[3]
while ((*strDest++=*strSrc++)!='/0'); //[4]
return strDestCopy;
}
錯誤的做法:
[1]
(A)不檢查指針的有效性,說明答題者不注重代碼的健壯性。
(B)檢查指針的有效性時使用((!strDest)||(!strSrc))或(!(strDest&&strSrc)),說明答題者 對C語言中類型的隐式轉換沒有深刻認識。在本例中char *轉換為bool即是類型隐式轉換,這種功能雖然靈活,但更多的是導緻出錯機率增大和維護成本升高。是以C++專門增加了bool、true、false 三個關鍵字以提供更安全的條件表達式。
(C)檢查指針的有效性時使用((strDest==0)||(strSrc==0)),說明答題者不知道使用常量的好處。直接使用字面常量(如本例中的 0)會減少程式的可維護性。0雖然簡單,但程式中可能出現很多處對指針的檢查,萬一出現筆誤,編譯器不能發現,生成的程式内含邏輯錯誤,很難排除。而使用 NULL代替0,如果出現拼寫錯誤,編譯器就會檢查出來。
[2]
(A)return new string("Invalid argument(s)");,說明答題者根本不知道傳回值的用途,并且他對記憶體洩漏也沒有警惕心。從函數中傳回函數體内配置設定的記憶體是十分危險的做法,他 把釋放記憶體的義務抛給不知情的調用者,絕大多數情況下,調用者不會釋放記憶體,這導緻記憶體洩漏。
(B)return 0;,說明答題者沒有掌握異常機制。調用者有可能忘記檢查傳回值,調用者還可能無法檢查傳回值(見後面的鍊式表達式)。妄想讓傳回值肩負傳回正确值和異常 值的雙重功能,其結果往往是兩種功能都失效。應該以抛出異常來代替傳回值,這樣可以減輕調用者的負擔、使錯誤不會被忽略、增強程式的可維護性。
[3]
(A)忘記儲存原始的strDest值,說明答題者邏輯思維不嚴密。
[4]
(A)循環寫成while (*strDest++=*strSrc++);,同[1](B)。
(B)循環寫成while (*strSrc!='/0') *strDest++=*strSrc++;,說明答題者對邊界條件的檢查不力。循環體結束後,strDest字元串的末尾沒有正确地加上'/0'。
2.傳回strDest的原始值使函數能夠支援鍊式表達式,增加了函數的“附加值”。同樣功能的函數,如果能合理地提高的可用性,自然就更加理想。
鍊式表達式的形式如:
int iLength=strlen(strcpy(strA,strB));
又如:
char * strA=strcpy(new char[10],strB);
傳回strSrc的原始值是錯誤的。其一,源字元串肯定是已知的,傳回它沒有意義。其二,不能支援形如第二例的表達式。其三,為了保護源字元串,形參用const限定strSrc所指的内容,把const char *作為char *傳回,類型不符,編譯報錯。
參考文獻:林銳,《高品質程式設計指南--C/C++語言》,北京,電子工業出版社,2002.6。
2.已知類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(const char *str)
{
if ( str == NULL ) //strlen在參數為NULL時會抛異常才會有這步判斷
{
m_data = new char[1] ;
m_data[0] = ' ' ;
}
else
{
m_data = new char[strlen(str) + 1];
strcpy(m_data,str);
}
}
String::String(const String &other)
{
m_data = new char[strlen(other.m_data) + 1];
strcpy(m_data,other.m_data);
}
String & String::operator =(const String &other)
{
if ( this == &other)
return *this ;
delete []m_data;
m_data = new char[strlen(other.m_data) + 1];
strcpy(m_data,other.m_data);
return *this ;
}
String::~ String(void)
{
delete []m_data ;
}
3.簡答
3.1 頭檔案中的ifndef/define/endif 幹什麼用?
答:防止該頭檔案被重複引用。
3.2#i nclude <filename.h> 和#i nclude “filename.h” 有什麼差別?
答:對于#i nclude <filename.h> ,編譯器從标準庫路徑開始搜尋filename.h
對于#i nclude “filename.h”,編譯器從使用者的工作路徑開始搜尋filename.h
3.3 在C++ 程式中調用被C 編譯器編譯後的函數,為什麼要加extern “C”?
答:C++語言支援函數重載,C 語言不支援函數重載。函數被C++編譯後在庫中的名字與C 語言的不同。假設某個函數的原型為: void foo(int x, int y);
該函數被C 編譯器編譯後在庫中的名字為_foo , 而C++ 編譯器則會産生像_foo_int_int 之類的名字。
C++提供了C 連接配接交換指定符号extern“C”來解決名字比對問題。
3.4 一個類有基類、内部有一個其他類的成員對象,構造函數的執行順序是怎樣的。(Autodesk)
答:先執行基類的(如果基類當中有虛基類,要先執行虛基類的,其他基類則按照聲明派生類時的順序依次執行),再執行成員對象的,最後執行自己的。
3.5 請描述一個你熟悉的設計模式(Autodesk)
3.6 在UML 中,聚合(aggregation)群組合(composition)有什麼差別 Autodesk)
答案:聚合關系更強,類似于pages 和book 的關系;組合關系要弱,類似于books和bookshelf 的關系。
3.7C#和C++除了文法上的差别以外,有什麼不同的地方?(Autodesk,Microsoft)
答案:(C#我隻是了解,不是很精通)
(1) c#有垃圾自動回收機制,程式員不用擔心對象的回收。(2)c#嚴禁使用指針,隻能處理對象。如果希望使用指針,則僅可在unsafe 程式塊中能使用指針。(3)c#隻能單繼承。(4)必須通過類名通路靜态成員。不能像C++中那樣,通過對象通路靜态成員。(5)在子類中覆寫父類的虛函 數時必須用關鍵字override,覆寫父類的方法要用關鍵字new
3.8ADO.net 和ADO 的差別?
答案:實際上除了“能夠讓 應用程式處理存儲于DBMS 中的資料“這一基本相似點外,兩者沒有太多共同之處。但是ADO 使用OLE DB 接口并基于微軟的COM 技術,而ADO.NET 擁有自己的ADO.NET 接口并且基于微軟的.NET 體系架構。衆所周知.NET 體系不同于COM 體系,ADO.NET 接口也就完全不同于ADO和OLE DB 接口,這也就是說ADO.NET 和ADO是兩種資料通路方式。ADO.net 提供對XML 的支援。
3.9 New delete 與malloc free 的差別 ( Autodesk)
答案:用malloc 函數不能初始化對象,new 會調用對象的構造函數。Delete 會調用對象的destructor,而free 不會調用對象的destructor.
3.10 #define DOUBLE(x) x+x (Autodesk)
i = 5*DOUBLE(10); i 是多少?正确的聲明是什麼?
答案:i 為60。正确的聲明是#define DOUBLE(x) (x+x)
3.11 有哪幾種情況隻能用intialization list 而不能用assignment? (Autodesk)
答案:當類中含有const、reference 成員變量;基類的構造函數都需要參數;類中含有其他類的成員對象,而該類的構造函數都需要參數。
3.11 C++是不是類型安全的? (Autodesk)
答案:不是。兩個不同類型的指針之間可以強制轉換。C#是類型安全的。
3.12 main 函數執行以前,還會執行什麼代碼? (Autodesk)
答案:全局對象的構造函數會在main 函數之前執行。
3.13 描述記憶體配置設定方式以及它們的差別。 (Autodesk , Microsoft)
答案:1) 從靜态存儲區域配置設定。記憶體在程式編譯的時候就已經配置設定好,這塊記憶體在程式的整個運作期間都存在。例如全局變量,static 變量。
(2) 在棧上建立。在執行函數時,函數内局部變量的存儲單元都可以在棧上建立,函數執行結束時這些存儲單元自動被釋放。棧記憶體配置設定運算内置于處理器的指令集。
(3) 從堆上配置設定,亦稱動态記憶體配置設定。程式在運作的時候用malloc 或new 申請任意多少的記憶體,程式員自己負責在何時用free 或delete 釋放記憶體。動态記憶體的生存期由我們決定,使用非常靈活,但問題也最多。
3.14 什麼是虛拟存儲器?virtual memory 怎樣映射到physical memory?頁面替換算法有哪些? (Microsoft)
見作業系統 p238 頁。掌握的頁面替換算法NRU,FIFO,第二次機會頁面替換算法,LRU
3.15 有四個同樣的容器,裡面裝滿了粒數相同的藥丸,正常藥丸的品質為m,變質藥丸的品質為m+1,現在已知這四個容器中,有一個裝的全是變質藥丸,用電子秤隻稱一次,找出哪個容器裝的是變質藥丸 (Microsoft)
答案:把四個容器依次編号為1、2、3、4,然後從中分别取出1、2、3、4 粒藥丸,稱這10 粒藥丸的品質,如果品質為10m+1,則說明第一個容器裝的是變質藥丸,如果為10m+2 則說明第二個裝的變質藥丸,依次類推。
3.16 比較一下C++中static_cast 和 dynamic_cast 的差別。 (Autodesk)
dynamic_casts在幫助你浏覽繼承層次上是有限制的。它不能被用于缺乏虛函數的類型上,它被用于安全地沿着類的繼承關系向下進行類型轉換。如你想在沒有繼承關系的類型中進行轉換,你可能想到static_cast
3.17 Struct 和class 的差別 (Autodesk)
答案:struct 中成員變量和成員函數預設通路權限是public,class 是private
3.18 當一個類A 中沒有生命任何成員變量與成員函數,這時sizeof(A)的值是多少,如果不是零,請解釋一下編譯器為什麼沒有讓它為零。(Autodesk)
答案:肯定不是零。我舉個反例,如果是零的話,聲明一個class A[10]對象數組,而每一個對象占用的空間是零,這時就沒辦法區分A[0],A[1]…了
3.19 在8086 彙編下,邏輯位址和實體位址是怎樣轉換的?(Intel)
答案:通用寄存器給出的位址,是段内偏移位址,相應段寄存器位址*10H+通用寄存器内位址,就得到了真正要通路的位址。
3.20 描述一下C++的多态 (microsoft)
答案:C++的多态表現在兩個部分,一個是靜态連編下的函數重載,運算符重載;動态連編下的虛函數、純虛函數(抽象類)
4.寫出BOOL,int,float,指針類型的變量a 與零的比較語句。
答案:
BOOL : if ( !a )
int : if ( a == 0)
float : const EXPRESSION EXP = 0.000001
if ( a < EXP && a >-EXP)
pointer : if ( a != NULL)
5.請說出const 與#define 相比優點
答案:
(1) const 常量有資料類型,而宏常量沒有資料類型。編譯器可以對前者進行類型安全檢查。而對後者隻進行字元替換,沒有類型安全檢查,并且在字元替換可能會産生意料不到的錯誤。
(2) 有些內建化的調試工具可以對const 常量進行調試,但是不能對宏常量進行調試。
6.簡述數組與指針的差別
數組要麼在靜态存儲區被建立(如全局數組),要麼在棧上被建立。指針可以随時指向任意類型的記憶體塊。
(1)修改内容上的差别
char a[] = “hello”;
a[0] = ‘X’;
char *p = “world”; // 注意p 指向常量字元串
p[0] = ‘X’; // 編譯器不能發現該錯誤,運作時錯誤
(2) 用運算符sizeof 可以計算出數組的容量(位元組數)。sizeof(p),p 為指針得到的是一個指針變量的位元組數,而不是p 所指的記憶體容量。C++/C 語言沒有辦法知道指針所指的記憶體容量,除非在申請記憶體時記住它。注意當數組作為函數的參數進行傳遞時,該數組自動退化為同類型的指針。
char a[] = "hello world";
char *p = a;
cout<< sizeof(a) << endl; // 12 位元組
cout<< sizeof(p) << endl; // 4 位元組
計算數組和指針的記憶體容量
void Func(char a[100])
{
cout<< sizeof(a) << endl; // 4 位元組而不是100 位元組
}
7.類成員函數的重載、覆寫和隐藏差別
答案:
成員函數被重載的特征:
(1)相同的範圍(在同一個類中);
(2)函數名字相同;
(3)參數不同;
(4)virtual 關鍵字可有可無。
覆寫是指派生類函數覆寫基類函數,特征是:
(1)不同的範圍(分别位于派生類與基類);
(2)函數名字相同;
(3)參數相同;
(4)基類函數必須有virtual 關鍵字。
“隐藏”是指派生類的函數屏蔽了與其同名的基類函數,規則如下:
(1)如果派生類的函數與基類的函數同名,但是參數不同。此時,不論有無virtual關鍵字,基類的函數将被隐藏(注意别與重載混淆)。
(2)如果派生類的函數與基類的函數同名,并且參數也相同,但是基類函數沒有virtual 關鍵字。此時,基類的函數被隐藏(注意别與覆寫混淆)
8.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
9.如何列印出目前源檔案的檔案名以及源檔案的目前行号?
答案:
cout << __FILE__ ;
cout<<__LINE__ ;
__FILE__和__LINE__是系統預定義宏,這種宏并不是在某個檔案中定義的,而是由編譯器定義的。
10.main 主函數執行完畢後,是否可能會再執行一段代碼,給出說明?
答案:可以,可以用_onexit 注冊一個函數,它會在main 之後執行int fn1(void), fn2(void), fn3(void), fn4 (void);
void main( void )
{
String str("zhanglin");
_onexit( fn1 );
_onexit( fn2 );
_onexit( fn3 );
_onexit( fn4 );
printf( "This is executed first./n" );
}
int fn1()
{
printf( "next./n" );
return 0;
}
int fn2()
{
printf( "executed " );
return 0;
}
int fn3()
{
printf( "is " );
return 0;
}
int fn4()
{
printf( "This " );
return 0;
}
The _onexit function is passed the address of a function (func) to be called when the program terminates normally. Successive calls to _onexit create a register of functions that are executed in LIFO (last-in-first-out) order. The functions passed to _onexit cannot take parameters.
11.如何判斷一段程式是由C 編譯程式還是由C++編譯程式編譯的?
答案:
#ifdef __cplusplus
cout<<"c++";
#else
cout<<"c";
#endif
12.檔案中有一組整數,要求排序後輸出到另一個檔案中
答案:
void Order(vector<int> &data) //起泡排序
{
int count = data.size() ;
int tag = false ;
for ( int i = 0 ; i < count ; i++)
{
for ( int j = 0 ; j < count - i - 1 ; j++)
{
if ( data[j] > data[j+1])
{
tag = true ;
int temp = data[j] ;
data[j] = data[j+1] ;
data[j+1] = temp ;
}
}
if ( !tag )
break ;
}
}
void main( void )
{
vector<int>data;
ifstream in("c://data.txt");
if ( !in)
{
cout<<"file error!";
exit(1);
}
int temp;
while (!in.eof())
{
in>>temp;
data.push_back(temp);
}
in.close();
Order(data);
ofstream out("c://result.txt");
if ( !out)
{
cout<<"file error!";
exit(1);
}
for ( i = 0 ; i < data.size() ; i++)
out<<data[i]<<" ";
out.close();
}
13.排序方法比較 (intel)
排序方法 平均時間 最壞時間 輔助存儲
直接插入排序
起泡排序
快速排序
簡單選擇排序
堆排序
歸并排序
基數排序
14.一個連結清單的結點結構
struct Node
{
int data ;
Node *next ;
};
typedef struct Node Node ;
(1)已知連結清單的頭結點head,寫一個函數把這個連結清單逆序 ( Intel)
Node * ReverseList(Node *head) //連結清單逆序
{
if ( head == NULL || head->next == NULL )
return head;
Node *p1 = head ;
Node *p2 = p1->next ;
Node *p3 = p2->next ;
p1->next = NULL ;
while ( p3 != NULL )
{
p2->next = p1 ;
p1 = p2 ;
p2 = p3 ;
p3 = p3->next ;
}
p2->next = p1 ;
head = p2 ;
return head ;
}
(2)已知兩個連結清單head1 和head2 各自有序,請把它們合并成一個連結清單依然有序。
Node * Merge(Node *head1 , Node *head2)
{
if ( head1 == NULL)
return head2 ;
if ( head2 == NULL)
return head1 ;
Node *head = NULL ;
Node *p1 = NULL;
Node *p2 = NULL;
if ( head1->data < head2->data )
{
head = head1 ;
p1 = head1->next;
p2 = head2 ;
}
else
{
head = head2 ;
p2 = head2->next ;
p1 = head1 ;
}
Node *pcurrent = head ;
while ( p1 != NULL && p2 != NULL)
{
if ( p1->data <= p2->data )
{
pcurrent->next = p1 ;
pcurrent = p1 ;
p1 = p1->next ;
}
else
{
pcurrent->next = p2 ;
pcurrent = p2 ;
p2 = p2->next ;
}
}
if ( p1 != NULL )
pcurrent->next = p1 ;
if ( p2 != NULL )
pcurrent->next = p2 ;
return head ;
}
(2)已知兩個連結清單head1 和head2 各自有序,請把它們合并成一個連結清單依然有序,這次要求用遞歸方法進行。 ( Autodesk)
答案:
Node * MergeRecursive(Node *head1 , Node *head2)
{
if ( head1 == NULL )
return head2 ;
if ( head2 == NULL)
return head1 ;
Node *head = NULL ;
if ( head1->data < head2->data )
{
head = head1 ;
head->next = MergeRecursive(head1->next,head2);
}
else
{
head = head2 ;
head->next = MergeRecursive(head1,head2->next);
}
return head ;
}
15.分析一下這段程式的輸出 (Autodesk)
class B
{
public:
B()
{
cout<<"default constructor"<<endl;
}
~B()
{
cout<<"destructed"<<endl;
}
B(int i):data(i)
{
cout<<"constructed by parameter" << data <<endl;
}
private:
int data;
};
B Play( B b)
{
return b ;
}
int main(int argc, char* argv[])
{
B temp = Play(5);
return 0;
}
請自己執行一下看看。
16.寫一個函數找出一個整數數組中,第二大的數 (microsoft)
答案:
const int MINNUMBER = -32767 ;
int find_sec_max( int data[] , int count) //類似于1 4 4 4這樣的序列将認為1是第二大數
{
int maxnumber = data[0] ;
int sec_max = MINNUMBER ;
for ( int i = 1 ; i < count ; i++)
{
if ( data[i] > maxnumber )
{
sec_max = maxnumber ;
maxnumber = data[i] ;
}
else
{
if ( data[i] > sec_max )
sec_max = data[i] ;
}
}
return sec_max ;
}
17 寫一個在一個字元串中尋找一個子串第一個位置的函數
這個題目的一般算法比較簡單我就不給出了,如果要求高效率的話請參見資料結構中的KMP 算法,不過在筆試時間有限情況下,寫出那個算法還是挺難的。
英文題目
1. Introduce yourself in English
2. What is your great advantage you think of yourself?
3. What is your drawback you think of yourself?
Maybe I will feel very tense if I make a speech in front of a lot of people.
4. How do you feel shanghai?
一. 華為一道面試題-1-n排序
有N個大小不等的自然數(1--N),請将它們由小到大排序。
要求程式算法:時間複雜度為O(n),空間複雜度為O(1)。
網上轉的,一開始也沒有注意到最開始的半句。
算法:N個不等的自然數1~N,排序完成後必然為1~N。是以可以一次周遊,遇到a[i]!=i的就把a[i]和a[a[i]]交換。
void sort(int a[], int n)
{
int i;
int t;
for (i=1; i<n+1; i++)
{
while(a[i]!=i)
{
t = a[a[i]];
a[a[i]] = a[i];//排好一個元素
a[i] = t;
}
}
}
二. 一次周遊 找 連結清單倒數第n個節點
一道面試題目,阿明和晨晨看到并且告訴我答案的。要求通過一次周遊找到連結清單中倒數第n個節點,連結清單可能相當大,可使用輔助空間,但是輔助空間的數目必須固定,不能和n有關。
算法思想:兩根指針,第一根先出發,相距n步後第二根出發。然後同時步進,直到第一根指針達到末尾。
struct iNode {
int value;
iNode * next;
};
iNode * getresult(iNode * head,int n)
{
iNode *pfirst;
iNode *psecond;
pfirst=head;
int counter;
for(counter=0;counter<n;counter++) {
pfirst=pfirst->next;
}
psecond=head;
while(pfirst!=NULL) {
pfirst=pfirst->next;
psecond=psecond->next;
}
return psecond;
}
三. VC++學習筆記
1. 日期轉成字元串:
COleDateTime ww;
ww=COleDateTime::GetCurrentTime();
AfxMessageBox(ww.Format("%Y-%m-%d %H:%M:%S"));
2. 字元串轉成日期:
COleDateTime dt;
dt.ParseDateTime(“2006-08-08 08:08:08”);
3. 資源檔案
資源檔案名:xxx.rc,其中要包含的主要檔案:resource.h和afxres.h
4. vc開發環境沒有自動提示時:
删除 目錄下的ncb檔案 ,再打開一般就ok了
5. 利用_variant_t 取資料庫資料的方法:
_variant_t ibb;
ibb=(_variant_t)rs->GetCollect("inta");
if(ibb.vt!=VT_NULL)
{
m_b=ibb.lVal;
}
6. 平時取記錄集字段值的方法:
(LPCTSTR)(_bstr_t)rs->GetCollect("datea")
7. DoModal()可以傳回兩個結果 IDOK,IDCANCEL,他們都是int型,分别是:1,2。通過EndDialog(IDOK)的方式傳回。
8. 一般将資料庫連接配接方面的資訊放到app中。則AfxGetApp()非常重要,如;
CAdo2App* mapp=(CAdo2App*)AfxGetApp();
Map->conn->Execute(sql,NULL,adCmdText);
9. DECLARE_DYNCREATE(類名),IMPLEMENT_DYNCREATE(類名,基類名) 使得由CObject繼承來的類在程式運作的時候能夠動态的建立。
10. DECLARE_DYNAMIC(類名),IMPLEMENT_DYNAMIC(類名,基類名) 可以在運作時獲得該類的資訊
11. DECLARE_SERIAL(類名),IMPLEMENT_SERIAL(類名,基類名,0)為一個可以串行化的CObject派生類産生必要的C++标題代碼
12. 獲得文檔的方法: CMainFrame * pFrame=(CMainFrame *) AfxGetMainWnd();
CPClientDoc * pDoc =(CPClientDoc *) pFrame->GetActiveDocument();
13. 獲得視圖的方法:CMainFrame * pFrame=(CMainFrame *) AfxGetMainWnd();
myView =(CPClientView*) pFrame->GetActiveView();
14. 如果要引用全局變量或者全局方法,須在目前類中引入:extern 名字;
16. 關聯、聚合(Aggregation)以及組合(Composition)的差別? 涉及到UML中的一些概念: 關聯是表示兩個類的一般性聯系,比如“學生”和“老師”就是一種關聯關系; 聚合表示has-a的關系,是一種相對松散的關系,聚合類不需要對被聚合類負責,如下圖所示,用空的菱形表示聚合關系:
500){this.resized=true;this.style.width=500;}" border=0> 從實作的角度講,聚合可以表示為: class A {...} class B { A* a; .....} 而 組合表示contains-a的關系,關聯性強于聚合:組合類與被組合類有相同的生命周期,組合類要對被組合類負責,采用實心的菱形表示組合關系:
500){this.resized=true;this.style.width=500;}" border=0> 實作的形式是: class A{...} class B{ A a; ...} 參考文章: http://www.cnitblog.com/Lily/archive/2006/02/23/6860.html http://www.vckbase.com/document/viewdoc/?id=422 17.面向對象的三個基本特征,并簡單叙述之? 1. 封裝:将客觀事物抽象成類,每個類對自身的資料和方法實行protection(private, protected,public) 2. 繼承:廣義的繼承有三種實作形式:實作繼承(指使用基類的屬性和方法而無需額外編碼的能力)、可視繼承(子窗體使用父窗體的外觀和實作代碼)、接口繼承 (僅使用屬性和方法,實作滞後到子類實作)。前兩種(類繼承)和後一種(對象組合=>接口繼承以及純虛函數)構成了功能複用的兩種方式。 3. 多态:是将父對象設定成為和一個或更多的他的子對象相等的技術,指派之後,父對象就可以根據目前指派給它的子對象的特性以不同的方式運作。簡單的說,就是一句話: 允許将子類類型的指針指派給父類類型的指針。 18. 重載(overload)和 重寫(overried,有的書也叫做“覆寫”)的差別? 常考的題目。從定義上來說: 重載:是指允許存在多個 同名函數,而這些函數的 參數表不同(或許參數個數不同,或許參數類型不同,或許兩者都不同)。 重寫:是指子類重新定義複類虛函數的方法。 從實作原理上來說: 重載: 編譯器根據函數不同的參數表,對同名函數的名稱做修飾,然後這些同名函數就成了不同的函數(至少對于編譯器來說是這樣的)。如,有兩個同名函數: function func(p:integer):integer;和function func(p:string):integer;。 那麼編譯器做過修飾後的函數名稱可能是這樣的:int_func、str_func。對于這兩個函數的調用,在編譯器間就已經确定了,是靜态的。也就是說,它們的位址在編譯期就綁定了(早綁定),是以,重載和多态無關! 重寫:和多态真正相關 。當子類重新定義了父類的虛函數後,父類指針根據賦給它的不同的子類指針,動态的調用屬于子類的該函數,這樣的函數調用在編譯期間是無法确定的(調用的子類的虛函數的位址無法給出)。是以,這樣的函數位址是在運作期綁定的(晚綁定)。 19. 多态的作用? 主要是兩個:1. 隐藏實作細節,使得代碼能夠子產品化;擴充代碼子產品,實作代碼重用;2. 接口重用:為了類在繼承和派生的時候,保證使用家族中任一類的執行個體的某一屬性時的正确調用。 20. Ado與Ado.net的相同與不同? 除 了“能夠讓應用程式處理存儲于DBMS 中的資料“這一基本相似點外,兩者沒有太多共同之處。但是Ado使用OLE DB 接口并基于微軟的COM 技術,而ADO.NET 擁有自己的ADO.NET 接口并且基于微軟的.NET 體系架構。衆所周知.NET 體系不同于COM 體系,ADO.NET 接口也就完全不同于ADO和OLE DB 接口,這也就是說ADO.NET 和ADO是兩種資料通路方式。ADO.net 提供對XML 的支援。
21. New delete 與malloc free 的聯系與差別?
答案:都是在堆(heap)上進行動态的記憶體操作。用malloc函數需要指定記憶體配置設定的位元組數并且不能初始化對象,new 會自動調用對象的構造函數。delete 會調用對象的destructor,而free 不會調用對象的destructor.
22. #define DOUBLE(x) x+x ,i = 5*DOUBLE(5); i 是多少?
答案:i 為30。
23. 有哪幾種情況隻能用intialization list 而不能用assignment?
答案:當類中含有const、reference 成員變量;基類的構造函數都需要初始化表。
24. C++是不是類型安全的?
答案:不是。兩個不同類型的指針之間可以強制轉換(用reinterpret cast)。C#是類型安全的。
25. main 函數執行以前,還會執行什麼代碼?
答案:全局對象的構造函數會在main 函數之前執行。
26. 描述記憶體配置設定方式以及它們的差別?
1) 從靜态存儲區域配置設定。記憶體在程式編譯的時候就已經配置設定好,這塊記憶體在程式的整個運作期間都存在。例如全局變量,static 變量。
2) 在棧上建立。在執行函數時,函數内局部變量的存儲單元都可以在棧上建立,函數執行結束時這些存儲單元自動被釋放。棧記憶體配置設定運算内置于處理器的指令集。
3) 從堆上配置設定,亦稱動态記憶體配置設定。程式在運作的時候用malloc 或new 申請任意多少的記憶體,程式員自己負責在何時用free 或delete 釋放記憶體。動态記憶體的生存期由程式員決定,使用非常靈活,但問題也最多。
27.struct 和 class 的差別
答案:struct 的成員預設是公有的,而類的成員預設是私有的。struct 和 class 在其他方面是功能相當的。
從 感情上講,大多數的開發者感到類和結構有很大的差别。感覺上結構僅僅象一堆缺乏封裝和功能的開放的記憶體位,而類就象活的并且可靠的社會成員,它有智能服 務,有牢固的封裝屏障和一個良好定義的接口。既然大多數人都這麼認為,那麼隻有在你的類有很少的方法并且有公有資料(這種事情在良好設計的系統中是存在 的!)時,你也許應該使用 struct 關鍵字,否則,你應該使用 class 關鍵字。
28.當一個類A 中沒有生命任何成員變量與成員函數,這時sizeof(A)的值是多少,如果不是零,請解釋一下編譯器為什麼沒有讓它為零。(Autodesk)
答案:肯定不是零。舉個反例,如果是零的話,聲明一個class A[10]對象數組,而每一個對象占用的空間是零,這時就沒辦法區分A[0],A[1]…了。
29. 在8086 彙編下,邏輯位址和實體位址是怎樣轉換的?(Intel)
答案:通用寄存器給出的位址,是段内偏移位址,相應段寄存器位址*10H+通用寄存器内位址,就得到了真正要通路的位址。
30. 比較C++中的4種類型轉換方式?
請參考:http://blog.bioon.com/user1/8688/archives/2006/45399.shtml,重點是static_cast, dynamic_cast和reinterpret_cast的差別和應用。
31.分别寫出BOOL,int,float,指針類型的變量a 與“零”的比較語句。
答案:
BOOL : if ( !a ) or if(a)
int : if ( a == 0)
float : const EXPRESSION EXP = 0.000001
if ( a < EXP && a >-EXP)
pointer : if ( a != NULL) or if(a == NULL)
32.請說出const與#define 相比,有何優點?
答案:1) const 常量有資料類型,而宏常量沒有資料類型。編譯器可以對前者進行類型安全檢查。而對後者隻進行字元替換,沒有類型安全檢查,并且在字元替換可能會産生意料不到的錯誤。
2) 有些內建化的調試工具可以對const 常量進行調試,但是不能對宏常量進行調試。
33.簡述數組與指針的差別?
數組要麼在靜态存儲區被建立(如全局數組),要麼在棧上被建立。指針可以随時指向任意類型的記憶體塊。
(1)修改内容上的差别
char a[] = “hello”;
a[0] = ‘X’;
char *p = “world”; // 注意p 指向常量字元串
p[0] = ‘X’; // 編譯器不能發現該錯誤,運作時錯誤
(2) 用運算符sizeof 可以計算出數組的容量(位元組數)。sizeof(p),p 為指針得到的是一個指針變量的位元組數,而不是p 所指的記憶體容量。C++/C 語言沒有辦法知道指針所指的記憶體容量,除非在申請記憶體時記住它。注意當數組作為函數的參數進行傳遞時,該數組自動退化為同類型的指針。
char a[] = "hello world";
char *p = a;
cout<< sizeof(a) << endl; // 12 位元組
cout<< sizeof(p) << endl; // 4 位元組
計算數組和指針的記憶體容量
void Func(char a[100])
{
cout<< sizeof(a) << endl; // 4 位元組而不是100 位元組
}
34.類成員函數的重載、覆寫和隐藏差別?
答案:
a.成員函數被重載的特征:
(1)相同的範圍(在同一個類中);
(2)函數名字相同;
(3)參數不同;
(4)virtual 關鍵字可有可無。
b.覆寫是指派生類函數覆寫基類函數,特征是:
(1)不同的範圍(分别位于派生類與基類);
(2)函數名字相同;
(3)參數相同;
(4)基類函數必須有virtual 關鍵字。
c.“隐藏”是指派生類的函數屏蔽了與其同名的基類函數,規則如下:
(1)如果派生類的函數與基類的函數同名,但是參數不同。此時,不論有無virtual關鍵字,基類的函數将被隐藏(注意别與重載混淆)。
(2)如果派生類的函數與基類的函數同名,并且參數也相同,但是基類函數沒有virtual 關鍵字。此時,基類的函數被隐藏(注意别與覆寫混淆)
35. 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
36. 如何列印出目前源檔案的檔案名以及源檔案的目前行号?
答案:
cout << __FILE__ ;
cout<<__LINE__ ;
__FILE__和__LINE__是系統預定義宏,這種宏并不是在某個檔案中定義的,而是由編譯器定義的。
37. main 主函數執行完畢後,是否可能會再執行一段代碼,給出說明?
答案:可以,可以用_onexit 注冊一個函數,它會在main 之後執行int fn1(void), fn2(void), fn3(void), fn4 (void);
void main( void )
{
String str("zhanglin");
_onexit( fn1 );
_onexit( fn2 );
_onexit( fn3 );
_onexit( fn4 );
printf( "This is executed first./n" );
}
int fn1()
{
printf( "next./n" );
return 0;
}
int fn2()
{
printf( "executed " );
return 0;
}
int fn3()
{
printf( "is " );
return 0;
}
int fn4()
{
printf( "This " );
return 0;
}
The _onexit function is passed the address of a function (func) to be called when the program terminates normally. Successive calls to _onexit create a register of functions that are executed in LIFO (last-in-first-out) order. The functions passed to _onexit cannot take parameters.
38. 如何判斷一段程式是由C 編譯程式還是由C++編譯程式編譯的?
答案:
#ifdef __cplusplus
cout<<"c++";
#else
cout<<"c";
#endif
39.檔案中有一組整數,要求排序後輸出到另一個檔案中
答案:
#i nclude<iostream>
#i nclude<fstream>
using namespace std;
void Order(vector<int>& data) //bubble sort
{
int count = data.size() ;
int tag = false ; // 設定是否需要繼續冒泡的标志位
for ( int i = 0 ; i < count ; i++)
{
for ( int j = 0 ; j < count - i - 1 ; j++)
{
if ( data[j] > data[j+1])
{
tag = true ;
int temp = data[j] ;
data[j] = data[j+1] ;
data[j+1] = temp ;
}
}
if ( !tag )
break ;
}
}
void main( void )
{
vector<int>data;
ifstream in("c://data.txt");
if ( !in)
{
cout<<"file error!";
exit(1);
}
int temp;
while (!in.eof())
{
in>>temp;
data.push_back(temp);
}
in.close(); //關閉輸入檔案流
Order(data);
ofstream out("c://result.txt");
if ( !out)
{
cout<<"file error!";
exit(1);
}
for ( i = 0 ; i < data.size() ; i++)
out<<data[i]<<" ";
out.close(); //關閉輸出檔案流
}
40. 連結清單題:一個連結清單的結點結構
struct Node
{
int data ;
Node *next ;
};
typedef struct Node Node ;
(1)已知連結清單的頭結點head,寫一個函數把這個連結清單逆序 ( Intel)
Node * ReverseList(Node *head) //連結清單逆序
{
if ( head == NULL || head->next == NULL )
return head;
Node *p1 = head ;
Node *p2 = p1->next ;
Node *p3 = p2->next ;
p1->next = NULL ;
while ( p3 != NULL )
{
p2->next = p1 ;
p1 = p2 ;
p2 = p3 ;
p3 = p3->next ;
}
p2->next = p1 ;
head = p2 ;
return head ;
}
(2)已知兩個連結清單head1 和head2 各自有序,請把它們合并成一個連結清單依然有序。(保留所有結點,即便大小相同)
Node * Merge(Node *head1 , Node *head2)
{
if ( head1 == NULL)
return head2 ;
if ( head2 == NULL)
return head1 ;
Node *head = NULL ;
Node *p1 = NULL;
Node *p2 = NULL;
if ( head1->data < head2->data )
{
head = head1 ;
p1 = head1->next;
p2 = head2 ;
}
else
{
head = head2 ;
p2 = head2->next ;
p1 = head1 ;
}
Node *pcurrent = head ;
while ( p1 != NULL && p2 != NULL)
{
if ( p1->data <= p2->data )
{
pcurrent->next = p1 ;
pcurrent = p1 ;
p1 = p1->next ;
}
else
{
pcurrent->next = p2 ;
pcurrent = p2 ;
p2 = p2->next ;
}
}
if ( p1 != NULL )
pcurrent->next = p1 ;
if ( p2 != NULL )
pcurrent->next = p2 ;
return head ;
}
(3)已知兩個連結清單head1 和head2 各自有序,請把它們合并成一個連結清單依然有序,這次要求用遞歸方法進行。 (Autodesk)
答案:
Node * MergeRecursive(Node *head1 , Node *head2)
{
if ( head1 == NULL )
return head2 ;
if ( head2 == NULL)
return head1 ;
Node *head = NULL ;
if ( head1->data < head2->data )
{
head = head1 ;
head->next = MergeRecursive(head1->next,head2);
}
else
{
head = head2 ;
head->next = MergeRecursive(head1,head2->next);
}
return head ;
}