Go 语言学习的入门部分,如果有C语言的基础类比学习会非常迅速。总结来说 Go语言 和 C语言很相似,语法更为简单,所以写起来会相对迅速很多。以下是基础语法的笔记。
>教程来自 http://www.yiibai.com/go/go_start.html
感谢博主的精心编写。
导入包
import "fmt"
import "math"
或者下面:
import (
"fmt"
"math"
)
导出名字
go 中,首字母大写名称是被导出的。全小写的名字是不会被导出。
fmt.Println(math.Pi) //这里PI 改为 pi 就是不可以的。
函数
func add(x int ,y int) int {
return x + y
}
或者
func add(x,y int) int {
return x + y
}
函数中命名返回值
func split (sum int )(x,y int){
x = sum * 4 /9 ;
y = sum - x ;
return ;
}
变量
变量定义看起来颇为随意,很方便。
var c, python , java = true , false , "no!"
var i , j int = 1 , 2
func main(){
fmt.Println(i,j,c,python,java)
}
变量在函数内还可以去掉var等关键字,直接退化为如下操作:
func main(){
var i , j int = 1 , 2
k := 3
c , python , java = true , false , "no!"
fmt.Println(i,j,k,c,python,java)
}
基本数据类型
bool
string
int int8 int16 int32 int64
uint uint8 uint16 uint32 uint64 uintptr
byte //uint8 别名
rune //int32 的别名
//代表一个unicode码
float32 float64
complex64 complex128
int,uint,uintptr在32位系统上是32位,而在64位系统上是64位。一般直接使用int就可以了。一般仅有特定理由才用定长整型或者无符号整型。
var (
ToBe bool = false
MaxInt uint64 = 1<<64 - 1
z complex128 = cmplx.Sqrt(-5 + 12i)
)
func main(){
const f = "%T(%v)\n"
fmt.Printf(f,z,z)
fmt.Printf(f,MaxInt,MaxInt)
fmt.Printf(f,ToBe,ToBe)
}
零值
变量在定义时候没有明确的初始化时会赋值为零值.
数值类型为 **0**
布尔类型为 **false**
字符串为 "" **空字符串**
package main
import "fmt"
func main(){
var i int
var f float64
var b bool
var s string
fmt.Printf("%v %v %v %q\n",i,f,b,s)
}
类型转换
表达式T(v)将值v转换为类型T。
一些关于数值的转换
var i int = 42
var f float64 = float64(i)
var u uint = uint(f)
//或者更为简单的形式
i := 42
f := float64(i)
u := uint(f)
类型推导
在定义一个变量却并不显式指定其类型的时候(使用 := 语法或者 var =表达式语法),变量的类型由(等号)右侧的值推导得出.
当右值定义了类型时候,新变量的类型与其相同:
var i int
j := i // j也是一个int
当时当右边包含了未指明类型的数字常量时候,新的变量就可能是int、float64或complex128 。 这取决于常量的精度:
i := 42 //int
f := 3.142 //float64
g := 0.867 + 0.5i //complex128
常量
常量的定义与变量类似,只不过使用 const 关键字。
常量可以是字符、字符串、布尔或数字类型的值。
常量不能使用 := 语法定义。
package main
import "fmt"
const Pi = 3.14
func main(){
const World = "世界"
fmt.Println("Hello",World)
fmt.Println("Happy",Pi,"Day")
const Truth = true
fmt.Println("Go rules?",Truth)
}
数值常量
数值常量是高精度的值。
一个未指定类型的常量由上下文来决定其类型。
也尝试一下输出 needInt(Big) 吧。(int 可以存放最大64位的整数,根据平台的不同有时会更少。)
const(
Big = 1 << 100
small = Big >> 99
)
func needInt(x int) int {return x * 10 + 1}
func needFloat(x float64) float64 {
return x * 0.1
}
控制流
for
Go 只有一种循环结构 --- for循环。这点和C比较相似。具体如下:
func main(){
sum : = 0
for i := 0 ; i < 10 ; i++ {
sum += i
}
fmt.Println(sum)
}
其中循环初始化语句和后置语句都是可选的:
func main(){
sum := 1
for ; sum < 1000 ; {
sum += sum
}
fmt.Println(sum)
}
这里和C语言很相似,但是不需要括号,如果有括号反而会报错。
for 也可以变成 C语言的中while
func main(){
sum := 1
for sum < 1000 {
sum += sum
}
fmt.Println(sum)
}
或者变为死循环
func main(){
for { //无退出条件,变成死循环
}
}
if
就像 for循环一样,Go 的 if语句也不要求用 ()括号括起来,但是 {} 大括号表示作用域的内容还是必须要有的。
func sqrt(x float64) string {
if x<0 {
return sqrt(-x) + "i"
}
return fmt.Sprint(math.Sqrt(x))
}
if的便捷语句
跟 for语句一样,if语句可以在条件之前执行一个简单的语句。由这个语句定义的变量的作用域仅在 if 范围之内。
func pow(x,n,lim float64) float64 {
if v := math.Pow(x,n); v < lim {
return v
}
return lim
}
if和else
在 if 的便捷语句定义的变量同样可以在任何对应的else块中使用。(提示:两个 pow 都在main 中调用 fmt.Println 前执行完了)
func pow(x,n,lim float64) float64 {
if v:math.Pow(x,n); v<lim{
return v
} else {
fmt.Printf("%g >= %g\n",v,lim)
}
return lim
}
swith语句
switch除非以 fallthrough 语句结束,否则分支会自动终止。
switch os: rumtime.GOOS ; os{
case "darwin":
fmt.Println("OS X")
case "linux":
fmt.Println("Linux")
default:
//freebsd , openbsd,
//plan9,windows...
fmt.Printf("%s.",os)
}
switch 的执行顺序
switch 的条件从上到下执行,当匹配成功的时候停止。
switch i {
case 0 :
case f():
}
//当i==0时候不会调用 **f()**
today := time.Now().Weekday()
switch time.Saturday {
case today + 0 :
fmt.Println("Today.")
case today + 1:
fmt.Println("Tomorrow")
case today + 2:
fmt.Println("In two days")
default:
fmt.Println("Too far away")
}
没有条件的swtich
没有条件的switch 同 switch true一样。这一构造使得可以用更清晰的形式来编写长的if-then-else 链。
t := time.Now()
switch {
case t.Hour() < 12 :
fmt.Println("Good morning!")
case t.Hour() < 17 :
fmt.Println("Good afternoon!")
default:
fmt.Println("Good evening.")
}
defer 语句
defer 语句会延迟函数的执行,直到上层函数返回。延迟调用的参数会立即生成,但是在上层函数返回前函数都不会调用。
func main(){
defer fmt.Println("world")
fmt.Println("hello")
}
defer 栈
延迟函数调用被压入一个栈中。当函数后进先出的顺序调用被延迟的函数调用。
fmt.Println("counting")
for i:= 0 ; i<10 ; i++{
defer fmt.Println(i)
}
fmt.Println("done")
指针
go 具有指针。指针保存了变量的内存地址。
类型 *T 是执行类型为 T 的指针,其零值是 nil。
var p *int
&符号会生成一个指向其作用对象的指针。
i := 42
p = &i
* 符号表示指针指向的底层的值。
fmt.Println(*p) //通过指针p读取i
*p = 21 //通过指针p设置i
这也就是通常所说的“间接引用”或“非直接引用”。
与C不同,Go 没有指针运算。
i,j := 100 , 666
p := &i
fmt.Println(*p) //通过p读取I的值
*p = 777
fmt.Println(i)
p = &j
*p = *p / 37
fmt.Println(j)
结构体字段
结构体字段使用点号来访问(这点和C很像)
type Vertex struct {
X int
Y int
}
func main(){
v:= Vertex(1,2)
v.X = 4
fmt.Println(v.X)
}
结构指针
结构体字段可以通过结构体指针来访问。
通过指针间接的访问是透明的
type Vertex struct{
X int
Y int
}
func main(){
v := Vertex{1,2}
p := &v
p.X = 1e9
fmt.Println(v)
}
结构体符文
结构体符文表示通过结构体字段的值作为列表来新分配一个结构体。
使用 Name: 语法可以仅列部分字段。(字段名的顺序无关。)
特殊的前缀 & 返回一个指向结构体的指针。
type Vertex struct {
X,Y int
}
var (
v1 = Vertex{1,2} //类型为 Vertex
v2 = Vertex{X:1} //Y:0 被省略
v3 = Vertex{ } //X:0 和 Y:0
p = &Vertex{1,2} //类型为 *Vertex
)
数组 (和C语言的数组概念类似)
类型 [n]T 是一个有 n 个类型为 T 的值的数组。
表达式
var a [10]int
定义变量 a 是一个有十个整数的数组。
数组的长度是其类型的一部分,因此数组不能改变大小。这看起来是一个制约,但是Go提供一个更加便利的方式来使用数组。
切片(slice)
一个 slice 会指向一个序列的值,并且包含了长度信息。
[]T 是一个元素类型为 T 的切片(slice)。
len(s) 返回 slice s 的长度。
func main(){
s := []int{2,3,4,5,11,13}
fmt.Println("s ==",s)
for i:= 0 ; i < len(s) ; i++{
fmt.Printf("s[%d] == %d\n",i,s[i])
}
}
slice的切片
切片slice 可以包含任意的类型,包括另一个 slice
func main(){
//create a tic-tac-toe board
game := [][]string{
[]string{"_","_","_"},
[]string{"_","_","_"},
[]string{"_","_","_"},
}
game[0][0] = "X"
game[2][2] = "O"
game[2][0] = "X"
game[1][0] = "O"
game[0][2] = "X"
printBoard(game)
}
func printBoard(s [][]string){
for i:=0 ; i < len(s); i++{
fmt.Printf("%s\n",strings.Join(s[i]," ")) //TODO 不懂
}
}
对slice切片
slice可以重新切片,创建新的 slice 值指向相同的数组。
表达式:
s[lo:hi]
表示从 lo 到 hi-1 的slice元素,含前端,不包含后端。因此:
s[lo:lo]
是空的,而
s[lo:lo+1]
具备一个元素。
func main(){
s := []int {2,3,5,7,11,13}
fmt.Println("s == ",s)
fmt.Println("s[1:4] ==",s[1:4])
//省略下标从0 开始
fmt.Println("s[:3] == ",s[:3])
//省略上标到 len(s) 结束
fmt.Println("s[4:] == ",s[4:])
}
构造 slice
slice 由函数 make 创建。这会分配一个全是零值的数组并且返回一个 slice 指向这个数组:
a := make([]int,5) // len(a) = 5
为了指定容量,可传递第三个参数到make :
b := make([]int,0,5) //len(b) = 0 , cap(b) = 5
b = b[:cap(b)] //len(b) = 5 , cap(b) = 5
b = b[1:] //len(b) = 4 , cap(b) = 4
以下为示例代码:
func main(){
a := make([]int,5)
printSlice("a",a)
b := make([]int,0,5)
printSlice("b",b)
c := b[:2]
printSlice("c",c)
d := c[2:5]
printSlice("d",d)
}
func printSlice(s string, x []int){
fmt.Printf("%s len=%d cap = %d %v\n",s,len(x),cap(x),x)
}
nil slice
slice 的零值是 nil
一个 nil 的 slice 的长度和容量是 0
var z []int
fmt.Println(z,len(z),cap(z))
if z == nil{
fmt.Println("nil!")
}
向slice添加元素
向 slice 的末尾添加元素是一种常见操作,因此 Go 提供了一个内建函数 append 。内建函数的文档对 append 有详细介绍。
func append(s []T, vs ...T) []T
append 的第一个参数是 s 是一个元素类型为 T 的 slice ,其余类型为 T 的值将会附加到该 slice 的末尾。
append 的结果是一个包含原 **slice** 所有元素加上新添加的元素 **slice**。
如果 s 的底层数组太小,而不能容纳所有值的时候,会分配一个更大的数组。返回 slice 会指向这个新分配的数组。
func main(){
var a []int
printSlice("a",a)
a = append(a,0)
printSlice("a",a)
a = append(a,1)
printSlice("a",a)
a = append(a,2,3,4)
printSlice("a",a)
}
func printSlice(s string, x []int){
fmt.Printf("%s len=%d cap=%d %v\n", s , len(x) , cap(x) , x)
}
范围(range)
for 循环的 range 格式可以对 slice 或者 map 进行迭代循环。
当使用 for 循环遍历一个 slice 时候,每次迭代 range 将返回两个值。第一个是当前的下标(序号),第二个是该下标所对应元素的一个拷贝。
var pow = []int{1,2,4,8,16,32,64,128}
func main(){
for i,v:= range pow{
fmt.Printf("2 ** %d = %d \n",i,v)
}
}
通过赋值给 _ 来忽略序号和值。
如果只需要索引值,去掉 “,value” 的部分就可以了。
func main(){
pow := make([] int,10)
for i := range pow{
pow[i] = 1 << uint(i)
}
for _,value := range pow{
fmt.Printf("%d\n",value)
}
}