【设计模式——学习笔记】23种设计模式——访问者模式Visitor(原理讲解+应用场景介绍+案例介绍+Java代码实现)

news2024/10/5 13:59:42

文章目录

  • 案例引入
    • 要求
    • 传统方案
  • 介绍
    • 基本介绍
    • 应用场景
    • 登场角色
      • 尚硅谷版本
      • 《图解设计模式》版本
  • 案例实现
    • 案例一
      • 实现
      • 拓展
    • 案例二(个人感觉这个案例较好)
      • 实现
      • 分析
      • 拓展一
      • 拓展二
      • 拓展三
  • 总结
  • 额外知识
    • 双重分发
  • 文章说明

案例引入

要求

测评系统需求:将观众分为男人和女人,对歌手进行测评,当看完某个歌手表演后,得到他们对该歌手不同的评价(比如 成功、失败 等)

传统方案

在这里插入图片描述

Man和Woman里面都有“成功”、“失败”的方法

【分析】

  • 如果系统比较小,这样设置是可以的,但是考虑系统增加越来越多新的功能时,对代码改动较大(如需要增加一个新的评价方式,就需要在Man和Woman类中同时添加),违反了ocp原则,不利于维护
  • 扩展性不好,比如增加了新的人员类型,或者增加新的评价,都需要修改很多代码

【改进】

使用访问者模式

介绍

基本介绍

  • 在数据结构中保存着许多元素,我们会对这些元素进行“处理”。这时,“处理”代码放在哪里比较好呢?通常的做法是将它们放在表示数据结构的类中。但是,如果“处理”有许多种呢?这种情况下,每当增加一种处理,我们就不得不去修改表示数据结构的类。在Visitor模式中,数据结构与处理被分离开来。我们编写一个表示“访问者”的类来访问数据结构中的元素,并把对各元素的处理交给访问者类。这样,当需要增加新的处理时,我们只需要编写新的访问者,然后让数据结构可以接受访问者的访问即可
  • 即访问者模式主要将数据结构数据操作分离,解决数据结构和操作耦合性问题
  • 访问者模式的基本工作原理是: 在被访问的类里面提供一个对外接待访问者的接口

应用场景

  • 需要对一个对象结构中的对象进行很多不同操作(而且这些操作彼此没有关联),需要避免让这些操作"污染"这些对象的类,可以选用访问者模式解决

登场角色

在这里插入图片描述

  • Visitor :是抽象访问者,为该对象结构中的ConcreteElement的每一个类声明一个visit方法
  • ConcreteVisitor:是一个具体的访问者,实现Visitor声明的每个方法
  • ObjectStructure:能枚举它的元素,可以提供一个高层的接口,用来允许访问者访问元素(比如案例一的ObjectStructure类的display方法)
  • Element:定义一个accept 方法,接收一个访问者对象
  • ConcreteElement:为具体元素,实现了accept 方法

尚硅谷版本

《图解设计模式》版本

在这里插入图片描述

  • Visitor(访问者):Visitor角色负责对数据结构中每个具体的元素(ConcreteElement角色)声明一个用于访问XXXXX的visit(XXXXX)方法。visit(XXXXX)是用于处理XXXXX的方法负责实现该方法的是ConcreteVisitor角色
  • ConcreteVisitor(具体的访问者):ConcreteVisitor角色负责实现 Visitor角色所定义的接口(API)。它要实现所有的visit(XXXXX)方法,即实现如何处理每个ConcreteElement角色
  • Element(元素):Element角色表示Visitor角色的访问对象。它声明了接受访问者的accept方法。accept 方法接收到的参数是Visitor角色
  • ConcreteElement(具体元素):ConcreteElement角色负责实现Element角色所定义的接口(API)
  • ObjectStructure(对象数据结构):ObjectStructur角色负责处理Element角色的集合,能够枚举它的元素(案例二的Directory类同时扮演该角色和ConcreteElement角色)

案例实现

案例一

在这里插入图片描述

实现

【Action(Visitor)】

package com.atguigu.visitor;

public abstract class Action {

   /**
    * 得到男性 的测评
    * @param man
    */
   public abstract void getManResult(Man man);

   /**
    * 得到女性 的测评
    * @param woman
    */
   public abstract void getWomanResult(Woman woman);
}

【Success(ConcreteVisitor)】

package com.atguigu.visitor;

public class Success extends Action {

   @Override
   public void getManResult(Man man) {
      System.out.println(" 男人给的评价该歌手很成功 !");
   }

   @Override
   public void getWomanResult(Woman woman) {
      System.out.println(" 女人给的评价该歌手很成功 !");
   }

}

【Fail(ConcreteVisitor)】

package com.atguigu.visitor;

public class Fail extends Action {

   @Override
   public void getManResult(Man man) {
      System.out.println(" 男人给的评价该歌手失败 !");
   }

   @Override
   public void getWomanResult(Woman woman) {
      System.out.println(" 女人给的评价该歌手失败 !");
   }

}

【Person(Element)】

package com.atguigu.visitor;

public abstract class Person {

    /**
     * 提供一个方法,让访问者可以访问
     *
     * @param action
     */
    public abstract void accept(Action action);
}

【Woman(ConcreteElement )】

package com.atguigu.visitor;

/**
 * 这里我们使用到了双分派, 即首先在客户端程序中,将具体状态作为参数传递到Woman中(第一次分派)
 * 然后 Woman 类调用作为参数的 "具体方法" 中方法getWomanResult, 同时将自己(this)作为参数传入,完成第二次的分派
 * 即互为对方方法的参数
 */
public class Woman extends Person{

   @Override
   public void accept(Action action) {
      action.getWomanResult(this);
   }

}

【Man(ConcreteElement )】

package com.atguigu.visitor;

public class Man extends Person {

   @Override
   public void accept(Action action) {
      // 自己认为是什么结果就是什么结果
      action.getManResult(this);
   }

}

【ObjectStructure】

package com.atguigu.visitor;

import java.util.LinkedList;
import java.util.List;

/**
 * 数据结构,管理很多人(Man , Woman)
 */
public class ObjectStructure {

    /**
     * 维护了一个集合
     */
    private List<Person> persons = new LinkedList<>();

    /**
     * 将元素增加到list,在访问者模式中,一般使用attach,不使用add
     *
     * @param p
     */
    public void attach(Person p) {
        persons.add(p);
    }

    /**
     * 移除
     *
     * @param p
     */
    public void detach(Person p) {
        persons.remove(p);
    }

    /**
     * 显示测评情况
     * @param action
     */
    public void display(Action action) {
        for (Person p : persons) {
            p.accept(action);
        }
    }
}

【客户端】

package com.atguigu.visitor;

public class Client {

   public static void main(String[] args) {
      //创建ObjectStructure
      System.out.println("=======添加观众========");
      ObjectStructure objectStructure = new ObjectStructure();
      objectStructure.attach(new Man());
      objectStructure.attach(new Woman());

      //成功
      System.out.println("=======测评结果是成功晋级========");
      Success success = new Success();
      objectStructure.display(success);

      System.out.println("=======测评结果是失败========");
      Fail fail = new Fail();
      objectStructure.display(fail);
   }

}

【运行】

=======添加观众========
=======测评结果是成功晋级========
 男人给的评价该歌手很成功 !
 女人给的评价该歌手很成功 !
=======测评结果是失败========
 男人给的评价该歌手失败 !
 女人给的评价该歌手失败 !

Process finished with exit code 0

拓展

上面的程序使用了双重分发,所谓双重分发是指不管类怎么变化,我们都能找到期望的方法运行。双重分发意味着得到执行的操作取决于请求的种类和两个接收者的类型。假设我们要添加一个Wait的状态类,考察Man类和Woman类的反应,由于使用了双重分发,只需增加一个Action子类即可在客户端调用即可,不需要改动任何其他类的代码

【增加类:Wait】

package com.atguigu.visitor;

public class Wait extends Action {

   @Override
   public void getManResult(Man man) {
      System.out.println(" 男人给的评价是该歌手待定 ..");
   }

   @Override
   public void getWomanResult(Woman woman) {
      System.out.println(" 女人给的评价是该歌手待定 ..");
   }

}

【客户端】

package com.atguigu.visitor;

public class Client {

   public static void main(String[] args) {
      //创建ObjectStructure
      System.out.println("=======添加观众========");
      ObjectStructure objectStructure = new ObjectStructure();
      objectStructure.attach(new Man());
      objectStructure.attach(new Woman());

      System.out.println("=======测评结果是待定========");
      Wait wait = new Wait();
      objectStructure.display(wait);
   }

}

【运行】

=======添加观众========
=======测评结果是待定========
 男人给的评价是该歌手待定 ..
 女人给的评价是该歌手待定 ..

Process finished with exit code 0

案例二(个人感觉这个案例较好)

在这里插入图片描述

实现

【访问者抽象类】

package com.atguigu.visitor.Sample;

/**
 * 访问者抽象类
 * 依赖要访问的数据结构,File和Directory
 */
public abstract class Visitor {
    /**
     * 访问File类的方法
     *
     * @param file
     */
    public abstract void visit(File file);

    /**
     * 访问Directory类的方法
     *
     * @param directory
     */
    public abstract void visit(Directory directory);
}

【接受访问的接口】

package com.atguigu.visitor.Sample;

/**
 * 接受访问的接口
 */
public interface Element {
    /**
     * 接受访问
     *
     * @param v
     */
    public abstract void accept(Visitor v);
}

【接受访问的抽象类】

这个类不需要实现accept方法,因为不是最终被访问的类

package com.atguigu.visitor.Sample;

import java.util.Iterator;

public abstract class Entry implements Element {
    /**
     * 获取名字
     *
     * @return
     */
    public abstract String getName();

    /**
     * 获取大小
     *
     * @return
     */
    public abstract int getSize();

    /**
     * 增加目录条目
     *
     * @param entry
     * @return
     * @throws FileTreatmentException
     */
    public Entry add(Entry entry) throws FileTreatmentException {
        // 对 Directory 才有效,这里先简单报个错,让它自己重写一遍
        throw new FileTreatmentException();
    }

    /**
     * 生成Iterator
     *
     * @return
     * @throws FileTreatmentException
     */
    public Iterator iterator() throws FileTreatmentException {
        // 对 Directory 才有效,这里先简单报个错,让它自己重写一遍
        throw new FileTreatmentException();
    }

    /**
     * 显示字符串
     *
     * @return
     */
    public String toString() {
        return getName() + " (" + getSize() + ")";
    }
}

【异常类】

package com.atguigu.visitor.Sample;

public class FileTreatmentException extends RuntimeException {
    public FileTreatmentException() {
    }

    public FileTreatmentException(String msg) {
        super(msg);
    }
}

【接受访问的具体类:File(ConcreteElement角色)】

package com.atguigu.visitor.Sample;

public class File extends Entry {
    private String name;
    private int size;

    public File(String name, int size) {
        this.name = name;
        this.size = size;
    }

    public String getName() {
        return name;
    }

    public int getSize() {
        return size;
    }

    public void accept(Visitor v) {
        // 把自己交给访问者访问
        v.visit(this);
    }
}

【接受访问的具体类:Directory(ConcreteElement角色、ObjectStructure角色)】

package com.atguigu.visitor.Sample;

import java.util.ArrayList;
import java.util.Iterator;

public class Directory extends Entry {
    /**
     * 文件夹名字
     */
    private String name;
    /**
     * 目录条目集合
     */
    private ArrayList dir = new ArrayList();

    /**
     * 构造函数
     * @param name
     */
    public Directory(String name) {
        this.name = name;
    }

    /**
     * 获取名字
     * @return
     */
    public String getName() {
        return name;
    }

    /**
     * 获取大小
     * @return
     */
    public int getSize() {
        int size = 0;
        Iterator it = dir.iterator();
        while (it.hasNext()) {
            Entry entry = (Entry) it.next();
            size += entry.getSize();
        }
        return size;
    }

    /**
     * 增加目录条目
     * @param entry
     * @return
     */
    public Entry add(Entry entry) {
        dir.add(entry);
        return this;
    }

    /**
     * 生成Iterator
     * @return
     */
    public Iterator iterator() {
        return dir.iterator();
    }

    /**
     * 接受访问者的访问
     * @param v
     */
    public void accept(Visitor v) {
        v.visit(this);
    }
}

【具体访问者】

package com.atguigu.visitor.Sample;

import java.util.Iterator;

public class ListVisitor extends Visitor {
    /**
     * 当前访问的文件夹的名字
     */
    private String currentdir = "";

    /**
     * 在访问文件时被调用
     * 访问的是文件,就简单输出一下,文件的信息
     * @param file
     */
    public void visit(File file) {
        System.out.println(currentdir + "/" + file);
    }

    /**
     * 在访问文件夹时被调用
     * 访问的是文件夹,不仅输出文件夹的信息,还要递归输出子文件和子文件夹的相关信息
     * @param directory
     */
    public void visit(Directory directory) {
        System.out.println(currentdir + "/" + directory);
        String savedir = currentdir;
        currentdir = currentdir + "/" + directory.getName();
        Iterator it = directory.iterator();
        while (it.hasNext()) {
            Entry entry = (Entry) it.next();
            // 继续让当前访问者访问子文件或者文件夹
            entry.accept(this);
        }
        currentdir = savedir;
    }
}

【主类】

package com.atguigu.visitor.Sample;

public class Main {
    public static void main(String[] args) {
        try {
            System.out.println("Making root entries...");
            Directory rootdir = new Directory("root");
            Directory bindir = new Directory("bin");
            Directory tmpdir = new Directory("tmp");
            Directory usrdir = new Directory("usr");
            rootdir.add(bindir);
            rootdir.add(tmpdir);
            rootdir.add(usrdir);
            bindir.add(new File("vi", 10000));
            bindir.add(new File("latex", 20000));
            // 接受访问,打印整个根目录下面的所有文件信息
            rootdir.accept(new ListVisitor());              

            System.out.println("");
            System.out.println("Making user entries...");
            Directory yuki = new Directory("yuki");
            Directory hanako = new Directory("hanako");
            Directory tomura = new Directory("tomura");
            usrdir.add(yuki);
            usrdir.add(hanako);
            usrdir.add(tomura);
            yuki.add(new File("diary.html", 100));
            yuki.add(new File("Composite.java", 200));
            hanako.add(new File("memo.tex", 300));
            tomura.add(new File("game.doc", 400));
            tomura.add(new File("junk.mail", 500));
            // 接受访问,打印整个根目录下面的所有文件信息
            rootdir.accept(new ListVisitor());              
        } catch (FileTreatmentException e) {
            e.printStackTrace();
        }
    }
}

【运行】

Making root entries...
/root (30000)
/root/bin (30000)
/root/bin/vi (10000)
/root/bin/latex (20000)
/root/tmp (0)
/root/usr (0)

Making user entries...
/root (31500)
/root/bin (30000)
/root/bin/vi (10000)
/root/bin/latex (20000)
/root/tmp (0)
/root/usr (1500)
/root/usr/yuki (300)
/root/usr/yuki/diary.html (100)
/root/usr/yuki/Composite.java (200)
/root/usr/hanako (300)
/root/usr/hanako/memo.tex (300)
/root/usr/tomura (900)
/root/usr/tomura/game.doc (400)
/root/usr/tomura/junk.mail (500)

Process finished with exit code 0

分析

  • ConcreteVisitor 角色的开发可以独立于File类和Directory类。也就是说,Visitor模式提高了File类和Directory类作为组件的独立性。如果将进行处理的方法定义在File类和Directory类中,当每次要扩展功能,增加新的“处理”时,就不得不去修改File类和Directory类

拓展一

在示例程序中增加一个FileFindvistor类,用于将带有指定后缀名的文件手机起来,存储到集合中

【FileFindvistor】

package com.atguigu.visitor.A1;

import java.util.ArrayList;
import java.util.Iterator;

public class FileFindVisitor extends Visitor {
    private String filetype;
    private ArrayList found = new ArrayList();

    /**
     * 指定.后面的文件后缀名,如".txt"
     *
     * @param filetype
     */
    public FileFindVisitor(String filetype) {
        this.filetype = filetype;
    }

    /**
     * 获取已经找到的文件
     *
     * @return
     */
    public Iterator getFoundFiles() {
        return found.iterator();
    }

    /**
     * 在访问文件时被调用
     *
     * @param file
     */
    public void visit(File file) {
        if (file.getName().endsWith(filetype)) {
            // 将符合格式的文件,添加到集合中
            found.add(file);
        }
    }

    /**
     * 在访问文件夹时被调用
     *
     * @param directory
     */
    public void visit(Directory directory) {
        Iterator it = directory.iterator();
        while (it.hasNext()) {
            Entry entry = (Entry) it.next();
            entry.accept(this);
        }
    }
}

【主类】

package com.atguigu.visitor.A1;

import java.util.Iterator;

public class Main {
    public static void main(String[] args) {
        try {
            Directory rootdir = new Directory("root");
            Directory bindir = new Directory("bin");
            Directory tmpdir = new Directory("tmp");
            Directory usrdir = new Directory("usr");
            rootdir.add(bindir);
            rootdir.add(tmpdir);
            rootdir.add(usrdir);
            bindir.add(new File("vi", 10000));
            bindir.add(new File("latex", 20000));

            Directory yuki = new Directory("yuki");
            Directory hanako = new Directory("hanako");
            Directory tomura = new Directory("tomura");
            usrdir.add(yuki);
            usrdir.add(hanako);
            usrdir.add(tomura);
            yuki.add(new File("diary.html", 100));
            yuki.add(new File("Composite.java", 200));
            hanako.add(new File("memo.tex", 300));
            hanako.add(new File("index.html", 350));
            tomura.add(new File("game.doc", 400));
            tomura.add(new File("junk.mail", 500));

            // 筛选出.html结尾的文件
            FileFindVisitor ffv = new FileFindVisitor(".html");     
            rootdir.accept(ffv);
            // 输出.html结尾的文件
            System.out.println("HTML files are:");
            Iterator it = ffv.getFoundFiles();                      
            while (it.hasNext()) {                                  
                File file = (File)it.next();                        
                System.out.println(file.toString());
            }                                                       
        } catch (FileTreatmentException e) {
            e.printStackTrace();
        }
    }
}

【运行】

HTML files are:
diary.html (100)
index.html (350)

Process finished with exit code 0

拓展二

Directory类的getSize方法的作用是获取文件夹大小,请编写一个获取大小的SizeVisitor类,用它替换掉 Directory类的getSize方法

【SizeVisitor】

import java.util.Iterator;

public class SizeVisitor extends Visitor {
    private int size = 0;

    public int getSize() {
        return size;
    }

    public void visit(File file) {
        size += file.getSize();
    }

    public void visit(Directory directory) {
        Iterator it = directory.iterator();
        while (it.hasNext()) {
            Entry entry = (Entry) it.next();
            entry.accept(this);
        }
    }
}

【修改Directory的方法】

package com.atguigu.visitor.A2;

import java.util.ArrayList;
import java.util.Iterator;

public class Directory extends Entry {
    private String name;
    private ArrayList dir = new ArrayList();

    public Directory(String name) {         // 构造函数
        this.name = name;
    }

    public String getName() {               // 获取名字
        return name;
    }

    public int getSize() {
        // 使用visitor来替换原来的方式
        SizeVisitor v = new SizeVisitor();
        accept(v);
        return v.getSize();
    }

    public Entry add(Entry entry) {
        dir.add(entry);
        return this;
    }

    public Iterator iterator() {
        return dir.iterator();
    }

    public void accept(Visitor v) {
        v.visit(this);
    }
}

拓展三

基于java.util.ArrayList类编写一个具有Element接口的ElementArrayList类,使得Directory类和File类可以被add至ElementArrayList 中,而且它还可以接受(accept) ListVisitor 的实例访问它

【ElementArrayList】

package com.atguigu.visitor.A3;

import java.util.ArrayList;
import java.util.Iterator;

/**
 * 继承ArrayList,这样就不用定义集合的add remove等操作
 */
class ElementArrayList extends ArrayList implements Element {
    public void accept(Visitor v) {
        // 使用迭代器遍历
        Iterator it = iterator();
        while (it.hasNext()) {
            Element e = (Element)it.next();
            e.accept(v);
        }
    }
}

由于visit方法不用传入ElementArrayList类作为参数,因此不用修改Visitor

【主类】

package com.atguigu.visitor.A3;

public class Main {
    public static void main(String[] args) {
        try {
            Directory root1 = new Directory("root1");
            root1.add(new File("diary.html", 10));
            root1.add(new File("index.html", 20));

            Directory root2 = new Directory("root2");
            root2.add(new File("diary.html", 1000));
            root2.add(new File("index.html", 2000));

            ElementArrayList list = new ElementArrayList();
            list.add(root1);
            list.add(root2);
            list.add(new File("etc.html", 1234));

            list.accept(new ListVisitor());
        } catch (FileTreatmentException e) {
            e.printStackTrace();
        }
    }
}

【运行】

/root1 (30)
/root1/diary.html (10)
/root1/index.html (20)
/root2 (3000)
/root2/diary.html (1000)
/root2/index.html (2000)
/etc.html (1234)

Process finished with exit code 0

总结

【优点】

  • 访问者模式符合单一职责原则、让程序具有优秀的扩展性、灵活性非常高
  • 访问者模式可以对功能进行统一,可以做报表、UI、拦截器与过滤器,适用于数据结构相对稳定的系统
  • 如果一个系统有比较稳定的数据结构,又有经常变化的功能需求,那么访问者模式就是比较合适的
  • 易于增加 ConcreteVisitor 角色

【缺点】

  • 具体元素对访问者公布细节,也就是访问者关注其他类的内部细节(如Success里面传入了Man,且调用其accept方法),这是迪米特法则不建议的,这样造成了具体元素变更比较困难
  • 违背了依赖倒转原则。访问者依赖的是具体元素,而不是抽象元素(Action里面依赖的是Man和Woman,而不是Person)
  • 难以增加ConcreteElement 角色。一旦增加了ConcreteElement 角色,需要在Visitor类中声明新的visit方法,而且所有的ConcreteVisitor都需要实现这个方法

额外知识

双重分发

// accept (接受)方法的调用方式
element.accept(visitor);

// visit(访问)方法的调用方式
visitor.visit(element);

ConcreteElementConcreteVisitor这两个角色互相调用共同决定了实际进行的处理

文章说明

  • 本文章为本人学习尚硅谷的学习笔记,文章中大部分内容来源于尚硅谷视频(点击学习尚硅谷相关课程),也有部分内容来自于自己的思考,发布文章是想帮助其他学习的人更方便地整理自己的笔记或者直接通过文章学习相关知识,如有侵权请联系删除,最后对尚硅谷的优质课程表示感谢。
  • 本人还同步阅读《图解设计模式》书籍(图解设计模式/(日)结城浩著;杨文轩译–北京:人民邮电出版社,2017.1),进而综合两者的内容,让知识点更加全面

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

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

相关文章

MySQL数据库——多表操作

文章目录 前言多表关系一对一关系一对多/多对一关系多对多关系 外键约束创建外键约束插入数据删除带有外键约束的表的数据删除外键约束 多表联合查询数据准备交叉连接查询内连接查询外连接查询左外连接查询右外连接查询满外连接查询 子查询子查询关键字ALL 关键字ANY 和 SOME 关…

2023年华数杯数学建模A题思路代码分析 - 隔热材料的结构优化控制研究

# 1 赛题 A 题 隔热材料的结构优化控制研究 新型隔热材料 A 具有优良的隔热特性&#xff0c;在航天、军工、石化、建筑、交通等 高科技领域中有着广泛的应用。 目前&#xff0c;由单根隔热材料 A 纤维编织成的织物&#xff0c;其热导率可以直接测出&#xff1b;但是 单根隔热…

详解Spring Bean的生命周期

详解Spring Bean的生命周期 Spring Bean的生命周期包括以下阶段&#xff1a; 1. 实例化Bean 对于BeanFactory容器&#xff0c;当客户向容器请求一个尚未初始化的bean时&#xff0c;或初始化bean的时候需要注入另一个尚未初始化的依赖时&#xff0c;容器就会调用createBean进…

剑指Offer 05.替换空格

剑指Offer 05.替换空格 目录 剑指Offer 05.替换空格05.替换空格题目代码&#xff08;容易想到的&#xff09;利用库函数的方法题解&#xff08;时间复杂度更低&#xff09;面试&#xff1a;为什么java中String类型是不可变的 05.替换空格 题目 官网题目地址 代码&#xff08;…

【Python小笔记】零碎同步

1.多字段连接&#xff0c;连接字段名不一致–left_on\right_on对应列示后可匹配 import pandas as pd df_A1pd.read_excel(E:\Mercy\data\mytest\A.xlsx,sheet_name0) df_A2pd.read_excel(E:\Mercy\data\mytest\A.xlsx,sheet_name1)df_Adf_A1.merge(rightdf_A2,howleft,left_o…

P1775 石子合并(弱化版)(内附封面)

石子合并&#xff08;弱化版&#xff09; 题目描述 设有 N ( N ≤ 300 ) N(N \le 300) N(N≤300) 堆石子排成一排&#xff0c;其编号为 1 , 2 , 3 , ⋯ , N 1,2,3,\cdots,N 1,2,3,⋯,N。每堆石子有一定的质量 m i ( m i ≤ 1000 ) m_i\ (m_i \le 1000) mi​ (mi​≤1000)。…

信号执行流程

信号执行是一种用户态与内核态和来回切换&#xff0c;进程不会一接收到信号&#xff0c;就立刻执行&#xff0c;而是在合适的时候执行信号&#xff0c;&#xff08;手头有重要的事情等等再说。 一般来说都是在从内核态返回用户态的时候检测是否有可执行的信号&#xff08;可执…

超详细|ChatGPT辅助论文降重教程100%降至13%

本文讲述使用ChatGPT对论文进行辅助降重&#xff0c;鼓励大家解放大脑&#xff0c;多思考核心论点 祝看到本教程的小伙伴们都完成论文&#xff0c;顺利毕业。 可以加QQ群交流&#xff0c;一群&#xff1a; 123589938 第一章 ChatGPT指令 1.1 同义词替换 对比分析&#xff0c;…

【MySQL】删除重复数据,先进先删

系列文章 C#底层库–MySQLBuilder脚本构建类&#xff08;select、insert、update、in、带条件的SQL自动生成&#xff09; 本文链接&#xff1a;https://blog.csdn.net/youcheng_ge/article/details/129179216 C#底层库–MySQL数据库操作辅助类&#xff08;推荐阅读&#xff0…

C语言之结构体篇(简)

结构体 结构体的认知结构体的声明一般声明特殊声明匿名结构体类型 结构体自引用结构体变量的定义与初始化结构体变量的定义结构体变量的初始化 结构体传参结构体内存对齐位段位段声明位段的内存分配位段跨平台问题: 结构体是由我们自己创造的一种类型&#xff0c;使得C语言有能…

kafka权威指南(阅读摘录)

零复制 Kafka 使用零复制技术向客户端发送消息——也就是说&#xff0c;Kafka 直接把消息从文件&#xff08;或者更确切地说是 Linux 文件系统缓存&#xff09;里发送到网络通道&#xff0c;而不需要经过任何中间缓冲区。这是 Kafka 与其他大部分数据库系统不一样的地方&#…

归并排序——“数据结构与算法”

各位CSDN的uu们好呀&#xff0c;今天&#xff0c;小雅兰的内容仍然是数据结构与算法专栏的排序呀&#xff0c;下面&#xff0c;让我们进入归并排序的世界吧&#xff01;&#xff01;&#xff01; 归并排序 归并排序&#xff08;MERGE-SORT&#xff09;是建立在归并操作上的一种…

[腾讯云 Cloud studio 实战训练营] 制作Scrapy Demo爬取起点网月票榜小说数据

首语 最近接触到了一个关于云开发的IDE&#xff0c;什么意思呢&#xff1f; 就是我们通常开发不是在电脑上吗&#xff0c;既要下载编译器&#xff0c;还要下载合适的编辑器&#xff0c;有的时候甚至还需要配置开发环境&#xff0c;有些繁琐。而这个云开发的IDE就是只需要一台…

“苏豪 x 莱佛士”再度携手,惊艳亮相上海发型师节!

2023年6月28日&#xff0c;以“为爱启航”为主题的第16届AHF亚洲发型师节在上海跨国采购中心盛大开幕。继上次在施华蔻专业2023春夏新季风发布会上&#xff0c;苏豪x莱佛士合作的大秀&#xff0c;赢得了现场观众阵阵掌声。这次“Kraemer苏豪x莱佛士”再度携手&#xff0c;惊艳亮…

PoseiSwap:基于 Nautilus Chain ,构建全新价值体系

在 DeFi Summer 后&#xff0c;以太坊自身的弊端不断凸显&#xff0c;而以 Layer2 的方式为其扩容成为了行业很长一段时间的叙事方向之一。虽然以太坊已经顺利的从 PoW 的 1.0 迈向了 PoS 的 2.0 时代&#xff0c;但以太坊创始人 Vitalik Buterin 表示&#xff0c; Layer2 未来…

MySQL常见问题处理(三)

MySQL 常见问题解决 夕阳留恋的不是黄昏&#xff0c;而是朝阳 上一章简单介绍了MySQL数据库安装(二), 如果没有看过, 请观看上一章 一. root 用户密码忘记&#xff0c;进行重置操作 复制内容来源链接: https://blog.csdn.net/weixin_48927364/article/details/123556927 一.…

【MFC]实现16进制文件浏览器-详细步骤+代码

学习MFC已经两天了&#xff0c;我们来写一个小项目&#xff1a;16进制文件浏览器&#xff0c;简单实现&#xff0c;因为我们MFC学的还不是很透彻&#xff0c;这里会给出详细的每一个步骤&#xff0c;并且详细解释每一个方法&#xff1a; 文章目录 提前了解步骤&#xff1a;基本…

Apache poi 对单元格进行合并

需求背景: 在导出excel时, 需要对内容相同的单元格进行纵向合并 期望达到的效果: poi 实现合并单元格的方法 sheet.addMergedRegion(new CellRangeAddress(开始行, 结束行, 开始列, 结束列)); 个人的实现思路: 1): 举个列子, 就拿截图贴出的 [公司类别] 这一列来进行说明 …

【QT学习】01:helloqt

helloqt OVERVIEW helloqt一、helloqt1.使用向导创建2.手动创建3.pro文件4.Qt应用程序框架 二、按钮创建main.cppmywidget.cpp 三、对象模型1.对象树引入2.存在的问题 一、helloqt 创建一个qt项目&#xff0c;可以使用creator的向导创建&#xff0c;也可自己手动创建&#xff…

企业如何搭建矩阵内容,才能真正实现目的?

当下&#xff0c;新媒体矩阵营销已成为众多企业的营销选择之一&#xff0c;各企业可以通过新媒体矩阵实现扩大品牌声量、维持用户关系、提高销售业绩等不同的目的。 而不同目的的矩阵&#xff0c;它的内容运营模式会稍有差别&#xff0c;评价体系也会大不相同。 企业在运营某类…