go语言基础(二)

copy函数 #

Go语言的内置函数 copy() 可以将一个数组切片复制到另一个数组切片中,如果加入的两个数组切片不一样大,就会按照其中较小的那个数组切片的元素个数进行复制。

//1.不同类型的切片无法复制
//2.如果s1的长度大于s2的长度,将s2中对应位置上的值替换s1中对应位置的值
//3.如果s1的长度小于s2的长度,多余的将不做替换
func main() {
    s1 := []int{1, 2, 3}
    s2 := []int{4, 5}
    s3 := []int{6, 7, 8, 9}
    copy(s1, s2)
    fmt.Println(s1) //[4 5 3]
    copy(s2, s3)
    fmt.Println(s2) //[6 7]
}
			l:=make([]string,len(s))
			copy(h,s)

var, :=, new() , make()的区别 #

说明 #

go语言中,提供了多种变量声明和初始化的方法。这里着重一一说明。并提供一个简单的指南。

指南 #

  • 使用make(),来初始化slicemapchannel
  • 大多数场合,类型明确的场合下,使用短变量声明方式:=
  • 当使用文字方式初始化一个变量,并且需要指明类型时,使用var变量声明方式。
  • 避免使用new(),除非你需要一个指针变量。

变量声明方式 #

go语言可以使用 var 来声明一个变量,并指明变量的数据类型。

	// 初始化整数变量,值为10。
	var v int = 10
	fmt.Println(v)
	// 输出: 10

	// 变量声明: 一个slice变量
	var vSlice []int = []int{1, 2, 3, 4}
	fmt.Println(vSlice, "type: ", reflect.TypeOf(vSlice).Kind())
	// 输出: [1 2 3 4] type: slice

	// 短变量声明: 一个map变量,指向的值为[]
	var vMap map[string]int = map[string]int{
		"a": 1,
		"b": 2,
	}
	fmt.Println(vMap)
	// 输出: map[a:1 b:2]

短变量声明方式 #

short variable declarations 符号: :=

短变量声明时,变量的默认类型是: bool, rune, int, float64, complex128 or string

	// 短变量声明: 一个整数变量。
	sdvInt := 10
	fmt.Println(sdvInt, "type: ", reflect.TypeOf(sdvInt).Kind())
	// 输出: 10 type:  int

	// 短变量声明: 一个slice变量
	sdvSlice := []int{1, 2, 3, 4}
	fmt.Println(sdvSlice, "type: ", reflect.TypeOf(sdvSlice).Kind())
	// 输出: [1 2 3 4] type: slice

	// 短变量声明: 一个map变量,指向的值为[]
	sdvMap := map[string]int{
		"a": 1,
		"b": 2,
	}
	fmt.Println(sdvMap)
	// 输出: map[a:1 b:2]

new(T) #

new(T)的特点:

  • 根据类型T分配内存
  • 设置内存为0
  • 返回内存的指针
	// 初始化一个整数指针变量,指向的值为0
	var i3 *int = new(int)
	fmt.Println(*i3)

	// 初始化一个slice指针变量
	var i4 = new([10]int)[0:5]
	fmt.Println(i4, "type: ", reflect.TypeOf(i4).Kind())
	// 输出: [0 0 0 0 0] type: slice

	// 初始化一个map指针变量,指向的值为[]
	var i5 *map[string]int = new(map[string]int)
	fmt.Println(*i5)
	// 输出: map[]

	// 初始化一个chan指针变量,指向的值为nil
	var i6 *chan int = new(chan int)
	fmt.Println(*i6)
	// 输出: nil

make() #

make只用于初始化 slicemapchannel

	// make只能用于创建slice, map, channel
	// 切片类型(slice)
	makeSlice := make([]int, 5, 10)
	fmt.Println(makeSlice)
	// 输出: [0 0 0 0 0]

	// Map 类型
	var makeMap map[string]int = make(map[string]int)
	fmt.Println(makeMap)
	// 输出: map[]

	// Channel 类型
	var makeChan chan int32 = make(chan int32, 100)
	fmt.Println(makeChan)
	// 输出: 0xc000112000

完整源码 #

package main

import (
	"fmt"
	"reflect"
)

func main() {

	// 初始化整数变量,值为10。
	var v int = 10
	fmt.Println(v)
	// 输出: 10

	// 变量声明: 一个slice变量
	var vSlice []int = []int{1, 2, 3, 4}
	fmt.Println(vSlice, "type: ", reflect.TypeOf(vSlice).Kind())
	// 输出: [1 2 3 4] type: slice

	// 短变量声明: 一个map变量,指向的值为[]
	var vMap map[string]int = map[string]int{
		"a": 1,
		"b": 2,
	}
	fmt.Println(vMap)
	// 输出: map[a:1 b:2]

	// 短变量声明: 一个整数变量。
	sdvInt := 10
	fmt.Println(sdvInt, "type: ", reflect.TypeOf(sdvInt).Kind())
	// 输出: 10 type:  int

	// 短变量声明: 一个slice变量
	sdvSlice := []int{1, 2, 3, 4}
	fmt.Println(sdvSlice, "type: ", reflect.TypeOf(sdvSlice).Kind())
	// 输出: [1 2 3 4] type: slice

	// 短变量声明: 一个map变量,指向的值为[]
	sdvMap := map[string]int{
		"a": 1,
		"b": 2,
	}
	fmt.Println(sdvMap)
	// 输出: map[a:1 b:2]

	// 初始化一个整数指针变量,指向的值为0
	var newInt *int = new(int)
	fmt.Println(*newInt)

	// 初始化一个slice指针变量
	var newSlice = new([10]int)[0:5]
	fmt.Println(newSlice, "type: ", reflect.TypeOf(newSlice).Kind())
	// 输出: [0 0 0 0 0] type: slice

	// 初始化一个map指针变量,指向的值为[]
	var newMap *map[string]int = new(map[string]int)
	fmt.Println(*newMap)
	// 输出: map[]

	// 初始化一个chan指针变量,指向的值为nil
	var newChan *chan int = new(chan int)
	fmt.Println(*newChan)
	// 输出: nil

	// make只能用于创建slice, map, channel
	// 切片类型(slice)
	makeSlice := make([]int, 5, 10)
	fmt.Println(makeSlice)
	// 输出: [0 0 0 0 0]

	// Map 类型
	var makeMap map[string]int = make(map[string]int)
	fmt.Println(makeMap)
	// 输出: map[]

	// Channel 类型
	var makeChan chan int32 = make(chan int32, 100)
	fmt.Println(makeChan)
	// 输出: 0xc000112000
}

print、println、printf的区别 #

Print 和 Println 这两个打印方式类似,只在格式上有区别

  1. Println 打印的每一项之间都会有空行,Print 没有,例如:
fmt.Println("go","python","php","javascript") // go python php javascript
fmt.Print("go","python","php","javascript") // gopythonphpjavascript
  1. Println 会自动换行,Print 不会,例如:
fmt.Println("hello")
fmt.Println("world")
// hello
// world
fmt.Print("hello")
fmt.Print("world")
// helloworld

Println 和 Printf

func main() {
    a:=10
    b:=20
    c:=30
    fmt.Println("a=", a , ",b=" , b , ",c=" , c)
    fmt.Printf("a=%d,b=%d,c=%d" , a , b , c)
}

二维数组去重 #

import (
	"fmt"
	"reflect"
)

func killRepetion(nums [][]int) [][]int  {
	newRes := make([][]int, 0)
    for i := 0; i < len(nums); i++ {
        flag := false
        for j := i + 1; j < len(nums); j++ {
           if reflect.DeepEqual(nums[i], nums[j]){
                flag = true
                break
            }
        }
        if !flag {
            newRes = append(newRes, nums[i])
        }
    }
	return newRes
}

func main()  {
	result := [][]int{{1,2,3},{1,2,3},{1,2,3},{1,2,2}}
	killDoble := killRepetion(result)
	fmt.Println(killDoble)
}

其中reflect函数中的 reflect.DeepEqual(a[], b[])可以比较a数组和b数组是否相同

序列化和反序列化 #

json.Unmarshall解析json字符串 #

var mat MaterialInfo
	err := json.Unmarshal([]byte(args[0]), &mat)
	if err != nil {
		return shim.Error("反序列化信息时发生错误")
	}
	
[]byte(args[0])  字符串切片将arg[0]中的字符串存储在切片中

json.Unmarshall  解析json字符串

marshal与unmarshal序列化与反序列化 #

type Stu struct {
	Name  string `json:"name"`
	Age   int
	HIgh  bool
	sex   string
	Class *Class `json:"class"`  
}
type Class struct {
	Name  string
	Grade int
}
func main() {
	//实例化一个数据结构,用于生成json字符串
	stu := Stu{
		Name: "张三",
		Age:  18,
		HIgh: true,
		sex:  "男",
	}
	//指针变量
	cla := new(Class)
	cla.Name = "1班"
	cla.Grade = 3
	stu.Class=cla
	//Marshal失败时err!=nil
	jsonStu, errs := json.Marshal(stu)
	if errs != nil {
		fmt.Println("生成json字符串错误")
	}
	//jsonStu是[]byte类型,转化成string类型便于查看
	fmt.Println(string(jsonStu))
	data:="{\"name\":\"张三\",\"Age\":18,\"high\":true,\"sex\":\"男\",\"CLASS\":{\"naME\":\"1班\",\"GradE\":3}}"
	str:=[]byte(data)
	//1.Unmarshal的第一个参数是json字符串,第二个参数是接受json解析的数据结构。
	//第二个参数必须是指针,否则无法接收解析的数据,如stu仍为空对象StuRead{}
	//2.可以直接stu:=new(StuRead),此时的stu自身就是指针
	stus:=Stu{}
	err:= json.Unmarshal(str, &stus)
	if err!=nil{
		fmt.Println(err)
	}
	fmt.Println(stu)
	fmt.Println(stu.Age)
	fmt.Println(stu.Class)
}
{"name":"张三","Age":18,"HIgh":true,"class":{"Name":"1班","Grade":3}}
{张三 18 true  0xc0000a6018}
18
&{1 3}



type StuRead struct {
	Name  interface{} `json:"name"`
	Age   interface{}
	HIgh  interface{}
	sex   interface{}
	Class interface{} `json:"class"` //interface{} 类型,空接口
}
func main() {
	data:="{\"name\":\"张三\",\"Age\":18,\"HIgh\":true,\"sex\":\"男\",\"class\":{\"Name\":\"1班\",\"Grade\":3}}"
	str:=[]byte(data)
	stu:=StuRead{}
	err:=json.Unmarshal(str,&stu)
	if err!=nil{
		fmt.Println(err)
	}
	fmt.Println(stu)
	fmt.Println(stu.Age)
}
{张三 18 true <nil> map[Grade:3 Name:1]}
18

Go的次方实现 #

以2的3次方为例:

a := math.Pow(2, 3)

特殊情况 #

当遇到要求 2 的 n 次方的时候,我们可以运用 Go 语言的左移运算符 « ,实现左移运算。

左移的运算规则是左移 N 位,就是乘以 2 的 N 次方。例子如下:

左移 « #

a := 1 << 3 // 2的3次方*1
b := 1 << 6 // 2的6次方*1    64
c := 4 << 2 // 2的2次方*4    16
d := 4 << 3 // 2的3次方*4    32

右移 » #

右移的运算规则是右移 N 位,就是除以 2 的 N 次方。

a := 16 >> 3 // 16除以2的3次方
1

list(列表) #

列表是一种非连续存储的容器,又多个节点组成,节点通过一些变量将彼此串联起来。列表底层常见的数据结构有:单链表、双链表等;go语言中,列表的实现都在 container/list 包中,内部实现原理是双链表。

初始化 #

变量名 := list.New()
var 变量名 = list.List

PS: 列表和 map (字典) 有什么区别?

相比较 map (字典),列表没有具体元素类型的限制,也就是说,你可以添加任意类型到 list 容器中,如字符串整型等。这带来了一些便利,但是也存在一些问题:给一个列表添加了非期望类型的值后,在取值时,将 interface{} 转换为期望类型时会发生宕机。

向列表中添加元素 #

双链表支持往队列前面或后面添加元素,对应的方法分别是:

  • PushFront
  • PushBack

示例代码如下:

l := list.New() 
l.PushFront("cc")  //队列前面添加元素
l.PushBack("dd") //队列后面添加元素

关于 list (列表) 插入元素的方法,如下表所示:

方法 功能
InsertAfter(v interface{}, mark *Element) *Element 在 mark 点后面插入元素
InsertBefore(v interface{}, mark *Element) *Element 在 mark 点前面插入元素
PushFrontList(other *List) 添加 other 列表中的元素到头部
PushBackList(other *List) 添加 other 列表中的元素到尾部

从 list (列表) 中删除元素 #

list (列表) 的插入函数的返回值是一个 *list.Element 结构,通过它来完成对列表元素的删除:

package main
import (
	"container/list"
)
func main()  {
	l := list.New()
	// 头部添加字符串
	l.PushFront("cc")
	// 尾部添加字符串
	l.PushBack("dd")
	// 尾部添加一个整型,并保持元素句柄
	element := l.PushBack(1)
	// 在 1 之后添加字符串 2
	l.InsertAfter("2", element)
	// 在 1 之前添加字符串 0
	l.InsertBefore("0", element)
	// 删除 element 对应的元素
	l.Remove(element)
}

最终队列中保存的元素有:

cc dd 0 2

遍历 list (列表) #

遍历 list (列表) 需要搭配 Front() 函数获取头元素,遍历过程中,只要元素不为空则可继续调用 Next 函数往下遍历:

package main
import (
	"container/list"
	"fmt"
)
func main()  {
	l := list.New()
	// 头部添加字符串
	l.PushFront("cc")
	// 尾部添加字符串
	l.PushBack("dd")
	// 遍历
	for i := l.Front(); i != nil; i = i.Next() {
		fmt.Println(i.Value)
	}
}

注意,在 for 语句遍历中:

  • 其中 i := l.Front() 表示初始赋值,用来获取列表的头部下标;
  • 然后每次会循环会判断 i != nil,若等于空,则会退出循环,否则执行 i.Next()继续循环下一个元素;

代码输出如下:

cc
dd

键盘获取 #

func main() {
	var name string
	var age int
	_, _ = fmt.Scanln(&name, &age)
	fmt.Printf("我叫 %s, 今年 %d 岁!", name, age)
}
var (
	a string
	b int
	c bool
)

func test_fmt() {
	n, err := fmt.Scanf("%s %d %t", &a, &b, &c) // hello 10 true
	// fmt.Scanf会将你通过空格分隔的字符串填充到对应的为, 返回n表示正确填充数, err表示是否出错,遇到换行结束
	fmt.Println(n, err)
	fmt.Println(a, b, c)
}

传入一维数组 #

func main() {
	var n, tmp int
	fmt.Scanln(&n)
	marry := make([]int, 0, n)
	for i := 0; i < n; i++ {
		fmt.Scanln(&tmp)
		marry = append(marry, tmp)
	}
	fmt.Println(marry)
}

传入二维数组 #

func main() {
	var n, m, tmp int
	fmt.Scanf("%v %v", &n, &m)
	way := make([][]int, 0, n)
	for i := 0; i < n; i++ {
		args := make([]int, 0, m)
		for j := 0; j < m; j++ {
			fmt.Scanf("%v", &tmp)
			args = append(args, tmp)
		}
		way = append(way, args)
	}
	fmt.Println("way", way)
}

实现error接口 #

在errors包下,有封装好了的方法直接在里面写上错误信息就可以

err=errors.New("我写错了")
//包内部 就是实现了error方法
package errors
func New(text string)error{
  return &errorString{text}
}
type errorString struct{
  s string
}
func (e *errorString)Error()string{
  return e.s
}