微信悬浮二维码
微信扫一扫
关注更多Golang内幕
go基础简介-Go语言社区

go基础简介

版权声明:本文来源CSDN,博主原创文章,遵循 CC 4.0 by-sa 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/alwaysrun/article/details/82657162

Go语言是一种静态强类型、编译型、并发型,并具有垃圾回收功能的编程语言。Go 的正式语法使用分号来结束语句,但大多数情况下是可以省略的(编译器会自动添加);如果你在一行中写多个语句,则需要用分号隔开不同的语句:

  • Go程序是通过package来组织的;

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

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

  • 如果导入的包但是没有用到类型或者函数则会报编译错误;

  • package别名: import io "fmt" 这样将fmt设置一个别名为io,调用时为io.Println("...");

在 main.main 函数执行之前所有代码都运行在同一个goroutine,也就是程序的主系统线程中。因此,如果某个 init 函数内部用go关键字启动了新的goroutine的话,新的goroutine只有在进入 main.main 函数之后才可能被执行到。

 

go的注释与C++类似:

  • 单行注释 // ...

  • 多行注释 /* ... */

 


数据类型

go中定义了以下基本数据类型:

  • 整数类型:

    • 与系统架构有关的:int与uint(在32位系统下为32,64位系统下为64);

    • 固定长度的:int8/16/32/64、uint8/16/32/64;

  • 浮点类型:float32、float64

  • 布尔类型:bool

  • 复数类型: complex64、complex128

  • 字符类型: byte(uint8的别名)、rune(int32的别名,标识Unicode)

  • 字符串类型: string

    • 一个字符串是一个不可改变的字节序列,可以包含任意的数据,包括byte值0;

    • for range 语法对UTF8字符串提供了特殊支持;

    • 对字符串和 []rune 类型的相互转换提供了支持:如utf8.RuneCountInString(str);

    • 字符串其实是一个结构体,因此字符串的赋值操作也就是reflect.StringHeader 结构体的复制过程,并不会涉及底层字节数组的复制。

 

高级类型:

  • 数组:[N]T,[...]T

  • 切片:[]T

  • 字典:map[K]T

  • 通道类型:chan T

    • 向无缓存通道写入时,会阻塞等待读取;

    • 关闭nil或已关闭通道会引发panic;

    • 向已关闭的通道发生数据会引发panic;

    • 从已关闭的通道读取,总是返回0值和false(对于缓存通道,先读取缓存的数据);

 

自定义数据类型(可使用type定义):

  • 别名:type BuffSize int;BuffSize为新的类型,底层类型与int相同,但是为不同的类型,互操作时要进行类型转换(BuffSize(var));

  • 自定义类型: type Name struct { ... }

c2 := make(chan struct{}) // 空结构体

go func() {

fmt.Println("c2")

    c2 <- struct{}{} // struct{}部分是类型, {}表示对应的结构体值

}()

<-c2

 

interface接口:golang不支持完整的面向对象思想,它没有继承,多态完全依赖接口实现;通过接口模拟继承(本质是组合)。

  • Interface定义一组方法,且不能包含任何变量;

  • 只要类型包含了一个接口中的所有方法,那么这个类型就实现这个接口;

  • 如果一个类型含有了多个interface的方法,那么就实现了多个接口;

  • 如果一个类型只含有了一个interface的部分方法,那就没有实现这个接口。

type User struct {

 Name string

 Age int32

}

var user User

var user1 *User = &User{}

var user2 *User = new(User)

// 实现Stringer接口(用于Sprintf)

func (p *User) Name() String{

 return p.name

}

 

类似下面的接口,要注意防止递归循环:

type MyString string

func (m MyString) String() string {

    // return fmt.Sprintf("MyString=%s", m) // 错误:会无限递归

    return fmt.Sprintf("MyString=%s", string(m)) // 可以:注意转换

}

 


数组与切片

Go语言中数组是值语义。一个数组变量即表示整个数组,它并不是隐式的指向第一个元素的指针(比如C语言的数组),而是一个完整的值。当一个数组变量被赋值或者被传递的时候,实际上会复制整个数组

数组的大小是其类型的一部分,不同长度或不同类型的数据组成的数组都是不同的类型,类型 [10]int 和 [20]int 是不同的。

 

切片是可以动态增长和收缩的序列,切片通过对数组进行封装,为数据序列提供了更通用、强大而方便的接口。若某个函数将一个切片作为参数传入,则它对该切片元素的修改对调用者而言同样可见, 这可以理解为传递了底层数组的指针。

 

切片的容量可通过内建函数 cap 获得,它将给出该切片可取得的最大长度。

尽管 Append 可修改 slice 的元素,但切片自身(其运行时数据结构包含指针、长度和容量) 是通过值传递的。所以append时需要使用返回值重新赋值:

 

我们还可以定义一个空的数组,长度为0的数组在内存中并不占用空间。

var d [0]int // 定义一个长度为0的数组

var f = [...]int{} // 定义一个长度为0的数组

 


控制语句

控制语句包括if、for、switch 与 select,其的左大括号一定紧跟在同一行(不能放在下一行)。

 

if 和 switch 像 for 一样可接受可选的初始化语句;没有圆括号,而其主体必须始终使用大括号括住。

if err := file.Chmod(0664); err != nil {

    log.Print(err)

} else { // else必须跟在右括号后面,不能在下一行

    ...

}

 

循环只有一个更通用的 for

// Like a C for

for init; condition; post { }

// Like a C while

for condition { }

// Like a C for(;;)

for { }

 

闭包对捕获的外部变量并不是传值方式访问,而是以引用的方式访问。这种行为可能会导致一些隐含的问题:

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

func(){ println(i) } ()

}

每个函数引用的都是同一个i迭代变量,在循环结束后这个变量的值为3,因此最终输出的都是3。修复的思路是在每轮迭代中为每个函数生成独有的变量。

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

i := i // 定义一个循环体内局部变量i

func(){ println(i) } ()

}

// 通过函数传入i

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

func(i int){ println(i) } (i)

}

 

Go 没有逗号操作符,而 ++ 和 -- 为语句而非表达式。 因此,若你想要在 for 中使用多个变量,应采用平行赋值的方式 (因为它会拒绝 ++ 和 --) 

for i, j := 0, len(a)-1; i < j; i, j = i+1, j-1 {

    a[i], a[j] = a[j], a[i]

}

for key, value := range oldMap {

    newMap[key] = value

}

 

Go 的 switch 比 C 的更通用。其表达式无需为常量或整数,case 语句会自上而下逐一进行求值直到匹配为止;switch 并不会自动下溯(相当于默认有一个隐藏的break),只有在case中明确使用fallthrough关键字,才会继续执行紧跟的下一个case;也 可通过逗号分隔来列举相同的处理条件

switch c {

    case ' ', '?', '&', '=', '#', '+', '%':

    return true

}

switch 也可用于判断接口变量的动态类型。

var t interface{}

t = functionOfSomeType()

switch t := t.(type) {

default:

    fmt.Printf("unexpected type %T", t) // 输出 t 是什么类型

case bool:

    fmt.Printf("boolean %tn", t) 

case int:

    fmt.Printf("integer %dn", t) // t 是 int 类型

case *int:

    fmt.Printf("pointer to integer %dn", *t) // t 是 *int 类型

}

 

当 select 有多个分支可用时,会随机选择一个可用的管道分支(可用于模拟随机数生成),如果没有可用的管道分支则选择 default 分支,否则会一直保存阻塞状态。

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

    select { // 通道中随机写入0/1

    case c <- 0:

    case c <- 1:

    }

}

 


函数

Go 与众不同的特性之一就是函数和方法可返回多个值。返回值或结果 “形参” 可被命名,就像传入的形参一样,可赋值修改。 命名后:就会被初始化为零值; 若该函数执行了一条不带实参的 return 语句,则结果形参的当前值将被返回。

func ReadFull(r Reader, buf []byte) (n int, err error) {

    for len(buf) > 0 && err == nil {

        var nr int

        nr, err = r.Read(buf)

        n += nr

        buf = buf[nr:]

    } 

    return // 若没有命名,则必须return n,err

}

 

从函数中返回一个局部变量的地址完全没有问题,这点与 C 不同。该局部变量对应的数据 在函数返回后依然有效。

 


错误处理

Go语言中的错误是一种接口类型。接口信息中包含了原始类型和原始的值。只有当接口的类型和原始的值都为空的时候,接口的值才对应 nil 。其实当接口中类型为空的时候,原始值必然也是空的;反之,当接口对应的原始值为空的时候,接口对应的原始类型并不一定为空的。在处理错误返回值的时候,没有错误的返回值最好直接写为 nil。

func returnsError() error {

    var p *MyError = nil

    if bad() {

        p = ErrBad

    } 

    return p // 总是返回non-nil error;应直接return nil;

}

 

恐慌使用panic抛出,recover用于捕获panic。recover 函数调用有着更严格的要求:我们必须在 defer 函数中直接调用 recover ;如果是在嵌套的 defer 函数中调用 recover 也将导致无法捕获异常。

 


示例程序

go程序的基本框架示例

package main // 包名

import(    // 引入包

 "fmt"

 "time"

 "context"

 "os"

 "os/signal"    // 使用signal引用包内元素

 "syscall"

 "strconv"

)

var _ = strconv.Itoa    // 防止未使用包报错

 

func TryClose(put chan<- int){

defer func() {

if err := recover(); err!=nil{ // 捕获恐慌,只能在defer中直接捕获

fmt.Println("Close fail: ", err)

}

}()

close(put)

}

 

func Producer(fact int, put chan<- int, ctx context.Context){

OutFor:

for i:=1 ; ; i++ {

select{

default: 

case <-ctx.Done():  // 是否完成

fmt.Println(ctx.Err())

break OutFor // 跳到循环外部,否则只是跳出select

}

put <- fact * i

time.Sleep(100*time.Millisecond)

}

TryClose(put)

}

 

func Consumer(get <-chan int){

for v := range get {

fmt.Println(v)

}

fmt.Println("Consumer over")

}

 

type ByteSize float64

const (

_ = iota // 通过赋予空白标识符来忽略第一个值0

KB ByteSize = 1 << (10 * iota)

MB

GB // 1 << 10*3

)

const MAXSIZE ByteSize = 1024*8

const MINSIZE = 16

func (p ByteSize)String() string{

switch{

case p>=GB:

return fmt.Sprintf("%.2fGB", p/GB)

case p>=MB:

return fmt.Sprintf("%.2fMB", p/MB)

case p>=KB:

return fmt.Sprintf("%.2f", p/KB)

}

return fmt.Sprintf("%.2fB", p)

}

 

func VarType(info interface{})(result int){

defer func() {result = result+1}()

 

// if v, ok := info.(int); ok{

//  fmt.Println("int: ", v)

//  result = v

// }else if v, ok := info.(string); ok{

//  fmt.Println("string: ", v)

// }else {

//  fmt.Printf("%vn", info)

// }

 

        // 直接通过switch-type判断

switch v := info.(type){

case int: fmt.Println("int: ", v)

case string: fmt.Println("string: ", v)

default: fmt.Printf("Deufalt: %vn", info)

}

return // 0,实际返回1,因defer在return之后执行

}

 

func ForTest(que []int){

for _, v := range que{

v := v // 若不重新定义,会全部输出最后一个值

go func() {

fmt.Println(v)

}()

}

}

 

func main(){

ch := make(chan int, 10)

        // 1秒钟后超时,若在此之前cancel,也会退出

ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second)

go Producer(3, ch, ctx)

go Producer(5, ch, ctx)

go Consumer(ch)

 // 不同变量定义方式

var zero = 0

var one, two int

three, four := 3, 4

ksize := ByteSize(1024*1024*8)

fmt.Println(zero, one, two, three, four, ksize)

VarType(1)

VarType("test")

ForTest([]int{1, 2, 3})

 

// Ctrl+C to quit

fmt.Println("Press Ctrl+C to quit:")

sig := make(chan os.Signal, 1)

signal.Notify(sig, syscall.SIGINT, syscall.SIGTERM)

fmt.Printf("quit (%v)n", <-sig) // 等待信号

cancel()

}

  • 发表于 2019-08-27 15:57
  • 阅读 ( 11 )

你可能感兴趣的文章

相关问题

推荐好书