التراكيب

التراكيب

تخيل أنك تملأ استمارة معلومات شخصية: الاسم، العمر، رقم الهاتف، العنوان — هذه المعلومات مترابطة ببعضها وتصف شخصًا معًا. في Go، الـ struct هو تلك "الاستمارة": يجمع عدة حقول من أنواع مختلفة في نوع مخصص مركب. مقارنة بالخرائط، يتم تحديد حقول التراكيب وقت الترجمة مما يجعلها آمنة نوعيًا وواضحًا دلاليًا. التراكيب هي الطريقة الأساسية لتنظيم البيانات في Go.


1. المفاهيم الأساسية

المفهوم الوصف
التعريف type StructName struct { ... }، اسم الحقل يأتي قبل النوع
التهيئة حرفي S{f1: v1, f2: v2}، new(S) يُرجع مؤشرًا، &S{}
الوصول إلى الحقول كتابة النقطة s.Field، المؤشرات تستخدم أيضًا p.Field (لا حاجة لـ ->)
نوع القيمة التراكيب أنواع قيم؛ التعيين ووسيطات الدوال تنسخ التراكيب بالكامل
مستلم المؤشر احصل على مؤشر عبر &s؛ تعديل التراكيب عبر المؤشر يؤثر على القيمة الأصلية
الحقول المجهولة اكتب النوع فقط بدون اسم حقل؛ يصبح اسم النوع هو اسم الحقل مما يتيح "التركيب"
التراكيب المتداخلة يمكن لحقول التراكيب أن تكون تراكيب بنفسها، وتدعم التداخل متعدد المستويات
علامات التراكيب بيانات وصفية محاطة بعلامات اقتباس عكسية، تُستخدم عادةً لتعيين JSON/قاعدة البيانات
⚠️ ملاحظة: التراكيب أنواع قيم؛ تمريرها كوسيطات ينشئ نسخة. لتعديل القيمة الأصلية، مرر مؤشرًا *S.


2. الصيغة الأساسية/الاستخدام

تعريف تراكيب

GO
// تعريف تراكيب Person
type Person struct {
    Name string
    Age  int
    City string
}
💡 نصيحة: في Go، إذا كان اسم حقل التراكيب يبدأ بحرف كبيرة، فهو مُصدَّر (عام)؛ إذا بدأ بحرف صغير، فهو غير مُصدَّر (خاص) ويكون مرئيًا فقط داخل الحزمة.

طرق التهيئة

GO
// الطريقة 1: اسم الحقل: القيمة (موصى بها، سهولة القراءة، الترتيب لا يهم)
p1 := Person{Name: "Alice", Age: 25, City: "Beijing"}

// الطريقة 2: التعيين حسب الترتيب (غير موصى بها للعديد من الحقول، عرضة للأخطاء)
p2 := Person{"Bob", 30, "Shanghai"}

// الطريقة 3: الإعلان أولاً ثم التعيين
var p3 Person
p3.Name = "Charlie"
p3.Age = 28
p3.City = "Guangzhou"

// الطريقة 4: الإنشاء باستخدام new (يُرجع مؤشرًا)
p4 := new(Person)  // *Person
p4.Name = "Diana"

// الطريقة 5: أخذ العنوان (يُرجع مؤشرًا)
p5 := &Person{Name: "Eve", Age: 22, City: "Shenzhen"}
💡 نصيحة: التراكيب المعلنة بـ var p Person تُهيّئ جميع الحقول إلى القيمة الصفرية لأنواعها (سلسلة ← ""، عدد صحيح ← 0، منطقي ← false).

الوصول إلى الحقول وتعديلها

GO
p := Person{Name: "Alice", Age: 25}

// قراءة حقل
fmt.Println(p.Name)  // Alice

// تعديل حقل
p.Age = 26
fmt.Println(p.Age)   // 26
💡 نصيحة: صيغة الوصول إلى الحقول عبر المؤشرات والقيم متطابقة — Go يحلل المؤشر تلقائيًا، لذا لا حاجة لـ p->Name.

مؤشرات التراكيب

GO
p := &Person{Name: "Alice", Age: 25}

// Go يحلل المؤشر تلقائيًا، استخدم كتابة النقطة مباشرة
fmt.Println(p.Name)  // Alice

// تعديل القيمة الأصلية عبر المؤشر
p.Age = 30
💡 نصيحة: ليس في Go عامل ->. سواء كان p قيمة أو مؤشرًا، تستخدم دائمًا p.Field للوصول إلى الحقول.


3. أمثلة الكود

مثال 1: الاستخدام الأساسي (الصعوبة ⭐)

تعريف تراكيب Book وإنشاء كائنات وطباعة المعلومات.

GO
package main

import "fmt"

// Book يُعرّف تراكيب كتاب
type Book struct {
    Title  string
    Author string
    Pages  int
    Price  float64
}

func main() {
    // الإنشاء باستخدام الكتابة الحرفية
    book1 := Book{
        Title:  "The Go Programming Language",
        Author: "Alan Donovan",
        Pages:  350,
        Price:  59.9,
    }

    // طباعة التراكيب
    fmt.Println(book1)

    // الوصول إلى حقول مفردة
    fmt.Printf("Title: %s\n", book1.Title)
    fmt.Printf("Author: %s\n", book1.Author)
    fmt.Printf("Price: %.1f\n", book1.Price)

    // تعديل حقل
    book1.Price = 49.9
    fmt.Printf("New price: %.1f\n", book1.Price)

    // الإنشاء حسب الترتيب (غير موصى به)
    book2 := Book{"Python Crash Course", "Eric Matthes", 280, 45.0}
    fmt.Println(book2)
}
▶ جرّب الكود

الناتج:

TEXT
{The Go Programming Language Alan Donovan 350 59.9}
Title: The Go Programming Language
Author: Alan Donovan
Price: 59.9
New price: 49.9
{Python Crash Course Eric Matthes 280 45}

مثال 2: الاستخدام المتقدم (الصعوبة ⭐⭐)

استخدام المؤشرات والتراكيب المتداخلة والحقول المجهولة.

GO
package main

import "fmt"

// تراكيب العنوان
type Address struct {
    City    string
    Street  string
    ZipCode string
}

// تراكيب الموظف (يستخدم الحقل المجهول Address)
type Employee struct {
    Name    string
    Age     int
    Address        // حقل مجهول: اسم النوع هو اسم الحقل
    Salary  float64
}

func main() {
    // ========== تهيئة التراكيب المتداخلة ==========
    emp := Employee{
        Name:   "Alice",
        Age:    30,
        Salary: 15000,
        Address: Address{
            City:    "Beijing",
            Street:  "1 Zhongguancun Street",
            ZipCode: "100080",
        },
    }

    fmt.Printf("Employee: %s\n", emp.Name)
    fmt.Printf("City: %s\n", emp.Address.City)  // وصول صريح

    // ========== الوصول "الترقية" للحقول المجهولة ==========
    // يمكن الوصول إلى الحقول المجهولة مباشرة باسم النوع
    fmt.Printf("Street: %s\n", emp.Street)   // نفس emp.Address.Street
    fmt.Printf("ZipCode: %s\n", emp.ZipCode) // نفس emp.Address.ZipCode

    // ========== عمليات المؤشرات ==========
    empPtr := &emp

    // التعديل عبر المؤشر (Go يحلل المؤشر تلقائيًا)
    empPtr.Age = 31
    empPtr.Salary = 18000
    empPtr.City = "Shanghai" // تعديل الحقل المجهول عبر المؤشر

    fmt.Printf("\nAfter modification: %+v\n", *empPtr)

    // ========== نوع القيمة مقابل نوع المؤشر ==========
    fmt.Println("\n--- Value type vs Pointer type ---")

    // نوع القيمة: التعيين هو النسخ
    emp2 := emp
    emp2.Name = "Bob"
    fmt.Printf("emp.Name:  %s\n", emp.Name)  // لا يزال Alice
    fmt.Printf("emp2.Name: %s\n", emp2.Name) // Bob

    // نوع المؤشر: التعيين هو المشاركة
    emp3 := empPtr
    emp3.Name = "Charlie"
    fmt.Printf("empPtr.Name: %s\n", empPtr.Name) // Charlie (تم التعديل)
}
▶ جرّب الكود

الناتج:

TEXT
Employee: Alice
City: Beijing
Street: 1 Zhongguancun Street
ZipCode: 100080

After modification: {Name:Alice Age:31 Address:{City:Shanghai Street:1 Zhongguancun Street ZipCode:100080} Salary:18000}

--- Value type vs Pointer type ---
emp.Name:  Alice
emp2.Name: Bob
empPtr.Name: Charlie

مثال 3: التطبيق الشامل (الصعوبة ⭐⭐⭐)

استخدام علامات التراكيب لسلسلة/استرجاع JSON، وبناء نظام إدارة طلاب.

GO
package main

import (
    "encoding/json"
    "fmt"
    "sort"
    "strings"
)

// Student تراكيب الطالب مع علامات JSON
type Student struct {
    ID     int      `json:"id"`
    Name   string   `json:"name"`
    Age    int      `json:"age"`
    Score  float64  `json:"score"`
    Tags   []string `json:"tags,omitempty"` // omitempty: يُحذف عندما يكون فارغًا
    secret string   // حقل غير مُصدَّر، غير قابل للوصول عبر JSON
}

// ClassRoom (تراكيب متداخل)
type ClassRoom struct {
    ClassName string    `json:"class_name"`
    Students  []Student `json:"students"`
}

// AddStudent يضيف طالبًا
func (c *ClassRoom) AddStudent(s Student) {
    c.Students = append(c.Students, s)
}

// GetTopN يحصل على أفضل N طلاب حسب الدرجة
func (c *ClassRoom) GetTopN(n int) []Student {
    // عمل نسخة لتجنب تعديل البيانات الأصلية
    sorted := make([]Student, len(c.Students))
    copy(sorted, c.Students)

    // الترتيب حسب الدرجة تنازليًا
    sort.Slice(sorted, func(i, j int) bool {
        return sorted[i].Score > sorted[j].Score
    })

    if n > len(sorted) {
        n = len(sorted)
    }
    return sorted[:n]
}

// Average يحسب متوسط الدرجات
func (c *ClassRoom) Average() float64 {
    if len(c.Students) == 0 {
        return 0
    }
    total := 0.0
    for _, s := range c.Students {
        total += s.Score
    }
    return total / float64(len(c.Students))
}

// SearchByName يبحث بالاسم (مطابقة ضبابية)
func (c *ClassRoom) SearchByName(keyword string) []Student {
    var result []Student
    for _, s := range c.Students {
        if strings.Contains(s.Name, keyword) {
            result = append(result, s)
        }
    }
    return result
}

func main() {
    // إنشاء فصل دراسي
    class := &ClassRoom{ClassName: "Class 1, Grade 2024"}

    // إضافة طلاب
    students := []Student{
        {ID: 1, Name: "Zhang San", Age: 18, Score: 92.5, Tags: []string{"good at math", "class monitor"}},
        {ID: 2, Name: "Li Si", Age: 19, Score: 88.0, Tags: []string{"good at sports"}},
        {ID: 3, Name: "Wang Wu", Age: 18, Score: 95.5},
        {ID: 4, Name: "Zhao Liu", Age: 20, Score: 78.0, Tags: []string{"art talent"}},
        {ID: 5, Name: "Zhang Wei", Age: 19, Score: 91.0, Tags: []string{"good at math", "competition winner"}},
    }

    for _, s := range students {
        class.AddStudent(s)
    }

    // ========== سلسلة JSON ==========
    fmt.Println("=== JSON Serialization ===")
    jsonData, err := json.MarshalIndent(class, "", "  ")
    if err != nil {
        fmt.Println("Serialization failed:", err)
    } else {
        fmt.Println(string(jsonData))
    }

    // ========== استرجاع JSON ==========
    fmt.Println("\n=== JSON Deserialization ===")
    jsonStr := `{"id":6,"name":"Sun Qi","age":17,"score":97.0,"tags":["good at English"]}`
    var newStudent Student
    if err := json.Unmarshal([]byte(jsonStr), &newStudent); err != nil {
        fmt.Println("Deserialization failed:", err)
    } else {
        fmt.Printf("New student: %+v\n", newStudent)
    }

    // ========== عرض الميزات ==========
    fmt.Println("\n=== Class Info ===")
    fmt.Printf("Class: %s, total %d students\n", class.ClassName, len(class.Students))
    fmt.Printf("Average score: %.1f\n", class.Average())

    fmt.Println("\n=== Top 3 Scores ===")
    top3 := class.GetTopN(3)
    for i, s := range top3 {
        fmt.Printf("  #%d: %s (%.1f)\n", i+1, s.Name, s.Score)
    }

    fmt.Println("\n=== Search 'Zhang' ===")
    results := class.SearchByName("Zhang")
    for _, s := range results {
        fmt.Printf("  %s (ID: %d, Score: %.1f)\n", s.Name, s.ID, s.Score)
    }
}
▶ جرّب الكود

الناتج:

TEXT
=== JSON Serialization ===
{
  "class_name": "Class 1, Grade 2024",
  "students": [
    {
      "id": 1,
      "name": "Zhang San",
      "age": 18,
      "score": 92.5,
      "tags": [
        "good at math",
        "class monitor"
      ]
    },
    {
      "id": 2,
      "name": "Li Si",
      "age": 19,
      "score": 88
    },
    {
      "id": 3,
      "name": "Wang Wu",
      "age": 18,
      "score": 95.5
    },
    {
      "id": 4,
      "name": "Zhao Liu",
      "age": 20,
      "score": 78,
      "tags": [
        "art talent"
      ]
    },
    {
      "id": 5,
      "name": "Zhang Wei",
      "age": 19,
      "score": 91,
      "tags": [
        "good at math",
        "competition winner"
      ]
    }
  ]
}

=== JSON Deserialization ===
New student: {ID:6 Name:Sun Qi Age:17 Score:97 Tags:[good at English]}

=== Class Info ===
Class: Class 1, Grade 2024, total 5 students
Average score: 89.0

=== Top 3 Scores ===
  #1: Wang Wu (95.5)
  #2: Zhang San (92.5)
  #3: Zhang Wei (91.0)

=== Search 'Zhang' ===
  Zhang San (ID: 1, Score: 92.5)
  Zhang Wei (ID: 5, Score: 91.0)

3. سيناريوهات التطبيق الشائعة

السيناريو 1: تفاعل بيانات JSON

في تطوير الويب، التراكيب هي الطريقة المعيارية للتعامل مع طلبات/استجابات JSON. العلامات التحكم في أسماء حقول JSON.

GO
package main

import (
    "encoding/json"
    "fmt"
)

// APIResponse هيكل استجابة API موحد
type APIResponse struct {
    Code    int         `json:"code"`
    Message string      `json:"message"`
    Data    interface{} `json:"data,omitempty"`
}

// UserDTO كائن نقل بيانات المستخدم
type UserDTO struct {
    ID       int    `json:"id"`
    Username string `json:"username"`
    Email    string `json:"email"`
    Password string `json:"-"` // "-" يعني تجاهل هذا الحقل في JSON
}

func main() {
    // بناء استجابة
    user := UserDTO{
        ID:       1,
        Username: "alice",
        Email:    "alice@example.com",
        Password: "secret123", // لن يُسلسل
    }

    resp := APIResponse{
        Code:    200,
        Message: "success",
        Data:    user,
    }

    // السلسلة
    jsonBytes, _ := json.MarshalIndent(resp, "", "  ")
    fmt.Println(string(jsonBytes))

    // الاسترجاع
    jsonStr := `{"code":404,"message":"User not found"}`
    var errResp APIResponse
    json.Unmarshal([]byte(jsonStr), &errResp)
    fmt.Printf("\nError code: %d, Message: %s\n", errResp.Code, errResp.Message)
}

الناتج:

TEXT
{
  "code": 200,
  "message": "success",
  "data": {
    "id": 1,
    "username": "alice",
    "email": "alice@example.com"
  }
}

Error code: 404, Message: User not found

السيناريو 2: محاكاة الكائنية (التركيب بدل الوراثة)

ليس في Go فئات أو وراثة؛ يتم تحقيق إعادة استخدام الكود عبر تداخل التراكيب (التركيب).

GO
package main

import "fmt"

// Logger قدرة التسجيل
type Logger struct {
    Prefix string
}

// Log يُخرج رسالة سجل
func (l *Logger) Log(msg string) {
    fmt.Printf("[%s] %s\n", l.Prefix, msg)
}

// Cache قدرة التخزين المؤقت
type Cache struct {
    data map[string]string
}

// Get يسترجع من التخزين المؤقت
func (c *Cache) Get(key string) (string, bool) {
    val, ok := c.data[key]
    return val, ok
}

// Set يُعيّن إدخال تخزين مؤقت
func (c *Cache) Set(key, value string) {
    if c.data == nil {
        c.data = make(map[string]string)
    }
    c.data[key] = value
}

// UserService يعيد استخدام قدرتي Logger و Cache عبر التركيب
type UserService struct {
    Logger       // تضمين Logger
    Cache        // تضمين Cache
    Name string
}

func main() {
    svc := UserService{
        Logger: Logger{Prefix: "UserService"},
        Name:   "User Service",
    }

    // استدعاء الأساليب المضمنة مباشرة
    svc.Log("Service started")              // من Logger
    svc.Set("user:1", "Alice")              // من Cache
    svc.Log("Cache initialized")

    if val, ok := svc.Get("user:1"); ok {
        svc.Log(fmt.Sprintf("Found user: %s", val))
    }
}

الناتج:

TEXT
[UserService] Service started
[UserService] Cache initialized
[UserService] Found user: Alice

❓ أسئلة شائعة

س1: هل التراكيب نوع قيمة أم نوع مرجع؟

التراكيب أنواع قيم. التعيين ووسيطات الدوال تنسخ التراكيب بالكامل. لتعديل القيمة الأصلية، مرر مؤشرًا:

GO
func updateAge(p *Person, age int) {
    p.Age = age  // يُعدّل القيمة الأصلية مباشرة
}

p := Person{Name: "Alice", Age: 25}
updateAge(&p, 30)
fmt.Println(p.Age)  // 30

س2: ما فائدة الحقول المجهولة؟ وكيف تختلف عن التراكيب المتداخلة؟

الحقول المجهولة تُحقق "التركيب". أساليب وحقول النوع الداخلي "تُرقى" إلى النوع الخارجي:

GO
type Address struct {
    City string
}

type Person struct {
    Name string
    Address        // حقل مجهول
}

p := Person{Name: "Alice", Address: Address{City: "Beijing"}}
fmt.Println(p.City)         // وصول مباشر، نفس p.Address.City
fmt.Println(p.Address.City) // يعمل أيضًا بالوصول الصريح

إذا كان للتراكيب المتداخلة حقول بنفس الاسم، تحتاج لاستخدام المسار الكامل للتمييز.

س3: ما هي علامات التراكيب؟ كيف أستخدمها؟

العلامات هي بيانات وصفية محاطة بعلامات اقتباس عكسية، تُستخدم عادةً للتحكم في سلوك JSON و ORM قواعد البيانات والمكتبات الأخرى:

GO
type User struct {
    ID       int    `json:"id"              db:"user_id"`
    Name     string `json:"name"            db:"user_name"`
    Password string `json:"-"               db:"password"`    // يُتجاهل في JSON
    Email    string `json:"email,omitempty"  db:"email"`       // يُحذف في JSON عندما يكون فارغًا
}

العلامات الشائعة:

س4: هل يمكن مقارنة التراكيب؟

يعتمد على أنواع الحقول. إذا كانت جميع الحقول أنواع قابلة للمقارنة (int، string، bool، مصفوفات، إلخ)، يمكن مقارنة التراكيب بـ == و !=. إذا كانت تحتوي على حقول غير قابلة للمقارنة مثل الشرائح أو الخرائط أو الدوال، لا يمكن مقارنتها مباشرة:

GO
type Point struct { X, Y int }

p1 := Point{1, 2}
p2 := Point{1, 2}
fmt.Println(p1 == p2)  // true

// التراكيب التي تحتوي شرائح لا يمكن استخدام ==
type Data struct { Items []int }
// d1 == d2  // خطأ ترجمة!

📖 ملخص


📝 تمارين

تمرين 1 (⭐)

عرّف تراكيب Rectangle بحقولي Width و Height. نفّذ ما يلي:

  1. أنشئ كائن Rectangle واطبعه
  2. اكتب دالة Area() لحساب المساحة
  3. اكتب دالة Perimeter() لحساب المحيط
  4. اكتب دالة IsSquare() للتحقق مما إذا كان مربعًا

تمرين 2 (⭐⭐)

عرّف تراكيب BankAccount بحقلي Owner (سلسلة) و Float64 (float64). نفّذ ما يلي:

  1. Deposit(amount) للإيداع
  2. Withdraw(amount) للسحب (أبلغ عند عدم كفاية الرصيد)
  3. Transfer(other *BankAccount, amount) للتحويل
  4. استخدم علامات JSON لسلسلة معلومات الحساب (أخفِ الرصيد باستخدام json:"-")

تمرين 3 (⭐⭐⭐)

نفّذ نظام إدارة مخزون المنتجات:

  1. عرّف تراكيب Product (المعرّف، الاسم، السعر، المخزون، العلامات)
  2. عرّف تراكيب Inventory (اسم المتجر، شريحة المنتجات)
  3. نفّذ الأساليب: AddProduct، RemoveProduct(id)، FindByTag(tag)، TotalValue() (إجمالي قيمة المخزون)
  4. نفّذ سلسلة JSON مع المتطلبات: السعر إلى خانتين عشريتين، حذف المخزون عند 0
  5. أنشئ كائنات، أضف منتجات، ابحث بالعلامة، احسب القيمة الكلية، وأخرج كـ JSON

الدرس التالي

👉 08-methods - الأساليب

Web-Tutorial.com

فريق Web-Tutorial التقني

منصة دروس برمجية يديرها عدة مطورين. كل درس يتم كتابته ومراجعته بواسطة مطورين متخصصين في المجال. نعمل على ضمان دقة وموثوقية المحتوى — إذا لاحظت أي مشكلة، فيرجى إخبارنا.

100%