一、继承基础
1.继承概念
继承允许新类(派生类)基于现有类(基类)创建,保留基类特性并扩展新功能。这实现了类设计层次的代码复用。
class Person {
public:
void Print() {
cout << "name:" << _name << "\nage:" << _age;
}
protected:
string _name = "peter";
int _age = 18;
};
class Student : public Person { // public继承
protected:
int _stuid; // 新增学号成员,派生类Student基于现有类Person进行扩展
};
在上述代码中,继承后基类Person的成员(成员函数和成员变量),都会变为派生类Student的一部分,体现了Student对Person的复用。
2.继承的本质
继承的本质是复用,将每个重复使用的模块剥离开,成为一个单独的模块(基类),需要使用这个模块时就直接拷贝到派生类中使用。
class Person
{
public:
Person()
{}
void Print()
{
cout << "name:" << _name << endl;
}
string _name = "peter";
};
class Student : public Person
{
public:
void func()
{
cout << "name:" << _name << endl;
}
void printid()
{
cout << _stuid << endl;
}
protected:
int _stuid = 0000;//学号
};
int main()
{
Student s1;
s1._name = "Tom";//修改继承的name不会改变基类中person中的值
s1.func();//这输出Tom
Person p;
p.Print();//这输出Peter
}
继承后,基类的成员会拷贝一份到派生类中,变成派生类的一部分。 从上述程序可以发现,修改派生类中的基类成员,不会改变原本的基类成员,说明,在继承中,派生类在结构上继承了原来的基类,但派生类实例化后,继承的基类与原来的基类不是同一个。
二、关键特性深度解析
1. 访问权限变化规则
基类成员/继承方式 | public继承 | protected继承 | private继承 |
---|---|---|---|
public成员 | public | protected | private |
protected成员 | protected | protected | private |
private成员 | 不可见 | 不可见 | 不可见 |
核心规则:
- 基类private成员在任何继承中均不可直接访问
- 派生类访问权限 =
min(基类访问权限, 继承方式)
- 默认继承方式:
class
为private,struct
为public(建议显式声明)
2. 对象切片(赋值转换)
1.切片:派生类对象可以赋值给基类的对象/指针/引用 。
2.基类不能赋值给派生类。
Student sobj;
Person pobj = sobj; // ✅ 派生类→基类(切片)
// sobj = pobj; // ❌ 禁止基类→派生类
Person* pp = &sobj; // ✅ 指针转换
Student* ps1 = (Student*)pp; // 强制转换(需确保安全)
int main()
{
Student s;
Person* ptr = &s;//将student中person部分切片,ptr指向切片,ptr指向切片部分的头部,且只能访问派生类中基类的部分
Person& ref = s;//ref引用s中person的部分,ptr指向切片部分的头部,且只能访问派生类中基类的部分
ptr->_name = "xxx";
cout << (*ptr)._name << endl;//ptr和ref指向的都是s中属于person区域的部分,修改ptr和ref指向区域的值,s也会改变。
cout << s._name << endl;
cout << ref._name << endl;
ref._name = "yyy";
cout << ref._name << endl;
cout << s._name << endl;
cout << (*ptr)._name << endl;
//cout << ref.printid() << endl;//这里会报错,说明派生类到基类后,只能访问基类结构中包含的元素
}
在上述程序中,将派生类赋值给基类的指针和引用,修改指针ptr,派生类和基类引用均会改变,修改引用ref,派生类和基类指针均会改变。说明切片的基类指针和引用,都指向派生类的那块地址。
3. 作用域与隐藏
同名成员会形成隐藏(重定义):
class Person {
protected:
int _num = 111; // 身份证号
};
class Student : public Person {
public:
void Print() {
cout << Person::_num; // 显式访问基类成员
cout << _num; // 访问派生类成员
}
protected:
int _num = 999; // 学号(隐藏基类_num)
};
重要:成员函数只需函数名相同即构成隐藏,与参数无关!
4. 派生类默认成员函数
class Student : public Person {
public:
// 构造函数必须调用基类构造
Student(const char* name, int num)
: Person(name), _num(num) {}
// 拷贝构造需调用基类拷贝构造
Student(const Student& s)
: Person(s), _num(s._num) {}
// 赋值运算符重载
Student& operator=(const Student& s) {
if (this != &s) {
Person::operator=(s); // 调用基类operator=
_num = s._num;
}
return *this;
}
// 析构函数自动调用基类析构(先派生后基类)
~Student() { /* 自动调用~Person() */ }
};
5.继承实现
//基类
class Person
{
public:
Person(const char* name = " ")//默认构造函数
:_name(name)
{
cout << "Person()" << endl;
cout << _name << endl;
}
Person(const Person& p)
:_name(p._name)//拷贝构造
{
cout << " Person(const Person& p) " << endl;
}
Person& operator=(const Person& p)//赋值
{
cout << "Person& operator=(const Person& p)" << endl;
if (this != &p)
{
_name = p._name;
}
return *this;
}
~Person()
{
cout << "~Person" << endl;
delete[] _str;
//string是自定义类型,析构释,会自动调用他的析构,所以这里只需要手动释放_str的空间即可
}
void func()
{
cout << _name << endl;
}
protected:
string _name;//姓名
char* _str = new char[10] {'x', 'y', 'z'};
};
//派生类
class Student :public Person
{
public:
Student(const char* name = " ", int x = 0, const char* address = " ")
:Person(name)//切片,派生类中初始化基类,要调用基类的默认构造
,_x(x)
,_address(address)
,_name(Person::_name + 'x')
{
cout << "Student(const char* name = " ", int x = 0, const char* address = " ") " << endl;
}
Student(const Student& st)
:Person(st)//调用基类的拷贝构造Person
,_x(st._x)
,_address(st._address)
,_name(Person::_name)
{
cout << "Student(const Student& st)" << endl;
}
Student& operator=(const Student& st)
{
if (this != &st)
{
Person::operator=(st);//调用基类的赋值
_x = st._x;
_address = st._address;
}
return *this;
}
~Student()//析构会自动调用基类的析构和自定义类型的析构
{
cout << "~Student()" << endl;
cout << _str << endl;
}
protected:
int _x = 1;
string _address = "厦门";
string _name;
};
int main()
{
Student s1;
Student s2("张三", 1, "厦门集美");
return 0;
}
三、继承VS组合
特性 | 继承 | 组合 |
---|---|---|
关系类型 | is-a(学生是一个人) | has-a(汽车有轮胎) |
耦合度 | 高(破坏封装) | 低(接口清晰) |
复用类型 | 白箱复用(内部可见) | 黑箱复用(内部隐藏) |
基类/成员改动影响 | 影响派生类 | 影响较小 |
黄金准则:
- 优先使用组合(降低耦合)
- 仅当is-a关系明确时使用继承
- 避免多继承,严禁菱形继承
四、继承小知识点
1.继承权限:始终显式声明访问控制符(public/protected/private)和继承方式,避免依赖默认规则导致的意外行为。
特性 | class | struct |
---|---|---|
默认成员访问 | private | public |
默认继承方式 | private | public |
能否省略继承方式 | ✅(默认private) | ✅(默认public) |
2.基类私有成员会被继承但不可访问,这是C++封装性的核心设计。继承权限影响的是基类成员在派生类中的可见性,而基类对象自身的访问规则保持不变。
3.
- 同名隐藏的本质:派生类成员会隐藏基类所有同名成员(无论函数参数/变量类型)
- 访问被隐藏成员的方法:使用作用域解析符
BaseClass::member
- 在基类和派生类中,允许存在同名成员变量或函数(无论参数是否相同),此时派生类成员会隐藏基类同名成员。
4.当基类没有默认构造函数时,派生类必须在初始化列表中显式调用基类构造函数.
5.构造/析构顺序
阶段 | 顺序 |
---|---|
构造顺序 | 1. 基类构造 → 2. 派生类成员构造 → 3. 派生类构造函数体 |
析构顺序 | 1. 派生类析构函数体 → 2. 派生类成员析构 → 3. 基类析构 |
当基类没有默认构造时,必须参考基类构造函数定义派生类构造。构造由外而内(基类→派生),析构由内而外(派生→基类)。派生类析构函数只需释放自身资源,基类析构由编译器自动调用完成。
6.友元关系是C++中唯一完全不可继承的关系。静态成员在继承体系中保持唯一实例,普通成员变量无论访问权限都会被继承(私有成员虽不可访问但占用内存)。
基类成员类型 | 是否被继承 | 派生类访问权限 |
---|---|---|
普通成员变量 | ✅ | 遵守继承权限规则 |
静态成员变量 | ✅(唯一实例) | 同普通变量 |
成员函数 | ✅ | 遵守继承权限规则 |
静态成员函数 | ✅ | 同普通函数 |
友元函数 | ❌ | 无任何特殊权限 |
构造函数 | ❌ | 不可继承 |
析构函数 | ❌ | 不可继承 |