2. C++ private、protected、public

八股文又来了, 看吧 看一遍你就会了 时间长忘了?那就再看一遍孩子

一、核心概述

privateprotectedpublic 是 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. 反模式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. 反模式2:为方便将 protected 改为 public

    cpp 复制代码
    // 错误
    class Base {
    public:
        int m_id; // 本应给子类复用的成员,被外部随意修改
    };
    
    // 正确
    class Base {
    protected:
        int m_id;
    public:
        int getId() const { return m_id; } // 仅暴露只读接口
    };
  3. 反模式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. 误区1protected 是"派生类的 private"→ 派生类之间不能访问对方的 protected 成员;
  2. 误区2:继承方式会修改基类自身的成员权限 → 仅修改"基类成员在派生类中的权限",基类自身权限不变;
  3. 误区3static 会改变访问控制 → static private 仍仅类内可访问,static 不影响权限;
  4. 误区4:访问控制针对"对象"→ 同一类的不同对象可互相访问 private 成员(类级别限制);
  5. 误区5private 绝对不可访问 → 友元是合法途径,指针强制转换是不规范黑科技,严禁使用;
  6. 误区6struct 没有访问控制 → struct 只是默认权限为 public,同样支持 private/protected

八、面试高频考点 & 标准答案

8.1 基础考点

  1. classstruct 的访问控制默认值有什么区别?
    class 默认访问控制是 privatestruct 默认是 public(C++ 对 C 的兼容设计,struct 保留 C 的特性);
  2. protectedprivate 的核心区别是什么?
    :核心区别在派生类的访问权限 ------ protected 允许派生类访问,private 不允许;类外两者都不可访问;
  3. :public 继承和 private 继承的核心区别?
    :public 继承保留基类 public 成员的公有属性(类外可访问),private 继承会把基类所有可访问成员变为 private(仅派生类内可访问);开发中优先用 public 继承;
  4. :友元是否受访问控制限制?
    :不受,友元函数/友元类可直接访问类的 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++ 允许的合法设计。

九、核心总结

  1. 访问控制的本质是控制类成员的可见性,是编译期语法检查,核心为封装性服务;
  2. 基础规则:public(类内/外/派生类)、private(仅类内+友元)、protected(类内+派生类);
  3. 继承中:派生类对基类成员的权限 = 「基类成员权限」和「继承方式」中更严格的那个,private 成员始终不可访问;
  4. 特殊细节:同一类的不同对象可互相访问 private 成员,protected 仅允许子类访问自身的基类成员;
  5. 友元是唯一突破访问控制的机制,遵循"单向、不传递、不继承",优先用"友元成员函数"最小化授权;
  6. 开发最佳实践:public 做接口、private 藏实现、protected 给子类复用,严禁 public 成员变量和过度友元。
相关推荐
我不是8神2 小时前
字节跳动 Eino 框架(Golang+AI)知识点全面总结
开发语言·人工智能·golang
古城小栈2 小时前
Rust复合类型 四大军阀:数、元、切、串
开发语言·后端·rust
kong79069283 小时前
Python核心语法-Python自定义模块、Python包
开发语言·python·python核心语法
爱敲代码的小鱼3 小时前
事务核心概念与隔离级别解析
java·开发语言·数据库
小冷coding4 小时前
【Java】遇到微服务接口报错导致系统部分挂掉时,需要快速响应并恢复,应该怎么做呢?如果支付服务出现异常如何快速处理呢?
java·开发语言·微服务
星火开发设计4 小时前
二维数组:矩阵存储与多维数组的内存布局
开发语言·c++·人工智能·算法·矩阵·函数·知识
夜勤月4 小时前
彻底终结内存泄漏与悬挂指针:深度实战 C++ 智能指针底层原理与自定义内存池,打造稳如泰山的系统基石
开发语言·c++
HeisenbergWDG4 小时前
线程实现runnable和callable接口
java·开发语言
Fcy6484 小时前
⽤哈希表封装unordered_map和unordered_set(C++模拟实现)
数据结构·c++·散列表