Go 语言入门指南:基础语法和常用特性解析(实践选题)|青训营
变量的使用方式(三种):
- 指定变量类型,声明后若不赋值,使用默认值:
在这种方式下,我们通过显式指定变量类型并声明变量,如果不对其进行赋值,它将使用默认值。默认值对于不同的类型有所不同,例如 int 类型的默认值是 0,float64 类型的默认值是 0.0,string 类型的默认值是空字符串。
var i int // i 声明为 int 类型,默认值为 0
fmt.Println("i =", i) // 输出: i = 0
- 根据值自行推断变量类型(类型推导):
Go 语言支持类型推导,这意味着您可以根据赋值的值自动推断变量的类型。在这种情况下,变量的类型将与赋值的值的类型相同。
var num = 1.011 // num 自动推断为 float64 类型
fmt.Println("num =", num) // 输出: num = 1.011
- 省略
var,使用:=进行变量声明和赋值(类型推导):
使用 := 进行变量声明和赋值是一种更简洁的方式,它会自动推断变量的类型,并且不需要显式地使用 var 关键字。
name := "Tom" // name 自动推断为 string 类型
fmt.Println("name =", name) // 输出: name = Tom
一次性声明多个变量(三种):
- 一次性声明多个变量不赋值:
在这种情况下,您可以同时声明多个变量,但不对它们进行赋值。这些变量将会根据其类型分别被初始化为对应类型的零值。
var n1, n2, n3 int // 声明三个 int 类型的变量
- 一次性声明多个变量并赋值:
您可以在一行代码中同时声明多个变量并对它们赋值,这使得代码更加简洁。
var n1, name, n3 = 100, "Tom", 800 // 分别对 n1、name 和 n3 进行赋值
- 一次性声明多个变量并赋值(使用
:=):
同样,您也可以使用 := 进行多个变量的声明和赋值,这更加简洁。
n1, name, n3 := 100, "Tom", 800 // 同时声明并赋值多个变量
指针:
指针是一个非常重要的概念,它允许我们间接访问变量的值。下面是更详细的关于指针的解释和示例。
基本介绍:
在 Go 语言中,每个变量都有一个内存地址,而指针就是存储变量内存地址的变量。通过指针,我们可以间接访问变量的值。
- 获取变量的地址使用
&符号。例如,&i返回变量i的地址。 - 获取指针类型指向的值使用
*符号。例如,*ptr返回指针变量ptr所指向的值。
赋值:
在这个示例中,我们将一个指向整数的指针 ptr 赋值为变量 i 的地址。
var ptr *int = &i // ptr 存储了变量 i 的地址
指针的使用细节:
- 值类型和对应的指针类型:Go 语言中的每个值类型都有对应的指针类型。形式为
* 数据类型。 - 值类型和引用类型:值类型直接存储值,而引用类型存储的是一个内存地址。引用类型的值实际上存储在堆内存中,由垃圾回收器(GC)负责回收。
var i int = 42
var ptr *int = &i // ptr 存储了变量 i 的地址
fmt.Println("Value of i:", *ptr) // 输出: Value of i: 42
闭包:
闭包是一种强大的概念,它允许一个函数与其相关的引用环境组合成一个整体。以下是一个更详细的关于闭包的解释和示例。
基本介绍:
闭包是一个由函数及其相关引用环境组合而成的实体。闭包允许一个函数捕获和存储其外部变量的引用,使得函数可以访问外部函数中定义的变量。
示例:
下面是一个闭包的示例,函数 incr 返回一个闭包函数,用于增加一个计数器。
func incr() func() int {
var x int
return func() int {
x++
return x
}
}
i := incr() // 调用 incr 函数返回闭包函数
fmt.Println(i()) // 输出: 1
fmt.Println(i()) // 输出: 2
在这个示例中,i 是一个闭包函数,它持有一个对 x 的引用。每次调用 i() 都会增加 x 的值,并返回新的值。
Map
- map是key-value数据结构
- 基本语法: var 变量名 key[keytype]valuetype
- 通常用int和string作为key
- valuetype可以是任何类型
- 通常为: 数字, string, map, struct
- 声明举例:var a map[string]string
- 声明是不会分配内存的,初始化需要make,分配内存后才能赋值和使用
- Println直接输出map可以看到内部数据
var a map[string]string
a = make(map[string]string, 10)
a["no1"] = "宋江"
fmt.Println(a)
- map在使用前一定要make
- make函数分配并初始化一个类型为切片、映射或通道的对象,其第一个实参为类型而非值。make的返回类型与第一个实参相同。
map的使用
map使用方式有下面几种:
-
var a map[string]string a = make(map[string]string, 10) -
var a = make(map[string]string) -
var a map[string]string = map[string]string{ "no1" : "北京" } a["no2"] = "天津"
map的增删改查操作
-
map的增加和更新:
-
map["key"] = value //如果key目前不存在就是增加,如果存在就是更新
-
map删除:
- delete(map, "key"),delete是一个内置函数,如果key存在,就删除该key-value,如果key不存在,就不进行操作也不会报错
- 如果要删除所有的key-value键值对,没有专门的内置函数可以做到,但可以通过遍历key的方式逐一删除
-
map查找:
val, ok := a["no1"] if ok{ fmt.Printf("有no1 key值为%v\n", val) }else{ fmt.Printf("没有no1 key\n") }
map的遍历
- 首先强调map的遍历是用for-range不能单纯地用for循环。(因为for循环不能用1,2,3等数字代表它的key)
for k, v := range cities{
fmt.Printf("key= %v, value = %v\n",k, v)
}
map切片
- 使用场景:一个妖怪对应一个名字和一个年龄,那么随着妖怪个数的增加map数量也会动态增加。
var monsters []map[string]string
monsters = make([]map[string]string, 2) //指增加两个妖怪
if monsters[0] == nil{
monsters[0] = make(map[string]string, 2)
monsters[0]["name"] = "妖怪1"
monsters[0]["age"] = "20"
}
if monsters[1] == nil{
monsters[1] = make(map[string]string, 2)
monsters[1]["name"] = "妖怪2"
monsters[1]["age"] = "50"
}
//这里如果要像前两个代码块一样加第三个妖怪则不能实现,因为make创造切片的空间时,设置size == 2,我们必须使用append方法才能动态增加
var newMonster map[string]string = map[string]string{
"name" : "妖怪3",
"age" : "90"
}
monsters = append(monsters, newMonster)
map排序
var keys []int
for k, v := range map1{
keys = append(keys, k)
}
sort.Ints(keys)
for _, k := keys{
fmt.Printf("key =%v, value = %v" k, map1[k])
}
- golang中没有任何一个方法是针对map的key进行排序的
- map中的元素是无序的,将其遍历输出的顺序也会变化
- map的排序是通过先将key进行排序,然后key值遍历得到的
map的使用细节
- map是个引用类型,遵循引用类型传递机制。在一个函数接受map,修改后,会直接在原map上进行修改
- map具有自动扩容机制,即使make的时候size设置得很小,也可以通过直接key-value赋值的形式增加元素个数。(而切片则必须通过append才能实现自动扩容)
- map的value也经常使用struct类型,更加适合管理复杂的数据。
举例:
type Stu struct{
Name String
Age int
Grade float32
}
func main(){
students := make(map[String]Stu, 10)
stu1 := Stu{"tome", 10, 99.3}
stu2 := Stu{"may", 20, 99.4}
stu3 := Stu{"snow", 30, 99.5}
students["no1"] = stu1
students["no2"] = stu2
students["no3"] = stu3
// 取出一个学生信息
for k, v := range students{
fmt.Printf("名字是%v", v.Name)
fmt.Printf("年龄是%v", v.Age)
fmt.Printf("成绩是%v", v.Grade)
}
}
结构体
- 结构体是自定义的数据类型,代表一类事物
- 结构体变量(实例)是具体的,代表一个具体变量
- 结构体是值类型(直接指向地址空间的)
构建结构体的方法
-
var person1 Person
-
person2 := Person{} //这样也可以 person2 := Person{"Mary", 20} //创建时赋值
-
var person3 *Person = new(Person) (*p3).Name = "John" //其实可以简化赋值:p3.Name = "John"等价于上述语句
- 结构体的所有字段在内存中是连续分布的
- 结构体类型进行转换时,必须保证字段的类型,名字以及个数全部一致。
结构体使用序列化
- 细节:因为我们想要使用json包下的marshal方法将结构体变量转化为序列化的字串,所有我们结构体中的变量名必须全部大写,暴露给json包。
- 问题:有时候我们需要将变量名小写的形式传递给客户端。
- 解决方法:使用tag:Name string
json:"name"(这里用到了反射)