Written by rdc
指针数据类型在FreeBasic数字数据类型中是唯一的。与其他数字类型一样,不包含数据,指针包含数据的内存地址。
在32位系统上,指针数据类型为4字节。FreeBasic为诸如ImageCreate之类的多个函数使用指针,并且指针在Windows API等外部库中大量使用。指针也很快,因为编译器可以直接访问指针指向的内存位置。指针的正确理解对于FreeBasic中的有效编程至关重要。
对于许多开始的程序员来说,指针看起来像一个奇怪而神秘的野兽。然而,如果你记住一个规则,你不应该在程序中使用指针有任何问题。规则非常简单:指针包含一个地址,而不是数据。如果你记住这个简单的规则,你应该没有使用指针的问题。
您可以将计算机中的内存视为一组邮政信箱(P.O.盒)在您当地的邮局。当你去租一个P.O.盒子里,店员会给你一个数字,比如100。这是你的P.O.的地址。框。你决定把这个号码写在一张纸上,把它放在你的钱包里。第二天你去邮局,拿出一张纸。你找到框100,看看框内,找到一堆垃圾邮件。当然,你想要折腾垃圾邮件,但是没有一个垃圾桶可以方便,所以你决定把邮件放回箱子里,然后把它丢弃。在FreeBasic中使用指针非常类似于使用P.O.框。
当你宣布一个指针时,它并没有指向任何类似于纸张空白的东西。为了使用指针,它必须被初始化为一个内存地址,这与写在纸上的数字100相同。一旦你有地址,找到正确的P.O.框中,您可以取消引用指针,打开邮箱,从指向内存位置添加或检索数据。您可以看到使用指针有三个基本步骤。
声明一个指针变量。
初始化指向内存地址的指针。
取消引用指针来操纵指向内存位置的数据。
这与使用标准变量没有什么不同,你使用的指针和标准变量的方式大致相同。两者之间唯一的真正区别在于,在标准变量中,您可以直接访问数据,并使用指针,您必须取消引用指针以与数据交互。
FreeBasic有两种类型的指针,键入和非类型。一个类型化的指针被声明为一个asscoaited数据类型。
Dim myPointer As Integer Ptr
这告诉编译器这个指针将被用于整数数据。使用类型化的指针可以让编译器进行类型检查,以确保您没有使用指针的错误类型的数据,并简化指针算术。
使用Any关键字声明非指针。
Dim myPointer As Any Ptr
未指针的指针没有类型检查,并且默认为字节大小。C类运行库和许多第三方库(如Win32 API)中使用了非指针,以适应C中的void指针类型。除非你特别需要一个无类型的指针,否则应该使用类型化的指针,以便编译器可以检查指针赋值。
FreeBasic具有以下
指针运算符.
您将注意到,addressof运算符不仅返回变量的内存地址,还可以返回子例程或函数的地址。您将使用子例程或函数的地址来创建回调函数,如CRT QSort函数中使用的。
FreeBasic还有一些与指针一起使用的
记忆功能.
这些功能对于创建许多动态结构(如链接列表,破损或动态数组以及与第三方库一起使用的缓冲区)很有用。
使用Allocate功能时,您必须使用方程式number_of_elements * Sizeof(datatype)根据数据类型指定存储大小。要为10个整数赋值空间,您的代码将如下所示:myPointer = Allocate(10 * Sizeof(Integer))。一个整数是4个字节,所以赋值10个整数将留出40个字节的内存。赋值不会清除内存段,因此段中的任何数据将是随机的,无意义的数据,直到它被初始化。
Callocate以相同的方式工作,但计算在内部完成。使用Callocate代码赋值相同的10个整数将如下所示:myPointer = Callocate(10,Sizeof(Integer))。与赋值不同,Callocate将清除内存段。
重新赋值将改变现有内存段的大小,使其根据需要变得更大或更小。如果新分段大于现有分段,则现有分段中的数据将被保留。如果新分段小于现有分段,则现有分段中的数据将被截断。重新赋值不会清除添加的内存或更改任何现有数据。
如果成功,所有这些功能将返回内存地址。如果函数无法赋值内存段,则返回NULL指针(0)。每次使用这些功能时,应该检查返回值,以确保内存段已成功创建。尝试使用不良指针会导致不良行为或系统崩溃。
| 没有确定赋值大小的内在方法。你必须自己跟踪这些信息。 |
| 注意不要使用相同的指针变量赋值两个或多个内存段。重新使用指针而不首先取消赋值它指向的段将导致内存段丢失,导致内存泄漏。 |
当您使用赋值函数创建内存段时,您将需要一种方法来访问段中包含的数据。在FreeBasic中有两种访问段中数据的方法:使用带指针算术的间接运算符和指针索引。
指针算术,顾名思义,将值添加到减去内存段内访问单个元素的指针。当您创建一个类型化的指针,如Dim myPointer作为Integer ptr时,编译器知道与该指针一起使用的数据的大小为整数或4字节。指针在初始化时指向段的第一个元素。您可以将其表示为*(myPtr + 0)。要访问第二个元素,需要向指针添加1,可以表示为*(myPtr + 1)。
由于编译器知道指针是整数指针,所以向指针引用中加1将实际上将myPtr中包含的地址增加4,即整数的大小。这就是为什么使用类型的指针比非类型指针更可取。编译器为访问内存段中的数据做了大量工作。
请注意,构造是*(myPtr + 1)而不是* myPtr + 1。*运算符的优先级高于+,所以* myPtr + 1实际上会增加myPtr指向的内容,而不是指针地址。
将首先对myPtr进行评估,返回内存位置的内容,然后对+1进行评估,向内存位置添加1。通过在括号内包装myPtr + 1,您强制编译器首先评估myPtr + 1,这会增加指针地址,然后应用*来返回新地址的内容。
指针索引的工作方式与指针算术相同,但详细信息由编译器处理。*(myPtr + 1)相当于myPtr [1].再次,由于编译器知道myPtr是一个整数指针,所以它可以计算正确的内存偏移量,以使用索引返回正确的值。你使用哪种格式,但是索引方法类似于标准数组访问方法,而且视觉上比间接操作符更容易理解。
Freebasic有一组指针函数来补充指针运算符。
CPtr将表达式转换为data_type指针。表达式可以是另一个指针或整数。
窥视返回指针的内存位置指针的内容。Data_type指定预期数据的类型。
戳将表达式的值放入指针指向的内存位置。data_type指定要放入内存位置的数据类型。
SAdd返回动态字符串中字符串数据所在的内存中的位置。
StrPtr与Sadd相同。
ProcPtr返回函数的地址。这与运算符@的地址相同。
VarPtr此函数的工作方式与address @的地址相同。
Sadd和Strptr函数使用字符串数据类型返回字符串数据的地址。Peek和Poke功能已被添加为支持旧代码的目的。Procptr和Varptr都像运算符@的地址一样工作,但Proptr仅适用于子程序和函数,而Varptr仅适用于变量。Cptr对于将无类型的指针转??换为类型化指针(例如来自第三方库的返回值)非常有用。
子程序和函数(如变量)驻留在内存中,并具有与其入口点相关联的地址。您可以使用这些地址在程序中创建事件,创建伪对象并在回调函数中使用。您创建一个子或函数指针,就像任何其他指针一样,除了将变量声明为指向子例程或函数的指针,而不是指向数据类型的指针。
在使用函数指针之前,必须使用Procptr或@将其初始化为子例程或函数的地址。一旦初始化,您将以与调用原始子例程或函数相同的方式使用指针。
您使用匿名声明语法声明函数指针。
Dim FuncPtr As Function(x As Integer, y As Integer) As Integer
然后,您需要使用代码中的实际子例程或函数来对该函数指针进行编译。
Function Power(number As Integer, pwr As Integer) As Integer
Return number^pwr
End Function
FuncPtr = @Power
然后可以调用函数指针,就像调用真正的函数一样。
FuncPtr(2, 4)
虽然这可能不是有用的乍一看,您可以使用这种技术来实现多态函数,其中单个变量实例可以指向几个不同的子例程或函数之一。
例如,假设你有一只狗和猫的对象。两个对象都需要一个Speak方法。通过将Speak定义为函数指针,并将Speak与狗的Bark子例程和猫的Meow子程序相关联,您可以使Speak发出“Woof!”。或“喵!”取决于对象类型。
函数指针的主要用途之一是创建回调函数。回调函数是您在程序中创建的函数,由另一个函数或子例程在您自己的代码空间或外部库中调用。Windows使用回调函数通过窗口对象枚举,如字体,打印机和表单。
C运行时库中包含的qsort函数使用回调函数对数组的元素进行排序,以确定排序顺序。qsort函数的原型包含在stdlib.bi中:
Declare Sub qsort cdecl Alias "qsort" (ByVal As Any Ptr, ByVal As size_t, ByVal As size_t, ByVal As Function cdecl(ByVal As Any Ptr, ByVal As Any Ptr) As Integer)
以下列出了qsort子程序的参数信息。
第一个参数是数组的第一个元素的地址。将此信息传递给qsort的最简单的方法是将运算符的地址附加到第一个元素索引:@myArray(0)。
第二个参数是数组中的元素数,即数组数。
第三个参数是每个元素的大小(以字节为单位)。对于整数数组,元素大小将为4个字节。
第四个参数是指向用户创建的比较函数的函数指针。该函数必须使用Cdecl传递模型来声明,如此参数所示。
使用此信息,您可以看到qsort的工作原理。通过传递第一个元素的地址以及元素的数量和每个元素的大小,qsort可以使用指针算术遍历数组。
Qsort将使用两个数组元素,将它们传递给用户定义的比较函数,并使用compare函数的返回值对数组元素进行排序。它反复执行,直到每个数组元素按照排序顺序排列。
您需要将函数原型声明为Cdecl,以确保以正确的顺序传递参数。
Declare Function QCompare cdecl (ByVal e1 As Any Ptr, ByVal e2 As Any Ptr) As Integer
然后,您将定义如下功能。
'The qsort function expects three numbers
'from the compare function:
'-1: if e1 is less than e2
'0: if e1 is equal to e2
'1: if e1 is greater than e2
Function QCompare cdecl (ByVal e1 As Any Ptr, _
ByVal e2 As Any Ptr) As Integer
Dim As Integer el1, el2
Static cnt As Integer
'Get the call count and items passed
cnt += 1
'Get the values, must cast to integer ptr
el1 = *(CPtr(Integer Ptr, e1))
el2 = *(CPtr(Integer Ptr, e2))
Print "Qsort called";cnt;" time(s) with";el1;" and";el2;"."
'Compare the values
If el1 < el2 Then
Return -1
ElseIf el1 > el2 Then
Return 1
Else
Return 0
End If
End Function
然后,您将调用通过回调函数的地址的QSort函数。
qsort @myArray(0), 10, SizeOf(Integer), @QCompare
在FreeBasic中,您可以创建指向任何受支持的数据类型的指针,包括指针数据类型。指向指针的指针在您需要返回指向函数的指针或创建特殊数据结构(如链表和粗糙数组)的情况下很有用。指向指针的指针称为多级间接。
指针指针的一个应用是创建一个像数组一样行为的内存段。例如,假设您要创建一个内存段来保存未知数量的整数。您可以创建一个动态内存段,您可以根据需要在运行时调整大小,以处理所需的整数。您将从创建指针到指针变量开始。
Dim myMemArray As Integer Ptr Ptr
然后,您可以使用Allocate或Callocate来初始化指针引用。
'Create 10 rows of integer pointers
myMemArray = CAllocate(10, SizeOf(Integer Ptr))
请注意,该变量初始化为
Integer Ptr ,因为此列表将指向另一个列表;这是指向另一个指针的指针。然后,您可以初始化刚创建的指向所需内存段的单个指针引用。
'Add 10 columns of integers to each row
For i = 0 To 9
myMemArray[i] = CAllocate(10, SizeOf(Integer))
Next
在这个代码片段中,列表中的各个指针被初始化为包含实际整数数据的10个内存段。
'Add some data to the memory segment
For i = 0 To 9
For j = 0 To 9
myMemArray[i][j] = Int(Rnd * 10)
Next
Next
该代码片段使用索引方法将实际数据加载到内存段中。请注意,这看起来和行为就像一个二维数组。虽然这在上下文中可能看起来不太有用,但您可以使用此代码在类型定义中创建动态数组。由于您不能在类型中具有标准动态数组,因此您可以获得相同的功能。
你需要注意的一件事是如何解除这样的结构。规则只是做相反的配置操作。由于最后一次赋值操作初始化了数据存储器段,因此首先释放这些内存段,然后可以释放基本指针。
'Free memory segment
For i = 0 To 9
Deallocate myMemArray[i]
Next
'Free the pointer to pointer
Deallocate myMemArray
您需要确保以正确的顺序解除赋值,否则您将最终得到不被释放但无法访问的内存段。这些是内存泄漏,可能会导致程序中的一大堆问题。