C++继承和多态

前言

        继承和多态发生在类里面,继承就相当于一个类能够继承另一个类的函数和成员。继承的类被称为子类,被继承的类被称为父类。本文会简单的举例一些继承的使用和介绍一些继承的多种因素,各种注意事项之类的内容。

        另外,这里是根据我自己的理解做的一些讲述,一些部分可能不全面。这时请参考其他博主的博客。

一、什么是继承

        继承,会保持原有特性的基础上进行扩展,增加成员函数,成员变量。例如:水果是在植物这个类里面的,所以水果包含植物的特性。这时水果被称为子类也称为派生类,而植物被称为父类也称为基类。

        在C++中继承分为三种方式public继承、protect继承、private继承。对应的父类中三种不同的成员有不同的继承效果。具体效果如下所示:

        其中基类的private成员在派生类中都是不可见的,public继承会继承所有相对应的属性。

二、继承的写法

        继承的写法需要给明继承的方式以及继承的对象,写在派生类对象的分号后面:

class Person
{
protected:
    string _name;
};

class Student : public Person
{
protected:
    string _id;
}

        例如上图,Student就public继承了Person。当然继承方式不写会有默认的继承方式,class的默认继承方式是private继承,struct的默认继承方式是public继承。

        也可以增加模版继承,不过如果需要调用末班中的函数就需要声明清楚模版是什么,例如:

template<class T>
class Stack : public vector<T>
{
public:
    void push(const T& x)
    {
        vector<T>::push_back(x); // 显示调用
    }

    size_t size()
    {
        return vector<T>::size(); // 显示调用
    }
};


int main()
{
    Stack<int> st;
    st.push(1);
    st.push(2);
    st.push(3);
    st.push(4);
    cout << st.size() << endl;
    return 0;
}

        这种也就是模版的按需实例化,如果不写vector<T>::指定,那么编译会报错。

        在继承中有以下特性:

        1、子类对象可以复制给父类对象/指针/引用(因为是有的时候能对子类进行切割找到父类)

        2、父类对象不能复制给子类对象。指针和引用可以,但是需要原来的指针和引用指向的是子类对象。如果需要判断父类的指针/引用是否指向子类,可以使用dynamic_const()函数。

三、继承中的函数

        由于基类和派生类是继承关系,所以基类的成员在派生类其他成员的前面。

1、构造函数

        构造函数如果不显示调用,子类会自动调用父类的默认构造,如果没有默认构造,就需要主动显示调用,构造函数会优先调用父类的构造函数:

class Person
{
public:
    Person(const int& id)
        :_id(id)
    {}

protected:
    int _id;
};

class Student : public Person
{
public:
    Student(const char* name, const int id = 1)
        :Person(id)
        ,_name(name)
    {}

    void Print()
    {
        cout << "name:" << _name << endl;
        cout << "id:" << _id << endl;
    }

protected:
    string _name;
};


int main()
{
    Student s1("张三");
    s1.Print();
    return 0;
}

2、拷贝构造

        如果没有写会调用默认构造,一般来说默认的拷贝构造已经够用了,除非需要深拷贝。

        总体来说,拷贝构造和构造函数差不多,这里我觉得也不需要过多举例了。

3、赋值重载

        子类的赋值重载“operator=”需要显示调用父类的“operator=”,因为构成了隐藏。隐藏是什么会在下一节中提到,这里先声明一下有这个东西。具体使用方法:

class Person
{
public:
    Person(const int& id)
        :_id(id)
    {}

    Person& operator=(Person man)
    {
        _id = man._id;
        return *this;
    }

    void Print()
    {
        cout << "id:" << _id << endl;
    }

protected:
    int _id;
};

class Student : public Person
{
public:
    Student(const char* name, const int id = 1)
        :Person(id)
        ,_name(name)
    {}

    void Print()
    {
        cout << "name:" << _name << endl;
        Person::Print();
    }

    Student& operator=(Student student)
    {
        Person::operator=(student);
        _name = student._name;
        return *this;
    }

protected:
    string _name;
};


int main()
{
    Student s1("张三");
    Student s2("古月方源", 99999);
    s1 = s2;
    s1.Print();
    return 0;
}

        这里的赋值重载其实也可以算是拷贝构造的例子,因为在赋值的时候同时调用了默认的拷贝构造。而在例子中也间接的可以提现出函数影藏的效果,在调用Student的“=”时,重载的函数中需要指定内域去访问Person的“=”。

4、析构函数

        析构函数也是默认的足够了,只有需要手动释放资源得到时候才需要自己实现,子类中的内容析构完成会自动调用父类的析构,这个顺序是为了确保释放资源时的安全性。需要注意的是,析构函数虽然看起来名字不一样,但是实际上是一样的,如果手动调用析构函数的话需要注明内域。

class Person
{
public:
    Person(const int& id)
        :_id(id)
    {}

    Person& operator=(Person man)
    {
        _id = man._id;
        return *this;
    }

    void Print()
    {
        cout << "id:" << _id << endl;
    }

    ~Person()
    {
        cout << "~Person()" << endl;
    }

protected:
    int _id;
};

class Student : public Person
{
public:
    Student(const char* name, const int id = 1)
        :Person(id)
        ,_name(name)
    {}

    void Print()
    {
        cout << "name:" << _name << endl;
        Person::Print();
    }

    Student& operator=(Student student)
    {
        Person::operator=(student);
        _name = student._name;
        return *this;
    }

    ~Student()
    {
        cout << "~Student()" << endl;
    }

protected:
    string _name;
};


int main()
{
    Student s1("张三");
    Person p1(0);
    return 0;
}

        这里p1后创建先析构,然后析构s1的子类,最后是父类。

四、继承关系

1、不能被继承

        如果希望类不被继承可以增加关键字final在类的后面:

class bot final
{
protected:
    int _level = 0;
};

class hard_bot : bot
{
protected:
    int _number;
};

int main()
{
    return 0;
}

        这样bot就不能被继承了。

        注意final关键字只能在C++11之后的版本使用。

2、友元的继承

        友元函数不会被继承,父类的友元函数不是子类的友元函数,无法访问子类的成员。如果想要访问子类的成员,需要在子类中也添加友缘声明。如下会出错:

class bot
{
    friend void func();
protected:
    int _level = 0;
};

class hard_bot : public bot
{
protected:
    int _number = 5;
};

void func()
{
    bot b1;
    hard_bot b2;

    cout << b1._level << endl;
    cout << b2._number << endl;
}

int main()
{
    func();
    return 0;
}

        所以需要增加子类的友元声明。

class bot
{
    friend void func();
protected:
    int _level = 0;
};

class hard_bot : public bot
{
    friend void func();
protected:
    int _number = 5;
};

void func()
{
    bot b1;
    hard_bot b2;

    cout << b1._level << endl;
    cout << b2._number << endl;
}

int main()
{
    func();
    return 0;
}

3、静态成员

        静态成员存在于类中,每个类的对象都可以使用,所以只有一个。即使在继承之后也不会增加,仍然只有一个。

class bot
{
public:
    void func()
    {
        _level++;
    }

    void print()
    {
        cout << _level << endl;
    }

protected:
    static int _level;
};

int bot::_level = 0;

class hard_bot : public bot
{
protected:
    int _number = 5;
};

int main()
{
    bot b1;
    hard_bot b2;
    hard_bot b3;

    b1.func();
    b1.print();
    b2.print();
    b3.print();
    return 0;
}

五、继承模型

1、单继承

        只有一个直接父类,这是称为单继承,以上举例均是单继承。如图所示,也可以理解为C继承A,D继承B。

2、多继承

        有两个或是以上的父类,这时称为多继承。如图所示,可以理解为E同时继承了C和D。也可以如下面代码,番茄是水果也是蔬菜。

class vegetable
{
protected:
    string _name;
};

class fruits
{
protected:
    string _name;
};

class tomato : public vegetable, public fruits
{
protected:
    string _id;
};

3、菱形继承(不推荐使用)

        如下图所示,菱形继承的关系就是父类友共同的父类。

        这样子继承在D中会有两份A,所以所占空间会很大。为了消除菱形继承的这种空间占用,C++有虚拟继承。在B和C的那里虚拟继承A,这样D中的A只会有一份。

class A
{
private:
    string _a;
};

class B : public A
{
private:
    string _b;
};

class C : public A
{
private:
    string _c;
};

class D : public B, public C
{
private:
    string _d;
};

class _B : virtual public A
{
private:
    string _b;
};

class _C : virtual public A
{
private:
    string _c;
};

class _D : public _B, public _C
{
private:
    string _d;
};

int main()
{
    cout << "sizeof(D)=" << sizeof(D) << endl;
    cout << "sizeof(_D)=" << sizeof(_D) << endl;
    return 0;
}

六、其他

1、多继承和父类指针

class B
{
private:
    string _b;
};

class C
{
private:
    string _c;
};

class D : public B, public C
{
private:
    string _d;
};


int main()
{
    D d;
    B* p1 = &d;
    C* p2 = &d;
    D* p3 = &d;
    cout << "p1 = "<< p1 << endl;
    cout << "p2 = "<< p2 << endl;
    cout << "p3 = "<< p3 << endl;
    return 0;
}

        由以上代码不难看出继承之间的空间关系,D先继承B然后是C,所以B中的成员在内存空间最前面,然后是C和D。另外在提取指针的时候p2会自动偏移到存放C内容的地方。这些关系都是计算机设计者安排的特性。

2、继承和组合

        组合就是直接嵌套,他和继承的关系如下:

template<calss T>
class stack1 : public vector<T>
{
protected:    
    int _size;
};

class stack2
{
protected:    
    vector<int> _v;
    int _size;
};

        他们所占的空间都是相同的,但是组合的方式更加多变,一般也更为方便。这主要是提现在接口方面。stack2能更好的适应接口,而继承的话接口就多了有不便之处。

结语

        本来在开始写博客的时候想好了怎么写结语的,但是写到最后的时候发现自己忘记了写啥。随便扯几句也不会有人发现吧。

        关于继承其实能讲的不算多,主要是理论为主,举例为辅。内容也比较短,这不是说继承简单,而是说虚继承的部分底层太难讲,感觉讲不明白。简单的来说虚继承就是最后改成了用指针继承,如果继承了相同的类会识别出来,不创建更多的空间。

        扩展内容也不是没有,不过感觉用处不大,本文也就不多做描述了。

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值