指针使用中一些常见问题

来源:互联网 发布:炒现货白银软件 编辑:程序博客网 时间:2024/06/05 21:33

      我相信很多人都会用指针,也都知道指针的概念。但是用起来或多或少都会出差,也会产生疑惑。我自然也不例外。今天有空就来写一篇文章,把容易混淆的地方做一些说明。

一,指针定义

       指针是一种容纳地址的变量,这一点和其他数据类型相同,比如int型的变量是存放的一个int数,float型的变量存放的是一个浮点数。。。,我们的指 针变量则是存放的内存地址。所以看起来,指针似乎也是一种类型,只不过是组合的数据类型。你定义了一个指针变量,告诉大家这个变量里面存的是一个地址,但 是光知道地址还不行,还得指定这个内存地址存的什么类型的数据。比如你要读这个内存地址,如果不知道类型,你就不知道到底要读多少字节的数据。

        那么既然指针存放的是内存地址,那么使用的时候就要小心了。你必须要保证你的指针指向的内存范围是预知的,不然将会导致灾难性的后果。根据这个原理,1.指针在使用前必须初始化;2,不用的指针要及时设为空。这里举几个容易犯错的地方。

    1.  上面说了,指针是组合数据类型,不是内置的数据类型。那么在定义的时候不要想当然,如

          char * a,b;                 

        这里的意思不是a,b都是char型指针变量,仅仅变量a是指针。根据语义解释的贪心法则,读取”char“后再读取’*‘,因为没有 char *这种数据类型,于是分析了出第一个关键词char,说明这一行定义的变量都和char相关。接着往后,读取“*”,有语义,再读入一个‘a’,有语义, 再读发现逗号是分隔符,所以就将‘*a’作为第二关键词,说明a是一个指针。依次类推,分析出关键词‘,’‘;’,。其中‘,’‘;’大家都懂。 显然这句话的意思就是achar型的指针,bchar型的变量。

      如果想将ab都定义为指针,可以写成

                 char   * a, *b;

       或者 typedef char* pchar; 然后pchar a, b ;

     2. 不能用auto变量的地址去初始化static型指针。例如

       void  fun( )       {          int i;          static int *p=&i; // ( × )          ..............       }
          根据上面的描述,必须要保证指针的指向是预知的。不光是地址范围是可知,类型也要可知。这里i是函数内的主动变量,函数退出后,i就销毁了。p是静态的, 在编译时只初始化一次。那么,在函数第二次,第三次。。。调用时,由于栈的位置不可预知,p实际指向就不是临时变量i的地址了。他实际指向的地址可能被操 作系统存储了其他的内容。这个内容就是未知了。

二,常量指针与指针常量

     const int * p,是常量指针,表示指针指向一个常量,即指向的内容是不能更改的,但是指针可以改变指向。而int  * const  p;则是指针常量,即指向的类容可以改变,但是指针本身是不能更改的,也就是不能改变指向位置。const int * cont p;显然是二者都不能更改的了。在使用的时候一定要想清楚“不能更改”地方。

 1. 显然,一个常量地址不能赋给普通指针,一个常量指针也不能赋给普通指针。例如

      char ch1 = 0;      const char ch2 = 0;      char *p1;      const char *p2;      p1 =  &ch1; //OK      p2 =  &ch1; //OK      p2 =  &ch2; //OK      p2 = p1;     //OK      p1 = &ch1; //error      p1 = p2;    //error
2. 虽然不能直接赋值,但是可以通过强制转换来完成。但是,这也仅仅是能通过编译,在执行的时候就会出错。例:
      char *p= "hello,world";//可以通过编译      *(p+5) = 0;//出错
因为“hello,world”是一个常量字符串(整体就是这个字符串的首地址,如“hello,world”[4]=='o'), 意思就是他是const属性的。按理是不能直接赋给p的,结果编译通过。但是通过p去修改常量,就会报错了。

3. 如果函数的参数是指针,并且函数不会修改指针指向的内容。那么最好将该参数声明为常量指针,防止函数里误操作而修改了指向的内容。另外,用const申明常量比用宏要可靠,因为宏是不做类型检测的。

三,指针和数组

     指针与数组名在使用上有很大的相似处,但是毕竟数组名和指针是不同类型的数据,有本质的区别,不能混淆。先说一下相同的地方,描述不同之处。

    1. 数组访问某个位置的数据也是通过首地址加偏移量来完成的,这一点和指针是一样的。例如

      int a[10] = {0,};      int *p = a;         *(p+5) = 10;//以下四句是等同的      a[5] = 10;      p[5] = 10;     *(a+5) = 10;
   上面四句都是一样的效果。对于多维数组其实也是一样,假如有一个三维数组A[10][10][10],那么A[3][4][1] *(*(*( A+3)+4)+1)等效。

    2.  虽然数组名是首地址,但是他是const属性的。在上面的列子中,p +=5; 可以的,表示指向了a[5], 但是a += 5;则是错误的,因为a不能更改。

    3. 上例中,要p指向a的首地址,可以用p=a;可以p=&a[0];但是不能p=&aa由于是数组首地址,那么第一个元素地址就刚好与指向的地址重合。那么前两句就是正确的。但是,a有数组的含义,&a不单是取得a的首地址,还指明这个地址存放的数据类型是一个数组,一个带10 int元素的数组,而不是int型的数据了。&a+1,表示从a的首地址往后偏移10int数据的长度。

   4. 在函数参数是数组是,自动转换成指针。那么在函数里面用sizeof对数组求长度,就不能得到真正的长度值了。因为函数参数是拷贝方式传递的,为了避免拷贝大批量数据,ANSI就强行规定了。

   5.scanfprintf里,数组名可以带也可以不带‘&’,而指针则不能带。如上列,printf("%s\n", &name);是可以的。printf("%s\n", &p);则是错误的。

四,指针数组,数组指针,函数指针等

1.指针数组。意思是数组的每个元素是一个指针。如:int *p[10]; p是一个有10个元素的数组,每个元素是int型指针。

2.数组指针。意思是指针指向的是一个数组。如:int (*p)[10];p是一个指向有10个int类数据的数组。p+1,移动的是10个int型数据的长度,而不是一个int的长度。

3. 函数指针。函数的调用是通过函数的地址跳转来实现的,那么我们可以用个指针来指向某个函数的起始地址,就可以调用该函数了。同样,光有地址不行,还得定义类型。那么函数指针的类型就是函数原型,包括参数列表,和还回值。例如: int (* p)(char, float);就定义了一个函数指针,该指针可以指向的函数是第一形参是char,第二形参是float,还回值是int的函数。同样,也可以用typedef来定义, typedefint (* FUN)(char, float) ; FUN f1,f2;

4. 指针函数。指针函数则是指还回值是一个指针的函数。如int *p(char, float);说明函数p的返回值是int型指针。

总结一下各个指针的类型:如下

int *ptr;char *ptr;int **ptr;int *ptr[3]; //一维指针数组int (*ptr)[3];//数组指针,指针指向数组,相当于是一个二维数组的指针。int *ptr [3][4];//二维指针数组int *(*ptr)[4]; //指向指针数组的指针,相当于三维数组的指针void* (*ptr)(void*);//指向函数的指针int * pMove();//返回指针的函数int (*p[3])(int);//函数指针数组,函数返回int型数据int (*p)[3][4];//指向二维数组的指针,相当于三维数组int (*p[3])[4];//指针数组的每个元素都是指向二维数组的指针
      还有更加复杂的组合类型的指针。除非是学习需要,不要让你的代码中出现晦涩难懂的句子。所以,这里对那种不大容易使用到的,就不做分析了。

五, void指针

    可以将任意类型的指针赋给void指针,但是反过来却有些差别。在C中,可以将void指针赋给任意类型的指针,但是在C++中,却会导致编译不通过。

根据以上原则,如果你函数参数可以是任意类型的指针,就可以声明为void指针。

在使用时,ANSIGNU标准有些诧异。如p++语句。ANSI认为void没有实际类型,不能进行该操作,因为到底移动几个位置不可知;而GNU默认void类型与char类型一致,就可以进行该操作。

六,malloc free函数

  使用 malloc 函数,请包含 stdlib.hC++ 中是 cstdlib, 而不是使用malloc.h

 出于安全性的考虑,mallocfree必须成对使用,也就是malloc的空间,必须free掉。原因不具体解释。

 malloc必须指定大小,大小不能算错。 -1是禁止的。0在某些系统中是可以的,比如linuxmalloc至少会申请到16个字节的空间。但是申请是有可能失败的,申请后一定要检查是否申请成功,申请成功了再使用。

malloc的还回值是void *,参照上一节void*使用。


原创粉丝点击