Golang特点总结

Posted 计算机程序

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Golang特点总结相关的知识,希望对你有一定的参考价值。

  • 什么是Go

    Go是一门 并发支持 、垃圾回收 的 编译型 系统编程语言,旨在创

造一门具有在静态编译语言的 高性能 和动态语言的 高效开发 之间拥有

良好平衡点的一门编程语言。

  •  Go 的特点

  1. 类型安全 和 内存安全

  2. 以非常直观和极低代价的方案实现 高并发

  3. 高效的垃圾回收机制

  4. 快速编译(同时解决C语言中头文件太多的问题)

  5. 为多核计算机提供性能提升的方案

  6. UTF-8编码支持

  • GOPATH

    根据约定,GOPATH下需要建立3个目录:

  1. bin(存放编译后生成的可执行文件)

  2. pkg(存放编译后生成的包文件)

  3. src(存放项目源码)

  • Go 常用的命令

  1. go get:获取远程包(需 提前安装 git或hg)

  2. go run:直接运行程序

  3. go build:测试编译,检查是否有编译错误

  4. go fmt:格式化源码(部分IDE在保存时自动调用)

  5. go install:编译包文件并编译整个程序

  6. go test:运行测试文件

  7. go doc:查看文档(CHM手册)


本地官网搭建

godoc -http=:8080


  • Go 内置关键字

(25个均为小写)


break    default     func     interface     select    case    defer    go    map    struct    chan    else    goto    package    switch    const    fallthrough    if    range    type    continue    for    import    return    var

Go 注释

  • // :单行注释

  • /* */:多行注释

Go 程序的一般结构

basic_structure.go


  • Go程序是通过 package 来组织的(与python类似)

  • 只有 package 名称为 main 的包可以包含 main 函数

  • 一个可执行程序 有且仅有 一个 main 包

  • 通过 import 关键字来导入其它非 main 包

  • 通过 const 关键字来进行常量的定义

  • 通过在函数体外部使用 var 关键字来进行全局变量的声明与赋值

  • 通过 type 关键字来进行结构(struct)或接口(interface)的声明

  • 通过 func 关键字来进行函数的声明

Go的package

  1. package的导入

    • 可以多次导入

import "os"

import "fmt"

    • 一次导入

import (

    "os"

    "fmt"

)


注意

导入package未使用,编译会出错

package 别名

用字符替换


import std "fmt"

std.Println("Hello world")

主要用来区分导入的相似包,或者为一些包指定功能名方便使用

省略调用

import . "fmt"

Println("Hello world")

使用.作为别名,不推荐使用

可见性规则

Go语言中,使用 大小写 来决定该 常量、变量、类型、接口、结构

或函数 是否可以被外部包所调用。

根据约定:

  •  函数名首字母 小写 即为private

  •  函数名首字母 大写 即为public

Go 基本类型

  • 布尔型:bool

    • 长度:1字节

    • 取值范围:true, false

    • 注意事项:不可以用数字代表true或false

  • 整型:int/uint

    • 根据运行平台可能为32或64位


  • 8位整型:int8/uint8

    • 长度:1字节

    • 取值范围:-128~127/0~255

  •  字节型:byte(uint8别名)

  • 16位整型:int16/uint16

    • 长度:2字节

    • 取值范围:-32768~32767/0~65535

  • 32位整型:int32(rune)/uint32

    • 长度:4字节

    • 取值范围:-2^32/2~2^32/2-1/0~2^32-1

  • 64位整型:int64/uint64

    • 长度:8字节

    • 取值范围:-2^64/2~2^64/2-1/0~2^64-1

  • 浮点型:float32/float64

    • 长度:4/8字节

    • 小数位:精确到7/15小数位

  • 复数:complex64/complex128

    • 长度:8/16字节

  • 足够保存指针的 32 位或 64 位整数型:uintptr


  • 其它值类型:

    • array、struct、string

  • 引用类型:

    • slice、map、chan

  • 接口类型:inteface

  • 函数类型:func

类型零值

零值并不等于空值,而是当变量被声明为某种类型后的默认值,

通常情况下值类型的默认值为0,bool为false,string为空字符串

类型别名

使用type

type (

    KB int8

)

单个变量的声明与赋值

  •  变量的声明格式:var <变量名称> <变量类型>

  •  变量的赋值格式:<变量名称> = <表达式>

  •  声明的同时赋值:var <变量名称> [变量类型] = <表达式>


多个变量的声明与赋值

  •  全局变量的声明可使用 var() 的方式进行简写

  •  全局变量的声明不可以省略 var,但可使用并行方式

  •  所有变量都可以使用类型推断

  •  局部变量不可以使用 var() 的方式简写,只能使用并行方式


变量的类型转换

  •  Go中不存在隐式转换,所有类型转换必须显式声明

  •  转换只能发生在两种相互兼容的类型之间

  •  类型转换的格式:

    • <ValueA> [:]= <TypeOfValueA>(<ValueB>)

一个问题:数字转字符串

package main


import "fmt"


func main() {

var a = 65

b := string(a)

fmt.Println(b)

}

Consle

A


string() 表示将数据转换成文本格式,因为计算机中存储的任何东西

本质上都是数字,因此此函数自然地认为我们需要的是用数字65表示

的文本 A。

解决办法

引入strconv包

package main


import (

"fmt"

"reflect"

"strconv"

)


func main() {

var a = 65

b := strconv.Itoa(a)

fmt.Println(b)

_type := reflect.TypeOf(b)

fmt.Println(_type)

}

Console


$ go run main.go

65

string

常量的定义

- 常量的值在编译时就已经确定

- 常量的定义格式与变量基本相同

- 等号右侧必须是==常量==或者==常量表达式==

- ==常量表达式中的函数必须是内置函数==

常量的初始化规则与枚举

  •  在定义常量组时,如果不提供初始值,则表示将使用上行的表达式

  •  使用相同的表达式不代表具有相同的值

  •  iota是常量的计数器,从0开始,组中每定义1个常量自动递增1

  •  通过初始化规则与iota可以达到枚举的效果

  •  每遇到一个const关键字,iota就会重置为0

运算符

  • Go中的运算符均是从左至右结合

  • 优先级(从高到低):

  •  `^` `!`  (一元运算符)

  •  `*` `/` `%` `<<` `>>` `&` `&^`

  •  `+` `-` `|` `^` (二元运算符)

  •  `==` `!=` `<` `<=` `>=` `>`

  •  `<-` (专门用于channel)

  •  `&&`

  •  `||`


 问题:

请尝试结合常量的iota与<<运算符实现计算机储存单位的枚举



const (

    _          = iota

    KB float64 = 1 << (iota * 10)  

    MB

    GB

    TB

    PB

    EB

    ZB

    YB

)

 指针

Go虽然保留了指针,但与其它编程语言不同的是,在Go当中不支持指针运算以及`->`运算符,而直接采用`.`选择符来操作指针目标对象的成员


  • 默认值为`nil`而非`NULL`

递增递减语句

在Go当中,++ 与 -- 是作为语句而并不是作为表达式

判断语句if

  •  条件表达式没有括号

  •  支持一个初始化表达式(可以是并行方式)

  •  左大括号必须和条件语句或else在同一行

  •  支持单行模式

  •  初始化语句中的变量为block级别,同时隐藏外部同名变量

  •  1.0.3版本中的编译器BUG

循环语句for

  •  Go只有for一个循环语句关键字,但支持3种形式

  •  初始化和步进表达式可以是多个值

  •  条件语句每次循环都会被重新检查,因此不建议在条件语句中

  •  使用函数,尽量提前计算好条件并以变量或常量代替

  •  左大括号必须和条件语句在同一行


选择语句switch

  •  可以使用任何类型或表达式作为条件语句

  •  不需要写break,一旦条件符合自动终止

  •  如希望继续执行下一个case,需使用fallthrough语句

  •  支持一个初始化表达式(可以是并行方式),右侧需跟分号

  •  左大括号必须和条件语句在同一行

跳转语句goto, break, continue

  •  三个语法都可以配合标签使用

  •  标签名区分大小写,若不使用会造成编译错误

  •  Break与continue配合标签可用于多层循环的跳出

  •  Goto是调整执行位置,与其它2个语句配合标签的结果并不相同

数组Array

  •  定义数组的格式:var <varName> [n]<type>,n>=0

  •  数组长度也是类型的一部分,因此具有不同长度的数组为不同类型==

  •  注意区分指向数组的指针和指针数组

  •  数组在Go中为值类型

  •  相同类型数组之间可以使用或!=进行比较,但不可以使用<或>

  •  可以使用new来创建数组,此方法返回一个指向数组的指针

  •  Go支持多维数组

切片

  •  其本身并不是数组,它指向底层的数组

  •  作为变长数组的替代方案,可以关联底层数组的局部或全部

  •  为引用类型

  •  可以直接创建或从底层数组获取生成

  •  使用len()获取元素个数,cap()获取容量

  •  一般使用make()创建

  •  如果多个slice指向相同底层数组,其中一个的值改变会影响全部

  •  make([]T, len, cap)

  •  其中cap可以省略,则和len的值相同

  •  len表示存数的元素个数,cap表示容量


Reslice

  •  Reslice时索引以被slice的切片为准

  •  索引不可以超过被slice的切片的容量cap()值

  •  索引越界不会导致底层数组的重新分配而是引发错误


Append

  •  可以在slice尾部追加元素

  •  可以将一个slice追加在另一个slice尾部

  •  如果最终长度未超过追加到slice的容量则返回原始slice

  •  如果超过追加到的slice的容量则将重新分配数组并拷贝原始数据


Copy 

如果长度不同,copy只会去最少长度copy,因为会受到容量限制。


注意:copy后的两个slice还是指向原来的底层数组。

map

  •  类似其它语言中的哈希表或者字典,以key-value形式存储数据

  •  Key必须是支持==或!=比较运算的类型,不可以是函数、map或slice

  •  Map查找比线性搜索快很多,但比使用索引访问数据的类型慢100倍

  •  Map使用make()创建,支持 := 这种简写方式

  •  make([keyType]valueType, cap),cap表示容量,可省略

  •  超出容量时会自动扩容,但尽量提供一个合理的初始值

  •  使用len()获取元素个数

  •  键值对不存在时自动添加,使用delete()删除某键值对

  •  使用 for range 对map和slice进行迭代操作


注意:for range 取出的值是值copy


线性搜索与索引访问数据

线性搜索


从一个元素序列中从头到尾检索查找目标数据,主要是用值比较


索引访问数据


比如数组,可以通过索引值直接获取数据。例如 a[0]为目标数据

函数function

  •  Go 函数 不支持 嵌套、重载和默认参数

  •  但支持以下特性:

    • 无需声明原型、不定长度变参、多返回值、命名返回值参数

    • 匿名函数、闭包

  •  定义函数使用关键字 func,且左大括号不能另起一行

  •  函数也可以作为一种类型使用

defer

  •  执行方式类似其它语言中的析构函数,在函数体执行结束后

  •  按照调用顺序的相反顺序逐个执行

  •  即使函数发生严重错误也会执行

  •  支持匿名函数的调用

  •  常用于资源清理、文件关闭、解锁以及记录时间等操作

  •  通过与匿名函数配合可在return之后修改函数计算结果

  •  Go 没有异常机制,但有 panic/recover 模式来处理错误

  •  Panic 可以在任何地方引发,但recover只有在defer调用的函数中有效

结构struct

- Go 中的struct与C中的struct非常相似,并且Go没有class

- 使用 type <Name> struct{} 定义结构,名称遵循可见性规则

- 支持指向自身的指针类型成员

- 支持匿名结构,可用作成员或定义成员变量

- 匿名结构也可以用于map的值

- 可以使用字面值对结构进行初始化

- 允许直接通过指针来读写结构成员

- 相同类型的成员可进行直接拷贝赋值

- 支持 == 与 !=比较运算符,但不支持 > 或 <

- 支持匿名字段,本质上是定义了以某个类型名为名称的字段

- 嵌入结构作为匿名字段看起来像继承,但不是继承

- 可以使用匿名字段指针

方法method

  •  Go中虽没有class,但依旧有method

  •  通过显示说明receiver来实现与某个类型的组合

  •  只能为同一个包中的类型定义方法

  •  Receiver 可以是类型的值或者指针

  •  不存在方法重载

  •  可以使用值或指针来调用方法,编译器会自动完成转换

  •  从某种意义上来说,方法是函数的语法糖,因为receiver其实就是方法所接收的第1个参数(Method Value vs. Method Expression)

  •  如果外部结构和嵌入结构存在同名方法,则优先调用外部结构的方法

  •  类型别名不会拥有底层类型所附带的方法

  •  方法可以调用结构中的非公开字段


Method value VS. Method Expression


package main


import (

"fmt"

)


type _A struct {

Name string

}


func main() {

    // Method Value

a := _A{Name: "Alice"}

fmt.Println(a.Name)

a.Rename()

fmt.Println(a.Name)

// Method EAxpression

b := _A{Name: "Alice"}

fmt.Println(b.Name)

(*_A).Rename(&b)

fmt.Println(a.Name)

}


func (c *_A) Rename() {

c.Name = "Bob"

}


output

Alice

Bob

Alice

Bob


接口interface

  •  接口是一个或多个方法签名的集合

  •  只要某个类型拥有该接口的所有方法签名,即算实现该接口,无需显示

  •  声明实现了哪个接口,这称为 Structural Typing

  •  接口只有方法声明,没有实现,没有数据字段

  •  接口可以匿名嵌入其它接口,或嵌入到结构中

  •  将对象赋值给接口时,会发生拷贝,而接口内部存储的是指向这个

  •  复制品的指针,既无法修改复制品的状态,也无法获取指针

  •  只有当接口存储的类型和对象都为nil时,接口才等于nil

  •  接口调用不会做receiver的自动转换

  •  接口同样支持匿名字段方法

  •  接口也可实现类似OOP中的多态

  •  空接口可以作为任何类型数据的容器

  •  类型断言

    • 通过类型断言的ok pattern可以判断接口中的数据类型

  • 使用type switch则可针对空接口进行比较全面的类型判断

  •  接口转换

    • 可以将拥有超集的接口转换为子集的接口


延伸阅读


评:为什么我不喜欢Go语言式的接口(www.ituring.com.cn/article/37642)


反射reflection


  •  反射可大大提高程序的灵活性,使得 interface{} 有更大的发挥余地

  •  反射使用 TypeOf 和 ValueOf 函数从接口中获取目标对象信息

  •  反射会将匿名字段作为独立字段(匿名字段本质)

  •  想要利用反射修改对象状态,前提是 interface.data 是 settable,即 pointer-interface

  •  通过反射可以“动态”调用方法


并发concurrency


很多人都是冲着 Go 大肆宣扬的高并发而忍不住跃跃欲试,但其实从

源码的解析来看,goroutine 只是由官方实现的超级“线程池”而已。

不过话说回来,每个实例 4-5KB 的栈内存占用和由于实现机制而大幅

减少的创建和销毁开销,是制造 Go 号称的高并发的根本原因。另外,

goroutine 的简单易用,也在语言层面上给予了开发者巨大的便利。


  1. 并发不是并行:Concurrency Is Not Parallelism 并发主要由切换时间片来实现“同时”运行,在并行则是直接利用多核实现多线程的运行,但 Go 可以设置使用核数,以发挥多核计算机的能力。

  2. Goroutine 奉行通过通信来共享内存,而不是共享内存来通信。

Channel

  •  Channel是 goroutine 沟通的桥梁,大都是阻塞同步的

  •  通过 make 创建,close 关闭

  •  Channel 是引用类型

  •  可以使用 for range 来迭代不断操作 channel

  •  可以设置单向或双向通道

  •  可以设置缓存大小,在未被填满前不会发生阻塞


Select


  •  可处理一个或多个 channel 的发送与接收

  •  同时有多个可用的 channel时按随机顺序处理

  •  可用空的 select 来阻塞 main 函数

  •  可设置超时


以上是关于Golang特点总结的主要内容,如果未能解决你的问题,请参考以下文章

golang 都有哪些比较稳定的 web 开发框架

老奶奶可以看懂系列之---Golang的Map映射

老奶奶可以看懂系列之---Golang的Map映射

老奶奶可以看懂系列之---Golang的Map映射

Golang M 2023 6 topic

[译]Golang 知识点总结