در این دوره زبان Go را از اولین برنامه تا همروندی با goroutine و channel یاد میگیرید. هر برنامه را در مرورگر مینویسید و همان لحظه اجرا و بررسی میشود.
package main
import (
"fmt"
"sync"
)
// isPrime عدد را با تقسیم تا ریشهٔ دومش بررسی میکند
func isPrime(n int) bool {
if n < 2 {
return false
}
for d := 2; d*d <= n; d++ {
if n%d == 0 {
return false
}
}
return true
}
func main() {
const limit, parts = 200000, 4
size := limit / parts
counts := make([]int, parts) // هر goroutine فقط در خانهٔ خودش مینویسد
var wg sync.WaitGroup
for p := 0; p < parts; p++ {
wg.Add(1)
go func(p int) {
defer wg.Done()
for n := p*size + 1; n <= (p+1)*size; n++ {
if isPrime(n) {
counts[p]++
}
}
}(p)
}
wg.Wait() // صبر تا هر چهار بخش تمام شود
total := 0
for p, c := range counts {
fmt.Printf("part %d: %6d..%-6d %5d primes\n", p+1, p*size+1, (p+1)*size, c)
total += c
}
fmt.Printf("total: %d primes up to %d\n", total, limit)
}
part 1: 1..50000 5133 primes part 2: 50001..100000 4459 primes part 3: 100001..150000 4256 primes part 4: 150001..200000 4136 primes total: 17984 primes up to 200000
بازهٔ ۱ تا ۲۰۰ هزار به چهار بخش تقسیم میشود و هر بخش را یک goroutine همزمان بررسی میکند. هر goroutine فقط در خانهٔ خودش مینویسد، پس خروجی هر بار یکسان است. نمونههای دیگر
این شش برنامه را برای همین صفحه نوشتهایم. هر کدام با همان Go درسها اجرا شده و خروجی کنارش دقیقاً همان چیزی است که چاپ میکند.
بازهٔ ۱ تا ۲۰۰ هزار به چهار بخش تقسیم میشود و هر بخش را یک goroutine همزمان بررسی میکند. هر goroutine فقط در خانهٔ خودش مینویسد، پس خروجی هر بار یکسان است.
package main
import (
"fmt"
"sync"
)
// isPrime عدد را با تقسیم تا ریشهٔ دومش بررسی میکند
func isPrime(n int) bool {
if n < 2 {
return false
}
for d := 2; d*d <= n; d++ {
if n%d == 0 {
return false
}
}
return true
}
func main() {
const limit, parts = 200000, 4
size := limit / parts
counts := make([]int, parts) // هر goroutine فقط در خانهٔ خودش مینویسد
var wg sync.WaitGroup
for p := 0; p < parts; p++ {
wg.Add(1)
go func(p int) {
defer wg.Done()
for n := p*size + 1; n <= (p+1)*size; n++ {
if isPrime(n) {
counts[p]++
}
}
}(p)
}
wg.Wait() // صبر تا هر چهار بخش تمام شود
total := 0
for p, c := range counts {
fmt.Printf("part %d: %6d..%-6d %5d primes\n", p+1, p*size+1, (p+1)*size, c)
total += c
}
fmt.Printf("total: %d primes up to %d\n", total, limit)
}
part 1: 1..50000 5133 primes part 2: 50001..100000 4459 primes part 3: 100001..150000 4256 primes part 4: 150001..200000 4136 primes total: 17984 primes up to 200000
سبد خرید یک استراکت با متد است. دو نوع تخفیف، درصدی و مبلغ ثابت، هر دو اینترفیس Discount را پیاده میکنند، بیآنکه جایی این را اعلام کنند.
package main
import "fmt"
type Item struct {
Name string
Price int // تومان
Qty int
}
type Cart struct{ Items []Item }
// گیرندهٔ اشارهگری: Add خود سبد را تغییر میدهد
func (c *Cart) Add(name string, price, qty int) {
c.Items = append(c.Items, Item{name, price, qty})
}
func (c Cart) Total() int {
sum := 0
for _, it := range c.Items {
sum += it.Price * it.Qty
}
return sum
}
// هر نوعی که این دو متد را داشته باشد، خودبهخود یک Discount است
type Discount interface {
Apply(total int) int
String() string
}
type Percent int
type Fixed int
func (p Percent) Apply(t int) int { return t - t*int(p)/100 }
func (p Percent) String() string { return fmt.Sprintf("%d%% off", int(p)) }
func (f Fixed) Apply(t int) int { return t - int(f) }
func (f Fixed) String() string { return fmt.Sprintf("%d off", int(f)) }
func main() {
var cart Cart
cart.Add("Keyboard", 850000, 1)
cart.Add("Mouse", 320000, 2)
cart.Add("Cable", 90000, 3)
for _, it := range cart.Items {
fmt.Printf("%-10s x%d %10d\n", it.Name, it.Qty, it.Price*it.Qty)
}
fmt.Printf("%-13s %10d\n", "total", cart.Total())
for _, d := range []Discount{Percent(10), Fixed(200000)} {
fmt.Printf("%-13s %10d\n", d, d.Apply(cart.Total()))
}
}
Keyboard x1 850000 Mouse x2 640000 Cable x3 270000 total 1760000 10% off 1584000 200000 off 1560000
تابع برداشت بهجای پرتاب استثنا، خطا را مثل یک مقدار برمیگرداند. برنامه با errors.Is و errors.As میفهمد کدام خطا رخ داده و پیام مناسب را چاپ میکند.
package main
import (
"errors"
"fmt"
)
// خطای از پیش تعریفشده که با errors.Is شناخته میشود
var ErrNotEnough = errors.New("not enough balance")
// نوع خطای سفارشی که با errors.As باز میشود
type LimitError struct{ Limit, Asked int }
func (e *LimitError) Error() string {
return fmt.Sprintf("daily limit is %d, asked %d", e.Limit, e.Asked)
}
func withdraw(balance, amount int) (int, error) {
if amount > 500 {
return balance, &LimitError{Limit: 500, Asked: amount}
}
if amount > balance {
return balance, fmt.Errorf("withdraw %d: %w", amount, ErrNotEnough)
}
return balance - amount, nil
}
func main() {
balance := 800
for _, amount := range []int{300, 700, 450, 200} {
next, err := withdraw(balance, amount)
var limit *LimitError
switch {
case err == nil:
balance = next
fmt.Printf("ok -%d, balance %d\n", amount, balance)
case errors.Is(err, ErrNotEnough):
fmt.Println("refused ", err)
case errors.As(err, &limit):
fmt.Printf("refused %d over the daily limit\n", limit.Asked-limit.Limit)
}
}
}
ok -300, balance 500 refused 200 over the daily limit ok -450, balance 50 refused withdraw 200: not enough balance
برنامه متن را از ورودی کلمهبهکلمه میخواند و تکرار هر کلمه را در یک مپ میشمارد. چون ترتیب پیمایش مپ تصادفی است، کلمهها را پیش از چاپ مرتب میکند.
package main
import (
"bufio"
"fmt"
"os"
"sort"
"strings"
)
func main() {
counts := map[string]int{}
scanner := bufio.NewScanner(os.Stdin)
scanner.Split(bufio.ScanWords) // ورودی را کلمهبهکلمه میخواند
for scanner.Scan() {
word := strings.Trim(scanner.Text(), ".،!؟")
counts[word]++
}
// ترتیب پیمایش مپ تصادفی است؛ برای خروجی ثابت، کلیدها را مرتب میکنیم
words := make([]string, 0, len(counts))
for w := range counts {
words = append(words, w)
}
sort.Slice(words, func(i, j int) bool {
a, b := words[i], words[j]
if counts[a] != counts[b] {
return counts[a] > counts[b] // اول کلمهٔ پرتکرارتر
}
return a < b // در تساوی، به ترتیب حروف
})
for _, w := range words[:4] {
fmt.Printf("%d %-5s %s\n", counts[w], strings.Repeat("#", counts[w]), w)
}
}
گو ساده است. کد گو ساده و خوانا است با گو کد کمتری مینویسید و کد ساده را راحت میخوانید کد خوب، کد ساده است
5 ##### کد 4 #### ساده 3 ### است 3 ### گو
فهرست کالاها بهصورت JSON میرسد و به برشی از استراکتها تبدیل میشود. برنامه ارزش انبار و کالاهای تمامشده را حساب میکند و گزارش را دوباره JSON میکند.
package main
import (
"encoding/json"
"fmt"
)
type Product struct {
Name string `json:"name"`
Price int `json:"price"`
Stock int `json:"stock"`
}
type Report struct {
Count int `json:"count"`
TotalValue int `json:"total_value"`
OutOfStock []string `json:"out_of_stock"`
}
func main() {
data := `[
{"name": "Notebook", "price": 45000, "stock": 12},
{"name": "Pen", "price": 15000, "stock": 0},
{"name": "Backpack", "price": 690000, "stock": 3},
{"name": "Ruler", "price": 20000, "stock": 0}
]`
// متن JSON به برشی از استراکت تبدیل میشود؛ نام فیلدها از برچسب json میآید
var products []Product
if err := json.Unmarshal([]byte(data), &products); err != nil {
fmt.Println("bad json:", err)
return
}
report := Report{Count: len(products), OutOfStock: []string{}}
for _, p := range products {
report.TotalValue += p.Price * p.Stock
if p.Stock == 0 {
report.OutOfStock = append(report.OutOfStock, p.Name)
}
}
out, _ := json.MarshalIndent(report, "", " ")
fmt.Println(string(out))
}
{
"count": 4,
"total_value": 2610000,
"out_of_stock": [
"Pen",
"Ruler"
]
}در Go تابع len بایتها را میشمارد، نه حرفها را. هر حرف فارسی در UTF-8 دو بایت است، پس «سلام» ۸ بایت و ۴ حرف دارد.
package main
import (
"fmt"
"unicode/utf8"
)
func main() {
// len بایتها را میشمارد؛ هر حرف فارسی در UTF-8 دو بایت است
for _, s := range []string{"Go", "سلام", "دینا کد"} {
fmt.Printf("%2d bytes, %d runes %s\n", len(s), utf8.RuneCountInString(s), s)
}
// range روی رشته، rune به rune جلو میرود و جای بایتی هر حرف را میدهد
for i, r := range "کد" {
fmt.Printf("byte %d U+%04X %c\n", i, r, r)
}
}
2 bytes, 2 runes Go 8 bytes, 4 runes سلام 13 bytes, 7 runes دینا کد byte 0 U+06A9 ک byte 2 U+062F د
هر فصل یک بخش از زبان را با برنامههای کامل و کوچک تمرین میکند.
با package main و fmt برنامه مینویسید، از ورودی میخوانید و با نوعهای عددی، تبدیل صریح و چاپ دقیق اعشار کار میکنید.
با const و iota مقدارهای ثابت میسازید و برنامه را با if، switch و حلقهٔ for هدایت میکنید.
تابع با چند مقدار بازگشتی، تابع variadic، closure، تابع بازگشتی و defer مینویسید.
با آرایه، اسلایس و مپ داده نگه میدارید، رفتار append و cap را میفهمید و خروجی مپ را با مرتب کردن کلیدها ثابت نگه میدارید.
با اشارهگر مقدار را در جا تغییر میدهید، داده را با استراکت مدل میکنید و متد با گیرندهٔ مقداری یا اشارهگری مینویسید.
اینترفیس تعریف میکنید، با type switch و type assertion نوع واقعی مقدار را میگیرید و با sort.Interface مرتب میکنید.
خطا را با %w بستهبندی و با errors.Is و errors.As بررسی میکنید و جای panic و recover را میشناسید.
با strings، strconv، fmt، sort، bufio، regexp، time و encoding/json کار میکنید و متن فارسی را درست میشمارید.
با goroutine، WaitGroup، channel، select، Mutex و sync.Once کار را موازی میکنید و خروجی را قابل پیشبینی نگه میدارید.
کد را به سبک رایج Go سازمان میدهید و ابزارهایی مثل خلاصهساز لاگ، ماشینحساب RPN، ارزیاب عبارت و یک مفسر کوچک بایتکد میسازید.
| موضوع | Go | پایتون |
|---|---|---|
| اجرا | به یک فایل اجرایی کامپایل میشود. | با مفسر پایتون اجرا میشود. |
| نوع متغیرها | ایستا؛ کامپایلر پیش از اجرا بررسی میکند. | پویا؛ هنگام اجرا معلوم میشود. |
| کار همزمان | goroutine و channel در خود زبان | با کتابخانههایی مثل threading و asyncio |
| مدیریت خطا | خطا مقداری است که تابع برمیگرداند. | استثنا با try و except |
| کاربرد رایج | سرویسهای سمت سرور، ابزارهای خط فرمان و زیرساخت | تحلیل داده، هوش مصنوعی، اسکریپتنویسی و وب |
هر دو زبان برای شروع انتخاب خوبی هستند. انتخاب میان آنها بیشتر به کاری بستگی دارد که میخواهید انجام دهید.
هر درس یک مفهوم را کوتاه توضیح میدهد و یک برنامهٔ کامل از شما میخواهد.
کد شما با Go کامپایل و اجرا میشود و خروجی یا پیام کامپایلر را همانجا میبینید.
دکمهٔ بررسی، برنامه را با چند ورودی اجرا میکند و خروجی را با جواب درست مقایسه میکند. اگر گیر کردید، راهنمای همان تمرین کمکتان میکند.
۲۳ سرفصل، ۱۴۸ درس، ۱۱۴ تمرین
درسهای اول رایگان است و برای شروع پرداختی لازم نیست. هر وقت خواستید ادامه دهید، همین دوره را بخرید یا با اشتراک پرو همهٔ دورهها را باز کنید.
۱۲ درس ابتدایی رایگان است. برای ادامه، اشتراک بگیرید یا دوره را بخرید.
بدون نیاز به پرداخت مبلغی برای شروع
اگر میخواهید سرویسهای سمت سرور سریع و ساده بنویسید.
اگر زبانی کامپایلی با نوع ایستا و همروندی داخلی را به مهارتهایتان اضافه میکنید.
اگر ابزار خط فرمان و برنامههای کمکی برای سرور و استقرار مینویسید.
پیشنیاز: یک رایانه و یک مرورگر.
اولین برنامهٔ Go خود را همین امروز در مرورگر اجرا کنید. شروع رایگان است و در کمتر از دو دقیقه وارد محیط یادگیری میشوید.