go基础知识

GO基础知识

程序基本结构

package main
import "fmt"
func main(){
	fmt.Println("hello world")
}

go语言和Java类似,程序文件需要隶属于某个包中,程序一般由三部分组成:包声明、导包语句和函数声明。如上面这个程序,使用package来声明一个包,同一个包下的程序必须声明同一个包名,程序执行入口(含有main函数的程序)必须声明为main包,否者无法编译执行。go程序使用import语句导包,导入多个包时,多个包名用括号括起来,每个包名各占一行。go使用func关键字声明函数。
go语言的语法比较严格,规定语句结束不使用分号;函数、控制结构等的左大括号{必须和函数声明或控制结构放在同一行;使用大小写来标示变量、常量、类型、接口、结构和函数的可见性,首字母小写代表private,仅包内可用,首字母大写为public,包外程序可见。

声明和赋值

关于变量

go是一种静态强类型编程语言,go的类型决定了变量如何被解析以及支持的操作和运算,go语言使用var关键字声明常量:

//显示声明变量的形式
var name elementType [= value] //[]内容可省略
//例如
var counter int
//可以在声明变量时赋值,若声明时不指定初始值,则默认初始化为类型零值
var counter int =10 //(赋值可以是表达式,如:2*5)
//支持类型推导
var counter=10
//支持多变量声明并初始化
var i,j,k = 1,2,3
//支持批量声明变量,注意var后的括号不能省略
var(
	a int
	b bool
	c string
)

在函数内部还可以使用短类型声明:

//编译器会自动进行类型推导,声明和赋值同时进行
name:=value
//支持多个类型变量同时声明并赋值
a,b,c:=1,true,"hello"

go语言的变量有值类型和引用类型,提供自动内存管理机制,go同一作用域内的变量名是唯一的,若在函数内部使用段类型声明,在同时声明多个变量时允许部分变量重复声明(即,每次声明时必须含有新的变量)。

上面提到的,若声明时未指定初始值,则变量默认为零值,零值有以下几种类型:

  • 布尔类型零值为false;
  • 数值类型的零值为0;
  • 字符串的零值为"";
  • 指针、切片、字典、通道、函数和接口的零值都是nil;

关于常量

go常量使用const关键字声明,分为布尔型、数值型和字符串型,在编译时已经确定,运行时存储在程序的只读区中:

//常量声明形式
//类型可缺省,赋值表达式必需为常量表达式,表达式中的函数必须是内置函数
const name elementType = vaule or expression 
//支持多常量声明和赋值
const _A,_B,_C=1,true,"hello" //常量名一般大写,若希望包外不可见,可首字母为_或c
//常量组声明
const(
	_C=1
	_D string="hello"
	_E =true && fales
)
//常量自增赋值
const(
	c0=iota  //c0==0
	c1  //c1==1
	c2  //c2==2
)
const(
	cA=iota //cA=0
	cB=5
	cC=iota //cC=2
	cD float64=iota*5 //cD=15.0
)

iota关键字专门用于初始化常量,同一const关键字定义的常量组中iota从零开始,每定义一次常量自动增1,使用const重新定义时重置为零。配合常量初始化原则,iota可以达到枚举的效果。

数据类型

数据类型取值范围注意事项
booltrue,false不能用0/1来代替布尔类型的值
int/uint32位或64位可定义为int8/uint8、int16/uint16、32或64
float32/float644/8字节,小数精确到7/15位go没有double类型
complex64/complex1288/16字节由两个float类型的值组成,分别表示实部和虚部
uintptr32位或64位保存指针,无符号整数型
其他类型array,struct,string
引用类型slice(切片),map(字典),chan(通道)
interface接口类型
func函数类型

基本类型

布尔类型

布尔类型的关键字为bool,该类型只有两个值:true和false,go布尔类型不能与整型互换,逻辑表达式的结果都是布尔类型。

整型和浮点型

整型除了8到64位int和uint外还有byte和uintptr两种类型,不同整型之间不能直接转换,必须进行强制转换,其中byte是uint8的别名,uintptr是8字节整型,用于指针运算。

go中有两种用于表示浮点的类型,分别为float32和float64,浮点类型默认推断为float64,由于浮点数难以被计算机精确表示,所以不应该对浮点数执行比较运算。

复数类型

go中的复数内部使用两个浮点数表示,一个表示实部,一个表示虚部,复数类型有两种:complex64和complex128,complex64由两个float32浮点数组成,complex128则由两个float64浮点数组成,浮点数的构造和使用如下所示:

var c1 complex64=5.5+7i
c2:=3.3+4i
c3:=complex(2.5,3) //构造一个复数complex128,2.5+3i
a:=real(c3)  //返回c3的实部
b:=image(c3) //返回c3的虚部
字符串和rune类型

go内置了两种字符类型,一种是byte的字节类型,另一种是表示unicode编码的字符rune类型,rune是int32的别名,顾名思义占了4个字节,go默认字符编码为utf-8,需要转换为可使用Unicode、UTF-8标准包中的工具进行转换。
go字符串类型底层是一个二元数据结构:

//字符串底层定义
type stringStruct struct{
	str unsafe.Pointer  //指向底层字节数组的指针
	len int //字节数组的长度
}

//两种表示形式
var str1 string ="str" //转义字符可生效,但无法换行
var str2 string =`str
123` //转义字符不生效,可以换行,换行也是字符串的一部分

//字符串长度
length:=len(str1) //表示字符串转换为byte数组的长度,不是字符个数

//与切片的转换
bs:=[]byte(str1) //转换为字节切片,将底层数组内容复制到新的字节切片中,数据量大时要慎用
rs:=[]rune(str1) //转换为unicode字切片,同上为复制操作

//切片操作
str:="hello,world"
s1:=str[0:2] //s1=="he"

//遍历操作
for i,v:=range str{ //这种遍历v为字符串转rune数组元素值
	fmt.Println(i,v) //而i却为每个字符在byte数组起始位置,如一个中文占3个byte,i则为0,3,6...
}

go字符串可以通过类似数组下标这样访问字符单元,但字符单元无法被修改,如str1[0],返回的是byte数组第零个元素的值。字符串的切片和原字符串使用相同的不可更改元素内容的底层数组,切片后返回的仍然是字符串而不是切片。

复合类型

指针

go语言指针不支持指针运算,如c中指针的移位操作。其定义和使用与c语言类似,go指针同样也支持多级指针,go指针变量本质上是用于存放地址的变量,不同类型的指针是不同的类型,不能相互转换,指针的定义与使用如下:

//指针的声明
var p *int //声明一个int类型的指针
var q **string //声明一个string类型的二级指针
//指针的赋值(取址)
a:=10
p=&a
s:="hello"
t:=&s
q=&t
//指针对元素值的操作(取值)
*p=100 //a==100
c:=*p //c==100

对于结构体元素的访问,go和c不同,go语言指针仍然是使用.操作符,指针的零值为nil

数组

数组是长度固定且可以容纳若干相同类型元素的容器,每个元素的值可以改变,go语言中的数组是值类型,在传参的时候直接进行复制,数组的声明和初始化如下:

var arr1 [10]int //声明一个长为10的整型数组,在声明后就分配了内存空间,元素默认初值为零值
var arr2=[5]int{1,2,3} //指定长度并初始化,arr2==[1,2,3,0,0]
var arr3=[...]int{1,2,3,4,5} //根据初始化内容自动指定长度,arr3的长度为5
var arr4=[4]int{1:3,2:5} //根据索引初始化,未指定的元素为零值,arr4==[0,3,5,0]
var arr5=[...]int{1:7,3:6} //未指定长度时,由最后一个索引确定长度,arr5==[0,7,0,6]
//多维数组
var arr6=[3][2]{{1,2},{3}} //arr6==[[1,2],[3,0],[0,0]]
var arr7 = [...][3]int{{1}, {2, 3}} //须指定列数,不支持变长,arr7==[[1,0,0],[2,3,0]]
var arr8 = [...][3]int{{1: 3}, {2: 5}} //同样支持索引赋值,arr8==[[0,3,0],[0,0,5]]
//另一种创建方式
var arr=new([10]int) //arr为一个指向长度为10的整型数组的指针,数组初值为零值

gos数组有以下特点:

  • 长度固定,不可扩容;
  • 数组长度是类型的一部分,数组的长度可由内建函数len(arr)获得;
  • 数组是值类型,复制、传参和函数返回都是直接拷贝内容;
  • go也持多维数组,,无论有几维数组的长度都是固定的;

数组可以用以下方式进行遍历:

arr:=[...]int{1,2,3,4,5}
for i,v:=range arr{
	//i为数组索引,v为对应数组元素值
}
for i:=range arr{
	//i为数组索引
}
for _,v:=range arr{
	//v为数组元素值,_为舍弃的索引
}
for i:=0;i<len(arr);i++{
	fmt.Println(arr[i])
}
arr2:=[3][3]int{{1,2,3},{4,5,6},{7,8,9}}
for i,v:=range arr{
	//i为二维数组行索引,v为对应行数组
}
切片

切片是go语言提供的一种变长数组,它是一种结构体,包含一个指向底层数组的指针、元素量和底层数组可用容量。由于包含了指向数组的指针,所以切片是一种引用类型,切片的定义如下:

type slice struct{
	array unsafe.Pointer //array是指向数组的指针
	len int  //切片元素的个数
	cap int  //底层数组的可用容量
}

切片的声明和创建如下所示:

//切片的声明
var s []string //声明一个字符串切片,切片零值为nil,s打印出来就是[]
//注意:s==nil为true,但其仍存在结构:array==nil,len==cap==0,这与指针的零值不同
var s1=[]int{1,2,3} //声明一个整型切片,并赋初值,s1==[1,2,3],len==cap==3
//切片的创建
var array=[...]int{1,2,3,4,5,6,7,8,9,0}
slice1=array[3:5] //其中3为开始索引,5为结束索引,slice1==[4,5],len==2,cap==7
slice2:=array[:4] //slice2==[1,2,3,4],len==4,cap==10
slice3:=array[7:] //slice3==[8,9,0],len==3,cap==3 
//通过内建函数make创建切片
slice4:=make([]int,3)  //slice4==[0,0,0],len==cap==3,通过make创建的切片元素默认为零值;
slice5:=make([]int,3,5) //slice5==[0,0,0],len==3,cap==5
//通过切片生成切片
slice6=slice2[2:] //slice6==[3,4],len==2,cap==2
//slice1-3,slice2与slice6都是公用同一个底层数组

从上面这些例子中可以看到,切片的长度其实就是切片结束索引减去开始索引,其值为切片有效元素的个数,而切片容量其实是切片从开始索引到底层数组结尾的长度,上述例子中slice5底层数组的长度为5,而切片实际元素的个数为3,slice4的长度和容量都是3。切片开始索引之前的元素对于切片来说不可使用,因为切片指向底层数组的指针并不是指向数组的初始地址,而是开始索引的地址,切片的长度与容量的关系就是这么简单,内部实现原理以后再讨论。切片支持以下操作:

array:=[...]int{1,2,3,4,5,6}
slice:=array[:3]
l:=len(slice)  //使用内建函数len获取切片元素个数,l==3
c:=cap(slice) //使用内建函数cap获取切片底层可用容量,c==6
//追加元素
slice=append(slice,1) //slice==[1,2,3,1],len==4,cap==6
slice=append(slice,2,3,4,5,6,7) //len==10,cap=12
s:=array[1:4]
slice=append(slice,s) //len==13,cap==24
//切片复制
slice2:=make([]int,5)
copy(slice2,slice) //将slice复制给slice2,只会复制slice的0-5(不含5)个元素给slice2
//切片重组reslicing
slice=slice[:len(slice)+1]  //len从原来的13变成14,新增的一位值为零值
//切片元素的取值和赋值
e:=slice[3] //取切片元素值,e==4
slice[3]=5 //切片元素赋值
//超出切片元素长度范围的元素存取,即使在容量之内,也会发生越界异常

对于切片的追加,若可用容量足够,则追加操作只会在原底层数组中进行,这会改变数组对应位置的元素值,如果切片的底层数组,没有足够的容量时,就会新建一个底层数组,把原来数组的值复制到新底层数组里,再追加新值,这时候就不会影响原来的底层数组了。所以在创建新切片时,最好使切片的长度和容量一致,这样在追加元素时会生成新的底层数组,和原有数组分离,就不会影响基于原底层数组创建的其他切片append函数会智能的增长底层数组的容量,目前的算法是:容量小于1000个时,总是成倍的增长,一旦容量超过1000个,增长因子设为1.25,也就是说每次会增加25%的容量。

对于切片的复制遵循的是最小复制原则,即:被复制的元素的个数总是等于切片长度较短的那个(注意是长度而不是容量,切片对元素的操作只能在长度的范围内,否则会发生越界异常),与append不同,复制操作不会对切片做扩容操作。

多维切片其实是元素为切片的切片,在后续文章中展现。

字典

go语言的字典(map)类型使用哈希表作为底层实现,是一种引用类型,同时支持自动扩容,字典表示为map[K]T,其中,"K"为键的类型,而"T"则代表元素值的类型,字典的键类型必须是可比较的,因此键不可以是切片、字典、函数类型。字典的声明和初始化如下所示:

var m1 map[string]int //声明一个键为string类型,值为int类型,默认初值为零值nil,长度为0
var m2=map[int]int{1:1,2:2} //声明并初始化一个键值都是整型的map,长度为2
m3:=make(map[string]string)  //创建并初始化一个键值都是string类型的内容为空的map,长度为零
m4:=make(map[string]string,10) //创建并初始化一个指定容量为10的map

map的使用和支持的操作如下:

m:=map[int]string{1:"a",2:"b",3:"c"}
//元素的存取
e1:=m[1] //e1=="a"
e2:=m[9] //e2==""
e3,s1:=m[3] //e3=="c",s1==true
e4,s2:=m[7] //e4=="",s2==false
m[3]="nc" //m[3]=="c"--->m[3]=="nc"
m[5]="d" //新增一个元素键为5,值为"d"
//获取map元素个数
num:=len(m) //获取map键值对个数,num==4
//删除元素
delete(m,2)  //内建函数delete(map,key)用于删除字典中的键值对,m={1:"a",3:"nc",5:"d"}
//遍历元素
for k,v:=range m{
//k为键,v为键对应的值
}
for k:=range m{
//k为键
}
for _,v:=range m{
//v为键值对中的值,_为舍弃的键
}

对于go中的map,若键不存在,则取出的元素为对应类型的零值.针对map的索引取值,可以返回两个结果,第一个为键值对的元素值,第二个结果为表示该键是否存在于字典中的bool类型,键存在则返回true否则返回false。对于delete操作,无论被删除的键值对的键是否在map中,都不会报错。

需要注意的是:
1、map超出容量时会自动扩容,但尽量提供一个合理的初始值,因为频繁扩容会浪费计算资源。
2、对于一个未初始化的map,取元素不会报错,但存元素会引发异常,因此每个map都必须进行初始化操作,同理对于切片也适用,在使用容器嵌套时应尤为注意这点!
3、若map元素值为复杂类型,如切片、字典或后面介绍的结构体,不要直接修改元素内的某个元素的值,而是先把元素取出来,修改后再重新放入map中。
4、go内置的map不是并发安全的,可以使用标准包sync中并发安全的map。
5、map中的元素并不一定是按照添加顺序存储的,而且每次遍历时元素输出的顺序也可能不同。

struct

go中的struct与c中的结构体类似,是一种可以由多种不同类型元素组合而成的复合结构,struct定义的类型是一种值类型,其内部元素的存储空间是连续的,按照字段声明的顺序存放,struct的定义和声明如下:

//声明一个struct(不方便使用)
struct{
	e1 string
	e2 int
	e3 float64
	e4 []int
}
//使用type关键字为struct申请别名,使其方便作为一种类型来使用(常用)
type student struct{
	name string
	age int
	score float32
}
var stu student //声明一个sutdent类型的变量,每个字段为零值

注意值类型在声明时就会被分配内存空间,其字段默认为零值,struct可以进行如下初始化操作:

type student struct{
	name string
	age int
	score float32
}
a:=student{} //声明并初始化一个student结构体,字段默认初值为对应类型零值
b:=student{"张三",18,95.5}  //该方法必必须顺序为结构体所有字段赋初值,否则报错
c:=student{name:"李四",age:19,score:55} //根据字段名赋值,未指定字段默认为零值
d:=student{
	name:"王五",
	score:60,
} //常使用这种方式,比较直观,每个字段各占一行,注意最后一个字段也要逗号,未指定字段默认为零值
e:=&student{
	score:65,
} //声明并初始化了一个student类型的空间并用指针e指向它
f:=new(student)  //声明并初始化一个student类型的空间,并返回指向它的指针

使用type定义的结构体,若名称首字母大写则对包外可见,否则只可包内可见,结构体内的字段名同样遵循这个规则,字段的访问如下所示:

type student struct{
	name string
	age int
	score float32
}
stu:=student{name:"李四",age:19,score:55}
stu2:=&student{name:"渣渣"}
n1:=stu.name //n1=="李四"
n2:=stu2.name //指针对结构体内字段的访问也是用".",n2=="渣渣"
stu.age=15
stu2.score=75

这些是struct的定义、声明、初始化和使用,struct还有更为复杂的使用方法和场景,放在后面详细介绍,类型系统中的函数、接口和通道也会在后续文章中一一展现。

程序控制流

go语言的程序执行模式也是包含顺序和跳转两种基本模式,go代码按照逻辑行顺序执行,遇到分支结构、函数调用等可发生条件跳转,此外,go还支持使用goto语句进行无条件跳转。

if-else

go语言使用if-else进行条件判断,不支持像c中常用的条件运算符(x>y?a:b),if语句中的条件不需要使用小括号括起来,if后可跟一个可选的简单的初始化语句,这是go的if语句的特色,以下是if-else的一些示例:

if x<=y{
	//代码块
}

if x>y{
	//代码块
}else{
	//代码块
}

if x,y:=1,3;x<y{  //判断之前使用初始化语句
	//代码块
}

if err:=f();err!=nil{
	return err
}else if x>y{
	return nil
}else{
	return errors.New("other error")
}

上述代码是go语言if-else的简单应用,需要注意的是,if-else条件分支内的代码块必须使用大括号括起来,而且大括号左边必须要在判断语句同行末尾,这是go强制的代码格式要求,在有大括号的其他结构中也是如此,分支内遇到return语句直接返回。

应当尽量减少if-else语句的复杂程度,复杂且频繁使用的可以拆分由不同函数封装。同时也应当尽量少使用嵌套或减少if-else的嵌套层次,让代码扁平,方便阅读。

switch

go语言switch是一种多分支结构,通过传入条件跳转到对应的分支执行代码块,和c语言等不同的是,switch默认在case代码块最后自带break语句,即执行完一个分支后不会顺序执行下面的case分支直到结束,如果需要可以在case分支代码块最后使用fallthrough关键字。switch的判断条件可以是任意支持相等比较运算的类型,如int、string等,与if类似,switch同样可以带一个可选的初始化语句。switch的使用示例如下:

switch i:="a";i{
case "a","b","c":  //支持多值并列,只要i为其中任意一值,就可进入该分支
	fmt.Println("i is a or b or c") //未设置fallthrough语句,分支执行结束后退出switch
case "d":
	fmt.Println("i is d")
case "e":
	fmt.Println("i is  e")
	fallthrough  //在执行完条件为i==“e”这个分支后继续强行执行后面的case分支内容(无需判断)
case "f":
	fmt.Println("i is f")
default:  //i未匹配到任何case转入该分支,该分支可选,未设置default语句未匹配时也不会报错
	fmt.Println("error")
}

switch还支持类型查询操作:

	var t interface{}
	t = "test"
	switch t.(type) { //t必须为接口类型
	case int:
		fmt.Println("int")
	case float64:
		fmt.Println("float64")
	case float32:
		fmt.Println("float32")
	case string:
		fmt.Println("string")
	case bool:
		fmt.Println("bool")
	}

go语言中还有一个与switch相似的,用于通信的select语句,使用时需要配合通道使用,等介绍到通道时再进行介绍。

for

go中没有while循环,但go中的for有多种使用场景,可以替代掉while,同时还支持类似于java增强for循环的对字符串、数组、切片map和通道的迭代访问方式。

//for的几种使用形式
//形式1
for init;condition;post{ 
	//init代表初始化语句,如i:=0
	//condition为循环条件,如i<=5
	//post为步进操作,如i++
}
//形式2
for condition{
	//condition为循环条件,类似while语句
}
//形式3
for{
	//死循环,类似于while(ture){}
}
//形式4
for i,v:=range array{
	//类似的,使用range迭代数组、字符串、切片、map和通道,在前面对应的类型里有介绍
}

标签和跳转

go语言中的跳转语句有goto、break、continue。go支持通过标签来标示一个跳转位置,使用跳转语句配合标签可以实现灵活跳转。goto语句用于函数内部的跳转,不能在函数间跳转,需要配合标签一起使用,执行语句跳转至标签后面的语句,如:

Lable1:
	for i:=0;i<10;i++{
		fmt.Println("i:",i)
	}
goto Lable1
//goto语句不能跳过内部变量声明语句
	goto L1
	v:="lable test"
L1:
//goto只能在同层作用域中跳转或跳转至自己上层作用域,不可跳转至内层作用域
goto L2
	for i:=0i<=5;i++{
		fmt.Println(i)
	L2:  //不允许跳转至L2
	}
	goto L3 //允许跳转至L3
		for i:=0i<=5;i++{
			fmt.Println(i)
		} //被跳过的代码段会被标识为 unreachable code
L3:

break用于跳出循环、switch和select语句执行。直接使用时是跳出当前所在的作用域到上层,只能跳转一层,如跳出一层for循环。配合标签使用可以跳转到标签后面开始执行,例如:

L1:
	for i:=0;i<10;i++{
		for j:=0;j<5;j++{
			if j>=3{
				break L1  //跳转至L1处执行
			}
			if j>1&&i>2{
				break //跳出临近的那层for循环
			}
			fmt.Println(i,j)
		}
	}

continue单独使用时用于跳出for循环本次的迭代至for的post(步进阶段,如果没有则跳转到条件判断开始下一次循环),或者说立即结束本次迭代,开始下一次迭代。结合标签使用时,会跳出标签后面的那个循环的本次迭代,进行下一次迭代。

L1:
	for i:=0;i<10;i++{
		for j:=0;j<10;j++{
			if j>=3{
				continue L1  //跳转至L1后面那个for的i++处执行
			}
			if j>1&&i>2{
				continue //跳到j++处开始执行(最近的那层)
			}
			fmt.Println(i,j)
		}
	}

注意:对于break和continue配合标签时,标签一定要在使用前定义,若在跳转语句之后定义则无效,使用标签的地方会出现标签未定义的错误,而标签定义处会报错定义但未被使用,即标签只能定义在break和continue所在的循环或嵌套循环的开始处,像上面这些例子,而不能定义到其他地方。这也是break和continue支持标签的意义:用于跳转当前所在的循环或多层循环,而不是乱跳导致代码不易控制和阅读。

运算符与优先级

算数运算符

+ 相加,- 相减,*相乘,/ 相除,% 求余,++ 自增,-- 自减,除自增和自减外的其它算数运算符可配合等号使用,如a+=5(相当于a=a+5)
需要注意:

  • 整数与浮点数进行运算,会转化成浮点数;
  • 求余,如果被除数或除数存在负数,那么余数与被除数保持一致(是求余而非取模);
  • ++--语句而非表达式,只能在变量后使用,如 i++(没有++i这种操作),不能像表达式那样赋值给变量;
  • 字符串可以使用+运算符来拼接;

其他运算符

指针运算:& 取址、* 取值、. 取元素(用于结构体,普通结构体类型的变量和指针都用它操作结构体字段)
关系运算符有:== 等于、!= 不等、 > 大于 、< 小于、 >= 大于等于、 <= 小于等于;
逻辑运算符有:&& 与、 || 或、 ! 非;
位运算符:& 按位与、| 按位或、^ 按位异或(按位,前后操作数不同为1相同为0)、&^ 按位与非(按位,后操作数为1则前操作数置零,否则保留,如0xAB&^0x0F==0xA0)、<< 算数左移、>> 算数右移;
关系运算符、逻辑运算符和位运算符(除左、右算数移位)返回布尔值,算数移位的左移的低位和右移的高位均补零,运算符种类和c相似;位运算符也可像算数运算符那样和等号组成自赋值运算符。

类型转换与优先级

类型不同的数据不能进行运算,而有些转换会自动完成,例如 5/4.0 整型会被自动转换为浮点数,使用 convertType(数据) 进行强制转换。go对类型要求比较严格,类型相互转换的场景多集中于数值计算当中。运算符优先级由高到低如下:

分类运算符结合性
后缀运算符( )、[ ]、->从左到右
单目运算符!、*(指针)、& 、++、–、+(正号)、-(负号)从右到左
部分算数运算符*(乘号)、/、%从左到右
加法与减法+、-从左到右
位移运算符<<、>>从左到右
部分关系运算符<、<=、>、>=从左到右
相等/不等==、!=从左到右
按位与&从左到右
按位异或^从左到右
按位或|从左到右
逻辑与&&从左到右
逻辑或||从左到右
赋值运算符=、+=、-=、*=、/=、 %=、 >=、 <<=、&=、^=、|=从右到左
逗号运算符,从左到右

参考学习资料

1.《Go核心编程》,李文塔 著
2.《Go专家编程》,任洪彩 著
3.《Go高级编程》,柴树杉、曹春晖 著
4.网上其他博客

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值