懂了嘎嘎乱杀,但我赌你会懵——指针进阶终极版

简介: 正片开始👀细化指针这一部分内容,现在着重把一些指针的运用情景搬出来康康,如果对指针盘的非常熟练了,或者指针还出于入门阶段的铁子请绕道(晕头警告)直接给大家盘个套餐:一维数组👏
int a[] = {1,2,3,4,5};
printf("%d\n",sizeof(a));
printf("%d\n",sizeof(a+0));
printf("%d\n",sizeof(*a));
printf("%d\n",sizeof(a+1));
printf("%d\n",sizeof(a[1]));
printf("%d\n",sizeof(&a));
printf("%d\n",sizeof(*&a));
printf("%d\n",sizeof(&a+1));
printf("%d\n",sizeof(&a[0]));
printf("%d\n",sizeof(&a[0]+1));

问题很简单,这组 printf 的值是多少?小朋友你是否有些许害怕,但是没有关系我们逐个击破,这是后续内容的基础

记住你的答案,来看看编译器是怎么解析的:


image.png

首先我们应该知道数组名代表的是数组首元素的地址,但是要知道我们有两种例外:

1.sizeof (),()内是数组名时,代表的就是整个数组,计算的就是数组的大小,单位是字节;

2. & 数组名时,表示的也是整个数组,取出的就是整个数组地址;

除了以上两种情况,其余的所有数组名都表示首元素地址!


所以1.sizeof(a)就是直接将数组名放进去,算出的就应该是 sizeof(int)*5 = 20

2.sizeof(a+0)此时不只有数组名,因此 a 代表首元素地址,a+0 等价于 a,是地址大小,在32位/64位平台下对应 4/8 字节大小

3.*a ,不只有数组名 a 代表首元素地址,解引用得到首元素,sizeof(a)= sizeof(int)= 4

4.a+1 老规矩还是首元素地址+1,就是第二个元素地址,是地址大小为 4/8 字节

5.a[ 1 ],大小为4

6.&a 为整个数组地址,但是地址终归是 4/8 字节

7. * &a ,&a 是类型为 int()[4] 的数组指针,解引用数组地址为整个数组,大小 20

8. &a +1取出整个数组地址,一个数组指针的单位就是整个数组,+1 就会跳过整个数组,大小还是为第二个数组地址,大小 4/8

9. &a[0],首元素地址,4/8

10. &a[0] + 1,第二个元素地址,4/8


字符数组👏

char a[] = {'a','b','c','d','e','f'};
printf("%d\n",sizeof(a));
printf("%d\n",sizeof(a+0));
printf("%d\n",sizeof(*a));
printf("%d\n",sizeof(a[1]));
printf("%d\n",sizeof(&a));
printf("%d\n",sizeof(&a+1));
printf("%d\n",sizeof(&a[0]+1));

和上面同理,再来看看编译器怎么解答的:

image.png

1.a 是数组名,首元素地址计算整个数组大小为 6

2.a + 0,首元素地址 + 0 还是首元素地址,4/8

3.*a,首元素大小 ,1

4.1

5.&数组名为整个数组地址,4/8

6.跳过一个数组,下一个数组地址,4/8

7.第二个元素地址,4/8


这么简单?nonono,这才刚刚开始

char a[] = {'a','b','c','d','e','f'};
printf("%d\n",strlen(a));
printf("%d\n",strlen(a+0));
printf("%d\n",strlen(*a));
printf("%d\n",strlen(a[1]));
printf("%d\n",strlen(&a));
printf("%d\n",strlen(&a+1));
printf("%d\n",strlen(&a[0]+1));

我还是先把运行结果啪出来吧:

image.png

诶?怪诶,打印7下出来2个?nnd给我玩阴的是吧。这里首先强调一下,sizeof 是操作符,海纳百川来啥算啥,‘ \0 ’也会照收,而 strlen 是傲娇的库函数,傲娇在于strlen 针对的是 \0 之前的字符串长度(个数),而且不包含 ‘ \0 ’。


1.a 数组名,没有在 sizeof 内部,为首元素地址,我们知道 strlen 在遇到 \0 之前是不会停下来的,字符数组我们没有给到 \0就是没有,因此什么时候遇到他我们不知道,起码会比当前数组大,不知道嘛时候停就是个随机值

image.png

2.同理,随机值

3.首元素地址解引用为首元素 ‘a’,strlen 需要的是一个地址,此时会从把‘a’ 默认为一个地址,a的ASCII码值为 97,就会从内存中地址为 97 的地方开始往后数字符个数,但是注意 97 不属于我原本分配的内容,属于非法访问内存,直接报错崩溃垮掉

4.同理,报错

5.数组的地址,虽然和 strlen 参数类型有所差异,但还是从第一个位置向后数,那就是随机值啦

6.跳过一个数组的地址,依然是随机值

7.第二个元素地址,依然依然是随机值


那么就又双可以联想到字符串了,引入指针变量进行讨论:

char* p = "abcdef";
printf("%d\n",sizeof(p));
printf("%d\n",sizeof(p+1));
printf("%d\n",sizeof(*p));
printf("%d\n",sizeof(p[0]));
printf("%d\n",sizeof(&p));
printf("%d\n",sizeof(&p+1));
printf("%d\n",sizeof(&p[0]+1));
printf("%d\n",strlen(p));
printf("%d\n",strlen(p+1));
printf("%d\n",strlen(*p));
printf("%d\n",strlen(p[0]));
printf("%d\n",strlen(&p));
printf("%d\n",strlen(&p+1));
printf("%d\n",strlen(&p[0]+1));

直接看结果吧,这下就感觉出有点意义不明了,结果对应哪个数据都不知道了,所以这时候自主分析的价值就出来了

image.png

p是一个指针,sizeof 算指针的大小,4/8字节

p+1,就是 p 的值加1,字符指针p是一个地址,数值上+1是 b 的地址,所以大小 4/8字节

p 为char* 的指针,解引用访问首元素,大小为 1

小问号你是否有很多朋友,指针为啥还可以 [0] ?其实 p[0] 等价于 *(p+0),p是首元素地址,p+0亦是,解引用出来就是首元素,1

p是地址,&p是对地址取地址,也就是二级指针,是地址那就是 4/8

跳过一个char*的地址即跳过了一个p的地址,本质上还是地址,4/8

p[0]为首元素,取地址+1 为第二个元素的地址,4/8


1.在 “abcdef”中是隐含了一个‘ \0 ’的,所以传入p算出大小为 6

2.同理,从第二个元素开始,5

3.解引用为a,97,报错

4,等价于*p,就是首元素,报错

5,取数组地址的地址往后数字符串,为随机值

6.+1跳过一个地址的地址往后数,依然是随机值

7.第二个元素的地址往后数,随机值,等价于 p+1

int a[3][4] = {0};
printf("%d\n",sizeof(a));
printf("%d\n",sizeof(a[0][0]));
printf("%d\n",sizeof(a[0]));
printf("%d\n",sizeof(a[0]+1));
printf("%d\n",sizeof(*a[0]+1));
printf("%d\n",sizeof(a+1));
printf("%d\n",sizeof(*(a+1)));
printf("%d\n",sizeof(&a[0]+1));
printf("%d\n",sizeof(*(&a[0]+1)));
printf("%d\n",sizeof(*a));
printf("%d\n",sizeof(a[3]));

看一下运行结果:

image.png

1.a数组名,为整个数组,124 = 48

2.数组首元素,14 = 4

3.数组第一行元素,44 = 16

4.a[0]此时为首元素地址,即a[0]|0] 地址,+1为 a[0]|1] 地址,4/8

5.等价于a[0] [1],,14 = 4

6.数组名不是单独存放为第一行地址,+1表示第二行地址,4/8

7.解引用为第二行元素,44 = 16

8.a[0]为第一行地址,&a[0]+1为第二行地址,4/8

9.解引用为第二行元素,44 = 16

10.解引用首元素地址为第一行,44 = 16

11.淦!好怪!数组只有3行却搞出个 a[3],这不越界了吗?其实编译器会很聪明的自行推算,按照已给数组排列就是 44 = 16

整点硬菜👏

热身完了,来整点题目,让你混乱的大脑脉动回来(雪上加霜

1.

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

ptr 是int类型指针,&a是数组地址,+1跳过一个数组,指向数组末位的地址,强转为 int* 类型,*(a+1)首元素地址+1再解引用得到第二个元素 ,ptr - 1指向 5 的地址,所以答案为 2,5。

//假设stu 大小为 20 字节
struct stu
{
char* name;
int age;
float score;
} *p;
int main()
{
(struct  stu*)p =0x100000;
printf("%p\n",p + 0x1);
printf("%p\n",(unsigned long)p + 0x1);
printf("%p\n",(unsigned int*)p + 0x1);
return 0;
}

p是一个指针类型指向结构体,代表结构体的地址, p + 0x1 就是地址数值上进行 + 0x1 操作,p = 0x100000 ,我们知道 int* +1 跳过4个字节,char* + 1跳过1 个字节,我们这里是结构体类型,就跳过 20 个字节,20 化成16进制相加就是 0x100014


p 强转为 unsigned long 其实就是整型,p 就是一个纯纯的数字了,那0x100000 既然已经是“数字”了,那就有了可以直接加减的属性,就是 0x100001


p强转成无符号整型指针类型, 一个指针类型是 4 个字节,结果就是 0x100004


到这里是不是有内味儿了,那咱继续

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

此题请仔细思考,大坑警告!


ptr 为整型指针,&a为整个数组地址,+1跳过整个数组来到末位的地址,再强转成 int * 类型(这里大可不必,因为它本来就是一个 int * 指针)ptr[-1] 等价于 * (ptr - 1),再代入 ptr 就代表最末地址 -1 来到 4 的地址,ptr[-1 ]就是 4。


ptr2中 a为首元地址,这里注意优先级问题,先强转成 int 就是个纯纯的数字可以直接进行 +1 操作,即地址进行了数值+1,格局高不高就要看下一步,接下来需要考虑 大小端问题,因为我们是小端存储,按照低位字节在低地址处,所以 1,2,3,4 在内存中是这样的:


…… | 01 | 00 | 00 | 00 | 02 | 00 | 00 | 00 | 03 | 00 | ……


假设 01 地址是 0x01,int 强转后 a +1 偏移一个字节,指向的就是 01 后面一个 00 的位置,所以 ptr2 就指向这个位置,我们解引用拿到他的值就是整型大小的值就是 00 00 00 02,再以小端的形式拿出来就是 20 00 00 00。

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

p[0] 等价于*(p+0),p = a[0], 就是*a[0] ,a[0] 是首元素地址,那么问题来了:是 0 的地址吗?


请仔细看看我们二维数组的逗号表达式,其实整个数组就只有 3 个元素:1,3 5;他们在数组中排列出来是:


1 | 3

5 | 0

0 | 0


所以解引用出来就是 1。

相关文章
|
1月前
|
C语言
【C语言】指针进阶之sizeof和strlen函数的对比
【C语言】指针进阶之sizeof和strlen函数的对比
|
1月前
|
C语言
C语言---指针进阶
C语言---指针进阶
19 0
|
1月前
C进阶:指针的练习(1)
C进阶:指针的练习(1)
|
1月前
C进阶:指针的进阶(4)
C进阶:指针的进阶(4)
|
1月前
|
存储 C语言
C进阶:指针的进阶(1)
C进阶:指针的进阶(1)
|
1月前
|
存储 C语言 C++
C语言指针进阶-1
C语言指针进阶-1
24 1
|
1月前
|
存储 C语言 C++
【指针的进阶】C语言
【指针的进阶】C语言
|
1月前
|
C语言
【C语言进阶】 指针进阶(二)
【C语言进阶】 指针进阶(二)
|
1月前
|
C语言 C++
【C语言进阶】 指针进阶(一)
【C语言进阶】 指针进阶(一)
|
1月前
|
C语言
【C语言】指针进阶之传值调用与传址调用
【C语言】指针进阶之传值调用与传址调用