【Rust基础①】基本类型、所有权与借用、复合类型

文章目录

  • 1 基本类型
    • 1.1 数值类型
      • 1.1.1 Rust 中的内置的整数类型:
      • 1.1.2 浮点类型
      • 1.1.3 数学运算
      • 1.1.4 位运算
      • 1.1.5 序列(Range)
    • 1.2 字符、布尔、单元类型
    • 1.3 语句和表达式
    • 1.4 函数
  • 2 所有权与借用
    • 2.1 栈(Stack)与堆(Heap)
    • 2.2 所有权原则
      • 2.2.1 转移所有权
      • 2.2.2 克隆(深拷贝)
      • 2.2.3 拷贝(浅拷贝)
      • 2.2.4 函数传值与返回
    • 2.3 引用与借用
      • 可变引用
      • 悬垂引用(Dangling References)
  • 3 复合类型
    • 3.1 字符串与切片
      • 3.1.1 切片(slice)
      • 3.1.2 字符串操作
        • 追加(push)
        • 插入(insert)
        • 替换(replace)
        • 删除(delete)
        • 连接(concatenate)
        • 遍历字符串
    • 3.2 元组
        • 用模式匹配解构元组
        • 用 `.` 来访问元组
    • 3.3 结构体
      • 3.3.1 结构体语法
        • 定义结构体
        • 创建结构体实例
        • 访问结构体字段
        • 简化结构体创建
      • 3.3.2 元组结构体(Tuple Struct)
      • 3.3.3 单元结构体(Unit-like Struct)
      • 3.3.4 使用 `#[derive(Debug)]` 来打印结构体的信息
    • 3.4 枚举
      • 3.4.1 枚举的语法
      • 3.4.2 Opention枚举处理空值
    • 3.5 数组
      • 3.5.1 创建数组
      • 3.5.2 数组越界访问
      • 3.5.3 数组切片

1 基本类型

示例, 在rust中,变量是默认不可变的。要使变量可变,要用mut修饰

// Rust 程序入口函数,跟其它语言一样,都是 main,该函数目前无返回值
fn main() {// 使用let来声明变量,进行绑定,a是不可变的// 此处没有指定a的类型,编译器会默认根据a的值为a推断类型:i32,有符号32位整数// 语句的末尾必须以分号结尾let a = 10;// 主动指定b的类型为i32let b: i32 = 20;// 这里有两点值得注意:// 1. 可以在数值中带上类型:30i32表示数值是30,类型是i32// 2. c是可变的,mut是mutable的缩写let mut c = 30i32;// 还能在数值和类型中间添加一个下划线,让可读性更好let d = 30_i32;// 跟其它语言一样,可以使用一个函数的返回值来作为另一个函数的参数let e = add(add(a, b), add(c, d));// println!是宏调用,看起来像是函数但是它返回的是宏定义的代码块// 该函数将指定的格式化字符串输出到标准输出中(控制台)// {}是占位符,在具体执行过程中,会把e的值代入进来println!("( a + b ) + ( c + d ) = {}", e);
}// 定义一个函数,输入两个i32类型的32位有符号整数,返回它们的和
fn add(i: i32, j: i32) -> i32 {// 返回相加值,这里可以省略returni + j
}

1.1 数值类型

Rust 每个值都有其确切的数据类型,总的来说可以分为两类:基本类型和复合类型。 基本类型意味着它们往往是一个最小化原子类型,无法解构为其它类型(一般意义上来说),由以下组成:

  • 数值类型: 有符号整数 (i8, i16, i32, i64, isize)、 无符号整数 (u8, u16, u32, u64, usize) 、浮点数 (f32, f64)、以及有理数、复数
  • 字符串:字符串字面量和字符串切片 &str
  • 布尔类型: true和false
  • 字符类型: 表示单个 Unicode 字符,存储为 4 个字节
  • 单元类型: 即 () ,其唯一的值也是 ()

1.1.1 Rust 中的内置的整数类型:

长度有符号类型无符号类型
8 位i8u8
16 位i16u16
32 位i32u32
64 位i64u64
128 位i128u128
视架构而定isizeusize

每个有符号类型规定的数字范围是 − ( 2 n − 1 ) -(2^{n-1}) (2n1)~ 2 n − 1 − 1 2^{n-1}-1 2n11,其中 n 是该定义形式的位长度。因此 i8 可存储数字范围是 -(27) ~ 27 - 1,即 -128 ~ 127。无符号类型可以存储的数字范围是 0 ~ 2n - 1,所以 u8 能够存储的数字为 0 ~ 28 - 1,即 0 ~ 255。

此外,isizeusize 类型取决于程序运行的计算机 CPU 类型: 若 CPU 是 32 位的,则这两个类型是 32 位的,同理,若 CPU 是 64 位,那么它们则是 64 位。

整形字面量可以用下表的形式书写:

数字字面量示例
十进制98_222
十六进制0xff
八进制0o77
二进制0b1111_0000
字节 (仅限于 u8)b’A’

1.1.2 浮点类型

浮点类型数字 是带有小数点的数字,在 Rust 中浮点类型数字也有两种基本类型: f32f64,分别为 32 位和 64 位大小。默认浮点类型是f64,在现代的 CPU 中它的速度与 f32 几乎相同,但精度更高。

当需要使用浮点数时,需遵守以下准则:

  • 避免在浮点数上测试相等性
  • 当结果在数学上可能存在未定义时,需要格外的小心

例如:

fn main() {let abc: (f32, f32, f32) = (0.1, 0.2, 0.3);let xyz: (f64, f64, f64) = (0.1, 0.2, 0.3);println!("abc (f32)");println!("   0.1 + 0.2: {:x}", (abc.0 + abc.1).to_bits());println!("         0.3: {:x}", (abc.2).to_bits());println!();println!("xyz (f64)");println!("   0.1 + 0.2: {:x}", (xyz.0 + xyz.1).to_bits());println!("         0.3: {:x}", (xyz.2).to_bits());println!();assert!(abc.0 + abc.1 == abc.2);assert!(xyz.0 + xyz.1 == xyz.2);
}

运行该程序,输出如下:

abc (f32)0.1 + 0.2: 3e99999a0.3: 3e99999axyz (f64)0.1 + 0.2: 3fd33333333333340.3: 3fd3333333333333thread 'main' panicked at 'assertion failed: xyz.0 + xyz.1 == xyz.2',
➥ch2-add-floats.rs.rs:14:5
note: run with `RUST_BACKTRACE=1` environment variable to display
➥a backtrace

对于数学上未定义的结果,例如对负数取平方根-42.1.sqrt(),会产生一个特殊的结果:Rust 的浮点数类型使用 NaN (not a number)来处理这些情况。可以使用 is_nan() 等方法,可以用来判断一个数值是否是 NaN

fn main() {let x = (-42.0_f32).sqrt();if x.is_nan() {println!("未定义的数学行为")}
}

1.1.3 数学运算

fn main() {// 编译器会进行自动推导,给予twenty i32的类型let twenty = 20;// 类型标注let twenty_one: i32 = 21;// 通过类型后缀的方式进行类型标注:22是i32类型let twenty_two = 22i32;// 只有同样类型,才能运算let addition = twenty + twenty_one + twenty_two;println!("{} + {} + {} = {}", twenty, twenty_one, twenty_two, addition);// 对于较长的数字,可以用_进行分割,提升可读性let one_million: i64 = 1_000_000;println!("{}", one_million.pow(2));// 定义一个f32数组,其中42.0会自动被推导为f32类型let forty_twos = [42.0,42f32,42.0_f32,];// 打印数组中第一个值,并控制小数位为2位println!("{:.2}", forty_twos[0]);
}

1.1.4 位运算

Rust的运算基本上和其他语言一样

运算符说明
& 位与相同位置均为1时则为1,否则为0
| 位或相同位置只要有1时则为1,否则为0
^ 异或相同位置不相同则为1,相同则为0
! 位非把位中的0和1相互取反,即0置为1,1置为0
<< 左移所有位向左移动指定位数,右位补0
>> 右移所有位向右移动指定位数,带符号移动(正数补0,负数补1)

1.1.5 序列(Range)

Rust 提供了一个非常简洁的方式,用来生成连续的数值,例如 1..5,生成从 1 到 4 的连续数字,不包含 5 ;1..=5,生成从 1 到 5 的连续数字,包含 5.

序列只允许用于数字或字符类型

1.2 字符、布尔、单元类型

  1. Rust 的字符不仅仅是 ASCII,所有的 Unicode 值都可以作为 Rust 字符,包括单个的中文、日文、韩文、emoji 表情符号等等,都是合法的字符类型。由于 Unicode 都是 4 个字节编码,因此字符类型也是占用 4 个字节:
fn main() {let x = '中';println!("字符'中'占用了{}字节的内存大小",std::mem::size_of_val(&x)); //4let your_character:char='8'; // What's your favorite character?// Try a letter, try a number, try a special character, try a character// from a different language than your own, try an emoji!if your_character.is_alphabetic() {println!("Alphabetical!");} else if your_character.is_numeric() {println!("Numerical!");} else {println!("Neither alphabetic nor numeric!");}
}
  1. Rust 中的布尔类型有两个可能的值:truefalse,布尔值占用内存的大小为 1 个字节

  2. 单元类型就是 ()。main 函数就返回这个单元类型 (),不能说 main 函数无返回值,因为没有返回值的函数在 Rust 中是有单独的定义的:发散函数( diverge function ),顾名思义,无法收敛的函数。
    例如常见的 println!() 的返回值也是单元类型 ()。再比如,可以用 () 作为 map 的值,表示我们不关注具体的值,只关注 key。 可以作为一个值用来占位,但是完全不占用任何内存。

1.3 语句和表达式

Rust 的函数体是由一系列语句组成,最后由一个表达式来返回值,例如:

fn add_with_extra(x: i32, y: i32) -> i32 {let x = x + 1; // 语句let y = y + 5; // 语句x + y // 表达式
}

语句会执行一些操作但是不会返回一个值,而表达式会在求值后返回一个值,因此在上述函数体的三行代码中,前两行是语句,最后一行是表达式。

对于 Rust 语言而言,这种基于语句(statement)和表达式(expression)的方式是非常重要的,你需要能明确的区分这两个概念。基于表达式是函数式语言的重要特征,表达式总要返回值。

调用一个函数是表达式,因为会返回一个值,调用宏也是表达式,用花括号包裹最终返回一个值的语句块也是表达式,总之,能返回值,它就是表达式:

fn main() {let y = {let x = 3;x + 1};println!("The value of y is: {}", y);
}

上面使用一个语句块表达式将值赋给 y 变量。表达式不能加分号!

表达式如果不返回任何值,会隐式地返回一个 () 。

fn main() {assert_eq!(ret_unit_type(), ())
}fn ret_unit_type() {let x = 1;// if 语句块也是一个表达式,因此可以用于赋值,也可以直接返回// 类似三元运算符,在Rust里我们可以这样写let y = if x % 2 == 1 {"odd"} else {"even"};// 或者写成一行let z = if x % 2 == 1 { "odd" } else { "even" };
}

if 是最基本的控制流语句,使用方法:

pub fn foo_if_fizz(fizzish: &str) -> &str {if fizzish == "fizz" {"foo"} else if fizzish=="fuzz" {"bar"}else {"baz"}
}

1.4 函数

函数要点:

  • 函数名和变量名使用蛇形命名法(snake case),例如 fn add_two() -> {}
  • 函数的位置可以随便放,Rust 不关心我们在哪里定义了函数,只要有定义即可
  • 每个函数参数都需要标注类型

Rust 是强类型语言,因此需要你为每一个函数参数都标识出它的具体类型。

特殊返回类型 - 单元类型 (),是一个零长度的元组。它没啥作用,但是可以用来表达一个函数没有返回值:

  • 函数没有返回值,那么返回一个()
  • 通过;结尾的表达式返回一个 ()
//显示返回()
fn clear(text: &mut String) -> () {*text = String::from("");
}

当用!作函数返回类型的时候,表示该函数永不返回( diverge function ),特别的,这种语法往往用做会导致程序崩溃的函数。

函数示例:

fn main() {let original_price = 51;println!("Your sale price is {}", sale_price(original_price));
}fn sale_price(price: i32) ->i32 {if is_even(price) {price - 10} else {price - 3}
}fn is_even(num: i32) -> bool {num % 2 == 0
}

2 所有权与借用

所有的程序都必须和计算机内存打交道,如何从内存中申请空间来存放程序的运行内容,如何在不需要的时候释放这些空间,成了重中之重,也是所有编程语言设计的难点之一。在计算机语言不断演变过程中,出现了三种流派:

  • 垃圾回收机制(GC),在程序运行时不断寻找不再使用的内存,典型代表:Java、Go
  • 手动管理内存的分配和释放, 在程序中,通过函数调用的方式来申请和释放内存,典型代表:C++
  • 通过所有权来管理内存,编译器在编译时会根据一系列规则进行检查

其中 Rust 选择了第三种,最妙的是,这种检查只发生在编译期,因此对于程序运行期,不会有任何性能上的损失。

2.1 栈(Stack)与堆(Heap)

堆和栈是编程语言最核心的数据结构,核心目标就是为程序在运行时提供可供使用的内存空间

栈按照顺序存储值并以相反顺序取出值,这也被称作后进先出

与栈不同,对于大小未知或者可能变化的数据,我们需要将它存储在堆上。当向堆上放入数据时,需要请求一定大小的内存空间。操作系统在堆的某处找到一块足够大的空位,把它标记为已使用,并返回一个表示该位置地址的指针, 该过程被称为在堆上分配内存,有时简称为 “分配”(allocating)。接着,该指针会被推入栈中,因为指针的大小是已知且固定的,在后续使用过程中,你将通过栈中的指针,来获取数据在堆上的实际内存位置,进而访问该数据。

因此,入栈比在堆上分配内存要快,因为入栈时操作系统无需分配新的空间,只需要将新数据放入栈顶即可;栈数据往往可以直接存储在 CPU 高速缓存中,而堆数据只能存储在内存中。访问堆上的数据比访问栈上的数据慢,因为必须先访问栈再通过栈上的指针来访问内存。

2.2 所有权原则

Rust中关于所有权的规则:

  • Rust 中每一个值都被一个变量所拥有,该变量被称为值的所有者
  • 一个值同时只能被一个变量所拥有,或者说一个值只能拥有一个所有者
  • 当所有者(变量)离开作用域范围时,这个值将被丢弃(drop)

2.2.1 转移所有权

String 类型是一个复杂类型,由存储在栈中的堆指针字符串长度字符串容量共同组成,其中堆指针是最重要的,它指向了真实存储字符串内容的堆内存,容量是堆内存分配空间的大小,长度是目前已经使用的大小。总之 String 类型指向了一个堆上的空间,这里存储着它的真实数据,例如:

let s1 = String::from("hello");
let s2 = s1;

注意,当变量离开作用域后,Rust 会自动调用 drop 函数并清理变量的堆内存。不过由于两个 String 变量指向了同一位置。这就有了一个问题:当 s1 和 s2 离开作用域,它们都会尝试释放相同的内存。这是一个叫做 二次释放(double free) 的错误,也是之前提到过的内存安全性 BUG 之一。两次释放(相同)内存会导致内存污染,它可能会导致潜在的安全漏洞。

Rust 这样解决该问题:当 s1 赋予 s2 后,Rust 认为 s1 不再有效,因此也无需在 s1 离开作用域后 drop 任何东西,这就是把所有权从 s1 转移给了 s2,s1 在被赋予 s2 后就马上失效了。拷贝指针、长度和容量而不拷贝数据听起来就像浅拷贝,但是又因为 Rust 同时使第一个变量 s1 无效了,因此这个操作被称为 移动(move),而不是浅拷贝。上面的例子可以解读为 s1 被移动到了 s2 中

在这里插入图片描述
Rust 永远也不会自动创建数据的 “深拷贝”。因此,任何自动的复制都不是深拷贝,可以被认为对运行时性能影响较小。

2.2.2 克隆(深拷贝)

如果我们确实需要深度复制 String 中堆上的数据,而不仅仅是栈上的数据,可以使用一个叫做 clone 的方法。但是对于执行较为频繁的代码,使用 clone 会极大的降低程序性能。

fn main() {
let s1 = String::from("hello");
let s2 = s1.clone();println!("s1 = {}, s2 = {}", s1, s2);
}

2.2.3 拷贝(浅拷贝)

Rust 有一个叫做 Copy 的特征,可以用在类似整型这样在栈中存储的类型。如果一个类型拥有 Copy 特征,一个旧的变量在被赋值给其他变量后仍然可用

那么什么类型是可 Copy 的呢?可以查看给定类型的文档来确认,不过作为一个通用的规则: 任何基本类型的组合可以 Copy ,不需要分配内存或某种形式资源的类型是可以 Copy 的。如下是一些 Copy 的类型:

  • 所有整数类型,比如 u32
  • 布尔类型,bool,它的值是 true 和 false。
  • 所有浮点数类型,比如 f64
  • 字符类型,char
  • 元组,当且仅当其包含的类型也都是 Copy 的时候。比如,(i32, i32) 是 Copy 的,但 (i32, String) 就不是。
  • 不可变引用 &T ,例如转移所有权中的最后一个例子,但是注意: 可变引用 &mut T 是不可以 Copy的

2.2.4 函数传值与返回

将值传递给函数,一样会发生 移动 或者 复制,就跟 let 语句一样,下面的代码展示了所有权、作用域的规则:

fn main() {let s1 = gives_ownership();         // gives_ownership 将返回值// 移给 s1let s2 = String::from("hello");     // s2 进入作用域let s3 = takes_and_gives_back(s2);  // s2 被移动到// takes_and_gives_back 中,// 它也将返回值移给 s3
} // 这里, s3 移出作用域并被丢弃。s2 也移出作用域,但已被移走,// 所以什么也不会发生。s1 移出作用域并被丢弃fn gives_ownership() -> String {             // gives_ownership 将返回值移动给// 调用它的函数let some_string = String::from("hello"); // some_string 进入作用域.some_string                              // 返回 some_string 并移出给调用的函数
}// takes_and_gives_back 将传入字符串并返回该值
fn takes_and_gives_back(a_string: String) -> String { // a_string 进入作用域a_string  // 返回 a_string 并移出给调用的函数
}

2.3 引用与借用

Rust 通过获取变量的引用,称之为借用(borrowing)。正如现实生活中,如果一个人拥有某样东西,你可以从他那里借来,当使用完毕后,也必须要物归原主。

fn main() {let x = 5;let y = &x;assert_eq!(5, x);//错误,不同类型的比较assert_eq!(5, *y);//使用 *y 来解出引用所指向的值(也就是解引用)。一旦解引用了 y,就可以访问 y 所指向的整型值并可以与 5 做比较。
}

可变引用

首先,声明 s 是可变类型,其次创建一个可变的引用 &mut s 和接受可变引用参数 some_string: &mut String 的函数。

fn main() {let mut s = String::from("hello");change(&mut s);
}fn change(some_string: &mut String) {some_string.push_str(", world");
}

同一作用域,特定数据只能有一个可变引用,这种限制的好处就是使 Rust 在编译期就避免数据竞争,数据竞争可由以下行为造成:

  • 两个或更多的指针同时访问同一数据
  • 至少有一个指针被用来写入数据
  • 没有同步数据访问的机制

可以通过手动限制变量的作用域:

let mut s = String::from("hello");{let r1 = &mut s;} // r1 在这里离开了作用域,所以我们完全可以创建一个新的引用let r2 = &mut s;

可变引用与不可变引用不能同时存在,比如

let mut s = String::from("hello");let r1 = &s; // 没问题
let r2 = &s; // 没问题
let r3 = &mut s; // 大问题println!("{}, {}, and {}", r1, r2, r3);

其实这个也很好理解,正在借用不可变引用的用户,肯定不希望他借用的东西,被另外一个人莫名其妙改变了。多个不可变借用被允许是因为没有人会去试图修改数据,每个人都只读这一份数据而不做修改,因此不用担心数据被污染。

注意,引用的作用域 s 从创建开始,一直持续到它最后一次使用的地方,这个跟变量的作用域有所不同,变量的作用域从创建持续到某一个花括号 }

悬垂引用(Dangling References)

悬垂引用也叫做悬垂指针,意思为指针指向某个值后,这个值被释放掉了,而指针仍然存在,其指向的内存可能不存在任何值或已被其它变量重新使用。在 Rust 中编译器可以确保引用永远也不会变成悬垂状态:当你获取数据的引用后,编译器可以确保数据不会在引用结束前被释放,要想释放数据,必须先停止其引用的使用。 例如:

fn dangle() -> &String { // dangle 返回一个字符串的引用let s = String::from("hello"); // s 是一个新字符串&s // 返回字符串 s 的引用
} // 这里 s 离开作用域并被丢弃。其内存被释放。// 危险!

因为 s 是在 dangle 函数内创建的,当 dangle 的代码执行完毕后,s 将被释放, 但是此时我们又尝试去返回它的引用。这意味着这个引用会指向一个无效的 String,这可不对!

其中一个很好的解决方法是直接返回 String:

fn no_dangle() -> String {let s = String::from("hello");s
}

这样就没有任何错误了,最终 String 的 所有权被转移给外面的调用者

3 复合类型

3.1 字符串与切片

3.1.1 切片(slice)

对于字符串而言,切片就是对 String 类型中某一部分的引用

let s = String::from("hello world");let hello = &s[0..5];
let world = &s[6..11];

这就是创建切片的语法,使用方括号包括的一个序列:[开始索引…终止索引],其中开始索引是切片中第一个元素的索引位置,而终止索引是最后一个元素后面的索引位置,也就是这是一个 右半开区间。在切片数据结构内部会保存开始的位置和切片的长度,其中长度是通过 终止索引 - 开始索引 的方式计算得来的。

对于 let world = &s[6..11]; 来说,world 是一个切片,该切片的指针指向 s 的第 7 个字节(索引从 0 开始, 6 是第 7 个字节),且该切片的长度是 5 个字节。
在这里插入图片描述
截取完整的String切片:

let s = String::from("hello");let len = s.len();let slice = &s[0..len];
let slice = &s[..];

字符串切片的类型标识是 &str,在对字符串使用切片语法时需要格外小心,切片的索引必须落在字符之间的边界位置,也就是 UTF-8 字符的边界,例如中文在 UTF-8 中占用三个字节,下面的代码就会崩溃:

 let s = "中国人";let a = &s[0..2];println!("{}",a);

因为我们只取 s 字符串的前两个字节,但是本例中每个汉字占用三个字节,因此没有落在边界处,也就是连 字都取不完整,此时程序会直接崩溃退出,如果改成 &s[0..3],则可以正常通过编译。

因为切片是对集合的部分引用,因此不仅仅字符串有切片,其它集合类型也有,例如数组:

let a = [1, 2, 3, 4, 5];let slice = &a[1..3];assert_eq!(slice, &[2, 3]);

该数组切片的类型是 &[i32],数组切片和字符串切片的工作方式是一样的,例如持有一个引用指向原始数组的某个元素和长度。

字符串是由字符组成的连续集合,Rust 中的字符是 Unicode 类型,因此每个字符占据 4 个字节内存空间,但是在字符串中不一样,字符串是 UTF-8 编码,也就是字符串中的字符所占的字节数是变化的(1 - 4),这样有助于大幅降低字符串所占用的内存空间。Rust 在语言级别,只有一种字符串类型: str,它通常是以引用类型出现 &str,也就是上文提到的字符串切片。但在标准库中String 则是一个可增长、可改变且具有所有权的 UTF-8 编码字符串,当 Rust 用户提到字符串时,往往指的就是 String 类型和 &str 字符串切片类型,这两个类型都是 UTF-8 编码

String&str类型之间的转换:

fn main() {let s = String::from("hello,world!");//等同于 let s="hello,world!".to_string()say_hello(&s); //String转&strsay_hello(&s[..]);say_hello(s.as_str());
}fn say_hello(s: &str) {println!("{}",s);
}

3.1.2 字符串操作

追加(push)

在字符串尾部可以使用 push() 方法追加字符 char,也可以使用 push_str() 方法追加字符串字面量。这两个方法都是在原有的字符串上追加,并不会返回新的字符串。由于字符串追加操作要修改原来的字符串,则该字符串必须是可变的,即字符串变量必须由 mut 关键字修饰。示例代码如下:

fn main() {let mut s = String::from("Hello ");s.push('r'); //Hello rs.push_str("ust!"); //Hello rust!
}
插入(insert)

可以使用 insert() 方法插入单个字符 char,也可以使用 insert_str() 方法插入字符串字面量,与 push() 方法不同,这俩方法需要传入两个参数,第一个参数是字符(串)插入位置的索引,第二个参数是要插入的字符(串),索引从 0 开始计数,如果越界则会发生错误。由于字符串插入操作要修改原来的字符串,则该字符串必须是可变的,即字符串变量必须由 mut 关键字修饰。示例代码如下:

fn main() {let mut s = String::from("Hello rust!");s.insert(5, ','); //Hello, rust!s.insert_str(6, " I like"); //Hello, I like rust!
}
替换(replace)

1、replace

该方法可适用于 String&str 类型。replace() 方法接收两个参数,第一个参数是要被替换的字符串,第二个参数是新的字符串。该方法会替换所有匹配到的字符串。该方法是返回一个新的字符串,而不是操作原来的字符串。示例代码如下:

fn main() {let string_replace = String::from("I like rust. Learning rust is my favorite!");let new_string_replace = string_replace.replace("rust", "RUST");dbg!(new_string_replace);
}

代码运行结果:

new_string_replace = "I like RUST. Learning RUST is my favorite!"

2、replacen

replacen方法可适用于 String&str 类型。replacen() 方法接收三个参数,前两个参数与 replace() 方法一样,第三个参数则表示替换的个数。该方法是返回一个新的字符串,而不是操作原来的字符串

示例代码如下:

fn main() {let string_replace = "I like rust. Learning rust is my favorite!";let new_string_replacen = string_replace.replacen("rust", "RUST", 1);dbg!(new_string_replacen);
}

代码运行结果:

new_string_replacen = "I like RUST. Learning rust is my favorite!"

3、replace_range

该方法仅适用于 String 类型。replace_range 接收两个参数,第一个参数是要替换字符串的范围(Range),第二个参数是新的字符串。该方法是直接操作原来的字符串,不会返回新的字符串。该方法需要使用 mut 关键字修饰

示例代码如下:

fn main() {let mut string_replace_range = String::from("I like rust!");string_replace_range.replace_range(7..8, "R");dbg!(string_replace_range);
}

代码运行结果:

string_replace_range = "I like Rust!"
删除(delete)

1、 pop —— 删除并返回字符串的最后一个字符

该方法是直接操作原来的字符串。但是存在返回值,其返回值是一个 Option 类型,如果字符串为空,则返回 None。 示例代码如下:

fn main() {let mut string_pop = String::from("rust pop 中文!");let p1 = string_pop.pop();let p2 = string_pop.pop();dbg!(p1);dbg!(p2);dbg!(string_pop);
}

代码运行结果:

p1 = Some('!',
)
p2 = Some('文',
)
string_pop = "rust pop 中"

2、 remove —— 删除并返回字符串中指定位置的字符

该方法是直接操作原来的字符串。但是存在返回值,其返回值是删除位置的字符串,只接收一个参数,表示该字符起始索引位置。remove() 方法是按照字节来处理字符串的,如果参数所给的位置不是合法的字符边界,则会发生错误。

示例代码如下:

fn main() {let mut string_remove = String::from("测试remove方法");println!("string_remove 占 {} 个字节",std::mem::size_of_val(string_remove.as_str()));// 删除第一个汉字string_remove.remove(0);// 下面代码会发生错误// string_remove.remove(1);// 直接删除第二个汉字// string_remove.remove(3);dbg!(string_remove);
}

代码运行结果:

string_remove 占 18 个字节
string_remove = "试remove方法"

3、truncate —— 删除字符串中从指定位置开始到结尾的全部字符

该方法是直接操作原来的字符串。无返回值。该方法 truncate() 方法是按照字节来处理字符串的,如果参数所给的位置不是合法的字符边界,则会发生错误。

示例代码如下:

fn main() {let mut string_truncate = String::from("测试truncate");string_truncate.truncate(3);dbg!(string_truncate);
}

代码运行结果:

string_truncate = "测"

4、clear —— 清空字符串

该方法是直接操作原来的字符串。调用后,删除字符串中的所有字符,相当于 truncate() 方法参数为 0 的时候。

示例代码如下:

fn main() {let mut string_clear = String::from("string clear");string_clear.clear();dbg!(string_clear);
}

代码运行结果:

string_clear = ""
连接(concatenate)

1、使用 + 或者 += 连接字符串

使用 + 或者 += 连接字符串,要求右边的参数必须为字符串的切片引用(Slice)类型。其实当调用 + 的操作符时,相当于调用了 std::string 标准库中的 add() 方法,这里 add() 方法的第二个参数是一个引用的类型。因此我们在使用 +, 必须传递切片引用类型。不能直接传递 String 类型。++= 都是返回一个新的字符串。所以变量声明可以不需要 mut 关键字修饰

let s1 = String::from("tic");
let s2 = String::from("tac");
let s3 = String::from("toe");// String = String + &str + &str + &str + &str
let s = s1 + "-" + &s2 + "-" + &s3;

String + &str返回一个 String,然后再继续跟一个 &str 进行 + 操作,返回一个 String 类型,不断循环,最终生成一个 s,也是 String 类型。

s1 这个变量通过调用 add() 方法后,所有权被转移到 add() 方法里面, add() 方法调用后就被释放了,同时 s1 也被释放了。再使用 s1 就会发生错误。

2、使用 format! 连接字符串

format! 这种方式适用于 String&strformat! 的用法与 print! 的用法类似,详见格式化输出。

示例代码如下:

fn main() {let s1 = "hello";let s2 = String::from("rust");let s = format!("{} {}!", s1, s2);println!("{}", s);
}

代码运行结果:

hello rust!
遍历字符串

1、按字符遍历,如果你想要以 Unicode 字符的方式遍历字符串,最好的办法是使用 chars 方法,例如:

for c in "中国人".chars() {println!("{}", c);
}

输出如下

中
国
人

2、按字节遍历,返回字符串的底层字节数组表现形式:

for b in "中国人".bytes() {println!("{}", b);
}

输出如下:

228
184
173
229
155
189
228
186
186

3.2 元组

元组是由多种类型组合到一起形成的,因此它是复合类型,元组的长度是固定的,元组中元素的顺序也是固定的。

可以通过以下语法创建一个元组:

fn main() {let tup: (i32, f64, u8) = (500, 6.4, 1);
}

变量 tup 被绑定了一个元组值 (500, 6.4, 1),该元组的类型是 (i32, f64, u8)

用模式匹配解构元组

用同样的形式把一个复杂对象中的值匹配出来。

fn main() {let tup = (500, 6.4, 1);let (x, y, z) = tup;println!("The value of y is: {}", y);}
. 来访问元组

模式匹配可以让我们一次性把元组中的值全部或者部分获取出来,如果只想要访问某个特定元素,那模式匹配就略显繁琐,对此,Rust 提供了 . 的访问方式,元组的索引从 0 开始:

fn main() {let x: (i32, f64, u8) = (500, 6.4, 1);let five_hundred = x.0;let six_point_four = x.1;let one = x.2;
}

可以通过使用元组使函数返回多个值:

fn main() {let s1 = String::from("hello");let (s2, len) = calculate_length(s1);println!("The length of '{}' is {}.", s2, len);
}fn calculate_length(s: String) -> (String, usize) {let length = s.len(); // len() 返回字符串的长度(s, length)
}

3.3 结构体

3.3.1 结构体语法

定义结构体

一个结构体由几部分组成:

  • 通过关键字 struct 定义
  • 一个清晰明确的结构体 名称
  • 几个有名字的结构体 字段
struct User {active: bool,username: String,email: String,sign_in_count: u64,
}

该结构体名称是 User,拥有 4 个字段,且每个字段都有对应的字段名及类型声明,例如 username 代表了用户名,是一个可变的 String 类型。

创建结构体实例

创建 User 结构体的实例

    let mut user1 = User {email: String::from("someone@example.com"),username: String::from("someusername123"),active: true,sign_in_count: 1,};

有几点值得注意:

  1. 初始化实例时,每个字段都需要进行初始化
  2. 初始化时的字段顺序不需要和结构体定义时的顺序一致
访问结构体字段

必须要将结构体实例声明为可变的,才能修改其中的字段,Rust 不支持将某个结构体某个字段标记为可变。

 user1.email = String::from("anotheremail@example.com");
简化结构体创建

当函数参数和结构体字段同名时,可以直接使用缩略的方式进行初始化

fn build_user(email: String, username: String) -> User {User {email,username,active: true,sign_in_count: 1,}
}

根据已有的结构体实例,创建新的结构体实例,例如根据已有的 user1 实例来构建 user2,Rust 为我们提供了 结构体更新语法

  let user2 = User {email: String::from("another@example.com"),..user1};

因为 user2 仅仅在 email 上与 user1 不同,因此我们只需要对 email 进行赋值,剩下的通过结构体更新语法 ..user1 即可完成。

.. 语法表明凡是我们没有显式声明的字段,全部从 user1 中自动获取。需要注意的是 ..user1 必须在结构体的尾部使用。

结构体更新语法跟赋值语句 = 非常相像,因此在上面代码中,user1 的部分字段所有权被转移到 user2 中:username 字段发生了所有权转移,作为结果,user1 无法再被使用。值得注意的是:username 所有权被转移给了 user2,导致了 user1 无法再被使用,但是并不代表 user1 内部的其它字段不能被继续使用:

let user1 = User {email: String::from("someone@example.com"),username: String::from("someusername123"),active: true,sign_in_count: 1,
};
let user2 = User {active: user1.active,username: user1.username,email: String::from("another@example.com"),sign_in_count: user1.sign_in_count,
};
println!("{}", user1.active);
// 下面这行会报错
println!("{:?}", user1);

即,把结构体中具有所有权的字段转移出去后,将无法再访问该字段,但是可以正常访问其它的字段

3.3.2 元组结构体(Tuple Struct)

结构体必须要有名称,但是结构体的字段可以没有名称,这种结构体长得很像元组,因此被称为元组结构体,例如:

    struct Color(i32, i32, i32);struct Point(i32, i32, i32);let black = Color(0, 0, 0);let origin = Point(0, 0, 0);

元组结构体在希望有一个整体名称,但是又不关心里面字段的名称时将非常有用。例如上面的 Point 元组结构体,众所周知 3D 点是 (x, y, z) 形式的坐标点,因此我们无需再为内部的字段逐一命名为:x, y, z

3.3.3 单元结构体(Unit-like Struct)

单元结构体没有任何字段和属性,但是好在,它还挺有用。如果你定义一个类型,但是不关心该类型的内容, 只关心它的行为时,就可以使用 单元结构体

struct AlwaysEqual;let subject = AlwaysEqual;// 我们不关心 AlwaysEqual 的字段数据,只关心它的行为,因此将它声明为单元结构体,然后再为它实现某个特征
impl SomeTrait for AlwaysEqual {}

如果想在结构体中使用一个引用,就必须加上生命周期。

3.3.4 使用 #[derive(Debug)] 来打印结构体的信息

#[derive(Debug)]
struct Rectangle {width: u32,height: u32,
}fn main() {let rect1 = Rectangle {width: 30,height: 50,};println!("rect1 is {:?}", rect1);
}

此时运行程序,就不再有错误,输出如下:

$ cargo run
rect1 is Rectangle { width: 30, height: 50 }

当结构体较大时,我们可能希望能够有更好的输出表现,此时可以使用 {:#?} 来替代 {:?},输出如下:

rect1 is Rectangle {width: 30,height: 50,
}

还有一个简单的输出 debug 信息的方法,那就是使用 dbg! 宏,它会拿走表达式的所有权,然后打印出相应的文件名、行号等 debug 信息,当然还有我们需要的表达式的求值结果。除此之外,它最终还会把表达式值的所有权返回!

dbg!` 输出到标准错误输出 `stderr`,而 `println!` 输出到标准输出 `stdout

下面的例子中清晰的展示了 dbg! 如何在打印出信息的同时,还把表达式的值赋给了 width:

#[derive(Debug)]
struct Rectangle {width: u32,height: u32,
}fn main() {let scale = 2;let rect1 = Rectangle {width: dbg!(30 * scale),height: 50,};dbg!(&rect1);
}

最终的 debug 输出如下:

$ cargo run
[src/main.rs:10] 30 * scale = 60
[src/main.rs:14] &rect1 = Rectangle {width: 60,height: 50,
}

3.4 枚举

3.4.1 枚举的语法

枚举(enum 或 enumeration)允许通过列举可能的成员来定义一个枚举类型枚举类型是一个类型,它会包含所有可能的枚举成员, 而枚举值是该类型中的具体某个成员的实例。 如:

enum PokerSuit {Clubs,Spades,Diamonds,Hearts,
}

结构体实现带值的枚举成员:

enum PokerSuit {Clubs,Spades,Diamonds,Hearts,
}struct PokerCard {suit: PokerSuit,value: u8
}fn main() {let c1 = PokerCard {suit: PokerSuit::Clubs,value: 1,};let c2 = PokerCard {suit: PokerSuit::Diamonds,value: 12,};
}

直接将数据信息关联到枚举成员上,可省去近一半的代码,例如:

enum PokerCard {Clubs(u8),Spades(u8),Diamonds(char),Hearts(char),
}fn main() {let c1 = PokerCard::Spades(5);let c2 = PokerCard::Diamonds('A');
}

任何类型的数据都可以放入枚举成员中: 例如字符串、数值、结构体甚至另一个枚举。以下代码:

enum Message {Quit,Move { x: i32, y: i32 },Write(String),ChangeColor(i32, i32, i32),
}fn main() {let m1 = Message::Quit;let m2 = Message::Move{x:1,y:1};let m3 = Message::ChangeColor(255,255,0);
}

该枚举类型代表一条消息,它包含四个不同的成员:

  • Quit 没有任何关联数据
  • Move 包含一个匿名结构体
  • Write 包含一个 String 字符串
  • ChangeColor 包含三个 i32

3.4.2 Opention枚举处理空值

Option 枚举包含两个成员,一个成员表示含有值:Some(T), 另一个表示没有值:None,定义如下:

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

其中 T 是泛型参数,Some(T)表示该枚举成员的数据类型是 T,换句话说,Some 可以包含任何类型的数据。Option<T>T(这里 T 可以是任何类型)是不同的类型。

为了使用 Option<T> 值,需要编写处理每个成员的代码。match 表达式就是这么一个处理枚举的控制流结构:它会根据枚举的成员运行不同的代码,这些代码可以使用匹配到的值中的数据。

fn plus_one(x: Option<i32>) -> Option<i32> {match x {None => None,Some(i) => Some(i + 1),}
}let five = Some(5);
let six = plus_one(five);
let none = plus_one(None);

plus_one 通过 match 来处理不同 Option 的情况。

3.5 数组

在 Rust 中,最常用的数组有两种,第一种是速度很快但是长度固定的 array,第二种是可动态增长的但是有性能损耗的 Vector。这两个数组的关系跟 &strString 的关系很像,前者是长度固定的字符串切片,后者是可动态增长的字符串。其实,在 Rust 中无论是 String 还是 Vector,它们都是 Rust 的高级类型:集合类型。数组的具体定义很简单:将多个类型相同的元素依次组合在一起,就是一个数组。数组声明格式为:[类型; 长度]。结合上面的内容,可以得出数组的三要素:

  • 长度固定
  • 元素必须有相同的类型
  • 依次线性排列

这里说的数组是 Rust 的基本类型,是固定长度的,这点与其他编程语言不同,其它编程语言的数组往往是可变长度的,与 Rust 中的动态数组 Vector 类似

3.5.1 创建数组

fn main() {//let a = [1, 2, 3, 4, 5]; 或者	let a: [i32; 5] = [1, 2, 3, 4, 5]; //包含5个i32类型的数let b = [3; 5]; //数组b中包含5个3
}

3.5.2 数组越界访问

下面是一个接收用户的控制台输入,然后将其作为索引访问数组元素的例子:

use std::io;fn main() {let a = [1, 2, 3, 4, 5];println!("Please enter an array index.");let mut index = String::new();// 读取控制台的输出io::stdin().read_line(&mut index).expect("Failed to read line");let index: usize = index.trim().parse().expect("Index entered was not a number");let element = a[index];println!("The value of the element at index {} is: {}",index, element);
}

使用 cargo run 来运行代码,因为数组只有 5 个元素,如果我们试图输入 5 去访问第 6 个元素,则会访问到不存在的数组元素,最终程序会崩溃退出:

Please enter an array index.
5
thread 'main' panicked at 'index out of bounds: the len is 5 but the index is 5', src/main.rs:19:19
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace

这就是数组访问越界,访问了数组中不存在的元素,导致 Rust 运行时错误。

当你尝试使用索引访问元素时,Rust 将检查你指定的索引是否小于数组长度。如果索引大于或等于数组长度,Rust 会出现 *panic*。这种检查只能在运行时进行,比如在上面这种情况下,编译器无法在编译期知道用户运行代码时将输入什么值,这种就是 Rust 的安全特性之一。

数组元素为非基础类型时,应调用std::array::from_fn

let array: [String; 8] = core::array::from_fn(|i| String::from("rust is good!"));println!("{:#?}", array);

3.5.3 数组切片

切片允许你引用集合中的部分连续片段,而不是整个集合,对于数组也是,数组切片允许我们引用数组的一部分:

let a: [i32; 5] = [1, 2, 3, 4, 5];let slice: &[i32] = &a[1..3];assert_eq!(slice, &[2, 3]);

上面的数组切片 slice 的类型是&[i32],与之对比,数组的类型是[i32;5],简单总结下切片的特点:

  • 切片的长度可以与数组不同,并不是固定的,而是取决于你使用时指定的起始和结束位置
  • 创建切片的代价非常小,因为切片只是针对底层数组的一个引用
  • 切片类型[T]拥有不固定的大小,而切片引用类型&[T]则具有固定的大小,因为 Rust 很多时候都需要固定大小数据类型,因此&[T]更有用,&str字符串切片也同理

数组综合示例:

fn main() {// 编译器自动推导出one的类型let one             = [1, 2, 3];// 显式类型标注let two: [u8; 3]    = [1, 2, 3];let blank1          = [0; 3];let blank2: [u8; 3] = [0; 3];// arrays是一个二维数组,其中每一个元素都是一个数组,元素类型是[u8; 3]let arrays: [[u8; 3]; 4]  = [one, two, blank1, blank2];// 借用arrays的元素用作循环中for a in &arrays {print!("{:?}: ", a);// 将a变成一个迭代器,用于循环// 你也可以直接用for n in a {}来进行循环for n in a.iter() {print!("\t{} + 10 = {}", n, n+10);}let mut sum = 0;// 0..a.len,是一个 Rust 的语法糖,其实就等于一个数组,元素是从0,1,2一直增加到到a.len-1for i in 0..a.len() {sum += a[i];}println!("\t({:?} = {})", a, sum);}
}

运行结果:

[1, 2, 3]: 	1 + 10 = 11	2 + 10 = 12	3 + 10 = 13	([1, 2, 3] = 6)
[1, 2, 3]: 	1 + 10 = 11	2 + 10 = 12	3 + 10 = 13	([1, 2, 3] = 6)
[0, 0, 0]: 	0 + 10 = 10	0 + 10 = 10	0 + 10 = 10	([0, 0, 0] = 0)
[0, 0, 0]: 	0 + 10 = 10	0 + 10 = 10	0 + 10 = 10	([0, 0, 0] = 0)

数组虽然很简单,但是其实还是存在几个要注意的点:

  • 数组类型容易跟数组切片混淆,[T;n]描述了一个数组的类型,而[T]描述了切片的类型, 因为切片是运行期的数据结构,它的长度无法在编译期得知,因此不能用[T;n]的形式去描述
  • [u8; 3][u8; 4]是不同的类型,数组的长度也是类型的一部分
  • 在实际开发中,使用最多的是数组切片[T],我们往往通过引用的方式去使用&[T],因为后者有固定的类型大小

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

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

相关文章

UI自动化的适用场景,怎么做?

经常有人会问&#xff0c;什么样的项目才适合进行UI自动化测试呢&#xff1f;UI自动化测试相当于模拟手工测试&#xff0c;通过程序去操作页面上的控件。而在实际测试过程中&#xff0c;经常会遇到无法找到控件&#xff0c;或者因控件定义变更而带来的维护成本等问题。 哪些场…

Java武侠文字游戏

import java.util.Random;public class Role {//姓名private String name;//血量private int blood;//性别private char gender;//长相(随机)private String face;String[] boyfaces {"风流俊雅", "气宇轩昂", "相貌英俊", "五官端正"…

巴以冲突中暴露的摄像头正对安全构成威胁

巴以冲突爆发后&#xff0c;许多配置不当的安全摄像头正暴露给黑客活动分子&#xff0c;使其周遭人员面临巨大安全风险。 Cyber​​news 研究人员发现&#xff0c;在以色列至少有165 个暴露的联网 RTSP 摄像头&#xff0c;在巴勒斯坦有 29 个暴露的 RTSP 摄像头。在巴勒斯坦&am…

华为eNSP配置专题-VLAN和DHCP的配置

文章目录 华为eNSP配置专题-VLAN和DHCP的配置1、前置环境1.1、宿主机1.2、eNSP模拟器 2、基本环境搭建2.1、基本终端构成和连接 3、VLAN的配置3.1、两台PC先配置静态IP3.2、交换机上配置VLAN 4、接口方式的DHCP的配置4.1、在交换机上开启DHCP4.2、在PC上开启DHCP 5、全局方式的…

【MySQL】深入了解索引的底层逻辑结构

文章目录 主键排序一. InnoDB的索引结构1. 单个page2. 多个page 二. 为什么选择B树三. 聚簇索引和非聚簇索引结束语 主键排序 我们创建一个user表&#xff0c;并乱序插入数据 mysql> create table if not exists user(-> id int primary key,-> age int not null,-&…

嵌入式SoC降低能够有效降低电力线表应用的成本

用于基于FSK的电力线通信&#xff08;PLC&#xff09;。MB87S2090和MB87S2090-F与ADD Semiconductor&#xff0c;电力线通信嵌入式SoC的开发商以及用于自动仪表管理的解决方案联合开发。两种器件都与ADD Semiconductor的设计引脚兼容。 MB87S2090是一种电力线通信嵌入式SoC&am…

centos7部署Nginx和RabbitMQ

文章目录 Nginx安装部署【简单】简介安装 RabbitMQ安装部署【简单】简介安装 Nginx安装部署【简单】 简介 Nginx (engine x) 是一个高性能的HTTP和反向代理web服务器&#xff0c;同时也提供了IMAP/POP3/SMTP服务。Nginx可以托管用户编写的WEB应用程序成为可访问的网页服务&am…

从零到一完成Midway.js登录、注册、鉴权功能

您好&#xff0c;如果喜欢我的文章&#xff0c;可以关注我的公众号「量子前端」&#xff0c;将不定期关注推送前端好文~ 前言 本文将从项目搭建到实现从零到一开发一个登录、注册、鉴权的简易版注册登录系统&#xff0c;主要功能和技术选型如下&#xff1a; 服务端框架———…

服务安全-应用协议rsync未授权ssh漏洞复现

目录 服务攻防-应用协议rsync&ssh漏洞复现漏洞复现配置不当-未授权访问-rsync文件备份OpenSSH 用户名枚举漏洞libssh身份验证绕过漏洞 服务攻防-应用协议rsync&ssh漏洞复现 漏洞复现 配置不当-未授权访问-rsync文件备份 rsync默认端口&#xff1a;873 rsync是Linux下…

初识华为云数据库GaussDB for openGauss

01 前言 GaussDB是华为自主创新研发的分布式关系型数据库。该产品具备企业级复杂事务混合负载能力&#xff0c;同时支持分布式事务&#xff0c;同城跨AZ部署&#xff0c;数据0丢失&#xff0c;支持1000的扩展能力&#xff0c;PB级海量存储。同时拥有云上高可用&#xff0c;高可…

PyTorch 深度学习之卷积神经网络(高级篇)Advanced CNN(十)

0. Revision 前面讲的比较简单的是 串行网络结构 1. GoogLeNet 1.1 Inception module w h 要一致 what is 11 convolution? 信息融合-eg.高中各门学科成绩比较(总分) 最主要工作:改变通道数量 why is 11 convolution? 减少10倍 1.2 implementation of inception module 拼…

深度学习实战57-pytorch框架搭建LSTM+CNN模型与实现时间序列的预测过程

大家好,我是微学AI,今天给大家介绍一下深度学习实战57-pytorch框架搭建LSTM+CNN模型与实现时间序列的预测过程, 随着科技的进步,我们越来越依赖数据来理解世界,预测未来。特别是在金融、气候研究、交通管理等领域,时间序列预测已经成为了重要的工具。本文将介绍如何使用L…

算法leetcode|84. 柱状图中最大的矩形(rust重拳出击)

文章目录 84. 柱状图中最大的矩形&#xff1a;样例 1&#xff1a;样例 2&#xff1a;提示&#xff1a; 分析&#xff1a;题解&#xff1a;rust&#xff1a;go&#xff1a;c&#xff1a;python&#xff1a;java&#xff1a; 84. 柱状图中最大的矩形&#xff1a; 给定 n 个非负整…

构建高效问题解答平台:使用Cpolar和Tipas在Ubuntu上搭建专属问答网站

文章目录 前言2.Tipask网站搭建2.1 Tipask网站下载和安装2.2 Tipask网页测试2.3 cpolar的安装和注册 3. 本地网页发布3.1 Cpolar临时数据隧道3.2 Cpolar稳定隧道&#xff08;云端设置&#xff09;3.3 Cpolar稳定隧道&#xff08;本地设置&#xff09; 4. 公网访问测试5. 结语 前…

KubeVela交付

有什么用我也不想说了&#xff0c;这个是k8s CI/CD,进阶玩家玩的了&#xff0c;比你们喜欢Arg CD更科学&#xff0c;更现代 在 Kubernetes 中安装 KubeVela helm repo add kubevela https://charts.kubevela.net/core helm repo update helm install --create-namespace -n v…

pip快速安装torch、opencv、scipy库

目录 一、pip安装torch 1.1 torch介绍 1.2 torch.nn相关库的导入 1.3win10上torch的安装命令 二、pip安装Opencv 三、pip安装scipy库 一、pip安装torch 1.1 torch介绍 torch的基本功能&#xff1a; ①torch&#xff1a;张量的相关运算&#xff0c;例如&#xff1a;创…

ti am335 RT-LINUX测试

RT-Linux是一个基于Linux内核的实时操作系统&#xff0c;它在满足Linux操作系统的通用性的同时兼顾 实时性能&#xff0c;它的核心是Linux内核的一个实时扩展&#xff0c;它为实时任务提供了必要的调度机制和时间管理。通过采用抢占式调度策略&#xff0c;高优先级的实时任务可…

STM32物联网基于ZigBee智能家居控制系统

实践制作DIY- GC0169-ZigBee智能家居 一、功能说明&#xff1a; 基于STM32单片机设计-ZigBee智能家居 二、功能介绍&#xff1a; 1个主机显示板&#xff1a;STM32F103C最小系统ZigBee无线模块OLED显示器 语音识别模块多个按键ESP8266-WIFI模块&#xff08;仅WIFI版本有&…

Python学习基础笔记七十二——IDE集成开发环境

集成开发环境&#xff0c;英文缩写是IDE。 IDE可以帮你更高效地开发项目代码。因为它提供了非常实用的功能&#xff0c;比如项目文件管理、语法高亮、代码导航、自动补齐代码、语法静态检查、调试、版本控制等等。 两款IDE&#xff1a;Pycharm和VSCode。 pycharm中的代码文件都…

HUAWEI(26)——防火墙双机热备

一、拓扑 二、需求 PC2 ping PC1 FW1与FW2双机热备,FW1为active,FW2为Standby,抢占延时1s VRRP 三、配置 1.IP地址,防火墙接口加入区域 防火墙用户名:admin 防火墙旧密码:Admin@123 防火墙新密码:admin@123 [FW1]interface GigabitEthernet 1/0/0 [FW1-GigabitEthe…