[৫.১] new(), make() দিয়ে মেমরি অ্যালোকেশন : (Memory allocation using new() & make())

[৫.১.১] new()

একটা পরিস্থিতি কল্পনা করি, যেখানে আমরা শুরুতে একটা ভ্যারিয়েবল এর শুধু মাত্র ডাটা টাইপ এবং সাইজ জানি; কিন্তু এটার ভ্যালু কি হবে তা বলতে পারছি না (শুরুতে সবাই nil/zero থাকবে), কিন্তু পরবর্তীতে অ্যাসাইন করে নিবো। এরকম কেইসে আমরা new() ব্যবহার করতে পারি।

new() হলো একটি বিল্ট-ইন ফাংশন, যেটা ব্যবহার করে, যে কোনো ডাটা টাইপ এর ভ্যারিয়েবলের জন্য একটা zero / nil  ভ্যালু যুক্ত এলোক্যাটেড মেমরি পাবো । এর সিনট্যাক্স

				
					   func new(Type) *Type
				
			

এই মেমরি কে অ্যাক্সেস করার জন্য dereferencing ( ভ্যারিয়েবল এর আগে * ব্যবহার) করতে হবে। নিচের কোডটি খেয়াল করি –

				
					 p := new(int) // allocate memory for an int using new() function

   // Before assigning any value
   fmt.Println("Value of p:", *p)
   fmt.Println("Memory address of p:", p)

   *p = 10 // assign a value to the variable pointed by the pointer

   // after assigning value
   fmt.Println("Value of p:", *p)
   fmt.Println("Memory address of p:", p)

				
			

কোডের আউটপুট –

				
					Value of p: 0
   Memory address of p: 0xc0000140f8
   Value of p: 10
   Memory address of p: 0xc0000140f8

				
			

এখন আমরা যদি ডাটা টাইপ হিসেবে অ্যারে ব্যবহার করতাম এবং তাহলে অবশ্যই আমাদের ঐ অ্যারে এর সাইজ  নির্দিষ্ট ভাবে জানা থাকা লাগবে। নিচের কোড টা খেয়াল করি –

এর আউটপুট – 

				
					 newArray := new([5]int)
   fmt.Println("length: ", len(newArray), " capacity: ", cap(newArray))
   newArray[0] = 10 //assigning a value to the first element
   newArray[4] = 20 //assigning a value to the last element
   fmt.Println("newArray: ", newArray)

				
			
				
					   length:  5  capacity:  5
   newArray:  &[10 0 0 0 20]
				
			

খেয়াল করি, অ্যারে হিসেবে ইনিশিয়ালাইজ করা newArray এর এলিমেন্ট অ্যাক্সেস করার জন্য আমাদের derefernce ব্যবহার করতে হচ্ছে না (কারণ কি?) 

১ম এবং শেষ ইনডেক্সে ভ্যালু অ্যাসাইন করে দেয়া হয়েছে, বাকি এলিমেন্ট গুলো 0 ই রয়ে গিয়েছে। 

ডাটা টাইপ হিসেবে Struct ব্যবহার করলে Struct এর সাইজ ও আগে থেকে নির্দিষ্ট করে দেয়া লাগবে – 

				
					 type Vertex struct {
       X int
       Y int
   }
   newStruct := new([5]Vertex)
   fmt.Println("length: ", len(newStruct), " capacity: ", cap(newStruct))
   // accessing the 1st element
   newStruct[0].X = 1
   newStruct[0].Y = 10
   // accessing the last element
   newStruct[4].X = 5
   newStruct[4].Y = 10
   fmt.Println("newStruct: ", newStruct)

				
			

এর আউটপুট – 

				
					  length:  5  capacity:  5
   newStruct:  &[{1 10} {0 0} {0 0} {0 0} {5 10}]
				
			

এখানে Struct এর অ্যারে ব্যবহার করা হয়েছে। অ্যারে ব্যবহার না করে শুধু Struct ও ব্যবহার করা যায় –

				
					 newStruct := new(Vertex)
   // accessing the element
   newStruct.X = 1
   newStruct.Y = 10
   fmt.Println("newStruct: ", newStruct) // output: &{1 10}

				
			

[৫.১.২] make()

এখন আমরা আরেকটা অবস্থা কল্পনা করিঃ 

  • যেখানে আমরা স্লাইস / map / চ্যানেল নিয়ে কাজ করছি।
  • শুরুতে কোনো ভ্যালু দিয়ে এদেরকে ইনিশিয়ালাইজ করে নিবো (নতুবা nil / zero দিয়ে ইনিশিয়ালাইজ হবে)
  • শুরুতে এদের সাইজ, ক্যাপাসিটি কত হবে তা আমরা জানলেও, পরে তা পরিবর্তন হতে পারে (অর্থাৎ এদের সাইজ, ক্যাপাসিটি ডাইনামিক্যালি পরির্তন হবে)

এরকম অবস্থায় আমরা make() ব্যবহার করতে পারি।

 

make() এর সিনট্যাক্স –

				
					   func make(t Type, size ...IntegerType) Type
				
			

লক্ষ্যনীয় বিষয় –  new() এর সাথে make()  এর পার্থক্য হলো, make() মেমরি এড্রেস রিটার্ন না করে, যে ডাটা টাইপ ব্যবহার করছি, তাই রিটার্ন করছে, সাথে ইনিশিয়ালাইজ করা ভ্যালু গুলাও আসছে। তার মানে এখানে ওই ভ্যারিয়েবল এর ভ্যালু অ্যাক্সেস করার জন্য আর dereference করতে হচ্ছে না। 

[৫.১.২.১] make()- স্লাইস

স্লাইস নিয়ে কাজ করলে এর সাইজ এর সাথে অপশনাল ৩য় প্যারামিটার হিসেবে আমরা ক্যাপাসিটি ও শুরুতে ইনিশিয়ালাইজ করে দিতে পারি। তবে অবশ্যই সাইজ, ক্যাপাসিটি ২ টাই ইন্টিজার হবে। 

কোডে  দেখা যাক –

				
					sliceMake := make([]int, 3, 5)
   fmt.Println("size: ", len(sliceMake), "||capacity:", cap(sliceMake))
   //size:3 || capacity: 5
   fmt.Println("full sliceMake:", sliceMake)                           
   //[0 0 0]
   sliceMake[0] = 10                           
   //accessing and assigning the 1st element of the slice
   fmt.Println("1st index value:", sliceMake[0])
   //output: 10
   fmt.Println("3rd index value:", sliceMake[2])
   //output: 0
   fmt.Println("5th index value:", sliceMake[4])
   //error: trying to access out of size index

				
			

এর আউটপুট –

				
					

   size:  3 || capacity:  5
   full sliceMake: [0 0 0]
   1st index value: 10
   3rd index value: 0
   panic: runtime error: index out of range [4] with length 3


 
				
			

এখানে ৩য় এলিমেন্ট টা কিন্তু 0 রয়ে গিয়েছে, কেননা আমরা শুধু মাত্র ১ম এলিমেন্ট টা পরিবর্তন করেছি, বাকি এলিমেন্ট গুলা 0 দিয়ে ইনিশিয়ালাইজড হয়ে ছিলো।

slice এর সাইজ (৩) এর বাইরের(৫ম ইনডেক্স) অ্যাক্সেস করাতে চাওয়ায় error এসেছে।

এই sliceMake এর সাইজ, ক্যাপাসিটি ডাইনামিক্যালি পরিবর্তন করতে পারি –

				
					 sliceMake = append(sliceMake, 1)
   sliceMake = append(sliceMake, 2)
   sliceMake = append(sliceMake, 3)
   fmt.Println("size:", len(sliceMake), "||capacity:", cap(sliceMake))
   //size:6||capacity:10
   fmt.Println("5th index value:", sliceMake[4])
   //trying to access the 5th index value after increment
   fmt.Println("full sliceMake: ", sliceMake)
   //[10 0 0 1 2 3]
				
			

৩ টা element append করার পরে আউটপুট –

				
					 size: 6 ||capacity: 10
   5th index value: 2
   full sliceMake:  [0 0 0 1 2 3]
				
			

এখানে লক্ষ্যনীয়ঃ স্লাইস length যখন আমরা ডাইনামিক্যালি বাড়িয়ে ৬ করলাম, তখন ক্যাপাসিটি ১০

এরকম ভাবে, 

যখন সাইজ = ১১, তখন ক্যাপাসিটি = ২০

যখন সাইজ = ২১, তখন ক্যাপাসিটি = ৪০

যখন সাইজ = ৪১, তখন ক্যাপাসিটি = ৮০

এখানে স্লাইস এর শুরুর ক্যাপাসিটি ছিলো ৫, তখন ক্যাপাসিটি = ৫, ১০, ২০, ৪০, ৮০, ১৬০…এভাবে পরিবর্তন হয়েছে, যদি ৫ না হয়ে ৩ হতো, তাহলে ক্যাপাসিটি = ৩, ৬, ১২, ১৮, ৩৬, …….এই হারে পরিবর্তন হতো।

[৫.১.২.২] make()- map

map এর সাথে সাইজ, ক্যাপাসিটি দেয়া লাগে না, কারণ map শুরুতেই পর্যাপ্ত মেমরি নিজ থেকে বরাদ্দ নিয়ে নেয় এবং পরবর্তীতে শুরুতে বরাদ্দ নেয়া মেমরির বেশি মেমরিও ডাইনামিক্যালি অ্যাক্সেস করতে পারি। নিচের কোডটি খেয়াল করি –

				
					mapMake := make(map[string]int)
   fmt.Println("map size(initially):", len(mapMake)) // output: 0
   mapMake["one"] = 1
   mapMake["two"] = 2
   mapMake["three"] = 3
   fmt.Println("Full map:", mapMake) //output: [one:1 three:3 two:2]

				
			

এর আউটপুট –

				
					map size(initially): 0
   Full map: map[one:1 three:3 two:2]

				
			

এখানে আমরা সাইজ ইনিশিয়ালাইজ করছি না, তাই যত গুলা এলিমেন্ট নিচ্ছি, map এর সাইজ ততই হবে।

তবে আমরা চাইলে সাইজ(অপশনাল) আগে থেকে বলে দিতে পারি –

				
					mapMake := make(map[string]int, 2)
   fmt.Println("map size(before allocating):", len(mapMake)) // output: 0
   // after allocating less number of elements as size of the map
   mapMake["one"] = 1
   fmt.Println("map size(allocation less than size):", len(mapMake)) //output: 1
   mapMake["two"] = 2
   
   // after allocating equal number of elements as size of the map
   fmt.Println("map size(equal number of allocation):", len(mapMake)) //output: 2

   // allocating more than the size of the map
   mapMake["three"] = 3
   fmt.Println("map size(allocating more than size):", len(mapMake)) //output: 3
				
			

এর আউটপুট –

				
					   map size(before allocating): 0
   map size(allocation less than size): 1
   map size(equal number of allocation): 2
   map size(allocating more than size): 3



				
			

এখানে মজার ব্যপার হলো, আমরা শুরুতে সাইজ ইনিশিয়ালাইজ করে দিলেও কোনো এলিমেন্ট অ্যাসাইন না করায়, এর সাইজ 0 ই রয়ে গেছে এবং পরবর্তীতে যতগুলা এলিমেন্ট অ্যাসাইন করছি map এর সাইজ ততই হচ্ছে।

[৫.১.২.৩] make() – চ্যানেল 

make() দিয়ে চ্যানেল declaration এর সময় আমরা ২য় প্যারামিটার(অপশনাল) হিসেবে চ্যানেল এর buffer_size ফিক্সড করে দিতে পারি, আর যদি না দেই অথবা 0 বসাই, তাহলে সেটাকে unbuffered চ্যানেল বলা হয়।

make() দিয়ে চ্যানেল declaration এর সিনট্যাক্স –

				
					 ch := make(chan data_type, buffer_size)

				
			

উদাহরণ – 

				
					ch := make(chan int, 10)