我不明白切片和引用之间的区别。 &String
和 &str
之间有什么区别?我在网上看到一些东西说,引用是一个细指针,而切片是一个粗指针,但我不知道它们两个的含义。 我知道一个切片可以强制转换成引用,但它是如何做到的? Deref
特性是什么?
我不明白切片和引用之间的区别。 &String
和 &str
之间有什么区别?我在网上看到一些东西说,引用是一个细指针,而切片是一个粗指针,但我不知道它们两个的含义。 我知道一个切片可以强制转换成引用,但它是如何做到的? Deref
特性是什么?
let my_array: [u32; 3] = [1, 2, 3];
我们可以像这样将my_array
切片成一个[u32]
:
let my_slice: [u32] = my_array[..];
但是通过切片,我们会失去静态的大小信息。由于局部变量最终会被放在需要预定大小的堆栈上,因此我们必须将其放在一个引用下:
let my_slice: &[u32] = &my_array[..];
let my_string: String = "Abc".to_string();
let my_str: &str = &my_string[..]; // As explained previously
// OR
let my_str: &str = &*my_string;
Deref
trait是描述解引用 (*
) 运算符的特征。正如你在上面看到的,我可以执行 *my_string
。这是因为 String
实现了 Deref
,它允许你对 String
进行解引用操作。同样地,我也可以将 Vec<T>
解引用为 [T]
。Deref
trait 在除了 *
的其他地方也会被使用:let my_string: String = "Abc".to_string();
let my_str: &str = &my_string;
&T
的值分配到类型为 &U
的位置,则 Rust 将尝试解引用我的 T
,直到得到一个 U
,同时仍保留至少一个引用。同样,如果我有一个 &&&&....&&&&T
,并试图将其赋值给 &&&&....&&&&U
,它仍然可以工作。
这个 被称为解引用强制转换:自动将 &T
转换为 &U
,其中一定数量的 *T
会产生一个 U
。
*const T
和*mut T
与引用的大小相同,但编译器将它们视为不透明。编译器对原始指针背后的内容没有任何保证,甚至不能保证它们正确对齐。因此,对其进行解引用是不安全的。但由于Deref
特性定义了一个安全的deref
方法,因此解引用原始指针是特殊的,也不会自动完成。extern type
s。这还包括作为其最后成员包含动态大小类型的struct
s,尽管这些非常难以正确构造,但在未来使用CoerceUnsized
特性将变得更加容易。可以使用unsized_locals
夜间功能使所有这些(除了extern type
s)无效,该功能允许一些使用动态大小局部变量。T
,如果T:Sized
,则T
的大小在编译时已知。如果T:?Sized
,则其大小可能在编译时未知(T:?Sized
是调用者最灵活的要求,因为它接受任何)。由于片段需要内部数据连续,并且大小和类型同质,因此不可能在片段、数组或Vec<T>
中包含动态大小的类型(或!Sized
),并保持O(1)
索引。虽然Rust可能可以编写特殊代码以对一组动态大小的类型进行索引,但目前还没有。Box<[T]>
或Rc<[T]>
。这些将自行处理切片的释放(当删除Box
时,当所有强引用和弱引用都被删除时(当所有强引用被删除时,值的析构函数被调用,但是直到所有弱引用也消失才释放内存。)Rc
)。&[T]
为切片(slice),因为对于语言的初学者来说更容易理解。关于切片的参考页面指出,_切片类型的写法为 [T]
_,而切片的文档则将其描述为 _一个动态大小的视图,查看连续的序列,即 [T]
_。 - Optimistic Peachlet my_slice: [u32] = my_array[..];
时,my_array
具有已知的大小。我也不确定动词“own”在这种情况下是否合适,因为它在Rust中有特殊的含义。我认为您在这里没有使用它的Rust意义。 - nbro引用类似于C语言的指针(代表内存位置),但引用永远不会无效*(即null),并且您不能在引用上进行指针算术运算。Rust中的引用与C ++中的引用非常相似。使用引用的一个重要动机是避免move
或clone
变量。假设您有一个计算向量总和的函数(注意:这是一个玩具示例,获取向量总和的正确方法是nums.iter().sum()
)。
fn sum(nums: Vec<u32>) -> Option<u32> {
if nums.len() == 0 {
return None;
}
let mut sum = 0;
for num in nums {
sum += num;
}
Some(sum);
}
let nums = vec!(1,2,3,4,5);
assert_eq!(sum(nums), 15);
assert_eq!(nums[0], 1); //<-- error, nums was moved when we calculated sum
fn sum(nums: &Vec<u32>) -> Option<u32> {
...
}
let nums = vec!(1,2,3,4,5);
assert_eq!(sum(&nums), 15);
assert_eq!(nums[0], 1); // <-- it works!
Slice指的是“作为指针和长度表示的连续内存块的视图”。它可以被视为对数组(或类似数组的东西)的引用。Rust安全性保证的一部分是确保您不会访问超出数组末尾的元素。为了实现这一点,slice在内部表示为指针和长度。与只包含没有长度信息的指针相比,这是较为臃肿的。就像上面的求和示例一样,如果nums
是一个数组,而不是一个Vec
,你应该传递一个slice给sum()
,而不是数组本身。
String和str之间的区别很容易混淆。简单来说,String是可变的,而str是不可变的。String是由标准库提供的类型,它是基于堆分配的并且拥有所有权。str是Rust中的原始类型之一,表示字符串切片。它通常出现在函数签名中,以表示将要处理的字符串的范围,而不需要分配新内存。
str
是一组 utf-8 编码的字符数组,而 &str
则是一组 utf-8 编码的字符切片。 String
是一组 utf-8 编码的字符向量,String
实现了 Deref<Target=str>
,这意味着 &String
行为类似于(强制转换为)&str
。这与 &Vec<u32>
类似,它的行为类似于 &[u32]
(Vec 实现了 Deref<Target=[T]>
)。
* 除非使用不安全的 Rust 使其无效
T
进行泛型处理时,&T
明确是一个引用,并且它也可能是一个切片。 - Aloso
&str
的变量x
,它有多大?与类型为i8
(1个字节)或 i128(16个字节)或struct Foo {x:i8,y:i128}
(1个字节+ 16个字节=17个字节,忽略对齐)的变量进行比较。类型为&Foo
的变量将使用指针的大小,解引用将给出一个大小为 17 字节的对象。由于str
或[i8]
的大小是可变的,因此必须将其存储为引用的 fat 指针形式。 - CoronA