调试是什么?

调试:又称除错,是发现和减少计算机程序或电子仪器设备错误的一个过程。

调试的基本步骤

1:发现程序错误的存在

2:以隔离和消除等方式对错误进行定位

3:情定错误产生的原因

4:提出纠正错误的解决办法

5:对程序错误予以改正,重新测试

Debug和Release版本的

Debug通常称为调试版本:包含调试信息,所以占的内存比较大,并且不做任何优化,利于程序员进行调试

Release为发布版本,进行了各种优化,程序在代码和圆形速度上都是最优的,以便用户更好的使用

我们用代码讲一下dubug版本

int main()
{
	int i = 0;
	for (i = 0; i < 10; i++)
	{
		printf("%d", i);

	}
	return 0;
}

在debug版本下,我们按F10或F11

调试RN iOS 调试是啥意思_c语言

我们可以通过F10或F11来控制代码运行的过程,我们打开监视也能看见变量的变化过程,假如我们切换为release版本,再按F10结果为

调试RN iOS 调试是啥意思_开发语言_02

 证明release版本下是不存在调试信息的,所以无法进行调试

接下来,我们介绍一下调试的快捷键

F5:启动调试,经常用来跳到下一个断点

F9:创建断点和取消断点

断点的重要作用:可以在程序的任意位置设置断点

这样就可以让程序在想要停下的位置停止,进而一步步执行下去

F10:逐过程,通常用来处理一个过程,这个过程可以是一个语句,也可以是一个函数

F11:逐语句,就是每次执行一个语句,但是这个快捷键可以让我们的执行逻辑进入函数内部

CTRL+F5:开始执行不调试,通常用于函数运行

F5调试

F5调试通常是与F9一起配合使用的

例如正常情况下

int main()
{
	int i = 0;
	int arr[10] = { 0 };
	for (i = 0; i < 10; i++)
	{
		arr[i] = i;
	}
	for (i = 0; i < 10; i++)
	{
		printf("%d", i);

	}
	return 0;
}

这假如我们知道我们的第一个for循环是没有问题的,我们想要直接跳过第一个for循环来访问第二个for循环,如果用最普通的方法的话,就需要按10多次F11,这时候我们就可以使用F9和F5配套使用

调试RN iOS 调试是啥意思_数组_03

 我们在第二个for循环处设置断点,按下F5

调试RN iOS 调试是啥意思_调试RN iOS_04

 可以发现,我们的箭头直接指向了第二个for循环,我们可以通过监视看

调试RN iOS 调试是啥意思_c++_05

 可以发现,我们数组的十个元素全部已经被初始化了,所以F5配合F9配套使用是直接跳过前面的所有代码(但会执行),直接跳到断点位置,继续执行。

这时候,有人会问,那怎么跳过scanf呢?scanf是需要输入值的,怎么跳过呢?

很简单,我们写个代码

调试RN iOS 调试是啥意思_开发语言_06

我们设置一个断点,该段点是在scanf之后的,我们按下F5

调试RN iOS 调试是啥意思_c语言_07

 这时候,弹出一个对话框,我们输入十个值,这十个代表我们要输入scanf的值

我们输入1 2 3 4 5 6 7 8 9 10

调试RN iOS 调试是啥意思_开发语言_08

 可以发现,我们已经跳过第一个for循环,并且我们的数组元素已经被初始化完毕

我们如果要设置两个端点的话

调试RN iOS 调试是啥意思_开发语言_09

 那我们能不能按两下F5,直接跳到858第二个for循环呢?

答案是不能

调试RN iOS 调试是啥意思_开发语言_10

 原因是我们跳过断点跳到的是逻辑上的下一个断点,逻辑上的下一个断点就表示i=1的第二次循环。

禁用断点

假设一个断点我们不想用了但不想删掉,这时候我们禁用断点

调试RN iOS 调试是啥意思_c语言_11

 这时候保存了第一个断点,但是这个断点并不起作用

假设我们的断点设置处是在一个循环中,并且我们知道这个循环的前五次是没有问题的,这时候我们如何设置呢

我们右键断点

调试RN iOS 调试是啥意思_开发语言_12

 点击条件

调试RN iOS 调试是啥意思_c语言_13

 输入条件,i==5

我们按下F5,这时候我们需要输入5个值所为scanf的需要

调试RN iOS 调试是啥意思_数组_14

可以发现,我们直接跳过了前五次循环 。

F10和F11的区别 

区别在函数调用上

int add(int a, int b)
{
	return a + b;
}
int main()
{
	int a = 10;
	int b = 20;
	int c = add(a, b);
	printf("%d", c);
	return 0;
}

我们首先按F10进行调试,调试到函数部分

调试RN iOS 调试是啥意思_c语言_15

 再按一下F10

调试RN iOS 调试是啥意思_调试RN iOS_16

 可以发现,直接跳过了函数的内部部分,没有对函数进行访问

假设我们按F11

调试RN iOS 调试是啥意思_c语言_17

可以发现,进入了函数内部,由此可得,F11和F10的区别是F11可以进入函数内部,而F10不能

自动窗口:

int add(int a, int b)
{
	return a + b;
}
int main()
{
	int a = 10;
	int b = 20;
	int c = add(a, b);
	printf("%d", c);
	return 0;
}

我们打开调试,窗口里面的自动窗口int add(int a, int b)

调试RN iOS 调试是啥意思_开发语言_18


 可见其自动加入了参数a和b,随着代码的运行,参数又多了个c

调试RN iOS 调试是啥意思_c语言_19

接下来,当我们调试到函数内部时

调试RN iOS 调试是啥意思_c语言_20

 可以发现,其自动添加了x,y,而删除了参数a,b,c

所以自动窗口会自动加载我们上下文的一些信息,但自动窗口也会自作主张,可能达不到我们的预期

局部变量

我们使用局部变量窗口进行调试

调试RN iOS 调试是啥意思_调试RN iOS_21

调试RN iOS 调试是啥意思_开发语言_22

调试RN iOS 调试是啥意思_开发语言_23

 我们可以发现,参数的位置会发生跳动,观察起来也不够方便。

 监视窗口

需要我们自动输入,但观察起来方便

调试RN iOS 调试是啥意思_开发语言_24

 而且就算我们进入函数内部,也可以对a,b进行查看

而且我们能根据我们的需求,随意进行参数的查看

调试RN iOS 调试是啥意思_开发语言_25

 而且我们甚至可以输入其他非参数值,例如

调试RN iOS 调试是啥意思_c语言_26

 接下来,我们讲一下数组传参的一些知识

void test(int a[])
{
	//
}
int main()
{
	int arr[10] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
	test(arr);
	return 0;
}

我们进行调试,输入arr,可以看到数组的全部内容

调试RN iOS 调试是啥意思_调试RN iOS_27

 但当我们输入a的时候呢,a也是数组

调试RN iOS 调试是啥意思_c语言_28

可以发现,a并不能显示所有数组的元素,原因是 a在这里表示的是数组首元素的地址,我们如何显示数组arr的全部元素呢?

我们把这里的a换成a,10就可以了

调试RN iOS 调试是啥意思_数组_29

 内存

我们打开调试窗口里的内存

调试RN iOS 调试是啥意思_c++_30

 最左侧的0x00表示数组的地址 中间的四行表示内存, 一行表示四个字节,四行中的内容用16进制表示。

我们进行调试的实例

求出n!+(n-1)!+(n-2)!----1!

我们首先学习求n的阶乘

int main()
{
	int a = 0;
	scanf("%d", &a);
	int ret = 1;
	int i = 0;
	for (i = 1; i <= a; i++)
	{
		ret *= i;
	}
	printf("%d", ret);
	return 0;
}

我们进行思考,如何求阶乘的和呢?

int main()
{
	int a = 0;
	scanf("%d", &a);
	int ret = 1;
	int i = 0;
	int j = 0;
	int sum = 0;

	for (j = 1; j <= a; j++)
	{
		for (i = 1; i <= j; i++)
		{
			ret *= i;
		}
		sum += ret;
	}


	printf("%d", ret);
	return 0;
}

这个代码看上去没什么错误,我们运行一下,检验结果

调试RN iOS 调试是啥意思_开发语言_31

 正确的结果应该为6+2+1=9,但结果却为12,我们进行调试检查错误

调试RN iOS 调试是啥意思_数组_32

 可以发现,我们在还没有计算在求3!的时候,可以发现ret的值并不是1,3!的结果是6,但最后ret求出的结果却为12

调试RN iOS 调试是啥意思_数组_33

 所以我们要在前面进行初始化操作

int main()
{
	int a = 0;
	scanf("%d", &a);
	int ret = 1;
	int i = 0;
	int j = 0;
	int sum = 0;

	for (j = 1; j <= a; j++)
	{
		ret = 1;
		for (i = 1; i <= j; i++)
		{
			ret *= i;
		}
		sum += ret;
	}


	printf("%d", sum);
	return 0;
}

我们进行运行,输入3

调试RN iOS 调试是啥意思_c语言_34

 可以发现,结果正确。

我们再写一个代码

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\n");
	}
	return 0;
}

正常情况下,这组代码应该会造成越界访问,因为我们的数组元素只有10个,我们却访问了13个元素,我们进行运行

调试RN iOS 调试是啥意思_c语言_35

 可以发现,无限循环打印hehe,这是为什么呢?我们进行调试

调试RN iOS 调试是啥意思_开发语言_36

调试RN iOS 调试是啥意思_调试RN iOS_37

 我们可以发现,arr[12]始终等于i,

调试RN iOS 调试是啥意思_调试RN iOS_38

 可以发现,i的地址和arr[12]的地址也相同

得出,arr[12]=i

为什么呢?

这时候,我们要说一下栈

数组arr和变量i都是在栈区,

栈区的使用规则:先使用高地址的内存空间,再使用低地址的内存空间

2:数组随着下标的增长,地址开始由低到高

由此,我们可以写出原因:我们先创建高地址处的i变量,在创建相对低地址的数组arr,因为数组元素随下标是从低到高增长的,所以随着下标的增长,低地址的数组的元素可能会覆盖到高地址的变量i上,这时候,这两个数就相等了,因为i值始终等于arr[12],所以当arr[12]被初始化为0时,i也为0

为什么没有报错呢?

因为代码一直在执行死循环,来不及报错。

字符串拷贝

最简单的方法

#include<string.h>
int main()
{
    char arr1[] = "hello bit";
	char arr2[20] = { 0 };
	strcpy(arr2, arr1);
	printf("%s\n", arr2);
	return 0;
}

其中,字符串拷贝函数strcpy的头文件是#include<string.h>,格式是strcpy(目标数组,原数组)

但是这种方法没法知道是否把/0也拷贝过去了,所以我们换一个初始化方式

#include<string.h>
int main()
{
    char arr1[]   = "hello bit";
	char arr2[20] = "xxxxxxxxxx";
	strcpy(arr2, arr1);
	printf("%s\n", arr2);
	return 0;
}

假如我们传递过去了/0,那么打印的结果应该就是hello bit 否则就是hello bitx,我们进行运行

调试RN iOS 调试是啥意思_调试RN iOS_39

 所以得出结果,strcpy是会把/0也拷贝过去的

我们自己模拟实现一个字符串拷贝函数

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

对这一部分进行解释:src实际上是arr1数组首元素的地址,dest是数组arr2数组首元素的地址,我们把src对应的元素赋值给dest对应的元素,顺便让两个地址++,表示循环执行赋值,然后我们设置限定条件,当str不等于\0的时候执行循环,当他等于\0的时候中断循环,中断循环后我们的\0还没有赋值,这时候我们把\0赋值给*dest。

但这种方法略显繁琐,我们进行优化

void my_strcpy(char*dest, char*src)
{
	while (*src != '\0')
	{
		*dest++ = *src++;
		
	}
	*dest = *src;
}

我们可以使用后置++来简化步骤

但是这两种方法有个共性,那就是把\0和其他元素分隔开进行了运算,有没有方法让他们合并起来运算呢?

void my_strcpy(char*dest, char*src)
{
	while (*dest++ = *src++);
	{
		
		;
	}
	
}

我们把我们要执行的while循环内部的语句直接设置成为while循环的判断部分,因为while循环的判断部分也需要执行,当运行到\0时,执行*dest++ = *src++,但是由于*src=*dest=\0 \0 的arcii码值是0,所以我们的循环终止,但我们的元素包括\0全部被拷贝过去了

void my_strcpy(char*dest, char*p)
{
	while (*dest++ = *src++);
	{
		
		;
	}
	
}
int main()
{
    char arr1[]   = "hello bit";
	char arr2[20] = "xxxxxxxxxx";
	char*p = NULL;
	my_strcpy(arr2, NULL);
	printf("%s\n", arr2);
	return 0;
}

假设我们传递错误,传递过去了一个空指针,对空指针解引用就会产生错误,那怎么办呢?

这时候,我们引入函数assert,assert的头文件是#include<assert.h>,assert(i),assert函数的内部的i为真的情况下,程序继续运行,假设i为假的情况下,则报错

#include<assert.h>
void my_strcpy(char*dest, char*src)
{
	assert(src != NULL);
	while (*dest++ = *src++);
	{
		
		;
	}
	
}

假设没有src!=NULL,则代码顺利进行,如果src==NULL,则代码报错

假如我们不下心把src和dest写反了呢?然后我们进行赋值的结果也犯了,也就产生错误了。如何进行纠正呢?

void my_strcpy(char*dest,const char*src)

我们只需在函数声明部分这样写,这样写的意思是固定*src的值,不允许通过解引用改变src的值,为什么要这样写呢?

假如我们在函数定义的内部把dest和src写反了,*src=*dest,这时候就会产生错误,程序无法运行

接下来,我们介绍一下const

int main()
{
	const int num = 10; 
	num = 20;
	return 0;
}

调试RN iOS 调试是啥意思_c语言_40

 说明num这时候是不可修改的值,原因是被const修饰的对象,相当于num是一个常量,无法进行更改

那num就不能改变了吗?

答案是否定的

int main()
{
	const int num = 10; 
	int *p = # 
	*p = 20;
	printf("%d", num);
	return 0;
}

我们绕一下,我们通过指针的方法改变num的值是可以实现的

调试RN iOS 调试是啥意思_开发语言_41

假设我们的const修饰指针会怎么样

int main()
{
	 int num = 10; 
	const int *p = # 
	*p = 20;
	printf("%d", num);
	return 0;
}

调试RN iOS 调试是啥意思_c++_42

 可以发现,当const在*的左侧的时候,不能通过解引用的方法改变p值

 

调试RN iOS 调试是啥意思_调试RN iOS_43

 但当我们打印*p的时候,也是可以打印的,得出结论,*修饰指针的时候,是可以访问的,但不可以更改。

但是这时候能不能改变num的值呢?同理,也是可以改变的

调试RN iOS 调试是啥意思_数组_44

 可以得出结论,当const修饰指针的时候,我们不能通过解引用的方法改变被const修饰的指针指向的元素值,但我们可以直接通过赋值的方法改变指针指向的元素值。

那么p可以改吗?

调试RN iOS 调试是啥意思_数组_45

 可以发现,p是可以更改的,所以const修饰指针,限制的只是指针指向的变量,而不是指针本身