1. 基本数据类型

  • bool
  • string
  • int、int8、int16、int32、int64
  • uint、uint8、uint16、uini16、uint32、uint64、uintptr
    • 其中int8、int16、int32、int64 大小不一的有符号和无符号整数类型
    • int和uint表示特定CPU平台的字长,其中 int表示有符号整数uint表示无符号整数
    • uintptr表示无符号整数类型,没有指定具体的bit大小,但是可以容纳指针。一般在底层编程时才需要,特别是在 GO语言和C语言函数库或者操作系统接口相交互的地方 (指针)
  • byte : uint8的别名
  • rune : int32的别名,代表一个Unicode码 (char)类型
  • float32、float64
  • complex64、complex128 数学中的复数

Go语言中有符号整数采用 2 的补码形式表示,也就是最高 bit 位用来表示符号位,一个 n-bit 的有符号数的取值范围是从 -2(n-1) 到 2(n-1)-1。无符号整数的所有 bit 位都用于表示非负数,取值范围是 0 到 2n-1。例如,int8 类型整数的取值范围是从 -128 到 127,而 uint8 类型整数的取值范围是从 0 到 255。

1.1 变量申明

变量声明时,默认值 int = 0,float = 0.0,bool = false,string = 空字符串,指针为nil等,所有的内存在go中都经过初始化,命名遵循驼峰命名法

var 变量名 变量类型 //单个命名

var ( 变量名 变量类型 ) //批量命名

名称 := 表达式 //简短格式命名

//单个命名
var numShips bool
//批量命名
var (a intb stringc []float32d func() boole struct{x int}
)
//简短模式
i, j := 0, 1

注意简单模式的缺点:

  • 定义变量,同时显示初始化
  • 不能提供数据类型
  • 只能在函数内部使用

1.2 变量初始化

var 变量名 类型 = 表达式

var hp int = 100 //因为右边的表达式能够确定左边的类型,所以可以编译器推导后可以确定
var hp = 100//下面是编译器根据右值推导后的初始化例子
var attack = 40
var defence = 20
var damageRate float32 = 0.17 //由于使用了小数,编译器会尽量提高精度避免精度损失,如果这里不指定,编译器会指定为float64,我们不需要这么长的精度,所以强制指定为32
var damage = float32(attack-defence) * damageRate

注意:简短写法,左边变量至少有一个是新出现的变量名称,即便其他变量可以能重复了,都不会报错

//简短写法
var hp int // 声明 hp 变量
hp := 10 // 再次声明并赋值,就会报错 no new variables on left side of :=
conn, err := net.Dial("tcp","127.0.0.1:8080") // Dial函数会返回两个对象,这里就可以使用简短写法,如果使用命名的方式,就是下面的格式了
var conn net.Conn
var err error
conn, err = net.Dial("tcp", "127.0.0.1:8080")

1.3 变量交换

编程最简单的算法之一,莫过于变量交换。交换变量的常见算法需要一个中间变量进行变量的临时保存。用传统方法编写变量交换代码如下:

func swap() {//变量交换var a = 100var b = 200var t intt = aa = bb = tfmt.Println(a, b)
}

在计算机刚发明时,内存非常“精贵”。这种变量交换往往是非常奢侈的。于是计算机“大牛”发明了一些算法来避免使用中间变量:

var a int = 100
var b int = 200
a = a ^ b
b = b ^ a
a = a ^ b
fmt.Println(a, b)

到了Go语言时,内存不再是紧缺资源,而且写法可以更简单。使用 Go 的“多重赋值”特性,可以轻松完成变量交换的任务: 多重赋值时,变量的左值和右值按从左到右的顺序赋值。

var a int = 100
var b int = 200
b, a = a, b
fmt.Println(a, b)

多重赋值在Go语言的错误处理和函数返回值中会大量地使用。例如使用Go语言进行排序时就需要使用交换,代码如下:

type IntSlice []int
func (p IntSlice) Len() int           { return len(p) }
func (p IntSlice) Less(i, j int) bool { return p[i] < p[j] }
func (p IntSlice) Swap(i, j int)      { p[i], p[j] = p[j], p[i] }

1.4 匿名变量

匿名变量的特点:_ 下划线来作为标识符,匿名变量不占用内存,不会分配内存空间

func GetData() (int, int) {return 100, 200
}
func main(){a, _ := GetData()_, b := GetData()fmt.Println(a, b)
}

1.5 变量作用域

Go语言会在编译时检查每个变量是否使用过,一旦出现未使用的变量,就会报编译错误

  • 函数内定义的为局部变量
  • 函数外定义的为全局变量:在一个源文件中定义,就可以在所有的源文件中使用,不包含这个变量的源文件需要使用 import 关键字进行导入,如果想在外部包中使用全局变量,首字母必须大写
  • 函数定义中的变量称为形式参数
func varF() {var a = 3var b = 4c := a + bfmt.Printf("a = %d, b = %d, c = %d", a, b, c)
}
import ("fmt"
)var c intfunc main() {var a, b inta = 3b = 4c = a + bfmt.Printf("a = %d, b = %d, c = %d", a, b, c)
}
// 这里的 a,b就叫做形式参数
func sum(a, b int) int {fmt.Printf("sum() 函数中 a = %d\n", a)fmt.Printf("sum() 函数中 b = %d\n", b)num := a + breturn num
}

1.6 浮点数

一个 float32 类型的浮点数可以提供大约 6 个十进制数的精度,而 float64 则可以提供约 15 个十进制数的精度,通常应该优先使用 float64 类型,因为 float32 类型的累计计算误差很容易扩散,并且 float32 能精确表示的正整数并不是很大。

浮点数在声明的时候可以只写整数部分或者小数部分,像下面这样:

const e = .71828 // 0.71828
const f = 1.     // 1

很小或很大的数最好用科学计数法书写,通过 e 或 E 来指定指数部分:

const Avogadro = 6.02214129e23  // 阿伏伽德罗常数
const Planck   = 6.62606957e-34 // 普朗克常数

用 Printf 函数打印浮点数时可以使用“%f”来控制保留几位小数

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

1.7 语言复数

在计算机中,复数是由两个浮点数表示的,其中一个表示实部(real),一个表示虚部(imag)

Go语言中复数的类型有两种,分别是 complex128(64 位实数和虚数)和 complex64(32 位实数和虚数),其中 complex128 为复数的默认类型。

var x complex128 = complex(1, 2) // 1+2i
var y complex128 = complex(3, 4) // 3+4i
fmt.Println(x*y)                 // "(-5+10i)"
fmt.Println(real(x*y))           // "-5"
fmt.Println(imag(x*y))           // "10"

Go语言内置的 math/cmplx 包中提供了很多操作复数的公共方法,实际操作中建议大家使用复数默认的 complex128 类型,因为这些内置的包中都使用 complex128 类型作为参数。

1.8 字符串

string在go的底层使用 byte数组实现,中文字符在 unicode 编码中占 3个字节,utf-8编码中占3-4个字节,golang默认使用utf-8进行编码。byte用于处理 ASCII 码的范围,rune可以输出UTF-8编码的范围

rune类型一般用来区字符串和整数值的,用于处理 unicode或utf-8字符,rune等同于int32,占4个字节;在go中一般代表一个 UTF-8 字符

编码:https://www.liaoxuefeng.com/wiki/1016959663602400/1017075323632896

func main() {s := "Yes我爱祖国!"fmt.Printf("%x\n", []byte(s))// utf-8编码中,一个中文字符串占三个字节for _, ch := range []byte(s) {fmt.Printf("%X ", ch)}fmt.Println()//可以看到每个字符的分布情况,数据打印出来的会是一个unicode编码for i, ch := range s {fmt.Printf("(%d, %X)", i, ch)}fmt.Println()//可以计算出字符串中 rune的个数fmt.Println("Rune count:", utf8.RuneCountInString(s))//数组的转换,将字节数组转换为 rune类型bytes := []byte(s)for len(bytes) > 0{//将数组进行解码成 rune类型,返回字符以及字符的长度ch , size := utf8.DecodeRune(bytes)bytes = bytes[size:]fmt.Printf("%c ", ch)}fmt.Println()
}

2. 强制类型转换

在 go 中所有数据都需要对应上,如果不等价,那么需要通过 float64() 构造函数的方式进行强制转换

func trac(l int, k int) {//Sqrt需要的是 float64类型,通过 float64() 可以进行强制数据转换var c int = int(math.Sqrt(float64(l * l + k * k)))fmt.Println(c)
}

3. 常量

const 名称 = 数据

const (名称 = 数据)

常量经过定义之后就不能再进行修改了

const fileName = "abc.txt"
func consts() {const a, b = 1, "123"//常量经过定义就不能再修改了a = 2fmt.Println(a, b)fmt.Println(fileName)
}

3.1 枚举

func enums() {const (java = iota  // iota表示这一组枚举使用自增方式进行 0,1,2,3_  //可以直接跳过 1 这个索引golangpythonc)fmt.Println(java, golang, python, c)const (b = 1 << (10 * iota)   //这里后续都会使用这个公式进行计算,打印出单位值kbmbgbtbpb)fmt.Println(b, kb, mb, gb, tb, pb)
}

4. 条件语句

4.1 if

func main() {const filename = "abc.txt"//判断是否返回了 err 错误类型,不等于nil,如果等于nil直接打印错误信息,否则打印文件内容if contents, err := ioutil.ReadFile(filename) ; err != nil {fmt.Println(err)  //打印的数据:open abcd.txt: The system cannot find the file specified.} else {fmt.Printf("%s\n", contents) //打印数据}
}

4.2 switch

// func定义 score为int类型的分数,op为抛出的异常信息,string代表函数的返回值
func switchDemo(score int) string {g := ""switch {case score < 0 || score > 100:panic(fmt.Sprintf("wrong score: %d", score)) // panic() 表示抛出异常case score < 60:g = "F"//自动break,不需要写多余的breakcase score < 80:g = "C"case score < 90:g = "B"case score < 100:g = "A"default:panic(fmt.Sprintf("wrong score: %d", score)) // panic() 表示抛出异常}return g
}

4.3 for

可以省略初始条件和递增条件,相当于 while,也可以省略初始条件

for { } 这就是一个死循环

//转换二进制
func convertToBin(n int) string  {result := ""for ; n > 0; n /= 2 {lsb := n % 2// strconv.Itoa() 转换字符串result = strconv.Itoa(lsb) + result}return result
}//一行一行的读取文本文档中的数据
func printFile(filename string)  {//打开文件流file, err := os.Open(filename)if err != nil {panic(err)}//开启一个扫描器,一行一行的读取数据scanner := bufio.NewScanner(file)//没有起始符以及截止符号跟while关键字类似,可以直接省略  for { } 什么都不加直接死循环for scanner.Scan() {fmt.Println(scanner.Text())}
}

4.5 要点回顾

  • for\if 后面的条件没有括号
  • if条件里也可以定义变量
  • 没有while
  • switch不需要break,也可以直接switch多个条件

5. 函数

5.1 函数定义

func eval(参数名 类型) 返回类型 { 方法体 }

//返回单个 int 类型数据
func eval(a int, b int, op string) (int, error) {switch op {case "+":return a + bcase "-":return a - bcase "*":return a * bcase "/"://如果只需要一个参数就可以使用 _ 下划线q, _ := div(a, b)return qdefault:panic("unsupported operatir:" + op)}
}// 13 / 3 = 4 并且余1,这里我们可以返回除数以及余数,可以返回多值
func div(a, b int) (int, int) {return a /b, a % b
}
// q, r := div(13,3) 就可以直接获取到对应的变量名称
func div(a, b int) (q, r int) {return a /b, a % b
}
//以下的方式也可以(只能用于很简单的函数体)
func div(a, b int) (q, r int) {q = a / br = a % breturn q, r
}

一般函数的执行都会返回两个值,一个处理后的数据,一个就是 error 值,用于判断执行的异常信息

func main() {//可以判断返回的error是否为nil,然后根据返回的数据进行打印if result, error := eval(3, 4, "x"); error != nil {fmt.Println("Error", error)} else {fmt.Println(result)}
}func eval(a int, b int, op string) (int, error) {switch op {case "+":return a + b, nilcase "-":return a - b, nilcase "*":return a * b, nilcase "/":q, _ := div(a, b)return q, nildefault:return 0, fmt.Errorf("unsupported operation: %s", op)}
}// 13 / 3 = 4 并且余1,这里我们可以返回除数以及余数
func div(a, b int) (int, int) {return a /b, a % b
}

改造为函数式编程

func main() {fmt.Println(apply(pow, 3, 4))//以下方式也可以fmt.Println(apply(func(a ,b int) int {return int(math.Pow(float64(a), float64(b)))}, 3, 4))
}
//第一个参数 是一个函数,a,b是数据
func apply(op func(int, int) int, a, b int) int {//通过反射获取到函数的指针p := reflect.ValueOf(op).Pointer()//获取到方法的名称opName := runtime.FuncForPC(p).Name()fmt.Println("Calling function %s with ars (%d, %d)", opName, a, b)return op(a, b)
}
func pow(a, b int) int {return int(math.Pow(float64(a), float64(b)))
}

5.2 可变参数列表

func sum(numbers ...int) int {sum := 0for i := range numbers {sum += numbers[i]}return sum
}

5.3 函数变量

func main() {arr := []int{2,4,6,8}//匿名函数的创建f := func(i []int) int {sum := 0for _ , v := range i {sum += v}return sum}fmt.Println("数据:", f(arr))//将函数作为参数传递f1(arr, func(i int) {fmt.Println(i)})
}
func f1(i []int, f func(int))  {for _, v := range i {f(v)}
}

6. 指针

6.1 值传递&引用传递

go语言中只有值传递一种方式

func main() {var (a = 3b = 4)swap(a, b)fmt.Println(a, b)swapFoPoniter(&a, &b)fmt.Println(a, b)
}//交换数据,如果使用这种方式就是采用值传递的方式
func swap(a, b int) {a, b = b, a
}
//将指针的数据进行交换
func swapFoPoniter(a, b *int) {*a, *b = *b, *a
}
//这种方式是最好的
func swapForReturn(a, b int) (int, int) {return b, a
}

7. 数组

数组采用的是值传递的方式,而且[5]int,[3]int还是不同的类型,进行数据传递会将 array 进行传递会进行拷贝

go语言中一般不直接使用数组而使用切片

func main() {//定义长度为5的int数组var arr1 [5]intarr2 := [3]int{1,3,5}//采用编译器来确定数据的长度arr3 := [...]int{2,4,6,8,10}//定义二位数组,4个长度为5的数组var grid [4][5]intfmt.Println(arr1, arr2, arr3)fmt.Println(grid)//遍历数组for i := 0; i < len(arr3); i++ {fmt.Println(arr3[i])}//使用range关键字进行遍历 i为所有 v为值for i, v := range arr3 {fmt.Println(i, v)}
}// 参数 arrays []int 代表切片, arrays [5]int才是真正的数组,而且[5]int,[3]int还是不同的类型
func count(arrays [5]int) int {count := 0for _,v := range arrays {count += v}return count
}//可以采用指针传递
func countForPointer(arrays *[5]int) int {count := 0for _,v := range arrays {count += v}return count
}

8. 切片(Slice)

8.1 切片概念

  • Slice本身是没有数据的,是对底层array的一个视图,由 数组指针、长度、容量 构成
  • 每次进行扩容的 cap 是两倍进行扩容
func main() {//slice不是值传递,因为slice内部里面是一个视图结构arr := [...]int{0,1,2,3,4,5,6,7,8}//会截取数组中索引 2 - 6 左闭右开fmt.Println("arr[2:6] = ", arr[2:6])fmt.Println("arr[:6] = ", arr[:6])fmt.Println("arr[2:] = ", arr[2:])fmt.Println("arr[:] = ", arr[:])//直接获取到切片进行更新s1 := arr[2:]updateSlice(s1)fmt.Println(s1)//reslice操作fmt.Printf("Reslice之前操作: %d\n", arr)s2 := arr[:5]fmt.Printf("Reslice[:5]之后操作: %d\n", s2)s2 = s2[2:]fmt.Printf("Reslice[2:]操作: %d\n", s2)
}//修改slice
func updateSlice(s []int) {s[0] = 100
}

8.2 切片扩展

func extending() {arr := [...]int{0,1,2,3,4,5,6,7}s1 := arr[2:6]  // [2,3,4,5],切了之后原数组后面还有 6和7可以进行扩展s2 := s1[3:5]   // [5, 6] ,这里在s1的基础上进行切片,s1可以扩展的是6和7,所以s2也可以进行扩展 6和7s3 := s1[3:7]   // 抛错,因为超过了 s1可以进行扩展的范围//可以获取到底层数组的长度fmt.Printf("s1=%v,len(s1)=%d, cap(s1)=%d\n",s1, len(s1), cap(s1))fmt.Printf("s2=%v,len(s2)=%d, cap(s2)=%d\n",s2, len(s2), cap(s2))fmt.Println("s3=", s3)
}

实现:

其中有 prt(切片的数据)、len(切片数据的长度)、cap(被切的数组数组)

也就是说只要不超过 cap 的范围就可以进行扩展,cap的范围就是切片的范围 ;

注意Slice只能向后扩展,不能向前

8.3 切片添加

  • 添加元素时如果超越了 cap 的范围,系统会从重新分配更大的底层数组
  • 由于值传递的关系,必须接受append的返回值
func extending() {arr := [...]int{0,1,2,3,4,5,6,7}s1 := arr[2:6]  // [2,3,4,5],切了之后原数组后面还有 6和7可以进行扩展s2 := s1[3:5]   // [5, 6] ,这里在s1的基础上进行切片,s1可以扩展的是6和7,所以s2也可以进行扩展 6和7//可以获取到底层数组的长度fmt.Printf("s1=%v,len(s1)=%d, cap(s1)=%d\n",s1, len(s1), cap(s1))fmt.Printf("s2=%v,len(s2)=%d, cap(s2)=%d\n",s2, len(s2), cap(s2))s3 := append(s2, 10)  //现在进行 append会将原数组的最后一位进行替换s4 := append(s3, 11)  //这里再次进行添加,slice内部因为超过了数组的长度,就创建了一个新的数组进行存放s5 := append(s4, 12)fmt.Println("s3, s4, s5 = ", s3, s4, s5)fmt.Println("arr = ", arr)
}

8.4 切片常用操作

1. 创建

//默认方式为0,每次添加如果cap的容量不够就会进行*2的扩容var s []intfor i := 0; i < 100; i++ {printSlice(s)s = append(s, 2 * i + 1)}s1 := []int{2,4,6,8}printSlice(s1)s2 := make([]int, 16)printSlice(s2)s3 := make([]int, 10, 32)printSlice(s3)

2. 复制

func main() {//复制s1 := []int {1,2,3,4}s2 := make([]int, 10)copy(s2, s1)fmt.Println(s1)
}

3. 删除

func main() {s1 := []int {1,2,3,4}//删除 s1 中的 3  append()后面是一个可变参数,需要加上...s1 = append(s1[:2], s1[3:]...)fmt.Println(s1)
}
//剪掉首尾
func main() {s1 := []int {1,2,3,4}printSlice(s1)//删除s1的首尾head := s1[0]s1 = s1[1:]fmt.Printf("首部数据:%d,删除后的数据:%d\n", head, s1)printSlice(s1)tail := s1[len(s1) - 1]s1 = s1[:len(s1) - 1]fmt.Printf("尾部数据:%d,删除后的数据:%d\n", tail, s1)printSlice(s1)
}
func printSlice(s []int)  {fmt.Printf("v = %v, len() = %d, cap() = %d \n", s, len(s), cap(s))
}

9. Map

9.1 定义

  • Map使用哈希表,必须可以比较相等
  • 除了 Slice、map、function的内建类型都可以作为key
  • Struct类型不包含上述字段,也可以作为key
func main() {m := map[string]string {"name": "ccmouse",}//创建map,在go中 map为nil也可以参与运算m2 := make(map[string]int)var m3 map[string]int  // map is nilfmt.Println(m, m2, m3)
}

9.2 遍历

map是无序的是一个 HashMap,如果需要顺序的话需要手动进行排序,可以使用 Slice

func main() {m := map[string]string {"name": "ccmouse",}for k,v := range m {fmt.Println(k, v)}
}

9.3 取值

//取值
name := m["name"]
fmt.Println(name)
//判断值是否存在,如果查询了一个不存在的值,会返回初始值
name, ok := m["name"]
fmt.Println(name, ok)
//通过表达式进行计算
if name, ok := m["name"]; ok {fmt.Println(name, ok)
}

9.4 删除

delete(m, "name")
name, ok = m["name"]
fmt.Println(ok)

9.5 例子

例:寻找最长不含有重复字符的子串

func lengthOfNonRepeatingSubStr(s string) int {//创建一个 maplastOccurred := make(map[byte]int)start := 0maxLength := 0//将s字符串转换成 byte[]for i, ch := range []byte(s) {//读取map中的数据,如果读取的数据存在并且长度大于 start的长度if lastI, ok := lastOccurred[ch]; ok && lastI >= start {start = lastI + 1}if i - start + 1 > maxLength {maxLength = i - start + 1}lastOccurred[ch] = i}return maxLength
}

10. 结构

  • go语言仅支持封装,不支持继承和多态
  • go语言没有类,只有struct

10.1 结构的创建

局部变量是在堆上创建还是栈上?

由编译器决定,局部变量返回出去的指针地址,是否有人在使用,然后考虑分配的地址在栈空间还是堆空间中

指针的数据也是直接使用 . 进行调用 ,编译器会根据方法调用的参数来确定是值传递还是指针传递

type treeNode struct {value int//左右都是指针地址left, right *treeNode
}//如果没有构造方法,可以通过一个工厂方法来创建
func createNode(value int) *treeNode {//这里返回的是一个局部变量的地址return &treeNode{value, nil, nil}
}func main() {var root treeNoderoot = treeNode{value: 3}root.left = &treeNode{}root.right = &treeNode{5, nil, nil}root.right.right = new(treeNode)nodes := []treeNode {{value: 3},{},{6, nil, &root},}fmt.Println(nodes)
}

10.2 给结构定义方法

func (node nodeTree) print() {

}

//给结构定义方法,在方法名前面指定接收者,go中没有this指针说法
func (node treeNode) print()  {fnt.Println(node.value)
}func main() {root.print()
}//值传递的方式进行复制,原值不会进行改变
func (node treeNode) setValue(value int)  {node.value = value
}//指针的方式进行赋值,原值会进行改变
func (node *treeNode) setValueForPointer(value int)  {node.value = value
}

nil指针也可以调用方法

10.3 值接收者 vs 指针接收者

  • 要改变内容必须使用指针接收者
  • 结构过大也考虑使用指针接收者
  • 一致性:如有指针接收者,最好都是指针接收者

11. 封装

针对包来说,每个目录一个包,main包包含可执行方法,为结构定义的方法必须放在同一个包内,可以是不同的文件;通过名称来确定权限

  • 名字一般使用Camelcase
  • 首字母大写:public
  • 首字母小写:private

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-tPIWF1lv-1653533658181)(images/1652702990978.png)]

entry

package mainimport "zhj.com/golearns/learns/tree"func main() {var root = tree.Node{}root.Left = &tree.Node{}root.Right = &tree.Node{Value: 5}root.Right.Right = &tree.Node{Value: 6}root.Traverse()
}

node

package tree
import "fmt"type Node struct {Value intLeft, Right *Node
}
//给结构定义方法,指定接收者
func (node Node) print()  {fmt.Println(node.Value)
}
func (node Node) SetValue(value int)  {node.Value = value
}
func (node *Node) SetValueForPointer(value int)  {node.Value = value
}
//如果没有构造方法,可以通过一个工厂方法来创建
func CreateNode(value int) *Node {return &Node{6, nil, nil}
}

traversal

package tree
func (node *Node) Traverse() {if node == nil {return}node.Left.Traverse()node.print()node.Right.Traverse()
}

11.1 扩展已有类型

  • 定义别名
type myTreeNode struct {node *tree.Node
}func (myNode *myTreeNode) postOrder() {if myNode == nil || myNode.node == nil {return}left := myTreeNode{myNode.node.Left}right := myTreeNode{myNode.node.Right}left.postOrder()right.postOrder()myNode.node.Print()
}func main() {root := tree.Node{Value: 3}root.Left = &tree.Node{}root.Right = &tree.Node{Value: 5}root.Left.Right = &tree.Node{Value: 2}root.Right.Left = &tree.Node{Value: 4}myNode := myTreeNode{&root}myNode.postOrder()
}
  • 使用组合
type Queue []intfunc (q *Queue) Push(v int) {*q = append(*q, v)
}func (q *Queue) Pop() int {head := (*q)[0]*q = (*q)[1:]return head
}func (q *Queue) IsEmpty() bool  {return len(*q) == 0
}
  • 内嵌方式:直接省略变量名,相当于直接将 Node 里面的所有属性都拿出来复制给了 myEmbeddingNode 类似于继承
type myEmbeddingNode struct {*tree.Node  //内嵌
}

12. 依赖管理

依赖管理的三个阶段

GOPATH —> GOVENDOR —> GO MOD

12.1 GOPATH

一个环境变量,配置的路径,通过GOPATH保存所有的依赖库

依赖优先查找 GOROOT 然后去 GOPATH 路径下面找

12.2 GOVENDOR

每个项目都创建一个 vendor 目录,存放第三方依赖

12.3 GOMOD

依赖会存到 GOPATH 下的 pkg 的路径下面,GOPATH的路径应该在src的上一级目录下,不能设置到 GOROOT

  • go get 更新依赖
  • go mod tidy 去除无用依赖
  • go mod:go mod init,go build ./… 迁移老的项目

13. 接口

13.1 接口定义

下面定义两个不同包下面的结构

package mock//定义一个结构
type Retriever struct {Contents string
}func (r Retriever) Get(url string) string  {return r.Contents
}
package realimport ("net/http""net/http/httputil""time"
)type Retriever struct {UserAgent stringTimeOut time.Duration
}func (r Retriever) Get(url string) string  {resp, err := http.Get(url)if err != nil {panic(err)}result, err := httputil.DumpResponse(resp, true)//关闭closeresp.Body.Close()if err != nil {panic(err)}return string(result)
}

下面main包中定义一个接口,以及一个方法,参数传入接口

type Retriever interface {Get(url string) string
}func download(r Retriever) string  {return r.Get("https://www.baidu.com")
}func main() {var r Retrieverr = real.Retriever{UserAgent: "mock", TimeOut: time.Minute}fmt.Println(download(r))
}

函数的方式实现接口

func main() {var invoker Invoker//将实例化的结构体赋值到接口,将匿名函数转换成 FunCaller接口invoker = FuncCaller(func(V any) {fmt.Println("from function", V)})//通过接口进行调用invoker.call(1)
}
type Invoker interface {//需要实现一个方法call(V any)
}
//定义函数为类型, 上面定义的any是interface的别名
type FuncCaller func(interface{})//再给函数进行实现
func (f FuncCaller) call(p interface{})  {//最后再调用函数本体f(p)
}

13.2 接口变量里面有什么?

interface{} 表示任何类型

  • 实现者的类型
  • 实现者的指针
func main() {var r Retrieverr = mock.Retriever{Contents: "this is a fake retriever"}inspect(r)r = &real.Retriever{UserAgent: "mock", TimeOut: time.Minute}//获取r接口的类型realRetriever := r.(*real.Retriever)inspect(r)
}func inspect(r Retriever)  {//打印接口的信息,看看内部变量有什么值?fmt.Printf("%T %v\n", r, r)// r.(type) 可以获取当前接口的类型switch v := r.(type) {case mock.Retriever:fmt.Println("Contents:", v.Contents)case *real.Retriever:fmt.Println("UserAgent:", v.UserAgent)}
}

13.3 接口的组合

实现者,只需要实现方法即可,不需要说明是否实现接口

type Retriever interface {Get(url string) string
}type Poster interface {Post(url string) string
}type RetrieverPoster interface {// Retriever 其中可以添加很多的接口,进行组合,这个时候 RetrieverPoster 就可以掉用所有接口的方法RetrieverPosterDelete(url string) string
}func session(s RetrieverPoster)  {s.Delete("url")s.Get("url")s.Post("url")
}

13.4 标准接口

  • stringer:用于进行 toString,实现后可以自定义打印格式
  • Reader/Writer:对文件的抽象

14. 函数式编程

参数,变量,返回值都可以是函数

14.1 斐波那契数列

// 斐波那契数列: 1,1,2,3,5,8
func fibonacci() func() int {//每一次就将数字进行后移a, b := 0, 1return func() int {//将b跟前后两个数相加,然后返回aa, b = b, a + breturn a}
}
func main() {f := fibonacci()fmt.Println(f())fmt.Println(f())fmt.Println(f())fmt.Println(f())fmt.Println(f())fmt.Println(f())fmt.Println(f())
}

14.2 函数实现接口

将斐波那契数列的方法进行封装成 Reader 进行调用

func fibonacci() intGen {a, b := 0, 1return func() int {a, b = b, a + breturn a}
}//函数实现接口
type intGen func() int//实现 Reader 接口的方法
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)
}//传入 Reader参数,因为 intGen 实现了函数接口
func printFileContents(reader io.Reader)  {scanner := bufio.NewScanner(reader)for scanner.Scan() {fmt.Println(scanner.Text())}
}func main() {f := fibonacci()printFileContents(f)
}

15. 资源管理与出错处理

15.1 defer

一般在以下情况下调用,等待方法执行结束或者 painc 异常结束前执行,先进后出

  • Open/Close
  • Lock/Unlock
  • PrintHeader/PrintFooter
func writeFile(fileName string) {if file , err := os.Create(fileName); err == nil {//延迟关闭,等return之前defer file.Close()writer := bufio.NewWriter(file)//刷新缓存到内存中defer writer.Flush()f := fib.Fibonacci()for i := 0; i < 20; i++ {fmt.Fprintln(writer, f())}} else {panic(err)}
}func main() {tryDeffer()writeFile("abc.txt")
}

15.2 错误处理

//当前 语法是为了 检查 err 接口值是否是传入的类型,如果不是就返回 false,一般就处理自己需要处理的异常
pathError, ok := err.(*os.PathError)

15.3 统一错误处理

error vs panic

  • 意料之中的错误:使用error,如:文件打不开,不存在这个文件
  • 意料之外的:使用panic。如:数组越界

定义错误的函数,这个错误的目的是用来区分是否给用户查看

//定义一种错误可以给用户看的错误
type userError interface {error  //给系统看的Message() string
}

将处理的函数进行封装,返回一个 error 错误

//定义一个函数
type appHandler func(writer http.ResponseWriter, request *http.Request) error

定义错误包装器,对返回的错误信息进行封装

func errWrapper(handler appHandler) func(writer http.ResponseWriter, request *http.Request) {return func(writer http.ResponseWriter, request *http.Request) {err := handler(writer, request)//使用defer,在函数处理之后调用 recover() 对http请求再次进行封装defer func() {if r := recover(); r != nil {log.Printf("painc :%v", r)http.Error(writer, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)}}()if err != nil {log.Printf("Error occurred handling reqeust :%s", err.Error())//判断是否是自定义的异常信息,使用 type Assert 方式if useErr, OK := err.(userError); OK {http.Error(writer,useErr.Message(),http.StatusBadRequest)return}//记录下 http的状态code := http.StatusOKswitch {//这里判断异常进行对应的状态值的封装case os.IsNotExist(err):http.Error(writer,http.StatusText(http.StatusNotFound),http.StatusNotFound)returndefault:code = http.StatusInternalServerError}http.Error(writer, http.StatusText(code), code)}}
}

文件处理

func FileHandlerList(writer http.ResponseWriter, request *http.Request) error {if strings.Index(request.URL.Path, prefix) != 0 {//使用自己定义的异常return userError("path must start with" + prefix)}//给http设置一个处理的函数//这里截取/list/后面的数据path := request.URL.Path[len(prefix):]file, err := os.Open(path)if err != nil {//panic(err)//这里应该返回错误信息,不应该使用panic报错//http.Error(writer, err.Error(), http.StatusInternalServerError)return err}defer file.Close()all, err := ioutil.ReadAll(file)if err != nil {return err}writer.Write(all)return nil
}

自定义错误的信息,用于实现上面的 userError

//实现用户定义的异常信息
type userError stringfunc (e userError) Error() string  {return e.Message()
}func (e userError) Message() string  {return string(e)
}

main方法

//这里参数传递了 filelisting.FileHandlerList,实现了上面定义的 appHandler 所以默认就是调用当前方法
http.HandleFunc("/", errWrapper(filelisting.FileHandlerList))
//开启一个端口监听 8888端口
err := http.ListenAndServe(":8888", nil)
if err != nil {panic(err)
}

16. 测试

16.1 传统测试 vs 表格驱动测试

16.1.1 传统测试

  • 测试数据和测试逻辑混乱在一起
  • 出错信息不明确
  • 一旦一个数据出错测试全部结束

16.1.2 表格驱动测试

  • 分离了测试数据和测试逻辑
  • 明确了出错信息
  • 可以部分失败
  • go语言的语法比较容易实现
//参数是 t *testing.T
func TestAdd(t *testing.T) {tests := []struct{a, b, c int32} {{3, 4, 7},{5, 12, 16},{10, 10, 20},}//循环遍历for _, tt := range tests {if actual := add(tt.a, tt.b); actual != tt.c {t.Errorf("add(%d, %d) got %d; expected %d", tt.a, tt.b, actual, tt.c)}}
}

16.1.3 代码覆盖率和性能测试

代码覆盖率

idea还提供了当前测试的代码覆盖率以及性能问题,测试是 Test 名称开头

红色部分就是没有覆盖到的测试代码,绿色的就代表覆盖到了

性能测试

注意名称:性能测试方法名称是 Beanchmark 开头

func BenchmarkAdd(b *testing.B) {tests := []struct{a, b, c int32} {{3, 4, 7},{5, 12, 16},{10, 10, 20},}//准备数据的时间不算b.ResetTimer()// b.N 由系统算法自定义for i := 0; i < b.N; i++ {for _, tt := range tests {if actual := add(tt.a, tt.b); actual != tt.c {b.Errorf("add(%d, %d) got %d; expected %d", tt.a, tt.b, actual, tt.c)}}}}

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-zAFG1xec-1653533658185)(images/1653210034559.png)]

  • go test -bean . -cpuprofile: 获取性能数据
  • go tool pprof:查看二进制的性能日志

17. 文档写法

下载 godoc 依赖

go get golang.org/x/tools/cmd/godoc

go install golang.org/x/tools/cmd/godoc

godoc -http :端口 可以通过web页面查看文档

17.1 示例代码

文件命名一定要 模块_test ,编译器会检查 Output 是否正确

func ExampleQueue_Pop() {q := Queue{1}q.Push(2)q.Push(3)q.Push(4)fmt.Println(q.Pop())fmt.Println(q.Pop())fmt.Println(q.IsEmpty())fmt.Println(q.Pop())fmt.Println(q.IsEmpty())// Output:// 1// 2// false// 3// true
}

以下就是页面的实例代码

2. Golang基本语法相关推荐

  1. Go语言学习笔记—golang基础语法

    视频来源:B站<golang入门到项目实战 [2022最新Go语言教程,没有废话,纯干货!]> 文章为自己整理的学习笔记,侵权即删,谢谢支持! 文章目录 golang基础语法 一.gola ...

  2. Golang简单语法

    Golang简单语法 文章目录 Golang简单语法 GO语言 简介 Golang的格式检查 注释 主函数模板 `变量` 输入和输出语句 变量的类型 变量的定义和赋值 常量 流程控制 `函数` 内置函 ...

  3. GoLang之语法糖讲解

    文章目录 GoLang之语法糖讲解 1.变量声明 1.1变量 1.2 "var"声明(指定类型) 1.3"var"声明(无指定类型) 1.4 "nam ...

  4. 万字Golang基础知识(肝爆三天三夜,手撕Golang基本语法结构)

    Golang基础知识 一. 初识Golang 1.1 Go的语法要求 1.1.1 token 1.2 变量和常量 1.2.1 变量 1.2.2 常量 1.3 基本数据类型 1.3.1 布尔类型 1.3 ...

  5. golang 切片 接口_一日看尽golang高级语法之slice

    golang系列的文章包含多篇文章,总篇如下,其中包含各篇文章的指引 明月映江雪:golang系列--个人学习笔记总篇​zhuanlan.zhihu.com 由于最近事情比较多,拖到现在才更新,另一方 ...

  6. golang基础语法

    看如下代码: package mainfunc main() {println("hello golang") } 注意:在windows下cmd打开项目对应的目录,可以进行如下操 ...

  7. golang基本语法——变量使用详解

    一.变量的使用 1.1 什么是变量 变量是为存储特定类型的值而提供给内存位置的名称.在go中声明变量有多种语法. 所以变量的本质就是一小块内存,用于存储数据,在程序运行过程中数值可以改变 1.2 声明 ...

  8. golang模板语法

    https://www.cnblogs.com/Pynix/p/4154630.html https://blog.csdn.net/huwh_/article/details/77140664 ht ...

  9. golang select default continue_golang系列——基础语法

    golang系列的文章包含多篇文章,总篇如下,其中包含各篇文章的指引 明月映江雪:golang系列--个人学习笔记总篇​zhuanlan.zhihu.com golang的基础语法和其他语言有共通之处 ...

最新文章

  1. ThinkPHP5执行流程分析
  2. asmr刷新失败无法连接上服务器_App Store显示无法连接怎么解决?两个步骤足够了...
  3. 我是如何使用git把本地代码上传到CODECHINA上的,值得借鉴
  4. php如何新建xml文件,PHP中的生成XML文件的4种方法分享
  5. 3 年后端、4 年前端,聊聊用户认证鉴权
  6. WEB测试—功能测试
  7. r - 求平均成绩_R语言 从零开始的笔记(一)
  8. html点击按钮 重新加载页面或者跳转页面实现
  9. mysql数据库 怎么替换_mysql数据库替换
  10. C语言基础选择题100道(附答案)01
  11. SQLITE测试工具
  12. python可视化数据分析交互作用_测试设计功能交互分析
  13. [附源码]PHP计算机毕业设计小斌美食网站(程序+LW)
  14. java中逗号运算符的含义_逗号运算符什么时候有用?
  15. linux下home目录迁移
  16. thinkphp5.x获取当前模块名称,当前控制器名称,当前类方法名称,当前模型名称
  17. FL Studio水果简体中文20.9版本下载
  18. 存储空间都去哪了?占用空间比文件大太多?可能是文件系统和默认簇大小惹的祸
  19. 安卓桌面壁纸_火莹视频桌面:好玩的动态桌面壁纸软件,让你的桌面动起来
  20. 代码签名证书_代码签名

热门文章

  1. Python灰帽子_黑客与逆向工程师的Python编程之道
  2. Tensorflow问题记录
  3. Hystrix Dashboard
  4. Rman操作简单分析
  5. ElasticSearch (ES)学习之路(二)Win10安装ES,可视化界面,Kibanna
  6. 寻呼(Paging)
  7. win32 play flash file
  8. 专家:滴滴优步合并存大数据垄断隐忧
  9. 老调重弹,Android Studio 打包H5项目(2020版)
  10. WebUploader重复多次上传问题