综述
Go(又称 Golang)是 Google 的 Robert Griesemer,Rob Pike 及 Ken Thompson 开发的一种静态强类型、编译型语言。Go 语言语法与 C 相近,但功能上有:内存安全,GC(垃圾回收),结构形态及 CSP-style 并发计算。
根据一些基准测试,Go的性能是python的30倍,其可以与C++媲美,但是在语法以及使用上又比C++简单。所以其又被成为互联网时代的C语言。
目前在大规模使用Go语言的公司有很多,比如今日头条、七牛云,而随着云计算越来越成熟,以及落地的案例越来越多,很多公司或者云厂商都或多或少地基于Go做了很多工具,比如著名地容器调度平台Kubernetes,比如容器Docker,比如分布式缓存存储Etcd等等。所以抱着一颗好奇心去认识这们语言,可能可以给我们日常的工作带来一些额外的思考。
Go语言最具有特色的特性莫过于goroutine, Go语言在语言层可以通过goroutine对函数实现并发并行,goroutine类似于线程,但并非线程, goroutine会在Go语言运行时进行自动调度,因此,Go语言非常适合用于高并发网络服务的编写。
整个Go语言的分享分为4篇,主要从Go的语法、基于Go的web研发、Go的一些框架以及容器调度相关内容。
本篇是Go语言的入门,主要还是围绕着Go语言的语法展开。
首先我们通过Go来写一个简单的web服务器来初步认识一下Golang。
package main
import (
"io"
"log"
"net/http"
)
func main() {
http.HandleFunc("/index", func(writer http.ResponseWriter, request *http.Request) {
writer.WriteHeader(200)
io.WriteString(writer,"Hello World")
})
error := http.ListenAndServe(":8080",nil)
if error!=nil {
log.Fatal(error)
}
}
现在我们打开服务器来测试一下这个简版的服务器。
我们可以看到运行成功过。是不是感觉比java简单多了。
下面我们进入正题,来初步了解一下Golang的基本语法,我们还是以实际的Demo进行。
1、变量定义
变量定义有两种方式,如下:
第一种方式:
package main
import "fmt"
func main() {
var a int = 1
fmt.Println(a)
}
第二种方式:
package main
import "fmt"
func main() {
a := 1
fmt.Println(a)
}
这两种方式都可以来定义一个变量,但是这两种有什么区别呢?
如果熟悉java泛型就应该知道,其实语法是有语法糖一说的,就是程序可以对你所定义的变量进行“类型推演”,简言说就是程序能够知道你所定义的变量是想表现为整型还是字符串类型。
比如我们可以做一个测试:
package main
import (
"fmt"
"reflect"
)
func main() {
var b int = 1
a := 1
//判别变量b的类型
fmt.Println(reflect.TypeOf(b))
//判别变量a的类型
fmt.Println(reflect.TypeOf(a))
//判别变量a,b的类型是否是一样的
fmt.Println(reflect.TypeOf(a)==reflect.TypeOf(b))
}
如下是输出的结果:
我们可以看到两个变量的类型是一样的。
我们将变量a换一下,代码如下:
package main
import (
"fmt"
"reflect"
)
func main() {
var b int = 1
a := "1"
//判别变量b的类型
fmt.Println(reflect.TypeOf(b))
//判别变量a的类型
fmt.Println(reflect.TypeOf(a))
//判别变量a,b的类型是否是一样的
fmt.Println(reflect.TypeOf(a)==reflect.TypeOf(b))
}
如下是输出的结果:
可以看到两个类型是不一致的。
2、常量值定义
常量值定义的关键字为const
package main
import "fmt"
const name = "gaoxiang"
func main() {
fmt.Println(name)
}
如果有多个常量值定义,我们可以这样来做:
package main
import "fmt"
const (
name = "gaoxiang"
age = 27
email = "gaoxiang002@jxlife.com.cn"
)
func main() {
fmt.Printf("My name is %s\n",name)
fmt.Printf("age is %d\n",age)
fmt.Printf("email is %s\n",email)
}
输出如下:
3、方法/函数定义
方法/函数是程序的灵魂,我们看下在golang如何去定义方法
package main
import "fmt"
func PrintUserInfo(name string,age int,email string){
fmt.Printf("My name is %s\n",name)
fmt.Printf("age is %d\n",age)
fmt.Printf("email is %s\n",email)
}
func main() {
PrintUserInfo("gaoxiang",27,"gaoxiang002@jxlife.com.cn")
}
定义一个带有返回值的方法
package main
import "fmt"
func PrintUserInfo(age int) int{
return age
}
func main() {
age := 27
fmt.Println(PrintUserInfo(age))
}
4、结构体的定义
使用java的同学应该知道面向对象的概念,那Golang种面向对象该如何进行了。这里我们就需要用到结构体,听到结构体大家应该很熟悉,C语言中我们经常使用结构体,正如我在开头所说的,其实Golang是一门比较偏向于底层的语言,所以在很多地方与大家所见到的C语言有相似的地方,我们来简单做一个demo:
package main
import "fmt"
type UserInfo struct {
Name string
Age int
Email string
}
func main() {
user := UserInfo{
Name: "gaoxiang",
Age: 27,
Email: "gaoxiang002@jxlife.com.cn",
}
fmt.Printf("My name is %s\n",user.Name)
fmt.Printf("age is %d\n",user.Age)
fmt.Printf("email is %s\n",user.Email)
}
内容正常输出,如下:
除了我们定义结构体变量外,我们之前说过一个程序的灵魂是方法/函数,那我们如何来定义一个结构体所对应的行为呢?
package main
import "fmt"
type UserInfo struct {
Name string
Age int
Email string
}
func (u UserInfo) AddUserInfo(info UserInfo){
fmt.Printf("My name is %s\n",info.Name)
fmt.Printf("age is %d\n",info.Age)
fmt.Printf("email is %s\n",info.Email)
}
func main() {
user := UserInfo{
Name: "gaoxiang",
Age: 27,
Email: "gaoxiang002@jxlife.com.cn",
}
user.AddUserInfo(user)
}
我们可以看到,Golang的语法和我们所熟悉的一些语言还是有一定的差异。
对比java来说,突然我们又发现一件事情,java中还有接口的含义,那Golang中有没有呢?
5、接口的定义
我们还是以如上的接口来举例,看如下一个例子:
package main
import "fmt"
type Person interface {
Inroduce(u UserInfo)
}
type UserInfo struct {
Name string
Age int
Email string
}
func (u UserInfo) Inroduce(info UserInfo){
fmt.Printf("My name is %s\n",info.Name)
fmt.Printf("age is %d\n",info.Age)
fmt.Printf("email is %s\n",info.Email)
}
func main() {
user := UserInfo{
Name: "gaoxiang",
Age: 27,
Email: "gaoxiang002@jxlife.com.cn",
}
user.Inroduce(user)
}
如上的代码就代表一个人有介绍自己的能力,而UserInfo其实就是实现了介绍自己的这个能力。
根据Golang的语法规则,如果实现某一个接口,只需要去实现接口中所定义的方法即可。
除了接口定义之外,我们知道java中还有一个概念叫做“继承”,那在Golang中是如何来实现继承的呢?
6、继承的定义
继承也是面向对象的中一个很重要的模块,在java中只存在单继承,而在Golang中,其实我们可以使用多继承,如下是关于继承的定义方式:
我们以交通工具来举例说明:
package main
import "fmt"
//交通工具定义
type Vechile struct {
//车轮
Tyre int
//交通工具名称
Name string
}
type Car struct {
Vechile
//座位的数量
Seats int
}
type Bike struct {
Vechile
//铃铛的数量
Bell int
}
func (v Vechile) Walk(){
fmt.Printf("The vechile's name is %s\n",v.Name)
}
func main() {
car := Car{
Vechile: Vechile{
Tyre: 4,
Name: "Car",
},
Seats: 4,
}
bike := Bike{
Vechile: Vechile{
Tyre: 2,
Name: "Bike",
},
Bell: 1,
}
car.Walk()
bike.Walk()
}
我们可以看到如下的输出结果:
如上说明一款交通工具有轮子和名字,自行车和小汽车都属于交通工具的一种,并且都能够行走。
下面我们来看一个多继承的例子,通过一个案例来改造一下程序。
交通工具我们都要在工厂生产,所以会有生产这一说,看如下的一段程序:
package main
import "fmt"
type Factory struct {
FatoryName string
Address string
}
//交通工具定义
type Vechile struct {
Factory
//车轮
Tyre int
//交通工具名称
Name string
}
type Car struct {
Vechile
//座位的数量
Seats int
}
type Bike struct {
Vechile
//铃铛的数量
Bell int
}
func (v Vechile) Walk(){
fmt.Printf("The vechile's name is %s,factory's name is %s\n",v.Name,v.FatoryName)
}
func main() {
car := Car{
Vechile: Vechile{
Tyre: 4,
Name: "Car",
Factory: Factory{
FatoryName: "BYD",
Address: "Beijing",
},
},
Seats: 4,
}
bike := Bike{
Vechile: Vechile{
Tyre: 2,
Name: "Bike",
Factory: Factory{
FatoryName: "Moby",
Address: "Shanghai",
},
},
Bell: 1,
}
car.Walk()
bike.Walk()
}
我们看一下输出的情况:
如上就实现了一个多继承的例子。
我们知道函数或者方法有形参和实参一说,而我们在学习C语言的时候了解过,通过指针我们可以去修改一个值,举个小例子:
package main
import "fmt"
type Family struct {
Persons int
}
func main() {
f := Family{Persons:10}
fmt.Println(f.Persons)
f1 := f
fmt.Println(f1.Persons)
f1.Persons = 20
fmt.Println(f1.Persons)
fmt.Println(f.Persons)
}
看到这段程序大家可以先想一下输出的是什么结果?
我们打印一下看看
我们可以看到f1的改变并没有让f也改变,其实这个就涉及到计算机中的内存分配,这里f1和f其实占据了两个内存的位置,所以f1的改变并不会导致f的改变,那么我们如何能够做到f1的改变也让f也能够改变呢?这个时候就需要用到指针。
7、指针的定义
我们知道指针所代表的值是指向内存的一个地址,而非值本身。
我们将如上的程序做一个修改。
package main
import "fmt"
type Family struct {
Persons int
}
func main() {
f := &Family{Persons:10}
fmt.Println(f.Persons)
f1 := f
fmt.Println(f1.Persons)
f1.Persons = 20
fmt.Println(f1.Persons)
fmt.Println(f.Persons)
}
看一下两段程序有什么不同?
其实就是在Family结构体前面加了一个地址引用的符号,我们看下输出的结果是什么?
这样我们可以看到值都发生了改变。
我们在看一个带方法的指针例子。
package main
import "fmt"
type Family struct {
Persons int
}
func (f *Family) Show(){
fmt.Println(f.Persons)
}
func main() {
f := &Family{Persons:10}
f.Show()
f1 := f
f1.Show()
f1.Persons = 20
f1.Show()
f.Show()
}
其实上面带不带*号都可以。这里只是想演示一下指针的用法。后期对于指针这一块还会有更深入的分析。
8、判断定义
每一款语言中都有一些基础的语法,比如分支流程的判断,如下:
package main
import "fmt"
func main() {
var a int = 10
if a>20 {
fmt.Println("yes")
}else {
fmt.Print("No")
}
}
9、循环定义
看如下的案例代码,常规的方式
package main
import "fmt"
func main() {
for i := 1;i<10;i++ {
fmt.Println(i)
}
}
当然,我们也可以定义一个无限循环的方式:
package main
import "fmt"
func main() {
for ;; {
fmt.Println(1)
}
}
我们可以看到无限输出1
10、队列的定义
个人觉得Golang中对于数据结构的支持并不是很丰富,还好现在已经有很多开源的库进行支持,后期也会去介绍这些开源的库,我们还是先从基本的开始,首先我们看一下队列的定义。
package main
import (
"container/list"
"fmt"
)
func main() {
list := list.New()
list.PushFront(1)
list.PushFront(2)
list.PushFront("gaoxiang")
fmt.Println(list.Len())
}
如上就是一个List的定义方式,我们可以看到Golang和java不太一样的是,Golang可以在List中放不同的类型的值,这个实现原理后期也会详细讲解。
那么我们如何来取出这些值呢?
package main
import (
"container/list"
"fmt"
)
func main() {
list := list.New()
list.PushFront(1)
list.PushFront(2)
list.PushFront("gaoxiang")
for v := list.Front();v!=nil;v = v.Next() {
fmt.Println(v.Value)
}
}
如上就可以去遍历List中所存放的元素。
11、Map的定义
package main
import "fmt"
func main() {
var m map[string]string
m["name"] = "gaoxiang"
m["email"] = "gaoxiang002@jxlife.com.cn"
fmt.Println(m)
}
但是假如我们map中其实有多个类型的值,那该如何定义了,其实Golang中是可以解决这个问题的,如下:
package main
import "fmt"
func main() {
var m map[interface{}]interface{}
m["name"] = "gaoxiang"
m["email"] = "gaoxiang002@jxlife.com.cn"
m["age"] = 27
fmt.Println(m)
}
我们可以看到Golang中可以使用interface{}做为泛型的使用。记住这个场景,在Golang中,这个能力很重要。
至此,我们第一篇关于Golang的基本语法就介绍完毕了。
第二篇我们将进入Golang的高级篇去探索此语言的更多特性,包括协程、锁、SyncPool等使用。
—————–EOF——————