跟著老貓來搞GO,基礎進階

程式設計師老貓發表於2021-11-08

回顧一下上一篇部落格,主要是和大家分享了GO語言的基礎語法,其中包含變數定義,基本型別,條件語句,迴圈語句。那本篇呢就開始和大家同步一下GO語言基礎的進階。

函式的定義

上次其實在很多的DEMO中已經寫出來一些函式了,但是沒有講清楚其函式定義。接下來我們同樣地要舉例說明一下,直接看程式碼。

func calculate(a,b int, op string) int {
	switch op {
	case "+":
		return a + b
	case "-":
		return a - b
	case "*":
		return a * b
	case "/":
		return a / b
	default:
		panic("unsupported op")
	}
}

以上是一個比較簡單的計算兩個整數加減乘除運算的一個函式,首先我們可以看到的是函式的定義其實也是遵循著變數的定義方式,我們們先定義函式的名稱,然後才是函式的返回值。當然函式中的引數定義也是如此。

除此以外,其實GO語言相對於其他語言來說有一個比較騷的操作,就是他可以存在多個返回值。例如下面我們們寫一個除法的例子,就是大家小學就學過的除不盡的時候存在餘數的情況。下面我們來看一個函式。

func div(a int, b int) (int,int){
	return a / b, a % b
}

大家看到上面這個返回值有什麼感想,其實這最終的兩個返回值是沒有體現任何業務意義的,我們們無法區分最終返回的結果到底是幹什麼用的。當然GO語言其實也發現了這個弊端,所以呢,我們的返回值的名稱也是可以定義的,具體如下,我們命名除法得到的商為q,餘數為r,那麼我們改進之後就得到如下:

func div(a int, b int) (q ,r int){
	return a / b, a % b
}

如果這樣的話我們main呼叫得到結果就可以這麼獲取

func main() {
	fmt.Println(div(4,3))
	q,r := div(5,6)
	fmt.Println(q,r)
}

那麼此時問題又來了,如果我們只要其中的一個商,餘數不要,這又是如何寫呢,因為我們都知道go的語法中,定義出來的變數後面都得用到才行,否則的話會報編譯錯誤,那其實我們直接用"_"來替換即可。具體程式碼塊如下

func main() {
	q,_ := div(5,6)
	fmt.Println(q)
}

這樣話我們們就可以只獲取其中一個值即可。

其實GO語言函數語言程式設計程式設計的語言,函式是非常重要的,所以我們們再高階一點的函式的寫法是可以將函式本身作為一個引數傳入函式的,說的比較繞,其實本身開始去接受的時候也是有點難理解,老貓在此先把例子寫一下,大家試著去理解一下,當然後面的話老貓會有更詳細地對函數語言程式設計的介紹,具體的例子如下

func apply(op func(int,int) int,a,b int) int{
	fmt.Printf("Calling %s with %d,%d\n",
		runtime.FuncForPC(reflect.ValueOf(op).Pointer()).Name(),a,b)
	return op(a,b)
}

我們對GO語言的函式來做一個簡單的總結:

  • 返回值型別寫在後面
  • 可以返回多個值
  • 函式可以作為引數
  • 沒有預設引數,可變引數,過載等等

指標

相關定義

關注老貓的應該大多數是軟體專業的同學,不曉得大家有沒有熟悉過C語言,C語言中其實也有指標,C語言的指標相對還是比較難的。其實GO語言也有指標,相對而言比較簡單,因為GO語言的指標不能運算。

指標說白了就是一個指標指向了一個值的記憶體地址。

GO語言中的去地址符為&,放到變數以前的話就會返回相應的變數記憶體地址。看個例子如下:

package main

import "fmt"
func main() {
   var a int = 10  
   fmt.Printf("變數的地址: %x\n", &a  )
}

這個呢,其實就是GO語言的地址獲取方式,那我們如何去訪問它呢?那麼我們的指標就登場了,如下程式碼示例

var var_name *var-type

var-type為指標型別,var_name為指標變數名稱,*用於指定變數是作為一個指標。那麼我們再來看下面的例子:

var ip *int        /* 指向整型*/
var fp *float32    /* 指向浮點型 */

那麼以上其實就是定義了兩個指標,分別指向int以及float32。

使用指標

package main

import "fmt"

func main() {
   var a int= 20   /* 宣告實際變數 */
   var ip *int        /* 宣告指標變數 */

   ip = &a  /* 指標變數的儲存地址 */

   fmt.Printf("a 變數的地址是: %x\n", &a  )

   /* 指標變數的儲存地址 */
   fmt.Printf("ip 變數儲存的指標地址: %x\n", ip )

   /* 使用指標訪問值 */
   fmt.Printf("*ip 變數的值: %d\n", *ip )
}

那麼我們得到的結果為

a 變數的地址是: 20818a220
ip 變數儲存的指標地址: 20818a220
*ip 變數的值: 20

GO語言其實也會存在空指標, 當一個指標被定義後沒有分配到任何變數時,它的值為 nil。 nil 指標也稱為空指標。 nil在概念上和其它語言的null、None、nil、NULL一樣,都指代零值或空值。 一個指標變數通常縮寫為 ptr。

如下例子

package main

import "fmt"

func main() {
   var  ptr *int
   fmt.Printf("ptr 的值為 : %x\n", ptr  )
}

結果

ptr 的值為 : 0

那麼我們一般對空指標的判斷即為

if(ptr != nil)     /* ptr 不是空指標 */
if(ptr == nil)    /* ptr 是空指標 */

以上就是老貓帶大家入一下指標的門,當然也是老貓的入門。後續,我們會在實際的例子中來慢慢體會指標的用法。

值傳遞以及引用傳遞

那麼什麼是值傳遞,什麼是引用傳遞?我們簡單地來看一段C++ 的程式碼,具體如下:

void pass_by_val(int a) {
   a++;
}
void pass_by_ref(int &a){
  a++;
}
int main(){
   int a = 3;
   pass_by_val(a);
   printf("After pass_by_val:%d\n",a);
   pass_by_ref(a);
   printf("After pass_by_ref:%d\n",a);
}

上面兩個方法,其中一個是值傳遞一個是引用傳遞,那麼最終輸出的結果是多少呢?大家可以先思考一下。其實答案為上面是3下面是4,那麼為什麼呢?

我們來看第一種,第一種的話是值傳遞,值傳遞的方式其實在上面的例子中可以這麼理解,該函式是將main中的值拷貝一份放到了函式中,雖然在函式中加了1,但是外層原始的那個值還是3,所以最終輸出的也還是3。

我們再來看另外一種,引用傳遞,從入參來看的話,其實裡面的a以及外面的a所引用的都是同一個地址,所以當內部函式對a進行自增的時候,外面的函式a的值就發生了變化,變成了4。

那麼我們的GO是值傳遞還是引用傳遞,其實GO語言只有值傳遞。

大家可能有點懵了,其實很多時候,大家不用太過糾結,因為在實際的用法中我們往往通過函式return的值就能解決相關問題。

寫在最後

上面呢,其實老貓和大家分享了GO語言的函式定義,以及一個比較重要的指標的概念,在後面的學習中,我們來更加深入地去體會。在實踐中去慢慢加深印象。當然上面的例子也希望大家能夠照著寫一下,執行著體會一下。有不理解的歡迎大家一塊溝通一起進步。
我是老貓,更多內容,歡迎大家搜尋關注老貓的公眾號“程式設計師老貓”。

相關文章