rust 学习笔记 04

Rust语言关键字

  • let
  • mut
  • fn
  • const
  • if
  • else

变量和常量

变量和常量的区别

不允许改变值的变量,可能会使你想起另一个大部分编程语言都有的概念:常量constants)。类似于不可变变量,常量是绑定到一个名称的不允许改变的值,不过常量与变量还是有一些区别。
首先,不允许对常量使用 mut。常量不光默认不能变,它总是不能变。
声明常量使用 const 关键字而不是 let,并且 必须 注明值的类型。在下一部分,“数据类型” 中会介绍类型和类型注解,现在无需关心这些细节,记住总是标注类型即可。
常量可以在任何作用域中声明,包括全局作用域,这在一个值需要被很多部分的代码用到时很有用。
最后一个区别是,常量只能被设置为常量表达式,而不能是函数调用的结果,或任何其他只能在运行时计算出的值。

隐藏

我们可以定义一个与之前变量同名的新变量,而新变量会 隐藏 之前的变量。Rustacean 们称之为第一个变量被第二个 隐藏 了,这意味着使用这个变量时会看到第二个值。可以用相同变量名称来隐藏一个变量,以及重复使用 let 关键字来多次隐藏
隐藏与将变量标记为 mut 是有区别的。当不小心尝试对变量重新赋值时,如果没有使用 let 关键字,就会导致编译时错误。通过使用 let,我们可以用这个值进行一些计算,不过计算完之后变量仍然是不变的。
mut 与隐藏的另一个区别是,当再次使用 let 时,实际上创建了一个新变量,我们可以改变值的类型,但复用这个名字。

数据类型

标量(scalar)类型代表一个单独的值。Rust 有四种基本的标量类型:整型、浮点型、布尔类型和字符类型。

整型

长度 有符号 无符号
8-bit i8 u8
16-bit i16 u16
32-bit i32 u32
64-bit i64 u64
128-bit i128 u128
arch isize usize

Rust 中的整型字面值

数字字面值 例子
Decimal (十进制) 98_222
Hex (十六进制) 0xff
Octal (八进制) 0o77
Binary (二进制) 0b1111_0000
Byte (单字节字符)(仅限于u8) b'A'

整型溢出

比方说有一个 u8 ,它可以存放从零到 255 的值。那么当你将其修改为 256 时会发生什么呢?这被称为 “整型溢出”(“integer overflow” ),关于这一行为 Rust 有一些有趣的规则。当在 debug 模式编译时,Rust 检查这类问题并使程序 panic,这个术语被 Rust 用来表明程序因错误而退出。第九章 “panic! 与不可恢复的错误” 部分会详细介绍 panic。

在 release 构建中,Rust 不检测溢出,相反会进行一种被称为二进制补码包装(two’s complement wrapping)的操作。简而言之,256 变成 0,257 变成 1,依此类推。依赖整型溢出被认为是一种错误,即便可能出现这种行为。如果你确实需要这种行为,标准库中有一个类型显式提供此功能,Wrapping。

浮点型

Rust 也有两个原生的 浮点数(floating-point numbers)类型,它们是带小数点的数字。Rust 的浮点数类型是 f32 和 f64,分别占 32 位和 64 位。默认类型是 f64,因为在现代 CPU 中,它与 f32 速度几乎一样,不过精度更高。

数值运算

#![allow(unused)]
fn main() {
    // 加法
    let sum = 5 + 10;

    // 减法
    let difference = 95.5 - 4.3;

    // 乘法
    let product = 4 * 30;

    // 除法
    let quotient = 56.7 / 32.2;

    // 取余
    let remainder = 43 % 5;
}

布尔型

true / false

字符类型

fn main() {
    let c = 'z';
    let z = 'ℤ';
    let heart_eyed_cat = '😻';
}

复合类型(Compound types)可以将多个值组合成一个类型。Rust 有两个原生的复合类型:元组(tuple)和数组(array)。

元组类型

fn main() {
    let tup: (i32, f64, u8) = (500, 6.4, 1);
}
#![allow(unused)]
// tup 变量绑定到整个元组上,因为元组是一个单独的复合元素。
// 为了从元组中获取单个值,可以使用模式匹配(pattern matching)来解构(destructure)元组值
fn main() {
    let tup = (500, 6.4, 1);

    let (x, y, z) = tup;

    println!("The value of y is: {}", y);
}
// 程序首先创建了一个元组并绑定到 tup 变量上。接着使用了 let 和一个模式将 tup 分成了三个不同的变量,x、y 和 z。
// 这叫做 解构(destructuring),因为它将一个元组拆成了三个部分。最后,程序打印出了 y 的值,也就是 6.4。
#![allow(unused)]
// 除了使用模式匹配解构外,也可以使用点号(.)后跟值的索引来直接访问它们。
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;
}

数组类型

另一个包含多个值的方式是 数组(array)。与元组不同,数组中的每个元素的类型必须相同。Rust 中的数组与一些其他语言中的数组不同,因为 Rust 中的数组是固定长度的:一旦声明,它们的长度不能增长或缩小。

当你想要在栈(stack)而不是在堆(heap)上为数据分配空间(第四章将讨论栈与堆的更多内容),或者是想要确保总是有固定数量的元素时,数组非常有用。但是数组并不如 vector 类型灵活。vector 类型是标准库提供的一个 允许 增长和缩小长度的类似数组的集合类型。当不确定是应该使用数组还是 vector 的时候,你可能应该使用 vector。

#![allow(unused)]
fn main() {
    let a = [1, 2, 3, 4, 5];
    let months = ["January", "February", "March", "April", "May", "June", "July",
              "August", "September", "October", "November", "December"];
    let a: [i32; 5] = [1, 2, 3, 4, 5];
    let a = [3; 5]; // 变量名为 a 的数组将包含 5 个元素,这些元素的值最初都将被设置为 3。
    // 这种写法与 let a = [3, 3, 3, 3, 3]; 效果相同,但更简洁。
    // 数组是一整块分配在栈上的内存。可以使用索引来访问数组的元素
    let first = a[0];
    let second = a[1];
}

函数

函数遍布于 Rust 代码中。你已经见过语言中最重要的函数之一:main 函数,它是很多程序的入口点。你也见过 fn 关键字,它用来声明新函数。

Rust 代码中的函数和变量名使用 snake case 规范风格。在 snake case 中,所有字母都是小写并使用下划线分隔单词。

fn main() {
    println!("Hello, world!");

    another_function();
}

fn another_function() {
    println!("Another function.");
}

函数参数

fn main() {
    another_function(5);
}

fn another_function(x: i32) {
    println!("The value of x is: {}", x);
}

在函数签名中,必须 声明每个参数的类型。这是 Rust 设计中一个经过慎重考虑的决定:要求在函数定义中提供类型注解,意味着编译器不需要你在代码的其他地方注明类型来指出你的意图。

fn main() {
    another_function(5, 6);
}
// 当一个函数有多个参数时,使用逗号分隔
fn another_function(x: i32, y: i32) {
    println!("The value of x is: {}", x);
    println!("The value of y is: {}", y);
}
fn main() {
    let x = 5;

    let y = {
        let x = 3;
        x + 1
    };

    println!("The value of y is: {}, x is {}", y, x);
}

具有返回值的函数

fn five() -> i32 {
    5
}

fn six() -> i32 {
    return 6;
}

fn main() {
    let x = five();

    println!("The value of x is: {}", x);
    let x = six();

    println!("The value of x is: {}", x);
}
fn main() {
    let x = plus_one(5);

    println!("The value of x is: {}", x);
}

fn plus_one(x: i32) -> i32 {
    println!("The value of x is: {}", x);
    let y = x + 2;
    println!("The value of y is: {}", y);
    x + 1
}

注释

fn main() {
    let x = plus_one(5);

    // 行内注释是这样吧
    println!("The value of x is: {}", x); // 或者这样
}
/**
 * 文档注释是这样吧
 */
fn plus_one(x: i32) -> i32 {
    println!("The value of x is: {}", x);
    let y = x + 2;
    println!("The value of y is: {}", y);
    x + 1
}

控制流

if 表达式

/**
 * 所有的 if 表达式都以 if 关键字开头,其后跟一个条件。
 * 在这个例子中,条件检查变量 number 的值是否小于 5。
 * 在条件为真时希望执行的代码块位于紧跟条件之后的大括号中。
 * if 表达式中与条件关联的代码块有时被叫做 arms,
 * 就像 “比较猜测的数字和秘密数字” 部分中讨论到的 match 表达式中的分支一样。
 * 
 * 也可以包含一个可选的 else 表达式来提供一个在条件为假时应当执行的代码块,这里我们就这么做了。
 * 如果不提供 else 表达式并且条件为假时,程序会直接忽略 if 代码块并继续执行下面的代码。
 */
fn main() {
    let number = 3;

    if number < 5 {
        println!("condition was true");
    } else {
        println!("condition was false");
    }
}

代码中的条件 必须 是 bool 值。如果条件不是 bool 值,我们将得到一个错误。

在 let 语句中使用 if

fn main() {
    let condition = true;
    let number = if condition {
        5
    } else {
        6
    };

    println!("The value of number is: {}", number);
}

代码块的值是其最后一个表达式的值,而数字本身就是一个表达式。在这个例子中,整个 if 表达式的值取决于哪个代码块被执行。这意味着 if 的每个分支的可能的返回值都必须是相同类型

if 代码块中的表达式返回一个整数,而 else 代码块中的表达式返回一个字符串。这不可行,因为变量必须只有一个类型。Rust 需要在编译时就确切的知道 number 变量的类型,这样它就可以在编译时验证在每处使用的 number 变量的类型是有效的。Rust 并不能够在 number 的类型只能在运行时确定的情况下工作;这样会使编译器变得更复杂而且只能为代码提供更少的保障,因为它不得不记录所有变量的多种可能的类型。

错误示例:

fn main() {
    let condition = true;

    let number = if condition {
        5
    } else {
       "six"
    };

    println!("The value of number is: {}", number);
}

使用循环重复执行

多次执行同一段代码是很常用的,Rust 为此提供了多种 循环(loops)。一个循环执行循环体中的代码直到结尾并紧接着回到开头继续执行。为了实验一下循环,让我们新建一个叫做 loops 的项目。

Rust 有三种循环:loop、while 和 for。我们每一个都试试。

使用loop重复执行代码

loop 关键字告诉 Rust 一遍又一遍地执行一段代码直到你明确要求停止。

fn main() {
    loop {
        println!("again!");
    }
}

使用 break 关键字来告诉程序何时停止循环。

loop 的一个用例是重试可能会失败的操作,比如检查线程是否完成了任务。然而你可能会需要将操作的结果传递给其它的代码。如果将返回值加入你用来停止循环的 break 表达式,它会被停止的循环返回:

fn main() {
    let mut counter = 0;

    let result = loop {
        counter += 1;

        if counter == 10 {
            break counter * 2;
        }
    };

    println!("The result is {}", result);
}

while 条件循环

在程序中计算循环的条件也很常见。当条件为真,执行循环。当条件不再为真,调用 break 停止循环。这个循环类型可以通过组合 loop、if、else 和 break 来实现;如果你喜欢的话,现在就可以在程序中试试。

然而,这个模式太常用了,Rust 为此内置了一个语言结构,它被称为 while 循环。

fn main() {
    let mut number = 3;

    while number != 0 {
        println!("{}!", number);

        number = number - 1;
    }

    println!("LIFTOFF!!!");
}

使用 for 遍历集合

fn main() {
    let a = [10, 20, 30, 40, 50];

    for element in a.iter() {
        println!("the value is: {}", element);
    }
}
fn main() {
    for number in (1..4).rev() {
        println!("{}!", number);
    }
    println!("LIFTOFF!!!");
}
  • 综合例子
fn main() {
    loop {
        println!("请输入工具编号:");
        println!("1: 华氏温度转为摄氏度");
        println!("2: 摄氏度转为华氏温度");
        println!("3: 生成 n 阶斐波那契数列");
        println!("4: 打印圣诞颂歌“The Twelve Days of Christmas” 的歌词");
        println!("0: 退出");
        let tn = loop {
            let mut tn = String::new();
            std::io::stdin().read_line(&mut tn).expect("Failed to read line");
            let tn: usize = match tn.trim().parse() {
                Ok(num) => num,
                Err(_) => {
                    println!("输入错误,请重新输入工具编号:");
                    continue;
                },
            };
            if tn == 0 || tn == 1 || tn == 2 || tn == 3 || tn == 4 {
                break tn;
            }
            println!("输入错误,请重新输入工具编号:");
        };
        println!("你输入了: {}",  tn);
        println!();
        if tn == 0 {
            println!("你选择退出, 下次再见咯 ^_^");
            return;
        }
        let tn = tn - 1;
        let tools_arr = ["华氏温度转为摄氏度", "摄氏度转为华氏温度", "生成 n 阶斐波那契数列", "打印圣诞颂歌“The Twelve Days of Christmas” 的歌词"];
        println!("你选择的工具为: {}", tools_arr[tn]);
        println!();
        if tn == 0 {
            translate_to_c();
        } else if tn == 1 {
            translate_to_h();
        } else if tn == 2 {
            nb();
        } else if tn == 3 {
            pc();
        }
        println!("\n\n");
    }
}
/** 
 * 相互转换摄氏与华氏温度。
 * 1 摄氏度=33.8 华氏度
 * 1 华氏度=-17.2222222 摄氏度
 * */
fn translate_to_c() {
    println!("请输入华氏温度:");
    let h;
    let tn = loop {
        let mut tn = String::new();
        std::io::stdin().read_line(&mut tn).expect("Failed to read line");
        let tn: f32 = match tn.trim().parse() {
            Ok(num) => num,
            Err(_) => {
                println!("输入错误,请重新输入华氏温度:");
                continue;
            },
        };
        h = tn;
        break 5.0 * (tn - 32.0 ) / 9.0;
    };
    println!("{}℉ = {}℃", h, tn);
}
/** 
 * 相互转换摄氏与华氏温度。
 * 1 摄氏度=33.8 华氏度
 * 1 华氏度=-17.2222222 摄氏度
 * */
fn translate_to_h() {
    println!("请输入摄氏温度:");
    let c;
    let tn = loop {
        let mut tn = String::new();
        std::io::stdin().read_line(&mut tn).expect("Failed to read line");
        let tn: f32 = match tn.trim().parse() {
            Ok(num) => num,
            Err(_) => {
                println!("输入错误,请重新输入摄氏温度:");
                continue;
            },
        };
        c = tn;
        break 9.0 * tn / 5.0 + 32.0;
    };
    println!("{}℃ = {}℉", c, tn);
}
/** 打印 n 阶斐波那契数列。*/
fn nb() {
    println!("请输入阶数 n:");
    let n = loop {
        let mut tn = String::new();
        std::io::stdin().read_line(&mut tn).expect("Failed to read line");
        let tn: usize = match tn.trim().parse() {
            Ok(num) => num,
            Err(_) => {
                println!("输入错误,请重新输入阶数 n:");
                continue;
            },
        };
        break tn;
    };
    println!("即将打印{}阶斐波那契数列:", n);
    for elem in 0..n+1 {
        print!("{} ", fbnq(elem));
    }
    println!();
}
/** 生成 n 阶斐波那契数列 */
fn fbnq(n: usize) -> usize {
    if n == 0 || n == 1 {
        return n;
    }
    fbnq(n - 1) + fbnq(n-2)
}
/** 打印圣诞颂歌 “The Twelve Days of Christmas” 的歌词,并利用歌曲中的重复部分(编写循环)。*/
fn pc() {
    // 略
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 205,132评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,802评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,566评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,858评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,867评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,695评论 1 282
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,064评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,705评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 42,915评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,677评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,796评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,432评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,041评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,992评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,223评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,185评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,535评论 2 343

推荐阅读更多精彩内容