C++面向对象编程之继承
目录一、继承的概念及定义1.1 继承的基本概念1.2 继承的定义与访问方式1.2.1 定义格式1.2.2 继承方式与访问权限1.3 继承类模板二、基类与派生类的转换2.1 向上转型Upcasting2.2 向下转型Downcasting三、 继承中的作用域3.1 隐藏规则3.2 函数隐藏四、派生类的默认成员函数4.1 六大默认成员函数在继承中的行为4.2 代码示例4.3 实现不能被继承的类五、继承与友元六、 继承与静态成员七、多继承与菱形继承7.1 继承模型7.2 虚继承7.3 IO库中的菱形虚拟继承八、继承与组合8.1 关系类型比较8.2 选择原则九、总结一、继承的概念及定义1.1 继承的基本概念继承Inheritance)是面向对象程序设计OOP中最核心的机制之一它允许我们基于已有类创建新类实现代码复用和功能扩展。通过继承派生类子类可以自动获得基类父类的属性和方法同时可以添加自己特有的成员。没有继承时的代码冗余问题class Student { public: void identity() { /* ... */ } void study() { /* ... */ } protected: string _name peter; string _address; string _tel; int _age 18; int _stud; // 学号 }; class Teacher { public: void identity() { /* ... */ } void teaching() { /* ... */ } protected: string _name 张三; int _age 18; string _address; string _tel; string _title; // 职称 };上述代码中Student和Teacher类有许多相同的成员变量和函数造成了代码冗余。使用继承优化后的代码class Person { public: void identity() { cout void identity() _name endl; } protected: string _name 张三; string _address; string _tel; int _age 18; }; class Student : public Person { public: void study() { /* ... */ } protected: int _stud; // 学号 }; class Teacher : public Person { public: void teaching() { /* ... */ } protected: string _title; // 职称 };1.2 继承的定义与访问方式1.2.1 定义格式下面我们看到Person是基类也称作父类。Student是派生类也称作子类。1.2.2 继承方式与访问权限继承方式有三种public、protected和private。不同继承方式下基类成员在派生类中的访问权限变化如下基类成员/继承方式public继承protected继承private继承public成员publicprotectedprivateprotected成员protectedprotectedprivateprivate成员不可见不可见不可见重要规则基类private成员在派生类中不可见但仍被继承访问权限计算Min(成员在基类的访问限定符继承方式)class默认private继承struct默认public继承实践中主要使用public继承// 实例演⽰三种继承关系下基类成员的各类型成员访问关系的变化 class Person { public : void Print () { cout_name endl; } protected : string _name ; // 姓名 private : int _age ; // 年龄 }; //class Student : protected Person //class Student : private Person class Student : public Person { protected : int _stunum ; // 学号 };1.3 继承类模板namespace bit { templateclass T class stack : public std::vectorT { public: void push(const T x) { std::vectorT::push_back(x); } void pop() { std::vectorT::pop_back(); } const T top() { return std::vectorT::back(); } bool empty() { return std::vectorT::empty(); } }; }二、基类与派生类的转换2.1 向上转型Upcastingpublic继承的派生类对象可以赋值给基类的指针/基类的引用。这里有个形象的说法叫切片或者切割。寓意把派生类中基类那部分切出来基类指针或引用指向的是派生类中切出来的基类那部分。class Person { protected: string _name; string _sex; int _age; }; class Student : public Person { public: int _No; // 学号 }; int main() { Student sobj; // 派生类对象赋值给基类指针/引用 Person* p sobj; Person rp sobj; // 派生类对象赋值给基类对象 Person pobj sobj; // 错误基类对象不能赋值给派生类对象 // sobj pobj; return 0; }2.2 向下转型Downcasting基类指针或引用可以通过强制类型转换赋值给派生类的指针或引用但需要注意安全性。三、 继承中的作用域3.1 隐藏规则在继承体系中基类和派生类都有独立的作用域。派生类和基类中有同名成员派生类成员将屏蔽基类对同名成员的直接访问这种情况叫隐藏。在派生类成员函数中可以使用基类::基类成员 显示访问class Person { protected: string _name 小李子; int _num 111; // 身份证号 }; class Student : public Person { public: void Print() { cout 姓名: _name endl; cout 身份证号: Person::_num endl; // 显式访问 cout 学号: _num endl; // 访问派生类的_num } protected: int _num 999; // 学号隐藏了基类的_num };3.2 函数隐藏需要注意的是如果是成员函数的隐藏只需要函数名相同就构成隐藏。在实际中在继承体系里面最好不要定义同名的成员。class A { public: void fun() { cout func() endl; } }; class B : public A { public: void fun(int i) { // 隐藏了A::fun() cout func(int i) i endl; } }; int main() { B b; b.fun(10); // 正确 // b.fun(); // 错误被隐藏 b.A::fun(); // 正确显式调用 return 0; }四、派生类的默认成员函数4.1 六大默认成员函数在继承中的行为6个默认成员函数默认的意思就是指我们不写编译器会帮我们自动生成一个那么在派生类中这几个成员函数是如何生成的呢构造函数必须调用基类构造函数初始化基类部分拷贝构造函数必须调用基类拷贝构造完成基类部分的拷贝赋值运算符必须调用基类赋值运算符完成基类部分的赋值析构函数完成后自动调用基类析构函数取地址运算符通常使用编译器生成版本const取地址运算符通常使用编译器生成版本因为多态中一些场景析构函数需要构成重写重写的条件之一是函数名相同(这个我们多态章节会讲解。那么编译器会对析构函数名进行特殊处理处理成destructor()所以基类析构函数不加virtual的情况下派生类析构函数和基类析构函数构成隐藏关系。4.2 代码示例class Person { public: Person(const char* name peter) : _name(name) { cout Person() endl; } Person(const Person p) : _name(p._name) { cout Person(const Person p) endl; } Person operator(const Person p) { cout Person operator(const Person p) endl; if (this ! p) _name p._name; return *this; } ~Person() { cout ~Person() endl; } protected: string _name; }; class Student : public Person { public: Student(const char* name, int num) : Person(name), _num(num) { cout Student() endl; } Student(const Student s) : Person(s), _num(s._num) { cout Student(const Student s) endl; } Student operator(const Student s) { cout Student operator(const Student s) endl; if (this ! s) { Person::operator(s); // 显式调用基类operator _num s._num; } return *this; } ~Student() { cout ~Student() endl; } protected: int _num; };4.3 实现不能被继承的类方法一C98方式构造函数私有化基类的构造函数私有派生类的构成必须调用基类的构造函数但是基类的构造函数私有化以后派生类看不见就不能调用了。那么派生类就无法实例化出对象。class NonInheritable { private: NonInheritable() {} // 私有构造函数 }; class Derived : public NonInheritable { // 错误无法访问私有构造函数 };方法二C11方式使用final关键字class Base final { // 使用final关键字 public: void func() { cout Base::func endl; } }; class Derive : public Base { // 错误不能继承final类 };五、继承与友元友元关系不能继承基类的友元不能访问派生类的私有和保护成员。class Student; class Person { public: friend void Display(const Person p, const Student s); protected: string _name; }; class Student : public Person { protected: int _stuNum; }; void Display(const Person p, const Student s) { cout p._name endl; // 正确友元访问 // cout s._stuNum endl; // 错误不是Student的友元 } // 解决方案让Display也成为Student的友元 class Student : public Person { friend void Display(const Person p, const Student s); protected: int _stuNum; };六、 继承与静态成员基类定义了static静态成员则整个继承体系里面只有一个这样的成员。无论派生出多少个派生类都只有一个static成员实例。class Person { public: string _name; static int _count; // 静态成员 }; int Person::_count 0; class Student : public Person { protected: int _stuNum; }; int main() { Person p; Student s; cout p._name endl; // 不同地址 cout s._name endl; // 不同地址 cout p._count endl; // 相同地址 cout s._count endl; // 相同地址 cout Person::_count endl; // 相同值 cout Student::_count endl; // 相同值 return 0; }七、多继承与菱形继承7.1 继承模型单继承一个派生类只有一个直接基类时称这个继承关系为单继承。多继承一个派生类有两个或以上直接基类时称这个继承关系为多继承。多继承对象在内存中的模型是先继承的基类在前面后面继承的基类在后面派生类成员放到最后面。菱形继承菱形继承是多继承的一种特殊情况。菱形继承的问题从下面的对象成员模型构造可以看出菱形继承有数据冗余和二义性的问题在Assistant的对象中Person成员会有两份。支持多继承就一定会有菱形继承像Java就直接不支持多继承规避掉了这里的问题所以实践中我们也是不建议设计出菱形继承这样的模型的。class Person { public: string _name; // 姓名 }; class Student : public Person { protected: int _num; //学号 }; class Teacher : public Person { protected: int _id; // 职⼯编号 }; class Assistant : public Student, public Teacher { protected: string _majorCourse; // 主修课程 }; int main() { // 编译报错error C2385: 对“_name”的访问不明确 Assistant a; a._name peter; // 需要显⽰指定访问哪个基类的成员可以解决⼆义性问题但是数据冗余问题⽆法解决 a.Student::_name xxx; a.Teacher::_name yyy; return 0; }7.2 虚继承很多人说C语法复杂其实多继承就是一个体现。有了多继承就存在菱形继承有了菱形继承就有菱形虚继承底层实现就很复杂性能也会有一些损失。所以最好不要设计出菱形继承。多继承可以认为是C的缺陷之一后来的一些编程语言大多没有多继承如Java。class Person { public: string _name; }; class Student : virtual public Person { // 虚继承 protected: int _num; }; class Teacher : virtual public Person { // 虚继承 protected: int _id; }; class Assistant : public Student, public Teacher { protected: string _majorCourse; }; int main() { Assistant a; a._name peter; // 正确无二义性且无数据冗余 return 0; }我们可以设计出多继承但是不建议设计出菱形继承因为菱形虚拟继承以后。⽆论是使⽤还是底层逻辑都会复杂很多。当然有多继承语法支持就⼀定存在会设计出菱形继承的情况像Java是不支持多继承的就避开了菱形继承。7.3 IO库中的菱形虚拟继承templateclass CharT, class Traits std::char_traitsCharT class basic_ostream : virtual public std::basic_iosCharT, Traits {}; templateclass CharT, class Traits std::char_traitsCharT class basic_istream : virtual public std::basic_iosCharT, Traits {};八、继承与组合8.1 关系类型比较继承is-a关系派生类是基类的一种特殊类型组合has-a关系类中包含另一个类的对象作为成员8.2 选择原则优先使用组合而不是继承只有真正的is-a关系才使用继承需要实现多态时必须使用继承组合耦合度低更易于维护// 组合示例 class Tire { protected: string _brand Michelin; size_t _size 17; }; class Car { protected: string _colour 白色; string _num 陕ABIT00; Tire _t1, _t2, _t3, _t4; // 组合关系 }; // 继承示例 class BMW : public Car { // is-a关系 public: void Drive() { cout 好开-操控 endl; } }; class Benz : public Car { // is-a关系 public: void Drive() { cout 好坐-舒适 endl; } };九、总结继承是C面向对象编程的核心机制之一正确理解和使用继承对于编写高质量、可维护的代码至关重要。本文详细介绍了继承的各个方面包括基本概念、访问控制、作用域规则、成员函数特性、多继承问题以及继承与组合的选择原则。在实际开发中应遵循以下最佳实践优先使用public继承尽量避免多继承和菱形继承优先选择组合而不是继承注意继承中的隐藏规则和作用域问题正确处理派生类中的默认成员函数通过深入理解这些概念和原则开发者可以更好地利用C的继承机制编写出更加健壮和可维护的面向对象程序。参考资料Stanley B. Lippman, C PrimerScott Meyers, Effective CBjarne Stroustrup, The C Programming Language
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2594609.html
如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!