研读Rust圣经解析——Rust learn-16(高级trait,宏)

news2024/11/26 11:50:56

研读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 manipulate
    let ast = syn::parse(input).unwrap();

    // Build the trait implementation
    impl_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_fn

      for 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 结构体和枚举的通用 AST
    let 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! {
        #input

        fn #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);
}

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

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

相关文章

day7 实现TCP通信

目录 函数介绍 代码实现 函数介绍 socket函数与通信域&#xff1a; #include <sys/types.h> #include <sys/socket.h> int socket(int domain, int type, int protocol); -domain&#xff1a;指定通信域&#xff08;通信地址族&#xff09;&#xff1b; AF_I…

python-chatgpt自动化批量改写文章-基于gpt-3-5-turbo模型

作者&#xff1a;虚坏叔叔 博客&#xff1a;https://xuhss.com 早餐店不会开到晚上&#xff0c;想吃的人早就来了&#xff01;&#x1f604; 一、ChatGPT官方文档介绍&#xff1a; ChatGPT API—0.002美元&#xff0c;1000个token。比之前的GPT-3.0&#xff0c;成本直接降低了9…

vue3.2+ts错误:找不到模块“./App.vue”或其相应的类型声明。ts(2307)

报错原因&#xff1a; 未定义 .vue文件的类型&#xff0c;导致 ts 无法解析其类型&#xff0c;在vite-env.d.ts中定义后即可解决。 解决方法&#xff1a; 找到项目src目录下的vite-env.d.ts 文件&#xff0c;追加以下内容&#xff1a; declare module "*.vue" {impor…

【Python】chinese_calendar包的介绍和使用案例介绍(含代码)

一、问题引入 在我们的比赛中,我们对应的有时间数据,我们需要考虑不同时间段(例如月头、月中、月末等)产品需求量有何特性,节假日对产品需求量的影响,促销(如618、双十一等)对产品需求量的影响,季节因素对产品需求量的影响等。 但是我们的数据集中,却没有这种相关的…

PyTorch中的交叉熵函数 CrossEntropyLoss的计算过程

CrossEntropyLoss() 函数联合调用了 nn.LogSoftmax() 和 nn.NLLLoss()。 关于交叉熵函数的公式详见&#xff1a; 交叉熵损失函数原理详解 CrossEntropyLoss() 函数的计算过程可以拆解为如下四个步骤&#xff1a; 1、对输出的结果进行softmax操作,因为softmax操作可以将所有输入…

【Java基础教程】初识Java

作者简介&#xff1a; 辭七七&#xff0c;目前大一&#xff0c;正在学习C/C&#xff0c;Java&#xff0c;Python等 作者主页&#xff1a; 七七的个人主页 **文章收录专栏&#xff1a;Java.SE&#xff0c;本专栏主要讲解运算符&#xff0c;程序逻辑控制&#xff0c;方法的使用&a…

Java实现数据压缩所有方式性能测试

目录 1 BZip方式1.1 引入依赖1.2 BZip工具类代码1.3 BZip2工具类代码 2 Deflater方式3 Gzip方式4 Lz4方式4.1 简介4.2 算法思想4.3 算法实现4.3.1 lz4数据格式2、lz4压缩过程3、lz4解压过程 4.4 Lz4-Java4.4.1 简介4.4.2 类库 5 SevenZ方式5.1 引入依赖5.2 工具类代码 6 Zip方式…

C++(继承和组合)

继承&#xff1a;public继承是一种 is-a 的关系&#xff0c;也就是每一个派生类对象都有一个基类对象 这些关系都适合用继承来表达 ----> 继承了之后父类的成员就变成了子类的一部分&#xff0c;子类对象可以直接用 组合&#xff1a; 是一种has -a&#xff08;有一个&…

GraphSAGE聚合流程计算实例

本篇中我们只讨论聚合流程&#xff0c;不考虑GraphSAGE的小批量训练等内容。 我们先来看一下GraphSAGE的聚合流程伪代码&#xff0c;之后会给出两个具体的计算例子进行说明&#xff1a; 11行中&#xff0c; N ( k ) ( u ) N^{(k)}(u) N(k)(u)表示节点u的邻居节点采样函数&…

力扣杯2023春·个人赛

文章目录 力扣杯2023春-个人赛[LCP 72. 补给马车](https://leetcode.cn/problems/hqCnmP/)模拟 [LCP 73. 探险营地](https://leetcode.cn/problems/0Zeoeg/)模拟 哈希 [LCP 74. 最强祝福力场](https://leetcode.cn/problems/xepqZ5/)二维差分 离散化扫描线 [LCP 75. 传送卷轴…

CANOE入门到精通——CANOE系列教程记录1 第一个仿真工程

本系列以初学者角度记录学习CANOE&#xff0c;以《CANoe开发从入门到精通》参考学习&#xff0c;CANoe16 demo版就可以进行学习 概念 CANoe是一种用于开发、测试和分析汽车电子系统的软件工具。它通过在不同层次上模拟汽车电子系统中的不同部件&#xff0c;如ECU、总线和传感…

自动化运维工具Ansible之playbook剧本

目录 一、playbook 1、playbook简述 2、playbook剧本格式 3、playbook组成部分 4、playbook启动及检测 5、playbook模块实战实例1 6、vars模块实战实例2 7、when模块实战实例3 8、with_items循环模块实战实例4 9、template模块实战实例5 10、tags模块实战实例6 一、…

VM中kali虚拟机创建docker部署WebGoat

这里选择在docker中配置&#xff08;因为方便&#xff09; 首先下载docker sudo apt-get install docker.io 然后从Docker Hub下载WebGoat 8.0 的docker镜像 使用命令 docker pull webgoat/webgoat-8.0 完成后查看现在kali虚拟机中的docker镜像列表 输入命令 docker images …

0704一阶线性微分方程-微分方程

文章目录 1 线性方程1.1 定义1.2 解法&#xff08;常数变易法&#xff09;1.3 例题 2伯努利方程3 简单变量替换解方程结语 1 线性方程 1.1 定义 一阶微分方程&#xff1a;形式上能化成 d y d x P ( x ) y Q ( x ) \frac{dy}{dx}P(x)yQ(x) dxdy​P(x)yQ(x)的方程&#xff0c;…

树莓派CSI摄像头使用python调用opencv库函数进行运动检测识别

目录 一、完成摄像头的调用 二、利用python调用opencv库函数对图像进行处理 2.1 图像处理大体流程 2.2 opencv调用函数的参数以及含义 2.2.1 ret, img cap.read() 读取帧图像 2.2.2 cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) 灰度图像 2.2.3 gray_diff_img cv2.absdiff(g…

详解子网划分练习题(32道)

目录 1 子网划分概念&#xff1a; 2 划分方法&#xff1a; 子网划分方法&#xff1a;段&#xff0c;块&#xff0c;数的计算三步。 段就是确定ip地址段中既有网络地址&#xff0c;又有主机地址的那一段是四段中的那一段&#xff1f; 块就确定上一步中确定的那一段中的主机…

【Linux】网络配置详细步骤及其相关基础知识介绍

一、Linux网络配置步骤 1、登录root账户 进行网络配置需要使用root权限&#xff0c;因此需要先登录root用户 2、输入ip addr查看网络信息 只有一个本机地址127.0.0.1&#xff0c;因为Linux操作系统的网卡开关还没有打开。 3、输入cd /etc/sysconfig/network-scripts/进入目录…

R语言 | 列表

目录 一、建立列表 1.1 建立列表对象——对象元素不含名称 1.2 建立列表对象——对象元素含名称 1.3 处理列表内对象的元素名称 1.4 获得列表的对象元素个数 二、获取列表内对象的元素内容 2.1 使用"$"符号取得列表对象的元素内容 2.2 使用"[[ ]]"符…

关于GeoServer发布服务时数据源设置的避坑指南

题外话 时光任然&#xff0c;一年一度的五一劳动节已然来到。作为疫情之后迎来的第一个五一&#xff0c;不知道各位小伙伴们怎么度过这个劳动节呢&#xff1f;是决定去另一个城市&#xff0c;观察体验一下不一样的风景&#xff0c;或者去旅游&#xff0c;给自己放假。昨天被123…

three.js进阶之动画系统

我曾在three.js进阶之骨骼绑定文章中提到了AnimationMixer、AnimationAction等内容&#xff0c;其实这些应该属于Three.js的动画系统&#xff0c;本文就系统的介绍一下动画系统&#xff08;Animation System&#xff09;。 前言 一般情况下&#xff0c;我们很少会使用three.j…