Rust之泛型、特性和生命期(四):验证有生存期的引用

这篇具有很好参考价值的文章主要介绍了Rust之泛型、特性和生命期(四):验证有生存期的引用。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

开发环境

 

  • Windows 10
  • Rust 1.71.0

 Rust之泛型、特性和生命期(四):验证有生存期的引用,Rust,rust,开发语言,后端

  • VS Code 1.80.1

Rust之泛型、特性和生命期(四):验证有生存期的引用,Rust,rust,开发语言,后端

项目工程

这里继续沿用上次工程rust-demo
Rust之泛型、特性和生命期(四):验证有生存期的引用,Rust,rust,开发语言,后端

验证具有生存期的引用

生存期是我们已经在使用的另一种泛型。生存期不是确保一个类型具有我们想要的行为,而是确保引用在我们需要时有效。

我们在第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引用的值在我们试图使用它之前已经超出了范围。以下是错误消息: 

Rust之泛型、特性和生命期(四):验证有生存期的引用,Rust,rust,开发语言,后端

变量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’rx的生命期的注释

这里,我们用“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之泛型、特性和生命期(四):验证有生存期的引用,Rust,rust,开发语言,后端

 帮助文本显示,返回类型需要一个通用的生存期参数,因为Rust无法判断返回的引用是指x还是y。实际上,我们也不知道,因为该函数体中的if块返回对x的引用,else块返回对y的引用!

当我们定义这个函数时,我们不知道将传递给这个函数的具体值,所以我们不知道是if情况还是else情况会执行。我们也不知道将要传入的引用的具体生存期,所以我们不能像在示例17和示例18中那样查看作用域来确定我们返回的引用是否总是有效。借用检查器也不能确定这一点,因为它不知道xy的生命周期如何与返回值的生命周期相关。要修复此错误,我们将添加定义引用之间关系的通用生存期参数,以便借项检查器可以执行其分析。 

生存期注释语法

 生存期注释不会改变任何引用的生存期。相反,它们描述了多个引用的生存期之间的关系,而不会影响生存期。正如当签名指定泛型类型参数时,函数可以接受任何类型一样,通过指定泛型生存期参数,函数可以接受任何生存期的引用。

生存期注释有一个稍微不同寻常的语法:生存期参数的名称必须以撇号(')开头,并且通常都是小写的,非常短,就像泛型类型一样。大多数人在第一个生命周期注释中使用名称'a。我们将生存期参数注释放在引用的&之后,使用空格将注释与引用的类型分开。

下面是一些例子:对没有寿命参数的i32的引用,对具有名为'a的寿命参数的i32的引用,以及对也具有寿命'ai32的可变引用。

&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之泛型、特性和生命期(四):验证有生存期的引用,Rust,rust,开发语言,后端

函数签名现在告诉Rust,对于某个生存期'a,函数采用两个参数,这两个参数都是至少与生存期'a一样长的字符串片段。函数签名还告诉Rust,从函数返回的字符串片段将至少与生存期'a一样长。实际上,这意味着由longest函数返回的引用的生存期与由函数参数引用的值的较小生存期相同。我们希望Rust在分析这段代码时使用这些关系。 

请记住,当我们在这个函数签名中指定生存期参数时,我们不会改变任何传入或返回的值的生存期。相反,我们指定借用检查器应该拒绝任何不符合这些约束的值。请注意,longest函数不需要确切知道xy将存在多长时间,只需要用某个范围来代替满足该签名的'a

在函数中标注生存期时,标注放在函数签名中,而不是函数体中。生存期注释成为函数契约的一部分,很像签名中的类型。让函数签名包含生命周期契约意味着Rust编译器所做的分析可以更简单。如果函数的注释方式或调用方式有问题,编译器错误可以更准确地指向我们的代码部分和约束。相反,如果Rust编译器对我们想要的生存期关系做了更多的推断,编译器可能只能指出我们代码的一个用法,而不是问题的原因。

当我们将具体的引用传递给longest时,替换'a的具体生存期是x的范围与y的范围重叠的部分,换句话说,泛型生存期'a将获得等于xy的较小生存期的具体生存期,因为我们已经用相同的生存期参数'a对返回的引用进行了注释,所以返回的引用对于xy的较小生存期的长度也是有效的。 

让我们看看生存期注释如何通过传入具有不同具体生存期的引用来限制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函数

Rust之泛型、特性和生命期(四):验证有生存期的引用,Rust,rust,开发语言,后端

在这个例子中,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

Rust之泛型、特性和生命期(四):验证有生存期的引用,Rust,rust,开发语言,后端

该错误显示,要使resultprintln!有效!语句中,string2需要在外部范围结束之前一直有效。Rust知道这一点,因为我们使用相同的生存期参数'a来注释函数参数和返回值的生存期。 

 作为人类,我们可以查看这段代码,发现string1string2长,因此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这个实现也将无法编译,因为返回值的生存期与参数的生存期完全无关。下面是我们得到的错误消息:

Rust之泛型、特性和生命期(四):验证有生存期的引用,Rust,rust,开发语言,后端

 问题是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应用第一个生命周期省略规则,给&selfannouncement它们自己的生命周期。然后,因为参数之一是&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
where
    T: 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中编写测试,这样您就可以确保您的代码按照它应该的方式运行。文章来源地址https://www.toymoban.com/news/detail-601116.html

本章重点

  • 验证生存期引用
  • 防止悬空引用
  • 借用检查器的概念
  • 函数中泛型生存期
  • 掌握生存期的注释语法
  • 函数签名生存期注释
  • 结构定义生存期注释
  • 方法定义中的生存期注释
  • 静态生存期
  • 泛型类型参数,trait界限及生存期

到了这里,关于Rust之泛型、特性和生命期(四):验证有生存期的引用的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • Go 泛型之泛型约束

    目录 Go 泛型之泛型约束 一、引入 二、最宽松的约束:any 三、支持比较操作的内置约束:comparable 四、自定义约束 五、类型集合(type set) 六、简化版的约束形式 七、约束的类型推断 八、小结 虽然泛型是开发人员表达“通用代码”的一种重要方式,但这并不意味着所有泛型

    2024年02月04日
    浏览(38)
  • Java之泛型

    泛型: 泛型实现了参数化类型的概念,使代码可以应用于多种类型。 泛型的目的是希望类或方法能够具有最广泛的表达能力。 Java的泛型的主要目的之一就是用来指定容器要持有什么类型的对象,而且由编译器来保证类型的正确性。 泛型类: 类型参数:用尖括号括住,实际

    2024年02月11日
    浏览(35)
  • TypeScript 进阶之泛型

    避免代码重复和创建可重用类型是编写干净代码的重要部分。 将所有类型属性都设置为可选 Required 与 Partial 相反。它构造一个类型,其中需要该类型的所有属性。它可用于确保没有可选属性出现在类型中。 多属性的对象中摘取某些属性。 键可以是字符串文字或字符串文字的

    2024年01月23日
    浏览(39)
  • java入坑之泛型

    Java泛型是JDK 5中引入的一个新特性,它提供了编译时类型安全检测机制,该机制允许程序员在编译时检测到非法的类型 泛型的本质是参数化类型,也就是说所操作的数据类型被指定为一个参数。这意味着你可以使用一套代码来处理多种不同类型的数据 ArrayListE,E表示元素El

    2024年02月10日
    浏览(34)
  • C++之泛型算法

    大多数算法在头文件algorithm中,在头文件numeric中定义了一组数值泛型算法。理解算法最基本的方法就是了解他们是否读取元素,改变元素,或是重排元素顺序 使用STL算法经常需要使用函数对象和函数适配器,所以需要#include 所有算法都被设计用来处理一个或多个iterator区间,

    2024年02月03日
    浏览(33)
  • 用可视化案例讲Rust编程4. 用泛型和特性实现自适配shapefile的读取

    本节已经涉及Rust学习曲线上的一个大坑:泛型和特性了,属于语言的深水区,如果初学者,建议看一眼知道有这个功能即可。 如果我们立足于功能实现,那么做到像上一节那样就可以了,从原理上来说,每个函数满足唯一的功能,是一种好的设计,软件工程里面“高内聚低

    2024年01月25日
    浏览(36)
  • 选择排序算法之泛型优化

    工作原理: 每一次从待排序的数据元素中选中最小的一个元素,然后,再从剩余未排序元素中继续寻找最小元素,将2个元素交换位置,就达到了已排序的元素一直是从小到大了。 这个算法的时间复杂度为O(n²),空间复杂度为O(1)。 当然,为了匹配多种类型的对象,可以使用

    2024年02月06日
    浏览(41)
  • C#(六十二)之泛型的约束

    类型约束 基类约束有两个重要的目的。 1:它允许在泛型类中使用有约束指定的基类成员。 2:确保只能使用支持指定基类或派生类的类型实例。 约束是使用 where 上下文指定的。 下表列出了五种类型的约束: 约束 说明 T:struct 类型参数必须是值类型。可以指定除

    2024年02月17日
    浏览(41)
  • 从零开始学习 Java:简单易懂的入门指南之泛型及set集合(二十二)

    1.1泛型概述 泛型的介绍 ​ 泛型是JDK5中引入的特性,它提供了编译时类型安全检测机制 泛型的好处 把运行时期的问题提前到了编译期间 避免了强制类型转换 泛型的定义格式 类型: 指定一种类型的格式.尖括号里面可以任意书写,一般只写一个字母.例如: 类型1,类型2…: 指定多

    2024年02月09日
    浏览(41)
  • 泛型、Trait 和生命周期(上)

    目录 1、提取函数来减少重复 2、在函数定义中使用泛型 3、结构体定义中的泛型  4、枚举定义中的泛型 5、方法定义中的泛型 6、泛型代码的性能 每一门编程语言都有高效处理重复概念的工具。在 Rust 中其工具之一就是  泛型 ( generics )。泛型是具体类型或其他属性的抽象

    2024年02月21日
    浏览(29)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包