बहुत सी दूसरी भाषाओं की तरह C++ में भी पॉइंटर होते हैं। आप पहले से जानते हैं कि रेफरेंस और पॉइंटर एक जैसे होते हैं, लेकिन इन्हें अपने कंप्यूटर के भीतरी कामकाज के एक स्तर और करीब मानिए। पॉइंटर ऐसे वेरिएबल होते हैं जिनमें ऑब्जेक्ट के एड्रेस रहते हैं। इनका उपयोग ऑब्जेक्ट के साथ सीधे काम करने के लिए किया जाता है। इससे C++ में मेमोरी को डायनैमिक तरीके से एलोकेट करना और डेटा को कुशलता से संभालना संभव हो जाता है।
अगर आप पॉइंटर में नए हैं, तो ये थोड़े रहस्यमय लग सकते हैं, लेकिन एक बार इनकी आदत पड़ जाने पर ये काफी सीधे और आसान लगते हैं।
ये C++ का बहुत ज़रूरी हिस्सा हैं, इसलिए इन्हें ठीक से समझने के लिए थोड़ा समय ज़रूर दीजिए। इस कॉन्सेप्ट में जिस सादे रूप की बात की गई है, उसे डम पॉइंटर या रॉ पॉइंटर भी कहा जाता है। आधुनिक C++ में स्मार्ट पॉइंटर भी होते हैं। यह साधारण टाइप बिल्कुल भी स्मार्ट नहीं है और आपको सारा काम खुद करना पड़ता है।
ब्यौरों में उतरने से पहले यह समझ लेना अच्छा रहेगा कि पॉइंटर का उपयोग क्यों होता है। पॉइंटर हमारे प्रोग्राम के दूसरे हिस्सों के साथ किसी ऑब्जेक्ट का एड्रेस साझा करने का तरीका हैं, जो दो मुख्य कारणों से काम आता है:
C++ में पॉइंटर घोषित करते समय उस डेटा टाइप को बताना होता है जिसकी ओर पॉइंटर इशारा करेगा, उसके बाद एक एस्टरिस्क (*) और पॉइंटर का नाम लिखना होता है।
पॉइंटर घोषित करते समय उन्हें अपने आप कोई शुरुआती वैल्यू नहीं मिलती।
अगर साफ़ तौर पर कोई वैल्यू असाइन न की जाए, तो पॉइंटर में आम तौर पर कोई अनिश्चित वैल्यू रहती है, जिसे अक्सर "कचरा एड्रेस" कहा जाता है।
कुछ कंपाइलर पॉइंटर को अपने आप nullptr पर सेट कर देते हैं, लेकिन सभी कंपाइलर ऐसा नहीं करते, इसलिए इस भरोसे पर न रहना ज़रूरी है।
सबसे अच्छा तरीका यह है कि रॉ पॉइंटर को साफ़ तौर पर शुरुआती वैल्यू दी जाए और इस्तेमाल से पहले यह जाँच लिया जाए कि वह null नहीं है, ताकि आगे कोई समस्या न हो।
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++ में पॉइंटर अंकगणित को ध्यान से न संभाला जाए तो यह आसानी से अपरिभाषित व्यवहार को जन्म दे सकता है। अपरिभाषित व्यवहार प्रोग्राम के अनपेक्षित नतीजों, क्रैश, या यहाँ तक कि सुरक्षा संबंधी कमजोरियों के रूप में सामने आ सकता है। अपरिभाषित व्यवहार के परिणामों का एक कुख्यात उदाहरण 1996 में एरियन 5 रॉकेट के विस्फोट के रूप में सामने आया, जहाँ 64-बिट फ्लोटिंग-पॉइंट संख्या को 16-बिट साइन्ड पूर्णांक में बदलने से पैदा हुए सॉफ्टवेयर एक्सेप्शन ने एक विनाशकारी विफलता को जन्म दिया।
C++ में किसी ऑब्जेक्ट के मेंबर तक उस ऑब्जेक्ट के पॉइंटर के ज़रिए पहुँचने के लिए -> ऑपरेटर का उपयोग किया जाता है।
यह एक ऐसा छोटा रूप है जो पॉइंटर से इशारा किए गए ऑब्जेक्ट के मेंबर तक पहुँचना आसान बनाता है।
उदाहरण के लिए, अगर ptr किसी ऐसे ऑब्जेक्ट का पॉइंटर है जिसमें x नाम का मेंबर वेरिएबल है, तो (*ptr).x इस्तेमाल करने के बजाय आप सीधे ptr->x लिख सकते हैं।
ऑब्जेक्ट के पॉइंटर के साथ काम करते समय यह ऑपरेटर कोड को पढ़ने में आसान बनाता है और लिखावट कम कर देता है।
यहाँ एक छोटा उदाहरण है, जिसमें struct Superhero है और उसमें superpower नाम का मेंबर वेरिएबल है।
main फंक्शन एक Superhero ऑब्जेक्ट (जो Wonder Woman को दर्शाता है) के लिए dianaPrince नाम का पॉइंटर बनाता है।
superpower मेंबर वेरिएबल तक पहुँचने के लिए -> ऑपरेटर का उपयोग किया गया है, जो Wonder Woman की मशहूर "Lasso of Truth" को सामने लाता है।
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;
पॉइंटर और रेफरेंस दोनों ऑब्जेक्ट तक परोक्ष रूप से पहुँचने की सुविधा देते हैं, लेकिन अपनी क्षमताओं और सुरक्षा के पहलुओं में दोनों अलग हैं। पॉइंटर अपना लक्ष्य ऑब्जेक्ट बदलने की सुविधा देते हैं और इन्हें null भी असाइन किया जा सकता है। लेकिन यही सुविधा कुछ समस्याएँ भी पैदा करती है, जैसे null पॉइंटर को डीरेफरेंस करना या डैंगलिंग पॉइंटर बना देना। दूसरी ओर, रेफरेंस null नहीं हो सकते और बनते ही किसी मान्य ऑब्जेक्ट से जुड़ जाते हैं, इसलिए इन समस्याओं से बचे रहते हैं। चूँकि रेफरेंस ज़्यादा सुरक्षित होते हैं, इसलिए जब तक पॉइंटर की दी गई अतिरिक्त सुविधाएँ ज़रूरी न हों, रेफरेंस को पॉइंटर पर प्राथमिकता देनी चाहिए।
C++ में रनटाइम के दौरान हीप पर ऑब्जेक्ट के लिए मेमोरी डायनैमिक तरीके से एलोकेट करने के लिए new का उपयोग किया जाता है।
यह एलोकेट किए गए मेमोरी एड्रेस का पॉइंटर लौटाता है, जिससे ऑब्जेक्ट का जीवनकाल किसी खास स्कोप से बँधा नहीं रहता।
लेकिन यह ज़रूरी है कि मेमोरी को ठीक से मुक्त करने के लिए new के साथ हमेशा delete का इस्तेमाल किया जाए।
मेमोरी लीक और मुक्त की गई मेमोरी तक पहुँचने जैसी आम समस्याओं से बचने के लिए डायनैमिक मेमोरी एलोकेशन का उपयोग सावधानी से करना चाहिए।
पॉइंटर को एक खास वैल्यू पर भी सेट किया जा सकता है, जिसे null पॉइंटर कहते हैं और जो बताता है कि वे किसी मान्य एड्रेस की ओर इशारा नहीं कर रहे। पॉइंटर को मान्य वैल्यू असाइन करने से पहले उन्हें शुरुआती वैल्यू देने के लिए आम तौर पर null पॉइंटर का उपयोग किया जाता है।
// 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 पर सेट किया जाना चाहिए।
इस तरह आपको पॉइंटर की जाँच सिर्फ null के लिए करनी होती है और आप मान सकते हैं कि null न होने वाला हर पॉइंटर मान्य है।
जब कोई ऑब्जेक्ट नष्ट हो जाता है, तो उस नष्ट ऑब्जेक्ट की ओर इशारा करने वाले सारे पॉइंटर डैंगलिंग रह जाते हैं (वे अपने आप nullptr पर सेट नहीं होते)।
ऐसे मामलों को पहचानना और यह पक्का करना आपकी ज़िम्मेदारी है कि उन पॉइंटर को आगे चलकर nullptr पर सेट कर दिया जाए।
पुराने कोड में आपको nullptr के दो विकल्प मिल सकते हैं।
पहला, लिटरल 0 को पॉइंटर के लिए खास तौर पर null वैल्यू माना जाता है, हालाँकि यही एक ऐसा मौका है जब किसी पूर्णांक लिटरल को पॉइंटर में असाइन किया जा सकता है।
दूसरा, C से विरासत में मिला और <cstddef> हेडर में परिभाषित preprocessor macro NULL, null पॉइंटर का एक और रूप है, हालाँकि आधुनिक C++ कोड में इसका उपयोग कम होता है।