C++温习面向对象——3、继承

本文深入探讨C++中的继承与多态概念,包括公有、私有和保护继承的特点,虚函数与虚析构函数的作用,以及多重继承和菱形继承的解决方案。通过实例代码,详细解释了如何正确使用这些特性来实现代码重用和多态行为。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

写在前面

C++面向对象的思想,之前总结到,主要包括封装、抽象、继承和多态。
继承是完成多态的基础,也是C++中简化代码的神兵利器。

继承的基本概念

1、一个类A可以继承另一个类B,那么我们称类B为基类(父类),类A为派生类(子类)。
2、子类从父类继承了所有成员,除了构造函数、析构函数、赋值运算符重载函数。(还是要自己构造自己和“自杀”滴)。
3、子类的成员分为两部分: 继承,扩展
4、虽然父类的私有成员被子类继承,但子类不能直接访问父类的私有成员。(朋友可以用,儿子不可以哈哈哈,可以结合之前的友元类)可以通过继承自父类的公有成员函数来访问。
5、子类可以自己实现与父类成员函数原型相同(函数名,参数列表)的成员函数,重写或覆盖(overwrite)。
6、当通过父类对象调用被覆盖的函数时,父类版本的函数被调用,当通过子类对象调用覆盖父类的函数时,子类版本被调用。
7、在子类中调用父类中被覆盖的版本时,可以用子类对象名.父类名::函数名

注意区分复合类和继承,复合类是组合,类似结构体嵌套。

#include <iostream>
using namespace std;

class Base{
private:
    int b_number;
public:
    Base(int i);
    void print() const;
    int ger_number() const;

};

Base::Base(int i) : b_number (i) {}
int Base::ger_number() const {
    return b_number;
}
void Base::print() const {
    cout<<b_number<<endl;
}

class Derive : public Base{
private:
    int d_number;
public:
    Derive(int i,int j);
    void print() const ;
    int get_number() const;
};

Derive::Derive(int i,int j) :Base(i),d_number(j) {}
void Derive::print() const {
    cout<<d_number<<endl;
}
int Derive::get_number() const {
    return d_number;
}

int main(){
    Base B(9);
    B.print();
    Derive D(1,2);
    D.print();
    D.Base::print();//打印的是父类的值

    return 0;
}

打印如下
在这里插入图片描述
注意看,子类在实例化一个对象调用构造函数时,需要构造一个父类,在参数列表中调用父类的构造函数。和组合类相似。

保护成员protected

子类继承之后,依旧无法直接访问父类的私有成员,需要继承自父类的成员函数进行访问,如果父类有protected成员,子类就可以直接访问。
子类访问父类的私有成员的两种方式:
1、通过父类的公有成员函数
2、访问protected成员

继承的使用

使用情况:
1、两个类之间有自然的继承关系,即一个类是另一个类的特例
2、实现代码重用。一个类可能需要使用其他类中定义的成员,此时可以定义一个派生类继承自该类,这样就不必重复编写代码。

类之间的两种交互关系:
1、组合:一个类是另一个类的成员变量,一个类拥有另一个类
2、继承:一个类是另一个类的特例

虚函数

1、有了虚函数,无需向下转型,就可以正确的使用父类的指针和引用,调用子类的函数
2、虚函数的目的:期望父类指针、引用,不管指向父类还是子类,在调用override函数时,可以反映真实情况(派生类重写继承来的函数后,调用父类的成员函数的方法另一种,显式调用)
3、调用虚函数时,到底调用哪个版本,是根据指针或引用实际所指向的对象的类型来确定,而不是调用者本身类型来确定。指向父类就调用父类,子类就调用子类。

#include <iostream>

using namespace std;

class Thing{
public:
    Thing();
    ~Thing();
    virtual void print() const;
private:

};
Thing::Thing() {}
Thing::~Thing() {
    cout<<"Thing desstructor"<<endl;
}
void Thing::print() const{
    cout<<"I am a Thing"<<endl;
}

class Book : public Thing{
public:
    Book();
    ~Book();
    void print() const;
private:
};
Book::Book() {}
Book::~Book() {
    cout<<"Book destructor"<<endl;
}
void Book::print() const {
    cout<<"I am a Book"<<endl;
}

int main() {
    Thing t;
    Book b;
    t.print();b.print();
    //声明一个父类指针
    Thing* array[2];
    array[0]=&t;
    array[1]=&b;
    array[0]->print();
    array[1]->print();
    //析构时,子类先调用自己的析构函数,再调用父类的析构函数,目的是为了析构继承来的成员
    return 0;
}

虚函数实现了利用父类指针操作子类的方法,和多态结合将是C++面向对象的利器。如上所示,利用父类指针或引用,结合虚函数,可以明确调用的是父类还是子类的方法。在子类进行析构时,会先调用子类的析构函数,再调用父类的析构函数,释放继承得到的成员。

虚析构函数

如果一个类有一个子类,则这个类(父类)的析构函数必然是虚函数,即虚析构。
如果父类的析构不是虚函数,则要删除一个指向子类对象的父类指针时,将调用父类版本的析构函数,子类只释放了来自于父类的那部分成员变量,自己扩展的成员没有被释放,造成内存泄露问题。

#include <iostream>
using namespace std;

class Base{
public:
    Base();
    virtual ~Base();
};
Base::Base() {}
Base::~Base() {
    cout<<"Base destructor"<<endl;
}

class Derived : public Base{
public:
    Derived();
    ~Derived();
};
Derived::Derived() {}
Derived::~Derived() {
    cout<<"Derived destructor"<<endl;
}

int main() {
    Base *p=new Derived();//声明一个子类对象在堆区,即自己开辟,父类指针指向
    delete p;//操作父类指针,析构函数没有虚化,只调用父类析构函数,子类只释放了继承来的成员
    return 0;
}

三大继承

公有继承,派生类可以访问基类的公有成员和保护成员。
私有继承,只有类能使用基类的成员,实例化对象不能使用(可以用组合类替代,更好,进行限定)
即编译器在确定派生类能否访问基类的公有或保护成员时,考虑的是继承层次结构中最严格的访问限定符

保护继承:
与私有继承类似
1、表示has-a的关系
2、允许派生类能访问基类的所有公有和保护成员
3、不能通过派生类的实例对象对基类的成员进行访问
不同在于,子类的子类可以访问基类,而私有继承不允许这样

多重继承

多重继承是指一个子类有两个或者两个以上的父类。如下图。
这种情况,存在一个问题,如果两个父类有同名的成员,子类对象在调用时调用谁的?
在这里插入图片描述
另一种情况,两个父类有一个共同的祖先类。菱形继承。
此时还会有另一个问题,两个父类都会继承祖先类的成员,又将继承下来的成员派生给子类,那么子类中将有两份祖先类的成员。虽然合法,但是多数情况下,是没有必要的。
在这里插入图片描述

#include <iostream>

using namespace std;

class R{//祖先类
public:
    R();
    R(int v);
    void print() const;
private:
    int r;
};
R::R() {};
R::R(int v) { r=v; }
void R::print() const {cout<<r<<endl;}

class A : public R{
public:
    A(int v1,int v2);
private:
    int a;
};
A::A(int v1, int v2) : R(v1),a(v2) {}

class B: public R{
public:
    B(int v1,int v2);
private:
    int b;
};
B::B(int v1, int v2) : R(v1),b(v2) {}

class C : public A,public B{
public:
    C(int v1,int v2,int v3);
private:
    int c;
};
C::C(int v1, int v2, int v3, int v) : A(v1,v3),B(v2,v3),c(v3) {}

int main() {
    R rr(10);
    A aa(1,2);
    B bb(3,4);
    C cc(5,6,7);
    rr.print();
    aa.print();
    bb.print();
    //cc.print();//Non-static member 'print' found in multiple base-class
    // subobjects of type 'R': class C -> class A -> class R
	//cc中有两个print()
    return 0;
}

为了解决二义性,使用虚继承。

虚继承

1、直接继承祖先的两个基类,在继承时,加上virtua,即途中的父类一和父类二。
2、通过多重继承而来的那个子类,在构造函数时,要调用祖先类的构造函数。孙子辈的派生类,直接继承祖先的成员,再继承两个父类各自扩展的成员。

第一步更改:将继承自祖先类的两个类在继承是加virtual
第二步:孙子类在构造函数中直接调用祖先类构造函数来继承祖先的成员。

多重继承引发的重复调用问题

#include <iostream>

using namespace std;

class R{//祖先类
public:
    R();
    R(int v);
    void print() const;
private:
    int r;
};
R::R() {};
R::R(int v) { r=v; }
void R::print() const {cout<<"r="<<r<<endl;}

class A :virtual public R{
public:
    A(int v1,int v2);
    void f(){
        cout<<"a="<<a<<endl;
        R::print();
    }
private:
    int a;
};
A::A(int v1, int v2) : R(v1),a(v2) {}

class B:virtual public R{
public:
    B(int v1,int v2);
    void f(){
        cout<<"b="<<b<<endl;
        R::print();
    }
private:
    int b;
};
B::B(int v1, int v2) : R(v1),b(v2) {}

class C : public A,public B{
public:
    C(int v1,int v2,int v3);
    void f(){
        cout<<"c="<<c<<endl;
        A::f();
        B::f();
    }
private:
    int c;
};
C::C(int v1, int v2, int v3) : R(v1),A(v1,v3),B(v2,v3),c(v3) {}

int main() {
    R rr(10);
    A aa(1,2);
    B bb(3,4);
    C cc(5,6,7);
    rr.print();
    aa.print();
    bb.print();
    cc.print();//Non-static member 'print' found in multiple base-class
    // subobjects of type 'R': class C -> class A -> class R
    cc.f();//r打印了两次,在A中调用打印一次,B中打印一次
    return 0;
}

在这里插入图片描述
解决办法:将每个类自己的工作,单独封装。

class R{//祖先类
private:
    int r;
public:
    R(int x=0):r(x){};
    void f(){
        cout<<"r:"<<r<<endl;
    }
};

class A :virtual public R{
public:
    A(int x,int y) :R(x),a(y){};
    void f(){
        cout<<"a="<<a<<endl;
        R::f();//重复调用了
    }
protected:
    //只打印自己
    void fA(){
        cout<<"a="<<a<<endl;
    }
private:
    int a;
};

class B:virtual public R{
public:
    B(int x,int y):R(x),b(y){};
    void f(){
        cout<<"b="<<b<<endl;
        R::f();
    }
private:
    int b;
protected:
    void fB(){
        cout<<"b="<<b<<endl;
    }
};

class C: public A, public B{
private:
    int c;
public:
    C(int x,int y,int z,int t):R(x),A(x,y),B(y,z),c(t){};
    void f(){
        R::f();
        fA();
        fB();
        cout<<"c="<<c<<endl;
    }
protected:
    void fC(){
        cout<<"c"<<c<<endl;
    }
};

将每个类单独封装一个自己的函数,在调用时避免重复调用。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值