input子系统的框架和重要数据结构详解

#1024程序员节 | 征文#
在这里插入图片描述
1

往期内容

I2C子系统专栏:

  1. 专栏地址:IIC子系统_憧憬一下的博客-CSDN博客
  2. 具体芯片的IIC控制器驱动程序分析:i2c-imx.c-CSDN博客
    – 末篇,有往期内容观看顺序

总线和设备树专栏:

  1. 专栏地址:总线和设备树_憧憬一下的博客-CSDN博客
  2. 设备树与 Linux 内核设备驱动模型的整合-CSDN博客
    – 末篇,有往期内容观看顺序

1. 回顾字符设备驱动程序

img

1. 确定主设备号

字符设备驱动需要一个主设备号来标识。你可以选择一个静态的设备号,或者使用动态分配的设备号。

#define DEVICE_NAME "my_chrdev"
#define CLASS_NAME  "my_class"
static int major;  // 保存主设备号

可以通过 register_chrdev() 函数静态注册一个设备号,或者通过 alloc_chrdev_region() 动态分配设备号。

2. 创建 file_operations 结构体

file_operations 结构体定义了设备文件操作的函数指针,如打开、读写、控制等。你需要在这里填充相关的操作函数:

static int drv_open(struct inode *inode, struct file *file) {
    printk(KERN_INFO "Device opened\n");
    return 0;
}

static int drv_release(struct inode *inode, struct file *file) {
    printk(KERN_INFO "Device closed\n");
    return 0;
}

static ssize_t drv_read(struct file *file, char __user *buf, size_t len, loff_t *offset) {
    printk(KERN_INFO "Reading from device\n");
    return 0;  // 返回读取的字节数
}

static ssize_t drv_write(struct file *file, const char __user *buf, size_t len, loff_t *offset) {
    printk(KERN_INFO "Writing to device\n");
    return len;  // 返回写入的字节数
}

static struct file_operations fops = {
    .open = drv_open,
    .release = drv_release,
    .read = drv_read,
    .write = drv_write,
};

3. 注册 file_operations 结构体

通过 register_chrdev()file_operations 注册到内核中。如果主设备号是动态分配的,需要通过 major 保存分配的设备号。

static int __init my_driver_init(void) {
    // 动态分配主设备号
    major = register_chrdev(0, DEVICE_NAME, &fops);
    if (major < 0) {
        printk(KERN_ALERT "Failed to register char device\n");
        return major;
    }
    printk(KERN_INFO "Registered char device with major number %d\n", major);

    // 创建设备类
    my_class = class_create(THIS_MODULE, CLASS_NAME);
    if (IS_ERR(my_class)) {
        unregister_chrdev(major, DEVICE_NAME);
        printk(KERN_ALERT "Failed to create class\n");
        return PTR_ERR(my_class);
    }

    // 创建设备节点
    my_device = device_create(my_class, NULL, MKDEV(major, 0), NULL, DEVICE_NAME);
    if (IS_ERR(my_device)) {
        class_destroy(my_class);
        unregister_chrdev(major, DEVICE_NAME);
        printk(KERN_ALERT "Failed to create device\n");
        return PTR_ERR(my_device);
    }

    return 0;
}

在这里,我们使用 class_create()device_create() 来创建设备类和设备节点,这样用户空间可以通过 /dev/my_chrdev 访问设备。

4. 编写入口函数

入口函数是模块加载时执行的代码,一般用于初始化设备、分配资源等。

static int __init my_driver_init(void) {
    printk(KERN_INFO "Initializing character device driver\n");

    // 注册字符设备
    major = register_chrdev(0, DEVICE_NAME, &fops);
    if (major < 0) {
        printk(KERN_ALERT "Failed to register a major number\n");
        return major;
    }
    printk(KERN_INFO "Registered with major number %d\n", major);

    // 创建类和设备节点
    my_class = class_create(THIS_MODULE, CLASS_NAME);
    if (IS_ERR(my_class)) {
        unregister_chrdev(major, DEVICE_NAME);
        printk(KERN_ALERT "Failed to register device class\n");
        return PTR_ERR(my_class);
    }

    my_device = device_create(my_class, NULL, MKDEV(major, 0), NULL, DEVICE_NAME);
    if (IS_ERR(my_device)) {
        class_destroy(my_class);
        unregister_chrdev(major, DEVICE_NAME);
        printk(KERN_ALERT "Failed to create the device\n");
        return PTR_ERR(my_device);
    }

    printk(KERN_INFO "Device class created\n");
    return 0;
}

5. 编写出口函数

当卸载驱动时需要释放相关资源,这个过程在出口函数中完成。

static void __exit my_driver_exit(void) {
    device_destroy(my_class, MKDEV(major, 0));  // 删除设备节点
    class_unregister(my_class);  // 注销设备类
    class_destroy(my_class);  // 销毁类
    unregister_chrdev(major, DEVICE_NAME);  // 注销字符设备
    printk(KERN_INFO "Character device driver unloaded\n");
}

6. 辅助函数

使用 class_createdevice_create 可以帮助自动创建 /dev 中的设备节点,从而方便用户访问。

  • class_create(THIS_MODULE, CLASS_NAME):创建一个类
  • device_create(my_class, NULL, MKDEV(major, 0), NULL, DEVICE_NAME):在 /dev 中创建设备节点

2. Input子系统框架

img

从图中可以看到,整个 Linux 输入子系统架构可以分为三大层:Input Device 层Input Core 层Input Handler 层

1. Input Device 层(输入设备层)

这个层主要是和硬件直接打交道的地方,如键盘、鼠标、触摸屏、GPIO
键等输入设备。输入设备的驱动程序从硬件中获取数据,并将这些原始数据转换为标准的输入事件,然后上报给核心层(Input Core)。

核心组件:

  • input_dev 结构体:这是设备驱动程序和输入子系统交互的核心结构体。它定义了设备驱动程序如何报告事件,比如按键按下、鼠标移动等。

简要流程: 硬件设备产生输入信号(如按下按键),设备驱动程序通过 input_dev 将这些输入信号传递给 Input Core。

2. Input Core 层(输入核心层)

这个层负责接收来自底层输入设备的事件,并将这些事件分发给上层的处理器(Handler)。它起到桥梁的作用,将底层设备的事件转换为上层可以理解的标准化事件。

核心组件:

  • input.c:是 Input Core 层的主要模块,它提供了 API 和机制来管理输入设备和输入事件。输入核心会将设备上报的事件(如键值、坐标)转交给处理这些事件的 handler

功能概述

  • 接收底层设备上报的输入事件。
  • 将事件转发给合适的 handler 进行处理。

3. Input Handler 层(输入处理层)

这个层负责处理输入核心层转发的输入事件,并向用户空间提供访问接口。在 Linux 中,evdev
是最常用的处理层,负责将输入事件暴露给用户空间。

核心组件:

  • evdev.c:该模块将输入设备的事件通过 /dev/input/eventX 设备节点暴露给用户空间的应用程序,应用程序可以通过 open()read()ioctl() 等系统调用来访问这些事件。

file_operations 结构体evdev_fops 定义了如何处理用户空间对输入设备的访问。这个结构体包括了多个操作函数,比如:

  • .read:读取输入事件。
  • .write:写入数据。
  • .poll:轮询事件。
  • .ioctl:处理设备的控制命令。

4. 用户空间访问

用户空间的应用程序可以通过 /dev/input/eventX 设备节点直接访问输入设备。这些应用程序通过
open()ioctl()poll()read() 等接口访问设备数据,处理输入事件。

举例:

  • tslib:是用于处理触摸屏输入的库。
  • libinput:是一个更高级的输入处理库,用于统一处理键盘、鼠标、触摸屏等设备的输入事件。

Input Device 层:负责将硬件设备的原始输入信号转换为标准事件。

Input Core 层:负责管理输入设备,并将标准事件传递给处理层。

Input Handler 层:将事件暴露给用户空间,用户通过访问 /dev/input/eventX 来读取输入事件。

3. Input子系统重要结构体

内核源码:

  • \Linux-4.9.88\include\linux\input.h📎input.h
  • \Linux-4.9.88\include\uapi\linux\input-event-codes.h📎input-event-codes.h

img

3.1 Input_dev

truct input_dev 是描述输入设备的核心结构体。它提供了输入设备的详细属性、支持的事件类型、处理事件的回调函数等。在开发输入设备驱动时,开发者需要初始化并注册该结构体,使其能够正确地处理输入事件并将其传递到上层。

\Linux-4.9.88\include\linux\input.h
struct input_dev {
	const char *name; //输入设备的名称。例如,"USB Keyboard"
	const char *phys; //输入设备的物理路径。例如,"usb-0000:00:1d.0-1/input0"。
	const char *uniq; //输入设备的唯一标识符,通常用于区分具有相同类型的多个设备。
	struct input_id id; //输入设备的硬件 ID,用于标识输入设备的制造商、产品 ID 和版本号。

	unsigned long propbit[BITS_TO_LONGS(INPUT_PROP_CNT)];

	unsigned long evbit[BITS_TO_LONGS(EV_CNT)];
    //设备支持的事件类型。事件类型定义在 linux/input.h 中,如 EV_KEY 表示按键事件,EV_ABS 表示绝对坐标事件。
	unsigned long keybit[BITS_TO_LONGS(KEY_CNT)];//设备支持的按键,如键盘按键或鼠标按键。
	unsigned long relbit[BITS_TO_LONGS(REL_CNT)];//设备支持的相对运动事件,如鼠标的移动。
	unsigned long absbit[BITS_TO_LONGS(ABS_CNT)];//设备支持的绝对坐标事件,如触摸屏的坐标数据。
	unsigned long mscbit[BITS_TO_LONGS(MSC_CNT)];//设备支持的杂项事件,如扫描码、遥控器信号等。
	unsigned long ledbit[BITS_TO_LONGS(LED_CNT)];//设备支持的 LED 事件(如键盘指示灯)。
	unsigned long sndbit[BITS_TO_LONGS(SND_CNT)];//设备支持的声音事件(如蜂鸣器)。
	unsigned long ffbit[BITS_TO_LONGS(FF_CNT)];//设备支持的力反馈事件(如游戏手柄的振动)。
	unsigned long swbit[BITS_TO_LONGS(SW_CNT)];//设备支持的开关事件。

	unsigned int hint_events_per_packet;
    // 每个数据包的建议事件数,优化设备的事件处理。

	unsigned int keycodemax;
	unsigned int keycodesize;
    //输入设备支持的最大键码数和每个键码的大小。
	void *keycode;//存储设备支持的键码映射表。

	int (*setkeycode)(struct input_dev *dev,
			  const struct input_keymap_entry *ke,
			  unsigned int *old_keycode);//设置设备的键码映射。
	int (*getkeycode)(struct input_dev *dev,
			  struct input_keymap_entry *ke);// 获取设备的键码映射。

	struct ff_device *ff;//力反馈设备的相关信息。

	unsigned int repeat_key;//保存最后一个重复的按键值。
	struct timer_list timer;//用于处理按键重复事件的定时器

	int rep[REP_CNT];//按键重复相关的参数,主要用于延迟与频率。

	struct input_mt *mt;//多点触控相关的数据结构。

	struct input_absinfo *absinfo;//保存绝对坐标相关的信息(如最小值、最大值、分辨率等)。

	unsigned long key[BITS_TO_LONGS(KEY_CNT)];//保存按键的当前状态
	unsigned long led[BITS_TO_LONGS(LED_CNT)];//保存 LED 的当前状态
	unsigned long snd[BITS_TO_LONGS(SND_CNT)];//保存声音设备的当前状态
	unsigned long sw[BITS_TO_LONGS(SW_CNT)];//保存开关的当前状态。

	int (*open)(struct input_dev *dev);//打开设备时调用的函数,通常用于初始化设备。
	void (*close)(struct input_dev *dev);// 关闭设备时调用的函数,通常用于释放资源。
	int (*flush)(struct input_dev *dev, struct file *file);//刷新设备时调用的函数,通常用于清空缓冲区。
	int (*event)(struct input_dev *dev, unsigned int type, unsigned int code, int value);
    //处理输入事件的回调函数

	struct input_handle __rcu *grab;

	spinlock_t event_lock;//用于保护输入事件处理的自旋锁。
	struct mutex mutex;//用于保护设备其他部分的互斥锁。

	unsigned int users;//记录打开该设备的用户数。
	bool going_away;//标识设备是否正在被卸载。

	struct device dev;//内核设备模型中的设备结构体。

	struct list_head	h_list; // 保存handle链表
	struct list_head	node;
    //设备在输入子系统链表中的挂载点。

	unsigned int num_vals;
	unsigned int max_vals;
    //当前设备的输入值数量及其最大值。
    
	struct input_value *vals;//保存输入值的数组

	bool devres_managed;//标识设备是否由资源管理器管理
};

3.2 input_handler

input_handler 作为输入事件的处理器,它的作用在于:

  • 事件的接收与处理:它定义了如何接收和处理来自输入设备的事件,如键盘按键、鼠标移动等。
  • 事件过滤filter 函数可以在事件传递前过滤掉不需要的事件,提高处理效率。
  • 设备匹配与连接:通过 matchconnect 函数,它负责将处理程序与适当的设备连接起来。
  • 事件传递eventevents 回调函数可以在设备产生事件时,将事件传递给上层逻辑,或通过驱动程序接口传递给用户空间应用。

工作流程简述

  1. 设备连接:当一个输入设备注册时,input_handlermatch 函数会被调用,以确定该处理程序是否可以处理这个设备。如果匹配成功,connect 函数被调用来建立连接。
  2. 事件处理:一旦设备产生事件,eventevents 回调会被调用来处理输入事件。根据设备和处理程序的实现,事件可以传递给用户空间或在内核中进一步处理。
  3. 设备断开:当设备断开时,disconnect 函数会被调用,处理程序负责清理与设备连接相关的资源。
\Linux-4.9.88\include\linux\input.h
struct input_handler {

	void *private;
    //用于存储处理程序的私有数据。驱动程序可以使用它来保存与特定设备相关的上下文信息。

	void (*event)(struct input_handle *handle, unsigned int type, unsigned int code, int value);
	//这是处理单个输入事件的回调函数。事件由 type、code 和 value 表示,分别代表事件类型、事件码和事件的值(如按键按下或松开、移动坐标等)。
    
    void (*events)(struct input_handle *handle,
		       const struct input_value *vals, unsigned int count);
    //处理多个输入事件的回调函数。vals 是一个包含多个事件的数组,count 是事件的数量。这允许处理一组输入事件,提高效率。
	
    bool (*filter)(struct input_handle *handle, unsigned int type, unsigned int code, int value);
	//这是一个事件过滤函数。它用于判断某个事件是否应该被传递给上层。如果返回 true,则事件会被过滤掉,不会继续处理;如果返回 false,事件将继续传递。
   
    bool (*match)(struct input_handler *handler, struct input_dev *dev);
	//用于匹配处理程序和输入设备。返回 true 表示当前 handler 可以处理 dev 设备。
    
    int (*connect)(struct input_handler *handler, struct input_dev *dev, const struct input_device_id *id);
	//当有输入设备与 handler 匹配时调用的回调函数,用于连接设备。返回 0 表示成功连接,其他值表示失败。
    
    void (*disconnect)(struct input_handle *handle);
	//当输入设备断开时调用的回调函数,用于处理设备断开连接后的清理工作。
    
    void (*start)(struct input_handle *handle);
    //在 connect 成功之后调用的回调函数,通常用于初始化某些特定资源或启动设备处理程序。
    
	bool legacy_minors;//指示处理程序是否使用了旧的次设备号 (minor number) 机制。这个标志主要用于向后兼容旧的输入设备模型。
	int minor;//用于存储处理程序关联的次设备号。次设备号帮助区分同一类设备中的不同实例。
	const char *name;//输入处理程序的名称,用于在内核日志或其他信息输出中标识处理程序。

	const struct input_device_id *id_table;
    //这是一个输入设备 ID 表,用于定义处理程序可以匹配的输入设备类型。它通常包含输入设备的厂商 ID、产品 ID 等信息,用于识别设备。

	struct list_head	h_list;//用于将 input_handler 添加到输入子系统的处理程序列表handle中,和相应的input_dev匹配保存进去
	struct list_head	node;//用于将 input_handler 添加到设备的处理程序链表中。
};

3.3 input_handle

输入子系统中的一个核心结构体,它用于连接输入设备 (input_dev) 和输入事件处理程序 (input_handler)。当设备和处理程序之间建立连接时,input_handle 负责维护两者之间的关联,确保事件能够从设备传递到合适的处理程序进行处理。

工作流程简述

  1. 设备与处理程序连接:当输入设备与输入处理程序匹配成功时,系统会创建并初始化一个 input_handle,用来维持设备和处理程序的关系。
  2. 事件传递:一旦输入设备产生事件,input_handle 会将这些事件从 input_dev 传递给对应的 input_handler 进行处理。
  3. 设备打开/关闭管理input_handle 中的 open 标志位用于记录设备的状态,确保设备只在被打开时接收和处理事件,并且在关闭时停止事件处理。
  4. 设备断开:当输入设备断开连接时,系统会调用 input_handlerdisconnect 函数,并清理 input_handle 中的资源。
\Linux-4.9.88\include\linux\input.h
struct input_handle {

	void *private;
    //这个指针用于保存输入处理程序的私有数据(上下文数据)。
    //输入子系统在连接设备时,可能需要为每个连接的 input_handle 维护独立的数据。

	int open; // 这个整数值用于表示设备是否已经打开。
	const char *name;
    //处理程序的名称,通常用于标识该输入处理程序,可以帮助调试和日志输出时识别当前使用的 input_handle。

	struct input_dev *dev;//它表示 input_handle 正在处理的输入设备。
	struct input_handler *handler;//它表示当前 input_handle 所使用的处理程序。

	struct list_head	d_node;
    //这个链表节点用于将 input_handle 挂接到输入设备的处理程序链表中。这意味着一个输入设备可以有多个 input_handle 进行事件处理。
	struct list_head	h_node; //同里
};

3.4 input_event/value

是 Linux 输入子系统中用来描述输入设备事件的核心结构体,它表示从输入设备(如键盘、鼠标、触摸屏等)发送到内核的每个输入事件。

\Linux-4.9.88\include\linux\input.h
struct input_event {
	struct timeval time; //用来记录事件发生的时间。它包括秒 (tv_sec) 和微秒 (tv_usec) 两个字段,表示该事件的时间戳。
	__u16 type; 
    // 表示事件的类型。它决定了事件的类别,比如按键事件、相对移动事件、绝对位置事件等。
    
	__u16 code;
    //表示具体的事件代码。其含义依赖于 type 的值。
    
	__s32 value;
    //表示事件的值,其含义取决于 type 和 code 的组合。
};

struct input_value { //差不多,用这个比较多好像
	__u16 type;
	__u16 code;
	__s32 value;
};

type:

\Linux-4.9.88\include\uapi\linux\input-event-codes.h
/*
 * Event types
 */
#define EV_SYN			0x00
#define EV_KEY			0x01
#define EV_REL			0x02
#define EV_ABS			0x03
#define EV_MSC			0x04
#define EV_SW			0x05
#define EV_LED			0x11
#define EV_SND			0x12
#define EV_REP			0x14
#define EV_FF			0x15
#define EV_PWR			0x16
#define EV_FF_STATUS		0x17
#define EV_MAX			0x1f
#define EV_CNT			(EV_MAX+1)

这些宏定义描述了 Linux 输入子系统中不同的 事件类型 (Event Types)。这些类型是用于标识从输入设备(例如键盘、鼠标、触摸屏等)传递到内核的事件类别,每个类别都有不同的意义。struct input_event 结构体中的 type 字段使用这些宏来确定事件的类别。

  1. EV_SYN (0x00)
  • 同步事件,用于标识一组输入事件的结束。例如,多个输入事件会通过 EV_SYN 通知系统完成一个事件序列。这是一个标志位,告诉系统已经处理完一个事件的所有数据。
  • 典型用途:报告鼠标或触摸屏输入后,系统应通过 EV_SYN 告诉用户空间事件已完成。
  1. EV_KEY (0x01)
  • 按键事件,表示设备上的按键或按钮状态的变化。这是键盘、鼠标按钮、游戏控制器按钮等输入设备常用的事件类型。
  • 典型用途:键盘按下某个按键(如 KEY_A)或者鼠标点击时触发。
  1. EV_REL (0x02)
  • 相对坐标事件,用于描述相对于当前坐标的位移量。常用于鼠标等设备,表示相对位置的改变。
  • 典型用途:鼠标移动过程中生成的相对位移数据(REL_X, REL_Y)。
  1. EV_ABS (0x03)
  • 绝对坐标事件,用于描述设备的绝对坐标值。常用于触摸屏、操纵杆等设备,表示其当前位置。
  • 典型用途:触摸屏上的触摸点的 X 和 Y 位置(ABS_X, ABS_Y)。
  1. EV_MSC (0x04)
  • 杂项事件,用于发送某些特定的输入设备数据,例如时间戳或者扫描码等。
  • 典型用途:键盘扫描码事件(MSC_SCAN)。
  1. EV_SW (0x05)
  • 开关事件,用于表示设备上的开关状态。例如,表示设备是否插入、盖子是否关闭等。
  • 典型用途:检测设备状态(如笔记本的盖子是否关闭)。
  1. EV_LED (0x11)
  • LED 事件,用于控制设备上的 LED 指示灯。例如,键盘上的大写锁定指示灯(Caps Lock)、数字锁定指示灯(Num Lock)等。
  • 典型用途:设置或读取键盘上的 LED 状态。
  1. EV_SND (0x12)
  • 声音事件,用于产生音频信号,例如蜂鸣器。
  • 典型用途:系统蜂鸣器发出声音。
  1. EV_REP (0x14)
  • 重复事件,用于控制按键重复行为。例如,按住某个键时,定义按键重复的时间间隔(重复速度和延迟)。
  • 典型用途:按住某个键时产生的重复输入。
  1. EV_FF (0x15)
  • 力反馈事件,用于与具有力反馈功能的设备(如游戏控制器)进行交互,产生物理反馈(震动等)。
  • 典型用途:控制游戏控制器的震动反馈。
  1. EV_PWR (0x16)
  • 电源事件,用于报告或控制设备的电源状态。
  • 典型用途:电源按钮或设备的电源管理事件。
  1. EV_FF_STATUS (0x17)
  • 力反馈状态事件,用于报告力反馈设备的状态。
  • 典型用途:检测力反馈是否成功启动或停止。
  1. EV_MAX (0x1f)
  • 事件类型的最大值,用于定义事件类型的范围。
  1. EV_CNT (EV_MAX + 1)
  • 事件类型的数量(总计 32 种事件类型)。用于计算事件类型的总数。

这些事件类型用于在内核和用户空间之间传递输入设备的事件信息,驱动程序根据事件类型来处理输入设备的数据。例如:

  • 键盘驱动程序会处理 EV_KEY 类型的事件,用于表示按键的按下与松开。
  • 鼠标驱动程序会处理 EV_REL 类型的事件,用于表示鼠标的移动。
  • 触摸屏驱动程序会处理 EV_ABS 类型的事件,用于表示触摸点的坐标。

通过这些事件类型,输入子系统可以统一处理不同输入设备的数据,并通过 /dev/input/eventX 接口传递给用户空间程序。

code:

以type为EV_KEY按键类型例子:

#define KEY_RESERVED		0
#define KEY_ESC			1
#define KEY_1			2
#define KEY_2			3
#define KEY_3			4
#define KEY_4			5
#define KEY_5			6
#define KEY_6			7
#define KEY_7			8
#define KEY_8			9
#define KEY_9			10
.......

以type为EV_ABS绝对位移类型例子:

/*
 * Absolute axes
 */
#define ABS_X			0x00 //x方向
#define ABS_Y			0x01 /y方向
#define ABS_Z			0x02
#define ABS_RX			0x03
.......

value:

对于按键,它的 value 可以是 0(表示按键被按下)、1(表示按键被松开)、2(表示长按);

对于触摸屏,它的 value 就是坐标值、压力值。

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

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

相关文章

【Linux:网络基础】

网络协议&#xff1a; 协议实际上可以称为一种“约定”&#xff0c;通过网络通信中的数据约定&#xff0c;不同主机必须遵循相同的网络协议才可以实现通信。 协议即为通信双方都认识的结构化的数据类型 协议分层 协议的本质也是软件&#xff0c;在设计上为了更好的进行模块…

C++刷怪笼(9)继承

目录 1.前言 2.正文 2.1继承的概念和定义 2.1.1继承的概念 2.1.2继承的定义 ​编辑 2.1.3继承类模板 2.2基类和派生类间的转换 2.3继承中的作用域 2.3.1隐藏规则 2.4派⽣类的默认成员函数 2.4.1个常⻅默认成员函数 2.4.2实现⼀个不能被继承的类 2.5继承与友元 2.…

QT中使用图表之QChart概述

在Qt中使用QChart类可以快速绘制一个图表出来&#xff0c;比如折线图、饼图、柱状图等 QChart类用来管理图表中的图形、图例、轴等 QChartView是专门用来显示图表的类&#xff0c;相当于一个QWidget或者窗口&#xff0c;用来显示QChart 即总的步骤就是 1、创建QChartView的…

codeforces _ 补题

C. Ball in Berland 传送门&#xff1a;Problem - C - Codeforces 题意&#xff1a; 思路&#xff1a;容斥原理 考虑 第 i 对情侣组合 &#xff0c;男生为 a &#xff0c;女生为 b &#xff0c;那么考虑与之匹配的情侣 必须没有 a | b &#xff0c;一共有 k 对情侣&#x…

零基础学西班牙语,柯桥专业小语种培训泓畅学校

No te comas el coco, seguro que te ha salido bien la entrevista. Ya te llamarn. 别瞎想了&#xff01;我保证你的面试很顺利。他们会给你打电话的。 这里的椰子是"头"的比喻。在西班牙的口语中&#xff0c;我们也可以听到其他同义表达&#xff0c;比如&#x…

一、开发环境的搭建

环境搭建步骤&#xff1a; 下载软件安装软件运行软件 其他&#xff1a; Visual studio 安装包文件&#xff1a;https://www.alipan.com/s/nd5RgzD4e3b 下载软件 在浏览器中搜索Visual studio&#xff0c;选择如图的选项 点击该区域&#xff0c;进入该页面&#xff0c;【或…

SSH免密钥登录

1: 用 ssh-key-gen 在本地主机上创建公钥和密钥 winr cmd 打开控制台 ssh-keygen -t rsa 一直按enter 2: 用 ssh-copy-id 把公钥复制到远程主机上 user 是用户名 remote_host是远程主机 ssh-copy-id -i ~/.ssh/id_rsa.pub userremote_host 3: 直接登录远程主机&#xf…

对外部供应商依赖带来的战略限制分析

当企业过分依赖单一或少数供应商时&#xff0c;一旦这些供应商出现问题&#xff0c;如生产延误、质量问题或财务困难&#xff0c;企业的运营将受到严重影响。例如&#xff0c;2020年的新冠疫情期间&#xff0c;许多企业因为供应商无法按时交货而面临生产停滞&#xff0c;导致巨…

Windows11家庭版安装Docker Desktop软件教程

下载Windows安装包 我们建议将源代码和其他数据绑定到 Linux 容器中时,将其存储在 Linux 文件系统中,而不是 Windows 文件系统中。 docker官网首页https://www.docker.com/ (需要科学上网)下载Windows版本的Docker Desktop。 或者使用已经下载好的Docker Desktop 安装包…

理解ADC:为什么量化噪声也会产生谐波?附带介绍 Dither(抖动)

前言 今天继续从经典的 ADI 《MT-001》说起&#xff0c;通常情况下量化噪声是白噪声&#xff0c;但如果量化噪声与输入信号之间存在相关性&#xff0c;就不能被当做白噪声对待。 文中举了一个有意思的例子&#xff1a;理想 ADC 的采样频率为 80 MSPS &#xff0c;一种情况输入…

外包干了7天,技术明显退步。。。。。

先说一下自己的情况&#xff0c;本科生&#xff0c;22年通过校招进入南京某软件公司&#xff0c;干了接近2年的功能测试&#xff0c;今年年初&#xff0c;感觉自己不能够在这样下去了&#xff0c;长时间呆在一个舒适的环境会让一个人堕落!而我已经在一个企业干了2年的功能测试&…

vue组件在项目中的常用业务逻辑(1)

若要使用一个组件&#xff08;已有基本结构&#xff09; 1.在api>index.js获取接口数据&#xff08;这里是模拟数据&#xff09; 2.去vuex仓库&#xff08;store>home.js&#xff09;存储数据&#xff08;state mutations actions三连环&#xff09; &#xff08;准备…

Linux网络命令:系统中用于显示和操作 ARP缓存表的命令arp详解

目录 一、概述 二、用法 1、基本语法 2、常用选项 3、获取帮助 三、示例 1. 显示所有 ARP 表项 2. 以数字形式显示 IP 地址 3. 删除指定的 ARP 表项 4. 添加一个静态 ARP 表项 5. 显示详细信息 四、详细说明 1、ARP 缓存表 2、静态 ARP 表项 3、动态 ARP 表项 五、常…

如何在短时间内入门并掌握深度学习?

如何在短时间内快速入门并掌握深度学习&#xff0c;是很多读者的困惑——晦涩难懂的数学 知识、复杂的算法、烦琐的编程……深度学习虽然让无数读者心怀向往&#xff0c;却也让不少人望而生畏&#xff0c;深感沮丧&#xff1a;时间没少花&#xff0c;却收效甚微。 如何才能更好…

图像分割从基础到进阶:阈值化、K-means和Mean-Shift算法的应用

图像分割是计算机视觉中的一项关键技术&#xff0c;用来将图像划分为若干个 有意义 的区域&#xff0c;以便后续的图像处理和分析工作。根据任务的不同&#xff0c;图像分割可以进一步细分为语义分割、实例分割和全景分割&#xff1a; 语义分割 (Semantic Segmentation) 对图像…

JavaEE进阶----18.<Mybatis补充($和#的区别+数据库连接池)>

详解了 1.$和#的区别 2.数据库连接池。 3.简单了解MySQL企业开发规范 一、Mybatis面试题&#xff1a;$和#的区别是什么&#xff1f; MyBatis 参数赋值有两种方式&#xff0c;咱们前面使用了 #{} 进行赋值&#xff0c;接下来我们看下二者的区别。 1.1 #是预编译SQL&#xff0c;$…

瑞格智慧心理服务平台 NPreenSMSList.asmx sql注入漏洞复现

0x01 产品描述&#xff1a; ‌ 瑞格智慧心理服务平台‌是一个集心理测评、心理咨询、心理危机干预、心理放松训练等功能于一体的综合性心理健康服务平台。该平台由北京瑞格心灵科技有限公司开发&#xff0c;旨在为用户提供全方位的心理健康服务。0x02 漏洞描述&#xff1a;…

Webserver(1.8)操作函数

目录 文件属性操作函数access函数chmod函数chown函数truncate函数 目录操作函数mkdir函数rmdir函数rename函数chdir函数*getcwd函数 目录遍历函数*opendir函数*readdir函数closedir函数 dup、dup2函数dupdup2 fcntl函数 文件属性操作函数 access函数 判断某个文件是否有某个权…

(49)MATLAB实现迫零均衡器原理与代码

文章目录 前言一、迫零均衡器设计说明二、迫零均衡器MATLAB源代码1.函数说明2.代码实现3.辅助函数 前言 使用MATLAB实现迫零均衡器。给出完整的MATLAB设计源代码。 一、迫零均衡器设计说明 理想的迫零均衡器有无限多个抽头权系数&#xff0c;是不能实现的&#xff0c;本文考虑…

springboot揭秘00-基于java配置的spring容器

文章目录 【README】【1】基本概念&#xff1a;Configuration与Bean【2】使用AnnotationConfigApplicationContext实例化spring容器【2.1】使用java配置简单构建spring容器【2.1.1】AnnotationConfigApplicationContext与Component及JSR-330注解类一起使用 【2.2】使用register…