C语言教程-13_2-指针类型与指针运算
前面13_1章节中提到一个问题:指针指向的数据类型的大小
.本章节围绕这个问题展开.
前置知识:
- 指针变量的声明,赋值,与解引用操作
- 指向不同类型的指针如何声明
指针指向类型的大小
前面提到,指针的类型
即为其指向的类型的指针
,声明语法如下:
1 | [类型] *[指针变量名]; |
C语言中任何值都有其类型,例如int
,float
,char
等等.
同理,每一个指针变量/指针值也都有固定的类型,例如int *
,char *
,float *
等,在这些基本类型的后面加一个*
,整体即为一个特定的指针类型
.
一个指针变量
只能指向与其指针类型
一致的变量.例如int *
的指针变量只能指向int类型的变量,而不能指向char类型的变量.原因就是,对一个指针进行寻址(解引用),必须要确定数据大小,从指针值开始,取出固定的几个字节.
以前面int i=2;
为例:
假设i在内存中的地址为0xFFFFFECC
,如图.我们知道int变量需要4个字节才能够存下,而实际上计算机内存的每个字节都有自己的地址,那么我们讲的"i的地址",实际上是使用其最小字节的地址作为"代表",也就是0xFFFFFECC
,当我们需要访问i的时候,从这个字节开始,将连续的4
个字节一起拿出来,作为一个整体来处理,也就是02 00 00 00
.
以上操作的前提是,我们已知了int占用4字节,即sizeof(int)==4
.使用指针进行访问也是如此,光知道了i的起始地址还不够,我们需要知道从这个指针值起始需要取出多少字节的数据.指针类型
就提供了这样的信息.例如我们有int *p;
想要获取一个指针指向类型的大小,可以这样:
int size = sizeof(*p);
由于sizeof后的表达式不求值
,所以尽管没有对p初始化,我们也是可以写sizeof(*p)
的,想想,p为int*
,那么对p解引用自然就是int
了,sizeof int
就算出来4
,即使用指针p进行解引用,要将4个字节作为一个整体去处理.同理,char *p;
对p解引用时只处理1个字节,因为sizeof(*p)
即为sizeof(char)
,结果为1
.
也就是说,我们可以确定一个指针所指向的类型占用的字节数,根据这个值在解引用
时取出特定长度的一段内存数据.
指针运算
指针本质上就是一个有类型的内存地址,其实就是一个特定长度的无符号整数,所谓的指针类型
是这个整数的特殊解释方式.
作为一个数值,指针也支持一些运算,但是由于其并不是一般的整数,他的运算较为特殊.
指针仅支持整数加减法
,而且两个指针间只能做减法
,因为其他的运算没有任何意义.另外,指针运算往往离不开数组.
指针加法
一个指针(地址)加1,即将地址加1,代表着这个指针指向下一个内存单元,即紧挨着原来那个字节再往后一个位置的字节.同理,一个指针加n,代表着这个指针指向原来的地址往后n个字节的地址.
例如有一个指向char类型的指针p,初始化为0x1
:
我们对p加上某个整数n,结果就是指向往后面数n个字节的那个内存地址.
现在问题来了,如果是int *p=(int*)0x1;
呢?
显然,p+1
不再是指向紧挨着p之后的那个字节,而是向后偏移
了整整4个字节,到达了0x5
的位置.
这个问题其实很简单,我们在对指针进行解引用的时候,需要计算对应类型(即这个指针指向的类型)的大小,然后一次性取出特定长度的数据.对应地,我们将指针加上某个数,就是想要指向后面对应的数据,彼此之间应该是不能冲突的.
因此,我们需要做一个乘法,即:
(p+1)
等价于(p)+sizeof(*p)*i
其中,前者是C语言表达式,而后者是普通的数学算式!
对于int *p
,其指向的类型int
占用4个字节(只考虑32位以上的机器),那么在计算p+1
的时候,要先将1乘以4,然后再和p的值进行相加,得到的就是0x1+1*4,即0x5.同理,p+2
得到的就是0x1+2*4,即0x9.
而对于char *p,其指向的类型char
只占用1个字节而已,所以加1仅仅是向后偏移1而已.
注:好好体会偏移
这个词,后面会经常遇到.
另一方面,虽然标题写的是指针加法
,但是两个指针进行相加是没有任何意义的…
1 | int a,b; |
指针减法
一个指针减去一个常数和指针加一个常数的意义是一样的,只不过这回是向前(更低的地址)偏移了而已,这里就不举例了.
另一方面,与两个指针相加不同,两个指针相减是合法的,而且作用很大!
这里,p2指向p1的下一个位置(相差1个int的长度),那么(p2-p1)
的结果是这两个指针之间间隔的元素个数.
也许加一个数组进来会更加直观:
1 |
|
输出结果是3,代表着二者之间相差3个int变量,从数组下标也能看出来.
也就是说,指针与指针的相减操作,表示两个指针指向的内存位置之间相隔多少个元素(而不是字节数),因此指针相减往往与数组结合使用.
另外,必须提出的一点是,这两个指针必须指向同一个数组的某两个元素,否则行为未定义
.
指针自增
C语言中,++
和--
运算符是可以用于指针变量的,和普通变量一样,对指针自增或自减都是让这个指针变量的值"加一"或"减一".
需要注意的区别是,其效果和指针加法
,指针减法
相同,都是向前向后偏移
一个元素大小的长度,而不是1个字节的大小.
对指针变量自增/自减会修改指针变量的值,也就是修改这个指针的指向.这种写法有着十分重要的作用,例如达到只使用一个指针进行遍历的效果.
1 | int a[10]={0}; |
同样需要注意的是,前缀++
和后缀++
的区别依然不变,如果不清楚的话请回看"运算符"这一部分.我们举一个例子:
1 |
|
唯一的区别是对指针的自增遵循指针运算,而不是简单地将地址值+1.
指针自增实际上还有许多细节,易错点和应用技巧,他们和数组有着紧密的关联,后面细说.
关于指针运算,详见算术运算符
本部分讲解了指针类型与指针运算,读者应该意识到(虽然可能为时尚早),指针的行为与其类型息息相关
.
接下来的指针话题会涉及到数组,而且比较复杂.
---WAHAHA
上一篇:C语言教程-13_1-初识指针