在导入包时可以使用别名来防止混淆,或者是使用省略调用来减少代码
//在导入包时定义别名
import io “fmt”
//使用时直接使用别名,而不是原来的包名
io.Println("Hello World")
//在导入包时声明省略调用
import . “fmt”
//使用时直接使用要使用的函数,不用加包名
Println("Hello World")
使用大小写来决定该常量,变量,类型,接口,结构或者函数是否可以被外部调用。
函数名首字母小写为private函数
函数名首字母大写为public函数
//变量的声明
//(var <变量名称> <变量类型>)
var a int
//变量的赋值
//(<变量名称> = <表达式>)
a = 1
//变量声明同时赋值
//(var <变量名称> [变量类型] = <表达式>)
var a int = 65
//变量声明同时赋值
//(var <变量名称> = <表达式>)
var a = 2
//变量的声明同时赋值
//<变量名> := <表达式>
a := 2
全局变量:
//多个变量的声明
// [变量名1],[变量名2],[变量名3]... <类型名>
var a,b,c,d int
//多个变量的赋值
//[变量名1],[变量名2],[变量名3] = <表达式>,<表达式>,<表达式>...
a,b,c = 1,2,"string"
//多个变量的声明和赋值
//var(<变量名> = <表达式>...)
var(
a = 1
b = "string"
)
局部变量:
//多个变量的声明
// [变量名1],[变量名2],[变量名3]... <类型名>
var a,b,c,d int
//多个变量的赋值
//[变量名1],[变量名2],[变量名3] = <表达式>,<表达式>,<表达式>...
a,b,c = 1,2,"string"
//多个变量的声明和赋值
//<变量名><变量名><变量名>... := <表达式><表达式><表达式>...
a,b,c := 1,2,3
Go中不存在隐式转换,所有的类型转换都需要显式声明
类型转换只能发生在两个相互兼容的类型之间
类型转换格式:
常量的值在编译时就已经确定
常量得到的定义格式与变量基本相同
常量右侧的表达式必须是常量或者是常量表达式
常量表达式中的函数必须是内置函数
在常量组中定义时,若是不提供初始值,则默认使用上一行的表达式
iota是常量的计数器,从0开始,组中每定义一个常量,就会自动增1
每遇到一个const关键字,iota就会重置为0
const (
FIRST = iota //FIRST=0
SECOND //SECOND = 1
THRID //THRID = 2
)
运算符 | 优先级 |
---|---|
^ ! | 一元运算符 |
* / % << >> & &^ | 二元运算符 |
+ - | ^ | 二元运算符 |
== != < <= > >= | 二元运算符 |
<- | 用于channel |
&& | |
|| |
Go中的指针采用”.”选择符来操作指针对象的成员
//指针的定义
var p *[]int
//只针的赋值
p = &a
++(自增运算符)和–(自减运算符)不再是作为表达式,而是作为单独的语句出现。也就是说他们都不可以出现在运算符的右侧
//错误使用
a := 1++;
//正确使用
a := 1
a++
在if之后没有括号,可以直接写判断表达式,而且大括号必须和if在同一行,支持在if语句中进行变量的初始化。
if a, b := 1, 2; a >= 0 || b >= 0 {
fmt.Println(a)
fmt.Println(b)
}
只有for一个循环语句关键字,但是支持3种形式,for中的条件语句每次都会被检查,因此不建议在条件语句中使用函数,同if语句一样,左大括号必须和条件语句在同一行。可以通过break来跳出循环。
for a, b := 1, 2; a >= 0 || b >= 0; {
fmt.Println(a + b)
a--
b--
}
//传统使用方式
a := 1
switch a {
case 0:
fmt.Println("a == 0")
case 1:
fmt.Println("a == 1")
default:
}
//在case中使用表达式
a := 1
switch {
case a >= 0:
fmt.Println("a == 0")
fallthrough //这样可以使满足case之后仍然进行判断,而不是直接跳出switch语句
case a >= 1:
fmt.Println("a == 1")
default:
fmt.Println("默认处理方式") //当所有的case都不满足使,会使用default来进行处理
}
//可以使用数组的下标来指定具体某个数组元素的初始值,若不指定则全部使用类型零值
f := [20]int{19: 1}
for i := 0; i < 20; i++ {
fmt.Println(f[i])
}
L := [9]int{1, 2, 3, 4, 5, 6, 7, 8, 9}
s1 := L//指向底层数组L的全部
fmt.Println(s1)
//----------
L := [9]int{0, 1, 2, 3, 4, 5, 6, 7, 8}
s1 := L[3:6] //指向底层数组L的部分,指向的是数组中下标为3,4,5的数
fmt.Println(s1)
//-----------
sl := make([]int, 3, 9)//[]int----所指向的类型,3----切片的长度,9----切片容量
L := [9]int{0, 1, 2, 3, 4, 5, 6, 7, 8}
slice := L[2:7] //输出为2,3,4,5,6
reslice := slice[1:4]//在slice的基础上再进行slice,输出为3,4,5
fmt.Println(reslice)
L := [9]int{0, 1, 2, 3, 4, 5, 6, 7, 8}
slice := L[2:7]
fmt.Printf("%v %p", slice, slice)
slice = append(slice, 2, 3, 4, 5)
fmt.Printf("%v %p", slice, slice)
L := [9]int{0, 1, 2, 3, 4, 5, 6, 7, 8}
slice := L[2:7]
slice2 := []int{1, 2, 3}
copy(slice, slice2)//将slice2中的元素拷贝到slice中
fmt.Println(slice, slice2)
//slice输出为[1,2,3,3,4,5,6,7,8]
//声明map变量:var关键字 变量名 map关键字 [Key类型]Value类型
var m map[int]string
//初始化map变量
m = map[int]string{1:"ok",2:"hello"}
//或者是
m = make(map[int]string)
//声明并赋值
var m map[int]string := make(map[int]string)
//其实map[int]string整体是作为一个类型名称的
//增加
m[1] = "ok"
//删除
delete(m, 1)
//多层map的嵌套
var m map[int]map[int]string //map的value类型为map
m = make(map[int]map[int]string) //对最外层的map进行初始化
m[1] = make(map[int]string) //对内层中Key为1的map进行初始化
m[1][1] = "hello"
fmt.Println(m[1][1])
//利用多返回值确定map是否初始化
data, ok := m[1][1]
if !ok {
m[1] = make(map[int]string)
}
m[1][1] = "hello"
data, ok = m[1][1]
fmt.Println(data, ok)
//通过for range来遍历map和slice,但是所有对value的操作都是对map和slice拷贝的操作
for k,v := range map{
//k是key,v是value
}
//func关键字 函数名 形参列表,返回值列表
func A(data1 int,data2 int,data3 int) (a int, b int, c string) {
a, b, c = 1, 2, "hello"
fmt.Println(d)
return a, b, c
}
//go中支持不定长参数
func A(d ...int) (a int, b int, c string) {
//通过在类型名前加...来使用不定长参数,但是不定长参数只能作为参数列表的最后一个参数进行声明
//不定长参数传入的其实是一个slice切片,但是其实际上是传入的一个值拷贝,在函数内修改不会影响原值
}
不给函数指定名称,而是直接将其作为变量类型使用
//匿名函数,直接将函数作为变量进行使用
a := func() {
fmt.Println("匿名函数")
}
a()
//定义返回值为一个函数
func closure(x int) func(int) int {
return func(y int) int {
return x + y
}
}
//使用函数
f := closure(10)
fmt.Println(f(1))
//打印结果会是a,c,b
func main{
fmt.Println("a")
defer fmt.Println("b")
defer fmt.Println("c")
}
//打印结果是3,3,3,因为传入的是一个引用,所以当循环体执行完之后i指向的是3,然后开始反向的顺序执行defer,所以会是3,3,3
func main(){
for i := 0; i < 3; i++ {
defer func() {
fmt.Println(i)
}()
}
}
go中没有try/catch语句来进行异常处理,都是用panic和recover来就进行
panic可以在需要的地方将程序中断,不再执行
,可以在任何地方引发
recover可以配合defer函数来进行程序的恢复运行,但是只有在defer调用的函数中有效
func main(){
B()
C()
D()
}
func B() {
fmt.Println("B")
}
func C() {
//defer函数必须在panic之前,不然无法注册的defer函数
//recover只能在defer调用的函数内使用
defer func() {
if err := recover(); err != nil {
fmt.Println("Recover in C")
}
}()
panic("Panic in c")
}
func D() {
fmt.Println("D")
}
type Person struct {
Name string
Age int
}
//匿名结构,不需要为定义的结构命名,直接定义好结构之后使用
a := struct {
Name string
Color string
}{
Name: "cai",
Color: "Red",
}
//在初始化时,内部的匿名结构不可以通过指定字段名来命名,只能通过操作符'.'来初始化。
type Person struct {
Name string
Age int
Family struct {
Mom string
}
}
//对结构体进行初始化
per := Person{
Name: "wqc",
Age: 10,
}
//只能这样初始化内部的结构体
per.Family.Mom = "妈妈"
在Go中不存在继承关系,主要是通过组合的方式来实现一些公有属性的抽象。
type People struct {
Name string
Age int
Sex int
}
type Student struct {
People
Sid int
}
//对使用结构组合的结构进行初始化
xiaoMing := Student{
Sid: 123,
People: People{
Name: "xiaoMing",
Age: 12,
Sex: 1,
},
}
xiaoMing.Age = 100
xiaoMing.Name = "whh"
xiaoMing.Sex = 11
fmt.Println(xiaoMing)
Go中通过显式说明receiver来实现与某个类型的组合
只能为同一个包中的类型定义方法
Receiver可以是类型的值或者是指针
不存在方法的重载
可以使用值或者是指针来调用方法,编译器会自动完成转换
如果外部结构和嵌入结构存在同名方法,则优先调用外部结构的方法
类型别名不会拥有底层类型所附带的方法
方法可以使用结构中非公开的字段
//方法的定义方式
type A struct {
Name string
}
//func(要绑定的结构名) 函数名(参数列表)(返回值列表)
//值传递方式传入的只是一个拷贝,无法对外部的值产生影响
func (a A) Print(x int) (flag bool) {
fmt.Println("1")
}
//指针传递,传入的是一个地址的拷贝,可以修改外部的值
func (a *A) Print(x int) (flag bool) {
fmt.Println("1")
}
//在Go中不允许方法重载,以上边为例:也就是说结构体A只能有一个叫Print的方法。
接口是一个或者是多个方法签名的集合
只要某个类型拥有该接口的所有方法签名,计算实现该接口,无需显示声明实现了那个接口,称为structural Typing
接口只有方法声明,没有实现,没有字段
接口可以嵌入其他接口或者是结构中去
只有当接口存储的类型和对象都为nil时接口才等于nil
将对象赋值给接口时会发生拷贝,而接口内部存储的是指向这个复制品的指针,既然无法修改复制品的状态,也无法回去指针
空接口可以最为任何类型数据的容器
//定义接口
type USB interface {
Name() string
Connect()
}
//定义结构
type PhoneConnector struct {
name string
}
//为结构声明实现了接口的方法
func (pc PhoneConnector) Name() string {
fmt.Println("Name ")
return pc.name
}
//为结构声明实现了接口的方法
func (pc PhoneConnector) Connect() {
fmt.Println("Connect...", pc.name)
}
package main
import (
"fmt"
"reflect"
)
type User struct {
Id int
Name string
Age int
}
func (user User) Hello() {
fmt.Println("Hello World")
}
func Info(o interface{}) {
t := reflect.TypeOf(o)
fmt.Println(t.Name())
//获取所包含的字段的信息
v := reflect.ValueOf(o)
for i := 0; i < t.NumField(); i++ {
f := t.Field(i)
val := v.Field(i).Interface()
fmt.Println(f.Name, f.Type, val)
}
//获取所包含的方法信息
for i := 0; i < t.NumMethod(); i++ {
m := t.Method(i)
fmt.Println(m.Name, m.Type)
}
}
func main() {
u := User{1, "ok", 12}
Info(u)
}
可以通过使用反射来获取变量的地址信息,然后直接对地址中所存的信息进行修改
x := 9
v := reflect.ValueOf(&x)
v.Elem().SetInt(999)
fmt.Println(x)
通过反射来修改接口中的字段值
- 首先判断是否获取到字段,而且值可以被修改
- 然后检验是否获取到所需要的字段
- 修改字段值
func Set(o interface{}) {
//获取接口中的字段值
v := reflect.ValueOf(o)
//检验是否获取正确
if v.Kind() == reflect.Ptr && !v.Elem().CanSet() {
fmt.Println("XXX")
return
} else {
v = v.Elem()
}
//获取具体某一字段
f := v.FieldByName("Name")
//检验是否成功获取到
if !f.IsValid() {
fmt.Println("错误")
}
//通过set方法修改字段值
if f.Kind() == reflect.String {
f.SetString("正确")
}
}
高并发是Go的核心亮点,其实goroutine是官方实现的“超级线程池”,每个实例4-5KB的占内存占用和由于实现机制而大幅减少的创建和销毁开销。通过通讯来进行消息交互,而不是通过共享内存来通讯。
Channel是goroutine沟通的桥梁,大都是阻塞同步的
通过make创建,通过close关闭
可以使用for range来迭代不断操作Channel
可以设置通道为单项或者是双向
可以设置缓存大小,在未被填满前不会发生阻塞
func main() {
//声明一个通道
channel := make(chan bool)
go func() {
fmt.Println("Go Go Go")
//向通道中写入数据
channel <- true
//关闭通道
close(channel)
}()
//通过for range从通道中读取数据
for v := range channel {
fmt.Println(v)
}
}
可以处理一个或多个channel的发送和接收
同时有多个可用的channel是按随机顺序处理
可用空的select来阻塞main函数
可设置超时机制