整型
数组索引的类型
Rust requires array indices to be usize values. Values representing the sizes of arrays or vectors or counts of the number of elements in some data structure also generally have the usize type.
rust要求数组的索引是usize类型。表示数组长度或者一些数据结构中的元素个数的值用usize类型。
字面量类型
字面量的类型可以用后缀的方式写明,例如:128i32
。
如果没有写明,则rust会通过类型推导来确定她的类型。
如果推导的结果有多个类型可用,且其中有i32,则使用i32。
其它情况就会报错。
下划线
下划线可以插入到数字字面量中,以增加可读性,插入的位置没有规定,也可以用来插在数字的类型后缀之间。
字节类型
rust的char类型和其它语言不一样,并不等同于数字类型。但rust提供字节类型的字面量b'A'
,他表示的其实就是u8。这样的字面量允许且只能用ASCII字符来表示u8类型。b'A' == 65u8
因为字节只是u8的一种表示方法,所以要考虑什么时候用byte而不是u8,比如要强调一个值是ASCII码的时候。
类型转换
从短类型往长类型转的时候,数值都能保持正确。
从长往短转的时候会截断长类型。
assert_eq!(-1_i8 as u8, 255_u8);
// 这里要注意的是,负数用补码表示
字面量的方法调用
字面量直接调方法时都要写明类型:2_u16.pow(4)
,因为所有的数值类型都有pow
这个方法,rust不知道要调哪个。使用默认的i32类型是是所有的方法都执行之后还有多个可选类型,而i32
是其中之一。而方法调用不适合这种情况,因为这个方法还没有被调用。
方法调用的优先级比一元运算符高,所以调用负数的方法时要用括号:(-4_i8).abs()
溢出的行为
在debug模式,溢出会panic。在release模式溢出会取模。
let mut i = 1_i8;
i += 127;
println!("{}", i);
// cargo run --release
// -128
但这些行为可以通过特定的方法进行统一,这些方法分成4类:
- checked:
如果可以得到正确的结果返回Some(v)
,否则返回None
。
// The sum of 10 and 20 can be represented as a u8.
assert_eq!(10_u8.checked_add(20), Some(30));
// Unfortunately, the sum of 100 and 200 cannot.
assert_eq!(100_u8.checked_add(200), None);
// Oddly, signed division can overflow too, in one particular case.
// A signed n-bit type can represent -2ⁿ⁻¹, but not 2ⁿ⁻¹.
assert_eq!((-128_i8).checked_div(-1), None);
- wrapping :
结果如果超过表示范围就wrap(也就是取模,也就是截断)。
在二进制移位操作中,移位的聚历也会被取模。
- Saturating
如果溢出返回最接近的值。也就是如果结果超过MAX
就返回MAX
,如果小于MIN
就返回MIN
。
但是,除法,求余和二进制位移没有saturating。
- Overflowing
返回一个tuple(result, overflowed),result和wrapping的结果一样。overflowed是一个bool表示是否溢出。
位移操作的overflowed和shift距离有关,如果距离超过类型的长度则是true。
浮点数
浮点数有正无穷,负无穷,NaN,不同的正负0。
浮点数的缺省类型是f64。
布尔值
Rust不会像其它语言那样在判断中使用其它类型,将他们的某些值当作true或false。
as操作符可以将bool转换为整型,但不能反之。
bool值在内存中占用一个字节。
字符类型
Rust字符类型表示一个Unicode字符,用32位。
Rust用char表示字符,但字符串用的是UTF-8编码,所以字符串并不是char的数组。
字面量:
- 直接写字符
'a'
- 如果字符的Unicode值在U+0000到U+007F之间,则可以写成
'\xHH'
- 任何值都可以写成
'\u{HHHHHH}'
,也就是最多6位的16进制数。
字符的表示范围0x0000 to 0xD7FF, or 0xE000 to 0x10FFFF
as操作符可以将char转成整型,但整型中之后u8可以转成char。rust的意图是as只进行便宜的且不会出错的转换。而整形中除u8意外的类型都含有非法的Unicode值。
但标准库中有从整型转换的方法,并有检查,例如std::char::from_u32
元组
元组中最后一个元素的后面可以加逗号,多个元素时,加不加都一样,只有一个元素时必须加,否则就不是元组了。
指针
其它语言,例如Java,如果一个对象中有一个字段是另一个对象,那这个字段是一个指向另外一个对象的引用,而不是真的包含这个对象。而Rust是真的包含这个对象,例如((1, 2), (3, 4))
是真的一个4个整型大小的内存,而没有在heap上申请任何内存。Rust如果要用指针必须显式的声明。
引用
类型&T
是一个指向T
的值的引用,&T
的值就是指向T
的地址,这个地址的大小是一个机器字,这个地址可能在stack也可能在heap上。
Boxes
最简单的在heap上申请内存的方法就是用Box::new(T)
Raw Pointers
类似C的指针,这类指针的安全是不被保障的。且dereference这些指针只能在unsafe代码块里。
数组,向量和切片
超过范围的索引会造成panic,所以这是运行时检查的,这样是不是算overhead?
索引的类型必须是usize
。
数组
查找,排序等方法都是实现在切片里的,但在调用方法的时候Rust会隐式的将数组转成切片,所以这些方法数组也可以直接调用。
定义向量的简便方法
let v: Vec<i32> = (0..5).collect();
// 使用collect时必须指定v的类型,因为collect不只能返回Vec
向量
向量也可以直接调用切片的方法。
一个Vec<T>
包含:1. 一个指向在堆上创建的buffer的指针,2. 这个buffer的容量,3. 这个buffer的长度(也就是现在包含的元素的个数)。
宏vec!
和collect
方法都是以现有的元素个数为容量,用Vec::with_capacity
创建的向量。
当增加元素超过容量的时候向量会重新申请内存然后将所有元素复制过去,重新申请的内似乎是按2的指数增长的。
let mut v = Vec::with_capacity(1);
v.push(1);
v.push(2);
// Typically prints "capacity is now 4":
println!("capacity is now {}", v.capacity());
切片
切片,写作[T],表示数组或向量的一部分。切片可以是任意的长度,所以不能存储到一个变量里,也不能传递到函数的参数,只能按引用来传递。
切片的指针是“胖指针”(fat pointer),有两个字长,包括指向第一个元素的指针和切片的长度。
字符串
字面量
字符串的字面量可以换行,换行和空白符都会保留。如果在换行前加上反斜线,则换行和空白符会被忽略。
println!("abc
def");
// 输出:
// abc
// def
println!("abc\
def");
// 输出:
// abcdef
raw string:在字符串前加一个r
,字符串里的转义就会被忽略。对正则,路径很方便。
但如果想要在raw string里放"
就需要在字符串的开头和接尾放相等数量的#
:let s = r##"he said: "hello, world!""##;
Byte Strings(字节串?)
写作b"abc"
,实际上是一个u8
数组的切片,并不是字符的数组。所以他没有字符串的一些方法。
raw byte string写作br"abc"
。
字节串只能包含ASCII字符或者\xHH
.
字符串的内存表示
字符串是Unicode字符,用UTF-8存储。ASCII字符用一个字节表示,其它字符使用不确定的多个字符。
所以.len()方法返回的不是字符个数,.chars().count()可以返回字符个数。
String
是在heap里的,字符串的字面量是定义在程序中的不可变的值,程序启动的时候创建直到程序结束。&str
是一个fat pointer可能指向String也可能指向字面量。&str
都是不可变的(除了修改ASCII字符的大小写,make_ascii_uppercase
,make_ascii_lowercase
),因为修改一个字符都可能会改变整个切片的长度。
因为字符串是用Unicode表示,Unicode中长得一样的字符可能有不同值,所以比较的时候会不一样。