C#类型转换

C#是静态类型的语言,变量一旦声明就无法重新声明或者存储其他类型的数据,除非进行类型转换。本章的主要任务就是学习类型转换的知识。类型转换有显式的,也有隐式的。所谓显式,就是我们必须明确地告知编译器,我们要把变量从源类型转换成什么类型;而隐式的则不需要,编译器会自动帮我们进行转换。知道装箱和拆箱吗?我们将在本文中学习装箱和拆箱的知识。


1 隐式类型转换

什么是隐式转换呢? 如果编译器认为从类型1(下称T1)到类型2(下称T2)的转换不会产生不良后果,那么T1到T2的转换就是由编译器自动完成的,这就是隐式转换。我们举个例子,如代码清单5-1所示。

代码清单5-1 隐式类型转换
namespace ProgrammingCSharp4
{class TypeConvert{private void DoSomething(){int intValue = 10;long longValue = intValue;}}
}

第8行执行的是int型到long型的转换,long型对应的是System.Int64int型对应的是System.Int32,显然long型的取值范围要比int型大,因此这种转换是安全的,编译器允许了此次转换。为了了解类型转换的实质,我们可以通过查看上述代码编译后生成的CIL代码,如代码清单5-2所示。

代码清单5-2 CIL代码
.method private hidebysig instance void DoSomething() cil managed
{// Code size 8 (0x8).maxstack 1.locals init([0] int32 intValue, [1] int64 longValue)IL_0000: nopIL_0001: ldc.i4.s 10IL_0003: stloc.0IL_0004: ldloc.0IL_0005: conv.i8IL_0006: stloc.1IL_0007: ret
} // end of method TypeConvert:DoSomething

为了突出重点,我们先忽略其他不相关内容,只关注与类型转换相关的CIL指令。

  • 第7行:类型为i4(即int32)的数据10,入栈;

  • 第8行:出栈,赋予变量[0],即intValue

  • 第9行:变量0数据入栈;

  • 第10行:将栈顶中的数据转换为i8类型(即int64,也就是long类型);

  • 第11行:出栈,赋予变量[1],即longValue

其中,最重要的是第11行,编译器生成了类型转换的CIL指令:

conv.<to type>

<to type>就是要转换到的目标类型。

可见,查看CIL代码有助于我们了解编译器所做的实际操作,有助于我们更加深刻地理解C#这门语言,以及.NET CLR的一些工作机制。在本书的其他章节,我们还会通过CIL代码来进行学习。由于CIL的知识超出了本文的范围,需要进一步了解CIL的读者,可以自行查阅其他资料。

大家现在应该对隐式类型转换有了初步的了解,接下来将进一步学习数值类型的隐式转换,以及引用类型中的隐式转换。


1.1 数值类型

C#语言支持的数值类型的隐式转换如下所示:

  1. sbyteshortintlongfloatdoubledecimal

  2. byteshortushortintuintlongulongfloatdoubledecimal

  3. shortintlongfloatdoubledecimal

  4. ushortintuintlongulongfloatdoubledecimal

  5. intlongfloatdoubledecimal

  6. uintlongulongfloatdoubledecimal

  7. longfloatdoubledecimal

  8. ulongfloatdoubledecimal

  9. charushortintuintlongulongfloatdoubledecimal

  10. floatdouble

上述的隐式转换是安全的,不会造成任何精度或者数量级的损失。需要说明的是,C#不支持任何其他类型到char类型的隐式转换。

有两种特殊的隐式转换需要说明,之所以说它们特殊,是因为它们会带来精度损失,但没有数量级损失,它们是:

  1. intuintlong或者ulongfloat的转换;

  2. long或者ulongdouble的转换。

我们还是以一段代码为例,演示从intfloat的类型转换,以此演示精度损失的情况,如代码清单5-3所示。

代码清单5-3 精度损失示例
using System;namespace ProgrammingCSharp4
{class TypeConvert{static void Main(string[] args){TypeConvert typeConvert = new TypeConvert();typeConvert.DoSomething();}public void DoSomething(){int max = int.MaxValue;float floatValue = max;Console.WriteLine(max);Console.WriteLine(floatValue);}}
}

上述代码打印了int类型支持的最大有效值(MaxValue),然后将它赋予了一个float类型的变量floatValue,编译器执行了隐式转换。运行结果是:

2147483647
2.147484E+09

这里2.147484E+09表示科学计数法,相当于2.147484 × 10^9,也就是2,147,484,000

可以看出,转换后的数值比原值的有效位减少了,因为原值是2,147,483,647(有效位:10),转换后的值是2,147,484,000(有效位:7),很显然,类型转换造成了精度损失,但数量级并没有损失。至于另外一种情况——从longulongdouble的转换,请大家自行验证。

我们在学习程序设计时一定要重视实验(注意不是“试验”,而是实地验证),将书本或者课题上讲的内容、知识点进行实际验证。这可以加深我们对知识的理解,同时也能积累解决问题的方式和方法。

下一节我们将讲述引用类型的隐式转换。


1.2 引用类型

符合以下情况之一者,编译器可以自动实施隐式类型转换,并且不需要运行时类型检查:

  1. 任意引用类型到object类型的转换;

  2. 派生类型到基类型的转换;

  3. 派生类型到其实现的接口类型的转换;

  4. 派生接口类型到基接口类型的转换;

  5. 数组类型到System.Array类型的转换;

  6. 委托类型到System.Delegate类型的转换;

  7. null类型到所有引用类型的转换。

对于引用类型来说,无论是隐式还是显式的类型转换,改变的仅仅是引用的类型,至于该引用指向的对象的类型以及对象的值都是保持不变的。如图5-1所示,它实际改变的是变量1的类型,而引用的对象“对象1”则保持类型和值不变。


1.3 装箱

之所以再次讨论装箱,是因为装箱也属于类型转换的知识范畴。我们先来看一段示例代码,如代码清单5-4所示。

代码清单5-4 装箱
namespace ProgrammingCSharp4
{class Boxing{public void DoSomething(){int x = 10;object obj = x;}}
}

第7行声明了一个int型变量x,并初始化为10。接着第8行声明了一个object类型obj,并使用x为其初始化,这里既是装箱,也是本文讲的类型转换,其本质还是类型转换,即将int型“装箱”为object类型,这个装箱的过程即是隐式的类型转换。

我们仍然通过查看上述代码编译生成的CIL代码来观察装箱的具体过程,CIL代码如代码清单5-5所示。

代码清单5-5 DoSomething()函数的CIL代码
.method public hidebysig instance void DoSomething() cil managed
{// Code size 12 (0xc).maxstack 1.locals init ([0] int32 x,[1] object obj)IL_0000: nopIL_0001: ldc.i4.s 10IL_0003: stloc.0IL_0004: ldloc.0IL_0005: box [mscorlib]System.Int32IL_000a: stloc.1IL_000b: ret
} // end of method Boxing:DoSomething

这里只关注与装箱相关的代码,对CIL有兴趣的读者可以自行查找相关资料进行学习。

代码清单5-5的第13行是重点,box指令指示把栈中的int型(值类型)变量装箱为引用类型(object)。经过装箱这一过程后,原来的值类型的变量就不存在了,取而代之的就是装箱后的引用类型的变量。

另外,枚举类型经过装箱以后成为System.Enum类型,因为System.Enum类是枚举类型的基类。而结构类型和枚举类型装箱后则为System.ValueType类型,原因一样,因为System.ValueType类型是所有结构类型和枚举类型的基类。

在本节的最后,我们对装箱的类型转换做个总结,如下:

  1. 值类型可隐式转换到object类型或System.ValueType类型;

  2. Nullable值类型可隐式转换到它实现的接口;

  3. 枚举类型可隐式转换到System.Enum类型。


2. 显式类型转换

显式类型转换又叫做显式强制类型转换、强制类型转换,因为不能自动进行转换(和隐式类型转换相比而言),因而需要显式地告知编译器需要类型转换。隐式类型转换往往是由窄向宽的转换,而显式类型转换恰恰相反,是由宽向窄的类型转换。以数值类型为例,从一个取值范围更大的类型向较小的类型转换时,由于可能导致精度损失或引发异常,因此编译器不会自动进行隐式转换,除非明确告知。因此,显式转换也称为收缩转换。

那么,该如何告诉编译器我们确定要做这种显式的转换呢?很简单,只需要在变量前使用一对小括号()运算符,小括号中是目标类型。如果未定义相应的()运算符,则强制转换会失败。以后我们还将学到,还可以使用as运算符进行类型转换,如代码清单5-6所示。

代码清单5-6 long类型到int类型的转换
using System;namespace ProgrammingCSharp4
{class TypeConvert{public void DoSomething(){long longValue = 10;int intValue = (int)longValue;}}
}

编译上述代码,编译器会产生如下编译错误:

无法将类型“long”隐式转换为“int”。存在一个显式转换(是否缺少强制转换?)

分析一下为什么会产生这样的错误,在代码的第10行,我们试图将取值范围更大的long类型隐式地转换为int类型。前面讲过,这可能会造成信息丢失,因此编译器将之作为一个错误,并拒绝进行转换。如果确实要进行转换,就需要显式类型转换了,即使用()运算符或者as运算符。知道了错误的原因,那么只需对第10行做如下修改即可解决问题:

int intValue = (int)longValue;

这里的()运算符(int)明确告知编译器需要将long转换为int。至此,问题解决。

其实,所有的隐式类型转换都可以显式地进行类型转换。因此,可以说隐式类型转换都是隐藏了()运算符的显式类型转换。例如:

int intValue = 10;
long longValue = (long)intValue; // 等价于 long longValue = intValue;

接下来,将分别研究数值类型、引用类型的显式类型转换,以及拆箱转换和显式类型转换的关系。


2.1 数值类型

在下列情况下,由于不存在自动的隐式转换,因此必须明确地进行显式类型转换:

  1. sbytebyteushortuintulongchar

  2. bytesbytechar

  3. shortsbytebyteushortuintulongchar

  4. ushortsbytebyteshortchar

  5. intsbytebyteshortushortuintulongchar

  6. uintsbytebyteshortushortintchar

  7. longsbytebyteshortushortintuintulongchar

  8. ulongsbytebyteshortushortintuintlongchar

  9. charsbytebyteshort

  10. floatsbytebyteshortushortintuintlongulongchardecimal

  11. doublesbytebyteshortushortintuintlongulongcharfloatdecimal

  12. decimalsbytebyteshortushortintuintlongulongcharfloatdouble

我们知道,隐式类型转换可以看作省略了()运算符的显式类型转换,因此对于数值类型间的转换来说,总是使用()运算符也没问题。

但是,显式的数值类型转换有可能造成信息丢失或者导致系统抛出异常,这也是系统为什么对于这种类型转换要求人工干预并且特别确认的原因。


2.2 溢出检查

当一种整型转换到另一种整型,这个过程取决于溢出检查上下文。checked关键字用于对整型算术运算和转换显式启用溢出检查,而unchecked关键字则用于取消整型算术运算和转换的溢出检查。

启用溢出检查

操作数的值在目标类型的取值范围内,则转换成功,否则将抛出一个System.OverflowException异常,如代码清单5-7所示。

代码清单5-7 使用checked上下文
using System;namespace ProgrammingCSharp4
{class TypeConvert{static void Main(string[] args){TypeConvert typeConvert = new TypeConvert();typeConvert.DoSomething();}public void DoSomething(){// MyInt的值为2147483647.try{int MyInt = int.MaxValue;byte MyByte = checked((byte)MyInt);}catch (OverflowException){throw;}}}
}

在上述代码中,第18行中的int型变量MyInt的值为2,147,483,647,在第19行将MyInt强制转换为byte类型后,由于byte型的取值范围为0~255,因为这里使用了checked关键字启用了溢出检查,因此这里因为byte型无法容纳远大于其容量的数值而抛出System.OverflowException异常。

取消溢出检查

由于在转换过程将不检查数据是否超过目标类型的取值范围,意味着类型转换永远都会成功。如果源类型的取值范围大于目标类型,那么超过的部分将被截掉;如果源类型的取值范围小于目标类型,那么转换后将使用符号或者零填充至与目标类型的大小相等;如果等于则直接转换至目标类型,如代码清单5-8所示。

代码清单5-8 使用unchecked上下文
namespace ProgrammingCSharp4
{class TypeConvert{static void Main(string[] args){TypeConvert typeConvert = new TypeConvert();typeConvert.DoSomething();}public void DoSomething(){// MyInt的值为2147483647.try{int MyInt = int.MaxValue;byte MyByte = (byte)MyInt;}catch (OverflowException){throw;}}}
}

第17行并没有启用溢出检查,因此并没有抛出System.OverflowException异常,但转换的值也是有问题的。限于byte类型的取值范围,这里赋值后MyByte的值将为255,与原始值可以说大相径庭。第19行还可以使用unchecked关键字改写:

byte MyByte = unchecked((byte)MyInt);

2.3 引用类型

引用类型不同于值类型,它由两部分组成:栈中的变量和堆中的对象。对于引用类型的显式类型转换来说,转换的是栈中变量的类型,而该变量指向的位于堆中的对象则类型和数据都不受影响。一般来说,从基类向派生类的转换需要显式转换,因为基类“宽”而派生类“窄”,故而必须进行显式类型转换。

符合下列情况之一的,需要进行显式类型转换:

  1. object类型到任何引用类型的转换(任何引用类型都是object类型的子类);

  2. 基类到派生类的转换;

  3. 类到其实现的接口的转换;

  4. 非密封类到其没有实现接口的转换;

  5. 接口到另一个不是其基接口的转换;

  6. System.Array类型到数组类型的转换;

  7. System.Delegate类型到委托类型的转换。

显式类型转换的结果是否成功只有在运行时才能知道,转换失败则会抛出System.InvalidCastException异常。


2.4 拆箱

与装箱相反,从引用类型到值类型的转换称为拆箱。符合以下条件之一的进行拆箱操作:

  1. object类型或System.ValueType到值类型的转换;

  2. 从接口类型到值类型(实现了该接口)的转换;

  3. System.Enum类型到枚举类型的转换。

在执行拆箱操作前,编译器会首先检查引用类型是否是某个值类型或枚举类型的“装箱”版本,如果是就将其值拷贝出来,还原为值类型的变量。


3. as和is运算符

我们知道,隐式转换是安全的,而显式转换往往是不安全的,有可能造成精度损失,甚至会抛出异常。但类型转换又是不可避免的,例如对于某些集合类型,常常会用到System.Object类型的变量(使用泛型可以避免这种情况),对于非泛型集合,在将数据放入集合时将发生“向上转型”,即当前类型信息丢失,数据的类型成了object类型;而当需要把数据从集合取出时,因为需要恢复数据的本来类型,因此也就需要执行“向下转型”到它本来的类型。因此,如何更安全地进行类型转换就是一个值得探讨的问题了。幸好,C#已经为我们提供了解决方案,我们有两种选择:

  1. 使用as运算符进行类型转换;

  2. 先使用is运算符判断类型是否可以转换,再使用()运算符进行显式类型转换。

那么,我们先来介绍一下asis运算符:

as运算符用于在两个引用类型之间进行转换,如果转换失败则返回null,并不抛出异常,因此转换是否成功可以通过结果是否为null进行判断,并且只能在运行时才能判断。

代码示例
using System;namespace ProgrammingCSharp4
{class Class1 { }class Class2 { }class TypeConvert{static void Main(string[] args){object[] objArray = new object[6];objArray[0] = new Class1();objArray[1] = new Class2();objArray[2] = "hello";objArray[3] = 123;objArray[4] = 123.4;objArray[5] = null;for (int i = 0; i < objArray.Length; ++i){string s = objArray[i] as string;Console.Write("{0}:", i);if (s != null){Console.WriteLine("是string类型,其值为:'" + s + "'");}else{Console.WriteLine("不是string类型");}}}}
}

这段代码用到了前面讲过的知识:数组、Console对象、命名空间、类;也有如for循环、if判断等。

编译运行上述代码,输出结果为:

0:不是string类型
1:不是string类型
2:是string类型,其值为:'hello'
3:不是string类型
4:不是string类型
5:不是string类型

特别要注意的是,as运算符有一定的适用范围,它只适用于引用类型或可以为null的类型,而无法执行其他转换,如值类型的转换以及用户自定义的类型转换,这类转换应使用强制转换表达式来执行。

is运算符用于检查对象是否与给定类型兼容,并不执行真正的转换。如果判断的对象引用为null,则返回false。由于仅仅判断是否兼容,因此它并不会抛出异常。用法如下:

if (obj is MyObject)
{// 其他操作...
}

上述代码可以确定obj变量是否是MyObject类型的实例,或者是MyObject类的派生类。

同样,也要注意is的适用范围,它只适用于引用类型转换、装箱转换和拆箱转换。而不支持其他的类型转换,如值类型的转换。

现在,我们已经了解了asis运算符,在实际工作中建议尽量使用as运算符,而少使用()运算符显式转换。理由如下:

  1. 无论是as还是is运算符,都比直接使用()运算符强制转换更安全;

  2. 不会抛出异常,免除了使用try...catch进行异常捕获的必要和系统开销,只需要判断是否为null

  3. 使用as比使用is性能上更好,这一点可以通过代码清单5-9来说明。

代码清单5-9 as和is运算符的性能对比
using System;
using System.Diagnostics;namespace ProgrammingCSharp4
{class Class1 { }class AsIsSample{private Class1 c1 = new Class1();public static void Main(){AsIsSample aiSample = new AsIsSample();Stopwatch timer = new Stopwatch();timer.Start();for (int i = 0; i < 10000; i++){aiSample.DoSomething1();}timer.Stop();decimal micro = timer.Elapsed.Ticks / 10m;Console.WriteLine("执行DoSomething1() 10000次的时间:{0:F1} 微秒.", micro);timer = new Stopwatch();timer.Start();for (int i = 0; i < 10000; i++){aiSample.DoSomething2();}timer.Stop();micro = timer.Elapsed.Ticks / 10m;Console.WriteLine("执行DoSomething2() 10000次的时间:{0:F1} 微秒.", micro);}public void DoSomething1(){object c2 = c1;if (c2 is Class1){Class1 c = (Class1)c2;}}public void DoSomething2(){object c2 = c1;Class1 c = c2 as Class1;if (c != null){// 其他操作...}}}
}

输出为:

执行DoSomething1() 10000次的时间:288.9 微秒.
执行DoSomething2() 10000次的时间:258.6 微秒.

从第37行开始,声明并定义了两个方法:DoSomething1DoSomething2,其中分别使用isas运算符进行类型转换。在第18行和第28行对每个方法分别连续调用10,000次,通过使用BCL中的Stopwatch对象对两者的调用时间进行统计。从结果可以看出,DoSomething2()的性能比DoSomething1()要好。至于原因,可以通过查看DoSomething1DoSomething2两个方法的CIL代码来一探究竟。方法DoSomething1的CIL代码如代码清单5-10所示。

代码清单5-10 方法DoSomething1的CIL代码
.method public hidebysig instance void DoSomething1() cil managed
{// Code size 34 (0x22).maxstack 2.locals init ([0] object c2, [1] class ProgrammingCSharp4.Class1 c, [2] bool CS$4$0000)IL_0000: nopIL_0001: ldarg.0IL_0002: ldfld class ProgrammingCSharp4.Class1 ProgrammingCSharp4.AsIsSample::c1IL_0007: stloc.0IL_0008: ldloc.0IL_0009: isinst ProgrammingCSharp4.Class1IL_000e: ldnullIL_000f: cgt.unIL_0011: ldc.i4.0IL_0012: ceqIL_0014: stloc.2IL_0015: ldloc.2IL_0016: brtrue.s IL_0021IL_0018: nopIL_0019: ldloc.0IL_001a: castclass ProgrammingCSharp4.Class1IL_001f: stloc.1IL_0020: nopIL_0021: ret
} // end of method ProgrammingCSharp4.AsIsSample::DoSomething1

代码清单5-10的第13行首先测试了是否能转换到Class1类型,如果可以则进行转换;第23行再次测试能否转换到Class1类型,如果测试成功则进行转换。

方法DoSomething2的CIL代码如代码清单5-11所示。

代码清单5-11 方法DoSomething2的CIL代码
.method public hidebysig instance void DoSomething2() cil managed
{// Code size 26 (0x1a).maxstack 2.locals init ([0] object c2, [1] class ProgrammingCSharp4.Class1 c, [2] bool CS$4$0000)nopldarg.0ldfld class ProgrammingCSharp4.Class1 ProgrammingCSharp4.AsIsSample::c1stloc.0ldloc.0isinst ProgrammingCSharp4.Class1stloc.1ldloc.1ldnullceqstloc.2ldloc.2brtrue.s IL_0019nopnopret
} // end of method ProgrammingCSharp4.AsIsSample::DoSomething2

代码清单5-11的第11行同样测试了能否转换到Class1类型,如果可以则进行转换。

由此可见,前者进行了两次测试和检查,而后者只进行了一次测试,这是造成两者之间性能差异的原因。

现在我们总结下,什么场合该使用is,什么场合该使用as:如果测试对象的目的是确定它是否属于所需类型,并且如果测试结果为真,就要立即进行转换,这种情况下使用as操作符的效率更高;但有时仅仅只是测试,并不想立即转换,也可能根本就不会转换,只是在对象实现了接口时,要将它加到一个列表中,这时is操作符就是一种更好的选择。

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

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

相关文章

智能物流升级利器——SAIL-RK3576核心板AI边缘计算网关设计方案(一)

近年来&#xff0c;随着物流行业智能化和自动化水平不断提升&#xff0c;数据的实时处理与智能决策成为推动物流运输、仓储管理和配送优化的重要手段。传统的集中式云平台虽然具备强大计算能力&#xff0c;但高延迟和带宽限制往往制约了物流现场的即时响应。为此&#xff0c;我…

【算法篇】前缀和

&#x1f525;个人主页&#xff1a;Quitecoder &#x1f525;专栏&#xff1a;算法笔记仓 前缀和是一种常用于处理数组区间求和问题的技巧。它可以用来减少重复计算&#xff0c;使得多次查询区间和的时间复杂度从 O(n) 降低到 O(1) 目录 1. 一维模版2. 二维模版3. 除自身以外数…

第R4周:LSTM-火灾温度预测

&#x1f368; 本文为&#x1f517;365天深度学习训练营 中的学习记录博客&#x1f356; 原作者&#xff1a;K同学啊 文章目录 一、代码流程1、导入包&#xff0c;设置GPU2、导入数据3、数据集可视化4、数据集预处理5、设置X&#xff0c;y6、划分数据集7、构建模型8、定义训练函…

机组存储系统

局部性 理论 程序执行&#xff0c;会不均匀访问主存&#xff0c;有些被频繁访问&#xff0c;有些很少被访问 时间局部性 被用到指令&#xff0c;不久可能又被用到 产生原因是大量循环操作 空间局部性 某个数据和指令被使用&#xff0c;附近数据也可能使用 主要原因是顺序存…

Windows图形界面(GUI)-QT-C/C++ - Qt图形绘制详解

公开视频 -> 链接点击跳转公开课程博客首页 -> ​​​链接点击跳转博客主页 目录 Qt绘图基础 QPainter概述 基本工作流程 绘图事件系统 paintEvent事件 重绘机制 文字绘制技术 基本文字绘制 ​编辑 高级文字效果 基本图形绘制 线条绘制 ​编辑 形状绘制 …

mapbox进阶,添加绘图控件

👨‍⚕️ 主页: gis分享者 👨‍⚕️ 感谢各位大佬 点赞👍 收藏⭐ 留言📝 加关注✅! 👨‍⚕️ 收录于专栏:mapbox 从入门到精通 文章目录 一、🍀前言1.1 ☘️mapboxgl.Map 地图对象1.2 ☘️mapboxgl.Map style属性1.3 ☘️MapboxDraw 绘图控件二、🍀添加绘图控…

client-go 的 QPS 和 Burst 限速

1. 什么是 QPS 和 Burst &#xff1f; 在 kubernetes client-go 中&#xff0c;QPS 和 Burst 是用于控制客户端与 Kubernetes API 交互速率的两个关键参数&#xff1a; QPS (Queries Per Second) 定义&#xff1a;表示每秒允许发送的请求数量&#xff0c;即限速器的平滑速率…

WeakAuras NES Script(lua)

WeakAuras NES Script 修星脚本字符串 脚本1&#xff1a;NES !WA:2!TMZFWXX1zDxVAs4siiRKiBN4eV(sTRKZ5Z6opYbhQQSoPtsxr(K8ENSJtS50(J3D7wV3UBF7E6hgmKOXdjKsgAvZFaPTtte0mD60XdCmmecDMKruyykDcplAZiGPfWtSsag6myGuOuq89EVDV9wPvKeGBM7U99EFVVVV33VFFB8Z2TJ8azYMlZj7Ur3QDR(…

【make】makefile 函数全解

目录 makefile简介函数全解介绍相关链接字符串处理函数subst 函数—字符串替换patsubst 函数 — 模式字符串替换strip 函数 — 去空格findstring 函数 — 查找字符串filter 函数 — 过滤器filter-out 函数 — 过滤器sort 函数 — 排序word 函数 — 取单词wordlist函数 — 取一串…

Android 15应用适配指南:所有应用的行为变更

Android系统版本适配&#xff0c;一直是影响App上架Google Play非常重要的因素。 当前Google Play政策规定 新应用和应用更新 必须以 Android 14&#xff08;API 级别 34&#xff09;为目标平台&#xff0c;才能提交到Google Play。现有应用 必须以 Android 13&#xff08;AP…

Java Agent(三)、ASM 操作字节码入门

目录 1、前言 2、什么是ASM&#xff1f; 2.1、工作流程 2.2、ASM集合核心API 2.1.1、ClassReader 2.1.2、ClassWriter 2.1.3、 ClassVisitor 2.1.4、MethodVisitor 2.1.5、 FieldVisitor 2.1.6、Opcodes 3、简单示例 3.1、maven依赖 3.2、hello world 3.3、执行结…

MySQL数据库(SQL分类)

SQL分类 分类全称解释DDLData Definition Language数据定义语言&#xff0c;用来定义数据库对象&#xff08;数据库&#xff0c;表&#xff0c;字段&#xff09;DMLData Manipulation Language数据操作语言&#xff0c;用来对数据库表中的数据进行增删改DQLData Query Languag…

[微服务]redis数据结构

介绍 我们常用的Redis数据类型有5种&#xff0c;分别是&#xff1a; StringListSetSortedSetHash 还有一些高级数据类型&#xff0c;比如Bitmap、HyperLogLog、GEO等&#xff0c;其底层都是基于上述5种基本数据类型。因此在Redis的源码中&#xff0c;其实只有5种数据类型。 …

PyQt5

PyQt5 环境搭建安装 pycharm安装 PyQt5 打包成exe安装 pyinstaller打包 报错进程已结束&#xff0c;退出代码-1073740791&#xff08;0xC0000409&#xff09; 环境搭建 安装 pycharm 安装 PyQt5 pip install pyqt5 -i https://pypi.tuna.tsinghua.edu.cn/simplepip install …

高级运维:shell练习2

1、需求&#xff1a;判断192.168.1.0/24网络中&#xff0c;当前在线的ip有哪些&#xff0c;并编写脚本打印出来。 vim check.sh #!/bin/bash# 定义网络前缀 network_prefix"192.168.1"# 循环遍历1-254的IP for i in {1..254}; do# 构造完整的IP地址ip"$network_…

Grails应用http.server.requests指标数据采集问题排查及解决

问题 遇到的问题&#xff1a;同一个应用&#xff0c;Spring Boot(Java)和Grails(Groovy)混合编程&#xff0c;常规的Spring Controller&#xff0c;可通过Micromete Pushgateway&#xff0c; 采集到http.server.requests指标数据&#xff0c;注意下面的指标名称是点号&#…

pycharm+pyside6+desinger实现查询汉字笔顺GIF动图

一、引言 这学期儿子语文期末考试有一道这样的题目&#xff1a; 这道题答案是B&#xff0c;儿子做错了选了C。我告诉他“车字旁”和“车”的笔顺是不一样的&#xff0c;因为二者有一个笔画是不一样的&#xff0c;“车字旁”下边那笔是“提”&#xff0c;而“车”字是“横”&am…

【2025 Rust学习 --- 17 文本和格式化 】

字符串与文本 Rust 的主要文本类型 String、str 和 char 内容概括&#xff1a; Unicode 背景知识&#xff1f;单个 Unicode 码点的 char&#xff1f;String 类型和 str 类型都是表示拥有和借用的 Unicode 字符序列。Rust 的字符串格式化工具&#xff0c;比如 println! 宏和 …

EasyCVR视频汇聚平台如何配置webrtc播放地址?

EasyCVR安防监控视频系统采用先进的网络传输技术&#xff0c;支持高清视频的接入和传输&#xff0c;能够满足大规模、高并发的远程监控需求。平台支持多协议接入&#xff0c;能将接入到视频流转码为多格式进行分发&#xff0c;包括RTMP、RTSP、HTTP-FLV、WebSocket-FLV、HLS、W…

rknn环境搭建之docker篇

目录 1. rknn简介2. 环境搭建2.1 下载 RKNN-Toolkit2 仓库2.2 下载 RKNN Model Zoo 仓库2.3 下载交叉编译器2.4 下载Docker镜像2.5 下载ndk2.5 加载docker镜像2.6 docker run 命令创建并运行 RKNN Toolkit2 容器2.7 安装cmake 3. 模型转换3.1 下载模型3.2 模型转换 4. 编译cdem…