实用调试技巧

watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBAWXV1ZWtpIFhZMzk=,size_16,color_FFFFFF,t_70,g_se,x_16

每一次写代码时,都要想好思路,理清逻辑顺序,而不是直接开写,到最后这改改那改改完事,拒绝-迷信式调试


目录

Debug和Release的介绍。

最常使用的几个快捷键:

实例一:实现代码:求 1!+2!+3! ...+ n! ;不考虑溢出。

再来看这道题,结果是什么?

模拟实现库函数strcpy

const的使用方法:

 练习2:模拟strlen

编程的常见错误


 

Debug和Release的介绍。


Debug 通常称为调试版本,它包含调试信息,并且不作任何优化,便于程序员调试程序。
Release 称为发布版本,它往往是进行了各种优化,使得程序在代码大小和运行速度上都是最优
的,以便用户很好地使用
watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBAWXV1ZWtpIFhZMzk=,size_19,color_FFFFFF,t_70,g_se,x_16


最常使用的几个快捷键:


F5
启动调试,经常用来直接跳到下一个断点处。
F9
创建断点和取消断点
断点的重要作用,可以在程序的任意位置设置断点。
这样就可以使得程序在想要的位置随意停止执行,继而一步步执行下去。
F5通常与F9搭配使用

F10
逐过程,通常用来处理一个过程,一个过程可以是一次函数调用,或者是一条语句。
F11
逐语句,就是每次都执行一条语句,但是这个快捷键可以使我们的执行逻辑进入函数内部(这是
长用的)。
CTRL + F5
开始执行不调试,如果你想让程序直接运行起来而不调试就可以直接使用。


实例一:实现代码:求 1!+2!+3! ...+ n! ;不考虑溢出。

int main()
{
	int i = 0;
	int sum = 0;//保存最终结果
	int n = 0;
	int ret = 1;//保存n的阶乘
	scanf("%d", &n);
	for (i = 1; i <= n; i++)
	{
		int j = 0;
		for (j = 1; j <= i; j++)
		{
			ret *= j;
		}
		sum += ret;
	}
	printf("%d\n", sum);
	return 0;
}

我们会发现, 我们输入3的话,结果等于15,而不等于9。问题出在哪里呢?

经过我们调试可得,是保存到ret的值,没有初始化为1,把上一次结果也存下来了。所以我可以改写成:

int main()
{
	int i = 0;
	int sum = 0;//保存最终结果
	int n = 0;
	scanf("%d", &n);
	for (i = 1; i <= n; i++)
	{
	    int ret = 1;//保存n的阶乘
		int j = 0;
		for (j = 1; j <= i; j++)
		{
			ret *= j;
		}
		sum += ret;
	}
	printf("%d\n", sum);
	return 0;
}

当然,这也不是我最优写法。我可以在2!上乘以3,就是3的阶乘,没必要从1开始依次乘以。

这样一来:我就可以这样写:

int main()
{
	int i = 0;
	int sum = 0;//保存最终结果
	int n = 0;
	int ret = 1;
	scanf("%d", &n);
	for (i = 1; i <= n; i++)
	{
		ret = ret * i;
		sum += ret;
	}
	printf("%d\n", sum);
	return 0;
}

这样一层循环就可以搞定。


再来看这道题,结果是什么?

int main()
{
	int i = 0;
	int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
	for (i = 0; i <= 12; i++)
	{
		arr[i] = 0;
		printf("hehe");
	}
	return 0;
}

watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBAWXV1ZWtpIFhZMzk=,size_20,color_FFFFFF,t_70,g_se,x_16

 在Vs2019的环境中,结果竟然是死循环,为什么?

经过调试可得:我i=11时,即便越界了,但是没有报错,并强制把arr[11]改成了0。最后改arr[12]的时候,竟然就把i也改掉了,为什么。

watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBAWXV1ZWtpIFhZMzk=,size_12,color_FFFFFF,t_70,g_se,x_16原来是i的地址,和arr[12]的地址是一样的,我改arr[12],也就改了i的值。所以i永远不会>12,所以造成了死循环。

为什么?

watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBAWXV1ZWtpIFhZMzk=,size_20,color_FFFFFF,t_70,g_se,x_16

 因为在Debug,X86的编译器环境下,我们先创建了i,再创建了数组。数组都是局部变量,都是在栈区创建的。栈区的使用习惯是先使用高地址,再使用低地址。数组却又是随着下标的增长,地址是由低到高变化的。如果arr数组和i之间刚好空了两个整形的空间的话,也就是说,当我数组越界访问,就很有可能使用arr[12]的时候,其实就是i,就改掉了我的i,造成了死循环。

在Vc6.0的环境中,可能写成10就死循环了,中间没有空出一个整形的空间。所以他非常依赖环境。不同的环境也是不同的。而且release版本会对代码进行一定优化,可能就不会出现死循环的情况。

watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBAWXV1ZWtpIFhZMzk=,size_20,color_FFFFFF,t_70,g_se,x_16但最终造成这个原因的是越界访问。


优秀代码的特点:1. 代码运行正常 2. bug很少 3. 效率高 4. 可读性高 5. 可维护性高 6. 注释清晰 7. 文档齐全。

常见的coding技巧: 1. 使用assert 2. 尽量使用const 3. 养成良好的编码风格 4. 添加必要的注释 5. 避免编码的陷阱。


示范:

模拟实现库函数strcpy

watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBAWXV1ZWtpIFhZMzk=,size_20,color_FFFFFF,t_70,g_se,x_16

两个参数,一个目标字符串,一个源头。而且“including the termination null character”,包括结束的空字符串 \0 。

watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBAWXV1ZWtpIFhZMzk=,size_20,color_FFFFFF,t_70,g_se,x_16

 注:传参传的都是首元素的地址。
        Null都是指\0

接下来模拟实现:

void my_strcpy(char*dest, char*src)
{
	while (*src!='\0')
	{
		*dest = *src;
		dest++;
		src++;
	}
	*dest = *src;
}
int main()
{
	char arr1[] = "hello world";
	char arr2[] = "xxxxxxxxxxxxxxxxx";
	my_strcpy(arr2, arr1);
	printf("%s", arr2);
	return 0;
}

这样写可以。我还能再改进一下吗?
这样写,就非常的巧妙:

void my_strcpy(char*dest, char*src)
{
	while (*dest++ = *src++)
	{
		;
	}
}
int main()
{
	char arr1[] = "hello world";
	char arr2[] = "xxxxxxxxxxxxxxxxx";
	my_strcpy(arr2, arr1);
	printf("%s", arr2);
	return 0;
}

解析:因为第一次当我进入循环时,h的ascii码值不可能为0,赋值后进入循环体内什么事也没发生。直到最后复制了\0,0为假,循环体结束。

我还能怎么改进呢?万一传来的是没有赋值的野指针呢?所以我们断言一样:
 

#include<assert.h>
void my_strcpy(char*dest, char*src)
{
	assert(dest != NULL);//断言,如果条件为假就报错
	assert(src != NULL);
                         //这两句也可以写成 assert(dest && src);因为NULL就是0,就是假
	while (*dest++ = *src++)
	{
		;
	}
}
int main()
{
	char arr1[] = "hello world";
	char arr2[] = "xxxxxxxxxxxxxxxxx";
	char* p = NULL;
	my_strcpy(p, arr1);
	printf("%s", arr2);
	return 0;
}

watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBAWXV1ZWtpIFhZMzk=,size_20,color_FFFFFF,t_70,g_se,x_16

 它可以直接告诉我们哪里出了问题。

我们还能如何改进呢?

watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBAWXV1ZWtpIFhZMzk=,size_20,color_FFFFFF,t_70,g_se,x_16

 我们写的,和官方的有两个不同,有何区别呢?

为什么要加个const呢?就可以防止我src和dest写反了

watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBAWXV1ZWtpIFhZMzk=,size_20,color_FFFFFF,t_70,g_se,x_16

 因为const修饰的变量,叫常变量,是无法被修改的。但是我*p地址来进行间接修改,就真的改掉了
watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBAWXV1ZWtpIFhZMzk=,size_18,color_FFFFFF,t_70,g_se,x_16

 这样就破坏了这种规则,const就没有效果了。所以我对p也进行一定限制
watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBAWXV1ZWtpIFhZMzk=,size_12,color_FFFFFF,t_70,g_se,x_16watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBAWXV1ZWtpIFhZMzk=,size_20,color_FFFFFF,t_70,g_se,x_16

 这样就无法修改了

const的使用方法:

总结:const可以修饰指针,const放到*的左边(const int*p),修饰的是*p,表示p指向的对象,不能通过*p指针来间接修改。但是p自己本身存放的变量的地址是可以修改的

const放在*的右边(int* const p),修饰的是p变量本身,表示p的内容不能被改变,但是p指向的对象,是可以通过*p指针来改变的。

简单来说:
如果 const 位于 * 的左侧,则 const 就是用来修饰指针所指向的变量,即指针指向为常量;
如果 const 位于 * 的右侧,则 const 就是修饰指针本身,即指针本身是常量;
 

const int * const p,就什么也不能改了

也就是说const后面跟着是什么,就限制什么。是指针p,那就无法通过指针进行间接修改。是变量p,那就无法通过变量p进行修改,但是可以用指针进行修改。

所以最后我加了个const,就避免出现一些写反了的问题

库里写的是返回的char*,我写的是void,我还能怎么改进呢?

watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBAWXV1ZWtpIFhZMzk=,size_10,color_FFFFFF,t_70,g_se,x_16返回的就是目标的起始地址。

这样的作用呢,就方便我实现链式访问

watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBAWXV1ZWtpIFhZMzk=,size_20,color_FFFFFF,t_70,g_se,x_16

 这就是非常完美的代码写法了:

#include<assert.h>
char* my_strcpy(char*dest,const char*src)
{
	assert(dest != NULL);//断言,如果条件为假就报错
	assert(src != NULL);
	char* ret = dest;
	while (*dest++ = *src++)
	{
		;
	}
	return ret;//实现链式访问
}
int main()
{
	char arr1[] = "hello world";
	char arr2[] = "xxxxxxxxxxxxxxxxx";
	my_strcpy(arr2, arr1);
	printf("%s", my_strcpy(arr2, arr1));
	return 0;
}

 练习2:模拟strlen

#include<assert.h>
int my_strlen(const char* p)
{
	assert(p != NULL);//一定不是*p !=NULL,这样我就解引用了,我是防止出现野指针的情况
	int i = 0;
	while (*p != '\0')
	{
		i++;
		p++;
	}
	return i;

}
int main()
{
	char arr[20] = "Hello,world";
	printf("%d", my_strlen(arr));
	return 0;
}

编程的常见错误

1.编译型错误
基本是属于语法错误,直接就能看到报错位置

2.链接型错误
基本是标识符名不存在或者拼写错误。看错误提示信息,然后定位问题所在。

3.运行时错误
基本是逻辑问题,需要通过调试进行修改。

 

 

 

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。
1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值