---页首---

Kotlin 基础速成

Kotlin 基础速成:变量 / 函数 / 类 / 可见性

目标:用最短路径让读者能读懂并写出 Kotlin 日常代码。
假设读者有 C++ / Go / Java / Python 任一语言基础,本文件只讲 Kotlin 与此不同的地方,相同的地方一句带过。
代码风格约定:所有结论基于 Kotlin 1.9/2.x 正式语法,无推测。


0. 心智模型:先记住这 6 条,再谈语法

Kotlin 的设计目标只有一个:在保留静态类型和性能的前提下,把样板代码砍到最少。
由此派生出 6 条贯穿全语言的规则,后面所有语法都是它们的结果。

# 规则 含义 与 C++/Java 的差异
1 默认非空 String 不能装 null,要装必须写成 String? Java 默认可空,C++ 引用不可空但裸/智能指针可空
2 默认不可变 val 优先,var 次之 C++ const 是可选的,Kotlin 反过来
3 默认 final 类和方法默认不能被继承/重写,要开放必须写 open Java 默认开放,Kotlin 反过来
4 默认 public 不写修饰符就是 public 与 Java 一致;比 C++ class 默认 private 友好
5 类型后置 name: Type,而非 Type name 与 Go 一致,Pascal 风格也同样如此
6 无 new、语句即表达式 if/when/try 都能返回值 C++/Java 需要三元或语句体,代码更啰嗦

推论:Kotlin 里你看到的每个 : Type、?、open,都不是冗余语法,而是"打破某个默认值"的显式开关。
读懂 Kotlin 代码的诀窍:看到显式的东西,问自己"它打破了哪个默认值"。


1. 变量:声明与定义

1.1 两类变量:val 和 var

val answer: Int = 42      // val = 只读引用(read-only reference),初始化后不能再赋值
var score: Int = 0        // var = 可变引用(mutable reference),可反复赋值
score = 10                // OK
// answer = 43            // 编译错误:Val cannot be reassigned

关键更正(高频误解):val 不等于"常量值",它等于"只读引用"。
引用本身不可改,但引用指向的对象内容可以改:

val list = mutableListOf(1, 2)
list.add(3)          // OK:对象内容可变
// list = otherList  // 错误:引用不可变

类比 C++:val 更像 T* const(指针常量),不定相同,指向可变对象。类比 Go:val ≈ slice 变量本身不可换,但底层数组可写。

1.2 类型推断:多数情况下不用写类型

val name = "Kotlin"       // 推断为 String
val pi = 3.14             // 推断为 Double
val count = 10            // 推断为 Int
val longVal = 10L         // 推断为 Long(加 L 后缀)
val hex = 0xFF            // Int(十六进制字面量)
val mask = 0b1010_1100    // Int(二进制 + 下划线分隔符)
val big = 1_000_000       // Int(下划线只是可读分隔符,无语义)
val f = 1.0f              // Float(加 f 后缀,否则推断为 Double)

原则(团队共识):

  • 局部变量:依赖推断,不写类型(减少噪音)。
  • 公开 API(函数返回、属性):显式写类型(推断结果变化不应意外改变对外契约)。

1.3 可空性:Kotlin 最重要的语言特性

类型系统中区分"可空"和"非空",编译器强制你处理 null。

var a: String = "hello"
// a = null             // 编译错误:Null can not be a value of a non-null type String

var b: String? = "hello"
b = null                 // OK

// 四种处理可空值的方式:
val len1 = b?.length              // 安全调用:b 为 null 时整条表达式返回 null,类型 Int?
val len2 = b?.length ?: 0         // Elvis 运算符:null 时给默认值 0,类型 Int
val len3 = b!!.length             // 非空断言:b 为 null 时抛 NPE(不推荐,等于放弃保护)
val len4 = if (b != null) b.length else 0   // 显式判空:编译器在此做了智能转换(smart cast)

智能转换(smart cast):一旦你在分支里检查了类型或非空,编译器自动把变量当作该类型用,无需手动强转:

if (b != null) {
    println(b.length)    // 此处 b 被智能转换为 String(非空),不需要 !!
}
// 注意:var 若在检查后被其它代码可能修改(如闭包捕获),智能转换会失效,编译器会报错

何时用 !!:几乎不用。出现 !! 通常意味着设计问题(可以把不可空性前移到函数签名上)。合法场景极少,例如已由外部逻辑保证非空但编译器无法证明(此时加注释说明)。

1.4 常量:const val / lateinit / lazy

写法 语义 约束 典型场景
const val X = 1 编译期常量,直接内联进字节码 只能是顶层 / object / companion object 内;类型限 String 和基本类型 协议号、魔法数、版本号
val x = compute() 运行时初始化一次,之后只读 任意位置;延迟到首次到达该语句才计算 构造期计算一次的派生值
lateinit var x: T 稍后手动初始化 只能 var;不可用于可空类型和基本类型(Int/Long/Boolean…);使用前必须已赋值,否则抛 UninitializedPropertyAccessException DI 注入、Android onCreate 中赋值、@Autowired
val x by lazy { ... } 首次访问时才计算,之后缓存 委托给 Lazy<T>,默认线程安全(SYNCHRONIZED) 开销大的懒加载对象
const val MAX_SIZE = 100          // OK:顶层编译期常量
// const val NOW = System.currentTimeMillis()   // 错误:值不是常量表达式

class Repo {
    lateinit var conn: Connection  // 稍后注入
    val expensive by lazy { buildIndex() }
}

1.5 基本类型:没有隐式拓宽转换(与 C++/Java 的重要差异)

Kotlin 不做隐式数值转换。这是为了避免 long -> int 这类静默精度丢失。

val i = 10          // Int
val l = 10L         // Long
// val wrong: Long = i      // 错误:Type mismatch,Int 不会自动变 Long
val right: Long = i.toLong()  // OK:显式转换

// 转换函数族(通吃):toByte() toShort() toInt() toLong() toFloat() toDouble() toChar()
// 字符串转换:安全的写法用 xxxOrNull(),失败返回 null 而不是抛异常
val n1 = "123".toInt()          // 123
val n2 = "abc".toIntOrNull()    // null(推荐,配合 ?: 使用)

1.6 字符串模板

val name = "World"
println("Hello, $name")           // 简单变量:$name
println("Length = ${name.length}") // 表达式:${...}
println("${'$'}99")                // 输出字面 $:需要转义

1.7 一段对照(可直接记住)

需求 Kotlin Java 等价
不可变局部变量 val x = 1 final int x = 1
可变局部变量 var x = 1 int x = 1
类常量 const val X = 1(伴生对象内) static final int X = 1
可能为 null var x: String? = null String x = null
无 new val d = Date() Date d = new Date()

2. 函数:声明、参数、返回值

2.1 基本形式

可见性? 修饰符? fun 函数名(参数名: 参数类型, ...): 返回类型 { 函数体 }
                                              ^^^^^^^^^^^^ 不能省略时必须写,见下
fun add(a: Int, b: Int): Int {
    return a + b
}

返回 Unit(等价于 Java void)时可省略返回类型,此时 return 也可省略:

fun printSum(a: Int, b: Int): Unit { println(a + b) }   // 完整写法
fun printSum2(a: Int, b: Int) { println(a + b) }        // 惯用写法:省略 Unit

2.2 单表达式函数体(expression body)

函数体只有一个表达式时,用 = 替代花括号,返回类型可推断(公开 API 仍建议显式写):

fun area(r: Double): Double = Math.PI * r * r
fun isEven(n: Int) = n % 2 == 0          // 推断 Boolean

判别建议:单行逻辑用 =,多行逻辑用 {}。这是 Kotlin 社区的统一写法,不是个人偏好。

2.3 参数:默认参数、命名参数、vararg

这三项是 Kotlin 消灭 Java "重载爆炸" 的手段。

fun connect(
    host: String,
    port: Int = 8080,          // 默认参数
    timeoutMs: Long = 3_000,
    useTls: Boolean = false
) { /* ... */ }

connect("db.local")                          // 用默认 port/timeout/useTls
connect("db.local", 5432)                    // 按位置传(跳过后面的)
connect("db.local", useTls = true)           // 命名参数:跳过中间的
connect(host = "db.local", port = 5432, timeoutMs = 1000)  // 全命名

命名参数规则(重要):一旦某个实参使用了命名形式,它之后的所有实参都必须是命名形式(避免歧义)。

fun f(a: Int = 1, b: Int = 2, c: Int = 3) { println("$a $b $c") }
f(b = 20, a = 10)          // OK:命名后可乱序
// f(a = 10, 20)           // 错误:混用后位置不明确

可变参数 vararg:

fun sumOf(vararg nums: Int): Int = nums.sum()
sumOf(1, 2, 3)             // nums 在函数体内是 IntArray
val arr = intArrayOf(1, 2, 3)
sumOf(*arr)                // 展开操作符 *:把数组打散传入
// 注意:vararg 之后仍可定义别的位置参数,但调用时必须用命名参数

2.4 函数可以放在哪儿

位置 写法 说明
顶层(文件直接下辖) fun top() {} 不需要类包裹。编译为 文件名Kt.class,JVM 上静态方法。这也是 Kotlin 没有 "工具类" 概念的原因
类成员(方法) class A { fun m() {} } 通过实例调用
伴生对象 companion object { fun create() = A() } 类似静态工厂,见 §3.4
局部函数 写在另一个函数内部 只能被外层函数访问,适合提取私有逻辑而不污染类作用域
扩展函数 fun String.last(): Char = this[length-1] 不动源码给已有类型加方法,见下
lambda / 匿名函数 val f: (Int) -> Int = { it * 2 } 一等公民,见 §2.5

扩展函数(extension function) —— Kotlin 招牌特性之一,Java 不存在:

fun String.isEmail(): Boolean = Regex("^\\S+@\\S+\\.\\S+$").matches(this)
"a@b.com".isEmail()        // true —— 看起来像成员方法,实际编译成静态 Util

- 它不会真的修改类,只是语法糖(静态解析),不存在动态分派。
- 若与真实成员方法同名同签名,成员方法优先(不产生冲突告警)。

2.5 传参进阶:把函数作为参数(高阶函数)

函数类型写作 (参数类型列表) -> 返回类型。

// 接收一个函数类型的参数
fun measure(block: () -> Unit): Long {
    val t0 = System.nanoTime()
    block()                        // 调用传入的函数
    return System.nanoTime() - t0
}

measure { println("work") }        // 尾随 lambda:最后一个参数是函数时可移到括号外

// 带参数、带返回值的函数类型
fun <T> Collection<T>.filterBy(predicate: (T) -> Boolean): List<T> {
    val out = ArrayList<T>()
    for (item in this) if (predicate(item)) out.add(item)
    return out
}

listOf(1, 2, 3).filterBy { it > 1 }   // it = 单参数隐式名,等价于 { n -> n > 1 }

lambda 规则速记:

  • 单参数时可用默认名 it,多参数必须显式命名 { a, b -> ... }。
  • 最后一个参数是函数类型 → 可写成 f(a) { ... } 或 f { ... }(尾随 lambda 语法)。
  • lambda 里 return 语义与 Java 不同:从外层函数返回。只想从 lambda 返回需用标签 return@filterBy。

2.6 其它常见修饰符

修饰符 作用 例子
inline 减少 lambda 带来的对象分配开销,常用于集合操作 inline fun measure(block: () -> Unit)
tailrec 尾递归优化为循环,防止栈溢出 tailrec fun gcd(a: Int, b: Int): Int
infix 中缀调用 a to b infix fun Int.pow(n: Int),调用 2 pow 3
operator 运算符重载 operator fun plus(other: Vec): Vec
suspend 协程挂起函数 suspend fun load(): Data
@JvmOverloads 给默认参数生成 Java 可见的重载 供 Java 调用方使用

2.7 返回值细节

  • Unit:无意义返回值,单元素半类型,可被当作普通值使用(不像 Java void 那样特殊)。
  • Nothing:永远不返回(抛异常或无限循环)。返回类型标成 Nothing 有助于编译器做流程分析(如在 Elvis 后的 smart cast)。
    fun fail(msg: String): Nothing = throw IllegalStateException(msg)
    val x = value ?: fail("missing")   // 之后编译器知道 x 非空
    
  • 具名返回值在 tailrec / 多出口函数里建议显式写返回类型,避免推断意外变化。

3. 类:声明与实现

3.1 主构造函数:一行搞定"字段 + 赋值"

class Person(
    val name: String,        // 主构造函数参数加 val/var → 自动成为属性(字段+getter/setter)
    var age: Int,            // var → 生成 getter + setter
    email: String            // 不加 val/var → 仅构造参数,不作为属性
) {
    val display = "$name <$email>"    // 属性初始化
    init {                            // 初始化块,主构造函数体
        require(age >= 0) { "age must be >= 0" }
    }
    fun greet() = "Hi, I'm $name"
}

val p = Person("Tom", 18, "t@x.com")
p.name               // 读(val 只读)
p.age = 19           // 写(var 可写)
// p.email           // 错误:未标记为 val/var,不是属性

对照 Java:上面 3 行 ≈ Java 中 25 行(字段 + 构造器 + getter + setter + toString 的一半)。这是 Kotlin 最大的生产力来源。

3.2 次构造函数(secondary constructor)

class User(val id: Long, val name: String) {
    constructor(name: String) : this(generateId(), name)   // 必须委派主构造函数
}

经验:99% 的场景不需要次构造函数 —— 用默认参数替代(§2.3),这也是官方推荐做法。

3.3 属性:getter / setter / 幕后字段 field

class Temperature(var celsius: Double) {
    val fahrenheit: Double
        get() = celsius * 9 / 5 + 32          // 自定义 getter,每次访问实时计算,无存储

    var label: String = ""
        set(value) {                          // 自定义 setter
            field = value.trim()              // field = 幕后字段(backing field),不能在 setter 里写 this.label,否则无限递归
        }
}

规则:

  1. field 只在 getter/setter 内可见,用于访问属性真实存储。
  2. 若 getter/setter 都不使用 field,则该属性无幕后字段,纯的派生值。
  3. 自定义 getter/setter 时可单独降低可见性:最常见是 private set(见 §4.3)。

3.4 特殊类形态

形态 声明 自动生成 用途
data class data class Point(val x: Int, val y: Int) equals/hashCode/toString/copy()/componentN()(解构) DTO、值对象
object(单例) object Config { val timeout = 30 } 线程安全的懒加载单例,直接 Config.timeout 访问 全局配置、工具类替代
companion object class A { companion object { fun create() = A() } } 类级别的"静态"成员,A.create() 工厂方法、类常量(const val)
enum class enum class Level { DEBUG, INFO } name/ordinal/values() 有限取值集合
sealed class sealed class Res { ... } 受限继承:子类必须在同包同模块(Kotlin 1.5+ 同编译单元、同包) 状态建模,when 可穷尽性检查(不需要 else)
value class (1.5+) @JvmInline value class Id(val raw: Long) 编译期内联,零包装开销 强类型 ID,避免 Long 混淆
interface interface Repo { fun find(id: Long): String } 方法可有默认实现 抽象契约
data class Point(val x: Int, val y: Int)
val p1 = Point(1, 2)
val p2 = p1.copy(y = 3)         // 复制并改部分属性,保持不可变习惯
val (px, py) = p1               // 解构(依赖 componentN)

sealed class Result {
    data class Ok(val data: String) : Result()
    data class Err(val msg: String, val code: Int = 500) : Result()
}
fun handle(r: Result) = when (r) {     // 不需要 else,编译器知道已穷尽
    is Result.Ok  -> r.data
    is Result.Err -> "Error ${r.code}: ${r.msg}"
}

3.5 继承:open / override / interface / abstract

open class Shape(open val area: Double) {   // 默认 final,必须写 open 才能被继承
    open fun draw() = println("shape")      // 方法同理,默认 final
}

class Circle(val r: Double) : Shape(Math.PI * r * r) {   // : 后面既是继承也是调用父类构造函数
    override fun draw() = println("circle r=$r")         // override 关键字强制写,不允许遗漏
}

abstract class Animal(val name: String) {   // abstract 类默认自带 open,方法同理
    abstract fun speak(): String            // 无实现,子类必须实现
}

interface Flyable {
    val maxAltitude: Double                 // 接口里可声明抽象属性
    fun fly() = println("flying")           // 接口方法可有默认实现
}

class Bird : Animal("bird"), Flyable {      // 单继承 + 多实现,逗号分隔
    override val maxAltitude = 100.0
    override fun speak() = "tweet"
}

踩点:

  1. 子类构造 : 父类必须立刻初始化 → 写法 class Sub : Base(args),即"继承列表里的位置就得调用父构造",没有 Java 的 super(...) 行。
  2. override 修饰的成员默认仍然是 open(保留可覆盖性)。要彻底终止继承,显式写 override fun f() = ... 后加 final override(很少用)。
  3. val 属性可以被 override 成 var(放宽),反之不行。
  4. Kotlin 单继承 + 多实现,接口冲突时必须显式实现:
    class C : A, B {
        override fun f() { super<A>.f(); super<B>.f() }   // 指定调用哪个父的实现
    }
    

3.6 对象表达式 vs object 声明

val listener = object : OnClick {          // 匿名内部类(C++ 无对应概念,Java new Interface(){} 的等价物)
    override fun onClick() = println("clicked")
}
object Registry { val map = mutableMapOf<String, Int>() }   // 具名单例

4. 可见性 / 调用权限

4.1 四个修饰符

修饰符 顶层声明(文件直属) 类成员 与 Java 差异
public(默认) 到处可见 任何能看到该类的地方可见 一致
private 仅当前文件内可见 仅当前类内可见 Java 顶层类 private 不允许内部类那样,Kotlin 允许 file-private
protected 不允许(顶层无 protected) 当前类 + 子类(不含同包其他类) Java 的 protected 含同包访问权,Kotlin 收窄,更严格更直觉
internal 同一 模块内可见 同一模块内可见 Java 无对应(近似"包私有 + 更大范围"),这是 Kotlin 独有

模块(module)的准确定义:一起编译的一套 Kotlin 文件 —— 一个 Gradle Module/Subproject、一个 Maven 项目、或一个 IntelliJ Module、或单次 kotlinc 调用的编译单元。
internal 是模块化封装的主要手段:库作者把实现细节标 internal,对外只暴露 public API。它会被编译进字节码但名字带 mangled 后缀(如 foo$module_name),Java 调用虽然理论可行但会被工具警告。

4.2 完整可见性矩阵

顶层声明:

声明 同文件 同模块 其它模块
fun f()(public) ✅ ✅ ✅
private fun f() ✅ ❌ ❌
internal fun f() ✅ ✅ ❌
protected fun f() ❌ 编译错误 — —

类成员:

声明 同一类 子类(同模块) 子类(跨模块) 同模块其它代码 跨模块其它代码
public ✅ ✅ ✅ ✅ ✅
private ✅ ❌ ❌ ❌ ❌
protected ✅ ✅ ✅ ❌ ❌
internal ✅ ✅ ✅(子类也是 internal/public 时可见) ✅ ❌

4.3 属性的读写权限分离:最常见用法

class Counter {
    var count = 0
        private set            // 外部只读内部可写:这是 Kotlin 中最常见的封装手法

    fun inc() { count++ }
}
val c = Counter()
println(c.count)    // OK:getter 仍是 public
// c.count = 5      // 错误:Cannot assign to 'count': the setter is private

也可写 UE:

internal val cache: Map<String, String>
    get() = internalCache      // getter 为 internal,因此属性整体 external 不可见(不写也可以,默认与属性同级)

规则:getter 的可见性必须与属性相同;只有 setter 允许比属性更严格(private set / internal set / protected set)。

4.4 构造函数的可见性

class Singleton private constructor(val data: String) {   // 禁止外部 new
    companion object {
        fun create(data: String) = Singleton(data)        // 只能通过工厂方法创建
    }
}

4.5 覆盖(override)时的可见性

open class Base {
    protected open fun hook() {}
}
class Derived : Base() {
    override fun hook() {}      // 默认保留 protected,无需重复写
    // public override fun hook() {}   // 可以放宽为 public,但**不能收窄为 private**(编译错误)
}

规则:子类覆盖时可见性只能不变或放宽,不能收窄。这与 Java 一致(里氏替换原则)。

4.6 internal 在公开 API 中的坑(真实项目高频编译错误)

internal class Impl
// class Api { fun get(): Impl = Impl() }      // 编译错误:public function exposes its internal return type

规则:public API 的签名(参数、返回值、泛型实参、父类)不能包含 internal 或 private 类型。函数体内部使用则没有限制。

4.7 对照速记

场景 Kotlin Java C++
文件内私有的辅助函数 private fun helper()(文件顶层) 只能塞进某个类并标 private static static 于 .cpp 的匿名 namespace
只给库内部用 internal class Impl 无直接对应(只能放同一个 package 并依赖封包约定的包私有) 不 export 的符号 / internal namespace
子类可见但外部不 protected open fun hook() protected void hook()(同包也可访问) protected:
只读暴露给外部 var x: T; private set private T x; public T getX() private: T x; public: T getX()

5. 常见编译错误 → 原因对照

报错信息(关键片段) 真实原因 修复
Val cannot be reassigned 给 val 重新赋值 改用 var,或本就该是不可变
Null can not be a value of a non-null type 把 null / 可空值赋给非空类型 类型加 ?,或用 ?. ?: 处理
Only safe (?.) or non-null asserted (!!.) calls are allowed 直接访问可空对象的成员 用 ?.;确认非空用 checkNotNull(x) 替代 !!
Smart cast is impossible 编译器无法证明 var 非空(如被闭包捕获 / 是 open 属性) 复制到局部 val a = x ?: return 后再用
Type mismatch(Int/Long) 无隐式数值转换 显式 .toLong() 等
'a' is final, so it cannot be overridden 父类成员未加 open 给父类成员/类加 open(或改成 abstract/interface)
'a' hides member of supertype and needs 'override' 子类忘了写 override 加 override
public function exposes its internal parameter/return type public 签名引用了 internal 类型 把 API 改 internal,或把返回类型抽成 public 接口
Cannot assign to 'x': the setter is private 外部写 private set 的属性 通过类方法修改
Modifier 'protected' is not applicable to 'top-level' 顶层写了 protected 顶层只允许 public/private/internal
lateinit property has not been initialized 用了未初始化的 lateinit var 检查初始化时机;或用 ::prop.isInitialized

6. 自测题(共 12 题,覆盖本文全部要点)

答案见 Answers.md。先做题再看答案,能立刻定位薄弱点。

  1. val list = mutableListOf(1) 之后 list.add(2) 合法吗?list = mutableListOf(3) 合法吗?为什么?
  2. 把 Int 变量赋给 Long 类型,为什么不能直接赋值?正确写法?
  3. "12a".toIntOrNull() 返回什么?相比 toInt() 的好处?
  4. 写出 fun connect(host: String, port: Int = 80, useTls: Boolean = false) 三种调用方式:只传 host;传 host+useTls=true;全命名传参。
  5. 命名参数的"混用规则"是什么?
  6. vararg 传数组给函数时要用到哪个符号?
  7. Unit 和 Nothing 分别代表什么?各自典型场景?
  8. class User(name: String, val id: Long) 中 name 能被 u.name 访问吗?
  9. 主构造函数里能做参数校验吗?写在哪?
  10. 自定义 setter 里访问真实存储用什么关键字?写 this.x = v 会怎样?
  11. 为什么 Kotlin 类默认是 final?想开放继承怎么做?
  12. 顶层 protected 允许吗?internal 是什么范围?private set 解决了什么问题?

7. 30 秒速查卡

变量:  val 只读(var? 可空)   var 可变          const val 编译期常量    lateinit var 延迟 var
类型:  x: T (后置)            T? 可空           T!!不太推荐             as? 安全强转
函数:  fun f(a: Int, b: Int = 1): R { return ... }      Unit 可省略返回类型
        fun f(a: Int): R = expr                          单表达式体
        f(b = 2) / vararg nums: Int / *arr               命名 / 可变参 / 展开
类:    class A(val x: Int, y: String) { init {}         主构造函数 + init
          var v: Int = 0; private set                    读写权限分离
          val computed: Int get() = x * 2                自定义 getter
        }                                                data/object/companion/enum/sealed/value
继承:  open class / override fun / abstract class / interface / : 父构造直接调用
可见性:public(默认) private(文件或类内) protected(类+子类) internal(同模块)

附录:怎么跑起来

# 方式 A:安装 kotlinc(Chocolatey / brew install kotlin / sdk install kotlin)
kotlinc KotlinBasics.kt -include-runtime -d app.jar
java -jar app.jar

# 方式 B:Kotlin 脚本(Kotlin 2.x 支持直接 .kts / 部分版本支持 .kt 直接运行)
kotlin KotlinBasics.kt

# 方式 C(零安装):https://play.kotlinlang.org —— 粘贴代码即可在线运行

# 方式 D(推荐长期):IntelliJ IDEA 新建 Kotlin/JVM 项目,直接右键 Run
posted @ 2026-10-19 14:01  20190311  阅读(8)  评论(0)    收藏  举报
---页脚---