שלום, 世界

ברוך הבא לסיור המודרך של שפת התכנות Go.

המדריך מחולק לשלושה חלקים. בסוף כל חלק ישנה סדרת תרגילים שתוכלו להשלים.

המדריך אינטרקטיבי. לחצו על כפתור ה"הרץ" עכשיו (או הקישו Shift+Enter) בכדי לקמפל ולהריץ את התוכנית על שרת מרוחק. המחשב שלך. תוצאת התוכנית תוצג מתחת לקוד.

הדוגמאות הנ"ל מציגות אספקטים שונים של Go. התוכניות במדריך הזה מיועדות להוות נקודת התחלה לניסויים האישיים שלך."

ערוך את הקוד והרץ את התוכנית שוב.

כשאתה מוכן להמשיך הלאה, לחץ על כפתור "הבא" או הקש על מקש הPageDown.

package main

import "fmt"

func main() {
	fmt.Println("שלום, 世界")
}

Go בשפה שלך

המדריך זמין בשפות הבאות:

(אם אתה מעוניין לתרגל את המדריך לשפה אחרת, הורד את הקוד מ https://code.google.com/p/go-tour, תרגם את static/index.html, והעלה אותו לApp Engine באמצעות ההוראות ב appengine/README.)

לחץ על כפתור "הבא" או על מקש PageDown בכדי להמשיך.

Go מקומי (Offline)

המדריך הזה זמין גם כתוכנת Stand-alone שניתנת לשימוש ללא חיבור לאינטרנט.

תוכנת הStand-alone מהירה יותר, מכיוון שהיא בונה ומריצה את דוגמאות הקוד ישירות על המחשב שלכם. היא גם כוללת תרגילים נוספים שלא קיימים בגרסאת האונליין.

בכדי להריץ את המדריך בצורה מקומית, קודם כל התקן את Go, ולאחר מכן השתמש ב go get בכדי להתקין את gotour:

go get code.google.com/p/go-tour/gotour

בסוף, הרץ את הקובץ שנוצר - gotour.

במידה ואינך מעוניין בהרצה מקומית, לחץ על כפתור "הבא" או על מקש הPageDown בכדי להמשיך.

(תוכל לחזור להוראות האלו ע"י לחיצה על כפתור האינדקס)

מבוא

חבילות (Packages)

כל תוכנית 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))
}

For

לGo יש רק מבנה לולאה אחת, לולאת for.

לולאת הfor הבסיסית נראית כמו שהיא נראית בC או בJava, חוץ מזה שנפטרנו מהסוגריים ( ) ושהסוגריים המסולסלות { } הן חובה.

package main

import "fmt"

func main() {
	sum := 0
	for i := 0; i < 10; i++ {
		sum += i
	}
	fmt.Println(sum)
}

For

כמו בC או Java, ניתן להשאיר את החלק המקדים או העוקב ריקים.

package main

import "fmt"

func main() {
	sum := 1
	for ; sum < 1000; {
		sum += sum
	}
	fmt.Println(sum)
}

For

בנקודה הזו ניתן לוותר על הנקודה-פסיק: while של C ממומשת באמצעות for בGo.

package main

import "fmt"

func main() {
	sum := 1
	for sum < 1000 {
		sum += sum
	}
	fmt.Println(sum)
}

For

אם נשמיט את תנאי הלולאה, היא תמשיך לנצח.

package main

func main() {
	for ; ; {
	}
}

For

וללא תנאים או חלקי-לולאה בכלל, ניתן להשמיט את חלקי הנקודה-פסיק. בצורה זו לולאה אינסופית היא פשוטה וקומפקטית.

package main

func main() {
	for {
	}
}

If

הצהרת 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))
}

If

כמו 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

משתנים שמוגדרים בתוך הגדרת 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})
}

Struct Fields

ניגש לשדה בתוך מבנה באמצעות נקודה.

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

הביטוי 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)
}

פרוסות (Slices)

פרוסה מצביעה למערך של ערכים וכוללת גם את האורך.

[]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
לפרוסות יש אורך וקיבולת. הקיבולת של פרוסה היא האורך המקסימלי שאליו הפרוסה יכולה לגדול בתוך המערך שתחתיו.

בכדי לציין את הקיבולת, העבר פרמטר שלישי לmake:

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

כנראה שכבר יכלתם לנחש איך מבנה ה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

במבנה 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 ללא תנאי מתפקד באותה צורה כמו 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 ולאחר מכן חוזרת על:

Newton's method

בתור התחלה, חזור על החישוב 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 בעבור שורשים משולשים, השיטה של ניוטון חוזרת על:

Newton's method

מצא את השורש המשולש של 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))
}

תרגול: HTTP Handlers

ממש את הסוגים הבאים והגדר בהם את המתודות של 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)
}

תרגול: קורא Rot13

תבנית נפוצה היא 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)
}
מקביליות (Concurrency)

מקביליות (Concurrency)

Goroutines

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)
}

ערוצים נאגרים (Buffered)

ניתן לאגור ערוצים. ספק את אורך האוגרן (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 ו Range

שולח יכול לסגור (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

פקודת ה 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)
}

Select עם ברירת מחדל

המצב 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. binary trees

פונקציה שתבדוק האם שני עצים בינארים מאחסנים את אותו הרצף היא דיי מסובכת ברוב השפות. אנו נשתמש במקביליות של 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() {
}

תרגול: Web Crawler

בתרגיל הזה תשתמש במקביליות של 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 למידע נוסף.