rust 笔记 高级错误处理

文章目录

    • 错误处理
      • 组合器
        • or() 和 and()
        • or_else() 和 and_then()
        • filter
        • map() 和 map_err()
        • map_or() 和 map_or_else()
        • ok_or() and ok_or_else()
      • 自定义错误类型
        • 错误转换 From 特征
      • 归一化不同的错误类型
        • Box<dyn Error>
        • 自定义错误类型
      • 简化错误处理
        • thiserror
        • anyhow

错误处理

组合器

与组合器模式有所不同,在 Rust 中,组合器更多的是用于对返回结果的类型进行变换:例如使用 ok_or 将一个 Option 类型转换成 Result 类型。

or() 和 and()

跟布尔关系的与/或很像,这两个方法会对两个表达式做逻辑组合,最终返回 Option / Result。

  • or(),表达式按照顺序求值,若任何一个表达式的结果是 Some 或 Ok,则该值会立刻返回
  • and(),若两个表达式的结果都是 Some 或 Ok,则第二个表达式中的值被返回。若任何一个的结果是 None 或 Err ,则立刻返回。
    实际上,只要将布尔表达式的 true / false,替换成 Some / None 或 Ok / Err 就很好理解了。
fn main() {let s1 = Some("some1");let s2 = Some("some2");let n: Option<&str> = None;let o1: Result<&str, &str> = Ok("ok1");let o2: Result<&str, &str> = Ok("ok2");let e1: Result<&str, &str> = Err("error1");let e2: Result<&str, &str> = Err("error2");assert_eq!(s1.or(s2), s1); // Some1 or Some2 = Some1assert_eq!(s1.or(n), s1);  // Some or None = Someassert_eq!(n.or(s1), s1);  // None or Some = Someassert_eq!(n.or(n), n);    // None1 or None2 = None2assert_eq!(o1.or(o2), o1); // Ok1 or Ok2 = Ok1assert_eq!(o1.or(e1), o1); // Ok or Err = Okassert_eq!(e1.or(o1), o1); // Err or Ok = Okassert_eq!(e1.or(e2), e2); // Err1 or Err2 = Err2assert_eq!(s1.and(s2), s2); // Some1 and Some2 = Some2assert_eq!(s1.and(n), n);   // Some and None = Noneassert_eq!(n.and(s1), n);   // None and Some = Noneassert_eq!(n.and(n), n);    // None1 and None2 = None1assert_eq!(o1.and(o2), o2); // Ok1 and Ok2 = Ok2assert_eq!(o1.and(e1), e1); // Ok and Err = Errassert_eq!(e1.and(o1), e1); // Err and Ok = Errassert_eq!(e1.and(e2), e1); // Err1 and Err2 = Err1
}

除了 or 和 and 之外,Rust 还为我们提供了 xor ,但是它只能应用在 Option 上.

or_else() 和 and_then()

它们跟 or() 和 and() 类似,唯一的区别在于,它们的第二个表达式是一个闭包。

fn main() {// or_else with Optionlet s1 = Some("some1");let s2 = Some("some2");let fn_some = || Some("some2"); // 类似于: let fn_some = || -> Option<&str> { Some("some2") };let n: Option<&str> = None;let fn_none = || None;assert_eq!(s1.or_else(fn_some), s1);  // Some1 or_else Some2 = Some1assert_eq!(s1.or_else(fn_none), s1);  // Some or_else None = Someassert_eq!(n.or_else(fn_some), s2);   // None or_else Some = Someassert_eq!(n.or_else(fn_none), None); // None1 or_else None2 = None2// or_else with Resultlet o1: Result<&str, &str> = Ok("ok1");let o2: Result<&str, &str> = Ok("ok2");let fn_ok = |_| Ok("ok2"); // 类似于: let fn_ok = |_| -> Result<&str, &str> { Ok("ok2") };let e1: Result<&str, &str> = Err("error1");let e2: Result<&str, &str> = Err("error2");let fn_err = |_| Err("error2");assert_eq!(o1.or_else(fn_ok), o1);  // Ok1 or_else Ok2 = Ok1assert_eq!(o1.or_else(fn_err), o1); // Ok or_else Err = Okassert_eq!(e1.or_else(fn_ok), o2);  // Err or_else Ok = Okassert_eq!(e1.or_else(fn_err), e2); // Err1 or_else Err2 = Err2
}
fn main() {// and_then with Optionlet s1 = Some("some1");let s2 = Some("some2");let fn_some = |_| Some("some2"); // 类似于: let fn_some = |_| -> Option<&str> { Some("some2") };let n: Option<&str> = None;let fn_none = |_| None;assert_eq!(s1.and_then(fn_some), s2); // Some1 and_then Some2 = Some2assert_eq!(s1.and_then(fn_none), n);  // Some and_then None = Noneassert_eq!(n.and_then(fn_some), n);   // None and_then Some = Noneassert_eq!(n.and_then(fn_none), n);   // None1 and_then None2 = None1// and_then with Resultlet o1: Result<&str, &str> = Ok("ok1");let o2: Result<&str, &str> = Ok("ok2");let fn_ok = |_| Ok("ok2"); // 类似于: let fn_ok = |_| -> Result<&str, &str> { Ok("ok2") };let e1: Result<&str, &str> = Err("error1");let e2: Result<&str, &str> = Err("error2");let fn_err = |_| Err("error2");assert_eq!(o1.and_then(fn_ok), o2);  // Ok1 and_then Ok2 = Ok2assert_eq!(o1.and_then(fn_err), e2); // Ok and_then Err = Errassert_eq!(e1.and_then(fn_ok), e1);  // Err and_then Ok = Errassert_eq!(e1.and_then(fn_err), e1); // Err1 and_then Err2 = Err1
}
filter

filter 用于对 Option 进行过滤:

fn main() {let s1 = Some(3);let s2 = Some(6);let n = None;let fn_is_even = |x: &i8| x % 2 == 0;assert_eq!(s1.filter(fn_is_even), n);  // Some(3) -> 3 is not even -> Noneassert_eq!(s2.filter(fn_is_even), s2); // Some(6) -> 6 is even -> Some(6)assert_eq!(n.filter(fn_is_even), n);   // None -> no value -> None
}
map() 和 map_err()

map 可以将 Some 或 Ok 中的值映射为另一个同类型的值:

fn main() {let s1 = Some("abcde");let s2 = Some(5);let n1: Option<&str> = None;let n2: Option<usize> = None;let o1: Result<&str, &str> = Ok("abcde");let o2: Result<usize, &str> = Ok(5);let e1: Result<&str, &str> = Err("abcde");let e2: Result<usize, &str> = Err("abcde");
// 统计字符串中let fn_character_count = |s: &str| s.chars().count();assert_eq!(s1.map(fn_character_count), s2); // Some1 map = Some2assert_eq!(n1.map(fn_character_count), n2); // None1 map = None2assert_eq!(o1.map(fn_character_count), o2); // Ok1 map = Ok2assert_eq!(e1.map(fn_character_count), e2); // Err1 map = Err2
}

但是如果你想要将 Err 中的值进行改变,此时我们需要用 map_err:

fn main() {let o1: Result<&str, &str> = Ok("abcde");let o2: Result<&str, isize> = Ok("abcde");let e1: Result<&str, &str> = Err("404");let e2: Result<&str, isize> = Err(404);let fn_character_count = |s: &str| -> isize { s.parse().unwrap() }; // 该函数返回一个 isizeassert_eq!(o1.map_err(fn_character_count), o2); // Ok1 map = Ok2assert_eq!(e1.map_err(fn_character_count), e2); // Err1 map = Err2
}
map_or() 和 map_or_else()

map_or 在 map 的基础上提供了一个默认值:

fn main() {const V_DEFAULT: u32 = 1;let s: Result<u32, ()> = Ok(10);let n: Option<u32> = None;let fn_closure = |v: u32| v + 2;assert_eq!(s.map_or(V_DEFAULT, fn_closure), 12);assert_eq!(n.map_or(V_DEFAULT, fn_closure), V_DEFAULT);
}

如上所示,当处理 None 的时候,V_DEFAULT 作为默认值被直接返回。

map_or_else 与 map_or 类似,但是它是通过一个闭包来提供默认值:

fn main() {let s = Some(10);let n: Option<i8> = None;let fn_closure = |v: i8| v + 2;let fn_default = || 1;assert_eq!(s.map_or_else(fn_default, fn_closure), 12);assert_eq!(n.map_or_else(fn_default, fn_closure), 1);let o = Ok(10);let e = Err(5);let fn_default_for_result = |v: i8| v + 1; // 闭包可以对 Err 中的值进行处理,并返回一个新值assert_eq!(o.map_or_else(fn_default_for_result, fn_closure), 12);assert_eq!(e.map_or_else(fn_default_for_result, fn_closure), 6);
}
ok_or() and ok_or_else()

这两兄弟可以将 Option 类型转换为 Result 类型。其中 ok_or 接收一个默认的 Err 参数:

fn main() {const ERR_DEFAULT: &str = "error message";let s = Some("abcde");let n: Option<&str> = None;let o: Result<&str, &str> = Ok("abcde");let e: Result<&str, &str> = Err(ERR_DEFAULT);assert_eq!(s.ok_or(ERR_DEFAULT), o); // Some(T) -> Ok(T)assert_eq!(n.ok_or(ERR_DEFAULT), e); // None -> Err(default)
}

而 ok_or_else 接收一个闭包作为 Err 参数:

fn main() {let s = Some("abcde");let n: Option<&str> = None;let fn_err_message = || "error message";let o: Result<&str, &str> = Ok("abcde");let e: Result<&str, &str> = Err("error message");assert_eq!(s.ok_or_else(fn_err_message), o); // Some(T) -> Ok(T)assert_eq!(n.ok_or_else(fn_err_message), e); // None -> Err(default)
}

自定义错误类型

虽然标准库定义了大量的错误类型,但是一个严谨的项目,光使用这些错误类型往往是不够的,例如我们可能会为暴露给用户的错误定义相应的类型。

为了帮助我们更好的定义错误,Rust 在标准库中提供了一些可复用的特征,例如 std::error::Error 特征:

use std::fmt::{Debug, Display};pub trait Error: Debug + Display {fn source(&self) -> Option<&(Error + 'static)> { ... }
}

当自定义类型实现该特征后,该类型就可以作为 Err 来使用。

实际上,自定义错误类型只需要实现 Debug 和 Display 特征即可,source 方法是可选的,而 Debug 特征往往也无需手动实现,可以直接通过 derive 来派生

最简单的错误

use std::fmt;// AppError 是自定义错误类型,它可以是当前包中定义的任何类型,在这里为了简化,我们使用了单元结构体作为例子。
// 为 AppError 自动派生 Debug 特征
#[derive(Debug)]
struct AppError;// 为 AppError 实现 std::fmt::Display 特征
impl fmt::Display for AppError {fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {write!(f, "An Error Occurred, Please Try Again!") // user-facing output}
}// 一个示例函数用于产生 AppError 错误
fn produce_error() -> Result<(), AppError> {Err(AppError)
}fn main(){match produce_error() {Err(e) => eprintln!("{}", e),_ => println!("No error"),}eprintln!("{:?}", produce_error()); // Err({ file: src/main.rs, line: 17 })
}

上面的例子很简单,我们定义了一个错误类型,当为它派生了 Debug 特征,同时手动实现了 Display 特征后,该错误类型就可以作为 Err来使用了。

事实上,实现 Debug 和 Display 特征并不是作为 Err 使用的必要条件,大家可以把这两个特征实现和相应使用去除,然后看看代码会否报错。既然如此,我们为何要为自定义类型实现这两个特征呢?原因有二:

错误得打印输出后,才能有实际用处,而打印输出就需要实现这两个特征
可以将自定义错误转换成 Box<dyn std::error:Error> 特征对象,在后面的归一化不同错误类型部分.
现在再来定义一个具有错误码和信息的错误:

use std::fmt;struct AppError {code: usize,message: String,
}// 根据错误码显示不同的错误信息
impl fmt::Display for AppError {fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {let err_msg = match self.code {404 => "Sorry, Can not find the Page!",_ => "Sorry, something is wrong! Please Try Again!",};
// 向一个缓冲区里写格式化的数据。
// write!将格式化的内容写入到指定的输出流中,例如write!(std::io::stdout(), "hello {}!", "world")将在标准输出流中打印出hello world!。
// print!也是对输出内容进行格式化,但是输出的目标是标准输出流write!(f, "{}", err_msg)}
}impl fmt::Debug for AppError {fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {write!(f,"AppError {{ code: {}, message: {} }}",self.code, self.message)}
}
// 测试错误方法
fn produce_error() -> Result<(), AppError> {Err(AppError {code: 404,message: String::from("Page not found"),})
}fn main() {match produce_error() {Err(e) => eprintln!("{}", e), // 抱歉,未找到指定的页面!_ => println!("No error"),}eprintln!("{:?}", produce_error()); // Err(AppError { code: 404, message: Page not found })eprintln!("{:#?}", produce_error());// Err(//     AppError { code: 404, message: Page not found }// )
}

在本例中,我们除了增加了错误码和消息外,还手动实现了 Debug 特征,原因在于,我们希望能自定义 Debug 的输出内容,而不是使用派生后系统提供的默认输出形式。

错误转换 From 特征

标准库、三方库、本地库,各有各的精彩,各也有各的错误。那么问题就来了,我们该如何将其它的错误类型转换成自定义的错误类型?总不能神鬼牛魔,同台共舞吧。

好在 Rust 为我们提供了 std::convert::From 特征:

pub trait From<T>: Sized {fn from(_: T) -> Self;
}

大家都使用过 String::from 函数吧?它可以通过 &str 来创建一个 String,其实该函数就是 From 特征提供的

下面一起来看看如何为自定义类型实现 From 特征:

use std::fs::File;
use std::io;#[derive(Debug)]
struct AppError {kind: String,    // 错误类型message: String, // 错误信息
}// 为 AppError 实现 std::convert::From 特征,由于 From 包含在 std::prelude 中,因此可以直接简化引入。
// 实现 From<io::Error> 意味着我们可以将 io::Error 错误转换成自定义的 AppError 错误
impl From<io::Error> for AppError {fn from(error: io::Error) -> Self {AppError {kind: String::from("io"),message: error.to_string(),}}
}fn main() -> Result<(), AppError> {let _file = File::open("nonexistent_file.txt")?;Ok(())
}
Error: AppError { kind: "io", message: "No such file or directory (os error 2)" }

上面的代码中除了实现 From 外,还有一点特别重要,那就是 ? 可以将错误进行隐式的强制转换:File::open 返回的是 std::io::Error, 我们并没有进行任何显式的转换,它就能自动变成 AppError ,这就是 ? 的强大之处!当然实现隐式转换的前提是实现了对应的From;

再来看看多个不同的错误转换成 AppError 的实现:

use std::fs::File;
use std::io::{self, Read};
use std::num;#[derive(Debug)]
struct AppError {kind: String,message: String,
}impl From<io::Error> for AppError {fn from(error: io::Error) -> Self {AppError {kind: String::from("io"),message: error.to_string(),}}
}impl From<num::ParseIntError> for AppError {fn from(error: num::ParseIntError) -> Self {AppError {kind: String::from("parse"),message: error.to_string(),}}
}fn main() -> Result<(), AppError> {let mut file = File::open("hello_world.txt")?;let mut content = String::new();file.read_to_string(&mut content)?;let _number: usize;_number = content.parse()?;Ok(())
}
// 01. 若 hello_world.txt 文件不存在
Error: AppError { kind: "io", message: "No such file or directory (os error 2)" }// 02. 若用户没有相关的权限访问 hello_world.txt
Error: AppError { kind: "io", message: "Permission denied (os error 13)" }// 03. 若 hello_world.txt 包含有非数字的内容,例如 Hello, world!
Error: AppError { kind: "parse", message: "invalid digit found in string" }

归一化不同的错误类型

在实际项目中,我们往往会为不同的错误定义不同的类型,这样做非常好,但是如果你要在一个函数中返回不同的错误呢?例如:

use std::fs::read_to_string;fn main() -> Result<(), std::io::Error> {let html = render()?;println!("{}", html);Ok(())
}fn render() -> Result<String, std::io::Error> {let file = std::env::var("MARKDOWN")?;let source = read_to_string(file)?;Ok(source)
}

上面的代码会报错,原因在于 render 函数中的两个 ? 返回的实际上是不同的错误:env::var() 返回的是 std::env::VarError,而 read_to_string 返回的是 std::io::Error。

为了满足 render 函数的签名,我们就需要将 env::VarError 和 io::Error 归一化为同一种错误类型。要实现这个目的有三种方式:

  • 使用特征对象 Box <dyn Error>
  • 自定义错误类型
  • 使用 thiserror
    下面依次来看看相关的解决方式。
Box<dyn Error>
use std::fs::read_to_string;
use std::error::Error;
fn main() -> Result<(), Box<dyn Error>> {let html = render()?;println!("{}", html);Ok(())
}fn render() -> Result<String, Box<dyn Error>> {let file = std::env::var("MARKDOWN")?;let source = read_to_string(file)?;Ok(source)
}

这个方法很简单,在绝大多数场景中,性能也非常够用,但是有一个问题:Result 实际上不会限制错误的类型,也就是一个类型就算不实现 Error 特征,它依然可以在 Result<T, E> 中作为 E 来使用,此时这种特征对象的解决方案就无能为力了。

自定义错误类型

与特征对象相比,自定义错误类型麻烦归麻烦,但是它非常灵活,因此也不具有上面的类似限制:

use std::fs::read_to_string;fn main() -> Result<(), MyError> {let html = render()?;println!("{}", html);Ok(())
}fn render() -> Result<String, MyError> {let file = std::env::var("MARKDOWN")?;let source = read_to_string(file)?;Ok(source)
}#[derive(Debug)]
enum MyError {EnvironmentVariableNotFound,IOError(std::io::Error),
}impl From<std::env::VarError> for MyError {fn from(_: std::env::VarError) -> Self {Self::EnvironmentVariableNotFound}
}impl From<std::io::Error> for MyError {fn from(value: std::io::Error) -> Self {Self::IOError(value)}
}impl std::error::Error for MyError {}impl std::fmt::Display for MyError {fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {match self {MyError::EnvironmentVariableNotFound => write!(f, "Environment variable not found"),MyError::IOError(err) => write!(f, "IO Error: {}", err.to_string()),}}
}

上面代码中有一行值得注意:impl std::error::Error for MyError {} ,只有为自定义错误类型实现 Error 特征后,才能转换成相应的特征对象。

简化错误处理

对于开发者而言,错误处理是代码中打交道最多的部分之一,因此选择一把趁手的武器也很重要,它可以帮助我们节省大量的时间和精力,好钢应该用在代码逻辑而不是冗长的错误处理上。

thiserror

thiserror可以帮助我们简化上面的第二种解决方案:

use std::fs::read_to_string;fn main() -> Result<(), MyError> {let html = render()?;println!("{}", html);Ok(())
}fn render() -> Result<String, MyError> {let file = std::env::var("MARKDOWN")?;let source = read_to_string(file)?;Ok(source)
}#[derive(thiserror::Error, Debug)]
enum MyError {#[error("Environment variable not found")]EnvironmentVariableNotFound(#[from] std::env::VarError),#[error(transparent)]IOError(#[from] std::io::Error),
}
anyhow

anyhow 和 thiserror 是同一个作者开发的,这里是作者关于 anyhow 和 thiserror 的原话:

如果你想要设计自己的错误类型,同时给调用者提供具体的信息时,就使用 thiserror,例如当你在开发一个三方库代码时。如果你只想要简单,就使用 anyhow,例如在自己的应用服务中。

use std::fs::read_to_string;use anyhow::Result;fn main() -> Result<()> {let html = render()?;println!("{}", html);Ok(())
}fn render() -> Result<String> {let file = std::env::var("MARKDOWN")?;let source = read_to_string(file)?;Ok(source)
}

关于如何选用 thiserror 和 anyhow 只需要遵循一个原则即可:是否关注自定义错误消息,关注则使用 thiserror(常见业务代码),否则使用 anyhow(编写第三方库代码).

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

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

相关文章

波卡三季度报告:已实现白皮书目标,异步支持与应用链技术推进

作者&#xff1a;Nicholas Garcia&#xff0c;Messari 研究分析师 编译&#xff1a;OneBlock 来源&#xff1a;https://messari.io/report/state-of-polkadot-q3-2023 知名分析平台 Messari 发布了 Polkadot 2023 年第三季度报告&#xff0c;分析了波卡的关键数据指标以及网…

挑战视觉边界,探索图形验证码背后的黑科技

在日常生活中&#xff0c;我们登录网站或者其他平台时&#xff0c;在填写完账号密码之后&#xff0c;还会让我们填写4或6位的数字或者英文字母等&#xff0c;填写正确才能请求登录。这个其实是防止某一个特定注册用户用特定程序暴力破解方式进行不断的登陆尝试&#xff0c;如下…

大数据HCIE成神之路之数学(1)——基础数学

基础数学 1.1 基础数学实验介绍1.1.1 内容介绍1.1.2 框架介绍 1.2 基础数学实现1.2.1 ceil实现1.2.2 floor实现1.2.3 cos实现1.2.4 tan实现1.2.5 degrees实现1.2.6 exp实现1.2.7 fabs实现1.2.8 factorial实现1.2.9 fsum实现1.2.10 fmod实现1.2.11 log实现1.2.12 sqrt实现1.2.13…

Java虚拟机运行时数据区结构详解

Java虚拟机运行时数据区结构如图所示 程序计数器 程序计数器&#xff08;Program Counter Register&#xff09;是一块较小的内存空间&#xff0c;它可以看作是当前线程所执行的字节码的行号指示器。 多线程切换时&#xff0c;为了能恢复到正确的执行位置&#xff0c;每条线程…

基于DE10-Standard Cyclone V SoC FPGA学习---开发板简介

基于DE10-Standard Cyclone V SoC FPGA学习---开发板简介 简介产品规格基于 ARM 的 HPS配置与调试存储器件通讯连接头显示器音频视频输入模数转换器开关、按钮、指示器传感器电源 DE10-Standard 开发板系统框图Connect HTG 组件配置设计资源其他资源 简介 开发板资料 见 DE10-…

【漏洞复现】NUUO摄像头存在远程命令执行漏洞

漏洞描述 NUUO摄像头是中国台湾NUUO公司旗下的一款网络视频记录器&#xff0c;该设备存在远程命令执行漏洞&#xff0c;攻击者可利用该漏洞执行任意命令&#xff0c;进而获取服务器的权限。 免责声明 技术文章仅供参考&#xff0c;任何个人和组织使用网络应当遵守宪法法律&…

业务:业务系统检查项参考

名录明细云平台摸底1.原有云平台体系&#xff1a;VMware、openstack、ovirt、k8s、docker、混合云系列及版本 2.原有云平台规模&#xff0c;物理机数量、虚拟机数量、迁移业务系统所占配额 3.待补充系统摸底 (适用于物理主机)每一台虚拟机或物理机&#xff1a; 1.系统全局参数…

Vellum —— Constraint 约束

目录 Stretch Bend Pin Drag 解算器对DOP外节点的约束属性&#xff0c;只会读取起始帧的值&#xff1b; Stretch 保持点间的初始距离&#xff1b; Stiffness 越高的stiffness&#xff0c;就需要越多的迭代来收敛&#xff0c;如constraint iterations或substeps(子步会更好)…

【Linux】:进程间通信

进程间通信 一.基本概念二.简单的通信-管道1.建立通信信道2.通信接口 一.基本概念 是什么 两个或多个进程实现数据层面的交互。 因为进程独立性的存在&#xff0c;导致进程间的通信成本比较高。 为什么 因为我们有多进程协同的需求。 怎么办 a.进程间通信的本质:必须让不…

无线物理层安全大作业

这个标题很帅 Beamforming Optimization for Physical Layer Security in MISO Wireless NetworksProblem Stateme![在这里插入图片描述](https://img-blog.csdnimg.cn/58ebb0df787c4e23b0c7be4189ebc322.png) Beamforming Optimization for Physical Layer Security in MISO W…

Android 屏幕适配

目录 一、为什么要适配 二、几个重要的概念 2.1 屏幕尺寸 2.2 屏幕分辨率 2.3 屏幕像素密度 2.4 屏幕尺寸、分辨率、像素密度三者关系 三、常用单位 3.1 密度无关像素(dp) 3.2 独立比例像素&#xff08;sp&#xff09; 3.3 dp与px的转换 四、解决方案 4.1 今日头条…

python_主动调用其他类的成员

# 主动调用其他类的成员 # 方式一: class Base(object):def f1(self):print("5个功能") class Foo(object):def f1(self):print("3个功能")# Base.实例方法(自己传self),与继承无关Base.f1(self)obj Foo() obj.f1()print("#"*20)# 方式二:按照类…

Netty+SpringBoot 打造一个 TCP 长连接通讯方案

项目背景 最近公司某物联网项目需要使用socket长连接进行消息通讯&#xff0c;捣鼓了一版代码上线&#xff0c;结果BUG不断&#xff0c;本猿寝食难安&#xff0c;于是求助度娘&#xff0c;数日未眠项目终于平稳运行了&#xff0c;本着开源共享的精神&#xff0c;本猿把项目代码…

【139.单词拆分】

目录 一、题目解析二、算法原理三、代码实现 一、题目解析 二、算法原理 三、代码实现 class Solution { public:bool wordBreak(string s, vector<string>& wordDict) {int n s.size();unordered_set<string> hash;for (auto x : wordDict) hash.insert(x);…

HDU1276:士兵队列训练问题 ← STL queue

【题目来源】http://acm.hdu.edu.cn/showproblem.php?pid1276【题目描述】 某部队进行新兵队列训练&#xff0c;将新兵从一开始按顺序依次编号&#xff0c;并排成一行横队&#xff0c;训练的规则如下&#xff1a;从头开始一至二报数&#xff0c;凡报到二的出列&#xff0c;剩下…

【spring】ApplicationContext的实现

目录 一、ClassPathXmlApplicationContext1.1 说明1.2 代码示例1.3 截图示例 二、FileSystemXmlApplicationContext2.1 说明2.2 代码示例2.3 加载xml的bean定义示例 三、AnnotationConfigApplicationContext3.1 说明3.2 代码示例3.3 截图示例 四、AnnotationConfigServletWebSe…

flutter web 中嵌入一个html

介绍 flutter web 支持使用 HtmlElementView嵌入html import dart:html; import dart:ui as ui; import package:flutter/cupertino.dart;class WebWidget extends StatelessWidget {const WebWidget({super.key});overrideWidget build(BuildContext context) {DivElement fr…

第77题. 组合

原题链接&#xff1a;第77题. 组合 全代码&#xff1a; class Solution { private:vector<vector<int>> result; // 存放符合条件结果的集合vector<int> path; // 用来存放符合条件结果void backtracking(int n, int k, int startIndex) {if (path.size() …

geoserver的ECQL查询

ECQL Reference — GeoServer 2.24.x User Manual CQL and ECQL — GeoServer 2.24.x User Manual ECQL是CQL的扩展&#xff0c;类似sql查询&#xff0c;比ogc的xml格式简单&#xff0c;可以应用在wfs和wms查询上。 通过可视化页面查看过滤效果&#xff0c;默认视图 主键不会…

STM32GPIO——上拉下拉电阻、施密特触发器、P-MOS/N-MOS管

图1和图2 两种版本的GPIO基本结构图 如上两个图所示&#xff0c;标号2都为上拉、下拉电阻部分&#xff0c;阻值约为30k~50k欧&#xff0c;通过对应开关进行控制&#xff0c;开关由寄存器控制。 当引脚外部的器件没有干扰引脚的电压时&#xff0c;即没有外部的上、下拉电压&a…