Go语言基础(1)
环境配置
(base) yuanjicai@localhost ~ % tail -4 .zprofile
export GOROOT="/usr/local/go"
export GOPATH="/Users/yuanjicai/go"
export GOBIN=$GOPATH/bin
export PATH=$PATH:$GOBIN
(base) yuanjicai@localhost ~ %
GOROOT 是安装go开发包的路径
工作区GOPATH
1) src/ 存储GoLang源代码程序
2) pkg/
3) bin/ 在执行go install
时,它先编译源代码得到可执行文件,然后将可执行文件移动到GOPATH
的bin目录下。
(base) yuanjicai@localhost ~ % ls $GOPATH
bin pkg src
常用命令
go build [-o DestFileName]
go run
go install
常见的路径组织方式:
src/project_doman_name/userName/projectName/moduleName
src/domain_name/departmentName/projectName/moduleName
跨平台编译
Mac 下编译 Linux 和 Windows平台 64位 可执行程序:
CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build
CGO_ENABLED=0 GOOS=windows GOARCH=amd64 go build
Linux 下编译 Mac 和 Windows 平台64位可执行程序:
CGO_ENABLED=0 GOOS=darwin GOARCH=amd64 go build
CGO_ENABLED=0 GOOS=windows GOARCH=amd64 go build
Windows下编译Mac平台64位可执行程序:
SET CGO_ENABLED=0
SET GOOS=darwin
SET GOARCH=amd64
go build
第一个Go程序
package main
import "fmt"
func main() {
fmt.Println("Hello World")
}
说明:
package 声明包名
如果是main包,编译时会编译成可执行文件;
如果是main包,必须有 func main() {} 函数;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
Go语言中还有37个保留字
Constants: true false iota nil
Types: int int8 int16 int32 int64
uint uint8 uint16 uint32 uint64 uintptr
float32 float64 complex128 complex64
bool byte rune string error
Functions: make len cap new append copy close delete
complex real imag
panic recover
变量
Go语言的变量声明格式为:
var 变量名 变量类型
批量变量声明
var (
a string
b int
c bool
d float32
)
可在声明变量的时候为其指定初始值
var 变量名 类型 = 表达式
在函数内部,可以使用更简略的 := 方式声明并初始化变量
func main() {
n := 10
m := 200 // 此处声明局部变量m
fmt.Println(m, n)
}
匿名变量用一个下划线_表示,
func foo() (int, string) {
return 10, "Q1mi"
}
func main() {
x, _ := foo()
_, y := foo()
fmt.Println("x=", x)
fmt.Println("y=", y)
}
说明:
1) :=不能使用在函数外。
2) _多用于占位,表示忽略值。
局部变量声明后 必须使用,否则编译无法顺利通过。同样import 包之后也必须使用,否则编译无法顺利通过。
常量
常量的声明和变量声明非常类似,只是把var换成了const,常量在定义的时候必须赋值。
const pi = 3.1415
const e = 2.7182
const (
pi = 3.1415
e = 2.7182
)
iota是go语言的常量计数器,只能在常量的表达式中使用
iota在const关键字出现时将被重置为0,const中每新增一行常量声明将使iota计数一次(iota可理解为const语句块中的行索引)。
使用_跳过某些值
const (
n1 = iota //0
n2 //1
_
n4 //3
)
数据类型
Go语言中有丰富的数据类型,除了基本的整型、浮点型、布尔型、字符串外,还有数组、切片、结构体、函数、map、通道(channel)等。Go 语言的基本类型和其他语言大同小异。
整型
整型分为以下两个大类: 按长度分为:int8、int16、int32、int64 对应的无符号整型:uint8、uint16、uint32、uint64
其中,uint8就是我们熟知的byte型,int16对应C语言中的short型,int64对应C语言中的long型。
类型 | 描述 |
---|---|
uint8 | 无符号 8位整型 (0 到 255) |
uint16 | 无符号 16位整型 (0 到 65535) |
uint32 | 无符号 32位整型 (0 到 4294967295) |
uint64 | 无符号 64位整型 (0 到 18446744073709551615) |
int8 | 有符号 8位整型 (-128 到 127) |
int16 | 有符号 16位整型 (-32768 到 32767) |
int32 | 有符号 32位整型 (-2147483648 到 2147483647) |
int64 | 有符号 64位整型 (-9223372036854775808 到 9223372036854775807) |
特殊整型
类型 | 描述 |
---|---|
uint | 32位操作系统上就是uint32,64位操作系统上就是uint64 |
int | 32位操作系统上就是int32,64位操作系统上就是int64 |
uintptr | 无符号整型,用于存放一个指针 |
注意: 在使用int和 uint类型时,不能假定它是32位或64位的整型,而是考虑int和uint可能在不同平台上的差异。
注意事项 获取对象的长度的内建len()函数返回的长度可以根据不同平台的字节长度进行变化。实际使用中,切片或 map 的元素数量等都可以用int来表示。在涉及到二进制传输、读写文件的结构描述时,为了保持文件的结构不会受到不同编译目标平台字节长度的影响,不要使用int和 uint
package main
import "fmt"
import "math"
func main(){
var a int = 10
fmt.Printf("%d \n", a) // 10
fmt.Printf("%b \n", a) // 1010 占位符%b表示二进制
var b int = 077
fmt.Printf("%o \n", b) // 77
var c int = 0xff
fmt.Printf("%x \n", c) // ff
fmt.Printf("%X \n", c) // FF
fmt.Printf("%.2f\n", math.Pi)
}
布尔型
Go语言中以bool类型进行声明布尔型数据,布尔型数据只有true(真)和false(假)两个值。
注意:
- 布尔类型变量的默认值为false。
- Go 语言中不允许将整型强制转换为布尔型.
- 布尔型无法参与数值运算,也无法与其他类型进行转换。
浮点型
Go语言支持两种浮点型数:float32和float64。这两种浮点型数据格式遵循IEEE 754标准: float32 的浮点数的最大范围约为 3.4e38,可以使用常量定义:math.MaxFloat32。 float64 的浮点数的最大范围约为 1.8e308,可以使用一个常量定义:math.MaxFloat64。
浮点数输出时,可以使用fmt包,并配合动词%f,默认使用float64
复数
complex64和complex128
var c1 complex64
c1 = 1 + 2i
var c2 complex128
c2 = 2 + 3i
fmt.Println(c1)
fmt.Println(c2)
复数有实部和虚部,complex64的实部和虚部为32位,complex128的实部和虚部为64位
字符串
Go语言中的字符串以原生数据类型出现,使用字符串就像使用其他原生数据类型(int、bool、float32、float64 等)一样。 Go 语言里的字符串的内部实现使用UTF-8编码。 字符串的值为双引号(")中的内容,可以在Go语言的源码中直接添加非ASCII码字符。Go 语言如果使用单引号,表示字符。
字符串转义符
Go 语言的字符串常见转义符包含回车、换行、单双引号、制表符等,如下表所示。
转义符 | 含义 |
---|---|
\r | 回车符(返回行首) |
\n | 换行符(直接跳到下一行的同列位置) |
\t | 制表符 |
\' | 单引号 |
\" | 双引号 |
\ | 反斜杠 |
多行字符串
Go语言中要定义一个多行字符串时,就必须使用反引号字符
字符串的常用操作
方法 | 介绍 |
---|---|
len(str) | 求长度 |
+或fmt.Sprintf | 拼接字符串 |
strings.Split | 分割 |
strings.contains | 判断是否包含 |
strings.HasPrefix,strings.HasSuffix | 前缀/后缀判断 |
strings.Index(),strings.LastIndex() | 子串出现的位置 |
strings.Join(a[]string, sep string) | join操作 |
例:
var str1 string = "aaaa bbbb ccc"
var str2 string = "111 222 333"
fmt.Print(len(str1), len(str2), "\n")
str3 := fmt.Sprintf("%s %s", str1, str2)
fmt.Println(str3)
fmt.Println(strings.Split(str3, " "))
fmt.Println(strings.Contains(str3, "ccc"))
fmt.Println(strings.HasPrefix(str3,"a"))
fmt.Println(strings.HasSuffix(str3,"4"))
fmt.Println(strings.Index(str3, "c"))
fmt.Println(strings.LastIndex(str3,"c"))
elem1 := strings.Split(str3, " ")
fmt.Println(strings.Join(elem1, "_"))
输出:
14 11
aaaa bbbb ccc 111 222 333
[aaaa bbbb ccc 111 222 333]
true
true
false
11
13
aaaa_bbbb__ccc_111_222_333
字符型
Go 语言的字符有以下两种:
- uint8类型,或者叫 byte 型,代表了ASCII码的一个字符。
- rune类型,代表一个 UTF-8字符。
当需要处理中文、日文或者其他复合字符时,则需要用到rune类型。rune类型实际是一个int32。
Go 使用了特殊的 rune 类型来处理 Unicode,让基于 Unicode 的文本处理更为方便,也可以使用 byte 型进行默认字符串处理,性能和扩展性都有照顾
字符串底层是一个byte数组,所以可以和[]byte类型相互转换。字符串是不能修改的 字符串是由byte字节组成,所以字符串的长度是byte字节的长度。 rune类型用来表示utf8字符,一个rune字符由一个或多个byte组成。
例:
str2 := "白萝卜"
str3 := []rune(str2) //把字符串转换成一个rune类型的列表
str3[0] = '红'
fmt.Println(string(str3))
输出:
红萝卜
类型转换
强制类型转换的基本语法如下:
T(表达式)
其中,T表示要转换的类型。表达式包括变量、复杂算子和函数返回值等.
整型、浮点型可以相互转换
流程控制
分支语句--if
语法如下:
if 表达式1 {
分支1
} else if 表达式2 {
分支2
} else{
分支3
}
例:
func ifDemo() {
if score := 65; score >= 90 {
fmt.Println("A")
} else if score > 75 {
fmt.Println("B")
} else {
fmt.Println("C")
}
}
说明:在 if 表达式之前添加一个执行语句,再根据变量值进行判断
分支语句--switch
使用switch语句可方便地对大量的值进行条件判断
func switchDemo1() {
finger := 3
switch finger {
case 1:
fmt.Println("大拇指")
case 2:
fmt.Println("食指")
case 3:
fmt.Println("中指")
case 4:
fmt.Println("无名指")
case 5:
fmt.Println("小拇指")
default:
fmt.Println("无效的输入!")
}
}
一个分支可以有多个值,多个case值中间使用英文逗号分隔。
func switchDemo2() {
age := 30
switch {
case age < 25:
fmt.Println("好好学习吧")
case age > 25 && age < 35:
fmt.Println("好好工作吧")
case age > 60:
fmt.Println("好好享受吧")
default:
fmt.Println("活着真好")
}
}
fallthrough语法可以执行满足条件的case的下一个case,是为了兼容C语言中的case设计的。
func switchDemo3() {
s := "a"
switch {
case s == "a":
fmt.Println("a")
fallthrough
case s == "b":
fmt.Println("b")
case s == "c":
fmt.Println("c")
default:
fmt.Println("...")
}
}
输出:
a
b
循环语句
for 初始语句;条件表达式;结束语句{
循环体语句
}
例
func forDemo() {
for i := 0; i < 10; i++ {
fmt.Println(i)
}
}
func forDemo2() {
i := 0
for ; i < 10; i++ {
fmt.Println(i)
}
}
func forDemo3() {
i := 0
for i < 10 {
fmt.Println(i)
i++
}
}
循环语句--for range
Go语言中可以使用for range遍历数组、切片、字符串、map 及通道(channel)。 通过for range遍历的返回值有以下规律:
- 数组、切片、字符串返回索引和值。
- map返回键和值。
- 通道(channel)只返回通道内的值
字符串默认返回索引和值
for i, v := range str1 {
fmt.Printf("%d %c \n", i, v)
}
跳转语句--goto
goto语句通过标签进行代码间的无条件跳转。goto语句可以在快速跳出循环、避免重复退出上有一定的帮助。Go语言中使用goto语句能简化一些代码的实现过程。
for i:=1; i<10; i++ {
if i==5 {
goto xx
}
fmt.Println(i)
}
xx:
fmt.Println("over")
循环控制关键字--break、continue
func breakDemo1() {
var breakFlag bool
for i := 0; i < 10; i++ {
for j := 0; j < 10; j++ {
if j == 2 {
// 设置退出标签
breakFlag = true
break
}
fmt.Printf("%v-%v\n", i, j)
}
// 外层for循环判断
if breakFlag {
break
}
}
}
Go语言运算符
算术运算
+ | 相加 |
---|---|
- | 相减 |
* | 相乘 |
/ | 相除 |
% | 求余 |
注意: ++(自增)和--(自减)在Go语言中是单独的语句,并不是运算符。
比较运算
== | 检查两个值是否相等,如果相等返回 True 否则返回 False。 |
---|---|
!= | 检查两个值是否不相等,如果不相等返回 True 否则返回 False。 |
> | 检查左边值是否大于右边值,如果是返回 True 否则返回 False。 |
>= | 检查左边值是否大于等于右边值,如果是返回 True 否则返回 False。 |
< | 检查左边值是否小于右边值,如果是返回 True 否则返回 False。 |
<= | 检查左边值是否小于等于右边值,如果是返回 True 否则返回 False。 |
逻辑运算
&& | 逻辑 AND 运算符。 如果两边的操作数都是 True,则为 True,否则为 False。 |
---|---|
|| | 逻辑 OR 运算符。 如果两边的操作数有一个 True,则为 True,否则为 False。 |
! | 逻辑 NOT 运算符。 如果条件为 True,则为 False,否则为 True。 |
位运算
& | 参与运算的两数各对应的二进位相与。 (两位均为1才为1) |
---|---|
| | 参与运算的两数各对应的二进位相或。 (两位有一个为1就为1) |
^ | 参与运算的两数各对应的二进位相异或,当两对应的二进位相异时,结果为1。 (两位不一样则为1) |
<< | 左移n位就是乘以2的n次方。 “a<<b”是把a的各二进位全部左移b位,高位丢弃,低位补0。 |
>> | 右移n位就是除以2的n次方。 “a>>b”是把a的各二进位全部右移b位。 |
&记忆方式: 很容易理解(真真才为真,相当于&&必须两个条件为真时才为真).
用途:
1) 一般用于位清零操作,和取位值操作 ;
2) 保留指定位;
3) 取一个数中某些指定位
例:0xD2 &= ~(0xf << 4) 高四位清0
a & b == b 说明: a>=b
a & 1 == 0 说明 a 为偶数 ; 反之为奇数
|记忆方式: 很容易理解(假假才为假,相当于||必须两个条件为假时才为假,任何有为真的都是返回真)
用途:
1) 一般用于位段设置值的操作;
2) 按位或运算常用来对一个数据的某些位定值为1
异或: 相同时为0,不同时为1
用途:
1) 使特定位翻转;
2) 与0相“异或”,保留原值;
3) 交换两个值,不用临时变量;
a ^ 0xf 实现低四位翻转 (任何数与全1相异或,都可以实现翻转)
a := 3
b := 4
fmt.Println(a^0, b^0) // 保持原值不变 (任何数与0异或,保持原值不变)
fmt.Println(a^b^a) // 输出4
fmt.Println(a^b^b) // 输出3
a=a^b
b=b^a
a=a^b
fmt.Printf("a=%d \t b=%d\n", a, b) // a=4 ; b=3 不使用临时变量,实现两个变量值的交换
赋值运算
= | 简单的赋值运算符,将一个表达式的值赋给一个左值 |
---|---|
+= | 相加后再赋值 |
-= | 相减后再赋值 |
*= | 相乘后再赋值 |
/= | 相除后再赋值 |
%= | 求余后再赋值 |
<<= | 左移后赋值 |
>>= | 右移后赋值 |
&= | 按位与后赋值 |
|= | 按位或后赋值 |
^= | 按位异或后赋值 |
数组
在Go语言中,数组从声明时就确定,使用时可以修改数组成员,但是数组大小不可变化。
基本语法:
var 数组名 [元素数量]T
例:
var a [3]int
例:
var cityArray = [3]string{"北京", "上海", "深圳"}
var numArray = [...]int{1, 2}
a := [...]int{1: 1, 3: 5}
例:
cities := [...]string{"北京", "上海", "深圳"}
for i, v := range cities{
fmt.Printf("index: %d\t value: %s\n", i, v)
}
for i:=0; i<len(cities); i++ {
fmt.Println(cities[i])
}
例:
cities2 := [2][3]string{
{"北京","上海","深圳"},
{"甘肃","青海","新疆"},
}
for _, v1 := range cities2{
for _, v2 := range v1 {
fmt.Printf("%s \t",v2)
}
fmt.Println()
}
数组是值类型,赋值和传参会复制整个数组。因此改变副本的值,不会改变本身的值
数组支持 “==“、”!=” 操作符,因为内存总是被初始化过的。
[n]*T表示指针数组,*[n]T表示数组指针
切片(Slice)
切片是一个拥有相同类型元素的可变长度的序列。它是基于数组类型做的一层封装。
切片是一个引用类型,它的内部结构包含地址、长度和容量。切片一般用于快速地操作一块数据集合。
声明切片类型的基本语法如下:
var name []T
说明:
1) name:表示变量名
2) T:表示切片中的元素类型
例:
a := [5]int{1, 2, 3, 4, 5}
s := a[1:3] // s := a[low:high] s的值是:[2 3]
t := a[1:3:5]
fmt.Printf("t:%v len(t):%v cap(t):%v\n", t, len(t), cap(t)) //t:[2 3] len(t):2 cap(t):4
var d = []bool{false, true}
fmt.Println(d == nil) //输出false
切片拥有自己的长度和容量,可以通过使用内置的len()函数求长度,使用内置的cap()函数求切片的容量
切片表达式从字符串、数组、指向数组或切片的指针构造子字符串或切片。它有两种变体:一种指定low和high两个索引界限值的简单的形式,另一种是除了low和high索引界限值外还指定容量的完整的形式。
例:
a1 := [5]int{1,2,3,4,5}
s1 := a1[1:3] //[2 3]
s2 := a1[:3] //[1 2 3]
s3 := a1[3:] //[4 5]
fmt.Println(s1, s2, s3)
长度=high-low,容量等于得到的切片的底层数组的容量。对于数组或字符串,如果0 <= low <= high <= len(a)
,则索引合法,否则就会索引越界(out of range)。如果索引在运行时超出范围,就会发生运行时panic
。
完整切片表达式如下:
a[low : high : max]
注: 切片a不能是字符串
使用make()函数构造切片
如果需要动态的创建一个切片,需要使用内置的make()函数,格式如下
make([]T, size, cap)
切片的本质
检查切片是否为空
要检查切片是否为空,请始终使用len(s) == 0来判断,而不应该使用s == nil
比较运算
切片之间是不能比较
var s1 []int //len(s1)=0;cap(s1)=0;s1==nil
s2 := []int{} //len(s2)=0;cap(s2)=0;s2!=nil
s3 := make([]int, 0) //len(s3)=0;cap(s3)=0;s3!=nil
切片的遍历
切片的遍历方式和数组是一致的,支持索引遍历和for range遍历
切片添加元素
slice append可以为切片动态添加元素。 可以一次添加一个元素,可以添加多个元素,也可以添加另一个切片中的元素
s4 := []string{"beijing", "shanghai", "shenzhen"}
s4 = append(s4, "qinghai")
fmt.Println(s4)
fmt.Printf("%d %d \n", len(s4), cap(s4))
s5 := []string{"xi an", "hangzhou"}
s4 = append(s4, s5...)
fmt.Println(s4)
切片的扩容策略
1) 首先判断,如果新申请容量(cap)大于2倍的旧容量(old.cap),最终容量(newcap)就是新申请的容量(cap)。
2) 否则判断,如果旧切片的长度小于1024,则最终容量(newcap)就是旧容量(old.cap)的两倍,即(newcap=doublecap),
3) 否则判断,如果旧切片长度大于等于1024,则最终容量(newcap)从旧容量(old.cap)开始循环增加原来的1/4,即(newcap=old.cap,for {newcap += newcap/4})直到最终容量(newcap)大于等于新申请的容量(cap),即(newcap >= cap)
4) 如果最终容量(cap)计算值溢出,则最终容量(cap)就是新申请容量(cap)。
需要注意的是,切片扩容还会根据切片中元素的类型不同而做不同的处理,比如int和string类型的处理方式就不一样。
切片值的修改
a := []int{1, 2, 3, 4, 5}
b := a
由于切片是引用类型,所以a和b其实都指向了同一块内存地址。修改b的同时a的值也会发生变化。
切片复制
Go语言内建的copy()函数可以迅速地将一个切片的数据复制到另外一个切片空间中, copy()函数的使用格式如下:
copy(destSlice, srcSlice []T)
说明:
1) srcSlice: 数据来源切片
2) destSlice: 目标切片
s6 := make([]string, 7, 7)
copy(s6, s4)
fmt.Println(s6)
删除切片成员
Go语言中并没有删除切片元素的专用方法,可以使用切片本身的特性来删除元素。例, 删除下标为1的成员
s4 = append(s4[:1], s4[2:]...)
fmt.Println(s4)
fmt.Printf("%d", cap(s4))
映射Map
Go语言中提供的映射关系容器为map,其内部使用散列表(hash)实现。
map是一种无序的基于key-value的数据结构,Go语言中的map是引用类型,必须初始化才能使用。
map的定义语法如下:
map [KeyType] ValueType
说明:
1) KeyType:表示键的类型。
2) ValueType:表示键对应的值的类型。
map类型的变量默认初始值为nil,需要使用make()函数来分配内存。语法为:
make(map[KeyType]ValueType, [cap])
其中cap表示map的容量,该参数虽然不是必须的,但是我们应该在初始化map的时候就为其指定一个合适的容量。
例:
scoreMap := make(map[string]int, 8)
scoreMap["张三"] = 90
scoreMap["小明"] = 100
Go语言中有个判断map中键是否存在的特殊写法,格式如下:
value, ok := map[key]
遍历map
Go语言中使用for range遍历map。注意: 遍历map时的元素顺序与添加键值对的顺序无关。
删除map中键值对
使用delete()内建函数从map中删除一组键值对,delete()函数的格式如下:
delete(map, key)
说明:
1) map:表示要删除键值对的map
2) key:表示要删除的键值对的键
指针
&(取地址)和*(根据地址取值)
例:
b := &a
总结: 取地址操作符&和取值操作符是一对互补操作符,&取出地址,根据地址取出地址指向的值。
变量、指针地址、指针变量、取地址、取值的相互关系和特性如下:
1) 对变量进行取地址(&)操作,可以获得这个变量的指针变量。
2) 指针变量的值是指针地址。
3) 对指针变量进行取值(*)操作,可以获得指针变量指向的原变量的值
分配内存
Go语言中new和make是内建的两个函数,主要用来分配内存。
new是一个内置的函数,它的函数签名如下:
func new(Type) *Type
说明:
1) Type表示类型,new函数只接受一个参数,这个参数是一个类型
2) *Type表示类型指针,new函数返回一个指向该类型内存地址的指针。
3) new函数不太常用,使用new函数得到的是一个类型的指针,并且该指针对应的值为该类型的零值
例: var a *int
只是声明了一个指针变量a但是没有初始化,指针作为引用类型需要初始化后才会拥有内存空间,才可以给它赋值。应该按照如下方式使用内置的new函数对a进行初始化之后就可以正常对其赋值了:
var a *int
a = new(int)
*a = 10
make也是用于内存分配的,区别于new,它只用于slice、map以及chan的内存创建,而且它返回的类型就是这三个类型本身,而不是他们的指针类型,因为这三种类型就是引用类型,所以就没有必要返回他们的指针了。
func make(t Type, size ...IntegerType) Type
make函数是无可替代的,在使用slice、map以及channel的时候,都需要使用make进行初始化,然后才可以对它们进行操作。
new与make的区别
- 二者都是用来做内存分配的。
- make只用于slice、map以及channel的初始化,返回的还是这三个引用类型本身;
- 而new用于类型的内存分配,并且内存对应的值为类型零值,返回的是指向类型的指针
本内容仅为个人学习Go的笔记,部分内容属于摘抄整理,若遇喷子敬请口下留情.
有疑问加站长微信联系(非本文作者)