অন্য অনেক ভাষার মতোই 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-এর পয়েন্টার" (*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
স্লাইস ও ম্যাপ বিশেষ ধরনের টাইপ, কারণ তাদের বাস্তবায়নে আগে থেকেই পয়েন্টার থাকে। এর মানে হলো, বেশিরভাগ ক্ষেত্রেই এই টাইপগুলোর মানের মেমরি অ্যাড্রেস শেয়ার করার জন্য আমাদের পয়েন্টার তৈরি করার দরকার হয় না। ধরা যাক আমাদের এমন একটি ফাংশন আছে যা একটি ম্যাপের একটি কী (key)-এর মান এক বাড়ায়:
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' থেকে আরও পড়তে পারেন।
আপনার কাছের একটি স্থানীয় স্কুলে খুব সক্রিয় একটি শিক্ষার্থী সংগঠন আছে। শিক্ষার্থী সংগঠনটি একজন সভাপতি পরিচালনা করেন এবং প্রতি ২ বছরে একবার নতুন সভাপতি নির্বাচনের জন্য নির্বাচন অনুষ্ঠিত হয়।
এ বছরের নির্বাচনে সিদ্ধান্ত নেওয়া হয়েছিল যে ভোট গণনার জন্য একটি নতুন ডিজিটাল সিস্টেম প্রয়োজন। এই নতুন সিস্টেমটি তৈরিতে স্কুলের আপনার সাহায্য দরকার।
নতুন ভোটিং সিস্টেমের প্রথম যে জিনিসগুলো দরকার তার মধ্যে একটি হলো ভোট কাউন্টার। এই কাউন্টার একটি নির্দিষ্ট প্রার্থীর প্রাপ্ত ভোটের হিসাব রাখার একটি উপায়।
একটি ফাংশন 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
নির্বাচন ফলাফলের স্ট্রাক্টটি ইতিমধ্যেই আপনার জন্য তৈরি করা হয়েছে এবং এটি এভাবে সংজ্ঞায়িত:
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 আকারে দেওয়া হয়, যেখানে কী (key) হলো প্রার্থীদের নাম এবং মান হলো তাঁর মোট ভোট।
var finalResults = map[string]int{
"Mary": 10,
"John": 51,
}
DecrementVotesOfCandidate(finalResults, "Mary")
finalResults["Mary"]
// => 9
Exercism-এ সাইন আপ করুন, Go ট্র্যাকের 34টি কনসেপ্ট165টি অনুশীলনী আর সত্যিকারের মানুষের মেন্টরিং দিয়ে শিখুন ও দক্ষ হয়ে উঠুন, সম্পূর্ণ বিনামূল্যে।