继承与派生

继承与派生

类之间的关系:使用、包含、继承
使用:一个类使用了另一个类的对象,友元类、
包含:一个类中有另一个类的对象,例如,圆类 和 点类
继承:一个类是另一个类的特殊实例

继承是使用已经编写好的类来创建新类,新的类具有原有类的所有属性和操作,也可以在原有的基础上进行增补。
通常我们将新类称为派生类(子类),将被继承的类称为基类(父类);

语法:

class 派生类: 继承权限 基类
{
};

继承权限有三种:public、protected、private

这里写图片描述
说明:
1.派生类中的 public 属性,在派生类的内部与外部都可以访问;
2.派生类中的 protected 属性,在派生类的内部可以访问,在外部无法访问;
3.派生类初次以 private 继承时,基类的public 和 protected 虽然都变成了 private,但依旧可以在类的内部进行访问,在类的外部不可访问。如果派生类又作为另一个类的基类,无论是以什么样的权限继承,都无法进行访问!
4.基类中的 private 成员无论以什么样的权限继承,都无法使用,但并不能认为没有从基类中继承过来。

举个动物类的例子,形象的说明:

#include <iostream>

using namespace std;

class Animal()
{
public: //基类有四个公有函数,init,sleep,eat,print;
    void init(int age, char *name)
    {
        this->age = age;
        this->name = name;
    }

    void eat()
    {
        cout << "动物: " << name << "在吃饭" << endl;    
    }

    void sleep()
    {
        cout << "动物: " << name << "在睡觉" << endl;
    }

    void print()
    {
        printf("age = %d , name  = %s\n", age, name);
    }
private:
    int age;
    char *name;
};

//公有继承时,基类中的四个函数都可被派生类对象直接调用
//派生类也可以实现自己的功能,例如,猫 捉老鼠
class Cat: public Animal 
{
public:
    void CatchMouse()
    {
        printf("猫 在捉老鼠\n");
    }
};

class Dog: public Animal
{
public:
    void LookHouse()
    {
        printf("狗 在看家\n");
    }
};

int main()
{
    Animal a;
    a.init(10, "动物");
    a.sleep();
    a.eat();
    a.print();

    Cat c;
    c.init(12, "蓝猫");
    c.sleep();
    c.eat();
    c.print();
    c.CatchMouse();

    Dog d;
    d.init(13, "哈士奇");
    d.sleep();
    d.eat();
    d.print();
    d.LookHouse();

    return 0;
}
//这个例子中,我们称 Aminal 为基类,Cat 和 Dog 为派生类

类型兼容性原则

类型兼容规则是指在需要基类对象的任何地方,都可以使用公有派生类的对象来替代。通过公有继承,派生类得到了基类中除构造函数、析构函数之外的所有成员。这样,公有派生类实际就具备了基类的所有功能,凡是基类能解决的问题,公有派生类都可以解决。

首先我们定义两个类:Parent 和 Child 类,Child 公有继承 Parent

//基类有两个私有成员 a 和 b,两个公有函数 setAB、printAB
class Parent
{
public:
    void setAB(int a, int b)
    {
        this->a = a;
        this->b = b;
    }
    void printAB() const 
    {
        cout << "a = " << a << ", b = " << b << endl;
    }
private:
    int a;
    int b;
};

//派生类有自己的成员 c,自己的函数 setC、printC
class Child :public Parent
{
public:
    void setC(int c)
    {
        this->c = c;
    }

    void printC()
    {
        cout << "c = " << c << endl;
    }
private:
    int c;
};

1.派生类对象可当做基类对象直接使用
理由:派生类对象只是在基类对象的基础上,添加自己特有的成员

int main()
{
    Child p
    p.setAB(1, 2);
    p.printAB();   //a = 1, b = 2

    return 0;
}

2.基类指针可以指向派生类对象

int main()
{
    Child *c = new Child;
    c->setAB(1, 2);
    c->SetC(3);

    Parent *p;
    p = c;
    p->printAB();  // a = 1, b = 2

    //基类的指针指向派生类的对象,但基类指针只可使用基类中有的方法和成员,派生类中特有的,无法访问
    //p->printC();

    return 0;
}

3.基类引用可以直接引用派生类对象
原因:引用的本质 ==> 指针

void test(Parent *p)
{
    p->printAB();  // printAB(p)
}

void test(const Parent &p)
{
    p.printAB();  // printAB(p)
}

int main()
{
    Parent p;
    p.setAB(1,2);
    // test(&p);
    test(p);

    Child c;
    c.setAB(5,6);
    c.setC(9);
    // test(&c);
    test(c); //调用函数时,&p = c ⇒ Parent *const p = &c

    Parent &p1 = c; 
    p1.setAB(7, 8);
    p1.printAB(); // a = 7, b = 8

    return 0;
}

4.派生类对象可直接初始化基类对象

int main()
{
    Child c;
    c.setAB(1, 2);
    c.setC(3);

    //派生类对象是一种特殊的基类对象,因此在初始化时,会发生拷贝构造
    Parent p = c; //Parent(c) ⇒ Parent(const Parent &obj) ⇒ Parent(&c) 
                  //Parent(&c) ⇒ Parent(const Parent *const obj)

    return 0;
}

5.派生类对象可直接对基类对象赋值

int main()
{
    Child c;
    c.setAB(1, 2);
    c.setC(3);

    Parent p;
    //Parent &operator=(const Parent &obj)
    p = c;

    return 0;
}

继承中的构造和析构

派生类的组成:基类继承过来的成员 + 派生类自己定义的
原则:谁的成员,谁去初始化
对于基类的成员,在对象初始化列表中,显示调用基类的构造函数

构造函数执行的顺序:1.基类的构造函数、2.组合对象的构造函数、3.派生类自己的构造函数
析构函数执行的顺序与 构造函数 相反。

#include <iostream>

using namespace std;

class Obj
{
public:
    Obj(int a, int b)
    {
        this->a = a;
        this->b = b;

        cout << "调用了Obj的构造函数" << endl;
    }
    ~Obj()
    {
        cout << "调用了Obj的析构函数" << endl;
    }
private:
    int a;
    int b;
};

class Parent
{
public:
    Parent(int a, int b)
    {
        this->a = a;
        this->b = b;

        cout << "调用了Parent的构造函数" << endl;
    }
    ~Parent()
    {
        cout << "调用了Parent的析构函数" << endl;
    }
private:
    int a;
    int b;
};

class Child:public Parent
{
public:
    Child(int c) :Parent(1, 2),o1(2,3),o2(3,4)
    {
        this->c = c;
        cout << "调用了Child的构造函数" << endl;
    }
    ~Child()
    {
        cout << "调用了Child的析构函数" << endl;
    }

private:
    int c;
    Obj o1;
    Obj o2;
};

int main()
{
    Child c(1);

    return 0;
}

执行结果如下:
调用了Parent的构造函数
调用了Obj的构造函数
调用了Obj的构造函数
调用了Child的构造函数
调用了Child的析构函数
调用了Obj的析构函数
调用了Obj的析构函数
调用了Parent的析构函数

继承中的同名成员和同名函数

当基类成员变量(函数)和派生类成员变量(函数)重名时,默认使用派生类自己的成员(函数),如若想使用基类中的,使用域解析符 :: 即可。

#include <iostream>

using namespace std;

class AA
{
public:
    void print()
    {
        printf("A 的成员函数\n");
    }
public:
    int a;
    int b;
};

class BB: public AA
{
public:
    void print()
    {
        printf("B 的成员函数\n");
    }
public:
    int a;
    int c;
};

int main()
{
    BB b;
    b.b = 2;
    b.c = 3;
    b.a = 1;        //默认使用派生类的成员
    b.AA::a = 4;    //使用域解析符,指明所属的类;
    b.print();      //B 的成员函数
    b.AA::print();  //A 的成员函数

    return 0;
}

继承中的重载问题

重载只能发生在一个类之中, 派生类不能重载基类的同名函数
当派生类重载基类的函数的时候,会将基类所有同名函数全屏蔽掉,不能再使用

重定义:派生类有和父类 成员函数原型一样的函数,叫函数重定义

#include <iostream>

using namespace std;


class Parent
{
public:
    void  print()
    {
        printf("a = %d, b = %d\n", a, b);
    }

    void func()
    {
        printf("基类: 无参\n");
    }

    void func(int a)
    {
        printf("基类: 有一个参数\n");
    }
public:
    int a;
    int b;
};

class Child : public Parent
{
public:
    // 派生类的同名成员函数会屏蔽基类的同名成员函数
    // 函数原型一样, 叫函数重定义
    void print()
    {
        printf("a = %d, c = %d\n", a, c);
    }

    void func(int a, int b)
    {
        printf("派生类: 有两个参数\n");
    }
public:
    int a;
    int c;
};

int main()
{
    Child c;
    c.Parent::a = 1;
    c.b = 2;
    c.a = 3;
    c.c = 4;

    // 默认使用派生类的函数
    c.print();

    c.Parent::print();

    c.Parent::func();
    //c.func(10);
    c.func(1, 2);

    return 0;
}

派生类中static关键字使用

#include <iostream>

using namespace std;

class A
{
public:
    int a;
    static int sa;
};

int A::sa = 10;

class B :public A
{
public:
    int b;
};

class C:public A
{
public:
    int c;
};

// 类的静态变量是所有派生类所共享的
// 静态变量存放在数据区,测算类的大小时,不包括静态变量
int main()
{
    cout << "sizeof B : " << sizeof B << endl; // sizeof B : 8
    B  b;
    b.sa = 123;

    cout << "C::sa = " << C::sa << endl; // C::sa = 123

    return 0;
}

多继承

当一个派生类有多个基类时,构造函数的执行顺序与继承时的声明顺序有关。
析构函数的执行顺序与 构造函数的执行顺序 相反

#include <iostream> 

using namespace std;

class AA
{
public:
    AA(int a, int b)
    {
        this->a = a;
        this->b = b;
        printf("AA 的构造函数被调用\n");
    }
    ~AA()
    {
        printf("AA 的析构函数被调用\n");
    }

    void printa()
    {
        printf("a = %d, b = %d\n", a, b);
    }
private:
    int a;
    int b;
};

class BB
{
public:
    BB(int a, int b)
    {
        this->c = a;
        this->d = b;
        printf("BB 的构造函数被调用\n");
    }
    ~BB()
    {
        printf("BB 的析构函数被调用\n");
    }
    void printb()
    {
        printf("c = %d, d = %d\n", c, d);
    }
private:
    int c;
    int d;

};

class CC :public AA, public BB
{
public:
    CC(int a) :AA(1, 2), BB(3, 4)
    {
        this->e = a;
        printf("CC 的构造函数被调用\n");
    }
    ~CC()
    {
        printf("CC 的析构函数被调用\n");
    }
    void printc()
    {
        printf("e = %d\n", e);
    }
private:
    int e;
};

void func1(AA &obj)
{
    obj.printa();
}

void func2(BB &obj)
{
    obj.printb();
}

int main()
{
    CC c(5);

    // 基类指针指向派生类对象的时候,指针会根据基类数据成员在派生类中的
    // 存储位置不同,做不同的偏移 
    func1(c);
    func2(c);

    AA *p = &c;
    BB *p1 = &c;
    printf("AA  %p\n", p);
    printf("BB  %p\n", p1);
    printf("&c  %p\n", &c);

    return 0;
}

执行结果如下:
AA 的构造函数被调用
BB 的构造函数被调用
CC 的构造函数被调用
a = 1, b = 2
c = 3, d = 4
AA  0115FDC0
BB  0115FDC8
&c  0115FDC0
CC 的析构函数被调用
BB 的析构函数被调用
AA 的析构函数被调用

内存模型:
这里写图片描述


多继承的二义性

1.普通继承时

#include <iostream>

using namespace std;

class Parent1
{
public:
    Parent1(int a, int b)
    {
        this->a = a;
        this->b = b;
        cout << "Parent1 构造函数被调用" << endl;
    }

    void  print1()
    {
        printf("a = %d, b = %d\n", a, b);
    }

public:
    int a;
    int b;
};

class Parent2
{
public:
    Parent2(int a, int d)
    {
        this->a = a;
        this->d = d;
        cout << "Parent2 构造函数被调用" << endl;
    }

    void  print2()
    {
        printf("c = %d, d = %d\n", a, d);
    }

public:
    int a;
    int d;
};

class C :public Parent1, public Parent2
{
public:
    C(int e) :Parent1(1, 2), Parent2(3, 4)
    {
        this->e = e;
        cout << "C 构造函数被调用" << endl;
    }
    void  printc()
    {
        printf("e = %d\n", e);
    }
private:
    int e;
};

int main()
{
    C c(10);

    printf("sizeof c = %d\n", sizeof(c));
    c.Parent1::a = 10;
    c.Parent2::a = 12;
    //c.a = 13;  编译器并不知道该调用哪一个基类的 a 
    c.print1();
    c.print2();
    c.printc();

    return 0;
}

运行结果如下:
Parent1 构造函数被调用
Parent2 构造函数被调用
C 构造函数被调用
sizeof c = 20
a = 10, b = 2
c = 12, d = 4
e = 10

内存模型:
这里写图片描述
2.虚继承
如果一个派生类从多个基类派生,而这些基类又有一个共同的基类,则在对该基类中声明的名字进行访问时,可能产生二义性,要使这个公共基类在派生类中只产生一个子对象,必须对这个基类声明为虚继承,使这个基类成为虚基类。

这里举个钻石型结构的例子

#include <iostream>

using namespace std;

class Parent
{
public:
    Parent(int a = 0)
    {
        this->a = a;
        cout << "Parent  构造函数被调用" << endl;
    }
public:
    int a;
};

class Parent1 :virtual public Parent
{
public:
    Parent1(int b) :Parent(1)
    {
        this->b = b;
        cout << "Parent1 构造函数被调用" << endl;
    }

    void  print1()
    {
        printf("b = %d\n", b);
        cout << "Parent1  构造函数被调用" << endl;
    }

public:
    int b;
};

class Parent2 :virtual public Parent
{
public:
    Parent2(int b) :Parent(3)
    {
        this->d = b;
        cout << "Parent2 构造函数被调用" << endl;
    }

    void  print2()
    {
        printf("d = %d\n", d);
    }

public:
    int d;
};

class C :public Parent1, public Parent2
{
public:
    C(int e) :Parent1(2), Parent2(4)
    {
        this->e = e;
        cout << "C 构造函数被调用" << endl;
    }
    void  printc()
    {
        printf("e = %d\n", e);
    }
private:
    int e;
};

int main()
{
    C c(10);

    printf("sizeof c = %d\n", sizeof(c));
    c.Parent1::a = 11;
    printf("a = %d\n", c.a);
    c.Parent2::a = 12;
    printf("a = %d\n", c.a);
    c.a = 13;
    printf("a = %d\n", c.a);

    return 0;
}

运行结果:
Parent  构造函数被调用
Parent1 构造函数被调用
Parent2 构造函数被调用
C 构造函数被调用
sizeof c = 24
a = 11
a = 12
a = 13

内存模型:
这里写图片描述

这里写图片描述

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值