विषय पर बढ़ें

अंतिम अद्यतन:2026-10-13

C++ const: कॉन्स्टेंट और दो प्रकार के const पॉइंटर

लेखक के बारे में

WeChat सार्वजनिक अकाउंट:

C++ const विस्तार से: कॉन्स्टेंट, const पॉइंटर vs पॉइंटर-टू-const | C++那些事

1. const का अर्थ

const टाइप उस टाइप को कहते हैं जिसे टाइप क्वालिफ़ायर const के साथ बताया गया हो; const टाइप के वेरिएबल या ऑब्जेक्ट की वैल्यू अपडेट नहीं की जा सकती।

2. const की भूमिका

  • इससे कॉन्स्टेंट परिभाषित किए जा सकते हैं
const int a=100;
  • टाइप जाँच

    • const कॉन्स्टेंट और #define मैक्रो-परिभाषित कॉन्स्टेंट के बीच अंतर:

      const कॉन्स्टेंट का एक टाइप होता है, इसलिए कंपाइलर सुरक्षा जाँच कर सकता है; #define मैक्रो परिभाषा का कोई डेटा टाइप नहीं होता, वह केवल सरल स्ट्रिंग प्रतिस्थापन है, इसलिए सुरक्षा जाँच संभव नहीं।यहाँ त्रुटि बताने के लिए दो विशेषज्ञों का धन्यवाद; देखें: issue

    • const कॉन्स्टेंट सभी प्रकारों का समर्थन करते हैं।

    • अन्य स्थितियों में यह केवल एक const क्वालिफ़ाई वेरिएबल है; इसे कॉन्स्टेंट से न जचाएँ।
  • संशोधन रोकना, सुरक्षा का कार्य करना और प्रोग्राम की मजबूती बढ़ाना

void f(const int i){
    i++; // error!
}
  • स्पेस बचाना और अनावश्यक मेमोरी आवंटन से बचना

    • पूर्णांक/एनम प्रकार से तथा कॉन्स्टेंट एक्सप्रेशन द्वारा इनिशियलाइज़ किया गया const (अर्थात् कॉन्स्टेंट एक्सप्रेशन) उपयोग में कंपाइल-टाइम कॉन्स्टेंट के काफ़ी करीब है: जब तक आप उसका एड्रेस नहीं लेते और केवल उसकी वैल्यू का उपयोग करते हैं, वह प्रायः इमीडिएट में अनुकूलित हो जाता है, ठीक #define की तरह; एड्रेस लेने या ट्रांसलेशन यूनिटों के बीच odr-use होने पर ही वह मेमोरी में जाता है, और पूरे प्रोग्राम में उसकी केवल एक ही प्रति होती है।
    • #define के लिटरल में एक्सपैंड होने के बाद वह इमीडिएट के रूप में इंस्ट्रक्शनों में संकलित होता है; इंस्ट्रक्शन भी स्वयं मेमोरी घेरते हैं, इसलिए कई जगह उपयोग करने पर कई जगह दिखाई देता है (देखें issue #5 की चर्चा)।
    • ध्यान दें: हर const वेरिएबल कॉन्स्टेंट एक्सप्रेशन नहीं होता — केवल पूर्णांक/एनम प्रकार के और कॉन्स्टेंट एक्सप्रेशन से इनिशियलाइज़ होने वाले ही कॉन्स्टेंट एक्सप्रेशन हैं; अन्य const वेरिएबल केवल const क्वालिफ़ाई वेरिएबल हैं (जैसे फ़्लोटिंग पॉइंट, यूज़र-डिफ़ाइंड प्रकार); कंपाइल-टाइम कॉन्स्टेंट को सही ढंग से व्यक्त करने का तरीका constexpr है।

3. const ऑब्जेक्ट डिफ़ॉल्ट रूप से फ़ाइल-लोकल होते हैं

ध्यान दें: गैर-const वेरिएबल डिफ़ॉल्ट रूप से extern होते हैं। const वेरिएबल को अन्य फ़ाइलों में एक्सेस कराने के लिए, उसे उस फ़ाइल में स्पष्ट रूप से extern निर्दिष्ट करना अनिवार्य है।

const से न छुए वेरिएबल की भिन्न फ़ाइलों में एक्सेस

// file1.cpp
int ext;
// file2.cpp
#include<iostream>

extern int ext;
int main(){
    std::cout<<(ext+10)<<std::endl;
}

const कॉन्स्टेंट की भिन्न फ़ाइलों में एक्सेस

// extern_file1.cpp
extern const int ext=12;
// extern_file2.cpp
#include<iostream>
extern const int ext;
int main(){
    std::cout<<ext<<std::endl;
}

सारांश:
देखा जा सकता है कि const से न छुए वेरिएबल को extern की स्पष्ट घोषणा की ज़रूरत नहीं! जबकि const कॉन्स्टेंट को extern की स्पष्ट घोषणा चाहिए और साथ ही इनिशियलाइज़ भी करना चाहिए! क्योंकि कॉन्स्टेंट को परिभाषा के बाद बदला नहीं जा सकता, इसलिए परिभाषा के समय ही उसे इनिशियलाइज़ करना अनिवार्य है।

4. कॉन्स्टेंट परिभाषित करना

const int b = 10;
b = 0; // error: assignment of read-only variable ‘b’
const string s = "helloworld";
const int i,j=0 // error: uninitialized const ‘i’

उपरोक्त में दो त्रुटियाँ हैं: + b एक कॉन्स्टेंट है, इसे बदला नहीं जा सकता! + i एक कॉन्स्टेंट है, इसे अनिवार्यतः इनिशियलाइज़ करना होगा! (क्योंकि कॉन्स्टेंट को परिभाषा के बाद बदला नहीं जा सकता, इसलिए परिभाषा के समय ही उसे इनिशियलाइज़ करना अनिवार्य है।)

5. पॉइंटर और const

पॉइंटर से संबंधित const चार प्रकार के होते हैं:

const char * a; // 指向const对象的指针或者说指向常量的指针。
char const * a; // 同上
char * const a; // 指向类型对象的const指针。或者说常指针、const指针。
const char * const a; // 指向const对象的const指针。

सारांश:
यदि const है * के बाईं ओर, तो const पॉइंटर द्वारा इंगित वेरिएबल को क्वालिफ़ाई करता है, अर्थात् पॉइंटर का इंगित बिंदु एक कॉन्स्टेंट है;
यदि const है * के दाईं ओर, तो const पॉइंटर को स्वयं क्वालिफ़ाई करता है, अर्थात् पॉइंटर स्वयं एक कॉन्स्टेंट है।

पढ़ने का एक और तरीका
अंग्रेज़ी को दाएँ से बाएँ पढ़ें और "to" को विभाजक मानें: "to" से पहले का भाग पॉइंटर की विशेषता बताता है, और "to" के बाद का भाग लक्ष्य की विशेषता बताता है

const char * p; //p is a pointer to const char
char const * p; //同上
char * const p; //p is a const pointer to char
const char * const p; //p is a const pointer to const char
जब पॉइंटर को const विशेषता दी जाती है, तो पॉइंटर अपने इंगित एड्रेस को नहीं बदल सकता
जब इंगित लक्ष्य की विशेषता char है, तो सामग्री को पॉइंटर के माध्यम से बदला जा सकता है, जैसे: *char='y';
जब इंगित लक्ष्य की विशेषता const char है, तो सामग्री को पॉइंटर के माध्यम से नहीं बदला जा सकता

ठोस उपयोग इस प्रकार है:

(1)कॉन्स्टेंट की ओर इशारा करने वाला पॉइंटर

const int *ptr;
*ptr = 10; // error

ptr एक int टाइप के const ऑब्जेक्ट की ओर इशारा करने वाला पॉइंटर है; const द्वारा परिभाषित किया जाता है int टाइप, यानी वह ऑब्जेक्ट टाइप जिसकी ओर ptr इशारा करता है, न कि ptr स्वयं; इसलिए ptr को आरंभिक मान देना ज़रूरी नहीं। लेकिन ptr के माध्यम से इंगित ऑब्जेक्ट की वैल्यू नहीं बदली जा सकती।

इसके अलावा, void* पॉइंटर का उपयोग करके const ऑब्जेक्ट का एड्रेस रखना भी नहीं किया जा सकता; const ऑब्जेक्ट का एड्रेस रखने के लिए const void* टाइप का पॉइंटर उपयोग करना अनिवार्य है।

const int p = 10;
const void * vp = &p;
void *vp = &p; // error

एक और महत्वपूर्ण बात: गैर-const ऑब्जेक्ट का एड्रेस const ऑब्जेक्ट की ओर इशारा करने वाले पॉइंटर को देना अनुमत है।

गैर-const ऑब्जेक्ट का एड्रेस const ऑब्जेक्ट के पॉइंटर को देना:

const int *ptr;
int val = 3;
ptr = &val; // ok

हम ptr पॉइंटर के माध्यम से val की वैल्यू नहीं बदल सकते, भले ही वह एक गैर-const ऑब्जेक्ट की ओर इशारा कर रहा हो!

हम const ऑब्जेक्ट की ओर इशारा करने वाले पॉइंटर से मूल ऑब्जेक्ट को नहीं बदल सकते; तथापि यदि वह पॉइंटर किसी गैर-const ऑब्जेक्ट की ओर इशारा करता है, तो इंगित ऑब्जेक्ट को अन्य तरीकों से बदला जा सकता है। const पॉइंटर द्वारा इंगित वैल्यू बदली जा सकती है — बस const ऑब्जेक्ट पॉइंटर के माध्यम से नहीं! इस प्रकार बदलें:

int *ptr1 = &val;
*ptr1=4;
cout<<*ptr<<endl;

सारांश:
1. कॉन्स्टेंट की ओर इशारा करने वाले पॉइंटर के लिए, पॉइंटर के माध्यम से ऑब्जेक्ट की वैल्यू नहीं बदली जा सकती।
2. void* पॉइंटर से const ऑब्जेक्ट का एड्रेस नहीं रखा जा सकता; const ऑब्जेक्ट का एड्रेस रखने के लिए const void* टाइप का पॉइंटर उपयोग करना अनिवार्य है।
3. गैर-const ऑब्जेक्ट का एड्रेस const ऑब्जेक्ट के पॉइंटर को देना अनुमत है; यदि पॉइंटर द्वारा इंगित ऑब्जेक्ट की वैल्यू बदलनी हो, तो उसे अन्य तरीकों से बदलना होगा, वर्तमान पॉइंटर के माध्यम से सीधे नहीं।

(2)कॉन्स्टेंट पॉइंटर (const पॉइंटर)

const पॉइंटर को अनिवार्यतः इनिशियलाइज़ करना होता है, और const पॉइंटर द्वारा इंगित वैल्यू बदली जा सकती है, पर उसका इंगित बिंदु नहीं।

#include<iostream>
using namespace std;
int main(){
    int num=0, num1=1;
    int * const ptr=&num; // const指针必须初始化!且const指针的指向不能修改
    ptr = &num1; // error! const指针不能修改指向!
    cout<<*ptr<<endl;
}

कोड में कंपाइल त्रुटि आती है: const पॉइंटर अपना इंगित बिंदु नहीं बदल सकता।

#include<iostream>
using namespace std;
int main(){
    int num=0, num1=1;
    int * const ptr=&num; // const指针必须初始化!且const指针的指向不能修改
    *ptr = 1;
    cout<<*ptr<<endl;
}

इस कोड में कुछ भी नहीं होता, सामान्य रूप से 1 आउटपुट होता है।

अंत में, जब किसी const कॉन्स्टेंट का एड्रेस ptr को दिया जाता है, तो क्योंकि ptr एक वेरिएबल की ओर इशारा करता है, const कॉन्स्टेंट की ओर नहीं, त्रुटि होती है: const int* -> int * त्रुटि!

#include<iostream>
using namespace std;
int main(){
    const int num=0;
    int * const ptr=&num; // error! const int* -> int*
    cout<<*ptr<<endl;
}

उपरोक्त को const int *ptr में बदलने पर, या const int *const ptr में बदलने पर, दोनों स्थितियों में सब ठीक चलता है!

(3)कॉन्स्टेंट की ओर इशारा करने वाला कॉन्स्टेंट पॉइंटर

पहले दो स्थितियों को समझ लेने के बाद, नीचे की स्थिति समझना आसान है:

const int p = 3;
const int * const ptr = &p; 

ptr एक const पॉइंटर है, जो int टाइप के एक const ऑब्जेक्ट की ओर इशारा करता है।

6. फ़ंक्शनों में const का उपयोग

const द्वारा फ़ंक्शन की रिटर्न वैल्यू क्वालिफ़ाई करना

यह const द्वारा सामान्य वेरिएबल और पॉइंटर को क्वालिफ़ाई करने के अर्थ के लगभग समान है:

(1)const int

const int func1();

यह स्वयं अर्थहीन है, क्योंकि रिटर्न वैल्यू तो वैसे ही किसी अन्य वेरिएबल को असाइन की जाती है!

(2)const int*

const int* func2();

पॉइंटर द्वारा इंगित सामग्री अपरिवर्तनीय है।

(3)int *const

int *const func2();

पॉइंटर स्वयं अपरिवर्तनीय है।

const द्वारा फ़ंक्शन पैरामीटर क्वालिफ़ाई करना

(1)पारित पैरामीटर और पॉइंटर स्वयं फ़ंक्शन के भीतर अपरिवर्तनीय — अर्थहीन!

void func(const int var); // 传递过来的参数不可变
void func(int *const var); // 指针本身不可变

इसका अर्थ है कि पैरामीटर को फ़ंक्शन बॉडी में नहीं बदला जा सकता, पर यहाँ इसका कोई अर्थ नहीं: var स्वयं एक फ़ॉर्मल पैरामीटर है और फ़ंक्शन के भीतर नहीं बदलता। यही बात तब भी है जब पारित फ़ॉर्मल पैरामीटर पॉइंटर हो।

जब इनपुट पैरामीटर "वैल्यू से पासिंग" अपनाता है, तो फ़ंक्शन स्वचालित रूप से उस पैरामीटर की प्रतिलिपि के लिए एक टेम्पोरेरी वेरिएबल बनाता है; उस इनपुट पैरामीटर को मूलतः सुरक्षा की ज़रूरत ही नहीं होती, इसलिए const न लगाएँ।

(2)पैरामीटर पॉइंटर द्वारा इंगित सामग्री एक कॉन्स्टेंट है, अपरिवर्तनीय

void StringCopy(char *dst, const char *src);

यहाँ src एक इनपुट पैरामीटर है और dst एक आउटपुट पैरामीटर। src पर const लगाने के बाद, यदि फ़ंक्शन बॉडी का कोई कथन src की सामग्री बदलने का प्रयास करे, तो कंपाइलर त्रुटि बताएगा। यही const लगाने के प्रभावों में से एक है।

(3)पैरामीटर एक रेफ़रेंस है — दक्षता बढ़ाने और साथ ही संशोधन रोकने के लिए।

void func(const A &a)

गैर-आंतरिक डेटा प्रकार के पैरामीटर के लिए, void func(A a) जैसी घोषित फ़ंक्शन की दक्षता अपने आप कम रहती है। क्योंकि फ़ंक्शन बॉडी में पैरामीटर a की प्रतिलिपि के लिए A टाइप का एक टेम्पोरेरी ऑब्जेक्ट बनेगा, और टेम्पोरेरी ऑब्जेक्ट के निर्माण, प्रतिलिपि और विनाश — तीनों में समय लगता है।

दक्षता बढ़ाने के लिए फ़ंक्शन घोषणा को void func(A &a) कर दिया जा सकता है, क्योंकि "रेफ़रेंस से पासिंग" केवल पैरामीटर का उपनाम उधार लेती है, उसे कोई टेम्पोरे री ऑब्जेक्ट बनाने की ज़रूरत नहीं होती।

लेकिन फ़ंक्शन void func(A &a) की एक कमी है:

"रेफ़रेंस से पासिंग" में पैरामीटर a बदल सकता है, जो हम नहीं चाहते। इस समस्या का समाधान बहुत आसान है — const लगा दें, और फ़ंक्शन अंततः void func(const A &a) बन जाता है।

इसी तर्क से, क्या दक्षता बढ़ाने के लिए void func(int x) को void func(const int &x) में बदल देना चाहिए? बिल्कुल ज़रूरत नहीं, क्योंकि आंतरिक डेटा प्रकार के पैरामीटर में निर्माण-विनाश की प्रक्रिया नहीं होती और प्रतिलिपि भी बहुत तेज़ होती है; "वैल्यू से पासिंग" और "रेफ़रेंस से पासिंग" की दक्षता लगभग समान होती है।

सारांश:
1. गैर-आंतरिक डेटा प्रकार के इनपुट पैरामीटर के लिए "वैल्यू से पासिंग" को "const रेफ़रेंस से पासिंग" में बदल देना चाहिए, उद्देश्य दक्षता बढ़ाना है। जैसे void func(A a) को void func(const A &a) में बदलना।

2. आंतरिक डेटा प्रकार के इनपुट पैरामीटर के लिए "वैल्यू से पासिंग" को "const रेफ़रेंस से पासिंग" में न बदलें। अन्यथा न दक्षता बढ़ती है, न फ़ंक्शन की समझ सरल रहती है। जैसे void func(int x) को void func(const int &x) में नहीं बदलना चाहिए।

उपरोक्त से दो इंटरव्यू प्रश्न हल हो गए:

  • यदि फ़ंक्शन को एक पॉइंटर पारित करना हो, तो क्या उस पॉइंटर पर const लगाना चाहिए, और const को पॉइंटर की भिन्न-भिन्न स्थितियों में लगाने से क्या अंतर आता है;
  • यदि लिखे गए फ़ंक्शन का पैरामीटर किसी जटिल प्रकार का इंस्टेंस हो, तो वैल्यू पैरामीटर या रेफ़रेंस पैरामीटर पास करने में क्या अंतर है, और कब पारित रेफ़रेंस पैरामीटर पर const लगाने की ज़रूरत होती है।

7. क्लासों में const का उपयोग

एक क्लास में, कोई भी फ़ंक्शन जो डेटा मेंबर को नहीं बदलता, उसे const टाइप के रूप में घोषित करना चाहिए। यदि const मेंबर फ़ंक्शन लिखते समय आप ग़लती से डेटा मेंबर बदल दें, या कोई अन्य गैर-const मेंबर फ़ंक्शन कॉल कर दें, तो कंपाइलर त्रुटि बताएगा — इससे प्रोग्राम की मजबूती निश्चित रूप से बढ़ती है।

const कीवर्ड से निर्दिष्ट मेंबर फ़ंक्शन को कॉन्स्टेंट मेंबर फ़ंक्शन कहते हैं। केवल कॉन्स्टेंट मेंबर फ़ंक्शन ही कॉन्स्टेंट या कॉन्स्टेंट ऑब्जेक्ट को संचालित करने के योग्य होते हैं; const कीवर्ड से न निर्दिष्ट मेंबर फ़ंक्शन का उपयोग कॉन्स्टेंट ऑब्जेक्ट के संचालन में नहीं किया जा सकता।

क्लास के const मेंबर वेरिएबल को अनिवार्यतः इनिशियलाइज़ेशन सूची से इनिशियलाइज़ करना होता है, जैसा नीचे दिखाया गया है:

class Apple{
private:
    int people[100];
public:
    Apple(int i); 
    const int apple_number;
};

Apple::Apple(int i):apple_number(i)
{

}

const ऑब्जेक्ट केवल const मेंबर फ़ंक्शन तक एक्सेस कर सकते हैं, जबकि गैर-const ऑब्जेक्ट किसी भी मेंबर फ़ंक्शन तक एक्सेस कर सकते हैं, const मेंबर फ़ंक्शन सहित।

उदाहरण के लिए:

// apple.cpp
class Apple
{
private:
    int people[100];
public:
    Apple(int i); 
    const int apple_number;
    void take(int num) const;
    int add();
    int add(int num) const;
    int getCount() const;

};
// apple.cpp
Apple::Apple(int i) : apple_number(i)
{
}
int Apple::add(int num)
{
    take(num);
    return 0;
}
int Apple::add(int num) const
{
    take(num);
    return 0;
}
void Apple::take(int num) const
{
    std::cout << "take func " << num << std::endl;
}
int Apple::getCount() const
{
    take(1);
    //    add(); // error
    return apple_number;
}
int main()
{
    Apple a(2);
    cout << a.getCount() << endl;
    a.add(10);
    const Apple b(3);
    b.add(100);
    return 0;
}
// main.cpp

कंपाइल: bazel run basic_content/const/class_const/first_example:main

इस समय त्रुटि आती है: getCount() एक const मेंबर फ़ंक्शन है, और उसके भीतर add() का ओवरलोड निर्णय केवल const संस्करण (int add(int num) const) से ही मेल खाता है; उस const संस्करण को कमेंट करने के बाद ही गैर-const add() को कॉल करने की त्रुटि आती है। अर्थात्: const ऑब्जेक्ट केवल const मेंबर फ़ंक्शन तक एक्सेस कर सकते हैं, और गैर-const ऑब्जेक्ट किसी भी मेंबर फ़ंक्शन तक एक्सेस कर सकते हैं (देखें issue #338)।

जब कॉल बदलकर यह किया जाता है:

const Apple b(3);
b.add(); // error

इससे सिद्ध होता है कि const ऑब्जेक्ट केवल const मेंबर फ़ंक्शन तक एक्सेस कर सकते हैं।

ऊपर बताई इनिशियलाइज़ेशन सूची विधि से const कॉन्स्टेंट इनिशियलाइज़ करने के अलावा, हम नीचे दी गई विधियों का भी उपयोग कर सकते हैं:

पहली: कॉन्स्टेंट की परिभाषा को static के साथ जोड़ें, यानी:

static const int apple_number

दूसरी: बाहर इनिशियलाइज़ करें:

const int Apple::apple_number=10;

बेशक, यदि आप c++11 से कंपाइल करते हैं, तो परिभाषा के स्थान पर ही सीधे इनिशियलाइज़ कर सकते हैं और सीधे ऐसे लिख सकते हैं:

static const int apple_number=10;
// 或者
const int apple_number=10;

ये दोनों c++11 में समर्थित हैं!

कंपाइल करते समय -std=c++11 लगा देना ही पर्याप्त है!

यहाँ static का ज़िक्र आ गया है, नीचे संक्षेप में कहा गया है:

C++ में, static स्टैटिक मेंबर वेरिएबल को क्लास के भीतर इनिशियलाइज़ नहीं किया जा सकता। क्लास के भीतर केवल घोषणा होती है; परिभाषा अनिवार्यतः क्लास परिभाषा बॉडी के बाहर होनी चाहिए, प्रायः क्लास की इम्प्लीमेंटेशन फ़ाइल में इनिशियलाइज़ किया जाता है।

क्लास में घोषणा:

static int ap;

क्लास की इम्प्लीमेंटेशन फ़ाइल में उपयोग:

int Apple::ap=666

इस मद के लिए, c++11 में घोषणा और इनिशियलाइज़ेशन एक साथ नहीं किया जा सकता, यानी उपरोक्त उपयोग विधि ही चलती है।

सामान्य प्रश्न

const पॉइंटर और पॉइंटर-टू-const में क्या अंतर है?

const int* p मतलब कॉन्स्टेंट की ओर पॉइंटर: मान बदला नहीं जा सकता। int* const p मतलब कॉन्स्टेंट पॉइंटर: पॉइंटर दोबारा असाइन नहीं होता। नियम: * के बाएँ const मान की रक्षा करता है, दाएँ पॉइंटर की।

क्या const ऑब्जेक्ट गैर-const मेंबर फ़ंक्शन कॉल कर सकता है?

नहीं। const ऑब्जेक्ट केवल const मेंबर फ़ंक्शन कॉल कर सकता है; गैर-const ऑब्जेक्ट दोनों कॉल कर सकता है।

कॉन्स्टेंट के लिए const या #define?

const बेहतर है: टाइप-चेकिंग, स्कोप का पालन और डिबगिंग संभव है। #define टाइप-रहित टेक्स्ट प्रतिस्थापन है।

संबंधित विषय

टिप्पणियाँ