Go入门指南-第七届字节跳动青训营第一课

90 阅读10分钟

什么是Go语言

Go性能好,部署简单,学习成本低,逐渐开始流行起来。
Go语言的特性:
1、高并发、高性能:像C++、Java一样高性能;
2、丰富的标准库:无需借助第三方库
3、语法简单,学习曲线平缓:语法类似C语言,但比C语言更加简化,很好上手
4、完善的工具链:编译、代码格式化、包管理、错误检查、补充提示、单元测试等工具
5、静态链接:默认编译结果都默认是静态链接的,可执行文件
6、快速编译:编译速度很快
7、跨平台:交叉编译特性,无需配置交叉编译环境
8、垃圾回收:开发过程无需考虑内存分配

有哪些公司在使用Go语言呢?

哪些公司在使用Go.png

基础语法

一、数据类型

1. 常用基本数据类型
  • 整型:int、int8、int16、int32、int64、uint(无符号整数)等等

  • 浮点型:float32、float64

  • 布尔类型:bool,值为true或false

  • 字符串类型:string
    (go语言中,字符串是内置类型,可以直接使用加号拼接,也可以直接使用等于号去比较两个字符串)

2. 常用复合数据类型
  • 数组 ([n]T):固定长度的元素集合,类型为 T,长度为 n

  • 切片 ([]T):动态大小的数组,可以随时增删元素

  • 映射 (map[K]V):键值对的集合,K 为键的类型,V 为值的类型

  • 结构体 (struct):一组字段的集合,可以定义自定义数据结构

  • 指针 (*T):存储另一个变量的内存地址

3. 引用类型
  • 接口 (interface):定义了一个或多个方法签名的集合

  • 通道 (chan T):用于 Goroutine 之间的通信

4. 函数类型
  • 函数类型可以作为变量类型或参数类型,用于传递函数

二、变量

go是强类型语言,每个变量都有自己的变量类型,而且在go中大部分运算符的使用和优先级都和C和C++类似

变量声明方式有两种:

  1. 第一种
    var a = "hello"(不显示指出变量类型,程序自动推断)
    var b, c int = 1,2(也可以自行写出变量类型,变量类型写在变量名之后)

  2. 第二种
    f := float32(e) (变量名 := 值)

常量:使用const定义,在go中的常量没有确定的类型,会根据使用的上下文来自动确认类型

三、if-else语句

if后面没有(),但if后必须要有{}

基本结构
 if 7%2 == 0 {
    fmt.Println("7 is even")
} else {
    fmt.Println("7 if odd")
}

四、循环语句

Go中只有for循环,与C++、Java语法类似,只是去除了条件语句的()

基本结构
for i := 0; i < 10; i++ {
    fmt.Println(i)
}

五、 switch

go中的switch语句,不需要显示加break语句,switch 会自动在每个 case 执行完后终止,不会自动继续执行后续的 case。这意味着每个 case 块都是独立的,只有匹配的 case 会被执行。

基本结构
a := 2
switch a {
case 1:
	fmt.Println("a is 1")
case 2:
	fmt.Println("a is 2")
case 3:
	fmt.Println("a is 3")
default:
	fmt.Println("a is not 1,2,3")
}
//输出a is 2

六、数组

数组 ([n]T):具有编号且长度固定的元素序列,与其他编程语言类似

//定义a为数组类型,长度为5
var a [5]int
//访问a[4]元素,并赋值
a[4] = 100
//另一种定义方式,长度为5,元素为1,2,3,4,5
b := [5]int{1,2,3,4,5}
数组遍历
arr := [5]int{1,2,3,4,5}
for i := 0; i < len(arr); i++ { 
    fmt.Println(arr[i]) // 输出每个元素 
}
for index, value := range arr {
    fmt.Printf("索引: %d, 值: %d\n", index, value) 
}

七、切片

切片slice ([]T):不同于数组,可以任意更改长度,也有很丰富的操作。

1. 创建

使用make创建(创建初始长度为3,类型为string的切片)
s := make([]string,3)

2. 赋值

可以像数组一样访问内部的值进行赋值

s[0] = "a"
s[1] = "b"
s[2] = "c"
3. 添加元素

使用append来追加元素(注意append会定义的初始长度后添加元素)
s = append(s,"d")

4. 访问

slice拥有像python一样的切片操作(左闭右开原则)

//s = [a,b,c,d]
fmt.Println(s[1:3])//[b c]
fmt.Println(s[:4])//[a b c d]
fmt.Println(s[1:])//[b c d]

八、map

映射 (map[K]V):是实际使用过程中最频繁用到的数据结构,golang的map是完全无序的,遍历时也不会按照字母顺序排序,而是随机排序

map 是一种内置的数据结构,用于存储键值对(key-value pairs),它具有高效的查找、插入和删除操作。map 的实现基于哈希表,因此它的操作复杂度平均为 O(1)。

1. 创建map

使用 make 函数可以创建一个空的 map,需要指定键和值的类型。

//map[key]value,创建一个空的 map,键为 string 类型,值为 int 类型
m := make(map[string]int)
2. 存储键值对

可以通过简单的赋值语法将键值对存储到 map 中。

m["one"] = 1
m["two"] = 2
fmt.Println(m)//map[one:1 two:2]
3. 获取map长度和值
fmt.Println(len(m))// 2
fmt.Println(m["one"])// 1
4.删除键值对
//map的删除
delete(m, "one")
fmt.Println(m)//map[two:2]
5.查询键是否存在
//map的查找
v, ok := m["two"]
fmt.Println(v, ok)// 2 true
r, ok := m["three"]
fmt.Println(r, ok)// 0 false

九、range

对于slice或者map来说,可以使用range来快速遍历,这样可以使代码更加简洁。

range遍历时会返回两个值,第一个索引,第二个是对应位置的值。如果不需要索引可以使用下划线来忽略

// 遍历 slice
slice s := []string{"apple", "banana", "cherry"} 
fmt.Println("Slice:") 
for i, v := range s { 
    fmt.Printf("Index: %d, Value: %s\n", i, v) 
} 
// 遍历 map
map m := map[string]int{ 
    "apple": 5, 
    "banana": 10, 
    "cherry": 15, 
} 
fmt.Println("\nMap:") 
for k, v := range m { 
    fmt.Printf("Key: %s, Value: %d\n", k, v) 
}

十、函数

这个是 Golang 里面一个简单的实现两个变量相加的函数。 Golang 和其他很多语言不一样的是,变量类型是后置的

Golang 里面的函数原生支持返回多个值。在实际的业务逻辑代码里面几乎所有的函数都返回两个值,第一个是真正的返回结果,第二个值是一个错误信息。

基本结构
func functionName(parameters) returnType { 
    // 函数体 
}
示例
func add(a int, b int) int {
	return a + b
}
func add2(a, b int) int {
	return a + b
}
func exists(m map[string]string, k string) (v string, ok bool) {
	v, ok = m[k]
	return v, ok
}

func main() {
	res := add(1, 2)
	fmt.Println(res)

	v, ok := exists(map[string]string{"a": "A", "b": "B"}, "c")
	fmt.Println(v, ok) //v返回空字符串,ok为false
}

十一、指针

在 Go 语言中,指针(Pointer)是用于存储变量地址的特殊类型,提供了直接操作内存的能力。与一些其他语言不同,Go 中不支持指针算术(如 p++ 等),主要用途就是对于传入参数进行修改,因此指针的使用相对安全且简单。

1. 指针的声明和使用
  • 使用 * 符号声明指针类型。例如,*int 表示一个指向 int 类型的指针。
  • 使用 & 符号获取变量的地址,从而创建指针。
  • 通过 * 操作符来解引用指针,即访问指针指向的值。
func main() {
    var x int = 10
    var p *int = &x  // p 是指向 x 的指针

    fmt.Println("x 的值:", x)         // 输出 10
    fmt.Println("x 的地址:", &x)      // 输出 x 的内存地址
    fmt.Println("p 的值:", p)         // 输出 x 的地址
    fmt.Println("p 指向的值:", *p)    // 输出 10,即 x 的值

    // 修改指针指向的值
    *p = 20
    fmt.Println("x 的新值:", x)       // 输出 20
}
2. 指针的零值
  • 指针的零值是 nil,表示指针不指向任何有效的内存地址。
  • 使用指针前一般检查是否为 nil,避免运行时错误。
var p *int // p 是一个 nil 指针
if p == nil {
    fmt.Println("p 是一个 nil 指针")
}
3. 在函数中使用指针

在 Go 中,函数参数是按值传递的,即传递的是副本。如果想要在函数内修改原始变量的值,可以传递指针。

//无效,n是形参
func add2(n int) {
    n += 2
}
//
func add2ptr(n *int) {
    *n += 2
}

func main() {
    n := 5
    add2(n)
    fmt.Println(n)//5

    add2ptr(&n)
    fmt.Println(n)//7
}

4. 指针和结构体

结构体常使用指针传递,以避免复制大块数据。可以通过结构体指针直接访问字段。

5. new 和 make 的区别
  • new:分配内存并返回指向该类型零值的指针。例如,new(int) 返回 *int,值为 0
  • make:用于初始化 slice、map 和 channel 等引用类型,返回的是类型本身而不是指针。
6. Go 中的指针和安全性
  • Go 中没有指针算术运算(如 p++),这减少了指针使用的复杂性和出错几率。
  • 垃圾回收机制确保不再使用的内存会自动释放,不需要手动管理内存.

十二、结构体

结构体是带类型的字段的集合

1. 定义结构体

Go 语言使用 type 关键字定义结构体。结构体由字段(字段名和字段类型)组成,每个字段都有自己特定的数据类型。

type Person struct {
    Name string
    Age  int
    Address string
}

上述代码定义了一个 Person 结构体,包含 NameAgeAddress 三个字段。

2. 结构体的实例化

结构体的实例化可以通过字面量、new 关键字或 & 操作符来完成。

// 1. 使用字面量
p1 := Person{Name: "Alice", Age: 30, Address: "123 Street"}

// 2. 使用 new 返回指针
p2 := new(Person)  // p2 是 *Person 类型
p2.Name = "Bob"
p2.Age = 25

// 3. 使用 & 操作符返回指针
p3 := &Person{Name: "Charlie", Age: 35}
3. 访问和修改字段

使用点号 . 可以访问结构体的字段,并对字段进行读取和修改。

go
复制代码
fmt.Println(p1.Name)  // 输出 "Alice"
p1.Age = 31           // 修改字段值
fmt.Println(p1.Age)   // 输出 31
4. 结构体方法

Go 支持为结构体定义方法。方法的定义类似于普通函数,但需要指定接收者(receiver)。

type Person struct {
    Name string
    Age  int
}

// 为 Person 结构体定义一个方法
func (p Person) Greet() {
    fmt.Println("Hello, my name is", p.Name)
}
  • 在方法定义中,p Person 表示接收者 pPerson 类型的实例。
  • 可以使用值接收者或指针接收者。值接收者不会改变原始数据,而指针接收者可以修改原结构体的字段。
5. 值接收者和指针接收者
  • 值接收者:方法的接收者是结构体的一个副本,方法内部的修改不会影响原结构体。
  • 指针接收者:方法的接收者是结构体的指针,方法内部可以直接修改原结构体的字段。
// 使用指针接收者
func (p *Person) UpdateAge(newAge int) {
    p.Age = newAge
}
6. 嵌套结构体和组合

Go 中不支持继承,但可以通过将一个结构体嵌入到另一个结构体来实现组合,这种方式可以让结构体之间共享字段或方法。

type Address struct {
    City  string
    State string
}

type Person struct {
    Name    string
    Age     int
    Address // 匿名嵌套
}

通过这种嵌套方式,Person 可以直接访问 Address 的字段:

p := Person{Name: "Alice", Age: 30, Address: Address{City: "New York", State: "NY"}}
fmt.Println(p.City)  // 输出 "New York"
7. 零值初始化

Go 中的结构体会自动初始化为零值。例如,字符串字段的默认值是空字符串 "",整型字段的默认值是 0

var p Person  // p 是一个零值的 Person 实例
fmt.Println(p.Name)  // 输出 ""(空字符串)
fmt.Println(p.Age)   // 输出 0
8. 标签(Tags)

Go 的结构体字段支持标签,通常用于 JSON 序列化、数据库映射等场景。

type Person struct {
    Name string `json:"name"`
    Age  int    `json:"age"`
}

标签 json:"name" 表示 Name 字段在 JSON 中的键名为 "name"