目录
以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语言编程-许式伟