golang语法基础
- 包
- 数据类型
- 常量
- 变量
- 数组
- 切片
- 字符串
-
- 字面量
- 访问
- 不支持修改
- 转换
- 字符串的内容是只读的不可变的,无法修改,但是字节切片是可以修改的
- 长度
- 拷贝
- 拼接
-
- [+ 拼接](#+ 拼接)
- 转为字节使用append添加
- strings.Builder
- 遍历
- 映射表
- 指针
- 结构体
此文章只做总结,以记住语法为准,学习go的同学应该都不是第一次学习编程语言了,既然玩了go,就要以生产实践为主。
包
导入
import "fmt"
起别名
import f "fmt"
批量导入,使用()来表示
import ( "fmt" "math" )
只导入不调用
import ( "fmt" _ "math" )
在go中禁止循环导入,无论直接还是间接,编译无法通过
导出
该规则适用于整个Go语言:对外暴露,首字母大写;不对外暴露,首字母小写
私有
go中约定,一个包内名为internal包为私有包,其他的包将无法访问私有包中的任何东西
数据类型
bool
true & false
在go中,整数0并不代表假值,非零整数也不能代表真值,即数字无法代替布尔值进行逻辑判断,两者是完全不同的类型
整型
uint8、uint16、uint32、uint64
int8、int16、int32、int64
uint、int
uintptr 等价于无符号64位整型
浮点型
float32、float64
字符类型
byte 等价于uint8表示ANSCII字符
rune 等价于int32表示Unicode字符
string 字节序列,可以转换为[]byte类型即字节切片
派生类型
数组 [5]int,长度为5的整型数组
切片 []float64,64位浮点数切片
映射表 map[string]int ,键为字符串类型、值为整型的映射表
结构体 type Gopher struct{} Gopher结构体
指针 *int 一个整型指针
函数 type f func(),一个参数,没有返回值的函数类型
接口 type Gopher interface{},Gopher接口
通道 chan int,整型通道
零值
数字类型 0
布尔类型 false
字符串类型 ""
数组 固定长度对应类型的零值集合
结构体 内部字段都是零值的结构体
切片、映射表、函数、接口、通道、指针 nil
常量
const 修饰的必须初始化的字面量、常量表达式
变量
var 修饰的
声明
只声明不赋值,则为零值
简单声明
var intNum int
同时声明相同的数据类型
var numA,numB,numC int
同时声明多个变量
var (
name string
age int
)
赋值
先声明再赋值
var name string
name = "jack"
声明直接赋值
var name string = "jack"
同时赋值多个变量
var name string
var age int
name,age = "jack",1
语法糖
a := 1
特殊情况,允许编译通过
go
func main() {
a := 1
a, b := 2, 2
fmt.Println(a)
fmt.Println(b)
}
交换
go
func main() {
num1, num2 := 10, 20
num1, num2 = num2, num1
fmt.Println(num1)
fmt.Println(num2)
}
匿名变量
a, b, _ := 1, 2, 3
比较
类型必须相同
minVal := min(1, 2, -1, 1.2)
数组
go中的数组是值类型,而非引用,并不是指向头部元素的指针
初始化
go
// 声明
var nums [5]int
// 元素初始化
nums := [5]int{1,2,3}
// new函数获取一个指针
nums := new([5]int)
使用
go
fmt.Println(nums[0])
nums[0] = 1
// 元素数量
len(nums)
// 数组容量
cap(nums)
切割
左闭右开
go
nums :=[5]{1,2,3,4,5}
nums[1:]
nums[:5]
nums[2:3]
切片
初始化
go
var nums []int // 值
nums := []int{1,2,3} // 值
nums := make([]int,0,0) // 值
nums := new([]int) // 指针
使用
切片的使用和数组完全一样
append
go
package main
import "fmt"
func main() {
nums := make([]int, 0, 0)
nums = append(nums, 1, 2, 3, 4, 5, 6, 7, 8, 9)
fmt.Println(len(nums), cap(nums))
}
插入元素
头插入
go
func main() {
nums := []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
nums = append([]int{-1, 0}, nums...)
fmt.Println(nums)
}
中间插入
例如在i的位置插入
nums = append(nums[:i+1], append([]int{999, 999}, nums[i+1:]...)...)
尾部插入
nums = append(nums,99,100)
删除元素
从头部删除n个元素
nums = nums[n:]
fmt.Println(nums) //n=3 [4 5 6 7 8 9 10]
从尾部删除n个元素
nums = nums[:len(nums)-n]
fmt.Println(nums) //n=3 [1 2 3 4 5 6 7]
从中间指定下标i位置开始删除n个元素
nums = append(nums[:i], nums[i+n:]...)
fmt.Println(nums)// i=2,n=3,[1 2 6 7 8 9 10]
删除所有元素
nums = nums[:0]
fmt.Println(nums) // []
拷贝
copy
clear
clear会将切片内所有的值置为零值
清空切片
go
func main() {
s := []int{1, 2, 3, 4}
s = s[:0:0]
fmt.Println(s)
}
字符串
在Go中,字符串本质上是一个不可变的只读的字节数组,也是一片连续的内存空间
字面量
普通字符串
支持转义、不支持多行书写
go
"这是一个普通字符串\n"
"abcdefghijlmn\nopqrst\t\\uvwxyz"
原生字符串
支持多行书写,不支持转义
访问
go
func main() {
str := "hello world"
fmt.Println(str[0])
fmt.Println(str[0:4])
}
不支持修改
str[0]='a'
转换
go
func main() {
str := "this is a string"
// 字符串转切片
bytes := []byte(str)
fmt.Println(bytes)
// 切片转字符串
fmt.Println(string(bytes))
}
字符串的内容是只读的不可变的,无法修改,但是字节切片是可以修改的
go
func main() {
str := "this is a string"
// 字符串转切片
bytes := []byte(str)
fmt.Println(bytes)
bytes = append(bytes, 96, 97, 98)
// 切片转字符串
fmt.Println(string(bytes))
}
提示:两种类型之间的转换都需要进行数据拷贝,其性能损耗会随着长度的增加而增长。
长度
字符串的长度,并不是字面量的长度,而是字节数组的长度
go
func main() {
str := "中国人"
fmt.Println(len(str))
}
拷贝
本质是字节切片的拷贝
go
func main() {
var dst, src string
src = "this is a string"
desBytes := make([]byte, len(src))
copy(desBytes, src)
dst = string(desBytes)
fmt.Println(src)
fmt.Println(dst)
}
也可以使用string.clone函数,本质内部实现差不多
go
func main() {
var dst, src string
src = "this is a string"
dst = strings.Clone(src)
fmt.Println(src, dst)
}
拼接
+ 拼接
转为字节使用append添加
go
package main
import "fmt"
func main() {
var str string = "hello world"
bytes := []byte(str)
bytes = append(bytes, "您好 世界"...)
fmt.Println(string(bytes))
}
strings.Builder
以上两种的性能都很差,如果对性能有要求,使用strings.Builder
go
package main
import (
"fmt"
"strings"
)
func main() {
builder := strings.Builder{}
builder.WriteString("hello world")
builder.WriteString("hello world")
fmt.Println(builder.String())
}
遍历
go
func main() {
str := "hello world 您好世界"
for _, r := range str {
fmt.Printf("%c", r)
}
}
rune 本质上是int32的别名
映射表
一般来说,映射表数据结构实现通常有两种,哈希表和搜索树,区别在于前者无序,后者有序,在Go中,map的实现是基于哈希通,所以也是无序的
初始化
字面量
go
func main() {
mp := map[int]string{
0: "a",
1: "b",
2: "c",
3: "d",
}
fmt.Println(mp)
}
make
go
func main() {
mp := make(map[string]int, 8)
mp["a"] = 1
fmt.Println(mp)
}
访问
不存在,返回对应的零值
go
func main() {
mp := make(map[string]int, 8)
mp["a"] = 1
fmt.Println(mp["b"])
}
访问方式
go
func main() {
mp := make(map[string]int, 8)
//mp["a"] = 1
fmt.Println(mp["b"])
if val, exist := mp["a"]; exist {
// 如果存在
fmt.Println(val)
} else {
// 如果不存在
fmt.Println("mp['a'] not exist")
}
}
删除
go
delete(mp,"a")
遍历
go
func main() {
mp := map[string]int{
"a": 1,
"b": 2,
"c": 3,
"d": 4,
}
for k, v := range mp {
fmt.Println(k, v)
}
}
清空
clear(mp)
Set
Set是一种无序的,不包含重复元素的集合,Go中并没有提供类似的数据结构的实现,但是map的键正是无序且不能重复的,所以也可以使用map来替代set
go
func main() {
set := make(map[int]struct{}, 10)
for i := 0; i < 10; i++ {
set[rand.Intn(100)] = struct{}{}
}
fmt.Println(set)
}
提示:一个空的结构体不会占用内存
并发安全
map并不是一个并发安全的数据结构 ,Go团队认为大多数情况下map的使用并不涉及高并发的场景,引入互斥锁会极大的降低性能,map内部有读写检测机制,如果冲突会触发fatal error
在这种情况下,需要使用sync.Map来替代。
指针
Go保留了指针,在一定程度上保证了性能,同时为了更好的GC和安全考虑,又限制了指针的使用
创建
go
func main() {
num := 2
p := &num
fmt.Println(p)
}
解引用符号
- 访问指针所指向元素
- 声明一个指针
go
func main() {
num := 2
p := &num
fmt.Println(*p)
var numPtr *int
numPtr = &num
fmt.Println(*numPtr)
}
指针声明初始化
go
func main() {
var numPtr *int = new(int)
fmt.Println(*numPtr)
numPtr1 := new(int)
fmt.Println(*numPtr1)
}
new 和 make
- new
- 返回值是类型指针
- 接收参数是类型
- 专用于给指针分配内存空间
- make
- 返回值是值,不是指针
- 接收的第一个参数是类型,不定长参数根据传入类型的不同而不同
- 专用于给切片,映射表,通道分配内存
结构体
Go中抛弃了类与继承,同时抛弃了构造方法,刻意弱化了面向对象的功能,Go并非是一个OOP的语言,但是Go依旧有着OOP的影子,通过结构体和方法可以模拟出一个类。结构体可以存储一组不同类型的数据,是一种复合类型
声明
go
type Person struct {
name string
age int
}
对于相同类型的字段,如下声明
go
type Rectangle struct {
height, width float32
}
注意:在声明结构体字段时,字段名和方法名不应该重复
创建
go
package main
import "fmt"
type Programmer struct {
Name string
Age int
Job string
Language string
}
func main() {
programmer := Programmer{
Name: "jack",
Age: 42,
Job: "bash",
Language: "python",
}
fmt.Println(programmer)
}
组合
在Go中,结构体之间的关系是通过组合来表示的,可以显式组合,也可以匿名组合,后者使用起来更类似于继承
go
import "fmt"
type Person struct {
name string
age int
}
type Student struct {
p Person
school string
}
type Employee struct {
p Person
job string
}
func main() {
student := Student{
p: Person{name: "jack", age: 18},
school: "lili school",
}
fmt.Println(student.p.name)
}
go
type Person struct {
name string
age int
}
type Student struct {
Person
school string
}
type Employee struct {
Person
job string
}
func main() {
student := Student{
Person: Person{name: "jack",age: 18},
school: "lili school",
}
fmt.Println(student.name)
}
指针
对于结构体而言,不需要解引用就可以直接访问结构体的内容
go
type Person struct {
name string
age int
}
func main() {
p := &Person{
name: "jack",
age: 18,
}
fmt.Println(p.age)
}
标签
结构体标签是一种元编程的形式,结合反射可以做出很多奇妙的功能
标签是一种键值对的形式,使用空格进行分隔。
结构体的容错性很低,如果没能按照正确的格式书写结构体,那么将导致无法正常读写,但是编译时不会有任何报错
go
type Programmer struct {
Name string `json:"name"`
Age int `json:"age"`
}
空间结构体
空间结构体没有字段,不占用内存空间,可以通过unsafe.SizeOf函数计算占用字节大小
go
func main() {
type Empty struct {
}
fmt.Println(unsafe.Sizeof(Empty{}))
}
应用:例如 将map作为set来使用