大家好,我是小张同学!今天继续来学习指针。
目录
1. 指针运算
指针变量保存的是地址,指针变量可以进行部分运算。
1.1 指针和整数进行加减运算
指针可以加上或减去一个整数,指针的这种运算和通常意义上的数值运算是不一样的。
举个例子:
int a[20];
int *p = a;
p++;
在上面这个例子中,指针 p 的类型是 int*,当执行 p++时,编译器的处理方法是将指针 p 加上 sizeof(int),在32位系统中,指针p就被加上了4,那这样的话,p就指向了 a[1],以此类推,p再加1,就指向了a[2]......
指针在和一个整数进行运算时,会在执行运算前根据合适大小进行调整,这个合适的大小就是指针所指类型的大小,如果将一个 char* 指针加1,实际上就是增加 sizeof(char),如果将一个 float* 指针加1,实际上就是增加sizeof(float),其他类型也是如此。
指针加上或减去一个整数的结果是另一个指针,我们需要注意的一个问题是,运算结果产生的指针指向哪里。
比如对于一个普通int变量,指针p指向此变量,对此指针进行加减运算,虽然不会报错,但是没什么意义,因为 p+1指向的内容大概率是杂乱数据。
但是在数组中,指针与整数的加减运算就有了现实的意义,因为数组中的所有元素在内存中是连续排列的,指针加一就指向数组的下一个元素,减一就指向上一个元素。
编译器不会判断运算结果产生的指针是否指向有效值,是否越界等,因此,当使用指针运算时,必须非常小心,确保结果指针指向有意义的东西。
1.2 指针相减
两个指针可以相减,一般用在数组方面。
只有当两个指针都指向同一个数组中的元素时,才允许两个指针相减,相减得到的结果是两个指针在内存中的距离。
两个指针相减,编译器的做法是将结果除以数组元素类型的长度,所以结果是以数组元素的长度为单位,而不是以字节为单位。
1.3 关系运算
指针变量还可以进行比较运算,如果两个指针相等,那么两个指针就指向同一份数据,否则就指向不同的数据。
如果两个指针都指向同一个数组中的元素,它们之间的比较可以用于判断元素在数组中的相对位置。
2. 指针表达式
int a[20];
int *p = a;
如果指针p指向数组a,那么来看一下以下这些表达式表示什么意义吧:
&p
*p
*p+1
*(p+1)
++p
p++
*++p
*p++
++*p
(*p)++
++*++p
晕了吗,反正我是晕了。
2.1 运算符的优先级和结合性
其实上面这些表达式之所以让人头晕,实际就是搞不清先计算谁后计算谁的问题,下面先来看一下运算符的优先级和结合性。
所谓优先级,就是当一个表达式中出现多个运算符时,先计算谁的问题。在C语言中,有很多运算符,要记住这些运算符的优先级是挺难的,而且实际在编程的时候,如果搞不清优先级,加一个括号就可以了,加括号后,优先级就一目了然,别人读起来也更方便。
结合性,就是当运算符的优先级相同的时候,应该先计算谁,如果结合性是从左到右,那么当优先级相同时,就按从左到右的顺序计算,如果结合性是从右到左,那么就按从右到左的顺序计算。
优先级 | 运算符 | 含义 | 结合方向 | 说明 |
---|---|---|---|---|
1 | [] | 数组下标 | 左到右 | |
1 | () | 圆括号 | 左到右 | |
1 | . | 成员选择 | 左到右 | |
1 | -> | 成员选择 | 左到右 | |
2 | - | 负号 | 右到左 | 单目运算符 |
2 | (类型) | 强制类型转换 | 右到左 | |
2 | ++ | 自增运算符 | 右到左 | 单目运算符 |
2 | -- | 自减运算符 | 右到左 | 单目运算符 |
2 | * | 解引用运算符 | 右到左 | 单目运算符 |
2 | & | 取地址运算符 | 右到左 | 单目运算符 |
2 | ! | 逻辑非运算符 | 右到左 | 单目运算符 |
2 | ~ | 按位取反运算符 | 右到左 | 单目运算符 |
2 | sizeof | 长度运算符 | 右到左 | |
3 | / | 除 | 左到右 | 双目运算符 |
3 | * | 乘 | 左到右 | 双目运算符 |
3 | % | 取余 | 左到右 | 双目运算符 |
4 | + | 加 | 左到右 | 双目运算符 |
4 | - | 减 | 左到右 | 双目运算符 |
5 | << | 左移 | 左到右 | 双目运算符 |
5 | >> | 右移 | 左到右 | 双目运算符 |
6 | > | 大于 | 左到右 | 双目运算符 |
6 | >= | 大于等于 | 左到右 | 双目运算符 |
6 | < | 小于 | 左到右 | 双目运算符 |
6 | <= | 小于等于 | 左到右 | 双目运算符 |
7 | == | 等于 | 左到右 | 双目运算符 |
7 | != | 不等于 | 左到右 | 双目运算符 |
8 | & | 按位与 | 左到右 | 双目运算符 |
9 | ^ | 按位异或 | 左到右 | 双目运算符 |
10 | | | 按位或 | 左到右 | 双目运算符 |
11 | && | 逻辑与 | 左到右 | 双目运算符 |
12 | || | 逻辑或 | 左到右 | 双目运算符 |
13 | ?: | 条件运算符 | 右到左 | 三目运算符 |
14 | = | 赋值运算符 | 右到左 | |
14 | /= | 除后赋值 | 右到左 | |
14 | *= | 乘后赋值 | 右到左 | |
14 | %= | 取模后赋值 | 右到左 | |
14 | += | 加后赋值 | 右到左 | |
14 | -= | 减后赋值 | 右到左 | |
14 | <<= | 左移后赋值 | 右到左 | |
14 | >>= | 右移后赋值 | 右到左 | |
14 | &= | 按位与后赋值 | 右到左 | |
14 | ^= | 按位异或后赋值 | 右到左 | |
14 | |= | 按位或后赋值 | 右到左 | |
15 | , | 逗号运算符 | 左到右 |
2.2 分析指针表达式
搞清楚了运算符的优先级和结合性,那就开始分析一下这些表达式吧。
&p:对p这个指针变量取地址,即指向指针的指针,表达式的值为指针变量p的地址
*p:解引用 p,得到其指向的值 a[0]
*p + 1:* 的优先级高于 +,所以先执行 *p 得到的值是a[0],然后将结果加1,即最终得到 a[0]+1,p指向位置不变
*(p + 1):和上面的例子相比,加了一个括号,这样就先执行加法运算,p+1得到 a[1] 的地址,然后再使用 * 得到这个地址处的值,最终得到 a[1],p指向位置不变
++p:++和--操作符在指针变量中使用的很频繁,先将p指针加1,然后返回新的p,最终得到指向 a[1] 的指针,p指向a[1]
p++:返回p的当前值,即a[0]的地址,然后p自增1,最终得到指向a[0]的指针,p指向a[1]
*++p:* 和 ++的优先级相同,且结合性都是从右向左,因此先将p加1,然后解引用新的p,最终得到 a[1],p指向a[1]
*p++:* 和 ++ 的优先级相同,并且结合性都是从右向左,这意味着,在这个表达式中,首先考虑++运算符,后置++会先返回当前p,即a[0]的地址,然后p自增1,然后考虑 * 运算符,应用于p++的返回值,所以最终得到a[0],p指向a[1]
*p++:* 和 ++ 的优先级相同,并且结合性都是从右向左,这意味着,在这个表达式中,首先考虑++运算符,后置++会先返回当前p,即a[0]的地址,然后p自增1,然后考虑 * 运算符,应用于p++的返回值,所以最终得到a[0],p指向a[1]
(*p)++:先解引用p的值,返回该值a[0],然后对a[0]自增,最终得到a[0],p指向位置不变,a[0]的值被改变
++*++p:先将p自增,再解引用新的p值,然后对其值自增,最后得到a[1]+1,p指向a[1],a[1]的值被改变
++*p++:首先计算后置++,返回当前p值,p指向a[1],然后解引用自增前的p值,然后对原解引用的值自增,最终得到a[0]+1,p指向a[1],a[0]的值被改变
看得懂吗?多看几遍就行了(doge)
本篇文章结束,下回接着讲指针。