【C++】继承详解

博客主页:https://blog.csdn.net/2301_779549673
欢迎点赞 收藏 ⭐留言 如有错误敬请指正!
本文由 JohnKi 原创,首发于 CSDN
未来很长,值得我们全力奔赴更美好的生活✨

在这里插入图片描述

在这里插入图片描述

文章目录

  • 前言
  • ️‍一、C++ 继承的基本概念
  • ️‍二、继承中的访问控制
    • ❤️一)不同继承方式的影响
    • 二)父类成员的设置与访问
  • ️‍三、继承中的构造和析构函数
    • ❤️一)父类构造和析构的调用
    • 二)构造和析构的执行顺序
  • ️‍四、继承中的默认成员函数
    • ❤️一)子类构造函数的初始化
    • 二)拷贝构造、赋值运算符重载与析构
  • 总结


前言

继承(inheritance) 机制是面向对象程序设计使代码可以复用的最重要的手段,它允许我们在保持原有类特性的基础上进行扩展,增加方法(成员函数)和属性(成员变量),这样产生新的类,称子类。继承呈现了面向对象程序设计的层次结构,体现了由简单到复杂的认知过程。以前我们接触的函数层次的复用,继承是类设计层次的复用。


️‍一、C++ 继承的基本概念

【C++】继承详解_第1张图片
C++ 中的继承是一种强大的面向对象编程特性,它允许一个类(派生类)从另一个类(基类)获取成员变量和成员函数。继承的定义在于通过特定的语法建立类之间的层次关系,实现代码的复用和扩展。

其特点显著,一方面,它大大减少了重复代码的编写,提高了代码的重用性和可扩展性。另一方面,它也增加了类之间的耦合性,当基类的某些成员发生改变时,派生类可能需要相应的修改。

继承的语法通常如下:class 派生类名: 继承方式 基类名 { // 派生类的成员 }; 。继承方式主要包括公有继承(public)保护继承(protected)私有继承(private)

在公有继承中,基类的公有和保护成员在派生类中保持其原有访问属性;保护继承下,基类的公有和保护成员在派生类中变为保护成员;私有继承时,基类的所有成员在派生类中都变为私有成员。

例如,定义一个基类Person和派生类Student

class Person {
public:
    void showInfo() {
        std::cout << "Person Info" << std::endl;
    }
protected:
    int age;
};

class Student: public Person {
public:
    void study() {
        std::cout << "Student is studying" << std::endl;
    }
};

在上述示例中,Student类通过公有继承获取了Person类的showInfo方法,并且可以在自身中定义新的方法study

总之,C++ 的继承机制为程序设计提供了极大的灵活性和可扩展性,但也需要谨慎使用,以避免不必要的复杂性和错误。

️‍二、继承中的访问控制

❤️一)不同继承方式的影响

在 C++ 的继承体系中,不同的继承方式会显著影响子类对父类成员的访问权限。

  • 公有继承(public):父类的公有成员和保护成员在子类中保持原有的访问级别。这意味着子类可以直接访问父类的公有成员和保护成员。例如,在上述Person和Student的示例中,由于Student类是公有继承自Person类,所以Student类的对象可以直接调用Person类的showInfo方法。

  • 私有继承(private):父类的公有成员和保护成员在子类中都变为私有成员。这使得子类对象在类内外都无法直接访问父类的这些成员,只有在子类内部的成员函数中可以通过特定方式访问。

  • 保护继承(protected):父类的公有成员和保护成员在子类中都变为保护成员。子类对象在类外无法访问这些成员,但子类内部的成员函数可以访问。

二)父类成员的设置与访问

合理设置父类成员的访问权限对于实现特定的设计需求至关重要。

如果希望子类能够完全继承并自由使用父类的某些成员,应将其设置为公有成员或保护成员。例如,在一个图形库中,父类Shape的一些通用属性如颜色、线条宽度等可能被设置为保护成员,以便派生类Circle、Rectangle等能够在内部进行处理和扩展。

相反,如果某些成员仅希望在父类内部使用,应将其设置为私有成员,防止子类的误访问和修改。比如,父类中的一些内部计算逻辑或临时变量。

总之,通过合理设置父类成员的访问权限,可以更好地控制类的封装性和可扩展性,减少潜在的错误和混乱。

️‍三、继承中的构造和析构函数

❤️一)父类构造和析构的调用

在继承关系中,当创建派生类对象时,父类的构造函数会先被调用,用于初始化从父类继承的成员。这是因为在构建派生类对象时,需要先确保父类部分的成员得到正确初始化。而在销毁派生类对象时,析构函数的调用顺序则相反,即先调用派生类的析构函数,然后再调用父类的析构函数,以保证资源的正确释放和清理。

例如:

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

class Child : public Parent {
public:
    Child() {
        std::cout << "Child constructor" << std::endl;
    }
    ~Child() {
        std::cout << "Child destructor" << std::endl;
    }
};

int main() {
    Child c;
    return 0;
}

输出结果为:

Parent constructor
Child constructor
Child destructor
Parent destructor

二)构造和析构的执行顺序

在复杂的继承和组合情况下,构造函数的执行顺序通常是先调用最顶层父类的构造函数,然后按照继承层次依次向下调用各派生类的构造函数,最后调用成员对象的构造函数。而析构函数的执行顺序则完全相反,先调用自身的析构函数,然后依次向上调用各派生类和父类的析构函数,最后调用成员对象的析构函数。
例如,在多重继承的情况下:

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

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

class Derived : public Base1, public Base2 {
public:
    Derived() {
        std::cout << "Derived constructor" << std::endl;
    }
    ~Derived() {
        std::cout << "Derived destructor" << std::endl;
    }
};

int main() {
    Derived d;
    return 0;
}

输出结果为:

Base1 constructor
Base2 constructor
Derived constructor
Derived destructor
Base2 destructor
Base1 destructor

在继承和组合情况混搭的情况下,如:

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

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

class Child : public Parent {
private:
    Object obj;
public:
    Child() {
        std::cout << "Child constructor" << std::endl;
    }
    ~Child() {
        std::cout << "Child destructor" << std::endl;
    }
};

int main() {
    Child c;
    return 0;
}

输出结果为:

Parent constructor
Object constructor
Child constructor
Child destructor
Object destructor
Parent destructor

️‍四、继承中的默认成员函数

❤️一)子类构造函数的初始化

在继承关系中,子类的构造函数通常需要借助父类的构造函数来完成对继承成员的初始化。当子类对象被创建时,如果父类的构造函数不是默认的无参构造函数,那么子类的构造函数就需要在初始化列表中明确调用父类的特定构造函数。例如,如果父类有一个带参数的构造函数,子类在构造自己的对象时,就需要在初始化列表中调用父类的这个带参构造函数,以确保从父类继承的成员得到正确的初始化。如果父类的构造函数存在多个参数,子类在初始化列表中也需要按照相应的顺序和类型提供这些参数。

class Parent {
public:
    Parent(int num) {
        std::cout << "Parent constructor with num: " << num << std::endl;
    }
};

class Child : public Parent {
public:
    Child(int num) : Parent(num) {
        std::cout << "Child constructor" << std::endl;
    }
};

int main() {
    Child c(10);
    return 0;
}

二)拷贝构造、赋值运算符重载与析构

在继承场景中,拷贝构造函数、赋值运算符重载和析构函数具有独特的特性。拷贝构造函数承担着创建并复制对象的责任,无论在何种情况下,都需要构造基类部分。赋值运算符重载则主要针对已经构造好的对象进行赋值操作。
对于析构函数,当子类对象被销毁时,先执行子类的析构函数,然后再执行父类的析构函数,以确保资源的正确释放。在子类的拷贝构造函数和赋值运算符重载函数中,如果没有显式地处理父类的相关操作,可能会导致父类部分的成员没有被正确拷贝或赋值。

例如,在下面的代码中:

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

class Child : public Parent {
public:
    Child() {
        std::cout << "Child constructor" << std::endl;
    }
    Child(const Child& c) {
        std::cout << "Child copy constructor" << std::endl;
    }
    Child& operator=(const Child& c) {
        std::cout << "Child assignment operator" << std::endl;
        return *this;
    }
    ~Child() {
        std::cout << "Child destructor" << std::endl;
    }
};

int main() {
    Child c1;
    Child c2 = c1;
    c2 = c1;
    return 0;
}

输出结果展示了拷贝构造函数、赋值运算符重载函数和析构函数的执行顺序和效果。


总结

本篇博文对 C++ 继承 做了一个较为详细的介绍,不知道对你有没有帮助呢

觉得博主写得还不错的三连支持下吧!会继续努力的~

请添加图片描述

你可能感兴趣的:(c++,java,android)