مانند بسیاری از زبانهای دیگر، C++ هم اشارهگر دارد. شما پیشتر میدانید که ارجاعها و اشارهگرها شبیه هم هستند، اما آنها را یک لایه نزدیکتر به سازوکار درونی رایانهتان در نظر بگیرید. اشارهگرها متغیرهایی هستند که آدرس اشیاء را نگه میدارند. از آنها برای تعامل مستقیم با اشیاء استفاده میشود و تخصیص حافظهی پویا و دستکاری کارآمد دادهها را در C++ ممکن میکنند.
اگر تازه با اشارهگرها آشنا شدهاید، ممکن است کمی رمزآلود به نظر برسند، اما وقتی به آنها عادت کنید، کاملاً سرراست میشوند.
آنها بخشی حیاتی از C++ هستند، پس وقت بگذارید تا واقعاً آنها را بفهمید. نسخهی ساده و ابتدایی آن در این مفهوم، اشارهگر احمق یا اشارهگر خام هم نامیده میشود. در C++ مدرن اشارهگرهای هوشمند هم وجود دارند، اما نوع پایه اصلاً هوشمند نیست و باید همهی کارها را دستی انجام دهید.
پیش از پرداختن به جزئیات، بهتر است کاربرد اشارهگرها را بفهمیم. اشارهگرها روشی برای به اشتراک گذاشتن آدرس یک شیء با بخشهای دیگر برنامهی ما هستند، که به دو دلیل عمده مفید است:
اعلان یک اشارهگر در C++ شامل مشخص کردن نوع دادهای است که اشارهگر به آن اشاره میکند، سپس یک ستاره (*) و اسم اشارهگر میآید.
وقتی اشارهگرها اعلان میشوند، بهطور خودکار مقداردهی اولیه نمیشوند.
بدون تخصیص صریح، یک اشارهگر معمولاً مقداری نامعین نگه میدارد که اغلب به آن «آدرس زباله» میگویند.
هرچند برخی کامپایلرها ممکن است اشارهگرها را به nullptr مقداردهی اولیه کنند، این رفتار در همهی کامپایلرها تضمین نشده است، پس ضروری است که به آن تکیه نکنید.
بهترین روش این است که اشارهگرهای خام را بهصراحت مقداردهی اولیه کنید و پیش از استفاده، غیرتهی بودن آنها را بررسی کنید تا از بروز مشکلات احتمالی جلوگیری شود.
int* ptr{nullptr}; // Declares a pointer and makes sure it is not invalid
برای نسبت دادن آدرس یک متغیر به یک اشارهگر، از عملگر آدرسگیری (&) استفاده میکنید.
ارجاعزدایی از یک اشارهگر با استفاده از عملگر غیرمستقیم (*) انجام میشود.
std::string opponent{"Solomon Lane"};
// 'ethan' points to the address of the string opponent
std::string* ethan{&opponent};
// Instead of ethan's, the opponent's name address is given to the passPort
std::string passportName{*ethan};
توجه: ارجاعزدایی باید بهصراحت انجام شود، در حالی که ارجاعها همچون یک نام مستعار عمل میکردند.
محاسبات اشارهگر به شما امکان میدهد عملیات حسابی را روی اشارهگرها انجام دهید، که بهویژه هنگام کار با آرایه مفید است. افزودن یک عدد صحیح به یک اشارهگر باعث میشود به عنصر دیگری اشاره کند.
// Stargate Coordinate Code
int gateCode[] = {462, 753, 218, 611, 977};
// 'ptr' points to the first element of 'gateCode'
int* ptr{&gateCode[0]};
// Accesses the third Stargate address through pointer arithmetic
int dialedAddress{*(ptr + 2)};
// Chevron encoded! Dialing Stargate address:
openStarGate(dialedAddress);
محاسبات اشارهگر در C++ اگر با دقت مدیریت نشود، بهراحتی میتواند به رفتار تعریفنشده منجر شود. رفتار تعریفنشده میتواند خود را در نتایج غیرمنتظرهی برنامه، فروپاشی، یا حتی آسیبپذیریهای امنیتی نشان دهد. یکی از نمونههای بدنام پیامدهای رفتار تعریفنشده، در انفجار موشک آریان ۵ در سال ۱۹۹۶ رخ داد، جایی که یک خطای نرمافزاری ناشی از تبدیل یک عدد اعشاری ممیز شناور ۶۴ بیتی به یک عدد صحیح علامتدار ۱۶ بیتی به یک شکست فاجعهبار انجامید.
در C++، از عملگر -> برای دسترسی به اعضای یک شیء از طریق اشارهگری به آن شیء استفاده میشود.
این یک شکل کوتاهنویسی است که دسترسی به اعضای اشیائی را که اشارهگرها به آنها اشاره میکنند ساده میکند.
برای نمونه، اگر ptr اشارهگری به شیئی با متغیر عضو x باشد، بهجای استفاده از (*ptr).x میتوانید مستقیماً از ptr->x استفاده کنید.
این عملگر هنگام کار با اشارهگرها به اشیاء، خوانایی کد را افزایش میدهد و از پرگویی میکاهد.
در اینجا یک مثال کوتاه داریم، با یک struct به نام Superhero که یک متغیر عضو superpower دارد.
تابع main اشارهگری به اسم dianaPrince به یک شیء Superhero ایجاد میکند (که نمایندهی Wonder Woman است).
از عملگر -> برای دسترسی به متغیر عضو superpower استفاده میشود و «Lasso of Truth» نمادین Wonder Woman را به نمایش میگذارد.
struct Superhero {
std::string superpower;
};
Superhero wonder_woman{};
Superhero* dianaPrince = &wonder_woman;
dianaPrince->superpower = "Lasso of Truth";
// Using the -> operator to access member variable superpower:
std::cout << "Wonder Woman, possesses the mighty " << dianaPrince->superpower;
// Memory cleanup:
delete dianaPrince;
اشارهگرها و ارجاعها هر دو دسترسی غیرمستقیم به اشیاء را ممکن میکنند، اما در تواناییها و ملاحظات ایمنی با هم تفاوت دارند. اشارهگرها انعطاف تغییر شیء هدف خود را ارائه میدهند و میتوان آنها را تهی کرد. با این حال، این انعطاف ریسکهایی به همراه دارد، مانند ارجاعزدایی از اشارهگرهای تهی یا ایجاد اشارهگرهای آویزان. ارجاعها اما نمیتوانند تهی باشند و هنگام ایجاد به اشیاء معتبر گره میخورند و از این ریسکها پرهیز میکنند. با توجه به ماهیت ایمنترشان، ارجاعها را باید بر اشارهگرها ترجیح داد، مگر آنکه کارکردهای اضافیای که اشارهگرها فراهم میکنند ضروری باشند.
در C++، از new برای تخصیص پویای حافظه برای اشیاء روی heap در زمان اجرا استفاده میشود.
این عملگر اشارهگری به آدرس حافظهی تخصیصیافته برمیگرداند و به اشیاء امکان میدهد عمری داشته باشند که به یک دامنهی مشخص گره نخورده است.
با این حال، مهم است که همیشه new را با delete جفت کنید تا حافظه بهدرستی آزاد شود.
تخصیص حافظهی پویا باید با احتیاط استفاده شود تا از مشکلات رایجی مانند نشت حافظه و دسترسی به حافظهی آزادشده جلوگیری شود.
اشارهگرها را همچنین میتوان به مقدار ویژهای به نام اشارهگر تهی تنظیم کرد، که نشان میدهد به آدرس معتبری اشاره نمیکنند. از اشارهگرهای تهی معمولاً برای مقداردهی اولیهی اشارهگرها پیش از نسبت دادن آنها به مقادیر معتبر استفاده میشود.
// There is a dark mark bearer, but who is it?
std::string* darkMarkBearer{nullptr};
// It is Bellatrix!
darkMarkBearer = new std::string{"Bellatrix Lestrange"};
// She has been defeated
delete darkMarkBearer;
// Reset pointer to null, symbolizing there is no more darkMarkBearer at the moment
darkMarkBearer = nullptr;
یک اشارهگر باید یا آدرس یک شیء معتبر را نگه دارد، یا به nullptr تنظیم شود.
به این ترتیب فقط لازم است اشارهگرها را از نظر تهی بودن بررسی کنید و میتوانید فرض کنید هر اشارهگر غیرتهی معتبر است.
وقتی یک شیء نابود میشود، هر اشارهگری به آن شیء نابودشده آویزان باقی میماند (بهطور خودکار به nullptr تنظیم نمیشود).
تشخیص این موارد و اطمینان از اینکه آن اشارهگرها پس از آن به nullptr تنظیم میشوند، بر عهدهی شماست.
در کدهای قدیمیتر، ممکن است با دو جایگزین برای nullptr روبهرو شوید.
نخست، مقدار ثابت 0 بهطور خاص بهعنوان مقدار تهی برای اشارهگرها تفسیر میشود، هرچند تنها موقعیتی است که یک مقدار ثابت صحیح میتواند به یک اشارهگر نسبت داده شود.
دوم، NULL که یک preprocessor macro است، از C به ارث رسیده و در هدر <cstddef> تعریف شده است و نمایش دیگری از اشارهگر تهی به شمار میرود، هرچند استفاده از آن در کدهای C++ مدرن کمتر رایج است.