C++筆試題

1. 運行下面的C++代碼,獲得的結果是什麼?

#include "stdafx.h"
#include<iostream>
using namespace std;

class A {  
private:  
        int m_value;  
   
public:  
        A(int value)  
        {  
                m_value = value;  
        }  
        void Print1() {  
                printf("hello world");  
        }  
        void Print2() {  
                printf("%d", m_value);  
        }  
    virtual void Print3() {  
                printf("hello world");  
        }  
};

int _tmain(int argc, _TCHAR* argv[])
{
	A* pA;
	pA->Print1();
	pA->Print2();
	pA->Print3();

	return 0;
}
複製代碼

答案是:Print1調用正常,打印出hello world,但運行至Print2時,程序崩潰。調用Print1時,並不須要pA的地址,由於Print1的函數地址是固定的。編譯器會給Print1傳入一個this指針,該指針爲NULL,但在Print1中該this指針並無用到。只要程序運行時沒有訪問不應訪問的內存就不會出錯,所以運行正常。在運行print2時,須要this指針才能獲得m_value的值。因爲此時this指針爲NULL,所以程序崩潰了。對於Print3這一虛函數,C++在調用虛函數的時候,要根據實例(即this指針指向的實例)中虛函數表指針獲得虛函數表,再從虛函數表中找到函數的地址。因爲這一步須要訪問實例的地址(即this指針),而此時this指針爲空指針,所以致使內存訪問出錯。ios

2. 運行下面的C++代碼,獲得的結果是什麼?

#include<iostream>
using namespace std;

class A {
public:  
         A()  
        {  
                Print();  
        }  
        virtual void Print() {  
                cout<<"A is constructed."<<endl;
        }  
}; 

class B: public A  
{  
public:  
        B()  
        {  
                Print();  
        }  
        virtual void Print() {  
               cout<<"B is constructed."<<endl; 
        }  
};  

int main() {  
        A* pA = new B();  
        delete pA;  

        return 0;  
}
複製代碼

前後打印出兩行:A is constructed. B is constructed. 調用B的構造函數時,先會調用B的基類A的構造函數。而後在A的構造函數裏調用Print。因爲此時實例的類型B的部分尚未構造好,本質上它只是A的一個實例,他的虛函數表指針指向的是類型A的虛函數表。所以此時調用的Print是A::Print。接着調用類型B的構造函數,並調用Print。此時已經開始構造B,而且虛函數表的指針已指向類B的虛函數表地址,所以此時調用的Print是B::Print。數組

3. 運行下面的C++代碼,輸出是什麼?

class A{  
private:  
        int n1;  
        int n2;  
public:  
        A(): n2(0), n1(n2 + 2)  
        {  
        }  
   
        void Print() {  
                std::cout << "n1: " << n1 << ", n2: " << n2 << std::endl;  
        }  
};  
int _tmain(int argc, _TCHAR* argv[])  
{  
        A a;  
        a.Print();  
        return 0;  
} 
複製代碼

輸出n1是一個隨機的數字,n2爲0。在C++中,成員變量的初始化順序與變量在類型中的申明順序相同,而與它們在構造函數的初始化列表中的順序無關。 所以首先初始化n1,而初始n1的參數n2尚未初始化,是一個隨機值。初始化n2時,根據參數0對其初始化,故n2=0。markdown

4. 編譯運行下面的C++代碼,結果是什麼?(A)編譯錯誤;(B)編譯成功,運行時程序崩潰;(C)編譯運行正常,輸出10。請選擇正確答案並分析緣由。

class A{  
private:  
        int value;  
public:  
        A(int n)  
        {  
                value = n;  
        }  
        A(A other)  
        {  
                value = other.value;  
        }  
        void Print() {  
                std::cout << value << std::endl;  
        }  
};  

int _tmain(int argc, _TCHAR* argv[])  
{  
        A a = 10;  
        A b = a;  
        b.Print();  
        return 0;  
} 
複製代碼

編譯錯誤。在複製構造函數中傳入的參數是A的一個實例。因爲是傳值,把形參拷貝到實參會調用複製構造函數。所以若是容許複製構造函數傳值,那麼會造成永無休止的遞歸併形成棧溢出。所以C++的標準不容許複製構造函數傳值參數,而必須是傳引用或者常量引用。 複製構造函數的參數須要改成:const A& other。函數

5. 運行以下的C++代碼,輸出是什麼?

class A{  
    public:  
       virtual void Fun(int number = 10) {  
           std::cout << "A::Fun with number " << number;  
       }  
    };  
       
    class B:public A  
    {  
    public:  
       virtual void Fun(int number = 20) {  
           std::cout << "B::Fun with number " << number;  
       }  
    };  
       
    int main() {  
       B b;  
       A &a = b;  
       a.Fun();  

	return 0;
    }  
複製代碼

輸出 B::Fun with number 10。因爲a是一個指向B實例的引用,所以在運行的時候會調用B::Fun。但缺省參數是在編譯期決定的。在編譯的時候,編譯器只知道a是一個類型a的引用,具體指向什麼類型在編譯期是不能肯定的,所以會按照A::Fun的聲明把缺省參數number設爲10。這一題的關鍵在於理解肯定缺省參數的值是在編譯的時候,但肯定引用、指針的虛函數調用哪一個類型的函數是在運行的時候this

6. 運行以下的C代碼,輸出是什麼?

char* GetString1() {  
   char p[] = "Hello World";//指向臨時分配的桟空間,當運行至函數體外時,空間將被釋放 
   return p;  
}  
char* GetString2() {  
   char *p = "Hello World";//指向全局常量區 
   return p;  
}  
int _tmain(int argc, _TCHAR* argv[])  
{  
   printf("GetString1 returns: %s. \n", GetString1());  
   printf("GetString2 returns: %s. \n", GetString2());  
   return 0;  
}
複製代碼

輸出兩行,第一行GetString1 returns: 後面跟的是一串隨機的內容,而第二行GetString2 returns: Hello World.兩個函數的區別在於GetString1中是一個數組,而GetString2中是一個指針。運行到GetString1時,p是一個數組,會開闢一塊內存,並拷貝"Hello World"初始化該數組。接着返回數組的首地址並退出該函數。因爲p是GetString1內的一個局部變量,當運行到這個函數外面的時候,這個數組的內存會被釋放掉。所以在_tmain函數裏再去訪問這個數組的內容時,結果是隨機的。運行到GetString2時,p是一個指針,它指向的是字符串常量區的一個常量字符串。該常量字符串是一個全局的,並不會由於退出函數GetString2而被釋放掉。spa

7. 運行下圖中C代碼,輸出的結果是什麼?

int _tmain(int argc, _TCHAR* argv[])  
    {  
       char str1[] = "hello world";//桟空間 
       char str2[] = "hello world";//桟空間,臨時分配,地址不一樣 
       char* str3 = "hello world";//常量區 
       char* str4 = "hello world";//指向同一塊全局常量區 
       if(str1 == str2)  
           printf("str1 and str2 are same.\n");  
       else  
           printf("str1 and str2 are not same.\n");  
       if(str3 == str4)  
           printf("str3 and str4 are same.\n");  
       else  
           printf("str3 and str4 are not same.\n");  
       return 0;  
    }  
複製代碼

這個題目與上一題目相似。str1和str2是兩個字符串數組。咱們會爲它們分配兩個長度爲12個字節的空間,並把"hello world"的內容分別拷貝到數組中去。這是兩個初始地址不一樣的數組,所以比較str1和str2的值,會不相同。str3和str4是兩個指針,咱們無需爲它們分配內存以存儲字符串的內容,而只須要把它們指向"hello world「在內存中的地址就能夠了。因爲"hello world」是常量字符串,它在內存中只有一個拷貝,所以str3和str4指向的是同一個地址。所以比較str3和str4的值,會是相同的。指針

8. 運行Test,輸出結果是什麼?

void Test() {  
    class B {  
    public:  
        B(void)  
        {  
            cout<<"B\t";  
        }  
        ~B(void)  
        {  
            cout<<"~B\t";  
        }  
    };  
    struct C {  
        C(void)  
        {  
            cout<<"C\t";  
        }  
        ~C(void)  
        {  
            cout<<"~C\t";  
        }  
    };  
    struct D : B  
    {  
        D()  
        {  
            cout<<"D\t";  
        }  
        ~D()  
        {  
            cout<<"~D\t";  
        }  
    private:  
        C c;  
    };  
    D d;  
}  
複製代碼

運行結果:B C D ~D ~ C ~B。當實例化D對象時,因爲繼承自B,於是首先調用B的構造函數,以後初始化私有成員C,完成父類的構造與私有成員的初始化後再進入D的構造函數體內;以後,按照相反順序完成對象的析構操做。初始化與賦值是不一樣的,通常初始化是在初始化列表完成的,構造函數體中進行的是賦值操做。code

9. 下列程序輸出結果是什麼?

class A {  
public:  
    int a;//4字節 
    char b;//1字節 
    double c;//8字節,以此爲基本單位進行字節對齊,上面的兩個變量對齊後共爲8字節,加上當前字節數,共爲8+8=16字節。 
    virtual void print()//虛函數,構建虛函數表,虛函數表指針須要4字節,字節對其,擴充爲8字節  {  
        cout<<"this is father's function!"<<endl;  
    }  
    virtual void print1()//地址存於虛函數表  {  
        cout<<"this is father's function1!"<<endl;  
    }  
        virtual void print2()//無需分配內存  {  
            cout<<"this is father's function2!"<<endl;  
        }  
private:  
    float d;//4字節,字節對其,擴充爲8字節 
};  
  
class B : A//首先承載A的大小:32字節 
{  
public:  
    virtual void print()//修改虛函數表地址  {  
        cout<<"this is children's function!"<<endl;  
    }  
    void print1()//僅存有函數入口地址,無需分配內存  {  
        cout<<"this is children's function1!"<<endl;  
    }  
private:  
    char e;//1字節,字節對齊,擴充爲8字節(能夠發現,繼承後,字節對齊單位也放生變化) 
};  
int main(void) {  
    cout<<sizeof(A)<<" "<<sizeof(B)<<endl;  
    system("pause");  
    return 0;  
}  
複製代碼

運行結果:32,40.這個題目解決的關鍵在於掌握字節對齊的相關知識點。具體見上面註釋。orm

10. 如下程序,在編譯與運行時或發生什麼?

class A {  
public:  
    virtual void foo() {  }  
};  
class B {  
public:  
    virtual void foo() {  }  
};  
class C : public A , public B  
{  
public:  
    virtual void foo() {  }  
};  
void bar1(A *pa) {  
    B *pc = dynamic_cast<B*>(pa);//運行期遍歷繼承樹 
}  
void bar2(A *pa) {  
    B *pc = static_cast<B*>(pa);//兩個類無關,編譯出錯 
}  
void bar3() {  
    C c;  
    A *pa = &c;  
    B *pb = static_cast<B*>(static_cast<C*>(pa));//存在繼承關係,編譯正確 
} 
複製代碼

對於bar1,dynamic_cast是在運行時遍歷繼承樹,因此,在編譯時不會報錯。可是由於A和B無繼承關係,因此運行時報錯。static_cast:編譯器隱式執行的任何類型轉換均可由它顯示完成。其中對於:(1)基本類型。如能夠將int轉換爲double(編譯器會執行隱式轉換),可是不能將int用它轉換到double(沒有此隱式轉換)。(2)對於用戶自定義類型,若是兩個類無關,則會出錯,若是存在繼承關係,則能夠在基類和派生類之間進行任何轉型,在編譯期間不會出錯。因此bar3能夠經過編譯。對象

11. 執行下列程序,會發生什麼?

class A {  
    public:  
        string a;  
        void f1() {  
            printf("Hello World");  
        }  
        void f2() {  
            a = "Hello World";  
            printf("%s",a.c_str());  
        }  
        virtual void f3() {  
            a = "Hello World";  
            printf("%s",a.c_str());  
        }  
        static void f4() {  
            printf("Hello World");  
        }  
    };  
      
    int main(void) {  
        A *aptr = NULL;  //建立一個A對象,對象指針爲空,意味着對象僅有空殼,沒法藉助指針訪問成員變量 
        aptr->f1();      //運行成功,調用f1函數僅需函數入口地址,無需訪問對象中的成員變量 
            aptr->f2();      //運行失敗,調用f2需訪問成員變量 
            aptr->f3();      //運行失敗,同上 
            aptr->f4();      //靜態成員不屬於任何對象,運行成功 
        return 0;  
    }  
複製代碼

此題解答如程序註釋所示。

12. 下列函數運行狀況如何?

int func() {  
    char b[2]={0};  
    strcpy(b,"aaa");  
}
複製代碼

Debug版崩潰,Release版正常。由於在Debug中有ASSERT斷言保護,因此要崩潰,而在Release中就會刪掉ASSERT,因此正常運行。可是不推薦這麼作,由於這樣會覆蓋不屬於本身的內存。

相關文章
相關標籤/搜索