C++中的继承

打印 上一主题 下一主题

主题 976|帖子 976|积分 2928

一、继承的概念和定义

1.1 继承的概念

继承(inheritance)机制是面向对象程序设计是代码可以复用的最重要的手段,它答应程序员在保持原有类特性的基础上举行扩展,增长功能,如许产生新的类,称为派生类。继承出现了面向对象程序设计的层次结构,体现了由简单到复杂的认知过程。以前我们打仗的复用都是函数复用,继承是类设计层次的复用
下面是一个继承的实例:
  1. #include<iostream>
  2. #include<vector>
  3. using namespace std;
  4. class Person
  5. {
  6. public:
  7.         void Print()
  8.         {
  9.                 cout << "name:" << _name << endl;
  10.                 cout << "age:" << _age << endl;
  11.         }
  12. protected:
  13.         string _name = "son";
  14.         int _age = 18;
  15. };
  16. //继承父类Person
  17. class Student : public Person
  18. {
  19. protected:
  20.         int _stuid;//学号
  21. };
  22. class Teacher : public Person
  23. {
  24. protected:
  25.         int _jobid;//工号
  26. };
  27. int main()
  28. {
  29.         Person p;
  30.         Student s;
  31.         Teacher t;
  32.         t.Print();
  33.         return 0;
  34. }
复制代码
继承后父类Person的成员(成员函数+成员变量)都会成为子类的一部分,但是是一种类似拷贝的方式但又不是拷贝,改变子类中继承父类的成员,真正的父类对象中的成员不会改变。

1.2 继承的定义

1.2.1 定义格式

  1. class Student : public Person
  2. {
  3. protected:
  4.         int _stuid;//学号
  5. };
复制代码
上面的形式就是继承的定义格式 :次序是 class 子类(派生类) : 继承方式(访问限定符) 父类
上面的Person是父类也叫做基类。Student是子类,也称作派生类
留意:继承父类的成员变量相当于拷贝过来的,继承父类的函数是共用的一个的
父类和子类的构造函数是各自的,不是共用的,但子类可以使用父类的构造来处置处罚子类中父类的成员
1.2.2 继承关系和访问限定符



###1.2.3 继承基类成员访问方式的变化
类成员/继承方式public继承protected继承private继承基类的public成员派生类的public成员派生类的的protected成员派生类的private成员基类的protected成员派生类的protected成员派生类的protected成员派生类的private成员基类的private成员在派生类中不可见在派生类中不可见在派生类中不可见

  • 基类(父类)private成员在派生类中无论以什么方式继承都是不可见的。不可见指的是基类的私有成员被继承到了派生类对象中,但是语法上限定派生类对象不管在类内还是类外都不能去访问它。
  • 基类private成员在派生类中是不能被访问的,如果基类成员不想在类外被直接访问,但是需要在派生类中能访问,就将基类成员定义为protected。保护成员限定符(protected)就是因为继承才出现的。
  • 基类成员在派生类中的访问方式 == Min(成员在基类中的访问限定符, 继承方式), 比较标准:public > protected > private。
  • 使用关键字class时默认的继承方式是private, 使用struct时的默认继承方式是public, 最好显式的写出继承方式。
  • 在实际运用中一般使用都是public继承,很少使用protected/private继承
二、基类和派生类对象赋值转换



  • 派生类对象可以赋值给基类对象/基类对象的指针/基类对象的引用,这个赋值的过程叫做切片或者切割,就是把子类中父类的那部分赋值给父类。
  • 基类对象不能赋值给派生类对象。
效果图如下:

下面是我们的代码实例:
  1. class Person
  2. {
  3. protected:
  4.         string _name;
  5.         string _sex;
  6.         int _age;
  7. };
  8. //继承父类Person
  9. class Student : public Person
  10. {
  11. public:
  12.         int _no;
  13. };
  14. void test()
  15. {
  16.         Student s;
  17.         //子类对象可以给父类对象赋值
  18.         Person per = s;
  19.         Person* pper = &s;
  20.         Person& rper = s;
  21.         //父类对象不能给子类对象赋值
  22.         //s = per;
  23. }
复制代码
三、 继承中的作用域


  • 在继承体系中基类派生类都有独立的作用域。
  • 子类和父类中有同名成员, 子类成员将屏蔽父类对同名成员的之间访问, 这种情况叫做隐藏 ,也叫做重定义。(在子类成员函数中,可以使用 父类 ::父类成员 的方式举行显式访问。
  • 如果是成员函数的隐藏, 只需要函数名相同就构成隐藏。
  • 在实际中在继承体系中最好不要定义同名的成员。
实例1(成员变量之间构成隐藏):
  1. class Person
  2. {
  3. protected:
  4.         string _name = "son";
  5.         int _id = 111;
  6. };
  7. class Student : public Person
  8. {
  9. public:
  10.         void Print()
  11.         {
  12.                 cout << "name: " << _name << endl;
  13.                 cout << "id: " << Person::_id << endl;
  14.                 cout << "id: " << _id << endl;
  15.         }
  16. protected:
  17.         int _id = 999;
  18. };
  19. void test2()
  20. {
  21.         Student s1;
  22.         s1.Print();
  23. }
复制代码
实例2(成员函数之间构成隐藏):
  1. class A
  2. {
  3. public:
  4.         void fun()
  5.         {
  6.                 cout << "fun()" << endl;
  7.         }
  8. };
  9. class B : public A
  10. {
  11. public:
  12.         void fun(int i)
  13.         {
  14.                 cout << "fun(int i)" << i << endl;
  15.         }
  16. };
  17. void test3()
  18. {
  19.         B b;
  20.         b.fun(109);
  21. }
复制代码
实例2中的fun()和fun(int i) 之间是不构成重载的因为它们不在同一作用域,B中的fun和A中的fun构成隐藏,成员函数只要满意函数名相同就构成隐藏。
四、派生类中的默认成员函数


  • 派生类的构造函数必须调用基类的构造函数初始化基类的那一部分成员。如果基类没有默认构造函数,则必须在派生类构造函数的初始化列表阶段显式调用。
  • 派生类的拷贝构造函数必须要调用基类的拷贝构造完成基类的拷贝初始化。
  • 派生类的operator=必须要调用基类的operator=来完成基类的复制。
  • 派生类的析构函数会在被调用后自动调用基类的析构函数来整理基类成员。因为如许才气保证派生类对象先整理派生类成员在整理基类成员的次序。
  • 派生类对象初始化先调用基类构造再调用派生类构造。
  • 派生类对象析构整理先调用派生类析构在调用基类的析构。
留意:

  • 由于多态,析构函数的名字会被同一处置处罚成destructor(), 析构函数全构成隐藏,以是要指定类域,但是父类的析构不能表现调用,调用子类的析构后会自动调用。
  • 构造初始化不能先子后父,因为子类构造初始化大概会用到父类成员,没有初始化父,父类成员就是随机值。
  • 初始化列表的次序是按照声明的次序来的。
  • 析构的时候必须是先子后父,不能先父后子,因为子类析构大概用到父类成员的,先父后子就大概出问题。
  • 父类的析构不是自己显式调用的,它会在子类析构竣事之后自动调用。
五、 继承与友元

友元关系是不能继承的,也就是说基类友元不能访问子类私有和保护成员。即基类的友元其派生类不是友元。
代码实例:
  1. #include<iostream>
  2. using namespace std;
  3. class Student;
  4. class Person
  5. {
  6. public:
  7.     friend void Display(const Person& p, const Student& s);
  8. protected:
  9.         string _name;
  10. };
  11. class Student : public Person
  12. {
  13. protected:
  14.         int _stunum; // 学号
  15. };
  16. void Display(const Person& p, const Student& s)
  17. {
  18.         cout << p._name << endl;
  19.         cout << s._stunum << endl;
  20. }
  21. int main()
  22. {
  23.         Person p1;
  24.         Student s1;
  25.         Display(p1, s1);
  26.         return 0;
  27. }
复制代码
六、 继承与静态成员

基类定义了static静态成员后,整个继承体系中就只有一个如许的成员,也就是无论派生出多少个子类都只有一个static成员实例。
  1. class Person
  2. {
  3. public:
  4.     Person()
  5.     {
  6.         ++_count;
  7.     }
  8. protected:
  9.     string _name; // 姓名
  10. public:
  11.     static int _count; // 统计人数
  12. };
  13. int Person::_count = 0;
  14. class Student : public Person
  15. {
  16. protected:
  17.     int _stunum; // 学号
  18. };
  19. class Graduate : public Student
  20. {
  21. protected:
  22.     string _seminarcourse; // 研究项目
  23. };
  24. void TestPerson()
  25. {
  26.     Student s1;
  27.     Student s2;
  28.     Student s3;
  29.     Graduate s4;
  30.     cout << "人数: " << Person::_count << endl;
  31.     Student::_count = 0;
  32.     cout << "人数 :" << Person::_count << endl;
  33. }
复制代码
七、 复杂的菱形继承以及菱形虚拟继承

单继承:一个子类只有一个直接父类时的关系就是单继承。

多继承:一个子类有两个或者两个以上的直接父类时的关系就是多继承。

菱形继承:菱形继承是多继承的一种特殊情况。

菱形继承存在的问题:菱形继承有数据冗余和二义性的问题,在Assistant的对象中Person成员会有两份。

下面是代码实例:
  1. class Person
  2. {
  3. public:
  4.     string _name; //姓名
  5. };
  6. class Student : public Person
  7. {
  8. protected:
  9.     int _num; //学号
  10. };
  11. class Teacher : public Person
  12. {
  13.     int _id; //编号
  14. };
  15. //继承上面两个类
  16. class Assistant : public Student, public Teacher
  17. {
  18. protected:
  19.     string _majorCourse; //主修课程
  20. };
  21. void Test()
  22. {
  23.     Assistant a;
  24.     //这样会有二义性无法确定访问的是哪一个
  25.     a._name = "rebenn";
  26.     //可以显示的指定访问那个父类的成员来解决二义性问题,但是无法解决数据冗余的问题
  27.     a.Student::_name = "peter";
  28.     a.Teacher::_name = "woter";
  29. }
复制代码
虚拟继承可以解决菱形继承的二义性以及数据冗余的问题, 就像上面的继承关系,在Student和Teacher的继承Person时使用虚拟继承,即可解决二义性和数据冗余的问题。留意虚拟继承不要在其他地方去随意使用。
虚拟继承方式:
   class 类名 :virtual public 父类
  virtual是虚拟继承的关键字。
代码实例:
  1. class Person
  2. {
  3. public:
  4.     string _name; // 姓名
  5. };
  6. class Student : virtual public Person
  7. {
  8. protected:
  9.     int _num; //学号
  10. };
  11. class Teacher : virtual public Person
  12. {
  13. protected:
  14.     int _id; //工号
  15. };
  16. class Assistant : public Student, public Teacher
  17. {
  18. protected:
  19.     string _majorcourse; //主修课程
  20. };
  21. void test()
  22. {
  23.     Assistant a;
  24.     a._name = "rebenn";
  25. }
复制代码
留意:实践中,不发起使用菱形继承,可以使用多继承,但也要少用。
io流就是一个菱形继承, 菱形继承是特殊的多继承。
八、 继承和组合



  • public继承是一种is-a的关系,也就是每个派生类对象是一个特殊的基类对象。
  • 组合是一种has-a的关系,假设B组合了A, 就是每个B对象中都含有一个A对象。
  • 继承和组合都是复用的体现。
  • 保举优先使用对象组合,而不是继承。
代码实例:
  1. //Car和BWM, Car和Benz之间是is-a关系,继承
  2. class Car
  3. {
  4. protected:
  5.     string _colour = "黑色"; //颜色
  6.     string _num = "xxxxxx";//车牌号
  7. };
  8. class BMW : public Car
  9. {
  10. public:
  11.     void Drive()
  12.     {
  13.         cout << "好操作" << endl;
  14.     }
  15. };
  16. class Benz : public Car
  17. {
  18. public:
  19.     void Drive()
  20.     {
  21.         cout << "车座舒适" << endl;
  22.     }
  23. };
  24. //Tire和Car构成has-a的关系, 组合
  25. class Tire
  26. {
  27. protected:
  28.     string _brand = "xxxx"; //品牌
  29.     size_t _size = 18; //尺寸
  30. };
  31. class Car
  32. {
  33. protected:
  34.     string _colour = "xxx"; //颜色
  35.     string _num = "xxxxx"; //车牌号
  36.     Tire _t; //轮胎
  37. };
复制代码
class BMW : public Car
{
public:
void Drive()
{
cout << “好操作” << endl;
}
};
class Benz : public Car
{
public:
void Drive()
{
cout << “车座舒适” << endl;
}
};
//Tire和Car构成has-a的关系, 组合
class Tire
{
protected:
string _brand = “xxxx”; //品牌
size_t _size = 18; //尺寸
};
class Car
{
protected:
string _colour = “xxx”; //颜色
string _num = “xxxxx”; //车牌号
Tire _t; //轮胎
};
[code][/code]
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

熊熊出没

金牌会员
这个人很懒什么都没写!
快速回复 返回顶部 返回列表