当前位置: 首页 > news >正文

研读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 {() => {};
}

宏选择器

  1. item:条目,例如函数、结构、模块等
  2. block:代码块
  3. stmt:语句
  4. pat:模式
  5. expr:表达式
  6. ty:类型
  7. ident:标识符
  8. path:路径,例如 foo、 ::std::mem::replace, transmute::<_, int>, …
  9. meta:元信息条目,例如 #[…]和 #![rust macro…] 属性
  10. 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&#xff08;高级trait&#xff0c;宏&#xff09; 高级trait关联类型Type为什么不用泛型而是Type 运算符重载&#xff08;重要等级不高&#xff09;重名方法消除歧义never typecontinue 的值是 ! 返回闭包 宏自定义宏&#xff08;声明宏&…...

html,Javascript,css前端面试题汇总免费

html,Javascript,css前端面试题汇总免费 下载地址&#xff1a; html,Javascript,css前端面试题汇总免费.docx下载—无极低码 一&#xff0c;html与css 1&#xff0c;页面导入样式&#xff0c;使用link与import有什么区别&#xff1f; (1) 从属关系&#xff1a;link是html标签…...

HFSS—RCS测量

RCS 引言单位仿真步骤新建工程建立待测物体模型设置边界条件设置入射波添加分析可行性分析和仿真结果输入引言 雷达散射截面是隐身技术中的重要指标。用于衡量目标物体在电磁波照射下产生回波强度,也就是散射的强度。 一方面,雷萨散射截面可以用入射电磁场的强度和散射电磁场…...

QUARTZ 石英框架

QUARTZ 石英框架 1.Quartz的概念 Quartz就是一个基于Java实现的任务调度框架&#xff0c;用于执行你想要执行的任何任务。 Quartz是OpenSymphony开源组织在Job scheduling&#xff08;定时调度&#xff09;领域的开源项目&#xff0c;它可以与J2EE和J2SE应用程序相结合也可以…...

基于centos7:Harbor-2.7.2部署和安装教程

基于centos7&#xff1a;Harbor-2.7.2部署和安装教程 1、软件资源介绍 Harbor是VMware公司开源的企业级DockerRegistry项目&#xff0c;项目地址为https://github.com/vmware/harbor。其目标是帮助用户迅速搭建一个企业级的Dockerregistry服务。它以Docker公司开源的registry…...

Windows上使用CLion配置OpenCV环境,亲测可用的方法(一)

一、Windows上使用CLion配置OpenCV环境&#xff0c;亲测可用的方法&#xff1a; Windows上使用CLion配置OpenCV环境 教程里的配置&#xff1a; widnows 10 clion 2022.1.1 mingw 8.1.0 opencv 4.5.5 Cmake3.21.1 我自己的配置&#xff1a; widnows 10 clion 2022.2.5 mingw 8.…...

代码随想录算法训练营第四十三天

代码随想录算法训练营第四十三天| 1049. 最后一块石头的重量 II&#xff0c;494. 目标和&#xff0c;474. 一和零 1049. 最后一块石头的重量 II494. 目标和474. 一和零 1049. 最后一块石头的重量 II 题目链接&#xff1a;最后一块石头的重量 II 重点&#xff1a; 本题其实就是…...

如何在 Mac 和 Windows 上恢复未保存或删除的 PDF

Adobe Acrobat PDF 是一种常用格式。我们可能会在不同的 PDF 编辑器中编辑和保存 PDF 文件。但是&#xff0c;如果不保存 PDF 文件或不小心将其删除&#xff0c;那将是一种令人不安的体验。 保持冷静&#xff01;首先&#xff0c;尽可能多地停止运行应用程序&#xff0c;这样它…...

windows开机不自动挂载磁盘的方法

本人的电脑系统为win11 写作时间20230430 开机不挂载某块磁盘的理由 1.本人电脑上有个仓库盘是机械硬盘&#xff0c;并不是每次开机都要用到&#xff0c;开机不挂载也许有利于增加数据盘的寿命 2.挂载了数据盘&#xff0c;有时候打开文件页面会比较慢&#xff0c;不够丝滑 …...

5 款 AI 老照片修复工具的横向比较

在大语言模型和各类 AI 应用日新月异的今天&#xff0c;我终于下定决心&#xff0c;趁着老照片们还没有完全发黄褪色、受潮粘连抑或损坏遗失&#xff0c;将上一代人实体相册里的纸质胶卷照片全部数字化&#xff0c;并进行一次彻底的 AI 修复&#xff0c;好让这些珍贵的记忆能更…...

2023企业服务的关键词:做强平台底座

作者 | 曾响铃 文 | 响铃说 4月下旬&#xff0c;软件行业相关的大会紧锣密鼓地开了好几场&#xff0c;不仅有政府主办的2023中国国际软件发展大会、中国软件创新发展大会&#xff0c;也有用友、浪潮等服务商举办的品牌活动&#xff0c;让软件业的话题一直保持热度。 以用友为…...

【Linux基本指令和权限(1)】

本文思维导图&#xff1a; 文章目录 一、Linux操作的特点二、使用指令从Xhell登录云服务器三、基本指令1.ls指令2. pwd指令&#xff1a;3.cd指令4. touch指令5. rm指令 写在最后 Linux是一个操作系统&#xff0c;操作系统是一款做软硬件管理的软件。 一、Linux操作的特点 Li…...

虹科新品 | 用于医疗应用的压力和气体流量传感器

ES Systems在创新MEMS方面拥有丰富的经验&#xff0c;设计了高质量和高性能的气体流量和压力传感器&#xff0c;由于其技术规格&#xff0c;出色的可靠性和有竞争力的价格&#xff0c;这些传感器在竞争产品中具有独特的品质。 Part.01 应用背景 众所周知&#xff0c;在医疗领域…...

原生小程序如何使用pdf.js实现查看pdf,以及关键词检索高亮

1.下载pdf.js库文件 前往 pdf.js 的 官网 下载库文件&#xff0c;下哪个版本都可以&#xff0c;后者适用于旧版浏览器&#xff0c;所以我下载的是后者 下载完成后&#xff0c;因为微信小程序打包的限制&#xff0c;我将库文件放到项目的后台系统了&#xff0c;在h5端处理会比在…...

「数据架构」MDM实现失败的主要原因

我经常参与一个组织的MDM程序&#xff0c;当他们在一个失败的项目之后向InfoTrellis请求帮助进行清理&#xff0c;或者开始尝试X&#xff0c;以实现对某些人来说非常困难的目标时。主数据管理实现失败的原因有很多&#xff0c;但是没有一个是由于在这些场景中使用的责备游戏的原…...

【Java基础 1】Java 环境搭建

&#x1f34a; 欢迎加入社区&#xff0c;寒冬更应该抱团学习&#xff1a;Java社区 &#x1f4c6; 最近更新&#xff1a;2023年4月22日 文章目录 1 java发展史及特点1.1 发展史1.2 Java 特点1.2.1 可以做什么&#xff1f;1.2.2 特性 2 Java 跨平台原理2.1 两种核心机制2.2 JVM…...

2023-4-26-C++11新特性之正则表达式

&#x1f37f;*★,*:.☆(&#xffe3;▽&#xffe3;)/$:*.★* &#x1f37f; &#x1f4a5;&#x1f4a5;&#x1f4a5;欢迎来到&#x1f91e;汤姆&#x1f91e;的csdn博文&#x1f4a5;&#x1f4a5;&#x1f4a5; &#x1f49f;&#x1f49f;喜欢的朋友可以关注一下&#xf…...

python接口自动化测试 requests库的基础使用

目录 简单介绍 Get请求 Post请求 其他类型请求 自定义headers和cookies SSL 证书验证 响应内容 获取header 获取cookies 简单介绍 requests库简单易用的HTTP库 Get请求 格式&#xff1a; requests.get(url) 注意&#xff1a;若需要传请求参数&#xff0c;可直接在 …...

Photon AI Translator 和做产品的一些思考

近 4 个月内我一直在做 Apple 平台的产品&#xff0c;虽然从使用量来说「简体中文」用户是占多数&#xff0c;但我一直有做多语言的支持&#xff1a;英语、简体中文和繁体中文。习惯上 Google 翻译的我&#xff0c;基本上在使用 Xcode 过程中也会一直在浏览器开着 Google Trans…...

IPTV系统架构的分析与研究

1 引言   IPTV业务是伴随着宽带互联网的飞速发展而兴起的一项新兴的互联网增值业务,它利用宽带互联网的基础设施&#xff0c;以家用电视机和电脑作为主要终端 &#xff0c;利用网络机顶盒(STB,Set -TopBox) &#xff0c;通过互联网协议来传送电视信号.提供包括 电视节 目在 内…...

workerman开发者必须知道的几个问题

1、windows环境限制 windows系统下workerman单个进程仅支持200个连接。 windows系统下无法使用count参数设置多进程。 windows系统下无法使用status、stop、reload、restart等命令。 windows系统下无法守护进程&#xff0c;cmd窗口关掉后服务即停止。 windows系统下无法在一个…...

golang Gin实现websocket

golang使用 Gin实现 websocket&#xff0c;这里笔者重新搭建一个项目 1、创建项目安装依赖 项目名为 go-gin-websocket 在指定文件夹下&#xff0c;新建项目文件夹 go-gin-websocket 进入项目文件夹&#xff0c;打开cmd窗口&#xff0c;在项目&#xff08;go-gin-websocket&a…...

冯·诺依曼体系结构与初始操作系统

目录 冯诺依曼体系结构 冯诺依曼体系结构图 内存 外存 网卡和磁盘 结构之间运算速度的差异 缓冲区 初始操作系统 概念 操作系统上边与下边分别有什么 从上到下依次顺序解析 用户 用户操作接口 系统调用接口 操作系统四项管理 驱动 硬件 冯诺依曼体系结构 冯诺…...

软件测试之黑盒测试的具体方法详解

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 前言一.基于需求的设计方法二.等价类三.边界值四.判定表4.1 **关系**4.2 如何设计测试用例4.3 实际案例第一步第二步第三步第四步 五.正交排列5.1 什么是正交表5.2 …...

图形编辑器:历史记录设计

大家好&#xff0c;我是前端西瓜哥。今天讲一下图形编辑器如何实现历史记录&#xff0c;做到撤销重做。 其实就是版本号的更替。每个版本保存一个状态。 数据结构 要记录图形编辑器的历史记录&#xff0c;支持撤销重做功能&#xff0c;需要两个栈&#xff1a;撤销&#xff0…...

ubuntu22.04下挂载第二块硬盘

文章目录 一、查看硬盘情况二、找到nvme1n1三、挂载四、修改分区文件 一、查看硬盘情况 首先要查看一下系统识别出来的设备。也就是说&#xff0c;我希望知道&#xff0c;ubuntu到底发现了几块硬盘。用命令&#xff1a;lsblk 显示结果如下&#xff1a; 有两块硬盘&#xff1a…...

举例说明.net中in与out的作用与区别

-----作用 在 .NET 中&#xff0c;in 和 out 是用于泛型类型参数的修饰符&#xff0c;它们用于指定参数类型的协变性和逆变性。 - in 修饰符&#xff1a;表示这个泛型类型参数是协变的。也就是说&#xff0c;in 类型参数可以从较特殊的类型隐式转换为较通用的类型。例如&…...

Java常见的100道面试题(内附答案及代码示例)持续更新

什么是Java程序&#xff1f; Java程序是一组可执行的代码&#xff0c;由Java编译器编译生成&#xff0c;可以在Java虚拟机&#xff08;JVM&#xff09;上运行。 public class HelloWorld {public static void main(String[] args) {System.out.println("Hello, World!&qu…...

策略设计模式知多少

目录 目标 概述 实现 目标 熟悉策略设计模式&#xff0c;了解策略设计模式的使用场景、具体实现。 概述 一、行为设计模式 行为设计模式是设计模式的一种类型。该类型的设计模式关注的重点在于对象的行为&#xff08;通信和交互&#xff09;&#xff0c;而非对象的创建方…...

第三十九章 配置镜像 - 配置 ISCAgent - 在 UNIX Linux 和 macOS 系统上为非根实例启动 ISCAgent

文章目录 第三十九章 配置镜像 - 配置 ISCAgent - 在 UNIX Linux 和 macOS 系统上为非根实例启动 ISCAgent在 UNIX/Linux 和 macOS 系统上为非根实例启动 ISCAgent在 Microsoft Windows 系统上启动 ISCAgent 自定义 ISCAgent 第三十九章 配置镜像 - 配置 ISCAgent - 在 UNIX Li…...