一、Go 语言结构
在我们开始学习 Go 编程语言的基础构建模块前,让我们先来了解 Go 语言最简单程序的结构。
Go Hello World 实例
Go 语言的基础组成有以下几个部分:
- 包声明
- 引入包
- 函数
- 变量
- 语句 & 表达式
- 注释
接下来让我们来看下简单的代码,该代码输出了"Hello World!":
package main import "fmt" func main() { /* 这是我的第一个简单的程序 */ fmt.Println("Hello, World!") }
让我们来看下以上程序的各个部分:
-
第一行代码 package main 定义了包名。你必须在源文件中非注释的第一行指明这个文件属于哪个包,如:package main。package main表示一个可独立执行的程序,每个 Go 应用程序都包含一个名为 main 的包。
-
下一行 import "fmt" 告诉 Go 编译器这个程序需要使用 fmt 包(的函数,或其他元素),fmt 包实现了格式化 IO(输入/输出)的函数。
-
下一行 func main() 是程序开始执行的函数。main 函数是每一个可执行程序所必须包含的,一般来说都是在启动后第一个执行的函数(如果有 init() 函数则会先执行该函数)。
-
下一行 /*...*/ 是注释,在程序执行时将被忽略。单行注释是最常见的注释形式,你可以在任何地方使用以 // 开头的单行注释。多行注释也叫块注释,均已以 /* 开头,并以 */ 结尾,且不可以嵌套使用,多行注释一般用于包的文档描述或注释成块的代码片段。
-
下一行 fmt.Println(...) 可以将字符串输出到控制台,并在最后自动增加换行字符 。
使用 fmt.Print("hello, world ") 可以得到相同的结果。
Print 和 Println 这两个函数也支持使用变量,如:fmt.Println(arr)。如果没有特别指定,它们会以默认的打印格式将变量 arr 输出到控制台。 -
当标识符(包括常量、变量、类型、函数名、结构字段等等)以一个大写字母开头,如:Group1,那么使用这种形式的标识符的对象就可以被外部包的代码所使用(客户端程序需要先导入这个包),这被称为导出(像面向对象语言中的 public);标识符如果以小写字母开头,则对包外是不可见的,但是他们在整个包的内部是可见并且可用的(像面向对象语言中的 protected )。
执行 Go 程序
让我们来看下如何编写 Go 代码并执行它。步骤如下:
-
打开编辑器如Sublime2,将以上代码添加到编辑器中。
-
将以上代码保存为 hello.go
-
打开命令行,并进入程序文件保存的目录中。
-
输入命令 go run hello.go 并按回车执行代码。
-
如果操作正确你将在屏幕上看到 "Hello World!" 字样的输出。
$ go run hello.go
Hello, World!
二、Go语言基础
Go 标记
Go 程序可以由多个标记组成,可以是关键字,标识符,常量,字符串,符号。如以下 GO 语句由 6 个标记组成:
fmt.Println("Hello, World!")
6 个标记是(每行一个):
1. fmt 2. . 3. Println 4. ( 5. "Hello, World!" 6. )
行分隔符
在 Go 程序中,一行代表一个语句结束。每个语句不需要像 C 家族中的其它语言一样以分号 ; 结尾,因为这些工作都将由 Go 编译器自动完成。
如果你打算将多个语句写在同一行,它们则必须使用 ; 人为区分,但在实际开发中我们并不鼓励这种做法。
以下为两个语句:
fmt.Println("Hello, World!") fmt.Println("666666666666666666")
注释
注释不会被编译,每一个包应该有相关注释。
单行注释是最常见的注释形式,你可以在任何地方使用以 // 开头的单行注释。多行注释也叫块注释,均已以 /* 开头,并以 */ 结尾。如:
// 单行注释 /* 哈哈哈哈 我是多行注释 */
标识符
标识符用来命名变量、类型等程序实体。一个标识符实际上就是一个或是多个字母(A~Z和a~z)数字(0~9)、下划线_组成的序列,但是第一个字符必须是字母或下划线而不能是数字。
以下是有效的标识符:
mahesh
kumar
abc
move_name
a_123
myname50
_temp
j
a23b9
retVal
以下是无效的标识符:
1ab(以数字开头) case(Go 语言的关键字) a+b(运算符是不允许的)
关键字
下面列举了 Go 代码中会使用到的 25 个关键字或保留字:
- var和const :变量和常量的声明
- var varName type 或者 varName : = value
- package and import: 导入
- func: 用于定义函数和方法
- return :用于从函数返回
- defer someCode :在函数退出之前执行
- go : 用于并行
- select 用于选择不同类型的通讯
- interface 用于定义接口
- struct 用于定义抽象数据类型
- break、case、continue、for、fallthrough、else、if、switch、goto、default 流程控制
- chan用于channel通讯
- type用于声明自定义类型
- map用于声明map类型数据
- range用于读取slice、map、channel数据
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 语言还有 36 个预定义标识符:
append | bool | byte | cap | close | complex | complex64 | complex128 | uint16 |
copy | false | float32 | float64 | imag | int | int8 | int16 | uint32 |
int32 | int64 | iota | len | make | new | nil | panic | uint64 |
println | real | recover | string | true | uint | uint8 | uintptr |
程序一般由关键字、常量、变量、运算符、类型和函数组成。
程序中可能会使用到这些分隔符:括号 (),中括号 [] 和大括号 {}。
程序中可能会使用到这些标点符号:.、,、;、: 和 …。
Go 语言的空格
Go 语言中变量的声明必须使用空格隔开,如:
var age int;
语句中适当使用空格能让程序看易阅读。
无空格:
fruit=apples+oranges;
在变量与运算符间加入空格,程序看起来更加美观,如:
fruit = apples + oranges;
go tools中可以使用gofmt 格式化代码
三、Go 语言数据类型
在 Go 编程语言中,数据类型用于声明函数和变量。
数据类型的出现是为了把数据分成所需内存大小不同的数据,编程的时候需要用大数据的时候才需要申请大内存,就可以充分利用内存。
Go 语言按类别有以下几种数据类型:
序号 | 类型和描述 |
---|---|
1 | 布尔型 布尔型的值只可以是常量 true 或者 false。一个简单的例子:var b bool = true。 |
2 | 数字类型 整型 int 和浮点型 float,Go 语言支持整型和浮点型数字,并且原生支持复数,其中位的运算采用补码。 |
3 | 字符串类型: 字符串就是一串固定长度的字符连接起来的字符序列。Go的字符串是由单个字节连接起来的。Go语言的字符串的字节使用UTF-8编码标识Unicode文本。 |
4 | 派生类型: 包括:
|
数字类型
Go 也有基于架构的类型,例如:int、uint 和 uintptr。
序号 | 类型和描述 |
---|---|
1 | uint8 无符号 8 位整型 (0 到 255) |
2 | uint16 无符号 16 位整型 (0 到 65535) |
3 | uint32 无符号 32 位整型 (0 到 4294967295) |
4 | uint64 无符号 64 位整型 (0 到 18446744073709551615) |
5 | int8 有符号 8 位整型 (-128 到 127) |
6 | int16 有符号 16 位整型 (-32768 到 32767) |
7 | int32 有符号 32 位整型 (-2147483648 到 2147483647) |
8 | int64 有符号 64 位整型 (-9223372036854775808 到 9223372036854775807) |
浮点型:
序号 | 类型和描述 |
---|---|
1 | float32 IEEE-754 32位浮点型数 |
2 | float64 IEEE-754 64位浮点型数 |
3 | complex64 32 位实数和虚数 |
4 | complex128 64 位实数和虚数 |
其他数字类型
以下列出了其他更多的数字类型:
序号 | 类型和描述 |
---|---|
1 | byte 类似 uint8 |
2 | rune 类似 int32 |
3 | uint 32 或 64 位 |
4 | int 与 uint 一样大小 |
5 | uintptr 无符号整型,用于存放一个指针 |
四、Go 语言常量
常量是一个简单值的标识符,在程序运行时,不会被修改的量。
常量中的数据类型只可以是布尔型、数字型(整数型、浮点型和复数)和字符串型。
常量的定义格式:
package main import "fmt" //常量的数据类型 只可以是布尔型、数字型(整数型、浮点型、复数)、字符串型 func main() { const identifier string = "chao" //声明多个相同类型 const name, last_name, first_name= "yu", "yuyu", "yuyuyu" //显示类型定义 const age int =10 //隠式类型定义 const year = 5 var age_year int age_year=age*year fmt.Printf("年纪是:%d",age_year) fmt.Println() fmt.Println(name,last_name,first_name) }
多个常量的应用:
package main import "unsafe" // 常量还可以用作枚举 const ( Unknow = 0 Female = 1 Male = 2 ) //常量可以计算表达式的值len(),cap(),unsafe.Sizeof()等, 常量表达式中,函数必须是内置函数,否则编译不过 const ( a="abc" b=len(a) c=unsafe.Sizeof(a) ) func main() { println(a,b,c) }
iota
iota,特殊常量,可以认为是一个可以被编译器修改的常量。
在每一个const关键字出现时,被重置为0,然后再下一个const出现之前,每出现一次iota,其所代表的数字会自动增加1。
iota 可以被用作枚举值:
const ( a = iota b = iota c = iota ) //因此这里,第一个iota a是0,新一行使用iota就自动加1,所以是 0,1,2 /* 也可以写作 const( a = iota b c ) */ func main() { println(a, b, c) }
iota 用法
package main // iota 特殊常量,一个可以被编译器修改的常量 // 每一个const关键字出现时,被重置为0,然后下一个const出现之前,每一个iota,代表的数字会增加1 import "fmt" const( i=1<<iota j=3<<iota k l // k=3<<2 l=3<<3 位运算,理解成 3乘以 2的3次方,3*8 ,所以3<<3等于24 ) func main(){ fmt.Println("i=",i) fmt.Println("j=",j) fmt.Println("k=",k) fmt.Println("l=",l) }
五、Go语言之讲解GOROOT、GOPATH、GOBIN
Go是一门全新的静态类型开发语言,具有自动垃圾回收
,丰富的内置类型
,函数多返回值
,错误处理
,匿名函数
,并发编程
,反射
等特性.
go
命令依赖一个重要的环境变量:$GOPATH
GOPATH
允许多个目录,当有多个目录时,请注意分隔符,多个目录的时候Windows是分号;
当有多个GOPATH时
默认将go get
获取的包存放在第一个目录下
$GOPATH
目录约定有三个子目录
src
存放源代码(比如:.go .c .h .s等) 按照golang默认约定,go run,go install等命令的当前工作路径(即在此路径下执行上述命令)。pkg
编译时生成的中间文件(比如:.a) golang编译包时bin
编译后生成的可执行文件(为了方便,可以把此目录加入到 $PATH 变量中,如果有多个gopath,那么使用${GOPATH//://bin:}/bin
添加所有的bin目录)
代码目录结构规划
GOPATH下的src目录就是接下来开发程序的主要目录,所有的源码都是放在这个目录下面,那么一般我们的做法就是一个目录一个项目,
例如: $GOPATH/src/mymath 表示mymath这个应用包或者可执行应用,这个根据package是main还是其他来决定,main的话就是可执行应用,其他的话就是应用包,这个会在后续详细介绍package。
首先看下我的go环境:go env
C:UsersAdministrator>go env set GOARCH=amd64 set GOBIN= set GOEXE=.exe set GOHOSTARCH=amd64 set GOHOSTOS=windows set GOOS=windows set GOPATH=D:project set GORACE= set GOROOT=D:BaiduNetdiskDownloadgo set GOTOOLDIR=D:BaiduNetdiskDownloadgopkg oolwindows_amd64 set GCCGO=gccgo set CC=gcc set GOGCCFLAGS=-m64 -mthreads -fmessage-length=0 set CXX=g++ set CGO_ENABLED=1 set CGO_CFLAGS=-g -O2 set CGO_CPPFLAGS= set CGO_CXXFLAGS=-g -O2 set CGO_FFLAGS=-g -O2 set CGO_LDFLAGS=-g -O2 set PKG_CONFIG=pkg-config
GOROOT
其实就是golang 的安装路径
当你安装好golang之后其实这个就已经有了
GOBIN
首先看一下结构:
我们通常是在project目录下执行go build,例如:
D:projectsrcgo_devday1package_examplemain>go run main.go 400 100
现在需要编译main.go,golang 会自动去src下找hello目录,因为我的main.go中代码的开通导入了packag main包,所以可以编译成可执行文件,但是这样默认在当前目录下生成可执行文件,虽然可以指定目录,但是还是感觉不是非常方便
d:project>go build go_dev/day1/package_examplemain
所以还有两个非常好用的命令:go get 和go install
go get
go get会做两件事:
1. 从远程下载需要用到的包
2. 执行go install
go install
go install 会生成可执行文件直接放到bin目录下,当然这是有前提的
你编译的是可执行文件,如果是一个普通的包,会被编译生成到pkg目录下该文件是.a结尾
关于go的整体一个开发目录
go_project // go_project为GOPATH目录 -- bin -- myApp1 // 编译生成 -- myApp2 // 编译生成 -- myApp3 // 编译生成 -- pkg -- src -- myApp1 // project1 -- models -- controllers -- others -- main.go -- myApp2 // project2 -- models -- controllers -- others -- main.go -- myApp3 // project3 -- models -- controllers -- others -- main.go
Linux下配置go环境
1、首先下载linux下的go包:https://studygolang.com/dl/golang/go1.9.2.linux-amd64.tar.gz 2、下载之后 tar -zxvf go1.9.2.linux-amd64.tar.gz 解压源码包 3、移动到 /usr/local/go 也就是GOROOT 4、设置GOPATH,还有PATH环境变量 export GOROOT=/usr/local/go #设置为go安装的路径 export GOPATH=$HOME/gocode #默认安装包的路径 export PATH=$PATH:$GOROOT/bin:$GOPATH/bin
查看Linux go env
GOARCH="amd64" GOBIN="" GOEXE="" GOHOSTARCH="amd64" GOHOSTOS="linux" GOOS="linux" GOPATH="/root/gocode" GORACE="" GOROOT="/usr/local/go" GOTOOLDIR="/usr/local/go/pkg/tool/linux_amd64" GCCGO="gccgo" CC="gcc" GOGCCFLAGS="-fPIC -m64 -pthread -fmessage-length=0 -fdebug-prefix-map=/tmp/go-build057487015=/tmp/go-build -gno-record-gcc-switches" CXX="g++" CGO_ENABLED="1" CGO_CFLAGS="-g -O2" CGO_CPPFLAGS="" CGO_CXXFLAGS="-g -O2" CGO_FFLAGS="-g -O2" CGO_LDFLAGS="-g -O2" PKG_CONFIG="pkg-config"
参考文章地址:
https://studygolang.com/articles/7202
http://www.cnblogs.com/zhaof/p/7906722.html
记录GOPATH在GOLAND中的坑
首先环境已配置好:
GO的目录结构是:
add.go
package calc
//函数名必须大写首字母,不然外部包找不到 func Add(a int,b int)(int){ return a+b }
sub.go
package calc func Sub(a int,b int)(int){ return a-b }
执行文件,main.go
package main import "fmt"
//导入包路径,这里src省略,默认加在GOPATH
import "go_dev/day1/package_example/calc" func main(){ sum := calc.Add(100,300) sub := calc.Sub(200,100) fmt.Println(sum,sub) }
在cmd正确执行是:
这样是没问题的,GOPATH已经确定正确
但是GOLAND有个坑
在IDE中执行一直报错:
这里的GOPATH已经出错了,所以编译找不到,我检查了go env也是正确的GOPATH....
原来我忘记了GOLAND IDE中也有设置:
在这里修改为正确的GOPATH,再次执行:
package main // 关键字var 声明变量 类型信息放在变量名后 //声明一个int型变量 var v1 int //声明一个string类型 var v2 string //声明多个变量 var v3, v4 bool //[0 0 0 0 0 0 0 0 0 0] 数组 var v5 [10]int //数组切片 var v6 []int //声明结构体 var v7 struct { f int } // 声明指针 var v8 *int //声明map key为string类型,value是int类型 var v9 map[string]int // 匿名函数和闭包 var v10 func(a int) int //多个需要声明的变量放在一起 var ( name int age string )
package main import "fmt" func main() { //声明变量的初始化,var 可以省略写法 var v1 int = 10 //编译器可以自动算出v2的类型 var v2 = 10 //编译器自动算出v3的类型 //同时进行变量声明和初始化 v3 := 10 fmt.Println(v1, v2, v3) } // 出现在:= 左侧的变量 只能声明一次,不可重复
package main import "fmt" //声明之后再赋值 func main() { var i int i = 100 var j int j = 50 //Go语言的多重赋值,如下代码交换i和j的变量 //go的多重赋值特性可以很明显的优化代码,相比c/c++ i, j = j, i fmt.Println(i, j) }
package main import "fmt" //使用强类型语言编程时,调用函数时为了获取一个值,却因为函数返回多个值,又得定义一堆变量 //可以使用多重返回和匿名变量来避免这些问题 func GetName() (firstname, lastname, nickname string) { return "yu", "yuchao", "chaoge" } //只想获得nickname,函数调用语句可以写 func main() { //优化代码的清晰度 _, _, nickname := GetName() fmt.Println(nickname) }
package main //声明go代码所属的包,包是go例最基本的分发单位,要生成可执行程序,必须名为main,且下面有个main()函数,作为执行起点 //导入本程序所以来的包,下列用到Println()函数,所以导入fmt import "fmt" //不得在源代码中写入未使用的包,否则编译器会出错 //软件工程的设计哲学,强制左花括号 { 的放置位置 //函数名的大小写规则 func Compute(value1 int, value2 float64) (resule float64, err error) { //函数体 fmt.Println(value1, value2) return } //main函数不能带参数,也不能定义返回值,命令行传入的参数存在os.Args变量中 func main() { Compute(1, 3) }
package main //常量是编译期间就已知,而且不可改变,可以是数值类型(整型、浮点型、复数)、布尔类型、字符串 const Pi float64 = 3.14159265358979323846 //无类型浮点常量 const Zero = 0.0 const ( //无类型常量 size int64 = 1024 eof = -1 ) //常量多重赋值 u=0.0 v=3.0 const u, v float32 = 0, 3 //无类型整型,字符串常量 const a, b, c = 3, 4, "foo"
1. 任何一个代码文件隶属于一个包 2. import 关键字,引用其他包: import(“fmt”) import(“os”) 通常习惯写成: import ( “fmt” “os” ) 3. golang可执行程序,package main, 并且有且只有一个main入口函数 4. 包中函数调用: a. 同一个包中函数,直接调用 b. 不同包中函数,通过包名+点+ 函数名进行调用 5. 包访问控制规则: 大写意味着这个函数/变量是可导出的 小写意味着这个函数/变量是私有的, 包外部不能访问
package main /* 算出结果是: 0+10=10 1+9=10 2+8=10 3+7=10 4+6=10 5+5=10 6+4=10 7+3=10 8+2=10 9+1=10 10+0=10 */ import "fmt" func list(n int) { for i := 0; i <= n; i++ { fmt.Printf("%d+%d=%d ", i, n-i, n) } } func main() { list(10) }
2. 一个程序包含两个包add和main,其中add包中有两个变量:Name和age。请问main
包中如何访问Name和age?
package main import ( //包别名的用法 a "fmt" a "go_dev/day2/example2/add" ) func main() { fmt.Println("Name=:", a.Name) //add.age是小写 私有变量,所以找不到 fmt.Println("age=:", a.Age) }
package add import ( //_"go_dev/day2/example2/test" ) func init() { Name = "hello world" Age = 10 } var Name string = "xxxxx" var Age int = 66
3. 包别名的应用,开发一个程序,使用包别名来访问包中的函数?
答案同2题
4. 每个源文件都可以包含一个init函数,这个init函数自动被go运行框架调用。开发一个程序
演示这个功能?
目录结构如图:
package main import ( //包别名的用法 a "fmt" a "go_dev/day2/example2/add" ) func main() { //5 打印add中的 fmt.Println("Name=:", a.Name) //add.age是小写 私有变量,所以找不到 //6 fmt.Println("age=:", a.Age) }
package test import "fmt" var Name string = "This is test package" var Age int = 1000 func init() { //1 fmt.Println("this is a test,init") //2 fmt.Println("test.package.Name=", Name) //3 fmt.Println("test.package.Age=", Age) Age=10 //4 fmt.Println("test.package.age=",Age) }
package add import ( _"go_dev/day2/example2/test" ) func init() { Name = "hello world" Age = 10 } var Name string = "xxxxx" var Age int = 66
6. 定义两个常量Man=1和Female=2,获取当前时间的秒数,如果能被Female整除,则
在终端打印female,否则打印man。
Second := time.Now().Unix()
package main import ( "fmt" "time" ) const ( Man = iota + 1 Female ) /* 6. 定义两个常量Man=1和Female=2,获取当前时间的秒数,如果能被Female整除,则 在终端打印female,否则打印man。 Second := time.Now().Unix() */ func main() { //for 就是个死循环 for { time.Sleep(100000 * time.Microsecond) second := time.Now().Unix() if second%Female == 0 { fmt.Println("female") } else { fmt.Println("man") } } }