Android 面试题 线程间通信 六

🔥 主线程向子线程发送消息 Thread+handler🔥

子线程中定义Handler,Handler定义在哪个线程中,就跟那个线程绑定,在线程中绑定Handler需要调用Looper.prepare(); 方法,主线程中不调用是因为主线程默认帮你调用了 : 

 

public class LoopThread implements Runnable {  
  
    public Handler mHandler = null;  
  
    @Override  
    public void run() {  
        Looper.prepare();  
        mHandler = new Handler() {  
            public void handleMessage(Message msg) {  
                String result = NetUtil.getJsonContent("北京");  
                //完成了获取北京天气的操作;  
                Log.i("test", "handler"+result);  
            }  
        };  
        Looper.loop();  
    }  
  
} 

其中Looper.prepare();和Looper.loop();维护了一个消息队列,等待消息注入并在子线程中执行;

主线程中这样调用

lThread.mHandler.sendEmptyMessage(0);   

主线程向子线程发消息,让子线程执行指定的操作,在Android中还有一种方法,即:HandlerThread,看下面的例子:

HandlerThread handlerThread = new HandlerThread("jerome");  
handlerThread.start();  
  
/** 
 * 这里要将HandlerThread创建的looper传递给threadHandler,即完成绑定; 
 */  
threadHandler = new Handler(handlerThread.getLooper()) {  
  
    @Override  
    public void handleMessage(Message msg) {  
        super.handleMessage(msg);  
        switch (msg.what) {  
        case 0:  
这儿可以做耗时的操作;  
            Log.i("jerome", "hello,I am sub thread");  
            break;  
        default:  
            break;  
        }  
    }  
};  

🔥 子线程向主线程发送消息 Thread+handler 🔥 

主线程中定义Handler: 

Handler mHandler = new Handler(){  
  
    @Override  
    public void handleMessage(Message msg) {  
        super.handleMessage(msg);  
        switch (msg.what) {  
        case 0:  
            //do something,refresh UI;  
            break;  
        default:  
            break;  
        }  
    }  
      
};

子线程处理完耗时操作之后发消息给主线程,更新UI: 

mHandler.sendEmptyMessage(0);  

这样在子线程与主线程任务分工的条件下完成了消息交互;

🔥 子线程向子线程发送消息 🔥 

创建线程 ThreadA 用来接受线程 ThreadB 传递的消息

class ThreadA implements Runnable{
        private Handler mHandler;
        //run运行后才不为null在main里判断
        public Handler getHandler(){
            return mHandler;
        }
 
        @SuppressLint("HandlerLeak")
        @Override
        public void run() {
            Looper.prepare();
            mHandler=new Handler(){
                @Override
                public void handleMessage(Message msg) {
                    super.handleMessage(msg);
                    switch (msg.what){
                        case 1:
                            Log.e("线程A","线程B发过来消息了--"+msg.obj);
                            break;
                    }
                }
            };
            Looper.loop();
        }
    }

创建线程 ThreadB 发送消息到线程 ThreadA 中 

class ThreadB implements Runnable{
 
        @Override
        public void run() {
                Message mess=Message.obtain();
                mess.what=1;
                mess.obj= "线程B"+System.currentTimeMillis();
                handler.sendMessage(mess);
        }
    }

在activity onCreate方法里 , 分别调用线程 ThreadA 和 ThreadB

        ThreadA threadA = new ThreadA();
        ThreadB threadb = new ThreadB();
        new Thread(threadA).start();
        if(threadA.getHandler() == null) {
            try {
                Thread.sleep(1000);
                handler = threadA.getHandler();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        new Thread(threadb).start();

🔥 runOnUiTherd() 🔥

主线程主要来完成UI绘制和响应用户的操作 , 大多数情况都习惯在 onCreate()、onResume()、onCreateView() 中启动我们的逻辑 ,  导致代码运行在主线程中,容易导致ANR(Application Not Responding),这些逻辑包括文件读写, 数据库读写, 网络查询等。

开启一个子线程来完成一个耗时操作,以避免阻塞主线程而出现卡顿甚至ANR导致闪退。

子线程执行完要更新UI的时候,我们又必须回到主线程来更新,实现这一功能常用的方法是执行
Activity的runOnUiThread() 方法:

runOnUiThread(new Runnable() {
     void run() {
         // do something
     }
});

Fragment/Presentation/Dialog中使用:

((MainActivity)getActivity()).runOnUiThread(new Runnable() {
            @Override
            public void run() {
                //在此进行更新UI的操作
            }
        });

深入理解runOnUiThread() 

 🔥 AsyncTask 🔥

一个 Android 已封装好的轻量级异步类。属于抽象类,即使用时需实现子类。

public abstract class AsyncTask<Params, Progress, Result> { 
 ... 
 }

作用 

  • 实现多线程:在工作线程中执行任务,如 耗时任务
  • 异步通信、消息传递:实现工作线程 & 主线程(UI线程)之间的通信,即:将工作线程的执行结果传递给主线程,从而在主线程中执行相关的UI操作,保证线程安全。

 优点

  • 方便实现异步通信
    不需使用 “任务线程(如继承Thread类) + Handler”的复杂组合
  • 节省资源
    采用线程池的缓存线程 + 复用线程,避免了频繁创建 & 销毁线程所带来的系统资源开销

类定义

public abstract class AsyncTask<Params, Progress, Result> { 
 ... 
}

// 类中参数为3种泛型类型
// 整体作用:控制AsyncTask子类执行线程任务时各个阶段的返回类型
// 具体说明:
    // a. Params:开始异步任务执行时传入的参数类型,对应excute()中传递的参数
    // b. Progress:异步任务执行过程中,返回下载进度值的类型
    // c. Result:异步任务执行完成后,返回的结果类型,与doInBackground()的返回值类型保持一致
// 注:
    // a. 使用时并不是所有类型都被使用
    // b. 若无被使用,可用java.lang.Void类型代替
    // c. 若有不同业务,需额外再写1个AsyncTask的子类
}

核心方法  execute()

作用 : 触发执行异步线程任务

调用时刻 : 手动调用

使用场景 : 必须在UI线程调用 ,  运行在主线程

核心方法  onPreExecute()

作用 : 执行 线程任务前的任务操作

调用时刻 : 执行 线程任务前 自动调用 , 即 execute() 执行前调用

使用场景 : 用于界面的初始化操作 , 如 : 显示进度条的对话框 

核心方法  doInBackground()

作用 : 接收输入参数 , 执行任务中的耗时操作 , 返回任务中执行的结果 

调用时刻 : 执行 线程任务时 自动调用 , 即 onPreExecute() 执行后 自动调用

使用场景 :  不能更改UI组件的信息 , 执行过程中可以调用 publishProgress() 更新进度信息

核心方法  onProgressUpdate()

作用 : 在主线程中显示线程任务执行的进度

调用时刻 : 调用publishProgress(Progress ... values)  时 自动调用

核心方法  onPostExecute()

作用 : 接受线程任务执行的结果 ,  将执行结果显示到UI组件上

调用时刻 : 线程任务结束时 自动调用

 核心方法  onCancelled()

作用 : 将异步任务设置为取消状态

调用时刻 : 异步任务被取消时  即自动调用

使用场景 : 该方法调用时 onPostExecute() 就不会被调用

 🔥 View.post 🔥 

View.post方法可以在UI线程上安排一个Runnable,确保在某些情况下,如视图大小改变后,执行特定操作。然而,使用View.post时,可能会遇到一些问题。以下是一些可能遇到的坑及其解决方法:

生命周期问题:在使用View.post时,如果Activity或Fragment的生命周期发生变化,如onDestroy或onDetach,可能导致内存泄漏。解决方法是在生命周期方法中移除所有已post的Runnable。

@Override
protected void onDestroy() {
    super.onDestroy();
    yourView.removeCallbacks(yourRunnable);
}
  1. 延迟执行:View.post可能会在稍后执行Runnable,这意味着如果您希望立即执行操作,可能会遇到问题。解决方法是使用View.post()之外的其他方法,如在onLayout或onSizeChanged中执行操作。
  2. 视图不可见:如果使用View.post时视图不可见(例如,它已被隐藏或从窗口分离),Runnable可能无法执行。解决方法是在执行操作前检查视图的可见性和附加状态。
  3. 多次执行:如果使用View.post多次调度相同的Runnable,可能会导致多次执行。为避免这种情况,请在调度新Runnable之前移除旧的Runnable。
  4. 视图未初始化:View.post在视图初始化之前可能无法正常工作。解决方法是确保在视图完全初始化之后再调用View.post。

总之,在使用View.post时要注意生命周期问题、视图可见性、多次执行以及视图初始化等潜在问题。了解这些问题并采取相应的解决方法,可以帮助您更有效地使用View.post

view.post()主要有两个作用:更新UI、获取view的实际宽高。 

🔥 View.postDelayed() 🔥 

UI控件延迟显示 View.postDelayed()  

@SuppressLint("NewApi")
public class TestFragment extends DialogFragment {
    private Button mButton;
    //默认初始值为true
    private boolean isSucess = true;
 
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
 
        //NetworkUtil.requestNet是开启了一个异步线程做请求任务
        //执行网络请求,根据返回成功与否(true or false) 来设定 button上的文字
        //如下为 伪代码
        isSucess = NetworkUtil.requestNet();
    }
 
    @Nullable
    @Override
    public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, Bundle savedInstanceState) {
        super.onCreateView(inflater, container, savedInstanceState);
        View viewLayout = inflater.inflate(R.layout.fragment_test, container);
        mButton = viewLayout.findViewById(R.id.button2);
        return viewLayout;
    }
 
    @Override
    public void onResume() {
        super.onResume();
        //存在bug的代码
       /* if (isSucess) {
            mButton.setText("返回结果成功");
        } else {
            mButton.setText("返回结果失败");
        }*/
 
        //可以用 View.postDelayed(Runnable action, long delayMillis)方法来解决此问题
        mButton.postDelayed(new Runnable() {
            @Override
            public void run() {
                if (isSucess) {
                    mButton.setText("返回结果成功");
                } else {
                    mButton.setText("返回结果失败");
                }
            }
        }, 1000); //这里延时时间根据网络环境的好坏设置
    }
}

 自定义view中,postDelayed执行失败

private boolean mAttached;

@Override
protected void onAttachedToWindow() {
      super.onAttachedToWindow();  
      mAttached = true;
}

 @Override
protected void onDetachedFromWindow() {
      mAttached = false;
      super.onDetachedFromWindow();
}

public void show() {
        //....
        if (mAttached) {
              postDelayed(mDelayedShow, MIN_DELAY);    
        } else {
              Handler handler = new Handler();
              handler.post(mDelayedShow);
        }
}

 🔥 线程间通信  eventbus 🔥

优点 : 

  • 简化组件之间的通信

  • 体积小

  • 将事件的发送者和接受者分离

  • 在activity fragment 线程之间性能优良

  • 避免复杂且容易出错的依赖关系和生命周期问题

  • 代码简单方便

粘性广播 

粘性广播有什么作用?怎么使用? 粘性广播主要为了解决,在发送完广播之后,动态注册的接收者,也能够收到广播。

举个例子首先发送一广播,我的接收者是通过程序中的某个按钮动态注册的。

如果不是粘性广播,我注册完接收者肯定无法收到广播了。

这是通过发送粘性广播就能够在我动态注册接收者后也能收到广播

EeventBus 粘性事件和普通事件的区别?

StickyEvent与普通Event的普通就在于,EventBus会自动维护被作为StickyEvent被post出来(即在发布事件时使用EventBus.getDefault().postSticky(new MyEvent())方法)的事件的最后一个副本在缓存中。

任何时候在任何一个订阅了该事件的订阅者中的任何地方(可以在任何函数中,而不仅仅是在onEventXXX方法中),都可以通过EventBus.getDefault().getStickyEvent(MyEvent.class)来取得该类型事件的最后一次缓存。

同时,即便事件已经在所有订阅者中传递完成了,如果此时再创建一个新的订阅者(如一个注册了该StickyEvent的Activity),则在订阅者启动后,会自动调用一次该订阅者的noEventXXX方法来处理该StickyEvent。也可以在需要的时候,利用removeStickyEvent方法来移除对某种StickyEvent的缓存。

Android Studio 配置的module的gradle内:

compile 'org.greenrobot:eventbus:3.0.0'

 定义一个消息事件 : 

public class User {
 
    private String name;
    private String pass;
 
    public User(String name, String pass) {
        this.name = name;
        this.pass = pass;
    }
 
    public String getName() {
        return name;
    }
 
    public void setName(String name) {
        this.name = name;
    }
 
    public String getPass() {
        return pass;
    }
 
    public void setPass(String pass) {
        this.pass = pass;
    }
 
    @Override
    public String toString() {
        return "User{" +
                "name='" + name + '\'' +
                ", pass='" + pass + '\'' +
                '}';
    }
}

普通事件(a--->b---值-->a),相当于数据回传

 1、注册和取消订阅事件(PuTong1Activity .java):

public class PuTong1Activity extends AppCompatActivity implements View.OnClickListener {
 
    private Button putong1_btn;
    private TextView putong1Text;
 
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_pu_tong1);
 
        initView();
        //注册事件
        EventBus.getDefault().register(this);
    }
 
    private void initView() {
        putong1_btn = (Button) findViewById(R.id.putong1_btn);
        putong1Text = (TextView) findViewById(R.id.putong1Text);
        putong1_btn.setOnClickListener(this);
    }
 
    @Override
    public void onClick(View v) {
        startActivity(new Intent(PuTong1Activity.this,PuTong2Activity.class));
    }
   //事件订阅者处理事件
    @Subscribe(threadMode = ThreadMode.MAIN)
    public void onMoonEvent(User user){
        putong1Text.setText(user.getName()+"---接收到的值----"+user.getPass());
    }
    @Override
    protected void onDestroy() {
        super.onDestroy();
        //取消注册事件
        EventBus.getDefault().unregister(this);
    }
}

2、事件发布者发布事件PuTong2Activity 类 

public class PuTong2Activity extends AppCompatActivity implements View.OnClickListener {
 
    private Button putong2_btn;
    private EditText nameEdit;
    private EditText passEdit;
 
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_pu_tong2);
 
        initView();
    }
 
    private void initView() {
        putong2_btn = (Button) findViewById(R.id.putong2_btn);
        nameEdit = (EditText) findViewById(R.id.nameEdit);
        passEdit = (EditText) findViewById(R.id.passEdit);
        putong2_btn.setOnClickListener(this);
    }
 
    @Override
    public void onClick(View v) {
        String nameStr = nameEdit.getText().toString().trim();
        String passStr = passEdit.getText().toString().trim();
        //普通时间发送消息给putong1用post方法
        EventBus.getDefault().post(new User(nameStr,passStr));
        finish();
    }
}

粘性事件(相当于直接从a—值—>b,与intent跳转传值类似)

 1.发送黏性事件(MainActivity .java)

public class MainActivity extends AppCompatActivity implements View.OnClickListener {
 
    private EditText nameEdit;
    private EditText passEdit;
    private Button login;
 
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        initView();
    }
 
    private void initView() {
        nameEdit = (EditText) findViewById(R.id.name);
        passEdit = (EditText) findViewById(R.id.pass);
        login = (Button) findViewById(R.id.login);
        login.setOnClickListener(this);
    }
 
    @Override
    public void onClick(View v) {
        String nameStr = nameEdit.getText().toString().trim();
        String passStr = passEdit.getText().toString().trim();
        if(null==nameStr&&nameStr.equals("")||null==passStr&&passStr.equals("")){
            Toast.makeText(this,"用户名密码不能为空",Toast.LENGTH_SHORT).show();
        }else{
            //2.发送消息粘性事件用postSticky
            EventBus.getDefault().postSticky(new User(nameStr,passStr));
            //跳转
            Intent intent = new Intent(MainActivity.this, ResultActivity.class);
            startActivity(intent);
        }
    }
 
}

2.订阅粘性事件(ResultActivity.java)

public class ResultActivity extends AppCompatActivity implements View.OnClickListener {
 
    private Button getResult;
    private TextView text;
    private String nameStr;
    private String passStr;
 
    //在接收消息的页面  注册EventBus
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_result);
 
        initView();
    }
 
    private void initView() {
        getResult = (Button) findViewById(R.id.getResult);
        text = (TextView) findViewById(R.id.text);
        getResult.setOnClickListener(this);
    }
 
    @Override
    public void onClick(View v) {
        //注册EventBus
        EventBus.getDefault().register(this);
 
    }
    //订阅者处理粘性事件
    @Subscribe(threadMode = ThreadMode.MAIN,sticky = true)
    public void onEventMainThread(User user) {
 
        String msg = "账号:" + user.getName()+"---密码:"+user.getPass();
        Log.d("ResultActivity", msg);
        text.setText(msg);
        Toast.makeText(ResultActivity.this, msg, Toast.LENGTH_LONG).show();
    }
    @Override
    protected void onDestroy() {
        super.onDestroy();
        //取消注册
        EventBus.getDefault().unregister(this);
    }
}

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

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

相关文章

IP 工具

什么是IP 工具 IP 工具是用于轻松扫描和排除网络 IP 地址空间故障的网络工程工具。IP 工具使网络管理员能够审核、跟踪和监视 IP 地址、子网以及使用 IP 的设备和主机的性能。这个全面的网络工程工具集包括高级 IP 工具&#xff0c;如 Ping、系统资源管理器、MAC 地址解析器和…

网格简化(QEM)学习笔记

文章目录 网格简化(QEM)1 概述与原理1.1 网格简化的应用1.2 常见的简化操作1.3 二次误差度量 2 算法流程2.1 逐步分析 3 Python代码实现3.1 测试结果 4 总结参考 网格简化(QEM) 1 概述与原理 网格简化&#xff0c;通过减少复杂网格数据的顶点、边和面的数量简化模型的表达&am…

Java版工程行业管理系统源码-专业的工程管理软件- 工程项目各模块及其功能点清单

&#xfeff; 工程项目管理软件&#xff08;工程项目管理系统&#xff09;对建设工程项目管理组织建设、项目策划决策、规划设计、施工建设到竣工交付、总结评估、运维运营&#xff0c;全过程、全方位的对项目进行综合管理 工程项目各模块及其功能点清单 一、系统管理 1、数据…

021 - STM32学习笔记 - Fatfs文件系统(三) - 细化与总结

021 - STM32学习笔记 - Fatfs文件系统&#xff08;三&#xff09; - 细化与总结 上节内容中&#xff0c;初步实现了FatFs文件系统的移植&#xff0c;并且实现了设备的挂载、文件打开/关闭与读写功能&#xff0c;这里对上节遗留的一些问题进行总结&#xff0c;并且继续完善文件…

Mybatis插件

文章目录 1. 如何自定义插件1.1 创建接口Interceptor的实现类1.2 配置拦截器1.3 运行程序 2. 插件原理2.1 解析过程2.2 创建代理对象2.2.1 Executor2.2.2 StatementHandler2.2. 3ParameterHandler2.2.4 ResultSetHandler 2.3 执行流程2.4 多拦截器的执行顺序 3. 1. 如何自定义插…

【Redis】内存数据库Redis进阶(Redis持久化)

目录 分布式缓存 Redis 四大问题Redis 持久化RDB (Redis DataBase)RDB执行时机RDB启动方式——save指令save指令相关配置save指令工作原理save配置自动执行 RDB启动方式——bgsave指令bgsave指令相关配置bgsave指令工作原理 RDB三种启动方式对比RDB特殊启动形式RDB优点与缺点 A…

Git全栈体系(三)

第六章 GitHub 操作 一、创建远程仓库 二、远程仓库操作 命令名称作用git remote -v查看当前所有远程地址别名git remote add 别名 远程地址起别名git push 别名 分支推送本地分支上的内容到远程仓库git clone 远程地址将远程仓库的内容克隆到本地git pull 远程库地址别名 远…

基于SpringCloud+Vue的分布式架构网上商城系统设计与实现(源码+LW+部署文档等)

博主介绍&#xff1a; 大家好&#xff0c;我是一名在Java圈混迹十余年的程序员&#xff0c;精通Java编程语言&#xff0c;同时也熟练掌握微信小程序、Python和Android等技术&#xff0c;能够为大家提供全方位的技术支持和交流。 我擅长在JavaWeb、SSH、SSM、SpringBoot等框架…

Spring入门-技术简介、IOC技术、Bean、DI

前言 Spring是一个开源的项目&#xff0c;并不是单单的一个技术&#xff0c;发展至今已形成一种开发生态圈。也就是说我们可以完全使用Spring技术完成整个项目的构建、设计与开发。Spring是一个基于IOC和AOP的架构多层j2ee系统的架构。 SpringFramework&#xff1a;Spring框架…

06-向量的更多术语和表示法

向量 引入的概念&#xff1a;向量就是一组有序的数字, 我们在理解它的时候&#xff0c; 可以把它理解成是一个有效的线段&#xff0c;也可以把它理解成是空间中的一个点&#xff0c;那么与之相对应的一个数字&#xff0c;也就是我们在初等数学中学的一个一个数&#xff0c;我们…

GRNN神经网络原理与matlab实现

1案例背景 1.1GRNN神经网络概述 广义回归神经网络(GRNN Generalized Regression Neural Network&#xff09;是美国学者 Don-ald F. Specht在1991年提出的,它是径向基神经网络的一种。GRNN具有很强的非线性映射能力和柔性网络结构以及高度的容错性和鲁棒性,适用于解决非线性问…

关于综合能源智慧管理系统的架构及模式规划的研究

安科瑞 华楠 摘 要&#xff1a;探讨了国内外能源互联网的研究发展&#xff0c;分析了有关综合智慧能源管理系统的定位&#xff0c;以及系统的主要特点&#xff0c;研究了综合智慧能源管理系统的构架以及模式规划。 关键词&#xff1a;综合能源&#xff1b;智慧管理系统&#…

如何在不使用脚本和插件的情况下手动删除 3Ds Max 中的病毒?

如何加快3D项目的渲染速度&#xff1f; 3D项目渲染慢、渲染卡顿、渲染崩溃&#xff0c;本地硬件配置不够&#xff0c;想要加速渲染&#xff0c;在不增加额外的硬件成本投入的情况下&#xff0c;最好的解决方式是使用渲云云渲染&#xff0c;在云端批量渲染&#xff0c;批量出结…

【PHP代码审计】ctfshow web入门 php特性 93-104

ctfshow web入门 php特性 93-104 web 93web 94web 95web 96web 97web 98web 99web 100web 101web 102web 103web 104 web 93 这段PHP代码是一个简单的源码审计例子&#xff0c;让我们逐步分析它&#xff1a; include("flag.php");: 这行代码将flag.php文件包含进来。…

从零开始学python(十二)如何成为一名优秀的爬虫工程师

前言 回顾之前讲述了python语法编程 必修入门基础和网络编程&#xff0c;多线程/多进程/协程等方面的内容&#xff0c;后续讲到了数据库编程篇MySQL&#xff0c;Redis&#xff0c;MongoDB篇&#xff0c;和机器学习&#xff0c;全栈开发&#xff0c;数据分析前面没看的也不用往…

SSL原理详解

SSL协议结构&#xff1a; SSL协议分为两层&#xff0c;下层为SSL记录协议&#xff0c;上层为SSL握手协议、SSL密码变化协议和SSL警告协议。 1.下层为SSL记录协议&#xff0c;主要作用是为高层协议提供基本的安全服务 建立在可靠的传输之上&#xff0c;负责对上层的数据进行分块…

DeepVO 论文阅读

论文信息 题目&#xff1a;DeepVO Towards End-to-End Visual Odometry with Deep Recurrent Convolutional Neural Networks 作者&#xff1a;Sen Wang, Ronald Clark, Hongkai Wen and Niki Trigoni 代码地址&#xff1a;http://senwang.gitlab.io/DeepVO/ (原作者并没有开源…

【C++】从0到1讲继承|复杂的菱形继承

个人主页&#xff1a;&#x1f35d;在肯德基吃麻辣烫 我的gitee&#xff1a;gitee仓库 分享一句喜欢的话&#xff1a;热烈的火焰&#xff0c;冰封在最沉默的火山深处。 前言 本文主要讲述的是继承的概念&#xff0c;以及基类和派生类和衍生出的各种东西&#xff0c;还有多继承…

前端代码注释率

nodejs差代码注释率 /*** author duan* source https://editor.csdn.net/md/?not_checkout1&spm1011.2124.3001.6192* date 2023-7-7* * 统计指定目录下代码行数及注释率* * 用法: node count.js <路径> [后缀名]...* 后缀名不填的话默认为统计 .js 和 .ts 文件* *…

Jenkins通过OpenSSH发布WinServer2016

上一篇文章> Jenkins集成SonarQube代码质量检测 一、实验环境 jenkins环境 jenkins入门与安装 容器为docker 主机IP系统版本jenkins10.10.10.10rhel7.5 二、OpenSSH安装 1、下载 官网地址&#xff1a;https://learn.microsoft.com/zh-cn/windows-server/administration/op…