指针运算拾遗 ||

指针运算深入解析

指针运算拾遗 ||

一、题目
1、*++p;
2、++*p;
3、*p++;
4、(*p)++;
5、++*++p;
6、++*p++;

二、分析
1、*++p; 相当于 *(++p);
2、++*p; 相当于 ++(*p); 即 *p = *p + 1;
3、*p++; 相当于 *(p++);
4、(*p)++; 就是本身的意思, 注意区分 *p++; 与 (*p)++;
5、++*++p; 相当于 ++(*(++p));
6、++*p++; 相当于 ++(*(p++));

三、验证代码

	int a[] = { 10, 20, 30, 40, 50 };
	int n = sizeof(a) / sizeof(a[0]);
	int *p = a;
	cout << *p++ << "\t" << *p << endl;  // *p++  : 即 *(p++), 相当于两步操作: 第一步: p++; 第二步: *p;
	disp(a, n);
	int *p1 = a;
	cout << ++*p1 << "\t" << *p1 << endl; // ++*p1 : 即 ++(*p1), 即: *p1 = (*p1) + 1;
	disp(a, n);

	int b[] = { 10, 20, 30, 40, 50 };
	int n2 = sizeof(b) / sizeof(b[0]);
	int *p2 = b;
	cout << (*p2)++ << "\t" << *p2 << endl;
	disp(b, n2);

	int c[] = { 10, 20, 30, 40, 50 };
	int n3 = sizeof(c) / sizeof(c[0]);
	int *p3 = c;
	cout << ++*++p3 << "\t" << *p3 << endl;//21 21
	//++*++p3:  ++(*(++p3)) 
	//(1):++p3,p3指向了a[1], 
	//(2):*(++p3),取a[1]中的值,20
	//(3):++(*(++p3)),对a[1]中的值进行自加1,21
	disp(c, n3);

	int d[] = { 10, 20, 30, 40, 50 };
	int n4 = sizeof(d) / sizeof(d[0]);
	int *p4 = d;
	cout << ++*p4++ << "\t" << *p4 << endl;//11 20
	// ++*p4++ : ++(*(p4++))
	//(1) p4++,该表达式结束后,指针p4指向a[1](数组a当前位置的下一个位置),在该表达式中,p4仍指向当前位置a[0]
	//(2) *(p4++),取a[0]中的值,10
	//(3) ++(*(p4++)),对a[0]中的值进行自加1操作,11
	//到第二个 *p4, 取p4指针所指地址中的值时,p4就指向了a[1], *p4,就是取a[1]中的值,20
	disp(d, n4);
}
/*
10      20
10      20      30      40      50
11      11
11      20      30      40      50
10      11
11      20      30      40      50
21      21
10      21      30      40      50
11      20
11      20      30      40      50
请按任意键继续. . .
*/
<think>我们正在讨论指针运算,用户之前的问题是关于C语言中一维和二维数组解引用的含义,现在用户进一步询问如何理解指针运算的含义。根据引用[1][2][3],我们可以将指针运算分为几个部分:指针与整数的加减运算指针之间的减法运算指针的关系运算。同时,我们需要注意指针运算的步长问题(引用[2])以及const修饰指针(引用[3])。 以下是详细解答: ### 1. 指针±整数(指针的加减运算指针加减整数并不是简单的地址值加减,而是根据指针指向的数据类型大小进行调整。具体公式为: $$ \text{新地址} = \text{原地址} \pm n \times \text{sizeof(类型)} $$ 其中,$n$是整数。 **示例**(参考引用[1]和[2]): ```c int arr[5] = {10, 20, 30, 40, 50}; int *p = arr; // p指向arr[0]的地址 // 指针加整数 int *p1 = p + 1; // 实际地址增加 sizeof(int) 个字节(通常是4) printf("%d\n", *p1); // 输出20 // 指针减整数 int *p2 = p1 - 1; // 回到p的位置 printf("%d\n", *p2); // 输出10 ``` 注意事项: - 对`void*`指针进行加减运算是非法的,因为无法确定步长(引用[2])。 - 指针加减整数常用于遍历数组。 ### 2. 指针-指针指针的减法运算) 两个指针相减的结果是它们之间相差的元素个数(不是字节数),前提是这两个指针指向同一个数组(或数组末尾之后的一个位置)。计算公式为: $$ \text{差值} = \frac{\text{地址差}}{\text{sizeof(类型)}} $$ **示例**(参考引用[1]和[3]): ```c int arr[5] = {0}; int *p1 = &arr[0]; int *p2 = &arr[3]; ptrdiff_t diff = p2 - p1; // 结果为3,因为p2指向arr[3],p1指向arr[0] printf("%td\n", diff); ``` 注意事项: - 指针减法结果类型为`ptrdiff_t`(定义在`stddef.h`中)。 - 如果两个指针不指向同一个数组,则行为未定义。 ### 3. 指针的关系运算 指针可以使用关系运算符(如`<`, `<=`, `>`, `>=`)进行比较,比较的是地址值的大小。通常用于判断两个指针在数组中的相对位置(参考引用[3])。 **示例**: ```c int arr[5] = {0}; int *p1 = &arr[0]; int *p2 = &arr[3]; if (p1 < p2) { printf("p1指向的元素在p2之前\n"); } ``` 注意事项: - 只有指向同一个数组(或数组末尾之后)的指针之间的比较才有意义。 - 标准允许指向数组最后一个元素后面的那个地址的指针参与比较(但不能解引用)。 ### 4. 指针运算与数组遍历 利用指针加减整数可以高效遍历数组(参考引用[3]): ```c int arr[5] = {1, 2, 3, 4, 5}; int *p = arr; for (int i = 0; i < 5; i++) { printf("%d ", *(p + i)); // 等价于arr[i] } ``` ### 5. const修饰指针 虽然用户主要询问指针运算,但引用[3]提到了const修饰指针,这里简要说明: - `const int *p`: 指向常量的指针,不能通过p修改指向的值(但p可以指向其他地址)。 - `int *const p`: 指针常量,p的指向不能改变(但可以通过p修改指向的值)。 - `const int *const p`: 指向常量的指针常量,都不能改变。 **示例**: ```c int a = 10, b = 20; const int *p1 = &a; // *p1 = 30; // 错误:不能修改 p1 = &b; // 正确 int *const p2 = &a; *p2 = 30; // 正确 // p2 = &b; // 错误 ``` --- ### 相关问题 1. 为什么指针加减整数需要根据数据类型调整步长? 2. 如何用指针减法计算数组的长度? 3. 指针关系运算在什么情况下会产生未定义行为? 4. `const`修饰指针时,如何区分顶层`const`和底层`const`? </think>### 如何理解C语言中的指针运算 指针运算是C语言的核心特性之一,主要包含三类操作:**指针±整数****指针-指针****指针关系运算**。理解这些运算需要结合内存布局和数据类型的底层机制[^1][^2][^3]。 --- #### 一、指针±整数(地址偏移) 指针加减整数不是简单的算术运算,而是基于**数据类型大小**的地址偏移。公式为: $$ \text{新地址} = \text{原地址} \pm n \times \text{sizeof(数据类型)} $$ **示例分析**(引用[2]): ```c double b; double *dp = &b; // 假设dp=0x1000 dp + 1 = 0x1008 // 偏移 sizeof(double)=8字节 dp + 2 = 0x1010 // 偏移 2×8=16字节 ``` 编译器实际将其转换为: ```c (double*)( (int)dp + 2 * sizeof(double) ) // 显式计算地址偏移 ``` **应用场景**: 遍历数组(等价于`arr[i]`) ```c int arr[5] = {10,20,30,40,50}; for(int *p = arr; p < arr+5; p++) { printf("%d ", *p); // 输出:10 20 30 40 50 } ``` > 每次`p++`使地址增加`sizeof(int)`(通常4字节)[^1]。 --- #### 二、指针-指针(元素距离) 两个指针相减的结果是它们之间**间隔的元素个数**,而非字节数: $$ \text{差值} = \frac{\text{地址差}}{\text{sizeof(数据类型)}} $$ **示例**: ```c int arr[5] = {0}; int *p1 = &arr[0]; // 地址0x1000 int *p2 = &arr[3]; // 地址0x100C int diff = p2 - p1; // 结果=3 ``` 编译器转换过程: ```c ((int)p2 - (int)p1) / sizeof(int) // (0x100C-0x1000)/4=3 ``` **限制**: 只能用于**同一个数组内**指针(否则行为未定义)[^3]。 --- #### 三、指针关系运算(地址比较) 通过运算符(`<`, `>`, `==`等)比较指针**内存地址值**: ```c int arr[5]; int *p1 = &arr[1]; int *p2 = &arr[3]; if(p1 < p2) // 真(地址值比较) if(p1 == &arr[1]) // 真 ``` **规则**: 1. 仅当指针指向**同一数组**时比较结果有意义 2. 允许指向数组末尾后的第一个地址(如`arr+5`) 3. `void*`指针不可比较(无法确定步长)[^2] --- #### 四、特殊限制与陷阱 1. **`void*`指针** 禁止直接进行算术运算(编译器无法确定步长): ```c void *vp = malloc(100); // vp + 1; // 编译错误! ``` 2. **`const`修饰指针**(引用[3]) - `const int *p`:指向常量(值不可改) - `int *const p`:指针常量(地址不可改) ```c const int *p1 = &a; // *p1=10; // 错误! int *const p2 = &b; // p2=&c; // 错误! ``` --- ### 关键总结 | 运算类型 | 本质 | 公式 | 适用场景 | |----------------|-----------------------|-------------------------------|------------------| | 指针±整数 | 地址按类型步长偏移 | $p \pm n \times \text{sizeof(T)}$ | 数组遍历 | | 指针-指针 | 计算元素间隔数 | $\dfrac{p_2 - p_1}{\text{sizeof(T)}}$ | 同数组内位置差 | | 指针关系运算 | 内存地址值比较 | $p_1 \ \text{op} \ p_2$ | 同数组内顺序判断 | > 所有指针运算的核心在于:**数据类型决定内存操作粒度**,编译器的隐式转换基于类型尺寸[^2][^3]。 --- ### 相关问题 1. 为什么对`void*`指针进行算术运算会导致编译错误? 2. 如何利用指针运算实现二维数组的遍历? 3. 指针关系运算在动态内存分配中有哪些应用场景? 4. `const int*`和`int* const`在指针运算时有何行为差异? [^1]: 指针±整数的基本操作与数组遍历实现 [^2]: 指针运算的步长机制及`void*`限制 [^3]: 指针关系运算规则与`const`修饰符的作用
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值