Zhong__Go笔记

时间:2021.06.22

环境:Windows

目的:

说明:

作者:Zhong QQ交流群:121160124 欢迎加入!

 

 

环境配置

格式化输出变量

查看变量字节大小

查看变量类型

查看整形变量对应的unicode字符

数据类型

常量

整形的类型

字符串和字符

值类型和引用类型

流程控制

条件语句

if

if...else

switch

select

循环语句

for循环

无限循环

控制语句

break 语句

continue 语句

goto 语句

defer

顺序控制

分支控制

循环控制

函数

函数参数

值传递

引用传递

函数用法

函数作为值

闭包

方法

数组

声明数组

初始化数组

数组完整操作

向函数传递数组

指针

声明指针

使用指针

空指针

指针数组

指向指针的指针

指针作为函数参数

结构体

定义结构体

匿名结构体

访问结构体成员

结构体作为函数参数

结构体指针

切片(Slice)

定义切片

切片初始化

len() 和 cap() 函数

空(nil)切片

切片截取

append() 和 copy() 函数

范围(Range)

Map(集合)

定义 Map

实例

delete() 函数

递归函数

阶乘

斐波那契数列

类型转换

实例

接口

实例

错误处理

实例

sync包与锁

互斥锁 Mutex

上面的示例中出现的问题怎么解决呢?加一个互斥锁 Mutex 就可以了

读写锁

正则表达式

regexp包

时间和日期

time

时间的获取

获取当前时间

时间戳转为时间格式

时间操作函数

定时器

时间格式化

解析字符串格式的时间

OS

命令行参数解析

mod包依赖管理工具

文件处理

JSON 文件

文本文件

写纯文本文件

数据库操作

链接mysql

环境配置

新建文件夹src 创建一个go模块

go mod init example.com/src

在src目录下写代码即可

格式化输出变量

查看变量的字节大小、数据类型等信息

查看变量字节大小

var t2 string = "abc" fmt.Printf("t2占用的字节数为:%d\n", unsafe.Sizeof(t2))

查看变量类型

var t1 int = 100 fmt.Printf("t1的数据类型:%T\n", t1)

查看整形变量对应的unicode字符

var t3 int = 10086 fmt.Printf("t3对应的unicode为:%c\n", t3)

数据类型

常量

常量是一个简单值的标识符,在程序运行时,不会被修改的量。

常量中的数据类型只可以是布尔型、数字型(整数型、浮点型和复数)和字符串型。

 

整形的类型

int有符号类型

 

int无符号类型

 

int其它的类型

 

字符串和字符

字符使用单引号定义的单个字节

var t4 byte = 'a' fmt.Printf("t4: %v\n", t4)

字符串是由多个字符连接组成的使用双引号/反引号定义

var t5, t6 string = "hi", `hello world` fmt.Printf("t5: %s t6: %s\n", t5, t6)

值类型和引用类型

所有像 int、float、bool 和 string 这些基本类型都属于值类型,使用这些类型的变量直接指向存在内存中的值

当使用等号 = 将一个变量的值赋值给另一个变量时,如:j = i,实际上是在内存中将 i 的值进行了拷贝

值类型的变量的值存储在栈中

 

更复杂的数据通常会需要使用多个字,这些数据一般使用引用类型保存。

一个引用类型的变量 r1 存储的是 r1 的值所在的内存地址(数字),或内存地址中第一个字所在的位置。

这个内存地址为称之为指针,这个指针实际上也被存在另外的某一个字中。

同一个引用类型的指针指向的多个字可以是在连续的内存地址中(内存布局是连续的),这也是计算效率最高的一种存储形式;也可以将这些字分散存放在内存中,每个字都指示了下一个字所在的内存地址。

当使用赋值语句 r2 = r1 时,只有引用(地址)被复制。

如果 r1 的值被改变了,那么这个值的所有引用都会指向被修改后的内容,在这个例子中,r2 也会受到影响。

流程控制

条件语句

if

package main import "fmt" func main() { /* 局部变量定义 */ var a int = 100; /* 判断布尔表达式 */ if a < 60 { /* 如果条件为 true 则执行以下语句 */ fmt.Printf("a 小于 60\n" ); } else if a < 80 { fmt.Printf("a 小于 80 大于 60\n" ); } else { /* 如果条件为 false 则执行以下语句 */ fmt.Printf("a 不小于 80\n" ); } fmt.Printf("a 的值为 : %d\n", a); }

if简写

package main import "fmt" func main() { fruits := map[string]string{ "apple": "delicious", "banana": "coca", } if res, ok := fruits["apple"]; ok { fmt.Println(res, ok) } else { fmt.Println("None") } }

if...else

if嵌套

switch

switch 语句用于基于不同条件执行不同动作。

package main import "fmt" func main() { /* 定义局部变量 */ var grade string = "B" var marks int = 90 switch marks { case 90: grade = "A" case 80: grade = "B" case 50,60,70 : grade = "C" default: grade = "D" } switch { case grade == "A" : fmt.Printf("优秀!\n" ) case grade == "B", grade == "C" : fmt.Printf("良好\n" ) case grade == "D" : fmt.Printf("及格\n" ) case grade == "F": fmt.Printf("不及格\n" ) default: fmt.Printf("差\n" ); } fmt.Printf("你的等级是 %s\n", grade ); }

用于判断类型的实例

package main import "fmt" func main() { var x interface{} switch i := x.(type) { case nil: fmt.Printf(" x 的类型 :%T",i) case int: fmt.Printf("x 是 int 型") case float64: fmt.Printf("x 是 float64 型") case func(int) float64: fmt.Printf("x 是 func(int) 型") case bool, string: fmt.Printf("x 是 bool 或 string 型" ) default: fmt.Printf("未知型") } }

select

select 语句类似于 switch 语句,但是select会随机执行一个可运行的case。如果没有case可运行,它将阻塞,直到有case可运行。

package main import "fmt" func main() { var c1, c2, c3 chan int var i1, i2 int select { case i1 = <-c1: fmt.Printf("received ", i1, " from c1\n") case c2 <- i2: fmt.Printf("sent ", i2, " to c2\n") case i3, ok := (<-c3): // same as: i3, ok := <-c3 if ok { fmt.Printf("received ", i3, " from c3\n") } else { fmt.Printf("c3 is closed\n") } default: fmt.Printf("no communication\n") } }

循环语句

循环处理语句

for循环

Go语言的For循环有3中形式,只有其中的一种使用分号。

for init; condition; post { } for condition { } for { }

 

package main import "fmt" func main() { var b int = 15 var a int numbers := [6]int{1, 2, 3, 5} /* for 循环 */ for a := 0; a < 10; a++ { fmt.Printf("a 的值为: %d\n", a) } for a < b { a++ fmt.Printf("a 的值为: %d\n", a) } for i,x:= range numbers { fmt.Printf("第 %d 位 x 的值 = %d\n", i,x) } }

循环嵌套

无限循环

如果循环中条件语句永远不为 false 则会进行无限循环,我们可以通过 for 循环语句中只设置一个条件表达式来执行无限循环

package main import "fmt" func main() { for true { fmt.Printf("这是无限循环。\n"); } }

控制语句

break 语句

经常用于中断当前 for 循环或跳出 switch 语句

continue 语句

跳过当前循环的剩余语句,然后继续进行下一轮循环。

goto 语句

将控制转移到被标记的语句。

defer

defer 语句会将函数推迟到外层函数返回之后执行。

推迟调用的函数其参数会立即求值,但直到外层函数返回前该函数都不会被调用。

package main import "fmt" func main() { defer fmt.Println("world") fmt.Println("hello") }

defer 栈

推迟的函数调用会被压入一个栈中。当外层函数返回时,被推迟的函数会按照后进先出的顺序调用。

package main import "fmt" func main() { fmt.Println("counting") for i := 0; i < 10; i++ { defer fmt.Println(i) } fmt.Println("done") }

顺序控制

从上到下的顺序执行代码

分支控制

if else

switch

循环控制

for

while

函数

Go 语言最少有个 main() 函数。

函数参数

函数如果使用参数,该变量可称为函数的形参。

形参就像定义在函数体内的局部变量。

调用函数,可以通过两种方式来传递参数:

值传递

值传递是指在调用函数时将实际参数复制一份传递到函数中,这样在函数中如果对参数进行修改,将不会影响到实际参数。

引用传递

引用传递是指在调用函数时将实际参数的地址传递到函数中,那么在函数中对参数所进行的修改,将影响到实际参数。

默认情况下,Go 语言使用的是值传递,即在调用过程中不会影响到实际参数。

函数用法

函数作为值

函数定义后可作为值来使用

闭包

闭包是匿名函数,可在动态编程中使用

方法

Go 没有类。不过你可以为结构体类型定义方法。

方法就是一个包含了接受者的函数

Go 语言中同时有函数和方法。一个方法就是一个包含了接受者的函数,接受者可以是命名类型或者结构体类型的一个值或者是一个指针。所有给定类型的方法属于该类型的方法集

方法接收者在它自己的参数列表内,位于 func 关键字和方法名之间。

在此例中,Abs 方法拥有一个名为 v,类型为 Vertex 的接收者。

package main import ( "fmt" "math" ) type Vertex struct { X, Y float64 } func (v Vertex) Abs() float64 { return math.Sqrt(v.X*v.X + v.Y*v.Y) } func main() { v := Vertex{3, 4} fmt.Println(v.Abs()) }

实例

package main import ( "fmt" ) /* 定义函数 */ type Circle struct { radius float64 } func main() { var c1 Circle c1.radius = 10.00 fmt.Println("Area of Circle(c1) = ", c1.getArea()) } //该 method 属于 Circle 类型对象中的方法 func (c Circle) getArea() float64 { //c.radius 即为 Circle 类型对象中的属性 return 3.14 * c.radius * c.radius }

数组

数组是具有相同唯一类型的一组已编号且长度固定的数据项序列,这种类型可以是任意的原始类型例如整形、字符串或者自定义类型。

Go语言中的数组可以在声明后再赋值 也可以在声明时初始化值 可以改变元素的值

声明数组

var arr1 [10] int // 默认数组中的元素全是0 arr1[0] = 10 // 指定数组中的第一个元素值为10

初始化数组

几种定义数组赋值的例子

package main import "fmt" func main () { var arr2 = [10] int {1,2,3,4,5,6,7} var arr3 = [] int {1, 2} var arr4 = [] int {} var arr5 = [10] int {0:0, 2:20, 5:50} arr5[7] = 70 fmt.Println(arr2) fmt.Println(arr3) fmt.Println(arr4) fmt.Println(arr5) }

数组完整操作

数组完整操作(声明、赋值、访问)的实例

package main import "fmt" func main() {    var n [10]int /* n 是一个长度为 10 的数组 */    var i,j int    /* 为数组 n 初始化元素 */             for i = 0; i < 10; i++ {       n[i] = i + 100 /* 设置元素为 i + 100 */    }    /* 输出每个数组元素的值 */    for j = 0; j < 10; j++ {       fmt.Printf("Element[%d] = %d\n", j, n[j] )    } }

向函数传递数组

在函数定义时,声明形参为数组,我们可以通过以下两种方式来声明:

形参设定数组大小

void myFunction(param [10]int){ . . . }

形参未设定数组大小

void myFunction(param []int){ . . . }

指针

一个指针变量可以指向任何一个值的内存地址

声明指针

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

使用指针

指针使用流程:

  • 定义指针变量。
  • 为指针变量赋值。
  • 访问指针变量中指向地址的值。

在指针类型前面加上 * 号(前缀)来获取指针所指向的内容。

package main import "fmt" func main() { var a int= 20 /* 声明实际变量 */ var ip *int /* 声明指针变量 */ ip = &a /* 指针变量的存储地址 */ fmt.Printf("a 变量的地址是: %x\n", &a ) /* 指针变量的存储地址 */ fmt.Printf("ip 变量的存储地址: %x\n", ip ) /* 使用指针访问值 */ fmt.Printf("*ip 变量的值: %d\n", *ip ) }

空指针

当一个指针被定义后没有分配到任何变量时,它的值为 nil。

nil 指针也称为空指针。

nil在概念上和其它语言的null、None、nil、NULL一样,都指代零值或空值。

一个指针变量通常缩写为 ptr。

指针数组

var ptr [10]*int;

指向指针的指针

如果一个指针变量存放的又是另一个指针变量的地址,则称这个指针变量为指向指针的指针变量。

当定义一个指向指针的指针变量时,第一个指针存放第二个指针的地址,第二个指针存放变量的地址

指向指针的指针变量声明

var ptr **int;

访问指向指针的指针变量值需要使用两个 * 号

指针作为函数参数

Go 语言允许向函数传递指针,只需要在函数定义的参数上设置为指针类型即可。

package main import "fmt" func main() { /* 定义局部变量 */ var a int = 100 var b int= 200 fmt.Printf("交换前 a 的值 : %d\n", a ) fmt.Printf("交换前 b 的值 : %d\n", b ) /* 调用函数用于交换值 * &a 指向 a 变量的地址 * &b 指向 b 变量的地址 */ swap(&a, &b); fmt.Printf("交换后 a 的值 : %d\n", a ) fmt.Printf("交换后 b 的值 : %d\n", b ) } func swap(x *int, y *int) { var temp int temp = *x /* 保存 x 地址的值 */ *x = *y /* 将 y 赋值给 x */ *y = temp /* 将 temp 赋值给 y */ }

结构体

Go 语言中数组可以存储同一类型的数据,但在结构体中我们可以为不同项定义不同的数据类型。

结构体是由一系列具有相同类型或不同类型的数据构成的数据集合。

定义结构体

结构体定义需要使用 type 和 struct 语句。struct 语句定义一个新的数据类型,结构体有一个或多个成员。type 语句设定了结构体的名称。

type Person struct { name string; age int; score float32; }

使用上面定义的结构体声明变量

LiuXiang := Person{"Liuxiang", 18, 12.88}

匿名结构体

package main import "fmt" func main() { person:= struct { // 匿名结构体 name string age int }{name:"匿名",age:1} fmt.Println("person:",person) }

访问结构体成员

要访问结构体成员,需要使用点号 (.) 操作符,格式为:"结构体.成员名"。

package main import "fmt" type Books struct { title string author string subject string book_id int } func main() { var Book1 Books /* 声明 Book1 为 Books 类型 */ /* book 1 描述 */ Book1.title = "Go 语言" Book1.author = "www.w3cschool.cn" Book1.subject = "Go 语言教程" Book1.book_id = 6495407 /* 打印 Book1 信息 */ fmt.Printf( "Book 1 title : %s\n", Book1.title) fmt.Printf( "Book 1 author : %s\n", Book1.author) fmt.Printf( "Book 1 subject : %s\n", Book1.subject) fmt.Printf( "Book 1 book_id : %d\n", Book1.book_id) }

结构体作为函数参数

package main import "fmt" type Books struct { title string author string subject string book_id int } func main() { var Book1 Books /* 声明 Book1 为 Books 类型 */ Book1.title = "Go 语言" Book1.author = "www.w3cschool.cn" Book1.subject = "Go 语言教程" Book1.book_id = 6495407 printBook(Book1) } func printBook( book Books ) { fmt.Printf( "Book title : %s\n", book.title); fmt.Printf( "Book author : %s\n", book.author); fmt.Printf( "Book subject : %s\n", book.subject); fmt.Printf( "Book book_id : %d\n", book.book_id); }

结构体指针

定义指向结构体的指针

var Book_Ptr *Books

上面定义的指针变量可以存储结构体变量的地址。查看结构体变量地址,可以将 & 符号放置于结构体变量前

Book_Ptr = &Book1;

实例

package main import "fmt" type Books struct { title string author string subject string book_id int } func main() { var Book1 Books /* Declare Book1 of type Book */ var Book2 Books /* Declare Book2 of type Book */ /* book 1 描述 */ Book1.title = "Go 语言" Book1.author = "www.w3cschool.cn" Book1.subject = "Go 语言教程" Book1.book_id = 6495407 /* book 2 描述 */ Book2.title = "Python 教程" Book2.author = "www.w3cschool.cn" Book2.subject = "Python 语言教程" Book2.book_id = 6495700 /* 打印 Book1 信息 */ printBook(&Book1) /* 打印 Book2 信息 */ printBook(&Book2) } func printBook( book *Books ) { fmt.Printf( "Book title : %s\n", book.title); fmt.Printf( "Book author : %s\n", book.author); fmt.Printf( "Book subject : %s\n", book.subject); fmt.Printf( "Book book_id : %d\n", book.book_id); }

Note

结构体实例类似于Python中的字典 但又不一样

结构体通过实现方法可实现类似于面向对象编程语言中类功能

切片(Slice)

Go 语言切片是对数组的抽象。

Go 数组的长度不可改变,在特定场景中这样的集合就不太适用,Go中提供了一种灵活,功能强悍的内置类型切片("动态数组"),与数组相比切片的长度是不固定的,可以追加元素,在追加时可能使切片的容量增大。

定义切片

声明一个未指定大小的数组来定义切片 切片不需要说明长度

var identifier []type

使用make()函数来创建切片

//var slice1 []type = make([]type, len) //也可以简写为 //slice1 := make([]type, len) //可以指定容量 其中capacity为可选参数 这里len是数组的长度并且也是切片的初始长度 //make([]T, length, capacity)

切片初始化

直接初始化切片,[]表示是切片类型,{1,2,3}初始化值依次是1,2,3.其cap=len=3

s :=[] int {1,2,3}

初始化切片s,是数组arr的引用

s := arr[startIndex:endIndex]

通过内置函数make()初始化切片s,[]int 标识为其元素类型为int的切片

s :=make([]int,len,cap)

len() 和 cap() 函数

切片是可索引的,并且可以由 len() 方法获取长度。

切片提供了计算容量的方法 cap() 可以测量切片最长可以达到多少。

package main import "fmt" func main() { var numbers = make([]int,3,5) printSlice(numbers) } func printSlice(x []int){ fmt.Printf("len=%d cap=%d slice=%v\n",len(x),cap(x),x) }

空(nil)切片

一个切片在未初始化之前默认为 nil,长度为 0

var numbers []int

切片截取

通过设置下限及上限来设置截取切片 [lower-bound:upper-bound]

package main import "fmt" func main() { /* 创建切片 */ numbers := []int{0,1,2,3,4,5,6,7,8} printSlice(numbers) /* 打印原始切片 */ fmt.Println("numbers ==", numbers) /* 打印子切片从索引1(包含) 到索引4(不包含)*/ fmt.Println("numbers[1:4] ==", numbers[1:4]) /* 默认下限为 0*/ fmt.Println("numbers[:3] ==", numbers[:3]) /* 默认上限为 len(s)*/ fmt.Println("numbers[4:] ==", numbers[4:]) numbers1 := make([]int,0,5) printSlice(numbers1) /* 打印子切片从索引 0(包含) 到索引 2(不包含) */ number2 := numbers[:2] number2[0] = 10 //会同时改变numbers printSlice(number2) /* 打印子切片从索引 2(包含) 到索引 5(不包含) */ number3 := numbers[2:5] printSlice(number3) printSlice(numbers) } func printSlice(x []int){ fmt.Printf("len=%d cap=%d slice=%v\n",len(x),cap(x),x) }

append() 和 copy() 函数

如果想增加切片的容量,我们必须创建一个新的更大的切片并把原分片的内容都拷贝过来。

package main import "fmt" func main() { var numbers []int printSlice(numbers) /* 允许追加空切片 */ numbers = append(numbers, 0) printSlice(numbers) /* 向切片添加一个元素 */ numbers = append(numbers, 1) printSlice(numbers) /* 同时添加多个元素 */ numbers = append(numbers, 2,3,4) printSlice(numbers) /* 创建切片 numbers1 是之前切片的两倍容量*/ numbers1 := make([]int, len(numbers), (cap(numbers))*2) /* 拷贝 numbers 的内容到 numbers1 */ copy(numbers1,numbers) printSlice(numbers1) } func printSlice(x []int){ fmt.Printf("len=%d cap=%d slice=%v\n",len(x),cap(x),x) }

Note:

新切片的cap为原切片的cap-新切片的startIndex

当新的切片len比原来的切片cap大时 新切片的cap将增加一倍

startIndex不能大于endIndex Index不能大于长度

范围(Range)

Go 语言中 range 关键字用于for循环中迭代数组(array)、切片(slice)、链表(channel)或集合(map)的元素。在数组和切片中它返回元素的索引值,在集合中返回 key-value 对的 key 值。

package main import "fmt" func main() { //这是我们使用range去求一个slice的和。使用数组跟这个很类似 nums := []int{2, 3, 4} sum := 0 for _, num := range nums { sum += num } fmt.Println("sum:", sum) //在数组上使用range将传入index和值两个变量。上面那个例子我们不需要使用该元素的序号,所以我们使用空白符"_"省略了。有时侯我们确实需要知道它的索引。 for i, num := range nums { if num == 3 { fmt.Println("index:", i) } } //range也可以用在map的键值对上。 kvs := map[string]string{"a": "apple", "b": "banana"} for k, v := range kvs { fmt.Printf("%s -> %s\n", k, v) } //range也可以用来枚举Unicode字符串。第一个参数是字符的索引,第二个是字符(Unicode的值)本身。 for i, c := range "go" { fmt.Println(i, c) } }

Map(集合)

在Go语言里Map的概念相当于Python语言里的Dict

Map 是一种无序的键值对的集合。Map 最重要的一点是通过 key 来快速检索数据,key 类似于索引,指向数据的值。

Map 是一种集合,所以我们可以像迭代数组和切片那样迭代它。不过,Map 是无序的,我们无法决定它的返回顺序,这是因为 Map 是使用 hash 表来实现的。

定义 Map

可以使用内建函数 make 也可以使用 map 关键字来定义 Map 如果不初始化 map,那么就会创建一个 nil map。nil map 不能用来存放键值对

/* 使用map关键字 声明变量,默认 map 是 nil */ var map_variable map[key_data_type]value_data_type /* 使用 make 函数 */ map_variable = make(map[key_data_type]value_data_type)

实例

package main import "fmt" func main() { //I.先声明一个nil map 再使用make初始化然后赋值 var countryCapitalMap map[string]string /* 创建集合 */ countryCapitalMap = make(map[string]string) /* map 插入 key-value 对,各个国家对应的首都 */ countryCapitalMap["France"] = "Paris" countryCapitalMap["Italy"] = "Rome" //II. 直接使用make创建再赋值 //countryCapitalMap := make(map[string]string) //countryCapitalMap["France"] = "Paris" //countryCapitalMap["Italy"] = "Rome" //III. 直接使用map创建、初始化并赋值 //countryCapitalMap := map[string]string{ // "France": "Paris", // "Italy": "Rome", //} /* 使用 key 输出 map 值 */ for country := range countryCapitalMap { fmt.Println("Capital of",country,"is",countryCapitalMap[country]) } /* 查看元素在集合中是否存在 */ captial, ok := countryCapitalMap["United States"] /* 如果 ok 是 true, 则存在,否则不存在 */ if(ok){ fmt.Println("Capital of United States is", captial) }else { fmt.Println("Capital of United States is not present") } }

delete() 函数

package main import "fmt" func main() { /* 创建 map */ countryCapitalMap := map[string] string {"France":"Paris","Italy":"Rome","Japan":"Tokyo","India":"New Delhi"} fmt.Println("原始 map") /* 打印 map */ for country := range countryCapitalMap { fmt.Println("Capital of",country,"is",countryCapitalMap[country]) } /* 删除元素 */ delete(countryCapitalMap,"France"); fmt.Println("Entry for France is deleted") fmt.Println("删除元素后 map") /* 打印 map */ for country := range countryCapitalMap { fmt.Println("Capital of",country,"is",countryCapitalMap[country]) } }

Note

使用map关键字创建和声明的Map默认为nil 不能增加元素和赋值 需要使用make方法初始化之后使用

使用make方法创建和声明的Map是初始化的Map可以做赋值操作

递归函数

递归,就是在运行的过程中调用自己。

Go 语言支持递归。但我们在使用递归时,开发者需要设置退出条件,否则递归将陷入无限循环中。

递归函数对于解决数学上的问题是非常有用的,就像计算阶乘,生成斐波那契数列等。

func recursion() { recursion() /* 函数调用自身 */ } func main() { recursion() }

阶乘

package main import "fmt" func Factorial(x int) (result int) { if x == 0 { result = 1; } else { result = x * Factorial(x - 1); } return; } func main() { var i int = 15 fmt.Printf("%d 的阶乘是 %d\n", i, Factorial(i)) }

斐波那契数列

package main import "fmt" func fibonacci(n int) int {   if n < 2 {    return n   }   return fibonacci(n-2) + fibonacci(n-1) } func main() {     var i int     for i = 0; i < 10; i++ {        fmt.Printf("%d\t", fibonacci(i))     } }

类型转换

类型转换用于将一种数据类型的变量转换为另外一种类型的变量。

实例

package main import "fmt" func main() { var sum int = 17 var count int = 5 var mean float32 mean = float32(sum)/float32(count) fmt.Printf("mean 的值为: %f\n",mean) }

go 不支持隐式转换类型

如下是不能通过编译的

package main import "fmt" func main() { var a int64 = 3 var b int32 b = a fmt.Printf("b 为 : %d", b) }

改成 ​b = int32(a)就可以

package main import "fmt" func main() { var a int64 = 3 var b int32 b = int32(a) fmt.Printf("b 为 : %d", b) }

接口

Go 语言提供了另外一种数据类型即接口,它把所有的具有共性的方法定义在一起,任何其他类型只要实现了这些方法就是实现了这个接口。

接口是一组仅包含方法名、参数、返回值的未具体实现的方法的集合。

实例

package main import ( "fmt" ) type Phone interface { call() } type NokiaPhone struct { } func (nokiaPhone NokiaPhone) call() { fmt.Println("I am Nokia, I can call you!") } type IPhone struct { } func (iPhone IPhone) call() { fmt.Println("I am iPhone, I can call you!") } func main() { var phone Phone phone = new(NokiaPhone) phone.call() phone = new(IPhone) phone.call() }

Note

接口的概念类似于Python中的多态

如果实现了接口的所有方法,则认为实现了该接口,无需在该类型上显示的添加声明。

错误处理

Go 语言通过内置的错误接口提供了非常简单的错误处理机制。

error类型是一个接口类型,这是它的定义

type error interface { Error() string }

我们可以在编码中通过实现 error 接口类型来生成错误信息。函数通常在最后的返回值中返回错误信息。使用errors.New 可返回一个错误信息

func Sqrt(f float64) (float64, error) { if f < 0 { return 0, errors.New("math: square root of negative number") } // 实现 }

实例

package main import (     "fmt" ) // 定义一个 DivideError 结构 type DivideError struct {     dividee int     divider int } // 实现 `error` 接口 func (de *DivideError) Error() string {     strFormat := `     Cannot proceed, the divider is zero.     dividee: %d     divider: 0 `     return fmt.Sprintf(strFormat, de.dividee) } // 定义 `int` 类型除法运算的函数 func Divide(varDividee int, varDivider int) (result int, errorMsg string) {     if varDivider == 0 {             dData := DivideError{                     dividee: varDividee,                     divider: varDivider,             }             errorMsg = dData.Error()             return     } else {             return varDividee / varDivider, ""     } } func main() {     // 正常情况     if result, errorMsg := Divide(100, 10); errorMsg == "" {             fmt.Println("100/10 = ", result)     }     // 当被除数为零的时候会返回错误信息     if _, errorMsg := Divide(100, 0); errorMsg != "" {             fmt.Println("errorMsg is: ", errorMsg)     } }

sync包与锁

限制线程对变量的访问

sync 包里提供了互斥锁 Mutex 和读写锁 RWMutex 用于处理并发过程中可能出现同时两个或多个协程(或线程)读或写同一个变量的情况

锁是 sync 包中的核心,它主要有两个方法,分别是加锁(Lock)和解锁(Unlock)。

互斥锁 Mutex

在并发的情况下,多个线程或协程同时其修改一个变量,使用锁能保证在某一时间内,只有一个协程或线程修改这一变量。

不使用锁时,在并发的情况下可能无法得到想要的结果

package main import ( "fmt" "time" ) func main() { var a = 0 for i := 0; i < 1000; i++ { go func(idx int) { a += 1 fmt.Println(a) }(i) } time.Sleep(time.Second) }

上面的示例中出现的问题怎么解决呢?加一个互斥锁 Mutex 就可以了

package main import (     "fmt"     "sync"     "time" ) func main() {     var a = 0     var lock sync.Mutex     for i := 0; i < 1000; i++ {         go func(idx int) {             lock.Lock()             defer lock.Unlock()             a += 1             fmt.Printf("goroutine %d, a=%d\n", idx, a)         }(i)     }     // 等待 1s 结束主程序     // 确保所有协程执行完     time.Sleep(time.Second) }

读写锁

读写锁有如下四个方法:

  • 写操作的锁定和解锁分别是func (*RWMutex) Lock和func (*RWMutex) Unlock;
  • 读操作的锁定和解锁分别是func (*RWMutex) Rlock和func (*RWMutex) RUnlock。

 

读写锁的区别在于:

  • 当有一个 goroutine 获得写锁定,其它无论是读锁定还是写锁定都将阻塞直到写解锁;
  • 当有一个 goroutine 获得读锁定,其它读锁定仍然可以继续;
  • 当有一个或任意多个读锁定,写锁定将等待所有读锁定解锁之后才能够进行写锁定。

 

所以说这里的读锁定(RLock)目的其实是告诉写锁定,有很多协程或者进程正在读取数据,写操作需要等它们读(读解锁)完才能进行写(写锁定)。

 

我们可以将其总结为如下三条:

  • 同时只能有一个 goroutine 能够获得写锁定;
  • 同时可以有任意多个 gorouinte 获得读锁定;
  • 同时只能存在写锁定或读锁定(读和写互斥)。

package main import ( "fmt" "math/rand" "sync" ) var count int var rw sync.RWMutex func main() { ch := make(chan struct{}, 10) for i := 0; i < 5; i++ { go read(i, ch) } for i := 0; i < 5; i++ { go write(i, ch) } for i := 0; i < 10; i++ { <-ch } } func read(n int, ch chan struct{}) { rw.RLock() fmt.Printf("goroutine %d 进入读操作...\n", n) v := count fmt.Printf("goroutine %d 读取结束,值为:%d\n", n, v) rw.RUnlock() ch <- struct{}{} } func write(n int, ch chan struct{}) { rw.Lock() fmt.Printf("goroutine %d 进入写操作...\n", n) v := rand.Intn(1000) count = v fmt.Printf("goroutine %d 写入结束,新值为:%d\n", n, v) rw.Unlock() ch <- struct{}{} }

正则表达式

regexp包

Go语言通过 regexp 包为正则表达式提供了官方支持,其采用 RE2 语法,除了\c、\C外,Go语言和 Perl、Python 等语言的正则基本一致。

package main import ( "fmt" "regexp" ) func main() { buf := "abc azc a7c aac 888 a9c tac" //解析正则表达式,如果成功返回解释器 reg1 := regexp.MustCompile(`a.c`) if reg1 == nil { fmt.Println("regexp err") return } //根据规则提取关键信息 result1 := reg1.FindAllStringSubmatch(buf, -1) fmt.Println("result1 = ", result1) }

时间和日期

time 包提供了时间显示和测量等所用的函数

time

时间的获取

获取当前时间

package main import ( "fmt" "time" ) func main() { now := time.Now() //获取当前时间 fmt.Printf("current time:%v\n", now) year := now.Year() //年 month := now.Month() //月 day := now.Day() //日 hour := now.Hour() //小时 minute := now.Minute() //分钟 second := now.Second() //秒 fmt.Printf("%d-%02d-%02d %02d:%02d:%02d\n", year, month, day, hour, minute, second) }

获取时间戳

时间戳是自 1970 年 1 月 1 日(08:00:00GMT)至当前时间的总毫秒数,它也被称为 Unix 时间戳(UnixTimestamp)

package main import ( "fmt" "time" ) func main() { now := time.Now() //获取当前时间 timestamp1 := now.Unix() //时间戳 timestamp2 := now.UnixNano() //纳秒时间戳 fmt.Printf("现在的时间戳:%v\n", timestamp1) fmt.Printf("现在的纳秒时间戳:%v\n", timestamp2) }

时间戳转为时间格式

package main import ( "fmt" "time" ) func main() { now := time.Now() //获取当前时间 timestamp := now.Unix() //时间戳 timeObj := time.Unix(timestamp, 0) //将时间戳转为时间格式 fmt.Println(timeObj) year := timeObj.Year() //年 month := timeObj.Month() //月 day := timeObj.Day() //日 hour := timeObj.Hour() //小时 minute := timeObj.Minute() //分钟 second := timeObj.Second() //秒 fmt.Printf("%d-%02d-%02d %02d:%02d:%02d\n", year, month, day, hour, minute, second) }

获取当前是星期几

package main import ( "fmt" "time" ) func main() { //时间戳 t := time.Now() fmt.Println(t.Weekday().String()) }

时间操作函数

Add

Add 函数可以返回时间点 t + 时间间隔 d 的值

package main import ( "fmt" "time" ) func main() { now := time.Now() later := now.Add(time.Hour) // 当前时间加1小时后的时间 fmt.Println(later) }

Sub

两个时间之间的差值

Equal

两个时间是否相同

 Before

一个时间点是否在另一个时间点之前

After

一个时间点是否在另一个时间点之后

定时器

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

package main import ( "fmt" "time" ) func main() { ticker := time.Tick(time.Second) //定义一个1秒间隔的定时器 for i := range ticker { fmt.Println(i) //每秒都会执行的任务 } }

时间格式化

时间类型有一个自带的 Format 方法进行格式化,需要注意的是Go语言中格式化时间模板不是常见的Y-m-d H:M:S 而是使用Go语言的诞生时间 2006 年 1 月 2 号 15 点 04 分 05 秒。

package main import ( "fmt" "time" ) func main() { now := time.Now() // 格式化的模板为Go的出生时间2006年1月2号15点04分 Mon Jan // 24小时制 fmt.Println(now.Format("2006-01-02 15:04:05.000 Mon Jan")) // 12小时制 fmt.Println(now.Format("2006-01-02 03:04:05.000 PM Mon Jan")) fmt.Println(now.Format("2006/01/02 15:04")) fmt.Println(now.Format("15:04 2006/01/02")) fmt.Println(now.Format("2006/01/02")) }

解析字符串格式的时间

Parse 函数可以解析一个格式化的时间字符串并返回它代表的时间。

func Parse(layout, value string) (Time, error)

与 Parse 函数类似的还有 ParseInLocation 函数。

func ParseInLocation(layout, value string, loc *Location) (Time, error)

ParseInLocation 与 Parse 函数类似,但有两个重要的不同之处:

  • 第一,当缺少时区信息时,Parse 将时间解释为 UTC 时间,而 ParseInLocation 将返回值的 Location 设置为 loc;
  • 第二,当时间字符串提供了时区偏移量信息时,Parse 会尝试去匹配本地时区,而 ParseInLocation 会去匹配 loc。

OS

os 包中提供了操作系统函数的接口,是一个比较重要的包。顾名思义,os 包的作用主要是在服务器上进行系统的基本操作,如文件操作、目录操作、执行命令、信号与中断、进程、系统状态等等。

在 os 包下,有 exec,signal,user 三个子包

os/exec 执行外部命令

os/user 获取当前用户信息

os/signal 信号处理

命令行参数解析

在Go语言中的 flag 包中,提供了命令行参数解析的功能。

mod包依赖管理工具

go module 是Go语言从 1.11 版本之后官方推出的版本管理工具,并且从 Go1.13 版本开始,go module 成为了Go语言默认的依赖管理工具。

Modules 官方定义为:

Modules 是相关 Go 包的集合,是源代码交换和版本控制的单元。Go语言命令直接支持使用 Modules,包括记录和解析对其他模块的依赖性,Modules 替换旧的基于 GOPATH 的方法,来指定使用哪些源文件。

文件处理

JSON 文件

文本文件

写纯文本文件

package main import ( "bufio" "fmt" "os" ) func main() { //创建一个新文件,写入内容 filePath := "./output.txt" file, err := os.OpenFile(filePath, os.O_WRONLY|os.O_CREATE, 0666) if err != nil { fmt.Printf("打开文件错误= %v \n", err) return } //及时关闭 defer file.Close() //写入内容 str := "http://c.biancheng.net/golang/\n" // \n\r表示换行 txt文件要看到换行效果要用 \r\n //写入时,使用带缓存的 *Writer writer := bufio.NewWriter(file) for i := 0; i < 3; i++ { writer.WriteString(str) } //因为 writer 是带缓存的,因此在调用 WriterString 方法时,内容是先写入缓存的 //所以要调用 flush方法,将缓存的数据真正写入到文件中。 writer.Flush() }

 

读纯文本文件

package main import ( "bufio" "fmt" "io" "os" ) func main() { //打开文件 file, err := os.Open("./output.txt") if err != nil { fmt.Println("文件打开失败 = ", err) } //及时关闭 file 句柄,否则会有内存泄漏 defer file.Close() //创建一个 *Reader , 是带缓冲的 reader := bufio.NewReader(file) for { str, err := reader.ReadString('\n') //读到一个换行就结束 if err == io.EOF { //io.EOF 表示文件的末尾 break } fmt.Print(str) } fmt.Println("文件读取结束...") }

数据库操作

安装依赖文件

go get github.com/go-sql-driver/mysql go get github.com/jmoiron/sqlx

链接mysql

package main import ( "fmt" _ "github.com/go-sql-driver/mysql" "github.com/jmoiron/sqlx" ) func connectMysql() (*sqlx.DB) { DB, err := sqlx.Open("mysql", "root:admin001@tcp(127.0.0.1:3306)/aliyun") if err != nil { fmt.Printf("mysql connect failed, detail is [%v]", err.Error()) } return DB } func queryData(Db * sqlx.DB) { rows, err := Db.Query("select * from tb_book") if err != nil { fmt.Printf("data insert faied, error:[%v]", err.Error()) return } for rows.Next(){ var id int var title string var created_at string var updated_at string err = rows.Scan(&id,&title,&created_at,&updated_at) if err != nil { fmt.Println(err.Error()) } fmt.Println(id,title,created_at,updated_at) } } func addRecord(Db *sqlx.DB) { for i:=0; i<2; i++ { result, err := Db.Exec("insert into userinfo values(?,?,?,?,?,?)",0, "2019-07-06 11:45:20", "johny", "123456", "技术部", "123456@163.com") if err != nil { fmt.Printf("data insert faied, error:[%v]", err.Error()) return } id, _ := result.LastInsertId() fmt.Printf("insert success, last id:[%d]\n", id) } } func updateRecord(Db *sqlx.DB){ //更新uid=1的username result, err := Db.Exec("update userinfo set username = 'anson' where uid = 1") if err != nil { fmt.Printf("update faied, error:[%v]", err.Error()) return } num, _ := result.RowsAffected() fmt.Printf("update success, affected rows:[%d]\n", num) } func deleteRecord(Db *sqlx.DB){ //删除uid=2的数据 result, err := Db.Exec("delete from userinfo where uid = 2") if err != nil { fmt.Printf("delete faied, error:[%v]", err.Error()) return } num, _ := result.RowsAffected() fmt.Printf("delete success, affected rows:[%d]\n", num) } func main() { var Db *sqlx.DB = connectMysql() defer Db.Close() queryData(Db) //addRecord(Db) //updateRecord(Db) //deleteRecord(Db) }

 

 

 

 

 

 

 

 

 

 

 

 

格式化输出变量

查看变量字节大小

查看变量类型

查看整形变量对应的unicode字符

数据类型

常量

整形的类型

字符串和字符

值类型和引用类型

流程控制

条件语句

if

if...else

switch

select

循环语句

for循环

无限循环

控制语句

break 语句

continue 语句

goto 语句

defer

顺序控制

分支控制

循环控制

函数

函数参数

值传递

引用传递

函数用法

函数作为值

闭包

方法

数组

声明数组

初始化数组

数组完整操作

向函数传递数组

指针

声明指针

使用指针

空指针

指针数组

指向指针的指针

指针作为函数参数

结构体

定义结构体

匿名结构体

访问结构体成员

结构体作为函数参数

结构体指针

切片(Slice)

定义切片

切片初始化

len() 和 cap() 函数

空(nil)切片

切片截取

append() 和 copy() 函数

范围(Range)

Map(集合)

定义 Map

实例

delete() 函数

递归函数

阶乘

斐波那契数列

类型转换

实例

接口

实例

错误处理

实例

sync包与锁

互斥锁 Mutex

上面的示例中出现的问题怎么解决呢?加一个互斥锁 Mutex 就可以了

读写锁

正则表达式

regexp包

时间和日期

time

时间的获取

获取当前时间

时间戳转为时间格式

时间操作函数

定时器

时间格式化

解析字符串格式的时间

OS

命令行参数解析

mod包依赖管理工具

文件处理

JSON 文件

文本文件

写纯文本文件

数据库操作

链接mysql

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

我变了_我没变

随意 。。。

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值