• [学习笔记] 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 << '	' << str2 << endl;
        cout << p1 << '	' << p2 << endl;
        Swap(str1, str2);
        Exchange(p1, p2);
        cout << str1 << '	' << str2 << endl;
        cout << p1 << '	' << 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 << ")
    "; }
        ~ctest(){ cout << "析构对象(" << a << ")
    ";}
        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。就很简单了。

    3.题目

    #include <iostream>
    using namespace std;
    class Base                                            // 基类
    {
    public:
        Base(int x=0) : a(x)
        {
            cout << "构造基类对象(" << a << ")
    ";
        }
        Base(const Base &b) : a(b.a)
        {
            cout << "拷贝构造基类的对象(" << a << ")
    ";
        }
        virtual ~Base()
        {
            cout << "析构基类对象(" << a << ")
    ";
        }
    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 << ")
    ";
        }
        Derived(const Derived &d) : Base(d), b(d.b)
        {                        // 拷贝构造函数中输出了对象个数[num],在园括号之前
            cout << "拷贝构造派生类的对象["
                  << ++num << "](" << a << ", " << b << ")
    ";
        }
        ~Derived()            // 析构函数中输出了对象个数[num],在园括号之后
        {
            cout << "析构派生类的对象("
                   << a << ", " << b << ")[" << --num << "]
    ";
        }
        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了,输出两行析构,结束。

    就三道啊,写不动了,让我休息会。

     

  • 相关阅读:
    WinDbg调试C#技巧,解决CPU过高、死锁、内存爆满
    Window环境下搭建Git服务器
    Virtual Box虚拟机Ubuntu系统安装及基本配置
    修改VS2017新建类模板文件添加注释
    .net core 使用IIS作为宿主Web服务器,部署常见问题
    Asp.Net进程外Session(状态服务器Session、数据库Session)
    百度地图DEMO-路线导航,测距,标点
    c#文件图片操作
    C#代码安装Windows服务(控制台应用集成Windows服务)
    通过经纬度获取地址信息
  • 原文地址:https://www.cnblogs.com/aoru45/p/9898691.html
Copyright © 2020-2023  润新知