超详细-数据结构-二叉树概念及结构,堆的概念及结构以及堆的代码的c语言实现

本篇博客将详细讲述二叉树的概念,堆的概念及结构以及堆的代码实现,以及二叉树,堆的相关应用。Top K 问题,堆排序的实现以及二叉树链式结构的实现将在之后的博客更新。你可在目录中找到你想重点阅读的内容。堆的完整代码实现在文章结尾处。

堆的其余文章在主页:

堆排序--TOP-K问题

堆排序的讲解

目录

一、树的概念及结构

1.1 树的概念

1.2 树的相关概念

1.3 树的表示

1.4 树在实际中的运用

二、二叉树概念及结构

2.1 概念

2.2 现实中的二叉树:

2.3 特殊的二叉树:

2.3.1 满二叉树

2.3.2 完全二叉树

2.4 二叉树的存储结构

2.4.1 顺序存储

a. 完全二叉树

a.1 完全二叉树规律:

b. 非完全二叉树

2. 链式存储(此篇省略,只说概念,后续会更新)

三、二叉树的顺序结构

3.1 二叉树的顺序结构

3.2 堆的概念及结构

a.堆的应用:

四、堆的实现

4.1 分析

4.1.1 所要实现的功能:Heap.h

4.1.2  堆的插入(向上调整)

4.1.3 堆的删除(向下调整)(堆的删除是删除根节点)

4.2 代码实现

4.2.1堆的初始化与堆的销毁

4.2.2 堆的插入

4.2.3 堆的删除

4.2.4 取堆顶的数据,堆的个数,堆的判空。

五、完整代码

1.My_Heap.h

2.My_Heap.c

3.测试用例


一、树的概念及结构

1.1 树的概念

       树是一种非线性的数据结构,它是由n(n>=0)个有限结点组成一个具有层次关系的集合。把它叫做树是因为它看起来像一棵倒挂的树,也就是说它是根朝上,而叶朝下的。

根结点: 无前驱节点;

树是递归定义的:除根节点外,其余结点被分成M(M>0)个互不相交(子树之间不能有交集)的集合T1、T2、……、Tm,其中每一个集合Ti(1<= i<= m)又是一棵结构与树类似的子树。每棵子树的根结点有且只有一个前驱,可以有0个或多个后继。一颗N个结点的树有N-1条边。

拆解:一个根,n颗子树(n>=0)构成,

根A

子树:B/C/D

1.2 树的相关概念

节点的度:一个节点含有的子树的个数称为该节点的; 如上图:A的为6
叶节点或终端节点:度为0的节点称为叶节点; 如上图:B、C、H、I...等节点为叶节点
非终端节点或分支节点:度不为0的节点; 如上图:D、E、F、G...等节点为分支节点
双亲节点或父节点:若一个节点含有子节点,则这个节点称为其子节点的父节点; 如上图:A是B的父节点
孩子节点或子节点:一个节点含有的子树的根节点称为该节点的子节点; 如上图:B是A的孩子节点
兄弟节点:具有相同父节点的节点互称为兄弟节点; 如上图:B、C是兄弟节点
树的度:一棵树中,最大的节点的度称为树的度; 如上图:树的度为6
节点的层次:从根开始定义起,根为第1层,根的子节点为第2层,以此类推;
树的高度或深度:树中节点的最大层次; 如上图:树的高度为4
堂兄弟节点:双亲在同一层的节点互为堂兄弟;如上图:H、I互为兄弟节点
节点的祖先:从根到该节点所经分支上的所有节点;如上图:A是所有节点的祖先
子孙:以某节点为根的子树中任一节点都称为该节点的子孙。如上图:所有节点都是A的子孙
森林:由m(m>0)棵互不相交的树的集合称为森林;(并查集就是森林)

1.3 树的表示

        树结构相对线性表就比较复杂了,要存储表示起来就比较麻烦了,既然保存值域,也要保存结点和结点之间的关系,实际中树有很多种表示方式如:双亲表示法,孩子表示法、孩子双亲表示法以及孩子兄弟表示法等。这里就了解最常用的孩子兄弟表示法。

a.孩子兄弟表示法

typedef int DataType;
struct Node
{
struct Node* _firstChild1; // 第一个孩子结点
struct Node* _pNextBrother; // 指向其下一个兄弟结点
DataType _data; // 结点中的数据域
};

如图所示:

1.4 树在实际中的运用

最常用的例如文件目录结构

二、二叉树概念及结构

2.1 概念

一棵二叉树是结点的一个有限集合,该集合:
1. 或者为空
2. 由一个根节点加上两棵别称为左子树和右子树的二叉树组成

从下图可以看出:

1.二叉树不存在度大于2的结点

2.二叉树的子树有左右之分,次序不能颠倒,二叉树是有序树

注意:每个结点最多有两个孩子不等价于度为2的树,度为2的树 就是 二叉树。

2.2 现实中的二叉树:

2.3 特殊的二叉树:

1. 满二叉树:一个二叉树,如果每一个层的结点数都达到最大值,则这个二叉树就是满二叉树。也就是说,如果一个二叉树的层数为K,且结点总数是 ,则它就是满二叉树。

(每一层都是满的)

2. 完全二叉树:完全二叉树是效率很高的数据结构,完全二叉树是由满二叉树而引出来的。对于深度为K的,有n个结点的二叉树,当且仅当其每一个结点都与深度为K的满二叉树中编号从1至n的结点一一对应时称之为完全二叉树。 要注意的是满二叉树是一种特殊的完全二叉树。(前h-1层是满的,最后一层不一定满,但是从左到右必须连续)

2.3.1 满二叉树


如图:假设一颗满二叉树的高度为h,第一层结点数:2^0,第二层:2^1,第三层:2^2......可得到第h层的结点数为:2^(h-1)

因此,一个满二叉树的总结点数:  2^0+2^1+2^2+...+2^(h-1) = 2^h - 1

假设有N个结点,可得到的高度为:h = log2(N+1)

2.3.2 完全二叉树

理解了完全二叉树概念之后可知道:

假设高度为h

节点的总数量区间:[2^(h-1)-1,2^h-1]

高度区间:               [(log2N )+ 1,log2(N+1)](当N足够大时,高度几乎就无区别)

2.4 二叉树的存储结构

二叉树一般可以使用两种结构存储,一种顺序结构,一种链式结构。


2.4.1 顺序存储


        顺序结构存储就是使用数组来存储,一般使用数组只适合表示完全二叉树,因为非完全二叉树会有空间的浪费。而现实中使用中只有堆才会使用数组来存储。二叉树顺序存储在物理上是一个数组,在逻辑上是一颗二叉树。

a. 完全二叉树
a.1 完全二叉树规律:

可发现一个规律:父子节点对应下标:

leftchild = parent*2+1,rightchild = parent*2+2

再者,由于数组下标都为整数:(不区分左右孩子)

parent = (child -1)/2   (可自行代入值计算)

b. 非完全二叉树

存入数组中,必须留空,造成空间浪费,才能实现如完全二叉树的规律,不适合数组结构存储,只适合链式存储。

2. 链式存储(此篇省略,只说概念,后续会更新)

二叉树的链式存储结构是指,用链表来表示一棵二叉树,即用链来指示元素的逻辑关系。 通常的方法是链表中每个结点由三个域组成,数据域和左右指针域,左右指针分别用来给出该结点左孩子和右孩子所在的链结点的存储地址 。链式结构又分为二叉链和三叉链,当前阶段还只能讲二叉链,后面会讲到高阶数据结构如红黑树等会用到三叉链。

三、二叉树的顺序结构

3.1 二叉树的顺序结构

        普通的二叉树是不适合用数组来存储的,因为可能会存在大量的空间浪费。而完全二叉树更适合使用顺序结构存储。现实中我们通常把堆(一种二叉树)使用顺序结构的数组来存储,需要注意的是这里的堆和操作系统虚拟进程地址空间中的堆是两回事,一个是数据结构,一个是操作系统中管理内存的一块区域分段。

3.2 堆的概念及结构

注意:每棵树的根,都是这棵树的最大/小值。兄弟之间,左孩子不一定小于右孩子

堆排序的时间复杂度:O(N*log2N)

要求:

大堆:任意一个父亲 >= 孩子

小堆:任意一个父亲 <= 孩子

a.堆的应用:

例如给全国的川菜馆进行一个排名,如果有100万家店,需要排几次? 20次

b.思考几个问题:

1.有序数组一定是堆?  是的

2.堆一定有序吗?      不一定

四、堆的实现

4.1 分析

4.1.1 所要实现的功能:Heap.h

typedef int HPDataType;
typedef struct Heap
{
    HPDataType* _a;
    int _size;
    int _capacity;
}Heap;

//堆的初始化
void HeapInit(Heap* php);
// 堆的销毁
void HeapDestory(Heap* hp);
// 堆的插入
void HeapPush(Heap* hp, HPDataType x);
// 堆的删除
void HeapPop(Heap* hp);
// 取堆顶的数据
HPDataType HeapTop(Heap* hp);
// 堆的数据个数
int HeapSize(Heap* hp);
// 堆的判空
int HeapEmpty(Heap* hp);

我主要针对于堆的插入与删除讲解

4.1.2  堆的插入(向上调整)

思路:将插入值,依次与他的父节点进行比较,若它的值小于(小堆)/大于(大堆),他的父节点的值,那么就进行值的交换。

仔细看图,我们需要往在这已经存在的 小堆 中插入26到数组的最后(设数组大小为size,那么26的下标:size - 1,而在这里 插入26后 size = 11,因此26的下标为 10),我们知道小堆的定义是:父节点 <= 孩子,因此对于26,我们先通过在之前找到的完全二叉树规律,可知26的父节点下标:int((10-1)/2) = 4。即对应值28, 26<28 ,交换26,28的对应下标的值,此时26对应的下标为:4 ,28对应的下标为:10。26应继续与它的父节点进行值比较,新父节点下标:1,对应值:18, 26>18,此时不用再交换,顺序正确。

知道了如何插入,那么如果插入的值比堆的所有值都小,什么情况下退出呢?

如图序号1-7,详细展现了,如果一个比堆内所有值都小的数进入堆后如何变化的图解。

看完此图后,序号7时,14已经到达堆顶,他的下标也变成了0,因此我们循环退出的条件就是下标child  == 0,为什么不使用parent?parent再减就数组越界了。

4.1.3 堆的删除(向下调整)(堆的删除是删除根节点)

思路:将插入值先与堆内最后一个数值进行交换,再删除掉最后一个值。再将插入值与他的孩子节点依次值比较,若插入值大于(小堆)/ 小于(大堆)孩子节点的值时,与孩子节点的值进行交换。为什么不可挪动数据覆盖?(将size+1,从最后一个值开始依次往后挪动),这样会直接破坏整个堆结构,想要实现,时间复杂度太大。

如图:

4.2 代码实现

4.2.1堆的初始化与堆的销毁

//堆的初始化
void HeapInit(Heap* hp)
{
    assert(hp);
    hp->_a = NULL;
    hp->_size = 0;
    hp->_capacity = 0;
}

// 堆的销毁
void HeapDestory(Heap* hp)
{
    assert(hp);
    free(hp->_a);
    hp->_a = NULL;
    hp->_size = hp->_capacity = 0;
}

4.2.2 堆的插入

//交换子节点与父节点的值

void Swap(HPDataType* p1, HPDataType* p2)//传的是地址
{
    HPDataType tmp = *p1;
//这里就是解引用取值
    *p1 = *p2;
    *p2 = tmp;
}

//调整堆结构
void AdjustUp(HPDataType* a, int child)
{
    int parent = (child - 1) / 2;
    while (child > 0)
    {
        if (a[child] < a[parent])
        {
            Swap(&a[child], &a[parent]);
//交换子节点与父节点的值
            child = parent;
            parent = (parent - 1) / 2;
//parent = (child - 1) / 2
        }
        else
        {
            break;
        }
    }
}


// 堆的插入
void HeapPush(Heap* hp, HPDataType x)
{
    assert(hp);
    if (hp->_size == hp->_capacity)
    {
        int newCapacity = hp->_capacity = 0 ? 4 : hp->_capacity * 2;
        HPDataType* obj = (HPDataType*)realloc(hp->_a, sizeof(HPDataType) * 2);
        if (obj == NULL)
        {
            perror("realoc fail");
            exit(-1);
        }
        hp->_a = obj;
        hp->_capacity = newCapacity;
    }

    hp->_a[hp->_size] = x;
    hp->_size++;
    AdjustUp(hp->_a, hp->_size - 1);
//调整堆结构
}

4.2.3 堆的删除

void Adjustdown(HPDataType* a, int size, int parent)
{
    int child = parent * 2 + 1;
    while (child <= size - 1)
    {

        //这里可能发生越界若不写(child + 1) <= size - 1
        if ((child + 1) <= size - 1 && a[child + 1] < a[child])
        {
            ++child;
        }
        if (a[child] < a[parent])
        {
            Swap(&a[child], &a[parent]);
            parent = child;
            child = parent * 2 + 1;
        }
        else
        {
            break;
        }

    }
}
// 堆的删除
void HeapPop(Heap* hp)
{
    assert(hp);
    assert(hp->_size > 0);
    Swap(&hp->_a[0], &hp->_a[hp->_size - 1]);
    hp->_size--;
    Adjustdown(hp->_a, hp->_size , 0);
//记得传入插入数据的值
}

4.2.4 取堆顶的数据,堆的个数,堆的判空。

// 取堆顶的数据
HPDataType HeapTop(Heap* hp)
{
    assert(hp);
    assert(hp->_size > 0);
    return hp->_a[0];
}
// 堆的数据个数
int HeapSize(Heap* hp)
{
    assert(hp);
    return hp->_size;

}
// 堆的判空
int HeapEmpty(Heap* hp)
{
    assert(hp);
    return hp->_size == 0;
}

 

五、完整代码

1.My_Heap.h

#include<stdio.h>
#include<assert.h>
#include<stdbool.h>
#include<stdlib.h>
typedef int HPDataType;
typedef struct Heap
{HPDataType* _a;int _size;int _capacity;
}Heap;//堆的初始化
void HeapInit(Heap* php);
// 堆的销毁
void HeapDestory(Heap* hp);
// 堆的插入
void HeapPush(Heap* hp, HPDataType x);
// 堆的删除
void HeapPop(Heap* hp);
// 取堆顶的数据
HPDataType HeapTop(Heap* hp);
// 堆的数据个数
int HeapSize(Heap* hp);
// 堆的判空
int HeapEmpty(Heap* hp);

2.My_Heap.c

#include"My_Heap.h"
//堆的初始化
void HeapInit(Heap* hp)
{assert(hp);hp->_a = NULL;hp->_size = 0;hp->_capacity = 0;
}
// 堆的销毁
void HeapDestory(Heap* hp)
{assert(hp);free(hp->_a);hp->_a = NULL;hp->_size = hp->_capacity = 0;
}
void Swap(HPDataType* p1, HPDataType* p2)
{HPDataType tmp = *p1;*p1 = *p2;*p2 = tmp;
}
void AdjustUp(HPDataType* a, int child)
{int parent = (child - 1) / 2;while (child > 0){if (a[child] < a[parent]){Swap(&a[child], &a[parent]);child = parent;parent = (parent - 1) / 2;}else{break;}}
}
// 堆的插入
void HeapPush(Heap* hp, HPDataType x)
{assert(hp);if (hp->_size == hp->_capacity){int newCapacity = hp->_capacity = 0 ? 4 : hp->_capacity * 2;HPDataType* obj = (HPDataType*)realloc(hp->_a, sizeof(HPDataType) * 2);if (obj == NULL){perror("realoc fail");exit(-1);}hp->_a = obj;hp->_capacity = newCapacity;}hp->_a[hp->_size] = x;hp->_size++;AdjustUp(hp->_a, hp->_size - 1);
}
void Adjustdown(HPDataType* a, int size, int parent)
{int child = parent * 2 + 1;while (child <= size - 1){if ((child + 1) <= size - 1 && a[child + 1] < a[child]){++child;}if (a[child] < a[parent]){Swap(&a[child], &a[parent]);parent = child;child = parent * 2 + 1;}else{break;}}
}
// 堆的删除
void HeapPop(Heap* hp)
{assert(hp);assert(hp->_size > 0);Swap(&hp->_a[0], &hp->_a[hp->_size - 1]);hp->_size--;Adjustdown(hp->_a, hp->_size , 0);
}
// 取堆顶的数据
HPDataType HeapTop(Heap* hp)
{assert(hp);assert(hp->_size > 0);return hp->_a[0];
}
// 堆的数据个数
int HeapSize(Heap* hp)
{assert(hp);return hp->_size;}
// 堆的判空
int HeapEmpty(Heap* hp)
{assert(hp);return hp->_size == 0;
}

3.测试用例

int main()
{int a[] = {15,18,19,25,28,34,65,49,27,37};Heap hp;HeapInit(&hp);for (int i = 0; i < sizeof(a) / sizeof(int); ++i){HeapPush(&hp, a[i]);}//int k = 3;//while (k--)//{//	printf("%d\n", HeapTop(&hp));//	HeapPop(&hp);//}while (!HeapEmpty(&hp)){printf("%d ", HeapTop(&hp));HeapPop(&hp);}printf("\n");return 0;
}

结语:

       随着这篇关于题目解析的博客接近尾声,我衷心希望我所分享的内容能为你带来一些启发和帮助。解题的过程往往充满挑战,但正是这些挑战让我们不断成长和进步。我在准备这篇文章时,也深刻体会到了学习与分享的乐趣。

       在此,我要特别感谢每一位阅读到这里的你。是你的关注和支持,给予了我持续写作和分享的动力。我深知,无论我在某个领域有多少见解,都离不开大家的鼓励与指正。因此,如果你在阅读过程中有任何疑问、建议或是发现了文章中的不足之处,都欢迎你慷慨赐教。         你的每一条反馈都是我前进路上的宝贵财富。同时,我也非常期待能够得到你的点赞、收藏,这将是对我莫大的支持和鼓励。当然,我更期待的是能够持续为你带来有价值的内容,让我们在知识的道路上共同前行。

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

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

相关文章

【秋招笔试题】方程

解析&#xff1a;暴力枚举。建议用Python的eval函数,C手写略麻烦。 #include <iostream> #include <string> #include <vector> #include <sstream>using namespace std;long long stringResult(const string &expr) {vector<string> plusP…

visual studio性能探测器使用案列

visual studio性能探测器使用案列 在visual studio中&#xff0c;我们可以使用自带的工具对项目进行性能探测&#xff0c;具体如下 1.选择性能探查器 Vs2022/Vs2019中打开方式&#xff1a; Vs2017打开方式&#xff1a; 注意最好将解决方案配置为&#xff1a;Release Debu…

昇思25天学习打卡营第22天|CycleGAN图像风格迁移互换

相关知识 CycleGAN 循环生成网络&#xff0c;实现了在没有配对示例的情况下将图像从源域X转换到目标域Y的方法&#xff0c;应用于域迁移&#xff0c;也就是图像风格迁移。上章介绍了可以完成图像翻译任务的Pix2Pix&#xff0c;但是Pix2Pix的数据必须是成对的。CycleGAN中只需…

如何获得某个Window画面所属包名packageName和用户userId

在安卓上获得某个Window画面所属包名packageName和用户userId的方法 1&#xff0c;用到的工具如下&#xff1a; adb androidSDK里的monitor工具 adb shell dumpsys window animator adb shell dumpsys window命令 jdk 1.8已在安卓14模拟器上测试通过。 以AOSP的launcher中的m…

【.NET 6 实战--孢子记账--从单体到微服务】--开发环境设置

在这一小节&#xff0c;我们将设置开发环境。 一、安装SDK 咱们的项目使用的是 .NET6&#xff0c;开发前我们需要从官网上下载.NET6 SDK&#xff08;点击下载&#xff09;&#xff0c;这里要注意的是我们需要下载.NET6 SDK&#xff0c;而不是 .NET6 Runtiem 。SDK 包含 Runti…

C++静态成员变量和静态成员函数

演示代码如下&#xff1a; #include<iostream> using namespace std;class Person { public://静态成员函数 所有对象共享一个函数&#xff0c;且只能调用静态成员变量 ******static void func(){m_A 300;cout << "静态成员函数调用" << endl;}/…

【MySQL进阶之路 | 高级篇】简述Bin Log日志

1. 日志类型 MySQL有不同类型的日志文件&#xff0c;用来存储不同类型的日志&#xff0c;分为二进制日志、错误日志、通用查询日志和慢查询日志&#xff0c;这也是常用的4种。MySQL 8又新增两种支持的日志:中继日志和数据定义语句日志。使用这些日志文件&#xff0c;可以查看M…

openFeign实现服务间调用

以两个模块&#xff08;batch&#xff0c;business&#xff09;为例子&#xff0c;期望实现batch调用business中的hello接口 在主程序batch中引入pom文件 <!--远程调用openfeign--><dependency><groupId>org.springframework.cloud</groupId><arti…

Linux网络工具“瑞士军刀“集合

一、背景 平常我们在进行Linux服务器相关运维的时候&#xff0c;总会遇到一些网络相关的问题。我们可以借助这些小巧、功能强悍的工具帮助我们排查问题、解决问题。 下面结合之前的一些使用经验为大家介绍一下一些经典应用场景下&#xff0c;这个网络命令工具如何使用的。例如怎…

游泳馆押金原路退回源码解析

<dl class"list "><dd class"address-wrapper dd-padding"><div class"address-container"><cyberdiv style"color:#f0efed;font-size:14px;float:right;position:absolute;right:10px;top: 2px;">●●●<…

MYSQL 第三次作业

1、第三次作业 01、SELECT * FROM student; SELECT * FROM score; 02、SELECT * FROM student LIMIT 1, 3; 03、SELECT * FROM student WHERE department IN (计算机系, 英语系); 04、SELECT * FROM student WHERE birth_year > 1998; 05、SELECT department, COUNT(*) as c…

CSP-J模拟赛day1——解析+答案

题目传送门 yjq的吉祥数 题解 送分题&#xff0c;暴力枚举即可 Code #include<bits/stdc.h> using namespace std;int l,r; int num1,tmp0,q[10000],a[10000]; int k (int x){for (int j1;j<tmp;j){if (xq[j])return 0;}return 1; } int main(){while (num<100…

Linux Vim全能攻略:实战代码,轻松掌握文本编辑神器

1. Vim简介与安装 1.1 Vim的历史与发展 Vim&#xff08;Vi IMproved&#xff09;是一款高度可配置的文本编辑器&#xff0c;它起源于1976年由Bill Joy开发的Vi编辑器。Vi是Unix系统上最古老的文本编辑器之一&#xff0c;因其强大的功能和高效的编辑方式而广受欢迎。随着时间的…

Photos框架 - 自定义媒体选择器(UI预览)

引言 在前面的博客中我们已经介绍了使用媒体资源数据的获取&#xff0c;以及自定义的媒体资源选择列表页。在一个功能完整的媒体选择器中&#xff0c;预览自然是必不可少的&#xff0c;本篇博客我们就来实现一个资源的预览功能&#xff0c;并且实现列表和预览的数据联动效果。…

GLSL教程 第9章:计算着色器

目录 9.1 计算着色器的基本概念 计算着色器的主要特点&#xff1a; 9.2 计算着色器的基础知识 1. 创建计算着色器 计算着色器代码&#xff1a; 2. 编译和链接计算着色器 示例代码&#xff1a; 3. 执行计算着色器 示例代码&#xff1a; 9.3 实现并行计算和数据并行处理…

SD-WAN 的真相以及它如何支持企业数字化转型

企业需要灵活、安全的网络解决方案&#xff0c;以支持随时随地工作模式和多云策略&#xff0c;他们正在转向软件定义广域网 (SD-WAN) 技术来实现这一目标。 其操作简单、独立于运营商的 WAN 连接和改进的安全功能可提供直接云访问&#xff0c;并为安全访问服务边缘 (SASE) 策略…

字典树、并查集适用于算法竞赛

字典树 题目&#xff1a;835. Trie字符串统计 - AcWing题库 又称单词查找树&#xff0c;Trie树&#xff0c;是一种树形结构&#xff0c;是一种哈希树的变种。典型应用是用于统计&#xff0c;排序和保存大量的字符串&#xff08;但不仅限于字符串&#xff09;&#xff0c;所以…

C++初学者指南-6.函数对象--函数对象

C初学者指南-6.函数对象–函数对象 文章目录 C初学者指南-6.函数对象--函数对象函数对象示例&#xff1a;区间查询区间内的查找区间划分(分组) 指南标准库函数对象比较算术运算 函数对象 提供至少一个成员函数重载 operator() 的对象 class Multiplier {int m_; public:// cons…

还在用if校验参数?SpringBoot使用validation优雅实现参数校验

&#x1f469;&#x1f3fd;‍&#x1f4bb;个人主页&#xff1a;阿木木AEcru (更多精彩内容可进入主页观看) &#x1f525; 系列专栏&#xff1a;《Docker容器化部署系列》 《Java每日面筋》 &#x1f4b9;每一次技术突破&#xff0c;都是对自我能力的挑战和超越。 目录 一、前…

鸿蒙APP架构及开发入门

1.鸿蒙系统 1.1 什么是鸿蒙 鸿蒙是一款面向万物互联时代的、全新的分布式操作系统。 在传统的单设备系统能力基础上&#xff0c;鸿蒙提出了基于同一套系统能力、适配多种终端形态的分布式理念&#xff0c;能够支持手机、平板、智能穿戴、智慧屏、车机、PC、智能音箱、耳机、…