一个PHPer眼中的Go语言

文章首发于公众号【程序员读书】,欢迎关注。

Go语言是二十一世纪的C语言,具有接近C/C++等编译语言的性能,又像脚本语言(PHP,Javascript,Python)一样容易上手学习,背靠Google,有强大的开发团队和活跃的社区。

Go语言的特性

  • 语法简单
  • 语言层面支持并发编程
  • 跨平台编译
  • 自带垃圾回收机制

Go语言语句不需要加分号

package main 

import "fmt"

func main(){
    
    s := "hello world"
    
    fmt.Println(s)
}
复制代码

数据类型

Go是强类型语言,声明变量时,需要指明变量是什么类型

var i int = 10

b := 10//错误

func Test(){
    
    s := 10
    
    s := "test"//错误
}
复制代码

PHP是弱类型编程语言,声明变量时,可以不指定变量的类型,由语言自己推导,但在之后该变量仍然可以被赋于其他类型的值

$a = 10;

$a = "test";
复制代码

for、if和switch

for语句、if语句和switch语句的条件体都不需要加括号()

for i := 0; i < 10; i++ {

}

i := 10

if i == 10 {

}
x := 10

switch x{
    
}
复制代码

switch

  • PHP中的switch语句
$a = 10;
switch ($a) {
    case 10:
        echo "111";
        break;
    case 20:
        echo "222";
        break;
}

复制代码

Go的switch语句

func main() {
   var level string = "B"
   var score int = 90

   switch score {
      case 90: level = "A"
      case 80: level = "B"
      case 50,60,70 : level = "C"
      default: level = "D"  
   }

   switch {
      case level == "A" :
         fmt.Printf("优秀!\n" )    
      case level == "B", level == "C" :
         fmt.Printf("良好\n" )      
      case level == "D" :
         fmt.Printf("及格\n" )      
      case level == "F":
         fmt.Printf("不及格\n" )
      default:
         fmt.Printf("差\n" );
   }
   fmt.Printf("你的成绩为: %s\n", level );      
}
复制代码
  • fallthrough关键字
package main

import "fmt"

func main() {
    switch {
    case false:
            fmt.Println("1、case 条件语句为 false")
            fallthrough
    case true:
            fmt.Println("2、case 条件语句为 true")
            fallthrough
    case false:
            fmt.Println("3、case 条件语句为 false")
            fallthrough
    case true:
            fmt.Println("4、case 条件语句为 true")
    case false:
            fmt.Println("5、case 条件语句为 false")
            fallthrough
    default:
            fmt.Println("6、默认 case")
    }
}
复制代码

循环

Go语言不像其他编程一样,支持whiledo...while语句,也不像PHP一样有foreach,Go的循环只有for语句

  • 普通循环
for i := 0; i < 10; i++ {

}
复制代码
  • 遍历数组(array)、map或者切片(slice)
for k,v := range arr {
    
    
}
复制代码

变量可见性

在PHP中,如果不使用类,并没有什么变量可见性,声明的都是全局变量:

a.php

$a = 10;

复制代码

b.php

include_once("a.php");

echo $a;//输出10
复制代码

如果使用类,类的成员,由于public,protected,private 这三个来区分成员的可见性。

class A{
    
    public $a;
    
    private $b;
    
    protected $c;
    
}

class B extends A{
    
    
}
复制代码

而Go语言以包来组织代码的,变量的可见性非常简单,以首字母是否大写包内可见和全部可见

a.go

package my


var Username string = "test"

var age int = 20

const Test = "test"

const t = 123

复制代码

b.go

package my


func GetUsername() string {
    return Username
}

func GetAge()int{
    return getAge()
}

func getAge()int{
    return a
}

复制代码

main.go

package main

import "my"
import "fmt"

func main(){
    fmt.Println(my.Username)
    fmt.Println(my.GetUsername())
    fmt.Println(my.GetAge())
    fmt.Println(my.Test)
    fmt.Println(my.age)//错误
    fmt.Println(my.getAge())//错误
    fmt.Println(my.t)//错误
    
}

复制代码

是否需要编译

Go静态编译型语言,PHP动态脚本语言

由于脚本语言需要由解释器解释执行,所以执行的性能比编译型语言差。

函数返回值

PHP语言与大多数据编程语言一样,函数或方法只支持一个返回值,而Go语言的函数直接多返回值。


func GetFile(path string)(file *os.File,err error){
    
    f,err := os.Open(path)
    
    return f,err
}

复制代码

空白标识符

由于声明变量后不使用会无法通过编译,因为当我们从某个函数接收多个值,但后面又不需要时,可以使用空白标志符。

func GetFile(path string) *os.File{
    
    file,_ := os.Open(path)
    
    return file
}
复制代码

字符双引号

Go语言的字符串类型不支持单引号,而PHP中,单引号或双引号都可以,如:

  • PHP字符串
$a = '';
$b = ""
复制代码
  • Go字符串
var s string = "" //必须使用双引号
复制代码

与Docker的结合

Go程序最终是打包为一个二进制文件,这个文件一般都很小,最多也就是几十M,而我们把可以直接把这个二制文件打包进Docker的镜像中。

但是PHP的项目运行离不开vendor这个存放所有第三方库的文件,这个文件可能很大,可能有几百M,所以使用docker打包PHP项目,镜像会变得非常大。

奇葩的日期格式化

  • PHP时间与日期格式化
$t = time();

//Y:年份,m:月份,d:日期,H:时,i:分钟,s:秒
echo date("Y-m-d H:i:s",$t);

复制代码
  • Go语言时间与日期格式化
//2006:年份,01:月份,02:日期,15/03:时,04:分钟,05:秒
fmt.Println(time.Now().Format("2006-01-02 15:04:05"))

fmt.Println(time.Now().Format("Jan"))//月份
fmt.Println(time.Now().Format("January"))//月份

fmt.Println(time.Now().Format("Monday"))//星期
fmt.Println(time.Now().Format("Mon"))//星期

复制代码

异常捕获机制

PHP像其他编程语言一样支持try...catch语句,用于捕获程序中的异常,如:

try{
    主要逻辑代码
}catch(Exception $ex){
    异常处理
}
复制代码

Go语言没有try...catch语句,Go语言的开发建议是程序员在开发的时候,返回error类型

func main() {
	dsn := "user:pass@tcp(127.0.0.1:3306)/dbname?charset=utf8mb4&parseTime=True&loc=Local"
	db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{})

	if err != nil {
		log.Fatal("数据库连接错误")
	}

	//主要逻辑代码
}

复制代码

如果实在是一定要抛出异常,不让程序继续执行,可以使用panic()函数,然后通过recover捕捉。

func Parse(input string) (s *Syntax, err error) {
    defer func() {
        if p := recover(); p != nil {
            err = fmt.Errorf("internal error: %v", p)
        }
    }()
    // ...parser...
    panic("报错啦")
}
复制代码

defer

Go语言的原创,用于函数执行完成后释放资源等后续操作

func main() {
	f := GetFile("./1.txt")

	defer f.Close()

	f.Write([]byte("test"))
}

func GetFile(path string) *os.File {

	file, _ := os.Open(path)

	return file
}
复制代码

并发

Go从语言层面支持并发,使用go关键字就可以简单直接创建一个goroutine,也就是协程,协程可以理解为一种轻量级的线程。

func main(){
    go func(){
        fmt.Println("hello world")
    }()
}

复制代码
  • 协程:大小开始只有2KB,可以动态调整,最大可以到达1GB。

  • 线程:固定要2M。

因此Go语言在一台机器创建成千上万的协程,支持高并发。

协程通讯有两种方式,共享内存和channel

面向对象

Go语言不是传统的面向对象编程语言,不支持类或者对象,但可以通过结构体来模拟一个类

type Person struct {
	name string //小写:不可导出
	Age  int    //大写,可导出
}
复制代码

与类不同的是,结构体不支持继承,与继承与相比,更推荐Go开发者使用结构体组合

type Student struct{
    
    p Person
    
    number string
}
复制代码
func main() {

	s := Student{
		p: Person{
			name: "test",
			Age:  10,
		},
		number: "123333",
	}

	fmt.Println(s.number)
	fmt.Println(s.p.name)
}
复制代码

匿名嵌入

type Student struct {
	Person
	number string
}

func main() {

	s := Student{
		Person: Person{
			name: "test",
			Age:  10,
		},
		number: "123333",
	}

	fmt.Println(s.number)
	fmt.Println(s.name)
}
复制代码

结构体也可以像类一样定义自己的方法


func (s *Student) Say(){
    fmt.Println("My name is:"+s.name)
}

func (s *Student) getName() string{
    return s.name
}

复制代码

接口

Go与PHP一样也支持接口,但不同的是,在Go语言中,并不是需要显性实现某个接口.

  • PHP实现某个接口
interface PosterInterface
{
    public function getData();
}

class ActivityPosterData implements PosterInterface
{
    public function getData(){
        //实现接口
    }
}
复制代码
  • Go实现某个接口

type Reader interface{
    Read(p []byte) (n int, err error)
}


type File struct{
    
}

func (f *File) Read(p []byte) (n int, err error){
    return 10,nil
}

func main() {
	var r Reader
	r = &File{}
	n, err := r.Read([]byte("test"))
	if err != nil {
		panic(err)
	}
	fmt.Println(n)//输出:10
}

复制代码

自定义类型

Go支持在已有的类型上定义新的类型

package main

import (
	"fmt"
)

type cm int

func (c cm) Format() string {
	return fmt.Sprintf("%d厘米", c)
}

func (c cm) Read(p []byte) (n int, err error){
    return 20,nil
}

func main() {
	var r Reader

	var i cm = 10
	fmt.Println(i.Format()) //输出:10厘米
	
	r = i
	m, err := r.Read([]byte("cm"))

	if err != nil {
		panic(err)
	}
	fmt.Println(m) //输出:20
}

复制代码

项目管理

在PHP中我们使用composer管理项目的依赖包,composer也是社区开发,并不是PHP官方提供的,而Go语言从诞生之初就提供了非常完善的项目管理工具。

工具链

$ go build . //编译

$ go run main.go //直接运行

$ go get github.com/spf13/viper //获取第三方包

$ go fmt //格式化

$ go test //单元测试或性能测试

$ go env //打印Go的环境变量
复制代码

Go Module

//初始化
go mod init jinhongtl/lottery

//整理模块
go mod tidy

//下载依赖包
go mod download
复制代码

风格

  • 大括号问题

函数,方法,if,switch,for,在Go语言中任何使用大插号的地方,大括号都不能单独成一行

//正确
func a(){
    
}

//错误,无法通过编译
func b()
{
    
}

//正确
for i:= 0;i< 10;i++{
    
}
复制代码
  • 命名问题
  1. 变量命名使用骆峰式命名
Username
username
复制代码
  1. 包命名建议使用小写字母,并且与目录同名

Web开发方面的区别

框架

PHP有大而全的框架,如Yii,Laravel,这些框架已经帮我们开发了很多直接使用的功能,比较日志打印,数据库ORM操作,队列,Redis读取等功能

在Go语言中,虽然Web开发方面也有类似的框架,但更常用的是根据自己的需求定制开发,比如,我只是简单开发一个Web应用,读取数据库,那么只需要引用相应的第三方库就可以了。

库名 作用
github.com/spf13/viper 配置读取
github.com/spf13/cobra 命令行框架
github.com/spf13/pflag 命令行参数
github.com/gin-gonic/gin Web框架
github.com/go-playground/validator 数据校验
gorm.io/gorm 数据库ORM
github.com/robfig/cron 定时任务
github.com/sirupsen/logrus 日志
go.uber.org/zap 日志
github.com/gorilla/websocket websocket
github.com/go-redis/redis Redis

方式

Go语言只需要几句代码就可以直接创建一个Web服务器,接收HTTP请求,如:

package main

import "net/http"

func main() {

	http.HandleFunc("/test", func(rw http.ResponseWriter, r *http.Request) {
		rw.Write([]byte("test"))
	})
	http.ListenAndServe(":8088", nil)
}

复制代码

而PHP如果不与nginx或apache配置,单独是无法提供HTTP服务的,PHP的工作方式如下所示:

推荐目录

如果我们要用Go开发自己的应用,推荐下面这个社区用得比较多的目录结构,在此结构的基础,我们可以引用自己需要的第三方库,来定制自己的项目。

├── README.md
├── api
├── assets
├── build
├── cmd
│   ├── apiserver
│   │   └── main.go
│   └── apiauth
│       └── main.go
├── configs
├── deployments
├── docs
├── examples
├── githooks
├── init
├── internal
│   ├── guess
│   ├── lottery
│   └── pkg
├── pkg
├── scripts
├── test
├── third_party
├── tools
├── vendor
├── web
└── website
复制代码

小结

与PHP相比,Go语言开发的程序具有更高性能,可以支持大量的并发,Go语言的代码也有统一的风格,由于完善的工具链,更易进行项目管理,同时,Go语言社区有大量功能单一但很强大的第三方,使得我们可以根据自己的需要,按需使用第三方库组装自己的项目。

猜你喜欢

转载自juejin.im/post/7055217931344429093