सी में संकेत: एम्परसेंड और तारांकन का उपयोग कब करें?


298

मैं बस संकेत के साथ शुरू कर रहा हूं, और मैं थोड़ा भ्रमित हूं। मुझे पता &है कि एक चर के पते का मतलब है और जो *सूचक द्वारा इंगित की गई वस्तु का मूल्य प्राप्त करने के लिए एक सूचक चर के सामने इस्तेमाल किया जा सकता है। जब आप सरणियों, तारों के साथ काम कर रहे हों या जब आप किसी चर की पॉइंटर कॉपी के साथ फ़ंक्शंस बुला रहे हों तो चीजें अलग तरह से काम करती हैं। इस सब के अंदर तर्क का एक पैटर्न देखना मुश्किल है।

मुझे कब &और *कैसे उपयोग करना चाहिए ?


5
कृपया बताएं कि आप किस तरह चीजों को कभी-कभी अलग तरीके से काम करते देखते हैं। अन्यथा, हमें अनुमान लगाना होगा कि यह क्या है जो आपको भ्रमित कर रहा है।
ड्रू डॉर्मन

1
नील बटरवर्थ से सहमत हूँ। संभवतः इसे एक पुस्तक से पहली बार प्राप्त करने वाली बहुत अधिक जानकारी प्राप्त होने वाली है, और K & R स्पष्टीकरण काफी स्पष्ट है।
टॉम

144
मैं आप सभी से असहमत हूं जो कहते हैं कि एसओ पर इस प्रकार के प्रश्न पूछना अच्छा नहीं है। Google पर खोज करने पर SO नंबर 1 संसाधन बन गया है। आप इन उत्तरों को पर्याप्त क्रेडिट नहीं दे रहे हैं। दान ओल्सन की प्रतिक्रिया पढ़ें। यह उत्तर वास्तव में व्यावहारिक और अविश्वसनीय रूप से newbies के लिए उपयोगी है। RTFMअनैतिक है, और काफी स्पष्ट रूप से बहुत अशिष्ट है। यदि आपके पास उत्तर देने का समय नहीं है, तो उन लोगों का सम्मान करें जो इन सवालों के जवाब देने के लिए समय लेते हैं। मेरी इच्छा है कि मैं इसे "एनॉन" कर सकता हूं, लेकिन जाहिर है कि उनके पास किसी भी सार्थक तरीके से योगदान करने का समय नहीं है।
एसएसएच यह

18
क्या SSH यह कहा बिल्कुल सच है। कुछ लोग "जस्ट गूगल इट" चिल्लाते हैं, लेकिन आजकल इसका दूसरा तरीका है: "बस स्टैकऑवरफ्लो को देखें।" यह प्रश्न कई लोगों के लिए उपयोगी है। (इसलिए अपवोट्स और डाउनवोट्स नहीं।)
एमसी सम्राट

जवाबों:


610

आपके पास संकेत और मान हैं:

int* p; // variable p is pointer to integer type
int i; // integer value

आप एक पॉइंटर को एक वैल्यू में बदल देते हैं *:

int i2 = *p; // integer i2 is assigned with integer value that pointer p is pointing to

आप एक पॉइंटर में एक मान को बदल देते हैं &:

int* p2 = &i; // pointer p2 will point to the address of integer i

संपादित करें: सरणियों के मामले में, उन्हें संकेतकर्ताओं की तरह व्यवहार किया जाता है। यदि आप उन्हें संकेत के रूप में सोचते हैं, तो आप *ऊपर बताए अनुसार उनके अंदर के मूल्यों को पाने के लिए उपयोग कर रहे होंगे , लेकिन []ऑपरेटर का उपयोग करने का एक और तरीका भी है :

int a[2];  // array of integers
int i = *a; // the value of the first element of a
int i2 = a[0]; // another way to get the first element

दूसरा तत्व प्राप्त करने के लिए:

int a[2]; // array
int i = *(a + 1); // the value of the second element
int i2 = a[1]; // the value of the second element

इसलिए []इंडेक्सिंग ऑपरेटर ऑपरेटर का एक विशेष रूप है *, और यह इस तरह काम करता है:

a[i] == *(a + i);  // these two statements are the same thing

2
यह कैसे काम नहीं करता है? int aX[] = {3, 4}; int *bX = &aX;
पीटर

5
एरे विशेष हैं और पारदर्शी रूप से पॉइंटर्स में परिवर्तित हो सकते हैं। यह एक पॉइंटर से एक मान प्राप्त करने का दूसरा तरीका हाइलाइट करता है, मैं इसे ऊपर दिए गए स्पष्टीकरण में जोड़ दूंगा।
डैन ओल्सन

4
अगर मैं इसे सही ढंग से समझता हूं ... उदाहरण int *bX = &aX;काम नहीं करता है क्योंकि aXपहले से ही aX[0](यानी &aX[0]) का पता देता है , इसलिए &aXउस पते का पता मिल जाएगा जिसका कोई मतलब नहीं है। क्या ये सही है?
पीटर

6
आप सही हैं, हालांकि ऐसे मामले हैं जहां आप वास्तव में पते का पता चाहते हैं। उस स्थिति में आप इसे int ** bX = & aX के रूप में घोषित करेंगे, लेकिन यह अधिक उन्नत विषय है।
डैन ओल्सन

3
@, दिया गया int aX[] = {3,4};, int **bX = &aX;एक त्रुटि है। &aX"सूचक को सरणी [2]" का प्रकार है int, न कि "सूचक को सूचक int"। विशेष रूप से, एक सरणी का नाम यूनरी के लिए इसके पहले तत्व के लिए एक संकेतक के रूप में नहीं माना जाता है &। आप ऐसा कर सकते हैं:int (*bX)[2] = &aX;
आलोक सिंघल

28

सरणियों और कार्यों से निपटने के दौरान एक पैटर्न है; यह पहली बार में देखना थोड़ा कठिन है।

सरणियों के साथ काम करते समय, निम्नलिखित को याद रखना उपयोगी होता है: जब एक सरणी अभिव्यक्ति सबसे संदर्भों में दिखाई देती है, तो अभिव्यक्ति का प्रकार स्पष्ट रूप से "एन-एलिमेंट सरणी ऑफ टी" से "पॉइंटर टू टी" में बदल जाता है, और इसका मान सेट होता है सरणी में पहले तत्व को इंगित करने के लिए। इस नियम के अपवाद तब होते हैं जब सरणी अभिव्यक्ति &या तो sizeofऑपरेटरों या ऑपरेटरों के एक ऑपरेंड के रूप में प्रकट होती है , या जब यह एक स्ट्रिंग शाब्दिक है जिसे घोषणा में इनिशियलाइज़र के रूप में उपयोग किया जा रहा है।

इस प्रकार, जब आप किसी फ़ंक्शन को तर्क के रूप में एरे एक्सप्रेशन के साथ कहते हैं, तो फ़ंक्शन एक पॉइंटर प्राप्त करेगा, न कि एरे:

int arr[10];
...
foo(arr);
...

void foo(int *arr) { ... }

यही कारण है कि आप "% s" से संबंधित तर्कों के लिए ऑपरेटर का उपयोग नहीं करते हैं :&scanf()

char str[STRING_LENGTH];
...
scanf("%s", str);

अंतर्निहित रूपांतरण के कारण, scanf()एक char *मान प्राप्त करता है जो strसरणी की शुरुआत को इंगित करता है । यह किसी भी फ़ंक्शन के लिए एक सरणी अभिव्यक्ति के साथ एक तर्क के रूप में कहा जाता है (बस किसी भी str*फ़ंक्शन *scanfऔर *printfफ़ंक्शन आदि के बारे में) सही है।

व्यवहार में, आप संभवतः किसी फ़ंक्शन को &ऑपरेटर के उपयोग से सरणी अभिव्यक्ति के साथ कभी नहीं कहेंगे , जैसे कि:

int arr[N];
...
foo(&arr);

void foo(int (*p)[N]) {...}

ऐसा कोड बहुत आम नहीं है; आपको फ़ंक्शन घोषणा में सरणी के आकार को जानना होगा, और फ़ंक्शन केवल विशिष्ट आकार के सरणियों के लिए कार्य करता है (T-10-सरणी सरणी के लिए सूचक एक 11-तत्व सरणी के लिए सूचक की तुलना में एक अलग प्रकार है T की)।

जब एक सरणी अभिव्यक्ति &ऑपरेटर के लिए एक ऑपरेंड के रूप में प्रकट होती है , तो परिणामस्वरूप अभिव्यक्ति का प्रकार "टी-एन-एलिमेंट ऑफ टी-पॉइंटर" होता है, या T (*)[N], जो पॉइंटर्स एरे से अलग होता है ( T *[N]) और एक पॉइंटर से बेस टाइप () T *)।

फ़ंक्शंस और पॉइंटर्स के साथ काम करते समय, याद रखने का नियम है: यदि आप एक तर्क के मूल्य को बदलना चाहते हैं और इसे कॉलिंग कोड में परिलक्षित करते हैं, तो आपको उस चीज़ को एक पॉइंटर पास करना होगा जिसे आप संशोधित करना चाहते हैं। फिर से, सरणियों ने बंदर के रिंच को काम में फेंक दिया, लेकिन हम पहले सामान्य मामलों से निपटेंगे।

याद रखें कि C मान द्वारा सभी फ़ंक्शन तर्क पास करता है; औपचारिक पैरामीटर वास्तविक पैरामीटर में मान की एक प्रति प्राप्त करता है, और औपचारिक पैरामीटर में कोई भी परिवर्तन वास्तविक पैरामीटर में परिलक्षित नहीं होता है। सामान्य उदाहरण एक स्वैप कार्य है:

void swap(int x, int y) { int tmp = x; x = y; y = tmp; }
...
int a = 1, b = 2;
printf("before swap: a = %d, b = %d\n", a, b);
swap(a, b);
printf("after swap: a = %d, b = %d\n", a, b);

आपको निम्न आउटपुट मिलेगा:

स्वैप से पहले: a = 1, b = 2
स्वैप के बाद: a = 1, b = 2

औपचारिक पैरामीटर xऔर yसे अलग वस्तुओं रहे हैं aऔर b, इसलिए करने के लिए परिवर्तन xऔर yमें नहीं दिखते aऔर b। जब से हम के मूल्यों को संशोधित करना चाहते aहैं और b, हम गुजरना होगा संकेत स्वैप समारोह के लिए उन्हें करने के लिए:

void swap(int *x, int *y) {int tmp = *x; *x = *y; *y = tmp; }
...
int a = 1, b = 2;
printf("before swap: a = %d, b = %d\n", a, b);
swap(&a, &b);
printf("after swap: a = %d, b = %d\n", a, b);

अब आपका आउटपुट होगा

स्वैप से पहले: a = 1, b = 2
स्वैप के बाद: a = 2, b = 1

ध्यान दें कि, स्वैप फ़ंक्शन में, हम xऔर के मूल्यों को नहीं बदलते हैं y, लेकिन क्या xऔर y इंगित करते हैं । लिखने *xसे लिखने के लिए अलग है x; हम xअपने आप में मूल्य को अपडेट नहीं कर रहे हैं , हम उस स्थान से xमूल्य प्राप्त करते हैं।

यह उतना ही सच है अगर हम एक पॉइंटर मान को संशोधित करना चाहते हैं; अगर हम लिखते हैं

int myFopen(FILE *stream) {stream = fopen("myfile.dat", "r"); }
...
FILE *in;
myFopen(in);

फिर हम इनपुट पैरामीटर के मूल्य को संशोधित कर रहे हैं stream, न कि किस stream बिंदु पर , इसलिए परिवर्तन streamका मूल्य पर कोई प्रभाव नहीं है in; इसे काम करने के लिए, हमें एक पॉइंटर से पॉइंटर में जाना होगा:

int myFopen(FILE **stream) {*stream = fopen("myFile.dat", "r"); }
...
FILE *in;
myFopen(&in);

फिर से, सरणियों ने बंदर के रिंच को काम में फेंक दिया। जब आप किसी फ़ंक्शन के लिए एक सरणी अभिव्यक्ति पास करते हैं, तो फ़ंक्शन जो प्राप्त करता है वह एक सूचक है। सरणी सबस्क्रिप्टिंग को किस प्रकार परिभाषित किया गया है, इसके कारण आप एक सूचक पर एक सबस्क्रिप्ट ऑपरेटर का उपयोग उसी तरह कर सकते हैं जिस प्रकार आप इसे किसी सरणी पर उपयोग कर सकते हैं:

int arr[N];
init(arr, N);
...
void init(int *arr, int N) {size_t i; for (i = 0; i < N; i++) arr[i] = i*i;}

ध्यान दें कि सरणी ऑब्जेक्ट असाइन नहीं किए जा सकते हैं; यानी, आप ऐसा कुछ नहीं कर सकते

int a[10], b[10];
...
a = b;

इसलिए आप सावधान रहना चाहते हैं जब आप एरियर्स के साथ काम कर रहे हैं; कुछ इस तरह

void (int (*foo)[N])
{
  ...
  *foo = ...;
}

काम नहीं करेगा।


16

सीधे शब्दों में कहें

  • &एड्रेस-इन का अर्थ है , आप देखेंगे कि सी के रूप में पैरामीटर चर को संशोधित करने के लिए कार्यों के लिए प्लेसहोल्डर्स, पैरामीटर चर मान से पारित किए जाते हैं, एम्परसेंड का उपयोग करके संदर्भ से गुजरना होता है।
  • *इसका मतलब है कि पॉइंटर वैरिएबल का डीरेफेरेंस , मतलब उस पॉइंटर वैरिएबल की वैल्यू पाने के लिए।
int foo(int *x){
   *x++;
}

int main(int argc, char **argv){
   int y = 5;
   foo(&y);  // Now y is incremented and in scope here
   printf("value of y = %d\n", y); // output is 6
   /* ... */
}

उपरोक्त उदाहरण fooपास-बाय-संदर्भ का उपयोग करके फ़ंक्शन को कॉल करने का तरीका दिखाता है , इसके साथ तुलना करें

int foo(int x){
   x++;
}

int main(int argc, char **argv){
   int y = 5;
   foo(y);  // Now y is still 5
   printf("value of y = %d\n", y); // output is 5
   /* ... */
}

यहाँ एक अनुमापन का उपयोग करने का चित्रण है

int main(int argc, char **argv){
   int y = 5;
   int *p = NULL;
   p = &y;
   printf("value of *p = %d\n", *p); // output is 5
}

उपरोक्त दिखाता है कि हमने पता कैसे प्राप्त किया yऔर इसे सूचक चर को सौंपा p। फिर हम भिन्नता p संलग्न द्वारा *इसे के सामने करने के लिए का मान प्राप्त करने के लिए p, यानी *p


10

हां, जो *C / C ++ में कई अलग-अलग उद्देश्यों के लिए उपयोग किए जाने के बाद से काफी जटिल हो सकता है ।

यदि *पहले से घोषित चर / फ़ंक्शन के सामने प्रकट होता है, तो इसका मतलब यह है कि:

  • a) *उस चर के मान को एक्सेस देता है (यदि उस चर का प्रकार एक सूचक प्रकार है, या *ऑपरेटर को अधिभारित किया गया है)।
  • b) *का अर्थ बहुली ऑपरेटर है, उस स्थिति में, बाईं ओर एक और चर होना चाहिए*

यदि *एक चर या फ़ंक्शन घोषणा में प्रकट होता है तो इसका मतलब है कि चर एक सूचक है:

int int_value = 1;
int * int_ptr; //can point to another int variable
int   int_array1[10]; //can contain up to 10 int values, basically int_array1 is an pointer as well which points to the first int of the array
//int   int_array2[]; //illegal, without initializer list..
int int_array3[] = {1,2,3,4,5};  // these two
int int_array4[5] = {1,2,3,4,5}; // are identical

void func_takes_int_ptr1(int *int_ptr){} // these two are identical
void func_takes int_ptr2(int int_ptr[]){}// and legal

यदि &एक चर या फ़ंक्शन घोषणा में प्रकट होता है, तो इसका आम तौर पर मतलब है कि चर उस प्रकार के एक चर का संदर्भ है।

यदि &पहले से घोषित चर के सामने प्रकट होता है, तो यह उस चर का पता देता है

इसके अतिरिक्त आपको पता होना चाहिए कि किसी फ़ंक्शन को सरणी पास करते समय, आपको हमेशा उस सरणी के सरणी आकार को पास करना होगा, इसके अलावा जब सरणी 0-टर्मिनेटेड cstring (चार सरणी) जैसा कुछ हो।


1
@akmozo s / func_takes int_ptr2 / func_takes_int_ptr2 / (अमान्य स्थान)
PixnBits

4

जब आप सूचक चर या फ़ंक्शन पैरामीटर घोषित कर रहे हैं, तो * का उपयोग करें:

int *x = NULL;
int *y = malloc(sizeof(int)), *z = NULL;
int* f(int *x) {
    ...
}

नायब: प्रत्येक घोषित चर की अपनी जरूरत है *।

जब आप किसी मूल्य का पता लेना चाहते हैं, तो & का उपयोग करें। जब आप पॉइंटर में मान पढ़ना या लिखना चाहते हैं, तो * का उपयोग करें।

int a;
int *b;
b = f(&a);
a = *b;

a = *f(&a);

Arrays आमतौर पर संकेत की तरह व्यवहार कर रहे हैं। जब आप किसी फ़ंक्शन में ऐरे पैरामीटर की घोषणा करते हैं, तो आप आसानी से घोषित कर सकते हैं कि यह एक पॉइंटर है (इसका मतलब वही है)। जब आप किसी फ़ंक्शन के लिए एक सरणी पास करते हैं, तो आप वास्तव में पहले तत्व को एक पॉइंटर पास कर रहे हैं।

फंक्शन पॉइंटर्स एकमात्र ऐसी चीजें हैं जो नियमों का पालन नहीं करती हैं। आप & का उपयोग किए बिना किसी फ़ंक्शन का पता ले सकते हैं, और आप * का उपयोग किए बिना फ़ंक्शन पॉइंटर को कॉल कर सकते हैं।


4

मैं बचाव के लिए यूनिवर्सिटी ऑफ न्यू साउथ वेल्स के वीडियो की ओर रुख कर रहा था, इसलिए सभी सरल स्पष्टीकरणों को देख रहे थे। यह सरल स्पष्टीकरण है: यदि हमारे पास एक सेल है जिसमें पता xऔर मूल्य है 7, तो मूल्य 7का पता पूछने के लिए अप्रत्यक्ष तरीका है &7और अप्रत्यक्ष तरीके पते पर मूल्य के लिए पूछना xहै *xतो (cell: x , value: 7) == (cell: &7 , value: *x).Another तरह से इस पर गौर करने के लिए: Johnपर बैठता है 7th seatव्याप्ति *7th seatको इंगित करेगा Johnऔर &Johnदे देंगे addressके स्थान / 7th seat। इस सरल व्याख्या ने मेरी मदद की और मुझे उम्मीद है कि इससे दूसरों को भी मदद मिलेगी। यहाँ उत्कृष्ट वीडियो के लिए लिंक है: यहाँ क्लिक करें।

यहाँ एक और उदाहरण है:

#include <stdio.h>

int main()
{ 
    int x;            /* A normal integer*/
    int *p;           /* A pointer to an integer ("*p" is an integer, so p
                       must be a pointer to an integer) */

    p = &x;           /* Read it, "assign the address of x to p" */
    scanf( "%d", &x );          /* Put a value in x, we could also use p here */
    printf( "%d\n", *p ); /* Note the use of the * to get the value */
    getchar();
}

ऐड-ऑन: हमेशा उनका उपयोग करने से पहले पॉइंटर को इनिशियलाइज़ करें। यदि नहीं, तो पॉइंटर कुछ भी इंगित करेगा, जिसके परिणामस्वरूप प्रोग्राम क्रैश हो सकता है क्योंकि ऑपरेटिंग सिस्टम आपको उस मेमोरी को एक्सेस करने से रोकेगा जिसे आप जानते हैं कि वह आपके पास नहीं है। लेकिन बस डाल p = &x;, हम पॉइंटर को एक विशिष्ट स्थान निर्दिष्ट कर रहे हैं।


3

वास्तव में, आप इसे नीचे पैट है, वहाँ कुछ भी नहीं है और आप को जानने की जरूरत है :-)

मैं बस निम्नलिखित बिट्स जोड़ूंगा:

  • दो संचालन स्पेक्ट्रम के विपरीत छोर हैं। &एक चर लेता है और आपको पता देता है, *एक पता लेता है और आपको चर (या सामग्री) देता है।
  • जब आप उन्हें फ़ंक्शंस में पास करते हैं, तो पॉइंटर्स को "डीग्रेड" करें।
  • आप वास्तव में अप्रत्यक्ष पर कई स्तर हो सकते हैं ( char **pइसका मतलब है कि pएक सूचक से एक संकेतक के लिए सूचक है char

चीजों को अलग तरह से काम करने के लिए, वास्तव में नहीं:

  • सरणियाँ, जैसा कि पहले ही उल्लेख किया गया है, कार्य करने के लिए पारित होने पर संकेत (सरणी में पहले तत्व) को नीचा दिखाना; वे आकार की जानकारी संरक्षित नहीं करते हैं।
  • सी में कोई तार नहीं हैं, बस चरित्र सरणियां, जो कि, कन्वेंशन द्वारा, शून्य ( \0) वर्ण द्वारा समाप्त किए गए वर्णों की एक स्ट्रिंग का प्रतिनिधित्व करती हैं ।
  • जब आप किसी फ़ंक्शन के लिए एक चर का पता पास करते हैं, तो आप सूचक को चर को बदलने के लिए स्वयं को संदर्भित कर सकते हैं (सामान्य रूप से चर को मानों (सरणियों को छोड़कर) द्वारा पारित किया जाता है)।

3

मुझे लगता है कि आप थोड़ा भ्रमित हैं। आपको पॉइंटर्स पर एक अच्छा ट्यूटोरियल / बुक पढ़ना चाहिए।

यह ट्यूटोरियल शुरुआत के लिए बहुत अच्छा है (स्पष्ट रूप से बताता है कि क्या &और क्या *हैं)। और हाँ केनेथ रीक द्वारा सी में पुस्तक पॉइंटर्स को पढ़ना न भूलें ।

के बीच का अंतर &और *बहुत स्पष्ट है।

उदाहरण:

#include <stdio.h>

int main(){
  int x, *p;

  p = &x;         /* initialise pointer(take the address of x) */
  *p = 0;         /* set x to zero */
  printf("x is %d\n", x);
  printf("*p is %d\n", *p);

  *p += 1;        /* increment what p points to i.e x */
  printf("x is %d\n", x);

  (*p)++;         /* increment what p points to i.e x */
  printf("x is %d\n", x);

  return 0;
}

1

ठीक है, ऐसा लगता है कि आपकी पोस्ट संपादित हो गई है ...

double foo[4];
double *bar_1 = &foo[0];

देखें कि आप &सरणी संरचना की शुरुआत के पते को प्राप्त करने के लिए कैसे उपयोग कर सकते हैं ? निम्नलिखित

Foo_1(double *bar, int size){ return bar[size-1]; }
Foo_2(double bar[], int size){ return bar[size-1]; }

वही काम करेगा।


प्रश्न को C नहीं C ++ टैग किया गया है।
प्रसून सौरव

1
और मैंने अपमानजनक कूट हटा दिया है
गेहूँ
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.