go基础学习

环境变量配置:

  • 在系统变量的Path中添加go的安装bin目录:D:\allAppInstall\go\bin
  • 在系统中新建变量GOROOT项目,值为go的安装目录:D:\allAppInstall\go

配置go环境:

在PowerShell中

$env:GO111MODULE="on"
$env:GOPROXY="http://goproxy.cn"

go语言命名规范

go是一门区分大小写的语言

任何需要对外暴露的名字必须以大写字母开头,不需要对外暴露的则以小写字母开头

当命名(包括常量、变量、类型、函数名、结构字段等等)以一个大写字母开头,如: GetUserName,那么使用这种形式的标识符的对象就可以被外部包的代码所使用(客户端程序需要先导入这个包),这被称为导出(像面向对象语言中的 public);命名如果以小写字母开头,则对包外是不可见的,但是他们在整个包的内部是可见并且可用的(像面向对象语言中的private )

包名称

保持package的名字和目录保持一致,尽量采取有意义的包名,简短,有意义,尽量和标准库不要冲突。包名应该为小写单词,不要使用下划线或者混合大小写

package dao
package service

文件名称

应尽量采取有意义的文件名,简短有意义,应尽量使用小写单词,使用下划线分隔各个单词

customer_dao.go

结构体命名

采用驼峰命名法,首字母根据访问控制大写或者小写

struct申明和初始化采用多行

type CustomerOrder struct{
    Name string 
    Address string
}
order := CustomerOrder{"zhangsan","北京海淀"}

接口命名

单个函数的结构命名以"er"作为后缀,例如 Reader,Writer

type Reader interface{
    Read(p []byte)(n int ,err error)
}

变量命名

遵循驼峰命名法,首字母根据访问控制原则大写或则小写,但遇到特有名词时,需要遵循以下规则:

如果变量为私有,而且特有名词为首个单词,则使用小写

var appService int

如果变量类型为bool,则名称应以Has,Is,Can或者Allow开头

var isExist bool
var hasConflict bool
var canMansge bool
var allowGitHook bool 

常量命名

常量使用全部大写字母组成,并且使用下划线分词

const APP_URL = "https://www.bilibili.com"

错误处理

错误处理的原则就是不能丢弃任何有返回err的调用,不要使用_丢弃,必须全部处理。接收到错误,要么返回err,或者使用log记录下来尽早return:一旦有错误发生,马上返回,尽量不要使用panic,除非你知道在做什么,错误描述如果是英文必须为小写,不需要标点结尾,采用独立的错误流进行处理

//错误写法
if err != nil{
    //错误处理
}else{
    //正常代码
}

//正确写法
if err != nil{
    //错误处理
    return //或者继续
}
//正常代码

单元测试

单元测试文件命名规范为:example_test.go

测试用例的函数名称必须以Test开头,例如:TestExample

每个重要的函数都要首先写测试用例,测试用例和正规代码一起提交方便进行回归测试

golang变量

变量是计算机语言中能储存计算结果或能表示值的抽象概念。不同的变量保存的数据类型可能会不一样。

声明变量

go语言中的变量需要声明后使用,同一作用域内不支持重复声明,并且go语言的变量声明后必须使用。

声明变量语法
var identifier type

var:声明变量关键字

identifier:变量名称

type:变量类型

例如:

var name string
批量声明
var(
	name string
	age int 
	flag bool
)

变量的初始化

go语言在声明变量的时候,会自动对变量对应的内存区域进行初始化操作。每个变量会被初始化成其类型的默认值,例如:整型和浮点型变量的默认值为0。字符串变量的默认值为空字符串""。布尔型变量默认为false 。切片、函数、指针变量的默认为nil

变量初始化语法
var 变量名 类型 = 表达式

例如:

var name string = "zhangsan"
var age int =20
类型推导

我们在声明变量时,可以根据初始化值进行类型推导,从而省略类型。

var name = "zhangsan"
var age =20

根据打印函数打印出数据类型

fmt.Printf("%T\n", name)
初始化多个变量
var name,site,age="zhangsan","www.xxx.com",20

短变量声明

函数内部,可以使用:=运算符对变量进行声明和初始化

package

func main(){
    name := "zhangsan"
    site := "www.xxx.com"
    age := 20
}

匿名变量

如果我们接收到多个变量,有一些变量使用不到,可以使用下划线_表示变量名称,这种变量叫做匿名变量。例如:

package main

import "fmt"

func t1() (int, int) {
	return 100, 200
}

// 匿名变量 _
func main() {
	//a, b := t1()
	//fmt.Print(a, b)
	a, _ := t1()
	fmt.Print(a)
}

golang常量

常量,就是在程序编译阶段就确定下来的值,而程序在运行时则无法改变该值。在Go程序中,常量可以是数值类型(包括整型、浮点型和复数类型)、布尔类型、字符串类型等。

定义常量的语法

定义一个常量使用const关键字,语法格式:

const constantName [type] =value

const:定义常量关键字

constantName:常量名称

type:常量类型

value:常量的值

实例

package main

import "fmt"

func main() {
	const URL string = "www"
	fmt.Println(URL)
}
  • const同时声明多个常量时,如果省略了值则表示一和上面一行的值相同
const (
    A = 1
    B
)
fmt.Println(A, B)//A,B都是1

iota

iota比较特殊,可以被认为是一个可被编译器修改的常量,它默认开始值是0,每调用一次加1。遇到const关键字时被重置为0。

  • 同时声明多个常量,并且赋值都为iota,则常量的数据依次增加1
package main

import "fmt"

func main() {

	const (
		a = iota
		b = iota
		c = iota
	)
	fmt.Println(a, b, c) //a,b,c 分别是1,2,3
}

可以使用下划线_跳过赋值

package main

import "fmt"

func main() {

	const (
		a = iota
		_
		c = iota
	)
	fmt.Println(a, c) //a,c 分别是1,3
}

golang数据类型

在go编程语言中,数据类型用于声明函数和变量。

数据类型的出现是为了把数据分成所需内存大小不同的数据,编程的时候需要用大数据的时候才需要申请大内存,就可以充分利用内存。

go语言按照类别有以下几种数据类型

类型描述
布尔型值只能是true或者false
数字类型整型int 和浮点型 (float32和float64)
字符串类型go语言的字符串的字节使用UTF-8编码标识Unicode文本
派生类型(a) 指针类型(Pointer)
(b) 数组类型
© 结构化类型(struct)
(d) Channel 类型
(e) 函数类型
(f) 切片类型
(g) 接口类型(interface)
(h) Map 类型

布尔类型

package main

import "fmt"

func main() {
	var flag1 bool = true
	var flag2 = true
	flag3 := true

	fmt.Printf("%v\n", flag1)
	fmt.Printf("%v\n", flag2)
	fmt.Printf("%v\n", flag3)
}

数字类型

go语言支持整型和浮点型数字,并且原生支持复数,其中位的运算采用补码

go也有基于架构的类型,例如:intuint,和uintptr

​ 这些类型的长度都是根据运行程序所在的操作系统类型决定的:

  • intuint在32位操作系统上,他们均使用32位(4字节),在64位操作系统上,他们均使用64位(8字节)
  • uintptr的长度被设定为足够存放一个指针即可

go语言中没有float类型。(go语言中只有float32和float64)没有double类型。

整型
序号类型和描述
1uint8 无符号 8 位整型 (0 到 255)
2uint16 无符号 16 位整型 (0 到 65535)
3uint32 无符号 32 位整型 (0 到 4294967295)
4uint64 无符号 64 位整型 (0 到 18446744073709551615)
5int8 有符号 8 位整型 (-128 到 127)
6int16 有符号 16 位整型 (-32768 到 32767)
7int32 有符号 32 位整型 (-2147483648 到 2147483647)
8int64 有符号 64 位整型 (-9223372036854775808 到 9223372036854775807)
以二进制,八进制,十六进制格式定义数字
package main

import "fmt"

func main() {
	var flag1 bool = true
	var flag2 = true
	flag3 := true

	fmt.Printf("%v\n", flag1)
	fmt.Printf("%v\n", flag2)
	fmt.Printf("%v\n", flag3)

	//十进制
	var a int = 8
	fmt.Printf("%d\n", a) //8
	fmt.Printf("%b\n", a) //1000

	//八进制 以0开头
	var b int = 010
	fmt.Printf("%d\n", b) //8
	fmt.Printf("%o\n", b) //10

	//十六进制 以0x开头
	var c int = 0xa
	fmt.Printf("%d\n", c) //10
	fmt.Printf("%x\n", c) //a
	fmt.Printf("%X\n", c) //A
}
浮点型

go语言支持两种浮点型数: float32和float64。这两种浮点型数据格式遵循IEEE 754标准:

float32 的浮点数的最大范围约为3.4e38,可以使用常量定义: math.MaxFloat32。

float64 的浮点数的最大范围约为1.8e308,可以使用一个常量定义: math.MaxFloat64。

打印浮点数时,可以使用fmt包配合动词%f:

package main

import (
	"fmt"
	"math"
)

func main() {
	fmt.Printf("%f\n", math.Pi)			//3.141593
	fmt.Printf("%.2f\n", math.Pi)		//3.14
}

关于复数

package main

import "fmt"

func main() {
	var c complex64 = 1 + 3i
	fmt.Println(c)
	fmt.Printf("%T", c)
}
序号类型和描述
1float32 IEEE-754 32位浮点型数
2float64 IEEE-754 64位浮点型数
3complex64 32 位实数和虚数
4complex128 64 位实数和虚数
其他数据类型
序号类型和描述
1byte 类似 uint8
2rune 类似 int32
3uint 32 或 64 位
4int 与 uint 一样大小
5uintptr 无符号整型,用于存放一个指针

golang字符串

一个go语言字符串是一个任意字节的常量序列 []byte

字符串字面量

在Go语言中,字符串字面量使用双引号""或者反引号`来创建。双引号用来创建可解析的字符串,支持转义,但不能用来引用多行;反引号用来创建原生的字符串字面量,可能由多行组成,但不支持转义,并且可以包含除了反引号外其他所有字符。双引号创建可解析的字符串应用最广泛,反引号用来创建原生的字符串则多用于书写多行消息,HTML以及正则表达式。

package main

import "fmt"

func main() {
	var str1 string = "hello"
	var str2 = "zhangsan"
	str3 := "lisi"
	fmt.Printf("str1:%s\n", str1)
	fmt.Printf("str2:%s\n", str2)
	fmt.Printf("str3:%s\n", str3)

	var str string = `
		hahaha
		hahaha`
	fmt.Printf("str:%s\n", str)
}
字符串拼接

使用+或者fmt.Sprintf等等

package main

import (
	"bytes"
	"fmt"
	"strings"
)

func main() {
	str1 := "hello"
	str2 := "zhangsan"
	str3 := str1 + str2
	fmt.Printf("%s\n", str3) //hellozhangsan

	str4 := fmt.Sprintf("%s:%s", str2, str1)
	fmt.Printf("%s\n", str4)	//zhangsan:hello

	s := strings.Join([]string{str2, str1}, ":")
	fmt.Printf("%s\n", s)		//zhangsan:hello

	var buffer bytes.Buffer
	buffer.WriteString(str2)
	buffer.WriteString(":")
	buffer.WriteString(str1)
	fmt.Printf("%v\n", buffer.String())//zhangsan:hello
}
字符串转义字符
转义符含义
\r回车符
\n换行符
\t制表符
\'单引号
\"双引号
\\反斜杠
字符串切片操作
package main

func main() {
	str := "hello world"
	println(str[0])   //获取字符串索引位置字符的原始字节
	println(str[1:5]) //截取字符串索引位置1到5的字符串
	println(str[1:])  //截取字符串索引位置1到最后的字符串
	println(str[:5])  //截取字符串起始位置到索引位置的字符串
}
字符串处理函数
Contains:包含
func Contains(s, substr string) bool      
//功能:字符串s中是否包含substr,返回bool值

例子

str := "hello world"
fmt.Printf("%t\n", strings.Contains(str, "ll"))//true
Join:拼接
func Join(a []string, sep string) string
//功能:字符串链接,把slice a通过sep链接起来

例子

s := []string{"abc", "def", "ghi"}
fmt.Printf("%s\n", strings.Join(s, "_"))//abc_def_ghi
Index:索引
func Index(s, sep string) int
//功能:在字符串s中查找sep所在的位置,返回位置值,找不到返回-1

例子

fmt.Printf("%d\n", strings.Index("helloworld", "hello"))//0
fmt.Printf("%d\n", strings.Index("helloworld", "nihao"))//-1
Repeat:重复
func Repeat(s string, count int) string
//功能:重复s字符串count次,最后返回重复的字符串

例子

fmt.Printf("%s\n", strings.Repeat("go", 3))//gogogo
Replace:替换
func Replace(s, old, new string, n int) string
//功能:在s字符串中,把old字符串替换为new字符串,n表示替换的次数,小于0表示全部替换

例子

fmt.Printf("%s\n", strings.Replace("hello", "l", "h", 1))//hehlo
Split:分割
func Split(s, sep string) []string
//功能:把s字符串按照sep分割,返回slice

例子

fmt.Printf("%s\n", strings.Split("hello_world", "_"))//[hello world]
Trim:去除开头结尾
func Trim(s string, cutset string) string
//功能:在s字符串的头部和尾部去除cutset指定的字符串

例子

fmt.Printf("#%s#\n", strings.Trim("   hello go  ", " "))	//#hello go#
Fields:删除前后空格
func Fields(s string) []string
//功能:去除s字符串的空格符,并且按照空格分割返回slice

例子

fmt.Printf("%s\n", strings.Fields(" hello world "))//[hello world]

golang格式化输出的占位符

fmt.Printf("%s\n","hello world")

普通占位符

package main

import "fmt"

type WebSite struct {
	Name string
}

func main() {
	site := WebSite{Name: "bilibili"}
	fmt.Printf("%v\n", site)	//{bilibili}
	fmt.Printf("%#v\n", site)	//main.WebSite{Name:"bilibili"}
	fmt.Printf("%T\n", site)	//main.WebSite
}

布尔占位符

package main

import "fmt"

func main() {
	var flag bool = true
	fmt.Printf("%t\n", flag)	//true
	fmt.Printf("%v\n", flag)	//true
}

整数占位符

package main

import "fmt"

func main() {
	fmt.Printf("b:%b\n", 10)     //二进制数
	fmt.Printf("d:%d\n", 10)     //十进制
	fmt.Printf("o:%o\n", 10)     //八进制
	fmt.Printf("c:%c\n", 0x5F7F) //相应Unicode码对应的字符
	fmt.Printf("q:%q\n", 0x5F7F) //单引号围绕的字符值
	fmt.Printf("x:%x\n", 10)     //十六进制表示,字母形式的小写
	fmt.Printf("X:%X\n", 10)     //十六进制表示,字母形式的大写
	fmt.Printf("U:%U\n", 10)     //Unicode格式
}
/**
输出结果:
b:1010
d:10
o:12
c:彿
q:'彿'
x:a
X:A
U:U+000A
*/

浮点数和复数占位符

package main

import "fmt"

func main() {
	fmt.Printf("b:%b\n", 3.141)          //无小数部分的,指数为二的幂的科学计数法
	fmt.Printf("e:%e\n", 3.1415)         //科学计数法
	fmt.Printf("E:%E\n", 3.1415)         //科学计数法
	fmt.Printf("f:%f\n", 3.1415)         //有小数点,无指数
	fmt.Printf("f:%.2f\n", 3.1415)       //指定小数位数
	fmt.Printf("g:%g\n", 3.14150+7.00i)  //没有末尾0
	fmt.Printf("G:%G\n", 3.14150+6.770i) //没有末尾0
}
/**
输出结果:
b:7072903214785364p-51
e:3.141500e+00
E:3.141500E+00
f:3.141500
f:3.14
g:(3.1415+7i)
G:(3.1415+6.77i)
*/

字符串与字节切片占位符

package main

import "fmt"

func main() {
	fmt.Printf("s:%s\n", "hello go") //字符串
	fmt.Printf("q:%q\n", "hello go") //双引号的字符串
	fmt.Printf("x:%x\n", "hello")    //十六进制,小写字母,每字节两个字符
	fmt.Printf("X:%X\n", "hello")    //十六进制,大写字母,每字节两个字符
}
/**
输出结果:
s:hello go
q:"hello go"
x:68656c6c6f
X:68656C6C6F
*/

指针的占位符

package main

import "fmt"

func main() {
	x := 100
	fmt.Printf("%p\n", &x)
}
/*
输出结果:
0xc00001e098
*/

golang语言中的流程控制

顺序,选择,循环

if

package main

func main() {
	a := 1
	b := 2
	if a > b {
		println(a)
	} else {
		println(b)
	}
}

初始变量可以声明在布尔表达式里面,但是作用域仅限于if语句

package main

func main() {
	if age := 20; age > 18 {
		println(true)
	} else {
		println(false)
	}
}

请注意

  • 不需要使用括号讲条件包含起来

  • {}必须存在,即使只有一行语句

  • 左括号必须在if或else的同一行

  • if之后,条件语句之前,可以添加变量初始化语句,使用分号;进行分隔

  • 不能使用0或者非0表示布尔值

switch

package main

import "fmt"

func main() {
	grade := 'A'
	switch grade {
	case 'A':
		fmt.Println("优秀")
	case 'B':
		fmt.Println("良好")
	default:
		fmt.Println("其他")
	}
}
  • case匹配多个值
package main

import "fmt"

func main() {
	day := 1
	switch day {
	case 1, 2, 3, 4, 5:
		fmt.Println("工作日")
	case 6, 7:
		fmt.Println("休息日")
	default:
		fmt.Println("非法值")
	}
}
  • switch后面不接变量,在case后面使用布尔表达式
package main

import "fmt"

func main() {
   score := 77
   switch {
   case score >= 60 && score < 70:
      fmt.Println("及格")
   case score >= 70 && score < 80:
      fmt.Println("中等")
   case score >= 80:
      fmt.Println("优秀")
   default:
      fmt.Println("不及格")
   }
}
  • fallthrough,执行完当前case分支之后执行下一个case分支
package main

func main() {
   a := 1
   switch a {
   case 1:
      println(1)
      fallthrough
   case 2:
      println(2)
   case 3:
      println(3)
   }
}

请注意:

  • 支持多条件匹配
  • 不同的case之间不使用break分隔,默认只会执行一个case
  • 如果想要执行多个case分支,需要使用fallthrough关键字,也可使用break终止
  • 分支还可以使用布尔表达式,此时switch后无需添变量

for

循环结构只有for关键字,没有whiledo while

package main

func main() {
	for i := 0; i < 10; i++ {
		println(i)
	}
}

无限循环:

package main

func main() {
   var i int = 0
   for {
      i++
      if i > 100 {
         break
      }
      println(i)
   }
}

for range

遍历数组:

package main

import "fmt"

func main() {
	var a = [...]int{1, 2, 3, 4, 5}
	for i, v := range a {
		fmt.Printf("%d:%d ", i, v)
	}
}
/*
0:1 1:2 2:3 3:4 4:5
*/

遍历切片(动态数组)

package main

import "fmt"

func main() {
	var s = []int{1, 2, 3}
	for _, v := range s {
		fmt.Printf("v: %v\n", v)
	}
}
/**
v: 1
v: 2
v: 3
*/

遍历map

package main

import "fmt"

func main() {
	m := make(map[string]string, 0)
	m["name"] = "zhangsan"
	m["age"] = "20"
	m["email"] = "1234"

	for key, value := range m {
		fmt.Printf("%v:%v\n", key, value)
	}
}
/**
name:zhangsan
age:20
email:1234
*/

遍历字符串:

package main

import "fmt"

func main() {
	s := "hello world"
	for _, v := range s {
		fmt.Printf("v:%c ", v)
	}
}
/**
v:h v:e v:l v:l v:o v:  v:w v:o v:r v:l v:d
*/

goto

goto语句通过标签进行代码间的无条件跳转。goto语句可以在快速跳出循环、避免重复退出上有一定的帮助。Go语言中使用goto语句能简化一些代码的实现过程。例如双层嵌套的for循环要退出时:

跳到指定的标签

package main

func main() {
	a := 1
	if a >= 2 {
		println(a)
	} else {
		goto END
	}
END:
	println("拜拜")
}

跳出双层循环

package main

import "fmt"

func main() {
	for i := 0; i < 10; i++ {
		for j := 0; j < 10; j++ {
			if i == 2 && j == 2 {
				goto END
			}
			fmt.Printf("%v,%v\n", i, j)
		}
	}
END:
	println("拜拜")
}

golang数组

数组是相同数据类型的一组数据的集合,数组一旦定义长度不能修改,数组可以通过下标(或者叫索引)来访问元素。

数组的定义

var variable_name [size] variable_type

varible_name:数组名称

size:数组长度,需要时常量

variable_type:数组保存元素的类型

package main

import "fmt"

func main() {
	var a [2]int
	var s [3]string
	fmt.Printf("%T\n", a)
	fmt.Printf("%T\n", s)
	fmt.Printf("%v\n", a)
	fmt.Printf("%v\n", s)
}
/**
[2]int
[3]string
[0 0]
[  ]
*/

数组的初始化

初始化就是给数组的元素赋值,没有初始化的数组,默认元素值为零,布尔类型位false,字符串是空值

func f2() {
	a1 := [3]int{1, 2, 3}
	fmt.Printf("a1: %v\n", a1)	//a1: [1 2 3]
}
//省略数组的长度...
func f3() {
	s1 := [...]string{"hello", "nihao", "zhangsan"}
	fmt.Printf("s1: %v\n", s1)	//s1: [hello nihao zhangsan]
}
//指定位置初始化
func f4() {
	a2 := [...]int{0: 1, 2: 2, 4: 4}
	fmt.Printf("a2: %v\n", a2)	// a2: [1 0 2 0 4]
}

数组的访问

可以通过下标的方式,来访问数组元素,数组的最大下标为length-1,大于这个下标回发生数组越界

func f4() {
	a2 := [...]int{0, 1, 2, 3}
	fmt.Printf("a2: %v\n", a2)
	fmt.Printf("数组a2的长度:%d\n", len(a2))
}
/**
a2: [0 1 2 3]
数组a2的长度:4
*/

golang切片

前面我们学习了数组,数组是固定长度,可以容纳相同数据类型的元素的集合。当长度固定时,使用还是带来一些限制,比如:我们申请的长度太大浪费内存,太小又不够用。

鉴于上述原因,我们有了go语言的切片,可以把切片理解为,可变长度的数组,其实它底层就是使用数组实现的,增加了自动扩容功能。**切片(Slice)**是一个拥有相同类型元素的可变长度的序列。

切片的语法

声明一个切片和声明一个数组类似,只要不添加长度就可以了

var identifier []type

切片类型是引用数据类型,可以使用make函数来创建切片:

var slice []type = make([]type ,len)
//简写
slice := make([]type ,len)

例如

func f101() {
	var s1 []int
	var s2 []string
	fmt.Printf("s1:%v\n", s1) //s1:[]
	fmt.Printf("s2:%v\n", s2) //s2:[]
}

也可指定容量,其中capacity为可选参数

make([]T,length,capacity)

length是数组的长度并且也是切片的初始长度

例如:

func f102() {
	s1 := make([]int, 2)
	fmt.Printf("s1的值:%v\n", s1)  //s1的值:[0 0]
	fmt.Printf("s2的类型:%T\n", s1) //s2的类型:[]int
}

切片的初始化

直接初始化:
s := []int{1, 2, 3}
使用数组初始化:
arr := [...]int{1, 2, 3}
s1 := arr[:]
fmt.Printf("%v\n", s1)	//[1 2 3]
使用数组的部分元素初始化(切片表达式)

切片的底层就是一个数组,所以我们可以基于数组通过切片表达式得到切片。切片表达式中的low和high表示一个索引范围(左包含,右不包含),得到切片的长度=high-low,容量等于得到的切片的底层数组的容量

package main

import "fmt"

func main() {
	arr := [...]int{1, 2, 3, 4, 5, 6, 7}
	s1 := arr[:]
	fmt.Printf("s1:%v\n", s1)
	s2 := arr[2:5]
	fmt.Printf("s2:%v\n", s2)
	s3 := arr[2:]
	fmt.Printf("s3:%v\n", s3)
	s4 := arr[:5]
	fmt.Printf("s4:%v\n", s4)
}
/**
s1:[1 2 3 4 5 6 7]
s2:[3 4 5]
s3:[3 4 5 6 7]
s4:[1 2 3 4 5]
*/

切片的遍历

遍历方法和数组的遍历非常类似,可以使用for循环遍历,或者使用for range循环

for循环遍历
package main

import "fmt"

func main() {
   s1 := []int{1, 2, 3, 4, 5, 6, 7}
   fmt.Printf("s1:%v\n", s1)

   for i := 0; i < len(s1); i++ {
      fmt.Printf("s1[%d]:%v ", i, s1[i])
   }
}
/*
s1:[1 2 3 4 5 6 7]
s1[0]:1 s1[1]:2 s1[2]:3 s1[3]:4 s1[4]:5 s1[5]:6 s1[6]:7
*/
for range遍历
package main

import "fmt"

func main() {
   s1 := []int{1, 2, 3, 4, 5, 6, 7}
   fmt.Printf("s1:%v\n", s1)
   for i, v := range s1 {
      fmt.Printf("s1[%v]:%v, ", i, v)
   }
}

/*
s1:[1 2 3 4 5 6 7]
s1[0]:1, s1[1]:2, s1[2]:3, s1[3]:4, s1[4]:5, s1[5]:6, s1[6]:7,
*/

切片元素的添加与删除

切片是一个动态数组,可以使用append()函数添加元素,go语言中并没有删除切片元素的专用方法,我们可以使用切片本身的特性来删除元素。由于,切片是引用类型,通过赋值的方式,会修改原有内容,go提供了copy()函数来拷贝切片

添加
package main

import "fmt"

func main() {
	s1 := []int{2}
	fmt.Printf("s1:%v\n", s1)	//s1:[2]

	s1 = append(s1, 1)
	fmt.Printf("s1:%v\n", s1)	//s1:[2 1]
	s1 = append(s1, 0, -1)
	fmt.Printf("s1:%v\n", s1)	//s1:[2 1 0 -1]
	s2 := []int{100, 101}
	s1 = append(s1, s2...)
    fmt.Printf("s1:%v\n", s1)	//s1:[2 1 0 -1 100 101]
}
删除
package main

import "fmt"

func main() {
	s1 := []int{10, 20, 30, 40, 50}
	fmt.Printf("s1:%v\n", s1) //s1:[10 20 30 40 50]
	s1 = append(s1[:2], s1[3:]...)
	fmt.Printf("s1:%v\n", s1) //s1:[10 20 40 50]
}
修改
package main

import "fmt"

func main() {
   s1 := []int{10, 20, 30, 40, 50}
   fmt.Printf("s1:%v\n", s1) //s1:[10 20 30 40 50]

   s1[1] = 2
   fmt.Printf("s1:%v\n", s1) //s1:[10 2 40 50]
}

golang Map

map是一种key :value键值对的数据结构容器。map内部实现是哈希表( hash )。

map最重要的一点是通过 key来快速检索数据,key类似于索引,指向数据的值。

map是引用类型的。

map的定义

语法格式:

//使用map关键字定义map集合
var map_variable map[key_data_type]value_data_type
//使用make函数
map_variable = make(map[key_data_type]value_data_type)

map_variable:map变量名称

key_data_type:key的数据类型

value_data_type:值的数据类型

package main

import "fmt"

func main() {
   m0 := map[string]string{
      "name":  "yewenjie",
      "age":   "30",
      "tenet": "Don't answer",
   }
   fmt.Printf("%v\n", m0)

   m1 := make(map[string]string)
   m1["name"] = "zhangsan"
   m1["age"] = "20"
   m1["tenet"] = "legal"
   fmt.Printf("%v\n", m1)
}
/**
map[age:30 name:yewenjie tenet:Don't answer]
map[age:20 name:zhangsan tenet:legal]
*/

map遍历

遍历key

package main

import "fmt"

func main() {
   m1 := make(map[string]string)
   m1["name"] = "zhangsan"
   m1["age"] = "20"
   m1["tenet"] = "legal"

   for key := range m1 {
      fmt.Printf("%v\n", key)
   }
}
/*
name
age
tenet
*/

遍历key和value

package main

import "fmt"

func main() {
   m1 := make(map[string]string)
   m1["name"] = "zhangsan"
   m1["age"] = "20"
   m1["tenet"] = "legal"
   //fmt.Printf("%v\n", m1)

   for key, value := range m1 {
      fmt.Printf("%v:%v\n", key, value)
   }
}
/*
name:zhangsan
age:20
tenet:legal
*/

golang函数

函数的go语言中的一级公民,我们把所有的功能单元都定义在函数中,可以重复使用。

函数包含函数的名称、参数列表和返回值类型,这些构成了函数的签名(signature) 。

函数特征

  1. go语言中有3种函数:普通函数、匿名函数(没有名称的函数)、方法(定义在struct上的函数)
  2. go语言中不允许函数重载(overload),也就是说不允许函数同名
  3. go语言中的函数不能嵌套函数,但可以嵌套匿名函数。
  4. 函数是一个值,可以将函数赋值给变量,使得这个变量也成为函数。
  5. 函数可以作为参数传递给另—个函数。
  6. 函数的返回值可以是一个函数。
  7. 函数调用的时候,如果有参数传递给函数,则先拷贝参数的副本,再将副本传递给函数。
  8. 函数参数可以没有名称。

函数的定义

函数在使用之前必须先定义,可以调用函数来完成某个任务。函数可以重复调用,从而达到代码重用。

定义语法
func function_name([parameter list]) [return_types]{
    //函数体
}

func:定义函数的关键字

function_name:函数名称,函数名和参数列表一起构成了函数签名

[parameter list]:参数列表,参数就像一个占位符,当函数被调用时,你可以将值传递给参数,这个值被称为实际参数。参数列表指定的是参数类型,顺序,及参数个数。参数是可选的,函数可以没有参数

[return_types]:返回类型,函数可以没有返回值

例如:

package main

import "fmt"

func sum(a int, b int) (num int) {
	return a + b
}
func sub(a int, b int) int {
	return a - b
}

func main() {
	fmt.Printf("%d\n", sum(1, 1))
	fmt.Printf("%d\n", sub(1, 1))
}

函数的返回值

无返回值

func t01() {
   fmt.Printf("hello\n")
}

有返回值

func t01() (flag bool) {
   return true
}

多个返回值

package main

import "fmt"

func t01() (a int, b int) {
	return 1, 2	//使用return覆盖命名返回值,返回值名称没有被使用
}

func main() {
	a, b := t01()
	fmt.Printf("%d,%d", a, b)
}

函数的参数

  • 可以有零个或多个参数,参数需要指定数据类型

  • go语言是通过传值的方式传参的,这意味着传递给函数的参数是拷贝后的副本,所以函数内部访问,修改的也是这个副本

    • map 、 slice、 interface 、 channel这些数据类型本身就是指针类型的,所以就算是拷贝传值也是拷贝的指针,拷贝后的参数仍然指向底层数据结构,所以修改它们可能会影响外部数据结构的值。
  • go语言可以使用变长参数,有时候并不能确定参数的个数,可以使用边长参数,可以在函数定义语句的参数部分使用ARGS...TYPE的方式。这时会将...代表的参数全部保存到一个名为ARGSslice中,注意这些参数的数据类型都是TYPE

可变参数
package main

import "fmt"

func t01(args ...int) {
	for _, v := range args {
		fmt.Printf("%d ", v)
	}
	fmt.Printf("\n")
}
func t02(name string, age int, args ...int) {
	fmt.Printf("%s ", name)
	fmt.Printf("%d\n", age)
	for _, v := range args {
		fmt.Printf("%d ", v)
	}
}

func main() {
	t01(1, 2, 3, 4, 5)
	t02("zhangsan", 20, 3, 4, 5)
}
/*
1 2 3 4 5
zhangsan 20
3 4 5
*/

函数类型与函数变量

定义一个函数类型的变量

可以使用type关键字来定义一个函数类型,语法格式如下:

type fun func(int int)int 
//定义一个fun函数类型,这种函数接收两个int类型的参数,并且返回一个int类型的返回值

例如:

package main

import "fmt"

func sum(a int, b int) int {
   return a + b
}
func max(a int, b int) int {
   if a > b {
      return a
   } else {
      return b
   }
}

func main() {
   type f1 func(int, int) int //定义一个函数类型的变量
   var fufu f1
   fufu = sum
   num := fufu(1, 2)
   fmt.Printf("%v\n", num)

   fufu = max
   num2 := fufu(1, 2)
   fmt.Printf("%v\n", num2)
}

高阶函数

go语言的函数,可以作为函数的参数,传递给另外一个函数,作为另外一个函数的返回值返回

函数作为参数
package main

import "fmt"

func sayHello(name string) {
   fmt.Printf("Hello %s!", name)
}

func f1(name string, f func(string)) {
   f(name)
}

func main() {
   f1("tom", sayHello)
}
函数作为返回值
package main

import "fmt"

func add(a int, b int) int {
   return a + b
}
func sub(a int, b int) int {
   return a - b
}
func cal(operator string) func(int, int) int {
   switch operator {
   case "+":
      return add
   case "-":
      return sub
   default:
      return nil
   }
}
func main() {
   ff := cal("+")
   r := ff(1, 2)
   fmt.Printf("%v\n", r)

   ff2 := cal("-")
   r2 := ff2(1, 2)
   fmt.Printf("%v\n", r2)
}

匿名函数

go语言函数不能嵌套,但是在函数内部可以定义匿名函数,实现一下简单功能调用。

所谓匿名函数就是,没有名称的函数。

语法格式:

func (参数列表)(返回值)

匿名函数的举例:

package main

import "fmt"

func main() {
   //匿名函数
   max := func(a int, b int) int {
      if a > b {
         return a
      } else {
         return b
      }
   }

   a := max(1, 2)
   fmt.Printf("max:%v\n", a)
}

匿名函数的自调用

package main

import "fmt"

func main() {
	num := func(a int, b int) int {
		return a + b
	}(1, 2)
	fmt.Printf("%v\n", num)		//3
}

闭包

闭包可以理解成定义在一个函数内部的函数。在本质上,闭包是将函数内部和函数外部连接起来的桥梁,或者说是函数和其引用环境的组合体。

闭包指的是一个函数和与其相关的引用环境组合而成的实体。简单来说,闭包=函数+作用域。

package main

func add() func(int) int {
   var x int
   return func(y int) int {
      x += y
      return x
   }
}
func main() {
   var f = add()
   println(f(10))
   println(f(20))
}

递归

函数内部调用函数自身的函数称为递归函数。使用递归函数最重要的三点:

  1. 递归就是自己调用自己。
  2. 必须先定义函数的退出条件,没有退出条件,递归将成为死循环。
  3. go语言递归函数很可能会产生一大堆的goroutine,也很可能会出现栈空间内存溢出问题。
package main

import "fmt"

func f11(a int) int {
   if a == 1 {
      return 1
   } else {
      return a * f11(a-1)
   }

}

func main() {
   fmt.Printf("%d\n", f11(6))
}

golang defer

go语言中的defer语句会将其后面跟随的语句进行延迟处理,在defer归属的函数即将返回时,将延迟处理的语句按defer定义的逆序进行执行

例子

package main

import "fmt"

func main() {
   fmt.Printf("start\n")
   defer fmt.Printf("%d\n", 1)
   defer fmt.Printf("%d\n", 2)
   defer fmt.Printf("%d\n", 3)
   fmt.Printf("end\n")
}
/**
start
end
3
2
1
*/

defer特性

  1. 关键字defer用于注册延迟调用
  2. 这些调用直到return前被执行,因此可以用来做资源管理
  3. 多个defer语句,按先进后出的方式执行
  4. defer语句中的变量,在defer声明时就决定了

defer的用途

  1. 关闭文件句柄
  2. 锁资源释放
  3. 数据库连接释放

golang init

init函数,先于main函数执行,实现包级别的一些初始化操作’

init函数的主要特点

  1. init函数先于main函数自动执行,不能被其他函数调用
  2. init函数没有输入参数、返回值
  3. 每个包可以有多个init函数
  4. 包的每个文件也可以有多个init函数,
  5. 同一个包的init执行顺序没有明确的定义,编程时,主要不要依赖这个执行顺序
  6. 不同包的init函数按照包导入的依赖关系决定执行顺序

初始化顺序

初始化变量—>init()—>mian()

package main

import "fmt"

func init() {
	fmt.Printf("%s\n", "init")
}

var i int = initVar()

func initVar() int {
	fmt.Printf("%s\n", "initVar")
	return 100
}

func main() {
	fmt.Printf("%s\n", "main")
}

golang指针

go支持指针, 允许在程序中通过引用传递来传递值和数据结构。

类型指针不能进行偏移和运算

&(取地址), *(根据地址取值)

指针地址和指针类型

每个变量在运行时都拥有一个地址,这个地址代表变量在内存中的位置

使用&字符放在变量前面对变量进行取地址操作

go语言中的类型值(int、float、bool、string、array、struct)都有对应的指针类型,如:
*int*int64*string

指针语法

一个指针变量指向了一个值的内存地址。

声明语法:

var var_name *var-type

var-type:为指针类型

var_name:指针变量名

*:用于指定变量是作为一个指针

指针声明实例

var ip *int 	//指向整型
var ip *float32		//指向浮点型

例子:

package main

import "fmt"

func main() {
   var ip *int		//声明指针变量
   fmt.Printf("ip:%v\n", ip)

   var i int = 100	//声明一个实际变量
   ip = &i			//指针变量指向实际变量的地址
   fmt.Printf("ip:%v\n", ip)
   fmt.Printf("ip:%v\n", *ip)
}
/*
ip:<nil>
ip:0xc00001c0e0
ip:100
*/

glang指向数组的指针

指针数组的每一个值都指向其对应的值

定于语法:

var ptr [MAX]*int

例子

package main

import "fmt"

func main() {
	a := [3]int{1, 2, 3}
	var pa [3]*int
	fmt.Printf("a:%v\n", a)
	fmt.Printf("pa:%v\n", pa)

	for i := 0; i < len(a); i++ {
		pa[i] = &a[i]
	}
	fmt.Printf("pa:%v\n", pa)
	for i := 0; i < len(a); i++ {
		fmt.Printf("pa[%d]:%v ", i, *pa[i])
	}
}
/*
a:[1 2 3]
pa:[<nil> <nil> <nil>]
pa:[0xc000010138 0xc000010140 0xc000010148]
pa[0]:1 pa[1]:2 pa[2]:3
*/

golang类型定义和类型别名

类型定义

类型定于的语法

type NewType Type

实例

package main

import "fmt"

func main() {
	type MyInt int
	var i MyInt
	i = 100
	fmt.Printf("i:%v\n", i)
	fmt.Printf("i:%T\n", i)
}
/*
i:100
i:main.MyInt
*/

类型别名

类型别名定义语法

type NewType = type

实例

package main

import "fmt"

func main() {
	type MyInt1 = int
	var j MyInt1
	j = 100
	fmt.Printf("j:%v\n", j)
	fmt.Printf("j:%T\n", j)
}
/*
j:100
j:int
*/

golang结构体

定义语法:

type struct_variable_type struct{
    member definition;
    member definition;
    ...
}

type:类型定义关键字

struct_variable_type:结构体类型名称

struct:结构体定义关键字

member definition:成员定义

实例

package main

import "fmt"

type Person struct {
	id    int
	name  string
	age   int
	email string
}

func main() {
	var tom Person
	tom.name = "tom"
	tom.age = 20
	tom.email = "123"
	fmt.Printf("%v\n", tom)
}
/*
{0 tom 20 123}
*/

结构体的初始化

未初始化的结构体,成员都是零值(int 0 ,float 0.0 , bool false , string nil )

初始化的两种方式:

package main

import "fmt"

type Person struct {
	id    int
	name  string
	age   int
	email string
}

func main() {
	//键值对方式
	kite := Person{
		id:    1,
		name:  "kite",
		age:   20,
		email: "kite@qq.com",
	}
	fmt.Printf("kite:%v\n", kite)
	//列表方式
	tom := Person{
		2, "tom", 21, "tom@qq.com",
	}
	fmt.Printf("tom:%v\n", tom)
}
/*
kite:{1 kite 20 kite@qq.com}
tom:{2 tom 21 tom@qq.com}
*/

结构体指针

package main

import "fmt"

type Person struct {
   id    int
   name  string
   age   int
   email string
}

func main() {
   //键值对的方式
   kite := Person{
      id:    1,
      name:  "kite",
      age:   20,
      email: "kite@qq.com",
   }
   var p_person *Person
   p_person = &kite

   fmt.Printf("tom:%p\n", p_person)
   fmt.Printf("tom:%v\n", *p_person)
}
/*
tom:0xc00007e4b0
tom:{1 kite 20 kite@qq.com}
*/

可以使用new关键字创建结构体指针

package main

import "fmt"

type Person struct {
	id    int
	name  string
	age   int
	email string
}

func main() {
	var tom = new(Person)
	tom.name = "tom"
	tom.id = 1
	tom.age = 20
	tom.email = "1234"
	fmt.Printf("tom:%v\n", *tom)
}
/*
tom:{1 tom 20 1234}
*/

结构体作为函数参数

go结构体可以像普通变量一样,作为函数的参数,传递给函数,这里分为两种情况:

  1. 直接传递结构体,这是一个副本(拷贝),在函数内部不会改变外面结构体内容
  2. 传递结构体指针,这时在函数内部,能够改变外部结构体内容

例如:函数的参数是一个结构体

package main

import "fmt"

type Person struct {
   id    int
   name  string
   age   int
   email string
}

func setP(person Person) {
   person.age = 18
}

func main() {
   tom := Person{1, "tom", 20, "123"}
   fmt.Printf("%v\n", tom)
   setP(tom)
   fmt.Printf("%v\n", tom)
}
/*
{1 tom 20 123}
{1 tom 20 123}
*/

修改之后,原来的结构体变量没有变化

改成指针类型的结构体之后:

package main

import "fmt"

type Person struct {
	id    int
	name  string
	age   int
	email string
}

func setP(person *Person) {
	person.age = 18
}

func main() {
	tom := Person{1, "tom", 20, "123"}
	fmt.Printf("%v\n", tom)
	setP(&tom)
	fmt.Printf("%v\n", tom)
}
/*
{1 tom 20 123}
{1 tom 18 123}
*/

发现结果已经被修改

嵌套结构体

go语言没有面向对象编程思想,也没有继承关系,但是可以通过结构体的嵌套来实现这种效果

package main

import "fmt"

func main() {
   type Dog struct {
      name string
      age  int
   }
   type Person struct {
      name string
      age  int
      dog  Dog
   }
   dog := Dog{"wangcai", 2}
   tom := Person{"tom", 20, dog}
   fmt.Printf("%v\n", tom)
   fmt.Printf("人的名字:%v\n", tom.name)
   fmt.Printf("狗的名字:%v\n", tom.dog.name)
}
/*
{tom 20 {wangcai 2}}
人的名字:tom
狗的名字:wangcai
*/

golang方法

go语言没有面向对象的特性,也没有类对象的概念,但是,可以使用结构体来模拟这些特性

可以声明一些方法,属于某个结构体

方法语法

go中的方法,是一种特殊的函数,定义于struct之上(与struct关联,绑定),被称为struct的接受者(receiver),即:方法就是有接收者的函数

type mytype struct{}

func(recv mytype) my_method(para) return_type{}
func(recv *mytype) my_method(para) return_type{}

mytype:定义一个结构体

recv:接受该方法的结构体

my_method:方法名称

para:参数列表

return_type:返回值类型

例子:

package main

import "fmt"

type Person struct {
   name string
   age  int
}

// (per Person)接收者 receiver
func (per Person) eat() {
   fmt.Printf("%v,eat...\n", per.name)
}
func main() {
   per := Person{
      name: "zhangsan",
   }
   per.eat()
}

方法的注意事项:

  1. 方法的receiver type并非要是struct类型,type定义的类型别名,slice,map,channel,func等类型都可以
  2. struct结合它的方法就等价于面向对象中的类。只不过struct可以和它的方法分开,并非一定要属于同一个文件,但必须属于同一个包。
  3. 方法有两种接收类型:(T Type)和(T *Type),它们之间有区别。
  4. 方法就是函数,所以Go中没有方法重载(overload)的说法,也就是说同一个类型中的所有方法名必须都唯一。
  5. 如果receiver是一个指针类型,则会自动解除引用。
  6. 方法和type是分开的,意味着实例的行为(behavior)和数据存储(field)是分开的,但是它们通过receiver建立起关联关系。

方法接收者类型

结构体实例,有值类型和指针类型,那么方法的接收者是结构体,也有值类型和指针类型

区别就是接收者是否复制结构体副本,值类型复制,指针类型不复制

golang接口

interface类型可以定义一组方法,但是这些方法不需要实现,并且interface不能包含任何变量,到某个自定义类型(比如结构体类型)要使用的时候,再根据具体情况把这些方法实现出来

接口里的所有方法都没有方法体,即接口的方法都是没有实现的方法,接口体现了程序设计的多态和高内聚低耦合的思想

go中的接口不需要显示实现,只要一个变量含有接口类型的所有方法,那么这个变量就实现了这个接口,没有implements关键字

定义语法

//定义接口
type interface_name interface{
    method_name1 [return_type]
    method_name2 [return_type]
    ...
}
//定义结构体
type struct_name struct{
    
}
//实现接口的方法
func (struct_name_variable struct_name) method_name1()[return_type]{
    //方法实现
}
...
//实现接口的方法
func (struct_name_variable struct_name) method_namen()[return_type]{
    //方法实现
}

实例:定义一个usb接口,有读read和写write两个方法,在再定义一个电脑Computer和手机Mobile来实现这个接口

package main

import "fmt"

// 定义接口
type USB interface {
	read()
	write()
}

type Computer struct {
	name string
}
type Mobile struct {
	name string
}

// computer实现usb接口方法
func (c Computer) read() {
	fmt.Printf("c.name:%v\n", c.name)
	println("computer read")
}
func (c Computer) write() {
	fmt.Printf("c.name:%v\n", c.name)
	println("computer write")
}

func main() {
	c := Computer{
		name: "联想",
	}
	c.read()
	c.write()
}
/*
c.name:联想
computer read
c.name:联想
computer write
*/

接口方法接收者值类型或指针类型

值类型是将值复制一份传递给方法,不会改变原变量的值

指针类型是将变量的地址直接传递给函数,直接改变原变量

例子

值类型:

package main

import "fmt"

type Pet interface {
	eat(string) string
}

type Dog struct {
	name string
}

func (dog Dog) eat(name string) string {
	dog.name = name
	return name
}

func main() {
	dog := Dog{
		name: "huahua",
	}
	s := dog.eat("奥里给")
	fmt.Printf("s:%v\n", s)
	fmt.Printf("dog:%v\n", dog)
}
/**
s:奥里给
dog:{huahua}
*/

指针类型:

package main

import "fmt"

type Pet interface {
	eat(string) string
}

type Dog struct {
	name string
}

func (dog *Dog) eat(name string) string {
	dog.name = name
	return name
}

func main() {
	dog := &Dog{
		name: "huahua",
	}
	s := dog.eat("奥里给")
	fmt.Printf("s:%v\n", s)
	fmt.Printf("dog:%v\n", dog)
}
/**
s:奥里给
dog:&{奥里给}
*/

golang接口和类型的关系

  1. 一个类型可以实现多个接口
  2. 多个类型可以实现同一个接口(多态)

一个类型可以实现多个接口

type Music interface {
	playMusic()
}
type Video interface {
	playVideo()
}

type MobilePhone struct {
}

func (phone MobilePhone) playMusic() {
	fmt.Printf("%v\n", "playMusic...")
}
func (phone MobilePhone) playVideo() {
	fmt.Printf("%v\n", "playVideo...")
}
func main() {
	m := MobilePhone{}
	m.playMusic()
	m.playVideo()
}
/*
playMusic...
playVideo...
*/

多个类型可以实现同一个接口(多态)

type Music interface {
	playMusic()
}

type MobilePhone struct {
}
type MobileComputer struct {
}

func (phone MobilePhone) playMusic() {
	fmt.Printf("%v\n", "MobilePhone playMusic...")
}
func (c MobileComputer) playMusic() {
	fmt.Printf("%v\n", "MobileComputer playMusic...")
}
func main() {
	var m Music
	m = MobilePhone{}
	m.playMusic()
	m = MobileComputer{}
	m.playMusic()
}
/*
MobilePhone playMusic...
MobileComputer playMusic...
*/

golang接口的嵌套

接口可以通过嵌套,创建新的接口。例如:飞鱼,既可以飞,又可以游泳。我们创建一个飞Fly接口,创建一个游泳接口Swim,飞鱼接口有这两个接口组成。

type Flyer interface {
	fly()
}
type Swimmer interface {
	swim()
}
type FlyFish interface {
	Flyer
	Swimmer
}

type Fish struct {
	//属性
}

func (fish Fish) fly() {
	fmt.Printf("%v\n", "fly...")
}
func (fish Fish) swim() {
	fmt.Printf("%v\n", "swim...")
}
func main() {
	var ff FlyFish
	ff = Fish{}
	ff.swim()
	ff.fly()
}
/**
swim...
fly...
*/

golang通过接口实现OCP设计原则

而面向对象的可复用设计的第一块基石,便是所谓的”开-闭“原则(Open-Closed Principle,常缩写为OCP)。虽然go不是面向对象语言,但是也可以模拟实现这个原则。对扩展是开放的,对修改是关闭的。

golang包

包可以区分命令空间(一个文件夹中不能有两个同名文件),也可以更好的管理项目。go中创建一个包,一般是创建一个文件夹,在该文件夹里面的go文件中,使用package关键字声明包名称,通常,文件夹名称和包名称相同。并且,同一个文件下面只有一个包

要使用某个包下面的变量或者方法,需要导入该包,

golang并发编程

golang并发编程之协程

Golang 中的并发是函相互独立运行的能力。Goroutines 是并发运行的函数。Golang 提供了Goroutines作为并发处理操作的一种方式。

创建一个协程非常简单,就是在一个任务函数前面添加一个go关键字:

go task()
package main

import (
	"fmt"
	"time"
)

func showMsg(msg string) {
	for i := 0; i < 5; i++ {
		fmt.Printf("mag: %v\n", msg)
		time.Sleep(time.Millisecond * 100)
	}
}

func main() {
	go showMsg("java") //启动了一个协程
	showMsg("golang")
}
/*
mag: golang
mag: java
mag: java
mag: golang
mag: java
mag: golang
mag: golang
mag: java
mag: java
mag: golang
*/

golang并发编程之通道channel

Go提供了一种称为通道的机制,用于在goroutine之间共享数据。当您作为goroutine执行并发活动时,需要在goroutine之间共享资源或数据,通道充当goroutine之间的管道(管道)并提供一种机制来保证同步交换。

需要在声明通道时指定数据类型。我们可以共享内置、命名、结构和引用类型的值和指针。数据在通道上传递:在任何给定时间只有一个goroutine 可以访问数据项:因此按照设计不会发生数据竞争。

根据数据交换的行为,有两种类型的通道:无缓冲通道和缓冲通道。无缓冲通道用于执行goroutine之间的同步通信,而缓冲通道用于执行异步通信。无缓冲通道保证在发送和接收发生的瞬间执行两个goroutine之间的交换。缓冲通道没有这样的保证。

通道由make函数创建,该函数指定chan关键字和通道的元素类型。

创建无缓冲和缓冲通道:
Unbuffered := make(chan int)//整型无缓冲通道
buffered := make(chan int,10)//整型有缓存通道
将值发送到通道需要使用<-运算符:
goroutine1 := make(chan string,5)	//字符串缓存通道
goroutine1 <- "American"			//通过通道发送字符串

一个包含5个值的缓存区的字符串类型的goroutine1通道,然后我们通过通道发送字符串"American"

接收通道中的值需要使用<-运算符:
data := <-goroutine1 //从通道接收字符串
通道的发送和接收特性
  1. 对于同一个通道,发送操作之间是互斥的,接收操作之间也是互斥的
  2. 发送操作和接收操作中对元素值的处理都是不可分割的
  3. 发送操作在完全完成之前会被阻塞。接收操作也是如此
package main

import (
	"fmt"
	"math/rand"
	"time"
)

// 创建int类型通道,只能传入int类型值
var values = make(chan int)

func send() {
	rand.Seed(time.Now().UnixNano())
	value := rand.Intn(10)
	fmt.Printf("send: %v\n", value)
	values <- value
}

func main() {
	//从通道接受值
	defer close(values)
	go send()
	fmt.Println("wait..")
	value := <-values
	fmt.Printf("receive: %v\n", value)
	fmt.Println("end..")
}

golang并发编程之WaitGroup实现同步

实现主协程等待某个协程结束

package main

import (
	"fmt"
	"sync"
)

var wp sync.WaitGroup

func showMessage(i int) {
	defer wp.Done()
	fmt.Printf("%v\n", i)
}

func main() {
	for i := 0; i < 10; i++ {
		//启动一个协程来执行
		go showMessage(i)
		wp.Add(1)
	}
	wp.Wait()
	//主协程
	fmt.Println("end..")
}
/**
9
4
0
1
2
3
6
5
7
8
end..
*/

golang并发编程之runtime包

runtime包里面定义了一些协程管理相关的api

runtime.Gosched()

让出cpu时间片,重新等待安排任务

package main

import (
	"fmt"
	"runtime"
)

func show(s string) {
	for i := 0; i < 2; i++ {
		println(s)
	}
}

func main() {
	go show("java")
	for i := 0; i < 2; i++ {
		runtime.Gosched()
		fmt.Printf("golang\n")
	}
}
/**
java
java
golang
golang
*/
runtime.GOMAXPROCS

显示最大核心数

package main

import (
	"fmt"
	"runtime"
	"time"
)

func a() {
	for i := 0; i < 10; i++ {
		fmt.Printf("A:%v, ", i)
		time.Sleep(time.Millisecond * 100)
	}
}
func b() {
	for i := 0; i < 10; i++ {
		fmt.Printf("B:%v, ", i)
		time.Sleep(time.Millisecond * 100)
	}
}

func main() {
	fmt.Printf("runtime.NumCPU(): %v\n", runtime.NumCPU())
	runtime.GOMAXPROCS(2)
	go a()
	go b()
	time.Sleep(time.Second)
}
/*
runtime.NumCPU(): 8
B:0, A:0, A:1, B:1, B:2, A:2, A:3, B:3, B:4, A:4, A:5, B:5, B:6, A:6, A:7, B:7, B:8, A:8, B:9, A:9, 
*/

golang并发编程之Mutex互斥锁实现同步

除了使用channel实现同步之外,还可以使用Mutex互斥锁的方式实现同步

package main

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

var i int = 100
var wg sync.WaitGroup

var lock sync.Mutex

func add() {
	lock.Lock()
	defer wg.Done()
	i += 1
	fmt.Printf("i++: %v\n", i)
	time.Sleep(time.Millisecond * 10)
	lock.Unlock()
}
func sub() {
	lock.Lock()
	defer wg.Done()
	i -= 1
	fmt.Printf("i--:%v\n", i)
	time.Sleep(time.Millisecond * 3)
	lock.Unlock()
}

func main() {
	for i := 0; i < 100; i++ {
		wg.Add(1)
		go add()
		wg.Add(1)
		go sub()
	}
	wg.Wait()
	fmt.Printf("end i: %v\n", i)
}
//使用锁的方式实现同步

golang并发编程之channel的遍历

package main

import "fmt"

var c = make(chan int)

func t1() {
	for i := 0; i < 2; i++ {
		c <- i
	}
	close(c)
}
func main() {
	go t1()
	//for i := 0; i < 3; i++ {
	//	r := <-c
	//	fmt.Printf("r: %v\n", r)
	//}
	for v := range c {
		fmt.Printf("V: %v\n", v)
	}
}

注意:如果通道关闭,读多写少,没有了就是默认值,例如,int就是0,如果没有关闭就会死锁。

golang并发编程之select switch

  1. selectGo中的一个控制结构,类似于switch语句,用于处理异步IO操作。select会监听case语句中channel的读写操作,当casechannel读写操作为非阻塞状态(即能读写)时,将会触发相应的动作。
    select中的case语句必须是一个channel操作
    select中的default子句总是可运行的。
  2. 如果有多个case都可以运行,select 会随机公平地选出一个执行,其他不会执行。
  3. 如果没有可运行的case语句,且有 default语句,那么就会执行default的动作。
  4. 如果没有可运行的case语句,且没有default语句, select将阻塞,直到某个case通信可以运行
package main

import (
	"fmt"
	"time"
)

var chanInt = make(chan int)
var chanStr = make(chan string)

func main() {

	go func() {
		chanInt <- 100
		chanStr <- "hello"
		close(chanInt)
		close(chanStr)
	}()

	for {
		select {
        //读出来的时int类型的时候
		case r := <-chanInt:
			fmt.Printf("chanInt: %v\n", r)
        //读出来的时char类型的时候
		case r := <-chanStr:
			fmt.Printf("chanStr: %v\n", r)
		default:
			fmt.Printf("default\n")
		}
		time.Sleep(time.Second)
	}
}

golang并发编程之Timer

定时器,可以实现一些定时操作,内部也是通过channel来实现的

package main

import (
	"fmt"
	"time"
)

func main() {
	//代表等待两秒之后
	time1 := time.NewTimer(time.Second * 2)

	t1 := time.Now()
	fmt.Printf("t1: %v\n", t1)
	t2 := <-time1.C
	fmt.Printf("t2: %v\n", t2)
}

golang并发编程之Ticker

Timer只执行一次,Ticker可以周期的执行

package main

import (
	"fmt"
	"time"
)

func main() {
	var count int = 0
	//创建tacker,周期时1s
	ticker := time.NewTicker(time.Second)
	for _ = range ticker.C {
		fmt.Printf("ticler\n")
		//计数停止
		count++
		if count >= 5 {
			ticker.Stop()
			break
		}
	}
}

golang并发编程之原子变量的引入

可以通过加锁的方式实现原子操作

package main

import (
	"sync"
	"time"
)

var a1 int = 100

var locks sync.Mutex

func adds() {
	locks.Lock()
	a1++
	locks.Unlock()
}
func subs() {
	locks.Lock()
	a1--
	locks.Unlock()
}
func main() {
	for i := 0; i < 100; i++ {
		go adds()
		go subs()
	}
	time.Sleep(time.Second * 2)
	print(a1)
}

或者是引入原子变量

package main

import (
	"sync/atomic"
	"time"
)

var a1 int32 = 100

func adds() {
	atomic.AddInt32(&a1, 1)
}
func subs() {
	atomic.AddInt32(&a1, -1)
}
func main() {
	for i := 0; i < 100; i++ {
		go adds()
		go subs()
	}
	time.Sleep(time.Second * 2)
	print(a1)
}

atomic提供的原子操作能够确保任何一时刻只有一个goroutine对变量进行操作,善用atomic能够避免程序中出现大量的锁操作。

atomic常见操作有:

  • 增减
  • 载入 read
  • 比较并交换
  • 交换
  • 存储 write
//载入和read
func load_store() {
	var i int32 = 100
	atomic.LoadInt32(&i)
	fmt.Printf("i %v\n", i)

	atomic.StoreInt32(&i, 200)
	fmt.Printf("i %v\n", i)
}
//比较并交换
func cas() {
	var i int32 = 100
	b := atomic.CompareAndSwapInt32(&i, 100, 200)
	println(i)
	println(b)
}

golang标准库os包

https://pkg.go.dev/std

文件目录相关

创建文件

//在指定目录位置创建文件
func createFile() {
	create, err := os.Create("stand/a.txt")
	if err != nil {
		println(err)
	} else {
		println(create.Name())
	}
}

创建目录

//创建目录为 stand/a/b
func createDir() {
	err := os.MkdirAll("stand/a/bstand/a/b", os.ModePerm)
	if err != nil {
		println(err)
	}
}

删除目录

//删除的目录为上述的stand/a/b中的b目录
func delDir() {
	err := os.RemoveAll("stand/a/b")
	if err != nil {
		fmt.Printf("err: %v\n", err)
	}
}

获得工作目录(项目工程所在的位置)

func getDir() {
	dir, err := os.Getwd()
	if err != nil {
		println(err)
	} else {
		println(dir)
	}
}
//D:\go\Test04

修改工作目录

func changeWorkSpace(){
	err := os.Chdir("d:/")
	if err!= nil{
		println(err)
	}
	println(os.Getwd())
}

修改文件名称

func rename() {
	err := os.Rename("stand/a.txt", "stand/b.txt")
	if err != nil {
		println(err)
	}
}

写文件

//文件的内容将被替换
func write() {
	s := "hello"
	err := os.WriteFile("stand/b.txt", []byte(s), os.ModePerm)
	if err != nil {
		println(err)
	}
}

读文件

func read() {
   file, err := os.ReadFile("stand/b.txt")
   if err != nil {
      println(err)
   } else {
      println(string(file[:]))
   }
}
文件读操作
// 打开和关闭
func openClose() {
	//f, err := os.Open("stand/b.txt")
	//if err != nil {
	//	fmt.Printf("%v\n", err)
	//} else {
	//	fmt.Printf("%v\n", f.Name())
	//	f.Close()
	//}

	file, err := os.OpenFile("stand/b.txt", os.O_RDWR|os.O_CREATE, 755)
	if err != nil {
		fmt.Printf("err: %v\n", err)
	} else {
		fmt.Printf("file.Name(): %v\n", file.Name())
		file.Close()
	}
}

// 创建文件
func create() {
	//等价于:OpenFile("fileName", O_RDWR|O_CREATE|O_TRUNC, 755)
	file, _ := os.Create("stand/a2.txt")
	fmt.Printf("file.Name(): %v\n", file.Name())
	temp, _ := os.CreateTemp("", "temp")
	fmt.Printf("temp.Name(): %v\n", temp.Name())
}

// 读文件
func readOps() {
	open, _ := os.Open("stand/b.txt")
	for {
		buf := make([]byte, 10)
		n, err := open.Read(buf)
		if err == io.EOF {
			return
		}
		fmt.Printf("n: %v\n", n)
		fmt.Printf("string(buf): %v\n", string(buf))
	}
}
文件写操作
func writeFiles() {
	//O_RDWR常量用于指定以读写模式打开文件的读写模式。
	//O_APPEND常量用于追加写入模式
	//O_TRUNC常量用于覆盖写入模式
	file, _ := os.OpenFile("stand/a2.txt", os.O_RDWR|os.O_TRUNC, 0755)
	n, err := file.Write([]byte("hello go!"))
	if err != nil {
		fmt.Printf("err: %v\n", err)
	} else {
		fmt.Printf("n: %v\n", n)
	}
	file.Close()
}
//在指定位置插入指定的字符串
func writeAt() {
	file, _ := os.OpenFile("stand/a2.txt", os.O_RDWR, 0755)
	n, err := file.WriteAt([]byte("aaa"), 1)
	if err != nil {
		fmt.Printf("err: %v\n", err)
	} else {
		fmt.Printf("n: %v\n", n)
	}
	file.Close()
}

galang标准库io包

Go语言中,为了方便开发者使用,将IO操作封装在了如下几个包中:

  • ioIO原语(I/O primitives)提供基本的接口 os File
  • io/ioutil封装一些实用的I/O函数
  • fmt实现格式化I/O,类似C语言中的printfscanf
  • bufio 实现带缓冲I/O

io 基本的IO接口

Reader接口
type Reader interface {
   Read(p []byte) (n int, err error)
}
Writer接口
type Writer interface {
   Write(p []byte) (n int, err error)
}

galang标准库sort包

sort包提供了排序切片和用户自定义数据集以及相关功能的函数。

sort包主要针对[]int[ ]float64[ ]string、以及其他自定义切片的排序。

galang标准库time包

获取时间

now := time.Now()
fmt.Printf("now: %v\n", now,now)
fmt.Printf("Year: %v\n", now.Year())
fmt.Printf("Month: %v\n", now.Month())
fmt.Printf("Day: %v\n", now.Day())
fmt.Printf("Hour: %v\n", now.Hour())
fmt.Printf("Minute: %v\n", now.Minute())
fmt.Printf("Second: %v\n", now.Second())
/*
now: 2023-01-29 16:43:49.0496523 +0800 CST m=+0.005771001
Year: 2023
Month: January
Day: 29
Hour: 16
Minute: 43
Second: 49
*/

获取时间戳

fmt.Printf("时间戳: %v\n", now.Unix())//时间戳: 1674982190
fmt.Printf("纳秒时间戳: %v\n", now.UnixNano())//纳秒时间戳: 1674982313549030400

将时间戳转化为时间

unix := time.Now().Unix()
time := time.Unix(unix, 0)
fmt.Printf("%v\n", time)
fmt.Printf("Year: %v\n", time.Year())
fmt.Printf("Month: %v\n", time.Month())

Add函数和Sub函数

now := time.Now()
addTime := now.Add(time.Hour)
fmt.Printf("%v\n", addTime)
fmt.Printf("%v\n", addTime.Sub(now))

Equal函数,判断两个时间是否相同,会考虑时区的影响,因此不同时区也可以正确的比较

func (t Time) Equal(u Time) bool

例子

now := time.Now()
addTime := now.Add(time.Hour)
fmt.Printf("now.Equal(addTime): %v\n", now.Equal(addTime))
//now.Equal(addTime): false

Before函数,t时间点在u之前,返回值为真,否则为假

func (t Time) Before(u Time) bool

After函数,t时间点在u之后,返回值为真,否则为假

func (t Time) After(u Time) bool

定时器,使用time.Tick(时间间隔) 来设置定时器,定时器的本质上是一个通道(channel)

func tick() {
   ticker := time.Tick(time.Second)
   for i := range ticker {
      fmt.Printf("%v\n", i)
   }
}
/*
2023-01-30 08:51:00.1026028 +0800 CST m=+1.011140801
2023-01-30 08:51:01.107312 +0800 CST m=+2.015850001
2023-01-30 08:51:02.1117447 +0800 CST m=+3.020282701
2023-01-30 08:51:03.1071476 +0800 CST m=+4.015685601
*/

时间格式化

func format() {
	now := time.Now()
	//格式化时间的时候不是使用的Y-m-d H:M:S 而是使用的Go语言诞生时间2006-01-02 15:04:05.000 Mon Jan
	fmt.Printf("%v\n", now.Format("2006-01-02 15:04:05.000 Mon Jan"))    //24h制
	fmt.Printf("%v\n", now.Format("2006-01-02 15:04:05.000 PM Mon Jan")) //12h制
	fmt.Printf("%v\n", now.Format("2006-01-02 15:04"))
	fmt.Printf("%v\n", now.Format("2006-01-02"))
}

将字符串解析成为时间对象

func stringToTime() {
	//加载时区
	loc, err := time.LoadLocation("Asia/Shanghai")
	if err != nil {
		fmt.Printf("%v\n", err)
		return
	}
	locationTimeObject, err := time.ParseInLocation("2006-01-02 15:04:05", "2023-01-30 08:51:10", loc)
	if err != nil {
		fmt.Printf("%v\n", err)
		return
	}
	fmt.Printf("locationTimeObject: %v\n", locationTimeObject)
}
//locationTimeObject: 2023-01-30 08:51:10 +0800 CST

golang标准库encoding/json

这个包可以实现json的编码和解码,就是将json字符串转化为struct,或者将struct转化为json

将struct编码成json,可以接收任意类型

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

将json转化为struct

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

import (
   "encoding/json"
   "fmt"
)

type Person struct {
	Name  string
	Age   int
	Email string
}

//将struct转化为json
func t1() {
	person := Person{
		Name:  "tom",
		Age:   20,
		Email: "tom@qq.com",
	}
	b, _ := json.Marshal(person)
	fmt.Printf("marshal: %v\n", string(b))
}
//将json转化为struct
func t2() {
	b := []byte(`{"Name":"tom","Age":20,"Email":"tom@qq.com"}`)
	var person Person
	json.Unmarshal(b, &person)
	fmt.Printf("person: %v\n", person)
}
/*
marshal: {"Name":"tom","Age":20,"Email":"tom@qq.com"}
person: {tom 20 tom@qq.com}
*/

golang标准库encoding/xml

type Person struct {
   XMLName xml.Name `xml:"person"`
   Name    string   `xml:"name"`
   Age     int      `xml:"age"`
   Email   string   `xml:"email"`
}

// 将struct转化为xml
func t1() {
   person := Person{
      Name:  "tom",
      Age:   20,
      Email: "tom@qq.com",
   }
   b, _ := xml.MarshalIndent(person, " ", " ")
   fmt.Printf("%v\n", string(b))
}

//将xml转化为struct
func t2() {
   s := "<person>\n  <name>tom</name>\n  <age>20</age>\n  <email>tom@qq.com</email>\n </person>\n"
	bytes := []byte(s) //字节切片
	//或者直接从文件中读取直接切片
	//file, _ := os.ReadFile("stand/b.txt")
	//bytes := file[:]

	var person Person
	xml.Unmarshal(bytes, &person)
	fmt.Printf("person: %v\n", person)
}
/*
 <person>
  <name>tom</name>
  <age>20</age>
  <email>tom@qq.com</email>
 </person>
person: {{ person} tom 20 tom@qq.com}
*/

golang标准库math

一些常量

fmt.Printf("%v\n", math.MaxInt)
fmt.Printf("%v\n", math.MaxInt32)
fmt.Printf("%v\n", math.Pi)
/*
9223372036854775807
2147483647
3.141592653589793
*/

一些函数

//绝对值
fmt.Printf("%v\n", math.Abs(-10))
//x的y次方
fmt.Printf("%v\n", math.Pow(10, 4))
//10的n次方
fmt.Printf("%v\n", math.Pow10(4))
//开平方
fmt.Printf("%v\n", math.Sqrt(100))
//开立方
fmt.Printf("%v\n", math.Cbrt(8))
//向上取整
fmt.Printf("%v\n", math.Ceil(4.1))
//向下取整
fmt.Printf("%v\n", math.Floor(4.1))
//取余数
fmt.Printf("%v\n", math.Mod(10, 3))
//分别取到整数部分和小数部分
modf, frac := math.Modf(3.1415)
fmt.Printf("整数部分: %v, 小数部分: %v\n", modf, frac)

随机数

//随机一个int类型的随机值
func f1() {
	fmt.Printf("%v\n", rand.Int())
}
//随机一个0~10的随机值
func f2() {
	a := rand.Intn(10)
	fmt.Printf("%v\n", a)
}
//为了实现每次随机值不一样
func init() {
	rand.Seed(time.Now().UnixMicro())
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值