虚基类构造函数调用顺序
含有虚基类的对象的构造顺序
含有虚基类的对象的构造顺序含有虚基类的对象的构造顺序在C++中,虚基类是指被继承的基类,通过虚拟继承可以避免派生类对基类的多次继承,从而消除了由多次继承所带来的二义性。
在具体实现中,含有虚基类的对象的构造顺序成为了一个比较复杂且需要重点关注的问题。
在本文中,我们将探讨含有虚基类的对象的构造顺序,并深入了解其中的相关概念和实现细节。
一、什么是虚基类1.1 虚基类的定义在进行类的多重继承时,如果某个类作为基类被多个派生类继承,就有可能出现同一个基类在派生类中存在多份拷贝的情况。
为了解决这一问题,C++引入了虚基类的概念。
通过在继承的基类前加上关键字virtual来表示虚基类,从而确保不论这个虚基类在继承体系中出现多少次,最终在派生类中只有一份拷贝。
1.2 虚基类的作用虚基类的引入避免了由多重继承带来的二义性,确保在派生类中只有一份共同的基类成员。
虚基类的存在使得类的继承关系更清晰,更符合逻辑。
二、含有虚基类的对象的构造顺序2.1 构造函数的调用顺序在含有虚基类的对象构造过程中,构造函数的调用顺序是需要特别注意的。
由于虚基类的特殊性,它的构造函数与普通的基类构造函数有所不同,因此构造函数的调用顺序也会在这种情况下发生变化。
2.2 构造函数的顺序在含有虚基类的对象构造过程中,构造函数的调用顺序如下:(1)最深层派生类的构造函数。
(2)虚基类构造函数。
(3)直接基类构造函数。
2.3 示例分析假设有如下的继承关系:```cppclass A {};class B : virtual public A {};class C : virtual public A {};class D : public B, public C {};```在这种情况下,对象D的构造顺序是:(1)D的构造函数。
(2)B的构造函数。
(3)A的构造函数。
(4)C的构造函数。
(5)A的构造函数。
三、个人观点和总结在C++中,虚基类的使用为多重继承带来了一种解决方案,避免了多次继承所带来的问题。
C++习题答案-4
第4章:继承与派生类[4_1]答:类的继承方式有public(公有继承)、protected(保护继承)和private(私有继承)3种,不同的继承方式导致原来具有不同访问属性的基类成员在派生类中的访问属性也有所不同。
(1)基类中的私有成员无论哪种继承方式,基类中的私有成员不允许派生类继承,即在派生类中是不可直接访问的。
(2)基类中的公有成员当类的继承方式为公有继承时,基类中的所有公有成员在派生类中仍以公有成员的身份出现;当类的继承方式为私有继承时,基类中的所有公有成员在派生类中都以私有成员的身份出现;当类的继承方式为保护继承时,基类中的所有公有成员在派生类中都是以保护成员的身份出现。
(3)基类中的保护成员当类的继承方式为公有继承时,基类中的所有保护成员在派生类中仍以保护成员的身份出现;当类的继承方式为私有继承时,基类中的所有保护成员在派生类中都是以私有成员的身份出现:当类的继承方式为保护继承时,基类中的所有保护成员在派生类中仍以保护成员的身份出现。
[4_2]答:派生类不能直接访问基类的私有成员,但是可以通过基类提供的公有成员函数间接地访问基类的私有成员。
[4_3]答:保护成员可以被派生类的成员函数访问,但是对于外界是隐藏起来的,外部函数不能访问它。
因此,为了便于派生类的访问,可以将基类私有成员中需要提供给派生类访问的成员定义为保护成员。
C++规定,派生类对于保护成员的继承与公有成员的继承很相似,也分为两种情况:若为公有派生,则基类中的保护成员在派生类中也为保护成员;若为私有派生,则基类中的保护成员在派生类中成为私有成员。
[4_4]答:通常情况下,当创建派生类对象时,首先执行基类的构造函数,随后再执行派生类的构造函数;当撤消派生类对象时,则先执行派生类的析构函数,随后再执行基类的析构函数。
[4_5]答:当基类的构造函数没有参数或没有显示定义构造函数时,派生类可以不向基类传递参数,甚至可以不定义构造函数。
C++构造函数初始化顺序详解
C++构造函数初始化顺序详解1.构造函数、析构函数与拷贝构造函数介绍构造函数1.构造函数不能有返回值2.缺省构造函数时,系统将⾃动调⽤该缺省构造函数初始化对象,缺省构造函数会将所有数据成员都初始化为零或空3.创建⼀个对象时,系统⾃动调⽤构造函数析构函数1.析构函数没有参数,也没有返回值。
不能重载,也就是说,⼀个类中只可能定义⼀个析构函数2.如果⼀个类中没有定义析构函数,系统也会⾃动⽣成⼀个默认的析构函数,为空函数,什么都不做3.调⽤条件:1.在函数体内定义的对象,当函数执⾏结束时,该对象所在类的析构函数会被⾃动调⽤;2.⽤new运算符动态构建的对象,在使⽤delete运算符释放它时。
拷贝构造函数拷贝构造函数实际上也是构造函数,具有⼀般构造函数的所有特性,其名字也与所属类名相同。
拷贝构造函数中只有⼀个参数,这个参数是对某个同类对象的引⽤。
它在三种情况下被调⽤:1.⽤类的⼀个已知的对象去初始化该类的另⼀个对象时;2.函数的形参是类的对象,调⽤函数进⾏形参和实参的结合时;3.函数的返回值是类的对象,函数执⾏完返回调⽤者。
【代码】复制代码代码如下:/*version: 1.0author: hellogiserdate: 2014/9/25*/#include "stdafx.h"#include <iostream>using namespace std;class point{private:int x, y;public:point(int xx = 0, int yy = 0){x = xx;y = yy;cout << "Constructor" << endl;}point(const point &p){x = p.x;y = p.y;cout << "Copy Constructor" << endl;}~point(){cout << "Destructor" << endl;}int get_x(){return x;}int get_y(){return y;}};void f(point p){// copy constructorcout << p.get_x() << " " << p.get_y() << endl;// destructor}point g(){point a(7, 33); //constructorreturn a; // copy constructor temp object}void test(){point a(15, 22); // constructorpoint b(a); //(1) copy constructorcout << b.get_x() << " " << b.get_y() << endl; // 15 22f(b);// (2) copy constructorb = g(); // (3) copy constructorcout << b.get_x() << " " << b.get_y() << endl; // 7 33}int main(){test();return 0;}/*ConstructorCopy Constructor15 22Copy Constructor15 22DestructorConstructorCopy ConstructorDestructorDestructor7 33DestructorDestructor*/2. 继承关系中构造函数执⾏顺序(1)任何虚拟基类(virtual)的构造函数按照它们被继承的顺序构造;(2)任何⾮虚拟基类(non-virtual)的构造函数按照它们被继承的顺序构造;(3)任何成员对象(data member)的构造函数按照它们声明的顺序调⽤;(4)类⾃⼰的构造函数(self)。
派生类构造对象时,构造函数执行顺序
派⽣类构造对象时,构造函数执⾏顺序先调⽤基类构造函数,再调⽤派⽣类构造函数1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18class Base{public:Base() { cout << "Base()" << endl;} };class Derived : public Base{public:Derived() { cout << "Derived()" << endl;} };int main(){Derived d;return 0;}输出:Base()Derived()先调⽤基类构造函数,再调⽤对象成员的构造函数,最后调⽤派⽣类构造函数.1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19class Base{public:Base() { cout << "Base()" << endl;} };class Derived : public Base{public:Base b1, b2;Derived() { cout << "Derived()" << endl;} };int main(){Derived d;return 0;}输出:Base()Base()Base()Derived()先调⽤基类构造函数,再调⽤对象成员的构造函数(对象声明顺序,不是继承顺序,更不是初始化成员列表顺序),最后调⽤派⽣类构造函数.5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25};class Base2{public:Base2() { cout << "Base2()" << endl;} };class Derived : public Base1, public Base2 {public:Base1b1;Base2b2;Derived() { cout << "Derived()" << endl;} };int main(){Derived d;return 0;}输出: Base1() Base2() Base1() Base2() Derived()1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25class Base1{public:Base1() { cout << "Base1()" << endl;} };class Base2{public:Base2() { cout << "Base2()" << endl;} };class Derived : public Base1, public Base2 {public:Base2b2;Base1b1;Derived() { cout << "Derived()" << endl;} };int main(){Derived d;return 0;}输出:Base1()Base2()Base2()Base1()Derived()先调⽤虚基类构造函数,再调⽤其他基类构造函数,然后调⽤对象成员构造,最后调⽤派⽣类构造函数.5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25};class Base2{public:Base2() { cout << "Base2()" << endl;}};class Derived : public Base1,virtual public Base2 {public:Base2b2;Base1b1;Derived() { cout << "Derived()" << endl;}};int main(){Derived d;return 0;}Base2()Base1()Base2()Base1()Derived()总结:先基类,再派⽣类;先虚基类,再其他基类;先对象成员,再派⽣类;顺序是声明顺序,⽽不是成员初始化列表顺序。
C++构造函数和析构函数的调用顺序
C++构造函数和析构函数的调⽤顺序1、构造函数的调⽤顺序基类构造函数、对象成员构造函数、派⽣类本⾝的构造函数2、析构函数的调⽤顺序派⽣类本⾝的析构函数、对象成员析构函数、基类析构函数(与构造顺序正好相反)3、特例局部对象,在退出程序块时析构静态对象,在定义所在⽂件结束时析构全局对象,在程序结束时析构继承对象,先析构派⽣类,再析构⽗类对象成员,先析构类对象,再析构对象成员4、例⼦#include <iostream>using namespace std;class Base1{public:Base1(void){cnt++;cout<<"Base1::constructor("<<cnt<<")"<<endl;}~Base1(void){cnt--;cout<<"Base1::deconstructor("<<cnt + 1<<")"<<endl;}private:static int cnt;};int Base1::cnt = 0;class Base2{public:Base2(int m){num = m; cout<<"Base2::constructor("<<num<<")"<<endl;}~Base2(void){cout<<"Base2::deconstructor("<<num<<")"<<endl;}private:int num;};class Example{public:Example(int n){num = n; cout<<"Example::constructor("<<num<<")"<<endl;}~Example(void){cout<<"Example::deconstructor("<<num<<")"<<endl;}private:int num;};class Derived:public Base1, public Base2{public:Derived(int m, int n):Base2(m),ex(n){cnt++;cout<<"Derived::constructor("<<cnt<<")"<<endl;}~Derived(void){cnt--;cout<<"Derived::deconstructor("<<cnt+1<<")"<<endl;}private:Example ex;static Example stex; //Example::constructor(1) //不能输出static int cnt;};int Derived::cnt = 0;Derived ge_a(1,2); // Base1::constructor(1)// Base2::constructor(1)// Example::constructor(2)// Derived::constructor(1)static Derived gs_b(3,4); // Base1::constructor(2)// Base2::constructor(3)// Example::constructor(4)// Derived::constructor(2)int main(void){cout << "---------start---------" << endl;Derived d(5,6); // Base1::constructor(3) // Base2::constructor(5)// Example::constructor(6)// Derived::constructor(3)Derived e(7,8); // Base1::constructor(4) // Base2::constructor(7)// Example::constructor(8)// Derived::constructor(4)cout << "----------end----------" << endl;//Derived e(7,8) 析构// Derived::deconstructor(4)// Example::deconstructor(8)// Base2::deconstructor(7)// Base1::deconstructor(4)//Derived d(5,6) 析构// Derived::deconstructor(3)// Example::deconstructor(6)// Base2::deconstructor(5)// Base1::deconstructor(3)return 0;}//static Derived gs_b(3,4) 析构// Derived::deconstructor(2)// Example::deconstructor(4)// Base2::deconstructor(3)// Base1::deconstructor(2)//Derived ge_a(1,2) 析构// Derived::deconstructor(1)// Example::deconstructor(2)// Base2::deconstructor(1)// Base1::deconstructor(1)//static Example stex 析构//Example::deconstructor(1) //不能输出#include <iostream>using namespace std;class A{public:A(){cout<<"A::constructor"<<endl;};~A(){cout<<"A::deconstructor"<<endl;}; };class B{public:B(){cout<<"B::constructor"<<endl;};~B(){cout<<"B::deconstructor"<<endl;}; };class C : public A{public:C(){cout<<"C::constructor"<<endl;};~C(){cout<<"C::deconstructor"<<endl;}; private:// static B b;B b;};class D : public C{public:D(){cout<<"D::constructor"<<endl;};~D(){cout<<"D::deconstructor"<<endl;}; };int main(void){C* pd = new D();delete pd;return 0;}/* Output----->B bA::constructorB::constructorC::constructorD::constructorC::deconstructorB::deconstructorA::deconstructor ----->static B b A::constructor C::constructor D::constructor C::deconstructor A::deconstructor */。
C++第一版答案
第一章:面向对象程序设计概述[1_1]什么是面向对象程序设计?面向对象程序设计是一种新型的程序设计范型。
这种范型的主要特征是:程序=对象+消息。
面向对象程序的基本元素是对象,面向对象程序的主要结构特点是:第一:程序一般由类的定义和类的使用两部分组成,在主程序中定义各对象并规定它们之间传递消息的规律。
第二:程序中的一切操作都是通过向对象发送消息来实现的,对象接受到消息后,启动有关方法完成相应的操作。
面向对象程序设计方法模拟人类习惯的解题方法,代表了计算机程序设计新颖的思维方式。
这种方法的提出是软件开发方法的一场革命,是目前解决软件开发面临困难的最有希望、最有前途的方法之一。
[1_2]什么是类?什么是对象?对象与类的关系是什么?在面向对象程序设计中,对象是描述其属性的数据以及对这些数据施加的一组操作封装在一起构成的统一体。
对象可以认为是:数据+操作在面向对象程序设计中,类就是具有相同的数据和相同的操作的一组对象的集合,也就是说,类是对具有相同数据结构和相同操作的一类对象的描述。
类和对象之间的关系是抽象和具体的关系。
类是多个对象进行综合抽象的结果,一个对象是类的一个实例。
在面向对象程序设计中,总是先声明类,再由类生成对象。
类是建立对象的“摸板”,按照这个摸板所建立的一个个具体的对象,就是类的实际例子,通常称为实例。
[1_3]现实世界中的对象有哪些特征?请举例说明。
对象是现实世界中的一个实体,其具有以下一些特征:(1)每一个对象必须有一个名字以区别于其他对象。
(2)需要用属性来描述它的某些特性。
(3)有一组操作,每一个操作决定了对象的一种行为。
(4 )对象的操作可以分为两类:一类是自身所承受的操作,一类是施加于其他对象的操作。
例如:雇员刘名是一个对象对象名:刘名对象的属性:年龄:36 生日:1966.10.1 工资:2000 部门:人事部对象的操作:吃饭开车[1_4]什么是消息?消息具有什么性质?在面向对象程序设计中,一个对象向另一个对象发出的请求被称为“消息”。
继承与派生类答案
继承与派生类知识要点1.掌握继承和派生的定义,派生类的定义方法。
(1)掌握继承的两种类型:单继承和多继承。
(2)掌握private,public,protected三种继承方式的特点。
继承方式决定了基类中的成员在派生类中的属性。
三种继承方式的共同点:基类的private成员在派生类中不可见。
区别:对于私有继承,基类的public、protected成员在派生类中作为private成员;对于公有继承,基类的public、protected成员在派生类中访问属性不变;对于保护继承,基类的public、protected成员在派生类中作为protected成员。
(3)掌握派生类中的构造函数和析构函数的使用。
基类的构造函数和析构函数不能继承,所以必要时在派生类中定义自己的构造函数和析构函数。
派生列的构造函数完成基类中新增数据成员和基类数据成员的初始化,基类数据成员的初始化通过基类构造函数来实现。
(4)掌握派生类的同名覆盖规则。
(5)掌握赋值兼容规则。
基类对象可以使用公有派生类对象来代替,包括:派生类对象可以赋值给基类对象;派生类对象可以初始化基类对象的引用;基类类型指针可以指向派生类对象。
2.掌握多重继承的概念、定义方法、多重继承派生类构造函数的执行顺序。
派生类构造函数的执行顺序是先执行所有基类的构造函数(顺序按照定义派生类时指定的各基类顺序),在执行对象成员所在类的构造函数(顺序按照他们在类中的声明顺序),最后执行派生类构造函数体中的内容。
3.掌握虚基类的概念和定义方法。
在多重继承中,如果多条继承路径上有一个公共的基类,则在这些路径的汇合点上的派生类会产生来自不同路径的公共基类的多个拷贝,如果用virtual把公共基类定义成虚基类,则只会保留公共基类的一个拷贝。
典型例题分析与解答例题1:下列对派生类的描述中,()是错误的。
A.一个派生类可以作为另一个派生类的基类B.派生类至少有一个基类C.派生类的成员除了它自己的成员外,还包含了它的基类成员D.派生类中继承的基类成员的访问权限到派生类保持不变答案:D分析:一个派生类可以作为另一个派生类的基类。
面向对象程序设计课后习题答案
面向对象程序设计课后习题答案第一章:面向对象程序设计概述[1_1]什么是面向对象程序设计?面向对象程序设计是一种新型的程序设计范型。
这种范型的主要特征是:程序=对象+消息。
面向对象程序的基本元素是对象,面向对象程序的主要结构特点是:第一:程序一般类的定义和类的使用两部分组成,在主程序中定义各对象并规定它们之间传递消息的规律。
第二:程序中的一切操作都是通过向对象发送消息来实现的,对象接受到消息后,启动有关方法完成相应的操作。
面向对象程序设计方法模拟人类习惯的解题方法,代表了计算机程序设计新颖的思维方式。
这种方法的提出是软件开发方法的一场革命,是目前解决软件开发面临困难的最有希望、最有前途的方法之一。
[1_2]什么是类?什么是对象?对象与类的关系是什么?在面向对象程序设计中,对象是描述其属性的数据以及对这些数据施加的一组操作封装在一起构成的统一体。
对象可以认为是:数据+操作在面向对象程序设计中,类就是具有相同的数据和相同的操作的一组对象的集合,也就是说,类是对具有相同数据结构和相同操作的一类对象的描述。
类和对象之间的关系是抽象和具体的关系。
类是多个对象进行综合抽象的结果,一个对象是类的一个实例。
在面向对象程序设计中,总是先声明类,再类生成对象。
类是建立对象的“摸板”,按照这个摸板所建立的一个个具体的对象,就是类的实际例子,通常称为实例。
[1_3]现实世界中的对象有哪些特征?请举例说明。
对象是现实世界中的一个实体,其具有以下一些特征:每一个对象必须有一个名字以区别于其他对象。
需要用属性来描述它的某些特性。
有一组操作,每一个操作决定了对象的一种行为。
对象的操作可以分为两类:一类是自身所承受的操作,一类是施加于其他对象的操作。
例如:雇员刘名是一个对象对象名:刘名对象的属性:年龄:36 生日:工资:2000 部门:人事部对象的操作:吃饭开车[1_4]什么是消息?消息具有什么性质?在面向对象程序设计中,一个对象向另一个对象发出的请求被称为“消息”。
虚基类的概念和定义
虚基类的概念和定义1. 概念定义虚基类(Virtual Base Class)是C++中的一个重要概念,用于解决多继承中的菱形继承问题。
菱形继承指的是一个派生类同时继承自两个不同的基类,而这两个基类又共同继承自一个公共的基类。
如果不加以处理,会导致派生类中存在两份公共基类的成员,从而产生二义性。
虚基类通过在派生类中使用关键字virtual来声明,它具有以下特点: - 被声明为虚基类的成员不会被派生类直接继承; - 虚基类的构造函数由最底层派生类负责调用; - 虚基类只会在内存中存在一份副本。
2. 重要性虚基类的引入解决了多继承中菱形继承问题,避免了二义性的发生。
它在面向对象编程中发挥着重要作用: - 避免冗余数据:虚基类可以确保在派生层次结构中只存在一份公共数据,避免了数据冗余和浪费。
- 解决二义性:通过将公共数据放在虚基类中,派生类只需要从虚基类继承一份公共数据,避免了二义性的发生。
- 灵活组合:多继承可以实现更灵活的组合关系,虚基类使得多继承更加可控和可靠。
3. 应用场景虚基类主要应用于多继承的场景中,特别是存在菱形继承的情况。
下面通过一个示例来说明虚基类的应用:#include<iostream>using namespace std;class Animal {public:int age;};class Mammal : virtual public Animal {public:void eat() {cout << "Mammal is eating." << endl;}};class Bird : virtual public Animal {public:void fly() {cout << "Bird is flying." << endl;}};class Platypus : public Mammal, public Bird {public:void swim() {cout << "Platypus is swimming." << endl;}};int main() {Platypus p;p.age = 5; // 访问公共数据成员agep.eat(); // 调用Mammal类的成员函数eatp.fly(); // 调用Bird类的成员函数flyp.swim(); // 调用Platypus类自身的成员函数swim}在上述示例中,Animal类是一个虚基类,它被Mammal和Bird两个派生类虚继承。
C++中构造函数的执行顺序小结
C++中构造函数的执行顺序
1首先,如果类中有静态成员,则先执行静态成员的构造函数。
注意,如果静态成员只是在类定义中声明了,而没有实现,是不用构造的。
必须初始化后才执行其构造函数。
2接下来,如果该类有直接或间接的虚基类,则先执行虚基类的构造函数。
3最后,如果该类有其他基类,则按照它们在继承声明列表中出现的次序,分别执行它们的构造函数,但构造过程中,不再执行它们的虚基类的构造函数。
补充:
1虚基类
虚基类的声明是在派生类的定义过程中进行的,其语法形式为:
class派生类名:virtual继承方式基类名
上述语句声明基类为派生类的虚基类。
声明了虚基类后,虚基类的成员在进一步的派生过程中和派生类一起维护同一个内存数据副本。
2一般虚函数
声明语法:virtual函数类型函数名();
3纯虚函数和抽象类
纯虚函数的声明格式:virtual函数类型函数名(参数表)=0;
抽象类是带有纯虚函数的类。
抽象类不能实例化。
抽象类派生出新的类之后,如果派生类给出所有纯虚函数的函数实现,这个派生类就可以定义自己的对象,而不再是抽象类;反之,如果派生类没有给出全部纯虚函数的实现,这时的派生类仍然是一个抽象类。
构造函数和析构函数的调用顺序
{
public:
Base(){ std::cout<<"Base::Base()"<<std::endl; }
virtual ~Base(){ std::cout<<"Base::~Base()"<<std::endl; }
};
class Derive:public Base
Derive(){ std::cout<<"Derive::Derive()"<<std::endl; }
~Derive(){ std::cout<<"Derive::~Derive()"<<std::endl; }
};
int _tmain(int argc, _TCHAR* argv[])
{
Base* pBase = new Derive();
//这种base classed的设计目的是为了用来"通过base class接口处理derived class对象"
delete pBase;
return 0;
}
输出的结果是:
Base::Base()
Derive::Derive()
Base::~Base()
从上面的输出结果可以看出,析构函数的调用结果是存在问题的,也就是说析构函数只作了局部销毁工作,这可能形成资源泄漏败坏数据结构等问题;那么解决此问题的方法很简单,给base class一个virtual析构函数;
3.派生类构造函数。
析构函数
析构函数的调用顺序与构造函数的调用顺序正好相反,将上面3个点反过来用就可以了,首先调用派生类的析构函数;其次再调用成员类对象的析构函数;最后调用基类的析构函数。
虚基类构造函数调用顺序
class Base1 {public:Base1(void) {cout << "class Base1" << endl;}};class Base2 {public:Base2(void) {cout << "class Base2" << endl;}};class Level1:virtual public Base2,public Base1 { public:Level1(void) {cout << "class Level1" << endl;}};class Level2:public Base2,virtual public Base1 { public:Level2(void) {cout << "class Level2" << endl;}};class Leaf:public Level1,virtual public Level2 {public:Leaf(void) {cout << "class Leaf" << endl;}};int main(void) {Leaf obj;return 0;}不看下面的分析,大家觉得输出结果应该是怎么样的?如果没有虚拟继承,也许能很快说出答案。
现在来分析一下在多继承和虚拟继承的情况下,构造函数的调用顺序是怎么样的。
编译器按照直接基类在声明中的顺序,来检查虚拟基类的出现情况。
在我们的例子中,Level1首先被检查,然后是Level2。
每个继承子树按照深度优先的顺序被检查。
即,查找从树根类开始,然后向下移动。
如对子树Level1而言,先检查Base2,然后是Base1,再到Level1。
但是在虚拟继承中,基类构造函数的查找顺序只是为了知道虚拟继承的情况而已,基类构造函数的调用顺序和查找顺序是不一样的,那应该遵循什么样的一个原则呢?遵循两个原则,而且按顺序优先满足:1 先调用完所以基类,再调用子类;2 先调用虚拟基类,再调用非虚拟基类。
虚基类构造函数
虚基类构造函数虚基类是 C++ 中的一种派生技术,可以让派生类继承多个基类的成员,但是每个基类只会在派生类中出现一次。
虚基类构造函数是用于构造虚基类对象的函数,它在虚基类对象的构造过程中被调用。
本文将深入探讨虚基类构造函数的相关知识。
一、虚基类的定义和使用虚基类是指在多层继承结构中,被声明为虚基类的基类。
在使用虚基类的过程中,我们可以使用虚基类指针来访问虚基类成员,从而避免多次继承中出现的二义性问题。
下面是一个使用虚基类的示例:```c++class Base {protected:int data;};class Derive1 : virtual public Base {public:void set(int x) { data = x; }};class Derive2 : virtual public Base {public:int get() { return data; }};class MI : public Derive1, public Derive2 {};MI obj;obj.set(5);std::cout << obj.get(); // 输出5}```在这个示例中,虚基类 Base 中的数据成员被 Derive1 和 Derive2 继承。
在派生类MI 中使用虚基类,可以使得 MI 类只继承一份 Base 类对象,从而避免 data 成员的二义性问题。
二、虚基类构造函数的调用虚基类构造函数的调用是在虚基类对象的构造过程中完成的。
虚基类构造函数的调用顺序是根据派生类构造函数的执行顺序而确定的。
对于下面的代码:```c++class Base {public:Base() { std::cout << "Base constructed\n"; }};class Derive1 : virtual public Base {public:Derive1() { std::cout << "Derive1 constructed\n"; }};class Derive2 : virtual public Base {public:Derive2() { std::cout << "Derive2 constructed\n"; }};class MI : public Derive1, public Derive2 {public:MI() { std::cout << "MI constructed\n"; }};MI obj;}```其输出结果为:```Base constructedDerive2 constructedDerive1 constructedMI constructed```这是因为在构造 MI 对象时,先调用虚基类 Base 的构造函数,然后调用 Derive2 和 Derive1 的构造函数,最后调用 MI 的构造函数。
构造函数的执行顺序
构造函数的执⾏顺序⼀、C#中构造函数有⼏种。
1.静态构造函数2.默认构造函数3.带参数的构造函数⼆、顺序如何执⾏呢?先看代码:class MyClass{static MyClass(){Console.WriteLine("静态构造函数被调⽤。
");}private static Component staticField = new Component("静态字段被实例化。
");private Component instanceField = new Component("实例成员字段被实例化。
");public MyClass(){Console.WriteLine("对象构造函数被调⽤。
");}}//此类型⽤于作MyClass类的成员//此类型在实例化的时候可以再控制台输出⾃定义信息,以给出相关提⽰class Component{public Component(String info){Console.WriteLine(info);}}class Program{static void Main(string[] args){MyClass instance = new MyClass();Console.Read();}} 运⾏结果:1.静态字段被实例化2.静态构造函数被调⽤3.实例成员字段被实例化4.对象构造函数被调⽤再来看看带基类的构造函数是如何运⾏class Base{static Base(){Console.WriteLine("基类静态构造函数被调⽤。
");}private static Component baseStaticField = new Component("基类静态字段被实例化。
");private Component baseInstanceField = new Component("基类实例成员字段被实例化。
虚基类_构造函数调用
表示派生关系
B::B(int i)
表示参数 传递关系
V::V(int i,int j):B(i)
`
X::X (int i,int j):V(i,j)
Y::Y(int i,int j):V(i,j)
W::W(int a,int b,int c,int d,int e,int f) : X(a, b), Y(c, d), V(e,f )
写出构造函数的调用顺序: class A {…}; class B:virtual public A {…}; cห้องสมุดไป่ตู้ass C:virtual public A {…}; class M {…}; class P {…}; class D:public B, public C { … 有虚基类时构造函数调用顺序: M member; A B C M P D P part; … 无虚基类时构造函数调用顺序: }; A B A C M P D void main( ) { 注意: D dd; 如间接基类A是虚基类,则D越级传递参数给A。 … 如间接基类A不是虚基类,则D不能越级向A传递参 } 数。
构造函数调用顺序: 五层派生中的虚基类: B -> V -> X -> Y -> W -> Z #include <iostream.h> class B {public: B(int i) { … } … }; class V: public B {public: V(int i, int j):B(i) {… } …}; class X: virtual public V {public: X(int i, int j):V(i, j) { … }…}; class Y: virtual public V {public: Y(int i, int j):V(i, j) { … }…}; class W : public X, public Y {public: W(int a, int b, int c, int d, int e, int f): X(a, b), Y(c, d), V(e, f) {…}…}; class Z : public W {public: Z(int a, int b, int c, int d, int e, int f, int g): W(b, c, d, e, f, g), V(f, g) { … }…}; void main() { Z obj(1, 2, 3, 4, 5, 6, 7); …}
C++构造函数和析构函数的调用顺序、虚析构函数的作用
C++构造函数和析构函数的调⽤顺序、虚析构函数的作⽤构造函数和析构函数的调⽤顺序构造函数的调⽤顺序:当建⽴⼀个对象时,⾸先调⽤基类的构造函数,然后调⽤下⼀个派⽣类的构造函数,依次类推,直⾄到达最底层的⽬标派⽣类的构造函数为⽌。
析构函数的调⽤书序:当删除⼀个对象时,⾸先调⽤该派⽣类的析构函数,然后调⽤上⼀层基类的析构函数,依次类推,直到到达最顶层的基类的析构函数为⽌。
简单的说,构造函数是“⾃上向下”调⽤,析构函数是“⾃下⽽上”调⽤。
演⽰代码如下:#include<iostream>using namespace std;class Base{public:Base(){cout<<"创建Base基类。
"<<endl;}~Base() {cout<<"删除Base基类。
"<<endl;}};class Child : public Base{public:Child() {cout<<"创建Child派⽣类。
"<<endl;}~Child() {cout<<"删除Child派⽣类。
"<<endl;}};int main(){cout<<"*********构造函数调⽤顺序⽰例***********"<<endl;Child *C1 = new Child;cout<<"*********析构函数调⽤顺序⽰例***********"<<endl;delete C1;}运⾏结果如下图:虚析构函数的作⽤通过基类的指针来删除派⽣类的对象时,基类的析构函数应该是虚的。
原因:在公有继承中,基类对派⽣类及其对象的操作,只能影响到那些从基类继承下来的成员。
虚基类构造函数
虚基类构造函数在C++中,虚基类是一种特殊的类,它被用来解决多重继承中的问题。
在多重继承中,如果一个类同时继承了多个基类,而这些基类又有共同的基类,那么就会出现多个相同的基类实例,这就会导致数据冗余和不一致性的问题。
为了解决这个问题,C++引入了虚基类的概念。
虚基类是一种被声明为虚拟的基类,它的作用是让派生类只包含一个共同的基类实例。
虚基类的构造函数是一个非常重要的概念,它用来初始化虚基类的成员变量。
在本文中,我们将详细介绍虚基类构造函数的概念和用法。
虚基类构造函数的定义虚基类构造函数是一个特殊的构造函数,它用来初始化虚基类的成员变量。
虚基类构造函数的定义格式如下:```class Base {public:Base(int x) : m_x(x) {}private:int m_x;};class Derived : virtual public Base {public:Derived(int x, int y) : Base(x), m_y(y) {}private:int m_y;};```在上面的代码中,Base是一个虚基类,Derived是一个派生类。
在Derived的构造函数中,我们调用了Base的构造函数来初始化Base的成员变量m_x。
由于Base是一个虚基类,所以我们需要在Derived的构造函数中使用virtual关键字来声明Base是一个虚基类。
虚基类构造函数的调用顺序在多重继承中,如果一个类同时继承了多个基类,那么这些基类的构造函数的调用顺序是非常重要的。
在虚基类中,虚基类构造函数的调用顺序是比较特殊的。
虚基类构造函数的调用顺序如下:1. 调用最远的虚基类的构造函数。
2. 调用直接基类的构造函数。
3. 调用派生类的构造函数。
在上面的代码中,Base是一个虚基类,Derived是一个派生类。
在Derived的构造函数中,虚基类构造函数的调用顺序是先调用Base 的构造函数,然后再调用Derived的构造函数。
继承下构造函数的执行顺序
继承下构造函数的执⾏顺序这⾥先给出结论,在贴出代码与执⾏结果~⼀个派⽣类构造函数的执⾏顺序如下:第⼀步执⾏:虚拟基类的构造函数(多个虚拟基类则按照继承的顺序执⾏构造函数)。
第⼆步执⾏:基类的构造函数(多个普通基类也按照继承的顺序执⾏构造函数)。
第三步执⾏:类类型的成员对象的构造函数(按照初始化顺序)。
第四部执⾏:派⽣类⾃⼰的构造函数。
如果⼀个派⽣类不仅继承于⼀个基类,⽽且还有这个基类的成员对象,那么会进⾏两次构造函数的执⾏(⼀个⽤于初始化派⽣类中基类部分的内部成员,另⼀个是初始化派⽣类的基类类型成员变量的内部成员),详细看派⽣类Son2的执⾏结果。
你下⾯声明了A,B,C,D,Object1,Object2留个基类以及Son1,Son2,Son3,Son4,Son5(Son5是⼀个错误的例⼦,编译不能通过)每个基类都有两个构造函数,默认的构造函数不接受参数,输出的Default+字符串带参数的输出的⾃⼰类的特有信息。
(为了⽅便观看,我在后⾯会再贴⼀下结论)参考Son1:可以验证上述派⽣类构造函数的执⾏顺序;参考Son2:可以验证构造函数是严格照上⾯所说的顺序执⾏,与初始化的顺序⽆关。
同时,如果不显⽰的执⾏基类构造函数的初始化,就会按照顺序调⽤默认的构造函数。
参考Son3:可以说明继承下执⾏的构造函数与类类型的成员变量的构造函数是占⽤两个不同的内存地址空间,⼆者不会相互影响。
参考Son4:可以说明,⽆论是否显⽰的初始化类类型的成员变量,都会按照成员变量在类中的声明顺序执⾏构造函数。
参考Son5:这个解决了我之前的疑问,如果在派⽣类的构造函数中初始化类类型的成员对象会怎么样,发现这样是不可取的,因为在类的声明中是不可以实际分配内存的,但是可以声明。
(关于Son5的理解需要进⼀步阐明:这⾥可能涉及到了C++内存分配,拷贝赋值的原理,如果不是很懂的话这⾥我们暂且按我说的⽅式去理解。
)classA{public:A(intnum){Show();}A(){cout<<"Defaultaaa"<<endl;}voidShow(){cout<<"aaa"<<endl;}};classB{public:B(intnum){Show();}B(){cout<<"Defaultbbb"<<endl;}voidShow(){cout<<"bbb"<<endl;}};classC{public:C(intnum){Show();}C(){cout<<"Defaultccc"<<endl;}voidShow(){cout<<"ccc"<<endl;}};classD{public:D(intnum){Show();}D(){cout<<"Defaultddd"<<endl;}voidShow(){cout<<"ddd"<<endl;}};classObject1{public:Object1(intnum){Show();}Object1(){cout<<"DefaultObject1"<<endl;}voidShow(){cout<<"Object1"<<endl;}};classObject2{public:Object2(intnum){Show();}Object2(){cout<<"DefaultObject2"<<endl;}voidShow(){cout<<"Object2"<<endl;}};classSon1:publicA,virtualpublicB,publicC,virtualpublicD{public:Son1():A(1),B(1),C(1),D(1),ob1(1),ob2(1){cout<<"son1"<<endl;}Object1ob1;Object2ob2;};classSon2:publicA,virtualpublicB,publicC,virtualpublicD{public:Son2():C(1),A(1),ob1(1),ob2(1){ //结果仍然是先执⾏A的构造函数,其次是C的,证明与初始cout<<"son2"<<endl; //化的顺序⽆关}Object1ob1;Object2ob2;};classSon3:publicA,virtualpublicB,publicC,virtualpublicD,virtualpublicObject1,publicObject2{public:Son3():ob1(1),ob2(1){cout<<"son3"<<endl;}Object1ob1;Object2ob2;};classSon4:publicA,virtualpublicB,publicC,virtualpublicD,virtualpublicObject1,publicObject2{public:Son4():ob2(1){ //注意,如果Object1没有默认构造函数,这⾥将⽆法编译通过cout<<"son4"<<endl;}Object1ob1;Object2ob2;};//class Son5:publicA,virtual public B,publicC,virtual public D//{//public:// Son5():A(1),B(1),C(1),D(1){// cout<<"son5"<<endl;// ob2=ob1; //这⾥编译不通过,没有与这些操作数匹配的”=”运算符(⼆者类型不同,需要重新定义‘=’) // ob1(1);// ob2(2); //这⾥编译不通过,在没有适当的operate()的情况下调⽤类类型对象// }// Object1 ob1(1); // 这⾥编译不通过,因为类的成员声明不需要分配内存,这样写就相当于执⾏//构造函数并分配内存了// Object2 ob2;//};int_tmain(intargc, _TCHAR* argv[]){cout<<"------------SON1------------"<<endl;Son1son1;cout<<"------------SON2------------"<<endl;Son2son2;cout<<"------------SON3------------"<<endl;Son3son3;cout<<"------------SON4------------"<<endl;Son4 son4;Object1obj;//son4.ob1(1); //这句话是错误的编译不通过,在没有适当的operate()的情况下调⽤类类型对象son4.ob1=obj;system("pause"); //这句只是为了让cmd停留显⽰,以免闪退(VS控制台程序需要)return 0;}以上代码是在VS2012ConsoleApplication控制台下编译测试的,结果如下:再贴⼀遍:参考Son1:可以验证⼀开始介绍的派⽣类构造函数的执⾏顺序;参考Son2:可以验证构造函数是严格照上⾯所说的顺序执⾏,与初始化的顺序⽆关(尽管表⾯上C⽐A初始化的要早)。
虚继承构造函数调用顺序
虚继承构造函数调用顺序虚继承是C++中一种特殊的继承方式,它的主要作用是解决多继承时出现的“钻石继承”问题。
但是,由于虚继承会影响到构造函数的调用顺序,因此在使用虚继承时,需要特别注意构造函数的调用顺序。
在普通的继承中,派生类的构造函数会隐含地调用基类的构造函数,而在虚继承中,由于存在虚基类,会出现多次调用基类构造函数的情况。
具体来说,当一个类通过虚继承继承自一个虚基类时,编译器会为虚基类分配一个虚基类指针,这个指针指向了存放虚基类对象的内存位置。
当派生类构造函数被调用时,编译器会先调用虚基类的构造函数,然后再调用普通基类的构造函数,最后才调用派生类的构造函数。
例如,考虑以下代码:```class A {public:A() { cout << 'A's constructor' << endl; }};class B : virtual public A {public:B() { cout << 'B's constructor' << endl; }};class C : virtual public A {public:C() { cout << 'C's constructor' << endl; }};class D : public B, public C {public:D() { cout << 'D's constructor' << endl; }};```在这个例子中,类A是虚基类,类B和类C都通过虚继承继承自A,类D又同时继承自B和C。
当我们构造一个D对象时,它的构造函数会先调用A的构造函数,然后再调用B和C的构造函数,最后才调用D的构造函数。
因此,这个程序的输出结果应该是:```A's constructorB's constructorC's constructorD's constructor```需要注意的是,虚基类的构造函数只会被调用一次,即使派生类中有多个虚继承路径指向同一个虚基类。
C++面向对象程序设计教程—-陈维兴,林小茶课后习题答案
C++面向对象程序设计教程课后题答案什么是面向对象程序设计?面向对象程序设计是一种新的程序设计范型.这种范型的主要特征是:程序=对象+消息面向对象程序的基本元素是对象。
主要结构特点是:第一,程序一般由类的定义和类的使用两部分组成;第二,程序中的一切操作都是通过向对象发送消息来实现的。
什么是对象?什么是类?对象与类之间的关系是什么?对象是描述其属性的数据以及对这些数据施加的一组操作封装在一起构成的统一体。
类就是具有相同的数据和相同的操作的一组对象的集合,也就是说,类是对具有相同数据结构和相同操作的一类对象的描述。
类和对象之间的关系是抽象和具体的关系。
类是多个对象进行综合抽象的结果,一个对象是类的一个实例。
现实世界中的对象有哪些特征?请举例说明。
现实世界中的对象具有以下特征:1) 每一个对象必须有一个名字以区别于其他对象;2) 用属性来描述对象的某些特征;3) 有一组操作,每组操作决定对象的一种行为;4) 对象的行为可以分为两类:一类是作用于自身的行为,另一类是作用于其他对象的行为。
例如一个教师是一个对象。
每个教师对象有自己的名字来和别的教师区别。
教师具有编号,姓名,年龄,职称,专业等属性。
教师拥有走路,吃饭,授课等行为操作。
走路,吃饭是作用于自身的行为,授课是作用于其他对象的行为。
什么是消息?消息具有什么性质?一个对象向另一个对象发出的请求成为“消息”。
消息具有以下3个性质:1) 同一个对象可以接收不同形式的多个消息,做出不同的相应;2) 相同形式的消息可以传递给不同的对象,所做出的响应可以是不同的;3) 对消息的响应并不是必须的,对象可以响应消息,也可以不响应。
什么是抽象和封装?请举例说明。
抽象是将有关事物的共性归纳、集中的过程。
例如:把所有具有大学生学籍的人归为一类,成为“大学生”,这就是一个抽象。
封装是指把数据和实现操作的代码集中起来放在对象内部,并尽可能隐藏对象的内部细节。
例如:每一台洗衣机都有出厂日期、机器编号等属性,也有启动、暂停、选择等操作。
- 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
- 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
- 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。
class Base1 {
public:
Base1(void) {
cout << "class Base1" << endl;
}
};
class Base2 {
public:
Base2(void) {
cout << "class Base2" << endl;
}
};
class Level1:virtual public Base2,public Base1 { public:
Level1(void) {
cout << "class Level1" << endl;
}
};
class Level2:public Base2,virtual public Base1 { public:
Level2(void) {
cout << "class Level2" << endl;
}
};
class Leaf:public Level1,virtual public Level2 {
public:
Leaf(void) {
cout << "class Leaf" << endl;
}
};
int main(void) {
Leaf obj;
return 0;
}
不看下面的分析,大家觉得输出结果应该是怎么样的?如果没有虚拟继承,也许能很快说出答案。
现在来分析一下在多继承和虚拟继承的情况下,构造函数的调用顺序是怎么样的。
编译器按照直接基类在声明中的顺序,来检查虚拟基类的出现情况。
在我们的例子中,Level1首先被检查,然后是Level2。
每个继承子
树按照深度优先的顺序被检查。
即,查找从树根类开始,然后向下移动。
如对子树Level1而言,先检查Base2,然后是Base1,再到Level1。
但是在虚拟继承中,基类构造函数的查找顺序只是为了知道虚拟继承的情况而已,基类构造函数的调用顺序和查找顺序是不一样的,那应该遵循什么样的一个原则呢?
遵循两个原则,而且按顺序优先满足:1 先调用完所以基类,再调用子类;2 先调用虚拟基类,再调用非虚拟基类。
一旦调用了虚拟基类的构造函数,则非虚拟基类构造函数就按照声明的顺序被调用。
所以针对我们这个例子,因为声明类Leaf的顺序是先Level1后Level2,所以先看看Level1这棵子树吧。
由于Level1虚拟继承Base2,非虚拟继承Base1,所以应该先调用Base2,但是这之后不能接着调用Level1这棵子树的Base1,因为其他子树还有虚拟继承。
现在来看看Level2这棵子树吧,由于Level2虚拟继承Base1,非虚拟继承Base2,所以先调用Base1,后Base2。
既然Level2的两个基类都调用了,并且Level2也是一个虚拟基类,所以现在应该调用Level2的构造函数了。
这样,Level2这棵子树的构造函数都调用完了,又回到Level1这棵
子树了。
接着刚才,由于Level1的Base2已经调用了,接着应该调用Base1了,之后是Level1。
当Leaf的所有直接和间接的基类都调用之后,最后是Leaf构造函数了。
经过以上分析,大家应该知道构造函数的调用顺序了吧,所以该程序的输出应该是
Base2
Base1
Base2
Level2
Base1
Level1
Leaf。