在上一章我们说了指针的一些基础内容,其实下面说的也是些基础,不过就是有点提高。如果还需要了解上一章内容,可以打开如下链接:
指针(上)–指针存放,数组指针及const的了解
同样,以下是这篇文章的参考文章,若想要更深入的了解可以打开以下链接:
c语言指针学习
关于C语言指针的问题
恩,接下来就先讲讲指针相关类型吧。
指针的相关类型
指针类型
什么是指针类型?我们知道有很多的数据类型,比如整型,字符型,浮点型等等,那么指针类型是什么呢?我们可以这样简单地理解,一个指针的类型就是在他声明时,去掉他的变量名之后留下的内容就为指针类型。来看下面的两个例子:
1、int* p;其指针类型就是去掉p之后的 (int * )
2、int* a[];其指针类型就是去掉a之后的( int* [] )
指针指向的类型
当你通过指针来访问指针所指向的内存区时,指针所指向的类型决定了编译器将把那片内存区里的内容当做什么来看待。
那么怎么取看一个指针所指向的类型呢?我们也可以这样简单地去理解,仅需要将声明时指针的名字和指针名字旁边的符号” * “去掉就可以看做是指针指向的类型了。
同样的,下面来看两个例子:
1、int* p;其指针指向的类型类型为去掉p和去掉符号” * “,即(int);
2、int* a[];其指针指向的类型为去掉a和符号” * “,即为(int [])。
指针的值
我们在前面一篇文章中说过,对于通常的编译器一个指针的值是XX,就相当于说该指针指向了以XX为首地址的一片内存区域;我们说一个指针指向了某块内存区域,就相当于说该指针的值是这块内存区域的首地址。
指针类型的使用
当然,我们并不会白白介绍指针类型,指针指向的类型和指针的值,在实际使用中我们也通常会碰到以下的情况,这种情况会让我们知道指针类型是多么地重要。请看下面一段上面博客中引用过来的代码。
int main()
{
char ch[] = {'a', 'b', 'c'};
char *p1,*p2,*p3;
char **pp;
p1 = ch;
pp = &ch;
p2 = *pp;
if(p1 == p2)
{
printf("p1 == p2\n");
}
else
{
printf("p1 != p2\n");
}
printf("p3 = %p\n", p3);
printf("p1 = %p,ch = %p, pp = %p, &ch = %p, p2 = %p, *pp = %p\n", p1, ch, pp, &ch, p2, *pp);
}
编译阶段:
编译以上代码,编译器会告诉我们“警告:从不兼容的指针类型赋值”或者“warning: assignment from incompatible pointer type”即类型不匹配,这个警告是在编译“pp = &ch;”时触发的,为什么呢?这里就用到了前面的指针类型知识。
可以利用上面说过的知识:
pp 的指针类型为( char** );
( &ch )的指针类型可分两步来看。首先ch的指针类型为( char [3] ),那么&ch的指针类型为(char *[3])。
所以可以看出pp的指针类型与&ch的指针类型并不匹配,导致编译时出现警告,那么这个警告会给我们后面的编程带来什么样的影响呢?请看下面的程序运行阶段。
程序运行阶段:
在程序运行时,打印如下:
p1 != p2
p3 = 0x80484bb
p1 = 0xbfdd0aad,ch = 0xbfdd0aad, pp = 0xbfdd0aad, &ch = 0xbfdd0aad, p2 = 0xad636261, *pp = 0xad636261
针对:p1 != p2
首先我们来解释下为什么是p1 != p2?照程序上来看数组的首地址ch赋值给了p1,而将数组首地址ch取地址的pp又取值给了p2。不是应该一样的吗?请看下面解释。
从上面的编译阶段我们知道了,pp指针类型与&ch的指针类型并不相同。
……………………………………………………………………………………………………………………………
如果数组还太难理解的话,我们可以先从一个简单的入手。请看下面一段代码,看一看指针类型不匹配会对后面的代码结果造成怎么样的影响:
int main()
{
char c = 'x';
int* a = &c;
printf("&a=%p\n", &a);
printf("&c=%p, a=%p, c=%d, *a=%d\n", &c,a,c,&a);
return 0;
}
当然这里指针类型不匹配也是肯定的,看看打印结果吧:
&a=0xbf84f428
&c=0xbf84f42f, a=0xbf84f42f, c=120, *a=-1081805784
从打印结果看出,在指针赋值时,确实是将c的地址赋值给了a,但为什么a取值取出来的值不对呢?这就涉及到上面讲的指针的值了。结合指针的值是指向存储内容的首地址,来看下下面这张图。
从上图,我们可以很清楚地看出指针在内存中的位置,而这里的问题出就出在取值上。如下分析:
当对&c进行取值时,因为指针类型是char*,那么其指针指向的类型为char,所以只取内存中的首地址0xbf84f42f空间下的内容;
当对a进行取值时,因为指针类型是int*,那么其指针指向的类型为int,所以他取值取的是从0xbf84f42f到0xbf84f4a2这4个字节的值,所以就不可能是仅仅首地址的’x’了。
……………………………………………………………………………………………………………………………
好了,看完这个简单点的例子,我们在回到原来的例子中,同样经过如下分析:
&ch的指针类型为char *[3],那么其指针指向的类型为char [3];
pp的指针类型为char *,那么其指针指向的类型为char ,如果对其取值,其值在内存中的范围为4个字节,与&ch取值所要表达的内容不同。
所以经上面分析,两个值p1与p2是不相等的。
所以切记,请不要对不匹配的指针赋值后进行取值的使用。切记,切记。
针对p3的打印:
这里我们不得不注意p3这个指针并没有进行初始化,所以他指向的内容也是不确定的,可能会指向一个危险的内存区域,所以我们这里要尽量避免对垃圾指针的取值。也可以说我们在声明指针时要尽量对其进行初始化,默认下我们赋值为NULL。
这点很重要,最好的情况是你取到的是垃圾地址接下来你需要对程序进行调试,最坏的情况则会导致程序崩溃。
所以我们在使用指针时,最好应该想想:
1、这个指针的类型是什么?
2、指针指的类型是什么?
3、该指针指向了哪里?
sizeof()的跨度
通过上面的指针类型,我们就可以理解同样是int类型为什么数组首地址加1和普通的变量地址加1所加的跨度不一样了。结合上面的指针类型下面看一段代码:
#include "stdio.h"
int main()
{
int a[5] = {1,2,3,4,5};
int *p = (int *)(&a+1);
printf("%d,%d", *(a+1), *(p-1));
}
其打印出来的值为:
2,5
为什么呢?我们先来分析其各自的指针类型:
1、首先&a的指针类型为int * [5],表示指向包含有5个int内容的数组的指针,以一个数组长度为单位长度;(这里注意,如果没有(int *)的强制类型转换编译器会出现警告)
2、a的指针类型为int [5],表示含有5个int型内容数组的首地址;
所以通过上面分析,a的指针类型为int [5],表示数组首地址,那么a+1则为数组首地址向右移动一个sizeof(int)的距离,单位长度为数组中一个内容的长度,所以为2;
&a的指针类型为int * [5],那么&a+1其实是已经移动了5个sizeof(int)字节了,那么现在指针是指到了数组最后一个元素的后一个元素,即已经越界了!但因为p的指针类型为int * ,所以对于x-1来说是向左移动一个sizeof(int)的距离,也就是说已经指向了数组的最后一个元素,即结果为5!
更具体的关于加减移动的内容,大家可以查看以下链接,本章内容也大多引用至此:
关于C语言指针的问题
下面再来说说函数指针。
函数指针
通常我们在程序编写时调用函数的方式就是直接写一个函数名,再在后面带上参数,如MyFunc(x)。那么我们是否想过这个函数名又是怎样去对应上我们的函数具体实现呢?
借鉴彻底搞定C语言指针详解-完整版-时候初学者-必备
函数指针声明
通数据类型声明一样,我们在使用函数时也需要事先对函数进行声明,函数的首地址也以存储在某个函数指针变量里的。这样,我就 可以通过这个函数指针变量来指向调用所指向的函数了。
比如声明一个可以指向MyFun函数的函数指针变量FunP:
void (FunP*) (int);
这个函数指针就能指向我们的类似void MyFunc(int x)格式的函数了。
函数指针的使用
从上面我们知道可以使用:
返回值类型x (函数名指针Func*) (不同参数类型及个数)来对应其他任何的除函数名不同外的其他函数。
那么我们在编程中如何使用到他们呢,下面来看一段代码:
void MyFun(int x); //这个申明也可写 成:void MyFun( int );
void (*FunP)(int ); //也可申明成void(*FunP)(int x),但习惯 上一般不这样。
int main(int argc, char* argv[])
{
MyFun(10); //这是 直接调用MyFun函数
FunP=&MyFun; //将MyFun函数的地址赋给FunP变量
(*FunP)(20); //这是通过函数指针变量FunP来调用MyFun函数的。
}
void MyFun(int x) //这里 定义一个MyFun函数
{
printf(“%dn”,x);
}
上面FunP=&MyFun这一句就是我们对函数指针的使用。
那么函数指针与我们的变量指针有区别吗?还是有的,请往下看。
调用函数的其它书写格式
函数调用同时也包含其他的格式,如下代码所示:
void MyFun(int x);
void (*FunP)(int );//申明一个用以指向同样参数,返回值函数 的指针变量。
int main(int argc, char* argv[])
{
MyFun(10); //这里是 调用MyFun(10);函数
FunP=MyFun; //将MyFun函数的地址赋给FunP变量
FunP(20); //这是通过函数指针变量来调用MyFun函数的。
return 0;
}
void MyFun(int x) // 这里定义一个MyFun函数
{
printf(“%dn”,x);
}
int main(int argc, char* argv[])
{
MyFun(10); //这里是调用MyFun(10);函数
FunP=&MyFun; //将MyFun函数的地址赋给FunP变量
FunP(20); //这是通过函数指 针变量来调用MyFun函数的。
return 0;
}
int main(int argc, char* argv[])
{
MyFun(10); //这里是调用MyFun(10);函数
FunP=MyFun; //将MyFun函数的地址赋给FunP变量
(*FunP)(20); //这是通过函数指针 变量来调用MyFun函数的。
return 0;
}
int main(int argc, char* argv[])
{
(*MyFun)(10); //函数名MyFun也可以有这样的调用格式
return 0;
}
从以上代码可以看出:
1、函数指针的赋值可以是:
FunP = &MyFun;
FunP = MyFun;
2、函数指针调用可以是:
FunP(20);
(*FunP)(20);
(*MyFun)(20);
这里可以看出以下几点结论:
- 其实,MyFun的函数名与FunP函数指针都是一样的,即都是函数指针。MyFun 函数名是一个函数指针常量,而FunP是一个函数数指针变量,这是它们的关系。
- 但函数名调 用如果都得如(*MyFun)(10);这样,那书写与读起来都是不方便和不习惯的。所以C语言的设计者们 才会设计成又可允许MyFun(10);这种形式地调用,这样能方便很多;
- 为统一起见,FunP函数指针变量也可以FunP(10)的形式来调用。
- 赋值时 ,即可FunP=&MyFun形式,也可FunP=MyFun.
但也请注意,这样的声明时写法是不允许的:
void MyFun(int ); //不能写成void (*MyFun)(int )。
void (*FunP)(int ); //不能写成void FunP(int )。
通过typedef定义某一函数的指针类型
首先看一段代码:
void MyFun(int x); //此处的申明也可写成:void MyFun( int );
typedef void (*FunType)(int ); //这样只是定义一个函数指针类型
FunType FunP; //然后 用FunType类型来申明全局FunP变量
int main(int argc, char* argv[])
{
//FunType FunP; //函数指针变量当然也是可以是局部的 ,那就请在这里申明了。
MyFun(10);
FunP=&MyFun;
(*FunP)(20);
return 0;
}
void MyFun(int x)
{
printf(“%dn”,x);
}
上面代码首先用typedef定义了一个函数指针类型FunType,就如同我们typedef一个结构体一样,typedef之后我们就可以像使用数据类型一样地去使用我们得函数指针类型。
然后利用定义好的函数指针类型声明一个函数Fun变量。
最后在main函数中给Fun变量赋值函数指针&MyFun。当然我们也可以将该函数指针指向其他的相同类型的函数,就如我们的数据类型指针一样。
函数指针作为某个函数的参数
既然函数指针在某种程度上类似于数据指针,那么我们能将函数指针作为某个函数的参数吗?当然可以。
请看下面一段代码。
//设计一个CallMyFun函数,这个函数可以通过参数中的函数指针值不同来分别调用MyFun1、MyFun2 、MyFun3这三个函数(注:这三个函数的定义格式应相同)
void MyFun1(int x);
void MyFun2(int x);
void MyFun3(int x);
typedef void (*FunType)(int ); //②. 定义一个函数指针类型FunType,与①函 数类型一至
void CallMyFun(FunType fp,int x);
int main(int argc, char* argv[])
{
CallMyFun(MyFun1,10); //⑤. 通过CallMyFun函数分别调用三个不同的函数
CallMyFun(MyFun2,20);
CallMyFun(MyFun3,30);
}
void CallMyFun(FunType fp,int x) //③. 参数fp的类型是FunType。
{
fp(x);//④. 通过fp的指针执行传递进来的 函数,注意fp所指的函数是有一个参数的
}
void MyFun1(int x) // ①. 这是个有一个参数 的函数,以下两个函数也相同
{
printf(“函数MyFun1中输出:%dn”,x);
}
void MyFun2(int x)
{
printf(“函数MyFun2中输出:%dn”,x);
}
void MyFun3(int x)
{
printf(“函数MyFun3中输出:%dn”,x);
}
代码中首先利用typedef定义一个函数指针类型;
然后通过void CallMyFun(FunType fp,int x);中的函数指针参数FunType fp作为CallMyFun的参数;
再在后面使用时将对应相同类型的函数名作为调用的CallMyFun函数的参数。