ברוך הבא לסיור המודרך של שפת התכנות Go.
המדריך מחולק לשלושה חלקים. בסוף כל חלק ישנה סדרת תרגילים שתוכלו להשלים.
המדריך אינטרקטיבי. לחצו על כפתור ה"הרץ" עכשיו (או הקישו Shift+Enter) בכדי לקמפל ולהריץ את התוכנית על שרת מרוחק. המחשב שלך. תוצאת התוכנית תוצג מתחת לקוד.
הדוגמאות הנ"ל מציגות אספקטים שונים של Go. התוכניות במדריך הזה מיועדות להוות נקודת התחלה לניסויים האישיים שלך."
ערוך את הקוד והרץ את התוכנית שוב.
כשאתה מוכן להמשיך הלאה, לחץ על כפתור "הבא" או הקש על מקש הPageDown.
package main
import "fmt"
func main() {
fmt.Println("שלום, 世界")
}
המדריך זמין בשפות הבאות:
(אם אתה מעוניין לתרגל את המדריך לשפה אחרת, הורד את הקוד מ
https://code.google.com/p/go-tour,
תרגם את static/index.html,
והעלה אותו לApp Engine באמצעות ההוראות ב
appengine/README.)
לחץ על כפתור "הבא" או על מקש PageDown בכדי להמשיך.
המדריך הזה זמין גם כתוכנת Stand-alone שניתנת לשימוש ללא חיבור לאינטרנט.
תוכנת הStand-alone מהירה יותר, מכיוון שהיא בונה ומריצה את דוגמאות הקוד ישירות על המחשב שלכם. היא גם כוללת תרגילים נוספים שלא קיימים בגרסאת האונליין.
בכדי להריץ את המדריך בצורה מקומית, קודם כל התקן את Go, ולאחר מכן השתמש ב go get בכדי להתקין את gotour:
go get code.google.com/p/go-tour/gotour
בסוף, הרץ את הקובץ שנוצר - gotour.
במידה ואינך מעוניין בהרצה מקומית, לחץ על כפתור "הבא" או על מקש הPageDown בכדי להמשיך.
(תוכל לחזור להוראות האלו ע"י לחיצה על כפתור האינדקס)
כל תוכנית Go מורכבת מחבילות.
תוכניות מתחילות לרוץ מתוך החבילה main.
התוכנית הנ"ל משתמשת בחבילות חיצוניות באמצעות ייבוא של
"fmt" ו "math".
ככלל, שם החבילה הוא אותו השם של הרכיב האחרון בנתיב הייבוא.
package main
import (
"fmt"
"math"
)
func main() {
fmt.Println("יום", math.Pi, "שמח")
}
הקוד הנ"ל מרכז את חלק ייבוא החבילות בצורה מאוחדת תוך כדי שימוש בסוגריים. ניתן להשתמש בפקודות import מרובות, לדוגמא:
import "fmt" import "math"אך נפוץ להשתמש בצורה המאוחדת בכדי למנוע בלאגן בקוד.
package main
import (
"fmt"
"math"
)
func main() {
fmt.Printf("עכשיו יש לך %g בעיות.",
math.Nextafter(2, 3))
}
לאחר שייבאנו חבילה, נוכל להתייחס לשמות שהיא מייצאת.
בGo, שם מיוצא אם הוא מתחיל באות גדולה.
Foo הוא שם מיוצא, כמו כן גם FOO.
השם foo אינו מיוצא.
הרץ את הקוד. לאחר מכן שנה את השם של
math.pi ל math.Pi
ונסה שוב.
package main
import (
"fmt"
"math"
)
func main() {
fmt.Println(math.pi)
}
פונקציה יכולה לקבל אפס או יותר משתנים.
בדוגמא הזו, add מקבלת שני משתנים מסוג int.
שיב לב שסוג המשתנה מגיע אחרי שם המשתנה.
(אם אתם מעוניינים לדעת עוד מדוע סוגי משתנים נראים כפי שהם נראים, קראו את המאמר על תחביר ההכרזות של Go.)
package main
import "fmt"
func add(x int, y int) int {
return x + y
}
func main() {
fmt.Println(add(42, 13))
}
כאשר שני משתנים עקביים או יותר בפונקציה חולקות סוג, ניתן לוותר על הכרזת הסוג בכולם חוץ מבאחרון.
בדוגמא זו, קיצרנו את
x int, y int
ל
x, y int
package main
import "fmt"
func add(x, y int) int {
return x + y
}
func main() {
fmt.Println(add(42, 13))
}
פונקציה יכולה להחזיר כל מספר של תוצאות.
הפונקציה הזו מחזירה שתי מחרוזות.
package main
import "fmt"
func swap(x, y string) (string, string) {
return y, x
}
func main() {
a, b := swap("hello", "world")
fmt.Println(a, b)
}
פונקציות מקבלות משתים. בGo, פונקציות יכולות להחזיר "משתני תוצאה" מרובים, לא רק ערך בודד. ניתן לתת להם שם ולפעול עליהם בדיוק כמו משתנים. "
אם הוגדרו שמות למשתני התוצאה, קריאת return ללא פרמטרים תחזיר את הערכים הנוכחיים של התוצאות.
package main
import "fmt"
func split(sum int) (x, y int) {
x = sum * 4/9
y = sum - x
return
}
func main() {
fmt.Println(split(17))
}
ההכרזה var מצהירה על רשימת משתנים;
כמו ברשימת משתנים של פונקציה, הסוג ימוקם בסוף.
package main
import "fmt"
var x, y, z int
var c, python, java bool
func main() {
fmt.Println(x, y, z, c, python, java)
}
ההכרזה var יכולה לכלול מאתחלים (initalizers), אחד בעבור כל משתנה.
אם קיים מאתחל, ניתן להשמיט את הסוג; המשתנה יקבל את הסוג שלו מהמאתחל.
package main
import "fmt"
var x, y, z int = 1, 2, 3
var c, python, java = true, false, "לא!"
func main() {
fmt.Println(x, y, z, c, python, java)
}
בתוך פונקציה, הצהרת ההשמה המקוצרת :=
ניתנת לשימוש במקום הצהרת var עם סוג מוגדר.
(מחוץ לפונקציה, כל הגדרה תתחיל במילת מפתח, ולא ניתן יהיה להשתמש ב:=)
package main
import "fmt"
func main() {
var x, y, z int = 1, 2, 3
c, python, java := true, false, "לא!"
fmt.Println(x, y, z, c, python, java)
}
קבועים מוכרזים בדיוק כמו משתנים, אבל עם מילת המפתח const.
קבועים יכולים להיות מסוג תו, מחרוזת, Boolean, או ערכים מספריים.
package main
import "fmt"
const Pi = 3.14
func main() {
const World = "世界"
fmt.Println("שלום", World)
fmt.Println("יום", Pi, "שמח")
const Truth = true
fmt.Println("Go אדירה?", Truth)
}
קבועיים מספרים הם ערכים בעלי רמת דיוק גבוהה.
קבוע ללא סוג יקבל את הסוג הנחוץ לפי ההקשר בקוד.
נסו גם להדפיס את needInt(Big) וראו מה קורה.
package main
import "fmt"
const (
Big = 1<<100
Small = Big>>99
)
func needInt(x int) int { return x*10 + 1 }
func needFloat(x float64) float64 {
return x*0.1
}
func main() {
fmt.Println(needInt(Small))
fmt.Println(needFloat(Small))
fmt.Println(needFloat(Big))
}
לGo יש רק מבנה לולאה אחת, לולאת for.
לולאת הfor הבסיסית נראית כמו שהיא נראית בC או בJava,
חוץ מזה שנפטרנו מהסוגריים ( )
ושהסוגריים המסולסלות { } הן חובה.
package main
import "fmt"
func main() {
sum := 0
for i := 0; i < 10; i++ {
sum += i
}
fmt.Println(sum)
}
כמו בC או Java, ניתן להשאיר את החלק המקדים או העוקב ריקים.
package main
import "fmt"
func main() {
sum := 1
for ; sum < 1000; {
sum += sum
}
fmt.Println(sum)
}
בנקודה הזו ניתן לוותר על הנקודה-פסיק:
while של C ממומשת באמצעות for בGo.
package main
import "fmt"
func main() {
sum := 1
for sum < 1000 {
sum += sum
}
fmt.Println(sum)
}
אם נשמיט את תנאי הלולאה, היא תמשיך לנצח.
package main
func main() {
for ; ; {
}
}
וללא תנאים או חלקי-לולאה בכלל, ניתן להשמיט את חלקי הנקודה-פסיק. בצורה זו לולאה אינסופית היא פשוטה וקומפקטית.
package main
func main() {
for {
}
}
הצהרת if נראית כפי שהיא נראית בC או Java.
חוץ מזה שנפטרנו מהסוגריים ( )
ושהסוגריים המסולסלות { } הן חובה.
(נשמע מוכר?)
package main
import (
"fmt"
"math"
)
func sqrt(x float64) string {
if x < 0 {
return sqrt(-x) + "i"
}
return fmt.Sprint(math.Sqrt(x))
}
func main() {
fmt.Println(sqrt(2), sqrt(-4))
}
כמו for, הצהרת הif יכולה להתחיל עם הצהרה קצרה שתרוץ לפני בדיקת התנאי.
משתנים שמוכרזים ע"י הצהרה זו זמינים רק בתחום (Scope) שעד סוף הצהרת הif.
(נסו להשתמש ב v בהצהרת הreturn האחרונה.)
package main
import (
"fmt"
"math"
)
func pow(x, n, lim float64) float64 {
if v := math.Pow(x, n); v < lim {
return v
}
return lim
}
func main() {
fmt.Println(
pow(3, 2, 10),
pow(3, 3, 20),
)
}
משתנים שמוגדרים בתוך הגדרת if מקוצרת יהיו זמינים גם בתוך כל אחד מבלוקי הelse.
package main
import (
"fmt"
"math"
)
func pow(x, n, lim float64) float64 {
if v := math.Pow(x, n); v < lim {
return v
} else {
fmt.Printf("%g >= %g\n", v, lim)
}
// v כאן לא נוכל להשתמש ב
return lim
}
func main() {
fmt.Println(
pow(3, 2, 10),
pow(3, 3, 20),
)
}
הסוגים הבסיסיים של Go הם:
bool
string
int int8 int16 int32 int64
uint uint8 uint16 uint32 uint64 uintptr
byte // uint8 שם נוסף ל
rune // int32 שם נוסף ל
// Unicode מייצג שורת קוד ב
float32 float64
complex64 complex128
package main
import (
"math/cmplx"
"fmt"
)
var (
ToBe bool = false
MaxInt uint64 = 1<<64 - 1
z complex128 = cmplx.Sqrt(-5+12i)
)
func main() {
const f = "%T(%v)\n"
fmt.Printf(f, ToBe, ToBe)
fmt.Printf(f, MaxInt, MaxInt)
fmt.Printf(f, z, z)
}
מבנה (Struct) הוא אוסף של שדות.
(וההצהרה type עושה מה שהיית מצפה שהיא תעשה.)
package main
import "fmt"
type Vertex struct {
X int
Y int
}
func main() {
fmt.Println(Vertex{1, 2})
}
ניגש לשדה בתוך מבנה באמצעות נקודה.
package main
import "fmt"
type Vertex struct {
X int
Y int
}
func main() {
v := Vertex{1, 2}
v.X = 4
fmt.Println(v.X)
}
לGo יש מצביעים (Pointers), אך אין לה אפשרות לביצוע פעולות חישוב על מצביעים.
ניתן לגשת לשדות במבנה באמצעות מצביע מבנה (Struct pointer). הגישה בעקיפין דרך המצביע מתבצעת בצורה שקופה לחלוטין.
package main
import "fmt"
type Vertex struct {
X int
Y int
}
func main() {
p := Vertex{1, 2}
q := &p
q.X = 1e9
fmt.Println(p)
}
מבנה מפורש (Struct literal) מציין יצירה של מבנה חדש עם רשימה של הערכים של השדות במבנה.
ניתן לציין את הערכים של חלק מהשדות בלבד באמצעות שימוש בתחביר Name:
(סדר שמות הסדות לא משנה.)
הקדימית המיוחדת & יוצרת מצביע למבנה מפורש.
package main
import "fmt"
type Vertex struct {
X, Y int
}
var (
p = Vertex{1, 2} // has type Vertex
q = &Vertex{1, 2} // has type *Vertex
r = Vertex{X: 1} // Y:0 is implicit
s = Vertex{} // X:0 and Y:0
)
func main() {
fmt.Println(p, q, r, s)
}
הביטוי new(T) מאתחל ערך T מאופס ומחזיר מצביע אליו.
var t *T = new(T)
או
t := new(T)
package main
import "fmt"
type Vertex struct {
X, Y int
}
func main() {
v := new(Vertex)
fmt.Println(v)
v.X, v.Y = 11, 9
fmt.Println(v)
}
מפה מייצגת מיפוי של מפתחות לערכים.
בכדי ליצור מפות נהיה חייבים להשתמש בmake (ולא בnew)
לפני השימוש; המפה nil ריקה, ולא יהיה ניתן לשים בה ערכים.
package main
import "fmt"
type Vertex struct {
Lat, Long float64
}
var m map[string]Vertex
func main() {
m = make(map[string]Vertex)
m["Bell Labs"] = Vertex{
40.68433, 74.39967,
}
fmt.Println(m["Bell Labs"])
}
מפות מפורשות הן כמו מבנים מפורשים, חוץ מזה שהמפתחות נחוצים.
package main
import "fmt"
type Vertex struct {
Lat, Long float64
}
var m = map[string]Vertex{
"Bell Labs": Vertex{
40.68433, -74.39967,
},
"Google": Vertex{
37.42202, -122.08408,
},
}
func main() {
fmt.Println(m)
}
אם הסוג שברמה-העליונה הוא רק שם סוג, ניתן ל להשמיט אותו מהאלמנטים של המפה המפורשת.
package main
import "fmt"
type Vertex struct {
Lat, Long float64
}
var m = map[string]Vertex{
"Bell Labs": {40.68433, -74.39967},
"Google": {37.42202, -122.08408},
}
func main() {
fmt.Println(m)
}
הכנסה או עדכון של אלמנט בתוך מפה m:
m[key] = elem
איחזור ערך של אלמנט:
elem = m[key]
מחיקת אלמנט:
delete(m, key)
בדיקה האם המפתח המבוקש קיים באמצעות השמת שני ערכים:
elem, ok = m[key]
אם המפתח key קיים בm,
אזי ok יהיה שווה ל true.
אם לא, ok יהיה שווה ל false.
הערך של elem יהיה הערך-המאופס של סוג המפה.
בדומה, כשנקרא ממפה שבה המפתח לא קיים, התוצאה תהיה הערך המאופס של סוג האלמנט במפה.
package main
import "fmt"
func main() {
m := make(map[string]int)
m["Answer"] = 42
fmt.Println("The value:", m["Answer"])
m["Answer"] = 48
fmt.Println("The value:", m["Answer"])
delete(m, "Answer")
fmt.Println("The value:", m["Answer"])
v, ok := m["Answer"]
fmt.Println("The value:", v, "Present?", ok)
}
פרוסה מצביעה למערך של ערכים וכוללת גם את האורך.
[]T היא פרוסה עם אלמנטים מסוג T.
package main
import "fmt"
func main() {
p := []int{2, 3, 5, 7, 11, 13}
fmt.Println("p ==", p)
for i := 0; i < len(p); i++ {
fmt.Printf("p[%d] == %d\n",
i, p[i])
}
}
ניתן לפרוס מחדש פרוסות, ובכך ליצור ערך פרוסה חדש שמצביע לאותו המערך.
הביטוי
s[lo:hi]
יצור פרוסה של האלמנטים מנקודה lo עד נקודה hi-1, כולל. כך בעצם
s[lo:lo]
יהיה ריק, ו
s[lo:lo+1]
יחזיר אלמנט אחד.
package main
import "fmt"
func main() {
p := []int{2, 3, 5, 7, 11, 13}
fmt.Println("p ==", p)
fmt.Println("p[1:4] ==", p[1:4])
// אם אין ערך תחתי, ברירת המחדל היא 0
fmt.Println("p[:3] ==", p[:3])
// אם אין ערך עליון, ברירת המחדל היא האורך של המערך
fmt.Println("p[4:] ==", p[4:])
}
פרוסות נוצרות באמצעות הפונקציה make.
זה עובד באמצעות יצירה של מערך מאופס והחזרת פרוסה שמתייחסת למערך:
a := make([]int, 5) // len(a)=5לפרוסות יש אורך וקיבולת. הקיבולת של פרוסה היא האורך המקסימלי שאליו הפרוסה יכולה לגדול בתוך המערך שתחתיו.
בכדי לציין את הקיבולת, העבר פרמטר שלישי ל
b := make([]int, 0, 5) // len(b)=0, cap(b)=5ניתן להגדיל פרוסות על ידי "פריסה מחדש" (עד הקיבולת שלהן):
b = b[:cap(b)] // len(b)=5, cap(b)=5 b = b[1:] // len(b)=4, cap(b)=4
package main
import "fmt"
func main() {
a := make([]int, 5)
printSlice("a", a)
b := make([]int, 0, 5)
printSlice("b", b)
c := b[:2]
printSlice("c", c)
d := c[2:5]
printSlice("d", d)
}
func printSlice(s string, x []int) {
fmt.Printf("%s len=%d cap=%d %v\n",
s, len(x), cap(x), x)
}
הערך המאופס של פרוסה הוא nil.
האורך והקיבולת של פרוסת nil הם 0.
(אם אתם מעוניינים ללמוד עוד על פרוסות, קראו את המאמר "Slices: usage and internals".)
package main
import "fmt"
func main() {
var z []int
fmt.Println(z, len(z), cap(z))
if z == nil {
fmt.Println("nil!")
}
}
פונקציות הן גם ערכים.
package main
import (
"fmt"
"math"
)
func main() {
hypot := func(x, y float64) float64 {
return math.Sqrt(x*x + y*y)
}
fmt.Println(hypot(3, 4))
}
כמו כן, פונקציות תחומות באופן מלא.
הפונקציה adder מחזירה תחימה.
כל תחימה היא בעלת משתנה sum משלה.
package main
import "fmt"
func adder() func(int) int {
sum := 0
return func(x int) int {
sum += x
return sum
}
}
func main() {
pos, neg := adder(), adder()
for i := 0; i < 10; i++ {
fmt.Println(
pos(i),
neg(-2*i),
)
}
}
הצורה range בלולאת for עוברת על פרוסה או מפה.
package main
import "fmt"
var pow = []int{1, 2, 4, 8, 16, 32, 64, 128}
func main() {
for i, v := range pow {
fmt.Printf("2**%d = %d\n", i, v)
}
}
ניתן להשמיט את האינדקס באמצעות שימוש ב_.
אם נרצה רק את האינדקס, נוכל להשמיט את כל החלק הבא
“, value”.
package main
import "fmt"
func main() {
pow := make([]int, 10)
for i := range pow {
pow[i] = 1<<uint(i)
}
for _, value := range pow {
fmt.Printf("%d\n", value)
}
}
כנראה שכבר יכלתם לנחש איך מבנה הswitch עומד להיראות.
כל בלוק של case ייעצר בסופו בצורה אוטומטית, אלא אם הוא יסתיים בהצהרת fallthrough.
package main
import (
"fmt"
"runtime"
)
func main() {
fmt.Print("Go רצה על ")
switch os := runtime.GOOS; os {
case "darwin":
fmt.Println("OS X.")
case "linux":
fmt.Println("Linux.")
default:
// freebsd, openbsd,
// plan9, windows...
fmt.Printf("%s.", os)
}
}
במבנה Switch, בלוקים של case יבדקו מלמעלה למטה, והמבנה ייעצר כאשר אחד מהcaseים מצליח.
(לדוגמא,
switch i {
case 0:
case f():
}
לא יקרא ל f אם i==0.)
package main
import (
"fmt"
"time"
)
func main() {
fmt.Println("מתי יום שבת?")
today := time.Now().Weekday()
switch time.Saturday {
case today+0:
fmt.Println("היום.")
case today+1:
fmt.Println("מחר.")
case today+2:
fmt.Println("עוד יומיים.")
default:
fmt.Println("מתישהו...")
}
}
מבנה Switch ללא תנאי מתפקד באותה צורה כמו switch true.
צורת הכתיבה הזו יכולה להיות דרך שימושית להמנע מכתיבה של מבני if-then-else ארוכים ומסורבלים.
package main
import (
"fmt"
"time"
)
func main() {
t := time.Now()
switch {
case t.Hour() < 12:
fmt.Println("Good morning!")
case t.Hour() < 17:
fmt.Println("Good afternoon.")
default:
fmt.Println("Good evening.")
}
}
כדרך פשוטה להתחיל לשחק עם פונקציות ולולאות, ממש את פונקציית השורש הריבועי באמצעות השיטה של ניוטון.
במקרה הזה, השיטה של ניוטון מעריכה בקירוב את Sqrt(x)
באמצעות בחירה של נקודת התחלה z ולאחר מכן חוזרת על:
בתור התחלה, חזור על החישוב 10 פעמים ובדוק כמה קרוב אתה מגיע לתשובה בעבור ערכים שונים (1,2,3...).
בהמשך, שנה את תנאי הלולאה כך שיעצור כאשר הערך מפסיק להשתנות (או בעבור שינויים בעלי הפרש קטן מאוד). ראה אם פעולה זו מקטינה או מגדילה את מספר החזרות. כמה קרוב הגעת ל math.Sqrt?
רמז: בכדי להכריז ולאתחל ערך float, תן לו ערך מספרי מסוג float או השתמש בהמרה:
z := float64(1) z := 1.0
package main
import (
"fmt"
)
func Sqrt(x float64) float64 {
}
func main() {
fmt.Println(Sqrt(2))
}
ממש את WordCount. פונקציה זו אמורה להחזיר מפה שסופרת כמה פעמים “word” מופיעה במחרוזת s.
הפונקציה wc.Test מריצה סדרת בדיקות מול הפונקציה המסופקת ומדפיסה האם הבדיקה עברה בהצלחה או כשלון.
יכול להיות שהלינק הבא יהיה שימושי בעבורך: strings.Fields.
package main import ( "tourcode.google.com/p/go-tour/wc" ) func WordCount(s string) map[string]int { return map[string]int{"x": 1} } func main() { wc.Test(WordCount) }
ממש את Pic. הפונקציה אמורה להחזיר פרוסה באורך dy, שבה כל אלמנט הוא פרוסה של dx מסוג 8-bit unsigned integer.
כאשר תריץ את תוכנית זו, היא תציג את התמונה שלך, ותפרש את המספרים כערכי Grayscale.
בחירת התמונה היא בידך.
פונקציות מעניינות כוללות את
The choice of image is up to you.
x^y, (x+y)/2, ו x*y.
(תצטרך להשתמש בלולאה בכדאי לאתחל כל []uint8 בתוך ה[][]uint8)
(השתמש בuint8(intValue) כדי להמיר בין סוגים שונים.)
package main import "tourcode.google.com/p/go-tour/pic" func Pic(dx, dy int) [][]uint8 { } func main() { pic.Show(Pic) }
בואו נהנה קצת עם פונקציות.
ממש פונקציית fibonacci שמחזיר פונקציה (תחימה) שתחזיר מספרים מסדרת פיבונאצ'י.
package main
import "fmt"
// fibonacci היא פונקציה שמחזירה פונקציה
// int שמחזירה
func fibonacci() func() int {
}
func main() {
f := fibonacci()
for i := 0; i < 10; i++ {
fmt.Println(f())
}
}
בואו נחקור את התמיכה המובנית של Go במספרים מורכבים באמצעות הסוגים
complex64 ו complex128.
שורש משולש הוא מספר שכאשר תכפיל אותו בעצמו שלוש פעמים, ייתן לך את המספר המקורי.
לדוגמא: 3√8 = 2
בעבור שורשים משולשים, השיטה של ניוטון חוזרת על:
מצא את השורש המשולש של 2, רק בשביל לוודא שהאלגוריתם עובד.
תוכל למצוא את הפונקצייה
Pow
בחבילה math/cmplx.
package main
import "fmt"
func Cbrt(x complex128) complex128 {
}
func main() {
fmt.Println(Cbrt(2))
}
לGo אין מחלקות. למרות זאת, תוכל להגדיר מתודות בעבור מבנים (Structs).
בעל המתודה מופיע ברשימת ארגומנטים משלו בין מילת המפתח func לשם המתודה.
package main
import (
"fmt"
"math"
)
type Vertex struct {
X, Y float64
}
func (v *Vertex) Abs() float64 {
return math.Sqrt(v.X*v.X + v.Y*v.Y)
}
func main() {
v := &Vertex{3, 4}
fmt.Println(v.Abs())
}
למעשה, ניתן להגדיר מתודה על כל סוג שתרצה בחבילה שלך, לא רק בעבור מבנים.
לא ניתן להגדיר מתודה בעבור סוג מחבילה חיצונית, או בעבור סוגים בסיסיים.
package main
import (
"fmt"
"math"
)
type MyFloat float64
func (f MyFloat) Abs() float64 {
if f < 0 {
return float64(-f)
}
return float64(f)
}
func main() {
f := MyFloat(-math.Sqrt2)
fmt.Println(f.Abs())
}
מתודות יכולות להיות משוייכות לסוג באמצעות שם או באמצעות מצביע לשם סוג.
הרגע ראינו שתי מתודות Abs.
אחת על סוג המצביע *Vertex והשניה על סוג הערך MyFloat.
ישנן 2 סיבות להשתמש בסוג מצביע. קודם כל, בכדי להמנע מלהעתיק את הערך בעבור כל קריאה למתודה (יותר יעיל במצב שסוג הערך הוא מבנה גדול כלשהו). שנית, שהמתודה תוכל לשנות את הערך שאליו הסוג מצביע.
נסה לשנות את ההגדרות של המתודות Abs וScale כך שישתמשו בVertex בתור הסוג המקבל, במקום ב *Vertex.
למתודה Scale אין שום השפעה כאשר v הוא Vertex.
Scale משנה את v.
כאשר v הוא ערך (לא מצביע), המתודה רואה עותק של הVertex ולא יכולה לשנות את הערך של המקורי.
Abs תעבוד בכל מצב. היא רק קוראת את הערך של v.
זה לא משנה אם היא קוראת את הערך המקורי (דרך מצביע) או עותק של הערך הזה.
package main
import (
"fmt"
"math"
)
type Vertex struct {
X, Y float64
}
func (v *Vertex) Scale(f float64) {
v.X = v.X * f
v.Y = v.Y * f
}
func (v *Vertex) Abs() float64 {
return math.Sqrt(v.X*v.X + v.Y*v.Y)
}
func main() {
v := &Vertex{3, 4}
v.Scale(5)
fmt.Println(v, v.Abs())
}
ממשק מוגדר ע"י סט של מתודות.
הערך של ממשק יכול להכיל כל ערך שמממש את המתודות האלה.
package main
import (
"fmt"
"math"
)
type Abser interface {
Abs() float64
}
func main() {
var a Abser
f := MyFloat(-math.Sqrt2)
v := Vertex{3, 4}
a = f // a MyFloat מממש את Abser
a = &v // a *Vertex מממש את Abser
a = v // a Vertex, לא ממש את
// Abser
fmt.Println(a.Abs())
}
type MyFloat float64
func (f MyFloat) Abs() float64 {
if f < 0 {
return float64(-f)
}
return float64(f)
}
type Vertex struct {
X, Y float64
}
func (v *Vertex) Abs() float64 {
return math.Sqrt(v.X*v.X + v.Y*v.Y)
}
בעבור סוג כלשהו אנו נממש ממשק באמצעות מימוש של המתודות בסוג המדובר.
אין שום הגדרה ממשית בעבור הכוונה ליצירת ממשק.
ממשקים אשר מוגדרים מראש מנתקים את החבילה שבה מתבצע המימוש מהחבילה שמגדירה את הממשק: שתיהן לא מסתמכות אחת על השניה.
מצב זה גם מעודד הגדרה מדוייקת יותר של ממשקים, מכיוון שכך לא תצטרך למצוא כל מימוש ולתייג אותו עם שם ממשק חדש.
החבילה io מגדירה Reader וגם Writer; אתה לא חייב להגדיר אותם.
package main
import (
"fmt"
"os"
)
type Reader interface {
Read(b []byte) (n int, err error)
}
type Writer interface {
Write(b []byte) (n int, err error)
}
type ReadWriter interface {
Reader
Writer
}
func main() {
var w Writer
// os.Stdout מממש את Writer
w = os.Stdout
fmt.Fprintf(w, "hello, writer\n")
}
שגיאה היא כל דבר שיכול לתאר את עצמו באמצעות מחרוזת שגיאה.
הרעיון נתפס באמצעות הסוג המוגדר-מראש והמובנה error עם המתודה הבודדת שלו, Error, שמחזירה מחרוזת:
type error interface {
Error() string
}
תהליכי ההדפסה השונים של החבילה fmt יודעים באופן אוטומטי לקרוא למתודה הנחוצה כאשר היא מתבקשת להדפיס שגיאת error.
package main
import (
"fmt"
"time"
)
type MyError struct {
When time.Time
What string
}
func (e *MyError) Error() string {
return fmt.Sprintf("at %v, %s",
e.When, e.What)
}
func run() error {
return &MyError{
time.Now(),
"זה לא עבד!",
}
}
func main() {
if err := run(); err != nil {
fmt.Println(err)
}
}
החבילה http מגישה בקשות HTTP באמצעות כל ערך שמממש את http.Handler:
package http
type Handler interface {
ServeHTTP(w ResponseWriter, r *Request)
}
בדוגמא זו, הסוג Hello מממש את http.Handler.
בקר ב http://localhost:4000/ בכדי לראות את הברכה. הערה: הדוגמא הנ"ל לא תעבוד כאשר נריץ אותה דרך המדריך באינטרנט. אם תרצו לכתוב שרתי אינטרנט, אולי תרצו להתקין את Go.
package main
import (
"fmt"
"net/http"
)
type Hello struct{}
func (h Hello) ServeHTTP(
w http.ResponseWriter,
r *http.Request) {
fmt.Fprint(w, "שלום!")
}
func main() {
var h Hello
http.ListenAndServe("localhost:4000",h)
}
החבילה image מגדירה את הממשק Image:
package image
type Image interface {
ColorModel() color.Model
Bounds() Rectangle
At(x, y int) color.Color
}
(ראה את הדוקומנטציה בשביל שאר הפרטים בנושא.)
כמו כן, color.Color ו color.Model הם ממשקים,
אבל אנחנו נתעלם מזה באמצעות שימוש במימושים המוגדרים מראש color.RGBA ו color.RGBAModel.
package main
import (
"fmt"
"image"
)
func main() {
m := image.NewRGBA(image.Rect(0, 0, 100, 100))
fmt.Println(m.Bounds())
fmt.Println(m.At(0, 0).RGBA())
}
העתק את פונקצית הSqrt שכתבת בתרגילים הקודמים ושנה אותה כך שתחזיר ערך error.
Sqrt צריכה להחזיר ערך error שהוא לא nil כאשר היא מקבלת מספר שלילי, מכיוון שהיא לא תומכת במספרים מורכבים.
צור סוג חדש
type ErrNegativeSqrt float64
והפוך אותו לסוג error באמצעות יצירת מתודת
func (e ErrNegativeSqrt) Error() string
כך שקריאה ל ErrNegativeSqrt(-2).Error() תחזיר
"לא יכול להשתמש בSqrt על מספר שלילי: -2".
הערה: קריאה ל fmt.Print(e) בתוך מתודת השגיאה
Error תכניס את התוכנית ללולאה אינסופית.
נוכל להמנע לזה ע"י כך שנמיר את e קודם לכן:
fmt.Print(float64(e)). מדוע?
שנה את פונקציית הSqrt שלך להחזיר ערך ErrNegativeSqrt כאשר מתקבל ערך שלילי.
package main
import (
"fmt"
)
func Sqrt(f float64) (float64, error) {
return 0, nil
}
func main() {
fmt.Println(Sqrt(2))
fmt.Println(Sqrt(-2))
}
ממש את הסוגים הבאים והגדר בהם את המתודות של ServeHTTP. רשום אותם כך שיוכלו לטפל בנתיבים ספציפיים בשרת האינטרנט שלך.
type String string
type Struct struct {
Greeting string
Punct string
Who string
}
לדוגמא, צריכה להיות לך את היכולת לרשום Handlers באמצעות:
http.Handle("/string", String("I'm a frayed knot."))
http.Handle("/struct", &Struct{"Hello", ":", "Gophers!"})
package main
import (
"net/http"
)
func main() {
// שלך יבואו פה http.Handle קריאות ה
http.ListenAndServe("localhost:4000", nil)
}
זוכר את מחולל התמונות שכתבת מקודם?
בואו ונכתוב עוד אחד, אבל הפעם הוא יחזיר מימוש של image.Image במקום פרוסה של נתונים.
הגדר סוג Image משלך, ממש
את המתודות הנחוצות,
וקרא לpic.ShowImage.
Bounds צריכה להחזיר ערך image.Rectangle, כמו
image.Rect(0, 0, w, h).
ColorModel צריך להחזיר color.RGBAModel.
At צריך להחזיר צבע;
הערך v במחולל התמונות הקודם מתייחס ל
color.RGBA{v, v, 255, 255} בדוגמא הזו.
package main import ( "image" "tourcode.google.com/p/go-tour/pic" ) type Image struct{} func main() { m := Image{} pic.ShowImage(m) }
תבנית נפוצה היא
io.Reader שעוטף
io.Reader נוסף, ומשנה את הStream בצורה כלשהי.
לדוגמא, הפונקציה
gzip.NewReader
מקבלת io.Reader (Stream של נתונים המכווצים בgzip
ומחזירה
*gzip.Reader שגם מממשת
io.Reader (Stream של הנתונים לאחר החילוץ).
ממש rot13Reader שמממש את
io.Reader וקורא מ io.Reader,
תוך כדי שינוי של הStream באמצעות החלה של צופ ההחלפה
ROT13
על כל האותיות האלפבתיות.
הסוג rot13Reader מסופק בעבורך.
הופך אותו ל io.Reader באמצעות מימוש של מתודת הRead שלו.
package main
import (
"io"
"os"
"strings"
)
type rot13Reader struct {
r io.Reader
}
func main() {
s := strings.NewReader(
"Lbh penpxrq gur pbqr!")
r := rot13Reader{s}
io.Copy(os.Stdout, &r)
}
goroutine הוא ת'רד קליל שמנוהל על ידי שכבת זמן-הריצה (Runtime) של Go.
go f(x, y, z)
מתחילה הרצה של goroutine חדש
f(x, y, z)
החישוב של
f, x, y, ו z
מתרחשים בgoroutine הנוכחי, וההרצה של f
מתבצעת בgoroutine חדש.
Goroutines רצים באותו מרחב כתובות בזכרון, ולכן גישה לזכרון המשותף חייבת להיות מסונכרנת.
החבילה
sync
מספקת כלים שימושיים.
למרות שלא תצטרכו אותם במיוחד בGo, מכיוון שישנם כלים אחרים.
(ראה את השקופית הבאה.)
package main import ( "fmt" "runtimetime" ) func say(s string) { for i := 0; i < 5; i++ { runtime.Gosched()time.Sleep(100 * time.Millisecond) fmt.Println(s) } } func main() { go say("world") say("hello") }
ערוצים הם סוג של מוליך בעל סוג שדרכו ניתן לשלוח ולקבל ערכים עם אופרטור הערוץ, <-
ch <- v // ch לערוץ v שולח את
v := <-ch // ch מקבל ערך מ
// v ושומר את הערך ב.
(הנתונים זורמים בכיוון החץ.)
כמו מפות ופרוסות, נהיה חייבים ליצור ערוץ לפני השימוש:
ch := make(chan int)
כברירת מחדל, שליחות וקבלות יוצרים חסימה עד שהצד השני מוכן. מצב זה מאפשר לgoroutines להסתנכרן בלי נעילות מפורשות או תנאים ומשתנים.
package main
import "fmt"
func sum(a []int, c chan int) {
sum := 0
for _, v := range a {
sum += v
}
c <- sum // c שולח את הסכום אל
}
func main() {
a := []int{7, 2, 8, -9, 4, 0}
c := make(chan int)
go sum(a[:len(a)/2], c)
go sum(a[len(a)/2:], c)
x, y := <-c, <-c // c מקבלים ערך מ
fmt.Println(x, y, x + y)
}
ניתן לאגור ערוצים. ספק את אורך האוגרן (buffer size) ל make בשביל לאתחל ערוץ נאגר:
ch := make(chan int, 100)
שליחות לערוצים נאגרים יוצרים חסימה רק כאשר האוגר מלא. קבלת נתונים מערוצים יוצרת חסימה כאשר האוגר ריק.
שנה את הדוגמא כך שנמלא את האוגר יתר על המידה וראה מה קורה.
package main
import "fmt"
func main() {
c := make(chan int, 2)
c <- 1
c <- 2
fmt.Println(<-c)
fmt.Println(<-c)
}
שולח יכול לסגור (close) ערוץ בכדי לסמן שערכים נוספים לא ישלחו.
מקבלים יכולים לבדוק האם ערוץ נסגר או לא באמצעות הוספה של פרמטר שני לביטוי הקבלה.
לאחר:
v, ok := <-ch
ok יהיה false במידה ואין ערכים נוספים לקבל והערוץ סגור.
הלולאה for i := range c מקבלת ערכים מהערוץ בהמשכיות עד שהוא נסגר.
הערה: רק השולח אמור לסגור ערוץ, אף פעם לא המקבל. שליחה לערוץ סגור תגרום לשגיאת panic.
הערה נוספת: ערוצים הם לא כמו קבצים; אתה לא באמת צריך לסגור אותם.
סגירה הכרחית אך ורק כאשר אנו רוצים לומר לצד המקבל שאין עוד ערכים שהוא צריך לצפות לכבל, כמו בשביל לוודא שלולאת range תסתיים.
package main
import (
"fmt"
)
func fibonacci(n int, c chan int) {
x, y := 0, 1
for i := 0; i < n; i++ {
c <- x
x, y = y, x + y
}
close(c)
}
func main() {
c := make(chan int, 10)
go fibonacci(cap(c), c)
for i := range c {
fmt.Println(i)
}
}
פקודת ה select נותנת לgoroutine לחכות
לפעולות תקשורת מרובות.
select יוצרת חסימה עד שאחד המצבים שלו יכולים לרוץ, ובמצב זה היא תריץ את המצב הזה.
הפקודה תבחר בצורה אקראית אם מצבים מרובים מוכנים להרצה.
package main
import "fmt"
func fibonacci(c, quit chan int) {
x, y := 0, 1
for {
select {
case c <- x:
x, y = y, x + y
case <-quit:
fmt.Println("quit")
return
}
}
}
func main() {
c := make(chan int)
quit := make(chan int)
go func() {
for i := 0; i < 10; i++ {
fmt.Println(<-c)
}
quit <- 0
}()
fibonacci(c, quit)
}
המצב default בפקודת select תרוץ כאשר אף אחד מהמצבים האחרים מוכן.
השתמש בdefault בכדי לנסות ולשלוח או לקבל בלי לחסום.
select {
case i := <-c:
// i השתמש ב
default:
// תיצור חסימה c קבלה מ
}
הערה: הדוגמא הזו לא תרוץ במדריך האינטרנטי מכיוון שלסביבת ההרצה אין שום קונספט של זמן. אולי תרצה להתקין את Go בכדי לראות את הקוד הזה בפעולה.
package main
import (
"fmt"
"time"
)
func main() {
tick := time.Tick(1e8)
boom := time.After(5e8)
for {
select {
case <-tick:
fmt.Println("tick.")
case <-boom:
fmt.Println("BOOM!")
return
default:
fmt.Println(" .")
time.Sleep(5e7)
}
}
}
יכולים להיות עצים בינארים שונים רבים עם אותתו רצף ערכים מאוחסן בעלים.
לדוגמא, הנה שני עצים בינאריים שמאחסנים את הערכים
1, 1, 2, 3, 5, 8, 13.
פונקציה שתבדוק האם שני עצים בינארים מאחסנים את אותו הרצף היא דיי מסובכת ברוב השפות. אנו נשתמש במקביליות של Go ובערוצים בכדי לכתוב פתרון פשוט.
הדוגמא משתמשת בחבילה tree, שמגדירה את הסוג:
type Tree struct {
Left *Tree
Value int
Right *Tree
}
1. ממש את הפונקציה Walk.
2. בדוק את הפונקציה Walk.
הפונקציה tree.New(k) יוצרת עץ בינארי בנוי-רנדומאלית שמאחסן את הערכים
k, 2k, 3k, ...,
10k.
צור ערוץ חדש ch ותריץ את הwalker:
go Walk(tree.New(1), ch)
לאחר מכן קרא אותו והדפס את 10 הערכים מהערוץ. הערכים צריכים להיות המספרים 1, 2, 3, ..., 10.
3. ממש את הפונקציה Same באמצעות Walk
בכדי לבדוק האם t1 וt2 מאחסנים את אותם הערכים.
4. בדוק את הפונקציה Same.
Same(tree.New(1), tree.New(1)) אמורה להחזיר true, -
Same(tree.New(1), tree.New(2)) אמורה להחזיר false.
package main import "tourcode.google.com/p/go-tour/tree" // צועדת בתוך העץ ושולחת את הערכים Walk // ch מהעץ הבינארי אל הערוץ func Walk(t *tree.Tree, ch chan int) // בודקת האם העצים Same // t1 ו t2 מכילים את אותם ערכים func Same(t1, t2 *tree.Tree) bool func main() { }
בתרגיל הזה תשתמש במקביליות של Go בכדי ליצור Web Crawler מקבילי.
ערוך את הפונקציה Crawl כך שתמשוך קישורים (URLs) במקביל בלי למשוך את אותו הקישור פעמיים.
package main
import (
"fmt"
)
type Fetcher interface {
// מחזיר את הגוף של הלינק Fetch
// ופרוסה של הלינקים שנמצאים בעמוד
Fetch(url string) (body string, urls []string, err error)
}
// Crawl משתמש בfetcher בכדי לבצע crawling באופן רקורסיבי
// החל מעמוד כלשהו, עד רמת עומק מסויימת
func Crawl(url string, depth int, fetcher Fetcher) {
// TODO: משוך לינקים במקביל.
// TODO: אל תמשוך את אותו הלינק פעמיים.
// המימוש הנ"ל לא מבצע אף אחת משני הדברים האלו
if depth <= 0 {
return
}
body, urls, err := fetcher.Fetch(url)
if err != nil {
fmt.Println(err)
return
}
fmt.Printf("found: %s %q\n", url, body)
for _, u := range urls {
Crawl(u, depth-1, fetcher)
}
return
}
func main() {
Crawl("http://golang.org/", 4, fetcher)
}
// fakeFetcher is Fetcher that returns canned results.
type fakeFetcher map[string]*fakeResult
type fakeResult struct {
body string
urls []string
}
func (f *fakeFetcher) Fetch(url string) (string, []string, error) {
if res, ok := (*f)[url]; ok {
return res.body, res.urls, nil
}
return "", nil, fmt.Errorf("not found: %s", url)
}
// fetcher is a populated fakeFetcher.
var fetcher = &fakeFetcher{
"http://golang.org/": &fakeResult{
"The Go Programming Language",
[]string{
"http://golang.org/pkg/",
"http://golang.org/cmd/",
},
},
"http://golang.org/pkg/": &fakeResult{
"Packages",
[]string{
"http://golang.org/",
"http://golang.org/cmd/",
"http://golang.org/pkg/fmt/",
"http://golang.org/pkg/os/",
},
},
"http://golang.org/pkg/fmt/": &fakeResult{
"Package fmt",
[]string{
"http://golang.org/",
"http://golang.org/pkg/",
},
},
"http://golang.org/pkg/os/": &fakeResult{
"Package os",
[]string{
"http://golang.org/",
"http://golang.org/pkg/",
},
},
}
אתה יכול להתחיל ע"י כך ש: תתקין את Go או שתוריד את ה Go App Engine SDK.
ברגע שיש לך את Go מותקנת על המחשב, הדוקומנטציה היא מקום נהדר להתחיל בו המשך התחל. היא מכלה רפרנסים, שיעורים, וידיואים, ועוד.
אם אתה צריך עזרה עם הספרייה הסטנדרטית, הסתכל על package reference. בעבור עזרה עם השפה עצמה, אולי תהיה מופתע לגלות ש מפרט השפה דיי קריא.
למחקר מעמיק יותר על מודל המקביליות של Go, קרא את Share Memory by Communicating.
המאמר First Class Functions in Go נותן פרספקטיבה מעניינת על סוגי הפונקציות בGo.
ה בלוג של Go מכיל ארכיון גדול של מידע ומסמכים בנוגע לGo.
בקר ב golang.org למידע נוסף.