Rust的常数、作用域与所有权

news2024/9/20 22:40:58

【图书介绍】《Rust编程与项目实战》-CSDN博客

《Rust编程与项目实战》(朱文伟,李建英)【摘要 书评 试读】- 京东图书 (jd.com)

Rust到底值不值得学,之一  -CSDN博客

Rust到底值不值得学,之二-CSDN博客

Rust的数据类型-CSDN博客

3.7  常数的数据类型

在Rust语言中,变量有类型,常量也有类型。我们知道,在定义const常量的时候,就要确定数据类型,这个问题不大,而直接常量如何确定类型呢?首先想个问题,为什么要把常量分为不同的类型呢?这是因为在程序中出现的常量需要存放在计算机内存的存储单元中。如果确定了数据的类型,也就能确定应该分配给它多少字节,按什么方式存储。例如,程序中有整数100,默认情况下,Rust编译器会分配给它4字节,按补码方式存储。那么怎样确定常量的类型呢?

从常量的表示形式即可判定其类型。对于字符常量很简单,只要看到由单撇号引起来的单个字符或转义字符就是字符常量。下面我们来讲一下数值常量。

(1)整数。整数在Rust中有一种特殊的表达方式,比如let a =33u16,因为u8、i8、u16、i32等都可以表示33,所以不指定类型的话,只有33,Rust就不知道它的精度是多少,于是let a=33会自动将a推断成i32。总之,对于没有明确指定整数类型的整数,且其值在i32范围内,Rust默认就认为它是i32类型。如果超出i32范围,且在i64范围内,则默认它是i64类型。

如果在整数后面加上u16类型,那么Rust就知道这是个u16类型的整数。对于let a=33u16,可以知道a是一个u16类型的变量,这和let a:u16=33的作用相同。当然,let a:u16=33u16也可以,只不过有点多此一举。但是let a:u16=33u32这种方式不可行,因为前后矛盾了。同样,如果在整数后面加上i8,且该整数没超出i8范围,就认为它是i8类型。

以上整数都是用十进制表示的,我们也可以使用二进制、八进制或十六进制创建整数,比如:

fn main() {

    let a = 33u16;

    let b: i32 = 0b11_01_10_11;                     // 二进制

    let c = 0o567i64;                                // 八进制

    let d = 0xFFFFu32;                               // 十六进制

    println!("{} {} {} {}", a, b, c, d);           // 输出:33 219 375 65535

}

(2)浮点数。凡以小数形式或指数形式出现的实数,都是浮点型常量,在内存中都以指数形式存储。例如,10是整型常量,10.0是浮点型常量。那么对浮点型常量是按f32处理还是按f64处理呢?Rust编译器把浮点型常量都按f64处理,分配8字节。

如果要明确指定浮点数类型,可以在后面加上类型,比如3.14159f32,这样就按f32处理了。

3.8  作  用  域

Rust的所有权系统和作用域息息相关,因此有必要先理解Rust的作用域规则。在Rust中,任何一个可用来包含代码的大括号都是一个单独的作用域。类似于Struct{}这样用来定义数据类型的大括号,不在讨论范围之内,本章后面所说的大括号也都不考虑这种大括号。以下几种结构中的大括号都有自己的作用域:

(1)if、while等流程控制语句中的大括号。

(2)match模式匹配的大括号。

(3)单独的大括号。

(4)函数定义的大括号。

(5)mod定义模块的大括号。

例如,可以单独使用一个大括号来开启一个作用域:

{                                         // s在这一行无效,因为它尚未声明

  let s = "hello";                     // 从这行起,s是有效的

  println!("{}", s);                   // 使用s

  println!("hello,world");            // 这行没有用到s,但s依然是有效的

}                                         // 到了这行,此作用域已结束,s不再有效

上面的代码中,变量s绑定了字符串字面值,在跳出作用域后,变量s失效,变量s所绑定的值会自动被销毁。

实际上,变量跳出作用域失效时,会自动调用Drop trait的drop函数来销毁该变量绑定在内存中的数据,这里特指销毁堆和栈上的数据,而字符串字面量是存放在全局内存中的,它会在程序启动到程序终止期间一直存在,不会被销毁。可通过如下代码验证:

fn main(){

    {

      let s = "hello";

      println!("{:p}", s);  // 0x7ff6ce0cd3f8

    }



    let s = "hello";

    println!("{:p}", s);  // 0x7ff6ce0cd3f8

}

因此,上面的示例中只是让变量s失效了,仅此而已,并没有销毁s所绑定的字符串字面量。但一般情况下不考虑这些细节,而是照常描述为跳出作用域时,会自动销毁变量所绑定的值。

任意大括号之间都可以嵌套。例如,可以在函数定义的内部再定义函数,在函数内部使用单独的大括号,在函数内部使用mod定义模块,等等。示例如下:

fn main(){

  fn ff(){

    println!("hello world");

  }

  ff();



  let mut a = 33;

  {

    a += 1;

  }

  println!("{}", a);  // 结果输出:34

}

虽然任何一种大括号都有自己的作用域,但函数作用域比较特别。在函数作用域内无法访问函数外部的变量,而其他大括号的作用域可以访问大括号外部的变量。比如:

fn main() {

  let x = 32;

  fn f(){

    // 编译错误,不能访问函数外面的变量x和y

    // println!("{}, {}", x, y); 

  }

  let y = 33;

  f();



  let mut a = 33;

  {

    // 可以访问大括号外面的变量a

    a += 1;

  }

  println!("{}", a);  //结果输出:34

}

在Rust中,能否访问外部变量称为捕获环境。比如,函数是不能捕获环境的,而大括号可以捕获环境。对于可捕获环境的大括号作用域,要注意Rust的变量遮盖行为。分析下面的代码:

fn main(){

  let mut a = 33;

  {

    a += 1;           // 访问并修改外部变量a的值



    // 又声明变量a,这会发生变量遮盖现象

    // 从此开始,大括号内访问的变量a都是该变量

    let mut a = 44;

    a += 2;

    println!("{}", a);             // 输出46

  }                                 // 大括号内声明的变量a失效

  println!("{}", a);               // 输出34

}

这种行为和其他语言不太一样,因此这种行为需要引起注意。

3.9  所  有  权

3.9.1  让我们回忆栈和堆

在学习C/C++时,老师经常出某变量被分配在栈上还是堆上的题目,几乎每次都有很大一批同学在这种题目上失手。栈和堆都是代码在运行时可供使用的内存,但是它们的结构不同。栈是一种后进先出(Last In First Out,LIFO)的数据结构,栈中的所有数据都必须占用已知且固定大小的内存。堆就好理解了,它是一个没有组织的结构,想怎么使用就怎么使用,只要堆够大,就可以申请一段内存空间,然后把这段内存标记为已使用,并得到指向这段内存开头的指针;当不再使用时,再将这段内存标记为未使用。当声明一个指针但并没有分配空间时,这个指针是空指针;当内存已经标记为未使用,而指针依然指向这段空间时,这个指针就是野指针。

C++中的堆和栈定义如下。

  • 堆:由程序员手动分配和释放,完全不同于数据结构中的堆,分配方式类似于链表。由malloc或者new来分配,由free和delete来释放。若程序员不释放,则程序结束时由系统释放。
  • 栈:由编译器自动分配和释放,存放函数的参数值、局部变量的值等。栈里面变量的内存必须是已知且固定大小的。函数调用时,参数、本地变量、指向堆的指针都压入一个栈,函数完成时退出。操作方式类似于数据结构中的栈(C和Python中也有,只要基于C的都有这个概念)。

其实分辨起来很容易,动态分配的变量就是在堆上,其他的都在栈上。

栈是一个成熟的结构,基本不会引发内存的问题,而没有组织的堆却很容易引发内存问题。垃圾回收和所有权都是为了解决堆的内存管理问题。

这就是C++相比于垃圾回收机制语言的优势,灵活高效,但是也会带来内存安全问题。

3.9.2  什么是所有权

计算机程序必须在运行时管理它们所使用的内存资源。大多数编程语言都有管理内存的功能:C/C++这样的语言主要通过手动方式管理内存,开发者需要手动申请和释放内存资源。但为了提高开发效率,只要不影响程序功能的实现,许多开发者没有及时释放内存的习惯。所以手动管理内存的方式常常造成资源浪费。

Java语言编写的程序在Java虚拟机(Java Virtual Machine,JVM)中运行,JVM具备自动回收内存资源的功能。但这种方式常常会降低运行时效率,所以JVM会尽可能少地回收资源,这样也会使程序占用较大的内存资源。

所有权对大多数开发者而言是一个新颖的概念,它是Rust语言为高效使用内存而设计的语法机制。所有权概念是为了让Rust在编译阶段更有效地分析内存资源的有用性以实现内存管理而诞生的概念。

Rust的所有权是一个跨时代的理念,是内存管理的第二次革命。较低级的语言依赖程序员分配和释放内存,一不小心就会出现空指针、野指针破坏内存;较高级的语言使用垃圾回收机制管理内存,在程序运行时不断地寻找不再使用的内存,虽然安全,却加重了程序的负担。Rust的所有权理念横空出世,通过所有权系统管理内存,编译器在编译时会根据一系列的规则进行检查,在运行时,所有权系统的任何功能都不会减慢程序,把安全的内存管理推向了零开销的新时代。

所有权概念是Rust语言的一个重要特性,因为通过它才使得Rust的“安全”“高并发”得以发挥出优势。因为它让Rust无须垃圾回收,即可保障内存安全。对于C/C++程序员来说,可能一直在跟内存安全打交道,如内存泄露、智能指针等。对于别的语言来说,会有垃圾回收机制。例如Python的垃圾回收机制,有“标记清除”“分代回收”等方式。这两种方式各有优缺点。Rust则是通过所有权和借用来保证内存安全的。很多人不理解为什么Rust是内存安全的,其实就是在默认情况下,是写不出内存不安全的代码的。

Rust的所有权并不难理解,它有且只有如下三条规则:

(1)Rust中的每个值都有一个被称为其所有者的变量(即值的所有者是某个变量)。

(2)值在任一时刻有且只有一个所有者。

(3)当所有者(变量)离开作用域时,这个值将被销毁。

这里对第三点做一些补充性的解释,所有者离开作用域会导致值被销毁,这个过程实际上是调用一个名为drop的函数来销毁数据释放内存的。在前面解释作用域规则时曾提到过,销毁的数据特指堆栈中的数据,如果变量绑定的值是全局内存区内的数据,则数据不会被销毁。例如:

fn main(){

  {

    let mut s = String::from("hello");

  } // 跳出作用域,栈中的变量s将被销毁,其指向的堆中的数据也被销毁

    // 但全局内存区的字符串字面量仍被保留

}

Rust中的每个值都有一个所有者,但这个说法比较容易产生误会。例如:

#![allow(unused)]

fn main() {

let s = String::from("hello");

}

很多人可能会误以为变量s是堆中字符串数据hello的所有者,但实际上不是。String字符串的实际数据在堆中,但是String大小不确定,所以在栈中使用一个胖指针结构来表示这个String类型的数据,这个胖指针中的指针指向堆中的String的实际数据。也就是说,变量s的值是那个胖指针,而不是堆中的实际数据。因此,变量s是那个胖指针的所有者,而不是堆中实际数据的所有者。但是,由于胖指针是指向堆中数据的,很多时候为了简化理解,简化描述方式,经常会说s是哪个堆中实际数据的所有者。但无论如何描述,都需要理解所有者和值之间的真相。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2111828.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Linux学习笔记12---主频和时钟配置实验

本章学习 I.MX6U 的时钟系统,学习如何配置 I.MX6U 的 系统时钟和其他的外设时钟,使其工作频率为 528MHz ,其他的外设时钟源都工作在 NXP 推荐的频率。 1、MX6U 时钟系统详解 I.MX6U 的系统主频为 528MHz,有些型号可以跑到 69…

2工作队列

工作队列 逻辑图 <!-- SpringBoot 消息队列的起步依赖 --> <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-amqp</artifactId> </dependency>轮询分发 Round-robin 生产者 import com…

[数据集][目标检测]人脸口罩佩戴目标检测数据集VOC+YOLO格式8068张3类别

数据集格式&#xff1a;Pascal VOC格式YOLO格式(不包含分割路径的txt文件&#xff0c;仅仅包含jpg图片以及对应的VOC格式xml文件和yolo格式txt文件) 图片数量(jpg文件个数)&#xff1a;8068 标注数量(xml文件个数)&#xff1a;8068 标注数量(txt文件个数)&#xff1a;8068 标注…

Fortran程序辅助构建(Python)

目的 Visual Studio用不明白&#xff0c;于是我找了一个Fortran解释器&#xff08;大概&#xff09;&#xff0c;接着了解到cmd也是可以直接运行Fortran的&#xff0c;于是VScode就又得1分。但是每次构建都得敲命令&#xff0c;后来我就写了一个脚本&#xff0c;专门解决这个痒…

【人工智能/机器学习/机器人】数学基础-学习笔记

函数 奇偶性&#xff1a; 偶函数&#xff1a; f ( − x ) f ( x ) f(-x)f(x) f(−x)f(x)   y轴对称 f ( x ) x 2 f(x)x^2 f(x)x2     f ( − x ) ( − x ) 2 x 2 f ( x ) f(-x)(-x)^2x^2f(x) f(−x)(−x)2x2f(x) 奇函数&#xff1a; f ( − x ) − f ( x ) f(-…

如何制作新生资料收集系统?

新学年伊始&#xff0c;学校需要高效收集学生信息和证件照。易查分提供了一个便捷的解决方案&#xff0c;通过创建一个集成信息和图片的收集系统&#xff0c;可以快速完成这项工作&#xff0c;并将信息导出为PDF&#xff0c;方便打印和存档。 制作步骤如下&#xff1a; 1. 准备…

Android Studio打开Modem模块出现:The project ‘***‘ is not a Gradle-based project

花了挺长时间处理该问题&#xff0c;特记录如下&#xff1a;1.背景&#xff1a; 在Android studio 下导入一个新增的modem模块&#xff0c;如MPSS.DE.3.1.1\modem_proc\AAA, 目的是看代码方便一些&#xff0c;可以自由搜索各种关键字。但导入该项目时出现了如下错误&#xff1a…

C++ 封装 DLL 供 Unity 调用

一&#xff1a;封装DLL 开发工具最好使用 Visual Studio 20XX 来制作&#xff0c;因为VS Code 需要配置很多东西&#xff0c;环境搭建过程比较复杂。 a、我安装的是 Visual Studio 2022&#xff0c;安装的时候&#xff0c;【工作负荷】记得勾选 【使用C的桌面开发】和【使用C的…

dubbo 服务消费原理分析之引用服务配置

文章目录 前言一、服务监听ContextRefreshedEvent1、AbstractApplicationContext.refresh2、AbstractApplicationContext.finishRefresh3、DubboDeployApplicationListener.onApplicationEvent4、DefaultModuleDeployer .referServices5、SimpleReferenceCache.get 二、引用服务…

SRT库介绍

文章目录 简介SRT协议介绍FFmpegSRS推拉流测试SRT库介绍apps示例程序srt-file-transmitsrt-live-transmitsrt-test-multiplexsrt-test-relaysrt-tunnel docs/buildsrtcoreexamples编译 安装错误处理 API说明初始化、回收创建配置套接字连接管理Socket Group属性设置传输数据统计…

CNC数控加工如何开启个性化制造新时代?

在现代制造业中&#xff0c;CNC 数控加工定做正以其独特的特点和显著的优势&#xff0c;成为满足各种复杂、高精度加工需求的首选方式。与时利和一起了解CNC 数控加工定做是如何开启个性化制造新时代! 一、CNC 数控加工定做的特点 1.高精度加工 CNC 数控加工依靠先进的计算机控…

Java并发编程实战 04 | 使用WaitNotify时要注意什么?

在 Java 中&#xff0c;wait()、notify() 和 notifyAll() 方法在多线程编程中主要用于线程间的协作和同步。理解这些方法的使用特点对于编写稳定的多线程程序至关重要。我们将从以下三个问题入手深入探讨它们的使用&#xff1a; 为什么必须在 synchronized 代码块中使用 wait(…

字体反爬(一)

网址 http://xxfb.mwr.cn/sq_djdh.html?v1.0 获取相关数据 解决 F12 先找接口吧&#xff0c; 搜索一下表格的数据 直接从表格中复制 复制过来乱码&#xff0c;基本锁定有字体反爬处理 先点进去看看 {"addvnm": "#GkcERlldm4_1725629424756otltag㯼㢴#Fon…

Linux 技巧汇编

10个重要的Linux ps命令实战 显示所有当前进程 根据用户过滤进程 通过cpu和内存使用来过滤进程 通过进程名和PID过滤 根据线程来过滤进程 树形显示进程 显示安全信息 格式化输出root用户&#xff08;真实的或有效的UID&#xff09;创建的进程 使用PS实时监控进程状态 …

泛型列表相关知识

集合 C#中集合是指在system.Collection下的类型&#xff0c;他们大多数是通过实现此命名空间下的接口来实现的。 C#集合是来维护一组对象的数据结构&#xff0c;与数组不同&#xff0c;集合包含更多的功能。如&#xff1a;自动添加元素到指定位置&#xff0c;排序等。 泛型集…

企业级WEB应用服务器---TOMACT

一、WEB技术介绍 1.1 Http和B/S结构 操作系统一般都有子进程系统&#xff0c;使用多进程就可以充分利用硬件资源&#xff0c;提高效率。在前面的学习中我们了解到进程中可以有多个线程&#xff0c;且每一个线程都可以被CPU调度执行&#xff0c;这样就可以让程序并行执行。一台…

深入浅出孪生神经网络,高效训练模型

大家好&#xff0c;在深度学习领域&#xff0c;神经网络几乎能处理各种任务&#xff0c;但通常需要依赖于海量数据来达到最佳效果。然而&#xff0c;对于像面部识别和签名验证这类任务&#xff0c;我们不可能总是有大量的数据可用。由此产生了一种新型的神经网络架构&#xff0…

【自考zt】【数据结构】【22.04】

一、单选 二、填空 三、解答 四、算法阅读 五、算法设计

【Flutter】解决第一次运行项目很慢(gradle需要下载依赖)

配置gradle默认下载路径 默认下C盘谁顶得住 配置环境变量 名称: GRADLE_USER_HOME 值: D:\Develop\gradle 自己创建一个 下边是重点 配置gradle远端下载地址 后边版本号自己换 https://mirrors.cloud.tencent.com/gradle/ https://mirrors.cloud.tencent.com/gradle/gradl…

Matlab 一维层状声子晶体振动传输特性

一维声子晶体的传递矩阵法是一种用于研究声波在一维周期性结构中传播的方法。这种方法基于‌波动方程和周期性边界条件&#xff0c;通过计算声波在不同介质中的传播特性&#xff0c;进而分析声子晶体的带隙结构。传递矩阵法可以有效地预测声波在一维声子晶体中的传播行为&#…