golang学习笔记完结篇-go基本语法全面总结【推荐收藏】

go基本语法总结,重要性不言而喻!

一、数据类型的分类与创建方式

1.分类

1.1 按复杂程度划分

基本类型复杂类型
整数型、浮点型、bool、字符串指针、函数、数组、切片、map、结构体、接口、管道

1.2 按传递类型划分

值类型引用类型
基本类型、函数、数组、结构体指针、切片、map、接口、管道

其中函数的类型有争议,个人认为是特殊值类型,因为定义时内存中存的是值,调用时使用的是值,赋值后使用的是引用
1.3 整数型分类

默认类型有符号无符号
int, uint, rune(int32), byte(uint8)int8, int16, int32, int64uint8, uint16, uint32, uint64

其中int,uint的大小取决于操作系统位数,32位就是32位,64位就是64位。Unicode码(任意字符)通常用rune存放,ASCII码通常用byte存放。
1.4 浮点型分类

单精度双精度
float32float64

小数常量默认为float64。高精度转低精度会有精度损失,值会改变。

2.创建方式(以具体实例演示)

类型创建方式1创建方式2创建方式3
整数型var i int;i = 1var i int = 1i := 1
浮点型var f float64;f = 1.0var f float64 = 1.0f := 1.0
boolvar b bool;b = truevar b bool = trueb := true
字符串var s string;s = "1"var s string = "1"s := "1"
指针i := 1;p := &ip := new(int)
函数func test(a int) int {return 1}f := func(a int) int {return 1}
数组arr := [3]int{1, 2, 3}arr := [...]int{1, 2, 3}arr := [...]int{0: 1, 1: 2, 3: 4}
切片arr := [4]int{10, 20, 30, 40};
slice := arr[1:4]
slice := make([]int, 3,5)slice := []int{1, 2, 3, 4, 5}
mapmp := make(map[string]int, 5);
mp["语文"] = 70
mp := map[string]int{
"语文" : 70,"数学" : 80}
管道ch := make(chan int, 3)

结构体

package main

import "fmt"

type Person struct {
	Name string
	Age  int
	Sex  string
}

func main() {
	var person1 Person
	person1.Name = "李华"
	person1.Age = 30
	person1.Sex = "女"
	fmt.Println(person1)

	person2 := Person{"王伟", 25, "男"} //顺序赋值
	fmt.Println(person2)

	person3 := Person{Age: 28, Name: "张敏", Sex: "女"} //乱序赋值
	fmt.Println(person3)
}

接口

package main

import "fmt"

//接口
type SayHello interface {
	Say() //声明方法
}

//接口的实现类型,这里是结构体
type Chinese struct {
}
type American struct {
}

//实现接口方法
func (c Chinese) Say() {
	fmt.Println("你好")
}

func (a American) Say() {
	fmt.Println("hello")
}

func main() {
	ch, am := Chinese{}, American{} //多态
	ch.Say()
	am.Say()
}

二、程序控制结构

1.分支结构

1.1 if else
语法:

if 条件1{
//语句1
}else if 条件2{
//语句2
}...
else if 条件n-1{
//语句n-1
}else{
//语句n
}

注意:条件表达式中可以定义变量,但只能用短变量声明,且后面必须跟分号。例:

if age := 20; age > 18 {
	fmt.Println("你已成年")
}

1.2 switch
语法:

switch 表达式{
case 表达式1,表达式2,...:
语句块1
case 表达式3,表达式4,...:
语句块2
...
default:
语句块n
}

注意:switch后面可以不跟表达式,当作if else分支来处理。
go专属机制:
1.switch穿透:fallthrough,fallthrough用于使当前case执行完后还能继续执行下一个case,必须放在case末尾且只能有一个,只能穿透一层。
2.type swich:类型推断,用于判断某个接口变量中实际指向的变量类型,语法:

switch 变量名 := 接口变量.(type) {
case 类型1:
	语句块1
case 类型2:
	语句块2
...
default:
	语句块n
}

2.循环结构

语法:

//语法1
for 循环变量初始化;循环进行条件;循环变量迭代{
//执行语句
}
//语法2
for 循环进行条件{
//执行语句
}
//语法3
for {
//执行语句
}
//语法4,for-range
for 索引变量名, 值变量名 := range 变量{
	//执行语句
}

注意
1.语法3如果没有break就是死循环,所以要加上break(有协程的情况除外)。
2.用for-range遍历字符串得到的value是Unicode码值,需要用%c才能显示原字符。
3.用for-range遍历管道,只有一个返回值value,没有index。

三、重要概念辨析

1.init函数,匿名函数,闭包

每个文件都可以定义init函数,它会在main函数执行前被调用,用于初始化。init的优先级:文件之间,被导包>当前包,文件内,全局变量>init>main。匿名函数就是没有名字的函数,用于变量初始化,其形式已在第一节讲过。使用匿名函数最需要注意的是:不加括号返回的是匿名函数的引用加括号就是调用一次匿名函数。如果匿名函数被用于协程,是一定要加括号的,因为匿名函数要作为轻量级线程,就是动态的程序概念,所以一定要调用。匿名函数作为协程还要特别注意多协程的情况,因为在多协程中,如果协程要使用循环变量,那么循环变量会和每个协程(匿名函数)形成闭包,使得循环变量一直留于内存,被所有协程引用。所以闭包的本质是匿名函数,特点是引用外部变量,使其一直留于内存

2.方法与函数的区别

方法函数
和其他类型的关系绑定到特定类型独立于任何类型
语法func (receiver ReceiverType) MethodName(parameters) returnTypefunc FunctionName(parameters) returnType
调用方式通过类型的实例调用 instance.MethodName()直接调用 FunctionName()
作用域可以访问绑定类型的字段只能访问传入的参数
指定类型是否需要和传入类型一致不需要(可以随意传入值或指针)需要

3.切片与数组的区别

1.数组的大小是固定的,在定义时就必须指定,不能动态改变。而切片是动态的,可以根据需要扩展。
2.数组是值类型,传递数组时会复制整个数组。切片是引用类型,传递的是指向底层数组的指针,因此切片的操作会影响原始数据。
3.数组在声明时分配内存,其大小固定。切片在运行时可以根据需要动态分配内存,并且可以通过append函数增加元素,go会自动处理内存分配。

4.封装,继承,多态

这是面向对象的三大特性。封装就是把字段及其操作方法封装在一起,被封装的字段无法被外部调用程序直接访问,只有通过调用指定的方法才能对字段进行操作,提高了数据的安全性继承就是一个或多个类(GO中是结构体)拥有另一个类(GO中是匿名结构体)的所有字段和方法,这种关系称为继承,被继承的类称作父类,继承类称作子类。子类不仅可以使用父类的字段和方法,还可以自定义父类没有的字段和方法,提高了代码的复用性。多态就是一个类型拥有不同的特征,这种特征在GO中通过接口实现。具体来说,一个接口中的方法可以被多个类型实现,使该接口呈现出不同类型的特征。这样,只需依赖该接口定义的行为,代码就可以处理不同类型的对象,而不需要了解它们的具体类型,提高了代码的灵活性

5.协程与管道

这是GO的核心,也是GO流行的主要原因,有了这两个机制,GO就能高效处理高并发行为。协程是一种线程,它能将自己的IO操作最大限度地隐藏起来,从而可以迷惑操作系统,让其将更多的cpu执行权限分配给自己。注意:线程是CPU控制的,而协程是程序自身控制的,属于用户级别的切换,操作系统感知不到,所以说协程是轻量级线程。协程如何将自己的IO操作最大限度地隐藏起来?原理是GO让协程和主线程并发执行,大大提高了程序的执行效率,而且由于读写锁机制,协程便能更高效地应对读操作数量远大于写操作的场景。但数据传输的安全性也很重要,所以GO引入了管道机制,管道的本质是一个队列,因其先进先出的特性,保证了多个协程操作同一个管道时,不会发生资源抢夺问题。

6.断言

在GO中,接口支持多态(即同一接口变量可以存储不同的具体类型的值)。通过类型断言,可以根据需要获取接口变量所存储的具体类型,从而访问特定的属性和方法。当需要处理多种类型时,类型断言可以与switch语句结合使用,成为类型选择,从而简化对多个类型的处理,以下是多态实例:

func Greet(s SayHello) {
	s.Say()
	switch person := s.(type) {
	case Chinese:
		person.NiuYangGe()
	case American:
		person.Disco()
	default:
		fmt.Println("未知类型")
	}
}

为了避免因断言失败而引发的panic,还可以使用安全类型断言,通过获取第二个返回值来判断断言是否成功:value, ok := interfaceVariable.(ConcreteType)

7.反射

反射的主要作用是提供动态操作的能力。它允许程序在运行时检查类型、调用方法、访问和修改字段等,而不是在编译时静态确定。这对以下场景特别有用:
(1)动态类型检查和处理:当程序需要处理未知类型或接口时,反射可以获取类型信息并做出相应处理。
(2)序列化和反序列化:反射可用于自动将数据结构转换为JSON/XML等格式,或者反过来,将这些格式的数据转换为GO结构体。
(3)通用库的实现:像fmt包中的Printf就使用了反射来处理变长参数,支持各种类型。
(4)动态调用方法:在运行时根据输入决定调用哪个方法,而不需要硬编码

8.错误处理

错误处理是一种显式的方式,函数通常会返回一个error类型值,调用者需要检查这个返回值来判断是否发生了错误。这样可以灵活地处理错误情况,并且控制权完全交由调用者
defer+recover机制用于从运行时恐慌(panic)中恢复。defer延迟执行某个函数,直到包含它的函数返回,通常用于资源释放等场景。当程序出现严重错误导致panic时,recover可以捕获panic,避免程序崩溃,从而优雅地恢复并继续执行代码。这在处理不可预见的错误时非常有用。

9.Kind()和TypeOf()的区别

特性reflect.TypeOf()reflect.Kind()
返回值返回 reflect.Type 类型的对象返回 reflect.Kind 类型的枚举值
作用获取变量的完整类型信息获取变量的基本种类(如 intstringstruct
适用场景当需要获取类型的详细信息时当只需要判断数据类型的基本特性时

四、重要类型的操作

1.切片

package main

import (
	"fmt"
)

func main() {
	// 1. 切片的初始化和修改
	slice := []int{1, 2, 3, 4}
	fmt.Println("初始切片:", slice)

	// 修改切片元素
	slice[0] = 10
	fmt.Println("修改后的切片:", slice)

	// 2. 添加元素导致扩容
	fmt.Printf("初始容量: %d\n", cap(slice))

	// 添加元素,超出容量
	slice = append(slice, 5, 6, 7, 8)
	fmt.Println("添加元素后的切片:", slice)
	fmt.Printf("添加元素后的容量: %d\n", cap(slice)) // 容量自动扩容

	// 3. 遍历切片
	fmt.Println("遍历切片:")
	for i, v := range slice {
		fmt.Printf("索引: %d, 值: %d\n", i, v)
	}

	// 4. 切片继续切片
	subSlice := slice[2:5]
	fmt.Println("子切片:", subSlice)

	// 5. 切片拷贝
	anotherSlice := make([]int, len(subSlice)) // 创建一个和子切片长度一样的新切片
	copy(anotherSlice, subSlice)
	fmt.Println("拷贝后的新切片:", anotherSlice)

	// 修改拷贝后的切片,不影响原切片
	anotherSlice[0] = 100
	fmt.Println("修改后的新切片:", anotherSlice)
	fmt.Println("原切片保持不变:", subSlice)
}

2.map

package main

import (
	"fmt"
)

func main() {
	// 1. 创建并初始化 map
	myMap := map[string]int{
		"apple":  5,
		"banana": 3,
		"orange": 7,
	}
	fmt.Println("初始 map:", myMap)

	// 2. 增加/更新元素
	myMap["grape"] = 4   // 增加
	myMap["banana"] = 10 // 更新
	fmt.Println("增加/更新元素后的 map:", myMap)

	// 3. 删除元素
	delete(myMap, "orange")
	fmt.Println("删除 'orange' 后的 map:", myMap)

	// 4. 查找元素
	value, exists := myMap["apple"]
	if exists {
		fmt.Println("'apple' 存在,值为:", value)
	} else {
		fmt.Println("'apple' 不存在")
	}

	// 查找一个不存在的元素
	_, exists = myMap["melon"]
	if !exists {
		fmt.Println("'melon' 不存在")
	}

	// 5. 遍历 map
	fmt.Println("遍历 map:")
	for key, value := range myMap {
		fmt.Printf("键: %s, 值: %d\n", key, value)
	}

	// 6. 清空 map - 方法1:使用 delete 逐个删除
	for k := range myMap {
		delete(myMap, k) // 逐个删除
	}
	fmt.Println("使用 delete 清空后的 map:", myMap)

	// 7. 清空 map - 方法2:使用 make 创建一个新的 map
	myMap = make(map[string]int) // 原来的 map 将被垃圾回收
	fmt.Println("使用 make 创建新 map 后的 map:", myMap)
}

3.管道

package main

import (
	"fmt"
	"time"
)

func main() {
	// 创建一个缓冲通道
	chan1 := make(chan int, 1)
	chan2 := make(chan string, 1)

	// 启动 goroutine 向 chan1 写入数据
	go func() {
		time.Sleep(time.Second * 1)
		chan1 <- 1
	}()

	// 启动 goroutine 向 chan2 写入数据
	go func() {
		time.Sleep(time.Second * 2)
		chan2 <- "hello"
	}()

	// 持续监听通道
	for {
		select {
		case v := <-chan1:
			fmt.Println("intchan:", v) // 如果 chan1 被写入,打印数据
			return                     // 读取后退出循环
		case v := <-chan2:
			fmt.Println("stringchan:", v) // 如果 chan2 被写入,打印数据
			return                        // 读取后退出循环
		default:
			fmt.Println("防止阻塞") // 如果没有通道可读,打印该信息
			// 等待一段时间,防止立即进入下一循环而输出过多信息
			time.Sleep(500 * time.Millisecond)
		}
	}
}

五、所有重要细节整理

1.每个go文件必须有package声明且只能有一个。
2.局部变量或导入的包必须被使用。
3.同一目录下的所有go文件必须属于同一个包,即同一目录下的所有go文件必须都作一样的package声明。
4.全局变量必须用var关键字定义,定义后可以不使用。
5.未定义数组元素部分采用该元素类型的默认值,比如int数组采用索引赋值法,未赋值的元素默认为0。
6.字符的本质是整数,和int运算时,按照其unicode码值运算。
7.字符串可以用下标访问,但无法通过下标修改。
8.字符串的拼接换行时,加号必须留在上一行末尾。
9.字符串是以字节形式存储的,当存在超过1个字节大小的字符时,遍历字符串要转成rune切片或用for-range。
10.for-range会将字符串视作Unicode字符(即一个rune)的集合进行遍历,所以每次迭代时,从字符串中提取一个rune。
11.当整数型或浮点型存储超出其大小的数字时会溢出报错,但强转的变量即使数值溢出也不会报错。
12.不同类型的变量之间必须强转才能赋值,type定义的类型和原类型之间也要强转。
13.函数名或全局变量名的首字母如果小写,则只能在同包内使用,无法跨包使用。
14.无论是分支结构还是循环结构,单个执行语句都要用花括号包裹。
15.for和switch后面都可以不跟表达式。
16.当返回列表存在且声明了函数内的变量名时,若只写return,那么返回变量的顺序由返回列表的变量名决定,若写了return,则return的优先级高于返回列表。
17.defer会将函数或函数调用语句压入一个栈中,再按栈的顺序取出栈顶元素,函数或调用语句中的变量的值也会一起保存。
18.recover必须在defer函数中调用才能捕获到恐慌,否则返回nil。
19.make只用于切片,map,管道,其中管道必须通过make初始化。
20.map中的key必须是可比较类型,所以不能是切片,映射,函数,管道。
21.go编译器会自动处理结构体指针,所以不仅能用.访问和修改结构体指针指向的内容,方法参数要求是结构体指针或值时,可以传入结构体值或指针。
22.结构体和其它类型进行转换时需要有完全相同的字段(名字、个数和类型)。
23.方法中的receiver的类型只能是自定义类型(结构体或某个类型的别名)。
24.若一个类型定义了其方法,则其他类型不能调用这个方法。
25.如果某个自定义类型实现了返回string,名字为String()的方法(如果是同包内调用,s可以小写),那么fmt包的Printf或Println函数打印该类型时,会自动调用String()。
26.子类和父类中的字段冲突,使用子类对象时优先用子类的字段,如果仍然想用父类的,那么字段访问时就要显示调用父类名:子类.父类.字段。
27.结构体的字段可以是结构体和匿名结构体的组合。二重结构体除了访问时还可以再“点”一下,和其他类型没什么区别,当然,二重结构体不是继承。
28.接口中只能包含方法和接口,不能包含变量。
29.实现接口必须实现其所有方法才算实现。
30.和方法一样,只要是自定义数据类型,就可以实现接口,一个自定义类型可以实现多个接口。
31.接口本身不能创建实例,但可以指向一个实现了该接口的变量(如结构体)。
32.一个接口可以继承多个接口,这时如果要实现该接口,必须将被继承接口的方法都实现。
33.空接口可以接收所有类型,未实现的非空接口除外。
34.管道遍历前一定要关闭,否则会发生死锁。
35.只有go关键字放主线程前,GO才会使协程和主线程并发执行。
36.主线程一旦结束,如果之后没有同步机制或睡眠等多余的操作,协程会立即结束,无论它是否执行完。
37.主线程和协程在宏观上是并行执行的,微观上是通过不同的时间片交替执行的,而且交替顺序未必是固定的,即并发执行。
38.管道用<-读数据和写数据,但这里的”读“是取出数据,”写“是存入数据。
39.空管道或满管道都会造成阻塞。
40.管道关闭后,就不能向它写数据了,但可以读数据。
41.默认情况下,管道是可读可写的,但可以声明为只读或只写。
42.select选取的仅是准备好的通道,只有多个通道都准备好才会随机选取,否则只选先准备好的通道。
43.切片的索引区间是左闭右开的。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

技术卷

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值