【Rust】001-基础语法:变量声明及数据类型

╰+哭是因爲堅強的太久メ 2023-10-15 15:05 73阅读 0赞

【Rust】001-基础语法:变量声明及数据类型

文章目录

  • 【Rust】001-基础语法:变量声明及数据类型
  • 一、概述
    • 1、学习起源
    • 2、依托课程
  • 二、入门程序
    • 1、Hello World
    • 2、交互程序
      • 代码演示
      • 执行结果
    • 3、继续上难度:访问链接并打印响应
      • 依赖
      • 代码
      • 执行命令
  • 三、数据类型
    • 1、标量类型
      • 整型标量类型
      • 其它
    • 2、复合类型
  • 四、变量声明与使用
    • 1、常量
      • 代码演示
      • 执行结果
    • 2、变量
      • 代码演示
      • 执行结果
    • 3、变量名复用
      • 代码演示
      • 执行结果
    • 4、声明时指定变量类型
      • 代码演示
      • 执行结果
    • 5、元组的使用
      • 代码演示
      • 执行结果
    • 6、数组的使用
      • 代码演示
      • 执行结果
    • 7、字符串
      • 代码演示
      • 执行结果
  • 五、演示 `Ownership(所有权)`、`Borrowing(借用)` 和 `Lifetime(生命周期)` 的基本概念的示例
      • 代码演示
      • 执行结果
  • 六、const 和 let 的区别
        1. 可变性(Mutability)
        1. 类型注解
        1. 初始化表达式
        1. 作用域和生命周期
        1. 内联

一、概述

1、学习起源

“一切能用 Rust 重写的项目都将或者正在用 Rust 重写”

2、依托课程

Rust 入门与实践:https://juejin.cn/book/7269676791348854839?utm\_source=course\_list

二、入门程序

1、Hello World

  1. fn main() {
  2. // 打印字符串
  3. println!("Hello, world!");
  4. }

2、交互程序

代码演示

  1. use std::io; // 使用标准库中的 io 这个模块
  2. fn main() {
  3. // 打印字符串
  4. println!("Hello, world!");
  5. // 打印字符串
  6. println!("请输入一个数字: ");
  7. // 在这里我们创建了一个新的 String,用来接收下面的输入
  8. let mut input = String::new();
  9. io::stdin()
  10. .read_line(&mut input) // 读取一行
  11. .expect("Failed to read input!"); // 比较粗暴的错误处理
  12. // 打印输入的原始内容
  13. println!("Your raw input is: {:?}.", input);
  14. // trim 把前后的空格、换行符这些空白字符都去掉,parse 将输入的字符串解析为 i64 类型,如果解析失败就报错
  15. let number: i64 = input.trim().parse().expect("Input is not a number!");
  16. // 打印 parse 之后的 i64 数字
  17. println!("Your input is: {}.", number);
  18. }

执行结果

  1. C:/Users/Administrator/.cargo/bin/cargo.exe run --color=always --package hello_rust --bin hello_rust
  2. Finished dev [unoptimized + debuginfo] target(s) in 0.01s
  3. Running `target\debug\hello_rust.exe`
  4. Hello, world!
  5. 请输入一个数字:
  6. 100
  7. Your raw input is: "100\n".
  8. Your input is: 100.
  9. 进程已结束,退出代码为 0

3、继续上难度:访问链接并打印响应

依赖

Cargo.toxml

  1. [package]
  2. name = "hello_rust"
  3. version = "0.1.0"
  4. edition = "2021"
  5. [dependencies]
  6. clap = { version = "4", features = ["derive"] }
  7. reqwest = { version = "0.11", features = ["blocking"] }

代码

  1. // 使用 use 引入一个标准库的包,或者第三方的包
  2. use std::error::Error;
  3. // clap 是一个 Rust 社区开发的命令行参数解析库
  4. use clap::Parser;
  5. // reqwest 是一个 Rust 社区开发的 HTTP 客户端库
  6. use reqwest::blocking::Client;
  7. use reqwest::header::HeaderMap;
  8. // 使用 derive 宏,用于自动生成 Parser 的实现
  9. // 在高级特性章节中我们会学到宏的用法及原理
  10. #[derive(Parser)]
  11. #[command(
  12. author,
  13. version,
  14. about = "Sends HTTP requests and prints detailed information"
  15. )]
  16. struct Cli {
  17. // arg 宏用于标记命令行参数,这里标记了一个必须的 URL 参数
  18. #[arg(short, long, help = "Target URL", required = true)]
  19. url: String,
  20. }
  21. /// Rust 程序入口
  22. fn main() -> Result<(), Box<dyn Error>> {
  23. // 解析命令行参数
  24. let cli = Cli::parse();
  25. // 发起 HTTP 请求
  26. // ? 是 Rust 中的错误传播语法糖,我们会在接下来的章节中学习
  27. let response = send_request(&cli.url)?;
  28. // 打印 HTTP 响应的详细信息
  29. print_response_details(response)?;
  30. Ok(())
  31. }
  32. /// 发起一个 HTTP 请求
  33. /// 参数是目标 URL 的引用
  34. /// 返回值是一个 Result,如果请求成功返回 Response,否则返回一个动态 Error
  35. fn send_request(url: &str) -> Result<reqwest::blocking::Response, Box<dyn Error>> {
  36. // 创建一个 HTTP 客户端
  37. let client = Client::builder().build()?;
  38. // 使用 GET 方法发起请求
  39. let response = client.get(url).send()?;
  40. Ok(response)
  41. }
  42. /// 打印出 HTTP 响应的详细信息
  43. /// 参数是 Response 对象
  44. /// 返回值是一个 Result,用于错误处理
  45. fn print_response_details(response: reqwest::blocking::Response) -> Result<(), Box<dyn Error>> {
  46. // 打印 HTTP 状态码
  47. println!("Status: {}", response.status());
  48. // 打印 HTTP 响应头
  49. println!("Headers:");
  50. print_headers(response.headers());
  51. // 读取并打印 HTTP 响应体
  52. let body = response.text()?;
  53. println!("Body:\n{}", body);
  54. Ok(())
  55. }
  56. /// 打印出 HTTP 响应头
  57. /// 参数是 HeaderMap 的引用
  58. fn print_headers(headers: &HeaderMap) {
  59. for (key, value) in headers.iter() {
  60. // 打印每个响应头的键和值
  61. // 如果值不是 UTF-8 字符串,就打印 [unprintable]
  62. println!(" {}: {}", key, value.to_str().unwrap_or("[unprintable]"));
  63. }
  64. }

执行命令

根目录执行

  1. cargo run -- --url https://juejin.cn/

三、数据类型

1、标量类型

整型标量类型

image-20230902162358238

只要记得最低从 8 开始,到 128 结束(当然,正常情况下我们最多用到 64,128 在很多平台上需要软件模拟而不是硬件支持,不推荐大家用);在赋值的时候除了直接十进制数字赋值外,还支持以下语法(大家了解一下就好,不用死记硬背):

image-20230902162458543

其它

  • 浮点数:f32 / f64
  • bool
  • char:这个比较特殊,Rust 中一个 char 占 4 字节,存放的是一个 UTF-32,而不像 C/C++ 那样本质上是个 u8

2、复合类型

  • 元组 tuple:let a = (1, 2); let (a, b) = (1, 2)
  • 数组 array: let a = [1, 2, 3]; let a = [0; 5] // 这个声明中 0 是默认值,5 是长度,等价于 let a = [0, 0, 0, 0, 0]

四、变量声明与使用

1、常量

代码演示

  1. fn main() {
  2. // 声明常量,表示年龄
  3. const AGE: u32 = 18;
  4. // 声明常量,表示名字
  5. let name = "张三";
  6. // 打印名字和年龄
  7. println!("{}的年龄是{}", name, AGE);
  8. }

执行结果

  1. 张三的年龄是18

2、变量

代码演示

  1. fn main() {
  2. // 声明变量,表示年龄
  3. let mut age = 18;
  4. // 打印变量
  5. println!("age = {}", age);
  6. // 修改变量
  7. age = 20;
  8. // 打印变量
  9. println!("age = {}", age);
  10. }

执行结果

  1. age = 18
  2. age = 20

3、变量名复用

代码演示

  1. fn main() {
  2. // 声明常量,表示年龄
  3. let age = 18;
  4. // 打印年龄
  5. println!("age = {}", age);
  6. // 再次声明 age 变量,此时不会报错
  7. let age = 20;
  8. // 打印年龄
  9. println!("age = {}", age);
  10. }

执行结果

  1. age = 18
  2. age = 20

4、声明时指定变量类型

代码演示

  1. fn main() {
  2. // 声明常量,表示年龄
  3. let age: i32 = 18;
  4. // 打印年龄
  5. println!("age = {}", age);
  6. }

执行结果

  1. age = 18
  2. age = 20

5、元组的使用

代码演示

  1. fn main() {
  2. // 声明一个包含三个元素的元组
  3. let my_tuple = (1, "hello", 3.14);
  4. // 使用索引访问元组中的元素
  5. println!("第一个元素是:{}", my_tuple.0); // 输出 "第一个元素是:1"
  6. println!("第二个元素是:{}", my_tuple.1); // 输出 "第二个元素是:hello"
  7. println!("第三个元素是:{}", my_tuple.2); // 输出 "第三个元素是:3.14"
  8. // 使用模式匹配解构元组
  9. let (x, y, z) = my_tuple;
  10. println!("解构后 x 的值是:{}", x); // 输出 "解构后 x 的值是:1"
  11. println!("解构后 y 的值是:{}", y); // 输出 "解构后 y 的值是:hello"
  12. println!("解构后 z 的值是:{}", z); // 输出 "解构后 z 的值是:3.14"
  13. // 忽略元组中不需要的值
  14. let (a, _, _) = my_tuple;
  15. println!("只需要第一个元素:{}", a); // 输出 "只需要第一个元素:1"
  16. // 嵌套元组
  17. let nested_tuple = (1, (2, 3), 4);
  18. let (_, (b, c), _) = nested_tuple;
  19. println!("嵌套元组中 b 的值和 c 的值分别是:{} 和 {}", b, c); // 输出 "嵌套元组中 b 的值和 c 的值分别是:2 和 3"
  20. }

执行结果

  1. 第一个元素是:1
  2. 第二个元素是:hello
  3. 第三个元素是:3.14
  4. 解构后 x 的值是:1
  5. 解构后 y 的值是:hello
  6. 解构后 z 的值是:3.14
  7. 只需要第一个元素:1
  8. 嵌套元组中 b 的值和 c 的值分别是:2 3

6、数组的使用

代码演示

  1. fn main() {
  2. // 声明一个包含5个元素的整数数组
  3. let int_array = [1, 2, 3, 4, 5];
  4. // 声明一个包含5个元素的浮点数数组,同时指定类型
  5. let float_array: [f64; 5] = [1.0, 2.0, 3.0, 4.0, 5.0];
  6. // 使用索引访问数组中的元素
  7. println!("整数数组的第一个元素是:{}", int_array[0]); // 输出 "整数数组的第一个元素是:1"
  8. println!("浮点数数组的第二个元素是:{}", float_array[1]); // 输出 "浮点数数组的第二个元素是:2.0"
  9. // 使用循环遍历整数数组
  10. println!("整数数组的所有元素:");
  11. for num in int_array.iter() {
  12. print!("{} ", num); // 输出 "1 2 3 4 5 "
  13. }
  14. println!();
  15. // 使用循环遍历浮点数数组,并获取索引
  16. println!("浮点数数组的所有元素和对应的索引:");
  17. for (index, num) in float_array.iter().enumerate() {
  18. println!("索引:{}, 元素:{}", index, num);
  19. // 输出 "索引:0, 元素:1.0"
  20. // 输出 "索引:1, 元素:2.0"
  21. // ...
  22. }
  23. // 声明一个全部元素为0的数组
  24. let zero_array: [i32; 5] = [0; 5];
  25. println!("全为0的数组:{:?}", zero_array); // 输出 "全为0的数组:[0, 0, 0, 0, 0]"
  26. }

执行结果

  1. 整数数组的第一个元素是:1
  2. 浮点数数组的第二个元素是:2
  3. 整数数组的所有元素:
  4. 1 2 3 4 5
  5. 浮点数数组的所有元素和对应的索引:
  6. 索引:0, 元素:1
  7. 索引:1, 元素:2
  8. 索引:2, 元素:3
  9. 索引:3, 元素:4
  10. 索引:4, 元素:5
  11. 全为0的数组:[0, 0, 0, 0, 0]

7、字符串

代码演示

  1. fn main() {
  2. // 使用字符串字面量声明一个不可变字符串
  3. let hello_str = "Hello, world!";
  4. println!("不可变字符串字面量:{}", hello_str); // 输出 "不可变字符串字面量:Hello, world!"
  5. // 使用 String::from 创建一个可变字符串
  6. let mut hello_string = String::from("Hello");
  7. println!("可变字符串:{}", hello_string); // 输出 "可变字符串:Hello"
  8. // 在可变字符串后追加字符串
  9. hello_string.push_str(", world!");
  10. println!("追加后的可变字符串:{}", hello_string); // 输出 "追加后的可变字符串:Hello, world!"
  11. // 字符串拼接
  12. let concat_str = [hello_str, " ", &hello_string].concat();
  13. println!("拼接后的字符串:{}", concat_str); // 输出 "拼接后的字符串:Hello, world! Hello, world!"
  14. // 使用索引获取字符串中的字符(注意:这种方式不推荐,因为会导致错误或崩溃)
  15. // let first_char = hello_str[0]; // 这样是错误的
  16. // Rust 的字符串是 UTF-8 编码的,直接索引可能会导致字符被截断。
  17. // 使用 chars 方法遍历字符串中的字符
  18. println!("使用 chars 方法遍历字符串:");
  19. for ch in hello_str.chars() {
  20. print!("{} ", ch); // 输出 "H e l l o , w o r l d ! "
  21. }
  22. println!();
  23. // 使用 bytes 方法遍历字符串中的字节
  24. println!("使用 bytes 方法遍历字符串字节:");
  25. for byte in hello_str.bytes() {
  26. print!("{} ", byte); // 输出对应的 ASCII 或 UTF-8 编码的字节值
  27. }
  28. println!();
  29. // 获取字符串长度
  30. println!("字符串 '{}' 的长度是:{}", hello_str, hello_str.len()); // 输出 "字符串 'Hello, world!' 的长度是:13"
  31. }

执行结果

  1. 不可变字符串字面量:Hello, world!
  2. 可变字符串:Hello
  3. 追加后的可变字符串:Hello, world!
  4. 拼接后的字符串:Hello, world! Hello, world!
  5. 使用 chars 方法遍历字符串:
  6. H e l l o , w o r l d !
  7. 使用 bytes 方法遍历字符串字节:
  8. 72 101 108 108 111 44 32 119 111 114 108 100 33
  9. 字符串 'Hello, world!' 的长度是:13

五、演示 Ownership(所有权)Borrowing(借用)Lifetime(生命周期) 的基本概念的示例

代码演示

  1. // 定义一个函数,演示所有权的转移
  2. fn takes_ownership(some_string: String) {
  3. println!("函数内部:{}", some_string);
  4. } // 这里 some_string 离开作用域,所有权也随之释放
  5. // 定义一个函数,演示借用(不可变)
  6. fn borrows_immutable(s: &String) {
  7. println!("函数内部(不可变借用):{}", s);
  8. }
  9. // 定义一个函数,演示借用(可变)
  10. fn borrows_mutable(s: &mut String) {
  11. s.push_str(", world!"); // 修改字符串
  12. println!("函数内部(可变借用):{}", s);
  13. }
  14. // 定义一个函数,演示生命周期
  15. // 注:'a 是生命周期标注,表明 x 和 y 的生命周期相同,并且与返回值的生命周期也相同
  16. fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
  17. if x.len() > y.len() {
  18. x
  19. } else {
  20. y
  21. }
  22. }
  23. fn main() {
  24. // 所有权(Ownership)
  25. let s1 = String::from("hello"); // s1 获取了字符串 "hello" 的所有权
  26. takes_ownership(s1); // 所有权转移到函数 takes_ownership
  27. // println!("main 函数:{}", s1); // 错误!因为 s1 的所有权已经被转移
  28. // 借用(Borrowing)
  29. let s2 = String::from("hello"); // s2 获取了字符串 "hello" 的所有权
  30. borrows_immutable(&s2); // 不可变借用,所有权仍在 s2
  31. println!("main 函数(不可变借用后):{}", s2);
  32. let mut s3 = String::from("hello"); // s3 获取了字符串 "hello" 的所有权,并且是可变的
  33. borrows_mutable(&mut s3); // 可变借用,所有权仍在 s3,但内容已经被修改
  34. println!("main 函数(可变借用后):{}", s3);
  35. // 生命周期(Lifetime)
  36. let str1 = "Rust";
  37. let str2 = "Programming";
  38. let result = longest(str1, str2);
  39. println!("更长的字符串是:{}", result); // 输出 "更长的字符串是:Programming"
  40. }

执行结果

  1. 函数内部:hello
  2. 函数内部(不可变借用):hello
  3. main 函数(不可变借用后):hello
  4. 函数内部(可变借用):hello, world!
  5. main 函数(可变借用后):hello, world!
  6. 更长的字符串是:Programming

六、const 和 let 的区别

1. 可变性(Mutability)

  • let: 默认情况下,使用 let 声明的变量是不可变的,但您可以使用 mut 关键字来使其可变。

    1. let x = 5; // 不可变
    2. let mut y = 6; // 可变
  • const: 使用 const 声明的常量始终是不可变的,并且不能使用 mut

    1. const X: i32 = 5; // 始终不可变

2. 类型注解

  • let: 可以选择是否添加类型注解。

    1. let x = 5; // 类型推断为 i32
    2. let y: i64 = 6; // 显示类型注解
  • const: 必须添加类型注解。

    1. const X: i32 = 5; // 必须提供类型

3. 初始化表达式

  • let: 可以使用任何类型的表达式进行初始化。

    1. let x = 5 + 5; // 算术表达式
  • const: 只能使用常量表达式进行初始化。

    1. const X: i32 = 5 + 5; // 常量表达式,但不能是函数调用、运行时计算等

4. 作用域和生命周期

  • let: 局部变量,作用范围仅限于声明它的代码块。
  • const: 可以在模块级别使用,生命周期可跨越整个程序。

5. 内联

  • const: 在编译时,常量的值会被直接内联到使用它的表达式中。
  • let: 取决于编译器优化。

总体来说,const 主要用于那些在编译时就能确定并且永远不会改变的值,而 let 则用于运行时可能会改变的值。希望这能帮助您更好地理解这两者之间的区别!

发表评论

表情:
评论列表 (有 0 条评论,73人围观)

还没有评论,来说两句吧...

相关阅读