[牛客網]錯題整理(2)

錯題集

1.定義一個函數指針,指向的函數有兩個int形參並且返回一個函數指針,返回的指針指向一個有一個int形參且返回int的函數?

答案:int (*(*F)(int, int))(int)

析:首先,一個函數指針,指向的函數有兩個int形參,這個就是(*F)(int, int),這返回的是一個指針

返回一個函數指針,返回的指針指向一個有一個int形參且返回int的函數;把上面的結果當成一個指針,相當於再做一次上面的步驟,所以結果爲:int (*(*F)(int, int))(int)

 

2.下面哪個指針表達式可以用來引用數組元素a[i][j][k][l]()

答案:*(*(*(*(a+i)+j)+k)+l)

解析:

a:整個四維數組的地址

*(a+i) = a[i]

*(a+i)+j = a[i] +j

*((a+i)+j) = a[i][j]

*((a+i)+j)+k =  a[i][j]+k

*( *((a+i)+j)+k ) = a[i][j][k]

*( *((a+i)+j)+k )+l =  a[i][j][k]+l

*( *( *((a+i)+j)+k )+l ) = a[i][j][k][l]

 

3.採用多路複用I/O監聽3個套接字的數據時,如果套接字描述符分別是:5,17,19,則

select(int maxfd,struct fd_set* rdset,NULL,NULL)中的maxfd應取爲()

答案:20

解析:Maxfd是三個套接字描述符中最大數字加上1

 

4.下面代碼的輸出結果是()

int main(){

   int pid;

   int num=1;

   pid=fork();

   if(pid>0){

   num++;

   printf("in parent:num:%d addr:%x\n",num,&num);

   }

   else if(pid==0){

   printf("in child:num:%d addr:%x\n",num,&num);

   }

}

答案:父子進程中輸出的num不同,num地址相同

解析:虛擬地址空間。num地址的值相同,但是其真實的物理地址卻不一樣。linux下實現了一下,發現地址值真的一樣。fork之後子進程複製了父進程的數據、堆棧。但是由於地址重定位器之類的魔法存在,所以,看似一樣的地址空間(虛擬地址空間),

其實卻是不同的物理地址空間。同時可以驗證c程序中輸出的地址空間其實都是虛擬地址空間。

 

5.不考慮任何編譯器優化(如:NRVO),下述代碼的第10行會發生

#include <stdio.h>//1

class B//2

{//3

};//4

B func(const B& rhs){//5

  return rhs;//6

}//7

int main(int argc,char **argv){//8

  B b1,b2;//9

  b2=func(b1);//10

}//11

答案:一次拷貝構造函數,一次析構函數,一次(拷貝賦值運算符)operator=

解析:一次拷貝構造函數發生在func函數調用完成,返回B類型的對象時,因爲返回的不是引用類型,所以會生成一個對象,不妨稱爲TEMP,將返回的對象通過拷貝構造函數複製給TEMP,然後,返回值所對應的對象會被析構。如果返回值是引用類型,則不會調用拷貝構造函數。賦值運算符在func函數執行完成後,將上面提到的TEMP,通過賦值運算符賦值給b2,值得注意的是賦值運算符重載函數如果不自己定義,

程序會認爲是調用缺省的賦值運算符重載函數。

 

6.請問下面的程序一共輸出多少個“-”?

int main(void)

{

    int i;

    for (i = 0; i < 2; i++) {

        fork();

        printf("-");

    }

    return 0;

}

答案:8

解析:一共調用了6次printf,但是會輸出8個-。因爲父進程的輸出緩衝也會被子進程複製

fork(); // i=0

    printf("-");    // buffer="-"

    fork(); // i=1

        printf("-");    // +1

    printf("-");

printf("-")

fork(); // i=1

    printf("-");    // +1

printf("-")

7.閱讀以下代碼:

class parent  

{  

    public:  

    virtual void output();  

};  

void parent::output()  

{  

    printf("parent!");  

}  

       

class son : public parent  

{  

    public:  

    virtual void output();  

};  

void son::output()  

{  

    printf("son!");  

}

son s;

memset(&s , 0 , sizeof(s));

parent& p = s;

p.output();

執行結果是()

答案:沒有輸出結果,程序運行出錯

解析:memset 將s所指向的某一塊內存中的每個字節的內容全部設置爲ch指定的ASCII值, 本代碼中, 不虛函數鏈表地址也清空了, 所以p.output調用失敗。 output函數的地址編程0

 

8.關於c++的inline關鍵字,以下說法正確的是

答案:定義在Class聲明內的成員函數默認是inline函數

解析: 內聯函數:

Tip: 只有當函數只有 10 行甚至更少時纔將其定義爲內聯函數.

定義: 當函數被聲明爲內聯函數之後, 編譯器會將其內聯展開, 而不是按通常的函數調用機制進行調用.

優點: 當函數體比較小的時候, 內聯該函數可以令目標代碼更加高效. 對於存取函數以及其它函數體比較短, 性能關鍵的函數, 鼓勵使用內聯.

缺點: 濫用內聯將導致程序變慢. 內聯可能使目標代碼量或增或減, 這取決於內聯函數的大小. 內聯非常短小的存取函數通常會減少代碼大小, 但內聯一個相當大的函數將戲劇性的增加代碼大小. 現代處理器由於更好的利用了指令緩存, 小巧的代碼往往執行更快。

結論: 一個較爲合理的經驗準則是, 不要內聯超過 10 行的函數. 謹慎對待析構函數, 析構函數往往比其表面看起來要更長, 因爲有隱含的成員和基類析構函數被調用!

另一個實用的經驗準則: 內聯那些包含循環或 switch 語句的函數常常是得不償失 (除非在大多數情況下, 這些循環或 switch 語句從不被執行).

有些函數即使聲明爲內聯的也不一定會被編譯器內聯, 這點很重要; 比如虛函數和遞歸函數就不會被正常內聯. 通常, 遞歸函數不應該聲明成內聯函數.(遞歸調用堆棧的展開並不像循環那麼簡單, 比如遞歸層數在編譯時可能是未知的, 大多數編譯器都不支持內聯遞歸函數). 虛函數內聯的主要原因則是想把它的函數體放在類定義內, 爲了圖個方便, 抑或是當作文檔描述其行爲, 比如精短的存取函數.

-inl.h文件:

Tip: 複雜的內聯函數的定義, 應放在後綴名爲 -inl.h 的頭文件中.

內聯函數的定義必須放在頭文件中, 編譯器才能在調用點內聯展開定義. 然而, 實現代碼理論上應該放在 .cc 文件中, 我們不希望 .h 文件中有太多實現代碼, 除非在可讀性和性能上有明顯優勢.

如果內聯函數的定義比較短小, 邏輯比較簡單, 實現代碼放在 .h 文件裏沒有任何問題. 比如, 存取函數的實現理所當然都應該放在類定義內. 出於編寫者和調用者的方便, 較複雜的內聯函數也可以放到 .h 文件中, 如果你覺得這樣會使頭文件顯得笨重, 也可以把它萃取到單獨的 -inl.h 中. 這樣把實現和類定義分離開來, 當需要時包含對應的 -inl.h 即可。

A 如果只聲明含有inline關鍵字,就沒有內聯的效果。 內聯函數的定義必須放在頭文件中, 編譯器才能在調用點內聯展開定義.   有些函數即使聲明爲內聯的也不一定會被編譯器內聯, 這點很重要; 比如虛函數和遞歸函數就不會被正常內聯. 通常, 遞歸函數不應該聲明成內聯函數.

B 內聯函數應該在頭文件中定義,這一點不同於其他函數。編譯器在調用點內聯展開函數的代碼時,必須能夠找到 inline 函數的定義才能將調用函數替換爲函數代碼,而對於在頭文件中僅有函數聲明是不夠的。

C 當然內聯函數定義也可以放在源文件中,但此時只有定義的那個源文件可以用它,而且必須爲每個源文件拷貝一份定義(即每個源文件裏的定義必須是完全相同的),當然即使是放在頭文件中,也是對每個定義做一份拷貝,只不過是編譯器替你完成這種拷貝罷了。但相比於放在源文件中,放在頭文件中既能夠確保調用函數是定義是相同的,又能夠保證在調用點能夠找到函數定義從而完成內聯(替換)。

對於由兩個文件compute.C和draw.C構成的程序來說,程序員不能定義這樣的min()函數,它在compute.C中指一件事情,而在draw.C中指另外一件事情。如果兩個定義不相同,程序將會有未定義的行爲:

      爲保證不會發生這樣的事情,建議把inline函數的定義放到頭文件中。在每個調用該inline函數的文件中包含該頭文件。這種方法保證對每個inline函數只有一個定義,且程序員無需複製代碼,並且不可能在程序的生命期中引起無意的不匹配的事情。

D 正確。 定義在類聲明之中的成員函數將自動地成爲內聯函數,例如:

class A {   public:  void Foo(int x, int y) { ... }   // 自動地成爲內聯函數   }

EF 在每個調用該inline函數的文件中包含該頭文件。這種方法保證對每個inline函數只有一個定義,且程序員無需複製代碼,並且不可能在程序的生命期中引起無意的不匹配的事情。最好只有一個定義!

 

 

 

 

 

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章