توضیح کلی: این جزوه به بررسی مفاهیم پایه و اولیه زبان Go با تمرکز بر متغیرها، انواع دادهها، ثابتها، و سایر موارد مرتبط میپردازد. Go یک زبان برنامهنویسی مدرن است که توسط گوگل توسعه یافته و به دلیل سادگی، عملکرد بالا، و پشتیبانی قوی از همزمانی (concurrency) محبوبیت زیادی پیدا کرده است. در این جزوه، هر بخش با توضیحات کامل، مثالهای عملی، و نکات تخصصی ارائه میشود تا یادگیری عمیق و کاربردی باشد.
توضیح: Go برای حل مشکلات برنامهنویسی در مقیاس بزرگ طراحی شده است. ویژگیهای کلیدی آن شامل موارد زیر است:
go fmt
(برای فرمتبندی کد)، go test
(برای تست)، و go doc
(برای مستندسازی) به توسعهدهندگان کمک میکنند.مثال:
package main
import "fmt"
func main() {
fmt.Println("Hello, Go!")
}
توضیح خطبهخط:
package main
: هر فایل Go باید به یک بسته (package) تعلق داشته باشد. main
بستهای است که نقطه ورود برنامه را مشخص میکند.import "fmt"
: پکیج fmt
برای عملیات ورودی/خروجی (مثل چاپ متن) استفاده میشود.func main()
: تابع اصلی که اجرای برنامه از آن شروع میشود.fmt.Println("Hello, Go!")
: متنی را در کنسول چاپ میکند.چرا مهم است؟: این ساختار ساده نشاندهنده فلسفه Go است: حداقل پیچیدگی با حداکثر کارایی.
توضیح: متغیرها در Go برای ذخیره دادهها استفاده میشوند. برخلاف برخی زبانها که متغیرها میتوانند بدون تعریف استفاده شوند، Go یک زبان تایپ استاتیک است و متغیرها باید قبل از استفاده تعریف شوند. Go چند روش برای تعریف متغیرها ارائه میدهد که هر کدام کاربرد خاص خود را دارند.
var
و نوع صریح:
var name string = "Ali"
var age int = 30
توضیح:
var
: کلمه کلیدی برای تعریف متغیر.name
, age
: نامهای متغیر که باید معنادار باشند.string
, int
: نوع داده متغیر که باید صراحتاً مشخص شود.= "Ali"
, = 30
: مقدار اولیه متغیر.var name string // مقدار پیشفرض: ""
var age int // مقدار پیشفرض: 0
var isActive bool // مقدار پیشفرض: false
توضیح:
0
""
(رشته خالی)false
nil
:=
:
name := "Ali" // استنتاج نوع به string
age := 30 // استنتاج نوع به int
توضیح:
:=
برای تعریف متغیر با استنتاج نوع استفاده میشود."Ali"
به string
).:=
برای تعریف متغیر در سطح بسته استفاده کرد.var (
name string = "Ali"
age int = 30
isAdmin bool = true
)
یا:
var x, y, z int = 1, 2, 3
توضیح:
var
برای تعریف چندین متغیر به صورت خوانا استفاده میشود.توضیح: نامگذاری متغیرها در Go باید از قوانین و کنوانسیونهای خاصی پیروی کند تا کد خوانا و استاندارد باشد.
_
شروع شود._
باشد.var Name string = "Ali"
var name string = "Bob" // متغیر متفاوت
userCount := 100 // خوب
numberOfUsersInSystem := 100 // بیش از حد طولانی
userName := "Ali"
isActive := true
var UserID int = 123 // صادراتی
var userID int = 456 // غیرصادراتی
_
در نامها: مگر در موارد خاص مثل نادیده گرفتن متغیرها.چرا مهم است؟: نامگذاری صحیح خوانایی کد را افزایش میدهد و از خطاهای توسعه جلوگیری میکند.
توضیح: دامنه متغیر مشخص میکند که متغیر در کجای برنامه قابل دسترسی است. Go دامنههای مختلفی را پشتیبانی میکند.
func main() {
x := 10 // دامنه محلی در main
if true {
y := 20 // دامنه محلی در بلاک if
fmt.Println(x, y) // خروجی: 10 20
}
// fmt.Println(y) // خطا: y تعریف نشده
}
توضیح:
x
در کل تابع main
قابل دسترسی است.y
فقط در بلاک if
معتبر است و خارج از آن قابل دسترسی نیست.package main
var globalVar int = 100 // قابل دسترسی در کل بسته
func main() {
fmt.Println(globalVar) // خروجی: 100
}
func anotherFunc() {
fmt.Println(globalVar) // همچنان قابل دسترسی
}
توضیح:
package mypkg
var ExportedVar = "I am exported" // قابل دسترسی در بستههای دیگر
var privateVar = "I am private" // فقط در بسته mypkg
توضیح:
چرا مهم است؟: درک دامنه متغیرها از دسترسی غیرمجاز یا خطاهای منطقی جلوگیری میکند.
توضیح: برخی اشتباهات رایج در تعریف متغیرها میتوانند باعث خطاهای کامپایل یا منطقی شوند.
var x int = 10 // خطا اگر x استفاده نشود
توضیح:
_
برای نادیده گرفتن استفاده کنید.x := 10
x := 20 // خطا: تعریف مجدد
توضیح:
:=
بازتعریف کرد.=
استفاده کنید:
x = 20 // صحیح
:=
در سطح بسته:
package main
x := 10 // خطا: := فقط در توابع
توضیح:
:=
فقط در توابع قابل استفاده است.var
استفاده کنید:
var x = 10
توضیح: Go یک زبان تایپ استاتیک است، یعنی نوع هر متغیر در زمان کامپایل مشخص میشود. این ویژگی خطاهای نوع را کاهش میدهد. Go انواع دادههای پایه و مرکب را پشتیبانی میکند.
توضیح: انواع پایه شامل اعداد، رشتهها، و بولینها هستند که برای عملیات اولیه استفاده میشوند.
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
var f32 float32 = 3.14
var f64 float64 = 3.14159265359 // دقت بالاتر
توضیح:
float32
دقت کمتری دارد و برای کاربردهای ساده مناسب است.float64
دقت بالاتری دارد و برای محاسبات علمی استفاده میشود.fmt.Println(0.1 + 0.2) // 0.30000000000000004
var c64 complex64 = 1 + 2i
var c128 complex128 = 3 + 4i
توضیح:
complex64
از float32
و complex128
از float64
تشکیل شده است.fmt.Println(real(c64), imag(c64)) // 1 2
var s string = "Hello, Go!"
توضیح:
fmt.Println(s[0]) // 72 (بایت 'H')
rune
استفاده کنید:
runes := []rune(s)
fmt.Println(runes[0]) // 72
s2 := s + " World" // الحاق
fmt.Println(len(s)) // طول
var b bool = true
توضیح:
true
یا false
.fmt.Println(true && false) // false
fmt.Println(true || false) // true
fmt.Println(!true) // false
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 کاراکترها)
توضیح: انواع مرکب برای ذخیره دادههای پیچیدهتر استفاده میشوند.
var arr [3]int = [3]int{1, 2, 3}
توضیح:
fmt.Println(arr[0]) // 1
arr[1] = 20 // تغییر
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)) // طول و ظرفیت
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)
type Person struct {
Name string
Age int
}
var p Person = Person{Name: "Ali", Age: 30}
p.Age = 31 // تغییر
توضیح:
.
:
fmt.Println(p.Name) // Ali
var x int = 10
var p *int = &x // اشارهگر به x
*p = 20 // تغییر مقدار x
fmt.Println(x) // 20
توضیح:
&
آدرس را میگیرد، *
مقدار را تغییر میدهد.type Speaker interface {
Speak() string
}
type Dog struct{}
func (d Dog) Speak() string { return "Woof!" }
var s Speaker = Dog{}
fmt.Println(s.Speak()) // Woof!
توضیح:
ch := make(chan int)
go func() { ch <- 42 }()
fmt.Println(<-ch) // 42
توضیح:
make
ساخته میشوند و میتوانند بافر داشته باشند:
ch := make(chan int, 10) // بافر 10
توضیح: Go به متغیرهای بدون مقدار اولیه، مقدار پیشفرض اختصاص میدهد:
int
, float
: 0
string
: ""
bool
: false
pointer
, slice
, map
, channel
, interface
: nil
مثال:
var s []int
fmt.Println(s == nil) // true
چرا مهم است؟: این رفتار از خطاهای زمان اجرا (مثل null pointer exception) جلوگیری میکند.
s := make([]int, 2, 5) // طول 2، ظرفیت 5
s = append(s, 1, 2, 3)
fmt.Println(len(s), cap(s)) // 5, 5
توضیح:
var mu sync.Mutex
m := make(map[string]int)
mu.Lock()
m["key"] = 1
mu.Unlock()
توضیح:
sync.Map
برای موارد خاص استفاده کنید.var p *int
if p == nil {
fmt.Println("Pointer is nil")
}
توضیح:
nil
برای جلوگیری از خطاهای زمان اجرا ضروری است.s := []int{1, 2}
fmt.Println(s[2]) // خطا: index out of range
راهحل:
if len(s) > 2 {
fmt.Println(s[2])
}
var m map[string]int
m["key"] = 1 // خطا: assignment to nil map
راهحل:
m = make(map[string]int)
var p *int
*p = 10 // خطا: nil pointer dereference
راهحل:
x := 10
p = &x
*p = 10
توضیح: ثابتها مقادیری هستند که در زمان اجرا تغییر نمیکنند و برای مقادیر ثابت مثل تنظیمات یا مقادیر ریاضی استفاده میشوند.
const pi float64 = 3.14159
const greeting = "Hello"
توضیح:
const
تعریف میشوند.pi
یا تنظیمات ثابت.const (
Monday = 1
Tuesday = 2
Wednesday = 3
)
توضیح:
const
برای تعریف چندین ثابت مرتبط استفاده میشود.توضیح: iota
برای تعریف ثابتهای متوالی استفاده میشود و به طور خودکار مقدار را افزایش میدهد.
const (
Read = 1 << iota // 1
Write // 2
Execute // 4
)
توضیح:
iota
از 0 شروع میشود و در هر خط افزایش مییابد.1 << iota
برای ایجاد مقادیر باینری (مثل مجوزها) استفاده میشود.const (
KB = 1 << (10 * iota) // 1 << 10 = 1024
MB // 1 << 20
GB // 1 << 30
)
const x = 42 // بدون نوع
var i int = x
var f float64 = x
توضیح:
const size = 10
var arr [size]int // اندازه آرایه در زمان کامپایل
توضیح:
const x = 10
x = 20 // خطا: cannot assign to x
راهحل: از متغیر به جای ثابت استفاده کنید.
iota
خارج از بلاک const
:
var x = iota // خطا
راهحل: iota
فقط در بلاک const
معتبر است.
توضیح: Go از تبدیل صریح نوع پشتیبانی میکند و تبدیل ضمنی (مثل برخی زبانها) وجود ندارد.
var i int = 42
var f float64 = float64(i) // تبدیل به float64
var u uint = uint(f) // تبدیل به uint
توضیح:
float64(i)
) انجام میشود.s := "123"
i, err := strconv.Atoi(s) // تبدیل به int
if err != nil {
fmt.Println("Conversion error:", err)
}
f, err := strconv.ParseFloat(s, 64) // تبدیل به float64
توضیح:
strconv
برای تبدیل رشته به عدد استفاده میشود.Atoi
برای تبدیل به int
و ParseFloat
برای float64
.err
ضروری است، چون ورودی نامعتبر (مثل "abc"
) باعث خطا میشود.i := 42
s := strconv.Itoa(i) // "42"
s2 := fmt.Sprintf("%d", i) // "42"
توضیح:
Itoa
روش مستقیم برای تبدیل int
به رشته است.fmt.Sprintf
انعطافپذیرتر است اما کندتر.type UserID int
var id UserID = 42
var plainID int = int(id)
توضیح:
UserID
) باید تبدیل صریح انجام شود.var f float64 = 3.14
i := int(f) // 3 (بخش اعشاری حذف میشود)
توضیح:
float64
به int
) ممکن است داده را تغییر دهد.var s string = "abc"
i := int(s) // خطا: cannot convert
راهحل: از strconv
استفاده کنید.
i, _ := strconv.Atoi("abc") // بد
راهحل:
i, err := strconv.Atoi("abc")
if err != nil {
fmt.Println("Error:", err)
}
توضیح: Shadowing زمانی رخ میدهد که متغیری در دامنه داخلی با همان نام متغیر دامنه خارجی تعریف شود.
func main() {
x := 10
if true {
x := 20 // Shadowing
fmt.Println(x) // 20
}
fmt.Println(x) // 10
}
توضیح:
x
در بلاک if
متغیر خارجی را “پوشانده” و فقط در آن بلاک معتبر است.=
) استفاده کنید:
x = 20 // به جای x := 20
for i, v := range []int{1, 2, 3} {
fmt.Println(i, v)
}
توضیح:
i
(اندیس) و v
(مقدار) فقط در دامنه حلقه معتبرند._
استفاده کنید:
for _, v := range []int{1, 2, 3} {
fmt.Println(v)
}
var counter int
var mu sync.Mutex
func increment() {
mu.Lock()
counter++
mu.Unlock()
}
توضیح:
sync.Mutex
دسترسی همزمان را کنترل میکند.ch := make(chan int)
go func() { ch <- counter + 1 }()
counter = <-ch
for _, v := range []int{1, 2, 3} {
fmt.Println(v) // نادیده گرفتن اندیس
}
توضیح:
_
برای نادیده گرفتن مقادیری که استفاده نمیشوند به کار میرود.var err error
_, err = someFunc()
if err != nil {
return err
}
_, err = anotherFunc() // استفاده مجدد از err
توضیح:
err
) تخصیص حافظه را کاهش میدهد.package mypkg
var Config = "global config" // صادراتی
توضیح:
x := 10
if true {
x := 20 // ممکن است به اشتباه متغیر خارجی را نادیده بگیرید
}
راهحل: از ابزارهایی مثل go vet
برای تشخیص Shadowing استفاده کنید.
var counter int
go func() { counter++ }() // خطا: race condition
راهحل: از sync.Mutex
یا کانالها استفاده کنید.
توضیح: رعایت بهترین شیوهها باعث خوانایی، نگهداری، و عملکرد بهتر کد میشود.
userCount := 100 // خوب
x := 100 // بد
توضیح:
x
, y
جز در حلقهها اجتناب کنید.func calculate() int {
result := 0 // دامنه محلی
// محاسبات
return result
}
توضیح:
name := "Ali" // بهتر از var name string = "Ali"
توضیح:
:=
کد را مختصر و خوانا میکند، بهویژه در توابع.func process(name string) string {
return "Hello, " + name
}
توضیح:
var s *string
if s == nil {
fmt.Println("String pointer is nil")
}
توضیح:
nil
برای اشارهگرها، اسلایسها، و مپها از خطاهای زمان اجرا جلوگیری میکند.const maxRetries = 3
توضیح:
go vet
توضیح:
go vet
متغیرهای تعریفشده اما استفادهنشده را شناسایی میکند.go fmt
توضیح:
go fmt
کد را بهصورت استاندارد فرمتبندی میکند.go tool pprof mem.out
توضیح:
// MaxConnections تعداد حداکثر اتصالهای همزمان
const MaxConnections = 100
توضیح:
godoc
این کامنتها را به مستندات تبدیل میکند.توضیح: شناسایی و رفع خطاهای رایج در استفاده از متغیرها برای توسعه کد باکیفیت ضروری است.
// بد
i, _ := strconv.Atoi("abc")
// خوب
i, err := strconv.Atoi("abc")
if err != nil {
fmt.Println("Error:", err)
}
توضیح:
// بد
var p *int
*p = 10 // خطا: nil pointer dereference
// خوب
x := 10
p = &x
*p = 20
توضیح:
nil
باعث خطاهای زمان اجرا میشوند.// بد
temp1 := calculateSomething()
temp2 := process(temp1)
return temp2
// خوب
return process(calculateSomething())
توضیح:
// بد
x := 10
if true {
x := 20 // Shadowing
}
// خوب
x := 10
if true {
x = 20
}
توضیح:
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
}
توضیح:
counter
بهصورت همزمان توسط چندین goroutine تغییر میکند.sync.Mutex
از شرایط رقابتی جلوگیری میکند.sync.WaitGroup
برای انتظار اتمام goroutineها استفاده میشود.for i := 0; i < 5; i++ {
i := i // کپی متغیر
go func() {
fmt.Println(i)
}()
}
توضیح:
i
، همه goroutineها به مقدار نهایی i
دسترسی پیدا میکنند.var debugMode bool
func init() {
debugMode = os.Getenv("DEBUG") == "true"
}
func logDebug(msg string) {
if debugMode {
fmt.Println("Debug:", msg)
}
}
توضیح:
debugMode
بر اساس متغیر محیطی تنظیم میشود.init
قبل از main
اجرا میشود و برای تنظیمات اولیه مناسب است.package mypkg
type Config struct {
MaxRetries int
}
var DefaultConfig = Config{MaxRetries: 3}
توضیح:
DefaultConfig
در بستههای دیگر قابل دسترسی است.توضیح: منابع زیر برای یادگیری عمیقتر Go توصیه میشوند:
https://golang.org/doc/
: شامل آموزشها و مرجع کامل.https://go.dev/tour
: آموزش تعاملی برای مبتدیان.https://gobyexample.com
): مثالهای عملی.r/golang
برای بحث و پرسوجو.https://gophers.slack.com
برای ارتباط با جامعه.go
برای سوالات فنی.توضیح:
kubernetes
یا hugo
برای یادگیری الگوهای واقعی.fmt
, net/http
) در پوشه src
Go.توضیح: این جزوه مفاهیم پایه و اولیه زبان Go با تمرکز بر متغیرها و موارد مرتبط را بهصورت جامع و عمیق پوشش داد. از تعریف متغیرها، انواع دادهها، ثابتها، و تبدیل نوع تا دامنهها، الگوهای پیشرفته، و بهترین شیوهها، هر بخش با توضیحات کامل، مثالهای عملی، و نکات تخصصی ارائه شد. برای یادگیری عمیقتر:
https://golang.org/doc/