Golang:從fmt.Scanf函式想到的
本文由碼農網 – Sandbox Wang原創,轉載請看清文末的轉載要求,歡迎參與我們的付費投稿計劃!
工作中使用go有一段時間了,隨著寫的程式碼數量的增長,越來越被go的魅力所折服,同時也對相關的社群有了更多的關注。早上在go語言技術交流群裡,有網友問了一個很有意思的問題,一段很簡單的程式碼,但是卻總得不到期望的結果。
還有什麼樣的東西更能引起程式猿的興奮呢?下面是程式碼。
func testScan() { var test [10]byte var test2 = test[0:] n, err := fmt.Scanf("%s", &test2) fmt.Println(n, err) fmt.Printf("%s, %s\n", test, test2) }
執行之後,輸入hello,輸出結果如下:
1 <nil> ,hello
按理說,test2是slice型別,它和test這個陣列共用資料儲存區,也就是說,test2被裝入了“hello”之後,test的內容也應該是“hello”才對,但是很遺憾的是,並不是。
我們知道,當slice B是從slice A初始化得來的話,A和B儲存同一份資料,但是當我們向B裡面新增更多的資料(新增之後的長度超過A原有長度)之後,B會重新開闢一個新的儲存區域來存放B原來的資料和新新增的資料。也就是說,在這個時候,A和B才有了各自獨立的儲存區域。
在我們的問題中,輸入的是hello,長度僅為5,並沒有超過10,那麼想必也不會引起test2重新開闢儲存區域吧。
百思不得其解,無奈開啟fmt/scan.go的原始碼,找到fmt.Scanf的實現:
func Scanf(format string, a ...interface{}) (n int, err error) { return Fscanf(os.Stdin, format, a...) }
實現很簡單,僅僅是呼叫了更通用的Fscanf,Fscanf的實現如下:
func Fscanf(r io.Reader, format string, a ...interface{}) (n int, err error) { s, old := newScanState(r, false, false) n, err = s.doScanf(format, a) s.free(old) return }
其中newScanState的呼叫返回了一個新的ScanState的實現,通過它的doScanf方法來完成實際的變數的解析。doScanf方法較為複雜,但是總的意思只有一個,就是逐個地對每個格式化控制符對應的變數進行解析:
func (s *ss) doScanf(format string, a []interface{}) (numProcessed int, err error) { defer errorHandler(&err) end := len(format) - 1 //省略 for i := 0; i <= end; { //省略 s.scanOne(c, arg) numProcessed++ s.argLimit = s.limit } return }
其中可以看到最關鍵的解析變數的任務是通過ScanState.scanOne函式來實現的,這裡的變數c是rune型別,我們的變數就是從它解析出來的。arg是interface{}型別的,代表我們傳入的*[]byte型別的變數,即&test2。
再找到ScanState.scanOne函式:
func (s *ss) scanOne(verb rune, arg interface{}) { s.buf = s.buf[:0] var err error // If the parameter has its own Scan method, use that. if v, ok := arg.(Scanner); ok { err = v.Scan(s, verb) if err != nil { if err == io.EOF { err = io.ErrUnexpectedEOF } s.error(err) } return } switch v := arg.(type) { //省略 case *string: *v = s.convertString(verb) case *[]byte: // We scan to string and convert so we get a copy of the data. // If we scanned to bytes, the slice would point at the buffer. *v = []byte(s.convertString(verb)) //省略 }
我們可以看到,scanOne方法的邏輯非常清晰,首先判斷arg物件是否具有Scanner介面的Scan方法,如果有的話,直接呼叫它。如果沒有的話,需要對它的型別進行switch遍歷判斷,如果型別是*[]byte的話,我們驚訝地看到了這樣的賦值:
*v = []byte(s.convertString(verb))
也就是說,我們傳入的型別為[]byte指標的變數被重新賦了一個新的[]byte值。我們想象中的io.Copy等等並沒有蹤影。
看到這裡,問題的原因已經非常清楚了。對於那些寫過很多遍C/C++版本的scanf的人,是不是很無奈呢?其實,我倒是對Go的這種實現並沒有什麼意見,如果我們的本意是想讀入字串的話,把上面的程式碼改成string的話,就沒有絲毫的問題了:
var test2 string n, err := fmt.Scanf("%s", &test2)
另外,通過這件事,我們再次得到提醒,slice物件雖然很像陣列,但是卻並不是陣列,而是類似下面的一個資料結構:
data *Elem |
len int |
cap int |
所以,當我們對slice物件進行再賦值或函式傳參的時候,上面的結構被完全複製了一份,但是資料指標域仍指向同一個資料儲存區域,即共享資料儲存。例如,下面的程式碼:
func testBasic() { a := make([]int, 4) b := a a[0] = 1 fmt.Printf("%p,%p,%v,%v\n", &a, &b, a, b) }
列印結果為:
0xc082004740,0xc082004780,[1 0 0 0],[1 0 0 0]
同時,就像在上面的問題中,當我們把一個slice指標作為引數傳入別的函式的時候,如果它所指向的slice被賦以一個新的slice的話,它原來所指向的值是不會發生變化的。簡單來說,就是這個指標本來指向A,後來被指向了新的B,那A當然不受影響了。
本文連結:http://www.codeceo.com/article/golang-fmtscanf.html
本文作者:碼農網 – Sandbox Wang
[ 原創作品,轉載必須在正文中標註並保留原文連結和作者等資訊。]
相關文章
- Golang init() 函式Golang函式
- Golang匿名函式Golang函式
- Golang 常用的 strings 函式Golang函式
- Golang 常用函式記錄Golang函式
- [譯] part 14: golang 函式Golang函式
- golang函式使用基礎Golang函式
- 從函式式的角度反思函式
- golang中的init初始化函式Golang函式
- Golang字串函式認識(二)Golang字串函式
- Golang字串函式認識(一)Golang字串函式
- Golang實現PHP常用函式GolangPHP函式
- 從找不到檔案想到的
- 我的 golang 學習筆記系列二:golang的函式運用Golang筆記函式
- Golang記憶體分配內建函式之new函式Golang記憶體函式
- Golang建立建構函式的方法詳解Golang函式
- [譯] part 33: golang 頭等函式Golang函式
- Golang 基礎之函式使用 (三)Golang函式
- Golang之不可重入函式實現Golang函式
- Golang學習筆記-1.6 函式Golang筆記函式
- Golang 基礎之函式使用 (二)Golang函式
- Golang 基礎之函式使用 (一)Golang函式
- golang http.Redirect()函式容易誤解的地方GolangHTTP函式
- Golang時間函式及測試函式執行時間案例Golang函式
- 從函式式元件引發的效能思考函式元件
- golang count 單字元 字串 統計函式Golang字元字串函式
- 從眾理論想到的分析應用
- 從一次故障解決想到的
- golang拾遺:內建函式len的小知識Golang函式
- 從settTimeout到匿名函式、箭頭函式之() => {}函式
- [靈性程式設計]函式委託,自動事件,函式觀察者(golang)程式設計函式事件Golang
- 小白學習Golang(六)Go語言函式Golang函式
- 在Golang中使用泛型reduce函式 - gosamplesGolang泛型函式
- Go 之基礎速學 (五) golang 裡函式以及函式之間引數的傳遞Golang函式
- Golang閉包案例分析與普通函式對比Golang函式
- golang學習之路之函式可變引數Golang函式
- Golang中函式傳參存在引用傳遞嗎?Golang函式
- 從語句繁簡和效能優化想到的......優化
- 從光大證券的軟體設計缺陷想到的。