Go基础语法学习

Go语言基础

Go是一门类似C的编译型语言,但是它的编译速度非常快。这门语言的关键字总共也就二十五个,比英文字母还少一个,这对于我们的学习来说就简单了很多。先让我们看一眼这些关键字都长什么样:

下面列举了 Go 代码中会使用到的 25 个关键字或保留字:

breakdefaultfuncinterfaceselect
casedefergomapstruct
chanelsegotopackageswitch
constfallthroughifrangetype
continueforimportreturnvar

Go程序设计的一些规则

Go之所以会那么简洁,是因为它有一些默认的行为:

  • 大写字母开头的变量是可导出的,也就是其它包可以读取的,是公有变量;小写字母开头的就是不可导出的,是私有变量。
  • 大写字母开头的函数也是一样,相当于class中的带public关键词的公有函数;小写字母开头的就是有private关键词的私有函数。

1. 变量、常量、Go内置类型

1.1 变量

Go语言里面定义变量有多种方式。

var
//定义一个名称为“valName”,类型为"type"的变量
var valName type

定义多个变量

//定义三个类型都是“type”的变量
var vname1, vname2, vname3 type

定义变量并初始化值

//初始化“vName”的变量为“value”值,类型是“type”
var vName type = value

同时初始化多个变量

/*
    定义三个类型都是"type"的变量,并且分别初始化为相应的值
    vname1为v1,vname2为v2,vname3为v3
*/
var vname1, vname2, vname3 type= v1, v2, v3

你是不是觉得上面这样的定义有点繁琐?没关系,因为Go语言的设计者也发现了,有一种写法可以让它变得简单一点。我们可以直接忽略类型声明,那么上面的代码变成这样了:

/*
    定义三个变量,它们分别初始化为相应的值
    vname1为v1,vname2为v2,vname3为v3
    然后Go会根据其相应值的类型来帮你初始化它们
*/
var vname1, vname2, vname3 = v1, v2, v3

你觉得上面的还是有些繁琐?好吧,我也觉得。让我们继续简化:

/*
    定义三个变量,它们分别初始化为相应的值
    vname1为v1,vname2为v2,vname3为v3
    编译器会根据初始化的值自动推导出相应的类型
*/
vname1, vname2, vname3 := v1, v2, v3
:=vartypevar
_2b1
_, b := 1, 2
i
package main

func main() {
    var i int
}

1.2 常量

所谓常量,也就是在程序编译阶段就确定下来的值,而程序在运行时无法改变该值。在Go程序中,常量可定义为数值、布尔值或字符串等类型。

它的语法如下:

const constantName = value
//如果需要,也可以明确指定常量的类型:
const Pi float32 = 3.1415926

下面是一些常量声明的例子:

const PI = 3.1415926
const MaxThread = 10
const Prefix = "so_"

1.3 内置基础类型

Go 语言按类别有以下几种数据类型:

bool

Boolean

//示例代码
var isActive bool  // 全局变量声明
var enabled, disabled = true, false  // 忽略类型的声明
func test() {
    var available bool  // 一般声明
    valid := false      // 简短声明
    available = true    // 赋值操作
}

数值类型

intuintruneint8int16int32int64byteuint8uint16uint32uint64runeint32byteuint8

需要注意的一点是,这些类型的变量之间不允许互相赋值或操作,不然会在编译时引起编译器报错。

如下的代码会产生错误:invalid operation: a + b (mismatched types int8 and int32)

var a int8

var b int32

c:=a + b

另外,尽管int的长度是32 bit, 但int 与 int32并不可以互用。

float32float64floatfloat64
complex128complex64RE + IMiREIMi
var c complex64 = 5+5i
//output: (5+5i)
fmt.Printf("Value is: %v", c)

字符串

UTF-8""string
func test(a,b int) {
   str := "hello world"
   m := "haha"
   result := str + m
   println(result)
   multStr := `hello
         world`
   println(multStr)
}

在Go中字符串是不可变的,例如下面的代码编译时会报错:cannot assign to s[0]

var s string = "hello"
s[0] = 'k'

如果你想要修改一个字符串怎么办呢:

s := "hello"
c := []byte(s)
c[0] = 'c'
s1 := string(c)
println(s1)

Go中可以使用 + 操作符来连接两个字符串:

str := "hello world"
m := "haha"
result := str + m
println(result)

如果要声明一个多行的字符串怎么办?可以通过`` 来声明:

multStr := `hello
         world`go
println(multStr)

括起的字符串为Raw字符串,即字符串在代码中的形式就是打印时的形式,它没有字符转义,换行也将原样输出。例如本例中会输出:

hello
    world

错误类型

errorpackageerrors
func error()  {
   e := errors.New("this is a error demo")
   if e != nil {
      println(e)
   }
}

1.4 iota枚举

enum
package main


const (
	x = iota // x == 0
	y
	z
	w 
)

const (
	h, i, j = iota, iota, iota //h=0,i=0,j=0 iota在同一行值相同
)

const (
	l       = iota //a=0
	m       = "B"
	n       = iota             //2
	o, p, q = iota, iota, iota //3,3,3
	r       = iota             //4
)

func main()  {
	println(x,y,z,w)
	println(h,i,j)
	println(l,m,n,o,p,q,r)
}

结果:
0 1 2 3
0 0 0
0 B 2 3 3 3 4

1.5 array,slice,map

array

array就是数组,定义方式如下:

var arr [n]type
[n]typentype[]
var arr [10]int  // 声明了一个int类型的数组
arr[0] = 1      // 数组下标是从0开始的
arr[1] = 2      // 赋值操作
[3]int[4]intslice
:=
a := [3]int{1, 2, 3} // 声明了一个长度为3的int数组

b := [10]int{1, 2, 3} // 声明了一个长度为10的int数组,其中前三个元素初始化为1、2、3,其它默认为0

c := [...]int{4, 5, 6} // 可以省略长度而采用`...`的方式,Go会自动根据元素个数来计算长度

Go支持嵌套数组,即多维数组。比如下面的代码就声明了一个二维数组:

// 声明了一个二维数组,该数组以两个数组作为元素,其中每个数组中又有4个int类型的元素
doubleArray := [2][4]int{[4]int{1, 2, 3, 4}, [4]int{5, 6, 7, 8}}

// 上面的声明可以简化,直接忽略内部的类型
easyArray := [2][4]int{{1, 2, 3, 4}, {5, 6, 7, 8}}

注意: [2][4] int表示的是一个int型数组,数组内有两个数组,每个数组有四个元素组成。

slice

知道python数组的就知道slice,跟python的实现是一样的。

slice有一些简便的操作

slicear[:n]ar[0:n]slicear[n:]ar[n:len(ar)]slicear[:]ar[0:len(ar)]

下面有一些关于slice的示例:

// 声明一个数组
var array = [10]byte{'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j'}
// 声明两个slice
var aSlice, bSlice []byte

// 演示一些简便操作
aSlice = array[:3] // 等价于aSlice = array[0:3] aSlice包含元素: a,b,c
aSlice = array[5:] // 等价于aSlice = array[5:10] aSlice包含元素: f,g,h,i,j
aSlice = array[:]  // 等价于aSlice = array[0:10] 这样aSlice包含了全部的元素

// 从slice中获取slice
aSlice = array[3:7]  // aSlice包含元素: d,e,f,g,len=4,cap=7
bSlice = aSlice[1:3] // bSlice 包含aSlice[1], aSlice[2] 也就是含有: e,f
bSlice = aSlice[:3]  // bSlice 包含 aSlice[0], aSlice[1], aSlice[2] 也就是含有: d,e,f
bSlice = aSlice[0:5] // 对slice的slice可以在cap范围内扩展,此时bSlice包含:d,e,f,g,h
bSlice = aSlice[:]   // bSlice包含所有aSlice的元素: d,e,f,g
sliceaSlicebSliceaSlicebSlice
slice
lenslicecapsliceappendslicesliceslicecopycopyslicesrcdst
//1.基于数组创建数组切片
var array  = [10]int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
var slice = array[1:7] //array[startIndex:endIndex] 不包含endIndex
//2.直接创建数组切片
slice2 := make([]int, 5, 10)
//3.直接创建并初始化数组切片
slice3 := []int{1, 2, 3, 4, 5, 6}
//4.基于数组切片创建数组切片
slice5 := slice3[:4]
println(slice5)
//5.遍历数组切片
for i, v := range slice3 {
println(i, v)
}
//6.len()和cap()
var len = len(slice2) //数组切片的长度
var cap = cap(slice)  //数组切片的容量
println("len(slice2) =", len)
println("cap(slice) =", cap)
//7.append() 会生成新的数组切片
slice4 := append(slice2, 6, 7, 8)
slice4 = append(slice4, slice3...)
println(slice4)
//8.copy() 如果进行操作的两个数组切片元素个数不一致,将会按照个数较小的数组切片进行复制
copy(slice2, slice3) //将slice3的前五个元素复制给slice2
println(slice2, slice3)

map

map[keyType]valueType
fruit := map[string]int{"apple":5,"orange":7,"pineapple":3}
println(fruit)
var appleCount = fruit["apple"]
println(appleCount)

使用map过程中需要注意的几点:

mapmapindexkeymapslicelenmapmapkeymapnumbers["one"]=11one11map
mapkey:valmapkey

1.6 make,new操作

makemapslicechannelnew
newnew(T)T*TT
new
make(T, args)new(T)slicemapchannelT*Tslicearrayslicenilslicemapchannelmake
make

对于不同的数据类型,零值的意义是完全不一样的。比如,对于bool类型,零值为false;int的零值为0;string的零值是空字符串:

b := new(bool)
println(*b)
i := new(int)
println(*i)
s := new(string)
println(*s)

输出:
false
0

注意:上面最后string的输出是空值。

1.7 零值

关于“零值”,所指并非是空值,而是一种“变量未填充前”的默认值,通常为0。 此处罗列 部分类型 的 “零值”

int     0
int8    0
int32   0
int64   0
uint    0x0
rune    0 //rune的实际类型是 int32
byte    0x0 // byte的实际类型是 uint8
float32 0 //长度为 4 byte
float64 0 //长度为 8 byte
bool    false
string  ""

1.8 一些技巧

1.分组声明

在Go语言中,同时声明多个常量、变量,或者导入多个包时,可采用分组的方式进行声明。

例如下面的代码:

import	"log"
import	"net/http"
import	"strings"

const a = 3
const b = 2
const c = 4

var str = "aa"
var prefix = "abc_"

可以写成如下分组形式:

import (
	"log"
	"net/http"
	"strings"
)

const(
	a = 2,
    b = 2,
    c = 4
)

var(
	str = "aa"
    prefix = "abc_"
)

Go程序设计的一些规则

Go之所以会那么简洁,是因为它有一些默认的行为:

classpublicprivate

2. 流程和函数

Go中流程控制分三大类:条件判断,循环控制和无条件跳转。

2.1 流程

if
if
if x > 80{
    println("better")
} else {
    println("good")
}
if
if countScore := getCountScore(); countScore >= 80 {
    println("better")
} else if countScore >= 60 {
    println("good")
} else {
    println("e......")
}

//这里打印countScore是找不到这个变量的
println(countScore)
goto
gotogoto
a := 4
b := 5
if a > b {
	println(a * b)
} else {
	Ding:
		a = a+12
		b = a + (32/4)
	if a < b {
		goto Ding
	}
}

注意:标签名是大小写敏感的

for

Go里面的for除了基本的循环外,还可以读取slice和map的数据:

sum := 0
for i:=0;i<10;i++ {
	sum += i
}
forrangeslicemap
fruit := map[string]int{"apple":5,"orange":7,"pineapple":3}
for k,v := range fruit{
    println(k,v)
}

小插曲:

"_"的使用

_
fruit := map[string]int{"apple":5,"orange":7,"pineapple":3}
for _,v := range fruit{
    println(v)
}

"_"相当于占位符的作用,这个位置必须要有一个值来接收,但是这个值又没有用,可以用 “ _”来占着位置。

switch

跟别的语言中的switch别无他致:

var sex byte
switch sex {
case 0:
	println("男生")
case 1:
	println("女生")
default:
	println("纳尼。。。。。")
}

2.2 函数

func
func funcName(input1 type1, input2 type2) (output1 type1, output2 type2) {
    //这里是处理逻辑代码
    //返回多个值
    return value1, value2
}

上面的代码我们看出:

funcfuncName,output1output2

来看一个最简单的函数:

package main

func add(a,b int) map[] {
	var resultMap = map[string]int{}
	resultMap["add"] = a + b
	resultMap["multi"] = a * b
	resultMap["subtract"] = a - b
	resultMap["division"] = a / b
	return resultMap
}

func main()  {
	a := 3
	b := 4
	count := add(a, b)
	println(count)
}

多个返回值:

Go中函数可以有多个返回值,这个比java强悍100倍:

package main

func add(a,b int) (int,int) {
	return a+b,a-b
}

func main()  {
	a := 3
	b := 4
	add,sub := add(a, b)
	println(add,sub)
}

可变参数:

跟Java中差不多吧:

func myFunc(arg ...int) {}

defer

defer是golang的一个特色功能,被称为“延迟调用函数”。当外部函数返回后执行defer。类似于其他语言的 try… catch … finally… 中的finally,当然差别还是明显的。

释放占用资源:

func test() error {

	file, err := os.Open("path")
	if err != nil {
		return err
	}

	//放在判断err状态之后
	defer file.Close()

	//todo
	//...

	return nil
	//defer执行时机
}

异常处理:

func test2() {
	defer func() {
		if err := recover(); err != nil {
			println(err)
		}
	}()

	file, err := os.Open("path")
	if err != nil {
		panic(err)
	}

	defer file.Close()

	//todo
	//...

	return
	//defer执行时机
}

日志输出:

func test3() {

	t1 := time.Now()

	defer func() {
		println("耗时: %f s", time.Now().Sub(t1).Seconds())
	}()

	//todo
	//...

	return
	//defer执行时机
}

3. struct类型

Java中我们会去声明一些bean对象,里面包含字段和属性,在Go中可以声明一个struct类型的实体,在这个实体中声明一些属性,但是不可以在里面定义func。

type person struct {
    name string
    age int
}

使用方式:

package main

type person struct {
	name string
	age int
}


func main()  {
	var p person
	p.age = 13
	p.name = "xiaoming"
	println(p.name,p.age)
	
	p1 := person{"xiaoming",13}
	println(p1.name,p1.age)
	
	p2 := person{age:13,name:"xiaoming"}
	println(p2.name,p2.age)
	
}

Go中的继承—匿名字段

上面我们在定义struct的时候,里面的属性都是字段名和类型一一对应的。实际上Go也支持只提供类型而不写字段名的方式,也就是匿名字段。

当匿名字段是一个struct的时候,那么这个struct所拥有的全部字段都被隐式地引入了当前定义的这个struct。

package main

type PersonOther struct {
	phone string
	address string
}

type Person struct {
	PersonOther
	name string
	age int
}


func main()  {
	p1 := Person{PersonOther{"13242342123","xxxxxxx"},"xiaoming",13}
	println(p1.address,p1.phone,p1.name,p1.age)


}

可以看到在p1中是可以看到address和phone属性的,这就跟Java中的继承一样。

既然说到继承,那么肯定会有这样的一种情况,就是在Person和PersonOther中都定义过了phone属性,当我们用p1去获取的时候到底获取的是哪个对象的phone属性呢?

Person.phone

当前如果你想访问父类中的phone也不是不可以,Go还保留着父类中的对象呢,你可以这样取出来:

parentPhone := p1.PersonOther.phone

在Java中如果是这样的话,父类的同名字段就被子类覆写了,取不出来。

4. 也谈谈面向对象编程----多态

在Java中我们经常这样做:

定义一个关于计算面积的接口;

定义一个计算圆面积的类实现接口;

定义一个正方形计算面积的类实现接口;

定义一个计算长方形面积的类实现接口;

这样我们就抽象出来一套统一的计算面积的方案由一个接口把持,需要哪个面积计算就调用相关的实现。

methodmethodfunc

method的语法如下:

func (r ReceiverType) funcName(parameters) (results)

看一个具体的示例:

package main

type Circle struct {
	redius float64
}

type Square struct {
	width,height float64
}

func (c Circle) area() float64  {
	return c.redius * c.redius * 3.14
}

func (s Square) area() float64  {
	return s.height * s.width
}


func main()  {
	c := Circle{3.55}
	s := Square{3,5}
	println(c.area())
	println(s.area())
}

可以看到调用method通过Circle示例访问就像访问struct里面的字段一样。