C++系列-继承

🌈个人主页:羽晨同学

💫个人格言:"成为自己未来的主人~"

继承的概念和定义

继承是面向对象程序设计使代码可以复用的最重要的手段,它允许程序员在保持原有类特性的基础上进行拓展,增加功能,这样可以产生新的类,叫做派生类,继承呈现了面向对象的程序设计的层次结构,继承是类设计层次的复用

cpp 复制代码
class Person
{
public:
	Person()
	{}
	void Print()
	{
		cout << "name:" << _name << endl;
		cout << "age:" << _age << endl;
		cout<<_tel<<endl;
	}
	string _name = "peter";//姓名
protected:
	int _age = 18;
	//父类定义本质,不想被子类继承
private:
	int _tel = 110;
};
//继承的父类的成员
class Student :public Person
{
public:
	void Func()
	{
		//子类用不了(不可见)
		//cout << _tel << endl;
		cout << _name << endl;
		cout << _age << endl;
	}
protected:
	int _stuid;//学号
};
class Teacher :public Person
{
protected:
	int _jobid;//工号
};

在这个代码当中,我们简单的使用继承创建了两个对象。

所以,我们看到的是Person是父类,也被叫做基类,Student是子类,也称作派生类。

继承管理和访问限定符

继承基类成员访问方式的变化。

在这其中,如果我们使用public继承和类成员是public成员或者protected成员,是可以直接使用成员,其他的就不可以,例如:

cpp 复制代码
class Student :public Person
{
public:
	void Func()
	{
		//子类用不了(不可见)
		cout << _tel << endl;
		cout << _name << endl;
		cout << _age << endl;
	}
protected:
	int _stuid;//学号
};

在这里,我们的_tel在Person中时private修饰的,所以派生类中不能进行调用。

但是,父类的私有成员变量是可以父类的成员函数来间接调用的,例如:

cpp 复制代码
class Person
{
public:
	Person()
	{}
	void Print()
	{
		cout << "name:" << _name << endl;
		cout << "age:" << _age << endl;
		cout<<_tel<<endl;
	}
	string _name = "peter";//姓名
protected:
	int _age = 18;
	//父类定义本质,不想被子类继承
private:
	int _tel = 110;
};

在实际使用中一般使用的都是public继承,几乎很少使用protected/private继承

派生类和基类对象的赋值转换

我们假如让p=s,这个是可以实现的,但这个实现的逻辑并不是类型转换,而是一种特殊的语法规则,不会产生任何的中间变量,我们可以看下面的代码实现逻辑。

cpp 复制代码
int main()
{
	Student s;
	Person p;
	//跟下面机制不一样
	//特殊语法规则:不是类型转换,中间没有产生临时变量
	p = s;
	Person* ptr = &s;
	Person& ref = s;
	ptr->_name += 'x';
	ref._name += 'y';
	s._name += 'z';
	return 0;
}
  • 基类对象不能赋值给派生类对象。
  • 派生类对象可以赋值给基类的对象/基类的指针/基类的引用,这里我们可以叫做切片或者切割。

那类型转换下的情况是什么吗?

这个就是我们经常说到的,截断和提升。

继承中的作用域

基类和派生类都有独立的作用域

子类和父类中有同名成员,子类成员将屏蔽父类对同名成员的直接访问,这种情况叫做隐藏,也叫做重定义(在子类成员函数中,可以使用基类:基类成员显示访问)

如果是成员函数之间的隐藏,只要函数名相同就能构成隐藏。

在实际中,最好不要在继承体系里面定义同名的成员

cpp 复制代码
#include<iostream>
using namespace std;
//student的_num和Prson的_num构成隐藏关系,可以看出代码虽然可以运行,但是非常容易混淆
class Person
{
protected:
	string _name = "小李子";
	int _num = 111;
};
class Student :public Person
{
public:
	void Print()
	{
		cout << "姓名" << _name << endl;
		cout << "学号" << _num << endl;//隐藏,重定义
		cout << "学号" << Person::_num << endl;
	}
protected:
	int _num = 999;
};
int main()
{
	Person p;
	Student s;
	s.Print();
	return 0;
}

你看,这里面就涉及到了隐藏和重定义。

派生类的默认成员函数

6个默认成员函数

  • 初始化和清理
  1. 构造函数主要完成初始化工作
  2. 析构函数主要完成清理工作
  • 拷贝复制
  1. 拷贝构造是使用同类对象初始化创建对象
  2. 赋值重载主要是把一个对象赋值给另一个对象
  • 取地址重载
  1. 主要是普通函数和const对象取地址,这两个很少会自己实现

在子类中,父类的那部分调用父类的什么函数,这个就叫做复用。

构造:

构造的时候,我们要先父后子,因为子类中关于父类成员的部分会用到父类的构造,

析构:

析构的时候,我们要先子后父,因为子类的析构可能会用到父类的析构函数。

cpp 复制代码
class Person
{
public:
	Person(const char* name = "")
		:_name(name)
	{
		cout << "Person()" << 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;
	}
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="")
		:_x(x)
		,_address(address)
		,_name(Person::_name+'x')
		,Person(name)
	{}
	Student(const Student&st)
		:Person(st)
		,_x(st._x)
		,_address(st._address)
	{}
	Student& operator=(const Student& st)
	{
		if (this != &st)
		{
			Person::operator=(st);
			_x = st._x;
			_address = st._address;
		}

		return *this;
	}
	//由于多态,析构函数的名字会被统一处理成destructor()
	//父类析构不能显示调用,因为显示调用不能保证先子后父
	~Student()
	{
		//析构函数会构成隐藏,所以这里要指定类域
		cout << "Student()" << endl;
		cout << _str << endl;
	}
protected:
	int _x = 1;
	string _address = "sdasa";
	string _name;
};

好了,本次的文章就到这里了,我们下次再见。

相关推荐
AI街潜水的八角几秒前
基于C++的决策树C4.5机器学习算法(不调包)
c++·算法·决策树·机器学习
q5673152318 分钟前
在 Bash 中获取 Python 模块变量列
开发语言·python·bash
白榆maple26 分钟前
(蓝桥杯C/C++)——基础算法(下)
算法
JSU_曾是此间年少30 分钟前
数据结构——线性表与链表
数据结构·c++·算法
许野平43 分钟前
Rust: 利用 chrono 库实现日期和字符串互相转换
开发语言·后端·rust·字符串·转换·日期·chrono
也无晴也无风雨1 小时前
在JS中, 0 == [0] 吗
开发语言·javascript
狂奔solar1 小时前
yelp数据集上识别潜在的热门商家
开发语言·python
此生只爱蛋1 小时前
【手撕排序2】快速排序
c语言·c++·算法·排序算法
blammmp2 小时前
Java:数据结构-枚举
java·开发语言·数据结构
何曾参静谧2 小时前
「C/C++」C/C++ 指针篇 之 指针运算
c语言·开发语言·c++