怎么做购物优惠券网站,网站视频外链怎么做,wordpress论坛样式,代理游戏一年能赚多少作者主页#xff1a; 作者主页 本篇博客专栏#xff1a;C 创作时间 #xff1a;2024年7月5日 一、什么是继承#xff1f;
继承的概念 定义#xff1a; 继承机制就是面向对象设计中使代码可以复用的重要手段#xff0c;它允许在程序员保持原有类特性的基础上进行扩展… 作者主页 作者主页 本篇博客专栏C 创作时间 2024年7月5日 一、什么是继承
继承的概念 定义 继承机制就是面向对象设计中使代码可以复用的重要手段它允许在程序员保持原有类特性的基础上进行扩展增加功能。这样产生的新类称为派生类子类被继承的类称为基类父类。 继承的定义
下面我们来看一下实力更深入的了解一下它吧。
下面是一个Student类继承Person类的具体实例
#includeiostreamusing namespace std;class Person
{
public:void Print(){cout _height endl;cout _age endl;cout _name endl;}
protected:double _height 1.85;//身高int _age 20;//年龄string _name zhangyu;//姓名
};
class Student :public Person
{
private:int _stuid 123456;//学号int _grade 1;//年级
};int main()
{Person p;Student s;return 0;
} 看一下这张图片就可以看出来使用Student定义的s就继承了Person的成员和函数。 当然不同继承方式的继承效果也就不同 我们先来说一下关于private对于基类中的private成员即使派生来对象中但是语法上限制派生类对象无论在类里面还是在类外面都无法访问它。看一段代码更好的去理解
class Person
{
//public:/*void Print(){cout _height endl;cout _age endl;cout _name endl;}*/
private:double _height 1.85;//身高int _age 20;//年龄string _name zhangyu;//姓名
};
class Student :public Person
{
public:void Print(){cout _height endl;cout _age endl;cout _name endl;cout _stuid endl;cout _grade endl;} 这里我们可以看到对于被private修饰的对象是无法在子类中访问的
所以这里也是private和protected的区别之一。
所以我们这里可以得到以下几个理论
积累private成员在派生类中无论以什么方式继承都是不可见的这里的不可见是指基类的私有成员还是被派生类对象中但是语法上限制派生类对象不管在类里面还是在类外面都无法去访问他们。基类中的private成员在派生类中是不能被访问如果基类成员不想再类外直接被访问但需要在派生类中被访问就用protected定义。可以看出保护成员限定符可能是由于继承才出现的。实际我们对上面的表格总结一下可以发现基类的私有成员在子类中都是不可访问的基类的其他成员的访问方式min(成员在基类中的访问限定符继承方式)。使用private默认的继承方式是private使用struct的默认继承方式是public但是我们在实际开发中最好还是写出继承方式。在实际应用中一般使用public继承几乎很少使用private和protected也不提倡使用他们因为protected继承下来的成员只能在派生类里面使用实际开发中可维护性不强。
二、基类与派生类的赋值转换
我们在前面的学习知道相近类型之间是能够赋值因为他们之间会发生隐式类型转换。
int a 10;
char b a;//隐式类型转换
char c a;//报错
const char c a;//正确double d a;//报错
const double d a;
char c a; 和 double d a; 这两行代码是非法的。因为引用必须绑定到与其类型完全匹配的对象上否则就会引起权限的放大因为产生的临时对象具有常性常性是一种编程中的约束和特性所以不能将 int 类型的变量直接绑定到 char 引用或 double 引用。
那么基类与派生类直接是否也遵循这个转换规则呢接下来让我们以Person类与Student类来验证一下吧。
2.1派生类对象赋值给基类对象
派生类对象是可以赋值给基类对象的因为派生类对象本就存在基类成员。相反基类成员就无法赋值给派生类成员因为有些成员派生类有而基类没有。 所以就会报错 2.2派生类对象的引用赋值给基类对象
派生类对象的引用赋值能够给基类对象其中引用不许需要const证明其赋值之间并没有发生隐式类型转换产生临时对象。 Student s;Person rp s;//ok2.3派生类对象指针赋值给基类对象
派生类对象的指针能够赋值给基类对象这种情况与引用十分类似。 Student s;Person* pp s;//ok2.4基类指针赋值给派生类指针
基类指针能够通过强转赋值给派生类指针但是也可能造成越界访问 Person p;Student *sp (Student*) p;//ok最后总结出基类与派生类的赋值转换遵循以下规则 派生类对象可以赋值给基类的对象 基类的指针基类的引用。这里有个形象的说法叫切片或者切割。寓意把派生类中父类那部分切来赋值过去。基类对象不能赋值给派生类对象。基类的指针或者引用可以通过强制类型转换赋值给派生类的指针或者引用。但是必须是基类的指针是指向派生类对象时才是安全的。这里基类如果是多态类型可以使用RTTIdynamic_cast 来进行识别后进行安全转换。 三、继承的作用域
3.1同名变量 看这里我们基类与派生类都定义了height变量那么这里输出谁呢 这里我们可以看到输出的结果是1.9要是想输出基类中的height如何做呢我们就要加上域作用限定符 void Print()//隐藏{cout Person:: _height endl;cout _age endl;cout _name endl;}这样就输出1.85了
3.2同名函数
class Person
{
public:void func(){cout func() endl;}protected:double _height 1.85;//身高int _age 20;//年龄string _name zhangyu;//姓名
};class Student :public Person
{
public:void func(int i){Person::func();cout func(int i)- i endl;}private:double _height 1.90;int _stuid 123456;//学号int _grade 1;//年级
};int main()
{Student s;s.func(1);return 0;
}
首先第一个问题两个fun函数之间是函数重载还是隐藏的关系答案当然是隐藏关系因为函数重载针对的是同一个作用域的函数而基类与派生类直接作用域不同。
在隐藏关系中同名函数默认调用的当前作用域的函数如果想调用其他作用域的函数则需要使用域作用限定符。 四、派生类的默认成员函数
我们知道在类中有6个默认成员函数如果不显示定义编译会自动生成。那么如果在派生类中这几个成员函数是如何生成的呢 4.1
class Person
{
public:Person():_name(xzy), _height(1.85),_age(20){cout Person() endl;}~Person(){cout ~Person() endl;}protected:double _height 1.85;//身高int _age 20;//年龄string _name zhangyu;//姓名
};class Student :public Person
{
public:Student():_height(1.80),_grade(2){cout Student() endl;}~Student(){cout ~Student endl;}private:double _height 1.90;int _stuid 123456;//学号int _grade 1;//年级
};int main()
{Student s;return 0;
} 显而易见派生类对象在调用构造函数时会先调用基类的构造函数再调用派生类的构造函数。调用析构函数时会先调用派生类的析构函数再调用基类的析构函数。 4.2派生类的构造函数必须调用基类的构造函数初始化基类的那一部分成员。如果基类没有默认的构造函数则必须在派生类构造函数的初始化列表阶段显示调用。
class Person
{
public:Person(const char* name)//没有默认构造: _name(name){}Person(const Person p): _name(p._name){}
protected:string _name; // 姓名
};
class Student : public Person
{
public:Student(int num, const char* name):_num(num)//,_name(name) error, Person(name)//正确初始化{;}
protected:int _num; //学号
};4.3编译器会对派生类与基类的析构函数名进行特殊处理都会被处理成destrutor()所以派生类与基类的析构函数构成隐藏关系。 Person(const char* name)//没有默认构造: _name(name){cout Person() endl;}~Person()//析构{cout ~Person() endl;}Student(int num, const char* name)//构造:_num(num)//,_name(name) error, Person(name)//正确初始化{cout Student() endl;}~Student(){//因为构成覆盖关系所以指定域作用限定符Person::~Person();cout ~Student() endl;}但是为什么Person的析构函数会多调用一次呢因为编译器为了保证基类的析构最后调用所以在调用派生类析构函数之后会自动调用基类的构造函数。所以为了保证调用的正确顺序派生类的析构函数我们不需要显示定义。
4.4拷贝构造与赋值重载必须调用基类的拷贝构造与赋值重载完成对基类的初始化。
//拷贝构造
Person(const Person p): _name(p._name)
{
}
//赋值重载
Person operator(const Person p)
{if (this ! p)_name p._name;return *this;
}
Student(const Student s)//拷贝构造:_num(s._num), Person(s)//派生类赋值给基类
{;
}
//赋值重载
Student operator (const Student s)
{if (this ! s){//加域作用限定否则发生死循环Person::operator (s);_num s._num;}return *this;
}派生类赋值重载调用基类赋值重载时记得加域作用限定符否则就会发生死循环。 五、继承中的友元与静态成员
5.1. 继承中的友元
友元关系不能继承也就是说父类的友元不是子类的友元不能访问子类私有和保护成员。
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;
}
int main()
{Person p;Student s;Display(p, s);return 0;
}5.2. 继承中的静态成员
基类定义了static静态成员则整个继承体系里面只有一个这样的成员静态成员被所有类对象包括起子类和子类的子类共享。无论派生出多少个子类都只有一个static成员实例 。
我们可以通过下面这段代码验证
class Person
{
public:Person() { _count; }string _name; // 姓名static int _count; // 统计人的个数。
};
int Person::_count 0;//静态成员初始化
class Student : public Person
{
protected:int _stuNum; // 学号
};
class Graduate : public Student
{
protected:string _seminarCourse; // 研究科目
};
int main()
{Person p;Student s;Graduate g;cout (p._name) endl;cout (s._name) endl;cout (g._name) endl;cout (p._count) endl;cout (s._count) endl;cout (g._count) endl;return 0;
}从上图我们就可以看出非静态成员在不同基类与派生类中地址不同这就说明他们在不同类是独立存在的。而非静态成员却恰恰相反地址相同证明基类与派生类都是用同一个静态成员。
六、菱形继承和虚拟继承
6.1. 菱形继承
单继承一个子类只有一个直接父类的继承关系为单继承 多继承一个子类有两个或以上直接父类。 菱形继承就是继承关系近似呈一个菱形形状如下图 菱形继承会造成两个问题数据冗余和二义性。
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; // 主修课程
};
void Test()
{Assistant a;// a._name peter; 这样会产生二义性无法明确知道访问的是哪一个类// 需要显示指定访问哪个父类的成员可以解决二义性问题但是数据冗余问题无法解决a.Student::_name xxx;a.Teacher::_name yyy;
}6.2. 虚拟继承
为了解决数据二义性与冗余的问题C引入虚拟继承。虚拟继承用法十分简单直接在继承前加上一个关键字virtual。
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; // 主修课程
};
void Test()
{Assistant a;a._name peter;
}七、继承与组合
8.1. is-a关系
public继承是一种is-a的关系。也就是说每个派生类对象都是一个基类对象。基类的内部细节对子类可见。
class A
{};class B : public A
{};继承允许你根据基类的实现来定义派生类的实现。这种通过生成派生类的复用通常被称为**白箱复用(white - box reuse)。**术语“白箱”是相对可视性而言在继承方式中基类的内部细节对子类可见 。继承一定程度破坏了基类的封装基类的改变对派生类有很大的影响。派生类和基类间的依赖关系很强耦合度高。
8.2. has-a关系
组合是一种has - a的关系。假设B组合了A每个B对象中都有一个A对象。
class A
{};class B
{A _aa;
};对象组合是类继承之外的另一种复用选择。新的更复杂的功能可以通过组装或组合对象来获得。对象组合要求被组合的对象具有良好定义的接口。这种复用风格被称为黑箱复用(black - box reuse)因为对象的内部细节是不可见的。对象只以“黑箱”的形式出现。组合类之间没有很强的依赖关系耦合度低。
所以一般推荐优先使用对象组合而不是类继承 最后
十分感谢你可以耐着性子把它读完和我可以坚持写到这里送几句话对你也对我
1.一个冷知识 屏蔽力是一个人最顶级的能力任何消耗你的人和事多看一眼都是你的不对。
2.你不用变得很外向内向挺好的但需要你发言的时候一定要勇敢。 正所谓君子可内敛不可懦弱面不公可起而论之。
3.成年人的世界只筛选不教育。
4.自律不是6点起床7点准时学习而是不管别人怎么说怎么看你也会坚持去做绝不打乱自己的节奏是一种自我的恒心。
5.你开始炫耀自己往往都是灾难的开始就像老子在《道德经》里写到光而不耀静水流深。
最后如果觉得我写的还不错请不要忘记点赞✌收藏✌加关注✌哦(ω)
愿我们一起加油奔向更美好的未来愿我们从懵懵懂懂的一枚菜鸟逐渐成为大佬。加油为自己点赞