2014年7月20日 星期日

Public, Protected and Private

原文 : http://stackoverflow.com/questions/860339/difference-between-private-public-and-protected-inheritance


class A 
{
public:
    int x;
protected:
    int y;
private:
    int z;
};

class B : public A
{
    // x is public
    // y is protected
    // z is not accessible from B
};

class C : protected A
{
    // x is protected
    // y is protected
    // z is not accessible from C
};

class D : private A
{
    // x is private
    // y is private
    // z is not accessible from D
};
 
IMPORTANT NOTE: Classes B, C and D all contain the variables x, y and z. It is just question of access.
About usage of protected and private inheritance you could read here.



原文 : http://www.programiz.com/cpp-programming/public-protected-private-inheritance

Summary of Public, Protected and Private Inheritance

Accessibility in Public Inheritance

Accessibilityprivateprotectedpublic
Accessible from own class?yesyesyes
Accessible from dervied class?noyesyes
Accessible outside dervied class?nonoyes

 

Accessibility in Protected Inheritance

Accessibilityprivateprotectedpublic
Accessible from own class?yesyesyes
Accessible from dervied class?noyesyes
Accessible outside dervied class?nonono

 

Accessibility in Private Inheritance

Accessibilityprivateprotectedpublic
Accessible from own class?yesyesyes
Accessible from dervied class?noyesyes
Accessible outside dervied class?nonono

C/C++筆試、面試題目大匯總(二) (轉貼)

原文 : http://goodfish.blog.hexun.com.tw/7601245_d.html


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 ;
}


 
41. 分析一下這段程序的輸出 (Autodesk)
class B
{
public:
B()
{
cout<<"default constructor"<<endl;
}
~B()
{
cout<<"destructed"<<endl;
}
B(int i):data(i)    //B(int) works as a converter ( int -> instance of  B)
{
cout<<"constructed by parameter " << data <<endl;
}
private:
int data;
};


B Play( B b)
{
return b ;
}

(1)                                            results:
int main(int argc, char* argv[])      constructed by parameter 5
{                                     destructed  B(5)形參析構
B t1 = Play(5); B t2 = Play(t1);     destructed  t1形參析構
return 0;               destructed  t2 註意順序!
}                                     destructed  t1

(2)                                   results:
int main(int argc, char* argv[])      constructed by parameter 5
{                                     destructed  B(5)形參析構
B t1 = Play(5); B t2 = Play(10);     constructed by parameter 10
return 0;               destructed  B(10)形參析構
}                                     destructed  t2 註意順序!

                                      destructed  t1
 


42. 寫一個函數找出一個整數數組中,第二大的數 (microsoft)答案:
const int MINNUMBER = -32767 ;
int find_sec_max( int data[] , int count)
{
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 ;
}

 


43. 寫一個在一個字符串(n)中尋找一個子串(m)第一個位置的函數。
KMP算法效率最好,時間復雜度是O(n+m)。
 


44. 多重繼承的內存分配問題:
   比如有class A : public class B, public class C {}
   那麽A的內存結構大致是怎麽樣的?


這個是compiler-dependent的, 不同的實現其細節可能不同。
如果不考慮有虛函數、虛繼承的話就相當簡單;否則的話,相當復雜。
可以參考《深入探索C++對象模型》,或者:
http://blog.csdn.net/wfwd/archive/2006/05/30/763797.aspx

 


45. 如何判斷一個單鏈表是有環的?(註意不能用標誌位,最多只能用兩個額外指針)
   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;
}

嵌入式系統入學測驗‏ (轉貼)

網路上查到的中文版本  作者不詳
 
 
 
C語言測試是徵選嵌入式系統程式員過程中必須而且有效的方法。
這些年我既參加也組織了許多這種測試,
在這過程中我意識到這些測試能為面試者和被面試者提供許多有用訊息,
此外,撇開面試的壓力不談,這種測試也是相當有趣的。
 
 
 
從被面試者的角度來講,你能了解許多關於出題者或監考者的情況。
這個測試只是出題者為顯示其對ANSI標準細節的知識而不是技術技巧而設計嗎 ?
這個愚蠢的問題嗎 ?如要你答出某個字符的ASCII值。
這些問題著重考察你的系統調用(invoke)和記憶體分發策略方面的能力嗎 ?
這反映出出題者也許花時間在微處理機上而不在嵌入式系統上。
 
 
如果上述任何問題的答案是“是”的話,
那麼我知道我得認真考慮我是否應該去做這份工作。
 
 
 
從面試者的角度來講,一個測試也許能從多方面揭示應試者的素質︰
最基本的,你能了解應試者C語言的水準。
不管怎麼樣,看一下這人如何回答他不會的問題也是滿有趣。
應試者是以好的直覺做出明智的選擇,還是只是瞎蒙呢 ?
當應試者在某個問題上卡住時是找藉口呢,還是表現出對問題的真正的好奇心,
把這看成學習的機會呢 ?我發現這些訊息與他們的測試成績一樣有用。
 
 
 
有了這些想法,我決定出一些真正針對嵌入式系統的考題,
希望這些令人頭痛的考題能給正在謀職的人一點幫住。
這些問題都是我這些年實際碰到的。其中有些題很難,但它們應該都能給你一點啟發。
 
 
 
 
這個測試適用於不同水準的應試者,大多數初級水準的應試者的成績會很差,
經驗豐富的程式員應該有很好的成績。
為了讓你能自己決定某些問題的偏好,每個問題沒有分發分數,
如果選擇這些考題為你所用,請自行按你的意思分發分數。
 
==============================================
 
預處理器 (Preprocessor)
 
1.用預處理指令#define 聲明一個常數,用以表示1年中有多少秒 (忽略閏年問題)
 
#define SECONDS_PER_YEAR (60 * 60 * 24 * 365)UL
 
我在這想看到幾件事情︰
 
a #define 語法的基本知識 (例如︰不能以分號結束,括號的使用,等等)
 
b 懂得預處理器將為你計算常數表達式的值,因此,
  直接寫出你是如何計算一年中有多少秒會比直接計算出實際的值更清晰。
 
c 意識到這個表達式將使一個16位元的機器產生整數型溢位 - 因此要用到長整型符號L,
  告訴編譯器這個常數是的長整型數。
 
d 如果你在你的表達式中用到UL (表示無符號長整型) ,那麼你有了一個好的起點。記住,第一印象很重要。
 
 
 
 
 
 
 
 
 
 
2.寫一個“標準”巨集MIN ,這個巨集輸入兩個參數並返回較小的一個。
 
#define MIN(A,B)  ( (A)  <= (B) ? (A) : (B))
 
這個測試是為下面的目的而設的︰
 
a 標識#define在巨集中應用的基本知識。這是很重要的,因為在行內(inline)運算子變為標準C的一部分之前,巨集是方便產生行內程式碼的唯一方法,對於嵌入式系統來說,為了能達到要求的性能,行內程式碼經常是必須的方法。
b 三元運算子的知識。這個運算子存在C語言中的原因是它使得編譯器能產生
  比if-then-else更優化的程式碼,了解這個用法是很重要的。
c 懂得在巨集中小心地把參數用括號括起來
 
  我也用這個問題開始討論巨集的副作用,例如︰當你寫下面的程式碼時會發生什麼事 ?
 
  least = MIN(*p++, b);
 
 
 
 
 
 
3.預處理器標識#error的目的是什麼 ?
 
如果你不知道答案,請看參考文獻1。
這問題對區分一個正常的伙計和一個書呆子是很有用的。
只有書呆子才會讀C語言課本的附錄去找出象這種問題的答案。
當然如果你不是在找一個書呆子,那麼應試者最好希望自己不要知道答案。
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
==============================================
 
無窮迴圈 (Infinite loops)
 
4.嵌入式系統中經常要用到無窮迴圈,你怎麼樣用C編寫無窮迴圈呢 ?
 
這個問題用幾個解決方案。我首選的方案是︰
 
while(1)
{
   ...
}
 
一些程式員更喜歡如下方案︰
 
for(;;)
{
   ...
}
 
這個實作方式讓我為難,因為這個語法沒有確切表達到底怎麼回事。
如果一個應試者給出這個作為方案,
我將用這個作為一個機會去探究他們這樣做的基本原理。
如果他們的基本答案是︰“我被教著這樣做,但從沒有想到過為什麼。”
這會給我留下一個壞印象。
 
 
第三個方案是用 goto︰
 
Loop:
   ...
goto Loop;
 
應試者如給出上面的方案,這說明或者他是一個組合語言程式員 (這也許是好事)
 或者他是一個想進入新領域的BASIC/FORTRAN程式員。
 
 
 
 
 
 
==============================================
 
數據宣告 (Data declarations)
 
5.用變數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
 
人們經常聲稱這裡有幾個問題是那種要翻一下書才能回答的問題,我同意這種說法。
當我寫這篇文章時,為了確定語法的正確性,我的確查了一下書。
但是當我被面試的時候,我期望被問到這個問題 (或者相近的問題)。
因為在被面試的這段時間裡,我確定我知道這個問題的答案。
應試者如果不知道所有的答案 (或至少大部分答案),那麼也就沒有為這次面試做準備,
如果該面試者沒有為這次面試做準備,那麼他又能為什麼出準備呢 ?
 
==============================================
 
 
 
 
 
 
 
 
 
Static
 
6.關鍵字static的作用是什麼 ?
 
這個簡單的問題很少有人能回答完全。在C語言中,關鍵字static有三個明顯的作用︰
a 在函數本體內(in Function Block),一個被宣告為靜態的變數,
  在這一函數被呼叫過程中維持其值不變。
b 在一個Block(ie. {...} )內 (但在函數體外),
  一個被宣告為靜態的變數可以被Block內所有的函數存取,
  但不能被Block外的其它函數存取。它是一個本地的全局變量。
c 在Block內,一個被聲明為靜態的函數,只可被這一Block內的其它函數呼叫。
  也就是這個函數被限制在宣告它的Block的本地範圍內使用。
 
大多數應試者能正確回答第一部分,一部分能正確回答第二部分,
 同是很少的人能懂得第三部分。這是一個應試者的嚴重的缺點,
 因為他顯然不懂得本地化資料和程式碼範圍的好處和重要性。
 
 
 
==============================================
 
Const(常量)
 
7.關鍵字const有什麼含意 ?
 
我只要一聽到被面試者說︰“const意味著常數”,我就知道我正在和一個業餘者打交道。
去年Dan Saks已經在他的文章裡完全概括了const的所有用法,
因此ESP(譯者︰Embedded Systems Programming)的每一位讀者應該非常熟悉const能做什
麼和不能做什麼。如果你從沒有讀到那篇文章,只要能說出const意味著“只讀”就可以了
。儘管這個答案不是完全的答案,但我接受它作為一個正確的答案。
(如果你想知道更詳細的答案,仔細讀一下Saks的文章吧。)
 
 
 
如果應試者能正確回答這個問題,我將問他一個附加的問題︰
下面的聲明都是什麼意思 ?
 
const int a;
int const a;
const int *a;
int * const a;
int const * a const;
 
/******/
前兩個的作用是一樣,a是一個常數型整數。
 
第三個意味著a是一個指向常數型整數的指標
(也就是,整型數是不可修改的,但指標可以)。
 
第四個意思a是一個指向整數的常數型指標
(也就是說,指標指向的整數是可以修改的,但指標是不可修改的)。
 
最後一個意味著a是一個指向常數型整數的常數型指標
(也就是說,指標指向的整數是不可修改的,同時指標也是不可修改的)。
 
如果應試者能正確回答這些問題,那麼他就給我留下了一個好印象。
順帶提一句,也許你可能會問,即使不用關鍵字 const,也還是能很容易寫出功能正確
的程式,那麼我為什麼還要如此看重關鍵字const呢 ?
 
我有以下的幾個理由︰
 
a 關鍵字const的作用是給讀你程式碼的人傳達非常有用的訊息,
  實際上,宣告一個參數為常量是為了告訴了程式員這個參數的應用目的。
  如果你曾花很多時間清理其它人留下的垃圾(記憶體回收),
  你就會很快學會感謝這點多餘的訊息。
  (當然,懂得用const的程式員很少會留下的垃圾讓別人來清理的)
b 透過給優化器一些附加的訊息,使用關鍵字const也許能產生更緊湊的程式碼。
c 合理地使用關鍵字const可以使編譯器很自然地保護那些不希望被改變的參數,
  防止其被無意的程式碼修改。簡而言之,這樣可以減少bug的出現。
 
==============================================
 
 
 
 
 
 
Volatile(易變的)
 
8. 關鍵字volatile有什麼含意? 並給出三個不同的例子。
 
一個定義為volatile的變量是說這變量可能會被意想不到地改變,
  這樣,編譯器就不會去假設這個變量的值了。
  精確地說就是,優化器在用到這個變量時必須每次都小心地重新讀取這個變量的值,
  而不是使用保存在暫存器裡的備份。
 
 
 下面是volatile變量的幾個例子︰
 
a 並行設備的硬體暫存器 (如︰狀態暫存器)
b 一個中斷服務次程序中會訪問到的非自動變數(Non-automatic variables)
c 多執行緒應用中被幾個任務(task)共享的變數
 
回答不出這個問題的人是不會被僱佣的。  [1;30m(os:還好我不是讓這個人面試.....) [m
我認為這是區分C程式員和嵌入式系統程式員的最基本的問題。
搞嵌入式的家伙們經常同硬體、中斷、RTOS等等打交道,
所有這些都要求用到volatile變量。不懂得volatile的內容將會帶來災難。
 
假設被面試者正確地回答了這是問題 (嗯,懷疑是否會是這樣),
我將稍微深究一下,看一下這家伙是不是直正懂得volatile完全的重要性。
 
Q 一個參數可以同時是const也是volatile嗎? 解釋為什麼。
Q 一個指標可以是volatile 嗎? 解釋為什麼。
Q 下面的函數有什麼錯誤︰
int square(volatile int *ptr)
{
   return *ptr * *ptr;
}
 
下面是答案︰
 
A 是的。舉的例子是"只讀的狀態暫存器"。它是volatile因為它可能被意想不到地改變。它是const因為程式不應該試圖去修改它。
A 是的。儘管這並不很常見。舉的例子是當一個執行中的次程序修該一個指向一個buffer
  的指標時。
A 這段程式碼有點變態。這段程式碼的目的是用來返指標*ptr指向值的平方,但是,
  由於*ptr指向一個volatile型參數,編譯器將產生類似下面的程式碼︰
 
int square(volatile int *ptr)
{
   int a,b;
   a = *ptr;
   b = *ptr;
   return a * b;
}
 
由於*ptr的值可能被意想不到地該變,因此a和b可能是不同的。
 結果,這段程式碼可能返回不是你所期望的平方值!正確的程式碼如下︰
 
long square(volatile int *ptr)
{
   int a;
   a = *ptr;
   return a * a;
}
 
 
 
 
 
==============================================
 
位元操作 (Bit Manipulation)
 
9.嵌入式系統總是要用戶對變量或暫存器進行位操作。
 給定一個整型變量a,寫兩段程式碼,第一個設置a的bit 3,
 第二個清除a 的bit 3。在以上兩個操作中,要保持其它位不變。
 
對這個問題有三種基本的回應
- 不知道如何下手。該被面者從沒做過任何嵌入式系統的工作。
- 用bit fields。Bit fields是被扔到C語言死角的東西,
  它保證你的程式碼在不同編譯器之間是不可移植的,
  同時也保證了的你的程式碼是不可重新使用的。
  我最近不幸看到 Infineon為其較複雜的通信晶片寫的驅動程式,
  它用到了bit fields因此完全對我無用,
  因為我的編譯器採用其它的模式來實現bit fields。
  從道德講︰永遠不要讓一個非嵌入式的家伙沾到實際硬體的邊。
- 用 #defines 和 bit masks 操作。這是一個有極高可移植性的方法,
  是應該被用到的方法。最佳的解決方案如下︰
 
#define BIT3 (0x1 << 3)
static int a;
 
void set_bit3(void)
{
   a |= BIT3;
}
 
void clear_bit3(void)
{
   a &= ~BIT3;
}
 
一些人喜歡為設定和清除值而定義一個掩碼同時定義一些說明常數,
 這也是可以接受的。我希望看到幾個要點︰說明常數、|= 和 &=~ 操作。
 
==============================================
 
存取固定的記憶體位置 (Accessing fixed memory locations)
 
10.嵌入式系統經常具有要求程式員去存取某特定的記憶體位置的特點。
 在某工程中,要求設定一個絕對位址為0x67a9的整數型變數的值為0xaa55。
  編譯器是一個純粹的ANSI編譯器。寫程式碼去完成這一任務。
 
這一問題測試你是否知道為了存取一絕對位址把一個整數型強製轉型 (typecast)
 為一指標是合法的。這一問題的實作模式隨著個人風格不同而不同。
 典型的類似程式碼如下︰
 
int *ptr;
ptr = (int *)0x67a9;
*ptr = 0xaa55;
 
A more obscure approach is:
一個較艱澀的方法是︰
 
*(int * const)(0x67a9) = 0xaa55;
 
即使你的品味更接近第二種方案,但我建議你在面試時使用第一種方案。
 
 
 
==============================================
 
中斷 (Interrupts)
 
11.中斷是嵌入式系統中重要的組成部分,這導致了很多編譯開發商提供一種擴展 -
  讓標準C支持中斷。具代表的事實是,產生了一個新的關鍵字 __interrupt。
 下面的程式碼就使用了__interrupt關鍵字去定義了一個中斷服務次程序(ISR),
  請評論一下這段程式碼的。
 
__interrupt double compute_area(double radius)
{
   double area = PI * radius * radius;
   printf("\nArea = %f", area);
   return area;
}
 
這個函數有太多的錯誤了,以至讓人不知從何說起了︰
~ ISR 不能返回一個值。如果你不懂這個,那麼你不會被雇用的。  [1;30m(os : 囧) [m
~ ISR 不能傳遞參數。如果你沒有看到這一點,你被雇用的機會等同第一項。
~ 在許多的處理器/編譯器中,浮點一般都是不可重入的。
  有些處理器/編譯器需要讓多餘的暫存器入棧(PUSH入堆疊),
  有些處理器/編譯器就是不允許在ISR中做浮點運算。
  此外,ISR應該是短而有效率的,在ISR中做浮點運算是不明智的。
~ 與第三點一脈相承,printf()經常有重入和性能上的問題。
  如果你丟掉了第三和第四點,我不會太為難你的。
  但如果你能得到後兩點,那麼你的被雇用前景越來越光明了。
 
==============================================
 
 
 
 
 
程式碼例子 (Code examples)
 
12.下面的程式碼輸出是什麼,為什麼 ?
 
void foo(void)
{
   unsigned int a = 6;
   int b = -20;
   (a+b > 6) ? puts("> 6") : puts("<= 6");
}
 
這個問題測試你是否懂得C語言中的整數自動轉型原則,
我發現極少有開發者懂得這些東西。不管如何,這unsigned int的答案是輸出是 "> 6"。
原因是當表達式中存在有符號類型和無符號類型時所有的操作數都自動轉換為無符號類型
(unsigned)。因此-20變成了一個非常大的正整數,所以該表達式計算出的結果大于6。
這一點對於應當頻繁用到無符號數據類型的嵌入式系統來說是非常重要的。
如果你答錯了這個問題,你也就到了得不到這份工作的邊緣。
 
 
 
 
13.評價下面的程式碼片斷︰
 
unsigned int zero = 0;
unsigned int compzero = 0xFFFF;
/*1's complement of zero */
 
對于一個int型不是16位的處理器為說,上面的程式碼是不正確的。應編寫如下︰
 
unsigned int compzero = ~0;
 
這一問題真正能揭露出應試者是否懂得處理器字長的重要性。在我的經驗裡,
好的嵌入式程式員非常準確地明白硬體的細節和它的限制,
然而PC機程式往往把硬體作為一個無法避免的煩惱。
 
到了這個階段,應試者可能完全垂頭喪氣了或者信心滿滿志在必得。
如果顯然應試者不是很好,那麼這個測試就在這裡結束了。
但如果顯然應試者做得不錯,那麼我就扔出下面的追加問題,這些問題是比較難的,
我想僅僅非常優秀的應試者能做得不錯。提出這些問題,
我希望更多看到應試者應付問題的方法,而不是答案。
不管如何,你就當是這個娛樂吧…
 
 
 
==============================================
 
 
 
 
動態記憶體分發 (Dynamic memory allocation):
 
14.儘管不像非嵌入式計算機那麼常見,
  嵌入式系統還是有從堆積(heap)中動態分發內存的過程的。
  那麼嵌入式系統中,動態分發記憶體可能發生的問題是什麼 ?
 
 
  這裡,我期望應試者能提到記憶體碎片,碎片收集的問題,變量的生命週期等等。
  這個主題已經在ESP雜誌中被廣泛地討論過了
  (主要是 P.J. Plauger,他的解釋遠遠超過我這裡能提到的任何解釋)。
  我拿出這麼一個小題目給應試者︰
 
下面的程式碼片段的輸出是什麼,為什麼 ?
 
char *ptr;
if ((ptr = (char *)malloc(0)) == NULL)
else
   puts("Got a null pointer");
puts("Got a valid pointer");
 
這是一個有趣的問題。最近在我的一個同事不經意把0值傳給了函數malloc,
得到了一個合法的指標之後,我才想到這個問題。這就是上面的程式碼,
該程式碼的輸出是"Got a valid pointer"。我用這個問題來開始討論,
看看被面試者是否想到怎樣做才是正確的。
得到正確的答案固然重要,但解決問題的方法和你做決定的基本原理更重要些。
 
 
 
 
==============================================
 
Typedef:
 
15.Typedef 在C語言中頻繁用以宣告一個已經存在的資料型態的同義字。
也可以用預處理器做類似的事。例如,思考一下下面的例子︰
 
#define dPS struct s *
typedef struct s * tPS;
 
以上兩種情況的意圖都是要定義dPS 和 tPS 作為一個指向結構s指標。哪種方法更好呢 ? (如果有的話)為什麼 ? 這是一個非常微妙的問題,任何人答對這個問題 (正當的原因) 是應當被恭喜的。答案是︰typedef更好。思考下面的例子︰
 
dPS p1, p2;
tPS p3, p4;
 
第一個擴展為
 
struct s * p1, p2;
 
上面的程式碼定義p1為一個指向結構的指標,p2為一個實際的結構,
這也許不是你想要的。
 
第二個例子正確地定義了p3 和p4 兩個指標。
 
 
 
==============================================
 
艱澀的語法:
 
16.C語言允許一些令人震驚的結構,下面的結構是合法的嗎,如果是,它做些什麼 ?
 
int a = 5, b = 7, c;
c = a+++b;
 
這個問題將做為這個測驗的一個愉快的結尾。不管你相不相信,
上面的例子是完全合法的。問題是編譯器如何處理它 ?
 水準不高的編譯作者實際上會爭論這個問題,根據最處理原則,
編譯器應當能處理儘可能所有合法的用法。因此,上面的程式碼被處理成︰
 
c = a++ + b;
 
因此,這段程式碼持行後
a = 6, b = 7, c = 12
如果你知道答案,或猜出正確答案,做得好。如果你不知道答案,
我也不把這個當作問題。
我發現這個問題的最大好處是這是一個關於程式碼編寫風格,
程式碼的可讀性,程式碼的可修改性的好的話題。
 
==============================================
 
好了,伙計們,你現下已經做完所有的測試了。這就是我出的C語言測試題,
我懷著愉快的心情寫完它,希望你以同樣的心情讀完它。如果是認為這是一個好的測試,
那麼盡量都用到你的謀職的過程中去吧。天知道也許過個一兩年,我就不做現下的工作,也需要找一個。
 
Nigel Jones 是一個顧問,現下住在Maryland。他很高興能收到讀者的來信,
他的email位址是: NAJones@compuserve.com 。
 
==============================================
 

C/C++筆試、面試題目大匯總(一) (轉貼)

原文 : http://goodfish.blog.hexun.com.tw/7601245_d.html

1.求下面函數的返回值(微軟)
int func(x)
{
    int countx = 0;
    while(x)
    {
          countx ++;
          x = x&(x-1);
     }
    return countx;

假定x = 9999。 答案:8
思路:將x轉化為2進制,看含有的1的個數。


2. 什麽是“引用”?申明和使用“引用”要註意哪些問題?
答:引用就是某個目標變量的“別名”(alias),對應用的操作與對變量直接操作效果完全相同。申明一個引用的時候,切記要對其進行初始化。引用聲明完畢後,相當於目標變量名有兩個名稱,即該目標原名稱和引用名,不能再把該引用名作為其他變量名的別名。聲明一個引用,不是新定義了一個變量,它只表示該引用名是目標變量名的一個別名,它本身不是一種數據類型,因此引用本身不占存儲單元,系統也不給引用分配存儲單元。不能建立數組的引用。


3. 將“引用”作為函數參數有哪些特點?
(1)傳遞引用給函數與傳遞指針的效果是一樣的。這時,被調函數的形參就成為原來主調函數中的實參變量或對象的一個別名來使用,所以在被調函數中對形參變量的操作就是對其相應的目標對象(在主調函數中)的操作。
(2)使用引用傳遞函數的參數,在內存中並沒有產生實參的副本,它是直接對實參操作;而使用一般變量傳遞函數的參數,當發生函數調用時,需要給形參分配存儲單元,形參變量是實參變量的副本;如果傳遞的是對象,還將調用拷貝構造函數。因此,當參數傳遞的數據較大時,用引用比用一般變量傳遞參數的效率和所占空間都好。
(3)使用指針作為函數的參數雖然也能達到與使用引用的效果,但是,在被調函數中同樣要給形參分配存儲單元,且需要重復使用"*指針變量名"的形式進行運算,這很容易產生錯誤且程序的閱讀性較差;另一方面,在主調函數的調用點處,必須用變量的地址作為實參。而引用更容易使用,更清晰。

4. 在什麽時候需要使用“常引用”? 
如果既要利用引用提高程序的效率,又要保護傳遞給函數的數據不在函數中被改變,就應使用常引用。常引用聲明方式:const 類型標識符 &引用名=目標變量名;
例1
int a ;
const int &ra=a;
ra=1; //錯誤
a=1; //正確
例2
string foo( );
void bar(string & s);
那麽下面的表達式將是非法的:
bar(foo( ));
bar("hello world");
原因在於foo( )和"hello world"串都會產生一個臨時對象,而在C++中,這些臨時對象都是const類型的。因此上面的表達式就是試圖將一個const類型的對象轉換為非const類型,這是非法的。
引用型參數應該在能被定義為const的情況下,盡量定義為const 。

5. 將“引用”作為函數返回值類型的格式、好處和需要遵守的規則?
格式:類型標識符 &函數名(形參列表及類型說明){ //函數體 }
好處:在內存中不產生被返回值的副本;(註意:正是因為這點原因,所以返回一個局部變量的引用是不可取的。因為隨著該局部變量生存期的結束,相應的引用也會失效,產生runtime error!
註意事項:
(1)不能返回局部變量的引用。這條可以參照Effective C++[1]的Item 31。主要原因是局部變量會在函數返回後被銷毀,因此被返回的引用就成為了"無所指"的引用,程序會進入未知狀態。

(2)不能返回函數內部new分配的內存的引用。這 條可以參照Effective C++[1]的Item 31。雖然不存在局部變量的被動銷毀問題,可對於這種情況(返回函數內部new分配內存的引用),又面臨其它尷尬局面。例如,被函數返回的引用只是作為一 個臨時變量出現,而沒有被賦予一個實際的變量,那麽這個引用所指向的空間(由new分配)就無法釋放,造成memory leak

(3)可以返回類成員的引用,但最好是const。 這條原則可以參照Effective C++[1]的Item 30。主要原因是當對象的屬性是與某種業務規則(business rule)相關聯的時候,其賦值常常與某些其它屬性或者對象的狀態有關,因此有必要將賦值操作封裝在一個業務規則當中。如果其它對象可以獲得該屬性的非常 量引用(或指針),那麽對該屬性的單純賦值就會破壞業務規則的完整性。

(4)流操作符重載返回值申明為“引用”的作用:
流操作符<<和>>,這兩個操作符常常希望被連續使用,例如:cout << "hello" << endl; 因此這兩個操作符的返回值應該是一個仍然支持這兩個操作符的流引用。可選的其它方案包括:返回一個流對象和返回一個流對象指針。但是對於返回一個流對象,程序必須重新(拷貝)構造一個新的流對象,也就是說,連續的兩個<<操作符實際上是針對不同對象的!這無法讓人接受。對於返回一個流指針則不能連續使用<<操作符。 因此,返回一個流對象引用是惟一選擇。這個唯一選擇很關鍵,它說明了引用的重要性以及無可替代性,也許這就是C++語言中引入引用這個概念的原因吧。賦值 操作符=。這個操作符象流操作符一樣,是可以連續使用的,例如:x = j = 10;或者(x=10)=100;賦值操作符的返回值必須是一個左值,以便可以被繼續賦值。因此引用成了這個操作符的惟一返回值選擇。
例3
#i nclude <iostream.h>
int &put(int n);
int vals[10];
int error=-1;
void main()
{
put(0)=10; //以put(0)函數值作為左值,等價於vals[0]=10;
put(9)=20; //以put(9)函數值作為左值,等價於vals[9]=20;
cout<<vals[0];
cout<<vals[9];
}
int &put(int n)
{
if (n>=0 && n<=9 ) return vals[n];
else { cout<<"subscript error"; return error; }
}

(5)在另外的一些操作符中,卻千萬不能返回引用:+-*/ 四則運算符。它們不能返回引用,Effective C++[1]的Item23詳細的討論了這個問題。主要原因是這四個操作符沒有side effect,因此,它們必須構造一個對象作為返回值,可選的方案包括:返回一個對象、返回一個局部變量的引用,返回一個new分配的對象的引用、返回一 個靜態對象引用。根據前面提到的引用作為返回值的三個規則,第2、3兩個方案都被否決了。靜態對象的引用又因為((a+b) == (c+d))會永遠為true而導致錯誤。所以可選的只剩下返回一個對象了。

6. “引用”與多態的關系?
引用是除指針外另一個可以產生多態效果的手段。這意味著,一個基類的引用可以指向它的派生類實例。
例4
Class A; Class B : Class A{...};  B b; A& ref = b;

7. “引用”與指針的區別是什麽?
指針通過某個指針變量指向一個對象後,對它所指向的變量間接操作。程序中使用指針,程序的可讀性差;而引用本身就是目標變量的別名,對引用的操作就是對目標變量的操作。此外,就是上面提到的對函數傳ref和pointer的區別。

8. 什麽時候需要“引用”?
流操作符<<和>>、賦值操作符=的返回值、拷貝構造函數的參數、賦值操作符=的參數、其它情況都推薦使用引用。


9. 結構與聯合有和區別?
(1) 結構和聯合都是由多個不同的數據類型成員組成, 但在任何同一時刻, 聯合中只存放了一個被選中的成員(所有成員共用一塊地址空間), 而結構的所有成員都存在(不同成員的存放地址不同)。


 (2) 對於聯合的不同成員賦值, 將會對其它成員重寫,  原來成員的值就不存在了, 而對於結構的不同成員賦值是互不影響的。


10. 下面關於“聯合”的題目的輸出?
a)
#i nclude <stdio.h>
union
{
int i;
char x[2];
}a;

void main()
{
a.x[0] = 10;
a.x[1] = 1;
printf("%d",a.i);
}
答案:266 (低位低地址,高位高地址,內存占用情況是Ox010A)
b)
     main()
     {
          union{                   /*定義一個聯合*/
               int i;
               struct{             /*在聯合中定義一個結構*/
                    char first;
                    char second;
               }half;
          }number;
          number.i=0x4241;         /*聯合成員賦值*/
          printf("%c%c\n", number.half.first, mumber.half.second);
          number.half.first='a';   /*聯合中結構成員賦值*/
          number.half.second='b';
          printf("%x\n", number.i);
          getch();
     }
答案: AB   (0x41對應'A',是低位;Ox42對應'B',是高位)
       6261 (number.i和number.half共用一塊地址空間)

11. 已知strcpy的函數原型:char *strcpy(char *strDest, const char *strSrc)其中strDest 是目的字符串,strSrc 是源字符串。不調用C++/C 的字符串庫函數,請編寫函數 strcpy。

答案:
char *strcpy(char *strDest, const char *strSrc)
{
if ( strDest == NULL || strSrc == NULL)
return NULL ;
if ( strDest == strSrc)
return strDest ;
char *tempptr = strDest ;
while( (*strDest++ = *strSrc++) != ‘\0’)
;
return tempptr ;
}



12. 已知String類定義如下:
class String
{
public:
String(const char *str = NULL); // 通用構造函數
String(const String &another); // 拷貝構造函數
~ String(); // 析構函數
String & operater =(const String &rhs); // 賦值函數
private:
char *m_data; // 用於保存字符串
};

嘗試寫出類的成員函數實現。
答案:
String::String(const char *str)
{
   if ( str == NULL ) //strlen在參數為NULL時會拋異常才會有這步判斷
     {
       m_data = new char[1] ;
       m_data[0] = '\0' ;
     }
   else
    {
       m_data = new char[strlen(str) + 1];
       strcpy(m_data,str);
    }


String::String(const String &another)
{
    m_data = new char[strlen(another.m_data) + 1];
    strcpy(m_data,other.m_data);
}


String& String::operator =(const String &rhs)
{
    if ( this == &rhs)
        return *this ;
    delete []m_data; //刪除原來的數據,新開一塊內存
    m_data = new char[strlen(rhs.m_data) + 1];
    strcpy(m_data,rhs.m_data);
    return *this ;
}


String::~String()
{
    delete []m_data ;
}



13. .h頭文件中的ifndef/define/endif 的作用?
答:防止該頭文件被重復引用。


14. #i nclude<file.h> 與 #i nclude "file.h"的區別?
答:前者是從Standard Library的路徑尋找和引用file.h,而後者是從當前工作路徑搜尋並引用file.h。


15.在C++ 程序中調用被C 編譯器編譯後的函數,為什麽要加extern “C”?
首先,作為extern是C/C++語言中表明函數和全局變量作用範圍(可見性)的關鍵字,該關鍵字告訴編譯器,其聲明的函數和變量可以在本模塊或其它模塊中使用
通常,在模塊的頭文件中對本模塊提供給其它模塊引用的函數和全局變量以關鍵字extern聲明。例如,如果模塊B欲引用該模塊A中定義的全局變量和函數時只需包含模塊A的頭文件即可。這樣,模塊B中調用模塊A中的函數時,在編譯階段,模塊B雖然找不到該函數,但是並不會報錯;它會在連接階段中從模塊A編譯生成的目標代碼中找到此函數
extern "C"是連接申明(linkage declaration),被extern "C"修飾的變量和函數是按照C語言方式編譯和連接的,來看看C++中對類似C的函數是怎樣編譯的:

作為一種面向對象的語言,C++支持函數重載,而過程式語言C則不支持。函數被C++編譯後在符號庫中的名字與C語言的不同。例如,假設某個函數的原型為:

void foo( int x, int y );
  
該函數被C編譯器編譯後在符號庫中的名字為_foo,而C++編譯器則會產生像_foo_int_int之類的名字(不同的編譯器可能生成的名字不同,但是都采用了相同的機制,生成的新名字稱為“mangled name”)。

_foo_int_int 這樣的名字包含了函數名、函數參數數量及類型信息,C++就是靠這種機制來實現函數重載的。例如,在C++中,函數void foo( int x, int y )與void foo( int x, float y )編譯生成的符號是不相同的,後者為_foo_int_float。
同樣地,C++中的變量除支持局部變量外,還支持類成員變量和全局變量。用戶所編寫程序 的類成員變量可能與全局變量同名,我們以"."來區分。而本質上,編譯器在進行編譯時,與函數的處理相似,也為類中的變量取了一個獨一無二的名字,這個名 字與用戶程序中同名的全局變量名字不同。

未加extern "C"聲明時的連接方式
假設在C++中,模塊A的頭文件如下:
// 模塊A頭文件 moduleA.h
#ifndef MODULE_A_H
#define MODULE_A_H
int foo( int x, int y );
#endif  
在模塊B中引用該函數:
// 模塊B實現文件 moduleB.cpp
#i nclude "moduleA.h"
foo(2,3);
  
實際上,在連接階段,連接器會從模塊A生成的目標文件moduleA.obj中尋找_foo_int_int這樣的符號!

加extern "C"聲明後的編譯和連接方式

加extern "C"聲明後,模塊A的頭文件變為:
// 模塊A頭文件 moduleA.h
#ifndef MODULE_A_H
#define MODULE_A_H
extern "C" int foo( int x, int y );
#endif  
在模塊B的實現文件中仍然調用foo( 2,3 ),其結果是:
(1)模塊A編譯生成foo的目標代碼時,沒有對其名字進行特殊處理,采用了C語言的方式;
(2)連接器在為模塊B的目標代碼尋找foo(2,3)調用時,尋找的是未經修改的符號名_foo。

如果在模塊A中函數聲明了foo為extern "C"類型,而模塊B中包含的是extern int foo( int x, int y ) ,則模塊B找不到模塊A中的函數;反之亦然。

所 以,可以用一句話概括extern “C”這個聲明的真實目的(任何語言中的任何語法特性的誕生都不是隨意而為的,來源於真實世界的需求驅動。我們在思考問題時,不能只停留在這個語言是怎麽 做的,還要問一問它為什麽要這麽做,動機是什麽,這樣我們可以更深入地理解許多問題):實現C++與C及其它語言的混合編程。  
明白了C++中extern "C"的設立動機,我們下面來具體分析extern "C"通常的使用技巧:
extern "C"的慣用法(1)在C++中引用C語言中的函數和變量,在包含C語言頭文件(假設為cExample.h)時,需進行下列處理:
extern "C"
{
#i nclude "cExample.h"
}
而在C語言的頭文件中,對其外部函數只能指定為extern類型C語言中不支持extern "C"聲明,在.c文件中包含了extern "C"時會出現編譯語法錯誤。

C++引用C函數例子工程中包含的三個文件的源代碼如下:
/* c語言頭文件:cExample.h */
#ifndef C_EXAMPLE_H
#define C_EXAMPLE_H
extern int add(int x,int y);
#endif

/* c語言實現文件:cExample.c */
#i nclude "cExample.h"
int add( int x, int y )
{
return x + y;
}

// c++實現文件,調用add:cppFile.cppextern "C" {
#i nclude "cExample.h"
}
int main(int argc, char* argv[])
{
add(2,3);
return 0;
}
如果C++調用一個C語言編寫的.DLL時,當包括.DLL的頭文件或聲明接口函數時,應加extern "C" { }。
(2)在C中引用C++語言中的函數和變量時,C++的頭文件需添加extern "C",但是在C語言中不能直接引用聲明了extern "C"的該頭文件,應該僅將C文件中將C++中定義的extern "C"函數聲明為extern類型。
C引用C++函數例子工程中包含的三個文件的源代碼如下:
//C++頭文件 cppExample.h
#ifndef CPP_EXAMPLE_H
#define CPP_EXAMPLE_H
extern "C" int add( int x, int y );
#endif

//C++實現文件 cppExample.cpp
#i nclude "cppExample.h"
int add( int x, int y )
{
return x + y;
}

/* C實現文件 cFile.c
/* 這樣會編譯出錯:#i nclude "cExample.h" */
extern int add( int x, int y );
int main( int argc, char* argv[] )
{
add( 2, 3 );
return 0;
}
15題目的解答請參考C++中extern “C”含義深層探索註解:


16. 關聯、聚合(Aggregation)以及組合(Composition)的區別?
涉及到UML中的一些概念:關聯是表示兩個類的一般性聯系,比如“學生”和“老師”就是一種關聯關系;聚合表示has-a的關系,是一種相對松散的關系,聚合類不需要對被聚合類負責,如下圖所示,用空的菱形表示聚合關系:
                           C/C++筆試、面試題目大匯總
從實現的角度講,聚合可以表示為:
class A {...}  class B { A* a; .....}
組合表示contains-a的關系,關聯性強於聚合:組合類與被組合類有相同的生命周期,組合類要對被組合類負責,采用實心的菱形表示組合關系:
                            C/C++筆試、面試題目大匯總
實現的形式是:
class A{...} class B{ A a; ...}
參考文章:http://blog.csdn.net/wfwd/archive/2006/05/30/763753.aspx
          http://blog.csdn.net/wfwd/archive/2006/05/30/763760.aspx


17.面向對象的三個基本特征,並簡單敘述之?
1. 封裝:將客觀事物抽象成類,每個類對自身的數據和方法實行protection(private, protected,public)
2. 繼承:廣義的繼承有三種實現形式:實現繼承(指使用基類的屬性和方法而無需額外編碼的能力)、可視繼承(子窗體使用父窗體的外觀和實現代碼)、接口繼承 (僅使用屬性和方法,實現滯後到子類實現)。前兩種(類繼承)和後一種(對象組合=>接口繼承以及純虛函數)構成了功能復用的兩種方式。
3. 多態:是將父對象設置成為和一個或更多的他的子對象相等的技術,賦值之後,父對象就可以根據當前賦值給它的子對象的特性以不同的方式運作。簡單的說,就是一句話:允許將子類類型的指針賦值給父類類型的指針。


18. 重載(overload)和重寫(overried,有的書也叫做“覆蓋”)的區別?
常考的題目。從定義上來說:
重載:是指允許存在多個同名函數,而這些函數的參數表不同(或許參數個數不同,或許參數類型不同,或許兩者都不同)。
重寫:是指子類重新定義復類虛函數的方法。
從實現原理上來說:
重載:編譯器根據函數不同的參數表,對同名函數的名稱做修飾,然後這些同名函數就成了不同的函數(至少對於編譯器來說是這樣的)。如,有兩個同名函數:function func(p:integer):integer;function func(p:string):integer;。那麽編譯器做過修飾後的函數名稱可能是這樣的:int_funcstr_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.csdn.net/wfwd/archive/2006/05/30/763785.aspx,重點是static_cast, dynamic_cast和reinterpret_cast的區別和應用。

2014年7月15日 星期二

如何將兩變數的值交換卻不用到第三個變數 (轉貼)

原文一 http://stackoverflow.com/questions/5344837/how-to-swap-without-third-variable

原文二 http://stackoverflow.com/questions/1826159/swapping-two-variable-value-without-using-3rd-variable

第一種方法
 int x = 15;
 int y = 5;

 x = x + y;
 y = x - y;
 x = x - y; 

第二種方法
int A = ...;
int B = ...;
A = A ^ B;
B = A ^ B;
A = A ^ B;


如何判斷一個數是否為2的N次方 (轉貼)

原文在此 http://www.exploringbinary.com/ten-ways-to-check-if-an-integer-is-a-power-of-two-in-c/

第九個方法很酷

9. Decrement and Compare

This function is a common one-liner you’ll find on the Web, and is how the check is implemented in malloc.c in the GNU C Library (glibc).
int isPowerOfTwo (unsigned int x)
{
  return ((x != 0) && !(x & (x - 1)));
}
There are two halves to the expression: x != 0 and !(x & (x – 1)). The first half makes 0 a special case, since the second half only works correctly for positive numbers. The second half — the interesting part of the expression — is true when x is a power of two and false when x is not. Let’s see why.
Let n be the position of the leftmost 1 bit if x. If x is a power of two, its lone 1 bit is in position n. This means x – 1 has a 0 in position n. To see why, recall how binary subtraction works. When subtracting 1 from x, the borrow propagates all the way to position n; bit n becomes 0 and all lower bits become 1. Now, since x has no 1 bits in common with x – 1, x & (x – 1) is 0, and !(x & (x – 1)) is true.
Here are some examples (I’ll use 8-bit unsigned integers to keep things manageable):
Decrement and Compare, when x is a power of two
xx – 1x & (x – 1)
000000010000000000000000
000001000000001100000000
000100000000111100000000
If x is not a power of two, x – 1 has a 1 in position n. This is because the borrow will not propagate to position n. Subtraction borrows from the lowest 1 bit, which by virtue of x not being a power of two, is before position n. The lowest 1 bit is like a firewall that prevents the borrow from reaching position n. Since x and x – 1 have at least one 1 bit in common — at position n — x & (x – 1) is nonzero, and !(x & (x – 1)) is false.
Here are some examples:
Decrement and Compare, when x is a NOT power of two
xx – 1x & (x – 1)
000000110000001000000010
000001100000010100000100
000010110000101000001010
000111000001101100011000

2014年7月7日 星期一

C/C++之指標 (pointer),參考 (reference) 觀念整理與常見問題 (轉貼)

原文 : http://www.dotblogs.com.tw/brian/archive/2012/10/18/77588.aspx

C/C++之指標 (pointer),參考 (reference) 觀念整理與常見問題
這篇文章是由我舊的blog轉貼過來的
文中某些小細節稍作修改
--------------------------------
前 言
這是以前替人代班教課時寫的一些東西
重新整理後放上來,一方面當作自己的備忘錄 (自己最看得懂的還是自己寫的東西)
另一方面如果有人有這方面的問題,希望此文能對你們也有一點點幫助。
--------------------------------
很多程式員說:學C/C++而不會使用指標,相當於沒學過C/C++。
本文針對C/C++中,指標與參考的常見問題或錯誤,做了一番整理,但求能達到拋磚引玉之效。如有疏漏或錯誤之處,尚請不吝告知指教。
目錄
  1. 何謂指標 (pointer)? 何謂參考 (reference)?
  2. call by value? call by address (或call by pointer)? call by reference? -- swap(int* a, int* b) v.s. swap (int &a, int &b)
  3. pointer to pointer, reference to pointer (int** v.s. int*&)
  4. function pointer
  5. void ** (*d) (int &, char **(*)(char *, char **))....如何看懂複雜的宣告…
1. 何謂指標 (pointer)? 何謂參考 (reference)?
我們先談指標 (pointer)。指標,其實也只是一個變數,只是這個變數的意義是:指向某個儲存位址。很玄嗎? 一點也不。下面這張圖就可以輕易的看出指標為何物。

圖 中,a, b, c, d, p1, p2都是一般的變數,儲存在記憶體 (memory) 中。其中,p1變數所記載的值是變數a的記憶體 (memory) 位址,而p2則記載著b的記憶體位址,像這樣的狀況,我們就稱p1是一個指向a的指標,相同的,p2是一個指向b的指標。
在C/C++中,我們用下面的式子來表示這個關係:
int *p1 = &a;
int *p2 = &b;
其中的&,稱為address of (取址)。即,p1 = address of a,p2 = address of b。
另一個符號*,代表的意義是指標。
int *p1
要由後往前閱讀來瞭解它的意義:p1 is a pointer points to an integer。因此,
int *p1 = &a;
這整行,我們可以看成:p1 is a pointer points to integer variable a,即:p1是一個指標,指向整數變數a。
且讓我們暫時打住指標的討論,轉頭看看參考 (reference)。
參考,可以想像成是一個變數或物件的別名 (alias)。通常,當函式 (function) 的參數 (parameter) 在函式中會被修改,而且要把這個修改結果讓呼叫函式的部份繼續使用,我們會用參考來當參數傳入函式中。
讓我們看看下面的例子:
void swap(int &a, int &b){
int tmp = a;
a = b;
b = tmp;
}
當其他程式呼叫此交換程式時,只要直接寫swap(x, y)就能交換x與y的值。在這裡,a和b為x與y的別名,即:a就是x,b就是y,如同美國國父就是華盛頓一樣。a和b不是x和y的複製品,任何做用在a與b上的動作都會反應在x與y上面,反之亦然。
指標和參考之所以難懂,有很大一部份的原因是符號上的陌生所致。加上&既能用於取址又能用於參考,容易造成初學者的混淆。下面我們提供幾個建議來幫助各位看懂這些符號。
  • 把int *p視為 int* p。
    把int和*連在一起看,當作是一種型態叫做 "指向整數之指標",要比int *p自然得多。同樣的方式也可以套在char* p或void* p等。但要注意的是下面的狀況:
int* p, q;
不要把這行誤解成p, q都是指向int之指標,事實上,q只是一個int變數。上面這行相當於
int *p, q;

int *p; int q;
如果p, q都要宣告成指向int之指標,應寫成:
int *p, *q
或者干脆分兩行寫:
int* p;
int* q;
  • 若&前面有資料型態 (ex: int &),則為參考,&前面有等號 (ex: int* p = &a),則為取址。
    由於&同時具有多種意義,因此容易造成混淆。這裡列出的這個方法,可以幫助弄清楚每個&的意義。
2. call by value? call by address (或call by pointer)? call by reference? -- swap(int* a, int* b) v.s. swap (int &a, int &b)
JAVA中的reference與C++的reference意義上並不相同,卻使用同一個字,這也是reference容易造成混淆的原因。在此,我們暫不考慮JAVA中reference的觀念 (關於java中reference的觀念,請參考Reference in JAVA -- 淺談java的指標),純粹把主題放在C/C++上。
呼叫副函式時,call by value, address, 或reference是三種不同的參數傳遞方式。其意義如下:
  • call by value
    假設函式A呼叫函式B(p, q),則B中的p和q是「複製」自函式A所傳入的參數,B中對p, q所做的任何運算都不會影響到A中的p和q,因為B執行完後,並不會把複製的p, q存回到A中。這種參數傳遞方式,我們稱之為call by value。
    以swap這個常見的函式為例,若swap寫成下面的樣子:
void swap(int a, int b){
int tmp = a;
a = b;
b = tmp;
}
則呼叫
swap(x, y)
後,x和y的值並不會有變化。
  • call by address (或call by pointer)
    利用指標來做參數傳遞,這種方法骨子裡仍是call by value,只不過call by value的value,其資料型態為指標罷了。我們同樣看看用call by address來寫swap交換兩個integer的例子。
void swap(int* a, int* b){
int tmp = *a;
*a = *b;
*b = tmp;
}
呼 叫swap時,要寫成swap(&x, &y)。呼叫swap時,x的指標 (x的儲存位置) 與y的指標 (y的儲存位置) 會被複製一份到swap中,然後把該位置內所記載的值做更換。swap結束後,&x (address of x) 和&y (address of y) 依然沒變,只是address of x所記錄之變數值與address of y所記錄之變數值交換了。因為&x 和&y 其實是利用call by value在傳,因此,call by address其實骨子裡就是call by value。
  • call by reference
    這是C++才加進來的東西,C本身並沒有call by reference。call by reference基本上是把參數做個別名 (alias),以下面的swap為例:
swap(int &a, int &amp;b){
int tmp = a;
a = b;
b = tmp;
}
未來使用時,只要呼叫swap(x, y),就可以讓x和y的值交換。在這個例子中,a 就是 x, b 就是 y。這個觀念在上一節已經提過,在此不再贅述。
3. pointer to pointer, reference to pointer (int** v.s. int*&)
當 我們用call by pointer (或address) 來傳遞參數時,被呼叫的函式複製一份pointer的值過去。但是,當我們想在函式內改變pointer的值 (而非pointer所指向之變數的值),而且改變的效果要能在函式外看得到時,call by pointer就不足夠用了。此時應該用的是"call by pointer to pointer"或"call by reference to pointer"。我們先看下面的例子:
int g_int = 0;
void changePtr(int* pInt){
pInt = &g_int;
}
void main(){
int localInt = 1;
int* localPInt = &localInt;
changePtr(localPInt);
printf("%d\n", *localPInt);
}
在這個例子中,印出來的數字仍然會是localInt的1,因為changPtr中的pInt是由localPInt「複製」過去的,對pInt做改變並不會反應到localPInt身上。
我們先用pointer to pointer對localPInt做改變,請看下例。
int g_int = 0;
void changePtr(int** pInt){
*pInt = &g_int;
}
void main(){
int localInt = 1;
int* localPInt = &localInt;
changePtr(&localPInt);
printf("%d\n", *localPInt);
}
本 例中,印出來的數字會是g_int的0。changePtr函式中的pInt是由&localPInt複製所得,因此對pInt做改變並不會影響 main中的&amp;localPInt (資料型態:pointer to pointer to integer)。但在changePtr函式中我們改變的對象是pInt所指向的內容,因此這項改變在main中會顯示出來。
同樣的功能,我們也可改用reference to pointer來完成。但同樣切記,reference是C++才有的功能,因此reference to pointer也只能在支援C++的環境中使用。
int g_int = 0;
void changePtr(int* &refPInt){
refPInt = &g_int;
}
void main(){
int localInt = 1;
int* localPInt = &localInt;
changePtr(localPInt);
printf("%d\n", *localPInt);
}
這 段程式印出來的數字會是0。因為在changePtr中,我們宣告的參數型態為int* &,即:reference to pointer to integer。因此,main中的localPInt與changePtr函式中的refPInt其實是「同一件東西」。
另一種 常見的混淆是pointer array (指標陣列) 與pointer to pointers,因為兩種都可以寫成**的型式。如,int**可能是pointer to pointer to integer,也可能是integer pointer array。但pointer array的觀念相對來講要簡單且直觀許多,這裡我們就暫不花篇幅敘述。常見的例子:main(int argc, char** argv)其實應該是main(int argc, char* argv[])。
4. function pointer
變數的指標指向變數的位址,同樣的,function pointer (函式指標) 也是指向函式的位址的指標。
函式指標的加入,讓C/C++的符號更複雜,也使更多人望之而卻步。在說明函式指標的用途前,我們先直接由語法來看看函式指標該怎麼宣告、怎麼理解。
假設有個函式長成下面的樣子:
void func1(int int1, char char1);
我們想宣告一個能指向func1的指標,則寫成下面這樣:
void (*funcPtr1)(int, char);
這樣的寫法應理解成:funcPtr1是一個函數指標,它指向的函數接受int與char兩個參數並回傳void。如果今天有另一個函式長成
void func2(int int2, char char2);
則funcPtr1也能指向func2。
指標指向的方法,寫成下面這樣:
funcPtr1 = &func1;
取址符號省略亦可,效果相同:
funcPtr1 = func1;
若欲在宣告時就直接給予初值,則寫成下面這樣:
void (*funcPtr1)(int, char) = &func1;    //&亦可省略
stdlib.h中提供的qsort函式是函式指標最常見的應用之一。此函式之prototype長得如下:
void qsort(void* base, size_t n, size_t size, int (*cmp)(const void*, const void*));
其中的int (*cmp)(const void*, const void*) 就使用到函式指標。
函式指標常見的使用時機是multithread時。函數指標負責把函數傳進建立執行緒的API中。
另外,callback function也是常使用函式指標的地方。所謂callback function即:發生某事件時,自動執行某些動作。在event driven的環境中,便時常使用callback function來實現此機制。
事實上,函式指標還能讓C語言實作polymorphism。但礙於篇幅,在此不再詳述。
5. void ** (*d) (int &, char **(*)(char *, char **))....如何看懂複雜的宣告…
在這裡,我們介紹兩種方式來看懂複雜的宣告。第一種要判斷的是:常數與指標混合使用時,到底const修飾的是指標還是指標所指的變數? 第二種是面對如標題所示這種複雜的宣告時,我們要怎麼讀懂它。
5.1 常數與指標的讀法
const double *ptr;
double *const ptr;
double const* ptr;
const double *const ptr;
以上幾個宣告,到底const修飾的對象是指標,還是指標所指向的變數呢?
其實,關鍵在於:*與const的前後關係!
當*在const之前,則是常數指標,反之則為常數變數。因此,
const double *ptr;    // ptr指向常數變數
double *const ptr;    // ptr是常數指標
double const* ptr;    // ptr指向常數變數
const double *const ptr;    // 指向常數變數的常數指標
事實上,在The C++ Programming Language中有提到一個簡單的要訣:由右向左讀!!讓我們用這個要訣再來試一次。
const double *ptr;    // ptr is a pointer points to double, which is a constant
double *const ptr;    // ptr is a constant pointer points to double
double const* ptr;    // ptr is a pointer points to constant double
const double *const ptr;    // ptr is a constant pointer points to double, which is a constant
結果完全相同 :-)
5.2 複雜宣告的讀法 void ** (*d) (int &, char **(*)(char *, char **)).......
其實閱讀C/C++中複雜的宣告有點像是讀英文的長句子,看多了,自然知道句子是怎麼構造出來的。
但對於句子還不熟的人,難免得藉助文法來拆解一個句子。關於C語言複雜宣告的解析文法,最令我印象深刻的,莫過於印度工程師Vikram的"The right-left rule"。他是這麼說的:
「從最內層的括號讀起,變數名稱,然後往右,遇到括號就往左。當括號內的東西都解讀完畢了,就跳出括號繼續未完成的部份,重覆上面的步驟直到解讀完畢。」
舉個例子:void ** (*d) (int &amp;, char*)依下面方式解讀:
1. 最內層括號的讀起,變數名稱: d
2. 往右直到碰到) : (空白)
3. 往左直到碰到( :是一個函數指標
4. 跳出括號,往右,碰到(int &, char*): 此函式接受兩個參數:第一個參數是reference to integer,第二個參數是character pointer。
5. 往左遇上void **: 此函式回傳的型態為pointer to pointer to void。
==> d是一個函式指標,指向的函式接受int&和char*兩個參數並回傳void**的型態。
如何,是不是好懂很多了呢?
標題中的void ** (*d) (int &, char **(*)(char *, char **))其實和上面的例子幾乎一樣,只是函式的第二個參數又是一個函式指標,接受char*和char**兩個參數並回傳char**的型態。