c++继承是怎么实现的,C++的继承

  c++继承是怎么实现的,C++的继承

  Yyds干货库存

  在写和讲这个之前,我们需要先讲一下C的几个主要特性,封装继承,多态.注意这三个不是真的提到,只是基础。面试时请注意。封装我们已经学过了,今天就开始继承吧。我们最好跟着简单的学。这里的语法可能有点难,但是我们用的时候一定要简单一点。

  说实话,C的那些大佬也考虑了很多方法,把继承搞得很复杂,即使有三种继承方式,所以后面的语言尽量简化这个知识点。我们学c的时候真的需要一些时间去思考,现在来看看什么是继承。

  什么是继承?我查了一些资料,对继承的概念做了简单详细的描述。

  继承(英文:Inheritance)是面向对象软件技术中的一个概念。如果一个类别B“继承”了另一个类别A,则称为“A的子类”,称A为“B的父类别”也可以称为“A是B的超类”。继承可以让子类拥有父类的各种属性和方法,而不用再写同样的代码。(来源:维基百科)。

  我举个不恰当的例子。张三父亲去世,张三继承了父亲的遗产。在这里,张三是‘子类’,父亲是‘父亲’。张三拥有父亲的遗产,但除此之外,他可能还有自己的财富。

  我们为什么要继承?我们可以举个例子来帮助理解。假设我们要做一个学校人事管理系统,里面有老师和学生.角色。如果我们为每个角色封装一个类,就认为有相同的成员变量,如姓名、年龄.在每一节课上,想想都会很头疼。如果我们把这个相同的属性拿出来,做成一个单独的类,让其他类继承?

  如何继承C的遗产,可以说是一件很头疼的事情。大老板认为这太复杂了。有三种继承方式,每种继承方式对于不同的访问修饰符是不同的。我们需要仔细看看。关于继承的方式我们后面再说。我们先来看看。

  阶级人士

  {

  公共:

  作废打印()

  {

  cout name: _ name endl;

  cout age: _ age endl;

  }

  受保护d:

  string _ name= peter//名称

  int _ age=18//年龄

  };

  班级学生:公众人物

  {

  受保护d:

  int _ stuid//学生ID

  };

  int main()

  {

  学生stu

  斯图。print();

  返回0;

  }

  你从父类继承了什么?在这里,我给你一个不恰当的结论。可以说子类继承了父类的成员函数和变量。这里面也有很大的问题。

  阶级人士

  {

  公共:

  作废打印()

  {

  cout name: _ name endl;

  cout age: _ age endl;

  }

  公共:

  string _ name= peter//名称

  int _ age=18//年龄

  };

  班级学生:公众人物

  {

  受保护d:

  int _ stuid//学生ID

  };

  int main()

  {

  学生stu

  斯图。_name=张三;

  斯图。_年龄=20;

  斯图。print();

  返回0;

  }

  前面说过,看到c的继承方法就觉得很头疼,我们知道访问修饰符也有三种,这个计算是九种情况。我们很多人都带来了很大的困难。

  类/继承方法

  公共遗产

  受保护的遗产

  私人的

  公共装饰

  子类公共成员

  子类受保护成员

  子类的私有成员

  受保护的修改

  子类受保护成员

  子类受保护成员

  子类的私有成员

  私人装修

  在派生类中不可见。

  在派生类中不可见。

  在派生类中不可见。

  不要慌,这九种情况很容易区分。我们可以得到以下两个结论。

  无论私有成员的继承方法是什么,在子类中都是不可见的,其余成员都是父类的成员。与继承方法相比,具有较小权限的方法的权限与public protected private的权限进行比较。这种情况我们不用担心。一般是公有继承。父类的大多数成员都受到保护,其他成员很少使用。

  隐形VS无继承。我们需要看到哪些是看不见的,哪些不是遗传的。没有继承,我们可以理解。这里主要看什么是看不见的。

  不可见性意味着你不能在子类中直接访问这个类型,但是你可以继承它。

  然后有一个问题。我们可以间接访问这个成员吗?可以,我们可以通过函数访问,这里就不分享了。有兴趣的话可以自己试试。

  继承父类和子类之间有许多用于继承的特征,其中有两个关键特征。

  切片隐藏切片我们可以认为父类可以接受子类的类型。这就像是理所当然的,而不是一种转变。这就好比父亲可以教孩子,也算是向上调整。这也是后来多态的基础。

  派生类可以赋给基类的对象/基类的指针/基类的引用。这里有个形象的说法叫切片或者切割。剪切派生类中隐含父类的部分,赋给过去。

  让我们看看下面的例子。

  阶级人士

  {

  受保护:

  string _ name//名称

  string _ sex//性别

  int _ age//年龄

  };

  班级学生:公众人物

  {

  公共:

  int _ No//学生ID

  };

  int main()

  {

  每人;

  学生stu

  per=stu

  人p=stu

  人* p=stu

  返回0;

  }这里想提一下,这个切片是有要求的。子类继承父类的方式必须是pubilic继承,不允许其他方式。

  我来分别说一下,有一些细节分享一下。

  对于直接将子类分配给父类,这是将调用父类的分配函数。编译器会自动将从属父类的内容切片,赋给父类对象。

  阶级人士

  {

  公共:

  作废操作员=(每人)

  {

  cout void operator=() endl;

  }

  受保护:

  string _ name//名称

  string _ sex//性别

  int _ age//年龄

  };

  班级学生:公众人物

  {

  公共:

  int _ No//学生ID

  };

  int main()

  {

  每人;

  学生stu

  per=stu

  返回0;

  }

  如果一个引用引用一个有父类的子类的对象,就相当于给属于父类的子类的对象一个别名。

  阶级人士

  {

  公共:

  String _name=张三;//名称

  String _ sex= male//性别

  int _ age=18//年龄

  };

  班级学生:公众人物

  {

  公共:

  int _ No//学生ID

  };

  int main()

  {

  学生stu

  Person per=stu

  per。_年龄;

  返回0;

  }

  指针更容易理解。指针指向子类中属于父类的东西。

  int main()

  {

  学生stu

  Person per=stu

  人* p=stu

  cout stu。_ age endl

  per。_年龄;

  cout stu。_ age endl

  p-_年龄;

  cout stu。_ age endl

  返回0;

  }

  子类可以接受父类吗?这种常见的方法是不可接受的。在Java中,这被称为离线转换,但在C中不是很有性,但可以通过指针来完成。这里先按无表,多态时再分享给大家。

  隐藏也是c中的一个重要概念,众所周知,语言中存在作用域的概念,类也有类域。如果同一个类中不能定义同名的成员变量,那么父类和子类就是两个类域,所以这里可以定义同一个变量。编译器优先考虑子类的变量,子类是隐藏的,成员函数也是。

  子类和父类中存在同名成员,子类成员会阻止父类对同名成员的直接访问。这种情况叫隐藏,也叫重定义。(在子类成员函数中,可以使用基类:基类成员来显示访问权限)

  以下是一些结论。

  子类会屏蔽父类对同名成员的直接访问,只需要相同的函数名就可以形成隐藏成员变量。我们先看成员变量,主要看如何访问与父类同名的变量。

  阶级人士

  {

  受保护:

  String _name=小李子;//名称

  int _ num=111//身份证号码

  };

  班级学生:公众人物

  {

  公共:

  作废打印()

  {

  cout name: _ name endl;

  Cout ID号: Person:_ num endl;//调用父类中的隐藏变量

  Cout的学号: _ num endl

  }

  受保护:

  int _ num=999//学生ID

  };

  int main()

  {

  学生stu

  斯图。print();

  返回0;

  }

  我在这里也给出一个结论。如果我们想访问一个成员变量,编译器首先调用子类中的成员变量。如果子类不存在,我们将在父类中寻找它。如果我们真的想在父类中使用它,我们将直接在类域中声明它。

  既然变量已经讲完了,我们可以谈谈函数的隐藏了。在我们再次谈论隐藏之前,我们先问一个问题。fun()和fun(int i)是什么关系?记住,这绝对不是函数重载。函数重载需要在同一范围内。这就构成了隐藏,成员函数的隐藏只需要相同的函数名就构成了隐藏。

  A级

  {

  公共:

  虚空乐趣()

  {

  cout func() endl;

  }

  };

  B类:公共A

  {

  公共:

  无效资金(int i)

  {

  cout func(int I)- I endl;

  }

  };

  子类有6个默认成员函数。“Silent * *”* *的意思是如果我们不写,编译器会自动为我们生成一个。那么这些成员函数是如何在派生类中生成的呢?这些默认功能有问题,里面很难。这里,我们先提出一个容易理解的想法。我们把子类中继承的父类看作一个成员变量,它是第一个被声明的成员变量。这可能有助于你理解。这里主要分享四个重要的功能。

  当函数子类实例化一个对象时,需要调用(或显示调用)父类的默认构造函数。这是我们需要知道的。

  阶级人士

  {

  公共:

  Person(const char* name=peter )

  :_name(名称)

  {

  cout Person() endl;

  }

  受保护:

  string _ name//名称

  };

  班级学生:公众人物

  {

  公共:

  学生(const char* name,int num)

  :_num(数字)

  {

  cout Student() endl;

  }

  受保护:

  int _ num//学生ID

  };

  int main()

  {

  学生s1(“杰克”,18);

  返回0;

  }

  调用父类的构造函数。如果我们想调用父类的构造函数,怎么做呢?

  当我们直接在初始化列表中初始化父类的成员时会发生什么?你看不到答案。

  阶级人士

  {

  公共:

  Person(const char* name=peter )

  :_name(名称)

  {

  cout Person() endl;

  }

  受保护:

  string _ name//名称

  };

  班级学生:公众人物

  {

  公共:

  学生(const char* name,int num)

  :_name(名称)

  ,_num(数字)

  {

  cout Student() endl;

  }

  受保护:

  int _ num//学生ID

  };

  我们在构造函数体中重新分配父类的成员,我们发现这是可能的,但是这里需要知道原理。初始化列表是声明和定义的地方,函数体中的重赋值结果也证明了初始化列表中调用了父类的默认构造函数。

  班级学生:公众人物

  {

  公共:

  学生(const char* name,int num)

  :_num(数字)

  {

  _ name=name//再次赋值

  cout Student() endl;

  }

  受保护:

  int _ num//学生ID

  };

  在初始化列表中显示调用构造函数是最正确的方式。

  班级学生:公众人物

  {

  公共:

  学生(const char* name,int num)

  :_num(数字)

  ,Person(name) //这是最正确的动作。

  {

  cout Student() endl;

  }

  受保护:

  int _ num//学生ID

  };

  父类是先构造的吗?是的,我们可以认为,在子类中,父类作为成员变量,是第一个被声明的变量。在构造子类之前,有必要先帮助父类构造。

  班级学生:公众人物

  {

  公共:

  学生(const char* name,int num)

  :_num(数字)

  ,Person(name) //这是最正确的动作。

  {

  cout Student() endl;

  }

  受保护:

  int _ num//学生ID

  };

  复制构造在谈完构造函数之后,我们需要谈一谈复制构造,所以这个比较简单。这里我在强调,父类被视为自定义类型变量,它会调用自己的副本构造,而且是第一次调用。这里唯一的问题是,在初始化列表中,应该如何传入父类copy构造的函数,传入什么类型?要知道,继承是可以切片的,传入子类就行了。

  阶级人士

  {

  公共:

  Person(const char* name=peter )

  :_name(名称)

  {

  cout Person() endl;

  }

  人员(常量人员p)

  :_ name(p . name)

  {

  cout Person(const Person p) endl;

  }

  受保护:

  string _ name//名称

  };

  班级学生:公众人物

  {

  公共:

  学生(const char* name,int num)

  :_num(数字)

  ,Person(name) //这是最正确的动作。

  {

  cout Student() endl;

  }

  学生(const学生)

  :_num(序号)

  ,将发生人员//切片

  {

  cout Student(const Student s) endl;

  }

  受保护:

  int _ num//学生ID

  };

  int main()

  {

  学生s1(“杰克”,18);

  学生S2(S1);

  返回0;

  }

  赋值重载赋值重载和复制构造类似,只是在函数体中调用,因为赋值重载没有初始化列表,注意一下就可以了。

  阶级人士

  {

  公共:

  Person(const char* name=peter )

  :_name(名称)

  {

  cout Person() endl;

  }

  人员操作员=(常量人员p)

  {

  cout Person operator=(const Person p) endl;

  如果(这个!=p)

  _ name=p. _ name

  返回* this

  }

  受保护:

  string _ name//名称

  };

  班级学生:公众人物

  {

  公共:

  学生(const char* name,int num)

  :_num(数字)

  ,Person(name) //这是最正确的动作。

  {

  cout Student() endl;

  }

  学生运算符=(常量学生)

  {

  cout Student运算符=(const Student s) endl;

  如果(这个!=s)

  {

  person:operator=(s);//隐藏的突破类域会发生。

  _ num=s. _ num

  }

  返回* this

  }

  受保护:

  int _ num//学生ID

  };

  int main()

  {

  学生s1(“杰克”,18);

  学生S2(‘小丑’,18);

  s2=s1

  返回0;

  }

  析构函数这里的析构函数有问题,我们真的需要好好看看。按照我们上面的想法,很快会是析构函数吗?是的,我先析构父类,最后析构子类(这个顺序不对),我们也是这样。

  阶级人士

  {

  公共:

  Person(const char* name=peter )

  :_name(名称)

  {

  cout Person() endl;

  }

  ~人()

  {

  cout ~ Person() endl;

  }

  受保护:

  string _ name//名称

  };

  班级学生:公众人物

  {

  公共:

  学生(const char* name,int num)

  :_num(数字)

  ,Person(name) //这是最正确的动作。

  {

  cout Student() endl;

  }

  ~学生()

  {

  ~ Person();

  cout ~ Student() endl;

  }

  受保护:

  int _ num//学生ID

  };

  int main()

  {

  学生斯图(‘杰克’,18);

  返回0;

  }

  我们对此感到困惑。我们为什么报告错误?我们似乎在使用正确的方法。子类和超类不构成隐藏函数。为什么?这是因为C在设计析构函数,函数名有问题。记住父子类的析构函数构成了隐藏关系。这是因为析构函数被编译器统一视为析构函数()。这是为了多态性的需要,需要突破类域。

  班级学生:公众人物

  {

  公共:

  学生(const char* name,int num)

  :_num(数字)

  ,Person(name) //这是最正确的动作。

  {

  cout Student() endl;

  }

  ~学生()

  {

  Person:~ Person();

  cout ~ Student() endl;

  }

  受保护:

  int _ num//学生ID

  };

  我们可以在这里销毁,现在又有一个问题。我们似乎毁灭了一个人两次。幸运的是,我们没有在父类中使用delete。如果我们不删除两次,编译器肯定会报错。

  所以在这里我们开始怀疑,如果我们不能做到这一点,那么我们就不能做到这一点。怎么才能做到呢?我们什么都不做,就这样。

  班级学生:公众人物

  {

  公共:

  学生(const char* name,int num)

  :_num(数字)

  ,Person(name) //这是最正确的动作。

  {

  cout Student() endl;

  }

  ~学生()

  {

  cout ~ Student() endl;

  }

  受保护:

  int _ num//学生ID

  };

  总结我们需要总结析构函数。子类的析构函数不需要照顾父类,但是编译器会自动调用它们。我们还需要总结一下析构函数,即先析构子类,后析构父类。这里符合先构建父类,后构建子类的顺序。

  朋友和遗产。如果我们在父类中放一个friend函数,这里需要说明的是,子类与这个函数无关,也就是friend不能被继承。

  班级学生;

  阶级人士

  {

  公共:

  好友void显示(常量人p,常量学生s);

  受保护:

  string _ name//名称

  };

  班级学生:公众人物

  {

  受保护:

  int _ stuNum//学生ID

  };

  无效显示(固定人员p,固定学生s)

  {

  cout p. _ name endl

  cout s. _ stuNum endl

  }

  int main()

  {

  人p;

  学生s;

  显示(p,s);

  返回0;

  }

  并且继承的静态成员的基类定义了静态静态成员,那么在整个继承系统中只有一个这样的成员。无论派生多少个子类,静态成员都只有一个实例。这里没什么好谈的。

  阶级人士

  {

  公共:

  静态int _ count//数人数。

  };

  int Person:_ count=0;

  班级学生:公众人物

  {

  };

  毕业班级:公共学生

  {

  };

  int main()

  {

  cout Person:_ count endl;

  cout学生:_ count endl

  cout毕业生:_ count endl

  返回0;

  }

  在多继承的现实世界中,一个人可能是老师的助手,也就是说他有两个身份,一个是学生,一个是老师,在C中他也支持这种情况,这是C最痛苦的地方,C支持多继承,好家伙,我们讨论的难度又要上一个台阶了。多继承存在很多问题,比如二义性和代码冗余,很多后来的语言Java都抛弃了多继承。

  钻石继承是多重继承的特例。这里我们以钻石传承为例。

  阶级人士

  {

  公共:

  string _ name//名称

  };

  班级学生:公众人物

  {

  受保护:

  int _ num//学生ID

  };

  班主任:公众人物

  {

  受保护:

  int _ id//员工编号

  };

  班级助理:公学生,公教师

  {

  受保护:

  string _ majorCourse//主修课程

  };

  我们可以接受代码冗余。助教分别继承老师和学生,两者都继承Person,导致代码有些冗余。

  int main()

  {

  助理a;

  返回0;

  }

  如果空间小,也不错。如果空间大,会造成空间浪费。我们在这里给person添加一个大数组,会造成巨大的空间浪费。

  阶级人士

  {

  公共:

  string _ name//名称

  int arr[100000];

  };

  int main()

  {

  cout sizeof(助理)endl

  返回0;

  }

  歧义说完了代码冗余,这里还有一个问题,就是歧义。我们想问,我们如何在继承person中访问那些成员变量?里面有两个_名,造成不确定性。

  int main()

  {

  助理a;

  a._ name= peter

  返回0;

  }

  当然,如果我们真的想访问这里的_name,也不是不可能。我们需要突破类域来访问它。

  int main()

  {

  助理a;

  a.学生:_ name= xxx

  a.老师:_ name= yyy

  返回0;

  }

  虚拟继承虚拟继承可以解决钻石继承的二义性和数据冗余。例如,上面的继承关系可以在学生和教师继承Person时使用虚拟继承来解决问题。应该注意的是,虚拟继承不应该在其他地方使用。

  让我们实际看看多重继承代码的冗余。在这里,我将查看实际内存。注意,这里没有监控窗口。VS的监控窗口被美化了。

  A级

  {

  公共:

  int _ a;

  };

  B类:公共A

  {

  公共:

  int _ b;

  };

  C类:公共A

  {

  公共:

  int _ c;

  };

  D类:公共B、公共C

  {

  公共:

  int _ d;

  };

  int main()

  {

  D d

  d.b:_ a=1;

  d.c:_ a=2;

  d._ b=3;

  d._ c=4;

  d._ d=5;

  返回0;

  }

  从这里我们可以看到D先继承了B,所以这里先为B创建空间,再为a创建空间,我们也可以看到这里确实创建了更多不必要的空间,比如_a已经创建了两次。

  虚拟继承现在,我们可以谈谈虚拟继承。虚拟继承就是只保留重复类内容的一个副本,解决代码的冗余和二义性。

  A级

  {

  公共:

  int _ a;

  int arr[100000];

  };

  B类:虚拟公众A

  {

  公共:

  int _ b;

  };

  C类:虚拟公共A

  {

  公共:

  int _ c;

  };

  D类:公共B、公共C

  {

  公共:

  int _ d;

  };

  int main()

  {

  cout sizeof(D)endl;

  返回0;

  }

  我们需要查看代码的实际内存,并分析虚拟继承是如何产生的。通过这个我们可以发现,虚继承的所有重复变量都放在一起,变成了一个副本,这样就解决了。

  如果你只是想知道C,你会在顶级完成。如果你想更深入,这里还有一个问题。

  这两个指针是一个指针。这两个指针叫虚基表指针,指针叫虚基表(图不对)。

  我们对此感到困惑。我们需要两个虚拟继任表做什么?仔细看,找到虚拟基表指针所指地址的下一个位置(4个字节)。一个20,一个12。我们将要讨论这两个数字。我们知道公共区域的偏移量。让我们看一看。这样无论公共区域走到哪里,只要有偏移就能找到。

  注意虚拟继承是如何发生的。只要我们有虚拟继承,就一定会有这个sequel表,这是为了统一规则,方便编译器的工作。这里我们知道编译器先找到这个虚拟继承表指针,计算偏移量,得到对应的内存,然后和自己的一起拿出来,这也是虚拟继承表的作用。

  int main()

  {

  D d

  b b=d;

  c c=d;

  返回0;

  }继承总结继承是一个语法很多的知识点,但在实际应用中相对简单。我们一般不会用看起来很复杂的代码,比如多继承,我们很少用。即使是单继承,我们一般也采用公有继承。

  继承和组合的结合就是让自定义类型成为类的成员。

  公共继承是一种is-a关系。也就是说,每一个派生类对象都是一个基类对象。组合是一种有-有关系。假设B组合A,每个B对象中都有一个A对象。在这里,我还想说一个特点。在计算机中,我们的类之间的联系越少越好。这也是软件工程提出的‘高内聚,低耦合’。没有无关代码,模块间自由。

  所以继承在一定程度上破坏了封装,父类和子类的关系过于紧密。继承允许您根据基类的实现来定义派生类的实现。这种通过生成派生类的重用通常被称为白盒重用。“白盒”这个词是相对于可见性而言的:在继承的方式中,基类的内部细节可以从子类中继承,这在一定程度上破坏了基类的封装性,基类的变化对派生类的影响很大。派生类和基类具有强依赖性和高耦合性。

  对象组合是除类继承之外的另一个重用选项。新的和更复杂的功能可以通过组装或组合对象来获得。对象组合要求要组合的对象具有定义良好的接口。这种重用方式被称为黑盒重用,因为对象的内部细节是不可见的。对象只显示为“黑盒”。类之间没有很强的依赖性,耦合度低。使用优先级对象组合有助于保持每个类的密封。

  对于适合继承和组合的,我们优先选择组合。

郑重声明:本文由网友发布,不代表盛行IT的观点,版权归原作者所有,仅为传播更多信息之目的,如有侵权请联系,我们将第一时间修改或删除,多谢。

相关文章阅读

  • office2010激活密钥大全 怎么永久激活office2010
  • project2010产品密钥免费_project2010激活密钥永久激活码
  • c语言调用退出函数 c语言退出整个程序怎么写
  • c语言中怎么给函数初始化 c语言的初始化语句
  • c语言编写函数计算平均值 c语言求平均函数
  • chatgpt是什么?为什么这么火?
  • ChatGPT为什么注册不了?OpenAI ChatGPT的账号哪里可以注册?
  • OpenAI ChatGPT怎么注册账号?ChatGPT账号注册教程
  • chatgpt什么意思,什么是ChatGPT ?
  • CAD中怎么复制图形标注尺寸不变,CAD中怎么复制图形线性不变
  • cad中怎么创建并使用脚本文件,cad怎么运行脚本
  • cad中快速计算器的功能,cad怎么快速计算
  • cad中快速修改单位的方法有哪些,cad中快速修改单位的方法是
  • cad中心点画椭圆怎么做,cad轴测图怎么画椭圆
  • CAD中常用的快捷键,cad各种快捷键的用法
  • 留言与评论(共有 条评论)
       
    验证码: