面向对象编程 具有 封装、继承、多态 三个主要特性
-
封装:
-
将属性和行为作为一个整体、一个类,来表现生活中的事物
-
将属性和行为加以权限控制,包括public 公共权限、protected 保护权限、private 私有权限这三种,从而让 自己的属性和行为 只让可信的类或对象操作,对不可信的进行隐藏。这样可以防止 外界干扰 或者 不确定性访问
-
-
继承
-
就是指 一个类 可以从 另一个类 中继承 属性和行为,前一个类称为 子类或派生类,后一个类称为 父类或基类。通过继承,子类可以使用父类的代码,并 添加新的功能 或 修改已有的功能,从而减少重复的代码
-
继承方式一共有三种:公共继承、保护继承、私有继承
-
一些细节:
-
访问权限:
-
注意,继承方式不影响子类对父类的访问!子类对父类的访问只看父类成员的访问权限即可。如下面三种继承方式都能访问父类中的public和protected成员:
class Teacher : /*public*/ /*protected*/ private Person { public:Teacher(const string& name, int age, const string& title): Person(name, age), m_title(title){} void ShowTeacherInfo(){ShowInfo(); //正确,public属性子类可见cout << "姓名:" << m_name << endl; //正确,protected属性子类可见//cout << "年龄:" << m_age << endl; //错误,private属性子类不可见 cout << "职称:" << m_title << endl; //正确,本类中可见自己的所有成员} private:string m_title; //职称 };
-
继承权限:
-
继承方式只是为了控制 子类的调用方(即子类对象)对父类的访问权限,不影响子类本身对父类的访问权限。如果父类的一个函数是public,子类采用是private或protected继承,那么在子类中可以访问这个函数,而子类的对象则无法访问这个函数(子类的对象其实就是外部)
class Person { public:...void ShowInfo(){cout << "姓名:" << m_name << endl;cout << "年龄:" << m_age << endl;} protected:string m_name; //姓名 private:int m_age; //年龄 }; class Teacher : private Person { public:...void ShowTeacherInfo(){ShowInfo(); //正确,public属性子类可见cout << "职称:" << m_title << endl; //正确,本类中可见自己的所有成员} private:string m_title; //职称 }; void TestPrivate() {Teacher teacher("李四", 35, "副教授");teacher.ShowInfo(); //错误,因为Teacher采用了private的继承方式,外部不可访问。cout << endl;teacher.ShowTeacherInfo(); }
注:public、protected、private三种继承方式,相当于把父类的public访问权限在子类中变成了对应的权限。 如protected继承,把父类中的public成员在本类中变成了protected的访问控制权限;private继承,把父类的public成员和protected成员在本类中变成了private访问控制权。
-
-
继承中 先调用父类构造函数,再调用子类构造函数,析构顺序与构造相反
-
成员或函数同名时,如何通过子类对象,访问子类和父类中的成员?
-
对于子类函数,直接访问即可
-
对于父类函数,需要再加上父类作用域
class Base { public:Base(){m_A = 100;} void func(){cout << "Base - func()调用" << endl;} void func(int a){cout << "Base - func(int a)调用" << endl;} public:int m_A; }; class Son : public Base { public:Son(){m_A = 200;} //当子类与父类拥有同名的成员函数,子类会隐藏父类中所有版本的同名成员函数,包括func(int a) //如果想访问父类中被隐藏的同名成员函数,需要加父类的作用域void func(){cout << "Son - func()调用" << endl;} public:int m_A; }; void test01() {Son s; cout << "Son下的m_A = " << s.m_A << endl;cout << "Base下的m_A = " << s.Base::m_A << endl;//加作用域 s.func();s.Base::func();//加作用域s.Base::func(10);//加作用域 }
-
另外:
-
如果是静态成员同名的话,处理方法也一样
-
多继承中,如果不同父类中出现了同名情况,子类使用时候要加作用域
-
-
-
菱形继承:两个派生类继承同一个基类,又有某个类同时继承者两个派生类。
class Animal { public:int m_Age; }; //继承前加virtual关键字后,变为虚继承 //此时公共的父类Animal称为虚基类 class Sheep : virtual public Animal {}; class Tuo : virtual public Animal {}; class SheepTuo : public Sheep, public Tuo {}; void test01() {SheepTuo st;st.Sheep::m_Age = 100;st.Tuo::m_Age = 200; cout << "st.Sheep::m_Age = " << st.Sheep::m_Age << endl;cout << "st.Tuo::m_Age = " << st.Tuo::m_Age << endl;cout << "st.m_Age = " << st.m_Age << endl; } int main() { test01(); return 0; }
-
菱形继承带来的主要问题是子类继承两份相同的数据,导致资源浪费以及毫无意义
-
利用虚继承可以解决菱形继承问题
-
-
-
-
多态:
-
多态分为两类
-
静态多态:函数重载 和 运算符重载 属于静态多态,复用函数名。静态多态的函数地址早绑定,在编译阶段就已经确定了函数地址
-
动态多态:即运行时多态,通过 派生类 和 虚函数 实现。动态多态的函数地址晚绑定,在运行阶段才确定函数地址
-
-
实现多态须同时满足的条件:
-
有继承关系
-
子类重写父类中的虚函数
-
-
使用多态须满足的条件
-
父类指针或引用指向子类对象
class Animal { public://Speak函数就是虚函数//函数前面加上virtual关键字,变成虚函数,那么编译器在编译的时候就不能确定函数调用了。virtual void speak(){cout << "动物在说话" << endl;} }; class Cat :public Animal { public:void speak(){cout << "小猫在说话" << endl;} }; class Dog :public Animal { public: void speak(){cout << "小狗在说话" << endl;} }; //我们希望传入什么对象,那么就调用什么对象的函数 //如果函数地址在编译阶段就能确定,那么静态联编 //如果函数地址在运行阶段才能确定,就是动态联编 void DoSpeak(Animal & animal) {animal.speak(); } // //多态满足条件: //1、有继承关系 //2、子类重写父类中的虚函数 //多态使用: //父类指针或引用指向子类对象 void test01() {Cat cat;DoSpeak(cat);Dog dog;DoSpeak(dog); } int main() {test01();return 0; }
-
-
一些细节:
-
纯虚函数:
-
在多态中,通常父类中虚函数的实现是毫无意义的,主要都是调用子类重写内容。因此可以将虚函数改为纯虚函数
-
纯虚函数语法:
virtual 返回值类型 函数名 (参数列表)= 0 ;
-
当类中有了纯虚函数,这个类也称为抽象类,它的特点:
-
无法实例化对象
-
子类必须重写抽象类中的纯虚函数,否则也属于抽象类
class Base { public://纯虚函数//类中只要有一个纯虚函数就称为抽象类//抽象类无法实例化对象//子类必须重写父类中的纯虚函数,否则也属于抽象类virtual void func() = 0; }; class Son :public Base { public:virtual void func() {cout << "func调用" << endl;}; }; void test01() {Base * base = NULL;//base = new Base; // 错误,抽象类无法实例化对象base = new Son;base->func();delete base;//记得销毁 } int main() {test01();return 0; }
-
-
-
虚析构和纯虚析构
-
多态使用时,如果子类中有属性开辟到堆区,父类指针如何调用子类的析构函数?
答:将父类中的析构函数改为虚析构或者纯虚析构,这样当delete 父类指针时,就可以将子类的数据彻底清除干净了,从而防止内存泄漏
-
虚析构和纯虚析构共性:
-
可以用父类指针 调用子类的析构函数,从而释放子类对象
-
都需要有具体的函数实现
-
-
虚析构和纯虚析构区别:
-
如果是纯虚析构,该类属于抽象类,无法实例化对象
-
-
虚析构语法:
virtual ~类名(){}
纯虚析构语法:
virtual ~类名() = 0; 类名::~类名(){}
class Animal { public:Animal(){cout << "Animal 构造函数调用!" << endl;}virtual void Speak() = 0;//在析构函数加上virtual关键字,变成虚析构函数//virtual ~Animal()//{// cout << "Animal虚析构函数调用!" << endl;//}virtual ~Animal() = 0; }; Animal::~Animal() {cout << "Animal 纯虚析构函数调用!" << endl; } class Cat : public Animal { public:Cat(string name){cout << "Cat构造函数调用!" << endl;m_Name = new string(name);}virtual void Speak(){cout << *m_Name << "小猫在说话!" << endl;}~Cat(){cout << "Cat析构函数调用!" << endl;if (this->m_Name != NULL) {delete m_Name;m_Name = NULL;}} public:string *m_Name; }; void test01() {Animal *animal = new Cat("Tom");animal->Speak();//通过父类指针去释放,会导致子类对象可能清理不干净,造成内存泄漏//怎么解决?给基类增加一个虚析构函数//虚析构函数就是用来解决通过父类指针释放子类对象delete animal; } int main() {test01();return 0; }
-
另外,如果子类中没有堆区数据,父类中可以不写虚析构或纯虚析构
-
-
-