1【单选题】在公有派生类的成员函数不能直接访问基类中继承来的某个成员,则该成员一定是基类中的( C )。(2.0分)
- A、公有成员
- B、保护成员
- C、私有成员
- D、保护成员或私有成员
注意从类外访问与从派生类中访问
2【单选题】什么情况下必须使用虚析构函数?( A )(2.0分)
- A、使用delete删除由基类指针指向的派生类对象
- B、派生类构造函数是虚函数
- C、使用delete删除由派生类指针指向的派生类对象
- D、基类构造函数是虚函数
构造函数不能是虚函数
3【单选题】一个类的层次结构中,定义有虚函数,并且都是公有继承,在下列情况下,实现动态绑定的是( D )。(2.0分)
- A、使用构造函数调用虚函数
- B、使用类名限定调用虚函数
- C、使用类的对象调用虚函数
- D、使用成员函数调用虚函数
4【单选题】下列关于虚函数的描述中,错误的是:( A )。(2.0分)
- A、static成员函数可以说明为虚函数
- B、虚函数是一个成员函数
- C、在类的继承的层次结构中,虚函数是说明相同的函数
- D、虚函数具有继承性
静态成员函数(
static
函数)不能被声明为虚函数。虚函数是与对象的动态类型相关的,它们依赖于对象的运行时类型,而静态成员函数不依赖于任何对象实例,因此不能是虚函数。
5【单选题】下列有关继承和派生的叙述中,正确的是:( B )(2.0分)
- A、派生类不能访问通过私有继承的基类的保护成员
- B、如果基类没有默认构造函数,派生类就应当声明带形参的构造函数
- C、多继承的虚基类不能够实例化
- D、基类的析构函数和虚函数都不能够被继承,需要在派生类中重新实现
如果派生类是通过私有方式继承基类,那么基类的保护成员(protected)在派生类中仍然是可以访问的,尽管它们不能被派生类的对象访问。
6【单选题】纯虚函数和虚函数的主要区别是: ( D )(2.0分)
- A、返回值类型
- B、成员访问修饰符
- C、类中的位置
- D、纯虚函数不能有实现
7【单选题】Employee是基类,HourlyWorker是派生类,并重定义了非虚函数print,下面的程序段调用了两个print函数,输出结果是否相同?( D )
HourlyWorker h;
Employee *ePtr = &h;
ePtr->print();
ePtr->Employee::print();
- A、取决于print函数的实现
- B、是,如果print是static函数
- C、否
- D、是
8【单选题】从同一个基类派生出的各个类的对象之间,( D )。(2.0分)
- A、共享部分数据成员,每个对象还包含基类的所有属性
- B、共享所有数据成员,每个对象还包含基类的所有属性
- C、共享部分数据成员和函数成员
- D、不共享任何数据成员,但每个对象还包含基类的所有属性
每个对象都有自己的实例变量副本,因此它们不共享数据成员。但是,每个派生类的对象确实包含了基类的所有属性,因为继承机制使得派生类可以访问基类的属性和方法。
9【单选题】下面叙述不正确的是( D )。(2.0分)
- A、赋值兼容规则也适用于多重继承的组合
- B、对基类成员的访问必须是无二义性的
- C、派生类一般都用公有派生
- D、基类的公有成员在派生类中仍然是公有的
10【单选题】以下说法正确的是:( D )(2.0分)
- A、派生类可以和基类有同名成员函数,但是不能有同名成员变量
- B、派生类和基类的同名成员函数必须参数表不同,否则就是重复定义
- C、派生类和基类的同名成员变量存放在相同的存储空间
- D、派生类的成员函数中,可以调用基类的同名同参数的成员函数
在派生类中,可以通过基类名和作用域解析运算符(::)显式调用基类的同名成员函数,或者使用
this
指针来调用基类的成员函数。
11【判断题】派生类可以有选择性地继承基类的部分成员。( 错 )(2.0分)
派生类会继承基类的所有成员(除了构造函数、析构函数和虚析构函数之外),而不能有选择性地继承部分成员。派生类会继承基类的所有公有和保护成员,但不会继承基类的私有成员。如果派生类需要使用基类的私有成员,可以通过基类的公有或保护成员函数来访问。
12【判断题】派生类的默认构造函数包含有直接基类的构造函数。( 对 )(2.0分)
13【判断题】一个抽象类中可以包含有多个纯虚函数,一个派生类也可以包含多个虚函数(对 )
注意书写形式
14【判断题】虚函数有继承性,基类中说明的虚函数只要在它的派生类中与它名字相同的,一定是虚函数。( 对 )
15【判断题】虚函数可以被类的对象调用,也可以被类的对象指针和对象引用调用。( 对 )(2.0分)
16【判断题】虚函数实现的多态是在编译时期确定的。( 错 )(2.0分)
虚函数实现的多态是在运行时(runtime)确定的,而不是在编译时期(compile-time)。
C++运行时会根据对象的实际类型(动态类型)来决定调用哪个函数,这个过程称为动态绑定或晚期绑定。
17【填空题】
写出下列程序的输出结果:
#include <iostream >using namespace std;class A {private:int nVal;public:void Fun(){ cout << "A::Fun" << endl; };virtual void Do(){ cout << "A::Do" << endl; }};class B:public A {public:virtual void Do(){ cout << "B::Do" << endl;}};class C:public B {public:void Do( ){ cout <<"C::Do"<<endl; }void Fun(){ cout << "C::Fun" << endl; }};void Call( A * p) {p->Fun(); p->Do();}int main() {Call( new A());Call( new C());return 0;}
1 A::Fun2 A::Do3 B::Do4 C::Do5 C::Fun
答案:
1 2 1 4
Call(new A())
:这里创建了类A的对象,并将其地址传递给Call
函数。由于Fun
不是虚函数,所以会调用A
类的Fun
函数,输出为"A::Fun"。对于Do
函数,它是虚函数,会根据对象的实际类型来调用相应的函数,因此会调用A
类的Do
函数,输出为"A::Do"。
Call(new C())
:这里创建了类C的对象,并将其地址传递给Call
函数。对于Fun
函数,虽然C类中定义了自己的Fun
函数,但由于Call
函数中的参数是A*
类型,所以会调用A
类的Fun
函数,输出为"A::Fun"(不是虚函数的话,就会调用A*)。对于Do
函数,它是虚函数,会根据对象的实际类型来调用相应的函数,因此会调用C
类的Do
函数,输出为"C::Do"。
18【填空题】写出下面程序的输出结果:
#include <iostream>using namespace std;class B {public:B( ){ cout << "B_Con" << endl; }~B( ) { cout << "B_Des" << endl; }};class C:public B {public:C( ){ cout << "C_Con" << endl; }~C( ) { cout << "C_Des" << endl; }};int main(){C * pc = new C;delete pc;return 0;}
答案:
[填空1] B_Con[填空2] C_Con[填空3] C_Des[填空4] B_Des
考察对多态中(继承)的构造函数与析构函数的运用
先调用基类的构造函数,再调用派生类的构造函数,析构函数的调用顺序与其相反
19【填空题】写出下面程序的输出结果:
#include <iostream >using namespace std;class A {public:A( ) { }virtual void func(){ cout << "A::func" << endl; }virtual void fund( ){ cout << "A::fund" << endl; }void fun(){ cout << "A::fun" << endl;}};class B:public A {public:B ( ) { func( ) ; }void fun( ) { func( ) ; }};class C : public B {public :C( ) { }void func( ){cout << "C::func" << endl; }void fund( ){ cout << "C::fund" << endl;}};int main(){A * pa = new B();pa->fun();B * pb = new C();pb->fun();return 0;}
1 A::func2 A::fund3 A::fun4 C::func5 C::fund
答案
1 3 1 4
- 1.
A * pa = new B();
:创建了一个B
类型的对象,并将其地址赋给了一个指向A
的指针。由于B
继承自A
,pa
实际上指向的是一个B
类型的对象。- pa->fun();在
B
类的构造函数中直接调用了func()
方法,而B
类并没有重写这个方法,所以调用的是基类A
中的func
方法。 输出A::func。- 然后再调用函数fun:
pa->fun();
:这里调用了fun
函数。由于fun
不是虚函数,所以会根据指针的静态类型(即A*
)来调用A
类的fun
函数,输出为"A::fun"。
B * pb = new C();
:创建了一个C
类型的对象,并将其地址赋给了一个指向B
的指针。由于C
继承自B
,pb
实际上指向的是一个C
类型的对象。
pb->fun();
:这里调用了fun
函数。由于fun
不是虚函数,所以会根据指针的静态类型(即B*
)来调用B
类的fun
函数。在B
类的构造函数中直接调用了func()
方法,而B
类并没有重写这个方法,所以调用的是基类A
中的func
方法。 输出A::func。- 由于
func
是虚函数,会调用C
类的func
方法(因为pb
实际上指向的是C
类型的对象),输出C::func
20【填空题】
#include <iostream>using namespace std;class A {public:A( ) { }virtual void func( ) {cout << "A::func" << endl; }~A( ) { }virtual void fund( ) {cout << "A::fund" << endl; }};class B:public A {public:B ( ) { func( ) ; }void fun( ) { func( ) ; }~B ( ) { fund( ); }};class C : public B {public :C( ) { }void func( ) { cout << "C::func" << endl; }~C() { fund( ); }void fund() { cout << "C::fund"<< endl; }};int main(){ C c; return 0; }
运行结果:[填空1][填空2][填空3]把对应的选项编号填入空中:1 A::func2 A::fund3 C::func4 C::fund
答案
1 4 2
C c;
:创建了一个C
类型的对象c
。由于C
继承自B
,B
继承自A
,对象的构造将遵循构造函数链,从最基类A
开始,然后是B
,最后是C
。先调用A的构造,再调用B的构造,其中会调用函数func(),但是B中无重写该函数,就会调用基类A的func(),输出A::func,最后调用C的构造(即使
C
类重写了func()
函数,B
类的构造函数中调用func()
时也不会调用C
类的版本,因为这是在编译时决定的,而且C
的构造函数还没有执行,C
类的对象还没有完全构造。因此,会调用A
类的func()
函数,输出"A::func"。)先调用C的析构,会调用虚函数fund(),但是C中已重写该函数,输出C::fund,然后调用B的析构,会调用虚函数fund(),但是B类中没有重写该函数,会调用A类中的fund(),输出A::fund,最后调用A的析构函数