《Go语言编程》 笔记

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程序。

待续

  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值