[C++] 面向对象语言的三大特性--继承



什么是面选对象语言的三大特性?

这里先简单来说下什么是面向对象的三大特性

  1. 封装
  2. 继承
  3. 多态

封装

把函数和变量全部整合在一起进行管理,保护,如兵马俑如果不造围墙保护起来,限定你的活动范围起来,不然这些文物没几年说不定就消逝了。而封装也是一样把重要的数据给保护起来,暴露我想给接口访问




什么是继承?


继承这个词一般是在那个场景会用到呢?意志在这里插入图片描述
那我们来以这个例子来引出继承吧

void test1()
{
    
    
    int a =1;
    int b =2;
    
    //交换
    int c=a;
    a=b;
    b=c;
}
void test2()
{
    
    
    int a=2;
    int b=4;
    //交换
    int c= a;
    a=b;
    b=c;
    
}

上面的代码你会发现很冗余交换这一部分的代码写了俩次,那么如果是你会如何做呢?
如果是我的话我会吧这一部分的代码提出去封装一个函数,那么我调用这个函数就可以理解为继承


那么下面这种情况要如何处理呢?在自定义类型(class,struct……)中出现了冗余

class Student
{
    
    
public:
    int StuId;
    
//重复部分
    string name;
    string address;
    int telephone;

};

class Teacher
{
    
    
public:
    int TechId;
    
//重复部分
    string name;
    string address;
    int telephone;
};

那我们也可以和向函数一样吧共的部分给提出来封装一个类,那么这个时候有俩种方法可以使用

  1. 在本类中创建一个重复部分类的对象
  2. 继承


    在这里下一个定义其实也是人如其名,他可以继承下 父亲类(当前例子封装的类)的 ’‘全部’‘ 成员

方法1

class PersonMessage
{
    
    
public:
    string name;
    string address;
    int telephone;
    
};
class Student
{
    
    

    PersonMessage d1;
    int StuId;
};
class Teacher
{
    
    
    PersonMessage d1;
    int TechId;
};

方法2

class PersonMessage
{
    
    
public:
    string name;
    string address;
    int telephone;
    
};

class Student: public PersonMessage
{
    
    
    int StuId;
};

class Teacher:public PersonMessage
{
    
    
    int TechId;
};

第一种叫方法叫做 “黑盒”,第二种叫做“白盒”(文章末尾在进行解析)


继承的使用说明

在类名后 加 : 继承方式 被继承的类
在这里插入图片描述

当俩个俩处于继承关系的时候他们他们就有一个专有的叫法

  • 被继承的类叫做 父类或者基类

  • 继承的类叫做 子类或者派生类


继承方式:

这个是用来确定继承下来的数据的属性(public,private,protected)其中之一,且继承下来的属性还受父类限定符的影响


那么简单的看看有哪些继承方式呢?

在这里插入图片描述

protected英语时保护的意思,没有继承的情况下和private一致,但继承后和private不同了,这就是代替了原来的private限制符,可以继承但是是保护(类外部无法直接访问)


那么上面不仅仅是由继方式还和父类的限定符有关那如何确定呢?

仔细观察上面的表


public继承public成员还是public
public继承protected就成了protectoe
private继承,public与protected都是private


可以看到是一个权限缩小的感觉,那么可以得出这样的关系 public>protected>private,而继承是继承权限小的属性


不可见是一个啥意思呢?

可以继承,但在派生类中是不可使用的。就如没有继承的时候你访问类的时候只可以访问公有的成员。private是不可用访问的这里也是类似




特殊用法,子类给父类赋值


你是否会疑问??? 自定义类型不是同类型的才支持赋值吗?

简单理解他可以类似不同内置类型赋值,他底层会强制类型转换,而子类赋值给父类的“强制类型转换”叫做切片


切片的原理
就是把子类继承父类中的变量给切割出来如图所示:
在这里插入图片描述

你或许会疑问??继承下来的为啥都在聚集在一起且在上半部分

这个与构造有关系本文下面有专门的讲解


子类可以赋值给父类,那么父类的指针与引用可不可以赋值呢?

那当然是可以的,和子类赋值父类一样,先切片,然后指针和引用就指向切下来的那块区域

class father
{
    
    
 public:
   string name;
   string address;
   int telephone;
   int age;
   char sex;
};
class child:public father
{
    
    
public:
 string favourite;
		
};
int main()
{
    
    
  father d1;
  child d2;
   …………(d2赋值操作)

   d1 =d2;//对象
   father *d3=&d2;//指针
   father& d4=d2;//引用
}

如图所示:
在这里插入图片描述




继承后构造、析构…………是如何实现的?

是否有这样的疑问,父类的元素如何初始化、拷贝、析构,赋值,是如何实现的?

构造

这里相比之前的构造不同需要分为三类

  1. 内置类型不处理
  2. 自定义类型调用
  3. 父类(直接看成一个整体,自定义类型处理)

情况1(不写构造函数):

在这里插入图片描述

父类调用自己的构造函数进行初始


情况2(写构造函数)

上述说过父类是看做一个整体,一个自定义类型,那么我们就可以单独写一个构造函数,指定构造父类的成员(类似STL实现多种构造进行重载)

在这里插入图片描述

如上所说父类是一个整体,一个自定义类型,只是没有变量名,所以直接用类名直接调用构造函数


数据构造的顺序

是父类先调用构造函数还是子类先调用?
在这里插入图片描述

linux下的结果
在这里插入图片描述

可以看到父类的变量地址比较低,且他们是内置类型,他们是存在栈上的,那么栈满足就是先定义先压栈,且占是向低地址增长的以linux为主吧,父类先初始化,子类后,clion编译器下是相反的


拷贝构造,赋值重载

和构造函数一样,他也是被分为三种情况,具体情况如上

情况1(不写写拷贝构造与赋值重载):

在这里插入图片描述

情况2(写拷贝构造与赋值重载):

这里拷贝构造与构造函数一样,但是赋值重载则有大问题
在这里插入图片描述

如图所示:
在这里插入图片描述

解释:

可能一些小伙伴马上就反应出来了,局部优先,半对半错吧。


这一块其实是一个隐藏,当父类和子类中有变量的变量名相同时就会构成隐藏,简单理解就是局部优先

那么如何解决呢?

这一块现在对你来说应该很简单吧,只需要制定一下类域即可


有人可能会问,父类可以用子类拷贝构造吗?可以给我赋值吗?

验证:
在这里插入图片描述

解释:

这里为什么会成功,应为运用了上述的一个操作“切片”,把子类中继承下来的数据切割出来,然后进行拷贝构造或者赋值重载


析构
这里也是被分为三种情况,具体情况如上

代码:
在这里插入图片描述

解释:

这里的意思大致说这个代码没有呀意义,因为这里构成了隐藏,为啥呢?不是要父子类函数名相同才构成重载吗,我不是函数名不同吗,这里编译器底层吧析全部替换成了distract(后面多态会用下篇博客)

解决方案

既然是隐藏那么我指定类域

这里你会看到一个特殊的情况,为啥我析构了俩次
在这里插入图片描述

解释:

上面有个概念就是构造的顺序,父类先构造,然后子类,按照栈的结构来看,会先析构子类,然后在析构父类。上面的代码我们指定析构父类,然后子类的析构完就调用父类的析构,才导致了double次析构




继承有啥缺陷吗?

看着继承似乎很完美,但是他有一个致命的缺陷,以至于这个继承在语言中只有C++有这一特性,java已经舍弃,那么他的缺陷就是多继承导致的菱形继承


菱形继承

当多继承的时候会出现这种情况,就是俩个父类都继承了同一个类
在这里插入图片描述


那么就会造成一个问题就是数据冗余(继承了俩份一样的数据),且有二义性(如图,class4 访问class中的数据时不知道访问class2 还是 class3 )


解决方案

  1. 指定类域访问(解决不了冗余)
  2. 虚继承

代码:

class PersonMessag//对应上图class 1
{
    
    
public:
    string name;
    string address;
    int telephone;
};
class  Student: public PersonMessage//对应上图class 2
{
    
    
public:
    int StuId;
};
class Teacher :public PersonMessage//对应上图class 3
{
    
    
public:
    PersonMessage d1;
    int TechId;
};
class Graduate:public Student,Teacher//对应上图class 4
{
    
    
};


多继承的缺陷:

int main()
{
    
    
  Graduate person;

  person.name;//error
}

如图所示:

在这里插入图片描述


解决方法一(指定类域)

int main()
{
    
    
   
  Graduate person;

  person.Student::name;
  person.Teacher::name;
}

有没有感觉似曾相识,这个场景在哪里见过?
命名空间,本质都是一样的


解决方法二(虚继承)

在重复继承的方式前加一个关键字 virtual(上图中 class 3 与 class 4 处)

在这里插入图片描述

代码:

class PersonMessage
{
    
    
public:
    string name;
    string address;
    int telephone;
};
class  Student:virtual public PersonMessage//腰处1,class 3
{
    
    
public:
    int StuId;
};
class Teacher :virtual public PersonMessage//腰处1,class 4 
{
    
    
public:
    int TechId;
};
class Graduate: public Student,Teacher
{
    
    

};

对象内存模型视图
在这里插入图片描述

偏移量指针指向一张‘’表‘’,虚基表,里面存有俩个元素一个是关于多态(下篇博客讲解),还有就是偏移量(上图中Tehcer,Studen 到 PersonMessage的距离)




面试看能会问到的题目


父类与子类的作用域

当父类与子类有重名的变量时那么下面代码运行结果时啥?

class Person
{
    
    
protected :
	int _num = 111; // 身份证号 
};
class Student : public Person
{
    
    
public:
    void Print()
    {
    
    
	   cout<<" 学号:"<<_num<<endl;
	} 
protected:
	int _num = 999; // 学号 
};

int main()
{
    
    
	Student s1;
    s1.Print();
}

结果:
在这里插入图片描述

解释:

抽象来理解就是局部优先,有同名的变量时先执行本类的。


真正的定义:
上述这些操作有一个专有名词就是隐藏,当子类和父类有变量名相同,那么子类会自动隐藏父类的变量


当父类与子类有重名的函数时那么下面代码fun算是重载还是隐藏


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(20);
}

解释:

这一块稍微点概念疏忽这一题就会选重载,重载时要在同一作用域下才会进行重载,而从题目1,2 可以看出,父类与子类是有各自的作用域的,所以不会构成重载,而是隐藏


下面代码中有多少个静态成员_count?

class Person
{
    
    
public :
    Person () 
    {
    
    
   		 ++ _count ;
    }
 static int _count; // 统计人的个数。
 
protected :
	string _name ; // 姓名 public :

 };
 
int Person :: _count = 0;//静态成员

class Student : public Person
{
    
    
};

class Graduate : public Student
{
    
    
};
void TestPerson()
{
    
    
Student s1 ;
Student s2 ;
Student s3 ;
Graduate s4 ;

cout<<Person::_count<<endl;
//验证
Student ::_count = 0;
cout<<Person::_count<<endl;

结果:
在这里插入图片描述

解释:

在继承流全部类共享这个静态变量,所以修改就是修改同一个静态变量




补充知识(白盒与黑盒)

白盒就可以理解里面的数据都是可见,而黑盒是不可见的(直观),如图所示:
在这里插入图片描述

其实更精确的来说就是白盒看起来就是属于自己本类的,黑盒就是像一个哆啦A梦的口袋一样看不到,到那时有需求去拿就好(大雄~~~)


那么现在有这样一个问题是白盒好还是黑盒好?

算是写程序的一个宗旨就是 “高内聚,低耦合”,模块与模块直接是低耦合(模块与模块关联小),但是模块本身是高内聚(模块自身关联强),举个例子:


三人要用积木搭个机器人,一人搭头,一人搭身体,一人搭手脚,假设搭好组装后发现不匹配,那么另外俩人就要重搭或者改造(这就是抽象的模块与模块之间高内聚,就会导致一个模块出错,别的也出错),如果是一个人之间搭身体,俩个人搭武器,如果机器人需改改动,这武器只需要微小的改动(模块与模块之间的低耦合,一个模块出错,对另外模块影响较小)




唠唠家常

这篇博客多多少少写了快半个月了,期间经过了过年,每天有一搭没一搭的写一点或者就不写,没错年已经过去7天了。
在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/Legwhite/article/details/122700362