一、前文
面向对象编程的三大特性:封装、继承和多态。
- 封装通过将数据和方法封装在对象中,提高了数据的安全性和代码的可维护性。
- 继承允许新类从现有类继承属性和方法,实现代码复用和扩展。
- 多态则通过统一的接口实现不同的行为,提高了代码的灵活性和扩展性。
封装:
- 数据和方法放到一起,把想给访问定义成公有,不想给你访问定义成私有和保护
- 一个类型放到另一个类型里面,通过typedef成员函数调整,封装另一个全新的类型相当于武器库
二、继承
2.1 继承概念
- 继承(inheritance)机制是面向对象程序设计使代码可以复用的最重要的手段,它允许程序员在保持原有类特性的基础上进行扩展,增加功能,这样产生新的类,称派生类。
- 它允许一个类(称为子类或派生类)从另一个类(称为父类或基类)继承属性和方法
- 每个派生类对象都是一个特殊的基类对象,派生类在基类的基础上进行扩展,增加新功能
- 继承呈现了面向对象程序设计的层次结构,体现了由简单到复杂的认知过程。
- 以前我们接触的复用都是函数复用,继承是类设计层次的复用
using namespace std; class Person { public: void Print() { cout << "name:" << _name << endl; cout << "age:" << _age << endl; } private: string _name = "peter";//名字 int _age = 18;//年龄 }; class Student : public Person { protected: int _stuid;//学号 }; class Teacher : public Person { protected: int jobid;//工号 }; int main() { Student s; Teacher t; s.Print(); t.Print(); return 0; }
从代码中可以看出Person是父类或基类,Student是子类或派生类
2.2 继承关系和访问限定符
2.3 继承基类成员访问方式间变化
【注意】:圈出来的是实践中最常见的设计方式,主要了解这部分。
2.4 基类的private成员
基类private成员在派生类中无论以什么方式继承都是不可见的,这里的不可见是指基类的私有成员还是被继承到了派生类中,但是语法上限制派生类对象不管在类里面还是在类外面都是不能去访问
class Person { public: void Print() { cout << "name:" << _name << endl; cout << "age:" << _age << endl; } protected: string _name = "peter"; private: int _age = 18; }; class Student : public Person { public: void func() { cout << _name << endl; cout << _age << endl; } protected: int _stuid; };
按照上面的意思,Student继承了Person属性和方法,但是由于_age是属于私有成员是不可见且不能被访问。
2.4.1 公共接口间接使用基类的私有变量
class Student : public Person { public: void func() { //间接的使用 Print(); } protected: int _stuid; };
由于基类private成员在派生类中式不能被访问的,如果基类成员不想在类外直接被访问,但是需要在派生类中能访问。这样子可以将基类private成员定义为protected成员,可以看出来保护成员限定符是因继承才出现的
思考:
- 类可以使用关键字class或者struct,他们只是默认的继承方式不同,为什么不使用struct呢?
解释:
- struct默认继承方式和访问限定符都是公有的
- class默认继承方式和访问限定符都是私有的
- 最好函数显式写出继承方式,偷偷摸摸的不好!
2.5小结
- 基类的私有成员在子类都是不可见。基类的其他成员在访问方式(取最小的权限)== Min(成员在基类的访问限定符、继承方式)public > prrotected > private
- 在实际中一般常用的是public继承,而不是protected/private继承,也不提倡使用protected/private继承,因为protected/private继承下来的成员都是只能在派生类的类里面使用,实际中扩展维护性不强
2.6 基类和派生类对象赋值转换
2.6.1 派生类赋值基类
每个派生类对象都是一个特殊的基类对象,派生类在基类的基础上进行扩展,增加新功能
class Person { protected: string _name; string _sex; int _age; }; class Student : public Person { public: int _No; //学号 };
派生类对象可以赋值基类的对象/基类的指针/基类的引用。这里有个形象的说法叫切片或者切割。(将派生类中父类那部分切来赋值过去)。
int main() { Student sobj; //子类对象可以赋值给父类对象 Person pobj = sobj; //子类对象可以赋值给父类指针 Person* pp = &sobj; //子类对象可以赋值给父类引用 Person& rp = sobj; }
2.6.2 基类对象不能赋值给派生类对象(建立在public继承)
int main() { Student sobj; Person pobj = sobj; Person* pp = &sobj; sobj = pobj; }
基类是不能直接赋值给派生类,因为可能派生类包含了基类没有的额外属性和方式,而且基类是不具备这些属性和方式,对此不能直接赋值。
2.6.3 强制基类赋值派生类
基类的指针或者引用可用通过强制类型转化赋值给派生类的指针或者引用。但是必须是基类的指针是指向派生类对象时才是安全的。
int main() { Student sobj; Person pobj = sobj; Person* pp = &sobj; 1.0 //pp存放这子类实例的地址,再进行强转 pp = &sobj; // 这种情况转换时可以的。 Student* ps1 = (Student*)pp; ps1->_No = 10; 2.0 //pp存放父类实例的地址 pp = &pobj; // 这种情况转换时虽然可以,但是会存在越界访问的问题 Student* ps2 = (Student*)pp; ps2->_No = 10; }
注意:
- 以上提前需要满足是公有继承
- 切割/切片是赋值兼容,编译器进行特殊处理,不产生临时对象,将子拷贝给父。内置类型值拷贝,自定义类型调用拷贝构造。
【C++】面向对象编程的三大特性:深入解析继承机制(二)https://developer.aliyun.com/article/1617389