关联(Association) 聚合(Aggregation) 组合(Composition)

组合

概述

在现实生活中,复杂的对象通常是由小的,简单的组成,从简单对象构建复杂对象的过程称为对象组合

例如

  • 汽车是用金属框架,发动机,轮胎,变速器和其他大量零件制造而成的
  • 个人电脑由CPU,主板,内存等组成
  • 即便是你也是由较小部分组成:头,身体,腿,手

从广义上讲,两个对象存在关系构成了对象组合模型

  • 汽车有变速箱,你的电脑有CPU,你自己有心脏
  • 复杂对象有时被称为整体或父对象,简单的对象通常被称为零件,子零件或组件

在C++中,你已经看到结构和类可以具体各种类型的数据成员(例如基础类型和其他类)

当我们使用数据成员构建类时,实际上是从较简单的部分构造复杂对象,即对象组合,因此,有时将结构和类称为复合类型

对象组合在C++上下文中很有用,因为它使我们可以通过组合更简单,更易于管理的创建复杂的类

这降低了复杂,并且使我们能够更快地编写代码并减少错误,因此我们可以重用已经编写,测试和验证过的有效代码

对象组合的类型

对象组合有两种基本的子类型

  • 组合(composition)
  • 聚合(aggregation)

关于术语的注释:术语“组合”通常用于指组合和聚合,而不仅指组合

在本篇文章中,当我们提及两者时,将术语“对象组合”,而当具体提及组合,将使用术语组合(composition)

组合

为了符合组合条件,对象和零件必须具有以下关系

  • 该部件(成员)是对象(类)的一部分
  • 该部件(成员)一次只能属于一个对象(类)
  • 该部件(成员)的存在由对象(类)管理
  • 该部件(成员)不知道对象(类)的存在

一个真实的组合例子就是一个人的身体和心脏的关系

组合的构成是部件 - 整体的关联,部件是整体的一部分

  • 例如,心脏是人的身体的一部分。组合中的部件一次只能是一个对象的一部分
  • 属于一个人的身体的心脏不能同时属于另一个人的身体

在组合构建中,对象负责部件的存亡

  • 意味着,部件在对象创建的时候创建,并在销毁对象时销毁
  • 从更广的范围来说,这意味着,对象不需要用户参与管理部件的生命周期
  • 例如,当创建一个身体时,心脏也被创建。当一个人的身体被破坏时,他们的心脏也被破坏。因此,这就构成了“死亡关系”

部件不知道整体的存在

  • 你的心脏幸福地运转着,却没有意识到它是更大结构的一部分
  • 我们称其为单向关系,因此身体了解心脏,但反之未然

部件的可移植说明

  • 心脏可以从一个身体移植到另外一个身体
  • 移植后仍然符合组合关系的要求(心脏现已由接受者拥有,除非再次移植,否则它只能是接受者的一部分)

例子

class Fraction
{
    private:
        int m_numerator;
        int m_denominator;

    public:
        Fraction(int numerator=0, int denominator=1):
            m_numerator{ numerator }, m_denominator{ denominator }
        {
            // We put reduce() in the constructor to ensure any fractions we make get reduced!
            // Since all of the overloaded operators create new Fractions, we can guarantee this will get called here
            reduce();
        }
};
  • 此类具有两个数据成员:分子和分母。分子和分母是分数的一部件(包含在其中)
  • 它们一次不能属于一个以上分数。分子和分母不知道它们是分数的部件,它们知道表示某个整数
  • 创建分数实例的时候,同时创建分子和分母两个成员变量。当分数被销毁的时候,分子和分母的成员变量也被销毁

更多例子

#include <iostream>
#include <string.h>

class Point2D
{
    private:
        int m_x;
        int m_y;

    public:
        // a default constructof
        Point2D(): m_x{0}, m_y{0}
        {

        }

        // a specific constructor
        Point2D(int x, int y): m_x{x}, m_y{y}
        {

        }

        // an overloaded output operator
        friend std::ostream& operator <<(std::ostream& out, const Point2D &point)
        {
            out << '(' << point.m_x << ", " << point.m_y << ')';
            return out;
        }

        // Access functions
        void setPoint(int x, int y)
        {
            m_x = x;
            m_y = y;
        }
};


class Creature
{
    private:
        std::string m_name;
        Point2D m_location;
    
    public:
        Creature(const std::string &name, const Point2D &location)
            :m_name{name}, m_location{location}
        {
            
        }

        friend std::ostream& operator<<(std::ostream& out, const Creature &creature)
        {
            out << creature.m_name << " is at " << creature.m_location;
            return out;
        }

        void moveTo(int x, int y)
        {
            m_location.setPoint(x, y);
        }
};

int main() {

    std::cout << "Enter a name for your creature.";
    std::string name;
    std::cin >> name;
    Creature creature{name, {4, 7}};

    while (true) {
        // print the creature's name and location
        std::cout << creature << '\n';

        std::cout << "Enter new X location for creature (-1 to quit): ";
        int x{0};
        std::cin >> x;
        if (x == -1) {
            break;
        }
        
        std::cout << "Enter new Y location for creature (-1 to quit):";
        int y{0};
        std::cin >> y;
        if (y == -1) {
            break;
        }
        creature.moveTo(x, y);
    }

    return 0;
}

组合的变体

尽管大多数组合模型中,部分随着整体创建而创建,部分随整体销毁而销毁。但是还有一些不一样的情况

例如

  • 在组合关系中,可能会延迟某些部分的创建,直到它们需要为止。例如,用户为字符串分配一些要保留的数据之前,字符串类可能不会创建动态字符数组
  • 在组合关系中,可能会选择使用已提供给它的零件作为输入,而不是自己创建零件
  • 在组合关系中,可能会将其部分的销毁要求委托给其他对象。如,垃圾回收例程

这里的关键点是,组合中整体管理部分,而组合的用户则不需要进行任何管理

组合和子类

当涉及对象组合时新程序员经常会问一个问题:“我什么时候应该使用子类而不是直接实现功能?”

例如,我们可以不使用Point2D类来实现Creature的位置,而可以只向Creature类添加2个整数,并在Creature类中编写代码来处理位置

将Point2D设为自己的类有很多好处:

  • 每个对象可以保持简单明了,专注于出色地完成一项任务,由于它们职责更分明,因此使这些类更容易编写和理解
    • 例如,Point2D只关注与点有关的时间,这使理解和编写代码更加简单
  • 每个子类可以独立的,这使得它们可以重用
    • 例如,我们可以在完全不同的应用程序中重用Point2D类
    • 或者,我们需要另外一个点作为试图要到达的目的地,那么可以简单添加另一个Point2D成员变量
  • 父类可以让子类完成大部分的工作,而自己专注协调子类之间的数据流。这有助于降低父对象的整体复杂性,因为它可以将任务委派给已经知道如何执行这些任务的子对象
    • 例如,当我们移动Creature时,它将任务委派给Point类,该类已经了解如何设置点
    • 因此,Creature类不必担心如何实现这些事情

一个好的经验法则是,应该构建单个类对应单个任务

在我们的示例中, 很明显Creature不必担心Point的实现方式或名称的存储方式

  • Creature的工作不需要知道哪些私密的细节
  • Creature的工作是担心如何协调数据流,并确保每个子类都知道应该做什么。接下来有各个子类担心它们怎么做

聚合

概述

要符合聚合条件,整个对象及其各个部件必须具有以下关系

  • 该部件(成员)是对象(类)的一部分
  • 该部件 (成员) 一次可以属于多个对象(类)
  • 该部件 (成员)的存在不由对象(类)管理
  • 该部件(成员)不知道对象(类)的存在

像组合一样,聚合仍然是部件与整体的关系,其中部件包含在整体中,并且是单向关系,但是与组合不同,部件一次可以属于一个以上的对象,而整个对象对部件的存在和生命周期不负责

创建聚合时,聚合不负责创建部件,销毁聚合时,聚合不负责销毁部件

例如,考虑一个人与其家庭住址之间的关系

  • 在此示例中,为简单起见,我们将说每个人都有一个地址。但是,该地址一次可以属于一个以上的人:如你和你的室友或其他重要的人
  • 但是,该地址不是由该人管理的-该地址可能在此人到达之前就已存在,而在该人离开后仍然存在
  • 另外,一个人知道他们住的地方,但地址不知道他们住的地方
  • 因为这是一个聚合关系

另外,考虑一下汽车和引擎

  • 汽车发动机是汽车的一部分,尽管引擎属于汽车,但它也可以属于其他事物,例如拥有汽车的人
  • 汽车不负责发动机的制造和毁坏
  • 虽然汽车知道它有引擎,但是引擎却不知道它是汽车的一部分

在对物理对象建模时,使用“破坏”一词可能会有些麻烦

  • 有人可能会争辩说“如果流星从天上掉下来砸碎了汽车,汽车零件也不会全部被毁吗?”当然是,但这是流星的错
  • 重要的是,汽车对零件的损坏不承担任何责任(但可能会受外力的影响)

我们可以说聚合模型“有”关系(一个部分有老师,汽车有引擎)。与组合类似,集合的各个部件可以单独的或相互调用

聚合案例

因为聚合与组合相似,因为它们都是部件 - 整体关系,因此它们的实现几乎完全相同,它们之间的差异主要是语义上的

在组合中,我们通常使用普通成员变量(或有组合类处理分配和释放过程的指针)将零件添加到组合中

在聚合中,我们还将部件添加为成员变量,但是,这些成员变量通常是引用或指针,用于指向已在类范围之外创建的对象。因此,聚合通常要么指向的对象用作构造函数参数,要么开始为空,然后再通过访问函数或运算符添加子对象

因为这些部分存在于类范围之外,所以当销毁该类时,指针或引用成员变量将销毁(但不会删除)。因此,部件本身仍将存在

#include <iostream>
#include <string>

class Teacher
{
    private:
        std::string m_name{};
    
    public:
        Teacher(const std::string& name): m_name{name}
        {

        }

        const std::string& getName() const
        {
            return m_name;
        }
};

class Department
{
    private:
        const Teacher& m_teacher;   // This dept holds only one teacher of simplicity, but it could hold many teachers
    public:
        Department(const Teacher& teacher): m_teacher{teacher}
        {

        }
};

int main() {

    // Create a teacher outside the scope of the Department
    Teacher bob {"Bob"};    // create a teacher

    {
        // Create a department and use the consturctos partameter to pass
        // the teacher to it.
        Department department {bob};
    }   // department goes out of scope here and is destoryed

    // bob still exists here, but the department doesn't
    std::cout << bob.getName() << " still exists! \n";
    return 0;
}
  • 首先,该部门只能容纳一位老师。其次,老师不会知道他们所属的部门
  • 所以在本例中,bob是独立于department创建的,然后传递给department的构造函数
  • 当department 被销毁的时,m_teacher引用被销毁。但是是教师本身并没有被销毁,因此它仍然存在,直到后来在main()被独立销毁

为建模选择正确的关系

尽管上面的例子中教师不知道他们为那么部门工作似乎有些愚蠢,但在给定程序的背景下这可能完全没问题

在确定要实现哪种关系时,请实现最简单的满足你的需求,而不是看起来最适合现实生活的关系

例如,如果你正在编写车身修理厂模拟器,则可能希望将汽车和引擎作为聚合来实现,因此可以将引擎卸下并放在架子上以备后用

但是,如果你正在编写赛车模拟游戏,则可能希望将汽车和引擎作为组合来实现,因为在这种情况下,引擎永远不会存在于汽车外部

关联

概述

要符合关联条件,一个对象与另一个对象必须具有以下关系

  • 关联的对象(成员)与对象(类)无关
  • 关联的对象(成员)一次可以属于多个对象(类)
  • 关联的对象(成员)没有由该成员(类)管理其存在
  • 关联的对象(成员)可能知道或可能不知道对象(类)的存在

与组合和聚合的部分是构成整体的一部分不同,在关联中,关联的对象与该对象无关

就像聚合一样,关联的对象可以同时属于多个对象,并且不受这些对象的管理

但是,与聚合(关系始终是单向的)不同的是,在关联中,关系可以是单向的也可以是双向的(两个对象相互了解)

  • 医生与患者之间的关系就是一个很好的例子
  • 医生显然与患者有关系,但从概念上讲,这个不是部分/整体(对象组合)的关系
  • 一个医生一天可以看很多病人,一个病人可以看很多医生(也许想要第二个医生意见,或者他们正在拜访不同类型的医生)
  • 该对象的寿命都没有和另外一个有关系
  • 我们可以说关联模型的是“用户-单个”关系。医生“使用”病人(以赚取收入)。患者使用医生(出于他们所需的任何健康目的)

案例

#include <cstdint>
#include <functional>
#include <iostream>
#include <string>
#include <vector>
using namespace std;

// Since Doctor and Patient have a circular dependency, we're going to farward declare Patient
class Patient;

class Doctor
{
    private:
        string m_name{};
        vector<reference_wrapper<const Patient>> m_patient{};

    public:
        Doctor(const string& name): m_name{name}
        {

        }

        void addPatient(Patient& patient);

        // We'll implement this function below Patient since we need Patient to be defined at that point
        friend ostream& operator<<(ostream &out, const Doctor &doctor);

        const string& getName() const 
        {
            return m_name;
        }
};

class Patient
{
    private:
        string m_name{};
        vector<reference_wrapper<const Doctor>> m_doctor{}; // so that we can use it here

        // We're going to make private because we don't want the public to use it
        // They should use Doctor::addPatient() instead, which is publiy exposed
        void addDoctor(const Doctor& doctor) 
        {
            m_doctor.push_back(doctor);
        }

    public:
        Patient(const string& name): m_name {name}
        {

        }

        // We'll friend Doctor::addPatient() so it can access the private function Patient::addDoctor()
        friend ostream& operator << (ostream &out, const Patient &patient);

        const string& getName() const 
        {
            return m_name;
        }

        // We'll friend Doctor::addPatient() so it can access the private function Patient::addDoctor()
        friend void Doctor::addPatient(Patient& patient);
};

void Doctor::addPatient(Patient& patient)
{
    // Our doctor will add this patient
    m_patient.push_back(patient);

    // and the patient will also add this doctor
    patient.addDoctor(*this);
}

ostream& operator<<(ostream &out, const Doctor &doctor)
{
    if (doctor.m_patient.empty()) {
        out << doctor.m_name << " has no patients right now";
        return out;
    }

    out << doctor.m_name << " is seeing patients: ";
    for (const auto& patient: doctor.m_patient) {
        out << patient.get().getName() << ' ';
    }

    return out;
}

ostream& operator<<(ostream &out, const Patient &patient)
{
    if (patient.m_doctor.empty()) {
        out << patient.getName() << " has no doctors right now";
        return out;
    }

    out << patient.m_name << " is seeding doctors: ";
    for (const auto& doctor: patient.m_doctor) {
        out << doctor.get().getName() << ' ';
    }

    return out;
}

int main() {

    // Create a Patient outside the scope of the Doctor
    Patient dave {"Dave"};
    Patient frank {"Frank"};
    Patient betsy {"Betsy"};

    Doctor james {"James"};
    Doctor scott {"Scott"};

    james.addPatient(dave);

    scott.addPatient(dave);
    scott.addPatient(betsy);

    cout << james << '\n';
	cout << scott << '\n';
	cout << dave << '\n';
	cout << frank << '\n';
	cout << betsy << '\n';
    return 0;
}

因为关联是一种广泛的关系,所以可以用许多不同的方式来实现它们。但是,大多数情况下,关联是使用指针实现的,其中对象指向关联的对象

在这个示例中,我们将实现双向“医生/患者”关系,因为让医生知道他们的患者是有意义的,反之亦然

通常,如果可以使用单向关联,则应避免使用双向关联,因为它们会增加复杂性,并且往往更容易编写而不会错误

反身关联

有时对象可能与相同类型的其他对象有关系,这被称为反身关联

反身关联的一个很好例子是大学课程与其先决条件(也就是大学课程)之间的关系

考虑简化的情况,其中一门课程只能有一个前提条件。我们可以做这样的是事情

include <string>
   class Course
   {
       private:
           std::string m_name;
           const Course *m_prerequisite;

       public:
           Course(const std::string &name, const Course *prerequisite = nullptr): m_name{name}, m_prerequisite{prerequisite}
           {
           }
   };
  • 这可能会导致关联链(课程具有先决条件等)

关联可以是间接的

#include <iostream>
#include <string>
using namespace std;

class Car
{
    private:
        string m_name;
        int m_id;
    
    public:
        Car(const string& name, int id): m_name{name}, m_id{id}
        {

        }

        const string& getName() const
        {
            return m_name;
        }

        int getId() const
        {
            return m_id;
        }
};

// Our CarLot is essentially just a static array of Cars and a lookup function to retrieven them
// Because it's static, we don't need to allocate an object of type CarLot to use it
class CarLot
{
    private:
        static Car s_carLot[4];
    
    public:
        CarLot() = delete;  // Ensure we don't try to create a CarLot

        static Car* getCar(int id)
        {
            for (int count{0}; count < 4; ++count) {
                if (s_carLot[count].getId() == id) {
                    return &(s_carLot[count]);
                }
            }
            
            return nullptr;
        }
};

Car CarLot::s_carLot[4]{{"Prius", 4}, {"Corolla", 17}, {"Accord", 84}, {"Matrix", 62}};

class Driver
{
    private:
        string m_name;
        int m_carId;        // we're associated with the Car by ID rather than pointer

    public:
        Driver(const string& name, int carId): m_name{name}, m_carId{carId}
        {

        }

        const string& getName() const 
        {
            return m_name;
        }

        int getCarId() const
        {
            return m_carId;
        }
};

int main() {

    Driver d {"Franz", 17};                     // Franz is driving the car with ID 17

    Car *car {CarLot::getCar(d.getCarId())};    // Get that car from the car lot

    if (car) {
        cout << d.getName() << " is driving a " << car->getName() << '\n';
    } else {
        cout << d.getName() << " couldn't find his car \n";
    }

    return 0;
}

在上面的示例中,我们有一个CarLot来存放我们的汽车。需要汽车的驾驶员没有指向他的汽车的指针-相反,他具有汽车ID,我们可以使用该ID在需要时从CarLot中获取汽车

在这个特定示例中,以这种方式执行操作有点愚蠢,因为将Car将退出CarLot要求的查找效率很低(将两者连接的指针要快得多)

但是,通过唯一的ID而不是指针来引用事物也具有优势

  • 例如,你可以引用当前不在内存中的内容(也许它们在文件或数据中,并且可以按需加载)
  • 同样,指针可以占用4或8个字节。如果空间有限,并且唯一对象的数量很少,则用8位或16位整数引用它们可以节省大量内存

总结

组合和聚合的总结

组合

  • 通常使用普通成员变量
  • 如果类本身处理对象分配/取消,则可以使用指针成语
  • 负责部件的创建/销毁

聚合

  • 通常使用指向或引用位于聚合类范围之外的对象的指针或引用成员
  • 不负责创建/销毁部件

值得注意的是,组合和聚合的概念不是相互排斥的,可以在同一类中自由混合。完全可以编写一个类来负责创建/销毁某些部分,而不是其他

例如,我们的部门类可以有一个名字和一个老师类

  • 该名称可以按组合添加到部门中,并随部门一起创建和销毁
  • 另一方面,将通过汇总将教师添加到部门,并独立创建/销毁该老师

尽管聚合可能非常有用,但它们也可能更加危险,因为聚合无法处理其部分的重新分配,解除分配留给外部人员完成。如果外部方不再指向废弃部分的指针或引用,或者只是忘记进行清理(假设该类将进行清理),则内存将被泄漏

出于这个原因,应该优先使用组合而不是聚合

例子

#include <iostream>
#include <string>
#include <vector>
using namespace std;

class Teacher
{
    private:
        std::string m_name{};
    
    public:
        Teacher(const std::string& name): m_name{name}
        {

        }

        const std::string& getName() const
        {
            return m_name;
        }
};

class Department
{
    private:
        vector<Teacher> m_listOfTeachers;
    public:
        Department()
        {

        }

        void add(const Teacher& teacher)
        {
            m_listOfTeachers.push_back(teacher);
        }

        friend std::ostream& operator<<(std::ostream& out, const Department &department)
        {
            out << "Department: ";
            for (auto& teac : department.m_listOfTeachers) {
                out << teac.getName() << ' ';
            }
            return out;
        }
};

int main() {

    // Create a teacher outside the scope of the Department
    Teacher t1 {"Bob"};
    Teacher t2 {"Frank"};
    Teacher t3 {"Beth"};

    {
        // Create a department and add some Teachers to it
        Department department{};    // create an empty Department

        department.add(t1);
        department.add(t2);
        department.add(t3);

        cout << department;
    }   // department goes out of scope here and is destoryed

    cout << t1.getName() << " still exists! \n";
    cout << t2.getName() << " still exists! \n";
    cout << t3.getName() << " still exists! \n";
    return 0;
}

组合 vs 聚合 vs 关联 总结

属性组合聚合关联
关系类型整体/部分整体/部分没有特定关系
成员属于多个类别
类管理成员生命周期
方向性单向单向单向或双向
关系动词Part-ofHas-aUses-a

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

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

相关文章

ChatGPT4深度解析:探索智能对话新境界

大模型chatgpt4分析功能初探 目录 1、探测目的 2、目标变量分析 3、特征缺失率处理 4、特征描述性分析 5、异常值分析 6、相关性分析 7、高阶特征挖掘 1、探测目的 1、分析chat4的数据分析能力&#xff0c;提高部门人效 2、给数据挖掘提供思路 3、原始数据&#xf…

Navicat终于免费了, 但是这个结果很奇葩

个人用下载地址: 点呀 好家伙, 每个机构最多5个用户, 对于正在审计的公司…

DAY1: 实习前期准备

文章目录 VS Code安装的插件C/CCMakeGitHub CopilotRemote-SSH收获 VS Code 下载链接&#xff1a;https://code.visualstudio.com 安装的插件 C/C 是什么&#xff1a;C/C IntelliSense, debugging, and code browsing. 为什么&#xff1a;初步了解如何在VS Code里使用C输出…

Vulnhub-Os-hackNos-1(包含靶机获取不了IP地址)

https://download.vulnhub.com/hacknos/Os-hackNos-1.ova #靶机下载地址 题目&#xff1a;要找到两个flag user.txt root.txt 文件打开 改为NAT vuln-hub-OS-HACKNOS-1靶机检测不到IP地址 重启靶机 按住shift 按下键盘字母"E"键 将图中ro修改成…

筛选Github上的一些优质项目

每个项目旁都有标签说明其特点&#xff0c;如今日热捧、多模态、收入生成、机器人、大型语言模型等。 项目涵盖了不同的编程语言和领域&#xff0c;包括人工智能、语言模型、网页数据采集、聊天机器人、语音合成、AI 代理工具集、语音转录、大型语言模型、DevOps、本地文件共享…

7-6 每日升学消息汇总

复旦附中清北比例大涨&#xff0c;从统计数据来看&#xff0c;今年复附的清北人数将创历史新高&#xff0c;达到前所未有年进43人。离上海7月9号中考出分&#xff0c;还有3天。小道消息说&#xff0c;画狮的数游天下又回来了&#xff0c;目前还未官方消息。2024第二届国际数学夏…

安卓虚拟位置修改1.25beta支持路线模拟、直接定位修改

导语:更新支持安卓14/15&#xff0c;支持路线模拟、直接定位修改&#xff0c;仅支持单一版本 无root需根据教程搭配下方链接所提供的虚拟机便可进行使用 有root且具备XP环境可直接真机运行 如你有特殊需求 重启问题设置打开XP兼容 针对具有虚拟机检测的软件 建议如下 度娘搜索…

多表查询sql

概述&#xff1a;项目开发中,在进行数据库表结构设计时,会根据业务需求及业务模块之间的关系,分析并设计表结构,由于业务之间相互关联,所以各个表结构之间也存在着各种联系&#xff0c;分为三种&#xff1a; 一对多多对多一对一 一、多表关系 一对多 案例&#xff1a;部门与…

在CMD中创建虚拟环境并在VSCode中使用和管理

1. 使用Conda创建虚拟环境 在CMD或Anaconda Prompt中执行以下代码以创建一个新的虚拟环境&#xff1a; conda create -n my_env python 3.8 这样会创建一个名为 my_env 的环境&#xff0c;并在Anaconda环境目录下生成一个相应的文件夹&#xff0c;包含该虚拟环境所需的所有…

STM32-ADC+DMA

本内容基于江协科技STM32视频学习之后整理而得。 文章目录 1. ADC模拟-数字转换器1.1 ADC模拟-数字转换器1.2 逐次逼近型ADC1.3 ADC框图1.4 ADC基本结构1.5 输入通道1.6 规则组的转换模式1.6.1 单次转换&#xff0c;非扫描模式1.6.2 连续转换&#xff0c;非扫描模式1.6.3 单次…

时间、查找、打包、行过滤与指令的运行——linux指令学习(二)

前言&#xff1a;本节内容标题虽然为指令&#xff0c;但是并不只是讲指令&#xff0c; 更多的是和指令相关的一些原理性的东西。 如果友友只想要查一查某个指令的用法&#xff0c; 很抱歉&#xff0c; 本节不是那种带有字典性质的文章。但是如果友友是想要来学习的&#xff0c;…

如何确保 PostgreSQL 在高并发写操作场景下的数据完整性?

文章目录 一、理解数据完整性二、高并发写操作带来的挑战三、解决方案&#xff08;一&#xff09;使用合适的事务隔离级别&#xff08;二&#xff09;使用合适的锁机制&#xff08;三&#xff09;处理死锁&#xff08;四&#xff09;使用索引和约束&#xff08;五&#xff09;批…

系统学习ElastricSearch(一)

不知道大家在项目中是否使用过ElastricSearch&#xff1f;大家对它的了解又有多少呢&#xff1f;官网的定义&#xff1a;Elasticsearch是一个分布式、可扩展、近实时的搜索与数据分析引擎。今天我们就来揭开一下它的神秘面纱&#xff08;以下简称ES&#xff09;。 ES 是使用 J…

uniapp零基础入门Vue3组合式API语法版本开发咸虾米壁纸项目实战

嗨&#xff0c;大家好&#xff0c;我是爱搞知识的咸虾米。 今天给大家带来的是零基础入门uniapp&#xff0c;课程采用的是最新的Vue3组合式API版本&#xff0c;22年发布的uniappVue2版本获得了官方推荐&#xff0c;有很多同学等着我这个vue3版本的那&#xff0c;如果没有学过vu…

CH12_函数和事件

第12章&#xff1a;Javascript的函数和事件 本章目标 函数的概念掌握常用的系统函数掌握类型转换掌握Javascript的常用事件 课程回顾 Javascript中的循环有那些&#xff1f;Javascript中的各个循环特点是什么&#xff1f;Javascript中的各个循环语法分别是什么&#xff1f;…

网页封装APP:让您的网站变身移动应用

网页封装APP&#xff1a;让您的网站变身移动应用 随着移动设备的普及&#xff0c;越来越多的人开始使用移动设备浏览网站。但是&#xff0c;传统的网站设计并不适合移动设备的屏幕尺寸和交互方式&#xff0c;这导致了用户体验不佳和流失。 有没有办法让您的网站变身移动应用&…

【ROS2】初级:客户端-编写一个简单的服务和客户端(Python)

目标&#xff1a;使用 Python 创建并运行服务节点和客户端节点。 教程级别&#xff1a;初学者 时间&#xff1a;20 分钟 目录 背景 先决条件 任务 1. 创建一个包2. 编写服务节点3. 编写客户端节点4. 构建并运行 摘要 下一步 相关内容 背景 当节点通过服务进行通信时&#xff0c…

【项目日记(一)】梦幻笔耕-数据层实现

❣博主主页: 33的博客❣ ▶️文章专栏分类:项目日记◀️ &#x1f69a;我的代码仓库: 33的代码仓库&#x1f69a; &#x1faf5;&#x1faf5;&#x1faf5;关注我带你了解更多项目内容 目录 1.前言2.后端模块3数据库设计4.mapper实现4.1UserInfoMapper4.2BlogMapper 5.总结 1.…

机器学习筑基篇,​Ubuntu 24.04 快速安装 PyCharm IDE 工具,无需激活!

[ 知识是人生的灯塔&#xff0c;只有不断学习&#xff0c;才能照亮前行的道路 ] Ubuntu 24.04 快速安装 PyCharm IDE 工具 描述&#xff1a;虽然在之前我们安装了VScode&#xff0c;但是其对于使用Python来写大型项目以及各类配置还是比较复杂的&#xff0c;所以这里我们还是推…

U盘非安全拔出后的格式化危机与数据拯救策略

在数字化时代&#xff0c;U盘作为便捷的数据携带工具&#xff0c;其重要性不言而喻。然而&#xff0c;许多用户在日常使用中往往忽视了安全退出的重要性&#xff0c;直接拔出U盘后再插入时可能会遭遇“需要格式化”的提示&#xff0c;这一状况不仅令人措手不及&#xff0c;更可…