鸿蒙剑气 發表於 2025-3-21 17:18:00

rust学习二十.6、RUST通用类型参数默认类型和运算符重载

<h1>一、前言</h1>
<p>为通用类型赋予一个默认的类型,大部分的语言是没有这个特性的,但是也有例外的,例如TypeScript(可能还有其它)。</p>
<p>例如TypeScript可以这样使用:</p>
<pre class="language-typescript highlighter-hljs"><code>class MyClass&lt;T = number&gt; {
    value: T;
    constructor(value: T) {
      this.value = value;
    }
    printValue(): void {
      console.log(`Value is ${this.value}`);
    }
}
const obj1 = new MyClass(42);// 使用默认类型 number
const obj2 = new MyClass&lt;string&gt;("Hello");// 使用指定类型 string</code></pre>
<p>而运算符重载,则不少语言也支持,最典型的莫过于C++,C#.</p>
<p>但是rust的运算符重载是比较特别的一种,该怎么说了?</p>
<p>rustc做了太多的工作,而且我觉得有点违背一些通用的设计规则。这是因为例子中的方法必须要求对象实现Copy,但是方法的参数又没有带&amp;,会让人误会!</p>
<p>不喜欢有太多默认约定的设计,更喜欢每个东西都明明白白地定义。</p>
<h1>二、通用类型参数默认类型</h1>
<p>读取来有点拗口,意思就是:</p>
<p>1.在有关对象(struc,特质等)或者方法中使用通用参数T</p>
<p>2.可以为T指定一个默认的类型,语法是T=xxx,其中xxx是某个具体类型</p>
<p>如果你不喜欢T,也可以换成任意合法的rust标识符.</p>
<p>就目前来看,通用参数的默认参数的作用有两点:运算符重载+方便</p>
<h1>三、运算符重载和其它作用</h1>
<h2><strong>3.1、运算符重载</strong></h2>
<p>所谓运算符重载就是除了运算符最原始的功能(编译器默认支持的)之外,还可以支持其它类型的运算数。</p>
<p>例如+通常用于整数、浮点数等的相加,但通过重载,其它类型对象实例也可以使用+。</p>
<p>以此类推,-*/等运算符号也可以。</p>
<p>不管怎么说,这算是一个好东西!</p>
<p>只不过类型参数的默认类型好像就是为了运算符重载而存在。</p>
<h2><strong>3.2、其它作用</strong></h2>
<p>查了一些资料,据说可以结合条件编译。其它作用就是无关紧要的。</p>
<p>条件编译示例</p>
<pre class="language-rust highlighter-hljs"><code>// 定义一个特性标志,用于条件编译

#
type DefaultNumType = f64;
#
type DefaultNumType = i32;
struct Point&lt;T = DefaultNumType&gt; {
    x: T,
    y: T,
}
impl&lt;T&gt; Point&lt;T&gt; {
    fn new(x: T, y: T) -&gt; Self {
      Point { x, y }
    }
}</code></pre>
<p>&nbsp;</p>
<h1>四、示例</h1>
<h2>4.1、示例代码</h2>
<p>由于例子涉及到Add,Sub两个特质,所以先列出此二特质的定义:</p>
<pre class="language-rust highlighter-hljs"><code>pub trait Add&lt;Rhs = Self&gt; {
    type Output;
    fn add(self, rhs: Rhs) -&gt; Self::Output;
}
pub trait Sub&lt;Rhs = Self&gt; {
    type Output;
    fn sub(self, rhs: Rhs) -&gt; Self::Output;
}</code></pre>
<p>对书本上的例子稍微改造了下:</p>
<pre class="language-rust highlighter-hljs"><code>use std::ops::{Add,Sub};

#
struct Point {
    x: i32,
    y: i32,
}
/**
* 这个使用默认类型,来自rust编程语言官方文档的例子
*/
impl Add for Point {
    type Output = Point;
    fn add(self, other: Point) -&gt; Point {
      Point {
            x: self.x + other.x,
            y: self.y + other.y,
      }
    }
}

/**
* 实现相减运算符(从而实现Point的-重载),需要实现Sub trait
*/
impl Sub for Point {
    type Output = Point;
    /**
   * 需要特别注意的是两个参数的定义
   * self -没有使用引用
   * other - 没有要求引用
   * 这种不引用的方式,不同于一般的方法定义
   */
    fn sub(self, other: Point) -&gt; Point {
      Point {
            x: self.x - other.x,
            y: self.y - other.y,
      }
    }
}


fn main() {
    let p1 = Point { x: 1, y: 2 };
    let p2 = Point { x: 3, y: 4 };
    //使用重载的方式调用
    println!("{:?}+{:?}={:?}",p1,p2, p1 + p2);
    println!("{:?}-{:?}={:?}",p1,p2, p1 - p2);

    //不使用重载的方式调用
    let p3 = p1.add(p2).sub(p2);
    let p4 = (p1.sub(p2)).add(p2);
    println!("{:?}+{:?}-{:?}={:?}",p1,p2, p2,p3);
    println!("{:?}-{:?}+{:?}={:?}",p1,p2,p2, p4);

    let lml= Person {name: "lml".to_string()};
    let ww= Animal {name: "ww".to_string()};
    lml.attack(ww);
    println!("{:?}",lml);
}

// --------------------------------------------------------
// 以下的代码是为了演示 参数不带&amp;是什么情况

trait Fight {
    type Item;
    //fn attack(&amp;self, other: &amp;Self::Item);
    fn attack(self, other: Self::Item);
}
#
struct Person {name: String}
#
struct Animal {name: String}

impl Fight for Person {
    type Item = Animal;
    fn attack(self, other: Self::Item) {
      println!(
            "{}攻击了{}",
            self.name,
            other.name
      );
    }   
}</code></pre>
<p>这个例子做了三件事情:</p>
<p>1.重载+</p>
<p>2.重载-</p>
<p>3.如果不使用Copy特质会怎么样</p>
<p>特质Fight和结构体Person,Animal就是为了验证第3点。</p>
<h2>4.2、名词解释</h2>
<p>在开始执行代码前,先解释两个重要的内容</p>
<p><strong>Rhs</strong></p>
<p>Rhs是 "Right-Hand Side"(右侧操作数)的缩写</p>
<p><strong>关键字self和Self</strong></p>
<p>仔细看看,才发现是两个,不是一个,要知道rust中是区分大小写的。</p>
<p>self-全小写,表示对象实例本身</p>
<p>Self-首字母大写,其它小写,表示类型本身</p>
<p>例如以下代码中:</p>
<div style="color: rgba(101, 123, 131, 1); background-color: rgba(253, 246, 227, 1); font-family: Consolas, &quot;Courier New&quot;, monospace; font-weight: normal; font-size: 14px; line-height: 19px; white-space: pre">
<div><span style="color: rgba(88, 110, 117, 1); font-weight: bold">trait</span><span style="color: rgba(101, 123, 131, 1)"> </span><span style="color: rgba(203, 75, 22, 1)">Fight</span><span style="color: rgba(101, 123, 131, 1)"> {</span></div>
<div><span style="color: rgba(101, 123, 131, 1)">&nbsp; &nbsp; </span><span style="color: rgba(88, 110, 117, 1); font-weight: bold">type</span><span style="color: rgba(101, 123, 131, 1)"> </span><span style="color: rgba(203, 75, 22, 1)">Item</span><span style="color: rgba(101, 123, 131, 1)">;</span></div>
<div><span style="color: rgba(101, 123, 131, 1)">&nbsp; &nbsp; </span><span style="color: rgba(133, 153, 0, 1)">fn</span><span style="color: rgba(101, 123, 131, 1)"> </span><span style="color: rgba(38, 139, 210, 1)">attack</span><span style="color: rgba(101, 123, 131, 1)">(</span><span style="color: rgba(133, 153, 0, 1)">&amp;</span><span style="color: rgba(38, 139, 210, 1)">self</span><span style="color: rgba(101, 123, 131, 1)">, </span><span style="color: rgba(38, 139, 210, 1)">other</span><span style="color: rgba(133, 153, 0, 1)">:</span><span style="color: rgba(101, 123, 131, 1)"> </span><span style="color: rgba(133, 153, 0, 1)">&amp;</span><span style="color: rgba(38, 139, 210, 1)">Self</span><span style="color: rgba(133, 153, 0, 1)">::</span><span style="color: rgba(203, 75, 22, 1)">Item</span><span style="color: rgba(101, 123, 131, 1)">);</span></div>
<div><span style="color: rgba(101, 123, 131, 1)">&nbsp; &nbsp; </span><span style="color: rgba(133, 153, 0, 1)">fn</span><span style="color: rgba(101, 123, 131, 1)"> </span><span style="color: rgba(38, 139, 210, 1)">defend</span><span style="color: rgba(101, 123, 131, 1)">&lt;</span><span style="color: rgba(203, 75, 22, 1)">T</span><span style="color: rgba(101, 123, 131, 1)">&gt;(</span><span style="color: rgba(133, 153, 0, 1)">&amp;</span><span style="color: rgba(38, 139, 210, 1)">self</span><span style="color: rgba(101, 123, 131, 1)">, </span><span style="color: rgba(38, 139, 210, 1)">danger</span><span style="color: rgba(133, 153, 0, 1)">:</span><span style="color: rgba(101, 123, 131, 1)"> </span><span style="color: rgba(133, 153, 0, 1)">&amp;</span><span style="color: rgba(203, 75, 22, 1)">T</span><span style="color: rgba(101, 123, 131, 1)">)</span></div>
<div><span style="color: rgba(101, 123, 131, 1)">&nbsp; &nbsp; </span><span style="color: rgba(133, 153, 0, 1)">where</span><span style="color: rgba(101, 123, 131, 1)"> </span><span style="color: rgba(203, 75, 22, 1)">T</span><span style="color: rgba(133, 153, 0, 1)">:</span><span style="color: rgba(101, 123, 131, 1)"> </span><span style="color: rgba(203, 75, 22, 1)">Danger</span><span style="color: rgba(101, 123, 131, 1)">; </span></div>
<div><span style="color: rgba(101, 123, 131, 1)">}</span></div>
</div>
<p>在方法attack中,第一个self表示具体对象实例,第二个Self则表示具体对象类型。</p>
<pre class="language-rust highlighter-hljs"><code>pub trait Add&lt;Rhs = Self&gt; {
    type Output;
    fn add(self, rhs: Rhs) -&gt; Self::Output;
}
pub trait Sub&lt;Rhs = Self&gt; {
    type Output;
    fn sub(self, rhs: Rhs) -&gt; Self::Output;
}</code></pre>
<p>现在代码应该容易看了。</p>
<h2>4.3、执行</h2>
<p>看看输出:</p>
<p><img src="https://img2024.cnblogs.com/blog/1177268/202503/1177268-20250319191235082-244463548.png"></p>
<p>只要把示例中如下一部分:</p>
<pre class="language-rust highlighter-hljs"><code>trait Fight {
&nbsp; &nbsp; type Item;
&nbsp; &nbsp; //fn attack(&amp;self, other: &amp;Self::Item);
&nbsp; &nbsp; fn attack(self, other: Self::Item);
}
#
struct Person {name: String}
#
struct Animal {name: String}


impl Fight for Person {
&nbsp; &nbsp; type Item = Animal;
&nbsp; &nbsp; fn attack(self, other: Self::Item) {
&nbsp; &nbsp; &nbsp; &nbsp; println!(
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; "{}攻击了{}",
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; self.name,
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; other.name
&nbsp; &nbsp; &nbsp; &nbsp; );
&nbsp; &nbsp; } &nbsp; &nbsp;
}</code></pre>
<div style="color: rgba(101, 123, 131, 1); background-color: rgba(253, 246, 227, 1); font-family: Consolas, &quot;Courier New&quot;, monospace; font-weight: normal; font-size: 14px; line-height: 19px; white-space: pre">&nbsp;</div>
<p>修改为:</p>
<pre class="language-rust highlighter-hljs"><code>trait Fight {
&nbsp; &nbsp; type Item;
&nbsp; &nbsp; fn attack(&amp;self, other: &amp;Self::Item);
&nbsp; &nbsp; //fn attack(self, other: Self::Item);
}
#
struct Person {name: String}
#
struct Animal {name: String}


impl Fight for Person {
&nbsp; &nbsp; type Item = Animal;
&nbsp; &nbsp; fn attack(&amp;self, other: &amp;Self::Item) {
&nbsp; &nbsp; &nbsp; &nbsp; println!(
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; "{}攻击了{}",
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; self.name,
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; other.name
&nbsp; &nbsp; &nbsp; &nbsp; );
&nbsp; &nbsp; } &nbsp; &nbsp;
}</code></pre>
<p>再把main的调用修改为:</p>
<div style="color: rgba(101, 123, 131, 1); background-color: rgba(253, 246, 227, 1); font-family: Consolas, &quot;Courier New&quot;, monospace; font-weight: normal; font-size: 14px; line-height: 19px; white-space: pre">
<div><span style="color: rgba(38, 139, 210, 1)">lml</span><span style="color: rgba(133, 153, 0, 1)">.</span><span style="color: rgba(38, 139, 210, 1)">attack</span><span style="color: rgba(101, 123, 131, 1)">(</span><span style="color: rgba(133, 153, 0, 1)">&amp;</span><span style="color: rgba(38, 139, 210, 1)">ww</span><span style="color: rgba(101, 123, 131, 1)">);</span></div>
</div>
<p>那么就可以正确输出:</p>
<p><img src="https://img2024.cnblogs.com/blog/1177268/202503/1177268-20250319191745082-17130819.png"></p>
<p>为什么在Point上没有这个问题了?这是因为Point实现了Copy特质,看下面的代码:#</p>
<p>rust的Copy特质奇怪的作用:允许类型进行隐式的、按位的复制,适用于简单数据类型,避免不必要的所有权移动,提升代码效率和便利性。同时,强调其使用条件和限制,帮助用户正确理解和应用</p>
<p>什么是按位复制?</p>
<p>也就是说,当赋值或作为函数参数传递时,不需要移动所有权,而是直接复制。不过,只有满足某些条件的类型才能实现Copy,比如所有字段都实现了Copy,并且类型本身没有实现Drop trait</p>
<p>所以,上例中,即使在方法中没有定义为引用类型,它也不会报错。而Person并没有实现Copy特质,所以会发生这个问题。</p>
<h1>五、示例2</h1>
<p>以下的示例演示了一个只包含字符串切片的struct如何相加</p>
<pre class="language-rust highlighter-hljs"><code>use std::ops::Add;
#
struct Name&lt;'a&gt;{
    name:&amp;'a str
}

impl&lt;'a&gt; Add for Name&lt;'a&gt;{
    type Output = Name&lt;'a&gt;;
    fn add(self, other: Self) -&gt; Self {
      let tmp=format!("{} {}",self.name,other.name);
      //Box::leak的作用是 将堆上的内存转换为 'static 生命周期的引用
      let leaked_str: &amp;'static str = Box::leak(tmp.into_boxed_str());
      Self{name: leaked_str }
    }
}

fn main() {
    let name1 = Name{name:"lu"};
    let name2 = Name{name:" mula"};
    let name3=name1+name2;
    println!("name1={:?},name2={:?}", name1,name2);
    println!("name3={:?}", name3);

    let name4=String::from("lu");
    let name5=String::from("lu");
    let name6=add(name4,name5);
    println!("name6={:?}", name6);
}

fn add(name1:String,name2:String)-&gt;String{
    format!("{} {}",name1,name2)
}</code></pre>
<p>运行结果:</p>
<p><img src="https://img2024.cnblogs.com/blog/1177268/202503/1177268-20250321171557340-159834724.png"></p>
<p>本例主要说明以下几个问题:</p>
<p>1.字符串切片可以Copy.所以包含字符串切片的类型也可以实现+等重载</p>
<p>2.实现和字符串切片有关的内容太麻烦,导出是奇怪的生命周期符号</p>
<h1>六、小结</h1>
<p>1.rust通过定义通用类型参数的默认类型来实现运算符重载</p>
<p>2.但也不是什么对象都可以重载,最好是能够实现Copy特质的类型,否则可能失败</p>

</div>
<div id="MySignature" role="contentinfo">
    <p>本文来自博客园,作者:正在战斗中,转载请注明原文链接:https://www.cnblogs.com/lzfhope/p/18777208</p><br><br>
来源:https://www.cnblogs.com/lzfhope/p/18777208
頁: [1]
查看完整版本: rust学习二十.6、RUST通用类型参数默认类型和运算符重载