旭日光辉 發表於 2025-2-26 15:34:24

Rust中的方法与关联函数使用解读

<div id="navCategory"><h5 class="catalogue">目录</h5><ul class="first_class_ul"><li>1. 方法(Methods)是什么?</li><li>2. 为什么要使用 &amp;self 而不是 &amp;Rectangle?</li><li>3. 同名字段与同名方法</li><li>4. 借用与解引用:为什么在调用方法时不需要写 &amp; 或 *?</li><li>5. 方法可以拥有多个参数</li><li>6. 关联函数(Associated Functions)</li><li>7. 多个 impl 块</li><li>8. 总结</li></ul></div><p class="maodian"></p><h2>1. 方法(Methods)是什么?</h2>
<p>在 Rust 里,方法和函数的定义方式很像:</p>
<ul><li>都使用 <code>fn</code> 来声明。</li><li>都能拥有参数和返回值。</li><li>都包含一段在被调用时执行的代码逻辑。</li></ul>
<p><strong>不同点在于:</strong> 方法必须定义在某个具体类型(比如 <code>struct</code>、<code>enum</code> 或者在某个 trait 对象里)的上下文中。而且方法的第一个参数固定要写成 <code>self</code>(可以是 <code>self</code>、<code>&amp;self</code> 或者 <code>&amp;mut self</code>),用来代表调用该方法的具体实例。</p>
<p>让我们来看看一个简单示例。假设我们有一个 <code>Rectangle</code> 结构体:</p>
<div class="jb51code"><pre class="brush:bash;">#
struct Rectangle {
    width: u32,
    height: u32,
}</pre></div>
<p>如果你想为 <code>Rectangle</code> 实例添加一个计算面积的功能,我们可以在 <code>impl</code>(implementation)块中为它定义一个方法 <code>area</code>:</p>
<div class="jb51code"><pre class="brush:bash;">impl Rectangle {
    fn area(&amp;self) -&gt; u32 {
      self.width * self.height
    }
}</pre></div>
<ul><li>这里 <code>impl Rectangle { ... }</code> 表示这个块里的所有函数都与 <code>Rectangle</code> 类型相关联。</li><li><code>fn area(&amp;self) -&gt; u32</code> 说明:这是一个方法,第一个参数是 <code>&amp;self</code>,表示<strong>以不可变引用的形式</strong>访问当前调用该方法的 <code>Rectangle</code> 实例。</li><li><code>self.width</code> 和 <code>self.height</code> 即代表该实例的字段。用 <code>self</code> 访问字段非常直观。</li></ul>
<p>在 <code>main</code> 函数中,当我们创建一个矩形实例后,就可以使用方法语法来获取面积:</p>
<div class="jb51code"><pre class="brush:bash;">fn main() {
    let rect1 = Rectangle {
      width: 30,
      height: 50,
    };
   
    println!("rect1 的面积是:{}", rect1.area());
}</pre></div>
<p>运行后,会输出:</p>
<blockquote><p>rect1 的面积是:1500</p></blockquote>
<p class="maodian"></p><h2>2. 为什么要使用 &amp;self 而不是 &amp;Rectangle?</h2>
<p>在我们将 <code>area</code> 从一个普通函数重构为一个方法时,你会注意到,函数签名由原本的</p>
<div class="jb51code"><pre class="brush:bash;">fn area(rectangle: &amp;Rectangle) -&gt; u32 { ... }</pre></div>
<p>变为</p>
<div class="jb51code"><pre class="brush:bash;">fn area(&amp;self) -&gt; u32 { ... }</pre></div>
<p>这是因为在 <code>impl Rectangle</code> 这个上下文中,Rust 给出了一个更具可读性的方式:让第一个参数自动变为 <code>self</code>,而 <code>Self</code> 则是当前实现块对应的类型别名。</p>
<p>如果你需要<strong>修改</strong>实例的字段,你可以将第一个参数写为 <code>&amp;mut self</code>;如果需要<strong>获取所有权</strong>并可能在方法内部把它&ldquo;转化&rdquo;成别的东西,则用 <code>self</code>。但这种把所有权转移给方法本身的做法很少见。</p>
<p>在大多数情况下,我们只是想读一下结构体数据而不改变它,这时使用 <code>&amp;self</code> 最为常见,也能让调用者继续使用这个实例。</p>
<p class="maodian"></p><h2>3. 同名字段与同名方法</h2>
<p>如果你在 <code>Rectangle</code> 内也有一个字段叫做 <code>width</code>,同时还想定义一个方法也叫 <code>width</code>,这是合法的。比如:</p>
<div class="jb51code"><pre class="brush:bash;">impl Rectangle {
    fn width(&amp;self) -&gt; bool {
      self.width &gt; 0
    }
}</pre></div>
<p>在调用时:</p>
<ul><li><code>rect.width</code> (不带括号)访问的是字段 <code>width</code> 的数值。</li><li><code>rect.width()</code> (带括号)调用的是同名方法,返回一个布尔值。</li></ul>
<p>在很多语言中,如果你只想单纯地返回字段值,会把这种方法称为&ldquo;getter&rdquo;。</p>
<p>Rust 并不会为你自动生成 getter,但你可以自行定义。</p>
<p>这样一来,你可以只把字段设为私有,但对外公开这个只读方法,让外部安全地访问它。</p>
<p class="maodian"></p><h2>4. 借用与解引用:为什么在调用方法时不需要写 &amp; 或 *?</h2>
<p>在 C/C++ 中,如果你要通过指针来调用成员函数,需要写 <code>-&gt;</code>。或者,如果你手头是指针,还要显式地 <code>(*object).method()</code> 等。</p>
<p>在 Rust 中则不需要这么麻烦,因为<strong>自动引用和解引用</strong>让你可以直接写 <code>object.method()</code>。</p>
<p>实际上,这些调用是一样的:</p>
<div class="jb51code"><pre class="brush:bash;">p1.distance(&amp;p2);
(&amp;p1).distance(&amp;p2);</pre></div>
<p>Rust 会根据方法签名(第一个参数是 <code>&amp;self</code>、<code>&amp;mut self</code> 还是 <code>self</code>)来自动推断是否需要帮你加 <code>&amp;</code>、<code>&amp;mut</code> 或者 <code>*</code>。这大大简化了调用方法时的语法。</p>
<p class="maodian"></p><h2>5. 方法可以拥有多个参数</h2>
<p>方法和函数在参数上并没什么区别,除了<strong>第一个参数是 <code>self</code></strong> 以外,其他参数你可以自由添加。</p>
<p>举例来说,为 <code>Rectangle</code> 再定义一个方法 <code>can_hold</code>,用来检查&ldquo;当前矩形&rdquo;是否可以完全容纳另一个矩形:</p>
<div class="jb51code"><pre class="brush:bash;">impl Rectangle {
    fn can_hold(&amp;self, other: &amp;Rectangle) -&gt; bool {
      self.width &gt; other.width &amp;&amp; self.height &gt; other.height
    }
}</pre></div>
<p>然后这样使用它:</p>
<div class="jb51code"><pre class="brush:bash;">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(&amp;rect2)); // true
    println!("Can rect1 hold rect3? {}", rect1.can_hold(&amp;rect3)); // false
}</pre></div>
<p class="maodian"></p><h2>6. 关联函数(Associated Functions)</h2>
<p>如果在 <code>impl</code> 块中定义的函数<strong>没有</strong> <code>self</code> 参数,那它就不是方法(method),而是<strong>关联函数(associated function)</strong>。</p>
<p>关联函数常被用来提供类似&ldquo;构造函数&rdquo;的功能。</p>
<p>举个例子,如果你想快速构造一个&ldquo;正方形&rdquo;:</p>
<div class="jb51code"><pre class="brush:bash;">impl Rectangle {
    // 关联函数
    fn square(size: u32) -&gt; Self {
      Self {
            width: size,
            height: size,
      }
    }
}</pre></div>
<p>调用的时候,使用 <code>::</code> 语法来调用关联函数:</p>
<div class="jb51code"><pre class="brush:bash;">fn main() {
    let sq = Rectangle::square(3);
    println!("正方形 sq: {:#?}", sq);
}</pre></div>
<p>打印结果为:</p>
<blockquote><p>正方形 sq: Rectangle {<br />&nbsp; &nbsp; width: 3,<br />&nbsp; &nbsp; height: 3<br />}</p></blockquote>
<p>在标准库里,我们也经常看到这种关联函数,比如 <code>String::from(&quot;Hello&quot;)</code>。它不需要某个已存在的 <code>String</code> 实例,就可以直接调用,用来创建一个新的字符串。</p>
<p class="maodian"></p><h2>7. 多个 impl 块</h2>
<p>你可以为同一个类型写多个 <code>impl</code> 块,比如:</p>
<div class="jb51code"><pre class="brush:bash;">impl Rectangle {
    fn area(&amp;self) -&gt; u32 {
      self.width * self.height
    }
}

impl Rectangle {
    fn can_hold(&amp;self, other: &amp;Rectangle) -&gt; bool {
      self.width &gt; other.width &amp;&amp; self.height &gt; other.height
    }
}</pre></div>
<p>这与把它们写在同一个 <code>impl</code> 中没有本质差别。之所以 Rust 允许你分开写,是为了在某些情况下(比如涉及到泛型、trait 实现等)组织代码更灵活。</p>
<p class="maodian"></p><h2>8. 总结</h2>
<ul><li><strong>方法</strong>:必须定义在某个类型(如 <code>struct</code>)的 <code>impl</code> 块中,第一个参数是 <code>self</code>(可变或不可变)。方法往往用于描述该类型实例的某些行为,读或写其内部数据。</li><li><strong>关联函数</strong>:在 <code>impl</code> 块里定义但不包含 <code>self</code> 参数的函数。常用于构造新实例或提供一些与实例无关的功能。</li><li>Rust 拥有<strong>自动引用和解引用</strong>特性,让我们可以简洁地使用 <code>object.method()</code> 来调用方法。</li><li>多个 <code>impl</code> 块可以并存,给代码的组织提供了很大灵活性。</li></ul>
<p>通过为自定义类型定义方法,我们不仅能让代码更具可读性,把相关的行为放到同一个 <code>impl</code> 块中,也能充分利用所有权、借用等特性来保证内存安全和并发安全。</p>
<p>希望这篇文章能帮你搞清楚在 Rust 中如何编写方法、何时使用 <code>&amp;self</code>、<code>&amp;mut self</code>、<code>self</code>,以及如何借助关联函数让代码更简洁优雅。</p>
<p>如果你还对 Rust 中的枚举(enum)或 trait 有兴趣,不妨继续阅读之后的章节,它们和 <code>struct</code> 一样,也是构建复杂逻辑的重要工具。</p>
<p>当然,以上为个人经验,希望能给大家一个参考,也希望大家多多支持琼殿技术社区。</p>
                           
                            <div class="art_xg">
                              <b>您可能感兴趣的文章:</b><ul><li>Rust中自定义Debug调试输出的示例详解</li><li>Rust中的注释使用解读</li><li>Rust中的模块系统之控制作用域与私有性详解</li><li>Rust之Rhai脚本编程的示例</li><li>Rust中的Trait与Trait&nbsp;Bounds详解</li></ul>
                            </div>

                        </div>
                        <!--endmain-->
頁: [1]
查看完整版本: Rust中的方法与关联函数使用解读