C++笔记:OOP三大特性之继承

文章目录

  • 一、继承的概念和定义
    • 1.1 概念
    • 1.2 定义格式
    • 1.3 继承关系和访问限定符
  • 二、基类和派生类对象赋值兼容转换
    • 2.1 类型转换存在临时对象的意义
    • 2.2 赋值兼容转换不会产生临时变量
  • 三、继承中的作用域
  • 四、派生类中的默认成员函数
    • 4.1 构造
    • 4.2 拷贝构造
    • 4.3 赋值重载
    • 4.4 析构
  • 五、继承与友元
  • 六、继承与静态成员
  • 七、复杂的菱形继承和菱形虚拟继承
    • 7.1 单继承与多继承
    • 7.2 菱形继承与虚继承
    • 7.3 虚继承的原理
  • 八、继承的总结和反思

一、继承的概念和定义

1.1 概念

假设现在要去设计一个学生管理系统,从数据库设计的角度来看,系统的整体是由一个个实体和它们之间的关系组成的,典型的三大实体有学生(Student)、教师(Teacher)、学校领导(Leader)。

在面向对象的编程中,通常会使用类来表示这些实体,转换成代码得到如下三个类:
在这里插入图片描述

但是通过观察却发现,有一部分属性和方法是这三个类都有的,仔细一想也可以理解,因为像姓名、年龄、家庭住址、联系电话都是一个人应该具有的基本属性,但是同样的东西却定义多份,这会导致代码冗余。

为了解决代码冗余问题,C++之父Bjarne Stroustrup提出了一种机制,称为 “继承”。这种机制允许将多个类共有的特征提取出来放到一个称为 “父类” 或 “基类” 的类中。然后,其他类可以从这个父类继承这些共有特征,并在自己定义自己独有的成员。这些继承了共有特征的类被称为 “子类” 或 “派生类”。当需要使用父类中的特征时,子类可以直接从父类中获取。

class Person
{
public:
	void Print()
	{
		cout << "name:" << _name << endl;
		cout << "age:" << _age << endl;
	}
protected:
	string _name = "peter"; // 姓名
	int _age = 18; // 年龄
};

class Student : public Person
{
public:
	void SetInfo()
	{
		_name = "张三";
		_age = 55;
	}
protected:
	int _stuid; // 学号
};

class Teacher : public Person

{
protected:
	int _jobid; // 工号
};

int main()
{
	Person p;
	Student s;
	Teacher t;

	p.Print();
	s.Print();
	t.Print();
	return 0;
}

在这里插入图片描述

【说明】

  • Person 类是父类,Student 类和 Teacher 类是子类。
  • 子类继承了父类之后,子类对象(st)能够使用父类的的成员和方法。

1.2 定义格式

类之间继承的格式如下:
在这里插入图片描述

1.3 继承关系和访问限定符

继承方式有三种:public(公有)继承、protected(保护)继承、private(私有)继承。

访问限定符也有三种:public(公有)访问、protected(保护)访问、private(私有)访问。

基类中不同访问限定符修饰的成员在派生类中的访问权限会发生变化,总结之后会得到如下的一张表:

类成员/继承方式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继承下来的成员都只能在派生类的类里面使用,实际中扩展维护性不强。

在这里插入图片描述

二、基类和派生类对象赋值兼容转换

2.1 类型转换存在临时对象的意义

int main()
{
	int i = 97;
	char ch = 'a';

	if (ch == i)
		cout << "等于" << endl;
	else
		cout << "不等于" << endl;

	return 0;
}

在这段代码中,如果问chi是否相等,答案是肯定相等,但是chi却不是直接进行比较的。

chi在比较过程中,编译器会调用cmp指令,这个指令要求比较对象双方的类型是一致的,但是显然chi的类型不一样,所以ch会发生整型提升,然后产生一个int类型的临时对象,然后临时对象和i进行比较,这样的处理方式不会影响chi本身。

2.2 赋值兼容转换不会产生临时变量

  1. 派生类对象 可以赋值给 基类的对象 / 基类的指针 / 基类的引用。C++特殊规定,这个赋值过程稿中不会产生临时对象,而是将派生类对象中的基类对象切割出来进行赋值。

在这里插入图片描述

  1. 基类对象不能赋值给派生类对象。
class Person
{
protected:
	string _name; // 姓名
	string _sex; // 性别
	int _age; // 年龄
};

class Student : public Person
{
public:
	int _No; // 学号
};

int main()
{
	Student sobj;

	// 1.子类对象可以赋值给父类对象/指针/引用
	Person pobj = sobj;
	Person* pp = &sobj;
	Person& rp = sobj;

	// 2.基类对象不能赋值给派生类对象
	// error C2679: 二元“=”: 没有找到接受“Person”类型的右操作数的运算符(或没有可接受的转换)
	sobj = pobj;
	
	return 0;
}

三、继承中的作用域

  1. 在继承体系中基类派生类都有独立的作用域
  2. 子类和父类中有同名成员,子类成员将屏蔽父类对同名成员的直接访问,这种情况叫隐藏,也叫重定义。(在子类成员函数中,可以使用 基类::基类成员 显式访问)
  3. 需要注意的是如果是成员函数的隐藏,只需要函数名相同就构成隐藏。
  4. 注意在实际中在继承体系里面最好不要定义同名的成员
class Person
{
public:
	void Print(int a)
	{
		cout << "Person::Print(int a)" << endl;
	}
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();
	s1.Person::Print(10);
	return 0;
}

在这里插入图片描述

  1. Student 类的 _num 和 Person 类的 _num 构成隐藏关系,由于就近原则直接访问只会访问到 Student 类的 _num,如果想要访问 Person 类的 _num 要加Person::指定类域.
  2. 两个类中的Print由于同名也构成隐藏关系,默认只会访问Student类的Print方法,访问Person类的Print方法同样需要指定类域。

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

一个类有 6 个默认成员函数,“ 默认 ” 的意思就是指我们不写,编译器会变我们自动生成一个(类的默认构造其实有三个,不过这里默认都是编译器自动生成的),这部分的内容主要是,默认成员成员函数的行为是怎样的,以及当基类没有默认成员函数时该如何显式调用。

事先说明,这里只涉及 6 个默认成员函数中的 4 个。

4.1 构造

  1. 与一般类对象相比,派生类对象多存储了基类部分的成员,由于派生类和基类是独立的两部分,所以派生类对象中的基类部分的成员应当调用基类的构造函数去初始化,而派生类对象中派生类部分的成员则调用派生类自己的构造函数去初始化
  2. 如果派生类没有实现构造函数,编译器会自动生成一个默认的构造函数,该函数对内置类型成员不做处理,对自定义类型成员会去调用它的默认构造函数,对基类部分成员会去调用基类的默认构造函数。
  3. 如果基类没有默认构造函数,那么需要通过初始化列表显式调用基类的构造函数,调用的方式是直接调用,如 Person(name)
  4. 由于基类比派生类先定义,所以初始化列表会先调用基类构造函数再初始化其余成员。
// 基类
class Person
{
public:
	Person(const char* name = "peter")
		: _name(name)
	{
		cout << "Person()" << endl;
	}
protected:
	string _name; // 姓名
};

// 派生类
class Student : public Person
{
public:
	Student(const char* name, int num)
		: Person(name)	// 1. 父类成员调用父类构造,子类成员调用子类构造;
						// 2. 父类无默认构造直接显式调用父类构造;
						// 3. 父类比子类先定义,初始化列表顺序为先父后子
		, _num(num)
	{
		cout << "Student()" << endl;
	}
protected:
	int _num; //学号
};

int main()
{
	Student s1("jack", 18);
	return 0;
}

在这里插入图片描述

4.2 拷贝构造

  1. 与默认构造函数的行为类似,我们不实现派生类的拷贝构造函数,编译器会自动生成一个默认的拷贝构造函数,该函数对内置类型成员会进行字节序的值拷贝,对自定义类型成员会去调用它的拷贝构造,对基类部分成员会去调用基类的默认拷贝构造函数。
  2. 如果是一个深拷贝的类就需要自己去显式实现拷贝构造函数,对于派生类类成员用对象的派生类部分的成员完成拷贝,对于基类要用派生类对象中基类的那一部分来进行拷贝,函数传参的过程中通过赋值兼容转换将派生类对象中的基类部分切割出来。
  3. 在派生类调用基类的拷贝构造函数要通过初始化列表,调用方式是直接调用,如:Person(s)
// 基类
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;
	}

protected:
	string _name; // 姓名
};

// 派生类
class Student : public Person
{
public:
	Student(const char* name, int num)
		: Person(name)	// 1. 父类成员调用父类构造,子类成员调用子类构造;
						// 2. 父类无默认构造直接显式调用父类构造;
						// 3. 父类比子类先定义,初始化列表顺序为先父后子
		, _num(num)
	{
		cout << "Student()" << endl;
	}

	Student(const Student& s)
		: Person(s)		
	// 1. 与构造类似,拷贝构造也是类名显式调用
	// 2. 一般的类不需要写,默认生成的拷贝构造对自定义类型完成值拷贝,对自定义类型去调用它的拷贝构造,这里的父类同样也是去调用父类的拷贝构造
	//	  如果是一个深拷贝的类就需要自己去写拷贝构造,自己写拷贝构造该怎么实现
	//	  对于子类成员用子类部分的成员拷贝
	//	  对于父类要用子类对象中父类的那一部分来进行拷贝,可以该怎么把子类对象中父类的那一部分切出来?
	//    赋值兼容转换!Student对象作为参数传给Person(),传参会进行切片,编译器把子类对象中父类的那一部分切出来形成一个新的Person对象形参
		, _num(s._num)
	{
		cout << "Student(const Student& s)" << endl;
	}

protected:
	int _num; //学号
};

int main()
{
	Student s1("jack", 18);
	Student s2(s1);
	return 0;
}

在这里插入图片描述

4.3 赋值重载

  1. 我们不显式实现派生类中的赋值重载,编译器自动生成的赋值重载对内置类型进行字节序的值拷贝,对自定义类型调用它的赋值重载,对派生类中的基类部分会去调用基类中的赋值重载。
  2. 由于基类中的赋值重载和派生类中的赋值重载同名,两个函数构成隐藏关系,调用基类的赋值重载需要指定类域。
// 基类
class Person
{
public:
	Person(const char* name = "peter")
		: _name(name)
	{
		cout << "Person()" << endl;
	}

	Person& operator=(const Person& p)
	{
		cout << "Person operator=(const Person& p)" << endl;
		if (this != &p)
			_name = p._name;
		return *this;
	}
protected:
	string _name; // 姓名
};

// 派生类
class Student : public Person
{
public:
	Student(const char* name, int num)
		: Person(name)	// 1. 父类成员调用父类构造,子类成员调用子类构造;
						// 2. 父类无默认构造直接显式调用父类构造;
						// 3. 父类比子类先定义,初始化列表顺序为先父后子
		, _num(num)
	{
		cout << "Student()" << endl;
	}

	Student& operator = (const Student& s)
	{
		cout << "Student& operator= (const Student& s)" << endl;
		// 如果不是自己给自己赋值
		if (this != &s)
		{
			// 父类的调用父类的来完成赋值
			Person::operator =(s);
			// 子类的用子类成员变量完成赋值
			_num = s._num;
		}
		// 最后返回自己的引用
		return *this;
	}
protected:
	int _num; //学号
};

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

在这里插入图片描述

4.4 析构

特殊规定:

  1. 派生类析构函数和基类析构函数构成隐藏关系,这是因为多态的原因,析构函数都会被特殊处理,函数名会被处理成destructor()
  2. 为了保证析构顺序是先派生类后基类,基类析构会在派生类析构后自动调用,这是因为先基类后派生类是有安全隐患,可能基类的资源已经被清理了但是由于某些原因,派生类的析构函数又去访问基类的资源,存在野指针的风险。
class Person
{
public:
	Person(const char* name = "peter")
		: _name(name)
	{
		cout << "Person()" << endl;
	}

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

class Student : public Person
{
public:
	Student(const char* name, int num)
		: Person(name)	// 1. 父类成员调用父类构造,子类成员调用子类构造;
						// 2. 父类无默认构造直接显式调用父类构造;
						// 3. 父类比子类先定义,初始化列表顺序为先父后子
		, _num(num)
	{
		cout << "Student()" << endl;
	}

	~Student()
	{
		cout << "~Student()" << endl;
	}
protected:
	int _num; //学号
};

int main()
{
	Student s1("jack", 18);
	return 0;
}

在这里插入图片描述

五、继承与友元

继承指的是继承基类的成员,友元不是基类的成员,所以派生类无法继承基类的友元关系。

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)
{
	// Display是Person类的友元,能够访问Person类的_name
	cout << p._name << endl;
	// 友元关系不能继承,所以Display不是Student的友元,Display无法访问_stuNum
	// 如果Display也想访问Student的_stuNum,得在Student类内加上友元声明
	// error:cout << s._stuNum << endl;
}

void main()
{
	Person p;
	Student s;
	Display(p, s);
}

六、继承与静态成员

  1. 静态成员存储在静态区,派生类继承的是访问权,而不是在派生类又生成一份。
  2. 在整个继承体系中,无论派生出多少个子类,都只有这一个static成员实例。
  3. 指定该继承体系中的任一类域都可以访问到static成员。
class Person
{
public:
	Person() { ++_count; }
protected:
	string _name; // 姓名
public:
	static int _count; // 统计人的个数。
};

int Person::_count = 0;

class Student : public Person
{
protected:
	int _stuNum; // 学号
};

class Graduate : public Student
{
protected:
	string _seminarCourse; // 研究科目
};
int main()
{
	// 统计创建了多少个对象
	Student s1;
	Student s2;
	Student s3;
	Graduate s4;
	cout << " 人数 :" << Person::_count << endl;
	// 继承体系中的任意类域都可以访问
	Student::_count = 0;
	cout << " 人数 :" << Person::_count << endl;
	// static成员只会存在一份
	cout << " Person::_count 地址 :" << &Person::_count << endl;
	cout << " Student::_count 地址 :" << &Student::_count << endl;

	return 0;
}

在这里插入图片描述

七、复杂的菱形继承和菱形虚拟继承

7.1 单继承与多继承

  • 单继承:一个子类只有一个直接父类时称这个继承关系为单继承

在这里插入图片描述

  • 多继承:一个子类有两个或以上直接父类时称这个继承关系为多继承

在这里插入图片描述

7.2 菱形继承与虚继承

菱形继承是多继承的一种特殊情况。
在这里插入图片描述
菱形继承的问题:从下面的对象成员模型构造,可以看出菱形继承有数据冗余和二义性的问题。在Assistant的对象中Person成员会有两份。
在这里插入图片描述

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; // 主修课程
};
void Test()
{
	// 这样会有二义性无法明确知道访问的是哪一个
	Assistant a;
	a._name = "peter";	// error C2385: 对“_name”的访问不明确
	
	// 显示指定访问哪个父类的成员可以解决二义性问题,但是数据冗余问题无法解决
	a.Student::_name = "xxx";
	a.Teacher::_name = "yyy";
}

虚拟继承可以解决菱形继承的二义性和数据冗余的问题。如上面的继承关系,在 Student 类和 Teacher 类继承 Person 类时使用虚拟继承(添加virtual关键字),即可解决问题。需要注意的是,虚拟继承不要在其他地方去使用,换言之,需要注意virtual关键字在菱形继承的哪些类里添加。

class Person
{
public:
	string _name; // 姓名
};
// 直接基类是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;
}

7.3 虚继承的原理

语法上解决菱形继承只需要使用虚继承即可,但是底层上的解决方法是怎样的?

接下来就用一个简化的菱形继承继承体系并借助内存窗口观察对象成员的模型,来研究虚拟继承原理。
注意:以下探究过程在VS2019上进行,别的编译器使用的解决方案可能在细节上略有差异

class A
{
public:
	int _a;
};

// class B : public A
class B : virtual public A
{
public:
	int _b;
};

// class C : public A
class C : virtual public A
{
public:
	int _c;
};

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

int main()
{
	D d;
	d.B::_a = 1;
	d.C::_a = 2;
	d._b = 3;
	d._c = 4;
	d._d = 5;
	return 0;
}

下图是菱形继承的内存对象成员模型:这里可以看到数据冗余
在这里插入图片描述

下图是菱形虚拟继承的内存对象成员模型:

从内存窗口可以分析出:

  1. 原本D类对象d中存在两份的成员_a,被抽出来放到的了对象组成的最下面,此时这个成员_a同时属于B类和C类且只存在一份,解决了数据冗余。
  2. B类和C类中原本存储_a的位置分别被替换成了一个指针,通过指针能够找到一张表,这两个指针被称为虚基表指针,这两张表被称之为虚基表,虚基表中存储的是偏移量,通过偏移量能够找到成员_a

在这里插入图片描述

现有下面两句代码,pb指针该如何找到_a呢?

B* pb = &d;
pb->_a++;

第一,pb指针是一个指向 B 类对象的指针,B* pb = &d;的赋值过程会发生兼容转换,将对象d中的B类部分的指针返回给pb指针,即pb指针的内容是0x00FAF8D0

第二,pb->_a这个操作将会去读取B类中_a存储的内容,由于虚继承的缘故,读取到的是一个虚基表指针,即0x00c07be8,编译器通过虚基表指针找到虚基表,通过虚基表可以直到,现在_a相对于pb指针(0x00FAF8D0)的偏移量是0x14

最后,编译器会在0x00FAF8D0 + 0x14 的地址处找到_a的位置,然后完成++操作。

上面的模型是对象d的模型,其实B类、C类的模型也发生了改变,B类、C类对象访问成员_a时也需要通过虚基表找偏移量,以下面这几句代码为例:

B bb;
B* pb = &bb;
pb->_a++;

在这里插入图片描述

下面是上面的Person关系菱形虚拟继承的原理解释:
在这里插入图片描述

八、继承的总结和反思

  1. 很多人说C++语法复杂,其实多继承就是一个体现。有了多继承,就可能存在菱形继承,有了菱形继承就有得涉及菱形虚拟继承,而它的底层实现就很复杂。因此,一般不建议设计出多继承,一定不要设计出菱形继承。否则在复杂度及性能上都有问题。
  2. 多继承可以认为是C++的缺陷之一,很多后来的 OOP 语言都没有多继承,如 Java。
  3. 继承被称为 “ 白箱复用 ”,术语“白箱”是相对可视性而言:在继承方式中,基类的内部细节对子类可见 。继承一定程度破坏了基类的封装,基类的改变,对派生类有很大的影响。派生类和基类间的依赖关系很强,耦合度高。
  4. 组合被称为 “ 黑箱复用 ”,因为对象的内部细节是不可见的。对象只以“黑箱”的形式出现。组合类之间没有很强的依赖关系,耦合度低。优先使用对象组合有助于你保持每个类被封装。
    在这里插入图片描述
  5. 继承和组合的选择
    • public继承是一种 is-a 的关系。 比如说Person类和Student类,学生是一个人。
    • 组合是一种 has-a 的关系。假设B组合了A,每个B对象中都有一个A对象。
    • 实际尽量多去用组合。组合的耦合度低,代码维护性好。不过继承也有用武之地的,有些关系就适合继承那就用继承,另外要实现多态,也必须要继承。类之间的关系可以用继承,可以用组合,就用组合。
// Car和BMW Car和Benz构成is-a的关系
class Car {
protected:
	string _colour = "白色"; // 颜色
	string _num = "陕ABIT00"; // 车牌号
};

class BMW : public Car {
public:
	void Drive() { cout << "好开-操控" << endl; }
};

class Benz : public Car {
public:
	void Drive() { cout << "好坐-舒适" << endl; }
};

// Tire和Car构成has-a的关系
class Tire {
protected:
	string _brand = "Michelin"; // 品牌
	size_t _size = 17; // 尺寸
};

class Car {
protected:
	string _colour = "白色"; // 颜色
	string _num = "陕ABIT00"; // 车牌号
	Tire _t; // 轮胎
};

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【进程创建】

目录 进程创建的方式查看进程pid 调用系统调用创建子进程fock函数做了的工作子进程刚开始创建的状态 一个变量&#xff0c;两个不同的值创建子进程的作用 进程创建的方式 1.在操作系统上输入的指令。 2.已经启动的软件。 3.程序员在代码层面上调用系统调用创建进程。 linux中第…

centos6安装

前期准备 官网 选择download 选择isos 选择mininal.iso下载 安装 打开vmware 输入root和密码登录

Block Coordinate Descent算法的部分构造技巧

文章目录 构造的目的定理另一篇中对于该定理的表述出处 构造的目的 通过增加辅助变量&#xff0c;使原来的非凸问题变为关于各个变量的凸子问题&#xff0c;交替优化各个辅助变量。 定理 Define an m m m by m m m matrix function E ( U , V ) ≜ ( I − U H H V ) ( I …

工具分享:Corn表达式在线生成工具介绍

无极低码 &#xff1a;https://wheart.cn Corn表达式在线生成工具介绍 在现代的信息技术领域中&#xff0c;定时任务是一个不可或缺的功能。无论是为了定期清理缓存、发送邮件、还是执行其他自动化任务&#xff0c;我们都需要一个高效、便捷的定时工具。而在众多的定时工具中…

Microsoft PowerToys:自定义 Windows 的实用程序

Microsoft PowerToys&#xff1a;自定义 Windows 的实用程序 Microsoft PowerToys 是一组实用程序&#xff0c;供高级用户调整和简化其 Windows 体验以提高工作效率。 微软官方地址为&#xff1a;https://learn.microsoft.com/en-us/windows/powertoys/&#xff0c;其github源…

springboot207基于springboot的实习管理系统

实习管理系统的设计与实现 摘要 近年来&#xff0c;信息化管理行业的不断兴起&#xff0c;使得人们的日常生活越来越离不开计算机和互联网技术。首先&#xff0c;根据收集到的用户需求分析&#xff0c;对设计系统有一个初步的认识与了解&#xff0c;确定实习管理系统的总体功…

qt-OPENGL-星系仿真

qt-OPENGL-星系仿真 一、演示效果二、核心程序三、下载链接 一、演示效果 二、核心程序 #include "model.h"Model::Model(QOpenGLWidget *_glWidget) { glWidget _glWidget;glWidget->makeCurrent();initializeOpenGLFunctions(); }Model::~Model() {destroyV…

HashMap 源码学习-jdk1.8

1、一些常量的定义 这里针对MIN_TREEIFY_CAPACITY 这个值进行解释一下。 java8里面&#xff0c;HashMap 的数据结构是数组 &#xff08;链表或者红黑树&#xff09;&#xff0c;每个数组节点下可能会存在链表和红黑树之间的转换&#xff0c;当同一个索引下面的节点超过8个时…

Elastic Stack--01--简介、安装

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 1. Elastic Stack 简介为什么要学习ESDB-Engines搜索引擎类数据库排名常年霸榜![在这里插入图片描述](https://img-blog.csdnimg.cn/direct/051342a83f574c8c910cda…

【软件架构】02-复杂度来源

1、性能 1&#xff09;单机 受限于主机的CPU、网络、磁盘读写速度等影响 在多线程的互斥性、并发中的同步数据状态等&#xff1b; 扩展&#xff1a;硬件资源、增大线程池 2&#xff09;集群 微服务化拆分&#xff0c;导致调用链过长&#xff0c;网络传输的消耗过多。 集…

【Web前端笔记10】CSS3新特性

10 CSS3新特性 &#xff11;、圆角 &#xff12;、阴影 &#xff08;&#xff11;&#xff09;盒阴影 &#xff13;、背景渐变 &#xff08;&#xff11;&#xff09;线性渐变&#xff08;主要掌握这种就可&#xff09; &#xff08;&#xff12;&#xff09;径向渐变 &…

滚雪球学Java(67):深入理解 TreeMap:Java 中的有序键值映射表

咦咦咦&#xff0c;各位小可爱&#xff0c;我是你们的好伙伴——bug菌&#xff0c;今天又来给大家普及Java SE相关知识点了&#xff0c;别躲起来啊&#xff0c;听我讲干货还不快点赞&#xff0c;赞多了我就有动力讲得更嗨啦&#xff01;所以呀&#xff0c;养成先点赞后阅读的好…

C++学习之list容器

C++ list基本概念 在C++中,std::list是一个双向链表(doubly linked list)容器,它包含在 <list> 头文件中。下面是一些关于C++ std::list的基本概念: 双向链表结构:std::list是由多个节点组成的双向链表结构,每个节点包含数据元素和指向前一个节点和后一个节点的指…

ABCDE联合创始人BMAN确认出席Hack .Summit() 2024香港Web3盛会

ABCDE联合创始人和普通合伙人BMAN确认出席Hack .Summit() 2024&#xff01; ABCDE联合创始人和普通合伙人BMAN确认出席由 Hack VC 主办&#xff0c;并由 AltLayer 和 Berachain 联合主办&#xff0c;与 SNZ 和数码港合作&#xff0c;由 Techub News 承办的Hack.Summit() 2024区…

穿越Redis单线程迷雾:从面试场景到技术内核的解读

目录 ​编辑 前言 Redis中的多线程 I/O多线程 Redis中的多进程 结论 延伸阅读 前言 很多人都遇到过这么一道面试题&#xff1a;Redis是单线程还是多线程&#xff1f;这个问题既简单又复杂。说他简单是因为大多数人都知道Redis是单线程&#xff0c;说复杂是因为这个答案…

Kotlin学习 6

1.接口 interface Movable {var maxSpeed: Intvar wheels: Intfun move(movable: Movable): String}class Car(var name: String, override var wheels: Int 4, _maxSpeed: Int) : Movable {override var maxSpeed: Int _maxSpeedget() fieldset(value) {field value}overr…

C++ Primer 笔记(总结,摘要,概括)——第4章 表达式

目录 4.1 基础 4.1.1 基本概念 4.1.2 优先级与结合律 4.1.3 求值顺序 4.2 算术运算符 4.3 逻辑和关系运算符 4.4 赋值运算符 4.5 递增和递减运算符 4.6 成员访问运算符 4.7 条件运算符 4.8 位运算符 4.9 sizeof运算符 4.10 逗号运算符 4.11 类型转换 4.11.1 算数转换…

Java的编程之旅19——使用idea对面相对象编程项目的创建

在介绍面向对象编程之前先说一下我们在idea中如何创建项目文件 使用快捷键CtrlshiftaltS新建一个模块&#xff0c;点击“”&#xff0c;再点New Module 点击Next 我这里给Module起名叫OOP,就是面向对象编程的英文缩写&#xff0c;再点击下面的Finish 点Apply或OK均可 右键src…

day3:界面跳转,qss与对话框

思维导图 完善对话框&#xff0c;点击登录对话框&#xff0c;如果账号和密码匹配&#xff0c;则弹出信息对话框&#xff0c;给出提示”登录成功“&#xff0c;提供一个Ok按钮&#xff0c;用户点击Ok后&#xff0c;关闭登录界面&#xff0c;跳转到其他界面 如果账号和密码不匹配…

成像光谱遥感技术中的AI革命:ChatGPT应用指南

遥感技术主要通过卫星和飞机从远处观察和测量我们的环境&#xff0c;是理解和监测地球物理、化学和生物系统的基石。ChatGPT是由OpenAI开发的最先进的语言模型&#xff0c;在理解和生成人类语言方面表现出了非凡的能力。重点介绍ChatGPT在遥感中的应用&#xff0c;人工智能在解…