寫在前面:Python和C++中的賦值與深淺拷貝,由于其各自語言特性的問題,在概念和實現(xiàn)上稍微有點差異,本文將這C++和Python中的拷貝與賦值放到一起,希望通過對比學(xué)習(xí)兩語言實現(xiàn)上的異同點,加深對概念的理解。
1. Python中的賦值、淺拷貝、深拷貝
C++中所謂的 淺拷貝就是由(系統(tǒng)默認的)拷貝構(gòu)造函數(shù)對數(shù)據(jù)成員進行逐一的賦值 ,通常默認的拷貝構(gòu)造函數(shù)就是可以達到該效果的,但是如果類中有指針類型的數(shù)據(jù)(需要在堆上分配內(nèi)存),那么此時使用默認的拷貝構(gòu)造函數(shù)就會帶來錯誤。因為此時采用簡單的淺拷貝,則兩個類中的兩個指針將指向同一個地址,當(dāng)對象釋放時,會調(diào)用兩次析構(gòu)函數(shù),而導(dǎo)致指針懸掛現(xiàn)象(懸浮指針)。
而C++的 深拷貝則是,使用自定義的拷貝構(gòu)造函數(shù),將原有對象的所有成員變量拷貝給新對象,對于指針等數(shù)據(jù)還會為新對象重新在堆上分配一塊內(nèi)存,并將原有對象所持有的堆上的數(shù)據(jù)也拷貝過來,這樣能保證原有對象和新對象所持有的動態(tài)內(nèi)存都是相互獨立的,更改一個對象的數(shù)據(jù)不會影響另一個對象,同時也不會造成double free
的錯誤。
C++中的 賦值,默認調(diào)用的是默認的拷貝構(gòu)造函數(shù)即淺拷貝,如果要使用深拷貝需要重載賦值運算符,為動態(tài)內(nèi)存在堆上分配空間即可~
C++ 淺拷貝示例:
#include <iostream>
// 淺拷貝 使用默認的構(gòu)造函數(shù)
class shallowCopy {
public:
shallowCopy(int len) : m_len(len) {
m_ptr = new int(0); // m_ptr指向一個值為0的int
}
shallowCopy() {}
~shallowCopy() {
delete m_ptr;
}
public: // 定義為public,方便輸出
int* m_ptr;
int m_len;
};
int main()
{
shallowCopy sc(1);
auto sc1 = sc; // 淺拷貝
std::cout << "shallowCopy: " << std::endl;
std::cout << "sc.m_ptr = " << sc.m_ptr << std::endl;
std::cout << "sc1.m_ptr = " << sc1.m_ptr << std::endl;
}
>>>shallowCopy:
sc.m_ptr = 0x560c930aeeb0
sc1.m_ptr = 0x560c930aeeb0
free(): double free detected in tcache 2 // 嘗試兩次釋放同一地址?。?!報錯
Aborted
C++ 深拷貝示例:
#include <iostream>
class deepCopy {
public:
deepCopy(int len) : m_len(len) {
std::cout << "call deepCopy(int len) " << std::endl;
m_ptr = new int(1);
}
deepCopy(const deepCopy& deepcopy) {
std::cout << "call deepCopy(const deepCopy& deepcopy) " << std::endl;
m_len = deepcopy.m_len;
m_ptr = new int(*(deepcopy.m_ptr)); // 重新分配內(nèi)存,并且賦值
} // 拷貝構(gòu)造函數(shù)
~deepCopy() {
delete m_ptr;
}
public:
int* m_ptr;
int m_len;
};
int main()
{
std::cout << "deepCopy: " << std::endl;
deepCopy dc(1);
deepCopy dc1(dc); // 深拷貝
std::cout << "dc.m_ptr = " << dc.m_ptr << std::endl;
std::cout << "dc1.m_ptr = " << dc1.m_ptr << std::endl;
}
>>>deepCopy:
call deepCopy(int len)
call deepCopy(const deepCopy& deepcopy)
dc.m_ptr = 0x560c930af2e0
dc1.m_ptr = 0x560c930af300
2. C++中的賦值、淺拷貝、深拷貝
在Python參數(shù)傳遞,“值傳遞”還是“引用傳遞“?一文中我們從Python中可變對象與不可變對象的角度理解了Python中的參數(shù)傳遞的方式,在賦值、深拷貝、淺拷貝中,我們同樣從這個角度入手,理解Python中的深淺拷貝。對可變對象、不可變對象不是很清晰的同學(xué),可以移步鏈接復(fù)習(xí)一下~。
- 不可變對象:一旦創(chuàng)建就不可修改的對象,包括字符串、元組、數(shù)值類型
(該對象所指向的內(nèi)存中的值不能被改變。當(dāng)改變某個變量時候,由于其所指的值不能被改變,相當(dāng)于把原來的值復(fù)制一份后再改變,這會開辟一個新的地址,變量再指向這個新的地址。)
- 可變對象:可以修改的對象,包括列表、字典、集合
(該對象所指向的內(nèi)存中的值可以被改變。變量(準確的說是引用)改變后,實際上是其所指的值直接發(fā)生改變,并沒有發(fā)生復(fù)制行為,也沒有開辟新的地址,通俗點說就是原地改變。)
2.1 概念
-
賦值,類似于C++中的引用(別名),只是復(fù)制了新對象的引用,不會開辟新的內(nèi)存空間,Python中賦值的一般形式為
a = 'nihao'
,內(nèi)存中實現(xiàn)是:內(nèi)存開辟空間存儲字符串nihao
,將a指向這塊內(nèi)存空間:
- 淺拷貝: 創(chuàng)建新對象,其內(nèi)容是原對象的引用。
? Python中的淺拷貝有三種形式: 切片操作,工廠函數(shù),copy模塊中的copy函數(shù)。
? 如: lst = [1,2,[3,4]]
? 切片操作:lst1 = lst[:]
或者 lst1 = [each for each in lst]
? 工廠函數(shù):lst1 = list(lst)
? copy函數(shù):lst1 = copy.copy(lst)
?
? 淺拷貝之所以稱為淺拷貝,是因為它僅僅只拷貝了一層,拷貝了最外圍的對象本身,內(nèi)部的元素都只是拷貝了一個引用而已,如在lst中有一個嵌套的 list[3,4],如果我們修改了它,情況就不一樣了。
? 淺拷貝要分兩種情況進行討論:
? 1)當(dāng)淺拷貝的值是 不可變對象(字符串、元組、數(shù)值類型) 時和“賦值”的情況一樣,對象的id值 (id()函數(shù)用于獲取對象的內(nèi)存地址) 與淺拷貝原來的id值相同。
? 2)當(dāng)淺拷貝的值是 可變對象(列表、字典、集合) 時會產(chǎn)生一個“不是那么獨立的對象”存在。
? 2.1) 拷貝的可變對象中無復(fù)雜子對象,原來值的改變并不會影響淺拷貝的值,同時淺拷貝的值改變也并不會影響原來的值。
? 2.2) 拷貝的可變對象中有復(fù)雜子對象(例如列表中的一個子元素是一個列表),如果不改變其中復(fù)雜子對象,淺拷貝的值改變并不會影響原來的值。 但是改變原來的值中的復(fù)雜子對象的值會影響淺拷貝的值。
- 深拷貝:和淺拷貝對應(yīng),深拷貝拷貝了對象的所有元素,包括多層嵌套的元素。深拷貝出來的對象是一個全新的對象,不再與原來的對象有任何關(guān)聯(lián)。
只有一種形式,copy模塊中的deepcopy函數(shù)
2.2 示例:從例子中理解
1) 不可變對象的賦值、深拷貝、淺拷貝
import copy
# 不可變對象,無法添加刪除元素
a = (1, 2, 3)
print("==========")
b = a
print(a, b)
print(id(a), id(b))
print("=====shallow copy=====")
s = copy.copy(a)
print(a, s)
print(id(a), id(s))
print("=====deep copy=====")
d = copy.deepcopy(a)
print(a, d)
print(id(a), id(d))
>>>==========
((1, 2, 3), (1, 2, 3))
(4564433008, 4564433008)
=====shallow copy=====
((1, 2, 3), (1, 2, 3))
(4564433008, 4564433008)
=====deep copy=====
((1, 2, 3), (1, 2, 3))
(4564433008, 4564433008)
2) 可變對象的賦值、淺拷貝與深拷貝
import copy
a = [1, 2, 3]
print("==========")
b = a
b.append(4)
print(a, b)
print(id(a), id(b)) # 賦值僅是變量的別名,兩變量擁有相同的內(nèi)存地址,無論更改哪一個另一個都會更改
a = [1, 2, 3]
print("=====shallow copy=====")
s = copy.copy(a)
print(a, s)
print(id(a), id(s))
a.append(4)
print("------append 4-------")
print(a, s)
print(id(a), id(s))
a = [1, 2, 3]
print("=====deep copy=====")
d = copy.deepcopy(a)
print(a, d)
print(id(a), id(d))
print("------append 4-------")
a.append(4)
print(a, d)
print(id(a), id(d))
>>>==========
([1, 2, 3, 4], [1, 2, 3, 4])
(4564157144, 4564157144)
=====shallow copy=====
([1, 2, 3], [1, 2, 3])
(4564158440, 4564158512)
------append 4-------
([1, 2, 3, 4], [1, 2, 3])
(4564158440, 4564158512)
=====deep copy=====
([1, 2, 3], [1, 2, 3])
(4564158368, 4564158440)
------append 4-------
([1, 2, 3, 4], [1, 2, 3])
(4564158368, 4564158440)
3) 可變對象深淺拷貝(外層、內(nèi)層改變元素)
# 外層元素更改
import copy
l = [1, 2, 3, [4, 5]]
l1 = l
l2 = copy.copy(l)
l3 = copy.deepcopy(l)
l.append(6)
print(l)
print(l1)
print(l2)
print(l3)
>>>[1, 2, 3, [4, 5], 6]
[1, 2, 3, [4, 5], 6]
[1, 2, 3, [4, 5]]
[1, 2, 3, [4, 5]]
# 內(nèi)層元素更改
import copy
l = [1,2,3,[4, 5]]
l1 = l #賦值
l2 = copy.copy(l) #淺拷貝
l3 = copy.deepcopy(l) #深拷貝
l[3].append(6)
print(l)
print(l1)
print(l2)
print(l3)
>>> [1, 2, 3, [4, 5, 6]]
[1, 2, 3, [4, 5, 6]]
[1, 2, 3, [4, 5, 6]]
[1, 2, 3, [4, 5]]
-
外層添加元素時,淺拷貝不會隨原列表變化而變化;內(nèi)層添加元素時,淺拷貝才會變化。
-
無論原列表如何變化,深拷貝都保持不變。文章來源:http://www.zghlxwxcb.cn/news/detail-785412.html
-
賦值對象隨著原列表一起變化。文章來源地址http://www.zghlxwxcb.cn/news/detail-785412.html
到了這里,關(guān)于[開發(fā)語言][c++][python]:C++與Python中的賦值、淺拷貝與深拷貝的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!