【笔记】Go语言学习笔记

一、概述­

 

­●什么是程序:

   程序:为了让计算机执行某些操作或解决某个问题而编写的一系列有序指令的集合

­●Go语言 是区块链最主流的编程语言

同时也是当前最具发展潜力的语言

­●Go语言是Google公司创造的语言,也是Google主推的语言。

­●Google创造Golang的原因

●Golang的语言的特点

        Go语言保证了既能到达静态编译语言的安全和性能,又达到了动态语言开发维护的高效率,使用一个表达式来形容Go语言: Go=C + Python,说明Go语言既有C静态语言程序的运行速度,又能达到Python动态语言的快速开发。

1 ) 从C语言中继承了很多理念,包括表达式语法,控制结构,基础数据类型,调用参数传值,指针等等,也保留了和C语言一样的编译执行方式及弱化的指针

2 ) 引入包的概念,用于组织程序结构,Go语言的一个文件都要归属于一个包,而不能单独存在。
3 ) 垃圾回收机制,内存自动回收,不需开发人员管理

4 ) 天然并发重要特点)
        (1)从语言层面支持并发,实现简单
        (2) goroutine,轻量级线程,可实现大并发处理高效利用多核

        (3)基于CPS并发模型(Communicating Sequential Processes )实现
5 ) 吸收了管道通信机制,形成Go语言特有的管道channel通过管道channel ,可以实现不同的goroute之间的相互通信。
6 ) 函数可以返回多个值

7 ) 新的创新:比如切片 slice 、 延时执行 defer等

二、Windows下搭建Go开发环境

1、SDK的全称(Software Development Kit 软件开发工具包)

2、SDK是提供给开发人员使用的,其中包含了对应开发语言的工具包。

SDK下载地址:Downloads - The Go Programming Language

3、解压后的 go目录就是SDK

bin文件夹存放 go的指令 go / godoc / gofmt

src文件夹放 go的源代码

4、go.exe 使用go.exe可以编译和运行我们的go源码

5、如何测试go的SDK安装成功 go version

 6、配置环境变量

三、Go语言快速开发入门

一、

1)go文件的后缀是.go

2)package main

    表示该hello.go 文件所在的包是main,在go中,每个文件都必须归属于一个包。

3)import "fmt"

    表示:引入一个包,包名fmt,引入该包后,就可以使用fmt包的函数,比如:fmt.Println

4)  func main(){

}

func 是一个关键字,表示一个函数

main是函数名,是一个主函数,即我们程序的入口

5)fmt.Println("hello")

    表示调用 fmt包的函数 Println输出"hello,world"

二、

通过go build 命令对该go文件进行编译,生成 .exe文件

 三、

运行hello.exe文件即可

 注意:通过 go run 命令可以直接运行hello.go 程序 [类似执行一个脚本文件的形式]

四、Golang执行流程分析

  如果是对源码编译后,再执行,Go的执行流程如下图

 如果对源码直接执行 go run 源码,Go的执行流程如下图

两种执行流程的方式区别

        1)如果我们先编译生成了可执行文件,那么我们可以将该可执行文件拷贝到没有go开发环境的机器上,仍然可以运行
        2)如果我们是直接 go run go源代码,那么如果要在另外一个机器上这么运行,也需要go开发环境,否则无法执行。
        3)在编译时,编译器会将程序运行依赖的库文件包含在可执行文件中,所以,可执行文件变大了很多。

Go语言程序开发的注意事项

1) Go源文件以“go”为扩展名。

2)Go应用程序的执行入口是mainO函数。这个是和其它编程语言(比如java/c)

3)Go语言严格区分大小写。

4) Go方法由一条条语句构成,每个语句后不需要分号(Go语言会在每行后自动加分号),这也体现出Golang 的简洁性。

5)Go编译器是一行行进行编译的,因此我们一行就写一条语句,不能把多条语句写在同一个,否则报错

6)go语言定义的变量或者import的包如果没有使用到,代码不能编译通过。

7)大括号都是成对出现的,缺一不可。

Go语言的转义字符

1) \t  : 表示一个制表符,通常使用它可以排版。

2)\n :换行符

3)\\ :一个\

4)  \" : 一个 "

5)  \r : 一个回车 fmt.Println("12346577\r758")

从当前行的最前面开始输出,覆盖掉以前内容

效果如下

小作业

实现下图 

package main

import "fmt"
func main(){
	fmt.Println("姓名\t年龄\t籍贯\t住址\njohn\t12\t河北\t北京")
}

Go格式化指令

gofmt -w xxx.go

该指令可以将格式化后的内容重写写入到文件。当程序员重新打开文件时,就会看到新的格式化后的文件

运算符两边习惯性各加一个空格。比如 : 2 + 4 * 5

代码风格

左边的写法是正确的   右边的写法是错误的

一行最长不超过80个字符,超过的请使用换行展示,尽量保持格式优雅

Golang 官方编程指南

说明: Golang 官方网站 https://golang.org , 需要特殊上网

官方标准库API文档,https://golang.org/pkg

国内API文档  https://studygolang.com/pkgdoc

api : application program interface:应用程序编程接口。

就是我们Go的各个包的各个函数

相对路径:从当前位置开始定位,去找对应的目录

绝对路径:从当前盘的最上面开始定位

五、变量

变量都是其程序的基本组成单位

变量相当于内存中一个数据存储空间的表示

变量使用的基本步骤

1)声明变量(也有人叫:定义变量)

2)非变量赋值

3)使用变量

package main
import "fmt"

func main() {
	var i int
	i = 10
	fmt.Println("i=", i)
}

 Golang变量使用的三种方式

1、指定变量类型,声明后若不赋值,使用默认值

package main
import "fmt"

func main() {
	var i int
	fmt.Println("i=", i)
}

 2、根据值自行判定变量类型(类型推导)

package main
import "fmt"

func main() {
	var num = 10.11
	fmt.Println("num=", num)
}

 3、省略var,【注意 := 左侧的变量不应该是已经声明过的,否则会导致编译错误】

package main
import "fmt"

func main() {
	name := "tom"
	fmt.Println("name=", name)
}

 4、多变量声明

在编程中,有时我们需要一次性声明多个变量,Golang也提供这样的语法

package main
import "fmt"

func main() {
	//方式1
	    var n1, n2, n3 int
	    fmt.Println("n1=", n1, "n2=", n2, "n3=", n3)
	//方式2 一次性声明多个变量的方式2
	    var n1, name , n3 = 100, "tom", 888
	    fmt.Println("n1=",n1, "name=", name, "n3=", n3)
    //一次性声明多个变量的方式3,同样可以使用类型推导    
	    n1, name , n3 := 100, "tom~~", 888
	    fmt.Println("n1=",n1, "name=", name, "n3=", n3)
}

 如何一次性声明多个全局变量【在go中函数外部定义变量就是全局变量】

package main
import "fmt"

//定义全局变量
var n1 = 100
var n2 = 200
var name = "jack"
//上面的声明方式,也可以改成一次声明
var(
	n3 = 300
	n4 = 900
	name2 = "mary"
)

func main(){
	fmt.Println("n1=",n1, "name=", name, "n2=", n2)
	fmt.Println("n3=",n3, "name2=", name2, "n4=", n4)
}

5、该区域的数据值可以在同一类型范围内不断变化(重点)

package main
import "fmt"

//变量使用的注意事项
func main() {
	//该区域的数据值可以在同一类型范围内不断变化
	var i int = 10
	i = 30
	i = 50
	fmt.Println("i=", i)
	i = 1.2 //[会报错,i为int,原因是不能改变数据类型]
}

6、变量在同一个作用域(在一个函数或者在代码块)内不能重名

	//变量在同一个作用域(在一个函数或者在代码块)内不能重名 如下:
	var i int = 59
	i := 99

7、变量三要素

变量=变量名+值+数据类型

8、Golang的变量如果没有赋初值,编译器会使用默认值。

比如int默认值 0

String默认值为空串

小数默认为 0  

六、程序中 + 号的使用

1)当左右两边都是数值型时,则做加法运算

2)当左右两边都是字符串,则做字符串拼接

package main
import "fmt"

func main(){

	var i = 1
	var j = 2
	var r = i + j //做加法运算
	fmt.Println("r=", r)

	var str1 = "hello"
	var str2 = "world"
	var res = str1 + str2 //做拼接操作
	fmt.Println("res=", res)
}

变量的数据类型

 七、整数类型

注: uint8的范围(0-255),其他的uint16,uint32依此类推

 整型的使用细节

1) Golang各整数类型分:有符号无符号,int uint 的大小和系统有关。

2)Golang的整型默认声明为int型

3)如何在程序查看某个变量的字节大小和数据类型

4) Golang程序中整型变量在使用时,遵守保小不保大的原则

        即:在保证程序正确运行下,尽量使用占用空间小的数据类型。【如:年龄】

5) bit:计算机中的最小存储单位。byte:计算机中基本存储单元。

八、浮点类型

 1)关于浮点数在机器中存放形式的简单说明,浮点数=符号位+指数位+尾数位3.56

 2)尾数部分可能丢失,造成精度损失。 -123.0000901

说明:float64的精度比float32的要准确

如果我们要保存一个精度高的数,则应该选用float64

 3)浮点型的存储分为三部分: 符号位 + 指数位 + 尾数位 在存储过程中,精度会有丢失

浮点型使用细节

1)Golang浮点类型有固定的范围和字段长度,不受具体OS(操作系统)的影响。

2)Golang的浮点型默认声明为 float64 类型。  【%T】

3)浮点型常量有两种表示形式

十进制数形式 如: 5.12   /     .512【效果等于0.512】(必须有小数点)

科学计数法形式 如:5.1234e2 = 5.12 * 10 的2次方     5.12E-2 = 5.12/10的2次方

4)通常情况下,应使用float64,因为它比float32更精确。【开发中,推荐使用float64】

九、字符类型(char)

Golang中没有专门的字符类型,如果要存储单个字符(字母),一般使用byte来保存。

      字符串就是一串固定长度的字符连接起来的字符序列。Go的字符串是由单个字节连接起来的。也就是说对于传统的字符串是由字符组成的,而Go的字符串不同,它是由字节组成的
 

package main
import (
	"fmt"
)

func main(){
	var c1 byte = 'a'
	var c2 byte = '1' //字符的1


	//当我们直接输出byte值,就是输出了的对应的字符的码值
	fmt.Println("c1=", c1)
	fmt.Println("c2=", c2)
	//如果我们希望输出对应字符,需要使用格式化输出
	fmt.Printf("c1=%c c2=%c\n", c1, c2)//注意此处为Printf

	//var c3 byte = '北' //overflow 溢出
	var c3 int = '北' //overflow 溢出
	fmt.Printf("c3=%c c3对应码值=%d", c3, c3)

}

1)如果我们保存的字符在ASCII表的,比如[0-1,a-z,A-Z...]直接可以保存到byte

2)如果我们保存的字符对应码值大于255,这时我们可以考虑使用int类型保存

3)如果我们需要安装字符的方式输出,这时我们需要格式化输出,即 fmt.Printf("%c", c1)...

4)字符类型是可以进行运算的,相当于一个整数,因为它都对应有Unicode码

注:Go语言的字符使用UTF-8编码

英文字母-1个字节 汉字-3个字节

Go语言的编码都统一成了utf-8。非常的方便,很统一,再也没有编码乱码的困扰了

存取过程

存储:字符--->对应码值--->二进制--->存储

读取: 二进制--->码值--->字符--->读取

十、布尔类型:bool

1)bool类型占1个字节

package main
import (
	"fmt"
	"unsafe"
)

func main() {
	var b = false
	fmt.Println("b=", b)
	fmt.Println("b 的占用空间 =", unsafe.Sizeof(b) )
	
}

2)boolean类型适于逻辑运算,一般用于程序流程控制

if条件控制语句:

for循环控制语句:

3)不可以0或非0的整数替代false和true,这点和C语言不同

十一、字符串类型:String

package main
import (
	"fmt"
)

func main() {
	var address string = "你好世界!"
	fmt.Println(address)
}

 注意事项

1、字符串一旦赋值了,字符串就不能修改了,在Go中字符串是不可变的

2、字符串的两种表示形式

(1)双引号,会识别转义字符

(2)反引号,以字符串的原生形式输出,包括换行和特殊字符,可以实现防止攻击、输出源代码等效果  【`....`】

基本数据类型默认值

 

 十二、基本数据类型的转换

Go在不同类型的变量之间赋值时需要显式转换(强制转换),而就是说Golang中数据类型不能自动转换

基本语法

表达式T(v)将值v转换为类型T

T:就是数据类型,比如 int32 , int64, float32等等

v:就是需要转换的变量

package main
import (
	"fmt"
)

func main(){
	var i int32 = 100
	//将i => float
	var n1 float32 = float32(i)
	var n2 int8 = int8(i)
	var n3 int64 = int64(i) 

	fmt.Printf("i=%v n1=%v n2=%v n3=%v", i, n1, n2, n3)
}

注意

1、Go中,数据类型的转换可以是从 表示范围小-->表示范围大,也可以 范围大--->范围小

2、被转换的是变量存储的数据(即值),变量本身的数据类型并没有变化

3、在转换中,比如将int64 转成 int8 【-128----127】,编译时不会报错,只是转换的结果是按溢出处理,和我们希望的结果不一样.因此在转换时,需要考虑范围

package main
import(
	"fmt"
)
func main() {
	var n1 int32 = 12
	var n2 int64
	var n3 int8

	n2 = n1 + 20  //int32 ---> int64 报错 改正【int64(n1) + 20】 
	n3 = n1 + 20  //int32 ---> int8 报错 改正【int8(n1) + 20】
	fmt.Println("n2=", n2, "n3=", n3)
}
package main
import(
	"fmt"
)
func main() {
	var n1 int32 = 12
	var n3 int8
	var n4 int8
	n4 = int8(n1) + 127 //【编译通过,但是 结果 不是127+12,按溢出处理】
	n3 = int8(n1) + 128 //【编译不通过】
	fmt.Println(n4)
}

上述代码结果为 n4 = -117   n3由于编译不通过无法运行代码

如何忽略包

十三、基本数据类型和string的转换

基本类型转string类型

方式1:fmt.Sprintf("%参数", 表达式)

package main
import (
	"fmt"
)

func main(){
	var num1 int = 99
	var num2 float64 = 23.456
	var b bool = true
	var a bool = true
	var myChar byte = 'h'
	var str string //空的str

	//使用第一种方式来转换 fmt.Sprintf方法

	str = fmt.Sprintf("%d", num1)
	fmt.Printf("str type %T str=%v\n", str, str)

	str = fmt.Sprintf("%f", num2)
	fmt.Printf("str type %T str=%v\n", str, str)

	str = fmt.Sprintf("%t", b)
	fmt.Printf("str type %T str=%v\n", str, str)

	str = fmt.Sprintf("%t", a)
	fmt.Printf("str type %T str=%q\n", str, str)

	str = fmt.Sprintf("%c", myChar)
	fmt.Printf("str type %T str=%q\n", str, str)
}

%v  值的默认格式表示

%t   单词true或false

%d  表示为十进制

%f   有小数部分但无指数部分,如123.456

%q  该值对应的单引号括起来的go语法字符字面值,必须时会采用安全的转义表示

方法2:使用strconv包的函数

package main
import (
	"fmt"
	"strconv"
)

func main() {
	var str string
	var num3 int = 99
	var num4 float64 = 23.456
	var b2 bool = true

	str = strconv.FormatInt(int64(num3), 10)
	fmt.Printf("str type %T str=%q\n", str, str)
	//str = strconv.FormatFloat(num4, 'f', 10, 64)
	//说明:'f' 格式 10 :表示小数位保留10位   64:表示这个小数是float64
	str = strconv.FormatFloat(num4, 'f', 10, 64)
	fmt.Printf("str type %T str=%q\n", str, str)

	str = strconv.FormatBool(b2)
	fmt.Printf("str type %T str=%q\n", str, str)
}

 string类型转基本数据类型

使用strconv包的函数

package main
import (
	"fmt"
	"strconv"
)

func main() {
	var str string = "true"
	var b bool
	//b , _ = strconv.ParseBool(str)
	//说明
	//1.strconv.ParseBool(str) 函数会返回两个值 (value bool, err error)
	//2.因为我只想获取到 value bool , 不想获取 err 所以我使用 _ 忽略
	b , _ = strconv.ParseBool(str)
	fmt.Printf("b type %T b=%v\n", b, b)

	var str2 string = "1234567890"
	var n1 int64
	var n2 int
	n1, _ = strconv.ParseInt(str2, 10, 64)
	n2 = int(n1)
	fmt.Printf("n1 type %T n1=%v\n", n1, n1)
	fmt.Printf("n2 type %T n1=%v\n", n2, n2)

	var str3 string = "123.456"
	var f1 float64
	f1, _ = strconv.ParseFloat(str3, 64)
	fmt.Printf("f1 type %T f1=%v\n", f1, f1)
}

因为返回的是int64或者float64,如希望要得到int32,float32等如下处理:

//如果希望将str->int32的可以这样处理
var num5 int32
num5 = int32(num)

注意事项

在将String 类型转成 基本数据类型时,要确保String类型能够转成有效的数据,比如我们可以把"123”,转成一个整数,但是不能把“hello”转成一个整数,如果这样做,Golang直接将其转成0 ,其它类型也是一样的道理, float => 0 bool => false

十四、指针

1、基本数据类型,变量存的就是值,也叫值类型

        分析一下基本数据类型在内存的布局

2、获取变量的地址, 用&,比如: var num int, 获取num的地址:&num

3、指针类型,指针变量存的是一个地址,这个地址指向的空间存的才是值

比如: var ptr *int = &num

package main
import (
	"fmt"
)

//演示golang中指针类型
func main() {
	//基本数据类型在内存布局
	var i int = 10
	// i 的地址是什么,&i
	fmt.Println("i的地址=", &i)

	//下面的 var ptr *int = &i
	//1. ptr 是一个指针变量
	//2. ptr 的类型 *int
	//3. ptr 本身的值&i
	var ptr *int = &i
	fmt.Printf("ptr=%v\n", ptr)
	fmt.Printf("ptr 的地址=%v\n", &ptr)
	fmt.Printf("ptr 指向的值=%v\n", *ptr)
}

 4、获取指针类型所指向的值,使用:*,比如: var ptr *int,使用*ptr获取ptr指向的值

5、

println和printf的区别

Println打印字符串变量

Printf打印需要格式化的字符串可以输出字符串类型的变量;不可以输出变量和整型;

简要概括 当需要格式化输出信息时,一般选择Printf,其余使用Println

课堂作业

1)写一个程序,获取一个int变量num的地址,并显示到终端

2)将num的地址赋给指针ptr,并通过ptr去修改num的值

package main
import(
	"fmt"
)

func main(){
	var num int = 100
	fmt.Printf("num= %v\n", &num)

	var ptr *int
	ptr = &num
	*ptr = 10 //在这里修改时,会到num的值变化
	fmt.Println("num= ", num)
}

指针的使用细节

1)值类型,都有对应的指针类型,形式为 *数据类型,比如 int 的对应的指针就是 *int , float32对应的指针类型就是  *float32,依次类推。
2)值类型包括:基本数据类型 int系列, float系列, bool, string 、数组 和 结构体struct

十五、值类型和引用类型

常见的值类型和引用类型

1)值类型:基本数据类型 int系列, float系列, bool, string、数组 和 结构体struct

2)引用类型:指针、slice切片、map、管道chan、interface 等都是引用类型

值类型和引用类型使用特点:

1)值类型:变量直接存储值,内存通常在栈中分配

2)引用类型:变量存储的是一个地址,这个地址对应的空间才真正存储数据(值),内存通常堆上分配,当没有任何变量引用这个地址时,该地址对应的数据空间就成为一个垃圾,由GC来回收。

3)内存的栈区和堆区示意图

 

十六、标识符的命名规范(重点)

Golang对各种变量、方法、函数等命名时使用的字符序列称为标识符

凡是自己可以起名字的地方都叫标识符

系统保留关键字

 预定标识符

十七、标识符命名注意事项(重点)

1、变量名、函数名、常量名:采用驼峰法

2、如果变量名、函数名、常量名首字母大写,则可以被其他的包访间;如果首字母小写,则只能在本包中使用(注:可以简单的理解成,首字母大写是公有的(public),首字母小写是私有的(private)),在golang中没有public , private 等关键字。

注意事项

当GO引入包报错 PACKAGE ... IS NOT IN GOROOT

首先查看go环境变量配置

go env

查看GO111MODULE是否设置为off

若没有设置则输入以下代码

go env -w GO111MODULE=off

使用案例:

在main文件夹下创建main.go

package main
import(
	"fmt"
	//为了使用utils.go文件的变量或者函数,我们需要先引入该model包
	"go_code/project02/model"
)
func main() {

	fmt.Println(model.HeroName)
}

在model文件夹下创建utils.go

package model

var HeroName string = "百特曼"

十八、运算符

1、算数运算符


 在golang中,++ 和 -- 只能独立使用

package main
import (
	"fmt"
)

func main() {
	var i int = 10
	i++
	fmt.Println("i=", i)
}

Golang的 ++ 和 -- 只能写在变量的后面,不能写在变量的前面,即:只有a++ a--没有++a  --a

2、关系运算符(比较运算符)

1)关系运算符的结果都是bool型,也就是要么是true,要么是false

2)关系表达式 经常用在 if结构的条件中或循环结构的条件中

 细节说明

 3、逻辑运算符

用于连接多个条件(一般来讲就是关系表达式),最终的结果也就是一个bool值

 4、赋值运算符

赋值运算符就是将某个运算后的值,赋给指定的变量。

 

赋值运算符的特点

赋值运算符的左边 只能是变量,

右边 可以是变量、表达式、常量值

5、运算符优先级

 6、位运算符

7、其他运算符

 Go语言明确不支持三元运算符

若要实现三元运算符则用 if else方式解决

//传统的三元运算
n = i > j ? i : j
//如果成立则返回i这个值,不成立则返回j这个值

Go要实现三元运算需要使用下面这种方法

package main
import (
	"fmt"
)
func main() {
	var n int
	var i int = 10
	var j int = 12
	
	if i > j {
		n = i 
	} else {
		n = j
	}
	fmt.Println("n=", n)
}

十九、键盘输入语句

编程步骤:1、导入fmt包   2、调用fmt包的 fmt.Scanln() 或者 fmt.Scanf()

特别注意 Scanln 会在换行时才停止扫描。最后一个条目后必须有换行或者到达结束位置

1)使用 fmt.Scanln() 获取

package main
import (
	"fmt"
)

func main() {
	//方式1 fmt.Scanln
	var name string
	var age byte
	var sal float32
	var isPass bool
	fmt.Println("请输入姓名")
	//程序会停止在这,等待用户输入,并回车
	fmt.Scanln(&name)

	fmt.Println("请输入年龄")
	fmt.Scanln(&age)

	fmt.Println("请输入薪水")
	fmt.Scanln(&sal)

	fmt.Println("请输入是否通过考试")
	fmt.Scanln(&isPass)

	fmt.Printf("名字是 %v 年龄是 %v 薪水是 %v 是否通过考试 %v \n", name, age, sal, isPass)
}

2)fmt.Scanf() 获取

package main
import (
	"fmt"
)

func main() {
	//方式2 fmt.Scanf,可以按指定的格式输入
	var name string
	var age byte
	var sal float32
	var isPass bool
	fmt.Println("请输入您的姓名、年龄、薪水、是否通过考试,使用空格隔开")
	fmt.Scanf("%s %d %f %t", &name, &age, &sal, &isPass)
	fmt.Printf("名字是 %v 年龄是 %v 薪水是 %v 是否通过考试 %v \n", name, age, sal, isPass)
}

二十、进制转换

1) 二进制转换成十进制

1011 = 1 * 2^0 + 1 * 2^1 + 0 * 2^2 + 1 * 2^3 = 1 + 2 + 0 + 8 = 11

2)八进制转换成十进制

0123 = 3 * 8^0 + 2 * 8^1 + 1 * 8^2 + 0 * 8^3 = 3 + 16 + 64 = 83

3)十六进制转换成十进制

0x34A = 10 * 16^0 + 4 * 16^1 + 3 * 16^2 = 10 + 64 + 768 = 842

4)十进制转换成二进制

将该数不断除以2,直到商为0为止,将每步得到的余数倒过来,就是对应的二进制

例:将56转化为二进制 = 111000

 5)十进制转换成八进制

 156转化为八进制 = 234

 6)十进制转换成十六进制

356转化为十六进制 = 0x164

7)二进制转八进制

每三位一组 (从低位开始组合)

11 010 101 = 011 010 101 = 3 2 5 = 325

8)二进制转十六进制

每四位一组 (从低位开始组合)

1101 0101 = 13 5 = D 5 = 0xD5

9)八进制转二进制

0237 = 0 2 3 7 = 000 010 011 111 = 10 011 111 = 10011111

10)十六进制转二进制

0x237 = 2 3 7 = 0010 0011 0111 = 10 0011 0111 = 1000110111

二十一、位运算

1、原码 反码 补码

        1)二进制最高位是符号位: 0为正数 , 1 为负数

        2)正数的 原、 反、 补 都一样

        3)负数的反码 = 符号位不变 ,其余取反 (0->1,1->0)

        4)负数的补码 = 反码 + 1

       例: 1 =====> [0000 0001]

               -1 =====>原码[1000 0001] 反码 [1111 1110] 补码[1111 1111]

        5)0的 反码 补码 都是 0

        6)计算机运算的时候,都是以补码的方式来运算的

2、位运算和移位运算

Golang中有3个位运算

按位与 &        :        两位全为 1 ,结果为1,否则为0

按位或 |         :      两位有一个为 1 ,结果为1 ,否则为0

按位异或 ^     :     两位 一个为0,一个为1,结果为1,否则为0 

例:

Golang中有2个移位运算

右移运算符 >> :低位溢出,符号位不变,并用符号位补溢出的高位

左移运算符 << : 符号位不变,低位补0

a := 1 >> 2      【右移两位】-------------       0000 0001 => 0000 0000(01) => 0000 0000 = 0

c := 1 << 2      【左移两位】-------------       0000 0001 => 0000 0001 00 =>00 0000 0100 = 4



---------<分割线:以上为基础知识>-----------



二十二、程序流程控制

1、单分支控制

 2、双分支控制

3、多分支控制

 对上面基本语法的说明

1)多分支的判断流程如下:
        (1)先判断条件表达式1是否成立,如果为真,就执行代码块1
        (2)如果条件表达式1如果为假,就去判断条件表达式2是否成立,如果条件表达式2为真,就执行代码块2
        (3)依次类推.
        (4)如果所有的条件表达式不成立,则执行else 的语句块。

2) else不是必须的。
3)多分支只能有一个执行入口。

注意:

        在golang语句中 if 后面必须是条件语句 不能是赋值语句

下面这种写法会报错

练习:

练习1

package main

import (
	"fmt"
	"math"
)

func main() {
	var a float64
	var b float64
	var c float64
	var x1 float64
	var x2 float64

	fmt.Println("输入a")
	fmt.Scanln(&a)
	fmt.Println("输入b")
	fmt.Scanln(&b)
	fmt.Println("输入c")
	fmt.Scanln(&c)

	var flag float64 = b*b - 4*a*c

	if flag > 0 {
		x1 = (-b + math.Sqrt(flag)) / 2 * a
		x2 = (-b - math.Sqrt(flag)) / 2 * a
		fmt.Printf("两个解,分别是 x1=%v x2=%v", x1, x2)
	} else if flag == 0 {
		x1 = (-b + math.Sqrt(flag)) / 2 * a
		fmt.Printf("一个解,x=%v", x1)
	} else {
		fmt.Println("无解")
	}

}

 

练习2

package main

import (
	"fmt"
)

func main() {
	var height int32
	var money float32
	var handsome bool

	fmt.Println("身高: 财富(单位万): 帅吗:")
	fmt.Scanf("%d %f %t", &height, &money, &handsome)

	if height >= 180 && money >= 1000 && handsome == true {
		fmt.Println("我一定要嫁给他")
	} else if height >= 180 || money >= 1000 || handsome == true {
		fmt.Println("嫁吧,比上不足,比下有余")
	} else {
		fmt.Println("不嫁")
	}

}

4、嵌套分支

 注:嵌套分支不宜过多,最多建议控制在3层

案例1

package main

import (
	"fmt"
)

func main() {
	var second float64
	var gender string

	fmt.Println("请输入成绩")
	fmt.Scanln(&second)
	fmt.Println("请输入性别")
	fmt.Scanln(&gender)

	if second <= 8 {
		if gender == "男" {
			fmt.Println("晋级男子组决赛")
		} else {
			fmt.Println("晋级女子组决赛")
		}
	} else {
		fmt.Println("淘汰")
	}
}

案例2

package main

import (
	"fmt"
)

func main() {
	var month byte
	var age byte
	var price float64 = 60.0
	fmt.Println("请输入买票月份")
	fmt.Scanln(&month)
	fmt.Println("请输入买家年龄")
	fmt.Scanln(&age)

	if month >= 4 && month <= 10 {
		if age >= 18 && age <= 60 {
			fmt.Printf("票价 %v", price)
		} else if age < 18 {
			fmt.Printf("票价 %v", price/2)
		} else {
			fmt.Printf("票价 %v", price/3)
		}
	} else {
		if age >= 18 && age <= 60 {
			fmt.Println("票价 40")
		} else {
			fmt.Println("票价 20")
		}
	}
}

5、switch分支结构

注意:

1)switch匹配项后面不需要再加break,因为默认会有

2)default语句不是必须的

3)case后的各个表达式的值的数据类型,必须和switch的表达式数据类型一致

4) case可以带多个表达式,使用逗号间隔,满足其中一个表达式就会进入.

5)switch后也可以不带表达式,类似if-else分支来使用

package main

import "fmt"

func main() {
	var day string

	fmt.Println("请输入a,b,c,d,e,f,g其中一个")
	fmt.Scanln(&day)

	switch day {
	case "a":
		fmt.Println("周一")
	case "b":
		fmt.Println("周二")
	case "c":
		fmt.Println("周三")
	case "d":
		fmt.Println("周四")
	case "e":
		fmt.Println("周五")
	case "f":
		fmt.Println("周六")
	case "g":
		fmt.Println("周日")
	default:
		fmt.Println("输入无效")
	}
}

6)switch 穿透 fallthrough,如果在case语句块后增加fallthrough,则会继续执行下一个case,也叫switch穿透

fallthrough默认只能穿透一层

7)Type Switch: switch语句 还可以被用于type-switch来判断某个interface变量中实际指向的变量类型

 课堂作业

1、对学生成绩大于60分的,输出“合格”。低于60分的,输出“不合格”(注:输出的成绩不能大于100)

package main

import "fmt"

func main() {
	var score byte

	fmt.Println("请输入成绩")
	fmt.Scanln(&score)

	if score > 100 {
		score = 200
	}

	switch int(score / 60) {
	case 1:
		fmt.Println("及格")
	case 0:
		fmt.Println("不及格")
	default:
		fmt.Println("输入成绩异常")
	}
}

2、根据用户指定月份打印该月份所属的季节。3,4,5春季 以此类推

package main

import "fmt"

func main() {
	var month int

	fmt.Println("请输入正确的月份")
	fmt.Scanln(&month)

	switch month {
	case 3, 4, 5:
		fmt.Println("春季")
	case 6, 7, 8:
		fmt.Println("夏季")
	case 9, 10, 11:
		fmt.Println("秋季")
	case 12, 1, 2:
		fmt.Println("冬季")
	default:
		fmt.Println("请输入正确月份")
	}
}

switch和if的比较

1)如果判断的具体数值不多,而且符合整数、浮点数、字符、字符串这几种类型。建议使用swtich语句,简洁高效。

2)其他情况:对区间判断和结果为bool类型的判断,使用if,if的使用范围更广

6、for循环控制

for循环三种方式示例:

1、

package main

import "fmt"

func main() {
	for i := 1; i <= 10; i++ {
		fmt.Println("你好世界")
	}
}

2、

package main

import "fmt"

func main() {
    i := 1
	for i <= 10 {
		fmt.Println("你好世界")
        i++
	}
}

3、

package main

import "fmt"

func main() {
    i := 1
	for i <= 10 {
		fmt.Println("你好世界")    
	} else {
        break
    }
    i++
}

对for循环来说,有四个要素:

        1)循环变量初始化

        2)循环条件

        3)循环操作(语句)

        4)循环变量迭代

 注意事项:

循环条件是返回一个布尔值的表达式

for-range-------方便遍历字符串和数组

Golang 提供 for-range 的方式,可以方便遍历字符串和数组(注:数组的遍历,我们放到讲数组的时候再讲解),案例说明如何遍历字符串

package main

import "fmt"

func main() {
	var str string
	str = "abc~ok"
	for index, val := range str {
		fmt.Printf("index=%d, val=%c \n", index, val)
	}
}

        传统方式无法正常输出中文,需要用以下代码实现输出中文的功能。

        对于for-range遍历方式而言,则不会出现次情况。它是按照字符方式遍历,因此如果有中文也能正常输出

package main

import "fmt"

func main() {
	var str string
	str = "只因你太美"
	str2 := []rune(str)
	for i := 0; i < len(str2); i++ {
		fmt.Printf("%c \n", str2[i])
	}
}

课堂小测

1、打印1~100之间所有是9的倍数的整数的个数及总和

package main

import "fmt"

func main() {
	var num int = 0
	var plus int = 0
	for i := 1; i <= 100; i++ {
		if i%9 == 0 {
			num++
			plus += i
		}
	}
	fmt.Printf("1~100之间所有是9的倍数的整数个数为 %v 和为 %v", num, plus)
}

2、

package main

import "fmt"

func main() {
	var n int = 6
	for x1 := 0; x1 <= n; x1++ {
		fmt.Printf("%v + %v = %v \n", x1, n-x1, n)
	}
}

7、Go语言没有while和do...while语法

若要实现可用for循环来实现相同的效果

8、多重循环控制(重点、难点)

1)在外边的for循环称为外层循环,在里面的for循环称为内层循环。

建议一般使用两层,最多不要超过3层

编程时两大绝招

(1)先易后难,即 将一个复杂的问题分解成简单的问题。

(2)先死后活,即 先把程序写死后用变量将程序盘活。

课堂小测

金字塔

1、编写一个程序,可以接收一个整数,表示层数,打印出金字塔

直角金字塔

package main

import "fmt"

func main() {
	for i := 1; i <= 5; i++ {
		for j := 1; j <= i; j++ {
			fmt.Printf("*")
		}
		fmt.Println()
	}
}

实心金字塔 

package main

import "fmt"

func main() {
	for i := 1; i <= 5; i++ {
		for k := 1; k <= 5-i; k++ {
			fmt.Printf(" ")
		}
		for j := 1; j <= 2*i-1; j++ {
			fmt.Printf("*")
		}
		fmt.Println()
	}
}

空心金字塔

package main

import "fmt"

func main() {
	var totalLevel int = 5

	for i := 1; i <= totalLevel; i++ {
		for k := 1; k <= totalLevel-i; k++ {
			fmt.Printf(" ")
		}
		for j := 1; j <= 2*i-1; j++ {
			if j == 1 || j == 2*i-1 || i == totalLevel {
				fmt.Printf("*")
			} else {
				fmt.Print(" ")
			}
		}
		fmt.Println()
	}
}

九九乘法表

package main

import "fmt"

func main() {
	var num int

	for i := 1; i <= 9; i++ {
		for j := 1; j <= i; j++ {
			num = i * j
			fmt.Printf("%v * %v = %v \t", j, i, num)
		}
		fmt.Println()
	}
}

9、跳转控制语句-break

随机生成1-100的一个数,直到生成了99这个数,看看你一共用了几次?

package main

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

func main() {
	//我们为了生成一个随机数,还需要rand设置一个种子
	//time。Now().UnixNano():返回一个从1970:01:01  的0时0分0秒到现在的微秒数
	//rand.Seed(time.Now().UnixNano())
	//如何随机的生成1-100整数
	//n := rand.Intn(100) + 1
	//rand.Intn(100) 随机生成 0 - 99的数
	var count int = 0
	for {
		rand.Seed(time.Now().UnixNano())
		n := rand.Intn(100) + 1
		fmt.Println("n=", n)
		count++
		if n == 99 {
			break
		}
	}
	fmt.Println("生成 99 一共使用了 ", count)
}

 注意事项

break默认会跳出最近的for循环

但使用 上图的break label2 结果会跳到 label1 内的语句块

10、跳转控制语句-continue

continue语句用于结束本次循环继续执行下一次循环

可以通过标签指明要跳过的是哪一层循环,和break标签的使用规则一样

 

 11、跳转控制语句-goto

1)Go语言的goto语句可以无条件地转移到程序中指定的行

2)goto语句通常与条件语句配合使用。可以用来实现条件转移,跳出循环体等功能

3)在Go程序设计中一般不主张使用goto语句,以免造成程序流程的混乱,使理解和调试程序都产生困难

 

 示例代码

package main

import "fmt"

func main() {
	var n int = 30

	fmt.Println("ok1")
	if n > 20 {
		goto label1
	}
	fmt.Println("ok2")
	fmt.Println("ok3")
	fmt.Println("ok4")
label1:
	fmt.Println("ok5")
	fmt.Println("ok6")
	fmt.Println("ok7")
}

12、跳转控制语句-return

return使用在方法或者函数中,表示跳出所在得方法或函数,在讲解函数得时候会详细得介绍

1)如果return是在普通的函数,则表示跳出该函数,即不再执行函数中return后面代码,也可理解成终止函数

2)如果return是在main函数,表示终止main函数,也就是说终止程序



------------以上是流程控制------------



二十三、函数

为完成某一功能的程序指令(语句)的集合,称为函数

在Go中,函数分别为:自定义函数、系统函数

 快速入门

package main

import "fmt"

//将计算的功能,放到一个函数中,然后在需要使用,调用即可
func cal(n1 float64, n2 float64, operator byte) float64 {
	var res float64
	switch operator {
	case '+':
		res = n1 + n2
	case '-':
		res = n1 - n2
	case '*':
		res = n1 * n2
	case '/':
		res = n1 / n2
	default:
		fmt.Println("操作符号错误...")
	}
	return res
}

func main() {
	var n1 float64 = 1.2
	var n2 float64 = 2.3
	var operator byte = '-'
	result := cal(n1, n2, operator)
	fmt.Println("result=", result)
}

1、包的引出和使用原理

原理图

包的基本概念

 go的每一个文件都是属于一个包的,也就是说go是以包的形式来管理文件和项目目录结构的

包的三大作用

1)区分相同名字的函数、变量等标识符

2)当程序文件很多时,可以很好的管理项目

3)控制函数、变量等访问范围,即作用域

包的相关说明

打包基本语法

package 包名


引入包的基本语法

import "包的路径"

在import包时,路径从$GOPATH的src下开始,不用带src,编译器会自动从src下开始引入

案例

 main.go

package main

import (
	"fmt"
	"go_code/project03/demo1/utils"
)

func main() {
	var n1 float64 = 1.2
	var n2 float64 = 2.3
	var operator byte = '+'
	result := utils.Cal(n1, n2, operator)
	fmt.Println("result=", result)
}

utils.go

package utils

import "fmt"

//将计算的功能,放到一个函数中,然后在需要使用,调用即可
//为了让其他包的文件使用Cal函数,需要将C大写 类似其他语言的public
func Cal(n1 float64, n2 float64, operator byte) float64 {
	var res float64
	switch operator {
	case '+':
		res = n1 + n2
	case '-':
		res = n1 - n2
	case '*':
		res = n1 * n2
	case '/':
		res = n1 / n2
	default:
		fmt.Println("操作符号错误...")
	}
	return res
}

注意:在同一包下,不能有相同的函数名(也不能有相同的全局变量名),否则报重复定义

如果要编译成一个可执行程序文件,就需要将这个包声明为main,即 package main。这个就是一个语法规范,如果是写一个库,包名可自定义

go build -o bin\my.exe go_code\chapter06\fundemo01\main

意思为:

将 go_code\chapter06\fundemo01\main 目录下的 main.go 重命名为my.exe并放在bin目录下

---------------------->>

在调用一个函数时,会给该函数分配一个新的空间,编译器会通过自身的处理让这个新的空间和其他的栈的空间区分开来

在每个函数对应的栈中,数据空间是空间的,不会混淆

当一个函数调用完毕(执行完毕)后,程序会销毁这个函数对应的栈空间

当函数有return语句时,就是将结果返回给调用者【谁调用我,就返回给谁】

 如果返回值只有一个,返回值类型列表可以不用“()”

2、函数-递归调用

一个函数在函数体内调用了本身,我们称为递归调用

快速入门

package main

import (
	"fmt"
)

func test(n int) {
	if n > 2 {
		n--
		test(n)
	}
	fmt.Println("n=", n)
}
func main() {
	test(4)
}

 

递归函数需要遵守的重要原则

1)执行一个函数时,就创建一个新的受保护的独立空间(新函数栈)

2)函数的局部变量是独立的,不会相互影响

3)递归必须向退出递归的条件逼近,否则就是无限递归)

4)当一个函数执行完毕,或者遇到return,就会返回,遵守谁调用,就将结果返回
给谁。

5)函数中的变量是局部的,函数外不生效

6)函数的命名遵循标识符命名规范,首字母不能是数字,首字母大写作用类似public,首字母小写作用类似private

7)基本数据类型数组默认都是值传递的,即进行值拷贝。在函数内修改,不会影响到原来的值。

8)Go函数不支持传统函数重载

9)支持对函数返回值命名

课堂作业

1、使用递归的方式,求出斐波那契数

package main

import (
	"fmt"
)

func fbn(n int) int {
	if n == 1 || n == 2 {
		return 1
	} else {
		return fbn(n-1) + fbn(n-2)
	}
}

func main() {
	res := fbn(3)

	fmt.Println("res=", res)
}

2、有一堆桃子,猴子第一天吃了其中的一半,并再多吃了一个!以后每天猴子都吃其中的一半,然后再多吃一个。当到第十天时,想再吃时(还没吃),发现只有1个桃子了。

问题:最初共多少个桃子?

package main

import (
	"fmt"
)

func eat(n int, day int) int {
	if day >= 10 {
		return n
	} else {
		n = (n + 1) * 2
		day++
		return eat(n, day)
	}
}

func main() {
	res := eat(1, 1)

	fmt.Println("res=", res)
}

课堂小测

 3、init函数

每一个源文件都可以包含一个init函数,该函数会在main函数执行前,被Go运行框架调用,也就是说init会在main函数前被调用。
通常可以在init函数中完成初始化工作

如果一个文件同时包含全局变量定义init函数main函数,则执行的流程是:

变量定义->init函数->main函数

4、匿名函数

Go支持匿名函数,匿名函数就是没有名字的函数,如果我们某个函数只是希望使用一次,可以考虑使用匿名函数,匿名函数也可以实现多次调用

使用方式1

在定义匿名函数时就直接调用,这种方式匿名函数只能调用一次

func main() {
	res1 := func (n1 int, n2 int) int {
		return n1 + n2
	}(10, 20)

	fmt.Println("res1=", res1)

使用方式2

在匿名函数赋给一个变量(函数变量),再通过该变量来调用匿名函数

a := func(n1 int, n2 int) int {
		return n1 - n2
	}

	res2 := a(10, 30)
	fmt.Println("res2=", res2)
	res3 := a(90, 30)
	fmt.Println("res3=", res3)

全局匿名函数

如果将匿名函数赋给一个全局变量,那么这个匿名函数,就成为一个全局匿名函数,可以在程序有效

var (
	//fun1就是一个全局匿名函数
	Fun1 = func(n1 int, n2 int) int {
		return n1 * n2
	}
)

func main() {
	res4 := Fun1(4, 9)
	fmt.Println("res4=", res4)

}

二十四、闭包

闭包就是一个函数其他相关的引用环境组合的一个整体(实体)

//累加器
func AddUpper() func(int) int {
	var n int = 10
	return func(x int) int {
		n = n + x
		return n
	}
}

func main() {
	f := AddUpper()
	fmt.Println(f(1))
	fmt.Println(f(2))
	fmt.Println(f(3))

}

AddUpper是一个函数,返回的数据类型是fun(int) int

下图就是此代码的闭包

 返回的是一个匿名函数,但是这个匿名函数引用到函数外的n,因此这个匿名函数就和n形成一个整体,构成闭包。

当我们反复的调用f函数时,因为n是初始化一次,因此每调用一次就进行累计。而不会重新初始化        

        我们要搞清楚闭包的关键,就是要分析出返回的函数它使用(引用)到哪些变量,因为函数和它引用到的变量共同构成闭包。

课堂作业

package main

import (
	"fmt"
	"strings"
)

func makeSuffix(suffix string) func(string) string {
	return func(name string) string {
		if !strings.HasSuffix(name, suffix) {
			return name + suffix
		}

		return name
	}
}

func main() {
	f2 := makeSuffix(".jpg")
	fmt.Println("文件名处理后=", f2("winter"))
	fmt.Println("文件名处理后=", f2("bird.jpg"))
}

二十五、函数中-defer

在函数中,程序员经常需要创建资源(比如:数据库连接、文件句柄、锁等),为了在函数执行完毕后,及时的释放资源,Go的设计者提供defer(延时机制)
 

package main

import (
	"fmt"
)

func sum(n1 int, n2 int) int {

	//当执行到defer时,暂时不执行,会将defer后面的语句压入到独立的栈(defer栈)
	//当函数执行完毕之后,再从defer栈,按照先后的方式出栈,执行
	defer fmt.Println("ok1 n1=", n1)
	defer fmt.Println("ok2 n2=", n2)

	res := n1 + n2
	fmt.Println("ok3 res=", res)
	return res
}

func main() {
	res := sum(10, 20)
	fmt.Println("res=", res)
}

 依次从栈顶取出语句执行(注:遵守栈  先入后出的机制)

说明:

1)在golang编程中的通常做法是,创建资源后,比如(打开了文件,获取了数据库的链接,或者是锁资源),可以执行defer.file.Close() defer connect.Close()
2)在defer后,可以继续使用创建资源.
3)当函数完毕后,系统会依次从defer栈中,取出语句,关闭资源.

4)这种机制,非常简洁,程序员不用再为在什么时机关闭资源而烦心。

二十六、函数参数的传递方式

两种传第方式

1)值传递:基本数据类型int系列, float系列, bool, string 、数组和结构体struct

2)引用传递:指针、slice切片、map、管道chan、interface等都是引用类型

一般来说,地址拷贝效率高,因为数据量小,而值拷贝决定拷贝的数据大小,数据越大,效率越低

 3)如果希望函数内的变量能修改函数外的变量,可以传入变量的地址&,函数内以指针的方式操作变量。从效果上看类似引用。【这个案例在前面详解函数使用注意事项的】

二十七、变量作用域

1)函数内部声明/定义的变量叫局部变量作用域仅限于函数内部
2)函数外部声明/定义的变量叫全局变量作用域在整个包都有效,如果其首字母为大写,则作用域在整个程序有效。
3)如果变量是在一个代码块,比如 for/if 中,那么这个变量的作用域就在该代码块

 函数课堂练习(综合)

1、函数可以没有返回值案例,编写一个函数,从终端输入一个整数打印出对应的金子塔

package main

import (
	"fmt"
)

func printPyramid(totalLevel int) {
	for i := 1; i <= totalLevel; i++ {
		for k := 1; k <= totalLevel-i; k++ {
			fmt.Printf(" ")
		}
		for j := 1; j <= 2*i-1; j++ {
			fmt.Printf("*")
		}
		fmt.Println()
	}
}

func main() {
	var n int
	fmt.Println("请输入打印金字塔的层数")
	fmt.Scanln(&n)
	printPyramid(n)
}

2、编写一个函数,从终端输入一个整数(1一9)打印出对应的乘法表

package main

import "fmt"

func printMulti(num int) {
	for i := 1; i <= num; i++ {
		for j := 1; j <= i; j++ {
			fmt.Printf("%v * %v = %v \t", j, i, i*j)
		}
		fmt.Println()
	}
}

func main() {
	var num int
	fmt.Println("请输入九九乘法表对应的数")
	fmt.Scanln(&num)
	printMulti(num)
}

二十八、字符串函数

1、常用的系统函数

        1)统计字符串的长度,按字节len(str)【注:golang的编码统一为utf-8(ascii的字符(字母和数字)占一个字节,汉字占用3个字节】

        2)字符串遍历,同时处理有中文的问题 r := [ ]rune(str)

        3)字符串转整数 n, err := strconv.Atoi("12")

n, err := strconv.Atoi("hello")
if err != nil {
    fmt.Println("转换错误", err)
}else {
    fmt.Println("转换的结果是", n)
}

        4)整数转字符串   str = strconv.Itoa(12345)

        5)字符串 转 [ ]byte: var bytes = [ ]byte("hello go")

        6)[ ]byte 转 字符串:str = string([ ]byte{97, 98, 99})

        7)10进制转 2,8,16进制: str = strconv.FormatInt(123, 2),返回对应的字符串

        8)查找子串是否在指定的字符串中:strings.Contains("seafood", "foo")【意为seafood中是否有foo】

        9)统计一个字符串有几个指定的子串:strings.Count("ceheese","e")

        10)不区分大小写的字符串比较(==是区分字母大小写的): fmt.Println(strings.EqualFold("abc", "Abc"))

        11)返回子串在字符串第一次出现的index值,如果没有返回-1:strings.Index("NLT_abc", "abc")

二十九、时间和日期函数

三十、内置函数

1、len:用来求长度 比如string、array、slice、map、channel

2、new:用来分配内存,主要用来分配值类型,比如int、float32、struct...返回的是指针

3、make:用来分配内存,主要用来分配引用类型,比如chan、map、slice

三十一、Go错误处理机制

1、Go语言追求简洁优雅,所以,Go语言不支持传统的try....catch...finally这种处理

2、Go中引入的处理方式为:defer,panic,recover

3、这几个异常的使用场景可以这么简单描述:Go中可以抛出一个panic的异常,然后在defer中通过recover捕获这个异常,然后正常处理

//使用defer + recover 来捕获和处理异常
defer func() {
    err := recover() //recover()内置函数,可以捕获到异常
    if err != nil {  //    说明捕获到错误
        fmt.Println("err=", err)
    }
}()

自定义错误

Go程序中,也支持自定义错误,使用errors.New 和 panic内置函数
导入 “errors”包

//函数去读取以配置文件init.conf的信息
//如果文件名传入不正确,就返回一个自定义错误
func readConf(name string) (err error) {
    if name == "config.ini" {
        //读取
        return nil
    } else {
        //返回一个自定义错误
        return errors.New("读取文件错误..")
    }
}

三十二、数组

package main

import "fmt"

func printArray(arr [5]int) {
	for i, v := range arr {
		fmt.Println(i, v)
	}
	arr[0] = 100
}

func main() {
	var arr1 [5]int
	arr2 := [3]int{1, 3, 5}
	arr3 := [...]int{2, 4, 6, 8, 10}
	var grid [4][5]int
	fmt.Println(arr1, arr2, arr3)
	fmt.Println("-----------------------")
	fmt.Println(grid)
	fmt.Println("-----------------------")
	printArray(arr1)
	fmt.Println("-----------------------")
	printArray(arr3)
	fmt.Println("-----------------------")
	fmt.Println(arr1, arr3)
}

提问: 为什么不能使用printArray(arr2)?

因为   arr2 := [3]int{1, 3, 5} 长度被设置为3

而 func printArray(arr [5]int)  长度被设置为5 故会报错

注意:

        1、[10]int和[20]int是不同类型

        2、调用func f(arr [10] int) 会 拷贝 数组

        3、在go语言中一般不直接使用数组

三十三、切片(Slice)

概念

1、切片是半开半闭区间 例如 arr[2:6] 意为  6>x>=2

 2、slice可以向后扩展,不可以向前扩展

 所以s1的值为[2 3 4 5], s2的值为[5 6]

 3、s[i]不可以超越len(s),向后扩展不可以超越底层数组cap(s)

操作

len是切片中实际的元素个数

cap是切片起始元素到底层数组最后一个元素

1、append

 

 

 注意:

        1】添加元素时如果超越cap,系统会重新分配更大的底层数组

        2】由于值传递的关系,必须接收append的返回值

        3】s= append(s,val)

2、make

s2 := make([]int, 16)
s3 := make([]int, 10, 32)

s2 :创建一个 len长度为16的数组

s3:创建一个 len长度为10 cap长度为32的数组

3、copy

copy(s2, s1)

讲s1的内容复制到s2的头部

 4、Deleting elements from slice

删除中间部分

	s2 = append(s2[:3], s2[4:]...)
	printSlice(s2)

此代码的目的是将数组中的8删除

注意: 在使用append时 (s2[:3], s2[4:]...)中的  ...   不能缺少

删除头

	front := s2[0]
	s2 = s2[1:]

	fmt.Println(front)

删除尾

	tail := s2[len(s2)-1]
	s2 = s2[:len(s2)-1]
	fmt.Println(tail)

三十四、Map

概念

mao[key] value 也可进行复合 map[key1]map[key2]value

package main

import "fmt"

func main() {
	m := map[string]string{
		"name":    "ccmouse",
		"course":  "golang",
		"site":    "imooc",
		"quality": "notbad",
	}

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

	var m3 map[string]int // m3 == nil

	fmt.Println(m, m2, m3)
}

虽然m2和m3最终的输出结果都是map[],但是两个的返回值各有不同

m2 == empty                        m3 == nil

操作

package main

import "fmt"

func main() {
	m := map[string]string{
		"name":    "ccmouse",
		"course":  "golang",
		"site":    "imooc",
		"quality": "notbad",
	}

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

	var m3 map[string]int // m3 == nil

	fmt.Println(m, m2, m3)
	fmt.Println("-------------------------------")
	fmt.Println("Traversing map")
	for k, v := range m {
		fmt.Println(k, v)
	}
	fmt.Println("-------------------------------")
	fmt.Println("Getting values")
	courseName, ok := m["course"]
	fmt.Println(courseName, ok)
	if causeName, ok := m["cause"]; ok {
		fmt.Println(causeName)
	} else {
		fmt.Println("key does not exist")
	}
	fmt.Println("-------------------------------")
	fmt.Println("Deleting values")
	name, ok := m["name"]
	fmt.Println(name, ok)

	delete(m, "name")
	name, ok = m["name"]
	fmt.Println(name, ok)
}

1、创建:make(map[string]int)

2、获取元素:m[key]

3、key不存在时,获得Value类型的初始值

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

5、用delete删除一个key

遍历

1、使用range遍历key,或者遍历key,value对

2、不保证遍历顺序,如需顺序,需手动对key排序

3、使用len获得元素个数

map的key

1、map使用哈希表,必须可以比较相等

2、出了slice,map,function的内建类型都可以作为key

3、Struct类型不包含上述字段,也可作为key



--------------------------以下是面向对象---------------------------------



三十五、结构体

  • go 语言仅支持封装,不支持继承和多态
  • go语言没有class,只有struct

1、结构的定义  

type treeNode struct {
    left, right *treeNode
    value int
}

2、结构的创建

不论地址还是结构本身,一律使用 .(点)来访问成员

root := TreeNode{Value: 3}
root.Left = &TreeNode{}
root.Right = &TreeNode{nil, nil, 5}
root.Right.Left = new(TreeNode)

Go语言没有构造函数可以使用工厂函数

func createTreeNode(value int) *TreeNode {
    return &TreeNode{Value: value}
}

root.Left.Right = createTreeNode(2)
  • 使用自定义工厂函数
  • 注意返回局部变量的地址

3、为结构定义方法

func (node TreeNode) print () {
    fmt.Print(node.Value)
}
  • 显示定义和命名方法接收者

前括号内的内容为接收者

使用指针作为方法接收者

func (node *TreeNode) setValue(value int){
    node.Value = value
}
  • 只有使用指针才可以改变结构内容
  • nil指针也可以调用方法
  • 2
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值