《深入解析 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.mzph.cn/news/762776.shtml

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

相关文章

【复杂网络建模】——建模工具Matlab入门

目录 一、认识MATLAB 二、认识工具箱 三、基本操作和函数 3.1 算术操作符 3.2 数学函数 3.3 矩阵操作 3.4 索引和切片 3.5 逻辑操作 3.6 控制流程 3.7 数据输入输出 四、变量和数据类型 4.1 数值类型 4.2 整型 4.3 复数 4.4 字符串 4.5 逻辑类型 4.6 结构体&a…

【数据结构】顺序表的实现

文章目录 **线性表(linear)&#xff1a;****顺序表****下列是需要实现的接口(Seqlist.h)****顺序表的初始化****顺序表的插入数据****顺序表的扩容(为插入数据提供保障)****顺序表的尾插****顺序表的头插****顺序表的删除数据****顺序表的尾删****顺序表的头删****查找指定位置…

SpringBoot+Redis实现分布式WebSocket

什么是分布式WebSocket&#xff1f; 是指在分布式系统架构中实现WebSocket的通信机制&#xff0c;它允许在不同的服务器节点之间共享和同步WebSocket会话状态&#xff0c;从而实现跨多个服务器的实时消息传递。 在分布式环境中实现WebSocket的挑战主要包括以下几点&#xff1a…

头条网盘拉新项目该怎么选择授权

作为十二月份首发上线的项目——头条网盘拉新。一经上线就受到很多想要做这行业人的关注&#xff0c;光是佣金已经是业内比较高的了&#xff01;每拉新一位新用户就可以获取到价格为9元一单的佣金&#xff0c;拉失活用户也可以获取价格为4元的佣金&#xff0c;推广方式和其他网…

基于python+vue云上水果超市的设计与实现flask-django-php-nodejs

本论文的主要内容包括&#xff1a; 第一&#xff0c;研究分析当下主流的web技术&#xff0c;结合超市日常管理方式&#xff0c;进行云上水果超市的数据库设计&#xff0c;设计云上水果超市功能&#xff0c;并对每个模块进行说明。 第二&#xff0c;陈列说明该系统实现所采用的架…

财报解读:“高端化”告一段落,华住开始“全球化”?

2023年旅游业快速复苏&#xff0c;全球酒店业直接受益&#xff0c;总体运营指标大放异彩&#xff0c;多数酒店企业都实现了营收上的明显增长&#xff0c;身为国内龙头的华住也不例外。 3月20日晚&#xff0c;华住集团发布2023年四季度及全年财报。整体实现扭亏为盈&#xff0c;…

飞跃前端瓶颈:技术进阶指南精华篇

引言&#xff1a; 在互联网的快车道上&#xff0c;前端技术日新月异。对于前端工程师而言&#xff0c;技术水平达到一定高度后&#xff0c;往往会遭遇成长的天花板。本文将探讨如何识别并突破这些技术瓶颈&#xff0c;分享实用的进阶策略和实践案例。 一、技术等级概览&#xf…

WP免费主题2个分享给需要的人

免费wordpress主题 粉色高端大气的免费wordpress主题&#xff0c;用免费的主题也可以搭建wordpress网站。 https://www.wpniu.com/themes/12.html 免费WP模板 绿色清爽的wordpress建站模板&#xff0c;用免费的WP模板也可以搭建出精美网站。 https://www.wpniu.com/themes/…

图解 LFU 缓存淘汰算法以及在 Redis 中的应用(附带个人完整代码实现)

文章目录 LFU 算法理论介绍算法实现数据结构查询操作插入/更新操作 Redis 缓存淘汰算法缓存污染难题Redis LFU缓存淘汰策略 本篇博客的主要内容&#xff1a; 以图解的方式&#xff0c;介绍 LFU 算法的一种实现&#xff1b;介绍 LFU 算法在 Redis 中的应用。 LFU 算法 理论介…

人工智能驱动客服行业变革:迈向智能化、自动化与数据驱动的新纪元

在科技浪潮的推动下&#xff0c;人工智能&#xff08;AI&#xff09;已经逐渐渗透到各个行业之中&#xff0c;其中客服行业正经历着一场前所未有的变革。AI技术的引入&#xff0c;为客服行业注入了智能化、自动化和数据驱动的新动力&#xff0c;引领着客服行业迈向一个崭新的时…

数据仓库的数据处理架构Lambda和Kappa

1.数据仓库 数据仓库(Data Warehouse),简写DW。顾名思义,数据仓库是一个很大的数据存储集合,为企业分析性报告和决策支持而创建,是对多元业务数据的筛选与整合,具备一定的BI能力,主要用于企业的数据分析、数据挖掘、数据报表等方向,指导业务流程改进、监视时间、成本、…

[Linux开发工具]——make/Makefile的使用

Linux项目自动化构建工具——make/Makefile 前言&#xff1a;一、背景二、认识make和makefile2.1 创建Makefile文件2.2 创建test.c文件&#xff0c;并打开Makefile2.3 我们想要test.c生成test文件2.4 编译2.5 清理可执行文件 三、理解依赖关系和依赖方法3.1 依赖关系3.2 依赖方…

发展的挺快的Rust

C 可能在将来会逐步的退出历史舞台 Rust 在linux 上出现的频次越来越多了 新的语言和重构带来了更方便快捷的体验 好玩的命令集合 https://github.com/ibraheemdev/modern-unix.git 这速度&#xff0c;这花活儿

07、面向对象进阶

面向对象进阶 文章目录 面向对象进阶static关键字继承多态包final抽象类与抽象方法接口拓展 内部类成员内部类静态内部类局部内部类匿名内部类 static关键字 static表示静态&#xff0c;是Java中的一个修饰符&#xff0c;可以修饰成员方法&#xff0c;成员变量 静态变量是随着…

C++ Qt开发:QProcess进程管理模块

Qt 是一个跨平台C图形界面开发库&#xff0c;利用Qt可以快速开发跨平台窗体应用程序&#xff0c;在Qt中我们可以通过拖拽的方式将不同组件放到指定的位置&#xff0c;实现图形化开发极大的方便了开发效率&#xff0c;本章将重点介绍如何运用QProcess组件实现针对进程的控制管理…

mysqly索引(explain 执行计划)

关键词 执行计划 EXPLAIN 语句查看mysql 优化后的语句 show warnings;EXPLAIN 执行后&#xff0c;各列的含义 要点&#xff1a; select_type 如何查询 表type 如何查询 行key 如何使用 索引key_len 索引 使用多少rows 行 预计使用多少extra 表 的额外信息 1.id id列的编…

Spring之@Qualifier注解

场景重现 当我们注入的依赖存在多个候选者,我们得使用一些方法来筛选出唯一候选者,否则就会抛出异常 demo演示 创建接口Car,以及两个实现其接口的bean public interface Car { }Component public class RedCar implements Car { }Component public class WhiteCar implemen…

荒野大嫖客2找不到emp.dll解决办法

首先我参考了几篇文章尝试来解决这个问题&#xff0c; s霍格沃兹emp.dll文件丢失要怎么处理&#xff1f;快速修复emp.dll的方法-CSDN博客 使用电脑自带的修复工具&#xff0c;直接按键盘的winr&#xff0c;然后输入cmd&#xff0c;打开小黑板&#xff0c;我们再在小黑板上输入…

MQTT 简介

MQTT 简介 MQTT 是非常简单的协议&#xff0c;最初由 IBM 的两位工程师 Andy Stanford-Clark 以及 Arlen Nipper 在 1999 年为监控输油管道设计的。它被设计的场景就是有限的带宽、轻量级以及很小的耗电量&#xff0c;在那个时候&#xff0c;卫星宽带就是那么小&#xff0c;且…

【理解机器学习算法】之Clustering算法(Agglomerative Clustering)

聚合聚类(Agglomerative Clustering)是一种层次聚类算法&#xff0c;通过逐步合并或“聚集”它们来构建嵌套聚类。这种方法采用自底向上的方式构建聚类层次&#xff1a;它从将每个数据点作为单个聚类开始&#xff0c;然后迭代合并最接近的聚类对&#xff0c;直到所有数据点合并…