فصل دوم - انواع و اعلان‌های از پیش تعریف‌شده

حالا که محیط توسعه خود را راه‌اندازی کرده‌اید، زمان آن رسیده است که شروع به بررسی ویژگی‌های زبان Go و چگونگی بهترین استفاده از آن‌ها کنیم. هنگام تلاش برای درک معنای "بهترین"، یک اصل اساسی وجود دارد: برنامه‌های خود را به گونه‌ای بنویسید که قصد و نیت شما واضح باشد. همان‌طور که ویژگی‌ها را بررسی می‌کنم و گزینه‌ها را مورد بحث قرار می‌دهم، توضیح خواهم داد که چرا یک رویکرد خاص کد واضح‌تری تولید می‌کند.

blogs tailwind section

حالا که محیط توسعه خود را راه‌اندازی کرده‌اید، زمان آن رسیده است که شروع به بررسی ویژگی‌های زبان Go و چگونگی بهترین استفاده از آن‌ها کنیم. هنگام تلاش برای درک معنای "بهترین"، یک اصل اساسی وجود دارد: برنامه‌های خود را به گونه‌ای بنویسید که قصد و نیت شما واضح باشد. همان‌طور که ویژگی‌ها را بررسی می‌کنم و گزینه‌ها را مورد بحث قرار می‌دهم، توضیح خواهم داد که چرا یک رویکرد خاص کد واضح‌تری تولید می‌کند.

با نگاه کردن به انواعی که در Go تعبیه شده‌اند و نحوه اعلان متغیرهای این انواع شروع خواهم کرد. در حالی که هر برنامه‌نویسی با این مفاهیم تجربه دارد، Go کارهایی را متفاوت انجام می‌دهد و تفاوت‌های ظریفی بین Go و سایر زبان‌ها وجود دارد.

انواع از پیش تعریف شده

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

مقدار صفر

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

لیترال‌ها

یک لیترال Go یک عدد، کاراکتر یا رشته صراحتاً مشخص شده است. برنامه‌های Go چهار نوع رایج لیترال دارند. (نوع پنجم نادر لیترال را هنگام بحث در مورد اعداد مختلط پوشش خواهم داد.)

یک لیترال عدد صحیح دنباله‌ای از اعداد است. لیترال‌های عدد صحیح به طور پیش‌فرض مبنای ۱۰ هستند، اما پیشوندهای مختلف برای نشان دادن مبنای‌های دیگر استفاده می‌شوند: 0b برای دودویی (مبنای ۲)، 0o برای هشت‌گانه (مبنای ۸)، یا 0x برای شانزده‌گانه (مبنای ۱۶). می‌توانید از حروف بزرگ یا کوچک برای پیشوند استفاده کنید. یک صفر در ابتدا بدون حرف بعد از آن راه دیگری برای نمایش یک لیترال هشت‌گانه است. از آن استفاده نکنید، زیرا بسیار گیج‌کننده است.

برای آسان‌تر کردن خواندن لیترال‌های عدد صحیح طولانی‌تر، Go به شما اجازه می‌دهد که خط زیر را در وسط لیترال خود قرار دهید. این به شما اجازه می‌دهد، به عنوان مثال، بر اساس هزارها در مبنای ۱۰ گروه‌بندی کنید (1_234). این خط‌های زیر هیچ تأثیری بر مقدار عدد ندارند. تنها محدودیت‌های خط زیر این است که نمی‌توانند در ابتدا یا انتهای اعداد باشند و نمی‌توانید آن‌ها را کنار هم داشته باشید. می‌توانید یک خط زیر بین هر رقم در لیترال خود قرار دهید (1_2_3_4)، اما این کار را نکنید. از آن‌ها برای بهبود خوانایی با تقسیم اعداد مبنای ۱۰ در جای هزارها یا برای تقسیم اعداد دودویی، هشت‌گانه یا شانزده‌گانه در مرزهای ۱، ۲ یا ۴ بایتی استفاده کنید.

یک لیترال اعشاری نقطه اعشار برای نشان دادن بخش کسری مقدار دارد. آن‌ها همچنین می‌توانند نمایی مشخص شده با حرف e و یک عدد مثبت یا منفی داشته باشند (مانند 6.03e23). همچنین گزینه نوشتن آن‌ها در شانزده‌گانه با استفاده از پیشوند 0x و حرف p برای نشان دادن هر نمای را دارید (0x12.34p5، که برابر با ۵۸۲.۵ در مبنای ۱۰ است). مانند لیترال‌های عدد صحیح، می‌توانید از خط زیر برای قالب‌بندی لیترال‌های اعشاری خود استفاده کنید.

یک لیترال rune یک کاراکتر را نمایش می‌دهد و با نقل قول‌های تکی احاطه شده است. برخلاف بسیاری از زبان‌های دیگر، در Go نقل قول‌های تکی و دوتایی قابل تعویض نیستند. لیترال‌های rune می‌توانند به عنوان کاراکترهای تکی یونیکد ('a')، اعداد هشت‌گانه ۸ بیتی ('\141')، اعداد شانزده‌گانه ۸ بیتی ('\x61')، اعداد شانزده‌گانه ۱۶ بیتی ('\u0061')، یا اعداد یونیکد ۳۲ بیتی ('\U00000061') نوشته شوند. همچنین چندین لیترال rune فرار با بک‌اسلش وجود دارد، که مفیدترین آن‌ها خط جدید ('\n')، تب ('\t')، نقل قول تکی ('\'')، و بک‌اسلش ('\\') هستند.

از نظر عملی، از مبنای ۱۰ برای نمایش لیترال‌های عدد صحیح و اعشاری خود استفاده کنید. نمایش‌های هشت‌گانه نادر هستند، عمدتاً برای نمایش مقادیر پرچم مجوز POSIX استفاده می‌شوند (مانند 0o777 برای rwxrwxrwx). شانزده‌گانه و دودویی گاهی اوقات برای فیلترهای بیت یا برنامه‌های شبکه و زیرساخت استفاده می‌شوند. از استفاده از هر یک از فرارهای عددی برای لیترال‌های rune اجتناب کنید، مگر اینکه زمینه کد شما را واضح‌تر کند.

دو راه برای نشان دادن لیترال‌های رشته وجود دارد. بیشتر اوقات، باید از نقل قول‌های دوتایی برای ایجاد یک لیترال رشته تفسیر شده استفاده کنید (به عنوان مثال، "Greetings and Salutations" را تایپ کنید). این‌ها شامل صفر یا بیشتر لیترال rune هستند. آن‌ها "تفسیر شده" نامیده می‌شوند زیرا لیترال‌های rune (هم عددی و هم بک‌اسلش فرار) را به کاراکترهای تکی تفسیر می‌کنند.

یک فرار بک‌اسلش لیترال rune در یک لیترال رشته قانونی نیست: فرار نقل قول تکی. با یک فرار بک‌اسلش برای نقل قول‌های دوتایی جایگزین می‌شود.

تنها کاراکترهایی که نمی‌توانند در یک لیترال رشته تفسیر شده ظاهر شوند، بک‌اسلش‌های فرار نشده، خط‌های جدید فرار نشده، و نقل قول‌های دوتایی فرار نشده هستند. اگر از یک لیترال رشته تفسیر شده استفاده کنید و می‌خواهید سلام‌تان در خطی متفاوت از سلام و درودهایتان باشد و می‌خواهید "Salutations" در نقل قول ظاهر شود، باید "Greetings and\n\"Salutations\"" را تایپ کنید.

اگر نیاز دارید بک‌اسلش، نقل قول دوتایی، یا خط جدید در رشته خود قرار دهید، استفاده از یک لیترال رشته خام آسان‌تر است. این‌ها با بک‌کوت (`) محدود می‌شوند و می‌توانند شامل هر کاراکتری به جز بک‌کوت باشند. هیچ کاراکتر فراری در یک لیترال رشته خام وجود ندارد؛ همه کاراکترها همان‌طور که هستند شامل می‌شوند. هنگام استفاده از یک لیترال رشته خام، شما یک سلام چندخطی را به این صورت می‌نویسید:

`Greetings and
"Salutations"`

لیترال‌ها بی‌نوع در نظر گرفته می‌شوند. این مفهوم را بیشتر در "لیترال‌ها بی‌نوع هستند" در صفحه ۲۷ بررسی خواهم کرد. همان‌طور که در "var در مقابل :=" در صفحه ۲۸ خواهید دید، شرایطی در Go وجود دارد که نوع صراحتاً اعلان نمی‌شود. در آن موارد، Go از نوع پیش‌فرض برای یک لیترال استفاده می‌کند؛ اگر چیزی در عبارت وجود نداشته باشد که نوع لیترال را واضح کند، لیترال به یک نوع پیش‌فرض می‌رود. هنگام بحث در مورد انواع مختلف از پیش تعریف شده، نوع پیش‌فرض برای لیترال‌ها را ذکر خواهم کرد.

مقادیر منطقی

نوع bool متغیرهای منطقی را نمایش می‌دهد. متغیرهای نوع bool می‌توانند یکی از دو مقدار داشته باشند: true یا false. مقدار صفر برای یک bool برابر false است:

var flag bool // هیچ مقداری اختصاص داده نشده، روی false تنظیم شده
var isAwesome = true

سخت است که در مورد انواع متغیر صحبت کنیم بدون اینکه یک اعلان متغیر نشان دهیم، و برعکس. ابتدا از اعلان‌های متغیر استفاده خواهم کرد و آن‌ها را در "var در مقابل :=" در صفحه ۲۸ توصیف خواهم کرد.

انواع عددی

Go دارای تعداد زیادی از انواع عددی است: ۱۲ نوع (و چند نام ویژه) که در سه دسته گروه‌بندی شده‌اند. اگر شما از زبانی مثل JavaScript می‌آیید که تنها با یک نوع عددی کار می‌کند، این ممکن است زیاد به نظر برسد. و در واقع، برخی از انواع بطور مکرر استفاده می‌شوند در حالی که سایرین تخصصی‌تر هستند. من با بررسی انواع صحیح (integer) شروع می‌کنم و سپس به انواع اعشاری (floating-point) و نوع پیچیده‌ی بسیار غیرمعمول (complex) می‌پردازم.

انواع صحیح (Integer types)

Go هم اعداد صحیح با علامت و هم بدون علامت را در اندازه‌های مختلف، از یک تا هشت بایت، ارائه می‌دهد. آنها در جدول ۲-۱ نشان داده شده‌اند.

جدول ۲-۱. انواع صحیح در Go

نام نوعمحدوده مقادیر
int8–128 تا 127
int16–32768 تا 32767
int32–2147483648 تا 2147483647
int64–9223372036854775808 تا 9223372036854775807
uint80 تا 255
uint160 تا 65535
uint320 تا 4294967295
uint640 تا 18446744073709551615

ممکن است از نام مشخص باشد، اما مقدار صفر برای تمام انواع صحیح، 0 است.

انواع ویژه صحیح

Go نام‌های ویژه‌ای برای انواع صحیح دارد. byte یک نام مستعار برای uint8 است؛ انتساب، مقایسه، یا انجام عملیات ریاضی بین byte و uint8 قانونی است. با این حال، شما به ندرت uint8 را در کد Go استفاده شده می‌بینید؛ فقط آن را byte بنامید.

نام ویژه دوم int است. روی یک CPU ۳۲-بیتی، int یک عدد صحیح ۳۲-بیتی با علامت مثل int32 است. روی اکثر CPU های ۶۴-بیتی، int یک عدد صحیح ۶۴-بیتی با علامت است، درست مثل int64. چون int از پلتفرم به پلتفرم یکسان نیست، انتساب، مقایسه، یا انجام عملیات ریاضی بین int و int32 یا int64 بدون تبدیل نوع (type conversion) یک خطای زمان کامپایل است (برای جزئیات بیشتر "تبدیل صریح نوع" در صفحه ۲۶ را ببینید). لیترال‌های صحیح به طور پیش‌فرض از نوع int هستند.

برخی معماری‌های غیرمعمول CPU ۶۴-بیتی از یک عدد صحیح ۳۲-بیتی با علامت برای نوع int استفاده می‌کنند. Go سه تا از آنها را پشتیبانی می‌کند: amd64p32، mips64p32، و mips64p32le.

نام ویژه سوم uint است. این همان قوانین int را دنبال می‌کند، فقط بدون علامت است (مقادیر همیشه ۰ یا مثبت هستند).

دو نام ویژه دیگر برای انواع صحیح وجود دارد، rune و uintptr. شما قبلاً لیترال‌های rune را بررسی کردید و من نوع rune را در "طعمی از رشته‌ها و Rune ها" در صفحه ۲۵ و uintptr را در فصل ۱۶ بحث خواهم کرد.

انتخاب اینکه کدام عدد صحیح استفاده شود

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

• اگر با فرمت فایل باینری یا پروتکل شبکه‌ای کار می‌کنید که عدد صحیحی با اندازه یا علامت مشخص دارد، از نوع صحیح متناظر استفاده کنید.

• اگر تابع کتابخانه‌ای می‌نویسید که باید با هر نوع صحیحی کار کند، از پشتیبانی Generics Go استفاده کنید و یک پارامتر نوع عمومی (generic type parameter) برای نمایش هر نوع صحیحی استفاده کنید (من در فصل ۵ بیشتر در مورد تابع‌ها و پارامترهایشان و در فصل ۸ بیشتر در مورد generics صحبت می‌کنم.)

• در تمام موارد دیگر، فقط از int استفاده کنید.

احتمالاً کد قدیمی پیدا خواهید کرد که جفت تابعی وجود دارد که همان کار را انجام می‌دهند، اما یکی int64 برای پارامترها و متغیرها دارد و دیگری uint64. دلیل این است که API قبل از اضافه شدن generics به Go ایجاد شده است. بدون generics، شما نیاز داشتید تابع‌هایی با نام‌های کمی متفاوت بنویسید تا همان الگوریتم را با انواع مختلف پیاده‌سازی کنید. استفاده از int64 و uint64 به این معنی بود که می‌توانستید کد را یک بار بنویسید و اجازه دهید فراخوان‌کنندگان از تبدیل نوع برای ارسال مقادیر و تبدیل داده‌های بازگردانده شده استفاده کنند.

می‌توانید این الگو را در کتابخانه استاندارد Go در تابع‌های FormatInt و FormatUint در پکیج strconv ببینید.

عملگرهای صحیح

اعداد صحیح Go از عملگرهای ریاضی معمول پشتیبانی می‌کنند: +، -، *، /، با % برای باقی‌مانده. نتیجه تقسیم صحیح یک عدد صحیح است؛ اگر می‌خواهید نتیجه اعشاری بگیرید، نیاز دارید از تبدیل نوع استفاده کنید تا اعداد صحیح خود را به اعداد اعشاری تبدیل کنید.

همچنین، مراقب باشید که عدد صحیحی را بر ۰ تقسیم نکنید؛ این باعث panic می‌شود (من بیشتر در مورد panic ها در "panic and recover" در صفحه ۲۱۸ صحبت می‌کنم).

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

می‌توانید هر کدام از عملگرهای ریاضی را با = ترکیب کنید تا متغیری را تغییر دهید: +=، -=، *=، /=، و %=. برای مثال، کد زیر منجر به داشتن مقدار ۲۰ برای x می‌شود:

var x int = 10
x *= 2

اعداد صحیح را با ==، !=، >، >=، <، و <= مقایسه می‌کنید.

Go همچنین عملگرهای دستکاری بیت برای اعداد صحیح دارد. می‌توانید بیت را با << و >> به چپ و راست جابجا کنید، یا ماسک بیت را با & (AND بیتی)، | (OR بیتی)، ^ (XOR بیتی)، و &^ (AND NOT بیتی) انجام دهید. مانند عملگرهای ریاضی، می‌توانید همه عملگرهای بیتی را با = ترکیب کنید تا متغیری را تغییر دهید: &=، |=، ^=، &^=، <<=، و >>=.

انواع اعشاری

Go دو نوع اعشاری دارد، همان‌طور که در جدول ۲-۲ نشان داده شده است.

جدول ۲-۲. انواع اعشاری در Go

نام نوعبزرگترین مقدار مطلقکوچکترین مقدار مطلق (غیرصفر)
float323.40282346638528859811704183484516925440e+381.401298464324817070923729583289916131280e-45
float641.797693134862315708145274237317043567981e+3084.940656458412465441765687928682213723651e-324

مانند انواع صحیح، مقدار صفر برای انواع اعشاری ۰ است.

اعشار در Go شبیه ریاضی اعشار در زبان‌های دیگر است. Go از مشخصات IEEE 754 استفاده می‌کند، که محدوده بزرگ و دقت محدود می‌دهد. انتخاب اینکه کدام نوع اعشاری استفاده شود ساده است: مگر اینکه مجبور باشید با فرمت موجودی سازگار باشید، از float64 استفاده کنید. لیترال‌های اعشاری نوع پیش‌فرض float64 دارند، بنابراین همیشه استفاده از float64 ساده‌ترین گزینه است. همچنین کمک می‌کند مسائل دقت اعشاری را کاهش دهد چرا که float32 تنها شش یا هفت رقم اعشار دقت دارد. نگران تفاوت در اندازه حافظه نباشید مگر اینکه از profiler استفاده کرده باشید تا مشخص کنید که منبع مهمی از مشکلات است. (تست و profiling در فصل ۱۵ پوشش داده شده است.)

سوال بزرگتر این است که آیا اصلاً باید از عدد اعشاری استفاده کنید یا نه. در بسیاری موارد، پاسخ خیر است. درست مثل زبان‌های دیگر، اعداد اعشاری Go محدوده عظیمی دارند، اما نمی‌توانند هر مقداری در آن محدوده را ذخیره کنند؛ آنها نزدیکترین تقریب را ذخیره می‌کنند. چون اعشارها دقیق نیستند، تنها می‌توانند در موقعیت‌هایی استفاده شوند که مقادیر غیردقیق قابل قبول هستند یا قوانین اعشار به خوبی درک شده باشند. این آنها را به چیزهایی مثل گرافیک، آمار، و عملیات علمی محدود می‌کند.

یک عدد اعشاری نمی‌تواند مقدار اعشاری را دقیقاً نمایش دهد. از آنها برای نمایش پول یا هر مقدار دیگری که باید نمایش اعشاری دقیق داشته باشد استفاده نکنید! شما در "وارد کردن کد شخص ثالث" در صفحه ۲۴۰ به ماژول شخص ثالث برای مدیریت مقادیر اعشاری دقیق نگاه خواهید کرد.

IEEE 754

همان‌طور که قبلاً اشاره شد، Go (و بیشتر زبان‌های برنامه‌نویسی دیگر) اعداد اعشاری را با استفاده از مشخصاتی به نام IEEE 754 ذخیره می‌کند.

قوانین واقعی خارج از محدوده این کتاب هستند و ساده نیستند. شما می‌تونید بیشتر درباره IEEE 754 از راهنمای نقطه شناور (The Floating Point Guide) یاد بگیرید.

شما می‌تونید تمام عملگرهای ریاضی و مقایسه‌ای استاندارد را با اعداد اعشاری استفاده کنید، به جز %. تقسیم نقطه شناور چند ویژگی جالب داره. تقسیم یک متغیر نقطه شناور غیرصفر بر ۰، +Inf یا -Inf (بی‌نهایت مثبت یا منفی) برمی‌گردونه، بسته به علامت عدد. تقسیم یک متغیر نقطه شناور که روی ۰ تنظیم شده بر ۰، NaN (عددی نیست - Not a Number) برمی‌گردونه.

در حالی که Go به شما اجازه استفاده از == و != برای مقایسه اعداد اعشاری رو می‌ده، این کار رو نکنید. به دلیل ماهیت غیردقیق اعداد اعشاری، دو مقدار نقطه شناور ممکنه وقتی که فکر می‌کنید باید برابر باشن، برابر نباشن. در عوض، یک حداکثر واریانس مجاز تعریف کنید و ببینید آیا تفاوت بین دو عدد اعشاری کمتر از اون مقداره یا نه. این مقدار (که گاهی اپسیلون نامیده می‌شه) به نیازهای دقت شما بستگی داره؛ من نمی‌تونم یک قانون ساده بهتون بدم. اگه مطمئن نیستید، از ریاضی‌دان محلی دوستانه‌تون مشاوره بگیرید. اگه نمی‌تونید پیداش کنید، راهنمای نقطه شناور صفحه "مقایسه" داره که می‌تونه کمکتون کنه (یا احتمالاً شما رو متقاعد کنه که از اعداد نقطه شناور اجتناب کنید مگر اینکه کاملاً ضروری باشه).

انواع پیچیده (احتمالاً ازشون استفاده نخواهید کرد)

یک نوع عددی دیگه هست و اون خیلی غیرمعموله. Go پشتیبانی درجه یک برای اعداد مختلط داره. اگه نمی‌دونید اعداد مختلط چی هستن، شما مخاطب هدف این ویژگی نیستید؛ آزادانه بخش بعدی رو بخونید.

چیز زیادی در پشتیبانی اعداد مختلط در Go وجود نداره. Go دو نوع عدد مختلط تعریف می‌کنه. complex64 از مقادیر float32 برای نمایش بخش حقیقی و موهومی استفاده می‌کنه، و complex128 از مقادیر float64 استفاده می‌کنه. هر دو با تابع داخلی complex اعلام می‌شن:

var complexNum = complex(20.3, 10.2)

Go از چند قانون برای تعیین نوع مقدار برگردانده شده توسط complex استفاده می‌کنه:

• اگه از ثابت‌های بدون نوع یا literals برای هر دو پارامتر تابع استفاده کنید، یک literal مختلط بدون نوع ایجاد می‌کنید که نوع پیش‌فرضش complex128 هست.

• اگه هر دو مقدار ارسال شده به complex از نوع float32 باشن، یک complex64 ایجاد می‌کنید.

• اگه یک مقدار float32 باشه و مقدار دیگه یک ثابت بدون نوع یا literal باشه که می‌تونه داخل float32 جا بشه، یک complex64 ایجاد می‌کنید.

• در غیر این صورت، یک complex128 ایجاد می‌کنید.

تمام عملگرهای حسابی استاندارد نقطه شناور روی اعداد مختلط کار می‌کنن. درست مثل اعداد اعشاری، شما می‌تونید از == یا != برای مقایسه‌شون استفاده کنید، اما اونا همون محدودیت‌های دقت رو دارن، پس بهتره از تکنیک اپسیلون استفاده کنید. می‌تونید بخش‌های حقیقی و موهومی یک عدد مختلط رو با توابع داخلی real و imag استخراج کنید. پکیج math/cmplx توابع اضافی برای دستکاری مقادیر complex128 داره.

مقدار صفر برای هر دو نوع اعداد مختلط، ۰ به هر دو بخش حقیقی و موهومی عدد اختصاص داده شده.

مثال ۲-۱ یک برنامه ساده رو نشون می‌ده که نحوه کار اعداد مختلط رو نمایش می‌ده. می‌تونید خودتون اجراش کنید در The Go Playground یا در دایرکتوری sample_code/complex_numbers در مخزن فصل ۲.

مثال ۲-۱. اعداد مختلط

func main() {
    x := complex(2.5, 3.1)
    y := complex(10.2, 2)
    fmt.Println(x + y)
    fmt.Println(x - y)
    fmt.Println(x * y)
    fmt.Println(x / y)
    fmt.Println(real(x))
    fmt.Println(imag(x))
    fmt.Println(cmplx.Abs(x))
}

اجرای این کد خروجی زیر رو می‌ده:

(12.7+5.1i)
(-7.699999999999999+1.1i)
(19.3+36.62i)
(0.2934098482043688+0.24639022584228065i)
2.5
3.1
3.982461550347975

اینجا هم می‌تونید عدم دقت نقطه شناور رو ببینید.

اگه تعجب می‌کردید پنجمین نوع literal اولیه چی بود، Go از imaginary literals برای نمایش بخش موهومی یک عدد مختلط پشتیبانی می‌کنه. اونا درست مثل literals نقطه شناور به نظر می‌رسن، اما پسوند i دارن.

علی‌رغم داشتن اعداد مختلط به عنوان نوع از پیش اعلام شده، Go زبان محبوبی برای محاسبات عددی نیست. پذیرش محدود بوده چون ویژگی‌های دیگه (مثل پشتیبانی ماتریس) بخشی از زبان نیستن و کتابخونه‌ها مجبورن از جایگزین‌های ناکارآمد استفاده کنن، مثل slices از slices. (به slices در فصل ۳ و نحوه پیاده‌سازیشون در فصل ۶ نگاه خواهید کرد.) اما اگه نیاز دارید مجموعه Mandelbrot رو به عنوان بخشی از یک برنامه بزرگ‌تر محاسبه کنید، یا یک حل‌کننده معادله درجه دو پیاده‌سازی کنید، پشتیبانی اعداد مختلط برای شما اونجاست.

شاید تعجب کنید چرا Go اعداد مختلط رو شامل می‌کنه. جواب سادست: کن تامپسون، یکی از سازندگان Go (و Unix)، فکر می‌کرد جالب خواهند بود. بحث‌هایی درباره حذف اعداد مختلط از نسخه آینده Go بوده، اما آسون‌تره که فقط این ویژگی رو نادیده بگیریم.

اگه می‌خواید اپلیکیشن‌های محاسبات عددی در Go بنویسید، می‌تونید از پکیج شخص ثالث Gonum استفاده کنید. اون از اعداد مختلط استفاده می‌کنه و کتابخونه‌های مفیدی برای چیزهایی مثل جبر خطی، ماتریس‌ها، انتگرال‌گیری، و آمار ارائه می‌ده. اما باید ابتدا زبان‌های دیگه رو در نظر بگیرید.

طعمی از رشته‌ها و Runeها

این ما رو به رشته‌ها می‌رسونه. مثل بیشتر زبان‌های مدرن، Go رشته‌ها رو به عنوان نوع داخلی شامل می‌کنه. مقدار صفر برای یک رشته، رشته خالیه. Go از Unicode پشتیبانی می‌کنه؛ همان‌طور که در "Literals" صفحه ۱۸ نشون دادم، می‌تونید هر کاراکتر Unicode رو داخل یک رشته قرار بدید. مثل اعداد صحیح و اعشاری، رشته‌ها برای برابری با == ، تفاوت با != ، یا ترتیب با >، >=، <، یا <= مقایسه می‌شن. اونا با استفاده از عملگر + به هم متصل می‌شن.

رشته‌ها در Go تغییرناپذیر هستن؛ می‌تونید مقدار یک متغیر رشته رو دوباره اختصاص بدید، اما نمی‌تونید مقدار رشته‌ای که بهش اختصاص داده شده رو تغییر بدید.

Go همچنین نوعی داره که یک نقطه کد واحد رو نمایش می‌ده. نوع rune نامی مستعار برای نوع int32 هست، درست مثل اینکه byte نامی مستعار برای uint8 هست. همان‌طور که احتمالاً حدس زدید، نوع پیش‌فرض یک rune literal، rune هست، و نوع پیش‌فرض یک string literal، string هست.

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

var myFirstInitial rune = 'J' // خوب - نام نوع با استفاده مطابقت داره
var myLastInitial int32 = 'B' // بد - قانونی اما گیج‌کننده

قراره خیلی بیشتر درباره رشته‌ها در فصل بعد صحبت کنم، و جزئیات پیاده‌سازی، روابط با بایت‌ها و rune ها، و همچنین ویژگی‌های پیشرفته و مشکلات رو پوشش بدم.

تبدیل صریح نوع (Explicit Type Conversion)

اکثر زبان‌هایی که دارای انواع عددی متعددی هستند، به طور خودکار یک نوع را به نوع دیگر تبدیل می‌کنند در صورت نیاز. این موضوع تبلیغ خودکار نوع (automatic type promotion) نامیده می‌شود، و در حالی که بسیار راحت به نظر می‌رسد، مشخص شده است که قوانین تبدیل صحیح یک نوع به نوع دیگر می‌تواند پیچیده شود و نتایج غیرمنتظره‌ای تولید کند. به عنوان زبانی که وضوح هدف و خوانایی را ارزشمند می‌داند، Go اجازه تبلیغ خودکار نوع بین متغیرها را نمی‌دهد. شما باید از تبدیل نوع استفاده کنید زمانی که انواع متغیرها مطابقت ندارند. حتی اعداد صحیح و اعشاری با اندازه‌های مختلف باید به همان نوع تبدیل شوند تا بتوانند با هم تعامل کنند. این موضوع دقیقاً مشخص می‌کند که چه نوعی می‌خواهید بدون اینکه نیاز باشد قوانین تبدیل نوع را به خاطر بسپارید (مثال ۲-۲ را ببینید).

مثال ۲-۲. تبدیل‌های نوع

var x int = 10
var y float64 = 30.2
var sum1 float64 = float64(x) + y
var sum2 int = x + int(y)
fmt.Println(sum1, sum2)

در این کد نمونه، شما چهار متغیر تعریف می‌کنید. x یک int با مقدار ۱۰ است، و y یک float64 با مقدار ۳۰.۲ است. از آنجا که اینها انواع یکسانی نیستند، نیاز دارید آنها را تبدیل کنید تا بتوانید آنها را با هم جمع کنید. برای sum1، شما x را به float64 با استفاده از تبدیل نوع float64 تبدیل می‌کنید، و برای sum2، y را به int با استفاده از تبدیل نوع int تبدیل می‌کنید. وقتی این کد را اجرا می‌کنید، خروجی ۴۰.۲ ۴۰ چاپ می‌شود.

همین رفتار برای انواع اعداد صحیح با اندازه‌های مختلف اعمال می‌شود (مثال ۲-۳ را ببینید).

مثال ۲-۳. تبدیل‌های نوع اعداد صحیح

var x int = 10
var b byte = 100
var sum3 int = x + int(b)
var sum4 byte = byte(x) + b
fmt.Println(sum3, sum4)

شما می‌توانید این مثال‌ها را در The Go Playground یا در دایرکتوری sample_code/type_conversion در مخزن فصل ۲ اجرا کنید.

این سختگیری در مورد انواع پیامدهای دیگری دارد. از آنجا که تمام تبدیل‌های نوع در Go صریح هستند، نمی‌توانید نوع دیگری از Go را به عنوان boolean در نظر بگیرید. در بسیاری از زبان‌ها، یک عدد غیرصفر یا یک رشته غیرخالی می‌تواند به عنوان boolean true تفسیر شود. درست مانند تبلیغ خودکار نوع، قوانین برای مقادیر "truthy" از زبانی به زبان دیگر متفاوت است و می‌تواند گیج‌کننده باشد. طبیعی است که Go اجازه truthiness نمی‌دهد. در واقع، هیچ نوع دیگری نمی‌تواند به bool تبدیل شود، نه به طور ضمنی و نه به طور صریح. اگر می‌خواهید از نوع داده دیگری به boolean تبدیل کنید، باید از یکی از عملگرهای مقایسه (==، !=، >، <، <=، یا >=) استفاده کنید. به عنوان مثال، برای بررسی اینکه آیا متغیر x برابر با ۰ است، کد x == 0 خواهد بود. اگر می‌خواهید بررسی کنید که رشته s خالی است، از s == "" استفاده کنید.

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

لیترال‌ها بدون نوع هستند (Literals Are Untyped)

در حالی که نمی‌توانید دو متغیر عدد صحیح را با هم جمع کنید اگر آنها به عنوان انواع مختلفی از اعداد صحیح تعریف شده باشند، Go به شما اجازه می‌دهد از یک لیترال عدد صحیح در عبارات اعشاری استفاده کنید یا حتی یک لیترال عدد صحیح را به یک متغیر اعشاری اختصاص دهید:

var x float64 = 10
var y float64 = 200.3 * 5

این به این دلیل است که، همانطور که قبلاً ذکر کردم، لیترال‌ها در Go بدون نوع هستند. Go یک زبان عملی است، و منطقی است که از اجبار یک نوع تا زمانی که توسعه‌دهنده یکی را مشخص کند، اجتناب کنیم. این بدان معناست که آنها می‌توانند با هر متغیری که نوعش با لیترال سازگار است، استفاده شوند. وقتی در فصل ۷ به انواع تعریف‌شده توسط کاربر نگاه می‌کنید، خواهید دید که حتی می‌توانید لیترال‌ها را با انواع تعریف‌شده توسط کاربر بر اساس انواع از پیش تعریف‌شده استفاده کنید. بدون نوع بودن تا حدی پیش می‌رود؛ نمی‌توانید یک لیترال رشته را به متغیری با نوع عددی اختصاص دهید یا یک لیترال عدد را به یک متغیر رشته، همچنین نمی‌توانید یک لیترال اعشاری را به int اختصاص دهید. اینها همه توسط کامپایلر به عنوان خطا علامت‌گذاری می‌شوند. محدودیت‌های اندازه نیز وجود دارد؛ در حالی که می‌توانید لیترال‌های عددی بنویسید که بزرگتر از هر عدد صحیحی که می‌تواند نگه دارد هستند، خطای زمان کامپایل است که سعی کنید لیترالی را اختصاص دهید که مقدارش از متغیر مشخص‌شده سرریز می‌کند، مانند سعی برای اختصاص لیترال ۱۰۰۰ به متغیری از نوع byte.

var در مقابل :=

برای یک زبان کوچک، Go راه‌های زیادی برای تعریف متغیرها دارد. دلیلی برای این موضوع وجود دارد: هر سبک تعریف چیزی در مورد نحوه استفاده از متغیر ارتباط برقرار می‌کند. بیایید راه‌هایی که می‌توانید در Go متغیر تعریف کنید را بررسی کنیم و ببینیم هر کدام چه زمانی مناسب است.

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

var x int = 10

اگر نوع در سمت راست = نوع مورد انتظار متغیر شما باشد، می‌توانید نوع را از سمت چپ = حذف کنید. از آنجا که نوع پیش‌فرض یک لیترال عدد صحیح int است، موارد زیر x را به عنوان متغیری از نوع int تعریف می‌کند:

var x = 10

برعکس، اگر می‌خواهید متغیری را تعریف کنید و مقدار صفر را به آن اختصاص دهید، می‌توانید نوع را نگه دارید و = در سمت راست را حذف کنید:

var x int

می‌توانید چندین متغیر را همزمان با var تعریف کنید، و آنها می‌توانند از همان نوع باشند:

var x, y int = 10, 20

می‌توانید همه مقادیر صفر از همان نوع را تعریف کنید:

var x, y int

یا از انواع مختلف:

var x, y = 10, "hello"

یک راه دیگر برای استفاده از var وجود دارد. اگر چندین متغیر را همزمان تعریف می‌کنید، می‌توانید آنها را در یک لیست تعریف قرار دهید:

var (
    x    int
    y        = 20
    z    int = 30
    d, e     = 40, "hello"
    f, g string
)

Go همچنین از فرمت کوتاه تعریف و اختصاص پشتیبانی می‌کند. وقتی درون یک تابع هستید، می‌توانید از عملگر := برای جایگزینی تعریف var که از استنتاج نوع استفاده می‌کند، استفاده کنید. دو دستور زیر دقیقاً همان کار را انجام می‌دهند—آنها x را به عنوان int با مقدار ۱۰ تعریف می‌کنند:

var x = 10
x := 10

همانند var، می‌توانید چندین متغیر را همزمان با استفاده از := تعریف کنید. این دو خط هر دو ۱۰ را به x و "hello" را به y اختصاص می‌دهند:

var x, y = 10, "hello"
x, y := 10, "hello"

عملگر := می‌تواند ترفندی انجام دهد که با var نمی‌توانید انجام دهید: این به شما اجازه می‌دهد که مقادیر را به متغیرهای موجود نیز اختصاص دهید. تا زمانی که حداقل یک متغیر جدید در سمت چپ := باشد، هر یک از متغیرهای دیگر می‌توانند از قبل وجود داشته باشند:

x := 10
x, y := 30, "hello"

استفاده از := یک محدودیت دارد. اگر متغیری را در سطح بسته تعریف می‌کنید، باید از var استفاده کنید زیرا := خارج از توابع قانونی نیست.

چگونه می‌دانید کدام سبک را استفاده کنید؟ همانطور که همیشه، آنچه را که هدف شما را واضح‌تر می‌کند انتخاب کنید. رایج‌ترین سبک تعریف درون توابع := است. خارج از تابع، از لیست‌های تعریف در مواقع نادری که چندین متغیر سطح بسته تعریف می‌کنید استفاده کنید.

در برخی موقعیت‌ها درون توابع، باید از := اجتناب کنید:

• وقتی متغیری را به مقدار صفر آن مقداردهی اولیه می‌کنید، از var x int استفاده کنید. این واضح می‌کند که مقدار صفر مورد نظر است.

• وقتی ثابت بدون نوع یا لیترال را به متغیری اختصاص می‌دهید و نوع پیش‌فرض برای ثابت یا لیترال، نوعی که برای متغیر می‌خواهید نیست، از فرم طولانی var با نوع مشخص‌شده استفاده کنید. در حالی که استفاده از تبدیل نوع برای مشخص کردن نوع مقدار و استفاده از := برای نوشتن x := byte(20) قانونی است، ایدیوماتیک است که var x byte = 20 بنویسید.

• از آنجا که := به شما اجازه می‌دهد هم به متغیرهای جدید و هم موجود اختصاص دهید، گاهی متغیرهای جدیدی ایجاد می‌کند وقتی فکر می‌کنید از موجودی‌ها استفاده مجدد می‌کنید (برای جزئیات "سایه‌گذاری متغیرها" در صفحه ۶۸ را ببینید). در آن موقعیت‌ها، صراحتاً تمام متغیرهای جدید خود را با var تعریف کنید تا واضح شود کدام متغیرها جدید هستند، و سپس از عملگر اختصاص (=) برای اختصاص مقادیر به متغیرهای جدید و قدیمی استفاده کنید.

در حالی که var و := به شما اجازه می‌دهند چندین متغیر را در همان خط تعریف کنید، از این سبک فقط وقتی استفاده کنید که چندین مقدار برگردانده‌شده از یک تابع یا اصطلاح comma ok را اختصاص می‌دهید (فصل ۵ و "اصطلاح comma ok" در صفحه ۵۸ را ببینید).

شما به ندرت باید متغیرها را خارج از توابع، در آنچه بلوک بسته نامیده می‌شود، تعریف کنید ("بلوک‌ها" در صفحه ۶۷ را ببینید). متغیرهای سطح بسته که مقادیرشان تغییر می‌کند ایده بدی هستند. وقتی متغیری خارج از تابع دارید، ردیابی تغییرات اعمال‌شده بر آن دشوار می‌تواند باشد، که درک نحوه جریان داده‌ها در برنامه شما را دشوار می‌کند. این می‌تواند منجر به باگ‌های ظریف شود. به عنوان قانون کلی، فقط باید متغیرهایی را در بلوک بسته تعریف کنید که عملاً تغییرناپذیر هستند.

از تعریف متغیرها خارج از توابع اجتناب کنید زیرا آنها تحلیل جریان داده را پیچیده می‌کنند.

ممکن است تعجب کنید: آیا Go راهی برای اطمینان از اینکه یک مقدار تغییرناپذیر است فراهم می‌کند؟ بله، اما کمی متفاوت از آنچه ممکن است در سایر زبان‌های برنامه‌نویسی دیده باشید. زمان آن رسیده که در مورد const یاد بگیریم.

استفاده از const

بسیاری از زبان‌ها راهی برای اعلان یک مقدار به عنوان تغییرناپذیر (immutable) دارند. در Go، این کار با کلیدواژه const انجام می‌شود. در نگاه اول، به نظر می‌رسد که دقیقاً همانطور که در سایر زبان‌ها کار می‌کند، عمل می‌کند. کد موجود در مثال ۲-۴ را در Go Playground یا در دایرکتوری sample_code/const_declaration در مخزن فصل ۲ امتحان کنید.

مثال ۲-۴. اعلان‌های const

package main

import "fmt"

const x int64 = 10

const (
    idKey   = "id"
    nameKey = "name"
)

const z = 20 * 10

func main() {
    const y = "hello"

    fmt.Println(x)
    fmt.Println(y)

    x = x + 1 // این کامپایل نخواهد شد!
    y = "bye" // این کامپایل نخواهد شد!

    fmt.Println(x)
    fmt.Println(y)
}

وقتی این کد را اجرا می‌کنید، کامپایل با پیام‌های خطای زیر شکست می‌خورد:

./prog.go:20:2: cannot assign to x (constant 10 of type int64)
./prog.go:21:2: cannot assign to y (untyped string constant "hello")

همانطور که می‌بینید، می‌توانید یک ثابت را در سطح بسته یا درون یک تابع اعلان کنید. درست مانند var، می‌توانید (و باید) گروهی از ثابت‌های مرتبط را درون مجموعه‌ای از پرانتزها اعلان کنید.

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

• لیترال‌های عددی • true و false • رشته‌ها • رون‌ها • مقادیر برگردانده شده توسط توابع داخلی complex، real، imag، len و cap • عبارت‌هایی که از اپراتورها و مقادیر قبلی تشکیل شده‌اند

توابع len و cap را در فصل بعدی پوشش خواهم داد. مقدار دیگری که می‌تواند با const استفاده شود، iota نامیده می‌شود. درباره iota صحبت خواهم کرد وقتی که ایجاد انواع خودتان را در فصل ۷ بحث کنم.

Go راهی برای مشخص کردن اینکه مقداری که در زمان اجرا محاسبه می‌شود، تغییرناپذیر است، ارائه نمی‌دهد. برای مثال، کد زیر با خطای x + y (value of type int) is not constant کامپایل نخواهد شد:

x := 5
y := 10
const z = x + y // این کامپایل نخواهد شد!

همانطور که در فصل بعدی خواهید دید، هیچ آرایه، برش، نقشه یا ساختار تغییرناپذیری وجود ندارد، و راهی برای اعلان اینکه فیلدی در یک ساختار تغییرناپذیر است، وجود ندارد. این کمتر از آنچه به نظر می‌رسد محدودکننده است. درون یک تابع، واضح است که آیا یک متغیر در حال تغییر است یا خیر، بنابراین تغییرناپذیری اهمیت کمتری دارد. در "Go فراخوانی بر اساس مقدار است" در صفحه ۱۱۴، خواهید دید که چگونه Go از تغییرات متغیرهایی که به عنوان پارامتر به توابع ارسال می‌شوند، جلوگیری می‌کند.

ثابت‌ها در Go راهی برای دادن نام به لیترال‌ها هستند. هیچ راهی در Go برای اعلان اینکه یک متغیر تغییرناپذیر است، وجود ندارد.

ثابت‌های تایپ‌شده و تایپ‌نشده

ثابت‌ها می‌توانند تایپ‌شده یا تایپ‌نشده باشند. یک ثابت تایپ‌نشده دقیقاً مانند یک لیترال کار می‌کند؛ نوع خاص خودش را ندارد اما نوع پیش‌فرضی دارد که زمانی استفاده می‌شود که هیچ نوع دیگری قابل استنباط نباشد. یک ثابت تایپ‌شده فقط می‌تواند مستقیماً به متغیری از همان نوع مقداردهی شود.

اینکه آیا ثابتی را تایپ‌شده کنیم یا نه، بستگی به دلیل اعلان ثابت دارد. اگر در حال دادن نام به یک ثابت ریاضی هستید که می‌تواند با انواع عددی متعدد استفاده شود، ثابت را تایپ‌نشده نگه دارید. به طور کلی، تایپ‌نشده نگه داشتن یک ثابت انعطاف بیشتری به شما می‌دهد. در موقعیت‌های خاصی، می‌خواهید ثابتی نوعی را اجباری کند. زمانی که شمارش‌ها با iota را در "iota برای شمارش‌هاست—گاهی اوقات" در صفحه ۱۵۲ پوشش دهم، کاربردی برای ثابت‌های تایپ‌شده خواهید دید.

اینطور است که اعلان یک ثابت تایپ‌نشده به نظر می‌رسد:

const x = 10

همه مقداردهی‌های زیر قانونی هستند:

var y int = x
var z float64 = x
var d byte = x

اینطور است که اعلان یک ثابت تایپ‌شده به نظر می‌رسد:

const typedX int = 10

این ثابت فقط می‌تواند مستقیماً به یک int مقداردهی شود. مقداردهی آن به هر نوع دیگری خطای زمان کامپایل مانند این ایجاد می‌کند:

cannot use typedX (type int) as type float64 in assignment

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

یکی از اهداف Go این است که همکاری تیم‌های بزرگ بر روی برنامه‌ها را آسان‌تر کند. برای انجام این کار، Go قوانینی دارد که در میان زبان‌های برنامه‌نویسی منحصر به فرد هستند. در فصل ۱، دیدید که برنامه‌های Go باید به روشی خاص با go fmt قالب‌بندی شوند تا نوشتن ابزارهای دستکاری کد آسان‌تر شود و استانداردهای کدنویسی ارائه دهد. الزام دیگر Go این است که هر متغیر محلی اعلان‌شده باید خوانده شود. اعلان یک متغیر محلی و نخواندن مقدار آن، خطای زمان کامپایل است.

بررسی متغیرهای استفاده‌نشده کامپایلر جامع نیست. تا زمانی که یک متغیر یک بار خوانده شود، کامپایلر شکایت نخواهد کرد، حتی اگر نوشته‌هایی به متغیر وجود داشته باشد که هرگز خوانده نمی‌شوند. موارد زیر یک برنامه Go معتبر است که می‌توانید آن را در Go Playground یا در دایرکتوری sample_code/assignments_not_read در مخزن فصل ۲ اجرا کنید:

func main() {
    x := 10 // این مقداردهی خوانده نمی‌شود!
    x = 20
    fmt.Println(x)
    x = 30 // این مقداردهی خوانده نمی‌شود!
}

در حالی که کامپایلر و go vet مقداردهی‌های استفاده‌نشده ۱۰ و ۳۰ به x را تشخیص نمی‌دهند، ابزارهای شخص ثالث می‌توانند آنها را تشخیص دهند. درباره این ابزارها در "استفاده از اسکنرهای کیفیت کد" در صفحه ۲۶۷ صحبت خواهم کرد.

کامپایلر Go شما را از ایجاد متغیرهای سطح بسته خوانده‌نشده باز نمی‌دارد. این یکی دیگر از دلایلی است که باید از ایجاد متغیرهای سطح بسته اجتناب کنید.

ثابت‌های استفاده‌نشده

شاید تعجب‌آور باشد، کامپایلر Go به شما اجازه می‌دهد ثابت‌های خوانده‌نشده با const ایجاد کنید. این به این دلیل است که ثابت‌ها در Go در زمان کامپایل محاسبه می‌شوند و نمی‌توانند هیچ اثر جانبی داشته باشند. این باعث می‌شود که حذف آنها آسان باشد: اگر ثابتی استفاده نشود، به سادگی در باینری کامپایل‌شده گنجانده نمی‌شود.

نام‌گذاری متغیرها و ثوابت

تفاوتی بین قوانین Go برای نام‌گذاری متغیرها و الگوهایی که توسعه‌دهندگان Go هنگام نام‌گذاری متغیرها و ثوابت خود دنبال می‌کنند، وجود دارد. مانند اکثر زبان‌ها، Go نیاز دارد که نام‌های شناسه با یک حرف یا خط زیر شروع شوند، و نام می‌تواند شامل اعداد، خط زیر، و حروف باشد. تعریف Go از «حرف» و «عدد» کمی گسترده‌تر از بسیاری از زبان‌ها است. هر کاراکتر یونیکد که حرف یا رقم محسوب می‌شود، مجاز است. این امر همه تعاریف متغیر در مثال ۲-۵ را کاملاً معتبر در Go می‌کند.

مثال ۲-۵. نام‌های متغیری که هرگز نباید استفاده کنید

_0 := 0_0
_1 := 20
π := 3
:= "hello" // Unicode U+FF41
__ := "double underscore" // two underscores
fmt.Println(_0)
fmt.Println(_1)
fmt.Println(π)
fmt.Println(a)
fmt.Println(__)

می‌توانید این کد وحشتناک را در The Go Playground آزمایش کنید. در حالی که کار می‌کند، از نام‌های متغیری مانند این استفاده نکنید. این نام‌ها غیرمتعارف محسوب می‌شوند زیرا قانون اساسی اطمینان از اینکه کد شما آنچه را که انجام می‌دهد ارتباط برقرار کند، را نقض می‌کنند. این نام‌ها گیج‌کننده هستند یا تایپ کردن آن‌ها در بسیاری از صفحه‌کلیدها دشوار است. نقاط کد یونیکد شبیه‌به‌هم بدخیم‌ترین هستند، زیرا حتی اگر به نظر همان کاراکتر باشند، نمایانگر متغیرهای کاملاً متفاوتی هستند. می‌توانید کد نشان داده شده در مثال ۲-۶ را در The Go Playground یا در دایرکتوری sample_code/look_alike_code_points در ریپازیتوری فصل ۲ اجرا کنید.

مثال ۲-۶. استفاده از نقاط کد شبیه‌به‌هم برای نام‌های متغیر

func main() {
:= "hello"   // Unicode U+FF41
    a := "goodbye" // standard lowercase a (Unicode U+0061)
    fmt.Println(a)
    fmt.Println(a)
}

هنگامی که این برنامه را اجرا می‌کنید، نتیجه زیر را دریافت می‌کنید:

hello
goodbye

حتی اگرچه خط زیر کاراکتر معتبری در نام متغیر است، به ندرت استفاده می‌شود، زیرا Go متعارف از snake case (نام‌هایی مانند index_counter یا number_tries) استفاده نمی‌کند. در عوض، Go متعارف از camel case (نام‌هایی مانند indexCounter یا numberTries) استفاده می‌کند زمانی که نام شناسه از چندین کلمه تشکیل شده باشد.

خط زیر به تنهایی (_) نام شناسه خاصی در Go است؛ وقتی توابع را در فصل ۵ پوشش می‌دهم، بیشتر در مورد آن صحبت خواهم کرد.

در بسیاری از زبان‌ها، ثوابت همیشه با حروف بزرگ نوشته می‌شوند، با کلمات جدا شده توسط خط زیر (نام‌هایی مانند INDEX_COUNTER یا NUMBER_TRIES). Go این الگو را دنبال نمی‌کند. این به این دلیل است که Go از بزرگی/کوچکی حرف اول در نام اعلان سطح بسته برای تعیین اینکه آیا آیتم خارج از بسته قابل دسترسی است یا نه، استفاده می‌کند. وقتی در فصل ۱۰ در مورد بسته‌ها صحبت می‌کنم، به این موضوع بازخواهم گشت.

درون یک تابع، نام‌های متغیر کوتاه را ترجیح دهید. هرچه دامنه یک متغیر کوچک‌تر باشد، نام کوتاه‌تری برای آن استفاده می‌شود. در Go معمول است که نام‌های متغیر تک‌حرفی با حلقه‌های for استفاده شوند. برای مثال، نام‌های k و v (مخفف key و value) به عنوان نام‌های متغیر در حلقه for-range استفاده می‌شوند. اگر از حلقه for استاندارد استفاده می‌کنید، i و j نام‌های رایج برای متغیر شاخص هستند. روش‌های متعارف دیگری برای نام‌گذاری متغیرهای انواع رایج وجود دارد؛ هنگامی که بخش‌های بیشتری از کتابخانه استاندارد را پوشش می‌دهم، آن‌ها را ذکر خواهم کرد.

برخی زبان‌ها با سیستم‌های نوع ضعیف‌تر، توسعه‌دهندگان را تشویق می‌کنند که نوع مورد انتظار متغیر را در نام متغیر لحاظ کنند. از آنجا که Go قویاً تایپ شده است، نیازی نیست این کار را برای پیگیری نوع زیرساختی انجام دهید. با این حال، ممکن است کد Go ببینید که در آن حرف اول یک نوع به عنوان نام متغیر استفاده می‌شود (برای مثال، i برای اعداد صحیح یا f برای اعداد اعشاری). هنگامی که انواع خود را تعریف می‌کنید، الگوهای مشابهی اعمال می‌شود، به خصوص هنگام نام‌گذاری متغیرهای گیرنده (که در بخش «متدها» در صفحه ۱۴۴ پوشش داده شده‌اند).

این نام‌های کوتاه دو هدف را دنبال می‌کنند. اول اینکه تایپ تکراری را حذف می‌کنند و کد شما را کوتاه‌تر نگه می‌دارند. دوم، آن‌ها به عنوان بررسی اینکه کد شما چقدر پیچیده است، عمل می‌کنند. اگر پیگیری متغیرهای نام‌کوتاه خود را دشوار می‌یابید، احتمالاً بلوک کد شما کارهای زیادی انجام می‌دهد.

هنگام نام‌گذاری متغیرها و ثوابت در بلوک بسته، نام‌های توصیفی‌تری استفاده کنید. نوع همچنان باید از نام حذف شود، اما از آنجا که دامنه گسترده‌تر است، نیاز به نام کامل‌تری دارید تا مشخص کنید مقدار چه چیزی را نمایندگی می‌کند.

برای بحث بیشتر در مورد توصیه‌های نام‌گذاری Go، بخش Naming از Google's Go Style Decisions را بخوانید.

تمرین‌ها

این تمرین‌ها مفاهیم بحث شده در فصل را نشان می‌دهند. راه‌حل‌های این تمرین‌ها، همراه با برنامه‌های این فصل، در ریپازیتوری فصل ۲ موجود است.

  1. برنامه‌ای بنویسید که متغیر عدد صحیحی به نام i با مقدار ۲۰ اعلان کند. i را به متغیر اعشاری به نام f اختصاص دهید. i و f را چاپ کنید.
  2. برنامه‌ای بنویسید که ثابتی به نام value اعلان کند که بتواند هم به متغیر عدد صحیح و هم به متغیر اعشاری اختصاص داده شود. آن را به عدد صحیحی به نام i و متغیر اعشاری به نام f اختصاص دهید. i و f را چاپ کنید.
  3. برنامه‌ای با سه متغیر بنویسید، یکی به نام b از نوع byte، یکی به نام smallI از نوع int32، و یکی به نام bigI از نوع uint64. به هر متغیر حداکثر مقدار قانونی برای نوعش را اختصاص دهید؛ سپس ۱ را به هر متغیر اضافه کنید. مقادیرشان را چاپ کنید.

جمع‌بندی

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