最新在学习Rust语言,被迫对内存管理,生命周期管理等做了一遍理解和研究。
顺便也就和C/C++,python作了一下对比。
C/C++
先说C/C++,这个语言都比较熟悉,赋值操作默认都是按照浅拷贝来的,也就是对于非指针和引用类型来说,元素如预期一样被复制了一份;但是对于引用和指针来说,复制的只是一份指向关系,目标内存还是只有一个。
这也是为什么C++类的析构函数中,经常要处理指针成员指向内存的原因。同时也是容器类型中经常需要存储对象指针而不是对象本身的原因。
python
python的内存管理是GC(垃圾回收),所以其赋值操作以及参数传递等都是采用了引用的方式。此种方式配合GC堪称完美,但是如果不理解就会搞出很多问题。
比如比较经典的问题,可变与不可变元素被“修改”时,对其操作结果的不同。
a = 13
b = a
b += 1
print(a) # 13
print(b) # 14
a = [1, 2]
b = a
a.append(3)
print(a) # [1, 2, 3]
print(b) # [1, 2, 3]
进一步引申到List中如果有可变和不可变,结果也不同
a = ['a', [1, 2, 3]]
b = list(a) // 浅拷贝,a b不同,但是里面的元素都是同样的对象的引用
a[0] = 'b'
print(a) // ['b', [1, 2, 3]]
print(b) // ['a', [1, 2, 3]]
a[1].append(4)
print(a) // ['b', [1, 2, 3, 4]]
print(b) // ['a', [1, 2, 3, 4]]
更隐蔽的场景是在函数的形参和实参传递时,形参其实也是实参的引用
def add_two_num(a, b):
a += b
return a
if __name__ == '__main__':
a = 3
b = 4
print(add_two_num(a, b)) # 7
print(a, b) # 3 4 a 没变
a = [1, 2]
b = [3, 4]
print(add_two_num(a, b)) # [1, 2, 3, 4]
print(a, b) # [1, 2, 3, 4] [3, 4] a 变了
a = (1, 2)
b = (3, 4)
print(add_two_num(a, b)) # (1, 2, 3, 4)
print(a, b) # (1, 2) (3, 4) a 没变
Rust
Rust的内存管理采用了C++的RAII(Resource Acquisition Is Initialization) + 生命周期的高级玩法,这样就使得赋值与众不同。
原则上来说,Rust的赋值默认是所有权转移,即右值对应内存的所有权会被转移到左值上去,同时右值处于未初始化状态。
而对于基本数据类型(整数/浮点/str),由于其默认实现了copy和clone trait,所以赋值都是做拷贝。
fn main() {
let a = 1;
let b = a;
println!("{}", a);
println!("{}", b);
let a = String::from("String");
let b = a; // String没有实现copy trait,所以这里是所有权转移 move
println!("{}", a); // ^ value borrowed here after move
println!("{}", b);
}
总结
对于不同的语言,内存管理方式是赋值差异的根本,所以必须优先搞清楚。其次对于赋值或者说所有权的管理方式,涉及到程序效率,正确性等方面,需要特别小心。
不断的刻意练习,才能让你在编码的时候下笔如飞却还能心中有数。