一、基础语法与代码风格
源文件后缀名.go,入口是main()函数,main()函数必须在main包。
go语言严格区分大小写,大写公开,小写私有。
每条语句后面不需要分号,编译器会自动加上分号。(你写分号不会报错。)
每一行必须只写一句,i++必须独占一行。
强制首花括号行尾风格,不在行尾编译报错。
引入的包和定义的变量必须使用,如果不用则编译报错。
先写变量名,再写类型名。
函数允许有多个返回值。
函数本身也是一种数据类型,可以作为参数传递。
变量不赋值,默认给零值。
与Java不同,Java的所有变量和方法都写在类的内部,以类为单位组织代码。Golang则是包内写变量、方法,以包为单位组织代码。
如果代码过长,可以换行保持优雅,注意不要在括号处换行。
注释风格和Java一样,//行注释,/**/块注释
二、编译运行
go是跨平台的静态编译型语言,可直接编译为机器码运行,不依赖虚拟机。
go编译器在编译期间可检查出大部分问题。
go具备自动垃圾回收机制,而且加入了逃逸分析算法,提升GC效率。
go是一种强类型语言,不支持类型的自动隐式转换。
编译命令:go build main.go
运行:直接双击或命令行输入main.exe
编译+运行命令:go run main.go
三、变量与常量
1、变量
go定义变量的四种方法:
var a int //若不赋值,则默认值为0
var b int = 10 //声明同时赋值
var c = 15 //省略类型,可以自动推断
d :=20 //这种简写不可用于定义全局变量
2、常量
go常量定义与枚举
常量定义的关键字:const
常量必须在声明的同时赋值。
常量const关键字不能省略,所以也不能用:=。
例如:
const a int = 8//定义整型常量,赋值为8
const b ="abc"//定义常量并赋值,省略类型,推断为字符串
3、枚举
枚举常用builtin包中成员iota(问:为什么builtin包中的iota用const关键字定义,却能一直递增?它到底是常量还是变量?)
const (
a,b = iota+1,iota+2 //iota = 0, a = 0+1, b= 0+2
c,d //iota = 1, c = 1+1, d = 1+2
e,f //iota = 2, e = 2+1, f = 2+2
g,h = iota*2,iota*3 //iota = 3, g = 3*2, h = 3*3
i,k //iota = 4, i = 4*2, k = 4*3
)
四、函数
1、函数的格式:
func(接收体)函数名(参数列表)(返回值列表){
函数体
return 返回值列表
}
2、多返回值
func show1(a int,b int) int{
c := a+b
return c
}
/*多返回值:匿名返回*/
func show2(a int, b int)(int string){
c :=a+b
d :="hello"
return c,d
}
/*多返回值:有形参名称的返回*/
func show3()(c int,d string){
fmt.Println(c)//此时值默认为0
c = 3
d = "hi"
return // 虽然return后面什么也不跟,但c和d是被返回去的
}
3、接收体
接收器是golang写结构体成员方法的常用语法。
函数除了函数名、参数、返回值,还有接收器的概念,接收器只能有一个,接收器的概念类似于面向对象概念中的this或self,传入的接收器对象(一般要加星以便可对其修改),在函数中可以对其进行修改。
go语言方法和接收器
go函数声明语法:函数名前的括号内的东西
五、流程控制
1、if语句
if(表达式){
//todo
}else if(表达式){
//todo
}
注意:1、表达式可以是bool值,但不允许用其他数据类型(不存在“非零即真”的情况)
2、在if语句中可以进行赋值操作,用分号与判断表达式隔开
3、表达式括号可省略。
if a:=3;a>1{
fmt.Println("a=3;a>1")//成功输出a=3;a>1
}
2、switch语句
2.1、switch语句常规用法
与其他语言不同,case中默认带break,也就是说不会自动穿透。
匹配不到则执行default语句块,default不是必须的。
case表达式可以有多个,用逗号隔开。意思是都匹配。
case表达式如果是常量则要求不能重复,常量会骗过编译器。
switch和case的表达式数据类型要一致。
var key byte
fmt.Scanf("%c",&key)
switch key{
case 'a':
fmt.Println("周一")
case 'b':
fmt.Println("周二")
case 'c':
fmt.Println("周三")
case 'd':
fmt.Println("周四")
case 'e':
fmt.Println("周五")
case 'f','g':
fmt.Println("周末")
default:
fmt.Println("输入有误")
}
2.2、switch语句不加表达式,case范围判断
这是go语言的特性,其他语言不可以。
var age int
fmt.Scanf("%d",&age)
switch{
case age<18:
fmt.Println("未成年")
case age>=18&&age<40:
fmt.Println("青壮年")
case age>=40&&age<60:
fmt.Println("中年")
case age>=60:
fmt.Println("老年")
default:
fmt.Println("输入有误")
}
2.3、switch穿透——fallthrough
如果case语句块后面有fallthrough关键字,则会穿透,不判断下一个case,直接执行下一个case的语句块。默认只能穿透一层。
case 10:
fmt.Println("ok1")
fallthrough//如果匹配到10,输出“ok1”,此处fallthrough会穿透case 20
case 20: //如果是穿透来的,不判断,直接执行下面语句块
fmt.Println("ok2")
3、for循环
3.1、多次循环
for i:=0;i<10;i++{
//todo
}
3.2、永远循环
for{
//一般配合break使用,否则就是死循环。有的语言如Java不允许这样。
}
3.3、条件循环、break跳出循环
golang中没有while和do……while……
一般用for和if…break配合实现同样的效果
for{
//todo
if(){
break;
}
}
3.4、break+标签
标签用于标示break跳出的是哪一层循环。
用法:以双重for循环为例,break跳出的是哪个循环,就把标签写到那个for上方,标签后面要有冒号。
func main(){
//label1:
for i := 1; i < 10; i++ {
label2:
for j := 1; j <= i; j++ {
fmt.Print(j," * ",i," = ",i*j," ")
if(i==7&&j==3){
//break label1;
break label2;
}
}
fmt.Println()
}
}
3.5、for-range遍历
需要数组、切片、map等数据结构前置知识。
示例:
func RangeSlice(slice []int) {
for index, value := range slice {
fmt.Println(index,value)
}
}
六、数组与切片
数组array是值类型,长度固定,切片slice是引用类型,有长度length和容量capacity。
1、如何初始化一个数组?
func main() {
var testArray [3]int //数组会初始化为int类型的零值
var numArray = [3]int{1, 2} //使用指定的初始值完成初始化
var cityArray = [3]string{"北京", "上海", "深圳"} //使用指定的初始值完成初始化
fmt.Println(testArray) //[0 0 0]
fmt.Println(numArray) //[1 2 0]
fmt.Println(cityArray) //[北京 上海 深圳]
}
2、如何初始化一个切片?
var a []string //声明一个字符串切片,未赋值,为nil
a = make{[]string,3,5} //开辟内存空间,长度3,容量5,默认元素3个""
var b = []int{1,2,3} //声明一个整型切片并初始化
var c = []bool{false, true} //声明一个布尔切片并初始化
var d = []bool{false, true} //声明一个布尔切片并初始化
e := make([]int, 2, 10) //用make()函数自定义容量长度的切片。
fmt.Println(e) //[0 0]
fmt.Println(len(e)) //2
fmt.Println(cap(e)) //10
3、如何查看切片的长度、容量?
用内置函数len() cap()查看
fmt.Println(len(b),cap(b))
4、数组在函数中是值传递,切片在函数中是引用传递
func main(){
nums := [5]int{1,1,1,1,1}
changeArray(nums)
fmt.Println(nums)//打印结果:[1 1 1 1 1]
slice :=[]int{1,1,1,1,1}
changeSlice(slice)
fmt.Println(slice)//打印结果:[3 1 1 1 1]
}
func changeSlice(slice []int){
slice[0] = 3
}
func changeArray(array [5]int){
array[0] = 3
}
5、切片如何追加元素?
slice :=[]int{1,8,9}
slice = append(slice,5)
fmt.Println(slice)//打印结果:[1 8 9 5]
6、切片在扩容的时候,长度、容量、内存地址是如何变化的?
var numSlice []int
for i := 0; i < 10; i++ {
numSlice = append(numSlice, i)
fmt.Printf("%v len:%d cap:%d ptr:%p\n", numSlice, len(numSlice), cap(numSlice), numSlice)
}
输出:
[0] len:1 cap:1 ptr:0xc0000a8000
[0 1] len:2 cap:2 ptr:0xc0000a8040
[0 1 2] len:3 cap:4 ptr:0xc0000b2020
[0 1 2 3] len:4 cap:4 ptr:0xc0000b2020
[0 1 2 3 4] len:5 cap:8 ptr:0xc0000b6000
[0 1 2 3 4 5] len:6 cap:8 ptr:0xc0000b6000
[0 1 2 3 4 5 6] len:7 cap:8 ptr:0xc0000b6000
[0 1 2 3 4 5 6 7] len:8 cap:8 ptr:0xc0000b6000
[0 1 2 3 4 5 6 7 8] len:9 cap:16 ptr:0xc0000b8000
[0 1 2 3 4 5 6 7 8 9] len:10 cap:16 ptr:0xc0000b8000
结论:会自动扩容,每次扩容是cap翻倍,扩容后内存地址变了。
七、channel
1、如何初始化channel?
var c chan int
c = make(chan int, 9)//容量为9的有缓冲的chan
//c = make(chan int)//无缓冲的chan
2、如何传入和取出数据?
//在一个goroutine中传入数据:
c <- 666
//......
//在另一个goroutine中取出数据:
num := <- c
八、map
map是引用类型,是乱序键值对。类似于Java中的HashMap。
简单运用,示例:
func main(){
var m1 map[string]string//声明map
m1 = make(map[string]string,10)//初始化m1
m1["China"] = "Beijing"//给m1存入值
m1["Japan"] = "Tokyo"
m1["USA"] = "NewYork"
m1["Rusia"] = "Moscow"
fmt.Println(m1["USA"])//通过key从m1中取出值
delete(m1,"Japan")//删除元素
m1["USA"] = "Washington"
fmt.Println(m1," length of m1 : ",len(m1))//打印m1,取m1的元素数量
}