资讯动态

【C++ 继承入门 1/2】访问权限、切片、隐藏与子类默认成员函数

发布时间:2026/10/23 4:59:40 来源:尧图企业网站定制
C 继承入门 1/2访问权限、切片、隐藏与子类默认成员函数一次理清哈喽大家好STL 系列之外咱们补一篇面向对象的基础——继承。继承是 C 面向对象三大特性之一也是后面多态的地基。这篇用 Person人和 Student学生两个类贯穿始终把继承格式、三种访问方式、切片、函数隐藏以及子类那几个默认成员函数到底怎么调父类的坑一次讲清。口语化讲解代码可直接编译放心食用~目录一、什么是继承在已有类上扩展二、先补一个访问限定符protected三、继承格式与三种继承方式3.1 继承格式冒号是标志3.2 权限怎么变Min 规则3.3 private 成员被继承了但看不见3.4 不写继承方式时的默认值四、切片子类可以赋给父类反过来不行五、函数隐藏同名就自动屏蔽父类六、子类的默认成员函数怎么调父类6.1 默认构造函数6.2 拷贝构造函数6.3 赋值运算符重载6.4 析构函数最特殊的一个七、类模板继承别忘了指定类域八、is-a 与 has-a留个小悬念九、总结一、什么是继承在已有类上扩展继承允许我们在保持原有类特性的基础上进行扩展增加自己的方法成员函数和属性成员变量从而复用代码、建立类与类之间的层次关系。被继承的一方叫父类基类继承的一方叫子类派生类。本篇用这两个角色贯穿Person人有姓名、年龄会打印信息Student学生是一种人额外有学号。学生是一种人所以让 Student 继承 Person就能白拿 Person 的姓名、年龄和打印能力只需要补上学号。二、先补一个访问限定符protected在封装阶段我们只学过public和private继承里多出一个新成员protected。它的权限强度介于两者之间限定符类内部类外部派生类内部public可访问可访问可访问protected可访问不可访问可访问private可访问不可访问不可访问一句话记住protected 对类外保密但对自己的子类开放。它就是专门为继承准备的家族内部可用、外人免进权限。三、继承格式与三种继承方式3.1 继承格式冒号是标志classPerson{public:voidPrint(){cout_nameendl;}protected:string _name;private:int_age;};// class Student : protected Person // 保护继承// class Student : private Person // 私有继承classStudent:publicPerson// 公有继承最常用{protected:int_stunum;// 学号子类自己新增的属性};看到类名后面的冒号了吗冒号就是继承的标志冒号后面写父类名和继承方式。3.2 权限怎么变Min 规则继承方式决定了父类的成员在子类里变成什么访问权限也决定了子类对象、子类派生链能不能访问这些成员。除了 private 成员单独处理外父类其他成员在派生类中的最终访问权限等于取两者的较小值派生类中的访问权限 Min(成员在基类的访问限定符, 继承方式)把 public 看作 3、protected 看作 2、private 看作 1谁小取谁基类成员 \ 继承方式public 继承protected 继承private 继承publicpublicprotectedprivateprotectedprotectedprotectedprivateprivate不可见不可见不可见可以看到public 继承最宽松成员权限原样保留private 继承最严父类成员到子类全部降级为 private。3.3 private 成员被继承了但看不见有个容易误解的点基类的 private 成员无论以什么方式继承在派生类中都是不可见的。注意不可见不等于不存在——父类的私有成员仍然被继承进了子类对象的内存里只是语法上限制不管在子类类内还是类外都不能直接访问它。子类想用得通过父类提供的 public/protected 接口间接拿。3.4 不写继承方式时的默认值有时能看到省略继承方式的写法classStudent:Person{};// class 默认 private 继承structStudent:Person{};// struct 默认 public 继承规则和声明类本身的默认权限一致用class默认是 private 继承用struct默认是 public 继承。但默认值只是编译器兜底实际开发中最好显式写出继承方式一眼看清意图也避免误读。四、切片子类可以赋给父类反过来不行在公有继承public前提下发生子类赋值给父类时会出现切片切割支持三种载体对象、指针、引用。Student s;Person p;ps;// 对象切片把子类对象赋给父类对象Person*ptrs;// 指针切片父类指针指向子类对象Personrefs;// 引用切片父类引用绑定子类对象普通赋值会产生临时变量再拷贝而切片相当于直接把子类中属于父类的那一部分切出来把父类的部分拷贝再赋给父类多余的子类部分比如学号被切掉丢弃。两条铁律✅子类可以赋值给父类对象 / 指针 / 引用都行❌父类不能赋值给子类——父类里压根没有子类新增的成员强行转过去就是非法访问。切片是后面多态的基础父类指针/引用能指向子类对象再配合虚函数就能表现出不同形态。这里先记住子赋父可以父赋子不行。五、函数隐藏同名就自动屏蔽父类当子类的成员函数和父类成员函数同名不管参数是否相同子类会自动屏蔽父类的同名函数这就叫隐藏name hiding。classPerson{public:voidPrint(){coutPersonendl;}};classStudent:publicPerson{public:voidPrint(){coutStudentendl;}// 隐藏了父类的 Print};intmain(){Student s;s.Print();// 调的是子类的 Print父类的被隐藏了s.Person::Print();// 想调父类的必须显式写父类类域}想调用被隐藏的父类函数要显式写出父类名加类域s.Person::Print()。实用建议尽量不要在子类里定义和父类同名的成员函数否则容易踩隐藏的坑也降低可读性。真要重写逻辑那是多态里虚函数重写的话题和这里的隐藏不是一回事别混淆。六、子类的默认成员函数怎么调父类类有六个默认成员函数继承后它们和父类的关系是面试高频逐个说。6.1 默认构造函数子类的默认构造函数里继承自父类的成员被视为一个整体会要求调用父类的默认构造函数来初始化这一部分。如果父类有默认构造编译器自动帮你调如果父类没有默认构造就必须在子类的初始化列表里显式声明调用父类构造classPerson{public:Person(conststringname):_name(name){}// 没有默认构造private:string _name;};classStudent:publicPerson{public:// 父类无默认构造必须在初始化列表显式调用Student(conststringname,intstunum):Person(name)// 先构造父类部分,_stunum(stunum){}private:int_stunum;};6.2 拷贝构造函数规则和默认构造一致子类拷贝构造时继承自父类的部分要调用父类的拷贝构造。显式写子类拷贝构造时把子类对象传给父类拷贝构造传参过程中会自动发生切片只拷贝父类那部分Student(constStudents):Person(s)// 传子类对象父类拷贝构造自动切片,_stunum(s._stunum){}只有当类里需要深拷贝资源时才需要自己写拷贝构造普通浅拷贝用编译器默认生成的就够。6.3 赋值运算符重载规则和拷贝构造基本一样显式写子类operator时要显式调用父类的赋值运算符来处理父类部分。注意子类和父类的operator也构成隐藏所以调用父类版本必须显式写父类类域Studentoperator(constStudents){if(this!s){Person::operator(s);// 显式调父类赋值s 在此切片_stunums._stunum;}return*this;}6.4 析构函数最特殊的一个析构和前面几个规则都不同这里有个常见误区// 错误示范不能这样手动调父类析构~Student(){~Person();// 语法报错也调不动父类析构}正确规则子类析构不需要也不能显式调用父类析构。子类析构执行完后编译器会自动调用父类析构保证析构顺序是先子后父构造是先父后子析构正好反过来。父子析构函数同样构成隐藏关系但因为有自动调用机制我们不用手动管。至于为什么析构常常要写成虚函数那是多态篇的内容先埋个伏笔。七、类模板继承别忘了指定类域继承不仅能继承普通类也能继承类模板。这里有个编译坑。比如想用继承实现一个 stack让它继承std::vectortemplateclassTclassstack:publicstd::vectorT{public:voidpush(constTx){// 必须指定类域 vectorT::否则报 C3861 找不到标识符vectorT::push_back(x);// push_back(x); // 这样写会报错}voidpop(){vectorT::pop_back();}constTtop(){returnvectorT::back();}};为什么直接写push_back(x)会报 C3861找不到标识符因为基类是类模板模板是按需实例化的stackint实例化时虽然也实例化了vectorint但push_back这些成员函数在没被用到时并不会实例化。编译器在 stack 类域里直接找push_back找不到就报错。解决办法显式指定基类类域vectorT::push_back(x)告诉编译器这个函数来自基类模板。八、is-a 与 has-a留个小悬念上面用继承实现 stack其实和之前讲过的容器适配器组合/包装 vector在功能上很像——都是在底层调用 vector 的成员函数。那继承和组合到底怎么选这里先给两个概念is-a是一个子类是一种父类比如 Student 是一种 Person适合用继承has-a有一个类里包含一个别的对象作为成员比如 stack 内部持有一个 vector适合用组合。stack 和 vector 的关系更接近 has-a所以标准库的 stack 用的是组合容器适配器而不是公开继承。这个区别后面讲适配器和多态时再展开感兴趣可以先自己琢磨。九、总结继承在父类基础上扩展父类基类子类派生类protected对类外保密、对子类开放。三种继承方式public / protected / private成员最终权限取 Min(基类权限, 继承方式)private 成员始终不可见但仍在对象里class 默认 private、struct 默认 public建议显式写。切片public 继承下子类可赋给父类对象/指针/引用父类不能赋给子类。函数隐藏子类与父类同名函数自动屏蔽父类调用父类需显式写类域尽量避免同名。默认成员函数构造/拷贝构造/赋值重载都要在初始化列表显式调父类对应版本析构特殊自动先子后父不能手动调。类模板继承基类成员要加vectorT::类域避免 C3861。选型上分清is-a继承和has-a组合。把 Person/Student 这套例子自己敲一遍再故意写一次切片和隐藏观察结果继承这关就稳了。如果这篇对你有帮助别忘了点赞收藏关注三连~ 咱们下篇见参考C 继承体系、访问控制、对象切片、名称隐藏与派生类默认成员函数规则

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑