当前位置: 首页 > news >正文

深入理解可变参数

1.C语言方式

目录

1.C语言方式

1.1.宏介绍

1.2.原理详解

1.3.宏的可变参数

1.4.案例分析

1.5.其他实例

2.C++之std::initializer_list

2.1.简介

2.2.原理详解

2.3.案例分析

3.C++之可变参数模版

3.1.简介

3.2.可变参数个数

3.3.递归包展开

3.4.逗号表达式展开

3.5.Lambda 捕获

3.6.转发参数包

4.总结


1.1.宏介绍

C语言中的可变参数是指函数可以接受可变数量的参数。这些参数的数量在编译时是未知的。在这些可变参数中的参数类型可以相同,也可以不同;可变参数的每个参数并没有实际的名称与之相对应,用起来是很灵活;在头文件stdarg.h中,涉及到的宏有:
va_list :   是指向参数的指针 ,通过指针运算来调整访问的对象
va_start :获取可变参数列表的第一个参数的地址
va_arg : 获取可变参数的当前参数,返回指定类型并将指针指向下一参数
va_end : 清空va_list可变参数列表

1.2.原理详解

函数的参数是存放在栈中,地址是连续的,所以可以通过相对位置去访问,这也是可变参数的访问方式;变长参数的实现需要依赖于C语言默认的cdecl调用惯例的自右向左压栈传递方式;可变参数是由1.1介绍的几个宏来实现,但是由于硬件平台的不同,编译器的不同,宏的定义也不相同,下面是AMD CPU x64平台下的定义:

typedef char* va_list;

va_list的定义

//[1]
#ifdef __cplusplus
#define _ADDRESSOF(v) (&reinterpret_cast<const char &>(v))
#else
#define _ADDRESSOF(v) (&(v))
#endif//[2]
#define va_start _crt_va_start
#define va_arg   _crt_va_arg
#define va_end   _crt_va_end
#define va_copy(destination, source) ((destination) = (source))//[3]
#define _PTRSIZEOF(n) ((sizeof(n) + sizeof(void*) - 1) & ~(sizeof(void*) - 1))//系统内存对齐
#define _ISSTRUCT(t)  ((sizeof(t) > sizeof(void*)) || (sizeof(t) & (sizeof(t) - 1)) != 0)
#define _crt_va_start(v,l)	((v) = (va_list)_ADDRESSOF(l) + _PTRSIZEOF(l))
#define _crt_va_arg(v,t)	_ISSTRUCT(t) ?						\(**(t**)(((v) += sizeof(void*)) - sizeof(void*))) :	\( *(t *)(((v) += sizeof(void*)) - sizeof(void*)))
#define _crt_va_end(v)		((v) = (va_list)0)
#define _crt_va_copy(d,s)	((d) = (s))

从上面的源码可以看出:
1) va_list  v; 定义一个指向char类型的指针v。
2) va_start(v,l) ;执行 v = (va_list)&l + _PTRSIZEOF(l) ,v指向参数 l 之后的那个参数的地址,即 v指向第一个可变参数在堆栈的地址。
3) va_arg(v,t) , ( (t )((v += _PTRSIZEOF(t)) - _PTRSIZEOF(t)) ) 取出当前v指针所指的值,并使 v 指向下一个参数。 v+=sizeof(t类型) ,让v指向下一个参数的地址。然后返回 v - sizeof(t类型) 的t类型指针,这正是第一个可变参数在堆栈里的地址。然后 用取得这个地址的内容。
va_end(v) ; 清空 va_list v。

1.3.宏的可变参数

标准C/C++语言宏定义的参数允许用三个小数点 ... 表示这里是可变参数,在宏替换的时候,用 __VA_ARGS__ 表示 ... 位置的所有的参数,例如:

#define example1(...) printf(__VA_ARGS__)
#define example2(fmt, ...) printf(fmt, __VA_ARGS__)

很多编译器扩展了可变参数的宏替换,参数后面带三个小数点,这样的写法更容易记忆,宏定义的参数后面可以带三个小数点,表示这里是可变参数,宏替换的时候,直接写这个参数就表示这个位置是所有的可变参数了。例如:

#define example1(fmt...) printf(fmt)
#define example2(fmt, args...) printf(fmt, args)

1.4.案例分析

#include <iostream>
#include <stdarg.h>void printValues(const char* format, ...) {va_list args;  // 定义一个va_list类型的变量va_start(args, format);  // 初始化argsfor (const char* arg = format; *arg != '\0'; ++arg) {if (*arg == '%') {++arg;switch (*arg) {case 'd':  // 对于整数std::cout << va_arg(args, int);break;case 's':  // 对于字符串std::cout << va_arg(args, char*);break;default:std::cout << "Invalid format specifier: " << *arg;}}else {std::cout << *arg;}}va_end(args);  // 清理va_list变量
}int main() {printValues("say self info: %s, age %d\n", "xiao", 45);  //输出: say self info xiao, age 45return 0;
}

printValues函数调用的时候展开为:

void printValues(const char* format, const char* param1, int param2)

从上面的代码来分析一下这个示例:在windows中,栈由高地址往低地址生长,调用printValues函数时,其参数入栈情况如下:

当调用va_start(args, format)时:args指针指向情况对应下图:

        当调用va_arg(args, ...)时,它必须返回一个由va_list所指向的恰当的类型的数值,同时递增args,使它指向参数列表中的一个参数(即递增的大小等于与va_arg宏所返回的数值具有相同类型的对象的长度)。因为类型转换的结果不能作为赋值运算的目标,所以va_arg宏首先使用sizeof来确定需要递增的大小,然后把它直接加到va_list上,这样得到的指针再被转换为要求的类型。

        在上面的示例中,我们定义了一个名为printValues的函数,它接受一个格式字符串和一个可变数量的参数。我们使用va_list、va_start、va_arg和va_end这些宏来处理可变参数。在格式字符串中,我们使用%来指定参数的类型,例如%d表示整数,%s表示字符串。然后,我们使用va_arg宏来获取相应的参数值。最后,我们使用va_end宏来清理va_list变量。

1.5.其他实例

1) printf实现

#include <stdarg.h>int printf(char *format, ...)
{va_list ap;int n;va_start(ap, format);n = vprintf(format, ap);va_end(ap);return n;    
}

2)定制错误打印函数error

#include  <stdio.h>
#include  <stdarg.h>void error(char *format, ...)
{va_list ap;va_start(ap, format);fprintf(stderr, "Error: ");vfprintf(stderr, format, ap);va_end(ap);fprintf(stderr, "\n");return;    
}

2.C++之std::initializer_list

        在C++中我们一般用()和=初始化参数或对象,还可以用{}来初始化参数或对象,比如数组的初始化int m[] = {1,4,5},除了数组,在STL里面很多标准的容器和自定义类型都用{} 进行初始化。

        自C++11标准开始就引入了列表初始化的概念,即支持使用{}对变量或对象进行初始化,且与传统的变量初始化的规则一样,也分为拷贝初始化和直接初始化两种方式。

2.1.简介

std::initializer_list<T> 类型对象是一个访问 const T 类型对象数组的轻量代理对象。
std::initializer_list 对象在这些时候自动构造:
1)用花括号初始化器列表列表初始化一个对象,其中对应构造函数接受一个 std::initializer_list 参数,如std::vector的构造函数  vector(initializer_list<_Ty> _Ilist, const _Alloc& _Al = _Alloc())
2)以花括号初始化器列表为赋值的右运算数,或函数调用参数,而对应的赋值运算符/函数接受 std::initializer_list 参数
3)绑定花括号初始化器列表到 auto ,包括在范围 for 循环中

initializer_list 可由一对指针或指针与其长度实现。复制一个 std::initializer_list 不会复制其底层对象。

注意:

a、底层数组不保证在原始 initializer_list 对象的生存期结束后继续存在。 std::initializer_list 的存储是未指定的(即它可以是自动、临时或静态只读内存,依赖场合)。

b、底层数组是 const T[N] 类型的临时数组,其中每个元素都从原始初始化器列表的对应元素复制初始化(除非窄化转换非法)。底层数组的生存期与任何其他临时对象相同,除了从数组初始化 initializer_list 对象会延长数组的生存期,恰如绑定引用到临时量(有例外,例如对于初始化非静态类成员)。底层数组可以分配在只读内存。

c、若声明了 std::initializer_list 的显式或偏特化则程序为谬构。

2.2.原理详解

源码面前无秘密,直接上源码:

template <class _Elem>
class initializer_list {
public:using value_type      = _Elem;using reference       = const _Elem&;using const_reference = const _Elem&;using size_type       = size_t;using iterator       = const _Elem*;using const_iterator = const _Elem*;constexpr initializer_list() noexcept : _First(nullptr), _Last(nullptr) {}  //1constexpr initializer_list(const _Elem* _First_arg, const _Elem* _Last_arg) noexcept: _First(_First_arg), _Last(_Last_arg) {}                               //2_NODISCARD constexpr const _Elem* begin() const noexcept {return _First;}_NODISCARD constexpr const _Elem* end() const noexcept {return _Last;}_NODISCARD constexpr size_t size() const noexcept {return static_cast<size_t>(_Last - _First);}private:const _Elem* _First;const _Elem* _Last;
};// FUNCTION TEMPLATE begin
template <class _Elem>
_NODISCARD constexpr const _Elem* begin(initializer_list<_Elem> _Ilist) noexcept {return _Ilist.begin();
}// FUNCTION TEMPLATE end
template <class _Elem>
_NODISCARD constexpr const _Elem* end(initializer_list<_Elem> _Ilist) noexcept {return _Ilist.end();
}

        从上面的STL的std::initializer_list源码来看,std::initializer_list是一个模版类,定义了指向该类对象首端、尾端的迭代器(即常量对象指针const T*),实际上就是对{}表达式内容的简单封装,当使用{}时,就会调用 initializer_list(const _Elem* _First_arg, const _Elem* _Last_arg) 构造出std::initializer_list。

        当得到了一个std::initializer_list对象后,再来寻找标准容器中以std::initializer_list为形参的构造函数,并调用该构造函数对容器进行初始化

2.3.案例分析

示例1:

class IMessageField1 {};//1
void  addMessageField(std::initializer_list<IMessageField1*> t)
{std::vector<IMessageField1*>  pTest(t);
}#if  0
//2
void  addMessageField(std::vector<IMessageField1*> t)
{std::vector<IMessageField1*>  pTest(t);
}
#endifvoid  main()
{//[1]std::unique_ptr<IMessageField1> a(new IMessageField1);std::unique_ptr<IMessageField1> b(new IMessageField1);std::unique_ptr<IMessageField1> c(new IMessageField1);std::unique_ptr<IMessageField1> d(new IMessageField1);std::unique_ptr<IMessageField1> e(new IMessageField1);addMessageField({ a.get(), b.get(), c.get(), d.get(), e.get() });
}

   上面代码1和2的方式都可以实现功能,2的方式实际上也是先临时生成一个std::initializer_list,再调用std::vector的构造函数临时生成一个std::vector,最后再用刚生成的std::vector初始化pTest,相比1的方式,多了几重复制,效率比较低,一般采用1的方式实现功能。

示例2:

#include <iostream>
#include <vector>
#include <initializer_list>template <class T>
struct S {std::vector<T> v;S(std::initializer_list<T> l) : v(l) {std::cout << "constructed with a " << l.size() << "-element list\n";}void append(std::initializer_list<T> l) {v.insert(v.end(), l.begin(), l.end());}std::pair<const T*, std::size_t> c_arr() const {return {&v[0], v.size()};  // 在 return 语句中复制列表初始化// 这不使用 std::initializer_list}
};template <typename T>
void templated_fn(T) {}int main()
{int a1[] = { 1,2,3,4,5,6 }; //数组拷贝初始化int a2[]{ 5,6,7,8,9,0 };   //数组直接初始化S<int> s = {1, 2, 3, 4, 5}; // 复制初始化s.append({6, 7, 8});      // 函数调用中的列表初始化std::cout << "The vector size is now " << s.c_arr().second << " ints:\n";for (auto n : s.v)std::cout << n << ' ';std::cout << '\n';std::cout << "Range-for over brace-init-list: \n";for (int x : {-1, -2, -3}) // auto 的规则令此带范围 for 工作std::cout << x << ' ';std::cout << '\n';auto al = {10, 11, 12};   // auto 的特殊规则std::cout << "The list bound to auto has size() = " << al.size() << '\n';//    templated_fn({1, 2, 3}); // 编译错误!“ {1, 2, 3} ”不是表达式,// 它无类型,故 T 无法推导templated_fn<std::initializer_list<int>>({1, 2, 3}); // OKtemplated_fn<std::vector<int>>({1, 2, 3});           // 也 OK
}

输出:

constructed with a 5-element list
The vector size is now 8 ints:
1 2 3 4 5 6 7 8
Range-for over brace-init-list: 
-1 -2 -3 
The list bound to auto has size() = 3

示例3:

struct MyTest{explicit  X(int a, int b) :a(a), b(b) { std::cout << "MyTest(int a,int b)\n"; }int a{};int b{};
};int main() {MyTest x{ 1,2 }; //OKMyTest x2( 1,2 ); //OKMyTest x3 = { 1,2 }; //Error
}

MyTest x3 ={1,2}; 参考复制初始化的规则:复制列表初始化(考虑 explicit 和非 explicit 构造函数,但只能调用非 explicit 构造函数)

3.C++之可变参数模版

3.1.简介

一个可变参数模板就是一个可以接受可变参数的模版函数或模板类;参数的类型是一种模板,是可经推导的,可以是任意存在的类型(系统类型或自定义类型);参数数目可变的,可以包括零个、一个或多个;可变数目的参数被称为参数包(parameter packet)。存在两种参数包:模板参数包(template parameter packet),表示零个或多个模板参数;函数参数包function parameterpacket),表示零个或多个函数参数。如:

template<typename... Arguments> class vtclass;vtclass< > vtinstance1;
vtclass<int> vtinstance2;
vtclass<float, bool> vtinstance3;
vtclass<long, std::vector<int>, std::string> vtinstance4;

3.2.可变参数个数

利用sizeof...()计算可变参数的大小,如:

template<class... Types>
struct count
{static const std::size_t value = sizeof...(Types);
};

3.3.递归包展开

C++的包展开是通过 args... 的形式,后面... 就意味着展开包,需要两个函数:递归终止函数 和 递归函数过程就是参数包在展开的过程中递归调用自己,每调用一次参数包中的参数就会少一个,直到所有的参数都展开为止,当没有参数时,则调用递归终止函数终止递归过程。如下:

#include <iostream>using namespace std;void print() {cout << endl;
}template <typename T> 
void print(const T& t) {  //边界条件cout << t << endl;
}template <typename First, typename... Rest> 
void print(const First& first, const Rest&... rest) {cout << first << ", ";print(rest...); //打印剩余参数,注意省略号必须有
}int main()
{print(); // calls first overload, outputting only a newlineprint(1); // calls second overload// these call the third overload, the variadic template,// which uses recursion as needed.print(10, 20);   //输出: 10, 20print(100, 200, 300); //输出:100, 200, 300print("first", 2, "third", 3.14159); //输出: first, 2, third, 3.14159
}

3.4.逗号表达式展开

逗号表达式是会从左到右依次计算各个表达式,并将最后一个表达式的值作为返回值返回;我们将最后一个表达式设为整型值,所以最后返回的是一个整型;将处理参数个数的动作封装成一个函数,将该函数作为逗号表达式的第一个表达式;…代表参数包,列表展开。如:

template <class T>
void printArg(T t) {cout << t << endl;
}//展开参数包
template <class ...Args>
void expand(Args... args) {int arr[] = { (printArg(args), 0)... };
}
int main()
{expand(1);expand(1, 'A');expand(1, "hello", 3);return 0;
}

函数执行expand(1, "hello", 3);的时候,调用expand,数组arr初始化会展开args参数,变化为:

int arr[] = {(printArg(1), 0), (printArg("hello"), 0), (printArg(3), 0)};

根据逗号表达式的规则,arr[] 还是 {0,0,0};

另外,还可以利用std::initializer_list 可以接收任意长度的初始化列表来展开包,如:

template<class F, class... Args>
void expand(const F& f, Args&&...args) {std::initializer_list<int>{(f(std::forward< Args>(args)), 0)...};
}int main()
{expand([](int i) { cout << i << endl; }, 23, 44, 2423);return 0;
}

3.5.Lambda 捕获

包展开可以在 lambda 表达式的捕获子句中出现:

template<class... Args>
void f(Args... args)
{auto lm = [&, args...] { return g(args...); };lm();
}

3.6.转发参数包

在C++11标准下,我们可以组合使用可变参数模板与std::forword机制来编写函数,实现将其实参不变地传递给其他函数,关于std::forward的详解讲解,可参考我的博客:C++之std::forward_c++ std::forward-CSDN博客

借助std::forward<Args>(args)... 就可以实现参数的完美转发了,如STL中map的插入函数emplace下:

template <class... _Valty>
iterator emplace(_Valty&&... _Val)
{return _Mybase::emplace(_STD forward<_Valty>(_Val)...).first;
}

4.总结

纸上得来终觉浅,绝知此事要躬行。

参考

形参包 (C++11 起) - cppreference.com

相关文章:

深入理解可变参数

1.C语言方式 目录 1.C语言方式 1.1.宏介绍 1.2.原理详解 1.3.宏的可变参数 1.4.案例分析 1.5.其他实例 2.C之std::initializer_list 2.1.简介 2.2.原理详解 2.3.案例分析 3.C之可变参数模版 3.1.简介 3.2.可变参数个数 3.3.递归包展开 3.4.逗号表达式展开 3.5…...

Centos7.9和Debian12部署Minio详细流程

一、安装minio Centos wget https://dl.min.io/server/minio/release/linux-amd64/archive/minio-20230227181045.0.0.x86_64.rpm -O minio.rpm sudo dnf install minio.rpmDebian wget https://dl.min.io/server/minio/release/linux-amd64/archive/minio_20230227181045.0…...

软件测试|教你如何使用UPDATE修改数据

简介 在SQL&#xff08;Structured Query Language&#xff09;中&#xff0c;UPDATE语句用于修改数据库表中的数据。通过UPDATE语句&#xff0c;我们可以更新表中的特定记录或多条记录&#xff0c;从而实现数据的修改和更新。本文将详细介绍SQL UPDATE语句的语法、用法以及一…...

新闻稿发布:媒体重要还是价格重要

在当今信息爆炸的数字时代&#xff0c;企业推广与品牌塑造不可或缺的一环就是新闻稿发布。新闻稿是一种通过媒体渠道传递企业信息、宣传品牌、事件或产品新闻的文本形式。发布新闻稿的过程旨在将企业的声音传递给更广泛的受众&#xff0c;借助媒体平台实现品牌故事的广泛传播。…...

prometheus grafana mysql监控配置使用

文章目录 前传bitnami/mysqld-exporter:0.15.1镜像出现了问题.my.cnf可以用这个"prom/mysqld-exporter:v0.15.0"镜像重要的事情mysql监控效果外传 前传 prometheus grafana的安装使用&#xff1a;https://nanxiang.blog.csdn.net/article/details/135384541 本文说…...

鸿蒙HarmonyOS-带笔锋手写板(三)

笔者用ArkTS 写了一个简单的带笔锋的手写板应用&#xff0c;并且可以将手写内容保存为图片。 一、效果图 手写效果如下&#xff08;在鸿蒙手机模拟器上运行&#xff0c;手写时反应可能会有点慢&#xff09; 二、实现方法 参考文章&#xff1a; 支持笔锋效果的手写签字控件_a…...

React 实现 Step组件

简介 本文将会实现步骤条组件功能。步骤条在以下几个方面改进。 1、将url与Step组件绑定&#xff0c;做到浏览器刷新&#xff0c;不会重定向到Step 1 2、通过LocalStorage 存储之前的Step&#xff0c;做到不丢失数据。 实现 Step.jsx (组件) import {useEffect, useState} fro…...

【OJ】单链表刷题

力扣刷题 1. 反转链表&#xff08;206&#xff09;1.1 题目描述1.2 题目分析1.2.1 头插法1.2.2 箭头反转 1.3 题目代码1.3.1 头插入1.3.2 箭头反转 2.合并两个有序链表&#xff08;21&#xff09;2.1 题目描述2.2 题目分析2.3 题目代码 1. 反转链表&#xff08;206&#xff09;…...

【UML建模】部署图(Deployment Diagram)

1.概述 部署图是一种结构图&#xff0c;用于描述软件系统在不同计算机硬件或设备上的部署和配置情况&#xff0c;以图形化的方式展示系统中组件、节点和连接之间的物理部署关系。 通过部署图&#xff0c;可以清晰地了解系统的物理结构和部署方式&#xff0c;包括系统组件和节…...

三、计算机理论-关系数据库-数据模型与数据视图;关系代数、关系演算及关系模型

数据模型 具体事物-抽象化-->概念模型-数据化-->数据模型 概念模型也称信息模型&#xff0c;在数据库设计阶段&#xff0c;由设计者按照用户的观点对数据和信息建模&#xff0c;实现对现实世界的概念抽象&#xff1b; 数据模型主要包括网状模型、层次模型、关系模型、面向…...

解读 $mash 通证 “Fair Launch” 规则(Staking 玩法解读篇)

Solmash 是 Solana 生态中由社区主导的铭文资产 LaunchPad 平台&#xff0c;该平台旨在为 Solana 原生铭文项目&#xff0c;以及通过其合作伙伴 SoBit 跨链桥桥接到 Solana 的 Bitcoin 生态铭文项目提供更广泛的启动机会。...

【C语言】关于C11的一些新特性

相比于VC 6.0使用的ANSI C标准&#xff0c;VS2022使用的C11标准与上一代有很多不同&#xff0c;相比之前的 C 标准&#xff08;如 C89/C90 和 C99&#xff09;&#xff0c;引入了一些新的功能、特性和改进。以下是 C11 标准相对于之前版本的一些主要变化和新增内容&#xff1a;…...

牛的速记(c++题解)

题目描述 奶牛们误解了速记的含义。他们是这样理解的&#xff1a; 给出一个少于255个字母的小写字母串。 找到一个出现次数最多的字母&#xff0c;将该字母从字母串中统统删去&#xff0c;如果出现次数最多的字母不止一个&#xff0c;就删去在字母表中靠前的一个&#xff0c;即…...

使用ffmpeg+flv.js + websokect播放rtsp格式视频流

对于rtsp的视频流网上有很多种的解决方案&#xff0c;但是大的趋势还是利用ffmpeg的工具进行rtsp的视频解析进行一个推流&#xff0c;我最终选择bilibili开源的flv.js&#xff0c;代码十分的简单全部都在底层封装好了。实现的方式也比较容易理解&#xff0c;ffmpeg进行rtsp的视…...

OAI openair3代码结构整理

openair3代码框架结构 OAI&#xff08;OpenAirInterface&#xff09;是一个开源的5G网络软件平台&#xff0c;用于研究和开发5G网络技术。OpenAir3是OAI项目中的一个子项目&#xff0c;专注于5G核心网络的功能实现。 一、OpenAir3的代码主要包括以下几个部分&#xff1a; NAS…...

Kubernets(K8S)启动和运行 01-01 Kubernetes简介

Kubernets(K8S)启动和运行 01-01 Kubernetes简介 Kubernetes is an open source orchestrator for deploying containerized applications. It was originally developed by Google, inspired by a decade of experience deploying scalable, reliable systems in containers …...

PHP特性知识点扫盲 - 下篇

概述 在实际的生产环境中遇到了实际需要解决的问题&#xff0c;需要把服务部署的方式梳理出来&#xff0c;在同一个服务器中部署多个PHP环境&#xff0c;架构图如下&#xff1a; 架构方案 在工作实践中遇到的很多问题的普遍性都是相通的&#xff0c;公司运行的可新项目都是版…...

HarmonyOS应用开发之DevEco Studio安装与初次使用

1、DevEco Studio介绍 DevEco Studio是基于IntelliJ IDEA Community开源版本打造&#xff0c;面向华为终端全场景多设备的一站式集成开发环境&#xff08;IDE&#xff09;&#xff0c;为开发者提供工程模板创建、开发、编译、调试、发布等E2E的HarmonyOS应用/服务的开发工具。…...

记录第一次在GitHub上面提交Issue

第一次在GitHub上面提交Issue&#xff0c;记录一下。 对着源码调了好久才发现&#xff0c;问题并不在程序而在模型&#xff08;虽然只是一个很小的问题&#xff0c;但是能够解决问题&#xff0c;并且做出了自己的一点小小贡献&#xff0c;还是很开心。嘻嘻&#xff0c;发博客记…...

【数据库设计和SQL基础语法】--用户权限管理--数据备份和恢复策略

一、引言 数据备份和恢复是数据库管理中至关重要的任务&#xff0c;对于确保数据安全性和业务连续性具有重大的意义。以下是一些关键的重要性方面&#xff1a; 防止数据丢失&#xff1a; 数据备份是防止因硬件故障、人为错误、恶意攻击或其他意外事件导致数据丢失的主要手段。…...

观成科技:隐蔽隧道工具Ligolo-ng加密流量分析

1.工具介绍 Ligolo-ng是一款由go编写的高效隧道工具&#xff0c;该工具基于TUN接口实现其功能&#xff0c;利用反向TCP/TLS连接建立一条隐蔽的通信信道&#xff0c;支持使用Let’s Encrypt自动生成证书。Ligolo-ng的通信隐蔽性体现在其支持多种连接方式&#xff0c;适应复杂网…...

springboot 百货中心供应链管理系统小程序

一、前言 随着我国经济迅速发展&#xff0c;人们对手机的需求越来越大&#xff0c;各种手机软件也都在被广泛应用&#xff0c;但是对于手机进行数据信息管理&#xff0c;对于手机的各种软件也是备受用户的喜爱&#xff0c;百货中心供应链管理系统被用户普遍使用&#xff0c;为方…...

Day131 | 灵神 | 回溯算法 | 子集型 子集

Day131 | 灵神 | 回溯算法 | 子集型 子集 78.子集 78. 子集 - 力扣&#xff08;LeetCode&#xff09; 思路&#xff1a; 笔者写过很多次这道题了&#xff0c;不想写题解了&#xff0c;大家看灵神讲解吧 回溯算法套路①子集型回溯【基础算法精讲 14】_哔哩哔哩_bilibili 完…...

Neo4j 集群管理:原理、技术与最佳实践深度解析

Neo4j 的集群技术是其企业级高可用性、可扩展性和容错能力的核心。通过深入分析官方文档,本文将系统阐述其集群管理的核心原理、关键技术、实用技巧和行业最佳实践。 Neo4j 的 Causal Clustering 架构提供了一个强大而灵活的基石,用于构建高可用、可扩展且一致的图数据库服务…...

Maven 概述、安装、配置、仓库、私服详解

目录 1、Maven 概述 1.1 Maven 的定义 1.2 Maven 解决的问题 1.3 Maven 的核心特性与优势 2、Maven 安装 2.1 下载 Maven 2.2 安装配置 Maven 2.3 测试安装 2.4 修改 Maven 本地仓库的默认路径 3、Maven 配置 3.1 配置本地仓库 3.2 配置 JDK 3.3 IDEA 配置本地 Ma…...

JAVA后端开发——多租户

数据隔离是多租户系统中的核心概念&#xff0c;确保一个租户&#xff08;在这个系统中可能是一个公司或一个独立的客户&#xff09;的数据对其他租户是不可见的。在 RuoYi 框架&#xff08;您当前项目所使用的基础框架&#xff09;中&#xff0c;这通常是通过在数据表中增加一个…...

Mysql中select查询语句的执行过程

目录 1、介绍 1.1、组件介绍 1.2、Sql执行顺序 2、执行流程 2.1. 连接与认证 2.2. 查询缓存 2.3. 语法解析&#xff08;Parser&#xff09; 2.4、执行sql 1. 预处理&#xff08;Preprocessor&#xff09; 2. 查询优化器&#xff08;Optimizer&#xff09; 3. 执行器…...

[免费]微信小程序问卷调查系统(SpringBoot后端+Vue管理端)【论文+源码+SQL脚本】

大家好&#xff0c;我是java1234_小锋老师&#xff0c;看到一个不错的微信小程序问卷调查系统(SpringBoot后端Vue管理端)【论文源码SQL脚本】&#xff0c;分享下哈。 项目视频演示 【免费】微信小程序问卷调查系统(SpringBoot后端Vue管理端) Java毕业设计_哔哩哔哩_bilibili 项…...

淘宝扭蛋机小程序系统开发:打造互动性强的购物平台

淘宝扭蛋机小程序系统的开发&#xff0c;旨在打造一个互动性强的购物平台&#xff0c;让用户在购物的同时&#xff0c;能够享受到更多的乐趣和惊喜。 淘宝扭蛋机小程序系统拥有丰富的互动功能。用户可以通过虚拟摇杆操作扭蛋机&#xff0c;实现旋转、抽拉等动作&#xff0c;增…...

Rust 开发环境搭建

环境搭建 1、开发工具RustRover 或者vs code 2、Cygwin64 安装 https://cygwin.com/install.html 在工具终端执行&#xff1a; rustup toolchain install stable-x86_64-pc-windows-gnu rustup default stable-x86_64-pc-windows-gnu ​ 2、Hello World fn main() { println…...