山西网站群建设,网站底部优化文字,阳山县网站住房和建设局,网站404页面编写凡是面向对象的语言#xff0c;都有三大特性#xff0c;继承#xff0c;封装和多态#xff0c;但并不是只有这三个特性#xff0c;是因为者三个特性是最重要的特性#xff0c;那今天我们一起来看继承#xff01; 目录
1.继承的概念及定义
1.概念
2.继承的定义
2.基类… 凡是面向对象的语言都有三大特性继承封装和多态但并不是只有这三个特性是因为者三个特性是最重要的特性那今天我们一起来看继承 目录
1.继承的概念及定义
1.概念
2.继承的定义
2.基类和派生类对象赋值转换
3.继承中的作用域
4.派生类的默认成员函数
1.构造和拷贝构造赋值
2.析构函数的两怪
5.继承与友元
6. 继承与静态成员
7.多继承
7.1继承分类
7.2 菱形继承 菱形虚拟继承
1.解决二义性的过程指定作用域
2.解决数据冗余需要虚继承
8.继承和组合都是一种复用
总结 1.继承的概念及定义
1.概念
继承(inheritance)机制是面向对象程序设计使代码可以复用的最重要的手段它允许程序员在保 持原有类特性的基础上进行扩展增加功能这样产生新的类称派生类。子类继承呈现了面向对象程序设计的层次结构体现了由简单到复杂的认知过程。以前我们接触的复用都是函数复用继承是类设计层次的复用。那到底是什么意思呢举个例子class Person
{
protected:char* _name;int _old;
};class student :public Person
{
private:char* _id;
};
int main()
{student s;
} 当好多类都需要写Person类中的成员时 为了避免数据冗余就可以使用类的继承使代码复用继承是让每一个派生类中都有一份基类父类的成员。 2.继承的定义 继承的方式当然继承的目的就是为了让子类可以拥有父类的成员并访问所以一般情况下我们只会进行公有继承public 那么我们来看一下继承方式和访问之间的关系
首先必须知道的一点是基类中有私有成员时子类中继承的父类的私有成员不可见。
不可见是指基类的私有成员还是被继承到了派生类对象中但是语法上限制派生类对象不管在类里面还是类外面都不能去访问它。
其次就是基类成员的权限和继承方式的权限谁的权限更小在子类中继承的成员就是更小的那个权限。public protected private。 如上图所示 2.基类和派生类对象赋值转换
首先得回想起赋值转换这个过程
不同的类型相互赋值时中间会产生临时变量通过临时变量进行赋值转换。
但是若子类和父类进行赋值交换时并不产生中间的临时变量而是天然的一个赋值。
只能向上转换即子类赋值给父类字可以给父父不可以给子
看下面代码
class Person
{
protected:string _name; // 姓名string _sex; // 性别
public:int _age; // 年龄
};class Student : public Person
{
public:int _No; // 学号
};int main()
{int i 1;double d 2.2;//中间会产生一个临时变量临时变量具有常性不可以改变。i d;//所以此时ri是中间的临时变量的引用而不是d的引用如果不加const就会放大权限const int ri d;//但父类和子类之间的赋值就不会产生中间的临时变量Person p;Student s;// 中间不存在类型转换天然的一个赋值p s;Person rp s;//对s的引用可以访问和修改成员变量rp._age 1;Person* ptrp s;ptrp-_age;return 0;
}
派生类对象 可以赋值给 基类的对象 / 基类的指针 / 基类的引用。这里有个形象的说法叫切片 或者切割。寓意把派生类中父类那部分切来赋值过去。切片的具体过程我们画图来了解只能向上转换即子类赋值给父类字可以给父父不可以给子 3.继承中的作用域
我们知道一个类他就是一个域作用域。同一作用域不能定义同名的两个变量但不同作用域它可以定义两个同名变量。所以父类子类中都有同名的成员变量时默认会自动访问子类的成员因为就近原则若想访问父类的成员那就可以指定作用域
同一作用域定义两个同名函数且参数不同叫做函数重载
不同作用域定义两个同名函数叫做重定义 1. 在继承体系中基类和派生类都有独立的作用域。2. 子类和父类中有同名成员子类成员将屏蔽父类对同名成员的直接访问这种情况叫隐藏 也叫重定义。在子类成员函数中可以使用 基类::基类成员 显示访问3. 需要注意的是如果是成员函数的隐藏只需要函数名相同就构成隐藏。 4. 注意在实际中在继承体系里面最好不要定义同名的成员举例说明
class A
{
public:void fun(){cout A::func() endl;}
};class B : public A
{
public:void fun(int i){cout B::func(int i)- i endl;}
};void Test()
{B b;b.fun(10);
};这中情况函数构成什么函数重载 重定义隐藏编译错误
首先我们知道两个类中的同名函数在不同作用域这就构成了重定义隐藏
若访问父类成员函数即b.A::fun() 4.派生类的默认成员函数
1.构造和拷贝构造赋值
先回顾一下默认成员函数无参全缺省编译器自己生成 具体分析
class Person //父类
{
public:Person(const char* name) : _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){}Student(const Student s)
//子类显示拷贝构造时父类不可以直接访问进行初始化必须调用父类自己的显示拷贝构造函数:Person(s), _num(s._num){}Student operator(const Student s){if (this ! s){
//赋值的运算符重载两个同名函数构成了隐藏需要指定作用域Person::operator(s);_num s._num;}return *this;}protected:int _num; //学号
};int main()
{Student s1(张三, 18);Student s2(s1);Student s3(李四, 20);s1 s3;return 0;
}
最重要的一句话父类成员必须调用父类自己的构造函数拷贝构造完成初始化或拷贝。
或者说子类中的父类那部分成员由父类自己的构造或者拷贝构造实现初始化或者拷贝。
2.析构函数的两怪
直接看代码
class Person
{
public:Person(const char* name ): _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;delete[] p;}protected:string _name; // 姓名int* p new int[10];
};class Student : public Person
{
public:Student(const char* name):Person(name), _num(1){}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);_num s._num;}return *this;}// 第一怪1、子类析构函数和父类析构函数构成隐藏关系。由于多态关系需求所有析构函数都会特殊处理成destructor函数名// 第二怪子类先析构父类再析构。子类析构函数不需要显示调用父类析构子类析构后会自动调用父类析构~Student(){//Person::~Person();cout ~Student() endl;}protected:int _num; //学号
};int main()
{Student s(张三);return 0;
}1、子类析构函数和父类析构函数构成隐藏关系。由于多态关系需求所有析构函数都会特殊处理成destructor函数名 2.子类先析构父类再析构。子类析构函数不需要显示调用父类析构子类析构后会自动调用父类析构
构造顺序先父类再子类析构顺序先子类再父类。 5.继承与友元
友元关系不能继承若子类对象也想访问友元函数那只能在子类中也加上友元但不建议使用友元会破坏继承关系 6. 继承与静态成员
子类继承父类不是继承父类这个对象而是会有一份父类的模型。父类有的成员变量子类也会有一份互不干扰。
但静态成员就不一样了他们是同一份静态成员属于整个类和类的所有对象。同时也属于所有派生类及派生类的对象。 class Person
{
public://friend void Display(const Person p, const Student s);
protected:string _name; // 姓名
public:static int _num;
};
int Person::_num 0;class Student : public Person
{
protected:int _stuNum; // 学号
};void test()
{Student s;Person p;cout p._num endl;p._num;cout p._num endl;s._num;cout s._num endl;cout s._num endl;cout p._num endl;
} 基类定义了static静态成员则整个继承体系里面只有一个这样的成员。无论派生出多少个子 类都只有一个static成员实例 重点 Person* ptr nullptr;ptr-Print();cout ptr-_num endl;cout ptr-_name endl;cout (*ptr)._num endl;(*ptr).Print();对象里面只存成员变量成员函数在代码段中所以以上代码哪个不对呢 我们知道空指针不能解引用解引用意思是这里是去访问指针指向对象的内部成员那看一看哪个访问了内部的成员呢 函数不在内部在代码段可以 _num为对象内部成员变量不能解引用访问不可以 (*ptr)是解引用了吗我们不能凭借解引用符号来判断是否解引用我们需要看内部的访问情况(*ptr)-Print();并没有访问内部成员可以 (*ptr)-_num;也可以_num是静态成员不在成员里面。 7.多继承 7.1继承分类 单继承一个子类只有一个直接父类 多继承一个子类有两个或两个以上的父类 菱形继承是多继承的一种特殊情况会产生数据冗余和二义性 person类的中的成员会在student和teacher中都有一份assistant继承student和teacher时assistant中会有两份person造成了数据冗余和二义性 解决方法 解决二义性 可以通过访问限定符来指定访问哪一个成员。 那如何解决二义性的问题呢 此时虚继承就上线了 虚继承在腰部继承谁引发的数据冗余谁就进行虚继承解决冗余 由此可见加上virtual变为虚继承以后确实解决了数据的冗余 那么到底如何解决的呢具体下面分析 7.2 菱形继承 菱形虚拟继承
为了研究虚拟继承原理我们给出了一个简化的菱形继承继承体系再借助内存窗口观察对象成 员的模型。 1.解决二义性的过程指定作用域 菱形继承 class A
{
public:int _a;
};class B:public A
{
public:int _b;
};class C:public A
{
public:int _c;
};class D:public B,public C
{
public:int _d;
};int main()
{D d;d._b 1;d._c 2;d._d 3;d.B::_a 4;d.C::_a 5;
}2.解决数据冗余需要虚继承 菱形虚拟继承 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;
};int main()
{D d;d._b 1;d._c 2;d._d 3;d.B::_a 4;d.C::_a 5;
}那如果遇到这种情况呢父子类的赋值转换切片 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;
};int main()
{D d;d._b 1;d._c 2;d._d 3;d._a 4;d._a 5;B b;b._a 1;b._b 3;B* ptr b;ptr-_a 2;ptr d;ptr-_a 6;
}从b对象可以看的出来只要是虚继承以后就会把虚基类放到最下面 就像切片这种情况ptr指向不同那么距离虚基类的距离就不同所以就必须要有虚基表的地址来访问虚基表继而找到偏移量然后访问到虚基类 我们通常使用下很忌讳出现菱形继承但可以多继承。
可以看得出虚继承在时间上确实有损耗过程比较复杂但是如果虚基类比较大时就可以很大程度上节省内存。 8.继承和组合都是一种复用
public继承是一种is-a是一个的关系。也就是说每个派生类对象都是一个基类对象。组合是一种has-a有一个的关系。假设B组合了A每个B对象中都有一个A对象。我们会说低耦合高内聚有意思就是相互的联系比较小不会因为改动一个而很大的影响另一个在组合中两个类中的成员变量一般都是私有那么就无法访问那么修改也不会相互影响到在继承中因为要继承所以父类成员一般子类都可以访问的那么修改的话彼此相互影响就比较大那么组合其实就是很好的低耦合。就比如我们平时举例说到的personstudent这就是继承关系学生是一个人那再举一个头有一双眼睛这就是组合事实上哪个适合就用哪个都适合就先用组合总结 一口气说了这么多你学会了吗细节还是比较多的我们应该下去多多自己琢磨反复调试去感受过程从而理解的更深刻下期再见