基础功能实现
从控制台接受用户信息
功能同Python的input函数,但在Go中是通过fmt.Scanln或fmt.Scanf来实现
func main(){
var name string
var age byte
fmt.Println("please input name,age")
fmt.Scanf("%s %d",&name,&age)
fmt.Printf("your name = %v ,age = %v",name,age) // daihaorui 22
}
流程控制
分支控制if
// 有如下细节需要注意
// 1. 不推荐将判断括起来
// 2. 两个判断语句中用逻辑运算符进行连接
// 3. else不能换行
func main(){
if [条件] {
[动作]
}else if [条件] {
[动作]
}else {
[动作]
}
}
分支控制switch
注意细节:
- case后面跟一个表达式(常量值、变量、有返回值的函数均可)
- case后面的各个表达式的值的数据类型,必须和switch的表达式数据类型一致
- case后面可以有多个表达式,用,进行分隔
- case后面的表达式如果是常量值(字面量),则要求不能重复
- default不是必须的
- switch后面可以不带表达式,类似if else分支来使用
- switch也可以直接声明/定义一个变量
- switch穿透-fallthrough。如果在case语句后添加fallthrough,则会继续执行下一个case(默认break)
func main() {
var grade string = "B"
var marks int = 90
switch marks {
case 90: grade = "A"
case 80: grade = "B"
case 50,60,70 : grade = "C"
default: grade = "D"
}
switch {
case grade == "A" :
fmt.Printf("优秀!\n" )
case grade == "B", grade == "C" :
fmt.Printf("良好\n" )
case grade == "D" :
fmt.Printf("及格\n" )
case grade == "F":
fmt.Printf("不及格\n" )
default:
fmt.Printf("差\n" )
}
fmt.Printf("你的等级是 %s\n", grade )
}
循环控制for
for init; condition; post { } // 方式一,常用
for condition { } // 方式二,类似于pthon的while循环
for { } // 方式三,类似于python的while循环
init: 一般为赋值表达式,给控制变量赋初值;
condition: 关系表达式或逻辑表达式,循环控制条件;
post: 一般为赋值表达式,给控制变量增量或减量。
for语句执行过程如下:
①先对表达式 init 赋初值;
②判别赋值表达式 init 是否满足给定 condition 条件,若其值为真,满足循环条件,则执行循环体内语句,然后执行 post,进入第二次循环,再判别 condition;否则判断 condition 的值为假,不满足条件,就终止for循环,执行循环体外语句。
字符串遍历for range
// 传统遍历方式
var str string = "hello,world!"
for i := 0 ; i < len(str) ; i++ {
fmt.Printf("%c\n", str[i])
} // 如果出现中文,会出现乱码。传统遍历是按照字节来遍历,而汉字在ut8编码对应3个字节(可以通过切片来实现)
// for range遍历方式,默认使用字符的方式遍历
var str1 string = "hello,world!"
for index, val := range str1 {
fmt.Printf("%d,%c",index,val)
}
通过for实现类似while循环
[循环变量初始化]
for {
if [循环条件表达式] {
break // 跳出循环
}
[循环操作]
[循环变量迭代]
}
跳转控制
break & continue
break用于终止某个语句块的执行,用于中断当前for循环或跳出switch循环。
当出现在多层嵌套的语句块中时,可以通过标签指明要终止的是哪一层语句块:
label1:
for i := 0 ; i < 4 ; i++ {
// label2:
for j := 0 ; j < 10 ;j++ {
if j == 2 {
break label1 // 跳出指定循环,默认跳出最近的循环
// continue
}
fmt.Println("j=",j)
}
}
goto
- goto语句可以无条件地转移到程序中指定地行;
- goto语句通常条件语句配合使用。可用来实现条件转移,跳出循环体等功能;
- 不主张使用goto语句,以免造成程序流程的混乱,使理解和调试程序都产生困难;
fmt.Println("1")
goto label3
fmt.Println("2")
fmt.Println("3")
fmt.Println("4")
label3:
fmt.Println("5") // 只输出1 5
return
用来跳出所在的方法或函数,返回数据。若返回列表有需要有多个返回数据,单个retuen可同时返回
复杂数据类型
数组Array
数组可以存放多个同一数据类型。数组也是数据类型,在Go中,属于值类型。数组的每个元素都可以通过索引下标来访问,索引下标的范围是从0开始到数组长度减1的位置。
数组在内存中的分布⭐
- 数组的地址可以通过数组名来获取&intArr
- 当数组被定义出来,都存在默认值
- 数组的index[0]的地址就是数组的首地址
- index[n]的地址是index[0]+[数据类型所占用的字节数] * n
- 例如int64占8个字节(int32占4个字节),当index[0]的地址为0xc000014170时,index[1]的地址为0xc000014178
// 格式: var [数组名] [数组大小] [数组类型]
// 赋初值: [数组名]\[索引\] = [实际的值]
var hens [3]float64
hens[0] = 3.0
hens[1] = 13.0
hens[2] = 23.0
var total float64 = 0.0
for i := 0 ; i < len(hens) ; i++ {
total += hens[i]
}
fmt.Printf("%v,%v",total,total/float64(len(hens)))
初始化数组的四种方式
var numArray01 [3]int = [3]int {1,2,3} // 在Go中,[3]int 和 [4]int 不是一个数据类型,长度是数据类型的一部分
var numArray02 = [3]int {1,2,3}
var numArray03 = [...]int {1,2,3,24,5}
var numArray04 = [...]int {1 : 800, 0 : 100 , 2 : 12318}
strArray05 := [...]string {1 : "tom" , 0 : "haorui"} fmt.Printf("numArray01=%v \n",numArray01) // numArray01=[1 2 3]
fmt.Printf("numArray02=%v \n",numArray02) // numArray02=[1 2 3]
fmt.Printf("numArray03=%v \n",numArray03) // numArray03=[1 2 3 24 5]
fmt.Printf("numArray04=%v \n",numArray04) // numArray04=[100 800 12318]
fmt.Printf("numArray05=%v \n",strArray05) // numArray05=[haorui tom]
数组遍历(for-range结构遍历)
for index, value := range array01 {} // 数组遍历标准格式
// index是数组下标;value是该下标位置的值;仅在for循环内部可见的局部变量;不想使用index下标时可以使用'_'代替;index和value名称可自行指定
for index, value := range numArray01 {
fmt.Printf("%d:%v\n",index,value)
}
使用细节
- 数组时多个相同类型的组合,数组一旦声明/定义,其长度时固定的,不能动态变化;
- 数组中的元素可以是任何数据类型,包括值类型和引用类型;
- 数组创建后,有默认值;
- 如果一个数组的元素类型可以相互比较,那么数据类型也可以相互比较,只有两个数据的所欲呕元素都相等的时候数组才是相等的
- 使用数组的步骤
- 声明数组并开辟空间
- 给数组各个元素赋值
- 使用数组
- 数组的索引从0开始
- 数组索引必须在指定范围内使用,否则报panic;数组越界,比如var arr [5]int 则有效索引为0-4
- Go的数组属于值类型,默认情况下是值传递,因此会进行值拷贝。数组间不会互相影响;
- 如想在其他函数中,去修改原来的数组,可以使用引用传递(指针方式)
func test01(arr [3]int){ // 值传递
arr[0] = 88
fmt.Printf("%v \n",arr) // [88 22 33]
}
func test02(arr *[3]int){ // 指针传递
(*arr)[0] = 88
fmt.Printf("%v \n",*arr) // [88 22 33]
}
func main() {
arr := [3]int {11, 22, 33}
test01(arr)
fmt.Printf("%v \n",arr) // [11 22 33]
test02(&arr)
fmt.Printf("%v \n",arr) // [88 22 33]
}
案例
func main() {
// 创建byte类型的26个元素的数组,放置A-Z,for循环访问打印所有元素
var mychars [26]byte
for i := 0 ; i < 26 ; i++ {
mychars[i] = 'A' + byte(i)
}
for index, value := range mychars{
fmt.Printf("mychars[%d]=%c\t", index, value)
}
// 求出一个数组的最大值,并得到下标
fmt.Println()
intArr := [...]int {1, -1 , 9, 90, 11}
maxVal := intArr[0]
maxValIndex := 0
for i := 0 ; i < len(intArr) ; i++ {
if maxVal < intArr[i] {
maxVal = intArr[i]
maxValIndex = i
}
}
fmt.Printf("maxValIndex=%d,maxVal=%v",maxValIndex,maxVal)
// 求出一个数组的和和平均值,使用for range方式
fmt.Println()
intArr1 := [...]float64 {1, -1 , 9, 91, 11}
sum := 0.00
avg := 0.00
for _ , value := range intArr1 {
sum += value
}
avg = sum / float64(len(intArr1))
fmt.Printf("该数组的和=%v,平均值=%v",sum,avg)
}
// 随机生成5个数,并将其反转打印
fmt.Println()
rand.Seed(time.Now().UnixNano()) // 给rand.Intn的随机因子
var intArr2 [5]int
var intArr3 [5]int
for i := 0 ; i < len(intArr2) ; i++ {
intArr2[i] = rand.Intn(100)
}
fmt.Printf("反转前=%v \n",intArr2)
for i := 0 ; i < len(intArr2) ; i++ {
intArr3[i] = intArr2[len(intArr2) - i -1]
}
fmt.Printf("反转后=%v \n",intArr3)
二维数组
在内存中的布局
遍历
func main() {
var arr [2][3]int = [2][3]int{{1, 2, 3}, {4, 5, 6}}
for i := 0; i < len(arr); i++ { // 传统方法遍历
for j := 0; j < len(arr[i]); j++ {
fmt.Printf("%v\t", arr[i][j])
}
fmt.Println()
}
for index, _ := range arr { // for range遍历
for _, value1 := range arr[index] {
fmt.Printf("%v\t", value1)
}
fmt.Println()
}
}
// 输出结果均为:
1 2 3
4 5 6
案例
计算每个班级的平均数及所有班级的平均数
func main() {
var arr [3][5]float64 = [3][5]float64{{10, 20, 30, 50, 60}, {40, 50, 60, 30, 50,}, {70, 80, 90, 30, 50,}}
totalSum := 0.0
totalCount := 0.0
for index, _ := range arr {
sum := 0.0
Count := 0.0
for _, value1 := range arr[index] {
sum += value1
Count++
totalCount++
}
fmt.Printf("班级%v的平均分=%v\t",index + 1,sum / Count)
totalSum +=sum
}
fmt.Printf("所有班级的平均分=%v\t",totalSum / totalCount)
}
切片slice
切片是数组的一个引用,在传递时,遵守引用传递的机制;切片的使用与数组类似;切片可以认为是动态变化数组。
// 格式: var [切片名] [] [数据类型]
func main() {
intArr := [5]int {1, 2, 3, 4, 6}
slice := intArr[1:3] // 取首不取尾,slice引用到intArr这个数组
fmt.Println("intArr=",intArr)
fmt.Println("slice的元素=",slice)
fmt.Println("slice的数量=",len(slice)) //长度,已有的数据
fmt.Println("slice的容量=",cap(slice)) // 容量,随着元素增加自动增长
}
切片在内存中的分布⭐
切片使用的三种方式
func main() {
slice01 := make([]int, 4, 10) // 第一种,直接用make来定义。make会创建一个数组,由切片在底层进行维护,对外不可见
intArr := [5]int {1, 2, 3, 4, 6}
slice02 := intArr[1:3] // 第二种,根据已有数组来引用,并能指向指定索引
slice03 := []int {1, 3, 5} // 第三种,通过创建数组来创建切片再来引用
fmt.Println("slice的元素=",slice)
}
切片的遍历
for i := 0 ; i < len(slice03) ; i++ {
fmt.Printf("slice03[%d]=%v\n",i, slice03[i])
}
for index, value := range slice03 {
fmt.Printf("slice03[%d]=%v\n",index, value)
}
注意事项及细节说明
- cap属于内置函数,用于统计切片的容量;
- 切片定义完毕后,需要先引用到数组或make一个空间供切片来使用;
- 切片可以继续切片;
arr := [...]int {1, 2, 3, 4, 5}
slice01 := arr[1:4]
slice02 := slice01[1:3]
fmt.Printf("slice01=%v \n",slice01) // [2 3 4]
fmt.Printf("slice02=%v \n",slice02) // [3 4]
- 通过append对切片进行动态添加(append操作的底层原理分析)
- 切片append操作的本质是对数组扩容;
- Go底层会创建一个新的数组newArr(安装扩容后大小)
- 将slice原来包含的元素拷贝到新的数组newArr
- selice重新引用到newArr;
slice03 := []int {1, 2, 3, 4, 5}
slice04 := make([]int, 10)
copy(slice03, slice04)
- 可以通过copy内置函数完成切片的拷贝(数组无法使用copy)
- 各切片互相独立
- 只有切片能使用copy函数
- 当被拷贝的数据长度不足时,只获取能获取的值
slice01 := []int {6, 7, 8, 9, 10} // 创建含5个数据的切片
slice02 := make([]int, 2) // 创建一个最大容量为2的空切片
copy(slice02,slice01) // 将slice01的数据copy给slice02,copy函数只能给切片使用
fmt.Printf("slice01=%v \n",slice01) // slice01=[6 7 8 9 10]
fmt.Printf("slice02=%v \n",slice02) // slice02=[6 7] slice02 =
append(slice02, slice01...) // 将slice02的数据追加到slice01上,可以直接追加
int类型 fmt.Printf("slice02=%v \n",slice02) // slice02=[6 7 6 7 8 9 10]
string与slice
- string底层是byte数组,所以string能进行切片处理;
- string是不可变的,不能通过str[0]='z'的方式来修改字符串;
- 如果需要修改字符串,需要转成[]byte数组或[]rune修改后重新转成string
str := "daihaorui@wlhiot.com"
slice05 := str[9:]
fmt.Printf("slice05=%v \n",slice05)
arr1 := []byte(str) // 只能修改英语或数字,因为只修改了一个字节
arr2 := []rune(str) // modify chinaese
arr1[0] = 'z'
str = string(arr1)
fmt.Printf("str=%v \n",str)
map
map是key-value数据结构,类似Pyhton的字典
基本语法:var [map名称] map[keytype]valuetype
,例如var a map[string]map[string]string
[keytype]
:slice
、map
和function
不可以作为[keytype],因为无法用==
做判断。常用int
或string
valuetype
:通常为数字、string、map、struct
声明map不会分配内存,初始化需要make,分配内存后才能赋值和使用
var a map[string]string // 声明变量,方式一
a = make(map[string]string, 1) // 开辟内存空间
b := make(map[string]string, 1) // 类型推导,方式二
var c map[string]string = map[string]string{"no5" : "成都",} // 方式三
使用
func main() {
var id map[int]map[string]string // 声明变量
id = make(map[int]map[string]string, 10) // 为id开辟内存空间
id[1] = make(map[string]string,10) // 为id[1]开辟内存空间
id[1]["name"] = "tom"
id[1]["sex"] = "男"
fmt.Println(id[1]["sex"]) // 男
fmt.Println(id) // map[1:map[name:tom sex:男]]
delete(id[1],"name") // 删除指定map中的map中的指定key
fmt.Println(id) // map[1:map[sex:男]]
val, findRes := id[14]
if findRes {
fmt.Printf("找到了val=%v且findRes=%v\n",val, findRes) // id[1]时,输出'找到了val=map[sex:男]且findRes=true'
} else {
fmt.Println("没有找到findRes=",findRes)// id[14]时,输出'没有找到findRes= false'
}
}
细节说明:
- 若指定key存在,findRes返回'true',否则返回'false';
- map的CRUD,增加和修改只需要重新定义,删除则需要调用内置函数delete()来实现;
- Go没有提供删除所有key的函数,若要删除需要手动for循环来实现;或者重新map=make(...)创建一个新map,让原来的map成为垃圾,由gc回收;
遍历
map的遍历使用for range的形式,因为下标不一定是数字。
for key, value := range id {
fmt.Printf("key=%v\n", key)
for key1, value1 := range value {
fmt.Printf("\tkey=%v,value=%v\n", key1, value1)
}
} // 输出如下:
key=1
key=name,value=tom
key=sex,value=男
key=2
key=sex,value=女
key=name,value=haorui
⭐切片
func main() {
var monsters []map[string]string // 1. 这边创建的是一个map类型的切片
monsters = make([]map[string]string, 2) // 2. 为切片分配内存空间
if monsters[0] == nil {
monsters[0] = make(map[string]string, 3) // 3. 为map分配内存空间
monsters[0]["name"] = "牛魔王"
monsters[0]["age"] = "500"
}
var newMonster map[string]string // 4. 创建map,类型同以上
newMonster = map[string]string { // 5. 为map赋予指定值,问题:为啥不需要make来分配内存空间?
"name" : "新的妖怪",
"age" : "200",
}
newMonster["sex"] = "男" // 为newMonster添加新的值
monsters = append(monsters, newMonster) // 对于切片来说,只能通过append函数实现动态增加
fmt.Println(monsters) // 输出'[map[age:500 name:牛魔王] map[] map[age:200 name:新的妖怪 sex:男]]'
fmt.Println(newMonster) // 输出'map[age:200 name:新的妖怪 sex:男]'
fmt.Println(newMonster["name"]) // 输出'新的妖怪'
}
排序
Go中的map默认是无序的(新版本为有序),每次遍历得到的key都可能不一样。以下代码可验证:
func main() {
var map1 map[int]int
map1 = make(map[int]int, 2)
map1[1] = 100
map1[41] = 23
map1[2] = 11
for key, value := range map1{
fmt.Printf("key=%v,value=%v",key,value)
}
}
使用细节
- map是引用类型,遵循引用类型传递的机制(在一个函数接受map,修改后,会直接修改原来的map)
- 即使达到map指定的最大容量,也能通过定义(切片则需要使用append)的方式来增加元素,说明map能实现动态增长键值对;
- map的value经常使用struct类型(而不推荐用map[int]map[string]string实现),更适合管理复杂的数据;
案例
- 使用map[string]map[string]string的数据类型
- key:表示用户名,是唯一的,不可重复
- 如果某个用户名存在,就将其密码修改为"888888";如果不存在就增加改用户,包括昵称nickname和密码pwd
- 编写一个函数modifyUser(users map[string]map[string]string, name string)完成上述功能
主要是考察map的make使用(每个map都需要手动开辟内存空间)
func modifyUser(users map[string]map[string]string, name string) {
if users[name] != nil {
users[name]["pwd"] = "888888"
} else {
users[name] = make(map[string]string, 2)
users[name]["pwd"] = "888888"
users[name]["nickname"] = "!" + name
}
}
func main() {
var stu map[string]map[string]string
stu = make(map[string]map[string]string, 5)
stu["haorui"] = make(map[string]string)
stu["haorui"]["pwd"] = "999999"
stu["haorui"]["nickname"] = "小戴"
modifyUser(stu, "haorui")
modifyUser(stu, "kaiyi")
fmt.Println(stu) // 输出"map[haorui:map[nickname:小戴 pwd:888888] kaiyi:map[nickname:!kaiyi pwd:888888]]"
}
排序和查找
冒泡排序算法:
func BubbleSort(slice []int) []int {
for i := 0; i < len(slice)-1; i++ {
for k := 0; k < len(slice)-i-1; k++ {
if slice[k] > slice[k+1] {
slice[k] += slice[k+1]
slice[k+1] = slice[k] - slice[k+1]
slice[k] = slice[k] - slice[k+1]
}
}
}
return slice
}
func main() {
slice := []int{23, 49, 79, 24, 11, 21}
fmt.Println(BubbleSort(slice))
}
查找
- 顺序查找
Array := [...]string{"白", "金", "紫", "青"}
var hero string = ""
fmt.Println("查找")
fmt.Scanln(&hero)
for i := 0 ; i < 4 ; i++ {
if Array[i] == hero {
fmt.Println("找到了") break
} else if i == len(Array) -1 {
fmt.Println("没找到")
}
}
- 二分查找(该数据是有序)
// 使用递归的精髓:处理整件事情 = 处理一部分 + 按照相同的办法处理剩余的部分
func find(slice []int, leftindex int, rightindex int, num int) {
if leftindex > rightindex {
fmt.Println("找不到")
return
}
middle := (leftindex + rightindex) / 2
if slice[middle] > num {
find(slice, leftindex, rightindex - 1, num)
}else if slice[middle] < num {
find(slice, middle + 1, rightindex, num)
}else {
fmt.Printf("index=%v",middle)
}
}
func main() {
Array1 := []int {1, 8, 10, 89, 1000, 1234, 2345}
find(Array1, 0, 6, 2)
}