閱讀925 返回首頁    go 技術社區[雲棲]


【一問一答】錯題庫整理

此文章不斷更新從 ”一問一答“上遇到的做錯的題目,持續更新

No.1   by 17th Sep 2013

以下代碼的輸出結果

#include <iostream>
using namespace std;

int compare(char *a, char *b){

    if((strlen(a)-strlen(b))>=0) return 1;
    else return 0;    
}

int main(){
   
    cout<<compare("ab","abc")<<endl;
    cout<<compare("abce","abc");
    
    system("pause");
    return 0;    
}

當時百思不得其解為什麼會輸出11 而不是01; 

後來google了後知道strlen返回值為size_t,而size_t 是unsigned int型的,所以 strlen("ab")-strlen("abc")得到的是很大的正數

No.2  by 22nd September 2013

假設AB是一個類,則以下代碼中,得到的動態變量為:

AB *s  = new AB(a,5);
A: *s  B: s->a C:s.a D:s 

Answer: 一般來說,編譯器將內存分為3部分:靜態存儲區,棧和堆。 靜態存儲區存儲的主要是全局變量和靜態變量,棧主要存儲函數調用中的變量,地址等,堆主要存儲動態變量,C中指 malloc,free等操作,C++中有new和delete等操作

題目中,首先編譯器利用new 為對象s創建分配內存,再調用AB類的構造函數,對存儲區域進行初始化,語句返回對象的地址,及一個AB類的指針。如果內存不足,則分配失敗,返回0,並不會調用AB類的構造函數,此時s為0.

可以對全局以及類內部的new和delete進行重載,以完成需要的功能;

還可以定位new和delete,使其對在指定的內存地址為對象分配空間。

delete隻能刪除new生成的對象,並且不能刪除void* 的指針,因為對象類型未定義,無法調用其析構函數,容易引起內存泄露。

C++中, 一個對象或對象的引用a,則用a. 來訪問其成員,一個對象指針b,則用b->來訪問其成員

答案A  s是一個指針,指向的內容是動態生成的變量,及*s

No.3 by  26th Sep 2013

C語言用戶標示符命名規則:
1. 隻能由字母,數字和下劃線三種字符組陳
2. 第一個字符必須是字母或下劃線
3. 不能和C語言中的關鍵字或保留字相同(C語言是大小寫敏感的,所以For,Main等詞雖然是關鍵字,但是屬於符合命名規則的名稱)
No.4 by 6th November 2013
判斷:
虛基類的作用是為了解決多繼承中公共基類在派生類中隻產生一個基類子對象的問題。

先看代碼:
class A
{
public:
    int iValue;
};

class B:public A
{
public:
    void bPrintf(){cout<<"This is class B"<<endl;};
};

class C:public A
{
public:
    void cPrintf(){cout<<"This is class C"<<endl;};
};

class D:public B,public C
{
public:
    void dPrintf(){cout<<"This is class D"<<endl;};
};

void main()
{
    D d;
    cout<<d.iValue<<endl; //錯誤,不明確的訪問
    cout<<d.A::iValue<<endl; //正確
    cout<<d.B::iValue<<endl; //正確
    cout<<d.C::iValue<<endl; //正確
}
代碼中類B和類C都public繼承自類A,因此類B和類C都有一個成員 iValue,而類D又多重繼承自B C,這樣類D就有一個重名的成員iValue,一個來自B一個來自C,在主函數中調用d.iValue就讓編譯器產生困惑不知道該調用哪一個iValue,就產生了二義性。正確的做法是采用作用域限定符來告訴編譯器到底調用那個成員。但是這樣的代碼會是的在類D的實例中會有多個iValue的實例,這樣就占用了額外的空間,因此C++中引入了虛基類的概念來解決該問題。
class A
{
public:
    int iValue;
};

class B:virtual public A
{
public:
    void bPrintf(){cout<<"This is class B"<<endl;};
};

class C:virtual public A
{
public:
    void cPrintf(){cout<<"This is class C"<<endl;};
};

class D:public B,public C
{
public:
    void dPrintf(){cout<<"This is class D"<<endl;};
};

void main()
{
    D d;
    cout<<d.iValue<<endl; //正確
}
在繼承的類的前麵加上關鍵字virtual表示被繼承的類是一個虛基類,它的被繼承的成員在派生類中隻保留一個實例,例如iValue,類D的對象d雖然是繼承自B和C,但是B和C中隻有一個iValue的副本,因此在主函數中調用d.iValue就不會產生二義性,同時又節省了存儲空間。
因此,本題的答案為 正確。


No.5 By 6th November 2013

求下麵代碼的輸出結果:

class A
{
public:
    void funPrint(){cout<<"funPrint of class A"<<endl;};
};

class B:public A
{
public:
    void funPrint(){cout<<"funPrint of class B"<<endl;};
};

void main()
{
    A *p; //定義基類的指針
    A a;
    B b;
    p=&a;
    p->funPrint();
    p=&b;
    p->funPrint();
}
不管引用的實例是哪個類的當你調用的時候係統會調用左值那個對象所屬類的方法。比如說 上麵的代碼類A B都有一個funPrint 函數,因為p是一個A類的指針,所以不管你將p指針指向類A或是類B,最終調用的函數都是類A的funPrint 函數。這就是靜態聯篇,編譯器在編譯的時候就已經確定好了。可是如果我想實現跟據實例的不同來動態決定調用哪個函數呢?這就須要用到 虛函數(也就是動態聯篇)。

class A
{
public:
    virtual void funPrint(){cout<<"funPrint of class A"<<endl;};
};

class B:public A
{
public:
    virtual void funPrint(){cout<<"funPrint of class B"<<endl;};
};

void main()
{
    A *p; //定義基類的指針
    A a;
    B b;
    p=&a;
    p->funPrint();
    p=&b;
    p->funPrint();
}
在基類的成員函數前加virtual關鍵字表示這個函數是一個虛函數,所謂虛函數就是在編譯的時候不確定要調用哪個函數,而是動態決定將要調 用哪個函數,要實現虛函數必須派生類的函數名與基類相同,參數名參數類型等也要與基類相同。但派生類中的virtual關鍵字可以省略,也表 示這是一個虛函數。下麵來解決一下代碼,聲明一個基類的指針(必須是基類,反之則不行)p,把p指向類A的實例a,調用funPrint函數,這 時係統會判斷p所指向的實例的類型,如果是A類的實例就調用A類的funPrint函數,如果是B類的實例就調用B類的funPrint函數。
因此原題目中兩個函數調用的輸出結果都是funPrint of class A。

小節:關於虛基類和虛函數:

虛基類 
    1, 一個類可以在一個類族中既被用作虛基類,也被用作非虛基類。 
    2, 在派生類的對象中,同名的虛基類隻產生一個虛基類子對象,而某個非虛基類產生各自的子對象。 
    3, 虛基類子對象是由最派生類的構造函數通過調用虛基類的構造函數進行初始化的。 
    4, 最派生類是指在繼承結構中建立對象時所指定的類。 
    5, 派生類的構造函數的成員初始化列表中必須列出對虛基類構造函數的調用;如果未列出,則表示使用該虛基類的缺省構造函數。 
    6, 從虛基類直接或間接派生的派生類中的構造函數的成員初始化列表中都要列出對虛基類構造函數的調用。但隻有用於建立對象的最派生 類的構造函數調用虛基類的構造函數,而該派生類的所有基類中列出的對虛基類的構造函數的調用在執行中被忽略,從而保證對虛基類子對象 隻初始化一次。 
    7, 在一個成員初始化列表中同時出現對虛基類和非虛基類構造函數的調用時,虛基類的構造函數先於非虛基類的構造函數執行。 

    虛函數 
    1, 虛函數是非靜態的、非內聯的成員函數,而不能是友元函數,但虛函數可以在另一個類中被聲明為友元函數。 
    2, 虛函數聲明隻能出現在類定義的函數原型聲明中,而不能在成員函數的函數體實現的時候聲明。 
    3, 一個虛函數無論被公有繼承多少次,它仍然保持其虛函數的特性。 
    4, 若類中一個成員函數被說明為虛函數,則該成員函數在派生類中可能有不同的實現。當使用該成員函數操作指針或引用所標識的對象時 ,對該成員函數調用可采用動態聯編。 
    5, 定義了虛函數後,程序中聲明的指向基類的指針就可以指向其派生類。在執行過程中,該函數可以不斷改變它所指向的對象,調用不同 版本的成員函數,而且這些動作都是在運行時動態實現的。虛函數充分體現了麵向對象程序設計的動態多態性。 純虛函數 版本的成員函數,而且這些動作都是在運行時動態實現的。虛函數充分體現了麵向對象程序設計的動態多態性。

No.6 Friend function by 6th Nov 2013

Question:判斷:

一個類的友元函數能訪問該類的所有成員。

如果將類的封裝比喻成一堵牆的話,那麼友元機製就像牆上了開了一個門,那些得 到允許的類或函數允許通過這個門訪問一般的類或者函數無法訪問的私有屬性和方法。友元機製使類的封裝性得到消弱,所以使用時一定要慎重。友元類的說明將外界的某個類在本類別的定義中說明為友元,那麼外界的類就成為本類的“朋 友”,那個類就可以訪問本類的私有數據了。

通常對於普通函數來說,要訪問類的保護成員是不可能的,如果想這麼做那麼必須把類的成員都生命成為public(共用的),然而這做帶來的問題遍是任何外部函數都可以毫無約束的訪問它操作它,c++利用friend修飾符,可以讓一些你設定的函數能夠對這些保護數據進行操作,避免把類成員全部設置成public,最大限度的保護數據成員的安全。友元能夠使得普通函數直接訪問類的保護數據,避免了類成員函數的頻繁調用,可以節約處理器開銷,提高程序的效率,但所矛盾的是,即使是最大限度大保護,同樣也破壞了類的封裝特性,這即是友元的缺點,在現在cpu速度越來越快的今天我們並不推薦使用它,但它作為c++一個必要的知識點,一個完整的組成部分,我們還是需要討論一下的。在類裏聲明一個普通數學,在前麵加上friend修飾,那麼這個函數就成了該類的友元,可以訪問該類的一切成員。

No.7 派生類構造函數的創建順序 by 6th Nov 2013

Question: 創建派生類成員時,各構造函數的構造順序為?

當派生類中不含對象成員時
●在創建派生類對象時,構造函數的執行順序是:基類的構造函數→派生類的構造函數;
●在撤消派生類對象時,析構函數的執行順序是:派生類的構造函數→基類的構造函數。
當派生類中含有對象成員時
●在定義派生類對象時,構造函數的執行順序:基類的構造函數→對象成員的構造函數→派生類的構造函數;
●在撤消派生類對象時,析構函數的執行順序:派生類的構造函數→對象成員的構造函數→基類的構造函數。.

構造和析構的過程是在stack上進行的,所以析構時和構造時是相反的。

No.7  by 6th Nov 2013

int a = 1;
    int b = 2;
    int c = 3;
    int d = 4;
    int m = 5;
    int n = 6;
    int e = (m = a>b)&&(n = c>d);   
    cout<<e<<endl;
    cout<<m<<endl;
    cout<<n<<endl;
a>b為0, m為0之後,由於是與運算,後麵的式子就不需要計算了,所有n的值不變

最後結果為  0 0 6 

No.8 Pointer Operate by 6th Nov 2013

對於同類型的指針變量,不能進行的操作是:  A. ==     B. +     C. -    D. =   E. > 

指針指的是內存中的地址,例如一個指針指向的地址是0x80008000, 另一個指向的是0x80008004

兩個指針可以進行大小,相等等比較操作,兩個指針相減,得到兩個指針的差值,及兩指針之間的距離,但是兩個指針相加,得到的結果沒有意義,所以沒有指針相加的操作。

No.9 Virtual Function by 14th Nov 2013

構造函數,內聯函數和靜態函數都不能是虛函數

構造函數:

從語義上理解,構造函數本身就是為了明確初始化對象本身才有的,而虛函數的作用是告訴編譯器現在還不明確,不確定什麼時候調用該函數。

內聯函數:

內聯函數是在編譯時被展開,減少函數調用的花銷,而虛函數是在運行時才被動態調用,這兩者本身就是矛盾的

靜態函數:

靜態函數對於一個類來說隻有一份代碼,所有的成員都共享該份代碼,同內聯函數一樣,靜態函數是在編譯期時就需要確定的,所以靜態函數也不能成為虛函數。






















最後更新:2017-04-03 16:49:07

  上一篇:go 網絡子係統26_設備傳輸的開啟和關閉
  下一篇:go SQL中truncate、delete與drop區別