一、选择题
1、下列关于动态联编的描述中,错误的是(D)。
A.动态联编是以虚函数为基础
B.动态联编是运行时确定所调用的函数代码的
C.动态联编调用函数操作是指向对象的指针或对象引用
D.动态联编是在编译时确定操作函数的
3、在派生类中重新定义虚函数时必须在(A,B,E)方面与基类保持一致。(多选题)
A.参数个数 B.参数类型 C.参数名字 D.操作内容 E.返回类型
5、C++类体系中,能被派生类继承的是(B)。
A.构造函数 B.虚函数 C.析构函数 D.友元函数
6、关于虚函数的描述中,正确的是(D)。
A.虚函数是一个静态成员函数
B.虚函数是一个非成员函数
C.虚函数即可以在函数说明定义,也可以在函数实现时定义
D.派生类的虚函数与基类中对应的虚函数具有相同的参数个数和类型
7、下面4个选项中,(A)是用来声明虚函数的。
A.virtual B.public C.using D.false
8、编译时的多态性可以通过使用( B )获得。
A.虚函数和指针 B.重载函数和析构函数 C.虚函数和对象 D.虚函数和引用
9、关于纯虚函数和抽象类的描述中,错误的是( C )。
A.纯虚函数是一种特殊的虚函数,它没有具体的实现
B.抽象类是指具有纯虚函数的类
C.一个基类中声明有纯虚函数,该基类派生类一定不再是抽象类
D.抽象类只能作为基类来使用,其纯虚函数的实现由派生类给出
10、下列描述中,( D )是抽象类的特征。
A.可以声明虚函数 B.可以进行构造函数重载 C.可以定义友元函数 D.不能声明其对象
11、以下( C )成员函数表示虚函数。
A.virtual int vf(int);B.void vf(int)=0;C.virtual void vf()=0; D.virtual void vf(int) { };
12、如果一个类至少有一个纯虚函数,那么就称该类为( A)。
A.抽象类 B.虚函数 C.派生类 D.以上都不对
13、要实现动态联编,必须通过(A)调用虚函数。
A.对象指针 B.成员名限定 C.对象名 D.派生类名
14、下面描述中,正确的是(A,B,)。
A.virtual可以用来声明虚函数
B.含有纯虚函数的类是不可以用来创建对象的,因为它是虚基类
C.即使基类的构造函数没有参数,派生类也必须建立构造函数
D.静态数据成员可以通过成员初始化列表来初始化
二、写出下列程序的输出结果。
1、分析下列程序的输出结果。
#include <iostream>
using std::endl;
using std::cout;
class A
{
public:
A()
{
cout << "A's cons." << endl;
}
virtual
~A()
{
cout << "A's des." << endl;
}
virtual
void f()
{
cout<<"A's f()."<<endl;
}
void g()
{
f();
}
};
class B
: public A
{
public:
B()
{
f();
cout << "B's cons." << endl;
}
~B()
{
cout << "B's des." << endl;
}
};
class C
: public B
{
public:
C()
{
cout<<"C's cons."<<endl;
}
~C()
{
cout<<"C's des."<<endl;
}
void f()
{
cout<<"C's f()."<<endl;
}
};
int main(void)
{
A *pa=new C();
pa->g();
delete pa;
return 0;
}
运行结果
A's cons. //A *pa = new C(); 先指向A的构造函数,再指向C的父类B的构造函数,最后C
A's f().
B's cons.
C's cons.
C's f().
C's des.
B's des.
A's des.
2、根据给定的程序执行结果,将下列程序补充完整。
#include <iostream>
using std::endl;
using std::cout;
class Base
{
public:
Base(int i)
{
b = i;
}
(1)
protected:
int b;
};
class Derive1
:public Base
{
public:
(2)
void Print()
{
cout<<"Derive1's Print() called."<<endl;
}
};
class Derive2
:public Base
{
(3)
};
void fun( (4) )
{
obj->Print();
}
int main(void)
{
(5)
fun(d1);
fun(d2);
return 0;
}
程序的执行结果如下:
Derive1's Print() called.
Derive2's Print() called.
(1)virtual void Print()=0;
(2)Derive1(int i=0):Base(i){}
(3)
Derive2(int i=0):Base(i){}
void Print()
{
cout<<"Derive2's Print() called."<<endl;
}
(4)Base *obj
(5)
Derive1 derive1;
Derive2 derive2;
Base *d1 = &derive1;
Base *d2 = &derive2;
3、根据给定的程序执行结果
#include <iostream>
using std::endl;
using std::cout;
class A
{
public:
A(int i,int j)
{
a=i;
b=j;
}
void move(int i,int j)
{
a += i;
b += j;
}
void disp()
{
cout << "(" << a << "," << b << ")" << endl;
}
private:
int a,b;
};
class B
: public A
{
public:
B(int i,int j,int k,int l)
:A(i,j)
,x(k)
,y(l)
{
}
void disp()
{
cout << x << "," << y << endl;
}
void fun()
{
move(3,5);
}
private:
int x,y;
};
int main()
{
A m(1,2);
m.disp();
B d(3,4,5,6);
d.fun();
d.A::disp();
d.disp();
return 0;
}
(1,2)
(6,9)
5,6
4、根据给定的程序执行结果
#include<iostream>
using std::endl;
using std::cout;
class Base1
{
public:
virtual void fun()
{
cout<<"--Base1--\n";
}
};
class Base2
{
public:
void fun()
{
cout<<"--Base2--\n";
}
};
class Derived
:public Base1
,public Base2
{
public:
void fun()
{
cout<<"--Derived--\n";
}
};
int main()
{
Base1 obj1, *ptr1;
Base2 obj2, *ptr2;
Derived obj3;
ptr1=&obj1;
ptr1->fun(); //Base1
ptr2=&obj2;
ptr2->fun(); //Base2
ptr1=&obj3;
ptr1->fun(); //Derived
ptr2=&obj3; //Base2
ptr2->fun();
return 0;
}
运行结果
--Base1--
--Base2--
--Derived--
--Base2--解析
Base2中的fun函数不是虚函数,Derived对Base2中的fun函数是隐藏不是重写,故编译器根据指针类型确定调用的函数。用Base2类型的对象指针去访问,会执行Base2中的fun()
5、写出下列程序的结果:
class A
{
public:
void FuncA()
{
printf( "FuncA called\n" );
}
virtual void FuncB()
{
printf( "FuncB called\n" );
}
};
class B
: public A
{
public:
void FuncA()
{
A::FuncA();
printf( "FuncAB called\n" );
}
virtual void FuncB()
{
printf( "FuncBB called\n" );
}
};
int main( void )
{
B b;
A *pa;
pa = &b;
A *pa2 = new A;
pa->FuncA(); //FuncA called
pa->FuncB(); //FunBB called
pa2->FuncA();//FuncA called
pa2->FuncB();//FUncB called
delete pa2;
return 0;
}
FuncA called
FuncBB called
FuncA called
FuncB called
6、写出下列程序的结果:
class Base
{
public:
Base(int j)
: i(j)
{
}
virtual ~Base()
{
}
void func1()
{
i *= 10;
func2();
}
int getValue()
{
return i;
}
protected:
virtual void func2()
{
i++;
}
protected:
int i;
};
class Child
: public Base
{
public:
Child(int j)
: Base(j)
{
}
void func1()
{
i *= 100;
func2();
}
protected:
void func2()
{
i += 2;
}
};
int main()
{
Base * pb = new Child(1);
pb->func1();
cout << pb->getValue() << endl; //12
delete pb;
return 0;
}
运行结果
12
参考解析:
Base中的func1是非虚函数,Child中的func1是对的Base中的func1的隐藏,通过Base类型对象指针去访问,是访问的Base的func1。而在func1中访问func2时,实际是this->func2(),由于指针指向的是Child类的对象,故会触发多态,访问的是Child中重写的func2。
7、写出下面程序的结果
class A
{
public:
virtual void func(int val = 1)
{
cout << "A->" << val << endl;
}
virtual void test()
{
func();
}
private:
long _a;
};
class B
: public A
{
public:
virtual
void func(int val = 10)
{
cout << "B->" << val << endl;
}
private:
long _b;
};
int main(void)
{
B b;
A *p1 = (A*)&b;
B *p2 = &b;
p1->func();//A->1
p2->func();//B->10
return 0;
}
B->1 B->10
三、简答题
1.什么是多态?虚函数的实现原理是什么?
多态:同一个操作,作用于不同的对象,可以有不同的解释,会产生不同的效果。
虚函数的实现原理:
解释版本一-黑马:
1.编译器发现类中有虚函数的时候,会创建一张虚表,表中存放了类中所有虚函数的入口地址。
2.编译器会在类中安插一个虚表指针,指针指向本类中的虚函数表。
3.继承了虚函数的派生类都有自己的虚函数指针------指针是从基类继承下来的,编译器为了初始化从基类继承过来的虚函数表指针,编译器在构造函数中添加了初始化虚函数指针的代码,让从父类继承过来的虚函数表指针指向了子类自己的虚函数表。
4.当编译器发现子类重写了父类中的虚函数时,那么子类重写的函数会覆盖掉父类的函数
5.基类的指针指向派生类对象的时候,再用该指针去调用虚函数,编译器根据当前指针的内存空间位置来找到函数的入口地址来调用函数,实现了多态。解释版本二:
1.当基类定义了虚函数后,会在基类对象的存储布局前面产生一个虚函数指针,该虚函数指针指向基类的虚函数表,虚函数表里存储的是类中各虚函数的入口地址。
2.派生类继承基类的时候,吸收基类的虚函数,产生自己的虚函数指针,指针指向自己的虚函数表,该虚表存放的是派生类自己的虚函数入口地址,派生类对基类虚函数重定义后,把虚表中的虚函数的入口地址进行替换。
2.不能设置为虚函数的函数有哪些?构造函数能设置为虚函数吗?为什么?
1.普通函数:普通成员函数无继承机制,无多态。发生在编译时。
2.静态成员函数:不依赖于对象而存在,每个类共用一个。发生在编译时。
3.内联成员函数:在编译时在代码中直接展开,而多态在运行时动态联编。
4.构造函数:构造函数不可继承,虚函数要能够继承。语义上也完全不合,虚函数是动态联编,运行前动作是不明确的,而构造函数是明确的
5.友元函数:将普通函数声明为友元,则由于是普通函数就不是虚函数。当友元函数是成员函数时,则可以作为其所在类的虚函数。但总之,友元关系与虚函数无关。
构造函数不能为虚函数,因为构造函数是用于初始化的,而虚函数的目的是为了在完全不了解细节的情况下处理对象的,如果将构造函数定义为虚函数,此时连对象都没有,因此不能。
3.在什么情况下析构函数要设置成虚函数?为什么?
一般来说,如果类中定义了虚函数,析构函数也应被定义为虚析构函数,尤其是类内有申请的动态内存,需要清理和释放的时候。
当基类的指针指向申请了动态内存的派生类指针,在释放的时候如果把基类的析构函数设置为虚函数,delete的时候,会先去调用派生类的析构函数,再去调用基类的析构函数,才能够完全释放。
原因:基类的析构函数被设为虚函数之后,派生类的析构函数自动变成了虚函数,编译器认为此时也是一种重写,会将基类的析构函数解释为destructor,派生类只要重定义了虚函数,也会解释为destructor,这种重写是名字不一样的重定义,之所以可以这么做就是因为对于任何一个类而言,一个类只有一个析构函数,具有唯一性。
4.什么是纯虚函数?什么是抽象类?抽象类的作用是什么?
1.纯虚函数是一种特殊的虚函数,是一种不能对虚函数给出有意义的实现,而把它声明为纯虚函数,它的实现就留给该基类的派生类去实现。
2.抽象类是类中含有至少一个纯虚函数,也称为抽象层。抽象层是连接起实现层和业务层的桥梁,面向对象的设计原则是开闭原则:对扩展开发,对修改关闭。
3.抽象层作为接口,只作声明,实现由派生类的实现层来完成。
5.什么是重载?什么是隐藏?什么是覆盖?他们之前的区别是?
1.重载:发生在同一个作用域内,函数名称相同,参数类型、个数、顺序不同(参数列表不同)
2.隐藏:发生在基类于派生类之间,派生类中的函数屏蔽了基类中的同名函数。如基类函数是非虚函数时,又或者派生类中有与基类中虚函数同名但参数列表或返回值不同的函数时,都会发生隐藏。
3.覆盖:发生在基类于派生类之间,同名虚函数,参数列表和返回值完全相同