1. 程式人生 > >[學習筆記] C++ 歷年題程式題解析

[學習筆記] C++ 歷年題程式題解析

發現程式題也挺有價值的。

順便記錄下來幾道。

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;

const char *p可以這麼理解,const後面是char*,所以char*對應的內容就不能改變;而char* const p的const後面是p,所以p的值不能改變,p的值是一個指標地址。綜上,緊接著const後面的內容就是不可改變的內容。這下應該可以做題了。

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,且是值返回,值返回之後會建立一個無名變數,然後這個無名變數再呼叫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。就很簡單了。

 

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了,輸出兩行析構,結束。

 

三套試卷我全看完了,只有去年的題出的用心了,出題老師顯然很用心。之前的都是送分的,比較簡單,就不解析了。。