仓颉语言与ArkTS互操作

在 OpenHarmony 系统上,ArkTS 具备完整广泛的生态,为复用 ArkTS 生态,仓颉支持与 ArkTS 高效跨语言互通。

仓颉-ArkTS 互操作基于仓颉 CFFI 能力,通过调用 ArkTS 运行时接口,为用户提供库级别的 ArkTS 互操作能力。

使用场景:

  1. 在 ArkTS 应用开发仓颉模块:把用户仓颉代码封装成为 ArkTS 模块,能够被 ArkTS 代码加载和调用。
  2. 在仓颉应用里使用 ArkTS 存量库:在仓颉代码里创建新的 ArkTS 运行时,并加载和执行 ArkTS 的字节码。

互操作库的主要组成和功能:

  1. JSValue: 统一的 ArkTS 数据类型,在跨语言调用中做传参,对 ArkTS 类型做判断和做数据转换。
  2. JSContext: 一个 ArkTS 互操作上下文,用户创建 ArkTS 数据,辅助把 JSValue 转换为仓颉数据。
  3. JSCallInfo: 一次 ArkTS 函数调用的参数集合,包含所有的入参和 this 指针。
  4. JSRuntime: 一个由仓颉创建的 ArkTS 运行时。

一、在 ArkTS 应用里开发仓颉模块

开发仓颉互操作模块:

1.【仓颉侧】导入互操作库。

import ohos.ark_interop.*
  1. 【仓颉侧】定义要导出的函数,可被 ArkTS 调用的仓颉函数的类型是固定的:(JSContext, JSCallInfo)->JSValue。
func addNumber(context: JSContext, callInfo: JSCallInfo): JSValue {// 从 JSCallInfo 获取参数列表let arg0: JSValue = callInfo[0]let arg1: JSValue = callInfo[1]// 把 JSValue 转换为仓颉类型let a: Float64 = arg0.toNumber()let b: Float64 = arg1.toNumber()// 实际仓颉函数行为let value = a + b// 把结果转换为 JSValuelet result: JSValue = context.number(value).toJSValue()// 返回 JSValuereturn result
}
  1. 【仓颉侧】注册要导出的函数。

// 类名没有影响

class Main {// 定义静态构造函数(也可用全局变量和静态变量的初始化表达式触发)static init() {// 注册键值对JSModule.registerModule {context, exports =>exports["addNumber"] = context.function(addNumber).toJSValue()}}
}

4.【ArkTS 侧】导入 ark_interop_loader,这是一个在 ohos-sdk 中提供的 napi 模块,作为仓颉运行时的启动器和仓颉模块的加载器。

import {requireCJLib} from "libark_interop_loader.so"

5.【ArkTS 侧】定义仓颉库导出的接口。

interface CangjieLib {// 定义的仓颉互操作函数,名称与仓颉侧注册名称一致。一般先定义 ArkTS 函数声明,在实现仓颉函数时根据声明来解析参数和返回。addNumber(a: number, b: number): number;
}

6.【ArkTS 侧】导入和调用仓颉库。

// 导入仓颉库,仓颉模块默认编译产物是 libentry_default.so,用户可以在 cjpm.toml 中修改配置。
const cjLib = requireCJLib("libentry_default.so") as CangjieLib;
// 调用仓颉接口
let result = cjLib.addNumber(1, 2);
console.log(`1 + 2 = ${result}`);

二、在仓颉应用里使用 ArkTS 模块

ArkTS 模块的编译产物主要有两种:

  1. C 代码(+ArkTS)编译成 so。
  2. 纯 ArkTS 代码编译成 abc。

2.1 加载 ArkTS so 模块

ArkTS so 模块根据部署方式的不同,分为以下几种:

  1. 随系统发布,在镜像的/system/lib64/module目录下。
  2. 随应用(hap)发布,在应用的/libs/arm64-v8a目录下,安装后在设备上的全局路径(通过hdc shell观察到的路径):/data/app/el1/bundle/public/${bundleName}/libs/arm64、沙箱路径(运行时可访问路径):/data/storage/el1/bundle/libs/arm64。
    随动态库(hsp)发布。
  3. 这里主要介绍怎么加载随系统发布的 so 模块,这些 so 在 OpenHarmony 的官方文档里会有开发文档。

接下来以相册管理模块作为示例,详细的介绍加载流程。

  1. 查看 ArkTS 文档,其导入模块的范本如下。
import photoAccessHelper from '@ohos.file.photoAccessHelper';
  1. 创建 ArkTS 运行时,准备互操作上下文。
import ohos.ark_interop.*func tryLoadArkTSSo() {// 创建新的 ArkTS 运行时let runtime = JSRuntime()// 获取互操作上下文let context = runtime.mainContext...
}
  1. 根据 ArkTS 文档里模块导入名称,推导仓颉的模块导入参数。
ArkTS 导入名仓颉导入参数说明
@ohos.file.photoAccessHelper(“file.photoAccessHelper”)以 @ohos 开头,那么参数只需要去掉 “@ohos.”。
@hms.core.push.pushService(“core.push.pushService”, prefix: “hms”)以非 @ohos 开头,那么参数去掉 “@xxx.”,并把 xxx 作为第二个参数。
  1. 导入 ArkTS 模块。
func tryLoadArkTSSo() {...let module = context.requireSystemNativeModule("file.photoAccessHelper")
}

模块导入进来是一个 JSValue,接下来可以按照操作 ArkTS 数据的方法去操作模块。

三、在仓颉里操作 ArkTS 数据

从 ArkTS 传过来的参数,其原始类型是JSValue,这是一个匿名类型的数据,首先需要知晓其类型。

  • 通过JSValue.typeof()获取其类型枚举JSType。
  • 通过其他途径(包括但不限于阅读 ArkTS 源码、参考文档以及开发者口述)知晓其类型,然后通过类型校验接口来验证,比如判断是否是 number 类型JSValue.isNumber()。

当知道其类型之后,再把JSValue转换为对应的仓颉类型或 ArkTS 引用。

  • 转换为仓颉类型,比如一个 ArkTS string 转换为仓颉 String,JSValue.toString(JSContext)。
  • 转换为 ArkTS 引用,比如一个 ArkTS string 转换为 JSString,JSValue.asString(JSContext)。

通过仓颉数据来构造 ArkTS 数据,是通过 JSContext 的方法类构造 ArkTS 数据。

一个应用进程可以存在多个 ArkTS 运行时,而 ArkTS 运行时之间的数据是不通用的,任何 ArkTS 数据都归属于一个特定的运行时,因此创建 ArkTS 数据接口是从运行时的角度出发。

以number举例,创建一个number的方式是JSContext.number(Float64)。

ArkTS 主要数据类型对应到仓颉类型的映射如下:

ArkTS 类型仓颉类型安全引用typeof 类型
undefined-JSUndefinedJSType.UNDEFINED
null-JSNullJSType.NULL
booleanBoolJSBooleanJSType.BOOL
numberFloat64JSNumberJSType.NUMBER
stringStringJSStringJSType.STRING
object-JSObjectJSType.OBJECT
Array-JSArrayJSType.OBJECT
bigintBigIntJSBigIntJSType.BIGINT
function-JSFunctionJSType.FUNCTION
symbol-JSSymbolJSType.SYMBOL

安全引用的安全体现在两个方面:

  • 类型安全,特定类型的接口只能从安全引用里访问,总是需要先做显式的类型转换再访问。
  • 生命周期安全,对于由 ArkTS 来分配和回收的对象,安全引用能保障这些对象的生命周期。

3.1 操作 ArkTS 对象

从一个互操作函数的实现举例,该函数在 ArkTS 的声明是:addByObject(args: {a: number; b: number}): number。

func addByObject(context: JSContext, callInfo: JSCallInfo): JSValue {// 获取首个参数let arg0 = callInfo[0]// 校验参数0是否是对象,否则返回undefinedif (!arg0.isObject()) {return context.undefined().toJSValue()}// 把参数0转换为JSObjectlet obj = arg0.asObject(context)// 从JSObject获取属性let argA = obj["a"]let argB = obj["b"]// 把JSValue转换为Float64let a = argA.toNumber()let b = argB.toNumber()let result = a + breturn context.number(result).toJSValue()
}

除了可以从对象上读取属性外,还可以对属性赋值或创建新属性,操作方式为 JSObject[key] = value,其中 key 可以是仓颉 String 、JSString 或 JSSymbol,value 是 JSValue 。

  • 说明

通过 JSObject[key] = value 定义属性时,该属性可写、可枚举、可配置。

更多参见JavaScript 标准内置对象

对属性赋值在以下几种场景会失败,失败之后没有异常或日志:

1.目标对象是 sealed 对象,由 Object.seal() 接口创建的对象具有不可修改的特性,无法创建新的属性和修改原有属性。
2. 目标属性的 writable 是 false ,由 Object.defineProperty(object, key, {writable: false, value: xxx}) 定义属性时,可以指定属性是否可写。

对于一个未知对象,可以枚举出该对象的可枚举属性:

func handleUnknownObject(context: JSContext, target: JSObject): Unit {// 枚举对象的可枚举属性let keys = target.keys()println("target keys: ${keys}")
}

创建一个新的 ArkTS 对象,可以通过 JSContext.object() 来创建。

对于 ArkTS 运行时,有一个特殊的对象,该对象是 ArkTS 全局对象,在任何 ArkTS 代码里都可以直接访问该对象下的属性,在仓颉侧可以通过 JSContext.global 来访问它。

3.2 调用 ArkTS 函数

拿到一个 ArkTS 函数后,可以在仓颉里直接调用,这里以一个互操作函数举例:addByCallback(a: number, b: number, callback: (result: number)=>void): void。

func addByCallback(context: JSContext, callInfo: JSCallInfo): JSValue {// 获取参数,并转换为Float64let a = callInfo[0].toNumber()let b = callInfo[1].toNumber()// 把第3个参数转换为JSFunctionlet callback = callInfo[2].asFunction(context)// 计算结果let result = a + b// 从仓颉Float64创建ArkTS numberlet retJSValue = context.number(result).toJSValue()// 调用回调函数callback.call(retJSValue)
}

这个用例里的函数是不带 this 指针的,针对需要 this 指针的方法调用,可以通过命名参数 thisArg 来指定。

func doSth(context: JSContext, callInfo: JSCallInfo): JSValue {let callback = callInfo[0].asFunction(context)let thisArg = callInfo[1]callback.call(thisArg: thisArg)
}

在 ArkTS 代码里,可以通过 对象.方法(…) 来进行调用,这时会隐式传递 this 指针。

class Someone {id: number = 0doSth(): void {console.log(`someone ${this.id} have done something`)}
}let target = new Someone()// 这里会隐式传递this指针,调用正常
target.doSth()let doSth = target.doSth;
// 这里没有传递this指针,会出现异常`can't read property of undefined`
doSth.call()

在仓颉里,对应的写法如下:

func doSth(context: JSContext, callInfo: JSCallInfo): JSValue {let object = callInfo[0].asObject(context)// 会隐式传递this指针,调用正常object.callMethod("doSth")let doSth = object["doSth"].asFunction(context)// 未传递this指针,会出现异常`can't read property of undefined`doSth.call()// 显式传递this指针,调用正常doSth.call(thisArg: object.toJSValue())
}

四、在 ArkTS 里操作仓颉对象

这里用例展示的是把仓颉对象分享到 ArkTS 运行时,使用 ArkTS 运行时的内存管理机制来控制仓颉对象的生命周期,并通过相关的互操作接口来访问该对象。

// 定义共享类
class Data <: SharedObject {Data(// 定义2个属性let id: Int64,let name: String) {}static init() {// 注册导出到ark的函数JSModule.registerFunc("createData", createData)JSModule.registerFunc("setDataId", setDataId)JSModule.registerFunc("getDataId", getDataId)}// 创建共享对象static func createData(context: JSContext, _: JSCallInfo): JSValue {// 创建仓颉对象let data = Data(1, "abc")// 创建js对仓颉对象的引用let jsExternal = context.external(data)// 返回js对仓颉对象的引用return jsExternal.toJSValue()}// 设置对象的idstatic func setDataId(context: JSContext, callInfo: JSCallInfo): JSValue {// 读取参数let arg0 = callInfo[0]let arg1 = callInfo[1]// 把参数0转换为js对仓颉对象的引用let jsExternal = arg0.asExternal(context)// 获取仓颉对象let data: Data = jsExternal.cast<Data>().getOrThrow()// 把参数1转换为Float64let value = arg1.toNumber()// 仓颉对象修改属性data.id = Int64(value)// 返回undefinedlet result = context.undefined().toJSValue()return result}// 获取对象的idstatic func getDataId(context: JSContext, callInfo: JSCallInfo): JSValue {let arg0 = callInfo[0]let jsExternal = arg0.asExternal(context)let data: Data = jsExternal.cast<Data>().getOrThrow()let result = context.number(Float64(data.id)).toJSValue()return result}
}
import {requireCJLib} from "libark_interop_loader.so"
// 定义导出符号
interface CustomLib {createData(): undefinedsetDataId(data: undefined, value: number): voidgetDataId(data: undefined): number
}// 加载自定义库
const cjLib = requireCJLib("libentry_default.so") as CustomLib// 创建共享对象
let data = cjLib.createData()
// 操作对象属性
cjLib.setDataId(data, 3)
let id = cjLib.getDataId(data)console.log("id is " + id)

JSExternal 对象在 ArkTS 里的类型会被识别为 undefined ,直接使用它来作为参数很容易被传递错误的参数。在使用前一个用例的仓颉库时:

...
// 创建共享对象
let data = cjLib.createData()
// 操作对象属性
cjLib.setDataId(undefined, 3) // 错误的参数,应该传递的是仓颉引用,但是编译器能通过编译
let id = cjLib.getDataId(data)
...

4.1 把仓颉对象的引用挂在 JSObject 上传递到 ArkTS

在实际开发接口时,可以把 JSExternal 对象绑定到一个 JSObject 对象上,把 JSExternal 的数据隐藏起来,以此来提高接口的安全性。

下面通过一个例子来展示:

// 定义共享类
class Data <: SharedObject {Data(// 定义2个属性var id: Int64,let name: String) {}static init() {// 注册导出到ark的函数JSModule.registerFunc("createData", createData)}// 创建共享对象static func createData(context: JSContext, _: JSCallInfo): JSValue {let data = Data(1, "abc")let jsExternal = context.external(data)// 创建空JSObjectlet object = context.object()// 把js对仓颉对象的引用挂在JSObject的隐藏属性上object.attachCJObject(jsExternal)// 为js对象增加2个方法object["setId"] = context.function(setDataId).toJSValue()object["getId"] = context.function(getDataId).toJSValue()return object.toJSValue()}// 设置对象的idstatic func setDataId(context: JSContext, callInfo: JSCallInfo): JSValue {// 获取this指针let thisArg = callInfo.thisArglet arg0 = callInfo[0]// 把this指针转换为JSObjectlet thisObject = thisArg.asObject(context)// 从JSObject上获取隐藏属性let jsExternal = thisObject.getAttachInfo().getOrThrow()// 从js对仓颉对象的引用上获取仓颉对象let data = jsExternal.cast<Data>().getOrThrow()// 把参数0转换为Float64let value = arg0.toNumber()// 修改仓颉对象的属性data.id = Int64(value)let result = context.undefined()return result.toJSValue()}// 获取对象的idstatic func getDataId(context: JSContext, callInfo: JSCallInfo): JSValue {let thisArg = callInfo.thisArglet thisObject = thisArg.asObject(context)let jsExternal = thisObject.getAttachInfo().getOrThrow()let data = jsExternal.cast<Data>().getOrThrow()let result = context.number(Float64(data.id)).toJSValue()return result}
}
import {requireCJLib} from "libark_interop_loader.so"
// 定义导出符号
interface Data {setId(value: number): voidgetId(): number
}interface CustomLib {createData(): Data
}// 加载自定义库
const cjLib = requireCJLib("libentry_default.so") as CustomLib// 创建共享对象
let data = cjLib.createData()
// 操作对象属性
data.setId(3)
let id = data.getId()console.log("id is " + id)

4.2 为仓颉共享对象创建 JSClass

把所有的对象操作方法直接挂在对象上,一方面占用内存比较大,另一方面创建对象的开销比较大。对于追求性能的场景,可以定义一个 JSClass 来加速对象创建和减小内存占用。

// 定义共享类
class Data <: SharedObject {Data(// 定义2个属性var id: Int64,let name: String) {}static init() {// 注册导出到ark的类JSModule.registerClass("Data") { context =>// 创建JSClasslet clazz = context.clazz(jsConstructor)// 增加方法clazz.addMethod(context.string("setId"), context.function(setDataId))clazz.addMethod(context.string("getId"), context.function(getDataId))return clazz}}// js构造函数static func jsConstructor(context: JSContext, callInfo: JSCallInfo): JSValue {// 获取this指针let thisArg = callInfo.thisArg// 转换为JSObjectlet thisObject = thisArg.asObject(context)// 创建创建对象let data = Data(1, "abc")// 创建js对仓颉对象的引用let jsExternal = context.external(data)// 设置JSObject属性thisObject.attachCJObject(jsExternal)return thisObject.toJSValue()}// 设置对象的idstatic func setDataId(context: JSContext, callInfo: JSCallInfo): JSValue {// 获取this指针let thisArg = callInfo.thisArg// 把this指针转换为JSObjectlet thisObject = thisArg.asObject(context)// 从JSObject上获取隐藏属性let jsExternal = thisObject.getAttachInfo().getOrThrow()// 从js对仓颉对象的引用上获取仓颉对象let data = jsExternal.cast<Data>().getOrThrow()let arg0 = callInfo[0]// 把参数0转换为Float64let value = arg0.toNumber()// 修改仓颉对象的属性data.id = Int64(value)let result = context.undefined()return result.toJSValue()}// 获取对象的idstatic func getDataId(context: JSContext, callInfo: JSCallInfo): JSValue {let thisArg = callInfo.thisArglet thisObject = thisArg.asObject(context)let jsExternal = thisObject.getAttachInfo().getOrThrow()let data = jsExternal.cast<Data>().getOrThrow()let result = context.number(Float64(data.id)).toJSValue()return result}
}
import {requireCJLib} from "libark_interop_loader.so"
// 定义Data的接口
interface Data {setId(value: number): voidgetId(): number
}interface CustomLib {// 定义Data的构造函数(JSClass)Data: {new (): Data}
}// 加载自定义库
const cjLib  = requireCJLib("libentry_default.so") as CustomLib// 创建共享对象
let data = new cjLib.Data()
// 操作对象属性
data.setId(3)
let id = data.getId()console.log("id is " + id)

五、ArkTS 互操作和仓颉多线程

ArkTS 是单线程执行的虚拟机,在运行时上没有对并发做任何的容错;而仓颉在语法上支持内存共享的多线程。

如果在互操作的场景不加限制的使用多线程,可能会导致无法预期的错误,因此需要一些规范和指引来保证程序正常执行:

  1. ArkTS 代码以及大部分互操作接口只能在 ArkTS 线程上执行,否则会抛出仓颉异常。
  2. 在进入其他线程前,需要把所有依赖的 ArkTS 数据转换为仓颉数据。
  3. 在其他线程如果想要使用 ArkTS 接口,需要通过 context.postJSTask 切换到 ArkTS 线程来执行。

下面通过一个用例来展示具体做法,该用例是互操作函数,该函数的功能是对两个数字相加,并调用回调来返回相加数。

import {requireCJLib} from "libark_interop_loader.so"
// 定义导出的接口
interface CustomLib {addNumberAsync(a: number, b: number, callback: (result: number)=>void): void
}
// 导入仓颉库
const cjLib = requireCJLib("libentry_default.so") as CangjieLib;
// 调用仓颉函数
cjLib.addNumberAsync(1, 2, (result)=> {console.log("1 + 2 = " + result)
})
// 类名没有影响
class Main {// 定义静态构造函数static init() {// 注册键值对JSModule.registerFunc("addNumberAsync", addNumberAsync)}
}func addNumberAsync(context: JSContext, callInfo: JSCallInfo): JSValue {// 从JSCallInfo获取参数列表let arg0: JSValue = callInfo[0]let arg1: JSValue = callInfo[1]let arg2: JSValue = callInfo[2]// 把JSValue转换为仓颉类型let a: Float64 = arg0.toNumber()let b: Float64 = arg1.toNumber()let callback = arg2.asFunction(context)// 新建仓颉线程spawn {// 实际仓颉函数行为let value = a + b// 发起异步回调context.postJSTask {// 创建resultlet result = context.number(value)// 调用js回调callback.call(result)}}// 返回 voidreturn context.undefined()
}

在 ArkTS 存在着 Promise,这是对回调机制的一种封装,配合 async 、 await 的语法让回调机制变成同步调用的形式。对于上一个用例,使用 Promise 的形式来定义接口和访问:

// 接口定义
func addNumberAsync(context: JSContext, callInfo: JSCallInfo): JSValue {// 参数转换为仓颉类型let a = callInfo[0].toNumber()let b = callInfo[1].toNumber()// 创建PromiseCapability对象let promise = context.promiseCapability()// 创建新线程spawn {// 在新线程执行仓颉逻辑let result = a + b// 切换到ArkTS线程context.postJSTask {// 在ArkTS线程执行resolvepromise.resolve(context.number(result).toJSValue())}}// 返回Promisepromise.toJSValue()
}
// ArkTS 调用
import {requireCJLib} from "libark_interop_loader.so"
// 定义导出的接口
interface CustomLib {addNumberAsync(a: number, b: number): Promise<number>
}async function main() {// 导入仓颉库const cjLib = requireCJLib("libentry_default.so") as CangjieLib;// 调用仓颉函数let result = await cjLib.addNumberAsync(1, 2)console.log("1 + 2 = " + result)
}main()

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

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

相关文章

深入理解Netty的Pipeline机制:原理与实践详解

深入理解Netty的Pipeline机制&#xff1a;原理与实践详解 Netty是一个基于Java的高性能异步事件驱动的网络应用框架&#xff0c;广泛应用于高并发网络编程。&#xff08;学习netty请参考&#xff1a;深入浅出Netty&#xff1a;高性能网络应用框架的原理与实践&#xff09;Nett…

探索Agent AI智能体的未来

随着人工智能&#xff08;AI&#xff09;技术的飞速发展&#xff0c;Agent AI智能体正成为一种改变世界的新力量。这些智能体不仅在当前的技术领域中发挥着重要作用&#xff0c;而且在未来将以更深远的影响改变我们的生活、工作和社会结构。本文将探讨Agent AI智能体的现状、潜…

微信小程序-伪类选择器

一.伪类选择器 结构伪类常见书写方式&#xff1a; 第一类&#xff1a;找第几个孩子 1. :first-child 找第一个孩子2. :last-child 找最后一个孩子3. :nth-child()&#xff0c;正着找数字&#xff1a;写数字几就是找第几个孩子&#xff0c;2n或者even:找偶数2n1或者o…

“论微服务架构及其应用”写作框架,软考高级,系统架构设计师

论文真题 论微服务架构及其应用近年来&#xff0c;随着互联网行业的迅猛发展&#xff0c;公司或组织业务的不断扩张&#xff0c;需求的快速变化以及用户量的不断增加&#xff0c;传统的单块&#xff08;Monolithic&#xff09;软件架构面临着越来越多的挑战&#xff0c;已逐渐…

一个关于空格的Sql Server面试题

引子 先上题目&#xff1a; 回答下面sql 的输出结果 declare s1 varchar(10) declare s2 varchar(10) set s1a b set s2a b if s1s2 select true 答案是 true 那么上面的 s1 和 s2 是否相等的呢&#xff1f; 我们再看看下面的sql declare s1 varchar(10) declare s2 …

【建议收藏】Android中高级大厂面试源码秘籍,为你备战2021金三银四,直通大厂

首先来说下为什么要读源码&#xff0c;有学习源码的必要吗&#xff1f; 为什么要阅读源码&#xff1f; 关于为什么阅读和学习源码&#xff0c;我个人认为可能有以下几点&#xff1a; &#xff08;一&#xff09;吊打面试官&#xff0c;应对面试 为了找到更好的工作&#xff…

异地局域网纯软件组网如何设置?

在现代社会中&#xff0c;随着企业的不断扩张和分布&#xff0c;异地办公成为一种常见的工作模式。随之而来的是&#xff0c;如何实现异地局域网的组网设置成为了一个挑战。在这种情况下&#xff0c;采用纯软件组网方案是一种有效的解决方案。本文将介绍异地局域网纯软件组网设…

双非本,3年时间从外包到阿里P6(Android岗),看我是怎么逆袭成功的?

而在小公司&#xff0c;因为我也在小公司呆过&#xff0c;所以我有最直接的感受。整个部门技术人员没几个&#xff0c;我又大学刚毕业&#xff0c;带我的人&#xff0c;问啥啥不会&#xff0c;只有一个大佬&#xff0c;跳槽来的&#xff0c;是我们技术总监&#xff0c;有问题谁…

python数据分析案例-信用卡违约预测分析

一、研究背景和意义 信用卡已经成为现代社会中人们日常生活中不可或缺的支付工具&#xff0c;它不仅为消费者提供了便利&#xff0c;还为商家提供了更广泛的销售渠道。然而&#xff0c;随着信用卡的普及和使用量的增加&#xff0c;信用卡违约问题逐渐成为金融机构面临的重要挑…

摄像头画面显示于unity场景

&#x1f43e; 个人主页 &#x1f43e; &#x1faa7;阿松爱睡觉&#xff0c;横竖醒不来 &#x1f3c5;你可以不屠龙&#xff0c;但不能不磨剑&#x1f5e1; 目录 一、前言二、UI画面三、显示于场景四、结语 一、前言 由于标题限制&#xff0c;这篇文章主要是讲在unity中调用摄…

深入讲解C++基础知识(一)

目录 一、基本内置类型1. 类型的作用2. 分类3. 整型3.1 内存描述及查询3.2 布尔类型 —— bool3.3 字符类型 —— char3.4 其他整型 4. 有符号类型和无符号类型5. 浮点型6. 如何选择类型7. 类型转换7.1 自动类型转换7.2 强制类型转换7.3 类型转换总结 8. 类型溢出8.1 注意事项 …

PHP 函数的未来发展有哪些变化呢

PHP 8.0 引入了一些新特性&#xff0c;比如 JIT 编译器、联合类型、nullsafe 运算符等。 JIT 编译器 (Just-In-Time Compiler)&#xff1a;PHP 8.0 引入了实验性的 JIT 编译器&#xff0c;可以显著提高代码执行速度。联合类型&#xff08;Union Types&#xff09;&#xff1a;…

机器学习好神奇,来看看Lasso的超参数调整与模型选择

目录 一、什么是机器学习&#xff1f;二、稀疏建模介绍三、Lasso回归简介四、Lasso超参数调整与模型选择 一、什么是机器学习&#xff1f; 机器学习是一种人工智能技术&#xff0c;它使计算机系统能够从数据中学习并做出预测或决策&#xff0c;而无需明确编程。它涉及到使用算…

Android面试题之动画+事件处理篇

1、Android 中的动画有哪几类 帧动画、补间动画、属性动画 2、动画能组合在一起使用么&#xff1f; 可以将动画组合在一起使用AnimatorSet&#xff0c; AnimatorSet.play() 播放当前动画的同时可以 .with() &#xff1a;将现有动画和传入的动画同时执行 .after() &#xff1a…

通过开放解析智能分块提高 RAG 性能

如果要使用大型语言模型 &#xff08;&#xff09;LLMs 实现生成式 AI 解决方案&#xff0c;则应考虑使用检索增强生成 &#xff08;RAG&#xff09; 的策略来生成上下文感知提示LLM。在启用 LLM RAG 的预生产管道中发生的一个重要过程是删除文档文本&#xff0c;以便仅将文档中…

[数据集][目标检测]药片药丸检测数据集VOC+YOLO格式152张1类别

数据集格式&#xff1a;Pascal VOC格式YOLO格式(不包含分割路径的txt文件&#xff0c;仅仅包含jpg图片以及对应的VOC格式xml文件和yolo格式txt文件) 图片数量(jpg文件个数)&#xff1a;152 标注数量(xml文件个数)&#xff1a;152 标注数量(txt文件个数)&#xff1a;152 标注类别…

听说你还不会用Dagger2?Dagger2 For Android最佳实践教程

Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); Log.d(TAG,chef.cook()); } } 可以看到&#xff0c;在使用Dagger2的时候&#xff0c;使用者的代码会变得非常简洁。但是&#…

洛谷:P1085 [NOIP2004 普及组] 不高兴的津津

1. 题目链接 https://www.luogu.com.cn/problem/P1085 P1085 [NOIP2004 普及组] 不高兴的津津 2. 题目描述 题目描述&#xff1a;津津每天要上课还要上辅导班&#xff0c;每天学习超过8小时就不开心&#xff0c;帮忙检查下津津的下周日程安排&#xff0c;然后告诉我她哪天不高…

如何用好swoole/webman/workerman/hyperf呢

Webman框架的依赖 "require": { "php": ">7.2", "workerman/webman-framework": "^1.5.0",// "monolog/monolog": "^2.0" }, 依赖的核心框架也是很久的了 webman-framework的核心依赖 &q…