1. 程式人生 > >C++筆試題(1),持續更新中……

C++筆試題(1),持續更新中……

重拾C++經典筆試題(30+)

31.標頭檔案的作用是什麼? 1、通過標頭檔案來呼叫庫功能。在很多場合,原始碼不便(或不準)向用戶公佈,只要向用戶提供標頭檔案和二進位制的庫即可。使用者只需要按照標頭檔案中的介面宣告來呼叫庫功能,而不必關心介面怎麼實現的。編譯器會從庫中提取出相應的程式碼。
2、標頭檔案能加強型別安全檢查。如果某個介面被實現或被使用時,其方式與標頭檔案中的宣告不一致,編譯器就會指出錯誤,這一簡單的規則能大大減輕程式設計師除錯、改錯的負擔。
32. 解釋 static 關鍵字的用途,越多越好

static關鍵字至少有下列作用:

1)函式體內static 變數的作用範圍為該函式體,不同於

auto 變數,該變數的記憶體只被分配一次,因此其值在下次呼叫時仍維持上次的值;

2)在模組內的static 全域性變數可以被模組內所有函式訪問,但不能被模組外其它函式訪問;

3)在模組內的static 函式只可被這一模組內的其它函式呼叫,這個函式的使用範圍被限制在宣告它的模組內;

4)在類中的static 成員變數屬於整個類所擁有,對類的所有物件只有一份拷貝;

5)在類中的static 成員函式屬於整個類所擁有,這個函式不接收this指標,因而只能訪問類的static成員變數。

32. 解釋 const 關鍵字的用途,越多越好

const關鍵字至少有下列作用:

1)欲阻止一個變數被改變,可以使用

const關鍵字。在定義該const變數時,通常需要對它進行初始化,因為以後就沒有機會再去改變它了;

2)對指標來說,可以指定指標本身為const,也可以指定指標所指的資料為const,或二者同時指定為const

3)在一個函式宣告中,const可以修飾形參,表明它是一個輸入引數,在函式內部不能改變其值;

4)對於類的成員函式,若指定其為const型別,則表明其是一個常函式,不能修改類的成員變數;

5)對於類的成員函式,有時候必須指定其返回值為const型別,以使得其返回值不為左值

33.在C++程式中呼叫被C編譯器編譯後的函式,為什麼要加extern “C”? c++語言支援函式過載,C語言不支援函式過載。函式被C++編譯後在庫中的名字與C語言的不同。
假設某個函式的原型為:void fee(int x,inty);
該函式被C編譯器編譯後在庫中的名字為_foo,而C++編譯器則會產生像_foo_int_int之類的名字,C++提供了C連線交換指定符號extern “C”來解決名字匹配的問題. 33.巨集和 inline 的區別

使用巨集和行內函數都可以節省在函式呼叫方面的時間和空間開銷。二者都是為了提高效率,但是卻有著顯著的區別:

(1)、在使用時,巨集只做簡單的前處理器符號表(字串)中的簡單替換。而行內函數可以進行引數型別檢查,且具有返回值(也能被強制轉換為可轉換的合適型別)。
(2)、行內函數首先是函式,函式的許多性質都適用於行內函數(如行內函數可以過載)。
(3)、行內函數可以作為某個類的成員函式,這樣可以使用類的保護成員和私有成員。而當一個表示式涉及到類保護成員或私有成員時,巨集就不能實現了(無法將this指標放在合適位置)。

可以用行內函數完全替代巨集。但是在使用行內函數時也要注意:作為行內函數,函式體必須十分簡單,不能包含迴圈,條件,選擇等複雜結構,否則不能作為行內函數。實際上,編譯器的優化系統會自動將

34.記憶體思考題

void GetMemory(char *p){

 p = (char *)malloc(100);

}
void Test(void)
{
   char *str = NULL;

   GetMemory(str);

   strcpy(str,”hello world”);

   printf(str);
}

請問Test函式會有什麼樣的結果?函式內的變數是獨立於main的,對其改變不會影響main的變數。程式會崩潰,因為GetMemory並不能傳遞動態記憶體,Test函式中的str一直是NULL。Strycpy(str,”hello world”);將使程式崩潰。

char *GetMemory(void)
{
        char p[] = “hello world”;
        return p;
}
void Test(void)
{
        char *str = NULL;
        str = GetMemory();
        printf(str);
}
請問Test函式會有什麼樣的結果?亂碼。因為GetMemory返回的是指向”棧記憶體”的指標,該指標的地址不是NULL,但其原現的內容已經被清除,新內容不知。
void GetMemory(char **p,int num)
{
        *p = (char *)malloc(num);
}
void Test(void)
{
        char *str = NULL;
        GetMemory(&str,100);
        strcpy(str,”hello”);
        printf(str);
}

請問執行Test函式會有什麼樣的結果?能夠輸出hello,記憶體洩露。

void Test(void)
{
 char *str = (char *) malloc(100);
 strcpy(str,”hello”);
 free(str);
 if(str != NULL)
 {
    strcpy((str,”world”);
    printf(str);
 }
}
請問執行Test函式會有什麼樣的結果?篡改動態記憶體區的內容,後果難以預料,非常危險。因為free(str);之後,str成為野指標,if(str!=NULL)語句不起作用。

重拾C++經典筆試1-10

1.    統計10進位制轉化為2進位制1的個數

int total2Cnts(int x)  
{  
         int count = 0;  
         while(x)  
         {  
              ++count;  
              x = x&(x-1);  
         }  
         cout << endl;  
         return count;  
}  
int main()  
{  
         int num = 9999;  
         for( int i = 0; i < 100; i++)  
         {  
              cout << i <<"中1的個數為:" <<total2Cnts(i) << endl;  
         }  
         return 0;  
}

2. 浮點轉換

int main()  
{  
         float a = 1.0f;  
         cout << (int)a << endl;  
         cout << &a << endl;  
         cout << (int&)a <<endl;  
         cout << boolalpha << ((int)a == (int&)a ) << endl; //false  
   
         float b = 0.0f;  
         cout << (int)b << endl;  
         cout << &b << endl;  
         cout << (int&)b <<endl;  
         cout << boolalpha << ((int)b == (int&)b ) << endl; //true  
         return 0;  
}
問題1:(int&)a中(int&)的確切含義是什麼?(int&)a 等價於*(int*)&a;

問題2:浮點數的記憶體和整數的儲存方式不同;int(&a) 相當於該浮點地址開始的sizeof(int)個位元組當成int型的資料輸出,這取決於float型資料在記憶體中的儲存方式(這是關鍵),而不是經過int(&a)顯示轉換的結果1。

3. 位運算

#include<iostream>  
usingnamespace std;  
intmain()  
{  
    char a = 'a';  
    char b = ~a;  
    cout<<sizeof(a)<<endl;                //1  
    cout << typeid(a).name() <<endl;        // char  
    cout<<sizeof(b)<<endl;                //1  
    cout << typeid(a).name() <<endl;        // char  
    cout<<sizeof(~a)<<endl;                  //4  
    cout << typeid(~a).name() <<endl;        // int  
    cout<<sizeof(a&b)<<endl;                //4  
    cout << typeid(a&b).name()<< endl;        // int  
    return 0;  
} 

4. coutprintf在多執行緒中的區別和影響

int g_cnt;  
unsignedint _stdcall ThreadProc1(PVOID lpParameter)  
{  
         g_cnt++;  
        printf("subThreadis running! g_cnt = %d\n",g_cnt);  
//       cout << "subThread is running!g_cnt = " << g_cnt << endl; //此處和printf列印在多執行緒操作中的區別?  
         return 0;  
}  
int main()  
{  
         g_cnt = 0;  
         const int nThreadNum = 5;  
         HANDLE hThread1[nThreadNum];  
         //Caution...!  
         for( int i=0; i < nThreadNum; i++)  
         {  
                   hThread1[i]=(HANDLE)_beginthreadex(NULL,0,ThreadProc1,NULL,0,NULL);  
         }        
         WaitForMultipleObjects(nThreadNum,hThread1,true,INFINITE);  
   
         _endthreadex(0);  
         return 0;  
}  
問題:寫多執行緒上述操作最基本例項的時候,發現了用printf能正常逐行列印輸出,而用cout<<輸出流則出現並行輸出的現象。
原因:一個是C庫(printf),一個是C++庫(cout)。兩者的緩衝方式有所不同,而且使用相互獨立的緩衝區。printf是傳統的行緩衝,cout則是基於字元的緩衝。注意同一個程式中不要混合兩種輸出方式,有可能導致輸出混亂。

5.     位運算

X&Y指取出X與Y的相同位;

X異或Y取出X與Y的不同位;

X右移1等價於X除以2。

X左移1等價於X乘以2.

//取出a,b中較大者。

int maxFun(int a, int b)  
{  
         return (((a+b) + abs(a-b))/2);  
} 

上式簡化為:若a>b,((a+b)+(a-b))/2= a; 若a<b,((a+b)-(a-b))/2=b.顯然才有了絕對值一說。

//a,b交換操作  
voidexchange(int& a, int& b)  
{  
         a = a^b;  
         b = a^b;  
         a = a^b;  
} 

6.     求解結構體偏移量的方法?

#define FIND(STRUC, e)(size_t)&(((STRUC*)0)->e)  
structstudent  
{  
         int a;  
         char b[20];  
         double c;  
};//32  
   
intmain()  
{  
         cout << FIND(student,a) <<endl; //0  
         cout << FIND(student,b) <<endl; //4  
         cout << FIND(student,c) <<endl; //24  
          
         cout << sizeof(student) <<endl; //32  
         return 0;  
}  
#define FIND(STRUC, e)(size_t)&(((STRUC*)0)->e)

解讀:

第一步,((TRUC*)0)將0轉化為STRUC*指標所指向的地址;

第二步,&(((STRUC*)0)->e)結構體指標成員e的地址,由於結構體的首地址為0,&(((STRUC*)0)->e)即為e距離結構體成員的偏移量。

第三步,強制型別轉換,最終求的是偏移值,最好定義為無符號資料。size_t 等價於 unsigned int

7.     注意以下的表示方法!

#define SECONDS_PER_YEAR 365UL*24UL*3600UL用法值得商榷!

#defineMIN(a,b) ((a)<=(b) ? (a):(b))

8.      constdefine對比?

const

define

1. 是否具有型別?

有型別

沒有型別

2. 是否進行安全檢查?

編譯器有安全檢查

僅是字元替換

3.        是否可除錯?

可除錯

不可除錯

行內函數與define巨集定義對比?

優點

缺點及注意點

Define巨集定義

1.提高了執行效率

1.不可除錯,無法進行安全檢查(型別)

2.可能出現邊際效應導致出錯。

3.不能操作類的私有資料成員。

行內函數

1提高效率及安全性;

2編譯器可以用上下文優化技術繼續對結果程式碼優化。

1每一次內聯處都要拷貝程式碼,使程式總工作量大;

2.短小、 簡單函式設為內聯(重複呼叫,無switch、for、while)等語句;

3.不要將構造、解構函式設定為行內函數

9.     不常用的mutalbe關鍵字

[MSDN]mutable

C++Specific —>mutable member-variable-declaration;

Thiskeyword can only be applied to non-static and non-const data members of aclass. If a data member is declared mutable, then it is legal to assign a valueto this data member from a const member function.

解讀:mutable成員變數的宣告,這個關鍵字只能應用於類的非靜態與非const資料成員。如果一個數據成員宣告為mutable,那麼通過const成員函式給資料成員分派一個值是合法的。

[作用]:加mutable關鍵字的成員變數,修飾為const的成員函式就可以修改它了。

不加mutable,會報錯:l-value specifies const object

classmut ClS  
{  
public:  
         mutClS(int nx):m_x(nx) {}  
         ~mutClS(){}  
         void increase( int incx) const  
         {  
                   m_x += incx;  
         }  
         void decrease(int decx) const  
         {  
                   m_x -= decx;  
         }  
         void display()  
         {  
                   cout << m_x <<endl;  
         }  
private:  
         mutable int m_x; //注意此處!  
};  
   
intmain()  
{  
         mutClS objA(35);  
         objA.increase(5);  
         objA.display();  
   
         objA.decrease(5);  
         objA.display();  
   
         return 0;  
}  

10.  C++物件的記憶體佈局

先看一下以下程式佔用記憶體的大小,即:sizeof(simpleClass)=?

class simpleClass  
{  
public:  
         simpleClass(){}  
         virtual ~simpleClass() {}  
   
         int getValue(){}  
         virtual void fool(){}  
         static void addCount(){}  
   
         static int nCount;  
         int nValue;  
         char c;  
};
該simpleClass類中含有構造、析構、靜態成員函式、虛擬函式、普通成員函式;靜態成員變數、普通成員變數。

分析:

類別

型別

儲存類別

佔記憶體情況

資料成員

static int nCount;

全域性/靜態儲存區

不作為物件佔據記憶體的一部分

int nValue;

char c;

非靜態資料成員

棧儲存區

根據地址對齊,二者佔8位元組空間。

成員函式

static void addCount(){}

C++編譯器採用普通與C函式類似的方式進行編譯,只不過對函式進行了名字修飾(name mangling),用來支援過載;並且在引數列增加了一個this指標,用來表明哪一個物件呼叫的該函式。

靜態和非靜態成員函式的多少對物件的大小沒有影響。

int getValue(){}

構造、解構函式、拷貝構造

virtual void fool(){}

C++編譯器在碰到虛擬函式的類時,會分配一個指標指向一個函式地址表,叫做“虛擬函式表”。

佔4個位元組,虛擬函式表指標佔據的4個位元組。

 看下面註釋的結果值,再分析:

int main()  
{  
         simpleClass aSimple;  
         cout << "Object startaddress:\t" << &aSimple << endl; //0012FF68  
         cout << "nValueaddress:\t" << &aSimple.nValue << endl; //0012FF6C  
         printf("c address: %x\n",&aSimple.c); //0012FF70  
         cout << "size: "<< sizeof(simpleClass) << endl; //12  
         return 0;  
}

&aSimple= 0012FF68;即虛擬函式表指標佔據的一個物件開始的4個位元組。

結論如下:

(1)非靜態資料成員是影響物件佔據記憶體的主要因素,隨著物件數目的增多,非靜態資料成員佔據的記憶體也會相應增加。

(2)所有的物件共享一份靜態資料成員,所以靜態資料成員佔據的記憶體的數量不會隨著物件數目的增加而增加。

(3)靜態成員函式和非靜態成員函式不會影響物件記憶體的大小,雖然其實現會佔據相應的記憶體空間,同樣也不會隨著物件數目的增加而增加。

(4)如果物件中包含虛擬函式,會增加4個位元組的空間,不論有多少個虛擬函式。

擴充套件一:如果在simpleClass的基礎上增加繼承類,如下:繼承類所佔記憶體大小是多少?

class derivedClass :public simpleClass  
{  
public:  
         derivedClass(){}  
         ~derivedClass(){}  
         int nSubValue;  
};

答案:16個位元組,派生類derivedClass與其基類simpleClass使用的是同一個虛擬函式表。或者說派生類在構造時,不再建立一個新的虛擬函式表,而應該是在基類的虛擬函式表中增加或修改。

擴充套件二:空類的大小,以及單繼承、多繼承,虛擬繼承後的空類大小。

class A  
{  
   
};  
class B  
{  
};  
   
class C : public  A  
{  
};  
   
class D : virtualpublic  B //4  
{  
};  
   
class E : public  A, public B  
{  
};  
   
int main()  
{  
         cout << sizeof(A) << endl; //1  
         cout << sizeof(B) << endl; //1  
         cout << sizeof(C) << endl; //1  
         cout << sizeof(D) << endl; //4[涉及虛擬繼承(虛指標)]  
         cout << sizeof(E) << endl; //1  
         return 0;  
}
擴充套件三:為了避免出現菱形問題,用使用虛擬繼承後的子類大小。示例如下:
class baseClass  
{  
public:  
         virtual void fool(void) {}  
         int nValue;  
         char c;  
};  
   
class midClass1 : virtualpublic baseClass  
{  
public:  
         virtual void setVal(){}  
         int nMidValue1;  
};  
   
class midClass2 : virtualpublic baseClass  
{  
public:  
         virtual void setVal(){}  
         int nMidValue2;  
};  
   
class derivedClass :  public midClass1,  public midClass2  
{  
public:  
         virtual void foo2(){}  
         int subVal;  
};  
int main()  
{  
         cout << sizeof(baseClass) << endl; //12  
         cout << sizeof(midClass1) << endl; //24  
         cout << sizeof(midClass2) << endl; //24  
         cout << sizeof(derivedClass) << endl; //48  
         return 0;  
} 
已經知道的,對於baseClass類的大小,考慮地址對齊為4(c)+4(nvalue)+4(虛擬函式指標)共12個位元組;

如果去掉虛擬繼承,為如下形式:

class midClass1 : publicbaseClass //僅是增加了nMidValue1,擴充套件為16位元組

class midClass2 : publicbaseClass //僅是增加了nMidValue2,擴充套件為16位元組

classderivedClass :  public midClass1,  public midClass2 //在繼承midclass1,midclass2基礎上僅是增加了subVal,為16+16+4=36位元組。

不理解點:為什麼加了虛擬繼承,sizeof(midClass1)= 24;sizeof(midClass2)=24;sizeof(derivedClass)48;

主要原因,VisualC++添加了了虛擬基類表指標來實現虛擬繼承,類中只要有visual函式就會產生這個vtb 虛擬函式表和一個vptr虛擬函式指標,它們都會佔記憶體的。

具體為什麼增加了8個位元組,希望與大家探討!

【已解決】主要原因,VisualC++添加了了虛擬基類表指標來實現虛擬繼承,因此,空間變大?實際怎麼多了8個位元組。。
      解讀:baseClass類包含1個虛擬函式表指標(4個位元組)、1個int型資料成員(4個位元組)、1個char型資料(對齊後4個位元組)成員,共12個位元組。
      midClass1同midClass2一致,需要在baseClass類的基礎上,多了1個虛擬函式表指標(4個位元組)、1個指向虛基類表的指標(4個位元組)、一個整形資料成員(4個位元組),合計共12+12 =24個位元組。
derivedClass 在上述的基礎上,包含baseClass(12個位元組)、midClass1(新增12個位元組)、midClass2(新增12個位元組)、derivedClass的1個整形資料成員(4個位元組),合計共40個位元組。注意derivedClass是繼承而非虛擬繼承自兩個父類,所以沒有指向虛基類表的指標。
      擴充套件,如果將上述繼承該為:class derivedClass : virtual public midClass1, virtual public midClass2.上述大小會變為48個位元組(多了兩個指向虛基類表的指標(每個4個位元組))。

重拾C++經典筆試提(11-20

11.     C++物件模型基本概念之程式使用記憶體區

計算機程式主要由程式碼+資料組成,兩部分是影響一個程式所需記憶體的重要因素。

資料區儲存分類

儲存內容

全域性/靜態資料區

全域性變數及靜態變數(全域性靜態變數、區域性靜態變數)

常量資料區

儲存程式中的常量字串等

儲存自動變數或區域性變數,以及傳遞的函式引數等

使用者控制的儲存區,儲存動態產生的資料

程式碼區

程式中程式碼

12.     不被重視的sizeof()大小問題。

(1)情況一

int a = 8;

         cout << sizeof(a=6) <<endl; //a=6是不被編譯的,只是轉換為a的型別。

        cout<< a << endl; //8

注意:sizeof(a=6)在編譯過程中是不被翻譯的,而是被替代型別。

(2)情況二,求函式大小等價於其對應返回值的大小。

int fun1()  
{  
         return 0;  
}  
void fun2()  
{  
   
}  
char fun3()  
{  
         return 'c';  
}  
double fun4()  
{  
         return 0.0;  
}  
         cout << sizeof(fun1()) << endl; //4  
        cout << sizeof(fun2()) << endl; //error C2070:illegal sizeof operand**  
         cout << sizeof(fun3()) << endl; //1  
        cout<< sizeof(fun4()) << endl; //8 

(3)情況三,求陣列大小。

         char c[2][3] = {"a",""};

         cout << sizeof(c) << endl; //2*3*1

(4)情況四,括號、給陣列大小賦值(主要原因,編譯的時候已經計算過sizeof大小了)。

int na = 35;  
      //等價於sizeof(na), 如果是變數名,可以不加括號。  
      cout << sizeof na << endl;  
      cout << sizeof(int)<< endl;  
  
      int nArray[25] = {0};  
      //可以通過sizeof( )定義陣列的大小,等價於new int[100].  
      int *pArray = new int[sizeof(nArray)];   

13.     深究結構體地址對齊的原則及應用例項

struct simpleA  
{  
         float f; //0  
         char p; //4  
         int adf[3]; //5—>8[按照基本資料型別對齊,第一個對齊了後面的自然也就對齊了]  
};//8+12-->20 

這裡有三點很重要:

(1)每個成員分別按自己的方式對齊,並能最小化長度

(2).複雜型別(如結構)的預設對齊方式是它最長的成員的對齊方式,這樣在成員是複雜型別時,可以最小化長度;

(3).對齊後的長度必須是成員中最大的對齊引數的整數倍,這樣在處理陣列時可以保證每一項都邊界對齊

補充一下,對於陣列,比如:char a[3];這種,它的對齊方式和分別寫3個char是一樣的。也就是說它還是按1個位元組對齊。

如果寫: typedef char Array3[3];Array3這種型別的對齊方式還是按1個位元組對齊,而不是按它的長度。不論型別是什麼,對齊的邊界一定是1,2,4,8,16,32,64....的一個。

 總結如下:

陣列 :按照基本資料型別對齊,第一個對齊了後面的自然也就對齊了。

聯合 :按其包含的長度最大的資料型別對齊。

結構體: 結構體中每個資料型別都要對齊。

14.     注意此處錯誤的原因

void swap1(int* p , int* q)  
{  
/*      int a = 0; 
//       int* temp;  //單純的temp會出現temp' used without having beeninitialized 
         int* temp = &a; 
  
         *temp = *p; 
         *p = *q; 
         *q = *temp; */  
   
         //等價於下面  
         int temp = *p;  
         *p = *q;  
         *q = temp;  
}

給隨機地址賦值,函式結束的時候不回收,會造成記憶體洩露。

15.     地址相減計算

int main()  
{  
         int a[3];  
         a[0] = 0;  
         a[1] = 1;  
         a[2] = 2;  
   
         int *p, *q;  
         p = a;  
         q = &a[2];  
   
         cout << p << endl;  
         cout << q << endl;  
          
         //q-p等價於[(q的地址值-p的地址值)/sizeof(int)].  
         cout << q-p << endl; //2  
         cout << a[q-p] << endl;//a[2] = 2;  
   
         return 0;  
}

16.     為什麼是1

classA  
{  
public:  
         A() { m_a = 1, m_b =2; }  
         ~A(){}  
         void fun() { printf("%d \t %d\n", m_a, m_b); }  
         int m_a;  
         int m_b;  
};  
   
classB  
{  
public:  
         B() { m_c = 3;}  
         ~B(){}  
         void fun() { printf("%d\n",m_c); } //為什麼是1,思考!  
         int m_c;  
};  
intmain()  
{  
         A a;  
         B *pb = (B*)&a;  
         pb->fun();                //為什麼是1,思考!  
          
         cout << &a <<endl;       //12FF6C  
         cout << &(a.m_a) <<endl; //12FF6C  
   
         printf("%08x\n",&A::m_a);  
         printf("%08x\n",&A::m_b);  
         printf("%08x\n",&B::m_c);  
   
         return 0;  
}

17.     一個含有10個指標的陣列,該指標指向一個函式,該函式有一個整形引數並返回一個整形數。

int(*p[10])(int)

18.      有了malloc/free為什麼還用new/delete?

1)malloc/free為C/C++標準庫函式;new/delete為C++運算子。他們都可以申請和釋放動態記憶體。

2)只用malloc/free無法滿足非內部資料型別的要求;物件在建立的時候自動呼叫建構函式,在銷燬的時候自動呼叫解構函式;而malloc/free是庫函式而不是運算子,不再編譯器控制權限之內,所以不能把呼叫建構函式和解構函式的任務強加給它們。

19.      注意下列的取值

int main()  
{  
         int a[] = {1,2,3,4,5};  
         int *ptr = (int*)(&a+1); //1代表1個sizeof(a)  
         printf("%d %d\n",*(a+1),*(ptr-1));//2 , 5  
         return 0;  
}  
   
int main()  
{  
         char* a[] ={"hello","the","word"};  
         char** pa = a; //pa為指向字串陣列的指標.  
         pa++;  
         cout << *pa << endl;  //the  
   
         cout << *pa[0] << endl; //t  
         cout << *(*pa+1)<< endl;//h  
         cout << *(*pa+2) <<endl;//e  
         return 0;  
} 

20.    題解:深拷貝+淺顯拷貝,為什麼?

通俗解釋深、淺拷貝:

深拷貝是指源物件與拷貝物件互相獨立,其中任何一個物件的改動都不會對另外一個物件造成影響。舉個例子,一個人名叫張三,後來用他克隆(假設法律允許)了另外一個人,叫李四,不管是張三缺胳膊少腿還是李四缺胳膊少腿都不會影響另外一個人。

淺拷貝是指源物件與拷貝物件共用一份實體,僅僅是引用的變數不同(名稱不同)。對其中任何一個物件的改動都會影響另外一個物件。舉個例子,一個人一開始叫張三,後來改名叫李四了,可是還是同一個人,不管是張三缺胳膊少腿還是李四缺胳膊少腿,都是這個人倒黴。

如果在類中沒有顯式地宣告一個拷貝建構函式,那麼,編譯器將會自動生成一個預設的拷貝建構函式,該建構函式完成物件之間的位拷貝。位拷貝又稱淺拷貝

class CA  
{  
public:  
CA(int b,char* cstr)  
{  
           cout << "CAconstructor!" << endl;  
           a=b;  
           str=new char[b];  
           strcpy(str,cstr);  
}  
void Show()  
{  
           cout<<str<<endl;  
}  
~CA()  
{  
           cout << "~CAconstructor!" << endl;  
           delete str;  
}  
   
private:  
int a;  
char *str;  
};  
   
int main()  
{  
CA A(10,"Hello!");  
CA B=A; //此處會呼叫預設的拷貝建構函式,是為淺拷貝。  
B.Show();  
//淺拷貝後A,B物件的str字串是同一個地址。當發生析構時會出現執行報錯!  
return 0;  
}

自定義拷貝建構函式是一種良好的程式設計風格,它可以阻止編譯器形成預設的拷貝建構函式,提高原始碼效率。

//對比淺拷貝,以下是深拷貝。

#include"stdafx.h"  
#include<iostream>  
usingnamespace std;  
   
class CA  
{  
public:  
         CA(int b,char* cstr)  
         {  
                   cout << "CAconstructor!" << endl;  
                   a=b;  
                   str=new char[b];  
                   strcpy(str,cstr);  
         }  
         //自定義拷貝建構函式  
        CA(constCA& C)  
        {  
                   a=C.a;  
                  str=newchar[a]; //深拷貝  
                  if(str!=0)  
                  strcpy(str,C.str);  
        }  
         void Show()  
         {  
                   cout<<str<<endl;  
         }  
         ~CA()  
         {  
                   cout << "~CAconstructor!" << endl;  
                   delete str;  
         }  
   
private:  
         int a;  
         char *str;  
};  
   
int main()  
{  
         CA A(10,"Hello!");  
         CA B=A;  
         B.Show();  
         return 0;  
}

深拷貝和淺拷貝的定義可以簡單理解成:如果一個類擁有資源(堆,或者是其它系統資源),當這個類的物件發生複製過程的時候,這個過程就可以叫做深拷貝,反之物件存在資源,但複製過程並未複製資源的情況視為淺拷貝。

淺拷貝資源後在釋放資源的時候會產生資源歸屬不清的情況導致程式執行出錯

重拾C++經典筆試題(21-30)

21.   為什麼Delete會出錯?

class CBase  
{  
public:  
         CBase() { cout <<"CBase" << endl; }  
         virtual ~CBase() { cout <<"~CBase" << endl;}  
};  
   
classCDerived : public CBase  
{  
public:  
         CDerived() { cout <<"CDerived" << endl; }  
         ~CDerived() { cout <<"~CDerived" << endl; }  
};  
   
int main()  
{  
         CBase base;  
         CBase* pBase = new CBase;  
         pBase = &base;  
         delete pBase; //執行時報錯!  
}


【分析如下】:

1.pBase指向了棧區記憶體,那是系統管理的空間,不能用delete釋放的。

2.程式在堆區new的空間最後沒有被釋放,造成了記憶體洩露。

3.最好不要隨便把申請到堆區空間的指標指向別處,至少也要有一個指標指向申請的空間。以便最後釋放的是自己申請的那塊記憶體。

【修正後做法】:

int main()  
{  
    CBase base;  
    CBase* pBase = new CBase;  
    CBase* pBase2 = pBase;   //至少也要有一個指標指向申請的空間  
    pBase = &base;  
    delete pBase2;  //以便最後釋放的是自己申請的那塊記憶體。  
} //執行時不再報錯!

【再深入點】:程式有兩個問題:

1.記憶體洩露,new出來的沒delete;

2.兩次析構;base不是new出來,在生命週期結束(也就是你函式結束的時候)會自動釋放,你主動呼叫delete將其析構,系統在函式結束時又會對其析構,所以才會報錯。而且報錯的地方應該是程式退出時

22.   類中靜態常成員變數的定義?

#include<iostream>  
usingnamespace std;  
//可以在類的宣告中對常量的類變數進行賦值  
//VS2008可以,vc6.0不可以。和編譯器有關。  
class myclass  
{  
public:  
         static const int i=20; //只有類的靜態常量資料成員才可以在類中初始化。  
};  
const int myclass::i = 10;  
int main()  
{  
         cout<<myclass::i<<endl;  
         return 0;  
}

23.  過載和多型的關係?

不同點

過載overload

覆蓋override

1.是否支援多型?

不支援

支援

2.存在形式?

可以在類中或在C++語言中都可以體現

存在於類中父類、子類之間。

3.引數列表、返回值

引數列表或返回值不同,或二者都不同。

引數列表、返回指標必須相同。

24.  輸出格式:printf