C++进阶之继承

在这里插入图片描述

继承

  • 继承的概念及定义
  • 基类和派生类对象赋值转换
  • 继承中的作用域
  • 派生类的默认成员函数
  • 继承与友元
  • 继承与静态成员
  • 复杂的菱形继承及菱形虚拟继承
  • 继承的总结和反思

继承的概念及定义

在C++中,继承是一种面向对象编程的重要概念,它允许一个类(称为子类或派生类)从另一个类(称为父类、基类或超类)那里继承属性和行为。继承是实现代码重用、构建层次结构以及实现多态性的基础。

在C++中,继承通过以下方式定义:

class BaseClass {// 基类的成员和方法
};class DerivedClass : public BaseClass {// 派生类的成员和方法
};

在上面的代码中,DerivedClass 继承了 BaseClass。继承关系通过 publicprotectedprivate 关键字来定义。这些关键字决定了基类成员在派生类中的可访问性:

  1. public 继承:基类的 public 成员在派生类中保持为 public,基类的 protected 成员在派生类中保持为 protected,基类的 private 成员在派生类中不可访问。
  2. protected 继承:基类的 publicprotected 成员在派生类中保持为 protected,基类的 private 成员在派生类中不可访问。
  3. private 继承:基类的 publicprotected 成员在派生类中变为 private,基类的 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继承下来的成员都只能在派生类的类里面使用,实际中扩展维护性不强

通过继承,派生类获得了基类的属性和方法。派生类可以扩展基类的功能,添加新的成员和方法,也可以覆盖基类的虚函数以实现多态性。

在这里插入图片描述

以下是一个简单的示例

class Person
{
public:void Print(){cout << "name:" << _name << endl;cout << "age:" << _age << endl;}
protected:string _name = "peter"; // 姓名int _age = 18; // 年龄
};
class Student : public Person
{protected:int _stuid; // 学号
};
class Teacher : public Person
{protected:int _jobid; // 工号
};
int main()
{Student s;Teacher t;s.Print();t.Print();return 0;
}

继承后父类的Person的成员(成员函数+成员变量)都会变成子类的一部分。这里体现出了StudentTeacher复用了Person的成员。下面我们使用监视窗口查看StudentTeacher对象,可以看到变量的复用。调用Print可以看到成员函数的复用。

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

  1. 派生类对象 可以赋值给 基类的对象 / 基类的指针 / 基类的引用。这里有个形象的说法叫切片或者切割。寓意把派生类中父类那部分切来赋值过去。
  2. 基类对象不能赋值给派生类对象。
  3. 基类的指针或者引用可以通过强制类型转换赋值给派生类的指针或者引用。但是必须是基类的指针是指向派生类对象时才是安全的。这里基类如果是多态类型,可以使用RTTI(RunTime Type Information)dynamic_cast 来进行识别后进行安全转换。
class Person
{
protected :string _name; // 姓名string _sex; // 性别int _age; // 年龄
};
class Student : public Person
{
public :int _No ; // 学号
};
void Test ()
{Student sobj ;// 1.子类对象可以赋值给父类对象/指针/引用Person pobj = sobj ;Person* pp = &sobj;Person& rp = sobj;//2.基类对象不能赋值给派生类对象sobj = pobj;// 3.基类的指针可以通过强制类型转换赋值给派生类的指针pp = &sobjStudent* ps1 = (Student*)pp; // 这种情况转换时可以的。ps1->_No = 10;pp = &pobj;Student* ps2 = (Student*)pp; // 这种情况转换时虽然可以,但是会存在越界访问的问题ps2->_No = 10;
}

在这里插入图片描述

在进行基类和派生类对象之间的赋值转换时,需要注意以下几个重要的问题:

  1. 切片问题: 将派生类对象赋值给基类对象时会发生切片,派生类特有的成员和属性会丢失。要确保赋值的对象类型是正确的,以避免意外的数据丢失。
  2. 虚函数和多态性: 如果在基类和派生类中使用了虚函数,并且派生类对这些虚函数进行了重写,赋值给基类对象或者通过基类指针/引用访问时,应该确保多态性仍然起作用,派生类的重写函数会被调用。
  3. 动态类型转换: 如果需要将基类指针转换为派生类指针,可以使用 dynamic_cast 进行类型转换。这会在运行时检查类型转换的合法性,并在转换失败时返回空指针。但是,这并不适用于不带虚函数的基类,也不适用于多重继承的情况。
  4. 对象生命周期: 赋值转换可能会涉及到对象的生命周期管理。如果派生类对象通过基类指针被赋值给其他对象或者在函数参数传递中,需要确保对象在使用完毕后不会导致悬空指针或内存泄漏。
  5. 继承关系和设计: 在使用继承时,应该考虑对象之间的逻辑关系和设计。基类和派生类之间的赋值转换应该符合对象在继承体系中的含义和用途。

总之,基类和派生类之间的赋值转换需要谨慎处理,确保代码的逻辑正确性和数据完整性。在进行类型转换时,始终要考虑继承关系、多态性、类型检查以及对象的生命周期管理。如果不确定如何进行赋值转换,可以借助类型转换操作符、dynamic_cast 或其他技术来确保安全和正确的转换。

继承中的作用域

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

  2. 子类和父类中有同名成员,子类成员将屏蔽父类对同名成员的直接访问,这种情况叫隐藏,也叫重定义。(在子类成员函数中,可以使用 基类::基类成员 显示访问)

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

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

// Student的_num和Person的_num构成隐藏关系,可以看出这样代码虽然能跑,但是非常容易混淆
class Person
{
protected :string _name = "zhangsan"; // 姓名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();
};

当子类和父类中存在同名成员(包括成员函数和成员变量)时,在调用这些同名成员时需要注意以下几点:

  1. 成员函数重写(覆盖): 如果子类中的成员函数与父类中的成员函数同名且参数列表也相同(即重写/覆盖),那么在通过子类对象调用这个函数时,会调用子类的版本而不是父类的版本。这是实现多态性的一种方式
  2. 隐藏成员函数: 如果子类中定义了与父类中的成员函数同名但参数列表不同的函数,那么父类的函数会被隐藏,除非使用作用域解析运算符 :: 显式地指定调用父类的函数,如下面的代码所示。
  3. 成员变量: 如果子类和父类有同名的成员变量,子类对象中的同名变量会隐藏父类中的同名变量。但通过子类对象调用成员函数时,仍然可以访问父类的成员变量,除非在子类中定义了同名的成员变量,此时子类的成员变量会隐藏父类的,所以上面在子类中定义得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. 派生类的构造函数必须调用基类的构造函数初始化基类的那一部分成员。如果基类没有默认的构造函数,则必须在派生类构造函数的初始化列表阶段显示调用

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

  3. 派生类的operator=必须要调用基类的operator=完成基类的复制。

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

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

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

  7. 因为后续一些场景析构函数需要构成重写,重写的条件之一是函数名相同。那么编译器会对析构函数名进行特殊处理,处理成destrutor(),所以父类析构函数不加virtual的情况下,子类析构函数和父类析构函数构成隐藏关系

#include <iostream>
#include <string>
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);//调用基类的`operator=`完成基类的复制_num = s._num;}return *this;}~Student() //不用再调父类的析构,会自动调用的{cout << "~Student()" << endl;}protected:int _num; // 学号
};void Test() 
{Student s1("jack", 18);  // 创建一个学生对象 s1Student s2(s1);           // 使用拷贝构造函数创建学生对象 s2Student s3("rose", 17);   // 创建另一个学生对象 s3s1 = s3;                  // 使用赋值操作符将 s3 赋值给 s1
}int main() 
{Test();return 0;
}

继承与友元

在 C++ 中,友元关系是不继承的,这意味着基类的友元不能访问派生类的私有和保护成员

如果一个类被声明为另一个类的友元,那么这个友元类可以访问该类的私有和保护成员。然而,这种访问权限不会被继承到派生类中。即使基类的成员函数被声明为子类的友元,也不能访问子类的私有和保护成员。

下面是一个示例,说明友元关系不能继承的情况:

#include <iostream>
using namespace std;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;//基类友元不能访问子类私有和保护成员
}
void main()
{Person p;Student s;Display(p, s);
}

在这个示例中,Display 函数是 Person 的友元函数,它可以访问 Person 类的私有和保护成员,但不能访问 Student 类的私有和保护成员。

继承与静态成员

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

#include <iostream>
using namespace std;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; // 研究科目
};
void TestPerson()
{Student s1;Student s2;Student s3;Graduate s4;cout << " 人数 :" << Person::_count << endl;Student::_count = 0;cout << " 人数 :" << Person::_count << endl;
}

在以上创建对象的过程中,Person 类的构造函数会被调用,每次创建一个对象,_count 会增加一次。由于 StudentGraduate 都是 Person 的派生类,因此它们的构造函数也会自动调用基类 Person 的构造函数,导致 _count 增加。

然后,通过 Student::_count = 0;Student 类的 _count 设置为零。这不会影响 Person::_count,因为 _count 是静态成员变量,它在整个类层次结构中是共享的。

复杂的菱形继承及菱形虚拟继承

单继承:一个子类只有一个直接父类时称这个继承关系为单继承
在这里插入图片描述

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

在这里插入图片描述

菱形继承:菱形继承是多继承的一种特殊情况
在这里插入图片描述

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

在这里插入图片描述

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

虚拟继承可以解决菱形继承的二义性和数据冗余的问题。如上面的继承关系,在StudentTeacher的继承Person时使用虚拟继承,即可解决问题。需要注意的是,虚拟继承不要在其他地方去使用

#include <iostream>
using namespace std;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; // 主修课程
};
void Test()
{Assistant a;a._name = "peter";
}

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

#include <iostream>
using namespace std;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放到的了对象组成的最下面,这个A同时属于B和C,那么B和C如何去找到公共的A呢?这里是通过了B和C的两个指针,指向的一张表。这两个指针叫虚基表指针,这两个表叫虚基表。虚基表中存的偏移量。通过偏移量可以找到下面的A
在这里插入图片描述

因为D中的B和C记录了公共A,所以通过通过B和C是可以通过偏移量的值来找到公共A,但同样可以直接使用D直接访问公共A,如d._a

下面是上面的Person关系菱形虚拟继承的原理解释

在这里插入图片描述

继承的总结和反思

  1. 很多人说C++语法复杂,其实多继承就是一个体现。有了多继承,就存在菱形继承,有了菱形继承就有菱形虚拟继承,底层实现就很复杂。所以一般不建议设计出多继承,一定不要设计出菱形继承。否则在复杂度及性能上都有问题

  2. 多继承可以认为是C++的缺陷之一,很多后来的语言都没有多继承,如Java。

  3. 继承和组合

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

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

※ 优先使用对象组合,而不是类继承 。

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

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

※ 实际尽量多去用组合。组合的耦合度低,代码维护性好。不过继承也有用武之地的,有些关系就适合继承那就用继承,另外要实现多态,也必须要继承。类之间的关系可以用继承,可以用组合,就用组合。

组合示例:

考虑一个图书馆系统,其中有两个主要类:Library(图书馆)和 Book(图书)。

class Book {
private:string title;string author;public:Book(const string& title, const string& author): title(title), author(author) {}string getTitle() const {return title;}string getAuthor() const {return author;}
};class Library {
private:vector<Book> books;public:void addBook(const Book& book) {books.push_back(book);}void displayBooks() {for (const Book& book : books) {cout << "Title: " << book.getTitle() << ", Author: " << book.getAuthor() << endl;}}
};

在这个示例中,Library 类组合了多个 Book 对象作为其成员,形成了一个图书馆。每个图书馆可以拥有多本图书,但图书与图书馆是独立的实体。这是一个典型的组合关系。

继承示例:

考虑一个动物分类系统,其中有一个基类 Animal(动物)和两个派生类 Dog(狗)和 Cat(猫)。

class Animal {
protected:string name;public:Animal(const string& name) : name(name) {}virtual void makeSound() const {cout << "Some generic animal sound." << endl;}
};class Dog : public Animal {
public:Dog(const string& name) : Animal(name) {}void makeSound() const override {cout << "Woof! Woof!" << endl;}
};class Cat : public Animal {
public:Cat(const string& name) : Animal(name) {}void makeSound() const override {cout << "Meow! Meow!" << endl;}
};

在这个示例中,DogCat 类继承自 Animal 类,表现出“is-a”关系。Animal 类作为基类,定义了共有的属性和方法,而派生类 DogCat 分别扩展了 makeSound 方法以实现不同的动物叫声。这是一个典型的继承关系。

什么时候用继承?什么时候用组合?

选择何时使用继承或组合取决于问题的性质、类之间的关系以及代码的设计目标。下面是一些指导原则,可以帮助您在使用继承和组合之间做出决策:

使用继承的情况:

  1. “is-a”关系: 当派生类是基类的一种特化时,表现出明确的“is-a”关系,通常使用继承。例如,CatDogAnimal 的特例。
  2. 代码重用: 如果多个类具有共同的属性和方法,可以将它们放在一个基类中,并通过继承来共享代码。这有助于避免重复编写相同的代码。
  3. 多态性需求: 如果您希望能够以统一的方式处理多个类的对象,并在运行时选择调用适当的方法,使用继承可以实现多态性。
  4. 扩展功能: 当需要在基类的基础上添加新的属性和方法时,使用继承可以方便地扩展现有功能。

使用组合的情况:

  1. “拥有”关系: 当一个类包含其他类作为其组成部分时,通常使用组合。例如,一个汽车包含引擎、轮胎等作为其组成部分。
  2. 灵活性和动态配置: 如果需要在运行时动态地组合不同的部件,使得一个对象可以包含不同类型的成员,使用组合可以提供更大的灵活性。
  3. 避免继承的复杂性: 继承可能会导致类层次结构变得复杂,特别是在多层次继承的情况下。组合可以减轻这种复杂性,使代码更易于理解和维护。
  4. 解耦合: 使用组合可以实现更低耦合的设计,因为成员对象可以是独立的实体,修改一个类不会直接影响其他类。

选择何时使用继承或组合取决于问题的性质、类之间的关系以及代码的设计目标。下面是一些指导原则,可以帮助您在使用继承和组合之间做出决策:

使用继承的情况:

  1. “is-a”关系: 当派生类是基类的一种特化时,表现出明确的“is-a”关系,通常使用继承。例如,CatDogAnimal 的特例。
  2. 代码重用: 如果多个类具有共同的属性和方法,可以将它们放在一个基类中,并通过继承来共享代码。这有助于避免重复编写相同的代码。
  3. 多态性需求: 如果您希望能够以统一的方式处理多个类的对象,并在运行时选择调用适当的方法,使用继承可以实现多态性。
  4. 扩展功能: 当需要在基类的基础上添加新的属性和方法时,使用继承可以方便地扩展现有功能。

使用组合的情况:

  1. “拥有”关系: 当一个类包含其他类作为其组成部分时,通常使用组合。例如,一个汽车包含引擎、轮胎等作为其组成部分。
  2. 灵活性和动态配置: 如果需要在运行时动态地组合不同的部件,使得一个对象可以包含不同类型的成员,使用组合可以提供更大的灵活性。
  3. 避免继承的复杂性: 继承可能会导致类层次结构变得复杂,特别是在多层次继承的情况下。组合可以减轻这种复杂性,使代码更易于理解和维护。
  4. 解耦合: 使用组合可以实现更低耦合的设计,因为成员对象可以是独立的实体,修改一个类不会直接影响其他类。

综上所述,继承和组合都有自己的优势和适用场景。在设计时,您应该根据问题的性质和需求来选择最合适的方法。通常情况下,优先选择组合,因为它可以带来更大的灵活性和解耦合,只有在确实存在“is-a”关系,并且需要多态性等特性时才使用继承。

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

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

相关文章

java-初识Servlet,Tomcat,JDBC

文章目录 前言一、ServletServlet 生命周期Servlet 实例Servlet 过滤器 二、TomcatJDBCJDBC连接数据库实例 总结 前言 java入门须知的重要概念/名词/技术 等 一、Servlet Servlet是Java Web开发中的一个核心组件&#xff0c;它是基于Java语言编写的服务器端程序&#xff0c;…

火热的大模型AIGC对数据中心存储趋势有什么影响?

随着人工智能和大数据技术的不断发展&#xff0c;业内AIGC&#xff08;人工智能、图形处理和云计算&#xff09;和大模型的发展趋势正在对数据中心存储发展方向产生深远的影响&#xff0c;主要集中对数据量和高性能计算的诉求。 大模型的普及要求数据中心存储具备更大的容量。大…

基于Googlenet深度学习网络的螺丝瑕疵检测matlab仿真

目录 1.算法运行效果图预览 2.算法运行软件版本 3.部分核心程序 4.算法理论概述 5.算法完整程序工程 1.算法运行效果图预览 2.算法运行软件版本 matlab2022a 3.部分核心程序 ....................................................................................% 获…

踏进字节的那一瞬间,我泪目了,这457天的外包经历值了....

一年半吗&#xff1f;我只记得437个日日夜夜 没有绝对的天才&#xff0c;只有持续不断的付出。对于我们每一个平凡人来说&#xff0c;改变命运只能依靠努力幸运&#xff0c;但如果你不够幸运&#xff0c;那就只能拉高努力的占比。 2021年8月&#xff0c;我有幸成为了字节跳动…

C语言面向对象的编程思想

面向对象编程 面向对象编程Object-Oriented Programming&#xff0c;OOP&#xff09; 作为一种新方法&#xff0c;其本质是以建立模型体现出来的抽象思维过程和面向对象的方法。模型是用来反映现实世界中事物特征的。任何一个模型都不可能反映客观事物的一切具体特征&#xff0…

MVC OR DDD

MVC OR DDD 说明&#xff1a;这篇是标题党&#xff0c;不包含相关概念说明 前段时间跟随师兄学习了解了DDD领域驱动模型&#xff0c;觉得这个思想更好&#xff0c;进行下面解析和学习方面的思考和实践&#xff0c;觉得很好&#xff0c;耐心读下去。希望对您有所帮助。 首先&am…

基于ADAU1452 DSP ANC和AEC算法的实现

是否需要申请加入数字音频系统研究开发交流答疑群(课题组)?加我微信hezkz17, 本群提供音频技术答疑服务,+群附加赠送,DSP音频项目核心开发资料, 1 使用Sigma中的NLMS算法模块 对应C源代码:

JavaSE 集合框架及背后的数据结构

目录 1 介绍2 学习的意义2.1 Java 集合框架的优点及作用2.2 笔试及面试题 3 接口 interfaces3.1 基本关系说明3.2 Collection 常用方法说明3.3 Collection 示例3.4 Map 常用方法说明3.5 Map 示例 4 实现 classes5 Java数据结构知识体系5.1 目标5.2 知识点 1 介绍 集合&#xf…

如何自己实现一个丝滑的流程图绘制工具(七)bpmn-js 批量删除、复制节点

背景 希望实现批量删除和复制节点&#xff0c;因为bpmn-js是canvas画的&#xff0c;所以不能像平时页面上的复制一样直接选择范围&#xff0c;会变成移动画布。 思路是&#xff1a; 绘制一个选择的效果框&#xff0c;这样才可以看出来选的节点有哪些。 上面的选中范围框效果…

K8S最新版本集群部署(v1.28) + 容器引擎Docker部署(下)

温故知新 &#x1f4da;第三章 Kubernetes各组件部署&#x1f4d7;安装kubectl&#xff08;可直接跳转到安装kubeadm章节&#xff0c;直接全部安装了&#xff09;&#x1f4d5;下载kubectl安装包&#x1f4d5;执行kubectl安装&#x1f4d5;验证kubectl &#x1f4d7;安装kubead…

Go死码消除

概念: 死码消除(dead code elimination, DCE) 是一种编译器优化技术, 作用是在编译阶段去掉对程序运行结果没有任何影响的代码 和 逃逸分析[1],内联优化[2]并称为 Go编译器执行的三个重要优化 效果: 对于 const.go代码如下: package mainimport "fmt"func max(a, b i…

wireshark过滤器的使用

目录 wiresharkwireshark的基本使用wireshark过滤器的区别 抓包案例 wireshark wireshark的基本使用 抓包采用 wireshark&#xff0c;提取特征时&#xff0c;要对 session 进行过滤&#xff0c;找到关键的stream&#xff0c;这里总结了 wireshark 过滤的基本语法&#xff0c;…

芯科科技宣布推出下一代暨第三代无线开发平台,打造更智能、更高效的物联网

第三代平台中的人工智能/机器学习引擎可将性能提升100倍以上 Simplicity Studio 6软件开发工具包通过新的开发环境将开发人员带向第三代平台 中国&#xff0c;北京 - 2023年8月22日 – 致力于以安全、智能无线连接技术&#xff0c;建立更互联世界的全球领导厂商Silicon Labs&…

java定位问题工具

一、使用 JDK 自带工具查看 JVM 情况 在我的机器上运行 ls 命令&#xff0c;可以看到 JDK 8 提供了非常多的工具或程序&#xff1a; 接下来&#xff0c;我会与你介绍些常用的监控工具。你也可以先通过下面这张图了解下各种工具的基本作用&#xff1a; 为了测试这些工具&#x…

从LeakCanary看内存快照解析

在从LeakCanary看内存快照生成一节中&#xff0c;我们已经了解了hprof的生成&#xff0c;并且将生成的hprof文件通过Android Studio进行解析&#xff0c;确实发现了内存泄漏对象MainActivity&#xff0c;但是在实际开发中&#xff0c;要求开发者自己去手动pull hprof文件进行解…

5.基于多能互补的热电联供型微网优化运行

MATLAB代码链接&#xff1a;基于多能互补的热电联供型微网优化运行 MATLAB代码&#xff1a;基于多能互补的热电联供型微网优化运行 关键词&#xff1a;多能互补 综合需求响应 热电联产 微网 优化调度 参考文档&#xff1a;《基于多能互补的热电联供型微网优化运行》基本完全…

链表(详解)

一、链表 1.1、什么是链表 1、链表是物理存储单元上非连续的、非顺序的存储结构&#xff0c;数据元素的逻辑顺序是通过链表的指针地址实现&#xff0c;有一系列结点&#xff08;地址&#xff09;组成&#xff0c;结点可动态的生成。 2、结点包括两个部分&#xff1a;&#x…

OLED透明屏显示技术:未来显示科技的领航者

OLED透明屏显示技术是一种创新性的显示技术&#xff0c;它的特殊性质使其成为未来显示科技的领航者。 OLED透明屏具有高对比度、快速响应时间、广视角和低功耗等优势&#xff0c;同时&#xff0c;其透明度、柔性和薄型设计使其成为创新设计的理想选择。 本文将深入探讨OLED透…

NSS [NUSTCTF 2022 新生赛]Ezjava1

NSS [NUSTCTF 2022 新生赛]Ezjava1 题目描述&#xff1a;你能获取flag{1}吗 开题&#xff0c;一眼java web中的index.jsp。 默认index.jsp中的body内容是$END$ 附件jar包导入IDEA&#xff0c;会自动反编译。看看源码。 附件结构大致如此。主要看classes.com.joe1sn中的代码就…

MyBatisx代码生成

MyBatisx代码生成 1.创建数据库表 CREATE TABLE sys_good (good_id int(11) NOT NULL,good_name varchar(255) COLLATE utf8mb4_general_ci DEFAULT NULL,good_desc varchar(255) COLLATE utf8mb4_general_ci DEFAULT NULL,PRIMARY KEY (good_id) ) ENGINEInnoDB DEFAULT CHA…