关于指针与数组这对兄弟

c/c++

浏览数:195

2019-3-30

指针和数组是 C 语言中比较重要的知识点,尤其是指针和数组结合之后,很多同学就不清楚是怎么一回事了。在这里结合几个例子,介绍一下我所理解的指针和数组,希望对大家有所帮助。

指针

指针在 C 语言中是一种类型,就和其他基本数据类型一样。通常在32位机器上面占四个字节,在64位机器上占八个字节。指针用来保存其他变量的地址,请看下面整型指针与整型变量的对比:

int main()
{
    int a = 4;      //定义整型变量 a
    int b = 10;     //定义整形变量 b
    int *p = &b;    //定义整型指针 p ,保存变量 b 的地址

    //a 变量和 p 变量的值
    printf("a 变量的值为: %d\n", a);
    printf("p 变量的值为: %p\n", p);
    
    //指针变量的特殊操作
    printf("指针所指向变量的值: %d\n", *p);
    *p = 15;
    printf("指针所指向变量的值: %d\n", *p);
    printf("b 变量的值为: %d\n", b);
    return 0;
}

解释:
在上面的程序中,定义了整型变量 a、整型变量 b 和 整型指针变量p,让指针变量 p 保存变量 b 的内存地址,也就是说 p 指针”指向“变量 b 。程序接着打印 a 变量的值和p 变量的值,分别代表整型变量 a 保存的整数值和指针变量 p 保存的变量 b 的地址值(%p表示用十六进制数字表示地址值)。指针还有特殊的操作:*操作,通过*操作可以访问指针所指向的变量,在程序中也就是说 *p 与变量 b 是等价的,操作变量 *p 就是操作变量 b

类型 变量 特殊操作
int a a (使用 %d 打印)
int * p p (使用 %p 打印) *p (代表指向的变量)

数组

数组是一种特殊的数据类型,它把具有相同类型的若干变量按照有序的顺序组织起来,极大的方便了我们的开发。数组名代表数组的首地址,即数组第一个元素的地址,通过下标 [] 来访问数组里面的元素。

int main() 
{
    int a[5] = {1, 2, 3, 4, 5};
    printf("%d\n", a[0]);
    return 0;
}

上面代码中的 a[0] 代表数组中的第一个元素,输出的结果为1;

数组和指针的联系

其实数组的下标 [] 操作也是一种特殊操作,与指针的 * 操作十分相似。数组名是一个指针:指针常量。它无法改变指向,只能指向数组的首地址,所以a++、a–之类的操作都是错误的(a是数组名)。既然数组名是一个指针,可以使用下标 [] 操作,那么普通的指针变量是不是也可以使用呢?反过来,普通指针的 * 操作是不是也适合数组呢?

int main()
{
    int a[4] = {5, 6, 7, 8};
    int *p = a;
    int i;
    printf("输出数组里面的内容:\n");
    for(i = 0; i < 4; i++)
        printf("a[%d] = %d\t", i, a[i]);
    printf("\n");

    for(i = 0; i < 4; i++)
        printf("p[%d] = %d\t", i, p[i]);
    printf("\n");

    for(i = 0; i < 4; i++)
        printf("*(a+%d)=%d\t", i, *(a+i));
    printf("\n");

    for(i = 0; i < 4; i++)
        printf("*(p+%d)=%d\t", i, *(p+i));
    printf("\n");
    return 0;
}

运行结果:

运行结果

从结果可以看出,[]* 操作都适用于指针变量,数组也是通过操作指针来实现对元素的查询、修改操作。让我们再深入一点,数组是如何通过下标操作找到对应的元素的呢?

int main()
{
    int a[5] = {1, 2, 3, 4, 5};
    int *p = a;
    printf("%d\n", p[2]);
    printf("%d\n", *(p+2));

    p = &a[2];
    printf("%d\n", p[0]);
    printf("%d\n", *p);
    return 0;
}

运行一下代码发现,输出的数据都是3,也就是数组中的第三个元素。怎么会这样?

解释:
程序中当指针 p 指向数组的第三个元素的时候,这时候 p[0] 输出的是3,也就是a[2] 的值,那么 p[0] 的操作结果与 *p 的操作结果一致;另外,大家试一试
输出 p[1] 的值,不出所料操作结果应与 *(p+1) 的操作结果一致。发现规律没?[] 操作其实就是 * 操作啊!!!发现新大陆了啊,有没有?

让我们冷静的分析一下: [] 操作和 * 操作实际上都是偏移操作(内存地址偏移),通过当前指针指向的内存地址以及常量来进行地址偏移,最后通过指针的类型解析所指向地址的内容。

那么偏移的单位是多少?这个与指针或者数组的类型有关,比如 int * 类型的指针一次偏移 sizeof(int) 的地址,double * 类型的指针一次偏移 sizeof(double) 的地址。

指针访问数组的方式

  1. 使用 [] 访问。例如 int a; int *p = a;用 p 表示输出 a[2] 的值:p[2] ;如果是这样int a[5]; int *p = &a[2];用 p 表示输出a[3] 的值:p[1]

  2. 使用 * 访问。例如int a[5]; int *p = a;用 p 表示输出 a[2] 的值:*(p+2);如果是这样:int a[5]; int *p = &a[2];用 p 表示输出 a[3] 的值:*(p+1)

思考

知道指针和数组的操作原理后,下面的题你会做吗?

第一题:

int main()
{
    int a[5] = {1, 2, 3, 4, 5};
    
    p = &a[2];
    printf("%d\n", p[-2]);
    printf("%d\n", *(p-2));
    return 0;
}

第二题:

int main()
{
    int a[5] = {1, 2, 3, 4, 5};
    int *ptr = (int *)(&a + 1);
    printf("%d %d\n", *(a+1), *(ptr-1));
    return 0;
}

第二题注意指针的类型变化哦,自己编写运行一下,看结果与自己想的一样吗?
ps: 竟然还能 p[-2],坑爹有没有…

总结

  1. 指针和数组都可以使用下标或者 * 操作。
  2. 下标操作或者 * 操作的本质都是地址偏移,通过指针的类型来解析内存地址里面存储的数据。
  3. void * 指针可以指向任何地址,但是无法使用 * 操作,原因就是编译器不知道如何解析void * 指针所指向的内存地址的内容。