التغليف في C ++ و C


تعريف


Encapsulation هي مجموعة من الأدوات للتحكم في الوصول إلى البيانات أو الطرق التي تدير تلك البيانات. يمكن العثور على تعريف تفصيلي لمصطلح "التغليف" في منشوري السابق على Habré على هذا الرابط . تركز هذه المقالة على أمثلة التغليف في C ++ و C.


التغليف في C ++


افتراضيًا ، تكون البيانات والطرق في الفصل ( class ) خاصة ( private ) ؛ يمكن قراءتها وتعديلها فقط من خلال الفصل الذي ينتمون إليه. يمكن تغيير مستوى الوصول باستخدام الكلمات الأساسية المناسبة التي يوفرها C ++.


تتوفر عدة تصفيات في C ++ ، وهي تعدل الوصول إلى البيانات على النحو التالي:


  • البيانات العامة متاحة للجميع ؛
  • محمي ( protected ) - متاح فقط للفصول الدراسية والطبقة الفرعية ؛
  • خاص - private - متاح فقط للفئة التي ينتمون إليها.

للإيجاز ، سيتم إبراز مستويين فقط (خاص وعام) في الأمثلة.


مثال التغليف


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


 #include <iostream> using namespace std; class Contact { private: int mobile_number; // private variable int home_number; // private variable public: Contact() // constructor { mobile_number = 12345678; home_number = 87654321; } void print_numbers() { cout << "Mobile number: " << mobile_number; cout << ", home number: " << home_number << endl; } }; int main() { Contact Tony; Tony.print_numbers(); // cout << Tony.mobile_number << endl; // will cause compile time error return 0; } 

ستؤدي محاولة طباعة أو تغيير متغير mobile_number من البرنامج الرئيسي ( main ) إلى حدوث خطأ في الترجمة لأن الوصول إلى البيانات الخاصة في الفصل محدود.


غلاف التغليف مع الأصدقاء (الممارسة الجيدة)


في C ++ ، هناك الكلمة "صديق" التي تتيح لك إضافة استثناءات إلى القواعد العامة للوصول إلى البيانات. إذا تم استدعاء وظيفة أو فئة إلى صديق لفئة Contact ، فسيحصلون على وصول مجاني إلى البيانات المحمية أو الخاصة.


هناك قاعدتان أساسيتان للصداقة - الصداقة ليست موروثة وليس متبادلة. وأيضًا ، لا يغير وجود "الأصدقاء" مستوى أمان البيانات - تظل البيانات الخاصة خاصة باستثناء "الصديق".


 #include <iostream> using namespace std; class Contact { private: int mobile_number; // private variable int home_number; // private variable public: Contact() // constructor { mobile_number = 12345678; home_number = 87654321; } // Declaring a global 'friend' function friend void print_numbers( Contact some_contact ); }; void print_numbers( Contact some_contact ) { cout << "Mobile number: " << some_contact.mobile_number; cout << ", home number: " << some_contact.home_number << endl; } int main() { Contact Tony; print_numbers(Tony); return 0; } 

في هذا المثال ، print_numbers() وظيفة print_numbers() دالة عادية وليست طريقة لفئة Contact . إن الإعلان عن وظيفة print_numbers() "كصديق" لفئة Contact هو السبب الوحيد print_numbers() وظيفة print_numbers() إلى بيانات خاصة. إذا قمت بإزالة السطر مع تعريف صديق ، فلن يتم تجميع الشفرة.


ملاحظة : من الأفضل عدم إساءة استخدام الأصدقاء. يجب اعتبار إضافة صديق استثناءً وليس ممارسة عامة.


انتهاك التغليف مع تحويل النوع والمؤشرات (سوء الممارسة)


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


ورثت C ++ العديد من الأدوات من C ، واحدة منها هي typecasting . بشكل افتراضي ، تكون جميع المتغيرات والأساليب في الفصل خاصة. في الوقت نفسه ، يكون المستوى القياسي للوصول إلى البيانات في البنية ( struct ) عامًا. من الممكن إنشاء هيكل أو فئة عامة بالكامل حيث سيتم تحديد موقع البيانات بشكل مماثل للبيانات الموجودة في فئة Contact واستخدام تحويل الكتابة للوصول إلى البيانات الخاصة.


 #include <iostream> using namespace std; class Contact { private: int mobile_number; // private variable int home_number; // private variable public: Contact() // constructor { mobile_number = 12345678; home_number = 87654321; } void print_numbers() { cout << "Mobile number: " << mobile_number; cout << ", home number: " << home_number << endl; } }; struct Contact_struct { int mobile_number; int home_number; }; int main() { Contact Tony; Contact_struct * structured_Tony; Tony.print_numbers(); structured_Tony = (Contact_struct *) & Tony; structured_Tony->mobile_number = 20; structured_Tony->home_number = 30; Tony.print_numbers(); return 0; } 

تمت قراءة البيانات الخاصة وتعديلها بسبب تحويل النوع


C التغليف


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


المتغيرات الخاصة


في سياق التغليف ، يمكن اعتبار جميع البيانات الموجودة في C عامة بشكل افتراضي. يمكن تغيير مستوى الوصول إلى المتغيرات في الهياكل ( struct ) إلى القطاع الخاص إذا كان تعريفهم معزولًا عن البرنامج الرئيسي. يمكن تحقيق التأثير المطلوب باستخدام ملفات رأس منفصلة (رأس ، .h) ومصدر (مصدر ، .c).


في هذا المثال ، تم تعريف البنية في ملف مصدر منفصل "private_var.c". منذ تهيئة الهيكل في C يتطلب تخصيص وتحرير الذاكرة ، تمت إضافة العديد من وظائف المساعد.


 #include "private_var.h" #include <stdio.h> #include <stdlib.h> struct Contact { int mobile_number; int home_number; }; struct Contact * create_contact() { struct Contact * some_contact; some_contact = malloc(sizeof(struct Contact)); some_contact->mobile_number = 12345678; some_contact->home_number = 87654321; return( some_contact ); } void delete_contact( struct Contact * some_contact ) { free(some_contact); } 

في ملف الرأس المقابل "private_var.h" ، تم التصريح عن هيكل Contact ، لكن محتوياته ظلت مخفية للبرنامج الرئيسي.


 #ifndef PRIVATE_VAR #define PRIVATE_VAR struct Contact; struct Contact * create_contact(); void delete_contact( struct Contact * some_contact ); #endif /* PRIVATE_VAR */ 

وبالتالي ، بالنسبة لـ "main.c" ، فإن محتويات البنية غير معروفة وستتسبب محاولات قراءة أو تعديل البيانات الخاصة في حدوث خطأ في الترجمة.


 #include "private_var.h" #include <stdio.h> int main() { struct Contact * Tony; Tony = create_contact(); // printf( "Mobile number: %d\n", Tony->mobile_number); // will cause compile time error delete_contact( Tony ); return 0; } 

الوصول إلى المتغيرات الخاصة مع المؤشرات


يمكن استخدام تحويل النوع للتغلب على التغليف في C وكذلك في C ++ ، ولكن تم بالفعل وصف هذا النهج. مع العلم أنه في الهيكل يتم ترتيب البيانات حسب ترتيب إعلانها ، تكون المؤشرات وحساب المؤشرات مناسبة لتحقيق الهدف.


الوصول إلى المتغيرات في الهيكل محدود. ومع ذلك ، يتم إخفاء المتغيرات فقط ، وليس الذاكرة التي يتم تخزين البيانات بها. يمكن اعتبار المؤشرات بمثابة مرجع لعنوان الذاكرة ، وإذا كانت هذه الذاكرة متاحة للبرنامج ، فيمكن قراءة البيانات المخزنة في هذه الذاكرة وتغييرها. إذا تم تعيين المؤشر إلى الذاكرة التي يخزن فيها الهيكل بياناته - فيمكن قراءتها. باستخدام تعريف البنية نفسه (نفس الملفات ".c" و ".h") وملف "main.c" المعدل ، تم التغلب على تقييد الوصول.


 #include "private_var.h" #include <stdio.h> int main() { struct Contact * Tony; Tony = create_contact(); int * mobile_number_is_here = (int *)Tony; printf("Mobile number: %d\n", *mobile_number_is_here); int * home_number_is_here = mobile_number_is_here + 1; *home_number_is_here = 1; printf("Modified home number: %d\n", *home_number_is_here); delete_contact( Tony ); return 0; } 

تمت قراءة البيانات في الهيكل وتعديلها


وظائف خاصة


الوظائف ، كونها خارجية (خارجية) بشكل افتراضي ، تكون مرئية في translation unit بأكملها. بمعنى آخر ، إذا تم تجميع عدة ملفات معًا في ملف كائن واحد ، فسيكون أي من هذه الملفات قادرًا على الوصول إلى أي وظيفة من أي ملف آخر. باستخدام static "الثابتة" عند إنشاء دالة ، ستقصر رؤيتها على الملف الذي تم تعريفها به ، وبالتالي ، لضمان خصوصية الوظيفة ، تحتاج إلى تنفيذ عدة خطوات:


  • يجب أن تكون الوظيفة ثابتة ( static ) إما في الملف المصدر (.c) أو في ملف الرأس المقابل (.h) ؛
  • يجب أن يكون تعريف الوظيفة في ملف مصدر منفصل.

في هذا المثال ، تم تعريف print_numbers() الدالة الثابتة في الملف "private_funct.c". بالمناسبة ، الدالة delete_contact() بنجاح باستدعاء print_numbers() لأنها موجودة في نفس الملف.


 #include "private_funct.h" #include <stdio.h> #include <stdlib.h> struct Contact { int mobile_number; int home_number; }; struct Contact * create_contact() { struct Contact * some_contact; some_contact = malloc(sizeof(struct Contact)); some_contact->mobile_number = 12345678; some_contact->home_number = 87654321; return( some_contact ); } static void print_numbers( struct Contact * some_contact ) { printf("Mobile number: %d, ", some_contact->mobile_number); printf("home number = %d\n", some_contact->home_number); } void delete_contact( struct Contact * some_contact ) { print_numbers(some_contact); free(some_contact); } 

في ملف الرأس المقابل "private_funct.h" ، تم إعلان print_numbers() كدالة ثابتة.


 #ifndef PRIVATE_FUNCT_H #define PRIVATE_FUNCT_H struct Contact; struct Contact * create_contact(); static void print_numbers( struct Contact * some_contact ); void delete_contact( struct Contact * my_points ); #endif /* PRIVATE_FUNCT_H */ 

يستدعي البرنامج الرئيسي ، "main.c" ، print_numbers() بشكل غير مباشر عبر delete_contact() ، لأن كلتا الوظيفتين في نفس المستند. ومع ذلك ، print_numbers() محاولة استدعاء print_numbers() من البرنامج الرئيسي إلى print_numbers() خطأ.


 #include "private_funct.h" #include <stdio.h> int main() { struct Contact * Tony; Tony = create_contact(); // print_numbers( Tony ); // will cause compile time error delete_contact( Tony ); return 0; } 

الوصول إلى الميزات الخاصة


من print_numbers() استدعاء print_numbers() من البرنامج الرئيسي. للقيام بذلك ، يمكنك استخدام الكلمة الأساسية goto أو تمرير مؤشر إلى وظيفة خاصة بشكل main . تتطلب كلتا الطريقتين تغييرات إما في الملف المصدر "private_funct.c" ، أو مباشرة في نص الوظيفة نفسها. نظرًا لأن هذه الطرق لا تتجاوز التغليف وإلغاؤها ، فهي خارج نطاق هذه المقالة.


استنتاج


يوجد التغليف خارج لغات OOP. تجعل لغات OOP الحديثة استخدام التغليف أمرًا طبيعيًا وطبيعيًا. هناك العديد من الطرق للتحايل على التغليف وتجنب الممارسات المشكوك فيها سيساعد في الحفاظ عليها في كل من C و C ++.

Source: https://habr.com/ru/post/ar444602/


All Articles