📄练习6:
code:
#include<stdio.h> #include<string.h> int main() { char* p = "abcdef"; printf("%zd\n", strlen(p)); printf("%zd\n", strlen(p + 1)); //printf("%zd\n", strlen(*p)); //printf("%zd\n", strlen(p[0])); printf("%zd\n", strlen(&p)); printf("%zd\n", strlen(&p + 1)); printf("%zd\n", strlen(&p[0] + 1)); return 0; }
运行结果:
6 5 3 11 5
💡解析
printf("%zdd\n", strlen(p)); //6
字符串中有\0
p中存放的是a的地址
从a的地址开始向后访问
⭕故,长度是6
printf("%zd\n", strlen(p + 1)); //5
p指向a
p+1指向b
从b开始往后数,直到遇到\0为止
⭕故,长度是5
//printf("%zd\n", strlen(*p));//err
p指向a
*p就是a
将p的值传递给strlen
⭕故,非法访问
//printf("%zd\n", strlen(p[0]));//err
p[0]=>*(p+0)=>*p
⭕故,非法访问
printf("%zd\n", strlen(&p)); //随机值
&p是p的地址
从p所占空间的起始位置开始查找的
完全不可知的
⭕故,随机值
printf("%zd\n", strlen(&p + 1));//随机值
&p+1指向的是p的地址后面
从&p后面的地址开始读取
也是不可知的
⭕故,随机值
printf("%zd\n", strlen(&p[0] + 1)); //5
&p[0] + 1是‘b’的地址
从’b’后面开始数
⭕故,长度是5
💻二维数组
📄练习:
code:
#include<stdio.h> int main() { int a[3][4] = { 0 }; printf("%zd\n", sizeof(a)); printf("%zd\n", sizeof(a[0][0])); printf("%zd\n", sizeof(a[0])); printf("%zd\n", sizeof(a[0] + 1)); printf("%zd\n", sizeof(*(a[0] + 1))); printf("%zd\n", sizeof(a + 1)); printf("%zd\n", sizeof(*(a + 1))); printf("%zd\n", sizeof(&a[0] + 1)); printf("%zd\n", sizeof(*(&a[0] + 1))); printf("%zd\n", sizeof(*a)); printf("%zd\n", sizeof(a[3])); return 0; }
运行结果:
48 4 16 4 4 4 16 4 16 16 16
💡解析
printf("%zd\n", sizeof(a)); //48
计算的是整个二维数组的大小,单位是字节
数组里共有3*4=12个元素
每个元素都是int类型
则344
⭕故,大小是48个字节
printf("%zd\n", sizeof(a[0][0])); //4
a[0][0]表示第一行第一个元素
⭕故,大小是4个字节
printf("%zd\n", sizeof(a[0])); //16
a[0]表示第一行数组名
将第一行数组名单独放在sizeof内部
计算的是第一行大小
⭕故,大小是16个字节
printf("%zd\n", sizeof(a[0] + 1)); //4
a[0]是第一行数组名,但是没有把a[0]单独放在sizeof中
这里的a[0]表示第一行第一个元素的地址
a[0] + 1表示第一行第一个元素地址+1,即第一行第二个元素地址==>a[0][1]的地址
是地址,大小是4或者8个字节
⭕故,大小是4或者8个字节
printf("%zd\n", sizeof(*(a[0] + 1))); //4
a[0] + 1是第一行第二个元素的地址
*(a[0] + 1)解引用,访问的就是第一行第二个元素
⭕故,大小是4个字节
printf("%zd\n", sizeof(a + 1)); //4
这里的a是二维数组的数组名
没有将a单独放在sizeof中
那么,a就是数组首元素地址,也就是第一行的地址😏
a+1跳过一行的地址,就是第二行地址
既然是地址,那么…
⭕故,大小是4或者8个字节
printf("%zd\n", sizeof(*(a + 1))); //16
a+1是第二行地址
*(a + 1)解引用,表示第二行
*(a + 1)==>a[1]
printf("%zd\n", sizeof(*(a + 1))); <==> printf("%zd\n", sizeof(a[1]);
两种表示方法,你get到了嘛??😏
⭕故,大小是16个字节
printf("%zd\n", sizeof(&a[0] + 1)); //4
a[0]是第一行数组名
&a[0]取出第一行的地址
&a[0] + 1第一行的地址+1,表示第二行地址
是地址…
⭕故,大小是4或者8个字节
printf("%zd\n", sizeof(*(&a[0] + 1))); //16
&a[0] + 1是第二行的地址
*(&a[0] + 1))对第二行地址解引用,访问第二行数组
⭕故,大小是16个字节
printf("%zd\n", sizeof(*a)); //16
a表示首元素地址,也就是第一行地址
*a对一行地址解引用,就是第一行
⭕故,大小是16个字节
printf("%zd\n", sizeof(a[3])); //16
a[3]是第四行,但是刚开始我们开的数组没有第四行
我们知道,sizeof()是根据类型计算的
实际上不会访问a[3]
a[3]和前面a[0]类型是一样的
⭕故,大小是16个字节
🗞️小结
- sizeof(数组名),这⾥的数组名表⽰整个数组,计算的是整个数组的⼤⼩。
- &数组名,这⾥的数组名表⽰整个数组,取出的是整个数组的地址。
- 除此之外所有的数组名都表⽰⾸元素的地址。
关于sizeof和strlen的介绍,可以看小编的文章《sizeof和strlen的对比》,里面有详细解释!!!
🚩指针运算笔试题
📄练习1:
code:
#include <stdio.h> int main() { int a[5] = { 1, 2, 3, 4, 5 }; int* ptr = (int*)(&a + 1); printf("%d,%d", *(a + 1), *(ptr - 1)); return 0; }
运行结果:
2,5
💡解析
1️⃣对*(a + 1)的分析:
- a表示数组首元素地址
- a+1首元素地址+1,表示第二个元素地址
- *(a + 1)解引用,表示第二个元素,即2
2️⃣对*(ptr - 1)的分析:
- 首先分析int* ptr = (int*)(&a + 1);
- &a表示整个数组地址
- &a+1表示跳过整个数组地址
- &a的类型是int(*)[5] ,+1后的类型还是这个类型
- 现在要将(&a + 1)赋值给ptr
- ptr是int* 类型,所以需要强制类型转换
- 分析*(ptr - 1)
- ptr-1表示前移动,即5的地址:
- *(ptr-1)解引用,表示5
📄练习2:
在X86环境下
假设结构体的⼤⼩是20个字节
code:
#include <stdio.h> struct Test { int Num; char* pcName; short sDate; char cha[2]; short sBa[4]; }*p = (struct Test*)0x100000; int main() { printf("%p\n", p + 0x1); printf("%p\n", (unsigned long)p + 0x1); printf("%p\n", (unsigned int*)p + 0x1); return 0; }
运行结果:
00100014 00100001 00100004
💡解析
printf("%p\n", p + 0x1); //00100014
0x1表示1(在16进制中)
p+0x1即p+1
那么,指针+1到底加几呢??😥
这个取决于指针类型,现在是一个结构体指针
那么加1就是跳过一个结构体大小
结构体大小是20个字节,加的是0x100014
⭕所以,大小变成00100014(16进制中)
printf("%p\n", (unsigned long)p + 0x1); //00100001
p的类型被强制转换成unsigned long,无符号整型
此时就不是指针类型啦
那么就是:整型+1
不再是整型指针+1,小小的细节,要清楚
0x100000+1=0x100001
⭕所以,输出结果:00100001
printf("%p\n", (unsigned int*)p + 0x1); //00100004
p被强制转换成整型指针(unsigned int)
整型指针+1就是+4,即0x100004
⭕所以,输出结果:00100004
📄练习3:
cod:
#include <stdio.h> int main() { int a[4] = { 1, 2, 3, 4 }; int* ptr1 = (int*)(&a + 1); int* ptr2 = (int*)((int)a + 1); printf("%x,%x", ptr1[-1], *ptr2); return 0; }
运行结果:
4,2000000
💡解析
- &a:取数组 a 的地址
- &a + 1:将指向数组的指针做加法运算,相当于移动了一个整个数组的大小。由于 a 是一个包含4个元素的 int 数组,所以加上1后指向数组之外的内存。
- (int*)(&a + 1):将指向数组之外的内存地址转换为 int 指针。
ptr1[-1]:通过指针 ptr1 往前访问前一个位置的值,即指向数组的最后一个元素。 - (int)a:将数组 a 转换为 int 类型的值,实际上是取数组首元素的地址。
- (int*)((int)a + 1):将数组首元素地址加上1后再转换为 int 指针。
- *ptr2:通过指针 ptr2 访问指向的值。
- 根据上述分析,我们可以预测输出结果。
- 由于 a 是一个包含4个 int 类型元素的数组,在内存中占用4个 int 大小的连续空间。
- ptr1[-1]:指向数组最后一个元素,即 a[3] 的值为4。
- *ptr2:指向数组首元素后面的1字节内存,此内存内容未定义。
- 因此,我们可以预测输出结果为 4,未定义的值(可能是未初始化的值或者随机值)。
这道题在随着后面的学习,会有更深刻的理解,可以在学习完《数组在内存中的储存》后,再来看这道题
📄练习4:
code:
#include <stdio.h> int main() { int a[3][2] = { (0, 1), (2, 3), (4, 5) }; int* p; p = a[0]; printf("%d", p[0]); return 0; }
运行结果:
1
💡解析
这里的数组初始化可不是下面这样:
int a[3][2] = { (0, 1), (2, 3), (4, 5) };
里面是逗号表达式
逗号表达式:从左向右依次计算,但是整个表达式的结果是最后一个表达式的结果
int a[3][2] = { (0, 1), (2, 3), (4, 5) }; <===> int a[3][2] = { 1, 3, 5 };
a[0]表示第一行数组名,是第一行首元素的地址,即a[0]==>&a[0][0]
p[0]==>*(p+0),即1
⭕所以,输出结果:1
📄练习5:
X86环境:
code:
#include <stdio.h> int main() { int a[5][5]; int(*p)[4]; p = a; printf("%p,%d\n", &p[4][2] - &a[4][2], &p[4][2] - &a[4][2]); return 0; }
运行结果:
FFFFFFFC,-4
💡解析
int a[5][5];
int(*p)[4]; p = a;
&p[4][2]
&p[4][2] - &a[4][2]
printf("%p,%d\n", &p[4][2] - &a[4][2], &p[4][2] - &a[4][2]);
以%p打印和以%d打印是不一样的
%d打印就直接是-4
%p打印的是-4的补码,即FFFFFFFC
📄练习6:
code:
#include <stdio.h> int main() { int aa[2][5] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 }; int* ptr1 = (int*)(&aa + 1); int* ptr2 = (int*)(*(aa + 1)); printf("%d,%d", *(ptr1 - 1), *(ptr2 - 1)); return 0; }
运行结果:
10,5
💡解析
int* ptr1 = (int*)(&aa + 1);
将&aa + 1强制转换成int* 型,因为ptr1是int*型
然后赋值给ptr1
*(ptr1 - 1)是10
int* ptr2 = (int*)(*(aa + 1));
*(aa + 1)<==>aa[1]
aa[1]本来就是一个地址,这里的强制类型转换其实是没有意义的,完全就是迷惑人的
将aa[1]赋值给ptr2
那么*(ptr2 - 1)就是5
📄练习7:
code:
#include <stdio.h> int main() { char* a[] = { "work","at","alibaba" }; char** pa = a; pa++; printf("%s\n", *pa); return 0; }
运行结果:
at
💡解析
这里是将‘w’,’a’,‘a’的地址存到数组里面去
内存布局:
char** pa = a; pa++;
a是数组首元素地址,即char*的地址
pa存放首元素地址
printf("%s\n", *pa);
对pa解引用,访问的就是第二个元素,即打印at
📄练习8:
code:
#include <stdio.h> int main() { char* c[] = { "ENTER","NEW","POINT","FIRST" }; char** cp[] = { c + 3,c + 2,c + 1,c }; char*** cpp = cp; printf("%s\n", **++cpp); printf("%s\n", *-- * ++cpp + 3); printf("%s\n", *cpp[-2] + 3); printf("%s\n", cpp[-1][-1] + 1); return 0; }
运行结果:
POINT ER ST EW
💡解析
本道题是比较难的一道题目!!!笔试原题
char* c[] = { "ENTER","NEW","POINT","FIRST" };
内存布局:
char** cp[] = { c + 3,c + 2,c + 1,c };
内存布局:
char*** cpp = cp;
图解:
printf("%s\n", **++cpp);
++cpp=cpp+1
在这里插入图片描述
*++cpp第一层解引用,访问到的是c+2
通过c+2,得到的是P的地址
则再次解引用,以%s打印,得到POINT
printf("%s\n", *-- * ++cpp + 3);
再次++cpp
从上面基础上再+1
*++cpp解引用,得到c+1
– * ++cpp:在c+1上减去1,得到c
此时也就不再指向N,而是指向E
*-- * ++cpp:再解引用,得到E的地址
*-- * ++cpp + 3:得到ER
printf("%s\n", *cpp[-2] + 3);
cpp[-2]其实就是*(cpp-2)
*cpp[-2]+3也就是 * *(cpp-2)+3
cpp-2在上面的额基础上减2,cpp不会变
*(cpp-2)通过解引用,得到c+3
**(cpp-2) 再次解引用,得到F
**(cpp-2)+3 ,得到ST
printf("%s\n", cpp[-1][-1] + 1);
cpp[-1][-1]也就是 * (*(cpp-1)-1)
即, * (*(cpp-1)-1)+1
cpp-1图解:
*(cpp-1)解引用,得到c+2,也就是P的地址
*(cpp-1)-1,得到N的地址
*(*(cpp-1)-1)再次解引用,拿到N的值 * (*(cpp-1)-1)+1 ,再加1,得到EW
🚩总结
本节题目来自于公司笔试,前面的题目不算很难,需要我们熟练掌握
后面的题目比较难,需要我们逐个分析
理解完这些题目,相信大家对指针有更深刻理解
😏😏😏😏😏😏😏😏😏😏😏😏😏😏😏😏😏😏😏😏😏😏😏