赞
踩
Go语言中有符号整数采用 2 的补码形式表示,也就是最高 bit 位用来表示符号位,一个 n-bit 的有符号数的取值范围是从 -2(n-1) 到 2(n-1)-1。无符号整数的所有 bit 位都用于表示非负数,取值范围是 0 到 2n-1。例如,int8 类型整数的取值范围是从 -128 到 127,而 uint8 类型整数的取值范围是从 0 到 255。
变量声明时,默认值 int = 0,float = 0.0,bool = false,string = 空字符串,指针为nil等,所有的内存在go中都经过初始化,命名遵循驼峰命名法
var 变量名 变量类型 //单个命名
var ( 变量名 变量类型 ) //批量命名
名称 := 表达式 //简短格式命名
//单个命名
var numShips bool
//批量命名
var (
a int
b string
c []float32
d func() bool
e struct{
x int
}
)
//简短模式
i, j := 0, 1
注意简单模式的缺点:
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")
编程最简单的算法之一,莫过于变量交换。交换变量的常见算法需要一个中间变量进行变量的临时保存。用传统方法编写变量交换代码如下:
func swap() {
//变量交换
var a = 100
var b = 200
var t int
t = a
a = b
b = t
fmt.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] }
匿名变量的特点:_ 下划线来作为标识符,匿名变量不占用内存,不会分配内存空间
func GetData() (int, int) {
return 100, 200
}
func main(){
a, _ := GetData()
_, b := GetData()
fmt.Println(a, b)
}
Go语言会在编译时检查每个变量是否使用过,一旦出现未使用的变量,就会报编译错误
func varF() {
var a = 3
var b = 4
c := a + b
fmt.Printf("a = %d, b = %d, c = %d", a, b, c)
}
import (
"fmt"
)
var c int
func main() {
var a, b int
a = 3
b = 4
c = a + b
fmt.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 + b
return num
}
一个 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)
}
在计算机中,复数是由两个浮点数表示的,其中一个表示实部(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 类型作为参数。
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() }
在 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)
}
const 名称 = 数据
const (名称 = 数据)
常量经过定义之后就不能再进行修改了
const fileName = "abc.txt"
func consts() {
const a, b = 1, "123"
//常量经过定义就不能再修改了
a = 2
fmt.Println(a, b)
fmt.Println(fileName)
}
func enums() { const ( java = iota // iota表示这一组枚举使用自增方式进行 0,1,2,3 _ //可以直接跳过 1 这个索引 golang python c ) fmt.Println(java, golang, python, c) const ( b = 1 << (10 * iota) //这里后续都会使用这个公式进行计算,打印出单位值 kb mb gb tb pb ) fmt.Println(b, kb, mb, gb, tb, pb) }
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) //打印数据
}
}
// 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,不需要写多余的break case 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 }
可以省略初始条件和递增条件,相当于 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()) } }
func eval(参数名 类型) 返回类型 { 方法体 }
//返回单个 int 类型数据 func eval(a int, b int, op string) (int, error) { switch op { case "+": return a + b case "-": return a - b case "*": return a * b case "/": //如果只需要一个参数就可以使用 _ 下划线 q, _ := div(a, b) return q default: 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 / b r = a % b return 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, nil case "-": return a - b, nil case "*": return a * b, nil case "/": q, _ := div(a, b) return q, nil default: 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))) }
func sum(numbers ...int) int {
sum := 0
for i := range numbers {
sum += numbers[i]
}
return sum
}
func main() { arr := []int{2,4,6,8} //匿名函数的创建 f := func(i []int) int { sum := 0 for _ , 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) } }
go语言中只有值传递一种方式
func main() { var ( a = 3 b = 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 }
数组采用的是值传递的方式,而且[5]int,[3]int还是不同的类型,进行数据传递会将 array 进行传递会进行拷贝
go语言中一般不直接使用数组而使用切片
func main() { //定义长度为5的int数组 var arr1 [5]int arr2 := [3]int{1,3,5} //采用编译器来确定数据的长度 arr3 := [...]int{2,4,6,8,10} //定义二位数组,4个长度为5的数组 var grid [4][5]int fmt.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 := 0 for _,v := range arrays { count += v } return count } //可以采用指针传递 func countForPointer(arrays *[5]int) int { count := 0 for _,v := range arrays { count += v } return count }
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 }
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
s3 := 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只能向后扩展,不能向前

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) }
//默认方式为0,每次添加如果cap的容量不够就会进行*2的扩容
var s []int
for 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)
func main() {
//复制
s1 := []int {1,2,3,4}
s2 := make([]int, 10)
copy(s2, s1)
fmt.Println(s1)
}
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)) }
func main() {
m := map[string]string {
"name": "ccmouse",
}
//创建map,在go中 map为nil也可以参与运算
m2 := make(map[string]int)
var m3 map[string]int // map is nil
fmt.Println(m, m2, m3)
}
map是无序的是一个 HashMap,如果需要顺序的话需要手动进行排序,可以使用 Slice
func main() {
m := map[string]string {
"name": "ccmouse",
}
for k,v := range m {
fmt.Println(k, v)
}
}
//取值
name := m["name"]
fmt.Println(name)
//判断值是否存在,如果查询了一个不存在的值,会返回初始值
name, ok := m["name"]
fmt.Println(name, ok)
//通过表达式进行计算
if name, ok := m["name"]; ok {
fmt.Println(name, ok)
}
delete(m, "name")
name, ok = m["name"]
fmt.Println(ok)
例:寻找最长不含有重复字符的子串
func lengthOfNonRepeatingSubStr(s string) int { //创建一个 map lastOccurred := make(map[byte]int) start := 0 maxLength := 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 }
局部变量是在堆上创建还是栈上?
由编译器决定,局部变量返回出去的指针地址,是否有人在使用,然后考虑分配的地址在栈空间还是堆空间中
指针的数据也是直接使用 . 进行调用 ,编译器会根据方法调用的参数来确定是值传递还是指针传递
type treeNode struct { value int //左右都是指针地址 left, right *treeNode } //如果没有构造方法,可以通过一个工厂方法来创建 func createNode(value int) *treeNode { //这里返回的是一个局部变量的地址 return &treeNode{value, nil, nil} } func main() { var root treeNode root = 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) }
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指针也可以调用方法
针对包来说,每个目录一个包,main包包含可执行方法,为结构定义的方法必须放在同一个包内,可以是不同的文件;通过名称来确定权限
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-tPIWF1lv-1653533658181)(images/1652702990978.png)]
entry
package main
import "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 int Left, 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()
}
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 []int func (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 }
type myEmbeddingNode struct {
*tree.Node //内嵌
}
依赖管理的三个阶段
GOPATH —> GOVENDOR —> GO MOD
一个环境变量,配置的路径,通过GOPATH保存所有的依赖库
依赖优先查找 GOROOT 然后去 GOPATH 路径下面找
每个项目都创建一个 vendor 目录,存放第三方依赖
依赖会存到 GOPATH 下的 pkg 的路径下面,GOPATH的路径应该在src的上一级目录下,不能设置到 GOROOT
下面定义两个不同包下面的结构
package mock
//定义一个结构
type Retriever struct {
Contents string
}
func (r Retriever) Get(url string) string {
return r.Contents
}
package real import ( "net/http" "net/http/httputil" "time" ) type Retriever struct { UserAgent string TimeOut 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) //关闭close resp.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 Retriever
r = 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) }
interface{} 表示任何类型
func main() { var r Retriever r = 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) } }
实现者,只需要实现方法即可,不需要说明是否实现接口
type Retriever interface { Get(url string) string } type Poster interface { Post(url string) string } type RetrieverPoster interface { // Retriever 其中可以添加很多的接口,进行组合,这个时候 RetrieverPoster 就可以掉用所有接口的方法 Retriever Poster Delete(url string) string } func session(s RetrieverPoster) { s.Delete("url") s.Get("url") s.Post("url") }
参数,变量,返回值都可以是函数
// 斐波那契数列: 1,1,2,3,5,8 func fibonacci() func() int { //每一次就将数字进行后移 a, b := 0, 1 return func() int { //将b跟前后两个数相加,然后返回a a, b = b, a + b return 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()) }
将斐波那契数列的方法进行封装成 Reader 进行调用
func fibonacci() intGen { a, b := 0, 1 return func() int { a, b = b, a + b return 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) }
一般在以下情况下调用,等待方法执行结束或者 painc 异常结束前执行,先进后出
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") }
//当前 语法是为了 检查 err 接口值是否是传入的类型,如果不是就返回 false,一般就处理自己需要处理的异常
pathError, ok := err.(*os.PathError)
定义错误的函数,这个错误的目的是用来区分是否给用户查看
//定义一种错误可以给用户看的错误
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.StatusOK switch { //这里判断异常进行对应的状态值的封装 case os.IsNotExist(err): http.Error(writer, http.StatusText(http.StatusNotFound), http.StatusNotFound) return default: 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 string
func (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)
}
//参数是 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) } } }
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)]
下载 godoc 依赖
go get golang.org/x/tools/cmd/godoc
go install golang.org/x/tools/cmd/godoc
godoc -http :端口 可以通过web页面查看文档
文件命名一定要 模块_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 }
以下就是页面的实例代码

Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。