c++学习笔记 -- 继承(3)

声明:代码出处http://www.weixueyuan.net/view/6363.html


派生类构造函数可以自动调用基类的默认构造函数,但是前提是默认构造函数必须存在。通常情况下,默认构造函数系统会自动生成的,但是如果在基类中,我们自己定义了一个带参数的构造函数,这个时候,系统是不会为基类自动生成默认构造函数的,这个时候派生类则无法自动调用基类的默认构造函数了,因为基类根本就不存在默认构造函数。遇到这种情况有两种解决方案:其一,在基类中定义一个默认构造函数(不带参数的构造函数)其二,派生类中的每一个构造函数都显式的调用基类中的带参构造函数。

class base
{
public:
    base(int a){x = a; y = 0;}
    base(int a, int b){x = a; y = b;}
private:
    int x;
    int y;
};

class derived: public base
{
public:
    derived(){z = 0;}
    derived(int c){z = c;}
private:
    int z;
};

建议在设计类的时候为每一个类设计一个默认构造函数,毕竟默认构造函数并不会妨碍构造函数的显式调用。通常我们还会遇到这样一种情况,派生类中并未显式定义构造函数,这个时候派生类中只有系统自动生成的默认构造函数,如此一来,如果我们不为基类设计一个默认构造函数,则程序就会编译出错。这种错误很玄妙,如果不小心还真是难以发现。为了避免这种情况的发生,我们建议为每一个类设计一个默认构造函数


#include<iostream>
using namespace std;

class base
{
public:
    base(){x = 0; y = 0;}
    base(int a){x = a; y = 0;}
    base(int a, int b){x = a; y = b;}
private:
    int x;
    int y;
};

class derived: public base
{
public:
    derived():base(){z = 0;}
    derived(int a, int b, int c):base(a,b){z = c;}
private:
    int z;
};

int main()
{
    derived A;
    derived B(1,2,3);
    return 0;
}

#include<iostream>
using namespace std;

class base
{
public:
    base(){x = 0; y = 0;}
    base(int a){x = a; y = 0;}
    base(int a, int b){x = a; y = b;}
private:
    int x;
    int y;
};

class derived: public base
{
public:
    derived():base(){z = 0;}
    derived(int a, int b, int c):base(a,b){z = c;}
private:
    int z;
};

int main()
{
    derived A;
    derived B(1,2,3);
    return 0;
}

为基类定义了默认构造函数,并且在派生类中显式地调用基类中的构造函数。

总的来说,在创建派生类对象时,必须显式或隐式地调用基类的某一个构造函数,这一点非常重要。当然被调用的基类的构造函数可以是带参构造函数,也可以是默认构造函数。


创建派生类对象时构造函数的调用顺序是按照继承顺序,先执行基类构造函数,然后再执行派生类的构造函数。但是对于析构函数,其调用顺序是正好相反的,即先执行派生类的构造函数,然后再执行基类的构造函数。


#include <iostream>
using namespace std;


class A
{
public:
    A(){cout<<"A constructor"<<endl;}
    ~A(){cout<<"A destructor"<<endl;}
};


class B: public A
{
public:
    B(){cout<<"B constructor"<<endl;}
    ~B(){cout<<"B destructor"<<endl;}
};


class C: public B
{
public:
    C(){cout<<"C constructor"<<endl;}
    ~C(){cout<<"C destructor"<<endl;}
};


int main()
{
    C test;
    return 0;
}


定义了三个类,C类继承自B类,B类继承自A类。在每个类中定义默认构造函数和析构函数。在主函数中我们定义了C类的一个对象,创建对象时各个类的构造函数会被调用,之后退出程序,各类的析构函数会被逐一调用。程序运行结果如下:
A constructor
B constructor
C constructor
C destructor
B destructor
A destructor

程序运行结果很好地说明了构造函数和析构函数的执行顺序。构造函数的执行顺序是按照继承顺序自顶向下的,从基类到派生类,而析构函数的执行顺序是按照继承顺序自下向上,从派生类到基类。


每一个类中最多只能有一个析构函数,因此调用的时候并不会出现二义性,因此析构函数不需要显式的调用


前面所有的例子中,派生类都只有一个基类,我们成这种情况为单继承。而在C++中一个派生类中允许有两个及以上的基类,我们称这种情况为多继承。单继承中派生类是对基类的特例化,例如前面中编程类书籍是书籍中的特例。而多继承中,派生类是所有基类的一种组合。


多继承可以描述事物之间的组合关系,但是如此一来也可能会增加命名冲突的可能性,冲突可能很有可能发生在基类与基类之间,基类与派生类之间。命名冲突是必须要解决的问题


#include <iostream>
using namespace std;

class A
{
public:
    void setx(int a){x = a;}
    int getx(){return x;}
private:
    int x;
};

class B
{
public:
    void setx(int a){x = a;}
    int getx(){return x;}
private:
    int x;
};

class C: public A, public B
{
public:
    void setx(int a){x = a;}
    int getx(){return x;}
private:
    int x;
};

int main()
{
    C test;
    test.setx(10);
    test.B::setx(20);
    test.A::setx(30);
    return 0;
}


为了说明命名冲突问题。我们来看一下例子,在本例中有三个类A、B和C,其中C类继承自类A和类B。在三个类中我们都有一个成员变量,变量名恰好都为x,然后成员函数都名为setx和getx。由于两个基类和派生类中出现了命名冲突,因此产生了遮蔽的情况。为了解决命名冲突问题我们只能采用域解析操作符来区分具体所调用的类中的成员函数。


多继承时很容易产生命名冲突问题,如果我们很小心地将所有类中的成员变量及成员函数都命名为不同的名字时,命名冲突依然有可能发生,比如非常经典的菱形继承层次。类A派生出类B和类C,类D继承自类B和类C,这个时候类A中的成员变量和成员函数继承到类D中变成了两份,一份来自A派生B然后派生D这一路,另一份来自A派生C然后派生D这一条路。




class A
{
public:
    void setx(int a){x = a;}
    int getx(){return x;}
private:
    int x;
};

class B: public A
{
public:
    void sety(int a){y = a;}
    int gety(){return y;}
private:
    int y;   
};

class C: public A
{
public:
    void setz(int a){z = a;}
    int getz(){return z;}
private:
    int z;
};

class D: public B, public C
{
    //......
};


类A中的成员变量及成员函数继承到类D中均会产生两份,这样的命名冲突非常的棘手,通过域解析操作符已经无法分清具体的变量了。为此,C++提供了虚继承这一方式解决命名冲突问题。虚继承只需要在继承属性前加上virtual关键字。


#include <iostream>
using namespace std;

class A
{
public:
    void setx(int a){x = a;}
    int getx(){return x;}
private:
    int x;
};

class B: virtual public A
{
public:
    void sety(int a){y = a;}
    int gety(){return y;}
private:
    int y;   
};

class C: virtual public A
{
public:
    void setz(int a){z = a;}
    int getz(){return z;}
private:
    int z;
};

class D: public B, public C
{
    //......
};

int main()
{
    D test;
    test.setx(10);
    cout<<test.getx()<<endl;
    return 0;
}

类B和类C都是继承类A都是虚继承,如此操作之后,类D只会得到一份来自类A的数据。在本例的主函数中,定义了类D的对象test,然后通过该对象调用从类A间接继承来的setx和getx成员函数,因为B和C继承自类A采用的是虚继承,故通过D调用setx和getx不会有命名冲突问题,因为D类只得到了一份A的数据




  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值