- 重载父类派生类成员函数
看如下代码,判断输出结果:
class A {
public:
void test(int a) { cout << "1"; }
};
class B :public A {
public:
void test(float b) { cout << "2"; }
};
int main() {
A* a = new A;
B* b = new B;
a = b;
b->test(2);
a->test(1.1);
return 0;
}
下面对这段代码解读:
这里考的是函数重载,在类B中,隐藏了继承下类A的成员函数,用类B的指针或引用去调用test函数,打印的结果都是“2”。当用类A的指针或者引用指向类B时,C++中有一种切片功能,当父类指针或引用指向派生类时,会切掉派生类的部分,保留父类的部分。故a调用test时打印的结果是“1”。最后打印的结果是“21”。
切不能被参数类型迷惑,记住父类成员函数与派生类成员函数构成重载时,派生类中会将父类的进行隐藏;父类指针或引用调用派生类对象时,具有切片功能。
- 多态
看如下代码,判断输出结果:
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; delete pb;
}
首先在创建Child对象时,将父类Base中的i赋值为了1。用父类的指针去调用func1,而func1不是虚函数,所以构成重载,又根据切片功能,所以调用Base的fun1函数,接着调用func2函数,而func2函数是虚函数,且在派生类中完成了重写覆盖,故调用派生类Child的func2函数,i的值为12
谨记构成虚函数重写的条件
- 函数名参数类型个数均相同
- 父类中该函数必须有virtual修饰
- 虚继承
看如下代码:
class A {
public:
A(const char* s) { cout << s << endl; } ~A() {}
};
class B : virtual public A {
public:
B(const char* s1, const char* s2) :A(s1) { cout << s2 << endl; }
};
class C : virtual public A {
public:
C(const char* s1, const char* s2) :A(s1) { cout << s2 << endl; }
};
class D : public B, public C {
public:
D(const char* s1, const char* s2, const char* s3, const 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;
}
在这里先大致解释一下虚继承:当出现这种菱形继承时,为了防止数据冗余,往往采用虚继承。意思就是当类D继承类B与类C时,D中只包括一份类A的数据,不会出现两份类A的数据,这就是虚继承。
弄清楚虚继承后,当构造D对象时,先从最最最基础的类A开始构造,而后按照B与C的继承先后顺序构造,即A->B->C->D
如果我们改变一下B与C的继承顺序
- 理解多态的动态绑定
看一段代码:
class A {
public:
void foo() { printf("foo"); }
A() { bar(); }
protected:
virtual void bar() { printf("bar"); }
};
class B :public A {
public:
void foo() { printf("b_foo"); }
protected:
void bar() { printf("b_bar"); }
};
int main() {
A* p = new B;
return 0;
}
分析代码: 首先构造出派生类B对象出来,此时调用B的构造函数与A的构造函数,A的构造函数中又调用函数bar() ,按照前面两题的思路函数bar()是虚函数且在派生类中完成了重写,所以调用B中的bar()会打印出“b_bar”,但是这是错误的,因为编译器在绑定虚函数时是动态绑定,此时类A正在实例化,类B还没有完成实例化,类B中的bar()函数自然没有完成重写,所以虚函数表中的指针仍是指向类A的bar(),所以打印“bar”。
- 动态绑定:运行时绑定
- 静态绑定:编译时就已经确定