zoukankan      html  css  js  c++  java
  • Golang-基础

    ###应用: 数据接口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 ", math.Pi)
    fmt.Printf("%T",p) //打印类型
    fmt.Printf("%p",p) //打印地址
    fmt.Printf("%+v", c) //打印结构体
    str := fmt.Sprintf("%v ",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"
    }

    两者一致;

  • 相关阅读:
    文本属性和属性连写
    并集选择器
    子代选择器
    后代选择器
    交集选择器
    xpath helper 表格爬取
    爬取xiachufang图片试手
    bs4 beautifullsoup网页内容选择器
    requests第三方库使用 抓取
    python 爬虫学习
  • 原文地址:https://www.cnblogs.com/ruili07/p/11458842.html
Copyright © 2011-2022 走看看