Go 语言程序设计-笔记

第一章五个程序

都很好!
但是初学编程/没有其他语言基础的不容易看懂。

记一遍不熟悉的东西:

  1. who = strings.Join(os.Args[1:], " ")
    //一个string类型sliece和一个分隔符为参数输入,返回一个分隔符把slice中所有字符串连接在一起的新字符串
    //把slice中所有元素拼接成一个字符串的神器

  2. //os.Args[0]存放的是程序名字,可以打印看看

  3. //filepath.base 返回传入路径的基础名=文件名
    //Base函数返回路径的最后一个元素。在提取元素前会去掉末尾的路径分隔符。如果路径是"",会返回".";如果路径是只有一个斜杆构成,会返回单个路径分隔符。

  4. os.Exit(1)
    //0正确,非0 错误

  5. stacker的例子是很好的鸭子类型的例子,栈先进后出,

func (stack *Stack) Pop() (interface{}, error) {
    theStack := *stack
    if len(theStack) == 0 {
        return nil, errors.New("can't Pop() an empty stack")
    }
    x := theStack[len(theStack)-1]
    *stack = theStack[:len(theStack)-1]
    return x, nil
}

//receiver类型是*Stack,因为要修改原stack,theStack,中间量

  1. 文件读写,截一段代码
func americanise(inFile io.Reader, outFile io.Writer) (err error) {
    reader := bufio.NewReader(inFile)
    writer := bufio.NewWriter(outFile)
    //一个延迟函数,等缓冲区的内容全部完成读写
    defer func() {
        if err == nil {
            err = writer.Flush()
        }
    }()
    //定义replacer是为了不重复命名err如果这里不给replacer,下一句就是if replacer, err:= 这样err就是个影子变量,因为返回值那里已经命名过了
    var replacer func(string) string
    if replacer, err = makeReplacerFunction(britishAmerican); err != nil {
        return err
    }
...(未完)

//这个程序还用了闭包,闭包可以保留之前的状态,从而把之前的文档中词语做特换

  1. goroutine
    polarCoord := <-questions这里阻塞

第二章 布尔&数值类型

  1. _不是新值,不初始
  2. 二元逻辑符 短路逻辑,比如a||b,a为真,b不用判断
  3. slice不能比较,可以通过reflect.DeepEqual()完成
  4. 类型转换string(a),CONST
  5. byte==uint 8
  6. 异或(XOR),不同为1,相同为0,与非(ANDNOT),先与后非
  7. 大整数,math/big包,这里对应用开发来说不是重点,可以略过,需要用的地方再回来查
  8. statistics的例子,用到了net/http包里的一些方法,,homepage里用到,书里讲解很详细,好评。

第三章 字符串

  1. 字符串只包含7位的ASCII字符,才能被字节索引,不过go的一个字符一个字符迭代更实用
//一个无聊的测试,可运行
func main() {
    fmt.Println("Hello, playground")
    a := "abcdefg"
    //a是只有ASCII字符的string,可以直接用索引的方式得到每一个字符
    //系统会把string按[]byte来处理,所以直接输出a[1]会得到一个0-255之间的数字
    //string()强转了a[1]的类型,所以可以输出b
    fmt.Println("hi,a", string(a[1]))

    //difference存储的是中文,中文一个字符可能不止存在一个索引指向的位置上,所以虽然difference只有8字符,但是索引大于7的地方也可能存了值
    difference := "皇家葫芦娃,go"
    fmt.Println("difference[8]", string(difference[8]))
    for i, qw := range difference {
        //用索引取值不能输出中文字符,直接输出value可以,
        fmt.Println("hi,", string(difference[i]))
        fmt.Println("hi中文,", string(qw))
    }
//非ASCII转成rune类型可以直接通过索引输出汉字
    r := []rune(difference)
    for i := 0; i < len(r); i++ {
        fmt.Println("r[", i, "]=", r[i], "string=", string(r[i]))
    }   
}
  1. “可解析字符串字面量”、`原生字符串字面量(可包含任何字符)`
  2. strconv.Itoa(i), 返回int类型i的字符串表示 和一个错误值,如果i=65, 返回“65”,nil
  3. 一个以个的方式追加字符串
var buffer bytes.Buffer
for{
  if piece,ok:=getNextValidString(); ok{
    buffer.WriteString(piece)
  }else{
break
}
}
fmt.Print(buffer.String(),"\n")
  1. 讲fmt包,有个讲包的github也讲了这个,里面有一段

为避免以下这类递归的情况:
type X string
func (x X) String() string { return Sprintf("<%s>", x) }
需要在递归前转换该值:
func (x X) String() string { return Sprintf("<%s>", string(x)) }
print等,输出到控制台
Fprint等,输出到一个writer
Sprint等,输出到一个字符串

  1. bufio.Reader.ReadString('\n')读,遇到\n停
  2. 调试可能会用到的格式化输出方法:
i:=5
fmt.Printf("|%p->%d|",&i,i)
//结构体
fmt.Printf("%v\n",[]float64{1.00,2.00,3.00})
fmt.Printf("%#v\n",[]float64{1.00,2.00,3.00})
//输出结果:|address->5|
//[1.00,2.00,3.00]
//[]float64{1.00,2.00,3.00}加上#修饰符,可以输出结构体类型那部分(map也可以),行话叫:以编程的形式输出Go语言代码
//%q适合输出slice,会使每个字符串都是可识别的
  1. strings包,很好玩的一个包
    举个🌰
fmt.Printf("%q\n", strings.SplitAfterN("a,b,c", ",", 2))
//Output:["a," "b,c"]返回的slice最多有2个string
  1. 进阶版分割,可以用
//非符号的字段,一段一段拿出来
f := func(c rune) bool {
    return !unicode.IsLetter(c) && !unicode.IsNumber(c)
}
fmt.Printf("Fields are: %q", strings.FieldsFunc("  foo1;bar2,baz3...", f))
Output: Fields are: ["foo1" "bar2" "baz3"]
  1. string.replac(要执行replace的string,“被替换string”,“替换成string”,“-1”) -1表示全部替换,其他数字表示替换几次;strings.Repeat 画三角塔神器,strings里很多方法都经常用,不过也很简单,不写了,用的时候去查库就可以
  2. strconv所有strconv转换函数都是返回一个结果,一个error,转换成功,结果为nil
  3. unicode ,utf8,regexp(compile,为了安全,编译通过返回一个*regexp)马住,用到再来

第四章 集合类型

  1. chan,func,method,map,slice 引用
  2. 指针,*
z:=17
pi:=&z
ppi:=&pi
fmt.Print(z,*pi,*ppi)
**ppi++
//==(*(*ppi))++, *(*ppi)++
fmt.Print(z,*pi,*ppi)
//output:171717
181818
  1. *也是类型修饰符,第一张stack那里有解释过,那里是指一个结构体指针,用的时候是不需要解引用的,很方便
  2. new(type)==&type{(可以直接初始化赋值)}
    这两种都分配一个type类型的空值,同时返回一个指向该值的指针
  3. 添加一个子切片:
s:=append(s,t...)添加slice t中所有值
s:=append(s,u[2,5]...)添加一个子切片
//把一个字符串字节添加到一个字节切片中
a:="abc"
b:=[]byte{'u','v'}
b=append(b,a...)
  1. 一个黑科技(我知道是我没见过世面,不过很神奇啊)
type Product struct {
    name string
}
func (product Product) String() string {
    return fmt.Sprintf("%s", product.name)
}
func main() {
    products:=[]*Product{{"a"},{"b"},{"c"}}
    fmt.Println(products)
//这里会调用String方法,虽然传入的是个*Product类型的slice,但是打印的时候会解引用,把整个slice打出来
}
  1. 在src slice的index位置插入insert slice
func InsertStringSlice(src,insert []string,index int) []string{
  retrun append(slice[:index],append(insertion,slice[index:]...)...)
}
  1. 删除slice中间的几个元素s:=append(s[:1],s[5:]...)删除了s1:5
  2. sort.Search的用法here
    官网例子很好玩,看书看累了可以试一试,嗯。
func GuessingGame() {
    var s string
    fmt.Printf("Pick an integer from 0 to 100.\n")
    answer := sort.Search(100, func(i int) bool {
        fmt.Printf("Is your number <= %d? ", i)
        fmt.Scanf("%s", &s)
        return s != "" && s[0] == 'y'
    })
    fmt.Printf("Your number is %d.\n", answer)
}

第五章 过程式编程

核心思想是介绍了go的语法,突出展示了go的简介和第一章说过的一些特性

  1. 要避免使用影子变量,比如for循环中声明的变量不要和外面的同名
  2. 类型转换,提到很多次了string(a)
  3. 断言:resultOfType, boolean:=expression.(Type)安全类型断言;resultOfType:=expression.(Type)非安全型断言,失败会panic()
  4. switch后没有跟表达式,编译器会默认将其设置为true;类型开关用法:switch x.(type)
  5. 万能for,用法简单,其他笔记里记过了,,下一个
  6. 并发,go func(),这个func是个已有函数或是临时创建的匿名函数,被调用的时候会立即执行,但是在另一个goroutine,(有点类似多线程),当前的goroutine执行会从下一条语句中立即恢复。所以,执行一个go语句之后肯定至少有两个goroutine在并发执行。
    🌰
func main() {
    ca := createCounter(2)
    cb := createCounter(102)
    for i := 0; i < 5; i++ {
        a := <-ca
        fmt.Printf("A %d \nB %d \n", a, <-cb)
    }
}
func createCounter(start int) chan int {
    next := make(chan int)
    go func(i int) {
        for {
            next <- i
            i++
        }
    }(start)
    return next
}
//输出是a 2 b102 a3 b103 ... a6 b106
  1. select,哪个chan通就执行哪个,很类似switch
  2. panic和err,总的来说,如果是逻辑错误,或者是希望一旦有错就强制崩溃,用panic,配合recover食用效果更佳;err的话是首先想确保程序健康执行,(公司大牛专门讲过:是程序员的问题,panic;启动的时候可以panic;逻辑问题不知道为什么出错,panic;想不清楚要不要panic就不要panic,┑( ̄Д  ̄)┍;数据错误不要panic
  3. 自定义函数讲了一下参数和返回值的基本写法和比较优雅的写法,变成很有用看起来,然而我至今没用到过
  4. init和mian,�可以不传参没有返回值,导入包的时候会执行init然后main,如果导入包的时候用_当别名,那就是只执行导入包的init函数
  5. 闭包,可以保留和他在统一作用于的变量的状态和值,所有的匿名函数都是闭包。另一种形式是return一个闭包函数这样的写法真的是看多了就好了,好处是这个闭包里是可以使用return所在的函数体的一些变量的。
  6. 递归,除了大家熟悉的Fibonacci数以外还有个好玩的
//判断一个单词是否是一个回文词,比如PULLUP,ROTOR
func IsPalindrome(word string) bool {
  if utf8.RuneCountInString(word)<=1{
  return true
  }
first, sizeOfFirst:=utf8.DecodeRuneCountInString(word)
last,sizeOfLast:=utf8.DecodeLastRuneCountInString(word)
if first!=last{return false}
return IsPalindrome(word[sizeOfFirst:len(word)-sizeOfLast])
}
  1. 动态函数,其实也是init的一种用法,init中可以做一个判断并协议中处理方式,如果符合,有限进行该处理,不符合就在包里继续找方法

  2. 泛型函数,返回一个interface{},然后通过调用的时候断言:example().(type)从而把我们需要的值转为我们需要的值,不过是很麻烦的一种写法

  3. switch的一个小技巧,如果每个case中有重复代码,可以直接在switch处加上一个if change,每个case上设置一个bool,eg:change=true

差不多就到这里了,struct和interface都是项目里特别常用的,看书好像不如实战效果好。

----分隔符----

第六章

  1. go不支持重载方法,不可以定义两个名字相同但是签名不同的方法,但可以设置变参,这样方法相当于可用性更高
  2. 可以自定义类型,给自定义的类型自定义方法
  3. 可通过结构体嵌套方式实现类似继承的功能
  4. 注意传递值还是传递指针,传递指针才能改变原始数据的值,另外传指针比传值更高效
  5. 方法表达式(6.2.1.2)这里没看懂,高人私我
  6. New***这类的方法可以看做是显式的构造函数,用来确保可以生成一个合法的某类型的实例
  7. 接口:声明一个或多个方法,完全抽象,不能实例化,但可以创建一个实现了该接口的类型,这个类型可以被实例化。命名最好以er结尾(传统)
    *第六章这种讲解方式有点看不下去。。。我 去第七章了,我惭愧

第七章

  1. main就是一个goroutine
  2. 小坑1:需要注意主goroutine退出的同时其他goroutine也会退出,所以我们得先确定其他goroutine已经退出了,再退出主goroutine
  3. 小坑2:可能发生死锁,用通道即可避免
  4. 解决方法1:常见的是让主goroutine在一个done的channel上等,根据接收工作是否完成来觉得退出的时间
  5. 解决方法2:sync.WaitGroup,让每个goroutine报告自己的完成状态,但是sync.WaitGroup本身也可能会产生死锁,特别是当所有工作的goroutine都处于锁定状态的时候调用sync.WaitGroup.Wait()
  6. channel为并发运行的goroutine之间提供了一种无锁通信方式,当一个通道发生通信时,发送通道和接受通道都处于同步状态
  7. 单向通道,比如只为了传递参数的时候
chan<- //类型是只允许发送
<-chan //类型是只允许接收
  1. channel里发生boolean, int, float64都是安全的,因为都是值传递,复制的方式,string也安全,因为go不允许改string
  2. 指针和引用类型不行,不安全,对这类值必须是串行访问,可以使用互斥量;或者是设定一个规则,一个指针或一个引用发送之后发送方就不会再访问他,让接受者来访问和释放指针或是引用的值;再或者让所有导出方法不能修改指针和引用的值,可以修改的都不能导出。但比如*regexp.Regexp可以被多个goroutine访问,因为这个指针指向的值的所有方法都不会改变这个值的状态
  3. 一段简单的代码,
go func(){
  for _,job:=range jobList{
  jobs<-job  //阻塞,等待channel里有值传过来
}
close(jobs)
}()
  1. 一般发送端关闭channel而不是接收端
  2. 一段棒棒的代码,
func waitAndProcessResults(done <-chan struct{}, results <-chan Result) {
    for working := workers; working > 0; {
        select { // Blocking
        case result := <-results:
            fmt.Printf("%s:%d:%s\n", result.filename, result.lino,
                result.line)
        case <-done:
            working--
        }
    }
DONE:
    for {
        select { // Nonblocking
        case result := <-results:
            fmt.Printf("%s:%d:%s\n", result.filename, result.lino,
                result.line)
        default:
            break DONE
        }
    }
}
  1. 后面讲的是几个safeMap的例子,以代码讲解为主,就不记了,书上讲解详细的很

第八章

(之前写过一点文件处理的代码,是archive和unarchive,当然也涉及到读写,所以会跳过这部分相关的内容,记相对不太熟悉的内容)

  1. 主要在讲encode/json包func Marshal(v interface{}) ([]byte, error)func Unmarshal(data []byte, v interface{}) error的用法

  2. 额,没其他了好像。。

事实证明struct和interface包括func这类都适合边做项目边学或者边看项目边学,比干看书效率高一点,当然书上的例子也不错,跟着看或者边看边做也会很好。

所以此篇应该完结了,二刷这本书的可能性不大,毕竟书是借阅的。┑( ̄Д  ̄)┍

推荐阅读更多精彩内容