【Kotlin精简】第3章 类与接口

news2024/11/25 13:48:14

1 简介

Kotlin类的声明和Java没有什么区别,Kotlin中,类的声明也使用class关键字,如果只是声明一个空类,Kotlin和Java没有任何区别,不过定义类的其他成员会有一些区别。实例化类不用写new,类被继承或者重写必须加上 open 关键字,构造器可以写在类后面。类的基本结构:构造函数属性方法
Kotlin接口和Java的抽象类基本类似,可以有抽象方法,也可以有方法体。但override作为关键字使用,而不是注解。

2 类的构造器

构造器也叫构造方法,是类创建的必要元素。构造函数可以用关键词constructor声明,
在Kotlin中,类允许定义一个主构造器,和若干个第二构造器。
主构造器是类头的一部分,紧跟在类名的后面,参数是可选的。

2.1 主构造器

如下代码定义了一个类,并指定了一个主构造器

class Person constructor(name: String) {
}

// 如果主构造器没有任何注解,任何修饰,constructor可以省略
class Person(name: String) {
}

// 如果是主构造器,需要在init代码块中进行初始化主构造器
// 注:主构造器中的参数不仅可以在init代码块中使用,还可以对类的属性进行初始化。
// var和val也可以修饰主构造器参数:
// 如果使用var,表示参数对于构造器来说是变量,在构造器内部可以对其进行操作和改变(在主构造器中修改值后,不会把修改后的值传到对象外面)
// 如果使用val,表示该参数是常量,在构造器中不能修改它的值。
class Person(var name: String) {
    val myName = name
    init {
        println(name)
    }
}

主构造函数不能包含代码块,它只能作为一个纯粹的参数列表声明,如果我们需要初始化逻辑,用关键词init可以声明一个局部作用域,它会在实例化时被调用。事实上所有init block都会被编译为主构造函数的一部分,然后按照声明顺序执行

2.2 第二构造器

Kotlin的类中,除了可以声明一个主构造器之外,还可以声明若干个第二构造器,第二构造器必须在类中声明,前面必须加constructor关键字。

class Person(var name: String) {
    init {
        name = "hello"+ name
        println(name)
    }
    constructor(age: Int) : this("js") {
        println(name + " " + age)
    }
    constructor(sex : Byte) :this(20){
        println(name +" "+ sex)
    }
}

注意:

  1. 主构造器中可以使用var和val修饰参数,但第二构造器中不能使用,也就意味着第二构造器中的参数都是只读的
  2. 由于nit block本质上是主构造函数的一部分,而次构造函数需要委托主构造函数,所以所有的init block要优先于次构造函数执行,
  3. 类级别的属性init block之前主构造函数后执行。

2.3 单例模式

Kotlin 中的单例模式,需要理解一个特殊的类型,这个类型叫做object,这个object可不是Java中所有类的父类(Object),这个object 就是创建单例用的,我们都知道,Java中单例有懒汉式、饿汉式,双重检查锁等几种单例变种,但是在Kotlin中除了可以使用这几种,还有object的单例实现方式,实际上该方式也是饿汉式的实现,只是Kotlin的语法糖使写法不一样而已。

2.3.1 饿汉式

object Singleton {
}

2.3.2 懒汉式

Kotlin的写法比较多,可以直接从java翻译,还有一种写法就有点复杂了,私有构造函数,我们也用到了他的get()方法。注意:companion object 修饰为伴生对象,伴生对象在类中只能存在一个,类似于java中的静态方法 Java 中使用类访问静态成员,静态方法。

class Singleton2 private constructor(){
    companion object {
        private var singleton2 : Singleton2? = null
        get() {
            if (field == null)
                field = Singleton2()
            return field;
        }
        fun get() : Singleton2? {
            return singleton2 
        }
    }
}

class Singleton3 private constructor(){
    companion object {
        val Instance by lazy(mode = LazyThreadSafetyMode.NONE) {
            Singleton3()
        }
    }
}

2.3.3 线程安全的懒汉式

Kotlin直接声明@Synchronized同步就行

class Singleton4 private constructor(){
    companion object {
        private var singleton4 : Singleton4? = null
            get() {
                if (field == null)
                    field = Singleton4()
                return field;
            }
        @Synchronized
        fun get() : Singleton4? {
            return singleton4 
        }
    }
}

2.3.4 线程安全双重校验

LazyThreadSafetyMode.SYNCHRONIZED即锁的意思

class Singleton5 private constructor(){
    companion object {
        val Instance by lazy(mode = LazyThreadSafetyMode.SYNCHRONIZED) {
            Singleton5()
        }
    }
}

2.3.5 静态内部类

class Singleton6 private constructor(){
    companion object {
        fun getInstance() = SingletonHolder.ins
    }

    private object SingletonHolder{
        var ins = Singleton6()
    }
}

由于Kotlin自身语法简洁的优势,写单例模式也就一两行的代码,这里没有深究一些高级用法,比如Lazy,属于Kotlin延迟属性的委托,有兴趣的可以自己研究下。

3 类的属性

Kotlin类的属性通过基本关键词val, var来声明,可以像Java一样直接声明在类体中,也可以通过语法糖直接写在主构造函数中。如果属性声明了默认值,根据类型推导规则可以省略类型声明

Kotlin中类的属性必须被初始化,或者声明为abstract。初始化有两种方式,一种是添加默认值,一种是延迟初始化,使用后者需要用lateinit修饰属性,表示我希望该属性在运行时动态加载,并且我信任自己的代码不会在它没有初始化之前就使用它(如果这么干,空指针crash), lateinit 延迟初始化 的属性 , 在使用前可以执行::属性名称.isInitialized检查 , 查看该属性是否进行了初始化操作。

lazy 惰性初始化 的 属性初始化操作 是 提前定义好的 , 在 调用之前 自动进行初始化操作 , 如果不调用 , 则不进行初始化 ;
lateinit 延迟初始化 的 属性初始化操作 , 需要 手动进行初始化 , 如果忘了初始化直接调用就会报错 ;

Kotlin的属性提供了getter/setter语法。一般情况下不需要手动重写get/set方法,下面例子是两种常见的重写case。

const val VERSION = "1.0"

class Person(
    val age: Int,
    val address: String = "Asia"
) {

    var country = "China"

    // 惰性初始化,使用的时候自动初始化
    val name by lazy { initName() }

    private fun initName(): String {
        return "Tom"
    }

    // lateinit延迟初始化
    lateinit var phone: String

    init{
        initPhone()
    }

    private fun initPhone(){
        // isInitialized检测lateinit延迟初始化的属性是否已经初始化
        if(!::phone.isInitialized) {
            phone = "138****1111"
        }
    }

    // 这里的`field`关键字是字面量的含义,可以粗略理解为它是当前变量在内存中的指针
    var size = 1
        get() = field
        set(value) {
            // 提供特殊的过滤逻辑
            field = if (value < 10) value else 10
        }

    private var _number = "000"
    var number
        // 对外仅仅暴露get方法,这里只是演示,真实情况_number一般用val声明
        get() = _number
        set(value) {
            _number = value
        }
}

Kotlin的类还存在编译时常量的概念,用const修饰,和Java的final概念基本一致

4 接口

接口本质上也是,是特殊的类Kotlin的接口在Java接口基础上扩充了能力,允许直接实现方法体,也允许声明属性(类似Java的抽象类)。但是需要注意的是接口中的属性要么是abstract的,要么提供了get方法,但是接口的属性不存在backing fields,无法用field关键字获取真实值。
接口可以继承多个接口,多个接口也可以被一个类继承。继承规则和上面的类继承基本一致。

继承多个接口遇到同名方法时,解决冲突方法如下:

interface A {
    fun foo() { print("A") }
    fun bar()
}

interface B {
    fun foo() { print("B") }
    fun bar() { print("bar") }
}

class C : A {
    override fun bar() { print("bar") }
}

class D : A, B {
    override fun foo() {
        super<A>.foo()
        super<B>.foo()
    }

    override fun bar() {
        super<B>.bar()
    }
}

但是事实上,几乎不会有人把两个接口的方法命名相同

Kotlin还支持一种特殊的语法糖,当接口中有且只有一个abstract方法时,可以进行如下简写

fun interface IntPredicate {
   fun accept(i: Int): Boolean
}
 
//传统写法
val isEven = object : IntPredicate {
   override fun accept(i: Int): Boolean {
       return i % 2 == 0
   }
}
 
//语法糖
val isEven = IntPredicate { it % 2 == 0 }

5 类的继承

Kotlin的继承Java的继承一样都是单继承,区别在于Kotlin用:来代替了extendsKotlin中所有类都有一个共同的超类 Any,这对于没有继承其他父类的超类型声明的类是默认超类

class Example // 从 Any 隐式继承

Any 有三个方法:equals()hashCode()toString()。因此,为所有 Kotlin 类都定义了这些方法。
默认情况下,Kotlin 类是最终(final)的:它们不能被继承。 要使一个类可继承,请用 open 关键字标记它。

继承需要涉及到一些关键词:
在这里插入图片描述

5.1 重写方法

open class Shape {
    //父类有一个带有形参的构造器
    constructor(name: String?) {

    }

    //父类有一个带有形参的构造器
    constructor() {

    }

    open fun draw() { /*……*/ }
    fun fill() { /*……*/ }
}

open class Circle(name: String?) : Shape(name) {

    constructor() : this(null) {//子类次构造器委托调用子类主构造器,间接委托调用父类构造器

    }

    // 父类和其中的draw()方法都是open修饰,因此可以继承并重写,但需要加override 关键词
    // 重写draw()方法后,如果不想被Circle子类再重写,可以加final修饰
    final override fun draw() { /*……*/ }
}

重写方法遵循“两同、两小、一大”原则:

  1. 两同:方法名相同、形参列表相同
  2. 两小:返回值类型比父类返回值类型小或相等、抛出异常类型比父类小或相等。
  3. 一大:访问权限比父类大或相等

5.2 重写属性

属性覆盖与方法覆盖类似;在超类中声明然后在派生类中重新声明的属性必须以 override 开头,重写的子类属性的类型必须与父类属性类型兼容(变量类型)。每个声明的属性可以由具有初始化器的属性或者具有 get 方法的属性覆盖。

open class Father {
    open var a : Float = 1.1f
    protected open var b : Float = 1.1f
    protected open val c : Float = 1.1f
}

class Son : Father() {
    override var a : Float = 2.2f

    // 子类属性访问权限必须大于等于父类类型
    public override var b : Float = 2.2f

    // 只读属性(val)可被重写成读写属性(var),读写属性(var)不能被重写成只读属性(val)
    // 因为一个 val 属性本质上声明了一个 get 方法, 而将其覆盖为 var 只是在子类中额外声明一个 set 方法。
    public override var c : Float = 2.2f
}

5.3 强制重写

当子类同时继承多个超类(只能继承一个类,但可以实现多个接口)时,如果超类成员(属性/方法)名称一样时,子类需强制重写该成员。

子类想调用父类该成员(函数与属性),需通过super<父类名>.成员的方式调用。

interface FatherInterfs {
    var a: Float
    fun method() {
        println("执行父接口里面该方法 a的值为:$a")//2.2
    }
}
 
 
open class Father {
    protected open val a: Float = 1.1f
    open fun method() {
        println("执行父类该方法 $a")//2.2
    }
}
 
 
class Son : Father(), FatherInterfs {
    override var a: Float = 2.2f
    override fun method() {
        super<FatherInterfs>.method()
        super<Father>.method()
        println("执行子类该方法 $a")//2.2
        println("父接口a的值为 ${super<Father>.a}")//1.1
    }
}

在一个内部类中访问外部类的超类,可以通过由外部类名限定的 super 关键字来实现:super@Outer

class FilledRectangle: Rectangle() {
    override fun draw() { 
        val filler = Filler()
        filler.drawAndFill()
    }
 
    inner class Filler {
        fun fill() { println("Filling") }
        fun drawAndFill() {
            super@FilledRectangle.draw() // 调用 Rectangle 的 draw() 实现
            fill()
            println("Drawn a filled rectangle with color ${super@FilledRectangle.borderColor}") // 使用 Rectangle 所实现的 borderColor 的 get()
        }
    }
}

6 其他

6.1 数据类 Data class

Kotlin新增的关键词data,修饰类名变成数据类。在Java开发中经常需要解析一个json文件到内存中,这时需要写一个Java Bean类,定义好对应的属性和get/set方法,然后用诸如GSON的解析库解析。这里的Java Bean作为数据的容器。Kotlin的数据类就可以替代这一功能。例如

//帐号信息
{
    "username": "somebody",
    "id": "18239048190234891032",
    "basic_info": {
        "age": 10,
        "level": 2
    }
}
 
//data class
data class User(
    val username: String = "unknown",
    val id: String = "unknown",
    val basicInfo: BasicInfo = BasicInfo()
)
 
data class BasicInfo(
    val age: Int = 0,
    var level: Int = 0
)

形如上述例子,数据类基本语法规则有如下几条:

  1. 主构造函数至少要有一个参数
  2. 主构造函数中的所有参数必须声明val/var,也就是把它们作为属性而声明
  3. data class不能用abstract, open, sealed, inner来修饰

如何理解这三条约束,需要考虑data class背后都干了些什么。所有声明在主构造函数中的属性都会自动生成如下方法

equals() hashCode() 用来判断两个对象是否相等
toString() 形如"User(param1 = value1, param2 = value2)"
componentN() 用于解构的语法糖
copy() “拷贝构造函数"
其中第一点需要强调,因为一般意义上我们可以用hashCode来区分两个对象(虽然这并不保险),但data class的这一特性使得下例中的风险很容易发生

因为data class类体中声明的属性不参与hashCode的计算,所以只要主构造函数的参数列表一致,两个对象的hashCode就相等,虽然它们在内存中是独立的两个对象

data class Person(val name: String) {
    var age: Int = 0
}
fun main() {
    val person1 = Person("John")
    val person2 = Person("John")
    person1.age = 10
    person2.age = 20
    println("person1 == person2: ${person1 == person2}")
    println("person1 with age ${person1.age}: ${person1}")
    println("person2 with age ${person2.age}: ${person2}")
}
 
//result
person1 == person2: true
person1 with age 10: Person(name=John)
person2 with age 20: Person(name=John)

关于第三点所说的解构语法,则是一种语法糖,在很多语言中都存在,最常见的例子如下

这里的(key,value)就是解构语法糖

val numbersMap = mutableMapOf<String, String>().apply { 
        this["one"] = "1"
        this["two"] = "2" 
    }
    
for ((key, value) in numbersMap) {        
    println(key + ' ' + value)
}

而data class会自动声明componetN方法,也就意味着我们可以对它的对象使用这种语法糖

data class User(val age: Int = 0, val name: String = "someone")
 
val (age, name) = User(10, "Alice")

关于第四点的拷贝函数,一个简单的例子是,假设某个人的帐号level信息改变了,其他都不变,那么你可以这么写

val someOne = User("Alice", "123345", BasicInfo(10, 2))
 
val copyOne = someOne.copy(
    basicInfo = someOne.basicInfo.copy(
        level = 3
    )
)

关于数据类最后一点是,kotlin标准库中的Pair和Triple都是data class,所以它们才能使用解构语法

另外,kotlin 1.1后, data class是可以继承自普通类或者接口的,但事实上data class的继承很少使用,暂且不提

6.2 密封类 Sealed Class

Kotlin新增关键字sealed,修饰类名变成密封类。某种意义上,它可以被认为是对枚举类的增强。因为它有以下特点

  1. 密封类的所有直接子类在编译期就被唯一确定
  2. 密封类的子类可以拥有多个实例
  3. 密封类和它的直接子类必须声明在同一个package下
  4. 密封类本身是abstract的,必须通过子类来实例化
  5. 密封类的构造器只能是protect或者private

其中第三点关于密封类及子类的位置,一般是把子类作为嵌套类放在密封类内部,也可以把它们拆分成多个文件放在同一个package下。但需要注意,必须是严格的相同package,不能有如下情况

packageA {
    
    sealed class Parent
 
    packageB {
 
        class child: Parent
 
    }
 
}
 //子类放在密封类所在package的子package中也是不合法的

关于密封类的其他4点,其实共同做了一件事情:“保证密封类只有有限的几种已知子类”。这样和枚举类型就非常相似,枚举类型的实例只能是一些基本类型,作为flag使用。而密封类的子类可以包含属性、方法,同时也能作为flag,是枚举类型的增强。

考虑如下的例子。假设我希望根据屏幕的亮度来自适应调整软件主题,可以设计这样一个Theme的工具类,这里的Dark, Normal两个子类就是对主题类型的枚举,同时内部也包含一定逻辑

fun main() {
   
    println(Theme.getThemeByBrightNess(234).toString())
    //Theme$Dark@7a07c5b4
 
}
 
sealed class Theme {
    
    companion object {
        
        const val NORMAL_MAX_BRIGHTNESS = 1000f
 
        fun getThemeByBrightNess(brightness: Int): Theme = when {
            
            Dark.isThisTheme(brightness) -> Dark
 
            Normal.isThisTheme(brightness) -> Normal
            
            else -> Normal
           
        }
 
    }
 
    abstract fun isThisTheme(brightness: Int): Boolean
 
 
    object Dark : Theme() {
 
        private val darkBrightRange = (0.1 * NORMAL_MAX_BRIGHTNESS).toInt() .. (0.3 * NORMAL_MAX_BRIGHTNESS).toInt()
 
        override fun isThisTheme(brightness: Int): Boolean = brightness in darkBrightRange
 
    }
 
    object Normal : Theme() {
        private val normalBrightRange = (0.3 * NORMAL_MAX_BRIGHTNESS).toInt() .. NORMAL_MAX_BRIGHTNESS.toInt()
        
        override fun isThisTheme(brightness: Int): Boolean = brightness in normalBrightRange
    }
 
}

关于密封类的其他细节,参见官方文档

6.3 嵌套类 Nested class

Kotlin并没有关键字nested,嵌套类形如下例,可以视为外部类的一个成员,通过点操作符调用,常见的例子是Adapter里嵌套ViewHolder的声明

interface OuterInterface {
    class InnerClass
    interface InnerInterface
}
 
class OuterClass {
    class InnerClass
    interface InnerInterface
}

需要注意的是,嵌套类并不持有外部类的引用,把它们嵌套纯粹是符合人类逻辑上的收敛

6.4 内部类 inner class

Kotlin用关键字inner修饰一个嵌套类,被称为内部类。二者唯一的变化就是内部类持有了外部类的引用,可以访问外部类的成员

class Outer {
    private val bar: Int = 1
    fun foo(): Int{
    	return 666
    }
    inner class Inner {
        fun foo() = bar
    }
}
 
val demo = Outer().Inner().foo() //  1

很显然的,内部类有两个潜在问题:

  1. this指针,如果遇到同名方法或属性,需要使用this@receiver的语法指定当前this指向哪一个作用域
  2. 内部类天然存在循环引用问题,可能会导致内存泄漏

6.5 枚举类 enum class

Kotlinenum修饰类成为枚举类,最常用的两种case如下。都是作为flag使用,只不过带不带参数

enum class Direction {
    NORTH, SOUTH, WEST, EAST
}
 
enum class Color(val rgb: Int) {
    RED(0xFF0000),
    GREEN(0x00FF00),
    BLUE(0x0000FF)
}

但事实上,enum class可以实现接口,自定义方法,来实现很多逻辑,例如

enum class ItemType {
        A,    
        B,
        c;
 
        fun getTypeForMob(): String {
            return when (this) {
                A -> "aa"
                B -> "bb"
                C -> "cc"
            }
        }
}

Kotlin官方库还有一些关于枚举类型的工具函数,用来罗列或查询枚举类型的成员,例如

fun main(args: Array<String>) {
    //罗列
    var directions = Direction.values()
    for (d in directions){
        println(d)
    }
 
    for (direction in enumValues<Direction>()) {
        println(direction)
    }
    
    //查找
    println(Direction.valueOf("WEST")) //创建枚举类对象用这样的方式
    
    val west = enumValueOf<Direction>("WEST")
    }

6.6 内联类 value class

Kotlin1.5之前,内联类使用inline 修饰类名,和内联函数共用一个修饰符。但1.5之后内联类改用value修饰符。之所以有这个改动,需要理解为什么要有内联类。

简单来说,jvm对Kotlin中的基本类型,如String等做了很多优化,比如将其内存分配从堆上分配改为栈上分配,这些优化能大幅提高代码性能。但是我们开发者有时候会对基本类型做一些封装(装饰者模式),装饰后的类就无法享受jvm的优化了。鱼与熊掌不可兼得

作为成熟的开发者,我们当然选择全部都要。使用如下的内联类语法即可

value class Password(private val s: String)

Kotlin为了实现这一功能,对内联类做了很多限制,主要的几点如下

有且仅有一个包含单个基本类型参数的构造器
内联类可以有成员和方法,但没有字面量(也就是在堆中无法分配内存),只能对构造器中的参数做一些简单处理
内联类可以实现接口,但不能继承其他类,也不能被其他类继承
在某种意义上,内联类和类型别名有些相似,它们之间的核心区别在于内联类声明了一个新的类型,不能与基本类型互相赋值,而类型别名可以

6.7 对象表达式 object expression

Kotlin用object关键字声明一个对象表达式,这个说法可能有些奇怪,但如果改成匿名内部类就觉得非常熟悉了。object就是对匿名内部类的优化,结合Kotlinlambda语法糖,可以让代码写得极度简洁

最通常的写法如下

window.addMouseListener(object : MouseAdapter() {
    override fun mouseClicked(e: MouseEvent) { /*...*/ }
 
    override fun mouseEntered(e: MouseEvent) { /*...*/ }
})

关于它的一些其他细节

对象表达式可以实现多个接口
对象表达式持有外部类的引用,可以访问外部作用域的成员(比如当前函数作用域)
注意,object关键字除了声明对象表达式,还可以声明单例和伴生对象

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

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

相关文章

如何开始使用 Kubernetes RBAC

基于角色的访问控制 (RBAC) 是一种用于定义用户帐户可以在 Kubernetes 集群中执行的操作的机制。启用 RBAC 可以降低与凭证盗窃和帐户接管相关的风险。向每个用户授予他们所需的最低权限集可以防止帐户拥有过多的特权。 大多数流行的 Kubernetes 发行版都从单个用户帐户开始,…

值得拥有的 12 大最佳照片恢复软件 [持续更新]

由于误删、损坏、病毒攻击等原因&#xff0c;您可能会丢失珍贵的照片。幸运的是&#xff0c;市场上有专业的照片恢复工具。但是&#xff0c;根据您的需要找到合适的照片恢复软件的任务可能压力很大。为了帮助您做出正确的决定&#xff0c;我们精心挑选了您可以信赖的收费最高的…

opencv 图像识别 指纹识别 - python 计算机竞赛

0 前言 &#x1f525; 优质竞赛项目系列&#xff0c;今天要分享的是 &#x1f6a9; 基于机器视觉的指纹识别系统 &#x1f947;学长这里给一个题目综合评分(每项满分5分) 难度系数&#xff1a;3分工作量&#xff1a;3分创新点&#xff1a;4分 该项目较为新颖&#xff0c;适…

STM32Cube高效开发教程<基础篇>(四)----GPIO输入/输出

声明:本人水平有限,博客可能存在部分错误的地方,请广大读者谅解并向本人反馈错误。    本专栏博客参考《STM32Cube高效开发教程(基础篇)》,有意向的读者可以购买正版书籍辅助学习,本书籍由王维波老师、鄢志丹老师、王钊老师倾力打造,书籍内容干货满满。 一、 GPIO功能…

Vue3 + Nodejs 实战 ,文件上传项目--实现文件批量上传(显示实时上传进度)

目录 技术栈 1.后端接口实现 2.前端实现 2.1 实现静态结构 2.2 整合上传文件的数据 2.3 实现一键上传文件 2.4 取消上传 博客主页&#xff1a;専心_前端,javascript,mysql-CSDN博客 系列专栏&#xff1a;vue3nodejs 实战--文件上传 前端代码仓库&#xff1a;jiangjunjie…

城市消防无人机控制系统的设计

目录 摘 要......................................................................................................................... 2 第一章 绪论.............................................................................................................…

Qt 5.12.12 静态编译(MinGW)

前置准备 系统环境 版本 Windows 11 专业版 版本 22H2 安装日期 ‎2023/‎6/‎18 操作系统版本 22621.2428 体验 Windows Feature Experience Pack 1000.22674.1000.0依赖工具 gcc Qt 5.12.12 安装 MinGW 后自动安装 https://download.qt.io/archive/qt/5.12/5.12.12/qt-ope…

【C/C++数据结构 - 2】:稳定性与优化揭秘,揭开插入排序、希尔排序和快速排序的神秘面纱!

文章目录 排序的稳定性插入排序插入排序的优化 希尔排序快速排序 排序的稳定性 稳定排序&#xff1a;排序前2个相等的数在序列中的前后位置顺序和排序后它们2个的前后位置顺序相同。&#xff08;比如&#xff1a;冒泡、插入、基数、归并&#xff09; 非稳定排序&#xff1a;排…

【Linux】自旋锁 以及 读者写者问题

自旋锁 以及 读者写者问题 一、自旋锁1、其他常见的各种锁2、自旋锁相关的API函数 二、读者写者问题1、读者与写者的关系2、读写锁的API函数3、用伪代码理解读写锁的原理4、读写锁的演示使用 一、自旋锁 1、其他常见的各种锁 悲观锁&#xff1a;在每次取数据时&#xff0c;总是…

Docker 构建Python镜像时,pip使用国内地址的dockerfile模版

一、问题现象 构建镜像时&#xff0c;使用pip命令打包报错&#xff1a; 二、问题根因 因国内无法访问pip的配置文件中的仓库地址 三、解决办法 这个办法同样适用于&#xff1a;物理机&#xff0c;这个地址是阿里云的 pip config set global.index-url http://mirrors.aliy…

<C++> IO流

C语言的输入与输出 在C语言当中&#xff0c;我们使用最频繁的输入输出方式就是scanf与printf&#xff1a; scanf&#xff1a; 从标准输入设备&#xff08;键盘&#xff09;读取数据&#xff0c;并将读取到的值存放到某一指定变量当中。printf&#xff1a; 将指定的数据输出到…

idea自动封装方法

例如 package com.utils;import java.lang.reflect.Field; import java.sql.*; import java.util.ArrayList; import java.util.List; import java.util.ResourceBundle;/*** author hrui* date 2023/10/13 13:49*/ public class DBUtils {private static ResourceBundle bund…

【网络编程】Linux网络编程基础与实战第二弹——Socket编程

Socket编程套接字概念套接字通讯原理 网络编程接口网络字节序sockaddr数据结构socket函数bind函数listen函数accept函数connect函数 ) Socket编程 套接字概念 Socket本身有“插座”的意思&#xff0c;在Linux环境下&#xff0c;用于表示进程间网络通信的特殊文件类型。本质为…

多机器人三角形编队的实现

文章目录 前言一、机器人编队前的准备二、配置仿真环境2.编写机器人编队.cpp文件 三、三角形编队测试 前言 前阵子一直想要实现多机器人编队&#xff0c;找到了很多开源的编队代码&#xff0c;经过好几天的思索&#xff0c;终于实现了在gazebo环境中的TB3三角形机器人编队。 一…

prostate数据集下载

1. prostatex 下载地址&#xff1a;https://wiki.cancerimagingarchive.net/pages/viewpage.action?pageId23691656 比赛&#xff1a;https://prostatex.grand-challenge.org/ 这个下载的是一个tcia文件&#xff0c;参考这篇文章打开该文件 2. promise12 地址&#xff1a;…

阿里健康大药房七周年峰会:两大变革叠加 风往何处吹

10月11日&#xff0c;2023数字医药产业论坛暨阿里健康大药房7周年活动在杭州举行。 作为一年一度的医药圈峰会&#xff0c;大会现场集聚了数百家全球知名医药健康企业、经济学者、学术智库等各界领袖、专家&#xff0c;针对健康行业新趋势、新技术、新场景分享产业见解和经验&…

Redis HyperLogLog的使用

Redis HyperLogLog知识总结 一、简介二、使用 一、简介 Redis HyperLogLog是一种数据结构&#xff0c;用于高效地计算基数&#xff08;集合中唯一元素的数量&#xff09;。它的主要作用是用于在内存中高效地存储和计算大量数据的基数&#xff0c;而无需完全存储所有的数据。Hy…

XMind思维导图软件forMac/win:让你的大脑更高效地运转

XMind 是一款非常实用的思维导图软件&#xff0c;它可以帮助用户更好地组织思维、提高工作效率。 您是否曾经遇到过这样的问题&#xff1a;在工作中需要处理大量的信息、任务和项目&#xff0c;但却又不知道该如何下手&#xff1f;这种情况很常见&#xff0c;但是&#xff0c;…

简单好用的解压缩软件:keka 中文 for mac

Keka是一款功能全面、易于使用的文件压缩和解压缩软件&#xff0c;为Mac用户提供了便捷的文件管理工具。它支持多种压缩格式&#xff0c;具有快速解压和强大的压缩功能&#xff0c;让您能够轻松地处理各种文件压缩需求。 隐私非常重要 安全共享只需设置密码并创建高度加密的文…

虚幻引擎:如何实现骨骼重定向

前言&#xff1a; 为什么需要做骨骼重定向&#xff0c;因为当前角色素材没有对应的动画&#xff0c;这时候我们可以找个身高体型差不多的带有动画素材的另一个角色来做重定向&#xff0c;这样我们就可以得到我们需要的动画素材了。 1.首先创建两个骨骼的IK绑定 2.然后给两个骨骼…