20天学会rust(三)没有object的rust怎么面向对象?
面向对象我们都很熟悉,可以说它是一种软件开发最重要的编程范式之一,它将程序中的数据和操作数据的方法组织成对象。面向对象有几个重要特性: 封装、继承和多态,基于这些特性带来了在可重用性、可维护性、扩展性、可靠性的优点。 Java提供了一套完整的实现,那么在Rust中如何面向对象编程呢?
别着急,在回答这个问题之前,我们先画个5分钟,学习几个基本概念,答案就呼之欲出了。
基本概念
结构体(struct)
结构体定义
在Rust中,通过使用 struct 关键字来定义结构体。结构体定义了一个包含多个字段的数据结构,并可以为结构体实现方法。每个字段都有自己的类型和名称。
// 定义一个Person结构体
struct Person {name: String,age: u32,
}
结构体创建
可以使用结构体的名称和字段的值来创建结构体。有两种常见的创建方式:
- 直接初始化:直接为每个字段指定值。
// 直接初始化结构体
let person = Person {name: String::from("Alice"),age: 30,
};
- 使用构造函数:通过实现一个返回结构体实例的函数来创建结构体。
impl Person {// 定义一个新的Person实例的构造函数fn new(name: String, age: u32) -> Person {Person { name, age }}
}// 使用构造函数创建结构体实例
let person = Person::new(String::from("Alice"), 30);
访问结构体字段:
可以使用点操作符( . )来访问结构体中的字段。通过结构体实例的名称后跟字段名称,即可访问该字段的值。
// 访问结构体字段
println!("Name: {}", person.name);
println!("Age: {}", person.age);
修改结构体字段:
在Rust中,结构体的字段默认是不可变的(immutable)。如果想要修改结构体的字段,可以使用 mut 关键字将结构体实例声明为可变的(mutable),然后通过点操作符来修改字段的值。
// 修改结构体字段
let mut mutable_person = Person::new(String::from("Bob"), 25);
mutable_person.age = 26;println!("Modified Age: {}", mutable_person.age);
在上述代码中,我们首先定义了一个名为Person的结构体,它有两个字段:name和age。然后,我们为Person结构体实现了一个构造函数 new ,用于创建新的Person实例。
在 main 函数中,我们通过直接初始化的方式实例化了一个Person结构体,将姓名设置为"Alice",年龄设置为30。然后,我们使用点操作符来访问结构体字段,并打印出姓名和年龄。
接下来,我们使用构造函数 Person::new 创建了一个可变的Person结构体实例 mutable_person ,将姓名设置为"Bob",年龄设置为25。然后,通过将实例声明为可变的,并使用点操作符来修改年龄字段的值为26,并打印出修改后的年龄。
ok,我们掌握了rust的对象定义方法,但是rust除了上述方式,还提供了一些struct的增强,学习了他们可以帮助更简洁的使用struct。
元组结构体
Rust中的元组结构体是一种特殊类型的结构体,它类似于元组,但具有命名的字段。元组结构体允许我们在结构体中组合不同类型的值,并为每个字段指定一个名称。直接看示例:
// 定义一个元组结构体
struct Person(String, u32);fn main() {// 创建一个Person实例let person = Person("Alice".to_string(), 25);// 访问元组结构体的字段let name = person.0;let age = person.1;println!("Name: {}", name);println!("Age: {}", age);
}
在上述代码中,我们定义了一个名为Person的元组结构体。它有两个字段:一个是String类型的name字段,另一个是u32类型的age字段。
在 main 函数中,我们创建了一个Person实例 person ,并为name字段赋值为"Alice",age字段赋值为25。
然后,我们通过使用索引来访问元组结构体的字段。元组结构体的字段可以使用 . 加上索引的方式进行访问,索引从0开始。在这个例子中,我们通过 person.0 访问了name字段,通过 person.1 访问了age字段。
最后,我们打印出name和age字段的值。
通过元组结构体,我们可以将不同类型的值组合在一起,并为每个字段指定一个名称,提高了代码的可读性和可维护性。
单元结构体
单元结构体是一种特殊类型的结构体,它不包含任何字段。它类似于C语言中的空结构体或者其他语言中的 unit 类型。单元结构体在Rust中常用于表示没有实际数据的情况,通常用作占位符或者标记类型。下面是一个示例代码来介绍单元结构体的使用:
// 定义一个单元结构体
struct EmptyStruct;fn main() {// 创建一个单元结构体实例let empty = EmptyStruct;// 访问单元结构体(无操作)// 打印单元结构体println!("{:?}", empty);
}
在上述代码中,我们定义了一个名为EmptyStruct的单元结构体。它没有任何字段。
在 main 函数中,我们创建了一个EmptyStruct实例 empty ,并没有对它进行任何操作。
最后,我们通过使用 println! 宏来打印出单元结构体 empty 。
单元结构体在Rust中用于表示没有实际数据的情况,通常用作占位符或者标记类型。例如,在某些情况下,我们可能只关心某个类型是否存在,而不关心它的具体值。这时,可以使用单元结构体来表示这种情况。
细心的朋友肯定注意到了,上面的例子里有一个新知识点:impl,这个是实现?我们下面就学习它
实现(impl)
impl 是Rust中的关键字,用于为类型实现方法。通过 impl 关键字,我们可以在特定类型上定义和实现方法。下面给出一个示例代码来介绍 impl 的使用:
// 定义一个名为Rectangle的结构体
struct Rectangle {width: u32,height: u32,
}// 为Rectangle结构体实现一个名为area的方法
impl Rectangle {// 定义area方法,用于计算矩形的面积fn area(&self) -> u32 {self.width * self.height}
}fn main() {// 创建一个Rectangle实例let rect = Rectangle {width: 10,height: 20,};// 调用Rectangle实例的area方法let area = rect.area();println!("Area: {}", area);
}
在上述代码中,我们首先定义了一个名为Rectangle的结构体,它有两个字段:width和height。然后,使用 impl 关键字为Rectangle结构体实现了一个名为area的方法。
在 impl Rectangle 块中,我们定义了一个area方法,用于计算矩形的面积。该方法接受一个 &self 参数,表示对Rectangle实例的借用。在方法体内,我们使用 self.width 和 self.height 来访问结构体的字段,并返回它们的乘积作为矩形的面积。
在 main 函数中,我们创建了一个Rectangle实例 rect ,并调用了它的area方法来计算矩形的面积。最后,我们打印出计算得到的面积。
通过 impl 关键字,我们可以在特定类型上定义和实现方法,使得代码更加模块化和可读性更高。
枚举
Rust中的枚举(Enum)是一种自定义数据类型,它允许我们将相关的值组合在一起,并为这些值定义一个公共的类型。枚举在Rust中非常常见,并且被广泛用于表示多个可能的取值。
下面使用颜色作为一个例子来说明枚举的使用。假设我们需要表示一组不同的颜色,可以使用枚举来定义这些颜色的类型。
enum Color {Red,Green,Blue,
}fn main() {let favorite_color = Color::Blue;match favorite_color {Color::Red => println!("I like red!"),Color::Green => println!("I like green!"),Color::Blue => println!("I like blue!"),}
}
在上述代码中,我们定义了一个名为Color的枚举类型,它有三个可能的取值:Red、Green和Blue。然后,在main函数中,我们创建了一个favorite_color变量,并将其设置为Color::Blue。接下来,我们使用match表达式来匹配favorite_color的取值,并根据不同的情况打印不同的消息。
通过枚举,我们可以将相关的值组合在一起,并为这些值定义一个公共的类型。这使得代码更加清晰和可读,同时也提供了更好的类型安全性。在实际开发中,枚举在表示状态、选项、错误类型等方面都非常有用。
接口(trait)
Rust中的trait可以类比java的implement,它是一种用于定义共享行为的机制。Trait可以看作是一组方法的抽象,它定义了一系列的方法签名,但没有提供默认的实现。通过实现trait,类型可以拥有这些方法,并共享相同的行为。
定义trait:
使用 trait 关键字可以定义一个trait,后面跟着trait的名称和方法签名。例如:
trait Printable {fn print(&self);
}
实现trait:
使用 impl 关键字可以为类型实现一个trait。在实现trait时,需要提供trait中定义的所有方法的具体实现。例如:
struct Person {name: String,
}
impl Printable for Person {fn print(&self) {println!("Name: {}", self.name);}
}
默认实现:
可以为trait中的方法提供默认的实现。这样,在实现trait时,如果不重写该方法,将会使用默认的实现。例如:
trait Printable {fn print(&self) {println!("Printing...");}
}
trait约束:
可以在函数或方法中使用trait约束,以指定参数必须实现某个trait。这样可以在函数内部使用trait中定义的方法。例如:
fn print_person<T: Printable>(person: T) {person.print();
}
多个trait约束:
可以使用 + 运算符将多个trait约束组合在一起。这样,参数必须同时实现这些trait。例如:
fn process<T: Printable + Clone>(data: T) {data.print();let cloned_data = data.clone();// ...
}
trait继承:
trait可以继承其他trait,从而扩展或组合行为。使用 : 符号可以指定一个trait继承另一个trait。例如:
trait Printable {fn print(&self);}trait Debuggable: Printable {fn debug(&self);}
Trait是Rust中非常强大的特性,它提供了一种灵活的方式来实现共享行为,并在编译时进行静态检查。通过trait,可以实现代码的重用性和可扩展性。
泛型
Rust是一种支持泛型编程的静态类型编程语言。泛型是一种编程技术,允许在编写代码时使用参数化类型,从而增加代码的灵活性和重用性。以下是Rust中泛型的一些特点和用法,并提供具体的例子:
- 定义泛型类型: 在Rust中,可以使用尖括号
<T>来定义泛型类型。例如,Vec<T>表示一个可以存储任意类型元素的动态数组。
let numbers: Vec<i32> = vec![1, 2, 3, 4, 5];
let names: Vec<String> = vec!["Alice".to_string(), "Bob".to_string()];
- 函数泛型: 在函数定义中,可以使用泛型类型参数来表示函数的参数和返回值的类型。例如,
fn foo<T>(x: T) -> T表示一个接受任意类型参数并返回相同类型的函数。
fn print_value<T>(value: T) {println!("Value: {}", value);
}print_value(42);
print_value("Hello");
- 结构体泛型: 在结构体定义中,可以使用泛型类型参数来表示结构体的字段类型。例如,
struct Point<T> { x: T, y: T }表示一个具有泛型字段的结构体。
struct Point<T> {x: T,y: T,
}let int_point: Point<i32> = Point { x: 10, y: 20 };
let float_point: Point<f64> = Point { x: 1.5, y: 2.5 };
- 方法泛型: 在结构体或枚举的方法定义中,可以使用泛型类型参数来表示方法的参数和返回值的类型。例如,
impl<T> Point<T> { fn get_x(&self) -> &T }表示一个具有泛型方法的结构体。
impl<T> Point<T> {fn get_x(&self) -> &T {&self.x}
}let int_point: Point<i32> = Point { x: 10, y: 20 };
println!("X coordinate: {}", int_point.get_x());
- trait泛型: 在trait定义中,可以使用泛型类型参数来表示关联类型或方法的参数和返回值的类型。例如,
trait Iterator<Item = T> { fn next(&mut self) -> Option<T> }表示一个具有泛型关联类型和方法的trait。
trait Iterator {type Item;fn next(&mut self) -> Option<Self::Item>;
}struct Counter {current: u32,max: u32,
}impl Iterator for Counter {type Item = u32;fn next(&mut self) -> Option<Self::Item> {if self.current < self.max {let value = self.current;self.current += 1;Some(value)} else {None}}
}let mut counter = Counter { current: 0, max: 5 };
while let Some(value) = counter.next() {println!("Counter: {}", value);
}
- 泛型约束: 可以使用泛型约束来限制泛型类型参数的行为。例如,
fn foo<T: Display>(x: T) { println!("{}", x) }表示一个接受实现了Displaytrait的泛型参数的函数。
use std::fmt::Display;fn print_value<T: Display>(value: T) {println!("Value: {}", value);
}
print_value(42);
print_value("Hello");
通过泛型,Rust允许我们编写通用的代码,适用于多种类型,并在编译时进行静态检查,从而提高代码的灵活性和重用性。
OK,fine!到此我们学完了rust关于结构体的基础知识,那么怎么面向对象呢?
面向对象
Rust是一门多范式的编程语言,它支持面向对象编程(OOP)的概念,包括封装、继承和多态。下面我将用一个例子来说明如何在Rust中实现面向对象的代码。
假设我们要创建一个图形库,其中包含不同类型的图形对象,比如矩形(Rectangle)和圆形(Circle)。我们可以使用结构体(struct)和特征(trait)来实现封装、继承和多态。
首先,我们定义一个 Shape 特征,用于表示图形对象的共同行为:
trait Shape {fn area(&self) -> f64;fn display(&self);
}
在这个特征中,我们定义了两个方法: area 用于计算图形对象的面积, display 用于显示图形对象的信息。
接下来,我们实现 Rectangle 结构体,并为其实现 Shape 特征:
struct Rectangle {width: f64,height: f64,
}impl Shape for Rectangle {fn area(&self) -> f64 {self.width * self.height}fn display(&self) {println!("Rectangle: width={}, height={}", self.width, self.height);}
}
在这个实现中,我们为 Rectangle 结构体实现了 Shape 特征的方法。通过实现 area 方法,我们可以计算矩形的面积;通过实现 display 方法,我们可以打印矩形的信息。
类似地,我们可以实现 Circle 结构体,并为其实现 Shape 特征:
struct Circle {radius: f64,
}impl Shape for Circle {fn area(&self) -> f64 {std::f64::consts::PI * self.radius * self.radius}fn display(&self) {println!("Circle: radius={}", self.radius);}
}
在这个实现中,我们为 Circle 结构体实现了 Shape 特征的方法。通过实现 area 方法,我们可以计算圆形的面积;通过实现 display 方法,我们可以打印圆形的信息。
最后,我们可以在主函数中使用这些图形对象,并调用它们的方法:
fn main() {let rectangle = Rectangle { width: 5.0, height: 3.0 };let circle = Circle { radius: 2.0 };let shapes: Vec<Box<dyn Shape>> = vec![Box::new(rectangle),Box::new(circle),];for shape in shapes {shape.display();println!("Area: {}", shape.area());}
}
在这个例子中,我们创建了一个包含矩形和圆形对象的 shapes 向量,并使用 Box<dyn Shape> 来存储不同类型的图形对象。然后,我们遍历 shapes 向量,调用每个图形对象的 display 和 area 方法。
通过这个例子,我们可以看到,在Rust中通过结构体和特征的组合,我们可以实现封装(将数据和行为封装在结构体中)、继承(通过实现特征来共享行为)和多态(通过使用 Box<dyn Shape> 来存储不同类型的对象)等面向对象的概念。是不是很简单,你学废了吗?
同样的留一个课后作业:用面向对象的方式完成题目:551. 学生出勤记录 I
,任何疑问评论区交流
相关文章:
20天学会rust(三)没有object的rust怎么面向对象?
面向对象我们都很熟悉,可以说它是一种软件开发最重要的编程范式之一,它将程序中的数据和操作数据的方法组织成对象。面向对象有几个重要特性: 封装、继承和多态,基于这些特性带来了在可重用性、可维护性、扩展性、可靠性的优点。 …...
整数规划——第三章 全单模矩阵
整数规划——第三章 全单模矩阵 若线性规划问题的约束矩阵为全单模矩阵,则该问题可行域的顶点都是整数点,从而线性规划与整数规划的最优解相同。 3.1 全单模性与最优性 考虑线性整数规划问题: (IP) min c T x , s . t . A x ≤ b , x …...
数据结构和算法
数据结构和算法目录表 CCJava线性结构 1. 数组、单链表和双链表 2. Linux内核中双向链表的经典实现 数组、单链表和双链表 数组、单链表和双链表 栈 栈 栈 队列 队列 队列树形结构 二叉查找树 二叉查找树 二叉查找树 AVL树 AVL树 AVL树 伸展树 伸展树 伸展树 1. 红黑树(一)之…...
[Vulnhub] matrix-breakout-2-morpheus
目录 <1> 信息收集 <2> getshell <3> Privilege Escalation(提权) <1> 信息收集 nmap -sP 192.168.236.0/24 扫描一下靶机ip 靶机ip: 192.168.236.154 nmap -A -p 1-65535 192.168.236.154 扫描一下靶机开放哪些服务 开放…...
JDK, JRE和JVM之间的区别和联系
JDK, JRE和JVM是与Java编程语言相关的三个重要的概念,它们分别代表Java Development Kit(Java开发工具包)、Java Runtime Environment(Java运行时环境)和Java虚拟机(Java Virtual Machine)。它们…...
mac电脑访问windows共享文件夹连接不上(设置445端口)
前提:首先需要保证mac和windows都在同一局域网内,如果不在肯定是连不上的,就不用往下看了。 事情是这样的,公司入职发了mac电脑,但是我是window重度用户,在折腾mac的过程中,有许多文件需要从wi…...
metersphere性能压测执行过程
(1) 首先在controller层,通过RunTestPlanRequest接收请求参数 PostMapping("/run")public String run(RequestBody RunTestPlanRequest request) (2) 在PerformanceTestService中的run中进行具体的逻辑处理, 首先根据请求中ID来获取库中存储…...
揭秘Word高级技巧:事半功倍的文字处理策略
Microsoft Word是一款广泛使用的文字处理软件,几乎每个人都有使用过它的经历。但是,你是否知道Word中隐藏着许多高级技巧和功能,可以帮助你事半功倍地处理文字?在本文中,我们将揭秘一些Word的高级技巧,让你…...
06-1_Qt 5.9 C++开发指南_对话框与多窗体设计_标准对话框
在一个完整的应用程序设计中,不可避免地会涉及多个窗体、对话框的设计和调用,如何设计和调用这些对话框和窗体是搞清楚一个庞大的应用程序设计的基础。本章将介绍对话框和多窗体设计、调用方式、数据传递等问题,主要包括以下几点。 Qt 提供的…...
模拟实现消息队列项目(系列7) -- 实现BrokerServer
目录 前言 1. 创建BrokerServer类 1.1 启动服务器 1.2 停止服务器 1.3 处理一个客户端的连接 1.3.1 解析请求得到Request对象 1.3.2 根据请求计算响应 1.3.3 将响应写回给客户端 1.3.4 遍历Session的哈希表,把断开的Socket对象的键值对进行删除 2. 处理订阅消息请求详解(补充) …...
vscode插件不能搜索安装
1 现象 vscode搜索自己的插件,报错: Error while fetching extensions. HXR failed2 原因 之前用vscode开发golang语言,设置了proxy代理,所以导致错误,删除即可 重启vscode 3 结果...
路由器工作原理(第二十九课)
路由器工作原理(第二十九课) 一图胜过千言 1) 路由:数据从一个网络到另外一个网络之间转发数据包的过程称为路由 2) 路由器:连接不同网络,实现不同网段之间的通信 3)路由表:路由器选择数据的传输路径的依据 原始的路由表 Destination/Mask Proto Pre Cost …...
linux log 日志
/* author: hjjdebug * date: 2023年 08月 08日 星期二 13:18:08 CST * descriptor: linux log 日志 * destinator: 搞清linux 下log 日志 * 下面代码编译通过即可运行 */ #include <stdio.h> #include <syslog.h> int main(void) { // 打开系统日志, 可…...
uniapp获取当前页面高度
设置动态高度:style"{height: pageHeightpx}" <view class"uni-content" :style"{height: pageHeightpx}" >... </view>获取当前页面高度: onLoad() {// 获取当前窗口高度this.pageHeight uni.getSystemInfoSync().wi…...
Java课题笔记~ Spring 集成 MyBatis
Spring 集成 MyBatis 将 MyBatis 与 Spring 进行整合,主要解决的问题就是将 SqlSessionFactory 对象交由 Spring 来管理。所以该整合,只需要将 SqlSessionFactory 的对象生成器SqlSessionFactoryBean 注册在 Spring 容器中,再将其注入给 Dao…...
分布式系统理论基础
文章目录 介绍目标 正文CAPConsistencyAvailabilityPartition tolerance BASEBasically AvailableSoft StateEventually Consistent ACIDatomicityconsistencyisolationdurability 参考文档 介绍 分布式系统面临的场景往往是众口难调,“这也要,那也要”…...
mfc 编辑框限制
DoDataExchange由框架调用,作用是交互并且验证对话框数据,主要由(DDX) 和 (DDV)宏实现。 永远不要直接调用这个函数,而是通过UpdateData(TRUE/FALSE)实现控件与变量之间值的传递。 当然你也可以不使用DoDataExchange而完成控件与变量之间值…...
web基础与tomcat环境部署
一. 简述静态网页和动态网页的区别。 请求响应信息,发给客户端进行处理,由浏览器进行解析,显示的页面称为静态页面。处理文件类型如.html、jpg、.gif、.mp4、.swf、.avi、.wmv、.flv等 请求响应信息,发给事务端进行处理࿰…...
Go 变量
在Go中,有不同的变量类型,例如: int 存储整数(整数),例如123或-123float32 存储浮点数字,带小数,例如19.99或-19.99string - 存储文本,例如“ Hello World”。字符串值用…...
【雷达通信】非相干多视处理(CSA)(Matlab代码实现)
💥💥💞💞欢迎来到本博客❤️❤️💥💥 🏆博主优势:🌞🌞🌞博客内容尽量做到思维缜密,逻辑清晰,为了方便读者。 ⛳️座右铭&a…...
大数据学习栈记——Neo4j的安装与使用
本文介绍图数据库Neofj的安装与使用,操作系统:Ubuntu24.04,Neofj版本:2025.04.0。 Apt安装 Neofj可以进行官网安装:Neo4j Deployment Center - Graph Database & Analytics 我这里安装是添加软件源的方法 最新版…...
【JavaEE】-- HTTP
1. HTTP是什么? HTTP(全称为"超文本传输协议")是一种应用非常广泛的应用层协议,HTTP是基于TCP协议的一种应用层协议。 应用层协议:是计算机网络协议栈中最高层的协议,它定义了运行在不同主机上…...
【人工智能】神经网络的优化器optimizer(二):Adagrad自适应学习率优化器
一.自适应梯度算法Adagrad概述 Adagrad(Adaptive Gradient Algorithm)是一种自适应学习率的优化算法,由Duchi等人在2011年提出。其核心思想是针对不同参数自动调整学习率,适合处理稀疏数据和不同参数梯度差异较大的场景。Adagrad通…...
【项目实战】通过多模态+LangGraph实现PPT生成助手
PPT自动生成系统 基于LangGraph的PPT自动生成系统,可以将Markdown文档自动转换为PPT演示文稿。 功能特点 Markdown解析:自动解析Markdown文档结构PPT模板分析:分析PPT模板的布局和风格智能布局决策:匹配内容与合适的PPT布局自动…...
全面解析各类VPN技术:GRE、IPsec、L2TP、SSL与MPLS VPN对比
目录 引言 VPN技术概述 GRE VPN 3.1 GRE封装结构 3.2 GRE的应用场景 GRE over IPsec 4.1 GRE over IPsec封装结构 4.2 为什么使用GRE over IPsec? IPsec VPN 5.1 IPsec传输模式(Transport Mode) 5.2 IPsec隧道模式(Tunne…...
中医有效性探讨
文章目录 西医是如何发展到以生物化学为药理基础的现代医学?传统医学奠基期(远古 - 17 世纪)近代医学转型期(17 世纪 - 19 世纪末)现代医学成熟期(20世纪至今) 中医的源远流长和一脉相承远古至…...
SQL慢可能是触发了ring buffer
简介 最近在进行 postgresql 性能排查的时候,发现 PG 在某一个时间并行执行的 SQL 变得特别慢。最后通过监控监观察到并行发起得时间 buffers_alloc 就急速上升,且低水位伴随在整个慢 SQL,一直是 buferIO 的等待事件,此时也没有其他会话的争抢。SQL 虽然不是高效 SQL ,但…...
LabVIEW双光子成像系统技术
双光子成像技术的核心特性 双光子成像通过双低能量光子协同激发机制,展现出显著的技术优势: 深层组织穿透能力:适用于活体组织深度成像 高分辨率观测性能:满足微观结构的精细研究需求 低光毒性特点:减少对样本的损伤…...
c++第七天 继承与派生2
这一篇文章主要内容是 派生类构造函数与析构函数 在派生类中重写基类成员 以及多继承 第一部分:派生类构造函数与析构函数 当创建一个派生类对象时,基类成员是如何初始化的? 1.当派生类对象创建的时候,基类成员的初始化顺序 …...
「全栈技术解析」推客小程序系统开发:从架构设计到裂变增长的完整解决方案
在移动互联网营销竞争白热化的当下,推客小程序系统凭借其裂变传播、精准营销等特性,成为企业抢占市场的利器。本文将深度解析推客小程序系统开发的核心技术与实现路径,助力开发者打造具有市场竞争力的营销工具。 一、系统核心功能架构&…...
