文章目录
20201学期《C语言程序设计B》平时自主学习
D13428.C
题干:
请单击此处下载文件D13428.C,然后对程序进行跟踪调试,要求不增加或删除行,测试时输入的数据为6 1 3 2 9 -222。
程序的功能是:从键盘上读入一组整数(最多100个,但若读入的数为-222时,则表示输入结束且-222不算在该组数内),然后对这一组数按从小到大的顺序进行排序,最后将排序后的这一组数输出到屏幕上。
下载的文件内容是:
#include <stdio.h>
#define maxNums 100
#define endFlag -222
int input(int arr[]);
void px(int data[], int n);
void output(int sz[], int n);
int main(void)
{
int num[maxNums], Count;
Count = input(num); //M1
px(num, Count); //M2
output(num, Count); //M3
return 0; //M4
}
//读入数据到arr中,以endFlag表示输入结束且不计endFlag,返回读到数据的个数
int input(int arr[])
{
int i;
printf("Please input numbers:"); //I1
for (i=0; i<maxNums; i++) //I2
{
scanf("%d", &arr[i]); //I3
if (arr[i] = endFlag) //I4
{
break; //I5
}
}
return i; //I6
}
//对data中的n个数按由小到大进行排序
void px(int data[], int n)
{
int i, j, mini, tmp;
for (i=0; i<n-1; i++) //P1
{
mini = i; //P2
for (j=i+1; j<n; j++) //P3
{
if (data[j] < data[mini]) //P4
{
mini = j; //P5
}
}
if (mini != i) //P6
{
tmp = data[mini]; //P7
data[mini] = data[i]; //P8
data[i] = tmp; //P9
}
}
}
//输出sz中的n个数
void output(int sz[], int n)
{
int i;
for (i=0; i<n; i++) //O1
{
if (i % 13 == 0) //O2
{
printf("\n"); //O3
}
printf("%-6d", sz[i]); //O4
}
printf("\n"); //O5
}
调试注意事项
- 在VC60中,可以打断点调试,也可以直接使用debug工具调试
- 调试时,尽可能一 步步调试,查看内存中变量或是数组中的元素的值的变化
- 尽量把程序读懂了再调试
- 跟踪过程,要细心,点击时,手不能抖,一下子跑好几步,一定是单步跟踪
- 碰到程序自定义函数时,一定要跟踪进入
- 有进入,就有返回跳出
- 有不需要的循环,如不需要关注的循环体,或是空循环等,可以运行到光标处,提高调试效率
- 每一行一条语句,可以分为
- 执行前
- 执行
- 执行后
- 一定要注意自己的光标所在的位置
- 如果出错,或是弹出窗口,要注意光标所在的当前位置
- 跟踪过程中,随时注意内存数据的变化,要知道变化的原因【因为赋值语句的执行,或是赋值表达式动作的执行】
- 自定义函数进入和跳出,要特别注意作用域发生的变化
对于跳转后的数组可见性的分析
主函数main的作用域
int main(void)
{
int num[maxNums], Count;
Count = input(num); //M1
px(num, Count); //M2
output(num, Count); //M3
return 0; //M4
}
显然,在主函数的作用域里,只能见到num数组和Count变量
当进入到input函数中时
功能函数input的作用域
//读入数据到arr中,以endFlag表示输入结束且不计endFlag,返回读到数据的个数
int input(int arr[])
{
int i;
printf("Please input numbers:"); //I1
for (i=0; i<maxNums; i++) //I2
{
scanf("%d", &arr[i]); //I3
if (arr[i] = endFlag) //I4
{
break; //I5
}
}
return i; //I6
}
进入到这个作用域之后,可以见到的变量有:
- 循环变量i
- 形参数组arr
- 主函数中的调用语句是:Count = input(num); //M1
- 进入之后,num是实参,实参的大小是确定的
- 发生调用跳转后,可以在watch窗口添加对arr[0],arr[1],arr[2],arr[3],arr[4],arr[5]元素的监视,因为这时,已经看不到num[0],num[1],num[2],num[3],num[4],num[5]了一定要小心这一点,但本质上,这两个数组名指针,指向的是同一块内存空间。因为数组名作用函数的参数,是传的地址,即首地址,数组的首地址
- 还可以看到两个全局变量,即:maxNums和endFlag
程序的出错点
if (arr[i] = endFlag) //I4
分析:
- 这一步,看似在进行比较判断,实质在做一个赋值动作,即endFlag的值,会写入到arr[i]中,第一次时,i的值就是0,也就是数组的首元素被赋值为0,见最后的图,在执行I4之后,num[0]的值变为-222
- 平时写程序,要特别注意这样的低级错误,C的语法,要求相等判断要用
==
,而不是=
再进入到px函数作用域
//对data中的n个数按由小到大进行排序
void px(int data[], int n)
{
int i, j, mini, tmp;
for (i=0; i<n-1; i++) //P1
{
mini = i; //P2
for (j=i+1; j<n; j++) //P3
{
if (data[j] < data[mini]) //P4
{
mini = j; //P5
}
}
if (mini != i) //P6
{
tmp = data[mini]; //P7
data[mini] = data[i]; //P8
data[i] = tmp; //P9
}
}
}
在这个作用域里,内存上可见:
-
i,j,mini,tmp,这些,就是本地变量,也叫局部变量,即这个函数的局部可见可用
-
两个形参变量:data数组 和 n
-
而这里,由于data在获取实参数,本质上就是data指针也指向了主函数的num数组,所以,对data的操作,即为对num的操作
-
这里程序的算法是实现了选择排序
-
显然,对于测试数据
测试时输入的数据为6 1 3 2 9 -222。
第一轮,会选出1和6交换
第二轮,会选出2和6交换
最终,有序的结果是:
1 2 3 6 9
这个即为M2执行后的num数组的状态
如果一步步跟踪程序的执行细节,可以看到选择排序的过程
- 第1次p9执行后,1和6交换
- 第2次p9执行后,2和6交换
- 而回到M2时,数组num已经变得有序了
小题答题情况
D13454.C
程序内容
#include <stdio.h>
int main(void)
{
int iA, iB;
printf("please input x y: "); //M1
scanf("%d%d", &iA, &iB); //M2
if (iA = iB) //M3
{
printf("\nyes\n"); //M4
}
else
{
printf("\nno\n"); //M5
}
return 0;
}
逻辑出错位置:
if (iA = iB) //M3
分析:
- 误将判断搞成了赋值
- 低级编程引发的血案
- 再次强调:等于是用双等号,等于是关系运算,等号是赋值运算
题目参考解答
D13455.C
程序内容
#include <stdio.h>
#include <string.h>
#define N 128
void conj(char *s1, char *s2, char *s3);
int main(void)
{
char s1[N * 2] = "99999", s2[N]="8888888", s3[N * 2]="777777777";
printf("Please input string1: "); //M1
gets(s1); //M2
printf("Please input string2: ");
gets(s2); //M3
conj(s1, s2, s3);
printf("\ns3 = %s\n", s3);
return 0;
}
void conj(char *s1, char *s2, char *s3)
{
int i1, i2, j;
for (i1=0, i2=0; s1[i1]!='\0' || s2[i2]!='\0'; )
{
for (j=0; j<1000; j++)
{
; //本步骤实际没有意义,别管它!
}
if (s1[i1] != '\0')
{
s3[i1 + i2] = s1[i1];
i1++; //C1
}
if (s2[i2] != '\0')
{
s3[i1 + i2] = s2[i2];
i2++; //C2
}
}
s3[i1 + i2] = '\0'; //C3
}
跟踪过程要点
- 字符串和字符数组的内涵关系
- 数组用于装字符串
- 字符串有结束符
\0
- 字符串可以申请很大内存空间,但只使用一部分
- 完成输入后,之后的内存空间里的数据是不用管的,到
\0
就结束了字符串
- 跟踪技巧
- 碰到循环,先找到光标位置
- 再要运行到光标处,提高效率
题目参考解答
D13456.C
程序内容
#include <stdio.h>
#include <string.h>
#define N 128
void rev(char *str);
void conj(char *s1, char *s2, char *s3);
char *up(char *str);
int main(void)
{
char s3[N*2], s1[N*2], s2[N];
printf("Please input string1: ");
gets(s1);
printf("Please input string2: ");
gets(s2);
conj(s1, s2, s3);
printf("\ns3 = %s\n", s3);
return 0;
}
void conj(char *s1, char *s2, char *s3)
{
int i1, i2, j;
for (i1=0, i2=0; s1[i1]!='\0' || s2[i2]!='\0'; )
{
for (j=0; j<1000; j++)
{
printf("H\n%s\n", up(s2));
}
if (s1[i1] != '\0')
{
s3[i1 + i2] = s1[i1];
i1++;
}
if (s2[i2] != '\0')
{
s3[i1 + i2] = s2[i2]; //C1
i2++;
}
}
s3[i1 + i2] = '\0';
rev(s3);
up(s3);
}
void rev(char *str)
{
int i, len, mid;
FILE * fp;
len = strlen(str);
mid = len / 2 + 10;
for (i=0; i<mid; i++)
{
char tmp;
tmp = str[i];
str[i] = str[len - i -1];
str[len - i -1] = tmp;
}
fp = fopen("temp.txt", "w");
if (fp == NULL)
{
printf("file open error");
}
else
{
fprintf(fp, "%s", str);
printf("%f", len);
fclose(fp);
}
}
char *up(char *str)
{
static int m=1;
char *old = str;
while (*str)
{
if (*str >= 'a' && *str <= 'z')
{
*str -= 'a' - 'A' - 1;
*str = (*str - 'A' + m) % 26 + 'A';
}
else if (*str >= 'A' && *str <= 'Z')
{
*str += 'a' - 'A' + 1;
*str = (*str - 'a' + m) % 26 + 'a';
}
str++;
}
m++;
m %= 10;
return old;
}
本题特点
程序无编译连接错误,其功能和意义不需要了解,也不需要读懂!!!
跟踪过程要点
- 自定义的函数,一定要step into
- 系统的库函数,一定要step over
题目参考解答
最后的建议
- 因为是学习,所以还是把每一行代码读懂啊
- 要学会调试本身,而不是做题
- 要学会使用断点
- 要学会高效调试,特别是程序的运行结果与程序员的预期不一致时
- 把内存里的数据看清楚,想清楚,就是开始进阶到高级编程阶段的时候
- 理解:图灵机和冯诺依曼的工作原理
- 存储程序【写内存】
- 执行程序【读内存】