现在你知道了如何定义指针及其标量的用途,那么让我们实际尝试使用一下指针。假设程序中有以下三条语句:
int a;
int b=5;
a=b;
最后一行赋值语句实际上包含着比你想象得更多的内容,我们把它展开一下。该语句稍微简化了编译过程,其作用是:转到变量b的左值并复制在那里找到的右值(即5)。现在转到符号表并查找a的左值。转到a的左值并将b的右值复制到a的右值中。简单地说,大多数(非指针)赋值语句只是将一个变量的右值复制到另一个变量的右值中。
指针赋值操作则不是这样。
回想一下,指针应该只包含内存地址或null。这意味着指针变量不包含右值。任何有用的指针都必须包含内存地址。那么我们如何将内存地址分配给指针呢?这个使用内存地址的操作运算符——“&”,就是对指针变量地址使用的左值,而不是右值。假设程序中有以下代码片段:
int number=5;
int*ptrNumber;
让我们进一步假设number的左值为2200,ptrNumber的左值为2294(如图8-2所示)。你用右值5初始化了number,但没有初始化ptrNumber,因此此时程序中包含垃圾数据。现在,让我们添加另一条语句:
int number=5;
int*ptrNumber;
ptrNumber=&number;
运算符(&)引用变量地址的目的是告诉编译器:不要在这个赋值中进行标准的右值到右值赋值。相反,取数字(2200)的地址,并将其复制到ptrNumber的右值中。仔细阅读前一句话大约10次,思考一下它在说什么。
首先,ptrNumber现在有一个保留number的内存地址(左值)的右值。这正是ptrNumber应该包含的内容:名为number的int变量的内存地址或左值。这关系如图8-4所示。请注意,在指针赋值发生后运算符将number的左值复制到ptrNumber的右值中。也就是说,现在是ptrNumber“指向”数字。想想这意味着什么?因为ptrNumber现在知道number变量的内存地址,ptrNumber可以完全访问number的数据(即其右值)。如果ptrNumber提供正确的标量值(ptrNumber是一个int指针,现在指向int命名的数字),然后你可以使用ptrNumber来更改number的右值!
图8-4 指针变量和普通变量数据赋值
如果希望使用指针更改它指向的变量的右值,则使用间接运算符。语法是:
*variableID=expression1;
例如:
*ptrNumber=10;
间接运算符是星号,它与乘法运算符相同,但是属于一元操作符。编译器会根据这个式子做这样的执行:获取ptrNumber(2200)的右值,转到该值内存地址,将值10复制到该指针的类型说明符地址的int字节内存中的位置,就是ptrNumber的右值。它告诉编译器将数字10转换为int字节的数据(即2字节),然后将这些字节复制到内存地址2200中。语句完成后的结果是这个数字现在等于10。你已经使用指针变量“间接”更改了ptrNumber的值。
想象一下,如果将ptrNumber定义为char指针而不是整型指针。在这种情况下,使用间接运算符的赋值语句将值10转换为1字节的值,并将其分配到数字的内存地址中。数字的值为现在只能是“正确的一半”,因为数字的第二个字节将会是随机垃圾数据存储在那个地址单元里。教训很简单:就是不要把苹果和橘子混在一起。如果你愿意使用间接更改int,则必须使用带有int类型说明符的指针。否则,所有涉及此类情况时,注定是无效的。实际上,Arduino编译器在捕获这种类型的错误上做得相当好,发出一条错误消息告诉你无法将一种类型的指针转换为另一种类型的指针。
让我们编写一个简短的程序来演示指针的使用。源代码如清单8-1所示。代码清单 8-1.A Simple Pointer Program
(www.xing528.com)
清单8-1中的代码只是使用串行通信在PC端显示ptrNumber和number信息。图8-5显示了在电脑上运行程序时的程序输出。它显示ptrNumber存储在内存地址2292处,其右值为25344。25344碰巧存储在从内存地址2292开始的2个字节中的随机位。如果使用以下语法定义了ptrNumber:
int*ptrNumber=NULL;
那么ptrNumber的右值将显示为0。注意变量编号的“垃圾”右值8123。
图8-5 程序输出信息
现在,让我们向清单8-1所示的程序中添加两条新语句,然后重新运行它:
ptrNumber=&number;
*ptrNumber=10;
你应该将这些语句放在清单8-1中的注释处:
//===Put new statements here!
现在重新编译、上传并运行新版本的程序。图8-6显示了结果。请注意,number的右值现在显示为10,而不是5。原因是:两条新语句中的第一条使用运算符的地址初始化ptrNumber以指向number。接下来,你使用间接运算符将值10指定到*ptrNumber对应的地址。
图8-6 程序输出结果
也可以在赋值中使用指针变量。例如,在loop()函数顶部附近添加变量k的新数据定义:
int k;
现在,在刚才添加的最后两行之后立即添加以下新代码行,如下所示:
ptrNumber=&number;
*ptrNumber=10;
k=*ptrNumber;并添加一些输出信息代码,以便在新语句执行后可以看到k的值:
Serial.print("The rvalue for k is:");
Serial.println(k,DEC);
运行此版本的程序时,输出结果如图8-7所示。请注意,代码使用间接方式将值10赋给变量k。与前面一样,间接运算符(*)指示代码转到ptrNumber(number的左值)所指向的地址,获取int字节数据(即,保存值10的2个字节),并将这2个字节复制到k的右值中。因此,number和k的右值相同。
图8-7 输出结果
代码清单8-1中的程序在执行10次循环后退出。由于串行通信的方式,调用Serial.-flush()向PC端返回所有产生的数据。Serial.print()调用实际上是将数据写入内存的一个小缓冲区。当缓冲区填满时,缓冲区中的数据通过串行数据链路发送(即“刷新”)到PC。这比逐字节发送数据更有效。然而,当我们结束程序的时候,有相当部分缓冲区可能仍保存一些尚未发送到PC的信息。调用Serial.flush()可以确保所有数据都已发送。
免责声明:以上内容源自网络,版权归原作者所有,如有侵犯您的原创版权请告知,我们将尽快删除相关内容。