(学习总结17)C++继承

news2024/11/13 10:13:57

C++继承

  • 一、继承的概念与定义
    • 继承的概念
    • 继承定义
      • 1. 定义格式
      • 2. 继承基类成员访问方式的变化
    • 继承类模板
  • 二、基类和派生类间的转换
  • 三、继承中的作用域
    • 隐藏规则
  • 四、派生类的默认成员函数
    • 4个常见默认成员函数
    • 实现一个不能被继承的类
  • 五、继承与友元
  • 六、继承与静态成员
  • 七、多继承及其菱形继承问题
    • 继承模型
    • 虚继承
    • 多继承中指针偏移问题
  • 八、继承和组合

以下代码环境为 VS2022 C++

一、继承的概念与定义

继承的概念

继承(inheritance)机制是面向对象程序设计使代码可以复用的最重要的手段,它允许我们在保持原有类特性的基础上进行扩展,增加方法(成员函数)和属性(成员变量),这样产生新的类,称为派生类。继承呈现了面向对象程序设计的层次结构,体现了由简单到复杂的认知过程。将一个功能设计成函数是函数层次的复用,继承是类设计层次的复用。

下面设计了两个没有继承的类 Student 和 Teacher,Student 和 Teacher 都有 姓名 / 地址 / 电话 / 年龄 等成员变量,都有 identity 身份认证的成员函数,设计到两个类里面就是冗余的。当然他们也有一些不同的成员变量和函数,比如老师独有成员变量是职称,学生的独有成员变量是学号;学生的独有成员函数是学习,老师的独有成员函数是授课

class Student
{
public:

	void identity()
	{
		// ...
	}

	void study()
	{
		// ...
	}

protected:

	string _name = "peter"; // 姓名
	string _address;		// 地址
	string _tel;			// 电话
	int _age;				// 年龄
	int _stuid;				// 学号
};

class Teacher
{
public:

	void identity()
	{
		// ...
	}

	void teaching()
	{
		//...
	}

protected:

	string _name = "张三";	// 姓名
	string _address;		// 地址
	string _tel;			// 电话
	int _age = 18;			// 年龄
	string _title;			// 职称
};

下面公共的成员都放到 Person 类中,Student 和 teacher 都继承 Person,可以复用这些成员,不需要重复定义,省去了很多麻烦。

class Person
{
public:

	void identity()
	{
		cout << "void identity()" << _name << endl;
	}

protected:

	string _name = "张三";	// 姓名
	string _address;		// 地址
	string _tel;			// 电话
	int _age;				// 年龄
};

class Student : public Person
{
public:

	void study()
	{
		// ...
	}

protected:

	int _stuid;				// 学号
};

class Teacher : public Person
{
public:

	void teaching()
	{
		//...
	}

protected:

	string _title;			// 职称
};

继承定义

1. 定义格式

下面看到 Person 是基类,也称作父类。Student 是派生类,也称作子类。(因为翻译的原因,所以既叫 基类 / 派生类,也叫 父类 / 子类)

在这里插入图片描述

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

类成员 / 继承方式public 继承protected 继承private 继承
基类的 public 成员派生类的 public 成员派生类的 protected 成员派生类的 private 成员
基类的 protected 成员派生类的 protected 成员派生类的 protected 成员派生类的 private 成员
基类的 private 成员在派生类中不可见在派生类中不可见在派生类中不可见
  1. 基类 private 成员在派生类中无论以什么方式继承都是不可见的。这里的不可见是指基类的私有成员还是被继承到了派生类对象中,但是语法上限制派生类对象不管在类里面还是类外面都不能去访问它

  2. 基类 private 成员在派生类中是不能被访问,如果基类成员不想在类外直接被访问,但需要在派生类中能访问,就定义为 protected。

  3. 根据上面的表格总结会发现,基类的私有成员在派生类都是不可见。基类的其他成员在派生类的访问方式 == Min(成员在基类的访问限定符,继承方式),public > protected > private。

  4. 使用关键字 class 时默认的继承方式是 private,使用 struct 时默认的继承方式是 public,不过最好显示的写出继承方式

  5. 在实际运用中一般使用都是 public 继承,几乎很少使用 protetced / private 继承,也不提倡使用 protetced / private 继承,因为 protetced / private 继承下来的成员都只能在派生类的类里面使用,实际中扩展维护性不强。

class Person
{
public:

	void identity()
	{
		cout << "void identity()" << _name << endl;
	}

protected:

	string _name = "张三";	// 姓名
	string _address;		// 地址
	string _tel;			// 电话

private:
	int _age;				// 年龄
};

// class Student : private Person
// class Student : protected Person
class Student : public Person
{
public:

	void study()
	{
		// ...
	}

protected:

	int _stuid;				// 学号
};

继承类模板

继承是一种 is - a 的关系。例如 Stack 可以算是 vector,则 Stack 可以继承 vector 来实现:

#include <iostream>
#include <vector>

template<class T>
class Stack : protected std::vector<T>
{
public:

	// 基类是类模板时,需要指定⼀下类域,
	// 否则编译报错找不到其标识符,
	// 因为 stack<int> 实例化时,也实例化 vector<int> 了
	// 但是模版是按需实例化,push_back 等成员函数未实例化,所以找不到
	void push(const T& n)
	{
		std::vector<T>::push_back(n);
	}

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

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

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

int main()
{
	Stack<int> st;
	for (int i = 1; i <= 5; ++i)
	{
		st.push(i);
	}

	while (!st.empty())
	{
		std::cout << st.top() << std::endl;
		st.pop();
	}

	return 0;
}

二、基类和派生类间的转换

  1. public 继承的 派生类对象 可以赋值给 基类的指针 / 基类的引用。这里有个形象的说法叫切片或者切割。寓意把派生类中基类那部分切出来,基类指针或引用指向的是派生类中切出来的基类那部分。

  2. 基类对象不能赋值给派生类对象。

  3. 基类的指针或者引用可以通过强制类型转换赋值给派生类的指针或者引用。但是必须是基类的指针指向派生类对象时才是安全的。这里基类如果是多态类型,可以使用 RTTI(Run-Time Type Information)的 dynamic_cast 来识别后进行安全转换。

#include <iostream>
using namespace std;

class Person
{
protected:

	string _name;	// 姓名
	string _sex;	// 性别
	int _age;		// 年龄
};

class Student : public Person
{
public:

	int _No;		// 学号
};

int main()
{
	Student stu;

	// 1.派⽣类对象可以赋值给基类的指针 / 引⽤
	Person* pp = &stu;
	Person& rp = stu;

	// 2.派⽣类对象可以赋值给基类的对象是通过调⽤基类的拷⻉构造完成的
	Person per = stu;

	// 3.基类对象不能赋值给派⽣类对象,这⾥会编译报错
	//sobj = pobj;

	return 0;
}

三、继承中的作用域

隐藏规则

  1. 在继承体系中基类和派生类都有独立的作用域。

  2. 派生类和基类中有同名成员,基类同名成员会被屏蔽,不能直接访问,这种情况叫隐藏。(在派生类成员函数中,可以使用 基类::基类成员 显示访问)

  3. 需要注意的是如果是成员函数的隐藏,只需要函数名相同就构成隐藏。

  4. 注意在实际中在继承体系里面不使用多态最好不要定义同名的成员

#include <iostream>
using namespace std;

// Student 的 _num 和 Person 的 _num 构成隐藏关系,
// 可以看出这样代码虽然能跑,但是⾮常容易混淆
class Person
{
protected:

	string _name = "张三";	// 姓名
	int _num = 111;			// ⾝份证号
};

class Student : public Person
{
public:

	void Print()
	{
		cout << " 姓名:" << _name << endl;
		cout << " 身份证号:" << Person::_num << endl;
		cout << " 学号:" << _num << endl;
	}

protected:

	int _num = 999;			// 学号
};

int main()
{
	Student s1;
	s1.Print();

	return 0;
}

四、派生类的默认成员函数

4个常见默认成员函数

6 个默认成员函数,在派生类中生成方式:

  1. 派生类的构造函数必须调用基类的构造函数初始化基类的那一部分成员。如果基类没有默认或无参的构造函数,则必须在派生类构造函数的初始化列表阶段显示调用。

  2. 派生类的拷贝构造函数必须调用基类的拷贝构造完成基类的拷贝初始化。

  3. 派生类的 operator= 必须要调用基类的 operator= 完成基类的复制。需要注意的是派生类的 operator= 隐藏了基类的 operator=,所以显示调用基类的 operator=,需要指定基类作用域。

  4. 派生类的析构函数会在被调用完成后自动调用基类的析构函数清理基类成员。因为这样才能保证派生类对象先清理派生类成员再清理基类成员的顺序。

  5. 派生类对象初始化先调用基类构造再调用派生类构造。

  6. 派生类对象析构清理先调用派生类析构再调用基类的析构。

  7. 在多态中一些场景析构函数需要构成重写,重写的条件之一是函数名相同。那么编译器会对析构函数名进行特殊处理,处理成 destructor(),所以基类析构函数不加 virtual 的情况下,派生类析构函数和基类析构函数构成隐藏关系。

#include <iostream>
using namespace std;

class Person
{
public:

	Person(const char* name = "peter")
		: _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)
		: Person(name)
		, _num(num)
	{
		cout << "Student()" << endl;
	}

	Student(const Student& s)
		: Person(s)
		, _num(s._num)
	{
		cout << "Student(const Student& s)" << endl;
	}

	Student& operator = (const Student& s)
	{
		cout << "Student& operator= (const Student& s)" << endl;
		if (this != &s)
		{
			// 构成隐藏,所以需要显⽰调⽤
			Person::operator=(s);
			_num = s._num;
		}
		return *this;
	}

	~Student()
	{
		cout << "~Student()" << endl;
	}

protected:

	int _num;			//学号
};

int main()
{
	Student s1("jack", 18);
	Student s2(s1);
	Student s3("rose", 17);
	s1 = s3;

	return 0;
}

实现一个不能被继承的类

方法1:基类的构造函数私有,派生类的构成必须调用基类的构造函数,但是基类的构成函数私有化以后,派生类看不见就不能调用,那么派生类就无法实例化出对象。

方法2:C++11 新增一个 final 关键字,final 修饰基类,派生类就不能继承了。

#include <iostream>
using namespace std;

// C++11的⽅法
class Base final	// 限制继承
{
public:

	void func5() 
	{ 
		cout << "Base::func5" << endl; 
	}

protected:

	int _a = 1;

private:

	//C++98的⽅法
	//Base()
	//{
	//	//...
	//}
};

class Derive :public Base
{
	void func4() 
	{ 
		cout << "Derive::func4" << endl; 
	}

protected:

	int _b = 2;
};

int main()
{
	Base b;
	Derive d;

	return 0;
}

五、继承与友元

友元关系不能继承,也就是说基类友元不能访问派生类 私有保护 成员 。

#include <iostream>
using namespace std;

class Student;

class Person
{
	friend void Display(const Person& p, const Student& s);

protected:

	string _name = "张三";		// 姓名
};

class Student : public Person
{
	friend void Display(const Person& p, const Student& s);	// 加上友元

protected:

	int _stuNum = 18;		// 学号
};

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

int main()
{
	Person p;
	Student s;
	// 编译报错 Student::_stuNum ⽆法访问, 因为是 protected 成员
	// Display 也变成 Student 的友元即可解决
	Display(p, s);

	return 0;
}

六、继承与静态成员

基类定义了 static 静态成员,则整个继承体系里面只有一个这样的成员。无论派生出多少个派生类,都只有一个 static 成员实例。

#include <iostream>
using namespace std;

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;
}

七、多继承及其菱形继承问题

继承模型

单继承:一个派生类只有一个直接基类时称这个继承关系为单继承。

多继承:一个派生类有两个或以上直接基类时称这个继承关系为多继承,多继承对象在内存中的模型是,先继承的基类在前面,后面继承的基类在后面,派生类成员在放到最后面。
在这里插入图片描述

菱形继承:菱形继承是多继承的一种特殊情况。菱形继承的问题,从下面的对象成员模型构造,可以看出菱形继承有 数据冗余二义性 的问题,在 Assistant 的对象中 Person 成员会有两份。支持多继承就一定会有菱形继承,像 Java 就直接不支持多继承,规避掉了这里的问题。

在这里插入图片描述

#include <iostream>
using namespace std;

class Person
{
public:

	string _name;			// 姓名
};

class Student : public Person
{
protected:

	int _num;				// 学号
};

class Teacher : public Person
{
protected:

	int _id;				// 职⼯编号
};

class Assistant : public Student, public Teacher
{
protected:

	string _majorCourse;	// 主修课程
};

int main()
{
	// 编译报错,因为对 _name 的访问不明确
	Assistant a;
	//a._name = "peter";

	// 需要显⽰指定访问哪个基类的成员可以解决⼆义性问题,
	// 但是数据冗余问题⽆法解决
	a.Student::_name = "xxx";
	a.Teacher::_name = "yyy";

	return 0;
}

虚继承

有了多继承,就存在菱形继承,有了菱形继承就有菱形虚拟继承,如果 Assistant 多继承产生菱形继承,在一个基类被多个派生类继承时派生类加上 virtual 关键字就行,但底层实现就很复杂,性能也会有一些损失,所以最好不要设计出菱形继承。多继承可以认为是 C++ 的缺陷之一。

在这里插入图片描述

#include <iostream>
using namespace std;

class Person
{
public:

	string _name;			// 姓名
	//int _tel;
	//int _age;
	//string _gender;
	//string _address;
	// ...
};

// 使⽤虚继承 Person 类
class Student : virtual public Person
{
protected:

	int _num;				// 学号
};

// 使⽤虚继承 Person 类
class Teacher : virtual public Person
{
protected:

	int _id;				// 职⼯编号
};

// 教授助理
class Assistant : public Student, public Teacher
{
protected:

	string _majorCourse;	// 主修课程
};

int main()
{
	// 使⽤虚继承,可以解决数据冗余和⼆义性
	Assistant a;
	a._name = "peter";

	return 0;
}

可以设计出多继承,但是不建议设计出菱形继承,因为菱形虚拟继承以后,无论是使用还是底层都会复杂很多,维护与使用也很麻烦。

#include <iostream>
using namespace std;

class Person
{
public:

	Person(const char* name)
		:_name(name)
	{
		;
	}

	string _name;			// 姓名
};

class Student : virtual public Person
{
public:

	Student(const char* name, int num)
		:Person(name)
		, _num(num)
	{
		;
	}

protected:

	int _num;				// 学号
};

class Teacher : virtual public Person
{
public:

	Teacher(const char* name, int id)
		:Person(name)
		, _id(id)
	{
		;
	}

protected:

	int _id;				// 职⼯编号
};

// 不要轻易使用菱形继承
class Assistant : public Student, public Teacher
{
public:

	Assistant(const char* name1, const char* name2, const char* name3)
		:Person(name3)
		, Student(name1, 1)
		, Teacher(name2, 2)
	{
		;
	}

protected:

	string _majorCourse;	// 主修课程
};

int main()
{
	// 思考⼀下这⾥ a 对象中 _name 是 "张三", "李四", "王五" 中的哪⼀个?
	Assistant a("张三", "李四", "王五");

	return 0;
}

多继承中指针偏移问题

指针偏移距离是根据类的大小,而类的大小同样遵循内存对齐规则,请参考:(学习总结6)C语言结构体的内存对齐和位段实现

#include <iostream>
using namespace std;

class Base1 
{ 
public: 
	
	int _b1; 
};

class Base2 
{ 
public: 
	
	int _b2; 
};

class Derive : public Base1, public Base2 
{ 
public: 
	
	char _ch;		
	int _d; 
};

int main()
{
	Derive d;
	Base1* p1 = &d;
	Base2* p2 = &d;
	Derive* p3 = &d;

	cout << ((int)p1 - (int)(p1 - 1)) << endl;
	cout << ((int)p2 - (int)(p2 - 1)) << endl;
	cout << ((int)p3 - (int)(p3 - 1)) << endl;

	return 0;
}

八、继承和组合

继承是一种 is - a 的关系。也就是说每个派生类对象都是一个基类对象。

组合是一种 has - a 的关系。假设 B 组合了 A,每个 B 对象中都有一个 A 对象。

继承允许根据基类的实现来定义派生类的实现。这种通过生成派生类的复用通常被称为白箱复用(white-box reuse)。术语 “ 白箱 ” 是相对可视性而言:在继承方式中,基类的内部细节对派生类可见 。继承一定程度破坏了基类的封装,基类的改变,对派生类有很大的影响。派生类和基类间的依赖关系很强,耦合度高。

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

优先使用组合,组合的耦合度低,代码维护性好。当然,重复较高的类之间关系就适合继承(is - a),另外要实现多态,也必须要继承。类之间的关系既适合用继承(is - a)也适用组合(has - a),就用组合

#include <iostream>
using namespace std;

// Tire(轮胎) 和 Car(⻋) 更符合 has - a 的关系
class Tire 
{
protected:

	string _brand = "Michelin"; // 品牌
	size_t _size = 17;			// 尺⼨
};

class Car 
{
protected:

	string _colour = "白色";		// 颜⾊
	string _num = "XXXXXXX";	// ⻋牌号
	Tire _t1;					// 轮胎
	Tire _t2;					// 轮胎
	Tire _t3;					// 轮胎
	Tire _t4;					// 轮胎
};

// Car 和 BMW / Benz 更符合 is - a 的关系
class BMW : public Car
{
public:

	void Drive() 
	{ 
		cout << "好开 - 操控" << endl; 
	}
};

class Benz : public Car 
{
public:

	void Drive() 
	{ 
		cout << "好坐 - 舒适" << endl; 
	}
};

template<class T>
class vector
{

};

// Stack和vector的关系,既符合 is - a,也符合 has - a
template<class T>
class Stack1 : public vector<T>
{

};

template<class T>
class Stack2
{
public:

	vector<T> _v;
};

int main()
{

	return 0;
}

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

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

相关文章

DFS:二叉树中的深搜

✨✨✨学习的道路很枯燥&#xff0c;希望我们能并肩走下来! 文章目录 目录 文章目录 前言 一. 深搜的总结 二. 二叉树的深搜题目 2.1 计算布尔二叉树的值 2.2 求根节点到叶节点的数字之和 2.3 二叉树剪枝 2.4 验证二叉搜索树 2.5 二叉搜索树中第k小的节点 2.6 二叉树的…

【C++】——继承详解

目录 1、继承的概念与意义 2、继承的使用 2.1继承的定义及语法 2.2基类与派生类间的转换 2.3继承中的作用域 2.4派生类的默认成员函数 <1>构造函数 <2>拷贝构造函数 <3>赋值重载函数 <4析构函数 <5>总结 3、继承与友元 4、继承与静态变…

在VC++6.0中创建一个C++项目

1、下载并安装VC6.0 暂时不介绍下载安装&#xff0c;后续可能会补充 2、打开VC6.0 初始界面如下图&#xff1a; 3、创建一个空工程 文件-新建 在新建弹框中选择&#xff1a;工程-win32 console Application-填写工程名、选择保存路劲-确定 在新的弹框中&#xff0c;选择&…

BIT小学期-电话号码问题

Output 输出包括两个部分&#xff0c;第一个部分是错误的电话号码&#xff0c;对于这些号码应当按照输入的顺序以原始的形式输出。在输出错误电话号码前输出Error:&#xff0c;随后输出这些号码&#xff0c;如果没有错误的电话号码&#xff0c;则输出Not found. 第二部分是重…

[C++进阶]AVL树

前面我们说了二叉搜索树在极端条件下时间复杂度为O(n),本篇我们将介绍一种对二叉搜索树进行改进的树——AVL树 一、AVL 树的概念 二叉搜索树虽可以缩短查找的效率&#xff0c;但如果数据有序或接近有序二叉搜索树将退化为单支树&#xff0c;查找效率低下。因此&#xff0c;两位…

6个Python小游戏项目源码【免费】

6个Python小游戏项目源码 源码下载地址&#xff1a; 6个Python小游戏项目源码 提取码: bfh3

深度学习Day-33:Semi-Supervised GAN理论与实战

&#x1f368; 本文为&#xff1a;[&#x1f517;365天深度学习训练营] 中的学习记录博客 &#x1f356; 原作者&#xff1a;[K同学啊 | 接辅导、项目定制] 一、 基础配置 语言环境&#xff1a;Python3.8编译器选择&#xff1a;Pycharm深度学习环境&#xff1a; torch1.12.1c…

cout无法正常显示中文

cout无法正常显示中文 虽然你使用了buf.length()来指定写入的字节数&#xff0c;但是在包含中文字符&#xff08;UTF-8编码下每个中文字符占用3个字节&#xff09;的情况下&#xff0c;直接使用length()可能不会正确反映实际的字节数&#xff0c;因为它给出的是字符数而非字节…

基于深度学习,通过病理切片直接预测HPV状态|文献速递·24-09-16

小罗碎碎念 有段时间没有写文献速递的推文了&#xff0c;搞得自己今天写还怪不适应的。 今天所有的推文&#xff0c;都是围绕一个系统的问题展开——既研究了HPV与EBV在头颈癌/鼻咽癌中的致病机制&#xff0c;也总结了如何结合病理组学直接由WSI预测HPV状态——没办法&#x…

变压器漏感对整流电路的影响

目录 1. 电压波形畸变 2. 输出电压波动 3. 电流纹波增加 4. 降低整流效率 5. 影响开关器件的性能 6. EMI&#xff08;电磁干扰&#xff09;增加 总结与应对措施 变压器漏感在整流电路中会产生一些影响&#xff0c;尤其在高频应用或电流变化较大的情况下&#xff0c;其影…

【GESP】C++一级练习BCQM3006,多行输出

多行输出练习题&#xff0c;使用cout或printf函数输出多行内容。 BCQM3006 题目要求 描述 在windows的控制台环境中所有的字符都是等宽的&#xff0c;默认情况下窗口中每行有 80 个字符&#xff0c;每个屏幕有 25 行&#xff0c;组成了一个字符矩阵。利用控制台的这个特点&a…

什么是 HTTP/3?下一代 Web 协议

毫无疑问&#xff0c;发展互联网底层的庞大协议基础设施是一项艰巨的任务。 HTTP 的下一个主要版本基于 QUIC 协议构建&#xff0c;并有望提供更好的性能和更高的安全性。 以下是 Web 应用程序开发人员需要了解的内容。 HTTP/3 的前景与风险 HTTP/3 致力于让互联网对每个人…

从登录到免登录:JSP与Servlet结合Cookie的基本实现

前言 JSP中应用Cookie解析&#xff1a; 用户登录成功后&#xff0c;将用户信息保存到Cookie中&#xff0c;在页面读取Cookie并显示&#xff0c;不需要再次登录可以直接进入页面 第一步&#xff1a;创建JavaWeb项目&#xff0c;配置pom.xml文件 创建maven项目&#xff0c;项目名…

背包问题 总结详解

就是感觉之前 dp 的 blog 太乱了整理一下。 0-1 背包 例题:P1048 朴素算法 思路 对于一个物品&#xff0c;我们可以选&#xff0c;也可以不选。 我们用表示第 i 件物品的重量&#xff0c;表示第 i 件物品的价值。 考虑表示前 i 件物品放入容量为j的背包中的最大价值。 如…

时间复杂度计算 递归(solve2 后续)

原帖 最近校内比较忙&#xff0c;更新缓慢&#xff0c;致歉。 这里函数每次都需要遍历 h h h 和 m m m 之间的数&#xff08;复杂度 O ( n ) O(n) O(n)&#xff09;&#xff0c;所以和 solve1 略有不同。仍然假设 T ⁡ ( n ) \operatorname{T}(n) T(n) 表示 m − h 1 n…

【C++二叉树】606.根据二叉树创建字符串

606. 根据二叉树创建字符串 - 力扣&#xff08;LeetCode&#xff09; 图文分析&#xff1a; 代码实现&#xff1a; 代码说明&#xff1a; 1、前序遍历方式&#xff1a;根-左子树-右子树。 2、题目要求将二叉树转换为字符串输出&#xff0c;所以定义了一个string对象str。 3…

MySQL —— 视图

概念 视图是一张虚拟的表&#xff0c;它是基于一个或多个基本表或其他视图的查询结果集。 视图本身不存储数据&#xff0c;而是通过执行查询来动态生成数据&#xff0c;用户可以像操作普通表一样使用视图来进行查询更新与管理等操作。 视图本身也不占用物理存储空间&#xf…

网络安全学习(五)Burpsuite

经过测试&#xff0c;发现BP需要指定的JAVA才能安装。 需要的软件已经放在我的阿里云盘。 &#xff08;一&#xff09;需要下载Java SE 17.0.12(LTS) Java Downloads | Oracle 1.2023版Burp Suite 完美的运行脚本的环境是Java17 2.Java8不支持 看一下是否安装成功&#xff0c…

开源AI应用安全指导框架 — OWASP AI Exchange

在当今信息化迅猛发展的时代&#xff0c;网络专业人士正竞相提升人工智能&#xff08;AI&#xff09;安全领域的专业技能。随着这一趋势的推进&#xff0c;他们的企业也在快速地引入各类AI工具、平台、应用程序和服务&#xff0c;业界也相应涌现出众多资源&#xff0c;以协助从…

电梯电动车检测-目标检测数据集(包括VOC格式、YOLO格式)

电梯电动车检测-目标检测数据集&#xff08;包括VOC格式、YOLO格式&#xff09; 数据集&#xff1a; 链接&#xff1a;https://pan.baidu.com/s/1qRMdF08Jinx_5CRa3al24A?pwd3twc 提取码&#xff1a;3twc 数据集信息介绍&#xff1a; 共有 5347 张图像和一一对应的标注文件 …