lvgl 界面管理器
lv_scr_mgr
lvgl 界面管理器
适配 lvgl 8.3
- 降低界面之间的耦合
- 使用较小的内存,界面切换后会自动释放内存
- 内存泄漏检测
使用方法
- 在lv_scr_mgr_port.h 中创建一个枚举,用于界面ID
- 为每个界面创建一个页面管理器句柄
- 将界面句柄添加到 lv_scr_mgr_port.c 数组中
- 在 lv_init() 后,对页面管理器进行初始化 lv_scr_mgr_init(NULL);
- 使用 lv_scr_mgr_switch 设置初始根界面
- 使用 lv_scr_mgr_push lv_scr_mgr_pop 对界面进行操作
git地址
git地址
gitee
/*********************************CopyRight ************************************* @file lv_scr_mgr.c* @author 不咸不要钱* @date 2023-10-11 13:4:36* @brief &#&* *******************************************************************************//* Includes ------------------------------------------------------------------*/
#include "lvgl.h"
#include "lv_scr_mgr.h"
#if !LV_SCR_MGR_REG_ENABLE
#include "lv_scr_mgr_port.c"
#endiftypedef struct
{uint32_t scr_cnt; void* param;lv_scr_mgr_handle_t **handles;
#if LV_SCR_MGR_PRINTF_MEMuint32_t *max_mem;
#endif
}scr_mgr_list_handle_t;static scr_mgr_list_handle_t mgr_list;
static lv_scr_mgr_stack_node_t* mgr_stack_top = NULL;
static lv_scr_mgr_stack_node_t* mgr_stack_root = NULL;static lv_scr_mgr_handle_t* find_handle_by_id(uint32_t id)
{for (int i = 0; i < mgr_list.scr_cnt; i++){if (mgr_list.handles[i]->scr_id == id){return mgr_list.handles[i];}}return NULL;
}
#if LV_SCR_MGR_PRINTF_MEM
static uint32_t* find_mem_addr_by_id(uint32_t id)
{for (int i = 0; i < mgr_list.scr_cnt; i++){if (mgr_list.handles[i]->scr_id == id){return &mgr_list.max_mem[i];}}return NULL;
}static void mem_max_printf(uint32_t id)
{static uint32_t mem_max = 0;lv_mem_monitor_t mon;lv_mem_monitor(&mon);if (mon.total_size - mon.free_size > mem_max){mem_max = mon.total_size - mon.free_size;LV_LOG_USER("used: %d (%d %%), frag: %d %%, biggest free: %d\n", mem_max,mon.used_pct,mon.frag_pct,(int)mon.free_biggest_size);}/* 当前界面最大使用内存 */uint32_t* page_max_mem = find_mem_addr_by_id(id);if (mon.total_size - mon.free_size > *page_max_mem){*page_max_mem = mon.total_size - mon.free_size;LV_LOG_USER("page id %d, used: %d (%d %%), frag: %d %%, biggest free: %d\n", id, *page_max_mem,mon.used_pct,mon.frag_pct,(int)mon.free_biggest_size);}
}static void anim_mem_max_printf(lv_event_t* e)
{lv_event_code_t event_code = lv_event_get_code(e);if (event_code == LV_EVENT_SCREEN_LOADED){mem_max_printf((uint32_t)lv_event_get_user_data(e));}
}
#endifstatic void scr_mgr_stack_free(void)
{lv_scr_mgr_stack_node_t* stack_node = NULL;/* 释放界面栈 */while (NULL != mgr_stack_top){stack_node = mgr_stack_top->prev;if(mgr_stack_top->handle->scr_destroy)mgr_stack_top->handle->scr_destroy();lv_mem_free((void*)mgr_stack_top);mgr_stack_top = stack_node;}mgr_stack_root = NULL;
}/*** @brief 入栈* @param tag 要入栈的句柄* @return 栈顶句柄
*/
static lv_scr_mgr_stack_node_t* scr_mgr_stack_push(lv_scr_mgr_handle_t* tag)
{lv_scr_mgr_stack_node_t* stack_node = NULL;stack_node = lv_mem_alloc(sizeof(lv_scr_mgr_stack_node_t));LV_ASSERT_MALLOC(stack_node);stack_node->handle = tag;stack_node->next = NULL;if (stack_node->handle->scr_first_create){stack_node->handle->scr_first_create();}if (tag->scr_create){stack_node->scr = tag->scr_create(stack_node->handle->scr_id, mgr_list.param);}else{LV_LOG_ERROR("no create fun!");}if (NULL == mgr_stack_top){stack_node->prev = NULL;mgr_stack_root = stack_node;}else{stack_node->prev = mgr_stack_top;mgr_stack_top->next = stack_node;}mgr_stack_top = stack_node;return stack_node;
}static int32_t scr_mgr_stack_pop(int32_t n)
{lv_scr_mgr_stack_node_t* stack_node = NULL;int32_t i = n;if ((NULL == mgr_stack_top) || (NULL == mgr_stack_top->prev)){return 0;}while (i){if ((NULL == mgr_stack_top) || (NULL == mgr_stack_top->prev)){break;}stack_node = mgr_stack_top->prev;if (mgr_stack_top->handle->scr_destroy){mgr_stack_top->handle->scr_destroy();}lv_mem_free((void*)mgr_stack_top);mgr_stack_top = stack_node;i--;}if (NULL != mgr_stack_top->handle->scr_create){mgr_stack_top->scr = mgr_stack_top->handle->scr_create(mgr_stack_top->handle->scr_id, mgr_list.param);}else{LV_LOG_ERROR("no create fun!");}if (i){LV_LOG_WARN("stack pop %d, but stack is %d", n, n-i);}return n - i;
}/*** @brief 切换界面* @param cur_scr 当前界面* @param stack_node 目标界面句柄* @param anim 切换界面动画开关* 关闭界面切换动画,切换界面时会先创建一个新的空界面,切换到空界面后,* 删除之前的界面,然后再创建切换到新界面,最后再删除中间界面。会节省内存。* 关闭界面切换动画,切换界面时直接创建新界面,然后再用动画切换到新界面。** @return true
*/
bool scr_mgr_switch(lv_obj_t* cur_scr, lv_scr_mgr_stack_node_t* stack_node, bool anim)
{lv_scr_load_anim_t load_anim = LV_SCR_MGR_LOAD_ANIM_DEFAULT;lv_obj_t* tmp_scr = NULL;if (anim){if ((stack_node->handle->anim_type != LV_SCR_LOAD_ANIM_NONE) && (LV_SCR_LOAD_ANIM_OUT_BOTTOM >= stack_node->handle->anim_type)){load_anim = stack_node->handle->anim_type;}#if LV_SCR_MGR_PRINTF_MEMlv_obj_add_event_cb(stack_node->scr, anim_mem_max_printf, LV_EVENT_SCREEN_LOADED, stack_node->handle->scr_id);
#endiflv_scr_load_anim(stack_node->scr, load_anim, LV_SCR_MGR_LOAD_ANIM_TIME, LV_SCR_MGR_LOAD_ANIM_DELAY, true);}else{if (NULL != cur_scr){tmp_scr = lv_obj_create(NULL);lv_scr_load(tmp_scr);lv_obj_del(cur_scr);cur_scr = NULL;}lv_scr_load(stack_node->scr);#if LV_SCR_MGR_PRINTF_MEMmem_max_printf(stack_node->handle->scr_id);
#endifif (NULL != tmp_scr){lv_obj_del(tmp_scr);}}return true;
}/*** @brief 初始化界面管理器* @param param 创建界面时的参数* @return
*/
bool lv_scr_mgr_init(void* param)
{mgr_list.param = param;
#if LV_SCR_MGR_REG_ENABLE#elsemgr_list.scr_cnt = sizeof(scr_mgr_handles) / sizeof(scr_mgr_handles[0]);mgr_list.handles = scr_mgr_handles;
#endif if (0 == mgr_list.scr_cnt){LV_LOG_ERROR("no screen!");return false;}#if LV_SCR_MGR_PRINTF_MEMmgr_list.max_mem = lv_mem_alloc(mgr_list.scr_cnt * sizeof(uint32_t*));LV_ASSERT(mgr_list.max_mem);memset(mgr_list.max_mem, 0, mgr_list.scr_cnt * sizeof(uint32_t*));
#endifreturn true;
}void lv_scr_mgr_deinit(void)
{mgr_list.param = NULL;
#if LV_SCR_MGR_PRINTF_MEMlv_mem_free(mgr_list.max_mem);
#endifscr_mgr_stack_free();
}void lv_scr_mgr_param_set(void* param)
{mgr_list.param = param;
}void* lv_scr_mgr_param_get(void)
{return mgr_list.param;
}/*** @brief 设置根界面* @param id 根界面序号* @param anim 动画开关* @return
*/
bool lv_scr_mgr_switch(uint32_t id, bool anim)
{lv_scr_mgr_handle_t* tag_handle = find_handle_by_id(id);lv_scr_mgr_handle_t* cur_handle = NULL;lv_scr_mgr_stack_node_t* stack_node = NULL;lv_obj_t* cur_scr = NULL;if (NULL == tag_handle){LV_LOG_ERROR("no screen, id %d", id);return false;}if (NULL != mgr_stack_top){/* 栈内有界面 */cur_handle = mgr_stack_top->handle;cur_scr = mgr_stack_top->scr;}else{cur_scr = lv_scr_act();}scr_mgr_stack_free();if ((NULL == cur_handle) || (tag_handle->scr_id == cur_handle->scr_id)){/* 没有界面切换,不使用动画效果 */anim = false;}stack_node = scr_mgr_stack_push(tag_handle);return scr_mgr_switch(cur_scr, stack_node, anim);
}/*** @brief 入栈一个新的界面* @param id * @param anim * @return
*/
bool lv_scr_mgr_push(uint32_t id, bool anim)
{lv_scr_mgr_handle_t* tag_handle = find_handle_by_id(id);lv_scr_mgr_stack_node_t* stack_node = NULL;lv_obj_t* cur_scr = NULL;if (NULL == tag_handle){LV_LOG_ERROR("no screen, id %d", id);return false;}if ((NULL == mgr_stack_top) || (NULL == mgr_stack_root)){LV_LOG_ERROR("no root screen, please use lv_scr_mgr_switch create root screen");return false;}cur_scr = mgr_stack_top->scr;stack_node = scr_mgr_stack_push(tag_handle);return scr_mgr_switch(cur_scr, stack_node, anim);
}/*** @brief 出栈n个界面* @param n 如果栈内界面没有n个,则返回根界面* @param anim * @return
*/
bool lv_scr_mgr_popn(uint32_t n, bool anim)
{lv_obj_t* cur_scr = NULL;if ((mgr_stack_top == NULL) || (mgr_stack_top->prev == NULL)){return false;}cur_scr = mgr_stack_top->scr;scr_mgr_stack_pop(n);return scr_mgr_switch(cur_scr, mgr_stack_top, anim);
}/*** @brief 出栈一个界面* @param anim * @return
*/
bool lv_scr_mgr_pop(bool anim)
{return lv_scr_mgr_popn(1, anim);
}/*** @brief 退回到根界面* @param anim * @return
*/
bool lv_scr_mgr_pop_root(bool anim)
{lv_scr_mgr_stack_node_t* stack_node = NULL;lv_scr_mgr_stack_node_t* stack_top = NULL;uint32_t cnt = 0;if (NULL == mgr_stack_root || NULL == mgr_stack_top){return false;}stack_top = mgr_stack_top;while (stack_top != NULL){cnt++;stack_node = stack_top->prev;stack_top = stack_node;}return lv_scr_mgr_popn(cnt-1, anim);
}/*** @brief 获取当前界面id* @param * @return
*/
int32_t lv_scr_mgr_get_cur_id(void)
{if (NULL != mgr_stack_top && NULL != mgr_stack_top->handle){return mgr_stack_top->handle->scr_id;}else{return -1;}
}/*** @brief 获取根界面id* @param* @return
*/
int32_t lv_scr_mgr_get_root_id(void)
{if (NULL != mgr_stack_root && NULL != mgr_stack_root->handle){return mgr_stack_root->handle->scr_id;}else{return -1;}
}
/************************ (C) COPYRIGHT ***********END OF FILE*****************/
/*********************************CopyRight ************************************* @file lv_scr_mgr.h* @author 不咸不要钱* @date 2023-10-11 9:31:49* @brief &#&* *******************************************************************************/
#ifndef _LV_SCR_MGR_H_
#define _LV_SCR_MGR_H_/* Includes ------------------------------------------------------------------*/
#include "stdint.h"
#include "lvgl.h"#ifdef __cplusplus
extern "C" {
#endif/*!< 界面切换动画默认值*/
#define LV_SCR_MGR_LOAD_ANIM_DEFAULT LV_SCR_LOAD_ANIM_MOVE_LEFT
#define LV_SCR_MGR_LOAD_ANIM_TIME 500
#define LV_SCR_MGR_LOAD_ANIM_DELAY 0/*!< 内存泄漏检测 */
#define LV_SCR_MGR_PRINTF_MEM 1 #define LV_SCR_MGR_REG_ENABLE 0#if LV_SCR_MGR_REG_ENABLE#else#endiftypedef struct
{uint32_t scr_id; /*!< id */lv_scr_load_anim_t anim_type; /*!< 切换动画类型 如果为空,则使用 LV_SCR_MGR_LOAD_ANIM_DEFAULT */void (*scr_first_create)(void); /*!< lv_scr_mgr_switch lv_scr_mgr_push 函数会调用该创建函数 pop则不会调用 可以方便实现pop记住焦点 而push使用默认焦点 */lv_obj_t* (*scr_create) (const uint32_t id, void* param); /*!< 创建界面,创建界面时不要使用 lv_scr_mgr_xxx 函数 */void (*scr_destroy)(void); /*!< 删除界面的回调函数,一般用于删除如 lv_timer 等不会随界面自动删除的资源 */
}lv_scr_mgr_handle_t;typedef struct _lv_scr_mgr_stack_node_t
{lv_scr_mgr_handle_t* handle;lv_obj_t* scr;struct _lv_scr_mgr_stack_node_t* prev;struct _lv_scr_mgr_stack_node_t* next;
}lv_scr_mgr_stack_node_t;bool lv_scr_mgr_init(void* param);
void lv_scr_mgr_deinit(void);
void lv_scr_mgr_param_set(void* param);
void* lv_scr_mgr_param_get(void);bool lv_scr_mgr_switch(uint32_t id, bool anim);
bool lv_scr_mgr_push(uint32_t id, bool anim);
bool lv_scr_mgr_popn(uint32_t n, bool anim);
bool lv_scr_mgr_pop(bool anim);
bool lv_scr_mgr_pop_root(bool anim);
int32_t lv_scr_mgr_get_cur_id(void);
int32_t lv_scr_mgr_get_root_id(void);
#ifdef __cplusplus
}
#endif#endif /* _LV_SCR_MGR_H_ *//************************ (C) COPYRIGHT *****END OF FILE*****************/
相关文章:

lvgl 界面管理器
lv_scr_mgr lvgl 界面管理器 适配 lvgl 8.3 降低界面之间的耦合使用较小的内存,界面切换后会自动释放内存内存泄漏检测 使用方法 在lv_scr_mgr_port.h 中创建一个枚举,用于界面ID为每个界面创建一个页面管理器句柄将界面句柄添加到 lv_scr_mgr_por…...

一篇文章让你了解“JWT“
一.JWT简介 1.概念 JWT (JSON Web Token) 是一种用于在网络上安全传输信息的开放标准(RFC 7519)。它是一种紧凑且自包含的方式,用于在不同组件之间传递信息,通常用于身份验证和授权目的。JWT 是以 JSON 格式编码的令牌ÿ…...

HJ73 计算日期到天数转换
HJ73 计算日期到天数转换 int main() {int year, month, day;cin >> year >> month >> day;int monthDays[13] { 0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334, 365 };int nday monthDays[month - 1] day;if (month > 2 &&((year…...

Unity实现设计模式——适配器模式
Unity实现设计模式——适配器模式 适配器模式又称为变压器模式、包装模式(Wrapper) 将一个类的接口变换成客户端所期待的另一种接口,从而使原本因接口不匹配而无法在一起工作的两个类能够在一起工作。 在一个在役的项目期望在原有接口的基础…...

【2023年11月第四版教材】专题1 - 计算题考点汇总 (合集篇)
专题1 - 计算题考点汇总 (合集篇) 1 进度类1.1 PERT三点估算1.1.1 β分布1.1.2 三角分布 1.2 单代号网络图1.2.1 画图1.2.2 找关键路径1.2.3 计算总工期1.2.4 总时差1.2.5 自由时差1.2.6 工期压缩 1.3 双代号网络图1.4 双代号时标网络图1.4.1 画图1.4.2 找关键路径1…...
系统架构设计:17 论信息系统的安全性与保密性设计
目录 一 信息安全基础 1 信息安全的基本要素 2 信息安全的范围 3 网络安全...

使用EasyDarwin+ffmpeg+EasyPlayerPro完成rtsp的推流操作和拉流操作
本文分享在做视频类测试过程中所用到的工具EasyDarwinffmpegEasyPlayerPro 首先说一下EasyDarwin,简单来讲,它就是个推流和拉流及系统消耗的监测软件,具体使用方法我会写在下方。 EasyDarwin 1、解压下载好的EasyDarwin压缩包,并找到EasyD…...

FPGA project : flash_secter_erase
flash的指定扇区擦除实验。 先发写指令,再进入写锁存周期等待500ns,进入写扇区擦除指令,然后写扇区地址,页地址,字节地址。即可完成扇区擦除。 模块框图: 时序图: 代码: module…...

HarmonyOS/OpenHarmony原生应用-ArkTS万能卡片组件Radio
单选框,提供相应的用户交互选择项。该组件从API Version 8开始支持。无子组件。 一、接口 Radio(options: {value: string, group: string}) 从API version 9开始,该接口支持在ArkTS卡片中使用。 参数: 二、属性 除支持通用属性外,还支持以…...

python opencv 深度学习 指纹识别算法实现 计算机竞赛
1 前言 🔥 优质竞赛项目系列,今天要分享的是 🚩 python opencv 深度学习 指纹识别算法实现 🥇学长这里给一个题目综合评分(每项满分5分) 难度系数:3分工作量:4分创新点:4分 该项目较为新颖…...

一图看懂CodeArts Inspector 三大特性,带你玩转漏洞管理服务
华为云漏洞管理服务CodeArts Inspector是面向软件研发和服务运维提供的一站式漏洞管理能力,通过持续评估系统和应用等资产,内置风险量化管理和在线风险分析处置能力,帮助组织快速感应和响应漏洞,并及时有效地完成漏洞修复工作&…...
【Mysql】Mysql的启动选项和系统变量(二)
概述 在Mysql的设置项中一般都有各自的默认值,比方说mysql 5.7服务器端允许同时连入的客户端的默认数量是 151 ,表的默认存储引擎是 InnoDB ,我们可以在程序启动的时候去修改这些默认值,对于这种在程序启动时指定的设置项也称之为…...

FPGA project : flash_read
实验目标: flash的普通读指令,在指定地址开始读。可以更改地址与读的数据个数。 先发送读指令扇区地址页地址字节地址。 然后读数据。再把读到的串行数据转化为8bit的数据,存入fifo。 然后读出FIFO中数据,通过uart_tx模块发送…...

nnunetv2训练报错 ValueError: mmap length is greater than file size
目录 报错解决办法 报错 笔者在使用 nnunetv2 进行 KiTS19肾脏肿瘤分割实验的训练步骤中 使用 2d 和3d_lowres 训练都没有问题 nnUNetv2_train 40 2d 0nnUNetv2_train 40 3d_lowres 0但是使用 3d_cascade_fullres 和 3d_fullres 训练 nnUNetv2_train 40 3d_cascade_fullres …...
React知识点系列(2)-每天10个小知识
目录 1. 如何优化 React 应用的性能?你用过哪些性能分析工具?2. 在 React 中,什么是 Context API?你在什么场景下会使用它?3. 你能解释一下什么是 React Fiber 吗?4. 在项目中,你是否使用过 Rea…...

AutoGPT:让 AI 帮你完成任务事情 | 开源日报 No.54
Significant-Gravitas/AutoGPT Stars: 150.4k License: MIT AutoGPT 是开源 AI 代理生态系统的核心工具包。它采用模块化和可扩展的框架,使您能够专注于以下方面: 构建 - 为惊人之作打下基础。测试 - 将您的代理调整到完美状态。查看 - 观察进展成果呈…...

USB 转串口芯片 CH340
目录 1、概述 2、特点 3、封装 4、引脚 6、参数 6.1 绝对最大值(临界或者超过绝对最大值将可能导致芯片工作不正常甚至损坏) 6.2 电气参数(测试条件:TA25℃,VCC5V,不包括连接 USB 总线的引脚&…...

Day 05 python学习笔记
循环 应用:循环轮播图 最基础、最核心 循环:周而复始,谓之循环 (为了代码尽量不要重复) while循环 while的格式 索引定义 while 表达式(只要结果为布尔值即可): 循环体 通过条件的不断变化,从…...

Python如何17行代码画一个爱心
🌈write in front🌈 🧸大家好,我是Aileen🧸.希望你看完之后,能对你有所帮助,不足请指正!共同学习交流. 🆔本文由Aileen_0v0🧸 原创 CSDN首发🐒 如…...

生产环境中常用Linux命令
太简单的我就不讲解啦,浪费时间,直接将生产中常用的 文章目录 1.总纲2.整机 top3.CPU vmstat3. 内存 free4. 硬盘: df5. 磁盘IO iostat6. 网络IO ifstat7: 内存过高的情景排查 1.总纲 整机:topcpu:vmstat内存:free硬盘:df磁盘io: iostat网络io:ifstat 2.整机 top 首先们要查…...
内存分配函数malloc kmalloc vmalloc
内存分配函数malloc kmalloc vmalloc malloc实现步骤: 1)请求大小调整:首先,malloc 需要调整用户请求的大小,以适应内部数据结构(例如,可能需要存储额外的元数据)。通常,这包括对齐调整,确保分配的内存地址满足特定硬件要求(如对齐到8字节或16字节边界)。 2)空闲…...

2.Vue编写一个app
1.src中重要的组成 1.1main.ts // 引入createApp用于创建应用 import { createApp } from "vue"; // 引用App根组件 import App from ./App.vue;createApp(App).mount(#app)1.2 App.vue 其中要写三种标签 <template> <!--html--> </template>…...
vue3 字体颜色设置的多种方式
在Vue 3中设置字体颜色可以通过多种方式实现,这取决于你是想在组件内部直接设置,还是在CSS/SCSS/LESS等样式文件中定义。以下是几种常见的方法: 1. 内联样式 你可以直接在模板中使用style绑定来设置字体颜色。 <template><div :s…...
高防服务器能够抵御哪些网络攻击呢?
高防服务器作为一种有着高度防御能力的服务器,可以帮助网站应对分布式拒绝服务攻击,有效识别和清理一些恶意的网络流量,为用户提供安全且稳定的网络环境,那么,高防服务器一般都可以抵御哪些网络攻击呢?下面…...
大数据学习(132)-HIve数据分析
🍋🍋大数据学习🍋🍋 🔥系列专栏: 👑哲学语录: 用力所能及,改变世界。 💖如果觉得博主的文章还不错的话,请点赞👍收藏⭐️留言Ǵ…...

Unity | AmplifyShaderEditor插件基础(第七集:平面波动shader)
目录 一、👋🏻前言 二、😈sinx波动的基本原理 三、😈波动起来 1.sinx节点介绍 2.vertexPosition 3.集成Vector3 a.节点Append b.连起来 4.波动起来 a.波动的原理 b.时间节点 c.sinx的处理 四、🌊波动优化…...
安卓基础(aar)
重新设置java21的环境,临时设置 $env:JAVA_HOME "D:\Android Studio\jbr" 查看当前环境变量 JAVA_HOME 的值 echo $env:JAVA_HOME 构建ARR文件 ./gradlew :private-lib:assembleRelease 目录是这样的: MyApp/ ├── app/ …...
LeetCode - 199. 二叉树的右视图
题目 199. 二叉树的右视图 - 力扣(LeetCode) 思路 右视图是指从树的右侧看,对于每一层,只能看到该层最右边的节点。实现思路是: 使用深度优先搜索(DFS)按照"根-右-左"的顺序遍历树记录每个节点的深度对于…...

C/C++ 中附加包含目录、附加库目录与附加依赖项详解
在 C/C 编程的编译和链接过程中,附加包含目录、附加库目录和附加依赖项是三个至关重要的设置,它们相互配合,确保程序能够正确引用外部资源并顺利构建。虽然在学习过程中,这些概念容易让人混淆,但深入理解它们的作用和联…...

计算机基础知识解析:从应用到架构的全面拆解
目录 前言 1、 计算机的应用领域:无处不在的数字助手 2、 计算机的进化史:从算盘到量子计算 3、计算机的分类:不止 “台式机和笔记本” 4、计算机的组件:硬件与软件的协同 4.1 硬件:五大核心部件 4.2 软件&#…...