目录
概述
Go语言是云计算时代的C语言。专门针对多处理器系统应用程序的编程进行了优化,会用Go编译的程序可以媲美C或C++的速度,而且更加安全,支持并行进程。Go不仅提供了高性能的语言,同时也让开发更快速。
Go语言优势
- 可直接编译成机器码,不依赖其他库,glibc的版本有一定要求,部署就是扔一个文件上去就完成了。
- 静态类型语言,但是有动态语言的感觉,静态类型的语言就是可以在编译的时候检查出来隐藏的大多数问题,动态语言的感觉就是有很多的包可以使用,写起来的效率很高。
- 语言层面支持并发,这个就是Go最大的特色,天生的支持并发。Go就是基因里面支持的并发,可以充分的利用多核,很容易的使用并发。
- 内置runtime,支持垃圾回收,这属于动态语言的特性之一吧,虽然目前来说GC(内存垃圾回收机制)不算完美,但是足以应付我们所能遇到的大多数情况,特别是Go1.1之后的GC。
- 简单易学,Go语言的作者都有C的基因,那么Go自然而然就有了C的基因,那么Go关键字是25个,但是表达能力很强大,几乎支持大多数你在其他语言见过的特性:继承、重载、对象等。
- 丰富的标准库,Go目前已经内置了大量的库,特别是网络库非常强大。
- 内置强大的工具,Go语言里面内置了很多工具链,最好的应该是gofmt工具,自动化格式化代码,能够让团队review变得如此的简单,代码格式一模一样,想不一样都很困难。
- 跨平台编译,如果你写的Go代码不包含cgo,那么就可以做到window系统编译linux的应用,如何做到的呢?Go引用了plan9的代码,这就是不依赖系统的信息。
- 内嵌C支持,Go里面也可以直接包含C代码,利用现有的丰富的C库,让开发更快速。
Go适合用来做什么?
- 服务器编程,例如处理日志、数据打包、虚拟机处理、文件系统等。
- 分布式系统,数据库代理器等。
- 网络编程,目前应用最广,包括Web应用、API应用、下载应用。
- 内存数据库,如google开发的groupcache、couchbase的部分组件。
- 云平台,目前国外很多云平台采用Go开发,CloudFoundy的部分组件、前VMare的技术总监自己出来高德apcera云平台。
环境搭建
安装和配置
配置Go语言开发运行环境(环境变量是自动配置的)。参考教程
Hello.go
// 1)go语言以包作为管理单位。
// 2)每个文件必须先声明包。
// 3)程序必须有一个main包。
package main
import "fmt"
// 入口函数 main(),左括号必须与函数名同行
func main() {
//打印。Println() 会自动换行。
//调用函数,大部分都需要导入包。
// go语言语句结尾没有分号。
fmt.Printf("hello world\n")
}
命令行运行程序
# 格式化go文件
go fmt 01_Hello.go
# 编译,成功后会生成exe可执行文件
go build 01_Hello.go
# 运行
.\01_Hello.exe
# 编译运行,但不生成exe可执行文件
go run 01_Hello.go
第01天(基本类型、流程控制)
1.1 变量和常量
1.1.1 声明变量:
// 变量名是a,类型是int(可以省略),初始化值是10(默认值是0)
var a int = 10
var b,c int
// 直接声明变量值,并根据 变量值 推导出变量的类型
var c1 = 20
fmt.Printf("c1=%d",c1)
d := 30
fmt.Printf("d type is %T",d)
// 多重复值
e,f,g := 10,20,30
// 交换两个变量的值
i,j := 10,20
i,j = j,i
fmt.Printf("i=%d,j=%d\n",i,j)
- fmt.Println() ,输出打印行。fmt.Println("a=",a,"b=",b,"c=",c):即 逗号 作为连接符。
- fmt.Printf(),格式化输出。fmt.Printf("a=%d,b=%d,c=%d \n",a,b,c)。
- := ,定义变量并初始化,据初始化值,自动推导变量类型。
- %T,格式化输出函数中,表示输出变量的类型。
1.1.2声明常量:
const a int = 10
const b = 3.14
1.1.3 iota枚举
func main() {
const {
a=iota // 0
b=iota // 1
c=iota // 2
}
fmt.Printf("a=%d,b=%d,c=%d\n",a,b,c)
const {
i = iota // 0
j1,j2,j3 = iota,iota,iota // 1
k = iota // 2
}
}
- iota常量自动生成器,从0开始,自动累加1。用于给常量赋值。
- 当重新使用 const 关键字声明常量时,iota重置为0。
- 在同一个const关键字中,一行iota,只会累加1。
1.2 基本数据类型
类型 | 名称 | 长度 | 零值/初始值 | 说明 |
---|---|---|---|---|
bool | 布尔类型 | 1 | false | true或false |
byte | 字节型 | 1 | 0 | uint8别名 |
rune | 字符类型 | 4 | 0 | 专用于鵆unicode编码,等价于uint32 |
int,uint | 整型 | 4或8 | 0 | 32位或64位 |
int8,uint8 | 整型 | 1 | 0 | -128~127,0~255 |
int16,uint16 | 整型 | 2 | 0 | -32768~32767,0~65535 |
int32,uint32 | 整型 | 4 | 0 | -21亿~21亿,0~42亿 |
int64,uint64 | 整型 | 8 | 0 | |
float32 | 浮点数 | 4 | 0.0 | 小数位精确到7位 |
float64 | 浮点数 | 8 | 0.0 | 小数位精确到15位 |
complex64 | 复数类型 | 8 | ||
complex128 | 复数类型 | 16 | ||
uintptr | 整型 | 4或8 | 足以存储指针的uint32或uint64整数 | |
string | 字符串 | "" | utf-8字符串 |
1.3 fmt包的格式化输出输入
输入格式
func main() {
var a int
fmt.Printf("请输入变量a: ")
// fmt.Scanf("%d",&a)
fmt.Scan(&a)
fmt.Println("a=",a)
}
格式 | 含义 |
---|---|
%% | 一个%字面量 |
%b | 一个二进制整数值(基数为2,)或者是一个(高级的)用科学计数法表示的指数为2的浮点数 |
%c | 字符型,可以把数字按照ASCII码转成对应的字符 |
%d | 十进制数 |
%e | 科学计数法,e表示浮点数或者复数值 |
%E | 科学计数法,E表示浮点数或者复数值 |
%f | 标准计数法表示的浮点数或复数值 |
%g,%G | 以%e或者%f表示的浮点数或者复数,任何一个都以最为紧凑的方式输出 |
%o | 八进制数 |
%p | 十六进制数,前缀为0x,字符是小写的a-f |
%q | 转义 |
%s | 字符串 |
%t | 布尔值,true或false |
%T | 值的类型 |
%U | Unicode编码表示的整型码点,默认为4个数字字符 |
%v | 使用默认格式输出的内置或自定义类型的值,或者是使用其类型的String()方式输出的自定义值,如果该方法存在的话。 |
%x,%X | 十六进制证书,分别是小写字母表示、大写字母表示 |
1.4 类型转换和别名
1.4.1 类型转换
func main() {
ch := 'c'
var a = int(ch)
fmt.Println("a=",a)
}
1.4.2 类型别名
type bigint int64 //int64类型改名为bigint
var x bigint = 100
type {
myint int //int改名为myint
mystr string //string改名为mystr
}
1.5 运算符
- & 取地址运算符 , 如:&a ,即是变量a的地址。
- * 取值运算符,如:*a,即指针变量a所指向内存的值。
略。
1.6 流程控制
1.6.1 if语句
func main() {
// if
var a = 3
if a == 3 {
fmt.Println("a==3")
}
// if 中的 变量定义
if b := 3; b == 3 {
fmt.Printf("b==3\n")
}
// if-else
if x := 3; x == 4 {
fmt.Println("a==4")
} else {
fmt.Println("a!=4")
}
// if-elseif-if
if y := 0; y == 3 {
fmt.Println("y==0")
} else if y == 1 {
fmt.Println("y==1")
} else {
fmt.Println("y!=0或1")
}
}
1.6.2 switch
func main() {
var num int
fmt.Printf("请输入num的值:")
fmt.Scan(&num)
switch num {
case 1:
fmt.Println("num=1")
break
case 2:
fmt.Println("num=2")
break
case 3:
fmt.Println("num=3")
break
default:
fmt.Println("num错误")
}
}
1.6.3 for循环
func main() {
sum:=0
for i:=1;i<=100;i++{
sum += i
}
fmt.Println("sum=",sum)
}
1.6.4 Range迭代
func main() {
str := "abc"
//迭代打印,默认返回两个值:元素位置、元素本身
for i,data := range str{
fmt.Printf("str[%d]=%c\n",i,data)
}
// 第2个返回值默认丢弃,只有i接收到元素位置
for i := range str{
fmt.Printf("str[%d]\n",i)
}
}
1.6.5 break和continue的区别
func main() {
i := 0
for{ // 死循环
i++
if i==5{
// 跳过之后的步骤,继续执行循环体
continue
}
if i==10{
// 打破/跳出循环体
break
}
fmt.Println("i=",i)
}
}
第02天(函数、工程管理)
函数组成:
- func:函数关键字,由此声明。
- funcName:函数名称
- 参数列表:可有可无,格式为:变量名 类型,多个参数逗号分隔,不支持默认参数
- 返回类型:若有返回值,必须使用return语句。
2.1 自定义函数
2.1.1 无参/有参 无返回值的函数
package main
import "fmt"
// 无参无返回值
func fun1(){
a:=6
fmt.Println("a=",a)
}
// 有参无返回值
func fun2(a int){
fmt.Println("a=",a)
}
// 不定参数 函数
func fun3(a int,args ... int){
fmt.Printf("a = %d \n",a)
for i,data := range args {
fmt.Printf("args[%d]=%d \n",i,data)
}
}
// 不定参数函数 的 不定参 传递
func fun4(args ... int){
//fun3(args[0],args ...)
// 从第0个元素开始,传递前2个元素
//fun3(args[0],args[:2] ...)
// 从第2个元素开始,传递包括第2个元素以及之后的所有元素
fun3(args[0],args[2:] ...)
}
func main() {
//fun1()
//fun2(2)
//fun3(1,2,3)
fun4(9,8,7,6)
}
2.1.2 有返回值的函数
// 需要 给 返回值 声明一个变量值
func fun1() (result int) {
result = 6
return
}
// 多个返回值
func fun2() (a int,b int,c int){
a,b,c = 1,2,3
return
}
func main() {
a := fun1()
fmt.Println("a=",a)
x,y,_ := fun2()
fmt.Printf("x=%d,y=%d,",x,y)
_,_,z := fun2()
fmt.Println("z=",z)
}
func getMax(a, b int) (max, min int) {
if a >= b {
max = a
min = b
} else {
max = b
min = a
}
return
}
func main() {
max, min := getMax(10, 20)
fmt.Printf("max=%d,min=%d", max, min)
}
2.2 递归函数
func fibonacci(item int) (result int) {
if item == 1 || item == 2 {
result = 1
return
} else if item > 2 {
result = fibonacci(item-1) + fibonacci(item-2)
return
} else {
result = -1
return
}
}
func main() {
item := 20
result := fibonacci(item)
fmt.Printf("斐波那契的第%d个元素是:%d", item, result)
}
2.3 函数类型
// 函数类型,函数也是一种数据类型。
// type 关键字,FuncType 是一个函数类型的自定义名称
type FuncType func(int, int) int
func Add(a,b int) int{
return a+b
}
func Minus(a,b int) int{
return a-b
}
// 回调函数,函数有一个参数是 函数类型,这个函数就是回调函数。声明一个 fTest 的函数类型作为形参
// 实现一个计算器,可以进行四则运算
// 多态
func Calc(a, b int, fTest FuncType) (result int) {
fmt.Println("Calc")
result = fTest(a, b)
return
}
func main() {
a := Calc(1,1,Add)
//a := Calc(1,1,Minus)
fmt.Println("a=",a)
}
2.4 匿名函数与闭包
func main() {
// 定义匿名函数,并调用 打印变量值
func1 := func(a int,str string){
fmt.Println("a=",a)
fmt.Println("str=",str)
}
func1(1,"Jack")
}
func main() {
a := 10
str := "Milk"
func() {
// 闭包 是 以 引用方式 获取外部变量的,当引用值被改变后,闭包外部的值也会被改变
a = 6
str = "go"
fmt.Printf("【内部】a=%d,str=%s\n", a, str)
}() // ()表示 直接调用
fmt.Printf("【外部】a=%d,str=%s\n", a, str)
}
func fun() func() int {
var x int
return func() int {
// 闭包中的变量,不会因为重新调用函数而被重新初始化
x++
return x * x
}
}
func main() {
result := fun()
fmt.Println("result=",result())
fmt.Println("result=",result())
fmt.Println("result=",result())
fmt.Println("result=",result())
}
2.5 延迟调用defer
关键字 defer 用于延迟一个函数或者方法(或者当前所创建的匿名函数)的执行。注意,defer语句只能出现爱你在函数或方法的内部。
defer语句经常被用于处理成对的操作,比如开&关,连接&断开,加锁&释放锁等。通过defer机制,不论函数逻辑多复杂,都能保证在任何执行路径下,资源被释放,释放资源的defer应该直接跟在请求资源的语句后。
func main() {
defer fmt.Println("延迟执行,main函数执行结束后才被执行。")
fmt.Println("直接被执行。")
}
多个defer的执行顺序
先进后出,写在前边的defer,最后被执行。哪怕之前有函数发生了错误,defer修饰的方法也会被执行。
defer和匿名函数结合使用
func main() {
a := 10
b := 20
defer func() {
fmt.Printf("【内部】a=%d, b=%d\n",a,b)
}()
a=111
b=222
fmt.Printf("【外部】a=%d, b=%d\n",a,b)
}
2.6 获取命令行参数
需要以命令行的形式执行,因为要获取 命令行输入的参数。idea打开terminal,执行 go run 01_Hello.go a b c d
func main() {
// 接收用户传递的参数,都是以字符串方式传递的
list := os.Args
for i,data := range list{
fmt.Printf("lislt[%d]=%s\n",i,data)
}
}
2.7 工程管理
2.7.1 工作区
Go代码必须放在工作区中。工作区是一个对应于特定工程的目录,包含三个子目录:src目录、pkg目录和bin目录。
- src目录:存放Go源码文件。
- pkg目录:存放 go install命令构建安装后的代码包(包含Go库源码文件)的“.a”归档文件。
- bin目录:与pkg目录类似。在通过go install命令完成安装后,保存由Go命令源码文件生成的可执行文件。
- 当GoPath中只包含一个工作区的目录路径时,go install命令才会把命令源码安装到当前工作区的bin目录下。若环境变量GoPath中包含多个工作区的目录路径,像这样执行 go install 命令就会失败,必须设置环境变量GoBin。
GoPath设置:为了能构建工程,需要先把所需工程的概目录加入到环境变量GoPath中,否则即使处于同一工作目录,代码之间也无法通过绝对代码包路径完成调用。
实际开发环境中,工作目录往往有多个,这些工作目录的路径都需要添加至GoPath。有多个目录时,需注意分隔符(Windows是分好,Linux是冒号),当有多个GoPath时,默认会将go get的内容放到第一个目录下。
2.7.2 导入包
导包的方式
所有Go语言的程序都会组织成多干组文件,每组文件被称为一个包。包的代码可以作为最小复用单元,被其他项目引用。
//点操作。在调用包中的函数时,可以省略前缀
import {
. "fmt"
}
func main() {
Println("Hello go")
}
// 别名操作,调用的包可以自定义别名
import {
io "fmt"
}
func main() {
io.Println("Hello go")
}
// 忽略导入包。有时用户可能需要导入一个包,但是不需要引用这个包的标识符。此时可以使用空白标识符_来重命名这个导入包。
// _操作其实是引入该包,但不直接使用包里的函数,而是调用了该包里面的init函数,init函数下面就介绍
import {
_ "fmt"
}
func main() {
}
包(类似于Java的类)中的 init函数和main函数
在main入口文件中,导入其它包时,会先执行该包的init函数,其它函数只有调用时才会执行。
第03天(复合类型)
类型 | 名称 | 长度 | 默认值 | 说明 |
---|---|---|---|---|
pointer | 指针 | nil | ||
array | 数组 | 0 | ||
slice | 切片 | nil | 引用类型 | |
map | 字典 | nil | 引用类型 | |
struct | 结构体 |
3.1 指针
- 默认值nil,没有NULL常量;
- 操作符“&”取变量地址,“*”通过指针访问目标对象;
- 不支持指针运算,不支持“->”运算符,直接用“.”访问目标成员。
// 值传递
func swap1(a, b int) {
a, b = b, a
fmt.Printf("【swap】a=%d,b=%d\n", a, b)
}
// 指针地址传递,交换值
func swap2(a, b *int) {
*a, *b = *b, *a
fmt.Printf("【swap】a=%d,b=%d\n", *a, *b)
}
func main() {
a, b := 10, 20
swap2(&a, &b)
fmt.Printf("【main】a=%d,b=%d\n", a, b)
}
3.2 数组
func main() {
// 创建随机数,设置种子。只需设置一次。以当前系统时间作为种子参数
rand.Seed(time.Now().UnixNano())
var a [10]int
for i:=0; i<10;i++ {
//随机数很大
//a[i] = rand.Int()
//限定随机数的范围
a[i] = rand.Intn(10)
}
fmt.Println("a=",a)
// 二维数组
var b [3][4]int
for i := 0; i < 3; i++ {
for j := 0; j < 4; j++ {
b[i][j] = i + j
}
}
fmt.Println("b=",b)
c := [2][3] int {
{1,2,3},
{2,3,4},
}
fmt.Println("c=",c)
}
// 冒泡排序,值传递
func bubble(a [5]int) {
fmt.Println("【排序前】", a)
for i := 0; i < len(a); i++ {
for j := i + 1; j < len(a); j++ {
if a[i] > a[j] {
a[i], a[j] = a[j], a[i]
}
}
}
fmt.Println("【排序后】", a)
}
// 冒泡排序,指针引用传递
func pointer(a *[5]int) {
for i := 0; i < len(a); i++ {
for j := i + 1; j < len(a); j++ {
if a[i] > a[j] {
a[i], a[j] = a[j], a[i]
}
}
}
}
func main() {
a := [5]int{5, 3, 8, 6, 9}
//bubble(a)
fmt.Println("【排序前】", a)
pointer(&a)
fmt.Println("【排序后】", a)
}
3.3 slice切片
切片:通过内部指针和相关属性引用数组片段,已实现数组变长方案。弥补数组的定长。
- 切片是数组的一个引用,因此切片是引用类型,在进行传递时,遵守引用传递的机制。
- 切片的使用和数组类似,遍历切片、访问切片的元素和求切片长度len(slice)都一样。
- 切片的长度是可以变化的,因此切片是一个可以动态变化数组。
// 切片的基础,main函数
func mainBackup() {
// 切片的创建过程中,[]是空的,表示长度不固定。
a := []int{1, 2, 3, 4, 5}
// 切片截取时,格式为[low:high:max],下标从low开始,截取到下标high(不包括),容量cap=max-low。
// low、high的下标值必须 在 切片a 元素下标的范围内,并且 max >= high-low && max < len(a)
// 下例中,cap容量: 5-1=4。
s := a[1:4:5]
fmt.Println("s=", s)
fmt.Printf("len(s)=%d, cap(s)=%d\n", len(s), cap(s))
y := []int{}
// 切片中 添加元素
y = append(s, 11)
fmt.Println(y)
}
// 创建切片
func main() {
//创建切片 1
a := []int{1,2,3,4,5}
fmt.Println(a)
// 创建切片 2
b := make([]int,5,10)
fmt.Printf("len(b)=%d, cap(b)=%d \n",len(b),cap(b))
fmt.Println(b)
}
切片截取的常用方法:
操作 | 含义 |
---|---|
s[n] | 切片s中索引位置为n的元素 |
s[:] | 切片s的索引位置0 到 len(s)-1 处 截取的切片 |
s[low:] | 切片s的索引位置 low 到 len(s)-1 处 截取的切片 |
s[:high] | 切片s的索引位置 0 到 high 处 截取的切片 |
s[low:high] | 切片s的索引位置 low 到 high 处 截取的切片 |
s[low:high:max] | 切片s的索引位置 low 到 high 处 截取的切片,长度len=high-low,容量cap=max-low |
len(s) | 切片s的长度,总是<=cap(s) |
cap(s) | 切片s的容量,总是>=len(s) |
3.3.1 append函数
向切片的末尾添加元素。
特点:扩容时,若超过底层数组的容量长度,通常以2倍的容量扩容,并复制原来的数组到新的底层数组中。
func main() {
a := []int{1, 2, 3}
a = append(a, 5)
a = append(a, 5)
a = append(a, 5)
fmt.Printf("len=%d, cap=%d\n", len(a), cap(a))
fmt.Println("[a]=", a)
}
3.3.2 copy函数
copy可以在两个slice间复制数据,复制长度以len小的为准,两个slice可指向同一底层数组。
func main() {
data := [...]int{0,1,2,3,4,5,6,7,8,9}
a := data[8:]
b := data[:5]
//将a切片的值,拷贝到b切片中,并覆盖掉b原先的值。
copy(b,a)
fmt.Println(b)
}
3.3.3 切片的值传递
//初始化切片
func InitData(s []int) {
rand.Seed(time.Now().UnixNano())
for i := 0; i < len(s); i++ {
s[i] = rand.Intn(100)
}
}
func BubbleSort(s []int) {
n := len(s)
for i := 0; i < n-1; i++ {
for j := 0; j < n-i-1; j++ {
if s[j] > s[j+1] {
s[j], s[j+1] = s[j+1], s[j]
}
}
}
}
func main() {
n := 10
s := make([]int, n)
InitData(s)
fmt.Println("排序前:", s)
BubbleSort(s)
fmt.Println("排序后:", s)
}
3.4 map
3.4.1 map的创建与初始化
func main() {
var m map[int]string
fmt.Printf("len=%d\t", len(m))
fmt.Println("m=", m)
m2 := make(map[int]string)
for i := 0; i < 10; i++ {
m2[i] = "Jack" + strconv.Itoa(i)
}
fmt.Println("m2=", m2)
m3 := make(map[string]string, 2)
m3["a"] = "Java"
m3["b"] = "Go"
m3["c"] = "C"
fmt.Println("len=", len(m3), "\t m3=", m3)
m4 := map[int]string{1: "python", 2: "C++", 3: "JavaScript"}
fmt.Println("m4=", m4)
}
3.4.2 map遍历和删除元素
// map作为函数参数传递
func operator(m map[int]string){
// map 删除key-value
delete(m,3)
fmt.Println("m=",m)
}
func main() {
m := make(map[int]string)
for i := 0; i < 10; i++ {
m[i] = "Jack" + strconv.Itoa(i)
}
//遍历,无序的
for key,value := range m{
fmt.Printf("%d—>%s \n",key,value)
}
operator(m)
}
3.5 结构体
3.5.1 结构体的创建
type Student struct {
id int
name string
sex byte
age int
addr string
}
func main() {
var stu = Student{1, "Jack", 'm', 18, "北京"}
fmt.Println("stu=", stu)
var stu2 = Student{name: "Michael", addr: "天津"}
fmt.Println("stu2=", stu2)
stu3 := &Student{age: 22, id: 101, name: "Mike"}
fmt.Println("stu3=", *stu3)
stu3.id = 102
fmt.Println("stu3.id=", stu3.id)
}
结构体的继承
type Person struct {
name string
sex byte
age int
}
type Student struct {
id int
Person
addr string
}
func main() {
//var stu = Student{128,Person{"Jack",'w',22},"河南"}
var stu = Student{Person: Person{name: "Michael"}, addr: "天津"}
fmt.Println("stu=", stu)
}
3.5.2 结构体的值传递
type Student struct {
id int
name string
sex byte
age int
addr string
}
func test1(stu Student){
stu.id=102
fmt.Println("【test1】stu=", stu)
}
func test2(stu *Student){
stu.id=102
fmt.Println("【test2】stu=", stu)
}
func main() {
var stu = Student{name: "Michael", addr: "天津"}
//test1(stu)
test2(&stu)
fmt.Println("【main】stu=", stu)
}
3.5.3 可见性规则
可见性:类似于Java中的public、private、protected的概念。如果想使用其他包的函数、结构体类型、结构体成员、函数名、类型名、结构体成员变量名,那么其首字母必须大写,才能可见。若是小写,只能在同一个包中使用。
第04天(面向对象编程)
4.1 匿名组合
略。
4.2 结构体的方法
type Person struct {
name string
sex byte
age int
}
// 方法:结构体的 打印函数
func (tmp Person) PrintInfo() {
fmt.Println("tmp=", tmp)
}
// 结构体的 初始化赋值 函数
func (p *Person) SetInfo(n string, s byte, a int) {
p.name = n
p.sex = s
p.age = a
}
func main() {
var p Person
(&p).SetInfo("Mike", 'm', 28)
p.PrintInfo()
}
type Person struct {
name string
sex byte
age int
}
// 接收者 为 普通变量, 值传递
func (p Person) SetInfoValue(n string, s byte, a int) {
p.name = n
p.sex = s
p.age = a
}
// 接收者为 指针变量 , 引用传递
func (p *Person) setInfoPointer(n string, s byte, a int) {
p.name = n
p.sex = s
p.age = a
}
func main() {
p := Person{"Jack",'m',12}
fmt.Println("&p=",&p) // 打印地址
p.SetInfoValue("Mike",'w',18)
fmt.Println("p=",p)
p.setInfoPointer("Rose",'w',27)
fmt.Println("p=",p)
(&p).setInfoPointer("Rose",'w',28)
fmt.Println("p=",p)
}
4.3 接口
go中,接口命名习惯以er结尾。
空接口:不包含任何方法的接口。所有类型都可以认为是实现了空接口。可以存储任意类型的数值。
func main() {
// 创建一个空接口
i := make([]interface{}, 3)
i[0] = 1
i[1] = "hello"
i[2] = Student{25, "Jack"}
fmt.Println(i)
}
4.3.1 创建
// 定义接口类型,并声明接口方法
type Humaner interface {
sayHi()
}
type Student struct {
id int
name string
}
// 实现 接口方法
func (tmp *Student) sayHi() {
fmt.Printf("Student[%s,%d] sayhi\n", tmp.name, tmp.id)
}
type Teacher struct {
addr string
group string
}
func (tmp *Teacher) sayHi() {
fmt.Printf("Teacher[%s,%s] sayhi\n", tmp.addr, tmp.group)
}
func main() {
var i Humaner
s := &Student{13, "Jack"}
i = s
i.sayHi()
t := &Teacher{"bj", "go"}
i = t
i.sayHi()
}
4.3.2 接口继承
// 定义接口类型,并声明接口方法
type Humaner interface {
sayHi()
}
// 继承 Humaner接口
type Personer interface {
Humaner
sing(lrc string)
}
type Student struct {
id int
name string
}
// 实现 接口方法
func (tmp *Student) sayHi() {
fmt.Printf("Student[%s,%d] sayhi\n", tmp.name, tmp.id)
}
func (tmp *Student) sing(lrc string) {
fmt.Printf("Student在唱:%s\n", lrc)
}
func main() {
// 接口继承 案例
var i Personer
s := &Student{13, "Jack"}
i = s
i.sayHi()
i.sing("好久不见")
//接口转换
var hunamer Humaner
var personer Personer
personer = &Student{22,"Rose"}
hunamer = personer
hunamer.sayHi()
}
4.3.3 通过if / switch实现类型断言
type Student struct {
id int
name string
}
func main() {
// 创建一个空接口
i := make([]interface{}, 3)
i[0] = 1
i[1] = "hello"
i[2] = Student{25, "Jack"}
// if 断言类型
for index,data := range i{
if value,ok := data.(int);ok==true{
fmt.Printf("i[%d] 类型为int,内容为%d\n",index,value)
}else if value,ok := data.(string);ok==true{
fmt.Printf("i[%d] 类型为string,内容为%s\n",index,value)
}else if value,ok := data.(Student);ok==true{
fmt.Printf("i[%d] 类型为Student,内容为 name=%s,id=%d\n",index,value.name,value.id)
}
}
// switch 断言类型
for index,data := range i{
switch value := data.(type) {
case int:
fmt.Printf("i[%d] 类型为int,内容为%d\n",index,value)
case string:
fmt.Printf("i[%d] 类型为string,内容为%s\n",index,value)
case Student:
fmt.Printf("i[%d] 类型为Student,内容为 name=%s,id=%d\n",index,value.name,value.id)
}
}
}
第05天(异常、文本文件处理)
5.1 error接口、panic、recover
在Go语言中,程序的错误由内建的error接口支持,errors标准包提供了最基本的错误处理方法,用户还可自定义错误处理。而程序的异常处理通常由panic和recover实现触发和终止。
5.1.1 错误处理:error
// Error接口
func createErr() {
err1 := fmt.Errorf("%s", "this is normal error")
fmt.Println("err1=", err1)
err2 := errors.New("this is normal err2")
fmt.Println("err2=", err2)
}
func MyDiv(a, b int) (result int, err error) {
if b == 0 {
err = errors.New("分母不能为0")
return
}
result = a / b
return
}
func main() {
createErr()
result, err := MyDiv(1, 1)
if err != nil {
fmt.Println("err=", err)
} else {
fmt.Println("result=", result)
}
}
5.1.2 异常处理:panic
当遇到不可恢复的错误状态,比如空指针引用、下标越界、向空map添加键值等,应该调用panic。当panic异常发生时,程序会中断运行,随后程序崩溃并输出日志信息,日志信息把包括panic value和函数调用的堆栈跟踪信息。
func test1() {
fmt.Println("aaaaa")
}
func test2() {
//主动调用 panic
panic("this is a panic test")
}
func test3() {
fmt.Println("ccccc")
}
// 数组越界
func test4(x int) {
var a [10]int
a[x] = 111
}
func main() {
//test1()
//test2()
//test3()
test4(20)
}
5.1.3 异常处理:recover
panic会导致程序崩溃(crash/终止运行)。
func test1() {
fmt.Println("aaaaa")
}
func test2(x int) {
defer func() {
if err := recover(); err != nil {
fmt.Println(err)
}
}()
var a [10]int
a[x] = 111
}
func test3() {
fmt.Println("ccccc")
}
func main() {
test1()
test2(11)
test3()
}
5.2 字符串处理
5.2.1 字符串常用操作
函数名 | 返回值 | 使用例子 | 说明 |
---|---|---|---|
contains | bool(true/false) | strings.Contains("seafood","food") | 字符串是否包含其他字符串 |
join | string | s:=[]string{"good","morning","Jack"} result:=strings.Join(s," ") fmt.Println(result) | 将数组中的字符串进行拼接 |
index | int | strings.Index("Morning", "ing") | 查找执行字符串所在的位置,不存在返回-1 |
repeat | string | "ba"+strings.Repeat("na",2) | 重复拼接字符串多次。 |
replace | string | strings.Replace("banana","na","bala",-1) | 字符串替换,n表示替换次数,小于0表示全部替换 |
split | slice切片 | strings.Split("Let us go to school"," ") | 字符串按照 某字符 进行分割,返回slice切片 |
trim | string | strings.Trim("oppo","o") | 头尾去除指定字符串 |
fields | slice切片 | strings.Fields(" it's to far from here ") | 去除字符串的空格符,并按照空格分割,返回slice切片类型 |
5.2.2 字符串转换
函数名 | 使用例子 | 说明 |
---|---|---|
Append系列函数 | func main() { str := make([]byte,0,100) // 初始化一个空数组 str = strconv.AppendInt(str,1234,10) // 以10进制方式追加 str = strconv.AppendQuote(str,"abcdefg") str = strconv.AppendQuoteRune(str,'崔') fmt.Println(string(str)) } | 将整数等转换为字符串后,添加到现有的字节数组中。 |
func main() {
var str string
str = strconv.FormatBool(false)
//'f'—— 打印格式:小数方式,-1——小数点位数(紧缩模式),64以float64处理
str = strconv.FormatFloat(3.14, 'f', -1, 64)
//整型转字符串,常用
str = strconv.Itoa(666)
fmt.Println("str=", str)
// 字符串转整型,常用
a,_ := strconv.Atoi("456")
fmt.Println("a=",a)
// 字符串转其他类型
var flag bool
var err error
flag, err = strconv.ParseBool("true")
if err == nil {
fmt.Println("flag=", flag)
} else {
fmt.Println("err=", err)
}
}
5.3 正则表达式
// 字符串匹配
func test1() {
buf := "abc azc a7c aac 888 a9c tac"
// 1. 解释规则,它会解析正则表达式,如果成功返回解释器
reg1 := regexp.MustCompile(`a.c`)
if reg1 == nil { // 解释失败,返回nil
fmt.Println("err=")
return
}
//2. 根据规则提取关键信息,获取符合 a*c 格式 的字符串。-1获取所有匹配的。
result1 := reg1.FindAllStringSubmatch(buf, 3)
fmt.Println("result1=", result1)
}
// 字符串中的 小数 匹配
func test2() {
buf := "43.15 568 asdf 1.25 3.14 7. 8.9 asqe 6.66 .5 8.9"
reg := regexp.MustCompile(`\d+\.\d+`)
if reg == nil {
fmt.Println("MustCompile err")
return
}
//result := reg.FindAllString(buf, -1)
// 提取后 分组
result := reg.FindAllStringSubmatch(buf,-1)
fmt.Println("result=", result)
}
func main() {
//test1()
test2()
}
5.4 JSON处理
5.4.1 结构体和JSON的相互转换
type IT struct {
Company string `json:"公司"` // json的二次编码
Subjects []string
IsOK bool `json:"-"` // - 表示 转换成json时 忽略该字段
Price float64 `json:"价格"`
Age int `json:",string"` // 定义 JSON 输出的格式
}
// 结构体 转 JSON
func struct2json() {
//定义一个结构体变量
s := IT{"cuiCompany", []string{"java", "c", "c++"}, true, 100.35, 28}
//buf, err := json.Marshal(s)
// JSON格式化
buf, err := json.MarshalIndent(s, "", "")
if err != nil {
fmt.Println("err=", err)
return
}
fmt.Println("byf=", string(buf))
}
// JSON 转 结构体
func json2struct() {
jsonBuf := `{
"公司": "cuiCompany",
"Subjects": [
"java",
"c",
"c++"
],
"价格": 100.35,
"Age": "28"
}`
var tmp IT
err := json.Unmarshal([]byte(jsonBuf), &tmp)
if err != nil {
fmt.Println("err=", err)
return
}
fmt.Println("tmp=", tmp)
fmt.Printf("tmp=%+v\n", tmp)
}
func main() {
//struct2json()
json2struct()
}
5.4.2 Map和JSON的相互转换
// map 转 JSON
func map2json() {
m := make(map[string]interface{}, 4)
m["company"] = "cuiCompany"
m["isok"] = true
m["subject"] = []string{"go", "c", "java", "c++"}
m["price"] = 3.1415
//result, err := json.Marshal(m)
// json格式化
result, err := json.MarshalIndent(m, "", " ")
if err != nil {
fmt.Println("err=", err)
return
}
fmt.Println("result=", string(result))
}
// JSON 转 map
func json2map() {
jsonBuf := `{
"company": "cuiCompany",
"isok": true,
"price": 3.1415,
"subject": [
"go",
"c",
"java",
"c++"
]
}`
tmp := make(map[string]interface{}, 4)
err := json.Unmarshal([]byte(jsonBuf), &tmp)
if err != nil {
fmt.Println("err=", err)
return
}
fmt.Println("tmp=", tmp)
fmt.Printf("tmp=%+v\n", tmp)
// map类型断言
var str string
for key, value := range tmp {
switch data := value.(type) {
case string:
str = data
fmt.Printf("map[%s]类型是string,value=%s\n", key, str)
case bool:
fmt.Printf("map[%s]bool,value=%v\n", key, data)
case float64:
fmt.Printf("map[%s]float64,value=%v\n", key, data)
case []string:
fmt.Printf("map[%s][]string,value=%v\n", key, data)
case []interface{}:
fmt.Printf("map[%s][]interface,value=%v\n", key, data)
}
}
}
func main() {
//map2json()
json2map()
}
5.5 文件操作
// 写入文件
func WriteFile(path string) {
//打开/新建文件
f, err := os.Create(path)
if err != nil {
fmt.Println("err=", err)
return
}
//关闭文件
defer f.Close()
var buf string
for i := 0; i < 10; i++ {
// "i=1" 字符串存储在buf中
buf = fmt.Sprintf("i = %d\n", i)
// 开始写入文件
n, err := f.WriteString(buf)
if err != nil {
fmt.Println("err=", err)
return
}
fmt.Println("n=", n)
}
}
// 读取文件
func ReadFile(path string) {
//打开文件
f, err := os.Open(path)
if err != nil {
fmt.Println("err=", err)
return
}
//关闭文件
defer f.Close()
buf := make([]byte, 1024*2) //2K大小
n, err1 := f.Read(buf)
if err1 != nil && err1 != io.EOF { //文件出错,并还没到文件结尾
fmt.Println("err1=", err1)
return
}
fmt.Println("buf=\n", string(buf[:n]))
}
// 按行读取文件
func ReadLine(path string) {
//打开文件
f, err := os.Open(path)
if err != nil {
fmt.Println("err=", err)
return
}
//关闭文件
defer f.Close()
// 新建缓冲区,暂时存放内容
r := bufio.NewReader(f)
for {
buf, err := r.ReadBytes('\n')
if err != nil {
if err == io.EOF { //文件结束
break
}
fmt.Println("err=", err)
}
fmt.Printf("buf=#%s#\n", string(buf))
}
}
func main() {
path := "./demo.txt"
//WriteFile(path)
//ReadFile(path)
ReadLine(path)
}
文件拷贝
从命令行输入 源文件、目的文件 参数,进行拷贝
PS E:\ideaWorkspace\HelloGo> go build .\01_Hello.go
PS E:\ideaWorkspace\HelloGo> .\01_Hello.exe .\demo.txt abc.txt
func main() {
list := os.Args //获取命令行参数
if len(list) != 3 {
fmt.Println("usage: xxx srcFile dstFile")
return
}
srcFile := list[1]
dstFile := list[2]
if srcFile == dstFile {
fmt.Printf("源文件和目的文件不能同名")
return
}
// 只读方式打开源文件
sF, err1 := os.Open(srcFile)
if err1 != nil {
fmt.Println("err1=", err1)
return
}
//新建目的文件
dF, err2 := os.Create(dstFile)
if err2 != nil {
fmt.Println("err2=", err2)
return
}
// 关闭文件
defer sF.Close()
defer dF.Close()
// 读取源文件内容,写入目的文件内容
buf := make([]byte, 4*1024) //4K的 临时缓冲区
for {
n, err := sF.Read(buf)
if err != nil {
if err == io.EOF { //读取完毕
break
}
fmt.Println("err=", err)
}
// 写
dF.Write(buf[:n])
}
}
第06天(并发编程)
6.1 概述
6.2 goroutine
6.3 channel
6.4 select
第07天(网络概述、socket编程)
7.1 网络概述
7.2 Socket编程
7.3 案例:并发的聊天室服务器
第08天(HTTP编程)
8.1 Web工作方式
8.2 Http报文格式
8.3 Http编程
8.4 案例:网络爬虫