济南制作网站企业,黄金网站app免费视频下载,wordpress调用外链图片,wordpress更改注册【欢迎关注编码小哥#xff0c;学习更多实用的编程方法和技巧】
1、类的继承
子类对象在创建时会首先调用父类的构造函数
父类构造函数执行结束后#xff0c;执行子类的构造函数
当父类的构造函数有参数时#xff0c;需要在子类的初始化列表中显式调用
Child(int i) : …【欢迎关注编码小哥学习更多实用的编程方法和技巧】
1、类的继承
子类对象在创建时会首先调用父类的构造函数
父类构造函数执行结束后执行子类的构造函数
当父类的构造函数有参数时需要在子类的初始化列表中显式调用
Child(int i) : Parent(在子类构造函数中的初始化列表进行显式调用父类构造函数)
析构函数调用的先后顺序与构造函数相反
继承与组合的混搭
类中的成员变量可以是其它类的对象组合
口诀先父母后客人再自己。
当子类中定义的成员变量与父类中的成员变量同名时
子类依然从父类继承同名成员
在子类中通过作用域分别符::进行同名成员区分
同名成员存储在内存中的不同位置 cout Parent::i Parent::i endl;
cout Child::i Child::i endl;
cout Parent::f Parent::f endl;
子类对象可以当作父类对象使用
子类对象在创建时需要调用父类构造函数进行初始化
子类对象在销毁时需要调用父类析构函数进行清理
先执行父类构造函数再执行成员构造函数
在继承中的析构顺序与构造顺序对称相反
同名成员通过作用域分辨符进行区分 2、函数的重写
父类中被重写的函数依然会继承给子类
默认情况下子类中重写的函数将隐藏父类中的函数
通过作用域分辨符::可以访问到父类中被隐藏的函数 Parent *p child;
p-print(); Parent rP child;
rP.print(); 打印输出的是父类的函数。
C编译器支持静态联编就是在编译阶段就可以确定下来的多态。
对于动态联编只有在运行时才能确定的对象类型编译器是不能作判断的最稳妥的做法就是使用父类的类型进行操作。 C与C相同是静态编译型语言
在编译时编译器自动根据指针的类型判断指向的是一个什么样的对象
所以编译器认为父类指针指向的是父类对象根据赋值兼容性原则这个假设合理
由于程序没有运行所以不可能知道父类指针指向的具体是父类对象还是子类对象
从程序安全的角度编译器假设父类指针只指向父类对象因此编译的结果为调用父类的成员函数
面向对象的新需求
根据实际的对象类型来判断重写函数的调用
如果父类指针指向的是父类对象则调用父类中定义的函数
如果父类指针指向的是子类对象则调用子类中定义的重写函数 实现了以上的功能就是面向对象中的多态。
多态
根据运行时实际的对象类型表现出不同的行为状态叫多态。
C中通过virtual关键字对多态进行支持
使用virtual声明的函数被重写后即可展现多态特性
虚函数
在父类函数声明前面加上virtual关键字使其成为虚函数。这时函数就表现出多态性。
函数重载
必须在同一个类中进行
子类无法重载父类的函数父类同名函数将被覆盖
重载是在编译期间根据参数类型和个数决定调用函数
函数重写
必须发生于父类与子类之间
并且父类与子类中的函数必须有完全相同的原型
使用virtual声明之后能够产生多态子类在重写时自动成为虚函数
多态是在运行期间根据具体对象的类型决定调用函数 隐藏
派生类中的函数与基类中的函数同名并且参数相同但基类函数不是虚函数
派生类中的函数与基类中的函数同名参数不同不管基类函数是否是虚函数基类函数都会被屏蔽。 child.Parent::func(); //使用作用域分别符可以调用
child.func(); //不可直接调用因为父类的同名函数被隐藏。 3、C中多态的实现原理
当类中声明虚函数时编译器会在类中生成一个虚函数表
虚函数表是一个存储类成员函数指针的数据结构
虚函数表是由编译器自动生成与维护的
virtual成员函数会被编译器放入虚函数表中
存在虚函数时每个对象中都有一个指向虚函数表的指针VPTR 每一个类都会由编译器自动生成一个虚函数表而且每个类只有唯一一个表。此类生成的每一个对象里面都隐含着一个指向该表的指针。
调用虚函数时会通过VPTR指针指向的虚函数表中查询该函数找到入口地址并调用。这个过程相对比较耗时因此执行效率相对比较低因此没有必要把所有的函数都设计为虚函数。 对象在创建的时候由编译器对VPTR指针进行初始化
只有当对象的构造完全结束后VPTR的指向才最终确定
父类对象的VPTR指向父类虚函数表
子类对象的VPTR指向子类虚函数表 构造函数中调用虚函数无法实现多态。 纯虚函数 面向对象中的抽象类
抽象类可用于表示现实世界中的抽象概念
抽象类是一种只能定义类型而不能产生对象的类
抽象类只能被继承并重写相关函数
抽象类的直接特征是纯虚函数 纯虚函数是只声明函数原型而故意不定义函数体的虚函数。
统一的格式
virtual 返回类型 函数名参数列表 0; 抽象类与纯虚函数
包含着纯虚函数的类叫抽象类
抽象类不能用于定义对象
抽象类只能用于定义指针和引用
抽象中的纯虚函数必须被子类重写
class Shape{public:virtual double area() 0;};class Rectangle : public Shape{public:Rectangle(double a, double b){m_a a;m_b b;}double area(){return m_a * m_b;}private:double m_a;double m_b;};class Circle : public Shape{private:double m_r;public:Circle(double r){m_r r;}double area(){return 3.14 * m_r * m_r;}};void func(Shape *s){cout s-area() endl;}int main(int argc, char *argv[]){Rectangle rect(3,2);Circle c(4);func(rect);func(c);return EXIT_SUCCESS;}多态与数组class Parent{protected:int i;public:virtual void func(){cout Parent::func() endl;}};class Child : public Parent{protected:int j;public:Child(int a, int b){i a;j b;}void func(){cout i i , j j endl;}};int main(int argc, char *argv[]){Parent *pp NULL;Child *pc NULL;Child ca[] {Child(1,2),Child(3,4),Child(5,6),Child(7,8)};pp ca;pc ca;cout sizeof(Parent) sizeof(Parent) endl;cout sizeof(Child) sizeof(Child) endl;cout setbase(16) pp pp endl;cout setbase(16) pc pc endl;pp-func();pc-func();pp;pc;cout setbase(16) pp pp endl;cout setbase(16) pc pc endl;//pp-func();//pc-func();return EXIT_SUCCESS;} 注Parent类对象占有8个字节因为需要维护一个虚函数表的指针也占4个字节。同样的Child对象除自身的变量j以外还要从父类继承一个变量i也要维护一个虚函数表的指针一共12个字节。 不要将多态应用于数组
指针运算是通过指针的类型进行的 编译时确定
多态是通过虚函数表实现的 运行时确定 虚基类及多重继承 被实际开发经验抛弃的多继承
工程开发中真正意义上的多继承是几乎不被使用的
多重继承带来的代码复杂性远多于其带来的便利
多重继承对代码维护性上的影响是灾难性的
在设计方法上任何多继承都可以用单继承代替 为了解决从不同途径继承来的同名数据成员造成的二义性问题 可以将共同基类设置为虚基类 。 这时从不同的路径继承过来的同名数据成员在内存中就只有一个拷贝。
class B : virtual public A
{
};
class C : virtual public A
{
};
这就是虚基类的来源。 C的接口设计
实际工程经验证明
多重继承接口不会带来二义性和复杂性问题
多重继承可以通过精心设计用单继承和接口代替
接口类只是一个功能说明而不是功能实现 。
子类需要根据功能说明定义功能实现 。
绝大多数面向对象语言都不支持多继承但都支持接口的概念
C中没有接口的概念
C中可以使用纯虚函数实现接口
class Interface
{
public:
virtual void func1() 0;
virtual void func2(int i) 0;
virtual void func3(int i, int j) 0;
};
接口类中只有函数原型的定义没有任何数据的定义。