news 2026/3/25 10:53:28

类的访问权限:public、private 与 protected 详解

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
类的访问权限:public、private 与 protected 详解

类的访问权限:public、private 与 protected 详解

在C++面向对象编程中,类的三大访问权限(public、private、protected)是实现封装特性的核心,也是连接类的定义与对象使用的关键纽带。上一篇博客《C++ 类与对象:类的定义与对象的创建》中,我们简单提及了访问权限的作用,但很多初学者仅停留在“知道三种权限”的层面,分不清它们的访问范围、核心用途,也不懂在实际编程中如何合理选择,导致写出的代码既不符合封装原则,也存在数据安全隐患。

本文将专门拆解public、private、protected三种访问权限,从核心定义、访问范围、实战用法,到继承场景下的差异、常见误区,逐一讲解,帮你彻底吃透访问权限的本质,掌握“成员变量私有化、成员函数公有化”的封装原则,学会在类的设计中合理分配访问权限,为后续学习继承、多态打下坚实基础。

核心前提回顾:C++类的核心作用是封装——将对象的属性(成员变量)和行为(成员函数)打包在一起,隐藏内部实现细节,只暴露外部需要的接口,以此保证数据安全性和代码可维护性。而访问权限修饰符,正是实现“隐藏细节、暴露接口”的核心工具,它决定了类的成员(成员变量、成员函数)能在哪些地方被访问。

一、核心认知:访问权限的本质的是什么?

很多初学者会误以为“访问权限是限制类的成员能否被使用”,其实这是片面的。访问权限的本质是限制“访问范围”——即类的成员(成员变量、成员函数)能在“类内部”“类外部”“子类内部”这三个场景中的哪些场景被访问和操作。

简单来说,访问权限就像“类的访问规则”:

  • 有的成员(比如核心属性)只能在类内部操作,不允许外部直接访问——对应private;

  • 有的成员(比如外部需要调用的接口)需要在类外部、类内部都能访问——对应public;

  • 有的成员(比如供子类继承使用的属性/方法)需要在类内部、子类内部能访问,不允许外部访问——对应protected。

关键注意:访问权限修饰符的作用范围,是“从当前修饰符开始,到下一个修饰符结束”;若未指定访问权限修饰符,C++默认是private(这是初学者最容易踩的坑之一)。

二、逐一看透:三种访问权限详解(核心重点)

下面逐一拆解public、private、protected三种访问权限,重点掌握“访问范围”和“核心用途”,这是区分三种权限的关键,也是笔试面试中高频考察的知识点。

1. private:私有权限(核心封装,最常用)

(1)核心定义

private修饰的类成员(成员变量、成员函数),仅能在当前类的内部被访问,类外部(包括通过对象访问)、子类内部均无法直接访问。

(2)访问范围总结
  • ✅ 类内部:可访问(成员函数可直接操作private成员变量、调用private成员函数);

  • ❌ 类外部:不可访问(无法通过对象直接访问private成员,编译报错);

  • ❌ 子类内部:不可访问(子类无法直接访问父类的private成员)。

(3)核心用途(必记)

private是实现封装的核心,用于存储类的核心属性(成员变量),隐藏类的内部细节,避免外部随意修改数据,保证数据安全性。

通俗类比:private成员就像“家里的保险柜”,只有家里人(类内部)能打开、操作;外人(类外部)、亲戚(子类)都不能直接触碰。

(4)实战示例

定义一个Student类,将核心属性(学号、姓名、成绩)设为private,演示private的访问限制:

#include<iostream>#include<string>usingnamespacestd;classStudent{// 私有成员:核心属性,仅类内部可访问private:string m_id;// 学号string m_name;// 姓名doublem_score;// 成绩// 私有成员函数:仅类内部可调用voidcheckScore(){if(m_score<60){cout<<m_name<<"成绩不及格,需要补考!"<<endl;}}// 公有成员:外部接口,后续讲解public:voidsetScore(doublescore){// 类内部可访问private成员变量m_scoreif(score>=0&&score<=100){m_score=score;// 类内部可调用private成员函数checkScore()checkScore();}else{cout<<"成绩输入错误,请输入0-100之间的数值!"<<endl;}}};intmain(){Student stu;// 1. 尝试访问private成员变量m_score(错误)// stu.m_score = 85; // 编译报错:m_score是private,类外部不可访问// 2. 尝试调用private成员函数checkScore()(错误)// stu.checkScore(); // 编译报错:checkScore()是private,类外部不可访问// 3. 通过public接口间接操作private成员(正确)stu.setScore(58);// 正确:setScore()是public,可外部调用,内部间接操作private成员return0;}
(5)关键说明
  • private成员变量不能被外部直接访问,但可以通过public成员函数(接口)间接访问和修改——这正是封装的核心思想:隐藏细节,通过接口暴露功能;

  • private成员函数通常用于类内部的辅助逻辑(比如示例中的checkScore(),用于校验成绩),不需要外部调用,因此设为private,避免暴露内部实现;

  • 日常编程中,绝大多数成员变量都应设为private,这是保证数据安全性的关键(比如示例中,通过setScore()的逻辑校验,避免输入非法成绩)。

2. public:公有权限(外部接口,最常用)

(1)核心定义

public修饰的类成员(成员变量、成员函数),可在类内部、类外部、子类内部均能被访问,没有访问限制。

(2)访问范围总结
  • ✅ 类内部:可访问;

  • ✅ 类外部:可访问(可通过对象直接访问public成员);

  • ✅ 子类内部:可访问(子类可直接访问父类的public成员)。

(3)核心用途(必记)

public用于定义类的外部接口,即类对外暴露的功能(成员函数),供类外部、子类调用,以此实现外部与类的交互。

通俗类比:public成员就像“家里的大门”,所有人(类内部、外部、子类)都能通过大门进入(访问),是类与外部交互的唯一通道。

(4)实战示例(延续Student类)

补充public成员函数,作为外部接口,实现对private成员的间接操作和访问:

#include<iostream>#include<string>usingnamespacestd;classStudent{// 私有成员:核心属性,隐藏细节private:string m_id;string m_name;doublem_score;voidcheckScore(){if(m_score<60){cout<<m_name<<"成绩不及格,需要补考!"<<endl;}}// 公有成员:外部接口,暴露功能public:// 接口1:设置学生姓名(间接修改private成员m_name)voidsetName(string name){m_name=name;}// 接口2:设置学生成绩(间接修改private成员m_score)voidsetScore(doublescore){if(score>=0&&score<=100){m_score=score;checkScore();}else{cout<<"成绩输入错误,请输入0-100之间的数值!"<<endl;}}// 接口3:获取学生信息(间接访问private成员)voidshowInfo(){cout<<"学号:"<<m_id<<",姓名:"<<m_name<<",成绩:"<<m_score<<endl;}};intmain(){Student stu;// 类外部通过public接口,间接操作private成员(正确)stu.setName("张三");stu.setScore(85);stu.showInfo();// 类外部通过public接口,间接访问private成员// 类外部可直接访问public成员(若有public成员变量)// 注意:通常不建议设置public成员变量,避免外部直接修改return0;}
(5)关键说明
  • public的核心作用是“暴露接口”,而非“暴露数据”——日常编程中,尽量避免设置public成员变量,否则会破坏封装,导致外部可随意修改数据,失去封装的意义;

  • 所有需要外部调用的功能,都应封装为public成员函数(接口),在接口内部实现对private成员的操作,同时可添加数据校验、逻辑判断,保证数据安全性;

  • public成员是类与外部交互的唯一通道,接口的设计应简洁、清晰,只暴露外部需要的功能,隐藏内部实现细节(比如示例中,外部无需知道checkScore()的存在,只需调用setScore()即可)。

3. protected:保护权限(继承专用,重点)

(1)核心定义

protected修饰的类成员(成员变量、成员函数),可在当前类内部、子类内部被访问,但类外部无法直接访问——这是protected与private、public的核心区别。

(2)访问范围总结
  • ✅ 类内部:可访问;

  • ❌ 类外部:不可访问(无法通过对象直接访问protected成员);

  • ✅ 子类内部:可访问(子类可直接访问父类的protected成员)。

(3)核心用途(必记)

protected是为继承场景设计的,用于定义“需要被子类继承、但不允许外部访问”的成员。它兼顾了封装性和继承性:既隐藏了类的内部细节(不允许外部访问),又为子类提供了必要的访问权限(子类可直接使用父类的protected成员)。

通俗类比:protected成员就像“家里的卧室”,家里人(类内部)、自己的孩子(子类)能进入,外人(类外部)不能进入——既保护了隐私,又方便家人使用。

(4)实战示例(结合继承)

定义父类Person,将需要被子类继承的成员设为protected;定义子类Student(继承Person),演示子类对父类protected成员的访问:

#include<iostream>#include<string>usingnamespacestd;// 父类:PersonclassPerson{// 私有成员:仅父类内部可访问,子类不可访问private:string m_idCard;// 身份证号(核心隐私,不被子类、外部访问)// 保护成员:父类内部、子类内部可访问,外部不可访问protected:string m_name;// 姓名(需要被子类继承,不允许外部直接访问)intm_age;// 年龄(需要被子类继承,不允许外部直接访问)// 公有成员:外部接口public:voidsetAge(intage){if(age>0&&age<150){m_age=age;}else{cout<<"年龄输入错误!"<<endl;}}};// 子类:Student(继承Person)classStudent:publicPerson{// 子类私有成员private:doublem_score;// 成绩// 子类公有成员(接口)public:// 子类内部可直接访问父类的protected成员m_name、m_agevoidsetStudentInfo(string name,intage,doublescore){m_name=name;// 访问父类protected成员m_name(正确)m_age=age;// 访问父类protected成员m_age(正确)m_score=score;// 尝试访问父类private成员m_idCard(错误)// m_idCard = "110101199001011234"; // 编译报错:子类不可访问父类private成员}voidshowStudentInfo(){// 子类内部可直接访问父类protected成员cout<<"姓名:"<<m_name<<",年龄:"<<m_age<<",成绩:"<<m_score<<endl;}};intmain(){Student stu;// 类外部尝试访问父类protected成员m_name(错误)// stu.m_name = "张三"; // 编译报错:protected成员外部不可访问// 类外部通过子类public接口,间接操作父类protected成员(正确)stu.setStudentInfo("张三",18,95.5);stu.showStudentInfo();// 类外部通过父类public接口,间接操作父类protected成员(正确)stu.setAge(19);stu.showStudentInfo();return0;}
(5)关键说明
  • protected仅在继承场景下有意义,若类不涉及继承,protected与private的作用完全一致(类外部不可访问,类内部可访问);

  • 子类可直接访问父类的protected成员,但不能访问父类的private成员——这是protected与private的核心区别,也是继承场景中选择protected的原因;

  • 即使子类继承了父类的protected成员,子类的外部依然无法直接访问该成员(只能通过子类的public接口间接访问),保证了封装性。

三、三者核心对比(表格总结,必背)

为了方便大家快速区分、记忆三种访问权限,整理了核心对比表格,涵盖访问范围、核心用途、使用场景,一目了然,笔试面试直接套用:

访问权限访问范围核心用途常用场景
private(私有)✅ 类内部
❌ 类外部
❌ 子类内部隐藏核心细节,保证数据安全绝大多数成员变量、类内部辅助函数
public(公有)✅ 类内部
✅ 类外部
✅ 子类内部暴露外部接口,实现类与外部交互类的外部接口(成员函数)
protected(保护)✅ 类内部
❌ 类外部
✅ 子类内部兼顾封装与继承,供子类访问需要被子类继承的成员(属性/函数)
补充口诀(快速记忆):
私有(private)藏内部,公有(public)对外露,保护(protected)给子类,封装继承两不误。

四、实战技巧:如何合理分配访问权限?(核心规范)

很多初学者学会了三种访问权限的定义,却不知道在实际编程中如何选择——比如什么时候用private,什么时候用public,什么时候用protected。其实只要遵循以下3个核心规范,就能写出符合封装原则、易维护的代码。

规范1:成员变量优先设为private(核心封装原则)

无论类是否涉及继承,所有核心属性(成员变量)都应设为private,不允许外部直接访问。外部需要操作这些变量时,通过public成员函数(set/get接口)间接实现,同时在接口中添加数据校验,保证数据安全性。

示例规范写法:

classPhone{private:string m_brand;// 成员变量私有化doublem_price;public:// set接口:修改private成员,添加校验voidsetPrice(doubleprice){if(price>=0){m_price=price;}else{cout<<"价格输入错误!"<<endl;}}// get接口:获取private成员,不允许修改doublegetPrice(){returnm_price;}};

规范2:外部需要调用的函数,设为public(接口暴露原则)

所有需要被类外部、子类外部调用的功能,都封装为public成员函数,作为类的对外接口。接口的设计应遵循“单一职责”——一个函数只做一件事,简洁清晰,避免暴露内部实现细节。

反例(不规范):将内部辅助函数设为public,暴露内部细节:

classStudent{private:doublem_score;public:voidsetScore(doublescore){m_score=score;checkScore();// 内部辅助函数}// 错误:checkScore()是内部辅助函数,无需外部调用,不应设为publicvoidcheckScore(){if(m_score<60){cout<<"不及格!"<<endl;}}};

规范3:仅在继承场景下,使用protected(继承专用原则)

若类不涉及继承,无需使用protected,直接用private即可;若类涉及继承,且某个成员需要被子类直接访问(无需通过父类接口),则将该成员设为protected。

注意:不要为了“方便子类访问”,将所有父类成员都设为protected——这会破坏父类的封装性,只有真正需要被子类继承、使用的成员,才设为protected。

五、常见误区:三种权限的5个高频坑(必避)

初学者在使用访问权限时,很容易因概念混淆、语法细节出错,这里总结5个最常见的坑,结合错误示例和正确写法,帮你少走弯路。

误区1:未指定访问权限,默认是public(最容易踩坑)

错误认知:很多初学者误以为,类中未指定访问权限修饰符时,默认是public;
正确认知:C++中,类的成员未指定访问权限时,默认是private

classStudent{// 未指定访问权限,默认是privatevoidshowInfo(){// private成员函数,外部无法访问cout<<"学生信息"<<endl;}};intmain(){Student stu;stu.showInfo();// 错误:showInfo()是private,外部不可访问return0;}

正确写法:需要外部访问的成员,明确指定为public:

classStudent{public:// 明确指定为publicvoidshowInfo(){cout<<"学生信息"<<endl;}};intmain(){Student stu;stu.showInfo();// 正确return0;}

误区2:子类可以访问父类的private成员

错误认知:子类继承了父类,就可以访问父类的所有成员;
正确认知:子类只能访问父类的public和protected成员,无法访问父类的private成员

classPerson{private:string m_name;// 父类private成员public:voidsetName(string name){m_name=name;}};classStudent:publicPerson{public:voidshowName(){// 错误:子类无法访问父类private成员m_namecout<<"姓名:"<<m_name<<endl;}};// 正确写法:将父类m_name设为protected,或通过父类public接口访问classPerson{protected:// 修改为protectedstring m_name;};classStudent:publicPerson{public:voidshowName(){cout<<"姓名:"<<m_name<<endl;// 正确}};

误区3:设置public成员变量,方便外部访问

错误做法:为了方便外部修改数据,将成员变量设为public,破坏封装;
正确做法:成员变量私有化,通过public的set/get接口间接访问和修改,添加数据校验。

// 错误写法:public成员变量,外部可随意修改,无数据安全保障classStudent{public:doublem_score;// public成员变量};intmain(){Student stu;stu.m_score=150;// 非法值,无校验,直接修改成功return0;}
// 正确写法:成员变量私有化,通过接口访问classStudent{private:doublem_score;public:voidsetScore(doublescore){if(score>=0&&score<=100){m_score=score;}else{cout<<"成绩输入错误!"<<endl;}}};

误区4:protected成员可以被类外部访问

错误认知:protected成员和public成员类似,类外部可以访问;
正确认知:protected成员类外部无法访问,仅能在类内部、子类内部访问。

classPerson{protected:string m_name;// protected成员};intmain(){Person p;p.m_name="张三";// 错误:protected成员,类外部不可访问return0;}

误区5:访问权限修饰符影响成员函数的功能实现

错误认知:访问权限修饰符会改变成员函数的功能,比如private成员函数无法实现复杂逻辑;
正确认知:访问权限修饰符只限制访问范围,不影响成员函数的功能实现——private、public、protected成员函数的实现逻辑完全一致,只是能被访问的场景不同。

六、综合实战:完整类设计(结合三种访问权限)

结合本文所有知识点,设计一个完整的类体系(父类Animal、子类Dog),合理分配三种访问权限,演示访问权限的实际应用,代码可直接运行测试,巩固所学内容。

#include<iostream>#include<string>usingnamespacestd;// 父类:Animal(动物)classAnimal{// private:核心隐私,仅父类内部可访问private:string m_id;// 动物编号(核心隐私,不暴露、不继承)// protected:需要被子类继承,不允许外部访问protected:string m_name;// 动物名称(子类需要继承)intm_age;// 动物年龄(子类需要继承)// public:外部接口,供外部、子类外部访问public:// 构造函数(后续讲解,此处用于初始化)Animal(string name,intage){m_name=name;m_age=age;m_id="A"+to_string(rand()%1000);// 随机生成编号}// 接口:获取动物编号(间接访问private成员m_id)stringgetId(){returnm_id;}// 接口:显示动物基础信息(父类内部访问protected、private成员)voidshowBaseInfo(){cout<<"动物编号:"<<m_id<<",名称:"<<m_name<<",年龄:"<<m_age<<endl;}};// 子类:Dog(狗),继承AnimalclassDog:publicAnimal{// private:子类核心属性,仅子类内部可访问private:string m_breed;// 狗的品种// public:子类外部接口public:// 子类构造函数(后续讲解)Dog(string name,intage,string breed):Animal(name,age){m_breed=breed;}// 子类内部访问父类protected成员(m_name、m_age)voidshowDogInfo(){cout<<"狗的信息:"<<endl;cout<<"名称:"<<m_name<<",年龄:"<<m_age<<",品种:"<<m_breed<<endl;cout<<"编号:"<<getId()<<endl;// 子类访问父类public接口}// 子类接口:修改父类protected成员m_agevoidsetDogAge(intage){if(age>0&&age<20){m_age=age;// 直接访问父类protected成员,正确}else{cout<<"狗的年龄输入错误!"<<endl;}}};intmain(){// 创建父类对象Animalcat("猫咪",3);cat.showBaseInfo();// 访问父类public接口(正确)cout<<"猫咪编号:"<<cat.getId()<<endl;// 访问父类public接口(正确)// cat.m_name = "小花"; // 错误:m_name是protected,外部不可访问// 创建子类对象Dogdog("旺财",2,"中华田园犬");dog.showDogInfo();// 访问子类public接口(正确)dog.setDogAge(3);// 访问子类public接口,间接修改父类protected成员(正确)dog.showDogInfo();// 子类对象访问父类public接口(正确)dog.showBaseInfo();return0;}
代码说明
  • 父类Animal:m_id设为private(核心隐私),m_name、m_age设为protected(供子类继承),showBaseInfo()、getId()设为public(外部接口);

  • 子类Dog:m_breed设为private(子类核心属性),showDogInfo()、setDogAge()设为public(外部接口),子类内部可直接访问父类的protected成员m_name、m_age;

  • 类外部只能通过public接口访问成员,无法直接访问private、protected成员,完全遵循封装原则;

  • 继承场景下,protected成员的作用体现:子类无需通过父类接口,可直接访问父类protected成员,简化子类代码。

七、总结

类的访问权限(public、private、protected)是C++封装特性的核心,也是类设计的基础,其本质是“限制成员的访问范围”,实现“隐藏内部细节、暴露外部接口”的目的。

掌握访问权限的关键,不在于死记硬背访问范围,而在于理解“封装”的思想——合理分配访问权限,既能保证数据安全性,又能简化代码、提高可维护性。后续学习继承、多态时,protected的作用会更加突出,学好本文内容,能让你在后续的类体系设计中更加得心应手。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/3/15 16:24:05

Polish1.72.247|AI图片编辑,全球超1亿次下载,比美图秀秀好用

Polish是一款专为寻求高效且易用编辑解决方案的用户打造的功能强大的AI照片编辑工具。它借助先进的人工智能技术&#xff0c;从分辨率、亮度与阴影、色彩平衡等多方面提升照片质量。用户只需一键操作&#xff0c;就能自动调整照片的亮度、对比度、饱和度等参数&#xff0c;获取…

作者头像 李华
网站建设 2026/3/15 1:21:35

我彻底抛弃了NAS专用系统,爱上了Ubuntu桌面版

过去几年,我一直是用各种专用NAS系统:Synology的DSM、QNAP的QTS、真道的TrueNAS,甚至还玩过TerraMaster的TOS。它们确实好用,开箱即用,界面友好,适合大多数人。但最近,我做了一个大胆决定:彻底卸载了所有专用NAS操作系统,把我的NAS硬件直接装上了Ubuntu 24.04 LTS桌面…

作者头像 李华
网站建设 2026/3/15 20:22:19

赶deadline必备!专科生专属AI论文网站 —— 千笔

你是否曾为论文选题发愁&#xff0c;反复修改却总对结果不满意&#xff1f;是否在深夜面对空白文档无从下笔&#xff0c;又担心查重率过高而焦虑&#xff1f;专科生的论文之路本就不易&#xff0c;再加上时间紧迫、资料繁杂&#xff0c;更是让人倍感压力。如果你正经历这些写作…

作者头像 李华
网站建设 2026/3/15 20:22:21

改稿速度拉满!千笔ai写作,备受喜爱的一键生成论文工具

你是否曾在论文写作中感到力不从心&#xff1f;选题无头绪、框架混乱、文献查找困难、查重率高、格式错误频出……这些难题是否让你倍感焦虑&#xff1f;作为MBA学生&#xff0c;面对高强度的学术任务&#xff0c;你是否渴望一个高效、专业的写作助手&#xff1f;千笔AI&#x…

作者头像 李华
网站建设 2026/3/24 12:31:30

用Matlab实现交直流潮流计算:统一迭代法详解

交直流潮流计算程序matlab 通过统一迭代法实现&#xff0c;程序注释齐全&#xff0c;方便理解&#xff0c;通用性强&#xff0c;可根据需要改成相应的节点数量 在电力系统分析中&#xff0c;潮流计算是一项关键任务。今天咱们就来聊聊如何用Matlab通过统一迭代法实现交直流潮流…

作者头像 李华