一、继承是什么?解决了什么问题?
在面向对象编程中,复用是永恒的追求。函数实现了代码块的复用,而将复用提升到了类设计的层次。
深入解析 C++ 继承机制,涵盖概念、语法细节及访问控制规则。重点讲解了三种继承方式对成员可见性的影响,作用域隐藏与函数重载的区别,以及派生类默认成员函数的执行顺序。此外,还探讨了友元、静态成员及多继承中的菱形问题解决方案(虚继承)。最后对比了继承与组合的适用场景,强调优先使用组合以降低耦合度,仅在明确 is-a 关系或多态需求时使用继承。

在面向对象编程中,复用是永恒的追求。函数实现了代码块的复用,而将复用提升到了类设计的层次。
以设计 Student(学生)和 Teacher(老师)两个类为例,两者都有姓名、地址、电话、年龄等共性属性,以及身份认证等共性行为;但也有差异 —— 学生有学号和学习方法,老师有职称和授课方法。
如果不使用继承,代码会存在大量冗余:
// 学生类
class Student {
public:
void identity() {} // 重复定义
void study() {}
protected:
string _name = "peter";
string _address;
string _tel;
int _age = 18;
int _stuid;
};
// 老师类
class Teacher {
public:
void identity() {} // 重复定义
void teaching() {}
protected:
string _name = "张三";
string _address;
string _tel;
int _age = 18;
string _title;
};
identity() 方法和 4 个属性完全重复,增加了代码量且埋下修改隐患。继承的核心思想是将共性抽离成基类(父类),让派生类(子类)继承特性并补充独有内容。
重构后的代码:
// 基类:抽离共性
class Person {
public:
void identity() { cout << "身份认证:" << _name << endl; }
protected:
string _name = "张三";
string _address;
string _tel;
int _age = 18;
};
// 派生类 Student:继承 Person
class Student : public Person {
public:
void study() {}
protected:
int _stuid;
};
// 派生类 Teacher:继承 Person
class Teacher : public Person {
public:
void teaching() {}
protected:
string _title;
};
int main() {
Student s;
Teacher t;
s.identity(); // 输出:身份认证:张三
t.identity(); // 输出:身份认证:张三
return 0;
}
基类成员在派生类中的访问权限,由基类的访问限定符和继承方式共同决定。
class 派生类名 : 继承方式 基类名 {
// 派生类的成员
};
例如 class Student : public Person 表示 Student 以 public 方式继承 Person。
| 基类成员类型 | public 继承 | protected 继承 | private 继承 |
|---|---|---|---|
| public 成员 | 派生类 public | 派生类 protected | 派生类 private |
| protected 成员 | 派生类 protected | 派生类 protected | 派生类 private |
| private 成员 | 不可见 | 不可见 | 不可见 |
关键点:
private 成员会被继承到对象中,但禁止访问。实例验证:
class Person {
public:
void Print() { cout << _name << endl; }
protected:
string _name = "张三";
private:
int _age = 18;
};
class Student : public Person {
public:
void Test() {
Print(); // 可以访问
_name = "李四"; // 可以访问
// _age = 20; // 编译报错
}
};
int main() {
Student s;
s.Print(); // 可以访问
// s._name = "王五"; // 编译报错
return 0;
}
class 默认为 private,struct 默认为 public(建议显式写出)。public 继承。protected 或 private 继承限制后续扩展,维护性差。基类和派生类拥有独立的作用域,同名成员会触发'隐藏'规则。
class Person {
protected:
string _name = "小李子";
int _num = 111; // 身份证号
};
class Student : public Person {
public:
void Print() {
cout << "姓名:" << _name << endl; // 继承的_name
cout << "身份证号:" << Person::_num << endl; // 加作用域
cout << "学号:" << _num << endl; // 派生类的_num,隐藏基类
}
protected:
int _num = 999; // 学号
};
int main() {
Student s;
s.Print();
return 0;
}
class A {
public:
void func() { cout << "func()" << endl; }
};
class B : public A {
public:
void func(int i) { cout << "func(int i): " << i << endl; }
};
int main() {
B b;
b.func(10); // 运行:调用 B::func
b.func(); // 编译报错:B::func 隐藏了 A::func
b.A::func(); // 解决方案:显式加作用域
return 0;
}
class Person {
public:
Person(const char* name = "peter") : _name(name) { cout << "Person 构造" << endl; }
Person(const Person& p) : _name(p._name) { cout << "Person 拷贝构造" << endl; }
Person& operator=(const Person& p) { if (this != &p) _name = p._name; cout << "Person 赋值重载" << endl; return *this; }
~Person() { cout << "Person 析构" << endl; }
protected:
string _name;
};
class Student : public Person {
public:
Student(const char* name, int num) : Person(name), _num(num) { cout << "Student 构造" << endl; }
Student(const Student& s) : Person(s), _num(s._num) { cout << "Student 拷贝构造" << endl; }
Student& operator=(const Student& s) {
if (this != &s) { Person::operator=(s); _num = s._num; }
cout << "Student 赋值重载" << endl; return *this;
}
~Student() { cout << "Student 析构" << endl; }
protected:
int _num;
};
int main() {
Student s1("jack", 18);
Student s2(s1);
Student s3("rose", 17);
s1 = s3;
return 0;
}
运行结果顺序:构造 Person→Student,析构 Student→Person。
private。final 关键字修饰基类。class Base final {
public:
void func() { cout << "Base::func" << endl; }
};
// class Derive : public Base {}; // 编译报错
基类的友元无法访问派生类的 private/protected 成员。
class Student;
class Person {
public:
friend void Display(const Person& p, const Student& s);
protected:
string _name = "张三";
};
class Student : public Person {
friend void Display(const Person& p, const Student& s);
protected:
int _stuNum = 1001;
};
void Display(const Person& p, const Student& s) {
cout << p._name << endl;
// cout << s._stuNum << endl; // 编译报错
}
基类的静态成员在整个继承体系中只有一份实例。
class Person {
public:
static int _count;
protected:
string _name;
};
int Person::_count = 0;
class Student : public Person {};
class Teacher : public Person {};
int main() {
Person p; Student s; Teacher t;
p._count++; s._count++; t._count++;
cout << Person::_count << endl; // 输出 3
return 0;
}
单继承:一个派生类只有一个直接基类。 多继承:一个派生类有多个直接基类,可能引发'菱形继承'问题。
Assistant 同时继承 Student 和 Teacher,而两者都继承 Person,导致 Assistant 中有两份 Person 成员,造成数据冗余和二义性。
在间接基类继承处加 virtual 关键字。
class Person { public: string _name; };
class Student : virtual public Person { protected: int _num; };
class Teacher : virtual public Person { protected: int _id; };
class Assistant : public Student, public Teacher { protected: string _major; };
int main() {
Assistant a;
a._name = "peter"; // 正常访问,无二义性
return 0;
}
注意:虚继承增加性能开销,不建议设计菱形结构。
// 场景 1:车和轮胎(has-a,用组合)
class Tire { protected: string _brand = "米其林"; int _size = 17; };
class Car { protected: string _color = "白色"; Tire _t1, _t2, _t3, _t4; };
// 场景 2:宝马和车(is-a,用继承)
class BMW : public Car { public: void Drive() { cout << "宝马:操控好" << endl; } };
// 场景 3:栈和 vector(优先组合)
class Stack2 {
private:
vector<int> _v;
public:
void push(int x) { _v.push_back(x); }
int top() { return _v.back(); }
};
当类之间明确是'is-a'关系且需要多态时,用继承。当类之间是'has-a'关系或关系模糊时,优先用组合。
private 成员不可见,优先用 public 继承。掌握继承的细节和陷阱,才能写出更优雅、更可维护的代码。

微信公众号「极客日志」,在微信中扫描左侧二维码关注。展示文案:极客日志 zeeklog
将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online
将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online
将 Markdown(GFM)转为 HTML 片段,浏览器内 marked 解析;与 HTML转Markdown 互为补充。 在线工具,Markdown转HTML在线工具,online
将 HTML 片段转为 GitHub Flavored Markdown,支持标题、列表、链接、代码块与表格等;浏览器内处理,可链接预填。 在线工具,HTML转Markdown在线工具,online
通过删除不必要的空白来缩小和压缩JSON。 在线工具,JSON 压缩在线工具,online
将JSON字符串修饰为友好的可读格式。 在线工具,JSON美化和格式化在线工具,online