go 學習筆記之值得特別關注的基礎語法有哪些
- 2019 年 10 月 3 日
- 筆記
在上篇文章中,我們動手親自編寫了第一個 Go
語言版本的 Hello World
,並且認識了 Go
語言中有意思的變量和不安分的常量.
相信通過上篇文章的斐波那契數列,你已經初步掌握了 Go
語言的變量和常量與其他主要的編程語言的異同,為了接下來更好的學習和掌握 Go
的基礎語法,下面先簡單回顧一下變量和常量相關知識.
有意思的變量和不安分的常量
- 變量默認初始化有零值
func TestVariableZeroValue(t *testing.T) { var a int var s string // 0 t.Log(a, s) // 0 "" t.Logf("%d %q", a, s) }
int
類型的變量初始化默認零值是零0
,string
類型的變量默認初始化零值是空字符串 ` `,其他類型也有相應的零值.
- 多個變量可以同時賦值
func TestVariableInitialValue(t *testing.T) { var a, b int = 1, 2 var s string = "hello Go" // 1 2 hello Go t.Log(a, b, s) }
其他主要的編程語言大多支持多個變量初始化,但極少數有像
Go
語言這樣,不僅支持同時初始化,還可以同時賦值.
- 多個變量可以用小括號
()
統一定義
func TestVariableShorter(t *testing.T) { var ( a int = 1 b int = 2 s string = "hello go" ) // 1 2 hello Go t.Log(a, b, s) }
用小括號
()
方式,省略了相同的var
關鍵字,看起來更加統一
- 變量類型可以被自動推斷
func TestVariableTypeDeduction(t *testing.T) { var a, b, s = 1, 2, "hello Go" // 1 2 hello Go t.Log(a, b, s) }
Go
語言可以根據變量值推測出變量類型,所以可以省略變量類型,再一次簡化了變量定義,但是變量類型仍然是強類型,並不像Js
那樣的弱類型.
- 變量可以用
:=
形式更加簡化
func TestVariableTypeDeductionShorter(t *testing.T) { a, b, s := 1, 2, "hello Go" // 1 2 hello Go t.Log(a, b, s) s = "hello golang" // 1 2 hello golang t.Log(a, b, s) }
省略了關鍵字
var
,轉而使用:=
符號聲明並初始化變量值且利用自動類型推斷能力進一步就簡化變量定義,再次賦值時不能再使用:=
符號.
- 變量
var
聲明作用域大於變量:=
聲明
var globalTestId = 2 // globalTestName := "type_test" is not supported var globalTestName = "type_test" func TestVariableScope(t *testing.T) { // 2 type_test t.Log(globalTestId, globalTestName) globalTestName = "TestVariableScope" // 2 TestVariableScope t.Log(globalTestId, globalTestName) }
var
聲明的變量可以作用於函數外或函數內,而:=
聲明的變量只能作用於函數內,Go
並沒有全局變量的概念,變量的作用範圍只是針對包而言.
- 常量的使用方式和變量一致
func TestConstant(t *testing.T) { const a, b = 3, 4 const s = "hello Go" // 3 4 hello Go t.Log(a, b, s) }
常量聲明關鍵字
const
,常量和變量的使用方式一致,具備類型推斷能力,也存在多種簡化常量定義的形式.
- 雖然沒有枚舉類型,但可以用
iota
配合常量來實現枚舉
func TestConstant2Enum(t *testing.T) { const ( java = iota golang cpp python javascript ) // 0 1 2 3 4 t.Log(java, golang,cpp,python,javascript) }
iota
在一組常量定義中首次出現時,其值為0
,應用到下一個常量時,其值為開始自增1
,再次遇到iota
恢復0
.效果非常像for
循環中的循環索引i
,明明是常量,偏偏玩出了變量的味道,也是我覺得iota
不安分的原因.
- 常量
iota
有妙用,還可以進行位運算
func TestConstantIotaBitCalculate(t *testing.T){ const ( Readable = 1 << iota Writable Executable ) // 0001 0010 0100 即 1 2 4 t.Log(Readable, Writable, Executable) // 0111 即 7,表示可讀,可寫,可執行 accessCode := 7 t.Log(accessCode&Readable == Readable, accessCode&Writable == Writable, accessCode&Executable == Executable) }
定義二進制位最低位為
1
時表示可讀的,左移一位表示可寫的,左移兩位表示可執行的,按照按位與運算邏輯,目標權限位若擁有可讀權限,此時和可讀常量進行按位與運算之後的結果一定是可讀的,由此可見,iota
非常適合此類操作.
總體來說,Go
語言中的變量很有意思,常量 iota
不那麼安分,從上述歸納總結中不難看出,Go
語言和其他主流的編程語言還是有很大不同的,學習時要側重於這些特殊之處.
如果想要回顧本節知識點,可以關注公眾號[雪之夢技術驛站]找到go 學習筆記之有意思的變量和不安分的常量 這篇文章進行查看.
簡潔的類型中格外關照了複數
在學習 Go
語言中的變量和常量時,雖然沒有特意強調變量或常量的類型,但是大多數編程語言的類型基本都是差不多的,畢竟大家所處的現實世界是一樣的嘛!
光是猜測是不夠的,現在我們要梳理一遍 Go
語言的類型有哪些,和其他主流的編程語言相比有什麼不同?
Go
語言的變量類型大致可以分為以下幾種:
bool
布爾類型
bool
,表示真假true|false
(u)int
,(u)int8
,(u)int16
,(u)int32
,(u)int64
,uintptr
int
類型表示整數,雖然不帶位數並不表示沒有位數,32
位操作系統時長度為32
位,64
位操作系統時長度為64
位.最後一個uintptr
是指針類型.
byte(uint8)
,rune(int32)
,string
byte
是位元組類型,也是uint8
的別名,而rune
是Go
中的字符類型,也是int32
的別名.
float32
,float64
,complex64
,complex128
只有
float
類型表示小數,沒有double
類型,類型越少對於開發者而言越簡單,不是嗎?complex64=float32+float32
是複數類型,沒錯!就是高中數學書本上的複數,3+4i
那種奇怪的數字!
Go
的類型還是比較簡單的,整數,小數,複數,位元組,字符和布爾類型,相同種類的類型沒有繼續細分不同的名稱而是直接根據類型長度進行命名的,這樣是非常直觀的,見名知意,根據數據大小直接選用類型,不費腦!
作為一種通用的編程語言,Go
內建類型中居然格外關照了複數這種數學概念類型,是一件有意思的事情,是不是意味着 Go
在工程化項目上做得更好?就像 Go
天生支持並發一樣?
既然為數不多的類型中格外關照了複數類型,那我們簡單使用下複數類型吧,畢竟其他類型和其他主流的編程語言相差不大.
func TestComplex(t *testing.T) { c := 3 + 4i // 5 t.Log(cmplx.Abs(c)) }
生命苦短,直接利用變量類型推斷簡化變量聲明,求出複數類型
c
的模(絕對值)
既然學習了複數,怎麼能少得了歐拉公式,畢竟是"世界上最美的公式",剛好用到了複數的相關知識,那我們就簡單驗證一下吧!
func TestEuler(t *testing.T) { // (0+1.2246467991473515e-16i) t.Log(cmplx.Pow(math.E, 1i*math.Pi) + 1) // (0+1.2246467991473515e-16i) t.Log(cmplx.Exp(1i*math.Pi) + 1) // (0.000+0.000i) t.Logf("%.3f", cmplx.Exp(1i*math.Pi)+1) }
由於複數
complex
是使用float
類型表示的,而float
類型無論是什麼編程語言都是不準確的,所以歐拉公式的計算結果非常非常接近於零,當只保留小數點後三位時,計算結果便是(0.000+0.000i)
,複數的模也就是0
,至此驗證了歐拉公式.
看過複數還是要研究類型特點
複數很重要,但其他類型也很重要,簡單了解過複數的相關知識後,我們仍然要把注意力放到研究這些內建類型的特殊之處上或者說這些類型總體來說相對於其他主流的編程語言有什麼異同.
- 只有顯示類型轉換,不存在隱式類型轉換
func TestExplicitTypeConvert(t *testing.T) { var a, b int = 3, 4 var c int c = int(math.Sqrt(float64(a*a + b*b))) // 3 4 5 t.Log(a, b, c) }
已知勾股定理的兩條直角邊計算斜邊,根據勾股定理得,直角邊長度的平方和再開根號即斜邊長度,然而
math.Sqrt
方法接收的float64
類型,返回的也是float64
類型,可實際值全是int
類型,這種情況下並不會自動進行類型轉換,只能進行強制類型轉換才能得到我們的期望值,這就是顯示類型轉換.
- 別名類型和原類型也不能進行隱式類型轉換
func TestImplicitTypeConvert2(t *testing.T) { type MyInt64 int64 var a int64 = 1 var b MyInt64 // b = a : cannot use a (type int64) as type MyInt64 in assignment b = MyInt64(a) t.Log(a, b) }
MyInt64
是int64
的別名,別名類型的b
和原類型的a
也不能進行也不能進行隱式類型轉換,會報錯cannot use a (type int64) as type MyInt64 in assignment
,只能進行顯示類型轉換.
- 支持指針類型,但不支持任何形式的計算
func TestPointer(t *testing.T) { var a int = 1 var pa *int = &a // 0xc0000921d0 1 1 t.Log(pa, *pa, a) *pa = 2 // 0xc0000901d0 2 2 t.Log(pa, *pa, a) }
同樣的,指針類型也是其他編程語言反過來書寫的,個人覺得這種反而不錯,指向
int
類型的指針*int
,&a
是變量a
的內存地址,所以變量pa
存的就是變量a
的地址,*pa
剛好也就是變量a
的值.
上例顯示聲明了變量類型卻沒有利用到 Go
的類型推斷能力,擺在那的能力卻不利用簡直是浪費,所以提供一種更簡短的方式重寫上述示例,並順便解釋後半句: "指針類型不支持任何形式的計算"
func TestPointerShorter(t *testing.T) { a := 1 pa := &a // 0xc0000e6010 1 1 t.Log(pa, *pa, a) *pa = 2 // 0xc0000e6010 2 2 t.Log(pa, *pa, a) // pa = pa + 1 : invalid operation: pa + 1 (mismatched types *int and int) //pa = pa + 1 // *int int int t.Logf("%T %T %T", pa, *pa,a) }
變量
pa
是指針類型,存儲的是變量的內存地址,只可遠觀而不可褻玩,*pa
就是指針所指向的變量的值,可以進行修改,當然沒問題就像可以重新賦值變量a
一樣,但是指針pa
是不可以進行任何形式的運算的,pa = pa + 1
就會報錯invalid operation
.
你猜運算符操作有沒有彩蛋呢
變量和類型還只是孤立的聲明語句,沒有計算不成邏輯,並不是所有的程序都是預定義的變量,Go
的運算符是簡單還是複雜呢,讓我們親自體驗一下!
- 算術運算符少了
++i
和--i
func TestArithmeticOperator(t *testing.T) { a := 0 // 0 t.Log(a) a = a + 1 // 1 t.Log(a) a = a * 2 // 2 t.Log(a) a = a % 2 // 0 t.Log(a) a++ // 1 t.Log(a) }
支持大部分正常的運算符,不支持前置自增,前置自減,這也是好事,再也不會弄錯
i++
和++i
的運算結果啦,因為根本不支持++i
!
- 比較運算符是否相等有花樣
func TestComparisonOperator(t *testing.T) { a, b := 0, 1 t.Log(a, b) // false true true t.Log(a > b, a < b, a != b) }
大於,小於,不等於這種關係很正常,
Golang
也沒玩出新花樣,和其他主流的編程語言邏輯一樣,不用特別關心.但是關於比較數組==
,Go
表示有話要說!
Go
中的數組是可以進行比較的,當待比較的兩個數組的維度和數組元素的個數相同時,兩個數組元素順序一致且相同時,則兩個數組相等,而其他主流的編程語言一般而言比較的都是數組的引用,所以這一點需要特別注意.
func TestCompareArray(t *testing.T) { a := [...]int{1, 2, 3} //b := [...]int{2, 4} c := [...]int{1, 2, 3} d := [...]int{1, 2, 4} // a == b --> invalid operation: a == b (mismatched types [3]int and [2]int) //t.Log(a == b) // true false t.Log(a == c,a == d) }
數組
a
和c
均是一維數組且元素個數都是3
,因此兩個數組可以比較且相等,若數組a
和b
進行比較,則報錯invalid operation
,是因為兩個數組的元素個數不相同,無法比較!
- 邏輯運算符老實本分無異常
func TestLogicalOperator(t *testing.T) { a, b := true, false t.Log(a, b) // false true false true t.Log(a&&b,a||b,!a,!b) }
- 位運算符新增按位清零
&^
很巧妙
Go
語言中定義按位清零運算符是 &^
,計算規律如下:
當右邊操作位數為 1
時,左邊操作為不論是 1
還是 0
,結果均為 0
;
當右邊操作位數為 0
時,結果同左邊操作位數.
func TestClearZeroOperator(t *testing.T) { // 0 0 1 0 t.Log(1&^1, 0&^1, 1&^0, 0&^1) }
不知道還記不記得,在介紹常量 iota
時,曾經以文件權限為例,判斷給定的權限碼是否擁有特定權限,同樣是給定的權限碼,又該如何撤銷特定權限呢?
func TestClearZeroOperator(t *testing.T) { const ( Readable = 1 << iota Writable Executable ) // 0001 0010 0100 即 1 2 4 t.Log(Readable, Writable, Executable) // 0111 即 7,表示可讀,可寫,可執行 accessCode := 7 t.Log(accessCode&Readable == Readable, accessCode&Writable == Writable, accessCode&Executable == Executable) // 0111 &^ 0001 = 0110 即清除可讀權限 accessCode = accessCode &^ Readable t.Log(accessCode&Readable == Readable, accessCode&Writing == Writing, accessCode&Executable == Executable) }
accessCode = accessCode &^ Readable
進行按位清零操作後就失去了可讀權限,accessCode&Readable == Readable
再次判斷時就沒有可讀權限了.
流程控制語句也有自己的傲嬌
if
有話要說
有了變量類型和各種運算符的加入,現在實現簡單的語句已經不是問題了,如果再輔助流程控制語句,那麼實現較為複雜擁有一定邏輯的語句便可更上一層樓.
Go
語言的 if
條件語句和其他主流的編程語言的語義是一樣的,不一樣的是書寫規則和一些細節上有着自己特點.
- 條件表達式不需要小括號
()
func TestIfCondition(t *testing.T) { for i := 0; i < 10; i++ { if i%2 == 0 { t.Log(i) } } }
Go
語言的各種省略形式使得整體上非常簡潔,但也讓擁有其他主流編程語言的開發者初次接觸時很不習慣,語句結束不用分號;
,條件表達式不用小括號()
等等細節,如果不用IDE
的自動提示功能,這些細節肯定要耗費不少時間.
- 條件表達式中可以定義變量,只要最後的表達式結果是布爾類型即可
func TestIfConditionMultiReturnValue(t *testing.T) { const filename = "test.txt" if content, err := ioutil.ReadFile(filename); err != nil { t.Log(err) } else { t.Logf("%sn", content) } }
Go
語言的函數支持返回多個值,這一點稍後再細說,ioutil.ReadFile
函數返迴文件內容和錯誤信息,當存在錯誤信息時err != nil
,輸出錯誤信息,否則輸出文件內容.
- 條件表達式中定義的變量作用域僅限於當前語句塊
如果嘗試在
if
語句塊外訪問變量content
,則報錯undefined: content
switch
不甘示弱
同其他主流的編程語言相比,switch
語句最大的特點就是多個 case
不需要 break
,Go
會自動進行 break
,這一點很人性化.
switch
會自動break
,除非使用fallthrough
func TestSwitchCondition(t *testing.T) { switch os := runtime.GOOS; os { case "darwin": t.Log("Mac") case "linux": t.Log("Linux") case "windows": t.Log("Windows") default: t.Log(os) } }
- 條件表達式不限制為常量或整數
其他主流的編程語言中
switch
的條件表達式僅支持有限類型,使用方式存在一定局限性,Go
語言則不同,這一點變化也是很有意思的,使用switch
做分支控制時不用擔心變量類型了!
case
語言支持多種條件,用逗號,
分開,邏輯或
func TestSwitchMultiCase(t *testing.T) { for i := 0; i < 10; i++ { switch i { case 0, 2, 4, 6, 8, 10: t.Log("Even", i) case 1, 3, 5, 7, 9: t.Log("odd", i) default: t.Log("default", i) } } }
- 省略
switch
的條件表達式時,switch
的邏輯和多個if else
邏輯相同
func TestSwitchCaseCondition(t *testing.T) { for i := 0; i < 10; i++ { switch { case i%2 == 0: t.Log("Even", i) case i%2 == 1: t.Log("odd", i) default: t.Log("default", i) } } }
for
姍姍來遲
最後登場的是 for
循環,一個人完成了其他主流編程語言三個人的工作,Go
語言中既沒有 while
循環也,也沒有 do while
循環,有的只是 for
循環.
- 循環條件不需要小括號
()
func TestForLoop(t *testing.T) { sum := 0 for i := 1; i <= 100; i++ { sum += i } // 1+2+3+...+99+100=5050 t.Log(sum) }
再一次看到條件表達式不需要小括號
()
應該不會驚訝了吧?if
的條件語句表達式也是類似的,目前為止,接觸到明確需要小括號的()
也只有變量或常量定義時省略形式了.
- 可以省略初始條件
func convert2Binary(n int) string { result := "" for ; n > 0; n /= 2 { lsb := n % 2 result = strconv.Itoa(lsb) + result } return result } func TestConvert2Binary(t *testing.T) { // 1 100 101 1101 t.Log( convert2Binary(1), convert2Binary(4), convert2Binary(5), convert2Binary(13), ) }
利用整數相除法,不斷取余相除,得到給定整數的二進制字符串,這裡就省略了初始條件,只有結束條件和遞增表達式.這種寫法同樣在其他主流的編程語言是沒有的,體現了
Go
設計的簡潔性,這種特性在以後的編程中會越來越多的用到,既然可以省略初始條件,相信你也能猜到可不可以省略其他兩個條件呢?
- 可以省略初始條件和遞增表達式
func printFile(filename string) { if file, err := os.Open(filename); err != nil { panic(err) } else { scanner := bufio.NewScanner(file) for scanner.Scan() { fmt.Println(scanner.Text()) } } } func TestPrintFile(t *testing.T) { const filename = "test.txt" printFile(filename) }
打開文件並逐行讀取內容,其中
scanner.Scan()
的返回值類型是bool
,這裡省略了循環的初始條件和遞增表達式,只有循環的終止條件,也順便實現了while
循環的效果.
- 初始條件,終止條件和遞增表達式可以全部省略
func forever() { for { fmt.Println("hello go") } } func TestForever(t *testing.T) { forever() }
for
循環中沒有任何表達式,意味着這是一個死循環,常用於Web
請求中監控服務端口,是不是比while(true)
要更加簡單?
壓軸的一等公民函數隆重登場
雖然沒有特意強制函數,但是示例代碼中全部都是以函數形式給出的,函數是封裝的一種形式,更是 Go
語言的一等公民.
- 返回值在函數聲明的最後,多個返回值時用小括號
()
func eval(a, b int, op string) int { var result int switch op { case "+": result = a + b case "-": result = a - b case "*": result = a * b case "/": result = a / b default: panic("unsupported operator: " + op) } return result } func TestEval(t *testing.T) { t.Log( eval(1, 2, "+"), eval(1, 2, "-"), eval(1, 2, "*"), eval(1, 2, "/"), //eval(1, 2, "%"), ) }
不論是變量的定義還是函數的定義,
Go
總是和其他主流的編程語言相反,個人覺得挺符合思維順序,畢竟都是先有輸入才能輸出,多個輸出當然要統一隔離在一塊了.
- 可以有零個或一個或多個返回值
func divide(a, b int) (int, int) { return a / b, a % b } func TestDivide(t *testing.T) { // 2 1 t.Log(divide(5, 2)) }
小學時就知道兩個整數相除,除不盡的情況下還有餘數.只不過編程中商和餘數都是分別計算的,
Go
語言支持返回多個結果,終於可以實現小學除法了!
- 返回多個結果時可以給返回值起名字
func divideReturnName(a, b int) (q, r int) { return a / b, a % b } func TestDivideReturnName(t *testing.T) { q, r := divideReturnName(5, 2) // 2 1 t.Log(q, r) }
還是整數除法的示例,只不過給返回值起了變量名稱
(q, r int)
,但這並不影響調用者,某些IDE
可能會基於次特性自動進行代碼補全,調用者接收時的變量名不一定非要是q,r
.
- 其他函數可以作為當前函數的參數
func apply(op func(int, int) int, a, b int) int { p := reflect.ValueOf(op).Pointer() opName := runtime.FuncForPC(p).Name() fmt.Printf("Calling function %s with args (%d,%d)n", opName, a, b) return op(a, b) } func pow(a, b int) int { return int(math.Pow(float64(a), float64(b))) } func TestApply(t *testing.T) { // 1 t.Log(apply(func(a int, b int) int { return a % b }, 5, 2)) // 25 t.Log(apply(pow, 5, 2)) }
apply
函數的第一個參數是op
函數,第二,第三個參數是int
類型的a,b
.其中op
函數也接收兩個int
參數,返回一個int
結果,因此apply
函數的功能就是將a,b
參數傳遞給op
函數去執行,這種方式比switch
固定運算類型要靈活方便!
- 沒有默認參數,可選參數等複雜概念,只有可變參數列表
func sum(numbers ...int) int { result := 0 for i := range numbers { result += numbers[i] } return result } func TestSum(t *testing.T) { // 15 t.Log(sum(1, 2, 3, 4, 5)) }
range
遍歷方式後續再說,這裡可以簡單理解為其他主流編程語言中的foreach
循環,一般包括當前循環索引和循環項.
指針類型很方便同時也很簡單
Go
的語言整體上比較簡單,沒有太多花里胡哨的語法,稍微有點特殊的當屬變量的定義方式了,由於具備類型推斷能力,定義變量的方式有點多,反而覺得選擇困難症,不知道這種情況後續會不會有所改變?
在 Go
語言的為數不多的類型中就有指針類型,指針本來是 c
語言的概念,其他主流的編程語言也有類似的概念,可能不叫做指針而是引用,但 Go
語言的發展和 c++
有一定關係,保留了指針的概念.
但是這並不意味着 Go
語言的指針像 C
語言那樣複雜,相反,Go
語言的指針很方便也很簡單,方便是由於提供我們操作內存地址的方式,簡單是因為不能對指針做任何運算!
簡單回憶一下指針的基本使用方法:
func TestPointerShorter(t *testing.T) { a := 1 pa := &a // 0xc0000e6010 1 1 t.Log(pa, *pa, a) *pa = 2 // 0xc0000e6010 2 2 t.Log(pa, *pa, a) // pa = pa + 1 : invalid operation: pa + 1 (mismatched types *int and int) //pa = pa + 1 // *int int int t.Logf("%T %T %T", pa, *pa,a) }
&
可以獲取變量的指針類型,*
指向變量,但不可以對指針進行運算,所以指針很簡單!
當指針類型和其他類型和函數一起發生化學反應時,我們可能更加關心參數傳遞問題,其他主流的編程語言可能有值傳遞和引用傳遞兩種方式,Go
語言進行參數傳遞時又是如何表現的呢?
func swapByVal(a, b int) { a, b = b, a } func TestSwapByVal(t *testing.T) { a, b := 3, 4 swapByVal(a, b) // 3 4 t.Log(a, b) }
swapByVal
函數內部實現了變量交換的邏輯,但外部函數TestSwapByVal
調用後變量a,b
並沒有改變,可見Go
語言這種參數傳遞是值傳遞而不是引用傳遞.
上面示例中參數傳遞的類型都是普通類型,如果參數是指針類型的話,結果會不會不一樣呢?
func swapByRef(a, b *int) { *a, *b = *b, *a } func TestSwapByRef(t *testing.T) { a, b := 3, 4 swapByRef(&a, &b) // 4 3 t.Log(a, b) }
指針類型進行參數傳遞時可以交換變量的值,拷貝的是內存地址,更改內存地址的指向實現了原始變量的交換,參數傳遞的仍然是值類型.
實際上,Go
語言進行參數傳遞的只有值類型一種,這一點不像其他主流的編程語言那樣可能既存在值類型又存在引用類型.
既然是值類型進行參數傳遞,也就意味着參數傳遞時直接拷貝一份變量供函數調用,函數內部如何修改參數並不會影響到調用者的原始數據.
如果只是簡單類型並且不希望參數值被修改,那最好不過,如果希望參數值被修改呢?那隻能像上例那樣傳遞指針類型.
簡單類型不論是傳遞普通類型還是指針類型,變量的拷貝過程不會太耗費內存也不會影響狀態.
如果傳遞的參數本身是比較複雜的類型,仍然進行變量拷貝過程估計就不能滿足特定需求了,可能會設計成出傳遞複雜對象的某種內部指針,不然真的要進行值傳遞,那還怎麼玩?
Go
只有值傳遞一種方式,雖然簡單,但實際中如何使用應該有特殊技巧,以後再具體分析,現在回到交換變量的例子,換一種思路.
func swap(a, b int) (int, int) { return b, a } func TestSwap(t *testing.T) { a, b := 3, 4 a, b = swap(a, b) // 4 3 t.Log(a, b) }
利用
Go
函數可以返回多個值特性,返回交換後的變量值,調用者接收時相當於重新賦值,比傳遞指針類型要簡單不少!
基礎語法知識總結和下文預告
剛剛接觸 Go
語言時覺得 Go
的語言很簡單也很特別,和其他主流的編程語言相比,有着自己獨特的想法.
語句結束不用分號 ;
而是直接回車換行,這一點有些不習慣,好在強大的 IDE
可以糾正這些細節.
變量聲明時變量名在前,變量類型在後,可能更加符合大腦思維,但是習慣了先寫變量類型再寫變量名,這確實有一定程度的不方便,後來索性不寫變量類型,自然就沒有問題了.
函數聲明同變量聲明類似,返回值放到了最後部分,並且還可以有多個返回值,經過了變量的洗禮,再熟悉函數的這一特點也就不那麼驚訝了,先輸入後輸出,想一想也有道理,難道其他編程語言的順序都是錯的?
接下來就是語法的細節,比如 if
的條件表達式可以進行變量賦值,switch
表達式可以不用 break
,只有 for
循環一種形式等等.
這些細節總體來說比較簡單方便,不用關心細節,放心大膽使用,從而專註於業務邏輯,等到語法不對時,IDE
自然會給出相應的報錯提醒,放心大膽 Go
!
本文主要介紹了 Go
的基本語法以及和其他主流的編程語言的異同,你 Get
到了嗎?
下文將開始介紹 Go
的內建容器類型,數組,切片,Map
來一遍!
歡迎大家一起學習交流,如有不當之處,懇請指正,如需完整源碼,請在公眾號[雪之夢技術驛站]留言回復,感謝你的評論與轉發!