1、hello world程序
package main
import (
"fmt" // 用来格式化输出数据,比如字符串,整数,小数
)
func main() {
fmt.Println("hello world")
}
函数定义的规则:
func 函数名 (参数列表) (返回值列表){
函数体
}
这里主要说一下返回值列表:接受这几种类型,返回值类型列表,参数列表(变量名与类型的结合)
请注意:函数定义返回值时,函数体里面必须要有return 语句
其次,函数的左大括号必须写在func这一行的最后,不能另起一行来写。
2、编译、运行
go build 和go run 的区别:go build仅仅表示编译,不代表运行。而go run在编译的同时直接运行程序,常用于调试程序。
需要注意的点:go build后面如果是多个文件的话,依次跟上,比如:
a.go文件
package main
import (
"fmt"
)
func main() {
fmt.Println("hello world")
pkgfunc()
}
b.go文件
package main
import "fmt"
func pkgfunc(){
fmt.Println("hello hahaha")
}
编译:go build a.go b.go
运行:
PS D:\go> .\a.exe
hello world
hello hahaha
3、变量的声明
使用var关键字声明变量
var name type
比如:var name string 和var age int
基本类型如下所示:
bool
string
int、int8、int16、int32、int64
uint、uint8、uint16、uint32、uint64、uintptr
byte // uint8 的别名
rune // int32 的别名 代表一个 Unicode 码
float32、float64
complex64、complex128
批量声明变量
var (
name string
age int
id []flost32
d func() bool
)
简短格式声明
名字 := 表达式
但是有一些限制:
1、定义变量的同时进行初始化
2、不能提供数据类型
3、只能在函数内部使用
比如下面这个例子
func main(){
x := 100
a,s := 1, 'abc'
}
简短变量声明被广泛用于大部分的局部变量的声明和初始化。var 形式的声明语句往往是用于需要显式指定变量类型地方,或者因为变量稍后会被重新赋值而初始值无关紧要的地方
4、变量初始化的标准格式
var 变量名 类型 = 表达式
比如:
var hp int = 100
表示声明一个变量hp,是int整型,值是100
编译器推到类型的格式:
# 可以简写
var hp = 100
也就是将int省略,编译器根据右值推导hp变量的类型
举几个例子:
var a = 40
var b= 20
var c float32 = 0.17
var d = float32(a-b) * c
像第三行的float32就不能省略,因为默认编译器当成64位的,但是这样子明显不是我们要的效果,此处需要强转,也就是手动声明float32
第四行的也是需要强转,因为需要把结果进行转化成float32类型的。
短变量声明并初始化
var的变量声明使用短格式有个注意事项
hp := 100
也就是这个hp变量不能在此之前被提前定义过,否则会编译错误。
短声明的形式在开发中的例子较多,比如
conn, err := net.Dial("tcp", "127.0.0.1:8080")
这种形式使用的比较多。
5、匿名变量
匿名变量在go语言里面使用 " _ "表示,这是一个特殊的标识符,被称为空白标识符。任何类型都可以赋值给这个变量,但是任何变量的值都将被抛弃。
package main
import "fmt"
func GetData() (int,int){
return 100,200
}
func main(){
a, _ := 100,200
_, b :=100,200
fmt.Println(a,b)
}
特点:匿名变量不占用内存空间,不会分配内存
6、变量的作用域
根据变量定义位置的不同,可以分为三种情况:
a、函数内定义的变量称为局部变量
b、函数外定义的变量称为全局变量
c、函数定义中的变量称为形式参数
先来讲讲局部变量
局部变量只作用在函数体内,且不可跨函数使用。
package main
import "fmt"
func main(){
var a int = 3
var b int = 4
c := a + b
fmt.Printf("a = %d, b = %d, c = %d\n",a,b,c)
}
讲讲全局变量
全局变量只需要在一个源文件中定义即可,就可以在所有的源文件中使用。其中不包含这个全局变量的源文件需要使用import关键字引入全局变量所在的源文件之后才能使用这个全局变量
注意:全局变量必须使用var关键字进行定义。如果想要在外部包中引入变量的首字母必须是大写
package main
import "fmt"
var c int
func main(){
c = 100
fmt.Printf("c = %d\n",c)
var a int = 3
var b int = 4
c := a + b
fmt.Printf("a = %d, b = %d, c = %d\n",a,b,c)
}
运行结果如下所示:
PS D:\go> go build c.go
PS D:\go> .\c.exe
c = 100
a = 3, b = 4, c = 7
再来讲讲形式参数
形式参数只在函数调用时才会生效。函数调用结束后销毁。形式参数只会作为函数的局部变量来使用。
package main
import (
"fmt"
)
var a int = 13
func main(){
var a int = 3
var b int = 4
fmt.Printf("main()函数中 a = %d\n",a)
fmt.Printf("main()函数中 b = %d\n",a)
c := sum(a,b)
fmt.Printf("main()函数的两个值得结果是: %d\n",c)
}
func sum(a, b int) int {
num := a + b
return num
}
看结果
PS D:\go> go build c.go
PS D:\go> .\c.exe
main()函数中 a = 3
main()函数中 b = 3
main()函数的两个值得结果是: 7
讲到这里,顺便说一下,对于函数里面return有两个返回值的例子
package main
import (
"fmt"
)
func main(){
var a,b int
a = 10
b = 20
var c float32
c = 0.15
d := float32(a) + c
fmt.Printf("Hello world: %d,%d,%f,%f\n",a,b,c,d)
e := Apple(a,b)
fmt.Printf("sum: %d\n",e)
f,g := Egg()
fmt.Printf("a is %d,b is %d",f,g)
}
func Apple(a,b int) int {
num := a + b
return num
}
func Egg() (int,int){
a := 11
b := 12
return a,b
}
7、讲讲整型
go语言里面的数值类型分为以下几种:整数、浮点数、复数
对于整数分为:有符号整数和无符号整数 。其中有符号整数类型有int8(bit)、int16、int32、int64 ,与之相反的无符号整数类型是:unit8、unit16、unit32、unit64 。
还有两种是int和unit类型,分别对应特定CPU平台的字长(机器字大小),实际开发中由于编译器和计算机硬件的不同,int和unit所能表示的整数大小会在32bit 和64bit之间变化。
注意:int和int32中,虽然int类型可能是32bit,但是需要把int类型当作int32类型使用的时候必须显式的对类型进行转换。
go语言中有符号整数采用2的补码形式表示,也就是最高bit位用来表示符号位,一个n-bit的有符号数的取值范围是从-2^(n-1) 到2^(n-1)-1。比如:int8表示是-128到127,int32是-2147 483648到2147483647。那么一个n-bit的无符号整数的所有bit位都用于表示非负数,取值范围是0到2n-1,比如:unit8类型的整数的取值范围是从0到255,unit32类型取值范围是从0到4 294967295
适合用int或者unit的场合:
切片和map的元素数量可以用int来表示
在二进制传输、读写文件的结构描述时,为了保持文件的结构不会受到不同编译目标平台字节长度的影响,不要使用 int 和 uint
8、讲讲浮点类型
go语言提供两种精度的浮点数float32和float64,由IEEE754浮点数国际标准定义。
说实话float32能取到的最大数值和最小数值看不懂。
一个float32类型的浮点数可以提供大约6个十进制数的精度,而float64则可以提供约15个十进制数的精度。
浮点数在声明的时候可以只写整数部分或者小数部分
package main
import "fmt"
func main(){
var a = .71828
var b = 1.
fmt.Printf("hello %f\n",a)
fmt.Printf("hello %f\n",b)
}
可以使用%f来控制保留几位小数
package main
import (
"fmt"
"math"
)
func main(){
fmt.Printf("%f\n",math.Pi)
fmt.Printf("%.2f\n",math.Pi)
}
9、bool类型
go语言里面的bool类型数据只能取值true和false,占用一个字节,默认值是false
bool类型主要用于逻辑运算,用于程序流程控制和条件判断。
package main
import (
"fmt"
)
func main(){
var isOk bool
var isOnline = true
fmt.Println("IsOK = ",isOk," IsOnline = ",isOnline)
}
10、字符串
计算字符串长度
package main
import (
"fmt"
)
func main(){
tip1 := "I am Mike"
tip2 := "中国"
fmt.Println(len(tip1))
fmt.Println(len(tip2))
}
计算结果如下所示:
PS D:\go> .\d.exe
9
6
为什么"中国"显示的6是指字节的个数,并不是字符的个数,按照习惯应该是2,这是因为go语言的字符串是以UTF-8格式保存,每个中文占用3个字节。
如果我们希望按照习惯来显示,就使用UTF-8包的提供的RuneCountlnString()函数,统计Uncode字符数量
package main
import (
"fmt"
"unicode/utf8"
)
func main(){
tip1 := "I am Mike"
tip2 := "中国"
fmt.Println(len(tip1))
fmt.Println(utf8.RuneCountInString(tip2))
}
总结:
ASCII 字符串长度使用 len() 函数。
Unicode 字符串长度使用 utf8.RuneCountInString() 函数
字符串之获取每一个字符串元素
a、遍历每一个ASCII字符
package main
import (
"fmt"
)
func main(){
tip1 := "I am Mike"
for i :=0;i<len(tip1);i++ {
fmt.Printf("字符是: %c, 对应的ASCII数值是: %d\n",tip1[i],tip1[i])
}
}
运行结果如下:
PS D:\go> go build d.go
PS D:\go> .\d.exe
字符是: I, 对应的ASCII数值是: 73
字符是: , 对应的ASCII数值是: 32
字符是: a, 对应的ASCII数值是: 97
字符是: m, 对应的ASCII数值是: 109
字符是: , 对应的ASCII数值是: 32
字符是: M, 对应的ASCII数值是: 77
字符是: i, 对应的ASCII数值是: 105
字符是: k, 对应的ASCII数值是: 107
字符是: e, 对应的ASCII数值是: 101
b、带有中文字符的遍历
如果带有中文字符,就不能像上面那样直接遍历了,因为取不出来,需要按照unicode字符遍历字符串
package main
import (
"fmt"
)
func main(){
tip1 := "名字是: Mike"
for a,s := range tip1{
fmt.Printf("当前字节: %d ",a)
fmt.Printf("字符是: %c %d\n",s,s)
}
// fmt.Printf(range tip1)
}
其中a变量表示字节的个数,可以使用匿名变量 _ 代替。
结果是:
PS D:\go> go build d.go
当前字节: 0 字符是: 名 21517
当前字节: 3 字符是: 字 23383
当前字节: 6 字符是: 是 26159
当前字节: 9 字符是: : 58
当前字节: 10 字符是: 32
当前字节: 11 字符是: M 77
当前字节: 12 字符是: i 105
当前字节: 13 字符是: k 107
总结:
ASCII 字符串遍历直接使用下标。
带有中文的Unicode 字符串遍历用 for range。
c、截取字符串,返回字符串的索引值
package main
import (
"fmt"
"strings"
)
func main(){
name := "Chenchaofeng"
i := strings.Index(name, "chao")
fmt.Println(i)
}
结果如下:
PS D:\go> .\d.exe
4
注意:go语言里面字符串必须使用双引号,单引号是单个字符下才会用
转义符:
go语言里面的几个转义
\n: 换行
\t: 制表符
\\: \本身
\": 双引号本身
\': 单引号本身
输出一个路径的例子:
func main() {
fmt.Println("\"D:\\go\\cli\\01day.exe\"")
}
执行结果是
PS D:\go> .\a.exe
"D:\go\cli\01day.exe"
go语言里面输出多行字符串,使用反引号,比如:
func main() {
s1 := `
欢迎来到go的世界
带领你学习一切的知识点
`
fmt.Println(s1)
}
执行结果如下所示:
PS D:\go> .\a.exe
欢迎来到go的世界
带领你学习一切的知识点
看的出了是原样输出的。前面也有制表符的距离。我们上面的输出的路径也可以在不加转义符的前提下直接把这个路径写在反引号里面。
比如:
func main() {
s1 := `
"D:\go\cli\01day.exe"
`
fmt.Println(s1)
}
执行结果如下所示:
PS D:\go> .\a.exe
"D:\go\cli\01day.exe"
字符串功能性操作
字符串拼接
两种方法:
func main() {
name := "迈克"
world := "杰克"
//这是第一种方法
ss := name + world
fmt.Println(ss)
//第二种方法
ss1 := fmt.Sprintf("%s%s",name,world)
fmt.Println(ss1)
}
执行结果如下所示:
PS D:\go> .\a.exe
迈克杰克
迈克杰克
注意:两种方法的区别是,第一种实际上跟python差不多,通过+号实现,第二种是通过Sprintf的方式,将字符串拼接后再返回给新的变量,Sprinf和Prinf的区别是Sprintf是有返回值的,而Printf是直接打印结果。
字符串切割
func main() {
s3 := "a-b-c-d-e-f-g"
ret := strings.Split(s3, "-")
fmt.Println(ret)
s4 := "D:\\go\\cli\\a.go"
// 注意需要转义
ret1 := strings.Split(s4,"\\")
fmt.Println(ret1)
}
结果如下所示:
PS D:\go> .\a.exe
[a b c d e f g]
[D: go cli a.go]
看的出来返回的是数组的格式
字符串包含contains
func main() {
s3 := "你好中国"
//注意返回值是bool类型
fmt.Println(strings.Contains(s3, "中国"))
fmt.Println(strings.Contains(s3, "中华"))
}
效果如下所示:
PS D:\go> .\a.exe
true
false
判断以什么字符串开头hasprefix
func main() {
s3 := "你好中国"
//注意返回值是bool类型
// 判断前缀
fmt.Println(strings.HasPrefix(s3, "你好"))
// 判断后缀
fmt.Println(strings.HasSuffix(s3, "中华"))
fmt.Println(strings.HasSuffix(s3, "中国"))
}
执行结果如下所示:
PS D:\go> .\a.exe
true
false
true
判断字串出现的位置
func main() {
s3 := "abcdefb"
// 判断字符c第一次出现的位置
fmt.Println(strings.Index(s3, "c"))
// 判断字符b最后一次出现的位置
fmt.Println(strings.LastIndex(s3, "b"))
}
结果如下所示:
PS D:\go> .\a.exe
2
6
字符串join操作
注意:是对切片的一种操作,比如:
func main() {
s3 := "a-b-c-d-e-f-b"
s4 := strings.Split(s3, "-")
fmt.Println(strings.Join(s4, "+"))
}
结果如下所示:
PS D:\go> go build a.go
PS D:\go> .\a.exe
a+b+c+d+e+f+b
这里面再补充一个知识点,就是字符串和字符的区别是什么:
简单来说,字符串是string类型,而字符就是rune类型,rune是int32的别名,因此这就产生了一些特殊的场合必须使用字符类型,使用字符串类型反而会报错。
func main() {
// 讲解字符串和字符的区别
s1 := "Apple"
s2 := 'A'
s3 := "你好中国"
s4 := '中'
s5 := "中"
fmt.Printf("s1: %T s2: %T\n",s1,s2)
fmt.Printf("s3: %T s4: %T\n",s3,s4)
fmt.Printf("s5: %T\n",s5)
}
效果如下所示:
PS D:\go> .\a.exe
s1: string s2: int32
s3: string s4: int32
s5: string
字符串修改:
字符串没有直接修改的方式,需要转化成切片进行字符的替换
func main() {
s1 := "红萝卜"
s2 := []rune(s1) //把字符串强制转换成一个rune切片
s2[0] = '白'
fmt.Println(string(s2)) //把rune切片强制转化成字符串
}
效果如下所示:
PS D:\go> .\a.exe
白萝卜
11、数据类型转换
go语言不存在隐式类型转换,所有的转换必须时显式的声明
目前已知的有整型与浮点数之间转换,字符串和切片之间可以转换
类型B的值 = 类型B(类型A的值)
比如;
a := 5.0
b := int(a)
注意事项,最好是同类型之间的转换,比如float32与int类型相互转换,但是缺点是高精度转化成低精度的值会发生一定的精度丢失。不同类型之间的转换会报错,比如int和bool类型进行转换。
浮点数在转换成整型时,会将小数部分去掉,只保留整数部分,不存在四舍五入。
这里补充一个知识点,以往我们输出的是十进制数,这里补充如何输出十六进制数。
package main
import (
"fmt"
)
func main(){
var a float32 = 20.123
var b = int(a)
fmt.Printf("十六进制: 0x%x, 十进制是: %d",b,b)
}
输出结果如下所示:
PS D:\go> .\d.exe
十六进制: 0x14, 十进制是: 20
补充一个知识点: %p ,这是打印指针的地址
func main(){
var a int = 1
var b int = 1
fmt.Printf("%p %p",&a,&b)
}
结果是:0xc000012098 0xc0000120c0
12、指针
变量、指针和地址三者的关系:每个变量都拥有地址,指针的值就是地址。
* 代表指针。比如ptr := $v ,那么ptr的类型是*T ,称作T的指针类型,*代表指针。
从指针获取指针指向的值
package main
import (
"fmt"
)
func main(){
var name string = "The name is Mike"
ptr := &name
fmt.Printf("指针的类型是: %T\n",ptr)
fmt.Printf("指针内存的地址是: %p\n",ptr)
//取值
value := *ptr
fmt.Printf("值的类型是: %T\n", value)
fmt.Printf("值是: %s\n", value)
}
查看结果:
PS D:\go> .\e.exe
指针的类型是: *string
指针内存的地址是: 0xc00005a240
值的类型是: string
值是: The name is Mike
补充知识点:%T 表示取出类型
取地址操作符&和取值操作符是一对互补,&取出内存地址,取出地址执行的值。
变量、指针地址、指针变量、取地址、取值的相互关系和特性如下:
- 对变量进行取地址操作使用&操作符,可以获得这个变量的指针变量。
- 指针变量的值是指针地址。
- 对指针变量进行取值操作使用*操作符,可以获得指针变量指向的原变量的值。
使用指针修改值
演示一个通过指针修改值的例子,这个是修改指针的值的例子:
package main
import (
"fmt"
)
func Swap(a,b *int){
t := *a
*a = *b
*b = t
}
func main(){
var a int = 1
var b int = 2
a1 := &a
b1 := &b
Swap(a1,b1)
fmt.Printf("a: %d,b: %d",a,b)
}
查看下运行的结果
PS D:\go> .\e.exe
a: 2,b: 1
操作符作为右值时,意义是取指针的值,作为左值时,也就是放在赋值操作符的左边时,表示 a 指针指向的变量。其实归纳起来,操作符的根本意义就是操作指针指向的变量。当操作在右值时,就是取指向变量的值,当操作在左值时,就是将值设置给指向的变量
一个有趣的例子: 使用指针变量获取命令行的输入信息
package main
import (
"flag"
"fmt"
)
var mode = flag.String("mode","","process mode")
func main(){
flag.Parse()
fmt.Println(*mode)
}
运行结果:
PS D:\go> .\f.exe --mode=hello
hello
PS D:\go> .\f.exe -h
Usage of D:\go\f.exe:
-mode string
process mode
再写一个带有默认值的例子:
package main
import (
"flag"
"fmt"
)
var mode = flag.String("name","Mike","your name")
func main(){
flag.Parse()
fmt.Println(*mode)
}
效果如下所示:
PS D:\go> go build f.go
PS D:\go> .\f.exe --name=chaofeng
chaofeng
PS D:\go> .\f.exe
Mike
PS D:\go> .\f.exe -h
Usage of D:\go\f.exe:
-name string
your name (default "Mike")
使用new()声明一个指针
package main
import (
"fmt"
)
func main(){
nameptr := new(string)
*nameptr = "Mike"
fmt.Printf("nameptr: %p\n",nameptr)
fmt.Printf("*nameptr: %s\n",*nameptr)
}
看效果:
PS D:\go> .\f.exe
nameptr: 0xc00005a240
*nameptr: Mike
go语言使用函数返回一个指针的例子:
package main
import (
"fmt"
)
func main(){
res := testPtr()
fmt.Printf("res: %p, city: %s",res,*res)
}
func testPtr() *string {
city := "深圳"
ptr := &city
return ptr
}
效果如下:
PS D:\go> .\f.exe
res: 0xc00005a240, city: 深圳
类比:这种返回指针的操作在C语言里面错误的,只有go语言里面有这种操作。
补充:如果将局部变量赋值给全局变量,将会阻止GC对这个局部变量的回收,导致不必要的内存占用,从而影响程序的性能。
讲一个内存逃逸的例子,讲讲自己的理解
package main
import (
"fmt"
)
var name *int
func main(){
f()
fmt.Printf("name is: %d", *name)
}
func f(){
var x = 100
name = &x
}
查看效果
PS D:\go> .\f.exe
name is: 100
这里的函数f()里面的变量x存在逃逸,必须在堆上面分配,因为他在函数退出后依然可以通过包一级的name变量找到。虽然在函数内部定义的,用Go语言的术语说,这个局部变量x从函数 f() 中逃逸了。
所以在实际的开发中,不需要刻意的实现变量的逃逸行为,因为逃逸的变量需要额外的分配内存。
make和new的区别
1、make和new都是用来申请内存的
2、new很少用,一般用来给基本数据类型申请内存,string、int返回的是对应类型的指针(string、int)
3、make是用来给slice、map、chan申请内存的,make函数返回的是对应的这个三个类型本身
注意:职业生涯上new很少用。
13、常量和const关键字
常量使用关键字const定义,用于存储不会改变的数据。,常量是在编译时被创建的。即使在函数内部也是如此。接受的类型是bool、数字型、字符串型
# 显式定义:
const name string = "Mike"
# 隐式定义:
const name = "Mike"
注意:常量的值必须是能够在编译时就能够确定。
声明多个常量
const ( a = 10 b = 20)
14、运算符
+、-、*、/
注意:i++、i--在go语言里面是一个单独的语句。不能放在=号的右边赋值。
这里只讲一下按位与、或、异或操作
func main() {
// 练习一下按位与或这些知识点
// 5的二进制位是101, 2的二进制位是10
// 与:两位均为1才是1,位数不够用0先补上再计算
fmt.Println(5 & 2) // 000,结果是0
// 或,两位只要有一个是1就为1
fmt.Println(5 | 2) //111,结果是7
// ^: 按位异或(两位不一样则为1)
fmt.Println(5 ^ 2) // 111,结果是7
// << : 将二进制位左移指定位数,用0来补
fmt.Println(5 << 1) //1010,结果是10
// >>:将二进制位右移指定的位数
fmt.Println(5 >> 2) //101去掉2位变成1,结果是1
}
结果如下所示:
PS D:\go> .\a.exe
0
7
7
10
1
15、数组
go语言里面的数组在声明的时候同时需要定义长度,即长度也是数组的一部分。
func main() {
var a1 [3]bool
var a2 [5]bool
fmt.Printf("a1: %T a2: %T\n",a1,a2)
}
打印的结果是:
PS D:\go> .\a.exe
a1: [3]bool a2: [5]bool
看的出来打印的结果中都包含了数组的类型和长度,这点需要特别注意。
接下来对数组进行一个初始化。
对初始化也有一个特别的概念:
数组如果不初始化:默认元素都是零值(布尔类型位false,数字和浮点都是0,字符串是"")
func main() {
var a1 [3]bool
var a2 [5]bool
fmt.Printf("a1: %T a2: %T\n",a1,a2)
fmt.Println(a1,a2)
// 1、初始化方式1
a1 = [3]bool{true,false,true}
a2 = [5]bool{true,false,true,false,true}
fmt.Println(a1)
fmt.Println(a2)
//2、初始化方式2:根据初始值自动推断数组的长度是多少
a3 := [...]int{0,1,2,3,4,5,6,7}
fmt.Println(a3)
// 3、初始化方式3:根据索引来初始化
a4 := [5]int{0:1,3:15,4:2}
fmt.Println(a4)
}
效果如下所示:
PS D:\go> .\a.exe
a1: [3]bool a2: [5]bool
[false false false] [false false false false false]
[true false true]
[true false true false true]
[0 1 2 3 4 5 6 7]
[1 0 0 15 2]
遍历数组
func main() {
citys := [...]string{"北京", "上海","深圳"}
// 第一种写法使用for循环
for i:=0;i<len(citys);i++{
fmt.Println(citys[i])
}
fmt.Println("-------------------")
// 第二种写法
for i,v := range citys {
fmt.Println(i,v)
}
fmt.Println("-------------------")
//不过一般不用打印索引,可以使用匿名函数省略掉索引
for _,v := range citys {
fmt.Println(v)
}
}
效果如下所示:
PS D:\go> .\a.exe
北京
上海
深圳
-------------------
0 北京
1 上海
2 深圳
-------------------
北京
上海
深圳
多维数组
func main() {
var a [3][2]int
a = [3][2]int {
[2]int{1,2},
[2]int{3,4},
[2]int{5,6},
}
fmt.Println(a)
}
结果如下所示:
PS D:\go> .\a.exe
[[1 2] [3 4] [5 6]]
遍历数组
func main() {
var a [3][2]int
a = [3][2]int {
[2]int{1,2},
[2]int{3,4},
[2]int{5,6},
}
fmt.Println(a)
for i := 0;i< len(a);i++ {
fmt.Println(a[i])
for j := 0 ;j<len(a[i]);j++{
fmt.Println(a[i][j])
}
}
fmt.Println("---------------------")
for _,v := range a{
for _,h := range v{
fmt.Printf("%d ",h)
}
}
}
执行结果如下所示:
PS D:\go> .\a.exe
[[1 2] [3 4] [5 6]]
[1 2]
1
2
[3 4]
3
4
[5 6]
5
6
---------------------
1 2 3 4 5 6
练习一个数组的练习题
有一个数组[1,3,5,7,8],要求输出和为8的索引,比如(0,3),(1,2)
func main() {
var a = [5]int{1,3,5,7,8}
for i := 0;i<len(a);i++ {
for x := i + 1;x < len(a);x++{
if a[i] + a[x] == 8{
fmt.Printf("(%d, %d)\n",i,x)
}
}
}
}
结果如下所示:
PS D:\go> .\a.exe
(0, 3)
(1, 2)
15、切片slice
数组的长度是确定的,如果想要新增元素等操作是不可能的,所以有局限性,因此有了切片。
切片是一个拥有相同类型元素的额可变长度的序列,他是基于数组类型做的一层的封装,支持自动扩容。
切片是一个引用类型,他的内部结构包含地址、长度、容量。用于快速的操作一块数据集合。
举例子:
func main() {
var s1 []int //定义一个int类型的数组
var s2 []string //定义一个string类型的数组
fmt.Println(s1,s2) //会发现打印出来的是空值。而数组的话打印出来的是false、0这些
// 对切片进行初始化
s1 = []int{1,2,3}
s2 = []string{"北京","深圳","上海"}
fmt.Println(s1,s2)
}
查看一下效果
PS D:\go> .\a.exe
[] []
[1 2 3] [北京 深圳 上海]
切片里面可以和nil做比较
func main() {
var s1 []int //定义一个int类型的数组
var s2 []string //定义一个string类型的数组
fmt.Println(s1,s2) //会发现打印出来的是空值。而数组的话打印出来的是false、0这些
fmt.Println(s1 == nil)
fmt.Println(s2 == nil)
// 对切片进行初始化
s1 = []int{1,2,3}
s2 = []string{"北京","深圳","上海"}
fmt.Println(s1,s2)
fmt.Println(s1 == nil)
fmt.Println(s2 == nil)
}
查看下结果:
PS D:\go> .\a.exe
[] []
true
true
[1 2 3] [北京 深圳 上海]
false
false
看到结果是true,说明还没有开辟内存空间
切片的长度和容量
使用len()和cap()来查看
func main() {
var s1 []int
var s2 []string
s1 = []int{1,2,3,4}
s2 = []string{"北京","深圳","上海"}
fmt.Printf("长度是: %d, 容量是: %d\n",len(s1),len(s1))
fmt.Printf("长度是: %d, 容量是: %d",len(s2),len(s2))
}
结果如下所示:
PS D:\go> .\a.exe
长度是: 4, 容量是: 4
长度是: 3, 容量是: 3
由数组得到切片
func main() {
//由数组得到切片
var s1 = [...]int{1,3,5,7,9,11,13,15}
//切割数组,左闭右开
s2 := s1[0:4]
fmt.Println(s2)
s3 := s1[1:6]
fmt.Println(s3)
s4 := s1[:5] // [0:5]
s5 := s1[3:] // [3:len(s1)]
s6 := s1[:] // [0:len(s1)]
fmt.Println(s4,s5,s6)
}
结果如下所示:
PS D:\go> .\a.exe
[1 3 5 7]
[3 5 7 9 11]
[1 3 5 7 9] [7 9 11 13 15] [1 3 5 7 9 11 13 15]
长度和容量的区别是什么呢?
看个例子
func main() {
var s1 = [...]int{1,3,5,7,9,11,13,15}
s4 := s1[:5]
s5 := s1[3:6]
fmt.Println(len(s4),cap(s4))
fmt.Println(len(s5),cap(s5))
}
结果是:
PS D:\go> .\a.exe
5 8
3 5
切片的长度就是他元素的个数,而切片的容量是底层数组从切片的第一个元素到最后一个元素的数量,这个比较绕,需要理会一下。
使用make()函数构造切片
使用数组来创建切片是不灵活的,如果需要动态的创建一个切片,就可以使用内置的make()函数。格式如下:
make([]T, size, cap)
T是类型,size是长度,cap是切片的容量。
比如:
func main() {
s1 := make([]int, 5, 10)
fmt.Printf("s1=%v len(s1)=%d cap(s1)=%d",s1,len(s1),cap(s1))
}
结果如下所示:
PS D:\go> .\a.exe
s1=[0 0 0 0 0] len(s1)=5 cap(s1)=10
解释一下:make()来创建一个切片,切片的的底层还是数组,切片本身只是一个框架而已,用来框起来一段的内存。所以我们make()创建分片之后,底层还是转化成了数组,长度是5,容量cap是10
一句话,切片属于引用类型,真正的数据都是保存在底层数组里面的。
切片不能直接比较。
不能使用== 号判断两个切片是否含有相同的元素,切片最多也就是和nil做一个比较罢了。
一个nil值的切片并没有底层数组,一个nil值的切片的长度和容量都是0,但是我们不能说长度和容量都是0的切片一定是nil,比如:
func main() {
var s1 []int
fmt.Println(s1 == nil)
s2 := []int{}
fmt.Println(s2 == nil)
s3 := make([]int, 0, 0)
fmt.Println(s3 == nil)
}
结果如下所示:
PS D:\go> .\a.exe
true
false
false
主要疑惑的地方是s2和s3的判断结果,原因是他们两个都进行了定义并初始化,我们知道数组进行初始化,bool默认是false,数字类型是0,string是"",虽然没有值,但是进行了初始化,已经分配了内存空间,但是s1仅仅表示声明,并没有开辟内存空间,而nil就表示0,并且没有分配内存空间,所以只有s1是正确的,结果为true。
切片的赋值和拷贝
切片是不保存数据的,切片只是一个框,真正保存内存的是底层的数组
func main() {
s1 := []int{1,2,3,4}
s2 := s1 //s3和s4都指向了同一个底层数组,
fmt.Println(s1)
s1[2] = 100
fmt.Println(s1, s2)
}
结果如下所示:
PS D:\go> .\a.exe
[1 2 3 4]
[1 2 100 4] [1 2 100 4]
切片的遍历
切片的遍历和数组的遍历都是一样的,可以使用for或者是range都是可以的。
这里不再演示了。
高级特性:切片扩容
16、使用append()方法给切片追加元素
append()方法可以给切片动态的增加元素。会产生一个问题就是底层数组会发生更换,扩容操作一般就是发生在append()函数调用,比如:
func main() {
s1 := []int{1,2,3,4,5}
fmt.Printf("切片是: %v, 长度是: %d, 容量是: %d\n",s1,len(s1),cap(s1))
// append追加元素,必须用原来的切片变量接受返回值。
// 必须用变量接受append的返回值
s1 = append(s1,6)
fmt.Printf("切片是: %v, 长度是: %d, 容量是: %d",s1,len(s1),cap(s1))
}
结果所示:
PS D:\go> .\a.exe
切片是: [1 2 3 4 5], 长度是: 5, 容量是: 5
切片是: [1 2 3 4 5 6], 长度是: 6, 容量是: 10
主要的疑惑点是容量为什么是翻倍了,解释如下所示(大概掌握即可):
append()追加两个元素
func main() {
s1 := []int{1,2,3,4,5}
fmt.Printf("切片是: %v, 长度是: %d, 容量是: %d\n",s1,len(s1),cap(s1))
s1 = append(s1,6,7,8,9)
fmt.Printf("切片是: %v, 长度是: %d, 容量是: %d\n",s1,len(s1),cap(s1))
// 将另一个切片追加到s1切片上。
s2 := []int{10,11,12,13}
s1 = append(s1,s2...) // ...表示拆开,变成10、11、12、13挨个追加
fmt.Printf("切片是: %v, 长度是: %d, 容量是: %d",s1,len(s1),cap(s1))
}
效果如下:
PS D:\go> .\a.exe
切片是: [1 2 3 4 5], 长度是: 5, 容量是: 5
切片是: [1 2 3 4 5 6 7 8 9], 长度是: 9, 容量是: 10
切片是: [1 2 3 4 5 6 7 8 9 10 11 12 13], 长度是: 13, 容量是: 20
copy()复制切片,复制的是切片的空间。
func main() {
s1 := []int{1,3,5}
s2 := s1 // 赋值操作
var s3 = make([]int,3,3) //申请的时候必须要大于源切片的长度才可以。
copy(s3,s1) //copy(dest,src)
fmt.Println(s1,s2,s3)
s1[0] = 100
fmt.Println(s1,s2,s3)
}
结果如下所示:
PS D:\go> .\a.exe
[1 3 5] [1 3 5] [1 3 5]
[100 3 5] [100 3 5] [1 3 5]
切片删除元素
func main() {
s1 := []int{1,3,5,7,9}
// 将s1切片的索引为2的元素5给删除掉
s1 = append(s1[:2],s1[3:]...)
fmt.Println(s1)
}
结果如下所示:
PS D:\go> .\a.exe
[1 3 7 9]
可以看到元素5被删除掉了。
讲一个切片和数组区别的案例,
func main() {
s1 := [...]int{1,2,3,4,5} //数组
s2 := s1[:] //切片
//delete一个元素
s2 = append(s2[:1],s2[2:]...) //修改了底层数组
fmt.Println(s1,s2)
fmt.Println(len(s1),cap(s1))
fmt.Println(len(s2),cap(s2))
//修改底层数组
s2[0] = 100
fmt.Println(s2)
}
结果如下所示:
PS D:\go> .\a.exe
[1 3 4 5 5] [1 3 4 5]
5 5
4 5
[100 3 4 5]
补充一个删除两个元素的例子:
func main() {
s1 := [...]int{1,2,3,4,5,6,7,8}
s2 := s1[:]
s2 = append(s2[:3],s2[5:]...)
fmt.Println(s1,s2)
}
结果如下所示:
PS D:\go> .\a.exe
[1 2 3 6 7 8 7 8] [1 2 3 6 7 8]
17、map是一种无序的key-value的数据结构,map是引用类型,必须初始化才能使用
map语法定义: map[keyType]ValueType
map类型的变量默认初始化值是nil,使用make()函数初始化来分配内存,语法是:
make(map[keyType]ValueType, [cap])
cap在map初始化的时候最好指定一个合适的容量
例子:
func main() {
score := make(map[string]int,10)
score["张三"] = 90
score["李四"] = 100
fmt.Println(score)
fmt.Println("---------------------")
fmt.Println(score["张三"])
//打印一个不存在的值则输出0
fmt.Println(score["王五"])
}
效果如下所示:
PS D:\go> .\a.exe
map[张三:90 李四:100]
---------------------
90
0
判断某个键是否存在
func main() {
score := make(map[string]int,10)
score["张三"] = 90
score["李四"] = 100
value, bool := score["张三"]
if bool{
fmt.Println(value)
}else{
fmt.Println("没有这个人")
}
// 第一个返回值是value,第二个是bool值
value1, bool1 := score["王五"]
if bool1{
fmt.Println(value1)
}else{
fmt.Println("没有这个人")
}
}
效果如下所示:
PS D:\go> .\a.exe
90
没有这个人
map的遍历
func main() {
score := make(map[string]int,10)
score["张三"] = 90
score["李四"] = 100
//遍历map
for k, v :=range score {
fmt.Println(k,v)
}
}
效果如下所示:
PS D:\go> .\a.exe
张三 90
李四 100
delete()函数删除map
func main() {
score := make(map[string]int,10)
score["张三"] = 90
score["李四"] = 100
// 删除一个存在的元素
delete(score, "张三")
fmt.Println(score)
// 删除一个不存在的元素
delete(score, "王五")
fmt.Println(score)
}
效果如下所示:
PS D:\go> .\a.exe
map[李四:100]
map[李四:100]
看的出来删除一个不存在的元素结果是不报错的,也没有提示
元素类型为map的切片
func main() {
// 元素类型为map的切片
var s1 = make([]map[int]string,10,10)
// 没有对内部的map做初始化,现在做一下
s1[0] = make(map[int]string, 2)
s1[0][0] = "北京"
s1[0][1] = "上海"
fmt.Println(s1)
}
效果如下所示:
PS D:\go> .\a.exe
[map[0:北京 1:上海] map[] map[] map[] map[] map[] map[] map[] map[] map[]]
值为切片类型的map
func main() {
// 值为切片类型的map
var s1 = make(map[string][]int,1)
s1["北京"] = []int{1,2,3}
fmt.Println(s1)
}
效果如下所示:
PS D:\go> .\a.exe
map[北京:[1 2 3]]
闭包的例子:
// 外层为固定值,内层为变化的值
func main(){
s1 := makesuffixfunc(".txt")
s2 := makesuffixfunc(".jpg")
fmt.Println(s1("apple"))
fmt.Println(s1("apple.txt"))
fmt.Println(s2("egg"))
fmt.Println(s2("egg.jpg"))
}
func makesuffixfunc(suffix string) func(string)string{
return func(name string) string{
if !strings.HasSuffix(name,suffix){
return name + suffix
}
return name
}
}
效果如下所示:
PS D:\go> .\c.exe
apple.txt
apple.txt
egg.jpg
egg.jpg
一个比较典型的例子
func calc(base int)(func(int) int,func(int)int) {
add := func(i int) int{
base += i
return base
}
sub := func(i int) int{
base -= i
return base
}
return add,sub
}
func main(){
f1,f2 := calc(10)
fmt.Println(f1(1),f2(2))
fmt.Println(f1(3),f2(4))
}
效果如下所示:
PS D:\go> .\c.exe
11 9
12 8
为什么是11和9这两个值呢?这是因为这行代码f1,f2 := calc(10) 这行代码有相互关联的作用,相当于内存地址分配的是一个地址,那么f1()函数执行完成之后,f2()函数继续执行,拿的是变化后的base的变量。或者说f1()函数改的是公用的变量base。
fmt.Println(f1(1),f2(2)) 执行完后再执行fmt.Println(f1(3),f2(4)) 依然拿得是base的变量。
异常机制:
recover()和panic()修复函数
func A(){
fmt.Println("A")
}
func B(){
defer func(){
err := recover()
if err != nil{
fmt.Println("修复完毕")
}
}()
panic("B()函数出现错误") //出现错误后执行defer的recover修复函数
fmt.Println("B")
}
func C(){
fmt.Println("C")
}
func main(){
A()
B()
C()
}
效果如下所示:
PS D:\go> .\c.exe
A
修复完毕
C
注意:recover()函数只有在defer调用的函数中有效。其次,recover()函数必须要在panic的函数之上,否则执行不到。
Go语言之string、int、int64互相转换
//string到int
int,err:=strconv.Atoi(string)
//string到int64
int64, err := strconv.ParseInt(string, 10, 64)
//int到string
string:=strconv.Itoa(int)
//int64到string
string:=strconv.FormatInt(int64,10)
//string到float32(float64)
float,err := strconv.ParseFloat(string,32/64)
//float到string
string := strconv.FormatFloat(float32, 'E', -1, 32)
string := strconv.FormatFloat(float64, 'E', -1, 64)
// 'b' (-ddddp±ddd,二进制指数)
// 'e' (-d.dddde±dd,十进制指数)
// 'E' (-d.ddddE±dd,十进制指数)
// 'f' (-ddd.dddd,没有指数)
// 'g' ('e':大指数,'f':其它情况)
// 'G' ('E':大指数,'f':其它情况)
Go语言之从键盘输入
inputreader := bufio.NewReader(os.Stdin)
fmt.Println("请输入姓名: ")
inputname,_ := inputreader.ReadString('\n')
fmt.Println("请输入存的金额: ")
fmt.Scanln(&inputmoney)
// inputmoney,_ := inputreader.ReadString('\n')
fmt.Println("请输入取款金额: ")
fmt.Scanln(&outputmoney)
// outputmoney,_ := inputreader.ReadString('\n')
n1,_ := strconv.Atoi(inputmoney)
n2,_ := strconv.Atoi(outputmoney)
inputname,_ := inputreader.ReadString('\n') 有一个缺点是inputname这个变量也带上了换行符,因此下面的strconv.Atoi() 将字符串的10转化成数字10,会发现转化后会是0,感觉这是一个不足,所以我就换成了fmt.Scanln() 函数来进行键盘输入。
一个银行取款的闭包的例子
var (
inputname,inputmoney,outputmoney string
)
func main(){
inputreader := bufio.NewReader(os.Stdin)
fmt.Println("请输入姓名: ")
inputname,_ := inputreader.ReadString('\n')
fmt.Println("请输入存的金额: ")
fmt.Scanln(&inputmoney)
// inputmoney,_ := inputreader.ReadString('\n')
fmt.Println("请输入取款金额: ")
fmt.Scanln(&outputmoney)
// outputmoney,_ := inputreader.ReadString('\n')
n1,_ := strconv.Atoi(inputmoney)
n2,_ := strconv.Atoi(outputmoney)
_main(inputname,n1,n2)
}
func _main(name string, cq ,qq int){
f1,f2 := atm(name)
fmt.Println("当前存款余额后: ",f1(cq))
fmt.Println("取款后余额是: ",f2(qq))
}
func atm(name string)(func(int)int,func(int)int){
var money = 0
cunqian := func(money1 int)int{
money += money1
return money
}
quqian := func(money2 int)int{
money -= money2
return money
}
return cunqian,quqian
}
|
请发表评论