Go入门(三)





一.内置函数

内置函数介绍
close关闭channel的
len求长度,例如 string,array,slice,map,channel
new用来分配内存,主要用来分配值类型,如int、struct。返回指针类型
make用来分配内存,用来分配引用类型,如channel
append用来追加元素到数组、slice 中
panic 和 recover用来做错误处理

二. panic 和 recover

Go 语言中,没有异常机制,但是使用panic/recover 模式来处理错误,panic 可以在任何地方引发,但是recover 只有在 defer 调用的函数中有效,首先看一个例子。

1.简单例子

package main

import "fmt"

func funcA() {
	fmt.Println("func A")
}

func funcB() {
	fmt.Println("func B")
}

func funcC() {
	fmt.Println("func C")
}

func main() {
	funcA()
	funcB()
	funcC()
}
// 输出
func A
func B
func C

2.添加panic 关键字

使用panic,相当于刨出了一个异常。

package main

import "fmt"

func funcA() {
	fmt.Println("func A")
}

func funcB() {
	fmt.Println("func B")
	panic("出现了严重的错误")  // 程序崩溃退出
}

func funcC() {
	fmt.Println("func C")
}

func main() {
	funcA()
	funcB()
	funcC()
}

// 运行结果
func A
func B
panic: 出现了严重的错误

3.立即执行函数的应用

panic 的执行顺序是在 defer 后面:

import "fmt"

func funcA() {
	fmt.Println("func A")
}

func funcB() {
	defer func() {
		fmt.Println("释放数据库链接退出")
	}() // 立即执行函数
	fmt.Println("func B")
	panic("出现了严重的错误") // 程序崩溃退出
}

func funcC() {
	fmt.Println("func C")
}

func main() {
	funcA()
	funcB()
	funcC()
}
// 输出:
func A
func B
释放数据库链接退出
panic: 出现了严重的错误

4.使用 recover 捕获异常

他能对异常进行捕获,并进行一些处理,他和上边的最大的区别,就是 funcC() 函数执行了,他能保证代码继续往后执行。

func funcA() {
	fmt.Println("func A")
}

func funcB() {
	defer func() {
		err := recover() // 使用recover 捕获错误
		fmt.Println(err) // 把异常输出
		if err != nil {
			fmt.Println("释放数据库链接退出")
		}
	}() // 立即执行函数
	fmt.Println("func B")
	panic("出现了严重的错误") // 程序崩溃退出
}

func funcC() {
	fmt.Println("func C")
}

func main() {
	funcA()
	funcB()
	funcC()
}

// 输出
func A
func B
出现了严重的错误
释放数据库链接退出
func C

4. 注意:

  • recover() 必须搭配 defer 使用;
  • defer 一定要在可能引发panic的语句之前定义;

recover(),一定是在异常报错以后使用,所以他一定要用defer搭配。

因为 panic 一旦触发,他后面的代码就不会执行了,所以你如果现在后面,他永远不会执行。


三.fmt标准库

1.输出

占位符说明
%V默认格式打印
%T打印值类型
%s打印字符串
func main() {
	s := "小王子"
	fmt.Printf("%s\n", s)
	fmt.Printf("%5s\n", s)
	fmt.Printf("%-1s\n", s)
}

// 输出如下:
小王子
  小王子
小王子  

二进制输出

func main() {
	fmt.Printf("%b", 100)
}

2.输入:

Go语言fmt 包中有fmt.Scanfmt.Scanffmt.Scanln 三个函数,可以在程序运行的过程中,从标准输入获得用户的输入。

  • Scan 从标准输入扫描文本,读取由空白符分割的值保存到传递给本函数的参数中,换行视为空白符。

读入一定要使用指针。


var s string
	fmt.Scan(&s)
	fmt.Println("你好", s)
	var (
		name  string
		age   int
		class string
	)

	fmt.Scanf("%s  %d %s \n", &name, &age, &class)
	fmt.Println(name, age, class)
	
// 输入
aa 18 bb
aa 18 bb

使用fmt.Scanlnfmt.Scanf 可以少输入一个百分号。

func main() {
	var (
		name  string
		age   int
		class string
	)

	fmt.Scanln(&name, &age, &class)
	fmt.Println(name, age, class)
}


// 输出
aa 18 bb
aa 18 bb

四.使用go解决分金币的问题

你有50枚金币,需要分配给以下几个人,Matthew,Sarah,Augustus,Heidi,Emilie,Peter,Giana,Adriano,Adron,Eliazbeth :
分配规则如下:
a:名字中有 一个‘e’ 或 ‘E’ 的分1枚金币
b:名字中有 一个‘i’ 或 ‘I’ 的分2枚金币
c:名字中有一个‘0’或‘O’ 分三枚金币,
d:名字中有 一个‘u’ 或 ‘U’ 的分4枚金币
写一个程序,计算每个用户分到多少金币,以及最后剩多少金币?


五.递归

简单点来讲,递归就是自己调用自己(如下)

// digui
func f1() {
	f1()
}


func main() {
	f1()
}

递归的应用场景,例如,删除文件夹里的文件,要先进入文件夹,然后删除文件,如果遇到文件夹,那么就继续进去,然后采用上一步的操作。继续看到文件就删除,看到文件夹就进去。

1. 递归适用于处理那种问题相同,规模越来越小的场景。
2. 递归一定要有一个明确的退出条件。

例如下面的阶层算法:

func f1(n int) int {
	if n <= 1 {
		return 1
	}
	return n * f1(n-1)
}

func main() {
	rel := f1(8)
	fmt.Println(rel)
}

用 Go 实现上台阶的问题。
有n个台阶,一次可以走一步,也可以走两步,有多少种走法。

func f1(n int) int {
	if n <= 1 {
		return 1 // 如果只有一个台阶,那么就只有一个
	}

	if n == 2 {
		return 2 // 如果最后剩下两个台阶,那么就有两种写法
	}
	// 最后剩下一个台阶的方法,加上最后一个台阶的方法
	return f1(n-1) + f1(n-2)
}

func main() {
	rel := f1(4)
	fmt.Println(rel)
}

// 输出5

六.自定义类型和类型别名

自定义类型是指的自己定义的类型,类型别名是指的这个类型就是本来应该有的类型,只不过我给他另外起了一个名字。

// type 是用来造类型的 (基于内置的类型,创造新的类型)
type myInt int    // 自定义类型
type youInt = int // 类型别名

func main() {
	var n myInt
	n = 100
	fmt.Println(n)
	fmt.Printf("%T\n", n)

	var c youInt
	c = 100
	fmt.Printf("%T\n", c)
}

// 输出
100
main.myInt
int

七.结构体:

type person struct {
	name   string
	age    int
	gender string
	hobby  []string
}

func main() {
	var p person
	p.name = "zhao"
	p.age = 20
	p.gender = "男"
	p.hobby = []string{"网球"}
	fmt.Println(p)
}

// 输出
{zhao 20[网球]}

匿名结构体

匿名结构体一般就是临时用一次,临时声明一下,所以一般都是写在方法里面。不是正规的使用方法。

type person struct {
	name   string
	age    int
	gender string
	hobby  []string
}

func main() {
	var p person
	p.name = "zhao"
	p.age = 20
	p.gender = "男"
	p.hobby = []string{"网球"}
	fmt.Println(p)

	var s struct {
		name string
		age  int
	}
	s.name = "nihao"
	s.age = 15

	fmt.Println(s)
}

//输出
{zhao 20[网球]}
{nihao 15}

结构体是指针类型

如何理解?看下面,处理完成以后,并不能改变原来结构体的内容。

GO 函数传递的参数,永远是copy

package main

import "fmt"

type person struct {
	name   string
	gender string
}

func f1(x person) {
	x.gender = "女"
}

func main() {
	var p person
	p.name = "zhao"
	p.gender = "男"
	f1(p)
	fmt.Println(p)
}

// 输出
{zhao 男}
type person struct {
	name   string
	gender string
}

func f1(x person) {
	x.gender = "女"
}

func f2(x *person) {
	//(*x).gender = "女" // 这是原始的写法
	x.gender = "女" // 这是语法糖的写法,
	// 因为go语言里面,不支持直接操作指针,所以他会自动的变回来
}

func main() {
	var p person
	p.name = "zhao"
	p.gender = "男"
	f1(p)
	fmt.Println(p)
	f2(&p)
	fmt.Println(p)
}
// 输出
{zhao 男}
{zhao 女}

复习一直指针的概念

只有指针才能用%p来进行取值;

func main() {
	a := 10
	b := &a
	fmt.Println(a) // 表示的是a
	fmt.Printf("%p\n", &a)
	fmt.Println(&a) // 表示的是对a取值
	fmt.Println(b)  // 表示的是b的地址
}
// 输出
10
0xc000128058
0xc000128058
0xc000128058

结构体指针的写法

type person struct {
	name string
	age  int
}

func main() {
	var p person
	p.name = "zhao"
	p.age = 12
	fmt.Println(p)

	var p2 = new(person) // 使用new,返回的是变量的指针
	p2.name = "zhang"
	fmt.Println(p2.name)

	var p3 = person{ // 声明的同时,进行初始化
		name: "wang", // 后面必须加 ,号
		age:  15,     //后面必须加,号
	}
	fmt.Println(p3)
}

// 输出
{zhao 12}
zhang
{wang 15}

结构体的构造函数

在GO 语言中,是默认没有构造函数这个概念的,但是他可以模仿写一个类似于的构造函数。

构造函数的返回,可以有两种,一种是返回类型,一种是返回指针

  • 当类型内的变量比较少的时候,可以直接返回值类型
  • 当类型内的变量比较多的时候,为了节约空间,可以选择返回指针类型。
type person struct {
	name string
	age  int
}

// 构造函数的一般默认名字,就是用new 开头
func newPerson(name string, age int) person {
	return person{name: name, age: age}
}

func newPerson2(name string, age int) *person {
	return &person{name: name, age: age}
}

func main() {
	var p1 = newPerson("zhao", 12)
	fmt.Println(p1)

	var p2 = newPerson2("zhang", 18)
	fmt.Println(p2)
	fmt.Println(*p2)
}

// 输出
{zhao 12}
&{zhang 18}
{zhang 18}

八.方法

在go 语言里,方法和函数不同,方法是指作用与特定类型变量的函数,这种特定类型变量,叫做接受者,接受者的概念。类似于其他语言的this 或者self

type dog struct {
	name string
}

func newdog(name string) *dog {
	return &dog{name: name}
}


// 方法是作用于特定类型的函数
// 接受者表示的是调用该方法的具体类型变量,多用类型首字母小写
func (d dog) wang() {
	fmt.Println(d.name)
	fmt.Println("旺旺旺")
}

func main() {
	d := newdog("zhao")
	d.wang()
}

// 输出
zhao
旺旺旺

标识符:变量名,函数名,类型名,方法名。
GO语言如果标识符的首字母大写,就表示对外部可见(暴露的,公开的)

函数的标准形式

func (接收者,接受者类型) 方法名 (参数列表)(返回参数){
               函数体
}

接受变量者的命名,接受者在参数进行命名的时候,建议使用接受者类型名的第一个小写字母,而不是self this 之类的命名。例如Person类型的接受者变量应该是p,Connnector类型的接受者变量应该命名为 c 等。


指针接受者和值接受者

值类型的接受者,没有办法改变他接受的值。

type Person struct {
	name string
	age  int
}

func newPerson(name string, age int) *Person {
	return &Person{
		name: name,
		age:  age,
	}
}

func (p Person) guonian() { // 值接受者
	p.age = p.age + 1
}

func (p *Person) zhenguonian(age int) { // 指针接受者
	p.age = age
}

func main() {
	p := newPerson("zhao", 20)
	fmt.Println(p.age)
	p.guonian()
	fmt.Println(p.age)
	p.zhenguonian(21)
	fmt.Println(p.age)
}
// 输出
20
20
21

什么时候使用指针接受者

  1. 需要修改接受者的值
  2. 接受者是拷贝代价比较大的对象
  3. 保证一致性,如果有某个方法使用了指针接受者,那么其他的方法也应该使用指针接受者。



九.函数版学生管理系统

package main

import (
	"fmt"
	"os"
)

/*
函数版学生管理系统
写一个系统可以查看、新增、删除、学生
*/
var (
	allStudent map[int64]*student // 包含的是 student的指针
)

type student struct {
	id   int64
	name string // 名字
}

// new student 是student的一个构造函数
func newStudent(id int64, name string) *student {
	return &student{
		id:   id,
		name: name,
	}
}

func showAllStrudent() { // 把所有的学生都打印出来
	for k, v := range allStudent {
		fmt.Println("学生ID:", k, "学生名字:", v)
	}
}

func addStudent() { // 向allstudent 中添加一个新学生。
	var (
		id   int64
		name string
	)
	fmt.Println("请输入id:")
	fmt.Scanln(&id)
	fmt.Println("输入名字")
	fmt.Scanln(&name)
	// 调用构造函数添加,生成
	nestu := newStudent(id, name)
	// 追加到map中
	allStudent[id] = nestu
}

func deleteStudent() { // 删除某个学生
	/*
	   1.请输入你要删除学生的学号
	   2.去 allstudent 这个map 中删除这个键值对
	*/
	var deletid int64
	fmt.Println("请输入你要删除学生的学号")
	fmt.Scanln(&deletid)
	delete(allStudent, deletid) // 要从map中删除一个人的时候,应该先输入他的 map,然后再输入他的 id
}

func main() {
	allStudent = make(map[int64]*student, 48) // 初始化(开辟空间)

	for i := 0; i < 100; i++ {
		// 1. 打印菜单
		fmt.Println("欢迎来到学生管理系统")
		fmt.Println(`
	1. 查看所有的学生
	2. 新增学生
	3. 删除学生
	4. 退出
	`)

		// 2. 等待用户选择要做什么
		var choice int64
		fmt.Print("请输入你想干嘛")
		fmt.Scanln(&choice)
		fmt.Println("你选择了", choice, "这个选项")
		switch choice {
		case 1:
			showAllStrudent()
		case 2:
			addStudent()
		case 3:
			deleteStudent()
		case 4:
			os.Exit(1) // 退出
		default:
			fmt.Println("滚~~")
		}
	}
}

十.匿名字段

匿名结构体,简而言之,就是没有名字的字段,引用的时候,可以直接引用类型即可。

type people struct {
	string
	int
}

func main() {
	p := people{
		"zhao",
		29,
	}

	fmt.Println(p.string)
}

嵌套数组

嵌套数组就是数组里面套数组

type address struct {
	provincias string
	city       string
}

type person struct {
	name string
	age  int
	addr address // 使用了嵌套
}

type person2 struct {
	name string
	age  int
	address
}

func main() {
	a := person{
		name: "zhao",
		age:  20,
		addr: address{
			provincias: "山东",
			city:       "青岛",
		},
	}
	fmt.Println(a)

	// 取某一个属性
	fmt.Println(a.addr.city)

	// 当结构体的变量名和类型名一样的时候,可以直接通过结构体来取变量
	b := person2{
		name: "zhang",
		age:  20,
		address: address{
			provincias: "陕西",
			city:       "西安",
		},
	}
	fmt.Println(b.city) // 当结构内的变量名和类型的名字重复的时候,可以直接写
}


十一.结构体

1.使用结构体模仿继承

原生的结构体是没有所谓的继承关系的。

package main

import (
	"fmt"
)

type animal struct {
	name string // 名字
}

func (a *animal) move() {
	fmt.Println(a.name, "会跑")
}

type dog struct {
	feet   uint // 有几条腿
	animal      // 这样就模仿实现了继承
}

// 给 dog 实现一个汪汪的方法
func (d *dog) wangwang() {
	fmt.Println(d.name, "旺旺")
}

func main() {
	dog := dog{
		feet: 4,
		animal: animal{
			name: "旺财",
		},
	}
	fmt.Println(dog.name)
	dog.wangwang()
	dog.move()
}

// 输出结果
旺财
旺财 旺旺
旺财 会跑

2.结构体与JSON

按照下面的方法,进行打印,会报错,这是因为,Go 语言中,名字必须是大写的,才能对外可见。
小写的会报错,所以要改

import (
	"encoding/json"
	"fmt"
)

type person struct {
	name string // 名字
	age  int    // 年龄
}

func main() {
	p1 := person{
		name: "zhao",
		age:  20,
	}

	b, err := json.Marshal(p1)
	if err != nil {
		fmt.Println("有错误", err)
		return
	}
	fmt.Println(b)
}

import (
	"encoding/json"
	"fmt"
)

type person struct {
	Name string // 名字
	Age  int    // 年龄
}

// 转换成 JSON 的过程称为序列化。
// 从 JSON 转换过来的过程称为 反序列化。

func main() {
	p1 := person{
		Name: "zhao",
		Age:  20,
	}

	b, err := json.Marshal(p1)
	if err != nil {
		fmt.Println("有错误", err)
		return
	}
	fmt.Println(string(b))
}
// 输出
{"Name":"zhao","Age":20}

对不同的对象中,变量可以有不同的名字,如下:

type person struct {
	Name string `json:"name" db:"namea"` // 如果是使用json的话,就编程 name,db 是在数据库中的名字
	Age  int    // 年龄
}

// 输出:
{"name":"zhao","Age":20}

反序列化:

import (
	"encoding/json"
	"fmt"
)

type person struct {
	Name string `json:"name" db:"namea"` // 如果是使用json的话,就编程 name,db 是在数据库中的名字
	Age  int    // 年龄
}

// 转换成 JSON 的过程称为序列化。
// 从 JSON 转换过来的过程称为 反序列化。

func main() {
	p1 := person{
		Name: "zhao",
		Age:  20,
	}

	b, err := json.Marshal(p1)
	if err != nil {
		fmt.Println("有错误", err)
		return
	}
	fmt.Println(string(b))

	// 反序列化
	var p2 person
	str := `{"name":"wang","Age":18}`
	json.Unmarshal([]byte(str), &p2) // 对str 反序列化,并存入到p2 中
	fmt.Println("p2: ", p2)
}

// 输出
{"name":"zhao","Age":20}
p2:  {wang 18}


posted @ 2021-04-08 23:32  沧海一声笑rush  阅读(81)  评论(0编辑  收藏  举报