【Go】Go语言学习笔记--Go语言基础【中】

简介: 运算符Go语言内置的运算符:算术运算符关系运算符逻辑运算符位运算符赋值运算符

运算符

Go语言内置的运算符:

  1. 算术运算符
  2. 关系运算符
  3. 逻辑运算符
  4. 位运算符
  5. 赋值运算符

算数运算符

运算符 描述
+ 相加
- 相减
* 相乘
/ 相除
% 取余

++--在Go语言里是单独的语句,不是运算符,与JavaScript不同。

关系运算符

运算符 描述
== 两个值是否相等,如果相等返回 True 否则返回 False
!= 两个值是否不相等,如果不相等返回 True 否则返回 False
左边是否大于右边
>= 左边是否大于等于右边
< 左边是否小于右边
<= 左边是否小于等于右边

逻辑运算符

运算符 描述
&& AND
|| OR
! NOT

位运算符

运算符 描述
& 参与运算的两数各对应的二进位相与
| 参与运算的两数各对应的二进位相或
参与运算的两数各对应的二进位相异或,当两对应的二进位相异时,结果为1

赋值运算符

运算符 描述
= 简单的赋值
+= 相加赋值
-= 相减赋值
*= 相乘赋值
/= 相除赋值

练习题

有一堆数字,如果除了一个数字以外,其他数字都出现了两次,那么如何找到出现一次的数字?

// 我的解法
numarr := [...]int{1,1,2,2,3,3,4}
for i:=0;i<len(numarr);i++ {
    flag := true
    for j:=0;j<len(numarr);j++ {
        if numarr[i]==numarr[j] && i!=j {
            flag = false
            break
        }
    }
    if flag {
        fmt.Println(numarr[i], ",只出现了一次")
        break
    }
}
// 比较好的解法
numarr := [...]int{1,1,2,2,3,3,4}
res := 0
for _,item := range numarr{
   res ^= item
}
fmt.Println(res)
复制代码

流程控制

if else

Go语言中if语句格式:

if 表达式1 {
    分支1
} else if 表达式2 {
    分支2
} else {
    分支3
}
复制代码

switch case

基本格式

switch 值 {
    case 值1:
        分支1
    case 值2:
        分支2
    default:
        默认分支
}
复制代码

多个case值

一个分支可以有多个值,多个case值之间用,隔开

switch 值 {
    case 值1,值2,值3:
        分支1
    case 值4,值5,值6:
        分支2
    default:
        默认分支
}
### 分支表达式
switch {
    case 表达式1:
        分支1
    case 表达式2:
        分支2
    default:
        默认分支
}
复制代码

fallthrough

fallthrough可以在满足一个case的条件下,继续向下执行case分支。

for循环

基本格式

for 初始语句;表达式;结束语句 {
    循环体语句
}
复制代码

无限循环

for {
    循环体语句
}
复制代码

for循环可以通过breakgotoreturnpanic语句强制退出循环。

for range键值循环

Go语言中可以使用for range遍历数组切片字符串map通道(channel),返回值有以下规律:

  1. 数组、切片、字符串返回索引和值(即index和item)
  2. map返回键和值
  3. channel只返回通道内的值 类似JavaScript中的map、forEach等循环。

goto(跳转到指定标签)

goto语句可以快速跳出循环,避免重复退出。

break(跳出循环)

break语句可以结束forswitchselect的代码块。

continue(继续下一次循环)

continue可以结束当前循环,立即开始执行下一次循环,只能在for循环内使用。

练习题

打印9*9乘法表

// 我的解法
for i:=1;i<=9;i++ {
    for j:=1;j<=i;j++ {
        fmt.Printf("%v*%v=%v ", j, i, j*i)
    }
    fmt.Println()
}
复制代码

数组(array)

数组定义

var 变量名称 [元素数量]类型
复制代码

举个栗子🌰

// 定义一个长度为3,元素类型为int的数组a
var a [3]int
复制代码

与JavaScript不同的是,数组的长度是必须常量,一旦定义,不能改变。比如[5]int[3]int就是不同的类型。

var a [5]int
var b [3]int
a = b // 不可以这样
复制代码

数组可以通过下标进行访问,从0开始,到len-1结束。

数组初始化

方法一

var arr1 [3]int // [0 0 0]
var arr2 [4]int{1,2,3} // [1 2 3 0]
var arr3 [5]string{"北京","上海","广州","深圳","杭州"} // [北京 上海 广州 深圳 ]
复制代码

方法二

可以让编译器根据初始值的个数自行推断数组的长度。

var arr1 = [...]int{1,2,3,4} //[1 2 3 4]
var arr2 = [...]string{"男","女"} //[男 女]
var arr3 = [...]bool{true, false} // [true false]
复制代码

方法三

指定索引值初始化数组。

var arr1 = [...]int{2:22,5:55} // [0 0 22 0 0 55]
复制代码

数组的遍历

遍历数组有两种方法:

// 方法一
var arr = [...]int{1,2,3,4,5}
for i:=0; i<len(arr); i++ {
    fmt.Printf("当前索引:%v; 当前项:%v \n", i, arr[i])
}
// 方法二
for index,item := range arr {
    fmt.Printf("当前索引:%v; 当前项:%v \n", index, item)
}
复制代码

多维数组

二维数组的定义

var arr = [3][2]string{
    {"北京","上海"},
    {"广州","深圳"},
    {"杭州","西安"},
}
fmt.Print(arr)//[[北京 上海] [广州 深圳] [杭州 西安]]
复制代码

二维数组的遍历

var arr = [3][2]string{
    {"北京","上海"},
    {"广州","深圳"},
    {"杭州","西安"},
}
// 方法一
for i:=0;i<len(arr);i++ {
    for j:=0; j<len(arr[i]);j++ {
        fmt.Println(arr[i][j])
    }
}
// 方法二
for _,item1 := range arr {
    for _,item2 := range item1 {
        fmt.Println(item2)
    }
}
复制代码

多维数组只有第一层可以使用...来让编译器推导长度。

数组是值类型

数组是值类型,赋值和传参会复制整个数组。因此改变副本的值,不会改变本身的值。(即JavaScript中的Array为引用数据类型)

func main()  {
   var arr = [3]int{10,20,30}
   modifyArr(arr)
   var arr2 = [3][2]int{
      {1,1},
      {1,1},
      {1,1},
   }
   modifyArr2(arr2)
   fmt.Println("main函数里的arr",arr2)
}
func modifyArr(x [3]int)  {
   x[0] = 100
}
func modifyArr2(x [3][2]int)  {
   x[2][0] = 100
   fmt.Println("modifyArr2中的arr",x)
}
复制代码

练习题

  1. 求数组[1, 3, 5, 7, 8]所有元素的和
// 我的解法
func reduceArr(arr [5]int) {
   var count int = 0
   for _, c := range arr{
      count += c
   }
   fmt.Print(count)
}
复制代码
  1. 找出数组中和为指定值的两个元素的下标,比如从数组[1, 3, 5, 7, 8]中找出和为8的两个元素的下标分别为(0,3)(1,2)
// 我的解法
func findIndex(num int, arr [5]int) {
   for i:=0;i<len(arr);i++ {
      for j:=i+1; j<len(arr);j++ {
         if arr[i]+arr[j] == num {
            fmt.Printf("(%v,%v)", i, j)
         }
      }
   }
}
复制代码

切片(slice)

数组有很多局限性,长度固定,不能添加新的元素到数组里,所以有了切片(slice),一个切片,可以看作是由:一个左指针和一个右指针以及capacity组成。

基本概念

切片是一个拥有相同类型元素的可变长度的序列,是基于数组的一层封装,非常灵活。

切片是引用类型的,内容结构包含了地址长度容量。切片一般用于快速的操作一块数据集合。

基本语法

var 变量名 []元素类型
复制代码

举个栗子🌰

var a []string
var b = []int{}
var c = []bool{false, true}
var d = []bool{false, true}
复制代码

切片的长度和容量

  • len()求长度
  • cap()求容量

切片表达式

切片表达式中lowhigh表示一个索引范围(包头不包尾),举个栗子🌰

var arr = [5]int{1,2,3,4,5}
var arr2 = arr[0,4]
fmt.Printf("arr2:%v;len:%v;cap:%v", arr2, len(arr2), cap(arr2))
//arr2:[1 2 3 4];len:4;cap:5
复制代码

切片表达式中的索引可以省略,low默认为0high默认为len(数组)

arr[2:0] //等于 arr[2:len(arr)]
arr[:3] // 等于 arr[0:3]
arr[:] // 等于 arr[0:len(arr)]
复制代码

注意

对于数组和字符串的切片,0 <= low <= high <= len(arr),索引合法。

对于切片切片,high的上限边界是切片的容量,而不是长度。

var arr = [5]int{1,2,3,4,5}
var s1 = arr[1:3]
fmt.Printf("s1:%v;len:%v;cap:%v", s1, len(s1), cap(s1))
fmt.Println()
var s2 = s1[1:4] // 索引的上限是cap(s1)而不是len(s1)
fmt.Printf("s2:%v;len:%v;cap:%v", s2, len(s2), cap(s2))
复制代码

cap函数

从数组中取

cap函数只计算从左指针原数组最后的值,也就是lowlen(arr)的个数,即len(arr) - low

从切片中取

举个栗子🌰

var arr = [9]int{1,2,3,4,5,6,7,8,9}
var s1 = arr[2:7] // 此时左指针偏移两位,cap(s1)是7,len(s1)是5, s1是[3 4 5 6 7]
var s2 = s1[3:4] // 此时左指针再次偏移三位,cap(s2)是4,len(s2)是1,s2是[6]
var s3 = s2[2:4] // 此时左指针再次偏移两位,cap(s3)是2,len(s3)是2,s3是[8 9]
复制代码

使用make函数构造切片

不基于数组,动态创建一个切片,基本格式:

make([]元素类型,元素数量,切片容量)
复制代码

举个栗子🌰

var s1 = make([]int, 2, 10)
//s1:[0 0] len(s1):2,cap(s1):10
复制代码

判断切片是否为空

始终使用len(s1) == 0来判断

切片的赋值拷贝

s1 := make([]int,3)
s2 := s1 // s1与s2共用一个底层数组
s1[0] = 100
fmt.Println(s1) // [100 0 0]
fmt.Println(s2) // [100 0 0]
复制代码

切片的遍历

var s1 = []int{1,2,3,4}
for index,item := range s1 {
    fmt.Println(index, item)
}
复制代码

append()添加元素到切片

var s1 = []int{1,2,3} 
s1 = append(s1, 4) // [1 2 3 4]
s1 = append(s1, 5,6,7) // [1 2 3 4 5 6 7]
var s2 = []int{8,9}
s1 = append(s1, s2...) // [1 2 3 4 5 6 7 8 9]
复制代码

每个切片都会指向一个底层数组,数组容量够用就添加新元素。底层数组不能容纳新增元素时,切片会自动按照一定的策略“扩容”,此时,该切片指向的底层数组就会更换。

举个栗子🌰

var s1 = []int
for i=0;i<10;i++ {
    s1 = append(s1, i)
    fmt.Printf("%v len:%d cap:%d ptr:%p\n", s1, len(s1), cap(s1), s1)
}
复制代码

输出:

[0] len:1 cap:1 ptr:0xc00010c008
[0 1] len:2 cap:2 ptr:0xc00010c030
[0 1 2] len:3 cap:4 ptr:0xc00011e020
[0 1 2 3] len:4 cap:4 ptr:0xc00011e020
[0 1 2 3 4] len:5 cap:8 ptr:0xc000120040
[0 1 2 3 4 5] len:6 cap:8 ptr:0xc000120040
[0 1 2 3 4 5 6] len:7 cap:8 ptr:0xc000120040
[0 1 2 3 4 5 6 7] len:8 cap:8 ptr:0xc000120040
[0 1 2 3 4 5 6 7 8] len:9 cap:16 ptr:0xc000122000
[0 1 2 3 4 5 6 7 8 9] len:10 cap:16 ptr:0xc000122000
复制代码

结论:

  1. append() 函数将元素追加到切片的最后,并返回切片
  2. s1的容量每次扩容后都是扩容前的2倍

copy()函数复制切片

切片直接赋值指向同一底层数组,利用copy可以将一个切片的数据复制到另外一个切片空间中。

copy(目标切片, 源切片 []元素类型)
复制代码

举个栗子🌰

var s1 = []int{1,2,3,4,5}
var s2 = make([]int, 5)
copy(s2, s1)
fmt.Println(s1,s2) // [1 2 3 4 5] [1 2 3 4 5]
s1[0] = 100
s2[0] = 999
fmt.Println(s1,s2) // [100 2 3 4 5] [999 2 3 4 5]
复制代码

注意:

  1. copy函数 目标切片需要先初始化长度
  2. 源切片中的元素类型为引用类型时,拷贝的是引用 举个栗子🌰
// 1.
var s1 = []int{1,2,3,4,5}
var s2 []int
copy(s2, s1)
fmt.Print(s1, s2) // [1 2 3 4 5] []
// 2.
var s1 = [][]int{
    {1, 1},
    {1, 1},
    {1, 1}
}
var s2 = make([][]int, 5)
copy(s1, s2)
s1[0][0] = 100
fmt.Print(s1, s2) // [[100 1] [1 1] [1 1]] [[100 1] [1 1] [1 1] [] []]
复制代码

正确的拷贝二维数组的方法为:

// 先初始化,再拷贝
var s1 = [][]int{
    {1,1},
    {1,1},
    {1,1},
}
var s2 = make([]int, 3)
for index,item := range s1 {
    s2[index] = make([]int, len(item))
    copy(s2[index], item)
}
复制代码

从切片中删除元素

Go语言中没有专门删除切片元素的方法......不像JavaScript,有splicepopshift。但是可以使用切片本身的特性来删除元素。

var s1 = []int{1,3,4,5,6,7}
// 要删除索引为2的元素
s1 = append(a[:2], a[3:]...)
fmt.Println(s1)
复制代码

总结一下就是:要从切片s1中删除索引为index的元素,操作方法是s1 = append(s1[:index], s1[index+1:]...)

练习题

  1. 请写出下面代码的输出结果
var s1 = make([]string, 5, 10) // s1:["" "" "" "" ""] len(s1):5 cap(s1):10
for i:=0; i < 10; i++ {
    s1 = append(s1, fmt.Sprintf("%v", i))
}
fmt.Println(s1) // ["" "" "" "" "" 0 1 2 3 4 5 6 7 8 9]
复制代码
  1. 请使用内置的sort包对数组var s1 = [...]int{3, 7, 8, 9, 1}进行排序。
package main
import (
    "fmt"
    "sort"
)
func main() {
    var arr = [...]int{3,7,8,9,1}
    var s1 = arr[:]
    sort.Ints(s1)
    fmt.Println(s1)
}
复制代码
  1. 请写出下面代码的输出结果
s := []int{5} 
s = append(s, 7)
s = append(s, 9) 
x := append(s, 11) 
y := append(s, 12)
fmt.Println(s, x, y)
复制代码

这个题目非常的有意思,以下是我的个人理解:

  1. 首先定义切片s:[5]len:1cap: 1sP: 0x001...
  2. append操作向切片s中添加一项7,此时len 2 > cap 1,触发扩容,产生新的数组,切片s:[5 7]len:2cap: 2sP: 0x002...
  3. append操作向切片s中添加一项9,此时len 3 > cap 2,触发扩容,产生新的数组,切片s:[5 7 9]len:3cap: 4sP: 0x003...
  4. 定义切片xappend操作向切片s中添加一项11,此时len 4 == cap 4,未触发扩容,不产生新的数组,切片s:[5 7 9]len:3cap: 4sP: 0x003...。 切片x:[5 7 9 11]len:4cap: 4sP: 0x003...
  5. 定义切片yappend操作向切片s中添加一项12,此时len 4 == cap 4,未触发扩容,不产生新的数组,切片s:[5 7 9]len:3cap: 4sP: 0x003...。 切片x:[5 7 9 12]len:4cap: 4sP: 0x003...重点:此时!len:4 cap4 sp:0x003变成了[5 7 9 12],所以切片x与切片y都是[5 7 9 12]

演变一下:

s := []int{5} // s[5] slen:1 scap:1 sp:0x001
s = append(s, 7) // s[5 7] slen:2 scap:2 sp: 0x002
s = append(s, 9) // s[5 7 9] slen:3 scap:4 sp: 0x003
s = append(s, 11) // s[5 7 9 11] slen: 4 scap:4 sp: 0x003
x := append(s, 13) // s[5 7 9 11] slen:4 scap:4 scp: 0x003; x[5 7 9 11 13] xlen:5 xcap:8 xp: 0x101
y := append(s, 15) // s[5 7 9 11] slen:4 scap:4 scp: 0x003; y[5 7 8 11 15] ylen: 4 ycap:8 yp: 0x102
复制代码

总结:切片是切片,数组是数组,切片是数组一个片段的描述,决定性因素有三个:长度容量指针,其中:

  1. 长度指的是切片引用的数目
  2. 容量指的是底层数组的元素数目
  3. 指针指的是切片指向哪一个底层数组 对切片的操作还有一个点:
  4. 某次操作未超过该切片的cap,此次会更新改变原数组
  5. 某次操作超过了该切片的cap,此次操作会新建数组,不会改变原数组

map

map是一种无序的基于key-value的数据结构,类似JavaScript中的object,是引用类型,必须初始化才能使用。

map的定义

语法如下:

map[键的类型]值的类型
复制代码

map类型的变量初始值为nil,需要使用make()函数为其分配内存,语法如下:

make(map[键的类型]值的类型,[cap])
复制代码

其中cap是map的容量,不必传。

map的基本使用

🌰

m1 := make(map[string]int, 8)
m1["李四"] = 12
m1["张三"] = 29
fmt.Println(m1, reflect.TypeOf(m1)) // [张三:12 李四:29] map[string]int
复制代码

map也支持在声明时填充元素,举个🌰

var m1 = map[string]string{
    "username": "HoMeTown",
    "passowrd": "123456",
}
fmt.Println(m1)
复制代码

判断某个键是否存在

Go语言中判断map中是否存在某个键的写法比较特殊,不像JavaScript或者python中可以使用in关键字来判断,写法如下:

value, ok = map[key]
复制代码

举个🌰

var m1 = map[string]string{
    "username": "admin",
    "password": "123456",
}
var v,ok = m1["username"]
if ok {
    fmt.Println(v)
} else {
    fmt.Println("不存在username")
}
复制代码

map的遍历

使用for range遍历map,写法如下:

var m1 = map[string]string{
    "username": "admin",
    "password": "123456",
    "gender": "0",
    "email": "admin123@gmail.com",
}
for key,val := range m1 {
    fmt.Println(key,val)
}
复制代码

只遍历key的话,可以这样:

for key := range m1 {
    fmt.Println(key)
}
复制代码

delete()函数删除键值对

格式如下:

delete(map, key)
复制代码

举个🌰

var m1 = map[string]string{
    "username": "admin",
    "password": "123456",
    "gender": "0",
    "email": "admin123@gmail.com",
}
fmt.Println(m1)
delete(m1, "email")
fmt.Println(m1)
复制代码

元素为map类型的切片

var mapS1 = make([]map[string]string,3)//make一个元素为map的切片
mapS1[0] = make(map[string]string,10)
mapS1[0]["name"] = "小王"
mapS1[0]["age"] = "12"
mapS1[0]["address"] = "北京市"
fmt.Println(mapS1)
复制代码


目录
相关文章
|
10天前
|
存储 JSON 监控
Viper,一个Go语言配置管理神器!
Viper 是一个功能强大的 Go 语言配置管理库,支持从多种来源读取配置,包括文件、环境变量、远程配置中心等。本文详细介绍了 Viper 的核心特性和使用方法,包括从本地 YAML 文件和 Consul 远程配置中心读取配置的示例。Viper 的多来源配置、动态配置和轻松集成特性使其成为管理复杂应用配置的理想选择。
30 2
|
8天前
|
Go 索引
go语言中的循环语句
【11月更文挑战第4天】
19 2
|
8天前
|
Go C++
go语言中的条件语句
【11月更文挑战第4天】
21 2
|
3天前
|
Go API 数据库
Go 语言中常用的 ORM 框架,如 GORM、XORM 和 BeeORM,分析了它们的特点、优势及不足,并从功能特性、性能表现、易用性和社区活跃度等方面进行了比较,旨在帮助开发者根据项目需求选择合适的 ORM 框架。
本文介绍了 Go 语言中常用的 ORM 框架,如 GORM、XORM 和 BeeORM,分析了它们的特点、优势及不足,并从功能特性、性能表现、易用性和社区活跃度等方面进行了比较,旨在帮助开发者根据项目需求选择合适的 ORM 框架。
16 4
|
3天前
|
缓存 监控 前端开发
在 Go 语言中实现 WebSocket 实时通信的应用,包括 WebSocket 的简介、Go 语言的优势、基本实现步骤、应用案例、注意事项及性能优化策略,旨在帮助开发者构建高效稳定的实时通信系统
本文深入探讨了在 Go 语言中实现 WebSocket 实时通信的应用,包括 WebSocket 的简介、Go 语言的优势、基本实现步骤、应用案例、注意事项及性能优化策略,旨在帮助开发者构建高效稳定的实时通信系统。
27 1
|
6天前
|
Go
go语言中的continue 语句
go语言中的continue 语句
16 3
|
7天前
|
安全 Go 调度
探索Go语言的并发模型:goroutine与channel
在这个快节奏的技术世界中,Go语言以其简洁的并发模型脱颖而出。本文将带你深入了解Go语言的goroutine和channel,这两个核心特性如何协同工作,以实现高效、简洁的并发编程。
|
8天前
|
Go
go语言中的 跳转语句
【11月更文挑战第4天】
17 4
|
8天前
|
JSON 安全 Go
Go语言中使用JWT鉴权、Token刷新完整示例,拿去直接用!
本文介绍了如何在 Go 语言中使用 Gin 框架实现 JWT 用户认证和安全保护。JWT(JSON Web Token)是一种轻量、高效的认证与授权解决方案,特别适合微服务架构。文章详细讲解了 JWT 的基本概念、结构以及如何在 Gin 中生成、解析和刷新 JWT。通过示例代码,展示了如何在实际项目中应用 JWT,确保用户身份验证和数据安全。完整代码可在 GitHub 仓库中查看。
40 1
|
1天前
|
存储 Go PHP
Go语言中的加解密利器:go-crypto库全解析
在软件开发中,数据安全和隐私保护至关重要。`go-crypto` 是一个专为 Golang 设计的加密解密工具库,支持 AES 和 RSA 等加密算法,帮助开发者轻松实现数据的加密和解密,保障数据传输和存储的安全性。本文将详细介绍 `go-crypto` 的安装、特性及应用实例。
11 0
下一篇
无影云桌面