八股文又来了, 看吧 看一遍你就会了 时间长忘了?那就再看一遍孩子
一、核心概述
private、protected、public 是 C++ 封装特性的核心载体,其本质是在编译期限制类成员(变量/函数)在不同作用域下的可见性和访问权限------简单来说,就是规定"哪些地方能使用类的某个成员,哪些地方不能"。掌握这三个关键字的规则,是写出符合工程化规范、高内聚低耦合 C++ 代码的基础。
二、基础定义 & 核心访问规则
2.1 核心规则表(必记)
| 关键字 | 官方定义 | 大白话解释 | 类内访问 | 类外访问(通过对象) | 派生类(子类)访问 |
|---|---|---|---|---|---|
public |
公有的,对外暴露的接口,无访问限制 | 谁都能访问:类自己能用、外部代码能用、子类也能用 | ✅ 允许 | ✅ 允许 | ✅ 允许 |
private |
私有的,仅类内部可见,封装的核心体现 | 只有类自己能用:外部代码不能用、子类也不能用(友元除外) | ✅ 允许 | ❌ 禁止 | ❌ 禁止 |
protected |
受保护的,介于public和private之间,为派生类预留的访问权限 | 类自己能用、子类能用,但外部代码不能用 | ✅ 允许 | ❌ 禁止 | ✅ 允许 |
2.2 默认访问控制(易错点)
- 用
class定义类时,默认访问控制是private(未写关键字的成员均为 private); - 用
struct定义类时,默认访问控制是public(C++ 对 C 的兼容设计,struct 仅默认权限与 class 不同)。
2.3 基础验证代码
cpp
#include <iostream>
using namespace std;
class MyClass {
// 未写关键字,class默认private
int default_val = 0;
public:
int pub_val = 10; // 公有成员
void pub_func() {
// 类内:能访问所有成员(public/private/protected)
cout << "类内访问 private_val: " << pri_val << endl;
cout << "类内访问 protected_val: " << pro_val << endl;
cout << "类内访问 default_val: " << default_val << endl;
}
private:
int pri_val = 20; // 私有成员
void pri_func() { cout << "私有函数" << endl; }
protected:
int pro_val = 30; // 受保护成员
void pro_func() { cout << "受保护函数" << endl; }
};
// 派生类(子类)
class Derived : public MyClass {
public:
void derived_func() {
// 派生类:能访问基类的public/protected,不能访问private
cout << "派生类访问 pub_val: " << pub_val << endl; // ✅ 允许
cout << "派生类访问 pro_val: " << pro_val << endl; // ✅ 允许
// cout << "派生类访问 pri_val: " << pri_val << endl; // ❌ 禁止:private不可访问
pro_func(); // ✅ 允许调用protected函数
// pri_func(); // ❌ 禁止调用private函数
}
};
int main() {
MyClass obj;
// 类外:仅能访问public成员
cout << "类外访问 pub_val: " << obj.pub_val << endl; // ✅ 允许
// cout << "类外访问 pro_val: " << obj.pro_val << endl; // ❌ 禁止:protected不可访问
// cout << "类外访问 pri_val: " << obj.pri_val << endl; // ❌ 禁止:private不可访问
obj.pub_func(); // ✅ 允许调用public函数
// obj.pro_func(); // ❌ 禁止调用protected函数
// obj.pri_func(); // ❌ 禁止调用private函数
Derived d_obj;
d_obj.derived_func(); // ✅ 派生类内部逻辑正常执行
return 0;
}
2.4 补充:同一类不同对象的访问规则
访问控制是"类级别"的限制,而非"对象级别"------同一个类的不同对象,可互相访问对方的 private/protected 成员(编译期认为属于"同一类的内部"):
cpp
#include <iostream>
using namespace std;
class MyClass {
private:
int m_val = 10;
public:
// 访问同类型另一个对象的private成员
void accessAnotherObj(MyClass& other) {
cout << "访问另一个对象的private成员:" << other.m_val << endl; // ✅ 允许
other.m_val = 20; // ✅ 可修改
}
int getVal() const { return m_val; }
};
int main() {
MyClass a, b;
a.accessAnotherObj(b); // 输出:访问另一个对象的private成员:10
cout << "修改后b的val:" << b.getVal() << endl; // 输出:20
return 0;
}
三、继承中的访问控制(面试必考)
派生类继承基类时,继承方式(public/protected/private) 会修改"基类成员在派生类中的访问权限",核心铁律:
派生类对基类成员的访问权限 = 「基类成员自身的访问权限」和「继承方式」中更严格的那个。
3.1 继承权限影响表
| 基类成员访问权限 | public继承(最常用) | protected继承 | private继承 |
|---|---|---|---|
| public | public(不变) | protected | private |
| protected | protected(不变) | protected | private |
| private | 不可访问(不变) | 不可访问 | 不可访问 |
3.2 单层继承代码示例
cpp
#include <iostream>
using namespace std;
// 基类
class Base {
public:
int pub_val = 1;
protected:
int pro_val = 2;
private:
int pri_val = 3;
};
// 1. public继承(开发中99%的场景,推荐)
class DerivedPub : public Base {
public:
void show() {
cout << "pub_val: " << pub_val << endl; // ✅ public → public
cout << "pro_val: " << pro_val << endl; // ✅ protected → protected
// cout << pri_val << endl; // ❌ 始终不可访问
}
};
// 2. protected继承
class DerivedPro : protected Base {
public:
void show() {
cout << "pub_val: " << pub_val << endl; // ✅ public → protected
cout << "pro_val: " << pro_val << endl; // ✅ protected → protected
}
};
// 3. private继承
class DerivedPri : private Base {
public:
void show() {
cout << "pub_val: " << pub_val << endl; // ✅ public → private
cout << "pro_val: " << pro_val << endl; // ✅ protected → private
}
};
int main() {
DerivedPub d_pub;
cout << d_pub.pub_val << endl; // ✅ public继承后,基类public仍为public,类外可访问
// cout << d_pub.pro_val << endl; // ❌ protected,类外不可访问
DerivedPro d_pro;
// cout << d_pro.pub_val << endl; // ❌ public继承后变为protected,类外不可访问
DerivedPri d_pri;
// cout << d_pri.pub_val << endl; // ❌ public继承后变为private,类外不可访问
return 0;
}
3.3 多层继承的权限链式影响
多层继承中,基类成员权限会随继承链"层层收紧"(仅会更严格,不会放宽):
cpp
#include <iostream>
using namespace std;
// 顶层基类
class A {
public:
int pub_a = 1;
protected:
int pro_a = 2;
};
// 第二层:B public继承A
class B : public A {
public:
void showB() {
cout << "B中访问A的pub_a:" << pub_a << endl; // ✅ public→public
cout << "B中访问A的pro_a:" << pro_a << endl; // ✅ protected→protected
}
};
// 第三层:C protected继承B
class C : protected B {
public:
void showC() {
// A的pub_a:A→B(public)→C(protected)→ 最终C中是protected
cout << "C中访问A的pub_a:" << pub_a << endl; // ✅ 允许(C内可访问protected)
// A的pro_a:A→B(protected)→C(protected)→ 最终C中是protected
cout << "C中访问A的pro_a:" << pro_a << endl; // ✅ 允许
}
};
// 第四层:D private继承C
class D : private C {
public:
void showD() {
// A的pub_a:经过三层继承后变为private
cout << "D中访问A的pub_a:" << pub_a << endl; // ✅ D内可访问private
}
};
int main() {
C c;
// cout << c.pub_a << endl; // ❌ C中pub_a是protected,类外不可访问
D d;
// cout << d.pub_a << endl; // ❌ D中pub_a是private,类外不可访问
return 0;
}
四、特殊场景的访问控制细节
4.1 静态成员的访问控制
static 仅改变成员的"存储方式"(属于类而非对象),访问控制规则与普通成员完全一致:
cpp
#include <iostream>
using namespace std;
class MyClass {
private:
static int s_pri_val; // 静态私有成员
protected:
static int s_pro_val; // 静态受保护成员
public:
static int s_pub_val; // 静态公有成员
static void showStatic() {
// 类内可访问所有静态成员
cout << "静态私有:" << s_pri_val << endl;
cout << "静态受保护:" << s_pro_val << endl;
}
};
// 静态成员必须类外初始化
int MyClass::s_pri_val = 10;
int MyClass::s_pro_val = 20;
int MyClass::s_pub_val = 30;
// 派生类
class Derived : public MyClass {
public:
static void showDerivedStatic() {
// 派生类可访问基类的static protected/public
cout << "派生类访问静态受保护:" << s_pro_val << endl; // ✅
cout << "派生类访问静态公有:" << s_pub_val << endl; // ✅
// cout << s_pri_val << endl; // ❌ 静态私有仍不可访问
}
};
int main() {
// 类外仅能访问静态公有成员
cout << MyClass::s_pub_val << endl; // ✅ 30
// cout << MyClass::s_pro_val << endl; // ❌ 静态受保护,类外不可访问
// cout << MyClass::s_pri_val << endl; // ❌ 静态私有,类外不可访问
Derived::showDerivedStatic(); // ✅ 派生类内可访问静态受保护
return 0;
}
4.2 protected 的深层细节(易踩坑)
protected 允许"子类访问自己的基类成员",但禁止子类访问其他子类对象的基类 protected 成员:
cpp
#include <iostream>
using namespace std;
class Base {
protected:
int m_val = 10;
};
class DerivedA : public Base {
public:
// 错误场景:DerivedA试图访问DerivedB对象的protected成员
void accessDerivedB(DerivedB& b) {
// cout << b.m_val << endl; // ❌ 禁止:不能访问其他子类对象的protected
}
// 正确场景:访问自己的protected成员
void accessSelf() {
cout << m_val << endl; // ✅ 允许
}
};
class DerivedB : public Base {}; // 另一个子类
int main() {
DerivedA a;
DerivedB b;
a.accessSelf(); // ✅ 输出10
// a.accessDerivedB(b); // ❌ 编译报错
return 0;
}
五、友元:突破访问控制的唯一机制
friend 是 C++ 中唯一能突破 private/protected 访问限制的语法,核心是"显式授权"------只有被类主动声明为友元的函数/类,才能访问其私有/受保护成员。友元按授权粒度从粗到细分为以下三种形式:
5.1 友元函数(单个函数授权)
友元函数是最基础的友元形式,授权单个普通函数访问类的私有/受保护成员,适用于"仅需一个外部函数访问类内部数据"的场景(如运算符重载、简单的外部工具函数)。
cpp
#include <iostream>
using namespace std;
class MyClass {
private:
int pri_val = 100;
// 声明友元函数:仅授权showPrivate函数访问当前类的私有成员
friend void showPrivate(MyClass& obj);
};
// 友元函数:可以直接访问MyClass的private成员(无需通过public接口)
void showPrivate(MyClass& obj) {
cout << "访问private成员: " << obj.pri_val << endl; // ✅ 允许
}
int main() {
MyClass obj;
showPrivate(obj); // 输出:访问private成员: 100
return 0;
}
核心总结:
- 授权粒度:仅单个函数,影响范围最小;
- 声明位置:友元函数的声明可写在类的
public/private/protected任意区域,效果完全一致; - 核心优势:无需为单个函数开放过多权限,兼顾封装性和实用性。
5.2 友元类(批量授权,整个类的所有成员函数)
友元类是"批量授权"形式,授权另一个类的所有成员函数(无论 public/private/protected)访问当前类的私有/受保护成员,适用于"两个类高度耦合且需要深度协作"的场景(如容器类和其迭代器类)。
cpp
#include <iostream>
using namespace std;
class A {
// 声明B为友元类:B的所有成员函数都能访问A的私有/受保护成员
friend class B;
private:
int m_val = 100;
};
class B {
public:
// B的公有成员函数:访问A的私有成员
void accessA(A& a) {
cout << a.m_val << endl; // ✅ 允许
}
private:
// B的私有成员函数:同样能访问A的私有成员(友元类的所有成员函数都有权限)
void privateAccessA(A& a) {
a.m_val = 200; // ✅ 允许修改
}
};
int main() {
A a;
B b;
b.accessA(a); // 输出:100
return 0;
}
核心总结:
- 授权粒度:整个类的所有成员函数,授权范围最广;
- 风险提示:友元类会大幅降低封装性,仅在确有必要时使用(优先考虑友元函数/友元成员函数);
- 访问规则:友元类的私有成员函数也能访问授权类的私有成员,权限无差别。
5.3 友元成员函数(精细授权,仅单个成员函数)
友元成员函数是"精准授权"形式,仅授权另一个类的某个特定成员函数访问当前类的私有/受保护成员,是兼顾"协作需求"和"封装性"的最优友元形式。
cpp
#include <iostream>
using namespace std;
// 前置声明:必须先声明A,才能在B的成员函数参数中使用A的引用
class A;
class B {
public:
// 先声明需要授权的成员函数(仅该函数需要访问A的私有成员)
void onlyThisFuncCanAccessA(A& a);
};
class A {
// 仅授权B类的onlyThisFuncCanAccessA成员函数访问当前类的私有成员
friend void B::onlyThisFuncCanAccessA(A& a);
private:
int m_val = 500;
};
// 实现授权的成员函数:可直接访问A的私有成员
void B::onlyThisFuncCanAccessA(A& a) {
cout << a.m_val << endl; // ✅ 允许
}
int main() {
A a;
B b;
b.onlyThisFuncCanAccessA(a); // 输出:500
return 0;
}
核心总结:
- 授权粒度:仅单个成员函数,是最精细的友元授权方式;
- 前置声明:必须先声明被授权的类(如A),再声明其成员函数,否则编译器无法识别;
- 最佳实践:工程开发中优先使用友元成员函数,最小化破坏封装性。
5.4 友元的核心特性(所有友元形式通用)
友元关系的核心规则不受授权形式影响,需牢记以下三点:
- 单向性:若A声明B为友元,仅B能访问A的私有成员,A不能访问B的私有成员(双向授权需双方互相声明);
- 不传递性 :若A授权B,B授权C,C不能访问A的私有成员(友元关系无法链式传递);
- 不继承性 :若A授权B,C继承B,C不能访问A的私有成员(友元关系无法被子类继承)。
六、实际开发中的应用场景 & 反模式
6.1 核心应用场景(最佳实践)
| 关键字 | 核心应用场景 | 示例 |
|---|---|---|
public |
对外提供的「接口」(稳定、不轻易修改) | 业务方法(如 calculate())、get/set函数(如 getName()/setName()) |
private |
类的「内部实现细节」(隐藏,避免外部篡改) | 成员变量(如 m_name)、内部辅助函数(如 checkValid()) |
protected |
基类中需要给「派生类复用」,但不希望外部访问的成员 | 基类的通用属性(如 m_id)、通用方法(如 initData()) |
6.2 常见反模式(严禁使用)
-
反模式1:成员变量设为 public
cpp// 错误 class Student { public: int m_age; // 外部可直接赋值 m_age = -5,数据完全不安全 }; // 正确 class Student { private: int m_age; public: void setAge(int age) { if (age >= 0 && age <= 120) m_age = age; // 带校验的写接口 } int getAge() const { return m_age; } // 只读接口 }; -
反模式2:为方便将 protected 改为 public
cpp// 错误 class Base { public: int m_id; // 本应给子类复用的成员,被外部随意修改 }; // 正确 class Base { protected: int m_id; public: int getId() const { return m_id; } // 仅暴露只读接口 }; -
反模式3:过度使用友元,破坏封装
cpp// 错误:授权多个类/函数,封装形同虚设 class A { friend class B; friend class C; friend void func1(); friend void func2(); private: int m_data; }; // 正确:仅授权必要的函数/类 class A { friend void onlyNecessaryFunc(A& a); private: int m_data; };
七、常见误区 & 避坑点
- 误区1 :
protected是"派生类的 private"→ 派生类之间不能访问对方的protected成员; - 误区2:继承方式会修改基类自身的成员权限 → 仅修改"基类成员在派生类中的权限",基类自身权限不变;
- 误区3 :
static会改变访问控制 →static private仍仅类内可访问,static不影响权限; - 误区4:访问控制针对"对象"→ 同一类的不同对象可互相访问 private 成员(类级别限制);
- 误区5 :
private绝对不可访问 → 友元是合法途径,指针强制转换是不规范黑科技,严禁使用; - 误区6 :
struct没有访问控制 →struct只是默认权限为 public,同样支持private/protected。
八、面试高频考点 & 标准答案
8.1 基础考点
- 问 :
class和struct的访问控制默认值有什么区别?
答 :class默认访问控制是private,struct默认是public(C++ 对 C 的兼容设计,struct 保留 C 的特性); - 问 :
protected和private的核心区别是什么?
答 :核心区别在派生类的访问权限 ------protected允许派生类访问,private不允许;类外两者都不可访问; - 问 :public 继承和 private 继承的核心区别?
答 :public 继承保留基类public成员的公有属性(类外可访问),private 继承会把基类所有可访问成员变为 private(仅派生类内可访问);开发中优先用 public 继承; - 问 :友元是否受访问控制限制?
答 :不受,友元函数/友元类可直接访问类的private/protected成员,是访问控制的合法例外。
8.2 进阶考点(结合多态)
问:为什么私有虚函数能被派生类重写?
cpp
#include <iostream>
using namespace std;
class Base {
private:
virtual void func() { cout << "Base::func" << endl; }
public:
void callFunc() { func(); } // 公有接口调用私有虚函数
};
class Derived : public Base {
private:
void func() override { cout << "Derived::func" << endl; }
};
int main() {
Base* ptr = new Derived();
ptr->callFunc(); // 输出:Derived::func
delete ptr;
return 0;
}
答 :访问控制(private)和多态(虚函数)是两个独立的机制:
- 访问控制限制"谁能直接调用函数";
- 虚函数重写限制"函数是否能被覆盖";
派生类重写私有虚函数后,基类的公有接口仍可调用重写后的版本,这是 C++ 允许的合法设计。
九、核心总结
- 访问控制的本质是控制类成员的可见性,是编译期语法检查,核心为封装性服务;
- 基础规则:
public(类内/外/派生类)、private(仅类内+友元)、protected(类内+派生类); - 继承中:派生类对基类成员的权限 = 「基类成员权限」和「继承方式」中更严格的那个,private 成员始终不可访问;
- 特殊细节:同一类的不同对象可互相访问 private 成员,
protected仅允许子类访问自身的基类成员; - 友元是唯一突破访问控制的机制,遵循"单向、不传递、不继承",优先用"友元成员函数"最小化授权;
- 开发最佳实践:
public做接口、private藏实现、protected给子类复用,严禁 public 成员变量和过度友元。