Go --- 基础使用
Posted whkzm
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Go --- 基础使用相关的知识,希望对你有一定的参考价值。
Go语言基础
在写Go代码之前,先建立Go的工作区,可以通过 go env 命令来查看Go当前的工作区,默认的工作区为 GOPATH=C:Userusernamego ,Go的工作区目录结构如下
-C:
-User:
-username:
-go:
-src:写的所有的go代码全放在这个文件夹下
-bin:编译后的可执行文件位置
-pkg:编译时生成的对象文件
1.hello word
package main # 每一个go文件都应该在开头尽心该声明,
import "fmt"
func main() {
fmt.Println("Hello World")
}
// 1.写go代码,必须把代码放在main包下的main函数中(go程序的入口,是main包下的main函数)
// 2.fmt.Println() 看到的a...其实是goland给你提示的,函数的形参是a...
// 3.go语言中,包导入必须使用,否则报错,注释掉包会自动删除(goland做的,其他编辑器没有)
1.1执行方法:
1.使用golang直接运行
2.在命令行敲:go run g1.go
2.变量
2.1声明变量的三种方式
2.1.1.全定义
var关键字 变量名 变量类型 = 变量值
var a int = 10
2.1.2 类型推导(自动推导出变量类型)
var关键字 变量名 = 变量值
var a = 10
fmt.Pringf("%T",a) # 查看a的类型
2.1.3 简略声明
a : = 10
fmt.Pringln(a)
附加1:只定义变量,不赋值
var a int
a = 10
fmt.Println(a)
附加2:声明多个变量
var a,b,c int=10,11,‘xxx‘
附加3:变量不能重复定义
//var a int
////var a=90 //重复定义
//a :=90 //重复定义
//a=90 //重复定义
变量:总结
1.变量定义了必须使用,否则报错(只有go要求这样)
2.查看变量没写
3.变量要先定义在使用
4.变量类型是固定的,不允许中途改变的(静态语言)
5.如果只是定义了变量,必须指定类型,只能用第一种定义方式
6.变量不允许重复定义
强调:
以后所有类型的变量定义,就参照变量定义的三种方式
3.变量类型
3.1 数字类型
//类型: 数字,字符串,布尔
/*
数字:
-int:整数类型(包括负数)
-int,int8,int16,int32,int64
-int8:表示能存储的长度 8个比特位,8个小格,一个格只能放0和1,
能表示的范围是:正的2的七次方-1到,负的2的7次方-1
-int16,32...类推
-int:在32位机器上,表示int32,在64位机器上表示int64
-表示人的年龄:300岁撑死了,int64 是不是有点浪费,可以定义成int8
-python中int类型,远远比go的int64大,大很多,python中一切皆对象,int int对象
-uint:正整数
-uint,uint8,uint16,uint32,uint64
-uint8:范围:正2的8次方减一,有个0
-float:小数
-float32,float64
-complex:复数(高等数学学得,实部和虚部,了解)
-complex32,complex64
-byte:uint8的别名,byte:翻译过来是字节,一个字节是多少,8个比特位,uint8就占8个比特位
-rune:int32的别名,4个字节,表示一个字符
3.2 string类型
string:用双引号表示的(单引号?表示的不是字符串,三引号?)
双引号和三引号
3.3 bool类型
bool类型标识一个布尔值,值为true或flase
package main
import "fmt"
func main() {
a := true
b := false
fmt.Println("a:", a, "b:", b)
c := a && b
fmt.Println("c:", c)
d := a || b
fmt.Println("d:", d)
}
在上面的程序中,a赋值为true,b赋值为false
c赋值为 a&&b ,仅当a和b都为 true 时,操作符 && 才会返回 true,
当 a 或者 b 为 true 时,操作符 || 返回 true
a: true b: false
c: false
d: true
4.常量
常量表示固定的值,不能再重新赋值为其他的值,不可以使用简略声明的方式创建常量
const关键字 变量名 变量类型 =值
const name string= "lqz"
//类型推导
const name = "lqz"
//简略声明 不可以
5.函数
5.1 定义函数
# 1.函数定义格式
func 关键字 函数名(参数1 类型,参数2 类型){ 需要执行的代码 }
# 2.函数的基本定义
func test(){}
# 3.带参数的函数
func test(a int,b int){ fmt.Println(a,b) }
# 4.带参数的函数,两个参数类型相同可以省略
func test(a,b int){ fmt.Println(a,b) }
# 5.带返回值(需要指定返回值类型是什么)
格式:func 关键字 函数名(参数1 类型,参数2 类型)返回值类型 {}
func test(a int,b int)int {
fmt.Println(a,b)
return a + b
}
# 6.多返回值(python中可以),返回两个int类型,需要指定每一个返回值的类型
func test(a int,b int)(int,int) {
fmt.Println(a,b)
return a , b
}
# 7.可变长,接受任意长度的参数,最后一个参数...T,表示可以接受多个T类型的参数,
func test2(s...string){
fmt.Println(s)
}
# 8.go中只有位置参数,没有关键字参数,没有默认参数
func test(a int,b string) {
fmt.Println(a)
fmt.Println(b)
}
# 9.匿名函数(没有名字的函数),一定要定义在函数内部
例1:定义匿名函数的时候就直接调用
func (n1 int, n2 int) int {
return n1 + n2
}(10, 30) //括号里的10,30 就相当于参数列表,分别对应n1和n2
例2: 将匿名函数赋值给一个变量,再通过该变量来调用匿名函数
test_fun := func (n1 int, n2 int) int {
return n1 - n2
}
res2 := test_fun(10, 30)
res3 := test_fun(50, 30)
fmt.Println("res2=", res2)
fmt.Println("res3=", res3)
fmt.Printf("%T", test_fun)
# 10.函数这个类型,他的参数,返回值,都是类型的一部分
//var a func()
//var b func(a,b int)
//var c func(a,b int)int
//var d func(a,b int)(int,string)
# 11.闭包 ---》 1.定义在函数内部 2.对外部作用域有引用
例1:没有参数
func test4() func(){
a:= func() {
fmt.Println("我是内层函数")
}
return a
}
例2:内层函数有参数
func test4()func(x,y int){
a:= func(x,y int)int {
fmt.Println("我是内层函数")
return x+y
}
return a
}
# 12.go实现装饰器 --go中欸有装饰器语法糖
func test5(x,y int)func(){
a:= func() {
fmt.Println(x+y)
}
return a
}
return 没有加返回值表示没有返回值,函数执行到这里就结束了
6.包
同一个文件夹下只能由一个包,也就是package后面的名字都要一致,默认跟文件夹名字一致,
总结
1.新建一个文件夹(包),包下新建任意多个go文件,但是包名都必须一致(建议就用文件夹名)
2.在包内定义的函数,大写字母开头,表示外部包可以使用,小写字母开头,表示只能内部使用,直接使用即可
3.在其他包中使用,要先导入(goland有自动提示)
4.公有和私有 就是大小写区分的
5.所有的包必须在gopath的src路径下,否则找不到
"""
package main
import "awesomeProject/test1"
func main() {
test1.Test()
}
"""
7.if判断
package main
import "fmt"
func main() {
//1 语法
/*
if 条件 {
//符合上面条件的执行
}
else if 条件{
//符合上面条件的执行
}else {
// 不符合上面的条件执行这个
}
*/
# 例1:定义全局变量
package main
import "fmt"
func main() {
var a int = 11
if a > 10{
fmt.Println("a>10")
} else if a ==10{
fmt.Println("a=10")
}else{
fmt.Println("a<10")
}
}
# 例2:定义局部变量
//作用域范围不一样
func main() {
if a:=10;a<9{
fmt.Println("小于9")
}else if a==10{
fmt.Println("10")
}else {
fmt.Println("都不符合")
}
}
}
8.for循环
package main
//循环:go中,没有while循环,只有一个for循环,for可以替代掉while
func main() {
# 1 语法,三部分都可以省略
/*
for关键字 初始化;条件;自增自减{
循环体的内容
}
*/
# 2 示例 从0 打印到9
//for i:=0;i<10;i++{
// fmt.Println(i)
//}
# 3 省略第一部分 从0 打印到9,把i的定义放在for外面
//i:=0
//for ;i<10;i++{
// fmt.Println(i)
//}
# 4 省略第三部分
//i:=0
//for ;i<10;{
// fmt.Println(i)
// i++
//}
# 5 第二部分省略,条件没了,死循环
//i:=0
//for ;;{
// fmt.Println(i)
// i++
//}
# 6 全省略的简略写法,死循环
//for {
// fmt.Println("xxxx")
//}
# 7 第一部分和第三部分都省略的变形
//i:=0
//for i<10 {
// fmt.Println(i)
// i++
//}
//for 条件{ 就是while循环
//
//}
# 8 break continue:任何语言都一样
}
9.switch
go中有的,python中没有,作用是多条件匹配,用于替换多个 if else
package main
import "fmt"
# 1.基本使用
func main() {
var a = 10
switch a {
case 4:
fmt.Println("4") # 满足条件后输出
case 11:
fmt.Println("11")
default:
fmt.Println("都不符合") # 都不符合情况下的默认
}
}
# 2.多表达式判断
func main() {
var a = 10
switch a {
case 4,5,6,7: # 满足条件中的一个
fmt.Println("4,5,6,7")
case 10,11,12,13:
fmt.Println("10,11,12,13")
default:
fmt.Println("都不符合")
}
}
# 3.无条件执行下一个
func main() {
var a = 10
switch a {
case 4,5,6,7: # 满足条件中的一个
fmt.Println("4,5,6,7")
case 10,11,12,13:
fmt.Println("10,11,12,13")
Fallthrough # 不管条件是否符合,都无条件执行下一个
default:
fmt.Println("都不符合")
}
}
10.数组
10.1 数组声明
数组是同一类型元素的集合,类似于python中的列表(列表可以放任意元素)
定义一个长度为3的int类型,返回的是默认值,int类型的0
var a [3]int
int 默认值:0
string 默认值:""
bool 默认值:false
切片 默认值:nil
数组默认值:跟他的数据是有关系的,数组存放类型的空值
10.2 数组定义
# 定义数组
var a [3]int
# 初始化数组
var a [3]string = [3]string{"haha","hehih","ajsdlkfjas"}
fmt.Println(a) # [haha hehih ajsdlkfjas]
var b [3]int = [3]int{1} # 如果值不满,使用默认值替换
fmt.Println(b) # [1 0 0]
10.3 数据类型
数据的类型有值类型和引用类型,go中的数组是值类型,当做函数传参,传到函数内部,修改数组,不会影响原来的,go中函数传参,都是copy传递
# 数组的长度
var b [9]int = [9]int{1,2,3,4,5,6,7,8,9}
fmt.Println(len(b))
# 循环数组
第一种:
var a = [10]int{1,2,3,9:111}
for i:=0;i<len(a);i++{
fmt.Println(a[i])
}
第二种:
var b = [9]int{1,2,3,4,5,6,7,8,9}
for i,v := range b{
fmt.Println(i,v)
}
# i是索引,v是具体的值,如果不想接收,使用_
# 多维数组:数组套数据,但是多维数组中的数据类型必须是一致的(一般最多二维)
var c [3][3]int = [3][3]int{{1,2,3},{4,5,6}}
fmt.Println(c) # [[1 2 3] [4 5 6] [0 0 0]]
10.4 切片
10.4.1 切片简介
切片是由数组建立的一种方便,灵活且功能强大的包装,切片本身不拥有任何数据,他们只是对现有数组的引用。
基本使用
var b = [9]int{1,2,3,4,5,6,7,8,9}
var d = b[:] # 全切
var d = b[5:] # 从索引为5开始且
fmt.Println(d)
10.4.2 切片的修改
var b = [9]int{1,2,3,4,5,6,7,8,9}
var d = b[5:]
d[0]=111
fmt.Println(d) # [111 7 8 9]
fmt.Println(b) # [1 2 3 4 5 111 7 8 9]
# 切片的修改会影响原数组,数组的更改也可以改变切片
10.4.3 切片的长度和容量
len():长度,现在有多少值
cap():容量,总共能放多少值
# 注:容量是从切片开始的位置往后算
10.4.4 使用make创建一个切片
# make内置函数,第一个参数写类型,第二个参数是切片的长度,第三个参数是切片的容量
var e []int = make([]int,3,4)
fmt.Println(e) # [0 0 0]
e[1]=100 # 给创建的切片赋值,赋值不能超出切片的长度范围,新添可以
10.4.5 追加切片元素
var b = [9]int{1,2,3,4,5,6,7,8,9}
var d []int= b[5:]
d = append(d, 111,222,333)
fmt.Println(b) # [1 2 3 4 5 100 7 8 9]
fmt.Println(d) # [100 7 8 9 111 222 333]
# 当最佳的值超过容量,会重新创建一个数组,让切片指向新的数组,新数组大小是原来切片容量的两倍
10.4.6 切片的函数传递(引用类型,传递,修改原来的值)
将切片当作函数形参一样传递
var a []int=make([]int,4,5)
fmt.Println(a)
test2(a) //copy传递
10.4.7 多维切片
var a [][]int=[][]int{{1,2,3},{2,3},{4,5,5,6,7,8,9}}
fmt.Println(a) # [[1 2 3] [2 3] [4 5 5 6 7 8 9]]
10.4.6 切片copy
var a []int=make([]int,6,7)
var b []int =[]int{1,2,3,4,5}
fmt.Println(a) # [0 0 0 0 0 0]
fmt.Println(b) # [1 2 3 4 5]
//把b的数据copy到a上
copy(a,b)
fmt.Println(a) # [1 2 3 4 5 0]
fmt.Println(b) # [1 2 3 4 5]
11.maps
相当于python中的字典,
11.1 如何创建map
map的类型:map[key的类型]value的类型
var a map[int]string
map的空值类型;nil (引用类型的控制都是nil)
11.2 定义并初始化(make完成)
定义map
# 没有经过初始化的map是空值类型,默认是nil
# 检测
var a map[int]string
if a==nil{
fmt.Println("我是空值")
}
初始化map
如果map不进行初始化,默认值为nil,不能进行操作
# 第一种:定义的时候直接传入值进行初始化
var a map[int]string= map[int]string{1:"lqz",2:"egon",3:"jason"}
fmt.Println(a) # map[1:lqz 2:egon 3:jason]
# 第二种:使用make进行初始化
var a = make(map[int]string)
11.3 给map添加元素
给map添加元素,有就更新,没有就新增,但是前提是map已经初始化过了
定义的map变量[key值] = value值 # 注意:key和value必须遵照规定的数据类型
a[1] = "no1"
11.4 获取map中的元素
a[1] # 如果a中有key为1的键值对,就获取,没有就返回默认值(空值)
从字典中取值,其实会返回两个值,一个是true和flase,一个是真正的值
如果用一个变量来接,就是真正的值,如果两个变量来接,第二个是true或flase
var a map[int]string = map[int]string{1:"haha",2:"heiheihei"}
t,v := a[1]
fmt.Println(t,v) # haha true
11.5 删除map中的元素
var a map[int]string = map[int]string{1:"haha",2:"heiheihei"}
delete(a,1)
fmt.Println(a) # map[2:heiheihei]
11.6 获取map的长度
var a map[int]string = map[int]string{1:"haha",2:"heiheihei"}
fmt.Println(len(a)) # 2
11.7 map是引用类型
11.8 map的value可以是任意类型
var a map[int]map[string]string=make(map[int]map[string]string)
11.9 map循环
# 只能使用range
var a =make(map[int]string)
a[1]="xx"
a[2]="uu"
a[3]="99"
a[4]="66"
a[6]="99"
fmt.Println(a)
//map是无序的 python3.6之前也是无序的,3.6以后有序了,如何做到的
for k,v:=range a{
fmt.Println(k,v)
}
12.指针
12.1 什么是指针
指针是一种存储变量内存地址的变量
变量 b 的值为 156,而 b 的内存地址为 0x1040a124
,变量 a 存储了 b 的地址,我们就称 a 指向了 b
12.2 指针使用
指针变量的类型为 *T
,该指针指向一个 T 类型的变量,指针的零值是 nil
package main
import "fmt"
func main() {
var b = 222
//a := &b
var a *int=&b
fmt.Println(*a) # 222
}
# 总结:
把b的内存地址赋值给a这个指针,直接打印a返回的是b的内存地址,
1.取一个变量的地址:& 取地址符号
2.如果在类型前面加 * ,表示指向这个类型的指针,例如:把 b 的地址赋值给 *int 类型的 a
3.在指针变量前加*,表示解引用(反解),可以获取对应指针的值,例如 *a
12.3 指针的解引用
指针的解引用可以获取指针所指向的变量值,将a解引用的语法是 *a
,
package main
import "fmt"
func main() {
var b = 222
a := &b
fmt.Println(&b) # 打印b的内存地址:0xc00000a0c8
*a += 10
fmt.Println(b) # 打印反解更改后b的值:232
fmt.Println(a) # 此时a的内存地址为:0xc00000a0c8
}
# 总结:
反解后的值的更改会影响原来值
12.4 向函数传递指针参数
package main
import "fmt"
func main() {
a :=100
b := &a
test(b)
}
func test(val *int){ # int 必须加上 *
fmt.Println(val)
}
12.5 不要向函数传递数组的指针,而应该使用切片
假如我们想在函数内修改一个数组,并希望调用函数的地方也能的带修改后的数组,一种解决方案是把一个指向数组的指针传递给这个函数
package main
import (
"fmt"
)
func modify(arr *[3]int) {
(*arr)[0] = 90 # 或者使用 arr[0] = 90 也可以
}
func main() {
a := [3]int{89, 90, 91}
modify(&a)
fmt.Println(a) # [90 90 91]
}
这种方法虽然好,但是在go中最好是使用切片来处理
package main
import (
"fmt"
)
func modify(sls []int) {
sls[0] = 90
}
func main() {
a := [3]int{89, 90, 91}
modify(a[:])
fmt.Println(a)
}
12.6 Go不支持指针运算
Go不支持其他语言中的指针运算
package main
func main() {
b := [...]int{109, 110, 111}
p := &b
p++
}
12.7 指针的指针
指针可以指向指针的指针
var b int = 156
var a *int = &b
var c **int c就是指向指针的指针,类型用**int表示,多一个*,如果多个,就用多个*
c = &a
12.8 数组指针和指针数组
# 数组指针:指向数组的指针
var a [3]int=[30]int{1,2,3}
var b *[3]int &a
# 指针数组:数组里面放指针
var x,y,z = 12,13,14
var b [3]*int = [3]*int{&x,&y,&c}
13.结构体
13.1 什么是结构体
结构体是一系列属性的集合,go语言中的结构体,类似于面向对象语言中的类,只不过只有属性,没有方法。
13.2 结构体声明
13.2.1 命名结构体
# 结构体定义:
type 结构体名字 struct {
属性 类型
属性 类型
}
# 举例:
type Employee struct {
firstName string
lastName string
age int
}
或者
type Employee struct {
firstName, lastName string
age, salary int
}
13.2.2 匿名结构体
声明结构体时不用声明一个新类型
var employee struct {
firstName, lastName string
age int
}
13.2.3 结构体初始化
# 对结构体进行实例化:
方法一:e:=Employee{}
方法二:var e Employee=Employee{}
# 传入属性:
1.按位置传(顺序不变,有几个值就要传几个值)
var e Employee=Employee{"1","2",3}
2.按关键字传(位置可以乱,可以少传)
var e Employee=Employee{firstName:"1",lastName:"2",age:3}
13.3 创建命名结构体
package main
import (
"fmt"
)
type Employee struct {
firstName, lastName string
age, salary int
}
func main() {
//creating structure using field names
emp1 := Employee{
firstName: "Sam",
age: 25,
salary: 500,
lastName: "Anderson",
}
//creating structure without using field names
emp2 := Employee{"Thomas", "Paul", 29, 800}
fmt.Println("Employee 1", emp1) # Employee 1 {Sam Anderson 25 500}
fmt.Println("Employee 2", emp2) # Employee 2 {Thomas Paul 29 800}
}
13.4 创建匿名结构体
匿名结构体,没有名字,不需要写type,定义在函数内部,只能使用一次,把一大堆属性方到一个变量中
package main
import ("fmt")
func main() {
emp3 := struct {
firstName, lastName string
age, salary int
}{
firstName: "Andreah",
lastName: "Nikola",
age: 31,
salary: 5000,
}
fmt.Println("Employee 3", emp3) # Employee 3 {Andreah Nikola 31 5000}
}
13.5 结构体的零值
定义好的结构体并没有被显式地初始化,该结构的字段默认赋值为字段的默认值
package main
import (
"fmt"
)
type Employee struct {
firstName, lastName string
age, salary int
}
func main() {
var emp4 Employee //zero valued structure
fmt.Println("Employee 4", emp4)
}
Employee 4 { 0 0}
13.6 访问结构体的字段
点号操作符 .
用于访问结构体的字段
package main
import (
"fmt"
)
type Employee struct {
firstName, lastName string
age, salary int
}
func main() {
emp6 := Employee{"Sam", "Anderson", 55, 6000}
fmt.Println("First Name:", emp6.firstName)
fmt.Println("Last Name:", emp6.lastName)
fmt.Println("Age:", emp6.age)
fmt.Printf("Salary: $%d", emp6.salary)
}
First Name: Sam
Last Name: Anderson
Age: 55
Salary: $6000
还可以创建零值的start,然后再给各个字段赋值
package main
import (
"fmt"
)
type Employee struct {
firstName, lastName string
age, salary int
}
func main() {
var emp7 Employee
emp7.firstName = "Jack"
emp7.lastName = "Adams"
fmt.Println("Employee 7:", emp7) # Employee 7: {Jack Adams 0 0}
}
13.7 结构体指针
创建结构体的指针,可以通过 .
的方式来访问结构体内的字段值,
如果需要改原来的值,就传入指针,如果不改变原来的,就传入实例对象过去
package main
import (
"fmt"
)
# 创建结构体
type Employee struct {
firstName, lastName string
age, salary int
}
func main() {
emp8 := &Employee{"Sam", "Anderson", 55, 6000} # 获取结构体的内存地址
fmt.Println("First Name:", (*emp8).firstName)
fmt.Println("Age:", (*emp8).age)
}
注:(*emp8).firstName 和 emp8.firstName 的作用是一样的,都可以被Go认同
13.8 匿名字段
当我们创建结构体时,字段可以只有类型,而没有字段名,这样的字段称为匿名字段
package main
import (
"fmt"
)
type Person struct {
string
int
}
func main() {
var p1 Person
p1.string = "naveen"
p1.int = 50
fmt.Println(p1) # {naveen 50}
}
虽然匿名字段没有名称,但其实匿名字段的名称就默认为他的类型,比如上面的 Person
结构体,虽说字段是匿名的,但Go默认这些字段名是他们各自的类型,所以 Person
结构体有两个名为 string
和 int
的字段
13.9 嵌套结构体
结构体的字段有可能也是一个结构体,这样的结构体称为嵌套结构体,结构体的类型就是定义的结构体的名
package main
import (
"fmt"
)
type Address struct {
city, state string
}
type Person struct {
name string
age int
address Address
}
func main() {
var p Person
p.name = "Naveen"
p.age = 50
p.address = Address {
city: "Chicago",
state: "Illinois",
}
fmt.Println("Name:", p.name)
fmt.Println("Age:",p.age)
fmt.Println("City:",p.address.city)
fmt.Println("State:",p.address.state)
}
# 输出
Name: Naveen
Age: 50
City: Chicago
State: Illinois
13.10 提升字段
将结构体中的 嵌套的结构体中的字段 提升到可以直接在结构体中进行访问
package main
import (
"fmt"
)
type Address struct {
city, state string
}
type Person struct {
name string
age int
Address
}
func main() {
var p Person
p.name = "Naveen"
p.age = 50
p.Address = Address{
city: "Chicago",
state: "Illinois",
}
fmt.Println("Name:", p.name)
fmt.Println("Age:", p.age)
fmt.Println("City:", p.city) //city is promoted field
fmt.Println("State:", p.state) //state is promoted field
}
# 输出
Name: Naveen
Age: 50
City: Chicago
State: Illinois
13.11 导出结构体和字段
如果结构体名称以大写字母开头,则他是其他包可以访问的导出类型,同样,如果结构体里的字段首字母大写,他也可能被其他包访问到
定义一个包
package computer
type Spec struct { //exported struct
Maker string //exported field
model string //unexported field
Price int //exported field
}
在另一个包中进行访问
package main
import "structs/computer"
import "fmt"
func main() {
var spec computer.Spec
spec.Maker = "apple"
spec.Price = 50000
fmt.Println("Spec:", spec)
}
13.12 结构体相等性
结构体是值类型,如果他的每一个字段都是可比较的,则该结构体也是可比较的,如果两个结构体变量的对应字段相等,则这两个变量也是相等,如果两个结构体包含不可比较的字段,则结构体变量也不可比较
package main
import (
"fmt"
)
type name struct {
firstName string
lastName string
}
func main() {
name1 := name{"Steve", "Jobs"}
name2 := name{"Steve", "Jobs"}
if name1 == name2 {
fmt.Println("name1 and name2 are equal")
} else {
fmt.Println("name1 and name2 are not equal")
}
name3 := name{firstName:"Steve", lastName:"Jobs"}
name4 := name{}
name4.firstName = "Steve"
if name3 == name4 {
fmt.Println("name3 and name4 are equal")
} else {
fmt.Println("name3 and name4 are not equal")
}
}
14.方法
14.1 什么是方法
方法其实就是一个函数,在 func 这个关键字和方法名中间加入一个特殊的接收器类型,接收器可以是结构体类型或者是非结构体类型,接受器是可以在方法的内部访问的
结构体 + 方法 = 面向对象中的类
# 创建方法的语法
func (t Type) methodName(parameter list) {}
14.2 方法的使用
package main
import "fmt"
// 1.定义结构体
type Person struct {
name string
age int
sex int
}
// 2.给结构体绑定方法
func (p Person)printName(){
fmt.Println(p.name)
}
func main(){
// 3.使用方法
p:=Person{name:"wang",age:12,sex:1}
// 4.自动传值
p.printName()
}
14.3 值接受器和指针接受器
值接受器不改变原来的,指针接收器改变原来的
package main
import (
"fmt"
)
type Employee struct {
name string
age int
}
/*
使用值接收器的方法。
*/
func (e Employee) changeName(newName string) {
e.name = newName
}
/*
使用指针接收器的方法。
*/
func (e *Employee) changeAge(newAge int) {
e.age = newAge
}
func main() {
e := Employee{
name: "Mark Andrew",
age: 50,
}
fmt.Printf("Employee name before change: %s", e.name)
e.changeName("Michael Andrew")
fmt.Printf("
Employee name after change: %s", e.name)
fmt.Printf("
Employee age before change: %d", e.age)
(&e).changeAge(51)
fmt.Printf("
Employee age after change: %d", e.age)
}
# 输出:
Employee name before change: Mark Andrew
Employee name after change: Mark Andrew
Employee age before change: 50
Employee age after change: 51
14.3.1 什么时候使用指针接收器和值接受器
指针接收器:对方法内部的接收器所做的改变应该对调查者可见时,因为指针接收器会改变原来的值,当拷贝一个结构体的待解过于昂贵的时候,使用指针接收器,结构体不会被使用,只会传递一个指针到方法内部使用
一般情况下推荐使用值接受器
14.4 匿名字段的方法
匿名字段:在一个结构体中调用添加另一个结构体,绑定方法的时候可以在这个结构体中访问到零一个结构体中的属性
type Hobby1 struct {
id int
hobbyName string
}
type Person5 struct {
name string
age int
sex int
Hobby1 //匿名字段
}
//Hobby1的绑定方法
func (h Hobby1)printName() {
fmt.Println(h.hobbyName)
}
//Person5的绑定方法
func (p Person5)printName() {
fmt.Println(p.name)
}
14.5 在方法中使用值接受器与在函数中使用值参数
当一个函数有一个值参数,他只能接受一个值参数
当一个方法有一个值接受器,它可以接受值值接受器和指针接收器
package main
import (
"fmt"
)
type rectangle struct {
length int
width int
}
# 定义一个值参数
func area(r rectangle) {
fmt.Printf("Area Function result: %d
", (r.length * r.width))
}
# 定义一个值接受器
func (r rectangle) area() {
fmt.Printf("Area Method result: %d
", (r.length * r.width))
}
func main() {
r := rectangle{
length: 10,
width: 5,
}
area(r) // 调用函数
r.area() // 调用方法
p := &r
/*
compilation error, cannot use p (type *rectangle) as type rectangle
in argument to area
*/
//area(p)
p.area() //通过指针调用值接收器
}
# 输出
Area Function result: 50
Area Method result: 50
Area Method result: 50
14.6 在方法中使用指针接收器与在函数中使用指针参数
和值参数类似,函数使用指针参数只接受指针,而使用指针接收器的方法可以使用值接受器和指针接收器
package main
import (
"fmt"
)
type rectangle struct {
length int
width int
}
# 定义一个函数,传入指针参数
func perimeter(r *rectangle) {
fmt.Println("perimeter function output:", 2*(r.length+r.width))
}
# 定义一个指针接收器,传入指针
func (r *rectangle) perimeter() {
fmt.Println("perimeter method output:", 2*(r.length+r.width))
}
func main() {
r := rectangle{
length: 10,
width: 5,
}
p := &r //pointer to r
perimeter(p)
p.perimeter()
/*
cannot use r (type rectangle) as type *rectangle in argument to perimeter
*/
//perimeter(r)
r.perimeter()//使用值来调用指针接收器
}
14.7 在非结构体上的方法
package main
import "fmt"
type myInt int // 给传统的数据类型重命名,然后绑定方法
func (a myInt) add(b myInt)myInt{
return a + b
}
func main() {
num1 := myInt(5)
num2 := myInt(10)
sum := num1.add(num2)
fmt.Println(sum)
}
15.接口
15.1 什么是接口
接口:接口定义一个对象的行为,一系列方法的集合
在Go语言中,接口就是方法签名的集合,当一个类型定义了接口中的所有方法,我们称他实现了该接口
15.2 接口的声明和实现
package main
import "fmt"
//1 定义一个鸭子接口(run方法 speak方法)
type DuckInterface interface {
run()
speak()
}
//1.1 写一个唐老鸭结构体,实现该接口
type TDuck struct {
name string
age int
wife string
}
//1.2 实现接口(只要结构体绑定了接口中的所有方法,就叫做:结构体实现了该接口)
func (t TDuck)run() {
fmt.Println("我是唐老鸭,我的名字叫",t.name,"我会人走路")
}
func (t TDuck)speak() {
fmt.Println("我是唐老鸭,我的名字叫",t.name,"我说人话")
}
//2.1 写一个肉鸭结构体,实现该接口
type RDuck struct {
name string
age int
}
//2.2 实现接口(只要结构体绑定了接口中的所有方法,就叫做:结构体实现了该接口)
func (t RDuck)run() {
fmt.Println("我是肉鸭,我的名字叫",t.name,"我会人走路")
}
func (t RDuck)speak() {
fmt.Println("我是肉鸭,我的名字叫",t.name,"我说人话")
}
func main() {
//var t TDuck=TDuck{"嘤嘤怪",18,"刘亦菲"}
//var r RDuck=RDuck{"建哥",18}
////唐老鸭的run方法
//t.run()
////肉鸭的run方法
//r.run()
//定义一个鸭子接口类型(因为肉鸭和唐老鸭都实现了鸭子接口,所有可以把这俩对象赋值给鸭子接口)
var i1 DuckInterface
i1=TDuck{"嘤嘤怪",18,"刘亦菲"}
var i2 DuckInterface
i2=RDuck{"建哥",18}
//唐老鸭的run方法
i1.run()
//肉鸭的run方法
i2.run()
}
# 输出
我是唐老鸭,我的名字叫 嘤嘤怪 我会人走路
我是肉鸭,我的名字叫 建哥 我会人走路
15.3 空接口
空接口因为一个接口都没有,所有类型都给了空接口
type Empty interface { }
var a Empty =[3]int{1,2,3}
fmt.Println(a)
15.4 匿名空接口
没有名字的空接口,不需要type关键字,只是用一次
var a interface{}="xxx"
fmt.Println(a)
15.5 类型选择
根据类型判断走那一条路,通过赋值给一个便令的方式,来对对应的接口进行取值。
# 4 类型选择
func test(i interface{}) { //可以接收任意类型的参数
switch a:=i.(type) {
case int:
fmt.Println("我是int")
case string:
fmt.Println("我是字符串")
case TDuck:
fmt.Println("我是TDuck类型")
//打印出wife
fmt.Println(a.wife)
case RDuck:
fmt.Println("我是RDuck类型")
fmt.Println(a.name)
default:
fmt.Println("未知类型")
}
}
15.6 实现多接口
//type SalaryCalculator interface {
// DisplaySalary()
//}
//
//type LeaveCalculator interface {
// CalculateLeavesLeft() int
//}
//
//type Employee struct {
// firstName string
// lastName string
//}
//
//func (e Employee)DisplaySalary() {
// fmt.Println("xxxxx")
//}
//
//func (e Employee)CalculateLeavesLeft() int {
// return 1
//}
15.7 接口嵌套
type SalaryCalculator interface {
DisplaySalary()
run()
}
//type LeaveCalculator interface {
// SalaryCalculator
// //DisplaySalary()
// //run()
// CalculateLeavesLeft()
//}
type Employee struct {
firstName string
lastName string
}
func (e Employee)DisplaySalary() {
}
func (e Employee)run() {
}
func (e Employee)CalculateLeavesLeft() {
}
func main() {
//4 实现多个接口,就可以赋值给不通的接口类型
//var a SalaryCalculator
//var b LeaveCalculator
//var c Employee=Employee{}
//a=c
//b=c
//4 接口嵌套
//var c Employee=Employee{}
//var b LeaveCalculator
//b=c
//5 接口零值:是nil类型,接口类型是引用类型
//var a LeaveCalculator
//fmt.Println(a)
15.6 侵入式接口和非侵入式接口
go,python:非侵入式接口 修改,删除,对子类没有影响
16.Go协程
并发:交替运行,看起来像同时运行
并行:多核cpu下才行
直接使用 go 关键字
goroutine:go的协程(并不是真正的协程,只是一个统称,有线程也有协程
16.1 启动一个协程
如果直接在main中使用 go func()
创建一个协程,呢么这个协程中的代码不会执行,因为在调用go协程之后,程序控制会立即返回到代码的下一行,忽略该协程的任何返回值,所以即使开了协程,也不会执行,可以使用时间延迟,等协程运行完,将值返回之后再运行下一行代码
package main
import (
"fmt"
"time"
)
func hello() {
fmt.Println("Hello world goroutine")
}
func main() {
go hello()
time.Sleep(1 * time.Second)
fmt.Println("main function")
}
17.信道
信道:Go中不同协程之间通信的通道,如同管道中的水,从一端流向另一端,信道就是一个变量
17.1 信道声明
信道的零值为 nil
所有的信道都关联了一个类型,信道只能运输这种类型的数据,而运输其他类型的数据都是非法的,
chan T 代表 T 类型的信道
# 信道操作
data := <- a // 读取信道 a
a <- data // 写入信道 a
// 声明一个信道
package main
import (
"fmt"
"time"
)
// 定义一个信道
func ttt(a chan int){
fmt.Println("ttt")
time.Sleep(time.Second*2)
a <- 1 # 将值放入信道中
fmt.Println("xxx")
}
func main() {
var a chan int = make(chan int ) # 对信道进行实例化
go ttt(a)
c := <-a # 将值取出
fmt.Println("我是c",c)
}
17.2 信道接收与发送
发送和接收默认是阻塞的,当把数据发送到信道时,程序控制会在发送数据的语句出发生阻塞,直到有其他Go协程从信道读取到数据,才会接触阻塞,当读取信道的数据实,如果没有其他的协程把数据写入到这个信道,呢么读取过程就会一致阻塞
17.3 死锁
使用信道需要考虑的一个重点就是死锁,当Go协程给一个信道发送数据时,如果没有其他协程来接收数据,就会在运行时触发panic,形成死锁
同理,当有Go协程等着从一个信道接收数据时,如果没有其他协程向该信道写入数据,就会触发panic
17.4 单向信道
单向信道的作用只能发送或者接收数据
package main
import "fmt"
// 把chal转换称一个唯送信道
func sendData(sendch chan<- int) {
sendch <- 10
}
func main() {
// 创建一个双向信道
cha1 := make(chan int)
go sendData(cha1)
fmt.Println(<-cha1)
}
17.5 关闭信道和使用for range遍历信道
数据发送方可以关闭信道,通知接收方这个信道不再有数据发送过来
当从新到接收数据时,接收方可以多用一个变量来检查信道是否已经关闭
v, ok := <- ch # 第一个是真实的值,第二个是true或者false
// 创建一个遍历信道
package main
import (
"fmt"
)
func producer(chnl chan int) {
for i := 0; i < 10; i++ {
chnl <- i
}
close(chnl)
}
func main() {
ch := make(chan int)
go producer(ch)
for {
v, ok := <-ch
if ok == false {
break
}
fmt.Println("Received ", v, ok)
}
}
18.有缓冲信道
有缓冲信道:管子可以放多个值,相当于一个队列,如果超出定义队列的长度,就会报错,提示是死锁。在缓冲为空的时候,才会阻塞从缓冲信道接收数据
18.1 有缓冲信道声明
# 通过向make函数再传递一个表示容量的参数(指定缓冲的大小),可以创建缓冲信道
ch := make(chan type, capacity) # capacity 即为缓冲信道容量
# 使用信道创建生产者消费者模型
package main
import (
"fmt"
"time"
)
func write(ch chan int) {
for i := 0; i < 5; i++ {
ch <- i
fmt.Println("successfully wrote", i, "to ch")
}
close(ch)
}
func main() {
ch := make(chan int, 2)
go write(ch)
time.Sleep(2 * time.Second)
for v := range ch {
fmt.Println("read value", v,"from ch")
time.Sleep(2 * time.Second)
}
}
18.2 死锁
当我们向容量为2的缓冲信道写入3个字符串,程序控制到达第3次写入时,由于他超出了信道的容量,会发生阻塞,这个时候我们就必须要有其他携程来读取这个信道的数据,如果没有,就会发生死锁
package main
import (
"fmt"
)
func main() {
ch := make(chan string, 2)
ch <- "naveen"
ch <- "paul"
ch <- "steve"
fmt.Println(<-ch)
fmt.Println(<-ch)
}
# 输出提示
fatal error: all goroutines are asleep - deadlock!
goroutine 1 [chan send]:
main.main()
/tmp/sandbox274756028/main.go:11 +0x100
18.3 长度 vs 容量
缓冲信道的容量是指信道可以存储的值的数量,我们在使用make函数创建缓冲信道的时候会指定容量带线啊哦
缓冲信道的长度是指信道中当前排队的元素个数
package main
import (
"fmt"
)
func main() {
ch := make(chan string, 3)
ch <- "naveen"
ch <- "paul"
fmt.Println("capacity is", cap(ch))
fmt.Println("length is", len(ch))
fmt.Println("read value", <-ch)
fmt.Println("new length is", len(ch))
}
# 输出
capacity is 3
length is 2
read value naveen
new length is 1
19.异常处理
go中没有try,发生异常的时候需要使用别的方法来捕获异常
go中捕获异常使用的是:
defer:延迟执行,即使出现严重错误也会执行
panic:主动抛出异常,终止执行
recover:恢复程序,继续执行
19.1 defer
defer是go语言中的关键字,延迟指定函数的执行。通常在资源释放、连接关闭、函数结束时调用。多个defer为堆栈结构,先进后出,也就是先进的后执行。defer可用于异常抛出后的处理。
19.2 panic
panic是go语言中的内置函数,抛出异常(类似java中的throw)。其函数定义为:
func panic(v interface{})
19.3 recover
recover() 是go语言中的内置函数,获取异常(类似java中的catch),多次调用时,只有第一次能获取值。其函数定义为:
func recover() interface{}
19.4 异常处理
// 终极总结异常处理,以后要捕获哪的异常,就在哪写
func f2() {
//defer fmt.Println("我会执行")
//在这捕获异常,把异常解决掉
# 只要哪里发生异常,就从使用下边的异常方法处理
defer func() {
if err := recover(); err != nil { //recover执行,如果等于nil 表示没有异常,如果有值,表示出错,err就是错误信息
fmt.Println(err)
}
fmt.Println("我是finally的东西")
}()
}
以上是关于Go --- 基础使用的主要内容,如果未能解决你的问题,请参考以下文章
[vscode]--HTML代码片段(基础版,reactvuejquery)
npm : 无法加载文件 D:softcodeProcess ode ode_global pm.ps1,因为在此系统上禁止运行脚本。有关详细信息,请参阅 https:/go.micr +(代码片段