go语言基础语法
0.基本目标
- 熟悉相关的Go语言基础语法
- 了解slice,map的部分底层原理
- 完成相关代码的自己实现
1.变量
go语言当中变量声明的基本格式是
var 变量名 变量类型
常用的几种的变量声明方式如下: ``
func Variable_tests() {
var a = "hello,world!" //自动推导变量类型 var 变量=value
var b, c int = 1, 2 //显式说明变量类型
var d = true
var e float64
f := float32(e)
g := a + "foo"
fmt.Println(a, b, c, d, e, f, g)
const s string = "constant"
const h = 300000
const i = 3e20 / h
fmt.Println(s, h, i, math.Sin(h), math.Sin(i))
}
关于变量我觉得需要注意的点在于匿名变量,这部分我们没有介绍,但是后面的例子上面用到了。
使用
多重赋值时,如果不需要在左值中接受变量,可以使用匿名变量
例如
t3, _ := time.Parse("2006-02-02 13:14:15", "2023-01-02 13:14:15")
“_”本身就是一个特殊的标识符,被称为空白标识符。它可以像其他标识符那样用于变量的声明或赋值(任何类型都可以赋值给它),但任何赋给这个标识符的值都将被抛弃,因此这些值不能在后续的代码中使用,也不可以使用这个标识符作为变量对其它变量进行赋值或运算。 上面的这个例子用来处理了error,但是本质是错误的。正常情况下,我们对于error一定要进行处理的,不可以将其舍弃。
2.流程控制
这部分包括if-else,for,switch和for range这四部分的课程内容相互对应。
2.1 if-else
go语言的if-else没有括号,即使你加上,编译器也会自动给你去掉。其次 go语言的if-else要求必须要加上{}
if conditions {
// 条件为真执行
}
else{
//否则实现else
}
这是其基础语法表现形式。 其中if-else彼此之间也可以互相之间嵌套 下面是我实现老师课程所讲内容代码。
func control_gramer() {
if 7%2 == 0 {
fmt.Println("7 is even")
} else {
fmt.Println("7 is odd")
}
if 8%4 == 0 {
fmt.Println("8 is divided by 4")
}
if num := 9; num < 0 {
fmt.Println(num, "is negative")
} else if num < 10 {
fmt.Println(num, "has a digit")
} else {
fmt.Println(num, "has multiple digit")
}
}
2.2 go语言的for循环
for循环的基本结构
for i := 0; i < 10; i++ {
//for循环内容
}
go语言抛弃了while和do-while循环只保存了一种形式,即for循环形式。 本质算法层面for循环和while和do-while也可以进行等价,三者彼此可以互相转换。详细一些底层实现可以看一下这篇文章从汇编角度看三种循环结构 关于for循环相关主要代码实现:
func ForTests() {
i := 1
for {
fmt.Println("loop")
break
}
for j := 7; j < 9; j++ {
fmt.Println(j)
}
for n := 0; n < 5; n++ {
if n%2 == 0 {
continue
}
fmt.Println(n)
}
for i <= 3 {
fmt.Println(i)
i++
}
}
接下来是一些探究内容: 对于go语言的for循环,跳出方式有四种。
- return语句
- break
- goto
- panic 对于1,我们运行到return语句就会退出,对于2运行到break会跳出循环,对于3可以goto到程序任何设置的位置,不过由于goto对于系统维护性具有较大损失,所以不建议使用。最后是panic,报错之后直接结束。
2.3 switch循环
switch循环语句,不是任天堂主机(狗头)。 go语言的switch和if类似没有括号,并且switch 分支表达式可以是任意类型,不限于常量。可省略 break,默认自动终止。其执行顺序从上到下依次执行,直到退出。 其基础语法如下:
switch var1 {
case val1:
...
case val2:
...
default:
...
}
这里所说的默认自动终止,如果有一个case的实现了程序自动运行break跳出。如果我们想要接着运行下一个case,我们使用fallthrough。无论下个条件是否为真,我们都会执行。 下面是我实现的相关代码:
func SwitchTest() {
a := 2
switch a {
case 1:
fmt.Println("one")
case 2:
fmt.Println("two")
case 3:
fmt.Println("three")
case 4, 5:
fmt.Println("four or five")
default:
fmt.Println("other")
}
t := time.Now()
switch {
case t.Hour() < 12:
println("It is before noon")
default:
println("It is after noon")
}
}
2.4 for-range
限于老师课上时间,这里没有介绍这个我们也是经常使用的一个语法for-range,其基本形式如下:
for key, val := range data {
...
}
这个由于在日常开发过程中,我们经常用来遍历数组,map,channel,用的非常频繁。后面介绍复合数据类型的时候重点介绍。
3.复合数据类型
3.1数组
数组是一个由固定长度的特定类型元素组成的序列,其长度可为0个元素或者多个元素。由于实际开发过程中,并不容易事先确定数组长度,因此我们使用slice更多一些。 go语言数组声明的基本形式如下:
var 数组变量名 [元素数量]Type
下面是我的一些实现:
unc ArrayTest() {
var a [5]int //声明数组
a[4] = 100 //赋值
fmt.Println(a[4], len(a))
b := [5]int{1, 2, 3, 4, 5} //同时进行声明和赋值
fmt.Println(b)
var TwoD [2][3]int //声明多维数组
for i := 0; i < 2; i++ {
for j := 0; j < 3; j++ {
TwoD[i][j] = i + j //对多维数组进行赋值
}
}
fmt.Println("2d", TwoD)
}
3.2切片
Slice(切片)代表变长的序列,序列中每个元素都有相同的类型。一个slice类型一般写作[]T,其中T代表slice中元素的类型;slice的语法和数组很像,只是没有固定长度而已。
上面的这段来自go语言圣经的定义介绍了一下什么是切片。Go语言中切片的内部结构包含地址、大小和容量,切片一般用于快速地操作一块数据集合。
要注意的是slice的第一个元素并不一定就是数组的第一个元素。
多个slice可能会共享同一个底层数据,并且引用的底层数组可能重合。
举个简单的例子,一年12个月,底层是12个月的数组,我们分别取3-9月和6到12月,这两个切片共用了同一个底层数据6到9月。
长度对应slice中元素的数目;数组的长度不能超过容量,容量一般是从slice的开始位置到底层数据的结尾位置。其中内置的len和cap函数分别返回slice的长度和容量,长度与容量代表两个不同含义。举个简单例子,一个房子能住100人,容量为100,实际住了20人,长度为20。
slice的基本格式如下:
//name 表示切片的变量名,Type 表示切片对应的元素类型。
var name []Type
下面是我代码的一些实现
func SliceTests() {
fmt.Println("切片相关测试")
s := make([]string, 3)
s[0] = "a"
s[1] = "b"
s[2] = "c"
fmt.Println("get", s[2])
fmt.Println("len", len(s))
s = append(s, "d")
s = append(s, "e", "f")
fmt.Println(s)
c := make([]string, len(s))
copy(c, s)
fmt.Println(c)
fmt.Println(s[2:5])
fmt.Println(s[2:])
fmt.Println(s[:5])
good := []string{"g", "o", "o", "d"}
fmt.Println(good)
}
slice和数组的区别:
- 数组拥有固定长度,而slice没有
- 数组可以直接使用==比较(不建议),slice使用bytes.Equal函数来判断两个字节型slice是否相等([]byte)。对于别的类型需要一一展开进行比较。
下面简单说一下slice的扩容: 每次调用append函数的时候,对于slice进行的检测,如果 底层数组拥有的的容量足够容纳新来的元素,那么将新加入的元素复制到拓展的空间,依然还在底层数组。
但是如果不足以容纳,会将以当前slice的容量的2倍开辟新区域,接着将原数组内容copy到新数组,并接下来复制append的元素。 这里需要注意的是,原数组这部分的数据使用的底层数组已经发生了改变。
3.3MAP
map 是一种无序的
键值对的集合。
map 是引用类型,在我们日常工作过程中,使用的非常多,其基本声明格式:
varname map[keytype] valuetype
下面是我的一些map的代码实现
func maptests() {
fmt.Println("哈希表的使用")
m := make(map[string]int)
m["one"] = 1
m["two"] = 2
m["three"] = 3
fmt.Println(m)
fmt.Println(len(m))
fmt.Println(m["one"])
fmt.Println(m["four"])
r, ok := m["four"]
println(r, ok)
delete(m, "one")
m2 := map[string]int{"one": 1, "two": 2}
var m3 = map[string]int{"one": 1, "two": 2}
println(m2, m3)
}
map的key需要不重复,但是没有提供set,因此map使用slice,例如通过将 value 定义为 []int 类型或者其他类型的切片。
3.4结构体
结构体是一种聚合的数据类型,是由零个或多个任意类型的值聚合成的实体。每个值称为结构体的成员。 结构体的定义格式如下:
type 类型名 struct {
字段1 字段1类型
字段2 字段2类型
}
下面是我关于结构体部分的代码实现:
type user struct {
id int
name string
password string
}
结构体成员的输入顺序也有重要的意义,如果元素定义的先后顺序不同,在程序中会被认作不同的结构体。
3.5JSON
JavaScript对象表示法(JSON)是一种用于发送和接收结构化信息的标准协议。
相比JSON,Protocol Buffers在微服务框架中广泛使用,YAML对于人来说较为直观清晰,JSON对于机器来说很好读取,XML对于以上都不好读取(狗头)。 但是由于简洁性、可读性和流行程度等原因,JSON是应用最广泛的一个。
go语言里面的JSON 操作非常简单,对于一个已有的结构体,我们可以什么都不做,只要保证每个字段的第一个字母是大写,也就是是公开字段。那么这个结构体就能用 JSON.marshaler 去序列化,变成一个JSON的字符串。 序列化之后的字符串也能够用 JSON.unmarshaler 去反序列化到一个空的变量里面。
这样默认序列化出来的字符串的话,它的风格是大写字母开头,而不是下划线。我们可以在后面用json tag 等语法来去修改输出 JSON 结果里面的字段名。
下面是我关于JSON的相关代码实现:
type userInfo struct {
Name string
Age int
Hobby []string
}
func jsontests() {
a := userInfo{"wang", 18, []string{"golang", "java"}}
buf, err := json.Marshal(a)
if err != nil {
panic(err)
}
fmt.Println(buf) //输出字节流
fmt.Println(string(buf))
buf, err = json.MarshalIndent(a, "", "\t")
if err != nil {
panic(err)
}
fmt.Println(buf) //输出字节流
fmt.Println(string(buf))
var b userInfo
err = json.Unmarshal(buf, &b)
if err != nil {
panic(err)
}
fmt.Printf("%v\n", b)
}
4.总结
上面简单介绍了go语言的变量,流程控制和常见的复合数据类型的一篇技术学习总结。希望可以帮到大家。