目录正片开始一维数组字符数组二维数组整点硬菜正片开始 细化指针这一部分内容,现在着重把一些指针的运用情景搬出来康康,如果对指针盘的不是非常熟练,或者指针还出于入门阶段的铁子请绕道(晕
细化指针这一部分内容,现在着重把一些指针的运用情景搬出来康康,如果对指针盘的不是非常熟练,或者指针还出于入门阶段的铁子请绕道(晕头警告)
直接给大家盘个套餐:
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 的值是多少?小朋友你是否有些许害怕,但是没有关系我们逐个击破,这是后续内容的基础
记住你的答案,来看看编译器是怎么解析的:
首先我们应该知道数组名代表的是数组首元素的地址,但是要知道我们有两种例外:
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));
和上面同理,再来看看编译器怎么解答的:
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));
我还是先把运行结果啪出来吧:
诶?怪诶,打印7下出来2个?nnd给我玩阴的是吧。这里首先强调一下,sizeof 是操作符,海纳百川来啥算啥,‘ \0 ’也会照收,而 strlen 是傲娇的库函数,傲娇在于strlen 针对的是 \0 之前的字符串长度(个数),而且不包含 ‘ \0 ’。
1.a 数组名,没有在 sizeof 内部,为首元素地址,我们知道 strlen 在遇到 \0 之前是不会停下来的,字符数组我们没有给到 \0就是没有,因此什么时候遇到他我们不知道,起码会比当前数组大,不知道嘛时候停就是个随机值
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));
直接看结果吧,这下就感觉出有点意义不明了,结果对应哪个数据都不知道了,所以这时候自主分析的价值就出来了
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]));
看一下运行结果:
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。
2.
//假设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
到这里是不是有内味儿了,那咱继续
3.
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。
4.
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。
今天就到这儿吧,摸了家人们
--结束END--
本文标题: 赌你会懵的C语言指针进阶数组场景解析
本文链接: https://lsjlt.com/news/139083.html(转载时请注明来源链接)
有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341
2024-03-01
2024-03-01
2024-02-29
2024-02-29
2024-02-29
2024-02-29
2024-02-29
2024-02-29
2024-02-29
2024-02-29
回答
回答
回答
回答
回答
回答
回答
回答
回答
回答
0