当前位置: 首页 > article >正文

【Rust 精进之路之第5篇-数据基石·下】复合类型:元组 (Tuple) 与数组 (Array) 的定长世界

系列: Rust 精进之路:构建可靠、高效软件的底层逻辑
作者: 码觉客
发布日期: 2025-04-20

引言:从原子到分子——组合的力量

在上一篇【数据基石·上】中,我们仔细研究了 Rust 的四种基本标量类型:整数、浮点数、布尔值和字符。它们就像构成物质世界的基本原子,各自拥有明确的特性和表示范围。然而,仅有原子是不够的,我们需要将它们组合起来,才能构建出更有意义、更复杂的结构,就像原子组成自分子一样。

Rust 提供了多种方式来组合基本类型,形成更复杂的数据结构。本篇我们将首先聚焦于两种最基础的复合类型 (Compound Types)元组 (Tuple)数组 (Array)。这两种类型都用于将多个值组合成一个单一的类型,但它们在使用场景和特性上有所不同。

元组允许你将不同类型的值组合在一起,形成一个固定的、有序的集合,非常适合用来传递或返回一组相关但类型可能不同的数据。而数组则要求所有元素必须具有相同类型,并且长度在编译时就已固定,适用于存储一系列同质的数据。

理解元组和数组的特性、用法以及它们与 Rust 所有权、内存布局的关系,是掌握 Rust 数据组织方式的基础。让我们一起探索这两个构建复杂数据结构的“初级粘合剂”。

一、元组 (Tuple):异构元素的有序组合

想象一下,你需要从一个函数返回两个相关但类型不同的值,比如一个学生的姓名(字符串)和他的年龄(整数)。在某些语言中,你可能需要定义一个小的结构体或者返回一个包含这两个值的对象。在 Rust 中,元组 (Tuple) 提供了一种更轻量、更直接的方式来处理这种情况。

元组是一个固定长度的、有序的元素集合,其中的元素可以是不同类型的。

创建元组:
元组通过将一系列值用逗号 ( ,) 分隔,并整体用圆括号 (()) 包裹起来创建。

fn main() {// 创建一个包含不同类型元素的元组// Rust 会推断出类型为 (i32, f64, u8)let tup = (500, 6.4, 1);// 也可以显式标注类型let point: (f32, f32, f32) = (1.0, 2.5, -0.8);// 元组本身也是一个类型let student_info: (&str, u8, bool) = ("Alice", 18, true); // (姓名, 年龄, 是否活跃)println!("元组 tup 的值: {:?}", tup); // 使用 {:?} (Debug trait) 来打印元组// 输出: 元组 tup 的值: (500, 6.4, 1)println!("三维空间点: {:?}", point);// 输出: 三维空间点: (1.0, 2.5, -0.8)println!("学生信息: {:?}", student_info);// 输出: 学生信息: ("Alice", 18, true)// 特殊元组:单元组 ()let unit = (); // 空元组,也称为“单元类型 (unit type)”// 它代表一个没有值的类型,常用于表示函数没有返回值 (或隐式返回)println!("单元类型的值: {:?}", unit); // 输出: ()
}

访问元组成员:解构与索引

有两种主要方式可以访问元组中的元素:

  1. 解构 (Destructuring): 使用 let 语句,通过模式匹配将元组“拆开”成单独的变量。这是最常用的方式,代码清晰易懂。

    fn main() {let student_info = ("Bob", 20, false);// 使用 let 解构元组let (name, age, is_active) = student_info;println!("姓名: {}", name);     // 输出: Bobprintln!("年龄: {}", age);      // 输出: 20println!("是否活跃: {}", is_active); // 输出: false// 如果你只关心部分元素,可以使用 _ 来忽略其他元素let (_, age_only, _) = student_info;println!("只关心年龄: {}", age_only); // 输出: 20
    }
    
  2. 通过索引访问: 使用点号 (.) 后跟元素的从 0 开始的索引来直接访问。

    fn main() {let numbers = (10, 20, 30);let first = numbers.0;  // 访问第一个元素 (索引 0)let second = numbers.1; // 访问第二个元素 (索引 1)// let third = numbers.2; // 访问第三个元素 (索引 2)println!("第一个数字: {}", first);   // 输出: 10println!("第二个数字: {}", second);  // 输出: 20// 注意:索引必须是编译时确定的字面量,不能是变量// let index = 1;// let value = numbers.index; // 编译错误!
    }
    

元组的特点与适用场景:

  • 固定长度: 一旦声明,元组的长度(元素个数)就确定了,不能增加或减少。
  • 异构性: 可以包含不同类型的元素。
  • 轻量级: 创建和传递元组通常比定义一个专门的结构体更简单快捷。
  • 内存布局: 元组的元素在内存中是连续存储的,其大小在编译时可知。它们通常存储在栈 (Stack) 上(除非包含堆分配的数据,如 String)。

元组非常适合用于:

  • 函数返回多个值: 这是元组最常见的用途之一。
    fn calculate_stats(numbers: &[i32]) -> (i32, i32, f64) { // 返回 (最小值, 最大值, 平均值)if numbers.is_empty() {return (0, 0, 0.0); // 或者返回 Option<(...)> 可能更好}let mut min = numbers[0];let mut max = numbers[0];let mut sum = 0.0;for &num in numbers {if num < min { min = num; }if num > max { max = num; }sum += num as f64;}(min, max, sum / numbers.len() as f64)
    }fn main() {let data = [1, 5, 2, 8, 3];let (min_val, max_val, avg_val) = calculate_stats(&data);println!("Min: {}, Max: {}, Avg: {}", min_val, max_val, avg_val);
    }
    
  • 临时组合相关数据: 当你只是临时需要将几个相关的、类型可能不同的值打包在一起传递或处理,而不想为此专门定义一个结构体时。

元组提供了一种灵活且高效的方式来组织小规模的、异构的数据集合。

二、数组 (Array):同质元素的定长序列

与元组不同,数组 (Array) 要求其所有元素必须具有相同的类型。同时,数组也具有固定的长度,这个长度在编译时就必须确定。

创建数组:
数组通过将一系列相同类型的值用逗号 ( ,) 分隔,并整体用方括号 ([]) 包裹起来创建。

fn main() {// 创建一个包含 5 个 i32 类型元素的数组let numbers = [1, 2, 3, 4, 5]; // 类型推断为 [i32; 5]// 显式标注类型:[类型; 长度]let months: [&str; 12] = ["January", "February", "March", "April", "May", "June","July", "August", "September", "October", "November", "December"];// 创建一个包含 500 个相同元素的数组// 语法:[初始值; 长度]let zeros = [0; 500]; // 创建一个包含 500 个 0 的数组,类型 [i32; 500] (i32 是默认整数类型)let flags: [bool; 10] = [true; 10]; // 创建一个包含 10 个 true 的数组println!("第一个数字: {}", numbers[0]); // 输出: 1println!("第三个月份: {}", months[2]); // 输出: Marchprintln!("zeros 数组的长度: {}", zeros.len()); // 输出: 500println!("flags 数组的第一个元素: {}", flags[0]); // 输出: true
}

访问数组元素:
数组元素通过方括号 ([]) 内的索引来访问。索引同样是从 0 开始,且必须是 usize 类型

fn main() {let primes = [2, 3, 5, 7, 11]; // 类型 [i32; 5]let first_prime = primes[0]; // 访问索引 0let third_prime = primes[2]; // 访问索引 2println!("第一个素数: {}", first_prime); // 输出: 2println!("第三个素数: {}", third_prime); // 输出: 5// 使用变量作为索引 (必须是 usize)let index: usize = 4;println!("索引 {} 处的素数: {}", index, primes[index]); // 输出: 11// 数组越界访问:运行时检查// let invalid_index = 10;// let value = primes[invalid_index]; // 这行代码会编译通过,但在运行时会 panic!// 推荐使用 get 方法进行安全的索引访问,它返回一个 Optionlet maybe_value = primes.get(10);match maybe_value {Some(value) => println!("获取到值: {}", value),None => println!("索引 10 超出范围!"), // 输出: 索引 10 超出范围!}let valid_value = primes.get(1);println!("安全获取索引 1 的值: {:?}", valid_value); // 输出: Some(3)
}

数组越界:Rust 的安全保障

访问数组时,如果你使用的索引超出了数组的有效范围(即大于或等于数组长度),Rust 会如何处理?

  • 编译时检查: 如果索引是一个编译时就能确定越界的常量,编译器可能会报错。
  • 运行时检查: 对于运行时才能确定的索引(如变量),Rust 会在每次数组访问时进行边界检查。如果检查发现索引无效,程序会立即 panic (崩溃)

这种运行时边界检查是 Rust 内存安全保证的重要组成部分。它确保了你不会意外地访问到数组之外的无效内存(这在 C/C++ 中是常见的安全漏洞来源,如缓冲区溢出)。虽然每次访问都有微小的性能开销,但 Rust 认为这种安全性是值得的。在性能极其敏感的场景下,可以使用 unsafe 代码块和 get_unchecked 方法来绕过边界检查,但这需要开发者自行承担保证索引有效的责任。

数组的特点与适用场景:

  • 固定长度: 长度在编译时确定,存储在类型信息中 ([T; N])。这意味着数组的大小不能在运行时改变。
  • 同质性: 所有元素必须是相同类型 T
  • 栈分配 (通常): 由于大小固定且在编译时可知,数组通常直接分配在栈 (Stack) 上。这使得数组的创建和访问非常快速。如果数组非常大,或者元素本身是堆分配的类型(如 String),情况会复杂些,但数组本身的元数据(指向数据的指针和长度)通常仍在栈上。
  • 内存连续: 数组的元素在内存中是紧密、连续存储的,这对于缓存友好性(CPU Cache Locality)和某些底层操作(如 SIMD)非常有利。

数组适用于:

  • 当你确切知道集合需要包含多少个元素,并且这个数量在程序运行期间不会改变时。
  • 存储一系列类型相同的数据,例如:
    • 月份名称、星期几
    • 固定大小的缓冲区
    • 表示颜色 (RGB 值 [u8; 3]) 或坐标 ([f64; 2])
    • 小型查找表

数组与 Vec 的区别(预告):
如果你需要一个长度可变的、可以动态增长或缩小的集合,那么 Rust 的数组 (Array) 并不适用。你需要的是另一种更灵活的数据结构——向量 (Vector, Vec<T>)Vec 是一个在堆 (Heap) 上分配内存的、可增长的数组类型,我们将在后续介绍集合类型的章节中详细学习它。现在只需记住:固定长度用数组 [T; N],可变长度用向量 Vec<T>

六、复合类型与所有权

元组和数组本身也遵循 Rust 的所有权规则:

  • 移动 (Move): 如果元组或数组的元素类型是实现了 Copy Trait 的(如标量类型),那么将元组或数组赋值给另一个变量时会发生复制。如果元素类型没有实现 Copy(如 String),则会发生所有权的移动。

    fn main() {// 包含 Copy 类型的元组和数组 - 发生复制let t1 = (1, true);let t2 = t1; // t1 的副本被赋给 t2,t1 仍然可用println!("t1: {:?}", t1); // 输出: (1, true)let a1 = [10, 20];let a2 = a1; // a1 的副本被赋给 a2,a1 仍然可用println!("a1: {:?}", a1); // 输出: [10, 20]// 包含非 Copy 类型的元组和数组 - 发生移动let s1 = String::from("hello");let t3 = (s1, 1);// let t4 = t3; // t3 的所有权会移动给 t4// println!("t3: {:?}", t3); // 编译错误!t3 的所有权已移动let s_arr1 = [String::from("a"), String::from("b")];// let s_arr2 = s_arr1; // s_arr1 的所有权会移动给 s_arr2// println!("s_arr1: {:?}", s_arr1); // 编译错误!s_arr1 的所有权已移动
    }
    
  • 函数参数传递: 同样遵循所有权规则。如果传递的元组或数组包含非 Copy 类型,所有权会转移给函数。通常更推荐传递引用 (&&mut),尤其是对于较大的数组。

总结:组织数据的初级结构

本篇我们学习了 Rust 的两种基础复合类型:

  • 元组 (Tuple (T1, T2, ...)):
    • 固定长度,有序。
    • 元素可为不同类型
    • 通过解构或索引 (.0, .1) 访问。
    • 适用于函数返回多个值或临时组合异构数据。
    • 通常在栈上分配。
  • 数组 (Array [T; N]):
    • 固定长度 N,在编译时确定。
    • 元素必须为相同类型 T
    • 通过索引 ([usize]) 访问,有运行时边界检查。
    • 适用于存储固定数量的同质数据,性能好,通常在栈上分配。
    • 内存连续。

元组和数组为我们提供了组织和访问多个值的基础手段。它们与 Rust 的类型系统和所有权规则紧密结合,构成了构建更复杂数据结构(如结构体、枚举)和高效算法的基石。虽然它们的长度是固定的,限制了其灵活性,但在需要这种确定性的场景下,它们是高效且安全的选择。

FAQ:关于元组和数组的疑惑

  • Q1: 元组和只有一个元素的元组有什么区别?
    • A: 严格来说,Rust 中没有“只有一个元素的元组”。(value) 这样的写法会被编译器理解为括号包裹的表达式,其类型就是 value 本身的类型。如果你确实需要一个只包含一个元素的元组(虽然很少见),语法是 (value,)——注意那个逗号。
  • Q2: 数组的长度是类型的一部分吗?
    • A: 是的![i32; 3][i32; 4]完全不同的类型。这意味着你不能将一个长度为 3 的数组赋值给一个期望长度为 4 的数组变量,也不能将它们直接作为参数传递给期望不同长度数组的函数(除非使用泛型或切片)。
  • Q3: 既然数组有运行时边界检查,性能会比 C/C++ 数组差吗?
    • A: 边界检查确实会引入非常小的运行时开销。但在大多数情况下,这个开销是可以忽略不计的,并且它换来了巨大的安全性提升。编译器有时也能进行优化,例如在循环中如果能证明索引不会越界,可能会移除检查。与可能导致安全漏洞和崩溃的内存错误相比,这点开销通常是值得的。
  • Q4: 我什么时候应该用元组,什么时候用结构体 (Struct)?
    • A: 如果只是临时组合几个值,尤其是函数返回值,且元素的含义通过上下文或顺序就能清晰理解,元组很方便。但如果这组数据代表一个更持久、有明确含义的实体(比如一个用户、一个点),并且你想给每个字段起个有意义的名字,那么定义一个结构体 (Struct) 会是更好的选择,代码更具可读性和可维护性。我们将在后续章节学习结构体。

下一篇预告:流程的掌控者——控制流

我们已经了解了如何在 Rust 中表示和组织数据(标量类型和基础复合类型)。接下来,我们需要学习如何让程序根据条件执行不同的代码路径,或者重复执行某些任务。

下一篇:【流程之舞】控制流:if/else, loop, while, for 与模式匹配初窥。 我们将探索 Rust 如何控制代码的执行流程,并初步接触其强大的模式匹配能力在控制流中的应用。敬请期待!

相关文章:

【Rust 精进之路之第5篇-数据基石·下】复合类型:元组 (Tuple) 与数组 (Array) 的定长世界

系列&#xff1a; Rust 精进之路&#xff1a;构建可靠、高效软件的底层逻辑 作者&#xff1a; 码觉客 发布日期&#xff1a; 2025-04-20 引言&#xff1a;从原子到分子——组合的力量 在上一篇【数据基石上】中&#xff0c;我们仔细研究了 Rust 的四种基本标量类型&#xff1…...

【前端样式】用 aspect-ratio 实现等比容器:视频封面与图片占位的终极解决方案

在网页开发中&#xff0c;处理视频封面、图片卡片等需要固定比例的容器一直是前端工程师的必修课。本文将以 aspect-ratio 属性为核心&#xff0c;深入探讨如何优雅实现等比容器&#xff0c;并通过完整代码示例和常见问题解析&#xff0c;助你彻底掌握这一现代布局利器。 目录…...

【网络安全】OWASP 十大漏洞

1. OWASP 十大漏洞 为了应对未来的风险&#xff0c;安全专业人员需要随时掌握最新信息。之前&#xff0c;您了解了CVE 列表&#xff0c;这是一个公开的已知漏洞和暴露列表。CVE 列表是全球安全社区相互共享信息的重要信息来源。 在本文中&#xff0c;您将了解安全专业人士参考…...

我用deepseek做了一个提取压缩文件夹下pdf和word文件工具

由于最近需要把大量的压缩文件的pdf和word文件统一复制到一个文件夹中。 我们一般正常操作方式的是把一个压缩文件一个一个解压&#xff0c;然后在把一个的解压好的文件夹下文件复制到另外一个文件夹中。 这个也需太繁琐了&#xff0c;从以往统计的需要花费两个小时间&#x…...

【Docker】在容器中使用 NVIDIA GPU

解决容器 GPU 设备映射问题&#xff0c;实现 AI 应用加速 &#x1f517; 官方文档&#xff1a;NVIDIA Container Toolkit GitHub 常见错误排查 若在运行测试容器时遇到以下错误&#xff1a; docker: Error response from daemon: could not select device driver ""…...

机器人进阶---视觉算法(五)仿射变换和投影变换有什么区别

仿射变换和投影变换有什么区别 1. 定义2. 几何特性3. 变换矩阵4. 应用场景5. Python代码示例仿射变换投影变换6. 总结仿射变换和投影变换都是图像处理中常用的几何变换方法,但它们在变换性质、应用场景和变换矩阵等方面存在一些关键区别。 1. 定义 仿射变换 (Affine Transform…...

如何在 Amazon EC2 上部署 Java(Spring Boot 版)

让我们学习如何将 Java Spring Boot Web 服务器部署到 Amazon EC2。每月只需 3 美元。 使用 Azure&#xff0c;您可能不知道要花费多少钱。 Spring Boot 项目示例 在本教程中&#xff0c;我们将重点介绍如何将 Java Spring Boot 服务器部署到 Amazon EC2&#xff0c;因此我们不…...

IDEA打不开、打开报错

目录 场景异常原因解决 场景 1、本机已经安装了IDEA 2、再次安装另外一个版本的IDEA后打不开、打开报错 异常 这里忘记截图了。。。 原因 情况1-打不开&#xff1a;在同一台电脑安装多个IDEA是需要对idea的配置文件进行调整的&#xff0c;否则打不开 情况2-打开报错&#…...

【React】项目的搭建

create-react-app 搭建vite 搭建相关下载 在Vue中搭建项目的步骤&#xff1a;1.首先安装脚手架的环境&#xff0c;2.通过脚手架的指令创建项目 在React中有两种方式去搭建项目&#xff1a;1.和Vue一样&#xff0c;先安装脚手架然后通过脚手架指令搭建&#xff1b;2.npx create-…...

如何提高单元测试的覆盖率

一、定位未覆盖的代码 ​利用 IDEA 的覆盖率工具​&#xff1a; 右键测试类 → ​Run with Coverage&#xff0c;或使用 AltShiftF10&#xff08;Windows&#xff09;打开运行菜单选择覆盖率。​查看高亮标记​&#xff1a; ​绿色​&#xff1a;已覆盖代码行。​红色​&#x…...

【SAP ME 43】RESRCE表操作导致HANA中表锁定解决方案

症状 SAP ME 通过执行以下 SQL 查询导致 RESRCE 表上的 HANA 数据库锁: 从 RESRCE WHERE HANDLE =? 选择站点待更新 或者 SELECT HANDLE FROM RESRCE WHERE HANDLE =... 用于更新 其他条款 HANA、锁、RESRCE 原因和前提条件 该问题是由运行 SQL FOR UPDATE 查询时的 …...

使用C#和FFmpeg开发RTSP视频播放器的完整指南

RTSP(Real Time Streaming Protocol)是流媒体技术中广泛使用的协议&#xff0c;广泛应用于视频监控、视频会议和在线直播等领域。本文将详细介绍如何使用C#和FFmpeg开发一个功能完整的RTSP视频播放器&#xff0c;涵盖从环境搭建到核心功能实现的全部过程。 一、开发环境准备 …...

CSS例子 > 图片瀑布流布局(vue2)

<template><div class"container"><!-- 临时容器用于计算高度 --><div v-if"!isLayoutReady" class"temp-container"><divv-for"(item, index) in list":key"temp- index":ref"(el) > …...

1.2软考系统架构设计师:系统架构的定义与作用 - 练习题附答案及超详细解析

系统架构定义与作用综合知识单选题 题目覆盖核心概念、发展历程、设计原则、评估标准及易混淆点&#xff0c;附答案解析&#xff1a; 1. 系统架构的标准定义源自于以下哪个标准&#xff1f; A. ISO/IEC 9126 B. IEEE 1471-2000 C. TOGAF 9.2 D. ITIL v4 答案&#xff1a;B 简…...

关于springmvc的404问题的一种猜测解决方案

本文是记录关于在学习动力结点老杜的springmvc时候遇到的404报错的一种解决方式&#xff1b; 由于本人之前学过老杜的springmvc&#xff0c;且运行成功&#xff0c;当时使用的是tomcat10.1.19版本。 idea使用2023.3.2版本。 而这次进行回顾的时候&#xff0c;使用tomcat10.0.1…...

PGSql常用操作命令

1 连接数据库&#xff1a; psql -U postgres &#xff08;psql -U username -d databse_name -h host -W&#xff09; -U 指定用户 -d 指定数据库 -h 要链接的主机 -W 提示输入密码 psql -h 主机名/服务器IP -p 端口号 -U 用户名 -d 数据库名 注意&#xff1a;&#xff08;…...

使用Postman调测“获取IAM用户Token”接口实际操作

概述 Postman是网页调试与辅助接口调用的工具&#xff0c;具有界面简洁清晰、操作方便快捷的特性&#xff0c;可以处理用户发送的HTTP请求&#xff0c;例如&#xff1a;GET&#xff0c;PUT、POST&#xff0c;DELETE等&#xff0c;支持用户修改HTTP请求中的参数并返回响应数据。…...

Java面试(2025)—— Spring MVC

什么是Spring MVC Spring MVC 是 Spring 框架的一个 基于 Java 的 Web 开发模块&#xff0c;它实现了 MVC&#xff08;Model-View-Controller&#xff09;架构模式&#xff0c;用于构建灵活、松耦合的 Web 应用程序。 它是 Spring 生态的核心组件之一&#xff0c;通过简化 HTT…...

如何测试雷达与相机是否时间同步?

在多传感器融合系统中&#xff0c;相机与雷达的协同感知已成为环境理解的关键。相机通过捕捉纹理信息识别物体类别&#xff0c;而雷达利用激光或毫米波实现全天候精确测距。两者的数据融合既能避免单一传感器缺陷&#xff08;如相机受光照影响、雷达缺乏语义信息&#xff09;&a…...

Redis基本安装和部署

环境&#xff1a; linux docker 安装: sudo apt install -y redis-server运行&#xff1a; 后台模式&#xff1a;redis-server & &#xff0c; 前台模式&#xff1a;redis-server , 用配置文件运行redis: sudo redis-server /etc/redis/redis.conf , /etc/redis/redis.co…...

数据分析与产品、运营、市场之间如何有效对齐

数据分析的重要性在于它能够将海量的原始信息转化为可操作的洞察。以产品开发为例,通过用户行为数据的分析,产品经理可以清晰了解哪些功能被频繁使用,哪些设计导致用户流失,从而优化迭代方向。运营团队则依靠数据分析来监控供应链效率、预测需求波动,甚至通过实时数据调整…...

Pytorch分布式训练(DDP)(记录)

为什么要分布式训练&#xff1f; 随着深度学习模型参数量和数据量不断增大&#xff0c;单卡显存和计算能力有限&#xff0c;单机单卡训练难以满足大模型/大数据集训练需求&#xff0c;因此我们需要&#xff1a; 单机多卡并行&#xff1a;利用一台机器上多张 GPU 加速训练。 …...

爆肝整理!Stable Diffusion的完全使用手册(二)

继续介绍Stable Diffusion的文生图界面功能。 往期文章详见: 爆肝整理&#xff01;Stable Diffusion的完全使用手册&#xff08;一&#xff09; 下面接着对SD的文生图界面的进行详细的介绍。本期介绍文生图界面的截图2&#xff0c;主要包含生成模块下的采用方法、调度类型、迭…...

Redis 键管理

Redis 键管理 以下从键重命名、随机返回键、键过期机制和键迁移四个维度展开详细说明&#xff0c;结合 Redis 核心命令与底层逻辑进行深入分析&#xff1a; 一、键重命名 1. ​RENAME​​ 与 ​RENAMENX​​ **RENAME key newkey​**&#xff1a; 功能&#xff1a;强制重命名…...

OpenCV day5

函数内容接上文&#xff1a;OpenCV day4-CSDN博客 目录 9.cv2.adaptiveThreshold(): 10.cv2.split()&#xff1a; 11.cv2.merge()&#xff1a; 12.cv2.add()&#xff1a; 13.cv2.subtract()&#xff1a; 14.cv2.multiply()&#xff1a; 15.cv2.divide()&#xff1a; 1…...

基于Spring Boot+微信小程序的智慧农蔬微团购平台-项目分享

基于Spring Boot微信小程序的智慧农蔬微团购平台-项目分享 项目介绍项目摘要目录系统功能图管理员E-R图用户E-R图项目预览登录页面商品管理统计分析用户地址添加 最后 项目介绍 使用者&#xff1a;管理员、用户 开发技术&#xff1a;MySQLSpringBoot微信小程序 项目摘要 随着…...

WPF的发展历程

文章目录 WPF的发展历程引言起源与背景&#xff08;2001-2006&#xff09;从Avalon到WPF设计目标与创新理念 WPF核心技术特点与架构基础架构与渲染模型关键技术特点MVVM架构模式 WPF在现代Windows开发中的地位与前景当前市场定位与其他微软UI技术的关系未来发展前景 社区贡献与…...

Franka机器人ROS 2来袭:解锁机器人多元应用新可能

前言&#xff1a; 在机器人技术蓬勃发展的当下&#xff0c;每一次创新都可能为行业带来新的变革。2025年3月12日&#xff0c;Franka Robotics发布的Franka ROS 2软件包首次版本0.1.0&#xff0c;将著名的franka_ros软件包引入当前的ROS 2 LTS Humble Hawksbill&#xff0c;这一…...

树莓派5+Vosk+python实现语音识别

简介 Vosk是语音识别开源框架&#xff0c;支持二十种语言 - 中文&#xff0c;英语&#xff0c;印度英语&#xff0c;德语&#xff0c;法语&#xff0c;西班牙语&#xff0c;葡萄牙语&#xff0c;俄语&#xff0c;土耳其语&#xff0c;越南语&#xff0c;意大利语&#xff0c;荷…...

系统分析师知识点:访问控制模型OBAC、RBAC、TBAC与ABAC的对比与应用

在信息安全领域&#xff0c;访问控制是确保数据和资源安全的关键技术。随着信息系统复杂度的提高&#xff0c;访问控制技术也在不断演进&#xff0c;从早期简单的访问控制列表(ACL)发展到如今多种精细化的控制模型。本文将深入剖析四种主流的访问控制模型&#xff1a;基于对象的…...