3. rust语法进阶

一、结构体

上一章介绍了Rust中的一些基本语法,本章继续深入学习Rust中的一些高级特性。

首先要介绍的就是结构体,它的作用与上一章介绍的元组类似,可以用来存放一系列有关联、不同类型的数据。

如果你了解C/C++,那基本与C/C++中的结构体一致,或者与python中的字典类似。

使用方法如下:

struct Student{
    name:String,
    age:u32,
    sex:char,
}
fn main() {
    let s=Student{
        name:String::from("yushi-"),
        age:100,
        sex:'男'
    };

   println!("{} {} {}", s.name, s.age,s.sex);
}

首先是声明一个结构体,需要用struct关键字,后面紧跟结构体的名称,这里我给它取名为Student,因为要用它来保存学生的信息。

struct Student{
    name:String,
    age:u32,
    sex:char,
}

随后就是一对大括号,而大括号中的内容就是我们这个结构体的各种属性。

比如这里我们想要保存一个学生的信息,就有姓名、年龄、性别等等,也就是这里的nameagesex

由于这种声明类型rust的编译器无法为我们自动推断各个属性的类型,所以需要在其后面手动添加类型注解。

比如上面的:String:u32以及:char,分别代表字符串类型、无符号整数类型、以及字符类型

而各个属性之间用,分隔,一般我们习惯于将各个属性分行写,更好看。

这里出现了一个字符串类型String,前面没有提到过。

之所以没有提到,一是因为它并不是rust中的基本类型,二是因为Rust中的字符串类型非常复杂与强大,需要后面专门花一章对它介绍才能将其讲清楚,所以这里就暂时先用着,只需要知道它是保存字符串的就行了。

比如"hello world"这种,用双引号包裹起来的就是字符串,但在Rust中又有一点不一样,我们后面再讨论。

声明好了这个结构体后,我们就可以像普通的类型那样,用它来定义一个变量:

    let s=Student{
        name:String::from("yushi-"),
        age:100,
        sex:'男'
    };

与声明这个结构体时的写法类似,只是去除了前面的struct关键字,然后把大括号中的各个属性值后面的类型注解,更换为你要给它们赋的值就好了!

注意,各个内部属性的赋值顺序是可以任意调换的,比如可以先给age字段赋值,然后再给nama字段赋值,但必须全部赋值

执行完上面的代码后,s变量就是一个我们自己的Student类型的变量了。

注意String类型中name属性的赋值方式,需要调用String里面的from方法将一个字符串转换为String

这就是Rust中比较独特的方式,虽然看起来比较繁琐,但到后面你明白了它的原理,应该就能理解它这样做的理由了。

如果要访问这个变量中的各个字段,也非常的简单,只需要用.即可。

println!("{} {} {}", s.name, s.age,s.sex);

比如这里我就用.的方式访问它内部的三个字段,并进行了输出。

当然了,它默认同样是不可修改的,如果你想要对它进行修改,请使用let mut

上面内容是结构体中最常见的用法,但事实上结构体还有其它形式,在一些开源代码中可能会经常看到。

比如你完全可以声明一个空的结构体:

struct S;

虽然此时它内部没有任何属性、看起来没什么用处。

但当你学习了本章后面的“方法”后,就会发现它在某些时候还是有点用处的。

比如某些工具函数,本身并不需要什么属性,但为了方便调用,就可以将其绑定到指定的空结构体上。

除此之外,如果你的结构体字段并不需要“名字”,那么其实你可以像写元组那样写结构体的,就像下面这样:

struct S(u32);

fn main() {
    let t = S(10);
    println!("{}", t.0);
}

其使用方法和前面提到的元组非常像。

这种用法在一些开源库中用的尤其多,比如在windows这个crate中,很多源码的结构体都是通过这种方式定义的。

因为像HANDLE这样的自定义类型实际上就是一个指针的重定义别名,同时又需要为其添加一些方法,所以这样定义非常合适这样的场景。

二、枚举基础

如果你学过其它语言,那么上面的结构体你应该并不陌生,甚至对于这里要说的枚举,也会有一定熟悉的感觉。

枚举最主要的作用是:限制输入选项。

比如IP地址,目前来说最常用的就Ipv4IPv6两种,你不可能再凭空创建一种。

再比如,一周是周一到周日的七天,你也不能凭空再创造一个周八。

rust中的枚举相比于其它语言又非常的不同,下面我们来看一看rust中的枚举是如何使用的:

enum IP{
    Ipv4,
    Ipv6
}
fn main() {
    let mut m:IP; //声明一个IP类型的变量
    m=IP::Ipv4; //正确
    m=IP::Ipv6; //正确
    //m=11; //错误 
}

可以看到枚举的定义方式与上面提到的结构体非常相似,只是将struct关键字改为了enumenumeration的缩写),后面跟着这个枚举类型的名称。

为了更好的体现出rust中枚举的特性,这里以IP为例,IP地址只有两种类型,所以我就在枚举中只写了两个类型:ipv4ipv6

这个名字是随便写的,不过一般会为其取一些有意义的名字,如果选用一周七天作为一个枚举类型,那其中的就可以写七种类型,分别为周一到周七。

接着,我在main函数中,声明了一个IP类型的枚举变量。

然后最重要的一部分就出现了,这个变量将只能等于IP这个枚举内部所写的类型,如果你等于其它类型的,比如数字11,就会出错。

取出其内部的枚举变量通过::的方式,比如这里的IP::ipv4,就是取出其中的ipv4

这有什么用?如上面所说,它的作用就是将一个变量限制到一个范围,便于我们比较、选择。

比如上面提到的IP地址,如果是IPv4地址类型,我就得用点分十进制的方式写ip地址,比如192.168.0.1

而如果是ipv6,那就必须得用十六进制的方式书写地址,比如:fdb8:27c5:221:5400:2c95:a7fc:5d57:a375

这两者是很不一样的,并且也不会出现第三种类型,这在函数传参时非常的有用

fn test_ip(t:IP) {
}

如上面这个函数一样,只要我将参数的类型改为IP类型,那这个时候,外面将只能传递进来两种类型,要么是IPv4,要么是IPv6,不会有第三种情况。

如果想要判断它具体是哪种类型,就必须用到rust中的match语句(意为匹配的意思)

注意,如果你学过其它语言,比如C/C++等,可能潜意识里会用if来进行判断,但这样直接判断是错误的,后面我会提及。

匹配代码如下:

    match t {
        IP::Ipv4 => {
            println!("传入的是IPv4类型");
        }
        IP::Ipv6 =>{
            println!("传入的是IPv6类型");
        }
    };

也就是使用match这个关键字。后面紧跟你要匹配的类型变量,这里就是函数传入的参数t

然后在后面的大括号中,就要分别为其所有可能的类型书写分支,比如这里IP只有两个类型,所以大括号中只有两个分支:

    IP::Ipv4 => {
        //分支ipv4
    }, // 不同分支用‘,’分隔
    IP::Ipv6 =>{
       //分支ipv6
    }

注意每条分支由三部分组成:

  1. 分支类型
  2. =>符号(作为分支类型与所属代码的分隔符)
  3. {}中的代码(如果匹配这个分支,就会执行里面的代码)

注意{}并不是必须的,如果该分支内部只有一条语句,那么{}可以省略:

match t {
    IP::Ipv4 => println!("传入的是IPv4类型"),
作者:余识
全部文章:0
会员文章:0
总阅读量:0
c/c++pythonrustJavaScriptwindowslinux