指针是C语言的核心,没有指针C语言就没有灵魂,它的强大之处在哪呢?我们接下来慢慢讨论。

1.了解指针

我们从一段最简单的代码开始:

int a=10;
int p=&a;

     很明显,a是一个整型变量,那么p是什么呢?

    内存开辟了四个字节大小的空间,p就是记录这块空间的一个变量。

    也就是说,p仍然是一个变量,只不过它的数据类型为整形指针。它存储的是另一个变量的内存地址。这个地址是计算机内存中的一个位置,我们可以通过这个地址找到存储在那里的值。听起来是不是很绕?不急,我们下面慢慢看。

  我们首先认识两个操作符”*“和“&”。

  • 取地址操作:使用&运算符可以获取一个变量的内存地址。例如,&a就是获取变量a的内存地址。
  • 解引用操作:使用运算符可以获取指针指向的内存地址中的值。例如,p就是获取p指向的内存地址中的值。

    我们接着看上一个例子:

2.指针的相关操作

其实"&"和“*”也是指针的操作,我们上面已经说过,这里的指针操作主要指的是指针的加减运算。

与其叫指针的加减运算,笔者其实更倾向与理解为指针的偏移,原因以下会说明。

  • 指针的加法运算(指针加整数)

当我们对一个指针进行加法运算时,我们实际上是在移动这个指针在内存中的位置。例如,如果我们有一个整数指针p,那么表达式p+1会将p移动到下一个整数的位置。

例如:

在这个例子中,arr代表的意思是指针首元素的地址,也就是说p最初指向数组的第一个元素(10)。当我们执行p = p + 3时,p现在指向数组的第四个元素(40)。我们也可以理解为p指针向后偏移了3个单位,最终的位置就是指向40。

我们接着看第二个例子

在这个例子中,p最初指向数组的第一个元素('a')。当我们执行p = p + 2时,p现在指向数组的第三个元素('c')。

在这里可能有一些细心的小伙伴发现问题了:int类型是四个字节,char类型是一个字节,为什么它们指针加法运算的时候都能精准的移到合适的位置呢?

这是因为当我们对指针进行加法或减法运算时,实际上是在移动指针在内存中的位置。这个移动的距离是根据指针所指向的数据类型的大小来决定的。这就是为什么int类型的指针加1会移动4个字节,而char类型的指针加1会移动1个字节。

这种设计使得我们可以方便地通过指针来访问和操作内存中的数据。例如,如果我们有一个int数组,我们可以通过一个int类型的指针来遍历这个数组。每次将指针加1,就可以让指针移动到数组的下一个元素。

这也是为什么指针的加减运算可以精准地移动到合适的位置。因为编译器知道指针所指向的数据类型的大小,所以它可以根据这个大小来计算指针的移动距离。这样,无论我们是在操作int数组还是char数组,指针都可以正确地指向数组的每一个元素。

  • 指针的减法运算(指针减整数)

指针的减法运算与加法运算类似,只不过方向相反。例如,如果我们有一个char类型的指针p,那么表达式p-1会将p移动到前一个元素的位置。

例如:

在这个例子中,首先我们复习指针的加法运算,p指向了数组的最后一个元素('e')。当我们执行p = p - 2时,p现在指向数组的第三个元素('c')。

指针与指针的减法

两个指针之间的减法会得到这两个指针之间的元素数量。

例如:

在这个例子中,diff的值为4,因为p2和p1之间有4个元素。

我曾经看到过一个很贴切地比喻:指针和指针的减法就像是日期减日期,得到的就是中间的天数。

那么就像日期加日期,没有任何意义,指针也一样,指针的加法运算(指针加指针)和乘法运算(指针乘以指针)在C/C++中是没有定义的。

3.多级指针

多级指针,顾名思义,就是指针的指针。也就是说,一个指针变量存储的是另一个指针变量的地址。这样的指针我们称之为二级指针。同样,我们也可以有三级指针,四级指针,等等。但是即使是我见过最复杂的项目,也只是用到二级指针,所以我们讨论二级指针即可。

例如:

在这个例子中,我们首先定义了一个整型变量a和一个指向a的指针p。然后,我们定义了一个二级指针pp,它指向的是p的内存地址。当我们通过两次解引用操作(*)来访问pp指向的值时,我们实际上是在访问a的值。

它的用途在哪里呢?主要是在动态内存分配、函数参数传递等方面使用。

4.数组指针

数组指针就是数组的指针,它实际上是一种特殊的指针,它指向数组的第一个元素。这种指针非常有用,因为它允许我们通过指针来操作数组。其实我们上面提到的一个例子就是数组指针。

在这个例子中,我们定义了一个整型数组arr和一个指向arr的第一个元素的指针p。我们可以通过p来访问和操作arr中的元素。

例如,我们可以通过以下方式来输出arr中的第一个元素:

printf(\"%d\n\", p); // 输出10

我们也可以通过以下方式来修改arr中的第一个元素:

p = 100; printf(\"%d\n\", arr); // 输出100

这里,我们通过指针p来修改了arr中的第一个元素。这就是数组指针的强大之处。

5.指针的安全使用

在我们探索指针的神奇世界时,有一个重要的警告需要我们牢记:在使用指针时,要特别注意不要访问未分配的内存或释放的内存,这可能会导致程序崩溃或其他未定义的行为。

这听起来可能有点吓人,但别担心,我们将在这一部分详细解释这个问题,并讲讲如何来避免这种情况。

首先,让我们来理解一下什么是未分配的内存。当你声明一个指针变量时,它只是一个存储地址的容器,但这个地址可能指向任何地方。如果你没有明确地将它指向一个已经分配的内存区域(例如,一个已经声明的变量或一个通过malloc等函数分配的内存块),那么这个指针就是所谓的“野指针”。

试图通过这样的指针访问或修改内存是非常危险的,因为你可能会无意中改变程序的其他部分,或者访问到你的程序没有权限访问的内存。

例如:

int p;  // 未初始化的指针,是一个野指针

p = 100;  // 危险!我们不知道p指向哪里

同样,当你释放了一个内存块(例如,通过free函数),任何指向这个内存块的指针都会变成“已被释放的内存”。这些指针仍然保持着被释放的内存的地址,但这个地址已经不再属于你的程序,再次访问它就可能导致错误。

例如:

为了避免这些问题,你应该遵循一些基本的规则:在声明指针变量时,总是将它初始化为NULL或一个已知的地址。在释放内存后,立即将任何指向它的指针设置为NULL。在使用指针之前,检查它是否为NULL。这样,你就可以确保你的指针总是指向有效的内存,或者至少不会无意中访问无效的内存。记住,指针是一个强大的工具,但是使用它需要谨慎和注意。

6.总结

以下是一些主要的要点:

1. 指针是一个变量,其值为另一个变量的内存地址。

2. "&"操作符用于获取一个变量的内存地址,"*"操作符用于获取指针指向的内存地址中的值。

3. 指针的加减运算实际上是在移动指针在内存中的位置,移动的距离根据指针所指向的数据类型的大小来决定。

4. 多级指针是指针的指针,一个指针变量存储的是另一个指针变量的地址。

5. 数组指针是指向数组的第一个元素的指针,可以通过数组指针来访问和操作数组中的元素。

注意事项:在使用指针时,要特别注意不要访问未分配的内存或释放的内存,这可能会导致程序崩溃或其他未定义的行为。同时,指针的加减运算需要根据指针所指向的数据类型的大小来进行,不能随意进行。

以上就是指针的基础,学到这里指针的大多数操作已经没有问题了,指针进阶我们下一篇讨论。