Rust之泛型、特性和生命期(四):验证有生存期的引用
开发环境
- Windows 10
- Rust 1.71.0
- VS Code 1.80.1
项目工程
这里继续沿用上次工程rust-demo
验证具有生存期的引用
生存期是我们已经在使用的另一种泛型。生存期不是确保一个类型具有我们想要的行为,而是确保引用在我们需要时有效。
我们在第4章“引用和借用”一节中没有讨论的一个细节是,Rust中的每个引用都有一个生命周期,这是该引用有效的范围。大多数时候,生存期是隐式的,是推断出来的,就像大多数时候,类型是推断出来的一样。只有在可能存在多种类型时,我们才必须对类型进行注释。类似地,当引用的生存期可以以几种不同的方式关联时,我们必须注释生存期。Rust要求我们使用通用的生存期参数来注释这些关系,以确保运行时使用的实际引用绝对有效。
注释生存期甚至不是大多数其他编程语言都有的概念,所以这会让人感到陌生。虽然我们不会在这一章完整地讨论生存期,但是我们会讨论你可能遇到的生存期语法的常见方式,这样你就可以熟悉这个概念了。
用生存期防止悬空引用
生存期的主要目的是防止悬空引用,悬空引用会导致程序引用它想要引用的数据之外的数据。考虑示例16中的程序,它有一个外部作用域和一个内部作用域。
fn main() {let r;{let x = 5;r = &x;}println!("r: {}", r);
}
示例16:试图使用其值超出范围的引用
注意:示例16、示例17和示例23中的例子声明了变量,但没有给它们一个初始值,所以变量名存在于外部作用域中。乍一看,这似乎与Rust没有空值相冲突。然而,如果我们试图在给变量赋值之前使用它,我们会得到一个编译时错误,这表明Rust确实不允许空值。
外部作用域声明了一个名为r的没有初始值的变量,内部作用域声明了一个名为x的初始值为5的变量。在内部范围内,我们试图将r的值设置为对x的引用。然后内部范围结束,我们试图打印r中的值。这段代码不会编译,因为r引用的值在我们试图使用它之前已经超出了范围。以下是错误消息:
变量x没有“活得足够久”原因是当内部范围在第1099行结束时,x将超出范围。但是r对外作用域仍然有效;因为它的范围更大,我们说它“寿命更长”如果Rust允许这段代码工作,r将引用x超出范围时释放的内存,我们试图用r做的任何事情都不会正确工作。那么Rust是如何确定这段代码无效的呢?它使用借用检查器。
借用检查器
Rust编译器有一个借用检查器,它比较范围以确定是否所有借用都是有效的。示例17显示了与示例16相同的代码,但是带有显示变量生命周期的注释。
fn main() {let r; // ---------+-- 'a// |{ // |let x = 5; // -+-- 'b |r = &x; // | |} // -+ |// |println!("r: {}", r); // |
} // ---------+
示例17:分别命名为‘a’和‘b’的r和x的生命期的注释
这里,我们用“a”标注了r的生存期,用“b”标注了x的生存期。正如您所看到的,内部的“b”块比外部的“a”生存期块小得多。在编译时,Rust比较了两个生存期的大小,发现r的生存期为“a ”,但它引用的内存的生存期为“b”。程序被拒绝,因为“b比”a短:引用的主题没有引用的生存期长。
示例18修正了代码,所以它没有悬空引用,编译时没有任何错误。
fn main() {let x = 5; // ----------+-- 'b// |let r = &x; // --+-- 'a |// | |println!("r: {}", r); // | |// --+ |
} // ----------+
示例18:有效的引用,因为数据比引用有更长的生命周期
这里,x的生存期为“b ”,在这种情况下,它大于“a ”,这意味着r可以引用x,因为Rust知道,当x有效时,r中的引用将始终有效。
既然您已经知道了引用的生命期在哪里,以及Rust如何分析生命期以确保引用总是有效的,那么让我们在函数的上下文中探索参数和返回值的一般生命期。
函数中的泛型生存期
我们将编写一个函数,返回两个字符串片段中较长的一个。该函数将接受两个字符串切片,并返回一个字符串切片。在我们实现了longest的函数之后,示例19中的代码应该打印出The longest string is abcd。
文件名:src/main.rs
fn main() {let string1 = String::from("abcd");let string2 = "xyz";let result = longest(string1.as_str(), string2);println!("The longest string is {}", result);
}
示例19:main函数,它调用最长的函数来查找两个字符串片段中较长的一个
注意,我们希望函数接受字符串片,字符串片是引用,而不是字符串,因为我们不希望longest函数接受其参数的所有权。关于为什么我们在示例19中使用的参数是我们想要的参数的更多讨论,请参考第4章中的“字符串片段作为参数”一节。
如果我们试图实现示例20所示的最长的函数,它不会编译。
文件名:src/main.rs
fn longest(x: &str, y: &str) -> &str {if x.len() > y.len() {x} else {y}
}
示例20:最长函数的一个实现,返回两个字符串片段中较长的一个,但还没有编译
帮助文本显示,返回类型需要一个通用的生存期参数,因为Rust无法判断返回的引用是指x还是y。实际上,我们也不知道,因为该函数体中的if块返回对x的引用,else块返回对y的引用!
当我们定义这个函数时,我们不知道将传递给这个函数的具体值,所以我们不知道是if情况还是else情况会执行。我们也不知道将要传入的引用的具体生存期,所以我们不能像在示例17和示例18中那样查看作用域来确定我们返回的引用是否总是有效。借用检查器也不能确定这一点,因为它不知道x和y的生命周期如何与返回值的生命周期相关。要修复此错误,我们将添加定义引用之间关系的通用生存期参数,以便借项检查器可以执行其分析。
生存期注释语法
生存期注释不会改变任何引用的生存期。相反,它们描述了多个引用的生存期之间的关系,而不会影响生存期。正如当签名指定泛型类型参数时,函数可以接受任何类型一样,通过指定泛型生存期参数,函数可以接受任何生存期的引用。
生存期注释有一个稍微不同寻常的语法:生存期参数的名称必须以撇号(')开头,并且通常都是小写的,非常短,就像泛型类型一样。大多数人在第一个生命周期注释中使用名称'a。我们将生存期参数注释放在引用的&之后,使用空格将注释与引用的类型分开。
下面是一些例子:对没有寿命参数的i32的引用,对具有名为'a的寿命参数的i32的引用,以及对也具有寿命'a的i32的可变引用。
&i32 // a reference
&'a i32 // a reference with an explicit lifetime
&'a mut i32 // a mutable reference with an explicit lifetime
一个生存期注释本身没有太多意义,因为注释是为了告诉Rust多个引用的通用生存期参数是如何相互关联的。让我们看看在longest函数的上下文中,生存期注释是如何相互关联的。
函数签名中的生存期注释
为了在函数签名中使用生存期注释,我们需要在函数名和参数列表之间的尖括号内声明泛型生存期参数,就像我们对泛型类型参数所做的那样。
我们希望签名表达以下约束:只要两个参数都有效,返回的引用就有效。这是参数的生存期和返回值之间的关系。我们将把生存期命名为'a,然后把它添加到每个引用中,如示例21所示。
文件名:src/main.rs
fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {if x.len() > y.len() {x} else {y}
}
示例21:longest的函数定义,指定签名中的所有引用必须有相同的生命周期'a
当我们使用示例19中的main函数时,这段代码应该可以编译并产生我们想要的结果。
函数签名现在告诉Rust,对于某个生存期'a,函数采用两个参数,这两个参数都是至少与生存期'a一样长的字符串片段。函数签名还告诉Rust,从函数返回的字符串片段将至少与生存期'a一样长。实际上,这意味着由longest函数返回的引用的生存期与由函数参数引用的值的较小生存期相同。我们希望Rust在分析这段代码时使用这些关系。
请记住,当我们在这个函数签名中指定生存期参数时,我们不会改变任何传入或返回的值的生存期。相反,我们指定借用检查器应该拒绝任何不符合这些约束的值。请注意,longest函数不需要确切知道x和y将存在多长时间,只需要用某个范围来代替满足该签名的'a。
在函数中标注生存期时,标注放在函数签名中,而不是函数体中。生存期注释成为函数契约的一部分,很像签名中的类型。让函数签名包含生命周期契约意味着Rust编译器所做的分析可以更简单。如果函数的注释方式或调用方式有问题,编译器错误可以更准确地指向我们的代码部分和约束。相反,如果Rust编译器对我们想要的生存期关系做了更多的推断,编译器可能只能指出我们代码的一个用法,而不是问题的原因。
当我们将具体的引用传递给longest时,替换'a的具体生存期是x的范围与y的范围重叠的部分,换句话说,泛型生存期'a将获得等于x和y的较小生存期的具体生存期,因为我们已经用相同的生存期参数'a对返回的引用进行了注释,所以返回的引用对于x和y的较小生存期的长度也是有效的。
让我们看看生存期注释如何通过传入具有不同具体生存期的引用来限制longest函数。示例22是一个简单的例子。
文件名:src/main.rs
fn main() {let string1 = String::from("long string is long");{let string2 = String::from("xyz");let result = longest(string1.as_str(), string2.as_str());println!("The longest string is {}", result);}
}
示例22:使用引用具有不同具体生命周期的String值的longest函数
在这个例子中,string1在外部作用域结束之前有效,string2在内部作用域结束之前有效,而result引用在内部作用域结束之前有效的内容。运行这段代码,您将看到借入检查器批准了;它将编译并打印The longest string is long string is long。
接下来,让我们尝试一个例子,它显示了result中引用的生存期必须是两个参数中较小的生存期。我们将把result变量的声明移到内部作用域之外,但是将值分配给string2作用域内的result变量。那我们就转移println!在内部范围结束后,在内部范围外使用result。示例23中的代码无法编译。
文件名:src/main.rs
fn main() {let string1 = String::from("long string is long");let result;{let string2 = String::from("xyz");result = longest(string1.as_str(), string2.as_str());}println!("The longest string is {}", result);
}
示例23:试图在string2超出范围后使用result
该错误显示,要使result对println!有效!语句中,string2需要在外部范围结束之前一直有效。Rust知道这一点,因为我们使用相同的生存期参数'a来注释函数参数和返回值的生存期。
作为人类,我们可以查看这段代码,发现string1比string2长,因此result将包含对string1的引用。因为string1还没有超出范围,所以对string1的引用对于println!仍然有效!声明。但是,编译器在这种情况下看不到引用有效。我们已经告诉Rust,由longest函数返回的引用的生存期与传入的引用的较小生存期相同。因此,借用检查器拒绝示例23中的代码,因为它可能有一个无效的引用。
尝试设计更多的实验,改变传递给longest函数的引用的值和生存期,以及如何使用返回的引用。在编译之前,假设您的实验是否会通过借用检查器;然后检查你是否正确!
从生存期的角度思考
您需要指定生存期参数的方式取决于您的函数正在做什么。例如,如果我们将longest函数的实现改为总是返回第一个参数,而不是最长的字符串片段,我们就不需要在y参数上指定生存期。下面的代码将会编译:
文件名:src/main.rs
fn longest<'a>(x: &'a str, y: &str) -> &'a str {x
}
我们已经为参数x和返回类型指定了生存期参数'a,但没有为参数y指定,因为y的生存期与x的生存期或返回值没有任何关系。
从函数返回引用时,返回类型的生存期参数需要与其中一个参数的生存期参数匹配。如果返回的引用不引用参数之一,则它必须引用在此函数中创建的值。但是,这将是一个悬空引用,因为在函数结束时,该值将超出范围。考虑一下这个不会编译的longest函数的尝试实现:
文件名:src/main.rs
fn longest<'a>(x: &str, y: &str) -> &'a str {let result = String::from("really long string");result.as_str()
}
这里,即使我们已经为返回类型指定了生存期参数'a这个实现也将无法编译,因为返回值的生存期与参数的生存期完全无关。下面是我们得到的错误消息:
问题是result超出了范围,在longest函数结束时被清除。我们还试图返回一个对函数result的引用。我们无法指定会改变悬空引用的生存期参数,Rust也不允许我们创建悬空引用。在这种情况下,最好的解决方法是返回一个自己的数据类型而不是引用,这样调用函数就负责清理这个值。
最终,生存期语法是关于连接各种参数和函数返回值的生存期。一旦它们被连接起来,Rust就有足够的信息来允许内存安全的操作,并禁止会创建悬空指针或违反内存安全的操作。
结构定义中的生存期注释
到目前为止,我们定义的结构都持有自己的类型。我们可以定义结构来保存引用,但在这种情况下,我们需要在结构定义中的每个引用上添加一个生存期注释。示例24有一个名为ImportantExcerpt的结构,它保存一个字符串切片。
文件名:src/main.rs
struct ImportantExcerpt<'a> {part: &'a str,
}fn main() {let novel = String::from("Call me Ishmael. Some years ago...");let first_sentence = novel.split('.').next().expect("Could not find a '.'");let i = ImportantExcerpt {part: first_sentence,};
}
示例24:一个包含引用的结构,需要一个生存期注释
这个结构有一个保存字符串片段的字段part,这是一个引用。与泛型数据类型一样,我们在结构名称后面的尖括号中声明泛型生存期参数的名称,这样我们就可以在结构定义的主体中使用生存期参数。此注释意味着ImportantExcerpt的实例不能比它在part字段中保存的引用存活得更久。
这里的main函数创建了ImportantExcerpt结构的一个实例,该实例保存了对变量novel所拥有的字符串的第一句的引用。在创建ImportantExcerpt实例之前,novel中的数据已经存在。此外,在ImportantExcerpt超出范围之前,novel不会超出范围,因此ImportantExcerpt实例中的引用是有效的。
终生省略
您已经了解到每个引用都有一个生存期,并且您需要为使用引用的函数或结构指定生存期参数。然而,在第四章中,我们在示例4-9中有一个函数,在示例25中再次显示,它编译时没有生命期注释。
文件名:src/lib.rs
fn first_word(s: &str) -> &str {let bytes = s.as_bytes();for (i, &item) in bytes.iter().enumerate() {if item == b' ' {return &s[0..i];}}&s[..]
}
示例25:我们在示例4-9中定义的一个函数,编译时没有生命期注释,即使参数和返回类型是引用
这个函数编译时没有生存期注释的原因是历史原因:在Rust的早期版本(1.0之前)中,这段代码不会编译,因为每个引用都需要一个显式的生存期。那时,函数签名应该是这样写的:
fn first_word<'a>(s: &'a str) -> &'a str {
在编写了大量Rust代码后,Rust团队发现Rust程序员在特定的情况下一遍又一遍地输入相同的生存期注释。这些情况是可以预测的,并且遵循一些确定的模式。开发人员将这些模式编程到编译器的代码中,因此借用检查器可以推断这些情况下的生存期,而不需要显式的注释。
这段Rust历史是相关的,因为更确定的模式可能会出现并被添加到编译器中。将来,可能需要更少的生存期注释。
编程到Rust的引用分析中的模式被称为生存期省略规则。这些不是程序员要遵守的规则;它们是编译器将考虑的一组特殊情况,如果您的代码符合这些情况,您就不需要显式地编写生存期。
省略规则没有提供完整的推论。如果Rust确定性地应用了这些规则,但是对于引用的生命周期仍然不明确,编译器不会猜测其余引用的生命周期应该是多少。编译器不会猜测,而是会给出一个错误,您可以通过添加生存期注释来解决这个错误。
函数或方法参数的生存期称为输入生存期,返回值的生存期称为输出生存期。
当没有显式注释时,编译器使用三个规则来计算引用的生存期。第一个规则适用于输入生存期,第二个和第三个规则适用于输出生存期。如果编译器到达了三个规则的末尾,但仍然有一些引用无法确定它们的生存期,编译器将会出错停止。这些规则适用于fn定义以及impl块。
第一条规则是编译器给每个引用的参数分配一个生存期参数。换句话说,有一个参数的函数得到一个生存期参数:fn foo < ' a >(x:& ' a i32);有两个参数的函数得到两个独立的生存期参数:fn foo<'a,' b>(x: &'a i32,y:& ' b i32);诸如此类。
第二个规则是,如果恰好有一个输入寿命参数,则该寿命被分配给所有输出寿命参数:fn foo<'a>(x: &'a i32) -> &'a i32。
第三个规则是,如果有多个输入生存期参数,但其中一个是&self或&mut self,因为这是一个方法,那么self的生存期被分配给所有输出生存期参数。第三条规则使得方法更易于读写,因为需要的符号更少。
假设我们是编译器。我们将应用这些规则来计算示例25中first_word函数签名中引用的生存期。签名开始时没有任何与引用相关联的生存期:
fn first_word(s: &str) -> &str {
然后编译器应用第一条规则,规定每个参数都有自己的生存期。我们像往常一样称之为‘a,所以现在的签名是这样的:
fn first_word<'a>(s: &'a str) -> &str {
第二个规则适用,因为只有一个输入生命周期。第二个规则指定将一个输入参数的生存期分配给输出生存期,因此签名现在是这样的:
fn first_word<'a>(s: &'a str) -> &'a str {
现在,这个函数签名中的所有引用都有生存期,编译器可以继续分析,而不需要程序员在这个函数签名中注释生存期。
让我们看另一个例子,这一次使用longest函数,当我们在示例20中开始使用它时,它没有生存期参数:
fn longest(x: &str, y: &str) -> &str {
让我们应用第一条规则:每个参数都有自己的生存期。这次我们有两个参数,而不是一个,所以我们有两个生存期:
fn longest<'a, 'b>(x: &'a str, y: &'b str) -> &str {
您可以看到第二条规则不适用,因为有多个输入生存期。第三条规则也不适用,因为longest是函数而不是方法,所以没有一个参数是self。在研究了所有三个规则之后,我们仍然没有弄清楚返回类型的生存期是多少。这就是为什么我们在试图编译示例20中的代码时出现错误:编译器通过了生存期省略规则,但仍然无法计算出签名中引用的所有生存期。
因为第三条规则实际上只适用于方法签名,我们将在接下来的上下文中查看生存期,以了解为什么第三条规则意味着我们不必经常在方法签名中注释生存期。
方法定义中的生存期注释
当我们在具有生存期的结构上实现方法时,我们使用与示例11所示的泛型类型参数相同的语法。我们在哪里声明和使用生存期参数取决于它们是与结构字段相关还是与方法参数和返回值相关。
结构字段的生存期名称总是需要在impl关键字之后声明,然后在结构名称之后使用,因为这些生存期是结构类型的一部分。
在impl块内部的方法签名中,引用可能与结构的字段中的引用的生存期相关联,或者它们可能是独立的。此外,生存期省略规则通常使得方法签名中不需要生存期注释。让我们看一些例子,使用我们在示例24中定义的名为ImportantExcerpt的结构。
首先,我们将使用一个名为level的方法,其唯一的参数是对self的引用,其返回值是i32,而不是对任何东西的引用:
impl<'a> ImportantExcerpt<'a> {fn level(&self) -> i32 {3}
}
impl后的生存期参数声明及其在类型名后的使用是必需的,但由于第一个省略规则,我们不需要注释self引用的生存期。
下面是一个应用第三生命周期省略规则的示例:
impl<'a> ImportantExcerpt<'a> {fn announce_and_return_part(&self, announcement: &str) -> &str {println!("Attention please: {}", announcement);self.part}
}
有两个输入生命周期,所以Rust应用第一个生命周期省略规则,给&self和announcement它们自己的生命周期。然后,因为参数之一是&self,返回类型获得&self的生存期,并且所有生存期都已被考虑。
静态生存期
我们需要讨论的一个特殊的生命周期是'static,这意味着受影响的引用可以存在于整个程序期间。所有字符串文字都有'static生存期,我们可以对其进行如下注释:
let s: &'static str = "I have a static lifetime.";
该字符串的文本直接存储在程序的二进制文件中,该文件总是可用的。因此,所有字符串文字的生存期都是'static。
您可能会在错误消息中看到使用“'static生存期的建议。但是在指定'static作为引用的生存期之前,请考虑您拥有的引用是否实际上存在于您的程序的整个生存期中,以及您是否希望它存在。大多数情况下,提示'static生存期的错误信息是由于试图创建悬空引用或可用生存期不匹配而导致的。在这种情况下,解决方案是修复这些问题,而不是指定'static生存期。
泛型类型参数、特征界限和生存期
让我们简单地看一下在一个函数中指定泛型类型参数、特征界限和生存期的语法!
use std::fmt::Display;fn longest_with_an_announcement<'a, T>(x: &'a str,y: &'a str,ann: T,
) -> &'a str
whereT: Display,
{println!("Announcement! {}", ann);if x.len() > y.len() {x} else {y}
}
这是示例21中longest的函数,返回两个字符串片段中较长的一个。但是现在它有了一个名为ann的泛型类型T的额外参数,该参数可以由实现where子句指定的Display特征的任何类型填充。这个额外的参数将使用{}打印,这就是为什么Display特征界限是必要的。因为生存期是泛型的一种类型,所以生存期参数'a和泛型类型参数T的声明放在函数名后面的尖括号内的同一列表中。
总结
这一章我们讲了很多!现在,您已经了解了泛型类型参数、特征和特征界限,以及泛型生存期参数,您已经准备好编写在许多不同情况下都可以工作的代码了。泛型类型参数允许您将代码应用于不同的类型。特征和特征界限确保了即使类型是泛型的,它们也会有代码需要的行为。您了解了如何使用生存期注释来确保这个灵活的代码不会有任何悬空引用。所有这些分析都发生在编译时,不会影响运行时性能!
信不信由你,关于我们在本章讨论的主题,还有很多东西要学:第17章讨论了trait对象,这是使用trait的另一种方式。还有一些更复杂的场景涉及生存期注释,您只需要在非常高级的场景中使用它们;对于这些,你应该阅读生锈的参考。但是接下来,您将学习如何在Rust中编写测试,这样您就可以确保您的代码按照它应该的方式运行。
本章重点
- 验证生存期引用
- 防止悬空引用
- 借用检查器的概念
- 函数中泛型生存期
- 掌握生存期的注释语法
- 函数签名生存期注释
- 结构定义生存期注释
- 方法定义中的生存期注释
- 静态生存期
- 泛型类型参数,trait界限及生存期
相关文章:

Rust之泛型、特性和生命期(四):验证有生存期的引用
开发环境 Windows 10Rust 1.71.0 VS Code 1.80.1 项目工程 这里继续沿用上次工程rust-demo 验证具有生存期的引用 生存期是我们已经在使用的另一种泛型。生存期不是确保一个类型具有我们想要的行为,而是确保引用在我们需要时有效。 我们在第4章“引用和借用”一…...

kubesphere安装中间件
kubesphere安装mysql 创建configMap [client] default-character-setutf8mb4[mysql] default-character-setutf8mb4[mysqld] init_connectSET collation_connection utf8mb4_unicode_ci init_connectSET NAMES utf8mb4 character-set-serverutf8mb4 collation-serverutf8mb4_…...
zookeeper学习(二) 集群模式安装
前置环境 三台centos7服务器 192.168.2.201 192.168.2.202 192.168.2.150三台服务器都需要安装jdk1.8以上zookeeper安装包 安装jdk 在单机模式已经描述过,这里略过,有需要可以去看单机模式中的这部分,注意的是三台服务器都需要安装 安装…...

选择合适的图表,高效展现数据魅力
随着大数据时代的来临,数据的重要性愈发凸显,数据分析和可视化成为了决策和传递信息的重要手段。在数据可视化中,选择合适的图表是至关重要的一环,它能让数据更加生动、直观地呈现,为观众提供更有说服力的信息。本文将…...

springboot自动装配
SPI spi : service provider interface : 是java的一种服务提供机制,spi 允许开发者在不修改代码的情况下,为某个接口提供实现类,来扩展应用程序 将实现类独立到配置文件中,通过配置文件控制导入ÿ…...
python小记-队列
队列(Queue)是一种常见的数据结构,它遵循先进先出(First-In-First-Out,FIFO)的原则。在队列中,新元素(也称为项)总是添加到队列的末尾,而最早添加的元素总是在…...

SpringBoot——持久化技术
简单介绍 在之前我们使用的数据层持久化技术使用的是MyBatis或者是MyBatis-plus,其实都是一样的。在使用之前,我们要导入对应的坐标,然后配置MyBatis特有的配置,比如说Mapper接口,或者XML配置文件,那么除了…...
Kafka 入门到起飞 - 生产者参数详解 ,什么是生产者确认机制? 什么是ISR? 什么是 OSR?
上回书我们讲了,生产者发送消息流程解析传送门 那么这篇我们来看下,生产者发送消息时几个重要的参数详解 ,什么是生产者确认机制? 什么是ISR? 什么是 OSR? 参数: bootstrap.servers : Kafka 集…...

【文献分享】比目前最先进的模型轻30%!高效多机器人SLAM蒸馏描述符!
论文题目:Descriptor Distillation for Efficient Multi-Robot SLAM 中文题目:高效多机器人SLAM蒸馏描述符 作者:Xiyue Guo, Junjie Hu, Hujun Bao and Guofeng Zhang 作者机构:浙江大学CAD&CG国家重点实验室 香港中文大学…...

【数据动态填充到element表格;将带有标签的数据展示为文本格式】
一:数据动态填充到element表格; 二:将带有标签的数据展示为文本格式; 1、 <el-row><el-col :span"24"><el-tabs type"border-card"><el-tab-pane label"返回值"><el-…...

小程序轮播图的两种后台方式(PHP)--【浅入深出系列008】
微信目录集链接在此: 详细解析黑马微信小程序视频–【思维导图知识范围】难度★✰✰✰✰ 不会导入/打开小程序的看这里:参考 让别人的小程序长成自己的样子-更换window上下颜色–【浅入深出系列001】 文章目录 本系列校训学习资源的选择啥是轮播图轮播…...

使用ComPDFKit PDF SDK 构建iOS PDF阅读器
在当今以移动为先的世界中,为企业和开发人员创建一个iOS应用程序是必不可少的。随着对PDF文档处理需求的增加,使用ComPDFKit这个强大的PDF软件开发工具包(SDK)来构建iOS PDF阅读器和编辑器可以让最终用户轻松查看和编辑PDF文档。 …...

一套流程6个步骤,教你如何正确采购询价
采购询价(RFQ)是一种竞争性投标文件,用于邀请供应商或承包商就标准化或重复生产的产品或服务提交报价。 询价通常用于大批量/低价值项目,买方必须提供技术规格和商业要求,该文件有时也称为招标书或投标邀请书。询价流…...
git使用
常用命令 git init git库初始化,初始化后会在文件中出现一个.git的隐藏文件 git clone 从远程克隆仓库 git pull 从远程库中拉取 git commit 将暂存提交到本地仓库 git push 提交本地仓库到远程 git branch 查看当前分支 git branch <branchName> 切换分支 …...

SkyWalking链路追踪-搭建-spring-boot-cloud-单机环境 之《10 分钟快速搭建 SkyWalking 服务》
首先了解一下单机环境 第一步,搭建一个 Elasticsearch 服务。第二步,下载 SkyWalking 软件包。第三步,搭建一个 SkyWalking OAP 服务。第四步,启动一个 Spring Boot 应用,并配置 SkyWalking Agent。第五步,…...

Rabbit MQ整合springBoot
一、pom依赖二、消费端2.1、application.properties 配置文件2.2、消费端核心组件 三、生产端3.1、application.properties 配置文件2.2、生产者 MQ消息发送组件四、测试1、生产端控制台2、消费端控制台 一、pom依赖 <dependency><groupId>org.springframework.boo…...
Golang 中的 time 包详解(一):time.Time
在日常开发过程中,会频繁遇到对时间进行操作的场景,使用 Golang 中的 time 包可以很方便地实现对时间的相关操作。接下来的几篇文章会详细讲解 time 包,本文先讲解一下 time 包中的结构体 time.Time。 time.Time time.Time 类型用来表示一个…...

CMU 15-445 -- Database Recovery - 18
CMU 15-445 -- Database Recovery - 18 引言ARIESLog Sequence NumbersNormal ExecutionTransaction CommitTransaction AbortCompensation Log Records Non-fuzzy & fuzzy CheckpointsSlightly Better CheckpointsFuzzy Checkpoints ARIES - Recovery PhasesAnalysis Phas…...

HTTP Header定制,客户端使用Request,服务器端使用Response
在服务器端通过request.getHeaders()是无效的,只能使用response.getHeaders()。 Overridepublic Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType mediaType,Class selectedConverterType, ServerHttpRequest request, ServerHttpRespo…...
Vue 3编写的父子组件示例,包括传递数据和调用父组件方法
下面是一个使用Vue 3编写的父子组件示例,包括传递数据和调用父组件方法: ChildComponent.vue: <template><div><p>Child Component</p><p>Message: {{ message }}</p><button click"updateMes…...

Debian系统简介
目录 Debian系统介绍 Debian版本介绍 Debian软件源介绍 软件包管理工具dpkg dpkg核心指令详解 安装软件包 卸载软件包 查询软件包状态 验证软件包完整性 手动处理依赖关系 dpkg vs apt Debian系统介绍 Debian 和 Ubuntu 都是基于 Debian内核 的 Linux 发行版ÿ…...

uniapp微信小程序视频实时流+pc端预览方案
方案类型技术实现是否免费优点缺点适用场景延迟范围开发复杂度WebSocket图片帧定时拍照Base64传输✅ 完全免费无需服务器 纯前端实现高延迟高流量 帧率极低个人demo测试 超低频监控500ms-2s⭐⭐RTMP推流TRTC/即构SDK推流❌ 付费方案 (部分有免费额度&#x…...

12.找到字符串中所有字母异位词
🧠 题目解析 题目描述: 给定两个字符串 s 和 p,找出 s 中所有 p 的字母异位词的起始索引。 返回的答案以数组形式表示。 字母异位词定义: 若两个字符串包含的字符种类和出现次数完全相同,顺序无所谓,则互为…...
爬虫基础学习day2
# 爬虫设计领域 工商:企查查、天眼查短视频:抖音、快手、西瓜 ---> 飞瓜电商:京东、淘宝、聚美优品、亚马逊 ---> 分析店铺经营决策标题、排名航空:抓取所有航空公司价格 ---> 去哪儿自媒体:采集自媒体数据进…...

以光量子为例,详解量子获取方式
光量子技术获取量子比特可在室温下进行。该方式有望通过与名为硅光子学(silicon photonics)的光波导(optical waveguide)芯片制造技术和光纤等光通信技术相结合来实现量子计算机。量子力学中,光既是波又是粒子。光子本…...
【Go语言基础【13】】函数、闭包、方法
文章目录 零、概述一、函数基础1、函数基础概念2、参数传递机制3、返回值特性3.1. 多返回值3.2. 命名返回值3.3. 错误处理 二、函数类型与高阶函数1. 函数类型定义2. 高阶函数(函数作为参数、返回值) 三、匿名函数与闭包1. 匿名函数(Lambda函…...

【JVM面试篇】高频八股汇总——类加载和类加载器
目录 1. 讲一下类加载过程? 2. Java创建对象的过程? 3. 对象的生命周期? 4. 类加载器有哪些? 5. 双亲委派模型的作用(好处)? 6. 讲一下类的加载和双亲委派原则? 7. 双亲委派模…...

WPF八大法则:告别模态窗口卡顿
⚙️ 核心问题:阻塞式模态窗口的缺陷 原始代码中ShowDialog()会阻塞UI线程,导致后续逻辑无法执行: var result modalWindow.ShowDialog(); // 线程阻塞 ProcessResult(result); // 必须等待窗口关闭根本问题:…...
Java 与 MySQL 性能优化:MySQL 慢 SQL 诊断与分析方法详解
文章目录 一、开启慢查询日志,定位耗时SQL1.1 查看慢查询日志是否开启1.2 临时开启慢查询日志1.3 永久开启慢查询日志1.4 分析慢查询日志 二、使用EXPLAIN分析SQL执行计划2.1 EXPLAIN的基本使用2.2 EXPLAIN分析案例2.3 根据EXPLAIN结果优化SQL 三、使用SHOW PROFILE…...
PostgreSQL 与 SQL 基础:为 Fast API 打下数据基础
在构建任何动态、数据驱动的Web API时,一个稳定高效的数据存储方案是不可或缺的。对于使用Python FastAPI的开发者来说,深入理解关系型数据库的工作原理、掌握SQL这门与数据库“对话”的语言,以及学会如何在Python中操作数据库,是…...