指针概述


指针是C语言中很必要的一项学习内容,使用指针简化一些C编程任务的执行,还有一些任务,如动态内存分配,没有指针是无法执行的。
指针最直观的理解就是借助内存地址访问该内存位置中存储的变量内容。我们知道每一个变量都有一个内存位置,每一个内存位置都定义了可使用 & 运算符访问的地址,指针可以理解为取地址。
首先指针是一个变量,由于指针就是取地址,所以指针变量的值就是另一个变量的地址,即,内存位置的直接地址。像其他变量或常量一样,指针在使用之前需要先进行声明,声明格式如下

type *var-name;

举例,以下是有效的指针声明:

int    *ip;    /* 一个整型的指针 */
double *dp;    /* 一个 double 型的指针 */
float  *fp;    /* 一个浮点型的指针 */
char   *ch;     /* 一个字符型的指针 */

简单使用如下:

#include <stdio.h>

int main ()
{
   int  var = 20;   /* 实际变量的声明 */
   int  *ip;        /* 指针变量的声明 */

   ip = &var;  /* 在指针变量中存储 var 的地址 */
   printf("Address of var variable: %p\n", &var  );

   /* 在指针变量中存储的地址 */
   printf("Address stored in ip variable: %p\n", ip );
   /* 使用指针访问值 */
   printf("Value of *ip variable: %d\n", *ip );

   return 0;
}

输出如下

Address of var variable: bffd8b3c
Address stored in ip variable: bffd8b3c
Value of *ip variable: 20

指针算术运算


可以对指针进行四种算术运算:++、--、+、-。进行运算的原则是,运算对指针产生的效果是移动指针到下一个符合运算规则的内存位置。注:这里需要强调的是内存位置。
举例说明,定义两个指向不同类型内存的指针

#include <stdio.h>
/*定义全局变量*/
const int MAX = 3;
int main()
{
    int  var[] = { 10, 100, 200 }; 
    char str[] = "ABC";
    int  i;

    int * ptr_1 = var ; /*指向32位整数(int型)内存类型的指针*/
    char * ptr_2 = str ;/*指向字符(char型)内存类型的指针*/

for (i = 0; i < MAX; i++)
    {
        printf("存储地址:var[%d] = %x\n", i, ptr_1);
        printf("存储地址:str[%d] = %x\n", i, ptr_2);

        printf("存储值:var[%d] = %d\n", i, *ptr_1);
        printf("存储值:str[%d] = %d\n\n", i, *ptr_2);

        /* 移动到下一个位置 */
        ptr_1++;
        ptr_2++;
    }

    return 0;
}

运行该程序得到的结果为

存储地址:var[0] = 81b5fab8
存储地址:str[0] = 81b5fae4
存储值:var[0] = 10
存储值:str[0] = 65

存储地址:var[1] = 81b5fabc
存储地址:str[1] = 81b5fae5
存储值:var[1] = 100
存储值:str[1] = 66

存储地址:var[2] = 81b5fac0
存储地址:str[2] = 81b5fae6
存储值:var[2] = 200
存储值:str[2] = 67

通过存储地址的变化对比可以看到,由于 32 位的整数(int型)的存储大小为4字节,字符型(char型)存储大小为1字节,因此指针移向下一个内存位置时,内存位置的地址变化不同,ptr1移动4字节而ptr2移动1字节。

指针数组


指针数组就是让数组存储指向 int 或 char 或其他数据类型的指针,举例,一个指向整数的指针数组的声明:

int *ptr[MAX];

在这里,把 ptr 声明为一个数组,由 MAX 个整数指针组成。因此,ptr 中的每个元素,都是一个指向 int 值的指针。

指向指针的指针


指向指针的指针是一种多级间接寻址的形式,或者说是一个指针链。通常,一个指针包含一个变量的地址。当我们定义一个指向指针的指针时,第一个指针包含了第二个指针的地址,第二个指针指向包含实际值的位置。
其实,指向指针的指针,弄明白每个指针指向的地址中的内容是什么,就能调用清楚,举个最简单的例子

#include <stdio.h>

int main ()
{
    int var;
    int *ptr;
    int **ptr_1;

    var=30;
    ptr=&var;
    ptr_1=&ptr;

    printf("Value of var = %d\n", var );
    printf("Value of var's address = %d\n", &var );
    printf("Value available at ptr = %d\n", ptr);
    printf("Value available at *ptr = %d\n", *ptr );
    printf("Value available at ptr_1 = %d\n", ptr_1);
    printf("Value available at *ptr_1 = %d\n", *ptr_1);
    printf("Value available at **ptr_1 = %d\n", **ptr_1);

   return 0;
}

输出结果

Value of var = 30
Value of var's address = 573569220
Value available at ptr = 573569220 
Value available at *ptr = 30
Value available at ptr_1 = 573569256
Value available at *ptr_1 = 573569220
Value available at **ptr_1 = 30

可以看到变量ptr中存储的是var中的实际内容所在地址,*ptr取实际内容;而ptr_1中存储的是指针ptr的地址,*ptr_1取得是变量ptr中的值,也就是var中存储值的地址,**ptr_1取的才是实际内容。

补充

补充数组指针的内容
考虑数组a,那么&a,a,&a[0]的区别是什么?
考虑以下代码

#include <stdio.h>

void main(void){

    int a[5] = {0};

    printf("a---------->%p\n", a);
    printf("&a--------->%p\n", &a);
    printf("&a[0]------>%p\n", &a[0]);

    printf("\nafter changed\n\n");
    printf("a+1-------->%p\n", a + 1);
    printf("&a+1------->%p\n", &a + 1);
    printf("&a[0]+1---->%p\n", &a[0]+1);

}

输出结果如下

a---------->00000042BADAFA58
&a--------->00000042BADAFA58
&a[0]------>00000042BADAFA58

after changed

a+1-------->00000042BADAFA5C//值增加了4,为一个int类型长度,则此地址为数组的第二个元素地址
&a+1------->00000042BADAFA6C//值增加了20,为五个int类型长度,则此地址为数组结束后的下一个元素地址
&a[0]+1---->00000042BADAFA5C//值增加了4,同a+1

虽然结果中 a == &a == &a[0] ,但是这三个值的意义不一样
a : 表示数组元素的首地址,同时也可做为数组的指针使用
&a : 表示整个数组的首地址
&a[0] : 表示数组第一个元素的地址