欢迎访问 生活随笔!

生活随笔

当前位置: 首页 >

第九天2017/04/18(2、类的继承、面试题:继承访问修饰符、组合、static、构造、多态)

发布时间:2025/3/21 40 豆豆
生活随笔 收集整理的这篇文章主要介绍了 第九天2017/04/18(2、类的继承、面试题:继承访问修饰符、组合、static、构造、多态) 小编觉得挺不错的,现在分享给大家,帮大家做个参考.

继承:可以使用原来的代码,代码复用
多态:代码复用、接口复用,用基类的指针“根据对象”调用“指定对象的函数”。

1、继承、访问修饰符//C++类成员的3种访问级别//C++继承的3种继承方式 2、C++不是类型安全的语言 3、赋值兼容问题:类型转换 4static与继承在基类中定义的静态成员,将被所有派生类共享;根据静态成员自身的访问特性和派生类的继承方式,在类层次体系中具有不同的访问属性;派生类中访问静态成员,用下面形式:类名::成员 、 对象名.成员 。/*在类中声明了static静态变量,如果不在类外进行初始化,就想访问 该static变量,此时会发生编译错误,错误信息见下: error LNK2001: 无法解析的外部符号 "public: static int B::b" 因此,在类中声明了static变量后,在使用该静态变量之前,一定要 先进行初始化。 */【讨论:在父类和子类中声明、定义同名的静态变量b,子类中的b会不会覆盖父类中的b】 #include <iostream> using namespace std; /* 在子类和父类中有同名的static成员变量static int b; 子类中有两个static变量b,只不过在子类中不加::作用域修饰符直接对b进行访问时,与普通变量一样,父类中的static变量b被子类中的static变量b覆盖掉。 */ class B { public:B(){cout<<"&B::b = "<<&/*B::*/b<<endl; //默认访问的是本类中的b}static int b; }; //int B::b = 0; //初始化static成员变量时,相当于告诉C++编译器给b变量开辟内存class D:public B { public:D(){cout<<"&D::b = "<<&/*D::*/b<<endl; //默认访问的是本类中的b,即子类的b把父类的b覆盖cout<<"&B::b = "<<&B::b<<endl;//用::作用域访问符可以访问父类中的b}static int b;//初始化static成员变量时,相当于告诉C++编译器给b变量开辟内存 }; int D::b = 0; //初始化static成员变量时,相当于告诉C++编译器给b变量开辟内存int main() {D d; }5、继承中的构造 ①子类中的父类如何进行初始化?答:用成员初始化列表调用父类的构造函数进行初始化子类中的父类。 ②继承与组合混搭的情况下,如何进行构造?答:构造顺序:基类、组合类、派生类 ③继承中同名成员变量的处理方法?#include <iostream> using namespace std; class A //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 //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 //D类用作派生类 {//子类中的父类如何进行初始化?答:用成员初始化列表进行初始化子类中的父类 public:D(int bb,int dd,int aa):a1(aa),B(bb),d(dd)//调用构造函数的顺序:父类、组合类、子类{cout<<"D"<<endl;}void show_D(){a1.show_A(); //D类public继承于B类,因此D中有B中的所有成员,包括get_B()函数,可以在D中调用从B中继承来的get_B()函数。cout<<"b="<<get_B()<<endl<<"d="<<d<<endl; //get_B()} protected: private:int d;A a1; //组合 };int main() {D d(1,2,3); //B A D d.show_D(); } 6、多继承 //问题:多继承中会发生“二义性”! //结论: //C多继承于A和B,A、B中有同名的成员变量a。 //在C对象c访问a时,要用A::a或者B::a明确指出访问的是A中的a还是B中的a。#include <iostream> using namespace std; class A { public:A(int i=1):a(i){} //虽然默认值是1,但是在构造C的对象时,传入了默认参数0给i,因此i=0int a; private:}; class B { public:B(int i=1):a(i){} //虽然默认值是1,但是在构造C的对象时,传入了默认参数0给i,因此i=0int a; private:}; class C:public A,public B { public:C(int i=0):A(i),B(i){} }; int main() {C c1;//cout<<c1.a<<endl; //编译失败:error: 对“a”的访问不明确 cout<<c1.A::a<<endl; //编译运行成功,结果:0cout<<c1.B::a<<endl; //编译运行成功,结果:0 } 虚继承图示见下:

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) { }//第一个动手脚的地方:virtual void f(int i)中的virtual,动了什么手脚? }; class D:public B { public:void f(int i) { } };void g(B& b)//第二个动手脚的地方:void g(B& b)中的b,我们怎么知道传入的是父类的对象?还是子类的对象? {b.f(1); } void g(B* b)//第二个动手脚的地方:void g(B* 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; //给父类的指针new一个子类的对象delete p; //在进行delete父类的指针的时候,如果父类的虚函数中不加virtual,则只会调用父类的析构函数,子类的析构函数不会被调用,造成内存泄漏。 }

总结

以上是生活随笔为你收集整理的第九天2017/04/18(2、类的继承、面试题:继承访问修饰符、组合、static、构造、多态)的全部内容,希望文章能够帮你解决所遇到的问题。

如果觉得生活随笔网站内容还不错,欢迎将生活随笔推荐给好友。