分类: C/C++
2015-06-04 19:49:26
人们最难意会的其中一件事情就是指针,尤其在C语言中。如果你想要有效率地进行编程,那你就必须彻底地知道如何使用指针,一旦理解了指针你就理解了C语言。在C语言中,对象(比如整型int x)储存在内存中,它们存放的位置称作地址。计算机拥有足够的能力识别保存你的银行帐户余额的和你玩WOW的天数的内存,它是通过把值存放在不同的地址来完成这种区分。
这里先让我们花点时间来哲学性地探讨一下“什么是地址?”考虑你的家庭住址 123 Main St(这是一个假地址,它要是与你家的地址雷同的话,那纯属巧合。如果确实是你的真实地址,那么好吧,我知道你住在哪里了)
我问你住在哪里?你说 123 Main St。我就知道了能如何找到你的房子,但事实上我并不在你的房子里。我知道的仅仅是它在什么地方,你并没有把你的房子给我,你只是把房子的指针给了我,你给我的是房子的地址。
这与你说int x与x的指针的情况是一样的。你并不拥有x,你获得的只是x的指针,你拥有的是x的地址。有了这个指针你就可以使用x。就是说你拥有一个对象的指针,那你就可以对它进行读或写。
这样的主要好处就是如果你把一个指针传递给某个函数,这个函数就可以在内部操作指针所指向的对象。
当你在C语言中调用函数时,所有函数的实参会被复制到函数形参列表中的形参变量。因为这些形参是函数的局部变量,修改它们只会影响到函数内部的局部拷贝,不能影响到它的调用者的原始实参。
为了绕开这个限制,你可以把变量的指针作为函数的实参进行传递。这并不是传递变量本身,而是传递变量的指针。函数通过变量的指针来对这个变量进行操作。
从现在开始我们将会呈现一大串C语言语法。很遗憾,由于在设计C语言时所做的选择导致在"*"的用法上产生了些混淆。但也不算太坏,只要你理解了它,就能掌握它。
我们可以做以下两件事情:
1.从对象到对象的指针。当拥有一个对象,我们需要得到这个对象的指针。
2.相反的过程:从对象的指针到对象本身。当拥一个对象指针时,我们需要得到这个对象以便能操作它。
先来处理第一种情况。先声明一个变量,然后打印出它的指针。记住,指针是对象的地址。为了取得对象的地址,我们要使用取址符&。
#include
int main(void)
{
int x = 10;
printf("%p\n", &x); // address-of x, aka "a pointer to x"
return 0;
}
在我的机器上运行以上代码时,它显示:"0x7fffd944f17c",它表示x的地址。对你来说可能会是别的数字,实际上每次运行时的值都不一样。准确的数字并不重要。
现在有趣的事情马上就要开始了。我们将把x的地址存入到一个变量中(稍后它会被传递给一个函数)。但问题是,这个变量的类型会是什么呢?其它一些东西如12,它是一个int型;而34.9当然也能够存放到一个float型变量中。如果有"int x",那么x的类型是int。但是x的指针,它的类型又是什么?
事实是它的类型是"指向int的指针",或"int指针"。确实有这样一种类型它的存在的目的就是用来指向其它类型。
如何声明这样的类型呢?很简单,在变量名之前类型之后放置一个*:
{
int x; // declare an int called "x"
int *y; // declare a pointer to an int (or "int-pointer"), called "y"
...
在上面的例子中,变量x未初始化。它是一个整型,但由于未被赋值,它也可能是任何值。同样,变量y也未初始化。它是一个int指针,但由于未被赋值,它可能指向任何地方。(就像是一张未写上地址的邮寄地址标签)
所以应该给它们赋上值,为了完整性和乐趣。下面是比较稳健的写法:
{
int x; // declare an int called "x"
int *y; // declare a pointer to an int (or "int-pointer"), called "y"
x = 3490; // assign 3490 to x
y = &x; // assign "the address of x" to y
// at this point "y points to x".
...
正如你所看到的,我们使用取址符来获得x的地址,并且把它存放在指针类型变量y中,y现在指向x。
到目前为止,我们完成了列表中的第一个目标:通过使用&符号实现了从对象到对象的指针。
第二,通过一个已有的指向对象的指针,我们可以得到这个对象以便能够处理它吗?有个神奇的操作叫作解指针。它表示,我所说的不是指针而是指针所指向的东西。
在C语言中使用间接操作符*表示这样的操作。不错,又是这个星号。但是我们并不是把它用在变量声明中,所以使用环境是不一样的。(在变量声明中,星号表示你将声明的是一个指针,而在用在表达式中时,除了表示乘法外则意味着解指针操作。)
看下面的代码:
{
int x; // declare an int called "x"
int *y; // declare a pointer to an int (or "int-pointer"), called "y"
int z; // declare another int called "z"
x = 3490; // assign 3490 to x
y = &x; // assign "the address of x" to y
// at this point "y points to x".
// here comes the dereference, which says "z is assigned the value
// of the thing y is pointing at" (namely 3490 in this case):
z = *y;
printf("%d\n", z); // prints "3490"
...
你会看到如何把一个与变量x相同的值赋给变量z。我们并不直接对z赋值,而是通过指向x的指针变量y来间接完成。
不仅可以通过指针y来读取x的值,实际上还能够通过指针y来对x进行间接赋值。如下所示:
int x;
int *y;
x = 3490; // x holds 3490
y = &x; // y points to x
printf("%d\n", x); // prints "3490"
*y = 3491; // assign 3491 into "x" via the pointer "y"
printf("%d\n", x); // prints "3491"
在上面的赋值语句"*y = 3491"中,我们说“y指向的东西被赋给值3491”。这就是在赋值语句后x的值改变的原因。
我们创建一个函数通过x的指针在远处对x的值进行操作。
void make24more(int *a)
{
*a += 24;
}
int main(void)
{
int x; // declare an int called "x"
int *y; // declare a pointer to an int (or "int-pointer"), called "y"
x = 3490; // assign 3490 to x
y = &x; // assign "the address of x" to y
printf("%d\n", x); // prints "3490"
make24more(y); // add 24 to whatever y points at, namely x
printf("%d\n", x); // prints "3514" (3490+24)
make24more(&x); // add 24 to whatever is at the address of x, namely x
printf("%d\n", x); // prints "3538" (3490+24+24)
...
看到它是如何工作的了吗?当你把变量的指针传递给函数make24more(),函数进行解指针后把值24添加到指针所指向的任何东西。
你会注意到我们以两种不同的方式调用make24more():传递指针类型的变量y以及传递x的地址&x。这两种方法都是完全可接受的。
当我们调用函数make24more()后会发生什么事情呢?那就是一个指针的复本会被创建,在这个例子中就是函数内部的局部变量a。函数并不关心形参只是指针的一个复本,因为它不对指针进行操作;通过使用*操作符,它所处理的是指针指向的东西。x的指针保存在指向x的指针变量y中,而x的指针的副本保存在形参变量a里,它也指向x。对其中任何一个解指针你都会得到x。不管你是得到我家的地址还是我家地址的副本,这都不重要,它们两个都可以让找到我的家。
在实际使用上这是很强大的。如果你拥有了一个对象的指针,你可以把它分发传递给不同的函数,这些函数都能够操作指针所指向的对象。实际上可以这样说,我不拥有对象,但我知道它在哪里,因为我有这个对象的指针,而且通过解指针我就能够改变它的值。
C语言中使用符号“NULL”来表示那些不指向任何对象的指针。
int *p; // p is uninitialized at this point
p = NULL; // now p points to nothing, explicitly
它可以用来作为指针数组结尾标记有时也作为标记错误条件,malloc()分配内存失败时会返回NULL通知你。
char *p;
p = malloc(1024); // allocate 1K
if (p == NULL) {
printf("Memory allocation failed!\n");
} else {
// go ahead and use the pointer:
strcpy(p, "Goats detected!");
}
C语言中的指针可以指向变量,数组中的元素,列表或树的结点,显式内存地址甚至函数。