Rust入坑-数据类型与使用

43 阅读11分钟

变量声明

注解

Rust中使用let关键词标识,未添加mut关键词修饰定义的变量不可修改进行

fn main() {
    let user_name = "1111";
    user_name = "2222"; //会报错
 
    let mut user_name = "111";
    user_name = "222"; //可正常运行
 
    //上面的例子均为rust自动匹配类型 也可以指定类型声明 格式如下
    let age: i32 = 18;
    let mut age: i32 = 18;
}

数值类型

注解
  1. Rust默认将整数当作i32类型,浮点数当作f64类型。
  2. 每种数值类型都有所能存储的最大数值和最小数值。当超出类型的有效范围时,Rust将报错(panic)
  3. Rust允许使用0b 0o 0x来表示二进制、八进制和十六进制的整数。
  4. 数值类型之间默认不会隐式转换,如果需要转换数值类型,可手动使用as进行转换
fn main() {
    let a: i64 = 1000000000000; //0太多不好看 可以使用_来划分数值
    let b: i64 = 1000_000_000_000;
    println!("a = {}, b = {}", a, b);
    assert_eq!(a, b);
    //类型转换
    assert_eq!(10_i8 as u16, 10_u16);
}

布尔类型

注解

Rust的布尔值可以使用as操作符转换为各种数值类型,false对应0,true对应1

fn main() {
    let flag = true;
    println!("{}", flag as u32);
    println!("{}", false as u8);
}

char类型

注解
  1. 单引号包围的任意单个字符,注意:char和单字符的字符串String是不同的类型。
  2. 可使用as将u8类型转char,不支持其他整数类型的值可能无法转换为char(UTF-8编码表范围的整数值)
fn main() {
 
    let my = '我';
 
    println!("{}", my);
}

字符串Str与String

注解
  1. Rust有两种字符串类型:Str和String。
  2. Str是String的切片类型
  3. Str类型的字符串值是String类型的字符串值的一部分或全部。
  4. String类型的字符串没有对应的字面量构建方式,只能通过Rust提供的方法来构建。
  5. Str类型的字符串和String类型的字符串是有联系的:Str字符串是String类型字符串的切片(Slice)类型
fn main() {
    let s = "hello world!"; //rust自动推导为 &str类型
    //等价与 let s: &str = "hello world!";
    //&str表示的是一个指向内存中str类型数据的指针,
    //该指针所指向的内存位置处保存了字符串数据"hello world!"。
    println!("{}", s);
}
fn main() {
    // 类型自动推导为: String
    let s = String::from("hello world");
    let s1 = "hello world".to_string();
    println!("{},{}", s, s1);
 
    //String类型的字符串可以原地修改,而&str类型的字符串不可以原地修改。
    let mut s2 = String::from("hello world");
    s2.push('!');
    s2.push_str("hello");
    println!("{}", s2);
}

Str和String的联系和区别

fn main() {
    let s = String::from("hello_world");
    // 自动推导数据类型为&str
    //   s[0..3]的类型为str
    //  &s[0..3]的类型为&str
    // 等价于&(s[0..3])而不是(&s)[0..3]
    // 现在s_str通过胖指针引用了源String字符串中的局部数据
    let s_str = &s[0..3];
 
    println!("{}", s_str); // 输出:hel
    //编译器对字符串字面量做了特殊处理:
    //编译器编译的时候直接将字符串字面量以硬编码的方式写入程序二进制文件中,
    //当程序被加载时,字符串字面量被放在内存的某个位置(不在堆中也不在栈中,而是在类似于静态数据区的全局字面量区)。
    //当程序执行到let s="hello";
    //准备将其赋值给变量s时(注:s在栈上),直接将字面量内存区的该数据地址保存到&str类型的s中。
}

tuple类型

注解

tuple类型可以存放0个、1个或多个任意数据类型的数据。使用tup.N的方式可以访问索引为N的元素。

fn main() {
    let list = (11, 22, 33);
    println!("{}", list.0);
    println!("{}", list.1);
    println!("{}", list.2);
 
    // 同时存放&str和i32类型的数据
    let p = ("hello", 123);
    println!("{}, {}", p.0, p.1);
 
    //类型推导
    let (str, num): (&str, i32) = p;
    println!("{}, {}", str, num);
 
    //如果只有一个元素 需要保留逗号不可省略
    let t = ("hello",);
    println!("{:?}", t);
 
    //不保存任何数据的tuple表示为()。
    //在Rust中,它是特殊的,它有自己的类型:unit。
    let x: () = ();
    println!("{:?}", x);
}

array类型

注解
  1. 数组的数据类型表示方式为[Type; N]
  2. Type是该数组要存储什么类型的数据,数组中的所有元素类型都必须是Type
  3. N是数组的长度,Rust不会自动伸缩数组的长度, 需要在编译期间就能确认的
fn main() {
    
    let arr: [&str; 3] = ["1", "2", "3"];
    println!("{:?}", arr);
 
    //自动推导类型为:[i64; 1024]
    // 该数组初始化为1024个i64类型的0
    // 可将之当作以0填充的1K的buf空间
    let arr1 = [0_i64; 1024];
    println!("{:?}", arr1);
    //for变量需要使用&arr或arr.iter()
    for i in arr1.iter() {
        println!("{}", i);
    }
 
    for i in &arr1 {
        println!("{}", i);
    }
 
    println!("arr1 len is {}", arr1.len())
}

引用类型

注解
  1. 使用&T表示类型T的引用类型
  2. 引用类型是一种数据类型,它表示其所保存的值是一个引用
  3. 通常来说是指向其他数据的一个指针或一个胖指针(有额外元数据的指针)。
  4. 引用类型保存值的引用
fn main() {
    let x: &i32 = &5_i32;
    println!("x = {}", x);
 
    //可以赋值给多个变量
    let t = 1;
    let t1 = &t;
    let t2 = &t;
    //使用std::ptr::eq()来判断两个引用是否指向同一个地址,即判断所指向的数据是否是同一份数据。
    println!("{}", std::ptr::eq(t1, t2));
 
    //直接使用&创建出来的引用是只读的
    //想要通过引用去修改源数据,需要使用&mut v来创建可修改源数据v的可变引用
    //通过引用获取到该引用所指向的原始值。
    //解引用使用*T表示
    let mut n = 33;
    let n_ref = &mut n;
    n = *n_ref + 1;
    println!("n = {}", n);
 
    //Rust绝大多数时候不会自动地解除引用.
    //自动解引用的情况有
    //(1).使用.操作符时(包括取属性值和方法调用),会隐式地尽可能解除或创建多层引用
    //例如以abc.func()有可能会自动转换为&abc.func(),反之,&abc.func()也有可能会自动转换为abc.func()。
 
    //(2).使用比较操作符时,若比较的两边是相同类型的引用,则会自动解除引用到它们的值然后比较
    //例如有引用类型的变量n,那么n > &30和*n > 30的效果是一样的。
}

slice类型

注解
  1. Rust中的切片操作只允许获取一段连续的局部数据,切片操作获取到的数据称为切片数据。
  2. 有三种类型已支持Slice操作:String类型、Array类型和Vec类型
fn main() {
    // s[n1..n2]:获取s中index=n1到index=n2(不包括n2)之间的所有元素
    // s[n1..]:获取s中index=n1到最后一个元素之间的所有元素
    // s[..n2]:获取s中第一个元素到index=n2(不包括n2)之间的所有元素
    // s[..]:获取s中所有元素
    // 其他表示包含范围的方式,如s[n1..=n2]表示取index=n1到index=n2(包括n2)之间的所有元素
 
    //切片操作允许使用usize类型的变量作为切片的边界。例如,n是一个usize类型的变量,那么s[..n]是允许的切片操作。
 
    // Slice类型是一个胖指针,它包含两份元数据:
    // 第一份元数据是指向源数据中切片起点元素的指针
    // 第二份元数据是切片数据中包含的元素数量,即切片的长度
 
    let mut arr = [1, 2, 3, 4, 5];
 
    let arr_slice = &arr[..=1];
    println!("{:?}", arr_slice);
 
    let arr_slice2 = &mut arr[..=1];
    arr_slice2[0] = 11;
    
    println!("{:?}", arr_slice2);
    println!("{:?}", arr);
}

vec类型

注解

Rust中数组的长度不可变,这是很受限制的。 Rust在标准库中提供了Vector类型(向量)。Vec类型和数组类型的区别在于前者的长度动态可变。

fn main() {
    // 创建向量有几种方式:
    // Vec::new()创建空的vec
    // Vec::with_capacity()创建空的vec,并将其容量设置为指定的数量
    // vec![]宏创建并初始化vec(中括号可以换为小括号或大括号)
    // vec![v;n]创建并初始化vec,共n个元素,每个元素都初始化为v
 
    let mut v1 = Vec::new();
    v1.push(1);
    v1.push(2);
    v1.push(3);
    assert_eq!(v1, [1, 2, 3]); //vec类型可以直接比较数组
 
    let v2 = vec![1, 2, 3];
    assert_eq!(v2, [1, 2, 3]);
 
    let v3 = vec![3; 3]; // 等价于vec![3,3,3]
    assert_eq!(v3, [3, 3, 3]);
 
    let mut v4 = Vec::with_capacity(10);
    v4.push(33);
    println!("{:?}", v4);
 
    //索引是usize类型的值,因此不接受负数索引。
    let index: usize = 1;
    println!("{}", v1[index]); //访问不存在的下标会报错 避免这个问题可以使用get或get_mut方法
 
    println!("{}", v1.get(10).is_none());
    println!("{}", v1.get_mut(10).is_none());
 
    //遍历
    for i in v1 {
        println!("{}", i);
    }
}
vec扩容机制
注解

当向vec插入新元素时,如果没有空闲容量,则会重新申请一块内存,大小为原来vec内存大小的两倍(官方手册指明目前Rust并没有确定扩容的策略,以后可能会改变),然后将原vec中的元素拷贝到新内存位置处,同时更新vec的胖指针中的元数据。

vec使用枚举存储多个类型
#[derive(Debug)]
enum SpreadsheetCell {
    Int(i32),
    Float(f64),
    Text(String),
}
 
fn main() {
    let row = vec![
        SpreadsheetCell::Int(3),
        SpreadsheetCell::Text(String::from("blue")),
        SpreadsheetCell::Float(10.12),
    ];
    println!("{:?}", row);
}

struct类型

注解
  1. Struct类型类似于面向对象的类,Struct的实例则类似于对象。
  2. Struct的实例和面向对象中的对象都可以看作是使用key-value模式的hash结构去存储数据,同时附带一些其他功能。
//Struct调试输出
//需要在对应struct前面添加宏#[derive(Debug)],自动生成实现Debug trait 的代码
#[derive(Debug)]
struct Class {
    name: String,
    num: i32,
}
//使用impl 给strcut添加方法
impl Class {
    fn new(name: String, num: i32) -> Class {
        Class { name, num }
    }
 
    fn get_name(&self) -> &String {
        &self.name
    }
 
    fn get_num(&self) -> i32 {
        self.num
    }
}
 
fn main() {
    let class1 = Class::new("一班".to_string(), 20);
 
    println!("{},{}", class1.name, class1.num);
 
    println!("{},{}", class1.get_name(), class1.get_num());
 
    //直接定义对应的变量名 可以简化赋值初始化
    let name = String::from("二班");
    let num = 30;
    let class2 = Class { name, num };
    println!("{:?}", class2);
 
    //有时候会基于一个Struct实例构造另一个Struct实例,Rust允许通过..xx的方式来简化构造struct实例的过程
    //此代码class2中的num字段所有权借用,再读取class2中的num会报错
    //如果确实需要使用 可以使用clone()方法 ..class2.clone()
    let class3 = Class {
        name: String::from("三班"),
        ..class2
    };
    println!("{:?}", class3);
 
    //tuple struct
    struct Color(i32, i32, i32);
    let color = Color(1, 2, 3);
    println!("{},{},{}", color.0, color.1, color.2);
 
    //unit-like struct
    #[derive(Debug)]
    struct St;
    let st = St;
    println!("{:?}", st);
}

enum类型

注解
  1. 枚举(Enum)类型通常用来归纳多种可穷举的具体事物。
  2. 枚举是一种包含零个、一个或多个具体值的数据类型。
//可以使用属性来约束大小
//比如 #[repr(u8)] 限定范围为`0..=255`
enum Week {
    Monday = 1, // 1
    Tuesday,    // 2
    Wednesday,  // 3
    Thursday,   // 4
    Friday,     // 5
    Saturday,   // 6
    Sunday,     // 7
}
 
//实现Display trait 方便打印查看
impl std::fmt::Display for Week {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            Week::Monday => write!(f, "星期一"),
            Week::Tuesday => write!(f, "星期二"),
            Week::Wednesday => write!(f, "星期三"),
            Week::Thursday => write!(f, "星期四"),
            Week::Friday => write!(f, "星期五"),
            Week::Saturday => write!(f, "星期六"),
            Week::Sunday => write!(f, "星期日"),
        }
    }
}
 
//自定义枚举方法
impl Week {
    fn is_monday(self) -> bool {
        match self {
            Week::Monday => true,
            _ => false,
        }
    }
}
 
fn main() {
    println!("Monday is {}", Week::Monday);
    println!("Tuesday is {}", Week::Tuesday);
    println!("Wednesday is {}", Week::Wednesday);
    println!("Thursday is {}", Week::Thursday);
    println!("Friday is {}", Week::Friday);
    println!("Saturday is {}", Week::Saturday);
    println!("Sunday is {}", Week::Sunday);
 
    let mon = Week::Monday;
    println!("{}", mon.is_monday());
 
    let tue = Week::Tuesday;
    println!("{}", tue.is_monday());
}

HashMap

//需要引入内置的HashMap
use std::collections::HashMap;
 
fn main() {
    //初始化一个HashMap
    let mut test = HashMap::new();
    //随便插入点数据 hashmap 使用了泛型 key或val不同类型编辑器会报错
    test.insert("name", "John".to_string());
    test.insert("age", "18".to_string()); //相同的key不同的值会被覆盖
    test.insert("age", "19".to_string());
    //打印这个HashMap
    println!("{:?}", test);
 
    //仅当键不存在时才添加键和值
    test.entry("name1").or_insert("John1".to_string());
 
    //循环打印这个HashMap
    for (k, v) in test.iter() {
        println!("{}: {}", k, v);
    }
 
    //利用HashMap统计
    let text = "a b a c";
    let mut sum = HashMap::new();
    for word in text.split_whitespace() {
        let count = sum.entry(word).or_insert(0); //返回对应的指针val
        *count += 1; //使用星号取消引用, 引用在循环结束时超出了范围
    }
    println!("{:?}", sum);
 
    let name = String::from("John");
    test.insert("name", name);
    println!("{:?}", name); //不能打印name 因为name变量的所有着被移动到test哈希映射中
}