子类声明必须显式指定public、protected或private继承方式,决定父类成员访问权限;构造函数不继承,需在初始化列表中显式调用父类构造函数;同名函数会隐藏而非覆盖父类版本,须用Base::func()访问。

子类声明时如何指定继承方式
继承方式决定父类成员在子类中的访问权限,必须显式写出,不能省略。C++ 支持三种:public、protected、private,最常用的是 public 继承。
-
public继承:父类的public成员在子类中仍为public,protected保持protected,private不可访问 -
protected继承:父类所有非private成员在子类中变为protected -
private继承(极少用):父类所有非private成员在子类中变为private
错误写法:class Derived : Base —— 缺少冒号和继承关键字,编译报错:expected ‘:’ before ‘{’ token。
构造函数不能被继承,但必须显式调用父类构造函数
C++11 起支持使用 using 声明继承部分构造函数(仅限无默认参数、无重载冲突的情况),但绝大多数场景下,子类需自己定义构造函数,并在初始化列表中调用父类构造函数。
- 父类没有默认构造函数时,子类构造函数**必须**在初始化列表中显式调用父类带参构造函数
- 遗漏调用会导致编译错误:
no matching function for call to ‘Base::Base()’ - 若父类有默认构造函数,不显式调用也不会报错,但逻辑可能不符合预期(比如父类成员未按需初始化)
class Base {
public:
Base(int x) : val(x) {}
private:
int val;
};
class Derived : public Base {
public:
Derived(int x, int y) : Base(x), data(y) {} // ✅ 正确:显式调用 Base(int)
private:
int data;
};
子类中访问父类同名成员需注意作用域与隐藏规则
子类定义了与父类同名的函数(无论参数是否相同),父类该名字的所有重载版本都会被**隐藏**,而非重载或覆盖。这是初学者最常踩的坑。
立即学习“C++免费学习笔记(深入)”;
- 想调用被隐藏的父类函数,必须用作用域解析符:
Base::func() - 仅当子类函数签名与父类虚函数完全一致且加了
override,才是覆盖(override);否则是隐藏(hiding) - 数据成员同名也会隐藏,但通常应避免——编译器不会报错,但语义混乱
class Base {
public:
void print() { std::cout << "Base\n"; }
void print(int x) { std::cout << "Base " << x << "\n"; }
};
class Derived : public Base {
public:
void print() override { std::cout << "Derived\n"; } // 隐藏了 Base::print() 和 Base::print(int)
// 下面这行会编译失败,除非显式写 Base::print(42)
// void foo() { print(42); }
void foo() { Base::print(42); } // ✅ 显式调用
};
多重继承时要注意菱形继承与虚基类
当两个父类都继承自同一个祖父类,而子类又同时继承这两个父类时,若不使用 virtual,子类会包含两份祖父类子对象,引发二义性和内存冗余。
- 解决方法:在中间继承路径上使用
virtual public Base - 虚基类的构造函数由**最派生类**直接调用,中间类的初始化列表中对虚基类构造函数的调用会被忽略
- 虚继承带来轻微性能开销(额外指针、间接寻址),仅在必要时使用
典型错误现象:error: ‘Base’ is an ambiguous base of ‘Derived’,或访问 Base 成员时报二义性。
sizeof 检查布局,用 gdb 或 clang -cc1 -ast-dump 看实际符号解析路径。









