2016.09.08
初识Go语言
Go语言的主要特性
- 自动垃圾回收
- 更丰富的内置类型 数组、字符串、map等
- 函数多返回值
- 错误处理
- 匿名函数和闭包
- 类型和接口
- 并发编程
- 反射
- 语言交互性
Hello, World!
package main
import "fmt"
func main() {
fmt.Println("Hello, world!")
}
导入没有用到的包会编译错误
函数:
func 函数名(参数列表)(返回值列表) {
// 函数体
}
e.g.
func Compute(value1 int, value2 float64)(result float64, err error) {
// 函数体
}
注释同C++
左花括号的不能另起一行放置,否则会编译错误
编译程序
例如:
hello.go
为源文件
编译并运行程序
$ go run hello.go
只生成编译结果
$ go build hello.go
运行
$ ./hello
工程管理
计算器例子,目录结构:
<calcproj>
|-<src>
|-<calc>
|-calc.go
|-<simplemath>
|-add.go
|-add_test.go # add.go的单元测试
|-sqrt.go
|-sqrt_test.go
|-<bin>
|-<pkg> # 包被安装在此处
把工程根目录<calcproj>
添加到GOPATH
环境变量中
把生成文件放到<bin>
目录中
$ mkdir bin
$ cd bin
$ go build calc
不需要编写makefile
工具,Go
命令行工具会自动分析包的依赖关系,在编译calc.go
之前根据import
语句先把依赖的simplemath
编译打包好。
执行单元测试
$ test simplemath
问题追踪和调试
打印日志
fmt.Println()
GDB
调试
详情参考GDB的用法
$ gdb calc
顺序编程
变量声明
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 // map key - string value - int
var v8 func(a int) int
语句结尾可以加分好也可以不加
声明多个变量
var (
v1 int
v2 string
)
变量初始化
var
关键字可有可无
var v1 int = 10
var v2 = 10 // 编译器可以自动推导v2的类型
v3 := 10
多重赋值
i, j = j, i // 交换i和j变量的值
多返回值和匿名变量
func GetName() (firstName, lastName, nickName string) {
return "May", "Chan", "Chibi Maruko"
}
如果只想获得nickName
,则
_, _, nickName := GetName()
常量
编译期间就已知且不可改变的值
例:
const Pi float64 = 3.14
const zero = 0.0
const (
size int64 = 1024
eof = -1
)
const u, v float32 = 0, 3
const a, b, c = 3, 4, "foo"
const mask = 1 << 3
预定义常量
true
false
iota
在每一个const关键字出现时被重置为0,然后在下一次const出现之前,每出现一次iota,其代表的数字就会自动加1,用法如下
const (
c0 = iota // c0 == 0
c1 = iota // c1 == 1
c2 = iota // c2 == 2
)
const (
a = 1 << iota // a == 1
b = 1 << iota // b == 2
c = 1 << iota // c == 4
)
const x = iota // x == 0
const y = iota // y == 0
// 如果两个const的赋值语句的表达式是一样的,那么可以省略后一个赋值表达式
const (
c0 = iota // c0 == 0
c1 // c1 == 1
c2 // c2 == 2
)
枚举
const (
Sunday = iota
Monday
Tuesday
numberOfDays // 大写字母开头的常量在包外可见,小写字母开头为包内私有
)
类型
内置的基础类型:
bool
int8 byte(uint8) int16 uint16 int32 uint32
int64 uint64
int uint 字节大小与平台相关
uintptr 指针 32位下4字节,64位下8字节
float32 float64
complex64 complex128 复数类型
string
rune 字符类型
error 错误类型
pointer 指针
array 数组
slice 切片
map 字典
chan 通道
struct 结构
interface 接口
布尔类型
布尔类型不支持自动或强制类型转换
var b bool
b = 1 // 编译错误
b = bool(1) // 编译错误
整型
var value2 int32
value1 := 64 // 自动推导为int类型
value2 = value1 // 编译错误,int int32 被认为是不同类型
value2 = int32(value1) // 编译通过
两种不同类型的整数不能直接比较
各种类型的整数变量可以直接和字面常量进行比较
位运算符除了取反运算符外其他都一样,Go中取反运算符为:
^x
浮点数的比较
import "math"
// p为用户自定义的比较精度,比如0.00001
func IsEqual(f1, f2, p float64) bool {
return math.Fdim(f1, f2) < p
}
复数类型
var value1 complex64
value1 = 3.2 + 12i
value2 := 3.2 + 12.i
value3 := complex(3.2, 12)
// 获得实部和虚部
z = complex(x, y)
real(z)
imag(z)
Go源文件必须用UTF-8
字符串类型
常用操作
x + y "Hello" + "123"
len(s) len("Hello")
s[i] "Hello"[1]
字符串遍历
// 一共会输出13个字节,utf-8中文字符占3个字节
str := "Hello, 世界"
n := len(str)
for i := 0; i < n; i++ {
ch := str[i] // 一个字节
fmt.Println(i, ch)
}
// 以Unicode字符方式遍历,输出9个
str := "Hello, 世界"
for i, ch := range str {
fmt.Println(i, ch) // ch的类型为rune
}
字符类型
byte
一个字节
rune
一个Unicode字符
数组
数组声明方法:
[32]byte // 长度为32的数组,每个元素一个字节
[2*N] struct { x, y int32 } // N估计是一个常量
[1000]*float63 // 指针数组
[3][5]int // 二维数组,3行5列
数组长度
arrLength := len(arr)
range
关键字遍历数组
for i, v := range array {
fmt.Println("Array element[", i, "]=", v)
}
Go语言中数组是值类型,传递参数时会产生一次复制动作。
数组切片
类似std::vector动态数组
数组的的长度在定以后无法修改,数组是值类型。
数组切片坯布数组的不足
数组切片就像一个指向数组的指针,实际上它拥有自己的数据结构
创建切片
var myArray [10]int = [10]int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
var mySlice1 []int = myArray[:5] // 1, 2, 3, 4, 5
var mySlice2 []int = myArray[:] // 所有元素
var mySlice3 []int = myArray[5:] // 6, 7, 8, 9, 10
mySlice4 := make([]int, 5) // 5个元素,初始值0
mySlice5 := make([]int, 5, 10) // 5个元素,初始值0,并预留10个元素的存储空间
mySlice6 := []int{1, 2, 3, 4, 5} // 创建并初始化5个元素
元素遍历
for i := 0; i < len(mySlice); i++ {
fmt.Println(mySlice[i])
}
for i, v := range mySlice {
fmt.Println(i, v)
}
动态增减元素
cap()
函数返回数组切片分配的空间大小
len()
函数返回元素个数
增加元素
// 在尾端增加3个元素
mySlice = append(mySlice, 1, 2, 3)
// 在尾端追加另一个数组切片
// 三个点是必须的,因为参数只接受元素类型
// 三个点...表示将数组切片的元素打散后传入
mySlice = append(mySlice, mySlice2...)
基于数组切片创建数组切片
// 只要范围不超过`cap()`返回的大小就是合法的
oldSlice := []int{1, 2, 3, 4, 5}
newSlice := oldSlice[:3]
内容复制
copy()
将内容从一个数组切片复制到另一个数组切片
// 按较小的那个数组切片的元素个数进行复制
slice1 := []int{1, 2, 3, 4, 5}
slice2 := []int{5, 4, 3}
copy(slice2, slice1) // 复制slice1的前三个元素到slice2中
copy(slice1, slice2) // 复制slice2的前三个元素到slice1中
map
map
为内置类型,不需要引入库
package main
import "fmt"
// PersonInfo是一个包含个人详细信息的类型
type PersonInfo struct {
ID string
Name string
Address string
}
func main() {
var personDB map[string] PersonInfo
personDB = make(map[string] PersonInfo)
// 插入数据
personDB["12345"] = PersonInfo{"12345", "Tom", "Room 203"}
personDB["1"] = PersonInfo{"1", "Jack", "Room 101"}
// 查找
person, ok := personDB["1234"]
if ok {
fmt.Println("person.Name")
} else {
fmt.Println("Did not find person with ID 1234")
}
}
元素删除
delete(myMap, "1234")
流程控制
条件语句
花括号是必须存在的
左花括号必须与if或else处于同一行
if a < 5 {
// ...
} else if a < 10 {
// ...
} else {
// ...
}
选择语句
不使用break
如果继续执行紧跟的下一个case
,使用fallthrough
关键字
switch i {
case 0:
// ...
case 1:
// ...
default:
// ...
}
循环语句
for i := 0; i < 10; ++i {
// ...
}
// 无限循环
for {
// ...
}
// break
for j := 0; j < 5; j++ {
for i := 0; i < 10; i++ {
if i > 5 {
break end
}
fmt.Println(i)
}
}
end:
// ...
跳转语句
func myfunc() {
i := 0
here:
fmt.Println(i)
i++
if i < 10 {
goto here
}
}
函数
函数定义
func MyFunc(a int, a int) (ret1 int, ret2 int) {
return a + b, a - b
}
// 参数类型一样时,可以写为a, b int
// 只有一个返回值时:
func MyFunc(a, b int) int {
return a + b
}
函数、类型、变量,小写字母开头只在本包内可见
面向对象编程
为类型添加方法
// 定义一个新类型Integer
// 它和int没有本质不同,只是它为内置的int类型增加了个新方法Less()
type Interger int
func (a Interger) Less(b Interger) bool {
return a < b
}
func main() {
var a Interger = 1
if a.Less(2) {
fmt.Println(a, "Less 2")
}
}
func (a *Interger) Add(b Interger) {
*a += b
}
func main() {
var a Interger = 1
a.Add(2)
}
var a = [3]int{1, 2, 3}
var b = &a
b[1]++
fmt.Println(a, *b)
// 该程序的运行结果如下:
// [1 3 3] [1 3 3]
type Rect struct {
x, y float64
width, height float64
}
// 定义成员方法Area()来计算矩形的面积:
func (r *Rect) Area() float64 {
return r.width * r.height
}
// 创建并初始化Rect类型的对象实例
rect1 := new(Rect)
rect2 := &Rect{}
rect3 := &Rect{0, 0, 100, 200}
rect4 := &Rect{width: 100, height: 200}
在Go语言中没有构造函数的概念,对象的创建通常交由一个全局的创建函数来完成,以
NewXXX来命名,表示“构造函数”:
func NewRect(x, y, width, height float64) *Rect {
return &Rect{x, y, width, height}
}
匿名组合 - 继承的实现
以下代码定义了一个Base类(实现了Foo()和Bar()两个成员方法),然后定义了一个
Foo类,该类从Base类“继承”并改写了Bar()方法(该方法实现时先调用了基类的Bar()
方法) 。
在“派生类” Foo没有改写“基类” Base的成员方法时,相应的方法就被“继承”,例如在下面的例子中,调用foo.Foo()和调用foo.Base.Foo()效果一致。
type Base struct {
Name string
}
func (base *Base) Foo() { ... }
func (base *Base) Bar() { ... }
type Foo struct {
Base
// ...
}
func (foo *Foo) Bar() {
foo.Base.Bar()
// ...
}
接口
在Go语言中,一个类只需要实现了接口要求的所有函数,我们就说这个类实现了该接口,例如:
type File struct {
// ...
}
func (f *File) Read(buf []byte) (n int, err error)
func (f *File) Write(buf []byte) (n int, err error)
func (f *File) Seek(off int64, whence int) (pos int64, err error)
func (f *File) Close() error
这里我们定义了一个File类,并实现有Read()、 Write()、 Seek()、 Close()等方法。设想我们有如下接口:
type IFile interface {
Read(buf []byte) (n int, err error)
Write(buf []byte) (n int, err error)
Seek(off int64, whence int) (pos int64, err error)
Close() error
}
type IReader interface {
Read(buf []byte) (n int, err error)
}
type IWriter interface {
Write(buf []byte) (n int, err error)
}
type ICloser interface {
Close() error
}
尽管File类并没有从这些接口继承,甚至可以不知道这些接口的存在,但是File类实现了这些接口,可以进行赋值:
var file1 IFile = new(File)
var file2 IReader = new(File)
var file3 IWriter = new(File)
var file4 ICloser = new(File)
接口查询
这个if语句检查file1接口指向的对象实例是否实现了two.IStream接口,如果实现了,则执行特定的代码。
var file1 Writer = ...
if file5, ok := file1.(two.IStream); ok {
// ...
}
在Go语言中,你可以询问接口它指向的对象是否是某个类型,比如:
这个if语句判断file1接口指向的对象实例是否是*File类型,如果是则执行特定代码。
var file1 Writer = ...
if file6, ok := file1.(*File); ok {
// ...
}
类型查询
var v1 interface{} = ...
switch v := v1.(type) {
case int: // 现在v的类型是int
case string: // 现在v的类型是string
// ...
}
Any类型
由于Go语言中任何对象实例都满足空接口interface{},所以interface{}看起来像是可
以指向任何对象的Any类型,如下:
var v1 interface{} = 1 // 将int类型赋值给interface{}
var v2 interface{} = "abc" // 将string类型赋值给interface{}
var v3 interface{} = &v2 // 将*interface{}类型赋值给interface{}
var v4 interface{} = struct{ X int }{1}
var v5 interface{} = &struct{ X int }{1}
由于Go语言初始定位为高并发的服务器端程序,尚未在GUI的支持上花费大量的精力,而当前版本的Go语言标准库中没有提供GUI相关的功能,也没有成熟的第三方界面库,因此不太适合开发GUI程序。
待续
有疑问加站长微信联系(非本文作者)