【Rust】Rust学习 第十章泛型、trait 和生命周期

news2025/1/17 3:15:52

泛型是具体类型或其他属性的抽象替代。我们可以表达泛型的属性,比如他们的行为或如何与其他泛型相关联,而不需要在编写和编译代码时知道他们在这里实际上代表什么。

之后,我们讨论 trait,这是一个定义泛型行为的方法。trait 可以与泛型结合来将泛型限制为拥有特定行为的类型,而不是任意类型。

最后介绍 生命周期lifetimes),它是一类允许我们向编译器提供引用如何相互关联的泛型。Rust 的生命周期功能允许在很多场景下借用值的同时仍然使编译器能够检查这些引用的有效性。

提取函数来减少重复

在介绍泛型语法之前,首先来回顾一个不使用泛型的处理重复的技术:提取一个函数。当熟悉了这个技术以后,我们将使用相同的机制来提取一个泛型函数!如同你识别出可以提取到函数中重复代码那样,你也会开始识别出能够使用泛型的重复代码。

考虑一下这个寻找列表中最大值

fn main() {
    let num = vec![12, 34, 45, 32, 15, 89];
    let mut largest = num[0];

    for number in num {
        if number > largest {
            largest = number;
        }
    }

    println!("the largest number is {}", largest);
}

如果需要在两个不同的列表中寻找最大值,我们可以重复上述代码

为了消除重复,我们可以创建一层抽象,在这个例子中将表现为一个获取任意整型列表作为参数并对其进行处理的函数。这将增加代码的简洁性并让我们将表达和推导寻找列表中最大值的这个概念与使用这个概念的特定位置相互独立。

fn find_largest(num: Vec<i32>) -> i32{
    let mut largest = num[0];

    for number in num {
        if number > largest {
            largest = number;
        }
    }
    largest
}


fn main() {
    let num = vec![12, 34, 45, 32, 15, 89];
    let data = find_largest(num);
    println!("the largest number is {}", data);

    let num = vec![120, 304, 405, 320, 105, 89];
    let data = find_largest(num);
    println!("the largest number is {}", data);
}

find_largest函数有一个参数 num,它代表会传递给函数的任何具体的 i32值的 slice。函数定义中的 num代表任何 &[i32]。当调用 find_largest函数时,其代码实际上运行于我们传递的特定值上。

总的来说,经历了如下几步:

  1. 找出重复代码。
  2. 将重复代码提取到了一个函数中,并在函数签名中指定了代码中的输入和返回值。
  3. 将重复代码的两个实例,改为调用函数。

在不同的场景使用不同的方式,我们也可以利用相同的步骤和泛型来减少重复代码。

10.1 泛型数据类型

在函数定义中使用泛型

当使用泛型定义函数时,我们在函数签名中通常为参数和返回值指定数据类型的位置放置泛型。以这种方式编写的代码将更灵活并能向函数调用者提供更多功能,同时不引入重复代码。

回到 largest 函数上,示例 中展示了两个提供了相同的寻找 slice 中最大值功能的函数。

fn largest_i32(list: &[i32]) -> i32 {
    let mut largest = list[0];

    for &item in list.iter() {
        if item > largest {
            largest = item;
        }
    }

    largest
}

fn largest_char(list: &[char]) -> char {
    let mut largest = list[0];

    for &item in list.iter() {
        if item > largest {
            largest = item;
        }
    }

    largest
}

fn main() {
    let number_list = vec![34, 50, 25, 100, 65];

    let result = largest_i32(&number_list);
    println!("The largest number is {}", result);
   assert_eq!(result, 100);

    let char_list = vec!['y', 'm', 'a', 'q'];

    let result = largest_char(&char_list);
    println!("The largest char is {}", result);
   assert_eq!(result, 'y');
}

这两个函数有着相同的代码,所以让我们在一个单独的函数中引入泛型参数来消除重复。

当需要在函数体中使用一个参数时,必须在函数签名中声明这个参数以便编译器能知道函数体中这个名称的意义。同理,当在函数签名中使用一个类型参数时,必须在使用它之前就声明它。为了定义泛型版本的 largest 函数,类型参数声明位于函数名称与参数列表中间的尖括号 <> 中,像这样:

fn largest<T>(list: &[T]) -> T {

这可以理解为:函数 largest 有泛型类型 T。它有一个参数 list,它的类型是一个 T 值的 slice。largest 函数将会返回一个与 T 相同类型的值。

// 注意这里的函数签名(形参、实参)
fn largest<T>(list: &[T]) -> T {
    let mut largest = list[0];

    for &item in list.iter() {
        if item > largest {
            largest = item;
        }
    }

    largest
}


fn main() {
    let number_list = vec![34, 50, 25, 100, 65];

    let result = largest(&number_list);
    println!("The largest number is {}", result);
   assert_eq!(result, 100);

    let char_list = vec!['y', 'm', 'a', 'q'];

    let result = largest(&char_list);
    println!("The largest char is {}", result);
   assert_eq!(result, 'y');
}

如果现在就尝试编译这些代码,会出现如下错误:

error[E0369]: binary operation `>` cannot be applied to type `T`
 --> src/main.rs:5:12
  |
5 |         if item > largest {
  |            ^^^^^^^^^^^^^^
  |
  = note: an implementation of `std::cmp::PartialOrd` might be missing for `T` 

注释中提到了 std::cmp::PartialOrd,这是一个 trait。下一部分会讲到 trait。不过简单来说,这个错误表明 largest 的函数体不能适用于 T 的所有可能的类型。因为在函数体需要比较 T 类型的值,不过它只能用于我们知道如何排序的类型。为了开启比较功能,标准库中定义的 std::cmp::PartialOrd trait 可以实现类型的比较功能。

结构体定义中的泛型

同样也可以使用 <> 语法来定义拥有一个或多个泛型参数类型字段的结构体。

#[derive(Debug)]
struct Point<T> {
    x: T,
    y: T,
}

fn main() {
    let point1 = Point{x: 4, y: 6};
    let point2 = Point{x: 'a', y: 'b'};

    println!("{:#?}", point1);
    println!("{:#?}", point2);
}

字段 x 和 y 必须是相同类型,因为他们都有相同的泛型类型 T

在这个例子中,当把整型值 5 赋值给 x 时,就告诉了编译器这个 Point<T> 实例中的泛型 T 是整型的。接着指定 y 为 4.0,它被定义为与 x 相同类型,就会得到一个像这样的类型不匹配错误:

如果想要定义一个 x 和 y 可以有不同类型且仍然是泛型的 Point 结构体,我们可以使用多个泛型类型参数。

#[derive(Debug)]
struct Point<T, U> {
    x: T,
    y: U,
}

fn main() {
    let point1 = Point{x: 5, y: 4.0};
    let point2 = Point{x: 'a', y: 'b'};

    println!("{:#?}", point1);
    println!("{:#?}", point2);
}

枚举定义中的泛型

类似于结构体,枚举也可以在其成员中存放泛型数据类型。

enum Option<T> {
    Some(T),
    None,
}

现在这个定义看起来就更容易理解了。如你所见 Option<T> 是一个拥有泛型 T 的枚举,它有两个成员:Some,它存放了一个类型 T 的值,和不存在任何值的None。通过 Option<T> 枚举可以表达有一个可能的值的抽象概念,同时因为 Option<T> 是泛型的,无论这个可能的值是什么类型都可以使用这个抽象。

枚举也可以拥有多个泛型类型。

enum Result<T, E> {
    Ok(T),
    Err(E),
}

Result 枚举有两个泛型类型,T 和 EResult 有两个成员:Ok,它存放一个类型 T 的值,而 Err 则存放一个类型 E 的值。这个定义使得 Result 枚举能很方便的表达任何可能成功(返回 T 类型的值)也可能失败(返回 E 类型的值)的操作。

方法定义中的泛型

也可以在定义中使用泛型在结构体和枚举上实现方法

struct Point<T> {
    x: T,
    y: T,
}

impl<T> Point<T> {
    fn x(&self) -> &T {
        &self.x
    }
}

fn main() {
    let p = Point { x: 5, y: 10 };

    println!("p.x = {}", p.x());
}

在 Point<T> 结构体上实现方法 x,它返回 T 类型的字段 x 的引用

注意必须在 impl 后面声明 T,这样就可以在 Point<T> 上实现的方法中使用它了。在 impl 之后声明泛型 T ,这样 Rust 就知道 Point 的尖括号中的类型是泛型而不是具体类型。

结构体定义中的泛型类型参数并不总是与结构体方法签名中使用的泛型是同一类型。下例中在示例 中的结构体 Point<T, U> 上定义了一个方法 mixup。这个方法获取另一个 Point 作为参数,而它可能与调用 mixup 的 self 是不同的 Point 类型。这个方法用 self 的 Point 类型的 x 值(类型 T)和参数的 Point 类型的 y 值(类型 W)来创建一个新 Point 类型的实例:

struct Point<T, U> {
    x: T,
    y: U,
}

impl<T, U> Point<T, U> {
    fn mixup<V, W>(self, other: Point<V, W>) -> Point<T, W> {
        Point {
            x: self.x,
            y: other.y,
        }
    }
}

fn main() {
    let p1 = Point { x: 5, y: 10.4 };
    let p2 = Point { x: "Hello", y: 'c'};

    let p3 = p1.mixup(p2);

    println!("p3.x = {}, p3.y = {}", p3.x, p3.y);
}

在 main 函数中,定义了一个有 i32 类型的 x(其值为 5)和 f64 的 y(其值为 10.4)的 Pointp2 则是一个有着字符串 slice 类型的 x(其值为 "Hello")和 char 类型的 y(其值为c)的 Point。在 p1 上以 p2 作为参数调用 mixup 会返回一个 p3,它会有一个 i32 类型的 x,因为 x 来自 p1,并拥有一个 char 类型的 y,因为 y 来自 p2println! 会打印出 p3.x = 5, p3.y = c

略复杂,但是万变不离其宗

泛型代码的性能

Rust 实现了泛型,使得使用泛型类型参数的代码相比使用具体类型并没有任何速度上的损失。

Rust 通过在编译时进行泛型代码的 单态化(monomorphization)来保证效率。单态化是一个通过填充编译时使用的具体类型,将通用代码转换为特定代码的过程。

let integer = Some(5);
let float = Some(5.0);

当 Rust 编译这些代码的时候,它会进行单态化。编译器会读取传递给 Option<T> 的值并发现有两种 Option<T>:一个对应 i32 另一个对应 f64。为此,它会将泛型定义 Option<T> 展开为 Option_i32 和 Option_f64,接着将泛型定义替换为这两个具体的定义。

enum Option_i32 {
    Some(i32),
    None,
}

enum Option_f64 {
    Some(f64),
    None,
}

fn main() {
    let integer = Option_i32::Some(5);
    let float = Option_f64::Some(5.0);
}

10.2 trait:定义共享的行为

trait 告诉 Rust 编译器某个特定类型拥有可能与其他类型共享的功能。可以通过 trait 以一种抽象的方式定义共享的行为。可以使用 trait bounds 指定泛型是任何拥有特定行为的类型。

注意:trait 类似于其他语言中的常被称为 接口(interfaces)的功能,虽然有一些不同。

定义trait

一个类型的行为由其可供调用的方法构成。如果可以对不同类型调用相同的方法的话,这些类型就可以共享相同的行为了。trait 定义是一种将方法签名组合起来的方法,目的是定义一个实现某些目的所必需的行为的集合。

例如,这里有多个存放了不同类型和属性文本的结构体:结构体 NewsArticle 用于存放发生于世界各地的新闻故事,而结构体 Tweet 最多只能存放 280 个字符的内容,以及像是否转推或是否是对推友的回复这样的元数据。

我们想要创建一个多媒体聚合库用来显示可能储存在 NewsArticle 或 Tweet 实例中的数据的总结。每一个结构体都需要的行为是他们是能够被总结的,这样的话就可以调用实例的 summarize 方法来请求总结。下面展示了一个表现这个概念的 Summary trait 的定义:

pub trait Summary {
    fn summarize(&self) -> String;
} 

fn main() {

}

这里使用 trait 关键字来声明一个 trait,后面是 trait 的名字,在这个例子中是 Summary。在大括号中声明描述实现这个 trait 的类型所需要的行为的方法签名,在这个例子中是 fn summarize(&self) -> String

在方法签名后跟分号,而不是在大括号中提供其实现。接着每一个实现这个 trait 的类型都需要提供其自定义行为的方法体,编译器也会确保任何实现 Summary trait 的类型都拥有与这个签名的定义完全一致的 summarize 方法。

抽象类?

trait 体中可以有多个方法:一行一个方法签名且都以分号结尾。

为类型实现 trait

现在我们定义了 Summary trait,接着就可以在多媒体聚合库中需要拥有这个行为的类型上实现它了。

pub trait Summary {
    fn summarize(&self) -> String;
}


pub struct NewsArticle {
    pub headline: String,
    pub location: String,
    pub author: String,
    pub content: String,
}

// 实现这个triat
impl Summary for NewsArticle {
    fn summarize(&self) -> String {
        format!("{}, by {} ({})", self.headline, self.author, self.location)
    }
}

pub struct Tweet {
    pub username: String,
    pub content: String,
    pub reply: bool,
    pub retweet: bool,
}

// 实现这个triat
impl Summary for Tweet {
    fn summarize(&self) -> String {
        format!("{}: {}", self.username, self.content)
    }
}

fn main() {

}

在类型上实现 trait 类似于实现与 trait 无关的方法。区别在于 impl 关键字之后,我们提供需要实现 trait 的名称,接着是 for 和需要实现 trait 的类型的名称。在 impl 块中,使用 trait 定义中的方法签名,不过不再后跟分号,而是需要在大括号中编写函数体来为特定类型实现 trait 方法所拥有的行为。

调用方法

fn main() {
    let tweet = Tweet {
        username: String::from("horse_ebooks"),
        content: String::from("of course, as you probably already know, people"),
        reply: false,
        retweet: false,
    };
    
    println!("1 new tweet: {}", tweet.summarize());

}

结果

实现 trait 时需要注意的一个限制是,只有当 trait 或者要实现 trait 的类型位于 crate 的本地作用域时,才能为该类型实现 trait。但是不能为外部类型实现外部 trait。

默认实现

有时为 trait 中的某些或全部方法提供默认的行为,而不是在每个类型的每个实现中都定义自己的行为是很有用的。这样当为某个特定类型实现 trait 时,可以选择保留或重载每个方法的默认行为。

pub trait Summary {
    fn summarize(&self) -> String {
        String::from("(Read more...)")
    }
}

如果想要对 NewsArticle 实例使用这个默认实现,而不是定义一个自己的实现,则可以通过 impl Summary for NewsArticle {} 指定一个空的 impl 块。

虽然我们不再直接为 NewsArticle 定义 summarize 方法了,但是我们提供了一个默认实现并且指定 NewsArticle 实现 Summary trait。

默认实现允许调用相同 trait 中的其他方法,哪怕这些方法没有默认实现。如此,trait 可以提供很多有用的功能而只需要实现指定一小部分内容。例如,我们可以定义 Summary trait,使其具有一个需要实现的 summarize_author 方法,然后定义一个 summarize 方法,此方法的默认实现调用 summarize_author 方法:

pub trait Summary {
    fn summarize_author(&self) -> String;

    fn summarize(&self) -> String {
        format!("(Read more from {}...)", self.summarize_author())
    }
}

为了使用这个版本的 Summary,只需在实现 trait 时定义 summarize_author 即可:

impl Summary for Tweet {
    fn summarize_author(&self) -> String {
        format!("@{}", self.username)
    }
}

一旦定义了 summarize_author,我们就可以对 Tweet 结构体的实例调用 summarize 了,而 summary 的默认实现会调用我们提供的 summarize_author 定义。因为实现了 summarize_authorSummary trait 就提供了 summarize 方法的功能,且无需编写更多的代码。

完整代码

pub trait Summary {
    fn summarize_author(&self) -> String;

    fn summarize(&self) -> String {
        format!("(Read more from {}...)", self.summarize_author())
    }
}


pub struct Tweet {
    pub username: String,
    pub content: String,
    pub reply: bool,
    pub retweet: bool,
}

// 实现这个triat
impl Summary for Tweet {
    fn summarize_author(&self) -> String {
        format!("@{}", self.username)
    }
}
fn main() {
    let tweet = Tweet {
        username: String::from("horse_ebooks"),
        content: String::from("of course, as you probably already know, people"),
        reply: false,
        retweet: false,
    };
    
    println!("1 new tweet: {}", tweet.summarize());

}

结果

trait作为参数

知道了如何定义 trait 和在类型上实现这些 trait 之后,我们可以探索一下如何使用 trait 来接受多种不同类型的参数。

例如在示例中为 NewsArticle 和 Tweet 类型实现了 Summary trait。我们可以定义一个函数 notify 来调用其参数 item 上的 summarize 方法,该参数是实现了 Summary trait 的某种类型。为此可以使用 impl Trait 语法

// 注意形参的表达形式
pub fn notify(item: impl Summary) {
    println!("Breaking news! {}", item.summarize());
}

对于 item 参数,我们指定了 impl 关键字和 trait 名称,而不是具体的类型。该参数支持任何实现了指定 trait 的类型。在 notify 函数体中,可以调用任何来自 Summary trait 的方法,比如 summarize

Trait Bound语法

10.3 生命周期与引用有效性

Rust 中的每一个引用都有其 生命周期lifetime),也就是引用保持有效的作用域。大部分时候生命周期是隐含并可以推断的,正如大部分时候类型也是可以推断的一样。类似于当因为有多种可能类型的时候必须注明类型,也会出现引用的生命周期以一些不同方式相关联的情况,所以 Rust 需要我们使用泛型生命周期参数来注明他们的关系,这样就能确保运行时实际使用的引用绝对是有效的。

参考:泛型、trait 与生命周期 - Rust 程序设计语言 简体中文版 (bootcss.com)

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

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

相关文章

Springboot04--vue前端部分+element-ui

注意点&#xff1a; 这边v-model和value的区别&#xff1a;v-model是双向绑定的&#xff0c;value是单向绑定 li的key的问题 vue的组件化开发&#xff1a; 1. NPM&#xff08;类似maven&#xff0c;是管理前段代码的工具&#xff09; 安装完之后可以在cmd里面使用以下指令 2.…

带你认识储存以及数据库新技术演进

01经典案例 1.0 潜在问题 02存储&数据库简介 2.1 存储器层级架构 2.1 数据怎么从应用到存储介质 2.1 RAID技术 2.2 数据库 数据库分为 关系型数据库 和 非关系型数据库 2.2.2 非关系型 2.2.1 关系型 2.3 数据库 vs 经典存储-结构化数据管理 2.3.1 数据库 vs 经典存储-事务能…

c++ static

static 成员 声明为static的类成员称为类的静态成员&#xff0c;用static修饰的成员变量&#xff0c;称之为静态成员变量&#xff1b;用 static修饰的成员函数&#xff0c;称之为静态成员函数。静态成员变量一定要在类外进行初始化。 看看下面代码体会一下: //其他类 class …

​运行paddlehub报错,提示:UnicodeDecodeError: ‘gbk‘ codec can‘t decode byte…**​

我在windows11环境下运行paddlehub报错&#xff0c;提示&#xff1a;UnicodeDecodeError: ‘gbk‘ codec can‘t decode byte…** 参考篇文字的解决方案&#xff1a;window10下运行项目报错&#xff1a;UnicodeDecodeError: ‘gbk‘ codec can‘t decode byte...的解决办法_uni…

C语言——将一串字符进行倒序

//将一串字符进行倒序 #include<stdio.h> #define N 6 int main() {int a[N]{0,1,2,3,4,5};int i,t;printf("原数组数值&#xff1a; ");for(i0;i<N;i)printf("%d",a[i]);for(i0;i<N/2;i){ta[i];a[i]a[N-1-i];a[N-1-i]t;}printf("\n排序…

Xilinx DDR3学习总结——3、MIG exmaple仿真

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 Xilinx DDR3学习总结——3、MIG exmaple例程仿真 前言仿真 前言 前面我们直接把exmaple例程稍加修改就进行了抢先上板测试&#xff0c;证明了MIG模块工作时正常的&#xff0…

SWIG使用方法

安装 下载 swigwin软件包&#xff0c;解压到合适的位置&#xff0c;然后将路径添加到环境变量即可。 编写C代码 //vector.hpp class Vector { private:int x;int y; public:Vector(int,int);double abs();void display(); };//vector.cpp #include "vector.hpp" …

C语言 ——指针数组与数组指针

目录 一、二维数组 二、指针数组 &#xff08;1&#xff09;概念 &#xff08;2&#xff09;书写方式 &#xff08;3&#xff09;指针数组模拟二维数组 三、数组指针 &#xff08;1&#xff09;概念 &#xff08;2&#xff09;使用数组指针打印一维数组 &#xff08;3&a…

网络协议栈-基础知识

1、分层模型 1.1、OSI七层模型 1、OSI&#xff08;Open System Interconnection&#xff0c;开放系统互连&#xff09;七层网络模型称为开放式系统互联参考模型 &#xff0c;是一个逻辑上的定义&#xff0c;一个规范&#xff0c;它把网络从逻辑上分为了7层。 2、每一层都有相关…

【C语言】分支语句(选择结构)详解

✨个人主页&#xff1a; Anmia.&#x1f389;所属专栏&#xff1a; C Language &#x1f383;操作环境&#xff1a; Visual Studio 2019 版本 目录 什么是分支语句&#xff1f; if语句 if if - else单分支 if - else if - else ...多分支 if - if嵌套 switch语句 基本语…

HackRF One Block Diagram

HackRF One r1-r8 Block Diagram HackRF One r9 Block Diagram

FPGA 之 xilinx DDS IP相位控制字及频率控制字浅析

浅析相位环在Xilinx DDS中的理解 本文仅为个人理解之用; 相关仿真结果如下:

Qt5.9.4搭建安卓环境-Qt for Android

目录 需要安装以下内容&#xff1a;安装JDK设置环境变量安装剩余文件 使用新建文件 可能出现的问题第一种解决方法&#xff1a; 第二种解决方法 需要安装以下内容&#xff1a; 下载地址&#xff1a; https://www.qter.org/portal.php?modview&aid10 很多Qt开发会用到的环…

理解-面向对象

目录 对象&#xff1a; 举例&#xff1a; 封装: 好处: 继承: 多态&#xff1a; 类和对象之间的关系 对象&#xff1a; 把一个东西看成对象&#xff0c;我们就可以孤立的审查它的性质&#xff0c;行为&#xff0c;进而研究它和其他对象的关系。 对象是一个应用系统中用…

探索极限:利用整数或字符串操作找出翻转后的最大数字

本篇博客会讲解力扣“1323. 6 和 9 组成的最大数字”的解题思路&#xff0c;这是题目链接。 对于这道题目&#xff0c;我会讲解2种解题思路&#xff0c;分别是直接操作整数&#xff0c;和利用字符串操作。希望大家通过本题学习关于整数和字符串的技巧。 显然&#xff0c;这道题…

如何在 .NET Core WebApi 中处理 MultipartFormDataContent 中的文件

问题描述# 上图示例展示了用户通过 IOS 客户端发送请求时&#xff0c;对应后端接口接收到的 Request 内容。从请求内容的整体结果&#xff0c;我们可以看出这是一个 multipart/form-data 的数据格式&#xff0c;由于这种数据是由多个 multipart section 组成&#xff0c;所以我…

ELK常见部署架构以及出现的问题及解决方案

ELK常见部署架构以及出现的问题及解决方案 ELK 已经成为目前最流行的集中式日志解决方案&#xff0c;它主要是由Beats 、Logstash 、Elasticsearch 、 Kibana 等组件组成&#xff0c;来共同完成实时日志的收集&#xff0c;存储&#xff0c;展示等一站式的解决方案。本文将会介…

无情被裁的后续来了

关注、星标公众号&#xff0c;直达精彩内容 来源&#xff1a;技术让梦想更伟大作者&#xff1a;不生气 大家好&#xff0c;给大家分享一个案例&#xff0c;收集好证据&#xff0c;劳动仲裁吧 &#xff0c;裁员时候老板打感情牌&#xff0c;员工呢坚持付出就得回报按照程序来。…

Zabbix 6.0 监控

文章目录 一、Zabbix简介1&#xff09;zabbix 是什么&#xff1f;2&#xff09;zabbix 监控原理3&#xff09;Zabbix 6.0 新特性1、Zabbix server高可用防止硬件故障或计划维护期的停机2、Zabbix 6.0 LTS新增Kubernetes监控功能&#xff0c;可以在Kubernetes系统从多个维度采集…

【计算机视觉|生成对抗】用深度卷积生成对抗网络进行无监督表示学习(DCGAN)

本系列博文为深度学习/计算机视觉论文笔记&#xff0c;转载请注明出处 标题&#xff1a;Unsupervised Representation Learning with Deep Convolutional Generative Adversarial Networks 链接&#xff1a;[1511.06434] Unsupervised Representation Learning with Deep Conv…