Go 语言
文章目录
1.概念
Go 是一个开源的编程语言,它能让构造简单、可靠且高效的软件变得容易。
Go是从2007年末由Robert Griesemer, Rob Pike, Ken Thompson主持开发,后来还加入了Ian Lance Taylor, Russ Cox等人,并最终于2009年11月开源,在2012年早些时候发布了Go 1稳定版本。现在Go的开发已经是完全开放的,并且拥有一个活跃的社区。
1.1 特色
- 简洁、快速、安全
- 并行、有趣、开源
- 内存管理、数组安全、编译迅速
1.2 结构
- 包声明
- 引入包
- 函数
- 变量
- 语句
- & 表达式
- List item
- 注释
1.3 Go程序
package main
import "fmt"
func main() {
/* 这是我的第一个简单的程序 */
fmt.Println("Hello, World!")
}
1.第一行代码 package main 定义了包名。你必须在源文件中非注释的第一行指明这个文件属于哪个包,如:package main。package main表示一个可独立执行的程序,每个 Go 应用程序都包含一个名为 main 的包。
2.下一行 import “fmt” 告诉 Go 编译器这个程序需要使用 fmt 包(的函数,或其他元素),fmt 包实现了格式化 IO(输入/输出)的函数。
3.下一行 func main() 是程序开始执行的函数。main 函数是每一个可执行程序所必须包含的,一般来说都是在启动后第一个执行的函数(如果有 init() 函数则会先执行该函数)。
4.下一行 /…/ 是注释,在程序执行时将被忽略。单行注释是最常见的注释形式,你可以在任何地方使用以 // 开头的单行注释。多行注释也叫块注释,均已以 /* 开头,并以 */ 结尾,且不可以嵌套使用,多行注释一般用于包的文档描述或注释成块的代码片段。
5.下一行 fmt.Println(…) 可以将字符串输出到控制台,并在最后自动增加换行字符 \n。
使用 fmt.Print(“hello, world\n”) 可以得到相同的结果。
Print 和 Println 这两个函数也支持使用变量,如:fmt.Println(arr)。如果没有特别指定,它们会以默认的打印格式将变量 arr 输出到控制台。
6.当标识符(包括常量、变量、类型、函数名、结构字段等等)以一个大写字母开头,如:Group1,那么使用这种形式的标识符的对象就可以被外部包的代码所使用(客户端程序需要先导入这个包),这被称为导出(像面向对象语言中的 public);标识符如果以小写字母开头,则对包外是不可见的,但是他们在整个包的内部是可见并且可用的(像面向对象语言中的 protected )。
2.基本语法
2.1 标记
Go 程序可以由多个标记组成,可以是关键字,标识符,常量,字符串,符号。
2.2 行分隔符
在 Go 程序中,一行代表一个语句结束。每个语句不需要像 C 家族中的其它语言一样以分号 ; 结尾,因为这些工作都将由 Go 编译器自动完成。
2.3 注释
注释不会被编译,每一个包应该有相关注释。
单行注释是最常见的注释形式,你可以在任何地方使用以 // 开头的单行注释。多行注释也叫块注释,均已以 /* 开头,并以 */ 结尾。
// 单行注释
/* 多行注释 */
2.4 标识符
2.5 变量
标识符用来命名变量、类型等程序实体。一个标识符实际上就是一个或是多个字母(AZ和az)数字(0~9)、下划线_组成的序列,但是第一个字符必须是字母或下划线而不能是数字。
2.6 +连接
数值型,则是加法运算
字符串,则是字符串拼接
package main
import "fmt"
func main() {
var i =1
var j =2
var r=i+j
fmt.Println("r = ",r)
var str1 = "h"
var str2 = "u"
var str3=str1+str2
fmt.Println("str3 = ",str3)
}
2.7 关键字
程序一般由关键字、常量、变量、运算符、类型和函数组成。
程序中可能会使用到这些分隔符:括号 (),中括号 [] 和大括号 {}。
程序中可能会使用到这些标点符号:.、,、;、: 和 …。
2.9 格式化字符串
Go 语言中使用 fmt.Sprintf 或 fmt.Printf 格式化字符串并赋值给新串:
- Sprintf 根据格式化参数生成格式化的字符串并返回该字符串。
- Printf 根据格式化参数生成格式化的字符串并写入标准输出。
2.10 类型转换
2.10.1 基本类型转String
- fmt.Sprintf(“%参数”,表达式)
package main
import "fmt"
func main() {
var num1 int = 99
var num2 float64 = 23.456
var b bool = true
var mychar byte = 'h'
var str string //空的str
//使用第一种方式来转换 fmt.sprintf方法
str = fmt.Sprintf("%d", num1)
fmt.Printf("str type %T str=%q\n", str, str)
str = fmt.Sprintf("%f", num2)
fmt.Printf("str type %T str=%q\n", str, str)
str = fmt.Sprintf("%t", b)
fmt.Printf("str type %T str=%q\n", str, str)
str = fmt.Sprintf("%c", mychar)
fmt.Printf("str type %T str=%q\n", str, str)
}
2.10.2 strconv函数
- 基本类型转String
var num3 int = 99
var b2 bool = true
str = strconv.FormatInt(int64(num3), 10)
fmt.Printf("str type %T str=%q ", str, str)
// strconv.FormatFloat(num4,f',10,64)// 说明:f’格式 10:表示小数位保10位 64 :表示这个小数是float64str = strconv.FormatFloat(num4,"f,10,64)fmt.Printf("str type %T str=%q\n", str, str)
str = strconv.FormatBool(b2)
fmt.Printf("str type %T str=%q\n", str, str)
2.String转基本类型
var str string = "true"
var b bool
//说明
// 1.strconv.ParseBool(str) 函数会返回两个值 (value bool,err error)// 2因为我只想获取到 value bool不想获取 err 所以我使用 忽略
b,_= strconv.ParseBool(str)
fmt.Printf("b type %T b=%v\n",b,b)
var str2 string = "1234590"
var n1 int64
var n2 int
n1,_= strconv.ParseInt(str2, 10, 64)
n2 = int(n1)
fmt.Println("n1 type %T n1=%v\n", n1, n1)
fmt.Println("n2 type %T n2=%v\n", n2, n2)
var str3 string = "123.456"
var f1 float64
f1,_= strconv.ParseFloat(str3,64)
fmt.Println("f1 type %T f1=%v\n", f1, f1)
2.11 输入
2.11.1 fmt.Scanln()获取
//演示golang中指针类型func main() {
//基本数据类型在内存布局var i int = 10// i 的地址是什么&ifmt.Println("i的地址=",&i)
//下面的 var ptr *int = &i
//1ptr 是一个指针变量
//2.ptr 的类型 *int
//3.ptr 本身的值&i
package main
import "fmt" +
func main() {
//要求:可以从控制台接收用户信息,[姓名,年,薪水,是否通过考试 ]。
//方式1 fmt.scanln//1先声明需要的变量
var name string
var age byte
var sal float32
var isPass bool
fmt.Println("请输入姓名")
//当程序执行到 fmt.scanln(&name),程序会停止在这里,等待用户输入,并回车
fmt.Scanln(&name)
fmt.Println("请输入年龄")
fmt.Scanln(&age)
fmt.Println("请输入薪水")
fmt.Scanln(&sal)
fmt.Println("请输入是否通过考试")
fmt.Scanln(&isPass)
fmt.Printf("名字是 %v \n 年是 %v \n 薪水是 %v \n 是否通过考试 %v\n", name, age,sal,isPass)
}
2.11.2 fmt.Scanf() 获取
fmt.Println("请输入你的姓名,年龄,薪水,是否通过考试")
fmt.Scanf("%s %d %f %t", &name, &age, &sal, &isPass )
fmt.Printf("名字是 %v\n 年龄是 %v\n 薪水是 %v\n 是通过考试 %v\n",name, age,sal,isPass)
3.数据类型
- 布尔型 布尔型的值只可以是常量 true 或者 false。
- 数字类型 整型int 和浮点型 float32、float64
- 字符串类型:字符串就是一串固定长度的字符连接起来的字符序列。
- 派生类型: 包括:
- 指针类型(Pointer)
- 数组类型
- 结构化类型(struct)
- Channel 类型
- 函数类型
- 切片类型
- 接口类型(interface)
- Map类型
4.变量
4.1 概念
变量相当于内存中一个数据存储空间的表示,通过变量访问变量值。
4.2 使用方法
4.2.1 指定变量类型(声明后若不赋值,使用默认值)
package main
import "fmt"
func main() {
var i int
fmt.Println("i=",i)
}
4.2.2 根据值自行判定变量类型
package main
import "fmt"
func main() {
var i = 10.11
fmt.Println("i=",i)
}
4.2.3 省略var
package main
import "fmt"
func main() {
i :="name"
fmt.Println("i=",i)
}
4.2.4 多变量声明
package main
import "fmt"
func main() {
i ,name,num :="name","湖北","i"
fmt.Println("i=",i,"name=",name,"num=",num)
}
5.运算符
5.1 算术运算符
5.2 关系运算符
5.3 逻辑运算符
5.4 位运算符
5.5 赋值运算符
5.6 其他运算符
6.条件语句
6.1.1 单分支
package main
import "fmt"
func main(){
var i int
fmt.Scanln(&i)
if i>18 {
fmt.Println("成年了")
}
}
6.1.2 双分支
package main
import "fmt"
func main() {
var i int
fmt.Scanln(&i)
if i > 18 {
fmt.Println("成年了")
} else {
fmt.Println("未成年")
}
}
6.1.3 多分支
package main
import "fmt"
func main() {
/* 定义局部变量 */
var a int = 100
var b int = 200
/* 判断条件 */
if a == 100 {
/* if 条件语句为 true 执行 */
if b == 200 {
/* if 条件语句为 true 执行 */
fmt.Printf("a 的值为 100 , b 的值为 200\n" );
}
}
fmt.Printf("a 值为 : %d\n", a );
fmt.Printf("b 值为 : %d\n", b );
}
6.1.4 switch分支
package main
import "fmt"
func main() {
var num int
fmt.Scanf("%d", &num)
switch num {
case 1:
fmt.Println("1")
case 2:
fmt.Println("2")
case 3:
fmt.Println("3")
default:
fmt.Println("错误")
}
}
1.case/switch后是一个表达式(即:常量值、变量、一个有返回值的函数等都可以)
2.case后的各个表达式的值的数据类型,必须和switch 的表达式数据类型一致
3.case后面可以带多个表达式,使用逗号间隔。
7.循环语句
7.1 for 循环
package main
import "fmt"
func main() {
for i:=1;i<10;i++{
fmt.Println("加油")
}
}
break 语句用于终止某个语句块的执行,用于中断当前for 循环或跳出 switch 语句。
continue 语句用于结束本次循环,继续执行下一次循环。
goto 语句可以无条件地转移到程序中指定的行。
return 使用在方法或者函数中,表示跳出所在的方法或函数。
7.2 Range
Go 语言中 range 关键字用于 for 循环中迭代数组(array)、切片(slice)、通道(channel)或集合(map)的元素。在数组和切片中它返回元素的索引和索引对应的值,在集合中返回 key-value 对。
定义
for key, value := range oldMap {
newMap[key] = value
}
8.函数
package main
import "fmt"
func cal ( num int ) {
switch num {
case 1:
fmt.Println("1")
case 2:
fmt.Println("2")
case 3:
fmt.Println("3")
default:
fmt.Println("错误")
}
}
func main() {
var num int
fmt.Scanf("%d", &num)
cal(num)
}
8.1 参数
8.2 用法
8.3 方法
Go 语言中同时有函数和方法。一个方法就是一个包含了接受者的函数,接受者可以是命名类型或者结构体类型的一个值或者是一个指针。所有给定类型的方法属于该类型的方法集。语法格式如下:
func (variable_name variable_data_type) function_name() [return_type]{
/* 函数体*/
}
package main
import (
"fmt"
)
/* 定义结构体 */
type Circle struct {
radius float64
}
func main() {`在这里插入代码片`
var c1 Circle
c1.radius = 10.00
fmt.Println("圆的面积 = ", c1.getArea())
}
//该 method 属于 Circle 类型对象中的方法
func (c Circle) getArea() float64 {
//c.radius 即为 Circle 类型对象中的属性
return 3.14 * c.radius * c.radius
}
8.4 递归
定义
func recursion() {
recursion() /* 函数调用自身 */
}
func main() {
recursion()
}
9.变量作用域
1.局部变量
在函数体内声明的变量称之为局部变量,它们的作用域只在函数体内,参数和返回值变量也是局部变量。
2.全局变量
在函数体外声明的变量称之为全局变量,全局变量可以在整个包甚至外部包(被导出后)使用。
3.形式参数
形式参数会作为函数的局部变量来使用。
10.数组
10.1 一维数组
var variable_name [SIZE] variable_type
10.2 多维数组
var variable_name [SIZE1][SIZE2]...[SIZEN] variable_type
10.3 函数传递数组
10.3.1 形参设定数组大小
void myFunction(param [10]int)
{
.
.
.
}
10.3.2 形参未设定数组大小
void myFunction(param []int)
{
.
.
.
}
10.4 切片
Go 数组的长度不可改变,在特定场景中这样的集合就不太适用,Go 中提供了一种灵活,功能强悍的内置类型切片(“动态数组”),与数组相比切片的长度是不固定的,可以追加元素,在追加时可能使切片的容量增大。
10.4.1 定义
var identifier []type
10.4.2 make()函数
var slice1 []type = make([]type, len)
也可以简写为
make([]type, length, capacity)
10.4.3 切片初始化
s :=[] int {1,2,3 }
直接初始化切片,[] 表示是切片类型,{1,2,3} 初始化值依次是 1,2,3,其 cap=len=3。
s := arr[:]
初始化切片 s,是数组 arr 的引用。
s := arr[startIndex:endIndex]
将 arr 中从下标 startIndex 到 endIndex-1 下的元素创建为一个新的切片。
s := arr[startIndex:]
默认 endIndex 时将表示一直到arr的最后一个元素。
s := arr[:endIndex]
默认 startIndex 时将表示从 arr 的第一个元素开始。
s1 := s[startIndex:endIndex]
通过切片 s 初始化切片 s1。
s :=make([]int,len,cap)
通过内置函数 make() 初始化切片s,[]int 标识为其元素类型为 int 的切片。
10.4.4 len() 和 cap() 函数
切片是可索引的,并且可以由 len() 方法获取长度。
切片提供了计算容量的方法 cap() 可以测量切片最长可以达到多少。
package main
import "fmt"
func main() {
/* 创建切片 */
numbers := []int{0,1,2,3,4,5,6,7,8}
printSlice(numbers)
/* 打印原始切片 */
fmt.Println("numbers ==", numbers)
/* 打印子切片从索引1(包含) 到索引4(不包含)*/
fmt.Println("numbers[1:4] ==", numbers[1:4])
/* 默认下限为 0*/
fmt.Println("numbers[:3] ==", numbers[:3])
/* 默认上限为 len(s)*/
fmt.Println("numbers[4:] ==", numbers[4:])
numbers1 := make([]int,0,5)
printSlice(numbers1)
/* 打印子切片从索引 0(包含) 到索引 2(不包含) */
number2 := numbers[:2]
printSlice(number2)
/* 打印子切片从索引 2(包含) 到索引 5(不包含) */
number3 := numbers[2:5]
printSlice(number3)
}
func printSlice(x []int){
fmt.Printf("len=%d cap=%d slice=%v\n",len(x),cap(x),x)
}
10.4.5 append() 和 copy() 函数
如果想增加切片的容量,我们必须创建一个新的更大的切片并把原分片的内容都拷贝过来。
下面的代码描述了从拷贝切片的 copy 方法和向切片追加新元素的 append 方法。
package main
import "fmt"
func main() {
var numbers []int
printSlice(numbers)
/* 允许追加空切片 */
numbers = append(numbers, 0)
printSlice(numbers)
/* 向切片添加一个元素 */
numbers = append(numbers, 1)
printSlice(numbers)
/* 同时添加多个元素 */
numbers = append(numbers, 2,3,4)
printSlice(numbers)
/* 创建切片 numbers1 是之前切片的两倍容量*/
numbers1 := make([]int, len(numbers), (cap(numbers))*2)
/* 拷贝 numbers 的内容到 numbers1 */
copy(numbers1,numbers)
printSlice(numbers1)
}
func printSlice(x []int){
fmt.Printf("len=%d cap=%d slice=%v\n",len(x),cap(x),x)
}
11.指针
一个指针变量指向了一个值的内存地址。
var var_name *var-type
- 定义指针变量。
- 为指针变量赋值。
- 访问指针变量中指向地址的值。
package main
import "fmt"
func main() {
var i int = 10
fmt.Println("i的地址=", &i)
var ptr *int = &i
fmt.Printf("ptr=%v\n", ptr)
fmt.Printf("ptr 的地址=%v", &ptr)
fmt.Printf("ptr 指向的值=%v", *ptr)
}
- 值类型,都有对应的指针类型, 形式为 *数据类型,
- 值类型包括:基本数据类型 int 系列,float 系列,bool,string 、数组和结构体 struct
12.结构体
12.1 定义
type struct_variable_type struct {
member definition
member definition
...
member definition
}
12.2 声明
variable_name := structure_variable_type {value1, value2...valuen}
或
variable_name := structure_variable_type { key1: value1, key2: value2..., keyn: valuen}
13.Map(集合)
Map 是一种无序的键值对的集合。
Map 最重要的一点是通过 key 来快速检索数据,key 类似于索引,指向数据的值。
Map 是一种集合,所以我们可以像迭代数组和切片那样迭代它。不过,Map 是无序的,遍历 Map 时返回的键值对的顺序是不确定的。
在获取 Map 的值时,如果键不存在,返回该类型的零值,例如 int 类型的零值是 0,string 类型的零值是 “”。
Map 是引用类型,如果将一个 Map 传递给一个函数或赋值给另一个变量,它们都指向同一个底层数据结构,因此对 Map 的修改会影响到所有引用它的变量。
13.1 定义
/* 使用 make 函数 */
map_variable := make(map[KeyType]ValueType, initialCapacity)
KeyType 是键的类型,ValueType 是值的类型,initialCapacity 是可选的参数,用于指定 Map 的初始容量。
13.2 获取元素:
// 获取键值对
v1 := m["apple"]
v2, ok := m["pear"] // 如果键不存在,ok 的值为 false,v2 的值为该类型的零值
13.3 修改元素:
// 修改键值对 m[“apple”] = 5
13.4 获取 Map 的长度:
// 获取 Map 的长度
len := len(m)
13.5 遍历 Map:
// 遍历 Map
for k, v := range m {
fmt.Printf("key=%s, value=%d\n", k, v)
}
13.6 删除元素:
// 删除键值对
delete(m, "banana")
package main
import "fmt"
func main() {
var siteMap map[string]string /*创建集合 */
siteMap = make(map[string]string)
/* map 插入 key - value 对,各个国家对应的首都 */
siteMap [ "Google" ] = "谷歌"
siteMap [ "Runoob" ] = "菜鸟教程"
siteMap [ "Baidu" ] = "百度"
siteMap [ "Wiki" ] = "维基百科"
/*使用键输出地图值 */
for site := range siteMap {
fmt.Println(site, "首都是", siteMap [site])
}
/*查看元素在集合中是否存在 */
name, ok := siteMap [ "Facebook" ] /*如果确定是真实的,则存在,否则不存在 */
/*fmt.Println(capital) */
/*fmt.Println(ok) */
if (ok) {
fmt.Println("Facebook 的 站点是", name)
} else {
fmt.Println("Facebook 站点不存在")
}
}
14.接口
Go 语言提供了另外一种数据类型即接口,它把所有的具有共性的方法定义在一起,任何其他类型只要实现了这些方法就是实现了这个接口。
接口可以让我们将不同的类型绑定到一组公共的方法上,从而实现多态和灵活的设计。
Go 语言中的接口是隐式实现的,也就是说,如果一个类型实现了一个接口定义的所有方法,那么它就自动地实现了该接口。因此,我们可以通过将接口作为参数来实现对不同类型的调用,从而实现多态。
/* 定义接口 */
type interface_name interface {
method_name1 [return_type]
method_name2 [return_type]
method_name3 [return_type]
...
method_namen [return_type]
}
/* 定义结构体 */
type struct_name struct {
/* variables */
}
/* 实现接口方法 */
func (struct_name_variable struct_name) method_name1() [return_type] {
/* 方法实现 */
}
...
func (struct_name_variable struct_name) method_namen() [return_type] {
/* 方法实现*/
}
15.错误处理
Go 语言通过内置的错误接口提供了非常简单的错误处理机制。
error类型是一个接口类型,这是它的定义:
type error interface {
Error() string
}
代码
package main
import (
"fmt"
)
// 定义一个 DivideError 结构
type DivideError struct {
dividee int
divider int
}
// 实现 `error` 接口
func (de *DivideError) Error() string {
strFormat := `
Cannot proceed, the divider is zero.
dividee: %d
divider: 0
`
return fmt.Sprintf(strFormat, de.dividee)
}
// 定义 `int` 类型除法运算的函数
func Divide(varDividee int, varDivider int) (result int, errorMsg string) {
if varDivider == 0 {
dData := DivideError{
dividee: varDividee,
divider: varDivider,
}
errorMsg = dData.Error()
return
} else {
return varDividee / varDivider, ""
}
}
func main() {
// 正常情况
if result, errorMsg := Divide(100, 10); errorMsg == "" {
fmt.Println("100/10 = ", result)
}
// 当除数为零的时候会返回错误信息
if _, errorMsg := Divide(100, 0); errorMsg != "" {
fmt.Println("errorMsg is: ", errorMsg)
}
}
16.并发
Go 语言支持并发,我们只需要通过 go 关键字来开启 goroutine 即可。
goroutine 是轻量级线程,goroutine 的调度是由 Golang 运行时进行管理的。
goroutine 语法格式:
go 函数名( 参数列表 )
package main
import (
"fmt"
"time"
)
func say(s string) {
for i := 0; i < 5; i++ {
time.Sleep(100 * time.Millisecond)
fmt.Println(s)
}
}
func main() {
go say("world")
say("hello")
}
17.通道
通道(channel)是用来传递数据的一个数据结构。
通道可用于两个 goroutine 之间通过传递一个指定类型的值来同步运行和通讯。操作符 <- 用于指定通道的方向,发送或接收。如果未指定方向,则为双向通道。
ch <- v // 把 v 发送到通道 ch
v := <-ch // 从 ch 接收数据
// 并把值赋给 v
声明一个通道很简单,我们使用chan关键字即可,通道在使用前必须先创建:
ch := make(chan int)
代码
package main
import "fmt"
func sum(s []int, c chan int) {
sum := 0
for _, v := range s {
sum += v
}
c <- sum // 把 sum 发送到通道 c
}
func main() {
s := []int{7, 2, 8, -9, 4, 0}
c := make(chan int)
go sum(s[:len(s)/2], c)
go sum(s[len(s)/2:], c)
x, y := <-c, <-c // 从通道 c 中接收
fmt.Println(x, y, x+y)
}
通道缓冲区
带缓冲区的通道允许发送端的数据发送和接收端的数据获取处于异步状态,就是说发送端发送的数据可以放在缓冲区里面,可以等待接收端去获取数据,而不是立刻需要接收端去获取数据。
不过由于缓冲区的大小是有限的,所以还是必须有接收端来接收数据的,否则缓冲区一满,数据发送端就无法再发送数据了。
注意:如果通道不带缓冲,发送方会阻塞直到接收方从通道中接收了值。如果通道带缓冲,发送方则会阻塞直到发送的值被拷贝到缓冲区内;如果缓冲区已满,则意味着需要等待直到某个接收方获取到一个值。接收方在有值可以接收之前会一直阻塞。
package main
import "fmt"
func main() {
// 这里我们定义了一个可以存储整数类型的带缓冲通道
// 缓冲区大小为2
ch := make(chan int, 2)
// 因为 ch 是带缓冲的通道,我们可以同时发送两个数据
// 而不用立刻需要去同步读取数据
ch <- 1
ch <- 2
// 获取这两个数据
fmt.Println(<-ch)
fmt.Println(<-ch)
}
18.包
1.引用方法
1.import "包名"
2.import("包名" "包名")
2.在import 包时,路径从 $GOPATH 的 src 下开始,不用带 src,编译器会自动从 src 下开始引入
3. 为了让其它包的文件,可以访问到本包的函数,则该函数名的首字母需要大写
4. 在访问其它包函数,变量时,其语法是 包名.函数名,
5. 如果包名较长,Go 支持给包取别名, 注意细节:取别后,原来的包名就不能使用了
6. 在同一包下,不能有相同的函数名(也不能有相同的全局变量名》,否则报重复定义