C++ : 继承

一、什么是继承

概念
继承(inheritance)机制是面向对象程序设计使代码可以复用的最重要的手段,它允许程序员在保持原有类特性的基础上进行扩展,增加功能,这样产生新的类,称派生类(或子类),被继承的类称基类(或父类)。

概念当然是苦涩难懂的,形式还是要走一下的,其实继承顾名思义就是传t承上一个类的成员变量和成员函数,既然类的成员会被访问限定符限制,继承也有继承方式:pubilc 、private 、protect
在这里插入图片描述
继承方式:

在这里插入图片描述
而继承下来的并不是只有单一的继承方式约束,还有成员的访问限定符约束
在这里插入图片描述
认真观察,不需要花时间记,其实访问限定符和继承方式取那个最小的

为继承而生的访问级别:protect
public公有级别能被外界直接访问,private只能被在类内部和类成员函数访问,子类继承父类后,便拥有了父类的所有属性,那么这个时候,子类能直接访问父类的私有成员吗?答案是不能,用protect修饰的成员,跟私有成员一样,无法被外界直接访问,但是能被子类直接访问。也可以说,protect就是专门为继承而生的。

二、子类和父类(基类和派生类)

class Person
{
public:
	int _age;
	string _name;
}
class Student
{
public:
	int _stu_id;
}

创建两个类: Person p,Student s
可以p = s, 却 s = p这样的行为编译不给过,会报错

在这里插入图片描述
为什么子类能轻松的赋给父类呢?
这里我们要了解一个东西叫做 切片或者 切割
就是派生类对象 可以赋值给 基类的对象 / 基类的指针 / 基类的引用。寓意把派生类中父类那部分切来赋值过去

在这里插入图片描述
通俗的理解,因为父类有的,子类一定有,而子类有的,父类不一定有,要是父类对象传给子类对象时,值有可能少传,所以当然是不可以这样的

三种赋值
1.赋值

	student s;//子类
	Person p;//父类
	p = s;

在这里插入图片描述
2.引用

Student s;
Person& p = s;

在这里插入图片描述
3. 指针

Student s;
Person* pPtr = &s;

在这里插入图片描述
这里的知识会在多态体现重要性

继承中的作用域

  1. 在继承体系中基类和派生类都有独立的作用域。
  2. 子类和父类中有同名成员,子类成员将屏蔽父类对同名成员的直接访问,这种情况叫隐藏,也叫重定义。(在子类成员函数中,可以使用 基类::基类成员 显示访问)
  3. 需要注意的是如果是成员函数的隐藏,只需要函数名相同就构成隐藏。
  4. 注意在实际中在继承体系里面最好不要定义同名的成员
class Person
{
protected :
string _name = "小吴"; // 姓名
int _num = 123456; // 身份证号
};
class Student : public Person
{
protected:
int _num = 999; // 学号
};

此时Student::_num与Person::_num构成隐藏

在这里插入图片描述

class AA
{
public:
	void func(int i)
	{
		cout << "AA:func" << endl;
	}
};

class BB : public AA 
{
public:
	void func()
	{
		cout << "BB:func" << endl;
	}
};

只要函数名相同,也会构成隐藏,对参数列表没有要求。

在这里插入图片描述

三、子类的默认成员函数

1.构造函数
编译器会默认先调用父类的构造函数,再调用子类的构造函数,如下

class Person {
public:
	Person(string name = "小明")
		:_name(name)
	{
		cout << "Person()" << endl;
	}
protected:
	string _name;
};


class Student :public Person {
public:
	Student(string name, int age)
		:_age(age)
	{
		cout << "Student()" << endl;
	}
protected:
	int _age;
};

在这里插入图片描述
编译器先调用了父类的构造,再调用了子类的构造
Person的构造是给了缺省值的,若是没给,此时
在这里插入图片描述
可是创建s给了值,却没用呢
其实要对Student的构造函数修改下

Student(string name, int age)
		:Person(name)
		,_age(age)
	{
		cout << "Student()" << endl;
	}

最好在子类的初始化列表中主动调用父类的构造函数

在这里插入图片描述

2.析构函数
析构函数和构造函数相反,编译器默认先调用子类的析构函数,再调用父类的析构函数。

再对上面的原有代码上去多写两个析构函数

class Person {
public:
	Person(string name)
		:_name(name)
	{
		cout << "Person()" << endl;
	}

	~Person()
	{
		cout << "~Person()" << endl;
	}
protected:
	string _name;
};


class Student :public Person {
public:
	Student(string name, int age)
		:_age(age)
		, Person(name)
	{
		cout << "Student()" << endl;
	}
	~Student()
	{
		cout << "~Student()" << endl;
	}
protected:
	int _age;
};

在这里插入图片描述
不需要在Student的析构函数里去主动调用Person的析构函数,编译器会自动给我们去调,这样的行为能确保一块区域不会被析构两次,出现造成野指针的问题。

3.拷贝构造

子类中调用父类的拷贝构造时,直接传入子类对象即可,父类的拷贝构造会通过“切片”拿到父类的那一部分。

class Person {
public:
	Person(string name)
		:_name(name)
	{}

	Person(const Person& p)
	{
		_name = p._name;
	}

protected:
	string _name;
};


class Student :public Person {
public:
	Student(string name, int age)
		:_age(age)
		, Person(name)
	{}

	Student(const Student& s)
		: Person(s)
	{
		_age = s._age;
	}

protected:
	int _age;
};

在这里插入图片描述
4. 赋值运算符重载

子类的operator=必须要显式调用父类的operator=完成父类的赋值。

来先来单独看下子类的赋值重载:

Student& operator=(const Student& s)
	{
		if (this != &s)
		{
			operator=(s);
			_age = s._age;
		}

		return *this;
	}

其他的不管,先来运行一下
在这里插入图片描述
因为子类和父类的运算符,编译器默认给与了同一个名字,所以构成了隐藏,所以每次调用=这个赋值运算符都会一直调用子类,会造成循环,导致了栈溢出,所以这里的赋值要直接修饰限定父类。

class Person 
{
public:
	Person(string name)
		:_name(name)
	{}

	Person& operator=(const Person& p)
	{
		if (this != &p)
		{
			_name = p._name;
		}
		return *this;
	}
	Person(const Person& p)
	{
		_name = p._name;
	}

protected:
	string _name;
};


class Student :public Person {
public:
	Student(string name, int age)
		:_age(age)
		, Person(name)
	{}

	Student(const Student& s)
		: Person(s)
	{
		_age = s._age;
	}

	Student& operator=(const Student& s)
	{
		if (this != &s)
		{
			Person::operator=(s);
			_age = s._age;
		}

		return *this;
	}

protected:
	int _age;
};

在这里插入图片描述

四.单继承和多继承

单继承: 一个子类只有一个直接父类的继承关系。
在这里插入图片描述
多继承: 一个子类有两个或以上直接父类时称这个继承关系为多继承

在这里插入图片描述

菱形继承:菱形继承是多继承的一种特殊情况

在这里插入图片描述
本贾尼大佬刚开始发明了多继承沾沾自喜,可却没想菱形继承的出现给他带来了更多烦恼
让我们来看看菱形继承

class A
{
public:
	int a;
};

class B : public A
{
public:
	int b;
};

class C : public A
{
public:
	int c;
};

class D : public B, public C
{
public:
	int d;
};

在这里插入图片描述
这样带来了行为突显出菱形继承有数据冗余和二义性的问题。在d的对象中a成员会有两份。
当去访问d中访问a变量时,偏移器会报错
在这里插入图片描述

我们可以通过虚继承来解决二义性问题

class B : public A -> class B : virtual public A
class C : public A -> class C : virtual public A

再次运行代码,去观察内存窗口
在这里插入图片描述
没有两份a成员了,多了两个指针,而这两个指针是什么呢?
指向的一张表。这两个指针叫虚基表指针,这两个表叫虚基表。虚基表中存的偏移量。通过偏移量
可以找到下面的A。

能力有限,反正多继承是C++复杂的一个体现。有了多继承,就存在菱形继承,为了解决菱形继承,又出现了菱形虚拟继承,其底层实现又很复杂。所以一般不建议设计出多继承,一定不要设计出菱形继承。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值