手机浏览 RSS 2.0 订阅 膘叔的简单人生 , 腾讯云RDS购买 | 超便宜的Vultr , 注册 | 登陆
浏览模式: 标准 | 列表2008年12月24日的文章

浅复制与深复制中的传值与传址

说实话,我看到这个标题的时候没有看懂,确实没有理解本文的标题想表达什么意思。看完之后我是理解了。先贴上原文,再谈我的理解。。。

原文地址:http://www.cn-cuckoo.com/2008/12/24/shallow-or-deep-copy-and-pass-by-value-or-reference-270.html

内容如下:

XML/HTML代码
  1. 这个标题念起来有点拗口,但却是理解数据结构的关键。标题中的4个术语,对应的英文分别是:shallow copy(注意,不是shadow copy)、deep copy、pass by value、pass by reference(或pass by address)。传址和传引用是一回事。  
  2.   
  3. 一门编程语言的核心是数据结构,粗略来讲,可以把数据结构分成不可变类型(immutable)和可变类型(mutable)。为什么这么分呢?这涉及到内存分配问题。对于不可变类型,只要分配有限的内存空间即可,而对于不可变类型,理论上则要分配没有大小限制的空间。因此,这么分是出于合理利用系统资源的考虑。实际上,堆内存和栈内存分别用于保存不可变类型值和可变类型值。  
  4.   
  5. 什么是不可变类型?就是该值一旦赋予某个变量,就只属于某个变量,不能同属于其他变量。如:  
  6.   
  7. var stringValue = “I’m immutable data structure, mean you can’t modify me!”;  
  8. var anotherStringValue = stringValue;  
  9. stringValue = “I have changed”;  
  10.   
  11. 此时,anotherStringValue中保存的值会不会也变成“I have changed”?不会。因为  
  12.   
  13. var anotherStringValue = stringValue;  
  14.   
  15. 照stringValue中保存的字符串的原样,复制一个字符串(相应地,在内存中分配一块新空间),并将该字符串赋给 anotherStringValue。换句话说,这两个变量虽然保存的值相同,但它们的值并不在一块内存中。因此,修改任何一个变量,都不会影响另一个变量。即  
  16.   
  17. stringValue = “I have changed”;  
  18.   
  19. 只会影响stringValue的值。但是,确切来讲,stringValue = “I have changed”;并不是修改stringValue,而是创建了一个新字符串(相应地,在内存中分配一块新空间),然后让stringValue引用该字符串——更像是替换变量的值;原来的字符串呢?因为没有变量引用它,也就成为垃圾了(当然,垃圾所占用的内存会被回收)。  
  20.   
  21. 由此可见,赋值操作对于不变类型而言,传递的是内存中的值本身。那么,对于可变类型呢?当然,传递的是内存中值的引用(或者说地址),而且无论传递多少次,内存中始终都只有一份原始值——毕竟可变类型大小莫测,只保存一份原始值能最大限度节省内存空间。例如:  
  22.   
  23. var objectValue = {1:1,’s’:’string’,'innerObject’:{’innerArray’ : [1,2,3]}};  
  24. var anotherObjectValue = objectValue;  
  25. objectValue[1] = 100;  
  26. anotherObjectValue[1]; //100  
  27.   
  28. 不言自明,这里的anotherObjectValue通过赋值操作,从objectValue那里只获得了对原始对象( {1:1,’s’:’string’,'innerObject’:{’innerArray’ : [1,2,3]}})的引用,也就是该对象在内存中的地址,或者说“门牌号码”。因此,修改通过objectValue修改原始对象的第一个元素(objectValue[1] = 100;),结果当然会在anotherObjectValue[1]这里得到反映了。  
  29.   
  30. 在JavaScript中,给函数传递参数是按照上述默认约定——即对不可变类型,传值;对可变类型,传址——进行的。如:  
  31.   
  32. function example(str, obj){  
  33. ……  
  34. }  
  35. example(stringValue,objectValue);  
  36.   
  37. 调用example函数时,第一个参数传递的是实际的字符串值,第二参数传递的是对象的引用(内存地址)。  
  38.   
  39. 在PHP中,定义函数时可以指定相应参数是传值还是传址——通常是传值。其实,这也很容易理解:假如函数要求为某个可变类型参数传值,而不是传址,那么也就意味着内存中会因此多出一份该类型值的副本。相应地,在函数中修改这份新副本,不会影响函数外的原副本。因为新旧副本在内存中就不是同一个地址。说到这,也就引出了浅复制和深复制的概念。事实上,浅复制和深复制的区别恰恰在于复制可变类型时,是传值还是传址。如果是像往常一样传址(传引用),那么就是浅复制。如果是传值,那么就是深复制。浅复制和深复制到底有什么区别呢?以下面的Python代码为例:  
  40.   
  41. >>> x = {’username’: ‘admin’, ‘machines’: [’foo’, ‘bar’, ‘baz’]}  
  42. >>> y = x.copy()  
  43. >>> y[’username’] = ‘mlh’  
  44. >>> y[’machines’].remove(’bar’)  
  45. >>> y  
  46. {’username’: ‘mlh’, ‘machines’: [’foo’, ‘baz’]}  
  47. >>> x  
  48. {’username’: ‘admin’, ‘machines’: [’foo’, ‘baz’]}  
  49.   
  50. 调用字典x的copy方法返回一个新字典并赋值给y,新字典中带有与原字典相同的键-值对。注意,copy方法采用浅复制创建的新字典,与原字典有区别也有联系。区别体现在,对于原字典中不可变的值,如数字、字符串、元组等,会在新字典中重新生成一份新副本;因此,修改(实际上是替换,或者说是重新赋值)这些键的值(y[’username’] = ‘mlh’)不会影响原字典。联系体现在,对于原字典中可变的值,如列表、字典,不会在新字典中生成新副本,而只复制值的引用,即新字典中相应的键保存的是引用,当然,原字典中相应的键保存的也是引用,而且这两个引用都指向同一块内存地址。这就是所谓的浅复制。因此,如果修改的是可变类型的值(y[’machines’].remove(’bar’)),就一定会影响引用该值的原字典项了。  
  51.   
  52. 深复制则不然。深复制是实实在在地把原字典中所有的值全都照原样子重新创建一遍,无论是不变类型值,还是可变类型值。执行深复制后,内存中会存在两份完全一样的数据段,但分别处于不同内存空间中,即地址不同。而且,分别由不同变量(原字典、新字典)引用。因此,经过深复制后修改一个字典,不会影响另一个字典。Python的copy模块中的deepcopy函数可以实现深复制:  
  53.   
  54. >>> from copy import deepcopy  
  55. >>> d = {}  
  56. >>> d[’names’] = [’Alfred’, ‘Bertrand’]  
  57. >>> c = d.copy()  
  58. >>> dc = deepcopy(d)  
  59. >>> d[’names’].append(’Clive’)  
  60. >>> c  
  61. {’names’: [’Alfred’, ‘Bertrand’, ‘Clive’]}  
  62. >>> dc  
  63. {’names’: [’Alfred’, ‘Bertrand’]}  
  64.   
  65. 显然,修改深复制得到的新值不会影响原值;而修改浅复制得到的“新”值,在某种程度上仍然会影响原值。  
作者想表达的意思应该是在javascript中变量的复制和赋值,对于普通的变量而言,赋值仅仅是一个复制,而对于对象而言,赋值则是一个引用。

比如:var a=1;var b=a;

在这里,b其实是a的一个复制,所以b=1,正因为是复制,所以复制完后,b和a就没有任何关系了,当a重新赋值的时候,对于b则没有影响,同样,对于b再重新赋值,对a也没有影响 。

但是,如果a是一个对象,那就不一样了

例如var test ={a:1,b:2};var test1 =test;

在这样的情况下,test1就不再是test的复制了,而是直接取了test的地址,所以对于Test的值的改变,也影响到了test1,比如我test.a = 2,那么我test1.a的值也就自动变成了2

这点其实在PHP5里面已经也这样了,在PHP4的时候,对象的赋值也是一个复制,所以我们为了保证只有一个实例,往往都是采用:$a = &$b ;但是从5开始则不一样,对于对象而言,如果没有特别指定的操作,那么就相当于是对地址的一个引用。效果和上面的JS代码类似。

作者在最后举了一个PYTHON的例子来说明深复制,其实也就是为对象也做一个拷贝而不是采用引用,这个,当然在PHP里也有,clone就是实现的这个效果。HOHO

Tags: javascript, copy, reference

平安夜

老外的节日中国人在过,不过也算是国外的新年吧
圣诞快乐,朋友们。。。
剩蛋,生蛋才行,过两天还有圆蛋。。。

 

可惜我还要上课,郁闷啊。。。回家的时候带了两杯老婆喜欢的咖啡,希望她也快乐。

Tags: 平安夜, 圣诞节

VI命令备份

网上找来的,因为需要在命令行下使用vi,不得己了。。。。人都是逼出来的嘛。

  进入vi的命令

  vi filename :打开或新建文件,并将光标置于第一行首

  vi +n filename :打开文件,并将光标置于第n行首

  vi + filename :打开文件,并将光标置于最后一行首

  vi +/pattern filename:打开文件,并将光标置于第一个与pattern匹配的串处

  vi -r filename :在上次正用vi编辑时发生系统崩溃,恢复filename

  vi filename....filename :打开多个文件,依次编辑

  移动光标类命令

  h :光标左移一个字符

  l :光标右移一个字符

  space:光标右移一个字符

  Backspace:光标左移一个字符

  k或Ctrl+p:光标上移一行

  j或Ctrl+n :光标下移一行

  Enter :光标下移一行

  w或W :光标右移一个字至字首

  b或B :光标左移一个字至字首

  e或E :光标右移一个字j至字尾

  ) :光标移至句尾

  ( :光标移至句首

  }:光标移至段落开头

  {:光标移至段落结尾

  nG:光标移至第n行首

  n+:光标下移n行

  n-:光标上移n行

  n$:光标移至第n行尾

  H :光标移至屏幕顶行

  M :光标移至屏幕中间行

  L :光标移至屏幕最后行

  0:(注意是数字零)光标移至当前行首

  $:光标移至当前行尾

  屏幕翻滚类命令

  Ctrl+u:向文件首翻半屏

  Ctrl+d:向文件尾翻半屏

  Ctrl+f:向文件尾翻一屏

  Ctrl+b;向文件首翻一屏

  nz:将第n行滚至屏幕顶部,不指定n时将当前行滚至屏幕顶部。

  插入文本类命令

  i :在光标前

  I :在当前行首

  a:光标后

  A:在当前行尾

  o:在当前行之下新开一行

  O:在当前行之上新开一行

  r:替换当前字符

  R:替换当前字符及其后的字符,直至按ESC键

  s:从当前光标位置处开始,以输入的文本替代指定数目的字符

  S:删除指定数目的行,并以所输入文本代替之

  ncw或nCW:修改指定数目的字

  nCC:修改指定数目的行

  删除命令

  ndw或ndW:删除光标处开始及其后的n-1个字

  do:删至行首

  d$:删至行尾

  ndd:删除当前行及其后n-1行

  x或X:删除一个字符,x删除光标后的,而X删除光标前的

  Ctrl+u:删除输入方式下所输入的文本

  搜索及替换命令 :

  /pattern:从光标开始处向文件尾搜索pattern

  ?pattern:从光标开始处向文件首搜索pattern

  n:在同一方向重复上一次搜索命令

  N:在反方向上重复上一次搜索命令

  :s/p1/p2/g:将当前行中所有p1均用p2替代

  :n1,n2s/p1/p2/g:将第n1至n2行中所有p1均用p2替代

  :g/p1/s//p2/g:将文件中所有p1均用p2替换

  选项设置

  all:列出所有选项设置情况

  term:设置终端类型

  ignorance:在搜索中忽略大小写

  list:显示制表位(Ctrl+I)和行尾标志($)

  number:显示行号

  report:显示由面向行的命令修改过的数目

  terse:显示简短的警告信息

  warn:在转到别的文件时若没保存当前文件则显示NO write信息

  nomagic:允许在搜索模式中,使用前面不带“”的特殊字符

  nowrapscan:禁止vi在搜索到达文件两端时,又从另一端开始

  mesg:允许vi显示其他用户用write写到自己终端上的信息

  最后行方式命令

  :n1,n2 co n3:将n1行到n2行之间的内容拷贝到第n3行下

  :n1,n2 m n3:将n1行到n2行之间的内容移至到第n3行下

  :n1,n2 d :将n1行到n2行之间的内容删除

  :w :保存当前文件

  :e filename:打开文件filename进行编辑

  :x:保存当前文件并退出

  :q:退出vi

  :q!:不保存文件并退出vi

  :!command:执行shell命令command

  :n1,n2 w!command:将文件中n1行至n2行的内容作为command的输入并执行之,若不指

  定n1,n2,则表示将整个文件内容作为command的输入

  :r!command:将命令command的输出结果放到当前行 。

Tags: vi, 常用, 备份