0


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

Rust内存回收

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

  1. 一些语言中具有垃圾回收机制,比如说java;
  2. 一些语言需要程序员手动分配和释放内存,比如说c;
  3. rust采用了第三种方式,使用所有权管理系统来管理内存

Rust内存分配

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

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

String类型

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

//使用String::from初始化一个stringlet 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失效

因为此时有两个数据指针指向了同一位置。这就有了一个问题:当

s2

s1

离开作用域,他们都会尝试释放相同的内存。这是一个叫做 二次释放 的错误,所以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 被移动到s3fntakes_and_gives_back(a_string:String)->String{ 
    a_string  // 返回 a_string 并移出给调用的函数}

引用

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

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

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

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

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

fnmain(){letmut s =String::from("hello");change(&mut s);}fnchange(some_string:&mutString){
    some_string.push_str(", world");}

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

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

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

//这个例子是不能通过编译的fnmain(){let reference_to_nothing =dangle();}fndangle()->&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!";fnfirst_word(s:&str)->&str{}

Rust内存回收

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

  1. 一些语言中具有垃圾回收机制,比如说java;
  2. 一些语言需要程序员手动分配和释放内存,比如说c;
  3. rust采用了第三种方式,使用所有权管理系统来管理内存

Rust内存分配

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

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

String类型

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

//使用String::from初始化一个stringlet 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失效

因为此时有两个数据指针指向了同一位置。这就有了一个问题:当

s2

s1

离开作用域,他们都会尝试释放相同的内存。这是一个叫做 二次释放 的错误,所以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 被移动到s3fntakes_and_gives_back(a_string:String)->String{ 
    a_string  // 返回 a_string 并移出给调用的函数}

引用

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

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

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

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

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

fnmain(){letmut s =String::from("hello");change(&mut s);}fnchange(some_string:&mutString){
    some_string.push_str(", world");}

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

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

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

//这个例子是不能通过编译的fnmain(){let reference_to_nothing =dangle();}fndangle()->&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!";fnfirst_word(s:&str)->&str{}

本文转载自: https://blog.csdn.net/weixin_46463785/article/details/128508368
版权归原作者 摸鱼老萌新 所有, 如有侵权,请联系我们删除。

“Rust入门(三):内存与指针”的评论:

还没有评论