Rust学习笔记-3-基础篇:表达式、语句、控制流、注释

前言

承接前文,本文主要对Rust基础概念中关于表达式、语句、控制流、注释、函数的知识点进行梳理。

表达式

  • Rust is primarily an expression language
  • Rust的表达式包括字面量表达式、方法调用表达式、数组表达式、 索引表达式、单目运算符表达式、双目运算符表达式等。
  • Rust表达式又 可以分为“左值”(lvalue)和“右值”(rvalue)两类。所谓左值,意思是 这个表达式可以表达一个内存地址。因此,它们可以放到赋值运算符左边使用。其他的都是右值。
  • 算术运算符包括:加(+)、减(-)、乘(*)、除(/)、 求余(%);比较运算符包括:等于(==)、不等于(!=)、小于 (<)、大于(>)、小于等于(<=)、大于等于(>=);位运算符包括:按位取反(!)、按位与(&)、按位或(|)、按位异或(^)、左移(<<)、右移(>>);逻辑运算符包括:逻辑取反(!)、逻辑与(&&)、逻辑或(||)。
  • 编译器会禁止此形式的连续比较:a==b==c。连续比较运算符必须加上括号。
  • 逻辑与、逻辑或有短路功能。按位与、按位或不会短路。
  • 比较运算符的两边必须是同类型的,并满足PartialEq约束。比较表达式的类型是bool
  • 赋值表达式:一个左值表达式、赋值运算符(=)和右值表达式,可以构成一个赋值表达式。赋值表达式具有“副作用”:当它执行的时候,会把右边表达式的 值“复制或者移动”(copy or move)到左边的表达式中。赋值号左右两边表达式的类型必 须一致,否则是编译错误。
  • 赋值表达式也有对应的类型和值。Rust规定,赋值表达式的类型为unit,即空的tuple()。如下例,输出是()。Rust这么设计是有原因的,比如说可以防止连续赋值。
    #![allow(unused)]
    fn main() {    
        let x = 1;    
        let mut y = 2;    
        // 注意这里专门用括号括起来了    
        let z = (y = x);    
        println!("{:?}", z); 
    }
  •  Rust也支持组合赋值表达式。但不支持++、--运算符,请使用+=1、-=1替代。
  • 语句块表达式:在Rust中,语句块也可以是表达式的一部分。语句和表达式的区分 方式是后面带不带分号(;)。如果带了分号,意味着这是一条语句, 它的类型是();如果不带分号,它的类型就是表达式的类型。所以在函数中,我们也可以利用这样的特点来写返回值。这种写法用在后面讲到的闭包closure中,会方便轻量(无需写return)。
    // 语句块可以是表达式,注意后面有分号结尾,x的类型是() 
    let x : () = { println!("Hello."); };
    // Rust将按顺序执行语句块内的语句,并将最后一个表达式类型返回,y的类型是 i32 
    let y : i32 = { println!("Hello."); 5 };
    

注:

1,查了下PartialEq,可参考:https://segmentfault.com/a/1190000011929777。 简言之:PartialEq定义了部分相等,且这种相等具有对称性(a==b则b==a)、传递性(a==b且b==c,则a==c)。但它没有反身性(即a==a不成立。作为对比,Eq具有反身性)。回想前文的浮点数,NAN!=NAN,所以浮点数只实现了PartialEq。

2,rust的运算符,实现了一些trait。要进行自定义的运算符重载,也是实现这些trait。可参考阅读:https://blog.csdn.net/u010824081/article/details/78447453

语句

  •  一个表达式总是会产生一个值,因此它必然有类型;语句不产生值,它的类型永远是()。如果把一个表达式加上分号,那么它就变成 了一个语句;如果把语句放到一个语句块中包起来,那么它就可以被当 成一个表达式使用。
  • 语句Statements)是执行一些操作但不返回值的指令。表达式Expressions)计算并产生一个值。
  • 使用 let 关键字创建变量并绑定一个值是一个语句。函数定义也是语句。
    //函数定义本身就是一个语句
    fn main() {
        let y = 6; //这是一个语句
    }
  • 语句不返回值。 看下面的示例:

    fn main() {
        let x = (let y = 6); //编译错误。let y = 6 是语句,并不返回值。
    }
  • 函数调用是一个表达式;宏调用是一个表达式;我们用来创建新作用域的大括号(代码块),{},也是一个表达式。

  • 表达式的结尾没有分号。再看个例子:

    fn main() {
        let x = 5;
    
        let y = {
            let x = 3;
            x + 1 //这是一个表达式。如果在结尾加上分号,它就变成了语句,而语句不会返回值。
        };
    
        println!("The value of y is: {}", y);
    }
    

控制流

条件分支:if-else、if-else if-...-else。其中else可以省略。

  • 条件必须是bool值。else if 如果比较多,可以考虑用match。以下是个示例:
    fn main() {
        let number = 6;
    
        if number % 4 == 0 {
            println!("number is divisible by 4");
        } else if number % 3 == 0 {
            println!("number is divisible by 3");
        } else if number % 2 == 0 {
            println!("number is divisible by 2");
        } else {
            println!("number is not divisible by 4, 3, or 2");
        }
    }
  • 在if语句中,后续的结果语句块要求一定要用大括号包起来,不能省略,以便明确指出该if语句块的作用范围。这个规定是为了避免“悬空 else”导致的bug。
  • 条件表达式并未强制要求用小括号包起来;如果加上小括 号,编译器反而会认为这是一个多余的小括号,给出警告。(试了下的确如此)
  • if-else结构还可以当表达式使用。
    fn main() {
        let condition = true;
        let number = if condition {
            5
        } else {
            6
        };
    
        println!("The value of number is: {}", number);
    }
  • if 分支和 else 分支的结果,其类型必须匹配。

循环:loopwhile 和 for

  • loop是个无限循环。可以用break或者continue跳出。与if结构一样,loop结构也可以作为表达式的一部分。以下是示例:
    fn main() {
        let mut counter = 0;
    
        let result = loop {
            counter += 1;
    
            if counter == 10 {
                break counter * 2; //跳出时可以返回值
            }
        };
    
        println!("The result is {}", result); //输出20
    }
  • break语句和continue语句还可以在多重循环中选择跳出到哪 一层的循环。我们可以在loop while for循环前面加上“生命周期标识符”。该标识符以单引号开头,在内部的循环中可以使用break语句选择跳出到哪一层。以下是示例。

    fn main() {
        // A counter variable    
        let mut m = 1;    
        let n = 1;
        'a: loop {
            if m < 100 {
                m += 1;
            } else {
                'b: loop {
                    if m + n > 50 {
                        println!("break");
                        break 'a;
                    } else {
                        continue 'a;
                    }
                }
            }
        }
    }
  • 如果一个loop永远不返回,那么它的类型就是“发散类型”。示例如下:

    fn main() {
        let v = loop {};
        println!("{}", v);
    }
  •  while语句中也可以使用continue和break来控制循环流程。 

  • 既然有while,为何还要loop?书中这么解释的:“主要原因在于,相比于其他的许多语言,Rust语言要 做更多的静态分析。loop和while true语句在运行时没有什么区别,它们主要是会影响编译器内部的静态分析结果”。看如下示例: 

    //编译可以通过
    let x; 
    loop { x = 1; break; } 
    println!("{}", x) 
    
    //编译通过不了。因为编译器会觉得while语句的执行跟条件表达式在运行阶段的值 有关,因此它不确定x是否一定会初始化
    let x; 
    while true { x = 1; break; } 
    println!("{}", x);
    

注:

坦白讲,即使看了这样的解释,还没想明白哪个应用场景非用loop不可。感觉还真有点多余。后面如果有了新的理解,再更新这里。

  • Rust中的for循环实际上是许多其他语言中的for-each循环。Rust中没 有类似C/C++的三段式for循环语句。以下是个示例。
    fn main() {
        let a = [10, 20, 30, 40, 50];
    
        for element in a.iter() {
            println!("the value is: {}", element);
        }
    }
    
  • for循环的主要用处是利用迭代器对包含同样类型的多个元素的容器 执行遍历,如数组、链表、HashMap、HashSet等。在Rust中,我们可以 轻松地定制自己的容器和迭代器,因此也很容易使for循环也支持自定义 类型。
  • for循环内部也可以使用continue和break控制执行流程。 

注释

  • 分为代码层面的注释和文档注释。
  • 代码注释的格式:一种是用//开头的,是行注释。一种是/**/,是块注释。
  • 文档注释的格式:///、//!、/**…*/、/*!…*/,
  • 用///开头的文档被视为是给它后面的那个元素做的说明;//!开头的文档被视为是给包含这块文档的元素做的说 明。/**…*/和/*!…*/也是类似的。
    mod foo {
        //! 这块文档是给 `foo` 模块做的说明
        /// 这块文档是给函数 `f` 做的说明    
        fn f() {
            // 这块注释不是文档的一部分
        }
    }
    
  • 文档内部支持markdown格式。可以使用#作为一级标题。比如标准 库中常用的几种标题:

    /// # Panics 
    /// # Errors 
    /// # Safety 
    /// # Examples
  • 文档中的代码部分要用``符号把它们括起来。代码块应该用```括起 来。示例如下。Rust文档里面的代码块,在使用cargo test命令时,也是会被当做测 试用例执行的。这个设计可以在很多时候检查出文档和代码不对应的情况。

    /// ```    
    /// let mut vec = Vec::with_capacity(10);
    ///    
    /// // The vector contains no items, even though it has capacity for more    
    /// assert_eq!(vec.len(), 0);    
    /// ```
    
  • 如果文档太长,也可以写在单独的markdown文件中。如果在单独 的文件中写文档,就不需要再用///或者//!开头了,直接写内容就可 以。然后再用一个attribute来指定给对应的元素:

    #![feature(external_doc)]
    #[doc(include = "external-doc.md")] 
    pub struct MyAwesomeType;

    注:

    利用cargo 创建文档注释的html、文档测试等相关知识点,这里跳过。后面和cargo一起说明。列个TODO项。

你可能感兴趣的:(rust,rust)