go语言学习笔记(一)——基础语法篇
开始学习 go 语言,整个学习进度将通过这个系列文章来进行记录和复盘。也算一种自我监督吧。
go 的环境搭建和ide等看这里即可,这里不做过多叙述。Go语言环境搭建详解
进入GoLand,完成用 go 编写的 helloworld:
package main
import "fmt"
func main() {
fmt.Println("Hello world")
}
Go 的变量定义
go变量声明大概可以分为如下三种情况
使用 var 关键字定义
- var a, b, c bool
- var s1, s2 string = "hello", "world"
- 可放在函数内,或者直接放在包内
- 可使用 var () 集中定义变量
让编译器自动决定类型
- var a, b, s = 3, true, "hello"
使用:=定义变量(相当于上一种的简写)
- a, b := 2, "hello"
- 只能在函数内使用
整理一下相当于下面这四种
s := ""
var s string
var s = ""
var s string = ""
声明方式如此之多,用哪种?
第一种形式,是一条短变量声明,最简洁,但只能用在函数内部,而不能用于包变量。
第二种形式依赖于字符串的默认初始化零值机制,被初始化为""。
第三种形式用得很少,除非同时声明多个变量。
第四种形式显式地标明变量的类型,当变量类型与初值类型相同时,类型冗余,但如果两者类型不同,变量类型就必须了。
实践中一般使用前两种形式中的某个,初始值重要的话就显式地指定变量的类型,否则使用隐式初始化。
第二种方法里的初始化零值机制,具体介绍如下
数值类型变量对应的零值是0,布尔类型变量对应的零值是false,字符串类型对应的零值是空字符串,接口或引用类型(包括slice、map、chan和函数)变量对应的零值是nil。数组或结构体等聚合类型对应的零值是每个元素或字段都是对应该类型的零值。
零值初始化机制可以确保每个声明的变量总是有一个良好定义的值,因此在Go语言中不存在未初始化的变量。这个特性可以简化很多代码,而且可以在没有增加额外工作的前提下确保边界条件下的合理行为。
Go 的内建变量类型
JavaScript里没有这么多类型,因此之后有空需要较为深入的研究各类型。byte和rune之后和UTF-8等单独写一篇学习笔记。
条件语句
go语言中主要的条件语句还是if……else语句,具体用法和JS基本一致,只是布尔表达式不需要用括号包裹
if 布尔表达式 {
/* 在布尔表达式为 true 时执行 */
} else {
/* 在布尔表达式为 false 时执行 */
}
switch的使用也和JS基本一致,只是switch 默认情况下 case 最后自带 break 语句,匹配成功后就不会执行其他 case,如果我们需要执行后面的 case,可以使用 fallthrough 。
switch var1 {
case val1:
...
case val2:
...
default:
...
}
循环
Go语言的For循环有3中形式,只有其中的一种使用分号。
for init; condition; post { } // 和JavaScript基本相同,只是没有括号
for condition { } // 类似于JavaScript的while
for { }
init: 一般为赋值表达式,给控制变量赋初值
condition: 关系表达式或逻辑表达式,循环控制条件
post: 一般为赋值表达式,给控制变量增量或减量
循环控制语句也是continue和berak:
break:经常用于中断当前 for 循环或跳出 switch 语句
continue:跳过当前循环的剩余语句,然后继续进行下一轮循环。
针对数组和map、slice的range在第二章学习内建容器时再讲
函数
go语言的函数定义格式如下:
func function_name( [parameter list] ) [return_types] {
函数体
}
func:函数由 func 开始声明
function_name:函数名称,函数名和参数列表一起构成了函数签名。
parameter list:参数列表,参数就像一个占位符,当函数被调用时,你可以将值传递给参数,这个值被称为实际参数。参数列表指定的是参数类型、顺序、及参数个数。参数是可选的,也就是说函数也可以不包含参数。
return_types:返回类型,函数返回一列值。return_types是该列值的数据类型。有些功能不需要返回值,这种情况下 return_types 不是必须的。
函数体:函数定义的代码集合。
指针
一个指针变量指向了一个值的内存地址。
类似于变量和常量,在使用指针前需要声明指针。指针声明格式如下:
var var_name *var-type
var-type 为指针类型,var_name 为指针变量名,* 号用于指定变量是作为一个指针。
以下是有效的指针声明,表示一个指向 int 和 float32 的指针。
var ip *int /* 指向整型*/
var fp *float32 /* 指向浮点型 */
指针的使用
在指针类型前面加上 * 号(前缀)来获取指针所指向的内容。
取地址符是 &,放到一个变量前使用就会返回相应变量的内存地址。
package main
import "fmt"
func main() {
var a int= 20 /* 声明实际变量 */
var ip *int /* 声明指针变量 */
ip = &a /* 指针变量的存储地址 */
fmt.Printf("a 变量的地址是: %x\n", &a )
/* 指针变量的存储地址 */
fmt.Printf("ip 变量储存的指针地址: %x\n", ip )
/* 使用指针访问值 */
fmt.Printf("*ip 变量的值: %d\n", *ip )
}
输出结果如下:
a 变量的地址是: 20818a220
ip 变量储存的指针地址: 20818a220
*ip 变量的值: 20
参数传递
参数传递分为值传递和引用传递
看一下下面的c++代码
void pass_by_val(int a) {
a++;
}
void pass_by_ref(int& a) {
a++;
}
int main() {
int a = 3;
pass_by_val(a);
printf("After pass_by_val: %d\n", a);
pass_by_ref(a);
printf("After pass_by_ref: %d\n", a);
}
答案输出是: 3, 4
pass_by_value就是值传递,相当于把a的值拷贝一份传入该函数中去,并没有对main里的a进行操作;
pass_by_ref则是传入了a的引用,是实际对main里的a进行了操作,因此a变为了4。
Go语言呢?在Go中,只有值传递一种方式。
以交换两个变量的值为例,要实现pass_by_ref的效果,如下:
func swap(a, b *int) {
*a, *b = *b, *a
}
func main() {
a, b := 1, 2
swap(&a, &b)
}
但这样操作其实并不简单,特别是对于我这种本身是前端开发,热衷使用JavaScript的。因此更喜欢这样实现:
func swap(a, b int) (int, int) {
return b, a
}
func main() {
a, b := 1, 2
a, b = swap(a, b)
}
方法里的指针使用
package main
import "fmt"
func main(){
person := Person{"TigerwolfC", 25}
fmt.Printf("person<%s:%d>\n", person.name, person.age)
person.sayHi()
person.ModifyAge(28)
person.sayHi()
}
type Person struct {
name string
age int
}
func (p *Person) sayHi() {
fmt.Printf("SayHi -- This is %s, my age is %d\n",p.name, p.age)
}
func (p *Person) ModifyAge(age int) {
fmt.Printf("ModifyAge")
p.age = age
}