下面哪种面向对象的方法可以让你变得富有( )
A: 继承 B: 封装 C: 多态 D: 抽象
( )是面向对象程序设计语言中的一种机制。这种机制实现了方法的定义与具体的对象无关,而对方法的
调用则可以关联于具体的对象。
A: 继承 B: 模板 C: 对象的自身引用 D: 动态绑定
面向对象设计中的继承和组合,下面说法错误的是?()
A:继承允许我们覆盖重写父类的实现细节,父类的实现对于子类是可见的,是一种静态复用,也称为白盒复用
B:组合的对象不需要关心各自的实现细节,之间的关系是在运行时候才确定的,是一种动态复用,也称为黑盒复用
C:优先使用继承,而不是组合,是面向对象设计的第二原则
D:继承可以使子类能自动继承父类的接口,但在设计模式中认为这是一种破坏了父类的封装性的表现
#include
using namespace std;
class A{
public:
A(char *s) { cout<<s<<endl; }
~A(){}
};
class B:virtual public A {
public:
B(char *s1,char*s2):A(s1) { cout<<s2<<endl; }
};
class C:virtual public A {
public:
C(char *s1,char*s2):A(s1) { cout<<s2<<endl;
};
class D:public B,public C {
public:
D(char *s1,char *s2,char *s3,char *s4):B(s1,s2),C(s1,s3),A(s1)
{ cout<<s4<<endl;}
};
int main() {
D *p=new D("class A","class B","class C","class D");
delete p;
return 0; }
A:class A class B class C class D B:class D class B class C class A
C:class D class C class B class A D:class A class C class B class D
class Base1 { public: int _b1; };
class Base2 { public: int _b2; };
class Derive : public Base1, public Base2 { public: int _d; };
int main(){
Derive d;
Base1* p1 = &d;
Base2* p2 = &d;
Derive* p3 = &d;
return 0; }
A:p1 == p2 == p3 B:p1 < p2 < p3 C:p1 == p3 != p2 D:p1 != p2 != p3
class A {
public:
virtual void func(int val = 1){ std::cout<<"A->"<< val <<std::endl;}
virtual void test(){ func();}
};
class B : public A {
public:
void func(int val=0){ std::cout<<"B->"<< val <<std::endl; }
};
int main(int argc ,char* argv[])
{
B*p = new B;
p->test();
return 0; }
A: A->0 B: B->1 C: A->1 D: B->0 E: 编译出错 F: 以上都不正确
参考答案:
为什么析构函数建议设置为虚函数
父类的析构函数强力建议设置为虚函数,这样动态释放父类指针所指的子类对象时,能够达到析构的多态
构造时,先构造父类,再构造子类部分,因此父类和子类的构造函数都会被调用;
析构时,要分情况:
(1) (父类析构函数不是虚函数)使用父类指针指向子类对象,析构该子类对象时,只会调用父类析构函数,因为不具多态性;代码理解:
class child:public parent{};
parent *p = new child(); //父类指针指向子类对象
delete p; //用父类指针调用,且未构成多态,调用的是父类析构函数,子类析构函数不会被调用
(2)上面存在一个问题,如果子类也分配的资源就会导致无法释放,使用虚函数可以解决上述问题。
class parent{
virtual ~parent();
};
class child:public parent
{
~child();
};
parent *p = new child(); //父类指针指向子类对象
delete p; //用父类指针调用,但构成多态,调用的是子类的析构函数。
//子类析构函数会先析构子类部分,再调用父类的析构函数析构父类部分
(3)对于第一种非虚函数的情况,如果是用子类指针指向子类对象,那么会先调用子类析构函数,再调用父类析构函数,子类释放子类中分配的,父类分配父类中分配的。
class child:public parent{};
child*p = new child(); //子类指针指向子类对象
delete p; //虽然未构成多态,但用子类指针调用,调用的是子类的析构函数。
//子类析构函数会先析构子类部分,再调用父类的析构函数析构父类部分
第三种方法也能够让父类和子类的资源都释放掉,使用virtual是为了使父类指针具有通用性。
2. 多态调用的条件
在继承中要构成多态还有两个条件:
1. 必须通过基类的指针或者引用调用虚函数
2. 被调用的函数必须是虚函数,且派生类必须对基类的虚函数进行重写
首先,p1会使程序奔溃,单p2调用则正常运行。
因为:类的成员函数存在公共代码段,空指针调用成员函数只要未涉及this指针即可正常运行。
p1是指针调用,调用的是虚函数,所以是多态调用。多态调用要到对象中找虚函数表指针,要用到this指针,故会报错。
p2是普通调用,无需this指针,且成员函数test中也未用到this指针,故不会报错。
为何static成员函数不能为virtual
虚函数依靠vptr和vtable来处理。vptr是一个指向虚函数表vtable的指针。因为vptr是类的成员,故需要用this指针来访问它,而静态成员函数没有this指针。所以static函数不能为virtual。
虚函数的调用关系:this -> vptr -> vtable ->virtual function
内联函数可以是虚函数吗?
答:
语法上:不可以,因为:inline函数在调用的地方展开了,故inline函数没有地址。而虚函数需要把函数地址放进虚函数表中。
实际中:可以,不过编译器就忽略inline属性,这个函数就不再是inline,因为虚函数要放到虚表中去。(vs下是这样,其他编译器请自行验证)
构造函数可以是虚函数吗?
答:不能,因为:
具体描述:如果构造函数是虚函数,那么调用构造函数前先要用虚表指针找到虚表,而此时对象中虚表指针都还没有初始化,因为对象中的虚表指针是在构造函数初始化列表阶段才初始化的。
简而言之:构造的时候才初始化虚表指针,而如果构造函数是虚函数,则构造前就要用到虚表指针,互相矛盾。
对象访问普通函数快还是虚函数更快?
答:如果是普通对象,是一样快的,因为不满足多态调用的两个条件,会像普通函数那样调用而不会走多态调用的路子。如果是对象指针或者是对象引用,则调用的普通函数快,因为构成多态,运行时调用虚函数需要到虚函数表中去查找。
虚函数表是在什么阶段生成的,存在哪的?
答:虚函数表是在编译阶段就生成的,一般情况下存在代码段(常量区)的。