面向对象的类class中就充斥着方法的概念
在Rust中,往往和对象成对出现:
object.method()
定义方法
使用impl来定义方法:
struct Circle{
x:f64,
y:f64,
radius:f64,
}
impl Circle{
//new是Circle的关联函数,因为它的第一个参数不是self,且new并不是关键字
fn new(x:f64,y:f64,radius:f64)->Circle{
Circle{
x:x,
y:y,
radius:radius,
}
}
//Circle的方法,&self表示借用当前的Circle结构体
fn area(&self)->f64{
std::std::consts::PI*(self.radius*self.radius)
}
}
Rust的方法定义和其他语言的区别:
Rust的对象定义和方法定义是分离的,给予了使用者极高的灵活度
#[derive(Debug)]
struct Rectangle{
width:u32,
height:u32,
}
impl Rectangle{
fn area(&self)->u32{
self.width*self.height
}
}
fn main(){
let rect1 = Rectangle{width:30,height:50};
println!("The area of the rectangle is {} square pixels.",rect1.area());
}
上面定义了一个Rectangle结构体,并定义了一个area方法,用于计算该矩形的面积
impl Rectangle{}表示为Rectangle实现方法,(impl是实现implement的缩写),这样的写法表明impl中的一切都是和Rectangle相关的
self,&self和&mut self
在上面area的签名中,我们使用&self代替rectangle:Rectangle,&self其实是self:&Self的简写,在一个impl块中,Self指代被实现方式的结构体类型,self指代这个类型的实例,换句话说self指代的是Rectangle结构体实例,这样的写法会让我们的代码简洁很多,而且很容易理解:我们为哪个结构体实现方法,那么self就是指代哪个结构体的实例;
self依然有所有权的概念:
self表示Rectangle的所有权转移到该方法中,这种形式用的较少&self表示该方法对Rectangle的不可变调用&mut self表示可变调用 回到上面的例子中,选择&self的理由在函数中使用&Rectangle是相同的:我们并不想获取所有权,也无需去改变它,只是希望能够读取结构体中的数据。 要想在方法中改变当前的结构体,需要将第一个参数设置为&mut self,仅仅通过使用self作为第一个参数来使方法获取实例的所有权是很少见的,这种方式往往用于将当前的对象转为另一个对象使用,转换完成后,就不再关注之前的对象,且可以防止对之前对象的误调用。 使用方法代替有以下好处:- 不用再函数签名中重复书写
self对应的类型 - 代码的组织性和内聚性更强,对于代码维护和阅读而言,好处巨大
方法名要和结构体字段名相同
在Rust中,允许方法名和结构体的字段名相同
impl Rectangle{
fn wifth(&self)->bool{
self.width>0
}
}
fn main(){
let rect1 = Rectangle{
width:30,
height:50,
};
if rect1.width(){
println!("The rectangle has a nonzero width: it is {}",rect1.width);
}
}
当我们使用rect1.width()时,Rust知道我们调用的是它的方法,如果使用rect1.width(),则是访问它的字段
一般而言,方法和字段同名,往往适用于实现getter访问器,如:
mod my{
pub struct Rectangle{
width:u32,
pub height:u32,
}
impl Rectangle{
pub fn new(width:u32,height:u32)->Self{
Rectangle{width,height}
}
pub fn width(&self)->u32{
return self.width;
}
pub fn height(&self)->u32{
return self.height;
}
}
}
fn main(){
let rect1 = my::Rectangle::new(30,50);
println!("{}",rect1.width());
println!("{}",rect1.height());
//println!("{}",rect1.width); //Error - the visibility of field defaults to private
println!("{}",rect1.height);
}
当从外部模块访问结构体时,结构体的字段默认是私有的,目的是隐藏信息(封装);如果想要从模块外部获取Recatangle的字段,只需要将new``width``height方法设置为公开可见,那么用户就可以通过创建一个矩形,同时通过访问rect1.width()和rect1.height()来取矩形的高宽
因为width是私有的,当用户访问rect1.width字段时,就会error,注意在此例子中,Self指代的就是实现方法的结构体Rectangle
这种默认的可见性可以通过pub来进行覆盖,这样对于外部模块而言,就可以直接访问刚才用pub修饰的字段而无需经过访问器;这种可见性仅仅当从定义结构的模块外部访问时才重要,并且有隐藏信息的目的。
->运算符到哪里去了 C/C++中,2种运算符来调用方法:.直接在对象上调用方法,而->在一个对象的指针上调用方法,这时候需要先解引用指针; 简而言之,如果object是一个指针,那么object->something和(*object).something是一样的 Rust没有和->一样的运算符,但是Rust有自动引用和解引用的功能,方法调用时rust中少数几个拥有这种行为的地方 当使用object.something()调用方法时,rust会自动为object添加&,*以便使object与方法签名匹配;也就是说,这些代码时等价的
#[derive(Debug,Copy,Clone)]
struct Ponit{
x:f64,
y:f64,
}
impl Point{
fn distance(&self,other:&Point)->f64{
let x_squared = f64::powi(other.x-self.x,2);
let y_squared = f64::powi(other.y-self.y,2);
f64::sqrt(x_squared+y_squared);
}
}
let p1 = Point{x:0.0,y:0.0};
let p2 = Point{x:5.0,y:6.5};
p1.distance(&p2);
(&p1).distance(&p2);
这种自动引用的行为之所以有效,是因为方法有一个明确的接收者——self的类型,在给出了接收者和方法名的前提下,rust可以明确的计算出仅仅读取(&self),做出修改(&mut self)或者时获取所有权(self).
事实上,rust对方法接收者的隐式借用让所有权在时间中更友好
带有多个参数的方法
方法和函数一样,可以带有多个参数:
impl Rectangle{
fn area(&self)->u32{
self.width*self.height
}
fn can_hold(&self,other:&Rectangle)->bool{
self.width>other.width&&self.height>other.height
}
}
fn main(){
let rect1 = Rectangle{width:30,height:50};
let rect2 = Rectangle{width:10,height:40};
let rect3 = Rectangle{width:60,height:45};
println!("Can rect1 hold rect2?{}",rect1.can_hold(&rect2));
println!("Can rect1 hold rect3?{}",rect1.can_hold(&rect3));
}
关联函数
为结构体定义一个构造器方法,就是接收几个参数,然后构造并返回该结构体的实例->参数中不包含self.即可
这种定义在impl中且没有self的函数称之为关联函数,因为没有self,不能用f.read()的方式进行调用,因此是一个函数不是一个方法,但是又在impl当中,因此称之为关联函数
在之前的代码中,我们已经多次使用过关联函数,例如String::from,用于创建一个动态字符串
#[derive(Debug)]
struct Rectangle{
width:u32,
height:u32,
}
impl Rectangle{
fn new(w:u32,h:u32)->Rectangle{
Rectangle{width:w,height:h}
}
}
rust中有一个约定俗成的规则,就是使用new作为构造器的名称,处于设计的考虑,rust特地没有使用new作为关键字
因为是函数,所以不能通过.来调用,需要用::来调用,例如let sq = Rectangle::new(3,3),这个方法位于结构体的命名空间中,::用于关联函数和模块创建的命名空间
多个impl定义
rust允许我们为一个结构体定义多个impl块,目的是提供更多灵活性和代码组织性,当语法多了之后,我们可以将相关的方法组织在同一个impl当中,那么就可以形成多个impl块
#[derive(Debug)]
struct Rectangle{
width:u32,
height:u32,
}
impl Rectangle{
fn area(&self)->u32{
self.width*self.height
}
}
impl Rectangle{
fn can_hold(&self,other:&Rectangle)->bool{
self.width>other.width&&self.height>other.height
}
}
为枚举实现方法
我们可以像结构体一样,为枚举实现方法
#![allow(unused)]
enum Message{
Quit,
Move{x:i32,y:i32},
Write(String),
ChangeColor(i32,i32,i32),
}
impl Message{
fn call(&self){
//Def
}
}
fn main(){
let m = Message::Write(String::from("hello"));
//这里的::不要和关联函数的::混淆了
m.call();
}
除了结构体和枚举,我们还能为trait实现方法