C++ [继承]

news2024/11/23 19:02:36

继承

本文已收录至《C++语言和高级数据结构》专栏!
作者:ARMCSKGT

CSDN


继承

  • 前言
  • 正文
    • 继承的概念及定义
      • 继承的概念
      • 继承的定义
      • 重定义
    • 基类和派生类对象赋值转换
    • 派生类中的默认成员函数
      • 隐式调用
      • 显示调用
    • 继承中的友元与静态成员
      • 友元
      • 静态成员
    • 菱形继承
      • 概念
    • 虚继承原理
    • 继承和组合
  • 最后


前言

面向对象的三大特性:封装,继承和多态,前面我们介绍了类和对象,如何对数据和数据操作方法进行封装,本章将为大家介绍另一大特性-继承,继承可以增强代码的复用性增强功能的可扩展性,我们将学习如何通过父类衍生出更多特性的子类!
水果类
这里,水果类作为父类,衍射出三种水果,苹果,西瓜和荔枝,这三种具体的水果都可以统称为水果,但是各自又有着自己的特性,例如苹果外皮是红色的,西瓜外皮是绿色的等等,这就是继承,继承将一个抽象的父类更加具体的实例表达!


正文


继承的概念及定义


继承的概念

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

其中:

  • 被继承的类是:父类/基类 (base)
  • 继承的类称为:子类/派生类 (derived)

继承的本质就是复用代码,我们通过一个校园信息管理类代码来展示和说明:
#include <iostream>
#include <string>
using namespace std;

>//基本人员信息类
class Person
{
public:
	string Name;         //姓名
	int Car_ID;          //身份证号
	string Date;         //生日
	long long telephone; //电话号码
};

//教师类
class Teacher : public Person
{
public:
	int Employee_ID; //教师员工号
	string Subjects; //教师所教科目
	double Wages;    //工资
	//...
};

//学生类
class Student : public Person
{
public:
	int Student_ID;     //学生学号
	string Faculties;   //学生所属院系
	string Specialized; //学生专业
	//...
};

我们在学校,最常见的两个身份就是老师和学生,老师和学生都有自己的姓名和身份证号等,但是老师有自己的员工号和任职科目,学生有自己的学号和专业信息,通过继承父类Person可以让老师和学生都拥有人的基本信息,然后在老师和学生类中增加不同身份所独有的特性,这样就不需要每个类都写上人员基本信息的变量条目了,实现了复用,当然函数后亦是如此!

继承的作用: 子类在继承父类后,会继承父类 公开(public)和保护(protected) 的成员,除了父类私有成员外,子类可以继承其余的所有成员!
结合访问限定符,可以让子类合理访问父类成员,互不干扰!

继承的定义

介绍完继承的定义相关知识,我们开始使用继承!
继承的格式如下:

class 子类 : 继承方式 父类 { /*子类成员*/ };

继承的使用

继承方式与权限
继承方式与权限

关于访问限定符有三种:

  • 公有(public):该成员可以被任意访问
  • 保护(protected):该成员只能被本类和子类中访问
  • 私有(private):该成员只能在本类中被访问

其中:保护在没有继承的情况下和private一样,外界无法访问,在有父子类的情况下才能有区别!
权限范围从大到小依次为: public > protected > private

访问限定符有三种,继承的方式则也有三种,继承方式与成员的访问限定符组合可以有多种情况:

访问/继承 权限公有public保护protected私有private
父类public成员仍然为public变为protected变为private
父类protected成员仍然为protected仍然为protected权限为private
父类private成员不可见不可见不可见

补充:

  • 父类(基类)的私有成员在子类中始终不可见,但并不代表不在子类中,其在子类中仍然被继承,但是不可见!
  • 父类(基类)的成员如果不想在类外被访问,但是可以被子类访问,则设置为protected,protected是专门为该场景设计的!
  • 在成员的继承上,权限只能被缩小,不能被放大,即成员最终的权限是在成员本身的访问权限和继承权限中取最小权限即可!
  • 在class继承时,如果我们不显示指定继承方式,则默认为private;在struct继承时,如果不显示指定继承方式,则默认为public;平时不建议省略继承方式!
  • 在实际运用中一般使用都是public继承,几乎很少使用protetced/private继承,也不提倡使用protetced/private继承,因为protetced/private继承下来的成员都只能在派生类的类里面使用,实际中扩展维护性不强!

重定义

重定义也叫隐藏(覆盖),是指子类继承父类后,子类重新声明的成员名字与父类相同,此时访问子类成员时默认访问的是子类的同名成员而隐藏父类成员(局部优先原则),同时也证明了父子类中的作用域是独立存在的!

class Base
{
public:
	void func() { cout << "Base num:" << num << endl; } //同名方法
	void Bfunc() { cout << "Bfunc" << endl; } //非同名方法
	int num = 1; //同名变量
	char b = 'b'; //非同名变量
};

class Derived : public Base
{
public:
	void func() { cout << "Derived num:" << num << endl; }
	void Dfunc() { cout << "Dfunc" << endl; }
	int num = 2;
	char d = 'd';
};

int main()
{
	Derived der; //声明子类对象
	return 0;
}

执行同名函数func:
执行同名函数func
此时我们发现,对于同名函数和同名变量,执行时访问子类的同名函数和变量!

如果我们删除子类的同名变量再执行:

此时执行的是子类函数访问的是父类的num变量!

访问各自的非同名成员:
访问各自的非同名成员
对于非同名成员,访问是正常的!

如果我们想要访问父类的隐藏成员,可以使用 作用域限定符 :: 指定父类成员进行显示访问!

int main()
{
	Derived der;
	der.Base::func();
	cout << der.Base::b << endl;
	return 0;
}



总结:

  • 在继承体系中基类和派生类都有独立的作用域。
  • 子类和父类中有同名成员,子类成员将屏蔽父类对同名成员的直接访问,这种情况叫隐藏,
    也叫重定义(在子类成员函数中,可以使用 基类::基类成员 显示访问)。
  • 如果是成员函数的隐藏,只需要函数名相同就构成隐藏。
  • 注意在实际中在继承体系里面最好不要定义同名的成员。

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


派生类对象 可以赋值给 基类的对象 / 基类的指针 / 基类的引用。这里有个形象的说法叫切片
或者切割
。寓意把派生类中父类那部分切来赋值过去。

基类对象不能赋值给派生类对象,子类是在基类的基础上进行拓展,将基类赋值给子类时需要编译器去推敲父类对象中需要增加什么,编译器不敢随便做这种事情(毕竟儿子不可能道反天罡)。

当基类的指针(引用)指向子类对象时,可以通过强制类型转换赋值给子类的指针(引用)。注意!这里必须是基类指针(引用)指向子类对象才是安全的!如果基类如果是多态类型,可以使用C++11中的安全的类型转换 dynamic_cast 进行赋值!
切片

总结:

  • 对于父类和子类之间的对象赋值会涉及类型转换,类似于double转int等。
  • 子类可以赋给父类对象,其中会产生类型转换,所以会有临时变量。
  • 但是父类不能赋值给子类(可以理解为子类的成员比父类多,父类无法判断),但是父类的指针可以指向子类。
  • 子类赋值给父类需要公开继承,是天然支持的,不存在类型转换(子类对象是一个特殊的父类对象)。
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.基类对象不能赋值给派生类对象 例如 sobj = pobj;
	
	// 3.基类的指针可以通过强制类型转换赋值给派生类的指针
	pp = &sobj;
	Student * ps1 = (Student*)pp; // 这种情况转换时可以的。
	ps1->_No = 10;
	pp = &pobj;
	Student* ps2 = (Student*)pp; // 这种情况转换时虽然可以,但是会存在越界访问的问题
	ps2->_No = 10;

	return 0;
}

派生类中的默认成员函数


前面我们介绍过,类的六大默认成员函数:
切片
同样的,子类在继承父类后也会有这六个默认成员函数;但是子类不写类的六大默认函数则调用时使用父类的默认函数,如果实现了这六个默认成员函数则按照隐藏的规则,在调用时默认调用子类的这六个默认成员函数,对于父类的则需要显示调用,这里我们对这种情况进行分析!

隐式调用

子类在实例化调用构造函数时,会先调用父类的构造函数先初始化父类再初始化子类本身;同样的,子类在析构时也会先析构自己再调用父类的析构函数析构父类空间!

class Base
{
public:
	Base() { cout << "Base()" << endl; }
	~Base() { cout << "~Base()" << endl; }
};

class Derived : public Base
{
public:
	Derived() { cout << "Derived()" << endl; }
	~Derived() { cout << "~Derived()" << endl; }
};

int main()
{
	Derived der;
	return 0;
}


注意: 自动调用是由编译器完成的,如果不存在默认构造函数,那么需要我们手动在子类的构造函数中初始化父类对象,如果不处理则会报错!


显示调用

对于一些类,我们不妨会使用 赋值重载拷贝构造 这里如果我们不对相关函数进行特殊处理,在发生拷贝时,子类无法调用父类的赋值重载和拷贝构造函数,父类部分极易容易发生浅拷贝!

class Base
{
public:
	Base() {}
	~Base() {}
	Base(const Base& b) { cout << "Base(const Base& b)" << endl; }
	Base& operator=(const Base& b)
	{ 
		cout << "operator=(const Base& b)" << endl; 
		return *this;
	}
};

//子类中不对父类对象做特殊除了
class Derived : public Base
{
public:
	Derived() {}
	~Derived() {}
	Derived(const Derived& d) { cout << "Derived(const Derived& d)" << endl; }
	Derived& operator=(const Derived& d) 
	{ 
		cout << "operator=(const Derived& d)" << endl; 
		return *this; 
	}
};

//子类中对父类进行特殊处理
//class Derived : public Base
//{
//public:
//	Derived() {}
//	~Derived() {}
//	Derived(const Derived& d):Base(d) //切片 构造父类对象 初始化父类
//	{ 
//		cout << "Derived(const Derived& d)" << endl; 
//	}
//	Derived& operator=(const Derived& d) 
//	{ 
//		Base::operator=(d); //切片 构造父类对象 赋值给子类的父类部分
//		cout << "operator=(const Derived& d)" << endl; 
//		return *this; 
//	}
//};

int main()
{
	Derived der1;
	Derived der2(der1);
	cout << "-----------------------------" << endl;
	der1 = der2;

	return 0;
}

我们分别对特殊处理和不特殊处理的Derived类,在同一段main函数代码下测试,测试结果:


我们可以发现,在子类的成员函数中对父类隐藏的成员函数显示调用进行特殊处理,可以避免浅拷贝的问题,而且我们可以在子类中 通过 初始化列表:: 显示调用父类 构造函数 和 默认成员函数父类的拷贝问题进行处理!

总结:

  • 派生类的构造函数必须调用基类的构造函数初始化基类的那一部分成员。如果基类没有默认
    的构造函数,则必须在派生类构造函数的初始化列表阶段显示调用。
  • 派生类的拷贝构造函数必须调用基类的拷贝构造在初始化列表完成基类的拷贝初始化。
  • 派生类的operator=必须要调用基类的operator=完成基类的复制。
  • 派生类的析构函数会在被调用完成后自动调用基类的析构函数清理基类成员。因为这样才能
    保证派生类对象先清理派生类成员再清理基类成员的顺序。
  • 派生类对象初始化先调用基类构造再调派生类构造,析构清理先调用派生类析构再调基类的析构(父类总是先构造最后析构)。
  • 对于析构函数,编译器会统一处理成 destructor 函数,对于父类的析构函数,我们不能显示调用,因为父类先构造,根据栈区规则必须先析构子类,此时因为类析构被统一处理为destructor,所以父类析构函数被隐藏,析构时只执行了子类析构造成父类内存空间不能被正常释放,此时我们需要对父类的析构函数进行重写(将父类析构使用virtual修饰成为虚函数)以满足析构要求,在多态中我们会重点介绍。


继承中的友元与静态成员


友元

友元关系不能继承,也就是说基类友元不能访问子类私有和保护成员!


静态成员

基类定义了static静态成员,则整个继承体系里面只有一个这样的成员。无论派生出多少个子类,都只有一个static成员实例。
静态变量为于静态区,不同于普通的堆栈区,静态变量的声明周期很长,通常是程序运行结束后才会被销毁,因此 假设父类中存在一个静态变量,那么子类在继承后,可以共享此变量!

class Base
{
public:
	void Bfunc() { cout << "Base a=" << a << endl; }
	static int a;
};

class Derived : public Base
{
public:
	void Dfunc() { cout << "Derived a=" << a << endl; }
};

int Base::a = 0;
//int Derived::a = 1; //继承中的静态成员只能被指定一次初始化

int main()
{
	Base b;
	Derived d;
	b.Bfunc();
	Base::a = 1;
	d.Dfunc();
	Derived::a = 2;
	b.Bfunc();
	return 0;
}


如预期所料,无论我们是使用子类访问静态成员a还是父类访问静态成员a都访问的是同一个静态成员a!


菱形继承


C++中,继承是可以单继承也可以多继承,单继承就是一个子类只继承一个父类,多继承就是一个子类继承多个父类!

关于多继承,即支持一个子类继承多个父类,使其基础信息更为丰富,但凡事都有双面性,多继承 在带来巨大便捷性的同时,也带来了个巨大的坑,即菱形继承问题!

因为多继承的弊端,其他大部分面向对象的语言都禁止多继承!

关于多继承,只需要使用 , 将继承的多个父类连接起来即可

class 子类 : 继承方式 父类1, 继承方式 父类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; // 主修课程
};

int main()
{
	// 这样会有二义性无法明确知道访问的是哪一个
	Assistant a;
	a._name = "peter"; //此语句会报错

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

	return 0;
}


我们注释掉这句错误访问代码,编译运行进行调试发现:

a对象中出现了两个 _name 变量,如果我们直接访问,编译器会提示不明确,因为编译器不知道我们要访问哪一个 _name 。

此时我们仍然要坚持访问 _name变量 只能通过 :: 指定访问域进行访问:

这种方法解决了访问的问题,但是数据冗余和二义性的问题仍然没有被解决!

我们在实际使用中不需要两个 _name ,我们想 _name 是唯一的,此时我们就需要借助虚继承(与虚函数没有任何关系)来解决这个问题,换句话说,虚继承就是专门为解决菱形继承而产生的!

虚继承方式:

class 子类 : virtual 继承方式 父类 { /*子类成员*/ }

这就是对父类的虚继承!
需要注意的是,虚拟继承不要在其他地方去使用。


我们只需要在多继承的腰部,也就是父类在继承父父类时添加virtual即可!

class Person
{
public:
	string _name; // 姓名
};

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

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

虚继承
此时我们可以发现,我们可以通过a对象直接访问 _name ,且修改 _name 后 所有的_name成员都发生了变化,此时就解决了数据冗余和二义性的问题!


此时内存中的成员分布为:

为什么会是这样?接下来我们探究一下虚继承的原理!

虚继承原理

利用 虚基表 将冗余的数据存储起来,此时冗余的数据合并为一份,原来存储 冗余数据 的位置,现在用来存储 虚基表指针

为了研究虚拟继承原理,我们给出了一个简化的菱形继承继承体系,再借助内存窗口观察对象成
员的模型。

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

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

B和C类中都有各自的A类成员!

下图是菱形虚拟继承的内存对象成员模型:这里可以分析出D对象中将A放到的了对象组成的最下
面,这个A同时属于B和C,那么B和C如何去找到公共的A呢?这里是通过了B和C的两个指针,指
向的一张表。这两个指针叫虚基表指针,这两个表叫虚基表。虚基表中存的偏移量。通过偏移量
可以找到下面的A。


此时无论这个 冗余 的数据存储在何处,都能通过 基地址 + 偏移量 的方式进行访问!

补充:
当我们使用父类指针指向子类时,会发生父子类赋值兼容问题,也就是切片,切出父类的那一部分,从首地址开始,但我们访问公共成员时,仍然是通过 基地址+偏移量 实现的!

当然我们也可以实例化B对象,让ptrb指向b对象:

我们发现,当不存在继承关系时,只要我们对父类进行了虚继承,其父类对象的存储发生就是在 原数据位存放虚表地址来获取偏移量,再通过偏移量找到父类的成员!

之所以这么做,就是因为对象指针ptrb可能指向子类类型的对象,也可能指向本类类型对象,此时如果我们要访问父类成员 _a ,一条语句 ptrb->_a 对于指向D对象和指向B对象的地址不同场景需要有不同的处理方式,为了统一操作,只要是虚继承,其父类对象的都放在对象后面,采用偏移量的方式去访问,只要简化了处理过程!
当编译器识别到需要通过 基地址+偏移量 去访问时,会在汇编指令中进行特殊处理,而不是按照原方式直接去访问对象中的成员!

如果虚继承的父类中其成员是另一个对象存在多个成员,在访问时编译器先通过偏移量找到该成员对象的首地址,然后通过成员对象的声明顺序去依次访问,该场景例如:

class test
{
public:
	int a;
	int b;
	int c;
};
//此时被虚继承的父类A中有一个对象t !
class Base
{
public:
	test t;
};

class Derived : virtual public Base {};
//此时Derived实例的对象首地址仍然是存放Base对象中t对象所在偏移量
//访问时,通过int一个一个跳过即可(即使涉及内存对齐问题,编译器也会根据规则做出调整)

所以,无论最终位置在何处,最终汇编指令都一样(得益于偏移量的设计模式)!


总结:

  • 虚继承底层是如何解决菱形继承问题的?
    对于冗余的数据位,改存指针,该指针指向虚表,虚表中从首地址偏移4字节即为冗余的数据位偏移到数据地址的偏移量;对于冗余的成员,统一放置在后面,通过首地址和虚表中偏移量进行访问!
  • 为何在冗余处存指针?
    方便统一访问同一个变量,解决二义性和数据冗余问题;同时,偏移量存放在虚表首地址偏移4字节处(一般为第二个条目),而首地址的四个字节是将来存放多态的虚表地址的!
  • 虚基表指针 和 虚基表 是否会造成空间浪费?
    不会,指针大小固定为 4/8 字节;虚基表可以忽略不计,所有对象共享!

为了解决 菱形继承 问题,想出了 虚继承 这种绝妙设计,但在实际使用中,要尽量避免出现 菱形继承 问题!



说明:在多继承中,关于多个父类谁先初始化与其声明顺序有关,例如在上面的D类:

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

此时D类实例化会先初始化B再初始化C,最后初始化自己,所以初始化的顺序与继承时声明父类的顺序有关!


继承和组合


除了可以通过继承使用父类中的成员外,还可以通过 组合 的方式进行使用,前提是父类对象中的成员是public权限允许在类外访问或可以通过函数进行访问

关于继承和组合:

  • 公有继承:is-a —> 高耦合,可以直接使用父类成员
  • 组合:has-a —> 低耦合,可以间接使用父类成员
    实际项目中,更推荐使用 组合 的方式,这样可以做到 解耦,避免因父类的改动而直接影响到子类,不过,具体使用哪种方式还要取决于具体场景,具体问题具体分析!
//父类
class A {};

//继承-直接使用
class B : public A {};

//组合
class C
{
private:
   A _oa;  //创建 _oa 对象,使用成员及方法
}

继承的作用主要是为多态做准备,继承是多态不可或缺的一步!


最后

以上就是关于C++继承的内容,本节我们介绍了面向对象三大特性之一的继承,介绍了什么是继承,怎么用,有那些问题等等;最后介绍了C++多继承中的问题菱形继承,使用虚继承解决了这个问题,虚继承的原理等,这些知识将为后面的多态进行铺垫,大家在学习后一定要动手实践,多多琢磨!

本次 <C++ 继承> 就先介绍到这里啦,希望能够尽可能帮助到大家。

如果文章中有瑕疵,还请各位大佬细心点评和留言,我将立即修补错误,谢谢!
C-PLUS-PLUS

🌟其他文章阅读推荐🌟
C++ <STL容器适配器> -CSDN博客
C++ <STL之list模拟实现> -CSDN博客
C++ <STL之list使用> -CSDN博客
🌹欢迎读者多多浏览多多支持!🌹

​​

​​


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

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

相关文章

C++|前言

c|前言 一、什么是C二、C发展史三、C的重要性3.1语言的使用广泛度3.2工作领域3.3校招领域 四、如何学习C4.1别人怎么学4.2自己怎么学 一、什么是C 在上回书已经学习了C语言&#xff0c;我们知道C语言是面向过程语言&#xff0c;C语言是结构化和模块化的语言&#xff0c;适合处理…

适合汽车音频系统的ADAU1977WBCPZ、ADAU1978WBCPZ、ADAU1979WBCPZ四通道 ADC,24-bit,音频

一、ADAU1977WBCPZ 集成诊断功能的四通道ADC&#xff0c;音频 24 b 192k IC&#xff0c;SPI 40LFCSP ADAU1977集成4个高性能模数转换器(ADC)&#xff0c;其直接耦合输入具有10 V rms性能。该ADC采用多位Σ-Δ架构&#xff0c;其连续时间前端能够实现低EMI性能。它可以直接连接…

vue3 开启 https

1、安装mkcert证书创建器 npm i mkcert -g 2、检验是否安装成功 mkcert --version 有版本好出现则成功 3、创建证书颁发机构 mkcert create-ca 会在当前目录生成&#xff0c;ca.crt 和 ca.key 两个文件 4、创建证书 mkcert create-cert 会在当前目录生成&#xff0c;…

ElementUI之el-progress动态修改进度条里面文本颜色与进度条色块统一

1.效果&#xff1a; 2.实现方式 通过行内style样式动态给整个progress赋颜色 再在样式里给进度条文字单独设置颜色为默认继承父级颜色就ok啦 <el-progress class"custom-progress" stroke-linecap"square" :style"{color:item.color}" :colo…

BAM(Bottleneck Attention Module)

BAM&#xff08;Bottleneck Attention Module&#xff09;是一种用于计算机视觉领域的深度学习模型结构&#xff0c;它旨在提高神经网络对图像的特征提取和感受野处理能力。BAM模块引入了通道注意力机制&#xff0c;能够自适应地加强或减弱不同通道的特征响应&#xff0c;从而提…

【斗破年番】彩鳞默认火火碰自己香肩,提其他女人,女王表示妒忌

Hello,小伙伴们&#xff0c;我是小郑继续为大家深度解析国漫资讯。 深度爆料&#xff0c;《斗破苍穹》年番国漫第69话最新剧情解析&#xff0c;主角萧炎来到了蛇人族的新部落。美杜莎是蛇人族的女王&#xff0c;她的威严和力量使她在族中拥有极高的地位。 在部落中&#xff0c…

通过创建自定义标签来扩展HTML

使用HTML时&#xff0c;例如&#xff0c;使用<b>标记显示粗体文本。 如果需要列表&#xff0c;则对每个列表项使用<ul>标记及其子标记<li> 。 标签由浏览器解释&#xff0c;并与CSS一起确定网页内容的显示方式以及部分内容的行为。 有时&#xff0c;仅使用一…

集合框架:Set集合的特点、HashSet集合的底层原理、哈希表、实现去重复

Set集合的特点 Set&#xff08;集合&#xff09;是一种无序的、不重复的数据结构&#xff0c;它的特点如下&#xff1a; 1. 集合中的元素是无序的&#xff1a;Set 中的元素没有顺序&#xff0c;无法通过索引来访问。 2. 集合中的元素是唯一的&#xff1a;Set 中不允许有重复…

SQL必知会(二)-SQL查询篇(1)-检索数据

第2课、检索数据 SELECT&#xff1a;查询 从一个或多个表中检索信息。 1&#xff09;检索单个列 需求&#xff1a;从 Products 表中查询所有行的产品名称。 SElECT prod_name FROM Products;输出结果&#xff1a; 2&#xff09;检索多个列 需求&#xff1a;从 Products 表…

Msa类处理多序列比对数据

同源搜索&#xff0c;多序列比对等都是常用的方式&#xff0c;但是有很多的软件可以实现这些同源搜索和多序列比对&#xff0c;但是不同的软件输出的文件格式却是不完全一致&#xff0c;有熟悉的FASTA格式的&#xff0c;也有A2M, A3M,stockholm等格式。 详细介绍&#xff1a; …

算法进阶指南图论 通信线路

通信线路 思路&#xff1a;我们考虑需要升级的那条电缆的花费&#xff0c;若其花费为 w &#xff0c;那么从 1 到 n 的路径上&#xff0c;至多存在 k 条路径的价值大于 w &#xff0c;这具有一定的单调性&#xff0c;当花费 w 越大&#xff0c;我们路径上价值大于 w 的花费会越…

Spring笔记(一)(黑马)(Ioc基础容器)

01、传统Javaweb开发的困惑 1.1 传统Javaweb开发困惑及解决方案 &#x1f616;问题一&#xff1a;层与层之间紧密耦合在了一起&#xff0c;接口与具体实现紧密耦合在了一起 解决思路&#xff1a;程序代码中不要手动new对象&#xff0c;第三方根据要求为程序提供需要的Bean对象…

SQL Server SSIS的安装

标题SQL SERVER 安装 下载SQL SERVER数据库&#xff1a;&#xff08;以SQL SERVER 2022 Developer版本&#xff09;(https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads?rtc1) 以administrator权限安装&#xff1a; 下载完成后&#xff0c;会出现以下界面&a…

docker容器中运行jar 出现invalid or corrupt jarfile

1&#xff0c;背景&#xff1a; 在本地java开发完毕之后&#xff0c;想要打包成docker镜像&#xff0c;方便安装。由于本地没有docker环境&#xff0c;也懒得装了。有一台测试的linux机器可以使用&#xff0c;所以先在本地打包生成xxx.jar&#xff0c;然后拷贝到有docker环境的…

BIM、建筑机器人、隧道工程施工关键技术

一、BIM简介 &#xff08;一&#xff09;BIM概念 BIM&#xff08;Building Information Modeling&#xff09;&#xff0c;建筑信息模型。该技术通过数字化手段&#xff0c;在计算机中建立虚拟建筑&#xff0c;该虚拟建筑提供从单一到完整、包含逻辑关系的建筑信息库。信息库…

11.把学生的信息 (学号,姓名,性别,住址) 放入结构体[???]

#include<stdio.h>struct stu { long int num; //学号 char name[3]; //姓名 char sex; //性别 char add[4]; //地址 }a;int main(){scanf("%ld,%s,%c,%s\n",&a.num,a.name,&a.sex,a.add);printf("%ld,%s,%c,%s\n",a.num,a.name,a.…

Java对象的拷贝与克隆

Java对象的拷贝与克隆 在日常开发中&#xff0c;我们经常需要给对象进行赋值&#xff0c;通常会调用其 set/get 方法&#xff0c;有些时候&#xff0c;为了简化代码&#xff0c;我们会采用第三方工具类进行属性拷贝。但是面对如此多的拷贝工具和方法&#xff0c;其性能差异如何…

yo!这里是STL::unordered系列简单模拟实现

目录 前言 相关概念介绍 哈希概念 哈希冲突与哈希函数 闭散列 框架 核心函数 开散列 框架 核心函数 哈希表&#xff08;开散列&#xff09;的修改 迭代器实现 细节修改 unordered系列封装 后记 前言 我们之前了解过map和set知道&#xff0c;map、set的底层结构是…

亚信科技斩获“鼎新杯”多项大奖!AntDB数据库在信创赛道再创佳绩

近日&#xff0c;第二届“鼎新杯”数字化转型应用大赛全国总决赛在北京落下帷幕&#xff0c;亚信科技成功收获一等奖1项、二等奖1项、三等奖3项、行业标杆奖1项。 “两江协同创新区智慧园区项目&#xff08;二期&#xff09;”斩获两项殊荣 在“行业数字化融合方向-智慧园区”…

Java并发工具-4-并发框架(ExecutorForkJoin)

一 Executor 并发框架介绍 1 整体结构介绍 executor [ɪɡˈzekjətə(r)] 执行者 execute [ˈeksɪkjuːt] 执行 从 JDK 1.5 开始&#xff0c;java 中将工作单元和执行机制做了分离&#xff0c;于是 Executor 并行框架出现。 什么是工作单元&#xff08;或称为任务&#xff…