在信息工程中指针是一个用来指示一个内存地址的计算机语言的变量或中央处理器(CPU)中寄存器(Register)。指针一般出现在比较近机器语言的语言,如汇编语言或C语言。面向对象的语言如Java一般避免用指针。指针一般指向一个函数或一个变量。在使用一个指针时,一个程序既可以直接使用这个指针所储存的内存地址,又可以使用这个地址里储存的变量或函数的值。
大家都认为,c语言之所以强大,以及其自由性,很大部分体现在其灵活的指针运用上。因此,说指针是c语言的灵魂,一点都不为过。同时,这种说法也让很多人产生误解,似乎只有c语言的指针才能算指针。basic不支持指针,在此不论。其实,pascal语言本身也是支持指针的。从最初的pascal发展至今的object pascal,可以说在指针运用上,丝毫不会逊色于c语言的指针。
指针
1、内存分配表
计算机中的内存都是编址的,就像你家的地址一样。在程序编译或者运行的时候,系统(可以不关心具体是什么,可能是编译器,也可能是操作系统)开辟了一张表。每遇到一次声明语句(包括函数的传入参数的声明)都会开辟一个内存空间,并在表中增加一行纪录。记载着一些对应关系。
----------------------------------------------------
Declaration | ID Name Address Length
----------------------------------------------------
Int nP; | 1 nP 2000 2B
char myChar; | 2 myChar 2002 1B
int *myPointer; | 3 myPointer 2003 2B
char *myPointer2; | 4 myPointer2 2005 2B
----------------------------------------------------
2、指针就是一个整数。
指针,是一个无符号整数(unsigned int,下简写为int),它是一个以当前系统寻址范围为取值范围的整数。32 位系统下寻址能力(地址空间)是4G-bit(0~232-1)二进制表示长度为32bit,也就是4B。
int 类型也正好如此取值。
例证就是程序1 得到的答案和程序2 的答案一致。(不同机器可能需要调整一下pT 取值。)
----------------------------------------------------
程序1
#include <stdio.h>
main()
{
char *pT;
char t='h';
pT=&t;
putchar(*pT);
}
----------------------------------------------------
程序2
#include <stdio.h>
main()
{
char *pT;
char t='h';
pT=(char *)1245048;
putchar(*pT);
}
----------------------------------------------------
加上(char *)是因为毕竟int 和char *不是一回事,需要强制转换,否则会有个警告。因为char *声明过的类型,一次访问1 个sizeof(char)长度,double *声明过的类型,一次访问1个sizeof(double)长度。
在汇编里int 类型和指针就是一回事了。因为不论是整数还是指针,执行自增的时候,都是其值加一。如果上文声明char *pT;,汇编语言中pT 自增之后值为1245049,可是C语言中pT++之后pT 值为1245049。如果32 位系统中, s 上文声明int *pT;,汇编语言中pT 自增之后值为1245049,可是C 语言中pT++之后pT 值为1245052。
为什么DOS下面的Turbo C,和Windows 下VC 的int 类型不一样长。因为DOS是16位的,Windows 是32 位的,可以预见,在64 位Windows 中编译,上文声明int *pT;,pT++之后pT 值为1245056。
那么,复杂的结构怎么分配空间呢?C 语言的结构体(汇编语言对应为Record 类型)按顺
序分配空间。
----------------------------------------------------
int a[20];
----------------------------------------------------
typedef struct st
{
double val;
char c;
struct st *next;
} pst;
----------------------------------------------------
pst pT[10];
----------------------------------------------------
在32 位系统下,内存里面做如下分配(单位:H,16 进制);
----------------------------------------------------
变量 2000 2001 2002 2003 2004 2005 2006 … 204C 204D 204E 204F
地址 a[0] a[1] … a[19]
----------------------------------------------------
变量 2050 2051 … 2057 2058 2059 205A 205B 205C 205D 205E 205F
地址 pst.val pst.c pst.next 无效 无效 无效
----------------------------------------------------
这就说明了为什么sizeof(pst)=16 而不是8。编译器把结构体的Size 规定为结构体成员中Size 最大的那个类型的整数倍。
至于pT 的存储,可以依例推得。总长为160,此不赘述。
有个问题,如果执行pT++,答案是什么?是自增16,还是160?别忘了,pT 是常量,不能加减。
所以,我们就可以声明:
----------------------------------------------------
typedef struct BinTree
{
int value;
struct BinTree *LeftChild;
struct BinTree *RightChild;
} BTree;
----------------------------------------------------
用一个整数,代表一棵树的结点。把它赋给某个结点的LeftChild/RightChild 值,就形成了上下级关系。只要无法找到一个路径,使得A->LC/RC->LC/RC...->LC/RC==A,这就构成了一棵二叉树。反之就成了图。
3、C 的按值传递
C 中函数调用是按值传递的,传入参数在子函数中只是一个初值相等的副本,无法对传入参数作任何改动。但实际编程中,经常要改动传入参数的值。这一点我们可以用传入参数的地址而不是原参数本身,当对传入参数(地址)取(*)运算时,就可以直接在内存中修改,从而改动原想作为传入参数的参数值。
----------------------------------------------------
#include <stdio.h>
void inc(int *val)
{
(*val)++;
}
main()
{
int a=3;
inc(&a);
printf("%d" , a);
}
----------------------------------------------------
在执行inc(&a);时,系统在内存分配表里增加了一行“inc 中的val”,其地址为新地址,值为&a。操作*val,即是在操作a 了。
4、*和&运算
为了(*p)操作是这样一种运算,返回p 的值作为地址的那个空间的取值。(&p)则是这样一种运算,返回当时声明p 时开辟的地址。显然可以用赋值语句对内存地址赋值。我们假设有这么两段内存地 |