目錄
以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語言編程-許式偉