OOP第三次作业

选择题3

1.

【单选】下面说法正确的有:

A) 若已定义类Obj,则对于Obj a; Obj b=a;,第二个语句首先调用Obj的默认构造函数(已定义)初始化b,再调用Obj的赋值运算符(已定义)实现a到b的拷贝。

B) 如果一个类没有显式定义拷贝构造函数但重载了赋值运算符,且编译器自动合成了拷贝构造函数,则合成的拷贝构造函数会调用拷贝赋值运算符完成拷贝。

C) 若有int x;,则int *y = &x;和int *z = &(x-7);都是合法的语句。

D) const int &x = -1;是合法的语句。

Answer: D
A应该会直接调用拷贝构造函数或者是移动构造函数。
B不太懂为什么。
C中int *z = &(x-7)是错误的,原因是(x-7)是一个右值,右值不能取地址。int y = &x含义是定义一个int指针y,y指向x的地址。输出y将会得到一个地址,输出y得到的是x的值。
D是引用的例外,即:常值左值引用也可以绑定右值。

2.

【单选】以下代码的输出是:
01    #include<iostream>
02    using namespace std;
03   
04    class T{
05    public:
06        T(){}
07        T(T& t){cout<<"A";}
08        T(T&& t){cout<<"B";}
09        T& operator =(T&& t){cout<<"C"; return *this;}
10    };
11    
12    void swap(T& a, T& b) { 
13        T tmp(std::move(a));//传入参数为右值,匹配参数为右值引用的构造函数
14        a = std::move(b);
15        b = std::move(tmp);//这两个都是匹配自定义的赋值函数
16    }
17    
18    int main(){
19        T a;
20        T b;//这两个都是调用默认构造函数
21        swap(a,b);
22        return 0;
23    }

A) ABCC

B) BCC

C) CCC

D) BBCBC
Answer: B

3.

【多选】关于以下代码说法正确的有:
01     #include <iostream>
02     using namespace std;
03   
04     class Complex
05     {  
06     public:  
07         int real;  
08         int imag; 
09         Complex():real(0),imag(0){}
10         Complex(int r, int i):real(r),imag(i){cout<<"B";}
11         Complex(int r): real(r),imag(0){cout<<"A";}
12         operator int(){
13             return real;
14         }
15         Complex operator+(const Complex& c){
16             return Complex(this->real+c.real,this->imag+c.imag);
17         } 
18     };  
19    
20     int main()
21     {
22         Complex c(3,4);
23         c=c+3.4;
24         cout<<c.real+c.imag;
25         return 0;
26     }

A) 该代码在第23行产生歧义会导致编译错误。可以将12-14行的代码注释或15-17行的代码注释,两种情况均可编译通过。

B) 如果注释15-17行,并且在12行前加explicit关键字,则第23行不可以通过编译。

C) 如果注释12-14行,输出结果是BA10。

D) 如果注释15-17行,输出结果是BA10。
Answer: AB
23行编译出错的原因是同时定义了两个类型转换函数,编译器不知道调用哪个(operator + ambiguous)。解决办法是把其中一个注释掉或者加上explicit关键字。

4.

【单选】下面说法错误的有:

A) 考虑代码int x; int &y=x;,则y是x的别名。

B) 编译器自动合成的拷贝构造函数对指针类型数据成员采用位拷贝。

C) int a=5; int &&x=a;是合法的语句。

D) int &&x=3;x=5;是合法的语句。
Answer: C
B: 正确,也因此会出错
C: a是左值,不能被右值引用绑定

5.

【单选】关于以下代码说法正确的有:
01    #include <iostream>
02    using namespace std;
03    class A {
04    public:
05        int a=1;
06    protected:
07        int b=2;
08    private:
09        int c=3;
10    };
11    
12    class B {
13    public:
14        int d=4;
15    protected:
16        int b=5;
17    private:
18        int e=6;
19    };
20    
21    class C: public A, private B{
22    public:
23        void print() {
24            cout << [1] << endl;
25        }
26    };
27    
28    int main() {
29        C obj_c;
30        obj_c.print();
31        cout << [2] << endl;
32        return 0;
33    }

A) 在[1]处,填a或c均可编译通过(可认为[2]填写正确,不影响编译)。

B) 在[2]处,填obj_c.a或obj_c.d均可编译通过(可认为[1]填写正确,不影响编译)。

C) 在[1]处,可以通过A::b和B::b分别访问A类和B类中的成员变量b。

D) 在[2]处,可以通过obj_c.A::b来访问A类中的成员变量b。
Answer: C
派生类中的成员函数能否调用基类的成员变量由基类中成员变量本身的权限决定,不受继承方法的影响(protected的含义是派生类可以访问但对于某一个实例对象来说和private一样)。而派生类的实例对象能否访问基类的成员变量取决于基类中成员函数权限和继承方法的交集(此时protected=private)。
据此:
A: a可以,c不可以
B: a可以,d不可以
D: b的权限是protected,对obj_c来说相当于private,不能访问

6.

【单选】关于下面这段代码运行结果说法正确的有 (编译选项含有-std=c++11):
01   #include <iostream>
02   using namespace std;
03   class A {
04       int data;
05   public:
06       static int count;
07       A():data(2019){count += 1; cout << count << endl;} 
08       A& operator = (const A & a){
09           if (this != &a) data = a.data;
10           return *this;
11       }
12       A(int i):data(i){count += 2; cout << count << endl;}
13       ~A(){cout << "destructor "<< data << endl;}
14   };
15    
16   class B {
17       int data{2020};
18       A a1,a2;
19   public:
20       B(){}
21       B(int i):a2(i){a1 = a2;}
22       ~B(){cout<<"destructor "<< data <<endl;}
23   };
24    
25   int A::count = 0;
26    
27   int main() {
28       B obj1;
29       B obj2(2021);
30       return 0;
31   }

A) 将第8-11行去掉对程序运行结果会有影响。

B) 输出的前4行是1\n2\n4\n5\n。

C) 将第20行去掉对程序编译不会有影响。

D) 输出中前缀是destructor的行,后面的数字按顺序是2020,2021,2021,2020,2019,2019。
Answer: D
A: 没有显式定义赋值运算符,编译器会生成隐式的赋值运算符
B: 读代码可知
C: 会有影响,当类显式定义了构造函数,编译器不会自动生成隐式的构造函数
D: 读代码可知

7.

【单选】关于下面代码说法正确的有:
01   #include <iostream>
02   using namespace std;
03   
04   class Animal{
05   public:
06       Animal(){};
07       Animal(int d){};
08       void move(double d) { cout << "Animal move" << d << "km\n"; }
09       void eat(double d) { cout << "Animal eat " << d << "g food\n"; }
10    };
11    
12    class Bird: public Animal{
13    public:
14        using Animal::move;
15        void move() { cout << "Bird move"; }
16        void move(int i) { cout << "Bird move" << i << "m\n"; }
17    };
18    
19    class Crow : public Bird{
20    public:
21        using Bird::move;
22        void move(int i) { cout << "Crow move" << i << "m\n"; }
23    };
24    
25    int main() {
26        Crow crow;
27        crow.move(10);
28        crow.move(4.9);
29        crow.eat(4.8);
30        crow.move();
31        return 0;
32    }

A) 如果将6行注释掉, 程序可以通过编译。

B) 如果将第14行注释掉, 程序仍能通过编译,且输出没有变化。

C) 如果将第21行注释掉, 程序仍能通过编译,但是输出有变化。

D) 如果将第12行改为class Bird: protected Animal{, 只需要去掉第29行就可以成功编译运行。
Answer: D
A: 注释掉之后由于已经显式定义过了Animal的构造函数,编译器不会再生成隐式的默认构造函数,而Crow crow需要调用的是无参数的构造函数,故会缺失构造函数。反而把所有Animal的构造函数注释掉之后可以编译通过
B: 不能通过编译,缺失了构造函数。Crow crow要调用带参数的Bird的构造函数,Bird的带参数的构造函数没有显式定义,需要调用Animal的带参数的构造函数,但构造函数不会继承给Bird,故缺失
C: 同理没有构造函数
D: Animal所有成员在Bird中是protected的,继承到Crow中后,Animal的所有成员仍然是protected的,因此crow对象不能调用eat()

8.

【多选】以下说法正确的有:

A) 派生类的成员函数可以访问通过私有继承的基类的保护成员。

B) 继承时,友元函数不可以被派生类继承。

C) 类C直接public继承类B和类A,而类B和类A中都包含public变量x,则代码C c;cout<<c.x;会因为二义性而编译错误。

D) 派生类的构造函数的成员初始化列表中,可以包含基类数据成员的初始化。
Answer: ABC
D: 会报错

B.引用?复制?

Test.h

#ifndef Test_h
#define Test_h
#include <iostream>

using namespace std;

class Test {
    int *buf;//buf是指向int的指针
public:
    Test();//默认构造函数
    Test(int val);//构造函数
    ~Test();//析构函数
    Test(const Test& t) : buf(new int(*t.buf)) {//new int也是指向int的指针,这里指向的int的值是作为参数的对象的buf所指向的int的值,相当于把参数对象的buf值拷贝到新的对象的buf里,存在两块内存
            cout << "Test(const Test&) called. this->buf @ "
                << hex << buf << endl;
        }//拷贝构造函数
    Test(Test&& t) : buf(t.buf) {//只是搞了个新指针指向参数对象buf所指的内存,可能存在安全隐患
            cout << "Test(Test&&) called. this->buf @ "
                << hex << buf << endl;
            t.buf = nullptr;//但是这里把参数对象的指针置空了,故没有隐患
        }//移动构造函数
    Test& operator= (const Test& right);//拷贝赋值运算符
    Test& operator= (Test&& right);//移动赋值运算符
    void print(const char *name);
};

#endif /* Test_h */

Test.cpp

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


Test::Test() {
        buf = new int(0);
        cout << "Test(): this->buf @ " << hex << buf << endl;
    }
Test::Test(int val) {
        buf = new int(val);
        cout << "Test(int): this->buf @ " << hex << buf << endl;
    }
Test::~Test() {
        cout << "~Test(): this->buf @ " << hex << buf << endl;
        if (buf) delete buf;
    }
Test& Test::operator= (const Test& right) {
        if (this != &right){
            if(buf) delete buf;
            buf = new int(*right.buf);
        }
        return *this;
    }
Test& Test::operator= (Test&& right) {
        if (this != &right){
            if(buf) delete buf;
            this->buf = right.buf;
            right.buf = nullptr;
        }
        return *this;
    }
void Test::print(const char *name) {
        cout << name << ".buf @ " << hex << buf << endl;
    }

A.cpp

0: Test F(Test a){//调用拷贝构造函数
1:     Test b = std::move(a);//调用移动构造函数
2:     return b;//调用移动构造函数
3: }
4: int main(){
5:     Test a;//调用默认构造函数
6:     a = 1;//此处1被隐式转换为Test,调用构造函数,得到一个临时对象,是右值,再调用移动赋值运算符
7:     Test A = F(a);//在F中调用移动构造函数,出来之后得到右值,调用移动构造函数
8:     return 0;
9: }

#include <iostream>
#include "Test.h"
Test F(Test a){
    Test b = std::move(a);
    return b;
}
int main(){
    Test a;
    a = 1;
    Test A = F(a);
    return 0;
}

输出结果:

Test(): this->buf @ 0x600000d98030#默认构造函数构造a
Test(int): this->buf @ 0x600000d98040#将1类型转换为Test,调用构造函数
~Test(): this->buf @ 0x0#在此之前调用了移动赋值运算符,1的对象buf被置为空指针,a原来的buf被delete掉,但没有输出,这一行是析构1构造出来的对象(右值立刻被析构)
Test(const Test&) called. this->buf @ 0x600000d98030#F函数的参数调用拷贝构造函数,显示的是函数中临时变量的地址,*这个地址恰好是a的地址^_^为啥?*
Test(Test&&) called. this->buf @ 0x600000d98030#函数体调用移动构造函数,故地址不变,参数的buf被置为空指针
Test(Test&&) called. this->buf @ 0x600000d98030#return的时候调用的移动构造函数**为什么?**,地址不变
~Test(): this->buf @ 0x0#析构b(函数体内的临时变量在出函数体的时候被析构)
Test(Test&&) called. this->buf @ 0x600000d98030#为A调用移动构造函数,同时F(a)的buf被置空
~Test(): this->buf @ 0x0#析构F(a),F(a)是右值,用完就被析构了
~Test(): this->buf @ 0x0#析构a,在函数返回值被析构时析构函数参数
~Test(): this->buf @ 0x600000d98030#析构A
~Test(): this->buf @ 0x600000d98040#析构a

B.cpp

0: Test F(const Test& a){//不用构造,是引用
1:     Test b = std::move(a);//**调用的是拷贝构造函数!为什么?**
2:     return b;//移动构造
3: }
4: int main(){
5:     Test A = F(1);//1先调用构造函数,出函数调用移动构造
6:     return 0;
7: }

#include <iostream>
#include "Test.h"

Test F(const Test& a){
    Test b = std::move(a);
    return b;
}
int main(){
    Test A = F(1);
    return 0;
}

输出结果:

Test(int): this->buf @ 0x600003928030
Test(const Test&) called. this->buf @ 0x600003928040
Test(Test&&) called. this->buf @ 0x600003928040
~Test(): this->buf @ 0x0
Test(Test&&) called. this->buf @ 0x600003928040
~Test(): this->buf @ 0x0
~Test(): this->buf @ 0x600003928030
~Test(): this->buf @ 0x600003928040

C.cpp

0: Test F(Test &&a){//不用构造,右值引用
1:     Test b = std::move(a);//移动构造
2:     return b;//移动构造
3: }
4: int main(){
5:     Test A = F(1);//构造+移动构造
6:     return 0;
7: }

#include <iostream>
#include "Test.h"

Test F(Test &&a){
    Test b = std::move(a);
    return b;
}
int main(){
    Test A = F(1);
    return 0;
}

输出结果:

Test(int): this->buf @ 0x600000434030
Test(Test&&) called. this->buf @ 0x600000434030
Test(Test&&) called. this->buf @ 0x600000434030
~Test(): this->buf @ 0x0
Test(Test&&) called. this->buf @ 0x600000434030
~Test(): this->buf @ 0x0
~Test(): this->buf @ 0x0
~Test(): this->buf @ 0x600000434030

转载请注明出处:http://www.xingnongyuan.com/article/20230506/711401.html