【C++】c++的继承

news2024/11/15 19:58:49

目录

思维导图大纲:

1.基类和派生类 

1.1 定义格式 

1.2 继承方式 

 1.3 基类和派生类的转换

 2. 继承中的作用域(隐藏关系)

2.1 考察继承作⽤域相关选择题

3. 派生类的默认成员函数

4. 继承类模板 

 5. 一个不能被继承的类

​编辑 6.继承与友元

​编辑 7. 继承与静态成员

​编辑 8. 继承类型

8.1 单继承 

8.2 多继承 

8.2.1 菱形继承 

二义性: 

数据冗余:

 8.3 多继承中的指针偏移问题

9. 继承和组合 

继承:is-a 

组合:has-a 


思维导图大纲:

1.基类和派生类 

基类和派生类又可以称作父类和子类,派生类可以继承基类中的成员,并且还可以拥有自己的成员,就好比植物是一个大类,而蒲公英也是一种植物拥有植物的特性(也就是植物类的成员属性),同时蒲公英也拥有自己的其他特点,如传播种子的方式;所以基类和派生类的关系如下图:

1.1 定义格式 

1.2 继承方式 

我们之前学习过每个类域都有着不同的成员,如public,protected,private。对于公共的成员public不管是类域内还是外都可以访问,对于后面两者只有在类域才可以访问,同样的是基类和派生类之间的基础方式也存在这种关系!

  •  由上图可知基类的不同成员和继承方式,会选取权限小的一方继承给派生类
  •  基类成员不希望外部访问,只希望派生类访问,因此出现了protected成员

  • 如果是基类的private成员无论以什么方式继承给派生类成员,在派生类中我们都不可见,语法上也不可以访问,但是基类的private成员确实继承给了派生类!
  • class默认为private继承,struct默认为public继承!
// 人->学生
class Person
{
public:
protected:
	string _name = "欧阳";
	int _age = 20;
	string gender = "男";
private:
	int _hide = 1;
};

class Student : public Person // public 继承方式
{
public:
	void Print() const
	{
		cout << _name << endl;
		cout << _age << endl;
		cout << gender << endl;
		cout << _id << endl;
		/*cout << Person::_hide << endl; */  // err
	}
protected:
	string _id = "001";
};


int main()
{
	Student s;
	s.Print();
	return 0;
}

 

 1.3 基类和派生类的转换

由之前我们可以得知派生类会继承基类的成员,所以我们可以将派生类的对象赋值给基类对象,实现这种操作的关键是切片操作!编译器会将派生类对象中与基类对象重合的成员切出来赋值给基类对象 ,但是基类对象不可以赋值给派生类对象,因为基类对象不含有派生类对象的部分成员!基类的指针和引用可以指向派生类对象

// 人->学生
class Person
{
public:
protected:
	string _name = "欧阳";
	int _age = 20;
	string gender = "男";
};

class Student : public Person // public 继承方式
{
public:
protected:
	string _id = "001";
};


int main()
{
	Person p;  // 基类对象
	Student s; // 派生类对象
	p = s;     // 派生类对象->基类对象
	//s = p;     // err
    Person* ptr1 = &s; // 指针
    Person& ptr2 = s;  // 引用
	return 0;
}

 2. 继承中的作用域(隐藏关系)

我们都知道基类和派生类属于两个类,因此他们拥有不同的类域,但是如果基类和派生类直接存在相同的变量或者函数时,会发生什么呢?

class A
{
public:
protected:
	int _num = 999;
};

class B : public A
{
public:
	void Print()
	{
		cout << _num << endl;
	}
protected:
	int _num = 111;
};

int main()
{
	B b;
	b.Print();
	return 0;
}

以上代码b.Print()会打印什么呢?是打印基类A的_num,还是派生类B的_num。 

 为什么是111呢,其实这边基类的_num和派生类的_num构成了隐藏的关系,隐藏起了基类的成员,如果我们需要访问打印基类的成员需要指定类域

  • 注意的是如果是成员函数的隐藏,只需要函数名相同就构成隐藏
class A
{
public:
protected:
	int _num = 999;
};

class B : public A
{
public:
	void Print()
	{
		cout << _num << endl;
		cout << A::_num << endl;
	}
protected:
	int _num = 111;
};

int main()
{
	B b;
	b.Print();
	return 0;
}

2.1 考察继承作⽤域相关选择题

class A
{
public :
	void fun()
	{
		cout << "func()" << endl;
	}
};
class B : public A
{
public :
	void fun(int i)
	{
		cout << "func(int i)" << i << endl;
	}
};
int main()
{
	B b;
	b.fun(10);
	b.fun();
	return 0;
};

  •  看问题一:

首先肯定不是重载关系,重载需要在同一作用域,函数名相同,参数类型,个数,顺序需要有一项不同,返回类型可以相同也可以不同!

A与B是继承的关系,根据前面基类和派生类中的函数只要函数名相同就构成隐藏关系

  • 看问题二:

由于构成了隐藏关系,所以b类中只存在 void fun(int i)函数,b.fun(10);的传参调用是正确的,但是b.fun();的函数调用是错误的!所以会编译报错

3. 派生类的默认成员函数

  •  派⽣类的构造函数必须调⽤基类的构造函数初始化基类的那⼀部分成员。如果基类没有默认的构造函数,则必须在派⽣类构造函数的初始化列表阶段显⽰调⽤
  • 派⽣类的拷⻉构造函数必须调⽤基类的拷⻉构造完成基类的拷⻉初始化。
  • 派⽣类的operator=必须要调⽤基类的operator=完成基类的复制。需要注意的是派⽣类的

    operator=隐藏了基类的operator=,所以显⽰调⽤基类的operator=,需要指定基类作⽤域

  • 派⽣类的析构函数会在被调⽤完成后⾃动调⽤基类的析构函数清理基类成员。因为这样才能保证派⽣类对象先清理派⽣类成员再清理基类成员的顺序。

  • 派⽣类对象初始化先调⽤基类构造再调派⽣类构造

  • 派⽣类对象析构清理先调⽤派⽣类析构再调基类的析构

class Person
{
public:
	Person(const char* name = "xxx")
		: _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;
	}
protected:
	string _name; // 姓名
};

class Student : public Person
{
public:
	Student(const char* name, int num, const char* addrss)
		:Person(name)
		,_num(num)
		,_addrss(addrss)
	{}

	// 严格说Student拷贝构造默认生成的就够用了
	// 如果有需要深拷贝的资源,才需要自己实现
	Student(const Student& s)
		:Person(s)
		,_num(s._num)
		,_addrss(s._addrss)
	{
		// 深拷贝
	}

	// 严格说Student赋值重载默认生成的就够用了
	// 如果有需要深拷贝的资源,才需要自己实现
	Student& operator=(const Student& s)
	{
		if (this != &s)
		{
			// 父类和子类的operator=构成隐藏关系
			Person::operator=(s);

			_num = s._num;
			_addrss = s._addrss;
		}

		return *this;
	}

	// 严格说Student析构默认生成的就够用了
	// 如果有需要显示释放的资源,才需要自己实现
	// 析构函数都会被特殊处理成destructor() 
	~Student()
	{
		// 子类的析构和父类析构函数也构成隐藏关系
		// 规定:不需要显示调用,子类析构函数之后,会自动调用父类析构
		// 这样保证析构顺序,先子后父,显示调用取决于实现的人,不能保证
		// 先子后父
		//Person::~Person();
		//delete _ptr;
	}
protected:
	int _num = 1; //学号
	string _addrss = "西安市高新区";

	int* _ptr = new int[10];
};

int main()
{
	Student s1("张三", 1, "西安市");
	Student s2(s1);

	Student s3("李四", 2, "咸阳市");
	s1 = s3;

	/*Person* ptr = new Person;
	delete ptr;*/

	return 0;
}

4. 继承类模板 

// 继承类模板
#include <vector>
template<class T>
class Stack : public std::vector<T>
{
public:
	void Push(const T& x)
	{
		push_back(x);
	}

	void Pop()
	{
		pop_back();
	}

	T& top()
	{
		return back();
	}

	bool empty()
	{
		return empty();
	}
};

int main()
{
	Stack<int> st;
	st.Push(1);
	st.Push(2);
	st.Push(3);
	while (!st.empty())
	{
		cout << st.top() << endl;
		st.Pop();
	}

	return 0;
}

我们编译以上代码会报错!

 这是为什么呢?首先我们是一个模板,我们使用Stack模板创建了st这个对象,但是在调用函数时会涉及到vector<int>,这时我们不仅实例化Stack<int>,也实例化vector<int>但是模板是需要按需实例化,所以push_back等函数并没有实例化,就会产生报错,我们在使用继承类模板时需要注意

  • 基类是类模板时,需要指定⼀下类域,
  • 否则编译报错:error C3861: “push_back”: 找不到标识符
  • 因为stack<int>实例化时,也实例化vector<int>了
  • 但是模版是按需实例化,push_back等成员函数未实例化,所以找不到
// 继承类模板
#include <vector>
template<class T>
class Stack : public std::vector<T>
{
public:
	void Push(const T& x)
	{
		vector<T>::push_back(x);
	}

	void Pop()
	{
		vector<T>::pop_back();
	}

	T& top()
	{
		return vector<T>::back();
	}

	bool empty()
	{
		return vector<T>::empty();
	}
};

int main()
{
	Stack<int> st;
	st.Push(1);
	st.Push(2);
	st.Push(3);
	while (!st.empty())
	{
		cout << st.top() << " ";
		st.Pop();
	}

	return 0;
}

 5. 一个不能被继承的类

需要使用关键字final

class A final
{
public:
};

class B : public A
{
public:
};

int main()
{
	B b;
	return 0;
}

 6.继承与友元

派生类继承基类,但是基类中的友元函数不可以被派生类继承 

class Student;
class Person
{
public :
	friend void Display(const Person& p, const Student& s);
protected:
	string _name; // 姓名
};
class Student : public Person
{
protected :
	int _stuNum; // 学号
};
void Display(const Person& p, const Student& s)
{
	cout << p._name << endl;
	cout << s._stuNum << endl;
} 

int main()
{
	Person p;
	Student s;
	// 编译报错:error C2248: “Student::_stuNum”: ⽆法访问 protected 成员
	// 解决⽅案:Display也变成Student 的友元即可
	Display(p, s);
	return 0;
}

 7. 继承与静态成员

派生类继承基类的非静态成员,都会额外开空间进行存储,但是对于静态成员有且只有一个,无论存在多少派生类 

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;
	// 这⾥的运⾏结果可以看到⾮静态成员_name的地址是不⼀样的
	// 说明派⽣类继承下来了,⽗派⽣类对象各有⼀份
	cout << &p._name << endl;
	cout << &s._name << endl;
	// 这⾥的运⾏结果可以看到静态成员_count的地址是⼀样的
	// 说明派⽣类和基类共⽤同⼀份静态成员
	cout << &p._count << endl;
	cout << &s._count << endl;
	// 公有的情况下,⽗派⽣类指定类域都可以访问静态成员
	cout << Person::_count << endl;
	cout << Student::_count << endl;
	return 0;
}

 8. 继承类型

8.1 单继承 

 

8.2 多继承 

8.2.1 菱形继承 

 菱形继承会带来两项问题:

  • 二义性
  • 数据冗余
二义性: 
class Person
{
protected:
	string _name;
	string _gender;
	int _age;
};

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

class Teacher : public Person
{
protected:
	int _id;
};

class Total : public Student, public Teacher
{
protected:
	string _data;
};

int main()
{
	Total tl;
	tl._name = "欧阳"; // 二义性!
	return 0;
}

我们可以指定类域解决这种问题 

int main()
{
	Total tl;
	//tl._name = "欧阳";
	tl.Student::_name = "欧阳";
	tl.Teacher::_name = "ouyang";
	return 0;
}
数据冗余:

 需要使用虚继承virtual解决这种问题!

class Person
{
public:
	string _name;
	string _gender;
	int _age;
};

class Student : virtual public Person
{
protected:
	int _StuNum;
};

class Teacher : virtual public Person
{
protected:
	int _id;
};

class Total : public Student, public Teacher
{
protected:
	string _data;
};

int main()
{
	Total tl;
	//tl._name = "欧阳";
	tl.Student::_name = "欧阳";
	tl.Teacher::_name = "ouyang";
	return 0;
}

 

 8.3 多继承中的指针偏移问题

当一个派生类继承多个基类时,指向不同基类的顺序会根据继承顺序产生指针偏移的问题,偏移的大小为前一个基类的数据大小 

class Base1 { public: int _b1; };
class Base2 { public: int _b2; };
class Derive : public Base1, public Base2 { public: int _d; };
int main()
{
	Derive d;
	Base1* p1 = &d;
	Base2* p2 = &d;
	Derive* p3 = &d;
	cout << p1 << endl;
	cout << p2 << endl;
	cout << p3 << endl;
	return 0;
}

9. 继承和组合 

继承:is-a 

继承允许你根据基类的实现来定义派⽣类的实现。这种通过⽣成派⽣类的复⽤通常被称为⽩箱复⽤ (white-box reuse)。术语“⽩箱”是相对可视性⽽⾔:在继承⽅式中,基类的内部细节对派⽣类可 ⻅。继承⼀定程度破坏了基类的封装,基类的改变,对派⽣类有很⼤的影响。派⽣类和基类间的依赖关系很强,耦合度⾼。 

组合:has-a 

对象组合是类继承之外的另⼀种复⽤选择。新的更复杂的功能可以通过组装或组合对象来获得。对 象组合要求被组合的对象具有良好定义的接⼝。这种复⽤⻛格被称为⿊箱复⽤(black-box reuse),因为对象的内部细节是不可⻅的。对象只以“⿊箱”的形式出现。组合类之间没有很强的依赖关 系,耦合度低。优先使⽤对象组合有助于你保持每个类被封装。 

低耦合高内聚 

// 继承 is-a
class Stack1 : public std::vector<int>
{
};

// 组合 has-a
class Stack2
{
protected:
	std::vector<int> _v;
};

int main()
{
	return 0;
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2138808.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Java面向对象六大设计原则总结(超级详细,附有代码、图解以及案例)

文章目录 三.软件(面向对象)设计原则3.1 开闭原则(OSP)3.1.1 概述3.1.2 案列 3.2 里氏代换原则(LSP)3.2.1 概述3.2.2 案例 3.3 依赖倒转原则(DIP)3.3.1概述3.3.2 案例 3.4 接口隔离原则(ISP)3.4.1 概述3.4.2 案列 3.5 迪米特法则(DP)3.5.1 概述3.5.2 案例 3.6 合成复用原则(CRP…

红黑树前语

目录 概念 性质 红黑树与AVL树的比较 过两天更新红黑树的模拟实现,中秋快乐各位 概念 1. 概念&#xff1a; 是一种搜索二叉树&#xff0c; 但在每个结点上增加一个存储位表示节点的颜色&#xff0c;可以是Red 或 Black。通过对任何一条从根到叶子的路径上各个节点着色方式的…

[JVM]JVM内存划分, 类加载过程, 双亲委派模型,垃圾回收机制

文章目录 一. JVM内存划分1. 堆2. 栈3. 元数据区4. 程序计数器 二. 类加载过程1. 加载2. 验证3. 准备4. 解析5. 初始化 三. 双亲委派模型四. JVM的垃圾回收机制GC1. 找到需要回收的对象2. 释放垃圾的策略 一. JVM内存划分 JVM就是java进程 这个进程一旦跑起来, 就会从操作系统…

Windows本地制作java证书(与jeecgboot配置本地证书ssl问题)

1&#xff1a;JDK生成自签证书SSL,首先以管理员身份运行CMD窗口&#xff0c;执行命令 keytool -genkey -alias testhttps -keyalg RSA -keysize 2048 -validity 36500 -keystore "F:/ssl/testhttps.keystore"F:\ssl>keytool -genkey -alias testhttps -keyalg R…

PCIe进阶之TL:Memory, I/O, and Configuration Request Rules TPH Rules

1 Memory, I/O, and Configuration Request Rules 下述规则适用于 Memory 请求、IO 请求和配置请求。 除了公共的 header 字段外,所有 Memory 请求、IO 请求和配置请求还包括以下字段: (1)Requester ID[15:0] 和 Tag[9:0],组成了 Transaction ID 。 (2)Last DW BE[3:0]…

计算架构模式之接口高可用

接口高可用整体框架 接口高可用主要应对两类问题&#xff1a;雪崩效应和链式效应。 雪崩&#xff1a;当请求量超过系统处理能力之后&#xff0c;会导致系统性能螺旋快速下降&#xff0c;本来系统可以处理1000条&#xff0c;但是当请求量超过1200的时候&#xff0c;此时性能会下…

【415】【最高乘法得分】

目录 使用dp python版本 java版本 递推式 python版本 java版本 PS: java语法 1.定义数组 2.记忆化 3.计算max 难绷&#xff0c;本来想着4个指针&#xff0c;和四数之和那道题挺类似的。。。。 四数之和好像剪枝和预处理都是先排序的比较好做。 无奈&#xff0c;只…

[网络]https的概念及加密过程

文章目录 一. HTTPS二. https加密过程 一. HTTPS https本质上就是http的基础上增加了一个加密层, 抛开加密之后, 剩下的就是个http是一样的 s > SSL HTTPS HTTP SSL 这个过程, 涉及到密码学的几个核心概念 明文 要传输的真正意思是啥 2)密文 加密之后得到的数据 这个密文…

CTF(misc)1和0的故事

题目链接 下载题目后是一堆整齐的01字符串&#xff0c;猜测是生成二维码&#xff0c;将0变成白色方块&#xff0c;1变成黑色方块。 0000000001110010000000000 0000000000011110100000000 0000000001110001000000000 0000000010111100000000000 0000000010101010000000000 00…

Python基础语法(3)下

列表和元组 列表是什么&#xff0c;元组是什么 编程中&#xff0c;经常需要使用变量&#xff0c;来保存/表示数据。变量就是内存空间&#xff0c;用来表示或者存储数据。 如果代码中需要表示的数据个数比较少&#xff0c;我们直接创建多个变量即可。 num1 10 num2 20 num3…

ModuleNotFoundError: No module named ‘datasets‘

报错信息&#xff1a; 解决&#xff1a;安装datasets 方法1: pip install datasets 方法2: python3可以使用以下命令&#xff1a; pip3 install datasets

【智路】智路OS Perception Fusion Service

Perception Fusion Service https://gitee.com/ZhiluCommunity/airos-edge/raw/r2.0/docs/02_Service/Perception_Fusion_Service.md 多传感器融合感知模块的主要任务是接收各传感器感知的障碍物信息&#xff0c;融合这些障碍物信息&#xff0c;得到融合后的障碍物信息。 智…

Tuxera NTFS for Mac 2023绿色版

​ 在数字化时代&#xff0c;数据的存储和传输变得至关重要。Mac用户经常需要在Windows NTFS格式的移动硬盘上进行读写操作&#xff0c;然而&#xff0c;由于MacOS系统默认不支持NTFS的写操作&#xff0c;这就需要我们寻找一款高效的读写软件。Tuxera NTFS for Mac 2023便是其中…

接口自动化框架入门(requests+pytest)

一、接口自动化概述 二、数据库概述 2.1 概念 存储数据的仓库&#xff0c;程序中数据的载体 2.2 分类 关系型数据库&#xff1a;安全 如mysql&#xff0c;oracle&#xff0c;SQLLite database tables 行列 非关系型数据库&#xff1a;高效 如redis&#xff0c;mongoDB 数…

C++笔记之子类初始化时父类带参构造函数的处理、父子类中模板参数的传递

C++笔记之子类初始化时父类带参构造函数的处理、父子类中模板参数的传递 code review! 文章目录 C++笔记之子类初始化时父类带参构造函数的处理、父子类中模板参数的传递一.子类初始化时父类带参构造函数的处理1.1.若父类只有带参数的构造函数,子类初始化时必须在初始化列表…

C++ 面试必备知识大全:从基础到高级特性全面解析

创作不易&#xff0c;您的打赏、关注、点赞、收藏和转发是我坚持下去的动力&#xff01; C 面试中常见的问题涵盖了语言基础、面向对象编程、内存管理、STL&#xff08;标准模板库&#xff09;、并发编程、设计模式等。以下是一些常见的 C 面试问题及其详细答案总结&#xff1…

protobuf中c、c++、python使用

文章目录 protobuf实例&#xff1a;例题1&#xff1a;[CISCN 2023 初赛]StrangeTalkBot分析&#xff1a;思路&#xff1a;利用&#xff1a; 例题2&#xff1a;[CISCN 2024]protoverflow分析&#xff1a; protobuf Protocol Buffers&#xff0c;是Google公司开发的一种数据描述语…

Tcl lnit error: Can’t find a usable init.tcl in the following directories 问题解决

这个问题出现在我用py2exe打包了一个包含tkinter的图形化界面&#xff0c;在当前电脑上运行无问题&#xff0c;在移动到新电脑上后提示报错、 这里吐槽一下&#xff0c;新电脑上报错信息一闪而过&#xff0c;我用的土法子解决的&#xff0c;就是录视频然后0.25倍速度暂定找到报…

删除Cookie原理

WebServlet("/deletecookie") // 这个注解指定了Servlet的URL映射路径 public class DeleteCookieServlet extends HttpServlet { // 定义一个继承自HttpServlet的类Override // 重写父类的方法protected void doGet(HttpServletRequest request, HttpServletResp…

ORM框架详解:为什么不直接写SQL?

想象一下&#xff0c;你正在开发一个小型的在线书店应用。你需要存储书籍信息、用户数据和订单记录。作为一个初学者&#xff0c;你可能会想&#xff1a;“我已经学会了SQL&#xff0c;为什么还要使用ORM框架呢&#xff1f;直接写SQL语句不是更简单、更直接吗&#xff1f;” 如…