C++继承

发布于:2025-08-01 ⋅ 阅读:(19) ⋅ 点赞:(0)

一、继承基础

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(汽车有轮胎)
耦合度 高(破坏封装) 低(接口清晰)
复用类型 白箱复用(内部可见) 黑箱复用(内部隐藏)
基类/成员改动影响 影响派生类 影响较小

 黄金准则

  1. 优先使用组合(降低耦合)
  2. 仅当is-a关系明确时使用继承
  3. 避免多继承,严禁菱形继承

四、继承小知识点

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++中唯一完全不可继承的关系。静态成员在继承体系中保持唯一实例,普通成员变量无论访问权限都会被继承(私有成员虽不可访问但占用内存)。

基类成员类型 是否被继承 派生类访问权限
普通成员变量 遵守继承权限规则
静态成员变量 ✅(唯一实例) 同普通变量
成员函数 遵守继承权限规则
静态成员函数 同普通函数
友元函数 无任何特殊权限
构造函数 不可继承
析构函数 不可继承

网站公告

今日签到

点亮在社区的每一天
去签到