Goku

مفاهیم پایه و اولیه در زبان برنامه‌نویسی Go: متغیرها و موارد مرتبط

توضیح کلی: این جزوه به بررسی مفاهیم پایه و اولیه زبان Go با تمرکز بر متغیرها، انواع داده‌ها، ثابت‌ها، و سایر موارد مرتبط می‌پردازد. Go یک زبان برنامه‌نویسی مدرن است که توسط گوگل توسعه یافته و به دلیل سادگی، عملکرد بالا، و پشتیبانی قوی از همزمانی (concurrency) محبوبیت زیادی پیدا کرده است. در این جزوه، هر بخش با توضیحات کامل، مثال‌های عملی، و نکات تخصصی ارائه می‌شود تا یادگیری عمیق و کاربردی باشد.


1. مقدمه‌ای بر زبان Go

1.1. ویژگی‌های کلیدی Go

توضیح: Go برای حل مشکلات برنامه‌نویسی در مقیاس بزرگ طراحی شده است. ویژگی‌های کلیدی آن شامل موارد زیر است:

مثال:

package main

import "fmt"

func main() {
    fmt.Println("Hello, Go!")
}

توضیح خط‌به‌خط:

چرا مهم است؟: این ساختار ساده نشان‌دهنده فلسفه Go است: حداقل پیچیدگی با حداکثر کارایی.


2. متغیرها در Go

2.1. تعریف متغیر

توضیح: متغیرها در Go برای ذخیره داده‌ها استفاده می‌شوند. برخلاف برخی زبان‌ها که متغیرها می‌توانند بدون تعریف استفاده شوند، Go یک زبان تایپ استاتیک است و متغیرها باید قبل از استفاده تعریف شوند. Go چند روش برای تعریف متغیرها ارائه می‌دهد که هر کدام کاربرد خاص خود را دارند.

روش‌های تعریف متغیر

  1. با استفاده از کلمه کلیدی var و نوع صریح:
    var name string = "Ali"
    var age int = 30
    

    توضیح:

    • var: کلمه کلیدی برای تعریف متغیر.
    • name, age: نام‌های متغیر که باید معنادار باشند.
    • string, int: نوع داده متغیر که باید صراحتاً مشخص شود.
    • = "Ali", = 30: مقدار اولیه متغیر.
    • این روش برای تعریف متغیرهایی با نوع مشخص و در هر دامنه‌ای (محلی یا بسته) مناسب است.
  2. بدون مقدار اولیه (مقدار پیش‌فرض):
    var name string // مقدار پیش‌فرض: ""
    var age int     // مقدار پیش‌فرض: 0
    var isActive bool // مقدار پیش‌فرض: false
    

    توضیح:

    • اگر مقدار اولیه مشخص نشود، Go مقدار پیش‌فرض (zero value) را اختصاص می‌دهد.
    • مقادیر پیش‌فرض:
      • اعداد: 0
      • رشته‌ها: "" (رشته خالی)
      • بولین: false
      • اشاره‌گرها، اسلایس‌ها، مپ‌ها، کانال‌ها، رابط‌ها: nil
    • این روش زمانی مفید است که نمی‌خواهید مقدار اولیه را بلافاصله تنظیم کنید.
  3. استنتاج نوع با استفاده از :=:
    name := "Ali" // استنتاج نوع به string
    age := 30     // استنتاج نوع به int
    

    توضیح:

    • := برای تعریف متغیر با استنتاج نوع استفاده می‌شود.
    • کامپایلر نوع متغیر را از مقدار اولیه تشخیص می‌دهد (مثلاً "Ali" به string).
    • این روش فقط در داخل توابع قابل استفاده است و کد را مختصرتر می‌کند.
    • نکته: نمی‌توان از := برای تعریف متغیر در سطح بسته استفاده کرد.
  4. تعریف چند متغیر به صورت همزمان:
    var (
        name    string = "Ali"
        age     int    = 30
        isAdmin bool   = true
    )
    

    یا:

    var x, y, z int = 1, 2, 3
    

    توضیح:

    • بلاک var برای تعریف چندین متغیر به صورت خوانا استفاده می‌شود.
    • روش دوم برای متغیرهای هم‌نوع مناسب است.
    • این روش برای متغیرهای مرتبط یا تنظیمات اولیه پروژه مفید است.

2.2. قوانین نام‌گذاری متغیرها

توضیح: نام‌گذاری متغیرها در Go باید از قوانین و کنوانسیون‌های خاصی پیروی کند تا کد خوانا و استاندارد باشد.

چرا مهم است؟: نام‌گذاری صحیح خوانایی کد را افزایش می‌دهد و از خطاهای توسعه جلوگیری می‌کند.

2.3. دامنه (Scope) متغیرها

توضیح: دامنه متغیر مشخص می‌کند که متغیر در کجای برنامه قابل دسترسی است. Go دامنه‌های مختلفی را پشتیبانی می‌کند.

چرا مهم است؟: درک دامنه متغیرها از دسترسی غیرمجاز یا خطاهای منطقی جلوگیری می‌کند.

2.4. خطاهای رایج در تعریف متغیر

توضیح: برخی اشتباهات رایج در تعریف متغیرها می‌توانند باعث خطاهای کامپایل یا منطقی شوند.


3. انواع داده‌ها در Go

توضیح: Go یک زبان تایپ استاتیک است، یعنی نوع هر متغیر در زمان کامپایل مشخص می‌شود. این ویژگی خطاهای نوع را کاهش می‌دهد. Go انواع داده‌های پایه و مرکب را پشتیبانی می‌کند.

3.1. انواع داده‌های پایه

توضیح: انواع پایه شامل اعداد، رشته‌ها، و بولین‌ها هستند که برای عملیات اولیه استفاده می‌شوند.

  1. عددی:
    • اعداد صحیح (Integers):
      var i int = 42        // پلتفرم-وابسته (32 یا 64 بیت)
      var i8 int8 = 127     // -128 تا 127
      var i16 int16 = 32767 // -32768 تا 32767
      var i32 int32 = 123   // -2^31 تا 2^31-1
      var i64 int64 = 456   // -2^63 تا 2^63-1
      

      توضیح:

      • int اندازه‌اش به پلتفرم بستگی دارد (32 بیت در سیستم‌های 32 بیتی، 64 بیت در سیستم‌های 64 بیتی).
      • انواع int8, int16, و غیره برای کنترل دقیق اندازه استفاده می‌شوند.
      • برای مقادیر مثبت از انواع بدون علامت استفاده کنید:
        var u uint = 42
        var u8 uint8 = 255
        var u16 uint16 = 65535
        
    • اعداد اعشاری (Floating-Point):
      var f32 float32 = 3.14
      var f64 float64 = 3.14159265359 // دقت بالاتر
      

      توضیح:

      • float32 دقت کمتری دارد و برای کاربردهای ساده مناسب است.
      • float64 دقت بالاتری دارد و برای محاسبات علمی استفاده می‌شود.
      • نکته: محاسبات اعشاری ممکن است خطای گرد کردن داشته باشند:
        fmt.Println(0.1 + 0.2) // 0.30000000000000004
        
    • اعداد مختلط (Complex):
      var c64 complex64 = 1 + 2i
      var c128 complex128 = 3 + 4i
      

      توضیح:

      • برای محاسبات ریاضی پیشرفته (مثل پردازش سیگنال) استفاده می‌شوند.
      • complex64 از float32 و complex128 از float64 تشکیل شده است.
      • عملیات:
        fmt.Println(real(c64), imag(c64)) // 1 2
        
  2. رشته‌ای (String):
    var s string = "Hello, Go!"
    

    توضیح:

    • رشته‌ها در Go مجموعه‌ای از بایت‌ها هستند و immutable (غیرقابل تغییر) هستند.
    • دسترسی به کاراکترها:
      fmt.Println(s[0]) // 72 (بایت 'H')
      
    • برای کار با کاراکترهای یونیکد (مثل emoji) از rune استفاده کنید:
      runes := []rune(s)
      fmt.Println(runes[0]) // 72
      
    • عملیات رشته‌ای:
      s2 := s + " World" // الحاق
      fmt.Println(len(s)) // طول
      
  3. بولین (Boolean):
    var b bool = true
    

    توضیح:

    • فقط دو مقدار: true یا false.
    • عملگرهای منطقی:
      fmt.Println(true && false) // false
      fmt.Println(true || false) // true
      fmt.Println(!true)         // false
      
  4. بایت و رون (Byte and Rune):
    • byte: نام مستعار برای uint8، برای ذخیره بایت‌ها.
      var b byte = 'A' // 65
      
    • rune: نام مستعار برای int32، برای کاراکترهای یونیکد.
      var r rune = '😊' // 128522
      

    توضیح:

    • byte برای داده‌های خام (مثل فایل‌ها) مناسب است.
    • rune برای پردازش متن‌های چندزبانه و یونیکد استفاده می‌شود.
    • مثال:
      s := "سلام"
      runes := []rune(s)
      fmt.Println(len(s), len(runes)) // 6 3 (بایت‌ها vs کاراکترها)
      

3.2. انواع داده‌های مرکب

توضیح: انواع مرکب برای ذخیره داده‌های پیچیده‌تر استفاده می‌شوند.

  1. آرایه (Array):
    var arr [3]int = [3]int{1, 2, 3}
    

    توضیح:

    • آرایه‌ها اندازه ثابت دارند و نوع عناصرشان مشخص است.
    • دسترسی:
      fmt.Println(arr[0]) // 1
      arr[1] = 20        // تغییر
      
    • نکته: آرایه‌ها به دلیل اندازه ثابت کمتر استفاده می‌شوند؛ اسلایس‌ها جایگزین رایج‌تر هستند.
  2. اسلایس (Slice):
    var slice []int = []int{1, 2, 3}
    slice = append(slice, 4) // افزودن
    

    توضیح:

    • اسلایس‌ها مرجعی به بخشی از آرایه هستند و اندازه پویا دارند.
    • با make می‌توان طول و ظرفیت اولیه مشخص کرد:
      s := make([]int, 3, 5) // طول 3، ظرفیت 5
      
    • عملیات:
      s = s[1:3] // برش
      fmt.Println(len(s), cap(s)) // طول و ظرفیت
      
  3. مپ (Map):
    var m map[string]int = map[string]int{
        "Ali": 30,
        "Bob": 25,
    }
    m["Ali"] = 31 // به‌روزرسانی
    delete(m, "Bob") // حذف
    

    توضیح:

    • مپ‌ها مجموعه‌ای از جفت‌های کلید-مقدار هستند.
    • بررسی وجود کلید:
      value, exists := m["Ali"]
      fmt.Println(value, exists) // 31 true
      
    • نکته: مپ باید با make یا مقدار اولیه ساخته شود:
      m = make(map[string]int)
      
  4. استراکچر (Struct):
    type Person struct {
        Name string
        Age  int
    }
    var p Person = Person{Name: "Ali", Age: 30}
    p.Age = 31 // تغییر
    

    توضیح:

    • Struct برای گروه‌بندی داده‌های مرتبط استفاده می‌شود.
    • دسترسی به فیلدها با .:
      fmt.Println(p.Name) // Ali
      
    • Structها برای مدل‌سازی داده‌های پیچیده (مثل مدل‌های دیتابیس) بسیار مفیدند.
  5. اشاره‌گر (Pointer):
    var x int = 10
    var p *int = &x // اشاره‌گر به x
    *p = 20         // تغییر مقدار x
    fmt.Println(x)  // 20
    

    توضیح:

    • اشاره‌گرها آدرس حافظه یک متغیر را ذخیره می‌کنند.
    • & آدرس را می‌گیرد، * مقدار را تغییر می‌دهد.
    • کاربرد: برای تغییر مستقیم متغیرها در توابع یا صرفه‌جویی در حافظه.
  6. رابط (Interface):
    type Speaker interface {
        Speak() string
    }
    type Dog struct{}
    func (d Dog) Speak() string { return "Woof!" }
    var s Speaker = Dog{}
    fmt.Println(s.Speak()) // Woof!
    

    توضیح:

    • رابط‌ها مجموعه‌ای از متدها را تعریف می‌کنند.
    • هر نوع که این متدها را پیاده‌سازی کند، رابط را ارضا می‌کند.
    • کاربرد: برای انعطاف‌پذیری و تست‌پذیری (مثل mocking).
  7. کانال (Channel):
    ch := make(chan int)
    go func() { ch <- 42 }()
    fmt.Println(<-ch) // 42
    

    توضیح:

    • کانال‌ها برای ارتباط بین goroutine‌ها استفاده می‌شوند.
    • با make ساخته می‌شوند و می‌توانند بافر داشته باشند:
      ch := make(chan int, 10) // بافر 10
      
    • کاربرد: هماهنگی در برنامه‌های همزمان.

3.3. مقدار پیش‌فرض (Zero Value)

توضیح: Go به متغیرهای بدون مقدار اولیه، مقدار پیش‌فرض اختصاص می‌دهد:

مثال:

var s []int
fmt.Println(s == nil) // true

چرا مهم است؟: این رفتار از خطاهای زمان اجرا (مثل null pointer exception) جلوگیری می‌کند.

3.4. نکات پیشرفته در انواع داده‌ها

3.5. خطاهای رایج در انواع داده‌ها


4. ثابت‌ها (Constants)

4.1. تعریف ثابت

توضیح: ثابت‌ها مقادیری هستند که در زمان اجرا تغییر نمی‌کنند و برای مقادیر ثابت مثل تنظیمات یا مقادیر ریاضی استفاده می‌شوند.

const pi float64 = 3.14159
const greeting = "Hello"

توضیح:

4.2. ثابت‌های چندگانه

const (
    Monday    = 1
    Tuesday   = 2
    Wednesday = 3
)

توضیح:

4.3. ثابت‌های iota

توضیح: iota برای تعریف ثابت‌های متوالی استفاده می‌شود و به طور خودکار مقدار را افزایش می‌دهد.

const (
    Read   = 1 << iota // 1
    Write              // 2
    Execute            // 4
)

توضیح:

4.4. نکات پیشرفته در ثابت‌ها

4.5. خطاهای رایج


5. تبدیل نوع (Type Conversion)

توضیح: Go از تبدیل صریح نوع پشتیبانی می‌کند و تبدیل ضمنی (مثل برخی زبان‌ها) وجود ندارد.

var i int = 42
var f float64 = float64(i) // تبدیل به float64
var u uint = uint(f)       // تبدیل به uint

توضیح:

5.1. تبدیل رشته به عدد

s := "123"
i, err := strconv.Atoi(s) // تبدیل به int
if err != nil {
    fmt.Println("Conversion error:", err)
}
f, err := strconv.ParseFloat(s, 64) // تبدیل به float64

توضیح:

5.2. تبدیل عدد به رشته

i := 42
s := strconv.Itoa(i) // "42"
s2 := fmt.Sprintf("%d", i) // "42"

توضیح:

5.3. نکات پیشرفته

5.4. خطاهای رایج


6. متغیرهای پیشرفته و الگوهای استفاده

6.1. Shadowing متغیر

توضیح: Shadowing زمانی رخ می‌دهد که متغیری در دامنه داخلی با همان نام متغیر دامنه خارجی تعریف شود.

func main() {
    x := 10
    if true {
        x := 20 // Shadowing
        fmt.Println(x) // 20
    }
    fmt.Println(x) // 10
}

توضیح:

6.2. متغیرهای موقتی در حلقه‌ها

for i, v := range []int{1, 2, 3} {
    fmt.Println(i, v)
}

توضیح:

6.3. متغیرهای global و همزمانی

var counter int
var mu sync.Mutex

func increment() {
    mu.Lock()
    counter++
    mu.Unlock()
}

توضیح:

6.4. استفاده از متغیرهای بدون نام

for _, v := range []int{1, 2, 3} {
    fmt.Println(v) // نادیده گرفتن اندیس
}

توضیح:

6.5. نکات پیشرفته

6.6. خطاهای رایج


7. بهترین شیوه‌ها در استفاده از متغیرها

توضیح: رعایت بهترین شیوه‌ها باعث خوانایی، نگهداری، و عملکرد بهتر کد می‌شود.

  1. نام‌گذاری معنادار:
    userCount := 100 // خوب
    x := 100         // بد
    

    توضیح:

    • نام‌ها باید هدف متغیر را به‌وضوح نشان دهند.
    • از نام‌های مبهم مثل x, y جز در حلقه‌ها اجتناب کنید.
  2. حداقل کردن دامنه:
    func calculate() int {
        result := 0 // دامنه محلی
        // محاسبات
        return result
    }
    

    توضیح:

    • تعریف متغیر در نزدیک‌ترین دامنه ممکن از نشت و سوءاستفاده جلوگیری می‌کند.
  3. استفاده از استنتاج نوع برای خوانایی:
    name := "Ali" // بهتر از var name string = "Ali"
    

    توضیح:

    • := کد را مختصر و خوانا می‌کند، به‌ویژه در توابع.
  4. اجتناب از متغیرهای global غیرضروری:
    func process(name string) string {
        return "Hello, " + name
    }
    

    توضیح:

    • به جای متغیر global، از پارامترها و مقادیر بازگشتی استفاده کنید.
  5. بررسی مقادیر nil:
    var s *string
    if s == nil {
        fmt.Println("String pointer is nil")
    }
    

    توضیح:

    • بررسی nil برای اشاره‌گرها، اسلایس‌ها، و مپ‌ها از خطاهای زمان اجرا جلوگیری می‌کند.
  6. استفاده از ثابت‌ها برای مقادیر ثابت:
    const maxRetries = 3
    

    توضیح:

    • ثابت‌ها برای مقادیر غیرقابل تغییر خوانایی و ایمنی را افزایش می‌دهند.

8. ابزارها و تکنیک‌های مرتبط

8.1. بررسی متغیرهای استفاده‌نشده

go vet

توضیح:

8.2. فرمت‌بندی کد

go fmt

توضیح:

8.3. پروفایلینگ حافظه

go tool pprof mem.out

توضیح:

8.4. مستندسازی متغیرها

// MaxConnections تعداد حداکثر اتصال‌های همزمان
const MaxConnections = 100

توضیح:


9. خطاهای رایج و راه‌حل‌ها

توضیح: شناسایی و رفع خطاهای رایج در استفاده از متغیرها برای توسعه کد باکیفیت ضروری است.

  1. عدم بررسی خطا در تبدیل نوع:
    // بد
    i, _ := strconv.Atoi("abc")
    // خوب
    i, err := strconv.Atoi("abc")
    if err != nil {
        fmt.Println("Error:", err)
    }
    

    توضیح:

    • نادیده گرفتن خطاها می‌تواند باعث رفتار غیرمنتظره شود.
    • همیشه خطاها را بررسی کنید.
  2. استفاده از اشاره‌گر بدون مقداردهی اولیه:
    // بد
    var p *int
    *p = 10 // خطا: nil pointer dereference
    // خوب
    x := 10
    p = &x
    *p = 20
    

    توضیح:

    • اشاره‌گرهای nil باعث خطاهای زمان اجرا می‌شوند.
    • قبل از استفاده، اشاره‌گر را مقداردهی کنید.
  3. تخصیص بیش از حد متغیرهای موقتی:
    // بد
    temp1 := calculateSomething()
    temp2 := process(temp1)
    return temp2
    // خوب
    return process(calculateSomething())
    

    توضیح:

    • متغیرهای موقتی غیرضروری حافظه و خوانایی را تحت تأثیر قرار می‌دهند.
    • مستقیماً از نتایج توابع استفاده کنید.
  4. Shadowing غیرعمدی:
    // بد
    x := 10
    if true {
        x := 20 // Shadowing
    }
    // خوب
    x := 10
    if true {
        x = 20
    }
    

    توضیح:

    • Shadowing می‌تواند باعث اشتباه در دسترسی به متغیر خارجی شود.
    • از ابزارهای تحلیل کد استفاده کنید.

10. نکات پیشرفته و الگوهای کاربردی

10.1. استفاده از متغیرها در همزمانی

func main() {
    var wg sync.WaitGroup
    counter := 0
    mu := sync.Mutex{}
    for i := 0; i < 10; i++ {
        wg.Add(1)
        go func() {
            defer wg.Done()
            mu.Lock()
            counter++
            mu.Unlock()
        }()
    }
    wg.Wait()
    fmt.Println(counter) // 10
}

توضیح:

10.2. متغیرهای پویا در حلقه‌ها

for i := 0; i < 5; i++ {
    i := i // کپی متغیر
    go func() {
        fmt.Println(i)
    }()
}

توضیح:

10.3. متغیرهای قابل تنظیم در زمان اجرا

var debugMode bool

func init() {
    debugMode = os.Getenv("DEBUG") == "true"
}

func logDebug(msg string) {
    if debugMode {
        fmt.Println("Debug:", msg)
    }
}

توضیح:

10.4. استفاده از متغیرهای exported در API

package mypkg

type Config struct {
    MaxRetries int
}

var DefaultConfig = Config{MaxRetries: 3}

توضیح:


11. منابع یادگیری و انجمن

11.1. منابع یادگیری

توضیح: منابع زیر برای یادگیری عمیق‌تر Go توصیه می‌شوند:

11.2. مشارکت در پروژه‌ها

توضیح:

11.3. نکات پیشرفته

11.4. خطاهای رایج


12. نتیجه‌گیری

توضیح: این جزوه مفاهیم پایه و اولیه زبان Go با تمرکز بر متغیرها و موارد مرتبط را به‌صورت جامع و عمیق پوشش داد. از تعریف متغیرها، انواع داده‌ها، ثابت‌ها، و تبدیل نوع تا دامنه‌ها، الگوهای پیشرفته، و بهترین شیوه‌ها، هر بخش با توضیحات کامل، مثال‌های عملی، و نکات تخصصی ارائه شد. برای یادگیری عمیق‌تر: