Bootstrap

大白话解指针(二):指针和数组

大白话解指针(二):指针和数组

数组和指针联系非常紧密,因为数组的元素在内存中是连续存放的,这意味着它们的地址也是连续的,那么有了其中一个元素的地址,整个数组都可以操作了,那可太棒了。

一维数组怎么用指针去指?

    int a[3] = { 1,2,3 };
    int* pointer_to_a_array = a;
    int* pointer_to_a_array_2 = &a[0];
    printf("%d\n", *pointer_to_a_array);
    printf("%d", *pointer_to_a_array_2);

得到的两个输出完全相同,我们发现让指针直接指向a或者指向a的第一个元素的地址,即a[0] 的地址,它们的输出是一样的,都是a[0]。

这是两种比较常用的方法。

所以数组的名字a,可以看做就是数组的首元素的地址

printf("%p\n", a);
printf("%p\n", pointer_to_a_array);

指针对一维数组的操作

上一节我们提到过,指针加1,指针的地址会加上它的指针类型的大小。配合取值符号*,我们可以用指针完成对数组的遍历,以及对数组的操作。

    int a[4] = { 1,2,3,4 };
    int* pointer_to_a_array = a;
    for (int i = 0; i < 4; i++)
    {
        *pointer_to_a_array = *pointer_to_a_array + 5;
        printf("%d  ", *pointer_to_a_array);
        pointer_to_a_array++;
    }

通过上面这个案例,我们不光完成了数组所有位置的访问,还对每一个数组的值作出更改,是对之前知识点的综合运用,体现出指针加减法的精妙。

指针数组和数组指针

初学时对这两个概念一直傻傻分不清,看到就恶心,考到就寄。

现在该直面它们,搞懂它们了。

指针数组

指针数组,是指针数组。

这个“的”很重要,说明它是个数组,数组的元素是指针。类比int数组的元素是int,float数组的元素是float一样。

指针的内容是地址,指针就是地址。

所以指针的数组,就是地址的数组,就是地址的大集合。

理解后,上案例

int a = 10;
int b = 20;
int c = 30;
printf("%p\n", &a);
printf("%p\n", &b);
printf("%p\n", &c);
int* ptr[3] = { &a,&b,&c };
for (int i = 0; i < 3; i++)
{
    printf("%p\n", ptr[i]);
}

//
//或者
int a = 10;
int b = 20;
int c = 30;
int *p1,*p2,*p3;
p1 = &a;
p2 = &b;
p3 = &c;
printf("%p\n", &a);
printf("%p\n", &b);
printf("%p\n", &c);
int* ptr[3] = { p1,p2,p3 };
for (int i = 0; i < 3; i++)
{
    printf("%p\n", ptr[i]);
}

要注意的是指针数组的写法,它其实和一般的数组命名没什么区别,无非是指明数组的元素是指针罢了,别怕它。

而且,既然是数组,那我们正好可以用一下上面的对指针直接操作的内容,玩点花的,来看看地址的变化,顺便验证一下指针的大小:

    int a = 10;
    int b = 20;
    int c = 30;
    int* p1, * p2, * p3;
    p1 = &a;
    p2 = &b;
    p3 = &c;
    int* ptr[3] = { p1,p2,p3 };
    for (int i = 0; i < 3; i++)
    {
        printf("%p\n", &ptr[i]);//数组所保存的指针的地址,略难理解,可以先搁置
    }

输出的3个地址它们连续相差8,正好是64位电脑上一个指针的大小。全部换成short、也是一样相差8,证明指针的大小和指针的类型无关。

数组指针

数组指针,数组指针

同样,这个“的”也很重要,说明它是指针,大小也是固定的,指向的是一个数组。

它的写法和指针数组完全不一样,用法也完全不一样,由于指向的是数组,所以它的用途非常多,特别是可以帮助我们实现指针对二维数组的访问和操作

int (*p)[n]; //定义的格式像这样,看不懂?不急,我来一一分析。

首先,其实应该大部分人会提出来,这种格式的()有必要吗?我的回答是

有必要

如果不加括号,就是 int* p[n],这不就是上面刚刚写过的指针数组吗?

哦,又有人说了,那我这样写 int *p[n]总行了吧?

不行

int* p[n] 和 int *p[n]是等价的,由于符号结合优先性,请想想定义单个指针时

是不是int* a和 int *a是一样的?

好了,所以记得加括号。

现在来具体解析它的含义:

int (*p)[n];

数组指针是就是个指针,指针的内容是地址。

所以要搞懂这个指针的到底要装进去谁的地址

我们先来引入一下:

提问,第一,

int a[3][4]的基本结构:

{int, int, int, int,

int, int, int, int,

int , int, int, int}

假设我们现在设了一个

int (*p)[4];

p = a;

这个p指向的就是一行有4个元素(就是有4列,列数就是一行的元素个数)的数组a的第0行的地址。

多说无益,上案例

int a[3][4] = { 1,2,3,4,
                5,6,7,8,
                9,10,11,12 };
int(*p)[4] = a;
//也可以写 int (*p)[4]; p = a;
for (int i = 0; i < 3; i++)
{
    for (int j = 0; j < 4; j++)
    {
        printf("%d ", p[i][j]);
    }
}

我自己用的一个比较好记的办法就是,想创建数组指针,和数组匹配只看后面,就是列数是否相同。

有创造力的同学可能发现了,是不是一维数组也是可以用这个数组指针来指?

比如这样?

int a[4] = {1,2,3,4};
int(*p)[4];
p = a;

不可以

你会发现会报错,因为变量类型不匹配。

但是你可以这样,用一个伪二维数组实现对一维的访问。

    int a[1][4] = {1,2,3,4};//这是一个伪二维数组,只有一行
    int(*p)[4];
    p = a;
    for (int i = 0; i < 1; i++)
    {
        for (int j = 0; j < 4; j++)
        {
            printf("%d ", p[i][j]);
        }
    }

很有意思,对不?

其实,重要的不光是理解数组指针的定义方法,还要知道怎么去用它。

肯定有很多同学不想仅限于只用数组下标的方式来访问二维数组的数据,我们用的是指针,那这个文章就该有个写指针博客的样子。

好,请出我们的取值符号 *

用指针的形式访问二维数组的方法如下:

    int a[3][4] = { 1,2,3,4,
                5,6,7,8,
                9,10,11,12 };
    int(*p)[4] = a;
    //也可以写 int (*p)[4]; p = a;
    for (int i = 0; i < 3; i++)
    {
        for (int j = 0; j < 4; j++)
        {
            printf("%d ", *(p[i]+j));
        }
    }

这是怎么回事嘞,p[i]是指现在检查到第i行了,+j是指现在在第i行第j列了。

我们讲过,只对指针做+1 -1变化,实际上地址会变动和指针类型相同的字节数。

后面的[4]更像是一个限定的条件,必须去匹配列数为4的二维数组。(个人便于理解的思路)

对指针直接操作的话,其+1 -1的变动还是按照int的字节数来决定

p[i] + j ,j就是向右部移动j个int元素。自然就非常好理解了。

同样,你可以利用*来对二维数组的值作出改变,如下

int main(void)
{
    int a[3][4] = { 1,2,3,4,
            5,6,7,8,
            9,10,11,12 };
    int(*p)[4] = a;
    //也可以写 int (*p)[4]; p = a;
    for (int i = 0; i < 3; i++)
    {
        for (int j = 0; j < 4; j++)
        {
            *(p[i] + j) = *(p[i] + j) + 10;
            printf("%d ", *(p[i] + j));
        }
    }
}

注意:你会发现,对数组指针的研究像个无底洞,感兴趣的同学可以详细研究,主要的使用方法我应该都概述完了。

附赠一个案例用于进行研究:

    int a[3][4] = { 1,2,3,4,
                5,6,7,8,
                9,10,11,12 };
    int(*p)[4] = a;
    printf("%p\n", p[0]);
    printf("%p\n", a[0]);
    printf("%p\n", p);
    printf("%p\n", a);
    printf("%d\n", p[0]);
    printf("%d\n", a[0]);
    printf("%d\n", p);
    printf("%d\n", a);

PS.二维数组单单提行有意义的只有地址,要拿值还得是行+列配合。

数组名字和指针

有同学已经发现了,很多时候数组名可以直接和指针相等。

p = a;很常见。

其实,数组名的值是数组首元素的指针常量,但数组名不是指针,而是一个不可修改的常量指针,其类型与数组元素类型相同。

它们有共通之处,但也不能随便说它们都是一样的指针。

比较常用的数组与指针的关系基本概述完了,后面我将再写一篇“大白话解指针(三) 函数和指针"。

非常感谢您可以耐心读到这里,博客内容写的很粗糙,只是分享自己在学习中的一些想法,有错误指正和改进欢迎提出!

;