14 函数
一个函数多种传参方法和调用方法
变长参数
func average(st ...float64) (avg float64) {
avg = 0
for _, val := range st {
avg += val
}
avg = avg / float64(len(st))
return
}
func main() {
//nums := []float64{1, 2, 3, 4, 5, 6}
//num
fmt.Println(average(1, 2, 3, 4, 5))
}
记住这样传参的话,就不能是 nums 数组形式了,要是 nums 一个个数字写进去 还有就是 求平均的时候,要 float64(强制转换下st的int类型)
或者使用
fmt.Println(average(nums...))
这样也可以,后面加上...就类似Python的*用法拆分
如果还是定义是
func average(st []float64) (avg float64) {
那么就不需要...了直接传数组即可
返回返回值是一个函数的写法
func makeGreeter() func() string {
return func() string {
return "Hello world!"
}
}
func main() {
greet := makeGreeter()
fmt.Println(greet())
fmt.Printf("%T\n", greet)
}
返回值是一个函数的话,写法就是 func() string了,这个也是类似命名返回值的写法
闭包
闭包记住一个核心原则,如果闭包捕获到了一个外部变量,那么闭包对这个外部变量的修改,都是对原始值的修改
func wrapper() func() int {
var x int
return func() int {
x++
return x
}
}
func main() {
increment := wrapper()
fmt.Println(increment())
fmt.Println(increment())
}
对于这样一段代码,一开始的warpper() 就生成了x的值缓存了,执行到了return的部分,后面对x的增加就会一直有效
比如这样一个过滤器
func myFilter(nums []int, callable func(int) bool) (result []int) {
for _, val := range nums {
if callable(val) {
result = append(result, val)
}
}
return
}
func main() {
result := myFilter([]int{1, 2, 3, 4}, func(x int) bool {
return x > 1
})
fmt.Println(result)
}
注意几个点,append 左侧一定要有个值来接收,否则的话,会报错 result没有使用 另外一个例子是
func myPrint(nums []int, callable func(int)) {
for _, val := range nums {
callable(val)
}
}
func main() {
myPrint([]int{1, 2, 3, 4}, func(x int) {
fmt.Println(x)
})
}
注意如果函数作为参数的话,写的是函数的签名,比如
func myFilter(nums []int, callable func(int) bool) (result []int) {
或者 func myPrint(nums []int, callable func(int)) {
要记住写的是 callable func(int) 要把参数也写上去
对于string来说,如果转成bytes来看的话,就是一个4位的bytes
func main() {
var x [256]string
fmt.Println(len(x))
fmt.Println(x[0])
for i := 0; i < 256; i++ {
x[i] = string(i)
}
for _, v := range x {
fmt.Printf("%v - %T - %v\n", v, v, []byte(v))
}
}
所以这段代码会输出的是
7 - string - [55]
8 - string - [56]
9 - string - [57]
是一位的bytes
o - string - [111]
p - string - [112]
q - string - [113]
r - string - [114]
s - string - [115]
t - string - [116]
u - string - [117]
v - string - [118]
也是一位的, 对于Unicode或者utf-8字符就是4位的
í - string - [195 173]
î - string - [195 174]
ï - string - [195 175]
ð - string - [195 176]
ñ - string - [195 177]
ò - string - [195 178]
18 切片
切片要扩充大小,只能使用append 如果要index赋值的话,就相当于固定大小的数组
func main() {
greeting := make([]string, 3, 5)
// 3 is length - number of elements referred to by the slice
// 5 is capacity - number of elements in the underlying array
greeting[0] = "Good morning!"
greeting[1] = "Bonjour!"
greeting[2] = "buenos dias!"
greeting[3] = "suprabadham"
//greeting = append(greeting, "suprabadham")
fmt.Println(greeting[2])
}
比如这样,如果greeting[3] 就会报错indexerror 要使用注释掉的append
另外cap 如果扩充的话,每次就是扩容一个cap的大小
比如 greeting := make([]string, 3, 5) 如果产生了一次扩充,即便只新增了4个,也是扩容到了10个的大小
另外注意append每次只能添加一个值,如果要添加多个,要加上...进行拆分,比如
func main() {
mySlice := []string{"Monday", "Tuesday"}
myOtherSlice := []string{"Wednesday", "Thursday", "Friday"}
mySlice = append(mySlice, myOtherSlice...)
fmt.Println(mySlice)
}
append 的时候进行切片也是可以的
func main() {
mySlice := []string{"Monday", "Tuesday"}
myOtherSlice := []string{"Wednesday", "Thursday", "Friday"}
mySlice = append(mySlice, myOtherSlice...)
fmt.Println(mySlice)
mySlice = append(mySlice[:2], mySlice[3:]...)
fmt.Println(mySlice)
}
对切片进行var 或者 := 声明的时候,不会创建,返回的也是nil 要添加只能append,但是make的话,就会创建,然后分配大小
func main() {
student := make([]string, 35)
students := make([][]string, 35)
student[0] = "Todd"
// student = append(student, "Todd")
fmt.Println(student)
fmt.Println(students)
}
如果 var 或者 := 创建的,那就没法使用student[0] = "Todd", 用make 创建了,就会真的给空间,然后分配默认值
19 map
map的声明和上面说的切片同理
func main() {
var myGreeting map[string]string
fmt.Println(myGreeting)
fmt.Println(myGreeting == nil)
}
var 创建的不会分配空间,也不能使用,返回的也是nil
可以写成
var myGreeting = make(map[string]string)
也可以写成
myGreeting := make(map[string]string)
区别就是 var 和 := 的区别
字典声明也可以使用{}来初始化,而不是一个一个的给值
myGreeting := map[string]string{
"Tim": "Good morning!",
"Jenny": "Bonjour!",
}
就不需要
myGreeting["Tim"] = "Good morning."
myGreeting["Jenny"] = "Bonjour."
另外字典是无序的
func main() {
myGreeting := map[string]string{
"Tim": "Good morning!",
"Jenny": "Bonjour!",
}
myGreeting["Harleen"] = "Howdy"
fmt.Println(myGreeting)
}
返回的是
map[Harleen:Howdy Jenny:Bonjour! Tim:Good morning!]
不是按照添加顺序来的
字典的删除可以使用delete,
delete(myGreeting, "two")
append 需要左侧有接收的值,但是delete不需要,delete的是key
要注意的是,如果删除一个不存在的key 那么也哈斯不会报错的,delete(myGreeting, 7) 7不存在,照样不会报错
同理如果取到了一个字典中不存在的值,也不会报错,只是会给2个返回值,第二个返回值是false
val, err := myGreeting[7]
fmt.Printf("val: %v, err: %v", val, err)
返回:
val: , err: false
所以还需要判断一下第二个值是不是nil或者true来判断字典中的取值是否存在
rune 的用法
rune 是输出Unicode的字符集的编码,也会是一个在Unicode 字符集里面的第几个
Unicode 是一个字符集,而utf-8是一种编码,这两个是不同的概念,Unicode 字符可以用不同的格式进行编码,比如 utf-8 utf-16 utf-32
utf-8 是一种采用 1~4个字节来表示一个Unicode 字符,他一个重要特性就是,能向后兼容Unicode,即ASCII 的字符在Unicode中也是一个字符来表示,且字节值与ASCII相同。 utf-8的这种特性使得他非常适合传输和存储文本数据,因为他可以无缝处理ASCII 编码,并且对于包含大量ASCII字符的文本来说,使用utf-8编码会非常节省空间
在go中,string类型是用来表示文本的,go 语言string类型内部用utf-8编码来存储Unicode字符,对比区别请看如下代码
s := "H你"
fmt.Println([]rune(s))
fmt.Println([]byte(s))
这段代码输出的是
[72 20320]
[72 228 189 160]
可以看出,对于ASCII就是单个字节,对于中文就是三个字节的变长存储
那么utf-8是怎么知道,后面三个要连起来看,而不是分开或者2个2个一起看呢
[72 228 189 160] 这个go 是怎么知道,前面一个是单独的,后面三个是要合并起来的呢
这就涉及到了ascii 的编码规则了
字节类型:根据第一格字节的二进制表示,可以判断出字符在使用的字节数
- 0xxxxx 单字节ASCII字符
- 1xxxxx 多字节字符的第一个字节(2个字节)
- 11xxxx 3个字节
- 111xxx 4个字节
也就是看开头有几个1,判断后面要跟上几个是一个字节
解析 [72 228 189 160]
根据 第一个字节 228 (二进制为 11100010)表明这是一个 3 字节字符的第一个字节,所以后面三个是黏在一起来判断
所以对应到这里
func main() {
s := "H你"
fmt.Println([]rune(s))
fmt.Println([]byte(s))
fmt.Println("------")
var r rune = '你'
fmt.Println(r)
fmt.Println(string(r))
fmt.Printf("%%s = %s\n", r)
fmt.Printf("%%c = %c\n", r)
}
输出就是
func main() {
s := "H你"
fmt.Println([]rune(s))
fmt.Println([]byte(s))
fmt.Println("------")
var r rune = '你'
fmt.Println(r)
fmt.Println(string(r))
fmt.Printf("%%s = %s\n", r)
fmt.Printf("%%c = %c\n", r)
}
rune 输出的是数字,要转化要string强制转化或者使用 %c 格式化
func main() {
word := "Hello"
letter := rune(word[0])
fmt.Println(letter)
fmt.Printf("%s\n", letter)
fmt.Printf("%c\n", letter)
fmt.Printf("%s\n", string(letter))
}
func main() {
word := "Hello"
letter := rune(word[0])
fmt.Println(letter)
fmt.Printf("%s\n", letter)
fmt.Printf("%c\n", letter)
fmt.Printf("%s\n", string(letter))
}
string的强制转化,转化数字也是可以的,类似于Python的char
func main() {
for i := 65; i <= 122; i++ {
fmt.Println(i, " - ", string(i), " - ", i%12)
}
}
这样输出的就是 a b c d
struct 结构体
见如下代码
package main
import "fmt"
type person struct {
First string
Last string
Age int
}
type doubleZero struct {
person
LicenseToKill bool
}
func (d doubleZero) show() {
fmt.Printf("First %s, Last %s, Age %d\n", d.First, d.Last, d.Age)
}
func main() {
p1 := doubleZero{
person{
"A",
"b",
18,
},
true,
}
p2 := doubleZero{
person: person{
First: "c",
Last: "d",
Age: 111,
},
LicenseToKill: false,
}
p1.show()
p2.show()
}
关于p2 一开始看到代码疑惑,为什么p2:直接冒号了,少了等于,不应该是 := 创建吗,后来写了p1才知道,相当于是一个命名了,所以才不需要等于号,简写就是p1的形式,不需要参数名
对于一个结构体,或者说类,定义他的结构体方法/类方法,就是 func (p 结构体名) 函数名
相当于是吧一个函数,绑定到结构体实例里面,所以是这样子写法,func 之后马上跟上括号的结构体实例,然后才是函数名
变量和函数大写小写的问题
比如这样两段代码
package main
import (
"encoding/json"
"fmt"
)
type person struct {
First string
Last string
Age int
notExported int
}
func main() {
p1 := person{"James", "Bond", 20, 007}
bs, _ := json.Marshal(p1)
//fmt.Println(bs)
//fmt.Printf("%T \n", bs)
fmt.Println(string(bs))
}
这个可以正常json.dumps 但是这个
package main
import (
"encoding/json"
"fmt"
)
type person struct {
first string
last string
age int
}
func main() {
p1 := person{"James", "Bond", 20}
//fmt.Println(p1)
bs, _ := json.Marshal(p1)
fmt.Println(string(bs))
}
就dumps 出来是{} 找了半天,发现区别就是 第一个的变量是大写的,只有大写的,才能导入
这是因为在go中,大写开头的变量和函数,才是能被包外的访问,才是可见,可以作为公共api 如果是小写开头,那么他们就是未被导出的,只能在【定义他们的包内部访问】,对于包外是不可见的,他们具有包的私有访问限制
同样的规则也适用于,函数,类型,接口,结构体的字段和方法可见性 go的包名(导入Import的最后一部分)也遵循这个原则,必须大写开头,以便其他的包能够导入他
package mypackage
// 导出的变量,可以被其他包访问
var ExportedVar int = 10
// 未导出的变量,仅在当前包内可见
var unexportedVar int = 20
// 导出的函数,可以被其他包调用
func ExportedFunc() {}
// 未导出的函数,仅在当前包内可见
func unexportedFunc() {}
tag
json 包定义的时候,还可以指定tag
package main
import (
"encoding/json"
"fmt"
)
type persion struct {
FirstName string
LastName string `json:"-"`
Age int `json:"wisdom score"`
Num int `json:"hello,omitempty"`
Num_str string `json:"num_str,omitempty"`
noImport string
}
func main() {
p1 := persion{
"a",
"b",
18,
0,
"",
"c",
}
bs, _ := json.Marshal(p1)
fmt.Println(bs)
fmt.Println(string(bs))
}
注意几个点:
- 结构体定义的时候,最后一个参数一定要写上,逗号,不写是错误的
- bs, _ := json.Marshal(p1) 这个是冒号等于 ,刚才第一下写成等于是错误的
- json包的tag 是 `` 然后里面包裹上json:"" 你要写的内容
这个json:"" 里面可以写新的名字,然后表示重命名 或者写上 - 代表忽略 或者写上忽略的 omitempty
注意一个特别的点:反引号 json:"num_str,omitempty" 这里面千万不能有空格,不能写成 json:"num_str, omitempty" 只要带了空格,那么就不会被识别,会被完全输出
由于Num和Num_str 都是零值,所以不会被输出,对的,甚至包括如果int等于0的话,也算零值,不会被输出
{"FirstName":"a","wisdom score":18}
其实很奇怪,为什么要弄出这么多选择,尤其是omieempty 如果为空省略的话,难道不会造成结构的不一致吗,缺少了某个字段
说省略零值,确实会导致结果的不一致,但是好处在于,向后兼容性,新增字段可能会导致向后兼容性,通过使用 omitempty 只有在有值的时候才包含他,可以避免不必要的版本升级 性能:主要可以减小序列化后的数据大小
其他的还有的选项比如
反序列化
使用 json.Unmarshar(bs, &p1) 注意bs的定义是 []byte(``) 这里面使用的是反引号
type person struct {
First string
Last string
Age int
}
func main() {
var p1 person
bs := []byte(`{"First":"a", "Last":"b", "Age": 3}`)
json.Unmarshal(bs, &p1)
fmt.Println(bs)
fmt.Println(string(bs))
fmt.Println(p1.First)
}
同样的如果带了tag的话,那么就是按照带的tag 去解析
type person struct {
FirstName string
LastName string `json:"-"`
Age int `json:"wisdom score"`
Num int `json:"hello,omitempty"`
Num_str string `json:"num_str,omitempty"`
noImport string
}
func main() {
var p1 person
bs := []byte(`{"FirstName":"a","LastName":"b", "wisdom score":18,"hello":0,"num_str":"","noImport": 123, "xxxx": false}
`)
json.Unmarshal(bs, &p1)
//fmt.Println(string(p1))
fmt.Printf("%v\n", p1)
fmt.Println("FirstName", p1.FirstName)
fmt.Println("LastName", p1.LastName)
fmt.Println("Age", p1.Age)
fmt.Println("Num", p1.Num)
fmt.Println("Num_str", p1.Num_str)
fmt.Println("noImport", p1.noImport)
fmt.Printf("%+v\n", p1)
}
之所以这么长的代码,是因为之前的那个代码错了,之前的是string(bs) 而不是string 结构式 是不能对结构体string的 另外发现 %v 输出的话,是没有带上字段值的,所以才自己写了这么多的println
后来查询发现,只要带上+就能正确输出了 fmt.Printf("%+v\n", p1) 即可正确输出
{FirstName:a LastName: Age:18 Num:0 Num_str: noImport:}
可以看出,我里面还带了结构体中不包含的其他值,但是在loads的时候也不会报错 说明如果json里面有多值只是不会解析,但是不会报错
json的Marshal 和 NewEncode
NewEncode 和 Marshal 是类似的
type person struct {
First string
Last string
}
func main() {
p1 := person{
"a",
"b",
}
fmt.Printf("%+v\n", p1)
json.NewEncoder(os.Stdout).Encode(p1)
var p2 person
rdr := strings.NewReader(`
{
"First": "c",
"Last": "d"
}
`)
//rdr := strings.NewReader(`{"First": "c","Last": “d”}`)
json.NewDecoder(rdr).Decode(&p2)
fmt.Printf("%+v\n", p2)
data, err := json.Marshal(p1)
if err != nil {
log.Fatal(err)
}
err = os.WriteFile("data.json", data, 0644)
if err != nil {
log.Fatal(err)
}
}
区别就是,Marshal返回包含一个json序列的字符串切片,然后如果要写入网络流或者文件流的话,需要手动再写入一次 NewEncoder 创建一个Encode的话,能直接写入到任何一个io.Write中,例如 os.Stdout 或者 net.conn
例如Marshal要写入网络流要加上
type person struct {
First string
Last string
}
func main() {
p1 := person{
"a",
"b",
}
fmt.Printf("%+v\n", p1)
json.NewEncoder(os.Stdout).Encode(p1)
var p2 person
rdr := strings.NewReader(`
{
"First": "c",
"Last": "d"
}
`)
//rdr := strings.NewReader(`{"First": "c","Last": “d”}`)
json.NewDecoder(rdr).Decode(&p2)
fmt.Printf("%+v\n", p2)
data, err := json.Marshal(p1)
if err != nil {
log.Fatal(err)
}
err = os.WriteFile("data.json", data, 0644)
if err != nil {
log.Fatal(err)
}
f, err := os.Create("data2.json")
if err != nil {
log.Fatal(err)
}
defer f.Close()
err = json.NewEncoder(f).Encode(p1)
if err != nil {
log.Fatal(err)
}
}
注意go 1.16或者更低的版本,要使用 io.WriteFile 后面的新版本都是使用 os.WriteFile
21 interface
接口的一个典型代码如下
package main
import (
"fmt"
"math"
)
type circle struct {
radius float64
}
type square struct {
side float64
}
type shape interface {
area() float64
}
func (c circle) area() float64 {
return math.Pi * c.radius * c.radius
}
func (c square) area() float64 {
return c.side * c.side
}
func info(c shape) {
fmt.Println(c)
fmt.Println(c.area())
}
func totalArea(shapes ...shape) (area float64) {
for _, s := range shapes {
area += s.area()
}
return
}
func main() {
s := square{10}
c := circle{10}
info(s)
info(c)
fmt.Println("Total Area: ", totalArea(s, c))
}
注意的是,这里area的函数定义和普通的不一样,普通的是 func 函数名(参数)...
而area 由于是接口中的函数,所以是 func (实例名 结构体名) 函数名(参数)这种形式
另外现在对...的使用更深刻了,...是跟在实例名后面的,然后才是类型,不是 shapes shape... 而是 shapes... shape 代表好多好多省略到的shape 类型都是shapes
这种接口的一个典型用法就是在io.Copy里面,不管是string还是byte 类型都实现了io.Copy接口
func main() {
msg := "Do not dwell in the past, do not dream of the future, concentrate the mind on the present."
rdr := strings.NewReader(msg)
io.Copy(os.Stdout, rdr)
rdr2 := bytes.NewBuffer([]byte(msg))
io.Copy(os.Stdout, rdr2)
//res, _ := http.Get("http://www.mcleods.com")
//io.Copy(os.Stdout, res.Body)
//res.Body.Close()
}
sort的用法
集中的用法如下
package main
import (
"fmt"
"sort"
)
type name []string
type name2 = []string
func (n name) Len() int { return len(n) }
func (n name) Swap(i, j int) { n[i], n[j] = n[j], n[i] }
func (n name) Less(i, j int) bool { return n[i] < n[j] }
//func (n name2) Len() int { return len(n) }
//func (n name2) Swap(i, j int) { n[i], n[j] = n[j], n[i] }
//func (n name2) Less(i, j int) bool { return n[i] < n[j] }
func main() {
a := []int{3, 1, 4, 5, 2}
sort.Ints(a)
fmt.Println(a)
sort.Sort(sort.IntSlice(a))
fmt.Println(a)
sort.Sort(sort.Reverse(sort.IntSlice(a)))
fmt.Println(a)
b := []string{"a", "aa", "aaa", "aaaa"}
sort.Strings(b)
fmt.Println(b)
sort.Sort(sort.Reverse(sort.StringSlice(b)))
fmt.Println(b)
fmt.Println("-------")
c := name{"a", "aa", "aaa", "aaaa"}
sort.Strings(c)
fmt.Println(c)
sort.Sort(sort.Reverse(sort.StringSlice(c)))
fmt.Println(c)
fmt.Println("-------")
d := name2{"a", "aa", "aaa", "aaaa"}
sort.Strings(d)
fmt.Println(d)
sort.Sort(sort.Reverse(sort.StringSlice(d)))
fmt.Println(d)
}
这里面注意几个点
第一个 name 是创建一个全新的类型,name2 是[]string的别名,实际上还是string
所以你如果对name2 实现 len less swap 的话,会报错,因为[]string本身已经实现了这些接口 因为name 是全新类型,所以必须要求实现下面三个函数
- Len
- Swap 注意这里函数的参数都是int类型的,用于在数组中调换位置
- Less
第二个,初始化name的时候,不是 []name类型初始化了,而是直接 name{...}来初始化
第三个,关于嵌套的sort
sort.Sort(sort.Reverse(sort.StringSlice(c)))
sort 本身可以sort.Ints 和sort.String 但是这个只能升序排列,你要降序的话,就得这么来操作
sort.StringSlice 是将string类型的切片,转化为 sort.Interface 接口,任何实现了sort.Interface 接口的就可以排序了 sort.Reverse 接受一个sort.Interface 接口,并对他进行反向排序 sort.Sort 接受一个sort.Interface 接口并对他进行排序
sort.Sort(sort.Reverse(sort.StringSlice(c))) 为什么不写成 sort.Reverse(sort.Sort(sort.StringSlice(c))) 呢,先排序然后倒叙呢?
因为sort.Reverse 是返回一个新的类型,这个类型在sort.Sort的时候会自动逆序排序,sort.Reverse 不是一个排序函数,而是一个对结果进行逆序操作的函数