本篇博客内容参考《c和指针》的内容,并从第六章指针开始记录。
首先我先说明两点:
1.内存中的每个位置由一个独一无二的地址标识
2.内存中的每个位置都包含一个值
如果你记住了一个值的存储地址,你以后可以根据这个地址取得这个值,但是这样太笨拙雷,所以高级语言通过名字而不是地址来访问内存的位置。
所以名字就是我们所称的变量。这是有编译器给我们的方便之门,硬件仍是通过地址访问内存位置
int a = 112, b = -1;
float c = 3.14;
int *d =&a;
float *e = &c;
在实际存储中c内存所存储的值是一个很大的整数。那么到底它是哪个呢?。这引出了一个重要的结论:不能简单地通过检查一个值的位来判断它的类型,具体的是看使用方式。
考虑下面这个以二进制形式表示的32位值:
01100111011011000110111101100010
下面是这些为可能被解释的许多结果中的几种。
类型 | 值 |
---|---|
1个32位整数 | 1735159650 |
2个16为整数 | 26476和28514 |
4个字符 | glob |
浮点数 | 1.116533×10e24 |
机器指令 | beg.+110和ble.102 |
请仔细考虑,a,b,c,d,e的值分别是什么呢?
前三个很简单:a是112,b是-1,c是3.14,指针变量其实也很容易,d的值是100,e的值是108。变量的值就是分配给该变量的内存位置所存储的数值,即使是指针变量也不例外。
通过指针访问它所指向的地址的过程称为间接访问(indirection)或解引用指针(dereferencing)。
下面这段代码说明了一个极为常见的错误
int *a;
*a = 12;
这个声明创建了一个名叫a的指针变量,后面那条赋值语句把12存储在a所指向的内存位置。
警告:
但是究竟a指向哪儿呢?我们并不知道。如果你运气好,编译器会提示你a的初始值是个非法地址,这样赋值语句将会出错,从而程序终止。在unix系统上,这个错误被称为“段违例(segmentation violation)”和“内存错误(memory fault)”。
一个更为严重的情况是:这个指针偶尔可能包含了一个合法的地址。那么接下来位于那个位置的值被修改,虽然你无意修改。像这样的类型的错误非常难以捕捉。所以在你对指针进行间接访问之前,必须非常小心,确保他们已被初始化。
标准定义雷NULL指针,它作为一个特殊的指针变量,表示不指向任何东西。要是一个指针变量为NULL,你可以给它赋一个零值。
NULL指针的概念是非常有用,因为他给了你一种方法,表示某个特定的指针目前并未指向任何东西。
在对指针进行解引用操作之前,你首先必须确保它并非NULL指针。
警告:
如果对一个NULL指针进行间接访问会发生什么情况呢?一般情况下都会引发错误,并终止程序。
提示:
对所有的指针变量进行显示的初始化是种好做法。
指针变量可以作为左值,并不是因为它们是个指针,而是因为它们是变量。下面两条语句中
*d = 10 - *d;
d = 10 - *d; ????
第1条语句包含了两个间接访问操作,右边的值是d所指向的位置所存储的值,左边是d所指向的位置把赋值符右侧的表达式计算的结果作为它的新值。
第2条语句是非法的,因为它表示把一个整型数量(10 - *d)存储于一个指针变量中。
来看看下面的表达式
*&a = 25 ;
如果你的答案是把值25赋值给变量a,恭喜!答对了。&操作符产生变量a的地址,他是一个指针常量。接着,*操作符访问其操作数所表示的地址。不过这样做太复杂,是程序效率变的底下,所以没人会这么做。
假定a的存储于位置100
*100 = 25;
这条语句是非法的,因为字面值100的类型是整型,而间接访问操作只能作用于指针类型表达式,如果你确实想把25存储于位置100,你必须使用强制类型转换。
*(int *)100 = 25 ;
int a = 12 ;
int *b = &a ;
int **c = &b;
唯一一个新面孔是 c = &b ; 这是合法的吗?是的,指针变量和其他变量一样,占据内存中某个特定的位置,所以用&操作符取地址是合法的(声明为register的变量例外)。
现在让我们观察各种不同的指针表达式。
char ch = 'a';
char *cp = &ch;
表达式 | 右值 | 左值 |
---|---|---|
&ch | 变量ch地址 | 非法 |
cp | cp的值 | cp所处的内容 |
&cp | 指向字符的指针的指针 | 非法 |
*cp | ch的值 | ch的地址 |
*cp+1 | ch的值+1 | 非法 |
*(cp+1) | cp+1所指向的值 | cp+1所指向的地址 |
++cp | 值增加 | 非法 |
cp++ | 先返回cp值的拷贝然后增加 | 非法 |
*++cp | ch后的值 | ch后的地址 |
*cp++ | 先得到ch 然后cp增加 | 先得到cp的值,然后cp增加 |
++*cp | ch的值增加 | 非法 |
(*cp)++ | ch的值增加 | 非法 |
指针加上一个整数的结果是另外一个指针,如果你将一个字符指针加1,运算结果会产生的指针指向内存中的下一个字符。float占据的内存空间不止1字节,如果将一个指向float的指针加1,将会发生什么呢?
答案是指针值加上1个float大小的值。换句话说,如果p是一个指向char的指针,那么表达式p+1就是指向下一个char。如果p是一个指向float的指针,那么p+1就指向下一个float。
c的指针算数运算只限于两种形式。
第一种是:指针 +- 整数
让指针指向数组最后一个元素后面的哪个位置是合法的,但对这个指针执行间接访问可能会失败。
第二种是:指针 - 指针
只有当两个指针指向同一个数组中的元素时,才允许从一个指针减去另一个指针。
两个指针相减的结果的类型是 ptrdiff_t,它是一种有符号整数类型。减法运算的值是两个指针在内存中的距离(以数组元素的长度为单位,而不是以字节为单位),因为减法运算的结果将除以数组元素类型的长度。
对指针执行关系运算也是有限制的。用下列关系操作符对两个指针值进行比较是可能的:<、<=、>、>=
不过前提是它们都指向用一个数组中的元素。
1.错误地对一个未初始化的指针变量进行解引用;
2.错误的对一个NULL指针进行解引用;
3.向函数错误的传递NULL指针;
4.未检测到指针表达是的错误,从而导致不可预料的错误;
5.对一个指针进行减法运算,使它非法的指向了数组第一个元素的前面的内存位置;
1.一个值应该只具有一种意思
2.如果指针并不指向任何有意的东西,就把它设置为NULL。