
继承是面向对象编程(OOP)中的一个核心概念,它允许一个类(派生类或子类)继承另一个类(基类或父类)的属性和方法。 继承的核心思想是代码复用和创建类之间的层次关系。通过继承,我们可以定义一个通用的基类,包含一些通用的属性和方法,然后派生出更具体的子类,这些子类可以继承基类的特性,并添加自己独特的属性和方法。
下面我们举一个例子来帮助大家理解:
想象一下你正在管理一个动物园,里面有各种各样的动物。这些动物有一些共同的特征和行为,比如它们都需要吃东西、睡觉,同时不同种类的动物又有各自独特的行为,像鸟儿会飞翔,鱼儿会游泳。 为了更有条理地管理这些动物信息,我们可以先把动物们的共同特征和行为总结出来,形成一个通用的描述,然后再针对每种动物的独特之处进行单独描述。
Animal 来实现,我们把这个类成为 基类 或 父类 。class Animal {
public:
Animal(const std::string& n) : name(n) {}
void eat() {
std::cout << name << " is eating." << std::endl;
}
void sleep() {
std::cout << name << " is sleeping." << std::endl;
}
private:
std::string name;
};Brid 来实现对 Animal 类的继承,这个 Brid 类就叫做 派生类 或者 子类,代表是在基类的基础上继承而来的,同时也可以包含鸟类独有的特点和行为,比如翅膀,飞翔。class Bird : public Animal {
public:
Bird(const std::string& n) : Animal(n) {}
void fly() {
std::cout << getName() << " is flying." << std::endl;
}
};Fish 类也可以继承 Animal 类。鱼类有自己独特的行为,比如游泳。class Fish : public Animal {
public:
Fish(const std::string& n) : Animal(n) {}
void swim() {
std::cout << getName() << " is swimming." << std::endl;
}
};class DerivedClassName : access-specifier BaseClassName {
// 派生类成员定义
};派生类 (Derived Class)
基类 (Base Class)
访问说明符 (access-specifier)
可选值:public、protected、private
默认值:
class 定义的类:privatestruct 定义的类:public访问限定符说明:
类成员 \ 继承方式 | public 继承 | protected 继承 | private 继承 |
|---|---|---|---|
基类的 public 成员 | 派生类的 public | 派生类的 protected | 派生类的 private |
基类的 protected 成员 | 派生类的 protected | 派生类的 protected | 派生类的 private |
基类的 private 成员 | 不可见 | 不可见 | 不可见 |
private 成员在派生类中无论以什么方式继承都是不可见的。这里的不可见是指基类的私有成员虽然被继承到了派生类对象中,但是语法上限制派生类对象无论在类里面还是类外面都无法访问它。private 成员在派生类中是不能被访问,如果基类成员不想在类外直接被访问,但需要在派生类中能访问,就定义为 protected。可以看出保护成员限定符是因继承才出现的。Min(成员在基类的访问限定符,继承⽅式),public > protected >private。基类:
class Base {
public:
int publicVar; // 基类 public 成员
protected:
int protectedVar; // 基类 protected 成员
private:
int privateVar; // 基类 private 成员(所有继承方式均不可访问)
};class PublicDerived : public Base {
public:
void access() {
publicVar = 1; // 继承为 public → 类外可访问
protectedVar = 2; // 继承为 protected → 仅派生类内部可访问
}
};class ProtectedDerived : protected Base {
public:
void access() {
publicVar = 1; // 继承为 protected → 仅派生类内部可访问
protectedVar = 2; // 继承为 protected → 仅派生类内部可访问
}
};class PrivateDerived : private Base {
public:
void access() {
publicVar = 1; // 继承为 private → 仅派生类内部可访问
protectedVar = 2; // 继承为 private → 仅派生类内部可访问
}
};public继承,几乎很少使用protetced/private继承,也不提倡使用protetced/private继承,因为protetced/private继承下来的成员都只能在派生类的类里面使用,实际中扩展维护性不强。在面向对象编程中,“is - a” 关系指的是类的继承关系,即一个类(派生类)是另一个类(基类)的特殊化 ;“has -a” 关系指的是一个类包含另一个类的对象作为成员变量,即聚合关系。
is - a,也符合 has - avector 本质是 std 命名空间中实现的类模板:
template<class T>
class vector{};is - a 关系,让 stack 通过继承 vector 类模板来实现其功能。namespace test{
template <class T>
class stack : public std::vector<T> {
public:
void push(const T &x) {
std::vector<T>::push_back(x); //要指定类域
//或者this->push_back(x);
}
void pop(){
std::vector<T>::pop_back();
}
const T &top(){
return std::vector<T>::back();
}
bool empty(){
return std::vector<T>::empty();
}
};
}注意:模板类继承另一个模板类时,基类的成员函数需要通过作用域限定符或this指针访问

error C3861: “push_back”: 找不到标识符这里涉及到对编译器对C++类模板的编译编译过程:
两阶段名称查找(Two-phase name lookup)
C++模板的编译分为两个阶段:
对于继承自模板基类的成员访问,需要显式指明来源:
T,其成员函数 push_back() 属于依赖型名称(Dependent name),编译器在模板定义阶段无法确定这些成员是否存在。另一种解决方案是利用 this->push_back 替代
依赖型名称的标记
this 的类型是 Derived<T>*,与模板参数 T 相关当 stack<int> 被实例化时:
// 实例化后的代码等价形式
class stack<int> : public vector<int> {
public:
void push(const T &x) {
this->push_back(x);此时vecotr<int>已完全实例化
}
};vector<int> 中查找 push_back()测试:
int main(){
test::stack<int> st;
st.push(1);
st.push(2);
st.push(3);
while (!st.empty()) {
std::cout << st.top() << " ";
st.pop();
}
std::cout << std::endl;
return 0;
}输出:
3 2 1public 继承的派生类对象 可以赋值给 基类的指针 /基类的引用。这里有个形象的说法叫切片。寓意把派生类中基类那部分切出来,基类指针或引用指向的是派生类中切出来的基类那部分。
为什么呢?下面为大家分析基类和派生类之间的转换是如何进行的,以及底层的原理。
首先我们需要了解基类和派生类中的成员变量是如何在内存中存储的

基类和派生类的内存结构
关键点
定义
特点:
安全性问题:
这里会涉及到一个陷阱:
| 基类成员 | 派生类新增成员 | → 值传递后 → | 基类成员 |下面我们举个例子来让大家理解值传递向上转型的安全问题:
基类:
// 基类
class Animal {
public:
int age = 0;
virtual void speak() { // 虚函数
cout << "Animal sound (age: " << age << ")" << endl;
}
};派生类:
// 派生类
class Cat : public Animal {
public:
int lives = 9; // 派生类特有成员
void speak() override { // 覆盖虚函数
cout << "Meow (lives: " << lives << ", age: " << age << ")" << endl;
}
};值传递:
// 值传递函数:参数为基类对象
void processByValue(Animal animal) {
animal.speak(); // 调用虚函数
animal.age = 100; // 修改基类成员
}引用传递:
// 引用传递函数:参数为基类引用
void processByRef(Animal& animal) {
animal.speak();
animal.age = 200;
}main函数:
int main() {
Cat cat;
cat.age = 3;
cat.lives = 9;
cout << "----- 值传递 -----" << endl;
processByValue(cat); // 值传递触发对象切片
cout << "值传递后 cat 的 age: " << cat.age << endl; // age 未被修改
cout << "值传递后 cat 的 lives: " << cat.lives << endl; // lives 保持原值
cout << "\n----- 引用传递 -----" << endl;
processByRef(cat); // 引用传递保持多态性
cout << "引用传递后 cat 的 age: " << cat.age << endl; // age 被修改
return 0;
}运行结果:
Animal sound (age: 3)
值传递后 cat 的 age: 3
值传递后 cat 的 lives: 9
----- 引用传递 -----
Meow (lives: 9, age: 3)
引用传递后 cat 的 age: 200定义:
特点:
static_cast 或 dynamic_cast 显式转换,无法隐式完成。为什么需要向下转型:
当基类指针/引用实际指向的是派生类对象时,若需要访问派生类特有的成员(方法或属性),必须通过向下转型恢复其原始类型才能访问。
class Animal {}; // 基类
class Cat : public Animal {
public:
void meow() { /* 派生类特有方法 */ }
};
Animal* animalPtr = new Cat(); // 基类指针指向派生类对象
animalPtr->meow(); // 错误!基类指针无法直接访问派生类方法此时必须通过向下转型操作:
Cat* catPtr = static_cast<Cat*>(animalPtr); // 向下转型
catPtr->meow(); // 正确向下转型的两种方式
static_cast(静态转型)特点:
dynamic_cast(动态转型)特点:
若转换非法:
nullptr。std::bad_cast 异常。要求基类至少有一个虚函数(多态类型)。
安全性问题:
static_cast 的未检查风险
static_cast 在编译期完成类型转换,但不验证实际对象类型。典型UB(未定义行为)场景:
class Animal {};
class Cat : public Animal { public: void meow() {} };
class Dog : public Animal {};
Animal* animal = new Dog(); // 实际指向Dog对象
Cat* cat = static_cast<Cat*>(animal); // 编译通过,但实际类型不匹配
cat->meow(); // 未定义行为!可能崩溃或破坏内存dynamic_cast 的局限性
RTTI:若基类无虚函数,dynamic_cast 无法使用class Base {}; // 无虚函数
class Derived : public Base { public: void foo() {} };
Base* base = new Derived();
Derived* derived = dynamic_cast<Derived*>(base); // 编译错误!名字查找顺序:
示例:
class Base {
public:
int value = 10;
void print() { cout << "Base: " << value << endl; }
};
class Derived : public Base {
public:
int value = 20; // 隐藏基类的value成员
void print() { // 隐藏基类的print函数
cout << "Derived: " << value << endl;
cout << "Base::value: " << Base::value << endl; // 显式访问基类成员
}
};同名成员隐藏
class Base {
public:
void func(int x) { cout << "Base::func(int)" << endl; }
};
class Derived : public Base {
public:
void func(double x) { // 隐藏Base::func(int)
cout << "Derived::func(double)" << endl;
}
};
Derived d;
d.func(5); // 输出 "Derived::func(double)"(参数隐式转换)
d.Base::func(5); // 显式调用基类函数虚函数与作用域
覆盖(Override)条件:
virtual。隐藏非虚函数:
class Base {
public:
virtual void foo() { cout << "Base::foo" << endl; }
void bar() { cout << "Base::bar" << endl; }
};
class Derived : public Base {
public:
void foo() override { cout << "Derived::foo" << endl; } // 正确覆盖
void bar(int) { cout << "Derived::bar(int)" << endl; } // 隐藏Base::bar()
};
Derived d;
d.bar(); // 错误!Base::bar()被隐藏
d.Base::bar(); // 正确- 使用 using 声明解除隐藏
class Base {
public:
void func(int) {}
void func(double) {}
};
class Derived : public Base {
public:
using Base::func; // 引入基类所有重载版本的func
void func(const char*) {} // 添加新重载
};
Derived d;
d.func(5); // 调用Base::func(int)
d.func("abc"); // 调用Derived::func(const char*)作用域逐层嵌套
class A { public: void f() {} };
class B : public A { public: void f(int) {} }; // 隐藏A::f()
class C : public B { public: void f() {} }; // 隐藏B::f(int)
C c;
c.f(); // 调用C::f()
c.B::f(5); // 显式调用B::f(int)
c.A::f(); // 显式调用A::f()虚函数的多层覆盖
class A { public: virtual void f() { cout << "A::f" << endl; } };
class B : public A { public: void f() override { cout << "B::f" << endl; } };
class C : public B { public: void f() override { cout << "C::f" << endl; } };
C c;
A* ptr = &c;
ptr->f(); // 输出 "C::f"(动态绑定)6个默认成员函数,默认的意思就是指我们不写,编译器会变我们自动生成⼀个,那么在派生类中,这几个成员函数是如何生成的呢?

operator= 必须调用基类的 operator= 完成基类的复制。需要注意的是,派生类的 operator= 会隐藏基类的operator=,因此显式调用基类的 operator= 时,需指定基类作用域(例如Base::operator=)。由于多态中一些场景下析构函数需要构成重写(重写条件之一是函数名相同,具体在多态章节讲解),编译器会对析构函数名进行特殊处理,统一处理为 destructor()。因此,若基类析构函数未加 virtual,派生类析构函数与基类析构函数构成隐藏关系(而非重写)。

(Derived() = default;)
基类构造规则:
成员初始化:
示例:
class Base {
public:
Base(int x) : value(x) {} // 无默认构造函数
private:
int value;
};
class Derived : public Base {
public:
// 错误!基类无默认构造函数,必须显式调用
// Derived() = default;
// 正确:显式调用基类构造函数
Derived() : Base(0) {}
};(Derived(const Derived&) = default;)
基类拷贝规则:
成员拷贝规则:
示例:
class Base {
public:
Base(const Base&) { cout << "Base copy" << endl; }
};
class Derived : public Base {
public:
int* data;
// 默认拷贝构造函数行为:
// 1. 调用 Base::Base(const Base&)
// 2. 拷贝 data 指针(浅拷贝)
Derived(const Derived&) = default;
};
Derived d1;
d1.data = new int(10);
Derived d2 = d1; // 调用默认拷贝构造函数,data 指针被浅拷贝(Derived& operator=(const Derived&) = default;)
基类赋值规则:
成员赋值规则:
示例:
class Base {
public:
Base& operator=(const Base&) {
cout << "Base copy assign" << endl;
return *this;
}
};
class Derived : public Base {
public:
string str;
Derived& operator=(const Derived&) = default; // 自动调用基类拷贝赋值
};
Derived d1, d2;
d1 = d2; // 调用 Base::operator= 和 string::operator=(~Derived() = default;)
析构顺序:
虚析构函数:
virtual,则派生类析构函数自动成为虚函数。示例:
class Base {
public:
virtual ~Base() { cout << "~Base" << endl; }
};
class Derived : public Base {
public:
~Derived() { cout << "~Derived" << endl; }
};
Base* ptr = new Derived();
delete ptr; // 输出:~Derived → ~Base注意事项:
显式调用基类版本
class Derived : public Base {
public:
Derived(const Derived& d) : Base(d) { // 调用基类拷贝构造函数
// 拷贝派生类成员...
}
Derived& operator=(const Derived& d) {
Base::operator=(d); // 调用基类拷贝赋值
// 赋值派生类成员...
return *this;
}
};继承构造函数(C++11)
using Base::Base; 继承基类构造函数:class Base {
public:
Base(int x) {}
};
class Derived : public Base {
public:
using Base::Base; // 继承 Base(int x)
};
Derived d(5); // 合法C++继承体系中,友元函数是不可被继承的。基类的友元函数不会自动成为派生类的友元。也就是说基类友元不能访问派生类私有和保护成员 。除非派生类也声明该函数为友元函数。
class Base {
friend void foo(Base&);
private:
int a;
};
class Derived : public Base {
private:
int b;
};
void foo(Base& b) {
b.a = 42; // 合法:foo是Base的友元
// b.b = 42; // 错误:无法访问Derived的私有成员
}基类的友元函数可以通过基类引用/指针,访问派生类对象中继承自基类的私有成员。
Derived d;
foo(d); // 合法:传递派生类对象给基类引用派生类需显式声明友元
class B {
friend class A;
private:
int secret;
};
class A {};
class C : public A {};
void test() {
C c;
// c.secret = 42; // 错误:C不是B的友元
}静态成员属于定义它的类,不会被派生类继承,但可以通过作用域运算符 (
::) 访问。基类定义了static静态成员,则整个继承体系里面只有⼀个这样的成员。无论派⽣出多少个派生类,都只有⼀个static成员实例。 派生类可以直接访问基类的 公有(public)或保护(protected)静态成员。
示例:
class Base {
public:
static int count; // 静态成员声明
static void print() { cout << "Base: " << count << endl; }
};
int Base::count = 0; // 静态成员初始化
class Derived : public Base {
public:
void increment() {
Base::count++; // 合法:访问基类的公有静态成员
}
};
int main() {
Derived d;
d.increment();
Base::print(); // 输出 "Base: 1"
Derived::print(); // 同样合法:调用基类的静态函数
}静态数据成员必须在类外单独初始化,且初始化位置不影响继承。即使通过派生类访问基类的静态成员,初始化仍需在基类作用域中完成
class Base {
public:
static int x;
};
int Base::x = 100; // 必须初始化
class Derived : public Base {};
int main() {
Derived::x = 200; // 修改基类的静态成员
cout << Base::x; // 输出 200
}静态成员函数不能是虚函数,因为它们不依赖于对象实例(没有
this指针)。 即使派生类定义了同名的静态函数,也不会覆盖基类的静态函数。
示例:
class Base {
public:
static void foo() { cout << "Base::foo\n"; }
};
class Derived : public Base {
public:
static void foo() { cout << "Derived::foo\n"; }
};
int main() {
Derived::foo(); // 输出 "Derived::foo"
Derived::Base::foo(); // 输出 "Base::foo"
}如果派生类定义了与基类同名的静态成员,基类的静态成员会被隐藏。 需要通过作用域运算符 (Base
::) 显式访问基类的静态成员。
class Base {
public:
static int value;
};
int Base::value = 10;
class Derived : public Base {
public:
static int value; // 隐藏基类的静态成员
};
int Derived::value = 20;
int main() {
cout << Base::value; // 输出 10
cout << Derived::value; // 输出 20
cout << Derived::Base::value; // 输出 10(显式访问基类静态成员)
}
定义
内存模型
class Animal {
public:
int age;
};
class Dog : public Animal { // 单继承
public:
int weight;
};
int main() {
Dog dog;
dog.age = 2; // 访问基类成员
dog.weight = 10; // 访问派生类成员
Animal* ptr = &dog; // 合法:基类指针指向派生类对象
return 0;
}定义
内存模型
class LandAnimal {
public:
void walk() { cout << "Walking\n"; }
};
class WaterAnimal {
public:
void swim() { cout << "Swimming\n"; }
};
class Frog : public LandAnimal, public WaterAnimal { // 多继承
public:
void jump() { cout << "Jumping\n"; }
};
int main() {
Frog frog;
frog.walk(); // 调用 LandAnimal 方法
frog.swim(); // 调用 WaterAnimal 方法
frog.jump(); // 调用派生类方法
// 显式指定基类指针类型
LandAnimal* landPtr = &frog;
WaterAnimal* waterPtr = &frog;
return 0;
}Diamond Inheritance
问题
示例代码(问题演示)
class Person {
public:
string name;
};
class Student : public Person {}; // 继承 Person
class Teacher : public Person {}; // 继承 Person
class Assistant : public Student, public Teacher {}; // 菱形继承
int main() {
Assistant assistant;
// assistant.name = "Alice"; // 错误:二义性(无法确定是 Student::name 还是 Teacher::name)
assistant.Student::name = "Alice"; // 显式指定路径
assistant.Teacher::name = "Bob"; // 数据冗余:Person 被存储两次
return 0;
}解决方案:虚继承(Virtual Inheritance)
virtual 关键字声明基类,确保公共基类在派生类中只保留一份。class Person {
public:
string name;
};
class Student : virtual public Person {}; // 虚继承
class Teacher : virtual public Person {}; // 虚继承
class Assistant : public Student, public Teacher {};
int main() {
Assistant assistant;
assistant.name = "Alice"; // 合法:Person 只保留一份
return 0;
}
template<class CharT, class Traits = std::char_traits<CharT>>
class basic_ostream : virtual public std::basic_ios<CharT, Traits>
{};
template<class CharT, class Traits = std::char_traits<CharT>>
class basic_istream : virtual public std::basic_ios<CharT, Traits>
{};“is-a” 的关系。也就是说每个派生类对象都是一个基类对象。“has-a” 的关系。假设 B 组合了 A,那么每个 B 对象中都有一个 A 对象。white - box reuse)。术语“白箱”是相对于可视性而言的:在继承方式中,基类的内部细节对派生类可见。继承在一定程度上破坏了基类的封装性,基类的改变会对派生类产生很大的影响。派生类和基类之间的依赖关系很强,耦合度高。black - box reuse),因为对象的内部细节是不可见的。对象只以 “黑箱” 的形式出现。组合类之间没有很强的依赖关系,耦合度低。优先使用对象组合有助于保持每个类的封装性。优先使用组合,而不是继承。实际上,应尽量多使用组合,因为组合的耦合度低,代码维护性好。不过也不能过于绝对,如果类之间的关系适合继承(is - a),那就使用继承;另外,要实现多态,也必须使用继承。如果类之间的关系既适合用继承(is - a),也适合用组合(has - a),则优先使用组合。
A和B类中的两个func构成什么关系()
下面程序的编译运行结果是什么()
#include <iostream>
using namespace std;
class A
{
public:
void fun()
{
cout << "func()" << endl;
}
};
class B : public A
{
public:
void fun(int i)
{
cout << "func(int i)" << i << endl;
}
};
int main()
{
B b;
b.fun(10);
b.fun();
return 0;
};多继承中指针偏移问题?下⾯说法正确的是( )
class Base1
{
public:
int _b1;
};
class Base2
{
public:
int _b2;
};
class Derive : public Base1, public Base2
{
public:
int _d;
};
int main()
{
Derive d;
Base1 *p1 = &d;
Base2 *p2 = &d;
Derive *p3 = &d;
return 0;
}