python 中的賦值操作,與c/c++的對比

python 中的賦值操作,與c/c++的對比

def foo(l):
      l += 'b'

l = 'abc'
foo(l)
print(l)        #result   'abc' not 'abcb'

l = ['abc']
foo(l)
print(l)       #result ['abc','b']

作個總結,網上已經有相關的內容了,這裏方便記憶。也許有理解錯誤的地方:)
先看後一種情況,python中的所有變量傳遞都是傳遞引用(感覺類似c中的指針,即傳遞對象的地址),類似java中的非內置類型。所以引用本身不可變,但是被引用的內容是可以變化的。
所以list l中的內容在執行foo(l)後變化了。
個人暫時理解,python,java中不支持引用的引用吧,類似c中的二級指針,例如如何實現下面的函數呢
createTree(Node *&root)
感覺不能實現,雖然createTree可以利用返回值用其它的方式實現但是上面的形式似乎無法在python,java中實現。

那麼對於上面的代碼,第一種情況,爲什麼內容不變呢,
個人認爲可以暫時這麼理解,內存中有個地址x,存儲'abc‘
l 是'abc'的一個引用(類似c中 int *l; l = x),在foo(l)的時候產生一個臨時變量 它也指向 'abc'
但問題在於python中的string,int等是不可變的,
也就是說 'abc' + 'b'會在內存中新開一個地址,存儲 'abcb' ,然後 foo作用域的l被重新賦值,作爲'abcb'的引用,但是foo執行完,glbal域的l依然指向'abc'依然是 'abc'的引用,同時脫離foo作用域後,'abcb'所佔用的空間應該已經被系統回收了。
這就是所謂對於函數的中變量賦值所帶來的引用所指的內容可以變,引用本身不變,而偏偏string 是不可變的 'abc' + 'b'並沒有改變'abc'而是新開空間存儲了'abcb'同時賦值給local l 這個賦值並不改變global
中的l.

所以這也就解釋了,爲什麼python,java中要實現類似c++中的swap
void swap(int &a, int &b)
需要採取一些work around的方法了。

所以
python,java中一切參數傳遞都是引用的傳遞,也可理解爲傳地址,對象的內容可以變,地址卻不會變。
所以傳過去的變量,其內容可以變化,但是地址不變。

def foo(a):
     a.left = 3
     a.right = 4
     b = A()
     b.left = 5
     b.right = 6
     a = b
#初始
a.left = 1
a.right = 1
#調用foo(a)
foo(a)
#之後a.left = 3 , a.right = 4

如果傳過去的變量a的類型爲A,可以理解爲
struct A{
   int left;
   int right;
}
void foo(A *a)  
   
void foo(A *a)
    a->left = 3
    a->right = 4
    A *b = new A;
    b->left = 5
    b->right = 6
    a = b
運行完 foo(a)
a->left = 3, a->right = 4

那麼考慮c++引用
void foo(A &a)
        a.left = 3
        a.right = 4
        A b;
        b.left = 5
        b.right = 6
        a = b

運行完 foo(a) a.left = 5, a.right = 6
這裏體現的c++與python的不同在 a = b
python中只是引用的賦值,可以理解爲對象地址的賦值,而C++這裏 a = b是內容的賦值,是內容拷貝了(淺拷貝,嚴格複製內容)。
所以完成後a.left = 5,a.right = 6.而foo中的b運行完 foo就已經不存在了。
而python 只是引用賦值,並且利用引用計數的技術,一個對象對它的引用爲0後會被回收。


如果要實現c++的引用效果,
swap(int &a, int &b)

只能採用 外包一個類如s s.a s.b
swap(s)
   temp = s.a
   s.a = s.b
   s.b = temp
s.a = a
s.b = b
swap(s)
a = s.b
b = s.a

或者用list 封裝 a, b
最簡單的寫法 [a, b] = [b, a]

對於createTree(Node *&root)
createTree(Node *&root)
    root = new Node()
    createTree(root->left())
    createTree(root->right())

將 root 作爲函數返回值

如果有多個 &,如 &num, &sum,那麼也應該按list封裝,或者封裝到類中傳類對象即可。
嗯,感覺在這種情況,c++寫起來更方便寫,不過python,java的處理也有它的道理,所有的傳遞都是傳對象地址,統一處理,
傳過去的變量,只會使其的內部內容變化,不會是它本身改變,本質上傳遞的參數還是要作爲右值的,可讀不可寫(寫無意義),
不會莫名其妙的運行一個函數後原來函數作用域的變量莫名其妙變化了。我們傳遞參數爲了使用它而不是改變它。
當然代價是失去c++在這裏處理方式的靈活性了。


09.9.14
另外補充下,
1. 在C++中其實大部分傳遞引用的函數例如下面,其實應該是隻讀的,如下
void TestSkipList(DeterminSkipList<T> &skip_list, ifstream &in, ofstream &out)
函數裏面會有skip_list.insert()等,也可能會改變skip_list所指向的對象的
內容,所以不可加 const 限制符。這裏用引用是和python中傳遞引用是完全一致的概念情形,如果不傳遞引用就好有臨時對象拷貝的產生,但是注意c++唯一的不同是
理論上skip_list = another_list 是對象拷貝,而對於python 是引用拷貝。所以會出現不同情形,注意區分即可。
1 #include <iostream>
2 using namespace std;
3 
4 class list {
5     public:
6         list(int a = 3): data(a){}
7         
8         int data;
9 };
10 
11 void foo(list &l)
12 {
13     l.data = 4;
14     
15     list b(5);
16 
17     l = b;
18 }
19 
20 
21 int main(int argc, char *argv[])
22 {
23     list a;
24 
25     list* pl = &a;
26 
27     foo(a);
28 
29     list* pl2 = &a;
30 
31     cout << a.data << endl;
32 
33     cout << (pl == pl2) << endl;
34     return 0;
35 }

c++運行結果
5
1

class list():
    
def __init__(self, a = 3):
        self.data 
= a

def foo(l):
    l.data 
= 4
    b 
= list(5)
    l 
= b

= list()
foo(a)
print(a.data)

python運行結果
4

所以結果不同就體現在l = b的賦值,c++是對象內容拷貝, python是對象引用賦值,而局部變量的變化不會影響到被傳遞的變量值。
類似的java代碼結果也應該是4.

2. 那麼對於一些python不好模擬的情況,如rec(int n, int &a, int &b)其實這裏的a,b是作爲一個類似全局變量的概念的,注意是類似,其實和python 3.x提出的nonlocal概念是一致的,很多時候會在C++中寫遞歸函數中用到類似rec(int n, int &a, int &b)的寫法,用,
a,和b記錄遞歸過程中的全局變量,對每個遞歸函數都可見,而不是像n,每個遞歸函數看到的都是local的拷貝。
這種情況下rec函數其實一般都是一個helper函數,外面用戶調用的時候其實調用一個其它的接口如foo(n)
int foo(int n) {
//調用rec
int a = 3;
int b = 4;
rec(n, a, b);
}

那麼其實可以把rec看成foo的內部實現所需要的一個helper 函數。在python中我們可以用嵌套函數來實現,而對於python3.x,利用
nonlocal變量,可以完全模擬c++的行爲。注意的是python2.x不支持nonlocal
下面給出代碼,運行結果都是22
C++
1 #include <iostream>
2 using namespace std;
3 
4 
5 void rec(int n, int &a, int &b) {
6     if (n == 0)
7         return;
8     a += 1;
9     b += 2;
10     rec(n - 1, a, b);
11 }
12 
13 int foo(int n) {
14     int a = 3;
15     int b = 4;
16     rec(n, a, b);
17     return a + b;
18 }
19 
20 
21 int main(int argc, char *argv[])
22 {
23     cout << foo(5<< endl;
24     return 0;
25 }
python
1 def foo(n):
2     def rec(n):
3         if n == 0:
4             return
5         nonlocal a
6         nonlocal b
7         a += 1
8         b += 2
9         rec(n - 1)
10     a = 3
11     b = 4
12     rec(n)
13     return a + b
14 
15 print(foo(5))

最後貼一下upenn的課件關於python變量的部分
image
image
image
發佈了11 篇原創文章 · 獲贊 8 · 訪問量 22萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章