题目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;
}
分析:本题考察数组指针运算和指针类型转换的理解。
1. 代码解析
数组 a 的定义:
int a[5] = { 1, 2, 3, 4, 5 };
- 创建了一个包含5个整数的数组
- 内存布局:a[0]=1, a[1]=2, a[2]=3, a[3]=4, a[4]=5
关键语句分析:
int* ptr = (int*)(&a + 1);
- &a:获取整个数组的地址,类型是 int(*)[5](指向包含5个int的数组的指针)
- &a + 1:指针算术运算,移动 1 * sizeof(int[5]) = 1 * (5 * sizeof(int)) = 20 字节(假设int为4字节)
- (int*):将 int(*)[5] 类型强制转换为 int* 类型
- 结果:ptr 指向数组 a 末尾之后的位置(即 a[5] 的地址,虽然这个元素不存在)
2. 输出语句分析
printf("%d,%d", *(a + 1), *(ptr – 1));
第一个输出:*(a + 1)
- a 是数组名,在大多数上下文中会退化为指向首元素的指针(int* 类型)
- a + 1:指针加1,移动 1 * sizeof(int) = 4 字节
- *(a + 1):解引用,获取 a[1] 的值
- 结果:2
第二个输出:*(ptr – 1)
- ptr 是 int* 类型,指向 a[5](数组末尾之后)
- ptr – 1:指针减1,向后移动 1 * sizeof(int) = 4 字节
- *(ptr – 1):解引用,获取 a[4] 的值
- 结果:5
3. 内存布局示意图
地址 内容 说明
0x1000 a[0]=1 数组起始位置
0x1004 a[1]=2 *(a + 1) 指向这里
0x1008 a[2]=3
0x100C a[3]=4
0x1010 a[4]=5 *(ptr – 1) 指向这里
0x1014 (越界) ptr 指向这里 (&a + 1 的结果)
4. 关键知识点总结
数组名与指针的关系:
- a 作为数组名,在大多数表达式中退化为指向首元素的指针(&a[0])
- &a 获取的是整个数组的地址,类型不同(int(*)[5])
指针算术运算的尺度:
- a + 1:移动 sizeof(int) 字节
- &a + 1:移动 sizeof(int[5]) 字节
- 指针加减整数的移动距离 = 整数 × 指针所指向类型的大小
类型转换的作用:
- (int*)(&a + 1) 将数组指针转换为普通整型指针
- 转换后,指针算术运算的尺度变为 sizeof(int)
数组越界访问:
- ptr 指向数组末尾之后是合法的指针运算
- ptr – 1 又回到数组范围内,访问 a[4] 是合法的
5. 预期输出结果
2,5
题目2
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;
}
分析:本题考察结构体指针运算和不同类型指针的算术运算规则。
1. 结构体大小计算
首先需要计算 struct Test 的大小(在32位系统下):
- int Num:4字节
- char *pcName:指针,4字节
- short sDate:2字节
- char cha[2]:2字节
- short sBa[4]:8字节
总大小 = 4 + 4 + 2 + 2 + 8 = 20字节
2. 三个输出语句分析
第一个输出:printf("%p\\n", p + 0x1);
- p 是 struct Test* 类型指针,指向地址 0x100000
- 指针加整数 0x1 表示移动 1 * sizeof(struct Test) 个字节
- sizeof(struct Test) = 20 字节
- 所以 p + 0x1 = 0x100000 + 20 = 0x100014
- 输出:0x100014
第二个输出:printf("%p\\n", (unsigned long)p + 0x1);
- (unsigned long)p 将指针转换为无符号长整型
- 整型加法是直接的数值加法
- 0x100000 + 0x1 = 0x100001
- 输出:0x100001
第三个输出:printf("%p\\n", (unsigned int*)p + 0x1);
- (unsigned int*)p 将指针转换为 unsigned int* 类型
- sizeof(unsigned int) = 4 字节(32位系统)
- 指针加整数 0x1 表示移动 1 * 4 = 4 字节
- 所以 (unsigned int*)p + 0x1 = 0x100000 + 4 = 0x100004
- 输出:0x100004
3. 关键知识点总结
- 保持指针类型时,算术运算按指针类型的大小进行
- 转换为整型时,进行直接的数值运算
- 转换为其他指针类型时,按新指针类型的大小进行运算
4. 预期输出结果
0x100014
0x100001
0x100004
5. 验证方法
可以在实际编译环境中运行验证,注意编译时可能需要指定32位模式(如 -m32 选项)以确保结构体大小与预期一致。
题目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;
}
分析:本题考察二维数组的初始化、逗号运算符的使用以及数组与指针的关系。
1. 代码解析
二维数组 a 的定义:
int a[3][2] = { (0, 1), (2, 3), (4, 5) };
这里有一个关键陷阱:初始化列表中使用的是圆括号 () 而不是花括号 {}。
- 在C语言中,(0, 1) 是逗号表达式,其值为最后一个表达式的值,即 1
- 同理,(2, 3) 的值为 3,(4, 5) 的值为 5
- 因此,实际初始化等价于:int a[3][2] = { 1, 3, 5 };
正确的二维数组初始化应该是:
int a[3][2] = { {0, 1}, {2, 3}, {4, 5} };
内存布局分析:
- a 是一个3行2列的二维数组,共6个int元素
- 按行优先顺序存储:a[0][0], a[0][1], a[1][0], a[1][1], a[2][0], a[2][1]
- 由于逗号表达式,实际初始化值为:1, 3, 5, 0, 0, 0(剩余元素自动初始化为0)
指针操作:
int *p;
p = a[0];
- a[0] 是二维数组第一行的首地址,类型为 int[2],但在赋值给 int* 时会退化为指向 a[0][0] 的指针
- p 指向 a[0][0],即数组的第一个元素
2. 输出语句分析
printf("%d", p[0]);
- p[0] 等价于 *(p + 0),即访问 p 指向的第一个元素
- 由于 p 指向 a[0][0],而 a[0][0] 被初始化为 1(来自 (0, 1) 逗号表达式的结果)
- 因此输出:1
3. 内存布局示意图
地址 内容 说明
0x1000 a[0][0]=1 (0,1)逗号表达式的结果
0x1004 a[0][1]=3 (2,3)逗号表达式的结果
0x1008 a[1][0]=5 (4,5)逗号表达式的结果
0x100C a[1][1]=0 自动初始化为0
0x1010 a[2][0]=0 自动初始化为0
0x1014 a[2][1]=0 自动初始化为0
↑
p指向这里 (a[0][0])
4. 关键知识点总结
逗号表达式:
- 格式:(表达式1, 表达式2, …, 表达式n)
- 值:最后一个表达式的值
- 本题中 (0, 1) 的值为 1,(2, 3) 的值为 3,(4, 5) 的值为 5
二维数组初始化:
- 正确语法:int a[3][2] = { {0, 1}, {2, 3}, {4, 5} };
- 错误语法(本题):使用圆括号导致逗号表达式
- 部分初始化:剩余元素自动初始化为0
数组与指针关系:
- a[0] 表示二维数组的第一行,类型为 int[2]
- 在赋值给 int* 时,a[0] 退化为指向 a[0][0] 的指针
- p = a[0] 等价于 p = &a[0][0]
数组下标访问:
- p[0] 等价于 *(p + 0),访问 p 指向的第一个元素
- 对于二维数组,a[i][j] 在内存中是连续存储的
5. 预期输出结果
1
题目4
#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;
}
分析:本题考察二维数组指针、指针类型转换以及指针算术运算的复杂应用。
1. 代码解析
数组定义:
int a[5][5];
- 定义一个5行5列的二维整型数组,共25个int元素
- 内存中按行优先顺序连续存储
指针定义:
int(*p)[4];
- p 是一个指向包含4个int的数组的指针(int(*)[4]类型)
- 注意:p指向的数组大小是4,而a是5×5的数组
指针赋值:
p = a;
- 将二维数组a的地址赋给指针p
- 这里存在类型不匹配:a的类型是int(*)[5],而p的类型是int(*)[4]
- 但C语言允许这种赋值(有警告),p现在指向a[0][0]
2. 输出语句分析
printf( "%p,%d\\n", &p[4][2] – &a[4][2], &p[4][2] – &a[4][2]);
关键计算:&p[4][2] – &a[4][2]
&a[4][2] 的计算:
- a[4][2] 是数组a的第5行第3列元素(0-based索引)
- 在内存中的偏移量:4 * 5 * sizeof(int) + 2 * sizeof(int) = 22 * sizeof(int)
- 假设int为4字节,则偏移量为88字节
&p[4][2] 的计算:
- p 是int(*)[4]类型,p[4]表示第5个包含4个int的数组
- p[4][2] 是p[4]这个数组的第3个元素
- 在内存中的偏移量:4 * 4 * sizeof(int) + 2 * sizeof(int) = 18 * sizeof(int)
- 假设int为4字节,则偏移量为72字节
指针减法:
- &p[4][2] – &a[4][2] 计算两个指针之间的元素个数差
- 由于两个指针都指向int类型,相减的结果是它们之间相差的int元素个数
- 地址差:72 – 88 = -16字节
- 元素个数差:-16 / sizeof(int) = -4(假设int为4字节)
输出格式:
- 第一个%p:以指针格式输出地址差(-4转换为无符号指针值)
- 第二个%d:以整数格式输出元素个数差(-4)
3. 预期实验结果
0xfffffffc,-4
或(取决于平台和编译器):
FFFFFFFFFFFFFFFC,-4
注意:%p输出的具体格式可能因平台而异,但表示的地址差值是相同的。负数的指针表示通常使用补码形式。
题目5
#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;
}
分析:本题考察二维数组的指针运算、数组名退化规则以及指针类型转换的综合理解。
1. 代码解析
二维数组 aa 的定义:
int aa[2][5] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
- 创建一个2行5列的二维整型数组,共10个元素
- 内存布局:按行优先顺序存储
- 第0行:aa[0][0]=1, aa[0][1]=2, aa[0][2]=3, aa[0][3]=4, aa[0][4]=5
- 第1行:aa[1][0]=6, aa[1][1]=7, aa[1][2]=8, aa[1][3]=9, aa[1][4]=10
指针 ptr1 的定义:
int *ptr1 = (int *)(&aa + 1);
- &aa:获取整个二维数组的地址,类型为 int(*)[2][5](指向2×5二维数组的指针)
- &aa + 1:指针算术运算,移动 1 * sizeof(int[2][5]) = 1 * (2×5×sizeof(int)) = 40 字节(假设int为4字节)
- (int*):将 int(*)[2][5] 类型强制转换为 int* 类型
- 结果:ptr1 指向数组 aa 末尾之后的位置
指针 ptr2 的定义:
int *ptr2 = (int *)(*(aa + 1));
- aa 是二维数组名,在表达式中退化为指向第一行的指针,类型为 int(*)[5]
- aa + 1:指针加1,移动 1 * sizeof(int[5]) = 1 * (5×sizeof(int)) = 20 字节
- *(aa + 1):解引用,获取第二行的首地址,类型为 int[5],但会退化为 int*
- (int*):显式转换为 int* 类型(实际上已经是 int*,转换是多余的但合法)
- 结果:ptr2 指向 aa[1][0],即第二行的第一个元素
2. 输出语句分析
printf( "%d,%d", *(ptr1 – 1), *(ptr2 – 1));
第一个输出:*(ptr1 – 1)
- ptr1 是 int* 类型,指向数组 aa 末尾之后(即 aa[2][0] 的位置,虽然这个元素不存在)
- ptr1 – 1:指针减1,向后移动 1 * sizeof(int) = 4 字节
- *(ptr1 – 1):解引用,获取 aa[1][4] 的值
- 计算:ptr1 指向 aa 末尾(偏移40字节),ptr1 – 1 指向 aa[1][4](偏移36字节)
- 结果:10(aa[1][4] 的值)
第二个输出:*(ptr2 – 1)
- ptr2 是 int* 类型,指向 aa[1][0](第二行第一个元素)
- ptr2 – 1:指针减1,向后移动 1 * sizeof(int) = 4 字节
- *(ptr2 – 1):解引用,获取 aa[0][4] 的值
- 计算:ptr2 指向 aa[1][0](偏移20字节),ptr2 – 1 指向 aa[0][4](偏移16字节)
- 结果:5(aa[0][4] 的值)
3. 内存布局示意图
地址 内容 说明
0x1000 aa[0][0]=1 数组起始位置
0x1004 aa[0][1]=2
0x1008 aa[0][2]=3
0x100C aa[0][3]=4
0x1010 aa[0][4]=5 *(ptr2 – 1) 指向这里
0x1014 aa[1][0]=6 ptr2 指向这里 (*(aa + 1))
0x1018 aa[1][1]=7
0x101C aa[1][2]=8
0x1020 aa[1][3]=9
0x1024 aa[1][4]=10 *(ptr1 – 1) 指向这里
0x1028 (越界) ptr1 指向这里 (&aa + 1 的结果)
4. 关键知识点总结
二维数组的地址运算:
- &aa:获取整个二维数组的地址,类型为 int(*)[2][5]
- aa:退化为指向第一行的指针,类型为 int(*)[5]
- aa[0] 或 *aa:指向第一行第一个元素的指针,类型为 int*
指针算术运算的尺度:
- &aa + 1:移动 sizeof(int[2][5]) = 40 字节
- aa + 1:移动 sizeof(int[5]) = 20 字节
- ptr1 – 1 或 ptr2 – 1:移动 sizeof(int) = 4 字节
数组名退化规则:
- 在大多数表达式中,数组名会退化为指向其首元素的指针
- 对于二维数组 aa,在 aa + 1 中退化为 int(*)[5]
- 在 *(aa + 1) 中,得到的是 int[5] 类型,但会进一步退化为 int*
类型转换的作用:
- (int*)(&aa + 1) 将二维数组指针转换为普通整型指针
- 转换后,指针算术运算的尺度从40字节变为4字节
5. 预期输出结果
10,5
题目6
#include <stdio.h>
int main()
{
char *a[] = {"work","at","alibaba"};
char**pa = a;
pa++;
printf("%s\\n", *pa);
return 0;
}
分析:本题考察指针数组、二级指针的声明与操作,以及指针算术运算在字符指针数组中的应用。
1. 代码解析
指针数组 a 的定义:
char *a[] = {"work","at","alibaba"};
- 定义了一个包含3个元素的指针数组 a
- 每个元素都是 char* 类型,指向一个字符串常量
- 内存布局:
- a[0] 指向字符串 "work" 的首字符
- a[1] 指向字符串 "at" 的首字符
- a[2] 指向字符串 "alibaba" 的首字符
- 数组 a 本身在栈上,包含3个指针(每个指针大小通常为4或8字节,取决于系统)
二级指针 pa 的定义:
char**pa = a;
- pa 是一个指向 char* 的指针(即二级指针)
- a 作为数组名,在大多数表达式中退化为指向其首元素的指针
- 首元素 a[0] 是 char* 类型,所以 &a[0] 是 char** 类型
- 因此 pa 指向 a[0],即指向指针数组的第一个元素
指针运算:
pa++;
- pa 是 char** 类型,指向 char* 类型的指针
- pa++ 使 pa 向前移动 sizeof(char*) 个字节
- 在32位系统中,sizeof(char*) = 4 字节
- 在64位系统中,sizeof(char*) = 8 字节
- 执行后,pa 指向 a[1](指针数组的第二个元素)
2. 输出语句分析
printf("%s\\n", *pa);
表达式解析:
- pa 现在指向 a[1](执行 pa++ 后)
- *pa 解引用一次,得到 a[1] 的值,即指向字符串 "at" 的 char* 指针
- printf 使用 %s 格式说明符,会从 *pa 指向的地址开始打印字符,直到遇到空字符 '\\0'
内存访问过程:
结果: 输出字符串 "at"
3. 内存布局示意图
栈内存(指针数组 a):
地址 内容 指向的字符串常量(只读区)
0x1000 a[0]=0x2000 → "work\\0"
0x1008 a[1]=0x2005 → "at\\0" ← pa 指向这里(执行 pa++ 后)
0x1010 a[2]=0x2008 → "alibaba\\0"
只读数据区:
0x2000 'w','o','r','k','\\0' // "work"
0x2005 'a','t','\\0' // "at"
0x2008 'a','l','i','b','a','b','a','\\0' // "alibaba"
指针关系:
初始:pa → a[0] (0x1000)
pa++ 后:pa → a[1] (0x1008)
*pa = a[1] = 0x2005 → "at"
4. 关键知识点总结
指针数组:
- char *a[] 声明一个指针数组,每个元素都是 char* 类型
- 常用于存储多个字符串,每个字符串可以有不同的长度
- 数组元素在内存中连续存储,但指向的字符串不一定连续
二级指针:
- char** pa 声明一个指向 char* 的指针
- 可以指向指针数组的首元素,用于遍历指针数组
- 对二级指针解引用一次得到一级指针,解引用两次得到字符
数组名退化规则:
- 在大多数表达式中,数组名会退化为指向其首元素的指针
- a 退化为 &a[0],类型为 char**
- 因此可以直接赋值给 char** pa
指针算术运算:
- 指针加减整数的移动距离 = 整数 × 指针所指向类型的大小
- pa++ 移动 sizeof(char*) 字节,指向下一个指针元素
- 与普通数组指针运算类似,但这里操作的是指针数组
字符串常量:
- 字符串常量如 "work"、"at" 存储在只读数据区
- 指针数组元素存储的是这些字符串常量的地址
- 通过指针可以访问但不能修改字符串常量内容
5. 预期输出结果
at
题目7
#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;
}
分析:本题考察多级指针、指针数组的复杂运算,以及前置自增/自减运算符与指针解引用的优先级组合。
1. 代码解析
指针数组 c 的定义:
char *c[] = {"ENTER","NEW","POINT","FIRST"};
- 定义了一个包含4个元素的指针数组 c
- 每个元素都是 char* 类型,指向一个字符串常量
- 内存布局:
- c[0] 指向字符串 "ENTER" 的首字符
- c[1] 指向字符串 "NEW" 的首字符
- c[2] 指向字符串 "POINT" 的首字符
- c[3] 指向字符串 "FIRST" 的首字符
指针数组 cp 的定义:
char**cp[] = {c+3,c+2,c+1,c};
- 定义了一个包含4个元素的指针数组 cp,每个元素是 char** 类型(二级指针)
- 初始化:
- cp[0] = c + 3 → 指向 c[3](即指向 "FIRST" 的指针)
- cp[1] = c + 2 → 指向 c[2](即指向 "POINT" 的指针)
- cp[2] = c + 1 → 指向 c[1](即指向 "NEW" 的指针)
- cp[3] = c → 指向 c[0](即指向 "ENTER" 的指针)
三级指针 cpp 的定义:
char***cpp = cp;
- cpp 是一个三级指针,指向 cp 数组的首元素
- cp 作为数组名退化为指向其首元素的指针,类型为 char***
- 初始时 cpp 指向 cp[0]
2. 输出语句分析
第一个输出:printf("%s\\n", **++cpp);
- ++cpp:前置自增,cpp 先自增,指向 cp[1]
- *++cpp:解引用一次,得到 cp[1] 的值,即 c + 2(指向 c[2] 的指针)
- **++cpp:解引用两次,得到 *(c + 2),即 c[2] 的值,指向字符串 "POINT"
- 输出:POINT
第二个输出:printf("%s\\n", *–*++cpp+3);
此时 cpp 指向 cp[1]。
- ++cpp:前置自增,cpp 先自增,指向 cp[2]
- *++cpp:解引用一次,得到 cp[2] 的值,即 c + 1(指向 c[1] 的指针)
- –*++cpp:对 c + 1 进行前置自减,得到 c(指向 c[0] 的指针)
- 注意:– 作用于 cp[2] 这个元素本身,即 cp[2] 的值从 c + 1 变为 c
- *–*++cpp:解引用,得到 *c,即 c[0] 的值,指向字符串 "ENTER"
- *–*++cpp+3:指针偏移3个字符,从 "ENTER" 的第3个字符开始(0-based索引)
- "ENTER" 的字符:'E'(0), 'N'(1), 'T'(2), 'E'(3), 'R'(4)
- 从索引3开始:"ER"
- 输出:ER
第三个输出:printf("%s\\n", *cpp[-2]+3);
此时 cpp 指向 cp[2]。
- cpp[-2]:等价于 *(cpp – 2),即 cp[0] 的值,也就是 c + 3
- *cpp[-2]:解引用,得到 *(c + 3),即 c[3] 的值,指向字符串 "FIRST"
- *cpp[-2]+3:指针偏移3个字符,从 "FIRST" 的第3个字符开始
- "FIRST" 的字符:'F'(0), 'I'(1), 'R'(2), 'S'(3), 'T'(4)
- 从索引3开始:"ST"
- 输出:ST
第四个输出:printf("%s\\n", cpp[-1][-1]+1);
此时 cpp 指向 cp[2]。
- cpp[-1]:等价于 *(cpp – 1),即 cp[1] 的值
- 注意:在第二个输出中,cp[2] 被修改为 c,但 cp[1] 未被修改,仍为 c + 2
- cpp[-1][-1]:等价于 *(*(cpp – 1) – 1),即 *(cp[1] – 1)
- cp[1] 是 c + 2,cp[1] – 1 是 c + 1
- *(c + 1) 即 c[1],指向字符串 "NEW"
- cpp[-1][-1]+1:指针偏移1个字符,从 "NEW" 的第1个字符开始
- "NEW" 的字符:'N'(0), 'E'(1), 'W'(2)
- 从索引1开始:"EW"
- 输出:EW
3. 内存布局与指针变化示意图
初始状态:
指针数组 c(存储字符串地址):
c[0] → "ENTER"
c[1] → "NEW"
c[2] → "POINT"
c[3] → "FIRST"
指针数组 cp(存储指向 c 元素的指针):
cp[0] = c + 3 → 指向 c[3]
cp[1] = c + 2 → 指向 c[2]
cp[2] = c + 1 → 指向 c[1]
cp[3] = c → 指向 c[0]
三级指针 cpp:
cpp → cp[0](初始状态)
执行过程中的指针变化:
第一个 printf 前:++cpp → cpp → cp[1]
第二个 printf 前:++cpp → cpp → cp[2],且 cp[2] 被修改为 c(原为 c+1)
第三个 printf 时:cpp 仍指向 cp[2](cpp[-2] 访问 cp[0])
第四个 printf 时:cpp 仍指向 cp[2](cpp[-1] 访问 cp[1])
4. 关键知识点总结
多级指针:
- char *c[]:指针数组,元素为 char*
- char **cp[]:指针数组,元素为 char**
- char ***cpp:三级指针,指向 char**
前置自增/自减运算符:
- ++cpp:先自增再使用,改变 cpp 本身的指向
- –*++cpp:先自增 cpp,再解引用,再对解引用的值自减
- 自增/自减会永久改变指针变量的值
指针数组的初始化:
- cp 数组的元素初始化为指向 c 中不同元素的指针
- c + 3 指向 c[3],c + 2 指向 c[2],以此类推
数组下标与指针运算的等价性:
- cpp[-2] 等价于 *(cpp – 2)
- cpp[-1][-1] 等价于 *(*(cpp – 1) – 1)
- 负下标在指针运算中是合法的,只要结果指向有效的内存区域
运算符优先级:
- 前置 ++/– 优先级高于 *(解引用)
- [](下标)优先级高于 +(加法)
- *cpp[-2]+3 等价于 (*(cpp[-2])) + 3
副作用与顺序:
- 每个 printf 语句中的 ++cpp 会永久改变 cpp 的指向
- 后续的 printf 基于前一个 printf 执行后的 cpp 状态
- 本题中 cp[2] 的值在第二个 printf 中被修改为 c
5. 预期输出结果
POINT
ER
ST
EW



