C++之继承

1、代码重用

组合:就是将一个类A作为另一个类B的对象成员,以此能用到另一个类的成员函数。(前提是类B需要用到类A中的某个功能,但是如果再在类B中写,这样代码就重复了,且要维护两份相同的代码)

2、继承

派生类表示的事务范围,要比基类的范围要小的多。派生类更加具体。

3、继承的语法

4、共有、私有、保护成员

主要取决于继承后变成什么样的属性,protected和private的区别主要是看你想不想让派生类里面能不能直接访问,也就是说这个成员要不要暴露给派生类,如果想要暴露的话,且不想在类外访问,那就声明为protected;如果不想暴露的话,那就private。

#include <iostream>
using namespace std;

class Base
{
public:
    int x_;
protected:
    int y_;
private:
    int z_;
};

class PublicInherit : public Base
{
public:
    void Test()
    {
        x_ = 10;
        y_ = 20;
        //z_ = 30;  //派生类不能访问基类中的privated成员,只能在Base类中访问
    }
private:
    int a_;
};
int main() {
    Base b;
    b.x_ = 20;
    //b.y_ = 22;  // 类外不能访问到protected成员
    return 0;
}

5、默认继承保护级别

6、接口继承与实现继承

接口继承(公有),也就是想把这个接口(或者重写后)再暴露出来;实现继承(私有和保护),也就是想要在类中直接调用,不暴露出来。

7、继承与重定义

overload:重载,只在作用域中相同的情况下

overwrite:重写或者重定义:针对于类的继承,函数名相同(参数相不相同都是重写,会隐藏基类中的函数)

override:覆盖:必须要有virtual虚函数才可以(后续讲到多态再讲解)

#include <iostream>
using namespace std;

class Base
{
public:
    Base() : x_(0)
    {

    }

    int GetBaseX() const
    {
        return x_;
    }

    void show()
    {
        cout << "call Base::show" << endl;
    }

    int x_;
};

class Derived : public Base
{
public:
    Derived() : x_(0)
    {

    }

    int GetDerivedX() const
    {
        return x_;
    }

    void show(int n)
    {
        cout << "call Derived::show n" << endl;
    }

    void show()
    {
        cout << "call Derived::show" << endl;
    }
    int x_;  // 如果有重新定义基类的数据成员,那么基类的数据就会被隐藏
};

int main() {
    Derived d;
    d.x_ = 20;
    cout << d.GetBaseX() << endl;
    cout << d.GetDerivedX() << endl;

    d.show();  // 对于子类来说,show这个接口已经被重定义成show(int n),所以这样调用会报错,相当于子类没有这个函数,所以子类中需要加上show()

    // 继承关系中,不存在重载,只有重写或者覆盖。因为重载只在作用域相同的情况下才行(就像Derived中的两个show)
    // 如果子类对象想要调用被隐藏的数据,那就这样调用
    d.Base::x_ = 30;
    d.Base::show();
    return 0;
}

8、继承与组合

9、不能自动继承的成员函数

10、继承与构造函数

必须使用构造函数初始化列表初始化的有如下:

        1、const成员:因为是常量,所以必须一开始就初始化。为什么不能在构造函数体里面初始化?因为如果在构造函数体里面初始化,本质上不叫初始化,叫做赋值。

        2、引用成员:因为引用在定义的时候,也必须直接初始化。如果在构造函数体中初始化,理由如上。

        3、类的对象成员没有默认构造函数的时候:只能在类的构造函数初始化列表中调用该对象的构造函数进程初始化

派生类对象构造次序:

先调用基类的对象成员的构造函数、然后再调用基类构造函数、再然后是派生类的对象成员的构造函数、最终是派生类自身的构造函数(总的来说,构造一个类,先对象成员,再构造函数)

#include <iostream>
using namespace std;

class ObjectB
{
public:
    ObjectB(int objb) : objb_(objb)
    {
        cout << "ObjectB ..." << endl;
    }
    ~ObjectB()
    {
        cout << "~ObjectB ... " << endl;
    }
    int objb_;
};
class ObjectD
{
public:
    ObjectD(int objd) : objd_(objd)
    {
        cout << "ObjectD ..." << endl;
    }
    ~ObjectD()
    {
        cout << "~ObjectD ... " << endl;
    }
    int objd_;
};

class Base
{
public:
    Base(int b) : b_(b), objb_(111)
    {
        cout << "Base ..." << endl;
    }
    // 拷贝构造
    Base(const Base& other) : objb_(other.objb_), b_(other.b_)
    {

    }
    ~Base()
    {
        cout << "~Base ... " << endl;
    }

    int b_;
    ObjectB objb_;  // 这边要调用ObjectB的默认构造函数,如果没有编译失败。要么增加默认构造函数,要么在构造函数初始化列表中初始化
};

class Derived : public Base
{
public:
    Derived(int d, int b) : d_(d), Base(b), objd_(222)   // 这边会调用基类的默认构造函数,但是此时没有默认的构造函数,所以会报错;所以这边要加上Base(10)去调用指定的构造函数
    {
        cout << "Derived ..." << endl;
    }

    // 拷贝构造
    Derived(const Derived& other) : d_(other.d_), objd_(other.objd_), Base(other)  // 因为这是拷贝构造,而且又是子类,所以必须把基类也一起拷贝,也就是Base(other),这个很经常遗漏!
    {

    }
    ~Derived()
    {
        cout << "~Dericed ..." << endl;
    }
    int d_;
    ObjectD objd_;
};

int main() {
    Derived d(100, 200);
    cout << d.b_ << " " << d.d_ << endl;

    Base b1(100);
    Base b2(b1);
    cout << b2.b_ << endl;

    Derived d2(d);
    return 0;
}

// 输出
ObjectB ...
Base ...
ObjectD ...
Derived ...
200 100
ObjectB ...
Base ...
100
~Dericed ...
~ObjectD ...
~Base ...
~ObjectB ...
~Base ...
~ObjectB ...
~Base ...
~ObjectB ...
~Dericed ...
~ObjectD ...
~Base ...
~ObjectB ...

11、友元关系与继承

12、静态成员与继承、

静态成员无所谓继承,因为静态成员是共享的,在内存中只有一份。

#include <iostream>
using namespace std;

class Base
{
public:

    static int b_;
};

int Base::b_ = 100;

class Derived : public Base
{

};

int main() {
    Base b;
    cout << Base::b_ << endl;
    cout << b.b_ << endl;  // 不推荐用对象进行访问,会让人误以为是普通成员函数

    Derived d;
    cout << Derived::b_ << endl;
    cout << d.b_ << endl;
    return 0;
}

// 输出
100
100
100
100

13、转换与继承

14、派生类到基类的转换

static_cast:用于编译器认可的静态转换。比如说从char 到int。从double到int。或者具有转换构造函数。或者重载了类型转换运算符。

reinterpret_cast:用于编译器不认可的静态转换。从int* 转为int,且在转型的时候,不做任何对其。

const_cast:去除常量性

dynamic_cast:用于动态转换。安全的向下转型。常用于多态

#include <string>
#include <iostream>
using namespace std;

class Employee
{
public:
    Employee(const string& name, const int age, const int depno) : name_(name), age_(age), depno_(depno)
    {

    }

private:
    string name_;
    int age_;
    int depno_;
};

class Manager : public Employee
{
public:
    Manager(const string& name, const int age, const int depno, int level)
    : Employee(name, age, depno), level_(level)  // 这边必须要对基类进行初始化列表,指定调用哪个基类的构造函数,如果不指定的话,那就是调用默认构造函数,当前例子没有构造函数
    {

    }
private:
    int level_;
};

class Manager2 : private Employee
{
public:
    Manager2(const string& name, const int age, const int depno, int level)
            : Employee(name, age, depno), level_(level)  // 这边必须要对基类进行初始化列表,指定调用哪个基类的构造函数,如果不指定的话,那就是调用默认构造函数,当前例子没有构造函数
    {

    }
private:
    int level_;
};
int main() {
    // public继承举例:
    Employee e1("niuma", 25, 1);
    Manager m1("ziben", 40, 1, 5);

    Employee* pe;
    Manager* pm;


    pe = &e1;  // 基类指针指向基类对象
    pm = &m1;  // 派生类指针指向派生类对象

    // 就好比如,人是一个基类,教师是派生类,教师(派生类)是一个人(基类),但是,人(基类)不一定是一个教师
    pe = &m1;  // 派生类对象指针可以转化成基类对象指针。将派生类对象看成是基类对象
    //pm = &e1;  // 基类指针无法转化为派生类指针,无法将基类对象看成是派生类对象

    e1 = m1;   // 派生类对象可以转化成基类对象。将派生类对象看成是基类对象
                //会产生切割(派生类特有成员消失;level_消失)。object slicing

    // private继承举例:
    Manager2 m2("aaa", 35, 2, 3);
    Manager2* pm2;

    pm2 = &m2;  // 派生类指针指向派生类对象

    //pe = pm2;  // 私有或保护继承的时候,派生类对象指针不能自动转化为基类对象指针
    pe = reinterpret_cast<Employee*>(pm2);

    //e1 = m2;  // 派生类对象无法转化为基类对象
    // e1 = (Employee)m2;  // 即使是强制转化也不行
    return 0;
}

15、基类到派生类的转换

#include <string>
#include <iostream>
using namespace std;

class Manager;
class Employee
{
public:
    Employee(const string& name, const int age, const int depno) : name_(name), age_(age), depno_(depno)
    {

    }

    operator Manager();

private:
    string name_;
    int age_;
    int depno_;
};

class Manager : public Employee
{
public:
    Manager(const string& name, const int age, const int depno, int level)
    : Employee(name, age, depno), level_(level)  // 这边必须要对基类进行初始化列表,指定调用哪个基类的构造函数,如果不指定的话,那就是调用默认构造函数,当前例子没有构造函数
    {

    }
    //构造函数只有一个参数,也称为转换构造函数;将其他类型转化为Manager类型
    // 从语法上来演示基类对象可以转化为派生类对象,没有实际意义
    Manager(const Employee& other) : Employee(other), level_(-1)
    {

    }
private:
    int level_;
};

Employee::operator Manager() {
    return Manager(name_, age_, depno_, -1);
}

int main() {
    // public继承举例:
    Employee e1("niuma", 25, 1);
    Manager m1("ziben", 40, 1, 5);

    // 基类对象转换成派生类对象
    // 方式1:使用转换构造函数(需提供转换构造函数),将其他类型转化为类类型
    m1 = e1;

    // 方式2:重载类型转换运算符:将类类型转换为其他类型
    // 也就是在Employee中增加 operator Manager()

    return 0;
}

14、多重继承

#include <iostream>
using namespace std;

class Furniture
{
public:
    Furniture(int weight) : weight_(weight)
    {

    }
    int weight_;
};
class Bed : public Furniture
{
public:
    Bed(int weight) : Furniture(weight)
    {

    }
    void Sleep()
    {
        cout << "Sleep .. " << endl;
    }
};

class Sofa : public Furniture
{
public:
    Sofa(int weight) : Furniture(weight)
    {

    }
    void WatchTV()
    {
        cout << "Watch TV ..." << endl;
    }
};

class SofaBed : public Bed, public Sofa
{
public:
    SofaBed(int weight) : Bed(weight), Sofa(weight)
    {
        FoldIn();
    }
    void FoldOut()
    {
        cout << "FoldOut ..." << endl;
    }
    void FoldIn()
    {
        cout << "FoldIn ..." << endl;
    }
};
int main() {
    SofaBed sofabed(10);
    //sofabed.weight_ = 10;  // 不能这样访问,因为编译器没办法确定是哪个基类的weight_;只能用下面的方式访问重名的成员
    sofabed.Bed::weight_ = 10;
    sofabed.Sofa::weight_ = 20;

    // 就算是将weight_写到Furniture类中,但是由于SofaBed继承了两个类,所以SofaBed里面还是会有两个weight_;我们希望是只有一个weight_
    // 这个时候就只能用虚继承来解决
    return 0;
}

15、虚继承与虚基类

#include <iostream>
using namespace std;

class Furniture
{
public:
//    Furniture(int weight) : weight_(weight)
//    {
//
//    }
    int weight_;
};
class Bed : virtual public Furniture
{
public:
//    Bed(int weight) : Furniture(weight)
//    {
//
//    }
    void Sleep()
    {
        cout << "Sleep .. " << endl;
    }
};

class Sofa : virtual public Furniture
{
public:
//    Sofa(int weight) : Furniture(weight)
//    {
//
//    }
    void WatchTV()
    {
        cout << "Watch TV ..." << endl;
    }
};

class SofaBed : public Bed, public Sofa
{
public:
//    SofaBed(int weight) : Bed(weight), Sofa(weight)
//    {
//        FoldIn();
//    }
    void FoldOut()
    {
        cout << "FoldOut ..." << endl;
    }
    void FoldIn()
    {
        cout << "FoldIn ..." << endl;
    }
};
int main() {
    SofaBed sofabed;
    sofabed.weight_ = 10;

    return 0;
}

16、虚基类及其派生类构造函数

所有派生类,如果有虚继承一个基类,则所有的派生类中的构造函数的初始化列表中,必须对这个虚基类的构造函数初始化。

如果有Furniture(weight)对虚基类的构造函数进行初始化,则Bed和Sofa中的构造函数中的构造函数初始化列表中对虚基类的初始化Furniture(weight)就会被忽略,这样就变成了虚基类只被构造一次
#include <iostream>
using namespace std;

class Furniture
{
public:
    Furniture(int weight) : weight_(weight)
    {
        cout << "Furniture(int weight) : weight_(weight) ..." << endl;
    }
    ~Furniture()
    {
        cout << "~Furniture() ..." << endl;
    }
    int weight_;
};
class Bed : virtual public Furniture
{
public:
    Bed(int weight) : Furniture(weight)
    {
        cout << "Bed(int weight) ..." << endl;
    }
    void Sleep()
    {
        cout << "Sleep .. " << endl;
    }

    ~Bed()
    {
        cout << "~Bed() ..." << endl;
    }
};

class Sofa : virtual public Furniture
{
public:
    Sofa(int weight) : Furniture(weight)
    {
        cout << "Sofa(int weight) ..." << endl;
    }
    void WatchTV()
    {
        cout << "Watch TV ..." << endl;
    }
    ~Sofa()
    {
        cout << "~Sofa() ..." << endl;
    }
};

class SofaBed : public Bed, public Sofa
{
public:
    // 下面如果有Furniture(weight)对虚基类的构造函数进行初始化,则Bed和Sofa中的构造函数中的构造函数初始化列表中对虚基类的初始化Furniture(weight)就会被忽略,这样就变成了虚基类只被构造一次
    SofaBed(int weight) : Bed(weight), Sofa(weight), Furniture(weight)
    {
        cout << "SofaBed(int weight) : Bed(weight), Sofa(weight), Furniture(weight) ..." << endl;
        FoldIn();
    }
    void FoldOut()
    {
        cout << "FoldOut ..." << endl;
    }
    void FoldIn()
    {
        cout << "FoldIn ..." << endl;
    }
    ~SofaBed()
    {
        cout << "~SofaBed() ..." << endl;
    }
};
int main() {
    SofaBed sofabed(10);

    sofabed.WatchTV();
    sofabed.FoldOut();
    sofabed.Sleep();
    return 0;
}

// 输出
Furniture(int weight) : weight_(weight) ...
Bed(int weight) ...
Sofa(int weight) ...
SofaBed(int weight) : Bed(weight), Sofa(weight), Furniture(weight) ...
FoldIn ...
Watch TV ...
FoldOut ...
Sleep ..
~SofaBed() ...
~Sofa() ...
~Bed() ...
~Furniture() ...

17、虚继承对C++内存模型造成的影响

1、类/对象大小计算

2、虚基类表

当你一个类虚继承自另一个类的时候,这个类的对象的内存中就会多一个虚基类表指针,指向了一个虚基类表。这个虚基类表中存放两个值,该类地址与该对象地址的偏移量和该类地址与虚基类表指针的偏移量。用处在于通过偏移量来获取到基类的成员。

其实就是为了虚基类成员的共享,一般放在内存的末尾,所以需要记录偏移量。

比如这个例子:

B1中的vbptr指向的第一个值应该是B1类的地址和DD的对象d之间的偏移量,因为恰好d内存中的第一个位置是B1,所以偏移量是0

B1中的vbptr指向的第二个值应该是B类地址和BB的地址之间的偏移量。

这时候,d.B1::bb,其实是通过d的地址,先加上第一个偏移量,先找到B1的地址,然后再加上第二个偏移量,找到bb。

同理,B2中的vbptr存的第一个是DD的对象地址与B2内存的初始地址的偏移量,第二个是B2内存的初始地址与BB内存的初始地址的偏移量,这样B2才能索引到BB中的值。

d.B2::bb,就是d的地址加上第一个偏移量索引到B2的位置,然后再加上第二个偏移量,索引到bb。

#include <iostream>
using namespace std;

class BB
{
public:
    int bb_;
};

class B1 : virtual public BB
{
public:
    int b1_;
};

class B2 : virtual public BB
{
public:
    int b2_;
};

class DD : public B1, public B2
{
public:
    int dd_;
};

int main() {

    cout << sizeof(BB) << endl;
    cout << sizeof(B1) << endl;
    cout << sizeof(DD) << endl;

    B1 b1;
    long** p;

    cout << &b1 << endl;
    cout << &b1.b1_ << endl;
    cout << &b1.bb_ << endl;

    p = (long**)&b1;
    cout << p[0][0] << endl;
    cout << p[0][1] << endl;
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值