Go语言介绍

Go语言是Google内部公司大佬开发的,主要起因于Google公司有大量的C程序项目,但是开发起来效率太低,维护成本高,于是就开发了Go语言来提高效率,而且性能只是差一点。(Go是2007年开始研发,2009推出发布)

Go语言目前的主要应用场景

  1. Go非常适用需要高性能高并发的网络编程,这里的网络编程是指不需要界面,底层只是用Socket相互传输数据的系统,类似于Java中Netty的用途。
  2. 一些云计算容器,比如Docker,K8s,底层就是Go语言开发的,也可以用做底层自研运维项目的开发。
  3. 一些游戏系统的开发,可以用Go语言
  4. 区块链的一些底层软件和一些应用软件。(区块链程序的第一开发语言)

第一个Go程序

package main //demo01

//导包
import "fmt"

/*
  程序入口(多行注释)
 */
func main() {
	//第一个go程序(单行注释)
	fmt.Println("hello GoLand")
}

语法注意事项

  1. 源文件以"go"为扩展名。
  2. 程序的执行入口是main()函数。
  3. 严格区分大小写。
  4. 方法由一条条语句构成,每个语句后不需要分号(Go语言会在每行后自动加分号),这也体现出Golang的简洁性。
  5. Go编译器是一行行进行编译的,因此我们一行就写一条语句,不能把多条语句写在同一个,否则报错
  6. 定义的变量或者import的包如果没有使用到,代码不能编译通过
  7. 大括号都是成对出现的,缺一不可

API文档

Golang中文网在线标准库文档: https://studygolang.com/pkgdoc

变量

变量使用的步骤和声明变量的四种方法

  1. 声明
  2. 赋值
  3. 使用
//1.声明变量a(int)//demo02
var a int
//2.变量的赋值
a = 18
//3.变量的使用
fmt.Println("a = ",a)

//1声明 2、赋值 合为一句代码(定义变量b=20)
var b int = 20
//3.变量的使用
fmt.Println("b = ",b)

//1声明 2、赋值 合为一句代码 类型省略 会自动获取定义变量c=20)
var c  = 22
//3.变量的使用
fmt.Println("c = ",c)

//等同于上面这种写法
d := 25
//变量的使用
fmt.Println("d = ",d)

注意: 不可以在赋值的时候给与不匹配的类型,go编译不会报错,运行时会报错

//定义一个in类型变量、赋值给了一个浮点类型
var i int = 2.5
fmt.Println(i)

上面代码会得到下面的异常

# command-line-arguments
.\demo02.go:26:6: constant 2.5 truncated to integer

全局变量的定义

package main //demo02

import (
	"fmt"
)

//全局变量(第一种写法)
var a1 = 33
var b1 = 44

//全局变量(第二种写法)
var (
	c1 = 29
	d1 = 92
)


func main()  {
	//打印全局变量的值到控制台
	fmt.Printf("a1 = %v b1 = %v c1 = %v d1 = %v",a1,b1,c1,d1)
}

常量定义

定义语法:const 常量名 常量类型 = 常量值

package main

import (
	"fmt"
)

//定义全局常量
const (
	e1 = 22
	f1 = 33
)

func main()  {
	//常量定义
	const e int = 66
	//隐式推断类型
	const f ="常量字符串"
    
	//错误示例:常量不允许更改,编译都过不去
	//e = 77

	//将常量输出到控制台
	fmt.Printf("e = %v f = %v",e,f)
}

数据类型

Go的数据类型分为了两种,基本数据类型和复杂数据类型

基本数据类型

整数型、浮点型、字符类型、布尔类型、字符串类型

整数型

整数型又分为了有符号整数类型和无符号整数类型以及其他整数类型,有符号整数类型能有负数,无符号类型的值只能大于0。

  • 有符号整数类型:int8int16int32int64 分别对应java中的byteshortintlong

  • 无符号整数类型:没有负数

  • 其他整数类型

友情提醒

1.定义好的类型赋值不能越界

// uint为无符号整数类型,不支持负数,这里肯定会报异常 //demo03
var num1 uint8 = -20
fmt.Println("num1 = ",num1)
constant -20 overflows uint8

2.隐式获取整数类型的变量默认为int类型,64位系统占8个字节

//测试隐式获取变量的类型 和所占字节数  //demo03
num := 1
//获取变量占用字节数
size := unsafe.Sizeof(num)
//格式化输出  %t获取变量的类型   %v获取变量的值
fmt.Printf("num类型:%t 占用字节数量:%v",num,size)
num类型:%!t(int=1) 占用字节数量:8

3.这么多整数类型,使用的时候该如何选择呢?在保证程序正确运行下,尽量使用占用空间小的数据类型

4.变量只声明不赋值的情况默认值为0

浮点型

浮点类型分为了float32float64

友情提醒

1.隐式获取浮点类型的变量默认为float64类型,占8个字节,变量不赋值默认为0

//定义一个浮点类型变量 //demo04
num := 3.14
//获取变量占用字节数
size := unsafe.Sizeof(num)
fmt.Printf("num数据类型:%t 占用字节数:%v",num,size)
num数据类型:%!t(float64=3.14) 占用字节数:8

2.不管是float32还是float64都会丢失精度,建议使用float64、实在不行就用decimal,不过decimal并不是gosdk中提供的

//浮点转换为string类型,保留两位小数 //demo04
convertf := fmt.Sprintf("%.2f", 19.90)
fmt.Printf("convertf类型:%t 值:%v \n",convertf,convertf)
//string类型转换为float64
num2, _ := strconv.ParseFloat(convertf, 64)
//输出转换后的值
fmt.Println("num2 = ",num2)
//将该值乘于100
fmt.Println("num2 * 100 = ",num2 * 100)
convertf类型:%!t(string=19.90) 值:19.90 
num2 =  19.9
num2 * 100 =  1989.9999999999998

//需要引入第三方包,放后面再说 github.com/shopspring/decimal

//将字符串转换成float64  //demo04
num, _ := strconv.ParseFloat(fmt.Sprintf("%.8f", 19.90), 64)
fmt.Println(num)

decimalValue := decimal.NewFromFloat(num)
decimalValue = decimalValue.Mul(decimal.NewFromInt(100))

res,_ := decimalValue.Float64()
fmt.Println(res
字符类型

go中没有专门的字符类型,如果要存储单个字符(字母),一般使用byte保存,go默认使用的字符为UTF-8

//定义字符类型变量 值为a  //demo05
var c1 byte = 'a'
//输出a对应的ASCII码和输出该ASCII码对应的值
fmt.Printf("c1对应的ASCII码为:%v, 对应的字符为:%c \n",c1,c1)

c2 := '6'
fmt.Printf("c2对应的ASCII码为:%v, 对应的字符为:%c \n",c2,c2)

c3 := '('
fmt.Printf("c3对应的ASCII码为:%v, 对应的字符为:%c \n",c3,c3)

//中字为汉字,底层对应Unicode码,对应20013,超过了byte范围的值,所有会用int来接收
c4 := '中'
fmt.Printf("c4对应的ASCII码为:%v, 对应的字符为:%c \n",c4,c4)
c1对应的ASCII码为:97, 对应的字符为:a 
c2对应的ASCII码为:54, 对应的字符为:6 
c3对应的ASCII码为:40, 对应的字符为:( 
c4对应的ASCII码为:20013, 对应的字符为:中 

该类型也没啥纠结的,实际开发中基本不用该类型,完全可以用string来代替

布尔类型

关键词为bool ,占用1个字节,等价于Java中的boolean,不赋值默认为false

字符串

go中字符串也被归为基本的数据类型,关键词为string用法跟Java中大同小异,

友情提醒

1.如果字符串太长,需要换行的话,使用+号拼接,不过+号必须留在上一行,不然编译器会认为上一行代码还没有写完

2.声明一个string类型的字符串,不给默认值的,默认为“”

var s1 string
var s2 string = ""
flag := strings.EqualFold(s1,s2)
fmt.Println("判断s1等于s2,结果:",flag)
判断s1等于s2,结果: true
数据类型之间的转换
//demo06
fmt.Println("<--------int转浮点---------------->")

n1 := 100
f2 :=float32(n1)

fmt.Println("int类型的100转为float32,结果:",f2)

fmt.Println("<--------浮点转int---------------->")

f3 := 3.14
var n2 = int(f3)

fmt.Println("3.14转为int,结果:",n2)

fmt.Println("<--------int8转int---------------->")
var n3 int8 = 125
var n4 = int(n3)

fmt.Println("int8类型的125转int64,结果:",n4)

fmt.Println("<--------int转int8---------------->")
//这里比较好玩,如果是128 转出来的是-128,129-> -127 依次类推   256为0 如果再大就会是 1,有点一致性哈希环的感觉,不管你传啥都会在-128 - 127之间
//所以在用的时候要特别小心
n5  := 128
var n6 = int8(n5)
fmt.Println("int类型的128转int8,结果:",n6)

fmt.Println("<--------int转uint---------------->")
//这里跟上面一样,会取到正值
var n7 int = -300
var n8 = uint(n7)

fmt.Println("int类型的-300转uint,结果:",n8)

<--------int转浮点---------------->
int类型的100转为float32,结果: 100
<--------浮点转int---------------->
3.14转为int,结果: 3
<--------int8转int---------------->
int8类型的125转int64,结果: 125
<--------int转int8---------------->
int类型的128转int8,结果: -128
<--------int转uint---------------->
int类型的-300转uint,结果: 18446744073709551316
基本数据类型转string

转换共有两种方式,推荐使用第一种方式

  • 方式1:fmt.Sprintf(format string, a …interface{}) api链接https://studygolang.com/pkgdoc 搜索 fmt

//int类型转string  //demo07
n1 := 110
//方式一(推荐方式)
sn1 := fmt.Sprintf("%d",n1)
fmt.Printf("sn1数据类型:%t 值为:%v \n",sn1,sn1)

//方式二
sn2 := strconv.Itoa(n1)
fmt.Printf("sn2数据类型:%t 值为:%v \n",sn2,sn2)

//float64转string
f1 := 3.14
//方式一(推荐)
sf1 := fmt.Sprintf("%.3f",f1)
fmt.Printf("sf1数据类型:%t 值为:%v \n",sf1,sf1)
//第二个参数:'f'(-ddd.dddd)  第三个参数:9 保留小数点后面9位  第四个参数:表示这个小数是float64类型
sf2 := strconv.FormatFloat(f1,'f',3,64)
fmt.Printf("sf2数据类型:%t 值为:%v \n",sf2,sf2)

//bool转string
var flag bool
//方式一(推荐)
sb1 := fmt.Sprintf("%d", flag)
fmt.Printf("sb1数据类型:%t 值为:%v \n",sb1,sb1)
//方式二
sb2 := strconv.FormatBool(flag)
fmt.Printf("sb2数据类型:%t 值为:%v \n",sb2,sb2)

//字符类型转string
c := 'a'
//第一种方式
sc1 := fmt.Sprintf("%c", c)
fmt.Printf("sc1数据类型:%t 值为:%v \n",sc1,sc1)
  • 方式2:strconv.FormatXxxx() api跟上面一样搜索 strcover

string类型转基本数据类型
//string转int64 //demo08
sn1 := "100"
//参数1 :待转换的字符串  参数2:转为几进制  参数3:转为int多少
//返回值为一个元组  返回值1:转换的结果 返回值2:异常信息
//这里跟Python有点像 不想要的值可以用_忽略掉
n1, _ := strconv.ParseInt(sn1, 10, 64)
fmt.Printf("n1的类型为:%t 值为:%v \n",n1,n1)

//string转float34
sf1 := "3.14"
f1, _ := strconv.ParseFloat(sf1, 64)
fmt.Printf("f1的类型为:%t 值为:%v \n",f1,f1)

//string转bool
sb1 := "true"
b1, _ := strconv.ParseBool(sb1)
fmt.Printf("b1的类型为:%t 值为:%v \n",b1,b1)

//不是对应的string类型转换为bool,会转换为对应类型默认值
sb2 := "GoLang"
b2, _ := strconv.ParseBool(sb2)
fmt.Printf("b1的类型为:%t 值为:%v \n",b2,b2)

友情提醒

如果string类型的值不是转换的目标类型,则会取对应类型的默认值

派生数据类型/复杂数据类型
指针

每个基本数据类型变量,内存都会为其开辟一块内存空间,这个空间的地址值,我们就叫它指针,看下图,大家应该会更加清楚指针的概念

取变量指针

使用&变量就能得到变量的指针,示例:&age。变量的指针只能用指针变量来接收,

指针变量

指针变量的定义在原本的变量数据类型前加上*,例如 var ptr *int = &age 。

指针修改变量

变量也可以通过指针直接改变变量的值。示例 *ptr = 20,我们就将age的值改为了20

//定义一个int类型的变量 //demo09
var age int = 18
//通过&可以获取到变量的指针,&表示取地址符
//指针必须使用对应的指针变量去接收 指针变量就是在原来的数据类型前面加了个*
var ptr *int = &age
fmt.Printf("age的指针为:%v,值为:%v \n",ptr , *ptr)

//改变指针对应变量的值
*ptr = 25
fmt.Printf("指针修改后age的值:%v \n",age)
age的指针为:0xc00000a0a0,值为:18 
指针修改后age的值:25 
a函数内的age值: 20

我们只需要记住,指针关键的两个符号,*&取内存地址,根据地址取值

友情提醒

1.指针变量一定接收的是地址值

2.指针变量的地址必须匹配对应的变量类型,int变量只能用 *int 来接收

3.我们先看个例子,大家可以先猜测一下最后输出的这个age是多少

//demo09
func main() { 
	//定义一个int类型的变量
	var age int = 18
	a(age)
	fmt.Printf("函数修改后age的值:%v \n",age)
}

//将age的值修改为20
func a(age int)  {
	age = 20
	fmt.Println("a函数内的age值:",age)
}

为了让大家加深印象,分享时再给出结论

数组

定义数组的语法:var array[size] int,数组中默认填充的都是对应数据类型的默认值

//定义一个空数组,默认填充对应类型的默认值
var array01 [10] int
//给数组第一个元素赋值
array01[0] = 2
fmt.Println("array01:",array01)

//定义一个长度为5的数组,并给定值
array02 := [5] int {1,2,3,4,5}
fmt.Println("array02:",array02)

//定义一个未知长度的数组,编译器会根据元素推断出数组长度
//编译完成后,假设现在是10个元素,后期在方法中也不能给该数组加元素
//你可以理解为编译时,go帮你把...替换成了10就行
array03 := [...] int {1,3,5,6,9,7,5,2,2,6}
fmt.Printf("array中元素个数 = %v \n",len(array03))
array01: [2 0 0 0 0 0 0 0 0 0]
array02: [1 2 3 4 5]
array中元素个数 = 10 
切片(Slice)

切片是对数组的抽象,由于数组定义好了长度,有局限性,所以就设计了切片,底层还是数组,切片会自动扩容,大家可以理解为java中的List

切片的定义

//方式一:定义一个空数组 //demo11
var slice1 [] int
fmt.Println("slice1",slice1)
//方式二:通过make函数来 创建 make(type,len,cap) 切片类型,切片初始长度,切片初始容量
//默认填充对应类型的默认值
slice2 := make([]int,0,3)
fmt.Println("slice2",slice2)
//区别 slice1=nil slice2相当于初始化了,不为nil

//定义带初始值的切片,支持自动扩容
slice3 := [] int {1,2,3,4}
fmt.Println("slice3:",slice3)
slice []
slice2 []
slice3: [1 2 3 4]

添加删除切片中的值

go语言提供了切片添加元素的函数append,但是并没有提供删除元素的函数,不过我们利用现有的语法能自己删除删除的功能

//给slice3添加元素  //demo11
slice3 = append(slice3, 1)
fmt.Println("slice3:",slice3)
//删除slice3的第一个元素
slice3 = remove(slice3, 0)
fmt.Println("slice3:",slice3)

/*
移除下标为index的元素,下面有将append的切片带的参数是啥意思
*/
func remove(slice [] int,index int) [] int {
	return append(slice[:index],slice[index + 1:]...)
}
slice3: [1 2 3 4 1]
slice3: [2 3 4 1]

获取切片长度和容量

使用len()方法和cap方法可以获取切片长度和容量

//获取切片的长度
lenth := len(slice3)
//获取切片的容量
capsize := cap(slice3)
fmt.Println("切片的长度:",lenth," 容量:",capsize)
切片的长度: 4  容量: 8

数组转切片

数组转切片,传的是数组的引用,数组值变化,切片中对应的值也会变化

语法:slice := array[startIndex,endIndex] 左闭右开 ,也可以只传一个参数

slice := array[:]取数组的全部元素

slice := array[startIndex:] 从startIndex开始取,后面所有的元素

slice := array[:endIndex] 从第一个开始取,取到下标(endIndex-1)为止

//定义一个数组  //demo12
array := [10] int {1,2,3,4,5,6,7,8,9,10}
//数组转对象,取数组中所有元素
slice1 := array[:]
fmt.Println("slice1:",slice1)
//数组转对象,从第一个开始取
slice2 := array[1:]
fmt.Println("slice2:",slice2)
//数组转对象,从第一个开始取到 5 - 1 个
slice3 := array[:5]
fmt.Println("slice3:",slice3)
slice1: [1 2 3 4 5 6 7 8 9 10]
slice2: [2 3 4 5 6 7 8 9 10]
slice3: [1 2 3 4 5]

数组转切片传的是引用,切片的值会跟着数组的变化而变化,如果后面切片添加元素超过了数组的长度时,底层会该切片创建一个容量为原来两倍的数组,此时老数组的值变动不会影响到扩容后的切片

//修改数组最后一个元素为100
array[9] = 100
fmt.Println("slice1:",slice1)
//为slice切片添加一个元素,此时会扩容,底层新创建一个容量为2倍的数组
slice1 = append(slice1, 11)
//改变老数组的值
array[8] = 120
fmt.Println("slice1:",slice1)
slice1: [1 2 3 4 5 6 7 8 9 100]
slice1: [1 2 3 4 5 6 7 8 9 100 11]

那么除了数组扩容,还有别的办法让切片中的元素不随数组变动而变动呢?

//创建一个切片 //demo12
slice4 := make([]int, 10)
//将切片slice1元素拷贝给slice4
copy(slice4,slice1)
//修改数组中的值
array[8] = 110
fmt.Println("slice1:",slice1)
fmt.Println("slice4:",slice4)
slice1: [1 2 3 4 5 6 7 8 110 100]
slice4: [1 2 3 4 5 6 7 8 9 100]

通过copy函数,我们可以发现数组变动以后,切片slice1的值变动了,但是拷贝的slice4还是拷贝之前的值,

我们可以发现copy是值拷贝,并不是引用拷贝。

Map

Map是一种无序的键值对的集合。底层也是基于hash表来实现的,我们可以理解为Java中的HashMap

Map定义

//定义一个map变量 初始值为nil,没有初始化不能往集合中添加元素
var map1 map[string] int
fmt.Println("map1",map1)
//异常:panic: assignment to entry in nil map
//map1["key"] = 1
//定义一个带初始值的map
map2 := map[string] int {"a": 1, "b":2}
fmt.Println("map2",map2)

//定义一个初始化的空map,容量为10
map3 := make(map[string]int, 10)
fmt.Println("map3",map3)
map1 map[]
map2 map[a:1 b:2]
map3 map[]

元素添加

map[key] = value,跟数据赋值差不多,数组给的是索引,map给的是key而已

//map2添加元素 key = a  value = 1
map3["c"] = 1
map3["d"] = 2
fmt.Println("map3",map3)
map3 map[c:1 d:2]

元素获取

value,ok := map[key],第一个元素为key对应的值,第二个表示key是否存在

//通过key获取value 会返回一个元组
//第一个元素为key对应的值,第二个表示是否存在
value,ok := map3["d"]
fmt.Println("value:",value,"ok",ok)
value: 2 ok true

元素删除

通过delete函数,通过key删除

//删除map3中的元素
delete(map3,"d")
fmt.Println("map3",map3)
map3 map[c:1]

管道、函数、接口

Logo

K8S/Kubernetes社区为您提供最前沿的新闻资讯和知识内容

更多推荐