Golang_02_使用基础


目录:

基础功能实现

从控制台接受用户信息

功能同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

注意细节:

  1. case后面跟一个表达式(常量值、变量、有返回值的函数均可)
  2. case后面的各个表达式的值的数据类型,必须和switch的表达式数据类型一致
  3. case后面可以有多个表达式,用,进行分隔
  4. case后面的表达式如果是常量值(字面量),则要求不能重复
  5. default不是必须的
  6. switch后面可以不带表达式,类似if else分支来使用
  7. switch也可以直接声明/定义一个变量
  8. 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

  1. goto语句可以无条件地转移到程序中指定地行;
  2. goto语句通常条件语句配合使用。可用来实现条件转移,跳出循环体等功能;
  3. 不主张使用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的位置。

数组在内存中的分布⭐

  1. 数组的地址可以通过数组名来获取&intArr
  2. 当数组被定义出来,都存在默认值
  3. 数组的index[0]的地址就是数组的首地址
  4. index[n]的地址是index[0]+[数据类型所占用的字节数] * n
  5. 例如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) 
}

使用细节

  1. 数组时多个相同类型的组合,数组一旦声明/定义,其长度时固定的,不能动态变化
  2. 数组中的元素可以是任何数据类型,包括值类型和引用类型;
  3. 数组创建后,有默认值;
  4. 如果一个数组的元素类型可以相互比较,那么数据类型也可以相互比较,只有两个数据的所欲呕元素都相等的时候数组才是相等的
  5. 使用数组的步骤
  6. 声明数组并开辟空间
  7. 给数组各个元素赋值
  8. 使用数组
  9. 数组的索引从0开始
  10. 数组索引必须在指定范围内使用,否则报panic;数组越界,比如var arr [5]int 则有效索引为0-4
  11. Go的数组属于值类型,默认情况下是值传递,因此会进行值拷贝。数组间不会互相影响

  1. 如想在其他函数中,去修改原来的数组,可以使用引用传递(指针方式)
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) 
}

注意事项及细节说明

  1. cap属于内置函数,用于统计切片的容量;
  2. 切片定义完毕后,需要先引用到数组或make一个空间供切片来使用;
  3. 切片可以继续切片;
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]
  1. 通过append对切片进行动态添加(append操作的底层原理分析)
  2. 切片append操作的本质是对数组扩容;
  3. Go底层会创建一个新的数组newArr(安装扩容后大小)
  4. 将slice原来包含的元素拷贝到新的数组newArr
  5. selice重新引用到newArr;
slice03 := []int {1, 2, 3, 4, 5} 
slice04 := make([]int, 10) 
copy(slice03, slice04)
  1. 可以通过copy内置函数完成切片的拷贝(数组无法使用copy)
  2. 各切片互相独立
  3. 只有切片能使用copy函数
  4. 当被拷贝的数据长度不足时,只获取能获取的值
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

  1. string底层是byte数组,所以string能进行切片处理;
  2. string是不可变的,不能通过str[0]='z'的方式来修改字符串;
  3. 如果需要修改字符串,需要转成[]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]slicemapfunction不可以作为[keytype],因为无法用==做判断。常用intstring
  • 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'    
    } 
}

细节说明:

  1. 若指定key存在,findRes返回'true',否则返回'false';
  2. map的CRUD,增加和修改只需要重新定义,删除则需要调用内置函数delete()来实现;
  3. 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)    
    } 
}

使用细节

  1. map是引用类型,遵循引用类型传递的机制(在一个函数接受map,修改后,会直接修改原来的map)
  2. 即使达到map指定的最大容量,也能通过定义(切片则需要使用append)的方式来增加元素,说明map能实现动态增长键值对
  3. map的value经常使用struct类型(而不推荐用map[int]map[string]string实现),更适合管理复杂的数据;

案例

  1. 使用map[string]map[string]string的数据类型
  2. key:表示用户名,是唯一的,不可重复
  3. 如果某个用户名存在,就将其密码修改为"888888";如果不存在就增加改用户,包括昵称nickname和密码pwd
  4. 编写一个函数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)) 
}

查找

  1. 顺序查找
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("没找到")    
    } 
}
  1. 二分查找(该数据是有序)

// 使用递归的精髓:处理整件事情 = 处理一部分 + 按照相同的办法处理剩余的部分 
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) 
}