看到指针这两个字,大家很多想到的都是C语言。但是今天指针被加了引号,所以今天不是讲C或者C++;然而除了这两门语言外,其他的高级语言中是没有指针这个概念,其实我们今天涉及的“指针”更类似于C++中的引用。C可能是很多同学在大学中学到的第一门语言,用指针这个概念,可能大家更好理解一些,可不要弄混奥。
1.最隐蔽的“指针”
java是没有指针的,但是我们可能没有注意到如果给一个函数传递一个对象类型的参数的时候,在函数内部给这个对象修改其属性的时候会发生什么情况呢?如果你按照java中参数传递是按值传递的固定思维的话,肯定会认为,这个函数内部对这个对象的属性做得都是无用功。上代码来验证:
public class MyTest {
public static void change(byte[] array) {
if (array == null) {
array = new byte[]{5};
} else {
array[0] = 5;
}
}
public static void main(String argc[]) {
byte[] array = new byte[]{1,2,3};
change(array);
System.out.println(array[0]);
}
}
代码片段1
代码中会输出5,如果我们死扣java参数按值传递的话,就会感觉很不可思议。其实在java中如果是简单数据类项的话,比如int boolean long byte等类型的话是按值传递的;但是如果是object类型的话,其实也是按值传递的,但是这个值是个是object的引用值,这里我们就可以理解为“地址”。再回到代码中,byte[]是一个object类型,通过change函数中参数array引用的“地址”上的byte[]上的第一个元素被修改为5。.
2.最神似的“指针”
perl有一个引用的操作。感觉跟C中的指针很神似,声明一个变量A作为另一个变量B的引用后,改变变量B的值后,我们通过“解引用”A,发现它所指向的内容也改变了。类似的C代码很普通:
#include <stdio.h>
#include <string.h>
typedef struct testStruct {
char name[20];
int age;
int sex;
}Test;
int main() {
Test myTest;
Test *testp = &myTest;
strcpy(myTest.name,"tom");
printf("%s/n",testp->name);
return 0;
}
代码片段2
这段代码输出的肯定是“tom”,这个显而易见。下面来看一段perl的代码:
sub getOneRecord($) {
my $recordString = shift;
my %recordHash = ();
my $recordHashRef = /%recordHash;
my @record = split(/,/,$recordString);
$recordHash{'name'} = $record[0];
$recordHash{'age'} = $record[1];
$recordHash{'sex'} = $record[2];
return $recordHashRef;
}
open(RECORD,"pointer_files/records.txt");#打开文件
my @records = ();
my $i = 0;
foreach my $line (<RECORD>) {
$records[$i++] = getOneRecord($line);
}
foreach my $record (@records) {
#print ${$record}[0]."/n";
print $record->{'name'}."/n";
}
close RECORD;#关闭文件
代码片段3
这里重点关注getOneRecord这个函数,代码第4行使用了引用,使变量$recordHashRef“指向”%recordHash,然后改变了%recordHash的值,然后最终我们通过“解引用”输出$recordHashRef后(第24行),发现它所指向的内容却是发生了变化。是不是跟C的代码有相似之处呢?
3最形似的“指针”
和perl类似php中也有引用,不过人家的引用符号,干脆就跟C的取地址用的是一个——“&”。原理跟perl类似,我就不罗嗦了,直接上代码:
<?php
function getRandArray(&$oldArray,&$newArray) {
if (count($oldArray) == 0) {
return;
}
$randKey = array_rand($oldArray);
$newArray[$randKey] = $oldArray[$randKey];
unset($oldArray[$randKey]);
if (count($oldArray) > 0) {
getRandArray($oldArray,$newArray);
}
}
function microtime_float()
{
list($usec, $sec) = explode(" ", microtime());
return ((float)$usec + (float)$sec);
}
$beginTime = microtime_float();
$oldArray = array(
'key1' => 1,
'key2' => 2,
'key3' => 3,
'key4' => 4,
'key5' => 5
);
$newArray = array();
getRandArray($oldArray,$newArray);
$lastTime = microtime_float() - $beginTime;
print_r($newArray);
echo '<br />cacluted in '.$lastTime.' seconds.';
代码片段4
注意代码段中的第一行,getRandArray(&$oldArray,&$newArray),熟悉C++的同学可能一下就有似曾相识的感觉。我在getRandArray函数内部使用了递归,但是由于函数的参数声明为引用方式,所以在整个getRandArray函数执行过程,$oldArray和$newArray看上去更像是“全局”的,因为在这个代码块中,它们的值是时刻变化的。大家可能还注意到我还特意计算了一下代码的运行时间,因为既然是使$oldArray和$newArray实现“全局化”的功能,我们也可以用面向对象的思想来实现,只需把$oldArray和$newArray当成类的两个成员属性即可,这样不管怎么使用递归,在成员属性始终是成员方法的“全局变量”。不过后来想想这和我们今天的话题不符,就略去不谈了。
再深入探究一下,$a = $b;这句话在php的C底层代码的操作是分别为变量@a和变量@b分配两个不同的地址*a和*b;而$a = &$b;在C底层的实现是*a和*b是相同的,也就是说$a和$b在内存中指向相同的地址。
php还有一个很BT的操作,给函数取引用,类似下面这种形式:
function &test()
{
static $b=0;//申明一个静态变量,类似于C中的static
$b=$b+1;
echo $b; //输出测试结果
return $b;
}
$a=test();//这条语句会输出 $b的值 为1
$a=5;
$a=test();//这条语句会输出 $b的值 为2
$a=&test();//这条语句会输出 $b的值 为3
$a=5;
$a=test();//这条语句会输出 $b的值 为6
代码片段5
在调用test函数的时候,如果不加&,跟普通调用没有什么两样,执行$a = &test();时,实际上,$a和test函数内部的$b的地址是一样的,即*a==*b,所以第二次调用test后输出的是6。.
4最圆滑的“指针”
现在要讲一下javascript,对js中也有引用:
function MyData() {
this.key1 = '';
this.key2 = '';
}
var datas = {};
for (var i=0;i<3;i++)
{
var oneData = datas[i] = new MyData();
oneData.key1 = 'key1' + i;
oneData.key2 = 'key2' + i;
}
var strToShow = '';
for (var i=0;i<3;i++)
{
strToShow += ('the ' + i + 'th data now , its key1 is ' + datas[i].key1 + '; ');
}
alert(strToShow);
//the 0th data now , its key1 is key10; the 1th data now , its key1 is key11; the 2th data now , its key1 is key12;
代码片段6
js中的引用有点类似java(虽然java和javascript是雷锋和雷锋塔之间的关系),那就是对于对象可以使用引用,但是对于基本数据类型不能使用引用。var a=b=3;b=5;我们打印a的值,它肯定是3。
可是我的标题中有“圆滑”二字,如果这就算圆滑的,那也太小看js了。
function MyChangeTest() {
this.sign = 'change1',
this.showSign = function(obj) {
alertSign.call(obj);//改变了this指针
}
function alertSign() {
alert(this.sign);
}
}
function AnotherObj() {
this.sign = 'another';
}
new MyChangeTest().showSign(new AnotherObj());
代码片段7
js中有两个函数call和apply,可以改变this指针指向的对象,这里只将call函数。它的声明看上去是这样的:
call([thisObj[,arg1[, arg2[, [,.argN]]]]])
调用后可将一个函数的对象上下文(即this指针)从初始的上下文改变为由 thisObj 指定的新对象。
代码片段7中第6行使用了call方法,它的使用方法是 method.call([thisObj[,arg1[, arg2[, [,.argN]]]]]),thisObj也可以不写,这样thisObj就为window对象,剩下的都是method的各个参数。再看代码片段的最后一句话,本来在类(姑且这么说,js中是没有类的)MyChangeTest中this指针指向MyChangeTest生成的对象,但是使用call后,他的this指针指向了AnotherObj生成的对象。
好了,终于写完了,费了我一个周末的时间。感觉我还想再写篇大杂烩的文章,下次就叫形形色色的反射吧,不过有些语言很长时间没有用了,不知道能不能写出来了。