基础语法
变量
定义
// 使用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
例题:寻找最长不含有重复字符的子串
对于每一个字母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("%%") | % |
布尔占位符
占位符 | 说明 | 举例 | 输出 |
---|---|---|---|
%t | true 或 false | Printf("%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-f | Printf("%x", 13) | d |
%X | 十六进制表示,字母形式为大写 A-F | Printf("%x", 13) | D |
%U | Unicode格式:U+1234,等同于 "U+%04X" | Printf("%U", 0x4E2D) | U+4E2D |
浮点数和复数的组成部分(实部和虚部)
占位符 | 说明 | 举例 | 输出 |
---|---|---|---|
%b | 无小数部分的,指数为二的幂的科学计数法 与 strconv.FormatFloat 的 'b' 转换格式一致。 | -1234.456e+78 | |
%e | 科学计数法,例如 -1234.456e+78 | Printf("%e", 10.2) | 1.020000e+01 |
%E | 科学计数法,例如 -1234.456E+78 | Printf("%e", 10.2) | 1.020000E+01 |
%f | 有小数点而无指数,例如 123.456 | Printf("%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 | 十六进制表示,前缀 0x | Printf("%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
对大多数的数值类型而言,宽度为输出的最小字符数,如果必要的话会为已格式化的形式填充空格。
而以字符串类型,精度为输出的最大字符数,如果必要的话会直接截断。