目录:

  • 前言
  • 一、继承的概念及定义
    • (一)概念
    • (二)继承定义
      • 继承关系和访问限定符
      • 继承基类成员访问方式的变化
  • 二、基类和派生类对象赋值转换
  • 三、继承中的作用域
  • 四、派生类的默认成员函数
  • 五、继承与友元
  • 六、继承与静态成员
  • 七、复杂的菱形继承及菱形虚拟继承
    • 菱形继承
    • 虚继承
    • 组合
  • 补充

前言

打怪升级:第50天

一、继承的概念及定义

(一)概念

继承机制是面向对象程序设计使代码可以复用的最重要的手段,他允许类在保持原有特性的基础上进行拓展,增加新的功能,这样产生的类叫做子类或者派生类。
继承呈现了面向对象程序设计的层次结构,体现了由简单到复杂的认知过程。

示例:

class Person
{public:void Print(){cout << "name : " << _name << endl;cout << "age : " << _age << endl;}
protected:string _name = "小李子";int _age = 20;
};//  Person类作为父类,被student 和 teacher继承后,父类的public 和 protected 成员(变量和函数)都会变成子类的一部分,可以在子类中进行访问。
class Student : public Person
{private:string _s_id = "000"; // 学号
};class Teacher : public Person
{private:string _t_id = "100"; // 工号
};void Test_person1()
{Student s1;s1.Print();Teacher t1;t1.Print();
}

运行实例:


(二)继承定义

继承关系和访问限定符

继承基类成员访问方式的变化

基类的私有成员(private)子类无论如何都是不可见的,基类的保护成员(protected)和共有成员(public)在子类中的受继承方式限制,一般都使用 public继承,也就都是可见的;
这就好比父亲挣得钱和 父亲藏的私房钱,父亲挣的钱一家人都可以用,但是私房钱只有父亲自己可以用,哪怕是儿子也不行。

这里有一个小问题:子类可以访问到父类的私有成员(私房钱)吗,
我们上面讲:父类的私有成员在子类中不可见,既然不可见(不知道老爸私房钱藏在哪里),那是不是就不能使用?

  • 总结
  1. 基类private成员在派生类中无论以何种方式继承都是不可见的。这里的不可见是指基类的私有成员还是被继承到了派生类对象中,但是语法上限制派生类不管来类内还是类外都无法访问到
  2. 基类private成员在派生类中是访问不到的,如果想要在派生类中可以访问,但是在类外不能访问到,就需要使用protected。可以看出:保护成员限定符是因继承才出现的(c++刚问世的时候只设置了私有和共有,在C++2.0时才加入了保护成员)。
  3. 基类的私有成员在子类都是不可见。基类的其他成员在子类的访问方式 == Min(成员在基类的访问限定符,继承方式),public > protected > private
  4. 使用关键字class时默认的访问和继承方式是private, 使用struct 时默认的访问和继承方式是public,不过最好显示写出继承方式
  5. 在实际运用中一般使用都是public继承,几乎很少使用 protected/private继承。
  6. 一句话总结:权限可以缩小但不可放大(类比:const变量不能被非const变量引用)。

二、基类和派生类对象赋值转换

  • 派生类可以赋值给基类的对象/基类的指针/基类的引用。这里有个形象的说法加切片或者切割
    寓意把派生类中基类那部分切出来赋值过去。

  • 基类对象不能赋值给派生类对象

  • 基类的指针或引用可以通过强制类型转换赋值给派生类的指针或引用。但是只有访问基类成员时才是安全的,否则可能造成越界


三、继承中的作用域

  1. 在继承体系中,基类和派生类都有独立的作用域
  2. 子类和父类中有同名成员子类成员将屏蔽对父类同名成员的直接访问,这种情况叫隐藏,也叫重定义。(在子类对象中,可以使用 基类::基类成员显示访问)。
  3. 注意:成员函数只要函数名相同就构成隐藏。(区分函数重载:在同一作用域内,函数名相同而参数不同);
  4. 注意在实际的继承体系里面最好不要定义同名的成员。
class Person
{public:void Print(){cout << "name : " << _name << endl;cout << "age : " << _age << endl;}string _name = "小李子";int _age = 20;
};class Student : public Person
{public:void Print(){cout << "id :" << _s_id << endl;}string _s_id = "000"; // 学号
};class Teacher : public Person
{public:void Print(){cout << "id :" << _t_id << endl;}string _t_id = "100"; // 工号
};void Test_Person3()
{Student s1;s1.Person::Print();  // 隐藏,显示调用同名成员s1.Print();cout << endl;Teacher  t1;t1.Person::Print();t1.Print();
}


四、派生类的默认成员函数

6个默认成员函数,“默认”的意思就是指我们不写,编译器会变我们自动生成一个,那么在派生类
中,这几个成员函数是如何生成的呢?

  1. 派生类的构造函数必须调用基类的构造函数初始化基类的那一部分成员。如果基类没有默认
    的构造函数,则必须在派生类构造函数的初始化列表阶段显示调用。
  2. 派生类的拷贝构造函数必须调用基类的拷贝构造完成基类的拷贝初始化
  3. 派生类的operator=必须要调用基类的operator=完成基类的复制
  4. 派生类的析构函数会在被调用完成后自动调用基类的析构函数清理基类成员。因为这样才能
    保证派生类对象先清理派生类成员再清理基类成员的顺序。
  5. 派生类对象初始化先调用基类构造再调派生类构造。
  6. 派生类对象析构清理先调用派生类析构再调基类的析构。(保证进栈出栈顺序)。
  7. 因为后续一些场景析构函数需要构成重写,重写的条件之一是函数名相同那么编译器会对析构函数名进行特殊处理,处理成destrutor(),所以父类析构函数不加virtual的情况下,子类析构函数和父类析构函数构成隐藏关系

class Person
{public:Person(string name = "小李子", int age = 20):_name(name), _age(age){}void Print(){cout << "name : " << _name << endl;cout << "age : " << _age << endl;}string _name;int _age;
};class Student : public Person
{public:Student(string name = "小李子", int age = 20, string id = "000"):Person(name, age)  // 显示调用基类的构造, _s_id(id){}Student(const Student& s):Person(s)           //  显示调用, _s_id(s._s_id){}void Print(){Person::Print();cout << "id :" << _s_id << endl;cout << endl;}string _s_id; // 学号
};void Test_Person4()
{Student s1;s1.Print();Student s2("陈平安", 40);s2.Print();Student s3("裴钱", 26, "001");s3.Print();Student s4 = s3;s4.Print();
}

class Base
{public:Base(int val = 10):b_val(val){cout << "Base()" << endl;}Base(const Base& b):b_val(b.b_val){cout << "Base(const Base&)" << endl;}Base& operator=(const Base& b){if (&b != this){b_val = b.b_val;cout << "Base::operator=()" << endl;}return *this;}~Base() { cout << "~Base()" << endl; }int b_val;
};class Son: public Base
{public:Son(int val1 = 10, int val2 = 20):Base(val1),s_val(val2){cout << "Son()" << endl;}Son(const Son& b) :Base(b)  //  派生类对象初始化基类对象,s_val(b.s_val){cout << "Son(const Base&)" << endl;}Son& operator=(const Son& s){if (&s != this){Base::operator=(s);  // 显示调用基类的赋值s_val = s.s_val;cout << "Son::operator=()" << endl;}return *this;}~Son() { cout << "~Son()" << endl; }int s_val;
};void Test_p5()
{Son s1;cout << endl;Son s2 = s1;cout << endl;Son s3;s3 = s1;cout << endl;Son s4(1, 2);cout << endl;
}

注意:在派生类的构造和赋值中 如果我们不显示调用基类的赋值和拷贝构造,编译器不会自动调用


五、继承与友元

友元关系不能继承,也就是说基类友元不能访问派生类私有和保护成员。

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;
}
void main()
{Person p;Student s;Display(p, s);
}


六、继承与静态成员

**基类定义了static静态成员,则整个继承体系里面只有这样一个静态成员。**无论派生出多少个子类,都只有一个static成员实例。

class Base
{public:static int cnt;
};
int Base::cnt = 0;   // 静态成员变量需要在类外初始化,且初始化时不需要加staticclass Son1 :public Base
{public:Son1(){++cnt;}
};class Son2 :public Base
{public:Son2(){++cnt;}
};void Test_Static1()
{Son1 s1_1;Son1 s1_2;cout << "cnt =  " << s1_2.cnt << endl;Son2 s2_1;Son2 s2_2;cout << "cnt =  " << s2_2.cnt << endl;
}


七、复杂的菱形继承及菱形虚拟继承



菱形继承

菱形继承造成的影响:Child从父类Student中继承了Person类的属性,又从父类Teacher中也继承了Person类的属性,那么此时,Child类中就有了两份Person类属性,
这会有两个问题:二义性和数据冗余

  1. 我们访问Person类属性时不能确定访问的到底是从父类STudent中继承下来的还是从父类Teacher中继承下来的;
  2. 多份的Person类属性会造成数据的冗余。

虚继承

为了解决菱形继承带来的问题,我们祖师爷在C++3.0给出了解决方案:虚继承
关键字:virtual(虚拟)

class A
{public:int _a;
};class B : virtual public A  //  虚继承
{public:int _b;
};class C : virtual public A  //  虚继承
{
public:int _c;
};class D : public B, public C
{public:int _d;
};void Test_1()
{D d1;d1.B::_a = 10;d1.C::_a = 30;}

这里有盆友可能会提出疑问:我们解决数据冗余不就是为了节省空间吗,这里多增加了一个A,而且父类中的A也还存在,并且A中存放的地址还指向另外一块区域,这样不就更加浪费空间了吗,难道我们的祖师爷在当时设计虚继承时“喝了假酒”,所以这部分写出问题来啦?

虚继承原理解析:

很多人说c++的语法复杂,多继承就是一个体现:有了多继承就会有菱形继承,有了菱形继承就会有菱形虚拟继承,底层实现就很复杂,
并且编译器在背后为我们做的一系列操作也是有时间消耗的,所以一般不建议写出多继承,一定不要写菱形继承!
由于c++现世的时间非常早,当时的参考资料较少,因此设计上难免会有一些不合理的地方(如多继承、string类等等),我们的前辈也在对c++不断的进行缝缝补补,后世的一些OO语言在参考了c++之后就限制了继承的操作,如Java就只有单继承没有多继承。

组合

class A
{public:int _a1;
protected:int _a2;
};class B :public A
{public:void Init(){_a1 = 10;_a2 = 20;}
};class C
{public:void Init(){_aa._a1 = 10;
//      _aa._a2 = 20;   //  不可访问}A _aa;      //   组合 -- c中有a
};


补充

【难学易用c++ 之 继承】相关推荐

  1. C++ 是一门难学易用的语言!

    译 序 C++ 是一门难学易用的语言! C++ 的难学,不仅在其广博的语法.语法背后的语义.语义背后的深层思维.深层思维背后的对象模型:C++ 的难学,还在于它提供了4种不同(相辅相成)的编程思维模型 ...

  2. 最易/难学习的编程语言榜单出炉,C++最难学?

    你还记得你第一次写代码的时候使用的是什么编程语言吗?在学习编程的过程中,你认为最简单易学或是最难上手的语言是什么呢?如果给新手推荐入门的编程语言,你会如何推荐或是建议他们避开什么语言呢? 在线学习平台 ...

  3. C++ 最难学?最易/难学习的编程语言 TOP 5 来了!

    在线学习平台 Springboard 罗列了一个最容易学习和最难学的编程语言 Top 5 榜单. 事实上,问一个程序员最容易学习的语言,就像问一个人他们最喜欢的冰淇淋.每个人都有自己的偏好,永远没有真 ...

  4. python 框架好学吗-python的flask框架难学吗

    Flask框架难学吗?它和Django哪个更容易一些,这可能是学Python web开发的同学经常问的问题,下面来说一下flask框架. Flask是python的web框架,最大的特征是轻便,让开发 ...

  5. 学python和java哪个难?,java和python哪个难学

    java和python哪个好学 ①python比Java简单,学习成本低,开发效率高;②Java运行效率高于python,尤其是纯python开发的程序,效率极低;③Java相关资料多,尤其是中文资料 ...

  6. C++太难学,怎么破?这本书给你指点迷津!

    2021 年在线学习平台 Springboard 选出了最难学的编程语言 TOP5,C++ 排在其中之一. C++ 难学的理由很多,比如它语法复杂,语法特性多,编程范式灵活,标准库内容过于基础,还要具 ...

  7. python难学不-python难学吗-没有编辑基础可以学python吗?

    肯定的回答您:只要下功夫,什么都不难.青岛白癜风医院:https://yyk.familydoctor.com.cn/20595/ 其实,很多个程序员都是从不会到会,每种知识也是从基础到复杂,大家都是 ...

  8. java和前端哪个难学?

    java和前端哪个难学? 不是同一样东西,无法比较. Java 和 JavaScript 哪个难学? 前端 和 后端 哪个难学? 问Java 和 前端哪个难学, 就像在问牛排和中餐哪个好吃一样.Jav ...

  9. python vb 哪个好学_最难学的七大编程语言,VB 第一,Python垫底,看你学的排第几...

    原标题:最难学的七大编程语言,VB 第一,Python垫底,看你学的排第几 在很多人眼里程序员的标配就是黑框眼镜+格子衫+双肩包+牛仔裤+运动鞋,拥有了这些可能就被判定为一个程序员. 而如何判定一个程 ...

最新文章

  1. javascript基础语法——表达式
  2. Reddit欲融资3亿美元,由腾讯领投
  3. MikroTik Routeros Wlan应用之-pppoe Server
  4. Linux内核探讨-- 第三章
  5. python字符串写入excel-python 将数据写入excel
  6. 快速掌握mysql,可备用查找相关用法(吐血整理)
  7. ECC6.0中数据导出到本地时报错GETWA_NOT_ASSIGNED
  8. Vue使用v-bind绑定动态数据
  9. C#语法:多线程编程(Thread)
  10. OpenCV SVM支持向量机和KNearest数字识别的实例(附完整代码)
  11. 双粗虚线中间一条实线_马路中间有一条黄色虚线和一条实线,能超车吗?
  12. 多亏它,让大家做好“山竹”来袭的准备【Make It Real故事汇】
  13. Dart 5-Day
  14. select简易的二级联动
  15. Linux配置nginx打开报404,Linux下Nginx配置404页面的方法
  16. Matlab读nc文件
  17. java毕业设计药品管理系统Mybatis+系统+数据库+调试部署
  18. JAVA中分号用中文还是英文_【英文中有分号(;)吗?怎么用呢?】作业帮
  19. gmail注册方法_如何在Gmail中释放空间:5种回收空间的方法
  20. 华为智慧屏鸿蒙评测,搭载鸿蒙系统的荣耀智慧屏值得入手吗?荣耀智慧屏全面评测...

热门文章

  1. Python程序退出方式小结
  2. c语言题之二维数组的查找
  3. android使用httpCanary抓包并解决不能联网问题
  4. 类脑神经界面研究有新进展-深圳先进院李骁健与华中科技大学罗志强合作研究将适合脑机接口应用的ECoG型传感器...
  5. EXCEL 怎么把一列数据转换为多行多列数据
  6. 线上展示工具:Blender制作三维动画和产品建模
  7. 软考-信息管理——学习笔记_证
  8. 微信小程序订阅消息推送(附带后台java代码)
  9. 电脑桌面文件删除了怎么恢复?分享3个技巧,你见过第3个吗?
  10. 又一次Hillston*(山*)靶机渗透—20220717