Go学习记录(1.5)

简介

本文为Go学习过程中记录的笔记,参考文档如下:《Go入门指南》

控制结构

1. if-else:

if condition{
    
}else if condition{
    
}else{
    
}
//在Go中,"{"不能成为某一行的开头,"}"若未完结也不能成为某一行的结尾
//因为Go的自动补全";"机制,猜测是因为Go会自动在"}"和不带有"{"结尾
//的每一行都加上";",因此才不能随便换行更改结构

在Go语言中,if后面携带的condition条件,可以内嵌一个初始化语句,初始化语句与condition之间需要使用 “;” 分开:

if initialization; condition{
    //这里的声明语句只能通过":="方式声明
    //且其生命周期只在if-else结构内
}

2. 获取多返回值函数的返回结果:

Go语言的函数经常使用两个返回值来表示执行成功;返回某个值以及true表示成功,返回零值(或者nil)和false表示失败。当不使用true或false的时候,也经常使用error类型的变量来代替第二个返回值,成功执行则error为nil,否则不为空且包含相关的错误信息。

var ori = "ABC"
var err error,newStr string
newStr,err = strconv.Atoi(ori)
if err!=nil{
    fmt.Println("error message is:",error)
    return
}

3. switch:

switch var{				//这里的var并不限制类型
    case val1,valOther:
    	……
    case val2:
    	……
    case val3:
    	……
    default:
    	……
}

在Go的switch分支中,会从上至下的遍历每一个case,同时不需要手动编写break语句,一旦匹配上一个case执行完相关代码后便会自动退出,也可以使用关键字 “fallthrough” 继续往后遍历;

switch也可以不提供任何值,case中存放各种condition,即变相地实现if-else:

switch{
    case condition1:
    	……
    case condition2:
    	……
    case default:
    	……
}

4. for(唯一的循环结构)

  • 基于计数器的迭代

    for 初始化语句;条件语句;修饰语句{
        
    }
    
  • 基于条件判断的迭代,有点类似于while循环

    for 条件语句{
        
    }
    
  • 无限循环:

    for ;;{ }
    for { }
    
  • for-range结构,它可以迭代任何一个集合:

    for ix,val:= range coll{ }
    //ix代表索引,val在值类型的情况下,是值类型的拷贝,无法修改原值
    //如果val是引用类型,则此时val是引用的副本,可以修改原值
    //这里的range是一个关键字,代表迭代coll集合
    

5. break和continue——跳出循环和执行下一次循环

6. 标签与goto:

标签用来标记某一部分代码块,其名称是大小写敏感的,为了提升可读性,一般建议全部使用大写字母。标签可以搭配break,continue,goto使用。

使用标签和goto是不被鼓励的,建议不使用。

函数

1. Go不允许函数重载,影响性能。

如果需要声明一个在外部定义的函数,只需要给出函数名和函数签名,不需要给出函数体:

func flushICache(begin, end uintptr)
//参考c语言中源文件引用外部文件的函数

函数也可以作为一个类型,如下:

type binOp func(int,int) int

2. Go的函数传递,本质上也是值传递,同时,在很多情况下,传递指针的消耗都比传递副本来得少。

返回值也可以命名,即进行变量声明,通过这种方式在return的时候不带上返回变量名也是可以的,如下图所示:

func funcName(input int)(re1 int,re2 int){
    re1 = ……
    re2 = ……
    return
}

尽量减少非命名返回值的使用,不是多好的编程习惯。

空白符 “_” 可以用来接收不需要的值。

3. 变长参数:如果函数的最后一个参数是采用 “…type” 的形式,那么这个函数就可以处理一个变长的参数。

func Greeting(prefix string,who …string)
//事实上这里接收两个参数,第一个参数为string类型,参数名为prefix
//第二个参数为string[],参数名为who

如果变长参数中的类型并不全都是一样的,可以使用结构类型用以存储所有可能的参数,然后以结构类型作为参数进行传递;(是否只能传递定长?)

type Options struct{
    par1 type1,
    par2 type2,
    ……
}

func F1(a, b, Options{}){
    
}

或者使用默认的空接口,这样就可以接受任何类型的参数,且使用空接口的情况下,即使参数长度未知,参数类型未知,空接口也可以实现传递。

4. defer关键字:

defer关键字修饰的语句,允许我们将某一句代码的执行推迟到函数返回之前(参考Java的finally),但代码的执行结果并不会因为被推迟而有所改变,即代码的执行结果与正常执行结果一致,只是在时序上推迟了。

func main() {
	a()
}

func a() {
	i := 0
	defer fmt.Println(i)
	i++
	fmt.Println(i)
	return
}
//代码执行结果为
//1
//0

当有多个defer行为被注册时,它们会以逆序执行(类似栈,即后进先出)

func main() {
	f()
}

func f() {
	for i := 0; i < 5; i++ {
		defer fmt.Printf("%d ", i)
	}
}
//代码执行结果为:4 3 2 1 0

一般可以使用defer用来关闭系统资源,如IO流,这里和Java不同的是,因为多defer的栈特性,所以在多资源申请的情况下,谁先申请谁先释放;或者对加锁资源解锁。

使用defer语句还可以实现在函数出口的相关信息打印;

5. 内置函数——不需要额外导包

- close		用于管道通信
- len		用于返回某个类型的长度或数量(字符串、数组、切片、map和管道)
- cap		用于返回某个类型的最大容量(只能用于切片和map)
- new		分配内存,用于值类型和用户定义的类型,new返回指针
- make		分配内存,用于内置引用类型(切片,map和管道)make返回值
- copy		用于复制切片
- append	用于连接切片
- panic		用于错误处理机制
- recover	用于错误处理机制
- complex	用于创建和操作复数
- real		得到复数的实数部分
- imag		得到复数的复数部分

6. 将函数作为参数:(前面提到的函数作为一个变量的体现)

func main() {
    callback(1, Add)
}

func Add(a, b int) {
    fmt.Printf("The sum of %d and %d is: %d\n", a, b, a+b)
}

func callback(y int, f func(int, int)) {
    f(y, 2) // this becomes Add(1, 2)
}

7. 闭包:匿名函数也成为闭包

匿名函数:func(x,y int) int { return x+y },这样的一个函数并不能够独立存在,但是可以在定义完之后直接调用或者赋值给变量,然后通过该变量名进行函数调用。

func(x,y int) int { return x+y } (3,4)
//这里的第一个"()"与func之间没有空格,因为匿名函数没有名称
func() {
    sum := 0
    for i := 1; i <= 1e6; i++ {
        sum += i
    }
}()

关键字defer经常配合匿名函数使用,它可以用于改变函数的命名返回值。在匿名函数中,它们被允许调用定义在其它环境下的变量。比如下面这个例子,defer修饰的匿名函数可以访问外部的ret。因为一个匿名函数继承了函数所声明时的作用域。

func f() (ret int) {
    defer func() {
        ret++
    }()
    return 1
}
func main() {
    fmt.Println(f())
}

8. 应用闭包:函数作为返回值,函数作为值类型的一种,也可以成为其他函数的返回值

func main() {
    var f = Adder()
    fmt.Print(f(1), " - ")
    fmt.Print(f(20), " - ")
    fmt.Print(f(300))
}

func Adder() func(int) int {
    var x int
    return func(delta int) int {
        x += delta
        return x
    }
}

//执行结果:1 - 21 - 321

三次调用函数的过程中,变量x的值是被保留的,这也是闭包函数的一个特性,即闭包函数会保存并积累其中的变量的值,不管外部函数退出与否,它都能够继续操作外部函数中的局部变量。同时,在闭包中对外部变量的操作也会对外部的变量产生相应的修改;

var g int
go func(i int) {
    s := 0
    for j := 0; j < i; j++ { s += j }
    g = s
}(1000) // Passes argument 1000 to the function literal.
//这里的go func组成了一个goroutine,且不执行,把关键字"go"
//去掉再在外部打印g就会发现g的值发生了更改

9. 使用闭包调试:

where := func(){
    _,file,line,_ := runtime.Caller(1)
    log.Printf("%s:%d",file,line)
}
//在有需要的地方调用where()即可实现
var where = log.Print
where()
//这部分代码与上面代码效果一致,简化版

10. 计算函数执行的时间:

```go
start := time.Now()
end := time.Now()
delta := end.Sub(start)
```

数组和切片

1. 数组:

数组是具有相同的唯一类型的一组以编号且长度固定的数据项序列,如果想要实现存储任意类型的数据,需要使用空接口作为类型。数组长度最大为2GB。

var arr1 = new([5]int)
var arr2 [5]int
//这里,arr1的类型是*[5]int
//而arr2的类型是[5]int
//因此,如果采用arr2 := *arr1,的方式赋值
//其实是*(*[5]int),是对原数组做数据拷贝
//即使更改了arr2也不会对arr1产生影响
arr2 := *arr1

在函数中如果想更改作为形参的数组中的值,需要传递指针;

在Go语言中定义数组,可以使用索引搭配值的方式:

var arrKeyValue = [5]string{3:"Chris",4:"Ron"}
//结果为:[,,,Chris,Ron]

Go语言的多维数组是矩形式的,即不像Java每行可以为不同长度的数组;

把一个大数组传递给函数会消耗很多内存,有两种方法可以避免:

  • 传递数组的指针
  • 使用数组的切片(常用)

2. 切片:

对数组一个连续片段的引用(该数组我们称之为相关数组,可能是匿名的),因此切片是一个引用类型。切片的很多地方都和数组差不多,只不过数组在初始化的时候就需要声明数组的长度,而切片的长度是可变的,最小为0,最大为相关数组的长度

切片在内存中的组织方式实际上是一个有3个域的结构体:指向相关数组的指针,切片长度以及切片容量。如下图所示示例:

image-20211218090858330

切片的长度可以通过len()获得,容量可以通过cap()获得。

切片可以作为参数传递给函数,对切片中引用做出的修改,在原数组上也会发生相应的更改。

创建切片:

var slice []type = make([]type,len)
//未指定cap的条件下,cap的值与len相等
make([]int,50,100)
new([100]int)[0:50]		//索引值,0-49
//上面两种方式可以生成相同的切片,但是仍旧存在差别
//new方式生成的为指向切片的指针,而非切片本身
//make方式生成的为切片本身
slice := type[n:m]
//获得type数组索引n到m-1的元素构成的切片,长度为m-n,容量为len(type)

make只适用于三种内建的引用类型:切片,map和channel。

多维切片:由一维切片组合成高纬,通过切片的切片,或者切片数组实现。

类型[]byte的切片十分常见,Go语言有一个bytes包,专门处理这种类型的数据。其中有一个比较常见的类,Buffer。比较常见的使用方式是,利用buffer的可变长特性,使用WriteString(string)方法拼接字符串,通过String()转换为字符串输出。

3. for-range结构:

可以用来遍历数组或者切片,但是for-range赋值的val,只是一个拷贝,不能真正更改切片或数组上对应位置的值。

for-range遍历多维切片:

for row := range screen {
    for column := range screen[row] {
        screen[row][column] = 1
    }
}

4. 切片重组——拓展长度,直到达到cap值

slice = slice[0:end]
//end是新的末尾索引,默认将新增加的部分以类型零值填充
//前面提到,cap一般是相关数组的长度,这是切片从0开始索引的情况
//如果相关数组的长度为10,切片从5开始索引,此时cap为10-5=5

切片的复制与追加——拓展cap值

拓展cap值的方法有两种:一个是创建一个新的更大的切片,然后把原始的切片的内容复制过来;另一种方式是调用append()直接在切片末尾追加新的元素;

sl_from := []int{1, 2, 3}
sl_to := make([]int, 10)
n := copy(sl_to, sl_from)
//这里的n返回值,记录了复制元素的个数

sl3 := []int{1, 2, 3}
sl3 = append(sl3, 4, 5, 6)
//append后面的参数也可以是切片
//append在发现旧切片的容量不足以容纳新的元素时
//会分配新的切片然后复制粘贴完成拓展
//因此,完成追加的切片的引用地址可能会发生改变

5. 字符串、数组和切片的应用:

  • 从字符串获得切片:

    c := []byte(s)				//获得字节数组的切片
    c := []int32(s)				//获得字符串对应的int切片
    c := []rune(s)				//获得UTF-8切片
    
    len([]int32(s))				//获得字符的数量
    utf8.RuneCountInString(s)	//获得字符的数量
    
  • 字符串的内存结构:双字,一个指向数据的指针,一个存储字符串长度;

  • 修改字符串中的某个字符:

    因为字符串是不可变的,因此,需要先将字符串转化成切片,对切片进行修改,然后再将切片转换成字符串格式。

  • 字节数组对比函数:bytes.Compare()

  • 搜索及排序切片和数组:

    查看是否已经排序:

    sort.IntsAreSorted(a []int) bool
    

    对相关数组或切片进行排序:

    sort.Ints(a []int)
    sort.Float64s(a []float64)
    sort.Strings(a []string)
    

    在数组或切片中搜索一个元素:(只对于已排序数组或切片操作,底层使用二分)

    sort.SearchInts(a []int,n int) int	//返回对应结果的索引值
    sort.searchFloat64s(a []float64,n float) int
    sort.searchStrings(a []string,n string) int
    
  • append函数常见用法:

    • 拓展切片

    • 删除位于索引i的元素:

      a=append(a[:i],a[i+1:])
      
    • 切除切片a中i到j的元素

      a=append(a[:i],a[j+1:])
      
    • 为切片a扩展j个元素长度

      a=append(a,make([]T,j))
      
    • 在索引i位置插入元素x

      a=append(a[:i],append(make(T[],{x}),a[i:]))
      
    • 在索引i位置插入长度为j的新切片

      a=append(a[:i],append(make(T[],j),a[i:]))
      
    • 取出位于切片啊最末尾的元素x:

      x,a=a[len(a)-1:],a[:len(a)-1]
      
  • 切片和垃圾回收:

    切片的底层指向一个数组,数组的实际容量可能要大于切片所定义的容量,只有在没有任何切片指向的时候,底层的数组内存才会被释放,这种特性有时会导致程序占用多余的内存(一点点有用的数据就占用了大部分内存)。可以通过拷贝我们需要的部分到一个新的切片中,以这种方式解决这个问题。

    func FindDigits(filename string) []byte {
        b, _ := ioutil.ReadFile(filename)
        return digitRegexp.Find(b)
    }
    
    func FindDigits(filename string) []byte {
       b, _ := ioutil.ReadFile(filename)
       b = digitRegexp.Find(b)
       c := make([]byte, len(b))
       copy(c, b)
       return c
    }
    
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值