课程笔记:《20个小时快速入门Go语言》

这篇博客为黑马《20个小时快速入门Go语言》的课程笔记,仅用于个人纪录


初始Go语言

Go语言优势

  1. 可直接编译成机器码,不依赖其他库
  2. 静态类型语言
  3. 语言层面支持并发
  4. GC

基础类型

var a int
var a int = 10
a := 10
a, b := 10, 20

var (
	a int
	b float64
)

交换

i, j = j, i

匿名变量

tmp, _ = i, j

配合函数返回值使用,才有优势

常量

const a int = 10
const b = 11.2

const(
	i = 10
	j = 3.14
)

iota

  • 常量自动生成器,每个一行,自动累加1
  • iota给常量赋值使用
  • iota遇到const,重置为0
  • 可以只写一个iota
  • 如果是同一行,值都一样

基础数据类型

bool、byte、int、uint、float32、string

字符类型

var ch byte
ch = 97
ch = ‘a’

字符串

var str string
str = “abc”
len(str)
str[0]

复数

var t complex128
t = 2.1 + 3.14i
real(t)
imag(t)

输入

var a int
fmt.Scanf("%d", &a)
fmt.Scan(&a)

类型转换

bool类型不能转换为整型,整型也不能转换为bool

var ch byte
ch = 'a'
var t int
t = int(ch)

类型别名

type bigint int64

运算符

流程控制

if

if a > 1 {
	fmt.Println(a)
}

if支持一个初始化语句,初始化语句和判断条件以分号分隔

if a := 10; a == 10 {
	fmt.Println(a)
}
a := 10
if a == 10{
	//
} else if a > 10{
	//
} else {
	//
}

switch语句,fallthrough不跳出switch语句,后面无条件的执行

switch num {
case 1:
	//
	break // 默认包含break
case 2:
	//
	break
default:
	//
}
score := 85
switch{
case score > 90:
	//
case score > 80:
	//
default:
	//
}

循环

for i := 1; i <= 100; i++ {
	//
}

range默认返回2个值:一个是元素的位置,一个是元素本身

str := "abc"
for i, data := range str {
	//
}
//第2个返回值,默认丢弃
for i := range str {
	//
}
//死循环
for {
}

goto跳转到标签

函数

定义格式如下:

func FuncName(参数列表) (o1 type1, o2 type2) {
	// 函数体
	return 返回值
}

不定参数列表

func MyFunc(args ...int){
	fmt.Println(len(args))
}

不定参数的传递

func MyFunc(args ...int){

}

func Test(args ...int){
	MyFunc(args...)
	MyFunc(args[2:]...)
}

带返回值的常用写法

func MyFunc() (result int) {
	result = 666
	return
}

多个返回值

func MyFunc() (int, int, int) {

}
//go 官方推荐
func MyFunc() (a int, b int, c int){
	a, b, c = 1, 2, 3
	return
}

函数类型

type FuncType func(int, int) int
var fTest FuncType

回调函数

函数有一个参数是函数类型

type FuncType func(int, intint

func Calc(a, b int, fTest FuncType) (result int) {
	result = fTest(a, b)
	return
}

匿名函数与闭包

//定义匿名函数,同时调用
func() {
	//
}()

//带参数的匿名函数
func(i, j int){
	//
}(1, 2)

//有参数有返回值的匿名函数
func(i, j int)(max, min int){
	//
	return
}(1,2)

闭包以引用的方式捕获外部变量

它不关心这些捕获了的变量和常量是否已经超出了作用域,所以只有闭包还在使用它,这些变量就还会存在

延迟调用defer

func main() {
	// defer在main函数结束前调用
	defer fmt.Println("aaa")
	fmt.Printlen("bbb")
}

// 输出:
// bbb
// aaa

如果一个函数中有多个defer语句,它们会以后进先出的顺序执行。哪怕函数或某个延迟调用发生错误,这些调用仍旧会被执行

defer和匿名函数的结合使用

func main() {
	a := 10
	b := 20
	defer func() {
		fmt.Printf("a = %d, b = %d\n", a, b)
	}()
	a = 111
	b = 222
}

输出:
111
222

获取命令行参数

package main

import "fmt"
import "os"

func main() {
	list := os.Args
	n := len(list)
}

工程管理

Go代码必须放在工作区。包含3个子目录:src、pkg、bin

设置GOPATH环境变量

同一个目录,包名必须一样

go env查看go相关的环境路径

同一个目录,调用别的文件的函数,直接调用即可,无需包名引用

调用不同包里面的函数,格式:包名.函数名()

如果包函数名首字母是小写,则其他包无法调用

常用导入方式

import (
	"fmt"
	"os"
)

.操作

调用函数,无需通过包名,但是这样方式不好

import . "fmt" 
func main() {
	Println("hello")
}

给包名起别名

import io "fmt"

忽略此包

_操作其实是引入该包,而不直接使用包里面的函数,而是调用了该包里面的init函数

import _ "fmt"

main函数和init函数

导入包时,会调用该包的init函数

go install

配置GOBIN后,执行go install,会生成pkgbin目录,pkg存放平台相关的库,bin存放可执行文件

复合类型

指针

Go语言对指针的支持介于Java和C/C++语言之间,它既没有像Java那样取消了代码对指针直接操作的能力,也避免了C/C++语言中由于对指针的滥用而造成的安全和可靠性问题

  • 指针默认值为nil,没有NULL常量
  • 不支持指针运算,不支持->运算符,直接用.访问目标成员
package main

func main() {
	var a int = 10
	// 每个变量有两层含义:变量的内存内容,变量的内存地址,a,&a
}

指针保存某个变量的地址,*int保存int的地址

var p *int
p = &a
*p = 666

new函数

a := 10
var p *int
p = new(int)
q := new(int)

我们只需要使用new()函数,无需担心其内存的生命周期

数组

数组长度必须是常量,[10]int[5]int是不同类型

var id [50]int

初始化

// 全部初始化
var a [5]int = [5]int{1, 2, 3, 4, 5}
b := [5]int{1, 2, 3, 4, 5}
c := [...]int{1, 2, 3, 4, 5}

// 部分初始化,没有初始化的元素,自动赋值为0
c := [5]int{1, 2, 3}

// 指定某个元素初始化
d := [5]int{2:10, 4:20}

二维数组

var a [3][4]int
b := [3][4]int{
	{1, 2, 3, 4},
	{5, 6, 7, 8},
	{9, 10, 11, 12}
}

// 指定元素初始化
c := [3][4]int{1:{1,2,3,4}}

数组比较和赋值

// 支持比较,只支持 == 或 !=,比较每一个元素
a := [5]int{1,2,3,4,5}
b := [5]int{1,2,3,4,5}
fmt.Println(a == b)

输出:true

随机数的使用

import "math/rand"
// 设置种子,只需一次
rand.Seed(666)
fmt.Println(rand.Int())
fmt.Println(rand.Intn(100))

数组做函数参数是值传递

slice

数组的长度在定义之后无法再次修改,并且数组是值类型,每次传递都将产生一份副本

切片并不是数组或数组指针,它通过内部指针和相关属性引用数组片段,以实现变长方案

slice并不是在真正意义上的动态数组,而是一个引用类型。slice总是指向一个底层array,slice的声明也可以像array一样,只是不需要长度

slice := array[low][high][max]

low:下标的起点
hith:下标的终点(不包括此下标)
cap:容量,cap = max - low

// 数组
a := [5]int{}

// 切片初始化
s := []int{}
s.append(s, 1)

// 自动推导类型,同时初始化
s1 := []int{1, 2, 3, 4}

// 借助make函数,格式make(切片类型,长度,容量)
s2 := make([]int, 5, 10)

// 没有指定容量,容量和长度一样
s3 := make([]int, 5)

切片常用操作

切片的截取

array := []int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}

// [low:high:max]
s1 := array[:] //[0:len(array):len(array)]
s2 := array[3:6:7]
s3 := array[:6] //从0开始,长度为6,容量也为6
s4 := array[3:] //从下标3开始,到结尾

// 操作某个元素,和数组操作方式一样
data := array[1]

切片和底层数组的关系

a := []int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}

s1 := a[2:5]
s1[1] = 666 // 原数组a的值也会改变

s2 := a[2:7]
s2[2] = 777 // 原数组a的值也改变

切片内建函数

append

s1 := []int{}
s1 = append(s1, 1)
s1 = append(s1, 2)

s2 := []int{1, 2, 3}
s2 = append(s2, 4)

append函数会智能地控制底层数组的容量增长,一旦超过原底层数组容量,通常以2倍容量重新分配底层数组,并复制原来的数据

copy

srcSlice := []int{1, 2}
dstSlice := []int{6, 6, 6, 6, 6, 6}
copy(dstSlice, srcSlice)
fmt.Println(dstSlice)

输出:
[1 2 6 6 6 6]

切片做函数参数

引用传递

猜数字游戏

package main
import(
	"fmt"
	"math/rand"
	"time"
)

func CreateNum(p *int) {
	rand.Seed(time.Now().UnixNano())
	var num int
	for {
		num = rand.Intn(10000)
		if num >= 1000 {
			break
		}
	}
	*p = num
}

func GetNum(s []int, num int){
	s[0] = num / 1000;
	s[1] = num % 1000 / 100
	s[2] = num % 100 / 10
	s[3] = num % 10
}

func OnGame(s []int){
	var num int
	for{
		for{
			fmt.Println("请输入一个4位数:")
			fmt.Scan(&num)
			if 999 <= num && num < 10000{
				break
			}
			fmt.Println("输入的数不符合要求")
		}
		keySlice := make([]int, 4)
		GetNum(keySlice, num)
		...
	}
}

func main() {
	var randNum int
	CreateNum(&randNum)
	randSlice := make(int[], 4)
	// 保存这个4位数的每一位
	GetNum(randSlice, randNum)
	
	OnGame(randSlice)
}

map

无序的key-value集合

info := map[int]string{
	1 : "hello"
}

注意:切片、函数以及包含切片的数据结构这些类型由于具有引用语义,不能作为映射的键,使用这些类型后编译错误

var m1 map[int]string

// 可以通过make创建
m2 := make(map[int]string)

// 可以通过make创建,并且指定长度,相当于指定容量
m3 := make(map[int]string, 10)
m3[1] = "mike"

// 初始化
m4 := map[int]string{1:"mike", 2:"go"}

map遍历

m := map[int]string{1:"mike", 2:"yoyo", 3:"go"}

for key, value := range m {
	...
}

// 如何判断一个key值是否存在
// 第一个返回值位key对应的value,第二个返回值位key是否存在
value, ok := m[0]

map删除

m := map[int]string{1:"mike", 2:"yoyo", 3:"go"}

delete(m, 1) // 删除key为1的内容

map做函数参数

引用传递

结构体

type Student struct{
	id int
	name string
	sex byte
	age int
	addr string
}

结构体初始化

func main() {
	// 顺序初始化,每个成员必须初始化
	var s1 Student = Student{1, "mike", 'm', 18, "beijing"}
	
	// 指定成员初始化
	s1 := Student{name: "mike", addr: "beijing"}
}

结构体指针变量初始化

var p1 *Student = &Student{}

结构体成员的使用

var s Student
var p *Student = &s
var p1 = new(Student)

// 操作成员,使用(.)运算符
s.id = 1;
s.name = "mike"
s.sex = 'm'

p.age = 18

结构体作为函数参数

值传递

可见性

如果想使用别的包的函数、结构体类型、结构体成员,函数名,类型名,结构体成员变量名的首字母必须大写,如果首字母是小写,只能在同一个包里使用

面向对象编程

没有继承(尽管匿名字段的内存布局和行为类似继承,但它并不是继承)、虚函数、构造函数和析构函数、隐藏的this指针

但是通过别的方式实现:

  • 封装:通过方法实现
  • 继承:通过匿名字段实现
  • 多态:通过接口实现

匿名组合

匿名字段初始化

type Person struct{
	name string
	sex byte
	age int
}

type Student struct{
	Person // 匿名字段,只有类型,没有名字,继承了Person的成员
	id int
	addr string
}

func main() {
	// 顺序初始化
	var s1 Student = Student{Person{"mike", 'm', 18}, 1, "beijing"}
	
	// 自动推导类型
	s2 := Student{Person{"mike", 'm', 18}, 1, "beijing"}

	// 指定成员初始化
	s3 := Student{id: 1}
}

成员的操作

s1 := Student{Person{"mike", 'm', 18}, 1, "beijing"}
s1.name = "yoyo"
s1.sex = 'f'
s1.Person = Person{"go", 'm', 18}

同名字段

type Person struct{
	name string
	sex byte
	age int
}

type Student struct{
	Person // 匿名字段,只有类型,没有名字,继承了Person的成员
	id int
	addr string
	name string // 和Person中的name同名
}

func main() {
	var s Student
	s.name = "mike" // 操作的是Student结构体中的name,而不是Person中的name,就近原则
	s.sex = 'm'
	s.age = 18
	s.addr = "beijing"
	s.Person.name = "yoyo"
}

非结构体匿名字段

type mystr string

type Student struct {
	Person // 结构体匿名字段
	int    // 基础类型的匿名字段
	addr string
	mystr
}

func main() {
	s := Student{Person{}, 1, "hello"}
	fmt.Println(s.name, s.age, s.sex, s.int, s.mystr)
	fmt.Println(s.Person, s.int, s.mystr)
}

结构体指针类型匿名字段

type Student struct{
	*Person
	id int
	addr string
}

func main() {
	s1 := Student{&Person{"mike", 'm', 18}, 666, "beijing"}
	
	// 先定义变量
	var s2 Student
	s2.Person = new(Person)
}

方法

本质上,一个方法则是一个和特殊类型关联的函数

在Go语言中,可以给任意自定义类型(包括内置类型,但不包括指针类型)添加相应的方法

方法总是绑定对象实例,并隐式将实例作为第一实参(receiver)

func (receiver ReceiverType) funcName(parameters) (results)

  • 参数receiver可任意命名。如方法中未曾使用,可省略参数名。
  • 参数receiver类型可以是T或*T。基类型T不能是接口或指针
  • 不支持重载方法,也就是说,不能定义名字相同但是不同参数的方法

接收者类型不同的函数可以同名,是不同的方法

// 实现2数相加
// 面向过程
func Add1(a, b int) int {
	return a + b
}

// 面向对象方法:给某个类型绑定一个函数
type long int
// tmp叫接收者,接收者就是传递的一个参数
func(tmp long) Add02(other long) long {
	return tmp + other
}

func main() {
	var result int
	result := Add01(1, 1) // 普通函数
	
	// 定义一个变量
	var a long = 2;
	// 调用方法格式:变量名.函数
	res := a.Add02(3)

	//面向对象只是换了一种表现形式
}

为结构体类型添加方法

type Person struct{
	name string
	sex byte
	age int
}

// 带有接收者的函数叫方法
func (tmp Person) PrintInfo() {
	fmt.Println(tmp)
}

// 通过一个函数,给成员赋值
func (p *Person) SetInfo(n string, s byte, a int){
	p.name = n
	p.sex = s
	p.age = a
}

func main() {
	p := Person{"mike", 'm', 18}
	p.PrintInfo()
	
	var p2 Person
	(&p2).SetInfo("yoyo", 'f', 22)
	p2.PrintInfo()
}

方法集

用实例value和pointer调用方法(含匿名字段)不受方法集约束,编译器总是查找全部方法,并自动转换receiver实参

type Person struct{
	name string
	sex byte
	age int
}

func (p Person) SetInfoValue(){
	...
}

func (p *Person) SetInfoPointer(){
	...
}

func main() {
	// 结构体变量是一个指针变量,它能够调用那些方法,这些方法的集合就是方法集
	p := &Person{"mike", 'm', 18}
	p.SetInfoPointer()
	p.SetInfoValue()
	(*p).SetInfoPointer()
}

匿名字段

方法的继承

type Person struct{
	name string
	sex byte
	age int
}

// Person类型,实现了一个方法
func (tmp *Person) PrintInfo() {
	...
}

type Student struct{
	Person
	id int
	addr string
}

func main() {
	s := Student{Person{"mike", 'm', 18}, 666, "beijing"}
}

方法的重写

type Person struct{
	name string
	sex byte
	age int
}

// Person类型,实现了一个方法
func (tmp *Person) PrintInfo() {
	...
}

type Student struct{
	Person
	id int
	addr string
}

func (tmp *Student) PrintInfo() {
	...
}

func main() {
	s := Student{Person{"mike", 'm', 18}, 666, "beijing"}
	s.PrintfInfo() // 就近原则,调用Student的PrintInfo()方法
	s.Person.PrintInfo()
}

方法值

type Person struct{
	name string
	sex byte
	age int
}

func (p Person) SetInfoValue(){
	...
}

func (p *Person) SetInfoPointer(){
	...
}

func main() {
	// 结构体变量是一个指针变量,它能够调用那些方法,这些方法的集合就是方法集
	p := Person{"mike", 'm', 18}
	p.SetInfoPointer()
	
	// 保存方法入口地址
	pFunc := p.SetInfoPointer
	pFunc() // 等价于p.SetInfoPointer
}

方法表达式

type Person struct{
	name string
	sex byte
	age int
}

func (p Person) SetInfoValue(){
	...
}

func (p *Person) SetInfoPointer(){
	...
}

func main() {
	// 结构体变量是一个指针变量,它能够调用那些方法,这些方法的集合就是方法集
	p := Person{"mike", 'm', 18}
	p.SetInfoPointer()
	
	f := (*Person).SetInfoPointer
	f(&p) // 显式把接收者传递过去
	
	f2 = (Person).SetInfoValue
	f2(p)
}

接口

Go通过接口实现了鸭子类型(duck-typing):“当看到一只鸟走起来像鸭子、游泳起来像鸭子、叫起来也像鸭子,那么这只鸟就可以被称为鸭子”。我们并不关心对象是什么类型,到底是不是鸭子,只关心行为

  • 接口命名习惯以er结尾
  • 接口只有方法声明,没有实现,没有数据字段
  • 接口可以匿名嵌入到其他接口,或嵌入到结构中
type Humaner interface {
	SayHi()
}

type Studetn struct {
	name string
	id int
}

type Teacher struct {
	addr string
	group string
}

// Student实现了SayHi()方法
func (tmp *Student) sayHi() {
	...
}

// Teacher实现了SayHi()方法
func (tmp *Teacher) sayHi() {
	...
}

// 定义一个普通函数,函数的参数为接口类型
// 只有一个函数,可以有不同表现,多态
func WhoSayHi(i Humaner){
	i.sayHi()
}

func main() {
	// 定义接口类型的变量
	var i Humaner
	
	// 只要实现了此接口方法的类型,那么这个类型的变量就可以给i赋值
	s := Student{"mike", 666}
	i = s
	i.sayHi()
	
	t := Teacher("beijing", "go")
	i = t
	t.sayHi()
	
	// 创建一个切片
	x := make([]Humaner, 3)
	x[0] = s
	x[1] = t
	
	for 
}

接口的继承

type Humaner interface {
	sayHi()
}

type Personer interface {
	Humaner
	sing(lrc string)
}

type Studetn struct {
	name string
	id int
}

// Student实现了SayHi()方法
func (tmp *Student) sayHi() {
	...
}

func (tmp *Student) sing(lrc string) {
	...
}

func main() {
	var i Personer
	s := &Student{"mike", 666}
	i = s
	
	s.sayHi()
	s.sing()
}

接口转换

type Humaner interface {
	sayHi()
}

type Personer interface {
	Humaner
	sing(lrc string)
}

type Studetn struct {
	name string
	id int
}

// Student实现了SayHi()方法
func (tmp *Student) sayHi() {
	...
}

func (tmp *Student) sing(lrc string) {
	...
}

func main() {
	var i Personer
	s := &Student{"mike", 666}
	i = s
	
	s.sayHi()
	s.sing()

	// 超集可以转换为子集,反过来不可以
	var iPro Personer
	var i Humaner
	
	iPro := &Student{"mike", 666}
	
	iPro = i //err
	i = iPro //success
	i.sayHi()
}

空接口

空接口没有任何方法,任何方法都实现了空接口

当函数可以接受任意的对象实例时,我们会将其声明为interface{},最典型的例子是标准库fmt中PrintXXX系列的函数,例如:

func Println(args ...interface{})

func main() {
	// 空接口万能类型,保存任意类型的值
	var i interface{} = 1 // 将int类型赋值给interface{}
}

类型查询


struct Student struct{
	name string
	id int
}

func main() {
	i := make([]interface{}, 3)
	i[0] = 1
	i[1] = "hello go"
	i[2] = Student{"mike", 666}
	
	// 类型查询(通过if实现)
	for index, data := range i {
		// 第一个返回值,第二个返回判断结果的真假
		if value, ok := data.(int); ok == true {
			...
		} else if value, ok := data.(string); ok == true {
			...
		} else if value, ok := data(Struct); ok == true {
			...
		}
	}

	// 类型查询(通过switch实现)
	for index, data := range i {
		switch value := data.(type) {
			case int:
				...
			case string:
				...
			case Student:
				...
			default:
				...
		}
	}
}

异常处理

Go语言引入了一个关于错误处理的标准模式,即error接口,它是Go语言内建的接口类型

type error interface {
	Error() string
}
func main() {
	err1 := fmt.Errorf("%s", "this is normal err1")
	err2 := errors.New("this is normal err2")
}

error接口应用

func MyDiv(a, b int) (result int, err error) {
	if b == 0 {
		err = errors.New("分母不能为0")
	}else{
		result = a/b
	}
	return 
}

func main() {
	result, err := MyDiv(10, 2)
	if err != nil {
		fmt.Println(err)
	} else {
		fmt.Println(result)
	}
}

panic

当遇到不可恢复的错误状态的时候,如数组访问越界、空指针引用,这些运行时错误会引起panic异常。

panic("this is a panic test") // 显式调用panic

recover

recover必须放在defer

func test(x int){
	defer func() {
		//recover()
		//fmt.Println(recover())
		if err := recover(); err != nil {
			fmt.Println(err)
		}
	}()
	var a [10]int
	a[x] = 111 // 当x为10的时候,导致数组越界
}

文本文件处理

字符串处理

字符串操作

  • Contains
  • Join
  • Index
  • Repeat
  • Replace
  • Split
  • Trim
  • Fields
package main

import (
	"fmt"
	"strings"
}

func main() {
	
	// "hellogo"中是否包含"hello",包含返回true,不包含返回false
	fmt.Println(strings.Contains("hellogo", "hello"))
	
	// Joins组合
	s := []string{"abc", "hello", "mike"}
	buf := strings.Join(s, "@")
	fmt.Println(buf)
	
	//Index 查找子串的位置
	fmt.Println(strings.Index("abcdhello", hello))
	
	// Repeat 重复
	buf = strings.Repeat("go", 3)
	fmt.Println(buf) // "gogogo"
	
	// Split 以指定的分隔符拆分
	buf = "hello@abc@go@mike"
	s2 := strings.Split(buf, "@")
	fmt.Println(s2) // ["hello" "abc" "go" "mike"]
	
	// Trim 去掉两头的空格
	// Fields 去掉两头空格,并把元素放入切片中
}

字符串转换

  • Append:
  • Format
  • Parse
  • 0
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值