《深入解析 C#》—— C# 3 部分

文章目录

    • 第三章 C#3:LINQ及相关特性
      • 3.1 自动实现属性(*)
      • 3.2 隐式类型 var(*)
      • 3.3 对象和集合初始化
        • 3.3.1 对象初始化器
        • 3.3.2 集合初始化器
      • 3.4 匿名类型
        • 3.4.1 基本语法和行为
        • 3.4.2 编译器生成类型
        • 3.4.3 匿名类型的局限性
      • 3.5 lambda 表达式
        • 3.5.1 捕获变量
        • 3.5.2 表达式树
      • 3.6 扩展方法
        • 3.6.1 声明扩展方法
        • 3.6.2 调用扩展方法
        • 3.6.3 扩展方法的链式调用
      • 3.7 查询表达式
        • 3.7.1 从 C# 到 C# 的查询表达式转换
        • 3.7.2 范围变量和隐形标识符
        • 3.7.3 选择使用哪种 LINQ 语法
      • 3.8 终极形态:LINQ

第三章 C#3:LINQ及相关特性

3.1 自动实现属性(*)

3.2 隐式类型 var(*)

3.3 对象和集合初始化

3.3.1 对象初始化器
image-20240319092546110

​ 对象初始化器的作用只是表达应该如何初始化每个属性。

​ 注意,只有在使用对象初始化器或者集合初始化器时,构造器的参数列表才再以省略。

  • 如果初始化值(“=” 右边的内容)是一个普通的表达式,那么会先计算该表达式的值,然后将结果传给属性对应的 set 访问器。
  • 如果初始化值是另一个对象初始化器,则不会调用 set 访问器,而会调用 get 访问器,然后将嵌套对象初始化器得到的结果应用于由 get 访问器返回的属性。
image-20240319092607335

​ 上述代码等同于以下代码:

image-20240319092623377
3.3.2 集合初始化器

​ 集合初始化器多用于创建新集合。下面这行代码创建了一个字符串集合并为其添加初始值:

image-20240319092730747

​ 编译器会将以上代码转换成一个构造器调用,其后紧跟一系列 Add 方法的调用:

image-20240319092750355

​ 对于 Dictionary<TKey, TValue>,添加元素的方法是 Add (key, value):

image-20240319092911075

​ 编译器把每个元素初始化器都看作一个 Add 调用。如果元素初始化器没有大括号,则将其作为单个参数传递给 Add 方法。上述字典的例子等同于如下代码:

image-20240319093017901 image-20240319093005951

​ 只有实现了 IEnumerable 接口的类型才能够使用集合初始化器。

3.4 匿名类型

3.4.1 基本语法和行为

​ 使用匿名类型可以更精练地表达“一次性”的类型需求,同时还不失静态类型的优势:

image-20240319093320358
  1. 匿名类型的语法类似于对象初始化器,但无须指定类型名称,只需要 new 关键字、左大括号、属性以及右大括号。这一形式称为匿名对象创建表达式

  2. 声明 player 变量使用了 var 关键字,因为所创建的类型是匿名类型,所以只能用 var 来声明(也可以使用 object 来声明,不过意义不大)。

  3. 以上代码依然属于静态类型的范畴。Visual Studio 会为 player 变量自动设置 Name 和 Score 属性。如果要访问一个不存在的属性(比如 player.Points),则编译器会报错。

  4. 属性的类型是根据赋值的类型进行推断的:player.Name 是 string 类型,player. Score 是 int 类型。

投射初始化器:

​ 可以从其他对象复制属性或字段到新对象中,并且二者的属性或字段名称相同。

image-20240319093835592

​ 上述例子中,除了 CustomerName,其他属性都使用了投射初始化器。以上代码的运行结果和下面这种显式写出每个属性名称得到的结果是相同的:

image-20240319093932139

​ 如果目标属性或字段的名称与源名称一致,那么可以交由编译器来推断名称,如以下代码:

image-20240319094354105

​ 可以直接简化为:

image-20240319094406624

说明:

​ 尽管以上两种形式的代码结果相同,但不是所有行为都相同。

​ 例如,在项目中将 Address 属性重命名为 CustomerAddress,若使用投射初始化器,那么 flattenedItem.Address 也将变为 flattenedItem.CustomerAddress。

3.4.2 编译器生成类型

​ 虽然源码中没有出现匿名类型的名称,但编译器需要为它生成一个类型。

  • 它在执行期没有任何特殊之处,对于执行期来说也只是一个普通的类型而已。
  • 该类型的名称不是一个有效的 C# 名称。

​ 关于该类型,还有几个比较有意思的特征(其中一些得到了语言规范层面的保证):

  1. 它是一个类(保证)。
  2. 其基类是 object (保证)。
  3. 该类是密封的(不保证,虽然非密封的类并没有什么优势)。
  4. 属性是只读的(保证)。
  5. 构造器的参数名称与属性名称保持一致(不保证,有时对于反射有用)。
  6. 对于程序集是 internal 的(不保证,在处理动态类型时会比较棘手)。
  7. 该类会覆盖 GetHashCode() 和Equals() 方法:两个匿名类型只有在所有属性都等价的情况下才等价(可以正常处理 null 值)。只保证会覆盖这两个方法,但不保证散列值的计算方式。
  8. 覆盖并完善 ToString() 方法,用于呈现各属性名称及其对应值。这一点不保证,但对于问题诊断来说作用重大。
  9. 该类型为泛型类,其类型形参会应用于每一个属性。具有相同属性名称但属性类型不同的匿名类型,会使用相同的泛型类型,但拥有不同的类型实参。这一点不保证,不同编译器的实现方式不同。
  10. 如果两个匿名对象创建表达式使用相同的属性名称,具有相同的属性类型以及属性顺序, 并且在同一个程序集中,那么这两个对象的类型相同。

​ 可以利用第 10 点使用匿名类型来创建隐式类型数组:

image-20240320122229641
3.4.3 匿名类型的局限性
  1. 难以应用于方法签名中。即, 难以在多处使用同一个匿名类型。
  2. 匿名类型不提供任何数据封装。即,匿名类型中不能有校验,也不能添加任何行为。

3.5 lambda 表达式

3.5.1 捕获变量

​ 给出如下设计好的代码示例:

image-20240320122846091
  • instanceField 是 CapturedVariablesDemo 类的一个实例字段,被 lambda 表达式所捕获。
  • methodParameter 是 CreateAction 方法的一个参数,被 lambda 表达式所捕获。
  • methodLocal 是 CreateAction 方法中的一个局部变量,被 lambda 表达式所捕获。
  • uncaptured 是 CreateAction 方法中的一个局部变量,因为没有被 lambda 表达式使用,所以不属于捕获变量。
  • lambdaParameter 是 lambda 表达式自己的参数,不属于捕获变量。
  • lambdaLocal 是 lambda 表达式内部的局部变量,不属于捕获变量。

通过生成类来实现捕获变量

  • 没有捕获任何变量,编译器会创建一个静态方法,不需要额外的上下文。
  • 仅捕获了实例字段,编译器会创建一个实例方法。实例字段的捕获数目没有影响,只需要一个 this 便都可以访问。
  • 捕获了局部变量或者参数,编译器会创建一个私有的嵌套类用于保存上下文信息,在该类中创建一个实例方法用于容纳原 lambda 表达式内容,并使用嵌套类来访问捕获变量。

​ 应用上述规则,编译器转义后的代码如下:

image-20240320123817033

说明:

​ 具体实现细节因编译器而异。例如对于没有捕获变量的 lambda 表达式,编译器可能会创建一个包含一个实例方法的嵌套类,而不是创建一个静态方法。委托的执行效率会因创建方式的不同而略有差异。这里只描述编译器为访问捕获变量所做的那些必要、基本的工作, 其复杂度可能根据实际需要而增加。

局部变量的多次实例化

​ 简单起见,下列代码不捕获参数和实例字段,只捕获一个局部变量:

image-20240320124047278

​ 在这段代码中,每次声明 text 时,该变量就完成一次实例化,因此每个 lambda 表达式捕获的都是不同的变量实例,于是 5 个完全独立的 text 变量被分别捕获。虽然这段代码中变量初始化后没有任何修改操作, 但编译器的做法是:每次初始化都创建一个不同的生成类型实例。编译器转义后的代码如下:

image-20240320124243481

多个作用域下的变量捕获

​ 循环的每次迭代都要实例化一次变量,是因为变量作用域的缘故。一个方法内部可能存在多个作用域,每个作用域都可能包含局部变量的声明,而一个 lambda 表达式可以从多个作用域捕获变量,给出如下示例代码:

image-20240320124442728

​ 其执行结果如下:

image-20240320124454985

​ 其中,outercounter 变量被两个委托共用,而 innerCounter 为画个委托分别所有。每个委托都需要各自的上下文,但是各自的上下文还需要指向一个公共的上下文。编译器会为这种情况创建两个私有嵌套类,转义后的结果如下:

image-20240320124554001

​ 大多数情况很少需要查看这样的代码,但编译器生成代码的方式会对程序性能有不小的影响。如果在性能敏感的代码中使用 lambda 表达式,那么需要注意可能会因为变量捕获而创建过多对象,从而影响性能

3.5.2 表达式树

​ lambda 表达式可以由编译器转换成表达式树。表达式树是将代码按照数据来表示的一种形式。这项特性是 LINQ 能够有效处理 SQL 数据库的核心秘诀所在。通过表达式树,C# 的代码可以在执行期被分析并转换成 SQL。 委托的作用是提供可运行的代码,而表达式树的作用是提供可查看的代码(这有点类似于反射机制)。虽然也可以在代码中直接构建表达式树,但更普遍的做法是让编译器负责把 lambda 表达式转换成表达式树。

​ 以下面的 lambda 表达式为例:

image-20240320133626815

​ 编译器并未在任何地方生成一个硬编码的字符串。以上字符串是通过表达式树动态构建出来的。这段代码表明:代码是可以进行执行期检查的。这就是表达式树的所有关键所在。

​ 首先看 adder 的类型:Expression<Func<int, int, int>>。把它拆解成两部分: Expression<TDelegate> 和 Func<int, int, int>。Func<int, int, int> 是 Expression<TDelegate> 的类型实参,它是一个代理类型,由两个 int 参数和一个 int 返回值构成。

​ Expression<TDelegate>是处理 TDelegate 类型的表达式树类型。其中 TDelegate 必须是委托类型。委托类型仅仅是表达式树相关的诸多类型之一,它们均位于 Systarn.Linq.Expressions 命名空间下。非泛型的 Expression 类是所有表达式类型的抽象基类。

​ adder 变量是一个表示接收两个整型值并返回一个整型值方法的表达式树表示,之后可以用 lambda 表达式来为该变量赋值。编译器负责生成适用于执行期的表达式树。示例代码如下:

image-20240321102700117 image-20240321102713913

转换表达式树的局限性

​ 只有拥有表达式主体的 lambda 表达式才能转换成表达式树。下面这句代码会编译报错:

image-20240321103154119
  • 从 .NET 3.5 开始,表达式树 API 就已经扩展支持代码块和其他构建了,但 C# 编译器依然保留了该限制,而且对于 LINQ 使用的表达式树也有同样的限制。
  • 这是对象初始化器和集合初始化器很重要的原因:可以在一个表达式内完成初始化,以供表达式树使用。
  • 另外,lambda 表达式不能使用赋值运算符,也不能使用 C# 4 的动态类型和 C# 5 的异步。

将表达式树编译成委托

​ 表达式树可用于在执行期动态构建委托。这种方式一般需要手动编写部分代码,而不是使用 lambda 表达式进行转化。

​ Expression<TDelegate> 有一个 Compile() 方法,该方法返回一个委托类型。该委托类型与普通的委托类型无异。

​ 以上述代码为例,构建出 adder 表达式树,将其编译成一个委托,然后调用该委托并打印出结果:

image-20240321103549075

3.6 扩展方法

3.6.1 声明扩展方法
  • 扩展方法必须声明在一个非嵌套、非泛型的静态类中。
  • 在 C#7.2 之前箕一个参数不能是 ref 参数。
  • 扩展方法所在的类不能是泛型类,但扩展方法自身可以是泛型方法。
  • 扩展方法的第一个参数有时称为扩展目标扩展类型
image-20240321103750423

​ 编译器唯一需要做的就是为扩展方法及其所在类添加[Extension]特性。该特性在命名空间 System.Runtime.CompilerServices 下。其本质上是一个标记,标记 ToInstant() 方法可以按照 DateTimeOffset 的实例方法那样凋用。

3.6.2 调用扩展方法

​ 扩展方法可以在其第一个参数的类型实例上以实例方法的调用方式进行调用,但还需要一个前提:让编译器可以查找到这个扩展方法。

优先级问题

  1. 如果存在一个与该类同名的普通实例方法,那么编译器总是会优先选择该实例方法来调用。

    • 在此过程中,无所谓扩展方法是否具有更匹配的形参。如果编译器查找到有可调用的实例方法,就不会再去查找扩展方法了。
  2. 如果编译器没有找到可调用的实例方法,那么会开始查找扩展方法。首先查找扩展方法调用代码所在的命名空间以及所有 using 指令指定的命名空间。

image-20240321104119590

​ 编译器会从以下位置查找扩展方法:

  • CSharpInDepth.Chapter03 命名空间下的静态类;
  • CSharpInDepth 命名空间下的静态类;
  • 全局命名空间下的静态类;
  • using 指令指定的命名空间下的静态类(例如 using System 这样的指向命名空间的命令);
  • (只在 C#6 中)using static 指定的静态类,10.1节还会介绍。

​ 补充:

  1. 编译器会从最内层的命名空间一路向外查找至全局命名空间。在查找的每条路径上,都要查找当前命名空间下的静态类,或者查找 using 指令指定的命名空间中的类。
  2. 查找的顺序并不重要。如果调整 using 指令的顺序后影响了扩展方法的查找结果,建议将扩展方法重新命名。
  3. 查找的每一步中都有可能找到多个适合调用的扩展方法。此时编译器会对当前所有候选方法执行常规的重载决议。
  4. 在决策完成与,编译器为调用扩展方法所生成的 IL 代码和调用普通静态方法所生成的 IL 代码是完全相同的。

说明:

​ x.Method(y);

​ 如果 Method 是实例方法,x 为 null,就会抛出 NulLReferenceException;

​ 而如果 Method 是一个扩展方法,那么即便 x 为 null,也会将 x 作为其首个参数进行方法调用。

3.6.3 扩展方法的链式调用

​ 下面示例代码是一个简单查询:现有一个单词序列,按照单词长度进行筛选,并将其按字母顺序排序,然后全部转换为大写。该查询只用到了 C#3 中的 lambda 表达式和扩展方法这两个特性。

image-20240321104753619

​ 注意:以上代码中 Where、OrderBy 和 Select 三个调用的顺序就是操作实际发生的顺序。由于 LINQ 中存在延退和优化策略,很难知道具体何时会执行什么操作,但代码的阅读顺序和执行顺序是一致的。

​ 下列代码实现了上述相同的查询功能,但没有使用扩展方法。

image-20240321104901442

​ 对比之下可以发现明显的缺陷:代码阅读起来很困难。代码中方法调用的顺序和实际执行的顺序刚好相反:Where 方法是第一个被调用的,却放在了末尾。lambda 表达式 word => word.ToUpper() 究竟属于哪个方法调用很不明确。它本属于 Select 方法, 但和 Select 中间隔了一堆代码。

​ 还有一个解决方法是将每个方法调用的结果都赋给一个局部变量,然后通过上一个变量再继续调用下一个方法。但大量额外的局部变量容易造成混淆且会分散注意力。

image-20240321105153037

​ 由上可见,方法的链式调用带来的好处不仅仅限于 LINQ。一个方法调用的结果用作另一个方法调用的开始。扩展方法能让我们以可读性强的方式编码任何类型,而且不局限于那些已经支持链式调用的类型。

3.7 查询表达式

​ 虽然几乎 C# 3 的所有特性都对 LINQ 有所贡献,但只有查询表达式是专门为 LINQ 设计的。 使用查询表达式,我们可以通过查询专用语句(select, where、let、group by 等)编写简洁的查询代码。由编译器负责把查询表达式翻译成非查询语句的形式,并进行常规编译。回顾一下 3.6.3 节的代码:

image-20240321105448250

​ 使用查询表达式改写的功能相同的查询代码如下所示,其中加粗的部分为查询表达式:

image-20240321105515218
3.7.1 从 C# 到 C# 的查询表达式转换

​ 语言规范直接将查询表达式定义为一种语法转译,且该转译过程发生在绑定或重载决议之前。即,查询表达式会首先被编译器转义为可执行的 C# 代码。很多时候,转译的结果就是使其变成对应的扩展方法调用,不过语言规范并没有强制要求该行为。

3.7.2 范围变量和隐形标识符

​ 查询表达式引入了范围变量的概念。范围变量与普通变量不同,范围变量充当了查询语句中每条子句中的输入。

​ 在上一个例子中,位于查询表达式起始位置的 from 子句引入了范围变量(加粗部分):

image-20240321110014925

​ 子句中引入范围变量的最简单方式应该是使用 let 关键字。假设需要在查询中多次使用单词长度这个变量,但又不想每次都调用 Length 属性。如果需要就单词长度进行琲序,并且在输出结果中使用长度变量,那么使用 let 子句的查询如下所示:

image-20240321110103716

​ 在对查询进行转译时,该如何表示 length 和 word 呢?这需要把原始的单词序列转换成“单词-长度”对。在需要访问范围变量的子句中,再通过变量对来访问其中的某个变量:

image-20240321110336455

​ 这里的 tmp 不属于查询转译的一部分,语言规范中是用 * 符号表示的。在语言规范并没有规定为查询构建表达式树时,参数应当使用什么名称。这个名称本身不重要,因为在编写查询时它是不可见的,因此把它称为隐形标识符

3.7.3 选择使用哪种 LINQ 语法
  • 查询表达式:更适合大规模查询,表现出众,可读性强。
    • 必须以 from 子句开始,以 select 或者 group by 子句结尾。
  • 方法语法:更适合简单查询,简单明了。

​ 例如:

image-20240321110831184

​ 对比采用扩展方法的写法,就显得有些笨拙了:

image-20240321110927691

说明:

​ 对于采用非查询表达式的语法,目前没有统一的术语,而有方法语法、点式语法、流式语法、lambda 语法等名称,之后会统一采用方法语法来代称。

​ 当查询变得更复杂时,方法语法依然可以从容应对:

  1. LINQ 中提供的很多方法,并没有与之对应的查询表达式语法。

    • 例如 Select 和 Where 的某些重载方法,返回的是元素以及元素对应的索引值。
  2. 如果想在查词的结尾执行一个方法调用(例如调用 ToList() 来把结果转换成 List<T> 对象),就要把整个查询表达式用圆括号括起来;

    如果使用方法语法,只需在末尾直接添加方法调用即可。

​ 在很多情况下(包括上述例子在内),两种方式难分高下。

3.8 终极形态:LINQ

​ 下面介绍 C#3 特性是如何成就 LINQ 的。假设有一个查询从 Entity Framework 获取数据,代码如下所示(假设已存在某数据库和相应的表结构):

image-20240321111400222

​ 短短 4 行代码,应用了所有新特性。

  1. 匿名类型.

    包括投射初始化器(只选择 name 和 price 这两个属性)。

  2. 使用 var 声明的匿名类型.

    因为无法声明 products 变量的有效类型。

  3. 查询表达式。

    当然对于本例可以不使用查询表达式,但对于更复杂的情况,使用查询表达式能事半功倍。

  4. lambda 表达式。

    lambda 表达式在这里作为查询表达式转译之后的结果。

  5. 扩展方法。

    使得转译后的查询可以通过 Queryable 类实现,因为 dbContext.Products 实现了 IQueryable<Product>接口。

  6. 表达式树。

    使得查询逻辑可以按照数据的方式传给 LINQ 提供器,然后转换成 SQL 语句并交由数据库执行。

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

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

相关文章

#Linux(文件系统概念)

&#xff08;一&#xff09;发行版&#xff1a;Ubuntu16.04.7 &#xff08;二&#xff09;记录&#xff1a; &#xff08;1&#xff09;查看文件系统情况df&#xff0c;man df查看df命令的功能 &#xff08;2&#xff09;查看文件系统的类型 df-T &#xff08;3&#xff09;df …

四川易点慧电子商务抖音小店:安全可靠,购物新选择

在数字化浪潮席卷全球的今天&#xff0c;电子商务已成为人们生活中不可或缺的一部分。四川易点慧电子商务抖音小店作为新兴的电商平台&#xff0c;以其安全可靠、便捷高效的特点&#xff0c;逐渐赢得了广大消费者的青睐。今天&#xff0c;就让我们一起走进四川易点慧电子商务抖…

前端项目部署后,如何提示用户版本更新

目录 前言解决方案1、public目录下新建manifest.json2、写入当前时间戳到manifest.json3、检查版本更新4、woker线程5、入口文件引入 可能出现的问题好书推荐 前言 项目部署上线后&#xff0c;特别是网页项目&#xff0c;提示正在操作系统的用户去更新版本非常 important。一般…

【开发环境搭建篇】Redis客户端安装和配置

作者介绍&#xff1a;本人笔名姑苏老陈&#xff0c;从事JAVA开发工作十多年了&#xff0c;带过大学刚毕业的实习生&#xff0c;也带过技术团队。最近有个朋友的表弟&#xff0c;马上要大学毕业了&#xff0c;想从事JAVA开发工作&#xff0c;但不知道从何处入手。于是&#xff0…

UniTask 异步任务

文章目录 前言一、UniTask是什么&#xff1f;二、使用步骤三、常用的UniTask API和示例1.编写异步方法2.处理异常3.延迟执行4.等待多个UniTask或者一个UniTas完成5.异步加载资源示例6.手动控制UniTask的完成状态7.UniTask.Lazy延迟任务的创建8.后台线程切换Unity主线程9.不要返…

力扣爆刷第102天之hot100五连刷96-100

力扣爆刷第102天之hot100五连刷96-100 文章目录 力扣爆刷第102天之hot100五连刷96-100一、136. 只出现一次的数字二、169. 多数元素三、75. 颜色分类四、31. 下一个排列五、287. 寻找重复数 一、136. 只出现一次的数字 题目链接&#xff1a;https://leetcode.cn/problems/sing…

C语言字符函数与字符串函数:编织文字的舞会之梦(下)

欢迎来到白刘的领域 Miracle_86.-CSDN博客 系列专栏 C语言知识 先赞后看&#xff0c;已成习惯 创作不易&#xff0c;多多支持&#xff01; 目录 七、strncpy的使用以及模拟实现 八、strncat的使用以及模拟实现 九、strncmp的使用以及模拟实现 十、strstr的使用以及模拟…

设计模式之抽象工厂模式解析

抽象工厂模式 1&#xff09;问题 工厂方法模式中的每个工厂只生产一类产品&#xff0c;会导致系统中存在大量的工厂类&#xff0c;增加系统的开销。 2&#xff09;概述 a&#xff09;产品族 和 产品等级结构 产品等级结构&#xff1a;产品的继承结构&#xff1b; 产品族&…

华为ensp中ospf基础 原理及配置命令(详解)

CSDN 成就一亿技术人&#xff01; 作者主页&#xff1a;点击&#xff01; ENSP专栏&#xff1a;点击&#xff01; CSDN 成就一亿技术人&#xff01; ————前言———— OSPF 的全称是 Open Shortest Path First&#xff0c;意为“开放式最短路径优先”。是一种内部网关协…

银行OA系统|基于SpringBoot架构+ Mysql+Java+ B/S结构的银行OA系统设计与实现(可运行源码+数据库+设计文档)

推荐阅读100套最新项目 最新ssmjava项目文档视频演示可运行源码分享 最新jspjava项目文档视频演示可运行源码分享 最新Spring Boot项目文档视频演示可运行源码分享 2024年56套包含java&#xff0c;ssm&#xff0c;springboot的平台设计与实现项目系统开发资源&#xff08;可…

Occupancy 训练策略

损失函数 Dice Loss Dice Loss 是一种用于图像分割的损失函数&#xff0c;其灵感来自于Dice 系数&#xff0c;是一种衡量两个样本相似度的方法。Dice 系数定义为&#xff1a; Dice 系数 2 * TP / (2 * TP FP FN) 其中&#xff1a; TP&#xff1a;预测为正且真实值为正的像…

网易有道 3/22

一面还比较常规&#xff0c;二面真的是压力爆炸&#xff0c;还是感觉自己太菜了 一面计网部分直接就是垮了&#xff0c;二面的话面试官水平很高&#xff0c;根本就和我不是一个级别的&#xff0c;三言两语就知道了我的项目大致情况&#xff0c;然后不断拷打 项目问完了又问了…

OpenGL学习笔记【3】—— GLAD配置

一、为什么用GLAD 由于OpenGL驱动版本众多&#xff0c;它大多数函数的位置都无法在编译时确定下来&#xff0c;需要在运行时查询。所以任务就落在了开发者身上&#xff0c;开发者需要在运行时获取函数地址并将其保存在一个函数指针中供以后使用。取得地址的方法因平台而异&…

超声波清洗机是用来干什么的?好用眼镜超声波清洗机不能错过

在快节奏的现代生活中&#xff0c;每一项提高效率和清洁效果的技术都值得我们关注。超声波清洗机就是这样一种技术的完美体现&#xff0c;它通过高频声波在液体中产生微小的气泡&#xff0c;这些气泡在压力作用下迅速爆炸&#xff0c;产生的强大冲击力能够深入物品的微小缝隙&a…

阿里云权益中心2024年五大上云优惠权益解析,助力用户优惠上云

上云首选&#xff0c;普惠好价&#xff0c;2024年阿里云通过权益中心为个人和企业用户提供五大上云优惠权益&#xff0c;为开发者和企业提供多款新老同享、续费同价产品&#xff1b;超150款免费试用产品&#xff1b;初创企业最高可得最低3500元&#xff0c;最高100万上云抵扣金…

Docker 安装 Skywalking以及UI界面

关于Skywalking 在现代分布式系统架构中&#xff0c;应用性能监控&#xff08;Application Performance Monitoring, APM&#xff09;扮演着至关重要的角色。本文将聚焦于一款备受瞩目的开源APM工具——Apache Skywalking&#xff0c;通过对其功能特性和工作原理的详细介绍&am…

Unity UGUI之Toggle基本了解

在Unity中&#xff0c;Toggle一般用于两种状态之间的切换&#xff0c;通常用于开关或复选框等功能。 它的基本属性如图&#xff1a; 其中&#xff0c; Interactable&#xff08;可交互&#xff09;&#xff1a;指示Toggle是否可以与用户交互。设置为false时&#xff0c;禁用To…

Leetcode 70.爬楼梯

心路历程&#xff1a; 这道题是之前学院的一道复试题&#xff0c;大家都没怎么刷过算法题&#xff0c;只记得当年凭借几次试错自己把这道题做出来了&#xff0c;当时也不知道动态规划之类的。 正常来讲&#xff0c;这种找不到循环结构的题一般都是递归解决。 注意的点&#x…

Java语法学习八之认识String类

String类的重要性 在C语言中已经涉及到字符串了&#xff0c;但是在C语言中要表示字符串只能使用字符数组或者字符指针&#xff0c;可以使用标准库提供的字符串系列函数完成大部分操作&#xff0c;但是这种将数据和操作数据方法分离开的方式不符合面相对象的思想&#xff0c;而…

使用 Pytorch 和 Rasterio 的自定义地理空间数据加载器

地理空间数据在从遥感和城市规划到环境监测和灾害管理的各个领域发挥着至关重要的作用。在处理机器学习任务的地理空间数据时,准备自定义数据加载器对于有效加载、预处理和增强数据而不丢失其属性至关重要,特别是当输入图像具有超过 3 个波段时。 Rasterio确实是一个专门为有…