go语言基础笔记---imooc

1、导学

goland插件:file watcher 添加

可以在保存后自动格式化代码

2、 go基础语法

1. 变量

package main

import "fmt"

var aa = 3     //作用于为包内,go没有全局变量的概念
var ss = "kkk" //函数外面也可以定义变量,但是必须使用var,不可以使用冒号等号

var ( //批量定义方式
	aaa = 3
	bbb = 4
	ccc = true
)

func variableZeroValue() {
	var a int
	var s string
	fmt.Printf("%d %q\n", a, s) //%q可以将字符串的引号打出来
}

func variableInitValue() {
	var a, b int = 3, 4
	var s string = "abc"

	fmt.Println(a, b, s) //定义的变量一定要用到,不然会报错
}

func variableTypeDeduction() { //自动推断变量类型
	var a, b, c, s = 3, 4, true, "def"
	fmt.Println(a, b, c, s)
}

func variableShorter() {
	a, b, c, s := 3, 4, true, "def" //推荐定义方式
	b = 5                           //再次赋值不可以用冒号
	fmt.Println(a, b, c, s)
}
func main() {
	fmt.Println("hello  world")
	variableZeroValue()
	variableInitValue()
	variableTypeDeduction()
	variableShorter()
	fmt.Println(aaa, bbb, ccc)
}

输出

hello  world
0 ""
3 4 abc
3 4 true def
3 5 true def
3 4 true

注意

:=只能在函数内部使用,在函数外只能使用var定义变量

2. 内建变量类型

bool
string
uint    //长度取决于操作系统的位数 
		// (u)int,(u)int8,(u)int16,(u)int32,(u)int64
uintptr  //指针
byte  //8位
rune //字符型,32位,相当于其他的语言的char
float32  // float64   需要明确指出float和complex的位数
complex64 //complex128   ,复数

在多国语言的环境下,一个字节的char会造成很多麻烦,所以go语言采用了4字节的rune代表字符

func euler() {
	c := 3 + 4i
	fmt.Println(cmplx.Abs(c))
	fmt.Println(cmplx.Pow(math.E, 1i*math.Pi) + 1)
	fmt.Printf("%.3f", cmplx.Exp(1i*math.Pi)+1)
}

结果

5
(0+1.2246467991473515e-16i)
(0.000+0.000i)

强制类型转换

  • 类型转换是强制的,go没有隐式类型转换
func triangle() {
	var a, b int = 3, 4
	var c int
    //错误写法:c = math.Sqrt(a*a + b*b)
	c = int(math.Sqrt(float64(a*a + b*b)))
	fmt.Println(c)
}

func Sqrt(x float64) float64 {
	if haveArchSqrt {
		return archSqrt(x)
	}
	return sqrt(x)
}

Sqrt需要的是float64类型的参数,因此要进行强制类型转换,返回值类型是float64,也要强制转换为int

3. 常量和枚举

常量

const filename = "abc.txt"
func consts() {
	const a, b = 3, 4
	var c int
	c = int(math.Sqrt(a*a + b*b))

	fmt.Println(filename, c)
}

如果定义常量时不指定类型,那么常量的数值可以作为各种类型使用,因此sqrt内部不需要进行类型转换。

一般go语言中常量不大写


枚举

func enums() {
	const (
		cpp = iota
		_
		goland
		python
		javascript
	)

	//b, kb, mb, gb, tb, pb
	const (
		b = 1 << (10 * iota)
		kb
		mb
		gb
		tb
		pb
	)

	fmt.Println(cpp, javascript, goland, python) //0 4 2 3
	fmt.Println(b, kb, mb, gb, tb, pb) //1 1024 1048576 1073741824 1099511627776 1125899906842624

}

iota表示自动编号,可以参与运算


变量定义要点回顾

  • 变量类型写在变量名之后
  • 编译器可以推测变量类型
  • 没有char,只有rune
  • 原生支持复数类型

4.条件语句

if: 条件不需要加括号

func test01() {
	const filename = "abc.txt"
	contents, err := ioutil.ReadFile(filename)
	if err != nil {
		fmt.Println(err)
	} else {
		fmt.Printf("%s\n", contents)
	}
}
//第二种写法
func test02() {
	const filename = "abc.txt"
	if contents, err := ioutil.ReadFile(filename); err != nil {
		fmt.Println(err)
	} else {
		fmt.Printf("%s\n", contents)
	}
}
  • if的条件里可以赋值
  • 条件里赋值的变量的作用域仅限于这个if语句内部

switch:自动break,除非使用fallthrough

//swich后可以不带表达式
func grade(score int) string {
	g := ""
	switch {
	case score < 0 || score > 100:
		panic(fmt.Sprintf("Wrong score: %d", score))
	case score < 60:
		g = "D"
	case score < 80:
		g = "C"
	case score < 90:
		g = "B"
	case score <= 100:
		g = "A"
	}
	return g
}

5. 循环

for条件里不需要括号,条件里可以省略初始条件,结束条件,递增表达式

//十进制转二进制:除以2反序取余
func convertToBinary(n int) string {
	if n == 0 {
		return strconv.Itoa(0)
	}
	result := ""
	for ; n > 0; n /= 2 {
		lsb := n % 2                        //取余
		result = strconv.Itoa(lsb) + result //反序
	}
	return result
}

func printFile(filename string) {
	file, err := os.Open(filename)
	if err != nil {
		panic(err)
	}

	scanner := bufio.NewScanner(file)
	for scanner.Scan() { //相当于while
		fmt.Println(scanner.Text())
	}
}

goland快捷键:自动生成函数返回值变量名:ctrl+shift+v


基本语法要点

  • for , if 后面条件没有括号
  • if条件里也可以定义变量
  • 没有while
  • switch不需要break,也可以直接switch多个条件

6. 函数

func eval(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 "/":
		//return a / b
		q, _ := div1(a, b)
		return q, nil
	default:
		return 0, fmt.Errorf("unsupported operation: %s", op)
	}
}

// 13 / 3 =  4.... 1
func div(a, b int) (int, int) {
	return a / b, a % b
}
func div1(a, b int) (q, r int) {  //返回值起别名
	q = a / b
    r = a % b
    return 
}

可以将函数当做形参传入函数内

func apply(op func(int, int) int, a, b int) int {
	pointer := reflect.ValueOf(op).Pointer()
	opName := runtime.FuncForPC(pointer).Name()
	fmt.Printf("Calling function %s with args "+
		"(%d, %d)\n", opName, a, b)
	return op(a, b)
}

func pow(a, b int) int {
	return int(math.Pow(float64(a), float64(b)))
}

func main() {
	fmt.Println(apply(pow, 3, 2))
}

输出

Calling function main.pow with args (3, 2)
9

可变参数列表

func sum(numbers ...int) int {
	sum := 0
	for i := range numbers {
		sum += numbers[i]
	}
	return sum
}
func main() {
	fmt.Println(sum(1, 2, 3, 4, 5, 6, 7, 8, 9))
}

函数语法要点

  • 返回值类型写在最后
  • 可以返回多个值
  • 函数作为参数
  • 没有默认参数、可选参数
  • 有可变参数列表

7. 指针

var a int = 3
var pa *int = &a
*pa = 5

指针不能运算

go语言只有值传递这一种方式

可以用指针来实现引用传递

func swap(a, b *int) {
	*b, *a = *a, *b
}
func swap1(a, b int) (int, int) {
	return b, a
}

func main() {
	a, b := 3, 4
	swap(&a, &b)
	fmt.Println(a, b)  //4 3
    
    c, d := 5, 6
	c, d = swap1(c, d)
	fmt.Println(c, d) //6 5
}

8. 数组

数组定义:

var arr1 [5]int
arr2 := [3]int{1, 3, 4}
arr3 := [...]int{2, 4, 5, 6, 7} //让编译器自动判断有多少数据,需要使用...
var grid [4][5]int   //4个长度为5的数组
//[[0 0 0 0 0] [0 0 0 0 0] [0 0 0 0 0] [0 0 0 0 0]]

数组遍历:

for i := 0; i < len(arr3); i++ {
    fmt.Println(arr3[i])
}
for i, v := range arr3 {
    fmt.Println(i, v)
}
for _, v := range arr3 {
    fmt.Println(v)
}
  • 使用下划线_来省略遍历
  • 类似于java中的:for each

数组是值类型

func printArray(arr [5]int) {
	arr[0] = 100
	for i, v := range arr {
		fmt.Println(i, v)
	}
}
func main() {
	arr3 := [...]int{2, 4, 6, 8, 10} 

	fmt.Println("Modified arr3:")
	printArray(arr3)
	fmt.Println("arr3:")
	fmt.Println(arr3)
}
/* Result:
Modified arr3:
0 100
1 4
2 6
3 8
4 10
arr3:
[2 4 6 8 10]
*/
  • [10]int[20]int是不同的类型
  • 调用func f(arr [10]int)拷贝数组,与java不同

可以传入数组的引用

func printArray2(arr *[5]int) {
	arr[0] = 100
	for i, v := range arr {
		fmt.Println(i, v)
	}
}
func main() {
	arr3 := [...]int{2, 4, 6, 8, 10} 
    
	fmt.Println("==============")
	fmt.Println("Modified arr3:")
	printArray2(&arr3) //注意需要使用引用&
	fmt.Println("arr3:")
	fmt.Println(arr3)
}
/*Result
Modified arr3:
0 100
1 4
2 6
3 8
4 10
arr3:
[100 4 6 8 10]
*/
  • go语言中一般不直接使用数组

9. 切片Slice

func main() {
	arr := [...]int{0, 1, 2, 3, 4, 5, 6, 7, 8}
	s := arr[2:6] //左闭右开
	s1 := arr[:6] 
	s2 := arr[2:] 
	s3 := arr[:]  

	fmt.Println("arr[2:6] =", s)
	fmt.Println("arr[:6] =", s1)
	fmt.Println("arr[2:] =", s2)
	fmt.Println("arr[:] =", s3)
}
/*
arr[2:6] = [2 3 4 5]
arr[:6] = [0 1 2 3 4 5]
arr[2:] = [2 3 4 5 6 7 8]
arr[:] = [0 1 2 3 4 5 6 7 8]
*/

Slice是数组的视图

func update(arr []int) {
	arr[1] = 100
}
func main() {
	arr := [...]int{0, 1, 2, 3, 4, 5, 6, 7, 8}
	s1 := arr[:6]

	fmt.Println("s1: ", s1)
	fmt.Println("After update")
	update(s1)
	fmt.Println("s1: ", s1)
	fmt.Println("arr:", arr)
}
/*
s1:  [0 1 2 3 4 5]
After update
s1:  [0 100 2 3 4 5]
arr: [0 100 2 3 4 5 6 7 8]
*/

使用数组的方法:切片

func printArray3(arr []int) {
	arr[0] = 100
	for _, v := range arr {
		fmt.Print(" ", v)
	}
}

printArray3(arr3[:])

Reslice

func main() {
	arr := [...]int{0, 1, 2, 3, 4, 5, 6, 7, 8}
	s2 := arr[2:]

	fmt.Println("Reslice")
	fmt.Println(s2)
	s2 = s2[:5]
	fmt.Println(s2)
	s2 = s2[2:]
	fmt.Println(s2)
}
/*
Reslice
[2 3 4 5 6 7 8]
[2 3 4 5 6]
[4 5 6]
*/

Slice扩展

问题1:

arr := [...]int{0, 1, 2, 3, 4, 5, 6, 7}	
s1 = arr[2:6]
s2 = s1[3:5]

s1 = ?  
s2 = ?
-----------
s1=  [2 3 4 5]
s2=  [5 6]
image-20220512221835852 image-20220512221927925
  • slice可以向后扩展,不可以向前扩展
  • s[i]不可以超越len(s),向后扩展不可以超越底层数组cap(s)

问题2:

arr := [...]int{0, 1, 2, 3, 4, 5, 6, 7}	
s2 = s1[3:5]
s3 = append(s2, 10)
s4 := append(s3, 11)
s5 := append(s4, 12)

s3 = ?
s4 = ?
s5 = ?
arr = ?
-------
[5 6 10] [5 6 10 11] [5 6 10 11 12]
[0 1 2 3 4 5 6 10]
  • 添加元素是如果超越cap,系统会重新分配更大的底层数组,原数组若无人使用则会被自动回收
  • 由于值传递的关系,必须接收append的返回值
  • s = append(s, val)

切片的创建

var s []int;
s1 := []int{2,4,6,8}
s2 := make([]int, 16)
s3 := make([]int, 16, 32)//长度为16,容量为32的切片
  • 切片容量的扩容按照2的倍数递增

切片内容的复制、删除

func printSlice(s []int) {
	fmt.Printf("%v, len=%d, cap=%d\n", s, len(s), cap(s))
}
func main() {
	var s []int
	s1 := []int{2, 4, 6, 8}
	s2 := make([]int, 16)
	s3 := make([]int, 16, 32) //长度为16,容量为32的切片

	fmt.Println(s, s1, s2, s3)

	fmt.Println("Coping elements from slices")
	copy(s2, s1)
	printSlice(s2)

	fmt.Println("Deleting elements from slices")
	s2 = append(s2[:3], s2[4:]...)
	printSlice(s2)

	fmt.Println("Poping from front")
	front := s2[0]
	s2 = s2[1:]
	fmt.Println(front)
	printSlice(s2)

	fmt.Println("Poping from tail")
	tail := s2[len(s2)-1]
	s2 = s2[:len(s2)-1]
	fmt.Println(tail)
	printSlice(s2)
}
-----------
[] [2 4 6 8] [0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0] [0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0]
Coping elements from slices
[2 4 6 8 0 0 0 0 0 0 0 0 0 0 0 0], len=16, cap=16
Deleting elements from slices
[2 4 6 0 0 0 0 0 0 0 0 0 0 0 0], len=15, cap=16
Poping from front
2
[4 6 0 0 0 0 0 0 0 0 0 0 0 0], len=14, cap=15
Poping from tail
0
[4 6 0 0 0 0 0 0 0 0 0 0 0], len=13, cap=15

10. Map

创建:

m := map[string]string{
    "name":    "gwf",
    "course":  "golang",
    "site":    "imooc",
    "quality": "notbad",
}
m2 := make(map[string]int) //m2 == empty map
var m3 map[string]int      //m3 == nil
fmt.Println(m, m2, m3)
----------output:-------------
map[course:golang name:gwf quality:notbad site:imooc] map[] map[]

读取元素:

  • key不存在时,获得value的nil

  • value,ok:= m[key]来判断是否存在key

courseName, ok := m["course"]
fmt.Println(courseName, ok)
if courseName1, ok := m["corse"]; ok {
    fmt.Println(courseName1, ok)
} else {
    fmt.Println("key does not exist")
}
----------output-----------
golang true
key does not exist

删除元素:

name, ok := m["name"]
fmt.Println(name, ok)
delete(m, "name")
name, ok = m["name"]
fmt.Println(name, ok)
---------output------------
gwf true
 false

遍历

  • 使用range遍历key,或者遍历key,value对
  • 不保证遍历顺序,如需顺序,需要手动对key进行排序

map中的key

  • map使用hash表,必须可以比较相等
  • 除了slice,map,function的内建类型都可以作为key
  • struct类型不包含上述字段,也可作为key

例题:寻找最长不含有重复字符的子串

剑指 Offer 48. 最长不含重复字符的子字符串 - 力扣(LeetCode)

请从字符串中找出一个最长的不包含重复字符的子字符串,计算该最长子字符串的长度。

示例 1:

输入: "abcabcbb"
输出: 3 
解释: 因为无重复字符的最长子串是 "abc",所以其长度为 3。

示例 2:

输入: "bbbbb"
输出: 1
解释: 因为无重复字符的最长子串是 "b",所以其长度为 1。

示例 3:

输入: "pwwkew"
输出: 3
解释: 因为无重复字符的最长子串是 "wke",所以其长度为 3。
     请注意,你的答案必须是 子串 的长度,"pwke" 是一个子序列,不是子串。

提示:

s.length <= 40000

思路

对于每一个字母x,

  • lastOccurred[x]不存在,或者小与start --->无需操作
  • lastOccurred[x]大于等于start --->更新start
  • 更新lastOccurred[x],更新maxLenth
func lengthOfNonRepeatingSubstr(s string) int {
	lastOccurred := make(map[byte]int)
	maxLength := 0
	start := 0
	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
}

11. 字符和字符串的处理rune

func main() {
	s := "yes我爱慕课网!"

	fmt.Println(s)

	for _, ch := range []byte(s) {
		fmt.Printf("%X ", ch)
	}
	fmt.Println()

	for i, ch := range s { //ch is a rune
		fmt.Printf("(%d %X) ", i, ch)
	}
	fmt.Println()

	fmt.Println("Rune count:", utf8.RuneCountInString(s))

	bytes := []byte(s)
	for len(bytes) > 0 {
		ch, size := utf8.DecodeRune(bytes)
		bytes = bytes[size:]
		fmt.Printf("%c ", ch)
	}
	fmt.Println()

	for i, ch := range []rune(s) {
		fmt.Printf("(%d %c) ", i, ch)
	}

}
yes我爱慕课网!
79 65 73 E6 88 91 E7 88 B1 E6 85 95 E8 AF BE E7 BD 91 21 
(0 79) (1 65) (2 73) (3 6211) (6 7231) (9 6155) (12 8BFE) (15 7F51) (18 21) 
Rune count: 9
y e s 我 爱 慕 课 网 ! 
(0 y) (1 e) (2 s) (3 我) (4 爱) (5 慕) (6 课) (7 网) (8 !) 
  • 使用range遍历pos,rune对
  • 使用utf8.RuneCountInString(s)获得字符串s的字符数量
  • 使用len获得字节长度
  • 使用[]byte获得全部字节

例题:寻找最长不含有重复字符的子串

国际版:支持所有字符

func lengthOfNonRepeatingSubstr(s string) int {
	lastOccurred := make(map[rune]int)
	maxLength := 0
	start := 0
	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
	}
	return maxLength
}

将byte换为rune即可

12. 面向对象--结构体

  • go语言仅支持封装,不支持继承和多态
  • go语言没有class,只有struct
type treeNode struct {
	value       int
	left, right *treeNode
}

func main() {
	var root treeNode

	root = treeNode{value: 3}
	root.left = &treeNode{}
	root.right = &treeNode{5, nil, nil}
	root.right.left = new(treeNode)

	nodes := []treeNode{
		{value: 3},
		{},
		{6, nil, &root},
	}

	fmt.Println(nodes)
}

root.right.left = new(treeNode):不论是地址还是结构本身,一律使用.来访问成员

  • go没有构造函数,可以用自定义工厂函数代替,如
func creatNode(value int) *treeNode {
	return &treeNode{value: value}   //与cpp不同,go语言可以向外界返回局部变量
}
  • 注意:返回了局部变量的地址

**结构创建在堆上还是栈上? **

  • 不需要知道

结构体方法

//结构体方法
func (node treeNode) print() {   //node传递的是值
	fmt.Print(node.value)
}

//使用
node.print()

node传递的是值

func (node treeNode) setValue(value int) {  //值传递
	node.value = value
}
func (node *treeNode) setValue1(value int) {   //引用传递
	node.value = value
}

root.right.left = new(treeNode)
root.right.left.setValue(4)
root.right.left.print()   // 0

root.right.left.setValue1(4)
root.right.left.print() // 4

编译器会自动确定传入的是地址还是值

  • 只有指针才能改变结构体内容
  • nil指针也可以调用方法
func (node *treeNode) setValue1(value int) {
	if node == nil {
		fmt.Println(" Setting value to nil. Ignored.")
		return
	}
	node.value = value
}

var proot *treeNode
proot.setValue1(300)
proot = &root   //root非空
proot.setValue1(500)
proot.print()
---------
 Setting value to nil. Ignored.
500

中序遍历

//中序遍历
func (node *treeNode) traverse() {
	if node == nil {
		return
	}
	//go支持nil调用方法,因此不必判断node.left != nil
	node.left.traverse()
	node.print()
	node.right.traverse()
}

值接收者 VS 指针接受者

  • 要改变内容必须使用指针接收者
  • 结构过大考虑使用指针接收者
  • 一致性:如果有指针接收者,最好都是指针接收者
  • 值接收者是go语言特有
  • 值/指针接收者均可接收值/指针

13. 封装

  • 名字一般使用CamelCase
  • 首字母大写代表public,首字母小写代表private --->针对包而言

  • 每个目录一个包
  • 包名与目录名不必相同
  • main包包含可执行入口
  • 为结构体定义的方法必须放在同一个包内,可以是不同的文件

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

  • 定义别名:最简单
  • 使用组合:最常用
  • 使用内嵌:可以省下很多代码

组合

type myTreeNode struct {
	node *tree.Node
}

func (myNode *myTreeNode) postOrder() {
	if myNode == nil || myNode.node == nil {
		return
	}

	left := myTreeNode{myNode.node.Left}
	left.postOrder()
	right := myTreeNode{myNode.node.Right}
	right.postOrder()
	myNode.node.Print()
}

myRoot := myTreeNode{&root}
myRoot.postOrder()

别名

type Queue []int

func (q *Queue) Push(v int) {
	*q = append(*q, v)
}

func (q *Queue) Pop() int {
	head := (*q)[0]
	*q = (*q)[1:]
	return head
}

func (q *Queue) IsEmpty() bool {
	return len(*q) == 0
}
func main() {
	q := queue.Queue{1, 2}
	q.Push(3)
	q.Push(4)
	fmt.Println(q.Pop())
	fmt.Println(q.Pop())
	fmt.Println(q.Pop())
	fmt.Println(q.IsEmpty())
	fmt.Println(q.Pop())
	fmt.Println(q.IsEmpty())
	//初始的q和后面的q不是同一个q,push和pop操作改变了指针
}

使用内嵌的方式来扩展已有的类型

type myTreeNode struct {
	*tree.Node //Embedding
}

func (myNode *myTreeNode) postOrder() {
	if myNode == nil || myNode.Node == nil {
		return
	}

	left := myTreeNode{myNode.Left}
	left.postOrder()
	right := myTreeNode{myNode.Right}
	right.postOrder()
	myNode.Print()
}

func main() {
	root := myTreeNode{&tree.Node{Value: 3}}
	root.Left = &tree.Node{}
	root.Right = &tree.Node{5, nil, nil}
	root.Right.Left = new(tree.Node)
	root.Left.Right = tree.CreatNode(2)
	root.Right.Left.SetValue1(4)
    
	root.postOrder()
}

相当于把tree.Node的内容全都拉过来了

与java的继承的区别

type myTreeNode struct {
	*tree.Node //Embedding
}

func (node *myTreeNode) Traverse() {
	fmt.Println("This is shadowed.")
}

func (myNode *myTreeNode) postOrder() {
	if myNode == nil || myNode.Node == nil {
		return
	}

	left := myTreeNode{myNode.Left}
	left.postOrder()
	right := myTreeNode{myNode.Right}
	right.postOrder()
	myNode.Print()
}

func main() {

	root := myTreeNode{&tree.Node{Value: 3}}
	root.Left = &tree.Node{}
	root.Right = &tree.Node{5, nil, nil}
	root.Right.Left = new(tree.Node)
	root.Left.Right = tree.CreatNode(2)
	root.Right.Left.SetValue1(4)


	fmt.Println("In-order traversal")
	fmt.Print("root.Traverse():  ")
	root.Traverse()
	fmt.Print("root.Node.Traverse():  ")
	root.Node.Traverse()
	fmt.Println()
	fmt.Println("post-order traversal")
	root.postOrder()

}
In-order traversal
root.Traverse():  This is shadowed.
root.Node.Traverse():  0 2 3 4 5 
post-order traversal
2 0 4 5 3 
  • 可以定义与内嵌对象的函数重名的函数:shadowed
  • 子类不能赋值给父类
posted @ 2022-05-15 21:31  不拘  阅读(67)  评论(0)    收藏  举报