关键字和注释方法

Go的关键字
golang的关键字很少,只有25个:
break、default、func、interface、select、case、defer、go、map、struct、chan、else、goto、package、switch、const、fallthrough、if、range、type、continue、for、import、return、var

Go的注释方法
和其他大多语言一样:
// :单行注释
/* */ :多行注释

运算符优先级

具有最高优先级的运算符放在表的顶部,具有最低优先级的运算符出现在底部。 在表达式中,将首先计算较高优先级运算符。
在这里插入图片描述

程序结构

源文件后缀为.go,比如 hello.go
Go程序是用package来组织的,在源文件第一个非注释行声明包,比如:package main
只有package为main的包才能包含main函数(程序的入口),并且一个可执行程序有且仅有一个main包
通过import关键字导入其它包,两种用法:
1.导入单个包:

import "fmt"

2.导入多个包:

import (
"fmt"
"hehe"
)

3.给包起别名:

import std "fmt"
import (
std "fmt"
)

导入包之后就可以用包名调用包中的函数,比如初学者最常用的输出函

fmt.Println("hello world")

这样做还可以省略包名直接调用(但是不建议这样做):

//import . "fmt"
import (
. "fmt"
)


func main() {
Println("hello world")
}

可见性规则(公有与私有,访问权限)

Go语言没有像其它语言一样有public、protected、private等访问控制修饰符,它是通过字母大小写来控制可见性的,如果定义的常量、变量、类型、接口、结构、函数等的名称是大写字母开头表示能被其它包访问或调用(相当于public),非大写开头就只能在包内使用(相当于private,变量或常量也可以下划线开头)

例如:
visibility/test.go

package visibility
 
import "fmt"
 
const PI = 3.145
const pi = 3.14
const _PI = 3.14
 
var P int = 1
var p int = 1
 
func private_function() {
fmt.Println("only used in this package!")
}
 
func Public_fuction() {
fmt.Println("used in anywhere!")
}

main.go

package main
 
import (
"visibility"
"fmt"
)
 
func main() {
visibility.Public_fuction() //used in anywhere!
//visibility.private_function() //不能访问私有函数,无法通过编译
fmt.Println(visibility.P) //1
//fmt.Println(visibility.p) //不能访问私有变量,无法通过编译
fmt.Println(visibility.PI) //3.14
//fmt.Println(visibility.pi) //不能访问私有常量,无法通过编译
//fmt.Println(visibility._PI) //不能访问私有常量,无法通过编译
}

基本数据类型

整型有:int8/uint8、int16/uint16、int32/uint32、int64/uint64、int/uint、byte、rune

  1. 数字表示占多少位,uint的u表示unsigned,有点C语言基础的都知道是什么意思,这里就不赘述了;
  2. int/uint会根据运行平台可能为32位或64位;
  3. rune相当于int32的别名,通常用于表示一个Unicode码点;
  4. byte相当于uint8的别名,一般用于强调数值是一个原始的数据而不是一个小的整数;

浮点型有:float32、float64
float64相当于其它某些语言的double,即双精度浮点型

字符串:string

布尔型:bool
只有true或false,不能用数字代替(比如C语言就允许0代表false,非0代表true)

空值:nil
有些语言是null或NULL

变量声明与赋值

变量声明与赋值示例:

s := ""
var s string
var s = ""
var s string = ""

第一种形式,是一条短变量声明,最简洁,但只能用在函数内部,而不能用于包变量。第二种形式依赖于字符串的默认初始化零值机制,被初始化为""。第三种形式用得很少,除非同时声明多个变量。第四种形式显式地标明变量的类型,当变量类型与初值类型相同时,类型冗余,但如果两者类型不同,变量类型就必须了。一般使用前两种形式中的某个,初始值重要的话就显式地指定变量的类型,否则使用隐式初始化。

var i int = 1 //var关键字声明一个变量,紧跟一个变量名,然后是变量类型,最后是赋值
var hehe = "hehe" //可以省略变量类型,编译器根据赋值的类型自动推算变量的类型
var a,b,c,d = 1,2,3.14,4 //同时申明多个并赋值
j := 10 //省略var关键字
如果是全局的变量,还可以这样:
var (
a = 1
b uint64 = 3
c =3.14
d := 4
)

常量声明与赋值

常量的值必须在编译时期就确定下来,所以要么直接给出确定的值,要么使用Go自带的函数(比如len(),计算长度);
用const关键字声明
定义与赋值示例:

const _THRESHOLD int = 10 //完整形式
const PI = 3.14 //可省略类型,编译器自动推断
const STR = "hello world"
const (
MAX_VALUE = 1000
MIN_VALUE int = -1000
LEN = len(STR)
)

常量计数器

关键字为iota,没遇到一个const关键字就会置为0,然后递增。还有就是如果定义常量组,没有赋值的编译器会自动将其赋值表达式置为和它前面那个变量的表达式一样

const (
A = 55
B = iota
C
D
)
func main() {
fmt.Println(A)
fmt.Println(B)
fmt.Println(C)
fmt.Println(D)
}

输出:
55
1
2
3

类型零值、类型别名、类型转换

类型零值
变量声明为某种类型后的值就为该类型的零值,它不是空值。通常数值类型默认为0,bool为false,string为空字符串
类型别名
比如:

type text string
func main() {
var t text = "hello world"
fmt.Println(t)
}

类型转换

Go语言不存在隐式转换,必须显式转换,并且只能在两种相互兼容的类型间转换,转换示例:
a := 3.14
b := int(a)
fmt.Println(b)
var c int32 = 256
var d = float64(c)
fmt.Println(d)

运算符、指针

运算符
Go的运算符是从左至右结合的(和其它很多语言相反)

指针
Go语言保留了指针,但不支持"->",而是直接用"."来操作指针目标对象的成员
简单使用示例:

var a int = 45
var p *int = &a
fmt.Println(*p)

关于++和–

和其它语言有很大不同,它只能放在变量的后面而不能放在前面,而且只能作为一条语句而不是表达式。
举个栗子:

var i =1
var j = i++

这段代码将不同通过编译

判断语句if

条件表达式没有括号
支持初始化表达式
初始化语句的变量自在本block内有效

if a,b,c := 1,2,3;a+b+c>6 {
fmt.Println("haha")
}else{
fmt.Println("hehe")
}

循环语句

Go循环只有for关键字,没有while,但可以达到同样的效果

for a,b:=1,2;a+b<100;a++ {
fmt.Println(a+b)
}
i := 1
for i<=100 {
fmt.Println(i)
i++
}
i = 1
for{
if i>100 {
break
}
fmt.Println(i)
i++
}

选择语句switch

条件语句可以使用任何类型或表达式,甚至不填
不需要写break,一旦条件符合自动终止,如果希望继续执行下一个case,需使用fallthrough
支持初始化表达式,右侧跟分号

var a int = 1
switch {
case a>=1:
fmt.Println("a>=1")
case a>=0:
fmt.Println("a>=0")
}
var a int = 1
switch a {
case 0:
fmt.Println("a=0")
case 1:
fmt.Println("a=1")
}
switch a:=1; {
case a>=1:
fmt.Println("a>=1")
fallthrough
case a>=0:
fmt.Println("a>=0")
}

跳转语句goto、break、continue

和其它许多语言用法大致一样,三种语法支持配合标签使用,标签区分大小写

goto
package main
 
import "fmt"
 
func main() {
var i int = 1
if i <=10 {
goto LABEL
}
fmt.Println("before label")
LABEL:
fmt.Println("after label")
}

输出:
after label

break

package main
 
import "fmt"
 
func main() {
LABEL:
for i:=0;i<100;i++ {
for j:=0;j<100;j++ {
fmt.Println(j)
if j==4 {
break LABEL
}
}
}
}

输出:
0
1
2
3
4

continue

package main
 
import "fmt"
 
func main() {
	LABEL:
	for i:=0;i<4;i++ {
		for j:=0;j<4;j++ {
			fmt.Println(j)
			if j==2 {
				continue LABEL
			}
		}
	}
}

输出:
0
1
2
0
1
2
0
1
2
0
1
2

数据array和切片slice

数组
数组定义格式示例:

var a [2]int
fmt.Println(a)
b := [2]int{2,3}
fmt.Println(b)

输出:
[0 0]
[2 3]

还可以省略数组长度,编译器自动推算:
a := [...]int{5,6,7,8}
还可以使用索引初始化:
a := [...]int{9:1}
fmt.Println(a)

输出:
[0 0 0 0 0 0 0 0 0 1]

需要注意的是,数组长度也是数组类型的一部分,因此长度不同的数组类型也就不同
数组在Go中为值类型,即给传递数组变量的时候是全复制,如果想传递引用,将使用Go特有类型:slice,后面会讲到
数组可以用==或!=来比较
可以使用new创建数组,此时返回的是数组指针:
a := new([10]int)

切片
底层就是数组,多个slice可以在底层可以指向同一个数组
可以指定初始长度和容量,注意长度和容量概念不同,长度是指该slice用于存放数据的数组长度,容量是底层数组分配连续内存的总长度。当给slice append元素,使得长度超出容量的时候,Go会再创建一个容量为当前容量两倍的新数组,并将数据拷贝进去。

切片只能往后拓展长度,不能往前拓展长度,只能截断前面的元素,截掉的前面的元素就不存在数据中了;只能拓展长度,无法拓展容量

var s0 []int //和定义数组类似,只是不需要指定长度
fmt.Println(s0) //[]
a := [10]int{0,1,2,3,4,5,6,7,8,9} //声明一个数组
s1 := a[5:10] //截取数组某一部分
fmt.Println(s1) //[5 6 7 8 9]
s2 := a[5:] //截取从某个索引开始到数组末尾
fmt.Println(s2) //[5 6 7 8 9]
s3 := a[:5] //截取从数组开头开始到某个索引为止
fmt.Println(s3) //[0 1 2 3 4]
s4 := make([]int,3,10) //创建一个类型为整型数组,长度为3,容量为10的slice
fmt.Println(len(s4),cap(s4),s4) //3 10 [0 0 0]
s5 := []int{1,2,3,4} //定义并初始化slice
fmt.Println(s5) //[1,2,3,4]
s6 := a[:] //指向整个a数组
fmt.Println(s6) //[0,1,2,3,4,5,6,7,8,9]

相当于其它语言的哈希表或字典,key-value形式存储数据
key必须是支持==和!=比较运算的类型,不能是函数、map或slice

var m1 map[int]string
m1 = make(map[int]string)
fmt.Println(m1) //map[]
var m2 map[int]string = make(map[int]string)
m2[1] = "OK"
fmt.Println(m2) //map[1:OK]
m3 := make(map[int]string)
fmt.Println(m3) //map[]
m4 := map[int]string{4:"d",1:"a",2:"b",3:"c"}
fmt.Println(m4) //map[4:d 1:a 2:b 3:c]

函数

用关键字func定义
可以有多个返回值

func main(){
f1() //f1
a,b := f2(1,1.2)
fmt.Println(a,b) //4.34 2
c,d,e := f3()
fmt.Println(c,d,e) //1 2 3
f4("OK",1,2,3,4) //OK [1 2 3 4]
f5() //hello world
f := f6(2)
fmt.Println(f(1)) //3
}
func f1(){ //没有返回值就不写
fmt.Println("f1")
}
func f2(a int,b float32)(float32,int){ //可以有多个返回值
return b+3.14,a+1
}
func f3()(a,b,c int){ //参数简写
return 1,2,3
}
func f4(a string,b ...int) int{ //变长参数
fmt.Println(a,b)
return 0
}
func f5(){ //匿名函数
a := func(){
fmt.Println("hello world")
}
a()
}
func f6(x int) func(y int)int{ //闭包
return func(y int)int{
return x+y
}
}

Golang语言的函数可以返回多个返回值,而且可以为每个返回值指定一个名称
声明函数时命名了返回值:
则不用再在函数体中声明,直接用,并且直接将需要返回的值赋予所设置的变量即可,最后使用return可以实现自动的返回对应的变量值。
若某个变量没有出现或被赋值则返回对应变量类型的“零值”。
注意,如果想更改显示的返回值。可以写 return 1, nil
命名变量将会被忽略,返回的真正值为 1, nil

声明函数时未命名返回值:
返回的变量A需要在函数体内声明并写return A

不支持重载,例如下面两个函数相同,参数不同,编译无法通过

func foo(i int) {
fmt.Println(i)
}
func foo(s string) {
fmt.Println(s)
}

处理异常

Go语言没有提供像其它语言一样的try catch的异常与捕获机制,可以用defer、panic、recover代替。
defer的语句或函数在当前函数执行完之后再执行,先defer的后执行,后defer的先执行。当程序发生严重错误的时候defer也能够执行。有点类似其它语言的析构函数
调用panic来制造一个异常
调用recover来获取异常信息

func main(){
f1()
f2()
f3()
}


 
func f1(){
fmt.Println("f1")
}
func f2(){
defer func() {
if err:=recover();err!=nil {
fmt.Println("catch the exception")
}
 
}()
panic("wa ha ha ha ha ~~~")
}
func f3(){
fmt.Println("f3")
}

输出:
f1
catch the exception
f3

结构struct

Go没有class,也没有继承机制,只能定义结构体,但是功能还是很强大。本文先演示下它的基本用法。

简单使用示例:

type person struct {
Name string
Age int
}
func main() {
a := person{ //声明的时候可以赋初始值,也可以不要
Name:"cpwl",
Age : 21,
}
a.Name = "roc" //改变值
fmt.Println(a) //{roc 21}
}

注意结构指针:

type person struct {
Name string
Age int
}
func main() {
a := person{
Name:"cpwl",
Age : 21,
}
change1(a)
fmt.Println(a) //{cpwl 21}
change2(&a)
fmt.Println(a) //{roc 21}
b := &person{ //创建的时候就返回指针,我们一般都这样做
Name:"zhansan",
Age:18,
}
change2(b)
fmt.Println(b) //&{roc 18}
}
func change1(p person) { //Go默认是值传递,struct也会拷贝传给调用的函数
p.Name = "roc"
}
func change2(p *person) { //传递指针,能改变原struct的值
p.Name = "roc"
}

匿名struct:

func main() {
a := struct{
Name string
Age int
}{
Name : "roc",
Age : 35,
}
fmt.Println(a) //{roc 35}
 
b := &struct{
Name string
Age int
}{
Name : "roc",
Age : 35,
}
fmt.Println(b) //&{roc 35}
}

嵌套struct:

type person struct {
Name string
Age int
Contact struct{
Phone,City string //知道Go把声明放在变量后面的好处了吧
}
}
func main() {
a := person{
Name:"roc",
Age:14,
}
a.Contact.Phone = "110"
a.Contact.City = "chengdu"
fmt.Println(a) //{roc 14 {110 chengdu}}
}

匿名字段:

type person struct {
string
int
}
func main() {
a := &person{"roc",25} //注意顺序必须和声明的类型顺序一致
fmt.Println(a) //&{roc 25}
}

struct组合

Go的struct可以嵌入组合其它struct,有点像是继承,下面是示例:

type human struct {
Sex int
}
type person struct {
human //嵌入结构,只需要写出结构类型
Name string
Age int
}
func main() {
a := person{
Name:"cpwl",
Age : 21,
human : human{ //默认用嵌入结构类型名作为字段名
Sex : 1,
},
}
fmt.Println(a) //{{1} cpwl 21}
//默认把嵌入结构的字段都给了外层结构,可以直接访问与赋值
fmt.Println(a.Sex) //1
//保留用嵌入结构类型名作为字段名的方式,
//是为了防止多个嵌入结构有相同字段名,发生冲突
fmt.Println(a.human.Sex) //1
}

方法method

Go不像其它面相对象语言一样可以写个class,然后在class里面写一堆方法,但是它也很巧妙的实现了这种效果,我们只需要在普通函数前面加个接受者(receiver,写在函数名前面的括号里面),这样编译器就知道这个函数(方法)属于哪个struct了。
下面是简单示例:

type A struct {
Name string
}
 
func (a A)foo() { //接收者写在函数名前面的括号里面
fmt.Println("foo")
}
 
func main() {
a := A{}
a.foo() //foo
}
需要注意的是,因为Go不支持函数重载,所以某个接收者(receiver)的某个方法只能对应一个函数,比如下面的就属于方法重复,编译无法通过:
func (a A)foo() {
fmt.Println("foo")
}
func (a A)foo(i int) {
fmt.Println(i)
}
这些可以叫做是方法绑定,接收者不仅可以是struct类型,还可以是任意自定义的其它类型,举个栗子:
type Integer int
func (integer *Integer)talk() {
fmt.Println("i am ",*integer)
}
func main() {
var i Integer = 250
i.talk() //i am 250
}
这下知道方法绑定是多么的灵活了吧

注意方法绑定只能绑定当前包里的类型

其实有两种调用方式,上面讲的那种官方管它叫method value,还有另一种调用方式,叫method expression:
type Integer int
func (integer *Integer)talk() {
fmt.Println("i am ",*integer)
}
func main() {
var i Integer = 250
(*Integer).talk(&i) //i am 250
}
最后说下访问权限,因为Go是以大小写来区分是公有还是私有,但都是针对包级别的,所以在包内所有的都能访问,而方法绑定本身只能绑定包内的类型,所以方法可以访问接收者所有成员。如果是包外调用某类型的方法,则需要看方法名是大写还是小写,大写能被包外访问,小写只能被包内访问。

接口interface

类型关键字为interface
不需要显式声明实现某个接口,只要实现相关方法就实现了接口
基本示例:

type Person interface {
Name() string
}
type Student struct {
name string
}
func (s Student)Name() string {
return s.name
}
func main() {
var p Person
p = Student{name:"roc"}
fmt.Println(p.Name()) //roc
}

接口也可以组合(相当于继承)
可以试探是否为某个struct或interface的实例(ok pattern),相当于Java中的instanceof

type Person interface {
Name() string
}
type Teacher interface {
Person //接口组合
Teach()
}
type MyTeacher struct {
name string
class string
}
func (t MyTeacher)Name() string {
return t.name
}
func (t MyTeacher)Teach() {
fmt.Println("I am teaching ",t.class)
}
func say_hello(p Person) {
if m,ok := p.(MyTeacher);ok { //看此Person是否为MyTeacher的实例,如果是再执行if内的内容
fmt.Println("hello ",m.name)
}
}
func main() {
var t Teacher = MyTeacher{
name : "roc",
class : "english",
}
fmt.Println(t.Name()) //roc
t.Teach() //I am teaching english
say_hello(t) //hello roc
}

可以使用匿名接口

var a interface{
Name() string
}

空接口可以看作是所有struct都实现了的。匿名空接口直接写成:interface{}
type switch可以判断某变量是哪种类型,并根据不同类型作不同处理

type A struct {
a string
}
type B struct {
b string
}
func say_hello(m interface{}) {
switch t := m.(type) { //type switch的关键
case A:
fmt.Println("hello ",t.a)
case B:
fmt.Println("hello ",t.b)
default:
fmt.Println("nobody")
}
}
func main() {
a := A{a:"a"}
b := B{b:"b"}
say_hello(a) //hello a
say_hello(b) //hello b
}

部分转载自:https://blog.csdn.net/QQ245671051