面试题答案
一键面试1. 实现 *
运算符重载
首先,定义 BigNum
结构体和 Mul
特征的实现。假设 BigNum
内部使用 Vec<u32>
来存储大整数的各个部分(这里为了简化,实际应用中可能会使用更优化的存储方式,如 Vec<u64>
或更复杂的结构)。
use std::ops::Mul;
#[derive(Debug)]
struct BigNum {
digits: Vec<u32>,
}
impl Mul for BigNum {
type Output = Result<BigNum, String>;
fn mul(self, other: BigNum) -> Result<BigNum, String> {
// 处理特殊情况:如果其中一个数为零,则结果为零
if self.digits.is_empty() || other.digits.is_empty() {
return Ok(BigNum { digits: vec![0] });
}
let mut result = vec![0; self.digits.len() + other.digits.len()];
for (i, a) in self.digits.iter().enumerate() {
let mut carry = 0;
for (j, b) in other.digits.iter().enumerate() {
let sum = a * b + result[i + j] + carry;
result[i + j] = sum % 10000;
carry = sum / 10000;
}
if carry > 0 {
result[i + other.digits.len()] += carry;
}
}
// 去除前导零
while result.len() > 1 && result.last().unwrap() == &0 {
result.pop();
}
Ok(BigNum { digits: result })
}
}
2. 内存管理与性能优化
- 减少中间数据生成:在上述实现中,我们直接在
result
向量中累加乘法结果,避免了生成大量临时的中间数据。每次乘法运算的结果直接累加到result
的对应位置,并处理进位。 - 内存管理:
BigNum
结构体使用Vec<u32>
来存储数据,Vec
类型在 Rust 中自动管理内存,在需要时分配和释放内存。在乘法运算过程中,我们预先分配足够大小的result
向量,减少动态内存分配的次数。
3. 处理溢出情况
在上述实现中,我们通过 carry
变量来处理每一步乘法和加法运算中的溢出情况。当 a * b + result[i + j] + carry
的结果大于 10000
(这里假设 u32
的最大值小于 10000
)时,我们将溢出部分存储在 carry
中,并在下一次迭代中处理。最终,如果 carry
在所有迭代后仍有值,将其累加到 result
的更高位。
4. 与所有权和借用规则协同工作
在 Rust 中,Mul
特征的 mul
方法签名为 fn mul(self, other: Self) -> Self::Output
。这意味着 self
和 other
都是值传递,方法结束后,原来的 BigNum
实例所有权被转移到 mul
方法内部,这避免了悬垂指针和双重释放等内存安全问题。
返回值类型 Result<BigNum, String>
也遵循所有权规则。如果运算成功,BigNum
实例的所有权被返回给调用者;如果运算失败(如处理溢出或其他错误情况),错误信息(String
)的所有权被返回。这样确保了内存安全,因为 Rust 的所有权系统会确保在任何时刻只有一个所有者可以访问和修改内存。
这样,我们通过合理的实现和遵循 Rust 的所有权规则,完成了 *
运算符的重载,实现了高效的大整数乘法运算,并处理了内存安全和溢出问题。