类的每一个成员函数都有一个隐含的特殊指针,通常称为this指针,this指针的类型就是成员函数所属的类型。通常不显式使用this指针。this指针是C++实现封装的一种机制,它将对象和该对象调用的成员函数连接在一起,在外部看来,每个对象都拥有自己的成员函数。this指针一般用于返回当前对象自身。程序的执行结果如下:s1∶n=2s2∶n=1s3∶n=3this指针大量用于运算符重载成员函数设计中,这将在后面详细介绍。......
2023-11-07
1.虚基类的声明
当某类的部分或全部直接基类是从另一个共同基类派生而来的时,这些直接基类中从上一级基类继承来的成员就拥有相同的名字。这时可以将直接基类的共同基类设置为虚基类,从不同的路径继承过来的该类成员在内存中只拥有一个拷贝,从而解决了同名成员的唯一标识问题。
虚基类的声明是在派生类的定义过程中进行的,在基类的继承方式前加上关键字“virtual”,其语法格式为:
class派生类名:virtual 继承方式基类名其中,“virtual”是虚基类的关键词。虚基类的声明是用在指定派生类时,放在派生类名的后面。
【例6.12】分析以下程序的执行结果。
解:上述程序中,虽然B和C是由共同的基类A派生而来,但由于引入了虚基类,其类层次如图6.13所示(带箭头的虚线表示虚继承),这时的数据成员x只存在一份拷贝,无论是B∷x,还是C∷x,其结果都是一样的,所以可以用x直接访问它的值。程序的执行结果如下:
x=0,y=1
x=0,z=3
m=5
x=4,y=1
x=4,z=3
m=5
图6.13 虚基类的类层次图
引进虚基类后,派生类(即子类)的对象中只存在一个虚基类的子对象。当一个类有虚基类时,编译系统将为该类的对象定义一个指针成员,让它指向虚基类的子对象。该指针被称为虚基类指针。
2.虚基类的构造函数
如果派生类有一个虚基类作为祖先类,那么在派生类构造函数的初始化列表中需要列出对虚基类构造函数的调用,如果未列出则表明调用的是虚基类的无参数构造函数。派生类构造函数调用的次序如下:
(1)先调用基类的构造函数,多个基类则按派生类声明时列出的次序、从左到右调用,而不是初始化列表中的次序。
(2)再次调用对象成员(子对象)的构造函数,按类声明中对象成员出现的次序调用,而不是初始化列表中的次序。
(3)最后执行派生类的构造函数。
在以上构造函数调用过程中,同一层中对虚基类构造函数的调用总是先于普通基类的构造函数。
【例6.13】分析以下程序的执行结果。
解:上述程序中,各类之间构成类层次如图6.14所示,其中虚线表示是虚继承,也就是说,其基类为虚基类。程序的执行结果如下:
class A
class B
class C1
class C2
class D
图6.14 类层次图
本例中各类没有数据成员,若包含有数据成员,需在构造函数中增加初始化列表,例如,增加了私有数据成员的程序如下:
程序执行结果如下:
class A:a=10
class B:50
class C1:c1=28
class C2:c2=26
class D:d=13
从结果看到,调用基类A的构造函数次序是D的B(i,j)→B的A(i)→执行A(i),而C1和C2的构造函数中B(2*i,j)和B(3*i,j)被忽略。也就是说,从虚基类派生的子类的构造函数的初始化列表中都要列出对虚基类构造函数的调用,但只有用于建立对象的派生类的构造函数调用虚基类的构造函数,而该派生类的所有基类中列出的对虚基类的构造函数的调用在执行中被忽略,从而保证对虚基类对象只初始化一次。
【例6.14】分析以下程序的执行结果。(www.chuimin.cn)
解:本程序的执行结果如下:
M:in B
B:from G
M:in C
C:from G
M:in A
A:from E
M:in E
E:from G
M:in D
D:from F
M:in F
F:from G
M:in G
G:from main
本例中各个类的派生层次如图6.15所示。其中每个类都有一个嵌入的M类的成员,有4个继承是虚继承:E派生自B和C,F派生自B和C。
图6.15 类层次图
在main()函数中,g的初始化需要首先初始化它的E和F部分,但是B和C子对象首先被初始化,因为它们是虚基类,并且两者的初始化在G的构造函数的初始化中进行,G是最高层派生类。类B没有基类,所以它的成员对象m被初始化,然后它的构造函数输出“B:from G”,对于E的C子对象处理相同。E子对象的初始化需要先对A、B和C子对象进行初始化。因为B和C已经被初始化,于是E子对象的A子对象接着被初始化,然后是E子对象自己初始化。相同的情况重复出现在G的F子对象上,但是虚基类的初始化不重复进行。
注意:在一般情况下,虚基类只允许定义不带参数的或带默认参数的构造函数。
3.虚基类的析构函数
同样,如果存在虚基类时,派生类析构函数的执行会涉及其基类或虚基类的析构函数的执行。析构函数的调用次序与构造函数的调用正好相反。派生类析构函数调用的次序如下:
(1)首先执行派生类的析构函数。
(2)其次调用对象成员(子对象)的析构函数,按类声明中对象成员出现的逆序调用,而不是初始化列表中的次序。
(3)再次调用普通基类的析构函数,多个基类则按派生类声明时列出的逆序、从右到左调用,而不是初始化列表中的次序。
在以上析构函数调用过程中,同一层中对普通基类构造函数的调用总是先于虚基类的析构函数。
【例6.15】分析以下程序的执行结果。
图6.16 类层次图
解:上述程序是将例6.14程序中的所有构造函数改为析构函数得到的,其对应的类层次图如图6.16所示,本程序的执行结果如下:
class E
class C
class B
class D
class B
class A
从结果可以看到,派生类的析构函数执行次序与构造函数的执行次序正好相反。
有关C++程序设计基础教程的文章
类的每一个成员函数都有一个隐含的特殊指针,通常称为this指针,this指针的类型就是成员函数所属的类型。通常不显式使用this指针。this指针是C++实现封装的一种机制,它将对象和该对象调用的成员函数连接在一起,在外部看来,每个对象都拥有自己的成员函数。this指针一般用于返回当前对象自身。程序的执行结果如下:s1∶n=2s2∶n=1s3∶n=3this指针大量用于运算符重载成员函数设计中,这将在后面详细介绍。......
2023-11-07
需要注意的是,基类指针虽然获取了派生类对象的地址,但只能访问派生类从基类继承的成员,不能访问派生类中新增的成员,除非对基类指针强制类型转换调用派生类的成员函数。......
2023-11-07
声明虚函数的一般格式如下:virtual函数类型函数名(参数表)其中,用关键词virtual声明的函数称为虚函数。如果一个成员函数被声明为虚函数,这就意味着该成员函数在派生类中可能有不同的实现。动态绑定只能通过指针或引用标识对象来调用虚函数。......
2023-11-07
指针初始化有几种方式:指针对象可以被一个具有相同类型的对象初始化。下面的例子将整型指针ip赋给另一整型指针ip2:int*ip2=ip;此时,这两个指针指向同一内存空间,可用图1.8表示。图1.8指针与指针所指对象如果程序员想强制赋给指针另一种类型变量的地址,则必须显式进行类型转换。如要将一个整型指针的值赋给浮点数指针,可以采用以下语句:float*fp=ip;通过直接分配内存地址得到初值。......
2023-11-07
在C++中,数组和指针密切相关,几乎可以互相使用。数组名字可以认为是常量指针,它指向存放数组第一个元素的内存地址。指针可以用于完成任何涉及数组下标的操作。实际上,数组下标表示法在编译期间将转换为指针表示法,所以用指针方式来书写数组下标表达式可以节省编译时间。如果再定义一个字符指针:char*pname=name;则name[i]、*和*具有相同的值,都是数组第i+1个元素的值。分析以下程序的执行结果。......
2023-11-07
程序设计是指设计、编写和调试程序的方法与过程。由于程序是软件的本体,因此软件的质量主要通过程序的质量体现,因此,研究一种切实可行的程序设计方法至关重要。继承是面向对象程序设计方法的一个重要标志,利用继承机制可以大大提高程序的可重用性和可扩充性。......
2023-11-07
声明函数模板的一般格式如下:template<模板参数表>函数返回值类型函数模板名(形参表)函数模板定义由关键字template开头,表示声明一个模板。模板参数表写在尖括号<>中,参数一般由关键字class或typename后加一个标识符构成。Class和typename的意义相同,表示后面的标识符是一个参数类型,代表一个潜在的标准类型或用户定义的类型。例如,定义max函数模板求两个数中的较大者。如下面的声明是错误的:可以看出,用函数模板比函数重载更方便,程序更简洁。......
2023-11-07
在类声明中,public、private和protected是关键字,称为成员访问限定符,它们分别表示公有、私有和保护的成员访问权限。其他函数不能访问该类的保护数据成员,也不能调用该类的保护成员函数。◇一旦给出了成员访问限定符,它后面的成员都具有这个成员访问权限,直到出现另一个成员访问限定符或类声明结束为止。类的成员函数可以访问类的所有成员,没有任何限制,而类的对象对类的成员的访问是受成员访问控制符制约的。......
2023-11-07
相关推荐