2021-03-02 学习记录
从今天开始尽量每天都记录一下自己学的东西,坚持每天做一点,希望可以面试顺利。接下来一段时间的内容更多是记录自己所学过程中遇到的问题,不再是完整的去写一个模块或内容的知识点或实现代码等。
今天学习的内容:
- 数据结构:链表
- Leetcode刷题:(1)剑指offer 22–链表中倒数第k个节点;(2)面试题 02.02–返回倒数第k个节点
- 学习微服务架构(完善user-srv这个demo服务)
1. 数据结构:链表
今天复习了数据结构中的链表。使用Go语言实现了一下链表的创建,遍历和增删查改。代码在github上,这里主要记录一下遇到的问题。
0x01 语言细节问题
由于使用go语言来实现,语言使用的不熟练导致了处理函数传参的时候出现了一些问题,我想在创建链表时传入任意类型的Slice,于是我开始写成了如下形式:
1 | func (linkedList *LinkedNode) Create(arr []interface{}) (err error) { |
结果直接报错。因为[]int
类型和[]interface{}
类型不兼容。然后我把[]interface{}
改成了interface{}
发现不能遍历。一时间不知道该怎么办。网上搜索一番后突然回忆起曾经写工作室招新平台的时候也遇到过这个任意类型Slice的传参问题,最后改成了如下代码:
1 | // CreateAnyTypeSlice converse interface{} to slice |
通过反射的方式实现类型转换可以解决这个问题。
上回写平台的时候因为赶时间只是无脑的copy了代码,这回准备仔细学一下go语言的reflect。(参考文章)
有关golang的反射(reflect)
首先反射的作用是采用某种机制来对自身行为进行描述和控制,并根据自身行为的状态和结果,调整或修改应用所描述行为的状态和相关的语义。那么这里我们就可以在运行时获取到某个变量的类型,值,和相关的属性。
go语言中,type包括static type(静态类型)和concrete type(直译意思是具体类型,也就是运行时类型)。static type是在编码过程中看见且可以确定的类型(如int、string),concrete type是runtime看见的类型,而只有interface{}
才有concrete type,因为它可以支持任意类型,且具体的类型是在运行时才能确定的,其他的类型如int, string, bool等在编码时就已经确定,也就不存在concrete type。
在Golang的实现中,每个interface变量都有一个对应pair,pair中记录了实际变量的值和类型:
1 | (value, type) |
其中value是实际变量值,type是实际变量的类型。反射就是用来检测存储在接口变量内部(值value,类型concrete type) pair对的一种机制。
(1) reflect.TypeOf() 和 reflect.ValueOf()
这两个方法中,TypeOf()是动态获取变量类型,ValueOf()是动态获取变量的值。
1 | // ValueOf returns a new Value initialized to the concrete value |
例如:
1 | package main |
运行结果:
1 | type: float64 |
注:
reflect.TypeOf
: 直接给到了我们想要的type类型,如float64、int、各种pointer、struct 等等真实的类型,且可以直接操作,不像v.(type)
这种需要用在switch语句中且不一定返回真实类型。reflect.ValueOf
:直接给到了我们想要的具体的值,如1.2345这个具体数值,或者类似&{1 “Bob” 25} 这样的结构体struct的值。也就是说明反射可以将“接口类型变量”转换为“反射类型对象”,即
reflect.Type
和reflect.Value
这两种。随后得到了类型就可以进行类型判断了,例如
reflect.ValueOf(i).Kind() == reflect.Float64
(2)从reflect.Value
中获取接口interface的信息
当执行reflect.ValueOf(interface)
之后,就得到了一个类型为relfect.Value
的变量,可以通过它本身的Interface()
方法获得接口变量的真实内容,然后可以通过类型判断进行转换,转换为原有真实类型。但是注意转换的时候类型必须完全符合,否则会panic。
例如:
1 | package main |
这里如果指针类型和值类型搞反了则会直接panic,同时这也说明反射可以将“反射类型对象”再重新转换为“接口类型变量”。
另一个作用是遍历结构体中的所有字段/方法。
1 | type A struct { |
运行结果:
1 | get type is: A |
(3)通过reflect.Value
设置实际变量的值
reflect.Value
是通过reflect.ValueOf(X)
获得的,只有当X是指针的时候,才可以通过reflect.Value
修改实际变量X的值,即:要修改反射类型的对象就一定要保证其值是“addressable”的。如果我们直接传值,则会提示unaddressable value。
1 | var x float64 = 3.4 |
此时reflect.ValueOf(x).CanSet()
方法返回false,提示不可修改。
正确写法:
1 | var x float64 = 3.4 |
随后我们便可直接修改值了。
1 | v.SetFloat(7.1) |
0x02 链表节点删除时注意事项
链表节点删除时,注意需要遍历node.next
,由于遍历到需要删除的节点时,需要将该节点的前序节点的next指针的值赋值为该节点的后继节点的值,如果使用node则无法获取前序节点(单向链表)。
2. Leetcode刷题
今天刷了两个链表的简单题,都是查询链表的倒数第k个元素。这个题首先我想到的是先获取链表长度i,记录下来,随后再遍历一次,同时新定义一个变量j计数,当计数值到j = i - k + 1
时则得到满足条件的值。
1 | /** |
但随后看题解发现了更好的解题思路:使用双指针法,无需统计链表长度。
首先初始化两个指针,slow 和 fast,都指向头节点。随后让fast指针向前移动k个节点的距离。再然后slow 指针和 fast 指针同时移动,直到 fast 指针刚好挪到链表末尾(即fast == nil
),slow指针指向的就是我们需要寻找的值。
1 | /** |
学习微服务架构
今天主要简单研究了一下go-micro项目的结构问题,通过阅读示例源码解决了一下数据库连接,明天继续研究。