خوش آموز درخت تو گر بار دانش بگیرد، به زیر آوری چرخ نیلوفری را


آموزش زبان ++C : ثابت های نمادین، Const و constexpr

آموزش زبان ++C : ثابت های نمادین، Const و constexpr
نویسنده : امیر انصاری
متغیرهای Const

نرم افزار سامانه مودیان راهکار



تاکنون، تمام متغیرهایی را که ما دیده ایم، غیر ثابت (non-constant) بوده اند، یعنی، مقدار این متغیرها در هر زمانی در کد می توانسته است تغییر کند. برای مثال :

int x { 4 }; // initialize x with the value of 4
x = 5; // change value of x to 5

با این حال، گاهی اوقات مفید است که متغیرهایی را معرفی کنیم که مقدار آنها قابل تغییر نباشند. برای مثال مقدار گرانش بر روی کره زمین که 9.8 متر در ثانیه به توان 2 می باشد را در نظر بگیرید :

the value of gravity on Earth: 9.8 meters/second^2


این مقدار چیزی نیست که به این زودی ها تغییر کند. معرفی این مقدار به عنوان یک ثابت (constant) به ما کمک می کند تا مطمئن شویم که مقدار آن به صورت اتفاقی در داخل برنامه تغییر نخواهد کرد.

برای اینکه بتوانیم متغیرها را به صورت ثابت (constant) معرفی کنیم، کافیست به سادگی هر چه تمامتر کلمه کلیدی Const را قبل یا بعد از نوع داده متغیر ذکر کنیم. مانند :

const double gravity { 9.8 }; // preferred use of const before type
int const sidesInSquare { 4 }; // okay, but not preferred

اگرچه در زبان برنامه نویسی ++C شما می توانید کلمه کلیدی Const را قبل یا بعد از نوع داده متغیر بیاورید، ما پیشنهاد می کنیم همیشه کلمه کلیدی Const را قبل از نوع داده متغیر بیاورید، چرا که در این روش بیشتر با قوانین استاندارد زبان انگلیسی معمولی مطابقت خواهد داشت، چرا که در انگلیسی اصلاح کننده ها (modifiers) قبل از شیئی که قرار است اصلاح شود می آیند (مانند a green ball و نه a ball green).

متغیرهای Const در هنگام معرفی الزاماً باید مقدار دهی اولیه شوند، و سپس با استفاده از عملیات انتساب (assignment) امکان تغییر مقادیر آنها وجود نخواهد داشت.

اعلام کردن یک متغیر به صورت Const از تغییر یافتن مقدار آن به صورت سهوی جلوگیری می کند، در مثال زیر برنامه خطا خواهد داد و کامپایل نخواهد شد :

const double gravity { 9.8 };
gravity = 9.9; // not allowed, this will cause a compile error

همچنین اگر به یک متغیر از نوع Const مقدار اولیه ندهید، باز هم برنامه خطای کامپایلر خواهد داد :

const double gravity; // compiler error, must be initialized upon definition

توجه داشته باشید که متغیرهای از نوع Const می توانند با متغیرهای غیرثابت (non-const) مقدار دهی اولیه شوند :

std::cout << "Enter your age: ";
int age;
std::cin >> age;

const int usersAge (age); // usersAge can not be changed

متغیرهای Const معمولاً به عنوان پارامترهای توابع مورد استفاده قرار می گیرند :

void printInteger(const int myValue)
{
std::cout << myValue;
}

اینکه پارامتری از یک تابع را به صورت ثابت (Const) معرفی کنیم دو چیز را انجام می دهد. اولین نکته اینست که تابع به شخصی که آن را فراخوانی می کند، می گوید که تابع مقدار ثابت myValue را در طول اجرای تابع، تغییر نخواهد داد. دوم اینکه، اطمینان حاصل می شود که تابع مربوطه مقدار myValue را تغییر نمی دهد.

هنگامی که پارامترهای یک تابع به صورت ارسال با مقدار (by value) به تابع پاس می شوند، که در مثال بالا نیز همینطور است، در حالت کلی برای ما مهم نیست که آیا تابع مربوطه مقدار پارامتر را تغییر بدهد یا خیر، چرا که در حالت by value فقط یک کپی از پارامتر ارسال می شود و بعد از اتمام تابع نیز نابود خواهد شد. به همین دلیل، ما در مورد پارامترهای از نوع by value از Const استفاده نخواهیم کرد. اما بعد از آن، ما در مورد نوع دیگری از پارامترهای توابع صحبت خواهیم کرد که در آنها تغییر دادن مقدار پارامتر ارسالی درون یک تابع منجر خواهد شد تا مقدار آرگومان اصلی ارسالی نیز که به تابع پاس شده است، تغییر یابد. برای این نوع از پارامترها استفاده از const مهم می باشد.

زمان کامپایل (Compile time) در مقایسه با زمان اجرا (runtime)


هنگامی که در فرآیند کامپایل برنامه خود می باشید، این مرحله زمان کامپایل (Compile time) نامیده می شود. در طول زمان کامپایل (Compile time)، کامپایلر تضمین می کند که کد شما از نظر دستور زبان (syntactically) صحیح نوشته شده باشد، و کدهای شما را به object files ها تبدیل می کند.

هنگامی که در حال اجرای برنامه خود می باشید، این مرحله زمان اجرا (runtime) نامیده می شود. در طول زمان اجرا (runtime) برنامه شما خط به خط اجرا خواهد شد.

Constexpr


زبان برنامه نویسی ++C در واقع دارای دو نوع ثابت (constant) می باشد.

ثابت های زمان اجرا (Runtime constants) آن ثابت هایی هستند که مقدار دهی اولیه آنها تنها می تواند در زمان اجرا (runtime) صورت پذیرد. متغیرهایی چون usersAge و myValue که در مثال های بالا دیدیم، از نوع ثابت های زمان اجرا (Runtime constants) می باشند، چرا که کامپایلر در زمان کامپایل (Compile time) نمی تواند مقادیر آنها را تعیین کند. متغیر usersAge بستگی به مقادیر وارد شده توسط کاربر دارد که تنها در زمان اجرا (runtime) امکان مقدار دهی اش وجود دارد، و متغیر myValue بستگی به مقداری که به تابع پاس می شود دارد که آنهم تنها در زمان اجرا (runtime) مشخص می شود.

ثابتهای زمان کامپایل (Compile-time constants) آن ثابت هایی هستند که مقدار دهی اولیه آنها در زمان کامپایل (Compile time) صورت می پذیرد. متغیر gravity که در مثال های بالا وجود داشت یک مثال از ثابتهای زمان کامپایل (Compile-time constants) می باشد. هر جا که gravity مورد استفاده قرار می گیرد، کامپایلر به سادگی می تواند مقدار لیترال 9.8 را با شناسه gravity جایگزین کند.

در بیشتر موارد، اینکه مقدار یک ثابت به صورت زمان اجرا (runtime) و یا زمان کامپایل (Compile time) باشد، مهم نیست. اگر چه، چند نمونه عجیب و غریب وجود دارد که زبان ++C نیاز دارد تا مقدار یک ثابت الزاماً در زمان کامپایل (Compile time) تعیین شود و نه در زمان اجرا (runtime)، مانند هنگامی که می خواهیم طول یک آرایه با اندازه ثابت (fixed-size array) را تعیین کنیم. در مورد آرایه ها در آینده حتماً بحث خواهیم کرد. از آنجا که مقدار یک ثابت (const) می تواند در زمان اجرا (runtime) و یا در زمان کامپایل (Compile time) تعیین شود، کامپایلر باید بداند که ثابت مربوطه از کدام نوع می باشد.

در زبان C++11 برای تشخیص بهتر اینکه یک ثابت از کدام نوع می باشد، کلمه کلیدی جدیدی با نام Constexpr ارائه شده است، که تضمین می کند یک ثابت از نوع ثابتهای زمان کامپایل (Compile-time constants) می باشد.

constexpr double gravity (9.8); // ok, the value of 9.8 can be resolved at compile-time
constexpr int sum = 4 + 5; // ok, the value of 4 + 5 can be resolved at compile-time

std::cout << "Enter your age: ";
int age;
std::cin >> age;
constexpr int myAge = age; // not okay, age can not be resolved at compile-time

قانون : هر متغیری که مقدار آن بعد از مقدار دهی اولیه (initialization) نباید تغییر کند و مقدار دهی اولیه آن در زمان کامپایل (Compile time) مشخص می شود، باید همراه با کلمه کلیدی Constexpr معرفی شود.

قانون : هر متغیری که مقدار آن بعد از مقدار دهی اولیه (initialization) نباید تغییر کند و مقدار دهی اولیه آن در زمان اجرا (runtime) مشخص می شود، باید با کلمه کلیدی Const معرفی شود.

نامگذاری متغیرهای از نوع Const


برخی برنامه نویس ها ترجیح می دهند تا متغیرهای از نوع Const را تماماً با حروف بزرگ (upper-case) نام گذاری کنند. برخی دیگر نیز از اسامی معمولی با پیشوند k استفاده می کنند. با این حال ما از روش نامگذاری معمول متغیرها استفاده می کنیم که رایج تر هم می باشد. متغیرهای از نوع Const دقیقاً مشابه متغیرهای معمولی عمل می کنند و تنها تفاوت آنها در این می باشد که ما نمی توانیم مقداری را به آنها نسبت بدهیم، بنابراین هیچ دلیل خاصی ندارد تا آنها را به صورت خاصی معرفی کنیم.

ثابت های نمادین (Symbolic constants)


در درس قبلی که در مورد لیترال ها (Literals) بود، ما در مورد اعداد جادویی (magic numbers) بحث کردیم، و گفتیم که اعداد جادویی (magic numbers) در واقع لیترال هایی هستند که ما به عنوان مقادیر ثابت در کد هایمان استفاده می کنیم. از آنجا که گفتیم اعداد جادویی (magic numbers) روش بدی هستند، خوب حالا باید چه چیزی را به جای آنها استفاده کنیم؟ پاسخ اینست که از ثابت های نمادین (Symbolic constants) استفاده کنید! یک ثابت نمادین (symbolic constant) یک نام است که به یک مقدار لیترال ثابت داده می شود. برای معرفی ثابت های نمادین (Symbolic constants) در زبان برنامه نویسی ++C دو روش وجود دارد. یکی از این روش ها خوب و آن دیگری بد است. ما در اینجا هر دو روش را به شما نشان می دهیم.

روش بد : استفاده از ماکروهای شبیه اشیاء (Object-like macros) با یک پارامتر جایگزینی به عنوان یک ثابت نمادین


ما قصد داریم تا روش نامطلوبتر معرفی یک ثابت نمادین (symbolic constant) را ابتدا بگوییم. این روش در بسیاری از کدهای قدیمی تر موجود است، و بنابراین ممکن است که در برنامه های قدیمی بارها این روش را دیده باشید.

در آموزش مربوط به پیش پردازنده ها (preprocessor) دانستید که ماکروهای شبیه اشیاء (Object-like macros) دو شکل دارند، در یک شکل از این ماکروها پارامتر جایگزینی وجود ندارد و معمولاً هم برای کامپایل شرطی (Conditional compilation) مورد استفاده قرار می گیرد، و در شکل دیگر این ماکروها پارامتر جایگزینی وجود دارد. در اینجا در مورد شکل دوم که دارای پارامتر جایگرینی می باشد صحبت خواهیم کرد. شکل این نوع ماکروها در ادامه آمده است :

#define identifier substitution_text

هر گاه که پیش پردازنده (preprocessor) با این دستورات برخورد می کند، هر نمونه از شناسه (identifier) مربوطه که بعد از دستور آمده باشد با متن جایگزینی، تعویض می شود. شناسه ها (identifier) به شیوه سنتی با حروف بزرگ نوشته می شوند، و همینطور از زیر خط (underscores) به جای فضاهای خالی (spaces) استفاده می شود.

قطعه کد زیر را در نظر بگیرید :

#define MAX_STUDENTS_PER_CLASS 30
int max_students = numClassrooms * MAX_STUDENTS_PER_CLASS;

هنگامی که این کد را کامپایل می کنید، پیش پردازنده قبل از شروع عملیات کامپایل، تمامی نمونه های موجود از MAX_STUDENTS_PER_CLASS را با مقدار لیترال 30 جایگزین می کند، و سپس عملیات کامپایل کد شما به فایل اجرایی صورت خواهد پذیرفت.

به احتمال زیاد شما هم موافق هستید که این رویکرد نسبت به روش استفاده از اعداد جادویی (magic numbers) به چند دلیل بسیار بصری تر و جالبتر است. شناسه MAX_STUDENTS_PER_CLASS شامل متنی است که مشخص می کند برنامه سعی دارد چه کاری را انجام بدهد، حتی بدون کامنت گذاری هم این متن واضح می باشد. دلیل دیگر برای بهتر بودن این روش نسبت به روش اعداد جادویی (magic numbers) اینست که اگر عدد مربوط به حداکثر دانش آموزان موجود در یک کلاس، تغییر بکند، تنها کاری که ما باید انجام بدهیم اینست که مقدار MAX_STUDENTS_PER_CLASS را در یک محل مشخص تغییر بدهیم، و بعد از یک مرتبه کامپایل، تمامی نمونه های موجود از MAX_STUDENTS_PER_CLASS با لیترال وارد شده، جایگزین می شوند.

مثال دوم ما را که در آن از define استفاده شده است در نظر بگیرید :

#define MAX_STUDENTS_PER_CLASS 30
#define MAX_NAME_LENGTH 30

int max_students = numClassrooms * MAX_STUDENTS_PER_CLASS;
setMax(MAX_NAME_LENGTH);

در این مثال واضح است که شناسه MAX_STUDENTS_PER_CLASS و شناسه MAX_NAME_LENGTH مقادیر مستقلی هستند، حتی با وجود اینکه مقدار آنها یکسان می باشد و هر دو عدد 30 را در خود دارند، اما از یکدیگر مستقل هستند و ارتباطی با هم ندارند.

بنابراین، چرا نباید از define برای معرفی ثابت های نمادین (Symbolic constants) استفاده کنیم؟ در اینجا حداقل دو مشکل بزرگ داریم.

اولین مشکل اینکه، از آنجا که ماکروها توسط پیش پردازنده (preprocessor) مدیریت می شوند، و پیش پردازنده ثابت نمادین (symbolic constant) را با مقدار لیترال موجود در شناسه جایگزین می کند، در هنگام اشکال زدایی کد، توسط ابزار دیباگر (debugger) شما نمی توانید این ثابت های نمادین (Symbolic constants) را ببینید و فقط لیترال ها را خواهید دید. و این مساله عملیات دیباگ (debug) و ردیابی مقادیر را مشکل می کند.

دومین مشکل اینست که، مقادیر معرفی شده توسط defined همیشه در محدوده فایل (file scope) می باشند، در مورد محدوده یا قلمرو متغیرها در آینده بحث های کاملی را ارائه خواهیم داد. معنای این موضوع این می باشد که مقداری که با استفاده از defined در یک بخش از کد تعریف شده است، ممکن است با مقدار دیگری که در همان فایل معرفی شده است با تداخل اسامی منجر گردد.

برای مثال :

#include "iostream"

void a()
{
// This define value is now available for the rest of this file
#define x 5
std::cout << x;
}

void b()
{
// Even though we're intending this x to be local to function b()
// it conflicts with the x we defined inside function a()
#define x 6
std::cout << x;
}

int main() {

a();
b();

return 0;
}

در مثال بالا، ما با استفاده از define مقدار x را در تابع a معرفی کردیم، و قصد داریم تا از آن در داخل تابع a استفاده نماییم. با این حال دستور

#define value x

در حقیقت در کل فایل ما مورد استفاده قرار می گیرد و مخصوص به تابع a نخواهد بود. بنابر این، هنگامی که در تابع b نیز دستور مشابهی داریم، ما با تصادم نامها مواجه خواهیم شد. حتی اگر در تابع b یک متغیر با نام x نیز داشته باشیم، و از define هم برای معرفی آن استفاده نکرده باشیم، باز هم این مشکل را خواهیم داشت، چرا که دستور پیش پردازنده مربوطه، آن متغیر x را نیز با مقدار 5 جایگزین خواهد کرد.

قانون : از دستور پیش پردازنده define برای معرفی ثابت های نمادین (Symbolic constants) استفاده نکنید و از آن برای این منطور اجتناب کنید.

یک راه حل بهتر : استفاده از متغیرهای از نوع Const


یک روش بهتر برای ایجاد ثابت های نمادین (Symbolic constants) استفاده از متغیرهای از نوع Const و یا حتی بهتر از آن استفاده از Constexpr می باشد :

constexpr int maxStudentsPerClass { 30 };
constexpr int maxNameLength { 30 };

اینها در دیباگر (debugger) نمایش داده می شوند و از نظر محدوده (scope) از تمامی قوانین عادی متغیرها پشتیبانی می کنند.

قانون : از متغیرهای از نوع Const برای ایجاد نام و مفهوم جهت اعداد جادویی (magic numbers) استفاده کنید.

استفاده از ثابت های نمادین (Symbolic constants) در داخل یک برنامه


در بسیاری از برنامه های کاربردی (applications)، یک ثابت نمادین (symbolic constant) مشخص، نیاز دارد تا در سرتاسر کدهای شما مورد استفاده قرار بگیرد (و نه فقط در یک محل خاص). این ثابت های نمادین (Symbolic constants) می توانند شامل ثابت های فیزیک و ریاضی باشند، که تغییر نمی کنند (برای مثال عدد pi)، و یا مقادیر خاص برای تنظیمات برنامه باشند (مانند ضریب های مورد استفاده). به جای اینکه هر بار که به آنها نیاز دارید، بخواهید آنها را معرفی کنید، بهتر است که همه این نوع ثابت ها را در یک محل مرکزی مشخص معرفی کنید و هر جا که لازم بود از آنها استفاده کنید. به این ترتیب، اگر شما نیاز به تغییر مقادیر ثابت ها نیز داشته باشید، تنها نیاز است که یکبار و در یک محل مشخص آنها را تغییر بدهید.

برای تسهیل این کار، چندین روش در زبان برنامه نویسی ++C وجود دارد، اما روش های زیر احتمالاً ساده ترین ها باشند :

  1. یک فایل هدر برای نگهداری این نوع ثابت ها بسازید.
  2. در داخل آن فایل هدر یک فضای نام (namespace) بسازید. در مورد فضای نام (namespace) در درس های آینده عمیقتر خواهیم شد.
  3. تمامی ثابت های خود را در داخل آن فضای نام (namespace) قرار بدهید.
  4. حالا هر جا که به آنها نیاز داشته باشید، کافیست تا فایل هدر مربوطه را include کنید.

برای مثال، constants.h :
#ifndef CONSTANTS_H
#define CONSTANTS_H

// define your own namespace to hold constants
namespace constants
{
constexpr double pi(3.14159);
constexpr double avogadro(6.0221413e23);
constexpr double my_gravity(9.2); // m/s^2 -- gravity is light on this planet
// ... other related constants
}
#endif

برای دسترسی به ثابت ها از عملگر وضوح دامنه (scope resolution operator) که نماد آن (::) می باشد، استفاده کنید :

#include "constants.h"
double circumference = 2 * radius * constants::pi;

در درسهای آینده ما به شما یک روش کارآمدتر برای معرفی ثابت های نمادین (Symbolic constants) توسط متغیرهای عمومی (global variables) را نشان خواهیم داد.


آموزش قبلی : آموزش زبان ++C : لیترال ها (Literals)

آموزش بعدی : آموزش زبان ++C : اولویت عملگرها (Operator precedence) و وابستگی (associativity)



نمایش دیدگاه ها (0 دیدگاه)

دیدگاه خود را ثبت کنید:

انتخاب تصویر ویرایش حذف
توجه! حداکثر حجم مجاز برای تصویر 500 کیلوبایت می باشد.