黑马程序员技术交流社区

标题: 分析调试我自己的一道基础测试题目 [打印本页]

作者: 麻木    时间: 2015-4-25 12:39
标题: 分析调试我自己的一道基础测试题目
本帖最后由 麻木 于 2015-4-25 14:52 编辑

1.程序
1.1程序介绍

从键盘输入6个字符串(仅仅包含英文字母和数字),对着6个字符串从小到大排列并输出结果。(C语言)

1.2程序设计分析

这个是我在基础测试题目环节从黑马报名系统里面下得题目。

初看题目,觉得此题不难,无非是:

*建立一个字符串的数组

*然后在一个循环6次的循环体里面循环输入6次,然后再把输入的字符串放到字符串数组里面

*然后建立一个长度数组来统计相对应的字符串的长度

*最后对长度数组中的元素按大小来排序(注意:对长度数组中的元素排序的时候,同时要对字符串数组进行一样的操作,方便后面输出字符串)

*按数组顺序输出字符串数组中的元素



1.3第一版本的程序如下
  1. /**
  2. *9.从键盘输入6个字符串(仅仅包含英文字母和数字),对这6个字符串从小到大排列并输出结果。(C语言)
  3. */

  4. #include <stdio.h>
  5. int main(int argc, const char * argv[])
  6. {
  7.   //1.新建一个字符串变量用于保存每次输入的字符串
  8.     char* string;
  9.     //2.新建一个字符串数组,保存各个字符串的地址
  10.     char* stringGroup[6];

  11.     //3.定义一个变量保存字符串的长度
  12.     unsigned long length[6];
  13.    
  14.     //4.系统提示用户连续输入6个字符串 回车确定
  15.     printf("请依次输入6个字符串,包含数字和字母,回车确定\n");
  16.    
  17.     //5.循环提示用户 连续输入字符串
  18.     for(int i = 0;i < 6;i++)
  19.     {
  20.         
  21.         //5.1提示用户输入第N个字符串
  22.         printf("请输入第%d个字符串\n",(i+1));
  23.         
  24.         //5.2保存第n个字符串到字符串指针数组的n项
  25.         scanf("%s",string);
  26.    
  27.         //5.3获取相应的字符串的长度并一次保存到长度数组
  28.         length = strlen(string);

  29.      //5.4string赋值给stringGroup数组
  30.      stringGroup[i] = string;

  31.         
  32.     }
  33.    
  34.     //6.新建2个变量 用作交换操作的中间变量
  35.     char *pSwap;
  36.     unsigned long swap;
  37.    
  38.     //7.冒泡排序
  39.     for(int i = 0;i < 6;i++)
  40.     {
  41.         for(int j = i+1;j < 6;j++)
  42.         {
  43.             //7.1当长度数组当前元素大于后面个元素的时候
  44.             if(length > length[j])
  45.             {
  46.                 //7.1.1交换相应索引的长度数组的值
  47.                 swap = length;
  48.                 length = length[j];
  49.                 length[j] = swap;
  50.                
  51.                 //7.1.2交换相应索引的字符串数组的值
  52.                 pSwap = stringGroup;
  53.                 stringGroup = stringGroup[j];
  54.                 stringGroup[j] = pSwap;
  55.             }
  56.             
  57.         }
  58.     }
  59.    
  60.     //8.循环打印出字符串数组
  61.     for(int i = 0;i < 6;i++)
  62.     {
  63.         printf("%s  ",stringGroup);
  64.     }
  65.     return 0;
  66. }
复制代码




1.4程序运行错误结果

结果程序输出大失所望,连续输出最后一次输入的字符串6遍。

例如:最后一次我输入的字符串是“itheima”,则程序运行结果如下:

itheima  itheima  itheima  itheima  itheima  itheima  



2.程序调试
2.1程序错误分析

首先,这个程序没有直接显示错误或者警告,也能运行,说明程序的问题在于算法和逻辑。

于是我采用在主程序最开始的地方加了一个断点一步一步跟踪程序看能有什么发现。



果然,在输入循环第二次的时候 我发现对string的输入字符串,stringGroup同样会变化,代码如下:

   
  1. for(int i = 0;i < 6;i++)
  2.     {  //这个时候i==1,已经是第二次循环了
  3.         printf("请输入第%d个字符串\n",(i+1));
  4.         //第二次字符串输入,例如:第一次我输入的是itheima,第二次我输入的是itcast
  5.         scanf("%s",string);
  6.         length<i> = strlen(string);

  7.      //这时我们会在Xcode下方左边的变量监视窗口神奇的发现stringGroup数组的第一的元素“itheima” 神奇的变为“itcast” 与最近输入的一个字符串一样
  8.      stringGroup<i> = string;  
  9.     }</i></i>
复制代码




2.2程序错误判断

经过分析,我发现了错误的原因。每次循环体string赋值的时候实际上是把string字符串的指针赋值给了stringGroup数组里面,也就是说

连续六次的输入都是string指针赋值到stringGroup数组里面,所以stringGroup数组里全是string指向的字符串。

所以当我们最后一次输入string字符串后,stringGroup数组保留的是6个一样的string字符串。



2.3程序第一次修改

这次我把字符串变量string定义的位置放到输入循环体里面,修改后的代码如下:

   
  1. for(int i = 0;i < 6;i++)
  2.     {  
  3.         char *string;
  4.         //这个时候i==1,已经是第二次循环了
  5.         printf("请输入第%d个字符串\n",(i+1));
  6.         //第二次字符串输入,例如:第一次我输入的是itheima,第二次我输入的是itcast
  7.         scanf("%s",string);
  8.         length<i> = strlen(string);

  9.      //这时我们会在Xcode下方左边的变量监视窗口神奇的发现stringGroup数组的第一的元素“itheima” 神奇的变为“itcast” 与最近输入的一个字符串一样
  10.      stringGroup<i> = string;  
  11.     }</i></i>
复制代码




结果还是一样的问题,那么问题出在哪里呢?



2.4程序第二次修改

于是我接着第二次跟踪调试,发现问题还是出现在string变量定义上面,我原以为每次循环后string变量会释放 然后重新分配。

但是实际上因为我们采用的是静态定义的方法 char *string,所以在for循环的{}后,string不会每次循环都被定义一次 然后被分配一次新的内存空间。



原因找到了,怎么解决这个问题呢?

*用C语言自身的相关的动态分配内存功能来操作,每次输入循环,调用内存分配一次新的空间来接受输入到的字符串,然后赋值给stringGroup,最后在程序结束的时候统一release。

*用OC里面的NSString,因为每次都是在堆里面动态分配内存 alloc,所以也能得到一样的效果,不过出于本题是要用C语言来解决,所以就没有用NSString了。



最终解决方案如下:

既然只是需要开辟6个字符串的内存空间,那么我就用最暴力的办法在程序开头定义6个字符串变量,然后赋值初始化stringGroup数组好了。

最后完整程序如下:

  1. /**
  2. *9.从键盘输入6个字符串(仅仅包含英文字母和数字),对这6个字符串从小到大排列并输出结果。(C语言)
  3. */

  4. #include <stdio.h>
  5. int main(int argc, const char * argv[])
  6. {
  7.     //1.从内存中开辟6个100长的字符串空间
  8.     char string1[100];
  9.     char string2[100];
  10.     char string3[100];
  11.     char string4[100];
  12.     char string5[100];
  13.     char string6[100];
  14.    
  15.     //2.新建一个字符串数组,保存各个字符串的地址
  16.     char* stringGroup[6]={string1,string2,string3,string4,string5,string6};

  17.     //3.定义一个变量保存字符串的长度
  18.     unsigned long length[6];
  19.    
  20.     //4.系统提示用户连续输入6个字符串 回车确定
  21.     printf("请依次输入6个字符串,包含数字和字母,回车确定\n");
  22.    
  23.     //5.循环提示用户 连续输入字符串
  24.     for(int i = 0;i < 6;i++)
  25.     {
  26.         
  27.         //5.1提示用户输入第N个字符串
  28.         printf("请输入第%d个字符串\n",(i+1));
  29.         
  30.         //5.2保存第n个字符串到字符串指针数组的n项
  31.         scanf("%s",stringGroup<i>);
  32.    
  33.         //5.3获取相应的字符串的长度并一次保存到长度数组
  34.         length<i> = strlen(stringGroup<i>);
  35.         
  36.     }
  37.    
  38.     //6.新建2个变量 用作交换操作的中间变量
  39.     char *pSwap;
  40.     unsigned long swap;
  41.    
  42.     //7.冒泡排序
  43.     for(int i = 0;i < 6;i++)
  44.     {
  45.         for(int j = i+1;j < 6;j++)
  46.         {
  47.             //7.1当长度数组当前元素大于后面个元素的时候
  48.             if(length<i> > length[j])
  49.             {
  50.                 //7.1.1交换相应索引的长度数组的值
  51.                 swap = length<i>;
  52.                 length<i> = length[j];
  53.                 length[j] = swap;
  54.                
  55.                 //7.1.2交换相应索引的字符串数组的值
  56.                 pSwap = stringGroup<i>;
  57.                 stringGroup<i> = stringGroup[j];
  58.                 stringGroup[j] = pSwap;
  59.             }
  60.             
  61.         }
  62.     }
  63.    
  64.     //8.循环打印出字符串数组
  65.     for(int i = 0;i < 6;i++)
  66.     {
  67.         printf("%s  ",stringGroup<i>);
  68.     }
  69.     return 0;
  70. }</i></i></i></i></i></i></i></i></i>
复制代码



3.总结
*字符串数组保存的只是每个字符串元素的起始地址,并没有镜像新的内存空间。

*如果是定义一个比较大的字符串数组,例如 char* stringGroup[1000] 那么我这种方式就不科学了,只能用一个循环体,每次在循环体里面new新的字符串。

*如果发现程序的算法和逻辑有错误,那么用单步调试外加变量值监视的方法就能最好的找出问题的缘由。

*指针知识点果然是C及其OC的重点和难点,也是C语言家族的特色,必须要充分理解和掌握。

作者: fantacyleo    时间: 2015-4-25 14:18
错误原因分析还是不到位。
【连续六次的输入都是string指针赋值到stringGroup数组里面,所以stringGroup数组里全是string指向的字符串】这句话意思含糊,也不正确。stringGroup其实是一个指针数组,里面包含6个char *。你最初程序的逻辑,并不是【stringGroup数组保留的是6个一样的string字符串】,而是只保留了一个string字符串,只不过你将它输出了6次。我这么说的依据是stringGroup = string;这条语句,它每次都只是给stringGroup[0]赋值

另外我觉得你对题意的理解不对。【6个字符串从小到大排列】在编程中的通常意义不是按字符串长度排序,而是按字符串的字典顺序排序
作者: 仰望的繁华    时间: 2015-4-25 18:05
同样有这道题,当时我也不知道字符串的大小标准是什么,也是搜了搜。
按照网上说的,首字符对齐比ASCII码。后面不足的长度就看作补零了。
然后比出结果,同样没扣分。所以也不知道题目到底算怎么要求了...

作者: ydy96315    时间: 2015-4-27 06:46
这是真题吗?
作者: 可乐zj    时间: 2015-4-27 07:27
很详细,点个赞




欢迎光临 黑马程序员技术交流社区 (http://bbs.itheima.com/) 黑马程序员IT技术论坛 X3.2