C++ 继承

目录

0.前言

1.继承的概念

1.1什么是继承?

1.2继承定义的格式

1.3继承关系和访问限定符

1.4继承基类成员访问方式的变化

2.基类与派生类的赋值转换

2.1 对象赋值

2.2 指针赋值

2.3 引用赋值

3.继承中的作用域

3.1 基类和派生类的作用域

3.2 成员隐藏

3.3 作用域解析运算符

4.派生类的默认成员函数

4.1 默认构造函数

4.2 拷贝构造函数

4.3 移动构造函数

4.4 赋值运算符

4.5 析构函数

5.继承与友元

5.1 基类与友元

5.2 派生类与友元

5.3 友元函数与派生类

6.继承与静态成员

6.1 静态成员变量

6.2 静态成员函数

7.单继承、多继承与菱形继承

7.1 单继承

7.2 多继承

7.3 菱形继承

8.继承与组合

8.1 继承(Inheritance)

8.2 组合(Composition)

8.3 继承与组合的区别

9.总结


(图像由AI生成) 

0.前言

C++作为一门强大的面向对象编程语言,其三大核心特性之一的继承,为代码的重用和扩展提供了极大的便利。继承允许一个类继承另一个类的属性和方法,实现了代码的高度复用和灵活扩展。然而,继承也带来了复杂性和潜在的风险,因此正确理解和使用继承机制至关重要。本篇博客将系统性地探讨C++中的继承,从概念、定义、应用到高级用法,帮助读者全面掌握这一重要特性。

1.继承的概念

1.1什么是继承?

继承是面向对象编程中的一种机制,通过它,一个类可以继承另一个类的属性和方法,从而实现代码的重用和功能的扩展。被继承的类称为基类或父类,继承基类的类称为派生类或子类。继承使得派生类可以复用基类的已有功能,并在此基础上添加新的功能或重写已有功能,以满足特定需求。

1.2继承定义的格式

在C++中,继承的定义格式如下:

class 派生类名 : 访问限定符 基类名 {
    // 派生类的成员
};

其中,访问限定符可以是publicprotectedprivate,它决定了基类成员在派生类中的访问权限。

1.3继承关系和访问限定符

继承关系中的访问限定符(publicprotectedprivate)决定了基类成员在派生类中的访问权限:

  • public继承:基类的public成员在派生类中仍为publicprotected成员在派生类中仍为protected
  • protected继承:基类的publicprotected成员在派生类中均为protected
  • private继承:基类的publicprotected成员在派生类中均为private

1.4继承基类成员访问方式的变化

下表展示了不同继承方式下基类成员访问方式的变化:

基类成员访问级别public继承protected继承private继承
publicpublicprotectedprivate
protectedprotectedprotectedprivate
private无法访问无法访问无法访问

具体示例代码如下:

class Base {
public:
    int publicVar;
protected:
    int protectedVar;
private:
    int privateVar;
};

class DerivedPublic : public Base {
    // publicVar 是 public
    // protectedVar 是 protected
    // privateVar 无法访问
};

class DerivedProtected : protected Base {
    // publicVar 是 protected
    // protectedVar 是 protected
    // privateVar 无法访问
};

class DerivedPrivate : private Base {
    // publicVar 是 private
    // protectedVar 是 private
    // privateVar 无法访问
};

2.基类与派生类的赋值转换

在C++中,基类与派生类之间的赋值转换是一种常见的操作。理解这些转换规则对确保程序的正确性和有效性至关重要。下面将详细介绍基类与派生类的赋值转换,包括对象赋值、指针赋值和引用赋值三个方面。

2.1 对象赋值

在C++中,可以将派生类对象赋值给基类对象,但不能直接将基类对象赋值给派生类对象。这是因为派生类对象包含了基类对象的所有属性和方法,但基类对象不包含派生类对象的扩展部分。

示例代码:

class Base {
public:
    int baseValue;

    Base(int val) : baseValue(val) {}

    void show() {
        std::cout << "Base Value: " << baseValue << std::endl;
    }
};

class Derived : public Base {
public:
    int derivedValue;

    Derived(int baseVal, int derivedVal) : Base(baseVal), derivedValue(derivedVal) {}

    void show() {
        Base::show();
        std::cout << "Derived Value: " << derivedValue << std::endl;
    }
};

int main() {
    Derived d(10, 20);
    Base b = d; // 派生类对象赋值给基类对象

    b.show(); // 只显示基类部分的值

    // Derived d2 = b; // 错误,不能将基类对象赋值给派生类对象

    return 0;
}

在上面的代码中,Base类有一个成员变量baseValueDerived类继承了Base类并添加了一个成员变量derivedValue。在main函数中,我们可以将Derived对象d赋值给Base对象b,但不能将Base对象b赋值给Derived对象d2

2.2 指针赋值

指针赋值比对象赋值更为常见,因为它允许我们在运行时实现多态。可以将指向派生类对象的指针赋值给指向基类对象的指针,但反之则不行。这种指针赋值转换被称为向上类型转换(upcasting),它是安全的,因为派生类对象总是包含基类对象的所有成员。

示例代码:

class Base {
public:
    int baseValue;

    Base(int val) : baseValue(val) {}

    virtual void show() {
        std::cout << "Base Value: " << baseValue << std::endl;
    }
};

class Derived : public Base {
public:
    int derivedValue;

    Derived(int baseVal, int derivedVal) : Base(baseVal), derivedValue(derivedVal) {}

    void show() override {
        Base::show();
        std::cout << "Derived Value: " << derivedValue << std::endl;
    }
};

int main() {
    Derived d(10, 20);
    Base* bp = &d; // 指向派生类对象的指针赋值给指向基类对象的指针

    bp->show(); // 调用的是Derived类的show方法

    // Derived* dp = bp; // 错误,不能将指向基类对象的指针赋值给指向派生类对象的指针
    Derived* dp = dynamic_cast<Derived*>(bp); // 使用dynamic_cast进行安全的类型转换
    if (dp) {
        dp->show();
    } else {
        std::cout << "Conversion failed!" << std::endl;
    }

    return 0;
}

在上面的代码中,基类Base和派生类Derived各自都有一个成员变量和一个show方法。show方法在Derived类中被重写。在main函数中,我们将指向Derived对象的指针赋值给指向Base对象的指针,并成功调用了Derived类的show方法。

如果需要将指向基类对象的指针转换为指向派生类对象的指针,可以使用dynamic_cast进行安全的类型转换,如上例所示。如果转换失败,dynamic_cast返回nullptr,因此需要进行检查。

2.3 引用赋值

引用赋值与指针赋值类似,可以将派生类对象的引用赋值给基类对象的引用,但不能反过来。这也是因为派生类包含基类的所有属性和方法。

示例代码:

class Base {
public:
    int baseValue;

    Base(int val) : baseValue(val) {}

    virtual void show() {
        std::cout << "Base Value: " << baseValue << std::endl;
    }
};

class Derived : public Base {
public:
    int derivedValue;

    Derived(int baseVal, int derivedVal) : Base(baseVal), derivedValue(derivedVal) {}

    void show() override {
        Base::show();
        std::cout << "Derived Value: " << derivedValue << std::endl;
    }
};

void display(Base& b) {
    b.show();
}

int main() {
    Derived d(10, 20);
    display(d); // 派生类对象的引用赋值给基类对象的引用

    // Base b(30);
    // Derived& dr = b; // 错误,不能将基类对象的引用赋值给派生类对象的引用

    return 0;
}

在上面的代码中,函数display接受一个基类对象的引用,并调用其show方法。在main函数中,我们将Derived对象的引用传递给display函数,从而展示了派生类的show方法。

3.继承中的作用域

在C++继承机制中,基类和派生类的作用域是一个重要的概念。它涉及到如何访问和隐藏成员、如何使用作用域解析运算符等内容。理解这些概念对于正确地使用继承机制至关重要。下面将详细介绍继承中的作用域,分析基类和派生类的作用域、隐藏等概念,并结合代码示例进行说明。

3.1 基类和派生类的作用域

基类和派生类的作用域指的是它们各自定义的成员(变量和函数)所能被访问的范围。当派生类继承基类时,基类的成员在派生类中会有不同的访问权限,这取决于继承方式(public、protected、private)。

3.2 成员隐藏

成员隐藏是指派生类中定义的成员(变量或函数)与基类中的同名成员发生冲突时,派生类的成员会隐藏基类的成员。派生类可以使用作用域解析运算符::来显式访问基类的成员。

示例代码:

#include <iostream>

class Base {
public:
    int value;

    Base(int val) : value(val) {}

    void show() {
        std::cout << "Base Value: " << value << std::endl;
    }
};

class Derived : public Base {
public:
    int value; // 隐藏了基类的value

    Derived(int baseVal, int derivedVal) : Base(baseVal), value(derivedVal) {}

    void show() {
        Base::show(); // 调用基类的show方法
        std::cout << "Derived Value: " << value << std::endl;
    }
};

int main() {
    Derived d(10, 20);
    d.show();

    std::cout << "Accessing Base::value through Derived object: " << d.Base::value << std::endl;

    return 0;
}

在上面的代码中,Derived类定义了一个与基类Base同名的成员变量value,这导致基类的value在派生类中被隐藏。通过在派生类的show方法中使用Base::show()调用基类的show方法,以及在main函数中使用d.Base::value访问基类的value,我们可以显式地访问基类的成员。

3.3 作用域解析运算符

作用域解析运算符::用于明确指定要访问的成员属于哪个类。这在成员隐藏时尤其有用。

示例代码:

#include <iostream>

class Base {
public:
    void display() {
        std::cout << "Base display" << std::endl;
    }
};

class Derived : public Base {
public:
    void display() {
        std::cout << "Derived display" << std::endl;
    }
    void show() {
        display();        // 调用派生类的display方法
        Base::display();  // 显式调用基类的display方法
    }
};

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

在上面的代码中,Derived类重写了Base类的display方法。在Derived类的show方法中,我们使用Base::display()来显式调用基类的display方法,以确保基类的方法被正确调用。

4.派生类的默认成员函数

在C++中,派生类会自动继承基类的默认成员函数,但这些默认成员函数的行为在某些情况下可能会发生变化。默认成员函数包括默认构造函数、拷贝构造函数、移动构造函数、赋值运算符和析构函数。下面将详细说明这些默认成员函数在派生类中的规则。

4.1 默认构造函数

默认构造函数是在没有提供任何参数的情况下调用的构造函数。如果基类有默认构造函数,那么派生类会自动调用基类的默认构造函数来初始化基类部分。如果派生类没有定义自己的默认构造函数,编译器会生成一个默认构造函数,该构造函数会调用基类的默认构造函数。

规则:

  1. 如果基类没有默认构造函数,派生类必须显式调用基类的其他构造函数。
  2. 如果派生类定义了构造函数但没有提供默认构造函数,则不会生成默认构造函数。

4.2 拷贝构造函数

拷贝构造函数用于创建一个对象,使其成为另一个对象的副本。如果基类有拷贝构造函数,派生类的拷贝构造函数会自动调用基类的拷贝构造函数来复制基类部分。

规则:

  1. 派生类的拷贝构造函数会先调用基类的拷贝构造函数,然后复制派生类特有的成员。
  2. 如果派生类没有定义拷贝构造函数,编译器会生成一个默认的拷贝构造函数。

4.3 移动构造函数

移动构造函数用于转移对象的所有权,从而避免不必要的复制。如果基类有移动构造函数,派生类的移动构造函数会自动调用基类的移动构造函数来移动基类部分。

规则:

  1. 派生类的移动构造函数会先调用基类的移动构造函数,然后移动派生类特有的成员。
  2. 如果派生类没有定义移动构造函数,编译器会生成一个默认的移动构造函数。

4.4 赋值运算符

赋值运算符用于将一个对象的值赋给另一个对象。如果基类有赋值运算符,派生类的赋值运算符会自动调用基类的赋值运算符来处理基类部分。

规则:

  1. 派生类的赋值运算符会先调用基类的赋值运算符,然后处理派生类特有的成员。
  2. 如果派生类没有定义赋值运算符,编译器会生成一个默认的赋值运算符。

4.5 析构函数

析构函数用于在对象生命周期结束时释放资源。如果基类有析构函数,派生类的析构函数会自动调用基类的析构函数来清理基类部分。

规则:

  1. 派生类的析构函数会先清理派生类特有的成员,然后调用基类的析构函数。
  2. 如果派生类没有定义析构函数,编译器会生成一个默认的析构函数。

示例代码:

#include <iostream>

class Base {
public:
    Base() { std::cout << "Base default constructor" << std::endl; }
    Base(const Base&) { std::cout << "Base copy constructor" << std::endl; }
    Base(Base&&) { std::cout << "Base move constructor" << std::endl; }
    Base& operator=(const Base&) { std::cout << "Base copy assignment" << std::endl; return *this; }
    Base& operator=(Base&&) { std::cout << "Base move assignment" << std::endl; return *this; }
    virtual ~Base() { std::cout << "Base destructor" << std::endl; }
};

class Derived : public Base {
public:
    Derived() { std::cout << "Derived default constructor" << std::endl; }
    Derived(const Derived& other) : Base(other) { std::cout << "Derived copy constructor" << std::endl; }
    Derived(Derived&& other) : Base(std::move(other)) { std::cout << "Derived move constructor" << std::endl; }
    Derived& operator=(const Derived& other) {
        Base::operator=(other);
        std::cout << "Derived copy assignment" << std::endl;
        return *this;
    }
    Derived& operator=(Derived&& other) {
        Base::operator=(std::move(other));
        std::cout << "Derived move assignment" << std::endl;
        return *this;
    }
    ~Derived() override { std::cout << "Derived destructor" << std::endl; }
};

int main() {
    Derived d1;
    Derived d2 = d1;
    Derived d3 = std::move(d1);
    d2 = d3;
    d3 = std::move(d2);
    return 0;
}

输出结果:

5.继承与友元

在C++中,友元(friend)机制允许一个类或者函数访问另一个类的私有和保护成员。友元关系不受继承的影响,具体来说,基类的友元并不会自动成为派生类的友元,反之亦然。下面将详细介绍继承与友元的关系,以及在继承关系中如何使用友元机制。

5.1 基类与友元

在基类中定义的友元函数或友元类,可以访问基类的私有和保护成员。但是,这些友元函数或友元类不能直接访问派生类的成员,除非派生类显式声明它们为友元。

示例代码:

#include <iostream>

class Base {
private:
    int baseValue;
public:
    Base(int val) : baseValue(val) {}

    friend void showBaseValue(Base& b);
};

void showBaseValue(Base& b) {
    std::cout << "Base Value: " << b.baseValue << std::endl;
}

class Derived : public Base {
private:
    int derivedValue;
public:
    Derived(int baseVal, int derivedVal) : Base(baseVal), derivedValue(derivedVal) {}

    // 需要显式声明友元函数
    friend void showDerivedValue(Derived& d);
};

void showDerivedValue(Derived& d) {
    std::cout << "Derived Value: " << d.derivedValue << std::endl;
    // 友元函数可以访问基类的成员
    showBaseValue(d); // 调用基类的友元函数
}

int main() {
    Derived d(10, 20);
    showDerivedValue(d);
    return 0;
}

在上面的代码中,showBaseValue是基类Base的友元函数,可以访问基类的私有成员baseValue。但它不能直接访问派生类Derived的成员,除非派生类显式声明它为友元。showDerivedValue是派生类的友元函数,可以访问派生类的私有成员derivedValue,并通过调用showBaseValue间接访问基类的成员。

5.2 派生类与友元

派生类可以定义自己的友元函数或友元类,这些友元可以访问派生类的私有和保护成员,但不能访问基类的私有成员,除非基类也声明它们为友元。

示例代码:

#include <iostream>

class Base {
private:
    int baseValue;
public:
    Base(int val) : baseValue(val) {}

    friend class FriendClass; // 基类的友元类
};

class FriendClass {
public:
    void showBase(Base& b) {
        std::cout << "FriendClass accessing Base Value: " << b.baseValue << std::endl;
    }
};

class Derived : public Base {
private:
    int derivedValue;
public:
    Derived(int baseVal, int derivedVal) : Base(baseVal), derivedValue(derivedVal) {}

    friend class DerivedFriendClass; // 派生类的友元类
};

class DerivedFriendClass {
public:
    void showDerived(Derived& d) {
        std::cout << "DerivedFriendClass accessing Derived Value: " << d.derivedValue << std::endl;
    }
};

int main() {
    Derived d(10, 20);
    FriendClass fc;
    fc.showBase(d); // 访问基类的成员

    DerivedFriendClass dfc;
    dfc.showDerived(d); // 访问派生类的成员
    return 0;
}

在上面的代码中,FriendClass是基类Base的友元类,可以访问基类的私有成员baseValueDerivedFriendClass是派生类Derived的友元类,可以访问派生类的私有成员derivedValue。但是,DerivedFriendClass不能直接访问基类的私有成员,除非基类也声明它为友元。

5.3 友元函数与派生类

友元函数可以访问类的所有成员,包括私有和保护成员。即使基类声明了一个友元函数,这个友元函数也不能直接访问派生类的私有成员。相反,如果派生类希望某个友元函数访问其私有成员,则必须显式声明这个友元函数。

示例代码:

#include <iostream>

class Base {
private:
    int baseValue;
public:
    Base(int val) : baseValue(val) {}

    friend void friendFunction(Base& b);
};

void friendFunction(Base& b) {
    std::cout << "Friend function accessing Base Value: " << b.baseValue << std::endl;
}

class Derived : public Base {
private:
    int derivedValue;
public:
    Derived(int baseVal, int derivedVal) : Base(baseVal), derivedValue(derivedVal) {}

    friend void friendFunction(Derived& d);
};

void friendFunction(Derived& d) {
    std::cout << "Friend function accessing Derived Value: " << d.derivedValue << std::endl;
    // 友元函数可以访问基类的成员
    friendFunction(static_cast<Base&>(d)); // 调用基类的友元函数
}

int main() {
    Derived d(10, 20);
    friendFunction(d);
    return 0;
}

在上面的代码中,我们定义了一个友元函数friendFunction,它可以访问基类Base的私有成员baseValue。在派生类Derived中,我们再次声明了这个友元函数,以便它能够访问派生类的私有成员derivedValue

6.继承与静态成员

在C++中,静态成员(包括静态成员变量和静态成员函数)属于类本身,而不是某个具体的对象。静态成员在继承关系中具有一些特殊的行为。下面将详细介绍继承与静态成员的关系,包括静态成员变量和静态成员函数的继承规则和使用方法。

6.1 静态成员变量

静态成员变量在类中只存在一个副本,无论创建多少个类的对象。静态成员变量在派生类中可以直接访问,但在继承时不会产生新的副本。

示例代码:

#include <iostream>

class Base {
public:
    static int baseValue;

    static void showBaseValue() {
        std::cout << "Base Value: " << baseValue << std::endl;
    }
};

// 静态成员变量的定义和初始化
int Base::baseValue = 10;

class Derived : public Base {
public:
    static void showDerivedBaseValue() {
        std::cout << "Derived accessing Base Value: " << baseValue << std::endl;
    }
};

int main() {
    // 直接通过类名访问静态成员变量
    Base::showBaseValue();
    Derived::showDerivedBaseValue();

    // 通过对象访问静态成员变量
    Base b;
    Derived d;
    b.showBaseValue();
    d.showDerivedBaseValue();

    // 修改静态成员变量
    Base::baseValue = 20;
    Base::showBaseValue();
    Derived::showDerivedBaseValue();

    return 0;
}

在上面的代码中,Base类定义了一个静态成员变量baseValue,并提供了一个静态成员函数showBaseValue来显示它的值。派生类Derived可以直接访问并使用baseValue,并且派生类可以通过自己的静态成员函数showDerivedBaseValue来显示基类的静态成员变量的值。

6.2 静态成员函数

静态成员函数属于类本身,可以访问静态成员变量,但不能访问非静态成员变量或非静态成员函数。静态成员函数在派生类中同样可以被继承和使用。

示例代码:

#include <iostream>

class Base {
public:
    static int baseValue;

    static void showBaseValue() {
        std::cout << "Base Value: " << baseValue << std::endl;
    }

    static void modifyBaseValue(int newValue) {
        baseValue = newValue;
    }
};

int Base::baseValue = 10;

class Derived : public Base {
public:
    static void showDerivedBaseValue() {
        showBaseValue(); // 调用基类的静态成员函数
    }

    static void modifyDerivedBaseValue(int newValue) {
        modifyBaseValue(newValue); // 调用基类的静态成员函数
    }
};

int main() {
    // 直接通过类名访问静态成员函数
    Base::showBaseValue();
    Derived::showDerivedBaseValue();

    // 修改静态成员变量
    Derived::modifyDerivedBaseValue(30);
    Base::showBaseValue();
    Derived::showDerivedBaseValue();

    return 0;
}

在上面的代码中,基类Base定义了一个静态成员函数modifyBaseValue,用于修改静态成员变量baseValue。派生类Derived可以调用基类的静态成员函数modifyBaseValue来修改静态成员变量的值。

7.单继承、多继承与菱形继承

C++ 支持三种继承方式:单继承、多继承和菱形继承(也称为钻石继承)。这些继承方式各有特点和适用场景。下面将详细介绍每种继承方式及其在C++中的实现和注意事项。

7.1 单继承

单继承是指一个派生类仅继承一个基类。这是最简单、最常见的继承方式,通常用于实现简单的类层次结构。

示例代码:

#include <iostream>

class Base {
public:
    void show() {
        std::cout << "Base class show" << std::endl;
    }
};

class Derived : public Base {
public:
    void display() {
        std::cout << "Derived class display" << std::endl;
    }
};

int main() {
    Derived d;
    d.show();    // 访问基类的方法
    d.display(); // 访问派生类的方法
    return 0;
}

在上面的代码中,Derived类继承了Base类,拥有了Base类的所有公有成员和方法,同时可以添加自己的成员和方法。

7.2 多继承

多继承是指一个派生类继承多个基类。C++是少数几种支持多继承的编程语言之一。多继承允许派生类从多个基类中继承成员,但也增加了复杂性和潜在的冲突。

示例代码:

#include <iostream>

class Base1 {
public:
    void show() {
        std::cout << "Base1 class show" << std::endl;
    }
};

class Base2 {
public:
    void display() {
        std::cout << "Base2 class display" << std::endl;
    }
};

class Derived : public Base1, public Base2 {
public:
    void introduce() {
        std::cout << "Derived class introduce" << std::endl;
    }
};

int main() {
    Derived d;
    d.show();      // 访问Base1的方法
    d.display();   // 访问Base2的方法
    d.introduce(); // 访问Derived的方法
    return 0;
}

在上面的代码中,Derived类同时继承了Base1Base2类,因此拥有了这两个基类的成员和方法。

7.3 菱形继承

菱形继承(或钻石继承)是多继承的一种特殊情况,其中两个基类继承自同一个祖先类,而一个派生类同时继承这两个基类。这种继承方式会导致基类成员的二义性和冗余,需要通过虚继承来解决。

问题示例:

#include <iostream>

class Base {
public:
    void show() {
        std::cout << "Base class show" << std::endl;
    }
};

class Derived1 : public Base {};

class Derived2 : public Base {};

class Derived : public Derived1, public Derived2 {};

int main() {
    Derived d;
    // d.show(); // 错误,show() 是不明确的
    return 0;
}

在上面的代码中,Derived1Derived2都继承了Base类,而Derived类同时继承了Derived1Derived2。由于Base类的成员被继承了两次,在Derived类中使用这些成员会导致二义性。

解决方案:虚继承

为了避免菱形继承的问题,可以使用虚继承。虚继承确保基类的成员在派生类中只有一份副本,从而消除二义性。

虚继承示例:

#include <iostream>

class Base {
public:
    void show() {
        std::cout << "Base class show" << std::endl;
    }
};

class Derived1 : virtual public Base {};

class Derived2 : virtual public Base {};

class Derived : public Derived1, public Derived2 {};

int main() {
    Derived d;
    d.show(); // 现在可以正常访问基类的方法
    return 0;
}

在上面的代码中,通过在继承时使用virtual关键字,Derived1Derived2虚继承Base类,从而确保Base类的成员在Derived类中只有一份副本,解决了二义性问题。

8.继承与组合

在面向对象编程中,继承和组合是两种常见的关系,用于构建类的层次结构和实现代码的复用。理解继承与组合的区别和应用场景,对于设计灵活和可维护的系统至关重要。

8.1 继承(Inheritance)

继承是“是一个”(is-a)关系,用于描述一个类是另一个类的特殊化。例如,狗是动物的一种,因此Dog类可以继承Animal类。继承使得派生类自动拥有基类的属性和方法,可以对基类的方法进行重载或重写。

示例代码:

#include <iostream>

class Animal {
public:
    void eat() {
        std::cout << "Animal is eating." << std::endl;
    }
    virtual void makeSound() {
        std::cout << "Animal sound." << std::endl;
    }
};

class Dog : public Animal {
public:
    void makeSound() override {
        std::cout << "Dog barks." << std::endl;
    }
};

int main() {
    Dog dog;
    dog.eat();        // 继承自 Animal 类的方法
    dog.makeSound();  // Dog 类重写的方法
    return 0;
}

在上面的代码中,Dog类继承了Animal类,因此Dog类可以使用Animal类的eat方法,并重写了makeSound方法。

8.2 组合(Composition)

组合是“有一个”(has-a)关系,用于描述一个类包含另一个类的实例。例如,汽车有一个引擎,因此Car类可以包含一个Engine类的对象。组合通过将对象作为成员变量来实现类之间的关系。

示例代码:

#include <iostream>

class Engine {
public:
    void start() {
        std::cout << "Engine starts." << std::endl;
    }
};

class Car {
private:
    Engine engine; // Car 有一个 Engine

public:
    void start() {
        engine.start();
        std::cout << "Car starts." << std::endl;
    }
};

int main() {
    Car car;
    car.start();
    return 0;
}

在上面的代码中,Car类包含一个Engine对象,并通过调用Engine对象的方法来实现自己的行为。

8.3 继承与组合的区别

  1. 关系类型

    • 继承表示“是一个”关系,派生类是基类的特殊类型。
    • 组合表示“有一个”关系,一个类包含另一个类的实例。
  2. 灵活性

    • 继承是静态的,类的层次结构在编译时确定,改变基类会影响所有派生类。
    • 组合是动态的,可以在运行时改变组成部分的实现,灵活性更高。
  3. 耦合度

    • 继承导致强耦合,派生类依赖于基类的实现。
    • 组合降低耦合度,类之间通过接口或抽象类进行通信,依赖关系较弱。
  4. 重用性

    • 继承重用基类的代码,但容易造成代码膨胀和继承链过长的问题。
    • 组合通过复用现有类的实例,提高代码的可维护性和复用性。

9.总结

C++的继承机制提供了强大的代码重用和扩展能力,通过理解和正确应用继承、友元、静态成员、单继承、多继承、菱形继承以及组合关系,我们可以设计出灵活且可维护的类层次结构。结合实际需求选择合适的继承和组合策略,是编写高效、清晰、可扩展代码的关键。希望这篇博客能帮助你深入理解C++中的继承相关概念和技术。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值