简介
Go语言是谷歌2009年发布的第二款开源编程语言(系统开发语言),它是基于编译、垃圾收集和并发的编程语言。Go语言专门针对多处理器系统应用程序的编程进行了优化,使用Go编译的程序可以媲美C/C++代码的速度,而且更加安全、支持并行进程。作为出现在21世纪的语言,其近C的执行性能和近解析型语言的开发效率,以及近乎于完美的编译速度,已经风靡全球。
特别是在云项目中,大部分使用了Golang来开发。不得不说,Golang早已深入人心。而对于一个没有历史负担的新项目,Golang或许是一个不二的选择。很多人将Go语言称为21世纪的C语言,因为Go不仅拥有C的简洁和性能,而且还很好地提供了21世纪互联网环境下服务端开发的各种实用特性。
被称为Go语言之父的Rob Pike就曾说过,,你是否同意Go语言,取决于你是认可少就是多,还是少就是少(less is more or less is less)。Go语言的整个设计哲学就是:将简单、实用体现的淋漓尽致。
如今Go已经是云计算编程语言,Go语言背靠Google这课大树,又不乏牛人坐镇,是名副其实的“牛二代”。想象一下:一个只有十几年发展经历的编程语言,已经成为了如此巨大而且不断发展的行业主导者,这种成功是无法想象的。
Go语言常用库
学习编程语言,早已经不是学一点语法规则这么简单。现在更习惯称为选择Ecosystem(生态圈),而这其中标准库的作用和分量尤其明显。
在Go语言的安装文件里包含了一些可以直接使用的包,即标准库。Go语言的标准库(通常被称为语言自带的电池),提供了清晰的构建模块和公共接口,包含I/O操作、文本处理、图像、密码学、网络和分布式应用程序等,并支持多标准化的文件格式和编解码协议。
在windows下,标准库的位置在Go根目录下的子目录pkg\windows_amd64中;在linux下,标准库在Go根目录下的子目录pkg\linux_amd64中。Go语言的编译器也是标准库的一部分,通过词法器扫描源码,使用语法树获得源码逻辑分支等,Go语言的周边工具也是建立在这些标准库上,在标准库上可以完成几乎大部分的需求。
Go语言的标准库以包的方式提供支持,下表列出了Go语言标准库中常见的包及其功能。
Go语言标准库包名 | 功能 |
bufio | 带缓冲的I/O操作 |
bytes | 实现字节操作 |
container | 封装堆、列表和环形列表等容器 |
crypto | 加密算法 |
database | 数据库驱动和接口 |
debug | 各种调试文件格式访问及调试功能 |
encoding | 常见算法如JSON、XML、Base64等 |
flag | 命令行解析 |
fmt | 格式化操作 |
go | Go语言的词法、语法树、类型等。可通过这个包进行代码信息提取和修改 |
html | HTML转义及模板系统 |
image | 常见图形格式的访问及生成 |
io | 实现I/O原始访问接口及访问封装 |
math | 数学库 |
net | 网络库,支持Socket、HTTP、邮件、RPC、SMTP等 |
os | 操作系统平台不依赖平台操作封装 |
path | 兼容各操作系统的路径操作使用函数 |
plugin | Go1.7加入的插件系统。支持将代码编译为插件,按需加载(一个完整的Go环境也很大,只存放需要的部分即可) |
reflect | 语言反射支持。可以动态获得代码中的类型信息,获取和修改变量的值 |
regexp | 正则表达式封装 |
runtime | 运行时接口 |
sort | 排序接口 |
strings | 字符串转换、解析及实用函数 |
time | 时间接口 |
text | 文本模板及Token词法器 |
Go语言代码清爽
Go语言语法类似于C语言,因此熟悉C语言及其派生语言(【C++】、【C#】、Objective-C等)的人都会迅速熟悉这门语言。
C语言的有些语法会让代码的可读性降低甚至发生歧义。Go语言在C语言的基础上取其精华去其糟粕,将C语言中较为容易发生错误的写法进行调整,作出相应的编译提示。
去掉循环冗余括号
Go语言在众多大师的丰富实战经验的基础上诞生,去除了C语言语法中一些冗余、繁琐的部分。
下面的代码是C语言的数值循环
for(int a=0;a<10;a++){
// 循环代码
}
在Go语言中,这样的循环变为
for a := 0;a<10;a++ {
// 循环代码
}
for两边的括号被去掉,int声明被简化为:=,直接通过编译器右值推导获得a的变量类型并声明。
去除表达式冗余括号
同样的简化也可以在判断语句中体现出来,以下是C语言的判断语句:
if (表达式) {
// 表达式成立
}
在Go语言中,无须添加表达式括号,代码如下:
if 表达式 {
// 表达式成立
}
强制的代码风格
Go语言中,左括号必须紧贴着语句不换行。其它样式的括号将被视为代码编译错误。这个特性刚开始会使开发者有些不习惯,但随着Go语言的不断熟悉,开发者就会发现风格统一让大家在阅读代码时把注意力集中在解决问题上,而不是在代码风格上。
同时Go语言也提供了一套格式化工具。一些Go语言的开发环境或者编辑器在保存时,都会使用格式化工具对代码进行格式化,让代码提交时已经是统一格式的代码。
不再纠结于i++和++i
C语言非常经典的考试题为:
int a, b;
a = i++;
b = ++i;
这种题目对于初学者经常摸不到头脑,为什么一个简单的自增表达式需要有两种写法?
在Go语言中,自增操作符不再是一个操作符,而是一个语句。因此在Go语言中,自增只有一种写法。
i++
如果写成前置自增++i,或者赋值后自增a=i++都将会导致编译错误。
两个数字交换
在C语言中,两个数字交换需要借助一个变量来完成。
int a=1;
int b=2;
int temp;
temp = a;
a = b;
b = temp;
而在Go语言中,只需简单的互换
a := 1
b := 2
a, b = b, a
Go语言基础之变量
变量的定义
在计算机语言发展历程中,最先使用的是机器语言,编写“0”、“ 1”这样的二进制数字去控制电脑,由于机器语言十分晦涩难懂,其中的含义往往要通过查表或者手册才能理解;汇编语言比起机器语言,更加便于记忆和书写,但仍是面向机器的语言,很难理解代码的设计,因此只能用在底层;后面才有了面向对象的高级语言,将人们能读懂的代码语言和机器进行很好地交互。
通过变量可以很好地存储特定类型的值,让计算机认识特定格式代码所表示的含义,可以清楚地知道这个变量名的类型以及存储的值。
Go语言定义变量和赋值
定义变量:var 变量名 变量类型
变量赋值:var 变量名 变量类型 = 变量值
注:必须按照特定格式定义赋值,否则报错
package main
import "fmt"
func main() {
// var 变量名 变量类型
// 变量定义 如果没有赋值操作,就是默认值
// string 默认值 "" int 默认值 0
var name string
var age int
// 使用var() 可以同时定义多个变量
var (
address string
gender int
)
fmt.Println(name)
fmt.Println(age)
fmt.Println(address)
fmt.Println(gender)
// 变量定义后,可以直接使用
name = "张三"
fmt.Println(name)
// 同一变量可以重复赋值
name = "李四"
fmt.Println(name)
// 变量定义的时候可以直接进行赋值操作
var cat string = "喵喵"
fmt.Println(cat)
}
通过自动推导,定义变量并赋值:变量名 := 变量值
语法糖 := 相当于快速定义变量。如果变量有赋值操作,那么会自动推导出它的类型并定义。所能自动推导的数据类型是go语言中的基本数据类型。
package main
import "fmt"
func main() {
// := 自动推导
name := "张三"
age := 18
fmt.Println(name)
fmt.Println(age)
}
注:如果变量已经被定义过了,那么通过:=将无法创建,变量不能被重新定义
变量交换
Go语言中,程序变量的交换,也有语法糖,通过变量a, 变量b = 变量b, 变量a实现,但底层本质还是用到了临时变量,简化开发。
package main
import "fmt"
func main() {
var a int = 100
var b int = 200
// fmt.Println 可以同时打印多个,用逗号隔开
fmt.Println("a=", a, ", b=", b) // a= 100 , b= 200
// 把a, b 赋值给b, a
b, a = a, b
fmt.Println("a=", a, ", b=", b) // a= 200 , b= 100
}
变量内存地址
package main
import "fmt"
func main() {
// 变量实际是代表一小片内存空间,通过变量地址指向这片空间
var num int
num = 10
// 如果想要查看变量的内存地址,只需要在变量名的前面添加&即可
fmt.Printf("num的值:%d, 内存地址:%p\n", num, &num) // num的值:10, 内存地址:0xc00001c098
num = 20
// Printf表示格式化输出,数字使用%d,取地址使用%p
fmt.Printf("num的值:%d, 内存地址:%p\n", num, &num) // num的值:20, 内存地址:0xc00001c098
var name string
name = "张三"
// 字符串使用 %s
fmt.Printf("name的值:%s, 内存地址:%p\n", name, &name) // name的值:张三, 内存地址:0xc000050250
name = "李四"
fmt.Printf("name的值:%s, 内存地址:%p\n", name, &name) // name的值:李四, 内存地址:0xc000050250
}
匿名变量
匿名变量,_(下划线)表示,可以理解为一个黑洞,任何赋值给匿名变量的值都会被丢弃。
如果登录过程,返回的信息有很多,但需要的信息很少,可以使用占位符_表示多余的信息。
package main
import "fmt"
// 基本调用函数,返回两个值,100 200
func test() (int, int) {
return 100, 200
}
func main() {
// 需求:只想要test函数返回的第一个结果
// 需要使用匿名变量表示第二个结果
a, _ := test()
fmt.Println(a) // 100
// 如果只想要test函数返回的第二个结果,则相反
_, b := test()
fmt.Println(b) // 200
}
变量作用域
package main
import "fmt"
// 全局变量定义在go文件中,非函数体内,在package和import下面
// 跨作用域,全局变量和局部变量是可以重名的
var c int
//d := 30 全局变量的定义必须使用var关键字,如果使用:=则无法创建使用该变量
func main() {
// 在函数体内定义局部变量
var a int = 3
var b int = 4
// 如果只有全局变量,则使用全局变量
c = a + b
fmt.Printf("a=%d, b=%d, c=%d\n", a, b, c)
fmt.Printf("c内存地址:%p\n", &c) // c内存地址:0xa493c8
b = 1
// 如果局部变量和全局变量是同名的变量,则优先使用局部变量
c := a + b
fmt.Printf("a=%d, b=%d, c=%d\n", a, b, c)
fmt.Printf("c内存地址:%p\n", &c) // c内存地址:0xa493c8
b = 5
// 就近使用局部变量
c = a + b
fmt.Printf("a=%d, b=%d, c=%d\n", a, b, c)
fmt.Printf("c内存地址:%p\n", &c) // c内存地址:0xc00001c0d0
}
就近原则
package main
import "fmt"
var a float64 = 3.14
func main() {
var a float64 = 2.14
// 优先使用局部变量
fmt.Println(a) // 2.14
}
Go语言基础之常量
常量的定义
常量:顾名思义,不能改变的量,特殊的变量,无法修改值,使用关键字const表示。
由于Go语言底层的约定,导致常量无法改变,实际可以通过其内存地址进行值的修改。
常量和变量的内存存放不同,常量定义赋值不使用不会报错,但变量则会报错。
package main
import "fmt"
func main() {
// 建议使用大写字母定义常量,区别于普通变量
// 格式:const 变量名 [变量类型] = 变量值
const URL string = "www.baidu.com"
// 隐式定义,可以省略基础类型
const URL2 = "www.taobao.com"
fmt.Println(URL)
fmt.Println(URL2)
// 可以同时定义多个常量
const URL3, URL4 string = "www.baidu.com", "www.taobao.com"
fmt.Println(URL3)
fmt.Println(URL4)
// 在真实世界中一些不会发生变化的值,可以使用常量表示
const PI = 3.14
// 特定的长宽高等
const LENGTH, WIDTH, HEIGHT = 100, 200, 300
}
特殊的常量iota(拓展)
package main
import "fmt"
// 可以使用const()定义多个常量
func main() {
// iota 常量计数器,默认值为0,在一组const中,每次定义新的常量,它会自动+1
const (
a = iota
b = iota
c = iota
d = 0
e = iota
// 如果常量没有赋值,默认沿用上面的iota常量自动+1
f
g
h = iota
)
fmt.Println(a, b, c, d, e, f, g, h) // 0 1 2 0 4 5 6 7
const (
i = iota // iota = 0
j = 0 // j=0 iota=iota+1=1
k = iota // iota=iota+1=2
)
fmt.Println(i, j, k) // 0 0 2
}
Go语言基础之基本数据类型
动态类型编程语言:如JavaScript等,运行时判断数据类型;
静态类型编程语言:如Go、Java等,在开发的时候,就需要给定义的变量赋值空间大小。
在Go语言中出现的基本数据类型,都会有一个默认的空间大小。
布尔类型
布尔类型的值只可以是常量true或者false。
package main
import "fmt"
func main() {
// 布尔类型,只有两个值,true和false
// 定义布尔类型
var b1 bool
var b2 bool
b1 = true
b2 = false
// 布尔类型使用%t输出值,使用%T输出类型
fmt.Printf("b1的类型:%T,b1的值:%t\n", b1, b1) // b1的类型:bool,b1的值:true
fmt.Printf("b2的类型:%T,b2的值:%t\n", b2, b2) // b2的类型:bool,b2的值:false
// 比大小的结果是布尔类型
var a int = 1
var b int = 2
fmt.Println(a < b) // true
if a < b {
fmt.Println("a小于b")
} else {
fmt.Println("a不小于b")
}
// 布尔类型默认值为false
var b3 bool
fmt.Println("bool默认值:", b3) // false
}
整数型
Go语言中整数型分为int有符号和uint无符号。
每种数字类型都是有存储范围的,意味着在计算机中,不能无限制地存储数据。
序号 | 类型和描述 |
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) |
浮点数
package main
import "fmt"
// 浮点数 float32 float64
func main() {
var f1 float32
f1 = 5
var f2 float64
f2 = 3.1415926
// 没有使用格式化输出,正常输出
fmt.Println(f2) // 3.1415926
// 浮点数的格式化输出:%f,默认都是保留了6位小数
fmt.Printf("%T, %f\n", f1, f1) // float32, 5.000000
fmt.Printf("%T, %f\n", f2, f2) // float64, 3.141593
// .3 保留三位小数,以此类推。四舍五入
fmt.Printf("%T, %.3f\n", f2, f2) // float64, 3.142
}
使用flot计算,可能导致数据不精确;float64的精度>float32的精度,在go语言中,浮点数默认使用的是float64。
package main
import "fmt"
// 浮点数 float32 float64
func main() {
// 浮点数:符号位 + 指数位 + 尾数位(存储过程中,可能会丢失,造成精度损失)
var num1 float32 = -123.0000902
var num2 float64 = -123.0000902
fmt.Println(num1) // -123.00009
// float64 空间 > float32
fmt.Println(num2) // -123.0000902
}
特殊的数字类型
package main
import "fmt"
func main() {
// byte = uint8(0-255之间的整数,通常使用byte来定义)
// uint8的别名 byte
var num1 byte = 255
fmt.Println(num1) // 255
fmt.Printf("%T\n", num1) // uint8
// 不经常使用,rune = int32
var num2 rune = 1000000000
fmt.Println(num2) // 1000000000
fmt.Printf("%T\n", num2) // int32
// int 默认按照系统位数来,32位就是int32, 64位就是int64
var num3 int = 100000
fmt.Println(num3) // 100000
fmt.Printf("%T\n", num3) // int
}
字符串类型
Go语言中,所有的字符串都是由单个字符连接起来的。单引号是字符、双引号是字符串,字符本质是整型。
package main
import "fmt"
func main() {
var str string
str = "Hello, World"
fmt.Printf("%T, %s\n", str, str) // string, Hello, World
// 单引号是字符类型,双引号是string类型
// 字符类型本质是整型
v1 := 'A'
v2 := "A"
fmt.Printf("%T, %d\n", v1, v1) // int32, 65
fmt.Printf("%T, %s\n", v2, v2) // string, A
v3 := '中'
fmt.Printf("%T, %d\n", v3, v3) // int32, 20013
}
数据类型转换
在开发中,必要的情况下,一个类型的值可以被转换成另一种类型的值。
由于Go语言不存在隐式类型转换,因此所有的类型转换都必须显式的声明。
在Go语言中,数据类型转换就一种格式,新类型的值 = 新类型(旧类型的值)。
package main
import "fmt"
// 类型转换
func main() {
// 浮点数转整数,不是四舍五入,而是截断,只保留整数部分
a := 5.9
b := int(a)
fmt.Printf("%T, %f\n", a, a) // float64, 5.900000
fmt.Printf("%T, %d\n", b, b) // int, 5
// 浮点数转换为整数
c := 1
d := float64(c)
fmt.Printf("%T, %d\n", c, c) // int, 1
fmt.Printf("%T, %f\n", d, d) // float64, 1.000000
// 布尔类型是不支持类型转换的
// var flag bool = true
// a := int(flag) 这里会报错
}