Go基础

基础语法

变量

定义

// 使用var关键字
var a, b, c bool
var s1, s2 string = "hello", "world"

// 可以放在函数内,或直接放在包内
// 使用var()集中定义变量

// 让编译器可以自动决定类型
var a, b, i, s1, s2 = true, false, 3, "hello", "world"

// 使用:=定义变量
a, b, i, s1, s2 := true, false, 3, "hello", "world"

只能在函数内使用

函数外部定义的变量可以不使用;函数内部定义的变量必须使用

函数外部的变量不是全局变量,是包内部变量 main包

变量类型写在变量名之后(与其他语言刚好相反)

没有定义类型变量,编译器可以推测变量类型

内建变量类型

​ 布尔型:bool
​ 字符串型:string
​ 整型: (u)int, (u)int8, (u)int16, (u)int32, (u)int64, uintptr
​ 无符号整数,加(u)
​ 有符号整数,不加(u)
​ 自增整数 uintptr
​ 整形分为规定长度 与 不规定长度(根据操作系统来定32位系统就是32位,64位系统就是64位)

byte:字节,其长度为8位
rune:(字符型,int32的别名)相当于其他语言当中的char类型,其长度为32位 [4字节]

​ 浮点型:float32, float64, complex64, complex128
​ complex 复数
​ complex64 实部和虚部分别是32位
​ complex128 实部和虚部分别是64位

​ 没有char 只有rule

​ 原生支持复数类型

复数回顾:

  • 实数是x轴上的数,复数是二维平面上的一个数 i = √-1
  • 复数:3 + 4i 实部3 虚部4i
  • |3 + 4i| = √3² + 4² = 5
  • i² = -1,i³ = -i,i⁴ = 1,i^5 = i,i^6 = -1,… 乘以i就是逆时针转90°
  • 最美公式 -- 欧拉公式

强制类型转换

go语言类型的转换是强制的,他没有隐式类型转换
(当数据从"小类型"转换成"大类型"时,被称为隐式类型转换,当数据从"大类型"转换为"小类型"时,被称为显示转换。显示类型转换被称为强制类型转换)

例如:

var a, b int = 3, 4
var c int = math.Sqrt(a * a + b * b) ❌
var c int = int(math.Sqrt(float64(a * a + b * b))) ✅

常量

定义

用const关键字定义常量,与变量的定义类似
const 数值可作为各种类型使用
常量定义后必须要赋值
常量不可以被改变

const filename = "abc.txt"
const a, b = 3, 4
var c int
c = int(math.Sqrt(a*a + b*b))
fmt.Println(filename, c, d)

// 使用常量定义枚举类型
// 普通枚举类型
const (
  cpp = 1
  java = 2
  python = 3
  golang = 4
)
fmt.Println(cpp, java, python, golang) // 1 2 3 4

// 自增枚举类型
const (
  cpp = iota + 10
  _
  java
  python
  golang
)
fmt.Println(cpp, java, python, golang) // 10 12 13 14
// iota 自增 _跳过当前自增值

// 更高级的用法
// b, kb, mb, gb, tb, pb
const(
  b = 1 << (10 * iota)
  kb
  mb
  gb
  tb
  pb
)
fmt.Println(b, kb, mb, gb, tb, pb) // 1 1024 1048576 1073741824 1099511627776 1125899906842624
// iota可以作为自增值的种子

运算符

条件语句

if语句

// if条件中不需要括号
const filename = "abc.txt"
contents, err := ioutil.ReadFile(filename)
if err != nil {
    fmt.Println(err)
} else {
    fmt.Printf("%s\n", contents)
}

// 或

const filename = "abc.txt"
if contents, err := ioutil.ReadFile(filename); err != nil{
    fmt.Println(err)
}else{
    fmt.Printf("%s\n", contents)
}
// if的条件可以赋值
// if的条件里赋值的变量作用域就在这个if语句里面

switch语句

// 默认情况下 case 后面 不需要加 break,程序会自动 break
// 如果不让 break 则需要加 fallthrough
// switch 后面可以没有表达式
g := ""
switch {
    case score < 60:
        g = "F"
    case score < 80:
        g = "C"
    case score < 90:
        g = "B"
    case score <= 100:
        g = "A"
    default:
        panic(fmt.Sprintf("Wrong score: %d", score))
}

循环语句

for语句

for后面没有括号

可以省略初始条件,相当于while

可以全部省略掉,相当于死循环

没有while语句

// 整数转二进制
func convertToBin(n int) string {
  result := ""
  for ; n > 0; n /= 2 {
    lsb := n % 2
    result = strconv.Itoa(lsb) + result
  }
  return result
}

// 可以省略初始条件和递增条件,相当于while
// 读取文件
func printFile(filename string) {
  file, err := os.Open(filename)
  if err != nil {
      panic(err)
    }
  // 一行一行的读
  scanner := bufio.NewScanner(file)
  for scanner.Scan() {
      fmt.Println(scanner.Text())
  }
}

// 可以全部省略掉,相当于死循环
// 死循环
func forever(){
  i := 1
  for {
      i += 1
      fmt.Println(i)
  }
}

函数

函数的定义

// 函数名在前,类型在后
func eval(a, b int, op string) int {
  switch op {
    case "+":
        return a + b
    case "-":
        return a - b
    case "*":
        return a * b
    case "/":
        return a / b
    default:
        panic("unsupported operation: " + op)
  }
}

多返回值函数

// 多返回值函数
func div(a, b int) (int, int) {
    return a / b, a % b
}
fmt.Println(div1(13, 3))
  • 函数返回多个值时可以起名字;
    ** 好处在于调用的地方可以直接生成返回值名字
    ** 仅用于非常简单的函数
    ** 对于调用者而言没有区别
  func div1(a, b int) (q, r int) {
      return a / b, a % b
  }

  func div2(a, b int) (q, r int) {
    q = a / b
    r = a % b
    return
  }

建议用第一种,因为函数体比较长的情况下不容易找到返回值是在哪里定义的,造成代码可读性差

只接收第一个返回值 _ 相当于占位或跳过

  q, _ = div1(13, 3)

多函数返回值一般用于两个返回值 一个为正确的返回内容,另一个为返回的错误信息,让调用者可以正确的处理错误。
例如:

func eval_f(a, b int, op string) (int, error) {
  switch op {
    case "+":
        return a + b, nil
    case "-":
        return a - b, nil
    case "*":
           return a * b, nil
    case "/":
        q, _ := div(a, b)
        return q, nil
    default:
      return 0, fmt.Errorf(
      "unsupported operation: %s", op)
  }
}

if result, err := eval_f(3, 4, "x"); err != nil{
    fmt.Println("Error:", err)
}else{
    fmt.Println(result)
}

函数式编程

函数式编程:可以给函数 传 函数参数

func apply(op func(int, int) int, a, b int) int {
  p := reflect.ValueOf(op).Pointer()
  opName := runtime.FuncForPC(p).Name()
  fmt.Printf("Calling function %s with args (%d, %d)", opName, a, b)
  return op(a, b)
}
func pow(a, b int) int{
    return int(math.Pow(float64(a), float64(b)))
}
fmt.Println(apply(pow, 3, 4)) // Calling function main.pow with args (3, 4)81
// 或
fmt.Println(apply(
  func(a, b int) int{
  return int(math.Pow(float64(a), float64(b)))
}, 3, 4)) // Calling function main.main.func1 with args (3, 4)81

可变参数列表

func sum(number ...int)int{
  s := 0
  for i := range number{
      s += number[i]
  }
  return s
}

没有默认参数,没有可选参数,没有函数重载,没有操作符重载

指针

指针不能运算

var a int = 2
var pa *int = &a
*pa = 3
fmt.Println(a)

值传递? 与 引用传递?

c++示例:

void pass_by_val(int a){
    a++;
}
void pass_by_val(int& a){
    a++;
}
int main(){
int a = 3;
pass_by_val(a);
printf("After pass_by_val: %d\n", a); // After pass_by_val: 3
pass_by_ref(a);
printf("After pass_by_ref: %d\n", a); // After pass_by_ref: 4

Go语言只有值传递一种方式

值传递
指针相当于引用传递
这里的cache是做为值来用(go语言中的 object 叫cache )

// 指针
func swap(a, b *int){
*b, *a = *a, *b
}
a := 3
b := 4
swap(&a, &b)
fmt.Println(a, b) // 4 3

// 值传递
func swap_cp(a, b int) (int, int){
b, a = a, b
return a, b
}
c := 3
d := 4
c, d = swap_cp(c, d)
fmt.Println(c, d)

内建容器

数组

定义

数量写在类型前面

// 
var arr1 [5]int // 指定长度
arr2 := [3]int{1, 3, 5}
arr3 := [...]int{2, 4, 6, 8, 10} // 不指定长度
fmt.Println(arr1, arr2, arr3)

// 二维数组
var grid [4][5]int
fmt.Println(grid)

// 遍历数组
// 通过获取数组长度循环遍历
for i := 0; i < len(arr3); i++ {
    fmt.Println(arr3[i])
}

// 通过 range 关键字遍历
// 只获取下标
for i := range arr3 {
    fmt.Println(i)
}

// 获取下标与值
for i, v := range arr3 {
    fmt.Println(i, v)
}

// 只获取值 , _下滑线省略变量
for _, v := range arr3 {
    fmt.Println(v)
}
数组是值类型
func printArray(arr [5]int){
  arr[0] = 100m
  for i, v := range arr {
      fmt.Println(i, v)
  }
}

func main() {
  var arr1 [5]int
  arr2 := [3]int{1, 3, 5}
  arr3 := [...]int{2, 4, 6, 8, 10}
  printArray(arr3)     // 100 4 6 8 10
  printArray(arr2)     // error 报错
  fmt.Println(arr3) // 2 4 6 8 10
}
// * [10]int 和 [20]int 是不同的类型
// * 调用func f(arr [10]int) 会 拷贝 数组

/** =========================================== */

// 不使用拷贝,使用指针
func printArray(arr *[5]int){
  arr[0] = 100
  for i, v := range arr {
    fmt.Println(i, v)
  }
}

func main() {
  var arr1 [5]int
  arr2 := [3]int{1, 3, 5}
  arr3 := [...]int{2, 4, 6, 8, 10}
  printArray(&arr3) // 100 4 6 8 10
  fmt.Println(arr3) // 100 4 6 8 10
}
// * 在go语言中,数组指针的方法一般是不用的,使用Slice

/** =========================================== */

// 不使用拷贝,使用指针,使用Slice
func printArray(arr [5]int){
  arr[0] = 100
  for i, v := range arr {
    fmt.Println(i, v)
  }
}
func main() {
  var arr1 [5]int
  arr2 := [3]int{1, 3, 5}
  arr3 := [...]int{2, 4, 6, 8, 10}
  printArray(arr3[:]) // 100 4 6 8 10
  fmt.Println(arr3) // 100 4 6 8 10
}

Slice (切片)

arr := [...]int{1, 2, 3, 4, 5, 6, 7}
// 区间2-6 * 计算机中的区间,一般都代表半开半闭区间
fmt.Println(arr[2:6])     // [3 4 5 6]
fmt.Println(arr[:6])         // [1 2 3 4 5 6]
fmt.Println(arr[2:])         // [3 4 5 6 7]
fmt.Println(arr[:])         // [1 2 3 4 5 6 7]
Slice不是值类型

Slice本身是没有数据的,是对底层array的一个view

[]不指定长度就是一个slice

func updateSlice(s []int){
    s[0] = 100
}

arr := [...]int{1, 2, 3, 4, 5, 6, 7}
s1 := arr[2:]         // [3 4 5 6 7]
s2 := arr[:]             // [1 2 3 4 5 6 7]
updateSlice(s1)
fmt.Println(s1)     // [100 4 5 6 7]
fmt.Println(arr)     // [1 2 100 4 5 6 7]
updateSlice(s2)
fmt.Println(s2)     // [100 2 100 4 5 6 7]
fmt.Println(arr)     // [100 2 100 4 5 6 7]
Reslice
fmt.Println(s2) // [100 2 100 4 5 6 7]
s2 = s2[:5]
fmt.Println(s2) // [100 2 100 4 5]
s2 = s2[2:]
fmt.Println(s2) // [100 4 5]
Slice的扩展
arr = [...]int{0, 1, 2, 3, 4, 5, 6}
s1 = arr[2:6]             // [2 3 4 5]
fmt.Println(s1[4])     // error 错误
s2 = s1[3:5]                 // [5 6]
// Slice底层的实现
// ptr : 指向slice开头的那个元素
// len : slice的长度
// cap : 从slice开头到 整个array的结束

// Slice可以向后扩展,不可以向前扩展
// s[i]不可以超过len,向后扩展不可以超越底层数组cap(s)
arr = [...]int{0, 1, 2, 3, 4, 5, 6, 7}
s1 = arr[2:6]         // [2 3 4 5]
s2 = s1[3:5]             // [5 6]
fmt.Println(s1, len(s1), cap(s1)) // 4 6
fmt.Println(s2, len(s2), cap(s2)) // 2 3
向Slice添加元素
arr = [...]int{0, 1, 2, 3, 4, 5, 6}
s2 = s1[3:5] // [5 6]
s3 := append(s2, 10) // [5 6 10]
s4 := append(s3, 11) // [5 6 10 11]
s5 := append(s4, 12) // [5 6 10 11 12]
fmt.Print(arr) // [0 1 2 3 4 5 6]

// * s4 s5 不再是arr的view
// 添加元素时如果超越cap,系统会重新分配更大的底层数组,并把原来的元素拷贝过去
// 原来的数组如果没有继续使用,系统会给回收掉(垃圾回收)


// 由于值传递的关系,必须接受append的返回值
s = append(s, val)
定义Slice
// 第一种:
var s []int // Zero value for slice is nil

// 第二种:(指定length)
s2 := make([]int, 6)
fmt.Printf("%v, len=%d, cap=%d",s2, len(s2), cap(s2)) // [0 0 0 0 0 0] , len=6, cap=6

// 第三种:指定length 和 capacity
s3 := make([]int, 6, 32)
fmt.Printf("%v, len=%d, cap=%d",s3, len(s3), cap(s3)) // [0 0 0 0 0 0] , len=6, cap=32

如果slice一开始比较短,但频繁操作后会变得很长,最好一开始就给slice定义好合适的cap

拷贝Slice
s1 := []int{2, 4, 6, 8}
s2 := make([]int, 6)
copy(s2, s1)                     // [2 4 6 8 0 0] 拷贝后面的元素到前面的元素中
删除Slice的元素
// 删除中间的第二个元素,只改变len,不会改变cap
s2 = append(s2[:1], s2[2:]...) // [2 4 6 8 0 0]
fmt.Printf("%v, len=%d, cap=%d",s2, len(s2), cap(s2)) // [2 6 8 0 0] , len=5, cap=6

// 取出第一个元素,删除第一个元素
front := s2[0]
fmt.Println(front)
s2 = s2[1:]

// 取出最后一个元素,删除最后一个元素
tail := s2[len(s2) - 1]
s2 = s2[:len(s2) - 1]
fmt.Println(tail)

Map

定义
// 第一种:
m := map[string]string{
"name": "ccmouse",
"course": "golang",
"site": "imooc",
}

// 第二种:make(map[string]int)
m2 := make(map[string]int) // m2 == empty map

// 第三种:
var m3 map[string]int // m3 == nil
Map的遍历
for k, v := range m {
    fmt.Println(k, v) // 每次打印出来的顺序可能不一样,因为map的key是无序的,map是一个hash map
}

map是一个无序的字典,不保证顺序;如需顺序,需手动对key排序(把所有的key拿出来加到slice里面,slice是可以排序的)
使用len获取元素的个数

获取Map值
fmt.Println(m["course"])
// key不存在时,获得value类型的初始值

// 取map中不存在的值的时候, 返回空行 zero value
causeName := m["cause"]
fmt.Println(causeName)

// 用 value, ok = m[key] 判断是否存在key
course, ok := m["course"]
fmt.Println(course, ok) // golang true
course, ok := m["course1"]
fmt.Println(course, ok) // false

if name, ok := m["names"];ok {
    fmt.Println(name)
} else {
  fmt.Println("key does not exist")
}
Map删除元素
delete(m, "site")
site, ok = m["site"]
fmt.Println(site, ok)
Map的key

map使用哈希表,必须可以比较相等
除了slice, map, function 的内建类型都可以作为key
自定义类型,Struct(结构)类型不包含上述字段,也可以作为key

例题:寻找最长不含有重复字符的子串
https://leetcode.com/problems/longest-substring-without-repeating-characters/description

对于每一个字母x
lastOccurrend[x]不存在,或者 < start → 无需操作 lastOccurrend[x] >= start → 更新start
更新lastOccurred[x],更新 maxLength

func lengthOfNonRepeatingSubStr(s string) int{
  lastOccurred := make(map[byte]int)
  start := 0
  maxLength := 0
  // F1 查看变量类型
  for i, ch := range []byte(s) {
    if lastI, ok := lastOccurred[ch];ok && lastI >= start {
      start = lastI + 1
    }
    if i - start + 1 > maxLength {
      maxLength = i - start + 1
    }
    lastOccurred[ch] = i
  }
    return maxLength
}

lengthOfNonRepeatingSubStr("abcabcbb")         // 3
lengthOfNonRepeatingSubStr("bbbbb")             // 1
lengthOfNonRepeatingSubStr("")                         // 0
lengthOfNonRepeatingSubStr("b")                     // 1
lengthOfNonRepeatingSubStr("abcdef")             // 6
lengthOfNonRepeatingSubStr("这里是慕课网")    // 9
lengthOfNonRepeatingSubStr("一二三二一")         // 5

/** ======================================================= */


// 要让上例支持中文,需要转成rune
func lengthOfNonRepeatingSubStr(s string) int{
  lastOccurred := make(map[rune]int)
  start := 0
  maxLength := 0
  // F1 查看变量类型
  for i, ch := range []rune(s) {
    if lastI, ok := lastOccurred[ch];ok && lastI >= start {
    start = lastI + 1
    }
    if i - start + 1 > maxLength {
      maxLength = i - start + 1
    }
    lastOccurred[ch] = i
  }
}
lengthOfNonRepeatingSubStr("一二三二一") // 3

字符和字符串

// rune 相当于go的char
s := "Yes我爱慕课网!" // UTF-8 可变长编码
fmt.Println(len(s)) // 19
fmt.Printf("%s", []byte(s)) // Yes我爱慕课网!

// 打印出来它里面字节的一个16进制数字
fmt.Printf("%X", []byte(s)) // 596573E68891E788B1E68595E8AFBEE7BD9121

for _, b := range []byte(s) {
    fmt.Printf("%X ", b) // 59 65 73 E6 88 91 E7 88 B1 E6 85 95 E8 AF BE E7 BD 91 21
}

// 使用range 遍历pos, rune对; ch is a rune (char32 四字节的整数)
for i, ch := range s{
    fmt.Printf("(%d %X) ", i, ch) // (0 59) (1 65) (2 73) (3 6211) (6 7231) (9 6155) (12 8BFE) (15 7F51) (18 21)
}
utf8.RuneCountInString(s) // 9

bytes := []byte(s)
fmt.Println(bytes) // [89 101 115 230 136 145 231 136 177 230 133 149 232 175 190 231 189 145 33]
fmt.Println(len(bytes)) // 19

for len(bytes) > 0 {
    ch, size := utf8.DecodeRune(bytes)
    bytes = bytes[size:]
    fmt.Printf("%c ", ch) // Y e s 我 爱 慕 课 网 !
}

for i, ch := range []rune(s) {
    fmt.Printf("(%d %c) ", i, ch) // (0 Y) (1 e) (2 s) (3 我) (4 爱) (5 慕) (6 课) (7 网) (8 !)
}
字符串操作

使用utf8.RuneCountInString 获得字符数量
使用len 获得字节长度
使用[]byte 获得所有的字节
使用[]rune 会进行所有的转换,把这些转换好的东西放在一个数组里面,开一个rune slice

其他字符串操作

Fileds, Split, Join // 字符串分开与合并
Contains, Index // 查找子串
ToLower, ToUpper
Trim, TrimRight, TrimLeft
… …

面向“对象”

go语言仅支持封装,不支持继承和多态
go语言没有class, 只有struct(结构体)

结构体

结构的创建

type treeNode struct {
  value int
  left, right *treeNode
}
var root treeNode
root = treeNode{value:3 }
root.left = &treeNode{}
root.right = &treeNode{5, nil, nil}
root.right.left = new(treeNode)

// 不论地址还是结构本身,一律使用 . 来访问成员
// go语言没有构造函数,有些时候需要自己来控制它的构造

// 使用自定义工厂函数,只是一些普通的函数
func createNode(value int) *treeNode{
    return &treeNode{value: value} // go语言中 局部变量地址 也可以返回给调用者使用
}

var root treeNode
root.left = &treeNode{}
root.left.right = createNode(2)
结构创建是在堆上还是栈上?

由 go语言的编译器加上它的运行环境 决定;
上面的例子中:
如果 treeNode 没有取地址,并且返回出去,编译器很可能认为这个变量不需要给外部用,就在栈上分配;
如果 treeNode 取了地址,并且返回出去,那么treeNode就会在堆上分配,分配完了以后,这个treeNode就会参与垃圾回收,外面的使用者使用完了这个指针以后,他就会被回收掉。

为结构定义方法

// 显示定义和命名 方法接收者
func (node treeNode) print(){ // node 为接收者,print函数是给node接收的,这里的node为值传递
    fmt.Print(node.value, " ")
}
使用指针作为方法接受者

只有使用指针才可以改变结构内容

// nil指针也可以调用方法
func (node *treeNode) setValue(value int){ // 修改value的值,设置treeNode为指针,作为地址传递
  if node == nil {
    fmt.Println("Setting value too nil node. Ignored.")
    return
  }
  node.value = value
}

// 遍历treeNode
func (node *treeNode) traverse() {
  if node == nil {
      return
  }
  node.left.traverse()
  node.print()
  node.right.traverse()
}

func main() {
  var root treeNode
  root.left = &treeNode{}
  root = treeNode{value:3 }
  root.print()                 // 3

  root.left.setValue(3)
  root.left.print()     // 3
  pRoot := &root
  pRoot.print()             // 3
  pRoot.setValue(200)
  pRoot.print()             // 200

  var cRoot *treeNode // nil
  cRoot.setValue(200) // Setting value too nil node. Ignored.
  cRoot = &root
  cRoot.setValue(300)
  cRoot.print()             // 300

  root.traverse()         // 3 2 300 0 5
}
值接收者 vs 指针接收者

要改变内容必须使用指针接收者
结构过大也考虑使用指针接收者
一致性:如果有指针接收者,最好都是指针接收者

值接收者 是go语言特有的
值/指针 接收者均可接收值/指针

封装

名字一版使用CamelCase(多个单词,每个单词的首字母大写,连在一起)
首字母大写代表 public;首字母小写代表 private(不仅对方法适用,对结构、常量等所有的定义都是这样)
public , private是针对包来说的

每个目录一个包,包名可以跟目录名不一样,每个目录只有一个包
main 包 包含可执行入口
为结构定义的方法必须放在同一个包内
可以为不同的文件

扩展类型

如何扩充系统类型或别人的类型

使用组合

依赖管理

GOPATH 和 GOVENDOR

GOPATH环境变量
默认在 ~/go (unix, linux), %USERPROFILE%\go (windows)
官方推荐:所有项目和第三方库都放在同一个GOPATH下
也可以将每个项目放在不同的GOPATH下

go get 获取第三方库
go get 命令演示
使用gopm 来获取无法下载的包
go get -v github.com/gpmgo/gopm
go build 来编译
go install 产生 pkg 文件和可执行文件
go run 直接编译运行

├ go
├── src -- git pository 包括自己的一些项目,没个占一个目录
├── git repository 1
├── git repository 2
├── pkg -- build过程 中间的一些文件
├── git repository 1
├── git repository 2
├── bin -- 编译产生的可执行文件
├── 执行文件 1, 2, 3…

go install ./… 安装当前目录下面所有的包

Go mod

面向接口

接口的概念

duck typing
描述事物的外部行为而非内部结构
严格说go属于结构化类型系统,类似duck typing

接口定义

接口由使用者定义
接口里面都是方法,不需要加func关键字
接口的实现是隐式的,只要实现里面的方法

type Retriever interface {
    Get(url string) string
}

func download(r Retriever) string{
    return r.Get("https://www.imooc.com")
}

一般不会用接口的指针,通常接口的肚子里含了一个指针

接口的值类型

接口变量包含两种:实现者的类型、实现者的值/指针

// 有两种方法来判断:
// 获取接口实例的实际类型 .(type) 必须配合switch来使用
func inspect(r Retriever) {
  switch v := r.(type){
      case mock.Retriever:
          fmt.Println("Contents:", v.Contents)
      case *real.Retriever:
          fmt.Println("UserAgent:", v.UserAgent)
  }
}

// Type assertion : 类型断言
if mockRetriever, ok := r.(mock.Retriever); ok{
    fmt.Println(mockRetriever.Contents)
}else{
    fmt.Println("not a mock retriever")
}

接口变量自带指针

接口变量自带指针
接口变量采用值传递,几乎不需要使用接口的指针
指针接收者实现只能以指针方式使用;值接收者都可

// 查看接口变量:
// 表示任何类型:interface{}
type Queue []interface{}
func (q *Queue) Push(v interface{}) {
    *q = append(*q, v)
}

Type Assertion

Type Switch

接口组合

type Retriever interface {
    Get(url string) string
}
type Poster interface {
    Post(url string, form map[string]string) string
}
type RetrieverPoster interface {
  Retriever
  Poster
}
func session(s RetrieverPoster) string{
    s.Post(url, map[string]string{
        "contents": "another faked imooc.com",
    })
    return s.Get(url)
}
func main(){
  var r Retriever
  retriever := mock.Retriever{"this is fake imooc.com"}
  fmt.Println(session(&retriever))
}

常用的系统接口

Stringer
Reader/Writer

函数式编程

函数与闭包

fmt格式“占位符”

golang 的fmt 包实现了格式化I/O函数,类似于C的 printf 和 scanf。

// 定义示例类型和变量
type Human struct { 
  Name string
}
var people = Human{Name:"zhangsan"}

普通占位符

占位符说明举例输出
%v相应值的默认格式。Printf("%v", people){zhangsan}
%+v打印结构体时,会添加字段名Printf("%+v", people){Name:zhangsan}
%#v相应值的Go语法表示Printf("#v", people)main.Human{Name:"zhangsan"}
%T相应值的类型的Go语法表示Printf("%T", people)main.Human
%%字面上的百分号,并非值的占位符Printf("%%")%

布尔占位符

占位符说明举例输出
%ttrue 或 falsePrintf("%t", true)true

整数占位符

占位符说明举例输出
%b二进制表示Printf("%b", 5)101
%c相应Unicode码点所表示的字符Printf("%c", 0x4E2D)
%d十进制表示Printf("%d", 0x12)18
%o八进制表示Printf("%d", 10)12
%q单引号围绕的字符字面值,由Go语法安全地转义Printf("%q", 0x4E2D)'中'
%x十六进制表示,字母形式为小写 a-fPrintf("%x", 13)d
%X十六进制表示,字母形式为大写 A-FPrintf("%x", 13)D
%UUnicode格式:U+1234,等同于 "U+%04X"Printf("%U", 0x4E2D)U+4E2D

浮点数和复数的组成部分(实部和虚部)

占位符说明举例输出
%b无小数部分的,指数为二的幂的科学计数法
与 strconv.FormatFloat 的 'b' 转换格式一致。
-1234.456e+78
%e科学计数法,例如 -1234.456e+78Printf("%e", 10.2)1.020000e+01
%E科学计数法,例如 -1234.456E+78Printf("%e", 10.2)1.020000E+01
%f有小数点而无指数,例如 123.456Printf("%f", 10.2)10.200000
%g根据情况选择 %e 或 %f 以产生更紧凑的(无末尾的0)输出Printf("%g", 10.20)10.2
%G根据情况选择 %E 或 %f 以产生更紧凑的(无末尾的0)输出Printf("%G", 10.20+2i)(10.2+2i)

字符串与字节切片

占位符说明举例输出
%s输出字符串表示(string类型或[]byte)Printf("%s", []byte("Go语言"))Go语言
%q双引号围绕的字符串,由Go语法安全地转义Printf("%q", "Go语言")"Go语言"
%x十六进制,小写字母,每字节两个字符Printf("%x", "golang")676f6c616e67
%X十六进制,大写字母,每字节两个字符Printf("%X", "golang")676F6C616E67

指针

占位符说明举例输出
%p十六进制表示,前缀 0xPrintf("%p", &people)0x4f57f0

其它标记

占位符说明举例输出
+总打印数值的正负号;对于%q(%+q)保证只输出ASCII编码的字符。Printf("%+q", "中文")"\u4e2d\u6587"
-在右侧而非左侧填充空格(左对齐该区域)
#备用格式:为八进制添加前导 0(%#o)

为十六进制添加前导 0x(%#x)或 0X(%#X),为 %p(%#p)去掉前导 0x;
如果可能的话,%q(%#q)会打印原始 (即反引号围绕的)字符串;
如果是可打印字符,%U(%#U)会写出该字符的 Unicode 编码形式(如字符 x 会被打印成 U+0078 'x')。
Printf("%#U", '中')U+4E2D
' '(空格)为数值中省略的正负号留出空白(% d);
以十六进制(% x, % X)打印字符串或切片时,在字节之间用空格隔开
0填充前导的0而非空格;对于数字,这会将填充移到正负号之后

golang没有 '%u' 点位符,若整数为无符号类型,默认就会被打印成无符号的。

宽度与精度的控制格式以Unicode码点为单位。宽度为该数值占用区域的最小宽度;精度为小数点之后的位数。

操作数的类型为int时,宽度与精度都可用字符 '*' 表示。

对于 %g/%G 而言,精度为所有数字的总数,例如:123.45,%.4g 会打印123.5,(而 %6.2f 会打印123.45)。

%e 和 %f 的默认精度为6

对大多数的数值类型而言,宽度为输出的最小字符数,如果必要的话会为已格式化的形式填充空格。

而以字符串类型,精度为输出的最大字符数,如果必要的话会直接截断。

暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇