बाकी कई भाषाओं की तरह Go में भी पॉइंटर होते हैं। अगर आप पॉइंटर के लिए नए हैं, तो शुरुआत में ये थोड़े रहस्यमय लग सकते हैं, लेकिन एक बार इनकी आदत पड़ जाने पर ये काफी सीधे-सरल लगते हैं। ये Go का बहुत ज़रूरी हिस्सा हैं, इसलिए इन्हें ठीक से समझने के लिए थोड़ा समय ज़रूर दीजिए।
बारीकियों में उतरने से पहले यह समझ लेना फायदेमंद है कि पॉइंटर काम किस तरह आते हैं। पॉइंटर हमारे प्रोग्राम के दूसरे हिस्सों के साथ मेमोरी साझा करने का तरीका हैं, और यह दो बड़े कारणों से काम आता है:
मान लीजिए हमारे पास एक साधारण पूर्णांक वेरिएबल a है:
var a int
जब हम कोई वेरिएबल घोषित करते हैं, तो Go को उसकी वैल्यू रखने के लिए मेमोरी में जगह ढूँढनी पड़ती है। यह बात हमसे काफी हद तक छिपी रहती है। जब हमें उस मेमोरी में रखी वैल्यू निकालनी होती है, तो हम सिर्फ वेरिएबल का नाम लेकर उसे पा सकते हैं।
जैसे, जब हम a + 2 लिखते हैं, तो असल में हम वेरिएबल a से जुड़ी मेमोरी में रखी वैल्यू निकालते हैं और उसमें 2 जोड़ते हैं।
इसी तरह, जब हमें a वाली मेमोरी में रखी वैल्यू बदलनी होती है, तो हम वेरिएबल का नाम इस्तेमाल करके असाइनमेंट कर सकते हैं:
a = 3
अब a से जुड़ी मेमोरी में वैल्यू 3 संग्रहीत हो जाएगी।
वेरिएबल हमें मेमोरी में रखी वैल्यू का संदर्भ देते हैं, लेकिन कई बार यह जानना भी काम आता है कि वेरिएबल किस मेमोरी एड्रेस की ओर इशारा कर रहा है। पॉइंटर उन वैल्यू के मेमोरी एड्रेस अपने पास रखते हैं। पॉइंटर टाइप का वेरिएबल घोषित करने के लिए मूल टाइप से पहले एक तारा चिह्न (*) लगाइए:
var p *int // 'p' contains the memory address of an integer
यहाँ हम p नाम का वेरिएबल घोषित कर रहे हैं, जिसका टाइप "पूर्णांक का पॉइंटर" (*int) है।
इसका मतलब है कि p में एक पूर्णांक का मेमोरी एड्रेस रहेगा।
पॉइंटर की ज़ीरो वैल्यू nil होती है, क्योंकि nil पॉइंटर के पास कोई मेमोरी एड्रेस नहीं होता।
किसी वेरिएबल की वैल्यू का मेमोरी एड्रेस पाने के लिए हम & ऑपरेटर इस्तेमाल कर सकते हैं।
उदाहरण के लिए, अगर हम वेरिएबल a का मेमोरी एड्रेस ढूँढकर पॉइंटर p में रखना चाहते हैं, तो हम यह कर सकते हैं:
var a int
a = 2
var p *int
p = &a // the variable 'p' contains the memory address of 'a'
जब हमारे पास कोई पॉइंटर होता है, तो कई बार हम यह जानना चाहते हैं कि जिस मेमोरी एड्रेस की ओर वह पॉइंटर इशारा करता है, उसमें कौन-सी वैल्यू रखी है।
इसके लिए हम * ऑपरेटर इस्तेमाल कर सकते हैं:
var a int
a = 2
var p *int
p = &a // the variable 'p' contains the memory address of 'a'
var b int
b = *p // b == 2
*p ऑपरेशन उस वैल्यू को निकालता है जो p में रखे मेमोरी एड्रेस पर संग्रहीत है।
इस ऑपरेशन को अक्सर "डीरेफरेंसिंग" कहा जाता है।
हम डीरेफरेंस ऑपरेटर का इस्तेमाल करके पॉइंटर द्वारा संदर्भित मेमोरी एड्रेस पर नई वैल्यू भी असाइन कर सकते हैं:
var a int // declare int variable 'a'
a = 2 // assign 'a' the value of 2
var pa *int
pa = &a // 'pa' now contains to the memory address of 'a'
*pa = *pa + 2 // increment by 2 the value at memory address 'pa'
fmt.Println(a) // Output: 4
// 'a' will have the new value that was changed via the pointer!
*pa पर असाइन करने से उस मेमोरी एड्रेस पर रखी वैल्यू बदल जाएगी जो pa अपने पास रखता है।
चूँकि pa में a का मेमोरी एड्रेस है, इसलिए *pa पर असाइन करके हम असल में a की वैल्यू ही बदल रहे हैं!
लेकिन एक सावधानी ज़रूरी है: डीरेफरेंस करने से पहले हमेशा जाँच लीजिए कि पॉइंटर nil नहीं है।
nil पॉइंटर को डीरेफरेंस करने से प्रोग्राम रनटाइम पर क्रैश हो जाएगा!
var p *int // p is nil initially
fmt.Println(*p)
// panic: runtime error: invalid memory address or nil pointer dereference
अब तक हमने सिर्फ बुनियादी वैल्यू के पॉइंटर देखे हैं। हम स्ट्रक्ट के लिए भी पॉइंटर बना सकते हैं:
type Person struct {
Name string
Age int
}
var peter Person
peter = Person{Name: "Peter", Age: 22}
var p *Person
p = &peter
हम एक नया Person बनाकर तुरंत उसका पॉइंटर भी रख सकते थे:
var p *Person
p = &Person{Name: "Peter", Age: 22}
जब हमारे पास किसी स्ट्रक्ट का पॉइंटर होता है, तो उसके किसी फील्ड तक पहुँचने से पहले पॉइंटर को डीरेफरेंस करने की ज़रूरत नहीं होती:
var p *Person
p = &Person{Name: "Peter", Age: 22}
fmt.Println(p.Name) // Output: "Peter"
// Go automatically dereferences 'p' to allow
// access to the 'Name' field
स्लाइस और मैप खास टाइप हैं, क्योंकि इनके अंदरूनी काम में पहले से ही पॉइंटर होते हैं। इसका मतलब है कि इन टाइप की वैल्यू का मेमोरी एड्रेस साझा करने के लिए हमें ज़्यादातर इनके पॉइंटर बनाने की ज़रूरत नहीं पड़ती। सोचिए, हमारे पास एक ऐसा फंक्शन है जो मैप के किसी एक की की वैल्यू बढ़ा देता है:
func incrementPeterAge(m map[string]int) {
m["Peter"] += 1
}
अगर हम एक मैप बनाकर इस फंक्शन को कॉल करें, तो फंक्शन खत्म होने के बाद भी मैप में किए गए बदलाव बने रहते हैं। यह व्यवहार वैसा ही है जैसा पॉइंटर इस्तेमाल करने पर मिलता है, लेकिन ध्यान दीजिए कि इस उदाहरण में हम कोई रेफरेंसिंग/डीरेफरेंसिंग या पॉइंटर का कोई सिंटैक्स इस्तेमाल नहीं कर रहे:
ages := map[string]int{
"Peter": 21
}
incrementPeterAge(ages)
fmt.Println(ages)
// Output: map[Peter:22]
// The changes the function 'incrementPeterAge' made to the map are visible after the function ends!
यही बात स्लाइस में पहले से मौजूद किसी एलिमेंट को बदलने पर भी लागू होती है।
लेकिन append जैसे वे काम, जो नया स्लाइस लौटाते हैं, एक खास मामला हैं और हो सकता है कि फंक्शन के बाहर वाले स्लाइस को बदलें ही नहीं।
ऐसा स्लाइस के अंदरूनी तरीके की वजह से होता है, लेकिन यह विषय काफी उन्नत है, इसलिए इस अभ्यास में हम इस पर विस्तार से नहीं जाएँगे।
अगर आप और जानने के इच्छुक हैं, तो इसके बारे में और पढ़ सकते हैं: Go Blog: Mechanics of 'append'
आपके आस-पास के एक स्थानीय स्कूल में छात्रों का एक बहुत सक्रिय संघ है। इस छात्र संघ का संचालन एक अध्यक्ष करता है, और हर 2 साल में एक बार नया अध्यक्ष चुनने के लिए चुनाव होते हैं।
इस साल के चुनाव में यह तय हुआ कि वोट गिनने के लिए एक नए डिजिटल सिस्टम की ज़रूरत है। यह नया सिस्टम बनाने में स्कूल को आपकी मदद चाहिए।
नए वोटिंग सिस्टम को सबसे पहले जिन चीज़ों की ज़रूरत है, उनमें से एक है वोट काउंटर। यह काउंटर किसी एक उम्मीदवार को मिले वोटों का हिसाब रखने का तरीका है।
एक फंक्शन NewVoteCounter बनाइए जो किसी उम्मीदवार के शुरुआती वोटों की संख्या लेता है और एक int की ओर इशारा करने वाला पॉइंटर लौटाता है, जिसमें दी गई शुरुआती वोट संख्या रखी जाती है।
var initialVotes int
initialVotes = 2
var counter *int
counter = NewVoteCounter(initialVotes)
*counter == initialVotes // true
अब आपके पास नए काउंटर बनाने का तरीका है! लेकिन अब आपको एहसास होता है कि नए सिस्टम को काउंटर से वोटों की संख्या निकालने का तरीका भी चाहिए होगा।
एक फंक्शन VoteCount बनाइए जो एक काउंटर (*int) को आर्गुमेंट के रूप में लेता है और काउंटर में मौजूद वोटों की संख्या लौटाता है।
अगर काउंटर nil है, तो मान लीजिए कि उस काउंटर में कोई वोट नहीं है:
var votes int
votes = 3
var voteCounter *int
voteCounter = &votes
VoteCount(voteCounter)
// => 3
var nilVoteCounter *int
VoteCount(nilVoteCounter)
// => 0
अब वोट गिनने का समय आ गया है! अब आपको काउंटर के वोट बढ़ाने का तरीका चाहिए।
एक फंक्शन IncrementVoteCount बनाइए जो एक काउंटर (*int) और वोटों की एक संख्या आर्गुमेंट के रूप में लेता है, और काउंटर को उतने वोटों से बढ़ा देता है।
आप मान सकते हैं कि दिया गया पॉइंटर कभी nil नहीं होगा।
var votes int
votes = 3
var voteCounter *int
voteCounter = &votes
IncrementVoteCount(voteCounter, 2)
votes == 5 // true
*voteCounter == 5 // true
अब जब सारे वोट गिन लिए गए हैं, तो पूरे स्कूल को नतीजे सुनाने की तैयारी का समय आ गया है। इसके लिए आप देखते हैं कि सिर्फ वोटों के काउंटर होना काफी नहीं है। वोटों की संख्या को किसी खास उम्मीदवार से जोड़ने का तरीका होना चाहिए।
एक फंक्शन NewElectionResult बनाइए जो किसी उम्मीदवार का नाम और उसके वोटों की संख्या लेता है और
एक नया चुनाव परिणाम लौटाता है।
var result *ElectionResult
result = NewElectionResult("Peter", 3)
result.Name == "Peter" // true
result.Votes == 3 // true
चुनाव परिणाम का struct आपके लिए पहले से बना दिया गया है और इसे इस तरह परिभाषित किया गया है:
type ElectionResult struct {
// Name of the candidate
Name string
// Votes of votes the candidate had
Votes int
}
अब स्कूल को नए अध्यक्ष की घोषणा करने का समय है!
नए अध्यक्ष की घोषणा स्कूल के छोटे डिजिटल मैसेज बोर्ड पर की जाएगी।
संदेश में नए अध्यक्ष का नाम और उसे मिले वोट इस प्रारूप में दिखने चाहिए: <candidate_name> (<votes>)।
ऐसे संदेश का एक उदाहरण है: "Peter (51)"।
एक फंक्शन DisplayResult बनाइए जो एक *ElectionResult को आर्गुमेंट के रूप में लेता है और दिखाने के लिए संदेश वाली एक स्ट्रिंग लौटाता है।
var result *ElectionResult
result = &ElectionResult{
Name: "John",
Votes: 32,
}
DisplayResult(result)
// => John (32)
यह पक्का करने के लिए कि अंतिम नतीजे सही थे, वोटों की दोबारा गिनती की गई। दोबारा गिनती में पता चला कि कुछ उम्मीदवारों के वोटों की संख्या में एक का अंतर था।
एक फंक्शन DecrementVotesOfCandidate बनाइए जो अंतिम नतीजे और उस उम्मीदवार का नाम लेता है जिसके वोटों की संख्या एक कम करनी है।
अंतिम नतीजे map[string]int के रूप में दिए गए हैं, जिसमें कीज़ उम्मीदवारों के नाम हैं और वैल्यूज़ उनके कुल वोट हैं।
var finalResults = map[string]int{
"Mary": 10,
"John": 51,
}
DecrementVotesOfCandidate(finalResults, "Mary")
finalResults["Mary"]
// => 9
Exercism पर साइन अप कीजिए और Go को 34 कॉन्सेप्ट165 अभ्यास तथा असली इंसानों से मिलने वाली मेंटरिंग के साथ सीखिए और उसमें महारत हासिल कीजिए, वह भी बिल्कुल मुफ्त।