go语言语法入门
1.源文件
- 源码采用
UTF-8
编码,对Unicode支持良好 - 在文件头部用
package
声明所属包的名称 - 左大括号
{
必须和函数声明或者控制结构放在同一行 - go语句最后的分号可省,出现分号的典型位置是for循环或类似之处
2.变量
声明单个变量
-
var name type
是声明单个变量的语法 - 如果变量未被赋值,Go 会自动地将其初始化,赋值该变量类型的零值(Zero Value)
- 这是对C语言的优化
- 如C语言
int* a,b;
a是指针,b不是,如果两者都是指针,必须要在b前重复一个* - 而go语言
var a,b *int
a,b都是指针
声明变量并初始化
-
var name type = initialvalue
的语法用于声明变量并初始化。
支持类型推断
-
var name = initialvalue
根据初始化值推断类型
声明多个变量
var name1, name2 type = initialvalue1, initialvalue2
- 不同类型的变量
var (
name1 = initialvalue1
name2 = initialvalue2
name3 type
)
简短声明
name:=initialvalue
- 要求1:
:=
操作符左边的所有变量都有初始值
-
name,age:="kyl"
就会报错,因为age没有赋值
- 要求2:
:=
操作符左边至少有一个变量是未声明的
强类型
- 不允许某一类型的变量赋值为其他类型的值
定义变量后要使用,否则会报错
var a int//声明单个变量
var b int=10//声明变量并初始化
var c="hello"//类型推断
d:=1.5//简短声明
3.if
语句
- go 中的条件表达式不需要像C语言括起来
- if 和else 后面的大括号不可省略,即使只有一条语句
- else 不可单独成行,必须要跟在if的右括号的后面,else if同理
- if 和条件间支持定义一个变量,但是变量只在条件块中有效
if a > b {
c = a
} else {
c = b
}
if d := 50; d > 60 {
fmt.Println("pass")
} else {
fmt.Println("fail")
}
4.输入语句
- 使用
fmt.Scanln()
获取输入/使用fmt.Scanf()
获取输入
var name string
var age int
var num int
//使用fmt.Scanln()
fmt.Println("input your name")
fmt.Scanln(&name) //莫忘取地址符号
fmt.Println("input your age")
fmt.Scanln(&age)
//使用fmt.Scanf()
fmt.Println("input your num")
fmt.Scanf("%d", &num)
fmt.Printf("名字是%v,年龄是%v,序号是%d\n", name, age, num)
5.switch
分支结构
- case 后面可以带多个表达式,用分号隔开
- case 语句块不需要写break , 因为默认会有,即在默认情况下,当程序执行完case 语句块后,就直接退出该switch 控制结构。
- case/switch 后是一个表达式( 即:常量值、变量、一个有返回值的函数等都可以)
- case 后的各个表达式的值的数据类型,必须和switch 的表达式数据类型一致
- case 后面的表达式如果是常量值(字面量),则要求不能重复
- switch 后也可以不带表达式,类似if --else 分支来使用
- 如果在case 语句块后增加
fallthrough
, 则会继续执行下一个case,而不去判断下一个case的表达式是否为true,也叫switch 穿透
var month int
fmt.Println("input month")
fmt.Scanln(&month)
switch month {
case 3, 4, 5:
fmt.Println("spring")
case 6, 7, 8:
fmt.Println("summer")
case 9, 10, 11:
fmt.Println("autumn")
case 12, 1, 2:
fmt.Println("winter")
default:
fmt.Println("input error!")
}
input month
12
winterinput month
13
input error!input month
5
spring
//switch后可以不带表达式,类似if-else来使用
var age int
fmt.Println("input age")
fmt.Scanln(&age)
switch {
case age == 10:
fmt.Println("age==10")
case age == 20:
fmt.Println("age==20")
default:
fmt.Println("no match")
}
//对范围的选择
var score int
fmt.Println("input score")
fmt.Scanln(&score)
switch {
case score >= 90://case后也可以对范围进行判断
fmt.Println("great")
case score >= 80:
fmt.Println("good")
case score >= 60:
fmt.Println("ok")
default:
fmt.Println("bad")
}
switch time.Now().Weekday() {//需要使用time包
case time.Saturday, time.Sunday:
fmt.Println("it's the weekend")
default:
fmt.Println("it's a weekday")
}
switch t := time.Now();true{//tru如果省略,默认为true;但‘;’不能少
case t.Hour() < 12:
fmt.Println("it's before noon")
default:
fmt.Println("it's after noon")
}
//fallthrough
var num int
switch num {
case 1:
fmt.Println("ok1")
fallthrough //默认只能穿透一层
case 2:
fmt.Println("ok2")
fallthrough
case 3:
fmt.Println("ok3")
default:
fmt.Println("no match")
}
6.循环
- 方式一:(与
c
相同)
-
for
循环变量初始化; 循环条件; 循环变量迭代{
循环操作(语句)
}
- 方式二:(相当于
c
中的while
)
-
for
循环判断条件{
//循环执行语句
}
将变量初始化和变量迭代写到其它位置
- 方式三:(需要配合
break
使用)
- for {
//循环执行语句
}
package main
import(
"fmt"
"time"
"math/rand"
)
func main(){
count:=0
for{
rand.Seed(time.Now().UnixNano())
n:=rand.Intn(100)+1
//fmt.Println("n=",n)
count++
if(n==50){//莫忘大括号
break
}
}
fmt.Println("生成50需要的次数:",count)
}
package main
import (
"fmt"
)
//打印九九乘法表
func main(){
var num int=9
for i:=1;i<=num;i++{
for j:=1;j<=i;j++{
fmt.Printf("%v * %v =%v \t",j,i,j*i)
}
fmt.Println()
}
}
package main
import (
"fmt"
)
func main(){
str:="a song 雪花飘飘,北风啸啸"
for i,v:=range str{
fmt.Printf("%d %c\n",i,v)
//可以看出utf-8中汉字占3个字节
}
}
7.函数
多返回值
- func 函数名(形参列表)(返回值列表){
执行语句…
return 返回值列表
}
package main
import (
"fmt"
)
func z(x,y int)(int,int){
s1,s2:=x,y
for t:=x%y;t!=0;t=x%y{
x=y
y=t
}
return y,s1*s2/y
}
func main(){
fmt.Println(z(16,24))
}
8 48
- 给每个返回值命名
- 就像形参一样,将每个返回值具体到变量上,这样就可以对返回的变量赋值。
package main
import (
"fmt"
)
//返回两个数的和与差
func ans(a, b int) (int, int) {
return a + b, a - b
}
//给返回值命名
func ans1(a,b int)(mul,div ,rem int){
mul=a*b
div=a/b
rem=a%b
return
}
func main() {
a, b := 5, 8
fmt.Println(ans(a, b))
fmt.Println(ans1(a,b))
}
13 -3
40 0 5
- 多返回值模型促进了”comma-ok”模式
package main
import (
"fmt"
)
func main(){
arr:=[3]int{5,6,7}
if v,ok:=get(2,arr);ok{
fmt.Println(v)
}
}
func get(i int,arr [3]int)(int,bool){
if i<0||i>len(arr){
return 0,false
}
return arr[i],true
}
7
匿名函数
- 匿名函数不能作为顶级函数,而只能放在其他函数的函数体中,也就是说它必须有一个外层函数
- 在定义匿名函数时就直接调用,这种方式匿名函数只能调用一次
- 将匿名函数赋给一个变量(函数变量),再通过该变量来调用匿名函数
package main
import (
"fmt"
)
func main(){
fmt.Println(func(a,b int) int{
return a+b
}(15,16))//声明匿名函数时调用
f1:=func(a,b int) int{
return a+b
}//声明一个匿名函数,并直接赋给函数类型变量f
fmt.Println(f1(1,6))
f:=func(a,b int) int{
return a+b
}(1,16)//将匿名函数返回值赋给变量
fmt.Println(f)
}
31
7
17
闭包
- 匿名函数被称为闭包,原因在于匿名函数通过某种方式使其可见范围超出了其定义的范围。
- go为声明匿名函数提供了简单的语法,像许多动态语言一样,这些匿名函数在它们被定义的范围内创建了词法闭包
- 函数返回一个匿名函数,而匿名函数使用了自身的外部变量,匿名函数与这个变量共同形成闭包
实例一
package main
import (
"fmt"
"strings"
)
//传入文件名,无后缀名的添加后缀名
func main() {
f2 := makeSuffix(".jpg")
fmt.Println("处理后=", f2("winter"))
fmt.Println("处理后=", f2("summer.jpg"))
}
func makeSuffix(suffix string) func(string) string {
return func(name string) string {
if !strings.HasSuffix(name, suffix) {
return name + suffix
}
return name
}
}
//与传统方式的不同在于,传统方法需要每次都传入后缀名
//而使用闭包返回的函数保留着后缀名,可以反复使用
处理后= winter.jpg
处理后= summer.jpg
实例二
package main
import (
"fmt"
)
func main() {
f := squares()
fmt.Println(f()) // "1"
fmt.Println(f()) // "4"
fmt.Println(f()) // "9"
fmt.Println(f()) // "16"
}
// squares返回一个匿名函数。
// 该匿名函数每次被调用时都会返回下一个数的平方。
func squares() func() int {
var x int
return func() int {
x++
return x * x
}
}
1
4
9
16
实例三
package main
import "fmt"
func main() {
add5 := makeAdder(5)//5+y
add36 := makeAdder(36)//36+y
fmt.Println(add5(add36(1)))
sub5 := makeSuber(5)//5-y
sub36 := makeSuber(36)//36-y
fmt.Println(sub5(sub36(1)))
}
func makeAdder(x int) func(int) int {
return func(y int) int { return x + y }
//引用了外部变量X
}
func makeSuber(x int) func(int) int {
return func(y int) int { return x - y }
//引用了外部变量x,此x与makeAdder的x不同
}
42
-30
回调函数
- 函数可以作为其它函数的参数进行传递,然后在其它函数内调用执行,一般称之为回调
实例
package main
import (
"fmt"
)
func main(){
callback(1,add)
callback(1,sub)
callback(5,mul)
callback(5,div)
}
func add(a, b int) {
fmt.Println(a, "+", b, "=", a+b)
}
func sub(a, b int) {
fmt.Println(a, "-", b, "=", a-b)
}
func mul(a, b int) {
fmt.Println(a, "*", b, "=", a*b)
}
func div(a, b int) {
fmt.Println(a, "/", b, "=", a/b, "...", a%b)
}
func callback(y int, f func(int, int)) {
f(y, 2)
}
1 + 2 = 3
1 - 2 = -1
5 * 2 = 10
5 / 2 = 2 … 1
defer语句
- 延迟调用语句,无论函数执行是否出错,都确保结束前被调用
- 用于数据清理工作,保证代码结构清晰,避免遗忘
srcFile,err:=os.Open(srcName)
defer srcFile.Close()//关闭文件句柄,结构清晰,避免忘记关闭
实例
package main
import (
"fmt"
)
func main(){
defer fmt.Println("雪花飘飘")
defer fmt.Println("北风啸啸")
fmt.Println("天地一片苍茫")
}
天地一片苍茫
北风啸啸
雪花飘飘
8.异常处理机制
- 在默认情况下,当发生错误后(panic) ,程序就会退出(崩溃.)
- 内置函数panic,中断原有控制流,抛出一个异常流
- 内置函数recover,捕获到panic的异常值并恢复
- recover仅在延迟函数中有效,即在defer语句中调用
实例
package main
import (
"fmt"
)
func main(){
test01()
fmt.Println("test01")
test02()
fmt.Println("test02")//没有输出
}
func test01(){
defer func(){
if err:=recover(); err !=nil{
fmt.Println("err=",err)
}
}()//声明并调用
num1:=10
num2:=0
res:=num1/num2
fmt.Println("res=",res)
}
func test02(){
num1:=10
num2:=0
res:=num1/num2
fmt.Println("res=",res)//没有输出
}
err= runtime error: integer divide by zero
test01
panic: runtime error: integer divide by zerogoroutine 1 [running]:
main.test02()
e:/file/golang/goproject/src/异常处理机制/main.go:27 +0x11
main.main()
e:/file/golang/goproject/src/异常处理机制/main.go:10 +0x8a
exit status 2
9.数组
定义、初始化
package main
import (
"fmt"
)
func main() {
//数组
var a [4]int //元素自动初始化为零
var aa [4]int = [4]int{1, 2, 3, 4} //定义并初始化数组aa
var aaa [4]int = [4]int{1, 2, 3, 4} //定义并初始化数组aa
b := [4]int{2, 5} //未提供初始值的元素自动初始化为零
c := [4]int{5, 3: 10} //可指定索引位置初始化
d := [...]int{1, 2, 3} //编译器按初始化值数量确定数组长度
e := [...]int{10, 3: 100} //支持索引初始化,但数组长度与此有关
f := [...]int{
5: 200,
9: 10,
//这里需要汪意:如果分成多进行声明+初始化,
//那么你需要在最后一行的最后加上','
}
fmt.Println(a) //可直接输出素组各个元素,比C方便
fmt.Println(aa, aaa, b, c, d, e, f)
//…表示不指定长度,长度由初始化列表决定,这种情况…不能省略,否则就成了切片
//
}
结构体数组
数组元素为结构体
实例
package main
import (
"fmt"
)
type user struct{
id int
name string
age byte
}
func main(){
brother :=[...]user{
{1,"刘备",20},
{2,"关羽",19},
{3,"张飞",18},
}
fmt.Println(brother)
fmt.Printf("%v\n%+v\n%#v",brother,brother,brother)
}
[{1 刘备 20} {2 关羽 19} {3 张飞 18}]
[{1 刘备 20} {2 关羽 19} {3 张飞 18}]
[{id:1 name:刘备 age:20} {id:2 name:关羽 age:19} {id:3 name:张飞 age:18}]
[3]main.user{main.user{id:1, name:“刘备”, age:0x14}, main.user{id:2, name:“关羽”, age:0x13}, main.user{id:3, name:“张飞”, age:0x12}}
注:%v%+v%#v
区别
1.%v 只输出所有的值
2.%+v 先输出字段类型,再输出该字段的值
3.%#v 先输出结构体名字值,再输出结构体(字段类型+字段的值)
多维数组
- …仅用在第一维
- 内置函数len和cap都可以返回第一维的长度
package main
import "fmt"
func main() {
a := [2][2]int{{1, 2}, {3, 4}}
b := [...][2]int{//...仅用在第一维
{10, 20},
3: {30, 40},
}
c := [...][2][2]int{
{
{1, 2},
{3, 4},
},
{
{10, 20},
{30, 40},
},//莫忘逗号
}
fmt.Printf("%v\n%v\n%v", a, b, c)
//内置函数len和cap都可以返回第一维的长度
}
[[1 2] [3 4]]
[[10 20] [0 0] [0 0] [30 40]]
[[[1 2] [3 4]] [[10 20] [30 40]]]
数组和指针
- 数组名字不代表数组的首地址
package main
import (
"fmt"
)
func main() {
x, y := 10, 20
a := [...]*int{&x, &y}
q := a
p := &a
fmt.Println(a, p, q)
fmt.Printf("%T,%v\n", a, a)
fmt.Printf("%T,%v\n", q, q)
fmt.Printf("%p %p %p\n", &a, &a[0], &q[0])
fmt.Printf("%v %v %v\n", &a, &a[0], &q[0])
fmt.Printf("%T,%p,%v,%v,%p\n", p, p, p, *p, &p)
}
[0xc0000100a0 0xc0000100a8] &[0xc0000100a0 0xc0000100a8] [0xc0000100a0 0xc0000100a8]
[[2]*int,[0xc0000100a0 0xc0000100a8]
[[2]*int,[0xc0000100a0 0xc0000100a8]
0xc00003a1f0 0xc00003a1f0 0xc00003a200
&[0xc0000100a0 0xc0000100a8] 0xc00003a1f0 0xc00003a200
*[2]*int,0xc00003a1f0,&[0xc0000100a0 0xc0000100a8],[0xc0000100a0 0xc0000100a8],0xc000006028
数组和函数
package main
import (
"fmt"
)
func test(x [2]int){
fmt.Printf("x:%p,%v\n",&x,x)
}
func main(){
a:=[2]int {10,20}
var b [2]int
b=a//数组整体复制
fmt.Printf("a:%p,%v\n",&a,a)
fmt.Printf("b:%p,%v\n",&b,b)
test(a)
//&x!=&a,说明数组名不代表首地址,
//赋值 和 传参 只复制全部元素
//形参变化不影响实参
}
a:0xc0000100a0,[10 20]
b:0xc0000100b0,[10 20]
x:0xc000010110,[10 20]
数组、指针和函数
package main
import (
"fmt"
)
func test(x *[2]int){
x[1]+=100
fmt.Printf("x:%p,%v\n",x,*x)
}
func main(){
a:=[2]int{10,20}
test(&a)
fmt.Printf("a:%p,%v\n",&a,a)
}
x:0xc0000100a0,[10 120]
a:0xc0000100a0,[10 120]
- 区分指针数组与数组指针
- 数组指针:
var x *[2]int
,x是指向数组的指针,即指向数组的首地址 - 指针数组:
var y [2]*int
,y是指针数组,即数组中每个元素都是指针
10.切片
切片基础
- [ ]表示切片
- 切片是数组的一个引用,因此切片是引用类型,在进行传递时,遵守引用传递的机制。
- 切片是一个动态数组
- make函数用来创建指定类型的对象,var 切片名[]type = make([]type, len, [cap])
- make的方式实际上也会创建一个数组,但是不可见
- cap()获取切片空间的大小
- len()获得有效元素的个数
- append()在切片有效元素末尾添加元素;如果超出容量限制,容量翻倍;这时,要把新地址赋给原切片
- append的原型为:append([]interface{}, …interface{})
- 功能:表示可以为任何类型([]interface{})的切片追加0-n个元素(…interface{}),
- 如m.musics[index+1:]…含义就是把切片m.musics[index+1:]打散成一个一个的可追加的参数
package main
import (
"fmt"
)
func main(){
x:=make([]int,0,5)//创建长度为0,容量为5的切片
for i:=0;i<8;i++{
x=append(x,i)//在切片的尾部添加数据,莫忘赋给x
}
fmt.Println(x,len(x),cap(x))
//如若超出容量限制时,容量自动翻倍
}
[0 1 2 3 4 5 6 7] 8 10
//数组中元素 有效长度 容量
切片和数组
- 定义一个切片,然后让切片去引用一个已经创建好的数组,容量为数组的长度减去引用的元素的起始下标
- 切片初始化时
var slice = arr[startIndex:endIndex]
说明:从arr 数组下标为startIndex,取到下标为endIndex 的元素(不含arr[endIndex])。 - var slice = arr[0:end] 可以简写var slice = arr[:end]
var slice = arr[start:len(arr)] 可以简写: var slice = arr[start:]
var slice = arr[0:len(arr)] 可以简写: var slice = arr[:]
package main
import (
"fmt"
)
func main(){
x:=[...]int{0,1,2,3,4,5,6,7,8,9}
s1:=x[2:5]//s1包含2、3、4(不包含5)
fmt.Println(s1)
s2:=x[:6]//即x[0:6]
fmt.Println(s2)
s3:=x[4:]//即x[4:len(x)]
fmt.Println(s3)
s4:=x[:]//即x[0:len(x)]
fmt.Println(s4)
//fmt.Println(s1[5])
for i,v:=range s1{//遍历
fmt.Printf("%v:%v\n",i,v)
}
fmt.Println("len",len(s1),"cap",cap(s1))
//利用切片可以修改在数组中对应的那部分元素的值
s1[0]=5
//但是切片只是指向数组的子集,修改不能超出切片范围
//如s1[5]就不合法
s1=append(s1,15,16)
//但是可以利用通过给切片增加元素的方式更改值
fmt.Println(s1)
}
[2 3 4]
[0 1 2 3 4 5]
[4 5 6 7 8 9]
[0 1 2 3 4 5 6 7 8 9]
0:2
1:3
2:4
len 3 cap 8
[5 3 4 15 16]
切片拷贝
- 切片可以进行值的拷贝
- 切片之间可以传递
package main
import (
"fmt"
)
func main(){
//切片是引用类型,可传递
arr:=[...]int{1,2,3,4,5}
slice0:=arr[:]
slice1:=slice0
slice0[0]=10
fmt.Println("arr",arr)//arr [10 2 3 4 5]
fmt.Println("slice0",slice0)//slice [10 2 3 4 5]
fmt.Println("slice1",slice1)//slice1 [10 2 3 4 5]
//切片和数组的值均改变
//对切片拷贝
var slice2[]int=[]int{1,2,3,4,5}
var slice3=make([]int,10)//省略容量
copy(slice3,slice2)
slice3[0]=5
fmt.Println("slice2",slice2)//slice2 [1 2 3 4 5]
fmt.Println("slice3",slice3)//slice3 [5 2 3 4 5 0 0 0 0 0]
//可见,两个数据空间是独立的,相互不影响
var slice4[]int=[]int{1,2,3,4,5}
var slice5[]int=make([]int,1)
fmt.Println("slice5",slice5)//[0]
copy(slice5,slice4)
fmt.Println("slice5",slice5)//[1]
a:=[...]int{5,6,7,8,9}
var slice6[]int=a[2:]
var slice7=make([]int,5)
copy(slice7,slice6)
slice7[1]=9
fmt.Println("slice6",slice6)
fmt.Println("slcie7",slice7)
}
arr [10 2 3 4 5]
slice0 [10 2 3 4 5]
slice1 [10 2 3 4 5]
slice2 [1 2 3 4 5]
slice3 [5 2 3 4 5 0 0 0 0 0]
slice5 [0]
slice5 [1]
slice6 [7 8 9]
slcie7 [7 9 9 0 0]
11.map
- var map 变量名 map[keytype]valuetype
- 声明不会分配内存,初始化需要make,分配内存后才能赋值和使用
- 可以声明的时候用{}初始化
- map在使用前一定要make
- make的key-value是无序的,所以每次遍历结果先后关系可能不一样
- make的value可以相同
- make的key不能相同,如果重复了,则以最后的key-value为准
- map的key可以是很多中类型,通常为int,string但是slice,map,functio不可以,因为这几个不能用==来判断
- map的value通常为数字,string,map,struct
- map是无序的,如果要排序,可以对key进行排序,再根据key的值遍历输出
package main
import (
"fmt"
)
//字典类型
func main() {
gf := make(map[string]map[int]string)
//如果key还没有,就是增加,如果key存在就是修改
//可以先make再初始化,如gf
//也可以直接初始化,如gf["萧峰"]
gf["萧峰"] = map[int]string{0: "降龙十八掌", 1: "太祖长拳"}
gf["段誉"] = map[int]string{0: "六脉神剑", 1: "凌波微步"}
gf["虚竹"] = map[int]string{0: "小无相功", 1: "北冥神功"}
//map遍历
for name, kongfu := range gf { //字典是无序存储,每次遍历先后关系不同
fmt.Printf("%s:", name)
for _, kf := range kongfu {
fmt.Printf("%s\t", kf)
}
fmt.Println()
}
//查找
if p, ok := gf["萧峰"]; ok {
fmt.Println(p)
}
//删除
delete(gf, "段誉")
fmt.Println(gf)
//一次性的删除所有key
//1.遍历所有的key,逐一删除
//2.直接make一个新的空间
gf=make(map[string]map[int]string)
fmt.Println(gf)
}
虚竹:小无相功 北冥神功
萧峰:降龙十八掌 太祖长拳
段誉:六脉神剑 凌波微步
map[0:降龙十八掌 1:太祖长拳]
map[萧峰:map[0:降龙十八掌 1:太祖长拳] 虚竹:map[0:小无相功 1:北冥神功]]
map[]
12.对象与方法
- go支持类似c++面对对象的风格
- go提供了方法定义和调用他们的方式,因此语法并不笨拙
- 可以建立对象的同时初始化
- 通过内置函数new来建立对象,new返回一个指针
- 成员或方法首字母大写为public,小写为private
- func和方法名之间指定了方法的接收者,即所属的对象,也是方法的第一个参数
- 方法如果想要更改对象中的值,那么第一个参数应设为指针类型,不然只是对对象的拷贝
package main
import (
"fmt"
"math"
)
//Point 点类
type Point struct {
x, y float64
}
//Distance 计算距离
func (p Point) Distance() float64 {
return math.Sqrt(p.x*p.x + p.y*p.y)
}
//Change 修改
func (p *Point) Change(){
p.x=5.5
}
func main() {
var p1 Point = Point{3, 4}
var p2 *Point = new(Point) //通过内置函数new来建立对象
p3 := new(Point)
p3.Change()
fmt.Println(p1.Distance(), p2, p3)
}
5 &{0 0} &{5.5 0}
13.接口
- 如果几个对象间存在公共行为,而开发者想要抽象这种行为,那么就可以将这类行为定义为接口,并使用它。
- 接口本身不能创建实例,但是可以指向一个实现了该接口的自定义类型的变量
- 一个自定义类型只有实现了某个接口,才能将该自定义类型的实例(变量)赋给接口类型
- 空接口interface{} 没有任何方法,所以所有类型都实现了空接口, 即我们可以把任何一个变量
赋给空接口。
package main
import (
"fmt"
)
//Printer 定义接口
type Printer interface{
Print()//公共方法
//无需显示给出实现类型
//add other methods
//没有数据字段
}
type camera struct{
name string
age byte
}
//实现接口,谁使用谁实现
func (u camera) Print(){
fmt.Println(u)
}
type goods struct{
name string
num,price byte
}
//实现接口,谁使用谁实现
func(g goods)Print(){
fmt.Println(g)
}
type computer struct{
}
func (c computer)working ( p Printer){//接受接口类型变量
p.Print()
}
func main(){
//usb插槽就是现实中的接口
u:=camera{"Tom",18}
u.Print()//像使用普通方法一样使用接口,接口的热插拔
g:=goods{"bed",10,200}
var ip Printer
//定义接口变量ip
//只要对象实现了接口的全部方法,
//就表示实现了该接口,
//就可以赋值给接口变量
ip=u
ip.Print()
ip=g
ip.Print()
g.name="sofa"
g.Print()
ip.Print()
//当对象赋值给接口时,会发生拷贝。
//接口内部存储的是指向这个复制品的指针,
//无法修改其状态,也无法获取指针;
com:=computer{}
com.working(u)
com.working(ip)
com.working(g)
}
14.继承
- 继承可以解决代码复用
- 当多个结构体存在相同的属性(字段)和方法时,可以从这些结构体中抽象出结构体,在该结构体中定义这些相同的属性和方法。
- 其它的结构体不需要重新定义这些属性(字段)和方法,只需嵌套一个 匿名结构体即可
- 当结构体和匿名结构体有相同的字段或者方法时,编译器采用就近访问原则访问,如希望访问
匿名结构体的字段和方法,可以通过匿名结构体名来区别
package main
import (
"fmt"
)
type user struct{
name string
age byte
}
//Output 定义方法
func (u user)Output()string{
return fmt.Sprintf("%+v\n",u)
}
type leader struct{
user//匿名成员,可实现继承
title string
}
func main(){
var wo leader
wo.user.name="kyl"//匿名成员有着和类型一样的名字
wo.user.age=18
//可以简化写法
wo.name="fcl"
wo.title="manager"
var us=leader{user{"lzc",18},"dom"}//莫忘user
//var us=user{" ",91}
fmt.Println(us)
}
15.并发
- 进程就是程序在操作系统的一次执行过程,是系统进行资源分配和调度的基本单位
- 线程是进程的一个执行实例,是程序执行的最小单元,它是比进程更小的能独立运行的基本单位
- 一个程序至少有一个进程,一个进程至少有一个线程
- 并行与并发
- 并发:多个任务作用在一个cpu,在一个时间点上,只有一个任务在执行
- 并行:多个任务作用在多个cpu,在一个时间点上,多个任务在同时执行
package main
import (
"fmt"
"time"
)
func main(){
//非并发
/*task1()
task2()*/
go task1()
go task2()
fmt.Println("hhhhhhhhhh")
time.Sleep(time.Second*6)//如果没有这句话,没有输出,因为main执行太快
}
func task1(){
for i:=0;i<5;i++{
fmt.Println("hello")
time.Sleep(time.Second)
}
}
func task2(){
for i:=0;i<5;i++{
fmt.Println("world")
time.Sleep(time.Second)
}
}
package main
import (
"fmt"
"time"
)
func main() {
go spinner(100*time.Millisecond)//计算的同时显示动画
const n = 45
fibN := fib(n) // slow
fmt.Printf("\rFibonacci(%d) = %d\n", n, fibN)
}
func spinner(delay time.Duration) {
for {
for _, r := range `-\|/` {
fmt.Printf("\r%c", r)
time.Sleep(delay)
}
}
}
func fib(x int) int {
if x < 2 {
return x
}
return fib(x-1) + fib(x-2)
}
16.通道
- 主线程在等待所有goroutine 全部完成的时间很难确定
- 如果主线程休眠时间长了,会加长等待时间,如果等待时间短了,可能还有goroutine 处于工作
状态,这时也会随主线程的退出而销毁 - channle 本质就是一个数据结构-队列
- 数据是先进先出【FIFO : first in first out】
- var 变量名 chan 数据类型
- ch:=make(chan int)声明并初始化
- ch:=make(chan int,10)带缓冲区的通道,容量是10
- channel 是引用类型
- channel必须初始化才能写入数据,即make后才能使用
- 管道是有类型的,只能放置特定类型的数据
- channel的数据满了之后,不能再放入,除非取出数据,才可以继续放入
- 写入数据 : channel变量名 <- 数据;向channel写入数据通常会导致程序堵塞,直到有其他goroutine来从这个通道中读取数据
- 读取数据 : value := <- channel变量名;如果通道之前没有写入数据,那么从通道中读取数据也会导致程序堵塞,直到channel中被写入数据
- 关闭: close(channel变量名)‘关闭通道后不能再写入数据,但是仍然可以读取数据
- 无论怎样都不应该在接收端关闭通道,因为那样无法判断发送端是否还会向该通道发送值。
- 我们在发送端调用close关闭通道却不会对接收端接收该通道中已有的元素值产生任何影响。
- 遍历:支持for-range的方式进行遍历,但遍历前,channel需要关闭,如若不然,会出现deadlock报错
package main
import (
"fmt"
)
func consumer(data chan int, done chan bool) {
for x := range data {//遍历前channel需要关闭
fmt.Println("recv:", x)
}
done <- true//消费结束,向done通道写入true
}
//生产者
func producer(data chan int) {
for i := 0; i < 5; i++ {
fmt.Println("send message", i)
data <- i
//写入数据
}
close(data)//莫忘关闭
}
func main() {
//各个并发/行体之间如何通信
//布尔通道
done:=make(chan bool)//channel make后才能使用
//整形通道
data:=make(chan int)
//启动生产者
go producer(data)
go consumer(data,done)
<-done
//阻塞,直到接受消费者发出的结束信号
}
17.数据类型
- 布尔:bool
- 长度:1字节
- 取值范围:true,false
- 注意事项:不可以用数字代表true或false
- 整型:int/uint
- 根据运行平台可能为32位或64位
- 8位整型:int8/uint8
- 长度:1字节
- 取值范围:-128127/0255
- 字节型:byte(uint别名)
- 16位整型:int16/uint16
- 长度:2字节
- 取值范围:-3276832767/065535
- 32位整型:int32(rune)/uint32
- 长度:4字节
- 取值范围:-232/2~232/2-1/0~2^32-1
- 64位整型:int64/uint64
- 长度:8字节
- 取值范围:-264/2~264/2-1/0~2^64-1
- 浮点型:float32/float64
- 长度:4/8字节
- 小数位:精确到7/15小数位
- 复数:complex64/complex128
- 长度:8/16字节
- 能够保存指针的 32 位或 64 位整数型:uintptr
- 其它值类型:
- array、struct、string
- 引用类型:
- slice、map、chan
- 接口类型:inteface
- 函数类型:func
- 零值类型
- 零值并不等于空值,而是当变量被声明为某种类型后的默认值
- 通常情况下值类型的默认值为0,bool为false,string为空字符串“”
- 类型转换
- Go中不存在隐式转换,所有类型转换必须显式声明
- 转换只能发生在两种相互兼容的类型之间
- 类型转换的格式
<ValueA> [:]= <TypeOfValueA>(<ValueB>)
- rune 类型示例
package main
import (
"fmt"
)
func main(){
var ch1 rune
ch1='考'//int32
ch2:='试'//int32
str1:=string(ch1)+string(ch2)
str2:=ch1+ch2
fmt.Println(ch1,ch2)
fmt.Printf("%T %T %c %c",ch1,ch2,ch1,ch2)
fmt.Println(str1,str2)
str3:="成功"
fmt.Printf("%T %T\n",str3[0],str3)
for i,v:=range str3{
fmt.Printf("%T %d %c\n",v,i,v)
}
fmt.Println("-----")
for i:=0;i<len(str3);i++{
fmt.Printf("%d %v %T\n",i,str3[i],str3[i])
}
fmt.Println("str3.len=",len(str3))
}
32771 35797
int32 int32 考 试考试 68568
uint8 string
int32 0 成
int32 3 功
-----
0 230 uint8
1 136 uint8
2 144 uint8
3 229 uint8
4 138 uint8
5 159 uint8
str3.len= 6
- complex示例
package main
import (
"fmt"
"math/cmplx"
)
func main() {
//var comp complex128
comp := 2 + 4i
fmt.Printf("%T", comp) //默认128位
r := real(comp) //实部
im := imag(comp) //虚部
fmt.Println("comp=", r, "+", im, "*i")
c := cmplx.Sqrt(comp) //用于128位复数开方
fmt.Println(c)
}
complex128comp= 2 + 4 *i
(1.7989074399478673+1.1117859405028423i)
18.关键字
25个
- var和const :变量和常量的声明
- var varName type 或者 varName : = value
- package and import: 导入
- func: 用于定义函数和方法
- return :用于从函数返回
- defer someCode :在函数退出之前执行
- go : 用于并行
- select 用于选择不同类型的通讯
- interface 用于定义接口
- struct 用于定义抽象数据类型
- break、case、continue、for、fallthrough、else、if、switch、goto、default 流程控制
- chan用于channel通讯
- type用于声明自定义类型
- map用于声明map类型数据
- range用于读取slice、map、channel数据
19.运算符
- go中运算符均是从左向右结合
- 优先级
- 优先级 运算符
7 ^ !
6 * / % << >> & &^
5 + - | ^
4 == != < <= >= >
3 <-
2 &&
1 ||
- &^为清除标志位运算,从a上清除b 的标志位;
1、如果右侧是0,则左侧数保持不变
2、如果右侧是1,则左侧数一定清零 - ^作为一元运算符为按位取反 ,作为二元运算符为异或
package main
import (
"fmt"
)
func main(){
var a uint8=5
var b uint8=3
fmt.Println(a&^b)//从a上清除b上的标准位
fmt.Println(a^b)//按位异或
fmt.Println(^a)//按位取反
fmt.Println(0 &^ 0)
fmt.Println(0 &^ 1)
fmt.Println(1 &^ 0)
fmt.Println(1 &^ 1)
}
4
6
250
0
0
1
0
20.指针、++、–
- Go虽然保留了指针,但与其它编程语言不同的是,在Go当中不支持指针运算(如p++)以及”->”运算符,而直接采用”.”选择符来操作指针目标对象的成员
- 操作符”&”取变量地址,使用”*”通过指针间接访问目标对象
- 默认值为 nil 而非 NULL
- 递增递减语句
- 在Go当中,++ 与 – 是作为语句而并不是作为表达式
21.常量
- 常量的值在编译时就已经确定
- 常量的定义格式与变量基本相同
- 等号右侧必须是常量或者常量表达式
- 常量表达式中的函数必须是内置函数
- 当操作数是常量时,一些运行时的错误也可以在编译时被发现,例如整数除零、字符串索引越界、任何导致无效浮点数的操作等。
- 一个常量的声明也可以包含一个类型和一个值,但是如果没有显式指明类型,那么将从右边的表达式推断类型。
const pi=3.14159//声明单个常量
const (
const (
e = 2.71828182845904523536028747135266249775724709369995957496696763
pi = 3.14159265358979323846264338327950288419716939937510582097494459
)
- 如果不提供初值,则表示将使用上行的表达式
const (
a = 1
b
c = 2
d
)
fmt.Println(a, b, c, d) // "1 1 2 2"
- itoa:常量计数器
- 常量声明可以使用iota常量生成器初始化,它用于生成一组以相似规则初始化的常量,但是不用每行都写一遍初始化表达式。在一个const声明语句中,在第一个声明的常量所在的行,iota将会被置为0,然后在每一个有常量声明的行加一。
type Weekday int
const (
Sunday Weekday = iota//默认为0
Monday
Tuesday
Wednesday
Thursday
Friday
Saturday
)
const(
today =iota//重置为0
)
- iota与<<结合实现计算机存储单位的枚举
const (
_ = iota//0
KB ByteSize = 1 << (10*iota)//iota为1
MB
GB
TB
PB
EB
ZB
YB
)