20201231-成信大-C语言程序设计-20201学期《C语言程序设计B》平时自主学习-跟踪调试题参考

文章目录

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
}

调试注意事项

  1. 在VC60中,可以打断点调试,也可以直接使用debug工具调试
  2. 调试时,尽可能一 步步调试,查看内存中变量或是数组中的元素的值的变化
  3. 尽量把程序读懂了再调试
  4. 跟踪过程,要细心,点击时,手不能抖,一下子跑好几步,一定是单步跟踪
  5. 碰到程序自定义函数时,一定要跟踪进入
  6. 有进入,就有返回跳出
  7. 有不需要的循环,如不需要关注的循环体,或是空循环等,可以运行到光标处,提高调试效率
  8. 每一行一条语句,可以分为
    1. 执行前
    2. 执行
    3. 执行后
    4. 一定要注意自己的光标所在的位置
  9. 如果出错,或是弹出窗口,要注意光标所在的当前位置
  10. 跟踪过程中,随时注意内存数据的变化,要知道变化的原因【因为赋值语句的执行,或是赋值表达式动作的执行】
  11. 自定义函数进入和跳出,要特别注意作用域发生的变化

对于跳转后的数组可见性的分析

主函数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已经变得有序了

小题答题情况

20201231-成信大-C语言程序设计-20201学期《C语言程序设计B》平时自主学习-跟踪调试题参考

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

分析:

  • 误将判断搞成了赋值
  • 低级编程引发的血案
  • 再次强调:等于是用双等号,等于是关系运算,等号是赋值运算

题目参考解答

20201231-成信大-C语言程序设计-20201学期《C语言程序设计B》平时自主学习-跟踪调试题参考

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就结束了字符串
  • 跟踪技巧
    • 碰到循环,先找到光标位置
    • 再要运行到光标处,提高效率

题目参考解答

20201231-成信大-C语言程序设计-20201学期《C语言程序设计B》平时自主学习-跟踪调试题参考

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

题目参考解答

20201231-成信大-C语言程序设计-20201学期《C语言程序设计B》平时自主学习-跟踪调试题参考

最后的建议

  • 因为是学习,所以还是把每一行代码读懂啊
  • 要学会调试本身,而不是做题
  • 要学会使用断点
  • 要学会高效调试,特别是程序的运行结果与程序员的预期不一致时
  • 把内存里的数据看清楚,想清楚,就是开始进阶到高级编程阶段的时候
  • 理解:图灵机和冯诺依曼的工作原理
    • 存储程序【写内存】
    • 执行程序【读内存】
上一篇:C语言编程练习6:墓碑上的字符


下一篇:Android开发学习之路-Android6.0运行时权限