1. switch
package main
import (
"fmt"
"os"
)
//從命令輸入引數,在switch中進行處理
func main() {
//C: argc , **argv
//Go: os.Args ==> 直接可以獲取命令輸入,是一個字串切片 []string
cmds := os.Args
//os.Args[0] ==> 程式名字
//os.Args[1] ==> 第一個引數 ,以此類推
for key, cmd := range cmds {
fmt.Println("key:", key, ", cmd:", cmd, ", cmds len:", len(cmds))
}
if len(cmds) < 2 {
fmt.Println("請正確輸入引數!")
return
}
switch cmds[1] {
case "hello":
fmt.Println("hello")
//go 的switch, 預設加上break了,不需要手動處理
//如果想向下穿透的話,那麼需要加上關鍵字: fallthrough
fallthrough
case "world":
fmt.Println("world")
default:
fmt.Println("default called!")
}
}
2. 標籤
package main
import "fmt"
func main() {
//標籤 LABEL1
//goto LABEL ===> 下次進入迴圈時,i不會儲存之前的狀態,重新從0開始計算,重新來過
//continue LABEL1 ===> continue會跳到指定的位置,但是會記錄之前的狀態,i變成1
//break LABEL1 ==> 直接跳出指定位置的迴圈
//標籤的名字是自定義的,後面加上冒號
LABEL121:
for i := 0; i < 5; i++ {
for j := 0; j < 5; j++ {
if j == 3 {
//goto LABEL1
//continue LABEL1
break LABEL121
//break
}
fmt.Println("i:", i, ",j:", j)
}
}
fmt.Println("over!")
}
3. 列舉const+iota
package main
import "fmt"
//在go語言中沒有列舉型別,但是我們可以使用const + iota(常量累加器)來進行模擬
//模擬一個一週的列舉
const (
MONDAY = iota //0
TUESDAY = iota //1
WEDNESDAY = iota //2
THURSDAY //3 ==> 沒有賦值,預設與上一行相同iota ==> 3
FRIDAY //4
SATURDAY //5
SUNDAY //6
M, N = iota, iota //const屬於預編譯期賦值,所以不需要:=進行自動推導
)
const (
JANU = iota + 1 //1
FER //2
MAR //3
APRI //4
)
//1. iota是常量組計數器
//2.iota從0開始,每換行遞增1
//3. 常量組有個特點如果不賦值,預設與上一行表示式相同
//4.如果同一行出現兩個iota,那麼兩個iota的值是相同的
//5.每個常量組的iota是獨立的,如果遇到const iota會重新清零
func main() {
fmt.Println("列印周:")
fmt.Println(MONDAY)
fmt.Println(TUESDAY)
fmt.Println(WEDNESDAY)
fmt.Println(THURSDAY)
fmt.Println(FRIDAY)
fmt.Println("M:", M, ",N:", N)
fmt.Println("列印月份:")
fmt.Println(JANU) //1
fmt.Println(FER) //2
fmt.Println(MAR) //3
//var number int
//var name string
//var flag bool
//
//
////可以使用變數組來將統一定義變數
//var (
// number int
// name string
// flag bool
//)
}
在goland中配置git shell,並且顯示中文:
我的安裝路徑D:\Program Files (x86)\Tools\Git\Git\etc)下bash.bashrc檔案
export LANG="zh_CN.UTF-8"
export LC_ALL="zh_CN.UTF-8"
git終端顯示中午設定:
4. 結構體
在go語言中,使用結構體來模擬類
package main
import "fmt"
//c語言裡面,我們可以使用typedef int MyInt
type MyInt int //type相當於typdef
//C:
//struct Person {
//
//}
//go語言結構體使用type + struct來處理
type Student struct {
name string
age int
gender string
score float64
}
func main() {
t1 := struct {
a int
}{
a : 100
}
fmt.Println(t1)
var i, j MyInt
i, j = 10, 20
fmt.Println("i+j:", i+j)
//建立變數,並賦值
lily := Student{
name: "Lily",
age: 20,
gender: "女生",
//score: 80, //最後一個元素後面必須加上逗號,如果不加逗號則必須與}同一行
//}
score: 80} //最後一個元素後面必須加上逗號,如果不加逗號則必須與}同一行
//使用結構體各個欄位
fmt.Println("lily:", lily.name, lily.age, lily.gender, lily.score)
//結構體沒有-> 操作
s1 := &lily
fmt.Println("lily 使用指標s1.name列印:", s1.name, s1.age, s1.gender, s1.score)
fmt.Println("lily 使用指標(*s1).name列印:", (*s1).name, s1.age, s1.gender, s1.score)
//在定義期間對結構體賦值時,如果每個欄位都賦值了,那麼欄位的名字可以省略不寫
//如果只對區域性變數賦值,那麼必須明確指定變數名字
Duke := Student{
name: "Duke",
age: 28,
//"男生",
// 99,
}
Duke.gender = "男生"
Duke.score = 100
fmt.Println("Duke:", Duke)
}
5.init函式
C語言沒有init函式,C語言一般需要自己去寫init,然後在建構函式中呼叫
Go語言自帶init函式,每一個包都可以包含一個或多個init函式
package sub
import "fmt"
//0.這個init會在包被引用的時候(import)進行自動呼叫
//1.init函式沒有引數,沒有返回值,原型固定如下
//2.一個包中包含多個init時,呼叫順序是不確定的(同一個包的多個檔案中都可以有init)
//3. init函式時不允許使用者顯示呼叫的
//4. 有的時候引用一個包,可能只想使用這個包裡面的init函式(mysql的init對驅動進行初始化)
//但是不想使用這個包裡面的其他函式,為了防止編譯器報錯,可以使用_形式來處理
//import _ "xxx/xx/sub"
func init() {
fmt.Println("this is first init() in package sub ==> sub.go")
}
func init() {
fmt.Println("this is second init() in package sub ==> sub.go ")
}
//在go語言中,同一層級目錄,不允許出現多個包名
func Sub(a, b int) int {
//init() ==> 不允許顯示呼叫
test4() //由於test4與sub.go在同一個包下面,所以可以使用,並且不需要sub.形式
return a - b
}
utils.go
package sub
//package utils //不允許出現多個包名
import "fmt"
func init() {
fmt.Println("this is init in sub utils.go")
}
func test4() {
fmt.Println("this is test4() in sub/utils!")
}
main.go
package main
import (
_ "day02/05-init函式/sub" //此時,只會呼叫sub裡面的init函式,編譯還不會出錯
//"fmt"
)
func main() {
//res := sub.Sub(10, 5)
//fmt.Println("sub.Sub(20,10) =", res)
}
效果:
使用init場景,在配置檔案管理器中寫init,用於載入配置檔案並解析:
configManager {
//解析載入配置檔案
//IP, PORT
}
6.defer(延遲)
延遲,關鍵字,可以用於修飾語句,函式,確保這條語句可以在當前棧退出的時候執行
lock.Lock()
a = "hello"
lock.Unlock() <=== 經常容易忘掉解鎖
go語言可以使用defer來解決這個問題
{
lock.Lock()
defer lock.Unlock() <=== 在當前棧退出的時候(例如:函式結束時)
a = "hello"
}
{
f1,_ := file.Open()
defer f1.Close()
}
例項:
package main
import (
"fmt"
"os"
)
func main() {
//1.延遲,關鍵字,可以用於修飾語句,函式,確保這條語句可以在當前棧退出的時候執行
//2. 一般用於做資源清理工作
//3. 解鎖、關閉檔案
//4. 在同一個函式中多次呼叫defer,執行時類似於棧的機制:先後入後出
filename := "01-switch.go"
readFile(filename)
}
func readFile(filename string) {
//func Open(name string) (*File, error) {
//1. go語言一般會將錯誤碼作為最後一個引數返回
//2. err一般nil代表沒有錯誤,執行成功,非nil表示執行失敗
f1, err := os.Open(filename)
//匿名函式,沒有名字,屬於一次性的邏輯 ==> lamada表示式
defer func(a int) {
fmt.Println("準備關閉檔案, code:", a)
_ = f1.Close()
}(100) //建立一個匿名函式,同時呼叫
if err != nil {
fmt.Println("os.Open(\"01-switch.go\") ==> 開啟檔案失敗, err:", err)
return
}
defer fmt.Println("0000000")
defer fmt.Println("1111111")
defer fmt.Println("2222222")
buf := make([]byte, 1024) //byte ==> char ==> uint8
//func (f *File) Read(b []byte) (n int, err error) {
n, _ := f1.Read(buf)
fmt.Println("讀取檔案的實際長度:", n)
fmt.Println("讀取的檔案內容:", string(buf)) ==> 型別轉換
}
go語言支援類的操作,但是沒有class關鍵字,使用struct來模擬類
1.封裝-繫結方法
package main
import "fmt"
//Person類,繫結方法:Eat,Run,Laugh, 成員
//public,private
/*
class Person {
public :
string name
int age
public :
Eat() {
xxx
}
}
*/
//任何type的型別,都可以繫結方法
type MyInt1 int
func (mi *MyInt1) printMyInt() {
fmt.Println("MyInt value is:", *mi)
}
type Person struct {
//成員屬性:
name string
age int
gender string
score float64
}
/*
Person:::Eat() {
}
*/
//在類外面繫結方法
func (this *Person) Eat() {
//fmt.Println("Person is eating")
//類的方法,可以使用自己的成員
//fmt.Println(this.name + " is eating!")
this.name = "Duke"
}
func (this Person) Eat2() {
fmt.Println("Person is eating")
//類的方法,可以使用自己的成員
this.name = "Duke"
}
func main() {
lily := Person{
name: "Lily",
age: 30,
gender: "女生",
score: 10,
}
lily1 := lily
fmt.Println("Eat,使用p *Person,修改name的值 ...")
fmt.Println("修改前lily:", lily) //lily
lily.Eat()
fmt.Println("修改後lily:", lily) //Duke
fmt.Println("Eat2,使用p Person,但是不是指標 ...")
fmt.Println("修改前lily:", lily1) //lily
lily1.Eat2()
fmt.Println("修改後lily:", lily1) //lily
var myint1 MyInt1 = 100
myint1.printMyInt()
}
2. 類繼承
package main
import "fmt"
type Human struct {
//成員屬性:
name string
age int
gender string
}
//在類外面繫結方法
func (this *Human) Eat() {
fmt.Println("this is :", this.name)
}
//定義一個學生類,去巢狀一個Hum
type Student1 struct {
hum Human //包含Human型別的變數, 此時是類的巢狀
score float64
school string
}
//定義一個老師,去繼承Human
type Teacher struct {
Human //直接寫Huam型別,沒有欄位名字
subject string //學科
}
func main() {
s1 := Student1{
hum: Human{
name: "Lily",
age: 18,
gender: "女生",
},
school: "昌平一中",
}
fmt.Println("s1.name:", s1.hum.name)
fmt.Println("s1.school:", s1.school)
t1 := Teacher{}
t1.subject = "語文"
t1.name = "榮老師" //下面這幾個欄位都是繼承自Human
t1.age = 35
t1.gender = "女生"
fmt.Println("t1 :", t1)
t1.Eat()
//繼承的時候,雖然我們沒有定義欄位名字,但是會自動建立一個預設的同名欄位
//這是為了在子類中依然可以操作父類,因為:子類父類可能出現同名的欄位
fmt.Println("t1.Human.name:", t1.Human.name)
}
訪問許可權
//在go語言中,許可權都是通過首字母大小來控制
//1. import ==》 如果包名不同,那麼只有大寫字母開頭的才是public的
//2. 對於類裡面的成員、方法===》只有大寫開頭的才能在其他包中使用
3. interface(介面)
package main
import "fmt"
//在C++中,實現介面的時候,使用純虛擬函式代替介面
//在go語言中,有專門的關鍵字 interface來代表介面
//interface不僅僅是用於處理多型的,它可以接受任意的資料型別,有點類似void
func main() {
//func Println(a ...interface{}) (n int, err error) {
fmt.Println("")
//var i,j,k int
//定義三個介面型別
var i, j, k interface{}
names := []string{"duke", "lily"}
i = names
fmt.Println("i代表切片陣列:", i)
age := 20
j = age
fmt.Println("j代表數字:", j)
str := "hello"
k = str
fmt.Println("k代表字串:", k)
}
package main
import "fmt"
//在C++中,實現介面的時候,使用純虛擬函式代替介面
//在go語言中,有專門的關鍵字 interface來代表介面
//interface不僅僅是用於處理多型的,它可以接受任意的資料型別,有點類似void
func main() {
//func Println(a ...interface{}) (n int, err error) {
fmt.Println("")
//var i,j,k int
//定義三個介面型別
var i, j, k interface{}
names := []string{"duke", "lily"}
i = names
fmt.Println("i代表切片陣列:", i)
age := 20
j = age
fmt.Println("j代表數字:", j)
str := "hello"
k = str
fmt.Println("k代表字串:", k)
//我們現在只知道k是interface,但是不能夠明確知道它代表的資料的型別
kvalue, ok := k.(int) //<<<==== 做型別的二次確認
if !ok {
fmt.Println("k不是int")
} else {
fmt.Println("k是int, 值為:", kvalue)
}
//最常用的場景: 把interface當成一個函式的引數,(類似於print),使用switch來判斷使用者輸入的不同型別
//根據不同型別,做相應邏輯處理
//建立一個具有三個介面型別的切片
array := make([]interface{}, 3)
array[0] = 1
array[1] = "Hello world"
array[2] = 3.14
for _, value := range array {
//可以獲取當前介面的真正資料型別
switch v := value.(type) {
case int:
fmt.Printf("當前型別為int, 內容為:%d\n", v)
case string:
fmt.Printf("當前型別為string, 內容為: %s\n", v)
case bool:
fmt.Printf("當前型別為bool, 內容為: %v\n", v) //%v可以自動推到輸出型別
default:
fmt.Println("不是合理的資料型別")
}
}
}
4. 多型
C語言的多型需要父子繼承關係
go語言的多型不需要繼承,只要實現相同的介面即可
package main
import "fmt"
//實現go多型,需要實現定義介面
//人類的武器發起攻擊,不同等級子彈效果不同
//定義一個介面, 注意型別是interface
type IAttack interface {
//介面函式可以有多個,但是隻能有函式原型,不可以有實現
Attack()
//Attack1()
}
//低等級
type HumanLowLevel struct {
name string
level int
}
func (a *HumanLowLevel) Attack() {
fmt.Println("我是:", a.name, ",等級為:", a.level, ", 造成1000點傷害")
}
//高等級
type HumanHighLevel struct {
name string
level int
}
func (a *HumanHighLevel) Attack() {
fmt.Println("我是:", a.name, ",等級為:", a.level, ", 造成5000點傷害")
}
//定義一個多型的通用介面,傳入不同的物件,呼叫同樣的方法,實現不同的效果 ==》 多型
func DoAttack(a IAttack) {
a.Attack()
}
func main() {
//var player interface{}
var player IAttack //定義一個包含Attack的介面變數
lowLevel := HumanLowLevel{
name: "David",
level: 1,
}
highLevel := HumanHighLevel{
name: "David",
level: 10,
}
lowLevel.Attack()
highLevel.Attack()
//對player賦值為lowLevel,介面需要使用指標型別來賦值
player = &lowLevel
player.Attack()
player = &highLevel
player.Attack()
fmt.Println("多型......")
DoAttack(&lowLevel)
DoAttack(&highLevel)
}
定義一個介面,裡面設計好需要的介面,可以有多個(Attack (), Attack()1 ..)
任何實現了這個介面的類,都可以賦值給這個介面,從而實現多型
多個類之間不需要有繼承關係
如果interface中定義了多個介面,那麼實際的類必須全部實現介面函式,才可以賦值
1. 基礎
併發:電腦同時聽歌,看小說,看電影。cpu根據時間片進行劃分,交替執行這個三個程式。我們人可以感覺是同時產生的。
並行:多個CPU(多核)同時執行
c語言裡面實現併發過程使用的是多執行緒(C++的最小資源單元),程式
go語言裡面不是執行緒,而是go程
==> goroutine,go程是go語言原生支援的
每一個go程佔用的系統資源遠遠小於執行緒,一個go程大約需要4K~5K的記憶體資源
一個程式可以啟動大量的go程:
- 執行緒 ==》幾十個
- go程可以啟動成百上千個, ===》 對於實現高併發,效能非常好
- 只需要在目標函式前加上go關鍵字即可
package main
import (
"fmt"
"time"
)
//這個將用於子go程使用
func display() {
count := 1
for {
fmt.Println("=============> 這是子go程:", count)
count++
time.Sleep(1 * time.Second)
}
}
func main() {
//啟動子go程
//go display()
go func() {
count := 1
for {
fmt.Println("=============> 這是子go程:", count)
count++
time.Sleep(1 * time.Second)
}
}()
//主go程
count := 1
for {
fmt.Println("這是主go程:", count)
count++
time.Sleep(1 * time.Second)
}
}
啟動多個字go程,他們會競爭cpu資源
package main
import (
"fmt"
"time"
)
//這個將用於子go程使用
func display(num int) {
count := 1
for {
fmt.Println("=============> 這是子go程:", num, "當前count值:", count)
count++
//time.Sleep(1 * time.Second)
}
}
func main() {
//啟動子go程
for i := 0; i < 3; i++ {
go display(i)
}
//go func() {
// count := 1
// for {
// fmt.Println("=============> 這是子go程:", count)
// count++
// time.Sleep(1 * time.Second)
// }
//}()
//主go程
count := 1
for {
fmt.Println("這是主go程:", count)
count++
time.Sleep(1 * time.Second)
}
}
2. 提前退出go程
package main
import (
"fmt"
"runtime"
"time"
)
//return ===> 返回當前函式
//exit ===> 退出當前程式
//GOEXIT ===> 提前退出當前go程
func main() {
go func() {
go func() {
func() {
fmt.Println("這是子go程內部的函式!")
//return //這是返回當前函式
//os.Exit(-1) //退出程式
runtime.Goexit() //退出當前go程
}()
fmt.Println("子go程結束!") //這句會列印嗎? 會1: 不列印2
fmt.Println("go 2222222222 ")
}()
time.Sleep(2 * time.Second)
fmt.Println("go 111111111111111")
}()
fmt.Println("這是主go程!")
time.Sleep(3 * time.Second)
fmt.Println("OVER!")
}
3. 無緩衝管道channel
package main
import (
"fmt"
"time"
)
func main() {
//sync.RWMutex{}
//當涉及到多go程時,c語言使用互斥量,上鎖來保持資源同步,避免資源競爭問題
//go語言也支援這種方式,但是go語言更好的解決方案是使用管道、通道 channel
//使用通道不需要我們去進行加解鎖
//A 往通道里面寫資料 B從管道里面讀資料,go自動幫我們做好了資料同步
//建立管道: 建立一個裝數字的管道 ==> channel
//strChan := make(chan string) //裝字串的管道
//make(map[int]string, 10)
//裝數字的管道,使用管道的時候一定要make, 同map一樣,否則是nil
//此時是無緩衝的管道
//numChan := make(chan int)
//有緩衝的管道
numChan := make(chan int, 10)
//建立兩個go程,父親寫資料,兒子讀資料
go func() {
for i := 0; i < 50; i++ {
data := <-numChan
fmt.Println("子go程1 讀取資料 ===》 data:", data)
}
}()
go func() {
for i := 0; i < 20; i++ {
//向管道中寫入資料
numChan <- i
fmt.Println("子go程2 寫入資料:", i)
//time.Sleep(1 * time.Second)
}
}()
for i := 20; i < 50; i++ {
//向管道中寫入資料
numChan <- i
fmt.Println("======> 這是主go程, 寫入資料:", i)
//time.Sleep(1 * time.Second)
}
time.Sleep(5 * time.Second)
}
4. 有緩衝區管道
package main
import (
"fmt"
"time"
)
func main() {
//numsChan := make(chan int, 10)
//1. 當緩衝寫滿的時候,寫阻塞,當被讀取後,再恢復寫入
//2. 當緩衝區讀取完畢,讀阻塞
//3. 如果管道沒有使用make分配空間,那麼管道預設是nil的,讀取、寫入都會阻塞
//4. 對於一個管道,讀與寫的次數,必須對等
var names chan string //預設是nil的
names = make(chan string, 10)
go func() {
fmt.Println("names:", <-names)
}()
names <- "hello" //由於names是nil的,寫操作會阻塞在這裡
time.Sleep(1 * time.Second)
numsChan1 := make(chan int, 10)
//寫
go func() {
for i := 0; i < 50; i++ {
numsChan1 <- i
fmt.Println("寫入資料:", i)
}
}()
//讀,當主程式被管道阻塞時,那麼程式將鎖死崩潰
//要求我們一定要讀寫次數保持一致
func() {
for i := 0; i < 60; i++ {
fmt.Println("主程式準備讀取資料.....")
data := <-numsChan1
fmt.Println("讀取資料:", data)
}
}()
for {
;
}
}
- 當管道的讀寫次數不一致的時候
- 如果阻塞在主go程,那麼程式會崩潰
- 如果阻塞在子go程,那麼會出現記憶體洩露
5. for range遍歷
package main
import "fmt"
func main() {
numsChan2 := make(chan int, 10)
//寫
go func() {
for i := 0; i < 50; i++ {
numsChan2 <- i
fmt.Println("寫入資料:", i)
}
fmt.Println("資料全部寫完畢,準備關閉管道!")
close(numsChan2)
}()
//遍歷管道時,只返回一個值
//for range是不知道管道是否已經寫完了,所以會一直在這裡等待
//在寫入端,將管道關閉,for range遍歷關閉的管道時,會退出
for v := range numsChan2 {
fmt.Println("讀取資料 :", v)
}
fmt.Println("OVER!")
}
本作品採用《CC 協議》,轉載必須註明作者和本文連結