lvgl 界面管理器

news2024/11/19 18:46:27

lv_scr_mgr

lvgl 界面管理器

适配 lvgl 8.3

  • 降低界面之间的耦合
  • 使用较小的内存,界面切换后会自动释放内存
  • 内存泄漏检测
    在这里插入图片描述

使用方法

  1. 在lv_scr_mgr_port.h 中创建一个枚举,用于界面ID
  2. 为每个界面创建一个页面管理器句柄
  3. 将界面句柄添加到 lv_scr_mgr_port.c 数组中
  4. 在 lv_init() 后,对页面管理器进行初始化 lv_scr_mgr_init(NULL);
  5. 使用 lv_scr_mgr_switch 设置初始根界面
  6. 使用 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"
#endif

typedef struct
{
    uint32_t               scr_cnt; 
    void*                  param;
    lv_scr_mgr_handle_t    **handles;
#if LV_SCR_MGR_PRINTF_MEM
    uint32_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));
    }
}
#endif

static 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_MEM
        lv_obj_add_event_cb(stack_node->scr, anim_mem_max_printf, LV_EVENT_SCREEN_LOADED, stack_node->handle->scr_id);
#endif

        lv_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_MEM
        mem_max_printf(stack_node->handle->scr_id);
#endif
        if (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

#else
    mgr_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_MEM
    mgr_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*));
#endif
    return true;
}

void lv_scr_mgr_deinit(void)
{
    mgr_list.param = NULL;
#if LV_SCR_MGR_PRINTF_MEM
    lv_mem_free(mgr_list.max_mem);
#endif
    scr_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
     
#endif

typedef 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*****************/




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

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

相关文章

C++11新特性(lambda,可变参数模板,包装器,bind)

lambda表达式是什么&#xff1f;包装器又是什么&#xff1f;有什么作用&#xff1f;莫急&#xff0c;此篇文章将详细带你探讨它们的作用。很多同学在学习时害怕这些东西&#xff0c;其实都是方便使用的工具&#xff0c;很多情况下我们学这些新的东西觉得麻烦&#xff0c;累赘&a…

自学嵌入式多久才可以达到找工作的水平

自学嵌入式多久才可以达到找工作的水平 时间以及达到嵌入式工作水平所需的具体努力因人而异。但一般而言&#xff0c;自学嵌入式系统开发需要时间和毅力。以下是一些关键因素&#xff0c;影响着您能够在多久内达到找工作的水平&#xff1a;最近很多小伙伴找我&#xff0c;说想要…

YOLOv4 论文总结

贡献&#xff1a; 1.有效且强大的模型&#xff0c;常规GPU&#xff08;1080ti or 2080ti&#xff09;可得到实时、高质量的检测结果。 2.在训练中&#xff0c;验证 Bag-of-Freebies 和 Bag-of-Specials 方法 3.提出了两种数据增强手段&#xff0c;马赛克和自对抗训练&#x…

LeetCode【74】搜索二维矩阵

题目&#xff1a; 代码&#xff1a; public static boolean searchMatrix(int[][] matrix, int target) {int rows matrix.length;int columns matrix[0].length;// 先找到行&#xff0c;行为当前行第一列<target&#xff0c;当前行1行&#xff0c;第一列>targetfor…

详细教程:Postman 怎么调试 WebSocket

WebSocket 是一个支持双向通信的网络协议&#xff0c;它在实时性和效率方面具有很大的优势。Postman 是一个流行的 API 开发工具&#xff0c;它提供了许多功能来测试和调试 RESTful API 接口&#xff0c;最新的版本也支持 WebSocket 接口的调试。想要学习更多关于 Postman 的知…

Linux友人帐之环境变量

一、环境变量 1.1 环境变量的概念 1. 什么是环境变量&#xff1f; 环境变量(environment variables)一般是指在操作系统中用来指定操作系统运行环境的一些参数。 2. 为什么会有环境变量&#xff1f; 在Linux系统中&#xff0c;我们发现我们在执行一些指令时&#xff0c;比如…

10个打工人必备AI神器,升职加薪靠AI

HI&#xff0c;同学们&#xff0c;我是赤辰&#xff0c;本期是第18篇AI工具类教程&#xff0c;文章底部准备了粉丝福利&#xff0c;看完后可领取&#xff01;1. Runway&#xff08;文字转视频AI工具&#xff09; 只需要一句提示词就能精确生成你所想象的视频场景&#xff0c;还…

natapp内网穿透-将本地运行的程序/服务器通过公网IP供其它人访问

文章目录 1.几个基本概念1.1 局域网1.2 内网1.3 内网穿透1.4 Natapp 2.搭建内网穿透环境3.本地服务测试 1.几个基本概念 1.1 局域网 LAN&#xff08;Local Area Network&#xff0c;局域网&#xff09;是一个可连接住宅&#xff0c;学校&#xff0c;实验室&#xff0c;大学校…

百乐钢笔维修(官方售后,全流程)

文章目录 1 背景2 方法3 结果 1 背景 在给钢笔上墨的途中&#xff0c;不小心总成掉地上了&#xff0c;把笔尖摔弯了&#xff08;虽然还可以写字&#xff0c;但是非常的挂纸&#xff09;&#xff0c;笔身没有什么问题&#xff0c;就想着维修一下笔尖或者替换一下总成。 一般维…

Vulnhub系列靶机---Raven: 2

文章目录 信息收集主机发现端口扫描目录扫描用户枚举 漏洞发现漏洞利用UDF脚本MySQL提权SUID提权 靶机文档&#xff1a;Raven: 2 下载地址&#xff1a;Download (Mirror) 信息收集 靶机MAC地址&#xff1a;00:0C:29:15:7F:17 主机发现 sudo nmap -sn 192.168.8.0/24sudo arp…

操作系统中的(进程,线程)

操作系统是一个搞管理的软件&#xff0c;它对上给各个应用程序提供稳定的运行环境&#xff1b;对下管理各种硬件设备。 进程 一个操作系统由内核和配套的应用程序组成。而进程就是操作系统内核中众多关键概念中的一个。进程通俗一点来讲就是一个已经跑起来的程序。 每个进程…

【数据结构与算法】二叉树的镜像实现

需求分析&#xff1a; 将所有节点的左子树与右子树交换&#xff0c;以达到交换前与交换后成为镜像的效果。 如图&#xff1a; 实现代码&#xff1a; 先准备一个二叉树具有节点类&#xff0c;添加左右子节点的方法和层序遍历方法。 /*** author CC* version 1.0* since2023/10…

数学术语之源——“齐次(homogeneity)”的含义

1. “homogeneous”的词源 “homogeneous”源自1640年代&#xff0c;来自中古拉丁词“homogeneus”&#xff0c;这个词又源自古希腊词“homogenes”&#xff0c;词义为“of the same kind(关于同一种类的)”&#xff0c;由“homos”(词义“same(相同的)”&#xff0c;参见“ho…

msvcr110dll是干嘛的,win系统提示缺少msvcr110.dll解决步骤分享

今天&#xff0c;要和大家探讨一个非常重要的话题——由于找不到msvcr110.dll无法执行代码的五种解决方案。首先&#xff0c;请允许我为大家简要介绍一下msvcr110.dll这个文件。 msvcr110.dll是Visual Studio 2012的一个动态链接库文件&#xff0c;它是Microsoft Visual C 2012…

如何报考产品总监认证(UCPD)?

从产品经理到产品总监&#xff0c;是我们职业生涯中锦鲤化龙的一次历程。中、高级管理人员所需要的知识和能力常常会泾渭分明&#xff0c;甚至大相迳庭。所以&#xff0c;当我们走向高级管理岗位前&#xff0c;尤其是有机会应聘大厂总监岗位时&#xff0c;我们需要一张产品总监…

ESP32网络开发实例-从SPIFFS加载Web页面文件

从SPIFFS加载Web页面文件 文章目录 从SPIFFS加载Web页面文件1、应用介绍2、软件准备3、硬件准备4、Web页面代码与SPIFFS文件系统上传4.1 Web页面代码实现4.2 Web页面代码上传5、Web服务器代码实现在文中,将展示如何构建一个 Web 服务器,为存储在 ESP32 的SPIFFS文件系统中的 …

sklearn处理离散变量的问题——以决策树为例

最近做项目遇到的数据集中&#xff0c;有许多高维类别特征。catboost是可以直接指定categorical_columns的【直接进行ordered TS编码】&#xff0c;但是XGboost和随机森林甚至决策树都没有这个接口。但是在学习决策树的时候&#xff08;无论是ID3、C4.5还是CART&#xff09;&am…

使用 GitHub Action 自动更新 Sealos 集群的应用镜像

在 IT 领域&#xff0c;自动化无疑已成为提高工作效率和减少人为错误的关键。Sealos 作为一个强大的云操作系统&#xff0c;已经为许多企业和开发者提供了稳定可靠的服务。与此同时&#xff0c;随着技术不断发展&#xff0c;集成更多的功能和服务变得尤为重要。考虑到这一点&am…

【学习笔记】项目进行过程中遇到有关composer的问题

composer.json内容详解 以项目中的composer.json为例&#xff0c;参考文档。 name&#xff1a;composer包名type&#xff1a;包的类型&#xff0c;project和library两种keywords&#xff1a;关键词&#xff0c;方便别人在安装时通过关键词检索&#xff08;没试过&#xff0c;好…

成为一个黑客要多久?

一个暑假能成为黑客吗&#xff1f;资深白帽黑客告诉你答案&#xff0c;如果你想的是能到阿里五角大楼内网四处溜达&#xff0c;但是不可能的&#xff0c;但是成为一个初级黑客还是绰绰有余&#xff0c;你只需要掌握好渗透测试、外攻防、数据库等基本内容&#xff0c;搞懂外部安…