赞
踩
在Rust中,变量通过let关键字来创建。
fn main() {
let first = 1;
println!("first number is {}", first); // first number is 1。
}
上述表明了标识符first与值1的绑定关系。
位置表达式是表示内存位置的表达式,比如:
值表达式一般只是引用了某个存储单元地址中的数据,只能进行读取操作。
位置表达式一般代表了持久性数据,值表达式代表了临时数据。值表达式要么是字面量,要么是求值过程中创建的临时值。
前面所说的let关键字默认声明的位置表达式为不可变的。
fn main() {
let a = 1;
//a = 2; //无法改变a的值,因为默认为不可变绑定
let mut b = 2;
b = 3 //加入mut关键字,声明为可变位置表达式。
}
let默认声明的不可变绑定只能对相应的存储单元进行读取,加入mut关键字后可进行写入操作。
先看代码,在下面代码中
fn main() {
let str1 = "hello";
let str2 = "hello".to_string();
let other = str1;
println!("{:?}", str1);
let other = str2;
println!({"?}", str2);
}
每个变量绑定实际上都是拥有该存储单元的所有权,这种转移内存地址的行为就是所有权转移,在RUST中被称为移动,那种不转移的情况被称为复制。
在日常开发中,有时候无需转移所有权,可以通过借用操作符&,直接取内存位置,可以通过该内存位置对存储进行读取。
fn main() {
let a = [1,2,3];
let b = &a; //借用a,a的所有权不转移。
println("{:?}", b); //打印a的地址。
let mut c = vec![1,2,3];
let d = &mut c; //变量c的可变借用
d.push(4);
println("{:?}", d); //打印结果为[1,2,3,4];
let e = &42;
assert_eq!(42, *e); //*为解引用符号, 可直接获取到e的具体值。
}
上述代码使用&借用符后,将赋值表达式右侧变成了位置上下文,只是共享内存地址。
函数通过fn关键字定义。如下列代码:
pub fn fizz_buzz(num: i32) -> String {
if num % 15 == 0 {
return "fizzbuzz".to_string();
} else if num % 3 == 0 {
return "fizz".to_string();
} else if num % 5 == 0 {
return "buzz".to_string();
} else {
return num.to_string();
}
}
上述代码中,通过关键字fn定义一个fizz_buzz的函数,函数中的括号为入参类型,约定入参类型为i32,符号->后面的String为该函数的返回类型,该函数定义的返回类型为String。
简单来讲,一个大括号就是一个生命周期,Rust语言的作用域是静态作用域。
fn main() {
let v = "hello world";
let v = "hello rust";
{
let v = "hello handsome boy";
println!("{?}", v);//新的作用域,这里输出的是hello handsome boy
}
println!("{"?}", v); //由于在同一个作用域中,第二个覆盖了第一个,所以这里输出的是hello rust。这种情况被称为变量屏蔽。
}
上述代码中,虽然是同样的代码变量,但是在不同的作用域,所以拥有不同的生命周期。
万物皆与函数,函数在Rust语言中分量很大,所以函数本身可以作为其他函数的参数和返回值使用。
下面是函数作为参数的情况:
//op为一个定义的函数 pub fn math(op: fn(i32,i32) ->i32, a: i32, b: i32) -> i32 { op(a,b) } fn sum(a:i32, b:i32) -> i32{ a+b } fn product(a:i32, b:i32) -> i32 { a*b } fn main() { let a = 2; let b = 3; let sum_res = math(sum, a, b); let product_res = math(product, a, b); println!("the sum result is {:?}", sum_res);//5 println!("the product result is {:?}", product_res);//6 }
下面是函数作为返回值的情况:
fn is_true() -> bool {
true
}
fn true_maker() ->fn() -> bool {
is_true//这里不能加上(),如果加上()后会直接调用is_true这个函数,不加则是返回同名函数的指针。
}
fn main() {
(true_maker())();//由于true_maker返回的是is_true的指针,加上()后则是直接调用该函数
}
闭包的特点:
下面是闭包的演示代码:
fn main() { let out = 42; fn add(i: i32, j: i32) -> i32 { i+j } let closure_annotated = |i: i32, j: i32| -> i32 { i+j+out }; //由于闭包理解成一个变量形式,所以需要用;结尾 let closure_inferred = |i, j| i+j+out; let i = 1; let j = 2; println!("{:?}",add(i,j)); //3 println!("{:?}",closure_annotated(i, j)); //45 println!("{:?}",closure_inferred(i, j)); //45 }
闭包和函数的重要区别在于,闭包可以捕获到外部变量,但是函数不可以。
比如上述方法中的add改为fn add(i:i32,j:i32)->i32{ i+j+out}则会出错。但是闭包可以。
闭包也可以作为函数的参数和返回体,下面是闭包作为参数:
fn math<F: Fn() -> i32>(op: F) ->i32 {
op()
}
fn main() {
let a = 2;
let b = 3;
println!("result is {:?}", math(|| a + b)); //5
println!("result is {:?}", math(|| a*b)); //6
}
上述定义了函数math,其参数是一个泛型F,并且这个F受到一个Fn() -> i32的trait限定,(trait理解成Java中的interface接口),代表该函数只允许实现Fn()->i32的trait的类型作为参数。
所以Rust中的闭包实际上就是一个匿名结构体和trait来组合实现的。
闭包作为返回值的情况:
fn two_times_impl() -> impl Fn(i32) -> i32 {
let i = 2;
move |j| j * i//move关键字将i的所有权转移到闭包中,因为闭包默认是引用,这里是为了防止悬垂指针,后续更新。
}
fn main() {
let result = two_times_impl();
println!("result is {:?}", result(2));
}
上述代码中,定义了impl Fn(i32)->i32作为返回值类型,但是在函数定义的时候并不知道具体的返回类型,在函数调用时,编译器会推断出来。
主要讲条件语句和循环语句
之所以叫表达式,就代表其一定有值,而且if表达式的各个分支必须返回同一个类型的值。
fn main() {
let n = 13;
let big_n = if (n < 10 && n > -10 ){
10*n
} else {
n/2
};
println!("big_n is {:?}", big_n); //6
}
上述代码中,根据逻辑推断出走的else分支,但是13/2应该是6.5,由于变量n默认推断为i32类型,且big_n变量也已经被Rust编译器根据上下文默认推断为i32类型,所以在计算n/2的时候,Rust编译器会将结果截取来符合所推断类型i32。
在Rust中,存在三种循环表达式:while,loop,for…in。
下面用for实现FizzBuzz:
fn main() {
for n in 1..101 {
if n % 15 == 0 {
println!("fizzbuzz");
} else if n % 3 == 0 {
println!("fizz");
} else if n % 5 == 0 {
prinln!("buzz");
} else {
prinln!("{}", n);
}
}
}
当使用无限循环的时候Rust可使用loop,避免使用while true
fn main() {
let number = 42;
match number {
0 => println!("zero"),
1...3 => println!("one to three"),
5 | 7 | 13 => println!("five or seven or thire"),
n @ 42 => println!("you can use n in this match,n is {}", n),
_ => println!("others"),
}
}
这种格式分为左右两个分支,左侧为number所匹配的具体结果,右侧为执行代码,上述代码,当number为0的时候,输出zero,当为数字1-3之间的数字时,执行右侧代码,依次,下面是单独匹配,当是5,7,13之间某一位数字的时候执行右侧代码,@符号为match中的赋值符号,可以把当前值赋值给变量n,供右侧代码使用。最后的下划线_为其余情况统一执行右侧代码。
match表达式左侧为模式,右侧为执行的代码。
这两个是在某些情况来代替match表达式的。
fn main() {
let boolean = true;
let mut binary = 0;
if let true = boolean {
binary = 1;
}
assert_eq!(binary, 1);
}
if let表达式左侧为模式,右侧为具体的值,上述代码及当boolean变量为true的时候,将binary的值从0改为1。
在某些循环场合下,while let可简化代码,循环代码如下:
fn main() {
let mut v = vec![1,3,5];
loop {
match v.pop() {
Some(x) => println!("{}",x),
None => break,
}
}
}
上述代码中,None => break只负责跳出代码,可简化,改为while let及为:
fn main() {
let mut v = vec![1,3,5];
while let Some(x) = v.pop() {
println!("{}", x);
}
}
Rust内置了布尔类型,类型名为bool,只有两个值,true和false。
fn main() {
let x = true;
assert_eq!(x as i32, 1);// 可以通过as把bool类型转为数字类型,但是不能数字转bool
let y: bool = false;
let x = 5;
if x > 1 {
println!("x is bigger than 1 ")
};
assert_eq!(y as i32, 0);
}
Rust的基本数字类型可为三类:固定取值范围的类型,动态取值范围的类型,浮点类型
在Rust中,用单引号来定义字符,每个字符占4个字节。
fn main() {
let x = 'r';
}
数组类型的特点:
fn main() {
let arr: [i32, 3] = [1,2,3];
let mut mut_arr = [1,2,3];// 绑定为一个可变数组
assert_eq!(1, arr[0]);
mut_arr[0] = 3; // 可修改可变数组中的数据
assert_eq!(3, mut_arr[0]);
let init_arr = [0; 10]; //创建一个长度为10,初始值为0的数组
//println!("out index is error :{}", arr[5])编译失败,数组越界。
}
对于越界的数组,Rust会编译报错。
对于原始的数组,只有实现了Copy trait的类型才能作为其元素。
范围类型包括了左闭右开和全闭合两种。
fn main() {
assert_eq!((1..5), std::ops::Range{start: 1, end: 5});//左闭右开
assert_eq!((1..=5), std::ops::RangeInclusive::new(1, 5));//全闭合
assert_eq!(3+4+5, (3..=5).sum());
assert_eq!(3+4+5, (3..6).sum());
for i in (1..5) {
println!("{}", i);// 1,2,3,4
}
for i in (1..=5) {
println!("{}", i);// 1,2,3,4,5
}
}
切片类型是对一个数组(包括固定大小的数组和动态数组)的引用片段,有利于安全有效的访问数组的一部分,而不需要拷贝。理论上切片的引用是已经存在的变量,在底层,切片代表一个指向数组起始位置的指针和数组长度。用[T]类型表示连续序列,那么切片类型就是&[T]和&mut [T]。
fn main() {
let arr: [i32, 5] = [1,2,3,4,5];
assert_eq!(&arr, &[1,2,3,4,5]);
assert_eq!(&arr[1..], [2,3,4,5]); //表示获取索引1以后的所有元素,索引是从0开始计算的。
assert_eq!((&arr).len(), 5);
let arr = &mut [1,2,3];// 定义一个可变切片,可以直接通过索引修改对应的值。
arr[1] = 7;
assert_eq!(arr, &[1,7,3]);
let vec = vec![1,2,3];
assert_eq!(&vec[..], [1,2,3]);
}
Rust存在原始的字符串类型str,通常都是以不可变借用的形式存在,既&str(字符串切片),处于内存安全考虑,Rust将字符串分为两类,一种是固定长度字符串,&str,另一种是可增长字符串,可以随意改变其长度,String。
fn main() {
// 定义一个静态生命周期的str字符串,则truth的生命周期和该程序代码的生命周期是同步的。
let truth: &'static str = "Rust是一门优雅的语言";
let ptr = truth.as_ptr();
let len = truth.len();
assert_eq!(28, len);
}
可以表示内存地址的类型被称为指针。Rust中包括了多种指针,引用(reference),原生指针(Raw Pointer),函数指针(fn pointer)和智能指针(Smart Pointer)。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。