البرمجة

التعامل مع السلاسل في جو

جدول المحتوى

التعامل مع السلاسل في لغة جو (Go)

تُعد لغة جو (Go) واحدة من اللغات البرمجية الحديثة التي تجمع بين الأداء العالي والبساطة في الكتابة، وتتميز بدعمها القوي للعمل مع النصوص والسلاسل النصية (Strings). تعتبر السلاسل النصية من أهم أنواع البيانات في أي لغة برمجة، حيث تُستخدم لتخزين ومعالجة النصوص، وتُعتبر في جو نوعًا أساسيًا ذو خصائص فريدة تتطلب فهمًا دقيقًا لكيفية التعامل معها بكفاءة وفعالية.

في هذا المقال، سنتناول السلاسل النصية في لغة جو بشكل مفصل، بداية من طبيعة هذا النوع في جو، مرورا بكيفية التعامل معها، والعمليات الأساسية التي يمكن تنفيذها عليها، مرورًا بمواضيع متقدمة مثل الترميز، والتقطيع، والكتابة، والأداء، وحتى التعامل مع السلاسل متعددة البايتات أو الرموز.


طبيعة السلاسل النصية في لغة جو

في جو، تُعرف السلاسل النصية (Strings) بأنها تسلسلات ثابتة الطول من البايتات، وكل سلسلة نصية هي نوع بيانات مُدمج (built-in type). تعني كلمة “ثابتة الطول” هنا أن السلسلة النصية لا يمكن تعديلها بعد إنشائها (immutable). إذا كنت بحاجة لتعديل محتويات السلسلة، عليك إنشاء نسخة جديدة مع التعديلات المطلوبة.

go
var s string = "مرحبا بالعالم"

تمثل السلسلة النصية في جو مجرد إشارة إلى مصفوفة من البايتات التي تمثل النص، حيث كل عنصر يمثل بايتًا واحدًا من النص المُرمز. وبما أن جو تعتمد على الترميز UTF-8 كسلسلة ترميز افتراضية للنصوص، فإن كل حرف أو رمز قد يشغل عددًا متغيرًا من البايتات (بين 1 إلى 4 بايتات).

ثبات السلاسل النصية (Immutability)

ثبات السلاسل يعني أنه لا يمكن تعديل محتويات السلسلة بعد إنشائها، وهذا يجعلها آمنة في بيئات العمل المتعددة (concurrent environments)، كما يقلل من الأخطاء المرتبطة بالتعديل غير المقصود للنص.

لتعديل نص معين، يجب إنشاء سلسلة جديدة:

go
s := "hello" s = s + " world" // سلسلة جديدة يتم إنشاؤها

إنشاء السلاسل النصية في Go

يمكن إنشاء السلاسل النصية بعدة طرق، منها:

  • باستخدام النصوص بين علامتي اقتباس مزدوجة:

go
var s1 string = "هذا نص"
  • باستخدام النصوص الخام (Raw string literals) بين علامتي اقتباس معكوفة `، وهي تسمح بكتابة نصوص تحتوي على أسطر متعددة وأحرف خاصة دون الحاجة للهروب:

go
s2 := `هذا نص يحتوي على أسطر متعددة`

العمليات الأساسية على السلاسل النصية

طول السلسلة النصية

يمكن معرفة عدد البايتات في السلسلة النصية باستخدام الدالة المدمجة len():

go
s := "مرحبا" fmt.Println(len(s)) // عدد البايتات، وليس عدد الأحرف

ملاحظة مهمة: لأن السلسلة النصية تعتمد الترميز UTF-8، فإن طول السلسلة بـ len() يعبر عن عدد البايتات، وليس عدد الأحرف. الأحرف قد تكون متعددة البايتات في حال كانت أحرف عربية أو رموز خاصة.

التكرار على السلسلة النصية

يمكن التكرار على السلسلة باستخدام حلقة for مع معامل range، وهو يدعم قراءة الأحرف (runes) وليس البايتات، وبالتالي يعيد كل رمز Unicode كقيمة منفصلة:

go
for index, char := range s { fmt.Printf("الحرف %c في الموقع %d\n", char, index) }

في هذا المثال، char يمثل رمز Unicode واحد (rune)، وindex هو موقع بداية الرمز بالبايت في السلسلة.

التجزئة (Slicing)

يمكن استخراج جزء من السلسلة النصية باستخدام عملية التجزئة المشابهة للمصفوفات:

go
s := "مرحبا بالعالم" sub := s[0:5] // يأخذ البايتات من 0 إلى 4 (غير شامل 5) fmt.Println(sub)

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


التعامل مع الرموز (Runes) بدل البايتات

ما هو الـ rune؟

في جو، rune هو نوع بيانات يمثل رمز Unicode واحد، وهو نوع بيانات 32-بت (int32) يُستخدم لتمثيل كل حرف أو رمز Unicode.

تحويل السلسلة إلى قائمة من الرمز (runes)

للتعامل مع الأحرف بشكل صحيح خاصة مع النصوص متعددة اللغات، من الأفضل تحويل السلسلة إلى شريحة من الـrunes:

go
s := "مرحبا" r := []rune(s) fmt.Println(len(s)) // عدد البايتات fmt.Println(len(r)) // عدد الأحرف الفعلي

هذا يسمح بالتعامل مع الأحرف بطريقة آمنة عند التعديل أو التكرار أو التجزئة.

التجزئة باستخدام الـrunes

لأخذ جزء من النص بناءً على عدد الأحرف الفعلي وليس البايتات:

go
sub := string(r[0:3]) // أول ثلاثة أحرف fmt.Println(sub)

دمج السلاسل النصية (Concatenation)

يمكن دمج سلاسل نصية بسهولة باستخدام عامل الجمع +:

go
s1 := "مرحبا" s2 := " بالعالم" s := s1 + s2 fmt.Println(s)

لكن في حالات الدمج المتكرر مع كميات كبيرة من النصوص، يفضل استخدام strings.Builder لتقليل استهلاك الذاكرة وتحسين الأداء.


استخدام strings.Builder لتحسين الأداء في الدمج

strings.Builder هو نوع يوفر طريقة فعالة لبناء سلاسل نصية من أجزاء متعددة دون الحاجة لإعادة تخصيص الذاكرة كثيرًا:

go
var builder strings.Builder builder.WriteString("مرحبا") builder.WriteString(" ") builder.WriteString("بالعالم") result := builder.String() fmt.Println(result)

هذه الطريقة أكثر كفاءة خاصة عند التعامل مع نصوص كبيرة أو متكررة.


الدوال الأساسية في مكتبة strings

توفر مكتبة strings في جو مجموعة واسعة من الدوال لمعالجة النصوص، من بينها:

الدالة الوصف
strings.Contains للتحقق مما إذا كانت السلسلة تحتوي على نص معين
strings.HasPrefix للتحقق مما إذا كانت السلسلة تبدأ بنص معين
strings.HasSuffix للتحقق مما إذا كانت السلسلة تنتهي بنص معين
strings.Index لإيجاد أول ظهور لنص معين في السلسلة
strings.Replace لاستبدال نص معين في السلسلة بنص آخر
strings.Split لتقسيم السلسلة إلى شريحة حسب محدد معين
strings.ToUpper لتحويل النص إلى حروف كبيرة
strings.ToLower لتحويل النص إلى حروف صغيرة
strings.TrimSpace لإزالة الفراغات من بداية ونهاية النص

أمثلة عملية:

go
s := "مرحبا بالعالم" fmt.Println(strings.Contains(s, "بالعالم")) // true fmt.Println(strings.HasPrefix(s, "مرح")) // true fmt.Println(strings.ToUpper(s)) // "مرحبا بالعالم" (قد لا يتغير في النص العربي)

التعامل مع الترميز (UTF-8) والبايتات

كما ذكرنا، جو تستخدم الترميز UTF-8 للسلاسل النصية، وهو ترميز متغير الطول لكل رمز Unicode.

  • كل حرف ASCII يشغل بايتًا واحدًا.

  • الأحرف العربية والرموز الخاصة قد تشغل من 2 إلى 4 بايتات.

هذا يجعل التعامل مع الطول أو التجزئة مباشرة باستخدام البايتات أمرًا حساسًا ويتطلب الحذر.


التشفير والتحويل بين السلاسل النصية وأنواع البيانات الأخرى

التحويل بين السلاسل النصية والمصفوفات من البايتات

go
s := "مرحبا" b := []byte(s) // تحويل السلسلة إلى مصفوفة بايتات fmt.Println(b) s2 := string(b) // تحويل مصفوفة البايتات إلى سلسلة نصية fmt.Println(s2)

هذا التحويل مهم عند التعامل مع البيانات الثنائية، مثل الشبكات أو الملفات.

التحويل بين السلاسل النصية وشرائح الـrunes

go
r := []rune(s) // تحويل السلسلة إلى runes s3 := string(r) // تحويل الـrunes إلى سلسلة نصية

التعديلات على السلاسل النصية

نظرًا لأن السلاسل النصية ثابتة (immutable)، لا يمكن تعديلها بشكل مباشر. للتعديل يجب إعادة بناء السلسلة. هناك عدة طرق:

  • استبدال نص معين:

go
s := "مرحبا بالعالم" s = strings.Replace(s, "بالعالم", "بالكون", 1) fmt.Println(s) // مرحبا بالكون
  • تقطيع النص وتجميعه:

go
r := []rune(s) r[0] = 'س' // تعديل أول حرف s = string(r) fmt.Println(s)

تطبيقات عملية متقدمة على السلاسل النصية

إزالة الفراغات من النص

go
s := " مرحبا بالعالم " s = strings.TrimSpace(s) fmt.Printf("'%s'\n", s)

تقسيم النص إلى كلمات

go
s := "مرحبا بالعالم كيف حالك" words := strings.Fields(s) // تقسم النص إلى كلمات حسب الفراغات fmt.Println(words)

الانضمام بين مجموعة كلمات إلى نص واحد

go
joined := strings.Join(words, "-") fmt.Println(joined) // مرحبا-بالعالم-كيف-حالك

الأداء والاعتبارات الخاصة بالسلاسل النصية في Go

  • بسبب كون السلاسل النصية ثابتة، فإن عمليات التعديل المتكررة قد تؤدي إلى استهلاك زائد للذاكرة. لذلك، من الأفضل استخدام strings.Builder أو bytes.Buffer عند بناء نصوص كبيرة أو معقدة.

  • التعامل مع الأحرف متعددة البايتات يتطلب الحذر عند استخدام عمليات التجزئة أو التكرار.

  • استخدام الـrunes مهم عند التعامل مع النصوص بلغات متعددة أو رموز خاصة.


أمثلة شاملة

go
package main import ( "fmt" "strings" ) func main() { s := "مرحبا بالعالم" // الطول بالبايتات fmt.Println("طول السلسلة بالبايت:", len(s)) // الطول بالأحرف runes := []rune(s) fmt.Println("طول السلسلة بالأحرف:", len(runes)) // تكرار على الأحرف for i, r := range runes { fmt.Printf("الحرف %c في الموقع %d\n", r, i) } // تعديل حرف في السلسلة runes[0] = 'س' s = string(runes) fmt.Println("بعد التعديل:", s) // استخدام مكتبة strings if strings.Contains(s, "عالم") { fmt.Println("السلسلة تحتوي على 'عالم'") } // بناء نص بكفاءة var builder strings.Builder builder.WriteString("مرحبا") builder.WriteString(" بالجميع") fmt.Println(builder.String()) }

خلاصة

في لغة جو، السلاسل النصية تتميز بالثبات والاستخدام المبسط في أغلب العمليات، مع دعم كامل للترميز العالمي UTF-8، وهذا يجعل التعامل معها معقدًا قليلًا عند التعامل مع لغات غير لاتينية مثل العربية. فهم الفرق بين البايتات والرموز (runes) هو المفتاح الرئيسي للتعامل الصحيح مع النصوص في جو.

تُوفر مكتبة strings أدوات قوية ومتعددة لتسهيل عمليات الفحص، التعديل، البحث، والتقسيم، مما يجعل العمل مع النصوص مرنًا وقويًا. كذلك وجود strings.Builder يساهم بشكل كبير في تحسين الأداء عند بناء نصوص كبيرة ومتكررة التغيير.

التعامل مع السلاسل في جو يتطلب دائمًا الانتباه لترميز UTF-8 وفهم كيفية التعامل مع النصوص المتعددة البايتات، وهذا ما يجعل لغة جو مناسبة جدًا لبناء تطبيقات قادرة على معالجة نصوص متعددة اللغات بكفاءة عالية.


المراجع

  1. وثائق لغة Go الرسمية حول الحلقات والنصوص:

    https://pkg.go.dev/strings

    https://golang.org/ref/spec#String_types

  2. كتاب “The Go Programming Language” للمؤلفين Alan A. A. Donovan و Brian W. Kernighan.