HarmonyOS ArkUI实战开发-NAPI异步编程

笔者在前 5 小节里讲述了在 OpenHarmony 上通过 NAPI 的方式实现了 JS 调用 C++的能力,但是这些实现都是同步的,本节笔者简单介绍一下 NAPI 的异步实现。

约定编程规范

ArkUI 开发框架对外提供的 API 命名是需遵守一定规范的,以 @ohos.display 模块提供的 API 为例,源码如下所示:

declare namespace display {
    function getDefaultDisplay(callback: AsyncCallback<Display>): void;
    function getDefaultDisplay(): Promise<Display>;
    function getDefaultDisplaySync(): Display;
}

根据该模块提供的方法,根据方法的命名规则可以得出 2 条规范:

  • 同步调用:

    • 方法名+ Sync 关键字,如:getMd5Sync():string
  • 异步调用:

    • 需要提供 AsyncCallback 和 Promise 的实现,如:getMd5(): Promise<string>getMd5(callback: AsyncCallback<Display>)

因此,我们在 index.d.ts 中声明 NAPI 方法时也按照系统约定的规范来。

定义异步方法

笔者在第 5 小结实现了 MD5 的计算,本节笔者把 MD5 的实现放在异步线程中,先在 index.d.ts 声明 JS 侧的方法,如下所示:

export const add: (a: number, b: number) => number;

// 声明异步方法
export function getMd5(value: string, callback: (md5: string) => void): void;
export function getMd5(value: string): Promise<string>;

// 声明同步方法
export function getMd5Sync(value: string): string;

getMd5Sync()表示同步实现 MD5 的计算,getMd5() 表示异步实现 MD5 的调用。

实现异步方法

声明完 JS 端的方法后,接着在 hello.cpp 中实现对应的方法,步骤如下:

  • 添加映射

    在 hello.cpp 的 Init() 方法里添加 JS 端的方法映射,代码如下所示:

    static napi_value Init(napi_env env, napi_value exports) {
        napi_property_descriptor desc[] = {
            {"add", nullptr, Add, nullptr, nullptr, nullptr, napi_default, nullptr},
            {"getMd5Sync", nullptr, GetMd5Sync, nullptr, nullptr, nullptr, napi_default, nullptr},
            {"getMd5", nullptr, GetMd5, nullptr, nullptr, nullptr, napi_default, nullptr},
        };

        napi_define_properties(env, exports, sizeof(desc) / sizeof(desc[0]), desc);
        return exports;
    }

"getMd5Sync" 和 GetMd5Sync 分别表示 JS 端和 C++ 端的方法,通过 napi_define_properties() 把他们映射在一起。

  • 方法实现
    getMd5() 的 C++ 端代码如下所示:
    // 定义异步线程执行中需要的上下文环境
    struct Md5Context {
        // 异步 worker
        napi_async_work work;

        // 对应 JS 端的 callback 函数
        napi_ref callback;

        // 对应 JS 端的 promise 对象
        napi_deferred promise;

        // 传递进来的参数
        string params;
        // 计算后的结果
        string result;
    };

    // 在子线程中执行
    static void doInBackground(napi_env env, void *data) {
        Md5Context *md5Context = (Md5Context *)data;

        // 模拟耗时操作,进行 MD5 计算
        string md5 = MD5(md5Context->params).toStr();

        // 计算后的 MD5 字存储到 result 中
        md5Context->result = md5;

        // 模拟耗时操作,让当前线程休眠 3 秒钟
        std::this_thread::sleep_for(std::chrono::seconds(3));
    }

    // 切换到主线程
    static void onPostExecutor(napi_env env, napi_status status, void *data) {
        Md5Context *md5Context = (Md5Context *)data;

        napi_value returnValue;
        if (napi_ok !=
            napi_create_string_utf8(env, md5Context->result.c_str(), md5Context->result.length(), &returnValue)) {
            delete md5Context;
            md5Context = nullptr;
            napi_throw_error(env, "-111", "napi_create_string_utf8: error");
            return;
        }

        if (md5Context->callback) {
            // 取出缓存的 js 端的 callback
            napi_value callback;
            if (napi_ok != napi_get_reference_value(env, md5Context->callback, &callback)) {
                delete md5Context;
                md5Context = nullptr;
                napi_throw_error(env, "-111", "napi_get_reference_value error");
                return;
            }

            napi_value tempValue;
            // 调用 callback,把值回调给 JS 端
            napi_call_function(env, nullptr, callback, 1, &returnValue, &tempValue);
            // 删除 callback
            napi_delete_reference(env, md5Context->callback);
        } else {
            // 以 promise 的形式回调数据
            if (napi_ok != napi_resolve_deferred(env, md5Context->promise, returnValue)) {
                delete md5Context;
                md5Context = nullptr;
                napi_throw_error(env, "-111", "napi_resolve_deferred error");
            }
        }

        // 删除异步任务并释放资源
        napi_delete_async_work(env, md5Context->work);
        delete md5Context;
        md5Context = nullptr;
    }

    static napi_value GetMd5(napi_env env, napi_callback_info info) {
        // 1、从 info 中读取 JS 传递过来的参数放入 args 里
        size_t argc = 2;
        napi_value args[2] = {nullptr};
        if (napi_ok != napi_get_cb_info(env, info, &argc, args, nullptr, nullptr)) {
            napi_throw_error(env, "-1001", "napi_get_cb_info error");
            return nullptr;
        }

        // 2、读取传入的参数类型
        napi_valuetype stringType = napi_undefined;
        if (napi_ok != napi_typeof(env, args[0], &stringType)) {
            napi_throw_error(env, "-1002", "napi_typeof string error");
            return nullptr;
        }

        // 3、传入的 string 如果为 null 或者 undefined 则抛异常
        if (napi_null == stringType || napi_undefined == stringType) {
            napi_throw_error(env, "-1003", "input params null or undefined");
            return nullptr;
        }

        // 4、读取传入的 string 内容长度
        size_t length = 0;
        if (napi_ok != napi_get_value_string_utf8(env, args[0], nullptr, 0, &length)) {
            napi_throw_error(env, "-1004", "get string length error");
            return nullptr;
        }

        // 5、判断传入的 string 长度是否符合
        if (0 == length) {
            napi_throw_error(env, "-1005", "string length can't be zero");
            return nullptr;
        }

        // 6、读取传入的 string 长度读取内容
        char *buffer = new char[length + 1];
        if (napi_ok != napi_get_value_string_utf8(env, args[0], buffer, length + 1, &length)) {
            delete[] buffer;
            buffer = nullptr;
            napi_throw_error(env, "-1006", "napi_get_value_string_utf8 string error");
            return nullptr;
        }

        // 7、读取 JS 有没有传递 callback,如果 callback 为 null 就表示是 promise 的回调方式
        napi_valuetype callbackType = napi_undefined;
        napi_status callbackStatus = napi_typeof(env, args[1], &callbackType);
        if (napi_ok != callbackStatus && napi_invalid_arg != callbackStatus) {
            delete[] buffer;
            buffer = nullptr;
            napi_throw_error(env, "-1004", "napi_typeof function error");
            return nullptr;
        }

        // 8、创建一个异步线程需要的数据 model,把传递过来的参数加入进去做下缓存
        auto context = new Md5Context();
        context->params = buffer;

        napi_value returnValue = nullptr;

        // 9、判断是 callback 的回调方式还是 promise 的回调方式
        if (napi_function == callbackType) {
            // 如果是 callback 的回调方式,需要创建 callback 的引用
            napi_ref callback;
            if (napi_ok != napi_create_reference(env, args[1], 1, &callback)) {
                delete[] buffer;
                delete context;
                buffer = nullptr;
                context = nullptr;

                napi_throw_error(env, "-11", "napi_create_reference error");
                return nullptr;
            }

            // 缓存 callback
            context->callback = callback;

            // 临时返回一个 undefined 值给 JS 端
            napi_get_undefined(env, &returnValue);
        } else {
            // promise 的回调方式,创建一个 Promise 的引用
            napi_deferred promise;
            if (napi_ok != napi_create_promise(env, &promise, &returnValue)) {
                delete[] buffer;
                delete context;
                buffer = nullptr;
                context = nullptr;

                napi_throw_error(env, "-11", "napi_create_promise error");
                return nullptr;
            }

            // 缓存 promise
            context->promise = promise;
        }

        napi_value resourceName;
        if (napi_ok != napi_create_string_utf8(env, "GetMd5", NAPI_AUTO_LENGTH, &resourceName)) {
            delete[] buffer;
            delete context;
            buffer = nullptr;
            context = nullptr;
            napi_throw_error(env, "-11", "napi_create_string_utf8 resourceName error");
            return nullptr;
        }

        // 10、创建一个异步任务
        napi_async_work asyWork;

        napi_status status =
            napi_create_async_work(env, nullptr, resourceName, doInBackground, onPostExecutor, (void *)context, &asyWork);
        if (napi_ok != status) {
            delete[] buffer;
            delete context;
            buffer = nullptr;
            context = nullptr;

            napi_throw_error(env, "-11", "napi_create_async_work error");
            return nullptr;
        }

        // 11、保存异步任务
        context->work = asyWork;

        // 12、添加进异步队列
        napi_queue_async_work(env, asyWork);

        return returnValue;
    }

getMd5() 的代码比较多,笔者添加的注释比较清楚,前 6 个小步骤是对传递进来的参数做基础校验,第 7 步是根据参数判断当前异步执行的回调方式是 Promise 还是 Callback。第 8 步创建了一个 Md5Context 对象,它的作用是把当前相关参数缓存下来目的是接下来在异步线程里使用这些参数,第 9 步根据异步回调的方法创建 Promise 或者 Callback 然后把他们保存在 Md5Context 对象里。第 10 步创建一个异步任务,然后把异步任务添加进异步队列中。

napi_create_async_work() 方法的第 3 、 4 个参数需要注意,doInBackground() 方法是在异步线程中执行的,onPostExecutor() 方法在异步线程结束后切换到主线程中执行。

  • 完整代码
    hello.cpp 全部代码如下所示:
    #include <cstddef>
    #include <cstring>
    #include "napi/native_api.h"
    #include <js_native_api.h>
    #include <js_native_api_types.h>
    #include <node_api.h>
    #include <node_api_types.h>
    #include <string>
    #include <thread>

    #include "./md5/md5.h"

    // 定义异步线程执行中需要的上下文环境
    struct Md5Context {
        // 异步 worker
        napi_async_work work;

        // 对应 JS 端的 callback 函数
        napi_ref callback;

        // 对应 JS 端的 promise 对象
        napi_deferred promise;

        // 传递进来的参数
        string params;
        // 计算后的结果
        string result;
    };

    static void doInBackground(napi_env env, void *data) {
        Md5Context *md5Context = (Md5Context *)data;

        // 模拟耗时操作,进行 MD5 计算
        string md5 = MD5(md5Context->params).toStr();

        // 计算后的 MD5 字存储到 result 中
        md5Context->result = md5;

        // 模拟耗时操作,让当前线程休眠 3 秒钟
        std::this_thread::sleep_for(std::chrono::seconds(3));
    }

    static void onPostExecutor(napi_env env, napi_status status, void *data) {
        Md5Context *md5Context = (Md5Context *)data;

        napi_value returnValue;
        if (napi_ok !=
            napi_create_string_utf8(env, md5Context->result.c_str(), md5Context->result.length(), &returnValue)) {
            delete md5Context;
            md5Context = nullptr;
            napi_throw_error(env, "-111", "napi_create_string_utf8: error");
            return;
        }

        if (md5Context->callback) {
            // 取出缓存的 js 端的 callback
            napi_value callback;
            if (napi_ok != napi_get_reference_value(env, md5Context->callback, &callback)) {
                delete md5Context;
                md5Context = nullptr;
                napi_throw_error(env, "-111", "napi_get_reference_value error");
                return;
            }

            napi_value tempValue;
            // 调用 callback,把值回调给 JS 端
            napi_call_function(env, nullptr, callback, 1, &returnValue, &tempValue);
            // 删除 callback
            napi_delete_reference(env, md5Context->callback);
        } else {
            // 以 promise 的形式回调数据
            if (napi_ok != napi_resolve_deferred(env, md5Context->promise, returnValue)) {
                delete md5Context;
                md5Context = nullptr;
                napi_throw_error(env, "-111", "napi_resolve_deferred error");
            }
        }

        // 删除异步任务并释放资源
        napi_delete_async_work(env, md5Context->work);
        delete md5Context;
        md5Context = nullptr;
    }

    static napi_value GetMd5(napi_env env, napi_callback_info info) {
        // 1、从 info 中读取 JS 传递过来的参数放入 args 里
        size_t argc = 2;
        napi_value args[2] = {nullptr};
        if (napi_ok != napi_get_cb_info(env, info, &argc, args, nullptr, nullptr)) {
            napi_throw_error(env, "-1001", "napi_get_cb_info error");
            return nullptr;
        }

        // 2、读取传入的参数类型
        napi_valuetype stringType = napi_undefined;
        if (napi_ok != napi_typeof(env, args[0], &stringType)) {
            napi_throw_error(env, "-1002", "napi_typeof string error");
            return nullptr;
        }

        // 3、传入的 string 如果为 null 或者 undefined 则抛异常
        if (napi_null == stringType || napi_undefined == stringType) {
            napi_throw_error(env, "-1003", "input params null or undefined");
            return nullptr;
        }

        // 4、读取传入的 string 内容长度
        size_t length = 0;
        if (napi_ok != napi_get_value_string_utf8(env, args[0], nullptr, 0, &length)) {
            napi_throw_error(env, "-1004", "get string length error");
            return nullptr;
        }

        // 5、判断传入的 string 长度是否符合
        if (0 == length) {
            napi_throw_error(env, "-1005", "string length can't be zero");
            return nullptr;
        }

        // 6、读取传入的 string 长度读取内容
        char *buffer = new char[length + 1];
        if (napi_ok != napi_get_value_string_utf8(env, args[0], buffer, length + 1, &length)) {
            delete[] buffer;
            buffer = nullptr;
            napi_throw_error(env, "-1006", "napi_get_value_string_utf8 string error");
            return nullptr;
        }

        // 7、读取 JS 有没有传递 callback,如果 callback 为 null 就表示是 promise 的回调方式
        napi_valuetype callbackType = napi_undefined;
        napi_status callbackStatus = napi_typeof(env, args[1], &callbackType);
        if (napi_ok != callbackStatus && napi_invalid_arg != callbackStatus) {
            delete[] buffer;
            buffer = nullptr;
            napi_throw_error(env, "-1004", "napi_typeof function error");
            return nullptr;
        }

        // 8、创建一个异步线程需要的数据 model,把传递过来的参数加入进去做下缓存
        auto context = new Md5Context();
        context->params = buffer;

        napi_value returnValue = nullptr;

        // 9、判断是 callback 的回调方式还是 promise 的回调方式
        if (napi_function == callbackType) {
            // 如果是 callback 的回调方式,需要创建 callback 的引用
            napi_ref callback;
            if (napi_ok != napi_create_reference(env, args[1], 1, &callback)) {
                delete[] buffer;
                delete context;
                buffer = nullptr;
                context = nullptr;

                napi_throw_error(env, "-11", "napi_create_reference error");
                return nullptr;
            }

            // 缓存 callback
            context->callback = callback;

            // 临时返回一个 undefined 值给 JS 端
            napi_get_undefined(env, &returnValue);
        } else {
            // promise 的回调方式,创建一个 Promise 的引用
            napi_deferred promise;
            if (napi_ok != napi_create_promise(env, &promise, &returnValue)) {
                delete[] buffer;
                delete context;
                buffer = nullptr;
                context = nullptr;

                napi_throw_error(env, "-11", "napi_create_promise error");
                return nullptr;
            }

            // 缓存 promise
            context->promise = promise;
        }

        napi_value resourceName;
        if (napi_ok != napi_create_string_utf8(env, "GetMd5", NAPI_AUTO_LENGTH, &resourceName)) {
            delete[] buffer;
            delete context;
            buffer = nullptr;
            context = nullptr;
            napi_throw_error(env, "-11", "napi_create_string_utf8 resourceName error");
            return nullptr;
        }

        // 10、创建一个异步任务
        napi_async_work asyWork;

        napi_status status =
            napi_create_async_work(env, nullptr, resourceName, doInBackground, onPostExecutor, (void *)context, &asyWork);
        if (napi_ok != status) {
            delete[] buffer;
            delete context;
            buffer = nullptr;
            context = nullptr;

            napi_throw_error(env, "-11", "napi_create_async_work error");
            return nullptr;
        }

        // 11、保存异步任务
        context->work = asyWork;

        // 12、添加进异步队列
        napi_queue_async_work(env, asyWork);

        return returnValue;
    }

    static napi_value GetMd5Sync(napi_env env, napi_callback_info info) {
        // 1、从info中取出JS传递过来的参数放入args
        size_t argc = 1;
        napi_value args[1] = {nullptr};
        if (napi_ok != napi_get_cb_info(env, info, &argc, args, nullptr, nullptr)) {
            napi_throw_error(env, "-1000", "napi_get_cb_info error");
            return nullptr;
        }

        // 2、获取参数的类型
        napi_valuetype stringType;
        if (napi_ok != napi_typeof(env, args[0], &stringType)) {
            napi_throw_error(env, "-1001", "napi_typeof error");
            return nullptr;
        }

        // 3、如果参数为null或者undefined,则抛异常
        if (napi_null == stringType || napi_undefined == stringType) {
            napi_throw_error(env, "-1002", "the param can't be null");
            return nullptr;
        }

        // 4、获取传递的string长度
        size_t length = 0;
        if (napi_ok != napi_get_value_string_utf8(env, args[0], nullptr, 0, &length)) {
            napi_throw_error(env, "-1003", "napi_get_value_string_utf8 error");
            return nullptr;
        }

        // 5、如果传递的是"",则抛异常
        if (length == 0) {
            napi_throw_error(env, "-1004", "the param length invalid");
            return nullptr;
        }

        // 6、读取传递的string参数放入buffer中
        char *buffer = new char[length + 1];
        if (napi_ok != napi_get_value_string_utf8(env, args[0], buffer, length + 1, &length)) {
            delete[] buffer;
            buffer = nullptr;
            napi_throw_error(env, "-1005", "napi_get_value_string_utf8 error");
            return nullptr;
        }

        // 7、计算MD5加密操作
        std::string str = buffer;
        str = MD5(str).toStr();

        // 8、把C++数据转成napi_value并返回
        napi_value value = nullptr;
        const char *md5 = str.c_str();
        if (napi_ok != napi_create_string_utf8(env, md5, strlen(md5), &value)) {
            delete[] buffer;
            buffer = nullptr;
            napi_throw_error(env, "-1006", "napi_create_string_utf8 error");
            return nullptr;
        }

        // 9、资源清理
        delete[] buffer;
        buffer = nullptr;

        return value;
    }

    static napi_value Add(napi_env env, napi_callback_info info) {
        size_t requireArgc = 2;
        size_t argc = 2;
        napi_value args[2] = {nullptr};

        napi_get_cb_info(env, info, &argc, args, nullptr, nullptr);

        napi_valuetype valuetype0;
        napi_typeof(env, args[0], &valuetype0);

        napi_valuetype valuetype1;
        napi_typeof(env, args[1], &valuetype1);

        double value0;
        napi_get_value_double(env, args[0], &value0);

        double value1;
        napi_get_value_double(env, args[1], &value1);

        napi_value sum;
        napi_create_double(env, value0 + value1, &sum);

        return sum;
    }

    EXTERN_C_START
    static napi_value Init(napi_env env, napi_value exports) {
        napi_property_descriptor desc[] = {
            {"add", nullptr, Add, nullptr, nullptr, nullptr, napi_default, nullptr},
            {"getMd5Sync", nullptr, GetMd5Sync, nullptr, nullptr, nullptr, napi_default, nullptr},
            {"getMd5", nullptr, GetMd5, nullptr, nullptr, nullptr, napi_default, nullptr},
        };

        napi_define_properties(env, exports, sizeof(desc) / sizeof(desc[0]), desc);
        return exports;
    }
    EXTERN_C_END

    static napi_module demoModule = {
        .nm_version = 1,
        .nm_flags = 0,
        .nm_filename = nullptr,
        .nm_register_func = Init,
        .nm_modname = "entry",
        .nm_priv = ((void *)0),
        .reserved = {0},
    };

    extern "C" __attribute__((constructor)) void RegisterEntryModule(void) {
        napi_module_register(&demoModule); 
    }

Index.ets 的测试代码如下:

    import testNapi from 'libentry.so';

    @Entry @Component struct Index {

      @State message: string = 'Hello,OpenHarmony'

      build() {
        Column({ space: 10 }) {

          Text(this.message)
            .fontSize(20)

          Button("同步回调")
            .onClick(() => {
              this.message = testNapi.getMd5Sync("Hello, OpenHarmony")
            })

          Button("异步 Callback 回调")
            .onClick(() => {
              this.message = "计算中...";
              testNapi.getMd5("Hello, OpenHarmony", (md5: string) => {
                this.message = md5;
              });
            })

          Button("异步 Promise 回调")
            .onClick(() => {
              this.message = "计算中...";
              testNapi.getMd5("Hello, OpenHarmony").then((md5: string) => {
                this.message = md5;
              }).catch((error: Error) => {
                this.message = "error: " + error;
              })
            })
        }
        .padding(10)
        .width('100%')
        .height("100%")
      }
    }

样例运行结果如下图所示:

小结

本节笔者简单讲述了 NAPI 的异步实现方式,下一小节笔者从源码的角度给大家讲解一下 NAPI 的实现原理,敬请期待……

码牛课堂也为了积极培养鸿蒙生态人才,让大家都能学习到鸿蒙开发最新的技术,针对一些在职人员、0基础小白、应届生/计算机专业、鸿蒙爱好者等人群,整理了一套纯血版鸿蒙(HarmonyOS Next)全栈开发技术的学习路线。大家可以进行参考学习:https://qr21.cn/FV7h05

①全方位,更合理的学习路径
路线图包括ArkTS基础语法、鸿蒙应用APP开发、鸿蒙能力集APP开发、次开发多端部署开发、物联网开发等九大模块,六大实战项目贯穿始终,由浅入深,层层递进,深入理解鸿蒙开发原理!

②多层次,更多的鸿蒙原生应用
路线图将包含完全基于鸿蒙内核开发的应用,比如一次开发多端部署、自由流转、元服务、端云一体化等,多方位的学习内容让学生能够高效掌握鸿蒙开发,少走弯路,真正理解并应用鸿蒙的核心技术和理念。

③实战化,更贴合企业需求的技术点
学习路线图中的每一个技术点都能够紧贴企业需求,经过多次真实实践,每一个知识点、每一个项目,都是码牛课堂鸿蒙研发团队精心打磨和深度解析的成果,注重对学生的细致教学,每一步都确保学生能够真正理解和掌握。

为了能让大家更好的学习鸿蒙(HarmonyOS NEXT)开发技术,这边特意整理了《鸿蒙开发学习手册》(共计890页),希望对大家有所帮助:https://qr21.cn/FV7h05

《鸿蒙开发学习手册》:https://qr21.cn/FV7h05

如何快速入门:

  1. 基本概念
  2. 构建第一个ArkTS应用
  3. ……

开发基础知识:https://qr21.cn/FV7h05

  1. 应用基础知识
  2. 配置文件
  3. 应用数据管理
  4. 应用安全管理
  5. 应用隐私保护
  6. 三方应用调用管控机制
  7. 资源分类与访问
  8. 学习ArkTS语言
  9. ……

基于ArkTS 开发:https://qr21.cn/FV7h05

  1. Ability开发
  2. UI开发
  3. 公共事件与通知
  4. 窗口管理
  5. 媒体
  6. 安全
  7. 网络与链接
  8. 电话服务
  9. 数据管理
  10. 后台任务(Background Task)管理
  11. 设备管理
  12. 设备使用信息统计
  13. DFX
  14. 国际化开发
  15. 折叠屏系列
  16. ……

鸿蒙开发面试真题(含参考答案):https://qr21.cn/FV7h05

大厂鸿蒙面试题::https://qr18.cn/F781PH

鸿蒙开发面试大盘集篇(共计319页):https://qr18.cn/F781PH

1.项目开发必备面试题
2.性能优化方向
3.架构方向
4.鸿蒙开发系统底层方向
5.鸿蒙音视频开发方向
6.鸿蒙车载开发方向
7.鸿蒙南向开发方向

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

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

相关文章

初学python记录:力扣39. 组合总和

题目&#xff1a; 给你一个 无重复元素 的整数数组 candidates 和一个目标整数 target &#xff0c;找出 candidates 中可以使数字和为目标数 target 的 所有 不同组合 &#xff0c;并以列表形式返回。你可以按 任意顺序 返回这些组合。 candidates 中的 同一个 数字可以 无限…

【STM32】嵌入式实验二 GPIO 实验:数码管

实验内容&#xff1a; 编写程序&#xff0c;在数码管上显示自己的学号。 数码管相关电路&#xff1a; PA7对应的应该是段码&#xff0c;上面的图写错了。 注意&#xff1a;选中数码管是低电平选中&#xff1b;并且用74HC595模块驱动输出的段码&#xff0c; 这个模块的学习可以…

echarts折线图默认不显示数据圆点,鼠标划上之后折线图才显示圆点

只需要设置showSymbol为false就可以了&#xff0c;表示只在 tooltip hover 的时候显示。 代码如下&#xff1a; option {tooltip: {trigger: axis},xAxis: {type: category,data: [Mon, Tue, Wed, Thu, Fri, Sat, Sun]},yAxis: {type: value},series: [{data: [150, 230, 224…

【智能算法】寄生捕食算法(PPA)原理及实现

目录 1.背景2.算法原理2.1算法思想2.2算法过程 3.结果展示4.参考文献 1.背景 2020年&#xff0c;AAA Mohamed等人受到自然界乌鸦-布谷鸟-猫寄生系统启发&#xff0c;提出了寄生捕食算法&#xff08;Parasitism – Predation Algorithm, PPA&#xff09;。 2.算法原理 2.1算法…

HTML使用jQuery实现两个点击按钮,分别控制改文本字体颜色和字体大小

jQuery 简介 jQuery 是一个广泛使用的 JavaScript 库&#xff0c;旨在简化对 HTML 文档的操作、事件处理、动画效果和 AJAX 等操作。 案例源码 <!DOCTYPE html> <html lang"en"> <head> <meta charset"UTF-8"> <meta name&q…

【C++】学习笔记——类和对象_4

文章目录 二、类和对象13.运算符重载赋值运算符重载 14. 日期类的实现Date.h头文件Date.cpp源文件test.cpp源文件 未完待续 二、类和对象 13.运算符重载 赋值运算符重载 我们之前学了一个拷贝构造函数&#xff0c;本质上就是创建一个对象&#xff0c;该对象初始化为一个已经…

go-cqhttp 机器人使用教程

API | go-cqhttp 帮助中心 参考 | go-cqhttp 帮助中心 机器人下载 发送消息 http://127.0.0.1:5700/send_msg?message_typeprivate&user_id911412667&message你好呀 检查端口是否打开 netstat -ano | findstr :5701 发送的请求 软件的dopost的解析 Overridepro…

Learn ComputeShader 02 Multiple kernels

前面已经成功创建了第一个compute shader&#xff0c;并且使用它替换掉quad的材质的纹理&#xff0c;现在我们将要在计算着色器中创建多个kernel。 首先调整上次的计算着色器&#xff0c;让它显示为红色。 然后再次创建一个kernel&#xff0c;显示为黄色。 结果应该是这样的…

【算法刷题】手撕LRU算法(原理、图解、核心思想)

文章目录 1.LRU算法1.1相关概念1.2图解举例1.3基于HashMap和双向链表实现1.3.1核心思想1.3.2代码解读1.3.3全部代码 1.LRU算法 1.1相关概念 LRU&#xff08;Least Recently Used&#xff0c;最近最久未使用算法&#xff09;&#xff1a; 定义&#xff1a;根据页面调入内存后的…

【Vue3】$subscribe订阅与反应

&#x1f497;&#x1f497;&#x1f497;欢迎来到我的博客&#xff0c;你将找到有关如何使用技术解决问题的文章&#xff0c;也会找到某个技术的学习路线。无论你是何种职业&#xff0c;我都希望我的博客对你有所帮助。最后不要忘记订阅我的博客以获取最新文章&#xff0c;也欢…

大模型实战—用户反馈分析

大模型实战—用户反馈概要提取 前面我们已经本地部署了大模型,正好公司有一个业务,可以用来练练手,业务背景是这样的,我们的产品上有一个用户反馈的功能,里面积累了有史以来用户对产品的反馈,公司现在想要分析一下用户目前对产品的评价,以及用户关注的点是什么。关于之…

OpenHarmony开发实例:【 待办事项TodoList】

简介 TodoList应用是基于OpenHarmony SDK开发的安装在润和HiSpark Taurus AI Camera(Hi3516d)开发板标准系统上的应用&#xff1b;应用主要功能是以列表的形式&#xff0c;展示需要完成的日程&#xff1b;通过本demo可以学习到 JS UI 框架List使用&#xff1b; 运行效果 样例…

vector的底层与使用

前言&#xff1a;vector是顺序表&#xff08;本质也是数组&#xff09; 文档参考网站&#xff1a;https://legacy.cplusplus.com/reference/vector/vector/vector/ //底层代码 #include<assert.h> #include<iostream> #include<vector> #include<string&g…

实现Spring底层机制(阶段1—编写自己的Spring容器,扫描包,得到bean的Class对象)

环境搭建抛出问题 1.环境搭建 1.创建maven项目 2.导入依赖 <project xmlns"http://maven.apache.org/POM/4.0.0" xmlns:xsi"http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation"http://maven.apache.org/POM/4.0.0 http://maven.ap…

隐藏表头和最高层级的复选框

隐藏表头和最高层级的复选框 <!-- 表格 --><el-tableref"tableRef"v-loading"tableLoading"default-expand-allclass"flex-1 !h-auto"row-key"regionId":header-cell-class-name"selectionClass":row-class-name&q…

20240422,C++文件操作

停电一天之后&#xff0c;今天还有什么理由不学习呜呜……还是没怎么学习 一&#xff0c;文件操作 文件操作可以将数据持久化&#xff0c;对文件操作时须包含头文件<fstream> 两种文件类型&#xff1a;文本文件&#xff1a;文件以文本的ASCII码形式存储&#xff1b;二进…

算法导论 总结索引 | 第三部分 第十一章:散列表

1、动态集合结构&#xff0c;它至少要支持 INSERT、SEARCH 和 DELETE字典操作 散列表 是实现字典操作的 一种有效的数据结构。尽管 最坏情况下&#xff0c;散列表中 查找一个元素的时间 与链表中 查找的时间相同&#xff0c;达到了 Θ(n)。在实际应用中&#xff0c;散列表的性…

GLID: Pre-training a Generalist Encoder-Decoder Vision Model

1 研究目的 现在存在的问题是&#xff1a; 目前&#xff0c;尽管自监督预训练方法&#xff08;如Masked Autoencoder&#xff09;在迁移学习中取得了成功&#xff0c;但对于不同的下游任务&#xff0c;仍需要附加任务特定的子架构&#xff0c;这些特定于任务的子架构很复杂&am…

Linux使用Docker部署DashDot访问本地服务器面板

文章目录 1. 本地环境检查1.1 安装docker1.2 下载Dashdot镜像 2. 部署DashDot应用 本篇文章我们将使用Docker在本地部署DashDot服务器仪表盘&#xff0c;并且结合cpolar内网穿透工具可以实现公网实时监测服务器系统、处理器、内存、存储、网络、显卡等&#xff0c;并且拥有API接…

前端开发攻略---拖动归类,将元素拖拽到相应位置

1、演示 2、代码 <!DOCTYPE html><html lang"en"><head><meta charset"UTF-8" /><meta http-equiv"X-UA-Compatible" content"IEedge" /><meta name"viewport" content"widthdevice-…