MK
摩柯社区 - 一个极简的技术知识社区
AI 面试

C++类不同继承方式的访问规则

2024-12-173.0k 阅读

C++类不同继承方式的访问规则

在C++编程中,类的继承是一项核心特性,它允许我们基于已有的类创建新的类。通过继承,新类(派生类)可以获得基类的属性和方法,同时还能添加自己特有的成员。然而,不同的继承方式会影响派生类对基类成员的访问权限,这是C++程序员必须深入理解的重要知识点。本文将详细探讨C++类不同继承方式的访问规则,并通过丰富的代码示例来加深理解。

访问修饰符概述

在深入探讨继承方式的访问规则之前,我们先来回顾一下C++中的访问修饰符。C++提供了三种访问修饰符:publicprivateprotected。这些修饰符用于控制类成员在类外部和派生类中的可访问性。

  • public(公有):被声明为 public 的成员可以在类的外部直接访问,也可以被派生类自由访问。例如,一个类的公有成员函数通常用于提供对外的接口,让其他代码可以与该类进行交互。
  • private(私有)private 成员只能在类的内部访问,无论是在类的外部还是派生类中都无法直接访问。这确保了类的内部实现细节对外部代码是隐藏的,提高了封装性。
  • protected(保护)protected 成员与 private 成员类似,不能在类的外部直接访问。但是,与 private 不同的是,protected 成员可以被派生类访问。这为派生类提供了一种访问基类部分内部状态的途径,同时仍然保持了对外部代码的封装。

公有继承(public inheritance)

公有继承是最常见的继承方式,它在保持基类成员访问权限的基础上,为派生类提供了对基类公有和保护成员的访问能力。在公有继承中,基类的公有成员在派生类中仍然是公有的,基类的保护成员在派生类中仍然是保护的,而基类的私有成员在派生类中仍然是不可访问的。

下面通过一个简单的代码示例来展示公有继承的访问规则:

#include <iostream>

// 基类
class Base {
public:
    int public_member;
protected:
    int protected_member;
private:
    int private_member;
public:
    // 公有成员函数
    void setPrivateMember(int value) {
        private_member = value;
    }
    void printMembers() {
        std::cout << "Public member: " << public_member << std::endl;
        std::cout << "Protected member: " << protected_member << std::endl;
        std::cout << "Private member: " << private_member << std::endl;
    }
};

// 派生类,公有继承Base
class Derived : public Base {
public:
    void accessBaseMembers() {
        // 可以访问基类的公有成员
        public_member = 10;
        // 可以访问基类的保护成员
        protected_member = 20;
        // 不能访问基类的私有成员
        // private_member = 30; // 编译错误
    }
};

int main() {
    Derived d;
    // 可以通过派生类对象访问基类的公有成员
    d.public_member = 100;
    // 不能通过派生类对象访问基类的保护成员
    // d.protected_member = 200; // 编译错误
    d.setPrivateMember(300);
    d.printMembers();

    return 0;
}

在上述代码中,Derived 类公有继承自 Base 类。在 Derived 类的成员函数 accessBaseMembers 中,可以访问 Base 类的公有成员 public_member 和保护成员 protected_member,但不能访问私有成员 private_member。在 main 函数中,通过 Derived 类的对象 d 可以访问 Base 类的公有成员 public_member,但不能访问保护成员 protected_member

私有继承(private inheritance)

私有继承意味着基类的所有公有和保护成员在派生类中都变成私有的。这意味着派生类可以访问这些成员,但从派生类外部无法直接访问这些从基类继承来的成员。而基类的私有成员在派生类中仍然是不可访问的。

以下是私有继承的代码示例:

#include <iostream>

// 基类
class Base {
public:
    int public_member;
protected:
    int protected_member;
private:
    int private_member;
public:
    // 公有成员函数
    void setPrivateMember(int value) {
        private_member = value;
    }
    void printMembers() {
        std::cout << "Public member: " << public_member << std::endl;
        std::cout << "Protected member: " << protected_member << std::endl;
        std::cout << "Private member: " << private_member << std::endl;
    }
};

// 派生类,私有继承Base
class Derived : private Base {
public:
    void accessBaseMembers() {
        // 可以访问基类的公有成员,现在是私有成员
        public_member = 10;
        // 可以访问基类的保护成员,现在是私有成员
        protected_member = 20;
        // 不能访问基类的私有成员
        // private_member = 30; // 编译错误
    }
    void printDerivedMembers() {
        printMembers();
    }
};

int main() {
    Derived d;
    // 不能通过派生类对象访问基类的公有成员,现在是私有成员
    // d.public_member = 100; // 编译错误
    // 不能通过派生类对象访问基类的保护成员,现在是私有成员
    // d.protected_member = 200; // 编译错误
    // 不能通过派生类对象访问基类的私有成员函数
    // d.setPrivateMember(300); // 编译错误
    d.printDerivedMembers();

    return 0;
}

在这个示例中,Derived 类私有继承自 Base 类。在 Derived 类的成员函数 accessBaseMembers 中,可以访问 Base 类的公有和保护成员,但这些成员在 Derived 类中变成了私有的。在 main 函数中,无法通过 Derived 类的对象 d 访问从 Base 类继承来的公有和保护成员。

保护继承(protected inheritance)

保护继承使得基类的公有成员在派生类中变成保护的,基类的保护成员在派生类中仍然是保护的,而基类的私有成员在派生类中仍然不可访问。这意味着从派生类外部无法直接访问这些从基类继承来的成员,但派生类的派生类(如果有)可以访问这些成员。

以下是保护继承的代码示例:

#include <iostream>

// 基类
class Base {
public:
    int public_member;
protected:
    int protected_member;
private:
    int private_member;
public:
    // 公有成员函数
    void setPrivateMember(int value) {
        private_member = value;
    }
    void printMembers() {
        std::cout << "Public member: " << public_member << std::endl;
        std::cout << "Protected member: " << protected_member << std::endl;
        std::cout << "Private member: " << private_member << std::endl;
    }
};

// 派生类,保护继承Base
class Derived : protected Base {
public:
    void accessBaseMembers() {
        // 可以访问基类的公有成员,现在是保护成员
        public_member = 10;
        // 可以访问基类的保护成员,仍然是保护成员
        protected_member = 20;
        // 不能访问基类的私有成员
        // private_member = 30; // 编译错误
    }
};

// 派生类的派生类
class GrandDerived : public Derived {
public:
    void accessBaseMembers() {
        // 可以访问基类的公有成员,现在是保护成员
        public_member = 100;
        // 可以访问基类的保护成员,仍然是保护成员
        protected_member = 200;
        // 不能访问基类的私有成员
        // private_member = 300; // 编译错误
    }
};

int main() {
    GrandDerived gd;
    // 不能通过派生类的派生类对象访问基类的公有成员,现在是保护成员
    // gd.public_member = 1000; // 编译错误
    // 不能通过派生类的派生类对象访问基类的保护成员,仍然是保护成员
    // gd.protected_member = 2000; // 编译错误
    // 不能通过派生类的派生类对象访问基类的私有成员
    // gd.private_member = 3000; // 编译错误

    return 0;
}

在这个示例中,Derived 类保护继承自 Base 类,GrandDerived 类又公有继承自 Derived 类。在 Derived 类的成员函数 accessBaseMembers 中,可以访问 Base 类的公有和保护成员。在 GrandDerived 类的成员函数 accessBaseMembers 中,同样可以访问 Base 类的公有和保护成员,但这些成员在 GrandDerived 类中也是保护的。在 main 函数中,无法通过 GrandDerived 类的对象 gd 访问从 Base 类继承来的公有和保护成员。

继承方式对指针和引用的影响

不同的继承方式不仅影响派生类对基类成员的访问权限,还会影响基类指针或引用与派生类对象之间的转换。

  1. 公有继承:在公有继承中,基类指针或引用可以指向派生类对象,并且可以通过该指针或引用访问派生类对象中从基类继承来的公有成员。例如:
#include <iostream>

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

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

int main() {
    Derived d;
    Base* basePtr = &d;
    basePtr->print();
    // 不能通过基类指针调用派生类特有的成员函数
    // basePtr->derivedPrint(); // 编译错误

    return 0;
}

在上述代码中,通过基类指针 basePtr 可以调用派生类对象 d 中从基类继承来的公有成员函数 print,但不能调用派生类特有的成员函数 derivedPrint

  1. 私有继承:在私有继承中,基类指针或引用不能直接指向派生类对象。这是因为私有继承意味着基类与派生类之间的关系是一种内部实现关系,不应该对外暴露。如果尝试进行这样的转换,会导致编译错误。例如:
#include <iostream>

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

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

int main() {
    Derived d;
    // 编译错误,私有继承不允许基类指针指向派生类对象
    // Base* basePtr = &d;

    return 0;
}
  1. 保护继承:在保护继承中,基类指针或引用不能在类外部直接指向派生类对象。与私有继承类似,这是为了保持封装性。然而,在派生类的派生类中,可以将基类指针或引用指向派生类对象,并访问从基类继承来的保护成员。例如:
#include <iostream>

class Base {
protected:
    void print() {
        std::cout << "Base::print()" << std::endl;
    }
};

class Derived : protected Base {
public:
    void derivedPrint() {
        print();
    }
};

class GrandDerived : public Derived {
public:
    void grandDerivedPrint() {
        Base* basePtr = this;
        basePtr->print();
    }
};

int main() {
    GrandDerived gd;
    // 不能在类外部通过基类指针指向派生类对象
    // Base* basePtr = &gd; // 编译错误
    gd.grandDerivedPrint();

    return 0;
}

在上述代码中,在 GrandDerived 类的成员函数 grandDerivedPrint 中,可以将基类指针 basePtr 指向自身,并调用从基类继承来的保护成员函数 print。但在 main 函数中,不能直接将基类指针指向 GrandDerived 类的对象。

多重继承中的访问规则

C++支持多重继承,即一个派生类可以从多个基类继承属性和方法。在多重继承中,每个基类的继承方式都独立影响派生类对其成员的访问权限。

例如,假设有两个基类 Base1Base2,一个派生类 Derived 从这两个基类继承:

#include <iostream>

class Base1 {
public:
    int member1;
};

class Base2 {
public:
    int member2;
};

class Derived : public Base1, private Base2 {
public:
    void accessMembers() {
        member1 = 10;
        member2 = 20;
    }
};

int main() {
    Derived d;
    d.member1 = 100;
    // 不能通过派生类对象访问从Base2继承来的成员,因为是私有继承
    // d.member2 = 200; // 编译错误

    return 0;
}

在这个示例中,Derived 类公有继承自 Base1,私有继承自 Base2。因此,Derived 类可以访问 Base1 的公有成员 member1,并且从 Base1 继承来的 member1Derived 类外部仍然是公有的。而 Derived 类虽然可以访问 Base2 的公有成员 member2,但由于是私有继承,member2Derived 类外部是不可访问的。

多重继承可能会导致一些复杂的问题,例如命名冲突和菱形继承问题。命名冲突可能发生在多个基类具有同名成员的情况下,而菱形继承问题则出现在一个派生类从多个基类继承,而这些基类又继承自同一个基类的场景中。为了解决菱形继承问题,C++引入了虚继承的概念,这超出了本文的讨论范围,但值得深入学习。

总结不同继承方式的访问规则

为了更清晰地理解不同继承方式的访问规则,下面通过表格进行总结:

继承方式基类公有成员在派生类中的访问权限基类保护成员在派生类中的访问权限基类私有成员在派生类中的访问权限基类指针/引用能否指向派生类对象(类外部)
公有继承公有保护不可访问
私有继承私有私有不可访问不能
保护继承保护保护不可访问不能

通过深入理解这些访问规则,C++程序员可以更好地设计类层次结构,实现代码的封装、继承和多态等特性,编写出健壮、可维护的程序。在实际编程中,应根据具体的需求选择合适的继承方式,以确保程序的正确性和安全性。同时,要注意避免滥用继承,特别是在多重继承中,要谨慎处理可能出现的复杂问题。

希望通过本文的详细讲解和丰富示例,读者对C++类不同继承方式的访问规则有了更深入的理解和掌握。在实际编程实践中,不断运用这些知识,将有助于提升C++编程能力。