Go语言最初由Google公司的Robert Griesemer、Ken Thompson和Rob Pike三个大牛于2007年开始设计发明,他们最终的目标是设计一种适应网络和多核时代的C语言。所以Go语言很多时候被描述为“类C语言”,或者是“21世纪的C语言”,当然从各种角度看,Go语言确实是从C语言继承了相似的表达式语法、控制流结构、基础数据类型、调用参数传值、指针等诸多编程思想。但是Go语言更是对C语言最彻底的一次扬弃,它舍弃了C语言中灵活但是危险的指针运算,还重新设计了C语言中部分不太合理运算符的优先级,并在很多细微的地方都做了必要的打磨和改变。
Go语言中有两种方式声明变量
1. 使用var
关键字声明,且需要注意的是,与大多数强类型语言不同,Go语言的声明变量类型位于变量名称的后面,Go语句结束不需要分号。
var num int
var result string = "this is result"
2. 使用 := 赋值
num := 3 等同于 var num int = 3
其中变量的类型会根据右侧的值进行匹配,例如"3"会匹配为int,"3.0"会匹配为float64,"result"会匹配为string。
使用const来声明一个常量,一个常量在声明后不可改变。
const laugh string = "go"
只声明未赋值的变量,其值为nil,类似于C/C++中的“NULL/nullptr”。
没有明确初始值的变量声明会被赋予它们的零值。
零值是:
若为数值类型 则为0
若为布尔类型 则为false
若为字符串类型 则为 ""(空字符串)
使用func关键字来定义一个方法,后面跟方法名,然后是参数,返回值(如果有的话,没有返回值则不写)。
func MethodName(p1 Parm, p2 Parm) int{}
//学习一个语言应该从Hello World开始!
package main
import "fmt"
func main() {
fmt.Println("Hello World!")// Hello World!
fmt.Println(add(3, 5)) //8
var sum = add(3, 5)
}
func add(a int, b int) int{
return a+b;
}
多个返回值
Go 函数与其他编程语言一大不同之处在于支持多返回值,这在处理程序出错的时候非常有用。例如,如果上述 add
函数只支持非负整数相加,传入负数则会报错。
//返回值只定义了类型 没有定义返回参数
func add(a, b int) (int, error) {
if a < 0 || b < 0 {
err := errors.New("只支持非负整数相加")
return 0, err
}
a *= 2
b *= 3
return a + b, nil
}
//返回值还定义了参数 这样可以直接return 并且定义的参数可以直接使用 return时只会返回这两个参数
func add1(a, b int) (z int, err error) {
if a < 0 || b < 0 {
err := errors.New("只支持非负整数相加")
return //实际返回0 err 因为z只定义没有赋值 则nil值为0
}
a *= 2
b *= 3
z = a + b
return //返回 z err
}
func main() {
x, y := -1, 2
z, err := add(x, y)
if err != nil {
fmt.Println(err.Error())
return
}
fmt.Printf("add(%d, %d) = %d\n", x, y, z)
}
在 Go 语言中,无论是变量、函数还是类属性和成员方法,它们的可见性都是以包为维度的,而不是类似传统面向编程那样,类属性和成员方法的可见性封装在所属的类中,然后通过 private
、protected
和 public
这些关键字来修饰其可见性。
Go 语言没有提供这些关键字,不管是变量、函数,还是自定义类的属性和成员方法,它们的可见性都是根据其首字母的大小写来决定的,如果变量名、属性名、函数名或方法名首字母大写,就可以在包外直接访问这些变量、属性、函数和方法,否则只能在包内访问,因此 Go 语言类属性和成员方法的可见性都是包一级的,而不是类一级的。
假如说一个名为domain
的文件夹下有3个.go文件,则三个文件中的package
都应为domain
,其中程序的入口main方法所在的文件,包为main
:
//定义了此文件属于 main 包
package main
//通过import导入标注库中包
import "fmt"
func main() {
fmt.Println("Hello World!")// Hello World!
fmt.Println(add(3, 5)) //8
var sum = add(3, 5)
}
func add(a int, b int) int{
return a+b;
}
func main() {
i := 0
//使用&来传入地址
fmt.Println(&i) //0xc00000c054
var a, b int = 3 ,4
//传入 0xc00000a089 0xc00000a090
fmt.Println(add(&a, &b))
}
//使用*来声明一个指针类型的参数与使用指针
func add(a *int, b *int)int{
//接收到 0xc00000a089 0xc00000a090
//前往 0xc00000a089位置查找具体数据 并取赋给x
x := *a
//前往 0xc00000a090位置查找具体数据 并取赋给y
y := *b
return x+y
}
// if
if condition {
// do something
}
// if...else...
if condition {
// do something
} else {
// do something
}
// if...else if...else...
if condition1 {
// do something
} else if condition2 {
// do something else
} else {
// catch-all or default
}
sum := 0
//普通for循环
for i := 1; i <= 100; i++ {
sum += i
}
//无限循环
for{
sum++
if sum = 100{
break;
}
}
//带条件的循环
for res := sum+1; sum < 15{
sum++
res++
}
//使用kv循环一个map或一个数组 k为索引或键值 v为值 k、v不需要时可以用_带替
for k, v := range a {
fmt.Println(k, v)
}
score := 100
switch score {
case 90, 100:
fmt.Println("Grade: A")
case 80:
fmt.Println("Grade: B")
case 70:
fmt.Println("Grade: C")
case 65:
fmt.Println("Grade: D")
default:
fmt.Println("Grade: F")
}
数组功能与C/C++语言类似,都是长度不可变,并且可以使用多维数组,也可以通过arrays[i]来存储或获取值。
//声明
var nums [3]int
//声明并初始化
var nums = [3]int{1,2,3} <==> nums:=[3]int{1,2,3}
//使用
for sum := 0, i := 0;i<10{
sum += nums[i]
i++
}
//修改值
num[0] = -1
数组使用较为简单,但是存在着难以解决的问题:长度固定
例如在程序中需要一个数据结构来存储获取到的所有用户,因为用户数量是会随着时间变化的,但是数组其长度却不可改变,所以数组并不适合存储长度会发生改变的数据,因此在Go语言中通过使用切片来解决以上问题。
切片与数组最大的不同就是切片不用声明长度。但是切片与数组并非毫无关系,数组可以看作是切片的底层数组,而切片则可以看作是数组某个连续片段的引用。切片可以只使用数组的一部分元素或者整个数组来创建,甚至可以创建一个比所基于的数组还要大的切片。
切片的长度就是它所包含的元素个数
切片的容量是从它的第一个元素开始数,到其底层数组元素末尾的个数
切片 s 的长度和容量可通过表达式 len(s) 和 cap(s) 来获取
创建切片
//声明一个数组
var nums =[3]int{1, 2, 3}
//0.直接声明
var slice =[]int{0, 1, 2}
//1.从数组中引用切片 其中a:b是指包括a但不包括b
var slice1 = nums[0:2] //{1,2}
//如果不写的则默认为0(左边)或最大值(右边)
var slice2 = slice1[:2] <==> var slice2 = slice1[0:] <==>var slice2 = slice1[:]
//2.使用make创建Slice 其中int为切片类型,4为其长度,5为容量
slice3 := make([]int, 5)
slice4 := make([]int, 4, 5)
动态操作切片
//使用append向切片中动态的添加元素
func append(s []T, vs ...T) []T
slice5 := make([]int, 4, 5) //{0, 0, 0, 0}
slice5 = append(slice5, 1) //{0,0,0,0,1}
//删除第一个0
sliece5 = slice5[1:]
切片的常用场景
//声明切片
var userIds = []int{}
//模拟获取所有用户ID
for i := 0; i< 100{
userIds = append(userIdS, i);
i++;
}
//对用户信息进行处理
for k,v := range userIds{
userIds[k] = v++
}
字典也可称为 ‘键值对’ 或 ‘key-value’,是一种常用的数据结构,C++中有各种map接口,常用的有map、unordered_map、set、unordered_set等。在Go中通过使用字典来实现键值对的存储,字典是无序的,所以不会根据添加顺序来保证数据的顺序。
//string为键类型,int为值类型
maps := map[string]int{
"java" : 1,
"go" : 2,
"python" : 3,
}
//还可以通过make来创建字典 100为其初始容量 超出可扩容
maps = make(map[string]int, 100)
字典的使用场景
//直接使用
fmt.Println(maps["java"]) //1
//赋值
maps["go"] = 4
//取值 同时判断map中是否存在该键 ok为bool型
value, ok := maps["one"]
if ok { // 找到了
// 处理找到的value
}
//删除
delete(testMap, "four")
对于任何一个优秀的语言来说,并发处理的能力都是决定其优劣的关键。在Go语言中,通过Goroutine来实现并发的处理。
func say(s string) {
fmt.Println(s)
}
func main() {
//通过 go 关键字新开一个协程
go say("world")
say("hello")
}
多协程之间通过Channel进行通信,从功能上可以类比为Java的volatile关键字。
ch := make(chan int)
声明一个int型的Channel,两个协程之间可以通过ch
进行int数据通信,
通过Channel进行数据传输。
ch <- v // 将 v 发送至信道 ch。
v := <-ch // 从 ch 接收值并赋予 v。
package main
import "fmt"
func sum(s []int, c chan int) {
sum := 0
for _, v := range s {
sum += v
}
c <- sum // 将和送入 c
}
//对于main方法来说 相当于就是开启了一个协程
func main() {
s := []int{7, 2, 8, -9, 4, 0}
c := make(chan int)
//通过go关键字开启两个协程 将chaneel当做参数传入
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)
}