Golang编程基础第一篇——Golang快入门

目录

 

一,为什么我们需要一门新语言

二,顺序编程

2.1 变量

2.2 常量

2.3 类型

2.4 流程控制

2.5 函数

2.6 错误处理 (golang的错误处理适合单出一篇)


以go语言编程为基底,记录下心得 需要后期维护和积累

一,为什么我们需要一门新语言

从摩尔定律说起,为什么我们需要一门新语言。

摩尔定律(英语:Moore's law)是由英特尔(Intel)创始人之一戈登·摩尔提出的。其内容为:集成电路上可容纳的晶体管数目,约每隔两年便会增加一倍;经常被引用的“18个月”,是由英特尔首席执行官大卫·豪斯(David House)提出:预计18个月会将芯片的性能提高一倍(即更多的晶体管使其更快),是一种以倍数增长的观测。

如果不能很快速理解的话,可以参考wiki总结的摩尔定律规律:

摩尔定律的定义归纳起来,主要有以下三种版本:
1,集成电路上可容纳的晶体管数目,约每隔18个月便增加一倍。
2,微处理器的性能每隔18个月提高一倍,或价格下降一半。
3,相同价格所买的电脑,性能每隔18个月增加一倍。

然而随着器件尺寸越来越接近物理极限,科学家们开始研究新的工艺结构,具体做法不用细究,总体上随着新工艺节点的不断推出,晶体管中原子的数量已经越来越少,因为在较小规模上一些量子特性开始出现导致漏电等一系列问题,并且精细晶体管成本增加,制造商开始着手从其他方面提高处理器的性能:
1. 向处理器添加越来越多的内核
2. 超线程技术
3. 为处理器加入缓存

Go做了什么?

同时需要提高软件的效率来解除硬件限制的瓶颈,其中就有09年推出的go

我们现在常用的编程语言大多诞生于单线程时代,即使推出适用于多核编程框架,如netty却会耗费大量精力去学习和运用(编程语言一个最大的比较点就是学习周期的长短)

而Go语言的并发是基于 goroutine 的,goroutine 类似于线程,但并非线程。可以将 goroutine 理解为一种虚拟线程。Go 语言运行时会参与调度 goroutine,并将 goroutine 合理地分配到每个 CPU 中,最大限度地使用CPU性能。开启一个goroutine的消耗非常小(大约2KB的内存),你可以轻松创建数百万个goroutine。

goroutine的特点:

goroutine具有可增长的分段堆栈。这意味着它们只在需要时才会使用更多内存。
goroutine的启动时间比线程快。
goroutine原生支持利用channel安全地进行通信。
goroutine共享数据结构时无需使用互斥锁。

从性能上看,go的性能更接近于java(单核运行某些还是没有java强,但特点是多核处理),而又同C一样是编译型语言执行效率更高。当然go在细节处理上也有很多改进,以后补坑。

二,顺序编程

我觉得学习编程语言不要拿教科书照本宣科,要充分利用语言间的共通性,自己用实例观察语言间的相同和不同,想不通的时候想想编译原理,语言细节上的改变就很自然懂了。看顺序编程前要先熟悉go的关键字和基本保留信息:https://www.runoob.com/go/go-program-structure.html

2.1 变量

2.1.1 变量声明

以下是go语言的变量声明格式

var v1 int    //整型
var v2 string    //字符串
var v3 [10]int    //数组
var v4 []int    //数组分片
var v5 struct {    //结构(里面有一个整形成员)
    f int
}
var v6 *int    //指针
var v7 map[string]int    //map,key为string类型,value为int类型
var v8 func(a int) int    //函数 输入int a,返回值为int

声明也可以这么写:

var {
    v1 int
    v2 string
    v3 [10]int
}

2.1.2 变量初始化

var v1 int = 10
var v2 = 10    //编译器自动推导类型
v3 := 10    //编译器自动推导类型,:=是go特有可以同时声明变量和初始化
var v4 int //0

2.1.3 变量赋值

除了一般等号赋值之外,go还可以多重赋值:

//交换i,j两个值
i , j = j , i

2.1.4 匿名变量

func GetName(b boolean) (firstName, lastName, nickName string) {
    return "James", "Lebron", "LJ"
}

//I just want the nickName
_, _, nickName := GetName()

2.2 常量

2.2.1 常量定义

const PI float64 = 3.14159265
//go的常量定义可以是一个编译期运算的常量表达式
const mask = 1 << 3

2.2.2 预定义变量

/* 
go预定义常量:true,false,iota
其中iota比较特殊也比较好玩,是一个可被编译期修改的常量
在每一个const出现时重置为0,
随后每出现一次iota,其所代表的数字就会增1
*/
 const (
            a = iota   //0
            b          //1
            c          //2
            d = "ha"   //独立值"ha",iota +=1
            e          //"ha"   iota +=1
            f = 100    //100    iota +=1
            g          //100    iota +=1
            h = iota   //7,恢复计数
            i          //8
    )

2.3 类型

2.3.1 布尔类型

//布尔型很常规,记住赋值和关键字
var v1 bool
v1 = true
v2 := (1 == 2)

2.3.2 整型

go的整型都有哪些类型?

这里要注意,两个不同类型的整型数不能直接比较和赋值,需要进行强制转换

var i int 32
j := 64    //编译识别为int
i = int32(j)
if i == int32(j) {
    fmt.Println("i == j")
}

关于位运算,go和c的情况大致相同,只不过取反操作go语言是^x而不是~x

2.3.3 浮点型

go的浮点型都有那些数据类型?

go只有float32(相当于c的float)和float64(相当于c的double类型)

那go编译自动推导的浮点型类型是哪个呢?

var value float32
value = 12
value2 := 12.0 //推导成float64
value = float32(value2)

另外,浮点数比较是一种不精确的表达方式(挖坑:浮点数是怎样存储的),因此不能用==直接比较

//一种替代方式
import "math"

//p为用户自定义的比较精度
func IsEqual(f1, f2, p float64) bool {
    return math.Fdim(f1, f2) < p
}

2.3.4 复数类型

复数由实部和虚部组成,在计算机中用浮点数表示

go中复数初始化

var value1 complex64    //由两个float32组成的复数
value1 = 3.2 + 12i
value2 := 3.2 + 12i
value3 := complex(3.2, 12)

2.3.5 字符串

在go语言中,字符串也是一种基本类型

声明和初始化:

var str string
str = "Hello string"
ch := str[0]    //'H'

字符串的内容不能在初始化后被修改

字符串类型内置len()函数获取字符串长度

字符串遍历:

str := "Hello, world"
n := len(str)
for i := 0; i < n; i++ {
    ch := str[i]    //这里注意是取字符串中的字符,类型为byte
    fmt.Println(i, ch)
}

2.3.6 字符类型

go中支持两个字符类型,一个是byte(uint8),另一个是rune,代表单个unicode字符

go中多数api都假设字符是utf8编码,go也提供了unicode和utf8之间的转换包

2.3.7 数组

数组声明:

[32]byte    //长度为32的byte数组
[2*N] struct {x, y int32}    //复杂类型数组 每个结构体是int类型的x和y组成
[1000]*float64    //指针数组
[3][5]int    //二维数组
[2][2][2]float64    //三维数组,相当于[2]([2]([2]float64))

数组访问:

//1.常规访问
for i := 0; i < len(array); i++ {
    fmt.Println(i + " " + array[i])
}

//2.range遍历 range有两个返回值 数组下标和对应的值
for i, v := range array {
    fmt.Println(i + " " + v)
}

值类型:

go中数组是一个值类型(挖坑,值类型与引用类型),因此函数体无法修改传入数组的内容,因为函数内操作的只是传入数组的一个副本

package main

import "fmt"

func modify(array [10]int) {
    array[0] = 0
}

func main() {
    array := [5]int{1,2,3,4,5}
    modify(array)
    fmt.Println(array)    //1 2 3 4 5
}

2.3.8 数组切片

数组的特点是数组长度在定义之后无法修改,而go提供了数组切片来满足这一需求,那什么是数组切片呢?

数组切片的数据结构可以抽象为3个变量:

1. 一个指向原生数组的指针

2. 数组切片中元素的个数

3. 数组切片已分配的存储空间

它与数组间的关系就相当于C++的std::vector之于数组,可以动态扩放存储空间

1. 创建与声明

/*
根据数组创建切片
*/
var myArray [10]int = [10]int{1,2,3,4,5,6,7,8,9,0}
var mySlice []int = myArray[:5]    //前5个元素创建切片
// var mySlice []int = myArray[5:]    //后5个元素创建切片
// var mySlice []int = myArray[:]    //全部元素创建切片
for _, v := range myArray {
    fmt.Print(v, " ")
}

/*
直接创建
*/
mySlice := make([]int, 5)    //创建初始个数为5的切片 元素值为0
mySlice := make([]int, 5, 10)    //创建一个。。。并预留10个元素的存储空间
mySlice := []int{1,2,3,4,5}    //直接初始化 和数组一样 不过没有size限制

/*
数组切片创建数组切片
*/
oldSlice := []int{1,2,3,4,5}
newSlice := oldSlice[:3]     //基于oldSlice前3个元素创建

2. 动态增减元素

mySlice := make([]int, 5, 10)
mySlice = append(mySlice, 1, 2, 3)
mySlice2 := []int{8,9,0}
mySlice.append(mySlice, mySlice2...)
//数组切片会自动处理存储空间不足的情况

4. 内容复制

slice1 := []int{1,2,3,4,5}
slice2 := []int{6,7,8}

copy(slice2, slice1)    //复制slice1的前3个元素到slice2中
copy(slice1, slice2)    //复制slice2的元素到slice1的前3个位置

2.3.9 map

在C++/Java中map是引入库的形式使用,而go直接定义成数据类型

package main

import "fmt"

type PersonInfo struct {
    ID string
    Name string
    Address string
}

func main() {
    //变量声明及创建
    var personDB map[string] PersionInfo
    personDB = make(map[string] PersonInfo)
    
    //赋值
    personDB["2345"] = PersonInfo{"2345", "Tom", "Room 203"}
    personDB["1"] = PersonInfo{"1", "Jack", "Room 201"}
    
    //删除
    delete(personDB, "1")

    //元素查找
    person, ok := personDB["1234"]
    
    //ok is a bool
    if ok {
        fmt.Println("Found Person", person.Name. "With ID 1234")
    } else {
        fmt.Println("Did not find person with ID 1234")
    }
}

2.4 流程控制

2.4.1 条件从句

关于条件语句,需要注意以下几点:
1. 条件语句不需要使用括号将条件包含起来();
2.无论语句体内有几条语句,花括号{}都是必须存在的;
3. 左花括号{必须与if或者else处于同一行;
4. 在if之后,条件语句之前,可以添加变量初始化语句,使用;间隔;
5. 在有返回值的函数中,不允许将“最终的”return语句包含在if...else...结构中

if a < 5 {
    return 0
} else {
    return 1
}

2.4.2 选择语句

在使用switch结构时,我们需要注意以下几点:
1. 左花括号{必须与switch处于同一行;
2. 条件表达式不限制为常量或者整数;
3. 单个case中,可以出现多个结果选项;
4. 与C语言等规则相反,Go语言不需要用break来明确退出一个case;
5. 只有在case中明确添加fallthrough关键字,才会继续执行紧跟的下一个case; 可以不设定switch之后的条件表达式,在此种情况下,整个switch结构与多个if...else...的逻辑作用等同。

switch i {
    case 0:
        fmt.Println("1")
        fallthrough
    case 1:
        fmt.Println("2"
}

2.4.3 循环语句

使用循环语句时,需要注意的有以下几点。
1. 左花括号{必须与for处于同一行。
2. Go语言中的for循环与C语言一样,都允许在循环条件中定义和初始化变量,唯一的区别是,Go语言不支持以逗号为间隔的多个赋值语句,必须使用平行赋值的方式来初始化多个变量。
 

a := []int{1,2,3,4,5,6}
for i, j := 0, len(a)-1; i < j; i, j = i+1, j-1 {
    a[i], a[j] = a[j], a[i]
}

2.4.4跳转语句

func myFunc() {
    i := 0
    HERE:
    fmt.Println(i)
    i++
    if i < 10 {
        goto HERE
    }
}

2.5 函数

函数的不定参数指函数传入参数个数为不定数量

func myFunc(args ...int) {
    for _, arg := range args {
        fmt.Println(args)
        //按原样传参
        myFunc2(args...)
        //传递片段
        myFunc3(args[1:]...)
    }
}

如上边这个函数就可以接受不定数量的参数,但参数类型必须全部是int

如果你想传递任意类型,可以指定类型为interface{}

下面是go语言标准库fmt.Println()函数原型

func Printf(format string, args ...interface{}) {
    //...
}

2.6 错误处理 (golang的错误处理适合单出一篇)

2.6.1 error接口

error是go的一个关于错误处理的标准模式

type PathError struct {
    Op string
    Path string
    Err error
}

func Stat(name string) (fi FileInfo, err error) {
    var stat syscall.Stat_t
    err = syscall.Stat(name, &stat)
    if err != nil {
        return nil, &PathError{"stat", name, err}
    }
    return fileInfoFromStat(&stat, name), nil
}

2.6.2 defer

defer是go引入的的帮助程序员做复杂清理工作的工作

defer后面的语句会在defer所在函数结束后调用

一个应用场景是互斥锁解锁:

func foo(...) {
    mu.Lock()
    defer mu.Unlock()

    // code logic
}

2.6.3 panic 和 recover

内置函数panic()和recover()用于报告和处理运行时错误和程序错误的场景,处理错误流程

当在一个函数执行过程中调用panic()函数时,正常的函数执行流程将立即终止,但函数中 之前使用defer关键字延迟执行的语句将正常展开执行,之后该函数将返回到调用函数,并导致 逐层向上执行panic流程,直至所属的goroutine中所有正在执行的函数被终止。错误信息将被报 告,包括在调用panic()函数时传入的参数,这个过程称为错误处理流程。

recover()函数用于终止错误处理流程。一般情况下,recover()应该在一个使用defer 关键字的函数中执行以有效截取错误处理流程。如果调用了内置函数recover,并且定义该defer语句的函数发生了panic异常,recover会使程序从panic中恢复,并返回panic value。导致panic异常的函数不会继续运行,但能正常返回。在未发生panic时调用recover,recover会返回nil。

下面是一个例子,猜猜输出是什么

package main
 
import "fmt"
 
func main() {
    f()
    fmt.Println("Returned normally from f.")
}
 
func f() {
    defer func() {
        if r := recover(); r != nil {
            fmt.Println("Recovered in f", r)
        }
    }()
    fmt.Println("Calling g.")
    g(0)
    fmt.Println("Returned normally from g.")
}
 
func g(i int) {
    if i > 3 {
        fmt.Println("Panicking!")
        panic(fmt.Sprintf("%v", i))
    }
    defer fmt.Println("Defer in g", i)
    fmt.Println("Printing in g", i)
    g(i + 1)
}

补充:

go语言指针

我们都知道,变量是一种使用方便的占位符,用于引用计算机内存地址。

Go 语言的取地址符是 &,放到一个变量前使用就会返回相应变量的内存地址。

一个指针变量指向了一个值的内存地址。

类似于变量和常量,在使用指针前你需要声明指针。指针声明格式如下:

var ip *int

指针使用流程:

  • 定义指针变量。
  • 为指针变量赋值。
  • 访问指针变量中指向地址的值。

在指针类型前面加上 * 号(前缀)来获取指针所指向的内容。

 空指针在go里是nil

参考:go语言编程-许式伟

猜你喜欢

转载自blog.csdn.net/wannuoge4766/article/details/104197417
今日推荐