继续接着榜首部分,上面咱们讲到了复合数据类型的数组,现在咱们接着讲跟数组休戚相关的切片,切片其实跟js的数组概念很像

榜首部分:后端言语很难?前端入门go根底语法只需求3小时!(上)

顺便给咱们看下,node和go的功用比照

后端语言很难?前端入门go基础语法只需要3小时!(中)

就上图的三个指标来说的话,前端的话,express一触即溃。。。(node中最快的前端结构是fastify,比上图的koa快一些,但不是许多)

想要go材料的直接私信我,我发给你,包括书和视频,一同go!

切片

Slice(切片)代表变长的序列,序列中每个元素都有相同的类型。一个slice类型一般写作[]T,其间T代表slice中元素的类型;slice的语法和数组很像,只是没有固定长度而已。

数组和slice之间有着严密的联系。一个slice是一个轻量级的数据结构,供给了拜访数组子序列(或者悉数)元素的功用,而且slice的底层的确引证一个数组目标。一个slice由三个部分构成:指针、长度和容量。指针指向榜首个slice元素对应的底层数组元素的地址,要留意的是slice的榜首个元素并不一定便是数组的榜首个元素。长度对应slice中元素的数目;长度不能超过容量,容量一般是从slice的开端方位究竟层数据的结尾方位。内置的len和cap函数别离回来slice的长度和容量。

后端语言很难?前端入门go基础语法只需要3小时!(中)

咱们讲讲指针,长度和容量。

在 Go 言语中,一个 slice 是由以下三部分组成的:

  • 指针: 指向底层数组中榜首个元素的地址
  • 长度: slice 中元素的数量
  • 容量: 从榜首个元素开端,slice 能够拜访的元素总数

这三部分共同组成了一个 slice。指针和长度组成了 slice 的视图,指向底层数组的一段接连的元素,而容量则是指 slice 最多能够拜访的元素数。

这个长度和容量是能够经过len(s)cap(s) 函数来获徖的。

例如:

arr := [5]int{1, 2, 3, 4, 5}
s := arr[1:3]
fmt.Println("length:", len(s))
fmt.Println("capacity:", cap(s))

榜首行,创立了一个底层数组,第二行创立了一个 slice s, 包括了底层数组中的第二个和第三个元素,所以s的长度为2,容量为4,因为s 能够拜访究竟层数组的第二个和第三个元素,还能够拜访第四个和第五个元素。

类比js,非常需求留意的是,slice竟然能够同享底层的数据,在js里是不存在根底值还能同享的

多个slice之间能够同享底层的数据,而且引证的数组部分区间可能堆叠。图4.1显现了表明一年中每个月份名字的字符串数组,还有堆叠引证了该数组的两个slice。数组这样界说

months := [...]string{1: "January", /* ... */, 12: "December"}

因而一月份是months[1],十二月份是months[12]。一般,数组的榜首个元素从索引0开端,可是月份一般是从1开端的,因而咱们声明数组时直接跳过第0个元素,第0个元素会被主动初始化为空字符串。

slice的切片操作s[i:j],其间0 ≤ i≤ j≤ cap(s),用于创立一个新的slice,引证s的从第i个元素开端到第j-1个元素的子序列。新的slice将只有j-i个元素。假如i方位的索引被省掉的话将运用0代替,假如j方位的索引被省掉的话将运用len(s)代替。因而,months[1:13]切片操作将引证悉数有用的月份,和months[1:]操作等价;months[:]切片操作则是引证整个数组。让咱们别离界说表明第二季度和北方夏天月份的slice,它们有堆叠部分:

后端语言很难?前端入门go基础语法只需要3小时!(中)

Q2 := months[4:7]
summer := months[6:9]
fmt.Println(Q2)     // ["April" "May" "June"]
fmt.Println(summer) // ["June" "July" "August"]

这意味着,其间一个切片改变数据,另一个切片也会受到影响.举个比如

package main
import (
	"fmt"
)
func main() {
	month := []string{"1", "2", "3"}
	s1 := month[0:2]
	s2 := month[1:3]
	s1[1] = "1"
	fmt.Println(s1, s2) // [1 1] [1 3]
}

要留意的是slice类型的变量s和数组类型的变量a的初始化语法的差异。slice和数组的字面值语法很类似,它们都是用花括弧包括一系列的初始化元素,可是关于slice并没有指明序列的长度。这会隐式地创立一个适宜大小的数组,然后slice的指针指向底层的数组。

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

和数组不同的是,slice之间不能比较,因而咱们不能运用==操作符来判别两个slice是否含有悉数持平元素。不过规范库供给了高度优化的bytes.Equal函数来判别两个字节型slice是否持平([]byte),可是关于其他类型的slice,咱们有必要自己打开每个元素进行比较:

func equal(x, y []string) bool {
    if len(x) != len(y) {
        return false
    }
    for i := range x {
        if x[i] != y[i] {
            return false
        }
    }
    return true
}

咱们讲解一下为啥slice不能直接比较:

在 Go 言语中,slice 是对数组的引证,因而它们包括指向底层数组的指针、长度和容量三个部分。在进行比较时,这三个部分都有必要完全相同才干视为持平,而在实践运用中,这很难做到。因而在 Go 言语中,slice 是不能直接进行比较的

而且因为是引证,值随时会变,这就有点搞了。。。

slice唯一合法的比较操作是和nil比较

if summer == nil { /* ... */ }

一个零值的slice等于nil。一个nil值的slice并没有底层数组。一个nil值的slice的长度和容量都是0,可是也有非nil值的slice的长度和容量也是0的。假如你需求测验一个slice是否是空的,运用len(s) == 0来判别,而不应该用s == nil来判别。

内置的make函数创立一个指定元素类型、长度和容量的slice。容量部分能够省掉,在这种情况下,容量将等于长度。

make([]T, len)
make([]T, len, cap) // same as make([]T, cap)[:len]

在底层,make创立了一个匿名的数组变量,然后回来一个slice;只有经过回来的slice才干引证底层匿名的数组变量。在榜首种句子中,slice是整个数组的view。在第二个句子中,slice只引证了底层数组的前len个元素,可是容量将包括整个的数组。额定的元素是留给未来的增加用的。

这儿咱们顺带介绍一下make语法:

make

make 是 Go 言语中的一个内建函数,它首要用来创立并初始化内置的数据类型。它能够用来创立:

  • slice
  • map
  • channel

语法格局如下:

make(type, size, capacity)
  • type 指定要创立的数据类型,能够是 slicemapchannel
  • size 指定创立的数据类型的长度。关于 slicemap,它指定了初始元素的数量;关于 channel,它指定了缓存大小。
  • capacity 首要用于创立 slice,它指定了底层数组的大小,即最多能够包容的元素数量。

示例:

// create a slice
s := make([]int, 5, 10)
fmt.Println(s) // [0 0 0 0 0]
// create a map
m := make(map[string]int, 100)
fmt.Println(m) // map[]
// create a channel
c := make(chan int, 10)
fmt.Println(c) //0xc420012010

留意:

  • 运用 make 创立的 map 和 slice 都是能够直接运用的,不需求再次初始化。
  • make 创立的 map,默许都是 nil, 所以不能直接对其进行操作, 需求先刺进元素。
  • 在运用 make 创立 slice 的时分,假如省掉 capacity 参数,则默许与 size 相同。

比照js,js创立数组和一般目标比go便利许多,毕竟有数组和目标字面量

Map

在Go言语中,一个map便是一个哈希表的引证,map类型能够写为map[K]V,其间K和V别离对应key和value。map中一切的key都有相同的类型,一切的value也有着相同的类型,可是key和value之间能够是不同的数据类型。其间K对应的key有必要是支撑==比较运算符的数据类型,所以map能够经过测验key是否持平来判别是否现已存在。尽管浮点数类型也是支撑持平运算符比较的,可是将浮点数用做key类型则是一个坏的想法。

内置的make函数能够创立一个map:

ages := make(map[string]int) // mapping from strings to ints

咱们也能够用map字面值的语法创立map,同时还能够指定一些开端的key/value:

ages := map[string]int{
    "alice":   31,
    "charlie": 34,
}

这相当于

ages := make(map[string]int)
ages["alice"] = 31
ages["charlie"] = 34

运用内置的delete函数能够删去元素:

delete(ages, "alice") // remove element ages["alice"]

类比js:这个跟js差距仍是很大的,跟ts也是,咱们知道js能够界说一个map,然后map的key和value基本上恣意类型都能够,而且能够界说key为引证类型,布尔值,string都行,而且不用都是相同的类型,go则强制有必要是相同的类型,因为go的类型体系不像ts有联合类型

要想遍历map中悉数的key/value对的话,能够运用range风格的for循环完结,和之前的slice遍历语法类似。下面的迭代句子将在每次迭代时设置name和age变量,它们对应下一个键/值对:

for name, age := range ages {
    fmt.Printf("%s\t%d\n", name, age)
}

Map的迭代次序是不确定的,而且不同的哈希函数完结可能导致不同的遍历次序。在实践中,遍历的次序是随机的,每一次遍历的次序都不相同。这是成心的,每次都运用随机的遍历次序能够强制要求程序不会依赖详细的哈希函数完结。假如要按次序遍历key/value对,咱们有必要显式地对key进行排序,能够运用sort包的Strings函数对字符串slice进行排序。下面是常见的处理办法:

import "sort"
var names []string
for name := range ages {
    names = append(names, name)
}
sort.Strings(names)
for _, name := range names {
    fmt.Printf("%s\t%d\n", name, ages[name])
}

类比js这个跟js里的map遍历,显现的key不同,js有一套自己的次序,比方数字字符串比字母的靠前,反正不是依据添加到目标的次序遍历的,比方说:

let o = { age: 14, 1: 'zhangsan' }
Object.keys(o) // ['1', 'age']

但js能够经过Map的数据结构保证遍历次序。

继续go的内容。

因为咱们一开端就知道names的终究大小,因而给slice分配一个适宜的大小将会更有用。下面的代码创立了一个空的slice,可是slice的容量刚好能够放下map中悉数的key:

names := make([]string, 0, len(ages))

map类型的零值是nil,也便是没有引证任何哈希表。

var ages map[string]int
fmt.Println(ages == nil) // "true"
fmt.Println(len(ages) == 0) // "true"

map上的大部分操作,包括查找、删去、len和range循环都能够安全工作在nil值的map上,它们的行为和一个空的map类似。可是向一个nil值的map存入元素将导致一个panic反常:

ages["carol"] = 21 // panic: assignment to entry in nil map

在向map存数据前有必要先创立map。

经过key作为索引下标来拜访map将发生一个value。假如key在map中是存在的,那么将得到与key对应的value;假如key不存在,那么将得到value对应类型的零值,正如咱们前面看到的ages[“bob”]那样。这个规则很实用,可是有时分可能需求知道对应的元素是否真的是在map之中。例如,假如元素类型是一个数字,你能够需求区别一个现已存在的0,和不存在而回来零值的0,能够像下面这样测验:

age, ok := ages["bob"]
if !ok { /* "bob" is not a key in this map; age == 0. */ }

在这种场景下,map的下标语法将发生两个值;第二个是一个布尔值,用于陈述元素是否真的存在。布尔变量一般命名为ok,特别适宜马上用于if条件判别部分。

你会经常看到将这两个结合起来运用,像这样:

if age, ok := ages["bob"]; !ok { /* ... */ }

和slice相同,map之间也不能进行持平比较;唯一的例外是和nil进行比较。要判别两个map是否包括相同的key和value,咱们有必要经过一个循环完结:

func equal(x, y map[string]int) bool {
    if len(x) != len(y) {
        return false
    }
    for k, xv := range x {
        if yv, ok := y[k]; !ok || yv != xv {
            return false
        }
    }
    return true
}

Go言语中并没有供给一个set类型,可是map中的key也是不相同的,能够用map完结类似set的功用。

类比js,我感觉es6后的js,数据类型结构丰富了非常多,从这个层面上讲,加上灵活性,我觉得是比go的数据结构更有优势的

结构体

结构体是一种聚合的数据类型,是由零个或多个恣意类型的值聚合成的实体。每个值称为结构体的成员。用结构体的经典事例处理公司的职工信息,每个职工信息包括一个唯一的职工编号、职工的名字、家庭住址、出生日期、工作岗位、薪资、上级领导等等。一切的这些信息都需求绑定到一个实体中,能够作为一个全体单元被复制,作为函数的参数或回来值,或者是被存储到数组中,等等。

比照js,其实结构体跟咱们js的目标的概念要更接近,因为值的类型能够是各种类型的聚合,这种类型对面向目标是更适宜的,毕竟你创立的目标不一定都是一个类型

下面两个句子声明晰一个叫Employee的命名的结构体类型,而且声明晰一个Employee类型的变量dilbert:

type Employee struct {
    ID        int
    Name      string
    Address   string
    DoB       time.Time
    Position  string
    Salary    int
    ManagerID int
}
var dilbert Employee

dilbert结构体变量的成员能够经过点操作符拜访,比方dilbert.Name和dilbert.DoB。因为dilbert是一个变量,它一切的成员也同样是变量,咱们能够直接对每个成员赋值:

dilbert.Salary -= 5000 // demoted, for writing too few lines of code

或者是对成员取地址,然后经过指针拜访:

position := &dilbert.Position
*position = "Senior " + *position // promoted, for outsourcing to Elbonia

点操作符也能够和指向结构体的指针一同工作:

var employeeOfTheMonth *Employee = &dilbert
employeeOfTheMonth.Position += " (proactive team player)"

咱们再举个更简略的比如,介绍结构体:

在 Go 言语中,结构体(struct)是一种用于组合数据的类型。结构体是一组命名字段,每个字段都有一个类型。

一个结构体能够由以下语法界说:

type structName struct {
    field1 type1
    field2 type2
    field3 type3
    ...
}

例如:

type Person struct {
    Name string
    Age int
    Gender string
}

结构体字段能够经过.来拜访, 比方:

p := Person{Name: "John", Age: 25, Gender: "male"}
fmt.Println(p.Name) // Output: John

JSON

类比js:json跟js里的差不多,因为json的规范是一个公共的协议,每个言语都要恪守,比方说json里的引证类型只能是数组和一般目标,比方函数是不可的,所以你能够了解一下js中的json语法,对你了解go的json语法大有裨益。

JSON是对JavaScript中各种类型的值——字符串、数字、布尔值和目标——Unicode本文编码。它能够用有用可读的办法表明第三章的根底数据类型和本章的数组、slice、结构体和map等聚合数据类型。

基本的JSON类型有数字(十进制或科学记数法)、布尔值(true或false)、字符串,其间字符串是以双引号包括的Unicode字符序列,支撑和Go言语类似的反斜杠转义特性。

Go言语规范库中包括了一个名为 “encoding/json” 的包,用于处理 JSON 数据。它供给了编码和解码 JSON 数据的函数。

运用该包能够便利地将 Go 中的结构体编码为 JSON 字符串,也能够将 JSON 字符串解码为 Go 中的结构体。

编码:

package main
import (
    "encoding/json"
    "fmt"
)
type User struct {
    Name string
    Age  int
}
func main() {
    user := &User{
        Name: "Bob",
        Age:  30,
    }
    jsonData, err := json.Marshal(user)
    if err != nil {
        fmt.Println("Error encoding JSON:", err)
        return
    }
    fmt.Println(string(jsonData))
}

假如你想让打印的json字符串有缩进,能够运用 MarshalIndent

data, err := json.MarshalIndent(user, "", " ")

类比js json.Marshal相当于咱们的JSON.stringify,可是需求留意, json.Marshal传入的是一个目标指针,而不是目标,至于为什么这么做,是因为go里边的目标参数,都会复制一份传入的参数,为了节约空间和时间,一般都传的是指针。

解码:

package main
import (
    "encoding/json"
    "fmt"
)
type User struct {
    Name string
    Age  int
}
func main() {
    jsonData := []byte(`{"Name":"Bob","Age":30}`) // 不理解byte类型无所谓,首要是看json的操作
    var user User
    err := json.Unmarshal(jsonData, &user)
    if err != nil {
        fmt.Println("Error decoding JSON:", err)
        return
    }
    fmt.Println(user)
}

经过上面的代码咱们能够看到,json包供给了 Marshal 和 Unmarshal 两个函数来完结编码和解码操作。

在上面的比如中,Marshal 函数将 User 结构体编码为 JSON 数据, Unmarshal 函数将 JSON 数据解码为 User 结构体。

需求留意的是,假如你计划将 Go 中的结构体编码为 JSON,这些结构体的字段名有必要是可导出的(即首字母大写)

类比js:相当于js的JSON.pase,可是go的json.Unmarshal第二个参数需求传一个指针,把榜首个参数目标的值赋予给它,其实咱们js内部也是这样完结的,只不过js内部帮咱们主动传入的第二个参数,暴露的API让整个API看起来更简略

go规范库中template

Go 的规范库中的 “template” 包供给了用于生成文本输出的功用。它能够用来生成 HTML、XML、JSON 等格局的文本输出。

运用 “template” 包时,首先需求创立一个模板,其间能够包括文本和特别的指令。指令以”{{}}” 来标识,用于刺进变量的值或履行逻辑操作。

例如:

package main
import (
	"os"
	"text/template"
)
func main() {
	tmpl, err := template.New("test").Parse("Hello, {{.Name}}!")
	if err != nil {
		panic(err)
	}
	err = tmpl.Execute(os.Stdout, map[string]string{"Name": "Alice"})
	if err != nil {
		panic(err)
	}
}

在上面的比如中,运用了 “template.New” 和 “Parse” 函数创立了一个名为 “test” 的模板,并用 “Hello, {{.Name}}!” 作为模板内容。然后,运用 “Execute” 函数将模板和数据结构(这儿是一个map)进行绑定,并将生成的文本输出到规范输出。

类比js: node中也有许多模板库,但都是第三方的,go属于内置的模板,js是有模板语法,可是js里的模板语法没有流程操控能力,比方写if else(当然能够用三元运算符模仿,因为三元运算是一个表达式,不是句子),写循环,所以go的模板语法更像是node里的模板库

操控流指令

模板包供给了一些操控流指令,例如 “if”、”range” 等,能够用来履行条件和循环操作。

{{if .IsAdmin}}
    <p>You are an admin</p>
{{else}}
    <p>You are not an admin</p>
{{end}}
{{range .Items}}
    <li>{{.}}</li>
{{end}}

模板函数

能够在模板中运用自界说函数来履行杂乱的操作。需求先在 Go 代码中界说函数,然后将其注册到模板中。

func double(n int) int {
	return n * 2
}
tmpl := template.Must(template.New("test").Funcs(template.FuncMap{
	"double": double,
}).Parse("{{double .Value}}"))
data := map[string]int{"Value": 5}
tmpl.Execute(os.Stdout, data)

函数

Go言语中的函数是一种能够被命名并能够被重复调用的代码块。在 Go 中,函数是一等公民,能够被赋值给变量、作为参数传递给其他函数,也能够作为其他函数的回来值。

函数的界说格局如下:

func function_name(parameter_list) return_type {
    // function body
}

其间,function_name 是函数的称号,parameter_list 是参数列表,return_type 是回来值类型。

例如,下面是一个求两个整数和的函数:

func add(a int, b int) int {
    return a + b
}

在 Go 中,函数能够回来多个值。例如,下面的函数回来两个值:

func swap(a, b int) (int, int) {
    return b, a
}

函数也能够有可选的回来值称号,如下面的比如:

func split(sum int) (x, y int) {
    x = sum * 4 / 9
    y = sum - x
    return
}

在 Go 中,函数能够承受一个或多个参数,也能够回来一个或多个值。参数和回来值都能够有类型,也能够省掉类型。

Go 中还有一种特别的函数类型,称为高阶函数。高阶函数是一种承受函数作为参数或回来函数作为回来值的函数。这种函数能够做许多风趣的事情,例如函数式编程。

实参经过值的办法传递,因而函数的形参是实参的拷贝。对形参进行修改不会影响实参。可是,假如实参包括引证类型,如指针,slice(切片)、map、function、channel等类型,实参可能会因为函数的简介引证被修改。

类比js:js的函数值类型能够看干事值传递,引证类型是引证的指针传递,go里边复制实参的值给形参

你可能会偶然遇到没有函数体的函数声明,这表明该函数不是以Go完结的。这样的声明界说了函数标识符。

package math
func Sin(x float64) float //implemented in assembly language

递归

函数能够是递归的,这意味着函数能够直接或直接的调用自身。对许多问题而言,递归是一种强有力的技术,例如处理递归的数据结构。

类比js:因为go言语里,函数是一等公民,跟js是相同,能够当做参数和回来值相同传递给函数,所以这点上来说是没啥区别的

可是留意的是,go里边的想设置递归的struct的时分,需求用到指针,如下的Node结构体中的FirstChild特点,它要递归Node的话,类型是指针

type Node struct {
    Type                    NodeType
    Data                    string
    Attr                    []Attribute
    FirstChild              *Node
}

多回来值

go中的多回来值其实跟js数组结构功用类似,可是go更倾向于说,多回来值中第二个回来值回来err,也便是过错。

例如,在 Go 中,许多规范库函数都会回来两个值,其间第二个值是 error 类型,用于表明是否发生了过错。

例如:

file, err := os.Open("example.txt")

在这个比如中,函数os.Open()会回来一个指向文件的指针以及一个error,假如文件打开成功,err的值便是nil,不然为过错信息

类比js:这个没啥好说的,跟咱们前端的解构赋值差不多

过错

在 Go 中,过错是一种特别的类型,一般用于陈述函数调用中发生的问题。Go 规范库中界说了一个名为 error 的接口类型,该类型包括一个名为 Error 的办法,该办法回来一个字符串,表明过错信息。

在 Go 中,许多函数都会回来一个 error 类型的值,表明函数是否成功履行。假如函数回来的过错为 nil,则表明函数调用成功,不然表明函数调用失利,过错为非nil,则表明函数调用失利,过错信息经过 error 类型的值回来。

例如,下面的代码运用 ioutil.ReadFile 函数读取文件,并判别函数调用是否成功:

content, err := ioutil.ReadFile("example.txt")
if err != nil {
    log.Fatal(err)
}
fmt.Println(string(content))

假如文件读取成功,err 的值为 nil,代码将继续履行。不然,调用 log.Fatal(err) 停止程序并打印过错信息。

类比js:这个最好跟node类比,node也是过错优先,特别调用一些io操作,go基本上也是这样的思维,所以前端能够学习这种思维,这种写法如同也有同学写过文章,大约意思是,假如回来没有过错就回来 [null, data],假如有过错就回来[err, null] (也便是数组榜首项代表error,第二项代表数据),这个思维能够用在async函数上

函数值

在Go中,函数被看作榜首类值(first-class values):函数像其他值相同,具有类型,能够被赋值给其他变量,传递给函数,从函数回来。对函数值(function value)的调用类似函数调用。比如如下:

func square(n int) int { return n * n }
func negative(n int) int { return -n }
func product(m, n int) int { return m * n }
f := square
fmt.Println(f(3)) // "9"
f = negative
fmt.Println(f(3)) // "-3"
fmt.Printf("%T\n", f) // "func(int) int"
f = product // compile error: can't assign func(int, int) int to func(int) int

函数类型的零值是nil。调用值为nil的函数值会引起panic过错:

var f func(int) int
f(3) // 此处f的值为nil, 会引起panic过错

函数值能够与nil比较。

类比js: 这个没啥特别的,跟ts的函数界说相同

匿名函数

在 Go 中,匿名函数是一种特别的函数,它没有称号。匿名函数能够赋值给一个变量,也能够直接调用。

匿名函数的语法如下:

func(参数列表)(回来值列表){
    函数体
}

例如,下面是一个匿名函数,该函数承受两个 int 类型参数并回来它们的和:

func(a, b int) int {
    return a + b
}

这个函数能够赋值给一个变量并调用,如下:

add := func(a, b int) int {
    return a + b
}
result := add(1, 2)

类比js:跟js差不多,也有匿名函数,用法也差不多。

匿名函数在 Go 中有许多用途。例如,能够运用匿名函数来完结回调函数,也能够在函数中创立并直接调用匿名函数,还能够将匿名函数作为参数传递给另一个函数。

例如,下面是一个运用匿名函数完结回调函数的比如:

package main
import "fmt"
func visit(numbers []int, callback func(int)) {
    for _, n := range numbers {
        callback(n)
    }
}
func main() {
    visit([]int{1, 2, 3, 4}, func(n int) {
        fmt.Println(n)
    })
}

上面的代码界说了一个名为 visit 的函数,该函数承受一个整型切片和一个回调函数。visit 函数会遍历切片中的一切整数并调用回调函数。在这个比如中,咱们传递了一个匿名函数给 visit 函数,该函数会打印每个整数。

可变参数

在 Go 中,可变参数是指一种特别类型的函数参数,它能够承受恣意多个参数。可变参数一般运用 “…” 来表明,它表明函数能够承受恣意数量的该类型的参数。

可变参数能够在界说函数时运用,例如:

func myFunction(firstArg string, args ...int) {
    // body of the function
}

在这个比如中, myFunction 承受榜首个参数是字符串类型的 firstArg, 后面有恣意个整数参数, 这些整数参数被打包成一个int的slice类型的args传入

类比js:我去,这不便是咱们js里的剩下参数语法吗,也是…,哈哈,搞定!

可变参数能够在调用函数时传入恣意数量的参数,如:

myFunction("Hello", 1, 2, 3)
myFunction("Hello", 1, 2, 3, 4, 5)
myFunction("Hello")

在函数体内部,可变参数被打包成一个切片类型的变量,能够像操作切片相同运用这个变量。

另外需求留意的是,可变参数只能放在函数参数的最后一个方位,在其他方位会报错。

类比js,咱们的剩下参数也是转成了一个数组,js数组便是go里边的切片,又是相同的,爽,去下个知识点喽

Deferred函数

在 Go 中,defer 是一个言语级别的关键字,它能够用来推延函数的履行。运用 defer 时,需求在函数中调用一个名为 defer 的函数,并传入需求推延履行的函数。这个函数会在该函数回来之前履行。

defer 函数一般用来做资源整理工作,例如封闭文件、开释锁、开释内存等。例如,下面的比如运用 defer 来封闭文件:

Copy code
package main
import (
	"fmt"
	"os"
)
func main() {
	file, err := os.Open("file.txt")
	if err != nil {
		fmt.Println(err)
		return
	}
	defer file.Close()
	// Do something with the file
	// ...
}

在这个比如中,咱们运用 defer 来封闭文件,defer 函数会在 main 函数回来之前履行。这样做的好处是即使在处理文件时发生了过错,文件也会被封闭,而不会因为没有封闭文件而导致资源泄露。

总结一下:

你只需求在调用一般函数或办法前加上关键字defer,就完结了defer所需求的语法。当defer句子被履行时,跟在defer后面的函数会被推迟履行。直到包括该defer句子的函数履行完毕时,defer后的函数才会被履行,不论包括defer句子的函数是经过return正常完毕,仍是因为panic导致的反常完毕。你能够在一个函数中履行多条defer句子,它们的履行次序与声明次序相反。

类比js: node里还真没有这种语法,process.nextTick() 来完结类似的效果,但process.nextTick()是在当时事情循环完毕后履行,跟当时函数关系不大,但如同也没人这么用,我猜测是因为node的api自身会主动帮咱们开释资源

Panic过错

Panic是 Go 言语中的一种特别类型的反常,它能够在函数中经过内置函数 panic() 抛出。Panic反常会导致程序当即停止履行,并导致栈追溯。

Panic反常一般用来处理不可康复的过错,例如空指针引证、越界拜访数组、分配内存失利等。

举个比如,假如函数中发现了一个不可康复的过错,能够运用 panic() 函数来抛出反常:

package main
func divide(a, b int) int {
    if b == 0 {
        panic("division by zero")
    }
    return a / b
}

当调用这个函数并传入 b=0 时,函数会抛出一个”division by zero”的反常。这个反常会导致程序停止并发生一个栈追溯(stack trace),栈追溯中包括了发生 Panic 反常的函数的称号和行号,以及调用该函数的函数的称号和行号。

当 panic 反常被触发时,Go 会当即停止当时函数的履行,并回来调用栈上的函数中。这个过程会一直继续直到程序中有一个函数被调用了 recover() 函数为止。

recover() 函数能够用来捕获 panic 反常并处理它,例如经过打印过错信息来记载过错日志。运用 recover() 函数能够防止程序因为未处理的 panic 反常而停止。

需求留意的是,recover 函数只能在 defer 中调用,因为它需求在 panic 反常被触发之前就被调用。

办法

从90年代早期开端,面向目标编程(OOP)就成为了称霸工程界和教育界的编程范式,所以之后简直一切大规模被运用的言语都包括了对OOP的支撑,go言语也不例外。

基于指针目标的办法声明

在 Go 中,办法是一种特别的函数,它是绑定在某个类型上的。在 Go 中,办法的声明格局如下:

func (receiver type) methodName(args) returnType {
    // method body
}

其间,receiver 是办法的接收者,它能够是一个类型的变量或指针,methodName 是办法称号,args 是办法参数,returnType 是办法回来值。

例如,下面是一个界说了一个名为 Employee 的结构体类型和一个名为 RaiseSalary 的办法的比如:

package main
import "fmt"
type Employee struct {
    name string
    salary int
}
func (e *Employee) RaiseSalary(percent int) {
    e.salary = e.salary + e.salary*percent/100
}

在上面的比如中,Employee是一个结构体类型, RaiseSalary 是一个办法,它的接收者是一个 Employee 类型的指针,办法名为 RaiseSalary ,参数为一个整型 percent,没有回来值.

它能够经过指针或值来调用,如下:

    e1:= Employee{"Mark",1000}
    e1.RaiseSalary(10)
    fmt.Println(e1.salary)
    e2:= &Employee{"John",1000}
    e2.RaiseSalary(10)
    fmt.Println(e2.salary)

在这个比如中,e1 是一个值,而 e2 是一个指针,两者都能够调用 RaiseSalary 办法。

类比js: js的目标既能够界说特点,又能够界说办法,可是go的结构体只能界说特点,办法要单独拎出来,经过写明接收体的办法来相关到结构体上

经过嵌入结构体来扩展类型

在 Go 中,能够经过嵌入结构体来扩展类型。这种办法类似于承继,但在 Go 中不存在承继关系。

嵌入结构体的语法如下:

type OuterType struct {
    InnerType
    // other fields
}

例如,下面是一个运用嵌入结构体来扩展类型的比如:

package main
import "fmt"
type Person struct {
    Name string
    Age int
}
type Employee struct {
    Person
    Salary int
}
func main() {
    e := Employee{
        Person: Person{
            Name: "Mark",
            Age: 30,
        },
        Salary: 5000,
    }
    fmt.Println(e.Name)
    fmt.Println(e.Age)
    fmt.Println(e.Salary)
}

在这个比如中, Person 是一个结构体类型, Employee 是另一个结构体类型,它嵌入了 Person 类型。因而, Employee 类型也包括了 Person 类型中的一切字段。

嵌入结构体还能够用来完结类似承继的行为,例如重写嵌入类型中的办法或完结接口。

类比js: js的calss语法是用extends语法完结承继的,实质是经过原型链来完结,是一种纵向的承继,而go没有原型链,类似于规划形式里的桥接形式,或者说组合完结的承继,其实编程原理主流的就那些,一通百通,都差不多

封装

Go言语供给了一种名为封装的特性,其目的是隐藏类型或结构体中的细节,并供给一组接口来操控对它们的拜访。在Go中,封装是经过大写字母开头的字段或办法来完结的。小写字母开头的字段或办法被视为私有的,只能在包内部拜访。而大写字母开头的字段或办法被视为揭露的,能够在包外部拜访。

举个比如,咱们能够界说一个名为Car的结构体来表明轿车,其间包括了一些私有字段,如引擎类型、油量等,以及一些揭露的办法,如发动、停止等。

type Car struct {
    make string
    model string
    year int
    engineType string
    fuelLevel float64
}
func (c *Car) Start() {
    fmt.Println("The car has started.")
}
func (c *Car) Stop() {
    fmt.Println("The car has stopped.")
}
func (c *Car) Drive() {
    fmt.Println("The car is now being driven.")
}

在这个比如中,咱们将结构体中的makemodelyearengineTypefuelLevel字段都设置为私有的。只能经过公有的办法来拜访这些字段。

这样的好处是,咱们能够在包外部拜访结构体的公有办法,而不能直接拜访结构体的私有字段。这样能够有用地保护结构体内部的状况,并确保对结构体的操作是正确和一致的。

类比js,这便是js的问题,类里边不能声明私有变量,新的提案如同是用# 号,封装对错常有必要区别私有和共有变量的,不过ts处理了这个问题,哈哈

接口

类型是对其它类型行为的笼统和概括;因为接口类型不会和特定的完结细节绑定在一同,经过这种笼统的办法咱们能够让咱们的函数愈加灵活和更具有适应能力。

许多面向目标的言语都有类似的接口概念,但Go言语中接口类型的独特之处在于它是满意隐式完结的。也便是说,咱们没有必要关于给定的详细类型界说一切满意的接口类型;简略地具有一些必需的办法就足够了。这种规划能够让你创立一个新的接口类型满意现已存在的详细类型却不会去改变这些类型的界说;当咱们运用的类型来自于不受咱们操控的包时这种规划特别有用。

接口类型

在 Go 中,接口是一种类型,它界说了一组办法。假如一个类型完结了接口中界说的一切办法,那么这个类型就能够被视为完结了该接口。

接口的语法如下:

type InterfaceName interface {
    MethodName1(parameter list) return type
    MethodName2(parameter list) return type
    ...
}

例如,下面是一个简略的接口示例:

package main
import "fmt"
type Shape interface {
    Area() float64
}
type Rectangle struct {
    width, height float64
}
func (r Rectangle) Area() float64 {
    return r.width * r.height
}
func main() {
    r := Rectangle{10, 5}
    var s Shape = r
    fmt.Println("Area of rectangle:", s.Area())
}

在这个比如中, Shape 是一个接口,它界说了一个 Area 办法。Rectangle 是一个结构体类型,它完结了 Shape 接口中界说的 Area 办法。因而,能够将一个 Rectangle 类型的变量赋值给一个 Shape 类型的变量。

Go 中的接口是隐式的,这意味着不需求显式地声明一个类型完结了某个接口,只要完结了接口中的一切办法就能够以为完结了接口中的一切办法就能够以为完结了该接口。

接口还能够用于类型断言和类型转化,以及依赖注入等规划形式。

除此之外,接口也能够包括已完结的办法,也能够界说一个空接口,这样任何类型都是它的完结类型。

接口还没写完,可是我看文字现已是9千500字左右了,太长不利于阅读,就先写到这,这周末争夺写完。