الـ Pointers كقيم في الـ Maps
جزء من قسم المنطق وتدفق التنفيذ في رحلة GO على Coddy. الدرس 27 من 68.
رغم أن تخزين البُنى مباشرةً في الخرائط مفيد، فإن تخزين مؤشرات إلى البُنى يتيح قدرة قوية: إمكانية تعديل بيانات البنية الأصلية مباشرةً من خلال الخريطة. ويُعد هذا الأسلوب ضروريًا عندما تحتاج إلى تحديث بياناتك بعد تخزينها.
عندما تخزّن مؤشرًا إلى بنية كقيمة في map، فأنت تخزّن عنوان الذاكرة الخاص بالبنية، وليس نسخةً منها. إليك كيفية التصريح عن مثل هذا map والعمل معه:
type Product struct {
Price float64
Quantity int
}
products := map[string]*Product{
"laptop": &Product{Price: 999.99, Quantity: 5},
"mouse": &Product{Price: 25.50, Quantity: 20},
}تتضح الميزة الأساسية عندما تحتاج إلى تعديل البيانات. نظرًا لأنك تعمل باستخدام المؤشرات، فإن التغييرات التي تُجرى من خلال map تؤثر في البنية الأصلية:
// حدّث سعر الـ laptop مباشرة من خلال الـ map
products["laptop"].Price = 899.99
fmt.Println(products["laptop"].Price) // يطبع: 899.99هذا النمط ذو قيمة خاصة في التطبيقات التي تحتاج فيها إلى تحديث البيانات المخزنة بشكل متكرر، مثل أنظمة inventory وملفات تعريف المستخدمين أو أي سيناريو تعمل فيه الخريطة كمخزن البيانات الأساسي لديك وتحتاج التعديلات إلى الاستمرار.
التحدي
سهلأنشئ نظامًا بسيطًا لإدارة Inventory يوضّح قوة استخدام المؤشرات إلى البُنى كقيم في map. يوضّح هذا التحدّي كيف تتيح الخرائط المعتمدة على المؤشرات تعديل البيانات المخزّنة مباشرةً، مما يجعل نظام Inventory فعّالًا وسريع الاستجابة للتحديثات.
ستتلقى ثلاثة مُدخلات:
- سلسلة تمثّل عدد المنتجات (مثلًا،
"4") - سلسلة تحتوي على بيانات المنتجات بالتنسيق:
"name1:price1:quantity1,name2:price2:quantity2,name3:price3:quantity3"(مثلًا،"laptop:999.99:5,mouse:25.50:20,keyboard:75.00:15,monitor:299.99:8") - سلسلة تحتوي على عمليات التحديث بالتنسيق:
"operation1:product1:value1,operation2:product2:value2"حيث تكون العمليات إما"price"أو"quantity"(مثلًا،"price:laptop:899.99,quantity:mouse:25,price:keyboard:65.00")
مهمتك هي:
- تعريف بُنية
Productبالحقول التالية:Price(float64) - سعر المنتجQuantity(int) - كمية المنتج المتوفرة في المخزون
- إنشاء map تكون مفاتيحها أسماء المنتجات (سلاسل نصية)، وقيمها مؤشرات إلى بُنى
Product - تحليل بيانات المنتجات من المُدخل الثاني:
- قسّم المُدخل باستخدام الفواصل للحصول على كل product entries على حدة
- لكل entry، قسّمه باستخدام النقطتين لفصل الاسم والسعر والكمية
- حوّل سلسلة السعر إلى float64 وسلسلة الكمية إلى عدد صحيح
- أنشئ بُنية
Productوخزّن مؤشرًا إليها في map
- احتفظ بأسماء المنتجات في slice منفصلة أثناء التحليل، بالترتيب الذي تظهر به في المُدخل. يؤدي التكرار على map في Go إلى زيارة المفاتيح بترتيب عشوائي، ولذلك فإن slice هي التي تحافظ على ظهور كل عنصر بالترتيب الموجود في المُدخل.
- اعرض Inventory الأولي مع العنوان
"Initial Inventory:" - اعرض كل منتج بالتنسيق:
"[name]: $[price] (Stock: [quantity])" - حلّل عمليات التحديث من المُدخل الثالث وطبّقها:
- قسّم المُدخل باستخدام الفواصل للحصول على كل operation على حدة
- لكل operation، قسّمه باستخدام النقطتين للحصول على نوع العملية واسم المنتج والقيمة الجديدة
- حدّث field المناسب مباشرةً من خلال مؤشر map
- اطبع كل تحديث بالتنسيق:
"Updated [product]: [field] changed to [new_value]"
- اعرض Inventory المحدّث مع العنوان
"Updated Inventory:" - اعرض كل منتج مرة أخرى بالتنسيق نفسه المستخدم في العرض الأولي
- احسب واعرض القيمة الإجمالية لـ Inventory:
"Total Inventory Value: $[total_value]"
استخدم حزمة strconv لتحويل سلاسل السعر والكمية إلى أنواعهما الرقمية المناسبة. نسّق جميع الأسعار إلى منزلتين عشريتين عند عرضها. يوضّح هذا التحدّي كيف يتيح تخزين المؤشرات في map تعديل بيانات البُنية الأصلية مباشرةً، مما يجعل تحديثات Inventory فورية ودائمة دون الحاجة إلى إعادة إسناد القيم إلى map.
جرّب بنفسك
package main
import (
"fmt"
"strconv"
"strings"
)
func main() {
// قراءة المدخلات
var numProductsStr string
var productDataStr string
var operationsStr string
fmt.Scanln(&numProductsStr)
fmt.Scanln(&productDataStr)
fmt.Scanln(&operationsStr)
// TODO: عرّف هيكل Product هنا
// TODO: أنشئ map لتخزين مؤشرات المنتجات
// TODO: أنشئ slice للحفاظ على ترتيب المنتجات (لضمان مخرجات متسقة)
// TODO: حلّل بيانات المنتجات واملأ الـ map
// تذكّر أن:
// - قسّم productDataStr بالفواصل للحصول على إدخالات فردية
// - لكل إدخال، قسّم بالنقطتين الرأسيتين للحصول على name و price و quantity
// - حوّل سلاسل price و quantity إلى الأنواع المناسبة
// - خزّن مؤشراً إلى هيكل Product في الـ map
// - أضف اسم المنتج إلى order slice
// TODO: Display initial inventory
// استخدم order slice للتكرار عبر المنتجات بشكل متسق
// الصيغة: "[name]: $[price] (Stock: [quantity])"
// TODO: حلّل وطبّق عمليات التحديث
// تذكّر أن:
// - قسّم operationsStr بالفواصل للحصول على العمليات الفردية
// - لكل عملية، قسّم بالنقطتين للحصول على type و name و value
// - حدّث الحقل المناسب مباشرة عبر مؤشر الـ map
// - اطبع رسالة التحديث لكل عملية
// TODO: Display updated inventory
// مرة أخرى، استخدم شريحة order لمخرجات متسقة
// احسب القيمة الإجمالية أثناء العرض
// TODO: Calculate and display total inventory value
// Format: "Total Inventory Value: $[total_value]"
}يتضمن هذا الدرس اختبارًا قصيرًا. ابدأ الدرس للإجابة عليه وتتبّع تقدمك.
جميع دروس المنطق وتدفق التنفيذ
1تدفق التحكم المتقدم
جملة Switch مع `fallthrough`الخروج من الحلقات المتداخلةالاستمرار في حلقة محددةجملة `goto`ملخص - التحكم المتقدم في الحلقات2الـ Structs والميثودز (Methods)
تعريف الـ Methods على الـ Structsالـ Value Receiversالـ Pointer Receiversاختيار الـ Receiversالـ Methods مقابل الـ Functionsملخص - سلوك الـ Struct5تعمق في الـ Maps
Maps من نوع Structsالـ Pointers كقيم في الـ Mapsالتحقق من الـ Nil Mapsمقارنة الـ Mapsمراجعة - عداد تكرار الكلمات3الواجهات (الأساسيات)
ما هي الواجهة؟تعريف الواجهةتطبيق الواجهةاستخدام أنواع الواجهاتالواجهة الفارغةتأكيدات النوع (Type Assertions)تبديل النوع (Type Switch)ملخص - الأشكال والسلوكياتتدرّب بنفسك: مترجم Go عبر الإنترنت