go基础类型的声明与赋值
变量声明
go语言的变量声明主要就是 var name type
,var关键字声明,name就是变量名,type就是变量的类型。
- 标准声明
//var name type
var variate string
variate = "vari"
- 多行声明
var (
a int
b string
c []float32
//d func() bool
e struct {
x int
}
)
可以看到用括号()进行包裹,声明多个不同类型的变量。
- 简写声明
x:=100
a,s:=1, "abc"
这是go语言特有的语法,即 variate := value,左边是变量名,右边是值,go会自动的做类型推断。并且可以左右都是多个来多变量声明。
- 变量交换值
a:=1
b:=4
a,b=b,a
//a=4 b=1
一大特色,java,js的swap都要
temp=a;
a=b;
b=temp;
而go只需要使用 :=
来进行交换。
需要注意: 使用
:=
其实也是声明一个新的变量,所以也不能重复声明,重复的话编辑器会报错的。
- 匿名变量
func GetData() (int, int) {
return 100, 200
}
m, _ := GetData()
_, n := GetData()
fmt.Print(m, n)
//100 200 _是匿名变量,不使用内存
匿名变量,也就是将位置占了,从而获取到我们想要的位置的值。
常量与iota
常量是一个简单值的标识符,在程序运行时,不会被修改的量。
常量的值必须是能够在编译时就能够确定的,可以在其赋值表达式中涉及计算过程,但是所有用于计算的值必须在编译期间就能获得。
const (
a = 1
b
c = 2
d
)
//const 批量声明时,除了第一个变量需要赋值,后面都可以省略
const c1 = 2 / 3 //正确的做法
//const c2 = getNumber() 错误的做法 引发构建错误: getNumber() 用做值
//const 变量的值需要能在编译阶段确定
const 多变量声明可以在有前值时省略后面的常量的赋值,它的值会等于最后一个赋了值的常量。在这里,b的值就是1,d的值就是2
func main() {
const (
a = iota //0
b //1
c //2
d = "ha" //独立值,iota += 1
e //"ha" iota += 1
f = 100 //iota +=1
g //100 iota +=1
h = iota //7,恢复计数
i //8
)
fmt.Println(a,b,c,d,e,f,g,h,i)
}
iota 在 const关键字出现时将被重置为 0(const 内部的第一行之前),const 中每新增一行常量声明将使 iota 计数一次(iota 可理解为 const 语句块中的行索引)。
这和ts的enum枚举是一样的。它不关心单个常量的赋值是多少,itoa总是标识着index(下标)。可以通过再次将常量赋值iota来恢复计数。
类型别名
类型别名:type typename = Type
,顾名思义就是为类型取一个别名,但实际上这个别名和真名是一致的。区别于类型定义:type typename Type
,它实际上是声明一个新的类型,它的类型和原类型比较的话是不一致的。举一个例子:
type newType int
type aliasType = int
var a newType
var b aliasType
fmt.Printf("newType typeof: %T ,aliasType typeof: %T", a, b)
//newType typeof: main.newType ,aliasType typeof: int
关键字
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 |
标识符
append | bool | byte | cap | close | complex | complex64 | complex128 | uint16 |
---|---|---|---|---|---|---|---|---|
copy | false | float32 | float64 | imag | int | int8 | int16 | uint32 |
int32 | int64 | iota | len | make | new | nil | panic | uint64 |
println | real | recover | string | true | uint | uint8 | uintptr |
数组与切片
数组是一个由固定长度的特定类型元素组成的序列,一个数组可以由零个或多个元素组成。因为数组的长度是固定的,所以在Go语言中很少直接使用数组。而是使用切片。
var a [3]int // 定义三个整数的数组
//不赋值的话就默认值为0
fmt.Println(a[0]) // 打印第一个元素
fmt.Println(a[len(a)-1]) // 打印最后一个元素
// 打印索引和元素
for i, v := range a {
fmt.Printf("%d %d\n", i, v)
}
// 仅打印元素
for _, v := range a {
fmt.Printf("%d\n", v)
}
数组没什么好说的。注意遍历数组使用range进行遍历。
for index,value:= range arrar{
//do something with index and value
}
切片
切片(slice)是对数组的一个连续片段的引用,所以切片是一个引用类型(因此更类似于 C/C++中的数组类型,或者 Python中的 list 类型),这个片段可以是整个数组,也可以是由起始和终止索引标识的一些项的子集,需要注意的是,终止索引标识的项不包括在切片内。
通过数组生成切片:
func main() {
var arr1 = [3]int{2, 3, 4}
fmt.Println(arr1[1:], arr1[:], arr1[1:2], arr1[1:1])
//[3 4] [2 3 4] [3] []
}
声明新切片:
var slice1 []int
println(len(slice1))
make生成新的切片
s2 := make([]int, 3)
fmt.Println(len(s2))
切片的使用和数组基本上是一致的,包括range遍历,append添加元素等。只是切片是动态的,而数组是固定的,切片动态扩容,而数组需要手动扩容。
map
map,对应就是java的hashmap,js的map。它的定义是 var mapName map[keyType]valueType。
func main() {
//var map2 map[string]string //声明,并没有生成实例
map1 := make(map[string]string) //make构造,返回map实例
map1["key1"] = "value1"
map1["key2"] = "value2"
map1["key3"] = "value3"
fmt.Println(map1)
for s, s2 := range map1 {
fmt.Println(s, s2)
}
//map[key1:value1 key2:value2 key3:value3]
//key1 value1
//key2 value2
//key3 value3
}
关于map的delete方法等看看就会用了,语义化的方法名。
sync.map
为了解决多线程map的读取而产生的异步的map,就是说map的增删是异步了,不会产生线程冲突。但是如果是单线程的使用map结果,肯定还是选用map更快而不是sync.map
var scene sync.Map
scene.Store("1", 1)
scene.Store("2", "2")
scene.Store(3, "v3")
scene.Store(4, 4)
//println(syncmap1.Load("key1"))
fmt.Println(scene.Load("2"))
fmt.Println(scene.LoadAndDelete(4))
goto语句
goto就是c不提倡的go语句,在golang里面可以很好的使用。用于跳出当前执行上下文,并跳转到goto的函数。
func main() {
for i := 0; i < 10; i++ {
for j := 0; j < 10; j++ {
if j == 8 {
fmt.Println(i, j)
goto breakAt
}
}
}
breakAt:
fmt.Println("break")
}
//0 8
//break
for
func main() {
//normal loop 循环和java/c类似,只是不需要写(;;)
for i := 0; i < 100; i++ {
fmt.Printf("i%d ", i)
}
fmt.Println()
//infinite loop / endless loop 无线循坏
n := 0
for {
n++
fmt.Println("this is do...")
if n > 100 {
fmt.Println("break...")
break
}
}
// 带goto,可以不对i初始化
var i int
for {
fmt.Printf("%d ", i)
if i > 10 {
goto breakHere
}
i++
}
breakHere:
fmt.Println("break")
}
函数
注意多返回值即可,另外还可以命名返回值,为返回值赋值,且return的时候可以省略。
func namedRetValues() (a, b int) {
a = 1
b = 2
return
}
func doubleRe(a int, b string) (i int, j string) {
return a, b
}
func main() {
fmt.Println(namedRetValues())
fmt.Println(doubleRe(2, "3"))
}
匿名函数
func(word string) {
fmt.Println("word is : ", word)
}("niganma")
在匿名函数后面加(arg...)就是自调用
f := func(word string) {
fmt.Println("word is : ", word)
}
f("niganma")
和js一样,也可以匿名函数赋值。
匿名函数的另一个使用是作为回调函数
// a func with a func parameter
func userCallBackFunc(i int, f func(int2 int)) {
f(i)
}
func main() {
//在这里传入匿名函数
userCallBackFunc(3, func(int2 int) {
fmt.Printf("do callBack and the number is %d \n", int2)
})
}
closure(闭包)
通过函数可以实现闭包,将函数作为返回值
// 匿名函数作为返回值
func Add(i int) func() int {
return func() int {
i++
return i
}
}
func main() {
//获取到返回的函数
a1 := Add(10)
//然后调用返回的函数,得到操作之后的结果,这就是go的函数实现的闭包
fmt.Println(a1()) // 13
}
可变参数
很实用的参数传递方式,而且可以通过迭代器遍历。这是因为可变参数的实现就是切片。以及arg...来将x可变参数传递给另一个函数。
func ddd(int1 ...int) {
for _, i2 := range int1 {
fmt.Println(i2)
}
}
func ddd2(int2 ...int) {
ddd(int2...)
//通过这个传回可变参数
}
func main() {
ddd2(1, 2, 3, 4)
}
//1 2 3 4
method
在golang中method和function是不一样的,function就是通过func定义然后直接调用的,而method则不能直接调用
方法(method)与函数(function)的区别是,函数不属于任何类型,方法属于特定的类型。
// Person 定义接收者结构体
type Person struct {
name string
age int
}
// func (接收者变量 接收者类型) 方法名(参数列表) (返回参数) {
//函数体
//}
func (p Person) pMSG() { //这里的意义就是Person这个结构体接收到了pMSG这个方法
fmt.Printf("this person's name is: %s and age is %d\n", p.name, p.age)
}
//定义多个方法到接收者上
func (p Person) pName() {
fmt.Println("name is " + p.name)
}
func main() {
p := Person{
name: "mio",
age: 18,
}
//p.就可以看到Person接收到的方法
p.pMSG()
p.pName()
}
defer
defer延迟调用是 Go语言中的一个关键字,一般用于释放资源和连接、关闭文件、释放锁等。就是熟知的异步调用。
需要注意的是,defer的调用是栈结构的,即先进后出,与之相反的是js的微任务队列。
func main() {
defer fmt.Println(1)
defer fmt.Println(2)
defer fmt.Println(3)
//3
//2
//1
//1先进反而后出,可以看出是栈结构的延迟调用
}
panic_recover
go语言的panic和recover就是类似try...catch的处理异常的语句。通过panic来让执行宕机(抛出异常),然后通过recover来捕获并处理。
//一定是通过defer来处理recover语句,因为不能先执行,又要等panic之后再来调用recover。
defer func() {
if err := recover(); err != nil {
fmt.Printf("this is a word with defer&recover%s\n", err)
}
}()
fmt.Println("this is a work without defer")
panic("err")
ps:panic 并不会第一时间宕机,而是栈的方式依次执行前面的defer语句。
struct
struct,类似于java的类的接口。
type Person struct {
name string
age int
}
var person1 Person
person1.age = 19
person1.name = "mio"
fmt.Println(person1, person1.name)
//{mio 19} mio
struct也是可以嵌套的。
type Person struct {
name struct {
firstName string
lastName string
}
age int
}
var person1 Person
person1.age = 19
person1.name.firstName = "m"
person1.name.lastName = "io"
fmt.Println(person1, person1.name.firstName+person1.name.lastName)
//{{m io} 19} mio
当然也可以导入type作为类型
type Name struct {
firstName string
lastName string
}
type Person struct {
name Name
age int
}
& 是取地址符号 , 即取得某个变量的地址 , 如 ; &a *是指针运算符 , 可以表示一个变量是指针类型 , 也可以表示一个指针变量所指向的存储单元 , 也就是这个地址所存储的值 .
理解上面看代码
relation := &People{
//取到这个结构体实例的地址
name: "爷爷",
child: &People{
name: "爸爸",
child: &People{
name: "我",
},
},
}
fmt.Println(relation, *relation.child, *(*relation.child).child)
//&{爷爷 0xc000004090} {爸爸 0xc0000040a8} {我 <nil>}
// 值肯定是地址,但是地址指向的区域包括name和另一个地址
反正就是套娃。
同时可以通过&对结构体进行实例化(这是最通用的实例化方法)
type Str struct {
a int
b int
}
//作为返回值
func newStr(a int, b int) *Str {
return &Str{a: a, b: b}
}
str33 := newStr(10, 20)
type People struct {
name string
child *People
// People结构体的指针
}
func tost(p1 *People) {
fmt.Println(p1)
}
var p1 People
p1.name = "ss"
tost(&p1)
通过指针完成函数的参数传递。
看到内嵌结构体的写法
type Str struct {
a int
b int
}
type Str2 struct {
Str
c, d float32
}
s := Str2{Str{3, 4}, 1.0, 2.0}
fmt.Println(s,s) //{{3 4} 1 2} {3 4} 3
内嵌的结构体有时会出现结构体的属性重名的情况,这个时候适当使用链式获取能够避免问题
type per1 struct {
name string
sex string
}
type per2 struct {
name string
age int
}
type per struct {
per1
per2
}
p := per{per1{name: "syh", sex: "男"}, per2{name: "syh2", age: 22}}
//fmt.Println(p.name) //错误的,编辑器能够识别这个错误--不能识别name是谁的name
fmt.Println(p.sex, p.per2.name) //链式的获取,正确的
interface
go也注重类型,接口就是强化类型的重要性
//type 接口类型名 interface{
// 方法名1( 参数列表1 ) 返回值列表1
// 方法名2( 参数列表2 ) 返回值列表2
//}
// Word 定义一个接口,有outWord方法
type Word interface {
outWord(data string) string
}
// Words 定义一个结构,他将要实现接口
type Words struct {
}
//定义一个方法,它和interface里的outWord定义的函数一致 (注意:这是一个方法而不是函数,words作为接收着)
func (w *Words) outWord(data string) string {
fmt.Println("outWords: ", data)
return " s "
}
func main() {
//定义好一个结构体变量,使用new分配空间
w := new(Words)
//定义一个接口变量
var word Word
//结构体实现接口,能够赋值的前提是w (words)实现了interface里定义的成员 (注意:必须全部实现才可以)
word = w
//那么这个被实现后的接口变量就可以调用outWord方法
word.outWord("data")
}
go 语言的接口和类型(结构)是多对多的关系的,也就是说一个接口可以被多个类型实现,一个类型也可以实现多个接口
assert
assert-断言和interface息息相关,断言类型,通常断言的就是interface类型
var x interface{}
x = 10
//断言能够得到value和Boolean(断言是否正确)的返回值
value, ok := x.(int)
fmt.Print(value, ",", ok, "\n")
var y interface{}
y = "string"
//通过变量.(type)来switch...case
switch y.(type) {
case int:
fmt.Println("int")
case string:
fmt.Println("string")
case float32:
fmt.Println("float32")
default:
fmt.Println("?")
}
sort
sort是c内置的排序包,可以通过通过实现接口来调用。
import (
"fmt"
"sort"
)
// MyStringList 将[]string定义为MyStringList类型
type MyStringList []string
// Len 实现sort.Interface接口的获取元素数量方法
func (m MyStringList) Len() int {
return len(m)
}
// Less 实现sort.Interface接口的比较元素方法
func (m MyStringList) Less(i, j int) bool {
return m[i] < m[j]
}
// Swap 实现sort.Interface接口的交换元素方法
func (m MyStringList) Swap(i, j int) {
m[i], m[j] = m[j], m[i]
}
type HeroKind int
type Hero struct {
Name string
Kind HeroKind
}
func main() {
// 准备一个内容被打乱顺序的字符串切片
names := MyStringList{
"3. Triple Kill",
"5. Penta Kill",
"2. Double Kill",
"4. Quadra Kill",
"1. First Blood",
}
// 使用sort包进行排序
sort.Sort(names)
// 遍历打印结果
for _, v := range names {
fmt.Printf("%s\n", v)
}
const (
fir = iota
sec
thi
fou
fiv
)
arr := []*Hero{
{"men", fir},
{"men2", fou},
{"men4", sec},
{"men3", fiv},
{"men5", thi},
}
sort.Slice(arr, func(i, j int) bool {
// 编写实现 less 接口
if arr[i].Kind != arr[j].Kind {
return arr[i].Kind < arr[j].Kind
}
return arr[i].Name < arr[j].Name
})
for _, v := range arr {
fmt.Println(*v)
}
总结
本次文章学习了golang的基础,包括基础类型,接口,结构,以及一些关键字。
结语
本次的文章到这里就结束啦!♥♥♥读者大大们认为写的不错的话点个赞再走哦 ♥♥♥
每天一个知识点,每天都在进步!♥♥