zoukankan      html  css  js  c++  java
  • C中的几组指针

     

    1、二维数组

       下面就三种二维数组进行说明。

       1: int **Ptr; 
       2: int *Ptr[ 5 ]; 
       3: int ( *Ptr )[ 5 ]; 

       以上三例都是整数的二维数组,都可以用形如 Ptr[ 1 ][ 1 ] 的方式访问其内容;但它们的差别却是很大的。下面我从四个方面对它们进行讨论:    

    1.1、内容

           它们本身都是指针,它们的最终内容都是整数。注意我这里说的是最终内容,而不是中间内容,比如你写 Ptr[ 0 ],对于三者来说,其内容都是一个整数指针,即 int *;Ptr[ 1 ][ 1  ] 这样的形式才是其最终内容。 

    1.2、意义

     

       1: int **Ptr 表示指向"一群"指向整数的指针的指针。 
       2: int *Ptr[ 5 ] 表示指向 5 个指向整数的指针的指针。 
       3: int ( *Ptr )[ 5 ] 表示指向"一群"指向 5 个整数数组的指针的指针。 

     

    1.3、所占空间

           (1)、int **Ptr 和 (3)、int ( *Ptr )[ 5 ] 一样,在32位平台里,都是4字节,即一个指针。 
           (2)、int *Ptr[ 5 ] 不同,它是 5 个指针,它占5 * 4 = 20个字节的内存空间

    1.4、用法

           (1)、int **Ptr  
           因为是指针的指针,需要两次内存分配才能使用其最终内容。首先,Ptr = ( int ** )new int *[ 5 ];这样分配好了以后,它和(2)的意义相同了;然后要分别对 5 个指针进行内存分配,例如: 
      Ptr[ 0 ] = new int[ 20 ]; 
      它表示为第 0 个指针分配 20 个整数,分配好以后, Ptr[ 0 ] 为指向 20 个整数的数组。这时可以使用下标用法 Ptr[ 0 ][ 0 ] 到Ptr[ 0 ][ 19 ] 了。 
          如果没有第一次内存分配,该 Ptr 是个"野"指针,是不能使用的,如果没有第二次内存分配,则 Ptr[ 0 ] 等也是个"野"指针,也是不能用的。当然,用它指向某个已经定义的地址则是允许的,那是另外的用法(类似于"借鸡生蛋"的做法),这里不作讨论(下同)。 
          (2)、int *Ptr[ 5 ] 
          这样定义的话,编译器已经为它分配了 5 个指针的空间,这相当于(1)中的第一次内存分配。根据对(1)的讨论可知,显然要对其进行一次内存分配的。否则就是"野"指针。 
          (3)、int ( *Ptr )[ 5 ] 
          这种定义我觉得很费解,不是不懂,而是觉得理解起来特别吃力,也许是我不太习惯这样的定义吧。怎么描述它呢?它的意义是"一群"指针,每个指针都是指向一个 5 个整数的数组。如果想分配 k 个指针,这样写: Ptr = ( int ( * )[ 5 ] ) new int[ sizeof( int ) * 5 * k ]。这是一次性的内存分配。分配好以后,Ptr 指向一片连续的地址空间, 
    其中 Ptr[ 0 ] 指向第 0 个 5 个整数数组的首地址,Ptr[ 1 ] 指向第1 个 5 个整数数组的首地址。 
       综上所述,我觉得可以这样理解它们: 
       int ** Ptr <==> int Ptr[ x ][ y ]; 
       int *Ptr[ 5 ] <==> int Ptr[ 5 ][ x ]; 
       int ( *Ptr )[ 5 ] <==> int Ptr[ x ][ 5 ]; 
       这里 x 和 y 是表示若干的意思。

    2、函数指针和指针函数

    2.1、通常的函数调用

    一个通常的函数调用的例子: 

       1: void MyFun(int x);  //此处的申明也可写成:void MyFun( int ); 
       2: int main(int argc, char* argv[]) 
       3: { 
       4: MyFun(10);  //这里是调用MyFun(10);函数 
       5: return 0; 
       6: } 
       7: void MyFun(int x)  //这里定义一个MyFun函数 
       8: { 
       9: printf(“%d
    ”,x); 
      10: } 

     

    这个MyFun函数是一个无返回值的函数,它并不完成什么事情。这种调用函数的格式你应该是很熟悉的吧!看主函数中调用MyFun函数的书写格式: 
    MyFun(10); 
    我们一开始只是从功能上或者说从数学意义上理解MyFun这个函数,知道MyFun函数名代表的是一个功能(或是说一段代码)。 直到学习到函数指针概念时。我才不得不在思考:函数名到底又是什么东西呢? 【js中函数】
    (不要以为这是没有什么意义的事噢!呵呵,继续往下看你就知道了。)

    2.2、函数指针变量的申明

    就象某一数据变量的内存地址可以存储在相应的指针变量中一样,函数的首地址也以存储在某个函数指针变量里的。这样,我就可以通过这个函数指针变量来调用所指向的函数了。 
    在C系列语言中,任何一个变量,总是要先申明,之后才能使用的。那么,函数指针变量也应该要先申明吧?那又是如何来申明呢?以上面的例子为例,我来申明一个可以指向MyFun函数的函数指针变量FunP。下面就是申明FunP变量的方法: 
    void (*FunP)(int) ;  //也可写成void (*FunP)(int x); 
    你看,整个函数指针变量的申明格式如同函数MyFun的申明处一样,只不过——我们把MyFun改成(*FunP)而已,这样就有了一个能指向MyFun函数的指针FunP了。(当然,这个FunP指针变量也可以指向所有其它具有相同参数及返回值的函数了。)

    2.3、通过函数指针变量调用函数

    有了FunP指针变量后,我们就可以对它赋值指向MyFun,然后通过FunP来调用MyFun函数了。看我如何通过FunP指针变量来调用MyFun函数的: (具体可参考C++ 虚函数表解析

       1: void MyFun(int x);  //这个申明也可写成:void MyFun( int ); 
       2: void (*FunP)(int ); //也可申明成void(*FunP)(int x),但习惯上一般不这样。 
       3: int main(int argc, char* argv[]) 
       4: { 
       5: MyFun(10);  //这是直接调用MyFun函数 
       6: FunP=&MyFun;  //将MyFun函数的地址赋给FunP变量 
       7: (*FunP)(20);  //这是通过函数指针变量FunP来调用MyFun函数的。 
       8: } 
       9: void MyFun(int x)  //这里定义一个MyFun函数 
      10: { 
      11: printf(“%d
    ”,x); 
      12: } 

    运行看看。嗯,不错,程序运行得很好。 
    哦,我的感觉是:MyFun与FunP的类型关系类似于int 与int *的关系。函数MyFun好像是一个如int的变量(或常量),而FunP则像一个如int *一样的指针变量。 
    int i,*pi; 
    pi=&i;  //与FunP=&MyFun比较。
    (你的感觉呢?) 
    呵呵,其实不然—— 。。。。。

     

    2.4、调用函数的其它书写格式

    函数指针也可如下使用,来完成同样的事情: 

     

       1: void MyFun(int x); 
       2: void (*FunP)(int );  //申明一个用以指向同样参数,返回值函数的指针变量。 
       3: int main(int argc, char* argv[]) 
       4: { 
       5: MyFun(10);  //这里是调用MyFun(10);函数 
       6: FunP=MyFun;  //将MyFun函数的地址赋给FunP变量 
       7: FunP(20);  //这是通过函数指针变量来调用MyFun函数的。 
       8: return 0; 
       9: } 
      10: void MyFun(int x)  //这里定义一个MyFun函数 
      11: { 
      12: printf(“%d
    ”,x); 
      13: } 

    运行试试,啊!一样地成功。 
    咦? 
    FunP=MyFun; 
    可以这样将MyFun值同赋值给FunP,难道MyFun与FunP是同一数据类型(即如同的int 与int的关系),而不是如同int 与int*的关系了?(有没有一点点的糊涂了?) 
    看来与之前的代码有点矛盾了,是吧!所以我说嘛! 
    请容许我暂不给你解释,继续看以下几种情况(这些可都是可以正确运行的代码哟!): 
    代码之三:

     

       1: int main(int argc, char* argv[]) 
       2: { 
       3: MyFun(10);  //这里是调用MyFun(10);函数 
       4: FunP=&MyFun;  //将MyFun函数的地址赋给FunP变量 
       5: FunP(20);  //这是通过函数指针变量来调用MyFun函数的。 
       6: return 0; 
       7: } 

    代码之四:

     

       1: int main(int argc, char* argv[]) 
       2: { 
       3: MyFun(10);  //这里是调用MyFun(10);函数 
       4: FunP=MyFun; //将MyFun函数的地址赋给FunP变量 
       5: (*FunP)(20); //这是通过函数指针变量来调用MyFun函数的。 
       6: return 0; 

    7: }

    
    
    真的是可以这样的噢! 
       1: int main(int argc, char* argv[]) 
       2: { 
       3: (*MyFun)(10);  //看,函数名MyFun也可以有这样的调用格式 
       4: return 0; 
       5: } 

    你也许第一次见到吧:函数名调用也可以是这样写的啊!(只不过我们平常没有这样书写罢了。) 
    那么,这些又说明了什么呢? 
    呵呵!依据以往的知识和经验来推理本篇的“新发现”,我想就连“福尔摩斯”也必定会由此分析并推断出以下的结论: 
    1. 其实,MyFun的函数名与FunP函数指针都是一样的,即都是函数指针。MyFun函数名是一个函数指针常量,而FunP是一个函数数指针变量,这是它们的关系。 
    2. 但函数名调用如果都得如(*MyFun)(10);这样,那书写与读起来都是不方便和不习惯的。所以C语言的设计者们才会设计成又可允许MyFun(10);这种形式地调用(这样方便多了并与数学中的函数形式一样,不是吗?)。 
    3. 为统一起见,FunP函数指针变量也可以FunP(10)的形式来调用。 
    4. 赋值时,即可FunP=&MyFun形式,也可FunP=MyFun。 
    上述代码的写法,随便你爱怎么着! 
    请这样理解吧!这可是有助于你对函数指针的应用喽! 
    最后补充说明一点:在函数的申明处: 
    void MyFun(int );  //不能写成void (*MyFun)(int )。 
    void (*FunP)(int );  //不能写成void FunP(int )。 
    (请看注释)这一点是要注意的。 

    2.5、定义某一函数的指针类型

    就像自定义数据类型一样,我们也可以先定义一个函数指针类型,然后再用这个类型来申明函数指针变量。 
    我先给你一个自定义数据类型的例子。 

     

       1: typedef int* PINT;  //为int* 类型定义了一个PINT的别名 
       2: int main() 
       3: { 
       4: int x; 
       5: PINT px=&x;  //与int * px=&x;是等价的。PINT类型其实就是int * 类型 
       6: *px=10;  //px就是int*类型的变量 
       7: return 0; 
       8: } 

     

    根据注释,应该不难看懂吧!(虽然你可能很少这样定义使用,但以后学习Win32编程时会经常见到的。) 
    下面我们来看一下函数指针类型的定义及使用:(请与上对照!) 

     

     1: void MyFun(int x);  //此处的申明也可写成:void MyFun( int ); 
       2: typedef void (*FunType)(int );  //这样只是定义一个函数指针类型 
       3: FunType FunP;  //然后用FunType类型来申明全局FunP变量 
       4: int main(int argc, char* argv[]) 
       5: { 
       6: //FunType FunP;  //函数指针变量当然也是可以是局部的 ,那就请在这里申明了。 
       7: MyFun(10); 
       8: FunP=&MyFun; 
       9: (*FunP)(20); 
      10: return 0; 
      11: } 
      12: void MyFun(int x) 
      13: { 
      14: printf(“%d
    ”,x); 
      15: } 

    首先,在void (*FunType)(int ); 前加了一个typedef 。这样只是定义一个名为FunType函数指针类型,而不是一个FunType变量。 
    然后,FunType FunP;  这句就如PINT px;一样地申明一个FunP变量。 
    其它相同。整个程序完成了相同的事。 
    这样做法的好处是: 
    有了FunType类型后,我们就可以同样地、很方便地用FunType类型来申明多个同类型的函数指针变量了。如下: 
    FunType FunP2; 
    FunType FunP3; 
    //……

     

    2.6、函数指针作为某个函数的参数

    既然函数指针变量是一个变量,当然也可以作为某个函数的参数来使用的。所以,你还应知道函数指针是如何作为某个函数的参数来传递使用的。 
    给你一个实例:
    要求:
    我要设计一个CallMyFun函数,这个函数可以通过参数中的函数指针值不同来分别调用MyFun1、MyFun2、MyFun3这三个函数(注:这三个函数的定义格式应相同)。 【类似C#中delegate】
    实现:代码如下: 

     

    1: void MyFun1(int x); 
       2: void MyFun2(int x); 
       3: void MyFun3(int x); 
       4: typedef void (*FunType)(int ); //②. 定义一个函数指针类型FunType,与①函数类型一至 
       5: void CallMyFun(FunType fp,int x); 
       6: int main(int argc, char* argv[]) 
       7: { 
       8: CallMyFun(MyFun1,10);  //⑤. 通过CallMyFun函数分别调用三个不同的函数 
       9: CallMyFun(MyFun2,20); 
      10: CallMyFun(MyFun3,30); 
      11: } 
      12: void CallMyFun(FunType fp,int x) //③. 参数fp的类型是FunType。 
      13: { 
      14: fp(x);//④. 通过fp的指针执行传递进来的函数,注意fp所指的函数是有一个参数的 
      15: } 
      16: void MyFun1(int x) // ①. 这是个有一个参数的函数,以下两个函数也相同 
      17: { 
      18: printf(“函数MyFun1中输出:%d
    ”,x); 
      19: } 
      20: void MyFun2(int x) 
      21: { 
      22: printf(“函数MyFun2中输出:%d
    ”,x); 
      23: } 
      24: void MyFun3(int x) 
      25: { 
      26: printf(“函数MyFun3中输出:%d
    ”,x); 
      27: } 

     

    输出结果:略 
    分析:(看我写的注释。你可按我注释的①②③④⑤顺序自行分析。)

    指针函数

    一个函数不仅可以带回一个整型数据的值,字符类型值和实型类型的值,还可以带回指针类型的数据,使其指向某个地址单元。

    返回指针的函数,一般定义格式为:

    类型标识符  *函数名(参数表)

    int *f(x,y);

    其中x,y是形式参数,f是函数名,调用后返回一个指向整型数据的地址指针。f(x,y)是函数,其值是指针。

    如:char *ch();表示的就是一个返回字符型指针的函数,请看下面的例题:

    在C中函数返回值默认为int

    【例】将字符串1(str1)复制到字符串2(str2),并输出字符串2。

     

     1: #include "stdio.h"
       2:  
       3: main()
       4:  
       5: {
       6:  
       7: char *ch(char *,char *);
       8:  
       9: char str1[]="I am glad to meet you!";
      10:  
      11: char str2[]="Welcom to study C!";
      12:  
      13: printf("%s",ch(str1,str2));
      14:  
      15: }
      16:  
      17: char *ch(char *str1,char *str2)
      18:  
      19: {
      20:  
      21: int i;
      22:  
      23: char *p;
      24:  
      25: p=str2 
      26: if(*str2==NULL) exit(-1);
      27:  
      28: do
      29:  
      30: {
      31:  
      32: *str2=*str1;
      33:  
      34: str1++;
      35:  
      36: str2++;
      37:  
      38: }while(*str1!=NULL);
      39:  
      40: return(p);
      41:  
      42: }

    通过分析可得

    函数指针是一个指向函数的指针,而指针函数只是说明他是一个返回值为指针的函数,函数指针可以用来指向一个函数

    3、数组指针和指针数组

    3.1、数组指针(也称行指针)

    定义 int (*p)[n];
    ()优先级高,首先说明p是一个指针,指向一个整型的一维数组,这个一维数组的长度是n,也可以说是p的步长。也就是说执行p+1时,p要跨过n个整型数据的长度。

    如要将二维数组赋给一指针,应这样赋值:

       1: int a[3][4];
       2: int (*p)[4]; //该语句是定义一个数组指针,指向含4个元素的一维数组。
       3:  p=a;        //将该二维数组的首地址赋给p,也就是a[0]或&a[0][0]
       4:  p++;       //该语句执行过后,也就是p=p+1;p跨过行a[0][]指向了行a[1][]

     

    所以数组指针也称指向一维数组的指针,亦称行指针。

    3.2、指针数组

    定义 int *p[n];
    []优先级高,先与p结合成为一个数组,再由int*说明这是一个整型指针数组,它有n个指针类型的数组元素。这里执行p+1是错误的,这样赋值也是错误的:p=a;因为p是个不可知的表示,只存在p[0]、p[1]、p[2]...p[n-1],而且它们分别是指针变量可以用来存放变量地址。但可以这样 *p=a; 这里*p表示指针数组第一个元素的值,a的首地址的值。
    如要将二维数组赋给一指针数组:

       1: int *p[3];
       2: int a[3][4];
       3: for(i=0;i<3;i++)
       4: p[i]=a[i];

    这里int *p[3] 表示一个一维数组内存放着三个指针变量,分别是p[0]、p[1]、p[2]。所以要分别赋值。

     

    这样两者的区别就豁然开朗了,数组指针只是一个指针变量,似乎是C语言里专门用来指向二维数组的,它占有内存中一个指针的存储空间。指针数组是多个指针变量,以数组形式存在内存当中,占有多个指针的存储空间
    还需要说明的一点就是,同时用来指向二维数组时,其引用和用数组名引用都是一样的。
    比如要表示数组中i行j列一个元素:
    *(p[i]+j)、*(*(p+i)+j)、(*(p+i))[j]、p[i][j]


  • 相关阅读:
    Python 类中方法的内部变量,命名加'self.'变成 self.xxx 和不加直接 xxx 的区别
    用foreach遍历 datagridView 指定列所有的内容
    treeView1.SelectedNode.Level
    YES NO 上一个 下一个
    正则 单词全字匹配查找 reg 边界查找 精确匹配 只匹配字符 不含连续的字符
    抓取2个字符串中间的字符串
    sqlite 60000行 插入到数据库只用不到2秒
    将多行文本以单行的格式保存起来 读和写 ini
    将秒转换成时间格式
    richtextbox Ctrl+V只粘贴纯文本格式
  • 原文地址:https://www.cnblogs.com/keanuyaoo/p/3313205.html
Copyright © 2011-2022 走看看