《C++ 继承》

继承的定义

继承的本质是一种复用。规定Person类为基类,Student类为派生类 。

继承方式分为public继承,protected继承,private继承。一般使用public继承,对成员的限制++private > protected > public++.

public继承,基类的private成员只能在基类使用,protected成员只能在基类和派生类中使用,public成员能在任意地方使用。

protect继承,基类的private成员只能在基类使用,protected成员只能在基类和派生类中使用,public成员只能在基类和派生类中使用。

private继承,基类的private成员只能在基类使用,protected成员只能在基类中使用,public成员只能在基类中使用。

cpp 复制代码
//基类/父类
class Person
{
public:
	//身份认证
	void identity()
	{
		cout << "void identity()" << endl;
	}
	void func()
	{
		_age++;
	}

protected:
	string _name = "张三";
	string _address;
	string _tel;
private:
	int _age = 18;
};

//子类/派生类
class Student : public Person
{
public:
	//学习
	void study()
	{
		cout << "void study()" << endl;
	}


	//不能在派生类声明时直接在派生类中来改变基类的成员变量
	//_name = "李四";

	void Set_name()
	{
		_name = "李四";
	}
protected:
	int _stuid;//学号
};

//不显示写继承方式 默认为private继承
//class Teacher : Person
class Teacher : public Person
{
public:
	void teach()
	{
		cout << "void teach()" << endl;
		//派生类中无法访问基类的private成员
		//age++;
	}
	 
protected:
	int _work_num;//工号

};

int main()
{
	Person p;
	p.identity();//基类调用成员函数
	Student s;
	s.identity();//派生类调用基类的成员函数
	Teacher t;
	t.identity();
	s.func();//调用基类的函数来改变基类的private变量
	s.Set_name();//调用成员函数可以该改变基类中的potected变量
	return 0;
}

继承类模板

在复用容器中的函数时会报错找不到标识符push_back()。

++因为lzk::stack<int> s实例化了stack<int>和vector<int>,但是没有实例化vector<int>::push_back(x)。++

这里的问题本质上时编译器对模板的两阶段查找规则
第一阶段(模板定义阶段)模板定义时自动触发(无需实例化)

编译器会检查所有不依赖模板参数 T 的名称(即非依赖名称,如直接写的 push_back)。

如果名称未在当前作用域或可见基类中声明,直接报错(即使基类模板实例化后可能有该成员)。

第二阶段(模板实例化阶段)实例化模板时触发(如 main() 中使用)

检查所有依赖 T 的名称(如 vector<T>::push_back)。

此时基类模板(如 vector<int>)已实例化,可以确认成员是否存在。

cpp 复制代码
//基类为类模板
namespace lzk
{
	template<class T>
	class stack : public vector<T>
	{
	public:
		void push(const T& x)
		{
			//报错找不到标识符
			//push_back();
			//lzk::stack<int> s;时实例化了stack<int> 和vector<int>
			//但是没有实例化vector<int>::push_back(x)
			//这里的问题本质上时编译器对模板的两阶段查找规则
			/*第一阶段(模板定义阶段)
			编译器会检查所有不依赖模板参数 T 的名称(即非依赖名称,如直接写的 push_back)。
			如果名称未在当前作用域或可见基类中声明,直接报错(即使基类模板实例化后可能有该成员)。
			这就是你遇到的 push_back 报错的根本原因。
			第二阶段(模板实例化阶段)
			检查所有依赖 T 的名称(如 vector<T>::push_back)。
			此时基类模板(如 vector<int>)已实例化,可以确认成员是否存在。*/
			//阶段1:模板定义时自动触发(无需实例化)。
			//阶段2:实例化模板时触发(如 main() 中使用)。
			vector<int>::push_back(x);
		}
		void pop()
		{
			vector<int>::pop_back();
		}
		const T& top()
		{
			return vector<int>::back();
		}
		bool empty()
		{
			return vector<int>::empty();
		}
	};

	 
}

int main()
{
	lzk::stack<int> s;
	s.push_back(1);
	s.push_back(2);
	s.push_back(3);
	s.push_back(4);
	while (!s.empty())
	{
		cout << s.top() << endl;
		s.pop();
	}
	return 0;
}

派生类和基类之前的转换

++public继承的派生类对象可以赋值给基类的对象和指针和引用,但是基类对象不能赋值给派生类对象。++

cpp 复制代码
//派生类对象和基类对象的转换
class Person
{
protected:
	string _name;
	string _sex;
	int _age;
};

class Student : public Person
{
public:
	int _num;
};

int main()
{
	Student s;

	//派生类对象可以赋值给基类的指针引用
	Person* p = &s;
	Person& rp = s;

	//派生类对象赋值给基类对象,通过基类的拷贝构造完成
	Person pojb = s;

	return 0;
}

隐藏

我们知道不同的类,有不同的类域,他们是相互独立的,++如果基类和派生类中存在同名成员变量或同名成员函数,派生类成员将屏蔽基类对同名成员的直接访问++,即隐藏。

cpp 复制代码
class Person
{
protected:
	string _name = "李四";
	string _sex = "男";
	int _age = 18;
public:
	void func()
	{
		cout << "func()" << endl;
	}
};

class Student : public Person
{
public:

	void print()
	{
		//将基类中的_age隐藏了
		cout << _age << endl;
	}
	int _num = 22;
	int _age = 19;
	void func(int i)
	{
		cout << "void func(int i)" << endl;
	}
};

int main()
{
	Student s;
	s.print();
	 
	//通过派生类对象调用基类隐藏函数,需要指定类域
	s.func(1);
	s.Person::func();
	return 0;
}

派生类的默认成员函数

构造函数的调用顺序是,++先调用基类的构造函数,再调用派生类的构造函数;先析构派生类对象,再析构基类对象++。

如果显示调用基类的析构,有两个问题

1.在编译过后,编译器会将基类和派生类的析构函数名称改为destructor,那么基类和派生类的析构函数就会构成隐藏关系,则需要指定类域调用
2.调用过后发现基类析构了两次,编译器为了析构的顺序是先派生类再基类,会在调用派生类的析构后再调用基类析构,如果有动态资源就会报错。

cpp 复制代码
//派生类的默认成员函数

//基类
class Person
{
public:
	Person(const string& name = "张三")
		:_name(name)
	{
		cout << "Person(const string& 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;
		_name = "";
	}
protected:
	string _name;

};

//派生类
class Student : public Person
{
public:

	Student(const string& n, int age, const string& name)
		:Person(name)
		, _n(n)
		, _age(age)
	{
		cout << "Student(const string& n, int age, const string& name)" << endl;
	}
	Student(const Student& s)
		:Person(s)//显示调用基类的拷贝构造
		,_n(s._n)
		,_age(s._age)
	{
		cout << "Student(const Student& s)" << endl;
	}
	Student& operator=(const Student& s)
	{
		cout << "Student& operator=(const Student& s)" << endl;
		if (this != &s)
		{
			_n = s._n;
			_age = s._age;
			Person::operator=(s);//显示调用基类的赋值重载
		}
		return *this;
	}
	~Student()
	{
		//如果显示调用基类的析构,有两个问题
		//1.
		//在编译过后,编译器会将基类和派生类的析构函数名称改为destructor(涉及到多态)
		//那么基类和派生类的析构函数就会构成隐藏关系
		//则需要指定类域调用
		//2.
		//调用过后发现基类析构了两次
		//编译器为了析构的顺序是先派生类再基类 
		//会在调用派生类的析构后再调用基类析构    
		//如果有动态资源就会报错
		//Person::~Person();
		cout << "~student()" << endl;
	}
private:
	string _n = "李四";
	int _age = 19;

};

int main()
{
	Student s("李好", 19,"张斌");
	Student s1(s);
	//Student s3("李一", 18,"李二" );
	//s1 = s3;
	return 0;
}

不能被继承的类

实现一个不能被继承的类有两种方法

一是将基类的构造函数列为私有成员。

二是在基类的名字后面加final关键字。

cpp 复制代码
//实现一个不能被继承的类
//c++11
class Base final//加关键字
{
public:	 
	void func5() { cout << "Base::func5" << endl; }
protected:
	int a = 1;
private:
	//c++98 
	//将构造函数设为私有
	//Base()
	//{};
};
//报错
//class Student : public Base

继承中的友元和静态成员

++友元关系不能被继承++,基类的友元函数不能访问派生类的成员变量,除非给派生类也声明友元。

在基类中定义了静态成员,则在++派生类中使用的也是这个成员++,不会再开辟新的空间。

cpp 复制代码
//继承和友元

//前置声明
class Student;

class Person
{
public:
	friend void print(const Person& p, const Student& s);
protected:
	int _age = 18;
};

class Student : public Person
{
protected:
	string _name = "李思思";
	friend void print(const Person& p, const Student& s);
};

void print(const Person& p, const Student& s)
{
	cout << p._age << endl;
	cout << s._name << endl;
}

int main()
{
	Student s;
	Person p;
	print(p,s);
	return 0;
}


//静态成员

class Person
{
public:
	string _name;
	static int _count;
};

//静态成员在类外定义
int Person::_count = 0;

class Student : public Person
{
protected:
	int _stuNum;
};

int main()
{
	Person p;
	Student s;
	//基类和派生类公用一个静态变量
	cout << &p._count << endl;
	cout << &s._count << endl;
	//基类和派生类非静态成员的地址是不一样的
	cout << &p._name << endl;
	cout << &s._name << endl;

	//通过指定类域可以访问静态成员
	cout << &Person::_count << endl;
	cout << &Student::_count << endl;
	return 0;
}

继承模型

单继承:一个派生类继承一个基类。

多继承:一个派生类继承多个基类。

菱形继承:诸如两个派生类继承同一个基类后,再被同一个派生类继承的情况。

菱形继承会产生数据冗余和二义性的问题,为了解决这个问题就有了虚继承,虚继承就是在继承方式前面加上virtual关键字。那么派生类对象就可以访问基类公开成员了。

cpp 复制代码
//单继承 多继承 菱形继承
class Person
{
public:
	string _name = "李思思";
};

class Student : public Person
{
public:
	int _nun;
};

class Teacher : public Person
{
public:
	int _worknum;
};

class Assistant : public Student, public Teacher
{
protected:
	string _course; // 主修课程
};

int main()
{
	//对_name的访问不明确
	//student类和teacher类中都有_name
	Assistant a;
	//a._name = "lisisi";

	//指定类域可以访问,解决二义性问题,但是存在数据冗余
	a.Student::_name = "lisisi";
	a.Teacher::_name = "lss";
}

//虚继承
//解决数据冗余和二义性

class Person
{
public:
	string _name = "李思思";
};

//加关键字virtaul
class Student : virtual public Person
{
public:
	int _nun;
};

class Teacher : virtual public Person
{
public:
	int _worknum;
};

class Assistant : public Student, public Teacher
{
protected:
	string _course; // 主修课程
};

int main()
{
	Assistant a;
	a._name = "李思思";
	return 0;
}

**任何足够先进的科技都与魔法无异,但魔法背后的真相永远是严谨的代码逻辑。愿我们既能享受创造的浪漫,也能保持对技术的敬畏之心 !**🚀

相关推荐
future14126 分钟前
C#学习日记
开发语言·学习·c#
码农编程录11 分钟前
【c/c++3】类和对象,vector容器,类继承和多态,systemd,std&boost
c++
king_harry21 分钟前
Java程序-OceanBase Connector/J 示例
开发语言
傻啦嘿哟1 小时前
Python 办公实战:用 python-docx 自动生成 Word 文档
开发语言·c#
翻滚吧键盘1 小时前
js代码09
开发语言·javascript·ecmascript
q567315231 小时前
R语言初学者爬虫简单模板
开发语言·爬虫·r语言·iphone
??tobenewyorker2 小时前
力扣打卡第二十一天 中后遍历+中前遍历 构造二叉树
数据结构·c++·算法·leetcode
rzl022 小时前
java web5(黑马)
java·开发语言·前端
时序数据说2 小时前
为什么时序数据库IoTDB选择Java作为开发语言
java·大数据·开发语言·数据库·物联网·时序数据库·iotdb
jingling5552 小时前
面试版-前端开发核心知识
开发语言·前端·javascript·vue.js·面试·前端框架