Go基础学习Part1 变量、常量、类型
前言
最近虽然一直在投简历,但是面试很少,在家闲着也是闲着。之前虽然也是在学习Vue3,自己搭框架写些有的没的。但是没有后端写起来总觉得不得劲,所以想着学一学后端。
关于为什么选择GO:第一,Java目前来说实在是太卷了。第二,之前在面试过程中,也是问过一些面试官,得到的回复也是推荐学习GO。第三,了解过后发现越来越多的大厂在使用Go。
所以,最终决定给自己定个目标:学好GO并搭配前端自己写个小项目!接下来,会陆陆续续写一些GO的学习笔记。开始吧~
概览
Go,又称Golang,是由Google开发的开源编程语言。在语法上与C语言相近,但增加了一些功能,如:垃圾回收机制、内存安全等。Go语言的应用场景广泛,不仅可用于网络编程和系统编程,还适用于并发编程和分布式编程。Go语言的推出旨在降低代码的复杂性,同时不损失应用程序的性能。它具有“部署简单、并发性好、语言设计良好、执行性能好”等优势。(摘自网络)
Go的特性
- 自动垃圾回收:所有的内存分配动作都会被在运行时记录,同时任何对该内存的使用也都会被记录,然后垃圾回收器会对所有已经分配的内存进行跟踪监测,一旦发现有些内存已经不再被任何人使用,就阶段性地回收这些没人用的内存。
- 丰富的内置类型:map、slice(数组切片);不需要再费事添加依赖,减少工作量,简洁代码。
- 函数多返回值
- 错误处理:三个关键字 defer、panic、recover;可以大量减少代码量,让开发者也无需仅仅为了程序安全性而添加大量一层套一层的try-catch语句。
- 匿名函数和闭包
- 类型和接口:不支持继承和重载,而只是支持了最基本的类型组合功能。
- 并发编程:引入协程(goroutine)概念。当一个协程阻塞的时候,调度器就会自动把其他协程安排到另外的线程中去执行,从而实现了程序无等待并行化运行。实现了CSP(通信顺序进程,Communicating Sequential Process)模型来作为goroutine间的推荐通信方式。
- 反射:通过反射,你可以获取对象类型的详细信息,并可动态操作对象。常用场景是做对象的序列化。
- 语言交互性
环境准备
安装Go
通过Go官网下载,根据需要下载相应安装包。mac也可以通过brew安装brew install go
通过go version检查是否安装成功。
$ go version
go version go1.22.2 darwin/arm64
VSCode 配置
- 打开扩展,搜索GO插件并安装
- 安装插件Tools
Command + Shift + P打开设置,输入Go install- 选择
Go: Install/Update Tools
- 全部勾选,点击确认。
PS: 如果出现大量的下载失败,那么打开终端,输入:
go env -w GO111MODULE=on
go env -w GOPROXY=https://proxy.golang.com.cn,direct
然后重启Vscode
基础学习
初步学习
文件结构
写一个helloworld
/* 声明包,可执行程序的包名通常为main */
package main
/* 导入包,按字母排序 */
import "fmt"
/* 函数定义,main函数是程序入口点。*/
func main() {
fmt.Println("Hello World!")
}
:= 操作符
又称短变量声明操作符,用于声明变量并初始化。在函数内部使用,用于创建新局部变量,且隐式赋予对应类型。
语法:variable := expression
需要注意,声明变量并初始化时,左侧变量不能是被声明过的,否则会编译报错。
程序编译
- 直接编译运行:
go run hello.go - 生成编译结果:
go build hello.go
注意项
- 不得包含没有用到的包,否则会编译报错
- 强制左花括号
{,另起一行会导致编译错误 - 不要求开发者在每个语句后面加上分号表示语句结束
待补充...
变量
声明
纯粹的变量声明,引入关键字var, 类型信息放在变量后。如果需要初始化,则var关键字则非必要,同时也可省略类型,编译器可以自动推导出类型。
// 单个变量声明
var a int
var b string
// 多个变量合并声明
var (
a int
b string
)
// 变量声明并初始化
var value1 int = 10 // 写法一
var value2 = 20 // 写法二:省略类型
value3 := 30 // 写法三【推荐】
赋值
普通变量赋值与其他语言一致。但go引入了多重赋值功能,显著减少代码行数。
// 普通变量声明赋值:
var a int
a = 123
// 多重赋值
i, j = j, i
// 等同于如下:其他没有多重赋值功能语言,需要引入一个中间变量。
// t = i; i = j; j = t;
匿名变量
传统强类型语言编程中,调用函数取值时,因为返回多个值而定义一些无用的变量。go通过匿名变量可以避免。🌰:
func GetUserInfo() (userName, nickName, tel string) {
return "Lumi", "麦浪冒险家", "12345678988"
}
// 获取手机号码
_, _, tel = GetUserInfo()
// 获取userName
userName, _, _ = GetUserInfo()
// 获取nickName
_, nick, _ = GetUserInfo()
常量
字面常量指程序中硬编码的常量,是无类型的,只要这个常量在相应类型的值域范围内,就可以作为该类型的常量。
定义
用关键字const定义,可以限定类型,但非必需。其右值也可以是一个编译期运算的常量表达式,但不能是任何需要运行期才能得到结果的表达式。
// 🌰
const a float64 = 3.14159265358979323846 // 完整写法
const b = 10.0 // 无类型浮动常数
const (
c int = 100
d = "Gi"
) // 多个常量声明
const x, y, z int = 3,4,5 // 多重赋值
const val1, val2, val3 = "hello", "world", 0 // 多重无类型赋值
const make = val3 << 3 // 常量表达式
预定义常量
go预定的常量: true,false,iota
iota:一个可被编译器修改的常量,在每个const关键字出现时重置为0,下一个const出现前,每出现一次iota,代表数字+1
// 🌰
const ( // iota被重设为0
c0 = iota // c0 == 0
c1 = iota // c1 == 1
c2 = iota // c2 == 2
// 可以简写为:
// c0 = iota
// c1
// c2
)
枚举
go不支持其他语言常见的enum关键字。举例常规枚举:以大写字母开头的常量在包外可见。
const (
Sunday = iota
Monday
Tuesday
Wednesday
Thursday
Friday
Saturday
numberOfDays // 这个常量没有导出,为包内私有
)
类型
基础类型
整型
分有符号整型和无符号整型
| 类型 | 长度(字节) | 值范围 |
|---|---|---|
| int8 | 1 | -128~127 |
| int16 | 2 | -32 768 ~ 32 767 |
| int32 | 4 | -2 147 483 648 ~ 2 147 483 647 |
| int64 | 8 | -9 223 372 036 854 775 808 ~ 9 223 372 036 854 775 807 |
| int | 平台相关 | 平台相关 |
| uint8(byte) | 1 | 0 ~ 255 |
| uint16 | 2 | 0 ~ 65 535 |
| uint32 | 4 | 0 ~ 4 294 967 295 |
| uint64 | 8 | 0 ~ 18 446 744 073 709 551 615 |
| uint | 平台相关 | 平台相关 |
| uintptr | 同指针 | 在32位平台下为4字节,64位平台下为8字节 |
需要注意⚠️:
编译器不会自动做类型转换,如 int 和 int32比较,需要通过强制类型转换。
强制类型转换需要注意精度丢失或值溢出问题。
运算
加减乘除、取余、比较运算 与多数其他语言一致。Go语言还支持位运算:
浮点型
用于包含小数点的数据。定义了两个类型 float32 和 float64。编译器自动推导的float,会被自动设为float64。两种类型的数比较需要强转类型。
关于浮点数的两数比较:因为浮点数在计算机中的表示是不精确的,存在舍入误差。所以通过设置一个容差值,计算两数差的绝对值是否小于容差值。结果为true的话,表示两数接近相等。
func IsEqual(f1, f2, p float64) bool {
return math.Abs(f1-f2) < p
}
布尔
关键字为 bool,可赋值true 和false。不接受其他类型赋值,不支持自动或强制类型转换。
// 🌰
var b bool
// 错误写法❌
// b = 1
// b = bool(1)
// 正确写法✔️
b = true
b = (1 != 0)
c := (1 != 2)
复数类型
定义了两种类型:complex64、complex128。由两个实数组成,一个表示实部、一个表示虚部。自动推导为complex128类型。
可以通过real(z)获取复数实部,用imag(z)获取复数虚部。
var value1 complex64 // 由2个float32构成的复数类型
value1 = 3.2 + 12i
value2 := 3.2 + 12i // value2是complex128类型
value3 := complex(3.2, 12) // value3结果同 value2
字符串
string,定义和初始化都非常简单。
- 字符串内容不能在初始化后被改变
- 用
len()获取字符串长度。 +字符串连接s[i]:取字符 字符串遍历有两种方法:以字节数组方式遍历 、 以Unicode字符遍历
// 方法一:
str := "Hello, world"
n := len(str)
for i := 0; i< n; i++ {
ch := str[i]
fmt.Println(i, ch)
}
//输出结果ch类型为byte
// 方法二:
str := "hello, world"
for i, ch := range str {
fmt.Println(i. ch)
}
// 输出结果ch类型为rune
// 早期go用int类型表示Unicode
// Go仅支持UTF-8和Unicode编码。
复合类型
引用类型:Slice、Channels、Map、Pointers、Functions
非引用类型:Array、Struct
数组
数组就是指一系列同一类型数据的集合。格式:var array_name =[length]datatype{values},常规声明:
[32]byte // 长度为32的数组,每个元素为一个字节
[2*N] struct { x, y int32 } // 复杂类型数组
[1000]*float64 // 指针数组
[3][5]int // 二维数组
[2][2][2]float64 // 等同于[2]([2]([2]float64))
数组长度在定义后不可更改,声明时长度可以是一个常量/常量表达式。用len(arr)获取数组个数/长度。数组遍历:
// 方法一
for i := 0; i < len(arr); i++ {
fmt.println(arr[i])
}
// 方法二:
for i, v := range arr {
fmt.Println(arr[i], v)
}
数组是一个值类型,赋值和作参传递时会产生一次复制动作。作参时,函数内操作的数组只是所传入数组的副本。那么,如何才能在函数内操作外部的数据结构呢?我们可以使用数组切片slice实现。
数组切片
数组切片弥补了数组的不足。数组切片拥有自己的数据结构,可以抽象为三部分:
- 指向原生数组的指针。
- 数组切片中的元素个数。
- 数组切片已分配的存储空间。
基于数组创建:可以只使用数组的一部分元素或全部元素;也可以创建比基于数组更大的数组切片。
// 使用arry[first:last]方法
myArray := [10]int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
mySlice1 := myArray[:] // 全部
mySlice2 := myArray[:5] // 前5个
mySlice3 := myArray[5:] // 第5个开始
基于数组切片创建:
// 使用arry[first:last]方法
myArray := [10]int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
mySlice1 := myArray[:] // 全部
mySlice2 := mySlice1[:5] // 前5个
直接创建: 通过内置函数make()创建,Go底层还会有一个匿名数组被创建。
mySlice1 := make([]int, 5) // 初始元素个数为5的数组切片,元素初始值为0
mySlice2 := make([]int, 5, 10) // 初始元素个数为5的数组切片,元素初始值为0,并预留10个元素的存储空间
mySlice3 := []int{1, 2, 3, 4, 5} // 创建并初始化包含5个元素的数组切片
支持内置函数
len()返回当前存储元素个数cap()返回数组切片分配空间大小append(arr, 元素1,...)新增元素:可以按自己需求添加若干元素,也可以是一个数组切片(如果第二参数是数组切片则必须加上省略号...,相当于打散解构后传入)copy(): 将一个数组切片复制到另一个数组切片,如果长度不一样大,按小的数组切片元素个数复制。
元素遍历:类似数组元素遍历
struct
struct将不同数据类型的多重值存储到一个变量中,可用于将数据分组在一起以创建记录。等同于其他语言的类,但放弃了大量面对对象特性,只保留组合这个最基础特性。
声明:结构成员可以是不同的数据类型。
type struct_name struct {
member1 datatype;
member2 datatype;
member3 datatype;
...
}
初始化:分几种方法
type Person struct {
Name string
Age int
}
// 方法一:
var p Person
p.Name = "lumi"
p.Age = "20"
// 方法二:
p1 := Person{Name: "lumi", Age: 20}]
// 方法三:使用new函数
p2 := new(Persion)
// 方法四:返回一个该结构体实例;也可以是返回指向结构体实例的指针
func NewPerson(name String, age int) Person {
return Person{Name: name, Age: age}
}
p3 := NewPerson("lumi", 22) // &{lumi, 22}
访问结构中变量使用结构变量.结构成员。同时,结构还可以作为函数参数传递。
type Person struct {
name string
age int
}
var pers1 Person
pers1.name = "Lumi"
pers1.age = 20
fmt.Println("Name: ", pers1.name) //Lumi
fmt.Println("Age: ", pers1.age) // 20
map
C++/Java中,map一般都以库的方式提供,而Go中,map不需要引入任何库。map是一堆键值对的未排序集合。
声明:var myMap map[keyType]valueType{}, keyType是 键类型,valueType是值类型
创建:make(map[keyType] valueType [, size])
赋值、更新、新增:map[key] = value
删除:delete(map, key)
查找:
//判断是否成功找到特定的键,不需要检查取到的值是否为nil,只需查看第二个返回值ok,这让表意清晰很多。
value, ok := myMap["1234"]
if ok { // 找到了
// 处理找到的value
}
循环:可以使用range循环获取键和值
func main() {
a := map[string]int{"one": 1, "two": 2, "three": 3, "four": 4}
for k, v := range a {
fmt.Printf("%v : %v, ", k, v)
}
}
注意:
keyType不能是Silces、Maps、Function;valueType可以是任何类型;- 如果一个
map引用了另一个map,那么一个map修改了,则会影响另一个map
指针
指针存储的是另一个变量的内存地址。通过指针可以直接访问和操作该内存地址中数据。使用场景:
- 修改函数外部变量值
- 节省内存空间
- 动态内存分配
定义:在类型名前加上*定义指针变量
分配内容并初始化:使用new函数或&操作符。
访问指针指向值、修改指针指向值:使用*操作符来访问/修改指针指向的值。
var ptr *int // 定义一个指向int类型的指针变量ptr
// new 初始化
ptr := new(int) // 分配一个int类型的内存,并返回指向它的指针
*ptr = 42 // 通过指针设置内存中的值为42
// & 操作符
var x int = 42
ptr := &x // 获取变量x的内存地址,并赋值给ptr
fmt.Println(*ptr) // 输出:42
*ptr = 100 // 修改ptr指向的内存中的值为100
作为函数参数
func modifyValue(ptr *int) {
*ptr = 100 // 修改指针指向的值
}
func main() {
x := 42
modifyValue(&x) // 传递x的地址给函数
fmt.Println(x) // 输出:100
}
类型转换
1、一般类型转换很简单,格式:value := typeName(其他类型值)
2、string和int互相转换,需要用到一个包strconv。
package main
import (
"fmt"
"strconv"
)
func main() {
var s string = "55"
// string 转换 int
v, _ := strconv.Atoi(s) // Atoi() 返回两个值(int, err)
var i int = 23
// int 转换为 string
str := strconv.Itoa(i)
}
3、int和float相互转换会产生值溢出或精度丢失问题。
4、string和byte互相转换:
// string 转 byte
var s string = "hello"
var b []byte = []byte(s)
// byte 转 string
ss := string(b)
5、除法运算中的隐式类型转换:
int / int, 结果类型为int
float / int, 结果类型为float