1. 数据的内部表示法
2. 内存地址
3. 左值和右值
4. 指针变量
5. 指针变量的内容
6. 间接访问操作符
7. 未初始化和非法的指针
8. NULL指针
9. Void*类型的指针
10. 指针常量
11. 指针的指针
12. 指针和数组
13. 指针运算
14. 指针表达式
15. 动态分配
-------------------------------------------------------------------------------------------------------
数据的内部表示法
计算机中所有的数值都是按照被称作比特(bit,binary digit的缩写)的基本单位的形式存储的。0和1是计算机所基于的二进制系统所用的两个数字。
为了方便地存储整型和字符型的信息,将若干个独立的比特组成一个较大的单元,作为完整的存储单元。这种最小的组合单位被成为字节(Byte)。
ANSI C并没有要求一个字节一定要多长,仅有的规则是char类型数据被定义为占据一个字节,因此,一个字节的长度必须足以容纳一个字符代码。故一个字节一般由8个比特组成。
一个字的大小通常要求容纳一个整型数据,整型数据对一般计算机而言都是两个字节或者四个字节组成。
计算机内存容量通常由千字节(kilobyte,KB)和兆字节(megabyte,MB)来衡量。由于计算机系统是二进制数据存储,故:
K=210=1024
M=220=1048576
-------------------------------------------------------------------------------------------------------
内存地址
内存中的每个位置都由一个独一无二的地址标识。
内存中的每个位置都包含一个值。
存储系统中,每个字节都有一个数字的地址。一般,第一个字节在计算机中的地址为0。
。。。 101 102 103 104 105 106107 108 。。。
比一个字符大的数值放在内存中连续的字节单元中。
许多机器以字为单位存储整数,每个字一般由2个或者4个字节组成。若一个字由4个字节组成,就需要4个字节的连续内存单元。但它仍然只有一个地址。至于它的地址是它最左边那个字节的位置还是最右边那个字节的位置,不同的机器有不同的规定。另外,有些要求边界对齐的机器上,整型值存储的起始位置只能是某些特定的字节,通常是2或4的倍数。但边界对齐的方式由硬件决定,不影响编程。以下为整型数据的存储形式:
。。。 102 106
-------------------------------------------------------------------------------------------------------
左值和右值(L-value和R-value)
这两个术语是多年前由编译器设计者所创造并沿用至今。
a =b+2;
a是个左值,因它标识了一个可以存储结果值的位置;
b+2是个右值,因为它指定了一个值。
b+2=a;对不?
a可以做右值,因为每个位置都包含一个值;
b+2不能作为左值,因为它并未标识一个特定的位置,故此赋值语句非法。
字面值常量也都不是左值。间接访问和下标引用的结果是个左值。
下标引用实际上是一个操作符,则
Int a[16];
a[b + 2]=0;
左边实际上是个表达式,但它标识了一个特定的位置,是左值。
int a, *pi;
...
pi = &a;
*pi =2;
第二条赋值语句,它左边的那个值是个表达式,是个合法左值。
指针pi的值是内存中某分特定位置的地址,*操作符使机器指向那个位置。当它作为左值使用时,这个表达式指定需要进行修改的位置。当它作为右值使用时,它就提取当前存储于这个位置的值。
-------------------------------------------------------------------------------------------------------
指针变量
变量的指针就是变量的地址。
存放变量地址的变量就是指针变量。
指针是一个数据项,它的值是其它值在内存中的地址。
数据结构总是在计算机的内存中,因此必然会有地址。因为内存地址在内部表示为一个整数,所以当数据结构本身很大时,利用指针引用,则节约大量内存空间。
对于左值,左值一旦声明,尽管左值的内容可以改变,但它的地址永远不能改变。
左值的地址本身也是数据,也能在内存进行操作和存储。
声明:
int *p;
变量p为指向整型的指针变量。
基本类型 * 变量名
Int *p1, *p2;
*在语法上属于变量名。
-------------------------------------------------------------------------------------------------------
指针变量的内容
一个变量的值就是分配给这个变量的内存位置所存储的数值。即使是指针变量也不例外,即是所存储的内存地址。
指针的初始化是用&操作符完成的,它用于产生操作数的内存地址。
int *d=&a;
float *e=&c;
同
int*d;
d=&a;
-------------------------------------------------------------------------------------------------------
间接访问操作符
通过一个指针访问它所指向的地址的过程称为间接访问或解引用指针。
操作符*
834 Ip 100 x
ip = &x
-------------------------------------------------------------------------------------------------------
未初始化和非法的指针
Int *a;
...
*a =3;
这个声明创建了一个名为a的指针变量,后面那条赋值语句把3存储在a所指向的内存位置。这是一个常见的错误。
a 究竟指向哪里?
声明了变量却没有进行初始化。如果变量是静态的,它会被初始化为0,但如果变量是自动的,它根本不会被初始化结果:
A的初始值是个非法地址,程序终止。
A是个合法地址,位于那个位置的值被修改。
-------------------------------------------------------------------------------------------------------
NULL指针
表示不指向任何东西。
要是一个指针变量为NULL,你可以给它赋一个零值。之所以选择零值是因为一种源代码约定。编译器将负责零值和机器内部值之间的翻译转换。
因为NULL指针并未指向任何东西,因此,对一个NULL指针进行解引用操作是非法的。在对指针进行解引用操作之前,必须确保它并非NULL指针。
如果对一个NULL指针进行间接访问会怎么样?它的结果因编译器而异,有些机器上,它会访问内存位置零,但是机器并未妨碍你修改这个位置,机器隐匿了它的症状,这就使这个错误难以寻找。其它机器上,对NULL指针进行间接访问将引发一个错误,并终止程序。
Void*类型的指针
通用指针类型
是一个指向空类型void的指针,void也用于指示无返回值或参数列表为空的函数。
声明:
Void *vp;
你可以将任何类型的指针值存入该变量,但不允许用*运算符间接引用vp,编译器不知道vp的基本类型是什么,所以没办法谈论vp指向的值。
指针常量
假定变量a存储于位置100,
*100 = 28;
这是错的。因为字面值100的类型是整型,而间接访问操作只能作用于指针类型表达式,如果你想把28存储于位置100,必须强制类型转换:
*(int*)100=28;
但是,你通常无法预测编译器会把某个特定的变量放在内存中的什么位置,所以你无法预知它的地址,用&操作符得到变量的地址是很容易的,但表达式在程序执行时才会进行求值,此时已经来不及把它的结果作用字面值常量复制到源代码。
要通过地址访问内存中的某个特定的位置,就得预先知道这些地址。
-------------------------------------------------------------------------------------------------------
指针的指针
Int a=2;
Int *b=&a;
ab
C = &b;
abc
B是一个“指向整型的指针”
C是一个指向“指向整型的指针”的指针,或一个指针的指针。
指针变量一样占据内存中某个特定的位置,故用&操作符取得它的地址是合法的。
声明C:
Int **c;
Int a=2;
Int *b=&a;
Int **c=&b;
*操作符是从右到左的结核性,故同于*(*c),
*c访问c所指向位置,知道这是b,
第二个间接引用访问这个位置所指向的地址也就是变量a.
表达式 | 相当的表达式 |
a | 12 |
b | &a |
*b | A,12 |
c | &b |
*c | B,&a |
**c | *b,a,12 |
-------------------------------------------------------------------------------------------------------
指针和数组
声明一个数组
Double list[3];
选择表达式
List[i]
由于list中第i个元素的地址取决于变量i的值,所以C语言编译器在编译程序时是无法计算这一地址的。为了确定这一地址,编译器产生一段指令,取数组的基地址,加上适当的偏移量,偏移量是将i的值乘以每个数组元素的字节数而得到的。故
List[i]的地址1000+i*8,list[0]的地址为1000
因为计算任何一个数组元素地址的过程是自动的,所以写程序时无需考虑计算的细节问题。
-------------------------------------------------------------------------------------------------------
数组名代表数组第一个元素的指针
数组名不是左值
将数组的基地址赋值给指针变量来初始化指针,如
P=array;
-------------------------------------------------------------------------------------------------------