• 指针和数组关系初探


     

    作者:朱金灿

    来源:http://blog.csdn.net/clever101

     

    指针是C语言中的精髓。《高质量C++编程指南》的作者林锐就曾说过:不会正确使用指针,肯定算不上是合格的程序员。昨晚我思考了一宿,自认找到了理解指针的正确途径。本文试图通过探究指针和数组的关系去研究指针。

     

    一般的C语言教科书上都会有这样的话:指针就是地址,数组名就是指针的首地址。这些不能不说是错误的,但是却没有深入进去,学生很难有较深的理解。

     

    我认为从本质上看,数组是一个单独的内存块,指针是单独一个内存单元。这个原理也许谁都懂。理解指针和数组,我觉得还要理解编译器的运行原理。比如,简单的如下面的几行代码,你知道编译器做了哪些工作吗?

     

    1.    float x=123.456;  
    2.    double p=3.1415926;
    3.     printf("%7.2f /n",x);
    4.     printf("%7.2lf /n",p);
    5.     printf("%-7.2f /n",x);
    6.     printf("%-7.2lf /n",p);

             

     上面几行代码,在VC中编译往往会出现一个警告:warning C4305: 'initializing' : truncation from 'const double' to 'float'。大意是将double型数据非法截断为float型。这个警告是针对第一行的。也许你会说:123.456不是浮点型吗?原因在哪儿呢?
    这简单的一行程序,对于编辑器来说,要做两个步骤:一是开劈变量,二是斌值。然而,编译器在这两步前就已经做了一步:开劈常量。注意:你会说这个程序里压根儿没有const
    但是,程序在这样处理的:在开劈变量之前,首先把你的程序扫描一遍,凡是你输入的数值、字符全部视为常量存放起来。所以,这么简单的一句话,不仅仅是开劈一个变量,
    在此之前早就开劈了一个常量。它的类型是double,值为2.34。你所要做的,就是提醒译器这是float,你把这行改为:float x = 2.34f;即可。现在你还看不到这个程序和我们要谈的话题的关系,但等下你会看到的。

     

    本文试图通过一些例子来说明问题。首先我想请读者思考下面的问题:

    例子一是那个著名的字符串复制程序:

    1. #include<stdio.h>
    2. #include<iostream.h>
    3. void  strcpy(char* strDin,char* strSrc)
    4. {
    5. while(*strSrc!='/0')
    6.    {
    7.    strDin++=strSrc++;
    8.    }
    9. }
    10. void main()
    11. {
    12. char   p[15]="hello world";
    13. char q[20];
    14. strcpy(q,p);
    15. printf("%s",q);
    16. }

               

    这个程序会有一个错误。错误行为 strDin++=strSrc++;编译器会提示说:=左边应是一个左值。你会问:为什么会有这个错误?我暂时不公布答案,我们继续看例子。

     

    这是一个《高质量C++编程指南》里的例子:

    1. char a[] = “hello”; 
    2. a[0] = ‘X’; 
    3. cout << a << endl; 
    4. char *p = “world”;     
    5. p[0] = ‘X’;           
    6. cout << p << endl;

             

    注意这个程序编译是并没有错误,但运行时会出现内存不能written的错误。为什么?

     

    例子三:

    1. #include <iostream.h>
    2. int main(void)
    3. {
    4. char a[20];
    5. a="hello"
    6. cout<<a<<endl;
    7. return 1;
    8. }

                

     这个程序会有一个错误:'=' : cannot convert from 'char [6]' to 'char [20]'。大意是=不能将char [6]转化为char [20]。你可能会问:为什么?

     

    现在我开始公布我的思考成果。我觉得问题的本质是一个常量与变量的问题,或者说是一个权限问题。为什么 strDin++=strSrc++;这句不能实现字符复制。其实我们把这行代码分解一下就会明白.

    strSrc++;

    strDin++;

    strDin= strSrc;

    首先我们明确strDinstrSrc都是一个字符指针变量,它有权改变放在自己的值。比如比如你可以给strDinstrSrc赋任何值。

    问题是你有权改变自己,却无权改变他人。假设strDin对应的值是1000strDin对应的值是2000

    strSrc++;     // 这时strSrc变为1002

    strDin++;     // 这时strDin变为2002

    strDin= strSrc; //strDin==2002

    你想这样有何不可呢?这样是可以的。但是你不要忘记,strDin是一个字符指针,也就是说strDin是一个内存地址,当strDin==2002时,你想你有权力改变2002这个内存单元的值吗?显然这样是无法复制字符串的。

     

    在例子2中你会看到char a[]变为“Xhello.但是字符串p并没有改变。你可能会为p抱不平:为什么数组a的字符可以改变,而指针p不能改变呢?这时因为“world”是一个常量字符串。char *p = world;  这一句编译器首先是找一个内存块把“world”这个常量字符串存诸起来,然后把首地址赋给p。因为这是一个常量字符串,也就是说它的字符是不能改变的。你可以去读这个字符串,却不能改动这个内存块的任何内容。那为什么数组a可以改变呢?因为数组a开辟的是一个内存块,它对这个内存块既可读又可写。这时你可能会说:数组可以转化为用指针表示,而指针却不一定能转化为数组。这种说法是有一定道理的。

     

    在例子3,为什么不能这样给字符数组a赋值呢?道理是一样的。a="hello";只是把常量字符串的首地址赋给a,但是数组a的首地址是一个常量,在定义时编译器已经开辟了一个空间给它。你可能会问:为什么char a[]="hello";这时因为数组a对开辟的这个内存块拥有绝对的权力,既可以读又可以写。这好比某个人在城东有一块土地,他可以在这块土地上种任何庄稼,但他却不能将这块土地从城东搬到城西。

     

    相对而言,一级指针还好理解点。二级指针就不一样了。特别是二级指针和二维数组之间的关系,更让人捉摸不透。我觉得学习原理的最好方法做实验。对程序而言就是想办法编一些例程去验证,从而得出一些结论。下面是我的一个例程:

    1. #include <stdio.h>
    2. void PrintfArray(int *pArray)
    3. {
    4.     for(int i=0;i<2;i++)
    5.     {
    6.         for(int j=0;j<2;j++)
    7.             printf("%d   ",*(pArray+i*2+j));
    8.     }
    9.     printf("/n");
    10. }
    11. int main(void)
    12. {
    13.     int a[2][2] = {1,2,3,4};
    14.     int **pNum = new int* [2];
    15.     int i=0;
    16.     int j=0;
    17.  for(i=0;i<2;i++)
    18.  {
    19.    pNum[i] = new int[2];
    20.  }
    21.  for(i=0;i<2;i++)
    22.     {
    23.         for(j=0;j<2;j++)
    24.         {
    25.             *((*(pNum+i))+j)= a[i][j];
    26.         }
    27.     }
    28.   for(i=0;i<2;i++)
    29.     {
    30.         for(j=0;j<2;j++)
    31.         {
    32.             printf("%d   ",*((*(pNum+i))+j));
    33.         }
    34.     }
    35.  printf("/n");
    36.  PrintfArray(a[0]);
    37.  PrintfArray(pNum[0]);
    38.   for(i=0;i<2;i++)
    39.   {
    40.       delete pNum[i];
    41.   }
    42.   delete pNum;
    43.   pNum = NULL;
    44.   return 1;
    45. }

                   

    运行结果是:

     

                                                                            

                       指针和数组关系初探程序测试结果图1

     

              

    你可能奇怪:为什么第3行和第2行的输出结果怎么不一样呢?现在我们把它的地址输出来,

    把程序改为:

    1. #include <stdio.h>
    2. void PrintfArray(int *pArray)
    3. {
    4.     for(int i=0;i<2;i++)
    5.     {
    6.         for(int j=0;j<2;j++)
    7.         {
    8.             printf("%d   ",*(pArray+i*2+j));
    9.             printf("%d/n",pArray+i*2+j); // 这里输出形参的地址
    10.         }
    11.     }
    12.     printf("/n");
    13. }
    14. int main(void)
    15. {
    16.     int a[2][2] = {1,2,3,4};
    17.     int **pNum = new int* [2];
    18.     int i=0;
    19.     int j=0;
    20.  for(i=0;i<2;i++)
    21.  {
    22.    pNum[i] = new int[2];
    23.  }
    24.  for(i=0;i<2;i++)
    25.     {
    26.         for(j=0;j<2;j++)
    27.         {
    28.             *((*(pNum+i))+j)= a[i][j];      
    29.         }
    30.     }
    31.   for(i=0;i<2;i++)
    32.     {
    33.         for(j=0;j<2;j++)
    34.         {
    35.             printf("%d   ",*((*(pNum+i))+j));
    36.         }
    37.     }
    38.  printf("/n");
    39.  PrintfArray(a[0]);
    40.  PrintfArray(pNum[0]);
    41.   for(i=0;i<2;i++)
    42.   {
    43.       delete pNum[i];
    44.   }
    45.   delete pNum;
    46.   pNum = NULL;
    47.   return 1;
    48. }

         

    运行结果是:

     

                                       指针和数组关系初探程序测试结果图2

     

    看它们的地址也是连续啊!但是此地址已非彼地址了。在把程序改为:

    1. #include <stdio.h>
    2. void PrintfArray(int *pArray)
    3. {
    4.     for(int i=0;i<2;i++)
    5.     {
    6.         for(int j=0;j<2;j++)
    7.         {
    8.             printf("%d   ",*(pArray+i*2+j));
    9.             printf("%d/n",pArray+i*2+j);
    10.         }
    11.     }
    12.     printf("/n");
    13. }
    14. int main(void)
    15. {
    16.     int a[2][2] = {1,2,3,4};
    17.     int **pNum = new int* [2];
    18.     int i=0;
    19.     int j=0;
    20.  for(i=0;i<2;i++)
    21.  {
    22.    pNum[i] = new int[2];
    23.  }
    24.  for(i=0;i<2;i++)
    25.     {
    26.         for(j=0;j<2;j++)
    27.         {
    28.             *((*(pNum+i))+j)= a[i][j];      
    29.         }
    30.     }
    31.   for(i=0;i<2;i++)
    32.     {
    33.         for(j=0;j<2;j++)
    34.         {
    35.             printf("%d   ",a[i][j]);
    36.             printf("%d/n",&(a[i][j]));
    37.         }
    38.     }
    39.   printf("/n");
    40.   for(i=0;i<2;i++)
    41.     {
    42.         for(j=0;j<2;j++)
    43.         {
    44.             printf("%d   ",*((*(pNum+i))+j));
    45.             printf("%d/n",((*(pNum+i))+j));
    46.         }
    47.     }
    48.  printf("/n");
    49.  PrintfArray(a[0]);
    50.  PrintfArray(pNum[0]);
    51.   for(i=0;i<2;i++)
    52.   {
    53.       delete pNum[i];
    54.   }
    55.   delete pNum;
    56.   pNum = NULL;
    57.   return 1;
    58. }

         运行结果为:

                                  指针和数组关系初探程序测试结果图3

     

             

    这下你该看到了吧,二级指针中行与行的地址并不是连续的,但在二维数组中第二行首元素的地址仅接着第一行末元素的地址。这是因为使用new可以开辟一段连续的内存地址,但两次new之间开辟的地址却不是连续的。这样你就明白了,为什么把二级指针pNum传进函数,却只能把头两个元素输出来。还有在二维数组中aa[0]是同一个东西,都表示数组的首地址,而pNumpNum[0]并不是同一样东西,pNumpNum[0]的地址。不信,你可以把它们都输出来看一下。

       

  • 相关阅读:
    linux办公软件的使用和病毒防范
    需要了解的基本礼仪素养
    遗留问题
    shell基本命令
    shell编程
    遇到过得问题
    mac电脑操作
    Linux编程
    BZOJ 1601 [Usaco2008 Oct]灌水 (建图+mst)
    BZOJ 2653 middle (可持久化线段树+中位数+线段树维护最大子序和)
  • 原文地址:https://www.cnblogs.com/lanzhi/p/6471316.html
Copyright © 2020-2023  润新知