研读Rust圣经解析——Rust learn-16(高级trait,宏)
研读Rust圣经解析——Rust learn-16(高级trait,宏)
- 高级trait
- 关联类型Type
- 为什么不用泛型而是Type
- 运算符重载(重要等级不高)
- 重名方法消除歧义
- never type
- continue 的值是 !
- 返回闭包
- 宏
- 自定义宏(声明宏)
- 宏的运作机制
- Rust编译过程
- 新建空白宏
- 宏选择器
- 什么是词条树
- 宏选择器设置各类入参
- 实现一个log宏
- 运行重复模式匹配
- 自定义derive宏(过程宏)
- 构建项目结构(一定要照着做不然会错)
- 设置工作空间
- 创建lib和main
- hello_macro
- lib.rs
- hello_macro_derive
- 添加依赖和激活`proc-macro`
- lib.rs
- 注意点(请好好读,官网上说的很清楚了,这个地方一定要搞懂)
- pancakes
- 添加依赖
- main.rs
- 错误
- can't find library `marco_t`, rename file to `src/lib.rs` or specify lib.path (为什么不能在单项目包里构建)
- can't use a procedural macro from the same crate that defines it
- 自定义类属性宏(个人认为最重要)
- 一个简单的例子
- 项目包结构
- json_marco
- 添加依赖
- 编写lib
- json_test
- 一些例子
- base
- lib
- main
- flaky_test
- lib
- main
- json_parse
- lib.rs
- main.rs
- fn_time
- lib
- main
高级trait
关联类型Type
我们使用type关键字即可声明一个关联类型,关联类型的作用就是简化和隐藏显示类型(个人认为)
- 简化:一个很长的类型总是被需要时,需要开发者耗费精力的重复书写,而且若有改动,则需要改多个地方
- 隐藏:对外部调用者隐藏,外部调用者无需知道它指的是什么,只要可快速使用即可
trait test {type Res = Result<i32, Box<&'static str>>;fn test_res() -> Res{//...}
}
为什么不用泛型而是Type
使用泛型,我们就需要在每次使用实现时显示的标注类型,但是当针对一个多处使用且无需修改类型的场景时,无疑耗时耗力,换而言之,Type牺牲部分灵活度换取常用性
运算符重载(重要等级不高)
Rust 并不允许创建自定义运算符或重载任意运算符,不过 std::ops 中所列出的运算符和相应的 trait 可以通过实现运算符相关 trait 来重载
因此我们就可以重载例如+,/,-,*等,
以下是官方给出的例子:
use std::ops::Add;#[derive(Debug, Copy, Clone, PartialEq)]
struct Point {x: i32,y: i32,
}impl Add for Point {type Output = Point;fn add(self, other: Point) -> Point {Point {x: self.x + other.x,y: self.y + other.y,}}
}fn main() {assert_eq!(Point { x: 1, y: 0 } + Point { x: 2, y: 3 },Point { x: 3, y: 3 });
}
重名方法消除歧义
当我们实现多个trait的时候,若遇到多个trait有同样的方法名,那么就会产生重名歧义,此时最晚实现的会覆盖前面的,为了消除歧义,我们可以采用trait::fn(&type)来申明调用
struct a {}trait b {fn get(&self) {}
}trait c {fn get(&self) {}
}impl b for a {fn get(&self) {todo!()}
}impl c for a {fn get(&self) {todo!()}
}fn main() {let a_struct = a {};b::get(&a_struct);c::get(&a_struct);
}
never type
Rust 有一个叫做 ! 的特殊类型,我们称作never type因为他表示函数从不返回的时候充当返回值
fn no_feedback()->!{//...
}
continue 的值是 !
let guess: u32 = match guess.trim().parse() {Ok(num) => num,Err(_) => continue,};
返回闭包
一个函数的返回值是可以为一个闭包的,这个没有限制,具体来说我们简单了解写法即可
fn test()->Box<dyn Fn()>{//...
}
我们通过返回一个Box即将返回值写入堆中
例如:
fn returns_closure() -> Box<dyn Fn(i32) -> i32> {Box::new(|x| x + 1)
}
宏
从根本上来说,宏是一种为写其他代码而写代码的方式,即所谓的 元编程(metaprogramming)。在附录 C 中会探讨 derive 属性,其生成各种 trait 的实现。我们也在本书中使用过 println! 宏和 vec! 宏。所有的这些宏以 展开 的方式来生成比你所手写出的更多的代码。
元编程对于减少大量编写和维护的代码是非常有用的,它也扮演了函数扮演的角色。但宏有一些函数所没有的附加能力。
一个函数签名必须声明函数参数个数和类型。相比之下,宏能够接收不同数量的参数:用一个参数调用 println!(“hello”) 或用两个参数调用 println!(“hello {}”, name) 。而且,宏可以在编译器翻译代码前展开,例如,宏可以在一个给定类型上实现 trait。而函数则不行,因为函数是在运行时被调用,同时 trait 需要在编译时实现。
实现宏不如实现函数的一面是宏定义要比函数定义更复杂,因为你正在编写生成 Rust 代码的 Rust 代码。由于这样的间接性,宏定义通常要比函数定义更难阅读、理解以及维护。
宏和函数的最后一个重要的区别是:在一个文件里调用宏 之前 必须定义它,或将其引入作用域,而函数则可以在任何地方定义和调用。
—https://kaisery.github.io/trpl-zh-cn/ch19-06-macros.html
自定义宏(声明宏)
接下来我们就直接自定义宏,少说废话,直接开干(为什么要学这个?因为甚至可以使用这个自己写一门语言)
宏的运作机制

Rust编译过程

新建空白宏
创建空白宏的方式很简单,直接使用macro_rules!进行声明,内部形似模式匹配推断(其实根本就是)
macro_rules! test {() => {};
}
宏选择器
- item:条目,例如函数、结构、模块等
- block:代码块
- stmt:语句
- pat:模式
- expr:表达式
- ty:类型
- ident:标识符
- path:路径,例如 foo、 ::std::mem::replace, transmute::<_, int>, …
- meta:元信息条目,例如 #[…]和 #![rust macro…] 属性
- tt:词条树
什么是词条树
tt词条树是指Rust编译器使用的一种数据结构,通常用于处理宏(Macro)和代码生成(Code Generation)。
tt指的是"Token Tree",它是由一系列"Token"构成的树形结构。"Token"是编程语言中最基础的语法单元,例如关键字、标识符、运算符、括号等等。而"Token Tree"则是这些"Token"按一定的层次结构排列而成的树。
在Rust语言中,宏通常是使用tt词条树作为输入,它可以让宏定义更加灵活和强大。通过对tt词条树进行递归、遍历和变换,宏可以生成代码,实现元编程(Metaprogramming)的效果。
除了宏之外,Rust编译器还会使用tt词条树来处理一些代码生成工作,例如构建抽象语法树(AST)或者生成代码的中间表示(IR)等等。
宏选择器设置各类入参
我们通过挑选适合的宏选择器,才能对应我们宏接受的参数
实现一个log宏
use std::time::{Instant, SystemTime, UNIX_EPOCH};
macro_rules! log {($log_name:tt)=>{let now = SystemTime::now();let timestamp = now.duration_since(UNIX_EPOCH).unwrap().as_secs();println!("=======================start-{}=========================",$log_name);println!("----------------createTime:{:?}",timestamp);println!("----------------title:{}",$log_name);println!("========================end-{}========================",$log_name);};
}fn main() {log!("zhangsan");
}

运行重复模式匹配
当我们有多个入参的时候就需要用到这个了,比如println!这个宏,我们可能会传入多个需要打印的内容,如果各个要取个名字,那么这样为什么还要去编写一个统一的,简化的宏呢?
重复模式匹配语法:
($($x:expr),*)=>{}
use std::time::{Instant, SystemTime, UNIX_EPOCH};
macro_rules! eq_judge {($($left:expr => $right:expr),*)=>{{$(if $left == $right{println!("true")})*}}
}fn main() {eq_judge!("hello"=>"hi","no"=>"no");
}
自定义derive宏(过程宏)
与上面的不一样的是,这个derive宏标注的位置在一般在结构体、enum上
比如:
#[derive(Debug)]
struct a{}
构建项目结构(一定要照着做不然会错)
以下是官方案例,我做了一遍之后重写顺序并强调犯错点,请大家一定要按照顺序做,遇到错误查看我这里写的错误
设置工作空间
首先随便创建一个项目,然后修改toml文件
- hello_macro:声明需要实现的trait
- hello_macro_derive:具体的解析,转化,处理逻辑
- pancakes:主执行包
[workspace]
members=["hello_macro","hello_macro_derive","pancakes"
]
创建lib和main
cargo new hello_macro --lib
cargo new hello_macro_derive --lib
cargo new pancakes
结构如下图:

hello_macro
lib.rs
书写需要实现的trait并使用pub暴露
pub trait HelloMacro {fn hello_macro();
}
hello_macro_derive
添加依赖和激活proc-macro
syn crate 将字符串中的 Rust 代码解析成为一个可以操作的数据结构。quote 则将 syn 解析的数据结构转换回 Rust 代码。这些 crate 让解析任何我们所要处理的 Rust 代码变得更简单:为 Rust 编写整个的解析器并不是一件简单的工作。
proc-macro表示这个cratq是一个proc-macro,增加这个配置以后,这个crate的特性就会发生一些变化,例如,这个crate将只能对外导出内部定义的过程宏,而不能导出内部定义的其他内容。
cargo add syn
cargo add quote
[package]
name = "hello_macro_derive"
version = "0.1.0"
edition = "2021"# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[lib]
proc-macro=true[dependencies]
quote = "1.0.26"
syn = "2.0.15"
lib.rs
#[proc_macro_derive(HelloMacro)]标识只要是结构体、enum上标注#[derive(HelloMacro)]后就会自动实现HelloMacro这个trait,具体的实现逻辑实际上在impl_hello_macro函数中
use proc_macro::TokenStream;
use quote::quote;
use syn;#[proc_macro_derive(HelloMacro)]
pub fn hello_macro_derive(input: TokenStream) -> TokenStream {// Construct a representation of Rust code as a syntax tree// that we can manipulatelet ast = syn::parse(input).unwrap();// Build the trait implementationimpl_hello_macro(&ast)
}fn impl_hello_macro(ast: &syn::DeriveInput) -> TokenStream {let name = &ast.ident;let gen = quote! {impl HelloMacro for #name {fn hello_macro() {println!("Hello, Macro! My name is {}!", stringify!(#name));}}};gen.into()
}
注意点(请好好读,官网上说的很清楚了,这个地方一定要搞懂)
当用户在一个类型上指定 #[derive(HelloMacro)]时,hello_macro_derive 函数将会被调用。因为我们已经使用 proc_macro_derive 及其指定名称HelloMacro对 hello_macro_derive 函数进行了注解,指定名称HelloMacro就是 trait 名,这是大多数过程宏遵循的习惯。
该函数首先将来自 TokenStream 的 input 转换为一个我们可以解释和操作的数据结构。这正是 syn 派上用场的地方。syn 中的 parse 函数获取一个 TokenStream 并返回一个表示解析出 Rust 代码的 DeriveInput 结构体。以下展示了从字符串 struct Pancakes; 中解析出来的 DeriveInput 结构体的相关部分:
DeriveInput {// --snip--ident: Ident {ident: "Pancakes",span: #0 bytes(95..103)},data: Struct(DataStruct {struct_token: Struct,fields: Unit,semi_token: Some(Semi)})
}
定义 impl_hello_macro 函数,其用于构建所要包含在内的 Rust 新代码。但在此之前,注意其输出也是 TokenStream。所返回的 TokenStream 会被加到我们的 crate 用户所写的代码中,因此,当用户编译他们的 crate 时,他们会通过修改后的 TokenStream 获取到我们所提供的额外功能。
当调用 syn::parse 函数失败时,我们用 unwrap 来使 hello_macro_derive 函数 panic。在错误时 panic 对过程宏来说是必须的,因为 proc_macro_derive 函数必须返回 TokenStream 而不是 Result,以此来符合过程宏的 API。这里选择用 unwrap 来简化了这个例子;在生产代码中,则应该通过 panic! 或 expect 来提供关于发生何种错误的更加明确的错误信息
pancakes
添加依赖
这里我们需要依赖我们自己写的lib所以需要用path指明
[package]
name = "pancakes"
version = "0.1.0"
edition = "2021"# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html[dependencies]
hello_macro = { path = "../hello_macro" }
hello_macro_derive = { path = "../hello_macro_derive" }
main.rs
use hello_macro::HelloMacro;
use hello_macro_derive::HelloMacro;#[derive(HelloMacro)]
struct Pancakes;fn main() {Pancakes::hello_macro();
}
错误
can’t find library marco_t, rename file to src/lib.rs or specify lib.path (为什么不能在单项目包里构建)
若你仅仅在一个包中构建,当你添加[lib] proc-macro = true你会出现以下错误:
Caused by:can't find library `marco_t`, rename file to `src/lib.rs` or specify lib.path
这说明我们不能把当前的包作为lib,因为是主执行包
原理︰考虑过程宏是在编译一个crate之前,对crate的代码进行加工的一段程序,这段程序也是需要编译后执行的。如果定义过程宏和使用过程宏的代码写在一个crate中,那就陷入了死锁:
要编译的代码首先需要运行过程宏来展开,否则代码是不完整的,没法编译crate.
不能编译crate,crate中的过程宏代码就没法执行,就不能展开被过程宏装饰的代码
can’t use a procedural macro from the same crate that defines it
那假如直接去掉不管这个,你会看到这个错误,意味着你必须将过程宏构建在lib中

自定义类属性宏(个人认为最重要)
类属性宏与自定义派生宏相似,不同的是 derive 属性生成代码,它们(类属性宏)能让你创建新的属性。它们也更为灵活;derive 只能用于结构体和枚举;属性还可以用于其它的项,比如函数
常见于各类框架中!
一个简单的例子
项目包结构
同自定义过程宏
我们需要把正在的解析处理逻辑放在lib下
[workspace]
members=[
"json_marco","json_test"
]
json_marco
添加依赖
[package]
name = "json_marco"
version = "0.1.0"
edition = "2021"# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html[lib]
proc-macro = true[dependencies]
proc-macro2 = "1.0.56"
quote = "1.0.26"
syn = { version = "2.0.15", features = ["full"] }
编写lib
use proc_macro::TokenStream;
use quote::quote;
use syn::{parse_macro_input, DeriveInput};#[proc_macro_attribute]
pub fn my_macro(attr:TokenStream,item:TokenStream)->TokenStream{println!("test");println!("{:#?}",attr);println!("{:#?}",item);item
}
这很简单就是单纯输出一下
json_test
toml映引入
[package]
name = "json_test"
version = "0.1.0"
edition = "2021"# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html[dependencies]
json_marco={path= "../json_marco"}
main.rs
use json_marco::my_macro;#[my_macro("test111")]
fn test(a: i32) {println!("{}", a);
}fn main() {test(5);
}
一些例子
base
lib
use proc_macro::TokenStream;
use quote::quote;
use syn::{parse_macro_input, DeriveInput};#[proc_macro_attribute]
pub fn my_macro(attr: TokenStream, item: TokenStream) -> TokenStream {// 解析输入的类型let input = parse_macro_input!(item as DeriveInput);// 获取类型名let name = input.ident;// 构建实现代码let expanded = quote! {impl #name {fn my_function(&self) {println!("This is my custom function!");}}};// 将生成的代码转换回 TokenStream 以供返回TokenStream::from(expanded)
}
main
#[my_macro]
struct MyStruct {field1: u32,field2: String,
}fn main() {let my_instance = MyStruct { field1: 42, field2: "hello".to_string() };my_instance.my_function();
}
flaky_test
lib
extern crate proc_macro;
extern crate syn;
use proc_macro::TokenStream;
use quote::quote;#[proc_macro_attribute]
pub fn flaky_test(_attr: TokenStream, input: TokenStream) -> TokenStream {let input_fn = syn::parse_macro_input!(input as syn::ItemFn);let name = input_fn.sig.ident.clone();TokenStream::from(quote! {#[test]fn #name() {#input_fnfor i in 0..3 {println!("flaky_test retry {}", i);let r = std::panic::catch_unwind(|| {#name();});if r.is_ok() {return;}if i == 2 {std::panic::resume_unwind(r.unwrap_err());}}}})
}
main
#[flaky_test::flaky_test]
fn my_test() {assert_eq!(1, 2);
}
json_parse
lib.rs
use proc_macro::TokenStream;
use quote::quote;
use syn::{parse_macro_input, Data, DeriveInput, Fields};#[proc_macro_attribute]
pub fn serde_json(_args: TokenStream, input: TokenStream) -> TokenStream {// 将输入解析为 DeriveInput 类型,这是所有 Rust 结构体和枚举的通用 ASTlet input = parse_macro_input!(input as DeriveInput);// 检查这是否是一个结构体,并拿到它的名称、字段列表等信息let struct_name = input.ident;let fields = match input.data {Data::Struct(data_struct) => data_struct.fields,_ => panic!("'serde_json' can only be used with structs!"),};// 生成代码,将结构体转换为 JSON 字符串let output = match fields {Fields::Named(fields_named) => {let field_names = fields_named.named.iter().map(|f| &f.ident);quote! {impl #struct_name {pub fn to_json(&self) -> String {serde_json::to_string(&json!({#(stringify!(#field_names): self.#field_names,)*})).unwrap()}}}}Fields::Unnamed(fields_unnamed) => {let field_indices = 0..fields_unnamed.unnamed.len();quote! {impl #struct_name {pub fn to_json(&self) -> String {serde_json::to_string(&json!([#(self.#field_indices,)*])).unwrap()}}}}Fields::Unit => {quote! {impl #struct_name {pub fn to_json(&self) -> String {serde_json::to_string(&json!({})).unwrap()}}}}};// 将生成的代码作为 TokenStream 返回output.into()
}
main.rs
#[serde_json]
struct MyStruct {name: String,age: u32,
}fn main() {let my_struct = MyStruct {name: "Alice".to_string(),age: 25,};let json_str = my_struct.to_json();println!("JSON string: {}", json_str);
}
fn_time
lib
use proc_macro::TokenStream;
use quote::quote;
use syn::{parse_macro_input, ItemFn};#[proc_macro_attribute]
pub fn run(_args: TokenStream, input: TokenStream) -> TokenStream {// 将输入解析为函数节点let input = parse_macro_input!(input as ItemFn);// 获取函数名称、参数列表等信息let func_name = &input.ident;let func_args = &input.decl.inputs;// 生成代码,在函数开始和结束时分别打印时间戳let output = quote! {#inputfn #func_name(#func_args) -> () {println!("{} started", stringify!(#func_name));let start = std::time::Instant::now();let result = #func_name(#func_args);let end = start.elapsed();println!("{} finished in {}ms", stringify!(#func_name), end.as_millis());result}};// 将生成的代码作为 TokenStream 返回output.into()
}
main
#[run]
fn my_function() -> i32 {// 模拟一些处理时间std::thread::sleep(std::time::Duration::from_secs(1));42
}fn main() {let result = my_function();println!("Result = {}", result);
}相关文章:
研读Rust圣经解析——Rust learn-16(高级trait,宏)
研读Rust圣经解析——Rust learn-16(高级trait,宏) 高级trait关联类型Type为什么不用泛型而是Type 运算符重载(重要等级不高)重名方法消除歧义never typecontinue 的值是 ! 返回闭包 宏自定义宏(声明宏&…...
html,Javascript,css前端面试题汇总免费
html,Javascript,css前端面试题汇总免费 下载地址: html,Javascript,css前端面试题汇总免费.docx下载—无极低码 一,html与css 1,页面导入样式,使用link与import有什么区别? (1) 从属关系:link是html标签…...
HFSS—RCS测量
RCS 引言单位仿真步骤新建工程建立待测物体模型设置边界条件设置入射波添加分析可行性分析和仿真结果输入引言 雷达散射截面是隐身技术中的重要指标。用于衡量目标物体在电磁波照射下产生回波强度,也就是散射的强度。 一方面,雷萨散射截面可以用入射电磁场的强度和散射电磁场…...
QUARTZ 石英框架
QUARTZ 石英框架 1.Quartz的概念 Quartz就是一个基于Java实现的任务调度框架,用于执行你想要执行的任何任务。 Quartz是OpenSymphony开源组织在Job scheduling(定时调度)领域的开源项目,它可以与J2EE和J2SE应用程序相结合也可以…...
基于centos7:Harbor-2.7.2部署和安装教程
基于centos7:Harbor-2.7.2部署和安装教程 1、软件资源介绍 Harbor是VMware公司开源的企业级DockerRegistry项目,项目地址为https://github.com/vmware/harbor。其目标是帮助用户迅速搭建一个企业级的Dockerregistry服务。它以Docker公司开源的registry…...
Windows上使用CLion配置OpenCV环境,亲测可用的方法(一)
一、Windows上使用CLion配置OpenCV环境,亲测可用的方法: Windows上使用CLion配置OpenCV环境 教程里的配置: widnows 10 clion 2022.1.1 mingw 8.1.0 opencv 4.5.5 Cmake3.21.1 我自己的配置: widnows 10 clion 2022.2.5 mingw 8.…...
代码随想录算法训练营第四十三天
代码随想录算法训练营第四十三天| 1049. 最后一块石头的重量 II,494. 目标和,474. 一和零 1049. 最后一块石头的重量 II494. 目标和474. 一和零 1049. 最后一块石头的重量 II 题目链接:最后一块石头的重量 II 重点: 本题其实就是…...
如何在 Mac 和 Windows 上恢复未保存或删除的 PDF
Adobe Acrobat PDF 是一种常用格式。我们可能会在不同的 PDF 编辑器中编辑和保存 PDF 文件。但是,如果不保存 PDF 文件或不小心将其删除,那将是一种令人不安的体验。 保持冷静!首先,尽可能多地停止运行应用程序,这样它…...
windows开机不自动挂载磁盘的方法
本人的电脑系统为win11 写作时间20230430 开机不挂载某块磁盘的理由 1.本人电脑上有个仓库盘是机械硬盘,并不是每次开机都要用到,开机不挂载也许有利于增加数据盘的寿命 2.挂载了数据盘,有时候打开文件页面会比较慢,不够丝滑 …...
5 款 AI 老照片修复工具的横向比较
在大语言模型和各类 AI 应用日新月异的今天,我终于下定决心,趁着老照片们还没有完全发黄褪色、受潮粘连抑或损坏遗失,将上一代人实体相册里的纸质胶卷照片全部数字化,并进行一次彻底的 AI 修复,好让这些珍贵的记忆能更…...
2023企业服务的关键词:做强平台底座
作者 | 曾响铃 文 | 响铃说 4月下旬,软件行业相关的大会紧锣密鼓地开了好几场,不仅有政府主办的2023中国国际软件发展大会、中国软件创新发展大会,也有用友、浪潮等服务商举办的品牌活动,让软件业的话题一直保持热度。 以用友为…...
【Linux基本指令和权限(1)】
本文思维导图: 文章目录 一、Linux操作的特点二、使用指令从Xhell登录云服务器三、基本指令1.ls指令2. pwd指令:3.cd指令4. touch指令5. rm指令 写在最后 Linux是一个操作系统,操作系统是一款做软硬件管理的软件。 一、Linux操作的特点 Li…...
虹科新品 | 用于医疗应用的压力和气体流量传感器
ES Systems在创新MEMS方面拥有丰富的经验,设计了高质量和高性能的气体流量和压力传感器,由于其技术规格,出色的可靠性和有竞争力的价格,这些传感器在竞争产品中具有独特的品质。 Part.01 应用背景 众所周知,在医疗领域…...
原生小程序如何使用pdf.js实现查看pdf,以及关键词检索高亮
1.下载pdf.js库文件 前往 pdf.js 的 官网 下载库文件,下哪个版本都可以,后者适用于旧版浏览器,所以我下载的是后者 下载完成后,因为微信小程序打包的限制,我将库文件放到项目的后台系统了,在h5端处理会比在…...
「数据架构」MDM实现失败的主要原因
我经常参与一个组织的MDM程序,当他们在一个失败的项目之后向InfoTrellis请求帮助进行清理,或者开始尝试X,以实现对某些人来说非常困难的目标时。主数据管理实现失败的原因有很多,但是没有一个是由于在这些场景中使用的责备游戏的原…...
【Java基础 1】Java 环境搭建
🍊 欢迎加入社区,寒冬更应该抱团学习:Java社区 📆 最近更新:2023年4月22日 文章目录 1 java发展史及特点1.1 发展史1.2 Java 特点1.2.1 可以做什么?1.2.2 特性 2 Java 跨平台原理2.1 两种核心机制2.2 JVM…...
2023-4-26-C++11新特性之正则表达式
🍿*★,*:.☆( ̄▽ ̄)/$:*.★* 🍿 💥💥💥欢迎来到🤞汤姆🤞的csdn博文💥💥💥 💟💟喜欢的朋友可以关注一下…...
python接口自动化测试 requests库的基础使用
目录 简单介绍 Get请求 Post请求 其他类型请求 自定义headers和cookies SSL 证书验证 响应内容 获取header 获取cookies 简单介绍 requests库简单易用的HTTP库 Get请求 格式: requests.get(url) 注意:若需要传请求参数,可直接在 …...
Photon AI Translator 和做产品的一些思考
近 4 个月内我一直在做 Apple 平台的产品,虽然从使用量来说「简体中文」用户是占多数,但我一直有做多语言的支持:英语、简体中文和繁体中文。习惯上 Google 翻译的我,基本上在使用 Xcode 过程中也会一直在浏览器开着 Google Trans…...
IPTV系统架构的分析与研究
1 引言 IPTV业务是伴随着宽带互联网的飞速发展而兴起的一项新兴的互联网增值业务,它利用宽带互联网的基础设施,以家用电视机和电脑作为主要终端 ,利用网络机顶盒(STB,Set -TopBox) ,通过互联网协议来传送电视信号.提供包括 电视节 目在 内…...
Linux链表操作全解析
Linux C语言链表深度解析与实战技巧 一、链表基础概念与内核链表优势1.1 为什么使用链表?1.2 Linux 内核链表与用户态链表的区别 二、内核链表结构与宏解析常用宏/函数 三、内核链表的优点四、用户态链表示例五、双向循环链表在内核中的实现优势5.1 插入效率5.2 安全…...
java调用dll出现unsatisfiedLinkError以及JNA和JNI的区别
UnsatisfiedLinkError 在对接硬件设备中,我们会遇到使用 java 调用 dll文件 的情况,此时大概率出现UnsatisfiedLinkError链接错误,原因可能有如下几种 类名错误包名错误方法名参数错误使用 JNI 协议调用,结果 dll 未实现 JNI 协…...
Leetcode 3577. Count the Number of Computer Unlocking Permutations
Leetcode 3577. Count the Number of Computer Unlocking Permutations 1. 解题思路2. 代码实现 题目链接:3577. Count the Number of Computer Unlocking Permutations 1. 解题思路 这一题其实就是一个脑筋急转弯,要想要能够将所有的电脑解锁&#x…...
多模态商品数据接口:融合图像、语音与文字的下一代商品详情体验
一、多模态商品数据接口的技术架构 (一)多模态数据融合引擎 跨模态语义对齐 通过Transformer架构实现图像、语音、文字的语义关联。例如,当用户上传一张“蓝色连衣裙”的图片时,接口可自动提取图像中的颜色(RGB值&…...
【ROS】Nav2源码之nav2_behavior_tree-行为树节点列表
1、行为树节点分类 在 Nav2(Navigation2)的行为树框架中,行为树节点插件按照功能分为 Action(动作节点)、Condition(条件节点)、Control(控制节点) 和 Decorator(装饰节点) 四类。 1.1 动作节点 Action 执行具体的机器人操作或任务,直接与硬件、传感器或外部系统…...
linux 下常用变更-8
1、删除普通用户 查询用户初始UID和GIDls -l /home/ ###家目录中查看UID cat /etc/group ###此文件查看GID删除用户1.编辑文件 /etc/passwd 找到对应的行,YW343:x:0:0::/home/YW343:/bin/bash 2.将标红的位置修改为用户对应初始UID和GID: YW3…...
涂鸦T5AI手搓语音、emoji、otto机器人从入门到实战
“🤖手搓TuyaAI语音指令 😍秒变表情包大师,让萌系Otto机器人🔥玩出智能新花样!开整!” 🤖 Otto机器人 → 直接点明主体 手搓TuyaAI语音 → 强调 自主编程/自定义 语音控制(TuyaAI…...
什么是Ansible Jinja2
理解 Ansible Jinja2 模板 Ansible 是一款功能强大的开源自动化工具,可让您无缝地管理和配置系统。Ansible 的一大亮点是它使用 Jinja2 模板,允许您根据变量数据动态生成文件、配置设置和脚本。本文将向您介绍 Ansible 中的 Jinja2 模板,并通…...
ABAP设计模式之---“简单设计原则(Simple Design)”
“Simple Design”(简单设计)是软件开发中的一个重要理念,倡导以最简单的方式实现软件功能,以确保代码清晰易懂、易维护,并在项目需求变化时能够快速适应。 其核心目标是避免复杂和过度设计,遵循“让事情保…...
音视频——I2S 协议详解
I2S 协议详解 I2S (Inter-IC Sound) 协议是一种串行总线协议,专门用于在数字音频设备之间传输数字音频数据。它由飞利浦(Philips)公司开发,以其简单、高效和广泛的兼容性而闻名。 1. 信号线 I2S 协议通常使用三根或四根信号线&a…...
