题目
我们需要编写一个图形相关的应用程序,并处理大量图形(Shape)信息,图形有矩形(Rectangle)、正方形(Square)、圆形(Circle)等种类。应用程序需要计算这些图形的面积,并且可能需要在某个设备上进行显示(使用在标准输出上打印信息的方式作为示意)。
(1)请使用面向对象的设计方法对以上需求进行设计,编写可能需要的结构体及其实现。
(2)请给出实现以上功能的示例性代码,从某处获取图形信息并且进行计算和显示。
解析
这道题主要考察应聘者对结构体、特征、封装等知识的整体理解和把控能力,涉及的知识点比较多,对于Rust初学者还是有一定难度的。
先来看第一道小题,我们需要使用面向对象的设计方法来进行类的封装和实现。根据题目所述,图形有矩形(Rectangle)、正方形(Square)、圆形(Circle)等种类,那么,如何进行封装呢?矩形、正方形、圆形虽然形状不同,但它们都有一些基本的共同的属性,比如:具有周长、面积等性质。因此,我们可以先定义一个图形的Trait,在这个Trait中定义一些通用的属性和方法,具体可参考下面的示例代码。
trait Shape {
fn area(&self) -> f64;
fn display(&self);
}
接下来,我们定义了下面名为Rectangle的结构体。这个结构体代表了一个矩形,包含两个关键的属性:width(宽度)和height(高度)。这两个属性都是64位浮点数类型(f64),这意味着矩形的尺寸可以是小数,从而提供了更高的精度和灵活性。随后,通过impl Shape for Rectangle语句,代码为Rectangle类型实现了Shape trait。这个实现包括了两个方法。
area方法:计算矩形的面积,通过将宽度乘以高度得出结果。这个方法没有副作用,它接收一个自身的引用(&self),并返回一个f64类型的面积值。
display方法:用于显示矩形的信息。这个方法使用println!宏来格式化并打印出矩形的宽度、高度和面积。值得注意的是:在打印面积时,它直接调用了之前定义的area方法,这展示了方法之间的互相调用以及代码的复用。
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 = {}, area = {}",
self.width,
self.height,
self.area()
);
}
}
正方形结构体Square的实现与矩形结构体相似,但其只有一个side的属性。具体的实现,可参考下面的示例代码。
struct Square {
side: f64,
}
impl Shape for Square {
fn area(&self) -> f64 {
self.side * self.side
}
fn display(&self) {
println!("Square: side = {}, area = {}", self.side, self.area());
}
}
最后,我们来封装圆形结构体Circle。Circle也只有一个属性,就是圆形的半径radius。具体的实现,可参考下面的示例代码。
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 = {}, area = {}", self.radius, self.area());
}
}
在第二道小题中,要求应聘者从某处获取图形信息并且进行计算和显示。实际上,就是编写一个使用上述图形类的简单程序。在下面的示例代码中,我们首先创建了一个名为shapes的向量(Vec),该向量中存储了不同类型的形状对象。这些对象都被装箱(Box::new)并转换为Box<dyn Shape>类型的trait对象。这意味着,尽管这些形状在内存中的具体表示可能不同,但它们都实现了Shape trait,因此可以以一种统一的方式被处理。
最后,代码通过一个for循环遍历shapes向量中的每个形状对象,并调用其display方法。由于每个对象都实现了Shape trait,因此它们都拥有display方法,这使得能够统一地处理和显示不同形状的信息。在循环中调用shape.display()时,Rust会在运行时动态地确定应该调用哪个具体实现的display方法。
fn main() {
let shapes = vec![
Box::new(Rectangle { width: 4.0, height: 5.0 }) as Box<dyn Shape>,
Box::new(Square { side: 6.0 }) as Box<dyn Shape>,
Box::new(Circle { radius: 3.0 }) as Box<dyn Shape>,
];
for shape in shapes {
shape.display();
}
}
执行上述示例代码后,其打印输出如下。
Rectangle: width = 4, height = 5, area = 20
Square: side = 6, area = 36
Circle: radius = 3, area = 28.274333882308138
总结
在本题中,我们学习了Rust中面向对象编程的一些关键概念,包括:多态性、Trait对象的使用以及动态方法分发。它允许程序员以一种灵活且类型安全的方式处理不同类型的对象,同时保持代码的简洁性和可读性。