python,C,Rust中的赋值的差异

472 阅读3分钟

最新在学习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);
}

总结

对于不同的语言,内存管理方式是赋值差异的根本,所以必须优先搞清楚。其次对于赋值或者说所有权的管理方式,涉及到程序效率,正确性等方面,需要特别小心。

不断的刻意练习,才能让你在编码的时候下笔如飞却还能心中有数。