وبلاگ
مفاهیم اساسی در Go: متغیرها، ثابتها و انواع داده
فهرست مطالب
“تسلط به برنامهنویسی پایتون با هوش مصنوعی: آموزش کدنویسی هوشمند با ChatGPT”
"تسلط به برنامهنویسی پایتون با هوش مصنوعی: آموزش کدنویسی هوشمند با ChatGPT"
"با شرکت در این دوره جامع و کاربردی، به راحتی مهارتهای برنامهنویسی پایتون را از سطح مبتدی تا پیشرفته با کمک هوش مصنوعی ChatGPT بیاموزید. این دوره، با بیش از 6 ساعت محتوای آموزشی، شما را قادر میسازد تا به سرعت الگوریتمهای پیچیده را درک کرده و اپلیکیشنهای هوشمند ایجاد کنید. مناسب برای تمامی سطوح با زیرنویس فارسی حرفهای و امکان دانلود و تماشای آنلاین."
ویژگیهای کلیدی:
بدون نیاز به تجربه قبلی برنامهنویسی
زیرنویس فارسی با ترجمه حرفهای
۳۰ ٪ تخفیف ویژه برای دانشجویان و دانش آموزان
0 تا 100 عطرسازی + (30 فرمولاسیون اختصاصی حامی صنعت)
دوره آموزش Flutter و برنامه نویسی Dart [پروژه محور]
دوره جامع آموزش برنامهنویسی پایتون + هک اخلاقی [با همکاری شاهک]
دوره جامع آموزش فرمولاسیون لوازم آرایشی
دوره جامع علم داده، یادگیری ماشین، یادگیری عمیق و NLP
دوره فوق فشرده مکالمه زبان انگلیسی (ویژه بزرگسالان)
شمع سازی و عودسازی با محوریت رایحه درمانی
صابون سازی (دستساز و صنعتی)
صفر تا صد طراحی دارو
متخصص طب سنتی و گیاهان دارویی
متخصص کنترل کیفی شرکت دارویی
مفاهیم اساسی در Go: متغیرها، ثابتها و انواع داده
زبان برنامهنویسی Go (که به Golang نیز مشهور است)، محصول شرکت گوگل، با تمرکز بر سادگی، کارایی و پشتیبانی قدرتمند از همزمانی (Concurrency)، به سرعت جایگاه ویژهای در دنیای توسعه نرمافزار پیدا کرده است. این زبان، انتخابی عالی برای توسعه سیستمهای ابری، میکرو سرویسها، ابزارهای خط فرمان و برنامههای کاربردی وب با کارایی بالا محسوب میشود. اما مانند هر زبان برنامهنویسی دیگری، تسلط بر Go نیازمند درک عمیق از مفاهیم بنیادی آن است. در این پست تخصصی، ما به سراغ سه ستون اصلی برنامهنویسی در Go خواهیم رفت: متغیرها، ثابتها و انواع داده. درک کامل این مفاهیم نه تنها برای نوشتن کدهای صحیح و کارآمد ضروری است، بلکه سنگ بنای درک ساختارهای پیچیدهتر و الگوهای طراحی پیشرفتهتر در Go را نیز تشکیل میدهد.
در Go، همه چیز تایپ شده (Typed) است و این موضوع، نقشی کلیدی در امنیت نوع (Type Safety) و عملکرد بالای آن ایفا میکند. این رویکرد به کامپایلر اجازه میدهد تا خطاهای زیادی را در زمان کامپایل شناسایی کند، پیش از آنکه برنامه به مرحله اجرا برسد و مشکلات پنهان، سر از خاک برآورند. با غواصی عمیق در این مباحث، شما قادر خواهید بود تا دادهها را به درستی ذخیره، مدیریت و از آنها در منطق برنامههای خود استفاده کنید.
هدف از این مقاله، ارائه یک مرجع جامع و تخصصی برای توسعهدهندگان Go است، از مبتدیانی که به تازگی سفر خود را آغاز کردهاند تا توسعهدهندگان باتجربهتری که به دنبال بازنگری و عمیقتر کردن دانش خود در این زمینهها هستند. با مثالهای عملی، توضیح مفاهیم پایهای و پیشرفته، و بررسی نکات ظریف، ما تلاش خواهیم کرد تا درکی شفاف و عملی از این ارکان اساسی Go را برای شما فراهم آوریم.
متغیرها در Go: ذخیرهسازی و مدیریت دادهها
در هسته هر برنامه کامپیوتری، مفهوم ذخیرهسازی و دستکاری دادهها قرار دارد. متغیرها، نامهایی هستند که به مکانهای حافظه اشاره میکنند و این امکان را فراهم میآورند که مقادیر مختلفی را در طول زمان در آنها ذخیره و بازیابی کنیم. در Go، مدیریت متغیرها از قوانین و الگوهای خاصی پیروی میکند که آن را از بسیاری از زبانهای دیگر متمایز میسازد.
اعلان و مقداردهی اولیه متغیرها
Go رویکردی صریح و در عین حال منعطف برای اعلان و مقداردهی اولیه متغیرها دارد. دو روش اصلی برای این کار وجود دارد:
1. استفاده از کلمه کلیدی var
این روش، روش سنتی و صریح برای اعلان یک متغیر است. شما میتوانید نوع داده را مشخص کنید یا اجازه دهید Go آن را از مقدار اولیه استنتاج کند.
اعلان با مشخص کردن نوع:
package main
import "fmt"
func main() {
varage int // اعلان یک متغیر به نام 'age' از نوع int
age = 30 // مقداردهی اولیه
fmt.Println("سن:", age)
var name string = "امیر" // اعلان و مقداردهی اولیه همزمان
fmt.Println("نام:", name)
var isStudent bool // اعلان بدون مقداردهی اولیه
// isStudent به مقدار پیشفرض خود (false) مقداردهی میشود.
fmt.Println("دانشجو:", isStudent)
}
در Go، متغیرهای اعلان شده اما بدون مقداردهی اولیه، به صورت خودکار با مقدار پیشفرض (zero value) نوع خود مقداردهی میشوند. برای انواع عددی، این مقدار 0
است؛ برای بولی false
؛ و برای رشتهها، یک رشته خالی ""
. برای انواع ساختار یافته (مانند آرایهها، اسلایسها، نقشهها و اشارهگرها) مقدار پیشفرض nil
است (به جز آرایهها که عناصرشان به zero value خود مقداردهی میشوند).
اعلان بدون مشخص کردن نوع (استنتاج نوع):
Go به صورت خودکار میتواند نوع یک متغیر را از مقدار اولیه آن استنتاج کند. این کار کد را کوتاهتر و خواناتر میکند.
package main
import "fmt"
func main() {
var salary = 50000.0 // Go نوع float64 را استنتاج میکند
fmt.Println("حقوق:", salary)
var message = "سلام دنیا" // Go نوع string را استنتاج میکند
fmt.Println("پیام:", message)
}
2. عملگر اعلان کوتاه (Short Declaration Operator) :=
این محبوبترین روش برای اعلان و مقداردهی اولیه متغیرها در Go است، به خصوص در توابع. این عملگر همزمان متغیر را اعلان و به آن مقداردهی اولیه میکند، و نوع آن را از مقدار اولیه استنتاج مینماید. نکته مهم این است که :=
فقط زمانی قابل استفاده است که حداقل یک متغیر جدید در سمت چپ اعلان شود.
package main
import "fmt"
func main() {
count := 10 // اعلان و مقداردهی اولیه یک متغیر 'count' از نوع int
fmt.Println("شمارش:", count)
price := 99.99 // اعلان و مقداردهی اولیه یک متغیر 'price' از نوع float64
fmt.Println("قیمت:", price)
name, age := "سارا", 25 // اعلان همزمان چندین متغیر
fmt.Println("نام:", name, "سن:", age)
// مثال استفاده مجدد از یک متغیر موجود و اعلان متغیر جدید
x := 10
y := 20
fmt.Println("x:", x, "y:", y)
x, z := 30, "جدید" // x مقداردهی مجدد میشود، z جدید اعلان میشود
fmt.Println("x:", x, "z:", z, "y:", y) // y بدون تغییر باقی میماند
}
این روش به دلیل کوتاهی و خوانایی، در Go بسیار رایج است. با این حال، استفاده از var
همچنان برای اعلان متغیرهایی که مقدار اولیه ندارند یا برای وضوح بیشتر در مواردی که نوع اهمیت دارد (مثلاً برای تطابق با اینترفیسها) مفید است.
قوانین نامگذاری متغیرها و نکات کلیدی
نامگذاری متغیرها در Go از قوانینی پیروی میکند که بر خوانایی و توافقپذیری (Convention) تأکید دارد:
- حرف اول: اگر حرف اول یک متغیر (یا تابع، نوع، و غیره) با حرف بزرگ شروع شود (مثلاً
Name
)، آن متغیر “صادر شده” (exported) است و از خارج از پکیجی که در آن تعریف شده، قابل دسترسی است. اگر با حرف کوچک شروع شود (مثلاًname
)، “صادر نشده” (unexported) است و فقط در داخل همان پکیج قابل استفاده است. - camelCase: برای نامگذاری متغیرها معمولاً از camelCase استفاده میشود، به این معنی که کلمه اول با حرف کوچک و کلمات بعدی با حرف بزرگ شروع میشوند (مثلاً
userName
،totalCount
). - کوتاهی: Go توسعهدهندگان را به استفاده از نامهای کوتاه و مختصر تشویق میکند، به شرطی که معنیدار باشند. برای مثال،
i
برای یک شمارنده حلقه،idx
برای اندیس،r
برای reader وw
برای writer کاملاً پذیرفته شده است. - کلمات کلیدی: نمیتوانید از کلمات کلیدی Go (مانند
var
,func
,if
,else
,const
,package
) به عنوان نام متغیر استفاده کنید.
متغیرهای بدون استفاده و خطاهای کامپایلر
یکی از ویژگیهای سختگیرانه اما مفید Go این است که داشتن متغیرهای اعلان شده اما بدون استفاده، باعث خطای کامپایل (Compilation Error) میشود. این ویژگی کمک میکند تا کدهای پاکتر و بدون زائدهای داشته باشیم و از اشتباهات احتمالی جلوگیری میکند.
package main
func main() {
var unusedVar int // این خط خطا میدهد: unusedVar declared and not used
// fmt.Println(unusedVar) // اگر این خط را فعال کنید، خطا برطرف میشود
}
این قانون شامل متغیرهای صادر نشده (unexported) میشود. متغیرهای صادر شده (Exported variables) که با حرف بزرگ شروع میشوند، حتی اگر در همان پکیج استفاده نشوند، خطا نمیدهند، زیرا فرض بر این است که ممکن است از پکیجهای دیگر استفاده شوند.
درک صحیح از متغیرها و نحوه اعلان و استفاده از آنها، پایه و اساس هر برنامه Go است. با رعایت قوانین نامگذاری و استفاده صحیح از انواع اعلان، میتوانید کدهایی خوانا، قابل نگهداری و کارآمد بنویسید.
ثابتها در Go: مقادیر تغییرناپذیر
بر خلاف متغیرها که مقادیرشان در طول اجرای برنامه قابل تغییر است، ثابتها (Constants) مقادیری هستند که در زمان کامپایل (Compile Time) تعیین میشوند و پس از آن، به هیچ عنوان قابل تغییر نیستند. استفاده از ثابتها به کد شما ثبات و خوانایی بیشتری میبخشد و از خطاهای ناشی از تغییر تصادفی مقادیر جلوگیری میکند.
اعلان ثابتها و تفاوت با متغیرها
برای اعلان یک ثابت در Go، از کلمه کلیدی const
استفاده میشود. ثابتها میتوانند از انواع بولی، عددی (صحیح، اعشاری، مختلط) و رشتهای باشند. آنها نمیتوانند از انواع دیگر مانند اسلایسها، نقشهها یا توابع باشند، زیرا این انواع نیاز به تخصیص حافظه در زمان اجرا دارند.
package main
import "fmt"
const PI = 3.14159 // اعلان یک ثابت اعشاری
const Greeting = "سلام" // اعلان یک ثابت رشتهای
const IsActive = true // اعلان یک ثابت بولی
func main() {
fmt.Println("PI:", PI)
fmt.Println("پیام خوشآمدگویی:", Greeting)
fmt.Println("فعال است:", IsActive)
// PI = 3.14 // این خط باعث خطای کامپایل میشود: cannot assign to PI (constant)
}
یکی از تفاوتهای کلیدی بین ثابتها و متغیرها، زمان مقداردهی است: ثابتها باید در زمان کامپایل مقداردهی شوند، در حالی که متغیرها میتوانند در زمان اجرا مقدار بگیرند. این به این معنی است که مقدار یک ثابت باید یک عبارت ثابت (constant expression) باشد، یعنی عبارتی که کامپایلر میتواند مقدار نهایی آن را در زمان کامپایل محاسبه کند.
const MaxUsers = 100
const TimeoutSeconds = 60 * 60 // 3600
const HalfPI = PI / 2 // با فرض اینکه PI قبلاً تعریف شده است.
ثابتهای بدون نوع (Untyped Constants) و انعطافپذیری آنها
یکی از ویژگیهای قدرتمند ثابتها در Go، مفهوم “ثابتهای بدون نوع” است. زمانی که شما یک ثابت را بدون صریح کردن نوع آن اعلان میکنید (مثلاً const PI = 3.14159
به جای const PI float64 = 3.14159
)، Go آن را به عنوان یک ثابت بدون نوع در نظر میگیرد. این ثابتها انعطافپذیری بالایی دارند زیرا میتوانند در هر جایگاهی که نوع سازگار با آنها انتظار میرود، بدون نیاز به تبدیل نوع صریح، استفاده شوند.
package main
import "fmt"
const BigNumber = 1_000_000_000_000_000_000 // یک ثابت بدون نوع بسیار بزرگ
func main() {
var i int = BigNumber // BigNumber به صورت int تبدیل میشود (اگر در محدوده int باشد)
var j int64 = BigNumber // BigNumber به صورت int64 تبدیل میشود
var f float64 = BigNumber // BigNumber به صورت float64 تبدیل میشود
fmt.Println("i (int):", i)
fmt.Println("j (int64):", j)
fmt.Println("f (float64):", f)
// اگر BigNumber از محدوده نوع مقصد بزرگتر باشد، خطای کامپایل رخ میدهد
// var smallInt int8 = BigNumber // خطای کامپایل: constant 1e+18 overflows int8
}
مزیت این رویکرد این است که Go برای ثابتهای بدون نوع، دقت بسیار بالایی (معمولاً با استفاده از اعداد با دقت نامحدود) را در زمان کامپایل حفظ میکند. تبدیل نوع فقط زمانی انجام میشود که ثابت در یک زمینه تایپ شده (typed context) استفاده شود، مانند مقداردهی به یک متغیر با نوع مشخص یا پاس دادن به یک تابع که پارامترهای تایپ شده دارد.
استفاده از iota
برای ثابتهای متوالی
Go یک شناسهگر ویژه به نام iota
را برای تعریف سریهای ثابتهای متوالی و مرتبط ارائه میدهد. iota
به طور پیشفرض از 0
شروع میشود و با هر خط const
جدید یا هر آیتم در یک بلوک const
، یک واحد افزایش مییابد. این ویژگی برای تعریف شمارشها (Enums) یا پرچمهای بیتی (Bit Flags) بسیار مفید است.
package main
import "fmt"
const (
// iota در اینجا 0 است
Red = iota // Red = 0
Blue // Blue = 1 (iota به 1 افزایش مییابد)
Green // Green = 2 (iota به 2 افزایش مییابد)
)
const (
// iota در اینجا دوباره از 0 شروع میشود
Monday = iota + 1 // Monday = 1
Tuesday // Tuesday = 2
Wednesday // Wednesday = 3
Thursday // Thursday = 4
Friday // Friday = 5
Saturday // Saturday = 6
Sunday // Sunday = 7
)
const (
// iota برای پرچمهای بیتی
FlagA = 1 << iota // FlagA = 1 (1 << 0)
FlagB // FlagB = 2 (1 << 1)
FlagC // FlagC = 4 (1 << 2)
FlagD // FlagD = 8 (1 << 3)
)
func main() {
fmt.Println("رنگها:", Red, Blue, Green)
fmt.Println("روزها:", Monday, Tuesday, Wednesday, Thursday, Friday, Saturday, Sunday)
fmt.Println("پرچمها:", FlagA, FlagB, FlagC, FlagD)
}
با استفاده از iota
و ثابتها، میتوانید کدهای خود را خواناتر، ایمنتر و نگهداریپذیرتر کنید، به خصوص در مواردی که با مجموعهای از مقادیر ثابت سروکار دارید.
انواع داده در Go: طبقهبندی و ساختار اطلاعات
نوع داده (Data Type) مجموعهای از مقادیر و مجموعهای از عملیات قابل اجرا بر روی آن مقادیر را تعریف میکند. در Go، انواع داده نقش حیاتی در تعریف رفتار متغیرها و چگونگی ذخیرهسازی آنها در حافظه دارند. Go یک زبان با نوعبندی ایستا (Statically Typed) است، به این معنی که نوع هر متغیر باید در زمان کامپایل مشخص باشد. این رویکرد به کامپایلر اجازه میدهد تا بهینهسازیهای بیشتری انجام دهد و خطاهای مربوط به نوع را در مراحل اولیه توسعه شناسایی کند.
انواع داده در Go به دو دسته اصلی تقسیم میشوند: انواع پایه (Basic Types) و انواع مرکب (Composite Types).
انواع داده عددی: صحیح و اعشاری
Go انواع داده عددی متنوعی را برای مدیریت اعداد صحیح و اعشاری با اندازهها و دقتهای متفاوت ارائه میدهد.
اعداد صحیح (Integers):
اعداد صحیح در Go به دو دسته علامتدار (Signed) و بدون علامت (Unsigned) تقسیم میشوند:
- علامتدار (Signed Integers): میتوانند مقادیر مثبت، منفی و صفر را ذخیره کنند.
int8
: 8 بیت (-128 تا 127)int16
: 16 بیت (-32768 تا 32767)int32
: 32 بیت (-2,147,483,648 تا 2,147,483,647)int64
: 64 بیت (-9,223,372,036,854,775,808 تا 9,223,372,036,854,775,807)int
: اندازه آن بستگی به معماری سیستم دارد (32 یا 64 بیت). توصیه میشود در اکثر موارد ازint
استفاده کنید مگر اینکه نیاز خاصی به اندازه مشخصی داشته باشید.
- بدون علامت (Unsigned Integers): فقط میتوانند مقادیر مثبت و صفر را ذخیره کنند.
uint8
(معادلbyte
): 8 بیت (0 تا 255)uint16
: 16 بیت (0 تا 65535)uint32
: 32 بیت (0 تا 4,294,967,295)uint64
: 64 بیت (0 تا 18,446,744,073,709,551,615)uint
: اندازه آن بستگی به معماری سیستم دارد.uintptr
: یک نوع صحیح بدون علامت به اندازه کافی بزرگ برای نگهداری یک آدرس حافظه (مفید برای کار با اشارهگرها در سطح پایین).
byte
: نام مستعار برایuint8
است و برای نمایش بایتها استفاده میشود.rune
: نام مستعار برایint32
است و برای نمایش کاراکترهای یونیکد (Unicode Code Points) استفاده میشود.
package main
import "fmt"
func main() {
var age int = 30
var smallNum int8 = 127
var largeNum int64 = 9000000000000000000
var b byte = 'A' // A = 65
var r rune = '😎' // Unicode character
fmt.Printf("Age: %d, Type: %T\n", age, age)
fmt.Printf("Small Number: %d, Type: %T\n", smallNum, smallNum)
fmt.Printf("Large Number: %d, Type: %T\n", largeNum, largeNum)
fmt.Printf("Byte: %d (%c), Type: %T\n", b, b, b)
fmt.Printf("Rune: %d (%c), Type: %T\n", r, r, r)
}
اعداد اعشاری (Floating-Point Numbers):
برای نمایش اعداد با نقطه اعشار، Go دو نوع را ارائه میدهد:
float32
: دقت تک (Single-precision floating-point number)float64
: دقت دوگانه (Double-precision floating-point number). این نوع پیشفرض برای لیترالهای اعشاری است و در اکثر موارد توصیه میشود.
package main
import "fmt"
func main() {
var pi float32 = 3.14
var price float64 = 49.99
fmt.Printf("PI: %f, Type: %T\n", pi, pi)
fmt.Printf("Price: %f, Type: %T\n", price, price)
}
اعداد مختلط (Complex Numbers):
Go همچنین از اعداد مختلط پشتیبانی میکند:
complex64
: با اجزایfloat32
complex128
: با اجزایfloat64
(پیشفرض)
package main
import "fmt"
func main() {
var c complex64 = 1 + 2i
var d complex128 = complex(3, 4) // ساخت عدد مختلط از اجزا
fmt.Printf("c: %v, Type: %T\n", c, c)
fmt.Printf("d: %v, Type: %T\n", d, d)
fmt.Println("بخش حقیقی c:", real(c))
fmt.Println("بخش موهومی d:", imag(d))
}
انواع داده بولی و رشتهای
نوع بولی (Boolean Type):
نوع bool
فقط دو مقدار میتواند داشته باشد: true
(درست) یا false
(غلط). این نوع داده برای عبارات شرطی و منطقی استفاده میشود.
package main
import "fmt"
func main() {
var isOpen bool = true
isComplete := false
fmt.Printf("آیا باز است؟ %t, Type: %T\n", isOpen, isOpen)
fmt.Printf("آیا کامل است؟ %t, Type: %T\n", isComplete, isComplete)
}
نوع رشتهای (String Type):
نوع string
برای ذخیره دنبالهای از بایتها استفاده میشود. در Go، رشتهها به صورت UTF-8 کدگذاری میشوند، به این معنی که میتوانند به راحتی کاراکترها از زبانهای مختلف از جمله فارسی را در خود جای دهند. رشتهها در Go تغییرناپذیر (Immutable) هستند، یعنی پس از ایجاد، نمیتوان محتوای آنها را تغییر داد.
package main
import "fmt"
func main() {
var greeting string = "سلام دنیا"
name := "جان"
message := `این یک رشته چند خطی است.
و میتوانداز کاراکترهای ویژه مانند "quotes" و \backslashes استفاده کند.`
fmt.Println(greeting)
fmt.Println(name)
fmt.Println(message)
fmt.Println("طول 'سلام دنیا':", len(greeting)) // طول بر حسب بایت
fmt.Println("طول 'سلام دنیا' (کاراکترها):", len([]rune(greeting))) // تبدیل به rune برای شمارش کاراکترها
// الحاق رشتهها
fullName := "محمد" + " " + "رضایی"
fmt.Println(fullName)
// دسترسی به بایتهای رشته (نه لزوما کاراکترها)
fmt.Println("بایت اول 'سلام دنیا':", greeting[0]) // خروجی 216 (کد اسکی 'س' در UTF-8)
}
برای کار با کاراکترهای یونیکد در رشتهها، اغلب نیاز به تبدیل رشته به اسلایسی از rune
ها دارید، زیرا len()
تعداد بایتها را برمیگرداند، نه تعداد کاراکترها.
انواع داده مرکب (Composite Types): آرایهها، اسلایسها و نقشهها
انواع داده مرکب به شما امکان میدهند مجموعهای از مقادیر را در یک ساختار واحد سازماندهی کنید.
آرایهها (Arrays):
آرایه یک دنباله شمارهگذاری شده از عناصر همنوع با طول ثابت است. طول آرایه بخشی از نوع آن است و در زمان کامپایل باید مشخص باشد. آرایهها Value Type هستند، به این معنی که هنگام کپی کردن یک آرایه، تمام محتویات آن کپی میشود.
package main
import "fmt"
func main() {
var numbers [5]int // اعلان آرایهای از 5 عدد صحیح
numbers[0] = 10
numbers[1] = 20
numbers[4] = 50 // عناصر دیگر به 0 مقداردهی میشوند
fmt.Println("آرایه numbers:", numbers) // [10 20 0 0 50]
// اعلان و مقداردهی اولیه همزمان
fruits := [3]string{"سیب", "موز", "پرتقال"}
fmt.Println("میوهها:", fruits)
// آرایهای با طول نامشخص که کامپایلر آن را از تعداد عناصر استنتاج میکند
colors := [...]string{"قرمز", "سبز", "آبی"}
fmt.Println("رنگها:", colors, "طول:", len(colors))
}
اسلایسها (Slices):
اسلایسها نسبت به آرایهها انعطافپذیری بیشتری دارند. آنها نمایندهای از یک آرایه زیرین هستند و طول پویا دارند. اسلایسها Reference Type هستند، به این معنی که کپی کردن یک اسلایس تنها یک اشارهگر به آرایه زیرین را کپی میکند، نه خود دادهها. این ویژگی اسلایسها را برای اکثر کاربردها مناسبتر از آرایهها میکند.
package main
import "fmt"
func main() {
// اعلان یک اسلایس تهی
var names []string
fmt.Println("اسلایس نامها (تهی):", names, "طول:", len(names), "ظرفیت:", cap(names))
// اعلان و مقداردهی اولیه با لیترال
cities := []string{"تهران", "اصفهان", "شیراز"}
fmt.Println("اسلایس شهرها:", cities, "طول:", len(cities), "ظرفیت:", cap(cities))
// اضافه کردن عنصر به اسلایس با append
cities = append(cities, "مشهد", "تبریز")
fmt.Println("اسلایس شهرها پس از append:", cities, "طول:", len(cities), "ظرفیت:", cap(cities))
// ایجاد اسلایس با make(type, length, capacity)
numbers := make([]int, 5, 10) // طول 5، ظرفیت 10
fmt.Println("اسلایس اعداد (make):", numbers, "طول:", len(numbers), "ظرفیت:", cap(numbers))
// برش (Slicing) از یک آرایه یا اسلایس موجود
fullArray := [7]int{1, 2, 3, 4, 5, 6, 7}
partialSlice := fullArray[2:5] // عناصر از ایندکس 2 تا (5-1)
fmt.Println("اسلایس جزئی:", partialSlice) // [3 4 5]
// نکته مهم: اسلایسها و آرایههای زیرین
arr := [4]int{10, 20, 30, 40}
s1 := arr[0:2] // [10 20]
s2 := arr[1:3] // [20 30]
fmt.Println("arr:", arr, "s1:", s1, "s2:", s2)
s1[1] = 200 // تغییر s1 باعث تغییر در آرایه زیرین میشود
fmt.Println("arr پس از تغییر s1:", arr, "s1:", s1, "s2:", s2) // arr: [10 200 30 40] s1: [10 200] s2: [200 30]
}
نقشهها (Maps):
نقشه (که در زبانهای دیگر به نام دیکشنری یا جدول هش نیز شناخته میشود) مجموعهای نامرتب از جفتهای کلید-مقدار است. کلیدها باید از یک نوع قابل مقایسه (مانند انواع پایه، structها با فیلدهای قابل مقایسه) باشند و مقادیر میتوانند از هر نوعی باشند.
package main
import "fmt"
func main() {
// اعلان و مقداردهی اولیه یک نقشه
// کلیدها string، مقادیر int
ages := map[string]int{
"علی": 30,
"مریم": 25,
"رضا": 35,
}
fmt.Println("نقشه ages:", ages)
// دسترسی به مقدار با کلید
fmt.Println("سن علی:", ages["علی"])
// اضافه کردن یا بهروزرسانی عنصر
ages["سارا"] = 28
fmt.Println("نقشه ages پس از اضافه کردن سارا:", ages)
// حذف عنصر
delete(ages, "رضا")
fmt.Println("نقشه ages پس از حذف رضا:", ages)
// بررسی وجود کلید و مقدار
ageOfMaryam, exists := ages["مریم"]
if exists {
fmt.Println("مریم در نقشه وجود دارد و سنش:", ageOfMaryam)
} else {
fmt.Println("مریم در نقشه وجود ندارد.")
}
// پیمایش نقشه
fmt.Println("پیمایش نقشه:")
for name, age := range ages {
fmt.Printf("نام: %s, سن: %d\n", name, age)
}
// ایجاد نقشه با make
employees := make(map[int]string)
employees[101] = "احمد"
employees[102] = "لیلا"
fmt.Println("نقشه employees:", employees)
}
ساختارها (Structs): تعریف انواع داده سفارشی
ساختار (Struct) یک نوع داده مرکب است که به شما امکان میدهد فیلدهایی با انواع داده مختلف را در یک واحد منطقی گروهبندی کنید. structها برای تعریف رکوردها و اشیای داده سفارشی استفاده میشوند.
package main
import "fmt"
// تعریف یک struct به نام Person
type Person struct {
FirstName string
LastName string
Age int
IsStudent bool
}
func main() {
// ایجاد یک نمونه از struct Person
var p1 Person
p1.FirstName = "فاطمه"
p1.LastName = "نیکزاد"
p1.Age = 22
p1.IsStudent = true
fmt.Println("شخص 1:", p1)
// ایجاد و مقداردهی اولیه به روش کوتاهتر
p2 := Person{
FirstName: "حسن",
LastName: "کریمی",
Age: 45,
IsStudent: false,
}
fmt.Println("شخص 2:", p2)
// دسترسی به فیلدها
fmt.Printf("%s %s, سن: %d\n", p2.FirstName, p2.LastName, p2.Age)
// structهای ناشناس (Anonymous Structs)
book := struct {
Title string
Author string
Pages int
}{
Title: "هنر برنامهنویسی",
Author: "فردوسی",
Pages: 500,
}
fmt.Println("کتاب:", book)
}
اشارهگرها (Pointers): ارجاع مستقیم به حافظه
اشارهگر (Pointer) متغیری است که آدرس حافظه یک متغیر دیگر را ذخیره میکند. در Go، استفاده از اشارهگرها کمتر از زبانهایی مانند C/C++ رایج است، اما برای مواردی مانند تغییر مقدار یک متغیر از طریق یک تابع یا کار با ساختارهای داده بزرگ برای جلوگیری از کپیهای غیرضروری، اهمیت دارند.
- عملگر
&
(address-of operator): آدرس حافظه یک متغیر را برمیگرداند. - عملگر
*
(dereferencing operator): به مقدار ذخیره شده در آدرس حافظهای که اشارهگر به آن اشاره میکند، دسترسی پیدا میکند.
package main
import "fmt"
func modifyValue(ptr *int) {
*ptr = 200 // تغییر مقدار متغیری که ptr به آن اشاره میکند
}
func main() {
x := 10
fmt.Println("x قبل از تغییر:", x) // 10
p := &x // p یک اشارهگر به x است
fmt.Println("آدرس حافظه x:", p) // آدرس حافظه (مثلاً 0xc0000a6008)
fmt.Println("مقدار اشاره شده توسط p:", *p) // 10
*p = 150 // تغییر مقدار x از طریق اشارهگر
fmt.Println("x پس از تغییر از طریق اشارهگر:", x) // 150
modifyValue(&x) // پاس دادن آدرس x به تابع
fmt.Println("x پس از فراخوانی تابع modifyValue:", x) // 200
var ptrNil *int // اشارهگر با مقدار صفر (nil)
fmt.Println("اشارهگر nil:", ptrNil)
// fmt.Println(*ptrNil) // این خط باعث panic میشود اگر ptrNil هنوز nil باشد
}
اینترفیسها (Interfaces): رفتارسازی در Go
اینترفیسها در Go، مجموعهای از امضاهای متد (method signatures) را تعریف میکنند. نوعی که تمام متدهای یک اینترفیس را پیادهسازی کند، به طور خودکار آن اینترفیس را پیادهسازی کرده است (Implicit Interface Implementation). اینترفیسها ستون فقرات پلیمورفیسم (Polymorphism) در Go هستند و امکان کدنویسی منعطف و قابل توسعه را فراهم میکنند.
package main
import "fmt"
// تعریف یک اینترفیس به نام Shape
type Shape interface {
Area() float64
Perimeter() float64
}
// تعریف struct Rectangle
type Rectangle struct {
Width, Height float64
}
// پیادهسازی متد Area برای Rectangle
func (r Rectangle) Area() float64 {
return r.Width * r.Height
}
// پیادهسازی متد Perimeter برای Rectangle
func (r Rectangle) Perimeter() float64 {
return 2 * (r.Width + r.Height)
}
// تعریف struct Circle
type Circle struct {
Radius float64
}
// پیادهسازی متد Area برای Circle
func (c Circle) Area() float6 4 {
return 3.14159 * c.Radius * c.Radius
}
// پیادهسازی متد Perimeter برای Circle
func (c Circle) Perimeter() float64 {
return 2 * 3.14159 * c.Radius
}
// تابعی که یک اینترفیس Shape میپذیرد
func PrintShapeInfo(s Shape) {
fmt.Printf("مساحت: %.2f, محیط: %.2f\n", s.Area(), s.Perimeter())
}
func main() {
rect := Rectangle{Width: 10, Height: 5}
circ := Circle{Radius: 7}
PrintShapeInfo(rect) // Rectangle به طور ضمنی Shape را پیادهسازی میکند
PrintShapeInfo(circ) // Circle به طور ضمنی Shape را پیادهسازی میکند
// اینترفیس تهی (Empty Interface) interface{}
// میتواند هر نوع مقداری را نگه دارد
var i interface{}
i = 42
fmt.Println("i (int):", i)
i = "سلام"
fmt.Println("i (string):", i)
}
اینترفیس تهی interface{}
یک مورد خاص است که میتواند هر نوع مقداری را در خود جای دهد. این بسیار قدرتمند است اما باید با احتیاط استفاده شود، زیرا از دست دادن اطلاعات نوع در زمان کامپایل، نیاز به بررسی نوع در زمان اجرا را ایجاد میکند.
کانالها (Channels): همزمانی امن در Go
کانالها (Channels) یکی از ویژگیهای منحصربهفرد و برجسته Go برای مدیریت همزمانی هستند. آنها مکانیزمی را برای ارتباط امن بین گورووتینها (goroutines) فراهم میکنند، با این فلسفه که "دادهها را با به اشتراک گذاشتن حافظه ارتباط ندهید، بلکه حافظه را با ارتباط به اشتراک بگذارید."
package main
import (
"fmt"
"time"
)
func producer(ch chan int) {
for i := 0; i < 5; i++ {
fmt.Println("ارسال:", i)
ch <- i // ارسال i به کانال
time.Sleep(time.Millisecond * 100)
}
close(ch) // بستن کانال پس از اتمام ارسال
}
func consumer(ch chan int) {
for val := range ch { // خواندن از کانال تا زمانی که بسته شود و تهی باشد
fmt.Println("دریافت:", val)
}
fmt.Println("مصرفکننده به پایان رسید.")
}
func main() {
// ایجاد یک کانال بافر نشده (بدون ظرفیت)
// ch := make(chan int)
// ایجاد یک کانال بافر شده با ظرفیت 2
ch := make(chan int, 2)
go producer(ch) // گورووتین تولیدکننده
consumer(ch) // گورووتین مصرفکننده (در همین گورووتین اصلی)
fmt.Println("برنامه اصلی به پایان رسید.")
}
این یک معرفی بسیار کوتاه به کانالها است و عمق آنها برای مبحث "مفاهیم اساسی" شاید بیش از حد باشد، اما اشاره به آن به عنوان یک نوع داخلی مهم در Go، برای درک جامع انواع داده ضروری است.
استنتاج نوع (Type Inference) و تبدیل نوع (Type Conversion)
یکی از ویژگیهای Go که به کوتاهی و خوانایی کد کمک میکند، قابلیت استنتاج نوع و همچنین قوانین صریح آن برای تبدیل انواع است.
قدرت استنتاج نوع Go
استنتاج نوع (Type Inference) به کامپایلر اجازه میدهد تا نوع یک متغیر را از مقدار اولیه آن، بدون اینکه شما صریحاً آن را ذکر کنید، تعیین کند. این ویژگی بیشتر با عملگر اعلان کوتاه :=
یا در اعلانهای var
بدون ذکر نوع استفاده میشود.
package main
import "fmt"
func main() {
// استنتاج نوع int
age := 30
fmt.Printf("Age: %d, Type: %T\n", age, age)
// استنتاج نوع float64
price := 99.99
fmt.Printf("Price: %f, Type: %T\n", price, price)
// استنتاج نوع string
message := "Go Programming"
fmt.Printf("Message: %s, Type: %T\n", message, message)
// استنتاج نوع bool
isValid := true
fmt.Printf("IsValid: %t, Type: %T\n", isValid, isValid)
// در مورد اعداد صحیح، اگر نوع صریحاً ذکر نشود، پیشفرض int است.
// اما برای اعداد اعشاری، پیشفرض float64 است.
number := 42 // int
decimal := 3.14 // float64
fmt.Printf("number: %d, Type: %T\n", number, number)
fmt.Printf("decimal: %f, Type: %T\n", decimal, decimal)
}
استنتاج نوع به شما کمک میکند تا کدهای مختصرتری بنویسید، اما در مواردی که نوع دقیق اهمیت دارد (مثلاً برای اطمینان از اینکه متغیر از یک اینترفیس خاص پیروی میکند یا برای کنترل اندازه دقیق حافظه)، استفاده صریح از var
همراه با نوع توصیه میشود.
تبدیل نوع صریح: چرا و چگونه؟
در Go، تبدیل نوع (Type Conversion) بین انواع مختلف باید صریحاً انجام شود. Go از تبدیل نوع ضمنی (Implicit Type Conversion) پشتیبانی نمیکند تا از خطاهای پنهان و از دست دادن داده جلوگیری کند. برای تبدیل یک مقدار از نوع T1
به نوع T2
، از سینتکس T2(value)
استفاده میشود.
package main
import "fmt"
func main() {
var i int = 10
var f float64 = float64(i) // تبدیل int به float64
var u uint = uint(f) // تبدیل float64 به uint (ممکن است منجر به از دست دادن دقت شود)
fmt.Printf("i: %d, Type: %T\n", i, i)
fmt.Printf("f: %f, Type: %T\n", f, f)
fmt.Printf("u: %d, Type: %T\n", u, u)
var str string = "123"
// num := int(str) // این خط باعث خطای کامپایل میشود: cannot convert str (type string) to type int
// برای تبدیل رشته به عدد نیاز به پکیج strconv داریم
num, err := fmt.Sscanf(str, "%d") // مثال ساده (برای تولید واقعی از strconv.Atoi استفاده کنید)
if err == nil {
fmt.Printf("تبدیل رشته به عدد: %d, Type: %T\n", num, num)
}
var bigNum int64 = 1_000_000_000_000
var smallNum int16 = int16(bigNum) // تبدیل int64 به int16 (ممکن است منجر به سرریز شود)
fmt.Printf("bigNum: %d, Type: %T\n", bigNum, bigNum)
fmt.Printf("smallNum: %d, Type: %T (توجه: سرریز رخ داده است)\n", smallNum, smallNum)
// تبدیل string به []byte و برعکس
s := "Hello, Go!"
b := []byte(s) // تبدیل string به slice of bytes
s2 := string(b) // تبدیل slice of bytes به string
fmt.Printf("Original string: %s, bytes: %v, converted back: %s\n", s, b, s2)
// تبدیل int32 (rune) به string (کاراکتر)
r := rune(97) // کد اسکی 'a'
char := string(r)
fmt.Printf("Rune 97 as char: %s\n", char) // a
r2 := '😎' // یک کاراکتر یونیکد
char2 := string(r2)
fmt.Printf("Rune 😎 as char: %s\n", char2) // 😎
}
تبدیل نوع در Go قدرتمند است اما مسئولیت از دست دادن دقت یا سرریز (overflow) را بر عهده توسعهدهنده میگذارد. همیشه هنگام تبدیل بین انواع داده با اندازههای مختلف یا نمایشهای متفاوت، جوانب احتیاط را رعایت کنید.
اهمیت انتخاب نوع داده صحیح و بهینهسازی
انتخاب نوع داده مناسب برای متغیرها و ثابتها بیش از یک تصمیم صرفاً گرامری است؛ این یک تصمیم حیاتی با تأثیرات عمیق بر عملکرد، مصرف حافظه، خوانایی و صحت برنامه شماست. در زبان Go، که به کارایی و همزمانی اهمیت زیادی میدهد، این انتخابها میتوانند تفاوت چشمگیری ایجاد کنند.
کاهش مصرف حافظه (Memory Footprint)
هر نوع داده، مقدار مشخصی از حافظه را اشغال میکند. استفاده از بزرگترین نوع داده ممکن "فقط برای اطمینان" میتواند منجر به مصرف غیرضروری حافظه شود، به خصوص در برنامههایی که با حجم زیادی از دادهها سروکار دارند یا در محیطهای با منابع محدود (مانند سیستمهای جاسازی شده یا میکرو سرویسها). برای مثال:
- اگر میدانید یک عدد هیچگاه از 255 بیشتر نخواهد شد، از
uint8
(1 بایت) به جایint
(که میتواند 4 یا 8 بایت باشد) استفاده کنید. - اگر مقادیر اعشاری شما نیازی به دقت بالا ندارند،
float32
میتواند نیمی از حافظهfloat64
را مصرف کند.
این بهینهسازیها در مقیاس بزرگ میتوانند منجر به صرفهجویی قابل توجه در منابع شوند.
بهبود عملکرد (Performance Implications)
علاوه بر حافظه، انتخاب نوع داده بر سرعت عملیات نیز تأثیر میگذارد. پردازش دادههای کوچکتر معمولاً سریعتر است. عملیات بر روی int64
یا float64
ممکن است به زمان بیشتری نسبت به int32
یا float32
نیاز داشته باشند، هرچند که در پردازندههای 64 بیتی مدرن، اغلب تفاوت ناچیز است.
همچنین، همراستایی دادهها (Data Alignment) و نحوه دسترسی پردازنده به حافظه میتواند تحت تأثیر قرار گیرد. Go بهینهسازیهایی را در این زمینه انجام میدهد، اما درک نحوه عملکرد انواع داده میتواند به شما در طراحی ساختارهایی که حافظه را به طور کارآمدتری استفاده میکنند (مثلاً با گروهبندی فیلدهای با اندازه مشابه در structها) کمک کند.
خوانایی و نگهداری (Readability and Maintainability)
انتخاب نوع داده مناسب، قصد شما را از متغیر شفاف میکند. وقتی یک متغیر از نوع uint8
است، به سرعت میتوان فهمید که این متغیر برای نگهداری مقادیر مثبت کوچک (مثل سن، تعداد آیتمها) در نظر گرفته شده است. این به خوانایی کد کمک کرده و باعث میشود توسعهدهندگان دیگر (و خود شما در آینده) سریعتر کد را درک کنند.
استفاده نادرست از انواع داده میتواند منجر به باگهای پنهان شود، مانند سرریزهای عددی که در زمان کامپایل شناسایی نمیشوند و تنها در زمان اجرا با ورودیهای خاص بروز میکنند. انتخاب صحیح نوع، به نوعی برنامهنویسی دفاعی (Defensive Programming) است.
مثالی از اهمیت انتخاب نوع:
فرض کنید میخواهید یک سیستم مدیریت موجودی انبار بنویسید:
type Product struct {
Name string
Quantity int // آیا int برای موجودی کافی است؟
Price float64 // قیمت میتواند اعشاری باشد
IsAvailable bool
}
در اینجا Quantity
به عنوان int
تعریف شده است. اگر تعداد موجودی یک محصول هیچگاه از 32767 تجاوز نکند، استفاده از int16
میتواند حافظه را نصف کند. اما اگر احتمال دارد موجودی به میلیونها برسد، int
یا حتی int64
ضروری است. انتخاب نادرست int16
در این حالت میتواند منجر به سرریز و نمایش نادرست موجودی شود.
به طور خلاصه، آگاهی از اندازه و محدوده انواع داده Go و پیامدهای انتخاب هر کدام، یک مهارت اساسی برای هر توسعهدهنده Go است. این دانش به شما امکان میدهد تا کدهایی بنویسید که نه تنها صحیح کار میکنند، بلکه بهینه، خوانا و قابل نگهداری نیز هستند.
نتیجهگیری و گامهای بعدی
در این پست، ما به عمق سه مفهوم بنیادی در زبان برنامهنویسی Go پرداختیم: متغیرها، ثابتها و انواع داده. درک صحیح و تسلط بر این ارکان اساسی، نه تنها برای شروع به کار با Go ضروری است، بلکه بنیان محکمی برای ساخت برنامههای پیچیدهتر، کارآمدتر و قابل نگهداری در این زبان فراهم میآورد. ما آموختیم که چگونه دادهها را با متغیرها ذخیره کنیم، چگونه از مقادیر تغییرناپذیر با ثابتها اطمینان حاصل کنیم، و چگونه با انتخاب انواع داده مناسب، دادههای خود را به طور مؤثر طبقهبندی و سازماندهی نماییم.
- متغیرها: دیدیم که Go رویکردهای صریح (با
var
) و کوتاه (با:=
) را برای اعلان و مقداردهی اولیه متغیرها ارائه میدهد، و بر اهمیت نامگذاری مناسب و مدیریت متغیرهای بدون استفاده تأکید کردیم. - ثابتها: با قدرت
const
برای تعریف مقادیر ثابت در زمان کامپایل آشنا شدیم و به ویژه روی انعطافپذیری ثابتهای بدون نوع و کاربردiota
برای سریهای متوالی ثابتها تمرکز کردیم. - انواع داده: سفر خود را از انواع پایه (اعداد صحیح، اعشاری، بولی، رشتهای) آغاز کردیم و به انواع مرکب (آرایهها، اسلایسها، نقشهها، ساختارها) و انواع پیشرفتهتر مانند اشارهگرها، اینترفیسها و حتی کانالها گذر کردیم. هر کدام از این انواع، ابزاری منحصربهفرد برای مدیریت جنبههای مختلف دادهها در Go هستند.
- استنتاج و تبدیل نوع: اهمیت استنتاج نوع برای کوتاهی کد و ضرورت تبدیل نوع صریح برای جلوگیری از خطاهای پنهان را بررسی کردیم.
- اهمیت انتخاب نوع: در نهایت، بر تأثیرگذاری انتخاب نوع داده صحیح بر مصرف حافظه، عملکرد، خوانایی و قابلیت نگهداری کد تأکید نمودیم.
مسیر تسلط بر Go مسیری پلهپله است. این مفاهیم پایه، آجرهای اولیه ساخت هر برنامه Go هستند. اما تنها خواندن کافی نیست؛ گام بعدی و حیاتی، تمرین مداوم و نوشتن کد است. سعی کنید مثالهای ارائهشده را خودتان بنویسید، تغییر دهید، و با سناریوهای مختلف آزمایش کنید. با دست و پنجه نرم کردن با کد، درک شما از این مفاهیم عمیقتر و شهودیتر خواهد شد.
گامهای بعدی در سفر یادگیری Go:
پس از تسلط بر متغیرها، ثابتها و انواع داده، میتوانید به سراغ مباحث زیر بروید:
- توابع (Functions): نحوه تعریف و استفاده از توابع، بازگشت چند مقدار، و پارامترهای متغیر (variadic parameters).
- کنترل جریان (Control Flow): دستورات شرطی (
if-else
)، حلقهها (for
)، و دستورات انتخاب (switch
). - پکیجها (Packages): نحوه سازماندهی کد در پکیجها، ایمپورت کردن پکیجهای دیگر، و مدیریت وابستگیها.
- رسیدگی به خطا (Error Handling): رویکرد منحصر به فرد Go به مدیریت خطاها با استفاده از مقادیر بازگشتی.
- کانالها و گورووتینها (Channels & Goroutines): عمیقتر شدن در مدل همزمانی Go که یکی از نقاط قوت اصلی این زبان است.
با پیگیری این مسیر، به تدریج قادر خواهید بود تا برنامههای Go پیچیدهتر و قویتری را توسعه دهید. به یاد داشته باشید که جامعه Go فعال و منابع آموزشی بسیاری در دسترس هستند. از آنها برای گسترش دانش و حل مشکلات خود استفاده کنید. موفق باشید!
“تسلط به برنامهنویسی پایتون با هوش مصنوعی: آموزش کدنویسی هوشمند با ChatGPT”
"تسلط به برنامهنویسی پایتون با هوش مصنوعی: آموزش کدنویسی هوشمند با ChatGPT"
"با شرکت در این دوره جامع و کاربردی، به راحتی مهارتهای برنامهنویسی پایتون را از سطح مبتدی تا پیشرفته با کمک هوش مصنوعی ChatGPT بیاموزید. این دوره، با بیش از 6 ساعت محتوای آموزشی، شما را قادر میسازد تا به سرعت الگوریتمهای پیچیده را درک کرده و اپلیکیشنهای هوشمند ایجاد کنید. مناسب برای تمامی سطوح با زیرنویس فارسی حرفهای و امکان دانلود و تماشای آنلاین."
ویژگیهای کلیدی:
بدون نیاز به تجربه قبلی برنامهنویسی
زیرنویس فارسی با ترجمه حرفهای
۳۰ ٪ تخفیف ویژه برای دانشجویان و دانش آموزان