Go语言是一门类C语言的编译型语言,这样对于第一语言为C/C++语言的同学们来说,这就是福利。Go语言简洁易懂,本文会概要介绍其基础知识。
一、Hello, World
按照惯例,介绍语法前都会先来一发Hello, World,在此也不能免俗,具体代码如下:
package main
import "fmt"
func main(){
fmt.Printf("Hello, World\n")
}
编译、链接、运行后输出销魂的Hello, World。不会这个的同学,请参考我的前一篇关于Go的拙文《
Golang之环境配置》。
在Go语言中,程序是通过package来组织的。package
标识当前文件所属的包,比如:前述程序第1行package main表明本文件属于man包,包名则表示其是一个可独立运行的包,在编译后会生成可执行文件。通常除了main包外,其它的包最后都会生成*.a文件即包文件,并放置在$GOPATH/pkg/$GOOS_$GOARCH目录下,以如博文《Golang之环境配置》中环境变量设置为例就是在C:\go\pkg\windows_386目录下。
Go语言中的包类似于Python中的module,这个可以参考我的Python系列博文之《Python之模块》,它们的优点在于程序的模块化和可重用性。包名和包所在的文件夹名可以不相同,包名通过package 声明而非文件夹名。
每个可独立运行的Go语言程序,必须包含一个package main,而包main中必须包含一个入口函数main,这与C语言保持一致,而不同的是在Go语言中,main函数既没有参数,也没有返回值。在程序的第3行,用关键字func定义了main函数,如C语言一样main函数体放在{}中。Go语言中关键字共25个,具体如下表:
break
|
default
|
func
|
interface
|
select
|
case
|
defer
|
go
|
map
|
struct
|
chan
|
else
|
goto
|
package
|
switch
|
const
|
fallthrough
|
if
|
range
|
type
|
continue
|
for
|
import
|
return
|
var
|
为了输出Hello, World,应用了函数Printf,其功能同C语言中的printf,该函数来自于fmt包,因此,在第2行中导入了fmt包,即import "fmt"。在调用包中函数时,采用.的方式调用,即fmt.Printf,这一点类似于Python。
Go语言原生支持UTF-8,因此,可以用Printf函数输出任何非ASCII码字符,即任何字符都可以直接输出,甚至可以用UTF-8中的任何字符作为标识符,这点在Web编程中算得上是如鱼得水。
二、语言基础
Go语言之所以简洁,是因为它有一些默认的行为:
(1)大写字母开头的变量是可导出的,即其它包可以读取,是公有变量;小写字母开头的不可导出,是私有变量。
(2)大写字母开头的函数也一样,相当于class中带public关键词的公有函数;小写字母开头的就是private关键词的私有函数。
Go程序在设计时遵循这些原则。
1、变量
Go语言定义变量方式有很多种,具体如下:
(1)使用var关键字。
使用var关键字是Go语言最基本的变量定义方式,Go语言把变量类型放在变量名后面,这点不同于C语言,具体如下:
var variableName type //variableName为变量名,type为变量类型
var vname1, vname2, vname3 type //定义多个变量
var variableName type = value //定义变量并初始化,value为初始化值
var vname1, vname2, vname3 type= v1, v2, v3 //定义多个变量并初始化
var vname1, vname2, vname3 = v1, v2, v3 //简化定义多个变量并初始化
一般脚本语言都可以根据变量值来推导出对应的类型,因此,可以更加简化如下:
vname1, vname2, vname3 := v1, v2, v3 //根据值推导类型即简单声明
简单声明只能用在函数内部,即只能用来定义局部变量;使用var才能在函数外部定义变量,即定义全局变量。
_即下划线是一个特殊变量,任何赋予它的值都会被丢弃,就像黑洞一样,比如:
_, b := 34, 35 //将值35赋给了b,同时丢弃34
在Go语言中,已声明的变量必须使用,否则编译时会报错。
2、常量
在Go语言中,常量可定义为数值、布尔值或字符串等类型,具体语法如下:
const constantName = value
const Pi float32 = 3.1415926 //也可以明确指定常量类型,类似于C++中的const
3、内置基础类型
(1)Boolean
在Go语言中,布尔值得类型为bool,值为true或false,默认为false。
var isActive bool
var enabled, disabled = true, false
(2)数值类型
(A)整数
整数类型有无符号和有符号两种,分别是对应int和uint,这两种类型的精度与编译器相关。Go语言也内置了位数固定的类型:rune, int8, in16, int32, int64和byte, uint8, uint16, uint32, uint64,其中,rune是int32的别称,byte是uint8的别称,有了这些后跨平台时就不需要单独在定义一面了,非常人性化。需要注意的是,这些类型的变量之间不允许互相赋值,否则编译时会报错,即int和int32、int8和int32之间都不可互用。
(B)浮点数
浮点数类型有float32和float64两种,但没有float类型,默认是float64。
(C)复数
复数形式为RE+IMi,其中,RE为实数部分,IM为虚数部分,i是虚数单位,默认类型是complex128,即64位实数+64位虚数。也有complex64,即32位实数+32位虚数。
var c complex64 = 5+5i
fmt.Printf("Value is: %v", c) //输出(5+5i)
(3)字符串
在Go语言中,字符串都采用UTF-8字符集编码,用双引号""或者反引号``括起来定义,其类型为string。
var emptyString string = ""
在Go语言中字符串是不可变的,否则编译时会报错:
var s string = "hello"
s[0] = 'c'
如果需要这么做,需要将字符串s转换为[]byte类型后,在按照数组赋值,然后转换回string:
s := "hello"
c := []byte(s)
c[0] = 'c'
s2 := string(c)
在Go语言中,可以使用+操作符连接两个字符串:
s := "hello "
m := "world"
a := s + m
4、一些技巧
(1)分组声明
在Go语言中,可以采用分组的方式同时声明多个常量、变量或者导入多个包:
import "fmt"
import "os"
const i = 100
const pi = 3.1415
var i int
var ppi float32
可以分组写出如下形式:
import(
"fmt"
"os"
)
const(
i = 100
pi = 3.1415
)
var(
i int
pi float32
)
除非被显示设置初值或者iota,则每个const分组的第一常量被默认设置为0值,第二及后续常量与第一个常量值相同,如果前面那个常量的值是itoa,则它也被设置为iota。
(2)iota枚举
Go语言里关键字iota在声明enum时使用,默认起始值是0,每调用一次加1,但每遇到一个const关键字,则iota会重置为0:
const(
x = iota //x==0
y = iota //y==1
z //z==2
)
const v = iota //v==0
(3)array
array即数组,其定义方式如下:
var arr [n]type //n表示数组长度,type表示数组元素类型
数组操作方式同其它语言,通过[]读取或者赋值数组元素,数组下标从0开始,数组长度不可变:
var arr [10]int //声明长度为10,元素类型为int的数组
arr[0] = 20 //数组元素赋值,下标从0开始
a := [3]int{1, 2, 3} //声明长度为3的int数组,元素依次初始化为1,2,3
b := [10]int{1, 2, 3} //声明长度为10的int数组,前3个元素依次初始化为1,2,3,其它默认为0
c := [...]int{4, 5, 6} //可以用...省略长度,Go语言会自动根据元素个数计算长度
Go语言支持嵌套数组即多维数组,以二维数组为例:
doubleArray := [2][4]int{[4]int{1, 2, 3, 4}, [4]int{5, 6, 7, 8}} //声明一个二维数组,每个以2个数组为元素,每个数组又有4个int型元素
easyArray := [2][4]int{{1, 2, 3, 4}, {5, 6, 7, 8}} //如果内部元素和外部一样,可以直接忽略内部元素类型
数组之间的赋值是值得赋值,即当把一个数组作为参数传入函数的时候,传入的是该数组的副本,而不是指针,如果要使用指针,则需要用到slice类型。
(4)slice
有时候我们并不知道数组多大合适,这就需要“动态数组”了,在Go语言中这个任务由slice来完成,但是slice并不是真正意义上的动态数组,而是一个引用类型,slice总是指向一个底层array,sliced的声明方式与array类似,只是不需要长度:
var fslice []int //声明一个slice与数组类似,但不需要指定长度
slice := []byte{'a', 'b', 'c', 'd'} //声明并初始化slice元素
slice还可以从一个数组或slice中再次声明,slice通过array[i:j]来获取,其中i是数组的开始位置,j是结束位置,但不包含array[j],其长度为j-i:
var ar = [10]byte{'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h'} // 声明一个含有8个byte元素的数组
var a, b []byte //声明2个含所有byte的slice
a = ar[2:5] //a指向数组的第3个元素开始,并到第5个元素结束,即a含有ar[2]、ar[3]和ar[4]
b = ar[3:5] //b含有ar[3]和ar[4]
slice有一些简便操作:
(A)slice的默认开始位置为0,ar[:n]等价于ar[0:n]。
(B)slice的第2个序列默认是数组的长度,ar[n:]等价于ar[n:len(ar)]。
(C)如果从一个数组里直接获取slice,可以这样ar[:],等价于ar[0:len(ar)],因为默认第一个序列是0,第二个是数组的长度。
slice是引用类型,所以当被引用元素的值改变时,其它的所有引用都会改变该值。
从概念上来说slice像一个结构体,这个结构体包含三元素:
(A)一个指针,指向数组中slice指定的开始位置。
(B)长度,即slice的长度。
(C)最大长度,也就是slice开始位置到数组的最后位置的长度。
slice有几个有用的内置函数:
(A)len获取slice的长度。
(B)cap获取slice的最大容量。
(C)append向slice里面追加一个或者多个元素,然后返回一个和slice一样类型的slice。
(D)copy函数copy从源slice的src中复制元素到目标dst,并且返回复制的元素的个数。
需要注意的是:append函数会改变slice所引用的数组的内容,从而影响到引用同一数组的其它slice。但当slice中没有剩余空间时即cap==len,此时将动态分配新的数组空间。返回的slice数组指针指向这个空间,而原数组的内容将保持不变,其它引用此数组的slice则不受影响。
(5)map
map类似于Python中的字典,其格式为map[keyType]valueType。
map的读取和设置与slice类似,都是通过key来操作,不同的是slice的index只能是int类型,而map只要完全定义了==与!=操作的类型都支持。
声明一个key是字符串,值为int的字典,这种方式的声明需要在使用之前用make初始化:
var numbers map[string] int
numbers := make(map[string]int)
numbers["one"] = 1
numbers["ten"] = 10
numbers["three"] = 3
fmt.Println("第三个数字是", numbers["three"]) //打印出:第三个数字是3
使用map时需要注意如下几点:
(A)map是无序的,不能通过index获取,而必须通过key获取。
(B)map的长度是不固定的,即和slice一样,也是一种引用类型。
(C)内置的len函数同样适用于map,即返回map拥有的key的数量。
(D)map的值可以通过key来修改。比如:numbers["one"]=11。
map的初始化也可以通过key:val来完成,并内置有判断是否存在key的方式,通过delete删除map中的元素:
rating := map[string]float32 {"C":5, "Go":4.5, "Python":4.5, "C++":2} //初始化map
csharpRating, ok := rating["C#"] //map返回有两个值,第一个值返回ok,即如果key不存在,则ok为false,否则为true,第二个返回value值
delete(rating, "C") //删除key为C的元素
由于map也是一种引用类型,那么如果两个map同时指向一个底层,则一个改变,另一个随之也会改变。
(6)make/new操作
make用于内建类型的内存分配,包括map、slice和channel。
new用于各种类型的内存分配。Go语言中的new同C++一样,即new(T)分配T类型的存储空间并初始化为零值,同时返回一个指向其地址的指针。
make(T, args)只能创建map、slice和channe并且返回一个非零初始值的T类型而不是指针l。之所以会返回一个非零值,是因为这三个数据类型指向的都是数据结构的引用,因此使用前必须被初始化,make初始化了该数据结构并填充了适当的值。
所谓零值,并非是空值,而是一种变量未填充前的默认值,通常为0,但bool是false,string是“”。
阅读(1101) | 评论(0) | 转发(0) |