• 【go入门学习笔记】


    1、走进Golang

    执行过程:

    .go ->go build -> .exe -> 执行 ./exe | go run hello.go

    语法、风格:

    行尾自动加;(体现简洁性)一行 < 80 字符;定义未使用,编译失败

    2、变量与数据类型

    变量定义、初始化、使用:

    var gog int ; var gog int = 2; var gog = 2; gog := 2
    
    • 1

    数据类型:

    //基本数据类型
    bool
    string
    intint8int16int32int64
    uintuint8uint16uint32uint64uintptr
    byte // uint8 的别名
    rune // int32 的别名 代表一个 Unicode 码
    float32float64
    complex64complex128
    
    //派生数据类型
    * []int struct pipline func slice interface map
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    1. go采用的是 Unicode 字符集,使用 UTF-8 编码方案(中文字符三个字节)。
    2. 字符本质是整数,可直接参与运算。Printf(“%v”, byte) 格式化输出
    3. 字符串不可修改,
    4. **·复杂字符串·**使用。go语言独特

    数据类型转换:

    why: 强类型语言

    //整型之间转换 
    var a int8  var b int64   b = int64(a)
    // 基本类型转string
    1var n1 float = 1.00 ;var s1 string = fmt.Sprintf("%f", n1)
    2var n1 int = 18;  var s2 string = strconv.FormatFloat(n2,'f',9,64) //9 表示 小数保留9位,64 表示float64类型
    // string 类型转 other
    var s1 = "88" ;b, err = stronv.ParseInt (s1)
    ParseFloat ParseBool; ParseUnit
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    注意事项
    1. _ 空标识符,忽略 返回值或 包
    2. 命名采用驼峰,首字母大写可被其他包访问,小写只能在本包中使用。(权限控制)
    3. 包名从 $GOPATH/src/ 后开始计算,使用/分隔

    3、运算符

    ++ 、-- 只能单独操作,

    4、流程控制

    注意事项:

    1. switch 穿透, fallthrougth 会继续执行下一个 case
    2. 死循环 for {} for ;; {
    3. for 循环是字节遍历,UTF-8编码格式,中文三个字节
    4. for key, value := range stringName

    5、函数

    特点:

    1. 不支持重载
    2. 基本数据类型和数组默认是值传递,值拷贝。 也可传递地址,类似应用传递。
    3. 支持可变参数 func test(agrs …int) ; test(6,5,5,4,3,2)
    4. 函数也是一个数据类型,可以赋值给一个变量(函数式编程),也可以作为形参并调用
    5. 为了简化数据类型定义,Go 支持自定义数据类型,type myInt int

    init 函数 ,执行顺序

    1. 当前包:全局变量->inti->main
    2. import test 包:test.全局变量->test.init->当前.全局变量->当前.init->当前main

    匿名函数

    //赋值
    resume := func (num1 int , num2 int) int {
        return num1 + num2
    } 
    //赋值并执行
    resume := func (num1 int , num2 int) int {
        return num1 + num2
    }(10,20)
    // 赋值 给全局变量, 则全局有效
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    闭包:函数与其应用环境组合的整体

    // 本质:是匿名函数,这个函数引入了外界的变量/参数(匿名函数+应用外界变量、参数=闭包)
    
    func sum() func(num int) int {
        var sum int = 0  // 变量 与 匿名函数 可以看成一个整体(函数类型的对象), 缓存在内存中,可以对其进行操作  
        return func (num int) int {
            sum := sum + num
            return sum
        }
    }
    var sum = sum()
    sum(5) = 5
    sum(7) = 12
    //使用场景:闭包可以保留上次引用的某个值,传入一次就可以反复使用了
    func sum(num2 int) func(num int) int {
        var sum int = 0  // 变量 与 匿名函数 可以看成一个整体(函数类型的对象), 缓存在内存中,可以对其进行操作  
        return func (num int) int {
            sum := sum + num + num2
            return sum
        }
    }
    var sumF = sumFor(2)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    defer

    //defer 后的语句压入栈中,等{} 执行完后,再执行defer语句 ****即使程序恐慌中断, 也会执行****
    defer fmt.Println("num=", num1)// 会将值压入栈中,不会随着函数后面的变化而变化,
    //使用场景, 关闭 一些资源,  file 、connect (避免后面忘记关闭)
    
    • 1
    • 2
    • 3

    内置函数

    len(str)
    []rune(str) //切片
    strconv.Atoi, Itoa//整型与string的转换
    time.Format("2006/01/02 15/04/05")
    num := new(int)
    
    • 1
    • 2
    • 3
    • 4
    • 5

    6、错误处理

    场景一:程序中出现错误/恐慌以后,程序被中断,无法继续执行

    //内建哈数 recover 
    //采用 defer recover 捕获异常
    defer func(){
        err := recover()
        if err!= nil {
            fmt.Println(err)
        }
    }()
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    场景二:程序出现错误以后,后续代码就没有必要执行,想让程序中断,退出程序:

    func main() {
    	err := testPanic()
    	if err != nil {
    		fmt.Println(err)
    		panic(err) //中断程序
    	}
    	fmt.Println("result")
    }
    func testPanic() error {
    	num1 := 10
    	num2 := 0
    	if num2 == 0 {
    		return errors.New("除数不能为0")
    	} else {
    		result := num1 / num2
    		fmt.Println(result)
    		return nil
    	}
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    7、数组

    定义、初始化、使用

    //定义格式
    var name [size]type    ex:   var nums [4]int
    // 初始化
    var arr1 [3]int = [3]int{3,6,9}//
    var arr2 = [3]int{3,6,9}//
    var arr2 = [...]int{3,6,9}//
    var arr2 = [...]int{2:3,1:6,0:9}//
    // 遍历
    for ;;; {}
    for key, value := range arr1 {}
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    注意事项:

    1. 长度为类型的一部分, [3]int 与 [6]int type 不同
    2. go中数组 属于 值类型, 默认是 值传递
    3. 需要传入指正来对数组进行操作

    8、切片

    切片的引入

    1. 切片(slice)是golang中一种特有的数据类型
    2. 数组有特定的用处,但是却有一些呆板(数组长度固定不可变),所以在 Go 语言的代码里并不是特别常见。相对的切片却是随处可见的,切片是一种建立在数组类型之上的抽象,它构建在数组之上并且提供更强大的能力和便捷。
    3. 切片(slice)是对数组一个连续片段的引用,所以切片是一个引用类型。这个片段可以是整个数组,或者是由起始和终止索引标识的一些项的子集。需要注意的是,终止索引标识的项不包括在切片内。切片提供了一个相关数组的动态窗口。
    4. 切片的语法:
      var 切片名 []类型 = 数组的一个片段引用

    切片的定义、遍历

    //方式一 : 引用数组
    var arr [3]int = [3]int{3,6,9}
    slice := arr[:]
    //方式二:make 内置函数 参数:type ,len, cap
    slice := make([]int, 3, 3)
    //PS : make底层创建一个数组,对外不可见,所以不可以直接操作这个数组,要通过slice去间接的访问各个元素,不可以直接对数组进行维护/操作
    //方式三:定义一个切片 直接 指定具体数组 类似make 
    slice := []int{1,3,7}
    slice := []int{}
    //====================遍历=======================
    for 
    for range
    //===================append======================
    var a []int
    a = append(a, 1) // 追加1个元素
    a = append(a, 1, 2, 3) // 追加多个元素, 手写解包方式
    a = append(a, []int{1,2,3}...) // 追加一个切片, 切片需要解包
    //===================copy  返回元素的数量==========
    copy( destSlice []T, srcSlice []T) int
    slice1 := []int{1, 2, 3, 4, 5}
    slice2 := []int{5, 4, 3}
    copy(slice2, slice1) // 只会复制slice1的前3个元素到slice2中
    copy(slice1, slice2) // 只会复制slice2的3个元素到slice1的前3个位置
    //================delete==========================
    //从开头位置删除
    a = []int{1, 2, 3}
    a = a[N:] // 删除开头N个元素
    a = []int{1, 2, 3}
    a = append(a[:0], a[N:]...) // 删除开头N个元素(a[0]地址不变)
    //还可以用 copy() 函数来删除开头的元素:
    a = []int{1, 2, 3}
    a = a[:copy(a, a[N:])] // 删除开头N个元素
    //从中间位置删除
    //对于删除中间的元素,需要对剩余的元素进行一次整体挪动,同样可以用 append 或 copy 原地完成:
    a = []int{1, 2, 3, ...}
    a = append(a[:i], a[i+1:]...) // 删除中间1个元素
    a = append(a[:i], a[i+N:]...) // 删除中间N个元素
    a = a[:i+copy(a[i:], a[i+1:])] // 删除中间1个元素
    a = a[:i+copy(a[i:], a[i+N:])] // 删除中间N个元素
    //从尾部删除
    a = []int{1, 2, 3}
    a = a[:len(a)-1] // 删除尾部1个元素
    a = a[:len(a)-N] // 删除尾部N个元素
    //删除开头的元素和删除尾部的元素都可以认为是删除中间元素操作的特殊情况, 开头 
    
    • 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

    9、映射

    map 介绍

    //1、定义:var name map[keytype]valuetype
    var mymap map[int]string  |   var mymap = make(map[int]string) 
    //2、初始化:
    mymap = map[int]string{20:"hello",30:"go"}      |       a := make(map[int]string)   ==  a := map[int]string{} 
    
    //注意:可以使用 make(),但不能使用 new() 来构造 map,如果错误的使用 new() 分配了一个引用对象,会获得一个空引用的指针,相当于声明了一个未初始化的变量并且取了它的地址:
    
    //3、map 容量
    map2 := make(map[string]float, 100)
    //当 map 增长到容量上限的时候,如果再增加新的 key-value,map 的大小会自动加 1,所以出于性能的考虑,对于大的 map 或者会快速扩张的 map,即使只是大概知道容量,也最好先标明。
    
    //4、用切片作为 map 的值(一个key=>n个value), 父进程(pid 为整形)作为 key,所有的子进程(以所有子进程的 pid 组成的切片)作为 value
    mp1 := make(map[int][]int)
    mp2 := make(map[int]*[]int)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    map 遍历、删除、清空

    	myMap := make(map[string]int)
    	myMap["route"] = 66
    	myMap["brazil"] = 4
    	myMap["china"] = 960
    	//删除
    	delete(scene, "route")
    	//遍历
    	for k, v := range myMap {
    		fmt.Println(k, v)
    	}
    	//清空  无  可以  make一个新的空的map
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    sync.Map

    // 并发读写  线程不安全, 运行代码会报错 fatal error: concurrent map read and map write
    // 加锁效率低
    //Go语言在 1.9 版本中提供了一种效率较高的并发安全的 sync.Map,sync.Map 和 map 不同,不是以语言原生形态提供,而是在 sync 包下的特殊结构。
    
     var scene sync.Map
     // 将键值对保存到sync.Map
     scene.Store("london", 100)
     // 从sync.Map中根据键取值
     fmt.Println(scene.Load("london"))
     // 根据键删除对应的键值对
     scene.Delete("london")
     // 遍历所有sync.Map中的键值对
     scene.Range(func(k, v interface{}) bool {
     fmt.Println("iterate:", k, v)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    list(列表)、nil

    //初始化列表
    //1) 通过 container/list 包的 New() 函数初始化 list
    a := list.New()
    //2) 通过 var 关键字声明初始化 list
    var a list.List
    
    
    //nil 是 map、slice、pointer、channel、func、interface 的零值  零值是Go语言中变量在声明之后但是未初始化被赋予的该类型的一个默认值。
    var s1 []int
    fmt.Println(s1 == nil)
    PS D:\code> go run .\main.go
    true
    
    不同类型的 nil 值占用的内存大小可能是不一样的
    var p *struct{}
    fmt.Println( unsafe.Sizeof( p ) ) // 8
    var s []int
    fmt.Println( unsafe.Sizeof( s ) ) // 24
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    10、面向对象

    介绍

    1. 没有类的概念,支持面向对象编程特性
    2. 基于 struct 来实现 oop 特性
    3. Golang 面向对象编程 很简洁,去掉了传统oop语言的方法重载、构造函数、隐藏this指针。保留了 继承、分装、多态的特性

    结构体

    1. 引入:方便统一管理变量

    2. 定义:type student struct {name string Age int}

    3. 使用:

    4. var stu student 
      var stu = student{"wh":10}
      var stu *student = new(student)    stu.name = "who";stu.age = 10
      var stu *student = &student{"go":10}
      
      
      • 1
      • 2
      • 3
      • 4
      • 5

    结构体之间的转换

    1. 结构体是用户单独定义的类型,和其它类型进行转换时需要有完全相同的字段(名字、个数和类型)

      type Student struct {
              Age int
      }
      type Person struct {
              Age int
      }
      func main(){
              var s Student = Student{10}
              var p Person = Person{10}
              s = Student(p)
              fmt.Println(s)
              fmt.Println(p)
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
    2. 结构体进行type重新定义(相当于取别名),Golang认为是新的数据类型,但是相互间可以强转

    type Student struct {
            Age int
    }
    type Stu Student
    func main(){
            var s1 Student = Student{19}
            var s2 Stu = Stu{19}
            s1 = Student(s2)
            fmt.Println(s1)
            fmt.Println(s2)
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    方法的引入

    //(1)结构体类型是值类型,在方法调用中,遵守值类型的传递机制,是值拷贝传递方式
    type Person struct {
    	Name string
    }
    
    func (p *Person) test() {
    	(*p).Name = "露露" // (*p)=>p
    	fmt.Println((*p).Name)//(*p)=>p
    }
    func main() {
    	var p Person
    	(&p).test() //(&p)=>p
    }
    
    底层编译器做了优化,底层会自动帮我们加上 &  *
    
    //(2)如果一个类型实现了String()这个方法,那么fmt.Println默认会调用这个变量的String()进行输出
    
    以后定义结构体的话,常定义String()作为输出结构体信息的方法,在fmt.Println会自动调用
            //传入地址,如果绑定了String方法就会自动调用
            fmt.Println(&stu)
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22

    方法和函数的区别

    //1、调用方式
    //2、方法需要绑定类型
    //3、函数 强参数类型(参数类型一致);对于方法来说,接收者为值类型,可以传入指针类型,接受者为指针类型,可以传入值类型。
    func method01(s Student){
            fmt.Println(s.Name)
    }
    func (s Student) test01(){
            fmt.Println(s.Name)
    }
    func main(){
            var s Student = Student{"丽丽"}
            method01(s)
            //method01(&s)错误
            s.test01()
            (&s).test01()//虽然用指针类型调用,但是传递还是按照值传递的形式(把sudent 对象拷贝一份传过去)
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    结构体的赋值

    // 1、按顺序
    var s1 Student = Student{"小李"19}
    // 2、按类型
    var s2 Student = Student{name:"小李",age:19}
    
    • 1
    • 2
    • 3
    • 4

    封装、继承、接口、多态

    //结构体首字母小写,跨包访问没问题:---》工厂模式(通过一个方法返回 实例对象)
    
    // 封装:把抽象出来的字段和对字段的操作 封装在一起,数据被保护在内部,程序的其他包只有通过被授权的操作方法才能对字段操作
    	why: 隐藏实现细节;可以对数据进行验证
    	how: 字段,结构体首字母小写;结构体所在包提供一个工厂模式函数首字母大写;提供 Set,Get 方法对属性判断赋值取值等操作
    // 继承:多个结构体存在相同的属性和方法时,抽象出公共的部分,其他结构体通过嵌套此结构的匿名结构体,即可获得公共的属性和方法 ,从而实现继承的特性
    	why: 提高代码的复用性,扩展性
    	how: type C struct{A}
    	注意事项:
    		组合模式:字段的类型为 A 结构体, 与嵌套有区别 type C struct{name A} 
    		嵌入的结构体类别:自定义类型;基本数据类型;指针 type C struct{A int *B}
    		Golang中支持多继承: type C struct{A int *B}
    		结构体可以使用嵌套匿名结构体所有的字段和方法,编译器采用就近访问原则访问
    // 接口:定义规则、定义规范,定义某种能力
    	总结:只要实现全部方法即可,和实际接口耦合性很低,比Java松散得多
    		接口本身不能创建实例,但是可以指向一个实现了该接口的自定义类型的变量
    		只要是自定义数据类型,就可以实现接口,不仅仅是结构体类型
    		接口可以继承多个接口,当时此接口时还需实现全部继承的接口方法
    // 多态:变量(实例)具有多种形态  如:func greet(s SayHello){s.sayHello()} 一个函数可以接受任意实现了SayHello能力的实例对象,
    		s 通过上下文来时识别具体是什么类型的实例,就体现出多态
    	how: 接口参数 (s SayHello)或者是多态参数;多态数组 [3]SayHello{A,B,C}
    // 断言:判断是否是该类型的变量 value, ok := element.(T)  value变量的值,ok bool,element是interface变量,T是断言的类型。
    		switch s.(type){//type属于go中的一个关键字,固定写法
                    case Chinese:
                            ch := s.(Chinese)
                            ch.niuYangGe()
                    case American:
                            am := s.(American)
                            am.disco()
            }
    
    		
    
    • 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

    11、文件操作

    //文件复制
    func main(){
            //定义源文件:
            file1Path := "d:/Demo.txt"
            //定义目标文件:
            file2Path := "d:/Demo2.txt"
            //对文件进行读取:
            content,err := ioutil.ReadFile(file1Path)
            if err != nil {
                    fmt.Println("读取有问题!")
                    return
            }
            //写出文件:
            err = ioutil.WriteFile(file2Path,content,0666)
            if err != nil {
                    fmt.Println("写出失败!")
            }
    }
    //带缓存读写
        //打开文件:
            file,err := os.Open("d:/Test.txt")
            if err != nil {//打开失败
                    fmt.Println("文件打开失败,err=",err)
            }
            //当函数退出时,让file关闭,防止内存泄露:
            defer file.Close()
            //创建一个流:
            reader := bufio.NewReader(file)
            //读取操作:
            for {
                    str,err := reader.ReadString('\n')//读取到一个换行就结束
                    if err == io.EOF {//io.EOF 表示已经读取到文件的结尾
                            break
                    }
                    //如果没有读取到文件结尾的话,就正常输出文件内容即可:
                    fmt.Println(str)
            }
      	//写入文件操作:
            //打开文件:
            file , err := os.OpenFile("d:/Demo.txt",os.O_RDWR | os.O_APPEND | os.O_CREATE,0666)
            if err != nil {//文件打开失败
                    fmt.Printf("打开文件失败",err)
                    return
            }
            //及时将文件关闭:
            defer file.Close()
            //写入文件操作:---》IO流---》缓冲输出流(带缓冲区)
            writer := bufio.NewWriter(file)
            for i := 0; i < 10;i++ {
                    writer.WriteString("你好 马士兵\n")
            } 
            //流带缓冲区,刷新数据--->真正写入文件中:
            writer.Flush()
            s :=os.FileMode(0666).String()
            fmt.Println(s)
    
    • 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
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55

    12、协程

    //1、WaitGroup:解决主线程在子协程结束后自动结束
    //定义一个变量:
    var totalNum int
    var wg sync.WaitGroup //只定义无需赋值
    //加入互斥锁:
    var lock sync.Mutex
    func add(){
            defer wg.Done()
            for i := 0 ;i < 100000;i++{
                    //加锁
                    lock.Lock()
                    totalNum = totalNum + 1
                    //解锁:
                    lock.Unlock()
            }
    }
    func sub(){
            defer wg.Done()
            for i := 0 ;i < 100000;i++{
                    //加锁
                    lock.Lock()
                    totalNum = totalNum - 1
                    //解锁:
                    lock.Unlock()
            }
    }
    func main(){
            wg.Add(2)
            //启动协程
            go add()
            go sub()
            wg.Wait()
            fmt.Println(totalNum)
    }
    //2、sync.Mutex: 解决互斥问题
    //3、RWMutex(读写锁):在读的时候,数据之间不产生影响,   写和读之间才会产生影响(提高效率)
    
    // channel : 数据结构-队列;先进先出;自身线程安全
    	定义:var intchan chan int    ***    var intchan chan<- int  <-chan  只读只写
    	初始化:intchan = make(chan,int,3)
    	使用:写 intchan<- 8   取 a := <-intchan
    	关闭:close(intchan)
    	注意事项:应用类型;空时再取就会报错,满时再写会报错
    			for range 遍历,遍历前要关闭chan (会报错,deadlock)
    			
    	阻塞:当管道只写入数据,没有读取,就会出现阻塞;写的快,读的满(管道读写频率不一致),不会出现阻塞问题:
    	多路复用:select 
    			case后面必须进行的是io操作,不能是等值,随机去选择一个io操作
    	        select{
                    case v := <-intChan :
                            fmt.Println("intChan:",v)
                    case v := <-stringChan :
                            fmt.Println("stringChan:",v)
                    default:
                            fmt.Println("防止select被阻塞")
            	}
         **** 多个协程工作,其中一个协程出现panic,导致程序崩溃
         	解决办法:利用refer+recover捕获panic进行处理,即使协程出现问题,主线程仍然不受影响可以继续执行。
         	defer func(){
                    err := recover()
                    if err != nil{
                            fmt.Println("devide()出现错误:",err)
                    }
            }()
    
    • 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
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64

    13、网络编程

    客户端

    package main
    import(
            "fmt"
            "net" //所需的网络编程全部都在net包下
            "bufio"
            "os"
    )
    func main(){
            //打印:
            fmt.Println("客服端启动。。")
            //调用Dial函数:参数需要指定tcp协议,需要指定服务器端的IP+PORT
            conn,err := net.Dial("tcp","127.0.0.1:8888")
            if err != nil {//连接失败
                    fmt.Println("客户端连接失败:err:",err)
                    return
            }
            fmt.Println("连接成功,conn:",conn)
            //通过客户端发送单行数据,然后退出:
            reader := bufio.NewReader(os.Stdin)//os.Stdin代表终端标准输入
            //从终端读取一行用户输入的信息:
            str,err := reader.ReadString('\n')
            if err != nil {
                    fmt.Println("终端输入失败,err:",err)
            }
            //将str数据发送给服务器:
            n,err := conn.Write([]byte(str))
            if err != nil{
                    fmt.Println("连接失败,err:",err)
            }
            fmt.Printf("终端数据通过客户端发送成功,一共发送了%d字节的数据,并退出",n)
    }
    
    • 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

    服务端

    package main
    import(
            "fmt"
            "net" //所需的网络编程全部都在net包下
    )
    func process(conn net.Conn){
            //连接用完一定要关闭:
            defer conn.Close()
            for{
                    //创建一个切片,准备:将读取的数据放入切片:
                    buf := make([]byte,1024)
                    //从conn连接中读取数据:
                    n,err := conn.Read(buf)
                    if err != nil{
                            return
                    }
                    //将读取内容在服务器端输出:
                    fmt.Println(string(buf[0:n]))
            }
    }
    func main(){
            //打印:
            fmt.Println("服务器端启动了。。")
            //进行监听:需要指定服务器端TCP协议,服务器端的IP+PORT
            listen,err := net.Listen("tcp","127.0.0.1:8888")
            if err != nil{//监听失败
                    fmt.Println("监听失败,err:",err)
                    return
            }
            //监听成功以后:
            //循环等待客户端的链接:
            for{
                    conn,err2 := listen.Accept()
                    if err2 != nil {//客户端的等待失败
                            fmt.Println("客户端的等待失败,err2:",err2)
                    }else{
                            //连接成功:
                            fmt.Printf("等待链接成功,con=%v ,接收到的客户端信息:%v \n",conn,conn.RemoteAddr().String())
                    }
                    //准备一个协程,协程处理客户端服务请求:
                    go process(conn)//不同的客户端的请求,连接conn不一样的
            }
    }
    
    • 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

    14、反射

    why:

    1. 反射可以在运行时动态获取变量的各种信息,比如变量的类型,类别等信息。
    2. 如果是结构体变量,还可以获取到结构体本身的信息(包括结构体的字段、方法)。
    3. 通过反射,可以修改变量的值,可以调用关联的方法。

    how:相关的函数

    1. reflect.TypeOf(变量名),获取变量的类型,返回reflect.Type类型
    2. reflect.ValueOf(变量名),获取变量的值,返回reflect.Value类型(reflect.Value是一个结构体类型),通过reflect.Value,可以获取到关于该变量的很多信息。
    //1、 基本类型
    //利用一个函数,函数的参数定义为空接口:
    func testReflect(i interface{}){//空接口没有任何方法,所以可以理解为所有类型都实现了空接口,也可以理解为我们可以把任何一个变量赋给空接口。
            //1.调用TypeOf函数,返回reflect.Type类型数据:
            reType := reflect.TypeOf(i)
            fmt.Println("reType:",reType)
            fmt.Printf("reType的具体类型是:%T",reType)
            //2.调用ValueOf函数,返回reflect.Value类型数据:
            reValue :=reflect.ValueOf(i)
            fmt.Println("reValue:",reValue)
            fmt.Printf("reValue的具体类型是:%T",reValue)
            //num1 := 100
            //如果真想获取reValue的数值,要调用Int()方法:返回v持有的有符号整数
            num2 := 80 + reValue.Int()
            fmt.Println(num2)
            //reValue转成空接口:
            i2 := reValue.Interface()
            //类型断言:
            n := i2.(int)
            n2 := n + 30
            fmt.Println(n2)
    }
    func main(){
            var num int = 100
            testReflect(num)
    }
    
    //1、 对结构体类型进行反射
    
    //利用一个函数,函数的参数定义为空接口:
    func testReflect(i interface{}){//空接口没有任何方法,所以可以理解为所有类型都实现了空接口,也可以理解为我们可以把任何一个变量赋给空接口。
            //1.调用TypeOf函数,返回reflect.Type类型数据:
            reType := reflect.TypeOf(i)
            fmt.Println("reType:",reType)
            fmt.Printf("reType的具体类型是:%T",reType)
            //2.调用ValueOf函数,返回reflect.Value类型数据:
            reValue :=reflect.ValueOf(i)
            fmt.Println("reValue:",reValue)
            fmt.Printf("reValue的具体类型是:%T",reValue)
            //reValue转成空接口:
            i2 := reValue.Interface()
            //类型断言:
            n,flag := i2.(Student)
            if flag == true {//断言成功
                    fmt.Printf("学生的名字是:%v,学生的年龄是:%v",n.Name,n.Age)
            }
            
    }
    //定义学生结构体:
    type Student struct{
            Name string
            Age int
    }
    func main(){
            stu := Student{
                    Name : "丽丽",
                    Age : 18,	
            }
            testReflect(stu)
    }
    
    // 3、获取变量的类别1)reflect.Type.Kind()2)reflect.Value.Kind()
    
    Type是类型, Kind是类别,Type和Kind 可能是相同的,也可能是不同的.
    比如:var num int = 10 num的Type是int , Kind也是int 
    比如:var stu Studentstu的 Type是 pkg1.Student , Kind是struct
    
    
    // 4、修改基本数据类型的值:
       var num int = 100
       testReflect(&num) //传入指针地址
       //通过SetInt()来改变值:
       reValue.Elem().SetInt(40)
    
    • 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
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75

    通过反射操作结构体的属性和方法

    package main
    import(
            "fmt"
            "reflect"
    )
    //定义一个结构体:
    type Student struct{
            Name string
            Age int
    }
    //给结构体绑定方法:
    func (s Student) CPrint(){
            fmt.Println("调用了Print()方法")
            fmt.Println("学生的名字是:",s.Name)
    }
    func (s Student) AGetSum(n1,n2 int) int{
            fmt.Println("调用了AGetSum方法")
            return n1 + n2
    }
    func (s Student) BSet(name string,age int){
            s.Name = name
            s.Age = age
    }
    //定义函数操作结构体进行反射操作:
    func TestStudentStruct(a interface{}){
            //a转成reflect.Value类型:
            val := reflect.ValueOf(a)、、
            fmt.Println(val)
            //通过reflect.Value类型操作结构体内部的字段:
            n1 := val.NumField()
            fmt.Println(n1)
            //遍历-获取具体的字段:
            for i := 0; i < n1;i++{
                    fmt.Printf("第%d个字段的值是:%v",i,val.Field(i))
            }
            fmt.Println()
            //通过reflect.Value类型操作结构体内部的方法:
            n2 := val.NumMethod()
            fmt.Println(n2)
            //调用CPrint()方法:
            //调用方法,方法的首字母必须大写才能有对应的反射的访问权限
            //方法的顺序按照ASCII的顺序排列的,a,b,c,,,,,,索引:0,1,2,,,,,
            val.Method(2).Call(nil)
            //调用AGetSum方法:
            //定义Value的切片:
            var params []reflect.Value
            params = append(params,reflect.ValueOf(10))
            params = append(params,reflect.ValueOf(20))
            result := val.Method(0).Call(params)
            fmt.Println("AGetSum方法的返回值为:",result[0].Int())
    }
    func main(){
            //定义结构体具体的实例:
            s := Student{
                    Name : "丽丽",
                    Age : 18,
            }
            //调用TestStudentStruct:
            TestStudentStruct(s)
    }
    
    
    • 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
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61

    通过反射修改变量

    package main
    import(
            "fmt"
            "reflect"
    )
    //定义一个结构体:
    type Student struct{
            Name string
            Age int
    }
    //给结构体绑定方法:
    func (s Student) CPrint(){
            fmt.Println("调用了Print()方法")
            fmt.Println("学生的名字是:",s.Name)
    }
    func (s Student) AGetSum(n1,n2 int) int{
            fmt.Println("调用了AGetSum方法")
            return n1 + n2
    }
    func (s Student) BSet(name string,age int){
            s.Name = name
            s.Age = age
    }
    //定义函数操作结构体进行反射操作:
    func TestStudentStruct(a interface{}){
            //a转成reflect.Value类型:
            val := reflect.ValueOf(a)
            fmt.Println(val)
            n := val.Elem().NumField()
            fmt.Println(n)
            //修改字段的值:
            val.Elem().Field(0).SetString("张三")
    }
    func main(){
            //定义结构体具体的实例:
            s := Student{
                    Name : "丽丽",
                    Age : 18,
            }
            //调用TestStudentStruct:
            TestStudentStruct(&s)
            fmt.Println(s)
    }
    
    
    • 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
  • 相关阅读:
    Netty(三)- NIO三大组件之Channel
    objective-c 基础学习
    为什么越来越多的企业在会议室使用无线流媒体网关?
    nginx配置实例-负载均衡
    SD-WAN组网相较传统组网的优点
    刷题记录(NC19246 数据结构)
    数额结构(6.1~6.8)
    八股文-- 2022.08.31
    css样式:禁用文字选中状态,禁止图片拖动
    Netty简略了解与 源码分析
  • 原文地址:https://blog.csdn.net/dian_ke_nan_/article/details/126918998