定义变量
变量类型写在变量名之后
var a int = 2
var a, b int = 3, 4
// 使用 ":=" 来代替var,这个语法只能在func内使用,在保内变量是不能使用的
// 可以不声明参数类型,编译器会自动判断类型
a, b, c, d := 1, 2, true, "def"
// 在包内部可以使用var批量定义
var (
aa = 3
bb = "ss"
)
内建变量类型
bool、string
(u)int,(u)int8,(u)int16,(u)int32,(u)int64,uintptr:指针; u代表无符号,正数
byte:8位, rune:32位,rune等同于char
float32, float64, complex64, complex128;complex为复数
// 十进制
i1 := 10
fmt.Printf("%d\n", i1)
fmt.Printf("%b\n", i1) // 十进制转二进制
fmt.Printf("%o\n", i1) // 十进制转八进制
fmt.Printf("%x\n", i1) // 十进制转十六进制
fmt.Printf("%T\n", i1) // 查看类型
// 强制转换
i2 := int8(i1)
i3 := int16(i1)
fmt.Printf("%T\n", i2)
fmt.Printf("%T\n", i3)
强制类型转换
func triangle() {
var a, b int = 3, 4
var c int
// Sqrt的参数为float64
c = int(math.Sqrt(float64(a*a + b*b)))
fmt.Println(c)
}
常量
使用const定义常量,go语言中常量不使用全部大写
func consts() {
const fileName = "a.txt"
const a, b = 3, 4
var c int
c = int(math.Sqrt(a*a + b*b))
fmt.Println(c)
}
枚举常量
iota是自增值,
// 如果后面没有赋值,则都是第一个元素的值
const(
n1 = 100
n2
n3
)
func enums() {
const (
java = iota
php
python
golang
)
const (
b = 1 << (10 * iota)
kb
mb
gb
tb
pb
)
fmt.Println(java, php, python, golang)
// 0 1 2 3
fmt.Println(b, kb, mb, gb, tb, pb)
// 1 1024 1048576 1073741824 1099511627776 1125899906842624
}
判断
go语言中是没有括号的
package main
import (
"fmt"
"io/ioutil"
)
func main() {
const filename = "a.txt"
contents, err := ioutil.ReadFile(filename)
// go语言中是没有括号的
if err != nil {
fmt.Println(err)
} else {
fmt.Printf("%s\n", contents)
}
// 也可以一起写,弊端就是返回的参数只能在if中使用,因为他的作用域就是if判断
if contents, err := ioutil.ReadFile(filename); err != nil {
fmt.Println(err)
} else {
fmt.Printf("%s\n", contents)
}
}
switch
不需要break,默认就有
func grade(score int) string {
g := ""
switch {
case score < 60:
g = "F"
case score < 80:
g = "C"
case score < 90:
g = "B"
fallthrough // 如果是用该函数,则满足score<90以后会继续执行下一个case,也就是score<=100
case score <= 100:
g = "A"
default:
// panic会中断执行,进行报错
panic(fmt.Sprintf("Wrong score:%d", score))
}
return g
}
for
go语言中没有括号,并且可以省略初始条件、递增条件
func convertToBin(n int) string {
res := ""
for ; n > 0; n /= 2 {
lsb := n % 2
res = strconv.Itoa(lsb) + res
}
return res
}
func readFile(filename string) {
file, err := os.Open(filename)
if err != nil {
panic(err)
}
scanner := bufio.NewScanner(file)
for scanner.Scan() {
fmt.Println(scanner.Text())
}
}
func forever() {
// 什么条件都没有就是死循环
for {
fmt.Println("死循环")
}
}
for i := 0; i < 10; i++ {
if i == 5 {
// 跳出for循环
break
}
fmt.Println(i)
}
for i := 0; i < 10; i++ {
if i == 5 {
// 跳过本次循环
continue
}
fmt.Println(i)
}
函数
go是函数式编程,可以返回多个值。通常情况下,第一个值是返回值,第二个值是错误
没有默认参数,可选参数,可以直接写匿名函数传递过去
package main
import (
"fmt"
"math"
"reflect"
"runtime"
)
func main() {
i3, err := eval(1, 2, "1+")
if err != nil {
fmt.Println(err)
}
fmt.Println(i3)
i, i2 := div(13, 3)
fmt.Println(i, i2)
q, r := div1(13, 3)
fmt.Println(q, r)
x, _ := div1(13, 3)
fmt.Println(x)
fmt.Println(apply(func(a int, b int) int {
return int(math.Pow(float64(a), float64(b)))
}, 3, 4))
}
func eval(a, b int, op string) (int, error) {
switch op {
case "+":
return a + b, nil
case "-":
return a - b, nil
case "*":
return a * b, nil
case "/":
return a / b, nil
default:
return 0, fmt.Errorf("unsupported operation: &s" + op)
}
}
func div(a, b int) (int, int) {
return a / b, a % b
}
func div1(a, b int) (q, r int) {
q = a / b
r = a % b
return
}
func apply(op func(int, int) int, a, b int) int {
p := reflect.ValueOf(op).Pointer()
opName := runtime.FuncForPC(p).Name()
fmt.Printf("Calling function %s with atgs (%d, %d)\n", opName, a, b)
return op(a, b)
}
指针、数传递
“”代表指针
Go语言只有值传递一种方式
var a int = 1
&a 代表内存地址
pa int 引用a
a, b := 1, 2
swap(&a, &b)
fmt.Println(a, b)
func swap(a, b *int) {
*a, *b = *b, *a
}
数组
定义数组可以使用var来定义,也可以是用:=来定义。如果是用:=来定义后面需要跟上具体设置的值。
也可以不写前面的数组大小,但是需要把具体数值使用[…]来代替
数组是值类型,会拷贝数组,在调用方法中修改值,原始值是不会被修改的,可以是用指针,将内存地址传递过去修改值
package main
import "fmt"
func main() {
var arr1 [5]int
arr2 := [2]int{1, 2}
arr3 := [...]int{2, 3, 4}
fmt.Println(arr1, arr2, arr3)
// 二维数组
var gaid [4][5]int
fmt.Println(gaid)
printArray(arr3)
printArray1(&arr3)
}
func printArray(arr [3]int) {
// 循环数组
for k, v := range arr {
fmt.Println(k, v)
}
arr[1] = 200
}
func printArray1(arr *[3]int) {
// 循环数组
for k, v := range arr {
fmt.Println(k, v)
}
arr[1] = 200
}
切片(slice)
Slice本身没有数据,是对底层array的一个view
package main
import "fmt"
func main() {
arr := [...]int{0, 1, 2, 3, 4, 5, 6, 7, 8}
fmt.Println("arr[2:6]", arr[2:6])
fmt.Println("arr[:6]", arr[:6])
s1 := arr[2:]
fmt.Println("s1", s1)
s2 := arr[:]
fmt.Println("s2", s2)
fmt.Println("After updateSlice(s1)")
updateSlice(s1)
fmt.Println(s1)
fmt.Println(arr)
fmt.Println("After updateSlice(s2)")
updateSlice(s2)
fmt.Println(s2)
fmt.Println(arr)
fmt.Println("Reslice")
s2 = s2[:5]
fmt.Println(s2)
s2 = s2[2:]
fmt.Println(s2)
}
func updateSlice(s []int) {
s[0] = 100
}
运行结果
arr[2:6] [2 3 4 5]
arr[:6] [0 1 2 3 4 5]
s1 [2 3 4 5 6 7 8]
s2 [0 1 2 3 4 5 6 7 8]
After updateSlice(s1)
[100 3 4 5 6 7 8]
[0 1 100 3 4 5 6 7 8]
After updateSlice(s2)
[100 1 100 3 4 5 6 7 8]
[100 1 100 3 4 5 6 7 8]
Reslice
[100 1 100 3 4]
[100 3 4]
slice扩展
slice可以向后扩展,不可以向前扩展。
s[i]不可以超越len[s],向后扩展不可以超越底层数组cap(s)
append添加的时候如果超过原始数组的大小,继续append的话就无法匹配到原始数组
fmt.Println("Extending slice")
arr1 := [...]int{0, 1, 2, 3, 4, 5, 6, 7}
s11 := arr1[2:6]
fmt.Printf("s11=%v, len(s11)=%d, cap(s11)=%d\n",
s11, len(s11), cap(s11))
s22 := s11[3:5]
fmt.Printf("s22=%v, len(s22)=%d, cap(s22)=%d\n",
s22, len(s22), cap(s22))
s3 := append(s22, 10)
s4 := append(s3, 11)
s5 := append(s4, 12)
fmt.Println(s3, s4, s5)
fmt.Println(arr1)
结果
Extending slice
s11=[2 3 4 5], len(s11)=4, cap(s11)=6
s22=[5 6], len(s22)=2, cap(s22)=3
[5 6 10] [5 6 10 11] [5 6 10 11 12]
[0 1 2 3 4 5 6 10]
package main
import "fmt"
func main() {
// Zero value for slice is nil
var s []int
for i := 0; i < 100; i++ {
printSlice(s)
s = append(s, 2*i+1)
}
fmt.Println(s)
s1 := []int{2, 4, 6, 8}
printSlice(s1)
// 创建空slice
s2 := make([]int, 16)
s3 := make([]int, 10, 32)
printSlice(s2)
printSlice(s3)
fmt.Println("Copying slice")
copy(s2, s1)
printSlice(s2)
fmt.Println("Deleting elements from slice")
s2 = append(s2[:3], s2[4:]...)
printSlice(s2)
fmt.Println("Popping from front")
front := s2[0]
fmt.Println(front)
s2 = s2[1:]
printSlice(s2)
fmt.Println("Popping from back")
tail := s2[len(s2)-1]
fmt.Println(tail)
s2 = s2[:len(s2)-1]
printSlice(s2)
}
func printSlice(s []int) {
fmt.Printf("%v, len=%d, cap=%d\n", s, len(s), cap(s))
}
结果
[1 3 5 7 9 11 13 15 17 19 21 23 25 27 29 31 33 35 37 39 41 43 45 47 49 51 53 55 57 59 61 63 65 67 69 71 73 75 77 79 81 83 85 87 89 91 93 95 97 99 101 103 105 107 109 111 113 115 117 119 121 123 125 127 129 131 133 135 137 139 141 143 145 147 149 151 153 155 157 159 161 163 165 167 169 171 173 175 177 179 181 183 185 187 189 191 193 195 197 199]
[2 4 6 8], len=4, cap=4
[0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0], len=16, cap=16
[0 0 0 0 0 0 0 0 0 0], len=10, cap=32
Copying slice
[2 4 6 8 0 0 0 0 0 0 0 0 0 0 0 0], len=16, cap=16
Deleting elements from slice
[2 4 6 0 0 0 0 0 0 0 0 0 0 0 0], len=15, cap=16
Popping from front
2
[4 6 0 0 0 0 0 0 0 0 0 0 0 0], len=14, cap=15
Popping from back
0
[4 6 0 0 0 0 0 0 0 0 0 0 0], len=13, cap=15
Map
创建使用:make(map[string]in)
获取元素:m[key]
map使用哈希表,必须可以比较相等
除了slice,map,function的内建类型都可以作为key
struct类型不包含上述字段,也可作为key
package main
import "fmt"
func main() {
m := map[string]string{
"name": "李四",
"course": "golang",
"site": "imooc",
"quality": "notbad",
}
m2 := make(map[string]int)
var m3 map[string]int
fmt.Println(m, m2, m3)
fmt.Println("Traversing map")
for k, s := range m {
fmt.Println(k, s)
}
fmt.Println("Getting values")
courseName := m["course"]
fmt.Println(courseName)
// 当值不存在时不会报错,打印为空
coursName := m["cours"]
fmt.Println(coursName)
// 当值不存在时不会报错,打印为空,接收第二个参数
coursName1, ok := m["cours"]
fmt.Println(coursName1, ok)
fmt.Println("Deleting values")
name, ok := m["name"]
fmt.Println(name, ok)
delete(m, "name")
name, ok = m["name"]
fmt.Println(name, ok)
}
结果
map[course:golang name:李四 quality:notbad site:imooc] map[] map[]
Traversing map
quality notbad
name 李四
course golang
site imooc
Getting values
golang
false
Deleting values
李四 true
false
Map实例:寻找最长不含有重复字符的字串
package main
import "fmt"
func main() {
fmt.Println(
lengthOfNonRepeatingSubStr("abcabcbb"),
lengthOfNonRepeatingSubStr("aaaaaaa"),
lengthOfNonRepeatingSubStr("123456"),
lengthOfNonRepeatingSubStr("123456pwwkew"),
lengthOfNonRepeatingSubStr("121234w"),
)
}
func lengthOfNonRepeatingSubStr(s string) int {
lastOccurred := make(map[byte]int)
start := 0
maxLength := 0
for i, ch := range []byte(s) {
if lastI, ok := lastOccurred[ch]; ok && lastI >= start {
start = lastOccurred[ch] + 1
}
if i-start+1 > maxLength {
maxLength = i - start + 1
}
lastOccurred[ch] = i
}
return maxLength
}
结果
3 1 6 8 5
rune:go语言的char
使用range遍历pos、rune
使用utf8.RuneCountInString获得字符数量
使用len获得字节长度
使用[]byte获得字节
package main
import (
"fmt"
"unicode/utf8"
)
func main() {
s := "a一个字符串!."
fmt.Println(len(s))
fmt.Printf("%s\n", []byte(s))
for _, b := range []byte(s) {
fmt.Printf("%X ", b)
}
fmt.Println()
for i, ch := range s {
fmt.Printf("(%d %X) ", i, ch)
}
fmt.Println()
fmt.Println("Rune count:", utf8.RuneCountInString(s))
bytes := []byte(s)
for len(bytes) > 0 {
ch, size := utf8.DecodeRune(bytes)
bytes = bytes[size:]
fmt.Printf("%c ", ch)
}
fmt.Println()
for i, ch := range []rune(s) {
fmt.Printf("(%d %c) ", i, ch)
}
fmt.Println()
}
结果
18
a一个字符串!.
61 E4 B8 80 E4 B8 AA E5 AD 97 E7 AC A6 E4 B8 B2 21 2E
(0 61) (1 4E00) (4 4E2A) (7 5B57) (10 7B26) (13 4E32) (16 21) (17 2E)
Rune count: 8
a 一 个 字 符 串 ! .
(0 a) (1 一) (2 个) (3 字) (4 符) (5 串) (6 !) (7 .)
面向对象
go语言仅支持封装,不支持继承和多态,没有构造函数
package main
import "fmt"
type treeNode struct {
value int
left, right *treeNode
}
func main() {
var root treeNode
root = treeNode{value: 3}
root.left = &treeNode{}
root.right = &treeNode{5, nil, nil}
root.right.left = new(treeNode)
root.left.right = createNode(2)
root.right.left.setValue(4)
root.traverse()
//root.print()
fmt.Println()
}
func (node *treeNode) traverse() {
if node == nil {
return
}
node.left.traverse()
node.print()
node.right.traverse()
}
func createNode(value int) *treeNode {
return &treeNode{value: value}
}
/**
和别的语言语法不同,接收的参数在前面,不过也可以写到方法名括号里面。如:
func print(node treeNode)
如果是上面这种写法的话,调用的时候就是print(参数)
*/
func (node treeNode) print() {
fmt.Print(node.value)
}
func (node *treeNode) setValue(value int) {
node.value = value
}
值接受者 vs 指针接收者
要改变内容必须使用指针接收者
结构过大也考虑使用指针接收者
一致性:如果有指针接收者,最好都是指针接收者
值接受者是go语言特有
封装
名字一般使用CamelCase
首字母大写:public
首字母小写:private
这里的public、private针对包来说
包
为结构定义的方法必须放在同一个保内
可以是不同文件
go语言没有继承,那如何扩充系统类型
定义别名:最简单
使用组合:最常用
使用内嵌:省下许多代码
依赖管理
依赖管理的三个阶段:GOPATH、GOVENDOR、go mod
go mod由go命令统一的管理,用户不必关心目录结构
go mod tidy // 清洁go.sum
// 拉取依赖
go get -u go.uber.org/zap
go get -u go.uber.org/zap@v1.11 // 获取指定版本的插件
迁移旧项目至go.mod
1. 创建go.mod文件
go mod init ‘包名’
2. 编译
go build ./...
接口
go语言是面向接口的语言,go语言中不需要实现就可以使用接口。严格说go属于结构化类型系统,类似duck typing。接口由使用者定义
duck typing:描述事物的外部行为而非内部结构
函数式编程vs函数指针
函数是一等公民:参数、变量、返回值都可以是函数
高阶函数
函数->闭包
“正统”函数式编程
不可变性:不能有状态,只有常量和函数
函数只能有一个参数
package main
import (
"bufio"
"fmt"
"io"
"strings"
)
// 斐波那契
func fibonacci() intGen {
a, b := 0, 1
return func() int {
a, b = b, a+b
return a
}
}
type intGen func() int
func (g intGen) Read(p []byte) (n int, err error) {
next := g()
if next > 10000 {
return 0, io.EOF
}
s := fmt.Sprintf("%d\n", next)
return strings.NewReader(s).Read(p)
}
func printFileContents(reader io.Reader) {
scanner := bufio.NewScanner(reader)
for scanner.Scan() {
fmt.Println(scanner.Text())
}
}
func main() {
f := fibonacci()
printFileContents(f)
}
资源管理与出错处理
资源管理:比如说打开文件、关闭文件
defer调用来实现资源管理,确保调用结束时发生
测试
传统测试vs表格驱动测试
传统测试:
- 测试数据和测试逻辑混在一起
- 出错信息不明企鹅
- 一旦一个数据出错测试全部结束
表格驱动测试:
- 分离的测试数据和测试逻辑
- 明确的出错信息
- 可以部分失败
- go语言的语法是的我们更容易实现表格驱动测试
表格驱动测试demo
package main
import "testing"
func TestTriangle(t *testing.T) {
tests := []struct{ a, b, c int }{
{3, 4, 5},
{8, 15, 17},
{10, 24, 26},
{12, 35, 37},
{30000, 40000, 50000},
}
for _, tt := range tests {
if actual := calcTriangle(tt.a, tt.b); actual != tt.c {
t.Errorf("calcTriangle(%d, %d); "+
"got %d; expected %d",
tt.a, tt.b, actual, tt.c)
}
}
}