Swift知识点(二)

6. 闭包表达式与闭包

闭包表达式(Closure Expression)

闭包表达式是一种在简短行内就能写完闭包的语法

也就是,闭包表达式,只是一种简洁、快速实现闭包的语法

Swift 的闭包表达式拥有简洁的风格,鼓励在常见场景中实现简洁,无累赘的语法
常见的优化包括:

  • 利用上下文推断形式参数和返回值的类型;
  • 单表达式的闭包可以隐式返回;
  • 简写实际参数名;
  • 尾随闭包语法。

函数的定义

  • 可以通过func定义一个函数
func sum(_ v1: Int, _ v2: Int) -> Int
{v1 + v2
}

可以理解,就是前面说的,函数也是闭包
闭包就是可以捕获上下文中的常量或变量

  • 也可以通过闭包表达式定义一个函数
    闭包表达式有如下的一般形式:
{ (parameters) -> (return type) instatements
}
等价于:
{(参数列表) -> 返回值类型 in函数体代码
}

闭包表达式语法能够使用常量形式参数、变量形式参数和输入输出形式参数,但不能提供默认值
可变形式参数也能使用,但需要在形式参数列表的最后面使用。
元组也可被用来作为形式参数和返回类型。

闭包的函数整体部分由关键字 in 导入,这个关键字表示:
闭包的形式参数类型和返回类型定义已经完成,并且闭包的函数体即将开始。

通常我们说的闭包更多指的是闭包表达式,也就是没有函数名称的代码块,因此也叫做匿名闭包。

var fn = {(v1: Int, v2: Int) -> Int inreturn v1 + v2
}
调用:fn(10, 20)或者
{(v1: Int, v2: Int) -> Int inreturn v1 + v2
}(10, 20)
闭包表达式的简写
函数定义:
func exec(v1: Int, v2: Int, fn:(Int, Int) -> Int){print(fn(v1, v2))
}调用方法一:
exec(v1: 10, v2: 20, fn:{(v1: Int, v2: Int) -> Int inreturn v1 + v2
})

由于类型可以被推断出来,因此,类型可以不写。
因为类型可以不写,返回值类型不写,那么->也可以省略
简写为:

调用方法二:

exec(v1: 10, v2: 20, fn:{v1, v2 inreturn v1 + v2
})

单个表达式闭包能够通过从它们的声明中删掉 return 关键字来隐式返回它们单个表达式的结果

调用方法三:

exec(v1: 10, v2: 20, fn:{v1, v2 inv1 + v2
})

在闭包里面,可以使用 $0、$1分别代表闭包表达式里面第1个参数和第2个参数,因此,可以简化为:

调用方法四:

exec(v1: 10, v2: 20, fn:{ $0 + $1 })

其实这次是省略了v1, v2 in。里面没有了参数列表,也就不需要in做区分,因此,前面可以省略

更简化的方法:

exec(v1: 10, v2: 20, fn: + )

简直不是人。。。

其他优化例子
reversedNames = names.sorted(by: { (s1: String, s2: String) -> Bool in return s1 > s2 } )
reversedNames = names.sorted(by: { s1, s2 in return s1 > s2 } )
reversedNames = names.sorted(by: { s1, s2 in s1 > s2 } )
reversedNames = names.sorted(by: { $0 > $1 } )

这里, $0 和 $1 分别是闭包的第一个和第二个 String 实际参数。

尾随闭包

如果函数的最后一个参数为闭包,则该闭包也被称为尾随闭包
如果你需要将一个很长的闭包表达式作为函数最后一个实际参数传递给函数且闭包表达式很长,使用尾随闭包将增强函数的可读性。
尾随闭包是一个被书写在函数形式参数的括号外面(后面)的闭包表达式,但它仍然是这个函数的实际参数。

  func someFunctionThatTakesAClosure(closure:() -> Void){}someFunctionThatTakesAClosure({})someFunctionThatTakesAClosure() {}

还是上面的例子:
可简写为:
reversedNames = names.sorted() { $0 > $1 }
如果闭包表达式作为函数的唯一实际参数传入,而你又使用了尾随闭包的语法,那你就不需要在函数名后边写圆括号了:
reversedNames = names.sorted { $0 > $1 }

真是简写的没法了

闭包(Closure)

定义: 一个函数和它所捕获的变量\常量环境组合起来,称为闭包

函数的定义有两种方式:func和闭包表达式

  • 一个函数,一般指的是:定义在函数内部的函数(嵌套函数)
  • 一般它捕获的是外层函数的局部变量\常量
//定义Fn式一个函数:参数Int,返回值Int
typealias Fn = (Int) -> Int//定义一个函数:函数名:getFn,参数:空,返回值类型Fn
func getFn() -> Fn{//局部变量var num = 0//内部方法func plus(_ i: Int) -> Int {num += ireturn num}//getFn函数的返回值return plus
}//返回的plus和num形成了闭包(也就是plus+num就称为闭包)var fn = getFn()
print(fn(1))
print(fn(2))
print(fn(3))
print(fn(4))
打印结果:
1
3
6
10

按说var fn = getFn()执行完毕后,局部变量num就被释放了。后续fn(1)此时再使用num,应该报错
但,其实是没错的,因为num已经被存储在堆空间了,因此没有被释放

并且,四次调用,访问的都是同一块堆上的内存空间num,因此,num的值被保留下来了(可以连加)

问:为啥num被存储到堆上面呢?

因为,返回出去的函数plus,里面用到了上层函数(getFn)的局部变量,就会分配堆空间,捕获该局部变量

并且,调用一次getFn()函数,就会分配一个单独的堆空间去存储num

是将num的值0,存储在堆空间。栈空间的num=0已经被回收了

var fn1 = getFn()
var fn2 = getFn()
print(fn1(1))
print(fn2(2))
print(fn1(3))
print(fn2(4))
打印:
1
2
4
6

可以把闭包想象成是一个类的实例对象

  • 内存在堆空间
  • 捕获的局部变量\常量就是对象的成员(存储属性)
  • 组成闭包的函数就是类内部定义的方法
//类
class Closure{var num = 0func plus(_ i: Int) -> Int {num += ireturn num}
}var cs1 = Closure()
var cs2 = Closure()
print(cs1.plus(1))
print(cs1.plus(2))
print(cs2.plus(3))
print(cs2.plus(4))
打印:
1
3
3
7

闭包能够捕获和存储定义在其上下文中的任何常量和变量的引用,这也就是所谓的闭合并包裹那些常量和变量,因此被称为“闭包”

全局和内嵌函数,实际上是特殊的闭包。
闭包符合如下三种形式中的一种:

  • 全局函数是一个有名字但不会捕获任何值的闭包;(全局函数是一个闭包)
  • 内嵌函数是一个有名字且能从其上层函数捕获值的闭包;(内嵌函数也是一个闭包)
  • 闭包表达式是一个轻量级语法所写的可以捕获其上下文中常量或变量值的没有名字的闭包

捕获值

一个闭包能够从上下文捕获已被定义的常量和变量。即使定义这些常量和变量的原作用域已经不存在,闭包仍能够在其函数体内引用和修改这些值。

函数和闭包都是引用类型
无论你什么时候赋值一个函数或者闭包给常量或者变量,你实际上都是将常量和变量设置为对函数和闭包的引用

自动闭包

有一个如下的比较函数:如果v1>0,则返回v1,否则返回v2

func getFirstPositive(_ v1: Int, _ v2: Int) -> Int {return v1 > 0 ? v1 : v2
}print(getFirstPositive(1, 2))
print(getFirstPositive(-1, 2))
打印:
1
2

当v2传入的是一个函数(参数为空,返回值为Int)的时候,即使v1>0,第二个参数也被调用了一次

func getNumber() -> Int {let a = 10let b = 11print("----")return a + b
}
print(getFirstPositive(1, getNumber()))
打印:
----
1

已经判断出v1 > 0了,其实后面的函数没必要执行

为了提高性能,可以将函数getFirstPositive的第二个参数修改为函数:

func getFirstPositive2(_ v1: Int, _ v2: () -> Int) -> Int {return v1 > 0 ? v1 : v2()
}print(getFirstPositive2(1, {print("1111")return 10
}))
print(getFirstPositive2(-1, {print("22222")return 10
}))
打印:
1
22222
10

可以看出,当v1>0的时候,不会执行第二个参数里面的函数
调用的时候,可以简写调用:

print(getFirstPositive2(10, {20}))
print(getFirstPositive2(-10){20})
打印:
10
20

看起来不易读

因此,swift提供了 自动闭包

func getFirstPositive3(_ v1: Int, _ v2: @autoclosure () -> Int) -> Int {return v1 > 0 ? v1 : v2()
}print(getFirstPositive3(1, 20))
print(getFirstPositive3(-1, 10))
打印:
1
10

如上所示,调用的时候,第二个参数闭包,传入的是一个Int值
其实,内部是将第二个参数20,修改为了{20}

语法糖

关于@autoclosure的注意点:

  • @autoclosure仅支持 () -> T格式的参数(无参,有返回值)
  • @autoclosure并非只支持最后一个参数
  • 空合并运算符 ?? 使用了@autoclosure技术
  • 有@autoclosure与无@autoclosure,也可以构成函数的重载

逃逸闭包

在 Swift 中,闭包是引用类型,当它被作为一个函数参数传递给一个函数时,这个函数会持有这个闭包的引用。大多数情况下,函数会立即执行闭包并返回,也就是说闭包的生命周期与函数的生命周期一致,我们称这样的闭包为非逃逸闭包。

但有一些情形下,函数在返回之后,闭包仍然被保留而未被执行,例如将闭包储存为函数外部的属性,或者在另外一个异步执行的闭包中被调用,我们称这样的闭包为逃逸闭包。

逃逸闭包必须在闭包参数名前标注 @escaping 关键字。这在使用闭包时会使你更清晰地理解闭包是如何和函数进行交互的。

func someFunctionWithEscapingClosure(completionHandler: @escaping () -> Void) {someArray.append(completionHandler)
}

在 this example example中, completionHandler 闭包参数有 @escaping 注解,意味着这个闭包是可逃逸的。因为闭包被加入到 someArray 数组中,并不会立即被执行,而是会在函数返回之后的某个时刻执行。

逃逸闭包在某些场景下非常有用,比如异步调用,延时调用,还有存储为全局变量或类的实例变量等场景。不过需要注意的一点是,由于逃逸闭包的生命周期可能超过函数本身,可能会引起 strong reference cycle 强引用环,不恰当的使用可能导致内存问题。为了解决这个问题,你需要在闭包中显式地使用 [unowned self] 或者 [weak self] 来避免强引用环。

闭包作为一个实际参数传递给一个函数,并且在函数返回后才被调用的时候,我们就说这个闭包从函数中逃逸了
在 Swift 中,我们需要在参数前加上 @escaping 关键字声明闭包是逃逸的。
如常见的异步操作就需要使用逃逸闭包。

swift中闭包与oc中的block有什么区别与相同点?

闭包(Closure)在 Swift 中,与 Objective-C 中的 blocks 形式上有相似处,但在实现、语法以及使用上有很多不同之处。

相似点:

  1. 匿名函数块:都是一种能捕获和存储上下文环境的匿名函数块。可以当作参数在函数之间传递,并且可以在需要的时候调用。

  2. 变量捕获:两者都能在其内部访问和修改外部变量。

区别:

  1. 语法:Swift 的闭包语法更为简洁清晰,通过对参数和返回类型的尾随闭包语法,类型推断等特性,使得闭包的定义及使用变得更为简洁,提高了代码的可读性。

Objective-C 的 block 语法要求声明返回类型,而 Swift 的闭包则不需要声明(但可以声明,这取决于具体用途)。

例如,在 Swift 中,你可以这样声明一个闭包:

let swiftClosure = { (number: Int) -> String inreturn "Swift closure: \(number)"
}

在 Objective-C 中,你需要这样声明一个 block:

NSString *(^objcBlock)(NSInteger) = ^(NSInteger number) {return [NSString stringWithFormat:@"Objective-C block: %ld", number];
};
  1. 内存管理:Objective-C 的 block 在捕获外部变量时需要注意 __block 、 __weak 标识符的使用,否则很容易造成循环引用。而 Swift 的闭包提供了 [unowned self] 和 [weak self] 语法来避免循环引用。

  2. 可访问性:Swift 的闭包能访问其作用域内的所有常量和变量,这包括其他函数中的参数和变量,这点在 Objective-C 的 block 中不完全支持。

  3. 可修改可逃逸性:在 Swift 中,闭包默认为不可逃逸,必要时可以使用 @escaping 关键字修改。Objective-C 的 block 默认为可逃逸,而且不能修改。

  4. 运行时处理:Objective-C 的 blocks 在运行时声明,Swift 的闭包在编译时声明。

总结来说,虽然 Swift 中的闭包和 Objective-C 中的 blocks 在面向函数编程的功能上有诸多相似之处,但在语法清晰度、内存管理、作用域访问性、可逃逸性等方面,Swift 的闭包具有较大优势。这些优点使得闭包适应更多的编程场景,提高了代码的可读性和可维护性。


8. 属性

Swift中,跟实例相关的属性可以分为两大类:存储属性、计算属性

存储属性(Stored Property)

类似于成员变量
存储在实例的内存中
结构体、类可以定义存储属性
枚举不可以定义存储属性

在创建结构体或类的实例时,必须为所有的存储属性设置一个合适的初值
可以在初始化器里设置,也可以为属性分配一个默认值

struct Circle{//存储属性var radius: Double//计算属性var diameter: Double{set{radius = newValue / 2}get{radius * 2}}
}var circle = Circle(radius: 5)
print(circle.radius)//5
print(circle.diameter)//10
//只需要设置radius的值,就可以通过计算属性中get方法确定diameter的值circle.diameter = 12
print(circle.radius)//6
print(circle.diameter)//12
//通过修改计算属性diameter的set方法,修改了radius的方法
对存储属性的懒加载

lazy只能用于var变量,不能用于let常量

lazy的两种用法:
方法一:
lazy var str: String = "Hello"

方法二:使用闭包

lazy var names: NSArray = {let names = NSArray()print("只在首次访问输出")return names
}()

疑问:为何使用闭包可以做到懒加载?

计算属性(Computed Property)

计算属性不是直接存储值,而是提供get和set方法
get:用来取值,封装取值的过程
set:用来设值,封装设值得过程

计算属性是用来间接修改其他属性值的

本质就是方法(函数)
不占用实例的内存
结构体、枚举、类都可以定义计算属性

上例中,set方法传入的新值默认叫做newValue
也可以自定义:

set(newName){radius = newName / 2
}

只读计算属性:只有get,没有set

struct Circle{//存储属性var radius: Double//计算属性var diameter: Double{get{radius * 2}}
}可以简写:struct Circle{//存储属性var radius: Double//计算属性var diameter: Double{radius * 2}
}

定义计算属性只能使用var,不能使用let
原因是,计算属性的值可能会发生变化(即使是只读计算属性)

属性监听器

计算属性本身就有监听,不需要考虑为其添加监听的事
对于存储属性,可以通过willSet和didSet来监听属性的改变
举个例子:
在这里插入图片描述
需要注意的是: willSet和didSet在初始化过程中是不会被调用的
也就是,在上面例子中,第11行初始化的时候不会调用willSet和didSet


9. 方法(Method)

定义在类、结构体、枚举内部的函数,被称为方法
方法又分为:实例方法和类型方法

  • 实例方法(Instance Methods):通过实例调用
  • 类型方法(Class Methods):通过类型调用。用staticclass关键字定义

在类型方法中调用self,self就代表类
在实例方法中调用self,self就代表实例

下标(subscrip)

使用subscrip可以给任意类型(枚举、结构体、类)增加下标功能
subscrip的语法类似实例方法、计算属性,其本质就是方法(函数)
举一个下标的例子:

class Point {var x = 0.0, y = 0.0//类似方法定义,只是没有func关键字,没有方法名subscript(index: Int) -> Double {//类似计算属性set {if(index == 0){x = newValue}else if(index == 1){y = newValue}}get {if index == 0{return x}else if(index == 1){return y}return 0}}
}var p = Point()
p[0] = 11.1
p[1] = 22.2
print(p.x)
print(p.y)
print(p[0])
print(p[1])
打印:
11.1
22.2
11.1
22.2

使用subscrip后,就可以直接对对象像数组一样操作,取p[0]、p[1]了

subscrip定义的返回值类型,决定了get方法的返回值类型,也决定了newValue的值
首先,返回值类型与get出去的类型,肯定是保持一致的。

subscrip可以没有set方法,但是必须有get方法

取值嘛

subscrip如果只有get方法,则get可以省略(语法糖)

上述下标是对象方法
下标也可以写成类方法:

class Sum {static subscript(v1: Int, v2: Int) -> Int{return v1 + v2}
}
//下标访问,使用[],传两个参数,用,隔开即可
print(Sum[10, 20])//30

在 Swift 中,静态下标是一种语法糖,它让我们可以用中括号 [] 访问或设置类型的特定值,而不需要调用一个方法或访问一个属性。

继承(Inheritance)

  • 值类型不支持继承,只有类支持继承

  • 在oc中,NSObject、NSProxy是基类,其余都继承NSObject

  • 在swift中,只要是不继承其他类的,都称为基类

  • swift是单继承

有继承就有重写:

重写

子类可以重写父类的:下标、方法、属性,重写必须调用override
方法重写很正常,OC中常用的
下标的本质就是方法,因此也可以重写
属性,有计算属性,本质也是方法,因此,也可以重写

子类重写父类的方法,当使用子类调用该方法的时候,调用子类的方法
如果在执行子类方法的同时,还想让父类方法也执行,那么,需要调用super.methd()

实例方法、下标重写
class Animal {func speak(){print("Animal speak")}subscript(index: Int) -> Int {return index}
}class Cat: Animal {override func speak() {super.speak()print("Cat speak")}//加overrideoverride subscript(index: Int) -> Int {//调用父类的下标调用方法super[index]return super[index] + 1}
}//多态:父类类型指向子类类型
//animal虽然是父,但其实子类Cat里面东西多(继承animal的东西+自己的东西)
let animal = Cat()
animal.speak()
print(animal[6])
打印:
Animal speak
Cat speak
7
类方法、下标重写
  • class修饰的类方法、下标,可以重写
  • static修饰的类方法、下标,不允许重写

这也是被class修饰的类方法、下标与被static修饰的类方法、下标的区别

属性重写
  • 子类可以将父类的属性(不管是存储属性,还是计算属性),重写为计算属性
  • 子类不可以将父类重写为存储属性
  • 只能重写var属性,不能重写let属性
  • 重写时,属性名、类型要一致
  • 子类重写后的属性权限 不能小于 父类属性的权限

父类只读 --> 重写后的子类 可以为读写属性
父类读写 --> 重写后的子类 必须为读写属性
不可以将一个继承来的读写属性重写为一个只读属性

存储属性、计算属性都可以重写
属性重写提供了:get、set、willSet、didSet四个关键字对属性进行重写

一个重写计算属性的例子:

import Foundationclass Car {var speed:Int {//计算属性set {print("car-set")}get {return 10}}
}class Tank: Car {override var speed:Int {//计算属性set {print("tank-set")}get {return 1}}
}let tank = Tank()
print(tank.speed)结果:1

Tank继承Car,本来car的speed有一个初值10,现在tank的初值变为了1


看完计算属性的重写,我们再看下存储属性的重写:
在这里插入图片描述报错,那么加个override是否就可以了呢?

在这里插入图片描述依然报错
那么,究竟怎么重写父类的存储属性呢???

一个重写存储属性的例子:

import Foundationclass Car {var speed:Int = 10
}class Tank: Car {override var speed:Int {get {return super.speed}set {print("tank - set")}}
}let tank = Tank()
print(tank.speed)结果: 10

从上面可以看出,重写属性,不管是计算属性还是存储属性,都需要借助get、set
也可以得出:存储属性重写为计算属性

例子2:
在这里插入图片描述

以上都是重新对象属性
对于类型属性:

  • class修饰的计算类型属性可以被子类重写

class不允许修饰存储属性,所以,上面只能是计算类型属性

  • static修饰的类型属性(存储、计算),不可以被子类重写
属性观察器

可以在子类中,为父类属性(除了只读计算属性、let属性)增加属性观察器(也就是willSet、didSet)

那么willSet、didSet如何使用呢?

在这里插入图片描述

final

如果方法、属性、下标被final修饰,则不允许被子类重写
如果类被final修饰,则不允许被继承


10. 多态原理、初始化、可选链

多态

let animal = Animal()
//父类指针,指向子类对象,这种称为多态
//animal虽然是父,但其实子类Cat里面东西多(继承animal的东西+自己的东西)
animal = Cat()

struck与class创建的值,调用方法时的区别

从汇编打断点来看:
struct建立的值,调用对应方法的时候,在编译期已经决定调用哪一个方法,是直接调用,调用性能快
class建立的值,调用对应方法的时候,在运行期才决定要调用哪一个方法,是动态调用,调用性能相比struct要弱一点

class对象(堆空间)里面存储的内容:

前8个字节存放:类型信息(类似OC中的isa),其本身是一个指针,指向的是另外一处堆空间,再这个堆空间里面,存有很多类型相关的信息:比如,要调用的方法地址

再8个字节存放:引用计数(retainCount)

剩余的存放:属性信息

遵循8的倍数原则

在这里插入图片描述

var dog1 = Dog()
var dog2 = Dog()

dog1与dog2的前8个字节,内存地址是一样的,也就是类型信息是一样的(只有一份)

从函数调用上,引申出一个问题:

Swift的派发机制

Swift 具有丰富的方法派发机制,这涉及到如何在运行时确定调用哪个方法。主要有以下几种机制:

  1. 直接派发(Direct Dispatch):此类方法调用发生在编译阶段,编译器会直接在调用处"内联"这个函数的实现。这种派发方式最快最高效,因为它避免了函数调用的开销。常见于结构体和枚举的方法派发。

  2. 虚表派发(Table Dispatch,常见于 v-table):这是面向对象语言中最常见的派发方式,Swift 的类方法默认使用这种方式。在这种机制中,如果有一个类 Animal,我们创建了一个子类 Dog 并重写了某个方法,例如 bark() ,在运行时如果子类调用 bark() ,此时会通过虚表找到子类 Dog 中 bark() 方法的地址去调用。

  3. 消息派发(Message Dispatch):在 Swift 中,这主要发生在和 Objective-C 交互的部分,例如使用了 dynamic 关键字或者 @objc 注解的方法。这种机制的弹性最大,但也是最慢的。例如:

@objc dynamic func myMethod() {print("This method uses Objective-C message dispatch.")
}

根据这些派发机制,有几个重要的注意点:

  • 尽可能使用直接派发。这是最快的,并且可以让编译器优化你的代码。只有当你需要使用类的多态特性时,才需要其他的派发机制。

  • 虚表派发能够提供面向对象编程的多态性,但是也增加了一定的开销。如果类或方法是 final 的,那么它们就可以使用直接派发,因为这些类和方法不能被重写。

  • 消息派发是最灵活的,但也是最慢的。一般来说,应避免使用,除非你需要和 Objective-C 的运行时进行交互,或使用一些动态特性。

举个具体的例子,让以上的派发机制更加形象化:

struct MyStruct {func method() {}  // 直接派发,因为是值类型
}class MyClass {func method() {}  // 默认虚表派发,因为是类
}final class MyFinalClass {func method() {}  // 直接派发,因为类是final
}class MyDynamicClass {@objc dynamic func method() {}  // 消息派发,因为 @objc dynamic
}

初始化器

类、结构体、枚举都可以定义初始化器
有两种初始化器:

  • 指定初始化器(designated initializer)
  • 便捷初始化器(convenience initializer)

其他主要知识点:

  • 每个类至少有一个指定初始化器,指定初始化器是类的主要初始化器
  • 默认初始化器:类的指定初始化器
  • 类偏向于少量指定初始化器,一个类通常只有一个指定初始化器
  • 默认初始化器,指定初始化器。当自定义了带参数的指定初始化器后,编译器生成的不带参的初始化器就没了
  • 便捷初始化器,要最终调用指定初始化器。自定义便捷初始化器后,默认初始化器还在

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

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

相关文章

由浅到深认识Java语言(11):封装

该文章Github地址:https://github.com/AntonyCheng/java-notes 在此介绍一下作者开源的SpringBoot项目初始化模板(Github仓库地址:https://github.com/AntonyCheng/spring-boot-init-template & CSDN文章地址:https://blog.c…

MySQL中的日历/时间/时间戳

一,日历 MySQL 使用通常所说的 proleptic 阳历。 每个将日历由朱利安改为阳历的国家在改变日历期间都不得不删除至少10天。 为了了解其运作,让我们看看1582年10月,这是由朱利安日历转换为阳历的第一次: 周一 周二 周三 周四 周五 周六…

单臂路由和三层交换机

目录 一.单臂路由 1.单臂路由的工作原理 2.单臂路由的配置 2.1画出拓扑图 2.2配置PC 2.3配置交换机 2.4配置路由器 2.5测试 二.三层交换机 1.三层交换机的概述 2.三层交换机的配置 2.1画出拓扑图 2.2配置PC 2.3配置二层交换机 2.4配置三层交换机 2.5测试 3.拓展 三.总结 一.…

git提交和回退

目录 一. git 提交二. git commit 后准备回退,尚未 git push三. git add 添加多余文件 撤销操作四. 更改 Git commit 的默认编辑器五. 撤销某个commit的变更六. 回退到之前的commit状态总结: 一. git 提交 git pull # 更新代码 git status # 查看代码状…

ITES | 深圳工业展正运动重磅产品即将亮相

■展会名称: 第二十五届深圳国际工业制造技术及设备展览会(以下简称“深圳工业展”) ■展会日期 2024年3月28日-31日 ■展馆地点 中国深圳国际会展中心(宝安) ■展位号 9号馆F04 2024年深圳工业展(ITES)将于3月28日至31日在深圳宝安国…

springboot多模块

一、demo 1、创建父项目 首先使用 Spring Initializr 来快速创建好一个Maven工程。然后删除无关的文件,只需保留pom.xml 文件。 (1)new Project -> spring initializr快速构建SpringBoot,artifactId为springbootmodules&…

SpringBoot+ElasticSearch实现文档内容抽取、高亮分词、全文检索

需求 产品希望我们这边能够实现用户上传PDF、WORD、TXT之内得文本内容,然后用户可以根据附件名称或文件内容模糊查询文件信息,并可以在线查看文件内容。 一、环境 项目开发环境: 后台管理系统springbootmybatis_plusmysqles 搜索引擎&#…

PTA L2-037 包装机

一种自动包装机的结构如图 1 所示。首先机器中有 N 条轨道,放置了一些物品。轨道下面有一个筐。当某条轨道的按钮被按下时,活塞向左推动,将轨道尽头的一件物品推落筐中。当 0 号按钮被按下时,机械手将抓取筐顶部的一件物品&#x…

JVM第八讲:GC - Java 垃圾回收基础知识

GC - Java 垃圾回收基础知识 本文是JVM第八讲, Java 垃圾回收基础知识。垃圾收集主要是针对堆和方法区进行;程序计数器、虚拟机栈和本地方法栈这三个区域属于线程私有的,只存在于线程的生命周期内,线程结束之后也会消失&#xff0…

最“原始”的收音机长啥样?

同学们大家好,今天我们继续学习杨欣的《电子设计从零开始》,这本书从基本原理出发,知识点遍及无线电通讯、仪器设计、三极管电路、集成电路、传感器、数字电路基础、单片机及应用实例,可以说是全面系统地介绍了电子设计所需的知识…

hadoop基本概念

一、概念 Hadoop 是一个开源的分布式计算和存储框架。 Hadoop 使用 Java 开发,所以可以在多种不同硬件平台的计算机上部署和使用。其核心部件包括分布式文件系统 (Hadoop DFS,HDFS) 和 MapReduce。 二、HDFS 命名节点 (NameNode) 命名节点 (NameNod…

k8s入门到实战(十四)—— Helm详细介绍及使用

Helm 使用 Helm 是一个 k8s 应用的包管理工具,类似于 Ubuntu 的 APT 和 CentOS 中的 YUM。 Helm 使用 chart 来封装 k8s 应用的 yaml 文件,我们只需要设置自己的参数,就可以实现自动化的快速部署应用。 Helm 通过打包的方式,支…

Nacos 配置管理-应用于分布式系统

** Nacos 配置管理-应用于分布式系统 ** 目录: 一、Nacos 配置管理-应用于分布式系统-微服务创建 1.1 发布配置 ( nacos-1.1.3 ) 1.2 打开 idea 创建一个父 Maven 工程 nacos_config 工程,和两个子模块(service1, service2 )…

MySQL进阶——锁

锁 概述 全局锁 表级锁 行级锁 概述 同Java中的锁。目的是为了保证数据一致性、完整性,提高并发安全、控制访问顺序。 分类 在MySQL中,根据锁的粒度分,分为以下3种: 全局锁:锁定数据库种的所有表 表级锁&#…

React中 类组件 与 函数组件 的区别

类组件 与 函数组件 的区别 1. 类组件2. 函数组件HookuseStateuseEffectuseCallbackuseMemouseContextuseRef 3. 函数组件与类组件的区别3.1 表面差异3.2 最大不同原因 1. 类组件 在React中,类组件就是基于ES6语法,通过继承 React.component 得到的组件…

后端常问面经之Java集合

HashMap底层原理 HashMap的数据结构: 底层使用hash表数据结构,即数组和链表或红黑树 当我们往HashMap中put元素时,利用key的hashCode重新hash计算出当前对象的元素在数组中的下标 存储时,如果出现hash值相同的key,此…

关于四篇GNN论文的阅读笔记PPT:包括GATNE,AM-GCN,HGSL和coGSL

关于四篇GNN论文的阅读笔记PPT:包括GATNE,AM-GCN,HGSL和coGSL 前言GATNEAM-GCNHGSLcoGSL 前言 这里的PPT主要是在跟Graph Transformer一起的: 【图-注意力笔记,篇章1】Graph Transformer:包括Graph Trans…

2024第六届环境科学与可再生能源国际会议能源 (ESRE 2024) 即将召开!

2024第六届环境科学与可再生能源国际会议 能源 (ESRE 2024) 即将举行 2024 年 6 月 28 日至 30 日在德国法兰克福举行。ESRE 2024 年 旨在为研究人员、从业人员和专业人士提供一个论坛 从工业界、学术界和政府到研究和 发展,环境科学领域的专…

网站首屏优化 | 提升首屏的几个简单手段

前言 在用户反馈中,诸如「白屏」、「加载慢」、「打不开」等关键词频繁出现,这些词汇直观地揭示了应用程序在实际操作中遭遇的技术挑战。 根据 Statista 的报告,应用加载速度的延迟超过 3 秒,用户流失率可增加 53% 。此外&#…

Linux :环境基础开发工具

目录: 1. Linux 软件包管理器 yum 1. 什么是软件包 2. 查看软件包 3. 如何安装软件 4. 如何卸载软件 2. Linux开发工具 1. Linux编辑器-vim的基本概念 2. vim使用 3. vim的基本操作 4. vim正常模式命令集 5. vim末行模式命令集 6. 简单vim配置 3. Linux编译器-gcc/…