个人博客:CSDN博客
打卡。
Go语言的核心开发团队:
Go语言有静态语言的安全和性能和动态语言开发维护的效率。
Go语言特性
Hello World (一定要注意目录结构!)
通过go build来编译go文件,得到exe文件
关于文件夹架构,一定要准确,不然找不到包。
%GOPATH%
src
go_code
project00 //项目名open这个项目
project01
注意配置PATH,GOPATH(项目的位置),GOROOT(SDK的位置)
并且配置一些settings里面相应的变量
定义变量
var i int = 10
var关键字+变量名+变量类型
var i = 10
自动推断类型
i := 10
简略写法
对应的,可以声明多个变量
var a, b, c int = 1, "a", 2
var a, str1, b = 1, "a", 2
a, str1, b := 1, "a", 2
var (
i = 1
j = 2
)
另一种声明方法,开发中常用
import (
"fmt"
"unsafe"
)
导包也可以类似这样
利用UTF-8编码,支持中文
go中字符串是常量,无法修改
引号
加号拼接,可以分行写(加号放行尾)
go不会自动转换类型,需要显式转换
var i int = 1
var j float32 = float32(i)
func main() {
var a int = 10
var b float32 = 3.14
var s string = fmt.Sprintf("%d %.2f", a, b)
fmt.Println(s)
}
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)
string也是基本类型,传入&地址。
func main() {
var str string
_, _ = fmt.Scanln(str)
fmt.Println(str)
}
传统方法
func main() {
for i := 1; i < 10; i++ {
fmt.Println("hello!")
}
}
for - range方法
func main() {
str := "abcde"
for idx, val := range str {
fmt.Printf("%v %c\n", idx, val)
}
}
idx是下标,val是值
go没有while和do-while使用for来实现
for {
if i > 10 {
break
}
fmt.Println(i)
i++
}
func 函数名(形参列表)返回值列表 {}
func add(a int, b int) int {
return a + b
}
分包写函数
package main
import (
"fmt"
"go_code/project01/model"
)
func main() {
fmt.Println(model.Add(1, 2))
}
包名和文件夹名可以不一致,这样下面调用的时候也要用另外的包名,一般来说,我们习惯于名字保持一致,这样我们导入包的时候,就就能知道包名是什么了。
再复习一个点,Go语言没有public private关键字,是用变量和函数第一个字母大小写来判断公有还是私有,大写是公有,小写是私有。
导包的路径,是从GOPATH/src 后面的部分,一直导到包文件夹。
在同一包下,不能又相同的函数名,不支持函数的重载
如果要编译可执行文件必须声明main包,main包是唯一的。
相当于把整个函数体当作函数的名字,后面的括号就是传入的参数列表
func main() {
res := func(a int, b int) int {
return a + b
}(1, 2)
fmt.Println(res)
}
如果不带括号,可以重复调用匿名函数,类似lamda表达式
res := func(a int, b int) int {
return a + b
}
//fmt.Println(res)
fmt.Println(res(1, 2))
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))
}
答案16
这里是一个返回值是(int)int的匿名函数,返回了一个含有未知参数并且引用了n的匿名函数,对这个匿名函数多次调用。
函数和引用的外部变量构成了闭包,相当于一个类,第一次调用得到一个匿名函数,可以类比成一个构造方法,构造出了一个类,n是类的一个成员。
或者,我们这样想,这个匿名函数和他所引用的变量构成的闭包,在匿名函数第一次返回的时候,这些变量也在相同的作用域进行声明。
to delay sth until a later time 推迟;延缓;展期(摘自牛津)
func main() {
defer fmt.Println("ok1")
defer fmt.Println("ok2")
fmt.Println("ok3")
fmt.Println("ok4")
}
输出顺序是:3->4->2->1
defer先不执行,等到函数快要释放的时候,defer执行顺序遵从栈的顺序,先进后出
当语句压入栈的时候,相关引用的变量也会拷贝一份进入栈。
len(string str)//求字符串长度,自带的不用包,中文一个字三字节
转成[]rune来处理
[]byte转string
str = string([]byte{...})
查找子串是否存在
若干字符函数
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()//同上,但是后缀
若干时间函数
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纳秒时间戳
内置函数built-in
len()//统计字符串长度,数组大小
new(Type) *Type //参数为类型,返回一块对应大小的清空的内存块的指针
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")
}
通过捕获异常,可以使得程序不崩溃停止!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()
}
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")
}
定义
func main() {
var arr [10]int
arr[0] = 1
fmt.Println(arr)
}
数组名地址&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} //自动推导
遍历for-range 同string 不赘述
数组中的元素可以是任何合法的类型,但是不能混用
Go中数组是值类型,会进行拷贝,要想修改原数组,需要使用指针,写法类似C语言的行指针
切片是引用类型,传递地址
切片和数组类似,但是长度是可以变化的!
声明
var a []int
func main() {
var arr [5]int = [...]int{1, 2, 3, 4, 5}
slice := arr[1:3] //从下标1与下标3,左闭右开的区间
fmt.Println(slice)
}
通过make声明
func main() {
slice := make([]int, 2, 4)//容量可以不声明
fmt.Println(slice)
}
make在底层维护一个数组,这个数组对外不可见
直接声明
var slive []string = []string
遍历和数组类似,不再赘述
简写
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)]全长
切片可以继续切片
切片可以追加,可以追加多个数,可以追加多个切片,利用append将追加后的切片赋值给原来的切片
slice1 = append(slice1, 1, 2, 3)//追加数
slice1 = append(slice1, slice1...)//要三个点
Go底层会创建一个新的数组,然后切片这个新的数组,这些过程均不可见
string可以进行切片处理
str := "sssssss"
slice := str[2:]//从下标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))
}
map是一个key-value的数据结构,又称为字段或关联数组
Golang自带的map是哈希表
声明
import "fmt"
func main() {
var a map[int]int
fmt.Println(a)
}
slice,map和func不能作为键值
声明map是不会分配内存的,初始化需要用make
import "fmt"
func main() {
var a map[int]int
a = make(map[int]int, 3)//可以存放三个键值对
fmt.Println(a)
}
Go的map的键值是没有顺序的
自动增长
func main() {
a := make(map[int]int)
a[1] = 2
a[2] = 1
fmt.Println(a)
}
直接初始化
func main() {
a := map[int]int{
1: 1,
2: 2,//这里也要,
}
fmt.Println(a)
}
delete(map, 1)//删除map键值为1的对,如果不存在不会操作
如果要完全清空
遍历key来删除,或者让map赋值一个新的map,给GC回收
val, flag := mp[1] //flag是bool,找到是true,没找到是false,val是对应值
func main() {
a := map[int]int{
1: 1,
2: 2,
}
for k, v := range a {
fmt.Println(k, v)
}
fmt.Println(a)
}
将键值追加到切片内,然后对切片排序
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)
}
map是引用类型
go语言是用struct来面向对象的
声明
type Node struct {
X int
Y int
}
func main() {
var a Node//空间自动分配
fmt.Println(a)
}
//输出{0 0}
赋值
func main() {
var a Node
a.X = 1
a.Y = 2
fmt.Println(a)
}
//输出{1 2}
初始化
//方法一
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{}
结构体的所有字段在内存中是连续的
两个结构体的字段类型完全相同的话可以强制类型转换
用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"}
但是这种大写的变量很多客户端不习惯,所以使用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"}
方法是作用在指定类型上的函数
func (a Node) ok() {
fmt.Println("ok")
}
//这个函数绑定给了Node
//调用
var a Node
p.ok()
动手
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的名字
这个方法只能用指定类型来调用,不能直接调用
如果想要修改原来的参数,我们使用结构体指针,并且这更常用,不用深拷贝,速度更快
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)
}
可以通过实现String方法,可以自定义格式化输出
相似的类具有相似的方法,反复绑定相同的方法,代码冗余,所以引入了继承的概念
嵌套匿名结构体来实现继承的效果
动手实践!
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)
}
注意看,a.ok()其实是a.Node.ok()底层自动识别,可以省略匿名结构体
基本数据类型可以匿名,但是不能出现多个相同类型的匿名基本类型
多态主要就是由接口来实现的
声明
type inter interface {
a()
b()
}
实现接口
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)
}
从上面我们可以看到,在声明一个接口之后,我们用工厂方法法obj对应接口的所有方法给实现了,然后另外整一个抽象类似的的结构体,绑定一个方法运行接口,这样我们就能通过接口把这两个类给链接在一起。
总结。
interface类型可以定义一组方法,方法不用实现,并且不能含有变量
Go语言没有implements关键字,只要一个变量类型,绑定了接口中所有的方法,这个变量就能实现这个接口
这个变量就能导入到含有接口作为变量的函数内
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)
}
此处我们有一个结构体n给空接口a赋值(空接口没有方法,相当于方法被n给完全实现,所以是可以赋值给接口的),然后我们想把接口赋值给具体的结构体对象,但是,这里会报错,需要使用类型断言的语法。用.(类型)类声明a的类型。
类型断言之后,编译器会判断这个变量是否是指向这个类型,如果是,就转换成这个类型来赋值
就是把抽象的接口转换成具体的类型的方法
如果类型不匹配的话,就会报panic,通过这个方法,可以判断接口个具体类型,执行特定操作
if a, flag := u.(xxx); flag == true {
xxxx
}
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}
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("-------------------")
}
首先打开文件,然后按行读取,注意读到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() //从缓冲区压入文件
}
flag.StringVar(&xxx, "x", "", "sss")
//第一个变量是传入的值所存的变量的地址,"x"是传入的参数的名字,""是默认值, "sss"是说明
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!")
}
}
在cal.go文件里写这个
package main
func add(a int, b int) int {
return a + b
}
运行go test -v的命令,就能运行单测
可以得到结果
=== RUN TestAdd
cal_test.go:8: Wrong Answer!
--- FAIL: TestAdd (0.00s)
testing框架import这个test文件之后,会调用所有TestXxx的函数,注意大写!
主线程类似进程
协程类似线程,是轻量级的线程
协程的特点
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)
}
}
输出
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
go关键字会另起一个协程,主线程执行到这里会开一个协程并行执行,如果主线程执行完毕退出,协程会被强制退出
M(Machine)是操作系统的主线程,也就是物理线程
P(Processor)协程执行的上下文
G(Gorountine)协程
Go语言的协程是轻量级的,是逻辑态的,可以起上万个协程;而C/java的多线程是内核态的,几千个就会耗光CPU
runtime.NumCPU()
//获取本地CPU数目
runtime.GOMAXPROCS(int)
//设置GO最大可用的CPU数目
//Go Max Processors
多个协程同时访问一个资源会发生冲突,会发生并发问题
在java中我们有锁和原子类来保证并发安全
声明一个全局锁变量lock
lock sync.Mutex
//sync是同步的意思,Muti-excluded互斥锁?
lock.Lock()//在进行并发的读写操作的时候,先上个锁
...//在进行操作的时候,别的协程会排队等待
lock.Unlock()//解锁之后,才能给别的协程使用
主线程读的时候也需要加锁,因为底层不知道协程已经解锁了,会发生资源冲突
但是这样不同协程之间没办法通讯,不知道什么时候协成完成任务了,白白空转浪费时间,或者提前结束主线程,终止协程,管道可能能解决这些问题,明天再学
var intChan chan int
intChan = make(chan int, 3)
java不是很熟悉,感觉chan有点像java的原子类
intChan<- xxx //存入
a := <= intChan//取出
管道不会自然增长,不能超过容量,不能从空的管道里取出数据,会上DeadLock
如果想要存储任意类型的管道,可以用空借口
var allChan chan interface{}
但是,取出的时候注意类型断言
close(intChan)
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
}
}
}
写入: 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
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
// }
//}
}
这样并没有报错,并且发现到了这里没有打印,说明read函数作为intChan的接收者一直在等待,这时候。
但是,主线程运行到下面的for的时候,此时exitChan是空的,因为intChan一直在死循环等待,所以触发了死锁
只读只写
var chanIn chan<- int//只写
var chanOut <-chan int//只读
select {case …}可以安全地取出数据
使用recover捕获协程终端 panic
编写函数适配器,序列化和反序列话可以用到
反射可以在运行时,动态获取变量的各种信息,例如类型,结构体本身的信息,修改变量的值,调用关联的方法
反射是不是和映射相反?是一种逆函数?
变量到空接口相互转换,空接口和reflect.value相互转换
动手一下
import (
"fmt"
"reflect"
)
func test(a interface{}) {
b := reflect.TypeOf(a)
fmt.Println(b)
}
func main() {
var a int = 10
test(a)
}
打印 “int”
reflect.TypeOf()//从接口获取原类型
reflect.ValueOf()//从接口获取reflect.Value类型.Int能取到具体的类型
//如果需要原类型,需要类型断言
reflect.Interface//把reflect.Value转换成空接口
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()//为指针申请空间,可以通过反射来创建类型
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)
}
//起一个协程为客户端服务
}
}
用telnet呼叫一下 telnet 127.0.0.1 8888
开始监听
等待连接...
con= &{{0xc00010ec80}}
等待连接...
//返回
conn, err := net.Dial("tcp", "ip...:端口")
//获取连接
//Dial是拨号的意思
通过端口就能和对应的程序进行交流
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}}
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)
}
}
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)
}