二、转换的本质 派生类可以转换为基类的本质是: ①为什么派生类可以转换为基类:派生类从基类而来,因此派生类中包含了基类的方法和成员。...此时基类可以通过指针或引用指向派生类(相当于将派生类从基类中继承的那部分方法和成员绑定到基类上了,相当于派生类被截断了),然后基类就可以将派生类假装是一个基类对象来使用(调用其中的成员/方法) ②为什么基类不能转换为派生类...如果将一个基类对象绑定到派生类的指针/引用上,此时派生类通过指针/引用访问自己新定义的成员/方法时,发现找不到(因此不能将基类转换为派生类) 例如:下面B继承于A,子类继承于父类,同时为父类的成员开辟了空间...此处p2指针的类型为A,因此调用A的getA()函数。又因为b对象使用setA()函数将整个继承体系中的a改为了20,因此打印出来的a为20 ?...演示案例② 我们修改演示案例①,上面是将基类的指针指向于派生类。
说明:本来基类指针是用来指向基类对象的,如果用它指向派生类对象,则进行指针类型转换,将派生类对象的指针先转换为基类的指针,所以基类指针指向的是派生类对象中的基类部分。...虚函数突破了这一限制,在派生类的基类部分中,派生类的虚函数取代了基类原来的虚函数,因此在使基类指针指向派生类对象后,调用虚函数时就调用了派生类的虚函数。...但是,从基类继承来的某些成员函数不完全适应派生类的需要,例如在例12.2中,基类的display函数只输出基类的数据,而派生类的display函数需要输出派生类的数据。...过去我们曾经使派生类的输出函数与基类的输出函数不同名(如display和display1),但如果派生的层次多,就要起许多不同的函数名,很不方便。如果采用同名函数,又会发生同名覆盖。...需要说明;有时在基类中定义的非虚函数会在派生类中被重新定义(如例12.1中的area函数),如果用基类指针调用该成员函数,则系统会调用对象中基类部分的成员函数;如果用派生类指针调用该成员函数,则系统会调用派生类对象中的成员函数
派生类与基类之间的关系: 派生类对象可以使用基类(公有的)方法。 基类指针可以在不进行显示类型转换的情况下指向派生类对象,但只能调用基类方法。...实现多态公有继承的方法: 在派生类中重新定义基类的方法。 使用虚方法(虚函数)。 3. 静态联编和动态联编 函数名联编:编译器将源代码中的函数调用解释为执行特定的函数代码称为函数名联编。...将派生类引用或指针转换为基类引用或指针称为向上强制转换,该转换使得公有继承不需要进行显示类型转换。...且该转换是可以传递的,例如基类A,其派生类AP,AP的派生类APP,则A指针或引用可以指向或引用AP类对象和APP类对象。 相反的,我们将基类指针或引用转换为派生类指针或引用称为向下强制转换。...因此,如果要重新定义继承的方法,则应确保与原来的原型完全相同,但是如果返回类型是基类引用或指针,则可以修改为指向派生类的引用或指针,即允许返回类型随类类型的变化而变化,这种特性被称为返回类型协变。
Bjarne Stoustrup设计了几种方案[LIPP88],第一种存在普通指针内,限定了虚表大小,规定值小于128即为虚函数索引。...每次传递这个指针,都必须产生较大的临时对象。 为了减少上述调用成本,VC++引入了vcall thunk,thunk将非虚函数地址改为存放指向一小段代码的指针,这一段代码用来选出并调用虚函数。...一个古老的实现方法是,在每一个派生类对象中存放一个虚基类指针而非传统对象模型中的基类对象本身,对虚基类的访问通过指针间接实现,以此实现共享。...对于问题二: 编译器使用拷贝操作获得所有嵌套虚基类的指针存至派生类,以空间换时间,从而解决了固定存取时间的问题。...而在基类指针调用派生类重写的虚函数时,则需要反过来调整this指针(由编译器插入或者thunk引入),从而正确指向对应的虚表。
为什么需要虚构函数 上面的代码如果加入析构函数释放内存,对于使用new在自由储存区中实例化的派生类对象,如果将其赋值给基类指针,并通过该指针调用delete,将不会调用派生类的析构函数,这可能导致资源未释放...抽象基类和纯虚函数 不能实例化的基类被称为抽象基类,这样的基类只有一个用途,那就是从它派生出其他类。在 C++中,要创建抽象基类,可声明纯虚函数。...这让基类可指定派生类中方法的名称和特征(Signature),即指定派生类的接口。虽然不能实例化抽象基类,但可将指针或引用的类型指定为抽象基类。...如果这些基类没有采用虚继承,将导致二义性。...注意:C++关键字virtual的含义随上下文而异(我想这样做的目的很可能是为了省事),对其含义总结如下: 在函数声明中,virtual意味着当基类指针指向派生对象时,通过它可调用派生类的相应函数。
演示案例 当我们使用基类的引用(或指针)时,我们并不清楚该引用(或指针)所绑定的对象的真实类型,该对象可能是基类的对象,也可能是派生类的对象。...A,因此调用A的getA()函数 A 20:虽然p2指针指向的类类型为B,但是访问规则只与指针/引用的类类型有关,而与指针/引用指向的类型无关。...又因为b对象使用setA()函数将整个继承体系中的a改为了20,因此打印出来的a为20 静态绑定 当我们调用non-virtual函数时,调用的函数版本与指针的类型有关 例如,上面的pB指针在初始化时,...{ D x; B *pB = &x; pB->mf(); //调用D::mf() D *pD = &x; pD->mf(); //调用D::mf() return 0; } 三、为什么不建议派生类隐藏基类的...如果: 我们在派生类中隐藏了基类的non-virtual函数,那么基类与派生类就会产生行为上的不一致,is-a关系就消失了 如果想要表现出派生类与基类的不同,那么应该将函数声明为virtual(其中虚析构函数是一个例子
访问基类方法 我们知道,在私有继承时,基类的公有对象以及保护对象会变成派生类的私有对象。我们可以在派生类方法当中使用它,但无法通过派生类对象直接调用,但无法访问基类的私有方法和对象。...这个概念我们很好理解,但具体到实现上,我们如何在派生类的方法当中调用基类的公有或者保护方法呢? 比如,在之前的类声明当中我们声明了一个Average方法,用来计算学生考试成绩的平均分。...const string& Student::Name() const { return (const string&) *this; } 因为Student类是从string类中派生而来,所以我们是可以将...访问基类的友元 方法和对象都好办,但友元怎么办呢?因为友元函数并不属于类,所以无法通过类名或者解析运算符搞定。针对这种情况,我们只能取巧,通过显式地对派生类进行类型转换实现。...引用不会自动发生转换,是因为在私有继承当中,在不进行显式类型转换的情况下,不能将派生类的引用或指针赋给基类的引用或指针。 那么问题来了,如果我们使用的是公有继承,那么是不是就可以了呢?
父类是一个更一般的类,它定义了一种通用的数据类型和方法,这些可以被其他类继承。...无法访问,因为_No是Student特有的成员,即使它实际上存在于sobj中 即使我们通过基类引用或指针操作对象,派生类对象的完整信息(所有成员变量和函数)仍然都在内存中,没有丢失。...使用引用和指针时不会发生切片 对象切片的问题仅在派生类对象被赋值给另一个基类类型的对象时才会发生,比如当派生类对象被传值给一个基类对象的函数参数,或者通过赋值构造一个新的基类对象。...在使用引用或指针时,这种情况并不会发生 基类对象不能赋值给派生类对象 基类的指针或者引用可以通过强制类型转换赋值给派生类的指针或者引用。但是必须是基类的指针是指向派生类对象时才是安全的。...; } protected: int _num = 999; // 学号 }; void Test() { Student s1; s1.Print(); }; 这段代码展示了成员隐藏,以及如何在派生类中访问基类的被隐藏成员的概念
派生类: 派生类是通过对基类进行修改和扩充得到的,在派生类中,可以扩充新的成员变量和成员函数。 派生类拥有基类的全部成员函数和成员变量,不论是private、protected、public。...---- — 5 — 派生类的构造函数 通常在初始化派生类构造函数时,派生类构造函数是要实现初始化基类构造函数的。...那么如何在派生类构造函数里初始化基类构造函数呢?...第33-36行代码是正确派生类构造函数初始化基类构造函数的方式,通过调用基类构造函数来初始化基类,在执行一个派生类的构造函数 之前,总是先执行基类的构造函数。...从上面的例子中我们也得知构造派生对象前,是先构造基类对象,那么在析构的时候依然依据“先构造,后初始化”的原则,所以派生类析构时,会先执行派生类析构函数,再执行基类析构函数。
当使用指针访问虚基类成员变量时,由于指针可以是指向派生类实例的基类指针,所以,编译器不能根据声明的指针类型计算偏移,而必须找到另一种间接的方法,从派生类指针计算虚基类的位置。...在VC++ 中,对每个继承自虚基类的类实例,将增加一个隐藏的“虚基类表指针”(vbptr) 成员变量,从而达到间接计算虚基类位置的目的。...况且,这种实现方式还有一个大弊端:从多个虚基类派生时,类实例将占用更多的内存空间;获取虚基类的虚基类的地址时,需要多次使用指针,从而效率较低等等。...4 多重继承下的虚函数 如果从多个有虚函数的基类继承,一个实例就有可能包含多个vfptr。考虑如下的R和S类: ?...MSC++的实现不是这样,MSC++有意将S::rvf编译为接受一个指向S中嵌套的R实例,而非指向S实例的指针(我们称这种行为是“给派生类的指针类型与该虚函数第一次被引入时接受的指针类型相同”)。
同样a[5] 改为a[6]依旧占用24byte,但是改为a[7]将占用32byte。...派生类构造函数必须对这3类成员进行初始化,其执行顺序是这样的: 先调用基类构造函数; 再调用子对象的构造函数; 最后调用派生类的构造函数体 当派生类有多个基类时,处于同一层次的各个基类的构造函数的调用顺序取决于定义派生类时声明的顺序...在派生时,派生类是不能继承基类的析构函数的,也需要通过派生类的析构函数去调用基类的析构函数。...而 虚函数 的作用是允许在派生类中重新定义与基类同名的函数,并且可以通过基类指针或引用来访问基类和派生类中的同名函数。...虚函数的声明方式: virtual 返回类型 函数名(); 当把基类某个成员函数声明为虚函数后,就允许在其派生类中对该函数重新定义,赋予它新的功能,且可以通过指向基类的指针指向同一类族中不同类的对象,从而调用其中的同名函数
函数参数也可以用const说明,用于保证实参在该函数内不被改动。 void型指针 void通常表示无值,但将void作为指针的类型时,它却表示不确定的类型。...因为使用对象引用作为函数参数不但具有用对象指针做函数参数的优点,而且用对象引用作函数参数将更简单、更直接。...,可以在派生类内完成以下几种功能: 可以增加新的数据成员和成员函数 可以对基类的成员进行重定义 可以改变基类成员在派生类中的访问属性 基类成员在派生类中的访问属性 派生类可以继承基类中除了构造函数与析构函数之外的成员...在C++中,可以通过将这个公共的基类声明为虚基类来解决这个问题。这就要求从类base派生新类时,使用关键字virtual将base声明为虚基类。...若虚基类由非虚基类派生而来,则仍然先调用基类构造函数,再调用派生类的构造函数。 ~ 5.6 赋值兼容规则 在一定条件下,不同类型的数据之间可以进行类型转换,如可以将整型数据赋值给双精度型变量。
4.子类析构时要调用父类的析构函数吗? 析构函数调用的次序是先派生类的析构后基类的析构,也就是说在基类的的析构调用的时候,派生类的信息已经全部销毁了。...定义一个对象时先调用基类的构造函数、然后调用派生类的构造函数;析构的时候恰好相反:先调用派生类的析构函数、然后调用基类的析构函数 5.C++中的class和struct的区别 从语法上,在C++中(只讨论...b.覆盖是指派生类函数覆盖基类函数,特征是: (1)不同的范围(分别位于派生类与基类); (2)函数名字相同; (3)参数相同; (4)基类函数必须有virtual关键字。 c....“隐藏”是指派生类的函数屏蔽了与其同名的基类函数,规则如下: (1)如果派生类的函数与基类的函数同名,但是参数不同。此时,不论有无virtual关键字,基类的函数将被隐藏(注意别与重载混淆)。...(2)如果派生类的函数与基类的函数同名,并且参数也相同,但是基类函数没有virtual关键字。
我们很多时候希望的是我们通过将基类指针指向派生类,然后可以动态调用派生类的函数,这时我们可以将基类的对应函数写为虚(virtual)函数来实现,此时发生的称为动态绑定 基类通过在声明语句前加上关键字virtual...实现虚函数,virtual只能添加在类内的函数声明前。...,这一项决定的是派生类对继承来的成员对外表现出的最高权限,也就是这一项并不会影响派生类内部的使用,但是一旦外部想调用派生类来使用成员: 如果此时访问说明符为public,则成员对外表现如基类的权限 如果此时为...为了规范和可读性,最好不要利用默认控制符,显式说明比较清晰 15.6 继承中的类作用域 类中的名字查找是从内到外查找的,当派生类中无法找到时,就会往直接基类查找,以此类推 名称查找是根据编译时的目标的静态类型进行查找的...,对于实现的内容我们一样可以使用=default简化 如果基类的析构函数不是虚函数,则delete一个指向派生类对象的基类指针将产生未定义的行为 如果定义了虚析构函数,则一样的合成移动操作将被阻止 派生类的析构函数和以往一样是空函数
要注意,重写和被重写的函数是在不同的类当中的,重写函数的访问修饰符是可以不同的,尽管 virtual 中是 private 的,派生类中重写可以改为 public。...大多数编译器中,参数是从右向左入栈(原因在于采用这种顺序,是为了让程序员在使用C/C++的“函数参数长度可变”这个特性时更方便。...如果对象类型是派生类,就调用派生类的函数,如果是基类,就调用基类的函数。...),仅执行基类的析构,派生类的自身内容将无法被析构,造成内存泄漏。...; 类之间应满足子类型关系,通常表现为一个类从另一个类公有派生而来; 必须先使用基类指针指向子类型的对象,然后直接或间接使用基类指针调用虚函数; 什么是类的继承?
需要注意的一点是, 派生类的虚函数表和基类的结构是一致的, 其中析构函数和 eat() 是自己的, bark() 沿用了 Animal 的 (析构函数编译器自动制造一个)....如果派生类中新增了虚函数, 虚函数表中会在原来的基础上新增....对象没有发生任何的变化, 仅仅是让一个基类的指针指向了派生类的对象. 如果把派生类的对象赋值给基类的对象会发生什么?...a 调用的还是 Animal 类内的函数....很自然的想法是将函数指针Fun 声明为 typedef void(*Fun)(Animal*);, 然后通过传参将 “this 指针” (实际上是指向对象的指针) 传给函数, 以期待函数将这个参数像 this
如果想使用其他的基类构造函数,我们需要以类名加圆括号内的实参列表的形式为构造函数提供初始值。这些实参用于帮助编译器到底应该选择哪个构造函数来初始化派生类对象的基类部分。...有一点需要注意的是,即使一个基类指针或者引用绑定在一个派生类对象上,我们也不能执行从基类到派生类的转换: Bulk_quote bulk; Quote *itemP = &bulk; //...3.4 存在继承关系的类型之间的转换规则 要想理解在具有继承关系的类之间发生的类型转换,有三点特别重要: 从派生类向基类的类型转换只对指针或者引用类型有效 基类向派生类不存在隐式类型转换 和任何其他成员一样...如果一个名字在派生类的作用域内无法正确解析,那么编译器将继续在外层的基类作用域中寻找该名字的定义。...一如往常,名字查找先于类型检查 声明在内层作用域的函数并不会重载声明在外层作用域的函数。因此定义派生类中的函数也不会重载其基类的成员。
下面对C++的一些特性,以及如何在c里实现或者替代,作一些初步的探讨: 说明: 函数Ixx为类xx的构造函数的实现。 原类的成员函数改为前缀为结构体名+‘_’的函数。...而函数前如果有virture,inline等修饰符也要去掉,如函数void funca(int a);改为void (*funca)(struct B *p,int a);大家可以看到函数指针的原型里加了一个指针...之所以所有的struct都用指针U是基于如下情况: 如果将子类指针赋给基类指针,基类指针在释放的时候不必考虑调用哪个函数名的析构函数,只需调用成员函数U即可。...除了将基类的构造函数名改为子类构造函数名外,不可以将基类定义的部分作其他改动。并在构造函数里调用基类的构造函数,然后如果子类覆盖了基类的函数,则要把该函数指针重定向到子类函数。...多继承也是可以改的,将多个基类的成员全部拷到子类里,遇到重复的成员名,则在前面加上前缀来区别,当然这个指的是基类之间有相同的,如果是派生类和基类之间有重名的,则会覆盖基类。
从输出可以看出: 派生类对象的构造次序: 先调用基类对象成员的构造函数,接着是基类的构造函数,然后是派生类的对象成员的构造函数,最后是派生类自身的构造函数。...初始化列表参数多个且其中有调用基类构造函数时,先执行基类构造函数(从最远的开始,如果多重继承则按继承的顺序);其他对象成员若不止一个,则按定义的顺序构造,与初始化列表顺序无关。...四、派生类到基类的转换 当派生类以public方式继承基类时,编译器可自动执行的转换(向上转型 upcasting 安全转换) 派生类对象指针自动转化为基类对象指针 派生类对象引用自动转化为基类对象引用...将派生类对象看成基类对象 //pm = &e1; // 基类对象指针无法转化为派生类对象指针。...无法将基类对象看成是派生类对象 e1 = m1; // 派生类对象可以转化为基类对象。将派生类对象看成基类对象 // 会产生对象切割(派生类特有成员消失)。
派生类对象可以直接赋值给基类对象/基类指针/基类引用,基类的对象可直接得到派生类对象中基类成员的那一部分,而指针或引用是直接指向或者引用到派生类对象中基类成员那一部分。 2....拷贝构造函数与构造不同,必须在派生类的拷贝构造的初始化列表处显示调用基类的拷贝构造,完成基类成员的复制。在传参时有人可能会有疑问,调用基类的拷贝构造该如何将子类中基类成员提取出来呢?...虚拟继承后,BC派生类中不再存储虚基类成员,改为存储虚基表指针,虚基表指针指向的一张表叫做虚基表,这个表中存储了派生类成员到虚基类成员的地址偏移量,通过偏移量就可以找到虚基类成员的内存地址。...而这个内存地址只有一份,所以这就解决了数据冗余的问题,因为内存中不再像原来一样,派生类中分别存储虚基类成员导致数据冗余,而是仅仅只存一份虚基类成员,派生类改为存储虚基类指针。 4....腰部的派生类存虚基表指针干嘛呢还。 5.
领取专属 10元无门槛券
手把手带您无忧上云