前言
该篇不深度讨论程式运行超时和超过内存大小限制这两方面,该篇面向新手,大佬勿喷(ಥ﹏ಥ)
首先我们对遇到的问题简单地进行分类
1.数值精度发生差别
这种情况极大概率是使用的是double还是float的原因,换着用一下基本能修好,如果要用的精度比这两种类型更高的话就要上高精度算法了。
2.生成了一个不应该生成的很大很乱的数
这种情况也要另分几个不同的情况
(一)数组越界
这里我们首先观察下标变量和数组有效值位置的关系。
看指向数组下标的变量是不是指到数组范围外了。
这里的arr数组的下标范围是0~9,但打印到了下标为10的元素,此时指向的就是内存中arr数组后的一个地址,这个地址没被初始化之前被解析出来的值是无法提前知道的,相当于一个随机的值。
那如果指向数组内但没被初始化的部分呢?
我们可以看到下标为10的元素没被我们手动初始化,但打印出来的值并不像上面那么乱。这就叫做数组的不完全初始化,没被手动初始化的部分默认值为0。
(二)变量还没有初始化也没赋值就使用
我们可以看到最大最小值都找出来了,最小值下标也正确了,但最大值下标却出现了问题。
分析其中的原因我们就可以发现
我们先把最大最小值赋值为了数组的第一个值,然后从数组的第一个元素开始比较,如果这个值比最大值(最小值)大(小)就更新一下最大值(最小值)并更新标记下标的变量。大体逻辑是没问题。
让我们一步一步来看;循环开始时先是用数组下标为0的值与MAX和min比较;第一个if语句比较了MAX和arr[0],他们的值其实是一样的,所以不符合if的判断条件,语句块不执行,M都没得到更新;而在后面的比较中没有一个值是比arr[0]大的,所以M就一直得不到更新。
同时M没有初始化为对应arr[0]的下标,这样他就会是一个很乱的值;最后进造成了这个打印结果。
改成这样程序就能正常运行了
(三)数值溢出
涉及数据的储存。我们这里只用int来举例,其它情况可以自行类比或者看其它专门说这个的文章。我们知道int类型的大小是4个字节也就是32个比特。
同时二进制最高位是符号位,0表示正数,1表示负数。
因此int的数据范围是-2147483648~2147483647;unsigned int的范围是 0~4294967295。
那如果我们进行2147483647+1会得出什么呢?
居然是-2147483648,这是为什么呢?
首先我们要知道2147483647的二进制表达为0111 1111 1111 1111 1111 1111 1111 1111;
那么在他进行加1之后就会一直进位直到变成1000 0000 0000 0000 0000 0000 0000 0000;
此时他的最高位是1,是个负数;
负数在内存中储存的是补码,转化为原码才是真实值。最终就变成了-2147483648
数值的关系大概是这样子了。
3.数值与差别较小
造成这种情况的原因很多,我们放到跟“其他”一起讲吧。这种可能是哪里多走了一步,哪里又多加了一个值等。
4.死循环
发生这种情况首先就去检查每个循环的判断条件是否有写错的情况,接着去检查循环变量,看看有没有发生循环变量的变化跟判断条件对不上的情况。
我们来打印5*5的”死循环“
#include
int main()
{
for (int i = 0; i < 5; i++)//打印5*5的死循环
{
for (int j = 0; j < 5; i++)
{
printf("死循环 ");
}
printf("\n");
}
return 0;
}
而代码的打印结果却是这样的,原因是内循环的循环变量变化为i++而不是j++,j<5的条件就持续成立。
#include
int main()
{
for (int i = 0; i < 5; i++)//打印5*5的死循环
{
for (int j = 0; i < 5; j++)
{
printf("死循环 ");
}
printf("\n");
}
return 0;
}
这样的打印代码打出来的结果也是一样的,原因不用我说了吧。
5.打印类型和打印目标类型不匹配
也就是指printf()中使用的打印类型(%d,%f,%c等)和对应的打印位置的类型(int,float,char等)没有一一对应导致的。
#include
int main()
{
int a = 5;
float b = 5.0f;
double c = 5.0;
char d = 'L';
printf("正确打印:\n");
printf("%d\n",a);
printf("%f\n",b );
printf("%lf\n",c );
printf("%c\n", d);
printf("错误打印:\n");
printf("%f\n", a);
printf("%lf\n", b);
printf("%d\n",c );
printf("%d\n", d);
return 0;
}
至于这个结果的原因,同样实际到数据存储等内容,这里就不详细展开讲了。哪天我有空再来仔细说一下吧。
6.其它
我们程序发生的错误往往不能用三言两语来概括,但不管是怎样的错误,我们都可以通过一个利器解决——调试。
接下来我将以vs2022为例子简单说明常用的调试方法,适用于轻量级程序。
先来讲一下简单调试会用到的操作:
1.开始调试;2.逐步进行;3.调用监视,内存等;4.设置断点;
接下来我们以用一个快速排序来作为例子讲解调试
#include
void my_qsort(int arr[], int left, int right)
{
if (left >= right)
{
return;
}
int i = left;
int j = right;
int pivot = arr[i];//确定轴
while (i < j)
{
while (arr[j] >= pivot && i < j) j--;//从右边开始找,找到第一个比轴小的数,并放到i的位置(轴的左边)
arr[i] = arr[j];
while (arr[i] <= pivot && i < j) i++;//从左边开始找,找到第一个比轴大的数,并放到j的位置(轴的右边)
arr[j] = arr[i];
}
arr[i] = pivot;//把轴放好
my_qsort(arr, left, i - 1);//左递归
my_qsort(arr, i + 1, right);//右递归
}
#define max 10
int main()
{
int arr1[max];
for (int i = 0; i < max; i++)
{
scanf("%d", &arr1[i]);
}
my_qsort(arr1, 0, max - 1);
for (int i = 0; i < max; i++) printf("%d ", arr1[i]);
return 0;
}
首先我们开始调试
然后我们会来到这个界面
我们需要注意三个地方:
1.当前执行到的位置;2.输入窗口;3.监视窗口;
我们进到这里之后应该第一时间在监视窗口这里放入我们需要观察的变量,点击”添加要监视的项“后输入变量名称
然后我们点击f10(逐过程),这样箭头就会一行一行向下移动,走过一行代表执行完成这一行。
在我们走到scanf这里之后会发现监视窗口会变灰,这就轮到输入窗口出动了。我们在输入窗口内输入要输入的值之后点击回车。然后我们接着点f10,值就一个一个输入到数组里了,同时我们可以在监视窗口看到数组内的变化。
如果要输入的数比较少还好,可以一旦要输入成百上千的数的话我们这样一个一个f10地点怕不是要点到手废掉。这时候我们就要请出”断点“了。
我们在31行左侧灰色那行点一下就会出现一个红点,这个点就叫做断点,再次点击能够取消。
设置完断点之后我们点一下f5(跳出),然后我们就发现箭头跳到了红点处,并且值也全部都输入进去了。
然后我们接着点f10一步一步走来调试,可是我们发现箭头一下子就走完了31行,这不对吧?!我们要进入这个函数内部一步一步来看他的过程才对。
那该怎么做呢?
这里提供2个方法,一是在函数内部设置一个断点,这样就可以通过f10进入函数了。
二是箭头指向函数的时候点击f11(逐语句),这样他也会进入函数,此方法也适用于进入库函数。
接下来就是一步一步地走并观察监视内的变化,这样就可以找出程序的bug了(吧?),上面提到的情况3也是这么处理。
除了上面的操作,我们还有观察内存地址,寄存器,反汇编等操作
平时写一些简单的代码不需要用,感兴趣的可以研究一下,我们这里再简单讲一下内存要怎么看。内存窗口其实就是调试页面上面那个窗口啦。
我们在”地址:“这一栏输入要观察的值的地址。我怎么知道那个值的地址是多少啊!
如果我们要看数组,就直接在这里面输入数组名就好了,因为数组名代表数组首元素地址,后面的值是按顺序存放的。
如果是别的一些的话我们可以使用&变量名来得到他的地址,比如
回车后
这样我们就可以看到一个值在内存中存储的情况了。
那么如果我们所处的环境不能使用编译器的调试功能怎么办?
你知道吗,我们的大脑竟然有860 亿个神经元!!!这无异于一台超级电脑!!!遇到这种情况就在自己的大脑内一直摁f10吧,记不住数值可以拿出纸笔。
我们遇到问题的时候千万不要害怕,要冷静下来仔细思考。
该篇意在引导新手遇到程序结果错误时的思考思路以及抛砖引玉,大家要是好的建议欢迎在评论区讨论,让我学习一下%%%%%