语法
1.变量可见性
1)声明在函数内部,是函数的本地值,类似private
2)声明在函数外部,是对当前包可见(包内所有.go文件都可见)的全局值,类似protect
3)声明在函数外部且首字母大写是所有包可见的全局值,类似public
2.变量声明
var(声明变量), const(声明常量),type(声明类型) ,func(声明函数)。
3.变量与常量
3.1变量声明
go中变量需要声明后才能使用,同一个作用域不支持重复声明。并且声明后必须使用
3.2 标准声明
var 变量名 变量类型
3.3 批量声明
var (
a string
b int
c bool
d float32
)
3.4 变量初始化
go在声明变量的时候,会自动对变量对于的内存区域进行初始化操作。每个变量会被初始化成其类型的默认值。例整型与浮点为0,布尔为false,引用为nil。
var 变量名 类型 = 表达式
//一次初始化多个变量
var name,sex = "hello",1
//类型推导
可以省略类型,编译器会根据等号右边的值来推导
//短变量声明
在函数内部可以使用:=方式声明并且初始化变量
func main(){
n := 10
m := 200
//类型转化
var e float64
f := float32(e) //将e转化成float32类型并且赋值给f
}
//匿名变量
在使用多重赋值时,如果想要忽略某个值,可以使用匿名变量(anonymous variable)。 匿名变量用一个下划线_表示,
func foo() (int, string) {
return 10, "Q1mi"
}
func main() {
x, _ := foo()
_, y := foo()
}
//注意
1.)函数外的每个语句必须以关键字开头(var,const,func)
2.):= 不能使用在函数外
3.)_多用于占位,表示忽略值
3.5 常量
将var变为const,并且定义时必须赋值
3.6 iota
是go语言的常量计数器,只能在常量的表达式中使用。iota在const关键字出现时将被重置为0.const中每新增一行常量声明,将使iota计数一次。
const (
n1 = iota //0
n2 //1
n3 //2
n4 //3
)
具体使用还可查看文档
4.流程控制
4.1 if
语法:
-
可省略括号
-
可以初始化局部变量
if n := "abc";x > 0{ fmt.println(n[2]) } //初始化语句未必就算定义变量,使用print也可以
4.2 循环
go循环有三种形式
1.) for init;condition; post{
}
2.) for condition{
}//代替while
3.) for {
}//代替 for(;;){} 只循环一次
//init 一般为赋值语句
//condition 为条件语句
//post 一般为赋值语句 给控制变量增量或者减量
4.)for true{
}//无限循环
4.3 switch
go中switch分支表达式可以是任何类型,不限制于常量。可省略break,默认自动终止
switch var1{
case val1:
case val2:
}
var1 和 val1,val2 必须是同一类型
Go的switch非常灵活,表达式不必是常量或整数,执行的过程从上至下,直到找到匹配项;
而如果switch没有表达式,它会匹配true。
Go里面switch默认相当于每个case最后带有break,
匹配成功后不会自动向下执行其他case,而是跳出整个switch,
但是可以使用fallthrough强制执行后面的case代码。
4.4 range
类似迭代器操作,返回(索引,值)或者(键,值)
for的range格式可以对 slice map 数组 字符串等进行迭代循环
for key, value := range oldMap {
newMap[key] = value
}
//进行遍历是时会对当前数据结构做一个拷贝,就算修改掉当前对象的引用也不会影响到range的遍历
返回索引 以及 值(s[索引])
可忽略不想要的返回值,或 "_" 这个特殊变量。
5.数组
5.1数组定义
var a [len]int
//长度是数组类型的一部分,因此var a[5] int var a [10]int
//越界会panic
//数组是值类型,赋值和传参会复制整个数组,而不是指针。因此会改变副本的值,不会改变本身的值
//指针数组 [n]*T,数组指针 *[n]T
a := [3]int{1, 2} // 未初始化元素值为 0。
b := [...]int{1, 2, 3, 4} // 通过初始化值确定数组长度。
c := [5]int{2: 100, 4: 200} // 使用引号初始化元素。
var str = [5]string{3: "hello world", 4: "tom"} //将索引为3和4位置的元素赋值
d := [...]struct {
name string
age uint8
}{
{"user1", 10}, // 可省略元素类型。
{"user2", 20}, // 别忘了最后一行的逗号。
}
5.2 多维数组
全局
var arr0 [5][3]int
var arr1 [2][3]int = [...][3]int{{1, 2, 3}, {7, 8, 9}}
局部:
a := [2][3]int{{1, 2, 3}, {4, 5, 6}}
b := [...][2]int{{1, 1}, {2, 2}, {3, 3}} // 第 2 纬度不能用 "..."。
5.3 与数组相关的内置函数
len 和 cap 都返回数组长度(元素个数)
6.内置类型与函数
6.1内置类型
bool
int(32 or 64), int8, int16, int32, int64
uint(32 or 64), uint8(byte), uint16, uint32, uint64
float32, float64
string
complex64, complex128
array -- 固定长度的数组
6.2 内置函数
append -- 用来追加元素到数组、slice中,返回修改后的数组、slice
close -- 主要用来关闭channel
delete -- 从map中删除key对应的value
panic -- 停止常规的goroutine (panic和recover:用来做错误处理)
recover -- 允许程序定义goroutine的panic动作
imag -- 返回complex的实部 (complex、real imag:用于创建和操作复数)
real -- 返回complex的虚部
make -- 用来分配内存,返回Type本身(只能应用于slice, map, channel)
new -- 用来分配内存,主要用来分配值类型,比如int、struct。返回指向Type的指针
cap -- capacity是容量的意思,用于返回某个类型的最大容量(只能用于切片和 map)
copy -- 用于复制和连接slice,返回复制的数目
len -- 来求长度,比如string、array、slice、map、channel ,返回长度
print、println -- 底层打印函数,在部署环境中建议使用 fmt 包
7.切片
7.1 切片结构
type slice struct {
array unsafe.Pointer // 指向底层数组的指针
len int // 切片的长度
cap int // 切片的容量
}
切片是一个包含指向底层数组的指针、长度和容量的结构
7.2 切片定义
//声明
var s []int
//直接赋值
s := []int{1,2,3,4}
//使用make创建
s := make([]int, 5) // 创建一个长度为 5 的切片
s := make([]int, 5, 10) // 创建一个长度为 5,容量为 10 的切片
//从数组中创建
arr := [5]int{1,2,3,4,5}
s := arr[1:4] //创建一个索引1到3的切片
切片在创建时底层会创建一个数组,并且这个切片会指向这个数组的一部分
指定了长度的为数组,没有指定长度的是切片
当使用切片符操作时会产生一个新的切片,如arr[1:4] ,无论是数组还是切片都会产生一个新的切片,新的切片还是引用的原来的数组
8.map
8.1定义
map[keyType]ValueType
//使用make分配内存
make(map[keyType]valeType,cap)
cap表示map的容量但是不是必须的
8.2 使用
//初始化
func main() {
userInfo := map[string]string{
"username": "pprof.cn",
"password": "123456",
}
fmt.Println(userInfo) //
}
//判断某个键是否存在
value, ok := map[key]
存在ok值为true 不存在值为 false
//使用delete函数删除键值对
delete(map, key)
9.函数
9.1 函数声明
- 当两个或多个连续的函数命名参数是同一类型,则除了最后一个类型之外,其他都可以省略。
- 可以返回任意数量的返回值
- 函数也是一种类型可作为参数传递
func test(x, y int, s string) (int, string) {
// 类型相同的相邻参数,参数类型可合并。 多返回值必须用括号。
n := x + y
return n, fmt.Sprintf(s, n)
}
func test(fn func() int) int {
return fn()
}
func main() {
s1 := test(func() int { return 100 }) // 直接将匿名函数当参数。
}
9.2 参数
Golang 可变参数本质上就是 slice。只能有一个,且必须是最后一个。
func myfunc(args ...int) { //0个或多个参数
}
func add(a int, args…int) int { //1个或多个参数
}
func add(a int, b int, args…int) int { //2个或多个参数
}
//使用 slice 对象做变参时,必须展开(slice...
func main() {
s := []int{1, 2, 3}
res := test("sum: %d", s...) // slice... 展开slice
println(res)
}
10.指针
&(取地址)和*(根据地址取值)。
Go语言中的值类型(int、float、bool、string、array、struct)都有对应的指针类型,如:*int、*int64、*string
当一个指针被定义后没有分配到任何变量时,它的值为 nil
10.1 分配空间
指针只能指向已经存在的内存空间
10.1.1 new
func new(Type) *Type
//使用new 函数得到一个类型的指针,并且该指针对于的值为该类型的零值
10.1.2 make
make也是用于内存分配的,区别于new,它只用于slice、map以及chan的内存创建,而且它返回的类型就是这三个类型本身,而不是他们的指针类型,因为这三种类型就是引用类型,所以就没有必要返回他们的指针了。make函数的函数签名如下:
func make(t Type, size ...IntegerType) Type
11.结构体
Go语言中没有“类”的概念,也不支持“类”的继承等面向对象的概念。Go语言中通过结构体的内嵌再配合接口比面向对象具有更高的扩展性和灵活性。
11.1 自定义类型
自定义类型是定义了一个全新的类型。我们可以基于内置的基本类型定义,也可以通过struct定义
//将MyInt定义为int类型
type MyInt int
//自定义user类型
type user struct{
name string
password string
}
11.2 类型别名
TypeAlias只是Type的别名,本质上TypeAlias与Type是同一个类型
type TypeAlias = Type
//我们之前见过的rune和byte就是类型别名,他们的定义如下:
type byte = uint8
type rune = int32
类型定义与类型别名的区别,前者是真实的类型,后者只是一个别名在实际输出时%T输出的是源类型
11.3 结构体的定义
使用type和struct关键字来定义结构体,具体代码格式如下:
type 类型名 struct {
字段名 字段类型
字段名 字段类型
…
}
//实例化
var p1 person
p1.name = "pprof.cn"
p1.city = "北京"
p1.age = 18
11.4 匿名结构体
var user struct{Name string; Age int}
user.Name = "pprof.cn"
user.Age = 18
11.5 创建指针类型结构体
var p2 = new(user) //创建一个指针类型
//直接实例化创建的是值类型,传递时是值传递
//取结构体的地址实例化
p3 := &person{} //相当与使用new创建
p3.name = "博客"其实在底层是(*p3).name = "博客",这是Go语言帮我们实现的语法糖。
11.6 结构体初始化
//默认初始化
type person struct{
name string
age int8
}
func main(){
var p person //输出默认值stirng为空串,int为0
}
//使用键值对初始化
p5 := person{
name: "pprof.cn",
city: "北京",
age: 18,
}//也可以对结构体指针初始化
//初始化可以不写键
必须初始化所有字段
顺序需要一致
不能与键值方式混用
11.7 结构体的内存布局
type test struct {
a int8
b int8
c int8
d int8
}
n := test{
1, 2, 3, 4,
}
fmt.Printf("n.a %p\n", &n.a)
fmt.Printf("n.b %p\n", &n.b)
fmt.Printf("n.c %p\n", &n.c)
fmt.Printf("n.d %p\n", &n.d)
n.a 0xc0000a0060
n.b 0xc0000a0061
n.c 0xc0000a0062
n.d 0xc0000a0063
//是一块连续的内存地址
11.8 结构体方法
11.8.1 方法和接收者
Go语言中的方法(Method)是一种作用于特定类型变量的函数。这种特定类型变量叫做接收者(Receiver)。接收者的概念就类似于其他语言中的this或者 self。
//方法的定义
func (接收者变量 接收者类型) 方法名(参数列表) (返回参数) {
函数体
}
//方法与函数的区别是,函数不属于任何类型,方法属于特定的类型。
func (p Person) Dream() {
fmt.Printf("%s的梦想是学好Go语言!\n", p.name)
}
//指针类型的接收与值类型接收
func (p *Person) SetAge(newAge int8) {
p.age = newAge
}
指针类型会改变实际值,值类型不会改变
11.9 匿名字段
定义时没有名字,只有类型,调用时可以直接调用类型,所以类型只能存在一种
//Person 结构体Person类型
type Person struct {
string
int
}
fmt.Printf("%#v\n", p1)
fmt.Println(p1.string, p1.int)
11.10 嵌套结构体
当访问结构体成员时会先在结构体中查找该字段,找不到再去匿名结构体中查找。
嵌套结构体内部可能存在相同的字段名。这个时候为了避免歧义需要指定具体的内嵌结构体的字段。
11.10.1 结构体的继承
利用找不到会去内部寻找该字段的特性
type Animal struct {
name string
}
func (a *Animal) move() {
fmt.Printf("%s会动!\n", a.name)
}
//Dog 狗
type Dog struct {
Feet int8
*Animal //通过嵌套匿名结构体实现继承
}
func main() {
d1 := &Dog{
Feet: 4,
Animal: &Animal{ //注意嵌套的是结构体指针
name: "乐乐",
},
}
d1.wang() //乐乐会汪汪汪~
d1.move() //乐乐会动!
}
11.11 结构体字段的可见性
结构体中字段大写开头表示可公开访问,小写表示私有(仅在定义当前结构体的包中可访问)。
11.12 结构体标签
结构体标签是结构体的元信息,可以在运行时通过反射的机制读取出来
11.12.1格式
Tag在结构体字段的后方定义,由一对反引号包裹起来,具体的格式如下
`key1:"value1" key2:"value2"`
//Student 学生
type Student struct {
ID int `json:"id"` //通过指定tag实现json序列化该字段时的key
Gender string //json序列化是默认使用字段名作为key
name string //私有不能被json包访问
}
12. 错误处理
13.格式化符号
%v | 任何类型的值,使用默认格式 |
|---|---|
%T | 任何类型的值,显示其类型 |
%t | 布尔值,显示 true 或 false |
%d | 十进制整数 |
%x | 十六进制整数,小写字母 |
%X | 十六进制整数,大写字母 |
%b | 二进制整数 |
%o | 八进制整数 |
%f | 浮点数,小数点表示 |
%e | 浮点数,科学记数法,小写字母 e |
%E | 浮点数,科学记数法,大写字母 E |
%g | 浮点数,根据值的大小选择 %e 或 %f |
%s | 字符串 |
%q | 字符串,用双引号包围,特殊字符转义 |
%p | 指针值,十六进制表示 |
%% | 百分号 % |
在fmt包中有些特别的输出
%+v 按照键值对形式输出
%#v 更加详细的输出,会输出结构体名以及键值 对方式
14.json处理
encoding/json包
私有化变量无法被json转化
buf,err := json.Marshal(a) // buf 返回字节的AScll值
string(buf) //将字节转成json串
//json.MarshalIndent 函数用于将 Go 数据结构转换为格式化(带缩进)的 JSON 字符串。这使得生成的 JSON 字符串更具可读性,特别适用于调试和日志记录。
buf,err = json.MarshalIndent(a,"","\t")
//将字节数组转成结构体
var b userInfo
err = json.Unmarshal(buf,&b)//没有返回值所以使用指针
15.时间处理
导入time包
now = time.Now()//获取当前时间2024-11-04 10:32:49.8537856 +0800 CST m=+0.000519501
//自定义时间
time.Date(2022,3,27,1,25,36,0,time.UTC)
t.Year(),t.Month()
//时间差
diff := t2.Sub(t)
//时间格式化 第一个串是格式化格式,第二个是要格式化的串
t3,err := time.Parse("2006-01-02 15:04:05","2022-03-27 01:25:26")
//获取当前时间戳
now.Unix()
16.字符串与数字的转换
导入strconv包
在 strconv.ParseInt 函数中,第二个参数 base 用于指定字符串的基数(即进制)。当 base 设置为 0 时,ParseInt 会根据字符串的前缀自动检测基数。具体来说:
- 如果字符串以
0x或0X开头,基数会被自动识别为 16(十六进制)。 - 如果字符串以
0开头,基数会被自动识别为 8(八进制)。 - 否则,基数会被自动识别为 10(十进制)。
//解析浮点数
f, _ := strconv.ParseFloat("1.235", 64)
//解析10进制数
n, _ := strconv.ParseInt("111", 10, 64)
// 解析整数(自动检测进制)
n2, _ := strconv.Atoi("123")
// 解析十六进制整数
n, _ = strconv.ParseInt("0x1000", 0, 64)
fmt.Println(n)