目录
Golang 数组
数组是相同数据类型的集合,数组一旦定义长度不能修改,数组可以通过索引访问
其内部元素。
定义一个数组
var arr1 [3]int
var arr2 = [3]int{
1, 2, 3}
arr3 := [3]int{
1, 2, 3}
slice (切片)
数组的长度是固定的,并不能实现动态扩容。
go语言的切片,可以把切片可以理解为,可变长度的数组,其实它底层就是使用数组实现
的,数组的基础上增加自动扩容机制。切片(Slice) 是一个拥有相同类型元素的可变长度的序列。
声明一个切片和声明一个数组差不多,不给出长度就为slice
var arr1 []int
var arr2 = []int{
1, 2, 3}
arr3 := []int{
1, 2, 3}
切片为引用类型,可以使用make创建
//make([] Type, size, capacity)
arr2 := make([]int, 10)
fmt.Printf("len(arr2): %v\n", len(arr2)) //10
初始化切片
直接初始化
var arr2 = []int{
1, 2, 3}
使用数组对其初始化
var arr1 [3]int
var arr2 = arr1[:]
fmt.Printf("arr2: %T\n", arr2) //arr2: []int
还可以使用数组的一部分其初始化
var arr1 = [7]int{
1, 2, 3, 4, 5, 6, 7}
var arr2 = arr1[0:3]
fmt.Printf("arr2: %v\n", arr2) // arr2: [1 2 3]
切片的crud
add
切片是一个动态数组,可以使用append()函数对其添加元素
arr := []int{
1, 2, 3}
arr = append(arr, 4)
arr = append(arr, 5)
arr = append(arr, 6, 7, 8)
fmt.Printf("arr: %v\n", arr) //arr: [1 2 3 4 5 6 7 8]
delele
go语言中并没有删除切片元素的专用方法,可以使用切片本身的特性来删除元素。
删除下标2位置的元素
arr := []int{
0, 1, 2, 3, 4, 5, 6, 7, 8}
arr = append(arr[:2], arr[3:]...)
删除index位置的元素公式:arr = append(arr[:index], arr[index + 1:]…)
copy
切片是引用类型,通过赋值的方式,会修改原有内容,go提供了copy()函数来拷贝切片。
arr := []int{
0, 1, 2, 3, 4, 5, 6, 7, 18}
var arr2 = make([]int, 9)
copy(arr2, arr)
fmt.Printf("arr2: %v\n", arr2) // arr2: [0 1 2 3 4 5 6 7 18]
Map
初始化map
var mp1 map[string]string //声明一个map,还未分配内存 不能直接使用 mp1 为 nil
var mp2 = map[string]string{
"1": "a"} //声明的同时初始化
mp3 := make(map[string]string)
mp3["apple"] = "苹果"
// mp1["1"] = "一"
// mp1["2"] = "二"
fmt.Printf("mp1: %v\n", mp1) //mp1: map[]
fmt.Printf("mp2: %v\n", mp2) //mp2: map[1:a]
fmt.Printf("mp3: %v\n", mp3) //mp3: map[apple:苹果]
遍历 map
一般使用 for range 遍历
mp3 := make(map[string]string)
mp3["apple"] = "苹果"
mp3["pear"] = "梨"
mp3["banana"] = "香蕉"
for _, value := range mp3 {
fmt.Printf("value: %v\n", value)
}
删除 map元素
使用全局的delete函数
mp1 := make(map[string]string, 10)
mp1["1"] = "1"
mp1["2"] = "2"
mp1["3"] = "3"
delete(mp1, "1")
fmt.Printf("mp1: %v\n", mp1) //mp1: map[2:2 3:3]
Golang 函数
go 中有3种函数 :普通函数、匿名函数(没有名称的函数)、方法(定义在struct 上的函数)。
注意点:
-
go语言中不允许存在同名的函数,也就是函数重载(overload)。
-
go语言中的函数不能嵌套函数,但可以嵌套匿名函数。
-
函数可以作为一个值直接赋值给变量,使得这个变量也成为函数,有点像C++ std::function。
-
函数参数可以没有名称。
定义一个函数
func HelloWorld() {
fmt.Println("helloworld")
}
要注意,函数的首字母需要大写才能在其他的包调用。
函数的返回值
Golang 允许返回多个参数
函数可以有0或多个返回值,返回值需要指定数据类型,返回值通过return关键字来指定。
return可以有参数,也可以没有参数,这些返回值可以有名称,也可以没有名称。go中的函数可以有多个返回值。
func GetStringAndInt() (string, int) {
fmt.Println("GetStringAndInt")
return "haha", 10
}
func main() {
str, num := GetStringAndInt()
fmt.Printf("str: %v\n", str)
fmt.Printf("num: %v\n", num)
// GetStringAndInt
// str: haha
// num: 10
}
-
return关键字中指定了参数时,返回值可以不用名称。如果return省略参数,则返回值部分必须带名称
-
当返回值有名称时,必须使用括号包围,逗号分隔,即使只有一个返回值
-
但即使返回值命名了,return 中也可以强制指定其它返回值的名称,也就是说return的优先级更高
-
命名的返回值是预先声明好的,在函数内部可以直接使用,无需再次声明。命名返回值的名称不能和函数参数名称相同,否则报错提示变量重复定义
-
return中可以有表达式,但不能出现赋值表达式,这和其它语言可能有所不同。例如return a+b 是正确的,但return c = a + b 是错误的。
-
Go中经常会使用其中一个返回值作为函数是否执行成功、是否有错误信息的判断条件。例如return ( value, exists)、return (value, ok)、 return (value, err)
-
当函数的返回值过多时,例如有4个以上的返回值,应该将这些返回值收集到容器中,然后以返回容器的方式返回。例如,同类型的返回值可以放进slice中,不同类型的返回值可以放进map中。
-
但函数有多个返回值时,如果其中某个或某几个返回值不想使用,可以通过下划线_来丢弃这些返回值。
函数的参数
go语言默认是值传递,如int、string、数组 意味着传递给函数的是拷贝后的副本,所以函数内部访问、修改不会影响实参。
go语言可以使用变长参数,有时候并不能确定参数的个数,可以使用变长参数,可以在函数定义语句的参数部分
可变参数
func Test2(str string, args ...int) {
fmt.Printf("str: %v\n", str)
for _, v := range args {
fmt.Printf("v: %v\n", v)
}
}
func main() {
Test2("hello", 1, 2, 3, 4)
}
引用传递的参数
map、slice、 interface 、channel 这些数据类型本身就是引用类型,在作为函数参数传递时,在函数内部修改也会影响实参。
函数类型
使用 type 关键字定义一个函数类型
func Func2(str string) (int, int) {
fmt.Printf("Func2: %v\n", str)
return 1, 2
}
func Func3(str string) (int, int) {
fmt.Printf("Func3: %v\n", str)
return 1, 2
}
type F1 func(string) (int, int)
func main() {
var f1 F1 = Func2
f1("hello")
f1 = Func3
f1("world")
}
函数作为参数传递
func Func1(str string) (int, int) {
fmt.Println("Func1")
fmt.Printf("str: %v\n", str)
return 11, 22
}
func Func2(f func(string) (int, int)) {
f("Func2 call")
fmt.Println("Func2")
}
func main() {
Func2(Func1)
}
匿名函数
定义一个匿名函数
func main() {
f1 := func() {
fmt.Println("匿名函数")
}
f1()
}
直接调用
func main() {
func() {
fmt.Println("匿名函数")
}()
}
defer 语句
golang 的defer语句会将其后面跟随的语句进行延迟处理。在defer归属的函数即将return 时,将延迟处理的语句按defer定义的逆序进行执行,也就是说,先被defer的语句最后被执行,最后被defer的语句,最先被执行。
一般用于资源释放,释放fd,锁。
示例:
要注意,并不是在出作用域执行,到达函数return时才执行
func main() {
{
defer fmt.Println("1")
defer fmt.Println("2")
defer fmt.Println("3")
defer fmt.Println("4")
}
fmt.Println("hehe")
// hehe
// 4
// 3
// 2
// 1
}
要注意以下情况
fun_return函数优先于 fun_defer()函数先执行,可以理解为defer语句到 } 彻底结束才会执行
func fun_return() int {
fmt.Println("fun_return")
return 1
}
func fun_defer() {
fmt.Println("fun_defer")
}
func test() int {
defer fun_defer()
return fun_return()
}
func main() {
test()
//打印
//fun_return
// fun_defer
}
init 函数
init函数先于main函数执行, 一般用于包级别的一些初始化操作。
- init函数先于main函数自动执行,不能手动调用
- init函数没有输入参数、返回值
- 每个包可以有多个init函数
- 包的每个源文件也可以有多个init函数
- 同一个包的init执行顺序,golang没有明确定义, 要注意程序不要依赖这个执行顺序。
- 不同包的init函数按照包导入的依赖关系决定执行顺序。
示例:
不能手动调用
func init() {
fmt.Println("init1")
}
func init() {
fmt.Println("init2")
}
func main() {
fmt.Println("hello world")
// init1
// init2
// hello world
}
Golang 指针
Golang的指针没有C/C++ 指针功能强大,Golang 指针不能进行偏移和运算。
//定义一个string类型的指针
var pstr *string
//定义一个int类型的指针
var pint *int
fmt.Printf("pstr: %T\n", pstr) // pstr: *string
fmt.Printf("pint: %v\n", pint) // pint: *int
Golang 指针数组
定义一个指针数组
var parr [10]*int
fmt.Printf("parr: %T\n", parr) //parr: [10]*int
对其赋值
arr := [10]int{
1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
var parr [10]*int
for i := 0; i < 10; i++ {
parr[i] = &arr[i]
}
for i := 0; i < 10; i++ {
fmt.Printf("parr[i]: %v\n", *parr[i])
}
类型定义和类型别名
类型定义 类型别名
//类型定义
type MYINT1 int
//类型声明
type MYINT2 = int
func main() {
//类型定义
var num1 MYINT1 = 1
fmt.Printf("num1: %T\n", num1) // num1: main.MYINT1
//类型声明
var num2 MYINT2 = 1
fmt.Printf("num2: %T\n", num2) //num2: int
}
类型定义与类型别名区别
类型不匹配,编译报错
//类型定义
type MYINT1 int
func Func1(v MYINT1) {
fmt.Printf("v: %v\n", v)
}
func main() {
//类型定义
var num1 MYINT1 = 1
//定义一个int类型变量
var num3 int = 1
Func1(num1)
Func1(num3) //编译器报错
}
编译通过,MYINT2 编译后的类型其实就是int,MYINT2只是int的别名
//类型声明
type MYINT2 = int
func Func2(v MYINT2) {
fmt.Printf("v: %v\n", v)
}
func main() {
//类型声明
var num2 MYINT2 = 1
//定义一个int类型变量
var num3 int = 1
Func2(num2)
Func2(num3) //编译器报错
}
类型定义相当于定义了一个全新的类型,与之前的类型不同。但是类型别名并没有定义一个新的类型,而是使用一个别名来替换之前的类型类型别名只会在代码中存在,在编译完成之后并不会存在该别名因为类型别名和原来的类型是一致的, 所以原来类型所拥有的方法,类型别名中也可以调用,但是如果是重新定义的一个类型,那么不可以调用之前的任何方法