Simple Example
package main
import (
"fmt"
)
func main() {
fmt.Println("hello world ")
}
简单数据类型
整形类型
类型 | 符号 | 范围 |
---|---|---|
uint | 无符号 | 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 | 64位(-9223372036854775808~9223372036854775807 |
package main
import (
"fmt"
)
func main() {
//二进制表示所有位都是0
var UINT_MIN uint64 = uint64(0)
//最大值取反就是最大值
const UINT_MAX = ^uint64(0)
fmt.Println(UINT_MIN)
fmt.Println(UINT_MAX)
//无符号最大值右移一位就是二分
const INT_MAX = int(^uint(0) >> 1)
const INT_MIN = ^(INT_MAX)
fmt.Println(INT_MIN)
fmt.Println(INT_MAX)
//输出二进制编码
fmt.Printf("%08b %d \n", INT_MIN, INT_MIN)
fmt.Printf("%08b %d \n", INT_MAX, INT_MAX)
}
浮点类型
类型 | 长度 |
---|---|
float32 | math.MaxFloat32 |
float64 | math.MaxFloat64 |
float32进度在6位,float64进度在15位
package main
import (
"fmt"
"math"
)
func main() {
const MAX_FLOAT = math.MaxFloat32
fmt.Println(MAX_FLOAT)
}
字符串
string底层是由byte数组构成,且是不可改变的字符数组,go默认使用的是utf-8格式的unicode字符,字符代表着一个rune类型,rune类型对应的是type rune = int32 ,几乎在所有方面都等同于int32,字符串复变量赋值后就变成了byte数组,英文为一个字节,中文是三个字节。
package main
import (
"fmt"
"log"
"strconv"
)
func main() {
str := "go语言"
//go字符串底层使用byte数组,且是不可改变
fmt.Println(len(str))
fmt.Println(len([]rune(str)))
//使用string 描述字符串,默认编码为UTF-8
for _, val := range str {
fmt.Print(val, " ")
}
//将string转为字节,一个英文字符一个字节,中文3个字节
fmt.Println()
for _, val := range []byte(str) {
fmt.Print(val, " ")
}
fmt.Println()
//string 转 int
ii, err := strconv.Atoi("1")
if err != nil {
log.Fatal("strconv atoi panic", err)
}
fmt.Println(ii)
i_64, err := strconv.ParseInt("111", 10, 64)
fmt.Println(i_64)
//string 转 float
f_32, err := strconv.ParseFloat("1.2", 32)
fmt.Println(f_32)
// int 转 string
str = strconv.Itoa(1)
fmt.Println(str)
/*
float 转 string
f 参数可以是e,E,g,G
-1 代表输出的精度小数点后的位数,如果是<0的值,则返回最少的位数来表示该数,如果是大于0的则返回对应位数的值
64 为float的类型,go中float分为32和64位,因此就需要传入32或者64
*/
str = strconv.FormatFloat(1.23456, 'E', 10, 32)
fmt.Println(str)
}
结果输出
8
4
103 111 35821 35328
103 111 232 175 173 232 168 128
1
111
1.2000000476837158
1
1.2345600128E+00
复数
类型 | 长度 |
---|---|
conplex64 | 32位实数和虚数 |
conplex128 | 64位实数和虚数 |
使用方式是 实部 + 虚部,函数 real© 和 imag© 可以分别获得相应的实数和虚数部分。
package main
import (
"fmt"
)
func main() {
var c1 = 5 + 5i
var c2 = 10 + 10i
fmt.Printf("%v\n", c1)
fmt.Printf("%v\n", c2)
fmt.Printf("%v\n", c1*c2)
fmt.Printf("%f %f\n", real(c1), imag(c1))
cc := complex(20, 20)
fmt.Printf("%v\n", cc)
}
输出
package main
import (
"fmt"
)
func main() {
var c1 = 5 + 5i
var c2 = 10 + 10i
fmt.Printf("%v\n", c1)
fmt.Printf("%v\n", c2)
fmt.Printf("%v\n", c1*c2)
fmt.Printf("%f %f\n", real(c1), imag(c1))
cc := complex(20, 20)
fmt.Printf("%v\n", cc)
}
变量
声明变量
var(
a int
b bool
s string
f float32
)
//显示声明
var a int = 10
//隐示声明
a,b,c := 1,true,"hello world","1,2"
变量声明后会进行赋值,常用类型被复制为0,0.0,“”,自定义类型或者指针为nil
全局变量定义和局部变量定义名称尽量不要重复,全局变量影响的范围不包含子模块里面
func main() {
x := 1
fmt.Println(x) // prints 1
{
fmt.Println(x) // prints 1
x := 2
fmt.Println(x) // prints 2
}
fmt.Println(x) // prints 1 (不是2)
}
使用下划线可以忽略一些值,如循环的时候,忽视err,包导入
package main
import (
_ "fmt"
"log"
"time"
)
func main(){
ii, _ := strconv.Atoi("1")
for _, val := range "hello go"{
fmt.Print(val, " ")
}
}
在golang中,使用的是引用传递,常用的引用类型有指针,slices,maps,channel
package main
import (
"fmt"
)
func Test(arr []int) {
arr[0] = 10
}
func TestPt(arr *[]int) {
(*arr)[0] = 20
}
func main() {
data := []int{1, 2, 3}
Test(data)
fmt.Println(data)
TestPt(&data)
fmt.Println(data)
}
golang中默认会将对象转为指针引用,效果如Test()和TestPt()
空指针nil
nil 标志符用于表示interface、函数、maps、slices和channels的“零值”,在一个 nil 的slice中添加元素是没问题的,但对一个map做同样的事将会生成一个运行时的panic:
package main
func main(){
var m map[string]int
m["test"] = 1 //panic error
}
常量和iota
package main
import (
"fmt"
)
func main() {
const PI = 3.14
var ff float32 = PI
var ff64 float64 = PI
//const 泛化 可以赋值给所有同类型的变量
fmt.Println(PI, " ", ff, " ", ff64)
const (
a = iota
b
c
d = 8 * iota
e
f = 10
g
h
i = 20
j = iota
k
)
//新的常量b声明后,iota 不再向下赋值
fmt.Println(a, b, c, d, e, f, g, h, i, j, k)
//遇到const重置值
const (
aa = iota
bb
cc
)
fmt.Println(aa, bb, cc)
}
常用容器使用
1.数组
数组是一系列数据项组成的一个固定大小的数据列表,其子项类型可以是任何数据类型,其长度是一个常量,并且必须是非负数。不同长度的数组是不同的数据结构,因此在使用数组的时候注意判断数据类型
package main
import (
"fmt"
)
func main() {
//定义常用的数据类型
var arr = [3]int{1, 2, 3}
//给数组大小为5的数组进行复制,在下表1,3分别赋值,其他位置默认为空
var arrstr = [5]string{1: "test", 3: "simple"}
//按顺序赋值,开始时按一定顺序赋值,指定循序的时候,后续的赋值在下标为3的地址
var arrStr = [6]string{"data", 2: "dsdaf", "fasdfsafa"}
//动态大小,和上面的情况相同
var arrCount = [...]string{"1", "2", "3"}
var arrCount1 = [...]int{10: 1}
//定义一个固定大小的数组
var array [10]int
//通过new形式获取数组对象的指针,通过*获取对象数据
var newArray = new([3]float32)
fmt.Println(arr, arrstr, arrStr, arrCount, arrCount1, array, *newArray)
}
值引用和指针引用
对象数组和指针数组,对象数组在赋值的时候是值传递,指针数组是引用传递
package main
import (
"fmt"
)
//参数的数据类型是[3]int
func Test(arr [3]int) {
//值传递
arr[2] = 100
}
//参数的数据类型是*[3]int
func Test1(arr *[3]int) {
//指针传递
arr[2] = 200
}
func main() {
//值传递
var arr = [3]int{1, 2, 3}
var arrCopy = arr
arrCopy[1] = 100
fmt.Println(arr, arrCopy)
Test(arr)
fmt.Println(arr)
//指针传递
var arrPt = new([3]int)
var arrPtCopy = arrPt
arrPtCopy[1] = 100
Test1(arrPt)
fmt.Println(arrPt, arrPtCopy)
}
一个大的数组会造成函数小号大量内存(值传递),经常使用是切片
传递数组的指针
使用数组的切片
多维数组
数组只能在第一维度使用…动态获取数据大小,同时可以使用len和cap来获取数组长度
//定义多维数组,长度为3,类型是[2]int的数组
var multiArr [3][2]int
//长度为2的多维数据
var multiArr1 = [...][2]int{{1, 2}, {2, 3}}
//长度为3的多维数组
var multiArr2 = [...][2][2]int{{{1}, {2}}, {{3}, {4}}}
fmt.Println(multiArr, multiArr1, multiArr2)
fmt.Println(len(multiArr), len(multiArr1), len(multiArr2))
fmt.Println(cap(multiArr), cap(multiArr1), cap(multiArr2))
数组遍历
var arr = [3]int{1, 2, 3}
for i := 0; i < len(arr); i++ {
}
for index, value := range arr {
}
数组类型比较,数组在go中是对象的形式存在的,因此
var arr1 [3]int
var arr2 [3]int
arr1 == arr2 // ----> true
var arr3 [4]int
arr1 == arr3 // ----> 编译错误 类型不同不能进行比较
arr2[1] = 1
arr1 == arr2 // ----> false
2.slice切片
切片是对数组的引用,len(slice)表示当前切片的长度,cap(slice)表示当前引用数组的长度,因此0<=len(slice)<=len(slice)恒成立。
切片是引用,因此在使用的使用不需要耗费其他内存空间,所有比数组效率更高。切片是引用,因此不能用指针去指向切片,引用类型在使用的使用会自动去判断,比如在函数参数传递的时候。定义切片的形式如下:
var identifier []type
//初始化后默认是nil ,长度是0,cap也是0
//切片引用,实际获取到的数据是下标start,end-1的数据
var slice []type = array[0:len(array)]
初始化切片
make函数原型 func make([]T, len, cap),len 是数组的长度并且也是 slice 的初始长度,cap是容量
var x = []int{2, 3, 5, 7, 11}
var xx = make([]int,2,10)
xxx := make([]int, 10, 50)
从数组或者切片中生成一个新的切片,我们可以使用下面的表达式, max-low的结果表示容量
a[low : high : max]
a := [5]int{1, 2, 3, 4, 5}
t := a[1:3:5]
//结果[2 3] 2 4 ,t的容量(capacity)是5-1=4 ,长度是2
切片重组(reslice)
slice := make([]int,1,1)
切片重组的好处可以动态进行数据扩容,当len(slice) == cap(slice)的时候,如果还想继续添加数据,那么就会使得新的cap(slice)的容量*2。
var aaa = make([]int, 1, 1)
fmt.Println(aaa, &aaa[0], len(aaa), cap(aaa))
aaa = append(aaa, 2)
fmt.Println(aaa, &aaa[0], len(aaa), cap(aaa))
//结果,容器的大小翻倍了,并且引用的底层数组变化了
[0] 0xc042068ff8 1 1
[0 2] 0xc042069020 2 2
当你重新划分一个slice时,新的slice将引用原有slice的数组。意思是原先数组数据太大,而引用的数据只是一小部分,这里就会导致一直会引用到底层的大数组,好比一个长度为1的切片引用了一个容器大小10000的底层数组,这里就不会释放资源。
package main
import "fmt"
func get() []byte {
raw := make([]byte, 10000)
fmt.Println(len(raw), cap(raw), &raw[0]) // prints: 10000 10000
return raw[:3] // 10000个字节实际只需要引用3个,其他空间浪费
}
func main() {
data := get()
fmt.Println(len(data), cap(data), &data[0]) // prints: 3 10000
}
从临时的切片中拷贝数据使用copy代替下标返回切片对象
package main
import "fmt"
func get() []byte {
raw := make([]byte, 10000)
fmt.Println(len(raw), cap(raw), &raw[0]) // prints: 10000 10000
res := make([]byte, 3)
copy(res, raw[:3]) // 利用copy 函数复制,raw 可被GC释放
return res
}
func main() {
data := get()
fmt.Println(len(data), cap(data), &data[0]) // prints: 3 3
}
多个切片引用一个相同的底层数据,当切片引用的范围在0-cap(slice)之内的时候可以修改底层数组的数据,当引用的数据长度超过原先的数组长度的时候会导致扩容,这时候就会产生新数组。
package main
import "fmt"
func main() {
s1 := []int{1, 2, 3}
fmt.Println(len(s1), cap(s1), s1)
s2 := s1[1:]
fmt.Println(len(s2), cap(s2), s2)
for i := range s2 {
s2[i] += 20
}
// s2的修改会影响到数组数据,s1输出新数据
fmt.Println(s1)
fmt.Println(s2)
// append 导致了slice 扩容
s2 = append(s2, 4)
for i := range s2 {
s2[i] += 10
}
// s1 s2引用的底层数组发生变化。
fmt.Println(s1, &s1[0])
fmt.Println(s2, &s2[0])
}
程序输出:
3 3 [1 2 3]
2 2 [2 3]
[1 22 23]
[22 23]
[1 22 23] 0xc042066100 //地址发生了变化
[32 33 14] 0xc042066120
3.Map
map在go中也是引用类型,在创建和声明的时候不需要知道长度,定义之后就是nil类型,同类型的map是可以进行比较超值,map的key只能是内建类型,比如int,string,float,因此数组,切片,结构体是无法作为key,value可以是任意类型,包含空接口。map在参数传递的时候,内存花销小,32位机器占4个字节,64位机器上占8位,和slice不同的是,不会去管底层存储了多少数据。
定义map
//var map1 map[keytype]valuetype
var mymap map[string]int
//make(map[keytype]valuetype,cap),cap指的是容器大小,可省略,在已知容器大小的时候必须申明出来
var mymap1 = make(map[string]int)
var mymap2 = make(map[string]int,10)
mymap3 := map[string]int{}
mymap4 := map[string]int{"one":1,"secend":2}
一个slice添加数据没问题,但是为初始化的map添加数据的时候会抛出异常
var mymap map[int]string
mymap[1] = "one" //会抛出运行时panic异常
对容器访问的时候通过类似数组取下标的形式获取数据 value = map【key】
判断key是否存在,使用下面的方式,和python3相似
var testmap = make(map[int]string,10)
testmap[1] = "one"
testmap[2] = "secend"
if _,ok := testmap[1]{
}
删除map中的数据使用Delete(map,key),假设key不存在也不会异常
mymap4 := map[string]int{"one": 1, "secend": 2}
delete(mymap4,"one")
使用range遍历map或者slice通过下标的形式可以修改值,使用range是数据的拷贝,使用这些值不会修改到原始容器,在使用slice的时候推荐使用下标的形式,能忽略value就忽略,减少内存开销
package main
import (
"fmt"
)
func main() {
mymap4 := map[string]int{"one": 1, "secend": 2}
for key, value := range mymap4 {
fmt.Println(key, value)
value = 100
}
fmt.Println(mymap4)
for key, value := range mymap4 {
mymap4[key] = value * 10//使用数据的原始地址
}
fmt.Println(mymap4)
data := []int{1, 2, 3, 4, 5}
for i, _ := range data { //忽略第二个参数降低内存拷贝
var t_val = data[i]
data[i] = data[i] * 10
fmt.Println(t_val)
}
}
更新日期:20190113
本文档持续更新中