golang 基础编程

golang 基础编程

大家好,又见面了,我是全栈君。

1.变量,常量

package main

import "fmt"

func main() {
	//变量
	var (
		x, y int //同时声明 x,y 为整数
		z    float64
	)
	//
	d := 33       //简短声明变量
	e := int64(2) //声明e为 2 的64位整数

	//常量
	const a = 64
	const (
		b = 3
		c = 0.1
	)

	fmt.Printf("a= %v b= %v c= %v  %v %v %v %v %v ", a, b, c, x, y, z, d, e)

	fmt.Println("hello word")
}

2.基本数据类型

我们可以将基本类型分为三大类:

  • 布尔类型
  • 数字类型
  • 字符串类型

Go 语言中的基本数据类型包含:

bool      the set of boolean (true, false)

uint8      the set of all unsigned  8-bit integers (0 to 255)
uint16      the set of all unsigned 16-bit integers (0 to 65535)
uint32      the set of all unsigned 32-bit integers (0 to 4294967295)
uint64      the set of all unsigned 64-bit integers (0 to 18446744073709551615)

int8      the set of all signed  8-bit integers (-128 to 127)
int16      the set of all signed 16-bit integers (-32768 to 32767)
int32      the set of all signed 32-bit integers (-2147483648 to 2147483647)
int64      the set of all signed 64-bit integers (-9223372036854775808 to 9223372036854775807)

float32      the set of all IEEE-754 32-bit floating-point numbers
float64      the set of all IEEE-754 64-bit floating-point numbers

complex64      the set of all complex numbers with float32 real and imaginary parts
complex128      the set of all complex numbers with float64 real and imaginary parts

byte      alias for uint8
rune      alias for int32
uint      either 32 or 64 bits
int      same size as uint
uintptr      an unsigned integer large enough to store the uninterpreted bits of a pointer value

string      the set of string value (eg: "hi")

数据类型实操:

package main

import (
	"fmt"
)

func main() {
	//1. 变量
	var (
		x, y int //同时声明 x,y 为整数
		z    float64
	)
	//
	d := 33       //简短声明变量
	e := int64(2) //声明e为 2 的64位整数

	//2. 常量
	const a = 64
	const (
		b = 3
		c = 0.1
	)

	//3.数据类型 布尔类型
	var bb = true
	cc := false

	//数字类型:分为有符号数,无符号数,有符号数可以表示负数,不同位数代表它们实际存储占用空间,以及取值范围
	var (
		ii uint8 = 1
		pp int8  = 18 // 超出范围报错 : constant 128 overflows int8
		oo int   = 64 // int uint,由操作系统决定,在32位系统中他们的长度为32位,在64位系统中,长度为64位
	)

	//字符串
	var str = "this a 测试"
	var oldstr = "\""                    //原样输出
	var line = ` 
	line1
	line2
	` //多行

	var str1 = "hello,世界"
	substr := str1[0]
	substr1 := str1[1]
	substr2 := str1[2]
	substr3 := str1[3]
	substr4 := str1[4]

	fmt.Printf("%v %v %v %v %v ", substr, substr1, substr2, substr3, substr4) // 104 101 108 108 111

	fmt.Println("\n\r")
	fmt.Printf("%v %v %v %v %v", string(substr), string(substr1), string(substr2), string(substr3), string(substr4)) //h e l l o
	//fmt.Println(substr) //104
	fmt.Println(len(str))         // 13 字符串有多少个字节  汉字算3个字节,空格算一个
	fmt.Println(len([]rune(str))) // 9 查看有多少个字符

	fmt.Println("=======\r")
	//byte: uint8 别名,表示二进制数据的byte
	//rune :int32 别名,用于表示一个符号
	for _,char:=range str{
		fmt.Printf(" %T ",char)
	}
	fmt.Println("\r\n=======")

	fmt.Printf("%v %v %v ", str, oldstr, line)

	fmt.Printf("%v %v %v ", ii, pp, oo) //1 1 64

	fmt.Println("\n\r")
	fmt.Printf("a= %v b= %v c= %v  %v %v %v %v %v %v %v ", a, b, c, x, y, z, d, e, bb, cc)

	fmt.Println("hello word")
}



//结果
104 101 108 108 111 

h e l l o13
9
=======
 int32  int32  int32  int32  int32  int32  int32  int32  int32 
=======
this a 测试 "  
        line1
        line2
         1 18 64 

a= 64 b= 3 c= 0.1  0 0 0 33 2 true false hello word

3.数组

package main

import "fmt"

func main(){
	var arr1 [5]int
	for i:=0;i<5 ;i++  {
		arr1[i]=i
	}
	printHelper("arr1",arr1)
}

func printHelper(name string,arr [5]int){
	for i:=0;i<5 ;i++  {
		fmt.Printf("%v[%v]: %v\n",name,i,arr[i])
	}

	//len 获取长度
	fmt.Printf("len of %v: %v\n",name,len(arr))
	//cap 也可以用来获取数组长度
	fmt.Printf("cap of %v: %v\n",name,cap(arr))

}

//结果:
arr1[0]: 0
arr1[1]: 1
arr1[2]: 2
arr1[3]: 3
arr1[4]: 4
len of arr1: 5
cap of arr1: 5

3.1数组的定义方式

数组的长度相同+数组的元素类型相同=相同类型

  • 定义:由若干相同类型的元素组成的序列
  • 数组的长度是固定的,声明后无法改变
  • 数组的长度是数组类型的一部分,eg:元素类型相同但是长度不同的两个数组是不同类型的
  • 需要严格控制程序所使用内存时,数组十分有用,因为其长度固定,避免了内存二次分配操作
package main

import "fmt"

func main() {
	//1 定义长度为5的数组
	var arr1 [5]int

	for i := 0; i < 5; i++ { //i<3 可以,i<10 报错
		arr1[i] = i
	}

	//printHelper("arr1", arr1)
	arr1 = [5]int{11, 12, 13, 14, 15} 长度和元素类型都相同,可以正确赋值

	//2. 简写模式,在定义的同时给出赋值
	arr2 := [5]int{22, 23, 24, 26}
	//printHelper("arr2", arr2)

	fmt.Println(arr2==arr1)

	//3. 不显示定义数组长度,由编译器完成数组长度的计算
	var arr3  =[...]int{33,34,35,36,37}
	printHelper("arr3",arr3)

	//4. 定义前三个元素为默认值0,第四个为2,最后一个元素为-1
	var arr4=[...]int{3: 2, -1}
	printHelper("arr4",arr4)

	//5.多维数组
	var twoArr [2][3]int
	for i:=0;i<2 ;i++  {
		for j:=0;j<3 ;j++  {
			twoArr[i][j]=i+j
		}
	}
	fmt.Println("twoArr:",twoArr)
}

func printHelper(name string, arr [5]int) {
	for i := 0; i < 5; i++ {
		fmt.Printf("%v[%v]: %v\n", name, i, arr[i])
	}

	//len 获取长度
	fmt.Printf("len of %v: %v\n", name, len(arr))

	//cap 也可以用来获取数组长度
	fmt.Printf("cap of %v: %v\n", name, cap(arr))
}

//结果:
false
arr3[0]: 33
arr3[1]: 34
arr3[2]: 35
arr3[3]: 36
arr3[4]: 37
len of arr3: 5
cap of arr3: 5
arr4[0]: 0
arr4[1]: 0
arr4[2]: 0
arr4[3]: 2
arr4[4]: -1
len of arr4: 5
cap of arr4: 5
twoArr: [[0 1 2] [1 2 3]]

4.切片

切片组成要素:

  • 指针:指向底层数组
  • 长度:切片中元素的长度,不能大于容量
  • 容量:指针所指向的底层数组的总容量
package main

import "fmt"

func main() {
	//1. make 初始化
	slice1 := make([]int, 5)     // slice1: [0 0 0 0 0]
	slice2 := make([]int, 6, 10) //初始化长度为5,容量为10的切片 slice2: [0 0 0 0 0 0]

	//2. 简短定义
	slice3 := []int{1, 2, 3, 4} // slice3: [1 2 3 4]
	//数组: arr1 = [5]int{11, 12, 13, 14, 15}
	//数组与切片区别: []内有数字的数组,没有数字的是切片

	//3.使用数组来初始化切片
	arr := [5]int{1, 2, 3, 4, 5}
	slice4 := arr[0:3] //左闭右开区间 slice4: [1 2 3]

	//4.使用切片来初始化切片
	sliceA := []int{11, 12, 13, 14, 15, 16}
	sliceB := sliceA[0:3] //A:[11 12 13 14 15 16] B:[11 12 13]

	//5. 长度和容量
	fmt.Println("len:", len(sliceA))
	fmt.Println("cap:", cap(sliceA))

	//6. 改变切片长度
	sliceA = append(sliceA, 17)

	fmt.Println("append after:")
	fmt.Println("len:", len(sliceA))
	fmt.Println("cap:", cap(sliceA))
	/*
		len: 6
		cap: 6
		append after:
		len: 7
		cap: 12*///底层数组容量不够时,会重新分配数组空间,通常为两倍

	//7.对底层数组的修改,将影响上层多个切片的值
	/*newsliceA:=sliceA[0:3]
	fmt.Println("sliceA:",sliceA)
	fmt.Println("newsliceA:",newsliceA)

	newsliceA[0]=10
	fmt.Println("after modifying:")
	fmt.Println("sliceA:",sliceA)
	fmt.Println("newsliceA:",newsliceA)*/

	/* 结果:
	sliceA: [11 12 13 14 15 16 17]
	newsliceA: [11 12 13]
	after modifying
	sliceA: [10 12 13 14 15 16 17]
	newsliceA: [10 12 13]
	*/

	//8.使用 copy 方法可以避免共享同一个底层数组
	newsliceA2 := make([]int, len(sliceA))
	copy(newsliceA2, sliceA)
	fmt.Println("sliceA: ", sliceA)
	fmt.Println("newsliceA2: ", newsliceA2)

	newsliceA2[0] = 111
	fmt.Println("after modifying: ")
	fmt.Println("sliceA: ", sliceA)
	fmt.Println("newsliceA2: ", newsliceA2)
	/*结果:
	    sliceA:  [11 12 13 14 15 16 17]
		newsliceA2:  [11 12 13 14 15 16 17]
		after modifying:
		sliceA:  [11 12 13 14 15 16 17]
		newsliceA2:  [111 12 13 14 15 16 17]
	*/

	//9.使用copy函数进行切片部分拷贝
	newsliceA3 := make([]int, len(sliceA))
	copy(newsliceA3, sliceA[3:]) //newsliceA3: [14 15 16 17 0 0 0]

	fmt.Println("newsliceA3:", newsliceA3)

	fmt.Printf("%v %v %v %v %v %v \n", slice1, slice2, slice3, slice4, sliceA, sliceB)

}

5.map

map 是一种无序的键值对, 它是数据结构 hash 表的一种实现方式,类似 Python 中的字典

使用关键字 map 来声明形如:

map[KeyType]ValueType

注意点:

  • 必须指定 key, value 的类型,插入的纪录类型必须匹配。
  • key 具有唯一性,插入纪录的 key 不能重复。
  • KeyType 可以为基础数据类型(例如 bool, 数字类型,字符串), 不能为数组,切片,map,它的取值必须是能够使用 == 进行比较。
  • ValueType 可以为任意类型。
  • 无序性。
  • 线程不安全, 一个 goroutine 在对 map 进行写的时候,另外的 goroutine 不能进行读和写操作,Go 1.6 版本以后会抛出 runtime 错误信息。
package main

import (
	"fmt"
	"sync"
)

func main() {
	//1. 使用var 声明
	var cMap map[string]int
	//cMap["beijing"] = 1
	fmt.Println(cMap == nil)    //true
	fmt.Println("cMap: ", cMap) //cMap:  map[]

	//线程不安全, 一个 goroutine 在对 map 进行写的时候,另外的 goroutine 不能进行读和写操作
	//panic: assignment to entry in nil map
	// goroutine 1 [running]:

	//2. 使用make 声明
	//在使用make初始化map的时候,可以指定初始容量,这在能预估map key数量的情况下,减少动态分配的次数,从而提升性能
	cMap2 := make(map[string]int, 10)
	cMap2["beijing"] = 2
	fmt.Println("cMap2: ", cMap2) //cMap2:  map[beijing:2]

	//3.简短声明
	cMap4 := map[string]int{"beijing": 1}
	fmt.Println("cMap4: ", cMap4) //cMap4:  map[beijing:1]

	//4.map 基本操作
	cMap5 := map[string]int{}
	cMap5["beijing"] = 1    //写
	cMap5["guangzhou"] = 12 //写

	code := cMap5["beijing"]    //读
	fmt.Println("code: ", code) //code:  1

	code2 := cMap5["guangzhou"]  //读不存在的key
	fmt.Println("code2:", code2) //code2: 0

	code3, ok := cMap5["guangzhou"] //检查key 是否存在
	if ok {
		fmt.Println("code3: ", code3)
	} else {
		fmt.Println("key not exist")
	}

	delete(cMap5, "beijing")      //删除key
	fmt.Println(cMap5["beijing"]) //0

	//5.循环和无序性
	cMap6 := map[string]int{"beijing": 1, "shanghai": 2, "shengzhen": 3}
	for city, code6 := range cMap6 {
		fmt.Printf("%s : %d ", city, code6)
		fmt.Println()
	}
	//beijing 1
	//shanghai 2
	//shengzhen 3

	//6.线程不安全
	cMap7 := make(map[string]int)
	var wg sync.WaitGroup
	//var wg sync.Map
	wg.Add(2)

	go func() {
		cMap7["beijing"] = 1
		wg.Done()
	}()

	go func() {
		cMap7["shanghai"] = 2
		wg.Done()
	}()
	wg.Wait()
	fmt.Printf("cMap7: %v \n", cMap7)//cMap7: map[beijing:1 shanghai:2]

	//7.map 嵌套
	provices := make(map[string]map[string]int)

	provices["beijing"] = map[string]int{
		"东城区": 11,
		"西城区": 12,
		"朝阳区": 13,
		"海淀区": 14,
	}

	fmt.Println(provices["beijing"])//map[东城区:11 朝阳区:13 海淀区:14 西城区tl:12]

}

6.自定义类型

语法: type Name type

package main

import "fmt"

//自定义类型
//声明: type City string

type City string
type Age int
type Height int

func main() {
	city := City("beijing")
	age := Age(12)
	height := Height(175)

	fmt.Println(" I live in", city+"上海")
	fmt.Println("city len:", len(city))
	if age > 14 {
		fmt.Println("age is bigger than 14")
	} else {
		fmt.Println("age is smaller than  14")
	}

	//因为 printAge 方法期望的是 int 类型,但是我们传入的参数是 Age,他们虽然具有相同的值,但为不同的类型
	printAge(int(age)) //显式的类型转换
	fmt.Println("除法计算: ", int(height)/int(age))
}

//函数参数
func printAge(ageNum int) {
	fmt.Println("Age is:", ageNum)
}

7.结构体

package main

import "fmt"

//结构体是由零个或多个任意类型的值聚合成的实体
//数组、切片和 Map 可以用来表示同一种数据类型的集合,但是当我们要表示不同数据类型的集合时就需要用到结构体。
//注意:
//1.字段名是唯一的
//2.结构体中字段占一行,但类型相同的字段,可以放在一行
//3.结构体中的字段如果是小写字母开头,那么其他package就无法使用该字段


//使用 type,struct 关键字定义结构体

type Student struct {
	Age  int
	Name string
	sex string
}

//结构体嵌套结构体,如果嵌入的结构体是本身,那么只能是指针
type Tree struct {
	value int
	left,right *Tree
}

func main() {
	stu := Student{
		Age:  11,
		Name: "lxw",
	}
	fmt.Println("学生1:", stu) //学生: {11 lxw}

	//在赋值的时候,字段名可以忽略
	fmt.Println("赋值: ",Student{20, "lxw22",""})

	//嵌套结构体
	tree := Tree{
		value: 1,
		left: &Tree{
			value: 1,
			left:  nil,
			right: nil,
		},
		right: &Tree{
			value: 2,
			left:  nil,
			right: nil,
		},
	}

	fmt.Printf(">>> %#v\n", tree)

	//结构体比较,前提是字段类型是可以比较的
	tree1:=Tree{
		value: 222,
	}
	tree2:=Tree{
		value: 333,
	}
	fmt.Printf(">>> %v\n",tree1==tree2)
	
	return
}

    //结构体内嵌匿名成员
    //匿名成员:声明一个成员对应的数据类型而不指明成员的名字

package main

import "fmt"

type Person struct {
	Age  int
	Name string
}

type Student struct {
	Person
}

//声明一个成员的数据类型而不指名成员的名字,这类成员就叫匿名成员

func main() {
	per := Person{
		Age:  18,
		Name: "lxw",
	}
	per2 := Person{
		Age:  20,
		Name: "hello",
	}

	stu := Student{Person: per}
	stu2 := Student{Person: per2}

	fmt.Println("stu.Age: ", stu.Age)
	fmt.Println("stu2.Name: ", stu2.Name)
	//stu.Age:  18
	//stu.Name:  hello
}

8.函数

package main

import "fmt"

//函数是语句序列的集合,能够将一个大的工作分解成小的任务,对外隐藏了实现细节
//函数组成: 函数名,参数列表(parameter-list), 返回值(result-list) ,函数体(body)

//语法
/*func name(param-list)(result-list)  {
	body
}*/

//1. 单返回值函数
func plus(a, b int) (res int) {
	return a + b
}

//2.多返回值函数
func multi() (string, int) {
	return "lxw", 18
}

//3.命名返回值
func namedReturnValue() (name string, height int) {
	name = "xiaoming"
	height = 180
	return
}

//4.参数可变函数
func sum(nums ...int) int {
	fmt.Println("\n len of nums is:", len(nums))
	res := 0

	for _, v := range nums {
		res += v
	}
	return res
}

//6.闭包函数
func addInt(n int) func() int {
	i := 0
	return func() int {
		i += n
		return i
	}
}

//7.函数作为参数
func sayHello(name string) {
	fmt.Println("Hello ", name)
}
func logger(f func(string), newName string) {
	fmt.Println("开始调用sayHello:")
	f(newName)
	fmt.Println("结束调用 sayHello \n")
}

//8. 传值和传引用
func sendValue(name string)  {
	name="lxw"
}

func sendAddress(name *string){ //传引用
	*name="lxw1234"
}

func main() {
	fmt.Println("单返回值函数plus: ", plus(1, 2)) //3

	name, age := multi()
	fmt.Printf("多返回值函数multi:[name:%v age:%v]\n", name, age)
	name2, _ := multi()
	fmt.Printf("多返回值函数multi:[name:%v ]\n", name2)

	name3, height := namedReturnValue()
	fmt.Printf("命名返回值: [name:%v height:%v]\n", name3, height)

	fmt.Printf("参数可变函数结果1: %v \n", sum(1))
	fmt.Printf("参数可变函数结果2: %v \n", sum(1, 2))
	fmt.Printf("参数可变函数结果3: %v \n", sum(1, 2, 3))

	//5.匿名函数
	func(name string) {
		fmt.Println(name)
	}("\n这个是匿名函数")

	//6.闭包函数
	addOne := addInt(1)
	fmt.Printf("闭包函数加1: %v \n", addOne())
	fmt.Printf("闭包函数加1: %v \n", addOne())
	fmt.Printf("闭包函数加1: %v \n\n", addOne())

	addTwo := addInt(2)
	fmt.Printf("闭包函数加2: %v \n", addTwo())
	fmt.Printf("闭包函数加2: %v \n", addTwo())
	fmt.Printf("闭包函数加2: %v \n", addTwo())

	//7.函数作为参数
	logger(sayHello, "lxw")

	//8.传值和传引用
	str:="北京"
	fmt.Println("开始调用sendValue,str : ",str) //开始调用sendValue,str :  北京
	sendValue(str)
	fmt.Println("结束调用sendValue,str : ",str) //结束调用sendValue,str :  北京

	fmt.Println("开始调用sendAddress,str : ",str) //开始调用sendAddress,str :  北京
	sendAddress(&str) //
	fmt.Println("结束调用sendAddress,str : ",str) //结束调用sendAddress,str :  lxw1234

}

结果:

单返回值函数plus:  3
多返回值函数multi:[name:lxw age:18]
多返回值函数multi:[name:lxw ]
命名返回值: [name:xiaoming height:180]

 len of nums is: 1
参数可变函数结果1: 1 

 len of nums is: 2
参数可变函数结果2: 3 

 len of nums is: 3
参数可变函数结果3: 6 

这个是匿名函数
闭包函数加1: 1 
闭包函数加1: 2 
闭包函数加1: 3 

闭包函数加2: 2 
闭包函数加2: 4 
闭包函数加2: 6 
开始调用sayHello:
Hello  lxw
结束调用 sayHello 

开始调用sendValue,str :  北京
结束调用sendValue,str :  北京
开始调用sendAddress,str :  北京
结束调用sendAddress,str :  lxw1234

9.方法method

在面向对象语言中,用"类"来封装属于自己的数据和函数,这些类的函数就叫做方法.

//方法(method)的声明和函数很相似,只不过它必须指定接受者
声明 func(t T)F(){}
注意:
接受者的类型只能为用关键字type 定义的类型,例如自定义类型,结构体
同一个接受者的方法名不能重复(没有重载),如果是结构体,方法名还不能和字段名重复
值作为接受者无法修改其值,如果有更改需求,需要使用指针类型
package main

import "fmt"

//在面向对象语言中,用"类"来封装属于自己的数据和函数,这些类的函数就叫做方法.

//方法(method)的声明和函数很相似,只不过它必须指定接受者
//声明 func(t T)F(){}
注意:
接受者的类型只能为用关键字type 定义的类型,例如自定义类型,结构体
同一个接受者的方法名不能重复(没有重载),如果是结构体,方法名还不能和字段名重复
值作为接受者无法修改其值,如果有更改需求,需要使用指针类型

//type T int64
//func (t T) F()  {} //T 接受者不是任意类型,它只能为用关键字type定义的类型(例如自定义类型,结构体)

//type T struct{}

//结构体方法名不能和字段重复
type T struct {
	F string
}
func (T)  F(){}
//func(T) F(a string){}

func main() {
	//t := T(10)
	t:=T{}
	t.F()
}

//2.接收者可以同时为值和指针

func (T) F(){}
func (*T)N()  {}

func main()  {
	t:=T{}
	t.F()
	t.N()

	t1:=&T{} //无论值类型T 还是指针类型 &T 都可以同时访问F和N方法
	t1.F()
	t1.N()
}

//3.值和指针作为接受者的区别
值作为接收者(T) 不会修改结构体值,而指针 *T 可以修改。
type T struct {
	value int
}

func (m T) stayTheSame() {
	m.value = 3
}

func (m *T) Update(){
	m.value=3
}

func main()  {
	m:=T{0}
	fmt.Println(m)//{0}

	m.stayTheSame()
	fmt.Println(m)//{0}
	
	m.Update()
	fmt.Println(m)//{3}
}

10.接口

接口类型是一种抽象类型,是方法的集合,其他类型实现了这些方法就是实现了接口
package main

import (
	"fmt"
	"math"
)

//接口类型是一种抽象类型,是方法的集合,其他类型实现了这些方法就是实现了接口

//打印矩形和圆的面积和周长

//定义接口
type tmp interface {
	area() float32      //面积
}
type geometry interface {
	//area() float32      //面积
	tmp  //接口中内嵌接口
	perimeter() float32 //周长
}

//定义结构体: 矩形
type rect struct {
	length, width float32
}

func (r rect) area() float32 {
	return r.length * r.width
}

func (r rect) perimeter() float32 {
	return 2 * (r.length + r.width)
}

//定义结构体:圆形

type circle struct {
	radius float32 //半径
}

func (c circle) area() float32 {
	return math.Pi * c.radius * c.radius
}

func (c circle) perimeter() float32 {
	return 2 * math.Pi * c.radius
}

//展示调用
func show(name string, param interface{}) {
	//fmt.Println(param)
	switch param.(type) {
	case geometry:
		//类型断言
		fmt.Printf(" %v的面积是 %v \n", name, param.(geometry).area())
		fmt.Printf(" %v的周长是 %v \n", name, param.(geometry).perimeter())
	default:
		fmt.Println("wrong type")
	}
}

func main() {

	rec := rect{
		length: 1,
		width:  2,
	}
	show("矩形", rec)

	cir := circle{radius: 1}
	show("圆形", cir)

	show("测试:", "test param")
}

//结果:
bogon:web liutao$ go run interface.go 
 矩形的面积是 2 
 矩形的周长是 6 
 圆形的面积是 3.1415927 
 圆形的周长是 6.2831855 
wrong type

11.分支循环控制

分支循环: if switch for 进行条件判断和流程控制
case 条件可以是多个值,同一个 case 中的多值不能重复
使用break 和continue 对循环进行控制
break 会结束所有循环
continue 会跳过当前循环直接进入下一次循环
package main

import "fmt"

//分支循环: if switch for 进行条件判断和流程控制

func main() {
	age := 82

	//1. if分支
	if age > 6 && age <= 12 {
		fmt.Println("it's primary school")
	} else if age > 12 && age <= 15 {
		fmt.Println("it is middle school")
	} else if age < 20 {
		fmt.Println(" it is high school")
	} else {
		fmt.Println("年龄超限")
	}

	//2. switch 分支
	switch age {
	case 5:
		fmt.Printf("his age is %v \n", age)
	case 6, 7, 8: //case 条件可以是多个值,同一个 case 中的多值不能重复
		fmt.Printf("his age is %v \n", age)
	default:
		fmt.Println("The age is unknown ")
	}

	//2.1 还可以使用if ...else 作为case 条件
	switch {
	case age >= 5 && age <= 12:
		fmt.Println("小学生")
	case age > 12 && age <= 15:
		fmt.Println("中学生")
	case age > 40 || age < 5:
		fmt.Println("不是学生")
	default:
		fmt.Println("age is unknown")
	}

	//2.2 调用
	checkType(8)
	checkType("hello world")

	//3.for 循环
	for i := 0; i < 2; i++ {
		fmt.Println("loop with index", i)
	}

	//3.1 使用for..range 对数组,切片 map 字符串等进行循环操作 ==等同于PHP中的foreach
	numbers := [5]int{1, 2, 3}
	for i, v := range numbers { //这里的i v 是切片元素的位置索引和值
		fmt.Printf("numbers[%d] is %d \n", i, v)
	}

	//3.2 循环 map
	cityCode := map[string]int{
		"北京": 1,
		"上海": 2,
		"广州": 3,
	}
	for i, v := range cityCode { //i  v 是map 键值对的键和值
		fmt.Printf("%s is %d \n", i, v)
	}

	//3.3 使用break 和continue 对循环进行控制
	//break 会结束所有循环
	//continue 会跳过当前循环直接进入下一次循环
	num := []int{1, 2, 3, 4, 5}
	for i, v := range num {
		if v == 6 {
			break
		}

		if v%2 == 0 {
			continue
		}

		fmt.Printf("num[%d] is %d \n", i, v)
	}

}

//2.2使用switch 对interface 进行断言
func checkType(i interface{}) {
	switch v := i.(type) { //.(type)只能在switch 中使用
	case int:
		//fmt.Printf("%v is a int\n", i.(type))   //报错: use of .(type) outside type switch
		fmt.Printf("%v is a int\n", v)
	case string:
		fmt.Printf("%v is a string\n", v)
	default:
		fmt.Printf("%v's type is unkown\n", v)
	}
}

//结果:
年龄超限
The age is unknown 
不是学生
8 is a int
hello world is a string
loop with index 0
loop with index 1
numbers[0] is 1 
numbers[1] is 2 
numbers[2] is 3 
numbers[3] is 0 
numbers[4] is 0 
北京 is 1 
上海 is 2 
广州 is 3 
num[0] is 1 
num[2] is 3 
num[4] is 5 

12.异常处理

package main

import (
	"errors"
	"fmt"
)

//1.defer
// defer 通常用于延迟调用指定的函数,被defer 调用的函数称为"延迟函数"
//defer 常用场景:
//defer 常被用于处理成对的操作,如打开和关闭,链接和断开链接,加锁和释放锁.恰当使用defer 能保证资源正确释放.

func outFunc() {
	defer fmt.Println(" world \n")
	fmt.Print("Hello")
}

func testDefer() (i int) {
	defer func() {
		fmt.Println(i)
		i = 4
	}()

	i = 2
	return i
}

func printNum() {
	for i := 0; i < 5; i++ {
		//1. defer fmt.Println(i) //4 3 2 1 0

		//2.
		/*defer func() {
			fmt.Println(i) //5 5 5 5 5 等到执行 defer 调用的函数时i 已经是5 了
		}()*/

		//3.
		defer func(v int) {
			fmt.Println(v) //4 3 2 1 0 等同于第一种情况,延迟直接打印值
		}(i)

	}
}

//2.panic
func panicFunc() {
	defer func() {
		//recover 返回的是一个interface类型的结果,如果捕获了panic事件,该结果就为非nil
		if p := recover(); p != nil {
			fmt.Println("panic info: ",p)
			fmt.Println("recover panic")
		}
	}()
	panic(errors.New("this is a test for panic"))
}

//recover
//recover 函数能使当前程序从panic中恢复,recover能够拦截panic事件,使得程序不会因为意外而触发panic事件而完全退出


func printNumbers(){
	for i:=0;i<5;i++{
		defer func( n int) {
			fmt.Printf("I is %v ,res is %v \n",i,n)
		}(i*2)
	}
}

func printArr(){
	defer func() {
		if f:=recover();f!=nil{
			fmt.Printf("超出数组长度越界:%v \n",f)
		}
	}()
	//当程序遇到致命错误导致无法继续运行时就会触发panic,如数组越界,空指针
	s := []int{1, 2, 3}
	for i := 0; i <= 4; i++ {
		fmt.Printf("数组s的第%v元素: %v \n ",i,s[i])
	}
}
func main() {

	outFunc()
	//printNum()

	printNumbers()

	testDefer() //2

	printArr()


	fmt.Println("before panic")
	panicFunc() //主动调用panic函数
	fmt.Println("after panic")

}
//结果:
Hello world 

I is 5 ,res is 8 
I is 5 ,res is 6 
I is 5 ,res is 4 
I is 5 ,res is 2 
I is 5 ,res is 0 
2
数组s的第0元素: 1 
 数组s的第1元素: 2 
 数组s的第2元素: 3 
 超出数组长度越界:runtime error: index out of range [3] with length 3 
before panic
panic info:  this is a test for panic
recover panic
after panic

13.goroutine(并发)

package main

import (
	"fmt"
	"log"
	"sync"
	"time"
)

func doSomething(id int) { //总耗时9秒,每个任务是阻塞的
	log.Printf("before do job :(%d) \n", id)
	time.Sleep(3 * time.Second)
	log.Printf("after do job :(%d) \n", id)
}

func doSomething2(id int, wg *sync.WaitGroup) {
	defer wg.Done()
	log.Printf("before do job :(%d) \n", id)
	time.Sleep(3 * time.Second)
	log.Printf("after do job :(%d) \n", id)
}

//使用goroutine 从9S 降低到3秒,提高了我们的效率,根据打印的输出结果得出:
//多个goroutine 的执行是随机的
//对于io密集型任务特别有效,比如文件,网络读写
func main() {
	/*go doSomething(1)
	go doSomething(2)
	go doSomething(3)
	time.Sleep(4 * time.Second)*/

	var wg sync.WaitGroup
	wg.Add(3) //fatal error: all goroutines are asleep - deadlock!

	go doSomething2(1, &wg)
	go doSomething2(2, &wg)
	go doSomething2(3, &wg)

	wg.Wait()
	log.Printf("finish all jobs \n")

	fmt.Println("========")

	//通过方法传参的方式,将i的值拷贝到新的变量v中,而在每个goroutine都对应了一个属于自己作用域的v变量,
	for i := 0; i < 3; i++ {
		go func(v int) {
			//fmt.Println(i)
			log.Printf("I is : %d \n", v)
		}(i)
	}
	time.Sleep(1 * time.Second)
}

//结果
2021/06/22 17:10:24 before do job :(1) 
2021/06/22 17:10:24 before do job :(3) 
2021/06/22 17:10:24 before do job :(2) 
2021/06/22 17:10:27 after do job :(3) 
2021/06/22 17:10:27 after do job :(1) 
2021/06/22 17:10:27 after do job :(2) 
2021/06/22 17:10:27 finish all jobs 
========
2021/06/22 17:10:27 I is : 0 
2021/06/22 17:10:27 I is : 1 
2021/06/22 17:10:27 I is : 2 

14.channel(通道)

package main

import (
	"fmt"
	"time"
)

//channel 是go 中实现并发的重要机制,channel 是goroutine 之间进行通信的重要桥梁.

//1. 使用内建函数make创建channel
//ch:=make(chan int) //channel 必须定义其传递的数据类型
//也可以用var 声明channel
//var ch chan int
//以上声明的channel 都是双向的,可以向该channel 发送数据,也可以接受数据
//发送和接受是channel 的两个基本操作
//ch <-x //channel 接受数据x
//x <-ch //channel 发送数据并赋值给x
//<- ch //channel 发送数据,忽略接受者

//注意的是,程序中必须同时有不同的 goroutine 对非缓冲通道进行发送和接收操作,否则会造成阻塞。
//main 函数是一个 goroutine, 在这一个 goroutine 中发送了数据给非缓冲通道,但是却没有另外一个 goroutine 从非缓冲通道中里读取数据, 所以造成了阻塞或者称为死锁

//2.单向channel :即限定了该channel 只能接受或者发送数据,单向通道通常作为函数的参数



func receive(receiver chan<- string, msg string) {
	receiver <- msg
}
func send(sender <-chan string, receiver2 chan<- string) {
	msg := <-sender
	receiver2 <- msg
	//receiver2 <- sender
	//fmt.Println(receiver2)
}

func strWorker(ch chan string) {
	time.Sleep(1 * time.Second)
	fmt.Println("do something with strWorker")
	ch <- "str"
}
func intWorker(ch chan int) {
	time.Sleep(1 * time.Second)
	fmt.Println("do something with intWorker")
	ch <- 1
}

func worker(done chan bool){
	fmt.Println("start working")
	done<-true
	fmt.Println("end working")
}

func main() {
	/*ch := make(chan string)
	go func() { //ge
		ch <- "ping"
	}()
	fmt.Println(<-ch) //fatal error: all goroutines are asleep - deadlock!*/

	/*ch := make(chan int, 3)
	ch <- 1 //chan 接受数据 1
	ch <- 2
	ch <- 3
	for i := 0; i < 3; i++ {
		fmt.Println(<-ch) // 发送数据
	}*/

	ch1 := make(chan string, 1)
	ch2 := make(chan string, 1)

	receive(ch1, "pass message")
	send(ch1, ch2)
	fmt.Println(<-ch2)

	//3.channel 遍历和关闭 close 函数用于关闭channel ,关闭后channel有缓冲数据,依然可以读取,
	// 但无法再发送数据给已经关闭的channel
	ch := make(chan int, 10)
	for i := 0; i < 10; i++ {
		ch <- i
	}
	close(ch)
	res := 0
	for v := range ch {
		res += v
	}
	fmt.Println(res)

	//4.select 语句
	//select 专门用于通道发送和接受操作,看起来和switch相似,但是进行选择和判断的方法完全不同
	//通过select 的使用,保证了worker 中事务可以执行完毕后才退出main函数

	/*chStr := make(chan string)
	chInt := make(chan int)

	go strWorker(chStr)
	go intWorker(chInt)
	for i := 0; i < 2; i++ {
		select {
		case <-chStr:
			fmt.Println("get value from strWorker")
		case <-chInt:
			fmt.Println("get value from intWorker")
		}
	}*/


	done:=make(chan bool ,1)
	go worker(done)
	fmt.Println(<-done) //注释掉后,main 函数会提前退出
}

//结果:
pass message
45
start working
end working
true

15.锁的使用

使用channel 在多个goroutine 之间进行通信,较为常用通信方式是共享内存.
在go中还有一种读写锁,sync.RwMutex,对于我们的共享对象,如果可以分离出读和写两个互斥信号的情况,可以考虑使用它来提高读的并发性能
总结:
我们可以通过共享内存的方式实现多个goroutine 中的通信.
多个goroutine 对于共享的内存进行写操作的时候,可以使用Lock来避免数据不一致的情况.
对于可以分离为读写操作的共享数据可以考虑使用 sync,RWMutex 来提高其读的并发能力.
package main

import (
	"fmt"
	"log"
	"sync"
	"sync/atomic"
	"time"
)

//使用channel 在多个goroutine 之间进行通信,较为常用通信方式是共享内存.

var name string

func printName() {
	log.Println("name is ", name)
}

func main() {
	//1.这就是最简单的通过共享变量(内存)的方式在多个goroutine 进行通信的方式
	name = "小明"
	go printName()
	go printName()
	time.Sleep(time.Second)

	name = "小红"
	go printName()
	go printName()
	time.Sleep(time.Second)

	//2.并发对同一个切片进行写操作的时候,会出现数据不一致的问题,这就是一个典型的共享变量的问题.
	//使用lock(锁)来修复,最后都包含0-9这10个数字

	//在go中还有一种读写锁,sync.RwMutex,对于我们的共享对象,如果可以分离出读和写两个互斥信号的情况,
	//可以考虑使用它来提高读的并发性能
	/*	var (
			wg      sync.WaitGroup
			numbers []int
			mux     sync.Mutex //互斥锁,只有一个信号标量,

		)
		for i := 0; i < 10; i++ {
			wg.Add(1)
			go func(i int) {

				mux.Lock()
				numbers = append(numbers, i)
				mux.Unlock()

				wg.Done()
			}(i)

		}
		wg.Wait()
		fmt.Println("The numbers is", numbers)*/

	//3.
	var (
		mux    sync.Mutex
		state1 = map[string]int{
			"a": 65,
		}
		muxTotal uint64

		rw     sync.RWMutex
		state2 = map[string]int{
			"a": 65,
		}
		rwTotal uint64
	)

	for i := 0; i < 10; i++ {
		go func() {
			for {
				mux.Lock()
				_ = state1["a"]
				mux.Unlock()
				atomic.AddUint64(&muxTotal, 1)
			}
		}()
	}

	for i := 0; i < 10; i++ {
		go func() {
			for {
				rw.RLock()
				_ = state2["a"]
				rw.RUnlock()
				atomic.AddUint64(&rwTotal, 1)
			}
		}()
	}
	time.Sleep(time.Second)
	fmt.Println("sync.Mutex readOps is", muxTotal)  //sync.Mutex readOps is 234 6154
	fmt.Println("sync.RwMutex readOps is", rwTotal) //sync.RwMutex readOps is 1780 6741
	//可以看到使用sync.RwMutex 的读的并发能力大概是 sync.Mutex 的十倍,从而大大提高了其并发能力

}
//结果:
2021/06/23 11:02:53 name is  小明
2021/06/23 11:02:53 name is  小明
2021/06/23 11:02:54 name is  小红
2021/06/23 11:02:54 name is  小红
sync.Mutex readOps is 2346154
sync.RwMutex readOps is 17806741

16.原子操作

一.原子操作

automic 提供的原子操作能够确保任一时刻只有一个 goroutine 对变量进行操作,善用atomic能够避免程序中出现大量的锁操作
automic 常见的操作有:增减, 载入,比较并变换,交换,存储
例子: func AddInt32(addr *int32,delta int32)(new int32)
第一个参数必须是指针类型的值,通过指针变量可以获取被操作数在内存中的地址,确保同一时间只有一个goroutine能够进行操作.

二.载入操作:
atomic 包中提供了如下以Load 为前缀的载入操作
例如: func LoadInt32(addr *int32)(val int32)
载入操作能够保证原子的读变量的值,当读取的时候,任何其他goroutine 都无法对该变量进行读写

三.比较并交换:CAS(compare And Swap) 这类操作的前缀为CompareAndSwap

总结:
atomic 和锁的方式其性能没有太大区别
atomic 写法相交于使用锁,更简单

package main

import (
	"fmt"
	"sync/atomic"
	"time"
)
//一.原子操作

//automic 提供的原子操作能够确保任一时刻只有一个 goroutine 对变量进行操作,善用atomic能够避免程序中出现大量的锁操作
//automic 常见的操作有:增减, 载入,比较并变换,交换,存储
//例子: func AddInt32(addr *int32,delta int32)(new int32)
//第一个参数必须是指针类型的值,通过指针变量可以获取被操作数在内存中的地址,确保同一时间只有一个goroutine能够进行操作.

//1.使用锁和原子操作来实现多个goroutine 对同一个变量进行累加操作

/*func main() {
	var (
		mux   sync.Mutex
		total uint64
	)
	for i := 0; i < 1; i++ {
		go func() {
			for{
				mux.Lock()
				total += 1
				mux.Unlock()
				time.Sleep(time.Microsecond)
			}
		}()
	}
	time.Sleep(time.Second)
	fmt.Println("the total numbers is", atomic.LoadUint64(&total))
}*/

//2.使用atomic 实现,累加操作
func main(){
	var total int64
	for i:=0;i<10 ;i++  {
		go func() {
			for{
				atomic.AddInt64(&total,-1)
				time.Sleep(time.Microsecond)
			}
		}()
	}
	time.Sleep(time.Second)
	fmt.Println("the total numbers is ",total)
}



//结果:
the total numbers is  -507066

17.读文件

package main

import (
	"bufio"
	"fmt"
	"io"
	"os"
)

//1.全量读
/*func main(){
	data,err:=ioutil.ReadFile("./lock.go")
	fmt.Println(err)
	fmt.Println(string(data))
}*/

//2.带缓冲区读: 输出文件的前 16个字节内容
/*func main()  {
	f,_:=os.Open("./lock.go")
	defer f.Close()

	buf:=make([]byte,16)
	f.Read(buf)
	fmt.Println(string(buf))
}*/

/*
package main
im
*/

// 3.任意位置读:一个文件特定地方特定长度的内容

func main() {
	f, _ := os.Open("./lock.go")
	defer f.Close()

	//第一种: f.Seek+f.Read 非并发安全的
	//File.Seek(offset, whence),设置光标的未知
	//offset步长值,偏移量
	//whence,从哪开始:0距离文件开头,1当前位置,2距离文件末尾
	/*for i := 0; i < 5; i++ {
		go func() {
			b1 := make([]byte, 2) //5 读取几个字节

			//f.Seek(5, 0)
			//f.Read(b1)

			f.ReadAt(b1,5)
			fmt.Println(string(b1))

			//f.Seek(2, 0)
			//f.Read(b1)

			f.ReadAt(b1,2)
			fmt.Println(string(b1))
		}()
	}
	time.Sleep(time.Second)*/

	//第二种 使用f.ReadAt 并发安全的
	//f.ReadAt(b1,5) //5 步长值,从哪个位置开始读取文件

	//实战一: 使用buf实现ioutil.ReadFile 效果
	/*content := make([]byte, 0)
	buf := make([]byte, 16)
	for {
		n, err := f.Read(buf)
		if err == io.EOF {
			break
		}

		if n == 16 {
			content = append(content, buf...)
		} else {
			content = append(content, buf[0:n]...)
		}
	}
	fmt.Println(string(content))*/

	//实战二:使用 bufio 实现行统计
	br := bufio.NewReader(f)
	totalLine := 0
	for {
		_, isPrefix, err := br.ReadLine()
		if !isPrefix {
			totalLine += 1
		}

		if err == io.EOF {
			break
		}
		//fmt.Println(line) //打印字母的 Unicode 字符编码

	}
	fmt.Println("total line is", totalLine)
}
//结果:
total lines is: 31

18.写入文件

创建文件

  • 使用 os.Create(name string) 方法创建文件
  • 使用 os.Stat(name string) 方法获取文件信息

通过 ioutil.WriteFile 一步完成文件创建和写入操作。假如该文件之前已经存在,那么将会覆盖掉原来的内容,写入新的内容。

writeAt 可以在文件指定位置写入内容;

使用 Buffered Writer 可以避免太多次的磁盘 IO 操作。写入的内容首先是存在内存中,当调用 Flush() 方法后才会写入磁盘。

package main

import (
	"bufio"
	"fmt"
	"io/ioutil"
	"os"
)

//创建文件
//使用os.Create(name string)方法创建文件
//使用os.Stat(name string) 方法获取文件信息
func checkErr(err error) {
	if err != nil {
		panic(err)
	}
}

func readFile(path string) {
	data, err := ioutil.ReadFile(path)
	checkErr(err)
	fmt.Println("file content:", string(data))
}

/*func main() {
	path := "test.txt"
	newFile, err := os.Create(path)
	checkErr(err)
	defer newFile.Close()

	fileInfo, err := os.Stat(path)
	if err != nil {
		if os.IsNotExist(err) {
			fmt.Println("文件不存在")
			return
		}
	}
	fmt.Println("文件存在,文件名:", fileInfo.Name())
	fmt.Println("文件存在,文件大小:", fileInfo.Size())
	fmt.Println("文件存在,文件权限:", fileInfo.Mode())
}*/

//2. ioutil.WriteFile 一步完成文件创建和写入操作,如果文件存在,则覆盖原来内容

/*func main(){
	path:="test.txt"
	str:="hello world"

	err:=ioutil.WriteFile(path,[]byte(str),0644)
	checkErr(err)
	readFile(path)
}
*/

//3.使用 writeAt 在文件指定位置写入内容
/*func main() {
	path := "test.txt"
	str := "hello world"
	newStr := "123456 "

	//创建文件
	newFile, err := os.Create(path)
	checkErr(err)

	n1, err := newFile.WriteString(str)
	checkErr(err)
	fmt.Println("n1: ", n1)
	readFile(path)

	n2,err:=newFile.WriteAt([]byte(newStr),6) //返回写入的字节数和错误(如果有)
	checkErr(err)
	fmt.Println("n2: ",n2)
	readFile(path)

	n3,err:=newFile.WriteAt([]byte(newStr),33)//从0下标位置开始写入
	checkErr(err)
	fmt.Println("n3: ",n3)
	readFile(path)

//结果:
//n1:  11
//	file content: hello world
//n2:  7
//	file content: hello 123456
//n3:  7
//	file content: hello 123456 123456

}*/

//4.使用 Buffered Writer 可以避免太多次的磁盘io操作,写入的内容首先是存在内存中,当调用Flush()方法才会写入磁盘

func main() {
	path := "test.txt"
	str := "hello world"

	//创建文件
	newFile, err := os.Create(path)
	checkErr(err)
	defer newFile.Close()

	bufferWrite := bufio.NewWriter(newFile)

	for _, v := range str {
		written, err := bufferWrite.WriteString(string(v))
		checkErr(err)
		fmt.Println("written: ", written) //循环写入11次,每次1个字节 ; written:  1
	}
	readFile(path) //没有flush 之前内容为空,file content:

	unflushSize := bufferWrite.Buffered()
	fmt.Println("unflushSize: ", unflushSize) //unflushSize:  11

	bufferWrite.Flush()
	readFile(path) //file content: hello world
	//结果:
//written:  1
//written:  1
//written:  1
//written:  1
//written:  1
//written:  1
//written:  1
//written:  1
//written:  1
//written:  1
//written:  1
//	file content:
//unflushSize:  11
//	file content: hello world

}

19.xml 序列化和反序列化

首先看下 xml 标签常见用法:

  • xml:"xxx,omitempty" 代表如果这个字段值为空,则序列化时忽略该字段
  • xml:"xxx,attr" 代表该字段为 xml 标签的属性说明
  • xml:"-" 代表序列化时忽略该字段
  • xml:"a>b>c" 代表 xml 标签嵌套模式

以下例子,演示了

  • xml 序列化,包含 xml 标签的不同用法
  • 写 xml 文件
  • 读 xml 文件
  • xml 反序列化
package main

import (
	"encoding/xml"
	"fmt"
	"io/ioutil"
)

type Student struct {
	Name    string `xml:"name"`              //xml 标签
	Address string `xml:"address,omitempty"` //如果该字段为空就过滤掉
	Hobby   string `xml:"-"`                 // 进行xml 序列化的时候忽略该字段
	Father  string `xml:"parent>father"`     //xml 标签嵌套模式
	Mother  string `xml:"parent>mother"`     //xml 标签嵌套模式
	Note    string `xml:"note,attr"`         //xml 标签属性
}

func checkErr(err error) {
	if err != nil {
		panic(err)
	}
}

func main() {
	stu1 := Student{
		Name:  "lxw",
		Hobby: "basketball",
		Note:  "userInfo",
	}
	//xml 序列化
	newData, err := xml.MarshalIndent(stu1, "", " ")
	checkErr(err)
	fmt.Println(string(newData))

	//写xml 文件
	err = ioutil.WriteFile("stu.xml", newData, 0777)
	checkErr(err)

	//读 xml 文件
	content, err := ioutil.ReadFile("stu.xml")
	stu2 := &Student{}
	fmt.Printf("stu2 指针:%#v \n",stu2) //空指针
	// stu2 指针:&main.Student{Name:"", Address:"", Hobby:"", Father:"", Mother:"", Note:""} 


	//xml 反序列化
	err = xml.Unmarshal(content, stu2) //注意:第二个参数必须是指针
	checkErr(err)
	fmt.Printf("stu2: %#v\n", stu2)

}

//结果:
<Student note="userInfo">
 <name>lxw</name>
 <parent>
  <father></father>
  <mother></mother>
 </parent>
</Student>
stu2 指针:&main.Student{Name:"", Address:"", Hobby:"", Father:"", Mother:"", Note:""} 
stu2: &main.Student{Name:"lxw", Address:"", Hobby:"", Father:"", Mother:"", Note:"userInfo"}

stu.xml文件内容:

golang 基础编程

20.JSON 序列化和反序列化

在 Go 中我们主要使用官方的 encoding/json 包对 JSON 数据进行序列化和反序列化,主要使用方法有:

  • 序列化:

    func Marshal(v interface{}) ([]byte, error)
    
  • 反序列化:

    func Unmarshal(data []byte, v interface{}) error

golang 基础编程

package main

import (
	"encoding/json"
	"io/ioutil"
)

//在http,rpc 的微服务调用中,以 json方式对数据进行序列化和反序列化

/*func main() {
	var (
		data  = `23`
		value int64
	)
	err1 := json.Unmarshal([]byte(data), &value)
	fmt.Println("Unmarshal error is:", err1)
	fmt.Printf("Unmarshal value is :%T,%v \n", value, value)

	value2, err2 := json.Marshal(value)

	fmt.Println("Marshal error is: ", err2)
	fmt.Printf("Marshal value is: %s \n", string(value2))
	//注意:在实际应用中,我们在序列化和反序列化的时候,需要检查函数返回的 err, 如果 err 不为空,表示数据转化失败。
}*/

/*func printHelper(name string, value interface{}) {
	fmt.Printf("%s Unmarshal value is: %T ,%v \n", name, value, value)
}
func main() {
	var (
		d1 = `false`
		v1 bool
	)

	json.Unmarshal([]byte(d1), &v1)
	printHelper("d1", v1)

	var (
		d2 = `2`
		v2 int
	)
	json.Unmarshal([]byte(d2), &v2)
	printHelper("d2", v2)

	var (
		d3 = `3.14`
		v3 float32
	)
	json.Unmarshal([]byte(d3), &v3)
	printHelper("d3", v3)

	var (
		d4 = `[1,2]`
		v4 []int
	)
	json.Unmarshal([]byte(d4), &v4)
	printHelper("d4", v4)

	var (
		d5 = `{"a","b"}`
		v5 map[string]string
		v6 interface{}
	)
	json.Unmarshal([]byte(d5), &v5)
	printHelper("d5", v5)

	json.Unmarshal([]byte(d5),&v6)
	printHelper("d6(interface{})",v6)

}*/
type Result struct {
	Name  string  `json:"name"`
	Score float64 `json:"score"`
}

type Student struct {
	Id     int      `json:"id"`
	Name   string   `json:"name"`
	Result []Result `json:"result"`
}

//反序列化
/*func main() {
	dat, _ := ioutil.ReadFile("data.json")
	var s Student
	json.Unmarshal(dat, &s)
	fmt.Printf("Student's result is:%v \n", s) //Student's result is:{1 小红 [{语文 90} {数学 100}]}
}*/

//序列化

func main() {
	s := Student{
		Id:   1,
		Name: "小明",
		Result: []Result{
			Result{
				Name:  "语文",
				Score: 90,
			},
			Result{
				Name:  "数学",
				Score: 98,
			},
		},
	}
	dat, _ := json.Marshal(s)
	ioutil.WriteFile("data2.json", dat, 0755)
}
{"id":1,"name":"小明","result":[{"name":"语文","score":90},{"name":"数学","score":98}]}

21.socket

核心步骤包括:

  • 创建连接:
Dial(network, address string) (Conn, error)

注意, 这里的 network 可以为:

"tcp", "tcp4", "tcp6"
"udp", "udp4", "udp6"
"ip", "ip4", "ip6"
"unix", "unixgram", "unixpacket"
  • 通过连接发送数据:
conn.Write([]byte("GET / HTTP/1.0\r\n\r\n"))
  • 通过连接读取数据:
buf := make([]byte, 256)
conn.Read(buf)
  • 关闭连接:
conn.Close()

注意: conn 是一个 IO 对象,我们主要使用 IO 相关的帮助方法来进行读写操作。

22.socket

udp_server.go

package main

import (
	"log"
	"net"
	"time"
)

func main() {
	//1. 监听本地 UDP `127.0.0.1:8888`。
	pc, err := net.ListenPacket("udp", "127.0.0.1:8888")
	if err != nil {
		log.Fatal(err)
	}
	log.Printf("start server with: %s", pc.LocalAddr())

	defer pc.Close()

	clients := make([]net.Addr, 0)

	go func() {
		for {
			for _, addr := range clients {
				//4.通过 `pc.WriteTo([]byte("pong\n"), addr)` 向所有客户端发送消息。
				_, err := pc.WriteTo([]byte("pong\n"), addr)
				if err != nil {
					log.Println(err)
				}
			}
			time.Sleep(time.Second * 5)
		}
	}()

	for {
		buf := make([]byte, 256)
		//2.使用 `pc.ReadFrom(buf)` 方法读取客户端发送的消息。
		n, addr, err := pc.ReadFrom(buf)
		if err != nil {
			log.Println(err)
			continue
		}
		//3.使用 `clients` 来保存所有连上的客户端连接。
		clients = append(clients, addr)

		log.Println(string(buf[0:n]))
		log.Println(addr.String(), "connecting...", len(clients), "connected")
	}
}

//2021/06/24 21:00:43 start server with: 127.0.0.1:8888
//2021/06/24 21:00:48 ping..
//2021/06/24 21:00:48 127.0.0.1:63609 connecting... 1 connected
//2021/06/24 21:00:52 ping..
//2021/06/24 21:00:52 127.0.0.1:50738 connecting... 2 connected
//2021/06/24 21:01:32 ping..
//2021/06/24 21:01:32 127.0.0.1:65461 connecting... 3 connected

udp_client.go

package main

import (
	"bufio"
	"log"
	"net"
)

func main() {
	conn, err := net.Dial("udp", "127.0.0.1:8888")
	if err != nil {
		log.Fatal(err)
	}
	defer conn.Close()

	_, err = conn.Write([]byte("ping.."))
	if err != nil {
		log.Fatal(err)
	}

	reader := bufio.NewReader(conn)
	for {
		dat, _, err := reader.ReadLine()
		if err != nil {
			log.Fatal(err)
		}
		log.Println(string(dat))
	}
}

//2021/06/24 21:00:48 pong
//2021/06/24 21:00:53 pong
//2021/06/24 21:00:58 pong
//2021/06/24 21:01:03 pong
//2021/06/24 21:01:08 pong

tcp_server.go

package main

import (
	"bufio"
	"fmt"
	"log"
	"net"
)

//我们先使用 net 包创建一个 TCP Server ,然后尝试连接 Server,
// 最后再通过客户端发送 hello 到 Server,同时 Server 响应 word
func main() {
	//通过 `net.Listen("tcp", "127.0.0.1:8888")` 新建一个 TCP Server。
	l, err := net.Listen("tcp", "127.0.0.1:8888")
	if err != nil {
		log.Fatal(err)
	}
	log.Printf("Start server with : %s", l.Addr())

	defer l.Close()

	for {
		//通过 `l.Accept()` 获取创建的连接。
		conn, err := l.Accept()
		if err != nil {
			log.Fatal(err)
		}
		// 通过 `go handleConnection(c)` 新建的 goroutine 来处理连接。
		go handleConnection(conn)
	}
}

func handleConnection(conn net.Conn) {
	reader := bufio.NewReader(conn)

	for {
		dat, _, err := reader.ReadLine()
		if err != nil {
			log.Fatal(err)
			return
		}

		fmt.Println("client:", string(dat))

		_, err = conn.Write([]byte("world\n"))
		if err != nil {
			log.Fatal(err)
			return
		}
	}
}
//2021/06/24 21:04:39 Start server with : 127.0.0.1:8888
//client: hello
//client: hello

tcp_client.go

package main

import (
	"bufio"
	"fmt"
	"log"
	"net"
	"time"
)

func main() {
	//通过 `net.DialTCP("tcp", nil, addr)` 尝试创建到 TCP Sever 的连接。
	conn, err := net.Dial("tcp", "127.0.0.1:8888")
	if err != nil {
		log.Fatal(err)
	}
	defer conn.Close()

	reader := bufio.NewReader(conn)
	for {
		//通过 `conn.Write([]byte("hello\n"))` 向服务端发送数据。
		_, err := conn.Write([]byte("hello\n"))
		if err != nil {
			log.Fatal(err)
		}

		//通过 `reader.ReadLine()` 读取服务端响应数据。
		dat, _, err := reader.ReadLine()
		if err != nil {
			log.Fatal(err)
		}

		fmt.Println("server:", string(dat))
		time.Sleep(time.Second * 5)

	}
}

//bogon:web liutao$ go run tcp_client.go
//server: world
//server: world
//server: world
//server: world

unix_server.go

package main

import (
	"log"
	"net"
)

func main() {
	//使用 `net.Listen("unix", "/tmp/unix.sock")` 启动一个 Server。
	l, err := net.Listen("unix", "/tmp/unix2.sock")
	if err != nil {
		log.Fatal("listen error", err)
	}
	for {
		//使用 `conn, err := l.Accept()` 来接受客户端的连接。
		conn, err := l.Accept()
		if err != nil {
			log.Fatal("accept error", err)
		}
		//使用 `go helloServer(conn)` 来处理客户端连接,并读取客户端发送的数据 `hi` 并返回 `hello`
		go helloServer(conn)
	}
}

func helloServer(c net.Conn) {
	for {
		buf := make([]byte, 512)
		nr, err := c.Read(buf)
		if err != nil {
			return
		}

		data := buf[0:nr]
		log.Println(string(data))

		_, err = c.Write([]byte("hello"))
		if err != nil {
			log.Fatal("write:", err)
		}
	}

}

unix_client.go

package main

import (
	"io"
	"log"
	"net"
	"time"
)

func reader(r io.Reader) {
	buf := make([]byte, 512)
	for {
		n, err := r.Read(buf[:])
		if err != nil {
			return
		}
		log.Println(string(buf[0:n]))
	}
}
func main() {
	c, err := net.Dial("unix", "/tmp/unix2.sock")
	if err != nil {
		log.Fatal(err)
	}
	defer c.Close()
	go reader(c)
	for {
		_, err := c.Write([]byte("hi"))
		if err != nil {
			log.Fatal("write error:", err)
			break
		}
		time.Sleep(3 * time.Second)
	}
}

学习文档:

代码地址: https://github.com/lxw1844912514/gobasestudy/

go 零基础编程入门

go语言中文网

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

发布者:全栈程序员-用户IM,转载请注明出处:https://javaforall.cn/111533.html原文链接:https://javaforall.cn

【正版授权,激活自己账号】: Jetbrains全家桶Ide使用,1年售后保障,每天仅需1毛

【官方授权 正版激活】: 官方授权 正版激活 支持Jetbrains家族下所有IDE 使用个人JB账号...

(0)


相关推荐

  • .net 常用开源框架

    .net 常用开源框架Json.NETCodePlexArchiveJson.Net是一个读写Json效率比较高的.Net框架.Json.Net使得在.Net环境下使用Json更加简单。通过LinqToJSON可以快速的读写Json,通过JsonSerializer可以序列化你的.Net对象。让你轻松实现.Net中所有类型(对象,基本数据类型等)和Json的转换。Math.NETMath.NETMath.NET的目标是为提供一款自身包含清晰框架的符号运算和数学运算/科学运算,它是C#开发的开.

  • 下列变量名不符合python命名规范的是_以下选项中不符合 Python 语言变量命名规则的是( )。_学小易找答案…[通俗易懂]

    下列变量名不符合python命名规范的是_以下选项中不符合 Python 语言变量命名规则的是( )。_学小易找答案…[通俗易懂]【判断题】表达式chr(ord(‘B’)+32)的值为’A’。【单选题】执行语句for(i=1;i++<4;);后变量i的值是()【单选题】表达式len(range(1,10))的值为()。【多选题】使用折线图时,需要注意以下哪些细节?【判断题】Python表达式not3>2>6+8的结果为True。【单选题】以下选项中不符合Python语言变量命…

  • android之cannot convert from Fragment1 to Fragment

    在写一个音乐播放器的时候,用到了fragment,结果在需要返回Fragment的方法里面,无法将Fragment1(Fragment的子类)强制转换成Fragment,很是纳闷,我是参照一个开源代码来做的,源码里面很正常,我这里却报错,后来才发现,是对包的导入出现了差错,在Fragment1中导入的是android.app.Fragment而在出错的那个类里面是用android.su

  • windows窗体线程异常_指针在声明和使用时有何不同

    windows窗体线程异常_指针在声明和使用时有何不同在多线程设计中,许多人为了省事,会将对话框类或其它类的指针传给工作线程,而在工作线程中调用该类的成员函数或成员变量等等。但是在Debug版本时,在某些情况下,特别是在工作线程中调用pWnd->UpdateData(FALSE)时,会出现错误。这个错误的原因网上有许多地方讲

  • navicat15免费激活码_最新在线免费激活2022.01.26

    (navicat15免费激活码)最近有小伙伴私信我,问我这边有没有免费的intellijIdea的激活码,然后我将全栈君台教程分享给他了。激活成功之后他一直表示感谢,哈哈~IntelliJ2021最新激活注册码,破解教程可免费永久激活,亲测有效,下面是详细链接哦~https://javaforall.cn/100143.html…

  • 编写测试用例方法—-因果图&判定表

    编写测试用例方法—-因果图&判定表因果图:一、应用场合       在一个界面中,有多个控件,测试的时候要考虑控件的组合关系,不同的控件组合会产生不同的输出结果的组合,为了弄清什么样的输入组合会产生什么样的输出组合,使用因果图法。 二、因果图核心1、因—-原因,输入条件2、果—-结果,输出结果使用图形的方式,分析软件输入和输出的对应关系。 三、图形符号1、基本图形    表示输入和输出的对应关系(1)恒等(-)Ⓐ(输入、因…

发表回复

您的电子邮箱地址不会被公开。

关注全栈程序员社区公众号