Go part 2 基礎語法

關鍵字、標識符

標識符:html

是用戶或系統定義的有意義單詞組合,或單詞與數字組合(具體意義有定義者決定)  python

標識符以字母下劃線開頭,大小寫敏感,好比:boy,  Boy,  _boy,  _(匿名變量,用來忽略結果)程序員

標識符命名規範:在習慣上,Go語言程序員推薦使用駝峯式命名,當名字有幾個單詞組成的時優先使用大小寫分隔,而不是優先用下劃線分隔。所以,在標準庫有QuoteRuneToASCII和parseRequestLine這樣的函數命名,可是通常不會用quote_rune_to_ASCII和parse_request_line這樣的命名。而像ASCII和HTML這樣的縮略詞則避免使用大小寫混合的寫法,它們可能被稱爲htmlEscape、HTMLEscape或escapeHTML,但不會是escapeHtml。數據庫

 

關鍵字:編程

是 Go 語言提供的有特殊含義的符號,也叫作「保留字」數組

系統保留關鍵字:安全

break default func interface select
case defer go map struct
chan else goto package switch
const fallthough if range type
continue for import return var

 

常量與變量

常量數據結構

常量使用 const 修飾,表示是隻讀的,不能修改多線程

const 只能修飾 boolean,number(int相關,浮點數,complex)和 string 類型閉包

語法:const identifier [type] = value(type 可省略)

優雅寫法:

const(
    name string = "skier"
    age  int = 10
    salary int = 5000 / 2
    // gender boolean = getGender()  // const不能從函數中獲取
)

常量由於在編譯期肯定,因此能夠用於數組聲明:

const size = 4
var arrayA [size]int

 

變量

聲明一個變量:var identifier [type]

// 聲明變量並賦值
var a int = 100

// 簡寫(自動推導類型)
a := 100

優雅寫法:

var (
    name string = "johny"
    age int = 10
)

默認值:

  • int        0
  • float     0.0,編譯默認推導爲 float64
  • bool     false
  • string   ""  
  • slice,函數,指針變量默認爲 nil  (slice 默認爲 nil,但打印輸出是 [],可以使用 == nil 進行判空)

 

做用域:從定義變量的代碼行開始,一直到直接所屬的大括號結束爲止(全局變量除外)

  • 在函數部聲明的變量叫作局部變量,生命週期僅限於函數內部
  • 在函數部聲明的變量叫作全局變量,生命週期做用於整個包,若是首字母大寫的話,則能夠被其它包導入

在編程中,變量在其實現了變量的功能後,做用範圍越小,所形成的問題可能性越小,每個變量表明一個狀態,有狀態的地方,狀態就會被修改,函數的局部變量只會影響一個函數的執行,但全局變量可能會影響全部代碼的執行狀態,所以限制變量的做用範圍對代碼的穩定性有很大的幫助

 

基本數據類型與操做符

數字類型:int8,  int16,  int32,  int64,  uint8,  uint16, uint32,  uint64

bool 類型:ture,  false  (bool 型沒法參與數值運算,也沒法與其餘類型進行轉換。)

浮點類型:float32,  float64

字符類型:byte

字符串類型:字符串實現基於 UTF-8 編碼

  ""  雙引號,定義行字符串

  ``  反引號,定義行字符串(在這種方式下,反引號間換行將被做爲字符串中的換行,可是全部的轉義字符均無效,文本將會原樣輸出)

    多行字符串通常用於內嵌源碼和內嵌數據等

 

類型轉換

格式:type(variable)

var a  int = 8
var b int32 = int32(a) 

浮點數轉換成 int 類型,精度會丟失

var c float32 = math.Pi
fmt.Println(int(c))

int32 轉換成 int16,數據會截斷

// 初始化一個32位整型值
var a int32 = 1047483647
// 輸出變量的十六進制形式和十進制值
fmt.Printf("int32: 0x%x %d\n", a, a)

// 將a變量數值轉換爲十六進制, 發生數值截斷
b := int16(a)
// 輸出變量的十六進制形式和十進制值
fmt.Printf("int16: 0x%x %d\n", b, b)

結果是:
int32: 0x3e6f54ff 1047483647
int16: 0x54ff 21759

 

字符串轉義符

轉移符 含  義
\r 回車符(返回行首)
\n 換行符(直接跳到下一行的同列位置)
\t 製表符
\' 單引號
\" 雙引號
\\ 反斜槓

 

操做符

數字操做符:+,  -,  *,  /,  %

比較運算符:>,  >=,  <,  <=,  ==,  !=

 

字符串操做

拼接:

var str1 string = "hello"
var str2 string = "world"
var str string = str1 + str2

// var str string = fmt.Sprintf("%s%s", str1, str2)
 
獲取長度:var length int = len(str)
 
切片:var substr string = str[:5]  (與python相似)

 

值類型與引用類型

值類型

本質上是原始類型,變量直接儲存值,內存一般在中分配,包括 int,  float,  bool,  string 以及數組和 struct(結構體)

對值類型進行操做,通常都會返回一個新建立的值,因此把這些值傳遞給函數時,其實傳遞的是一個值的副本

func main() {
	name:="張三"
	fmt.Println(modify(name))
	fmt.Println(name)
}

func modify(s string) string{
	s=s+s
	return s
}

//Output
張三張三
張三

以上是一個操做字符串的例子,經過打印的結果,能夠看到,原本 name 的值並無改變,也就是說,咱們傳遞的是一個副本,而且返回一個新建立的字符串

基本類型由於是值的拷貝,而且在對他進行操做的時候,生成的也是新建立的值,因此這些類型在多線程裏是安全的,咱們不用擔憂一個線程的修改影響了另一個線程的數據

 

引用類型

引用類型與值類型偏偏相反,它的修改能夠影響到任何引用到它的變量;變量存儲的是地址,這個地址存儲最終的值,一般在內存上分配,經過 GC 回收,包括 指針,select,map,chan 等

引用類型之因此能夠引用,是由於咱們建立引用類型的變量,實際上是一個標頭值,標頭值裏包含一個指針,指向底層的數據結構,當咱們在函數中傳遞引用類型時,其實傳遞的是這個標頭值的副本,它所指向的底層結構並無被複制傳遞,這也是引用類型傳遞高效的緣由。 

 

流程控制

if else 分支判斷

if condition1 {
    block1
} else if condition2 {
    block2   
} else {
    block3
}

 

switch case 語句

func main(){
	var variabel string = "a"
	switch variabel {
		case "a", "b":
			fmt.Println(variabel)
			// fallthrough  // 會執行下一個case的語句塊
		case "c":
			fmt.Println(variabel)
		default:
			fmt.Println("default output")
	}
}

case 後邊的值能夠寫多個,是 或 的關係

case 語句塊末尾若是加上 fallthrough,會接着執行下一個 case 的語句塊

 

for 循環

for i:=0; i<100 ; i++ {
    fmt.Println("hello, world~")
}

 

死循環(for)

func main(){
	for {
		fmt.Println("hello, world~")
		time.Sleep(time.Second)
	}
}

 

加判斷的 for 循環

func main(){
	var i int
	for i<100 {
		fmt.Println(i)
		i += 1
	}
}

 

for range 語句

可使用 for range 遍歷數組、切片、字符串、map 及通道(channel)。經過 for range 遍歷的返回值有必定的規律:

  • 數組、切片、字符串返回索引和值
  • map 返回鍵和值
  • 通道(channel)只返回通道內的值

遍歷數組:

var arrayA = [3]string{"hammer", "soldier", "mum"}
 
for index, value := range arrayA {
        fmt.Println(index, value)
}
 
運行結果:
0 hammer
1 soldier
2 mum

 

用匿名標識符忽略 index

var arrayA = [3]string{"hammer", "soldier", "mum"}
 
for _, value := range arrayA {
        fmt.Println(value)
}
 
運行結果:
hammer
soldier
mum

 

固然,for 循環中也可以支持:break,  continue 

 

goto 語句

能夠經過標籤進行代碼間的無條件跳轉,goto語句能夠快速跳出循環 或 實現一樣的邏輯 有必定的幫助:

快速跳出循環:

func main(){
    for i:=0; i<=100; i++{
        for j:=0; j<=100; j++{
            if j == 10 {
                // 直接跳轉到標籤
                goto breakHere
            }
        }
    }

    breakHere:
        fmt.Println("hello world~")
}

運行結果:
hello world~

  

使用 goto 集中處理錯誤:

err := firstCheckError()
if err != nil {
    goto onExit
}

err = secondCheckError()
if err != nil {
    goto onExit
}

fmt.Println("done")
return

onExit:
    fmt.Println(err)
    exitProcess()

 

函數

Go 語言支持普通函數、匿名函數和閉包

普通函數聲明:func 函數名(參數列表)  (返回值列表)  {函數體}

不支持重載,一個源文件內不能有兩個相同名稱的函數

函數是一等公民,也是一種類型,能夠賦值給變量

函數的傳參方式

  • 值傳遞        (基本數據類型都是值傳遞)
  • 引用傳遞          (指針,slice,map,chan,interface)

注意:不管是值傳遞仍是引用傳遞,傳遞給函數的都是變量的副本,不過,值傳遞是對值的拷貝,引用傳遞是地址的拷貝,通常來講,地址拷貝更爲高效,而值拷貝取決於拷貝對象的大小,對象越大,則性能越低

 

返回值命名

返回值不須要定義,直接使用(命名的返回值變量的默認值爲類型的默認值,即數值爲 0,字符串爲 "",布爾爲 false、指針爲 nil)

func calc(a int, b int) (c int) {
	c = a + b
	return c
}

 

可變長參數

可變參數變量是一個包含全部參數的切片

func calc(a int, b int, arg... int) {
	fmt.Println(arg[0])
}

 

defer 的用途

  1. 延遲調用是在 defer 所在函數結束時進行,函數結束能夠是正常返回時,也能夠是發生宕機
  2. 多個 defer 語句,按先進後出(棧)的順序執行
  3. defer 語句中的變量,在 defer 聲明時就決定了
  4. defer 語句正好是在函數退出時執行的語句,因此使用 defer 能很是方便地處理資源釋放問題

關閉文件句柄

注意:不能將這一句代碼放在第3行空行處,一旦文件打開錯誤,f將爲空,在延遲語句觸發時,將觸發宕機錯誤

func read(){
    file err := open(filename)
if err != nil{ return } defer file.Close() }

 

鎖資源的釋放

func lock(){
    mc.Lock()
    defer mc.Unlock()
}

 

數據庫鏈接的釋放

func connect(){
    conn := openDatabase()
    defer conn.Close()
}

 

調用函數

函數在定義後,能夠經過調用的方式,讓當前代碼跳轉到被調用的函數中進行執行。調用前的函數局部變量都會被保存起來不會丟失;被調用的函數結束後,恢復到被調用函數的下一行繼續執行代碼,以前的局部變量也能繼續訪問

 

遞歸函數

一個函數在內部調用本身,就叫作遞歸,下面來舉兩個遞歸函數的Demo

遞歸求階乘

func calc(n int) int {
	if n <= 1{
		return 1
	}

	return calc(n-1) * n
}


func main(){
	result := calc(5)
	fmt.Println(result)
}

運行結果:
120

 

斐波拉契數

func fab(n int) int {
	if n<=1{
		return 1
	}

	return fab(n-1) + fab(n-2)

}

func main(){
	var n int = 6
	var fabCount int
	for i:=0; i<=n; i++{
		fabCount += fab(i)
	}
	
	fmt.Println(fabCount)
}

運行結果:
33

 

匿名函數

匿名函數沒有函數名,只有函數體,能夠直接被當作一種類型賦值給函數類型的變量,匿名函數也每每以變量的方式被傳遞

匿名函數常常被用於實現回調函數、閉包

定義一個匿名函數

func(str string){
    fmt.Println("hello", str)
}("world")

運行結果:
hello world

 

也能夠將匿名函數賦值給變量

f := func(str string){
    fmt.Println("hello", str)
}

f("world")

 

匿名函數當作參數

func visit(sliceA []int, f func(int)){
    for _, value := range sliceA {
        f(value)
    }
}

func main(){
    var sliceA []int = []int{1,2,3,4,5}

    f := func(a int){
        fmt.Print(a)
    }
    visit(sliceA, f)
}

運行結果:
12345

 

使用匿名函數實現操做封裝

func main(){
    var mapA map[string]func()
    mapA = map[string]func(){
        "fire": func(){
            fmt.Println("chicken fire")
        },
        "run": func(){
            fmt.Println("soldier run")
        },
        "fly": func(){
            fmt.Println("angel fly")
        },
    }
    // fmt.Println(mapA)
    // 接收命令行參數,key,默認值,幫助
    var skill *string = flag.String("skill", "", "skill type")
    flag.Parse()

    f, err := mapA[*skill]
    if err == true {
        f()
    } else {
        fmt.Println("skill not fount")
    }
}

運行效果:
$  go run main.go --skill=fly
angel fly
$  go run main.go --skill=run
soldier run
View Code

 

閉包(Closure)

閉包是引用了自由變量的函數,被引用的自由變量和函數一同存在,即便已經離開了自由變量的環境 也不會被釋放或者刪除,在閉包中能夠繼續使用這個自由變量(閉包(Closure)在某些編程語言中也被稱爲 Lambda 表達式)

簡單的說:

閉包 = 函數 + 引用環境

同一個函數與不一樣的引用環境組合,能夠造成不一樣的實例,如圖:

實現一個簡單的閉包

func closureFunc(str string) func(){
    wapper := func(){
        fmt.Println("hello", str)
    }
    return wapper
}


func main(){
    f := closureFunc("world~")
    // 調用閉包函數
    f()
}

運行結果:
hello world~

 

累加器的實現(閉包的記憶效應)

func accumulate(num int) func() int {
    wapper := func() int {
        num += 1
        return num
    }
    return wapper
}


func main(){
    accumulator := accumulate(10)
    ret1 := accumulator()
    fmt.Println(ret1)
    ret2 := accumulator()
    fmt.Println(ret2)
}

運行結果:
11
12

 

go 基本程序結構

// 任何一個代碼源文件隸屬於一個包
package main

//import 關鍵字,同一個包內的函數能夠直接調用,不一樣包中的函數經過 包名.函數名 的方式調用
import (
    "fmt"
    "/go_dev/test"
)

// 初始化函數
func init(){
    fmt.Println("執行初始化操做")
}

// main 程序入口
func main(){
    fmt.Println("hello, world~")
}

init 函數

每一個源文件均可以包含一個 init 函數,會自動被編譯器執行

 

包訪問控制規則

    1.  包內函數名首字母大寫表示此函數/變量是可導出的
    2. 小寫表示此函數/變量是私有的,在包外部不能訪問

 

程序執行順序(棧)  *

  1. 導入其它包,初始化 被導入包 內的全局變量,執行 被導入包 內全部源文件的 init 函數
  2. 初始化 main 包內全局變量
  3. 調用 main 包 init 函數
  4. 執行 main 函數

 

練習1:寫一個函數,對於一個整數n,求出全部兩兩相加等於n的組合,好比 n=5

package main

import (
	"fmt"
)

func calc(n int){
	for i:=0; i<=n; i++ {
		num := n - i
		fmt.Printf("%d+%d=%d\n", i, num, n)
	}

}

func main(){
	calc(5)
}

結果:
0+5=5
1+4=5
2+3=5
3+2=5
4+1=5
5+0=5

 

練習2:一個程序包含 add 和 main 兩個包,add 包中有兩個變量 Name 和 age,在 main 中訪問 Name 和 age 變量,打印輸出

首先在 go_dev 下新建一個目錄 example(go_dev 目錄在環境變量 GOPATH 下 src 目錄,go編譯器根據系統路徑會找到 go_dev 目錄下的包)

add包下寫 add.go

package add

var Name string = "skier"
var age int = 19

main包下寫 main.go

package main

import (
        "go_dev/example/add"
        // a "go_dev/example/add"  // 包別名的應用
        "fmt"
)

func main(){
	fmt.Println(add.Name)
	fmt.Println(add.age)  // 不能訪問
}

 

做業

1.判斷101~200之間有多少個素數,並輸出打印

package main

import (
    "fmt"
)

func main(){
    for i:=101; i<=200; i++{
        var flag bool = true
        for j:=2; j<i; j++{
            if (i % j == 0) {
                flag = false
            }
        }
        if (flag == true){
            fmt.Println("素數i:", i)
        }
    }
}
第一題

2.打印出101~999中全部的水仙花數,所謂水仙花數是指一個三位數,其各位數字的立方和等於該數自己,例如153是一個水仙花數,1*1*1 + 5*5*5 + 3*3*3 = 153

package main
  
import (
"fmt"
)

func narcissisticNum(num int) bool {
    var g int = num % 100 % 10 
    var s int = num % 100 / 10
    var b int = num / 100
    //fmt.Println(g, s, b)
    var cube int = g*g*g +  s*s*s + b*b*b
    return num == cube
}

func main(){
    for i:=100; i<=999; i++{
        result := narcissisticNum(i)
        if result == true {
            fmt.Println("水仙花數是:", i)
        }
    }
}
第二題

3.對於一個數n,求n的階乘之和,即:1! + 2! + ... + n!

package main

import (
    "fmt"
)

func summation(n int) int {
    var result int
    for i:=1; i<=n; i++ {
        var tmpe int = 1
        for j:=1; j<=i; j++{
            tmpe *= j
        }
        result += tmpe
    }
    return result
}

func main(){
    var num int = 5
    var result int = summation(num)
    fmt.Println("sum result:", result)
}
第三題

4.在終端打印 9*9 乘法表

package main

import(
    "fmt"
)

func multiplication(){
    for column:=1; column<=9; column++{
        for row:=1; row<=column; row++{
            fmt.Printf("%d*%d=%d\t", column, row, column*row)
        }
        fmt.Println()
    }
}

func main(){
    multiplication()
}
第四題

5.一個數若是剛好等於它的因子之和,這個數就稱之爲「完數」,例如:1+2+3=6,在終端輸出1000之內的全部完數

package main

import(
    "fmt"
)

func showPerfect(n int){
    for i:=1; i<n; i++{
        var sum int

        for j:=1; j<i; j++{
            if i % j == 0{
                sum += j
            }
        }
        
        if sum == i{
            fmt.Printf("完數:%d \n", i)
        }
    }
}

func main(){
    const num int = 1000
    showPerfect(num)
}
第五題 

6.輸入一個字符串,判斷其是否爲「迴文」,迴文字符串是指從左到右讀,與從右到左讀是徹底相同的字符串

package main

import (
    "fmt"
)

func isReverse(str string) bool { 
    // 轉爲字符
    char := []rune(str)

    var length int = len(char)

    for i:=0; i<=length / 2 - 1; i++{
        if char[i] != char[length-1-i]{
            return false    
        }
    } 

    return true
}

func main(){
    var input string
    fmt.Scanf("%s", &input)
    result := isReverse(input)
    fmt.Println("result:", result)
}
第六題

7.輸出一行字符串,分別統計其中英文字母,空格,數字和其它符號的個數

package main

import (
    "fmt"
    "bufio"
    "os"
)

func count(str string) (wordCount, spaceCount, numCount, otherCount int) {
    chars := []rune(str)
    for _, value := range chars{
        switch {
        case value >= 'a' && value <= 'z':
            wordCount += 1
        case value >= 'A' && value <= 'Z':
            wordCount += 1
        case value == ' ':
            spaceCount += 1
        case value >= '0' && value <= '9':
            numCount += 1
        default:
            otherCount += 1  
        }
    }
    return
}


func main(){
    reader := bufio.NewReader(os.Stdin)
    // 讀取一行的內容
    result, _, error := reader.ReadLine()

    if error == nil{
        wordCount, spaceCount, numCount, otherCount := count(string(result))
        fmt.Printf("wordCount:%d\nspaceCount:%d\nnumCount:%d\notherCount:%d\n", wordCount, spaceCount, numCount, otherCount)
    }
}
第七題

8.計算兩個大數相加的和,這兩個大數會超過 int64 的表示範圍

相關文章
相關標籤/搜索