###应用: 数据接口API, 自动邮件 ###
1. 值传递与引用传递
-> 1. 任何值传递都无改变原始值(map自带地址属性, 数组内的元素自带地址属性), 其他只能通过地址引用;
func do(a *[]int){
*a=append(*a,5)
}
var a = []int{1,2,3} or &[]int{1,2,3}
do(&a)
-> 2. m := make(map[string]int) or map[string]int{"a":1}
for k,v := range m{
fmt.Println(k,v)
}
v, exist : m["a"]
if exist{...}
2. 打印
fmt.Println()
fmt.Printf("%.2f\n", math.Pi)
fmt.Printf("%T",p) //打印类型
fmt.Printf("%p",p) //打印地址
fmt.Printf("%+v", c) //打印结构体
str := fmt.Sprintf("%v\n",data)
3. 生命周期
栈(Stack) 先入后出, func内创建的变量, 随着方法结束而消除;
defer 函数正是用到这一点, defer close() 压入最底端;
-> 1. 在结构体中设置抽象方法 - 单例模式
type TestStruct struct {
name string
PostRun func(s string)
GetRun func(s string)
}
// 实现抽象方法
func PostRun(s string) {
fmt.Println(s)
}
// go单例模式
var ins *TestStruct
var once sync.Once
func GetIns() *TestStruct {
once.Do(func() {
ins = &TestStruct{}
})
return ins
}
// 在main() 中执行:
t := &TestStruct{"test", PostRun, PostRun}
t.PostRun(t.name)
-> 2. 接口实现 - 类型转换 - 接口类型判断 - 单例模式
type TestInterface interface {
Call(interface{})
}
type TestInterfaceStruct struct {
}
//当方法作用于非指针接收器时,Go 语言会在代码运行时将接收器的值复制一份。在非指针接收器的方法中可以获取接收器的成员值,但修改后无效。
func (tis *TestInterfaceStruct) Call(any interface{}) {
switch any.(type) {
case int:
fmt.Println(strconv.Itoa(any.(int))) // int 转 string
}
}
// 使用设计模式实现单例
var mu sync.Mutex
var ins2 *TestInterfaceStruct
func GetIns2() *TestInterfaceStruct{
if ins2 == nil {
mu.Lock()
defer mu.Unlock()
if ins2 == nil {//需要重新判断
ins2 = &TestInterfaceStruct{}
}
}
return ins2
}
// 在main()中
var tis TestInterface
tisImpl := new(TestInterfaceStruct)
tis = tisImpl
tis.Call(5)
4. 其他集合:
l := list.New()
l.InsertBefore("noon", element)
for i := l.Front(); i != nil; i = i.Next() {
fmt.Println(i.Value)
}
switch a { //case 变量
case "mum", "daddy":
}
switch {
case r > 10 && r < 20:
}
if err != nil {
goto onExit
}
//定义标签
onExit:
fmt.Println(err)
exitProcess()
-> 使用匿名函数创建回调操作
func do(m map[string]int, f func(int)){
if val, ok := m["run"] ; ok{
f(val)
}
5. defer与互斥锁
var (
m = make(map[string]int)
guard sync.Mutex // 保证使用映射时的并发安全的互斥锁
)
func readVal(key string) int {
guard.Lock() //对共享资源加锁
defer guard.Unlock() // 不会立即执行, 而是等到函数结束调用, 避免取值之后再 Unlock 多写两行代码;
return m[key]
}
func fileSize(filename string) int64 {
f,err = os.Open(filename)
defer f.Close()
info,err = f.Stat()
return info.Size()
}
6. 宕机
// 保证传入的函数, 被包在try/catch中; ProtectRun 相当于try/catch
func ProtectRun(entry func()) {
// 延迟处理的函数
defer func() {
// 发生宕机时,获取panic传递的上下文并打印
err := recover()
switch err.(type) {
case runtime.Error: // 运行时错误
fmt.Println("runtime error:", err)
default: // 非运行时错误
fmt.Println("error:", err)
}
}()
entry()
}
7. 并发
-> goroutine
Go 程序会智能地将 goroutine 中的任务合理地分配给每个 CPU, 相当于线程池;
协程其实就是异步+回调的方式)所以当程序全部运行结束的时候,协程还没有走完,最终没有输出结果. 用time.Sleep(1) 等待回调; 但是更有效的是:使用类似join的东西来阻塞住主线。那就是信道。
其实,就是在做goroutine之间的内存共享:
ch := make(chan int)
<- ch // 阻塞main goroutine, 信道c被锁
当你向里面加数据或者存数据的话,都会锁死信道, 并且阻塞当前 goroutine, 也就是所有的goroutine(其实就main线一个)都在等待信道的开放(没人拿走数据信道是不会开放的),也就是死锁咯。
避免死锁: 把没取走的数据取走,没放入的数据放入, 因为无缓冲信道不能承载数据,那么就赶紧拿走!
无缓冲的信道是一批数据一个一个的「流进流出」; 缓冲信道则是一个一个存储,然后一起流出去
func simple_goroute() {
for i := 0; i < 10; i++ {
go func(id int) { //每一个goroute都是独立的空间, 除非是并发参数, 否则不共享;
fmt.Print(id)
}(i)
}
time.Sleep(1)
}
func chan_goroute() {
c := make(chan int, 2)
for i := 0; i < 10; i++ {
go func(c chan int, id int) {
fmt.Println(id)
c <- id
}(c, i)
}
for data := range c {
fmt.Println("chan", data)
if len(c) <= 0 {
break
} // 存取数据量一定要一致, 否则就是死锁;
}
close(c)
}
-> select
为了保证数据交换的正确性,必须使用互斥量对内存进行加锁,这种做法势必造成性能问题。
Go 语言提倡使用通信的方法代替共享内存,这里通信的方法就是使用通道(channel),在任何时候,同时只能有一个 goroutine 访问通道进行发送和获取数据。总是遵循先入先出(First In First Out)的规则
如果有一个或多个IO操作可以完成,则Go运行时系统会随机的选择一个执行,否则的话,如果有default分支,则执行default分支语句,
如果连default都没有,则select语句会一直阻塞,直到至少有一个IO操作可以进行
func adv_select_count(){
var count int32
c := make(chan int32)
for i:=0; i<100; i++{
go func() {
atomic.AddInt32(&count,1)
val := atomic.LoadInt32(&count)
c <- val
}()
}
flag := false
for {
select {
case data := <-c:
fmt.Println(data)
case <- time.After(time.Second * 10): // 这个是读取时间超过1秒, 无法用它来判断chan是否为空; 因此在这里判断的是同步超时的情况;
return
default: // 只能通过default判断chan 是否为空;
flag = true
}
if flag==true {break}
}
}
-> 使用context上下文管理 多层goroutine
func main(){
ctx, cancel := context.WithCancel(context.Background())
go doStuff(ctx)
time.Sleep(10 * time.Second)
cancel() //关闭该子服务, 将关闭信息传入context通道;
}
func doStuff(ctx context.Context){
for{
select{
case <- ctx.Done():
return
default:
time.Sleep(time.Second * 2)
time.AfterFunc(time.Second, func() {
fmt.Println("working")
})
}
}
}
-> 定时调度任务, time.After , 打点器, time.NewTicker , 计时器, time.NewTimer
func main() {
// 创建一个打点器, 每500毫秒触发一次
ticker := time.NewTicker(time.Millisecond * 500)
// 创建一个计时器, 2秒后触发
stopper := time.NewTimer(time.Second * 2)
// 声明计数变量
var i int
// 不断地检查通道情况
for {
// 多路复用通道
select {
case <-stopper.C: // 计时器到时了
fmt.Println("stop")
// 跳出循环
goto StopHere
case <-ticker.C: // 打点器触发了
// 记录触发了多少次
i++
fmt.Println("tick", i)
}
}
// 退出的标签, 使用goto跳转
StopHere:
fmt.Println("done")
}
############################### 额外 ######################################################################
包访问控制规则:
大写意味着这个函数/变量是可导出的
小写意味着这个函数/变量是私有的,包外部不能访问
-> 乐观锁与悲观锁
//原子访问
import "sync/atomic"
func GenID() int64 {
// 尝试原子的增加序列号
return atomic.AddInt64(&seq, 1)
}
for i:=0; i<10; i++ {
go GenID()
}
//互斥锁
var (
// 逻辑中使用的某个变量
count int
// 与变量对应的使用互斥锁
countGuard sync.Mutex
)
//线程安全的设定
func SetCount(c int) {
countGuard.Lock()
count = c
countGuard.Unlock()
}
-> string与nil , 类型转换
// 空接口转string
str, ok := data.(string)
if str, ok := data.(string); ok {
/* act on str */
} else {
/* not string */
}
-> 排序
/**
map 根据value排序
*/
func sortMap(mp map[string]int) {
var newMpVal = make([]int, 0)
var newMpKey = make([]string, 0)
for k, v := range mp {
newMp = append(newMpVal, v)
newMpKey = append(newMpKey, k)
}
sort.Ints(newMp)
for k, v := range newMp {
fmt.Println("根据value排序后的新集合》》 key:", newMpKey[k], " value:", v)
}
}
//自定义排序
func (a ByAge) Len() int { return len(a) }
func (a ByAge) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
func (a ByAge) Less(i, j int) bool { return a[i].Age < a[j].Age }
func main() {
people := []Person{
{"Bob", 31},
{"John", 42},
{"Michael", 17},
{"Jenny", 26},
}
fmt.Println(people)
sort.Sort(ByAge(people))
fmt.Println(people)
}
// 反转字符串
func reverseString(s string) string {
runes := []rune(s) //rune 专门处理utf-8字符串, 避免出现字节码
for from, to := 0, len(runes)-1; from < to; from, to = from+1, to-1 {
runes[from], runes[to] = runes[to], runes[from]
}
return string(runes)
}
golang的基本类型不能赋值nil:
bool
int
uint
float
complex
byte
rune
string
struct
golang的非基本类型都能赋值nil:
array
slice
map
chan
func
interface
golang的指针类型也能赋值nil:
pointer
-> 反射
import "reflect"
var a int
typeOfA := reflect.TypeOf(a)
fmt.Println(typeOfA.Name(), typeOfA.Kind())
//通过指针获取元素类型
ins := &cat{}
typeOfCat := reflect.TypeOf(ins)
typeOfCat = typeOfCat.Elem()
//获取结构体字段
typeOfCat.NumField()
typeOfCat.Field(i)
typeOfCat.FieldByName("Type")
r := reflect.ValueOf(a)
r.SetInt(1) //通过反射改变值
.IsValid() 判断是否为nil
reflect.ValueOf(s).MethodByName("").IsValid()
//通过反射创建对象
// 取变量a的反射类型对象
typeOfA := reflect.TypeOf(a)
// 根据反射类型对象创建类型实例
aIns := reflect.New(typeOfA)
//通过反射调用方法
// 将函数包装为反射值对象
funcValue := reflect.ValueOf(add)
// 构造函数参数, 传入两个整型值
paramList := []reflect.Value{reflect.ValueOf(10), reflect.ValueOf(20)}
// 反射调用函数
retList := funcValue.Call(paramList)
-> 编译与安装
$ export GOPATH=/home/davy/golangbook/code
$ go install chapter11/goinstall
-> 指针操作详解
在 main(){
[]*Profile{{Name: "张三", Age: 30, Married: true},} 得到 [] &{张三 30 true} list[0].Age = 25
[]Profile{{Name: "张三", Age: 30, Married: true},} 得到 [] {张三 30 true} list[0].Age = 25
&[]Profile{{Name: "张三", Age: 30, Married: true},} 得到 *[] {张三 30 true} (*list)[0].Age = 25
指针加到 数组之前, &[]Profile 是将整个数组作为 内存地址; 将指针加到 数组之后 []*Profile 是将每个元素作为内存地址;
}
在func函数中
// 整个数组作为值传递
func take(p [4]Profile) [4]Profile {
p[0].Age = 25
p[3] = Profile{Name: "李四", Age: 21}
return p
}
take(list2)
fmt.Println(list2)
[{张三 25 true} {李四 21 false} {王麻子 21 false}]
数组本质就是指针, 记录的是内部元素的内存地址, 所以元素上的值可改变, 而数组本身无法改变; 所以只能通过重新赋值取到新值, list_take = take(list2)
// 整个数组作为指针传递
func take(p *[4]Profile) *[4]Profile {
p[0].Age = 25
p[3] = Profile{Name: "李五", Age: 25}
return p
}
take(list2)
fmt.Println(*list2)
[{张三 25 true} {李四 21 false} {王麻子 21 false} {李五 25 false}]
// [4]*Profile{} 只是把数组中的每个元素作为指针传递, 创建时创建指针即可, p[3] = &Profile{Name: "李五", Age: 25}, 依然不能作用于整体数组
take(list2)
fmt.Println(list2)
[0x110460d0 0x110460e0 0x110460f0 <nil>]
对于map, 属于天然的内部指针传递
func test(m *map[string]string){
(*m)["a"] = "a"
}
func test(m map[string]string){
m["a"] = "a"
}
两者一致;