[學習筆記] C++ 歷年試題解析(二)--程序題

發現程序題也挺有價值的。ios

順便記錄下來幾道。函數

 

1.題目

#include <iostream>
#include <cstring>
using namespace ①  std    ;

void Swap(char * const str1, char * const str2)
{    // 形參爲鎖定目標的指針(即指針常量,亦即指針的指向不能改變,內容可更改)
    int n = strlen(②  str1    ) + 1;
    char *temp = new char[③ n   ];
    ④  strcpy(temp, str1)     ;
    ⑤  strcpy(str1, str2)     ;
    ⑥  strcpy(str2, temp)     ;
    delete [] temp;
}

void Exchange(const char *&str1, const char *&str2)
{                    // 形參不使用二級指針,而使用指針的常引用(常量指針的別名)const     char *temp;
    ⑧  temp = str1             ;
    ⑨  str1 = str2             ;
    ⑩  str2 = temp             ;
}

int main()
{
    char str1[80] = "Tom", str2[80] = "Jerry";
    const char *p1 = "Tom", *p2 = "Jerry";
    cout << str1 << '\t' << str2 << endl;
    cout << p1 << '\t' << p2 << endl;
    Swap(str1, str2);
    Exchange(p1, p2);
    cout << str1 << '\t' << str2 << endl;
    cout << p1 << '\t' << p2 << endl;
    return 0;
}

 

解析:

這題應該是要搞清楚const char *p; 和 char * const p;this

const char *p能夠這麼理解,const後面是char*,因此char*對應的內容就不能改變;而char* const p的const後面是p,因此p的值不能改變,p的值是一個指針地址。綜上,緊接着const後面的內容就是不可改變的內容。這下應該能夠作題了。spa

swap函數的const後跟的是指針,因此指針地址不能改變,而其指向地址對應的值是能夠改變的,因此,咱們只能想辦法改變str1和str2指向的值,因爲能夠調用strcpy進行賦值,中間變量天然就是對應的char * const temp,這裏圖中沒用const也能夠,但我感受要對應,仍是加上const 最好。固然,我已經改過以後跑過了。個人理解是對的。操作系統

exchange函數的const後跟的是char*,這就意味着字符串的值是不能改變的,那麼咱們就只能交換兩個別名對應的地址了,注意不要搞混,別名的綁定是不能改變的,咱們只能改變別名對應的地址。臨時變量也應該對應 const char* temp;這樣就對了。指針

 

2.題目

#include <iostream>
using namespace std;

class ctest
{
public:
    ctest(int x=0) : a(x) { cout << "構造對象(" << a << ")\n"; }
    ~ctest(){ cout << "析構對象(" << a << ")\n";}
    ctest & Add()                                // 本成員函數爲引用返回
    {
        ++a;
        return *this;
    }
    ctest add()                                    // 本成員函數爲值返回
    {
        ctest temp(*this);
        ++a;
        return temp;
    }
    friend ostream & operator<<(ostream &out, const ctest &c)
    {
        out << c.a;
        return out;
    }
private:
    int a;
};
int main()
{
    ctest a(100), b(200);
    cout << a << ", " << b << endl;    // 第3行輸出
    a.Add().Add();
    b.add().add();                            // 拷貝構造臨時無名對象時無輸出
    cout << a << ", " << b << endl;    // 第6行輸出
    a.~ctest();                                // 主動調用析構函數,並不銷燬對象
    b.~ctest();                                // 主動調用析構函數,並不銷燬對象
    cout << a << ", " << b << endl;    // 對象a,b仍可被訪問。第9行輸出
    cout << "返回操做系統。" << endl;    // 第10行輸出
    return 0;
}
/*
構造對象(100)                                                        
構造對象(200)                                                        
100, 200                                                            
析構對象(200)                                                        
析構對象(201)                                                        
102, 201                                                            
析構對象(102)                                                        
析構對象(201)                                                        
102, 201                                                            
返回操做系統。                                                        
析構對象(201)                                                        
析構對象(102)    
*/

解析:

這個題考輸出,要注意的是無名對象的析構問題,也就是第四和第五行的輸出,咱們來逐行分析代碼,首先建立a和b對象,分別初始化爲100和200,這時候會調用二者的構造函數,所以輸出兩行構造函數該輸出的東西。而後輸出a和b的值,「100,200」,再往下執行,調用a.Add()函數,是引用返回,可是返回是其自己,期間沒有建立中間變量,也就是說a.Add執行完了以後返回的結果仍是a,而後後面就再執行一個a.Add,這時候返回的仍是a,可是a對象的成員值自增了兩次,已經變成102了,期間沒有對象銷燬,所以沒有析構,沒有輸出。再往下執行,調用b.add函數,根據函數定義咱們知道,函數執行過程當中先拷貝構造一個temp對象,可是沒有寫拷貝構造函數,所以不輸出任何內容,且默認是淺拷貝,而後b的成員值自增變爲201,但這時候temp的成員值仍是200,由於拷貝過去以後會從新分配空間,因此temp的成員值和b的成員值不是一個地址,而後返回temp,且是值返回,值返回以後其實temp尚未析構,而是做爲一個無名變量繼續使用,而後這個無名變量再調用add函數,致使的是temp對象的成員值+1=201,而這時候就已經和對象b無關了。可是要注意的是,因爲b調用的add是值返回,b.add().add()結束以後呢,兩個無名變量就沒用了,就會被銷燬(以前講過,無名變量在語句結束以後被銷燬,是整條語句),因此析構兩個無名對象,無名對象的析構順序又和正常對象的析構順序恰好相反(這個我專門查了資料),因此是依次析構,所以輸出的是200和201,這裏惟一要理解的是,值返回的對象的析構時間並非出了函數體,而是執行完調用這個函數的語句,才被析構。再往下走,輸出a和b的成員值,a是102,b是201,而後a主動析構,輸出「析構對象102」,而後b主動析構,輸出「析構對象201」,注意,這裏是主動析構,析構函數裏面除了輸出什麼也沒有,只是至關於調用了一個輸出函數。下一步輸出a、b的成員值,仍是102和201,而後輸出返回操做系統。下一步結束main函數,這時候纔是a、b真正的析構時間,b先析構,而後是a。就很簡單了。code

 

3.題目

#include <iostream>
using namespace std;
class Base                                            // 基類
{
public:
    Base(int x=0) : a(x)
    {
        cout << "構造基類對象(" << a << ")\n";
    }
    Base(const Base &b) : a(b.a)
    {
        cout << "拷貝構造基類的對象(" << a << ")\n";
    }
    virtual ~Base()
    {
        cout << "析構基類對象(" << a << ")\n";
    }
protected:
    int a;
    static int num;                                 // 靜態數據成員
};
int Base::num = 0;                                 // 靜態數據成員定義及初始化
class Derived : public Base                    // 派生類
{
public:
    Derived(int x=0, int y=0) : Base(x), b(y)
    {                        // 構造函數中輸出了對象個數[num],在園括號以前
        cout << "構造派生類的對象["
              << ++num << "](" << a << ", " << b << ")\n";
    }
    Derived(const Derived &d) : Base(d), b(d.b)
    {                        // 拷貝構造函數中輸出了對象個數[num],在園括號以前
        cout << "拷貝構造派生類的對象["
              << ++num << "](" << a << ", " << b << ")\n";
    }
    ~Derived()            // 析構函數中輸出了對象個數[num],在園括號以後
    {
        cout << "析構派生類的對象("
               << a << ", " << b << ")[" << --num << "]\n";
    }
    void Set(int x, int y)
    {
        a = x; b = y;
    }
    friend ostream &operator<<(ostream &out, const Derived &d)
    {
        out << '(' << d.a << ", " << d.b << ')';
        return out;
    }
private:
    int b;
};
void f(Derived &r, const Derived *p, Derived x)
{        // 形參分別爲引用傳遞、指針傳遞、值傳遞
    cout << "in f function..." << endl;
    cout << r << endl;
    r.Set(50, 80);                    // 引用傳遞的對象,值被重置
    cout << *p << endl;            // 雖然從指針p看其目標爲常對象,可是……
    cout << x << endl;
    x.Set(0, 0);
    cout << x << endl;
}
int main()
{
    Derived d(100, 200);
    cout << "Calling f function..." << endl;
    f(d, &d, d);            // 請注意:用同一個對象或對象的地址值作實參
    cout << "return to Operating System." << endl;
    return 0;
}

/*
構造基類對象(100)                                                    
構造派生類的對象[1](100, 200)                                       
Calling f function...                                             
拷貝構造基類的對象(100)                                              
拷貝構造派生類的對象[2](100, 200)                                   
in f function...                                                   
(100, 200)                                                          
(50, 80)                                                            
(100, 200)                                                          
(0, 0)                                                               
析構派生類的對象(0, 0)[1]                                           
析構基類對象(0)                                                       
return to Operating System.                                      
析構派生類的對象(50, 80)[0]                                         
析構基類對象(50)   
*/

解析:

仍是至上而下分析,先初始化一個派生類對象d,賦值100,200,這時候先執行基類的構造函數,輸出「構造基類對象100」,而後執行派生類構造函數,輸出「構造派生類對象[1](100,200)」,而後進入函數,輸出第三行,進入函數的過程當中,第一個參數是引用傳遞,不構建對象,第二個參數是值傳遞的地址,也沒有構造對象,所以這二者都不會調用構造函數,且二者指向的是同一對象;而第三個參數是值傳遞,傳遞的是對象,所以會產生實參是臨時對象,會爲實參構建對象,實參調用構造函數,分別輸出第四行和第五行。接下來輸出第六行,而後輸出r的值,(100,200),而後r調用set函數從新賦值,輸出p成員的值,剛纔說過,r和p指向同一對象,雖然經過p不能改變其成員的值,可是經過r能夠改,所以輸出set以後的值(50,80)。接下來輸出x,x是個臨時對象,其值是構造時候的(100,200),輸出。而後x調用其set函數,將臨時對象的成員值都設爲 了0。輸出x,固然是兩個0了。出函數,臨時變量該析構了。按照析構的順序先析構派生類,再析構基類。輸出兩行析構。接着往下走,cout一行英文,出main函數,該析構對象d了,輸出兩行析構,結束。對象

 

就三道啊,寫不動了,讓我休息會。blog

相關文章
相關標籤/搜索