ExcelHome技术论坛

 找回密码
 免费注册

QQ登录

只需一步,快速开始

快捷登录

搜索
EH技术汇-专业的职场技能充电站 妙哉!函数段子手趣味讲函数 Excel服务器-会Excel,做管理系统 效率神器,一键搞定繁琐工作
HR薪酬管理数字化实战 Excel 2021函数公式学习大典 Excel数据透视表实战秘技 打造核心竞争力的职场宝典
让更多数据处理,一键完成 数据工作者的案头书 免费直播课集锦 ExcelHome出品 - VBA代码宝免费下载
用ChatGPT与VBA一键搞定Excel WPS表格从入门到精通 Excel VBA经典代码实践指南
查看: 21607|回复: 47

[原创] 简单讲讲Copymemory这个函数

  [复制链接]

TA的精华主题

TA的得分主题

发表于 2016-6-5 08:13 | 显示全部楼层 |阅读模式
本帖已被收录到知识树中,索引项:Windows API应用
本帖最后由 hyefeifei 于 2016-6-6 11:00 编辑


  我尽量讲得简单通俗,有些说法甚至只是比喻,这就会忽略复杂性、也可能有失准确,但对初学者来说却是更容易理解,讲之前我搜了一下关于这个函数及指针的帖子,有几个贴子可谓专业翔实,非初学者可以看那些贴子,因水平所限,所讲难免有误,望大家指正,一起把问题搞清楚。

  开讲:

  一、长整型

  Sub Test1()
    Dim p As Long
    ‘Debug.Print Hex(VarPtr(p))
    p=20008
    ‘Debug.Print Hex(20008)
  End Sub

  1. 当执行dim p as long这一句代码时,发生了什么?

  此时,程序会在内存中分配一个4字节的空间,用0填充,以备写入数据。

  请看我执行此句后的内存截图:

   1.png

  绿框处为分配的空间,红框处005FF1B0为空间的首字节地址,它可以用varptr函数得到。

  2. 当执行p=20008时,程序会把20008转成16进制数4E28,然后从低位至高位依次填入保留的空间,所以填充次序是:284E,下面的截图是当程序执行完这句时的内存状态:

   2.png
  
  二、字节型、整形,单精度,双精度,布尔,货币,Variant

  当声明为上面的数据类型时,发生的情况与长整形的情况只有一点不同:

  分配的预留空间长度不同,比如双精度会分配8个字节的空间,其实大家都知道,我就不说了。

  三、字符串

  程序如下:

  Sub Test2()
    Dim str1 As String
    ‘Debug.Print Hex(VarPtr(str1))
    str1="abcde"
    ‘Debug.Print Hex(StrPtr(str1))
  End Sub


  当程序执行dim str1 as string时,发生了什么?,请看内存截图:

   3.png
  
  程序为str1在内存中分配了一个4个字节的空间,但一个字符需要2个字节的空间存储,四个字节的空间只能存2个字符啊,这是怎么回事?

  事情是这样嘀:

  它分配的这4个字节的空间,其实是保存一个长整形数值的,这个长整形数值是一个内存地址。

  当执行str1=”abcde”时,可以看到这个空间被0E054EF4填充了:

   4.png

  注意这个填充是按低位到高位的顺序填充的,我们说过这是一个地址,那我们就转到这个地址看一下,见图:

   5.png

  说明如下:

  1. 在这个地址处,我们看到了字符串”abcde”,”abcde”被保存在绿框处。

  大家注意,所有字符都是用一个2字节整形数表示的,比如a就是用&H0061表示的,你可以查一下ASCII码表,a表示为&H61,而这里&H0061是Unicode表示法。或者在立即窗口中执行?chr(&H0061),就明白了。

  但不要忘了,数字在内存中是从低位到高位填充的,所以a在内存中就变成6100,这样内存中61006200630064006500就表示abcde,即圈2绿框处。

  2. 那我们如何知道字符串在哪里结束呢?你看圈3处0000(表示空字符),这其实就是用一个空字符来表示字符串结束的位置。

  3. 但是第2条中用空字符表示结束有问题,因为我们的字符串中可能包含空字符,遇到这种情况你怎么判断字符串在哪结束?

  4. 第3条中的问题如何解决呢?从0E054EF4这个指针(即首位地址)往前数4个字符,圈4处,我们会看到一个长整形数字:0000000A,转成十进制数即是10,这个就是字符串的长度。

  示意图如下:

   6.png

  简单总结一下:

  当声明一个字符串时,会在内存中预分配一个四字节的空间,这个空间的首地址可以用varptr(str1)得到。这个空间存的不是字符串本身,当我们给字符串赋值时,程序会在内存中另辟一块空间存放字符串本身,而把另辟的那块空间的首地址写在这个预留的四字节的空间中。这个地址可以用strptr(str1)得到。

  待续…

评分

9

查看全部评分

TA的精华主题

TA的得分主题

发表于 2016-6-5 08:56 | 显示全部楼层
[广告] Excel易用宝 - 提升Excel的操作效率 · Excel / WPS表格插件       ★免费下载 ★       ★ 使用帮助
曲高和寡啊!

TA的精华主题

TA的得分主题

发表于 2016-6-5 11:20 | 显示全部楼层

TA的精华主题

TA的得分主题

发表于 2016-6-5 11:44 | 显示全部楼层

TA的精华主题

TA的得分主题

发表于 2016-6-5 12:58 | 显示全部楼层
[广告] VBA代码宝 - VBA编程加强工具 · VBA代码随查随用  · 内置多项VBA编程加强工具       ★ 免费下载 ★      ★使用手册
先MARK一下,学习。。。

TA的精华主题

TA的得分主题

发表于 2016-6-5 14:03 | 显示全部楼层

TA的精华主题

TA的得分主题

发表于 2016-6-5 14:56 | 显示全部楼层

TA的精华主题

TA的得分主题

 楼主| 发表于 2016-6-5 15:12 | 显示全部楼层
[广告] Excel易用宝 - 提升Excel的操作效率 · Excel / WPS表格插件       ★免费下载 ★       ★ 使用帮助
本帖最后由 hyefeifei 于 2016-6-6 11:06 编辑


  四、数组

  数组的情况又如何呢?

  程序如下:

  Sub Test3()
    Dim arr(2)
    ‘Debug.Print Hex(VarPtr(arr(0)))
    arr(0) = "大连"
    arr(1) = 900
    arr(2) = 800.5
    ‘Debug.Print Hex(StrPtr(arr(0)))
  End Sub

  1. 当程序执行到 Dim arr(2) 时,vba会在内存中划分出一块16*3 大小的空间,因为数组是Variant数据类型的,有3个元素,Variant类型占16个字节,所以此数组总共需要16*3大小的空间。

  见图:


   a.png

  2. 这个空间的第一个地址(也即此数组的指针)是0DF2E8E8,可以用varptr(arr(0))得到,可想而知,这个空间要被数组第一个元素,第二元素…依次填充,所以第一个元素的首位地址即是此空间的首位地址。

  3. 当程序执行完

    arr(0) = "大连"

    arr(1) = 900

    arr(2) = 800.5

  会发生什么呢?我们看下图做一说明

   b.png

  1. 先看arr(0),头2个字节存储的是数据类型,当为0008时,意味着数据类型为字符串,(各位可以查看一下vartype函数的返回值,08为字符串02为整型05为双精度)从第9位开始,数4个字节,内容为:0DF210C4,这即是内存中真正存放字符串空间的首位地址。我们转到这个地址看一下,见图:

   c.png

  一处为字符串的长度,二处为字符串本身,3处为空字符,表示字符串结束。

  你可以在立即窗口输入:

  ?hex(ascb(midb("大",1)))

  ?hex(ascb(midb("大",2)))

  得到27 59

  输入:

  ?hex(ascb(midb("连",1)))

  ?hex(ascb(midb("连",2)))

  得到 DE 8F

  可知圈2处即为“大连”。

  2. 再看arr(1),头两个字节为00 02,这表示数据类型为Integer,这个类型占2个字节,我们从第9个字节往后数2个字节,发现是: 03 84 转成十进制即是900,但第11 12 位被填充了B3 0C,我也不明白是为什么,有知道的朋友麻烦告知一下,好在这不影响程序取数据的正确,因为已经知道是Integer类型的了,所以只取2个字节,后面的字节忽略了。

  3. 最后看一下arr(2),这2个字节为 00 05, 表示数据类型为双精度,双度精占8个字节,我们从第9位往后数8个字节,为:4089040000000000,此16进制数转成十进制,即为800.5,进制转换各位可以搜一下百度。

  当我们把数组声明为dim arr()时,又如何呢?其实,这种情况与前面的情况是一样的,只不过vba把分配内存空间的时刻,移到了执行redim arr(2)的时刻,你早晚都得执行这句,不是吗。

  可见,当数组中某个元素为string类型时,vba会在预留的空间中存放一个指针(四字节),这个指针指向真正存放字符串空间的首地址,它可以用strptr()函数得到;当元素为字节型、整形,单精度,双精度,布尔,货币数据类型时,直接存放元素的内容。

  待续…

评分

9

查看全部评分

TA的精华主题

TA的得分主题

发表于 2016-6-6 10:17 | 显示全部楼层
没学过C 语言,不能真正理解内存与指针,想吃透copymemory 真是不易。

TA的精华主题

TA的得分主题

 楼主| 发表于 2016-6-6 10:24 | 显示全部楼层
本帖最后由 hyefeifei 于 2016-6-6 10:47 编辑


  五、数组II

  请看下面代码:

  1.   Sub Test4()
  2.     Dim arr(1 To 3, 1 To 2) As String
  3.     Debug.Print Hex(VarPtr(arr(1, 1)))
  4.     arr(1, 1) = "a"
  5.     arr(2, 1) = "b"
  6.     arr(3, 1) = "c"
  7.     arr(1, 2) = "d"
  8.     arr(2, 2) = "e"
  9.     arr(3, 2) = "f"
  10.     Debug.Print Hex(StrPtr(arr(3, 2)))
  11.   End Sub
复制代码

  前面我们说过,当我们把数组声明为可变类型时,vba会自动判断每个元素的数据类型,如果是字符串类型的,则在四个字节的空间中存放一个指针(即是实际字符串存放位置的首地址),如果是整形,单精度等,则根据其长度,直接在相应位置存放其内容。

  我们如果把数组声明为string时,会如何呢?

  当我们执行Dim arr(1 To 3, 1 To 2) As String时,会发生什么?

  因为此数组共3*2=6个元素,而已经明确是字符串型的,所以每个元素需要4个字节的空间,存放指针,所以VBA会预先在内存中划分一块4*6=24字节的空间。见下图:

   a2.png

  当我们执行完:

  1.     arr(1, 1) = "a"
  2.     arr(2, 1) = "b"
  3.     arr(3, 1) = "c"
  4.     arr(1, 2) = "d"
  5.     arr(2, 2) = "e"
  6.     arr(3, 2) = "f"
复制代码

  时,此块预先分配的内存地址已经被填充,截图如下:

   a3.png
 
  6个红框的内容分别是指向真正存放字符串地址的指针。
 
  下面我们再来看看多维数组是按什么顺序在内存中存储的,执行下面的程序:
  
  1.   Sub Test5()
  2.     Dim arr(1 To 3, 1 To 2) As Integer
  3.     Debug.Print Hex(VarPtr(arr(1, 1)))
  4.     arr(1, 1) = 1
  5.     arr(2, 1) = 3
  6.     arr(3, 1) = 5
  7.     arr(1, 2) = 7
  8.     arr(2, 2) = 8
  9.     arr(3, 2) = 9
  10.   End Sub
复制代码

  执行完程序的内存截图:

   a1.png

  大家看6个红框,按顺序是1 3 5 7 8 9,可见多维数组是按列的顺序填充内存的,这一点很重要,不明白这一点,就不能正确复制数组。

  另外,说个题外的,当你用redim 重新定义数组大小时,程序会在另一个区域新辟一块内存,供数组使用,原来数组所在的内存会被释放,而如果你要保留原来的数据(redim Preserve),程序会把原来的数据复制到新区域中。尽管这个过程非常之快,但如果redim preserve 处于一个很大的循环中时,还是会托慢程序。如何处理,因无关这个题目就不多说了。

  待续...

评分

3

查看全部评分

您需要登录后才可以回帖 登录 | 免费注册

本版积分规则

手机版|关于我们|联系我们|ExcelHome

GMT+8, 2024-11-15 14:24 , Processed in 0.058411 second(s), 16 queries , Gzip On, MemCache On.

Powered by Discuz! X3.4

© 1999-2023 Wooffice Inc.

沪公网安备 31011702000001号 沪ICP备11019229号-2

本论坛言论纯属发表者个人意见,任何违反国家相关法律的言论,本站将协助国家相关部门追究发言者责任!     本站特聘法律顾问:李志群律师

快速回复 返回顶部 返回列表