C++ النطاقات ( Namespaces )

مفهوم النطاق

النطاق ( Namespace ) أسلوب مفيد جداً في تنظيم كود المشروع نتبعه لضمان عدم حدوث أي مشكلة سببها إستخدام نفس أسماء الأشياء التي تم تعريفها أكثر من مرة, أي لتجنب حدوث تضارب في الأسماء.

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

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

تضارب الأسماء

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


في المثال التالي قمنا حاولنا تعريف متغيرين إسمهما x في نفس الدالة مما سيؤدي لظهور مشكلة تضارب الأسماء عند التشغيل.
ملاحظة: الخطأ سيحدث عند محاولة إعادة تعريف المتغير للمرة الثانية في نفس المكان.

المثال الأول

Main.cpp
#include <iostream>

int main()
{
	int x;
	int x;   // إعادة تعريف متغير يملك نفس الإسم سيؤدي لحدوث تضارب في الأسماء
	
    return 0;
}
		

سيظهر الخطأ التالي عند التشغيل و الذي يعني أنه لا يمكن إعادة تعريف المتغير x كما حاولنا أن نفعل في السطر رقم 6.

Line 6 | error: redeclaration of 'int x'


في المثال التالي قمنا بإنشاء دالتين إسمهما printMsg() في نفس المكان مما سيؤدي لظهور مشكلة تضارب الأسماء عند التشغيل.
ملاحظة: الخطأ سيحدث عند محاولة إعادة تعريف الدالة للمرة الثانية في نفس المكان.

المثال الثاني

Main.cpp
#include <iostream>

void printMsg()
{

}

void printMsg()   // إعادة تعريف دالة تملك نفس الإسم و عدد الباراميترات سيؤدي لحدوث تضارب في الأسماء
{

}

int main()
{
    return 0;
}
		

سيظهر الخطأ التالي عند التشغيل و الذي يعني أنه لا يمكن إعادة تعريف الدالة printMsg() كما حاولنا أن نفعل في السطر رقم 8.

Line 8 | error: redeclaration of 'void printMsg()'

الكلمة المفتاحية namespace

تستخدم الكلمة namespace لتعريف نطاق جديد و وضع الكود فيه أو لتعريف إسم مختصر لنطاق موجود سابقاً.
وضع الكود بداخل نطاق يتيح لك إعادة استخدام نفس أسماء الأشياء المعرّفة سابقاً في نفس المكان.


الشكل العام لتعريف نطاق

namespace namespace_name 
{
   // هنا تضع أي كود تريد تعريفه بداخل النطاق
}
	

  • مكان الكلمة namespace_name تقوم بوضع الإسم الذي تريد وضعه للنطاق.
  • بين أقواس البداية و النهاية {...} يمكنك تعريف أي شيء تريده مثل متغيرات, دوال, كلاسات و حتى أنه يمكنك تعريف نطاقات داخلية.


الشكل العام لتعريف نطاق بداخل نطاق

يمكنك تعريف نطاق بداخل نطاق و هذا الأسلوب يسمى Nested Namespaces كالتالي.

namespace outer_namspace_name 
{
	namespace inner_namspace_name 
	{

	}
}
	

  • مكان الكلمة outer_namespace_name تقوم بوضع إسم النطاق الخارجي و بداخل حدوده يمكنك كتابة أي كود تريده.
  • مكان الكلمة innner_namespace_name تقوم بوضع إسم النطاق الداخلي و بداخل حدوده يمكنك كتابة أي كود تريده أيضاً.


في المثال التالي قمنا بتعريف متغير إسمه x بشكل مباشر في الملف و بجانبه قمنا بتعريف نطاق إسمه harmash يحتوي على متغير إسمه x أيضاً.

المثال الأول

Main.cpp
#include <iostream>

// x هنا قمنا بتعريف متغير عام إسمه
int x;

// أيضاً x يحتوي على متغير إسمه harmash هنا قمنا بتعريف نطاق إسمه
namespace harmash
{
    int x;
}

int main()
{
	// harmash الخاص بالنطاق x و القيم 2 في المتغير x هنا قمنا بوضع القيمة 1 في المتغير العام
    x = 1;
    harmash::x = 2;

	// harmash الخاص بالنطاق x و المتغير x هنا قمنا بعرض قيمة المتغير العام
    std::cout << "x = " << x << std::endl;
    std::cout << "harmash::x = " << harmash::x;

    return 0;
}
		

سنحصل على النتيجة التالية عند التشغيل.

x = 1
harmash::x = 2
		


في المثال التالي قمنا بتعريف دالة إسمه printMsg() بشكل مباشر في الملف و بجانبها قمنا بتعريف نطاق إسمه harmash يحتوي على دالة إسمها printMsg() أيضاً.

المثال الثاني

Main.cpp
#include <iostream>

// printMsg() هنا قمنا بتعريف دالة عامة إسمها
void printMsg()
{
	std::cout << "printMsg is called \n";
}

// أيضاً printMsg() يحتوي على دالة إسمها harmash هنا قمنا بتعريف نطاق إسمه
namespace harmash
{
	void printMsg()
	{
		std::cout << "harmash::printMsg is called \n";
	}
}

int main()
{
	// harmash الخاصة بالنطاق printMsg() و الدالة printMsg() هنا قمنا باستدعاء الدالة العامة
    printMsg();
    harmash::printMsg();

    return 0;
}
		

سنحصل على النتيجة التالية عند التشغيل.

printMsg is called 
harmash::printMsg is called 
		


في المثال التالي قمنا بتعريف نطاق إسمه outer يحتوي على دالة إسمها printMsg() و بداخله نطاق إسمه inner يحتوي على دالة إسمها printMsg() أيضاً.

المثال الثالث

Main.cpp
#include <iostream>

// printMsg() يحتوي على دالة إسمها outer هنا قمنا بتعريف نطاق إسمه
namespace outer
{
	void printMsg()
	{
		std::cout << "outer::printMsg is called \n";
	}
	
	// أيضاً printMsg() يحتوي على دالة إسمها inner هنا قمنا بتعريف نطاق إسمه
	namespace inner
	{
		void printMsg()
		{
			std::cout << "inner::printMsg is called \n";
		}
	}
}

int main()
{
	// inner الخاصة بالنطاق printMsg() و الدالة outer الخاصة بالنطاق printMsg() هنا قمنا باستدعاء الدالة العامة
    outer::printMsg();
    outer::inner::printMsg();

    return 0;
}
		

سنحصل على النتيجة التالية عند التشغيل.

outer::printMsg is called
inner::printMsg is called
		

الكلمة المفتاحية using

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


معلومة تقنية

في الدروس السابقة كنا نستخدم دائماً الكلمة using حين كنا نكتب using namespace std; بهدف استخدام الكود الموجود فيها بدون الحاجة لكتابة std:: في كل مرة, و لكن تذكر دائماً أنه يفضّل تجنب هذا الأمر لكي لا تقوم بتضمين أي كود لست بحاجة له و لضمان أن لا يحدث أي تضارب مع أسماء الأشياء التي تقوم بتعريفها.


الشكل العام لتضمين كل كود النطاق

using namespace namespace_name;
	

  • مكان الكلمة namespace_name تقوم بوضع إسم النطاق الذي تريد تضمين كل الكود الموجود فيه.


الشكل العام لتضمين كود نطاق موجود بداخل نطاق آخر

using namespace parent_namespace_name::child_namespace_name;
	

  • مكان الكلمة parent_namespace_name تقوم بوضع إسم النطاق الخارجي.
  • مكان الكلمة child_namespace_name تقوم بوضع إسم النطاق الداخلي الذي تريد كل الكود الموجود فيه.


في المثال التالي قمنا بتعريف نطاق إسمه harmash يحتوي على دالتين إسمهما func1() و func2().
بعدها قمنا بتضمين كل محتوى النطاق harmash حتى نتمكن من الوصول إلى الأشياء الموجودة فيه بشكل مباشر, أي بدون الحاجة لكتابة harmash:: في كل مرة.

المثال الأول

Main.cpp
#include <iostream>

// يحتوي على دالتين harmash هنا قمنا بتعريف نطاق إسمه
namespace harmash
{
    void func1()
	{
		std::cout << "func1 is called \n";
	}
	
    void func2()
	{
		std::cout << "func2 is called \n";
	}
}

// harmash هنا قمنا بتضمين كل الكود الموجود في النطاق
using namespace harmash;

int main()
{
	// harmash اللتين قمنا بتضمينها من النطاق func2() و func1() هنا قمنا باستدعاء الدالتين
    func1();
    func2();

    return 0;
}
		

سنحصل على النتيجة التالية عند التشغيل.

func1 is called
func2 is called
		


المثال التالي يوضح المشكلة التي قد يسببها تضمين كل محتوى النطاق.

في المثال التالي قمنا بتعريف دالة إسمها printMsg() و نطاق إسمه harmash يحتوي على دالة إسمها printMsg() أيضاً.
بعدها قمنا بتضمين كل محتوى النطاق harmash و بالتالي أصبح هناك دالتين printMsg() في نفس المكان مما سيؤدي لمشكلة عند محاولة استدعاء الدالة بهذا الشكل printMsg(); سببها تضارب الأسماء عند التشغيل.

المثال الثاني

Main.cpp
#include <iostream>

// printMsg() هنا قمنا بتعريف دالة عامة إسمها
void printMsg()
{
	std::cout << "harmash::printMsg is called \n";
}

// printMsg() يحتوي على إسمها harmash هنا قمنا بتعريف نطاق إسمه
namespace harmash
{
    void printMsg()
	{
		std::cout << "harmash::printMsg is called \n";
	}
}

// الأمر الذي سيؤدي لحدوث مشكلة تضارب الأسماء عند محاولة harmash هنا قمنا بتضمين كل الكود الموجود في النطاق
// موجودتين في نفس المكان printMsg() لأنه بالنسبة للمترجم أصبح هناك دالتين إسمهما printMsg() استدعاء الدالة
using namespace harmash;

int main()
{
	// الأمر الذي سيسبب مشكلة تضارب الأسماء printMsg() هنا حاولنا استدعاء الدالة
	printMsg();
	
    return 0;
}
		

سيظهر الخطأ التالي عند التشغيل بسبب السطر رقم 25 لأن المترجم حائر و لا يعرف أي دالة printMsg() تريده أن ينفذ لك.

25 | error: call of overloaded 'printMsg()' is ambiguous

وضع إسم مختصر للنطاق

في حال كان إسم النطاق كبيراً يمكنك استخدام الكلمة namespace لوضع إسم مختصر له.
ما تفعله ببساطة هو تعريف نطاق جديد تعطيه إسم مختصر و تجعله يساوي النطاق الذي يملك إسم كبير.


الشكل العام لوضع إسم مختصر للنطاق

namespace very_long_namespace_name 
{

}

namespace alias_name = very_long_namespace_name;
	

  • مكان الكلمة very_long_namespace_name تضع إسم النطاق الذي تنوي وضع إسم مختصر له.
  • مكان الكلمة alias_name تضع الإسم المختصر الذي تريد وضعه للناطق.


في المثال التالي قمنا بتعريف نطاق إسمه harmashcom يحتوي على دالة printMsg().
بعدها قمنا بتعريف إسم مختصر للنطاق harmashcom إسمه hc و من ثم قمنا باستدعاء الدالة من خلاله.

مثال

Main.cpp
#include <iostream>

// يحتوي على دالة واحدة harmashcom هنا قمنا بتعريف نطاق إسمه
namespace harmashcom
{
    void printMsg()
	{
		std::cout << "printMsg is called \n";
	}
}

// harmashcom هنا قمنا بتعريف إسم مختصر للنطاق
namespace hc = harmashcom;

int main()
{
	// harmashcom من خلال الإسم المختصر الذي وضعناه للنطاق printMsg() هنا قمنا باستدعاء الدالة
    hc::printMsg();
	
    return 0;
}
		

سنحصل على النتيجة التالية عند التشغيل.

printMsg is called
		

الدورات

أدوات مساعدة

أقسام الموقع

دورات
مقالات كتب مشاريع أسئلة