c++ 常見問題 1

1.在C++ 程序中調用被C 編譯器編譯後的函數,爲何要加extern 「C」?html

  答:C++語言支持函數重載,C 語言不支持函數重載。函數被C++編譯後在庫中的名字與C 語言的不一樣。假設某個函數的原型爲: void foo(int x, int y);c++

  該函數被C 編譯器編譯後在庫中的名字爲_foo , 而C++ 編譯器則會產生像_foo_int_int 之類的名字。編程

  C++提供了C 鏈接交換指定符號extern「C」來解決名字匹配問題。數組

http://yjbys.com/bishi/timu/598258.html安全

 

2.char * strcpy(char * strDest,const char * strSrc);ide

已知strcpy函數的原型是:
char * strcpy(char * strDest,const char * strSrc);
⒈不調用 庫函數,實現strcpy函數。
⒉解釋爲何要返回char *。
解說
1
2
3
4
5
6
7
8
9
strcpy 的實現代碼
char  strcpy ( char  * strDest, const  char  * strSrc)
{
if  ((NULL==strDest) || (NULL==strSrc))  //[1]
throw  "Invalid argument(s)" //[2]
char  * strDestCopy = strDest;  //[3]
while  ((*strDestCopy++=*strSrc++)!= '\0' );  //[4]
return  strDest;
}
錯誤的作法:
[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 (*strDestCopy++=*strSrc++);,同[1](B)。
(B)循環寫成while (*strSrc!='\0') *strDest++=*strSrc++;,說明答題者對邊界條件的檢查不力。循環體結束後,strDest字符串的末尾沒有正確地加上'\0'。
⒉返回strDest的原始值使函數可以支持鏈式 表達式,增長了函數的「附加值」。一樣功能的函數,若是能合理地提升的可用性,天然就更加理想。
鏈式表達式的形式如:
int iLength=strlen(strcpy(strA,strB));
又如:
char * strA=strcpy(new char[10],strB);
返回strSrc的原始值是錯誤的。其一,源字符串確定是已知的,返回它沒有意義。其二,不能支持形如第二例的表達式。其三,爲了保護源字符串, 形參用const限定strSrc所指的內容,把const char *做爲char *返回,類型不符,編譯報錯。
http://baike.baidu.com/link?url=_q_nYQSrK6uYGTSFjM9Y0ts-Xj6I4cTgca4TDgDnFJsjIt5Udg6kDI6reH4EIcxseA-g2eOZkokQHKxlceeXo_
 
3. C++的多態性
多態性包含編譯時的多態性、運行時的多態性兩大類。 即:多態性也分靜態多態性和動態多態性兩種。

   靜態多態性

  靜態多態性是指定義在一個類或一個函數中的同名函數,它們根據參數表(類型以及個數)區別語義,並經過靜態聯編實現,例如,在一個類中定義的不一樣參數的構造函數

——函數重載(區分函數重載、函數覆蓋)

動態多態性

  動態多態性是指定義在一個類層次的不一樣類中的重載函數,它們通常具備相同的函數,所以要根據指針指向的對象所在類來區別語義(不一樣的子類對象由父類指針指向,提升代碼複用),它經過動態聯編實現。
 
  在用戶不做任何干預的環境下,類的成員函數的行爲能根據調用它的對象類型自動做出適應性調整,並且調整是發生在程序運行時,這就是程序的動態多態性。即,發出一樣的消息被不一樣類型的對象接收時,有可能致使徹底不一樣的行爲。
 
博主的總結挺好的:

C++多態性是經過虛函數來實現的,虛函數容許子類從新定義成員函數,而子類從新定義父類的作法稱爲覆蓋(override),或者稱爲重寫。(這裏我以爲要補充,重寫的話能夠有兩種,直接重寫成員函數和重寫虛函數,只有重寫了虛函數的才能算做是體現了C++多態性)而重載則是容許有多個同名的函數,而這些函數的參數列表不一樣,容許參數個數不一樣,參數類型不一樣,或者二者都不一樣。編譯器會根據這些函數的不一樣列表,將同名的函數的名稱作修飾,從而生成一些不一樣名稱的預處理函數,來實現同名函數調用時的重載問題。但這並無體現多態性。模塊化

  多態與非多態的實質區別就是函數地址是早綁定仍是晚綁定。若是函數的調用,在編譯器編譯期間就能夠肯定函數的調用地址,並生產代碼,是靜態的,就是說地址是早綁定的。而若是函數調用的地址不能在編譯器期間肯定,須要在運行時才肯定,這就屬於晚綁定。函數

  那麼多態的做用是什麼呢,封裝可使得代碼模塊化,繼承能夠擴展已存在的代碼,他們的目的都是爲了代碼重用。而多態的目的則是爲了接口重用。也就是說,不論傳遞過來的到底是那個類的對象,函數都可以經過同一個接口調用到適應各自對象的實現方法。url

  最多見的用法就是聲明基類的指針,利用該指針指向任意一個子類對象,調用相應的虛函數,能夠根據指向的子類的不一樣而實現不一樣的方法。若是沒有使用虛函數的話,即沒有利用C++多態性,則利用基類指針調用相應的函數的時候,將總被限制在基類函數自己,而沒法調用到子類中被重寫過的函數。由於沒有多態性,函數調用的地址將是必定的,而固定的地址將始終調用到同一個函數,這就沒法實現一個接口,多種方法的目的了。spa

虛函數理解:

對於虛函數調用來講,每個對象內部都有一個虛表指針,該虛表指針被初始化爲本類的虛表。因此在程序中,無論你的對象類型如何轉換,但該對象內部的虛表指針是固定的,因此呢,才能實現動態的對象函數調用,這就是C++多態性實現的原理。 
總結(基類有虛函數): 
一、 每個類都有虛表。 
二、 虛表能夠繼承,若是子類沒有重寫虛函數,那麼子類虛表中仍然會有該函數的地址,只不過這個地址指向的是基類的虛函數實現。若是基類3個虛函數,那麼基類的虛表中就有三項(虛函數地址),派生類也會有虛表,至少有三項,若是重寫了相應的虛函數,那麼虛表中的地址就會改變,指向自身的虛函數實現。若是派生類有本身的虛函數,那麼虛表中就會添加該項。 
三、 派生類的虛表中虛函數地址的排列順序和基類的虛表中虛函數地址排列順序相同。

http://blog.csdn.net/youngchang06hpu/article/details/8715011

4. 引用與指針

http://blog.csdn.net/youngchang06hpu/article/details/8715011

參考:《c和指針》

1 引用是C++中的概念,C中沒有引用,而只有&操做,即取地址符,其必須做用於一個左值。

2 引用是變量的一個別名,即創建引用並不會致使新內存的分配,引用在初始化時必須存在一個對應的被引用的內存對象,且此引用關係創建後,引用不能再被改變,而所引用的變量是能夠改變的。

3 引用在參數傳遞中使用,下降了傳值傳遞的消耗。若是引用做爲函數的返回值,那麼函數能夠做爲一個左值來使用。這一點在C++的流操做部分很常見。

對於數組名和對數組名取地址

 int a[5]={1,2,3,4,5};
 int *p1=(int*)(&a+1);
 int *p2=(int*)((int)a+1);
 int *p3=(int*)(&a)+1;
    // p3=p3+1;
 printf("%x,%x,%x",p1[-1],*p2,p3[-1]);

執行結果爲 5,2000000,1。對於結果5仍是比較糾結。看了一下網上的帖子和幾本書,總結了一下。

 

首先說一下關於對數組名取地址:
        關於對數組名取地址的問題,因爲數組名是右值,原本&array 是不合法的,早期很多編譯器就是指定&array 是非法的,但後來C89/C99認爲數組符合對象的語義,對一個對象取地址是合理的,所以,從維護對象的完整性出發,也容許&array 。只不過,&array 的意義並不是對一個數組名取地址,而是對一個數組對象取地址,也正由於如此,array 纔跟&array 所表明的地址值同樣,同時sizeof(array )應該跟sizeof(&array )同樣,由於sizeof(&array )表明取一個數組對象的長度。

         要注意到 array 和 &array 的類型是不一樣的。array爲一個指針,而&array是指向數組int [100]的指針。array 至關於 &array[0],而 &array 是一個指向 int[100] 的指針,類型是 int(*)[100]。


另外從步長的角度分析這個問題
執行以下語句:
printf("array=%p, array+1=%p/n", array, array+1); 
printf("&array=%p, &array+1=%p/n", &array, &array+1);

結果爲:
array=0012FDF0, array+1=0012FDF4     //+sizeof(int)
&array=0012FDF0, &array+1=0012FF80  //+sizeof(&array)

在《C專家編程》書中關於數組一章P203,有以下解釋:    不管指針仍是數組,在連續的內存地址上移動時,編譯器都必須計算每次前進的步長。    編譯器自動把下標值調整到數組元素大小,對起始地址進行加法操做以前,編譯器都會負責計算每次增長的步長,這就是爲何指針類型老是有類型限制,每一個指針只能指向一種類型的緣由所在,由於編譯器須要知道對指針進行解除引用操做時應該取幾個字節,以及每一個小標的步長應取幾個字節。    另外步長的自動調整還和上下語句相關: int *p3=(int*)(&a);      p3=p3+1;    首先對P3指針變量賦初值,指向數組int [5]的指針,而後對指針進行加一的操做,其中P3定義爲一個指向int類型的指針,所以最終P3的值等價P3+sizeof(int) int *p3=(int*)(&array+1);    &array+1,步長爲1,其中步長的長度和&array的類型匹配,即&array是指向數組int [100]的指針,因此&array+1等價爲&array+sizeof(&array)   最終p1[-1]等價爲*(P1-1),所以等價爲第二個int [5]的數據首地址(並不存在第二個數組顯然當前指針已經越界了,另外數組元素在內存中是連續存貯的)減去一個爲sizeof(int)的步長,因此指向了第一個數組的最後1個元素。

相關文章
相關標籤/搜索