面向对象
封装基础上了解C++的继承、多态;
了解什么叫做继承,继承方式,继承内容,如何使用继承。
了解C++
继承
继承定义
-
定义:
a. 面向对象程序设计中最重要的一个概念是继承。继承允许我们依据另一个类来定义一个类,这使得创建和维护一个应用程序变得更容易。这样做,也达到了重用代码功能和提高执行效率的效果。
b. 当创建一个类时,您不需要重新编写新的数据成员和成员函数,只需指定新建的类继承了一个已有的类的成员即可。
这个已有的类称为基类,新建的类称为派生类。
c. 继承代表了 is a 关系。例如,哺乳动物是动物,狗是哺乳动物,因此,狗是动物,等等。class 基类名{}; class 派生类名: pulbic 基类名{//public private protected //派生类新增的数据成员和成员函数 }; //____多继承___ class <派生类名>:<继承方式1><基类名1>,<继承方式2><基类名2>,… { <派生类类体> };
-
继承权限+访问权限
继承内容
继承中的构造与析构函数(不会被继承)
-
调用(构造和重名调用不同)
b. 子类对象在创建时会首先调用父类的构造函数
c. 父类构造函数执行完毕后,才会调用子类的构造函数
d. 当父类构造函数有参数时,需要在子类初始化列表(参数列表)中显示调用父类构造函数
e. 析构函数调用顺序和构造函数相反#include <iostream> #include <string> using namespace std; class A { public: int a; A(int a) { cout << a <<"A"<< endl; } protected: int b; private: int c; }; class B :public A//public |->protected |->private { public: B(int a) :A(a) { cout << a << "b" << endl; } }; void main() { B b(2); system("pause"); }
-
继承中构造和析构函数调用顺序;类嵌套构造函数和析构函数调用顺序这里记忆
-
继承中的父子重名
父子方法重名,子类会隐藏父类(包括父类重写,如果想要调用父类的方法,需要注明作用域。)
成员属性与方法一样son.fun();//重名子类方法 son.father::fun();//重名父类方法
-
static静态成员处理,静态成员属性可以继承。静态成员函数和非静态成员函数的共同点:a.他们都可以被继承到派生类中。b.如果重新定义一个静态成员函数,所有在基类中的其他重载函数会被隐藏。c.如果我们改变基类中一个函数的特征,所有使用该函数名的基类版本都会被隐藏。静态成员函数不能是虚函数(virtual function)
-
多继承 ,多继承容易让代码逻辑复杂、思路混乱,一直备受争议,中小型项目中较少使用,后来的 Java、C#、PHP 等干脆取消了多继承。菱形继承二义性问题虚继承(继承虚基类)解决 virtual。工程开发中真正意义上的多继承是几乎不被使用,因为多重继承带来的代码复杂性远多于其带来的便利,多重继承对代码维护性上的影响是灾难性的,在设计方法上,任何多继承都可以用单继承代替。
class BigBase{ public: BigBase(){ mParam = 0; } void func(){ cout << "BigBase::func" << endl; } public: int mParam; }; #if 0 //虚继承 class Base1 : virtual public BigBase{}; class Base2 : virtual public BigBase{}; #else //普通继承 class Base1 : public BigBase{}; class Base2 : public BigBase{}; #endif class Derived : public Base1, public Base2{};
-
类嵌套、类继承构造和析构函数的调用顺序;重载函数、 重名函数调用。
区别 类嵌套 类继承 定义 class A {class B{};}; class A{}; class B:public A{}; 造析构函数调用顺序 Ag、Bg、Bx、Ax Ag、Bg、Bx、Ax 间访问权限 可以看以前博客 也可以看网络 区别 重载 重写(覆盖) 重定义(隐藏) 数名 相同 相同 相同 用域 同一作用域 子类父类 子类父类 数 不同 相同 不同1 回值 可以不同 相同 不同2
多态
c++支持编译时多态(静态多态重载)和运行时多态(动态多态),运算符重载和函数重载就是编译时多态,而派生类和虚函数实现运行时多态。
-
父类指针、引用指向子类对象---->多态:
#include <iostream> #include <string> using namespace std; class Animal { public: void speak() { cout << "动物在唱歌..." << endl; } }; class Dog : public Animal { public: void speak() { cout << "小狗在唱歌..." << endl; } }; void DoBussiness(Animal& animal) { animal.speak();//speak函数类型已经绑定好了(静态联编,未运行编译阶段便确定了地址) //将静态链接改变为动态链接,父类声明virtual,子类中可写可不写(建议写增加可读性) } void test() { Dog dog; DoBussiness(dog); } void main() { test(); system("pause"); }
运行结果: 动物在唱歌
问题抛出: 我们给DoBussiness传入的对象是dog,而不是animal对象,输出的结果应该是Dog::speak。
问题解决:virtual 虚函数解决class Animal{ public: virtual void speak(){//虚函数virtual cout << "动物在唱歌..." << endl; } }; class Dog : public Animal{ public: virtual void speak(){ cout << "小狗在唱歌..." << endl; } }; void DoBussiness(Animal& animal){ animal.speak(); } void test(){ Dog dog; DoBussiness(dog); }
virtual关键字使得class内部结构改变,virtual 增加了一个指针vfptr(virtual funtion pointer)指向虚函数表;继承时:构造函数执行,所有虚函数表指针指向自身虚函数表(此操作看不到);自身虚函数表覆盖(重写)继承的虚函数表。
Animal * animal = new Cat;//父类指针指向子类 animal.speak(); //函数内部结构 *(int *)*(int *)animal;//函数地址 void(*)()//函数指针 (void(*)() (*(int*)*(int *)animal)) ();//函数调用 /* 重写原理: 当父类有virtual关键字定义的虚函数时,内部多了一个vfptr指针,vfptr指向了虚函数表; 父类中结构 vtptr &animal::peak;子类中进行继承,会继承虚函数表; 构造函数中,会构造虚函数指针,指向自己的虚函数表; 如果发生了重写,会替换虚函数表中的speak 为&cat::speak; */
指针 指针指向的表生成,重写(覆盖)。
扩展开发,修改关闭!父类指针指向子类对象 -
纯虚函数(virtual+普通函数)
a. 如果父类中有纯虚函数,那么子类继承必须要实现纯虚函数;
b. 如果父类中有纯虚函数,那么此函数无法实例化(相当于一个声明);
c. 如果类中有纯虚函数,又称抽象类。 -
虚析构函数
虚析构 纯虚析构 virtual ~ 类名(){};声明实现不限 类内声明 virtual~ 类名()=0 ; 类外实现 A::~A(){} 过父类指针指向子类对象释放不干净导致的问题; 不影响实例化 出现纯虚构函数,默认为抽象类,不可以实例化对象 -
转型
向下转型 向上转型 父类转子类(基类转派生类) 子类转父类(派生类转基类) 不安全 安全 如果多态() 都是安全的