当前位置:首页 > 问答 > 正文内容

《话说程序调试》示例--分享(转载)

茂名印刷厂4年前 (2020-11-04)问答149
印刷厂直印●彩页1000张只需要69元●名片5元每盒-更多报价➦联系电话:138-1621-1622(微信同号)

  4.2流程观察分析法

  流程观察分析法,是通过观察分析程序执行流程,了解程序执行信息、并分析这些信息,从而找出错误原因及位置。如果,程序的输出信息不足以提供程序执行流程,那么,需要在程序的控制结构中插入信息输出(即程序插装)语句,使得程序试运行时能输出相关信息以获取程序执行流程信息,使调试者能根据这些信息进行分析,获知程序运行流程与错误结果间的关系,进而判断出错原因。对于一些新手遇到的“不见程序运行结果”的情形,通过在程序流程的适当位置插入输出语句后,能够了解程序运行的大致情况,从而进入下一步调试。

  例4.1 需求描述:设计程序,将一维数组(元素为int型)中指定位置pos处的1个元素删除。

  程序:

  /*ch4_ex1.c*/

  #include stdio.h

  #define MAX 10

  main()

  int a[MAX],i,pos,num;

  num=MAX;

  printf("Input array a(%d integer numbers):",MAX);

  for(i=0;iMAX;i++)/*输入数组元素*/

  scanf("%d",a[i]);

  printf("Specify the position:");

  scanf("%d",pos);/*输入删除位置,首元从1开始*/

  if(pos1||posMAX)/*删除位置合法性检查*/

  printf("Eroor! Enter a key to continue...\n");

  getchar();

  return;

  while(posMAX) /*将pos及其后的元素复制到前一单元*/

  a[pos-1]=a[pos];

  pos++;

  num--;

  printf("Now,array a:\n");

  for(i=0;inum;i++)/*输出删除后的数组元素*/

  printf("%8d",a[i]);

  程序运行情况:

  程序运行后,根据提示信息Input array a (10 integer numbers):输入数据

  1 2 3 4 5 6 7 8 9 10

  再根据提示信息Specify the position:输入 5,程序没有反应,强行退出程序执行。分析程序出错误情况,从交互信息看,程序已经执行到while循环,可能无法退出循环而表现为没有反应。那么,看看循环体的语句吧。从while语句到程序结束的语句

  while(posMAX) /*将pos及其后的元素复制到前一单元*/

  a[pos-1]=a[pos];

  pos++;

  num--;

  printf("Now,array a:\n");

  for(i=0;inum;i++)/*输出删除后的数组元素*/

  printf("%8d",a[i]);

  中,按算法意图可以看出,while循环是将pos及其后的元素复制到前一单元,在重复复制过程中,pos++应该与复制操作(a[pos-1]=a[pos];)同步进行,但原程序中却没有用花括号将这两条语句括起来构成循环体,先将发现的这个问题改正了

  while(posMAX) /*将pos及其后的元素覆盖前一单元*/

  a[pos-1]=a[pos];

  pos++;

  num--;

  printf("Now,array a:\n");

  for(i=0;inum;i++)/*输出删除后的数组元素*/

  printf("%8d",a[i]);

  运行改正后的程序,结果正常。这样,程序的错误就算排除了。

  更多的时候,情形并非如此,可能不能直接从程序试运行所得结果中了解程序执行流程信息。有些程序在运行时看不到运行结果,或者程序的输出结果看不出程序的执行流程。确实遇到过这样的情形,程序运行后没有任何输出,也就不知道程序运行后做了些什么,是否结束运行还是停在什么位置,这与程序设计风格有关,有些程序甚至在数据键盘输入的语句之前,也没有相应的提示信息输出,应该避免这样的做法。

  例4.3 需求描述:老鼠走迷宫(参见例2.8)

  算法描述:(参见文献[6])以二维数组maze表示迷宫,其中元素为0表示走得通、元素为1表示走不通(受阻),路径只考虑水平和垂直两个方向(上下左右)。假设老鼠从maze[1][1]进入迷宫,而迷宫的唯一出口在maze[m][n](右下角),任一时刻老鼠在迷宫中的位置为maze[i][j],它有四个方向可以试探,设下一个位置为maze[g][h],显然,若向右走一步,则g=i,h=j+1;向上走一步,则g=i-1,h=j。但当老鼠走到迷宫边缘时,不可能再有四个方向可以试探,因此可以将迷宫数组扩大为maze[m+2][n+2],并令第0、m+1行和第0、n+1列元素均为1(受阻)。那么,老鼠走迷宫的步骤如下:

  1) 老鼠走进迷宫入口;

  2) 在当前位置上从右方开始,然后依下、左、上的顺序探测前进方向;

  3) 向可以进入的位置前进,即该位置的maze和mark(标志数组)的对应元素均为0。前进一步后,将原来位置改为当前位置,将标志数组mark的相应元素标志为1(已走过),并将前一位置坐标及进入当前位置的方向入栈;

  4) 重复步骤2和3;

  5) 若找不到前进通路,从原路后退一步(退栈),改变探测方向,再重复步骤2和3,以寻找另一条新的通路。

  6) 重复步骤2-5,直到走出迷宫或宣布无通路为止。

  程序:例2.8改正了语法错误后的函数mazepath()如下

  /*ch2_ex8.c*/

  void mazepath(int move[2][4],int m,int n)

  int i,j,d,g,h;/*d记录方向, i,j 为移动前坐标,g,h为新坐标*/

  mark[1][1]=1;

  top=0;

  i=1;

  j=1;

  d=0;

  do

  g=i+move[0][d];

  h=j+move[1][d]; /*try*/

  if((maze[g][h]==0)(mark[g][h]==0))

  mark[g][h]=1; /*Enter new pos*/

  top++;

  stack[top].i=i; /*old pos push*/

  stack[top].j=j;

  stack[top].d=d;

  i=g;

  j=h;

  d=0;

  else

  if(d3) d=d+1;

  else

  if(top0)

  { i=stack[top].i;

  j=stack[top].j;

  d=stack[top].d;

  top--;

  } /*try again*/

  else

  printf("No path has been found\n");

  } while((g==m)(h==n));/*do-while*/

  printf("Out maze:g=%d,h=%d\n",g,h);

  在例2.8中省略的函数init_array()和disp_path()如下:

  void init_array(int a[MAX_SIZE][MAX_SIZE],int m,int n,int fac)

  { /*将数组元素赋初值fac*/

  int i,j;

  for(i=0;i=m+1;i++)

  for(j=0;j=n+1;j++)

  a[i][j]=fac;

  void disp_path()

  { /*输出走出迷宫的路径*/

  int k;

  for(k=0;k=top;k++)

  printf("row=%d,col=%d\n",stack[k].i,stack[k].j);

  程序运行情况:

  用如图4.2所示的迷宫布局数据,运行该程序。

  运行程序并输入图4.2b)的迷宫矩阵,结果输出:

  Out maze

  row=0,col=0

  row=1,col=1

  该结果显然有问题,因为,既然老鼠走出迷宫了,应该显示出完整的路径,但这里仅显示老鼠到达位置(1,1),路径是不完整的。

  调试:

  首先用数据透视法查看数组mark和maze中的数据是否正确,即在main函数调用新增的如下函数:

  Void disp_array(int a[MAX_SIZE][MAX_SIZE],int m,int n)

  int i,j;

  printf("The array:\n");

  for(i=0;i=row+1;i++)

  for(j=0;j=col+1;j++)

  printf("%3d",mark[i][j]);

  printf("\n");

  将数组maze和mark的元素输出显示后,发现数组中的数据是正确的。这样,把调试重点放到函数mazepath中。该函数主要部分是do循环,也是老鼠走迷宫算法的主体。不妨用流程观察分析法来调试该函数。为此,在mazepath函数的do循环中增加语句

  printf("i=%d\n",i);

  且作为do循环体的第一句。再运行该程序,显示结果为:

  i=1

  Out maze

  row=0,col=0

  row=1,col=1

  从显示结果可以看出,do循环仅仅执行了1(i=1)次。分析该循环控制条件,根据算法描述,只有当老鼠到达最右下角 (m,n)时,才能走出迷宫,也就是说,当(g==m)(h==m)时应结束循环,而继续循环的条件应是上述退出循环条件的非,即(gm)||(hn),但程序中循环体末的}后的循环条件是:

  while((g==m)(h==n));/*do-while*/

  当程序初始进入循环后g、h的取值只可能是0、1、2,所以执行1次就退出循环了。显然,这个条件的表述搞反了。其次,分析if结构,发现

  if(top0)

  { i=stack[top].i;

  j=stack[top].j;

  d=stack[top].d;

  top--;

  else

  printf("No path has been found\n");

  的else分支在输出了"No path has been found"信息、报告没有通路后,没有及时退出该函数,这将在迷宫无通路时导致死循环。因此,将其改正如下

  if(top0)

  { i=stack[top].i;

  j=stack[top].j;

  d=stack[top].d;

  top--;

  } /*try again*/

  else

  printf("No path has been found\n");

  return;

  } while((gm)||(hn));/*do-while*/

  去掉刚刚插入do循环体的语句Printf("i=%d\n",i),再试运行程序。显示如下

  Out maze

  row=0,col=0

  row=1,col=1

  row=1,col=2

  row=2,col=2

  row=2,col=3

  row=3,col=3

  这个结果又出现了新的问题,按算法描述,数组maze的四周为受阻(不可走),怎么会输出0行0列坐标,又出口应该是(3,4)而堆栈中保存的路径却没有该坐标呢?为了找出原因,在if((maze[g][h]==0)(mark[g][h]==0))分支复合语句中,增加如下输出语句:

  if((g==m)||(h==n)) printf("(%d,%d)\n",g,h);

  作为该复合语句中的第一条语句,以查看老鼠是否曾到达坐标(3,4)。运行结果显示,老鼠到达了出口位置(3,4)。那么,为什么堆栈中没有记录呢?分析函数mazepath代码,其4行将top赋初值0,do循环的if分支中第一个坐标入栈前已将top++。这样,使得stack栈底(0)元素不是老鼠曾经所在的位置;同时,老鼠走出迷宫时的最后坐标(3,4)也不在栈所保存的路径中,因为从程序流程发现,其总是将老鼠的上一次所在位置入栈,而当老鼠到达出口时的位置却没有入栈。正确的做法应该是,首先将入口位置压入栈底,而后每前进一步就将当前位置入栈。依据上述分析,应将mazepath函数修改为:

  void mazepath(int move[2][4],int m,int n)

  int i,j,d,g,h;

  mark[1][1]=1;

  top=0;

  i=1;

  j=1;

  d=0;

  stack[top].i=i; /*迷宫入口位置入栈*/

  stack[top].j=j;

  stack[top].d=d;

  do

  g=i+move[0][d];

  h=j+move[1][d];

  if((maze[g][h]==0)(mark[g][h]==0))

  mark[g][h]=1; /*Enter new pos*/

  top++;

  stack[top].i=g; /*old pos push*/

  stack[top].j=h;

  stack[top].d=d;

  i=g;

  j=h;

  d=0;

  else {

  if(d3) d=d+1;

  else

  if(top0)

  { i=stack[top].i;

  j=stack[top].j;

  d=stack[top].d;

  top--;

  } /*try again*/

  else

  printf("No path has been found\n");

  return;

  }while((gm)||(hn));/*do-while*/

  printf("Out maze\n");

  再运行程序,显示结果正确。费了这么多工夫,终于把它调试正确了,听听音乐轻松一下吧。

  好了,轻松之后,我们来讨论一下迷宫布局的一般情形,如果迷宫的入口不是本例算法描述所指定的(1,1)、以及出口也不是位置(m,n),那么只需要简单地修改mazepath函数中的i、j初始化语句,以及do循环末尾的while()条件表达式,即可使程序具有通用性。

  通常情况下,流程观察分析法可以帮助我们了解程序执行的流程,通过分析程序的执行流程,可以发现程序在实现算法时存在的一些差错,从而加以改正。对于算法流程复杂、规模较大的程序调试,该法是有效的方法。

收藏0

发表评论

访客

看不清,换一张

◎欢迎参与讨论,请在这里发表您的看法和观点。