C++派生类与继承(超详细)

一、派生类的概念

1.1、为什么要使用继承

1.2、派生类的说明

1.3、保护成员的作用(protected)

二、派生类的构造函数与析构函数

2.1、派生类构造函数和析构函数的执行顺序

通常情况下,当创建派生类对象时, 首先执行基类的构造函数, 随后再执行派生类的构造函数; 当撤消派生类对象时, 则先执行派生类的析构函数, 随后再执行基类的析构函数。
下列程序的运行结果,反映了基类和派生类的构造函数及析构函数的执行顺序。

2.2、派生类构造函数和析构函数的构造规则

当基类的构造函数没有参数,或没有显式定义构造函数时, 派生类可以不向基类传递参数,甚至可以不定义构造函数。派生类不能继承基类中的构造函数和析构函数。当基类含有带参数的构造函数时,派生类必须定义构造函数,以提供把参数传递给基类构造函数的途径。
在 C + + 中,派生类构造函数的一般格式为:

派生类构造函数名(参数表) :基类构造函数名( 参数表)
{
/ /}

其中基类构造函数的参数,通常来源于派生类构造函数的参数表, 也可以用常数值。
下面的程序说明如何传递一个参数给派生类的构造函数和传递一个参数给基类的构造函数。

说明:

  • 当基类构造函数不带参数时, 派生类不一定需要定义构造函数, 然而当基类的构造函数哪怕只带有一个参数,它所有的派生类都必须定义构造函数,
    甚至所定义的派生类构造函数的函数体可能为空,仅仅起参数的传递作用。
  • 若基类使用缺省构造函数或不带参数的构造函数, 则在派生类中定义构造函数时可略去“∶基类构造函数名(参数表)”;此时若派生类也不需要构造函数, 则可不定义构造函数。
  • 如果派生类的基类也是一个派生类, 则每个派生类只需负责其直接基类的构造,依次上溯。 由于析构函数是不带参数的,在派生类中是否要定义析构函数与它所属的基类无关,故基类的析构函数不会因为派生类没有析构函数而得不到执行, 它们各 自是独立的。

三、多重继承

3.1、多重继承的声明

一般形式如下:

class 派生类名: 派生方式 1 基类名 1,,派生方式 n 基类名 n
{
// 派生类新增的数据成员和成员函数
} ;

冒号后面的部分称基类表,各基类之间用逗号分隔, 其中“派生方式 i”( i = 1, 2, …, n )规定了派生类从基类中按什么方式继承: private 或 public,缺省的派生方式是 private

class z: public x, y
{
    // 类 z 公有继承了类 x,私有继承了类 y 
    // …
};
class z: x, public y
{
    // 类 z 私有继承了类 x,公有继承了类 y
    // …
};
class z: public x, public y
{
    // 类 z 公有继承了类 x 和类 y
    // …
};

如下:对基类成员的访问必须是无二义的, 例如下列程序段对基类成员的访问是二义的,必须想法消除二义性。

class X
{
public:
    int f();
};
class Y
{
public:
    int f();
    int g();
};
class Zpublic X, public Y
{
public:
    int g();
    int h();
};

假如定义类 Z 的对象 obj: Z obj;则以下对函数 f( )的访问是二义的:

  • obj .f( ) ;
    / / 二义性错误,不知调用的是类 X 的 f( ) ,还是类 Y 的 f( )
    使用成员名限定可以消除二义性,例如:

  • obj .X∷f( ) ;
    / / 调用类 X 的 f( )

  • obj .Y∷f( ) ;
    / / 调用类 Y 的 f( )

3.2、多重继承的构造函数与析构函数

多重继承构造函数的定义形式与单继承构造函数的定义形式相似, 只是 n 个基类的构造函数之间用“,”分隔。多重继承构造函数定义的一般形式如下:

派生类构造函数名(参数表) :基类 1 构造函数名 ( 参数表), 基类 2 构造函数名 (参数表),,基类 n 构造函数名(参数表)
{
    // …
}

例如,由一个硬件类 Hard 和一个软件类 Soft ,它们共同派生出系统类 System, 声明如下:

class Hard
{
protected:
    char bodyname[20];
public:
    Hard(char * bdnm );     // 基类 Hard 的构造函数
    // …
};
class Soft
{
protected:
    char os[10];
    char Lang[15];
public:
    Soft( char * o, char * lg);// 基类 Soft 的构造函数
    // …
} ;
class System: public Hard, public Soft
{
private:
    char owner[10] ;
public:
    System( char * ow, char * bn, char * o, char * lg) // 派生类 System 的构造函数Hard( bn), Soft(o, lg);
    // 缀上了基类 Hard 和 Soft 的构造函数
    // …
};

注意:在定义派生类 System 的构造函数时,缀上了 Hard 和 Soft 的构造函数。
再如,现有一个窗口类 window 和一个滚动条类 scrollbar, 它们可以共同派生出一个带有滚动条的窗口,声明如下:

class window
{
    // 定义窗口类 window
    // …
public:
    window(int top, int left, int bottom, int right);
    ~window();
    // …
} ;
class scrollbar
{
    // 定义滚动条类 scrollbar
    // …
public:
    scrollbar(int top, int left, int bottom, int right);
    ~scrollbar();
    // …
};
class scrollbarwind∶window,scrollbar
{
    / / 定义派生类
    / /public:
    scrollbarwind(int top, int left, int bottom, int right);
    ~scrollbarwind();
    // …
};
scrollbarwind∷scrollbarwind(int top, int left, int bottom, int right)window( top, left,bot tom, right),scrollbar(top, right - 20, bottom, right)
{
    // …
}

在这个例子中, 定义派生类 scrollbarwind 的构造函数时, 也缀上了基类 window 和scrollbar 的构造函数。

下面我们再看一个程序,其中类 X 和类 Y 是基类, 类 Z 是类 X 和类 Y 共同派生出来的,请注意类 Z 的构造函数的定义方法。

#include<iostream>
using namespace std;
class X
{
    int a;
public:
    X(int sa ) // 基类 X 的构造函数
    {
        a = sa;
    }
    int getX()
    {
        return a ;
    }
};
class Y
{
    int b;
public:
    Y(int sb) // 基类 Y 的构造函数
    {
        b = sb;
    }
    int getY()
    {
        return b;
    }
} ;
class Z: public X, private Y
{
    // 类 Z 为基类 X 和基类 Y 共同的派生类
    int c;
public:
    Z(int sa, int sb, int sc ) :X(sa), Y(sb) // 派生类 Z 的构造函数,缀上
    {
        c = sc ;
    } // 基类 X 和 Y 的构造函数
    int getZ()
    {
        return c;
    }
    int getY()
    {
        return Y::getY();
    }
};
int main()
{
    Z obj( 2, 4, 6) ;
    int ma = obj.getX();
    cout << "a = "<< ma << endl;
    int mb = obj .getY();
    cout << "b = "<< mb << endl;
    int mc = obj .getZ();
    cout << "c = "<< mc << endl;
    return 0 ;
}

上述程序运行的结果如下:
a = 2
b = 4
c = 6
由于派生类 Z 是 X 公有派生出来的, 所以类 X 中的公有成员函数 getX( )在类 Z 中仍是公有的, 在 main ( ) 中可以直接引用, 把成员 a 的值赋给 main ( ) 中的变量 ma ,并显示在屏幕上。类 Z 是从类 Y 私有派生出来的, 所以类 Y 中的公有成员函数 getY( ) 在类 Z 中成为私有的,在 main( ) 中不能直接引用。为了能取出 b 的值,在 Z 中另外定义了一个公有成员函数 Z∷getY( ) ,它通过调用 Y∷getY( ) 取出 b 的值。主函数 main( )中的语句:int mb = obj .getY( ) ;调用的是派生类 Z 的成员函数 getY( ) , 而不是基类 Y 的成员函数 getY( )。由于类 Z 中的成员函数 getZ( ) 是公有成员,所以在 main( ) 中可以直接调用取出 c 的值。

总结:
多重继承的构造函数的执行顺序与单继承构造函数的执行顺序相同, 也是遵循先执行基类的构造函数,再执行对象成员的构造函数, 最后执行派生类构造函数的原则。在多个基类之间, 则严格按照派生类声明时从左到右的顺序来排列先后。而析构函数的执行顺序则刚好与构造函数的执行顺序相反。

3.3、虚基类

不难理解,如果在上例中类 base 只存在一个拷贝, 那么对 a的引用就不会产生二义性。在 C + + 中,如果想使这个公共的基类只产生一个拷贝,则可以将这个基类说明为虚基类。这就要求从类 base 派生新类时, 使用关键字 virtual 将类base 说明为虚基类。

#include <iostream>
using namespace std;
class base
{
protected:
    int a;
public:
    base()
    {
        a = 5;
    }
};
class base1:virtual public base
{
public:
    base1()
    {
        cout << "base1 a = "<< a << endl;
    }
};
class base2:virtual public base
{
public:
    base2()
    {
        cout << "base2 a = "<< a << endl;
    }
};
class derived:public base1, public base2
{
public:
    derived()
    {
        cout << "derived a = "<< a << endl;
    }
};
int main ()
{
    derived obj;
    return 0 ;
}

在上述程序中,从类 base 派生出类 base1 和类 base2 时,使用了关键字 virtual ,把类base 声明为 base1 和 base2 的虚基类。这样, 从 base1 和base2 派生出的类 derived 只有一个基类 base, 从而可以消除二义性。

虚基类的初始化
虚基类的初始化与一般的多重继承的初始化在语法上是一样的,但构造函数的调用顺序不同。虚基类构造函数的调用顺序是这样规定的:

  • 若同一层次中包含多个虚基类, 这些虚基类的构造函数按对它们说明的先后次序调用。
  • 若虚基类由非虚基类派生而来, 则仍然先调用基类构造函数, 再调用派生类的构造函数。
  • 若同一层次中同时包含虚基类和非虚基类, 应先调用虚基类的构造函数, 再调用非虚基类的构造函数,最后调用派生类构造函数, 例如:
 class Xpublic Y, virtual public Z
{
   // …
};
X one;

定义类 X 的对象 one 时,将产生如下的调用次序:
Z( ) ;
Y( ) ;
X( ) ;

#include<iostream>
using namespace std;
class base
{
    int a ;
public:
    base (int sa)
    {
        a = sa;
        cout << "Constructing base"<< endl;
    }
};
class base1: virtual public base
{
    int b;
public:
    base1 (int sa, int sb) : base(sa)
    {
        b = sb;
        cout << "Constructing baes1"<< endl;
    }
};
class base2: virtual public base
{
    int c;
public:
    base2 (int sa, int sc) : base (sa)
    {
        c = sc ;
        cout << "Constructing baes2"<< endl;
    }
};
class derived: public base1, public base2
{
    int d;
public:
    derived(int sa, int sb, int sc, int sd ) :
        base(sa ), base1 (sa,sb), base2(sa, sc )
    {
        d = sd;
        cout << "Constructing derived"<< endl;
    }
};
int main()
{
    derived obj(2, 4, 6, 8 ) ;
    return 0 ;
}

在上述程序中, base 是一个虚基类,它只有一个带参数的构造函数, 因此要求在派生类 base1、base2 和 derived 的构造函数的初始化表中,都必须带有对 base 构造函数的调用。

如果 base 不是虚基类,在派生类 derived 的构造函数的初始化表中调用 base 的构造函数是错误的,但是当 base 是虚基类且只有带参数的构造函数时, 就必须在类 derived 构造函数的初始化表中调用类 base 的构造函数。因此, 在 derived 构造函数的初始化表中,不仅含有对 base1 和 base2 构造函数的调用, 还有对虚基类 base 构造函数的调用。上述程序运行的结果为:
Constructing base
Constructing base1
Constructing base2
Constructing derived

不难看出,上述程序中虚基类 base 的构造函数只执行了一次。显然, 当 derived 的构造函数调用了虚基类 base 的构造函数之后, 类 base1 和类 base2 对 base 构造函数的调用被忽略了。这也是初始化虚基类和初始化非虚基类不同的地方。

说明:

  • 关键字 virtual 与派生方式关键字 ( public 或 private ) 的先后顺序无关紧要, 它只说明是“虚拟派生”。 例如以下两个虚拟派生的声明是等价的。
class derived: virtual public base{
// …
} ;
class derived: public virtual base{
// …
} ;
  • 一个基类在作为某些派生类虚基类的同时, 又作为另一些派生类的非虚基类, 这种情况是允许存在的,例如:
class B{
// …
} ;
class Xvirtual public B{
// …
} ;
class Yvirtual public B{
// …
} ;
class Zpublic B{
// …
} ;
class AApublic X, public Y , public Z{
// …
} ;

此例中,派生类 AA 由类 X、类 Y 和类 Z 派生而来。AA 与它的间接基类 B 之间的对应关系是:类 B 既是 X、Y 继承路径上的一个虚基类, 也是 Z 继承路径上的一个非虚基类。

  • 2
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
第4章 MFC实用技术 93 4.1 MFC常用宏应用 94 0188 获取32位整数的低字节和高字节数据 94 0189 将两个16位数组合为一个32位数 94 4.2 MFC常用函数 94 0190 MFC常用调试函数 94 0191 判断某个句柄是否关联一个窗口 95 0192 MFC应用程序信息和管理函数 95 0193 Internet URL解析全局函数 95 4.3 MFC框架技术 96 0194 在的定义使其具有运行型识别的功能 96 0195 运行判断某个对象是否是指定的型 96 0196 禁止文档/视图应用程序运行显示视图选择窗口 96 0197 多个窗口消息共享同一个消息处理函数 98 0198 遍历对话框中的子控件 99 0199 在程序中捕捉CException及其派生类的异常 100 0200 扩展消息映射宏 100 0201 THIS_FILE的含义 100 0202 为静态文本控件命名 100 0203 在基于对话框的应用程序中添加文档\视图的支持 101 0204 解析浮动状态下工具栏的父窗口 101 4.4 MFC编程技术 101 0205 根据位图资源ID获取位图大小 101 0206 将某个控件对象关联到对话框中的控件资源 102 0207 将一个全局函数指针关联到对话框的某个方法 102 0208 修改应用程序的图标 102 0209 使用安全数组 103 0210 将子窗口的客户区域映射到父窗口中 103 0211 判断两个间段的差距 103 0212 重新设置工程名称 103 0213 为dll文件生成lib文件 104 0214 如何将一个工程中的部分资源加到另一个工程中 104 0215 根据句柄获得窗口对象的方法 104 0216 如何共享MSDN 104 0217 从完整的文件名中去除路径 104 0218 从复合字符串中解析子串 105 0219 如何获得应用程序的完整路径 105 0220 修改对话框图标的几种方法 105 0221 将多个具有不同参数的函数赋值为同一个函数指针 105
### 回答1: 在C++中,继承是一种重要的面向对象编程概念,它允许我们定义一个新的,该继承现有的所有属性和方法。派生类继承的子,它可以使用继承的所有属性和方法,并且可以添加自己的属性和方法。 在实验中,我们学习了C++继承派生类的相关知识,包括: 1. 继承的访问控制:公有继承、私有继承和保护继承。 2. 多重继承:一个派生类可以同继承多个基类。 3. 虚函数和纯虚函数:虚函数是在基类中定义的函数,可以在派生类中重写;纯虚函数是没有实现的虚函数,必须在派生类中实现。 4. 虚函数表和虚函数指针:虚函数表是用于存储虚函数地址的表,虚函数指针指向虚函数表。 通过实验,我们能够更深入地理解C++继承派生类的概念和应用,能够更加灵活地使用面向对象编程思想来设计和实现程序。 ### 回答2: 继承是面向对象编程中的重要概念之一,它允许我们在已有的基础上创建新的,新可以继承并拥有已有的属性和方法。通过继承,可以减少代码的重复性,并且使代码更加可维护和扩展。 在进行派生类实验的过程中,我深刻体会到了继承的重要性和灵活性。首先,通过定义一个基类,我可以将一些通用的属性和方法抽象出来,避免在每个派生类中都重复定义。这为程序的整体结构设计提供了便捷。 其次,派生类可以在继承基类的基础上进行扩展,添加新的属性和方法。这种灵活性使得派生类在满足基本功能的同,也能根据具体需求进行定制化开发。例如,在一个动物的基础上,我可以派生出猫、狗等具体的动物子,它们各自有着自己的特点和行为。 另外,继承还支持多层次的派生关系。我可以从一个派生类中再派生出新的派生类,这样可以形成的层次结构。这种层次化设计可以更好地组织代码,使得代码更加清晰可读。 通过这次实验,我进一步理解了继承性与派生类的概念,学会了如何设计和使用继承关系。同,我也认识到了继承关系的合理运用能够提高代码的效率和可维护性。继承不仅是面向对象编程的基础,也是实现代码重用和扩展的重要工具之一。在今后的编程实践中,我将更加灵活地运用继承,提高代码的质量和可扩展性。 ### 回答3: 继承性是面向对象编程中的一个重要特性,指的是子能够继承的属性和方法。通过继承,子可以重用父的代码,并且可以在此基础上进行扩展和修改。 派生类是指通过继承创建的新。在派生类中,可以通过重写父的方法,改变其行为,实现多态性。派生类还可以新增自己的成员变量和成员方法,以满足自身的特殊需求。 在实验中,我们通过创建派生类的关系,研究了继承性和派生类的特性。 通过继承,我们可以将通用的属性和方法放在父中,让子共享这些代码。这样可以提高代码的重用性和可维护性。同,当需要对父中的方法进行修改,只需在子中进行重写,不会对其他子造成影响。 在派生类中,我们可以根据需要重写父的方法,改变其行为。这使得可以根据实际情况来实现多态性,同一个方法在不同的派生类中可能表现出不同的行为。 派生类还可以新增自己的成员变量和成员方法。通过这样的方式,可以为子添加独特的功能,以满足特定的需求。 继承性和派生类是面向对象编程中非常重要的概念。通过合理运用这两个特性,可以使代码更加模块化和可扩展,提高代码的复用性和可维护性。同派生类的特性也使得面向对象编程更加灵活,可以根据实际需求进行扩展和修改。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值