指针综合篇

1、指针php

一、指针的概念:用来保存地址的“变量”叫作指针,能够理解成指针是地址的一个别名。数组


例:定义一个×××指针ide

spacer.gif

wKiom1cfdxHw4FQsAAA37X6Ri0A666.png


二、“指针的内容”,“指针所指向的内容”,“指针变量的地址”函数

wKiom1cfdybjUbapAACjM92HHMQ499.png

spacer.gif

  (1)、指针的内容:url

    指针变量p里面存放的是a的地址,也就是0x0018ff44.spa

  (2)、指针所指向的内容:3d

    指针变量p里面存放的地址(0x18ff44)这块空间所对应的值,也就是10,咱们经过*p(解引用)能够访问到这个值。即:*p做为右值时,*p==10,当*p做为左值时表明的就是a这块空间。指针

  (3)、指针的地址orm

    指针p自己有一个地址,由编译器分配的咱们不知道,注意:不要讲指针变量p自己的地址和p所保存的地址(0x0018ff44)相混淆。blog


三、"未初始化的指针"和"NULL指针"

例:定义两个指针变量p1和p2:

int *p1;              //未初始化的指针

int *p2=NULL;         //空指针

*p1=10;

*p2=10;

这样赋值显然是错误的,为何呢???

  试想一下,p1,p2是一个指针变量,因此p1,p2里面应该存放的应该是一个地址。而对于p1咱们没有往里面放地址,这时p1是随机指向的。若是此时解引用*p1,访问到的是块随机的地址,再修改这个随机地址里面的值,假设这个随机空间里面的值很是重要,那你就再也找不回来了,因此一般定义一个指针就将他初始化为NULL。

对于p2:虽然咱们将它初始化为NULL,但它指向的是一块空地址啊!!!向一块空地址里面放东西,根本是不可能的。


四、指针常量:

例:*(int *)200=10;

相信不少人对这个表达是都会产生疑惑,其实很好理解的,200是一个常数,咱们将它强制转化为 int *(也就是将常数200转化成一个×××地址),再对这块地址进行*引用,就访问到一块空间,能够对这块空间进行赋值。


五、常量指针

例:int *p=&100;

让指针指向一个常量,通常用不到。


2、高级指针


一、二级指针概念:

  什么是指针???存放地址的变量就叫作指针,因此二级指针就是存放一级指针地址的指针变量。

wKiom1cfd2rCXgJrAAA5oj3837c189.png

spacer.gif

注意:跟一级指针同样,二级指针变量p2里面存放的是一级指针变量p1的地址,一级指针变量p1里面存放的是a的地址。要想访问一块地址里面的内容可使用间接访问符“*”,因此:

*p2==&p1, *p2就是访问p2这块空间里面存放的地址所对应的内容。

**p2==10,由于*p2获得的结果是p1的地址,在对p1的地址进行解引用*p1就访问到了10.


  例1:分析下面几种状况下能不能做为可修改的左值(可修改的左值必须表明一块空间)

int a=10;

int *cp=&a;


  (1)*cp=20 //能够做为左值,当cp指向a时,*cp放到等号左边表明a这块空间,当*cp放到等号右边表明a这块空间的值。


  (2)&a=20  //错误,&a不能够做为左值,由于他不能表示一块特定的空间,&a获得的结果是a的地址,但并不表明a这块空间,要想使用这块空间,必须进行*引用,*&a=20正确。&a能够做为右值,表明是a的地址这个数值。


  (3)*cp+1     //不能够做为左值,由于*优先级高于+,因此*cp先结合,再加1至关于10+1,不表明一块空间。


  (4)*(cp+1)   //能够做为左值,cp+1先结合,指向a的下一块空间,再对这块空间进行*引用,放在等号左边就表明这块空间。


  (5)++cp  //不能够做为左值,由于++cp只是将cp里面的内容加一,并无进行*引用


  (6)cp++  //不能够做为左值


  (7)*cp++   //能够做为左值,先对cp里面的地址进行*引用。再让cp=cp+1(也就是让cp指向a的下一块空间,由于++优先级高于*)


  (8)++*cp   //不能够做为左值,*cp表明cp所指向的内容,再让其加一,至关于10+1

注意:C中++cp和--cp都不能作左值,C++中++cp能够做为左值。


  例2:const 修饰一级指针,修饰二级指针(const修饰的变量,仍是一个变量,只不过只是可读的 

int const a=10;

int b=30;

  一、a=20;   //错误,const修饰a,因此不能改变a的值


  二、int const *p; //const修饰的是*p,因此不能改变*p

    p=&a;       //正确  

    *p=20;      //错误 不能经过*p改变a的值


  三、const int *p;    //const修饰的是*p

    p=&a;       //正确      

    *p=20;      //错误


  四、int *const p=&a;    //const修饰的是p

     p=&b;          //错误   不能改变p

     *p=20;         //正确     


  五、int const * const p;  //const修饰*p,也修饰p,因此*p,p都不能改变

     p=&b;        //错误     

     *p=20;       //错误

注意const修饰变量的原则是离谁近修饰谁。const int *p与int const *p彻底同样。


二、指针和数组的关系 ,指针数组,数组指针,指针的运算


  2.一、指针和数组的关系:

    不少人都分不清指针和数组之间的关系,严格的来说指针和数组之间不要紧,指针是指针,数组是数组。只不过他们两个均可以经过“*”引用的方式和下标的方式来访问元素而已。


例1:

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

int *p=a;

  a[5]占20个字节的大小,而p只占4个字节的大小,其次p自己有本身的地址,只不过他里面存放的是数组首元素的地址。

要访问3则有两种方式:a[2]或者*(a+2).

  其中*(a+2)就是*的形式访问的,由于a表示首元素的地址,加2表示向后偏移2个×××大小,找到3的地址,在经过*获得3.

在编译器中a[2]会被先解析成*(a+2)访问的。


例2:

wKioL1cfeOvAekVuAAC6r3lQoAM257.png因此必须保持定义和声明的一致性,指针就是指针,数组就是数组。


三、指针数组,数组指针

spacer.gifwKiom1cfeFXidtNwAABQmND7fSo334.png

注意:[]的优先级高于*,指针数组是一个数组,只不过里面的元素所有都是指针。数组指针是一个指针,指向数组的指针,偏移的单位是整个数组。


例1:

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

int (*p2)[6];

p2=a;

  这是错误的,由于指针p2的类型是int [6],因此应该是p2=&a;

int (*p2)[3];

  这样的话p2的类型是int [3],因此p2=(int(*) [3])&a;  要强制转换成数组指针的类型

注意:数组指针“所指向”的类型就是去掉指针变量以后所剩下的内容。

数组指针的类型就是去掉指针后剩下的内容。


例2:int (*p3)[5];

  p3的类型是 int(*)[5];

  p3所指向的类型是 int [5];


四、指针的运算:

   一、指针相减获得的结果是两指针之间元素的个数,而不是字节数。

   二、指针的运算

例1:

struct test

{

int name;

char *pcname;

short data;

char c[2];

}*p;

   假设p的值是0x100000,结构体的大小是12;

则:

  一、 p+0x1=0x10000c    //p指向结构体 则p+1表示的是p+sizeof(test)*1


  二、(unsigned int)p+0x1=0x100001     //p已经被转化为一个无符号的数,加一就至关于两个数相加


  三、(int *)p+0x1=0x100004            //p被转化为int *,因此p+1就表示p+sizeof(int *)*1


例2:假设当前机器小端存储

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

   int *p = (int *)(a + 1);

   int *p1 = (int *)(&a + 1);

     int *p2 = (int *)(( int)&a + 1);

     printf( "*p=%d,*p1=%d,*p2=%d\n" , *p, p1[-1],*p2);


     输出结果:*p=2,*p1=4,*p2=2000000 

解析:p = (int *)(a + 1); a表明首元素地址,加1指向第二个元素,因此是2.

     p1 = (int *)(&a + 1); &a表示数组的地址,&a+1就至关于&a+sizeof(a),p1指向的就是a[3]后面的地址,p1[-1]被解析成*(p1-1),因此是4.

     p2 = (int *)(( int)&a + 1); (int)&a是把数组的地址取出来再转化为一个int类型的数再加1,这时的加1就至关于两个数相加(效果至关于让&a向后偏移一个字节),相加的结果再转化成(int *)地址,使p2指向这个地址。

     当前数组的存储:01 00 00 00 02 00 00 00 ........... 由于&a指向的是01这个字节的位置,(int)&a+1的结果是指向了01的下一个字节,这时在强制类型转换成int *,则p2这时指向的空间的内容就是 00 00 00 02,由于是小端存储,因此大端就是 02 00 00 00,输出后就是2000000.


五、二维数组与二级指针参数

(1)、二维数组作参数:

   二维数组作参数与一维数组作参数同样,传递的都是首元素的地址,只不过二维数组的每一个元素又是一个一维数组 。

例:int arr[5][10];

   这是一个5行10列的×××数组,能够将它当作一个只有5个元素的一维数组,只不过每一个元素又是一个大小为10的一维数组

   咱们来分析一下当arr做为实参是,须要什么类型的形参来接受它???


   arr做为参数时,表明首元素地址,要接受这个地址必须是一个指针或者是一个数组,可是他的每一个元素的类型是int[10]。

   因此咱们能够用一个指向类型为int [10]的数组指针来接受arr[5][10],即:int (*p)[10] 。    

   一样也能够用一个数组来接受arr,即:int arr1[][10],这里第二维的大小不能省略,在这里int arr1[][10] 也能够被解析成int (*arr1)[10];


(2)、二级指针作参数:

   例:char *p[5];

    这是一个大小为5,每一个元素都是char *类型的数组,当他做为实参时,须要什么样类型的形参来接受他呢???

    分析:既然p是一个数组,那么数组在做为实参时,实际上传递过去的是首元素的地址,可是p的每个元素都是一个char *类型,也是一个地址,因此形参的类型应该是char **。


总结:

spacer.gifwKiom1clqGqSbiKWAABFE00QjMY890.png

   数组名只有在sizeof()和取&时才不发生降级,其他地方都表明首元素地址。


六、函数指针


(1)函数指针: 是一个指向函数的指针

声明:  

   例:void (*p)();           首先p是一个指针,它指向一个返回值为空,参数为空的函数。

初始化:

    要明确,函数指针本质仍是一个指针,既然是指针,那么就必须给他进行初始化才能使用它,下面来看一看函数指针的初始化,定义一个返回值为空参数为空的函数:void fun()。

        p=fun;

        p=&fun;

    这两种初始化的方式都是正确的。由于函数名在被编译后其实就是一个地址,因此这两种方式本质上没有什么区别。

调用:

    p();  

   (*p)();

    这两种方式都是正确的。p里面存的fun的地址,而fun与&fun又是同样的。


例:分析一下(*(void (*) () ) 0 ) ()是个什么东东!!!

   首先,void (*) ();是一个返回值为空,参数为空的函数指针类型。

   void (*) () 0 ;   它的做用是把0强制类型转换成一个返回值为空,参数为空的函数指针。

   (*(void (*) () )0) ; 找到保存在0地址处的函数。

   (*(void (*) () ) 0 ) (); 对这个函数进行调用。


用途:

       一、回调函数:用户将一个函数指针做为参数传递给其余函数,后者再“回调”用户的函数,这种方式称为“回调函数”,若是想要你编写的函数在不一样的时候执行不一样的工做,这时就可使用回调函数。回调函数也算是c语言里面为数很少的一个高大上的东西。

        二、转换表:转换表本质上是一个函数指针数组,说白了就是一个数组,只不过数组里面存放的所有都是函数指针。


例:实现一个计算器

要求:有“+、-、*、/”四个功能。用户输入操做数,得出结果。

#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
int Add(int a, int b)
{
  return a + b;
}
int Sub(int a, int b)
{
 return a - b;
}
int Mul(int a, int b)
{
 return a *b;
}
int Div(int a, int b)
{
 assert(b != 0);
 return a / b;
}
int operator(int (*fun)(int,int))   //回调函数
{
  int a = 0;
  int b = 0;
  printf( "请输入操做数:" );
  scanf( "%d%d", &a, &b);
  return (*fun )(a,b);              
}
int main()
{
  printf( "*************************************\n" );
  printf( "*0.exit           1.Add****\n" );
  printf( "*2.Sub           3.Mul****\n" );
  printf( "*4.Div           *********\n" );
  int(*fun[5])(int ,int);         //转换表
  fun[1] = Add;
  fun[2] = Sub;
  fun[3] = Mul;
  fun[4] = Div;
  int input = 1;
  while (input)
  {
    printf( "请选择> " );
    scanf( "%d", &input);
    if (input<0 || input>4)
    {                                             printf( "选择无效\n" );
    }
    else if (input == 0)
    {                                                    break;
    }
    else
    {                                              int ret = operator(fun[input]);
      printf( "%d\n", ret);
    }
  }
   system( "pause");
   return 0;
}



在这个计算器程序中,就使用到了转换表fun[]。fun[]里面存放了加减乘除四个函数,根据input的不一样,fun[input]调用不一样的函数。这种方式与switch() case的功能比较类似,不过转换表比switch()case更简单。


(2)、函数指针数组:

       函数指针数组是一个指针数组,也就是一个数组,只不过里面存放的全都是函数指针。

声明:例: char* (*p[5])(int,int);

       p与[5]先结合成一个数组,因此这是一个大小为5的数组,数组元素的类型是一个函数指针,这个函数指针指向一个返回值为char *,有两个参数,且参数类型为int,int的数组。

能够这样理解:char * (*)(int,int)   p[5];        其中char*(*)(int int)是p[5]的类型。


(3)、函数指针数组的指针:

       函数指针数组的指针是一个数组指针,本质上是一个指针,只不过指向的是一个存放函数指针的数组。

声明:例:char *(*(*p)[5])(int,int);

        *与p先结合成一个指针,因此p是一个指针,指针所指向的是一个大小为5的数组,这个数组存放的是函数指针,这些函数指针所指向的类型是返回值为char *,有两个int型参数的函数。

        能够这样理解: char *(*[5])(int,int) *p;    p是一个指针,类型是char*(*[5])(int,int).


总结:指针数组,是一个数组,里面存放的是指针。

数组指针,是一个指针,指向一个数组的指针。

函数指针数组,是一个数组,里面存放的是函数指针。

函数指针数组指针,是一个指针,指向一个存放函数指针的数组。

其实规律很简单,强调的都是最后两个字,最后两个字是什么他就是什么,而前面的字就是他的类型。



3、求内存和长度的差别:

一、×××数组:

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

printf( "%p\n",a);          //a表明首元素地址

printf( "%p\n",a+1);  //a+1表示第二个元素的地址

printf( "%p\n",&a);      //&a表明整个数组的首地址

printf( "%p\n",&a+1);  //&a表明数组的地址,&a+1则跳过整个数组

printf( "%d\n", sizeof (a));         //a在sizeof()中表明整个数组,    16  

printf( "%d\n", sizeof (a + 0));   //表明&a[0],32位下全部的地址大小都为4                4  

printf( "%d\n", sizeof (*a));     //表示a[0],int占4个字节                   4

printf( "%d\n", sizeof (a + 1));    //表示&a[1],32位下全部的地址大小都为4             4  

printf( "%d\n", sizeof (a[1]));      //表示a[1],int占4个字节                 4

printf( "%d\n", sizeof (&a));        //表明整个数组的地址,32位下全部的地址大小都为4    4

printf( "%d\n", sizeof (&a + 1));     //由于&a表明整个数组地址,&a+1跳过整个数组,但仍是一个地址       

printf( "%d\n", sizeof (&a[0]));       //表示a[0]的地址        4

printf( "%d\n", sizeof (&a[0] + 1)); //表示a[1]的地址        4

printf( "%d\n", sizeof (*&a));         //&a表明整个数组,再*引用访问这块地址,就至关于求取真个数组的大小                   16 


二、字符数组:

char name[] = "abcdef" ;                          //这时字符串不表明首元素地址,而是字符数组的一种初始化方式,而且字符串老是默认以’\0‘结尾

printf( "%d\n", sizeof (name[0]));        //表示a,32位下char大小为1         1

printf( "%d\n", sizeof (&name));          //表示整个数组的地址,32位下地址就是4          4

printf( "%d\n", sizeof (*name));           //表示 a                                  1

printf( "%d\n", sizeof (&name+1));      //表示指向整个数组以后的一块空间,但仍是一个地址  4

printf( "%d\n", sizeof (name+1));         //表示b的地址            4

printf( "%d\n", sizeof (name));              //表明整个数组,还要加上‘\0'      7      

printf( "%d\n", strlen(name));                //求取字符串长度,不包括’\0‘      6

printf( "%d\n", strlen(&name));             //表明整个数组的地址,&name==name,strlen遇到’\0‘停下                          6

printf( "%d\n", strlen(&name + 1));       //是一个随机值,表示指向整个数组以后的一个地址,从这个地址开始向后寻找'\0',由于’\0‘位置不肯定因此是随机值

printf( "%d\n", strlen(name + 1));         //表示b的地址


三、字符指针:

char *name = "abcdef" ;                      //字符串放在等号右边表明首元素地址

printf( "%d\n", sizeof (name[0]));        //如下标形式访问,表明 a         1

printf( "%d\n", sizeof (&name));         //表示字符指针name的地址        4

printf( "%d\n", sizeof (*name));          //经过*访问,表示a                    1

printf( "%d\n", sizeof (&name+1));      //表示指针name以后的一块地址      4

printf( "%d\n", sizeof (name+1));          //表示b的地址                                 4

printf( "%d\n", sizeof (name));              //表明a的地址,                          4

printf( "%d\n", strlen(name));                //表明字符串首元素地址            6

printf( "%d\n", strlen(&name));             //表示指针name自己地址,由于从name开始向后’\0‘的位置不肯定,因此是个随机值

printf( "%d\n", strlen(&name + 1));         //表示指针name自己地址以后的地址,由于’\0‘的位置不肯定,因此是个随机值

printf( "%d\n", strlen(name + 1));             //表明b的地址                   5

相关文章
相关标签/搜索