變數&函式
最近在學習golang,寫下學習筆記提升記憶。 為了看起來不是那麼枯燥,本學習筆記採用分析程式碼的形式。
首先搬出我們最經典的第一段程式碼:
hello world
package main // 0
import "fmt" // 1實現格式化的 I/O
/* Print something */ // 2
func main() { // 3
fmt.Println("Hello, world; or καλημε ́ρα κóσμε; orこんにちは 世界") // 4
}
複製程式碼
首先我們要認識到
每個Go 程式都是由包組成,程式的執行入口是包main
- 首行這個是必須的。所有的 Go 檔案以 package 開頭,對於獨立執行的執行檔案必須是 package main;
- 這是說需要將fmt加入到main。不是main 的包被稱為庫 末尾以 // 開頭的內容是單行註釋 Package fmt包含有格式化I/O函式,類似於C語言的printf和scanf
- 這也是註釋,表示多行註釋。
- package main 必須首先出現,緊跟著是 import。在 Go 中,package 總是首先出現, 然後是 import,然後是其他所有內容。當 Go 程式在執行的時候,首先呼叫的函式 是 main.main(),這是從 C 中繼承而來。這裡定義了這個函式
- 呼叫了來自於 fmt 包的函式列印字串到螢幕。字串由 " 包裹,並且可以包含非 ASCII 的字元。這裡使用了希臘文和日文、中文"
編譯和執行程式碼
構建 Go 程式的最佳途徑是使用 go 工具。 構建 helloworld 只需要:
1. go build helloworld.go
# 結果是叫做 helloworld 的可執行檔案。
2. ./helloworld
# Hello, world; or καλημε ́ρα κóσμε; or こんにちは世界
複製程式碼
變數
Go 是靜態型別語言,不能在執行期改變變數型別。
變數如果不提供初始化值將自動初始化為零值。如果提供初始化值,可省略變數型別,由編譯器自動推斷。
var x int
// 使用關鍵字 var 定義變數, 跟函式的引數列表一樣,型別在後面。
var c, python, java bool
// 多個相同型別的變數可以寫在一行。
var f float32 = 1.6
var i, j int = 1, 2
// 變數定義可以包含初始值,每個變數對應一個。
var s = "abc"
// 如果初始化是使用表示式,則可以省略型別;變數從初始值中獲得型別。
複製程式碼
變數在定義時沒有明確的初始化時會賦值為零值 。
零值是:
- 數值型別為 0 ,
- 布林型別為 false ,
- 字串為 "" (空字串)。
在函式內部,可用更簡略的 ":=" 式定義變數。
func main() {
n, s := 12, "Hello, World!"
println(s, n)
}
複製程式碼
函式外的每個語句都必須以關鍵字開始( var 、 func 、等等), := 結構不能使用在函式外。
可一次定義多個變數。
var x, y, z int
var s, n = "abc", 123
var (
a int
b float32
)
func main() {
n, s := 0x1234, "Hello, World!"
println(x, s, n)
}
複製程式碼
一個特殊的變數名是 _(下劃線)。任何賦給它的值都被丟棄。在這個例子中,將 35 賦值給 b,同時丟棄 34。
_, b := 34, 35
複製程式碼
Go 的編譯器會對宣告卻未使用的變數報錯
var s string // 全域性變數沒問題。
func main() {
i := 0 // Error: i declared and not used。(可使 "_ = i" 規避)
}
複製程式碼
定義完之後的變數可以被重新賦值 比如第8行,將計算結果賦值給result。
常量
常量值必須是編譯期可確定的數字、字串、布林值。
常量的定義與變數類似,只不過使用 const 關鍵字
const x, y int = 1, 2
const s = "Hello, World!"
// 多常量初始化 // 型別推斷
// 常量組
const (
a, b = 10, 100
c bool = false
)
func main() _{
const x = 'xxx' // 未使用區域性常量不會引發編譯錯誤
}
複製程式碼
在常量中,如果不提供型別和初始化值,那麼被看作和上一常量相同
const (
s = "abc"
x // x = "abc"
)
複製程式碼
變數值的引用
通常情況下 go 語言的變數持有相應的值
。
對於通道
、函式
、方法
、對映
以及切片
的引用變數,它們持有的都是引用
,也既是儲存指標的變數
。
值在傳遞給函式或者方法的時候會被複制一次
不同型別引數所佔空間如下:
型別 | 佔用空間 |
---|---|
bool | 型別佔1~8個位元組 |
傳遞字串 | 佔 16個位元組(64位)或者8個位元組(32位) |
傳遞切片 | 佔 16個位元組(64位)或者12個位元組(32位) |
傳遞指標 | 佔 8個位元組(64位)或者4個位元組(32位) |
陣列
是按值傳遞的,所以傳遞大陣列代價較大 可用切片代替
變數是賦給記憶體塊的名字,該記憶體塊用於儲存特定的資料型別
。
指標是指儲存了另一個變數記憶體地址的變數
。建立的指標用來指向另一個某種型別的變數。
為了便於理解,我們看以下兩段程式碼。
x := 3 y := 22
// 變數 x, y 為int型 分別賦值 3 22 記憶體地址 0xf840000148 0xf840000150
x == 3 && y == 22
複製程式碼
pi := &x
// 變數pi 為 *int(指向int型變數的指標) 在這裡我們將變數x的記憶體地址賦值給pi,即pi 儲存了另一個變數的記憶體地址(這也是指標定義)
pi == 3 && x == 3 && y == 22
x++
// x + 1 此時 x==4 pi 指向x的記憶體地址 所以
pi == 4 && x == 4 && y == 22
*pi++
// *pi ++ 意為著pi指向的值增加
*pi == 5 & x == 5 && y == 22
pi := &y
//pi 指向y的記憶體地址
*pi == 22 && x == 5 && y == 22
*pi++
// *pi++ 意為著pi指向的值增加
*pi == 23 && x == 5 && y == 23
複製程式碼
基本型別
Go 有明確的數字型別命名, 支援 Unicode, 支援常用資料結構
型別 | 長度 | 預設值 | 說明 |
---|---|---|---|
bool | 1 | false | |
byte | 1 | 0 | unit8 |
rune | 4 | 0 | int32 的別名 代表一個Unicode 碼 |
int, unit | 4 或 8 | 0 | 32 或 64 |
int8, unit8 | 1 | 0 | -128 ~ 127, 0~255 |
int16, unit16 | 2 | 0 | -32768 ~ 32767, 0 ~ 65535 |
int32, unit32 | 4 | 0 | -21億~ 21億, 0 ~ 42億 |
int64, unit64 | 8 | 0 | |
float32 | 4 | 0.0 | |
float64 | 8 | 0.0 | |
complex64 | 8 | ||
complex128 | 16 | ||
unitptr | 4或8 | 足以儲存指標的unit32 或unit64 整數 | |
array | 值型別 | ||
struct | 值型別 | ||
string | "" | UTF-8 字串 | |
slice | nil | 引用型別 | |
map | nil | 引用型別 | |
channel | nil | 引用型別 | |
interface | nil | 介面 | |
function | nil | 函式 |
int
,uint
和uintptr
型別在32位的系統上一般是32位,而在64位系統上是64位。當你需要使用一個整數型別時,你應該首選int
,僅當有特別的理由才使用定長整數型別或者無符號整數型別。 引用型別包括slice
、map
和channel
。它們有複雜的內部結構,除了申請記憶體外,還需要初始化相關屬性
型別轉換
go 不支援
隱式的型別轉換
使用表示式 T(v) 將值 v 轉換為型別 T 。
var b byte = 100
// var n int = b // Error: cannot use b (type byte) as type int in assignment
var n int = int(b) // 顯式轉換
複製程式碼
不能將其他型別當 bool 值使用
a := 100
if a { // Error: non-bool a (type int) used as if condition
println("true")
}
複製程式碼
函式
首先看下面這段程式碼
package main
import "fmt"
func add(x int, y int) int {
return x + y
}
func main() {
fmt.Println(add(42, 13))
}
複製程式碼
函式定義
使用關鍵字 func 定義函式,左大括號不能另起一行
golang中符合規範的函式一般寫成如下的形式:
func functionName(parameter_list) (return_value_list) {
…
}
// parameter_list 是引數列表
// return_value_list 是返回值列表 下邊有詳細的講解
複製程式碼
函式的特性
- 無需宣告原型。 (1)
- 支援不定長變參。
- 支援多返回值。
- 支援命名返回引數。
- 支援匿名函式和閉包。
- 不支援 巢狀 (nested)、過載 (overload) 和 預設引數 (default parameter)
func test(x int, y int, s string) (r int, s string) { // 型別相同的相鄰引數可合併
n := x + y // 多返回值必須用括號。
return n, fmt.Sprintf(s, n)
}
複製程式碼
關鍵字
func
用於定義一個函式test
是你函式的名字 int 型別的變數 x, y 和 string 型別的變數 s 作為輸入引數
引數用pass-by-value
方式傳遞,意味著它們會被複制 當兩個或多個連續的函式命名引數是同一型別
,則除了最後一個型別之外,其他都可以省略。
在這個例子中:
x int, y int
複製程式碼
被縮寫為
x, y int
複製程式碼
變數
r 和 s 是這個函式的命名返回值
。在 Go 的函式中可以返回多個值。
如果不想對返回的引數命名,只需要提供型別:(int, string)。 如果只有一個返回值
,可以省略圓括號。如果函式是一個子過程,並且沒有任何返回值,也可以省略這些內容。
函式體。注意 return 是一個語句,所以包裹引數的括號是可選的。
不定長引數其實就是slice,只能有一個,且必須是最後一個。
func test(s string, n ...int) string {
var x int
for _, i := range n {
x += i
}
return fmt.Sprintf(s, x)
}
// 使用slice 做變參時,必須展開
func main() {
s := []int{1, 2, 3}
println(test("sum: %d", s...))
}
複製程式碼
函式是第一類物件,可作為引數傳遞
就像其他在 Go 中的其他東西一樣,函式也是值而已。它們可以像下面這樣賦值給變數:
func main() {
a := func() { // 定義一個匿名函式,並且賦值給 a
println("Hello")
} // 這裡沒有 ()
a() // 呼叫函式
}
複製程式碼
如果使用 fmt.Printf("%T\n", a) 列印 a 的型別,輸出結果是 func()
返回值
函式可以返回任意數量返回值
Go 函式的返回值或者結果引數可以指定一個名字,並且像原始的變數那樣使用,就像 輸入引數那樣。如果對其命名,在函式開始時,它們會用其型別的零值初始化
package main
import "fmt"
func swap(x, y string) (string, string) {
return y, x
}
func main() {
a, b := swap("hello", "world")
fmt.Println(a, b)
}
/*
函式可以返回任意數量返回值
swap 函式返回了兩個字串
*/
複製程式碼
Go 的返回值可以被命名,並且就像在函式體開頭宣告的變數那樣使用。
package main
import "fmt"
func split(sum int) (x, y int) { // 初始化返回值為 x,y
x = sum * 4 / 9 // x,y 已經初始化,可以直接賦值使用
y = sum - x
return // 隱式返回x,y(裸返回)
}
func main() {
fmt.Println(split(17))
}
/*
在長的函式中這樣的裸返回會影響程式碼的可讀性。
*/
複製程式碼
有返回值的函式,必須有明確的return 語句,否則會引發編譯錯誤
名詞解釋
函式原型
函式宣告
由函式返回型別、函式名和形參列表組成。形參列表必須包括形參型別,但是不必對形參命名。這三個元素被稱為函式原型,函式原型描述了函式的介面函式原型
類似函式定義時的函式頭,又稱函式宣告。為了能使函式在定義之前就能被呼叫,C++規定可以先說明函式原型,然後就可以呼叫函式。函式定義可放在程式後面。 由於函式原型是一條語句,因此函式原型必須以分號結束。函式原型由函式返回型別、函式名和參數列組成,它與函式定義的返回型別、函式名和參數列必須一致。函式原型必須包含引數的識別符號(對函式宣告而言是可選的) 注意:函式原型與函式定義
必須一致,否則會引起連線錯誤。
下節預告
變數和函式部分暫時這些,有更新還會補充。下一篇將會是控制流。 將會用到的程式碼為:
package main
import "fmt"
func main() {
result := 0
for i := 0; i <= 10; i++ {
result = fibonacci(i)
fmt.Printf("fibonacci(%d) is: %d\n", i, result)
}
}
func fibonacci(n int) (res int) {
if n <= 1 {
res = 1
} else {
res = fibonacci(n-1) + fibonacci(n-2)
}
return
}
複製程式碼
參考連結
最後,感謝女朋友支援和包容,比❤️
想了解以下內容可以在公號輸入相應關鍵字獲取歷史文章: 公號&小程式
| 設計模式
| 併發&協程