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.Int64
,int
型对应的是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#语言支持的数值类型的隐式转换如下所示:
-
sbyte
到short
、int
、long
、float
、double
或decimal
; -
byte
到short
、ushort
、int
、uint
、long
、ulong
、float
、double
或decimal
; -
short
到int
、long
、float
、double
或decimal
; -
ushort
到int
、uint
、long
、ulong
、float
、double
或decimal
; -
int
到long
、float
、double
或decimal
; -
uint
到long
、ulong
,float
、double
或decimal
; -
long
到float
、double
或decimal
; -
ulong
到float
、double
或decimal
; -
char
到ushort
、int
、uint
、long
、ulong
、float
、double
或decimal
; -
float
到double
。
上述的隐式转换是安全的,不会造成任何精度或者数量级的损失。需要说明的是,C#不支持任何其他类型到char
类型的隐式转换。
有两种特殊的隐式转换需要说明,之所以说它们特殊,是因为它们会带来精度损失,但没有数量级损失,它们是:
-
从
int
、uint
、long
或者ulong
到float
的转换; -
long
或者ulong
到double
的转换。
我们还是以一段代码为例,演示从int
到float
的类型转换,以此演示精度损失的情况,如代码清单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),很显然,类型转换造成了精度损失,但数量级并没有损失。至于另外一种情况——从long
或ulong
到double
的转换,请大家自行验证。
我们在学习程序设计时一定要重视实验(注意不是“试验”,而是实地验证),将书本或者课题上讲的内容、知识点进行实际验证。这可以加深我们对知识的理解,同时也能积累解决问题的方式和方法。
下一节我们将讲述引用类型的隐式转换。
1.2 引用类型
符合以下情况之一者,编译器可以自动实施隐式类型转换,并且不需要运行时类型检查:
-
任意引用类型到
object
类型的转换; -
派生类型到基类型的转换;
-
派生类型到其实现的接口类型的转换;
-
派生接口类型到基接口类型的转换;
-
数组类型到
System.Array
类型的转换; -
委托类型到
System.Delegate
类型的转换; -
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
类型是所有结构类型和枚举类型的基类。
在本节的最后,我们对装箱的类型转换做个总结,如下:
-
值类型可隐式转换到
object
类型或System.ValueType
类型; -
非
Nullable
值类型可隐式转换到它实现的接口; -
枚举类型可隐式转换到
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 数值类型
在下列情况下,由于不存在自动的隐式转换,因此必须明确地进行显式类型转换:
-
sbyte
到byte
、ushort
、uint
、ulong
、char
-
byte
到sbyte
、char
-
short
到sbyte
、byte
、ushort
、uint
、ulong
、char
-
ushort
到sbyte
、byte
、short
、char
-
int
到sbyte
、byte
、short
、ushort
、uint
、ulong
、char
-
uint
到sbyte
、byte
、short
、ushort
、int
、char
-
long
到sbyte
、byte
、short
、ushort
、int
、uint
、ulong
、char
-
ulong
到sbyte
、byte
、short
、ushort
、int
、uint
、long
、char
-
char
到sbyte
、byte
、short
-
float
到sbyte
、byte
、short
、ushort
、int
、uint
、long
、ulong
、char
、decimal
-
double
到sbyte
、byte
、short
、ushort
、int
、uint
、long
、ulong
、char
、float
、decimal
-
decimal
到sbyte
、byte
、short
、ushort
、int
、uint
、long
、ulong
、char
、float
、double
我们知道,隐式类型转换可以看作省略了()
运算符的显式类型转换,因此对于数值类型间的转换来说,总是使用()
运算符也没问题。
但是,显式的数值类型转换有可能造成信息丢失或者导致系统抛出异常,这也是系统为什么对于这种类型转换要求人工干预并且特别确认的原因。
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 引用类型
引用类型不同于值类型,它由两部分组成:栈中的变量和堆中的对象。对于引用类型的显式类型转换来说,转换的是栈中变量的类型,而该变量指向的位于堆中的对象则类型和数据都不受影响。一般来说,从基类向派生类的转换需要显式转换,因为基类“宽”而派生类“窄”,故而必须进行显式类型转换。
符合下列情况之一的,需要进行显式类型转换:
-
object
类型到任何引用类型的转换(任何引用类型都是object
类型的子类); -
基类到派生类的转换;
-
类到其实现的接口的转换;
-
非密封类到其没有实现接口的转换;
-
接口到另一个不是其基接口的转换;
-
System.Array
类型到数组类型的转换; -
System.Delegate
类型到委托类型的转换。
显式类型转换的结果是否成功只有在运行时才能知道,转换失败则会抛出System.InvalidCastException
异常。
2.4 拆箱
与装箱相反,从引用类型到值类型的转换称为拆箱。符合以下条件之一的进行拆箱操作:
-
从
object
类型或System.ValueType
到值类型的转换; -
从接口类型到值类型(实现了该接口)的转换;
-
从
System.Enum
类型到枚举类型的转换。
在执行拆箱操作前,编译器会首先检查引用类型是否是某个值类型或枚举类型的“装箱”版本,如果是就将其值拷贝出来,还原为值类型的变量。
3. as和is运算符
我们知道,隐式转换是安全的,而显式转换往往是不安全的,有可能造成精度损失,甚至会抛出异常。但类型转换又是不可避免的,例如对于某些集合类型,常常会用到System.Object
类型的变量(使用泛型可以避免这种情况),对于非泛型集合,在将数据放入集合时将发生“向上转型”,即当前类型信息丢失,数据的类型成了object
类型;而当需要把数据从集合取出时,因为需要恢复数据的本来类型,因此也就需要执行“向下转型”到它本来的类型。因此,如何更安全地进行类型转换就是一个值得探讨的问题了。幸好,C#已经为我们提供了解决方案,我们有两种选择:
-
使用
as
运算符进行类型转换; -
先使用
is
运算符判断类型是否可以转换,再使用()
运算符进行显式类型转换。
那么,我们先来介绍一下as
和is
运算符:
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
的适用范围,它只适用于引用类型转换、装箱转换和拆箱转换。而不支持其他的类型转换,如值类型的转换。
现在,我们已经了解了as
和is
运算符,在实际工作中建议尽量使用as
运算符,而少使用()
运算符显式转换。理由如下:
-
无论是
as
还是is
运算符,都比直接使用()
运算符强制转换更安全; -
不会抛出异常,免除了使用
try...catch
进行异常捕获的必要和系统开销,只需要判断是否为null
; -
使用
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行开始,声明并定义了两个方法:DoSomething1
和DoSomething2
,其中分别使用is
和as
运算符进行类型转换。在第18行和第28行对每个方法分别连续调用10,000次,通过使用BCL中的Stopwatch
对象对两者的调用时间进行统计。从结果可以看出,DoSomething2()
的性能比DoSomething1()
要好。至于原因,可以通过查看DoSomething1
和DoSomething2
两个方法的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
操作符就是一种更好的选择。
相关文章:

C#类型转换
C#是静态类型的语言,变量一旦声明就无法重新声明或者存储其他类型的数据,除非进行类型转换。本章的主要任务就是学习类型转换的知识。类型转换有显式的,也有隐式的。所谓显式,就是我们必须明确地告知编译器,我们要把变…...

【Vue】Vue组件--上
目录 一、组件基础 二、组件的嵌套关系 1. 基础架构 2. 嵌套 三、组件注册方式 1. 局部注册: 2. 全局注册: 四、组件传递数据 1. 基础架构 2. 传递多值 3. 动态传递数据 五、组件传递多种数据类型 1. Number 2. Array 3. Object 六、组…...

21、Transformer Masked loss原理精讲及其PyTorch逐行实现
1. Transformer结构图 2. python import torch import torch.nn as nn import torch.nn.functional as Ftorch.set_printoptions(precision3, sci_modeFalse)if __name__ "__main__":run_code 0batch_size 2seq_length 3vocab_size 4logits torch.randn(batch…...

构建高性能网络服务:从 Socket 原理到 Netty 应用实践
1. 引言 在 Java 网络编程中,Socket 是实现网络通信的基础(可以查看我的上一篇博客)。它封装了 TCP/IP 协议栈,提供了底层通信的核心能力。而 Netty 是在 Socket 和 NIO 的基础上,进一步封装的高性能、异步事件驱动的…...
Spring Boot教程之五十六:用 Apache Kafka 消费 JSON 消息
Spring Boot | 如何使用 Apache Kafka 消费 JSON 消息 Apache Kafka 是一个流处理系统,可让您在进程、应用程序和服务器之间发送消息。在本文中,我们将了解如何使用 Apache Kafka 在 Spring Boot 应用程序的控制台上发布 JSON 消息。 为了了解如何创建 …...

Elasticsearch ES|QL 地理空间索引加入纽约犯罪地图
可以根据地理空间数据连接两个索引。在本教程中,我将向你展示如何通过混合邻里多边形和 GPS 犯罪事件坐标来创建纽约市的犯罪地图。 安装 如果你还没有安装好自己的 Elasticsearch 及 Kibana 的话,请参考如下的链接来进行安装。 如何在 Linux࿰…...
csp-j知识点:联合(Union)的基本概念
一、联合(Union)的基本概念 联合是C/C语言中一种特殊的数据结构,它的主要特点是所有成员共享同一块内存空间。这意味着在任何给定时刻,联合中只有一个成员是有效的,因为它们都占用相同的物理内存位置。联合的大小取决…...

docker-compose 方式安装部署confluence
一、confluence简介 Confluence是一款由澳大利亚软件公司Atlassian开发的企业协作工具。它是一个基于web的团队协作平台,用于帮助团队成员共享和协同工作的知识、文档、想法和项目。 Confluence提供了一个集中管理和共享文档、知识库和项目信息的平台。团队成员可…...

深入理解计算机系统阅读笔记-第十二章
第12章 网络编程 12.1 客户端-服务器编程模型 每个网络应用都是基于客户端-服务器模型的。根据这个模型,一个应用时由一个服务器进程和一个或者多个客户端进程组成。服务器管理某种资源,并且通过操作这种资源来为它的客户端提供某种服务。例如…...

网络原理(九):数据链路层 - 以太网协议 应用层 - DNS 协议
目录 1. 数据链路层 1.1 以太网协议 1.1.1 以太网帧格式 1.2 mac 地址 1.2.1 IP 地址和 mac 地址的区别 1.3 帧中的类型字段 1.3.1 MTU - 最长载荷长度 1.3.2 ARP 协议 2. DNS 协议 1. 数据链路层 数据链路层, 是一个底层的层次, 主要用于交换机开发, 对于 Java 开发…...

rtthread学习笔记系列(4/5/6/7/15/16)
文章目录 4. 杂项4.1 检查是否否是2的幂 5. 预编译命令void类型和rt_noreturn类型的区别 6.map文件分析7.汇编.s文件7.1 汇编指令7.1.1 BX7.1.2 LR链接寄存器7.1.4 []的作用7.1.4 简单的指令 7.2 MSR7.3 PRIMASK寄存器7.4.中断启用禁用7.3 HardFault_Handler 15 ARM指针寄存器1…...

【拒绝算法PUA】3065. 超过阈值的最少操作数 I
系列文章目录 【拒绝算法PUA】0x00-位运算 【拒绝算法PUA】0x01- 区间比较技巧 【拒绝算法PUA】0x02- 区间合并技巧 【拒绝算法PUA】0x03 - LeetCode 排序类型刷题 【拒绝算法PUA】LeetCode每日一题系列刷题汇总-2025年持续刷新中 C刷题技巧总结: [温习C/C]0x04 刷…...
今日总结 2025-01-14
学习目标 掌握运用 VSCode 开发 uni - app 的配置流程。学会将配置完善的项目作为模板上传至 Git,实现复用。项目启动 创建项目:借助 Vue - Cli 方式创建项目,推荐从国内地址 https://gitee.com/dcloud/uni - preset - vue/repository/archiv…...

关于扫描模型 拓扑 和 传递贴图工作流笔记
关于MAYA拓扑和传递贴图的操作笔记 一、拓扑低模: 1、拓扑工作区位置: 1、准备出 目标 高模。 (高模的状态如上 ↑ )。 2、打开顶点吸附,和建模工具区,选择四边形绘制. 2、拓扑快捷键使…...
C#知识|泛型Generic概念与方法
哈喽,你好啊,我是雷工! 关于泛型在前面学习记录过 《泛型集合List相关方法》、《Dictionary泛型集合的使用总结》; 其中泛型集合 List<T>、Dictionary<k,v>所在的命名空间为:System.Collection.Generic…...

centos 8 中安装Docker
注:本次样式安装使用的是centos8 操作系统。 1、镜像下载 具体的镜像下载地址各位可以去官网下载,选择适合你们的下载即可! 1、CentOS官方下载地址:https://vault.centos.org/ 2、阿里云开源镜像站下载:centos安装包…...
vscode vue 自动格式化
vscode vue 自动格式化 安装Prettier和Vetur插件 选择设置,并且转到编辑文件。增加如下内容。 {"editor.formatOnSave": true,"editor.defaultFormatter": "esbenp.prettier-vscode","[vue]": {"editor.defaultFor…...

Webpack 5 混淆插件terser-webpack-plugin生命周期作用时机和使用注意事项
参考案例代码 海南酷森科技有限公司/webpack-simple-demo Terser(简要的/简短的) 混淆依据 混淆是发生在代码已经 bundle 之后的事情 变量或者函数在被引用或赋值时才能被混淆 孤立的函数或者变量可能会被移除,但不会被混淆,要…...
MQTT(Message Queuing Telemetry Transport)协议
文章目录 一、MQTT 的原理1. 通信模型2. 核心概念3. 工作流程 二、MQTT 的优势1. 轻量级2. 异步通信3. 可靠性4. 实时性5. 支持断线重连6. 跨平台支持7. 安全性 三、MQTT 的典型应用场景四、与其他协议的对比 MQTT(Message Queuing Telemetry Transport)…...
【MySQL学习笔记】MySQL存储过程
存储过程 1、基础语法2、变量2.1 系统变量2.2 用户自定义变量2.3 局部变量 3、if 流程控制4、参数5、case 流程控制6、循环结构6.1 while 循环6.2 repeat 循环6.3 loop 循环 7、游标 存储过程是事先经过编译并存储在数据库中的一段 SQL 语句的集合,调用存储过程可以…...
conda相比python好处
Conda 作为 Python 的环境和包管理工具,相比原生 Python 生态(如 pip 虚拟环境)有许多独特优势,尤其在多项目管理、依赖处理和跨平台兼容性等方面表现更优。以下是 Conda 的核心好处: 一、一站式环境管理:…...

使用VSCode开发Django指南
使用VSCode开发Django指南 一、概述 Django 是一个高级 Python 框架,专为快速、安全和可扩展的 Web 开发而设计。Django 包含对 URL 路由、页面模板和数据处理的丰富支持。 本文将创建一个简单的 Django 应用,其中包含三个使用通用基本模板的页面。在此…...
日语学习-日语知识点小记-构建基础-JLPT-N4阶段(33):にする
日语学习-日语知识点小记-构建基础-JLPT-N4阶段(33):にする 1、前言(1)情况说明(2)工程师的信仰2、知识点(1) にする1,接续:名词+にする2,接续:疑问词+にする3,(A)は(B)にする。(2)復習:(1)复习句子(2)ために & ように(3)そう(4)にする3、…...

通过Wrangler CLI在worker中创建数据库和表
官方使用文档:Getting started Cloudflare D1 docs 创建数据库 在命令行中执行完成之后,会在本地和远程创建数据库: npx wranglerlatest d1 create prod-d1-tutorial 在cf中就可以看到数据库: 现在,您的Cloudfla…...
镜像里切换为普通用户
如果你登录远程虚拟机默认就是 root 用户,但你不希望用 root 权限运行 ns-3(这是对的,ns3 工具会拒绝 root),你可以按以下方法创建一个 非 root 用户账号 并切换到它运行 ns-3。 一次性解决方案:创建非 roo…...

高危文件识别的常用算法:原理、应用与企业场景
高危文件识别的常用算法:原理、应用与企业场景 高危文件识别旨在检测可能导致安全威胁的文件,如包含恶意代码、敏感数据或欺诈内容的文档,在企业协同办公环境中(如Teams、Google Workspace)尤为重要。结合大模型技术&…...
高防服务器能够抵御哪些网络攻击呢?
高防服务器作为一种有着高度防御能力的服务器,可以帮助网站应对分布式拒绝服务攻击,有效识别和清理一些恶意的网络流量,为用户提供安全且稳定的网络环境,那么,高防服务器一般都可以抵御哪些网络攻击呢?下面…...

OPenCV CUDA模块图像处理-----对图像执行 均值漂移滤波(Mean Shift Filtering)函数meanShiftFiltering()
操作系统:ubuntu22.04 OpenCV版本:OpenCV4.9 IDE:Visual Studio Code 编程语言:C11 算法描述 在 GPU 上对图像执行 均值漂移滤波(Mean Shift Filtering),用于图像分割或平滑处理。 该函数将输入图像中的…...
服务器--宝塔命令
一、宝塔面板安装命令 ⚠️ 必须使用 root 用户 或 sudo 权限执行! sudo su - 1. CentOS 系统: yum install -y wget && wget -O install.sh http://download.bt.cn/install/install_6.0.sh && sh install.sh2. Ubuntu / Debian 系统…...

视觉slam十四讲实践部分记录——ch2、ch3
ch2 一、使用g++编译.cpp为可执行文件并运行(P30) g++ helloSLAM.cpp ./a.out运行 二、使用cmake编译 mkdir build cd build cmake .. makeCMakeCache.txt 文件仍然指向旧的目录。这表明在源代码目录中可能还存在旧的 CMakeCache.txt 文件,或者在构建过程中仍然引用了旧的路…...