GO程序设计2——面向过程基础知识

GO语言google开发的新语言。有如下特性:

  自动垃圾回收、更丰富的内置数据类型、函数多返回值、错误处理、匿名函数和闭包、类型和接口、并发编程、反射、多语言混合编程

package main

// 我们需要使用fmt包中的Println()函数

import "fmt"

func main() { 
    fmt.Println("Hello, world!") 
} 

1.1 代码结构

package main:每个GO源代码文件的开头声明该Go代码所属的包,包是Go语言里最基本的分发单位,要生成可执行程序,必须建立一个名字为main的包,并且在该包中包含一个叫main()的函数(该函数是Go可执行程序的执行起点)。

func main():主函数main(),该函数是执行程序的起点,不能带参数,也不能定义返回值。命令传入的参数在os.Args变量中保存。如果需要支持命令行开关,使用flag包。

import 语句:导入该程序所需要的其他包。由于用到了系统函数Println(),所以导入该函数所属的fmt包。

注意:不能把源代码文件中没有用到的包导进去,否则Go编译器会报编译错误

func关键字:

所有的Go函数(包括在对象编程中会提到的类型成员函数)以关键字func开头。一个常规的函数定义如下:

func 函数名(参数列表)(返回值列表){
  //函数体
}

  可以看出Go语言与其他语言定义函数的区别,它把返回值列表也列在里面。

对应示例:

func Compute(value1 int,value2 float64)(result float64,err error){
  //函数体
}

  Go支持多个返回值。上面的函数Compute()返回了两个值,一个叫result,一个是err,并不是所有返回值都必须赋值,如果没有明确赋值的返回值被设置为默认值,会根据基本数据类型对返回值初始化,float64为0.0, error为nil,也就是result是0.0 err为nil

注释:

Go程序的代码注释有两种:块注释和行注释

/*

块注释

*/

//行注释

  关于语句结束符号:Go程序不要求开发者在每个语句后面加上分号表示语句结束,当然加上也可以,但Google提供的IDE开发工具在加上分号;后编译时会自动消掉分号

函数体外的大括号位置:

  在Java程序设计里面有提到过大括号怎么放。Go语言中,在函数的返回值后面紧着函数体的左大括号,不能另起一行,这一点与Java通常用法是一致的,也有人把同一个级别的函数体左括号单独起一行,与右括号放在一列,这样在Go语言中是不行的。

func 函数名(参数列表)(返回值列表)
{
  //函数体
}

  其实可以发现左括号放在返回值列表后面是有一个好的地方是:在很多编辑器里面,比如notepad,它把左括号和右括号之间用线连起来,显得是一个整体,如果把左括号单独起一行,会显得函数定义时候func行不与函数体一起,而把左大括号放在func行,会显得整个函数浑然一体。

若干命令:

查看go版本:

cmd —— go version输出 go version go1.0.3

编译源程序

go build HelloWorld.go

运行编译后代码

HelloWorld.exe

函数的单元测试:

xxx.go对应的单元测试是xxx_test.go,这是go工程里的命名规则

2 顺序编程

2.1 变量

  从根本上说,变量是对一块数据存储空间的命名,程序可以通过定义一个变量来申请一块数据存储空间,之后通过调用(引用)变量名来使用这块存储空间

2.1.1 变量声明

  Go语言的变量声明方式与其他语言相差比较大。引入了一个关键字var(但不是必须的),表示一个变量

var 变量名 变量类型

示例:var name string

  这种方式其实更符合英语习惯 可以把上面的定义方式扩展成英语:the variable name is a string 变量name是一个string,如果去掉动词、量词就可以看出var name string 比较近似我们平时使用的英语语法结构。

类似地:

var v1 int
var v2 string
var v3 [10]int     //定义一个固定长度的数组
var v4 []int     //定义一个可变长度的数组
var v5 struct{
  f int
}
var v6 *int     //指针
var v7 map[string]int    //字典,或者说关联数组与Java的map类似,key为string类型,value为int类型
var v8 func(a int) int

可以使用var关键字声明若干个关键字

var(
  v1 int
  v2 string
)

  从上面多出的var关键字可以看出,有一个var关键字更加符合英语习惯,不仅如此,可以同时声明多个变量,把变量用一个关键字var包起来,形成一个整块,这样可读性更好,不会让变量乱放。

2.1.2 变量赋值:

//定义一个变量

var v1 int

//对一个变量初始化赋值

v1 = 10

上面的两步可以合成,也就是定义加初始化用一个语句完成。

有三种方式

var v1 int = 10 //正确使用方式1

var v2 = 10 //正确使用方式2,编译器可以自动推导出v2的类型

v3 := 10 //正确使用方式3,编译器可以自动推导v3的类型

:=这个运算符包含了定义和初始化两步操作。出现这个运算符的变量不应该是已经被定义过的,否则会编译错误。

  指定变量类型不是必须的,看起来有点像动态类型语言。但实际是静态语言。

2.1.3 多重赋值

  Go语言提供了多重赋值功能

  i,j = j,i

2.1.4匿名变量

  我们在使用传统的强类型语言编程时,经常会出现这种情况,即在调用函数时为了获取一个值,却因为该函数返回多个值而不得不定义一堆没用的变量。在Go中这种情况可以通过结合使

用多重返回和匿名变量来避免这种丑陋的写法,让代码看起来更加优雅。

  假设GetName()函数的定义如下,它返回3个值,分别为firstName、lastName和nickName:

func GetName() (firstName, lastName, nickName string) { 
  return "May", "Chan", "Chibi Maruko" 
} 

  若只想获得nickName,则函数调用语句可以用如下方式编写:

  _, _, nickName := GetName()

  这种用法可以让代码非常清晰,基本上屏蔽掉了可能混淆代码阅读者视线的内容,从而大幅降低沟通的复杂度和代码维护的难度。

2.2 常量

  常量是指编译期间已知且不可改变的值,常量可以是数值类型(包括整型、浮点型和复数类型)、布尔类型、字符串类型

2.2.1 字面常量

  字面常量是指程序中硬编码的常量,如

无类型 2 //由于2在许多数值类型定义域内,并且没有规定2的类型,因此是一个无类型,当然也可以指定2L,是一个long型的

浮点型 3.1415926

复数型 3.2+12i

布尔型 true

字符串 "true"

2.2.2常量定义

通过const关键字,可以指定字面常量名字

const Pi float64 = 3.1432423424234
const zero = 0.0
const(
size int64 = 1024
eof = -1//无类型整型常量
)
const u,v float32 = 0.3 //定义多重赋值
const a,b,c = 3,4,"true" //a = 3,b = 4,c = "true",无类型整型和字符串常量

  常量定义的右值也可以是一个编译期运算的常量表达式

const mask = 1 <<3

  常量的赋值是一个编译期行为,所以右值不能出现任何需要运行期才能得出结果的表达式(否则就不是常量了)

  下面的例子就会导致编译错误,常量就是运行前就已经确定的值。

const Home = os.GetEnv("HOME")

2.2.3 预定义常量

  Go语言预定义了这些常量true、false和iota

  iota比较特殊,可以被认为是一个可被编译器修改的常量,在每一个const关键字出现时被

重置为0,然后在下一个const出现之前,每出现一次iota,其所代表的数字会自动增1。

从以下的例子可以基本理解iota的用法:

const ( // iota被重设为0 
c0 = iota // c0 == 0 
c1 = iota // c1 == 1 
c2 = iota // c2 == 2 
) 

const ( 
a = 1 << iota // a == 1 (iota在每个const开头被重设为0) 
b = 1 << iota // b == 2 
c = 1 << iota // c == 4 
) 

const ( 
u = iota * 42 // u == 0 
v float64 = iota * 42 // v == 42.0 
w = iota * 42 // w == 84 
) 
const x = iota // x == 0 (因为iota又被重设为0了) 
const y = iota // y == 0 (同上) 

  如果两个const的赋值语句的表达式是一样的,那么可以省略后一个赋值表达式。因此,上

面的前两个const语句可简写为:

const ( // iota被重设为0 
c0 = iota // c0 == 0 
c1 // c1 == 1 
c2 // c2 == 2 
) 

const ( 
a = 1 <<iota // a == 1 (iota在每个const开头被重设为0) 
b // b == 2 
c // c == 4 
) 

2.3 枚举

  枚举指一系列相关的常量,比如下面关于一个星期中每天的定义。通过上一节的例子,我们看到可以用在const后跟一对圆括号的方式定义一组常量,这种定义法在Go语言中通常用于定义

枚举值。Go语言并不支持众多其他语言明确支持的enum关键字。

下面是一个常规的枚举表示法,其中定义了一系列整型常量:

const ( 
Sunday = iota 
Monday 
Tuesday 
Wednesday 
Thursday 
Friday 
Saturday 
numberOfDays // 这个常量没有导出 
) 

  同Go语言的其他符号(symbol)一样,以大写字母开头的常量在包外可见。

以上例子中numberOfDays为包内私有,其他符号则可被其他包访问。

2.4 数据类型

Go支持如下数据类型

布尔类型:bool

整型:byte int8 int16 int uint uintptr

浮点类型:float32、float65

复数类型:complex64、complex128

字符串:string

字符类型:rune

错误类型:error

此外Go语言也支持以下复合类型

指针:pointer

数组:array

切片:slice

字典:map

通道:chan

结构体:struct

接口:interface

  在这些基础类型之上Go还封装了下面这几种类型:int、uint和uintptr等。这些类型的特点在于使用方便,但使用者不能对这些类型的长度做任何假设。对于常规的开发来说,用int

和uint就可以了,没必要用int8之类明确指定长度的类型,以免导致移植困难。

2.4.1布尔类型

Go语言中的布尔类型与其他语言基本一致,关键字也为bool,可赋值为预定义的true和false

var v1 bool
v1 = true
//v2也会被推导为bool类型
v2 := (1 == 2)

布尔类型关键字是bool,只有true和false两种取值,不能接受其他类型的赋值,不支持自动或强制的类型转换。以下示例是错误的用法:

var b bool
b = 1
b = bool(1)

以下是正确的

var b bool
b = (1 != 0)

2.3.2 整型

  整型是所有编程语言里最基础的数据类型。下面是Go语言所支持的类型。

1.类型表示

需要注意的是,int 和int32在Go语言里被认为是两种不同的类型,编译器也不会帮你自动做类型转换,比如下面的例子是错误的:

var value2 int32
value1 := 64
value2 = value1 

编译错误类似于:

cannot use value1 (type int) as type int32 in assignment。

使用强制类型转换可以解决这个编译错误:

value2 = int32(value1) // 编译通过

当然,开发者在做强制类型转换时,需要注意数据长度被截短而发生的数据精度损失(比如

将浮点数强制转为整数)和值溢出(值超过转换的目标类型的值范围时)问题。

2. 数值运算

  Go语言支持下面的常规整数运算:+、?、*、/和%。加减乘除就不详细解释了,需要说下的

是,% 和在C语言中一样是求余运算,比如:

  5 % 3 // 结果为:2

3. 比较运算

Go语言支持以下的几种比较运算符:>、<、==、>=、<=和!=。这一点与大多数其他语言相

同,与C语言完全一致。

下面为条件判断语句的例子:

i, j := 1, 2 
if i == j { 
fmt.Println("i and j are equal.") 
} 

  两个不同类型的整型数不能直接比较,比如int8类型的数和int类型的数不能直接比较,但

各种类型的整型变量都可以直接与字面常量(literal)进行比较,比如:

var i int32 
var j int64 

i, j = 1, 2 

if i == j { // 编译错误 
fmt.Println("i and j are equal.") 
}


if i == 1 || j == 2 { // 编译通过 
fmt.Println("i and j are equal.") 
}

4. 位运算

Go语言支持位运算符。

运 算 含 义 样 例

x << y 左移 124 << 2 // 结果为496

x >> y 右移 124 >> 2 // 结果为31

x ^ y 异或 124 ^ 2 // 结果为126

x & y 与 124 & 2 // 结果为0

x | y 或 124 | 2 // 结果为126

^x 取反 ^2 // 结果为?3

Go语言的大多数位运算符与C语言都比较类似,除了取反在C语言中是~x,而在Go语言中

是^x。

2.3.3 浮点型

  浮点型用于表示包含小数点的数据,比如1.234就是一个浮点型数据。Go语言中的浮点类型采用IEEE-754标准的表达方式。

1. 浮点数表示

  Go语言定义了两个类型float32和float64,其中float32等价于C语言的float类型,float64等价于C语言的double类型。 在Go语言里,定义一个浮点数变量的代码如下:

var fvalue1 float32 
fvalue1 = 12 
fvalue2 := 12.0 // 如果不加小数点,fvalue2会被推导为整型而不是浮点型 

  对于以上例子中类型被自动推导的fvalue2,需要注意的是其类型将被自动设为float64,

而不管赋给它的数字是否是用32位长度表示的。因此,对于以上的例子,下面的赋值将导致编译

错误:

fvalue1 = fvalue2

而必须使用这样的强制类型转换:

fvalue1 = float32(fvalue2)

2. 浮点数比较

  因为浮点数不是一种精确的表达方式,所以像整型那样直接用==来判断两个浮点数是否相等是不可行的,这可能会导致不稳定的结果。

下面是一种推荐的替代方案:

import "math" 

// p为用户自定义的比较精度,比如0.00001 
func IsEqual(f1, f2, p float64) bool { 
return math.Fdim(f1, f2) < p 
}

2.3.4 复数类型

  复数由实部和虚步组成。go支持复数类型,关键字是complex64

var value1 complex64 // 由2个float32构成的复数类型 
value1 = 3.2 + 12i 
value2 := 3.2 + 12i // value2是complex128类型 
value3 := complex(3.2, 12) // value3结果同 value2 

2. 实部与虚部

  对于一个复数z = complex(x, y),就可以通过Go语言内置函数real(z)获得该复数的实部,也就是x,通过imag(z)获得该复数的虚部,也就是y。 更多关于复数的函数,请查阅math/cmplx标准库的文档。

2.3.5 字符串

  在Go语言中,字符串也是一种基本类型。相比之下, C/C++语言中并不存在原生的字符串类型,通常使用字符数组来表示,并以字符指针来传递。 Go语言中字符串的声明和初始化非常简单,举例如下:

var str string // 声明一个字符串变量 
str = "Hello world" // 字符串赋值 
ch := str[0] // 取字符串的第一个字符 
fmt.Printf("The length of \"%s\" is %d \n", str, len(str)) 
fmt.Printf("The first character of \"%s\" is %c.\n", str, ch) 

输出结果为:

The length of "Hello world" is 11

The first character of "Hello world" is H.

  字符串的内容可以用类似于数组下标的方式获取,但与数组不同,字符串的内容不能在初始化后被修改,比如以下的例子:

str := "Hello world" // 字符串也支持声明时进行初始化的做法

str[0] = 'X' // 编译错误

  编译器会报类似如下的错误:

cannot assign to str[0]

  在这个例子中我们使用了一个Go语言内置的函数len()来取字符串的长度。这个函数非常有用,我们在实际开发过程中处理字符串、数组和切片时将会经常用到。 本节中我们还顺便示范了Printf()函数的用法。有C语言基础的读者会发现,Printf()函数的用法与C语言运行库中的printf()函数如出一辙。读者在以后学习更多的Go语言特性时,可以配合使用Println()和Printf()来打印各种自己感兴趣的信息,从而让学习过程更加直观、有趣。

  Go编译器支持UTF-8的源代码文件格式。这意味着源代码中的字符串可以包含非ANSI的字符,比如“Hello world. 你好,世界!”可以出现在Go代码中。但需要注意的是,如果你的Go代

码需要包含非ANSI字符,保存源文件时请注意编码格式必须选择UTF-8。特别是在Windows下一般编辑器都默认存为本地编码,比如中国地区可能是GBK编码而不是UTF-8,如果没注意这点在编译和运行时就会出现一些意料之外的情况。

  字符串的编码转换是处理文本文档(比如TXT、XML、HTML等)非常常见的需求,不过可惜的是Go语言仅支持UTF-8和Unicode编码。对于其他编码,Go语言标准库并没有内置的编码转

换支持。不过,所幸的是我们可以很容易基于iconv库用Cgo包装一个。这里有一个开源项目:https://github.com/xushiwei/go-iconv。