Go学习笔记

以下总结来自 学习 《The Go Programming Language》 Go语言圣经中文版

正如Rob Pike所说,“软件的复杂性是乘法级相关的”,通过增加一个部分的复杂性来修复问题通常将慢慢地增加其他部分的复杂性。通过增加功能、选项和配置是修复问题的最快的途径,但是这很容易让人忘记简洁的内涵,即从长远来看,简洁依然是好软件的关键因素。

简洁的设计需要在工作开始的时候舍弃不必要的想法,并且在软件的生命周期内严格区别好的改变和坏的改变。通过足够的努力,一个好的改变可以在不破坏原有完整概念的前提下保持自适应,正如Fred Brooks所说的“概念完整性”;而一个坏的改变则不能达到这个效果,它们仅仅是通过肤浅的和简单的妥协来破坏原有设计的一致性。只有通过简洁的设计,才能让一个系统保持稳定、安全和持续的进化。

Go项目包括编程语言本身,附带了相关的工具和标准库,最后但并非代表不重要的是,关于简洁编程哲学的宣言。就事后诸葛的角度来看,Go语言的这些地方都做的还不错:拥有自动垃圾回收、一个包系统、函数作为一等公民、词法作用域、系统调用接口、只读的UTF8字符串等。但是Go语言本身只有很少的特性,也不太可能添加太多的特性。例如,它没有隐式的数值转换,没有构造函数和析构函数,没有运算符重载,没有默认参数,也没有继承,没有泛型,没有异常,没有宏,没有函数修饰,更没有线程局部存储。但是,语言本身是成熟和稳定的,而且承诺保证向后兼容:用之前的Go语言编写程序可以用新版本的Go语言编译器和标准库直接构建而不需要修改代码。

包管理

包名一般采用单数的形式

某些包以复数形式存在是为了避免歧义,如strings包之所以复数是要与string类型区分

重命名包:

import io "fmt" //引用fmt这个包时,名字重命名为io

如果包中含有多个.go源文件,它们将按照发给编译器的顺序进行初始化,Go语言的构建工具首先会将.go文件根据文件名排序,然后依次调用编译器编译。

每个包在解决依赖的前提下,以导入声明的顺序初始化,每个包只会被初始化一次。因此,如果一个p包导入了q包,那么在p包初始化的时候可以认为q包必然已经初始化过了。初始化工作是自下而上进行的,main包最后被初始化。以这种方式,可以确保在main函数执行之前,所有依赖的包都已经完成初始化工作了。

匿名导入:如果只是导入一个包而不使用导入的包将会导致编译错误。但是有时候我们只是想利用导入包而产生的副作用:它会计算包级变量的初始化表达式和执行导入包的init初始化函数。

import _ "image/png" // register PNG decoder

函数

go不支持默认参数,默认函数参数,其实并不是一个很好的行为.强类型语言,应该是没有任何隐喻的,仅从名字和参数就能知道大概的行为和用法,而默认函数参数,可能导致调用者不知道默认参数是什么出现一些问题。一个行为一定是确定的,无二意的,而默认参数的函数违背了这种不二意思想.Go语言追求显式的表达,避免隐含。

Google的C++规范里面也禁止使用函数默认参数.类似的,在这个规范的,凡是有可能导致二意行为的也是被禁止的.

匿名函数:

拥有函数名的函数只能在包级语法块中被声明,通过匿名函数,我们可绕过这一限制,在任何表达式中表示定义并使用函数。

strings.Map(func(r rune) rune { return r + 1 }, "HAL-9000")
// squares返回一个匿名函数。
// 该匿名函数每次被调用时都会返回下一个数的平方。
func squares() func() int {
    var x int
    return func() int {
        x++
        return x * x
    }
}
func main() {
    f := squares()
    fmt.Println(f()) // "1"
    fmt.Println(f()) // "4"
    fmt.Println(f()) // "9"
    fmt.Println(f()) // "16"
}

squares的例子证明,函数值不仅仅是一串代码,还记录了状态。在squares中定义的匿名内部函数可以访问和更新squares中的局部变量,这意味着匿名函数和squares中,存在变量引用。也叫闭包。

defer:

包含defer语句的函数和参数表达式得到计算,但直到包含该defer语句的函数执行完毕时,defer后的函数才会被执行,不论包含defer语句的函数是通过return正常结束,还是由于panic导致的异常结束。你可以在一个函数中执行多条defer语句,它们的执行顺序与声明顺序相反

defer语句经常被用于处理成对的操作,如打开、关闭、连接、断开连接、加锁、释放锁。通过defer机制,不论函数逻辑多复杂,都能保证在任何执行路径下,资源被释放。释放资源的defer应该直接跟在请求资源的语句后

func ReadFile(filename string) ([]byte, error) {
    f, err := os.Open(filename)
    if err != nil {
        return nil, err
    }
    defer f.Close() // 函数的其他语句全部执行完成后,才会执行f.Close()
    return ReadAll(f)
}

调试复杂程序时,defer机制也常被用于记录何时进入和退出函数。

func bigSlowOperation() {
    defer trace("bigSlowOperation")() // don't forget the extra parentheses
    // ...lots of work…
    time.Sleep(10 * time.Second) // simulate slow operation by sleeping
}
func trace(msg string) func() {
    start := time.Now()
    log.Printf("enter %s", msg)
    return func() { 
        log.Printf("exit %s (%s)", msg,time.Since(start)) 
    }
}

bigSlowOperation被调时,trace会返回一个函数值,该函数值会在bigSlowOperation退出时被调用。通过这种方式, 我们可以只通过一条语句控制函数的入口和所有的出口,甚至可以记录函数的运行时间,如例子中的start。需要注意一点:不要忘记defer语句后的圆括号,否则本该在进入时执行的操作会在退出时执行,而本该在退出时执行的,永远不会被执行。

在循环体中的defer语句需要特别注意,因为只有在循环体执行完毕后,这些被延迟的函数才会执行。下面的代码会导致系统的文件描述符耗尽,因为在所有文件都被处理之前,没有文件会被关闭。

for _, filename := range filenames {
    f, err := os.Open(filename)
    if err != nil {
        return err
    }
    defer f.Close() // NOTE: risky; could run out of file descriptors
    // ...process f…
}

种解决方法是将循环体中的defer语句移至另外一个函数。在每次循环时,调用这个函数。

for _, filename := range filenames {
    if err := doFile(filename); err != nil {
        return err
    }
}
func doFile(filename string) error {
    f, err := os.Open(filename)
    if err != nil {
        return err
    }
    defer f.Close()
    // ...process f…
}

defer实现函数计时器

func bigSlowOperation() {
	defer trace("bigSlowOperation")() // don't forget the extra parentheses
	// ...lots of work…
	time.Sleep(10 * time.Second) // simulate slow operation by sleeping
}
func trace(msg string) func() {
	start := time.Now()
	log.Printf("enter %s", msg)
	return func() {
		log.Printf("exit %s (%s)", msg, time.Since(start))
	}
}

字符串操作:

import (
	"strconv"
	"strings"
)
转数字
str := "12"
page, err := strconv.Atoi(str)


以指定字符分割
str := "aa,bb,cc"
strings.Split(str, ",")  
//strings.Split(被分割的字符串, 用什么分割)
// [aa bb cc]

// 按照空格切割字符串, 多个空格会合并为一个空格处理
arr7 := strings.Fields("a  b c    d")
// [a b c d]


切片
"abcd"[1:]
"abcd"[:2]


是否包含
strings.Contains("aabbcc", "bb")


是否以什么开头和结尾
strings.HasPrefix("aabbcc", "aa")
strings.HasSuffix("aabbcc", "cc")


替换
str := "hello world world"
res0 := strings.Replace(str, "world", "golang", 2)
res1 := strings.ReplaceAll("原字符串", "被替换的内容", "替换的内容")
//trings.Replace("原字符串", "被替换的内容", "替换的内容", 替换次数) 替换次数小于0会替换所有
//结果 hello golang golang


去掉首尾空格
res1 := strings.TrimSpace("  asd ")


去掉首尾指定字符串 Trim/TrimLeft/TrimRight/TrimSuffix/TrimPrefix
strings.Trim("!!Welcome to nhooo !!", "!")
strings.Trim("$$Welcome to nhooo !!", "!$")


切片转换为字符串
sce := []string{"aa", "bb", "cc"}
str1 := strings.Join(sce, "-")
// aa-bb-cc

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值