继承:可以使用原来的代码,代码复用
多态:代码复用、接口复用,用基类的指针“根据对象”调用“指定对象的函数”。
1、继承、访问修饰符
2、C++不是类型安全的语言
3、赋值兼容问题:类型转换
4、
static与继承在基类中定义的静态成员,将被所有派生类共享;根据静态成员自身的访问特性和派生类的继承方式,在类层次体系中具有不同的访问属性;派生类中访问静态成员,用下面形式:类名::成员 、 对象名.成员 。【讨论:在父类和子类中声明、定义同名的静态变量b,子类中的b会不会覆盖父类中的b】
#include <iostream>
using namespace std;
class B
{
public:B(){
cout<<
"&B::b = "<<&b<<endl; }
static int b;
};
class D:
public B
{
public:D(){
cout<<
"&D::b = "<<&b<<endl;
cout<<
"&B::b = "<<&B::b<<endl;}
static int b;
};
int D::b =
0;
int main()
{D d;
}
5、继承中的构造
①子类中的父类如何进行初始化?答:用成员初始化列表调用父类的构造函数进行初始化子类中的父类。
②继承与组合混搭的情况下,如何进行构造?答:构造顺序:基类、组合类、派生类
③继承中同名成员变量的处理方法?
#include <iostream>
using namespace std;
class A
{
public:A(
int aa):a(aa){
cout<<
"A"<<endl;}
int get_A(){
return a;}
void show_A(){
cout<<
"a="<<a<<endl;}
protected:
private:
int a;
};
class B
{
public:B(
int bb):b(bb){
cout<<
"B"<<endl;}
int get_B(){
return b;}
void show_B(){
cout<<
"b="<<b<<endl;}
protected:
private:
char b;
};
class D:
public B
{
public:D(
int bb,
int dd,
int aa):a1(aa),B(bb),d(dd){
cout<<
"D"<<endl;}
void show_D(){a1.show_A();
cout<<
"b="<<get_B()<<endl<<
"d="<<d<<endl; }
protected:
private:
int d;A a1;
};
int main()
{D d(
1,
2,
3); d.show_D();
}
6、多继承
#include <iostream>
using namespace std;
class A
{
public:
A(
int i=
1):a(i){}
int a;
private:};
class B
{
public:
B(
int i=
1):a(i){}
int a;
private:};
class C:
public A,
public B
{
public:
C(
int i=
0):A(i),B(i){}
};
int main()
{C c1;cout<<c1.A::a<<endl; cout<<c1.B::a<<endl;
} 虚继承图示见下:
7、多态(发生的条件)父类的指针或引用指向子类的对象,并调用子类重写的父类的虚函数。
多态成立的3个条件:有继承子类重写父类的虚函数父类的指针或引用指向子类对象
【总结】
动态联编:当有
virtual修饰时,调用子类?父类?的函数由“____对象”决定!
静态编译:当无
virtual修饰时,调用子类?父类?的函数由“____指针”决定!
8、类与类之间的关系
1.组合:包含关系
2.关联:一个类部分的使用另外一个类。通过类之间成员函数的相互联系,定义友元或对象参数传递实现。(了解)
3.继承
4.友元类
9、多态面试题
①多态的实现效果:多态:同样的调用语句有多种不同的形态
②多态的三个条件:继承、子类重写父类的虚函数、父类指针或引用指向子类对象
③多态的C++实现:虚函数表、虚函数指针、动态绑定
virtual关键字,告诉C++编译器要进行“迟绑定”的动态联编;不要根据指针类型判断如何调用,而是要根据指针所指向的实际对象类型来判断如何调用。
④多态的理论基础:动态联编PK静态联编,根据实际的对象类型来判断重写函数的调用。
⑤多态的重要意义:设计模式的基础
⑥实现多态的理论基础:函数指针作函数参数
#include <iostream>
using namespace std;
class B
{
public:
virtual void f(
int i) { }
};
class D:
public B
{
public:
void f(
int i) { }
};
void g(B& b)
{b.f(
1);
}
void g(B* b)
{b->f(
1);
}
void main()
{D dd;g(dd); g(&dd);
}
以上面代码为例,分析一下要实现多态,C++编译器应该动什么手脚?
①第一个动手脚的地方:
virtual void f(
int i)中的
virtual,动了什么手脚?虚函数表、虚函数指针
②第二个动手脚的地方:
void g(B& b)中的b,我们怎么知道传入的是父类的对象?还是子类的对象?传入的对象中有一个虚函数表指针*Vptr,通过该虚函数表指针*Vptr找到对应的虚函数表,之后再找到要调用的函数的函数指针,通过函数指针对函数进行调用。因此,加上
virtual关键字声明后的函数的调用结果是由“对象类型”决定的!C++中多态实现原理当类中声明虚函数时,C++编译器会在类中生成一个虚函数表虚函数表是一个存储类成员函数指针的数据结构虚函数表是由编译器自动生成和维护的
virtual成员函数会被编译器放入虚函数表中存在虚函数表时,每个对象中有一个指向虚函数表的指针(vptr指针)
【答案:面试题1、2】
【说明1】
通过虚函数表指针Vptr调用重写函数是在程序运行时进行的,因此需要通过寻址操作才能确定真正应该调用的函数;而普通成员函数是在编译时就确定了调用的函数,因此:在效率上,虚函数的效率要低很多。
【说明2】
出于效率问题,没有必要把所有的成员函数都声明为虚函数。
【答案:面试题4】可以把所有的函数声明为虚函数,但是会降低代码的执行效率。因此,不使用多态时,不要把函数声明为虚函数。
父类中的虚函数表指针能否被子类继承?
【答案:面试题
5】构造函数中调用虚函数不能实现多态!
为什么不能?Vptr指针的初始化过程是分步完成的
1.构造子类对象过程中,先执行父类的构造函数,此时C++编译会初始化子类的Vptr指针,让Vptr指针暂时先指向父类的函数表。因此,在父类的构造函数中调用虚函数
virtual void f(),此时调用的是父类中的f().
2.当父类的构造函数执行完成后,再执行子类的构造函数,此时C++编译会重新修改子类的Vptr指针,让Vptr指针真正的指向子类的函数表。同样:析构函数中调用虚函数不能实现多态!
#include <iostream>
using namespace std;
class B
{
public:B(){f();}
virtual void f() {
cout<<
"父类"<<endl; }
virtual ~B(){f();}
};
class D:
public B
{
public:D(){}
virtual void f() {
cout<<
"子类"<<endl; }~D(){}
};
int main()
{D d;
return 0;
}
【答案:面试题
8】
#include <iostream>
using namespace std;
class A
{
public:
virtual ~A(){
cout<<
"析构父类A"<<endl;}
};
class B:
public A
{
public:~B(){
cout<<
"析构子类B"<<endl;}
};
int main()
{A *p =
new B;
delete p;
}
总结
以上是生活随笔为你收集整理的第九天2017/04/18(2、类的继承、面试题:继承访问修饰符、组合、static、构造、多态)的全部内容,希望文章能够帮你解决所遇到的问题。
如果觉得生活随笔网站内容还不错,欢迎将生活随笔推荐给好友。