Go 语言入门指南:基础语法和常用特性解析(上)|青训营

100 阅读11分钟

Go语言

根据老师的视频进行课后的一个基础语法学习,从零开始,简单做了一些笔记,这里是一些针对算法可以运用的语法,后面的几点语法会在下一篇文章,当然这个笔记现在还比较捡漏,在后续的运用中应该还会有一些补充~

Go安装

go下载链接:studygolang.com/dl

再自己下个VS code,里面下一个go的扩展就好了,弄好之后可以试一下终端输入 go version,有版本号就代表安装成功了。

然后你可以配置一下环境

新建文件夹 goprogects 用于存放工程代码(可创建在任意磁盘),并在该文件夹下创建 bin、pkg、src 这三个文件夹,其中 bin 文件夹用于存放在编译项目时,生成的可执行文件,pkg文件夹用于存放在编译项目时,生成的包文件,src 文件夹用于存放编写的代码。

image-20230725152643434.png

在系统变量中新建(GO111MODULE, on)、(GOBIN, 上一步新建的bin地址,E:\goprojects\bin)、(GOPATH, E:\goprojects)、(GOPROXY, goproxy.cn)、(GOROOT, 你安装go时候的那个目录)这5个变量,到此环境变量配置完成。其中 GOPATH 和 GOBIN 都是新建的用于存放代码的 goprogects 目录, GOROOT 是 GO 编译器安装的目录。 1、在D:\goprojects\src目录下新建文件夹命名为1,用于存放第一个go程序。

image-20230725152651601.png

打开vscode,打开新建的 1文件夹中,新建一个 go 文件,命名为 hello.go ,之后在这个 go 文件中输入以下代码,然后按 Ctrl + s 保存代码,并运行。

package main  //入口文件

import(  //导入fmt包
	"fmt"
)

func main(){
	fmt.Println("hello world")
}

输出:

image-20230725152752277.png

Go语言基础语法

1.1什么是Go语言

1.高性能、高并发

2.语法简单、学习曲线平缓

3.丰富的标准库

4.完善的工具链

5.静态链接

6.快速编译

7.跨平台

8.垃圾回收

package main

import(
	"net/http"
)

func main(){
	http.Handle("/",http.FileServer(http.Dir(".")))  //在http里面新建一个路由
	http.ListenAndServe(":8080",nil)  //增添8080端口,启动服务器
}

1.2基础语法

1.变量

初始化:

var 变量名字 类型 = 表达式

零值初始化机制:。如果初始化表达式被省略,那么将用零值初始化该变

量。

名字:= 表达式

调用用内建的new函数。表达式new(T)将创建一个T类型的匿名变量,初始化为T类型的零值,然后返回变量地址,返回的指针类型为 *T

p := new(int) // p, *int 类型, 指向匿名的 int 变量

fmt.Println(*p) // "0"

*p = 2 // 设置 int 匿名变量的值为 2

fmt.Println(*p) // "2"
// 产生1~100的随机数:
package main
import "fmt"
import "math/rand"
import "time"
 
func main() {
	// 设置随机数种子,time.Now().Unix():返回的1970:0:0到现在的秒数
	rand.Seed(time.Now().Unix())
	//生成1~100的随机数
	n := rand.Intn(100) + 1
	fmt.Println(n)
}

多个变量的一次性定义

// golang一次定义多个变量的三种方式:
var i1, j1, k1 int
i1, j1, k1 = 10, 10, 10
var i2, j2, k2 = 20, 20.0, "str2"
i3, j3, k3 := 20, 30.0, "str3"
fmt.Println("i1 =", i1, "j1 =", j1, "k1 =", k1)
fmt.Println("i2 =", i2, "j2 =", j2, "k2 =", k2)
fmt.Println("i3 =", i3, "j3 =", j3, "k3 =", k3)

变量的作用域

  • 局部变量:函数内部声明和定义、使用;
  • 全局变量:函数外声明和定义、使用,该变量在整个包中有效(如果首字母大写,则在整个程序中的其他包中也是有效);
  • 当全局变量和局部变量同名时,会采用“就近原则”;
// 全局变量的定义时的注意事项:
var n1 int = 10   // 正确
n2 := 10          // 报错,因为该语句等价于 var n2 int   n2 = 10,故报错发生了(第二条语句,不能在函数外进行赋值操作)

基本数据类型: 数值型(整型(int、int8(代表8位的整型)、int16、int 32、int64、uint、uint8、uint16、uint32、uint64、byte)、浮点型(float32、float64))、字符型(没有专门的字符型,使用byte来保存单个字符)、布尔型、字符串(go将string归为基本数据类型)

派生/复杂数据类型: 指针Pointer、数组、结构体struct、管道Channel、函数(也是一种数据类型)、切片slice、接口interface、map

2.3.3 基本数据类型的默认值(又称为零值)

数据类型默认值
整型0
浮点型0
字符串"""
布尔类型false

注意:

  1. 格式化输出fmt.printf()中,%v表示按照变量的值输出,%T表示输出变量的类型;
  2. golang中查看变量占用的字节数,可以调用unsafe包中的Sizeof()函数;
  3. 写程序时整型变量采用 “保小不保大” 的原则;
  4. Golang中统一采用utf-8编码;
基本数据类型间的相互转换
  1. 表达式T(v),将值v转换为T类型。转换时,注意范围,防止由于溢出造成数据丢失。
  2. 被转换的是变量存储的数据,变量本身的数据类型并没有变化。
基本数据类型 与 string类型的转换:
package main
import "fmt"
 
func main() {
	var (
		num1 int = 90
		num2 float64 = 23.33
		b bool = true
		m_char byte = 'a'
		str string
	)
	
	fmt.Printf("num1 = %d num2 = %f b = %t m_char = %c\n", num1, num2, b, m_char)
	
    // 方式一:使用fmt.Sprintf(),将基本数据类型转为字符串string类型
	str = fmt.Sprintf("%d", num1)
	fmt.Printf("str type %T str=%s\n", str, str)
 
	str = fmt.Sprintf("%f", num2)
	fmt.Printf("str type %T str=%q\n", str, str)
 
	str = fmt.Sprintf("%t", b)
	fmt.Printf("str type %T str=%q\n", str, str)
 
	str = fmt.Sprintf("%c", m_char)
	fmt.Printf("str type %T str=%q\n", str, str)
	
   	// 方式二:使用strconv包中的函数
	str = strconv.FormatInt(int64(num1), 10)   // 返回num1的10进制字符串
	fmt.Printf("str type %T str=%q\n", str, str)  
	
	// ‘f’表示生成的字符串中小数的表示格式***.***;5表示小数点后保留5位小数;64表示num2是float64类型
	str = strconv.FormatFloat(num2, 'f', 5, 64)    
	fmt.Printf("str type %T str=%q\n", str, str)  
 
	str = strconv.FormatBool(b)    
	fmt.Printf("str type %T str=%q\n", str, str)  
    
    // 整型int转字符型string
	var num3 int = 3
	str = strconv.Itoa(num3)
	fmt.Printf("str type %T str=%q\n", str, str)    
}
字符串 转换成 基本数据类型:
package main
import "fmt"
import "strconv"
 
func main() {
	var (
		num1 string = "90"
		num1_ int64
		num2 string = "23.33"
		num2_ float64
		b string = "true"
		b_ bool
	)
 
	b_, _ = strconv.ParseBool(b)
	fmt.Printf("b_ type %T, b_=%t\n", b_, b_)
	
	// 10表示转成10进制,64表示转成int64
	num1_, _ = strconv.ParseInt(num1, 10, 64)
	fmt.Printf("num1_ type %T, num1_=%d\n", num1_, num1_)
 
	// 64表示转成float64
	num2_, _ = strconv.ParseFloat(num2, 64)
	fmt.Printf("num2_ type %T, num2_=%f\n", num2_, num2_)
}
2.if else

if语句检查指定的条件,并在条件满足时执行指定的操作。

Go里的条件语句模型是这样的

if 条件 1 {
    分支 1
}else if 条件 2 {
    分支 2
}else if 条件 ... {
    分支 ...
}else {
    分支 else
}

Go是强类型,要求条件表达式必须严格返回布尔型的数据(nil和0和1都不行)

3.循环

经典for循环结构中 , for关键字后面有三个表达式,且每个表达式都可以省略

for i := 0; i < 3; i++ {
	fmt.Println(i)
}
//等价于
j := 0
for ; j < 3; {
	fmt.Println(j)
	j++
}

for关键字后面也可以只有一个表达式,表示如果条件成立执行循环体代码

for i := 0; i < 3; i++ {
	fmt.Println(i)
}
//等价于
j := 0
for j < 3 {
	fmt.Println(j)
	j++
}

4.switch

switch 语句用于基于不同条件执行不同动作,每一个 case 分支都是唯一的,从上直下逐一测试,直到匹配为止。 Golang switch 分支表达式可以是任意类型,不限于常量。可省略 break,默认自动终止。

switch var1 {
    case val1:
        ...
    case val2:
        ...
    default:
        ...
}

变量 var1 可以是任何类型,而 val1 和 val2 则可以是同类型的任意值。类型不被局限于常量或整数,但必须是相同的类型;或者最终结果为相同类型的表达式。 您可以同时测试多个可能符合条件的值,使用逗号分割它们,例如:case val1, val2, val3。

5.数组
一维数组

1)数组可以用来存放多个同一类型的数据;

2)Go中,数组也是一种数据类型,且是值类型;

3)数据定义和内存分配:数组名、数组的第一个元素的地址,都等于数组的首地址;

package main 
import "fmt"
import "strconv"
 
func main() {
	var nums [6]float64   // 数组中元素是float64类型,故每个元素占8bytes
	nums[0] = 12.0
	nums[1] = 11.0
	nums[2] = 13.0
    fmt.Printf("%p %p %p\n", &nums, &nums[0], &nums[1])
    
    // 分析数组在内存中的空间分配情况:可以通过数组的首地址访问到所有变量
	total_nums_sum := 0.0
	for i := 0; i < len(nums); i++ {
		fmt.Printf("%v -> %f\t", &nums[i], nums[i])
		total_nums_sum += nums[i]
	}
	fmt.Printf("\n")
    
	avg_nums_sum_str := fmt.Sprintf("%.2f", total_nums_sum / float64(len(nums)))
	avg_nums_sum, _ := strconv.ParseFloat(avg_nums_sum_str, 64)     // 64表示转成float64
	fmt.Printf("total_nums_sum=%f\n avg_nums_sum_str=%q -> avg_nums_sum=%f", total_nums_sum, avg_nums_sum_str, avg_nums_sum)
}

4)数组的四种初始化方式;

var numsArr1 [3]int = [3]int{1, 2, 3}
var numsArr2 = [3]int{1, 2, 3}
var numsArr3 = [...]int{1, 2, 3}
var numsArr4 = [3]string{1:"jary", 0:"tom", 2:"mark"}
// var numsArr4 = [3]int{1:2,0:1,2:3}

5)数组的两种遍历方式:①下标;②for - range;

package main
import "fmt"

// 数组的两种遍历方式:1)下标;2)for - range;
func printIntArr(arr *[3]int, size int) {
	for i := 0; i < size; i++ {
		fmt.Printf("%v ", arr[i])
	}
	fmt.Printf("\n")
}
func printStrArr(arr *[3]string, size int) {
	for index, val := range arr {
		fmt.Printf("arr[%d]=%v ", index, val)
	}
	fmt.Printf("\n")
}

func main() {
	// 四种初始化数组的方式
	var arr1 [3]int = [3]int{1, 2, 3}
    var arr4 = [3]string{1:"Tom", 0:"Steven", 2:"Jarry"}      

	printIntArr(&arr1, 3)
	printStrArr(&arr4, 3)

}

数组的使用注意事项:

1)数组是多个相同类型数据的组合,一旦声明/定义,其长度是固定的,不能动态变化;

2)var arr []int,这时arr就是一个slice切片;

3)数组中的元素可以是任何数据类型,包括 值类型 和 引用类型,但不能混用;

4)数组创建后,如果没有赋值,会有默认值(零值);

5)使用步骤:①声明数组并开辟空间;②给数组各个元素赋值(默认零值);③使用数组;

6)数组属于值类型,默认情况下是值传递,因此会进行值拷贝,数组间不会相互影响;如果想用其他函数修改原数组,可以使用引用传递(指针方式);

package main
import "fmt"
 
func PrintArr(arr *[3]int, size int) {
	for i := 0; i < size; i++ {
		fmt.Printf("%d ", arr[i])
	}
	fmt.Printf("\n")
}
 
// 使用值传递
func modify1(arr [3]int) {
	arr[0] = 4
}
 
// 使用引用传递
func modify2(array *[3]int) {
    (*array)[0] = 4
	(*array)[1] = 5
}
 
func main() {
	var arr [3]int = [3]int{1, 2, 3}
	PrintArr(&arr, 3)
	modify1(arr)
	PrintArr(&arr, 3)
	modify2(&arr)
	PrintArr(&arr, 3)
}

7)长度是数组类型的一部分,在传递函数参数时,需要考虑数组的长度;

二维数组

基本语法:var arr_2D [行][列]int

package main
import "fmt"
 
func main() {
    // 方式一:先定义/声明,再赋值
	var arr_2D [3][3]int
	for i := 0; i < 3; i++ {
		for j := 0; j < 3; j++ {
			arr_2D[i][j] = i * j
		}
	}
    fmt.Println(arr_2D)
    
    // 方式二:直接初始化
    var arr_2D2 [3][3]int = [3][3]int{{1, 2, 3}, {1, 2, 3}, {1, 2, 3}}
	fmt.Println(arr_2D2)
}

二维数组的遍历

for i := 0; i < len(arr_2D2); i++ {
    for j := 0; j < len(arr_2D2[i]); j++ {
    	fmt.Printf("%d ", arr_2D2[i][j])
    }
    fmt.Printf("\n")
}
 
fmt.Printf("\n")
 
for _, address_1D := range arr_2D2 {    // address_1D是二维数组中保存的每一行的地址
    for _, val := range address_1D {
        fmt.Printf("%d ", val)
    }
    fmt.Printf("\n")
}
6.切片

1)切片的基本定义:var 变量名 []类型;

2)切片的使用和数组类似,遍历切片(for - len 或者 for index, val : range slice)、访问、求切片的长度len(slice),均与数组相同;

3)切片的内存分布:内存空间中,保存了:首地址、长度、容量

4)切片slice的三种使用方式:

  1. 定义一个切片并让切片取引用一个已经创建好的数组,如slice := arr[start:len(arr)];
  2. 用make来创建切片,可指定大小和容量,且默认是零值;
  3. 定义一个切片,直接就指定具体数组,使用原理类似make的方式;
package main 
import "fmt"
 
// 切片(可以理解为动态数组)的基本使用
// 内存中的布局:类似于结构体,有首地址、长度、容量
// 切片定义后,本身是空的;需要让其引用到一个数组 或者 make一个空间供切片使用
func main() {
    // 切片的使用:方式一:定义一个切片并让切片取引用一个已经创建好的数组
	var arr [6]int = [6]int{0, 1, 2, 3, 4, 5}
	slice1 := arr[1:4]  // slice[start:end] --> [start, end)
    // slice1 := arr[:]   // 表示切片包含了说组arr中的所有元素
	fmt.Println("arr=", arr)
	fmt.Println("slice=", slice1)
	fmt.Println("len(slice)=", len(slice1))
	fmt.Println("capability of slice is ", cap(slice1))
    
    fmt.Printf("\n")
    
    // 方式二:用make来创建切片
    // 基本语法:make(type, len, [cap])   // cap是可选的,cap >= len
    var slice2 []float64 = make([]float64, 4, 10)
    fmt.Println("slice2=", slice2)
	fmt.Println("len(slice2)=", len(slice2), "capability of slice2 is ", cap(slice2))
    
    fmt.Printf("\n")
    
    // 方式三:定义一个切片,直接就指定具体数组,使用原理类似make的方式
    var slice3 []int = []int{1, 2, 3}
    fmt.Println("slice3=", slice3)
	fmt.Println("len(slice3)=", len(slice3), "capability of slice3 is ", cap(slice3))
}

5)切片是一个数组的引用,因为切片是引用类型,故在传递数组时,需要遵守引用传递的机制;

package main
import "fmt"
 
func test1() {
    var arr [3]int = [3]int{1, 1, 3}
    slice1 := arr[:]
    var slice2 []int = slice1
    slice2[1] = 2    // 此时,slice1、slice2均指向数组arr所在的内存空间
	fmt.Println(arr, "\t", slice1, "\t", slice2)
	// 结果是arr、slice1、slice2: [1, 2, 3]
}
 
// 该处调用函数时,切片形参只是拷贝了切片实参的{指向的数组的首地址, len, cap}, 
//故本质上只是形参和实参变量本身的地址不同,其指向的数组内存空间相同
func test_slice(slice []int) {
	slice[1] = 2   // 这里修改slice指向的内存空间的数据,会改变调用该函数的切片实参
	
	// 切片作为形参时,切片只能在函数内部修改值,不能直接添加值
	slice = append(slice, 29)   // 无效
	
	fmt.Printf("%p\n", &slice)
	// 表明:slice和外部调用该函数的切片实参,指向的是同一块数组内存空间
}
 
// 切片指针作为形参时,切片即能在函数内部修改值,又能直接添加值
func test_slice_ptr(slice *[]int){
	(*slice)[1] = 1
	(*slice) = append((*slice), 4)
}
 
func test2() {
	var arr [3]int = [3]int{1, 1, 3}
	slice := arr[:]
	fmt.Printf("%p\n", slice)
	test_slice(slice)
	fmt.Println(arr, "\t", slice)
	test_slice_ptr(&slice)
	fmt.Println(arr, "\t", slice)
    // 结果是arr、slice: [1, 2, 3]
}
 
func main() {
	test1()
	test2()
}

6)切片的长度是变化的,故可以认为切片时一个动态数组,即切片可以动态增长;

7)切片的切片:slice2 = slice1[start:end],此时slice1和slice2指向的是同一个数组空间;

8)用append内置函数,可以使切片进行动态增加;

slice3 = append(slice3, 元素1, 元素2); slice3 = append(slice3, slice2...);(...是对slice2进行解构为一个个元素)

var arr []int = []int{1, 2, 3}
slice1 := arr[:]
slice2 := make([]int, 10)  // 默认值全是0
// slice1、slice2的数据空间是独立的,不会相互影响
copy(slice2, slice1)       // slice2:[1,2,3,0,0,0,0,0,0,0]
// copy(para1, para2)中,para1、para2必须都是切片类型
7.map

map是key-value的数据结构,又称字段或者关联数组。

1)基本语法:map[keytype]valuetype

keytype只能是int、string、数字、bool、指针、channel、接口、结构体、数组等 (因为slice、map、function无法用==判断,故keytype不能是slice、map、function),但通常用int、string;

valuetype通常是数字(整型、浮点型)、string、map、struct等; 2)map声明举例:

var a map[string]string
var a map[string]int
var a map[int]string
var a map[string]map[string]string等;

3)map在声明时并不会分配内存,用make初始化后,才会分配内存,然后才能进行赋值和使用;

4)golang中的map,默认是无序的状态,且没有专门的针对排序的方法;key不能重复,但value可以重复;

5)map的三种使用方式:

先声明,再make分配内存空间,再使用; 声明后直接make分配内存空间,再使用; 声明后直接赋值;

package main
import "fmt"
 
// map在声明时并不会分配内存,用make初始化后,才会分配内存,然后才能进行赋值和使用;
func main() {
	// map的三种使用方式:
	// 方式一:先声明,再make分配内存空间,再使用
	var map1 map[int]string
	map1 = make(map[int]string, 5)
 
	// 方式二:声明后直接make分配内存空间,再使用
	map2 := make(map[int]string)
	for i := 1; i <= 5; i++ {
		map2[i] = string('A' + byte(i))
	}
 
	// 方式三:声明后直接赋值
	map3 := map[int]string{"1":"A", "2":"B", "3":"C", "4":"D", "5":"E"}
 
	// map的遍历:
	for key, value := range map3 {
		fmt.Printf("map3[%d]=%q\n", key, value)
	}
	fmt.Println(map3)
}

map[string]map[string]string的使用:

package main
import "fmt"
 
func main() {
	studentMap := make(map[string]map[string]string)
 
	studentMap["01"] = make(map[string]string, 3)
	studentMap["01"]["name"] = "tom"
	studentMap["01"]["sex"] = "male"
	studentMap["01"]["address"] = "shanxi"
 
	studentMap["02"] = make(map[string]string, 3)
	studentMap["02"]["name"] = "jary"
	studentMap["02"]["sex"] = "female"
	studentMap["02"]["address"] = "shanghai"
 
	fmt.Println(studentMap)
 
	// map[string]map[string]string的遍历:
	for _, address := range studentMap {
		for key, value := range address {
			fmt.Printf("[%q]=%q ", key, value)
		}
		fmt.Printf("\n")
	}
}

6)map的增删改查操作:

map的增加和更新:map[key]=value;如果key不存在则是增加,否则则是更新。 map的删除:使用内置函数delete(map,key);如果可以存在则删除,否则不操作也不报错。 补充说明:如果要删除map中所有的key,1)可以通过遍历逐个删除;2)make一个新的map并直接赋值给map(map=make(...)),即这会原来的map成为垃圾被GC回收。

map的查找:1)val,ok=map[key]; 2)通过for-range遍历不同的key来实现查找的目的。

package main
import "fmt"
import "unsafe"
 
func SearchMap(map_ map[string]string, key_search string) {
	fmt.Printf("%p\n", &map_)
	for key, value := range map_ {
		if key == key_search {
			fmt.Printf("[%q]=%q\n", key, value)
		}
	}
}
 
// map的增、删、改、查
func main() {
	map_ := make(map[string]string, 5)
	map_["name"] = "SGY"       // 增
	map_["sex"] = "male"
	map_["address"] = "shanxi"
	map_["phone"] = "8888"
	fmt.Println(map_)
	fmt.Printf("%d\n", unsafe.Sizeof(map_))
	fmt.Printf("%d\n", unsafe.Sizeof(map_["sex"]))
 
	map_["name"] = "Guangyuan" // 改
	fmt.Println(map_)
 
	fmt.Printf("%p\n", &map_)   
	SearchMap(map_, "address") // 查 
	value, ok := map_["address"]
	if ok {
		fmt.Printf("[%q]=%q\n", "address", value)
	} else {
		fmt.Printf("[%q]不存在", "address")
	}
 
	// delete(map, key)如果key存在,则删除key-value;不存在,则不操作,也不会报错;
	delete(map_, "phone")      // 删
	fmt.Println(map_)
    fmt.Printf("map_的key-value的对数,%d\n", len(map_))
 
	// 没有专门的方法能直接删除map中的所有的key,有两种解决方法:
	// 1)只能通过遍历逐个删除;
	for key, _ := range map_ {
		delete(map_, key)
	}
	fmt.Println(map_)
    // 2)重新给map分配空间,使原来的内存空间的引用变成零,即会被GC当作垃圾回收;
	map_ = make(map[string]string)
	fmt.Println(map_)
}
map切片

切片的数据类型如果是map,则称为slice of map(map切片),这样使用map的个数就可以动态变化了。

package main
import "fmt"
import "strconv"
 
// 切片的数据类型如果是map,则称为slice of map(map切片),这样使用map的个数就可以动态变化了
func main() {
	var map_slice []map[string]string;
	map_slice = make([]map[string]string, 5)
 
	//map_ := make(map[string]string, 10)
	var map_ map[string]string
	for i := 0; i < len(map_slice); i++ {
		map_ = make(map[string]string, 10)
		if map_slice[i] == nil {
			for j := 0; j < 10; j++ {
				map_[strconv.Itoa(j)] = strconv.Itoa(j * 10)
			}
			map_slice[i] = map_
			fmt.Println(map_)
		}
	}
	fmt.Println(map_slice)
 
	// 用append给map切片添加元素,来动态的增加map切片的长度
	map_ = map[string]string{
		"...":"...///",
		"..":"..///",
		".":".///",
	}
	map_slice = append(map_slice, map_)
	fmt.Println(map_slice)
 
	fmt.Println("map_slice的切片长度:", len(map_slice))
}
map排序

golang中map默认是无序的,且每次遍历的输出顺序都不同;map的排序,只能先将key进行排序,再根据key值遍历输出。

package main 
import (
	"fmt"
	"strconv"
	"sort"
)
 
// 当切片作为形参时,切片只能在函数内部修改值,不能直接添加值
func Insert_Sort(map_ map[int]string) []int {
	var key_ []int
	for key, _ := range map_ {
		key_ = append(key_, key)
		sort_len := len(key_)
		if sort_len == 1 {
			continue
		} else {
			left := 0
			right := sort_len - 2
			for {
				if (right - left) > 1 {
					middle := (left + right) / 2
					if key < key_[middle] {
						right = middle
						continue
					} else if key > key_[middle] {
						left = middle
						continue
					} else {
						for i := sort_len - 2; i >= middle; i-- {
							key_[i + 1] = key_[i] 
						} 
						key_[middle] = key
						break
					}
				} else {
					if key >= key_[left] && key <= key_[right] {
						for i := sort_len - 2; i >= right; i-- {
							key_[i + 1] = key_[i] 
						} 
						key_[right] = key
					} else if key > key_[right]{
						for i := sort_len - 2; i >= right + 1; i-- {
							key_[i + 1] = key_[i] 
						} 
						key_[right + 1] = key
					} else {
						for i := sort_len - 2; i >= left; i-- {
							key_[i + 1] = key_[i] 
						} 
						key_[left] = key
					}
					break
				}
			}
		}
	}
	return key_
}
 
func GetSortedValue(value *[]string, map_ map[int]string, key_ []int) {
	for _, key := range key_ {
		*value = append((*value), map_[key])
	}
}
 
// golang中map默认是无序的,且每次遍历的输出顺序都不同;
//map的排序,只能先将key进行排序,再根据key值遍历输出; 
func main() {
	map_ := make(map[int]string, 20)
	for i := 0; i < 20; i++ {
		map_[i] = strconv.Itoa(i * 10)
	}
	fmt.Println(map_)
 
	// 方式一:先将key放入切片中, 并对切片进行排序;最后按照切片中的key查找相应的value
	var key_1 []int
	for key, _ := range map_ {
		key_1 = append(key_1, key)
	}
	sort.Ints(key_1)
	fmt.Println(key_1)
 
	var value_1 []string
	GetSortedValue(&value_1, map_, key_1)
	fmt.Println(value_1)
 
	// 方式二:将key逐个放入key_中,并利用插排,完成排序;最后按照切片中的key查找相应的value
	var key_2 []int = Insert_Sort(map_)
	fmt.Println(key_2)
 
	var value_2 []string
	GetSortedValue(&value_2, map_, key_2)
	fmt.Println(value_2)
}
8.range

range是一种迭代遍历手段,可操作的类型有数组、切片、string、map、channel等

遍历数组

myArray := [3]int{1, 2, 3}
for i, ele := range myArray {
    fmt.Printf("index:%d,element:%d\n", i, ele)
    fmt.Printf("index:%d,element:%d\n", i, myArray[i])
}

遍历slice

mySlice := []string{"I", "am", "peachesTao"}
for i, ele := range mySlice {
    fmt.Printf("index:%d,element:%s\n", i, ele)
    fmt.Printf("index:%d,element:%s\n", i, mySlice[i])
}

遍历string

s:="peachesTao"
for i,item := range s {
   fmt.Println(string(item))
   fmt.Printf("index:%d,element:%s\n", i, string(s[i]))
}

遍历map

myMap := map[int]string{1:"语文",2:"数学",3:"英语"}
for key,value := range myMap {
    fmt.Printf("key:%d,value:%s\n", key, value)
    fmt.Printf("key:%d,value:%s\n", key, myMap[key])
}

遍历channel

myChannel := make(chan int)
go func() {
  for i:=0;i<10;i++{
     time.Sleep(time.Second)
     myChannel <- i
  }
}()
 
go func() {
 for c := range myChannel {
    fmt.Printf("value:%d\n", c)
 }
}()

for range 和 for的区别

  • for range可以直接访问目标对象中的元素,而for必须通过下标访问
  • for frange可以访问map、channel对象,而for不可以

所以在range中修改时,需要通过下标访问slice中的元素对其赋值修改。

9.函数、
// 函数:实现某一功能的代码块
func 函数名(形参列表) (返回值列表) {
	执行语句
	return 返回值列表
}
打包/引包的方法:
  1. 一般包名和.go文件名相同;且package ***打包操作,必须在.go文件的开头;

  2. 为了让包中的函数/变量/常量在其他包中,能够正常访问,需要在声明和定义时将函数/变量/常量的首字母大写;

  3. 同一个包下,不能有同名的函数/变量/常量,否则会报重复定义的错误;

utils.go

package Utils   // package cal
import "fmt"
 
func ArithmeticOperate(num1 float64, num2 float64, operator byte) float64 {
	var result float64
	switch operator {
		case '+':
			result = num1 + num2
		case '-':
			result = num1 - num2
		case '*':
			result = num1 * num2
		case '/':
			result = num1 / num2
		default:
			fmt.Printf("Operator Error")
	}
	return result
}

main.go

package main
import "fmt"
import "Go_Code/Function/ArithmeticOperate/utils"
// import包时,路径是从$GOPATH的src下开始的,不需要再带src,编译器会自动从src开始引入 
 
func main() {
    var num1 float64 = 4
    var num2 float64 = 3
    var operator byte = '/'
    
	res := utils.ArithmeticOperate(num1, num2, operator)  
    // res := cal.ArithmeticOperate(num1, num2, operator)   
	fmt.Printf("%.4f", res)
}

注意:

1)函数的形参列表、返回值列表,都可以是多个的;

2)形参列表 和 返回值列表的数据类型,可以是值类型 和 引用类型;

3)函数的首字母大写,认为是可以挎包使用的,即public;首字母小写,认为是private,只能在本包中使用;

4)基本数据类型 和 数组,默认都是 值传递 的,即进行值拷贝。如果想用引用传递,则必须传入变量的地址,并在函数内通过指针的方式操作变量,类似于引用;5)Go语言不支持函数重载

6)在Go中,函数也是一种数据类型,可以赋值给一个变量,则该变量就是一个函数类型的变量,通过该变量可以对函数进行调用;函数也可以作为形参支持对函数返回值命名;

package main
import "fmt"
 
// 支持对函数返回值命名
func GetSum2(n1 int, n2 int) (n int) {
	n = n1 + n2 
	return
}
 
func GetSum1(n1 int, n2 int) int {
	n := n1 + n2 
	return n
}
 
// 函数也可以作为形参
func GetSum_(myAddfunc func(int, int) int, n1 int, n2 int) int {
	return myAddfunc(n1, n2)
}
 
func main() {
	// Go中,**函数也是一种数据类型**,可以赋值给一个变量,则该变量就是一个函数类型的变量
	FuncTypeVar := GetSum2
	fmt.Printf("FuncTypeVar type is %T\n", FuncTypeVar)  // func(int, int) int
 
	// 通过该变量可以对函数进行调用
	result := FuncTypeVar(10, 11)
	fmt.Printf("result = %d\n", result)
 
	// 函数也可以作为形参
	result2 := GetSum_(FuncTypeVar, 10, 11)
	fmt.Printf("result2 = %d\n", result2)
}

为了简化数据类型定义,Go支持自定义数据类型

基本语法:type 自定义数据类型 数据类型 // 相当于一个别名 举例:type mySum func(int, int) int 8)占位符_,可以在调用有多个返回值的函数时,忽略某个返回值;

9)Go支持可变参数:可变参数要放在,形参的最后。

其中,args是slice切片,可以通过下标args[index]访问到

// 支持0 ~ 多个参数
func (args... int) int {
    
}
 
// 支持1 ~ 多个参数
func (n1 int, args... int) int {
    
}
package main
import "fmt"
 
func sum(n1 int, args... int) int {
	sum := n1
	for i := 0; i < len(args); i++ {
		sum += args[i]  // 表示依次取出args切片中的各个元素
	}
	return sum
}
 
func main() {
	res := sum(1, 2, 3, 4)
	fmt.Printf("result = %d\n", res)
}
init函数

每个.go文件,都可以包含一个init函数,该函数会在main函数执行之前,被Go运行框架调用,也就是说init会在main函数前被调用,通常用来完成初始化的工作。

如果一个函数中同时含有全局变量的定义、init()函数、main()函数,则执行的顺序是,全局变量的定义 -> init()函数 -> main()函数。

package main
import "fmt"
 
// init()函数,可以用来完成一些初始化的工作
var age = test()
 
func test() int {
	fmt.Println("test()")
	return 90
}
 
func init() {
	fmt.Println("..... Init() .....")
}
 
func main() {
	fmt.Println("..... main() .....", age)
}

Go支持匿名函数,不多展开。

10.指针

&x表达式(取x变量的内存地址)将产生一个指向该整数变量的指针

*p 对应一个变量,所以该表达式也可以出现在赋值语句的左边,表示更新指针所指向的变量的值、

基本数据类型(int系列、float系列、bool、string、数组和结构体struct)中,变量存的是值,也叫值类型。值类型都有对应的指针类型,形式为数据类型。 获取变量的地址用&; 指针变量ptr存的是一个地址,且该地址指向的空间存的是值,比如var ptr int = &num; 获取指针的值,使用,比如ptr;