Go语言入门(一)
Day 01
01 知名编程语言或系统的发展简史
02 Go语言的前世今生
- 缺少一个执行兼顾运行效率与开发效率的语言:
- C++运行效率高,开发效率低。
- Java开发效率高,运行效率低。
03 Go语言logo和版本
- Go语言的最大优势是执行速度和开发效率都不错:
- 执行速度:编译型静态语言。
- 开发效率:语法与结构简明。
04 Go语言的核心特性
- 并发编程
- 内存回收(GC)
- 内存分配
- 编译
- 网络编程
- 函数多返回值
- 语言交互性
- 异常处理
05 Go语言和其他语言的对比
06 Go语言能做什么
07 Go语言环境搭建
08 第一个程序:HelloWorld
|
|
09 Go的执行原理以及Go的命令
Go的源码文件
- 命令源码文件:声明自己属于 main 代码包、包含无参数声明和结果声明的 main 函数。
- 库源码文件。
- 测试源码文件。
Go的命令
go run
:- 专门用来运行命令源码文件的命令,注意,这个命令不是用来运行所有 Go 的源码文件的!
- go run 命令只能接受一个命令源码文件以及若干个库源码文件(必须同属于 main 包)作为文件参数,且不能接受测试源码文件。它在执行时会检查源码文件的类型。如果参数中有多个或者没有命令源码文件,那么 go run 命令就只会打印错误提示信息并退出,而不会继续执行。
- go build:go build 用于编译我们指定的源码文件或代码包以及它们的依赖包。但是注意如果用来编译非命令源码文件,即库源码文件,go build 执行完是不会产生任何结果的。这种情况下,go build 命令只是检查库源码文件的有效性,只会做检查性的编译,而不会输出任何结果文件。
- 如果是普通包,当你执行go build命令后,不会产生任何文件。
- 如果是main包,当只执行go build命令后,会在当前目录下生成一个可执行文件。如果需要在$GOPATH/bin目录下生成相应的exe文件,需要执行go install 或者使用 go build -o 路径/可执行文件。
- 如果某个文件夹下有多个文件,而你只想编译其中某一个文件,可以在 go build 之后加上文件名,例如 go build a.go;go build 命令默认会编译当前目录下的所有go文件。
- 你也可以指定编译输出的文件名。比如,我们可以指定go build -o 可执行文件名,默认情况是你的package名(非main包),或者是第一个源文件的文件名(main包)。
- go build 会忽略目录下以”_”或者”.”开头的go文件。
- go install:用来编译并安装代码包或者源码文件的。
- go install 命令在内部实际上分成了两步操作:第一步是生成结果文件(可执行文件或者.a包),第二步会把编译好的结果移到
$GOPATH/pkg
或者$GOPATH/bin
。 - 可执行文件: 一般是 go install 带main函数的go文件产生的,有函数入口,所有可以直接运行。
- .a应用包: 一般是 go install 不包含main函数的go文件产生的,没有函数入口,只能被调用。
- 安装代码包会在当前工作区的 pkg 的平台相关目录下生成归档文件(即 .a 文件)。
- 安装命令源码文件会在当前工作区的 bin 目录(如果 GOPATH 下有多个工作区,就会放在 GOBIN 目录下)生成可执行文件。
- go install 命令在内部实际上分成了两步操作:第一步是生成结果文件(可执行文件或者.a包),第二步会把编译好的结果移到
- go get:用于从远程代码仓库(比如 Github )上下载并安装代码包。注意,go get 命令会把当前的代码包下载到 $GOPATH 中的第一个工作区的 src 目录中,并安装。
- 调用 git clone 方法下载源码。
- 编译。
- 把库源码文件编译成归档文件安装到 pkg 对应的相关平台目录下。
10 安装Goland开发工具
11 编码规范
命名规范
- 以字母或下划线开头。
- 不允许在命名时中使用@、$和%等标点符号。
- 区分大小写。
- 当命名(包括常量、变量、类型、函数名、结构字段等等)以一个大写字母开头,如:Group1,那么使用这种形式的标识符的对象就可以被外部包的代码所使用(客户端程序需要先导入这个包),这被称为导出(像面向对象语言中的 public)。
- 命名如果以小写字母开头,则对包外是不可见的,但是他们在整个包的内部是可见并且可用的(像面向对象语言中的 private )。
包命名:package
-
保持package的名字和目录保持一致,尽量采取有意义的包名,简短,有意义,尽量和标准库不要冲突。包名应该为小写单词,不要使用下划线或者混合大小写。
1 2 3
package demo package main
文件命名
-
尽量采取有意义的文件名,简短,有意义,应该为小写单词,使用下划线分隔各个单词。
1
my_test.go
结构体命名
-
采用驼峰命名法,首字母根据访问控制大写或者小写
-
struct 申明和初始化格式采用多行,例如下面:
1 2 3 4 5 6 7 8 9 10 11
// 多行申明 type User struct{ Username string Email string } // 多行初始化 u := User{ Username: "astaxie", Email: "astaxie@gmail.com", }
接口命名
- 命名规则基本和上面的结构体类似。
变量命名
- 和结构体类似,变量名称一般遵循驼峰法,首字母根据访问控制原则大写或者小写。
- 若变量类型为 bool 类型,则名称应以 Has, Is, Can 或 Allow 开头。
常量命名
-
常量均需使用全部大写字母组成,并使用下划线分词
1
const APP_VER = "1.0"
-
如果是枚举类型的常量,需要先创建相应类型:
1 2 3 4 5 6
type Scheme string const ( HTTP Scheme = "http" HTTPS Scheme = "https" )
关键字
注释
- Go提供C风格的
/* */
块注释和C ++风格的//
行注释。
包注释
-
每个包都应该有一个包注释,一个位于package子句之前的块注释或行注释。包如果有多个go文件,只需要出现在一个go文件中(一般是和包同名的文件)即可。 包注释应该包含下面基本信息(请严格按照这个顺序,简介,创建人,创建时间):
-
包的基本简介(包名,简介)
-
创建者,格式: 创建人: rtx 名
-
创建时间,格式:创建时间: yyyyMMdd
-
例如 util 包的注释示例如下
|
|
结构(接口)注释
-
每个自定义的结构体或者接口都应该有注释说明,该注释对结构进行简要介绍,放在结构体定义的前一行,格式为: 结构体名, 结构体说明。同时结构体内的每个成员变量都要有说明,该说明放在成员变量的后面(注意对齐),实例如下:
1 2 3 4 5
// User , 用户对象,定义了用户的基础信息 type User struct{ Username string // 用户名 Email string // 邮箱 }
函数(方法)注释
-
每个函数,或者方法(结构体或者接口下的函数称为方法)都应该有注释说明,函数的注释应该包括三个方面(严格按照此顺序撰写):
-
简要说明,格式说明:以函数名开头,“,”分隔说明部分
-
参数列表:每行一个参数,参数名开头,“,”分隔说明部分
-
返回值: 每行一个返回值
-
示例如下:
|
|
代码风格
缩进和折行
语句的结尾
- Go语言中是不需要类似于Java需要分号结尾,默认一行就是一条数据。
- 如果你打算将多个语句写在同一行,它们则必须使用 ;
括号和空格
- go 会强制左大括号不换行,换行会报语法错误。
import 规范
-
import在多行的情况下,goimports会自动帮你格式化,但是我们这里还是规范一下import的一些规范,如果你在一个文件里面引入了一个package,还是建议采用如下格式:
1 2 3
import ( "fmt" )
-
如果你的包引入了三种类型的包,标准库包,程序内部包,第三方包,建议采用如下方式进行组织你的包:
1 2 3 4 5 6 7 8 9 10 11
import ( "encoding/json" "strings" "myproject/models" "myproject/controller" "myproject/utils" "github.com/astaxie/beego" "github.com/go-sql-driver/mysql" )
-
有顺序的引入包,不同的类型采用空格分离,第一种实标准库,第二是项目包,第三是第三方包。
-
在项目中不要使用相对路径引入包:
1 2 3 4 5
// 这是不好的导入 import “../net” // 这是正确的做法 import “github.com/repo/proj/src/net”
-
但是如果是引入本项目中的其他包,最好使用相对路径。
错误处理
-
错误处理的原则就是不能丢弃任何有返回err的调用,不要使用 _ 丢弃,必须全部处理。接收到错误,要么返回err,或者使用log记录下来。
-
尽早return:一旦有错误发生,马上返回。
-
尽量不要使用panic,除非你知道你在做什么。
-
错误描述如果是英文必须为小写,不需要标点结尾。
-
采用独立的错误流进行处理。
1 2 3 4 5 6 7 8 9 10 11 12 13
// 错误写法 if err != nil { // error handling } else { // normal code } // 正确写法 if err != nil { // error handling return // or continue, etc. } // normal code
测试
- 单元测试文件名命名规范为 example_test.go
- 测试用例的函数名称必须以 Test 开头,例如:TestExample
- 每个重要的函数都要首先编写测试用例,测试用例和正规代码一起提交方便进行回归测试。
Day 02
变量
变量的定义
变量的声明
-
指定变量类型,声明后若不赋值,使用默认值。
1 2
var name type name = value
-
根据值自行判定变量类型(类型推断Type inference)
-
如果一个变量有一个初始值,Go将自动能够使用初始值来推断该变量的类型。因此,如果变量具有初始值,则可以省略变量声明中的类型。
1
var name = value
-
-
简短声明
-
省略var, 注意 :=左侧的变量不应该是已经声明过的(多个变量同时声明时,至少保证一个是新变量),否则会导致编译错误。
-
这种方式它只能被用在函数体内,而不可以用于全局变量的声明与赋值
1 2 3 4 5 6
name := value // 例如 var a int = 10 var b = 10 c : = 10
-
多变量声明
-
第一种,以逗号分隔,声明与赋值分开,若不赋值,存在默认值
1 2
var name1, name2, name3 type name1, name2, name3 = v1, v2, v3
-
第二种,直接赋值,下面的变量类型可以是不同的类型
1
var name1, name2, name3 = v1, v2, v3
-
第三种,集合类型
1 2 3 4
var ( name1 type1 name2 type2 )
注意事项
- 变量必须先定义才能使用
- go语言是静态语言,要求变量的类型和赋值的类型必须一致。
- 变量名不能冲突。(同一个作用于域内不能冲突)
- 简短定义方式,左边的变量名至少有一个是新的
- 简短定义方式,不能定义全局变量。
- 变量的零值。也叫默认值。
- 变量定义了就要使用,否则无法通过编译。
- 在相同的代码块中,我们不可以再次对于相同名称的变量使用初始化声明。
- 在同一个作用域中,已存在同名的变量,则之后的声明初始化,则退化为赋值操作。
常量
常量声明
-
常量是一个简单值的标识符,在程序运行时,不会被修改的量。
1 2 3
const identifier [type] = value 显式类型定义: const b string = "abc" 隐式类型定义: const b = "abc"
-
常量可以作为枚举,常量组
1 2 3 4 5
const ( Unknown = 0 Female = 1 Male = 2 )
注意事项
-
常量中的数据类型只可以是布尔型、数字型(整数型、浮点型和复数)和字符串型。
-
不曾使用的常量,在编译的时候,是不会报错的。
-
显示指定类型的时候,必须确保常量左右值类型一致,需要时可做显示类型转换。这与变量就不一样了,变量是可以是不同的类型值。
iota
-
iota,特殊常量,可以认为是一个可以被编译器修改的常量。
-
iota 可以被用作枚举值:
1 2 3 4 5
const ( a = iota b = iota c = iota )
-
第一个 iota 等于 0,每当 iota 在新的一行被使用时,它的值都会自动加 1;所以 a=0, b=1, c=2 可以简写为如下形式:
1 2 3 4 5
const ( a = iota b c )
Day 03
数据类型
基本数据类型
-
布尔类型
-
数值类型:
- 整数型:
-
int8:有符号 8 位整型 (-128 到 127);长度:8bit;byte
-
int16:有符号 16 位整型 (-32768 到 32767)
-
int32:有符号 32 位整型 (-2147483648 到 2147483647);rune
-
int64:有符号 64 位整型 (-9223372036854775808 到 9223372036854775807)
-
uint8:无符号 8 位整型 (0 到 255);8位都用于表示数值。
-
uint16:无符号 16 位整型 (0 到 65535)
-
uint32:无符号 32 位整型 (0 到 4294967295)
-
uint64:无符号 64 位整型 (0 到 18446744073709551615)
-
- 浮点型:
-
float32:IEEE-754 32位浮点型数
-
float64:IEEE-754 64位浮点型数
-
complex64:32 位实数和虚数
-
complex128:64 位实数和虚数
-
- 整数型:
-
字符串型
-
字符串就是一串固定长度的字符连接起来的字符序列。Go的字符串是由单个字节连接起来的。Go语言的字符串的字节使用UTF-8编码标识Unicode文本
1 2
var str string str = "Hello World"
-
-
数据类型转换
- 语法格式:Type(Value)
- 常数:在有需要的时候,会自动转型
- 变量:需要手动转型 T(V)
- 注意点:兼容类型可以转换
复合数据类型
运算符
算术运算符
|
|
- ++与–不能参与运算,不能放在变量之前。
关系运算符
|
|
逻辑运算符
运算符 | 描述 |
---|---|
&& | 所谓逻辑与运算符。如果两个操作数都非零,则条件变为真 |
|| | 所谓的逻辑或操作。如果任何两个操作数是非零,则条件变为真 |
! | 所谓逻辑非运算符。使用反转操作数的逻辑状态。如果条件为真,那么逻辑非操后结果为假 |
位运算符
A | B | A&B | A|B | A^B |
---|---|---|---|---|
0 | 0 | 0 | 0 | 0 |
0 | 1 | 0 | 1 | 1 |
1 | 1 | 1 | 1 | 0 |
1 | 0 | 0 | 1 | 1 |
运算 | 描述(假设A为60,B为13) | 示例 |
---|---|---|
& | 二进制与操作副本位的结果,如果它存在于两个操作数 | (A & B) = 12, 也就是 0000 1100 |
| | 二进制或操作副本,如果它存在一个操作数 | (A | B) = 61, 也就是 0011 1101 |
^ | 二进制异或操作副本,如果它被设置在一个操作数就是按位取非 | (A ^ B) = 49, 也就是 0011 0001 |
&^ | 二进制位清空&^ | (A&^B)=48,也就是110000 |
« | 二进制左移位运算符。左边的操作数的值向左移动由右操作数指定的位数 | A « 2 =240 也就是 1111 0000 |
» | 二进制向右移位运算符。左边的操作数的值由右操作数指定的位数向右移动 | A » 2 = 15 也就是 0000 1111 |
赋值运算符
运算符 | 描述 | 示例 |
---|---|---|
= | 简单的赋值操作符,分配值从右边的操作数左侧的操作数 | C = A + B 将分配A + B的值到C |
+= | 相加并赋值运算符,它增加了右操作数左操作数和分配结果左操作数 | C += A 相当于 C = C + A |
-= | 减和赋值运算符,它减去右操作数从左侧的操作数和分配结果左操作数 | C -= A 相当于 C = C - A |
*= | 乘法和赋值运算符,它乘以右边的操作数与左操作数和分配结果左操作数 | C *= A 相当于 C = C * A |
/= | 除法赋值运算符,它把左操作数与右操作数和分配结果左操作数 | C /= A 相当于 C = C / A |
%= | 模量和赋值运算符,它需要使用两个操作数的模量和分配结果左操作数 | C %= A 相当于 C = C % A |
«= | 左移位并赋值运算符 | C «= 2 相同于 C = C « 2 |
»= | 向右移位并赋值运算符 | C »= 2 相同于 C = C » 2 |
&= | 按位与赋值运算符 | C &= 2 相同于 C = C & 2 |
^= | 按位异或并赋值运算符 | C ^= 2 相同于 C = C ^ 2 |
|= | 按位或并赋值运算符 | C |= 2 相同于 C = C | 2 |
运算符优先级
优先级 | 运算符 |
---|---|
7 | ~ ! ++ – |
6 | * / % « » & &^ |
5 | + - ^ |
4 | == != < <= >= > |
3 | <- |
2 | && |
1 | || |
键盘输入和打印输出
常用打印函数
-
格式化打印:func Printf(format string, a …interface{}) (n int, err error)
-
格式化打印中的常用占位符:
|
|
键盘输入
- 常用方法:
func Scan(a …interface{}) (n int, err error)
func Scanf(format string, a …interface{}) (n int, err error)
func Scanln(a …interface{}) (n int, err error)
|
|
Day 04
程序的流程结构
- 顺序结构:从上向下,逐行执行。
- 选择结构:条件满足,某些代码才会执行。0-1次
- 分支语句:if,switch,select
- 循环结构:条件满足,某些代码会被反复的执行多次。0-N次
- 循环语句:for
条件语句
if 语句
-
语法格式:
1 2 3
if 布尔表达式 { /* 在布尔表达式为 true 时执行 */ }
1 2 3 4 5
if 布尔表达式 { /* 在布尔表达式为 true 时执行 */ } else { /* 在布尔表达式为 false 时执行 */ }
1 2 3 4 5 6 7
if 布尔表达式1 { /* 在布尔表达式1为 true 时执行 */ } else if 布尔表达式2{ /* 在布尔表达式1为 false ,布尔表达式2为true时执行 */ } else{ /* 在上面两个布尔表达式都为false时,执行*/ }
if 变体
-
如果其中包含一个可选的语句组件(在评估条件之前执行),则还有一个变体。它的语法是
1 2 3 4 5 6 7
if statement; condition { } if condition{ }
-
示例代码:
1 2 3 4 5 6 7 8 9 10 11 12 13
package main import ( "fmt" ) func main() { if num := 10; num % 2 == 0 { //checks if number is even fmt.Println(num,"is even") } else { fmt.Println(num,"is odd") } }
需要注意的是,num的定义在if里,那么只能够在该if..else语句块中使用,否则编译器会报错的。
switch语句
-
switch是一个条件语句,它计算表达式并将其与可能匹配的列表进行比较,并根据匹配执行代码块。它可以被认为是一种惯用的方式来写多个if else子句。
-
switch 语句用于基于不同条件执行不同动作,每一个 case 分支都是唯一的,从上直下逐一测试,直到匹配为止。
-
switch 语句执行的过程从上至下,直到找到匹配项,匹配项后面也不需要再加break。
-
而如果switch没有表达式,它会匹配true
-
Go里面switch默认相当于每个case最后带有break,匹配成功后不会自动向下执行其他case,而是跳出整个switch, 但是可以使用fallthrough强制执行后面的case代码。
-
变量 var1 可以是任何类型,而 val1 和 val2 则可以是同类型的任意值。类型不被局限于常量或整数,但必须是相同的类型;或者最终结果为相同类型的表达式。
-
您可以同时测试多个可能符合条件的值,使用逗号分割它们,例如:case val1, val2, val3。
-
和 if 一样可以在swath关键词后初始化变量。
1 2 3 4 5 6 7 8
switch var1 { case val1: ... case val2: ... default: ... }
示例代码:
|
|
fallthrough
-
如需贯通后续的case,就添加fallthrough。一个fallthrough只穿透后续一个case,必须位于case的最后一行。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
package main import ( "fmt" ) type data [2]int func main() { switch x := 5; x { default: fmt.Println(x) case 5: x += 10 fmt.Println(x) fallthrough case 6: x += 20 fmt.Println(x) } }
运行结果:
|
|
switch的注意事项
- case后的常量值不能重复
- case后可以有多个常量值
- fallthrough应该是某个case的最后一行。如果它出现在中间的某个地方,编译器就会抛出错误。
Type Switch
Day 05
循环语句
for循环(Go 没有while循环)
-
语法结构:
1
for init; condition; post { }
-
示例代码:
1 2 3 4 5 6 7 8 9 10 11
package main import ( "fmt" ) func main() { for i := 1; i <= 10; i++ { fmt.Printf(" %d",i) } }
for循环变体
-
所有的三个组成部分,即初始化、条件和post都是可选的。
-
效果与while相似
1
for condition { }
-
效果与for(;;) 一样
1
for { }
-
for 循环的 range 格式可以对 slice、map、数组、字符串等进行迭代循环
1 2 3
for key, value := range oldMap { newMap[key] = value }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
package main import "fmt" func main() { var b int = 15 var a int numbers := [6]int{1, 2, 3, 5} /* for 循环 */ for a := 0; a < 10; a++ { fmt.Printf("a 的值为: %d\n", a) } for a < b { a++ fmt.Printf("a 的值为: %d\n", a) } for i,x:= range numbers { fmt.Printf("第 %d 位 x 的值 = %d\n", i,x) } }
多层for循环
跳出循环的语句
break语句
continue语句
goto语句
-
goto:可以无条件地转移到过程中指定的行。
-
语法结构:
1 2 3 4
goto label; .. .. label: statement;
-
可用于集中处理错误
1 2 3 4 5 6 7 8 9 10 11 12 13
err := firstCheckError() if err != nil { goto onExit } err = secondCheckError() if err != nil { goto onExit } fmt.Println("done") return onExit: fmt.Println(err) exitProcess()