go基础简介

目录

数据类型

高级类型

自定义数据类型

interface接口

数组与切片

控制语句

if

for

闭包问题

平行赋值

switch

select

函数

错误处理

示例程序


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

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

if err := file.Chmod(0664); err != nil {
    log.Print(err)
} else { // else必须跟在右括号后面,不能在下一行
    // ...
}

 

for

循环只有一个更通用的 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
}

 

switch

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 %t\n", t) 
case int:
    fmt.Printf("integer %d\n", t) // t 是 int 类型
case *int:
    fmt.Printf("pointer to integer %d\n", *t) // t 是 *int 类型
}

 

select

select 是 Go 中的一个控制结构,类似于用于通信的 switch 语句:

  • 每个 case 必须是一个通信操作,要么是发送要么是接收。
  • 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("%.2fKB", p/KB)
	}
	return fmt.Sprintf("%.2fB", p)
}

///
// defer即类型判断示例
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("%v\n", info)
	// }
	
	// 直接通过switch-type判断
	switch v := info.(type){
	case int: fmt.Println("int: ", v)
	case string: fmt.Println("string: ", v)
	default: fmt.Printf("Deufalt: %v\n", 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()
}

 

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值