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

news2024/11/26 8:21:41

目录

一、继承的概念

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 constructor
    return 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 constructor
    return 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 constructor
    return 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 operator
    return 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 operator
    return 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 destructor
    return 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 destructor
    return 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 destructor
    return 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.coloradmin.cn/o/1693837.html

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

相关文章

Kubeadm安装部署k8s集群、踩坑日常

背景 ​ Docker是一个非常流行的容器化平台&#xff0c;它可以让我们方便构建、打包、发布和运行容器化应用程序。但是&#xff0c;在生产环境中&#xff0c;我们可能需要处理成百上千个容器&#xff0c;需要更好的管理这些容器&#xff0c;这就是Kubernetes(K8S)的用武之地。…

vue15:记事本vue指令案例

效果图&#xff1a; vue指令 代码 <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width, initial-scale1.0"><title>帅临记事本</…

prometheusgrafananode_export搭建监控平台

一、环境要求 1、docker安装docker环境 2、docker安装prometheus 3、docker安装grafana 4、node-exportor(安装在被测服务器上) 5、我的服务器是Ubuntu 二、docker 安装prometheus 1、下载Prometheus镜像 docker pull prom/prometheus 2、检查端口是否被占用 安装netstat命…

24款奔驰S450升级原厂后排娱乐系统 主动氛围灯有哪些功能

24款奔驰S400豪华升级原厂主动氛围灯与后排娱乐系统&#xff1a;画蛇添足还是锦上添花&#xff1f; 在当今汽车市场竞争激烈的环境下&#xff0c;汽车制造商们为了满足消费者的多元化需求&#xff0c;不断推出各种升级配置和豪华版本。24款奔驰S400豪华版作为奔驰S级的一款重要…

碌时刻必备!微信自动回复让你告别消息堆积

在忙碌的时候&#xff0c;我们往往会面临消息堆积如山的情况。无法及时回复消息不仅容易造成交流障碍&#xff0c;还可能错过重要的机会。 但是现在&#xff0c;有一个神奇的工具——个微管理系统&#xff0c;可以帮助我们轻松应对这个问题 &#xff0c;实现微信自动回复。 首…

AIGC-风格迁移-style Injection in Diffusion-CVPR2024HighLight-论文精度

Style Injection in Diffusion: A Training-free Approach for Adapting Large-scale Diffusion Models for Style Transfer-CVPR2024HighLight 代码&#xff1a;https://github.com/jiwoogit/StyleID 论文&#xff1a;https://jiwoogit.github.io/StyleID_site/ 为了解决风格迁…

WXSS模板样式-全局样式和局部样式

一、WXSS 1.WXSS WXSS(WeiXin Style Sheets)是一套样式语言&#xff0c;用于美化WXML的组件样式&#xff0c;类似于网页开发中的CSS 2.WXSS和CSS的关系 WXSS具有CSS大部分特性&#xff0c;同时&#xff0c;WXSS还对CSS进行了扩充以及修改&#xff0c;以适应微信小程序的开发…

详解 Spring MVC(Spring MVC 简介)

什么是 Spring MVC&#xff1f; Spring MVC 是 Spring 框架提供的一个基于 MVC 模式的轻量级 Web 框架&#xff0c;是 Spring 为表示层开发提供的一整套完整的解决方案&#xff0c;Spring MVC 使用了 MVC 架构模式&#xff0c;将 Web 层职责解耦&#xff0c;基于请求驱动模型&…

26计算机操作系统408考研-操作系统进程与线程篇章(三)

操作系统进程与线程篇章 ` 文章目录 操作系统进程与线程篇章前言一、进程概念进程控制块进程创建进程终止进程的阻塞和唤醒进程唤醒进程挂起和激活线程多线程线程实现与线程模型总结互斥和同步并发原理硬件同步信号量机制信号量的应用管程经典同步问题消息传递前言 一、进程概…

Qt 科目一考试系统(有源码)

项目源码和资源&#xff1a;科目一考试系统: qt实现科目一考试系统 一.项目概述 该项目是一个基于Qt框架开发的在线考试系统&#xff0c;主要实现了考试题目的随机抽取、考试时间限制、成绩统计等功能。用户可以通过界面操作进行考试&#xff0c;并查看自己的考试成绩。 二.技…

清理安卓手机广告

保存脚本另存为 Fuck_AD.sh&#xff0c;在手机执行后体验效果。 echo ""echo " " echo " - 开始执行清理广告库文件" sleep 3files(/data/app/*/*/lib/arm64/libpangleflipped.so/data/app/*/*/lib/arm64/libzeus_direct_dex.so/data/app/*/*/l…

力扣--哈希表13.罗马数字转整数

首先我们可以知道&#xff0c;一个整数&#xff0c;最多由2个罗马数字组成。 思路分析 这个方法能够正确将罗马数字转换为阿拉伯数字的原因在于它遵循了罗马数字的规则&#xff0c;并且对这些规则进行了正确的编码和处理。 罗马数字规则 罗马数字由以下字符组成&#xff1a…

第八届能源、环境与材料科学国际学术会议(EEMS 2024)

文章目录 一、重要信息二、大会简介三、委员会四、征稿主题五、论文出版六、会议议程七、出版信息八、征稿编辑 一、重要信息 会议官网&#xff1a;http://ic-eems.com主办方&#xff1a;常州大学大会时间&#xff1a;2024年06月7-9日大会地点&#xff1a;新加坡 Holiday Inn …

langchian进阶二:LCEL表达式,轻松进行chain的组装

LangChain表达式语言-LCEL&#xff0c;是一种声明式的方式&#xff0c;可以轻松地将链条组合在一起。 你会在这些情况下使用到LCEL表达式: 流式支持 当你用LCEL构建你的链时&#xff0c;你可以得到最佳的首次到令牌的时间(输出的第一块内容出来之前的时间)。对于一些链&#…

C语言.数据结构.顺序表

1.顺序表的概念及结构 1.1线性表 线性表&#xff08;linear list&#xff09;是n个具有相同特性的数据元素的有限序列。 线性表是一种在实际中广泛使用的数据结构&#xff0c;常见的线性表&#xff1a;顺序表、链表、栈、队列、字符串… 线性表在逻辑上是线性结构&#xff0c;…

创建FreeRTOS工程

创建STM32CubeMX工程 配置时钟 配置FreeRTOS 生成Keil MDK的工程 打开工程 结尾 这就是我们用STM32CubeMX创建的最基本的一个FreeRTOS的工程。可以看到&#xff0c;这个与我们使用stm32开发的裸机程序有相同的地方&#xff0c;也有不同的地方&#xff0c;我们可以发现&am…

Redis 哨兵机制的工作原理——Java全栈知识(22)

Redis 哨兵机制的工作原理 在之前的文章我们讲到了 Redis 的三种集群架构&#xff1a;跳转文章&#xff1a;Redis集群模式 接下来我们详细讲哨兵机制的作用以及实现原理 以下是 Redis 哨兵的结构图 1、Redis 哨兵的作用 哨兵的作用如下&#xff1a; 1、监控&#xff0c;2、…

Mysql基础(七)DQL之select 语句(二)

一 select 语句续 WHERE子句后面跟着的是一个或多个条件,用于指定需要检索的行COUNT(): 多少条数据 where 11 和 count(1) 与 count(*) count(1)、count(*)和count(指定字段)之间的区别 ① order by 排序 mysql 之数据排序扩展 1、使用 order by 语句来实现排序2、排序可…

数据迁移利器登场!Elasticdumpv6.110震撼发布,助你轻松搬迁大数据!

简介 Elasticdump 是一个用于导出和导入 Elasticsearch 数据的工具。它能够从一个 Elasticsearch 集群读取数据并写入到另一个 Elasticsearch 集群、文件系统或其他数据存储&#xff08;例如 S3&#xff09;。这个工具非常有用&#xff0c;特别是在进行数据迁移、备份和恢复操作…

# AI产品经理的自我修养:既懂用户,更懂技术!

今天上班的时候&#xff0c;发现很多AI社群都在讨论一篇播客《一个顶级AI产品经理的自我修养&#xff0c;对谈光年之外产品负责人Hidecloud》&#xff0c;这篇播客的嘉宾是光年之外的产品负责人——Hidecloud&#xff08;张涛&#xff09;&#xff0c;聊了许多关于他在做AI产品…