细说指针
1、内存与地址
当我们最初认识计算机内存与地址关系时,通常把计算机的内存看作是一条长街上的一排房屋;内存单元就是房子,可以容纳数据;地址就是房屋编号,编号标志房屋所处的位置。
这个比喻形象生动,但也有不足之处,没有描述出其真正的含义。计算机内存由数以亿万的比特位(bit)组成的,每个比特位可以容纳0或1。一个比特位所能表示的数组范围有限,通常由若干个bit组成一个单位,以存储较大的数值。一般情况下,计算机以字节作为最小单位,每个字节包含8个bit。地址的标号就是以一个字节开始编号的。
为了能存储更大的值,又把多个字节组合在一起作为更大的内存单位。许多计算机器以字作为存储整数的基本单位,每个字是4个字节。尽管一个字包含4个字节,它仍然就一个地址。地址编号取决于机器本身,可以是最左边的字节位置,也可以是最右边的字节位置。在要求边界对齐的机器上,整型值存储的起始位置只能是某些特定的位置,通常是2或4的倍数,但对C程序员影响不是很大。
每个内存单元中都有其内容,对于得到内存单元中的值有两种方式:
1.内存地址
2.变量名
如果我们知道了一个值的内存地址,就可以通过这个地址取得相应的值,但记住每个值的地址是笨拙的方式。在高级言语中,我们可以通过变量名的方式来访问内存的位置。编译器为我们提供了变量名与内存位置的关联,硬件还是通过地址来访问内存位置的。
2、值与类型
我们知道,一个变量对应着相应大小的内存单元。比如,char类型占一个字节、int类型占四个字节;每个内存单元都是0或1序列,它们可以被解释为整型值,也可以被解释为浮点型值,这都取决于它们被使用的方式。如果是使用整型算术指令,这个值是整数;如果是使用浮点型算术指令,这个值就是浮点数。
这个事实可以得出一个重要的结论:不能简单地通过检查一个值的位模式来判断它的类型。
3、间接访问操作符
通过一个指针访问它所指的地址的过程称为间接访问或解引用指针,单目运算符‘*’用于执行间接访问的操作。
int a = 0;
int * pint = &a;//0x0030fea0
*pint = 1;
*pint = 1表示访问地址0x0030fea0的内存位置并对其赋值。
4、NULL指针
标准定义了NULL指针,它作为一个特殊的指针变量,表示目前不指向任何东西。源代码规定要使一个指针变量变为NULL指针,可以对该指针变量赋0值。就机器内部而言,NULL指针的实际值可能与此不同,在这种情况下,编译器将负责零值和内部值之间的翻译转换。
对指针进行解应用操作可以获得它所指向的值。但从定义上看,NULL指针并未指向任何地方,因此,对一个NULL指针进行解引用操作是非法的。
5、指针运算
指针加上一个整数的结果是另一个指针。当一个指针和一个整数量执行算术运算时,整数在执行加法运算前始终会根据合适的大小进行调整。“合适的大小”指的是指针指向类型的大小,“调整”就是把整数量和“合适的大小”相乘。
例如:
int array[5] = { 1, 2, 3, 4, 5};
int *pint = array;
printf("pint = 0x%08x,data = %d\n",pint,*pint);
pint = pint +2;
printf("pint = 0x%08x,data = %d\n",pint,*pint);
pint = NULL;
结果:
可以看出指针加2,指针向后移动8个字节,即指针指向array[2]。
5.1 指针算术运算
C指针算术运算只限于两种形式。
1、指针+整数(指针-整数)
标准规定这种形式只能指向数组中某个元素的指针。
该类表达式的结果类型也是指针,这种形式也适合使用malloc函数动态分配获得的内存。
注意:
对指针进行加减法运算之后,如果指针所指向的位置在数组第一个元素的前面是非法的;指针指向数组最后一个元素后面的那个位置是合法的,但对这个指针执行解引用却是非法的。
2、指针-指针
只有当两个指针指向同一个数组中的元素时,才允许从一个指针减去另一个指针。两个指针相减的类型是ptrdiff_t,它是一种有符号整数类型。减法运算的值是两个指针在内存中的距离,该距离是以数组元素的长度为单位。
如果两个指针所指向的不是同一个数组中的元素,那么它们之间相减的结果是未定义的。
5.2 关系运算
当两个指针都是指向同一个数组中的元素时,可以执行的关系运算有:<、<=、>、>=;比较表达式将告诉我们哪个指针指向数组中更前或更后的元素。然而,对于任意两个指针间执行相等或不等测试是允许的。