程序员必备技能之调试

news2025/1/19 8:19:52

目录

前言

本期内容介绍

一、什么是Bug?

二、调试以及调试的重要性

2.1什么是调试?

2.2调试的基本步骤

​三、Debug和Release介绍

Debug和Release

四、windows环境下的调试介绍

4.1调试环境

4.2一些调试常用的快捷键

4.3调试时查看当前程序的信息

a、查看临时变量的值

b、查看程序的内存信息

c、查看程序的调用堆栈

d、查看程序的汇编信息

e、查看寄存器信息

五、一些调试实例

六、如何写出“好”代码?

6.1什么是好代码?

6.2如何写出好代码?

assert介绍

const介绍

七、常见的错误解析

7.1编译型错误

7.2链接型错误

7.3运行时错误


前言

我们平时在写代码的时候会出现各种错误,面对程序出现的错误我们之前好像没有介绍过如何高效、科学、系统地处理这种错误!您平时一般是如何说处理的呢?走读?猜?还是???无论哪种方式其实都是不是最高效且合理的!本期小编将介绍VS常用调试技巧!

本期内容介绍

什么是Bug?

调试及调试的重要性

Debug和Release介绍

windows环境调试介绍

一些调试实例

如何写出"好"代码?

常见错误解析

一、什么是Bug?

Bug的意思是:飞蛾或昆虫!在计算机中用Bug来代指一些未被发现的或隐藏的错误或缺陷!

下面是百度百科的介绍以及历史上第一个Bug的图片:

这是关于Bug的更详细的链接程序错误_百度百科 (baidu.com)感兴趣的可以看看!

二、调试以及调试的重要性

2.1什么是调试?

调试又称除错!是当程序或电子仪器出现了问题之后对程序或电子仪器进行科学排查、寻找问题以及解决问题的过程!

2.2调试的基本步骤

OK,我们来写个有问题程序来走一遍上面步骤:(求n的阶乘和)

int main()
{
	int n = 0;
	scanf("%d", &n);
	int sum = 0, ret = 1;//sum存储n的阶乘和 ret用于存储每一个的阶乘  
	for (int i = 1; i <= n; i++)
	{
		for (int j = 1; j <= i; j++)
		{
			ret *= j;
		}
		sum += ret;
	}

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

我们知道3!和为:1!+ 2! + 3! = 1 + 2*1 + 3* 2*1 = 9 我们来看结果:

怎么是15???这和我们的预期不一样,是不是就是出现了Bug了!我们就得想办法来解决这个问题!第一步:发现问题,结果和预期不一样,已经发现问题!第二步:定位问题:我们这里对变量sum 和 ret分别监视,一一比对每一次sum、ret的值是否与预期一样!第三步:确定错误原因:

这里确定了问题的原因,怎么解决呢?我们发现这里应该对每一个i的阶乘都要单独算,要就是说每一个ret都应该独立的,每一次的ret都应该是没有被用过的!那我们在每次计算每个i的时候对ret一开始先置 1即可!第四步:解决问题

int main()
{
	int n = 0;
	scanf("%d", &n);
	int sum = 0, ret = 1;//sum存储n的阶乘和 ret用于存储每一个的阶乘  
	for (int i = 1; i <= n; i++)
	{
		ret = 1;//防止后面的值重复叠加
		for (int j = 1; j <= i; j++)
		{
			ret *= j;
		}
		sum += ret;
	}

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

OK,第五步:重新测试:


三、Debug和Release介绍

我们的VS编译器上会经常看到一个东西是Debug!

其实不止有Debug还有一个是Release!

他们是什么呢?下面我们就来介绍一下Debug和Release:

Debug和Release

Debug:

Debug被称为调试版本,它包含了调试信息,不做任何优化,用于程序员调试解决问题!

Release:

Release被称为发布版本,它往往是对程序进行了优化,是的代码在运行速度以及空间大小都是最优的!用于用户使用的!

无论是Debug还是Release只要编译就会有相应的文件包,(就和Java中的.class类似,一个类编译一次有一个.class文件):

只在Debug编译:

再在Release下编译:

由于Release是用户使用的所以不能用来调试,而Debug是对程序员的所以可以用来调试!

OK。我们来看一段代码看看在两个版本下的差异:

Debug:

int main()
{
	int i = 0; 
	int arr[10] = { 0 };
	for (i = 0; i <= 12; i++)
	{
		arr[i] = 0;
		printf("hehe\n");
	}
	return 0;
}

我们先不管结果为什么是这样!我们先来看看两者结果的差别!:

这可不是结束了,这就是典型的死循环!!!为什么死循环我们不管,待会分析!

再来看看Release版本下的结果:

这就很好的说明了release版本对程序的优化!!!具体如何优化的这个得问问微软的开发工程师~

四、windows环境下的调试介绍

4.1调试环境

本期所有调试都是在VS2019的Debug下进行的!!!

4.2一些调试常用的快捷键

这里应该最清楚的一个就是:ctrl+F5了我们执行代码就是这个,他其实是开始执行不调试!!!除此之外还有很多快捷键!我们下面介绍几个最常用的!

F5

启动调试,常用于直接跳到下一个断点处!(这个断点是逻辑断点,非物理断点)

F9

创建和取消断点,然后F5直接跳过去到当前断点位置,然后就可以一步一步调试观察是否和预期一样!

所以,F5和F9是一起配合使用的!

F10

逐过程,通常是用来处理一个过程例如一次函数调用或一条语句!

F11

逐语句,每一次只能执行一条语句,可以进到函数内部!

如果您在调试的时候,上面的键按一下不起作用的话,有可能是Fn打开了,关掉Fn即可或者是上面的键+Fn配合使用即可!!!

下面是知乎的一篇VS2019的调试技巧快捷键介绍:VS2019调试快捷键大全

4.3调试时查看当前程序的信息

a、查看临时变量的值

在开始调试之后如何查看临时变量的值!其实我们上面已经用到了,只不过刚刚没有介绍!

我们按下F10之后,启动调试后,点击调试,进去选择窗口找到监视,有四个窗口随便一个都可以!

OK,我们在来一个看一个代码顺便调试看一下临时变量!


void swap(int x, int y)
{
	int tmp = x; 
	x = y;
	y = tmp;
}

int main()
{
	int a = 3;
	int b = 5;
	printf("交换前:a = %d  b = %d\n", a, b);
	swap(a, b);
	printf("交换后:a = %d  b = %d\n", a, b);
	return 0;
}

这就是交换两个数的代码,我们先来看看结果!

似乎没有交换,其实原因我们都清楚--》形参是实参的一份临时拷贝,改变形参不改变实参!

这里我们假装不知道,我们此时程序的结果和我们的预期不符合出了问题!就需要调试,我们来练习一下刚刚学的!

这就是F9打的断点,我们分析程序是在swap函数出问题的,所以F9直接跳过没问题的.然后F5到当前断点来!

此时问题在函数里面,我们介绍过。F11是逐语句执行,我们可以用它进入函数内部,看看!

注意此时监视x = 3, y = 5, a = 3, b = 5, 我们待会观察a,b,x,y的变化情况!

这是调试结束的结果,此时x = 5, y = 3确实交换了,但a = 3, b = 5好像一点也没变!我们结束打印的是a 和 b:

结果还是a = 3, b = 5;所以没有交换成功!这里小编也就不废话了,这里改变了形参形参是实参的一份拷贝,两者的空间不同,改变形参实参不受影响!我们前面已经不止一遍的介绍过这东西了,这里就不在唠叨了!

b、查看程序的内存信息

int main()
{
	int a = 0x11223344;

	return 0;
}

这段代码的内存布局以及a的值在内存中是如何存的?我们一起来look一look:

还是先调试起来!!! 点调试--->窗口--->内存-->4个窗口随便一个!

这里输入&a就可以看到,a的内存布局以及a中的值在内存中的存储形式,我们前面介绍过倒着存是因为对当前平台是小端存储模式!

c、查看程序的调用堆栈

我们就用下面这个简单的代码演示一下:

void test2()
{
	printf("hahaha\n");
}

void test1()
{
	test2();
}

int main()
{
	test1();
	return 0;
}

这个代码就是打印一个haha,但它是如何调用的?我们就可以来看看调用堆栈!

先调试起来!!!再点击调试-->窗口-->调用堆栈

一开始还没有调用test1只有main函数的栈帧!

F11调用test1在main函数的上面开辟了test1的栈帧:

在调用test2就有在test1上面创建了test2的函数栈帧:

然后接下来打印完haha后就会把test2的函数栈帧销毁,我们在函数栈帧的那一期介绍过,在调用一个函数的时候会把当前语句的下一条语句提前存起来,等栈帧销毁的时候就直接执行下一条语句了!所以test2带哦万就销毁了,只剩test1和main函数的栈帧了!

然后会最后main函数的栈帧也会销毁!!!这里我又想起来了我在介绍函数栈帧的时候说过main函数是被其他函数调用的,但那时候操作不当在调用堆栈那里没有找到!今天小编找到了!

在VS上调用main函数的函数是用C++写的!当然这个具体实现得看编译器,各个编译器可能不一样!

d、查看程序的汇编信息

先调试起来!!!再点击调试-->窗口-->反汇编

int maxNum(int x, int y)
{
	return x > y ? x : y;
}

int main()
{

	int a = 3; 
	int b = 5;
	int ret = maxNum(a, b);
	printf("max = %d", ret);
	return 0;
}

红色的框是在建立函数栈帧!这个我们函数栈帧介绍过!下面蓝色的框是在创建变量!

这就是调用函数的指令!

这个就是maxNum的函数栈帧!!!!的创建以及销毁的反汇编!

e、查看寄存器信息

这个可能很多刚刚入门的或者还没入门的小伙伴都不知道!

寄存器(register)是cpu周围的暂时放数据的地方,由于在cpu周围所以访问速度很快但空间不大!而且在寄存器上创建的变量是没有地址的!!!!

OK,我们来段代码看看:

int main()
{
	int a = 3;
	register int b = 5;

	return 0;
}

b就是一个寄存器的变量!他是没有地址的!我们先对a取地址:

在对b 取地址:

在cpu周围不需要开辟内存直接可以使用!!!

五、一些调试实例

(一)、代码实现:1! + 2! + ...+n!

int main()
{
	int sum = 0;
	int ret = 1;
	int n = 0;
	scanf("%d", &n);
	for (int i = 1; i <= n; i++)
	{
		for (int j = 1; j <= i; j++)
		{
			ret *= j;
		}

		sum += ret;
	}

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

这段代码我们其实一开始就介绍了,它的错误原因在于:每一次没有对ret初始化为1,导致后面的ret把前面的结果叠加的乘了上去!!!我们上面也调试了,这里就不在调试了!正确代码如下:

int main()
{
	int sum = 0;
	int ret = 1;
	int n = 0;
	scanf("%d", &n);
	for (int i = 1; i <= n; i++)
	{
		ret = 1;
		for (int j = 1; j <= i; j++)
		{
			ret *= j;
		}

		sum += ret;
	}

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

(二)、下列程序打印机次haha?

int main()
{
	int i = 0; 
	int arr[10] = { 0 };
	for (i = 0; i <= 12; i++)
	{
		arr[i] = 0;
		printf("haha\n");
	}
	return 0;
}

如果你以前没有见过这道题或了解过地层相关的知识的话,这道题你可能100%会回答错!你可能只会说这道题的问题不就是越界嘛!打印13个haha然后报错!但事实真的是这样吗?我们看结果:

死循环了!!!问什么呢?我们先来找原因,其实如果之前看过函数栈帧的那一期的伙伴应该会反映过来,在VS上,创建的变量一个里一个差两行0hcccccccccc:

这里我们就不在用这种方式往下解释了,我们用调试:

果然他也是差了两行的ccccccccc,我们知道栈的使用规则是:先试用高地址,在使用低地址!而,数组的下标访问是先用低地址,后用高地址!

所以数组访问的地址路线如下:

会不会是在越界的时候把i改了呢?我们来监视看看:
刚刚创建好变量和数组:

i==9是,马上要越界了:

i == 12:

在i == 12,将arr[12] = 0;执行完后i也变成了0,我们猜想会不是越界的时候到i=12时将i的值改为了0?换句话说i的地址和arr[12]是一样的,他们是一块空间?我们此时只需要看看他两的地址即可:

果然一模一样!!!!我们上面的分析是正确的!!我在画个图来解释一下上面:

那找到了问题,确定了原因,就得解决问题:

int main()
{
	int arr[10] = { 0 };
	for (int i = 0; i <= 12; i++)
	{
		arr[i] = 0;
		printf("haha\n");
	}
	return 0;
}

这样写的话他i的创建在i的后面,前面的那块空间一单越界就会报错!

当然这种代码在实践中肯定是一般碰不到的!这个栗子仅仅是说明遇到问题了如何去解决!!!虽然在实践中遇不到但不一定在笔试面试中碰不到:这道题曾经就是一家叫Nice公司的笔试题:

这道提不就和刚刚这道题一样吗?只是换了数字而已!思路、问题、解决方法都是一样的!!!!

六、如何写出“好”代码?

6.1什么是好代码?

我们经常听说好代码,什么是好代码呢?我们认为具备以下特征的代码就是好代码!

1、能正常运行

2、Bug很少

3、效率高

4、可维护性高

5、注释清晰

6、可读性高

7、文档齐全

假设说你写了个代码,运行不了,或者即使运行起来了也全是Bug,你写的代码只有你自己你看的懂,不加注释。你可能说只有自己看得懂那不就公司裁不了我了吗?这样想就太天真了!当代码有几十万行时出了问题,你自己都控制不了的时候,,,你细品~。所以我们平时写代码要注重可读性以及注释!!!

6.2如何写出好代码?

这是一些写好代码的几个tips:

1、使用assert

2、使用const

3、编码风格规范

4、添加注释

这里提到了assert和const,我们以前经常用但没有介绍过这两个!下面我就先来介绍一下:

assert介绍

assert 中文意思就是断言的意思。顾名思义就是判断!他是一个宏!使用时要包含相应的头文件:#include <assert.h>

这是官网对他的介绍,人家明确说了这是一个!!!长得像函数的宏!作用是断言!如果它的判断的那个表达式判断失败就会调用:abort这个函数!并终止程序!这个宏所显示的消息与具体的实现库有关,一个编译器和一个编译器不一样!但他至少显示:断言失败的源文件名和对应的行数!如果你包含了头文件还是不能用就得添加上面我画出来的#definede 那个了!加上就OK了!!

这里还提到了一个函数:abort:

这个函数的作用是终止当前进程,进程是网络那部分的东西,这里就不多介绍了!这里就理解为终止当前程序(异常终止)!调用这个函数时这个函数会捕捉去一个信号,如果没有捕捉到,就会导致程序终止,并向平台返回一个错误码!程序被终止是不会破坏任何对象!!

OK!我们来写个代码用一下:

typedef struct Stu
{
	char name[20];
	int age;
}S;

void print(S* ps)
{
	assert(ps);
	printf("%d", ps->age);
}

void Modif(S* ps, int age)
{
	assert(ps);
	ps->age = age;
}
int main()
{
	S s = { "张三",20 };
	Modif(&s, 19);
	print(&s);
	return 0;
}

这就是assert的作用, 当然你可以按自己的需要判断值是否符合预期~!

const介绍

const这是一个关键字,我们前面开始介绍常量和变量的时候说过,被const 修饰的变量是常变量!具有常量属性但本质是个变量(只不过这个变量被初始化以后就不能在被修改了)!例如:

#define M 10//#define定义的常量

int main()
{
	int a = 3;//变量
	const int b = 5;//const修饰的常变量
	
	return 0;
}

因为b是被const修饰具有常量属性不能被改,所以这里对他的值进行修改就会报错:

而且也不可以用它来定义数组!我们知道定义数组时[ ]里面的是一个常量(变长数组除外),变量就不行,b是常变量所以他也不行!!!

当然我们今天不是为了回忆以前学的,而是在此基础上再进行拔高一层!比如const修饰指针的问题!先看如下代码:

void test1()
{
	int m = 3;
	int n = 5;
	int* p = &n;
	*p = 20;
	p = &m;
}

void test2()
{
	int m = 3;
	int n = 5;
	const int* p = &n;
	*p = 20;
	p = &m;
}

void test3()
{
	int m = 3;
	int n = 5;
	int* const p = &n;
	*p = 20;
	p = &m;
}

void test4()
{
	int m = 3;
	int n = 5;
	const int* const p = &n;
	*p = 20;
	p = &m;
}

int main()
{
	test1();
	test2();
	test3();
	test4();
	return 0;
}

上面的4个代码分是对指针p的 * 两边都不用const修饰,在*左边修饰右边不修饰,右边修饰左边不修饰,以及两边都修饰!具体会出什么结果过呢?我们一个一个看一看!

test1两边都不修饰:

考虑此时的 m 和 n值是多少?

这个不怎么难一看就知道但我想说的是!这里有一行很不起眼的代码你可能没有注意到!就是倒数第二行!p = &m;你可能会说这不就是一行很简单的赋值吗?这有啥可介绍的!你这么想就可使有点外行了!这可是指针啊!!!有了指针可以直接修改的!!!例如:

void test1()
{
	int m = 3;
	int n = 5;
	int* p = &n;
	*p = 20;
	p = &m;
	printf("m = %d  n = %d\n", m, n);
	*p = 200;
	printf("m = %d  n = %d\n", m, n);
}

看结果:

这里你还觉得他仅仅赋值那么简单吗?是不是感觉他很不安全呀!的确指针使用不当很危险!所以在使用指针前对他进行检查以及作相应的修饰限制,例如const,下面就来看看被const此时的各种情况吧!

const在*左边:

void test2()
{
	int m = 3;
	int n = 5;
	const int* p = &n;
	*p = 20;
	p = &m;
}

这个就是const修饰*左边的例子!如果被const修饰了左边,会和不修饰有什么区别呢?我们想爱你编译看看!

这貌似和const修饰一般的变量一样!不能被修改,这个在左边好像不能修改p指向那块空间的值!那我们猜测:如果在右边是不是不能改变p的值呢?我们来看看:

void test3()
{
	int m = 3;
	int n = 5;
	int* const p = &n;
	*p = 20;
	p = &m;
}

还真和我们猜的一样!再回到上面的栗子test2:

void test2()
{
	int m = 3;
	int n = 5;
	const int* p = &n;
	//*p = 20;
	p = &m;
	p = NULL;
}

既然不让修改p指向的值的话,那我改变p的值,的确可以!

和这个类比一下,在右边我不能改 p的值,那我可以改p指向的那块空间的值吧!验证一下:

果然!那我们可以再猜测一下是不是在两边修饰就是既不能修改p的值也不能修改p只向空间的值呢?试一试:

果然!这样好像更安全了~的确是!我们现在就可以进行总结一下!

const 两边都不修饰

很不安全!!!只要拿到指针既可以修改指针也可以修改指针指向的空间的值!

const 修饰*左边

不能对指针指向的那块空间的值进行修改!但可以对指针的修改!

const 修饰*右边

不能修改指针的值,但可以修改指针指向空间的值!

const 修饰两边

既不能修改指针的值,也不能修改指针指向空间的值!

OK,这三种修饰看具体情况使用!!!!!我们刚刚上面的aeesrt的那个代码是不是可以再来用优化一下呢?

typedef struct Stu
{
	char name[20];
	int age;
}S;

void print(const S* const ps)
{
	assert(ps);
	printf("%d", ps->age);
}

void Modif(S* const ps, int age)
{
	assert(ps);
	assert(age);

	ps->age = age;
}
int main()
{
	S s = { "张三",20 };
	Modif(&s, 19);
	print(&s);
	return 0;
}

print函数只是打印信息不需要改变ps的值以及ps指向空间的值!下面的修改函数是需要修改ps指向空间的值但不需要改变自身的值~,加上assert和const的代码是不是更加健壮了!以后能用就多用!!!另外多加注释!!!OK,下面我们就来完整的写一个好代码!模拟实现strcpy!这个函数不必多介绍了吧,我们已经前面模拟实现了两遍了!它的作用就是拷贝字符串!~函数原型如下:

两个参数,destination是目的地也即是要拷贝到的空间!另一个是,cosnt修饰的source是源头,也就是要拷贝的数据!因为他只要求拷贝不让修改,所以const修饰左边!我们下面来实现一个:

char* MyStrcpy(char* dest, const char* src)
{
	//检查空指针
	assert(dest);
	assert(src);

	//拷贝
	char* ret = dest;
	while (*dest++ = *src++)
		;

	return ret;
}

int main()
{
	char dest[20] = { 0 };
	char* src = "hello world!";
	strcpy(dest, src);//库函数
	printf("%s\n", dest);
	printf("-----------------------\n");
	MyStrcpy(dest, src);//自己函数
	printf("%s\n", dest);
	return 0;
}

看结果:

OK!模拟完成!

七、常见错误解析

7.1编译型错误

一般可以直接看到错误信息,凭借经验就可以解决!一半多为语法错误~

例如:

int main()
{
	int a = 3
	return 0;
}

这里是少个封号,导致语法错误!看编译错误信息:

这种问题一般会很简单,看看错误信息就可以解决!

7.2链接型错误

这种错误一般是由标识符错误火不存在导致的!看错误信息也很容易解决!

int Add(int a, int b)
{
	return a + b;
}

int main()
{
	int a = 3;
	int b = 5;
	int ret = add(a, b);
	printf("%d", ret);
	return 0;
}

这里add和Add不是一个东西就会表连接时错误!

这种找到改回来就好了!

7.3运行时错误

这是最麻烦的一种,语法没有问题可是结果就是不符合预期,得一步一步调试~

调试也是程序员的内功,非一日之功!~得多练习~,!

OK,好兄弟本期分享就到这里,我们下期再见~!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/967161.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

FLBOOK一个制作电子期刊的必备工具

在日常工作中&#xff0c;肯定有不少的人找不到合适的制作电子期刊工具吧&#xff01;大家可以试试FLBOOK简单又实用 为什么推荐FLBOOK&#xff1f; 1.直观易用的界面 用户可以通过拖拽、插入图片、添加文字等方式来设计期刊的布局 2.多种模板和主题 用户可以根据自己的需求…

ImageSharp.Web实战:轻松搭建高效图片服务

很多情况下&#xff0c;在开发如PC、H5、小程序等综合平台的时候&#xff0c;图片的展示是个比较头疼的问题。尤其是有会员功能&#xff0c;会员可以上传图片的平台&#xff0c;更是一件麻烦事。 平台展示图片的地方&#xff0c;尺寸是定义好的。但用户不配合&#xff0c;上传的…

载入qss时出现Could not parse application stylesheet

我这里其实qss文件本身没有错误。 参考&#xff1a;解决Qt Creator修改qss文件后导致样式无效问题_qt qss改变但运行结果没变_风吹沙走的博客-CSDN博客 我的解决方法&#xff1a; (1)UTF-8 BOM:总是删除 (2) 文本重新编码为ANSI 这时候中文会变成乱码。 (3)我事先复制了一…

Java 线程池概念总结(thread pool)

一、池化思想的应用 池化思想是一种常见软件设计和优化技术。以下是几个常见池化思想应用场景&#xff1a; 线程池&#xff1a;线程池是池化思想一个典型应用。通过预先创建一组线程并将它们置于就绪状态&#xff0c;以复用线程减少线程创建和销毁的开销&#xff0c;并提高系统…

PY32F003F18按键输入

一、PY32F003F18的GPIO介绍 1、PY32F003F18的18个I/O&#xff0c;均可作为外部中断&#xff1b; 2、每个GPIO都可以由软件配置为输出&#xff1a; 1)、推挽输出(push-pull) 2)、开漏极输出(open drain) 注意:驱动电流为8mA; 3、每个GPIO都可以由软件配置为输入&#xff1a; 1)、…

视频图像处理算法opencv在esp32及esp32s3上面的移植,也可以移植openmv

opencv在esp32及esp32s3上面的移植 Opencv简介 OpenCV是一个基于Apache2.0许可&#xff08;开源&#xff09;发行的跨平台计算机视觉和机器学习软件库&#xff0c;可以运行在Linux、Windows、Android和Mac OS操作系统上&#xff0c;它轻量级而且高效——由一系列 C 函数和少量…

02-请解释一下Java的内存模型和happens-before规则?【Java面试题总结】

请解释一下Java的内存模型和happens-before规则&#xff1f; 概念&#xff1a;Java内存模型&#xff0c;简称JMM&#xff0c;是一种定义了多线程程序中内存访问行为的规范。它定义了线程如何与主内存和工作内存进行交互&#xff0c;以及如何保证多线程程序的正确性和可见性。J…

聚鑫数藏平台——引领数字资产管理新风向

随着数字经济的飞速发展&#xff0c;新金融生态应运而生。区块链技术的崭新突破&#xff0c;使数字资产的重要性日益凸显&#xff0c;为投资者带来了前所未有的机遇和挑战。在此背景下&#xff0c;聚鑫数藏平台横空出世&#xff0c;引领着数字资产管理的新风向。 聚鑫数藏平台&…

2023数学建模国赛四天速成计划来啦!(内含大量资料)

大家好呀。高教社杯全国大学生数学建模竞赛&#xff08;下称国赛&#xff09;9.7日下午6点就正式开始了&#xff1a; 在这里给大家带来一个五天的速成计划啦&#xff01;大家可以收藏本文章或者转发到你们队友群哈&#xff0c;此外我还会发放很多资料给大家&#xff0c;注意&am…

数据结构|栈和队列以及实现

栈和队列 一、栈1.1栈的概念及结构1.2栈的实现 二、队列2.1队列的概念及结构2.2队列的实现 一、栈 1.1栈的概念及结构 栈&#xff1a;一种特殊的线性表&#xff0c;其只允许在固定的一端进行插入和删除元素操作。进行数据插入和数据删除的一端称为栈顶&#xff0c;另一端称为栈…

UE4 植物生长

这个可以改变SplineMesh朝向

leetcode 724. 寻找数组的中心下标

2023.9.3 本题先 求出数组总和sum&#xff0c;再寻找数组的中心下标&#xff1a;if(sum - temp -nums[i] temp) return i; 代码如下&#xff1a; class Solution { public:int pivotIndex(vector<int>& nums) {//求数组元素总和int sum 0; for(int num : nums…

【C++技能树】继承概念与解析

Halo&#xff0c;这里是Ppeua。平时主要更新C&#xff0c;数据结构算法&#xff0c;Linux与ROS…感兴趣就关注我bua&#xff01; 继承 0. 继承概念0.1 继承访问限定符 1. 基类和派生类对象赋值兼容转换2. 继承中的作用域3. 派生类中的默认成员函数4.友元5.继承中的静态成员6.菱…

lenovo联想笔记本小新 潮7000-14IKBR 2018款(81GA)原装出厂Windows10系统镜像

自带所有驱动、出厂主题壁纸LOGO、Office办公软件、联想电脑管家等预装程序 链接&#xff1a;https://pan.baidu.com/s/1ynP4d5z7MPF9l5U5lCjDzQ?pwdhjvj 提取码&#xff1a;hjvj 所需要工具&#xff1a;16G或以上的U盘 文件格式&#xff1a;ISO 文件大小&#x…

2.4 关系数据库

思维导图&#xff1a; 前言&#xff1a; 这段话描述了“关系数据库”及其背后的理论基础。首先&#xff0c;我们来拆分这段话并逐步解释每部分。 关系数据库是采用关系模型作为数据组织方式的数据库。 这句话的关键是“关系模型”。关系模型是一种表示和操作数据库的理论模型…

软考备考-程序员-考试介绍和考试大纲

软考程序员-考试介绍和考试大纲 全国计算机技术与软件专业技术资格&#xff08;水平&#xff09;考试 计算机技术与软件专业技术资格&#xff08;水平&#xff09;考试&#xff08;以下简称计算机软件资格考试&#xff09;是原中国计算机软件专业技术资格和水平考试&#xff0…

第三方系统测试怎么做?有些什么流程

系统测试 软件系统研发出来以后&#xff0c;产品的各个模板功能是否能够正常运转需要进行软件系统测试。而对于普通的互联网企业来说&#xff0c;软件测试的各项工作已经超出开发工作内容&#xff0c;由开发人员进行系统测试不仅需要耗费软件团队精力&#xff0c;可能测试效果…

Pyecharts数据可视化(三)

目录 1.绘制词云图 2.绘制桑基图 3.绘制平行坐标图 4.绘制结点图 5.绘制地图 本文主要介绍了如何利用Pyecharts绘制词云图、桑基图、平行坐标图、节点图和地图&#xff0c;虽然这些图平时不是很常用&#xff0c;但是看起来还是比较好看的&#xff0c;如果放在论文当中&am…

leetcode793. 阶乘函数后 K 个零(java)

阶乘函数后 K 个零 题目描述二分法代码模拟 题目描述 难度 - 困难 阶乘函数后 K 个零 f(x) 是 x! 末尾是 0 的数量。回想一下 x! 1 * 2 * 3 * … * x&#xff0c;且 0! 1 。 例如&#xff0c; f(3) 0 &#xff0c;因为 3! 6 的末尾没有 0 &#xff1b;而 f(11) 2 &#xf…

Bito使用手册

第一步&#xff1a;输入网站 https://alpha.bito.co/bitoai/ 第二步&#xff1a;填写邮箱 第三步&#xff1a;登录邮箱&#xff0c;获取验证码 第四步&#xff1a;填写验证码 第五步&#xff1a;完成