Kotlin基础知识5

定义类与field关键字

class test {
    var name = "Derry"
        get() = field
        set(value) {
            field = value
        }
    /* 背后做的事情:会转换成下面的java代码
       @NotNull
       private String name = "Derry";

       public void setName( @NotNull String name) {
            this.name = name;
       }

       @NotNull
       public String getName() {
            return this.name;
       }
       //因此,如果定义方法的时候,不要定义setName和getName,会冲突
     */

    var value = "ABCDEFG"
        // 下面的隐式代码,不写也有,就是下面这个样子
        get() = field
        set(value) {
            field = value
        }

    var info = "abcdefg ok is success"
        get() = field.capitalize() // 把首字母修改成大写
        set(value) {
            field = "**【$value】**"
        }//如果set方法为private,那么setInfo就为private
    /* 背后做的事情:

        @NotNull
        private String info = "abcdefg ok is success";

        public void setInfo( @NotNull String info) {
            this.info = "**【" + info + "】**";
        }

        @NotNull
        public String getInfo() {
            return StringKt.capitalize(this.info)
        }

     */
}

fun main() {
    // 背后隐式代码:new test().setName("Kevin");
    test().name = "Kevin"
    // 背后隐式代码:System.out.println(new test().getName());
    println(test().name)


    println(">>>>>>>>>>>>>>>>>>")


    // 背后隐式代码:System.out.println(new test().getInfo());
    println(test().info)

    // 背后隐式代码:new test().setInfo("学习KT");
    test().info = "学习KT"
}

计算属性


class testclass {
    val number : Int = 0
    /* 背后的代码:
       private int number = 0;

       public int getNumber() {
            return this.number;
       }
     */

    // 计算属性  下面这样写 get函数覆盖了 field 内容本身,相当于field失效了,无用了,以后用不到了
    val number2 : Int
        get() = (1..1000).shuffled().first() // 从1到1000取出随机值 返回给 getNumber2()函数
    /*
        背后隐式代码:

        当转换成java代码时为什么没有看到 number2 属性定义?
        答:因为属于 计算属性 的功能,根本在getNumber2函数里面,就没有用到 number2属性,所以 number2属性 失效了,无用了,以后用不到了

         public int getNumber2() {
            return (1..1000).shuffled().first()java的随机逻辑 复杂 ;
       }
     */

    var info: String ? = null // ""

    // 防范竞态条件  当你调用成员,这个成员,可能为null,可能为空值,就必须采用 防范竞态条件,这个是KT编程的规范化
    fun getShowInfo() : String {

        // 这个成员,可能为null,可能为空值,就启用 防范竞态条件
        // 这种写法,就属于 防范竞态条件,我们可以看到专业的KT开发者,有大量这种代码
        // also永远都是返回 info本身
        return info?.let {
            if (it.isBlank()) {
                "info你原来是空值,请检查代码..." // 是根据匿名函数最后一行的变化而变化
            } else {
                "最终info结果是:$it" // 是根据匿名函数最后一行的变化而变化
            }
        } ?: "info你原来是null,请检查代码..."
    }
}

// TODO 71.Kotlin语言的 计算属性 与 防范竞态条件
fun main() {
    // 背后隐式代码:System.out.println(new testclass().getNumber());
    println(testclass().number)

    // 背后隐式代码:new testclass().setNumber(9);
    // testclass().number = 9 // val 根本就没有 setXXX函数,只有 getXXX函数

    // 背后隐式代码:System.out.println(new testclass().getNumber2());
    println(testclass().number2)

    // 背后隐式代码:System.out.println(new testclass().getShowInfo());
    println(testclass().getShowInfo())
}

主构造函数


// 主构造函数:规范来说,都是增加_xxx的方式,临时的输入类型,不能直接用,需要接收下来 成为变量才能用
// _name 等等,都是临时的类型,不能直接要弄,需要转化一下才能用
class test(_name: String, _sex: Char, _age: Int, _info: String) // 主构造函数
{
    var name = _name
        get() = field // get不允许私有化
        private set(value) {
            field = value
        }

    val sex = _sex
        get() = field
        // set(value) {} 只读的,不能修改的,不能set函数定义

    val age: Int = _age
        get() = field + 1

    val info = _info
        get() = "【${field}】"

    fun show() {
        // println(_name) 临时的输入类型,不能直接用,需要接收下来 成为变量才能用
        println(name)
        println(sex)
        println(age)
        println(info)
    }
}

fun main() {

    val p = test(_name = "Zhangsan", _info = "学习KT语言", _age = 88, _sex = 'M')
    // println(p.name)
    // p.name = "AAA" 被私有化了,不能调用
    p.show()
}

// 一步到位,不像上面是分开写的
class test (var name: String, val sex: Char, val age: Int, var info: String)
{
    fun show() {
        println(name)
        println(sex)
        println(age)
        println(info)
    }
}

// TODO Kotlin语言的主构造函数里定义属性
fun main() {
    test(name = "Zhangsan", info = "学习KT语言", age = 88, sex = 'M').show()
}

次构造函数


class test(name: String) // 主构造
{
    // 2个参数的次构造函数,必须要调用主构造函数,否则不通过,  为什么次构造必须调用主构造?答:主构造统一管理 为了更好的初始化设计
    constructor(name: String, sex: Char) : this(name) {
        println("2个参数的次构造函数 name:$name, sex:$sex")
    }

    // 3个参数的次构造函数,必须要调用主构造函数
    constructor(name: String, sex: Char, age: Int) : this(name) {
        println("3个参数的次构造函数 name:$name, sex:$sex, age:$age")
    }

    // 4个参数的次构造函数,必须要调用主构造函数
    constructor(name: String, sex: Char, age: Int, info: String) : this(name,sex) {
        println("4个参数的次构造函数 name:$name, sex:$sex, age:$age, info:$info")
    }
}
// name: String, sex: Char, age: Int, info: String
fun main() {
    val p = KtBase74("李元霸") // 调用主构造

    test("张三", '男') // 调用 2个参数的次构造函数

    test("张三2", '男', 88) // 调用 3个参数的次构造函数

    test("张三3", '男', 78, "还在学校新语言") // 调用 4个参数的次构造函数
}

默认参数


class test(name: String = "李元霸") // 主构造
{
    init {
        println(name)
    }
    // 2个参数的次构造函数,必须要调用主构造函数
    constructor(name: String = "李连杰", sex: Char = 'M') : this(name) {
        println("2个参数的次构造函数 name:$name, sex:$sex")
    }

    // 3个参数的次构造函数,必须要调用主构造函数
    constructor(name: String = "李小龙", sex: Char = 'M', age: Int = 33) : this(name) {
        println("3个参数的次构造函数 name:$name, sex:$sex, age:$age")
    }

    // 4个参数的次构造函数,必须要调用主构造函数
    constructor(name: String = "李俊", sex: Char = 'W', age: Int = 87, info: String = "还在学校新开发语言") : this(name) {
        println("4个参数的次构造函数 name:$name, sex:$sex, age:$age, info:$info")
    }
}

fun main() {
    val p = test("李元霸2") // 调用主构造

    test("张三", '男') // 调用 2个参数的次构造函数

    test("张三2", '男', 88) // 调用 3个参数的次构造函数

    test("张三3", '男', 78, "还在学校新语言") // 调用 4个参数的次构造函数

    test() // 到底是调用哪一个 构造函数,是次构造 还是 主构造 ? 答:优先调用主构造函数
}

初始化块

/*class A {

    {

    }

    A() {

    }

}*/

// username: String, userage: Int, usersex: Char  临时类型,必须要二次转换,才能用
class test (username: String, userage: Int, usersex: Char) // 主构造
{
    // 这个不是Java的 static{}
    // 相当于是Java的 {} 构造代码块,对象每次初始化都会被调用
    // 初始化块  init代码块
    init {
        println("主构造函数被调用了 $username, $userage, $usersex")

        // 如果第一个参数是false,就会调用第二个参数的lambda,并且抛出异常
        // 判断name是不是空值 isNotBlank   ""
        require(username.isNotBlank()) { "你的username空空如也,异常抛出" }

        require(userage > 0) { "你的userage年龄不符合,异常抛出" }

        require( usersex == '男' || usersex == '女') { "你的性别很奇怪了,异常抛出" }
    }

    constructor(username: String) : this(username, 87, '男') {
        println("次构造函数被调用了")
    }

    fun show() {
        // println(username) // 用不了,必须要二次转换,才能用
    }
}
// 临时类型只有在 init代码块才能调用
fun main() {
    test("李四", userage = 88, usersex = '女')  // 调用主构造
    println()
    test("王五") // 调用次构造

    test("李四", userage = -1, usersex = 'M')  // 调用主构造

    test("李四", userage = 1, usersex = '男')  // 调用主构造
}

构造初始化顺序

// 第一步:生成val sex: Char
class test(_name: String, val sex: Char) // 主构造
{

    // 第二步: 生成val mName  // 由于你是写在 init代码块前面,所以先生成你, 其实类成员 和 init代码块 是同时生成
    val mName = _name

    init {
        val nameValue = _name // 第三步:生成nameValue细节
        println("init代码块打印:nameValue:$nameValue")
    }

    // 次构造 三个参数的  必须调用主构造
    constructor(name: String, sex: Char, age: Int) :this(name, sex) {
        // 第五步:生成次构造的细节
        println("次构造 三个参数的, name:$name, sex:$sex, age:$age")
    }

    // 第四步
    val derry = "AAA"

    // 纠正网上优秀博客的错误: 类成员先初始生成   再init代码块初始生成  错误了
    // init代码块 和 类成员 是同时的,只不过你写在 init代码块前面 就是先生成你
}
fun main() {
    // 调用次构造
    test("李元霸", '男', 88)  // 调用次构造
}

延迟初始化lateinit


class testClass {

    // lateinit val AAA; // AAA 无法后面在修改了,我还怎么延时初始化?
    lateinit var responseResultInfo: String // 我等会儿再来初始化你,我先定义再说,所以没有赋值

    // 模拟服务器加载
    fun loadRequest() { // 延时初始化,属于懒加载,用到你在给你加载
        responseResultInfo = "服务器加载成功,恭喜你"
    }

    fun showResponseResult() {
        // 由于你没有给他初始化,所以只有用到它,就奔溃
        // if (responseResultInfo == null) println()
        // println("responseResultInfo:$responseResultInfo")

        if (::responseResultInfo.isInitialized) {
            println("responseResultInfo:$responseResultInfo")
        } else {
            println("你都没有初始化加载,你是不是忘记加载了")
        }
    }
}

fun main() {
    val p = testClass()

    // 使用他之前,加载一下(用到它才加载,就属于,懒加载)
    p.loadRequest()

    // 使用他
    p.showResponseResult()
}

惰性初始化


class testClass {

    // >>>>>>>>>>>>>>>>>>> 下面是 不使用惰性初始化 by lazy  普通方式(饿汉式 没有任何懒加载的特点)
     val databaseData1 = readSQlServerDatabaseAction()//一初始化就会调用方法,并不会等到需要用时才调用

    // >>>>>>>>>>>>>>>>>>> 使用惰性初始化 by lazy  普通方式
    val databaseData2 by lazy { readSQlServerDatabaseAction() }

    private fun readSQlServerDatabaseAction(): String {
        println("开始读取数据库数据中....")
        println("加载读取数据库数据中....")
        println("加载读取数据库数据中....")
        println("加载读取数据库数据中....")
        println("加载读取数据库数据中....")
        println("加载读取数据库数据中....")
        println("加载读取数据库数据中....")
        println("加载读取数据库数据中....")
        println("加载读取数据库数据中....")
        println("结束读取数据库数据中....")
        return "database data load success ok."
    }

}

// lateinit 是在使用的时候,手动加载的懒加载方式,然后再使用
// 惰性初始化by lazy  是在使用的时候,自动加载的懒加载方式,然后再使用
fun main() {
    // >>>>>>>>>>>>>>>>>>> 下面是 不使用惰性初始化 by lazy  普通方式(饿汉式 没有任何懒加载的特点)
    val p = testClass()

    Thread.sleep(5000)

    println("即将开始使用")

    println("最终显示:${p.databaseData1}")


    // >>>>>>>>>>>>>>>>>>> 使用惰性初始化 by lazy  普通方式
//    val p = testClass()
//
//    Thread.sleep(5000)
//
//    println("即将开始使用")
//
//    println("最终显示:${p.databaseData2}")

}

Any超类


// 在KT中,所有的类,都隐士继承了 : Any() ,你不写,默认就有
// Any类在KT设计中:只提供标准,你看不到实现,实现在各个平台处理好了
class Obj1 : Any()
class Obj2 : Any()
class Obj3 : Any()
class Obj4 : Any()
class Obj5 : Any()
class Obj6 : Any()
// Any == java Object
fun main() {
    println(Obj1().toString())
}

继承与open关键字


// KT所有的类和类中的方法,默认是final修饰的,不能被继承,和Java相反
// open:移除final修饰
open class Person(private val name: String) {

    private fun showName() = "父类 的姓名是【$name】"

    // KT所有的函数,默认是final修饰的,不能被重写,和Java相反
    open fun myPrintln() = println(showName())
}

class Student(private val subName: String) : Person(subName) {

    private fun showName() = "子类 的姓名是【${subName}】"//在子类也可以调用name,实际上调用的时getName方法

    override fun myPrintln() = println(showName())

}
fun main() {
    val person: Person = Student("张三")
    person.myPrintln()
}

类型转换


import java.io.File

open class Person2(private val name: String) {

    fun showName() = "父类 的姓名是【$name】"

    // KT所有的函数,默认是final修饰的,不能被重写,和Java相反
    open fun myPrintln() = println(showName())
}

class Student2(private val subName: String) : Person2(subName) {

    fun showName2() = "子类 的姓名是【${subName}】"

    override fun myPrintln() = println(showName2())

}
fun main() {
    val p: Person2 = Student2("王五")
    p.myPrintln()

    println(p is Person2)
    println(p is Student2)
    println(p is File)

    // is + as = 一般是配合一起使用
    if (p is Student2) {
        (p as Student2).myPrintln()
    }

    if (p is Person2) {
        // (p as Person2).myPrintln() // 因为子类重写了父类
        println((p as Person2).showName())
    }
}

枚举类

enum class Week {
    星期一,
    星期二,
    星期三,
    星期四,
    星期五,
    星期六,
    星期日
}
fun main() {
    println(Week.星期一)
    println(Week.星期四)

    // 枚举的值 等价于 枚举本身
    println(Week.星期二 is Week)
}
enum class Exam {
    Fraction1, // 分数差
    Fraction2, // 分数及格
    Fraction3, // 分数良好
    Fraction4, // 分数优秀
}

class Teacher (private val exam: Exam) {
    fun show() =
        when (exam) {
            Exam.Fraction1 -> "该学生分数很差"
            Exam.Fraction2 -> "该学生分数及格"
            Exam.Fraction3 -> "该学生分数良好"
            Exam.Fraction4 -> "该学生分数优秀"
            // else -> 由于我们的show函数,是使用枚举类类型来做判断处理的,这个就属于 代数数据类型,就不需要写 else 了
            // 因为when表达式非常明确了,就只有 四种类型,不会出现 else 其他,所以不需要写
        }
}
fun main() {
    println(Teacher(Exam.Fraction1).show())
    println(Teacher(Exam.Fraction3).show())
}

枚举类中定义函数


// 四肢信息class,我就是为了方便toString打印
data class LimbsInfo (var limbsInfo: String, var length: Int) {
    fun show() {
        println("${limbsInfo}的长度是:$length")
    }
}

enum class Limbs(private var limbsInfo: LimbsInfo) {
    LEFT_HAND(LimbsInfo("左手", 88)), // 左手
    RIGHT_HAND(LimbsInfo("右手", 88)), // 右手

    LEFT_FOOT(LimbsInfo("左脚", 140)), // 左脚
    RIGHT_FOOT(LimbsInfo("右脚", 140)) // 右脚

    ; // 结束枚举值

    // 1. WEEK 这个时候 再定义单调的 枚举值,就报错了,必须所有枚举值,保持一致的效果
    // 2. 枚举的 主构造的参数 必须和 枚举(的参数) 保持一致

    fun show() = "四肢是:${limbsInfo.limbsInfo}的长度是:${limbsInfo.length}"

    fun updateData(limbsInfo: LimbsInfo) {
        println("更新前的数据是:${this.limbsInfo}")
        this.limbsInfo.limbsInfo = limbsInfo.limbsInfo
        this.limbsInfo.length = limbsInfo.length
        println("更新后的数据是:${this.limbsInfo}")
    }
}
fun main() {
    // 显示枚举值

    // 一般不会这样用
    /*println(Limbs.show())
    println(Limbs().show())*/

    // 一般的用法如下:
    println(Limbs.LEFT_HAND.show())
    println(Limbs.RIGHT_HAND.show())
    println(Limbs.LEFT_FOOT.show())
    println(Limbs.RIGHT_FOOT.show())

    println()

    // 更新枚举值
    Limbs.RIGHT_HAND.updateData(LimbsInfo("右手2", 99))
    Limbs.LEFT_HAND.updateData(LimbsInfo("左手2", 99))
    Limbs.LEFT_FOOT.updateData(LimbsInfo("左脚2", 199))
    Limbs.RIGHT_FOOT.updateData(LimbsInfo("右叫2", 199))
}

数据类

data class LoginRequest(var info: String)
// 条件一:服务器请求回来的响应的 JavaBean  LoginResponseBean 基本上可以使用 数据类
// 条件二:数据类至少必须有一个参数的主构造函数
// 条件三:数据类必须有参数, var val 的参数
// 条件四:数据类不能使用 abstract,open,sealed,inner 等等 修饰 (数据类,数据载入的事情 数据存储)

单例类


object KtBase87 {
    /* object 对象类背后做了什么事情

        public static final KtBase87 INSTANCE;

        private KtBase87() {} // 主构造废除一样的效果

        public final void show() {
            String var1 = "我是show函数...";
            ...
            System.out.println(var1);
        }

        // 这个区域是 object 不同点:
        static {
            KtBase87 var0 = new KtBase87();
            INSTANCE = var0;
            String var1 = "KtBase91 init...";
            ...
            System.out.println(var0);
        }

     */

    init {
        println("KtBase91 init...")
    }

    fun show() = println("我是show函数...")
}

fun main() {
    // object KtBase87 既是单例的实例,也是类名
    // 小结:既然是 单例的实例,又是类名,只有一个创建,这就是典型的单例
    println(KtBase87) // 背后代码:println(KtBase87.INSTANCE)
    println(KtBase87) // 背后代码:println(KtBase87.INSTANCE)
    println(KtBase87)
    println(KtBase87)
    println(KtBase87)
    println(KtBase87)

    // 背后代码:KtBase87.INSTANCE.show();
    println(KtBase87.show())
}

伴生对象


class KtBase89 {

    // 伴生对象
    companion object {
        val info = "DerryINfo"

        fun showInfo() = println("显示:$info")

        val name = "Derry"
    }

    /* companion object {} 背后的逻辑

       private static final String name = "Derry";
       private static final String info = "DerryINfo";
       public static final KtBase89.Companion Companion = new KtBase89.Companion(xxx);

       public static final class Companion {

          @NotNull
          public final String getInfo() {
             return KtBase89.info;
          }

          @NotNull
          public final String getName() {
             return KtBase89.name;
          }

          public final void showInfo() {
             String var1 = "显示:" + ((KtBase89.Companion)this).getInfo();
             boolean var2 = false;
             System.out.println(var1);
          }

          private Companion() {}

          // $FF: synthetic method
          public Companion(DefaultConstructorMarker $constructor_marker) {
             this();
          }
        }

     */
}

// 伴生对象的由来: 在KT中是没有Java的这种static静态,伴生很大程度上和Java的这种static静态 差不多的
// 无论 KtBase89() 构建对象多少次,我们的伴生对象,只有一次加载
// 无论 KtBase89.showInfo() 调用多少次,我们的伴生对象,只有一次加载
// 伴生对象只会初始化一次
fun main() {
    // 背后代码:System.out.println(KtBase89.Companion.getInfo())
    println(KtBase89.info)

    // 背后代码:System.out.println(KtBase89.Companion.getName())
    println(KtBase89.name)

    // 背后代码:KtBase89.Companion.showInfo()
    KtBase89.showInfo()

    // new KtBase89();
    KtBase89()
    KtBase89()
    KtBase89()
    KtBase89()
    // ...
}

内部类与嵌套类


// TODO 内部类
// 内部类的特点: 内部的类 能访问 外部的类
//              外部的类 能访问 内部的类
class Body(_bodyInfo: String) { // 身体类

    val bodyInfo = _bodyInfo

    fun show() {
        Heart().run()
    }

    // 默认情况下:内部的类 不能访问 外部的类,要增加修饰符inner 成为内部类才可以访问外部类
    inner class Heart { // 心脏类
        fun run() = println("心脏访问身体信息:$bodyInfo")
    }

    inner class Kidney { // 肾脏
        fun work() = println("肾脏访问身体信息:$bodyInfo")
    }

    inner class Hand { // 手
        inner class LeftHand { // 左手
            fun run() = println("左手访问身体信息:$bodyInfo")
        }

        inner class RightHand { // 右手
            fun run() = println("右手访问身体信息:$bodyInfo")
        }
    }
}

// TODO 嵌套类
// 默认情况下:就是嵌套类关系
// 嵌套类特点:外部的类 能访问 内部的嵌套类
//           内部的类 不能访问 外部类的成员
class Outer {

    val info: String  = "OK"

    fun show() {
        Nested().output()
    }

    class Nested {

        fun output() = println("嵌套类")

    }
}

fun main() {
    // 内部类:
    Body("isOK").Heart().run()
    Body("isOK").Hand().LeftHand().run()
    Body("isOK").Hand().RightHand().run()

    // 嵌套类:
    Outer.Nested().output()

}

copy函数


data class KtBase92 (var name: String, var age: Int) // 主构造
{
    var coreInfo : String = ""

    init {
        println("主构造被调用了")
    }

    // 次构造
    constructor(name: String) : this(name, 99) {
        println("次构造被调用")
        coreInfo = "增加非常核心的内容信息"
    }

    override fun toString(): String {
        return "toString name:$name, age:$age, coreInfo:$coreInfo"
    }
}

/* 生成的toString 为什么只有两个参数?
   答:默认生成的toString 或者 hashCode equals 等等... 主管主构造,不管次构造
    public String toString() {
      return "KtBase92(name=" + this.name + ", age=" + this.age + ")";
    }
 */
fun main() {
    val p1 = KtBase92("李元霸") // 调用次构造初始化对象
    println(p1)

    val newP2 = p1.copy("李连杰", 78)
    println(newP2)

    // copy toString hashCode equals 等等... 主管主构造,不管次构造
    // 注意事项:使用copy的时候,由于内部代码只处理主构造,所以必须考虑次构造的内容
}

解构声明


// 普通类
class Student1(var name: String , var age: Int, var sex: Char) {
  //普通类默认是没有解构声明的,需要自己实现
//    // 注意事项:component0不可以 顺序必须是 component1 component2 component3 和成员一一对应,顺序下来的
    operator fun component1() = name
    operator fun component2() = age
    operator fun component3() = sex
}

// 数据类
data class Student2Data(var name: String , var age: Int, var sex: Char)

// TODO 93.Kotlin语言的解构声明学习
fun main() {
    val(name, age, sex) = Student1("李四", 89, '男')
    println("普通类 结构后:name:$name, age:$age, sex:$sex")

    val(name1, age1, sex1) = Student2Data("李四", 89, '男')
    println("数据类 结构后:name:$name1, age:$age1, sex:$sex1")

    val(_, age2, _) = Student1("李四", 89, '男')
    println("数据类 结构后: age2:$age2")
}

运算符重载


class AddClass(number1: Int, number2: Int)

// 写一个数据类,就是为了,toString 打印方便而已哦
data class AddClass2(var number1: Int, var number2: Int) {
    operator fun plus(p1: AddClass2) : Int {
        return (number1 + p1.number1) + (number2 + p1.number2)
    }

    // 查看 整个KT可以用的  运算符重载 方式
    // operator fun AddClass2.
}

// TODO 94-Kotlin语言的运算符重载学习
fun main() {
    // C++语言  +运算符重载就行了  -运算符重载就行了
    // KT语言  plus代表+运算符重载

    println(AddClass2(1, 1) + AddClass2(2, 2))
}

数据类

// 普通类
class ResponseResultBean1(var msg: String, var code: Int, var data: String) : Any()
// 转换成java时,只有set get 构造函数

// 数据类 -- 一般是用于 JavaBean的形式下,用数据类
data class ResponseResultBean2(var msg: String, var code: Int, var data: String) : Any()
// 转换成java时,会生成set get 构造函数 解构操作 copy toString hashCode equals  数据类 生成 更丰富
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值