Golang 初探
因为自己是一名 Javaer,所以会把重点放在这两门语言不同的地方上。
Golang 初探
1 变量与常量
1.1 变量
Golang 是强类型的,Golang 中定义变量的方式和 Java 有不小的差别,倒是和 JS 有一些类似,例:
var x int // 定义一个整型变量,值为 0(默认值)
var y = 42 // 根据右侧值推导类型,这里推导出 int 类型
var z int = 100 // 显式指定类型
w := 88 // 简洁方式,但仅能用在函数体内
// 批量定义变量
var (
x int = 10
y string = "chenxi"
z bool = true
)
这么多定义变量的方式,如何选择?
- 函数内部局部变量,使用
:=
简洁高效。 - 全局变量,使用
var
显式指定类型。 - 一次性定义多个变量,使用批量声明。
另外,Golang 中定义局部变量后,就必须使用,否则会报错。
1.2 常量
Golang 中的常量关键字为 const
,声明方式类似于将声明变量的 var
替换为 const
,就不再赘述。
但 Golang 中的常量中有个比较有意思的东西:iota
, iota
是 Golang 中提供的常量关键字,一般用在使用 const()
批量定义常量时,它会在 const
关键字出现时被重置为 0(const
内部的第一行之前),const
中每新增一行常量声明将使 iota
计数一次,例:
const (
a = iota
b
c
d
)
fmt.Println(a, b, c, d) // 0 1 2 3
const (
x = iota
y = 3
z = iota
)
fmt.Println(x, y, z) // 0 3 2
2 函数
Golang 中的函数定义:
func function_name([parameter list]) [return_types] {
函数体
}
Golang 中的函数可以有多个返回值:
package main
import "fmt"
func swap(x, y string) (string, string) {
return y, x
}
func main() {
a, b := swap("Google", "Runoob")
fmt.Println(a, b)
}
Golang 中的函数可以作为参数传递:
type fc func() string
func func1() string {
return "我是 func1"
}
func func2(f fc) string {
return f()
}
func main() {
f := func1
fmt.Println(func2(f))
}
Golang 中的闭包函数,即在一个函数中定义一个新函数,函数内部的函数就称为闭包函数,闭包函数能访问外部函数的局部变量,且能保存外部函数的变量状态,算是 Golang 的特色了,例:
func outer() func() int {
x := 1
return func() int {
x++
return x
}
}
func main() {
fc := outer()
x := fc()
fmt.Println(x) // 2
x = fc()
fmt.Println(x) // 3
}
3 Golang 的面向对象
在 Golang 中没有像 Java 那样的类的概念,或者说 Golang 本身就不是一门面向对象的语言,但在 Golang 中仍可进行面向对象的编程。
3.1 封装
在 Golang 中使用结构体 struct
来组织和封装数据,虽然不能在结构体中直接添加方法,但可以通过为函数定义接收者,来为结构体添加方法,例:
// 定义一个结构体类型
type Person struct {
Name string
Age int
}
// 给结构体定义一个方法
func (p Person) Greet() {
fmt.Println("Hello, my name is", p.Name)
}
func main() {
// 创建结构体实例
p := Person{Name: "Alice", Age: 30}
p.Greet() // 调用结构体的方法
}
同时,对于结构体中的属性,若是首字母大写,则可以被其它包进行访问,反之若是首字母小写,则仅能在定义的包内进行访问。
3.2 继承
Golang 中没有像 Java 那样使用 extends
实现继承的概念,而是通过组合(在一个结构体中嵌入另一个结构体)的方式来实现继承:
type Animal struct {
Name string
}
func (a Animal) Speak() {
fmt.Println(a.Name, "makes a sound")
}
// 通过嵌套 Animal 类型实现组合
type Dog struct {
Animal // 嵌套 Animal
Breed string
}
func main() {
d := Dog{
Animal: Animal{Name: "Buddy"},
Breed: "Golden Retriever",
}
d.Speak() // 调用 Animal 的 Speak 方法
}
3.3 多态
Golang 中是有着接口的,但无需显式的使用 implements
,只要定义的结构体实现了接口定义的所有方法,就代表该结构体实现了该接口。
type UserService interface {
getUserName() string
}
type UserServiceImpl struct {
}
func (u UserServiceImpl) getUserName() string {
return "chenxi"
}
func main() {
var userService UserService = UserServiceImpl{}
fmt.Println(userService.getUserName())
}
4 指针
Golang 中是有着指针的,和 C++ 中的指针有些相似。
func test(a *int) {
*a = 10
}
func main() {
a := 20
test(&a)
fmt.Println(a) // 10
}
5 数组与切片
Golang 中的数组:
numbers := [5]int{1, 2, 3, 4, 5}
numbers := [...]int{1, 2, 3, 4, 5} // 数组长度不确定可使用 ...
numbers := [5]int{0: 2, 1: 3} // 指定数组长度后,可通过指定下标来初始化元素
而 Golang 中的切片则类似于 Java 中的 ArrayList,是动态数组:
slice1 := []int{1, 2, 3}
fmt.Println(slice1, len(slice1), cap(slice1)) // [1 2 3] 3 3
slice1 = append(slice1, 4)
fmt.Println(slice1, len(slice1), cap(slice1)) // [1 2 3 4] 4 6
也可以使用 make
来定义切片,make([]T, length, capacity)
6 Map
Map 也算是各大语言中必备的数据类型了,Go 中的 Map:
// 使用 make 创建 Map
map_variable := make(map[KeyType]ValueType, initialCapacity)
// 使用字面量创建 Map
m := map[string]int{
"apple": 1,
"banana": 2,
"orange": 3,
}
// 基于 key 删除元素
delete(countryCapitalMap, "France")
7 range
Go 中对数组、切片、Map 的遍历,往往会使用到 range
,例:
for key, value := range oldMap {
newMap[key] = value
}
for key := range oldMap
for _, value := range oldMap
如果是遍历数组、切片、字符串,rang
返回的第一个元素是下标,而第二个元素则是下标对应的值。
8 并发
8.1 Goroutine
Goroutine 是轻量级线程,由 Golang 在运行时进行管理,要在 Golang 中使用 Goroutine 非常简单,语法:
go 函数名(参数列表)
8.2 Channel
Channel 用于 Goroutine 之间数据的传递:
ch := make(chan int, 1) // 声明一个通道,缓存为 1
x := 3
ch <- x // 把 x 发送到通道 ch
v := <-ch // 从 ch 接收数据
注意:如果声明的通道不带缓冲区,发送方会阻塞到接收方进行了数据的接收,也就是从 Channel 中发送和接收数据变成了同步的操作。
要遍历 Channel 可使用 range
,range
的停止条件是 Channel 的关闭,所以若是声明通道后,没有进行通道的关闭,则会导致 range
循环无法退出。
同时,也可使用 selcet
用于接收多个通道的数据:
select {
case <-ch1:
// 如果通道 ch1 可读取数据,则执行此块
case ch2 <- value:
// 如果通道 ch2 可以接收数据,则执行此块
default:
// 如果以上两个操作都没有准备好,则执行此块
}