Go语言语法基础入门 | (上)
2023/7/29 ·雨辰login
目录:
声明
变量
复合数据类型
分支
函数
面向对象
一些闲话:看到别人做的笔记都非常精致,内容也很干货满满,反观我的笔记就像是一个匆忙的学生在一节难度很高的课上的随堂记。但是至少我做的笔记要让我看得明白看得舒服,才有可能会有更多人来分享经验或者提出意见。
废话不多说,由于内容过多,该文章分为上下两篇(或者上中下三篇)分别发布,主要用于记录自己在学习过程中想要总结的知识内容。 笔记内容主要分为目录中的几部分,虽然是自用,但是如有不全欢迎读者指正。
内容主要总结于
[Go语言圣经]
字节跳动青训营后端入门 - Go语言原理与实践
声明
学习一门新语言时,会有一种自然的倾向,按照自己熟悉的语言套路写新语言程序。学习Go的过程中,请警惕这种想法。
——《Go语言圣经》
声明语句定义了程序的各种实体对象以及部分或全部的属性。Go主要有四种类型的声明语句:var、const、type和func,分别对应常量、变量、类型和函数实体对象的声明
先上代码:
package main
import "fmt"
const boilingF = 212.0
func main() {
var f = boilingF
var c = (f - 32) * 5 / 9
fmt.Printf("boiling point = %g°F or %g°C\n", f, c)
// Output: boiling point = 212°F or 100°C
}
每个源文件中以包的声明语句开始,说明该源文件是属于哪个包。包声明语句之后是import语句导入依赖的其它包,然后是包一级的类型、变量、常量、函数的声明语句(类似于cpp中的全局变量),包一级的各种类型的声明语句的顺序无关紧要(译注:函数内部的名字则必须先声明之后才能使用)。
在包一级声明语句声明的名字可在整个包对应的每个源文件中访问,而不是仅仅在其声明语句所在的源文件中访问。相比之下,局部声明的名字就只能在函数内部很小的范围被访问。
例如,上面的例子中声明了一个常量boilingF(包一级)、一个函数main和两个变量f, c(局部)
关于变量和函数在之后会仔细说,在这里先对常量和类型有一个了解。
常量:const,类似于cpp中的常量,定义后值不可改变,不再赘述
类型: type,一个类型声明语句创建了一个新的类型名称,和现有类型具有相同的底层结构。新命名的类型提供了一个方法,用来分隔不同概念的类型,这样即使它们底层类型相同也是不兼容的。
格式:type 类型名字 底层类型
例如:
type Celsius float64 // 摄氏温度
type Fahrenheit float64 // 华氏温度
我们在这个包声明了两种类型:Celsius和Fahrenheit分别对应不同的温度单位。它们虽然有着相同的底层类型float64,但是它们是不同的数据类型,因此它们不可以被相互比较或混在一个表达式运算。
命名类型还可以为该类型的值定义新的行为。这些行为表示为一组关联到该类型的函数集合,我们称为类型的方法集。
下面的声明语句,Celsius类型的参数c出现在了函数名的前面,表示声明的是Celsius类型的一个名叫String的方法(类似于cpp中结构体里面的函数),该方法返回该类型对象c带着°C温度单位的字符串:
func (c Celsius) String() string { return fmt.Sprintf("%g°C", c) }
关于方法,我们不过多说,以后的笔记中会详细提到。
声明这一块讲的有点多,但是这是以后学习的基础。
变量
变量的声明
不论是python,cpp还是go,任何编程语言都离不开变量的存在。提到变量,我们可以想到变量的声明,变量的初始化等等。
Go中变量声明的几种语法以及注意事项如下:
var a, b int = 8, 9 // 声明变量方式1: var 变量名称 变量类型(该例中int把a和b都涵盖进来了) = 表达式
var a float, b int = 8, 9 // 声明变量方式2: var 变量名称 变量类型(该例中a, b和float, int分别对应) = 表达式
f := float32(e) // 声明变量方式3:(简短变量声明) 变量名称:= 表达式
var str = "this is a string" // 直接定义str,Go会自动判断类型为string
在var 变量名字 类型 = 表达式中,"类型"或“= 表达式”中可以任意省略一个:
如果省略的是类型信息,那么将根据初始化表达式来推导变量的类型信息。如果初始化表达式被省略,那么将用零值初始化该变量。
数值类型变量对应的零值是0,布尔类型变量对应的零值是false,字符串类型对应的零值是空字符串,接口或引用类型(包括slice、指针、map、chan和函数)变量对应的零值是nil。数组或结构体等聚合类型对应的零值是每个元素或字段都是对应该类型的零值。
零值初始化机制可以确保每个声明的变量总是有一个良好定义的值,因此在Go语言中不存在未初始化的变量。
比如说:
var s string
fmt.Println(s) //output: ""
指针
为什么在变量这里讲指针呢?因为一个变量对应一个保存了变量对应类型值的内存空间。一个指针的值就是一个变量的地址。
由此可见,指针和变量有密切的关系,有变量就一定有指针。
x := 1
p := &x // p, of type *int, points to x
fmt.Println(*p) // "1"
*p = 2 // 即 x = 2
fmt.Println(x) // "2"
一个指针对应变量在内存中的存储位置。并不是每一个值都会有一个内存地址,但是对于每一个变量必然有对应的内存地址。通过指针,我们可以直接读或更新对应变量的值,而不需要知道该变量的名字(如果变量有名字的话)。
任何类型的指针的零值都是nil。如果p指向某个有效变量,那么p != nil测试为真。指针之间也是可以进行相等测试的,只有当它们指向同一个变量或全部是nil时才相等。
如果用“var x int”声明语句声明一个x变量,那么&x表达式(取x变量的内存地址)将产生一个指向该整数变量的指针,指针对应的数据类型是*int,指针被称之为“指向int类型的指针”。如果指针名字为p,那么可以说“p指针指向变量x”,或者说“p指针保存了x变量的内存地址”。同时*p表达式对应p指针指向的变量的值。
new函数
说到指针,我们很容易联想到cpp中创建指针的方法new。在go中也有new函数的存在,只不过有一些小区别。
另一个创建变量的方法是调用内建的new函数。表达式new(T)将创建一个指向T的指针,初始化为T类型的零值,然后返回变量地址,返回的指针类型为*T。上代码,一看就明白。
p := new(int) // p, *int 类型, 指向匿名的 int 变量
fmt.Println(*p) // "0"
*p = 2 // 设置 int 匿名变量的值为 2
fmt.Println(*p) // "2"
复合数据类型
有了变量的概念之后,我们可以试着创建一些由简单数据类型构成的符合数据类型。先来了解一下:
数组
定义数组依然有不同的方法:
var arr [3]int = [3]int{1, 2, 3}
arr := [3]int{1,2,3}
var arr [3]int
arr[0] = " "
arr[1] = “Hello”
arr[2] = "ByteDance"
在实践中,数组的使用场景比较局限,更受欢迎的一种数据类型则是切片。
切片(slice)
` s := make([]string, 3) `
- 用make:定义切片(slice),就是长度不限制(其实是有额定的,但是可以不断扩容)的数组,3是初始长度
- 切片的扩容:
s = append(s, "d")
append返回的是扩容过后的新切片,因此要重新赋值
- 创建s一样的长度的切片并拷贝
c := make([]string, len(s))
copy(c,s)
这个时候就产生问题了:为什么不能直接c = s呢?
二者的区别:copy会复制切片中的内容并且占用新的内存空间来存放,而直接用=赋值不会赋值原内容,相当于两个指针指向了一个地方
- 和python语言的语法类似,Go语言使用左开右闭区间选取对应下标范围内的的部分元素,并且有自动补齐到尽头的规则
// slice[n:m]
// 开始下标:n
// 结束下标:m - 1
// 元素个数:m - n
字典(map)
Go语言中的map类型,类似其他语言中的字典或哈希表。
初始化字典的方法:map := make(map[int]string)
其中,int是key的值,string是value的内容
添加键值对:map[key] = "value"
删除键值对:delete(key, value)
range遍历
提完数组,切片,字典就必须说一下range遍历
先上代码:
var arr [3]int = [3]int{1, 2, 3} // arr := [3]int{1,2,3}
for k, v := range arr{
fmt.Printf("%d %d\n", k, v)
}
//结果
0 1
1 2
2 3
其中,range遍历数组、slice、map时候返回key/value两个值,所以需要两个变量(k, v)来接收返回值。
上篇的内容就先写在这里
下篇(中篇)积极更新中~