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

C++ class与struct在访问控制上的差异

2022-05-103.0k 阅读

C++ class 与 struct 在访问控制上的差异

一、C++ 中访问控制的基础概念

在深入探讨 classstruct 在访问控制上的差异之前,我们先来回顾一下 C++ 中访问控制的基本概念。访问控制是面向对象编程(OOP)中的一个关键特性,它用于控制对类成员(包括数据成员和成员函数)的访问。通过访问控制,我们可以隐藏类的内部实现细节,只向外部提供必要的接口,这有助于实现数据封装和信息隐藏,提高代码的安全性和可维护性。

C++ 提供了三种访问修饰符:publicprivateprotected

  1. public(公共的):使用 public 修饰的成员可以在类的外部被直接访问。这意味着任何代码,无论是类的成员函数、友元函数还是其他外部函数,都可以访问 public 成员。
  2. private(私有的)private 修饰的成员只能在类的内部被访问,即只有类的成员函数和友元函数可以访问 private 成员。外部代码无法直接访问 private 成员,这有效地隐藏了类的内部实现细节。
  3. protected(受保护的)protected 修饰的成员与 private 成员类似,它们不能在类的外部被直接访问。但是,protected 成员可以被派生类(子类)的成员函数访问。这为继承机制提供了一种在子类中访问基类部分成员的方式,同时又限制了外部代码的访问。

二、class 的访问控制默认规则

在 C++ 中,class 的默认访问控制是 private。这意味着,如果在定义 class 时没有显式地使用访问修饰符,那么所有的成员(包括数据成员和成员函数)默认都是 private 的。

下面是一个简单的 class 示例:

class MyClass {
    int data; // 默认是 private
public:
    void setData(int value) {
        data = value;
    }
    int getData() {
        return data;
    }
};

在这个 MyClass 类中,data 成员变量没有显式的访问修饰符,因此它是 private 的。这意味着在类的外部,不能直接访问 data。但是,public 修饰的 setDatagetData 成员函数可以在类的外部被调用,通过这两个函数,外部代码可以间接地访问和修改 data 成员变量。

int main() {
    MyClass obj;
    // obj.data = 10;  // 错误,data 是 private 无法直接访问
    obj.setData(10);
    int value = obj.getData();
    return 0;
}

三、struct 的访问控制默认规则

class 不同,struct 的默认访问控制是 public。当定义一个 struct 时,如果没有显式地使用访问修饰符,所有成员默认都是 public 的。

以下是一个 struct 的示例:

struct MyStruct {
    int data; // 默认是 public
    void setData(int value) {
        data = value;
    }
    int getData() {
        return data;
    }
};

在这个 MyStruct 结构体中,data 成员变量默认是 public 的,所以在类的外部可以直接访问它。同样,setDatagetData 成员函数也是 public 的。

int main() {
    MyStruct obj;
    obj.data = 10;  // 正确,data 是 public 可以直接访问
    obj.setData(20);
    int value = obj.getData();
    return 0;
}

四、使用访问修饰符改变默认访问规则

虽然 classstruct 有各自不同的默认访问控制规则,但我们可以通过显式地使用访问修饰符来改变这种默认行为。

对于 class,可以通过 publicprivateprotected 修饰符来指定成员的访问权限,以覆盖默认的 private 规则。例如:

class AnotherClass {
public:
    int publicData;
private:
    int privateData;
protected:
    int protectedData;
public:
    void setPrivateData(int value) {
        privateData = value;
    }
    int getPrivateData() {
        return privateData;
    }
};

在这个 AnotherClass 类中,publicDatapublic 的,privateDataprivate 的,protectedDataprotected 的。通过这种方式,我们可以根据实际需求灵活地控制类成员的访问权限。

同样,对于 struct,也可以使用访问修饰符来改变默认的 public 规则。例如:

struct AnotherStruct {
private:
    int privateData;
public:
    void setPrivateData(int value) {
        privateData = value;
    }
    int getPrivateData() {
        return privateData;
    }
};

在这个 AnotherStruct 结构体中,通过 private 修饰符将 privateData 成员变量设置为 private,这样外部代码就不能直接访问它,只能通过 publicsetPrivateDatagetPrivateData 函数来间接访问。

五、在继承中的访问控制差异

除了默认访问控制规则不同外,classstruct 在继承时的访问控制也存在差异。

当使用 class 进行继承时,如果没有显式指定继承方式,默认的继承方式是 private 继承。例如:

class BaseClass {
public:
    int publicData;
private:
    int privateData;
protected:
    int protectedData;
};

class DerivedClass : BaseClass {
public:
    void accessBaseData() {
        publicData = 10;
        // privateData = 20;  // 错误,privateData 是 private 无法访问
        protectedData = 30;
    }
};

在这个例子中,DerivedClassBaseClass 继承,但没有显式指定继承方式,所以默认是 private 继承。在 DerivedClass 中,publicDataprotectedData 可以被访问,因为它们在 BaseClass 中的访问权限在 private 继承下分别变为 privateprivate,而 privateData 仍然无法访问。

当使用 struct 进行继承时,如果没有显式指定继承方式,默认的继承方式是 public 继承。例如:

struct BaseStruct {
public:
    int publicData;
private:
    int privateData;
protected:
    int protectedData;
};

struct DerivedStruct : BaseStruct {
public:
    void accessBaseData() {
        publicData = 10;
        // privateData = 20;  // 错误,privateData 是 private 无法访问
        protectedData = 30;
    }
};

在这个例子中,DerivedStructBaseStruct 继承,默认是 public 继承。在 DerivedStruct 中,publicData 的访问权限在继承后仍然是 publicprotectedData 的访问权限仍然是 protected,所以在 DerivedStruct 的成员函数中可以访问它们,而 privateData 由于在 BaseStruct 中是 private 的,所以无法访问。

如果我们显式指定继承方式,无论是 class 还是 struct,都可以按照我们指定的方式进行继承。例如,使用 public 继承 class

class PublicDerivedClass : public BaseClass {
public:
    void accessBaseData() {
        publicData = 10;
        // privateData = 20;  // 错误,privateData 是 private 无法访问
        protectedData = 30;
    }
};

在这个 PublicDerivedClass 中,由于显式指定了 public 继承,BaseClass 中的 public 成员在 PublicDerivedClass 中仍然是 public 的,protected 成员仍然是 protected 的。

同样,使用 private 继承 struct

struct PrivateDerivedStruct : private BaseStruct {
public:
    void accessBaseData() {
        publicData = 10;
        // privateData = 20;  // 错误,privateData 是 private 无法访问
        protectedData = 30;
    }
};

在这个 PrivateDerivedStruct 中,由于显式指定了 private 继承,BaseStruct 中的 publicprotected 成员在 PrivateDerivedStruct 中都变为 private 的。

六、访问控制差异对代码设计的影响

  1. 数据封装程度:由于 class 的默认访问控制是 private,它更倾向于实现高度的数据封装。这使得类的内部实现细节可以被很好地隐藏起来,外部代码只能通过类提供的 public 接口来访问和操作类的成员。这种方式有助于提高代码的安全性和可维护性,因为内部实现的改变不会影响到外部使用类的代码。例如,在一个复杂的图形绘制库中,class 可以用来定义图形对象,其内部的坐标、颜色等数据成员可以设置为 private,只通过 public 的绘制函数来显示图形,这样外部代码只需要关心如何调用绘制函数,而不需要了解图形对象的内部细节。

struct 的默认 public 访问控制使得它更适合用于简单的数据聚合,在这种情况下,我们可能希望直接访问结构体的成员,而不需要通过额外的接口函数。例如,在一个表示二维点的结构体中,直接访问 xy 坐标可能更加方便,而不需要像 class 那样通过 getXgetY 等函数来获取坐标值。

  1. 代码可读性和风格classstruct 的访问控制差异也会影响代码的可读性和风格。在大型项目中,使用 class 来定义具有复杂行为和数据封装需求的对象,可以使代码结构更加清晰,因为外部代码可以很明显地看到哪些是可以直接使用的接口,哪些是类的内部实现细节。而 struct 由于默认的 public 访问控制,更适用于简单的数据结构,其代码风格更加简洁明了,适合用于一些对数据封装要求不高的场景。

  2. 继承和多态:在继承和多态的应用中,classstruct 的访问控制差异也需要特别注意。由于 class 默认的 private 继承方式,在使用继承时需要显式指定 publicprotected 继承,以确保基类的成员在派生类中具有合适的访问权限。而 struct 默认的 public 继承方式在一些简单的继承场景下更加方便,但在需要更细粒度控制继承访问权限时,也需要显式指定继承方式。例如,在一个游戏开发项目中,当定义游戏角色类时,如果使用 class 进行继承,需要根据具体需求仔细选择继承方式,以保证游戏角色的属性和行为在继承体系中能够正确地被访问和扩展;而如果使用 struct 来定义一些简单的游戏数据结构,如坐标结构体的继承,默认的 public 继承方式可能更符合需求。

七、总结差异及最佳实践建议

  1. 差异总结

    • 默认访问控制class 的默认访问控制为 privatestruct 的默认访问控制为 public
    • 继承默认方式class 默认采用 private 继承,struct 默认采用 public 继承。
    • 用途倾向class 更适合用于实现数据封装和复杂的面向对象设计,struct 更适合用于简单的数据聚合。
  2. 最佳实践建议

    • 数据封装优先:如果需要强调数据封装和信息隐藏,并且希望控制对成员的访问,优先使用 class。例如,在设计一个数据库连接类时,将数据库的连接字符串、用户名、密码等敏感信息设置为 private,通过 public 的成员函数来进行数据库的连接、查询等操作,以确保数据的安全性。
    • 简单数据结构:对于简单的数据结构,如表示点、颜色、日期等,当希望直接访问数据成员而不需要额外的访问函数时,使用 struct。例如,在一个图形处理程序中,表示颜色的结构体可以直接包含红、绿、蓝三个分量,并且默认是 public 访问,方便在程序中直接使用和修改颜色值。
    • 继承场景:在继承时,根据具体需求明确指定继承方式。如果希望保持基类成员的访问权限不变,对于 class 使用 public 继承,对于 struct 则根据是否需要改变访问权限来决定是否显式指定继承方式。例如,在一个基于图形基类的图形绘制框架中,如果派生类需要公开基类的部分接口,对于 class 应使用 public 继承;而如果使用 struct 进行继承,默认的 public 继承可能就满足需求,但如果需要改变基类成员的访问权限,也应显式指定继承方式。

通过深入理解 classstruct 在访问控制上的差异,并根据实际需求合理使用它们,我们可以编写出更加健壮、可读和易于维护的 C++ 代码。无论是在小型项目还是大型工程中,正确运用这两种类型定义方式都能为我们的开发工作带来很大的便利。

希望通过以上详细的讲解和丰富的代码示例,能让大家对 C++ 中 classstruct 在访问控制上的差异有更深入的理解,从而在实际编程中能够更加灵活、准确地使用它们。