发布
2011年11月12日 (最后更新:2011年11月12日)

指针

评分:4.2/5 (437 票)
*****
你熟悉 int 吗? int 是一个对象,它占用一定的内存空间(通常是 4 字节,有时是 8 字节),并存储一个数值。

指针看起来也一样。它占用与 int 相同的内存空间,并存储一个数值。所有指针看起来都一样,无论是指向 int 的指针还是指向 double 的指针,或是其他类型的指针。它们占用相同的空间,并存储一个数值。

到目前为止,你明白了吗?

现在,你知道如何创建 int 了吗?像这样

 
int a;


你创建指针的方式完全一样;

1
2
int* a; // This create a pointer to an int
double* b; // This creates a pointer to a double  


就像任何对象一样,在使用指针之前必须先创建它。

你可以使用这种语法创建指向任何你能想到的对象的指针,但别被愚弄了——所有指针都占用 4 (有时是 8) 字节,并存储一个数值。

你知道,如果你创建一个 int 而不给它赋值,它就会包含一些垃圾值吗?只是当时内存中碰巧有的任何值。

 
int b; // This int could have any value at all  


指针也是一样

 
int* b; // This int pointer could have any value at all  


当然,给事物命名的相同规则也适用,所以这个

1
2
int a;
int* a;


将无法编译,因为我有两个同名的对象。

到目前为止,很简单,对吧?

指针有一个特殊的功能。你还记得指针存储一个数值吗?我可以对指针执行一个称为“解引用”的操作,这仅仅意味着我返回存储在那个数值所指向的内存位置的那个对象。所以,如果我的指针存储的值是 0x22334455(我这里使用了十六进制,但它只是一个数字),当我解引用指针时,我将获得内存位置 0x22334455 处的任何内容。我们有时说指针指向内存位置 0x22334455 中的对象。

我们如何知道要将什么数值放入指针(或者换句话说,我们如何知道对象的内存地址,以便我们可以将该内存地址存储在指针中)?幸运的是,存在一个运算符 "&",它将返回任何对象的地址(即该对象在内存中的位置的数值)。

1
2
int a; // an integer
&a; // the address of that integer 


所以,这是我们如何使用它

1
2
int* p; // a new pointer, with some garbage value - could be pointing anywhere
p = &a; // now we've stored the address of a in the pointer, so now the pointer is pointing at the object a  


在某些情况下,你可能需要手动输入地址位置,你可以这样做。假设你知道某个感兴趣的值确实在地址 0x2323FFFF。你可以手动设置它,我们稍后会讲到。

如果所有指针的大小都相同并且存储一个数值,那么为什么我们有 int* 和 double* 和 string* 以及其他所有类型?因为当我们解引用它们时,我们需要知道我们会得到什么类型的对象。仅此而已。如果我们想要麻烦一点,我们可以创建一个 int*,然后强制它指向一个 double,因为指针只存储一个数字——它不存储任何关于它指向的内容类型的信息。不过,这有点高级。

我们使用 "*" 来执行这个解引用操作。所以,如果我有一个名为 MrPointer 的指针,并且我想获取它指向的内容,我会这样做

 
*MrPointer;


假设它指向一个 int。我可以这样做

 
int someInt = *MrPointer;


或者我也可以在表达式中使用它

 
int anotherInt = 4 + *MrPointer;


我将详细说明。
1
2
3
4
5
6
7
8
9
10
11
12
int a = 7; // Make an int, give it the value 7
int* MrPointer; // Make a pointer. Don't give it a value - it's got some garbage value
            //      if I try to use it, I deserve everything I get!

MrPointer = &a; // Now, MrPointer contains the memory address of the int a

*MrPointer; // This means "get me the int at the memory address you're pointing to" - we know the value
          //    of that int will be 7. I'm not doing anything with it, so this is a bit of a waste of code!

int b = *MrPointer; // Now I've made a new int, and set it to the value of whatever MrPointer points at. 
                   //    We know MrPointer holds the memory address of a, so the value returned is 7,
                   //     so the value of b will be 7.  


那么,接下来是指针算术。

你对普通算术很熟悉吗?让我们用 int 来回顾一下。

1
2
3
int a = 7;
a++; // Now, a holds the value 8
a = a+4; // Now, a holds the value 12  


很简单。指针呢?首先,后退一步,回想一下指针的意义。它们的目的是存储内存地址,而内存地址是内存中某个对象的地址。所以 b 将有一个值,比如 0x22334455。

 
int* b = &a; // the pointer b holds the memory address of our int a, from above  


所以 b 将有一个值,比如 0x22334455。

指针算术就是对指针进行加(和减)值的算术。回想一下,指针存储一个内存地址,所以指针算术就是对内存地址进行加(和减)值的算术。如果我们给指针加 1,我们不是说“将 1 加到你正在持有的内存地址”。我们的意思是“前进足够的内存,以便指向内存中的下一个对象”。

int 通常是 4 字节。如果 b 的地址是 0x22334455,那么 int 将占用字节 0x22334455、0x22334456、0x22334457 和 0x22334458(在某些系统上,它将是 0x22334455、0x22334454、0x22334453 和 0x22334452——哪个都无所谓,因为你的编译器会为你跟踪所有这些事情,并正确调整指针)。所以,如果我们有一个指向 int 的指针,比如上面的 b,当我们给那个指针加 1 时,内存地址将改变 4 个字节,以便它现在持有内存中下一个 int 的内存地址。我们需要确保那里确实有一个 int!编译器会信任我们做得对。

所以,假设我们有两个 int 值在内存中是相邻的。我们可以通过创建一个数组来实现

1
2
int c[2]; // This will make two int values in memory, right next to each other
int* p = &(c[0]); // This makes a pointer p, and gives it the value of the address of c[0], which is the first int  


请注意,这也将起作用;

1
2
int*p = c; // This makes a pointer p, and gives it the value of the address of c, which is itself a pointer,
            // pointing at the start of the array.  


现在,让我们给指针加 1。

 
p = p+1; // Now, the pointer holds the value of the next int along, c[1].  


很巧妙,不是吗?当你给指针加 1 时,它持有的值不会改变 1;它会改变正确的量,以便指向下一个对象。它知道那有多远,因为当你创建它时,你指定了它将指向的对象类型。如果它是一个 int 指针,它持有的值将改变 4 个(有时是 8 个)字节。如果它指向一个占 40 字节的奇怪对象,那么值将改变 40 个字节。你不需要担心内存地址实际改变了多少;你可以相信编译器会为你处理。

如果你给指针加 2,指针的值将改变正确的量以指向内存中靠后的第 2 个对象,依此类推。如果你从指针减 1,它将指向内存中靠前的第 1 个对象。

所以,检查一下这个

1
2
3
4
5
6
7
double x[10]; // an array of 10 doubles
double* p = x; // p is a pointer, now pointing at the first double
for (int i=0;i<10;i++)
{
  std::cout << *p << std::endl;  // output the double value
  p++; // make the pointer point to the next one
}


此代码输出了一个 double 数组的(垃圾)值,并演示了指针算术。

你会意识到,这开启了各种巧妙的功能。如果你有一个 int,仅仅为了好玩,你想逐个查看每个字节,那该怎么办?char 定义为一字节长,所以如果你有一个 char 指针,让它指向那个 int,然后你给 char 指针加 1,你就会看到下一个字节——在 int 里面!

1
2
3
4
5
6
7
8
9
10
11
#include <iostream>

int main()
{

  int a = 34;
  char* p = (char*)&a; // Make a char*, and force the compiler to treat the address of a as a char pointer.
  for (int i =0;i<4;i++)
    {std::cout << (int)(*p) << std::endl; p++;} // Cycle through the 4 bytes of the int, outputting each byte as if it were an int
  return 0;
}


这个会稍微有趣一点

1
2
3
4
5
6
7
8
9
#include <iostream>
int main()
{
  int a = 256;
  char* p = (char*)&a; // Make a char*, and force the compiler to treat the address of a as a char pointer.
  for (int i =0;i<4;i++)
    {std::cout << (int)(*p) << std::endl; p++;} // Cycle through the 4 bytes of the int, outputting each byte as if it were an int
  return 0;
}


你可以深入了解内存中的确切数字,你应该能够弄清楚数字 256 是如何用这 4 个字节表示的。

正如你所猜到的,我们可以用这种直接干预单个字节的强大能力做很多疯狂的事情。欢迎来到 C 和 C++ 的底层威力!


使用 "*" 处理指针时的不同用法
"*" 有两种含义。


让我们先讲第一种含义
1
2
int b; // Make a new object. The new object is to be an int.
int* a; // Make a new object. The new object is to be an int-pointer  


当我们创建对象时,"*" 只是描述我们想要创建的对象类型的一部分。一旦我们创建了对象,我们只需使用对象的名称。

这是我们如何传递我们刚刚创建的对象 b

 
someFunction(b);


当我们实际使用 b 时,我们不需要指定它是一个 int。

所以,这是我们如何传递我们刚刚创建的指针

 
someOtherFunction(a);


当我们实际使用 a 时,我们不需要指定它是一个 int 指针。

"*" 的另一种用途是作为解引用运算符。我们在指针上使用它,当我们不想要实际的指针时;我们想要它指向的东西。

1
2
int* c; // Here, "*" is part of the kind of object we want to make
*c; // Here, "*" means dereference the pointer and give us what it points at  


这是两种不同的用法,具有两种不同的含义。如果你看到 int* 或 double* 或 string* 或任何其他这样的 <object-type>*,它是在描述一种对象类型。如果你看到 *pointer,它的意思是解引用。

你将在创建指针时以及在指定函数接收(和返回)的对象类型时看到“描述一种对象类型”的用法,例如在这个函数原型中

 
void DealDamage(int *EnemyHpPointer, int damage, string EnemyName);


DealDamage 接收一个指向 int 的指针、一个 int 和一个字符串。它可以这样使用

1
2
3
4
5
6
int* somePointer;
int someValue;
string someString;

// and then, after these have been given values, use the function
DealDamage(somePointer, someValue, someString);