系列文章目录
【rust】| 00——开发环境搭建
【rust】| 01——编译并运行第一个rust程序
【rust】| 02——语法基础 | 变量(不可变?)和常量
【rust】| 03——语法基础 | 数据类型
【rust】| 04——语法基础 | 函数
【rust】| 05——语法基础 | 流程控制
【rust】| 06——语言特性 | 所有权
所有权是Rust语言中一个关键的特性。它是一种内存管理规则(机制)。它通过这个规则(即所有权系统)结合编译器对代码进行检查 从而确保内存安全。
在其他语言如C/C++ 等 都有自己的内存管理方式。C/C++需要我们显式(手动)处理所用的内存(申请、释放) 如果处理不当会造成很严重的内存安全问题。而Rust中这一特性很好的可以帮我们确保内存安全性。
1、每个值都对应一个变量 owner
2、一个值只能有一个 owner
3、有作用域,当owner超出作用域 值会被销毁 (这个变量有生命周期 在作用域内)
Rust中变量的作用域和其他语言类型。比如函数体内的 变量(局部变量) 它的作用域在 函数大括号之间 生命周期也是这么短。
可以看到 超过范围销毁了。 对应所有权规则的最后一条。owner 就是这个值藏在暗处的变量名。 它超过变量作用域会消失。Rust内部在超出后 会掉清理函数 drop
清理变量占用的内存。
Rust中的移动 类似浅拷贝 但有细节之处。
我们通过代码例子去理解
1、变量赋值操作
分析:
1、整型变量赋值 变量a 复制到了 变量b
2、字符串赋值 变量s1 复制到了 变量s2
我们可以看到 两种不同的类型 操作几乎是一样的。 整型它只是一个数字 再怎么复制占用的大小就那么多 无关紧要。可是当是一个很大的字符串 复制给另一个变量 那所占用的大小(很大*2) 这样的操作 太占用大小 咋的一个字符串准备要我命啊。是不是和其他语言复制操作很类似。
但是在 Rust中 复制的操作 还是有细微差别的 我们深入了解下rust下 具体复制的具体过程
细看 字符串变量的定义及复制到其他变量的过程
可以看到 字符串变量的内容 hello 存储在堆上 而 定义的变量 S1 只保存了 指向堆上内容的指针、长度和容量这三个 这些内容存储在栈上。并没有将 字符串内容也保存。
下面我们看 S1内容复制给S2 是怎样的
可以看到 S1 复制到 S2 也只是复制了 指针、长度、容量三件套。不重复复制堆上内容。这样的情况下S1 S2是不是 占用栈大小很少。
如果S1 S2把数据一起保存呢
可以看到 全保存过来 每个变量 都这样完全复制 那栈吃不消。当内容很大时 是不是 一个赋值操作直接给 栈空间 用完 干废掉啦。
通过上面图我们可以看到 rust中 变量保存了指向内容的指针等。 S1 和S2 中的指针都指向了 数据内容。我们知道所有权规则之一 变量超出作用域 会没了。那么当S1和S2 超出作用域 会尝试释放 因为它两指向一个地址那么 两次释放是不是会出大问题呢。也称为 双重释放错误。
当然 rust中也会想到这种错误 所以它会避免啦。
在Rust中 执行完 S2=S1这样的操作 S1已经变得无效啦 所以不会再存在上面双重释放的隐患了。
其实 Rust中S2 = S1 的操作类似浅拷贝 只拷贝了指针长度容量。但是rust 中超出范围 让其消失的规则 即S1 无效啦的操作 我们把这样称为 移动。
Rust中 我们把类似 深拷贝(把数据内容一起复制)的操作
称为克隆 clone。
前面学习完 移动和克隆 我们理解到 在Rust中 通过变量给变量赋值的操作 如果直接赋值 S2 = S1 这种 类似于浅拷贝 而且S1会消失 没有拷贝在堆上的内容 而 通过克隆 s2=s1.clone() 这种 类似深拷贝的操作 这样S1还活着。 下面我们通过例子理解COPY
为什么x 还能用? 因为X是整型 编译器编译时 知道它的大小 将内容全部存储在栈中。也可以说整型具有copy特性 所以才会存在栈上。字符串型 大小我们不确定因为不知道后面会不会 改变。 在这种情况下 深浅拷贝(克隆)没区别。
具有copy特性的类型
函数和返回值 都可以转移属性
通过函数入参 将变量移动/复制 和变量移动/复制一样的
通过例子学习
通过这个可以看到 和变量是一样的
通过返回值 转移
有些场景我们只需要使用值 而且会多次使用 一个变量本身具有所有权 多次使用需要移动来移动去 好麻烦 有没有办法不要所有权 那就是通过引用的方式。
引用: 使用值而不转移(获取)所有权
引用没有所有权
引用通过地址来访问存储在该地址的数据
我们可以看到上面 函数定义入参 s: &sring 它的含义 我们创建了一个引用 来接收传入的实参引用 也称为 借用
借用 是创建引用的动作
借用是 不能修改引用的值
借用 顾名思义 我借了别人东西 我要原模原样还给人家 不能说 搞坏了还吧
上demo
可以看到 引用 和 默认情况下的变量 一样是不可变的
引用默认情况下 不可修改内容 和默认变量一样
上面代码因为我们定义了不可变的变量等 那我们给他给加上可变的标志 是不是就可以变啦 是的
可以看到 定义变量 函数入参等 都修改为 可变的 少一个都不行 否者会报错 既然要该那么都得是可变属性哦
可变引用的约束
1、一个变量只能有一个可变引用
防止多个引用同时对一个值 修改
类似空指针。
在rust中 编译器会保证不会存在空引用。
编译器检测过程
但我们又想用 函数内局部变量怎么办
前面我们学习了 返回值可以转移所有权 那么我们返回 变量是不是就可以了
切片也是一种引用,切片没有所有权。
切片:对一个集合(元组 字符串) 引用其部分连续元素的操作
注: 切片不能对整个切 只能连续的某部分 是有范围的
我们看看切片数据的内部存储结构是怎样的
可以看到切片只存储了 起始位置的指针 和切的长度
还有一种字符串类型 &str 它指向二进制文件 这个字符串不可变
let s=“awdada”; // s是 &str类型
和字符串切片类似 只是切的对象换了 可以切好多
比如 切数组 引用数组中一部分元素
我们通过前面知道切片是如何创建
知道了 [ ] 方括号之间需要填切片的范围
那么当我们想切片的起始位置从0开始 除了 [0…x] 还有其他的表达方式
1、切片索引从0开始 的表达方式
同样的 结尾位置 也可以 用省略 表达
2、切片索引 到末尾 的表达方式
3、表示整个 同理 省略起始和结尾值 [ . . ]