0%

DNS解析


1
本篇内容基于自己在使用阿里云DNS解析域名,使用子域名访问不同网站(项目)

DNS原理

什么是DNS

DNS( Domain Name System)是“域名系统”的英文缩写,是一种组织成域层次结构的计算机和网络服务命名系统,它用于TCP/IP网络,它所提供的服务是用来将主机名和域名转换为IP地址的工作。

DNS的过程

    DNS是应用层协议,事实上他是为其他应用层协议工作的,包括不限于HTTP和SMTP以及FTP,用于将用户提供的主机名解析为ip地址。
具体过程如下:
    ①用户主机上运行着DNS的客户端,就是我们的PC机或者手机客户端运行着DNS客户端了
    ②浏览器将接收到的url中抽取出域名字段,就是访问的主机名,比如http://www.baidu.com/, 并将这个主机名传送给DNS应用的客户端
    ③DNS客户机端向DNS服务器端发送一份查询报文,报文中包含着要访问的主机名字段(中间包括一些列缓存查询以及分布式DNS集群的工作)
    ④该DNS客户机最终会收到一份回答报文,其中包含有该主机名对应的IP地址
    ⑤一旦该浏览器收到来自DNS的IP地址,就可以向该IP地址定位的HTTP服务器发起TCP连接

这就涉及了,在阿里云上配置DNS的过程。在解析参数中主机记录填写为hexoblog.maplestory.work,后缀为本博客的域名,记录值填写maple-chan.github.io, 也就是github给的端口。如此一来就能通过该域名访问我在github中的项目。DNS解析将自定义的域名(hexoblog.maplestory.work)和maple-chan.github.io联系起来。

如图:

avatar

但是怎么就能通过hexoblog.maplestory.work定位到我对应的哪一个工程呢?(maplestory.work解析到当前博客的工程)。这就用到了CNAME。在git项目中添加CNAME文件,内容只写hexoblog.maplestory.work, 这样在GitPage中就知道你这个域名要访问的是哪一个工程了。

GitHub中CNAME文件的作用:GitHub记录了CNAME文件,在有请求来时查看工程中CNAME文件下所写的域名,访问对应上得工程。

(这是我的理解,如我有错误,麻烦在GitHub上issue我吧!)

Kotlin 学习记录


1
本文只记录重要的或者与C/C++、Java 出入较大的内容

逻辑控制

  1. IF

​ 需要注意,Kotlin中没有三元运算符 :?, 因为if表达式会有返回值,当条件内逻辑为代码段时,则选择最后一句的值作为返回值。

  1. FOR

​ Kotlin中不再有 for(int i=0;i<n;++i) 这种语法。

  • 关键字 until
1
2
3
4
> for (i in 0 until 5){
> print("i => $i \t") //until[0,5)
> }
>
  • 关键词downTo
1
2
3
4
> 	for(i in 15 downTo 0){
> print("i=> $i \t") //downTo[15->0]
> }
>
  • 关键符号“..”
1
2
3
4
5
6
> print("使用 符号`..`的打印结果\n")
> for (i in 20 .. 25){
> print("i => $i \t")
> }
> println() //输出结果为 i=>20 i=>21 i=>22 i=>23 i=>24 i=>25
>
  • 设置步长
1
2
3
4
> for(i in 10 until 16 step 2){
> print("i => $i \t") //输出10,12,14
> }
>
  • 迭代

提供一个迭代器来遍历任何东西;数组被编译为一个基于索引的循环,他不会创建一个迭代器对象

  • 遍历字符串
1
2
3
4
> for (i in "abcdefg"){
> print("i => $i \t")
> }
>
  • 遍历数组
1
2
3
4
5
> var arrayListOne = arrayOf(10,20,30,40,50)
> for (i in arrayListOne){
> print("i => $i \t")
> }
>
  • 使用indices遍历数组
1
2
3
4
5
> var arrayListTwo = arrayOf(1,3,5,7,9)
> for (i in arrayListTwo.indices){
> println("arrayListTwo[$i] => " + arrayListTwo[i])
> }
>
  • 使用withIndex()遍历数组
1
2
3
4
5
> var arrayListTwo = arrayOf(1,3,5,7,9)
> for ((index,value) in arrayListTwo.withIndex()){
> println("index => $index \t value => $value")
> }
>
  • 使用列表或数组的扩展函数遍历
1
2
3
4
5
6
> var arrayListThree = arrayOf(2,'a',3,false,9)
> var iterator: Iterator<Any> = arrayListThree.iterator()
> while (iterator.hasNext()){
> println(iterator.next())
> }
>

//需要学习 Kotlin中的 it关键字 / Array函数本质 / lamda表达 / 函数定义

Kotlin 学习记录


1
本文只记录重要的或者与C/C++、Java 出入较大的内容

Kotlin 变、常量用法

  1. 在类中定义变量必须初始化,暂时不能初始化的可用lateinit[后期初始化]关键字声明
  2. lateinit只能声明于 var 变量(不能为可空变量、不能为基本数据类型、使用变量前必须赋值)
  3. 延后初始化。只能用于只读变量。
1
2
3
4
5
6
7
8
9
10
11
12
13
// 声明一个延迟初始化的字符串数组变量
private val mTitles : Array<String> by lazy {
arrayOf(
ctx.getString(R.string.tab_title_android),
ctx.getString(R.string.tab_title_ios),
ctx.getString(R.string.tab_title_h5)
)
}

// 声明一个延迟初始化的字符串
private val mStr : String by lazy{
"我是延迟初始化字符串变量"
}
  1. val 不是常量,是不可修改的变量。常量为 const val 且 const 不能用于修饰var
  2. 常量声明的三种正确方式
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// 1. 顶层声明
const val NUM_A : String = "顶层声明"

// 2. 在object修饰的类中
object TestConst{
const val NUM_B = "object修饰的类中"
}

// 3. 伴生对象中
class TestClass{
companion object {
const val NUM_C = "伴生对象中声明"
}
}

fun main(args: Array<String>) {
println("NUM_A => $NUM_A")
println("NUM_B => ${TestConst.NUM_B}")
println("NUM_C => ${TestClass.NUM_C}")
}常量声明的三种正确方式

数据类型

  1. Kotlin当中不支持8进制
1
2
3
var num16 = 0x0F
var num2 = 0b11110001
var num10 = 123
  1. Kotlin中可以通过下划线作数值中的逗号
1
2
var oneMillion = 1_000_000
println("var oneMillion = 1_000_000 => var oneMillion = $oneMillion")
  1. 数值比较:== 比较值,===比较内存中的地址
  2. 位运算大有不同:
1
2
3
4
5
6
7
8
9
var operaNum = 2

var shlnum = operaNum shl(2)
var shrnum = operaNum shr(2)
var ushrnum = operaNum ushr(2)

println(" shlOperaNum => $shlnum \n" +
" shrOperaNum => $shrnum \n" +
" ushrOperaNum => $ushrnum \n " )

Kotlin中对于按位操作,和Java是有很大的差别的。Kotlin中没有特殊的字符,但是只能命名为可以以中缀形式调用的函数,下列是按位操作的完整列表(仅适用于整形(Int)和长整形(Long)):

  • shl(bits) => 有符号向左移 (类似Java<<)
  • shr(bits) => 有符号向右移 (类似Java>>)
  • ushr(bits) => 无符号向右移 (类似Java>>>)
  • and(bits) => 位运算符 and (同Java中的按位与)
  • or(bits) => 位运算符 or (同Java中的按位或)
  • xor(bits) => 位运算符 xor (同Java中的按位异或)
  • inv() => 位运算符 按位取反 (同Java中的按位取反)
  1. 数组分 arrayOf(), arrayOfNulls(), Array(), 原始类型数组

Kotlin 学习记录


1
本文只记录重要的或者与C/C++、Java 出入较大的内容

Kotlin 函数

  1. 默认参数

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    fun defArgs(numA : Int  = 1, numB : Float = 2f, numC : Boolean = false){
    println("numA = $numA \t numB = $numB \t numC = $numC")
    }

    fun main(args: Array<String>) {

    // 默认参数的函数使用
    defArgs()
    defArgs(1,10f,true)
    }
  1. 命名参数

    1
    callFun("str",isTrue = true,numA = 3) //java中不支持这么写
  1. 可变参数

    ​ 当一个函数中的参数是不定数量的个数并且是同一个类型,则可是使用vararg修饰符去修饰这个变量,则被vararg修饰的参数相当于一个固定类型的数组。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    fun varargFun(numA: Int, vararg str : String){
    // 遍历
    for (s in str) {

    }

    // 获取元素
    // str[index]
    // str.component1() ... str.component5()

    // 或者其高阶函数用法
    // str.map { }
    // str.filter { }
    // str.sortBy { }

    }

    /*
    普通传递 : varargFun(1,"aaa","bbb","ccc","ddd","fff")
    数组传递:
    val strArray = arrayOf("aaa","bbb","ccc","ddd","fff")
    varargFun(1,*strArray) // *叫做伸展操作符
    */
  1. 单表达式函数

    ​ 函数具备返回值的时候,可以省略花括号并且在=赋值符号之后指定代码体,而函数的返回值是有编辑器自动推断的

    1
    2
    3
    4
    5
    6
    7
    8
    // 无参数的情况
    fun test1() = 2 // 自动推断为:返回类型为Int

    // 有参数的情况
    fun test2(num : Int) = num * 2 // 自动推断为:返回类型为Int

    // 或者
    fun test3(x : Float, y : Int = 2) = x * y // 和默认参数一起使用,返回值为Float型

高阶函数

将函数作为参数或者返回值的函数称为高阶函数。

  1. 1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    ///函数作为参数
    private fun resultByOpt(num1 : Int , num2 : Int , result : (Int ,Int) -> Int) : Int{
    return result(num1,num2)
    }

    private fun testDemo() {
    val result1 = resultByOpt(1,2){
    num1, num2 -> num1 + num2
    }

    val result2 = resultByOpt(3,4){
    num1, num2 -> num1 - num2
    }

    val result3 = resultByOpt(5,6){
    num1, num2 -> num1 * num2
    }

    val result4 = resultByOpt(6,3){
    num1, num2 -> num1 / num2
    }

    println("result1 = $result1")
    println("result2 = $result2")
    println("result3 = $result3")
    println("result4 = $result4")
    }
  2. 1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    ///返回值为函数
    fun test5(a:Int):()->Int{
    println("")
    var b = 3
    return fun():Int{
    println("b=> $b")
    b++
    return b + a
    }
    }

常用的标准高阶函数

  1. TODO函数:将会抛出异常,根据参数的内容输出异常内容

  2. run函数:两种用法

    ​ 当我们需要执行一个代码块的时候就可以用到这个函数,并且这个代码块是独立的。即我可以在run()函数中写一些和项目无关的代码,因为它不会影响项目的正常运行。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    private fun testRun1() {
    val str = "kotlin"

    run{
    val str = "java" // 和上面的变量不会冲突
    println("str = $str")
    }

    println("str = $str")
    }

​ 因为run函数执行了我传进去的lambda表达式并返回了执行的结果,所以当一个业务逻辑都需要执行同一段代码而根据不同的条件去判断得到不同结果的时候。可以用到run函数

1
2
3
4
5
6
7
8
9
10
11
val index = 3
val num = run {
when(index){
0 -> "kotlin"
1 -> "java"
2 -> "php"
3 -> "javaScript"
else -> "none"
}
}.length
println("num = $num")

​ 被一个对象所调用。当我们传入的lambda表达式想要使用当前对象的上下文的时候,我们可以使用这个函数。

1
2
3
4
5
6
val str = "kotlin"
str.run {
println( "length = ${this.length}" )
println( "first = ${first()}")
println( "last = ${last()}" )
}
  1. 同时还有with / let / apply / also / takeIf() / takeUnless / repeat() / lazy() 分别有不同的用法

Kotlin 学习记录


1
本文只记录重要的或者与C/C++、Java 出入较大的内容

Lambda 表达式

语法

无参数的情况

1
2
3
4
5
6
7
8
val fun1 = {9} //定义了一个返回了Int的函数

//该定义参照了情况3
//传入一个Lambda来表示的形式参数,该形参为一个无参返回Int的函数
var fun11 : (()->(Int)) ->Int = {initfunc -> initfunc()}

var value00 = fun11(fun1)
println("value00 => $value00")

有参数的情况

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
var value1 = 9
val fun2:(Int,Int)->Double = {a,b-> (a-b).toDouble()}
var value2 = fun2(value1,3)
println("value2 => $value2")//输出:value2 => 6.0

//等价形式如下
var fun3 = {a:Int,b:Int -> a+b}
var value3 = fun3(3,6)

println("value3 => $value3")//输出:value3 => 9
//3. lambda表达式作为函数中的参数的时候,这里举一个例子:
// fun test(a : Int, 参数名 : (参数1 : 类型,参数2 : 类型, ... ) -> 表达式返 //回类型){
// ...
// }
//

//下面两个新形式都可以作为value计算的表达式,fun与var功能似乎一样了?NO
//三种形式定义该test函数,fun声明,var声明,匿名函数。
//fun test(a:Int,b:Int,add:(Int,Int)->Int):Int{ return add(a,b)}//是一个函数
//var test: (Int,Int,(Int,Int)->Int) -> Int = {a,b,add -> add(a,b)}//该变量是lambda变量

var test = fun(a:Int,b:Int,add:(Int,Int)->Int):Int{ return add(a,b)} //是一个函数

println("value4 = $value4")

lambda表达式总是被大括号括着

​ 定义完整的Lambda表达式如上面实例中的语法2,它有其完整的参数类型标注,与表达式返回值。当我们把一些类型标注省略的情况下,就如上面实例中的语法2的另外一种类型。当它推断出的返回值类型不为Unit时,它的返回值即为->符号后代码段中的最后一个表达式的类型(如同if-else语句块中的返回值一样)

​ 当函数的参数仅有一个Lambda表达式的时候可以省略参数的那个小括号

it

  • it不是关键字

  • it在高阶函数中的lambda表达式的参数只有一个的时候可以使用it来使用此参数。it可表示单个参数的隐式名称

    例子:

1
2
3
4
5
6
 fun test(num1 : Int, bool : (Int) -> Boolean) : Int{
return if (bool(num1)){ num1 } else 0
}

println(test(10,{it > 5})) // {} 代表这是一个Lambda表达式,无{}会使得编译器不认识it
println(test(4,{it > 5}))

_

在使用Lambda表达式的时候,可以用下划线表示未使用的参数,表示不处理这个参数。

1
2
3
4
5
6
7
8
val map = mapOf<String,String>("key1" to "value1","key2" to  "value2","key3" to  "value3")

map.forEach{
key,value -> println("$key '-' $value")
}
map.forEach{
_,value -> println(" '-' $value")
}

匿名函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
val test1 = fun (x:Int,y:Int) = x+y; //单表达式函数可以 = 替换 {}
val test2 = fun (x:Int,y:Int) : Int= x+y;
val test3 = fun (x:Int,y:Int) : Int{
return x+y
}
println(test1(1,3))
println(test2(2,3))
println(test3(3,3))

/**
//这是错的,fun的需要返回值为Unit,但你给了个Int
val test4= fun(x:Int,y:Int) {
return x+y
}
*/

带接收者的函数字面值

  1. 匿名函数作为接收者类型

    匿名函数语法允许直接指定函数字面值的接收者类型,如果你需要使用带接收者的函数类型声明一个变量。

1
2
val iop = fun  Int.(other:Int):Int = this + other
println(20.iop(2)) //上面的this指的是左边的20 或者是 上面fun后的第一个Int

  1. Lambda表达式作为接收者类型

    要用Lambda表达式作为接收者类型的前提是**接收着类型可以从上下文中推断出来

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

class HTML{
fun body(){
println("This is HTML body")
}
}

fun html(init: HTML.() -> Unit): HTML{
val html = HTML()
html.init()
return html
}

html {
body()
}

闭包

闭包,可以函数中包含函数。

  • 携带状态
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
让函数返回一个函数,并携带状态值

fun test5(a:Int):()->Int{
println("")
var b = 3
return fun():Int{
println("b=> $b")
b++
return b + a
}
}

var t = test5(3)
println(t()) // 每次执行t函数的时候,b变量的值都是保留了上次执行结束的值,
// 因此,叫做携带状态值
println(t())
println(t())
  • 引用外部变量,并改变外部变量的值
1
2
3
4
5
6
var sum = 0
val arr = arrayOf(1,2,3,4,5,6,7,8,9,10,11)
arr.filter { it<7 }.forEach{sum += it}
//arr.filter { it<7 }.forEach ({sum += it})

println(sum)

可空类型、空安全、非空断言

  1. 判空的方法,if-else / ?. 判断

    1
    2
    3
    4
    var str : String? = "12346"
    str = null

    println(str?.length) //输出null
  2. 当一个函数/方法有返回值时,如果方法中的代码使用?.去返回一个值,那么方法的返回值的类型后面也要加上?符号

  3. let操作符

    let操作符作用:当使用?.符号时验证时忽略掉null

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    val arrTest : Array<Int?> = arrayOf(1,2,null,3,null,5,6,null)

    // 传统写法
    for (index in arrTest) {
    if (index == null){
    continue
    }
    println("index => $index")
    }

    // let写法
    for (index in arrTest) {
    index?.let { println("index => $it") }
    }

    /**
    index => 1
    index => 2
    index => 3
    index => 5
    index => 6
    */
  1. Evils操作符

    安全性操作符有三种:?: /!! / as?

    ?:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    val testStr : String? = null

    var length = 0

    // 例: 当testStr不为空时,输出其长度,反之输出-1

    // 传统写法
    length = if (testStr != null) testStr.length else -1

    // ?: 写法
    length = testStr?.length ?: -1

    println(length)

    !!

    1
    2
    3
    val testStr : String? = null
    println(testStr!!.length)
    //如果变量为空,使用!!修饰,运行时会抛出空指针异常

    as

    使用as进行强制转换,在不能转换时会抛出异常,而使用as?则会返回null,但不会抛出异常。

Kotlin 学习记录


1
本文只记录重要的或者与C/C++、Java 出入较大的内容

Class

  1. 当类没有结构体时可以省略大括号:class Test

  2. 主构造函数

    1
    2
    3
    4
    5
    6
    7
    class Test constructor(num :Int){
    //...
    }

    class Test (num:Int){
    //...
    }
  1. 初始化代码块

    1
    2
    3
    4
    5
    6
    class Test constructor(var num:Int){
    init{
    num = 5
    println("num = $num")
    }
    }
  1. 如上,声明属性可以直接在类头声明

  2. 当构造函数不具有注释符或者使用默认的可见性修饰符时,可以省略constructor关键字

  3. 辅助构造函数

    1
    2
    3
    4
    class Test{
    constructor(参数){
    }
    }
  1. 同时存在主构造函数和二级构造函数

    ​ 如果类具有主构造函数,则每个辅助构造函数需要通过另一个辅助构造函数直接或间接地委派给主构造函数。 使用this关键字对同一类的另一个构造函数进行委派:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    class Class_example2 constructor( num1:Int = 2){
    private var num2 :Int = 30

    init {
    println(num1)
    num2
    }

    constructor( num1:Int = 2, num3:Int):this(num1){
    println("num3+num1 = $num1 + $num3")
    }

    //虽然这里写的是num3,但其实调用的是主构造函数
    constructor(num1: Int = 2,num3: Int,num2:Int):this(num3){
    println("constructor - 3 para")
    }

    }
    fun main(){
    var c :Class_example2 = Class_example2( 3,num3 = 3)
    var cc:Class_example2 = Class_example2(1,2,3)

    }

    /*
    输出:
    3
    num3+num1 = 3 + 3
    2
    constructor - 3 para

    */
  1. 当类的主构造函数都存在默认值的情况下

    • JVM上,如果类主构造函数的所有参数都具有默认值,编译器将生成一个额外的无参数构造函数,它将使用默认值。 这使得更容易使用Kotlin与诸如JacksonJPA的库,通过无参数构造函数创建类实例。
    • 同理可看出,当类存在主构造函数并且有默认值时,二级构造函数也适用
    • 如果第一个第一个参数不是默认的,则不会有无参的,可以调用一个参数的。
    • 总结:如果中间有不具备默认参数的,则到该参数为止都需要进行强制给值,直到参数赋值完或者后面的都是由默认值的形参。
  2. 类的实例化

    没有 new 关键字。

  3. 类的类别

    密封类、内部类、抽象类、枚举类、接口类、数据类

属性与字段

  1. Getter & Setter

    1. 在Kotlin中,想要外部变量不能访问某类内的变量则将Setter进行private修饰,若使用private修饰属性则该变量不能对该属性进行访问
      1. val属性不能有setter函数
      2. getter一般写,默认实现。写了 get()=”修改也不变”,则当前属性值永远为“修改也不变”
  2. 修改访问器(Getter/Setter)的可见性

    1. get函数前面的可见性修饰符需要和属性一直
    2. 可以用@Inject set 来对实现Setter
    3. 共有属性var,setter用private进行修饰,则表示该属性不能外部修改值。
  3. 后备字段 (What is backing field)

    1. 定义:如果属性使用至少一个访问器的默认实现,或者自定义访问通过field标识符引用,则将为属性生成后备字段。 (换句话,如果没有默认访问器实现 && 没有自定义通过访问field标识符进行引用,则不会有后备字段)

    2. 原理:Kotlin中没有字段,但有后备字段。在isEmpty例子中可以学习到,判断类中是否为空不需要单独的字段,只需要对size进行判断即可,因此该变量不需要字段。而size则需要后备字段。

      1
      2
      3
      4
      5
      6
      7
      8
      class DummyClass {
      var size = 0;
      var isEmpty //no backing field
      get() = size == 0
      set(value) {
      size = size * 2
      }
      }
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
         public final class DummyClass {
      private int size;

      public final int getSize() {
      return this.size;
      }

      public final void setSize(int var1) {
      this.size = var1;
      }

      public final boolean isEmpty() {
      return this.size == 0;
      }

      public final void setEmpty(boolean value) {
      this.size *= 2;
      }
      }
  4. 后备属性

    _table是private 没有法访问,定义了table属性来对 _table进行get操作。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    private var _table: Map<String, Int>? = null
    public val table: Map<String, Int> /// table就是后备属性。
    get() {
    if (_table == null) {
    _table = HashMap() // 初始化
    }
    // ?: 操作符,如果_table不为空则返回,反之则抛出AssertionError异常
    return _table ?: throw AssertionError("Set to null by another thread")
    }
  1. 编译时常数

    编译时常数必须为顶层声明,初始化为String或者基本类型,没有自定义的getter()

    1
    2
    const val CONST_NUM = 5
    const val CONST_STR = "Kotlin"
  2. 后期初始化属性

    只能用于修饰var,没有自定义的setter与getter函数,属性必须为空且类型不能为基本类型。

  3. 委托属性

修饰符

public、internal、protected、private

  1. 顶层声明

    1. 在顶层声明中,文件不能用protected修饰
    2. 不同文件中,访问顶层声明的可以访问,public和internal
  2. 在类中声明

    1. 类中可以使用任意修饰符,且类内可以任意访问
    2. 类外的函数,只能访问public和internal
  3. 在接口中声明

    1. 只能声明public属性。
    2. 修饰private类和private的方法
    3. 用private修饰的方法不能被实现该接口的类重载。
  4. 在构造函数中的声明

    1. 任意使用修饰符
    2. 在二级构造函数中,不能用任意修饰符,可以说是默认修饰(public)
  5. 在局部声明中同上步中的二级构造函数

  6. 与Java中的对比

    四个修饰符不同、默认修饰符不同

继承

  1. 超类(Any);用:符号继承

  2. open修饰符

    1. open修饰符是定义继承类的修饰符
    2. 类和成员都需要使用open关键字
  3. 继承类的构造函数

    1. 实现类无主构造函数

      每个辅助构造函数必须使用super关键字初始化或者委托给另一个构造函数。

    2. 存在主构造函数

      主构造函数一般实现基类中参数最多的构造函数,参数少的哦那个this引用即可。

  4. 函数的重写

    1. 子类不能重写基类中没有用open修饰的同名函数。

    2. 当一个类不是用open修饰时,该类默认实final,不能被再次继承

    3. 子类用final关键字修饰方法,以此来禁止后续子类重写该方法。

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      open class A{
      open fun foo(){}
      }

      // B这个类继承类A,并且类B同样使用open修饰符修饰了的
      open class B : Demo(){

      // 这里使用final修饰符修饰该方法,禁止覆盖掉类A的foo()函数
      final override fun foo(){}
      }
  1. 重写属性

    1. 重写属性必须用override修饰。

    2. 当基类属性修饰为val时,实现类可以用var去重写,反之却不行。

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      open class Demo{
      open val valStr = "我是用val修饰的属性"
      }

      class DemoTest : Demo(){

      /*
      * 这里用val、或者var重写都是可以的。
      * 不过当用val修饰的时候不能有setter()函数,编辑器直接会报红的
      */

      // override val valStr: String
      // get() = super.valStr

      // override var valStr: String = ""
      // get() = super.valStr

      // override val valStr: String = ""

      override var valStr: String = "abc"
      set(value){field = value}
      }

      fun main(arge: Array<String>>){
      println(DemoTest().valStr)

      val demo = DemoTest()
      demo.valStr = "1212121212"
      println(demo.valStr)
      }
    3. 重写属性是不能用 get() = super.xxx,因为这样的话,不管你是否重新为该属性赋了新值,还是支持setter(),在使用的时候都调用的是基类中的属性值。

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      class DemoTest : Demo(){

      /*
      * 这里介绍重写属性是,getter()函数中使用`super`关键字的情况
      */

      override var valStr: String = "abc"
      get() = super.valStr
      set(value){field = value}
      }

      fun main(arge: Array<String>>){
      println(DemoTest().valStr)

      val demo = DemoTest()
      demo.valStr = "1212121212"
      println(demo.valStr)
      }

      也不能 get() = this.valStr / get() = valStr 。会报运行错误。

      Exception:StackOverflowError

      java.lang.StackOverflowError:stacksize8MBStackOverflowError是由于当前线程的栈满了,也就是函数调用层级过多导致。堆栈溢出错误一般是递归调用。出现这种异常,大多是由于循环调用。出现的情况:大多数都是在本方法中调用本方法。也就是我们常说的递归调用,所以才导致这个错误的出现。

      应该用默认的 get() = field

  2. 在主构造函数重写

    1
    2
    3
    4
    5
    6
    class DemoTest2(override var num: Int, override val valStr: String) : Demo()

    fun main(args: Array<String>){
    val demo2 = DemoTest2(1,"构造函数中重写")
    println("num = ${demo2.num} \t valStr = ${demo2.valStr}")
    }
  3. 覆盖规则:解决两个接口方法名相同问题

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    open class A{
    open fun test1(){ println("基类A中的函数test1()") }

    open fun test2(){println("基类A中的函数test2()")}
    }

    interface B{
    fun test1(){ println("接口类B中的函数test1()") }

    fun test2(){println("接口类B中的函数test2()")}
    }

    class C : A(),B{
    override fun test1() {
    super<A>.test1()
    super<B>.test1()
    }

    override fun test2() {
    super<A>.test2()
    super<B>.test2()
    }
    }

接口类/枚举类

枚举

  1. 枚举类的初始化及使用

    1
    2
    3
    4
    5
    6
    enum class Color(var argb : String){
    RED(""),
    WHITE(""),
    BLACK(""),
    GREEN("")
    }

    枚举常量,枚举类中的每个枚举常量都是对象,用逗号分隔。(如上述的RED(“”),)

    直接用Color.RED进行访问。

  2. 枚举常量匿名类,必须提供一个抽象方法,且该方法定义在枚举类内部。而且必须在枚举变量的后面,有抽象函数,则最后一个枚举变量必须用;隔开。

  3. 枚举常量的属性:name(常量名)和ordinal(常量位置)

  4. 可以用enumValues<T>()enumValuesOf<T>()访问

    1
    2
    3
    4
    5
    6
    println(enumValues<Color>().joinToString { it.name })
    println(enumValueOf<Color>("RED"))

    //输出
    //RED, WHITE, BLACK, GREEN
    //RED
  1. valueof()values()检测

    1
    2
    3
    4
    5
    println(Color.valueOf("RED"))
    println(Color.values()[0])
    println(Color.values()[1])
    println(Color.values()[2])
    println(Color.values()[3])

    其中,若使用Color.valueOf("不存在的枚举常量"),则会抛出IllegalArgumentException 异常,即枚举变量不存在。若使用Color.values()[大于枚举常量位置],则会抛出下标越界异常。

接口类

  1. 进行对接口的实现
  2. Kotlin中接口中可以写属性,作为抽象属性、作为访问器
  3. 多接口可用super<接口名>.方法名来区分。

数据类和密封类

数据类

  1. 关键字data
  2. 构造函数必须存在至少一个参数。
  3. 数据类的特性:
    1. 数据类不能是抽象的、开放的、密封的或者内部的。
    2. 数据类可以实现接口,同时也可以继承其他类,如密封类。

密封类

  1. 关键字sealed
  2. sealed class SealedExpr()
  3. 密封类不能被实例化,他的作用是表示受限的类继承结构
  4. 密封类可以有多个实例。
  5. 密封类的子类必须是在密封类的内部或必须存在于密封类的同一文件,密封类可以有效地保护代码。

抽象类&内部类

抽象类

  1. 抽象类有抽象成员,抽象成员都带abstract关键字
  2. Kotlin中的抽象类,在顶层定义时只能使用public
  3. 抽象类中可以定义内部抽象类
  4. 只能继承一个抽象类
  5. 抽象类,可以通过子类向上转型
  6. 抽象类可以继承另一个类,但不建议用open修饰抽象类

嵌套类类

  1. 定义:一个类嵌套在另一个类当中

  2. 外部类.嵌套类().嵌套类方法/属性。在调用的时候嵌套类是需要实例化的

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    class Outter{
    class Nested{
    fun execute(){
    Log.d("test", "Nested -> execute")
    }
    }
    }

    // 调用
    Outter.Nested().execute()

    //输出
    Nested -> execute

内部类

  1. 定义:用inner class 来进行声明类

  2. 内部类不能直接被实例化,需要外部的类实例化了对象,再利用该对象进行实例化内部类。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    class Outter{
    val testVal = "test"
    inner class Inner{
    fun execute(){
    Log.d("test", "Inner -> execute : can read testVal=$testVal")
    }
    }
    }

    // 调用
    val outter = Outter()
    outter.Inner().execute()

    // 输出
    Inner -> execute : can read testVal=test
  1. 监听器的实现方法

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    package edu.zju.maple.learning

    class NickInnerClass{
    lateinit private var listener:OnClickListener

    fun setOnClickListener(listener: OnClickListener){
    this.listener = listener
    }

    fun activeListener(){

    listener.onItemClick()
    }

    }

    interface OnClickListener{
    fun onItemClick()
    }

    fun main(){
    val nick = NickInnerClass()
    nick.setOnClickListener(object:OnClickListener{
    override fun onItemClick() {

    println("执行了activeListener函数,才有这句输出")
    }

    })
    var i=0;
    while (i<20){
    i++

    nick.activeListener() ///唤醒方法
    for ( temp in 1..0xFFFF){

    }

    }
    }

局部类

  1. 局部类只能在定义该局部类的方法中使用
  2. 定义在实例方法中的局部类可以访问外部类的所有变量和方法,但不能修改
  3. 局部类可以定义属性、方法。

C# 查看串口


场景简述

​ 在

方法介绍

  1. 在WPF中,每一个窗口都拥有一个Loaded事件传入接口,可以将函数传入该接口(即把函数委托给Loaded事件)。Loaded事件在元素即将要被渲染时触发。

  2. 在Loaded时定义拦截Windows消息拦截事件。事件函数(DeviceChanged)被委托给HwndSource的Hook。

  3. 在DeviceChanged函数中过滤串口插拔事件,并执行串口插拔后需要完善的逻辑操作。

    1. 事件序号:0x219 移动设备改变事件。0x8000设备插入事件。0x8004设备拔出事件。

具体实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
> 
> public MainWindow()
> {
> Loaded+=MainWindow_Loaded;
> }
>
> void MainWindow_Loaded(object sender,RoutedEventArgs e)
> {
> HwndSource hwndSource = PresentationSource.FromVisual(this) as HwndSource;
> if(null!=hwndSource)
> hwndSource.AddHook(new HwndSourceHook(DevieceChanged));
> }
>
> private IntPtr DeviceChanged(IntPtr hwnd,int msg,IntPtr wParam,IntPtr lParam,ref bool handled)
> {
> string[] PortNames;
> if (msg == WM_DEVICECHANGE)
> {
> switch (wParam.ToInt32())
> {
> case DBT_DEVICEARRIVAL://设备插入
> PortNames = SerialPort.GetPortNames();
> if (IsWorking == true && PortNames.Contains(ConfigInfo.Port))
> {
> if (serialPortUtil != null)
> {
> serialPortUtil.OpenPort();
> MsgBox.Show("串口连接成功!");
> }
> }
> break;
> case DBT_DEVICEREMOVECOMPLETE: //设备卸载
> PortNames = SerialPort.GetPortNames();
> if (IsWorking == true && !PortNames.Contains(ConfigInfo.Port))
> {
> MsgBox.Show("串口连接断开!");
> }
> break;
> default:
> break;
> }
> }
> return IntPtr.Zero;
> }
>

通过该方法可以实现对串口插拔事件的监听。







参考内容

​        [C#获取串口列表](https://www.cnblogs.com/xj2015/p/6100406.html)

C# 监听串口插拔事件


场景简述

​ 在进行C#串口开发时,往往需要对可访问的串口通过ComboList组件进行呈现共用户进行选择软件需要连接的串口,在这过程中就需要对串口的插拔事件进行监听。当事件到来对列表、串口开关进行对应的变化。使得整个软件操作起来更加合理。

方法介绍

  1. 在WPF中,每一个窗口都拥有一个Loaded事件传入接口,可以将函数传入该接口(即把函数委托给Loaded事件)。Loaded事件在元素即将要被渲染时触发。

  2. 在Loaded时定义拦截Windows消息拦截事件。事件函数(DeviceChanged)被委托给HwndSource的Hook。

  3. 在DeviceChanged函数中过滤串口插拔事件,并执行串口插拔后需要完善的逻辑操作。

    1. 事件序号:0x219 移动设备改变事件。0x8000设备插入事件。0x8004设备拔出事件。

具体实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
> 
> public MainWindow()
> {
> Loaded+=MainWindow_Loaded;
> }
>
> void MainWindow_Loaded(object sender,RoutedEventArgs e)
> {
> HwndSource hwndSource = PresentationSource.FromVisual(this) as HwndSource;
> if(null!=hwndSource)
> hwndSource.AddHook(new HwndSourceHook(DevieceChanged));
> }
>
> private IntPtr DeviceChanged(IntPtr hwnd,int msg,IntPtr wParam,IntPtr lParam,ref bool handled)
> {
> string[] PortNames;
> if (msg == WM_DEVICECHANGE)
> {
> switch (wParam.ToInt32())
> {
> case DBT_DEVICEARRIVAL://设备插入
> PortNames = SerialPort.GetPortNames();
> if (IsWorking == true && PortNames.Contains(ConfigInfo.Port))
> {
> if (serialPortUtil != null)
> {
> serialPortUtil.OpenPort();
> MsgBox.Show("串口连接成功!");
> }
> }
> break;
> case DBT_DEVICEREMOVECOMPLETE: //设备卸载
> PortNames = SerialPort.GetPortNames();
> if (IsWorking == true && !PortNames.Contains(ConfigInfo.Port))
> {
> MsgBox.Show("串口连接断开!");
> }
> break;
> default:
> break;
> }
> }
> return IntPtr.Zero;
> }
>

通过该方法可以实现对串口插拔事件的监听。







参考文献

​        [C# WPF USB 串口插入拔出识别监测](https://blog.csdn.net/barry_hui/article/details/80326403)

设计模式


​ 不能滥用static。

​ 正常的设计模式上不能随便使用static变量,应该先懂得如何编写足以证明“赋值结果冲突、混乱”的测试用例,然后再使用static变量[1]

目录

生产者消费者模式

观察者模式>

单例模式

工厂模式

生产者消费者模式

C# 访问UI线程


  • 采用SynchronizationContext的Post/Send方法更新

    ​ 异步更新UI控件需要两步:

       1. 是要获取UI线程的上下文

       2. 调用post方法

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    > public class ChangeUI //处在另一个线程
    > {
    > SynchronizationContext _syncContext = null;
    >
    > publc ChangeUI(){
    > _syncContext = SynchronizationContext.Current;
    > }
    > public update(){
    > Thread demoThread =new Thread(new ThreadStart(threadMethod));
    > demoThread.Start();//启动线程
    > }
    > private void threadMethod() {
    > syncContext.Post(changeControl, "修改后的文本");//子线程中通过UI线程上下文更新UI
    > }
    > prviate void changeControl(){
    > //执行设置控件代码
    > }
    > }
    >
  • 使用控件自身的invoke/BeginInvoke方法

      继承Control类的UI控件都可以使用Invoke方法异步更新

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    > private void button6_Click(object sender, EventArgs e) 
    > {
    >    Thread demoThread =new Thread(new ThreadStart(threadMethod));
    >    demoThread.IsBackground = true;
    >    demoThread.Start();//启动线程
    > }
    >   
    >  void threadMethod()
    >  { 
    >    Action<String> AsyncUIDelegate=delegate(string n){label1.Text=n;};
    >    label1.Invoke(AsyncUIDelegate,new object[]{"修改后的label1文本"});
    >  }
    >