三方库移植之NAPI开发[2]C/C++与JS的数据类型转

通过NAPI框架进行C/C++与JS数据类型的转换

  • OpenHarmony NAPI将ECMAScript标准中定义的Boolean、Null、Undefined、Number、BigInt、String、Symbol和Object八种数据类型,以及函数对应的Function类型,统一封装成napi_value类型,下文中表述为JS类型,用于接收ArkUI应用传递过来的数据及返回数据给ArkUI应用。

ECMAScript是一种由Ecma国际(通过ECMA-262标准化的脚本程序设计语言。这种语言在万维网上应用广泛,它往往被称为JavaScript或JScript,所以它可以理解为是JavaScript的一个标准,但实际上后两者是ECMA-262标准的实现和扩展。

  • 下面通过扩展一个简单的接口------Add(num1, num2)讲述具体细节,接口使用同步方式实现,NAPI的同步方式调用的扩展API代码处理流程如下图。

.cpp源码实现

  • 在《三方库移植之NAPI开发[1]---Hello OpenHarmony NAPI》一文的基础上修改hellonapi.cpp文件,其余文件不变。

  • hellonapi.cpp内容如下:

    #include <string.h>
    #include "napi/native_node_api.h"
    #include "napi/native_api.h"

    //NAPI定义API方法(接口业务实现)时的接收参数为(napi_env, napi_callback_info),
    static napi_value Add(napi_env env, napi_callback_info info) {

      //获取2个参数,值的类型是js类型(napi_value)
      size_t requireArgc = 2;
      size_t argc = 2;
      napi_value args[2] = {nullptr};
      //NAPI提供了napi_get_cb_info()方法可从napi_callback_info中获取参数列表、this及其他数据
      NAPI_CALL(env, napi_get_cb_info(env, info, &argc, args, nullptr, nullptr));
    
      //获取并判断js参数类型
      napi_valuetype valuetype0;
      NAPI_CALL(env, napi_typeof(env, args[0], &valuetype0));
    
      napi_valuetype valuetype1;
      NAPI_CALL(env, napi_typeof(env, args[1], &valuetype1));
    
      if (valuetype0 != napi_number || valuetype1 != napi_number) {
      napi_throw_type_error(env, NULL, "Wrong arguments. 2 numbers are expected.");
      return NULL;
    }
    
      //将js类型(napi_value)的参数值转换成C++类型double
      double value0;
      NAPI_CALL(env, napi_get_value_double(env, args[0], &value0));
    
      double value1;
      NAPI_CALL(env, napi_get_value_double(env, args[1], &value1));
    
      //将结果由C++类型(double)转换成js类型(napi_value)
      //NAPI提供了一些方法以便将C/C++不同类型的值转为node_value类型,返回给JS代码。
      napi_value sum;
      NAPI_CALL(env, napi_create_double(env, value0 + value1, &sum));
    
     //返回napi_value类型结果
      return sum;
    

    }

    // napi_addon_register_func
    //2.指定模块注册对外接口的处理函数,具体扩展的接口在该函数中声明
    static napi_value registerFunc(napi_env env, napi_value exports)
    {

      // 在napi_property_descriptor desc[]中将编写C的"Add方法与对外暴露Js的接口"add"方法进行关联
      static napi_property_descriptor desc[] = {
          { "add", nullptr, Add, nullptr, nullptr, nullptr, napi_default, nullptr }
      };
      napi_define_properties(env, exports, sizeof(desc) / sizeof(desc[0]), desc);
      return exports;   
    

    }

    // 1.先定义napi_module,指定当前NAPI模块对应的模块名
    //以及模块注册对外接口的处理函数,具体扩展的接口在该函数中声明
    // nm_modname: 模块名称,对应eTS代码为import nm_modname from '@ohos.ohos_shared_library_name'
    //示例对应eTS代码为:import hellonapi from '@ohos.hellonapi'
    static napi_module hellonapiModule = {
    .nm_version = 1,
    .nm_flags = 0,
    .nm_filename = nullptr,
    .nm_register_func = registerFunc, // 模块对外接口注册函数
    .nm_modname = "hellonapi", // 自定义模块名
    .nm_priv = ((void*)0),
    .reserved = { 0 },
    };

    //3.模块定义好后,调用NAPI提供的模块注册函数napi_module_register(napi_module* mod)函数注册到系统中。
    // register module,设备启动时自动调用此constructor函数,把模块定义的模块注册到系统中
    extern "C" attribute((constructor)) void hellonapiModuleRegister()
    {
    napi_module_register(&hellonapiModule);
    }

.cpp源码解析

注册NAPI模块、添加接口声明

// napi_addon_register_func
//2.指定模块注册对外接口的处理函数,具体扩展的接口在该函数中声明
static napi_value registerFunc(napi_env env, napi_value exports)
{
    static napi_property_descriptor desc[] = {
        { "add", nullptr, Add, nullptr, nullptr, nullptr, napi_default, nullptr }
    };
    napi_define_properties(env, exports, sizeof(desc) / sizeof(desc[0]), desc);
    return exports;   
}

// 1.先定义napi_module,指定当前NAPI模块对应的模块名
//以及模块注册对外接口的处理函数,具体扩展的接口在该函数中声明
// nm_modname: 模块名称,对应eTS代码为import nm_modname from '@ohos.ohos_shared_library_name'
//示例对应eTS代码为:import hellonapi from '@ohos.hellonapi'
static napi_module hellonapiModule = {
    .nm_version = 1,
    .nm_flags = 0,
    .nm_filename = nullptr,
    .nm_register_func = registerFunc, // 模块对外接口注册函数
    .nm_modname = "hellonapi",  // 自定义模块名
    .nm_priv = ((void*)0),
    .reserved = { 0 },
};

//3.模块定义好后,调用NAPI提供的模块注册函数napi_module_register(napi_module* mod)函数注册到系统中。
// register module,设备启动时自动调用此constructor函数,把模块定义的模块注册到系统中
extern "C" __attribute__((constructor)) void hellonapiModuleRegister()
{
    napi_module_register(&hellonapiModule);
}

接口业务实现C/C++代码

//NAPI定义API方法(接口业务实现)时的接收参数为(napi_env, napi_callback_info),
//其中napi_callback_info为上下文的信息
static napi_value Add(napi_env env, napi_callback_info info) {

    //获取2个参数,值的类型是js类型(napi_value)
    size_t requireArgc = 2;
    size_t argc = 2;
    napi_value args[2] = {nullptr};
    NAPI_CALL(env, napi_get_cb_info(env, info, &argc, args, nullptr, nullptr));

    //NAPI框架提供了napi_typeof方法用于获取指定js参数类型
    napi_valuetype valuetype0;
    NAPI_CALL(env, napi_typeof(env, args[0], &valuetype0));

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

    if (valuetype0 != napi_number || valuetype1 != napi_number) {
    napi_throw_type_error(env, NULL, "Wrong arguments. 2 numbers are expected.");
    return NULL;
  }

    //将js类型(napi_value)的参数值转换成C++类型double
    double value0;
    NAPI_CALL(env, napi_get_value_double(env, args[0], &value0));

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

    //将结果由C++类型(double)转换成js类型(napi_value)
    napi_value sum;
    NAPI_CALL(env, napi_create_double(env, value0 + value1, &sum));

   //返回napi_value类型结果
    return sum;
}
获取参数
static napi_value Add(napi_env env, napi_callback_info info) {
......
}
  • NAPI定义API方法时的接收参数为(napi_env, napi_callback_info)

    • 其中napi_callback_info为上下文的信息。
  • NAPI提供了napi_get_cb_info()方法可从napi_callback_info中获取参数列表、this及其他数据。

napi_get_cb_info函数在ohos3.2beta3源码foundation/arkui/napi/native_engine/native_api.cpp中

// Methods to work with napi_callbacks
// Gets all callback info in a single call. (Ugly, but faster.)
NAPI_EXTERN napi_status napi_get_cb_info(napi_env env,              // [in] NAPI environment handle
                                         napi_callback_info cbinfo, // [in] Opaque callback-info handle
                                         size_t* argc,         // [in-out] Specifies the size of the provided argv array
                                                               // and receives the actual count of args.
                                         napi_value* argv,     // [out] Array of values
                                         napi_value* this_arg, // [out] Receives the JS 'this' arg for the call
                                         void** data)          // [out] Receives the data pointer for the callback.
{
    CHECK_ENV(env);
    CHECK_ARG(env, cbinfo);

    auto info = reinterpret_cast<NativeCallbackInfo*>(cbinfo);

    if ((argc != nullptr) && (argv != nullptr)) {
        size_t i = 0;
        for (i = 0; (i < *argc) && (i < info->argc); i++) {
            argv[i] = reinterpret_cast<napi_value>(info->argv[i]);
        }
        *argc = i;
    }

    if (argc != nullptr) {
        *argc = info->argc;
    }

    if (this_arg != nullptr) {
        *this_arg = reinterpret_cast<napi_value>(info->thisVar);
    }

    if (data != nullptr && info->functionInfo != nullptr) {
        *data = info->functionInfo->data;
    }

    return napi_clear_last_error(env);
}

napi_get_cb_info函数说明如下:

napi_status napi_get_cb_info(napi_env env,              
                             napi_callback_info cbinfo, 
                             size_t* argc,                          
                             napi_value* argv,     
                             napi_value* this_arg, 
                             void** data)         
  • 参数说明:

    • [in] env: 传入接口调用者的环境,包含js引擎等,由框架提供,默认情况下直接传入即可
    • [in] cbinfo: napi_callback_info对象,上下文的信息
    • [in-out] argc: argv数组的长度。若napi_callback_info中实际包含的参数的个数大于请求的数量argc,将只复制argc的值所指定数量的参数只argv中。若实际的参数个数小于请求的数量,将复制全部的参数,数组多余的空间用空值填充,并将参数实际长度写入argc。
    • [out] argv: 用于接收参数列表
    • [out] this_arg: 用于接收this对象
    • [out] data: NAPI的上下文数据 返回值:返回napi_ok表示转换成功,其他值失败。下面的返回napi_status方法一样。
  • 在Add方法中,调用napi_get_cb_info函数:

    // env、info 参数由NAPI框架传入
    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_CALL(env, napi_get_cb_info(env, info, &argc, args, nullptr, nullptr));
    napi_value sum;
    return sum;
    }

JS类型值转换为C/C++类型的值
  • 此示例中传入的参数是Javascript值类型,被NAPI框架封装成统一的唯一类型------napi_value类型,为了能够进行计算,我们需要获取其对应在C/C++中的类型的值。
    • NAPI提供了包括以下方法以便获取不同类型的值(ohos3.2beta3源码foundation/arkui/napi/native_engine/native_api.cpp中)
      • napi_get_value_double
      • napi_get_value_int32
      • napi_get_value_uint32
      • napi_get_value_int64
      • napi_get_value_bool
      • napi_get_value_string_latin1(Copies LATIN-1 encoded bytes from a string into a buffer)
      • napi_get_value_string_utf8(Copies UTF-8 encoded bytes from a string into a buffer)
      • napi_get_value_string_utf16
      • napi_get_value_external
      • napi_get_value_bigint_int64
      • napi_get_value_bigint_uint64
      • napi_get_value_bigint_words
    • 此示例hellonapi.cpp中使用到了napi_get_value_double方法,函数定义如下:
NAPI_EXTERN napi_status napi_get_value_double(napi_env env, napi_value value, double* result)
{
    CHECK_ENV(env);
    CHECK_ARG(env, value);
    CHECK_ARG(env, result);

    auto nativeValue = reinterpret_cast<NativeValue*>(value);

    RETURN_STATUS_IF_FALSE(env, nativeValue->TypeOf() == NATIVE_NUMBER, napi_number_expected);

    *result = *reinterpret_cast<NativeNumber*>(nativeValue->GetInterface(NativeNumber::INTERFACE_ID));
    return napi_clear_last_error(env);
}

参数说明:

  • [in] env: 传入接口调用者的环境,包含js引擎等,由框架提供,默认情况下直接传入即可。
  • [in] value: 传入要转换的napi_value类型数据对象(可视为一个JS对象)。
  • [out] result: 转换出对应类型(double)结果。 返回值:返回napi_ok表示转换成功,其他值失败。

获取参数的C/C++类型的值前,需要先判断值的类型,本示例需要判断传入参数的JS值必须为number类型

  • NAPI框架提供了napi_typeof方法用于获取指定对象的类型,其函数定义如下:

    // Methods to get the native napi_value from Primitive type
    NAPI_EXTERN napi_status napi_typeof(napi_env env, napi_value value, napi_valuetype* result)
    {
    CHECK_ENV(env);
    CHECK_ARG(env, value);
    CHECK_ARG(env, result);

      auto nativeValue = reinterpret_cast<NativeValue*>(value);
    
      *result = (napi_valuetype)nativeValue->TypeOf();
      return napi_clear_last_error(env);
    

    }

参数说明:

  • [in] env: 传入接口调用者的环境,包含js引擎等,由框架提供,默认情况下直接传入即可。
  • [in] value: 传入要转换的napi_value类型数据对象(可视为一个JS对象)。
  • [out] result: 返回value参数对应的JS类型。
    • napi_valuetype对应了ECMAScript标准中定义的Boolean、Null、Undefined、Number、BigInt、String、Symbol和Object八种数据类型,以及函数对应的Function类型。
    • 另外,napi_valuetype还包括了一个napi_external类型,其表示没有任何属性也没有任何原型的对象。

综上所述参数类型判断及值转换,示例代码如下:

static napi_value Add(napi_env env, napi_callback_info info) {
    // 1\. 获取2个参数,值的类型是js类型(napi_value)
    size_t requireArgc = 2;
    size_t argc = 2;
    napi_value args[2] = {nullptr};

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

    // 2\. 获取并判断js参数类型
    napi_valuetype valuetype0;
    NAPI_CALL(env, napi_typeof(env, args[0], &valuetype0));

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

    //输入的数据类型异常处理
    if (valuetype0 != napi_number || valuetype1 != napi_number) {
    napi_throw_type_error(env, NULL, "Wrong arguments. 2 numbers are expected.");
    return NULL;
  }

    // 3\. 将js类型(napi_value)的参数值转换成C++类型double
    double value0;
    NAPI_CALL(env, napi_get_value_double(env, args[0], &value0));

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

    napi_value sum;
    return sum;
计算结果转换为JS类型并返回
static napi_value Add(napi_env env, napi_callback_info info) {
···
  // 4\. 将结果由C++类型(double)转换成js类型(napi_value)
  napi_value sum;
  NAPI_CALL(env, napi_create_double(env, value0 + value1, &sum));
··· 
}
  • 计算的结果是C/C++类型,需要转换成NAPI node_value类型返回给JS。

  • NAPI提供了一些方法以便将C/C++不同类型的值转为node_value类型,返回给JS代码。例如:

    • napi_create_double
    • napi_create_int32
    • napi_create_uint32
    • napi_create_int64
    • napi_create_string_latin1
    • napi_create_string_utf8
    • napi_create_string_utf16
  • 以napi_create_double方法为例,函数定义如下:

NAPI_EXTERN napi_status napi_create_int32(napi_env env, int32_t value, napi_value* result)
{
    CHECK_ENV(env);
    CHECK_ARG(env, result);

    auto engine = reinterpret_cast<NativeEngine*>(env);
    auto resultValue = engine->CreateNumber(value);

    *result = reinterpret_cast<napi_value>(resultValue);
    return napi_clear_last_error(env);
}

参数说明:

[in] env: 传入接口调用者的环境,包含js引擎等,由框架提供,默认情况下直接传入即可.

[in] value: 传入要转换的double类型数据值.

[out] result: 转换出结果

ArkUI应用实现代码

ArkUI应用实现目录结构

index.ets内容如下:

index.ets

import hellonapi from '@ohos.hellonapi'

@Entry
@Component
export struct HelloNAPI {
  private textInputController1: TextInputController = new TextInputController()
  private textInputController2: TextInputController = new TextInputController()
  private tittle: string = 'C/C++与JS的数据类型转换'
  private message: string = '计算x+y'
  private tipsNum1: string = '请输入X:'
  private tipsNum2: string = '请输入Y:'
  private tipsResult: string = '结果:'
  private buttonSubmit: string = '计算'
  @State result: number = 0.0
  @State num1: number = 0.0
  @State num2: number = 0.0

  build() {
    Row() {
      Column() {
        Row(){
          Text(this.tittle).height('100%').align(Alignment.Center).fontSize(50).fontWeight(800)
        }.height('30%').width('100%').justifyContent(FlexAlign.Center)

        Row(){
          Text(this.message).height('100%').align(Alignment.Center).fontSize(35).fontWeight(500)
        }.height('15%').width('100%').justifyContent(FlexAlign.Center)

        Row(){
          Text(this.tipsNum1).fontColor(Color.Black).fontSize('65px').width('30%').height('100%').margin({left:30})
          TextInput({ placeholder: 'X', controller:this.textInputController1}).type(InputType.Number)
            .height('100%').width('60%').margin({left:10,right:30}).fontSize('25px')
            .onChange(value =>{this.num1 = parseFloat(value)})
        }.height('6%').width('100%').justifyContent(FlexAlign.Start)

        Row(){
          Text(this.tipsNum2).fontColor(Color.Black).fontSize('65px').width('30%').height('100%').margin({left:30})
          TextInput({ placeholder: 'Y', controller:this.textInputController2}).type(InputType.Number)
            .height('100%').width('60%').margin({left:10,right:30}).fontSize('25px')
            .onChange(value =>{this.num2 = parseFloat(value)})
        }.height('6%').width('100%').margin({top:20})

        Row(){
          Text(this.tipsResult).fontColor(Color.Black).fontSize(35).width('40%').height('100%').margin({left:30})
          Text(''+this.result).fontColor(Color.Black).fontSize(35).width('60%').height('100%')
        }.height('10%').width('100%').touchable(false)

        Row(){
          Button(this.buttonSubmit)
            .fontSize(37)
            .fontWeight(FontWeight.Bold)
            .margin({top:5})
            .height(80)
            .width(200)
            .onClick(() => {

              //hellonapi为BUILD.gn文件中定义的ohos_shared_library结构体名称
              this.result = hellonapi.add(this.num1,this.num2)
            })
        }.height('30%').width('100%').justifyContent(FlexAlign.Center)
      }
      .width('100%')
    }
    .height('100%')
  }
}

效果图如下:

index.ets解析

  • 参数说明
字段 类型 说明
tittle string 标题
message string 说明
tipsNum1 number 提示输入第一个参数
tipsNum2 number 提示输入第二个参数
tipsResult string 提示结果
buttonSubmit string 计算按钮名称
result string 结果
num1 number 输入的第一个数
num2 number 输入的第二个数
  • 设置参数

    import hellonapi from '@ohos.hellonapi'

    @Entry
    @Component
    export struct HelloNAPI {
    private textInputController1: TextInputController = new TextInputController()
    private textInputController2: TextInputController = new TextInputController()
    private tittle: string = 'C/C++与JS的数据类型转换'
    private message: string = '计算x+y'
    private tipsNum1: string = '请输入X:'
    private tipsNum2: string = '请输入Y:'
    private tipsResult: string = '结果:'
    private buttonSubmit: string = '计算'
    @State result: number = 0.0
    @State num1: number = 0.0
    @State num2: number = 0.0
    ...
    build() {
    ...
    }
    }

  • 界面实现

    import hellonapi from '@ohos.hellonapi'

    @Entry
    @Component
    export struct HelloNAPI {
    ...
    build() {
    Row() {
    Column() {
    Row(){
    Text(this.tittle).height('100%').align(Alignment.Center).fontSize(50).fontWeight(800)
    }.height('30%').width('100%').justifyContent(FlexAlign.Center)

          Row(){
            Text(this.message).height('100%').align(Alignment.Center).fontSize(35).fontWeight(500)
          }.height('15%').width('100%').justifyContent(FlexAlign.Center)
    
          Row(){
            Text(this.tipsNum1).fontColor(Color.Black).fontSize('65px').width('30%').height('100%').margin({left:30})
            TextInput({ placeholder: 'X', controller:this.textInputController1}).type(InputType.Number)
              .height('100%').width('60%').margin({left:10,right:30}).fontSize('25px')
              .onChange(value =>{this.num1 = parseFloat(value)})
          }.height('6%').width('100%').justifyContent(FlexAlign.Start)
    
          Row(){
            Text(this.tipsNum2).fontColor(Color.Black).fontSize('65px').width('30%').height('100%').margin({left:30})
            TextInput({ placeholder: 'Y', controller:this.textInputController2}).type(InputType.Number)
              .height('100%').width('60%').margin({left:10,right:30}).fontSize('25px')
              .onChange(value =>{this.num2 = parseFloat(value)})
          }.height('6%').width('100%').margin({top:20})
    
          Row(){
            Text(this.tipsResult).fontColor(Color.Black).fontSize(35).width('40%').height('100%').margin({left:30})
            Text(''+this.result).fontColor(Color.Black).fontSize(35).width('60%').height('100%')
          }.height('10%').width('100%').touchable(false)
    
          Row(){
            Button(this.buttonSubmit)
              .fontSize(37)
              .fontWeight(FontWeight.Bold)
              .margin({top:5})
              .height(80)
              .width(200)
              .onClick(() => {
    
                //hellonapi为BUILD.gn文件中定义的ohos_shared_library结构体名称
                this.result = hellonapi.add(this.num1,this.num2)
              })
          }.height('30%').width('100%').justifyContent(FlexAlign.Center)
        }
        .width('100%')
      }
      .height('100%')
    }
    

    }

  • 绑定事件、关联参数
    两个TextInput组件分别绑定onChange事件,并分别关联num1,num2来记录输入的参数

          Row(){
            Text(this.tipsNum1).fontColor(Color.Black).fontSize('65px').width('30%').height('100%').margin({left:30})
            TextInput({ placeholder: 'X', controller:this.textInputController1}).type(InputType.Number)
              .height('100%').width('60%').margin({left:10,right:30}).fontSize('25px')
              .onChange(value =>{this.num1 = parseFloat(value)})
          }.height('6%').width('100%').justifyContent(FlexAlign.Start)
    
          Row(){
            Text(this.tipsNum2).fontColor(Color.Black).fontSize('65px').width('30%').height('100%').margin({left:30})
            TextInput({ placeholder: 'Y', controller:this.textInputController2}).type(InputType.Number)
              .height('100%').width('60%').margin({left:10,right:30}).fontSize('25px')
              .onChange(value =>{this.num2 = parseFloat(value)})
          }.height('6%').width('100%').margin({top:20})
    
  • Button组件添加点击事件,调用hellonapiu.cpp中的Add方法(调用js中的add,add和Add已经在napi.cpp中绑定)

    Row(){
    Button(this.buttonSubmit)
    .fontSize(37)
    .fontWeight(FontWeight.Bold)
    .margin({top:5})
    .height(80)
    .width(200)
    .onClick(() => {

                //hellonapi为BUILD.gn文件中定义的ohos_shared_library结构体名称
              this.result = hellonapi.add(this.num1,this.num2)
              })
    
  • 通过NAPI框架输入到C的Add函数的JS参数是num1和num2,输出的JS参数是result

@ohos.hellonapi.d.ts接口文档

declare namespace hellonapi {
	export const add: (a: number, b: number) => number;
    /**
     * 
     *
     * @since 9
     * @syscap SystemCapability.Ability.AbilityRuntime.AbilityCore
     */

}
export default hellonapi;

总结

hellonapi.cpp

index.ets

为了帮助到大家能够更有效的学习OpenHarmony 开发的内容,下面特别准备了一些相关的参考学习资料:

OpenHarmony 开发环境搭建:https://qr18.cn/CgxrRy

《OpenHarmony源码解析》:https://qr18.cn/CgxrRy

  • 搭建开发环境
  • Windows 开发环境的搭建
  • Ubuntu 开发环境搭建
  • Linux 与 Windows 之间的文件共享
  • ......

系统架构分析:https://qr18.cn/CgxrRy

  • 构建子系统
  • 启动流程
  • 子系统
  • 分布式任务调度子系统
  • 分布式通信子系统
  • 驱动子系统
  • ......

OpenHarmony 设备开发学习手册:https://qr18.cn/CgxrRy

OpenHarmony面试题(内含参考答案):https://qr18.cn/CgxrRy

相关推荐
yuyanjingtao23 分钟前
CCF-GESP 等级考试 2023年9月认证C++四级真题解析
c++·青少年编程·gesp·csp-j/s·编程等级考试
闻缺陷则喜何志丹38 分钟前
【C++动态规划 图论】3243. 新增道路查询后的最短距离 I|1567
c++·算法·动态规划·力扣·图论·最短路·路径
charlie1145141911 小时前
C++ STL CookBook
开发语言·c++·stl·c++20
小林熬夜学编程1 小时前
【Linux网络编程】第十四弹---构建功能丰富的HTTP服务器:从状态码处理到服务函数扩展
linux·运维·服务器·c语言·网络·c++·http
倔强的石头1061 小时前
【C++指南】类和对象(九):内部类
开发语言·c++
Jackey_Song_Odd1 小时前
C语言 单向链表反转问题
c语言·数据结构·算法·链表
A懿轩A2 小时前
C/C++ 数据结构与算法【数组】 数组详细解析【日常学习,考研必备】带图+详细代码
c语言·数据结构·c++·学习·考研·算法·数组
机器视觉知识推荐、就业指导2 小时前
C++设计模式:享元模式 (附文字处理系统中的字符对象案例)
c++
半盏茶香2 小时前
在21世纪的我用C语言探寻世界本质 ——编译和链接(编译环境和运行环境)
c语言·开发语言·c++·算法
枫叶丹43 小时前
【HarmonyOS之旅】HarmonyOS开发基础知识(三)
华为od·华为·华为云·harmonyos