Golang的Main方法
1
2
3
4
5
6
| package main
func main() {
print("Hello, World!")
}
|
Java與Golang的不同-宣告變數
這應該是大家最快認為Java跟Golang不同的地方,在Java中,撇除掉Java10加入的 var 宣告,Java的變數宣告格式大略如下,也沒有其他方式宣告了
在Golang中則是有三種不同的方式去宣告一個變數
- 標準變量聲明,這個最類似Java,與Java不同在於前面加了一個var,並且是先聲明變量名稱再宣告型別
- 類型推斷宣告,有點類似javascript的宣告,讓編譯器自己去推斷age的型別為int
- 短變量聲明(簡短賦值),golang常見的簡便聲明方式,由於var這個詞太頻繁出現了,就用:=來取代它
短變量聲明(:=)只允許在函式內部使用,如果是package Level的則不行
var還有一種Java沒有的用法,就是「宣告串列」可以用這種方式一次宣告多個變數
1
2
3
4
5
6
7
8
9
10
| func main() {
var (
age = 10
name = "John"
salary string
)
print(age)
print(name)
print(salary)
}
|
Java與Golang的不同-未使用的區域變數會導致編譯錯誤
在Golang中,為了程式碼的精簡,是「不允許有未使用的變數」存在於程式碼之中的,比如說在Java中,我們可能有時候在函式內部聲明了一個orderId,但久了未使用就繼續讓它存在下去。
1
2
3
4
5
| public void saveItem(){
...
String orderId = orderDao.getOrderId();
return void;
}
|
但在Golang中,是不允許這樣的行為的,編譯器跟IDE都會跟你說「Unused variable ‘orderId’」
但未被使用的常數是允許的
1
2
3
4
5
6
| const sum = 20 * 10
func main() {
print("Hello")
}
|
Golang的基本型態
- 布林:主要就是True跟False,在Golang裡面這樣宣告,跟Java差不多
1
2
3
4
| func main() {
var isOK bool = true
print(isOK)
}
|
1
2
3
4
5
| func main() {
var name string = "Hoxton"
print(name)
}
|
整數:跟Java只有一個int不同,Golang的整數包含了int, int8, int16, int32, int 64, uint, unit8, uint16, uint32, uint 64,int開頭的就代表是signed(帶正負號),而uint就代表unsigned(未帶正負號的)。int8就代表了 -128~127,uint8就代表0-255,以此類推。
通常就是先使用 int 就可以了,在不同系統上的 int 代表的範圍不一樣,如果是32位元的int範圍就是-231~+231,如果是64位元的系統則是-263~+263,
1
2
3
4
| func main(){
var age int = 27
print(age)
}
|
- 浮點:跟Java只有一個float的不一樣,golang分成float 32 以及 float64 ,這邊除非特殊原因也是無腦選擇使用float64,就可以了。關於精度的問題當然也跟所有的程式語言一樣,會有精度丟失的問題,進得不要使用它來表達金額
1
2
3
4
5
| func main() {
var size float64 = 5.0
print(size)
}
|
- 複數:不可能吧,你不可能想在golang上使用複數吧,由於golang本身不支援矩陣,以及種種的一些原因,如果你想在golang裡面進行這些運算,可以考慮使用Gonum這個程式包,但在Golang上進行複數運算是個不太適合的行為,已經有人在討論會在未來的版本移除Golang對複數的支援了
Java與Golang的不同-不允許自動型態轉換
在Java中,我們是允許不同型別的數字相加的,比如說
1
2
3
4
5
6
| public static void main(String[] args) {
float height =10.0f;
int width = 20;
System.out.println(height+width);
//印出 30.0
}
|
但在Golang中,這種事情是不允許的,Golang不允許變數的自動型態提升
1
2
3
4
5
| func main() {
var height int = 10
var weight float64 = 20.5
print(height + weight)
}
|
甚至編譯器本身就會報錯,提醒你
「Invalid operation: height + weight (mismatched types int and float64)」
Java與Golang的不同-宣告一個final變數使用的是const關鍵字
1
2
3
4
| func main() {
const greeting = "hello"
print(greeting)
}
|
有個跟Java比較不一樣的是,在Java中如果我們宣告一個為final的常數,我們通常會用All UPCASE的方式命名,如
1
2
| private final INTEGER AGE_LIMIT=100
private final BIGDICEL TAX_RATE=0.5
|
但在golang中,使用package level等級的宣告名稱的第一個字母的大小寫,會被用來確認這個常數能不能被程式包外面的程式使用,也就是說,首字母的大小寫與否會控制常數的private of public,因此如果你的常數不是一個global的常數,請不要用大寫開頭來命名
複合型別-Array、Map、Struct
Array
宣告方式:
1
2
3
4
5
6
7
8
9
10
11
12
13
| func main() {
//1. 第一種宣告,宣告陣列長度,不宣告陣列內部元素
var array [3]int
//2. 第二種宣告,宣告陣列長度,宣告陣列內部元素
var array2 = [3]int{1, 2, 3}
//3. 第三種宣告,不宣告陣列長度,宣告陣列內部元素
var array3 = [...]int{1, 2, 3, 4, 5}
print(array[0])
print(array2[0])
print(array3[0])
}
|
Go的Array相較於Java來說非常不好用,一個最重要的概念是,Go的陣列大小是陣列型態的一部分,意味著長度為4的陣列和長度為5的陣列,雖然看起來都是陣列,但它們其實有不一樣的型態,以Java來說,我們可以寫一個方法,打印出所有的字串陣列元素
1
2
3
4
5
6
7
8
9
10
11
12
13
14
| public class Main {
public static void main(String[] args) {
String [] arr = {"a", "b", "c"};
String [] arr2 ={"d", "e", "f"};
printArray(arr);
printArray(arr2);
}
static public void printArray(String[] arr) {
for (int i = 0; i < arr.length; i++) {
System.out.println(arr[i]);
}
}
}
|
但在Golang中,我們是沒辦法這樣子的
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
| func main() {
//1. 第一種宣告,宣告陣列長度,不宣告陣列內部元素
var array [3]int
//2. 第二種宣告,宣告陣列長度,宣告陣列內部元素
var array2 = [3]int{1, 2, 3}
//3. 第三種宣告,不宣告陣列長度,宣告陣列內部元素
var array3 = [...]int{1, 2, 3, 4, 5}
print(array[0])
print(array2[0])
print(array3[0])
printArray(array)
printArray(array2)
printArray(array3)
}
func printArray(arr []int) {
for _, value := range arr {
fmt.Print(value, " ")
}
fmt.Println()
}
|
「Cannot use ‘array3’ (type [5] int) as the type [] int」
如果你要使用的話,可以將printArray吃的參數變為陣列長度為3的參數,但這樣,長度為5的array3還是不能使用
更好的Array-Slice
既然Array這麼難用,那我們有時候會需要Array的性質做一些處理時該怎麼做呢?可以使用Slice,Slice是Golang最實用的功能之一,比較一下Slice跟Array兩者的差別
- Slice的宣告不需要指定大小
1
2
3
4
5
6
7
| func main() {
var array = [3]int{1, 2, 3}
var slice = []int{4, 5, 6}
fmt.Println(array, slice)
}
|
- Slice未賦值時會是nil (一種像Java null但又不全然是null的東西,代表的是某些型態缺值)
- Slice不能拿來跟nil以外的東西做==, !=的比較,會出現編譯錯誤,只能跟nil做比較
1
2
3
4
5
6
| func main() {
var slice1 = []int{4, 5, 6}
var slice2 = []int{4, 5, 6}
print(slice1 == slice2)
}
|
- slice有容量的概念,所謂容量,相較於slice的長度,就類似Java中ArrayList的自動擴容一樣。當我們宣告一個slice時,其實是在記憶體中選取一塊連續的空間來寫入這些值,而容量就是這個連續空間的大小。當你的容量等於slice長度時,golang就會去自動配置更大的容量來適配它
Slice的用法
1
2
3
4
| func main() {
var slice1 = []int{4, 5, 6}
print(len(slice1))
}
|
1
2
3
4
5
6
7
8
9
| func main() {
var slice1 = []int{4, 5, 6}
println(len(slice1))
//長度為3
slice1 = append(slice1, 7)
println(len(slice1))
//長度為4
}
|
- make:宣告slice的長度,宣告slice的容量
1
2
3
4
5
6
7
8
9
| func main() {
// 僅宣告長度,不宣告容量
var slice1 = make([]int, 5)
println(len(slice1))
//長度為5
// 長度為5,容量為10
var slice2 = make([]int, 5, 10)
}
|
如何很Go的宣告一個slice
核心目標就是減少slice的擴增次數,如果slice完全不需要擴增,那就使用var並且不設值來建立nil slice
又或者是說你的slice有一些初始值,或是slice的值不會變,那麼slice常值是個很好的選擇
還有一種情況比較複雜,就是你「大概」知道slice有多大,但不確定具體的值是多少,比如說你可能一個商品可能有不同顏色,你知道最少有一個,最多不超過十二個,那你要將slice宣布成多少呢?
Well,這有三種流派的做法
- 如果將slice當成一個緩衝區來用,那就指定一個非零長度
1
2
3
4
5
| func main() {
var itemColor = make([]string, 6)
println(len(itemColor))
println(cap(itemColor))
}
|
- 如果「精確」的知道想要的大小,那可以指定長度,並檢索slice來設定值,但這問題的缺點在於,如果你指定了錯誤的大小,要嘛slice的結尾有許多零值,要嘛slice會存取不存在的元素觸發panic
- 最推薦的作法則是在make時,長度設定為0,並指定容量,如此一來可以使用append來將item加入slice中,這種做法比起前兩種會稍微慢一點,但它造成Bug的機率最低
1
2
3
4
5
| func main() {
var itemColor = make([]string, 0,12)
println(len(itemColor))
println(cap(itemColor))
}
|
Slice的切割
slice顧名思義就是切割,用法有點像python,就像這樣
1
2
3
4
5
6
7
| func main() {
slice := []int{1, 2, 3, 4, 5, 6, 7}
//: 的左邊右邊不寫,就代表是第一位跟最後一位
fmt.Println(slice[1:])
fmt.Println(slice[:1])
fmt.Println(slice[:])
}
|
注意, 從slice取slice出來,不會製造副本,而是類似Java的淺拷貝一樣,除此之外,子 slice 還有一些特性。
1
2
3
4
5
6
7
8
9
10
11
12
| func main() {
father := []int{1, 2, 3, 4, 5}
child := father[:3]
fmt.Println("father:", father)
fmt.Println("child:", child)
child = append(child, 30)
fmt.Println("father:", father)
fmt.Println("child:", child)
}
|
執行完這段程式碼,你會發現一些令人費解的事情
father: [1 2 3 4 5]
child: [1 2 3]
father: [1 2 3 30 5]
child: [1 2 3 30]
這其中的原因有點複雜,大意大概是,當你從一個slice中取出另一個slice時,子slice的容量是原始slice的容量減去子slice在原始slice內的位移(offset)。也就是說,原始slice未使用的容量也會與任何子slice共享。
slice的注意事項
總之,為了避免產生一些奇怪的現象,絕對不要同時使用append與子slice,要再次強調,父子slice會共用同一段記憶體,而且修改其中一個,都會影響另一個,不要在切割slice後修改它們!!!
slice的複製
如果想要擁有獨立的slice,可以使用copy函數
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
| func main() {
father := []int{1, 2, 3, 4, 5}
child := make([]int, 5)
num := copy(child, father)
fmt.Println(num)
fmt.Println("father:", father)
fmt.Println("child:", child)
child = append(child, 30)
fmt.Println("father:", father)
fmt.Println("child:", child)
}
|
Map
Golang Map的宣告跟Java不太相同
1
| HashMap<String, Integer> map = new HashMap<String, Integer>();
|
而Go則是
1
2
3
| var stringMap = map[string]int{}
stringMap := map[string]int{}
stringMap := map[string]int{"one": 1, "two": 2, "three": 3}
|
而Map跟Slice也有一些特性是共通的
如
- Map也會自動擴充
- 可以使用make來指定Map的長度
- 也可以調用len得知長度
- Map的零值是nil
- Map無法被比較,只能用來檢查是否等於nil
Golang的Map與Java的Map用法大置雷同,但還是有一些差別,以下說明差異的部分
Java與Golang的不同-逗號ok(comma ok)的寫法
簡單來講就是用來驗證map中是否存在某個Key值,如下
1
2
3
4
5
6
7
| func main() {
studentMap := map[int]string{1: "Hoxton", 2: "Ian", 3: "Jenny", 4: "Jenny", 5: "Jenny"}
v, ok := studentMap[1]
fmt.Println(v, ok)
v2, ok2 := studentMap[6]
fmt.Println(v2, ok2)
}
|
打印的結果
這種逗號ok的寫法,會將map的結果指派給兩個變數,第一個會是索引的值,第二個則是bool,名稱通常為ok,代表索引鍵是否存在於map中
Java與Golang的不同-Golang中不存在set,使用Map來模擬操作
大意就是用Map不能重複的性質來去做類似Set的操作
1
2
3
4
5
6
7
8
9
10
11
12
| func main() {
orderIdMap := map[int]bool{}
orderIdSlice := []int{1, 2, 3, 3, 3, 3}
for _, orderId := range orderIdSlice { // 遍历切片
if _, ok := orderIdMap[orderId]; !ok { // 如果map中不存在该orderId
orderIdMap[orderId] = true // 添加到map中
}
}
fmt.Println(orderIdMap)
}
|
Struct
Struct跟Class有什麼差別呢?很簡單!Struct沒有繼承的概念!x
Go宣告一個struct,以及賦值的方式如下
1
2
3
4
5
6
7
8
9
10
11
12
13
14
| func main() {
type person struct {
Name string
age int
Pet string
}
hoxton := person{Name: "hoxton", age: 22, pet: "dog"}
fmt.Println(hoxton)
ian := person{Name: "ian", age: 22}
fmt.Println(ian)
}
|
Java有匿名Class,那麽golang當然也是不會錯過的
1
2
3
4
5
6
7
8
9
10
| func main() {
pet := struct {
Name string
Type string
}{Name: "red", Type: "dog"}
fmt.Println(pet.Type)
}
|
如果有兩個struct,他們的內部的Field名稱、Field數量、Field順序、Field型別等等,全部都相同,唯一不同的只有struct名稱不一樣,那麼這兩個struct是可以相互轉換的
Go的控制結構-If, For
If
Go的If還蠻正常的,如下,跟其他語言的差別在於條件的部分沒有加上小括號而已
1
2
3
4
5
6
7
8
9
10
11
12
| func main() {
number := rand.Intn(10)
if number == 0 {
fmt.Println(number, " number Is 0")
} else if number > 5 {
fmt.Println(number, "number is greater than 5")
} else {
fmt.Println(number, "number is less than 5 , but not 0")
}
}
|
另外