關(guān)于go語言的幾個陷阱,以及我們應(yīng)該注意的東西
- 閉包
所謂閉包就是指一個函數(shù)中的函數(shù),并且這個函數(shù)可以調(diào)用外部的變量并且無論使用多少次,
都可以一直擁有這個變量不回收,那么這個變量可以稱為閉包變量。
- 循環(huán)體變量
package main
import (
"fmt"
"time"
)
func main(){
tt()
}
func tt() {
for i := 0; i < 10; i++ {
go func() {
time.Sleep(1e3)
fmt.Println(i)
}()
}
time.Sleep(1e9)
}
這個函數(shù)執(zhí)行的時候 tt中打印出來的是10
原因也是很簡單,因?yàn)間o在初始化的時候先初始化參數(shù)量,全局先初始參數(shù)再看函數(shù),在函數(shù)內(nèi)部先初始參數(shù)再進(jìn)行運(yùn)算,所以 就造成在for執(zhí)行完后 這里的i是同樣的i
以為初始化的參數(shù) i一直會變,但是都是這個變量本身,又因?yàn)閒or循環(huán)比內(nèi)部的函數(shù)速度快很多,導(dǎo)致,當(dāng)for循環(huán)進(jìn)行完了,這些函數(shù)還沒正式開始運(yùn)行,然后i就取最終值 10了
如果你想學(xué)習(xí)算法歡迎添加微信群,群內(nèi)禁止廣告,我們只分享知識~

如果二維碼失效 可以添加我的微信 MMY5du 我拉你進(jìn)群。
- return 和 defer 的執(zhí)行順序
在go里
package main
import "fmt"
func tt() int {
var i = 0
defer func() {
fmt.Println(i)
i++
fmt.Println(i)
}()
return i
}
func main() {
tt()
}
在這個函數(shù)中 執(zhí)行順序是這樣的 首先先初始化 i = 0
然后 defer無法初始化(參數(shù)變量)因?yàn)樗鼪]有
然后到了return 然后 直接執(zhí)行了后面的內(nèi)容 沒錯 什么都沒有就是i而已
然后 開始了return 直接將值返回了,這時候它沒有結(jié)束 因?yàn)橛衐efer
所以開始執(zhí)行了defer,然后defer中i是幾?嗯 是0 因?yàn)檫@個時候i是0了
然后 打印了0 i再次++ i等于1了,然而并沒有什么機(jī)會去用這個i了,因?yàn)?br>
已經(jīng)return過了,所以這個i就被收回了,加入return后面是一個閉包,那么這個i
就有用了,它就不會被收回。
然后 這個時候函數(shù)就結(jié)束了。
看一下 這幾種特殊情況
// return 1 "defer tt1 0"
func tt1() int {
var i = 0
defer fmt.Println("defer tt1", i)
i++
return i
}
// return 1 "defer tt1 0"
func tt2() int {
var i = 0
defer func(i int) {// 參數(shù)復(fù)制,值的復(fù)制。
fmt.Println("defer tt2:", i)
}(i)
i++
return i
}
// 1 1
func tt3()int{
var i =0
defer func() {
fmt.Println("defer tt3",i)
i++
}()
i++
return i
}
//1 2
//2
func tt4() func() int {
var i = 0
defer func() {
fmt.Println("defer tt4:",i)
i++
}()
i++
return func() int {
return i// 引用變量。
}
}
--------
package main
import "fmt"
// 0 13
func tt5() (num int) {
defer func() {
fmt.Println("dd", num)
num++ //這里的num的作用于屬于上層的函數(shù)體
}()
return 12// 返回值是函數(shù)運(yùn)行最后的num值,很明顯是defer中的num值。然后返回1
// 這種形式的返回值,因?yàn)閞eturn 后面什么都沒,所以它就會查找這個函數(shù)域內(nèi)的最后值。因?yàn)楹瘮?shù)的運(yùn)行是
//運(yùn)行return后面的的內(nèi)容 + defer + 隱藏的os.Exit()
普通模式下是 運(yùn)行return后面的內(nèi)容 +返回return后面的內(nèi)容 +運(yùn)行defer + os.Exit()
}
func main(){
fmt.Println(tt5())
}
func main() {
fmt.Println(tt5())
fmt.Println(tt6())
}
//dd 0 dd1 2 return 1
func tt6() (num int) {
defer func(num int) {//// 使用這種形式 defer的函數(shù)內(nèi)部的值已經(jīng)是num的一個拷貝了,所以它里面怎么改變都不影響外部的return
fmt.Println("dd", num)
num++// 這里的num屬于本層函數(shù)的作用域,所以它無法改變外層函數(shù)的數(shù)值。
num++
fmt.Println("dd1", num)
}(num)
num++
return
}
- 變量的調(diào)用和直接改變參數(shù)本身
func tt(){
var i
dd(i)
fmt.Println(i)
}
func dd(i int){
i++
fmt.Println(i)
}
// 1 0
原因就是dd(i)這個是代表了 賦值,也就是將var的值賦予了dd的形參
可以看做是 d = i
dd(d) 這個叫做賦值 然后值的拷貝或者是指針的傳入以及指針的獲取實(shí)際值是這個地方的問題
然后還有一種是這樣的
func tt(){
var i = 0
{
i = 12
}
fmt.Println(i)
}
我之前因?yàn)楦x值搞混所以我總是是用
指針來更更改i其實(shí)是錯誤的理解,因?yàn)檫@個地方的i = 12 壓根就沒有賦值
這一種說法它不過是更改自己的值而已,就像上面的那個函數(shù)即使使用指針,
那么更改指針的實(shí)際值的時候也是這么干的,所以i = 12 只是這個參數(shù)的在
調(diào)整自己的值罷了,它是改變的自己,這里就不牽涉到 是值的拷貝還是引用的拷貝了
因?yàn)樗鼔焊鶝]有拷貝,僅僅是改變自己罷了。
- 值的方法和指針的方法
首先 指針的方法和值的方法可以互相調(diào)用,因?yàn)間o會自動幫你
比如指針的方法g()你使用了值來調(diào)用那么go會幫你自動取地址相對的如果是
值的方法 go自動幫你取 *
第二個地方
首先 值上面可以有方法 指針上面也是有方法,我們談的是 關(guān)于對象的方法這點(diǎn)先闡述
因?yàn)槌藄truct(對象)其它類型除了 指針和nil都可以有自己的方法
其它類型不討論
就是指針的方法的時候,那么go會自動幫你取這個對象的,因?yàn)橹羔槢]辦法取得對象
里面的value值,只能去得到,但是go幫你取了,所以你可以使用看似
指針直接去值。
- 關(guān)于實(shí)現(xiàn)接口
這個地方go很嚴(yán)格,首先就是接口類型的變量不允許取指針,本來它就是引用類型了(初始化是nil)nil取不到method。
雖然slice這種也是引用類型但是go允許你取它的指針,但是接口類型不允許?。ㄈ×艘矝]有意義)
而且對于實(shí)現(xiàn)一個接口來說如果你是指針類型實(shí)現(xiàn)的接口,那么將變量傳遞給指針類型時
也必須是指針類型的變量,值同樣,不允許自動取&或者*
舉個例子
type a interface{
get()
}
type b struct {
c value
}
func(b1 *b)get(){
fmt.Print(b1.c)// go幫你自動取了 *b1這個地方不變,go幫你自動取對象的值,在這個地方也可以。
}
func ddc(a1 a){
a1.get()
}
func main(){
b1 := new(b)
var b2 b
ddc(b1)// 正確
ddc(b2)// 錯誤?
}
- 關(guān)于slice
slice賦值的時候可以不用指定類型
type t int
slice := make([]t,10)
slice = [][]int{
{1}, //不需要使用 t{1}
{2},
{3},
}
如果slice里面還是slice或者是map等這種引用類型的話是這么處理的
slice := make([][]int,10)
slice[0] = make([]int,4)
// 或者
slice[1] = []int{
1,2,3,}
因?yàn)?引用類型不初始化的話 本身就是nil 所以會panic
- 關(guān)于變量的初始化
關(guān)于這個地方我也出錯過
func dd(t *int){
fmt.Println(*t)
}
func main(){
var t *int
dd(t)
}
這樣就會出錯,因?yàn)?所有的變量都會初始化(go沒有聲明 go會自動初始化)
但是t是個指針類型,它的初始化就是nil,所以*nil是錯誤的,正確的方法是
func dd(t *int){
fmt.Println(*t)
}
func main(){
var t int
dd(&t)
}
或者優(yōu)雅一點(diǎn)
func dd(t *int){
fmt.Println(*t)
}
func main(){
t := new(int)
dd(t)
}
- slice 關(guān)于他的len和cap
不要超過它的len來查找數(shù)據(jù)。(而不是cap)只要是超過了len就會報錯,雖然沒有超過cap
但是它的out of range 錯誤是根據(jù)len來定的。
- 不要獲取map的值的地址
t := make(map[string]string)
t["12"] = 12
fm.println(&t["12"])
因?yàn)閙ap是動態(tài)的,所以它的value的值 的地址不是固定的所以go不允許取得
它的地址。
但是 slice可以。
b := make([]int,12)
b[1] = 12
fmt.Println(&b[1])//0xc00001e128 slice 可以
- recover的使用只能在 defer中使用(其它地方調(diào)用無效果)
func tt(){
defer func(){
if t := recover;t {
fmt.Print(t)
}
}
dd()//dd里有panic
}
- 關(guān)于接口類型的斷言
- 接口實(shí)例.(接口類型)
- 接口實(shí)例.(實(shí)際類型)
但是這兩個的前面 無一例外都需要傳入實(shí)際的類型也就是變成了
- 實(shí)際類型的實(shí)例.(接口類型)
- 實(shí)際類型的實(shí)例.(實(shí)例類型)
舉個例子
type a struct {
value string
}
type b interface {
get()
}
type c interface{
post()
}
func tt(b1 b){
// 第一種情況
if v,ok := b1.(c);ok {// 這個實(shí)例 相當(dāng)于實(shí)現(xiàn)了這兩個interface
fmt.println(v.post())
fmt.Println(v.get())
}
// 第二種情況
if v,ok := b1.(a);ok {
fmt.Println(v.get())
}
}
再看一個實(shí)際運(yùn)用上的例子:
type a struct {
value string
}
type ber interface {
get()
}
type cer interface {
post()
}
func (a1 a) get() {
fmt.Println(a1.value)
}
func (a1 a) post() {
fmt.Println(a1.value + "p")
}
func t(b1 ber){
// 這個內(nèi)部的cer必不可少。
type cer interface {// 這就是為了驗(yàn)證 已經(jīng)實(shí)現(xiàn)了ber的變量是否也實(shí)現(xiàn)了cer
post()
}
if v, ok := b1.(cer); ok {// 這個地方隱藏的說明了 a的實(shí)例是滿足ber的,不然它這一步就會panic然后它還得滿足cer不然還會panic所以這一步直接驗(yàn)證了兩次。
v.post()
}
b1.get()
}
- 關(guān)于遞歸,
遞歸其實(shí)就是在執(zhí)行函數(shù)里的函數(shù),直到所有函數(shù)都結(jié)束了,然后就結(jié)束即可。舉個例子
func testVisit(ii int) int {
if ii == 0 {
return 100
}
fmt.Println(ii)
ii = testVisit(ii - 1)
return ii+1
}
它的執(zhí)行很明顯是從外層的初始棧開始往里執(zhí)行,然后所有棧執(zhí)行完畢即可,這里我使用了ii = testVisit(ii -1) 目的有兩個,1 為了讓每下一個的ii都少1,2 就是為了獲取上一個棧的返回值,然后每次返回都+1 最后的返回值是109 這也證明了每次返回都是從最上層的棧開始往下調(diào)用然后到最下面的然后返回。
- 關(guān)于 channel
chan 的機(jī)制是這樣的,當(dāng)一個沒有緩存的(有緩存也是一樣只是當(dāng)緩存滿了就一樣了)chan,顯示導(dǎo)入一個數(shù)據(jù),這個時候
這個發(fā)送chan的goruntine就睡眠了(阻塞)然后直到這個chan被接受(只要被接收就行,不管是不是在同樣一個goruntine)然后這個數(shù)據(jù)就被獲取了,然后開始喚醒這個chan的發(fā)送者的那個goruntine。如果沒有后續(xù)的數(shù)據(jù)那么這個chan就應(yīng)該被關(guān)閉了可以人工關(guān)閉(close)也可能被系統(tǒng)收回。
看一個例子 這是一個有緩存的,并且利用緩存來限制 http請求數(shù)量的操作
var st = make(chan struct{},20)// 將訪問的數(shù)據(jù)限制在20
var sy synv.WaitGroup
func main(){
dd := []string{"htps://...",",,,,,",",,,"}
sy.Add(len(dd))
for _,v := range dd {
go read(dd)//
}
sy.Wait()
fmt.Println("執(zhí)行完畢")
}
func read(st){
defer sy.Done()
st <- struct{}// 因?yàn)槭怯芯彺娴腸han所以可以保證一直有20個gorutine是不阻塞的。
// 只要有一個goruntine不是阻塞的就不會造成死鎖
rea(st)
<- st
}
func rea(st string){
res,err := http.Get(st)
}
只要有一個goruntine不是阻塞的就不會造成死鎖,死鎖是程序想退出,但是chan內(nèi)還有東西,沒辦法退出,但是又沒辦法運(yùn)行,造成了無法結(jié)束的窘迫,最終就是各個goruntine都是阻塞然而又不能退出的局面??傊?死鎖問題有必要再開一個文件來討論一下。
- 關(guān)于 type
alias的類型和底層可以轉(zhuǎn)化但是不是隱式是顯式。
這里分幾個內(nèi)容
- 一就是
type hand func(http....,http.....)
// 例如
httprouter.handle("/",httprouter.handle)
// 這個時候就是
httprouter.Handle("/",func(http....,http....))
// 即可。
這種類型的尤其是在函數(shù)的調(diào)用的時候 要滿足 一個hand類型也是很簡單 就是函數(shù)滿足后面那個樣式即可
- type hand string
這種情況也是 函數(shù)滿足后面的那個 type即可 也就是 是string即可 。
- 關(guān)于引用類型
舉個 slice說明一下
func main(){
t := make([]int,0,10)
t = []int{
"12",
}
visit(t)
fmt.Println(t)
}
func visit(t []string){
t = append(s,"1221")
}
猜一下 輸出的是什么?
是 ["12","1221"]嗎?
我本來一直以為是,后來我發(fā)現(xiàn)其實(shí)不是,我們要先證實(shí)一個問題,引用類型并不是指針,它是一個數(shù)據(jù)結(jié)構(gòu)通常是 一個cap 一個len和一個指針對象。所以它本身也是一個實(shí)際的值。當(dāng)這個地方把t傳入visit后,其實(shí)是值的復(fù)制,然后在visit中,t等于了一個append返回的一個新的slice,那么它就不是指向了原來的那個底層數(shù)組了,(換言之,這樣的話就不是改變底層數(shù)組了,是重新分配了一個數(shù)組,那么原來的那個slice自然就跟這個新的底層數(shù)組沒有關(guān)系了)那么什么時候會改變呢,也很簡單
func visit(t []string){
t [1] = "112"
}
這就叫做賦值,它是直接操控底層的數(shù)組進(jìn)行了值的改變,這并沒有去進(jìn)行值的拷貝或者是指針傳遞。到這里我么可以說一下了
在go里所有的類型只要是傳遞數(shù)據(jù)只有兩種模式,1 值的拷貝(包括引用類型它的拷貝只不過是拷貝的它的數(shù)據(jù)組織)2 指針的拷貝,說白了,指針的拷貝也是值的拷貝,因?yàn)樗旧硪彩且环N值只不過象征了一種鑰匙罷了,所以,除了對數(shù)據(jù)本身進(jìn)行直接改變,改變他的數(shù)據(jù)本身,這種行為可以改變它自己,值的傳遞的話 統(tǒng)統(tǒng)是有拷貝行為。所以我們以后不能把引用類型看成指針,很不一樣。如果是指針的話 那么肯定必須要獲取值才能去改變,但是引用類型是go的編譯器自動的行為。(例如擴(kuò)容啊,自動獲取底層數(shù)據(jù)的值啊這種)
- 關(guān)于帶(bare return)形參及不帶形參的返回值和defer的故事
看兩個例子:
之前的例子說過了,defer只是執(zhí)行滯后但是參數(shù)記住是參數(shù)也就是將形參傳入實(shí)參的過程其實(shí)是同步的并沒有什么區(qū)別。
// 這個例子中返回值是1
func tt()(t int){
defer func() {
t ++
}()
return
}
// 這個例子中返回值是0
func tt1()int{
t := 0
defer func() {
t ++
}()
return t
}
// 0 這個例子證明了 參數(shù)順序執(zhí)行化。
func tt1()int{
t := 0
defer func(t int) {
t ++
}(t)
return t
}
原因也是很簡單,首先如果是沒有形參的返回值,都是在return后面直接返回的,然后再執(zhí)行defer然后再執(zhí)行 os.exit() 但是有形參的就不一樣了,它必須返回它形參定義的參數(shù)之歌例子中就是t,那么t在哪最后一個出現(xiàn)呢?就是在defer中,所以它的執(zhí)行過程就變成了,找尋最后出現(xiàn)的t(這里出現(xiàn)在defer中)然后直接執(zhí)行os.exit() 因?yàn)樗黵eturn后面沒有東西,所以它和沒有形參的return XXX 很不一樣。
那么如果是這樣的呢?
func age()(n int,err error){
return
}
它會有什么返回結(jié)果呢?答案就是0 nil ---- 如果只有return 但是卻沒有出現(xiàn)n和err那么簡單 返回值里不是已經(jīng)初始化了嘛,那么久返回初始化的結(jié)果不就好了嘛所以是 0 nil(他們的初始化值)
- 關(guān)于buffered
我們在go的執(zhí)行中經(jīng)常使用的一種技巧就是限制go并發(fā)的速度,那么這個時候buffered變量就可以實(shí)現(xiàn)了它的實(shí)現(xiàn)是這樣的 make(chan xxx,number) 在get請求中一般我都會這么使用make(chan struct{},20) 我們定義了一個新的類型就是 struct{} 這個類型是代表了空,當(dāng)然你也可以使用bool 都可以 struct{} 類型使用的時候 用 struct{}{} 即可。這就代表了這個chan中最多可以暫存number個數(shù)據(jù),這就是所謂的緩存技術(shù),也叫做 buffered數(shù)據(jù)
- 關(guān)于 recover和并發(fā)(多goruntine)
如果是在go的多協(xié)程中的panic一定要在這個協(xié)程中recover否則在主協(xié)程的recover根本無法獲取這個panic
go func(i int) {
defer sy.Done()
defer func() { // 如果是在外部獲取recover可以說壓根獲取不了,想想也是知道的因?yàn)槟悴⒉恢乐鲄f(xié)程和這個協(xié)程到底哪個運(yùn)行到哪了,所以要在這個協(xié)程中搞定這個panic
if e := recover();e != nil {
fmt.Println(e)
}
}()
start := time.Now()
resp, err := http.Get(url[i])
if err != nil {
fmt.Println(err)
}
n, err := html.Parse(resp.Body)
if err != nil {
fmt.Println("err",err)
return
}
defer resp.Body.Close()
if err != nil {
fmt.Println(err)
}
wor, im := countWordsAndImagesAsync(nums, ch, n)
ma.Store(url[i]+" num", wor)
ma.Store(url[i]+" image", im)
end := time.Now()
timeS := end.Sub(start)
ma.Store(url[i]+"花費(fèi)的時間是:",timeS.String())
}(i)
- 關(guān)于遞歸的出棧和進(jìn)棧
遞歸都有一個進(jìn)出棧的過程,
func a(){
visit(start,end)
}
func visit(start,end func()){
start()//在進(jìn)棧時執(zhí)行的函數(shù)
for {
visit()
if XXXXXX 然后退出這個棧開始出棧
}
end()// 在出棧時執(zhí)行的函數(shù)。
}
- 關(guān)于 函數(shù)內(nèi)部的函數(shù)
func t(){
var d func()int // 使用這種方式一般都是函數(shù)內(nèi)部有遞歸,如果不實(shí)現(xiàn) 聲明一下 函數(shù)內(nèi)部的遞歸函數(shù)將無法運(yùn)行
d = func()int{
//fdffd
}
d()
// 或者
var d = func(){
}
d()
總之,不能使用
func()int{
}
int() 在go語言中這種行為不允許
}
關(guān)于函數(shù)內(nèi)部聲明類型 倒是很隨意
func t(t1 inter){
type t struct{
get()
}
if d,ok := t1.(t);ok {
d.get()
}
t1.post()
}
- 只有接口和nil不能擁有方法。
invalid receiver type io.Writer (io.Writer is an interface type) // 這是使用了接口的報錯。
nil is not a type// 這是使用了nil的報錯
ps: 永遠(yuǎn)不要去取接口的指針,沒有絲毫的意義。如果取 slice的指針還有些許的意義(比如在append的時候)但是接口的指針有什么意義?
接口本來就沒有實(shí)際的意義它本來就是一個抽象的東西。而且它本來也就是引用對象。
- time.After 的用法
它的作用是 當(dāng)這個系統(tǒng)沒有東西了,然后在設(shè)置后幾秒后運(yùn)行,如果其他的case一直有東西,那么它是不會被執(zhí)行的。
因?yàn)樵趕elect選擇的時候只有在其他的case都沒有反應(yīng)了的時候才會去選擇time.After 所以它可以用在 比如什么東西都沒有數(shù)據(jù)了以后
然后按照某時間后去取消這個東西,當(dāng)然還有一種應(yīng)用場景就是,無論如何就是要5分鐘后取消,打死都要
那么可以 使用兩個select,有一個select就放一個after和一個default即可。
select{
case time.After():
return // 這樣就強(qiáng)制 退出了
default:
}
select {
// 這個select就是干正事的。
}
或則,使用 context包的withcanceltimeout 這個函數(shù)厲害 無論如何 只要設(shè)置的分鐘數(shù)到了,就能立馬取消。
因?yàn)閏ancle withcancle那個函數(shù) 如果 不執(zhí)行cancle函數(shù) 那么ctx.done 就無法運(yùn)行,這個時候 cancle的關(guān)閉就要在執(zhí)行這個有ctx的函數(shù)之前了。就不能使用defer函數(shù)來關(guān)閉這個,因?yàn)橐恢庇袞|西運(yùn)行。
- 關(guān)于 string字符串 []byte 以及[]byte的十六進(jìn)制表示(以string形式儲存)
// 將string字符串,以unicode編碼的形式,找到所有的字符的unicode表示,然后返回位一個數(shù)組。
// [72 101 108 108 111] 就是這個數(shù)組(slice)
src := []byte("Hello")
// 這個encodeStr 是什么呢?它其實(shí)就是把這個數(shù)組的所有的數(shù)字用16進(jìn)制表示并且沒有加[]而已,而是將這個串變成了字符串的形式儲存
// 就是這個“48656c6c6f” 這個字符串其實(shí)還是unicode編碼只是 用的16進(jìn)制并且沒有[]罷了,一定不要認(rèn)為它就是"HELLO"
encodedStr := hex.EncodeToString(src)
fmt.Println(src)
// 48656c6c6f -> 48(4*16+8=72) 65(6*16+5=101) 6c 6c 6f
fmt.Println(encodedStr)
byteValue,_ := hex.DecodeString(encodeStr)
string(byteValue) == "Hello"
- 關(guān)于json的一個解析的問題
{"code":0,"data":{"ip":"173.82.115.125","country":"美國","area":"","region":"加利福尼亞","city":"洛杉磯","county":"XX","isp":"XX","country_id":"US","area_id":"","region_id":"US_104","city_id":"US_1018","county_id":"xx","isp_id":"xx"}}
type Data struct {
Data Values `json:"data"`
}
type Values struct {
Country string `json:"country"`
City string `json:"city"`
}
定義的時候可以缺少字段,但是,不能跟json字段的格式不符合,舉個例子 這里的數(shù)據(jù)是 在json整個文件下的data對象中,那么你需要兩個struct 一個是代表整個的json的數(shù)據(jù),第二個struct是代表那個data,你看 那個code和其它字段沒有定義吧,沒有定義無所謂,但是字段的格式一定要遵守 如果直接把Values傳進(jìn)去就是錯誤的行為,是不會解析的。
- 關(guān)于go template
第一點(diǎn) 如果你使用template.Execute 那么你在那個最外邊的layout那個文件里不能使用{{define "layout"}}{{end}}
如果你想使用{{define "layout"}}{{end}} 那么 你需要tem.ExecuteTemplate(w,"layout",nil)那個中間的變量要用最外邊的那個模塊
所以最好的就是最外邊的那個不用模塊,然后使用那個沒有模塊的就ok了
第二點(diǎn) 如果如果你的子模塊就是小的模塊很多人稱作是母模塊 我當(dāng)他們是小模塊子模塊,他們里面有變量,那么你肯定是最后使用的是layout這個最外面的文件或者說是模塊
那么你就要{{template "son".}} . 看到了嗎 這個點(diǎn)沒有這個點(diǎn) 你在最外面的模塊也就是最終使用的時候你發(fā)現(xiàn)你的變量壓根沒有導(dǎo)入,這個就是 變量導(dǎo)入的標(biāo)志
也就是是 你的子有了 如果不導(dǎo)入 那么這個數(shù)據(jù)就消失了,我被這個地方坑了幾個小時。我的天~~~~。
加入公眾號:算法和數(shù)據(jù)結(jié)構(gòu)的峽谷
