指针全解析

        C语言中,指针式一个难点。主要表现在两个方面:1、各种类型的指针众数繁多。2、各种类型的指针如何应用。但是由于,指针有着运算速度快的优势,如果你进行操作系统,关注操作系统的内核(以linux为例),你就会发现操作系统来说更多运用到指针,究其原因,一个很重要的原因就是处理速度快。那么,今天,就把C语言的指针做个总结。


第一、 指针和变量

  其表现形式只有一种:指针变量

常数是不能直接幅值给指针的,如果想给指针赋给常数,需要借用变量。具体如下操作:

  int num=8;
  int *pnum=#

  这时我们需要注意,pnum指向8存储的地址,*pnum为该地址存储的数据8。   

     指针变量的运算

指针变量不能相加 不能相乘 也不能相除

如果两个指针变量指向的是同一块连续空间中的不同存储单元,

则这两个指针变量才可以相减


第二、 指针和数组

       1、数组名是一个指针变量是一个地址,它存放的是数组首元素的地址。

          在分析数组的时候,要注意到三点:

  1)数组名代表的是什么;

  2)数组元素如何表示;

 3)数组作为函数参数表示什么,如何使用

如果这两个问题弄清楚了,相信在编程中就不会出错。     

    下标和指针的关系:如果p是个指针变量,则p[i]永远等于 *(p+i)

               
             另外,指针和数组有着天然的联系,其实数组就是一个连续地址存放着常数,我们看下面一个例子:         

                          int arry[3]={1,3,5};

             那么arry就是该数组的首地址,*arry就是该数组首地址存放的数据1,*(arry+1)则为该数组的第二个位置存放的数据3。
        从以上分析可以看出:数组名可以看成指针,可以把数组名当做指针来操作

注意:

                          sizeof运算与数组,指针。

        char a[] = "hello";
	char *p = "hello";
	char b[2][3];
	printf("_______%d\n",sizeof(a));//6
	printf("_______%d\n",sizeof(b));//4
	printf("_______%d\n",sizeof(p));//6
                 分析:数组名虽然可以看成指针,但是本质是不一样的。

          静态数组存放在全局变量区,或者栈上,是一个有固定大小的内存区域;而指针可以存放在任何区域,其内容是一个地址;所以,sizeof a 得出数组的大小;但是sizeof p只是指针的大小;sizeof b也是其数组的大小,虽然没有初始化。


       2、其表现形式有三种:数组元素的指针、数组指针、指针数组

          数组元素的指针:

 int a[5];
 int *p;
 p = &a[0];//p=a;作用是将数组首元素的地址赋值给p;
           /****
          或者 int *p=a; 作用是将数组首元素的地址赋值给p;因为数组名就是首地址;
         等价于 int *p = &a[0];
            *****/
    注意:
                int a[10];

               int  *p = &a                      

     此时p还是指向a的首元素,但含义不同,p具有行指针的含义,p+1不再是a[1],而是跳过40的字节,指向数组末尾a[9]的下一个元素。

数组指针:a pointer to an array,即指向数组的指针,也称为行指针,注意:它是指针,不存在数组名一说

指针数组:array of pointers,即用于存储指针的数组,也就是数组元素都是指针

                  指针数组最频繁的用处是存放不同长度的字符数组

下面举例说明数组指针与指针数组的区别:

int (*a)[4] 数组指针       表示:一个指向一维数组的指针  

                元素表示:(*a)[i]

         

int* a[4] 指针数组          表示:一个数组元素都是指针的数组

                           数组名:数组名a表示数组的第一个元素,即:a[0]

                           元素表示:*a[i] *(a[i])是一样的,因为[]优先级高于*

例如:
a 、int *data[3] 为指针数组,数组中每个元素为一个指向int型数据的指针,赋值如下:
  int arry[3]={1,3,5};

  int *data[3]={arry,arry+1,arry+2};

    分析:

   (1)数组名:data为data数组的首地址;不存在*data一说;

   (2)数组元素的表示及其地址的表示:

                     因为指针数组中都是指针,所以,要表示数组元素的值,要加上*,否则只是地址。

            元素的地址: data[0]为指向arry首地址,

                                  data[1]为指向arry第二个元素的地址,

            元素的值:*data[0]为该地址存放的数据1,

                               *data[1]为该地址存放的数据3。


b、int(*data)[3]为数组指针,指向int型数组,赋值如下:
  int arry[3]={1,3,5};
  int (*data)[3]=&arry;

 分析:

   (1)表示一指针,所以没有数组名一说。表示*data有3个元素,每个元素为整型,也就是说data指向一个有3个整型元素的数组。

            另外,数组名可以看做指针,所以,数组指针有点二级指针的味道,不是一个,而是一组二级指针。

   (2)指针所指向的数组元素的表示:

    元素的地址: *data、data[0]是第一个元素的地址,

                           data[1]是第二个元素的地址, 但*data+1不是第二个元素的地址,它指向数组的末尾;    

    元素的值:**data、*data[0]就为首元素的值1,**data就是二级指针

                      *data[1]为第二个元素的值3。


      3、二维数组  

        int data[3][4]:一维数组的数组                    

(1)数组名:因为二维数组的数组名是一个数组指针,所以data就是一个数组指针。

                          数组名data代表二维数组首行(一维数组)的首地址,还记得上面说过数组指针就是行指针么;

                          data+1代表第一行的首地址,data+2代表第二行的首地址

(2)数组元素的的值及其地址:

         地址的确定:要先使用行指针定位在哪一行,然后在这一行中定位出列;    

     地址及其值:

          data[0]是首行数组的数组名,因为数组名代表数组首元素地址,所以

          data[0]表示一维数组data[0]中第0列元素的地址,即&data[0][0];

          data[0][1]和*(data[0]+1)都表示首行第二个元素; 

       同理

          data[1]代表第二行的首地址,也代表了一维数组data[1]中第0列元素的地址,即&data[1[0];


     4、数组作为函数参数

一维数组作参数:退化为一级指针

二维数组作参数:退化为行指针

指针数组做参数:退化为二级指针

【分析】一维数组的数组名就是一级指针;二维数组的数组名就是行指针;指针数组的数组名是一级指针,而数组元素又是一个指针,所以是二级指针。例如main函数的参数。

【结论】数组作为形参,看数组名的函数含义

    5、指针做函数参数

一级指针作为形参:偏移量是sizeof 指针类型  

二级指针作为形参:偏移量是一行

行指针作为形参:偏移量是一行


一级指针作为形参:可以用来遍历一维数组 

二级指针作为形参:可以用来遍历指针数组,不可以遍历二维数组
                               可以给实际参数是一级指针变量申请堆空间

行指针作为形参:可以用来遍历二维数组

         【结论】函数形参的类型与遍历对象对应

第三、 指针与函数

其表现形式有两种:函数指针指针函数

函数指针是指向函数的指针变量,即本质是一个指针变量。

指针函数是指带指针的函数,即本质是一个函数。函数返回类型是某一类型的指针。

      例如:

 int (*f) (int x); /* 声明一个函数指针 */

 f=func; /* 将func函数的首地址赋给指针f */

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

   int *f(x,y)

1、指针函数:

格式:

类型说明符 * 函数名(参数)

当然了,由于返回的是一个地址,所以类型说明符一般都是int。
例如:int *GetDate();
           int * aaa(int,int);
函数返回的是一个地址值,经常使用在返回数组的某一元素地址上。例如:

int * GetDate(int wk,int dy);

        main()
        {
            int wk,dy;
            do
            {
                printf(Enter week(1-5)day(1-7)\n);
                scanf(%d%d,&wk,&dy);
            }
            while(wk<1||wk>5||dy<1||dy>7);
            printf(%d\n,*GetDate(wk,dy));
        }

        int * GetDate(int wk,int dy)
        {
            static int calendar[5][7]=
            {
               {1,2,3,4,5,6,7},
               {8,9,10,11,12,13,14},
               {15,16,17,18,19,20,21},
               {22,23,24,25,26,27,28},
               {29,30,31,-1}
            };
            return &calendar[wk-1][dy-1];
        }
程序应该是很好理解的,子函数返回的是数组某元素的地址。输出的是这个地址里的值。

  2、函数指针

       (1)其函数类型由:返回类型和参数表决定

       (2)定义和使用。因为() 运算优先级高于*,所以要给指针加上()。

        函数指针在linux操作系统中很常见,现在拿个简单的例子来进行说明:

int print(int a, int b ,char *p)
  { 
             printf("in put number sum is %d,input string is %s\n",a+b,p);//三个参数,int int和char*
        return a+b;
        }
  int main()
  {
  int (* pprint)(int,int,char *);//定义指向函数print的指针函数*pprint,三个参数,int int和char*
  pprint=print;//给指针函数赋值
  int c;
  c=pprint(3,6,var);
  printf("%d\n",c);//输出print的return值
  }

  从上面这个例子中可以看出,一个函数其实就是从一个地址开始的特殊功能程序,其函数名就为该程序的首地址,所以可以这么给指针函数赋值:pprint=print;//给指针函数赋值

       注意使用typedef可以简化函数指针。见下例:

#include<stdio.h>
#include<stdlib.h>

int inc(int a)
{
   return (++a);
}
int multi(int *a,int *b,int *c)
{ 
  return (*c=*a* *b);
}
typedef int (*FUNC1)(int in);
typedef int (*FUNC2)(int*,int*,int*);
void show(FUNC2 fun,int arg1,int *arg2)
{
  FUNC1 p=&inc;
  int temp=p(arg1);
  fun(&temp,&arg1,arg2);
  printf("%d\n",*arg2);
}
int main()
{ 
   int a;
   show(multi,10,&a);
   system("pause");
   return 0;
}
输出:110。
分析:typedef int (*FUNC1)(int in);//定义了一个函数指针,FUNC1

           FUNC1 p=&inc;//等价于FUNC1 p= inc;

           此时,我们可以使用   p(a); 或者 inc(a);

第四、 指针与结构体
  这个也是一个很有趣的方面,先看一个例子:
  struct PERSON
  {
  char *pername;
  int age;
  }person;
  如果想给*pername幅值,那么可以如下操作:
  person.pername="jack";//用指针,可以完成赋值
  如果结构体定义为
  struct PERSON
  {
  char pername[20];
  int age;
  }person;

  那么person.pername[20]="jack"是不能通过的,究其原因就是结构体为抽象数据类型,不分配存储单元,所以数组赋值不通过。如果采用指针赋值,那么可以解决这类问题。


第五、const与指针       

       有了const修饰的变量,我们不称它为变量,而称符号常量。const 的作用是不能在它处重新赋新值该变量了。

       另外,我 们还要知道格式的写法。有两种:const int ic=20;与int const ic=20;。它们是完全相同的。这一 点我们是要清楚。总之,你务必要记住const 与int哪个写前都不影响语义。
       有了这个概念后,我们来看 这两个家伙:const int * pi与int const * pi ,按你的逻辑看,它们的语义有不同吗?呵呵,你只要记住一点,int 与const 哪个放前哪个放后都是一样的,就好比const int ic;与int const ic;一样 。也就是说,它们是相同的。

       好了,我们现在已经搞定一个“双包胎”的问题。那么 int * const pi与前两个式子又有什么不同呢?我下面就来具体分析它们的格式与语义吧!

const int * pi(指向的变量值不能修改,但地址可以修改)int const * pi

//*************代码开始 ***************
int i1=30;
int i2=40;
const int * pi=&i1;
pi=&i2;    //4.注意这里,pi可以在任意时候重新赋值一个新内存地址
i2=80;    //5.想想看:这里能用*pi=80;来代替吗?当然不能
printf( “%d”, *pi ) ;  //6. 输出是80
//*************代码结束***************

int * const pi(地址不能修改,变量值可以修改)

//*************代码开始 ***************
int i1=30;
int i2=40;
int * const pi=&i1;
//pi=&i2;    4.注意这里,pi不能再这样重新赋值了,即不能再指向另一个新地址。
   //所以我已经注释了它。
i1=80;    //5.想想看:这里能用*pi=80;来代替吗?可以,这 里可以通过*pi修改i1的值。
     //请自行与前面一个例子比较。
printf( “% d”, *pi ) ;  //6.输出是80
//***************代码结束 *********************
看了这段代码,你明白了什么?有没有发现 pi值是不能重新赋值修改了。它只能永远指向初始化时的内存地址了。


总结:

变量指针、数组指针、函数指针都是指针;

指针数组是数组,常用于二维数组;指针函数是函数,返回值是一个地址,常用int型;

const int * pi 与 int const * pi(指向的变量值不能修改,但地址可以修改)

int * const pi(地址不能修改,变量值可以修改)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值