回顧一下上一篇博客,主要是和大家分享了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語言的函數定義,以及一個比較重要的指針的概念,在后面的學習中,我們來更加深入地去體會。在實踐中去慢慢加深印象。當然上面的例子也希望大家能夠照着寫一下,運行着體會一下。有不理解的歡迎大家一塊溝通一起進步。
我是老貓,更多內容,歡迎大家搜索關注老貓的公眾號“程序員老貓”。