继承与多态学习笔记
2013-04-29 22:38
330 查看
1.私有成员会被继承吗?
类的私有成员一定存在,也一定被继承到派生类中,只不过受到C++语法的限制,在派生类中访问基类的私有成员只能通过间接的方式进行。 在派生类中可以通过调用基类的公有函数的方式间接的访问基类的私有成员,包括私有成员变量和私有成员函数。怎么访问类的私有成员?请看下面的程序.......
#include <iostream> using namespace std; class A { private: int i; void PrivateFunc() { cout<<"class A private function\n"; //cout<<i; } public: A() { i = 5;} }; class B:public A { int j; public: void PrintBaseI() { cout<<"PrintBase() \n"; int *p = reinterpret_cast<int *>(this); //获取当前对象的首地址 } void UsePrintFunction() { cout<<"UsePrintFunction() \n"; void (*ptrfun)(); //定义函数指针,用来存放基类私有成员函数的入口地址 _asm { mov eax, A::PrivateFunc mov ptrfun, eax //mov ecx, a //如果有一个 A 的实例 a, 则将 a 的首地址放入ecx寄存器中 } cout<<"see: "; ptrfun(); //调用A::PrivateFunc() //注意! 此处A::PrintFunc函数中不能操作A的成员变量, //如果需要操作,则必须传递一个A的实例的 } public: B() { j = 911; } }; int main() { B b; cout<<"size A:"<<sizeof(A)<<"\nsize B:"<<sizeof(B)<<endl; //从输出可以看到B包含了A的成员变量 b.PrintBaseI(); b.UsePrintFunction(); return 0; }
2.怎样理解构造函数不能继承?
》类的构造函数、析构函数、赋值操作符函数是不能被继承的。》类的构造函数、析构函数、类型转换操作符函数是不能有返回值类型的。
基类对象的所有属性,派生类对象都有,基类对象的所有行为,派生类对象也都有。这就是继承的本质,也是谈论继承关系是否成立的最主要的依据。 设计模式中的里氏代换原则就是根据这个而来, 子类对象可以替换父类对象。
现在再来理解构造函数为什么不能继承:如果派生类可以继承基类的构造函数,就意味着可以利用基类的构造函数为派生类对象初始化,而这时不可能的。同样,也不能利用基类类的析构函数来销毁一个派生类对象,不能利用基类的复制操作符函数完成派生类对象之间的赋值操作。因此,不能继承构造函数、析构函数和赋值构造操作符函数是从继承的概念出发得出的结论。
赋值操作符函数原型:className & operator=(const className& cn)
同样,基类中声明的友元函数或友元类,永远只能是基类的友元,而不会是派生类的友元,因此,基类的友元是不会被继承的。
派生类并不继承基类的构造函数,派生类仅仅在自己的构造函数中调用基类的构造函数来初始化基类的数据域。
3.什么是虚拟继承?
C++允许多重继承,多重继承使得派生类与积累的关系变得很复杂,使用虚拟继承保证基类对象只会被构造一次,即在内存中只有一份拷贝。4.一个不能被继承的类
#include <iostream> using namespace std; class FinalParent //定义一个基类,不含任何数据成员 { protected: //它的构造函数为protected FinalParent(){} }; class FinalClass:private virtual FinalParent //私有虚拟继承基类,,也就让这个类终结了继承 { int num; public: FinalClass() { num = 5; } void show() { cout<<num<<endl; } }; class Finalchild:public FinalClass //继承了终结类,,不能调用基类的构造函数,所以初始化的时候会出错 { }; int main() { FinalClass s; s.show(); FinalChild b; //程序会报错 b.show(); return 0; }
任何一个类,只要有私有虚拟继承类FinalParent,就不能被继续继承,从而实现了“终结类”。
5.什么是RTTI
RTTI是Runtime Type Identification的缩写,意思是运行时类型识别。RTTI特性是C++语言加入比较晚的特性之一,这与C++对性能的要求有关。动态类型识别会在一定程度上造成运行效率的降低,所以,除非的确有必要,否则尽量少用RTTI。RTTI通过两个运算符实现:typeid和dynamic_cast。下面来一一介绍它们的用法。。。
1.typeid
typeid可以静态确定操作数的类型,也可以动态确定操作数的类型,这取决于操作数本身是否拥有虚函数。当typeid的操作数是一个基本数据类型的变量,或者是一个不带虚函数的对象时,typeid的运算符结果是在编译阶段确定的,所以是一种静态的类型判断。一个静态确定操作数类型的例子:
#include <iostream> using namespace std; template <typename T> void func(T a) { if(typeid(T) == typeid(int)) cout<<"Instance wien int"<<endl; else if(typeid(T) == typeid(double)) cout<<"Instance with double\n"; } int main() { func(1); func(3.3); return 0; }
typeid更多的时候是在运行时用来动态的确定指针或引用所指对象的类型,这是就要求typeid所操作的对象一定要有虚函数。
一个动态确定操作数类型的例子:
#include <iostream> using namespace std; class A { virtual void func(){} }; class B:public A { }; void reportA(A *pa) { if(typeid(*pa) == typeid(A)) cout<<"Type of *pa is:A\n"; else if(typeid(*pa) == typeid(B)) cout<<"Type of *pa is:B\n"; } void reportB(B *pb) { if(typeid(*pb) == typeid(A)) cout<<"Type of *pb is:A\n"; else if(typeid(*pb) == typeid(B)) cout<<"Type of *pb is:B\n"; } int main() { A a, *pa; B b, *pb; pa = &a; reportA(pa); pa = &b; reportA(pa); pb = static_cast<B*>(&a); reportB(pb); pb = &b; reportB(pb); return 0; }
如果将class A中函数func() 定义为普通函数(把前面的virtual去掉),那么typeid(*pa)的结果永远都是typeid(A),而typeid(*pb)的结果也永远都是typeid(B).
C++中的一切“动态机制”,包括虚函数、RTTI等,都必须通过指针或引用来实现。
2.dynamic_cast的用法
dynamic_cast是一个“纯”动态操作符,因此,它只能用于指针或引用间的转换。而且dynamic_cast运算符所操作的指针或引用,指针所指向的对象或引用所绑定的对象必须拥有虚函数成员,否则会出现编译错误。C++编译器认为从派生类转换为基类指针总是安全的 (向上转换)。这时根本没有必要使用dynamic_cast,就算用了,它也不会工作,成为摆设。
具体的说,dynamic_cast可以进行如下类型的转换:
1.在指向基类的指针(引用)与指向派生类的指针(引用)之间进行转换;
2.在多重继承的情况下,在派生类的多个基类之间进行转换(称为交叉转换:crosscast )
所以,一定要明确:dynamic_cast用来向下转换(由基类指针转换为派生类指针)的安全性检查总是有效的。
一个dynamic_cast的例子:(主要检车的是向下转换)
#include <iostream> using namespace std; class A { public: int i; virtual void show() //基类,一定要有虚函数,否则使用dynamic_cast时会报错 { cout<<"class A\n"; } A(){ i = 1;} }; class B:public A { public: int j; void show() { cout<<"class B\n"; } B(){ j = 2;} }; class C:public B { public: int k; void show() { cout<<"class C\n"; } C(){ k = 3;} }; int main() { A a, *pa = NULL; B b, *pb; C c, *pc; pb = dynamic_cast<B*>(pa); //pa=NULL, 所以转换会失败 pa = &b; pb = dynamic_cast<B*>(pa); //pa实际指向的类型是class B,所以转换成B*肯定正确 if(pb) { pb->show(); cout<<pb->j<<endl; } else cout<<"Convertion failed\n"; pc = dynamic_cast<C*>(pa); //pa指向B类型, 将pa转换成C* 会失败 //pc = static_cast<C*>(pa); //这一句可以将 pa 转换成 C* if(pc) { pc->show(); cout<<pc->k<<endl; } else cout<<"Convertion failed\n"; return 0; }
交叉转换: 交叉转换是在两个“平行”的类对象之间进行。本来它们没有什么关系,从其中的一种转换为另一种是不可能
的,但是,如果 类A 和 类B 都是某个派生类C的基类,而指针所指向的对象本身就是一个类C的对象,那么该对象既可
以被视为类A的对象,也可以被视为类B的对象, 类型A*(A&) 和 B*(B&)之间的转换就成为可能
一个交叉转换的例子:
#include <iostream> using namespace std; class A { public: int num; A(){ num = 4;} virtual void funcA(){} //虚函数不可少 }; class B { public: int num; B(){ num = 5;} virtual void funcB(){} //虚函数不可少 }; class C:public A, public B { }; int main() { C c; A *pa; B *pb; pa = &c; //派生类指针转换为基类指针总是安全的 cout<<pa->num<<endl; //输出class A中的num pb = dynamic_cast<B*>(pa); //交叉转换 //pb = static_cast<B*>(pa); //会报错 cout<<"pa = "<<pa<<endl; if(pb) { cout<<"pb = "<<pb<<endl; cout<<"Convertion succeeded\n"; cout<<pb->num<<endl; //输出Class B中的num } else cout<<"Convertion failed\n"; return 0; }
在类C的对象中,基类A和基类B的成员所占的位置是不同的,所以讲A*转换成B*时,要对指针的位置进行调整。
相关文章推荐
- Java学习笔记——继承、接口、多态
- 【Java学习笔记】继承和多态
- java学习笔记------继承和多态 覆盖
- 【Python学习笔记】面向对象编程:继承和多态
- C++学习笔记--继承与多态
- 【Python】学习笔记——-7.3、继承和多态
- 非专业码农 JAVA学习笔记 4 java继承和多态
- c++学习笔记之继承和多态
- 黑马程序员--IOS学习笔记(封装、继承、多态)
- Java学习笔记-对象-继承-接口-多态-内部类
- 黑马程序员java学习笔记——面向对象的特征封装、继承和多态
- JAVA学习笔记(初级)--继承与多态
- Java学习笔记之六——Java类的继承和多态(1)
- 黑马程序员-自己总结的java学习笔记(5)继承,接口,多态。
- C++入门学习笔记(四)--继承与多态
- java程序设计基础学习笔记:继承和多态
- Java编程思想学习笔记_2(继承和多态)
- c#学习笔记五 面向对象编程的基本概念 接口 继承和多态
- java学习笔记继承和多态5.24
- 【Java学习笔记之十六】浅谈Java中的继承与多态