做购物网站是怎么链接银行,网站开发语言在线检测,创建wordpress用户访问数据库,国外开发网站所有权
Rust的核心特性就是所有权所有程序在运行时都必须管理他们使用计算机内存的方式 有些语言有垃圾收集机制#xff0c;在程序运行时#xff0c;他们会不断地寻找不再使用的内存在其他语言中#xff0c;程序员必须显式的分配和释放内存 Rust采用了第三种方式#xff1…所有权
Rust的核心特性就是所有权所有程序在运行时都必须管理他们使用计算机内存的方式 有些语言有垃圾收集机制在程序运行时他们会不断地寻找不再使用的内存在其他语言中程序员必须显式的分配和释放内存 Rust采用了第三种方式 内存是通过一个所有权系统来管理的其中包含一组编译器在编译时检查的规则在程序运行时所有权特性不会减慢程序的运行速度。
Stack VS Heap
在像Rust这样的系统级编程语言里一个值是在还是在heap上对语言的行为和你为什么要做某些决定是有更大的影响的在你的代码运行的时时候Stack和Heap都是你可用的内存但他们的结构很不相同
存储数据
Stack按值的接收顺序来存储按相反的顺序将他们移除(后进先出) 添加数据叫压入栈移除数据叫弹出栈 所有存储在stack上的数据必须拥有已知的固定的大小 编译时大小位置的数据或运行时大小可能发生变化的数据必须存放在heap上 Heap内存组织性差一些 当你把数据放入heap时你会请求一定数量的空间操作系统在heap里找到一块足够大的空间把它标记为在用并返回一个指针也就是这个空间的地址这个过程叫做在heap上进行分配有时仅仅称为分配 把值压到stack上不叫分配因为指针是已知固定大小的可以把指针放在stack上 但如果想要实际数据你必须使用指针来定位 把数据压到stack上比在heap上分配快得多 因为操作系统不需要寻找用来存储新数据的空间那个位置永远都是在stack的顶端 在heap上分配空间需要做更多的工作 操作系统首先需要找到一个足够大的空间来存放数据然后要做号记录方便下次分配
访问数据
访问heap中的数据要比访问stack中的数据慢因为需要通过指针才能找到heap中的数据 对于现代的处理器来说由于缓存的缘故如果指针在内存中跳转的次数越少那么速度就越快 如果数据存放的距离比较近那么处理器的处理速度就会快一些(stack上)如果数据之间存放的距离比较远那么处理速度就会慢一些(heap上) 在heap上分配大量的空间也是需要时间的
函数调用
当你的代码调用函数时值被传入函数(也包括指向heap的指针)。函数本地的变量被压到stack上。当函数结束后这些值会从stack上弹出
所有权存在的原因
所有权存在的原因 追踪代码的那些部分正在使用heap的哪些数据最小化heap上的重复数据量清理heap上未使用的数据以避免空间不足
所有权规则
每个值都有一个变量这个变量是该值的所有者每个值同时只能有一个所有者当所有者超出作用域(scope)时该值将被删除
变量作用域
Scope就是程序中一个项目的有效范围
fn main(){// s不可用let s hello; // s可用// 可以对s进行相关操作
}// s作用域到此结束s不再可用String类型
String比那些基础标量数据类型更复杂字符串字面值程序里手写的那些字符串值。他们是不可变的Rust还有第二种字符串类型String。 在heap上分配。能够存储在编译时未知数量的文本
创建String类型的值
可以使用from函数从字符串字面值创建出String类型
fn main(){let mut s String::from(Hello); // ::表示from是String类型下的函数s.push_str(,world);println!({},s);
}这类字符串是可以被修改的 因为他们处理内存的方式不同
内存和分配
字符串字面值在编译时就知道他的内容了其文本内容直接被硬编码到最终的可执行文件里 速度快高效。是因为其不可变性 String类型为了支持可变性需要在heap上分配内存来保存编译时未知的文本内容 操作系统必须在运行时来请求内存 这步通过调用String::from来实现 当用完String后需要使用某种方式将内存返回给操作系统 这步在拥有GC的语言中GC会跟踪并清理不再使用的内存没有GC就需要我们识别内存何时不再使用并调用代码将他返回 如果忘了就会浪费内存如果提前做了变量就会非法如果做了两次也是Bug。必须一次分配对应一次释放 Rust采用了不同的方式对于某个值来说当拥有它的变量走出作用范围时内存会立即自动的交换给操作系统。
变量和数据交互的方式移动(Move)
多个变量可以与同一个数据使用一种独特的方式来交互
fn main(){let x 5;let y x;// 整数是已知且固定大小的简单的值这两个5被压到了stack中
}String版本
fn main(){let s1 String::from(hello);let s2 s1;
}一个String由三部分组成 一个指向存放字符串内容的内存的指针一个长度一个容量 上面这些东西放在stack上存放字符串内容的部分在heap上长度len就是存放字符串内容所需的字节数容量capacity是指String从操作系统总共获得的内存的总字节数 当把s1赋给s2String的数据被复制了一份 在stack上复制了一份指针长度容量并没有复制指针所指向的heap上的数据 当变量离开作用域时Rust会自动调用drop函数并将变量使用的heap内促释放当s1s2离开作用域时他们都会尝试释放相同的内存 二次释放(double free)bug 为了保证内存安全 Rust没有尝试复制被分配的内存Rust让s1失效 当s1离开作用域的时候Rust不需要释放任何东西
fn main(){let s1 String::from(hello);let s2 s1;// 此时我们去使用s1println!({},s1);
}会报错 浅拷贝(shallow copy)深拷贝(deep copy)你也许会将复制指针长度容量视为浅拷贝但由于Rust让s1失效了所以我们用一个新的术语移动(Move)隐含的一个设计原则Rust不会自动创建数据的深拷贝 就运行时性能而言任何自动赋值的操作都是廉价的
变量和数据交互的方式克隆(Clone)
如果真想对heap上面的String数据进行深度拷贝而不仅仅是stack上的数据可以使用clone方法
fn main(){let s1 String::from(hello);let s2 s1.clone();println!!({},{},s1,s2);
}Stack上的数据复制
fn main(){let x 5;let y x;println!({},{},x,y);
}Copy trait可以用于像整数这样完全存放在stack上面的类型如果一个类型实现了Copy这个trait那么旧的变量在赋值后仍然可用如果一个类型或者该类型的一部分实现了drop trait那么Rust不允许让他再去实现Copy trait了
一些拥有Copy trait的类型
任何简单标量的组合类型都可以是Copy的任何需要分配内存或某种资源的都不是Copy的一些拥有Copy trait的类型 所有的整数类型例如u32boolchar所有的浮点类型例如f64tuple(元组)如果其所有的字段都是Copy的
所有权与函数 在语义上将值传递给函数和把值赋给变量是类似的 将值传递给函数将发生移动或复制 fn main(){let s String::from(Hello World);take_ownership(s); // s失效因为函数结束后会调用drop方法let x 5;makes_copy(x); // 因为分配在stack中无事发生println!(x:{},x)
}fn take_ownership(some_string:String){println!({},some_string);
}fn makes_copy(some_number:i32){println!({},some_number);
}返回值与作用域
函数在返回值的过程中同样也会发生所有权的转移
fn main() {let s1 gives_ownership();let s2 String::from(hello);let s3 takes_and_gives_back(s2);
}fn gives_ownership() - String {let some_string String::from(hello);some_string
}fn takes_and_gives_back(a_string:String) - String {a_string
}一个变量的所有权总是遵循同样的模式 把一个值赋给其他变量时就会发生移动当一个包含了heap数据的变量离开作用域时他的值就会被drop函数清理除非数据的所有权移动到另一个变量上了
如何让函数使用某个值但不获得其所有权
fn main(){let s1 String::from(hello);let (s2,len) calculate_length(s1);println!(the length of {} is {},s2,len);
}fn calculate_length(s:String) - (String,usize){let length s.len();(s,length)
}Rust有一个特性叫做引用(Reference)
引用和借用
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();
}参数的类型是String而不是String符号就表示引用允许你引用某些值而不取得其所有权我们把引用作为函数参数的这个行为叫做借用是否可以修改借用的东西 不行 和变量一样引用默认也是不可变的
可变引用
fn main(){let mut s1 String::from(Hello);let len calculate_length(mut s1);println!(the length of {} is {},s1,len);
}fn calculate_length(s:mut String) - usize {s.push_str(,world);s.len()
}可变引用有一个重要的限制在特定作用域内对某一块数据只能有一个可变的引用 这样做的好处是可在编译时防止数据竞争 以下三种行为下会发生数据竞争 两个或多个指针同时访问同一个数据至少有一个指针用于写入数据没有使用任何机制来同步对数据的访问 我们可以通过创建新的作用域来允许非同时的创建多个可变引用
fn main(){let mut s String::from(Hello);{let s1 mut s;}let s2 mut s;
}不可以同时拥有一个可变引用和不可变引用多个不可变的引用是可以的
悬空引用(Dangling References)
悬空指针(Dangling Pointer)一个指针引用了内存中的某个地址而这块内存可能已经释放并分配给其它人使用了在Rust里编译器可保证引用永远都不是悬空引用。 如果你引用了某些数据编译器将保证在引用离开作用域之前数据都不会离开作用域
引用的规则
在任何给定的时刻只能满足下列条件之一 一个可变的引用任意数量不可变的引用 引用必须一直有效
切片
Rust的另外一种不持有所有权的数据类型切片(Slice)字符串切片是指向字符串中一部分内容的引用形式[开始索引…结束索引] (左闭右开)
注意
字符串切片的索引范围必须发生在有效的UTF-8字符边界内如果尝试从一个多字节的字符中创建字符串切片程序会报错并退出
将字符串切片作为参数传递
采用str作为参数类型这样就可以同时接受String和str类型的参数了定义函数时使用字符串切片来代替字符串引用会使我们的API更加通用且不会损失任何功能。