重慶分公司,新征程啟航
為企業提供網站建設、域名注冊、服務器等服務
為企業提供網站建設、域名注冊、服務器等服務
go語言math包里面定義了min/max函數,但是是float64類型的,而并沒有整數類型的min/max。
成都創新互聯公司服務項目包括昆都侖網站建設、昆都侖網站制作、昆都侖網頁制作以及昆都侖網絡營銷策劃等。多年來,我們專注于互聯網行業,利用自身積累的技術優勢、行業經驗、深度合作伙伴關系等,向廣大中小型企業、政府機構等提供互聯網行業的解決方案,昆都侖網站推廣取得了明顯的社會效益與經濟效益。目前,我們服務的客戶以成都為中心已經輻射到昆都侖省份的部分城市,未來相信會繼續擴大服務區域并繼續獲得客戶的支持與信任!
因為go沒有重載,這是個大坑。所以math庫里min/max函數都只能定義一個,所以官方選擇了比較難實現的float64類型。而簡單的整形就需要讓程序員自己實現了
Go語言中沒有“類”的概念,也不支持“類”的繼承等面向對象的概念。Go語言中通過結構體的內嵌再配合接口比面向對象具有更高的擴展性和靈活性。
自定義類型
在Go語言中有一些基本的數據類型,如string、整型、浮點型、布爾等數據類型, Go語言中可以使用type關鍵字來定義自定義類型。
自定義類型是定義了一個全新的類型。我們可以基于內置的基本類型定義,也可以通過struct定義。例如:
通過Type關鍵字的定義,MyInt就是一種新的類型,它具有int的特性。
類型別名
類型別名是Go1.9版本添加的新功能。
類型別名規定:TypeAlias只是Type的別名,本質上TypeAlias與Type是同一個類型。就像一個孩子小時候有小名、乳名,上學后用學名,英語老師又會給他起英文名,但這些名字都指的是他本人。
type TypeAlias = Type
我們之前見過的rune和byte就是類型別名,他們的定義如下:
類型定義和類型別名的區別
類型別名與類型定義表面上看只有一個等號的差異,我們通過下面的這段代碼來理解它們之間的區別。
結果顯示a的類型是main.NewInt,表示main包下定義的NewInt類型。b的類型是int。MyInt類型只會在代碼中存在,編譯完成時并不會有MyInt類型。
Go語言中的基礎數據類型可以表示一些事物的基本屬性,但是當我們想表達一個事物的全部或部分屬性時,這時候再用單一的基本數據類型明顯就無法滿足需求了,Go語言提供了一種自定義數據類型,可以封裝多個基本數據類型,這種數據類型叫結構體,英文名稱struct。 也就是我們可以通過struct來定義自己的類型了。
Go語言中通過struct來實現面向對象。
結構體的定義
使用type和struct關鍵字來定義結構體,具體代碼格式如下:
其中:
舉個例子,我們定義一個Person(人)結構體,代碼如下:
同樣類型的字段也可以寫在一行,
這樣我們就擁有了一個person的自定義類型,它有name、city、age三個字段,分別表示姓名、城市和年齡。這樣我們使用這個person結構體就能夠很方便的在程序中表示和存儲人信息了。
語言內置的基礎數據類型是用來描述一個值的,而結構體是用來描述一組值的。比如一個人有名字、年齡和居住城市等,本質上是一種聚合型的數據類型
結構體實例化
只有當結構體實例化時,才會真正地分配內存。也就是必須實例化后才能使用結構體的字段。
基本實例化
舉個例子:
我們通過.來訪問結構體的字段(成員變量),例如p1.name和p1.age等。
匿名結構體
在定義一些臨時數據結構等場景下還可以使用匿名結構體。
創建指針類型結構體
我們還可以通過使用new關鍵字對結構體進行實例化,得到的是結構體的地址。 格式如下:
從打印的結果中我們可以看出p2是一個結構體指針。
需要注意的是在Go語言中支持對結構體指針直接使用.來訪問結構體的成員。
取結構體的地址實例化
使用對結構體進行取地址操作相當于對該結構體類型進行了一次new實例化操作。
p3.name = "七米"其實在底層是(*p3).name = "七米",這是Go語言幫我們實現的語法糖。
結構體初始化
沒有初始化的結構體,其成員變量都是對應其類型的零值。
使用鍵值對初始化
使用鍵值對對結構體進行初始化時,鍵對應結構體的字段,值對應該字段的初始值。
也可以對結構體指針進行鍵值對初始化,例如:
當某些字段沒有初始值的時候,該字段可以不寫。此時,沒有指定初始值的字段的值就是該字段類型的零值。
使用值的列表初始化
初始化結構體的時候可以簡寫,也就是初始化的時候不寫鍵,直接寫值:
使用這種格式初始化時,需要注意:
結構體內存布局
結構體占用一塊連續的內存。
輸出:
【進階知識點】關于Go語言中的內存對齊推薦閱讀:在 Go 中恰到好處的內存對齊
面試題
請問下面代碼的執行結果是什么?
構造函數
Go語言的結構體沒有構造函數,我們可以自己實現。 例如,下方的代碼就實現了一個person的構造函數。 因為struct是值類型,如果結構體比較復雜的話,值拷貝性能開銷會比較大,所以該構造函數返回的是結構體指針類型。
調用構造函數
方法和接收者
Go語言中的方法(Method)是一種作用于特定類型變量的函數。這種特定類型變量叫做接收者(Receiver)。接收者的概念就類似于其他語言中的this或者 self。
方法的定義格式如下:
其中,
舉個例子:
方法與函數的區別是,函數不屬于任何類型,方法屬于特定的類型。
指針類型的接收者
指針類型的接收者由一個結構體的指針組成,由于指針的特性,調用方法時修改接收者指針的任意成員變量,在方法結束后,修改都是有效的。這種方式就十分接近于其他語言中面向對象中的this或者self。 例如我們為Person添加一個SetAge方法,來修改實例變量的年齡。
調用該方法:
值類型的接收者
當方法作用于值類型接收者時,Go語言會在代碼運行時將接收者的值復制一份。在值類型接收者的方法中可以獲取接收者的成員值,但修改操作只是針對副本,無法修改接收者變量本身。
什么時候應該使用指針類型接收者
任意類型添加方法
在Go語言中,接收者的類型可以是任何類型,不僅僅是結構體,任何類型都可以擁有方法。 舉個例子,我們基于內置的int類型使用type關鍵字可以定義新的自定義類型,然后為我們的自定義類型添加方法。
注意事項: 非本地類型不能定義方法,也就是說我們不能給別的包的類型定義方法。
結構體的匿名字段
匿名字段默認采用類型名作為字段名,結構體要求字段名稱必須唯一,因此一個結構體中同種類型的匿名字段只能有一個。
嵌套結構體
一個結構體中可以嵌套包含另一個結構體或結構體指針。
嵌套匿名結構體
當訪問結構體成員時會先在結構體中查找該字段,找不到再去匿名結構體中查找。
嵌套結構體的字段名沖突
嵌套結構體內部可能存在相同的字段名。這個時候為了避免歧義需要指定具體的內嵌結構體的字段。
結構體的“繼承”
Go語言中使用結構體也可以實現其他編程語言中面向對象的繼承。
結構體字段的可見性
結構體中字段大寫開頭表示可公開訪問,小寫表示私有(僅在定義當前結構體的包中可訪問)。
結構體與JSON序列化
JSON(JavaScript Object Notation) 是一種輕量級的數據交換格式。易于人閱讀和編寫。同時也易于機器解析和生成。JSON鍵值對是用來保存JS對象的一種方式,鍵/值對組合中的鍵名寫在前面并用雙引號""包裹,使用冒號:分隔,然后緊接著值;多個鍵值之間使用英文,分隔。
結構體標簽(Tag)
Tag是結構體的元信息,可以在運行的時候通過反射的機制讀取出來。 Tag在結構體字段的后方定義,由一對反引號包裹起來,具體的格式如下:
`key1:"value1" key2:"value2"`
結構體標簽由一個或多個鍵值對組成。鍵與值使用冒號分隔,值用雙引號括起來。鍵值對之間使用一個空格分隔。 注意事項: 為結構體編寫Tag時,必須嚴格遵守鍵值對的規則。結構體標簽的解析代碼的容錯能力很差,一旦格式寫錯,編譯和運行時都不會提示任何錯誤,通過反射也無法正確取值。例如不要在key和value之間添加空格。
例如我們為Student結構體的每個字段定義json序列化時使用的Tag:
不是為了與眾不同。而是為了更加清晰易懂。
Rob Pike 曾經在 Go 官方博客解釋過這個問題(原文地址:),簡略翻譯如下(水平有限翻譯的不對的地方見諒):
引言
Go語言新人常常會很疑惑為什么這門語言的聲明語法(declaration syntax)會和傳統的C家族語言不同。在這篇博文里,我們會進行一個比較,并做出解答。
C 的語法
首先,先看看 C 的語法。C 采用了一種聰明而不同尋常的聲明語法。聲明變量時,只需寫出一個帶有目標變量名的表達式,然后在表達式里指明該表達式本身的類型即可。比如:
int x;
上面的代碼聲明了 x 變量,并且其類型為 int——即,表達式 x 為 int 類型。一般而言,為了指明新變量的類型,我們得寫出一個表達式,其中含有我們要聲明的變量,這個表達式運算的結果值屬于某種基本類型,我們把這種基本類型寫到表達式的左邊。所以,下述聲明:
int *p;
int a[3];
指明了 p 是一個int類型的指針,因為 *p 的類型為 int。而 a 是一個 int 數組,因為 a[3] 的類型為 int(別管這里出現的索引值,它只是用于指明數組的長度)。
我們接下來看看函數聲明的情況。C 的函數聲明中關于參數的類型是寫在括號外的,像下面這樣:
int main(argc, argv)
int argc;
char *argv[];
{ /* ... */ }
如前所述,我們可以看到 main 之所以是函數,是因為表達式 main(argc, argv) 返回 int。在現代記法中我們是這么寫的:
int main(int argc, char *argv[]) { /* ... */ }
盡管看起來有些不同,但是基本的結構是一樣的。
總的來看,當類型比較簡單時,C的語法顯得很聰明。但是遺憾的是一旦類型開始復雜,C的這套語法很快就能讓人迷糊了。著名的例子如函數指針,我們得按下面這樣來寫:
int (*fp)(int a, int b);
在這兒,fp 之所以是一個指針是因為如果你寫出 (*fp)(a, b) 這樣的表達式將會調用一個函數,其返回 int 類型的值。如果當 fp 的某個參數本身又是一個函數,情況會怎樣呢?
int (*fp)(int (*ff)(int x, int y), int b)
這讀起來可就點難了。
當然了,我們聲明函數時是可以不寫明參數的名稱的,因此 main 函數可以聲明為:
int main(int, char *[])
回想一下,之前 argv 是下面這樣的
char *argv[]
你有沒有發現你是從聲明的「中間」去掉變量名而后構造出其變量類型的?盡管這不是很明顯,但你聲明某個 char *[] 類型的變量的時候,竟然需要把名字插入到變量類型的中間。
我們再來看看,如果我們不命名 fp 的參數會怎樣:
int (*fp)(int (*)(int, int), int)
這東西難懂的地方可不僅僅是要記得參數名原本是放這中間的
int (*)(int, int)
它更讓人混淆的地方還在于甚至可能都搞不清這竟然是個函數指針聲明。我們接著看看,如果返回值也是個函數指針類型又會怎么樣
int (*(*fp)(int (*)(int, int), int))(int, int)
這已經很難看出是關于 fp 的聲明了。
你自己還可以構建出比這更復雜的例子,但這已經足以解釋 C 的聲明語法引入的某些復雜性了。
還有一點需要指出,由于類型語法和聲明語法是一樣的,要解析中間帶有類型的表達式可能會有些難度。這也就是為什么,C 在做類型轉換的時候總是要把類型用括號括起來的原因,像這樣
(int)M_PI
Go 的語法
非C家族的語言通常在聲明時使用一種不同的類型語法。一般是名字先出現,然后常常跟著一個冒號。按照這樣來寫,我們上面所舉的例子就會變成下面這樣:
x: int
p: pointer to int
a: array[3] of int
這樣的聲明即便有些冗長,當至少是清晰的——你只需從左向右讀就行。Go 語言所采用的方案就是以此為基礎的,但為了追求簡潔性,Go 語言丟掉了冒號并去掉了部分關鍵詞,成了下面這樣:
x int
p *int
a [3]int
在 [3]int 和表達式中 a 的用法沒有直接的對應關系(我們在下一節會回過頭來探討指針的問題)。至此,你獲得了代碼清晰性方面的提升,但付出的代價是語法上需要區別對待。
下面我們來考慮函數的問題。雖然在 Go 語言里,main 函數實際上沒有參數,但是我們先謄抄一下之前的 main 函數的聲明:
func main(argc int, argv *[]byte) int
粗略一看和 C 沒什么不同,不過自左向右讀的話還不錯。
main 函數接受一個 int 和一個指針并返回一個 int。
如果此時把參數名去掉,它還是很清楚——因為參數名總在類型的前面,所以不會引起混淆。
func main(int, *[]byte) int
這種自左向右風格的聲明的一個價值在于,當類型變得更復雜時,它依然相對簡單。下面是一個函數變量的聲明(相當于 C 語言里的函數指針)
f func(func(int,int) int, int) int
或者當它返回一個函數時:
f func(func(int,int) int, int) func(int, int) int
上面的聲明讀起來還是很清晰,自左向右,而且究竟哪一個變量名是當前被聲明的也容易看懂——因為變量名永遠在首位。
類型語法和表達式語法帶來的差別使得在 Go 語言里調用閉包也變得更簡單:
sum := func(a, b int) int { return a+b } (3, 4)
指針
指針有些例外。注意在數組 (array )和切片 (slice) 中,Go 的類型語法把方括號放在了類型的左邊,但是在表達式語法中卻又把方括號放到了右邊:
var a []int
x = a[1]
類似的,Go 的指針沿用了 C 的 * 記法,但是我們寫的時候也是聲明時 * 在變量名右邊,但在表達式中卻又得把 * 放到左左邊:
var p *int
x = *p
不能寫成下面這樣
var p *int
x = p*
因為后綴的 * 可能會和乘法運算混淆,也許我們可以改用 Pascal 的 ^ 標記,像這樣
var p ^int
x = p^
我們也許還真的應該把 * 像上面這樣改成 ^ (當然這么一改 xor 運算的符號也得改),因為在類型和表達式中的 * 前綴確實把好些事兒都搞得有點復雜,舉個例子來說,雖然我們可以像下面這樣寫
[]int("hi")
但在轉換時,如果類型是以 * 開頭的,就得加上括號:
(*int)(nil)
如果有一天我們愿意放棄用 * 作為指針語法的話,那么上面的括號就可以省略了。
可見,Go 的指針語法是和 C 相似的。但這種相似也意味著我們無法徹底避免在文法中有時為了避免類型和表達式的歧義需要補充括號的情況。
總而言之,盡管存在不足,但我們相信 Go 的類型語法要比 C 的容易懂。特別是當類型比較復雜時。
Go語言也稱 Golang,兼具效率、性能、安全、健壯等特性。這套Go語言教程(Golang教程)通俗易懂,深入淺出,既適合沒有基礎的讀者快速入門,也適合工作多年的程序員查閱知識點。
Go 語言
這套教程在講解一些知識點時,將 Go 語言和其他多種語言進行對比,讓掌握其它編程語言的讀者能迅速理解 Go 語言的特性。Go語言從底層原生支持并發,無須第三方庫、開發者的編程技巧和開發經驗就可以輕松搞定。
Go語言(或 Golang)起源于 2007 年,并在 2009 年正式對外發布。Go 是非常年輕的一門語言,它的主要目標是“兼具 Python 等動態語言的開發速度和 C/C++ 等編譯型語言的性能與安全性”。
Go語言是編程語言設計的又一次嘗試,是對類C語言的重大改進,它不但能讓你訪問底層操作系統,還提供了強大的網絡編程和并發編程支持。Go語言的用途眾多,可以進行網絡編程、系統編程、并發編程、分布式編程。
Go語言的推出,旨在不損失應用程序性能的情況下降低代碼的復雜性,具有“部署簡單、并發性好、語言設計良好、執行性能好”等優勢,目前國內諸多 IT 公司均已采用Go語言開發項目。Go語言有時候被描述為“C 類似語言”,或者是“21 世紀的C語言”。Go 從C語言繼承了相似的表達式語法、控制流結構、基礎數據類型、調用參數傳值、指針等很多思想,還有C語言一直所看中的編譯后機器碼的運行效率以及和現有操作系統的無縫適配。
因為Go語言沒有類和繼承的概念,所以它和 Java 或 C++ 看起來并不相同。但是它通過接口(interface)的概念來實現多態性。Go語言有一個清晰易懂的輕量級類型系統,在類型之間也沒有層級之說。因此可以說Go語言是一門混合型的語言。
此外,很多重要的開源項目都是使用Go語言開發的,其中包括 Docker、Go-Ethereum、Thrraform 和 Kubernetes。Go 是編譯型語言,Go 使用編譯器來編譯代碼。編譯器將源代碼編譯成二進制(或字節碼)格式;在編譯代碼時,編譯器檢查錯誤、優化性能并輸出可在不同平臺上運行的二進制文件。要創建并運行 Go 程序,程序員必須執行如下步驟。
使用文本編輯器創建 Go 程序;
保存文件;編譯程序;運行編譯得到的可執行文件。
這不同于 Python、Ruby 和 JavaScript 等語言,它們不包含編譯步驟。Go 自帶了編譯器,因此無須單獨安裝編譯器。
鏈喬教育在線旗下學碩創新區塊鏈技術工作站是中國教育部學校規劃建設發展中心開展的“智慧學習工場2020-學碩創新工作站 ”唯一獲準的“區塊鏈技術專業”試點工作站。專業站立足為學生提供多樣化成長路徑,推進專業學位研究生產學研結合培養模式改革,構建應用型、復合型人才培養體系。
Go 由于不支持泛型而臭名昭著,但最近,泛型已接近成為現實。Go 團隊實施了一個看起來比較穩定的設計草案,并且正以源到源翻譯器原型的形式獲得關注。本文講述的是泛型的最新設計,以及如何自己嘗試泛型。
例子
FIFO Stack
假設你要創建一個先進先出堆棧。沒有泛型,你可能會這樣實現:
type?Stack?[]interface{}func?(s?Stack)?Peek()?interface{}?{
return?s[len(s)-1]
}
func?(s?*Stack)?Pop()?{
*s?=?(*s)[:
len(*s)-1]
}
func?(s?*Stack)?Push(value?interface{})?{
*s?=?
append(*s,?value)
}
但是,這里存在一個問題:每當你 Peek 項時,都必須使用類型斷言將其從 interface{} 轉換為你需要的類型。如果你的堆棧是 *MyObject 的堆棧,則意味著很多 s.Peek().(*MyObject)這樣的代碼。這不僅讓人眼花繚亂,而且還可能引發錯誤。比如忘記 * 怎么辦?或者如果您輸入錯誤的類型怎么辦?s.Push(MyObject{})` 可以順利編譯,而且你可能不會發現到自己的錯誤,直到它影響到你的整個服務為止。
通常,使用 interface{} 是相對危險的。使用更多受限制的類型總是更安全,因為可以在編譯時而不是運行時發現問題。
泛型通過允許類型具有類型參數來解決此問題:
type?Stack(type?T)?[]Tfunc?(s?Stack(T))?Peek()?T?{
return?s[len(s)-1]
}
func?(s?*Stack(T))?Pop()?{
*s?=?(*s)[:
len(*s)-1]
}
func?(s?*Stack(T))?Push(value?T)?{
*s?=?
append(*s,?value)
}
這會向 Stack 添加一個類型參數,從而完全不需要 interface{}。現在,當你使用 Peek() 時,返回的值已經是原始類型,并且沒有機會返回錯誤的值類型。這種方式更安全,更容易使用。(譯注:就是看起來更丑陋,^-^)
此外,泛型代碼通常更易于編譯器優化,從而獲得更好的性能(以二進制大小為代價)。如果我們對上面的非泛型代碼和泛型代碼進行基準測試,我們可以看到區別:
type?MyObject?struct?{
X?
int
}
var?sink?MyObjectfunc?BenchmarkGo1(b?*testing.B)?{
for?i?:=?0;?i??b.N;?i++?{
var?s?Stack
s.Push(MyObject{})
s.Push(MyObject{})
s.Pop()
sink?=?s.Peek().(MyObject)
}
}
func?BenchmarkGo2(b?*testing.B)?{
for?i?:=?0;?i??b.N;?i++?{
var?s?Stack(MyObject)
s.Push(MyObject{})
s.Push(MyObject{})
s.Pop()
sink?=?s.Peek()
}
}
結果:
BenchmarkGo1BenchmarkGo1-16?????12837528?????????87.0?ns/op???????48?B/op????????2?allocs/opBenchmarkGo2BenchmarkGo2-16?????28406479?????????41.9?ns/op???????24?B/op????????2?allocs/op
在這種情況下,我們分配更少的內存,同時泛型的速度是非泛型的兩倍。
合約(Contracts)
上面的堆棧示例適用于任何類型。但是,在許多情況下,你需要編寫僅適用于具有某些特征的類型的代碼。例如,你可能希望堆棧要求類型實現 String() 函數
當需要定義一個整形變量a 心里是這樣想的:我現在需要一個整形的變量,我要定義它,于是我先寫一個int,再思考它的名字 a ,于是就這么寫出來了int a ?。而不是我寫了個變量a,我得給它區分個類型int。2. 在調用一個方法的時候,func(abdfsasdffdg int, bagressdgf string, csdgesredg bool) ? ?那個go函數看的很亂,程序員其實根本就不怎么看參數名字是什么,而只是看需要傳入什么類型,注意力只在于int,string,bool這三個,如果如上那么寫,反而影響了視線,亂系八糟的。func(int adsfasdfsdaf, string asdfasfasf, bool gwegasgs),這么寫我只注意類型,就不受名稱影響了。3. IDE自動提示 ? ?go本身就是為快而生,定義一個結構變量Rectangle rectangle,當鍵盤敲下r時候,IDE會自動給出rectangle,直接回車就出來了,反過來就的自己一個字母一個字母敲上去,蛋疼啊4. 至于go給出的解釋,當遇到復雜函數時…… ? ?一個項目中能寫幾個復雜函數,為了去解決這么一點小問題就把優勢給犧牲了.