ميزات C ++ الحديثة التي يحتاج جميع المبرمجين إلى معرفتها

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

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



اليوم سنتحدث عن بعض الميزات الجديدة للغة (بدءًا من C ++ 11 ، والتي ، بالمناسبة ، عمرها بالفعل 8 سنوات) ، والتي ستكون مفيدة لأي مبرمج يعرفها.

الكلمة الرئيسية التلقائي


منذ ظهور الكلمة الأساسية auto الإصدار C ++ 11 ، أصبحت حياة المبرمجين أسهل. بفضل هذه الكلمة الرئيسية ، يمكن للمترجم إنتاج أنواع متغيرة في وقت الترجمة ، مما يوفر لنا دائمًا الحاجة إلى تحديد أنواع بأنفسنا. اتضح أن هذا مناسب جدًا ، على سبيل المثال ، في الحالات التي يتعين عليك فيها العمل مع أنواع البيانات مثل map<string,vector<pair<int,int>>> . عند استخدام الكلمة الأساسية auto ، هناك بعض الأشياء التي يجب مراعاتها. النظر في مثال:

 auto an_int = 26; //    ,     - int auto a_bool = false; //   bool auto a_float = 26.04; //   float auto ptr = &a_float; //       auto data; // #1     ?    - . 

انتبه إلى السطر الأخير في هذا المثال ، حيث تم وضع علامة على التعليق الذي يحمل علامة #1 (يشار إليه فيما يلي ، بطريقة مماثلة ، سنضع علامة على الخطوط التي سنقوم بتحليلها بعد الأمثلة). لا يوجد مُهيئ في هذا الخط ، لا يمكنك القيام بذلك. يمنع الكود الموجود على هذا الخط المترجم من معرفة نوع المتغير المقابل.

في البداية ، كانت الكلمة الأساسية auto في C ++ محدودة للغاية. ثم ، في الإصدارات الأحدث من اللغة ، تم إضافة ميزات auto . هنا مثال آخر:

 auto merge(auto a, auto b) //            auto! {   std::vector<int> c = do_something(a, b);   return c; } std::vector<int> a = { ... }; // #1 -  std::vector<int> b = { ... }; // #2  -  auto c = merge(a,b); //       

يطبق الخطان #1 والثاني التهيئة المتغيرة باستخدام الأقواس المتعرجة - ميزة جديدة أخرى في الإصدار C ++ 11.

تذكر أنه عند استخدام الكلمة الأساسية auto ، يجب أن يكون لدى المترجم طريقة ما لاستنتاج نوع المتغير.

الآن ، سؤال مثير للاهتمام. ماذا يحدث إذا كنت تستخدم تصميمًا مثل auto a = {1, 2, 3} ؟ ما هذا المتجهات ، أو سبب خطأ التحويل البرمجي؟

في الواقع ، ظهر إنشاء النموذج std::initializer_list<type> في C ++ 11. سيتم التعامل مع قائمة الأقواس لقيم التهيئة كحاوية باستخدام الكلمة الأساسية auto .

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

 void populate(auto &data) { //    !   data.insert({"a",{1,4}});   data.insert({"b",{3,1}});   data.insert({"c",{2,3}}); } auto merge(auto data, auto upcoming_data) { //         auto result = data;   for(auto it: upcoming_data) {       result.insert(it);   }   return result; } int main() {   std::map<std::string, std::pair<int,int>> data;   populate(data);   std::map<std::string, std::pair<int,int>> upcoming_data;   upcoming_data.insert({"d",{5,3}});   auto final_data = merge(data,upcoming_data);   for(auto itr: final_data) {       auto [v1, v2] = itr.second; // #1               std::cout << itr.first << " " << v1 << " " << v2 << std:endl;   }   return 0; } 

ألق نظرة على السطر #1 . يمثل التعبير auto [v1,v2] = itr.second ميزة جديدة في C ++ 17. هذا هو ما يسمى بتحلل التصريحات المتغيرة. في الإصدارات السابقة من اللغة ، كان لا بد من استخراج كل قيمة على حدة. بفضل هذه الآلية ، أصبح إجراء مثل هذه العمليات أكثر ملاءمة.

علاوة على ذلك ، إذا كنت بحاجة إلى التعامل مع البيانات باستخدام الروابط ، فهذا يكفي لإضافة حرف واحد فقط لهذا البناء ، وتحويله إلى النموذج التالي: auto &[v1,v2] = itr.second .

تعبيرات لامدا


C ++ 11 يقدم الدعم لتعبيرات lambda. إنها تشبه الوظائف المجهولة في JavaScript ، ويمكن مقارنتها بالكائنات الوظيفية بدون أسماء. وهي تلتقط متغيرات في نطاقات مختلفة اعتمادًا على وصفها ، والتي تستخدم فيها التركيبات النحوية المدمجة. بالإضافة إلى ذلك ، يمكن تعيينها للمتغيرات.

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

 std::vector<std::pair<int,int>> data = {{1,3}, {7,6}, {12, 4}}; //        std::sort(begin(data), end(data), [](auto a, auto b) { //   - auto!   return a.second < b.second; }); 

يمكنك أن تجد الكثير من الأشياء المثيرة للاهتمام في هذا المثال القصير.

أولاً ، انتبه إلى مدى ملائمة استخدام التهيئة المتغيرة باستخدام الأقواس المتعرجة. بعد ذلك ، يمكننا أن نرى الإنشاءات القياسية begin() و end() ، والتي ظهرت أيضًا في الإصدار C ++ 11. ثم تأتي وظيفة lambda ، التي تُستخدم كآلية لمقارنة البيانات. تم الإعلان عن معلمات هذه الوظيفة باستخدام الكلمة الأساسية auto ، فقد ظهرت هذه الميزة في الإصدار C ++ 14. سابقًا ، لا يمكن استخدام هذه الكلمة الأساسية لوصف معلمات الدوال.

لاحظ الآن أن تعبير lambda يبدأ بأقواس مربعة - [] . هذا هو قناع ما يسمى المتغيرات. إنه يحدد نطاق التعبير ، أي أنه يسمح لك بالتحكم في علاقة تعبير lambda مع المتغيرات والكائنات المحلية.

إليك مقتطف من هذا المستودع المخصص لميزات C ++ الحديثة:

  • [] - لا يعبر التعبير عن أي شيء. هذا يعني أنه في تعبير lambda يستحيل استخدام المتغيرات المحلية من النطاق الخارجي لها. يمكن استخدام المعلمات فقط في التعبير.
  • [=] - يلتقط التعبير قيم الكائنات المحلية (أي المتغيرات المحلية والمعلمات). هذا يعني أنه يمكن استخدامها ، ولكن لا يمكن تعديلها.
  • [&] - التعبير يلتقط الإشارات إلى الكائنات المحلية. يمكن تعديلها ، كما هو موضح في المثال التالي.
  • [this] - يعبر التعبير عن قيمة this المؤشر.
  • [a, &b] - يعبر التعبير عن قيمة الكائن a ومرجع إلى الكائن b .

نتيجة لذلك ، إذا كنت بحاجة داخل وظيفة lambda إلى تحويل البيانات إلى تنسيق آخر ، فيمكنك استخدام الآليات المذكورة أعلاه. النظر في مثال:

 std::vector<int> data = {2, 4, 4, 1, 1, 3, 9}; int factor = 7; for_each(begin(data), end(data), [&factor](int &val) { //    factor     val = val * factor;   factor--; // #1   - ,  -    factor   }); for(int val: data) {   std::cout << val << ' '; // 14 24 20 4 3 6 9 } 

هنا ، إذا تم الوصول إلى متغير factor حسب القيمة (فسيتم استخدام قناع المتغير [factor] لوصف تعبير lambda) ، ثم في السطر #1 لا يمكن تغيير قيمة factor - لأنه ببساطة لن يكون لدينا حقوق في إجراء مثل هذه العملية. في هذا المثال ، لدينا الحق في مثل هذه الإجراءات. في مثل هذه الحالات ، من المهم عدم إساءة استخدام القدرات التي توفرها متغيرات الوصول بالرجوع إليها.

بالإضافة إلى ذلك ، لاحظ أنه val الوصول إلى val أيضًا بالرجوع إليها. هذا يضمن أن تغييرات البيانات التي تحدث في وظيفة lambda تؤثر على vector .

تعبيرات التهيئة المتغيرة في الداخل وتبني التحويلات


أحببت حقًا هذا الابتكار لـ C ++ 17 بعد أن اكتشفته. النظر في مثال:

 std::set<int> input = {1, 5, 3, 6}; if(auto it = input.find(7); it==input.end()){ //   - ,  -    std::cout << 7 << " not found" << std:endl; } else {   //    else      it   std::cout << 7 << " is there!" << std::endl; } 

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

 if( init-statement(x); condition(x)) {   //    } else {   //     x   //    } 

أداء حسابات وقت الترجمة باستخدام constexpr


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

يسعى المبرمجون إلى تقليل مقدار الحساب الذي يتم تنفيذه أثناء تنفيذ البرنامج. نتيجة لذلك ، إذا كان من الممكن إجراء عمليات معينة أثناء عملية التحويل البرمجي وبالتالي إزالة التحميل من النظام أثناء تنفيذ البرنامج ، فسيكون لهذا تأثير جيد على سلوك البرنامج أثناء التنفيذ. هنا مثال:

 #include <iostream> constexpr long long fact(long long n) { //       constexpr return n == 1 ? 1 : (fact(n-1) * n); } int main() { const long long bigval = fact(20); std::cout<<bigval<<std::endl; } 

هذا مثال شائع لاستخدام constexpr .

نظرًا لأننا أعلنا وظيفة حساب المضروب باعتباره constexpr ، يمكن constexpr أن يحسب مسبقًا قيمة fact(20) في وقت تجميع البرنامج. نتيجة لذلك ، بعد const long long bigval = fact(20); البرمجي ، السلسلة const long long bigval = fact(20); يمكن الاستعاضة عنها const long long bigval = 2432902008176640000; .

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

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

ومن المثير للاهتمام أن نلاحظ أنه في وقت لاحق ، في C ++ 17 ، ظهرت بنيات constexpr-if و constexpr-lambda .

هياكل البيانات Tuple


مثل بنية بيانات pair ، فإن بنية بيانات tuple (tuple) هي مجموعة من القيم لأنواع مختلفة من الحجم الثابت. هنا مثال:

 auto user_info = std::make_tuple("M", "Chowdhury", 25); //  auto     //    std::get<0>(user_info); std::get<1>(user_info); std::get<2>(user_info); //  C++ 11     tie std::string first_name, last_name, age; std::tie(first_name, last_name, age) = user_info; //  C++ 17, ,       auto [first_name, last_name, age] = user_info; 

في بعض الأحيان ، بدلاً من بنية بيانات tuple ، يكون استخدام std::array أكثر ملاءمة. تشبه بنية البيانات هذه الصفائف البسيطة المستخدمة في لغة C ، وهي مزودة بميزات إضافية من مكتبة C ++ القياسية. ظهرت بنية البيانات هذه في الإصدار C ++ 11.

استنتاج نوع وسيطة قالب الفئة تلقائيًا


يبدو اسم هذه الميزة طويلًا ومعقدًا ، لكن في الواقع لا يوجد شيء معقد هنا. الفكرة الرئيسية هنا هي أنه في C ++ 17 ، يتم تنفيذ إخراج أنواع وسائط القالب أيضًا لقوالب الفئات القياسية. في السابق ، كان هذا معتمدًا فقط للقوالب الوظيفية. نتيجة لذلك ، اتضح أنهم اعتادوا أن يكتبوا مثل هذا:

 std::pair<std::string, int> user = {"M", 25}; 

مع إصدار C ++ 17 ، يمكن الآن استبدال هذا البناء بهذا:

 std::pair user = {"M", 25}; 

نوع الاستدلال يتم ضمنيا. هذه الآلية هي أكثر ملاءمة للاستخدام عندما يتعلق الأمر tuples. أي قبل أن اضطررت إلى كتابة ما يلي:

 std::tuple<std::string, std::string, int> user ("M", "Chy", 25); 

الآن نفس الشيء يبدو كالتالي:

 std::tuple user2("M", "Chy", 25); 

تجدر الإشارة إلى أن هذه الميزات لن تبدو شيئًا يستحق الاهتمام لأولئك الذين ليسوا على دراية بنماذج C ++.

مؤشرات ذكية


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

لحسن الحظ ، قدمت C ++ 11 مؤشرات ذكية أكثر ملاءمة بكثير من المؤشرات العادية. أنها تساعد مبرمج تجنب تسرب الذاكرة عن طريق تحرير الموارد عندما يكون ذلك ممكناً. بالإضافة إلى ذلك ، فإنها توفر ضمان أمان للاستثناءات.

النتائج


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

أعزائي القراء! ما هي ميزات C ++ الحديثة التي تجدها أكثر إثارة للاهتمام ومفيدة؟

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


All Articles