Fork me on GitHub

Go-基础

为什么要学习 Go,Go 的适用场景,解决了什么为题?

  • 并行支持,适合高性能分布式服务

  • 云计算

GO的特别之处

  • 允许返回多个值

  • Go中的数据类型都是放到最后的(参数声明和定义等)

  • 接口是非侵入的,只要实现了接口的所有方法就被认作实现了接口,不用显示的声明

  • 代码规范强制统一

  • 错误处理

  • 并发编程

困惑

站在GO的角度多想想GO为什么这样做而不是一直与Java做对比

环境准备

GOROOT

Go的安装目录

GOROOT/bin配置到环境变量PATH中,方便我们在全局中使用Go

GOPATH

指定我们的开发工作区(workspace),是存放源代码、测试文件、库静态文件、可执行文件的工作。

当我们使用go get命令去获取远程库的时候,安装到工作区

构建工程时,用来查找源码的位置

GOBIN

开发程序编译后二进制命令的安装目录

使用go install命令编译和打包应用程序时,该命令会将编译后二进制程序打包GOBIN目录

HelloWord

package main

import "fmt"

func main() {
	fmt.Printf("Hello World")
}
  • 程序从main包的main函数开始运行
  • 开头必须指定所属的package,一般是文件夹的名字,但也可以是其他的名字
  • main函数没有参数和返回值,参数可以通过命令行传入,命令行传入的参数在os.Args变量中保存

导入包

import "fmt"

导入多个包时可以只写一个import

import (
	"fmt"
  "math"
)

不能导入用不到的包,否则编译报错

注释

支持两种注释:

行注释://

块注释:/* */

变量

变量: 一块存储空间的名字,我们可以通过变量名来使用这块内存。

变量声明

var name type

与 Java 不同,变量名在前,变量类型在后

声明多个变量

  1. 类型相同
var (
	name string
  age int
)

避免写多个 var

  1. 类型不同
var 变量[,变量1] 类型

初始化

声明并初始化

var age int = 18
// 类型可以推导
var age =18
// var 可以用:=替代
age :=18
// 多个变量进行初始化
age, name := 18, "Tom"

变量赋值

Go提供了多重赋值功能

i,j := 1,2
i, j = j, i 

匿名变量

在调用函数时为了获取一个值,却因为该函数返回多个值而不得不定义一堆没用的变量时,其他变量不用声明,用_表示。

常量

常量的定义

通过const关键字定义

const name type = value

常量的类型是非必须的,可以推断出来。

常量可以用len(), cap(), unsafe.Sizeof()函数计算表达式的值.

常量赋值是编译期的行为,所以不能赋运行期才知道的结果。

与 import 和变量声明类似,通过()可以一次声明多个常量

const name1, name2 = value1, value2
const(
	name1 = value1
  name2
)

在常量声明中,如果一个常量没有赋值,则他就跟上一行的赋值相同

字面常量

就是硬编码的常量。Go的字面常量是无类型的,-12可以给int,uint,int32,float等

预定义常量

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

iota

  • iota是一个特殊的常量,可以当成是一个可以被编译器修改的常量

  • iota第一次出现时的值为0, 在const中iota出现一次加一

  • 每出现 const 时 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
) 

常量的使用

包名.常量名

枚举

Go不支持 enum,通常用 const 定义一系列相关的常量,这种定义法在Go语言中通常用于定义枚举值。

const (
    Unknown = 0
    Female = 1
    Male = 2
)

数据类型

内置的基础类型

内置类型:不用 import 直接使用的类型

  • 布尔类型:bool,不接收其他类型的赋值,1不是 true。
  • 整型:int8、byte、int16、int、uint、uintptr等,int,uint 的范围与平台相关。int和int32在Go语言里被认为是两种不同的类型,编译器也不会帮你自动做类型转换,需要强制类型转换(需要注意长度截断和值溢出的问题)。
  • 浮点类型:float32、float64,小数默认推导成 float64。
  • 复数类型:complex64、complex128。
  • 字符串:string。 可以使用反引号来包含双引号
  • 字符类型:byte(uint8)字符串的单个字节值,rune(int32)。出于简化语言的考虑,Go语言的多数API都假设字符串为UTF-8编码。尽管Unicode字符在标准库中有支持,但实际上较少使用。
  • 错误类型:error。

浮点型

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

字符串

字符串初始化后不能被修改(即不能通过字符数组的下标来修改)

len

println(len("你好"))

运行结果为 6。实际 len 返回的不是字符数,而是字符串所占的字节数。UTF-8 编码中,一个中文占三个字节

遍历字符串

遍历字符串遍历的是字符串的字节值

  1. 通过 fori
s := "Hell0 世界"
for i := 0; i < len(s); i++ {
  println(i, s[i])
}
  1. 通过 for range,以 Unicode 字符方式遍历
for index, value := range s {
  println(index, value)
}

区别

  • fori 打印了 11 个字节值
  • for range 打印了 8 个 Unicode 值

复合类型

  • 指针(pointer)
  • 数组(array)
  • 切片(slice)
  • 字典(map):Golang 的 Map 类似 Java 的 HashMap,是无序的
  • 通道(chan)
  • 结构体(struct)
  • 接口(interface)

数组

声明

[32]byte // 长度为32的数组,每个元素为一个字节
[2*N] struct { x, y int32 } // 复杂类型数组
[1000]*float64 // 指针数组
[3][5]int // 二维数组
[2][2][2]float64 // 等同于[2]([2]([2]float64)) 

初始化

// 初始化列表进行初始化
arr := [3]int{1, 2}
// 省略初始化长度
arr := [...]int{1, 2}
// 制定索引值进行初始化
arr := [...]int{1: 1, 2: 1}

遍历数组:

  1. 获得len,for循环通过下标访问
  2. range,range会返回两个值:下表和元素值
for i, v := range array {
 fmt.Println("Array element[", i, "]=", v)
} 

在Go语言中数组是一个值类型, 值类型变量在赋值和作为参数传递是都是复制操作,产生一个副本。

数组切片

  • 数组长度不能改变,切片可以动态扩容
  • 数组是值类型,切片不会重复复制

数组切片包含三个变量

  • 指向原始数组的指针
  • 切片中元素的个数
  • 切片已分配的存储空间

创建切片

  1. 基于数组
// 先定义一个数组
var myArray [10]int = [10]int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
// 基于数组创建一个数组切片
var mySlice []int = myArray[:5] 
  1. 直接创建
// 创建一个元素个数为 5 的切片
mySlice1 := make([]int, 5) 
// 提前指定存储空间大小,减少底层重新分配内存的次数
mySlice2 := make([]int, 5, 10) 
mySlice3 := []int{1, 2, 3, 4, 5} 
  1. 基于切片创建新的切片
oldSlice := []int{1, 2, 3, 4, 5}
newSlice := oldSlice[:3] // 指定范围创建新的切片

指定范围可以超过 len,不能超过 cap

cap与 len

cap 获得切片存储空间个数,len 获得当前存储的元素个数

遍历切片

fori 或者 for range

动态增减

append

// 在 mySlice 后添加三个元素然后生成一个新切片
mySlice = append(mySlice, 1, 2, 3)
mySlice2 := []int{8, 9, 10}
// 给mySlice后面添加另一个数组切片生成一个新切片
mySlice = append(mySlice, mySlice2...)

...表述将元素取出来传进去

delete

没有删除的方法,需要借助append生成一个新的切片

// 要删除下标为i的元素
s = append(s[:i],s[i+1:]...)

copy()

为了不共享同一块内存

直接赋值复制的是地址值,用的是同一个切片

如果加入的两个数组切片不一样大,就会按其中较小的那个数组切片的元素个数进行复制。

copy(target, source)

map

声明

var 变量名 map [key类型] value类型

与Java不同key的类型写到[]中,value写到[]外。因为使用过[key]来获得value的

创建,通过make

make(map [KeyType] ValueType)
// 创建并指定出事存储能力
make(map [KeyType] ValueType, 初始容量)

创建并初始化

map[string]int{
  "Tom":18,
  "Jerry":19,
}

注意最后一个键值对后也需要逗号

赋值

myMap["key"] = value

删除

delete(数组名, key)

检查key是否存在

value, ok := myMap["1234"]
if ok { // 找到了
 // 处理找到的value
}

遍历

通过range遍历切片或map,每次循环会返回下表和值

var pow = []int{1, 2, 4, 8, 16, 32, 64, 128}

func main() {
	for i, v := range pow {
		fmt.Printf("2**%d = %d\n", i, v)
	}
}

可以使用_忽略下标或者值,如果只给一个变量,那这个变量表示下标

类型转换

强制类型转换

a :=1
b :=float64(a)

字符串与数字的转换

流程控制

条件判断

条件语句

if a < 5 {
  return 0
} else {
  return 1
}
  • if后不用括号
  • 无论后面有几行,{}是必须的

if语句可以包含一个前置语句,使用范围是该if语句内(包含else)

if a:=0; a < 5 {
 return 0
} else {
 return 1
} 

选择语句

func example(x int) int {
	switch x {
	case 0:
		fmt.Printf("0")
	case 1:
		fmt.Printf("1")
	case 2:
		fallthrough
	case 3:
		fmt.Printf("3")
	case 4, 5, 6:
		fmt.Printf("4, 5, 6")
	default:
		fmt.Printf("Default")
	}
}
  • switch自动加了break
  • 只有明确添加fallthrough关键字,才会紧接着执行下一个 case
switch {
 case 0 <= Num && Num <= 3:
 fmt.Printf("0-3")
 case 4 <= Num && Num <= 6:
 fmt.Printf("4-6")
 case 7 <= Num && Num <= 9:
 fmt.Printf("7-9")
} 
  • switch后的语句不是必须的,在此种情况下,整个switch结构与多个if...else...的逻辑作用等同,多个if-else时使用switch更简洁

循环

只支持for循环

for init; condition; modify {
    
}

前置语句和后置语句是非必需的

for ; condition;  {
    
}

也可以不指定条件无限循环

for {
    println("hello")
}

跳转

func myfunc() {
 i := 0
 HERE:
 fmt.Println(i)
 i++
 if i < 10 {
 goto HERE
 }
}

跳转到某个标签

range

for搭配range,可以用来遍历字符串,数组,切片,map和信道

  1. 对于字符串,数组和切片:range有两个返回值,第一个返回值是下表,第二个是元素的值,只有一个返回值时表示下表
func main() {
	ints := []int{1, 2, 3}
	for index, value := range ints {
		fmt.Printf("%d, %d\n", index, value)
	}
}
  1. 对于map:第一个是key,第二个是value
m := map[int]string{1: "Tom", 2: "Tony"}
	for key, value := range m {
		fmt.Printf("%d, %s\n", key, value)
	}
  1. 对于channel,就是信道中的值
	go func() {
		for i := 0; i < 10; i++ {
			c <- i
		}
		close(c)
	}()

	for i := range c {
		fmt.Printf("%d\n", i)
	}

函数

定义

由func关键字、函数名、参数列表、返回值列表、函数体和返回语句构成

func Add(a, b int) (ret int, err error) {
	// TODO do something
  return
}
  • 可以有多个返回值,包含错误,成功时error返回nil。这样我们不用在去专门定义一个结构体
  • 就像函数的输入参数一样。 返回值被命名之后,它们的值在函数开始的时候被自动初始化为空。在函数中执行不带任何参数的return语句时,会返回对应的返回值变量的值。
  • 参数列表中类型相同的可以只写最后一个参数的一个类型,返回值相同
  • 返回值只有一个值时,可以省略括号
  • 小写字母开头的函数只在本包内可见,大写字母开头的函数才能被其他包使用
  • 命名返回值:返回值可以被函数当成是一个变量,对返回值进行赋值,最后只写一个return,或者最后return 返回值

不定参数: 如...type格式的类型只能作为函数的参数类型存在,并且必须是最后一个参数。

高阶函数

函数可以用来作为参数或返回值

匿名函数

匿名函数:不带函数名的函数

花括号中直接接参数列表或者用变量加参数列表进行调用。

f := func(x, y int) int {
 return x + y
} 

闭包

闭包:内部函数引用外部函数的变量。

闭包的价值

别的函数都是执行完里面的变量就结束了,闭包中的变量会被内部变量保持

闭包的使用场景

posted @ 2022-06-18 15:15  ptuo  阅读(34)  评论(0编辑  收藏  举报