• Go学习[合集]


    Go学习-Day1

    • 个人博客CSDN博客

    • 打卡。

    • Go语言的核心开发团队:

      • Ken Thompson (C语言,B语言,Unix的发明者,牛人)
      • Rob Pike(UTF-8发明人)
      • Robert Griesemer(协助HotSpot编译器,Js引擎V8)
    • Go语言有静态语言的安全和性能和动态语言开发维护的效率。

    • Go语言特性

      • 继承了C语言很多概念(Ken爷!)包括指针。
      • 引入包的概念
      • 垃圾回收机制
      • 天然并发(核心)
      • 管道通信机制(Channel)
      • 函数可以有多返回值
      • 新增切片slice,延时执行defer
    • Hello World (一定要注意目录结构!)

    • 通过go build来编译go文件,得到exe文件

    • 关于文件夹架构,一定要准确,不然找不到包。

    • %GOPATH%

      • src

        • go_code

          • project00 //项目名open这个项目

          • project01

            • main//包
            • pkg//其他包
    • 注意配置PATH,GOPATH(项目的位置),GOROOT(SDK的位置)

    • 并且配置一些settings里面相应的变量


    • 琐碎的细节
      • go语言没有分号结尾,因此一行就写一条语句
      • 定义的变量和导入的包如果没有用到就无法通过编译
      • 块注释不能嵌套,尽量使用行注释

    Go学习-Day2

    标识符

    • 驼峰法,首字母大写可以在其他包里使用,首字母小写只能在本包内使用
    • 跨包使用,的import地址从src的子目录开始,src以及src所在的GOPATH自动补全

    变量基础语法

    • 定义变量

    • var i int = 10
      
      • 1
    • var关键字+变量名+变量类型

    • var i = 10
      
      • 1
    • 自动推断类型

    • i := 10
      
      • 1
    • 简略写法


    • 对应的,可以声明多个变量

    • var a, b, c int = 1, "a", 2
      
      • 1
    • var a, str1, b = 1, "a", 2
      
      • 1
    • a, str1, b := 1, "a", 2
      
      • 1

    • var (
      	i = 1
      	j = 2
      )
      
      • 1
      • 2
      • 3
      • 4
    • 另一种声明方法,开发中常用

    • import (
      	"fmt"
          "unsafe"
      )
      
      • 1
      • 2
      • 3
      • 4
    • 导包也可以类似这样


    • 不能改变变量的类型,例如开始赋值整数,后来又赋值浮点数。
    • 默认值,数默认为0,字符串默认为空串

    字符串类型

    • 利用UTF-8编码,支持中文

    • go中字符串是常量,无法修改

    • 引号

      • 双引号"" :会识别转义字符
      • 反引号``:不识别转义字符(防止SQL注入之类的?)
    • 加号拼接,可以分行写(加号放行尾)

    类型转换

    • go不会自动转换类型,需要显式转换

    • var i int = 1
      var j float32 = float32(i)
      
      • 1
      • 2

    string和其他基本类型转换

    其他类型转string

    func main() {
    	var a int = 10
    	var b float32 = 3.14
    	var s string = fmt.Sprintf("%d %.2f", a, b)
    	
    	fmt.Println(s)
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    string转其他类型

    b, err := strconv.ParseBool("true")
    f, err := strconv.ParseFloat("3.1415", 64)//返回64位要 强转
    i, err := strconv.ParseInt("-42", 10, 64)//进制 和 位数
    u, err := strconv.ParseUint("42", 10, 64)
    
    • 1
    • 2
    • 3
    • 4
    • 返回值有两个
    • 可以使用_代替err,下划线是特殊的变量,表示忽略返回值。
    • 如果无法转换则返回0
    • 学英语:parse是分析的意思,strconv = string-conversion

    指针类型

    • 和C语言类似,不赘述。

    运算符

    • 没有三元运算符,只能用if else,if后面没有小括号
    • 运算与C语言一致
    • 自增自减只能单独使用,不能在自增自减的同时给变量赋值
    • 自增自减的++和–都必须放在变量的后边!

    标准IO

    • string也是基本类型,传入&地址。

    • func main() {
      	var str string
      	_, _ = fmt.Scanln(str)
      	fmt.Println(str)
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5

    分支语句

    • 基本和C语言一致
    • switch 不用break;
    • 可以匹配多个表达式,逻辑或的关系
    • case,switch后面是一个表达式(不一定是常量)
    • case和switch的数据类型必须一致
    • case的常量(字面量)不能重复
    • switch后面可以不带表达式,可以代替if else作分支选择
    • fallthrough关键字可以穿透到下一分支,用来代偿省略break的功能

    Go学习-Day3

    循环语句

    • 传统方法

    • func main() {
      	for i := 1; i < 10; i++ {
      		fmt.Println("hello!")
      	}
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
    • for - range方法

    • func main() {
      	str := "abcde"
      	for idx, val := range str {
      		fmt.Printf("%v %c\n", idx, val)
      	}
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
    • idx是下标,val是值

    • go没有while和do-while使用for来实现

    • 	for {
      		if i > 10 {
      			break
      		}
      		fmt.Println(i)
      		i++
      	}
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7

    函数

    声明

    • func 函数名(形参列表)返回值列表 {}

    • func add(a int, b int) int {
      	return a + b
      }
      
      • 1
      • 2
      • 3
    • 分包写函数

    • package main
      
      import (
      	"fmt"
      	"go_code/project01/model"
      )
      
      func main() {
      	fmt.Println(model.Add(1, 2))
      }
      
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
    • 包名和文件夹名可以不一致,这样下面调用的时候也要用另外的包名,一般来说,我们习惯于名字保持一致,这样我们导入包的时候,就就能知道包名是什么了。

    • 再复习一个点,Go语言没有public private关键字,是用变量和函数第一个字母大小写来判断公有还是私有,大写是公有,小写是私有。

    • 导包的路径,是从GOPATH/src 后面的部分,一直导到包文件夹。

    • 在同一包下,不能又相同的函数名,不支持函数的重载

    • 如果要编译可执行文件必须声明main包,main包是唯一的。

    init函数

    • 每个源文件都可以包含一个init函数,这个函数会在main函数之前被调用,全局变量定义,init函数,main函数的顺序调用
    • 可能相当于类当中的构造函数

    匿名函数

    • 相当于把整个函数体当作函数的名字,后面的括号就是传入的参数列表

    • func main() {
      	res := func(a int, b int) int {
      		return a + b
      	}(1, 2)
      	fmt.Println(res)
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
    • 如果不带括号,可以重复调用匿名函数,类似lamda表达式

    • 	res := func(a int, b int) int {
      		return a + b
      	}
      	//fmt.Println(res)
      	
      	fmt.Println(res(1, 2))
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6

    闭包

    • func main() {
      	f := func() func(int) int {
      		var n int = 10
      		return func(x int) int {
      			n = x + n
      			return n
      		}
      	}
      	ff := f()
      	ff(1)
      	ff(2)
      	fmt.Println(ff(3))
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
    • 答案16

    • 这里是一个返回值是(int)int的匿名函数,返回了一个含有未知参数并且引用了n的匿名函数,对这个匿名函数多次调用。

    • 函数和引用的外部变量构成了闭包,相当于一个类,第一次调用得到一个匿名函数,可以类比成一个构造方法,构造出了一个类,n是类的一个成员。

    • 或者,我们这样想,这个匿名函数和他所引用的变量构成的闭包,在匿名函数第一次返回的时候,这些变量也在相同的作用域进行声明。

    defer

    • to delay sth until a later time 推迟;延缓;展期(摘自牛津)

    • func main() {
      	defer fmt.Println("ok1")
      	defer fmt.Println("ok2")
      	
      	fmt.Println("ok3")
      	fmt.Println("ok4")
      	
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
    • 输出顺序是:3->4->2->1

    • defer先不执行,等到函数快要释放的时候,defer执行顺序遵从栈的顺序,先进后出

    • 当语句压入栈的时候,相关引用的变量也会拷贝一份进入栈。

    Go学习-Day4

    函数

    值传递,引用传递

    • 值传递直接拷贝值,一般是基本数据类型,数组,结构体也是
    • 引用传递传递地址 ,效率高,指针,slice切片,map,管道,interface等

    常用的函数

    • len(string str)//求字符串长度,自带的不用包,中文一个字三字节
      
      • 1
    • 转成[]rune来处理

    • []byte转string

    • str = string([]byte{...})
      
      • 1
    • 查找子串是否存在

    • 若干字符函数

    • strings.Contains("aaa", "aaa") //bool
      strings.Index("aaa", "aaa")//返回下标
      strings.LastIndex("aaa", "aaa")//返回最后一个下标,没有就返回-1
      strings.Replace(str, str1, str2, n)//把1中str1替换成str2,n是替换个数,-1表示全部替换
      strings.Split(str, "某字符")//分割字符串
      strings.TrimSpace(str)//裁剪空格,去掉前导和后导空格
      strings.Trim(str, "字符集")//去掉指定字符
      strings.TrimLeft()//同上,去掉左侧,并且还有TrimRight
      strings.HasPrefix(str, "后缀")//前缀匹配
      strings.HasSuffix()//同上,但是后缀
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
    • 若干时间函数

    • now := time.Now()//返回时间类型,当前时间
      //2023-08-23 16:37:07.5402748 +0800 CST m=+0.001148901 大概是这样
      //时间类型是结构体,可以使用.运算符来获取其他时间信息,now.Year()
      //月份可以直接转int
      time.Sleep(time.Millisecond * 100)//只能用乘法,不能有浮点数,利用时间单位常量
      time.Unix()//获取unix秒时间戳
      time.UnixNano()//unix纳秒时间戳
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
    • 内置函数built-in

    • len()//统计字符串长度,数组大小
      new(Type) *Type //参数为类型,返回一块对应大小的清空的内存块的指针
      
      • 1
      • 2

    异常处理

    • Go中没有try catch

    • Go利用defer panic recover来处理异常

    • 抛出一个panic的异常,在defer中通过recover捕获异常

    • package main
      
      import "fmt"
      
      func test() {
      	defer func() {
      		err := recover() //捕获异常
      		if err != nil {
      			fmt.Println(err)
      		}
      	}()
      	num1 := 10
      	num2 := 0
      	res := num1 / num2
      	fmt.Println(res)
      }
      func main() {
      	test()
      	fmt.Println("ok")
      }
      
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17
      • 18
      • 19
      • 20
      • 21
    • 通过捕获异常,可以使得程序不崩溃停止!main函数的其他部分照常运行

    • 自定义错误

    • func myError(x int) (err error) {
      	if x == 0 {
      		return nil
      	} else {
      		return errors.New("错误")
      	}
      }
      
      func test() {
      
      	err := myError(1)
      	if err != nil {
      		panic(err)
      	}
      }
      
      func main() {
      	test()
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17
      • 18
      • 19
    • panic会终止程序

    • 捕获自定义错误

    • func myError(x int) (err error) {
      	if x == 0 {
      		return nil
      	} else {
      		return errors.New("错误")
      	}
      }
      
      func test() {
      	defer func() {
      		err := recover() //捕获异常
      		if err != nil {
      			fmt.Println(err)
      		}
      	}()
      	err := myError(1)
      	if err != nil {
      		panic(err)
      	}
      }
      
      func main() {
      	test()
      	fmt.Println("ok")
      }
      
      • 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

    数组

    • 定义

    • func main() {
      
      	var arr [10]int
      	arr[0] = 1
      	fmt.Println(arr)
      
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
    • 数组名地址&arr

    • 初始化

    • var arr [3]int = [3]int{1, 2, 3}
      
      var arr = [3]int{1, 2, 3}
      
      var arr = [...]int{1, 2, 3}
      
      var arr = [...]int{1: 800, 0: 900, 2: 999}//指定下标
      
      var arr := [...]int{1, 2, 3} //自动推导
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
    • 遍历for-range 同string 不赘述

    • 数组中的元素可以是任何合法的类型,但是不能混用

    • Go中数组是值类型,会进行拷贝,要想修改原数组,需要使用指针,写法类似C语言的行指针

    Slice切片

    • 切片是引用类型,传递地址

    • 切片和数组类似,但是长度是可以变化的!

    • 声明

    • var a []int
      
      • 1
    • func main() {
      	var arr [5]int = [...]int{1, 2, 3, 4, 5}
      
      	slice := arr[1:3] //从下标1与下标3,左闭右开的区间
      	fmt.Println(slice)
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
    • 通过make声明

    • func main() {
      	slice := make([]int, 2, 4)//容量可以不声明
      	fmt.Println(slice)
      }
      
      • 1
      • 2
      • 3
      • 4
    • make在底层维护一个数组,这个数组对外不可见

    • 直接声明

    • var slive []string = []string 
      
      • 1
    • 遍历和数组类似,不再赘述

    • 简写

    • var slice = arr[:end] // var slice = arr[0:end]前缀,不含end
      var slice = arr[start:]//var slice = arr[start:]后缀
      var slice = arr[:]//var slice = arr[0:len(arr)]全长
      
      • 1
      • 2
      • 3
    • 切片可以继续切片

    • 切片可以追加,可以追加多个数,可以追加多个切片,利用append将追加后的切片赋值给原来的切片

    • slice1 = append(slice1, 1, 2, 3)//追加数
      slice1 = append(slice1, slice1...)//要三个点
      
      • 1
      • 2
    • Go底层会创建一个新的数组,然后切片这个新的数组,这些过程均不可见

    • string可以进行切片处理

    • str := "sssssss"
      slice := str[2:]//从下标2开始切后缀
      
      • 1
      • 2
    • string底层也指向一个byte数组,我们用切片来拷贝这个只读的byte数组再进行操作

    • 通过切片能够改变字符串

    • arr := byte[](str)
      arr[0] = 'a'
      str = string(arr)
      //但是不支持中文
      arr := rune[](str)
      arr[0] = '好'
      str = string(arr)
      //弄中文
      func main() {
      	str := "?????"
      	arr := []rune(str)
      	arr[0] = '好'
      	fmt.Println(string(arr))
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14

    Go学习-Day5

    map

    • map是一个key-value的数据结构,又称为字段或关联数组

    • Golang自带的map是哈希表

    • 声明

    • import "fmt"
      
      func main() {
      	var a map[int]int
      	fmt.Println(a)
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
    • slice,map和func不能作为键值

    • 声明map是不会分配内存的,初始化需要用make

    • import "fmt"
      
      func main() {
      	var a map[int]int
          a = make(map[int]int, 3)//可以存放三个键值对
      	fmt.Println(a)
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
    • Go的map的键值是没有顺序的

    • 自动增长

    • func main() {
      	a := make(map[int]int)
      	a[1] = 2
      	a[2] = 1
      	fmt.Println(a)
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
    • 直接初始化

    • func main() {
      	a := map[int]int{
      		1: 1,
      		2: 2,//这里也要,
      	}
      	fmt.Println(a)
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7

    增加和更新

    • 直接给键值赋值即可

    删除

    • delete(map, 1)//删除map键值为1的对,如果不存在不会操作
      
      • 1
    • 如果要完全清空

    • 遍历key来删除,或者让map赋值一个新的map,给GC回收

    查询

    val, flag := mp[1] //flag是bool,找到是true,没找到是false,val是对应值
    
    • 1

    遍历(for-range)

    • func main() {
      	a := map[int]int{
      		1: 1,
      		2: 2,
      	}
      	for k, v := range a {
      		fmt.Println(k, v)
      	}
      	fmt.Println(a)
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10

    map切片

    • 同样slice的用法,注意map也要make就行,相当于在底层维护一个map类型的数组

    关于哈希表遍历的一点看法

    • 大概是这样的,在底层有一个迭代器链表,或者,有一个指针数组,每次存放一个指针,迭代器依次访问这些指针,但是在添加元素的时候, 会rehash,导致顺序变化。所以Go的设计者把他设置成无序,每次都打乱这个数组,防止程序员误用哈希map

    对map的key排序

    • 将键值追加到切片内,然后对切片排序

    • import (
      	"fmt"
      	"sort"
      )
      
      func main() {
      
      	mp := make(map[int]int, 10)
      	mp[1] = 2
      	mp[3] = 1
      	mp[2] = 5
      	mp[5] = 6
      	var keys []int //切片
      	for key, _ := range mp {
      		keys = append(keys, key)
      	}
      
      	sort.Ints(keys)
      	fmt.Println(keys)
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17
      • 18
      • 19
      • 20
    • map是引用类型

    结构体与OOP

    声明、初始化、序列化

    • go语言是用struct来面向对象的

    • 声明

    • type Node struct {
      	X int
      	Y int
      }
      
      func main() {
      
      	var a Node//空间自动分配
      	fmt.Println(a)
      
      }
      //输出{0 0}
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
    • 赋值

    • func main() {
      
      	var a Node
      	a.X = 1
      	a.Y = 2
      	fmt.Println(a)
      
      }
      //输出{1 2}
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
    • 初始化

    • //方法一
      a := Node{1, 2}
      
      //方法二
      ptr *Node = new(Node)
      (*ptr).X = 1
      (*ptr).Y = 2
      //--------
      func main() {
      
      	var ptr *Node = new(Node)
      	(*ptr).X = 1
      	(*ptr).Y = 2
      	fmt.Println(*ptr)
      
      }
      //---底层自动识别,这样也可以
      func main() {
      
      	var ptr *Node = new(Node)
      	ptr.X = 1
      	ptr.Y = 2
      	fmt.Println(*ptr)
      }
      //方法三
      var node *Node = &Node{}
      
      • 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
    • 结构体的所有字段在内存中是连续的

    • 两个结构体的字段类型完全相同的话可以强制类型转换

    • 用type struct1 struct2给struct2取别名,相当于定义了一个新的类型,两者之间可以强制类型转换,但是不能直接赋值

    • struct的每个字段上可以写上一个tag,该tag可以通过反射机制获取,常见于序列化和反序列化

    • 复习一个点,Go没有public和private,所以用首字母大写和小写来确定是公共的还是私有的

    • 序列化:对象转json字符串

    • 反序列化:json字符串转对象

    • 动手

    • import (
      	"encoding/json"
      	"fmt"
      )
      
      type Node struct {
      	Name     string
      	Password string
      }
      
      func main() {
      
      	a := Node{
      		"aaaaaa",
      		"123456",
      	}
      
      	str, _ := json.Marshal(a)
      	fmt.Println(a)
      	fmt.Println(string(str))
      }
      //输出
      //{aaaaaa 123456}
      //{"Name":"aaaaaa","Password":"123456"}
      
      
      • 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
    • 但是这种大写的变量很多客户端不习惯,所以使用tag,如果使用小写,就无法被结构体外部函数使用,无法序列化

    • import (
      	"encoding/json"
      	"fmt"
      )
      
      type Node struct {
      	Name     string `json:"name"`
      	Password string `json:"password"`
      }
      
      func main() {
      
      	a := Node{
      		"aaaaaa",
      		"123456",
      	}
      
      	str, _ := json.Marshal(a)
      	fmt.Println(a)
      	fmt.Println(string(str))
      }
      //输出
      //{aaaaaa 123456}
      //{"name":"aaaaaa","password":"123456"}
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17
      • 18
      • 19
      • 20
      • 21
      • 22
      • 23
      • 24

    方法

    • 方法是作用在指定类型上的函数

    • func (a Node) ok() {
          fmt.Println("ok")
      }
      //这个函数绑定给了Node
      //调用
      var a Node
      p.ok()
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
    • 动手

    • import (
      	"fmt"
      )
      
      type Node struct {
      	Name     string `json:"name"`
      	Password string `json:"password"`
      }
      
      func (a Node) ok() {
      	fmt.Println(a.Name)
      }
      func main() {
      
      	a := Node{
      		"aaaaaa",
      		"123456",
      	}
      
      	a.ok()
      }
      //输出了Node的名字
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17
      • 18
      • 19
      • 20
      • 21
      • 22
    • 这个方法只能用指定类型来调用,不能直接调用

    • 如果想要修改原来的参数,我们使用结构体指针,并且这更常用,不用深拷贝,速度更快

    • type Node struct {
      	Name     string `json:"name"`
      	Password string `json:"password"`
      }
      
      func (a *Node) ok() {
      	a.Name = "bbbb"
      }
      func main() {
      
      	a := Node{
      		"aaaaaa",
      		"123456",
      	}
      
      	a.ok()//编译器底层自动识别变为&a
      
      	fmt.Println(a)
      }
      
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17
      • 18
      • 19
      • 20
    • 可以通过实现String方法,可以自定义格式化输出

    工厂模式

    • 类似于构造函数,在结构体所在包下写相应构造的函数,返回结构体指针,这样就可以在结构体私有的情况下,在其他包调用这个函数直接返回结构体对象

    Go学习-Day6

    封装

    • 类似java的类的封装,这里我们利用大小写和工厂模式来实现封装的功能
    • 略过

    继承

    • 相似的类具有相似的方法,反复绑定相同的方法,代码冗余,所以引入了继承的概念

    • 嵌套匿名结构体来实现继承的效果

    • 动手实践!

    • type Node struct {
      	Name     string `json:"name"`
      	Password string `json:"password"`
      }
      
      type Point struct {
      	Node
      	X int
      	Y int
      }
      
      func (a *Node) ok() {
      	a.Name = "bbbb"
      }
      func main() {
      
      	var a Point = Point{
      		Node{"aaa", "bbb"},
      		1,
      		2,
      	}
      
      	a.ok()
      
      	fmt.Println(a)
      }
      
      • 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
    • 注意看,a.ok()其实是a.Node.ok()底层自动识别,可以省略匿名结构体

    • 基本数据类型可以匿名,但是不能出现多个相同类型的匿名基本类型

    接口

    • 多态主要就是由接口来实现的

    • 声明

    • type inter interface {
      	a()
      	b()
      }
      
      • 1
      • 2
      • 3
      • 4
    • 实现接口

    • import "fmt"
      
      type inter interface {
      	a()
      	b()
      }
      type obj1 struct {
      }
      
      type union struct {
      }
      
      func (o obj1) a() {
      	fmt.Println("okkk")
      	//用node对象给接口的a()实现
      }
      
      func (o obj1) b() {
      	fmt.Println("ohhh")
      }
      
      func (u union) ok(in inter) {
      	in.a()
      	in.b()
      }
      
      func main() {
      
      	x := union{}
      	y := obj1{}
      	x.ok(y)
      }
      
      
      • 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
    • 从上面我们可以看到,在声明一个接口之后,我们用工厂方法法obj对应接口的所有方法给实现了,然后另外整一个抽象类似的的结构体,绑定一个方法运行接口,这样我们就能通过接口把这两个类给链接在一起。

    • 总结。

    • interface类型可以定义一组方法,方法不用实现,并且不能含有变量

    • Go语言没有implements关键字,只要一个变量类型,绑定了接口中所有的方法,这个变量就能实现这个接口

    • 这个变量就能导入到含有接口作为变量的函数内

    Go学习-Day7

    断言

    type Node struct {
    	x int
    	y int
    }
    
    func main() {
    	var a interface{}
    	var n Node = Node{1, 2}
    	a = n
    	var b Node
    	b = a.(Node)
    	fmt.Println(b)
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 此处我们有一个结构体n给空接口a赋值(空接口没有方法,相当于方法被n给完全实现,所以是可以赋值给接口的),然后我们想把接口赋值给具体的结构体对象,但是,这里会报错,需要使用类型断言的语法。用.(类型)类声明a的类型。

    • 类型断言之后,编译器会判断这个变量是否是指向这个类型,如果是,就转换成这个类型来赋值

    • 就是把抽象的接口转换成具体的类型的方法

    • 如果类型不匹配的话,就会报panic,通过这个方法,可以判断接口个具体类型,执行特定操作

    • if a, flag := u.(xxx); flag == true {
          xxxx
      } 
      
      • 1
      • 2
      • 3
    • x.(type)会返回类型

    文件

    打开/关闭文件

    import (
    	"fmt"
    	"os"
    )
    
    func main() {
    	f, err := os.Open("E:\\JetBrains\\GoLandSpace\\src\\go_code\\project01\\main\\test.txt")
    	if err != nil {
    		fmt.Println(err)
    	}
    	fmt.Printf("%v", f)
    
    	err = f.Close()
    	if err != nil {
    		fmt.Println(err)
    	}
    }
    //返回&{0xc00010c780}
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 先用os包打开文件,再用方法关闭,上面的绝对路径当中,如果用正斜杠要双写,反斜杠就不用

    读取文件

    • import (
      	"bufio"
      	"fmt"
      	"io"
      	"os"
      )
      
      func main() {
      	f, err := os.Open("E:/JetBrains/GoLandSpace/src/go_code/project01/main/test.txt")
      	if err != nil {
      		fmt.Println(err)
      	}
      
      	defer f.Close() //函数推出的时候自动关闭
      
      	reader := bufio.NewReader(f) //创建一个缓冲区来读入
      
      	for {
      		str, err := reader.ReadString('\n') //读到换行符就停止
      		if err == io.EOF {
      			break //读到末尾
      		}
      		fmt.Println(str)
      	}
      	fmt.Println("-------------------")
      }
      
      • 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
    • 首先打开文件,然后按行读取,注意读到EOF要结束死循环

    • ioutil.ReadFile可以一次性读入到一个字节数组内,不过文件需要比较小的情况下使用

    写入文件

    import (
    	"bufio"
    	"fmt"
    	"os"
    )
    
    func main() {
    	filePath := "E:/JetBrains/GoLandSpace/src/go_code/project01/main/a.txt"
    	file, err := os.OpenFile(filePath, os.O_WRONLY|os.O_CREATE, 0666)
    	//后面那个int在windows下无用
    	if err != nil {
    		fmt.Printf("%v", err)
    		return
    	}
    	defer file.Close()
    
    	str := "ok\n"
    
    	writer := bufio.NewWriter(file)
    
    	writer.WriteString(str)
    
    	writer.Flush() //从缓冲区压入文件
    
    }
    
    • 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
    • 写入文件的流程也是非常亲切
    • 我们写入写入缓冲区之后,我们必须要flush刷新一下缓冲区,将缓冲区内的字符刷到磁盘上
    • 通过不同的标识符,有追加,截断清除的写入操作
    • 类似的可以拷贝文件,有io.Copy()函数可以更方便地拷贝文件

    命令行参数解析

    Args

    • os.Args这是go的命令行参数的数组,但是不方便

    flag包

    flag.StringVar(&xxx, "x", "", "sss")
    //第一个变量是传入的值所存的变量的地址,"x"是传入的参数的名字,""是默认值, "sss"是说明
    
    • 1
    • 2
    • 设置完这些变量之后使用flag.parse()来解析命令行的参数

    JSON

    • JSON(JavaScript Object Notation)是一种轻量级的数据交换格式 key-val的形式
    • 中括号表示数组,大括号表示对象,一个个的键值对用,号分隔开
    • 序列化方法上面写过了,用json.Marshal()
    • 反序列化用json.Unmarshal([]byte(str), &xxx)

    Go学习-Day8

    单元测试

    • testing框架会将xxx_test.go的文件引入,调用所有TestXxx的函数

    • 在cal_test.go文件里面写这个

    • package main
      
      import "testing"
      
      func TestAdd(t *testing.T) {
      	a, b := 1, 2
      	if add(a, b) != 4 {
      		t.Fatalf("Wrong Answer!")
      	}
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
    • 在cal.go文件里写这个

    • package main
      
      func add(a int, b int) int {
      	return a + b
      }
      
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
    • 运行go test -v的命令,就能运行单测

    • 可以得到结果

    • === RUN   TestAdd
          cal_test.go:8: Wrong Answer!
      --- FAIL: TestAdd (0.00s)
      
      
      • 1
      • 2
      • 3
      • 4
    • testing框架import这个test文件之后,会调用所有TestXxx的函数,注意大写!

    Goroutine

    进程和线程

    • 进程是程序的在操作系统的一次执行过程
    • 线程是比进程更小的单位,一个进程能创建销毁多个线程
    • 一个程序至少有一个进程,一个进程至少有一个线程

    并发和并行

    • 多线程在单核上运行,就是并发
    • 多线程在多核上运行,就是并行

    Go协程和主线程

    • 主线程类似进程

    • 协程类似线程,是轻量级的线程

    • 协程的特点

      • 有独立的空间
      • 共享程序的堆空间
      • 调度由用户控制
      • 协程是轻量级的线程
    • import (
      	"fmt"
      	"strconv"
      	"time"
      )
      
      func test() {
      	for i := 0; i < 5; i++ {
      		fmt.Println("test() calls! " + strconv.Itoa(i))
      		time.Sleep(time.Second)
      	}
      }
      
      func main() {
      	go test()
      
      	for i := 0; i < 5; i++ {
      		fmt.Println("main() calls! " + strconv.Itoa(i))
      		time.Sleep(time.Second)
      	}
      }
      
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17
      • 18
      • 19
      • 20
      • 21
      • 22
    • 输出

    • main() calls! 0
      test() calls! 0
      test() calls! 1
      main() calls! 1
      main() calls! 2
      test() calls! 2
      test() calls! 3
      main() calls! 3
      main() calls! 4
      test() calls! 4
      
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
    • go关键字会另起一个协程,主线程执行到这里会开一个协程并行执行,如果主线程执行完毕退出,协程会被强制退出

    MPG模式

    • M(Machine)是操作系统的主线程,也就是物理线程

    • P(Processor)协程执行的上下文

    • G(Gorountine)协程

    • Go语言的协程是轻量级的,是逻辑态的,可以起上万个协程;而C/java的多线程是内核态的,几千个就会耗光CPU

    CPU相关

    runtime.NumCPU()
    //获取本地CPU数目
    runtime.GOMAXPROCS(int)
    //设置GO最大可用的CPU数目
    //Go Max Processors
    
    • 1
    • 2
    • 3
    • 4
    • 5

    协程并行的资源竞争

    • 多个协程同时访问一个资源会发生冲突,会发生并发问题

    • 在java中我们有锁和原子类来保证并发安全

    • 声明一个全局锁变量lock

    • lock sync.Mutex
      //sync是同步的意思,Muti-excluded互斥锁?
      
      • 1
      • 2
    • lock.Lock()//在进行并发的读写操作的时候,先上个锁
      ...//在进行操作的时候,别的协程会排队等待
      lock.Unlock()//解锁之后,才能给别的协程使用
      
      • 1
      • 2
      • 3
    • 主线程读的时候也需要加锁,因为底层不知道协程已经解锁了,会发生资源冲突

    • 但是这样不同协程之间没办法通讯,不知道什么时候协成完成任务了,白白空转浪费时间,或者提前结束主线程,终止协程,管道可能能解决这些问题,明天再学

    Go学习-Day9

    Channel

    • Channel本质是一个队列
    • 多goroutine访问时不需要加锁,Channel天然线程安全
    • channel有类型,只能写入相同类型
    • channel是引用类型
    • channel必须初始化才能写入数据,make分配内存

    声明

    • 	var intChan chan int
      	intChan = make(chan int, 3)
      
      • 1
      • 2
    • java不是很熟悉,感觉chan有点像java的原子类

    存入取出

    • intChan<- xxx //存入
      a := <= intChan//取出
      
      • 1
      • 2
    • 管道不会自然增长,不能超过容量,不能从空的管道里取出数据,会上DeadLock

    • 如果想要存储任意类型的管道,可以用空借口

    • var allChan chan interface{}
      
      • 1
    • 但是,取出的时候注意类型断言

    • close(intChan)
      
      • 1
    • channel关闭之后就不能再写入了,但是能继续读出

    • 关闭之后能用for-range来遍历,如果不关闭的话会出现死锁

    • 死锁的情况很多,建议多找几篇文章看看,写写实操一下

    • 空的缓冲chan相当于无缓冲的chan,无缓冲的chan需要接收者,传入者,否则就会死锁,注意及时关闭

    • 只向管道内写入,不读取就会deadlock,读得慢没有关系

    • 关键是要给每个管道安排一个发送者,和接收者!!!

    一个简单的死锁分析

    package main
    
    import (
    	"fmt"
    	"time"
    )
    
    func write(intChan chan int) {
    	for i := 0; i < 5; i++ {
    		fmt.Println("写入: ", i)
    		intChan <- i
    		time.Sleep(time.Second)
    	}
    	//close(intChan)
    }
    
    func read(intChan chan int, exitChan chan bool) {
    	for {
    		val, ok := <-intChan
    		if !ok {
    			break
    		}
    		fmt.Println("读到", val)
    	}
    	exitChan <- true
    	close(exitChan)
    }
    func main() {
    
    	intChan := make(chan int, 20)
    	exitChan := make(chan bool, 1)
    	go write(intChan)
    	go read(intChan, exitChan)
    
    	for {
    		_, ok := <-exitChan
    		if !ok {
    			break
    		}
    	}
    }
    
    
    • 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
    • 输出
    写入:  0
    读到 0
    写入:  1
    读到 1
    写入:  2
    读到 2
    写入:  3
    读到 3
    写入:  4
    读到 4
    fatal error: all goroutines are asleep - deadlock!
    
    goroutine 1 [chan receive]:
    main.main()
            E:/JetBrains/GoLandSpace/src/go_code/project01/main/hello.go:36 +0xe8
    
    goroutine 7 [chan receive]:
    main.read(0x0?, 0x0?)
            E:/JetBrains/GoLandSpace/src/go_code/project01/main/hello.go:19 +0x99
    created by main.main
            E:/JetBrains/GoLandSpace/src/go_code/project01/main/hello.go:33 +0xd9
    
    Process finished with the exit code 2
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 下面是个人的分析,不一定对,有大佬可以来指正
    • 如果我们不close,channel是可以读的,我们可以边读,边写,并且,读的速度是可以更慢或者更快的,go底层会通过上下文自行判断。
    • 但是这里,我们写的协程,我们关闭channel,在程序运行完之后自行关闭,此时我们读的协程会卡在intChan,等待读入,但是此时还不会报错,因为协程会因为主线程结束而结束。但是后面的exitChan会导致报错
    package main
    
    import (
    	"fmt"
    	"time"
    )
    
    func write(intChan chan int) {
    	for i := 0; i < 5; i++ {
    		fmt.Println("写入: ", i)
    		intChan <- i
    		time.Sleep(time.Second)
    	}
    	//close(intChan)
    }
    
    func read(intChan chan int, exitChan chan bool) {
    	for {
    		val, ok := <-intChan
    		if !ok {
    			break
    		}
    		fmt.Println("读到", val)
    	}
    	fmt.Println("到了这里")
    	//exitChan <- true
    	//close(exitChan)
    }
    func main() {
    
    	intChan := make(chan int, 20)
    	exitChan := make(chan bool, 1)
    	go write(intChan)
    	go read(intChan, exitChan)
    
    	time.Sleep(time.Second * 10)
    	//for {
    	//	_, ok := <-exitChan
    	//	if !ok {
    	//		break
    	//	}
    	//}
    }
    
    
    • 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
    • 这样并没有报错,并且发现到了这里没有打印,说明read函数作为intChan的接收者一直在等待,这时候。

    • 但是,主线程运行到下面的for的时候,此时exitChan是空的,因为intChan一直在死循环等待,所以触发了死锁

    • 只读只写

    • var chanIn chan<- int//只写
      
      • 1
    • var chanOut <-chan int//只读
      
      • 1
    • select {case …}可以安全地取出数据

    • 使用recover捕获协程终端 panic

    Go学习-Day10

    反射

    • 编写函数适配器,序列化和反序列话可以用到

    • 反射可以在运行时,动态获取变量的各种信息,例如类型,结构体本身的信息,修改变量的值,调用关联的方法

    • 反射是不是和映射相反?是一种逆函数?

    • 变量到空接口相互转换,空接口和reflect.value相互转换

    • 动手一下

    • import (
      	"fmt"
      	"reflect"
      )
      
      func test(a interface{}) {
      	b := reflect.TypeOf(a)
      	fmt.Println(b)
      }
      
      func main() {
      	var a int = 10
      	test(a)
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
    • 打印 “int”

    • reflect.TypeOf()//从接口获取原类型
      reflect.ValueOf()//从接口获取reflect.Value类型.Int能取到具体的类型
      //如果需要原类型,需要类型断言
      reflect.Interface//把reflect.Value转换成空接口
      
      • 1
      • 2
      • 3
      • 4
    • Kind是大的种类,Type是小的类型

    • 常量在定义的时候必须初始化

    • reflect.Value.Kind返回的是常量

    • 如果传入指针类型的话(反射常常需要改变原来的值)指针类型需要.Elem方法取到值,再用.SetInt之类的方修改原来的值

    • Value//指reflect.Value
      Value.NumField()//获取字段数
      Value.Field()//根据下标,获取第几个字段,返回的也是relect.Value
      Tpye//指reflect.Type
      Tpye.Field().Tag.Get("key")//可以获取tag,键值是结构体里面设置的例如,"json:"的key就是json,序列化反序列化的键值固定取json,其实可以自定义
      Value.NumMethod()//获取方法数
      Value.Method().Call(...)//获取第几个方法,然后调用
      //这个顺序是按照函数名字典序排列的,Call传的是Value切片,返回的也是Value切片
      //输入的时候需要定义一个Value切片,用reflect.ValueOf(xx)插入这个切片
      Value.Elem().Field().SetXxx//修改字段
      ...FieldByName()//可以用字段名来找
      Value.New()//为指针申请空间,可以通过反射来创建类型
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12

    网络编程

    • Golang的主要设计目标之一就是面向大规模的后端服务程序,网络通信是服务端程序必不可少的一部分
    • 网络编程有两种 TCP(Transmission Control Protocol) socket编程和HTTP编程(建立在前者之上)
    • 做服务器尽量少开端口,一个端口只能被一个程序监听

    监听端口小Demo

    • net包提供了可以指的I/O接口

    • package main
      
      import (
      	"fmt"
      	"net"
      )
      
      func main() {
      
      	fmt.Println("开始监听")
      	//使用tcp协议,监听本机
      	listen, err := net.Listen("tcp", "0.0.0.0:8888")
      	if err != nil {
      		fmt.Println("err=", err)
      	}
      	//延迟关闭
      	defer listen.Close()
      	//循环等待
      	for {
      		//等待客户端连接
      		fmt.Println("等待连接...")
      		//获取连接
      		conn, err := listen.Accept()
      		if err != nil {
      			fmt.Println("err=", err)
      		} else {
      			fmt.Println("con=", 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
    • 用telnet呼叫一下 telnet 127.0.0.1 8888

    • 开始监听
      等待连接...
      con= &{{0xc00010ec80}}
      等待连接...
      //返回
      
      • 1
      • 2
      • 3
      • 4
      • 5

    客户端

    • conn, err := net.Dial("tcp", "ip...:端口")
      //获取连接
      //Dial是拨号的意思
      
      • 1
      • 2
      • 3
    • 通过端口就能和对应的程序进行交流

    • func main() {
      	conn, err := net.Dial("tcp", "127.0.0.1:8888")
      	if err != nil {
      		fmt.Println("err=", err)
      	}
      	fmt.Println("连接成功conn=", conn)
      }
      //注意此时要开着上面的监听程序
      //输出 连接成功conn= &{{0xc00010ca00}}
      
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10

    发送&&接收

    server.go

    package main
    
    import (
    	"fmt"
    	"net"
    )
    
    func process(conn net.Conn) {
    	//连接过多不关闭的话就会导致其他连接无法成功
    	defer conn.Close()
    
    	for {
    		buf := make([]byte, 512)
    		//如果没有Write会停在这里,类似我们stdin输入的时候,光标会停在输入的位置
    		//如果连接突然中断的话,这里会报错
    		//TCP底层会定时发送消息,检查连接是否存在
    		n, err := conn.Read(buf)
    		if err != nil {
    			fmt.Println("err=", err)
    			return
    			//有可能是关闭了
    		}
    		//字节切片要强制转换
    		//buf后面的存的可能是乱七八糟的东西,注意取前n个!
    		fmt.Print(string(buf[:n]))
    
    	}
    }
    
    func main() {
    
    	fmt.Println("开始监听")
    	//使用tcp协议,监听本机
    	listen, err := net.Listen("tcp", "0.0.0.0:8888")
    	if err != nil {
    		fmt.Println("err=", err)
    	}
    	//延迟关闭
    	defer listen.Close()
    	//循环等待
    	for {
    		//等待客户端连接
    		fmt.Println("等待连接...")
    		//获取连接
    		conn, err := listen.Accept()
    		if err != nil {
    			fmt.Println("err=", err)
    		} else {
    			fmt.Println("con=", conn)
    		}
    		//起一个协程为客户端服务
    		go process(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
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55

    client.go

    package main
    
    import (
    	"bufio"
    	"fmt"
    	"net"
    	"os"
    )
    
    func main() {
    	conn, err := net.Dial("tcp", "127.0.0.1:8888")
    	if err != nil {
    		fmt.Println("err=", err)
    	}
    	fmt.Println("连接成功conn=", conn)
    
    	//创建标准stdin的reader
    	reader := bufio.NewReader(os.Stdin)
    	//读取一行
    	str, err := reader.ReadString('\n')
    	if err != nil {
    		fmt.Println("err=", err)
    	}
    
    	n, err := conn.Write([]byte(str))
    	if err != nil {
    		fmt.Println("err=", err)
    	}
    	fmt.Println("发送了n个字节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
    • 一个小点,发送的字节数多2,应该是回车键的缘故,可能这里是当成\n\r
  • 相关阅读:
    Unity技术手册 - 干扰/噪音/杂波(Noise)子模块
    计算机毕业设计Java迎新管理系统演示录像2020(系统+程序+mysql数据库+Lw文档)
    Techempower web框架性能测试第21轮结果发布--asp.net core继续前进
    `算法知识` 线段树
    正厚软件-软件测试用例设计方法之二-边界值
    基于SpringBoot的每日推购物推荐网站的设计与实现
    中间件安全:Apache Tomcat 弱口令.(反弹 shell 拿到服务器的最高控制权.)
    spark基础
    css选择器(通配符、(元素、类、id)、后代(子代、所有后代)、兄弟(相邻兄弟、所有兄弟)、属性(属性、指定值的属性)、交集并集)
    vscode 画流程图
  • 原文地址:https://blog.csdn.net/gfyy_bkj/article/details/132676429