一:声明变量
在golang语言中声明变量的方式
package main
import "fmt"
func main() {
var a int = 3 //关键字 var + 变量名 + 变量指定类型 = 变量值
var b int //关键字 var + 变量名 + 变量指定类型(注意:当变量没赋值时是按照变量指定类型的默认值进行赋值,如 【var b int】最后输出的就是int类型的0 而【var b bool】则会输出bool类型的false )
var c = 4 //关键字 var + 变量名 = 变量值(注意:当变量未设置类型则会根据变量值自行补充,如【var c = 4】则c的类型为int 而【var c = "hello"】c的类型则为string)
var d, e int = 1, 2 //这是在第一个声明的基础上进行多个变量声明
var f, g int //这是在第二个声明基础上进行多个变量声明
var h, i = 7, 8 //这是在第三个声明基础上进行多个变量声明
//输出
fmt.Println(a, b, c, d, e, f, g, h, i)
//输出的值为 3 0 4 1 2 0 0 7 8
}
注意:在方法中设置变量的时候,一定要在方法体中用到,否则会报错(如果 var c int 在方法中却没有使用,则运行程序会报错!)!! -- go的全局变量不限制,仅校验方法中的变量
go中除了用 var 关键字声明的变量外,还提供了 := 的方式声明未声明过的变量。
package main
import "fmt"
func main() {
a := 3
b := "hellow"
c, d := 8, "word"
//输出
fmt.Println(a, b, c, d)
//输出的值为 3 hellow 8 word
}
注意::= 只能用于声明 未被声明过的变量( 如声明 var a int = 3 后再 a := 4 就会报错)
二:声明常量
常量的声明可以参考变量声明的第一部分-此处省略
const a int = 3 //关键字 const + 常量名 + 常量类型 = 常量值
注意:常量在方法中声明不校验是否调用、
除了上面的常量声明方式外,常量还可用作枚举
package main import "fmt" func main() { const ( Unknown = 0 Female = 1 Male = 2 ) //输出 fmt.Println(Unknown, Female, Male) //输出值为 0 1 2 }
在常量声明时,可以引入"unsafe" 包使用 函数计算表达式,注意:必须是系统内置函数
package main
import "fmt"
import "unsafe"
func main() {
const (
a = "abc"
b = len(a)
c = unsafe.Sizeof(a)
)
const e string = "abg"
const f int = len(a)
const g = unsafe.Sizeof(a)
//输出
fmt.Println(a, b, c, e, f, g)
//输出值为 abc 3 16 abg 3 16
}
相对于常量声明中 枚举类型 有个 iota 用法 ,就是 从0开始依次递增
package main
import "fmt"
func main() {
const (
a = iota //0
b //1
c //2
d = "ha" //独立值,iota += 1
e //"ha" iota += 1
f = 100 //iota +=1
g //100 iota +=1
h = iota //7,恢复计数
i //8
)
fmt.Println(a,b,c,d,e,f,g,h,i)
}
三、运算符
go的运算符和其他编程语言基本一致
+(加)、-(减)、*(乘)、/(除)、%(取余数)、++(自增)、--(自减)、==(相等)、!=(不等)、>(大于)、<(小于)、>=(大于等于)、<=(小于等于)、&&(与)、||(或)、!(非)等等
这里着重记录一下 位运算 和 其他运算符 (了解即可,日常业务中很少用到)
位运算符:&(二进制相与)、|(二进制相或)、^(二进制相异)、<<(二进制左移)、>>(二进制右移)
package main
import "fmt"
func main() {
var a uint = 60 /* 60 = 0011 1100 */
var b uint = 13 /* 13 = 0000 1101 */
var c uint = 0
c = a & b /* 12 = 0000 1100 */ //二进制相与,当二进制码对应位都为1时记录为1
fmt.Printf("第一行 - c 的值为 %d\n", c )
c = a | b /* 61 = 0011 1101 */ //二进制相或,当二进制码对应位有一位为1时记录为1
fmt.Printf("第二行 - c 的值为 %d\n", c )
c = a ^ b /* 49 = 0011 0001 */ //二进制相异,当二进制码对应位值不同时记录为1
fmt.Printf("第三行 - c 的值为 %d\n", c )
c = a << 2 /* 240 = 1111 0000 */ //二进制左移,将左侧丢弃,右侧填补为0,<<2就是丢弃两位
fmt.Printf("第四行 - c 的值为 %d\n", c )
c = a >> 2 /* 15 = 0000 1111 */ //二进制右移,将右侧丢弃,左侧填补为0
fmt.Printf("第五行 - c 的值为 %d\n", c )
}
其他运算符:&(获取变量存储地址)、*(指针变量)
package main
import "fmt"
func main() {
var a int = 4
var ptr *int
/* & 和 * 运算符实例 */
ptr = &a /* 'ptr' 包含了 'a' 变量的地址 */
fmt.Printf("a 的值为 %d\n", a); //输出a变量的值 4
fmt.Printf("a 的值为 %d\n", &a); //输出a变量的变量存储地址,能粗浅理解为数据库的索引
fmt.Printf("*ptr 为 %d\n", *ptr); //输出的是指针变量下的值 4
fmt.Printf("ptr 为 %d\n", ptr); //输出指针变量的存储地址
}
在go中也有其他语言的 if、if...else..、switch、等。
也有:for 循环和嵌套,有 break、continue、goto语句。
注意:在go中没有while循环
四、函数、方法声明和调用
package main
import "fmt"
func main() {
var a, b int = 4,5
var c int
var s string
c = sum(a,b)
s = fmt.Sprintf("获取a+b=%d",c)
fmt.Println(s)
}
func sum(x int,y int) int{ //声明函数 关键字func + 函数名称 + 函数参数和参数类型 + 返回类型
return x+y
}
注意:声明函数的时候,必须设定参数类型和返回值类型!
这里记录一下 函数多个返回值 和 函数闭包(匿名函数) 的用法
package main
import "fmt"
func main() {
a, b := swap("Google", "Runoob")
fmt.Println(a, b)
}
func swap(x, y string) (string, string) { //设定两个string类型的返回值
return y, x
}
package mainimport "fmt"
func main() {
// 定义一个匿名函数并将其赋值给变量add
add := func(a, b int) int {
return a + b
}
// 调用匿名函数
result := add(3, 5)
fmt.Println("3 + 5 =", result)
// 在函数内部使用匿名函数
multiply := func(x, y int) int {
return x * y
}
product := multiply(4, 6)
fmt.Println("4 * 6 =", product)
// 将匿名函数作为参数传递给其他函数
calculate := func(operation func(int, int) int, x, y int) int {
return operation(x, y)
}
sum := calculate(add, 2, 8)
fmt.Println("2 + 8 =", sum)
// 也可以直接在函数调用中定义匿名函数
difference := calculate(func(a, b int) int {
return a - b
}, 10, 4)
fmt.Println("10 - 4 =", difference)
}
再记录一下 go 中的方法
package main
import (
"fmt"
)
/* 定义结构体 */
type Circle struct {
radius float64
}
func main() {
var c1 Circle
c1.radius = 10.00
fmt.Println("圆的面积 = ", c1.getArea())
}
//该 method 属于 Circle 类型对象中的方法
func (c Circle) getArea() float64 {
//c.radius 即为 Circle 类型对象中的属性
return 3.14 * c.radius * c.radius
}
注意:从上面的例子中我们可以看出,在go中 方法和函数是不同的。方法在定义的时候会在 func 关键字后面 方法名的前面 设置一个接收器( c Circle) 该接收器可在方法中调用
五、变量作用域
函数方法外面的是全局变量,方法或函数内的则是局部变量,局部变量优先级大于全局变量。局部变量除非作为参数传入另一个方法或函数中,否则两个方法或函数中的局部变量不能共用。
六、数组
在go中初始化数组,需要明确个数和类型
var a [10]int //初始化数组 关键字 var + 数组名 + [数组元素个数]数组元素类型
var a = [5]int{1,2,3,4,5} //初始化数组 a 并赋值
a := [5]int{1,2,3,4,5} //省略关键字 var 初始化数组 a 并赋值
var a = [...]int{1,2,3} //省略数组元素个数,通过"..."程序会自动通过赋值确定元素个数
a := [...]int{1,2,3} //和上面同理
var a []int //设置空切片a 注意:不指定长度的数组都是切片,第七点记录切片
a := []int{} //设置空切片a
多维数组也同理 var a [][]int 初始化即可
package main
import "fmt"
func main() {
// Step 1: 创建数组
values := [][]int{} //创建二维空数组
// Step 2: 使用 append() 函数向空的二维数组添加两行一维数组
row1 := []int{1, 2, 3}
row2 := []int{4, 5, 6}
values = append(values, row1)
values = append(values, row2)
// Step 3: 显示两行数据
fmt.Println("Row 1")
fmt.Println(values[0])
fmt.Println("Row 2")
fmt.Println(values[1])
// Step 4: 访问第一个元素
fmt.Println("第一个元素为:")
fmt.Println(values[0][0])
}
七、切片
相较于数组的长度不可改变,go还提供了切片的类型也可以称之为 动态数组,即它的长度不固定,可以直接追加元素提升切片的元素数
通过**make()**函数来创建切片
var slice []type = make([]type, len) //注意:len只是定义切片的初始长度 可以为0
slice := make([]type, len)
如:
slice1 := make([]int,0) //这行代码创建了个长度为0 的slice1 切片
除了用make() 函数来创建切片外
slice2 := []int{} 和 var slice2 []int 也是初始化了一个切片
对于数组来说,切片除了不用固定长度这一个优点外,还有一个切片赋值的功能
package main
import "fmt"
func main() {
arr :=[]int {1,2,3 } //初始化一个切片 a 里面有三个值
s := arr[0:2] //将切片下标0-2 的值 赋值给s
fmt.Println(s) //输出 [1 2]
fmt.Println(len(arr)) //
fmt.Println(cap(arr)) //
}
切片除了通过[:]来获取指定下标的值,由于切片有索引,所以go很多内置函数都可作用于切片上
如:len()//获取切片长度 cap() //计算容量获得切片最大长度
八、range的用法
当需要循环数组、切片、集合等数据时,可以用for + range 的关键字来遍历。
package main
import "fmt"
var pow = []int{1, 2, 4, 8, 16, 32, 64, 128}
func main() {
for i, v := range pow { //应用range时,会直接将循环中的key值提供给i,value值提供给v(i,v是变量类型,可以设置成你熟悉的变量名 如:k,v 也可以)
fmt.Printf("2**%d = %d\n", i, v)
}
}
除了将 pow 的值完全按照 key-value 的形式完全赋值给 i,v 外,如果只想要key,则可以省略v 如:for i := range pow{ 或者用 _ 代替 如 for i,_ := range pow {
当然,如果只想要value 不要key时也可以用 _ 代替 如: for _,v := range pow{
注意://range也可以用来枚举 字符串。第一个参数是字符的索引,第二个是字符(Unicode的值)本身
九、map的用法
Map 是一种集合,所以我们可以像迭代数组和切片那样迭代它。不过,Map 是无序的,遍历 Map 时返回的键值对的顺序是不确定的。
在获取 Map 的值时,如果键不存在,返回该类型的零值,例如 int 类型的零值是 0,string 类型的零值是 ""。
Map 是引用类型,如果将一个 Map 传递给一个函数或赋值给另一个变量,它们都指向同一个底层数据结构,因此对 Map 的修改会影响到所有引用它的变量。
// 创建一个空的 Map
m := make(map[string]int)
// 创建一个初始容量为 10 的 Map
m := make(map[string]int, 10)
// 使用字面量创建 Map m := map[string]int{ "apple": 1, "banana": 2, "orange": 3, }
// 获取键值对 v1 := m["apple"] //map 就和 php中的数组对象类似,用值的时候直接 变量[key值] 即可 v2, ok := m["pear"] // 如果键不存在,ok 的值为 false,v2 的值为该类型的零值
Map很像php 中的数组对象类型,但是Map是引用类型,要注意改变其中一个值时 其他引用过Map的变量都会更改
package main
import "fmt"
func main() {
s := map[string]string{ //初始化一个map数据
"one" : "one",
"two" : "two",
"three" : "three",
}
c := s //将 s 赋值给 c
a := s["two"]
b := s["two"]
a = "for"
fmt.Println(a, b, s, c) //输出值 for two map[one:one three:three two:two] map[one:one three:three two:two]
s["two"] = "fiv"
fmt.Println(a, b, s, c) //for two map[one:one three:three two:fiv] map[one:one three:three two:fiv]
}
从上面的示例可以看出,当将整个map类型的s赋值给c时,修改s 的值,c同样会变化!
delete() 函数可以删除map中的值 delete(map变量,需要删除的key值)
十、变量类型转换
1、数值类型转换
var a float64 = 3.7415926 //定义float64类型a为3.7415926
var b int = int(a) //将a转变成int类型赋值给b
fmt.Println(a, b) //输出结果 a=3.7415926 b=3 从float转化为int时,会直接舍去小数点后的值
注意:go 不支持隐式转换类型,比如 :
package main import "fmt" func main() { var a int64 = 3 var b int32 b = a fmt.Printf("b 为 : %d", b) }
会报错
cannot use a (type int64) as type int32 in assignment cannot use b (type int32) as type string in argument to fmt.Printf
但是如果改成 b = int32(a) 就不会报错了:
package main import "fmt" func main() { var a int64 = 3 var b int32 b = int32(a) fmt.Printf("b 为 : %d", b) }
2、字符串类型转换
var str string = "10" //定义字符串str var num int //初始化int类型num num, _ = strconv.Atoi(str) //将字符串转换为num的数据类型,其中 _ 位置取的是转换时的错误信息,如果想查看错误信息则需
var msg error //定义error类型 msg
num,msg = strconv.Atoi(str) //通过 msg 获取转换失败的信息
或者可以用下面方法直接获取:
number, msg = strconv.Atoi(str) //没有错误时 msg为nil
注意:使用strconv.Atoi() 函数时,要引入 strconv 包
3.接口类型转换
接口类型转换有两种情况**:类型断言** 和类型转换。
类型断言用于将接口类型转换为指定类型:
value.(T)
其中 value 是接口类型的变量, T 是要转换成的类型。
如果类型断言成功,它将返回转换后的值和一个布尔值,这个布尔值表示转换是否成功。
package main
import "fmt"
func main() {
var i interface{} = "Hello, World" //定义接口变量i
str, ok := i.(string) //将i类型断言为字符串类型 返回转换后的值和断言结果
if ok { //类型断言成功
fmt.Printf("'%s' is a string\n", str)
} else { //类型断言失败
fmt.Println("conversion failed")
}
}
类型转换用于将一个接口类型的值转换为另一个接口类型:
T(value)
T 是目标接口类型,value 是要转换的值。
注意:在类型转换中,我们必须保证要转换的值和目标接口类型之间是兼容的,否则编译器会报错。
package main
import "fmt"
type Write interface { //定义接口Write 接口参数为 []byte类型数据 返回值为 int 和 error类型
Write([]byte)(int,error)
}
type WriteString struct { //定义结构体WriteString 结构体属性 str 为字符串类型
str string
}
func (ws WriteString) Write(data []byte)(int, error){
ws.str += string(data)
return len(ws.str) , nil
}
func main(){
var w WriteString
ss := "hellow_Word!"
sb := []byte(ss)
fmt.Println(w.Write(sb))
}
十一、go实现并发
Go 允许使用 go 语句开启一个新的运行期线程, 即 goroutine,以一个不同的、新创建的 goroutine 来执行一个函数。 同一个程序中的所有 goroutine 共享同一个地址空间。
package main
import (
"fmt"
"time"
)
func say(s string) {
for i := 0; i < 5; i++ {
time.Sleep(100 * time.Millisecond)
fmt.Println(s)
}
}
func main() {
go say("world")
say("hello")
}
//输出的 world 和 hello 出现的顺序并不固定,也从侧面证明了 使用go关键字后确实 两次方法调用确实没有先后,是处于两条并发线程中
通道可用于两个 goroutine 之间通过传递一个指定类型的值来同步运行和通讯。操作符 <-
用于指定通道的方向,发送或接收。如果未指定方向,则为双向通道。
package main
import "fmt"
func sum(s []int, c chan int) {
sum := 0
for _, v := range s {
sum += v
}
c <- sum // 把 sum 发送到通道 c
}
func main() {
s := []int{7, 2, 8, -9, 4, 0}
c := make(chan int)
go sum(s[:len(s)/2], c)
go sum(s[len(s)/2:], c)
x, y := <-c, <-c // 从通道 c 中接收
fmt.Println(x, y, x+y)
}
目前我认为通道的作用 就是用于等待时常 可以同时获取到 两个goroutine 的值。
注意:x和y的值不一定是按照执行代码先后时间来分配的,也就是说x有可能获得第二个结果
如下面的代码,只要多执行几次,总会获得 *d, *e, *f 依旧会输出 1, 2, 3 的情况 或者 其中几个值是sum 运行后的值,从而证明了 并发并不是执行完goroutine 之后再进行下面代码的执行。
package main
import "fmt"
func sum(s []int, c *int) {
sum := 0
for _, v := range s {
sum += v
}
*c = sum
fmt.Println(" sum中:",*c,"------\n")
}
func main() {
s := []int{7, 2, 8, -9, 4, 0}
a,b,c := 1,2,3
d, e, f := &a, &b, &c
go sum(s[:len(s)/2], d)
go sum(s[len(s)/2:], e)
go sum(s[2:len(s)-2], f)
fmt.Println(" 变量值 ",*d, *e, *f,"------\n")
}
或者我提供另一个佐证 执行这段代码时,会沉睡,直至x和y 接收到结果
package main
import (
"fmt"
"time"
)
func sum(s []int, c chan int) {
sum := 0
time.Sleep(10 * time.Second) //沉睡十秒
for _, v := range s {
sum += v
}
c <- sum // 把 sum 发送到通道 c
}
func main() {
s := []int{7, 2, 8, -9, 4, 0}
c := make(chan int)
go sum(s[:len(s)/2], c)
go sum(s[len(s)/2:], c)
x, y := <-c, <-c // 从通道 c 中接收
fmt.Println(x, y, x+y)
}
学习时借助查看 菜鸟教程