基本概念
一般把数据都放在存储器中,存储器中一个字节称为一个内存单元
不同数据类型所占用的内存单元数不等,如整型量占2个单元,字符量占1个单元,每个内存单元都有编号.这个编号也叫地址.就是我们所说的指针.内存单元的指针和内存单元的内容是两个不同的概念.对于一个内存单元来说,单元的地址即为指针,其中存放的数据才是该单元的内容.在C语言中,允许用一个变量来存放指针,这种变量称为指针变量.
因此,一个指针变量的值就是某个内存单元的地址或称为某内存单元的指针.
“指针”是指地址,是常量,”指针变量”指取值为地址的变量.定义指针的目的是通过指针去访问内存单元.
变量的指针和指向变量的指针变量
首先,变量的指针就是变量的地址.
存放变量地址的变量是指针变量.
在C语言中允许用一个变量来存放指针.
这种变量称为指针变量.因此
一个指针变量的值,就是某个变量的地址.或者称为某个变量的指针.
指针变量和它指向的变量的关系:
C语言中用“*”表示指向,例如用i_pointer表示一个指针变量,则*i_pointer表示i_pointer所指向的变量。
指针数组和数组指针
数组类型
C语言中数组有自己特定的类型数组的类型由元素类型和数组大小共同决定,比如int array[5]的类型为int[5]。定义数组类型
C语言中通过typedef为数组重命名typedef type(name)[size]数组类型typedef int(AINT5)[5]typedef float(AFLOAT10)[10]数组定义AINT5 iArrayAFLOAT10 fArray数组指针
数组指针用于指向一个数组数组名是数组首元素的起始地址,但不是数组的起始地址,通过取地址符&作用域数组名可以得到数组的起始地址。可以通过数组类型定义数组指针如:ArrayType *pointer,也可以直接定义type (*pointer)[n]
指针数组
指针数组是一个普通的数组,数组中的每个元素都是指针指针数组的定义为:type *pArray[n]指向指针的指针
指针变量在内存中占用一定的空间可以定义指针来保存指针变量的地址值为什么需要指向指针的指针?指针本质上也是变量,对于指针同样也存在传值调用和传址调用。二维数组和二级指针
二维数组在内存中以一维的方式排布二维数组中的第一维是一维数组,第二维才是具体的值二维数组的数组名可以看做常量指针
数组名
一维数组的数组名代表数组首元素的地址int a[5]->a的类型是int *二维数组的数组名同样代表数组首元素的地址int m[2][5]->m的类型是int(*)[5]所以得出以下结论:
二维数组的数组名可以看做指向数组的常量指针二维数组可以看做一维数组,其中每个元素都是同类的一维数组数组参数和指针参数分析
C语言中只会以值拷贝的方式传递参数当向函数传递数组时,会将数组名看做常量指针传递数组首元素地址。注意:
C语言中无法向一个函数传递任意的多维数组为了提供正确的指针运算,必须提供除第一维之外的所有维度的长度信息限制:一维数组参数--必须提供一个标识数组结束位置的长度信息。二维数组参数--不能直接传递给函数三维或者更多维度的数组--无法使用函数类型
C语言中的函数有自己特定的类型。函数的类型由返回值,参数和参数个数共同决定。
在C语言中通过typedef为函数重命名如:
typedef type name(paramter list);
函数指针
函数指针用于指向一个函数函数名是执行函数体的入口地址可以通过函数类型定义函数指针:functype *pointer也可以直接定义:type (*pointer)(parameter list)回调函数。
回调函数是利用函数指针实现的一种调用机制,起调用机制原理如下:
调用者不知道具体事件发生时候需要调用的具体函数,被调用函数不知道何时被调用,只知道被调用会需要完成的任务。当具体事件发生时,调用者通过函数指针调用具体函数,回调机制将调用者和被调函数分开,两者互补依赖。
如果在程序中定义了一个函数,那么在编译时系统就会为这个函数代码分配一段存储空间,这段存储空间的首地址称为这个函数的地址。而且函数名表示的就是这个地址。既然是地址我们就可以定义一个指针变量来存放,这个指针变量就叫作函数指针变量,简称函数指针。
那么这个指针变量怎么定义呢?虽然同样是指向一个地址,但指向函数的指针变量同我们之前讲的指向变量的指针变量的定义方式是不同的。例如:
int(*p)(int, int);
这个语句就定义了一个指向函数的指针变量 p。首先它是一个指针变量,所以要有一个“*”,即(*p);其次前面的 int 表示这个指针变量可以指向返回值类型为 int 型的函数;后面括号中的两个 int 表示这个指针变量可以指向有两个参数且都是 int 型的函数。所以合起来这个语句的意思就是:定义了一个指针变量 p,该指针变量可以指向返回值类型为 int 型,且有两个整型参数的函数。p 的类型为 int(*)(int,int)。
所以函数指针的定义方式为:
函数返回值类型 (* 指针变量名) (函数参数列表);
“函数返回值类型”表示该指针变量可以指向具有什么返回值类型的函数;“函数参数列表”表示该指针变量可以指向具有什么参数列表的函数。这个参数列表中只需要写函数的参数类型即可。
我们看到,函数指针的定义就是将“函数声明”中的“函数名”改成“(*指针变量名)”。但是这里需要注意的是:“(*指针变量名)”两端的括号不能省略,括号改变了运算符的优先级。如果省略了括号,就不是定义函数指针而是一个函数声明了,即声明了一个返回值类型为指针型的函数。
那么怎么判断一个指针变量是指向变量的指针变量还是指向函数的指针变量呢?首先看变量名前面有没有“*”,如果有“*”说明是指针变量;其次看变量名的后面有没有带有形参类型的圆括号,如果有就是指向函数的指针变量,即函数指针,如果没有就是指向变量的指针变量。
最后需要注意的是,指向函数的指针变量没有 ++ 和 -- 运算。
使用函数指针调用函数
int Func(int x); /*声明一个函数*/
int (*p) (int x); /*定义一个函数指针*/
p = Func; /*将Func函数的首地址赋给指针变量p*/
注意:赋值时函数 Func 不带括号,也不带参数。由于函数名 Func 代表函数的首地址,因此经过赋值以后,指针变量 p 就指向函数 Func() 代码的首地址了。
举例:
# include <stdio.h>
int Max(int, int); //函数声明
int main(void)
{
int(*p)(int, int); //定义一个函数指针
int a, b, c;
p = Max; //把函数Max赋给指针变量p, 使p指向Max函数
printf("please enter a and b:");
scanf("%d%d", &a, &b);
c = (*p)(a, b); //通过函数指针调用Max函数
printf("a = %d\nb = %d\nmax = %d\n", a, b, c);
return 0;
}
int Max(int x, int y) //Max函数实现
{
int z;
if (x > y)
{
z = x;
}
else
{
z = y;
}
return z;
}
运行结果:
please enter a and b:10 8
a = 10
b = 8
max = 10
指针定义举例和详解
int p; //这是一个普通的整型变量 。
int *p; //首先从p 处开始,先与*结合,所以说明p 是一个指针,然后再与int 结合,说明指针所指向的内容的类型为int 型。所以p是一个返回整型数据的指针。
int p[3]; //首先从p 处开始,先与[]结合,说明p 是一个数组,然后与int 结合,说明数组里的元素是整型的,所以p 是一个由整型数据组成的数组 。
int *p[3]; //首先从p 处开始,先与[]结合,因为其优先级比*高,所以p是一个数组,然后再与*结合,说明数组里的元素是指针类型,然后再与int 结合,说明指针所指向的内容的类型是整型的,所以p 是一个由返回整型数据的指针所组成的数组 。
int (*p)[3]; //首先从p 处开始,先与*结合,说明p是一个指针然后再与[]结合(与"()"这步可以忽略,只是为了改变优先级),说明指针所指向的内容是一个数组,然后再与int 结合,说明数组里的元素是整型的,所以p是一个指向由整型数据组成的数组的指针 。
int **p; //首先从p开始,先与*结合,说是p 是一个指针,然后再与*结合,说明指针所指向的元素是指针,然后再与int 结合,说明该指针所指向的元素是整型数据,由于二级指针以及更高级的指针极少用在复杂的类型中。
int p(int); //从p 处起,先与()结合,说明p 是一个函数,然后进入()里分析,说明该函数有一个整型变量的参数,然后再与外面的int 结合,说明函数的返回值是一个整型数据 。
Int (*p)(int); //从p处开始,先与指针结合,说明p 是一个指针,然后与()结合,说明指针指向的是一个函数,然后再与()里的int 结合,说明函数有一个int 型的参数,再与最外层的int 结合,说明函数的返回类型是整型,所以p 是一个指向有一个整型参数且返回类型为整型的函数的指针 。
int *(*p(int))[3]; //从p开始,先与()结合,说明p 是一个函数,然后进入()里面,与int 结合,说明函数有一个整型变量参数,然后再与外面的*结合,说明函数返回的是一个指针,然后到最外面一层,先与[]结合,说明返回的指针指向的是一个数组,然后再与*结合,说明数组里的元素是指针,然后再与int 结合,说明指针指向的内容是整型数据。所以p是一个参数为一个整数类型且返回一个指向由整型指针变量组成的数组的指针变量的函数。
运算符&和*
这里&是取地址运算符,*是间接运算符。&a 的运算结果是一个指针,指针的类型是a 的类型加个*,指针所指向的类型是a 的类型,指针所指向的地址嘛,那就是a 的地址。*p 的运算结果就五花八门了。总之*p 的结果是p 所指向的东西,这个东西有这些特点:它的类型是p 指向的类型,它所占用的地址是p所指向的地址。
int a=12; int b; int *p; int **ptr;
p=&a; //&a 的结果是一个指针,类型是int*,指向的类型是
//int,指向的地址是a 的地址。
*p=24; //*p 的结果,在这里它的类型是int,它所占用的地址是
//p 所指向的地址,显然,*p 就是变量a。
ptr=&p; //&p 的结果是个指针,该指针的类型是p 的类型加个*,
//在这里是int **。该指针所指向的类型是p 的类型,这
//里是int*。该指针所指向的地址就是指针p 自己的地址。
*ptr=&b; //*ptr 是个指针,&b 的结果也是个指针,且这两个指针
//的类型和所指向的类型是一样的,所以用&b 来给*ptr 赋
//值就是毫无问题的了。
**ptr=34; //*ptr 的结果是ptr 所指向的东西,在这里是一个指针,
//对这个指针再做一次*运算,结果是一个int 类型的变量。