北大C++程序设计编程作业答案+解析·多态

本章一共包含五个编程习题:

  1. 看上去像多态
  2. Fun和Do
  3. 这是什么鬼delete
  4. 怎么又是Fun和Do
  5. 编程填空:统计动物数量

以下习题答案全部通过OJ,使用编译器为:G++(9.3(with c++17))

1. 看上去像多态

考点:基类和派生类有同名函数的调用

解析:这题看起来是多态,但实际不是,只是基类和派生类有同名函数的调用。那么我们现根据题目要求来分析一下需要实现哪些功能:

D *pd;
D d( 4 );
d.Fun(); // D::Fun

从上面的代码,我们可以抽象出类D的结构:

class D {
    int nDVal;
public:
    void Fun() { std::cout << "D::Fun" << std::endl; }

    D( int n ) : nDVal( n ) {}
};

接着我们继续分析后续代码:

pb->Fun(); // B::Fun
pd->Fun(); // D::Fun
pb->Print(); // nBVal=2

到此我们还需要给类D添加一个Print()函数:

void Print() {
    std::cout << "nDVal=" << nDVal << std::endl;
}

其他都没有问题,但是下面这行代码就有问题:

// 按照之前的类D的结构,这里只会打印出:
// nDVal=8
// 但是题目还多一行输出:nBVal=24,也就是打印了类B的成员变量
// 但是我们知道nBVal是类B的私有成员变量,我们不能访问它,
// 只能调用类B的Print()方法,但是现在类D没有任何方法来调用B的途径,
// 只有通过继承B来达到调用类B的Print()
pd->Print();
// 所以我们需要修改一下之前类D的定义,并且再Print()调用基类的同名方法
class D: public B {
    // 其他变量和函数省略
public:
    void Print() {
        B::Print();
        std::cout << "nDVal=" << nDVal << std::endl;
    }
    
    D( int n ) : B( n ), nDVal( n ) {}
};

好像解决了问题,但是可以执行一下程序,会发现还有一个问题,nBVal的值不对,这里打印出来是8,但是题目是24,那我们有理由怀疑在初始化基类的时候,你BVal = n * 3,之后的代码都符合要求

pb = &d;
pb->Fun(); // B::Fun
// 这里不是多态,所以指针是谁,就调用谁的函数,即pb是B的指针,即调用类B的Print(),即使其指向的对象是类D
pb->Print(); // nBVal=12

答案:完整源码地址

// 这里只给到需要补完的代码,完整代码请移步到github
// 在此处补充你的代码
class D: public B {
    int nDVal;
public:
    void Print() {
        B::Print();
        std::cout << "nDVal=" << nDVal << std::endl;
    }

    void Fun() { std::cout << "D::Fun" << std::endl; }

    D( int n ) : B( n * 3 ), nDVal( n ) {}
};

2. Fun和Do

考点:多态

解析:如果只是解题,这题可以暴力解,只有三种可能,全部尝试一遍就能得到答案,但是我们还需要为什么要这样做:

// class A => void Fun();
// class B => undefined;
// class C => void Fun();
p.Fun(); // A::Fun

这里我们可以看到,程序实际输出的类A的Fun()函数,而且相关定义都没有virtual关键字,所以不可能是多态,那么根据上一题的原则:

多余非多态的同名成员函数,指针或引用是谁,就调用谁的函数

所以似乎Call()函数的参数类型可以是A引用,接下来我们来看看下一句能否满足题意:

// class A => void Do();
// class B => virtual void Do();
// class C => [ virtual ] void Do();
p.Do(); // C::Do

这里打印的结果是类C的Do()函数,而且从类B开始才有virtual关键词,因此类C里面的同名函数默认带有virtual,但是类A没有,因为它是类B的基类,那么这里参数类型是类A行不行呢?如果是,那么程序应该打印类A里面的Do()函数,所以不对?那么是类C可以么?如果是,这里是多态,所以没问题,但是上一句有问题,会打印类C的Fun()函数。那么唯一的可能性就是参数类型是类B,这里因为是多态,还是打印类C的Do(),上一句因为类B没有Fun()的定义,会向上去基类寻找相关函数,找到了并执行,所以打印类A的Fun(),所以参数类型是类B符合题意。

答案:完整源码地址

// 这里只给到需要补完的代码,完整代码请移步到github
void Call( 
    // 在此处补充你的代码
	B &p
);

3. 这是什么鬼delete

考点:析构函数的多态

解析:这里就需要注意一点,如果不把类A的析构函数定义成virtual,delete的时候只会调用B的析构函数,需要添加virtual,那么编译器会调用类B的析构,再调用类A的析构函数。

答案:完整源码地址

// 这里只给到需要补完的代码,完整代码请移步到github
class A {
public:
    A() {}

    // 在此处补充你的代码
    virtual ~A() { std::cout << "destructor A" << std::endl; }
};

4. 怎么又是Fun和Do

考点:多态

解析:这题和第二题类似,但是其实都不用分析,答案就是类A的指针,为什么呢?因为只有类A才能编译通过,其他两个类是不能通过下面的语句:、

// 如果函数参数类型是类B和C,会编译错误,因为这样相当于认为A是B或C,
// 但是我们知道基类不一定是派生类,但是派生类一定是基类
Call( new A() );

如果要问问什么是类A的引用,我们可以这样进行分析:

Call( new A() );
// Fun()没有定义为Virutal,不是多态,所以需要需要参数类型是类A
p->Fun(); // A::Fun
// Do()定义为virtual,是多态,调用实际指向的对象,所以参数类型还是类A
p->Do(); // A::Do

Call( new C() );
// 理由同上
p->Fun(); // A::Fun
// 理由同上,调用实际指向的对象
p->Do(); // C::Do

答案:完整源码地址

// 这里只给到需要补完的代码,完整代码请移步到github
void Call( 
    // 在此处补充你的代码
	A *p
);

5. 编程填空:统计动物数量

考点:多态和继承

解析:从题意可知,我们需要实现三个类:Animal,Dog和Cat,并且很自然想到,Dog和Cat都继承自Animal,那么为了实现计数功能,我们可以每初始化一个Animal,计数增加一,这个计数为所有动物的总数,同样每初始化一个Dog和Cat,各自的计数增加一,再因为初始化派生类,会先自动调用基类构造函数,所以我们只需在各个类的构造函数里面进行计数即可,这里要注意每个类的变量需要声明为static,因为在print()函数里面,我们才能用类::变量的形式进行访问。

答案:完整源码地址

// 这里只给到需要补完的代码,完整代码请移步到github
// 在此处补充你的代码
class Animal {
public:
    static size_t number;

    Animal() { number++; }

    virtual ~Animal() { number--; }
};

class Dog: public Animal {
public:
    static size_t number;

    Dog() : Animal() { number++; }

    ~Dog() { number--; }
};

class Cat: public Animal {
public:
    static size_t number;

    Cat() : Animal() {
        number++;
    }

    ~Cat() { number--; }
};

上一章:继承
下一章:输入输出和模板

6. 参考资料

  1. C++程序设计
  2. pixiv illustration: 芽吹きの剣

7. 免责声明

※ 本文之中如有错误和不准确的地方,欢迎大家指正哒~
※ 此项目仅用于学习交流,请不要用于任何形式的商用用途,谢谢呢;


在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值