目录摘要一题目1整形数组的指针运算2结构体的指针运算3二维数组的初始化4指针的类型转换5二维数组的指针运算6字符数组的指针运算17字符数组的指针运算2二总结1指针加减的意义2字符数组 / 字符串存储3运算符优先级4类型转换5逗号表达式摘要本博客讲解7道指针运算笔试题从易到难画图讲解最后总结重点~一题目1整形数组的指针运算#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; }解释(a 1)a得到了整个数组的首地址类型为in(*)[5]所以此时1会跳过整个数组然后把原有的类型in(*)[5]强转为了int*赋给了ptr则ptr仍指向最后一个元素5的后面一个字节~①*(a 1)a是数组名代表首元素1的地址此时1表示2的地址再*解引用得到的就是2②*(ptr - 1)ptr(类型为int*)指向最后一个元素5的后面一个字节此时若直接解引用应该是未知的但是题目进行了-1则倒退4个字节此时指向5的起始地址然后*解引用打印出的就是5注(a 1)得到的是in(*)[5]类型的指针题目为了让ptr能够倒退合适距离去指向到数组中的某个元素所以采取了将in(*)[5]强转为int*类型的手段2结构体的指针运算#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; }解释题目创建了一个结构体指针名为p值为0x100000(16进制)代码中使用0x是为了告诉编译器这是个16进制的数字须知(以指针4字节大小为例)a32 个二进制位 4 字节 8 个十六进制位(每位4bit)所以完整表示一个 32个二进制位只需要 8 个十六进制。而指针本身就是一串32个的二进制位所以为了方便观察指针一般是以16进制的形式打印指针进行观察这样更短一些~b%p的参数为void*其专门用来打印指针类型所以打印出来的16进制位若不足8位一般会前补前导零而%x用来打印unsigned int这种整形类型其不补省略前导零只输出有效数字。所以指针就用%p其他需要观察其16进制形式的整形值就用%x①printf(%p\n, p 0x1);p 0x1其实就是p1也就是让p指针往后跳一步而指针p的类型是struct Test*结构体大小为20字节所以p 1就代表让指针p往后跳20个字节所以只需让p的值0x10000020但注意20是我们10进制的写法20的16进制就是0x14所以p的值变成了0x100014而因为是%p打印所以打印出了前导零00100014②printf(%p\n, (unsigned long)p 0x1);p的类型为struct Test*将其强转为了unsigned long无符号长整型说白了不就是变成了整形值吗然后0x1就是1整形1直接相加即可只不过这里是对1000001得到100001然后此时将整形100001以%p形式打印%p会以16进制的角度看到100001所以会在前面补上前导零变成8位数字00100001③printf(%p\n, (unsigned int*)p 0x1);p的类型为struct Test*将其强转为了unsigned int*无符号整形指针说白了不还是一个指针吗指针0x1就是1则往后跳4个字节即可(unsigned int大小)所以值为0x100004而因为是%p打印所以打印出了前导零得到00100004上述结果用%x打印则不会用前导零~3二维数组的初始化#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; }解释①printf(%d, p[0]);代码中二维数组的初始化是错误的其中使用的是三个小括号而不是大括号{ }所以三个小括号都会变成三个逗号表达式所以此行代码实则效果int a[3][2] { 1,3,5};所以该三行二列的数组只初始化了一半后面均自动补0而a[0]就是第一行的数组名单独的数组名代表首元素的地址所以类型为int*将其赋给了int*类型的p也是合理的故p就是第一行第一个元素的地址p[0]就等同于*p所以得到的当然是1~4二维数组指针的类型转换#include stdio.h //环境是x86环境求程序输出的结果 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; }解释程序规定是x86环境也就是32位环境下此时指针大小一定为4字节a是一个二维数组所以a数组名表示数组首元素地址也就是第一行这个一维数组的整体地址类型为int(*)[5]此时题目让类型为int(*)[4]的p去接收int(*)[5]虽然类型不相同但是不会报错因为地址赋给地址是合理的此时该5行5列的数组就会被p指针按照4列的角度去看待和管理而二维数组其实在内存中就是一行很长的空间所以画图如下①%p打印p[4][2] - a[4][2]指针相减得到的是两个指针之间的元素个数所以红色块的起始地址减去绿色块的起始地址得到的就是-4而以%p的形式打印意味着要用16进制打印-4而-4在内存中是以补码形式存储所以-4的补码和打印出的16进制如下11111111 11111111 11111111 11111100 转换为16进制为--- FFFFFFFC②%d打印p[4][2] - a[4][2]同理得到-4直接%d打印就是-4注此题要求的是x86,32位环境下如果是x64,64位环境下则指针长度为816进制打印-4就会多出4个字节则前缀需要8个16进制全为F5二维数组的指针运算#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; }解释a(aa 1)中的aa会得到整个二维数组的首地址此时1会跳过整个二维数组指向了最后一个元素的下一个字节处然后强转为int*类型再给int*类型的ptr1所以ptr1指向最后一个元素的下一个字节处b(*(aa 1)中的aa代表数组首元素地址也就是第一行这个一维数组整体的地址1得到第二行整体的地址类型为类型是int(*)[5]此时再*解引用就得到了第二行这个一维数组再强转为int*赋给int*类型的ptr2此时ptr2就是第二行的首元素的地址所以这句代码等同于int* ptr2 (int*)(aa[1]));①%d打印*(ptr1 - 1)ptr1指向最后一个元素的下一个字节处类型为int*所以此时-1会往回走4个字节指向二维数组的最后一个元素10此时*解引用就是10%d打印10②%d打印*(ptr2 - 1)ptr2是第二行的首元素的地址类型为int*此时-1就会往回走4个字节指向第一行最后一个元素5此时*解引用就是5%打印56字符数组的指针运算1#include stdio.h int main() { char* a[] { work,at,alibaba }; char** pa a; pa; printf(%s\n, *pa); return 0; }解释此题根据前两行代码可得下图此时pa也就是pa1pa的类型是char**所以pa跳过char*大小个字节也就是从指向a数组的第一个元素变成了指向第二个元素如下①%s打印*pa此时pa的内容是a数组的第二个元素的地址*pa则得到a数组的第二个元素的内容该内容就是字符串at中首元素a的地址此时再以%s打印会直接打印出at这个字符串注%s打印只需一个char*也及时只需告诉它起点他会打印到\0前的所有字符7字符数组的指针运算2#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; }解释由题目可得下图①printf(%s\n, **cpp);第一步cpp前置会影响到cpp的值cpp现在指向cp数组的第一个元素此时cppcpp就会指向cp数组的第二个元素cp[1]所以此时cpp存储的就是cp[1]的地址第二步*cpp此时第一次*解引用就获得了cp[1]也就是cp数组的第二个元素的内容内容就是c2也就是c数组中第三个元素的地址第三步 **cpp此时再*解引用就直接得到了c的第三个元素也就是POINT的首字符P的地址最后%s打印直接打印出完整的POINT而经过①的操作之后呢因为cpp改变了所以我们图也变了②printf(%s\n, *-- * cpp 3);第一步cppcpp 经过第①个printf之后此时已经指向了 cp[1]此时再cpp让 cpp 指向 cp[2]即 cp 的第三个元素此时 cpp 保存的是 cp[2] 的地址第二步* cpp相当于对 cp[2] 解引用*解引用后就得到 cp[2] 的内容cp[2] 的内容就是 c 1c1就是c数组的第二个元素的地址第三步-- * cpp前置--是作用在 c 1上(c数组的第二个元素的地址)所以c 1 是 char** 类型指针指向 c[1]-- 让它回退一个 sizeof(char*) → 变成 c变成指向c[0]的指针并且此时 cp[2] 被修改为 c第四步*-- * cpp得到指向c[0]的指针之后此时再*解引用就得到了c[0]的内容就是ENTER中第一个E的地址第五步*-- * cpp 3然后3我们就会得到ENTER中第二个E的地址此时再%s打印打印出了“ER”而经过②的操作之后呢我们图变成如下③printf(%s\n, *cpp[-2] 3);第一步cpp[-2]cpp[-2]等同于*(cpp-2)素以-2会让cpp此时指向cp[0]存储的就是cp[0]的地址然后*解引用得到了cp[0]内容为c3也就是说cp的第一个元素cp[0]存储的内容是c的第四个元素的地址第二步*cpp[-2]再*解引用得到了就得到了c的第四个元素也就是FIRST的F的地址第三步*cpp[-2] 33得到了FIRST的S的地址此时%s打印打印出了ST④printf(%s\n, cpp[-1][-1] 1);再第③个printf中没有让其中的值改变所以我们的图依然为第一步cpp[-1]cpp[-1]等同于*(cpp-1)所以直接得到了cp[1]的内容内容为c2也就是c的第三个元素的地址第二步cpp[-1][-1]之前得到了cp[1]的内容内容为c的第三个元素的地址此时再进行[-1]也就是*(c的第三个元素的地址-1)所以先得到了c的第二个元素的地址然后*解引用得到了c的第二个元素内容也就是NEW中N的地址第三步cpp[-1][-1] 11所以得到了NEW中E的地址此时%s打印出了EW二总结1指针加减的意义指针 n跳过n × sizeof(指向的类型)个字节指针 - n回退n × sizeof(指向的类型)个字节指针 - 指针得到两个指针(起始地址)之间的元素个数(不是字节数)结果为有符号整数数组名 1跳过整个数组数组名除 和 sizeof 外代表首元素地址类型为元素类型*2字符数组 / 字符串存储字符串常量如ENTER存储在内存中以\0结尾char*指针接收字符串时指针存的是字符串首字符的地址%s打印时从给定地址开始一直打印到遇到\0为止3运算符优先级*p先p再*解引用*p先*p再p-- *p先*p取值再对值进行--4类型转换(int*)(a 1)将跳过整个数组后的地址强转实现指针倒退访问最后一个元素(unsigned long)p 1将指针转成整数1 就是纯数值加1不再是指针跳步不同指针类型 1跳过的字节数 sizeof(该指针指向的类型)5逗号表达式int a[3][2] { (0,1), (2,3), (4,5) };实际上等于{ 1, 3, 5 }因为括号内是逗号表达式取最后一个值 [ 作者 ] shylyly [ 首次发布 ] 2024.3.11❌ [ 最新修改 ] 2026.10.13 [ 声明 ] 由于笔者水平有限文中难免有疏漏或不妥之处还望读者不吝赐教。
阅读完成 · 觉得有帮助?