C++类成员的访问权限(public、protected和private)
在 C++ 中,类成员包括类的成员变量和成员函数,它们分别用于描述类的属性和行为。而类成员的访问权限决定了哪些成员是公开的,能被外界访问,也能被类内部访问;哪些成员是私有的,只能在类的内部访问,外界无法访问。就像一个人的钱包,只有他自己能动,别人是不能动的。
大家可能会问,为什么需要对类成员的访问进行控制,而不是让任何人都可以访问呢?这与现实世界中对事物访问的自然限制相呼应。正如我们只能了解自己钱包里有多少钱,而无法知晓他人的财务状况一样,C++ 中的类成员访问控制确保了数据的安全性和封装性。
设想一下,如果钱包里的钱可以被任何人访问,那么它的安全性将无法得到保障。同样,当“人”这个类的属性用成员变量表示时,这些变量的访问也应该受到限制,只能通过类的行为(成员函数)来进行操作,从而对外界隐藏,确保不被未授权的访问或修改。
如果不对访问加以控制,数据可能会被错误地修改,从而威胁到数据的完整性和安全性。因此,为了保护类成员不被不安全的访问,我们必须实施访问控制。通过这种方式,我们可以确保只有适当的操作能够触及敏感数据,从而维护程序的稳定性和可靠性。

图 1 访问级别
公有的成员主要作为类与外界交互的接口,它们是类提供给外界的访问点,允许外界通过这些成员与类进行通信和交互。
例如,在 Teacher 类中,如果有一个表示上课行为的 GiveLesson() 成员函数,该函数是类向外界提供的服务之一,因此应该设置为公有的,以便外界可以访问并调用它。这样的设计允许 Teacher 类的用户在需要时请求上课服务,而无须了解类内部的实现细节。
受保护的访问级别主要用于实现属性或方法的继承,允许派生类访问和使用基类的某些成员。
例如,考虑一个 Teacher 类,它包含一个表示姓名的成员变量 m_strName。出于数据封装和安全性的考虑,我们不希望外界能够直接修改教师的姓名。因此,m_strName 应在 Teacher 类内部可见,同时对于任何可能的派生类,如表示大学教师的 Lecturer 类,我们也希望它们能够访问和使用这个姓名属性。在这种情况下,将 m_strName 设置为受保护的成员是最合适的选择。
私有的访问级别可以确保类的成员被完全隐藏,从而有效地保护类中数据和行为的安全。例如,如果将钱包视为一个类的私有成员,它将不会被外部访问,从而防止潜在的非法访问或修改。
需要说明的是,在使用 class 关键字定义的类中,如果成员没有显式地声明其访问级别,默认为私有成员。这意味着,除非通过类的公共接口,否则外界无法直接访问这些成员。这种默认的访问控制强调了封装的重要性,鼓励开发者通过公共接口来操作类的状态。
相对地,使用 struct 关键字定义的结构体,其默认的访问级别为公有的。这通常用于定义简单的数据结构,其中成员变量和函数可以被直接访问。
在构造函数中,我们可以访问成员变量 m_strName,虽然它是受保护的,但在类自身的构造函数中可以对它进行修改。同时,为了让外界能够安全地读取该成员变量的值,我们为 Teacher 类添加了一个公有的成员函数 GetName(),通过这个函数,外界可以访问类中受保护的成员以获得必要的数据。
此外,Teacher 类还提供了一个公有的 GiveLesson() 函数,外界可以直接调用这个函数以获得 Teacher 类提供的上课服务。在这个公有成员函数内部,我们也可以访问类中的受保护成员和私有成员。
这些访问都是合理合法的。然而,如果尝试在类的外部直接访问受保护或私有成员,编译器会检测到这种非法访问,并产生编译错误,提示无法访问受保护或私有成员。这样,有了编译器的帮助,小偷就再也不能动我们的钱包了。
通过控制对类成员的访问,我们能够有效地保护数据和行为,防止数据被外界随意修改,同时也限制了外界对类的行为的不合理使用。如果类的某些成员变量因为业务逻辑的需要允许外界访问(例如这里的 m_strName),建议通过公有接口的方法来访问这些成员变量,而不是把这些成员变量设置为公有的。一般情况下,类的成员变量应该设置为受保护的或私有的。
大家可能会问,为什么需要对类成员的访问进行控制,而不是让任何人都可以访问呢?这与现实世界中对事物访问的自然限制相呼应。正如我们只能了解自己钱包里有多少钱,而无法知晓他人的财务状况一样,C++ 中的类成员访问控制确保了数据的安全性和封装性。
设想一下,如果钱包里的钱可以被任何人访问,那么它的安全性将无法得到保障。同样,当“人”这个类的属性用成员变量表示时,这些变量的访问也应该受到限制,只能通过类的行为(成员函数)来进行操作,从而对外界隐藏,确保不被未授权的访问或修改。
如果不对访问加以控制,数据可能会被错误地修改,从而威胁到数据的完整性和安全性。因此,为了保护类成员不被不安全的访问,我们必须实施访问控制。通过这种方式,我们可以确保只有适当的操作能够触及敏感数据,从而维护程序的稳定性和可靠性。
C++三种访问权限
在 C++ 中,对类成员的访问控制是通过设置成员的访问权限来实现的。按照访问范围的大小,访问级别分为公有的(public)、受保护的(protected)和私有的(private)三种,如下图所示。
图 1 访问级别
C++公有的访问权限(public)
公有成员通过在成员变量或成员函数前加上 public 关键字来修饰,这表明这些成员的访问不受限制,可以在类的内部和外部被访问。公有的成员主要作为类与外界交互的接口,它们是类提供给外界的访问点,允许外界通过这些成员与类进行通信和交互。
例如,在 Teacher 类中,如果有一个表示上课行为的 GiveLesson() 成员函数,该函数是类向外界提供的服务之一,因此应该设置为公有的,以便外界可以访问并调用它。这样的设计允许 Teacher 类的用户在需要时请求上课服务,而无须了解类内部的实现细节。
C++受保护的访问权限(protected)
受保护成员用关键字 protected 修饰,其声明格式与 public 型相同,这些成员在类的外部不可见,但可以在类的内部以及所有继承自该类的派生类中访问。受保护的访问级别主要用于实现属性或方法的继承,允许派生类访问和使用基类的某些成员。
例如,考虑一个 Teacher 类,它包含一个表示姓名的成员变量 m_strName。出于数据封装和安全性的考虑,我们不希望外界能够直接修改教师的姓名。因此,m_strName 应在 Teacher 类内部可见,同时对于任何可能的派生类,如表示大学教师的 Lecturer 类,我们也希望它们能够访问和使用这个姓名属性。在这种情况下,将 m_strName 设置为受保护的成员是最合适的选择。
C++私有的访问权限(private)
私有成员用关键字 private 修饰,其声明格式与 public 类型相同。这些成员只能在类的内部被访问,所有来自类外部的访问都是被禁止的。私有的访问级别可以确保类的成员被完全隐藏,从而有效地保护类中数据和行为的安全。例如,如果将钱包视为一个类的私有成员,它将不会被外部访问,从而防止潜在的非法访问或修改。
需要说明的是,在使用 class 关键字定义的类中,如果成员没有显式地声明其访问级别,默认为私有成员。这意味着,除非通过类的公共接口,否则外界无法直接访问这些成员。这种默认的访问控制强调了封装的重要性,鼓励开发者通过公共接口来操作类的状态。
相对地,使用 struct 关键字定义的结构体,其默认的访问级别为公有的。这通常用于定义简单的数据结构,其中成员变量和函数可以被直接访问。
C++实例演示三种访问权限
例如下面是一个 Teachar 类,类中结合为各个成员设置了合理的访问权限,更真实地反映了现实的情况:// 定义访问控制后的 Teacher 类 class Teacher { // 公有成员 // 外界通过访问这些成员与该类进行交互,以获得类提供的服务 public: // 冒号后的变量或者函数都受到它的修饰 // 构造函数应该是公有的,这样外界才可以利用构造函数创建该类的对象 Teacher(string strName) : m_strName(strName) { // ... } // 老师要为学生们上课,它应该被外界调用,所以这个成员函数是公有的 void GiveLesson() { // 在类的内部,可以访问自身的受保护的和私有的成员 PrepareLesson(); // 先备课,访问受保护成员 cout << "老师上课。" << endl; m_nWallet += 100; // 一节课钱包增加 100 元,访问私有成员 } // 我们不让别人修改名字,但需要让别人知道我们的名字 // 对于只可供外界只读访问的成员变量 // 可以提供一个公有的成员函数供外界对其进行读取访问 string GetName() { return m_strName; } // 受保护成员 // 不能被外界访问,但可以被自身访问,也可以“遗传”给子类,供子类访问 protected: // 自己和子类需要备课,所以不能把“备课”这个功能公开(public)给外界,而是要保护(protected)起来 void PrepareLesson() { cout << "老师备课。" << endl; } // 只有自己可以修改自己的名字,子类也需要这样的属性 string m_strName; // 私有成员 // 只有自己可以访问,连子类这个“亲儿子”都不能访问 private: int m_nWallet; // 钱包只有自己可以访问,所以设置为私有的 };设置了访问权限之后,对 Teacher 类对象的成员进行访问时需要特别注意,我们只能访问它的公有成员,试图访问它的受保护或者私有成员,就会被毫不留情地拒之门外:
int main() { // 创建对象时调用类的构造函数 // 在 Teacher 类中,构造函数是公有的,所以可以直接调用 Teacher MrChen("Chen"); // 外部变量,用于保存从对象获得的数据 string strName; // 通过类的公有成员函数,读取并获得类中受保护的成员变量 strName = MrChen.GetName(); // 错误:无法直接访问类的受保护和私有的成员 // 想改我的名字,先要问我答应不答应 MrChen.m_strName = "WangGang"; // 想从我钱包中拿走 200 元,那更不行了 MrChen.m_nWallet -= 200; return 0; }在主函数中,首先创建了一个 Teacher 类对象,这个创建过程会调用它的构造函数,因此构造函数必须是公有的。
在构造函数中,我们可以访问成员变量 m_strName,虽然它是受保护的,但在类自身的构造函数中可以对它进行修改。同时,为了让外界能够安全地读取该成员变量的值,我们为 Teacher 类添加了一个公有的成员函数 GetName(),通过这个函数,外界可以访问类中受保护的成员以获得必要的数据。
此外,Teacher 类还提供了一个公有的 GiveLesson() 函数,外界可以直接调用这个函数以获得 Teacher 类提供的上课服务。在这个公有成员函数内部,我们也可以访问类中的受保护成员和私有成员。
这些访问都是合理合法的。然而,如果尝试在类的外部直接访问受保护或私有成员,编译器会检测到这种非法访问,并产生编译错误,提示无法访问受保护或私有成员。这样,有了编译器的帮助,小偷就再也不能动我们的钱包了。
通过控制对类成员的访问,我们能够有效地保护数据和行为,防止数据被外界随意修改,同时也限制了外界对类的行为的不合理使用。如果类的某些成员变量因为业务逻辑的需要允许外界访问(例如这里的 m_strName),建议通过公有接口的方法来访问这些成员变量,而不是把这些成员变量设置为公有的。一般情况下,类的成员变量应该设置为受保护的或私有的。