C++学习笔记(10)——虚基类的作用

合集下载

虚函数,虚基类

虚函数,虚基类

虚函数、虚基类、抽象类一:虚基类解决二义性,防止双份拷贝间接基类。

(否则得用作用域分辨符来区分进行的多个拷贝)将共同基类设置为虚函数,这是从不同的路径继承过来的同名数据成员在内存中就只有一个拷贝,同一个函数名虚基类的声明是在派生类的定义过程中进行的,语法形式为:class 派生类名:virtual 继承方式基类名虚基类及派生类的构造函数,例如:#includeusing namespace std;class B0{public:B0(int i){nv = i;}private:int nv;};class B1:virtual public B0{public:B1(int i):B0(i){};};class B2:virtual public B0{public:B2(int i):B0(i){};};class D1:public B1,public B2{public:D1(int i):B1(i),B2(i),B0(i){};};二:虚函数1)虚函数主要是实现运行时多态。

虚函数的声明只能出现在类定义中的函数原型声明中,2)而不能在成员函数2)只有使用指针或者引用的方式调用虚函数时,虚函数才能起到运行时多态的作用。

3)在程序中如果派生类没有显式的给出虚函数的声明,系统会根据以下规则来判断派生类中的函数成员是否是*是否与基类的虚函数有相同的名称*是否与基类的虚函数有相同的参数个数及相同的对应参数类型。

*相同的返回值4)只有虚函数是动态绑定的,如果派生类需要修改基类的行为,就应该将基类中相应的函数声明为虚函数。

5)再重写继承来的虚函数时,如果函数有默认的形参值,千万不要重新定义不同的值。

原因是:虽然虚函数是定的。

6)在c++中不能声明虚构造函数,但可以声明虚析构函数,如果一个类的析构函数声明为虚函数,那么他派生而函数。

保证使用基类类型指针就能够调用适当的析构函数针对不同的对象进行清理工作。

含有虚基类的对象的构造顺序

含有虚基类的对象的构造顺序

含有虚基类的对象的构造顺序含有虚基类的对象的构造顺序在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++中,虚基类是一种特殊的类,它被用来解决多重继承中的问题。

在多重继承中,如果一个类同时继承了多个基类,而这些基类又有共同的基类,那么就会出现多个相同的基类实例,这就会导致数据冗余和不一致性的问题。

为了解决这个问题,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的构造函数。

C++ 虚基类

C++ 虚基类

C++ 虚基类9/3/2001 8:22:51· ·--··pcvc1 2 下一页在《多继承》中讲过的例子中,由类A,类B1和类B2以及类C组成了类继承的层次结构。

在该结构中,类C的对象将包含两个类A的子对象。

由于类A是派生类C两条继承路径上的一个公共基类,那么这个公共基类将在派生类的对象中产生多个基类子对象。

如果要想使这个公共基类在派生类中只产生一个基类子对象,则必须将这个基类设定为虚基类。

虚基类的引入和说明前面简单地介绍了要引进虚基类的原因。

实际上,引进虚基类的真正目的是为了解决二义性问题。

虚基类说明格式如下:virtual <继承方式><基类名>其中,virtual是虚类的关键字。

虚基类的说明是用在定义派生类时,写在派生类名的后面。

例如:class A{public:void f();protected:int a;};class B : virtual public A{protected:int b;};class C : virtual public A{protected:int c:};class D : public B, public C{public:int g();private:int d;};由于使用了虚基类,使得类A,类B,类C和类D之间关系用DAG图示法表示如下:A{ f(), a }/ \B{b} C{c}\ /D{g(),d}从该图中可见不同继承路径的虚基类子对象被合并成为一个对象。

这便是虚基类的作用,这样将消除了合并之前可能出现的二义性。

这时,在类D的对象中只存在一个类A的对象。

因此,下面的引用都是正确的:D n;n.f(); file://对f()引用是正确的。

void D::g(){f(); file://对f()引用是正确的。

}下面程序段是正确的。

D n;A *pa;pa = &n;其中,pa是指向类A对象的指针,n是类D的一个对象,&n是n对象的地址。

虚函数和虚基类的区别

虚函数和虚基类的区别

虚函数和虚基类的区别 C++虚函数,纯虚函数,抽象类以及虚基类的区别Part1.C++中的虚函数什么是虚函数:直观表达就是,如果⼀个函数的声明中有 virtual 关键字,那么这个函数就是虚函数。

虚函数的作⽤:虚函数的最⼤作⽤就是实现⾯向对象程序设计的⼀⼤特点,多态性,多态性表达的是⼀种动态的概念,是在函数调⽤期间,进⾏动态绑定,以达到什么样的对象就实现什么样的功能的效果。

虚函数的⼀般声明语法:virtual 函数类型函数名 (形参表)注意:虚函数的声明只能出现在类的定义中,不能出现在成员函数实现的时候虚函数⼀般不声明为内联函数,但是声明为内联函数也不会引起错误在运⾏过程中要实现多态的三个条件:类之间满⾜赋值兼容关系(也就是类之间有继承关系)要声明为虚函数调⽤虚函数时,要由成员函数或者是指针和引⽤来访问代码举例#include <iostream>using namespace std;class Base1 {public:public:virtual void play();};void Base1::play(){cout << "Base1::play()" << endl;}class Base2:public Base1{virtual void play();};void Base2::play() {cout << "Base2::play()" << endl;}class Derived :public Base2{virtual void play();};void Derived::play() {cout << "Derived:: play()" << endl;}void fun(Base1* ba) { //声明⼀个基类的指针ba->play();}int main(){Base1 ba1;Base2 ba2;Derived de;//分别⽤不同的对象指针来调⽤ fun 函数fun(&ba1);fun(&ba2);fun(&de);return 0;}这代码含义就充分体现了虚函数作为实现多态条件的原因,由于 Base1 是 Base2 和 Derived 的⽗类,所以,Base1 是可以兼容 Base2 和Derived 的,所以在 fun 函数这⾥是⽤的 Base1 的指针来作为形参,不同的是当我传⼊参数不同时,fun 函数执⾏的是不同的结果,这就体现了多态的效果,我需要那个类型的实例,他就执⾏那个实例对应的⽅法。

什么叫做虚基类

什么叫做虚基类

7-4 什么叫做虚基类?有何作用?《“菱形结构”的派生关系。

当一个类的部分或全部直接基类又有共同父类,这个类将从多个直接基类多次继承共同父类的成员,导致同名成员在内存中同时拥有多个拷贝。

可以将直接基类的共同基类设置为虚基类,这时从不同的路径继承过来的该类成员在内存中只拥有一个拷贝,这样就解决了同名成员的惟一标识问题。

》当某类的部分或全部直接基类是从另一个基类派生而来,这些直接基类中,从上一级基类继承来的成员就拥有相同的名称,派生类的对象的这些同名成员在内存中同时拥有多个拷贝,我们可以使用作用域分辨符来惟一标识并分别访问它们。

我们也可以将直接基类的共同基类设置为虚基类,这时从不同的路径继承过来的该类成员在内存中只拥有一个拷贝,这样就解决了同名成员的惟一标识问题。

虚基类的声明是在派生类的声明过程中,其语法格式为:class 派生类名:virtual 继承方式基类名上述语句声明基类为派生类的虚基类,在多继承情况下,虚基类关键字的作用范围和继承方式关键字相同,只对紧跟其后的基类起作用。

声明了虚基类之后,虚基类的成员在进一步派生过程中,和派生类一起维护一个内存数据拷贝。

《所以构造函数要调虚基类的构造函数》7-5 声明一个基类Shape,在此基础上派生出Rectangle和Circle,二者都有getArea()函数计算对象的面积。

使用Rectangle类创建一个派生类Square。

#include <iostream>using namespace std;class Shape{public:Shape(){}virtual ~Shape(){}virtual float getArea ()const {return -1;}};class Rectangle:public Shape{public:Rectangle(float Length,floatWidth):length(Length),width(Width){ };~Rectangle(){}float getArea()const {return length*width;}float getLength()const { return length; }float getWidth()const { return width; }void setLength(float Length) { length = Length; }void setWidth(float Width) { width = Width; }private:float length;float width;};class Circle:public Shape{public:Circle(float Radius):radius(Radius){}Circle(){}float getArea()const{return 3.14*radius*radius;}float getRadius() const {return radius;}void setRadius(float Radius) { radius = Radius; }private:float radius;};class Square: protected Rectangle //正方形类//也可仿照Circle写{ public:Square(float Side): Rectangle(Side,Side) {}~Square() {}void setSide(float Side) //设置边长。

虚基类的概念和定义

虚基类的概念和定义

虚基类的概念和定义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两个派生类虚继承。

CC++中虚基类详解及其作用介绍

CC++中虚基类详解及其作用介绍

CC++中虚基类详解及其作⽤介绍⽬录概述多重继承的问题虚基类初始化例⼦总结概述虚基类 (virtual base class) 是⽤关键字 virtual 声明继承的⽗类.多重继承的问题N 类:class N {public:int a;void display(){cout << "A::a=" << a <<endl;}};A 类:class A : public N {public:int a1;};B 类:class B : public N {public:int a2;};C 类:class C: public A, public B{public:int a3;void display() {cout << "a3=" << a3 << endl;};};main:int main() {C c1;// 合法访问c1.A::a = 3;c1.A::display();return 0;}输出结果:A::a=3存在的问题:A::a 和 B::a 是 N 类成员的拷贝A::a 和 B::a 占⽤不同的空间虚基类我们希望继承间接共同基类时只保留⼀份成员, 所以虚基类就诞⽣了. 当基类通过多条派⽣路径被⼀个派⽣类继承时, 该派⽣类只继承该基类⼀次.语法:class 派⽣类名: virtual 继承⽅式基类名初始化通过构造函数的初始化表对虚拟类进⾏初始化. 例如:N 类:class N {public:int n;N(int n) : n(n) {};};A 类:class A : virtual public N {public:A(int n) : N(n) {};};B 类:class B : virtual public N {public:B(int n) : N(n) {};};C 类:class C: public A, public B{public:C(int n) : N(n), A(n), B(n){};};例⼦Person 类:#ifndef PROJECT5_PERSON_H#define PROJECT5_PERSON_H#include <iostream>#include <string>using namespace std;class Person {protected:string name;char gender;public:Person(string n, char g) : name(n), gender(g) {}void display() {cout << "name: " << name << endl;cout << "gender: " << gender << endl;}};#endif //PROJECT5_PERSON_HStudent 类:#ifndef PROJECT5_STUDENT_H#define PROJECT5_STUDENT_H#include <string>#include "Person.h"using namespace std;class Student : virtual public Person {protected:double score;public:Student(string n, char g, double s) : Person(n, g), score(s) {}; };#endif //PROJECT5_STUDENT_HTeacher 类:#ifndef PROJECT5_TEACHER_H#define PROJECT5_TEACHER_H#include <string>#include "Person.h"using namespace std;class Teacher : virtual public Person {protected:string title;public:Teacher(string n, char g, string t) : Person(n, g), title(t) {};};#endif //PROJECT5_TEACHER_HGraduate 类:#ifndef PROJECT5_GRADUATE_H#define PROJECT5_GRADUATE_H#include "Teacher.h"#include "Student.h"#include <string>using namespace std;class Graduate : public Teacher, public Student{private:double wage;public:Graduate(string n, char g, double s, string t, double w) : Person(n, g), Student(n, g, s), Teacher(n, g, t), wage(w) {}; void display() {Person::display();cout << "score: " << score << endl;cout << "title: " << title << endl;cout << "wages: " << wage << endl;};};#endif //PROJECT5_GRADUATE_Hmain:#include <iostream>#include "Graduate.h"using namespace std;int main() {Graduate grad1("⼩⽩",'f',89.5,"教授",1234.5);grad1.display();return 0;}输出结果:name: ⼩⽩gender: fscore: 89.5title: 教授wages: 1234.5总结使⽤多重继承时要⼗分⼩⼼, 否则会进场出现⼆义性问题不提倡在程序中使⽤多重继承只有在⽐较简单和不易出现⼆义性的情况或实在必要时才使⽤多重继承能⽤单⼀继承解决的问题就不要使⽤多重继承到此这篇关于C/C++中虚基类详解及其作⽤介绍的⽂章就介绍到这了,更多相关C++虚基类内容请搜索以前的⽂章或继续浏览下⾯的相关⽂章希望⼤家以后多多⽀持!。

详解C++中基类与派生类的转换以及虚基类

详解C++中基类与派生类的转换以及虚基类

详解C++中基类与派⽣类的转换以及虚基类C++基类与派⽣类的转换在公⽤继承、私有继承和保护继承中,只有公⽤继承能较好地保留基类的特征,它保留了除构造函数和析构函数以外的基类所有成员,基类的公⽤或保护成员的访问权限在派⽣类中全部都按原样保留下来了,在派⽣类外可以调⽤基类的公⽤成员函数访问基类的私有成员。

因此,公⽤派⽣类具有基类的全部功能,所有基类能够实现的功能,公⽤派⽣类都能实现。

⽽⾮公⽤派⽣类(私有或保护派⽣类)不能实现基类的全部功能(例如在派⽣类外不能调⽤基类的公⽤成员函数访问基类的私有成员)。

因此,只有公⽤派⽣类才是基类真正的⼦类型,它完整地继承了基类的功能。

不同类型数据之间在⼀定条件下可以进⾏类型的转换,例如整型数据可以赋给双精度型变量,在赋值之前,把整型数据先转换成为双精度型数据,但是不能把⼀个整型数据赋给指针变量。

这种不同类型数据之间的⾃动转换和赋值,称为赋值兼容。

现在要讨论的问题是:基类与派⽣类对象之间是否也有赋值兼容的关系,可否进⾏类型间的转换?回答是可以的。

基类与派⽣类对象之间有赋值兼容关系,由于派⽣类中包含从基类继承的成员,因此可以将派⽣类的值赋给基类对象,在⽤到基类对象的时候可以⽤其⼦类对象代替。

具体表现在以下⼏个⽅⾯。

1) 派⽣类对象可以向基类对象赋值可以⽤⼦类(即公⽤派⽣类)对象对其基类对象赋值。

如A a1; //定义基类A对象a1B b1; //定义类A的公⽤派⽣类B的对象b1a1=b1; //⽤派⽣类B对象b1对基类对象a1赋值在赋值时舍弃派⽣类⾃⼰的成员。

也就是“⼤材⼩⽤”,如图实际上,所谓赋值只是对数据成员赋值,对成员函数不存在赋值问题。

请注意,赋值后不能企图通过对象a1去访问派⽣类对象b1的成员,因为b1的成员与a1的成员是不同的。

假设age是派⽣类B中增加的公⽤数据成员,分析下⾯的⽤法:a1.age=23; //错误,a1中不包含派⽣类中增加的成员b1.age=21; //正确,b1中包含派⽣类中增加的成员应当注意,⼦类型关系是单向的、不可逆的。

c++继承与虚基类的定义

c++继承与虚基类的定义

c++继承与虚基类的定义
在C++中,继承是一种机制,使得一个类(称为派生类或子类)能够获得另一个类(称为基类或父类)的成员。

继承提供了代码重用和多态性的一种方式。

有两种类型的继承:公有(public)和私有(private)。

在公有继承中,基类的公有成员在派生类中也是公有的,基类的保护成员在派生类中是保护的。

而在私有继承中,基类的公有和保护成员在派生类中都是私有的。

然而,在某些情况下,我们可能希望派生类只继承基类的部分成员,而不是全部。

在这种情况下,我们可以使用虚基类。

虚基类是C++中的一种特殊类型的基类。

如果一个类被多个父类以不同的路径继承,则这个基类就成为虚基类。

例如,假设有两个父类A和B,都继承了一个共同的基类C,那么在这种情况下,C就是一个虚基类。

在定义虚基类时,需要在继承列表中使用关键字`virtual`。

例如:
```cpp
class C {
// ...
};
class A : virtual public C {
// ...
};
class B : virtual public C {
// ...
};
```
在这个例子中,类A和类B都以不同的路径继承了基类C,因此C是一个虚基类。

注意,使用`virtual`关键字声明了虚基类。

如果不使用`virtual`关键字,那么在编译时会产生一个错误,因为编译器会认为这是重复的继承。

C++学习笔记(10)——虚基类的作用

C++学习笔记(10)——虚基类的作用

C++学习笔记(10)——虚基类的作用当一个基类被声明为虚基类后,即使它成为了多继承链路上的公共基类,最后的派生类中也只有它的一个备份。

例如:class CBase { };class CDerive1:virtual public CBase{ };class CDerive2:virtual public CBase{ };class CDerive12:public CDerive1,CDerive2{ };则在类CDerive12的对象中,仅有类CBase的一个对象数据虚基类的特点:虚基类构造函数的参数必须由最新派生出来的类负责初始化(即使不是直接继承);虚基类的构造函数先于非虚基类的构造函数执行。

代码如下:/************************************************************************* 混合继承:多基类继承与多重继承************************************************************************/#include <IOSTREAM.H>//基类class CBase{protected:int a;public:CBase(int na){a=na;cout<<"CBase constructor! ";}~CBase(){cout<<"CBase deconstructor! ";}};//派生类1(声明CBase为虚基类)class CDerive1:virtual public CBase{public:CDerive1(int na):CBase(na){cout<<"CDerive1 constructor! ";}~CDerive1(){cout<<"CDerive1 deconstructor! ";}int GetA(){return a;}};//派生类2(声明CBase为虚基类)class CDerive2:virtual public CBase{public:CDerive2(int na):CBase(na){cout<<"CDerive2 constructor! ";}~CDerive2(){cout<<"CDerive2 deconstructor! ";}int GetA(){return a;}};//子派生类class CDerive12:public CDerive1,public CDerive2{public:CDerive12(int na1,int na2,int na3):CDerive1(na1),CDerive2(na2),CBase(na3){cout<<"CDerive12 constructor! ";}~CDerive12(){cout<<"CDerive12 deconstructor! ";}};void main(){CDerive12 obj(100,200,300);//得到从CDerive1继承的值cout<<" from CDerive1 : a = "<<obj.CDerive1::GetA();//得到从CDerive2继承的值cout<<" from CDerive2 : a = "<<obj.CDerive2::GetA()<<endl<<endl;}1. 子派生类对象的值:从上例可以看出,在类CDerived12的构造函数初始化表中,调用了间接基类CBase的构造函数,这对于非虚基类是非法的,但对于虚基类则是合法且必要的。

C++_虚基类

C++_虚基类

C++_虚基类虚基类1.为什么要引⼊虚基类当某⼀个类的多个直接基类是从另⼀个共同基类派⽣⽽来时,这些直接基类中从上⼀级基类继承来的成员就拥有相同的名称。

在派⽣类的对象中,这些同名成员在内存中同时拥有多个拷贝。

如何进⾏分辨呢?⼀种⽅法就是使⽤作⽤域标⽰符来唯⼀表⽰它们另⼀种⽅法是定义虚基类,使派⽣类中只保留⼀份拷贝。

例虚基类的引例#include <iostream.h>class base {public:base(){ a=5; cout<<"base a="<<a<<endl; }protected:int a;};class base1:public base{public:base1() { a=a+10; cout<<"base1 a="<<a<<endl; }};class base2:public base{public:base2(){a=a+20; cout<<"base2 a="<<a<<endl;}};class derived:public base1,public base2{public:derived() {cout<<"base1::a="<<base1::a<<endl;cout<<"base2::a="<<base2::a<<endl;}};main(){derived obj;return 0;}程序运⾏结果如下:base a=5base1 a=15base a=5base2 a=25base1::a=15base2::a=25⾮虚基类的类层次图2. 虚基类的概念class derived:public base1,public base2{public:derived(){ cout<<"base1::a="<<a<<endl; }}可使⽤虚基类解决上例访问类base成员a的⼆义性问题。

C++论文(虚基类的应用)

C++论文(虚基类的应用)

虚基类的应用理学院08信息与计算科学080108010028袁世翰C++背景简介:C++语言是一种优秀的面向对象程序设计语言,它在C语言的基础上发展而来,但它比C语言更容易为人们学习和掌握。

C++以其独特的语言机制在计算机科学的各个领域中得到了广泛的应用。

面向对象的设计思想是在原来结构化程序设计方法基础上的一个质的飞跃,C++完美地体现了面向对象的各种特性。

C++语言是从C语言发展演变而来的一种面向对象的程序设计语言。

C++语言的主要特点表现在两个方面,一是全面兼容C,二是支持面向对象的方法。

面向对象的程序设计(OOP)方法将数据及对数据的操作方法封装在一起,作为一个相互依存、不可分离的整体——对象。

对同类型对象抽象出其共性,形成类。

类中的大多数数据只能用本类的方法进行处理。

类通过一个简单的外部接口与外界发生关系,对象与对象之间通过消息进行通信。

这样,程序模块间的关系简单,程序模块的独立性、数据的安全性具有良好的保障。

同时通过继承与多态性,使程序具有很高的可重用性,使软件的开发和维护都更为方便。

由于面向对象方法的突出优点,目前它已经成为开发大型软件时所采用的主要方法,而C++语言是面向对象的程序设计语言中应用最广泛的一种。

实验内容:如果一个派生类有多个直接基类,而这些直接基类又有一个共同的基类,则在最终的派生类中会保留该间接共同基类的数据成员的多份同名成员。

在引用这些同名成员时,必须在派生类对象后面增加直接基类名,以避免产生二义性。

在一个类中保留间接共同基类的多份同名成员,虽然有时是必要的,可以在不同的数据成员中分别存放不同的数据,也可以通过构造函数分别对它们进行初始化。

但在大多数情况下,这种现象是不希望出现的。

因为保留多份数据成员的拷贝,不仅占用较多的存储空间,还增加了访问这些成员时的困难,容易出错。

而且在实际上,并不需要有多份拷贝。

C++提供虚基类(virtual base class)的方法,使得在间接共同基类时只保留一份成员。

虚基类——精选推荐

虚基类——精选推荐

虚基类1.虚基类的概念在C++语⾔中,⼀个类不能被多次说明为⼀个派⽣类的直接基类,但可以不⽌⼀次地成为间接基类。

这就导致了⼀些问题。

为了⽅便说明,先介绍多继承的“类格”表⽰法。

派⽣类及其基类可⽤⼀有向⽆环图(DAG)表⽰,其中的箭头表⽰“由派⽣⽽来”。

类的DAG常称为⼀个“类格”。

复杂类格画出来通常更容易理解。

例如:例5-19class L{ public:int next;…};class A : public L{ };class B : public L{ };class C : public A, public B{ public :void f(){next=0;}};这时,next有两个赋值语句next=0; 具有⼆义性,它是将A::next置为零,还是将B::next置为零,或者将两者都置为0,需要在函数f()中被显式的说明.如果希望间接基类L与其派⽣类的关系是如下图当在多条继承路径上有⼀个公共的基类,在这些路径中的某⼏条路经汇合处,这个公共基类就会产⽣多个实例。

如果只想保存这个基类的⼀个实例,可以将这个公共基类说明为虚拟基类或称虚基类。

它仅是简单地将关键字virtual加到基类的描述上,例如改写上述例⼦为例5-20注意⼀个派⽣类的对象的地址可以直接赋给虚基类的指针,例如:C obj;L * ptr=&obj;这时不需要强制类型转换,并且,⼀个虚基类的引⽤可以引⽤⼀个派⽣类的对象,例如:C obj2;L &ref=obj2;反之则不⾏,⽆论在强制类型转换中指定什么路径,⼀个虚基类的指针或引⽤不能转换为派⽣类的指针或引⽤。

例如:C * P=(C*)(A*)ptr;将产⽣编译错误。

2. 虚基类对象的初始化虚基类的初始化与多继承的初始化在语法上是⼀样的,但隐含的构造函数的调⽤次序有点差别。

虚基类构造函数的调⽤次序是这样规定的:1. 虚基类的构造函数在⾮虚基类之前调⽤。

2. 若同⼀层次中包含多个虚基类,虚基类构造函数按它们说明的次序调⽤。

课后答案——C++语言程序设计教程(第二版)

课后答案——C++语言程序设计教程(第二版)

1.1习题1解答1.(1)机器语言是计算机直接理解执行的语言,由一系列(二进制)指令组成,其助记符构成了汇编语言;接近人的自然语言习惯的程序设计语言为高级语言。

(2)结构化程序设计方法主要内容有:自顶向下,逐步求精;面向对象方法将现实世界中的客观事物描述成具有属性和行为的对象,抽象出共同属性和行为,形成类。

(3)C++程序开发通常要经过5个阶段,包括:编辑,编译,连接,运行,调试。

首先是编辑阶段,任务是编辑源程序,C++源程序文件通常带有.c p p 扩展名。

接着,使用编译器对源程序进行编译,将源程序翻译为机器语言代码(目标代码),过程分为词法分析、语法分析、代码生成3个步骤。

在此之前,预编译器会自动执行源程序中的预处理指令,完成将其他源程序文件包括到要编译的文件中,以及执行各种文字替换等。

连接器的功能就是将目标代码同缺失函数的代码连接起来,将这个“漏洞”补上,生成可执行文件。

程序运行时,可执行文件由操作系统装入内存,然后CPU从内存中取出程序执行。

若程序运行进程中出现了错误,还在需要对程序进行调试。

(4)对象与对象之间通过消息进行相互通信。

(5)类是具有相同属性和行为的一组对象的抽象;任何一个对象都是某个类的一个实例。

(6)多态性是指在一般类中定义的属性或行为,被特殊类继承之后,可以具有不同的数据类型或表现出不同的行为。

(7)面向对象的软件开发过程主要包括面向对象的方法分析、面向对象的设计、面向对象的编程、面向对象的测试和面向对象的维护。

(8)泛型程序设计是指在程序设计时,将数据类型参数化,编写具有通用性和可重用的程序。

(9)# include <iostream>是一条预处理指令(语句),在编译(或预处理)时由编译器(或预编译器)执行,其功能是将iostream文件包含(复制)到指令处。

(10)C++中使用cin作为标准输入流对象,通常代表键盘,与提取操作符>>连用;使用cout 作为标准输出流对象,通常代表显示设备,与<< 连用。

大学c++期末简答题整理,期末复习必看

大学c++期末简答题整理,期末复习必看

1. 什么是虚基类,并简述其特点。

答:以virtual方式继承基类就是虚基类。

将共同基类设置为虚基类时,从不同路经继承过来的同名数据成员只有一个副本,同一个函数也只有一个映射。

2. 派生类对基类成员的继承方式有哪些?答:公有继承(public),私有继承(private),保护继承(protected)3.C++的作用域有哪几种?答:函数原型作用域,局部作用域,类作用域,文件作用域,命名空间作用域4内联函数的实现?答:内联函数不是在调用时发生控制转移,而是在编译时将函数体嵌入每一个调用处。

这样就节省了参数传递、控制转移等开销。

5.什么是函数重载?答:两个以上函数,具有相同的函数名,但是形参的个数或者类型不同,编译器根据实参和形参的类型及个数的最佳匹配,自动确定调用哪一个函数,这就是函数重载。

6.抽象是什么?答:面向对象方法中的抽象,是指对具体问题进行概括,抽出一类对象的公共性质并加以描述的过程。

对一个问题的抽象应该包括两个方面:数据抽象和行为抽象。

7.封装是什么?答:封装就是将抽象得到的数据和行为(或功能)相结合,形成一个有机的整体,也就是将数据与操作数据的函数代码进行有机地结合,形成“类”,其中的数据和函数是类的成员。

8.多态性是什么?答:多态性是指一段程序能够处理多种类型对象的能力。

在C++语言中,这种多态性可以通过强制多态,重载多态,类型参数化多态,包含多态四种形式来实现。

9.类是什么?答:类是对逻辑上相关的函数与数据封装,它是对问题的抽象描述。

10.构造函数的作用是什么?答:构造函数的作用就是在对象被创建时利用特定的值构造对象,将对象初始化为一个特定的状态。

构造函数在对象被创建的时候自动调用。

11.复制构造函数的作用?答:复制构造函数的作用是使用一个已经存在的对象(由复制构造函数的参数指定),去初始化同类的一个新对象。

而隐含复制构造函数的功能是,把初始值对象的每个数据成员的值都复制到新建立的对象中。

C++的虚基类,抽象类,虚函数,纯虚函数,virtual

C++的虚基类,抽象类,虚函数,纯虚函数,virtual
C++的虚基类,抽象类,虚函数,纯虚函数,virtual 虚基类 在说明其作用前先看一段代码
class A { public: int iValue; };
class B:public A { public: void bPrintf(){cout<<"This is class B"<<endl;}; };
void main() { D d; cout<<d.iValue<<endl; //错误,不明确的访问 cout<<d.A::iValue<<endl; //正确 cout<<d.B::iValue<<endl; //正确 cout<<d.C::iValue<<endl; //正确 }
从代码中可以看出类 B C 都继承了类 A 的 iValue 成员,因此类 B C 都有一个成员变量 iValue ,而类 D 又 继承了 B C,这样类 D 就有一个重名的成员 iValue(一个是从类 B 中继承过来的,一个是从类 C 中继承过 来的).在主函数中调用 d.iValue 因为类 D 有一个重名的成员 iValue 编译器不知道调用 从谁继承过来的 iValue 所以就产生的二义性的问题.正确的做法应该是加上作用域限定符 d.B::iValue 表示调用从 B 类继承 过来的 iValue。不过 类 D 的实例中就有多个 iValue 的实例,就会占用内存空间。所以 C++中就引用了虚 基类的概念,来解决这个问题。
class Bike:public Vehicle { public: virtual void PrintTyre(){cout<<"Bike tyre two"<<endl;}; };

虚基类的实现原理

虚基类的实现原理

虚基类的实现原理虚继承和虚函数是完全无相关的两个概念。

虚继承是解决C++多重继承问题的一种手段,从不同途径继承来的同一基类,会在子类中存在多份拷贝。

这将存在两个问题:其一,浪费存储空间;第二,存在二义性问题,通常可以将派生类对象的地址赋值给基类对象,实现的具体方式是,将基类指针指向继承类(继承类有基类的拷贝)中的基类对象的地址,但是多重继承可能存在一个基类的多份拷贝,这就出现了二义性。

虚继承可以解决多种继承前面提到的两个问题:虚继承底层实现原理与编译器相关,一般通过虚基类指针和虚基类表实现,每个虚继承的子类都有一个虚基类指针(占用一个指针的存储空间,4字节)和虚基类表(不占用类对象的存储空间)(需要强调的是,虚基类依旧会在子类里面存在拷贝,只是仅仅最多存在一份而已,并不是不在子类里面了);当虚继承的子类被当做父类继承时,虚基类指针也会被继承。

实际上,vbptr指的是虚基类表指针(virtual base table pointer),该指针指向了一个虚基类表(virtual table),虚表中记录了虚基类与本类的偏移地址;通过偏移地址,这样就找到了虚基类成员,而虚继承也不用像普通多继承那样维持着公共基类(虚基类)的两份同样的拷贝,节省了存储空间。

在这里我们可以对比虚函数的实现原理:他们有相似之处,都利用了虚指针(均占用类的存储空间)和虚表(均不占用类的存储空间)。

虚基类依旧存在继承类中,只占用存储空间;虚函数不占用存储空间。

虚基类表存储的是虚基类相对直接继承类的偏移;而虚函数表存储的是虚函数地址。

#include<iostream>using namespace std;class A //大小为4{public:int a;};class B :virtual public A //大小为12,变量a,b共8字节,虚基类表指针4{public:int b;};class C :virtual public A //与B一样12{public:int c;};class D :public B, public C //24,变量a,b,c,d共16,B的虚基类指针4,C的虚基类指针{public:int d;};int main(){A a;B b;C c;D d;cout << sizeof(a) << endl;cout << sizeof(b) << endl;cout << sizeof(c) << endl;cout << sizeof(d) << endl;system("pause");return 0;。

  1. 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
  2. 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
  3. 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。

C++学习笔记(10)——虚基类的作用
当一个基类被声明为虚基类后,即使它成为了多继承链路上的公共基类,最后的派生类中也只有它的一个备份。

例如:class CBase { };
class CDerive1:virtual public CBase{ };
class CDerive2:virtual public CBase{ };
class CDerive12:public CDerive1,CDerive2{ };
则在类CDerive12的对象中,仅有类CBase的一个对象数据
虚基类的特点:
虚基类构造函数的参数必须由最新派生出来的类负责初始化(即使不是直接继承);
虚基类的构造函数先于非虚基类的构造函数执行。

代码如下:
/************************************************************************
* 混合继承:多基类继承与多重继承
************************************************************************/
#include <IOSTREAM.H>
//基类
class CBase
{
protected:
int a;
public:
CBase(int na)
{
a=na;
cout<<"CBase constructor! ";
}
~CBase(){cout<<"CBase deconstructor! ";}
};
//派生类1(声明CBase为虚基类)
class CDerive1:virtual public CBase
{
public:
CDerive1(int na):CBase(na)
{
cout<<"CDerive1 constructor! ";
}
~CDerive1(){cout<<"CDerive1 deconstructor! ";}
int GetA(){return a;}
};
//派生类2(声明CBase为虚基类)
class CDerive2:virtual public CBase
{
public:
CDerive2(int na):CBase(na)
{
cout<<"CDerive2 constructor! ";
}
~CDerive2(){cout<<"CDerive2 deconstructor! ";}
int GetA(){return a;}
};
//子派生类
class CDerive12:public CDerive1,public CDerive2
{
public:
CDerive12(int na1,int na2,int na3):CDerive1(na1),CDerive2(na2),CBase(na3)
{
cout<<"CDerive12 constructor! ";
}
~CDerive12(){cout<<"CDerive12 deconstructor! ";}
};
void main()
{
CDerive12 obj(100,200,300);
//得到从CDerive1继承的值
cout<<" from CDerive1 : a = "<<obj.CDerive1::GetA();
//得到从CDerive2继承的值
cout<<" from CDerive2 : a = "<<obj.CDerive2::GetA()<<endl<<endl;
}
1. 子派生类对象的值:
从上例可以看出,在类CDerived12的构造函数初始化表中,调用了间接基类CBase的构造函数,这对于非虚基类是非法的,但对于虚基类则是合法且必要的。

对于派生类CDerived1和CDerived2,不论是其内部实现,还是实例化的对象,基类CBase是否是它们的虚基类是没有影响的。

受到影响的是它们的派生类CDerived12,因为它从两条路径都能到达CBase。

2. 运行结果:
由此可知,其公共基类的构造函数只调用了一次,并且优先于非基类的构造函数调用;并且发现,子派生类的对象obj的成员变量的值只有一个,所以,当公共基类CBase被声明为虚基类后,虽然它成为CDerive1和CDerive2的公共基类,但子派生类CDerive12中也只有它的一个备份。

可以仔细比较与例2的运行结果有什么不同。

相关文档
最新文档