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

C#与C/C++交互(1)——需要了解的基础知识

【前言】

 C#中用于实现调用C/C++的方案是P/Invoke(Platform Invoke),让托管代码可以调用库中的函数。类似的功能,JAVA中叫JNI,Python中叫Ctypes。

常见的代码用法如下:

[DllImport("Test.dll", EntryPoint = "Load", CallingConvention = CallingConvention.Cdecl,SetLastError = true)]
public static extern int Load([MarshalAs(UnmanagedType.LPWStr)] string jarg1, IntPtr jarg2, int jarg3, out int jarg4);

调用过程为

  • 查找dll,例子中为Test.dll'
  • 将该dll加载到内存中
  • 查找函数在内存中的地址,例子为查找Load函数,并将其参数按照函数的调用约定压栈,例子中调用约定为Cdecl
  • 将控制权转移给非托管函数

 【代码含义详解】

Test.dll其表示要加载哪个动态库,EntryPoint显式指定函数入口点,如果没有EntryPoint,那么会把方法名作为入口点,EntryPoint和方法名不一定相同。

SetLastError = true

非托管代码中有报错时,很少像托管代码中抛出异常,将SetLastError设置为true,可以按照C#标准的方式抛出异常,报告错误。

stdcall和cdecl的区别

告诉编译器参数的传递约定,参数的传递约定是指参数的传递顺序(从左到右还是从右到左)和由谁来恢复堆栈指针(调用者或者是被调用者)

cdecl是 C Declaration 的缩写,表示 C 语言默认的函数调用方法:所有参数从右到左依次入栈,这些参数由调用者清除,称为手动清栈。因为调用者知道传递了多少个参数,因此被调用函数无需要求调用者传递多少参数,调用者传递过多或者过少的参数,甚至完全不同的参数都不会产生编译阶段的错误。

stdcall 是Standard Call的缩写,是C 的标准调用方式:所有参数从右到左依次入栈,如果是调用类成员的话,最后一个入栈的是this指针。这些堆栈中的参数由被调用的 函数在返回后清除,使用的指令是 retn X,X表示参数占用的字节数,CPU在ret之后自动弹出X个字节的堆栈空间,称为自动清栈。因为是被调用者恢复堆栈指针,所以被调用者必须要知道传递进来了多少个参数,也即函数在编译的时候就必须确定参数个数。

C#中默认是stdcall调用的,如果你能很确定函数调用参数数量不会变化,用stdcall和cdecl没区别。

[DllImport("__Internal")]

如果使用了静态库,那么需要使用[DllImport("__Internal")] (注意有两条下画横线)。静态库将在链接阶段将函数入口链接好,在运行时会直接调用库中的函数,而动态库需要在运行时加载,然后查找函数入口,相比而言,静态库降低了P/Invoke的消耗。

ref和out参数

如果对基元数据类型存在引用,使用ref或out参数,而不是指针。另外,仅有一个的话,可以考虑作为返回值。尤其是当结构体作为传入参数时,要注意使用ref参数。

MarshalAsAttribute

该特性用于描述字段、方法或参数的封送处理格式,在不同平台对数据类型的表示方式有区别,在传递前需要一些说明,用MarshalAs说明,其可用于参数、字段、返回值。使用范例如下:

using System;
using System.Text;
using System.Runtime.InteropServices;class Program
{//Applied to a parameter.public void M1([MarshalAs(UnmanagedType.LPWStr)]String msg) {}//Applied to a field within a class.class MsgText {[MarshalAs(UnmanagedType.LPWStr)]public String msg = "Hello World";}//Applied to a return value.
[return: MarshalAs(UnmanagedType.LPWStr)]public String GetMessage(){return "Hello World";}static void Main(string[] args){  }
}decimal _money;   public decimal Money 
{[return: MarshalAs(UnmanagedType.Currency)]get { return this._money; }[param: MarshalAs(UnmanagedType.Currency)]set { this._money = value; }
}

UnmanagedType的类型如下,一般来说用的比较多的是关于字符串的:

  • BStr  长度前缀为双字节的 Unicode 字符串,默认的
  • LPStr  单字节、null为终止符的 ANSI 字符串
  • LPWStr  一个 2 字节、null为终止符的 Unicode 字符串

更多的类型需要参考MSDN

【类型传递】 

Blittable和Non-Blittable

有些数据类型在托管和非托管之间传递时不需要特殊处理,可以直接传递,这些数据类型被称为Blittable类型,否则就是Non-Blittable类型。

在使用P/Invoke时,函数的返回值的结构只能是Blittable类型,其包括int、float、byte、short、IntPtr等,由这些Blittable类型组成的数组,也被视为Blittable类型。注意,bool、char、string是Non-Blittable类型。

类型关系对应表

注意托管代码中,像int这样的基元数据类型不会随处理器改变大小,无论16位、32位还是64位处理器,int始终是32位。而在非托管代码中,内存指针会随处理器而变化,因此对于void*等指针类型要映射位System.IntPtr,其大小将随处理器内存布局而滨化。

StructLayoutAtrribute

有些自定义的类型没有非托管和托管的类型对应关系,需要用StructLayoutAtrribute来定义该类型中的字段的内存布局,以便在托管和非托管代码中能够正确从内存中读取数据。(这里的类型指struct、class)。

使用范例为:[StructLayout(LayoutKind.Explicit, Pack = 4,Size=16, CharSet=CharSet.Ansi)]

 内存布局三种情况:

  • 默认(LayoutKind.Sequential)情况下,CLR对struct的Layout的处理方法与C/C++中默认的处理方式相同,即按照结构中占用空间最大的成员进行对齐(Align)
  • 使用LayoutKind.Explicit的情况下,CLR不对结构体进行任何内存对齐(Align),而且需要我们自己设置FieldOffset
  • 使用LayoutKind.Auto的情况下,CLR会对结构体中的字段顺序进行调整,使实例占有尽可能少的内存,并按照4字节的内存对齐(Align)

StructLayout特性支持三种附加字段:CharSet、Pack、Size

CharSet定义在结构中的字符串成员在结构被传给DLL时的排列方式。可以是Unicode、Ansi或Auto。其中Unicode和Auto表示字符串按照Unicode编码(LPWSTR),Ansi表示按照ANSI编码(LPSTR)

  • Pack用于指定按多少位进行内存对齐,默认是0,表示使用当前平台默认的内存对齐,其值可以是1、2、4、8、16、32、64、128。通过示例,可以明白指定Pack对类型实际占用的内存大小的影响。

size用于表明class或struct的绝对大小,其必须大于所有字段大小总和。

【Marshal 常用API】

marshal:直译为“编排”, 在计算机中特指将数据按某种描述格式编排出来。在C#中,Marshal类的定义为:提供一个方法集合,分配非托管内存,拷贝非托管内存块,转换托管和非托管类型,以及一些和非托管代码交互的杂类方法。其所在命名空间为System.Runtime.InteropServices

Marshal.SizeOf

其作用是获取对象占用的内存大小。

参数为类型对象或类型的实例,计算需要分配多少字节的非托管内存,可用于任何对象实例或运行时类型。sizeof运算符参数为类型对象,计算需要为对象的实例分配多少字节的托管内存。在C#中,sizeof运算符仅适用于编译时已知的类型,而不适用于变量。

Marshal.AllocHGlobal 与Marshal.FreeHGlobal

作用分别是从进程的非托管内存中分配和释放内存,一般配合相互配合使用。

分配内存常用的方法为public static IntPtr AllocHGlobal (int cb),通过使用指定的字节数,从进程的非托管内存中分配内存,返回值是指向分配的内存的第一个字节的地址,这块分配的内存用Marshal.FreeHGlobal释放内存。具体指定多少字节数通常用Marshal.SizeOf计算出来。

(GCHandle.Alloc不会分配内存,其只是从托管内存中拿到托管对象的句柄,以便于从非托管代中访问托管对象,需要用GCHandle.Free释放)

Marshal.PtrToStructure和Marshal.StructureToPtr

前者作用是将指针所指的非托管内存中的数据转为托管对象,将托管对象转为非托管内存并返回非托管内存的指针。

注意由于涉及托管和非托管内存,两者之间的数据是copy的,这里的structure必须要是值类型、结构体或者用的StructLayoutAtrribute修饰的类的实例,否则无法确定在分配在非托管内存中需要多少内存。如果structure包含了IntPtr引用类型,例如接口、没有用layout修饰的类、System.Object等,那么这些引用类型所指的托管对象的引用被赋值了一份到非托管内存中;所有其他引用类型,例如字符串和数组,会被copy。在释放非托管内存前,必须主动调用Marshal.DestroyStructure将非托管内存中的数据清理掉。

public static void StructureToPtr (object structure, IntPtr ptr, bool fDeleteOld);该方法有一个fDeleteOld参数,其意义为:

首次调用该方法时,IntPtr所指向的内存没有包含其他数据,该参数必须为false。如果IntPtr已经指向的内存中有数据,必选为true,此时在将数据copy过去前,会自动调Marshal.DestroyStructure将非托管内存中的数据清理掉。如果不这样可能会导致内存泄露。

使用范例如下:

using System;
using System.Runtime.InteropServices;public struct Point
{public int x;public int y;
}class Example
{static void Main(){// Create a point struct.Point p;p.x = 1;p.y = 1;Console.WriteLine("The value of first point is " + p.x + " and " + p.y + ".");// Initialize unmanged memory to hold the struct.IntPtr pnt = Marshal.AllocHGlobal(Marshal.SizeOf(p));try{// Copy the struct to unmanaged memory.Marshal.StructureToPtr(p, pnt, false);// Create another point.Point anotherP;// Set this Point to the value of the// Point in unmanaged memory.anotherP = (Point)Marshal.PtrToStructure(pnt, typeof(Point));Console.WriteLine("The value of new point is " + anotherP.x + " and " + anotherP.y + ".");}finally{// Free the unmanaged memory.Marshal.FreeHGlobal(pnt);}}
}

字符串相关API

Marshal.PtrToStringAnsi和Marshal.StringToHGlobalAnsi

Marshal.PtrToStringAuto和Marshal.StringToHGlobalAuto

Marshal.PtrToStringUni和Marshal.StringToHGlobalUni

这些相当于将Structure换成了String。

Marshal.Copy

将托管数据中的数据拷贝到指针指向的非托管内存中,或者反过来。

使用范例如下:

using System;
using System.Runtime.InteropServices;class Example
{static void Main(){// Create a managed array.int[] managedArray = { 1, 2, 3, 4 };// Initialize unmanaged memory to hold the array.int size = Marshal.SizeOf(managedArray[0]) * managedArray.Length;IntPtr pnt = Marshal.AllocHGlobal(size);try{// Copy the array to unmanaged memory.Marshal.Copy(managedArray, 0, pnt, managedArray.Length);// Copy the unmanaged array back to another managed array.int[] managedArray2 = new int[managedArray.Length];Marshal.Copy(pnt, managedArray2, 0, managedArray.Length);Console.WriteLine("The array was copied to unmanaged memory and back.");}finally{// Free the unmanaged memory.Marshal.FreeHGlobal(pnt);}}
}

Marshal.AddRef和Marshal.Release

public static int AddRef (IntPtr pUnk);

public static int Release (IntPtr pUnk);

其增加和减少对象的引用计数,返回值是当前引用的数量。

Marshal.GetFunctionPointerForDelegate

public static IntPtr GetFunctionPointerForDelegate (Delegate d);

其作用是将一个委托转为能从非托管代码中调用的函数指针,可以通过UnmanagedFunctionPointerAttribute来设置调用约定。必须手动防止垃圾收集器从托管代码中收集委托。垃圾收集器不跟踪对非托管代码的引用。

【其他简要介绍】

MonoPInvokeCallBack

这个特性只在静态方法上有效,用于让Mono的AOT编译器知道这个方法是从native code调用的,在编译时需要生成一些必要的代码以支持native code调用managed code。在常规的ECMA CIL程序中,这是自动发生的,不需要特别标记任何内容。

UnmanagedFunctionPointerAttribute

控制作为指向或来自非托管代码的非托管函数指针传递的委托签名的封送处理行为

fixed 和 unsafe

有时需要用指针直接访问和操纵内存,C#通过“不安全代码”构造提供这方面的支持。通过将代码区指定为unsafe可以绕过C#的类型检查机制,直接操作内存和地址。使用这个关键字时需要在VS中打开项目属性窗口,勾选“生成”标签页中的“允许不安全代码”,unity的话需要在Project Setting中勾选。

在unsafe中,可以像C++一样使用指针。但是引用类型、泛型类型、内部包括引用类型时不能使用 指针,也即string* str是无效的,Status* status(Status是结构体,其中有一个string字段)也是无效的。值类型(int* char* bool* byte* )的指针,void*指针是有效的。

我们知道给指针赋值时先要获取数据的地址,用&操作符来获取值类型的地址。但当对象在托管内存中时,其可能被垃圾回收或转移位置,为了将数据的地址赋值给指针,需要将数据固定住,有如下方法:

1.用fixed固定:其要求数据属于一个非托管的变量。fixed使得限定的代码块中,赋值的数据不会再移动,使用范例如下,bytes被固定不动:

unsafe
{byte[] bytes = { 1, 2, 3 };fixed (byte* pointerToFirst = bytes)//用bytes取代冗长的&bytes[0]{Console.WriteLine($"The address of the first array element: {(long)pointerToFirst:X}.");Console.WriteLine($"The value of the first array element: {*pointerToFirst}.");}
}
// Output is similar to:
// The address of the first array element: 2173F80B5C8.
// The value of the first array element: 1.unsafe
{int[] numbers = { 10, 20, 30 };fixed (int* toFirst = &numbers[0], toLast = &numbers[^1]){Console.WriteLine(toLast - toFirst);  // output: 2}
}

由于垃圾回收器不能压缩已经固定的对象,fixed语句可能导致内存碎片化。为了解决该问题,最好的做法是在执行前期就固定好代码块,而且宁可固定较少的几个大块,也不要固定许多小块。 

 2.分配在栈上:栈上的数据不会被垃圾回收,也不会被终结器清理,可以在栈上分配非托管类型的数组。例如:

int length = 3;
Span<int> numbers = stackalloc int[length];
for (var i = 0; i < length; i++)
{numbers[i] = i;
}unsafe
{int length = 3;int* numbers = stackalloc int[length];for (var i = 0; i < length; i++){numbers[i] = i;}
}

在栈上分配就没有内存碎片化的问题,但只能在栈上分配很小的内存,以防止栈空间被耗尽而导致程序崩溃。一般情况下,程序的栈空间不到1MB。

SafeHandle

当涉及到一些资源的需要手动清理释放,但要求每次都记得手动释放是不现实的,类似C#中非托管资源要继承IDispose,跨平台时可以继承System.Runtime.InteropServices.SafeHandle。

【参考】 

MSDN

《C#本质论8.0》

相关文章:

C#与C/C++交互(1)——需要了解的基础知识

【前言】 C#中用于实现调用C/C的方案是P/Invoke&#xff08;Platform Invoke&#xff09;&#xff0c;让托管代码可以调用库中的函数。类似的功能&#xff0c;JAVA中叫JNI&#xff0c;Python中叫Ctypes。 常见的代码用法如下&#xff1a; [DllImport("Test.dll", E…...

LeetCode笔记:Weekly Contest 356

LeetCode笔记&#xff1a;Weekly Contest 356 1. 题目一 1. 解题思路2. 代码实现 2. 题目二 1. 解题思路2. 代码实现 3. 题目三 1. 解题思路2. 代码实现 4. 题目四 1. 解题思路2. 代码实现 比赛链接&#xff1a;https://leetcode.com/contest/weekly-contest-356/ 1. 题目一…...

2 Python的基础语法

概述 在上一节的内容中&#xff0c;我们介绍了Python的诞生、发展历程、特色、缺点和应用领域。从本节开始&#xff0c;我们将正式学习Python。Python是一门简洁和优雅的语言&#xff0c;有自己特殊的一些语法规则。因此&#xff0c;在介绍Python编程的有关知识之前&#xff0c…...

抖音seo矩阵系统源代码开发搭建技术分享

抖音SEO矩阵系统是一个较为复杂的系统&#xff0c;其开发和搭建需要掌握一定的技术。以下是一些技术分享&#xff1a; 技术分享 抖音SEO矩阵系统的源代码可以使用JAVA、Python、PHP等多种语言进行开发。其中&#xff0c;JAVA语言的应用较为广泛&#xff0c;因为JAVA语言有良好…...

python#django数据库一对一/一对多/多对多

一对一OneToOneField 用户和用户信息 搭建 # 一对一 class TestUser(models.Model): usernamemodels.CharField(max_length32) password models.CharField(max_length32) class TestInfo(models.Model): mick_namemodels.CharField(max_length32) usermode…...

记RT-Thread rt_timer_start函数的问题

我使用的RT-Thread版本为4.0.3。 我看了5.0.1的代码&#xff0c;此问已经被修复。 在4.0.3版本中的rt_timer_start函数源码如下&#xff1a; rt_err_t rt_timer_start(rt_timer_t timer) {unsigned int row_lvl;rt_list_t *timer_list;register rt_base_t level;rt_list_t *r…...

C++初阶——拷贝构造和运算符重载(const成员)

目录 1. 拷贝构造函数 1.2 拷贝构造函数特征&#xff1a; 2. 默认拷贝构造函数 2.1 未显式定义&#xff0c;编译器会生成默认的拷贝构造函数。 默认的拷贝构造函数对象按内存存储按字节序完成拷贝&#xff0c;这种拷贝叫做浅拷贝&#xff0c;或者值拷贝 3. 运算符重载 3.1…...

go练习 day01

DTO: note_dto.go package dtoimport "king/model"type NoteAddDTO struct {ID uintTitle string json:"title" form:"title" binding:"required" message:"标题不能为空"Content string json:"conten…...

C# Blazor 学习笔记(0.1):如何开始Blazor和vs基本设置

文章目录 前言资源推荐环境如何开始Blazor个人推荐设置注释快捷键热重载设置 前言 Blazor简单来说就是微软提供的.NET 前端框架。使用 WebAssembly的“云浏览器”&#xff0c;集成了Vue,React,Angular等知名前端框架的特点。 资源推荐 微软官方文档 Blazor入门基础视频合集 …...

原码的乘法运算 补码乘法运算

补码乘法 对比...

找不到d3dx9_43.dll丢失怎么解决(分享几种解决方法)

为什么我们打开电脑软件或许游戏时候&#xff0c;电脑会报错出现d3dx9_43.dll丢失&#xff0c;或许找不到d3dx9_43.dll等等的提示。下面来详细介绍一下d3dx9_43.dll详细解决方法跟d3dx9_43.dll是什么。 如果你的系统中没有安装或安装不完整的d3dx9_43.dll运行时&#xff0c;应…...

篇四:建造者模式:逐步构造复杂对象

篇四&#xff1a;“建造者模式&#xff1a;逐步构造复杂对象” 设计模式是软件开发中的重要组成部分&#xff0c;建造者模式是创建型设计模式中的一种。建造者模式旨在逐步构造复杂对象&#xff0c;将对象的构造与其表示分离&#xff0c;从而使得同样的构建过程可以创建不同的…...

vs导出和导入动态库和静态库

1. 动态库和导出和导入 1.1 动态库的导出 1. 创建新项目 新建新项目&#xff0c;选择动态链接库&#xff08;DLL&#xff09;。 填写项目名称&#xff0c;并选择项目保存的路径&#xff0c;然后点击创建。 创建完成后&#xff0c;会自动生成如下所示文件&#xff0c;可以根据…...

30 使用easyExcel依赖生成Excel

30.1 导入依赖 <dependency><groupId>com.alibaba</groupId><artifactId>easyexcel</artifactId><version>2.2.6</version></dependency><dependency><groupId>org.projectlombok</groupId><artifactId&…...

排序进行曲-v2.0

文章目录 小程一言直接插入排序步骤举例复杂度分析应用场景实际举例代码实现 希尔排序步骤举例复杂度分析应用场景实际举例代码实现 堆排序步骤举例复杂度分析应用场景实际举例代码实现 小程一言 这篇文章是在排序进行曲1.0之后的续讲&#xff0c; 由于在上一篇讲的排序的基本…...

反弹shell的N种姿势

预备知识1. 关于反弹shell 就是控制端监听在某TCP/UDP端口&#xff0c;被控端发起请求到该端口&#xff0c;并将其命令行的输入输出转到控制端。reverse shell与telnet&#xff0c;ssh等标准shell对应&#xff0c;本质上是网络概念的客户端与服务端的角色反转。2. 反弹shel…...

创意视频剪辑教程:快速合并视频并标题,让你的作品更吸睛!

想要让你的视频作品脱颖而出&#xff0c;引人注目&#xff1f;不再担心&#xff0c;我们为你带来了一款创意视频剪辑教程&#xff0c;教你如何快速合并视频并添加令人惊艳的标题效果&#xff01;让你的作品在分钟内变得酷炫而精彩&#xff0c;向世界展示你的创意&#xff01; …...

解决Hadoop审计日志hdfs-audit.log过大的问题

【背景】 新搭建的Hadoop环境没怎么用&#xff0c;就一个环境天天空跑&#xff0c;结果今天运维告诉我说有一台服务器磁盘超过80%了&#xff0c;真是太奇怪了&#xff0c;平台上就跑了几个spark测试程序&#xff0c;哪来的数据呢&#xff1f; 【问题调查】 既然是磁盘写满了&…...

【Java】java和kotlin关于Json写文件

Java写json文件 public class WriterJson {public static void main(String[] args) {// 创建一个 JSON 对象JSONObject jsonObject new JSONObject();jsonObject.put("case", "testtest");JSONObject jsonObjects new JSONObject();jsonObjects.put(&q…...

【深度学习】采用自动编码器生成新图像

一、说明 你知道什么会很酷吗&#xff1f;如果我们不需要所有这些标记的数据来训练 我们的模型。我的意思是标记和分类数据需要太多的工作。 不幸的是&#xff0c;大多数现有模型从支持向量机到卷积神经网&#xff0c;没有它们&#xff0c;卷积神经网络就无法训练。无监督学习不…...

业务系统对接大模型的基础方案:架构设计与关键步骤

业务系统对接大模型&#xff1a;架构设计与关键步骤 在当今数字化转型的浪潮中&#xff0c;大语言模型&#xff08;LLM&#xff09;已成为企业提升业务效率和创新能力的关键技术之一。将大模型集成到业务系统中&#xff0c;不仅可以优化用户体验&#xff0c;还能为业务决策提供…...

大型活动交通拥堵治理的视觉算法应用

大型活动下智慧交通的视觉分析应用 一、背景与挑战 大型活动&#xff08;如演唱会、马拉松赛事、高考中考等&#xff09;期间&#xff0c;城市交通面临瞬时人流车流激增、传统摄像头模糊、交通拥堵识别滞后等问题。以演唱会为例&#xff0c;暖城商圈曾因观众集中离场导致周边…...

线程同步:确保多线程程序的安全与高效!

全文目录&#xff1a; 开篇语前序前言第一部分&#xff1a;线程同步的概念与问题1.1 线程同步的概念1.2 线程同步的问题1.3 线程同步的解决方案 第二部分&#xff1a;synchronized关键字的使用2.1 使用 synchronized修饰方法2.2 使用 synchronized修饰代码块 第三部分&#xff…...

UE5 学习系列(三)创建和移动物体

这篇博客是该系列的第三篇&#xff0c;是在之前两篇博客的基础上展开&#xff0c;主要介绍如何在操作界面中创建和拖动物体&#xff0c;这篇博客跟随的视频链接如下&#xff1a; B 站视频&#xff1a;s03-创建和移动物体 如果你不打算开之前的博客并且对UE5 比较熟的话按照以…...

【配置 YOLOX 用于按目录分类的图片数据集】

现在的图标点选越来越多&#xff0c;如何一步解决&#xff0c;采用 YOLOX 目标检测模式则可以轻松解决 要在 YOLOX 中使用按目录分类的图片数据集&#xff08;每个目录代表一个类别&#xff0c;目录下是该类别的所有图片&#xff09;&#xff0c;你需要进行以下配置步骤&#x…...

pikachu靶场通关笔记22-1 SQL注入05-1-insert注入(报错法)

目录 一、SQL注入 二、insert注入 三、报错型注入 四、updatexml函数 五、源码审计 六、insert渗透实战 1、渗透准备 2、获取数据库名database 3、获取表名table 4、获取列名column 5、获取字段 本系列为通过《pikachu靶场通关笔记》的SQL注入关卡(共10关&#xff0…...

以光量子为例,详解量子获取方式

光量子技术获取量子比特可在室温下进行。该方式有望通过与名为硅光子学&#xff08;silicon photonics&#xff09;的光波导&#xff08;optical waveguide&#xff09;芯片制造技术和光纤等光通信技术相结合来实现量子计算机。量子力学中&#xff0c;光既是波又是粒子。光子本…...

排序算法总结(C++)

目录 一、稳定性二、排序算法选择、冒泡、插入排序归并排序随机快速排序堆排序基数排序计数排序 三、总结 一、稳定性 排序算法的稳定性是指&#xff1a;同样大小的样本 **&#xff08;同样大小的数据&#xff09;**在排序之后不会改变原始的相对次序。 稳定性对基础类型对象…...

GitFlow 工作模式(详解)

今天再学项目的过程中遇到使用gitflow模式管理代码&#xff0c;因此进行学习并且发布关于gitflow的一些思考 Git与GitFlow模式 我们在写代码的时候通常会进行网上保存&#xff0c;无论是github还是gittee&#xff0c;都是一种基于git去保存代码的形式&#xff0c;这样保存代码…...

云原生安全实战:API网关Kong的鉴权与限流详解

&#x1f525;「炎码工坊」技术弹药已装填&#xff01; 点击关注 → 解锁工业级干货【工具实测|项目避坑|源码燃烧指南】 一、基础概念 1. API网关&#xff08;API Gateway&#xff09; API网关是微服务架构中的核心组件&#xff0c;负责统一管理所有API的流量入口。它像一座…...