【逐步剖C】-番外篇-实用调试技巧

news2024/12/27 12:32:21

一、 Bug介绍

在正式开始讲解调试技巧之前先介绍一下我们亦敌亦友的Bug。

程序错误,即英文的Bug,也称为缺陷、臭虫,是指在软件运行中因为程序本身有错误而造成的功能不正常、死机、数据丢失、非正常中断等现象。 早期的计算机由于体积非常庞大,有些小虫子可能会钻入机器内部,造成计算机工作失灵。史上的第一只 “Bug” ,真的是因为一只飞蛾意外走入一电脑而引致故障,因此Bug从原意为臭虫引申为程序错误。——百度百科

说其是敌,是因为Bug的出现总让人苦恼于如何修复Bug;说其是友,是因为它让我们意识到自己尚存不足之处,需要进一步的学习;它还帮助我们积累了经验,而谨防它下一次出现。
那么接下来我们将开始分享和介绍与它打交道的方法——调试

二、 调试

“所有发生的事情都一定有迹可循,如果问心无愧,就不需要掩盖也就没有迹象了,如果问心有愧,
就必然需要掩盖,那就一定会有迹象,迹象越多就越容易顺藤而上,这就是推理的途径。
顺着这条途径顺流而下就是犯罪,逆流而上,就是真相。”

一名优秀的程序员是一名出色的侦探。
每一次调试都是尝试破案的过程

这里先来页漫画自我反省一下,因为在学调试之前,我一直都是如下漫画的迷信式调试:
在这里插入图片描述
学会调试之后,一定要拒绝这种迷信式调试!

1. 调试的概念

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

2. 调试的基本过程

(1)发现程序错误的存在
(2)以隔离、消除等方式对错误进行定位
(3)确定错误产生的原因
(4)提出纠正错误的解决办法
(5)对程序错误予以改正,重新测试
(6)“循环”执行上述过程,直至程序运行结果符合预期

3. 调试环境介绍

这里以Windows环境VS 2022 社区版为基础来介绍调试的具体方法和用途
(PS:以后学习Linux系统的开发后会再介绍Linux环境下的调试)
(1)调试环境的准备
在这里插入图片描述
在环境中选择Debug选项才能使代码正常调试
(Releas和Debug的区别后文有说明)
(2)常用快捷键

  • F5:启动调试,常用来直接跳到下一个断点处
  • F9:创建断点和取消断点。可以在程序的任意位置设置断点,这样就可以使得程序在想要的位置随意停止执行,继而一步步执行下去。右击断点还能执行更多操作。
    F9常与F5配合使用,用于应对多行代码及跨文件代码的调试。
  • F10:逐过程,通常用于处理一个过程,可以是一次函数调用或者是一条语句。
  • F11:逐语句,即每次都执行一条语句,但与F10不同的是这个快捷键可以使我们的执行逻辑进入到函数的内部而观察函数的具体执行过程。(这是最常用的!!!)
  • Ctrl + F5:程序直接开始运行而不进行调试。

下面给附上一篇介绍快捷键大全的博客的链接:
更多快捷键
(3)调试时查看程序程序当前信息

  • 查看临时变量的值
    在调试开始后可以在调试一栏找到监视窗口并打开(调试开始后一般自动打开),从监视窗口中,我们能观察变量的当前值及随程序运行的变化情况。
    在这里插入图片描述
    如下段代码:
int main()
{
	int i = 0;
	int arr[3] = { 1,2,3 };
	for (i = 0; i < 3; i++)
	{
		printf("%d", arr[i]);
	}
	return 0;
}

当程序执行完前两句语句时,我们通过监视窗口就能观察到变量i和数组arr值的情况。
其中自动窗口展现的是编译器自动判断当前语句执行可能会影响到的变量的值的情况:
在这里插入图片描述

局部变量窗口展现的是当前函数内所有局部变量的值的情况:
在这里插入图片描述
监视窗口展示的是我们自己想观察的变量的值的情况:
在这里插入图片描述
在监视窗口中,我们想查看哪个变量的值就可以输入对应的变量名,除此之外,还可以输入表达式,观察整个表达式的结果等,是最常用的窗口!!!

  • 查看内存信息
    在开始调试后,同样在窗口一栏,找到内存并打开,通过内存窗口我们就可以观察到变量的内存信息。
    在这里插入图片描述
    打开内存窗口后,我们就能查看变量的地址,及地址中对应的内容:
    在这里插入图片描述
    中间一栏是以十六进制展现的内容,可以在右上角改变其显示的列数,也就是显示的字节数,当前显示的是4个字节的内容(图中显示8个十六进制数,1个十六进制位为4个比特位,1个字节 = 8比特)
    在搜索栏中输入地址就能观察地址所对应的内存情况,还是上面那段代码,当语句执行完前两句时,我们输入&i就能看到局部变量i的地址及对应内容:
    (PS:在输入完按下回车后搜索栏会自动变为搜索内容的地址)
    在这里插入图片描述
    可以看到,i初始化为0,同样我们可以输入数组名(代表数组首元素的地址),查看整个数组:
    在这里插入图片描述
    可以看到,数组的三个元素分别被初始化为1,2,3
    通过内存窗口,我们还可以观察每个字节的变化情况,具体可见【逐步剖C】-第五章-指针初阶中对指针类型意义的介绍部分。
  • 查看调用堆栈
    同样在开始调试之后窗口一栏中找到调用堆栈打开,通过调用堆栈,可以清晰地反映函数的调用关系及当前调用所处的位置。
    在这里插入图片描述
    如下面这段代码:
void test2()
{
	printf("hehe\n");
}
void test1()
{
	test2();
}
void test()
{
	test1();
}
int main()
{
	test();
	return 0;
}

按分析来看,在main函数中调用了test函数,在test函数中又调用了test1函数,在test1函数中又调用了test2函数。这个逻辑关系在调用堆栈的窗口中展现地很清楚:
在这里插入图片描述
当我们通过F11来到test2函数内部时,调用堆栈情况如下:
在这里插入图片描述
函数的调用其实就是我们数据结构中所对应的栈的结构。

接下来介绍的两个通常配套使用,来观察系统中函数栈帧的创建和销毁情况*(PS:函数栈帧的创建和销毁,增加我们“内功”必不可少的知识,后续补上链接)*

  • 查看汇编信息
    有两种查看方法:
    开始调试后,在窗口一栏找到反汇编
    这里插入图片描述
    或者通过鼠标右键点击转到反汇编
    在这里插入图片描述

  • 查看寄存器信息
    这里也有两种方式
    同样在开始调试后,可以在窗口一栏中找到打开
    这里插入图片描述
    或者直接在监视窗口输入寄存器的名字,如ebp,eax等
    在这里插入图片描述
    关于调试环境的准备就介绍的这,接下来我们来看一个经典实例

三、 实战

1. 分析代码结果

看如下一段代码:

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

相信大家一眼就看出来了数组越界的问题,但实际运行起来程序会怎么样呢?
答案是:死循环打印 “hehe”。接下来我们就通过调试的方法来探究一下死循环原因。
(1)调试过程:
开始调试,当i的值为9时,屏幕上打了9个hehe:
在这里插入图片描述

目前一切正常,再进行下一次循环,就要产生越界访问了

  • i = 10时,通过监视窗口我们可以看到,arr[10]是一个随机值:
    (PS:越界访问内存中的值都是随机值)
    在这里插入图片描述
    执行循环体内容后,这个随机值被改为0:
    在这里插入图片描述
  • i = 11时,arr[11]同样也是个随机值:
    在这里插入图片描述
    执行循环体内容后,随机值被改为0:
    在这里插入图片描述
  • i = 12时,神奇的一幕发生了:
    在这里插入图片描述
    我们惊人地发现arr[12]的值竟然和i的值一样!
    接着我们再用“ & ”取地址符来看看它们的地址:
    在这里插入图片描述
    发现它们的地址也是一样的!那么死循环的原因我们就找到了:
    局部变量i的地址与数组往后越界到下标为12时(arr[12]) 的地址相同,当i = 12时,执行语句arr[i] = 0;也就相当于把i的值也置为了0,改变了控制循环的变量的值,让循环重新开始。

看到这我们不禁想问,变量i的地址怎么就恰好等于arr[12]的地址呢,接下来我们通过画图来讲述一下这个重要的知识点
(2)重要知识点
局部变量是存放在栈区中的,而栈区的使用习惯是:
先使用低地址,再使用高地址。
那么如上问题代码,就可以用一张图来表示,如图:
在这里插入图片描述
:那把语句int i = 0;放在语句int arr[10] = {0};之后是不是就不会死循环了呢?
:按照上面栈区的使用习惯确实是这样的,此时当程序运行起来就会报一个数组越界访问的错。
:对于每个编译器数组可访问的最后一个元素(arr[9])的地址和变量i的地址都是刚好差两个整型的大小吗?
:不是的,这只是一种在VS2022编译器环境下测得的一种结果。更多的测试结果大家可以看看一本书叫做《C陷阱和缺陷》,作者对这个问题做了较多的测试。

四、Debug与Releas版本区别

上面我们所说的所有关于调试的内容都是在Debug版本下进行的,这一点在调试的环境准备中也提到了。那么接下来我们简单介绍一下Debug版本与Release版本有什么区别。

1. 概念

Debug 通常称为调试版本,它包含调试信息,并且不作任何优化,便于程序员调试程序。

Release 称为发布版本,它往往是进行了各种优化,使得程序在代码大小和运行速度上都是最优的,以便用户很好地使用

2. 区别

由概念我们可以知道,它们本质上的区别就在于有没有对程序进行优化
下面通过两个方面来感受一下它们的区别
1)二者反汇编代码的对比
有这样一段简单的代码:

int main()
{
	int i = 0;
	printf("%d", i);
	return 0;
}

Debug版本下的反汇编代码:
在这里插入图片描述
Release版本下的反汇编代码:
在这里插入图片描述

可以看到Release版本下的反汇编代码比Debug版本下的简单了不少
(2)对于相同代码程序的不同执行结果
这里的例子是第三部分中我们所展示的会让程序死循环的代码:

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

由前面我们知道,如上代码在Debug版本中运行,程序会陷入死循环;但若在Release版本中运行,程序就会按规定循环次数打印hehe。
如下是Release版本下运行的结果:
在这里插入图片描述
我们在变量和数组创建后分别打印它们的地址(Release版本下不能进行调试,所以无法通过监视窗口来观察)就可以知道原因了:

#include <stdio.h>
int main()
{
	int i = 0;
	int arr[10] = { 0 };
	printf("%p\n", &i);
	printf("%p\n", arr);
	for (i = 0; i <= 12; i++)
	{
		arr[i] = 0;
		printf("hehe\n");
	}
	return 0;
}

地址结果:
在这里插入图片描述
可以看到变量 i 的地址比数组首元素的地址还要小,即i的内存空间使用的是更低的地址,故不会造成死循环。
所以原因就是,Release版本改变了变量在内存中开辟的顺序,从而影响到了程序执行的结果

五、如何写好(易于调试)的代码

1. 优秀的代码

(1)优秀的代码一般都有以下几个标准

  • 代码运行正常
  • bug很少
  • 效率高
  • 可读性高
  • 可维护性高
  • 注释清晰
  • 文档齐全

(2)可以通过以下几点来学习写出优秀的代码

  • 使用断言assert
  • 尽量使用关键字const
  • 养成良好的编码风格
  • 添加必要的注释
  • 避免编码的陷阱

下面我们就以模拟系统库函数strcpy为例,来说明如何一步步让代码变得“优秀”。

2. 模拟实现strcpy

这里先简单说明一下这个函数的功能:把一个字符传中的内容拷贝到另一个字符串当中。
假设字符串1为“xxxxxx”,字符串2为“abc”那么strcpy("xxxxxx","abc")就实现了将字符串2拷贝到了字符串1中,从而字符串1变为了"abcxxx"。(完整的函数信息大家可参考:strcpy - C++ Reference)
我们把字符串1叫做destination(拷贝目的地),字符串2叫做source(拷贝来源)
(1) 那么由上面例子我们可以知道,实现这个函数,无非就是用source中的字符串对应着逐一替换destination中的内容,如下图所示:
在这里插入图片描述
先替换第一位,然后让destinationsource两个指针++,替换下一位,循环直到source指针遇到字符串结束标志 ‘\0’ ,跳出循环后再拷贝最后的 ‘\0’ 写成代码就是:

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

有了上面代码以后,我们开始考虑对其进行优化。优化的思路总共有两点:

  • 既然是后置++,那么循环体中的内容就可以简化为*dest++ = *src++;先进行当前位的替换,再进行指针的后移进行下一位的替换
  • 语句*dest++ = *src++;是赋值表达式,而赋值表达式的结果就为所赋的值,所以我们可以将这个语句作为循环的判断条件,这样最后在拷贝完 ‘/0’ (赋值表达式的值为‘\0’,也就是0)之后循环也刚好停止。
    那么整体的代码就优化为:
void my_strcpy(char* dest, char* src)
{
	while (*dest++ = *src++)
	{
		;
	}
}

(2) 到这我们发现代码其实还存在问题:若不小心传了一个空指针,那对空指针进行解引用操作就是非法访问内存的行为了,所以我们需要在函数的一开始对参数进行有效性的检验,那么这里可以用if语句实现:

if (src == NULL || dest == NULL)
{
	return;
}

但if语句有一个缺点:但某个参数是空指针时,函数返回,这样确实规避了问题,但没有真正暴露出问题,这样就不利于对整体代码的维护。想要真正地暴露问题我们就需要用到断言函数assert

  • 断言assert的运用
    使用这个函数需要包含的头文件为<assert.h>;格式为assert(表达式);当表达式为真时,什么是都不会发生,而当表达式为假时,程序就会报错,并且会给出相关的错误信息。
    如下面这段代码:
    在这里插入图片描述

运行结果:
在这里插入图片描述
可以看到,程序报错,并且在屏幕上给出了错误信息:发生错误的文件位置以及发生错误的行数。
规避了问题的同时,又帮我们找出了问题(๑•̀ㅂ•́)👍。
(3)到这代码基本上没有什么大问题了,但其实我们还可以继续对其进行优化。
假设我们不小心把*dest++ = *src++写反了,变为*src++ = *dest++,此时用assert语句就不能很好地发现问题了,此时若运行代码,最终程序会错误终止。为了应对这个问题,我们就需要用到关键字const

  • 关键字const的运用
    我们都知道,被const修饰的常量无法修改。那么回到函数中,我们的期望是把字符串source中的内容拷贝到字符串destination中,也就是不去改变source字符串中的内容,那么我们就可以在参数部分用const修饰source字符串,如:void my_strcpy(char* dest, const char* src)。这样一来,若粗心写成了*src++ = *dest++,编译器会直接指出错误信息,而不给你运行的机会了
    所以,在以后实现某个函数时,希望某个参数在函数的实现的过程中不被改变就可以用const来进行修饰。

补充:关于const,这里补充两个小知识点:
第一就是有关const修饰指针问题,所谓“指针常量”和“常量指针”的区别。
也就是const int* int* const的区别,它们分别叫什么不重要,重要的是看到要能区分开:

对于const int * const修饰的是指针指向的内容,保证指针指向的内容不能通过指针来改变。但是指针变量本身的内容可变;
对于int * constconst修饰的是指针变量本身,保证了指针变量的内容不能修改,但是指针指向的内容,可以通过指针改变。

这里有一个我个人的理解小技巧,分享给大家:const在“ * ”左边,此时把“ * ”理解为解引用操作符,那么就可以认为const修饰到了这个解引用操作符,故认为不能对指针进行解引用操作,即指针指向的内容不能通过指针来改变。但是指针变量本身的内容可变;同理,const在“ * ”右边就是没有修饰到这个解引用操作符,那就认为可以对指针进行解引用操作,也就是指针指向的内容可以通过指针改变,对应地,指针变量本身的内容就不能修改。

第二,有关const修饰变量的问题
我们知道当有这样的语句const int n = 10;时,n的值就不能被改变,但这里准确来说是不能通过赋值表达式改变,因为此时通过取n的地址,然后解引用仍能改变n的值,“不让进门可以翻窗进”,要注意这一点。

(4) 最后其实还有一个可以改变的地方——函数的返回类型
我们上面所模拟的strcpy的返回类型为void型,但对比原函数我们发现,原函数的返回类型为char*型,相比于void型,其优点在于可以实现函数的链式访问,即我们可以这样写:

printf("%s\n", my_strcpy(str1, str2));
//字符串拷贝完成后直接打印输出

对于返回类型为void型,函数在完成拷贝函数自动返回,不需要返回值,所以也就不需要额外的变量来保存字符串的地址;但对于返回类型为char*型,我们需要返回拷贝完成后的字符串,也就是destination,所以在进行拷贝之前需要创建一个变量来保存字符串destination的地址。
代码实现如下:

char* my_strcpy(char* dest, const char* src)
{
	assert(dest && src);//断言指针的有效性
	char* ret = dest;	//保存地址
	while (*dest++ = *src++)
	{
		;
	}
	return ret;
}

以上就是全部对模拟实现strcpy函数的优化过程了,一步步的优化其实就是一个个写出优秀代码的关键点,我们平常写代码时也应该多多考虑这些问题,养成良好的编程习惯。
下面附上VS2022中strcpy函数的源码供大家参考:
在这里插入图片描述

七、常见的编译错误

最后给大家介绍三种编译错误,希望我们都能做一个有心人,积累排错经验,不断朝着写出优秀代码的目标前进。

1. 编译型错误

通过直接看错误提示信息一般就可以解决问题,或者凭借经验也可以搞定,解决难度相对来说简单

2. 链接型错误

看错误提示信息,主要在代码中找到错误信息中的标识符,然后定位问题所在。一般是标识符名不
存在或者拼写错误

3. 运行时错误

只能借助调试,逐步定位问题。解决难度最棘手

本章完。

看完觉得有觉得帮助的话不妨点赞收藏鼓励一下,有疑问或有误地方的地方还恳请过路的朋友们留个评论,多多指点,谢谢朋友们!🌹🌹🌹

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

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

相关文章

2.21、管程

管程就是一个软件模块&#xff0c;里面封装了实现同步&#xff0c;互斥的函数 1、为什么要引入管程 信号量机制存在的问题&#xff1a;编写程序困难、易出错 能不能设计一种机制&#xff0c;让程序员写程序时不需要再关注复杂的 PV 操作&#xff0c;让写代码更轻松呢? 1973年…

使用python从houdini导出arnold材质

先说结果,如果要使用python从houdini导出arnold材质可以使用以下命令 import hou import htoa vops [hou.node("/shop/arnold_vopnet1/")] htoa.material.materialExport(vops,"D:/mat.mtlx")以下是在寻找解决方法的过程中记录的流水账日志 google泛搜索…

MySQL入门篇-MySQL MHA高可用实战

MHA简介 MHA&#xff08;Master High Availability&#xff09;目前在MySQL高可用方面是一个相对成熟的解决方案&#xff0c;它由日本DeNA公司的youshimaton&#xff08;现就职于Facebook公司&#xff09;开发&#xff0c;是一套优秀的作为MySQL高可用性环境下故障切换和主从提…

2023软考考哪个证书好?

软考有三个级别&#xff08;初级&#xff0c;中级和高级&#xff09;&#xff0c;这三个级别分别对应5个方向&#xff0c;下面这张图片呢&#xff0c;可以一目了然&#xff0c;一些小小建议&#xff01;&#xff01;&#xff01;遵循一个原则&#xff1a;首先选专业对口的科目&…

为什么这11道JVM面试题这么重要(附答案)

本文内容整理自 博学谷狂野架构师 运行时数据区都包含什么 虚拟机的基础面试题 程序计数器Java 虚拟机栈本地方法栈Java 堆方法区 程序计数器 程序计数器是线程私有的&#xff0c;并且是JVM中唯一不会溢出的区域&#xff0c;用来保存线程切换时的执行行数 程序计数器&#xff…

算法笔记(三)—— 桶排序及排序总结

堆 逻辑上是一棵完全二叉树&#xff08;依次遍满或者全满&#xff09;。 数组可以转为完全二叉树&#xff0c;完全二叉树某结点左孩子(2*i1)&#xff0c;右孩子(i*22)&#xff0c;父结点((i-1/)2)&#xff0c;根节点的父还是自己。 如何将数组转化为堆&#xff08;大根堆&…

Web自动化测试——selenium篇(一)

文章目录一、环境准备二、Web 自动化测试 Demo三、元素定位常用方法四、元素定位失败可能原因五、测试对象操作六、等待操作七、信息打印在学习 Web 自动化测试的过程中&#xff0c;selenium 是其中的常用工具。除了其开源免费&#xff0c;包含丰富的 API 以外&#xff0c;它还…

基于“PLUS模型+“生态系统服务多情景模拟预测

工业革命以来&#xff0c;社会生产力迅速提高&#xff0c;人类活动频繁&#xff0c;此外人口与日俱增对土地的需求与改造更加强烈&#xff0c;人-地关系日益紧张。此外&#xff0c;土地资源的不合理开发利用更是造成了水土流失、植被退化、水资源短缺、区域气候变化、生物多样性…

windows安装proget实现nuget私有包部署

下载proget 官网 下载地址 免费下载 安装proget 下载完成之后双击安装 选择ProGet 默认选择即可 也可以指定数据库&#xff0c;SQL Server数据库 Server服务器名;Database数据库名;User Id用户名;Password密码 Serverlocalhost;DatabaseProGet2;User Idsa;Passwordxxxx…

浅谈一下前端工作中全流程多层次的四款测试工具

在应届生找工作的时候&#xff0c;我们经常会见到一条招聘要求&#xff1a;要求实习经历。或者 有实习经历者优先。 为什么大部分公司在招聘时&#xff0c;都要求你必须有实习经历&#xff1f; 商业项目与个人项目不同&#xff0c;一段实习经历&#xff0c;能够熟悉公司中成熟…

最接近的三数之和-力扣16-java排序+双指针

一、题目描述给你一个长度为 n 的整数数组 nums 和 一个目标值 target。请你从 nums 中选出三个整数&#xff0c;使它们的和与 target 最接近。返回这三个数的和。假定每组输入只存在恰好一个解。示例 1&#xff1a;输入&#xff1a;nums [-1,2,1,-4], target 1输出&#xff…

Multisim14 安装包及安装教程

Multisim14 安装教程 Multisim14下载地址&#xff1a;Kevin的学习站–安装包下载地址 Multisim14 简介&#xff1a; Multisim 14 是美国国家仪器有限公司&#xff08;National Instrument&#xff0c;NI&#xff09;推出的以 Windows 为基础、符合工业标准的、具有 SPICE 最佳仿…

三数之和-力扣15-java排序+双指针

一、题目描述给你一个整数数组 nums &#xff0c;判断是否存在三元组 [nums[i], nums[j], nums[k]] 满足 i ! j、i ! k 且 j ! k &#xff0c;同时还满足 nums[i] nums[j] nums[k] 0 。请你返回所有和为 0 且不重复的三元组。注意&#xff1a;答案中不可以包含重复的三元组。…

好用的研发管理看板工具有哪些?10款主流看板管理软件盘点

10大企业看板工具软件&#xff1a;1.软件开发项目看板 PingCode&#xff1b;2.通用看板软件 Worktile&#xff1b;3.开源看板软件 Wekan&#xff1b;4.免费看板软件 Trello&#xff1b;5.个人和小团队的看板软件 Todoist &#xff1b;6.开源免费看 Kanboard&#xff1b;7.面向个…

想考个PMP证书,怎么报考啊?

先说报名条件&#xff1a; PMP报名条件说难也不难&#xff0c;但也是有条件的&#xff0c;看下面的视频&#xff0c;最难满足的就是题主担心的工作经验&#xff0c;其实这个也并不一定要项目管理经验&#xff0c;年纪合适&#xff0c;一般来说也是可以考的。 PMP考试时间 23年…

微服务实战--高级篇:分布式缓存 Redis

分布式缓存 – 基于Redis集群解决单机Redis存在的问题 单机的Redis存在四大问题&#xff1a; 1.Redis持久化 Redis有两种持久化方案&#xff1a; RDB持久化AOF持久化 1.1.RDB持久化 RDB全称Redis Database Backup file&#xff08;Redis数据备份文件&#xff09;&#xf…

数据结构 - 优先级队列(堆)

文章目录前言1.介绍优先级队列2. 认识堆3. 实现优先级队列3.1 了解优先级队列的构造方法&#xff1a;3.2 使用优先级队列解决问题&#xff1a;总结前言 本篇PriorityQueue优先级队列的介绍其底层是堆&#xff0c;关于堆的认识&#xff0c;使用优先级队列能解决的一些问题&…

day12_Django的基本操作

文章目录创建django项目项目-各个文件的含义app-各个文件的含义MVT运行&#xff0c;查看是否创建项目框架成功ORM迁移->在数据库中形成对应的 表通过类名 对象的方法完成数据库的增删查改操作通过模型类&#xff08;shell&#xff09;的方法操作数据表后台管理进入后台http:…

dvwa靶场的搭建过程

dvwa靶场的搭建过程 记录一下自己重新开始学习web安全之路①。 首先一个要搭建一个网站需要的准备 服务器 – 操作系统 windows linux&#xff08;可以将电脑当成服务器&#xff09; 数据库 MySQL sql server Oracle access 、、、、作用&#xff1a;存储数据 中间件 apac…

零基础学MySQL(五)-- 详细讲解数据库中的常用函数

目录&#x1f387;一、聚合函数1️⃣count 函数&#xff08;1&#xff09;基本语法&#xff08;2&#xff09;基本练习&#xff08;3&#xff09;注意细节2️⃣sum 函数&#xff08;1&#xff09;基本语法&#xff08;2&#xff09;基本练习&#xff08;3&#xff09;注意细节3…