【C++高阶(一)】继承

目录

一、继承的概念

1.继承的基本概念

2.继承的定义和语法

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

​编辑 4.总结

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

三、继承中的作用域

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

1.派生类中的默认构造函数

2.派生类中的拷贝构造函数

3.派生类中的移动构造函数

4.派生类的拷贝赋值运算符

 5.派生类的移动赋值运算符

6.派生类的析构函数

为什么基类析构函数需要virtual关键字修饰?

理由:多态性和正确的析构顺序

问题:非虚析构函数导致的资源泄漏

总结

五、继承和友元

六、继承与静态成员

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

1.单继承

2.多继承

3.菱形继承

 菱形继承的问题

4.菱形虚拟继承

5.虚拟继承解决数据冗余和二义性的原理

虚基表的工作机制


一、继承的概念

在C++中,继承是一种面向对象编程的重要特性,它允许一个类(称为派生类或子类)从另一个类(称为基类或父类)继承属性和行为(成员变量和成员函数)。通过继承,派生类不仅可以拥有基类的所有成员,还可以扩展或修改这些成员以提供更具体或特殊的功能。

1.继承的基本概念

  1. 基类(Base Class):提供基础属性和行为的类。
  2. 派生类(Derived Class):从基类继承并扩展或修改其功能的类。
  3. 访问控制(Access Control)
    • Public 继承:基类的public和protected成员在派生类中保持其访问级别不变,public成员依然是public,protected成员依然是protected。
    • Protected 继承:基类的public和protected成员在派生类中都变为protected成员。
    • Private 继承:基类的public和protected成员在派生类中都变为private成员。
  4. 构造函数和析构函数:派生类的构造函数在执行前会先调用基类的构造函数,析构函数的调用顺序则相反,先调用派生类的析构函数,再调用基类的析构函数。
  5. 多重继承(Multiple Inheritance):C++允许一个派生类从多个基类继承。

2.继承的定义和语法

class Base {
public:int baseValue;void baseFunction() {// 基类成员函数}
};class Derived : public Base {
public:int derivedValue;void derivedFunction() {// 派生类成员函数}
};

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

 4.总结

  1. 基类private成员在派生类中无论以什么方式继承都是不可见的。这里的不可见是指基类的私有成员还是被继承到了派生类对象中,但是语法上限制派生类对象不管是在类内还是在类外都不能去访问它。
  2. 基类private成员在派生类中是不能被访问的,如果基类成员不想在类外直接被访问,但需要在派生类中能访问,就定义为protected。可以看出保护成员限定符是继承才出现的。
  3. 实际上面的表格我们进行一下总结就能发现,基类的私有成员在子类中都是不可见的。基类的其他成员在子类的访问方式 == Min(成员在基类的访问限定符,继承方式),public > protected > private。
  4. 使用关键字class时默认的继承方式是private,使用struct时默认的继承方式是public,不过最好显示的写出继承方式。
  5. 在实际运用中一般都是使用public继承,几乎很少使用protected/private继承,也不提倡使用protected/private继承,因为 protected/private继承下来的成员都只能在派生类的类里使用,实际中扩展维护性不强。

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

  • 派生类对象 可以赋值给 基类的对象 / 基类的指针 / 基类的引用。这里有个形象的说法叫切片或者切割。寓意把派生类中父类那部分切来赋值过去。
  • 基类对象不能赋值给派生类对象。
  • 基类的指针或者引用可以通过强制类型转换赋值给派生类的指针或者引用。但是必须是基类的指针是指向派生类对象时才是安全的。

class Base {
public:int baseValue;virtual void display() {std::cout << "Base class" << std::endl;}
};class Derived : public Base {
public:int derivedValue;void display() override {std::cout << "Derived class" << std::endl;}
};Base baseObj;
Derived derivedObj;baseObj = derivedObj;  // 对象切割发生
baseObj.display();     // 输出 "Base class"

在上面的例子中,尽管derivedObj赋值给了baseObj,但baseObj只保留了Base类的部分,派生类的derivedValue被切割掉了,调用display函数时也只会调用基类的版本

此外,C++允许使用基类的指针或引用来指向派生类对象,这可以实现多态性。多态性允许你通过基类接口调用派生类的重载函数。

Base* basePtr = &derivedObj;
basePtr->display();  // 输出 "Derived class"(多态性)Base& baseRef = derivedObj;
baseRef.display();  // 输出 "Derived class"(多态性)

 在上面代码中,basePtr和baseRef都指向Derived对象,并且调用display方法时,会调用派生类Derived中的版本,这是因为display函数被声明为virtual。(virtual关键字我们下面会讲)

另外还有类型转换:static_cast和dynamic_cast,感兴趣的可以去了解下。

三、继承中的作用域

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

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

在之前的学习中, 我们知道类可以自动生成一些默认的成员函数,这些成员函数包括默认构造函数、拷贝构造函数、移动构造函数、拷贝赋值运算符、移动赋值运算符和析构函数。而对于派生类,这些默认成员函数的生成和行为有一些特殊的规则和注意事项,下面我讲详细介绍。

1.派生类中的默认构造函数

默认构造函数在没有用户定义的构造函数时自动生成。对于派生类的默认构造函数,它会调用基类的默认构造函数(如果存在),然后初始化派生类的成员。而如果基类没有默认构造函数,则必须在派生类构造函数的初始化列表阶段显示调用。

class Base {
public:Base() {std::cout << "Base default constructor" << std::endl;}
};class Derived : public Base {
public:Derived() {std::cout << "Derived default constructor" << std::endl;}
};int main() {Derived d;  // 输出:Base default constructor//      Derived default constructorreturn 0;
}

2.派生类中的拷贝构造函数

拷贝构造函数在没有用户定义的情况下自动生成,用于创建类的对象副本。派生类的拷贝构造函数会首先调用基类的拷贝构造函数,然后复制派生类的成员。

class Base {
public:Base(const Base&) {std::cout << "Base copy constructor" << std::endl;}
};class Derived : public Base {
public:Derived(const Derived& other) : Base(other) {std::cout << "Derived copy constructor" << std::endl;}
};int main() {Derived d1;Derived d2 = d1;  // 输出:Base copy constructor//      Derived copy constructorreturn 0;
}

3.派生类中的移动构造函数

移动构造函数在没有用户定义的情况下自动生成,用于移动资源所有权。派生类的移动构造函数会首先调用基类的移动构造函数,然后移动派生类的成员。

class Base {
public:Base(Base&&) noexcept {std::cout << "Base move constructor" << std::endl;}
};class Derived : public Base {
public:Derived(Derived&& other) noexcept : Base(std::move(other)) {std::cout << "Derived move constructor" << std::endl;}
};int main() {Derived d1;Derived d2 = std::move(d1);  // 输出:Base move constructor//      Derived move constructorreturn 0;
}

4.派生类的拷贝赋值运算符

拷贝赋值运算符在没有用户定义的情况下自动生成,用于将一个对象的内容赋值给另一个对象。派生类的拷贝赋值运算符会首先调用基类的拷贝赋值运算符,然后赋值派生类的成员。

class Base {
public:Base& operator=(const Base&) {std::cout << "Base copy assignment operator" << std::endl;return *this;}
};class Derived : public Base {
public:Derived& operator=(const Derived& other) {Base::operator=(other);std::cout << "Derived copy assignment operator" << std::endl;return *this;}
};int main() {Derived d1, d2;d1 = d2;  // 输出:Base copy assignment operator//      Derived copy assignment operatorreturn 0;
}

 5.派生类的移动赋值运算符

移动赋值运算符在没有用户定义的情况下自动生成,用于将一个对象的内容移动到另一个对象。派生类的移动赋值运算符会首先调用基类的移动赋值运算符,然后移动派生类的成员。

class Base {
public:Base& operator=(Base&&) noexcept {std::cout << "Base move assignment operator" << std::endl;return *this;}
};class Derived : public Base {
public:Derived& operator=(Derived&& other) noexcept {Base::operator=(std::move(other));std::cout << "Derived move assignment operator" << std::endl;return *this;}
};int main() {Derived d1, d2;d1 = std::move(d2);  // 输出:Base move assignment operator//      Derived move assignment operatorreturn 0;
}

6.派生类的析构函数

析构函数在没有用户定义的情况下自动生成,用于清理对象派生类的析构函数会首先调用派生类的析构函数,然后调用基类的析构函数。

class Base {
public:virtual ~Base() {std::cout << "Base destructor" << std::endl;}
};class Derived : public Base {
public:~Derived() {std::cout << "Derived destructor" << std::endl;}
};int main() {Base* b = new Derived();delete b;  // 输出:Derived destructor//      Base destructorreturn 0;
}

为什么基类析构函数需要virtual关键字修饰?

基类的析构函数需要加virtual关键字是为了确保在删除派生类对象时能够正确调用析构函数。这是一个非常重要的概念,尤其是在使用多态性和通过基类指针或引用操作派生类对象时。

理由:多态性和正确的析构顺序

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

当基类的析构函数是虚函数时,通过基类指针删除派生类对象时,C++会首先调用派生类的析构函数,然后再调用基类的析构函数。这确保了派生类中分配的资源可以先被正确释放,再释放基类中分配的资源。

int main() {Base* b = new Derived();delete b;  // 输出顺序:Derived destructor//           Base destructorreturn 0;
}

问题:非虚析构函数导致的资源泄漏

如果基类的析构函数不是虚函数,则通过基类指针删除派生类对象时,只会调用基类的析构函数,而不会调用派生类的析构函数。这会导致派生类中的资源没有被正确释放,造成资源泄漏。

class Base {
public:~Base() {std::cout << "Base destructor" << std::endl;}
};class Derived : public Base {
public:~Derived() {std::cout << "Derived destructor" << std::endl;}
};int main() {Base* b = new Derived();delete b;  // 只输出:Base destructorreturn 0;
}

在上述代码中,由于 Base 类的析构函数不是虚函数,删除 b 时只调用了 Base 的析构函数,Derived 类的析构函数没有被调用,这会导致 Derived 类中的资源没有被正确释放。

总结

为了确保在使用多态性时派生类对象可以被正确地销毁,避免资源泄漏,基类的析构函数应该声明为虚函数。这一做法可以保证删除派生类对象时,派生类和基类的析构函数都能被正确调用。以下是总结的要点:

  1. 多态性支持:使用基类指针或引用操作派生类对象时,确保正确调用派生类的析构函数。
  2. 正确的析构顺序:先调用派生类的析构函数,再调用基类的析构函数,确保资源正确释放。
  3. 避免资源泄漏:防止派生类中的资源没有被释放,导致内存泄漏或其他资源泄漏。

五、继承和友元

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

#include <iostream>// 基类
class Base {
private:int basePrivateVar;
protected:int baseProtectedVar;
public:int basePublicVar;Base() : basePrivateVar(1), baseProtectedVar(2), basePublicVar(3) {}friend void baseFriendFunction(Base &obj);
};// 基类的友元函数
void baseFriendFunction(Base &obj) {std::cout << "Base Private Var: " << obj.basePrivateVar << std::endl;std::cout << "Base Protected Var: " << obj.baseProtectedVar << std::endl;
}// 派生类
class Derived : public Base {
private:int derivedPrivateVar;
protected:int derivedProtectedVar;
public:int derivedPublicVar;Derived() : derivedPrivateVar(4), derivedProtectedVar(5), derivedPublicVar(6) {}friend void derivedFriendFunction(Derived &obj);
};// 派生类的友元函数
void derivedFriendFunction(Derived &obj) {// 基类的友元不能访问派生类的私有或保护成员// std::cout << "Derived Private Var: " << obj.derivedPrivateVar << std::endl; // 错误// std::cout << "Derived Protected Var: " << obj.derivedProtectedVar << std::endl; // 错误std::cout << "Derived Public Var: " << obj.derivedPublicVar << std::endl;
}int main() {Base baseObj;Derived derivedObj;baseFriendFunction(baseObj); // 可以访问Base类的私有和保护成员derivedFriendFunction(derivedObj); // 可以访问Derived类的公共成员// 基类的友元函数不能访问派生类的私有和保护成员// baseFriendFunction(derivedObj); // 错误return 0;
}

六、继承与静态成员

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

静态成员变量需要在类外进行定义和初始化。静态成员函数则不需要在类外定义。

#include <iostream>// 基类
class Base {
public:static int staticVar;  // 声明静态成员变量static void staticFunction() {  // 声明并定义静态成员函数std::cout << "Static Function in Base" << std::endl;}
};// 定义静态成员变量
int Base::staticVar = 10;// 派生类
class Derived : public Base {
public:void display() {std::cout << "Base staticVar: " << staticVar << std::endl;  // 访问基类的静态成员变量staticFunction();  // 调用基类的静态成员函数}
};int main() {Derived obj;obj.display();// 静态成员可以通过类名直接访问Base::staticVar = 20;Derived::staticVar = 30;std::cout << "Base staticVar after modification: " << Base::staticVar << std::endl;std::cout << "Derived staticVar after modification: " << Derived::staticVar << std::endl;return 0;
}

静态成员的特点

  1. 类共享性:所有类的对象共享同一个静态成员变量。
  2. 类作用域:静态成员变量和静态成员函数在类作用域内,但可以通过类名直接访问。
  3. 内存管理:静态成员变量在程序启动时分配内存,程序结束时释放内存。

注意:

  • 静态成员函数:静态成员函数不能访问非静态成员变量和非静态成员函数,因为它们属于类本身,而不是类的某个对象。但是静态成员函数可以访问静态成员变量和其他静态成员函数。

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

1.单继承

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

2.多继承

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

3.菱形继承

菱形继承(也称钻石继承)是指一种特殊的多继承情况,其中一个类从两个基类继承,而这两个基类又继承自同一个祖先类。这种继承关系形成了一个菱形结构。

 菱形继承的问题

 从上面的对象成员模型构造,可以看出菱形继承有数据冗余和二义性的问题。在Assistant的对象中Person成员会有两份。

#include <iostream>// 祖先类
class A {
public:int value;A() : value(0) {}
};// 两个派生类继承自 A
class B : public A {};
class C : public A {};// 派生类 D 同时继承自 B 和 C
class D : public B, public C {};int main() {D obj;// obj.value; // 错误:二义性问题,不知道是从 B 继承的 A 还是从 C 继承的 A// 解决方法之一是明确指定路径obj.B::value = 1;obj.C::value = 2;std::cout << "obj.B::value: " << obj.B::value << std::endl;std::cout << "obj.C::value: " << obj.C::value << std::endl;return 0;
}

4.菱形虚拟继承

为了解决上面菱形继承所带来的问题,我们可以使用虚拟继承。虚拟继承确保在菱形继承结构中只存在一个基类的实例。

如在上面的代码中,我们可以在B和C继承A的时候使用虚拟继承,即

class B : virtual public A {};
class C : virtual public A {};

需要注意的是,虚拟继承不要在其他地方去使用。

5.虚拟继承解决数据冗余和二义性的原理

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

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

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

 

这里可以分析出D对象中将A放到了D对象组成的最下面,这个A同时属于B和C,那么B和C如何去找到公共的A呢?

这里是通过了B和C的两个指针,指向的一张表。这两个指针叫虚基表指针,这两个表叫虚基表。虚基表中存储的是偏移量,通过偏移量就能找到A 。

总结:

虚拟继承通过确保每个虚拟基类在派生类中只有一个共享实例,从而避免了重复实例化和二义性问题。为了实现这一点,编译器会使用虚基表来跟踪和管理虚拟基类的实例。

虚基表的工作机制

  1. 虚基表的引入: 每个使用虚拟继承的类会包含一个虚基表指针。这个指针指向一个虚基表,该表包含虚拟基类的指针。

  2. 共享基类实例: 在派生类(如 D)的对象中,虚基表指针确保所有虚拟基类实例都指向同一个实际基类实例。这意味着 D 中只有一个 A 类的实例。

  3. 成员访问的重定向: 在访问基类成员时,编译器使用虚基表来正确地定位基类成员,确保访问的是唯一的基类实例。

所以,当一个类虚拟继承另一个类时,编译器在对象布局中插入一个虚基表指针(vbptr)。这个指针指向一个虚基表(vbtbl),而虚基表中包含指向虚拟基类的偏移量或地址。通过这种方式,每个派生类能够正确地定位并访问唯一的虚拟基类实例。


上面就是我们对C++继承的全部理解了~

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

[国产大模型简单使用介绍] 开源与免费API

个人博客:Sekyoro的博客小屋 个人网站:Proanimer的个人网站 随着大模型技术蓬勃发展和开源社区越来越活跃,国内的大模型也如雨后春笋一般.这时,一些就会问了,有了llama3,Mistral还有Gemma等等,国外大厂接连发力,一些开源社区也会有一些不错的模型,国内怎么比?对一个人使用,oll…

02.爬虫---HTTP基本原理

02.HTTP基本原理 1.URI 和 URL 的区别2.HTTP 和 HTTPS 的区别3.请求过程 1.URI 和 URL 的区别 URL&#xff08;Uniform Resource Locator&#xff09;即-统一资源定位符 URL是用来定位和访问互联网上资源的独特标识&#xff0c;它包括了资源的位置&#xff08;如IP地址或域名&a…

node.js —— 解读http模块

目录 http模块&#xff1a; http模块的引入&#xff1a; 创建web服务器的基本步骤&#xff1a; web服务器的一些基本属性&#xff1a; 上述知识汇总案例&#xff1a; http模块&#xff1a; http模块的引入&#xff1a; const http require (http) 创建web服务器的基本步骤…

记录docker ps查找指定容器的几个命令

1.docker ps | grep registry 查询包含registry的容器 2.docker ps | grep -E "reigistry\s" 开启正则匹配模式&#xff0c;匹配registry后面为空格的容器&#xff0c;若是匹配一整行可以这样写docker ps | grep -E "^([0-9a-f]{12})\sregistry\s.*" 这…

第八节 条件装配案例讲解

一、条件装配的作用是什么 条件装配是 Spring 框架中一个强大的特性&#xff0c;使得开发者能够创建更加灵活和可维护的应用程序。在 Spring Boot 中&#xff0c;这个特性被大量用于自动配置&#xff0c;极大地简化了基于 Spring 的应用开发。 二、条件装配注解 <dependen…

Android-自定义三角形评分控件

效果图 序言 在移动应用开发中&#xff0c;显示数据的方式多种多样&#xff0c;直观的图形展示常常能带给用户更好的体验。本文将介绍如何使用Flutter创建一个自定义三角形纬度评分控件&#xff0c;该控件可以通过动画展示评分的变化&#xff0c;让应用界面更加生动。 实现思…

Vue3实战easypan(六):回收站+设置

一、回收站 src/views/recycle/Recycle.vue <template><!-- 上方两个按钮 --><div class"top"><el-button type"success" :disabled"selectFileIdList.length 0" click"revertBatch"><span class"ic…

[保姆式教程]使用目标检测模型YOLO V8 OBB进行旋转目标的检测:训练自己的数据集(基于卫星和无人机的农业大棚数据集)

最近需要做基于卫星和无人机的农业大棚的旋转目标检测&#xff0c;基于YOLO V8 OBB的原因是因为尝试的第二个模型就是YOLO V8&#xff0c;后面会基于YOLO V9模型做农业大棚的旋转目标检测。YOLO V9目前还不能进行旋转目标的检测&#xff0c;需要修改代码 PS:欢迎大家分享农业大…

Plotly库利用滑块创建数据可视化

使用了Plotly库来创建一个数据可视化图表&#xff0c;并使用滑块来控制显示哪些数据 import plotly.graph_objects as go from plotly.subplots import make_subplots# 示例数据 x [1, 2, 3, 4, 5] y1 [1, 2, 3, 4, 5] y2 [5, 4, 3, 2, 1] y3 [2, 3, 1, 5, 4]# 创建子图 f…

12306技术内幕

公司内部做的一次技术分享 文章目录 12306的成就12306系统特点12306系统难点解决思路产品角度技术角度余票库存的表如何设计&#xff1f; 抢票软件推荐巨人的肩膀 对于未公开的技术部分&#xff0c;只能结合已公开的信息&#xff0c;去做大胆的猜想。 本文提到的一些解决方案&…

【车载开发系列】Autosar中的VFB

【车载开发系列】Autosar中的VFB # 【车载开发系列】Autosar中的VFB 【车载开发系列】Autosar中的VFB一. 什么是VFB二. VFB的优点与缺点1&#xff09;VFB的缺点2&#xff09;VFB的好处 三. RTE与VFB之间关系四. 总线架构模式 一. 什么是VFB Virtual Functional Bus。它就是虚拟…

Python函数、类和方法

大家好&#xff0c;当涉及到编写可维护、可扩展且易于测试的代码时&#xff0c;Python提供了一些强大的工具和概念&#xff0c;其中包括函数、类和方法。这些是Python编程中的核心要素&#xff0c;可以帮助我们构建高效的测试框架和可靠的测试用例。 本文将探讨Python中的函数、…

Vue3实战笔记(43)—Vue3组合式API下封装可复用ECharts图表组件

文章目录 前言一、封装echart图标钩子二、使用步骤总结 前言 接上文&#xff0c;已经安装好了ECharts&#xff0c;开始封装组件方便使用。 一、封装echart图标钩子 首先应用我们之前学习的钩子方式&#xff0c;在hooks目录下创建一个名为 useECharts.js 的文件&#xff0c;用…

从零起航,Python编程全攻略

新书上架~&#x1f447;全国包邮奥~ python实用小工具开发教程http://pythontoolsteach.com/3 欢迎关注我&#x1f446;&#xff0c;收藏下次不迷路┗|&#xff40;O′|┛ 嗷~~ 目录 一、Python入门之旅 二、Python进阶之道 三、Python爬虫实战 四、Python数据分析利器 五…

linux系统——终止进程命令

linux进程&#xff0c;有所谓进程树的概念&#xff0c;在此之上&#xff0c;有父进程与子进程 pgrep进程名可以查看进程信息 同时&#xff0c;此命令也可以使用参数进行调节 关于kill有一系列命令参数 echo $?可以输出上次命令执行的情况

【Spring Boot】深度复盘在开发搜索引擎项目中重难点的整理,以及遇到的困难和总结

&#x1f493; 博客主页&#xff1a;从零开始的-CodeNinja之路 ⏩ 收录文章&#xff1a;【Spring Boot】深度复盘在开发搜索引擎项目中重难点的整理&#xff0c;以及遇到的困难和总结 &#x1f389;欢迎大家点赞&#x1f44d;评论&#x1f4dd;收藏⭐文章 目录 什么是搜索引…

Ajax异步删除

在页面上定义一个按钮 <button type"button" class"btn"><a href"JavaScript:;" class"id" b_id"{{$attachment[id]}}">删除</a></button> js代码 <script>$(.id).click(function (){va…

[读论文]精读Self-Attentive Sequential Recommendation

论文链接&#xff1a;https://arxiv.org/abs/1808.09781 其他解读文章&#xff1a;https://mp.weixin.qq.com/s/cRQi3FBi9OMdO7imK2Y4Ew 摘要 顺序动态是许多现代推荐系统的一个关键特征&#xff0c;这些系统试图根据用户最近执行的操作来捕获用户活动的“上下文”。为了捕捉…

ES基础概念

本文不介绍如何使用ES&#xff08;使用ES见&#xff1a;&#xff09; 1.ES生态圈 ES&#xff1a; Logstash&#xff1a;数据处理服务程序&#xff0c;解析转换加工数据&#xff1b; Kibana&#xff1a;数据展示、集群管理&#xff0c;数据可视化、ES管理与监控、报表等&#xf…

区块链钱包如果丢失了私钥或助记词,资产还能恢复吗?

如果你丢失了区块链钱包的私钥或助记词&#xff08;通常是用于恢复钱包的短语或种子&#xff09;&#xff0c;那么你的资产在大多数情况下是无法恢复的。私钥是访问和控制你在区块链上资产的唯一凭证&#xff0c;而助记词&#xff08;如BIP39标准中的12、18、24个单词的短语&am…