高级指针的概念和应用完整版(图文讲解)

来源:互联网 发布:windows 2012 kms激活 编辑:程序博客网 时间:2024/05/16 11:48
<span style="font-size:18px; font-family: Arial, Helvetica, sans-serif; background-color: rgb(255, 255, 255);"><span style="font-family: Arial, Helvetica, sans-serif;"></span></span>

一:数组指针和指针数组的理解:
<span style="font-family: Arial, Helvetica, sans-serif; background-color: rgb(255, 255, 255);">1.首先我们从一个例子开始讲起</span>

include<stdio.h>

int main()

{

int a[5][5]={0};//创建一个二维整型数组,并初始每个元素化为0;

int(*p)[4]=NULL;//定义一个数组指针,这个指针指向大小为四,并且每个元素为int型的指针,将此指针初始化为空;

p=(int(*)[4])a;//二维数组的数组名a,代表一个大小为5,每个指针指向5个元素的指针数组;而此时的p为一个可以指                       向

                    //四个元素的指针,所以此时要给a强制类型转换为指向四个int类型元素的数组指针,再付给p;

printf("%p,%d",&p[4][2]-&a[4][2],&p[4][2]-&a[4][2]);

return 0;

}

结果为:


详细分析:


对于最后一行的分析:

printf("%p,%d",&p[4][2]-&a[4][2],&p[4][2]-&a[4][2]);


(1)指针减指针输出的是元素个数,又因为p[4][2]的地址小于a[4][2]的地址;

所以如果以%d的格式输出来直接就是-4;

(2)printf函数族中对于%p一般以十六进制整数方式输出指针的值,附加前缀0x。

所以把-4用十六进制输出,数字在内存中以补码的方式存在;

-4的32位二进制

原码为:10000000 00000000 00000000 00000100

反码(符号位不变,其余安位取反):11111111 11111111 11111111 11111011

补码(反码加1):11111111 11111111 11111111 11111100

将此补码转换成十六进制为:fffffffc

所以最后输出的是fffffffc    

(3)总结此题:本题考察的是指针的部署, int(*p)[4]=NULL,像这样的指针加1,则跳过了一个大小为四,每个元素为整型的数组;

  2.再看一个例子:              

#include<stdio.h>
int main()
{
int aa[2][5]={1,2,3,4,5,6,7,8,9,10};
int *prt1=(int *)(&aa+1);
int *prt2=(int *)(*(aa+1));
printf("%d,%d",*(prt1-1),*(prt2-1));
return 0;
}


(1)结果为:


(2)分析:



3.其实在指针这块还是很有趣的,

例如给出这样一个例子

#include<stdio.h>

int main()

{

int a[]={1,2,3,4,5,6};

printf("%d",a[2]);

printf("%d",2[a]);

}

(1)程序输出的结果为:

(2)分析:

因为a[2]相当于*(a+2)

然而2[a]的意义是*(2+a)

因为a代表首元素的地址,为整型,2也为整型,所以a和2顺序可以互换,不影响结果;


4.总结:

(1)数组指针:强调的是指针,它是一个指向数组的指针;int (*arr)[3];

(2)指针数组:强调的是数组,数组里面保存的是指针,数组的每个元素都是指针类型;int*  arr[3];

二:二级指针

1.概念:二级指针用来存放一级指针的地址。

2.格式:char**p

3.二级指针的内存分布:


三:数组参数和指针参数:

1.一维数组参数:

#include<stdio.h>

void fun(char arr[])//此时,可以不写形参数组的大小,因为此时只是定义了一个数组名字,并没有给这个数组开辟内存空间。                              

                            //因为,实参传过去的并不是整个数组,而是数组首元素的地址,所以不能用sizeof在函数内部求取                               

                          //整个数组的大小;求取的是指针的大小,为四个字节。所以,形参可以写成一个指针 void fun(char* arr),

{                           

char c=arr[4];

}                          

int main()

{

char arr[]="abcde";

fun(arr);//函数调用传递整个数组时,实参只需要写数组名即可,不可给数组名之后加下标;加了下标表示数组的某                  

           //个元素;此时其实并没有传递整个数组,因为数组名代表数组首元素的地址。

return 0;

}

2.一级指针参数:

(1)简单举例:

#include<stdio.h>

void fun (char*p)

{

char c=p[3];//等价于char c=*(p+3)

}

int main()

{

char* p ="abcdef"       //此时p的空间中保存地是字符a的地址;

fun(p) ;                      //传递a的地址;

return 0;

}。

3.二维数组参数和二级指针参数:

(1)首先从一例子开始分析:

include<stdio.h>

//viod  fun(int  **arr)。错误:传参时。不能用二级指针接受二维数组。

//viod  fun(int  arr[][])。错误:如果去掉后一个方括号中的数字3,则不能确定传进来的数组指针指向的数组的大小。

//viod  fun(int  arr[][3])  正确

//viod  fun(int  (* arr)[3])   正确

viod fun(int arr[2][3])

{

;

}

int main()

{

int arr[2][3];

fun(arr);  //数组名在传参的时候发生降级,表示数组首元素的地址;

              // 此时,这个二维数组的名表示一个数组指针,指向一个大小为三,每个元素类型为int的指针类型

               //也就是这样int (*)[3];

return 0;

}


(2)深入理解二级指针做形参的情况:

int fun(char** p)

//int fun(char* arr[])

{

;

}

int main()

{

char*pc;//一级指针

char  **ppc;//二级指针

char  *arr[3];/指针数组; 

fun(&pc);//二级指针就是用来存放以及指针的地址的。

fun(ppc)//二级指针传给二级指针,正确。

fun(arr);//因为此时arr为指针数组,数组的每个元素都是指针类型;而我们一再强调,

             //数组名代表数组首元素的地址;从而arr带表它的数组首元素指针的地址;

              //接收指针的地址当然要用二级指针去接收。

return 0;

}

(3)总结:

上面讨论的那么多,我们把二维数组和二维指针参数的等效关系总结一下;

a.当数组的参数为数组的数组,即二维数组时,如char  arr[2][2];

                           所对应的指针参数为 数组指针,char(*)[2]

b.当数组的参数为指针数组,例如:char * arr[5];

  所对应的指针参数为指针的指针,char** ;


c.注意:

int fun(char** p)           //指针形式接受参数

int fun(char* arr[])       //数组形式接受参数

这两个只是在传参的时候起的作用相同,并不相等。

一个是二级指针,一个是指针数组。

d.数组的数组转化为数组的指针的时候,只要把第一维(高位)的改写为指向数组的指针之后,

后面的维数不必改写;

例如:chara[2][3][4]         char(*p)[3][4]


四:函数指针:

1.引入话题;在内存中函数也是有相应的地址;那么我们可以通过地址去访问函数,是问题变得简单。

那么函数的地址改保存在哪了呢,所以我们引入了函数指针来保存函数的地址。

2.写法举例:void (*p)(int ,char); //函数指针变量p,指向一个形参为int,char,函数值为空的函数

           类型:void (*) (int ,char )

注意:void * p (int ,char) //这个是一个函数,函数名叫p,形参类型是int,char。函数值为空。

           (有些地方叫指针函数,意思是返回类型为指针的函数)

3.实力举例:

void* fun(char*str,int a)

{

printf("%s",str);

return NULL;

}

int main()

{

void*  (*pfun)(char*,int )=fun  // 或者写成:void*  (*pfun)(char*,int )=&fun ,,

                                               //     函数名和取地址函数名意思一样:把函数的地址存起来;

pfun("abc",2) ;    //对函数指针的解引用,此时不用在函数指针的前面解引用,因为此时的pfun就是函数的地址

                        //而函数的调用只需知道函数的地址就行了,函数名前的*不管加几个都是起一样的效果,调用函数 。  pfun("hhh",2) ;                                  

}

函数测试结果:


4.难点,怪点分析:

a. (*( void(*)  () )  0  )  ();

  含义:从内往外看,首先定义了一个返回值为void的函数指针类型,然后将0强制转换为此类型;

此时的0就是一个函数地址,然后解引用0;将此部分括起来再传参,就是调用这个0地址函数。

b.void ( *signal(int , viod(*) (int) ) )  (int);

含义:见到这个我们也不要害怕,一层一层的 分析,没有什么过不去的坎,

首先:viod(*) (int) ,这表示一个函数指针,指向参数为int型,返回值为void的函数。

然后:signal(int , viod(*) (int))   表示一个函数名为signal,函数参数为int型和 viod(*) (int)型的函数。

在然后:我们可以看到剩下的壳子是.void ( *)  (int)  这表示一个函数指针,指向参数为int型,返回值为void的函数。

所以最后可以得到,这表示一个函数名字为signal,函数参数为int型和 viod(*) (int)型的函数。这个函数的返回值是一个函数指针,

该指针指向参数为int型,返回值为void的函数。

注:显然我们平常不用这种方法定义一个函数,这样容易让人误解,理解起来比较难。

我们可以用另一种方法定义,用typedef   void(*pfun_t)(int); 将 void(*)(int)这种函数指针类型重命名为型类型名 pfun_t

所以我们可以写成这样:

typedef   void(*pfun_t)(int);

pfun_t   signal(int ,  pfun_t);

五:函数指针数组

1.任何指针都可以放在和它同类型的指针数组中去;

例如:

int *p;

int * arr[10];

arr[0]=p;//将指针变量p放在和arr指针数组的首个指针元素中。

2举例:

a.void  ( *pfunarr[3] ) (char *); 

含义:首先[]的优先级高于*;所以先拿出pfunarr[3],这代表一个大小为三的数组;

剩下的void  ( * ) (char *) 代表一个函数指针类型,说明pfunarr[3]这个数组每个元素的类型为一个函数指针类型;

指向参数类型为char*,返回值类型为void的函数;

b.初始化函数指针数组;

void  ( *pfunarr[3] ) (char *)={0};

c. 程序举例:

#include<stdio.h>void fun1(char* str){printf("%s\n",str);}void fun2(char* str){printf("%s\n",str);}void fun3(char* str){printf("%s\n",str);}int main(){void(*parr[3])(char*)={0};//创建一个函数指针数组,并且初始化为0;    int i=0;parr[0]=fun1;//分别对函数指针数组赋初值;每个函数的地址付给数组;    parr[1]=fun2;    parr[2]=fun3;for(i=0;i<3;i++){parr[i]("nihao");//解引用函数指针;相当于调用函数;}return 0;}
d.用函数指针数组写一个计算器:
</pre><p></p><pre name="code" class="cpp">#include<stdio.h>enum OP//枚举类型{EXIT,//0ADD,//1SUB,//2MUL,//3DIV//4};void menu()//打印菜单{    printf("      1.add      2.sub     \n");    printf("      3.mul      2.div     \n");    printf("      0.exit               \n");}int Add(int num1, int num2)//加{  return num1+num2;}int Sub(int num1, int num2)//减{  return num1-num2;}int Mul(int num1, int num2)//乘{  return num1*num2;}int Div(int num1, int num2)//除{  return num1/num2;//此处应该考虑num2不等于0的情况;}int main(){int input=1;while(input){ int num1=0;int num2=0;int ret=0;menu();printf("请选择>:");scanf("%d",&input);printf("请输入两个操作数>:");scanf("%d%d",&num1,&num2);switch(input){         case ADD://使用枚举类型;增加代码的可读性;不必记住繁琐的数字ret=Add(num1,num2);break;         case SUB:       ret=Sub(num1,num2); break;         case MUL:       ret=Mul(num1,num2); break;         case DIV:       ret=Div(num1,num2); break; case EXIT: break; default: printf("选择错误!"); break;         }printf("ret=%d \n",ret);      }return 0;}

 结果:



显然,这个程序还可以改近;每次输入完成得到结果以后不用出现菜单:

 


改近以后的程序为:

#include<stdio.h>enum OP//枚举类型{EXIT,//0ADD,//1SUB,//2MUL,//3DIV//4};void menu(){printf("      1.add      2.sub     \n");    printf("      3.mul      2.div     \n");    printf("      0.exit               \n");}int Add(int num1, int num2){  return num1+num2;}int Sub(int num1, int num2){  return num1-num2;}int Mul(int num1, int num2){  return num1*num2;}int Div(int num1, int num2){  return num1/num2;//此处应该考虑num2不等于0的情况;}int op(int(*p)(int,int))//创建一个参数为函数指针变量,返回值为int型的函数,参数用于接收不同函数的地址。                        //从而来完成不同的函数功能{int num1=0;int num2=0;printf("请输入两个操作数>:");scanf("%d%d",&num1,&num2);return p(num1,num2);//用函数指针变量调用函数。返回所得的值。}int main(){int input=1;while(input){ int ret=0;menu();printf("请选择>:");scanf("%d",&input);switch(input){         case ADD://使用枚举类型;增加代码的可读性;不必记住繁琐的数字            ret=op(Add);//调用op函数,此时的函数名Add表示Add函数的地址。            printf("ret=%d \n",ret);break;         case SUB:            ret=op(Sub);            printf("ret=%d \n",ret); break;         case MUL:            ret=op(Mul);            printf("ret=%d \n",ret); break;         case DIV:            ret=op(Div);            printf("ret=%d \n",ret); break; case EXIT: break; default: printf("选择错误!"); break;         }      }return 0;}

再次改进以后:
#include<stdio.h>enum OP//枚举类型{EXIT,//0ADD,//1SUB,//2MUL,//3DIV//4};void menu(){    printf("      1.add      2.sub     \n");    printf("      3.mul      2.div     \n");    printf("      0.exit               \n");}int Add(int num1, int num2){  return num1+num2;}int Sub(int num1, int num2){  return num1-num2;}int Mul(int num1, int num2){  return num1*num2;}int Div(int num1, int num2){  return num1/num2;//此处应该考虑num2不等于0的情况;}
<span style="font-family: Arial, Helvetica, sans-serif;">//回调函数</span>
int op(int(*p)(int,int))//创建一个参数为函数指针变量,返回值为int型的函数,参数用于接收不同函数的地址。                        //从而用同一个函数来完成不同的函数功能{int num1=0;int num2=0;printf("请输入两个操作数>:");scanf("%d%d",&num1,&num2);return p(num1,num2);//用函数指针变量调用函数。返回所得的值。}//转移表int (*arr[5])(int,int)={0,Add,Sub,Mul,Div};//建立一个函数指针数组,数组名为arr,有五个元素,每个元素是一个函数指针,                                          //保存函数地址.int main(){int input=1;while(input){ int ret=0;menu();printf("请选择>:");scanf("%d",&input);if(input>=1&&input<=4){        ret=op(arr[input]);printf("ret=%d \n",ret);}else if(input==0){return 0;}else {printf("输入错误!");}}return 0;}
六:函数指针数组指针

看到这里想必大家都烦了吧,怎么这么麻烦,不过大家只要咬牙坚持住,其实马上就完了!

1.先从最简单的开始

以前我们讲过,一个数组的地址可以保存到一个数组指针中去;

例如:  int  arr[10];

           int (*p) [10]=&arr;

2.然后慢慢深入:

建立一个函数指针数组:int ( *p[5] ) ( int , int );

然后根据这个建立一个函数指针数组指针指向上面这个函数指针数组:int (*  (*p)  [5] ) ( int , int ); 

理解:int (*  (*p)  [5] ) ( int , int ); //指向   存放函数指针的数组   的指针

首先(*p)为一个指针,然后剩下这个int (*   [5] ) ( int , int )我们很熟悉的东西;这不就是一个函数指针数组吗!

含义:这是一个函数指针数组指针;指针名叫p;指向五个元素[5];每个元素是一个函数指针类型int (* ) ( int , int ); 

好了:终于写完了。可能有些说的不准确或者错误,请看到的朋友同学说一下,大家共同进步!!!


1 0
原创粉丝点击