本章一共包含五个编程习题:
以下习题答案全部通过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. 参考资料
7. 免责声明
※ 本文之中如有错误和不准确的地方,欢迎大家指正哒~
※ 此项目仅用于学习交流,请不要用于任何形式的商用用途,谢谢呢;