Rust入门(三):内存与指针

编程入门 行业动态 更新时间:2024-10-18 08:26:27

Rust入门(三):内存与<a href=https://www.elefans.com/category/jswz/34/1768268.html style=指针"/>

Rust入门(三):内存与指针

Rust内存回收

所有程序都必须管理其运行时使用计算机内存的方式。

  1. 一些语言中具有垃圾回收机制,比如说java;

  2. 一些语言需要程序员手动分配和释放内存,比如说c;

  3. rust采用了第三种方式,使用所有权管理系统来管理内存

Rust内存分配

rust的 栈和堆都是代码在运行时可供使用的内存,它们的结构不同。

  • 栈中的所有数据都必须占用已知且固定的大小
  • 堆是缺乏组织的,当向堆放入数据时,你要请求一定大小的空间。内存分配器在堆的某处找到一块足够大的空位,把它标记为已使用,并返回一个表示该位置地址的指针,然后将该指针存储在栈,因为这个指针是固定大小的。
  • 入栈比在堆上分配内存要快,无需为存储新数据去搜索内存空间;访问堆上的数据比访问栈上的数据慢
  • 之前提到的所有的基础数据类型(整数、浮点数、元组等等)都存储在栈上,他们的长度是固定的
  • 后续会介绍一些存储在堆上的可变长度的数据结构,例如Vector、String等,本章节仅用 String 这一个结构来介绍本章节的内容

String类型

String 这个类型管理被分配到堆上的数据,能够存储在编译时未知大小的文本,也可以在初始化后改变存储的数据的长度

//使用String::from初始化一个string
let s = String::from("hello");
//push_str这个方法可以追加字符串的内容
s.push_str(", world!");

Rust所有权规则

rust的值遵循以下的规则:

  1. rust 中的每一个值都有一个所有者。
  2. 值在任一时刻有且只有一个所有者。
  3. 当所有者(变量)离开作用域,这个值将被丢弃。

具体的说明如下:

  • 作用域机制

和其他语言类似,rust的作用域也是用 { } 包裹的一个代码块,一个变量从他被声明开始有效,到代码块的尽头结束

{                      // s 在这里无效, 它尚未声明let s = "hello";   // 从此处起,s 是有效的
}                      // 此作用域已结束,s 不再有效
  • 数据所有权

当我们将一个基础数据的变量赋值给另一个变量的时候,实际上是将这个变量的数值给予了另一个变量,这个行为被称为复制

let x = 5;
let y = x;
//此时x,y都有效

但是我们将String类型做相同的操作的时候,并不能成立,因为在栈上存储的只有String的指针和其他信息,具体数据存储在堆上,这样的操作并没有复制数据,仅仅复制了它的指针的其他信息。

let s1 = String::from("hello");
let s2 = s1;
//这个表述会让s1失效

因为此时有两个数据指针指向了同一位置。这就有了一个问题:当 s2s1 离开作用域,他们都会尝试释放相同的内存。这是一个叫做 二次释放 的错误,所以rust禁止了这种做法,直接让 s1 失效。这种做法被称为移动

如果我们想要实现一个堆上类型的复制,我们需要对其数据进行克隆,然后创建一个新的指针指向复制出来的数据,类似于 js 中的深拷贝的做法,在rust里使用 clone 这个函数来实现

let s1 = String::from("hello");
let s2 = s1.clone();
  • 所有权与函数

在rust中,将数据传入到函数中和上文提到的句子一样,会产生数据的移动和复制,所以如果我们将一个 String 传入函数,那么我们接下来就不能调用这个 String 了

let s = String::from("hello");  // s 进入作用域
takes_ownership(s);
//这个操作是不允许的
println!("{}",s);let x = 5;                      // x 进入作用域
makes_copy(x);
//这个操作就是允许的
println!("{}",x);

如果想要继续使用这个变量,可以在函数中返回一个传入的变量,实现一个移动

let s2 = String::from("hello");    
let s3 = takes_and_gives_back(s2);  // s2 被移动到s3
fn takes_and_gives_back(a_string: String) -> String { a_string  // 返回 a_string 并移出给调用的函数
}

引用

对于类似于 String 的数据结构,我们可以使用引用来传递这类数值,引用类似 c语言的指针,它是一个地址,它指向某个特定类型的有效值,其使用 & 符号定义。

let s1 = String::from("hello");
let s2 = &s1;

在调用函数时,如果传入一个引用,那么获取到的就是引用,而不是数据本身,所以本身的所有权并没有转移,所以不会被销毁。

fn main() {let s1 = String::from("hello");//传入时也需要加上&let len = calculate_length(&s1);println!("The length of '{}' is {}.", s1, len);
}
//这里接收一个引用类型
fn calculate_length(s: &String) -> usize {s.len()
}

引用同样是可以设置为mut的,在设置为 mut 之后,如果我们在函数内部改变引用的值,那么在函数结束后,被传入的值将会改变,因为引用相当于传入了地址,函数对这个地址指向的数据进行了修改,那么再用这个地址寻找数据时,获得的自然是修改后的数据。

fn main() {let mut s = String::from("hello");change(&mut s);
}fn change(some_string: &mut String) {some_string.push_str(", world");
}

但是可变引用是有条件的,如果有多个可变引用的话,可能会产生同时有多个逻辑修改同一片地址的情况;如果同时存在可变引用和不可变引用的话,不可变引用调取的内容可能被可变引用改变。所以引用的条件是

  • 同时只能有一个可变引用,在拥有了一个可变引用之后,不能在拥有其他引用
  • 可以同时拥有多个不可变引用
//这是不允许的
let mut s = String::from("hello");
let r1 = &mut s;
let r2 = &mut s;

同时,在其他语言中可能出现以下情况,一个引用指向的内容被释放了,导致某给引用指向了空值的情况,在rust中,不允许这种情况的存在,rust必须保证你引用指向的对象一直有效。

//这个例子是不能通过编译的
fn main() {let reference_to_nothing = dangle();
}
fn dangle() -> &String {let s = String::from("hello");&s
}//这里s的作用域结束了

Slice类型

slice允许你引用集合中一段连续的元素序列,而不用引用整个集合,slice 是一类引用,所以它没有所有权。

字符串slice可以截取字符串中的一部分,可以使用一个由中括号中的 [starting_index..ending_index] 指定的 range 创建一个 slice,包含开始位置不包含结束位置

let s = String::from("hello");
let slice = &s[0..2];

如果直接定义字符串的字面值就是slice类型。如果想要将其传递到函数中,需要用 &str 来作为它的类型

let s = "Hello, world!";
fn first_word(s: &str) -> &str {}

Rust内存回收

所有程序都必须管理其运行时使用计算机内存的方式。

  1. 一些语言中具有垃圾回收机制,比如说java;

  2. 一些语言需要程序员手动分配和释放内存,比如说c;

  3. rust采用了第三种方式,使用所有权管理系统来管理内存

Rust内存分配

rust的 栈和堆都是代码在运行时可供使用的内存,它们的结构不同。

  • 栈中的所有数据都必须占用已知且固定的大小
  • 堆是缺乏组织的,当向堆放入数据时,你要请求一定大小的空间。内存分配器在堆的某处找到一块足够大的空位,把它标记为已使用,并返回一个表示该位置地址的指针,然后将该指针存储在栈,因为这个指针是固定大小的。
  • 入栈比在堆上分配内存要快,无需为存储新数据去搜索内存空间;访问堆上的数据比访问栈上的数据慢
  • 之前提到的所有的基础数据类型(整数、浮点数、元组等等)都存储在栈上,他们的长度是固定的
  • 后续会介绍一些存储在堆上的可变长度的数据结构,例如Vector、String等,本章节仅用 String 这一个结构来介绍本章节的内容

String类型

String 这个类型管理被分配到堆上的数据,能够存储在编译时未知大小的文本,也可以在初始化后改变存储的数据的长度

//使用String::from初始化一个string
let s = String::from("hello");
//push_str这个方法可以追加字符串的内容
s.push_str(", world!");

Rust所有权规则

rust的值遵循以下的规则:

  1. rust 中的每一个值都有一个所有者。
  2. 值在任一时刻有且只有一个所有者。
  3. 当所有者(变量)离开作用域,这个值将被丢弃。

具体的说明如下:

  • 作用域机制

和其他语言类似,rust的作用域也是用 { } 包裹的一个代码块,一个变量从他被声明开始有效,到代码块的尽头结束

{                      // s 在这里无效, 它尚未声明let s = "hello";   // 从此处起,s 是有效的
}                      // 此作用域已结束,s 不再有效
  • 数据所有权

当我们将一个基础数据的变量赋值给另一个变量的时候,实际上是将这个变量的数值给予了另一个变量,这个行为被称为复制

let x = 5;
let y = x;
//此时x,y都有效

但是我们将String类型做相同的操作的时候,并不能成立,因为在栈上存储的只有String的指针和其他信息,具体数据存储在堆上,这样的操作并没有复制数据,仅仅复制了它的指针的其他信息。

let s1 = String::from("hello");
let s2 = s1;
//这个表述会让s1失效

因为此时有两个数据指针指向了同一位置。这就有了一个问题:当 s2s1 离开作用域,他们都会尝试释放相同的内存。这是一个叫做 二次释放 的错误,所以rust禁止了这种做法,直接让 s1 失效。这种做法被称为移动

如果我们想要实现一个堆上类型的复制,我们需要对其数据进行克隆,然后创建一个新的指针指向复制出来的数据,类似于 js 中的深拷贝的做法,在rust里使用 clone 这个函数来实现

let s1 = String::from("hello");
let s2 = s1.clone();
  • 所有权与函数

在rust中,将数据传入到函数中和上文提到的句子一样,会产生数据的移动和复制,所以如果我们将一个 String 传入函数,那么我们接下来就不能调用这个 String 了

let s = String::from("hello");  // s 进入作用域
takes_ownership(s);
//这个操作是不允许的
println!("{}",s);let x = 5;                      // x 进入作用域
makes_copy(x);
//这个操作就是允许的
println!("{}",x);

如果想要继续使用这个变量,可以在函数中返回一个传入的变量,实现一个移动

let s2 = String::from("hello");    
let s3 = takes_and_gives_back(s2);  // s2 被移动到s3
fn takes_and_gives_back(a_string: String) -> String { a_string  // 返回 a_string 并移出给调用的函数
}

引用

对于类似于 String 的数据结构,我们可以使用引用来传递这类数值,引用类似 c语言的指针,它是一个地址,它指向某个特定类型的有效值,其使用 & 符号定义。

let s1 = String::from("hello");
let s2 = &s1;

在调用函数时,如果传入一个引用,那么获取到的就是引用,而不是数据本身,所以本身的所有权并没有转移,所以不会被销毁。

fn main() {let s1 = String::from("hello");//传入时也需要加上&let len = calculate_length(&s1);println!("The length of '{}' is {}.", s1, len);
}
//这里接收一个引用类型
fn calculate_length(s: &String) -> usize {s.len()
}

引用同样是可以设置为mut的,在设置为 mut 之后,如果我们在函数内部改变引用的值,那么在函数结束后,被传入的值将会改变,因为引用相当于传入了地址,函数对这个地址指向的数据进行了修改,那么再用这个地址寻找数据时,获得的自然是修改后的数据。

fn main() {let mut s = String::from("hello");change(&mut s);
}fn change(some_string: &mut String) {some_string.push_str(", world");
}

但是可变引用是有条件的,如果有多个可变引用的话,可能会产生同时有多个逻辑修改同一片地址的情况;如果同时存在可变引用和不可变引用的话,不可变引用调取的内容可能被可变引用改变。所以引用的条件是

  • 同时只能有一个可变引用,在拥有了一个可变引用之后,不能在拥有其他引用
  • 可以同时拥有多个不可变引用
//这是不允许的
let mut s = String::from("hello");
let r1 = &mut s;
let r2 = &mut s;

同时,在其他语言中可能出现以下情况,一个引用指向的内容被释放了,导致某给引用指向了空值的情况,在rust中,不允许这种情况的存在,rust必须保证你引用指向的对象一直有效。

//这个例子是不能通过编译的
fn main() {let reference_to_nothing = dangle();
}
fn dangle() -> &String {let s = String::from("hello");&s
}//这里s的作用域结束了

Slice类型

slice允许你引用集合中一段连续的元素序列,而不用引用整个集合,slice 是一类引用,所以它没有所有权。

字符串slice可以截取字符串中的一部分,可以使用一个由中括号中的 [starting_index..ending_index] 指定的 range 创建一个 slice,包含开始位置不包含结束位置

let s = String::from("hello");
let slice = &s[0..2];

如果直接定义字符串的字面值就是slice类型。如果想要将其传递到函数中,需要用 &str 来作为它的类型

let s = "Hello, world!";
fn first_word(s: &str) -> &str {}

更多推荐

Rust入门(三):内存与指针

本文发布于:2024-02-12 16:35:56,感谢您对本站的认可!
本文链接:https://www.elefans.com/category/jswz/34/1688579.html
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。
本文标签:指针   入门   内存   Rust

发布评论

评论列表 (有 0 条评论)
草根站长

>www.elefans.com

编程频道|电子爱好者 - 技术资讯及电子产品介绍!