سؤال

لماذا ++i هي قيمة l وi++ ليست كذلك؟

هل كانت مفيدة؟

المحلول

حسنًا كما أشار مجيب آخر إلى السبب بالفعل ++i القيمة هي تمريرها إلى مرجع.

int v = 0;
int const & rcv = ++v; // would work if ++v is an rvalue too
int & rv = ++v; // would not work if ++v is an rvalue

سبب القاعدة الثانية هو السماح بتهيئة مرجع باستخدام حرفي، عندما يكون المرجع مرجعًا لـ const:

void taking_refc(int const& v);
taking_refc(10); // valid, 10 is an rvalue though!

لماذا نقدم قيمة rvalue على الإطلاق قد تسأل.حسنًا، تظهر هذه المصطلحات عند بناء قواعد اللغة لهاتين الحالتين:

  • نريد أن يكون لدينا قيمة محدد.سيمثل ذلك موقعًا يحتوي على قيمة يمكن قراءتها.
  • نريد تمثيل قيمة التعبير.

النقطتان المذكورتان أعلاه مأخوذتان من معيار C99 الذي يتضمن هذه الحاشية السفلية اللطيفة المفيدة جدًا:

اسم "lvalue" يأتي في الأصل من تعبير المهمة e1 = e2 ، حيث يكون المعامل الأيسر e1 مطلوبًا ليكون lvalue (modi able).ربما يعتبر أفضل تمثيل كائن "قيمة محدد".ما يسمى أحيانًا "rvalue" في هذا المعيار الدولي الموصوف على أنه "قيمة التعبير".]

يتم استدعاء قيمة محدد الموقع lvalue, ، بينما تسمى القيمة الناتجة عن تقييم ذلك الموقع com.rvalue.هذا صحيح أيضًا وفقًا لمعيار C++ (يتحدث عن تحويل القيمة إلى قيمة r):

4.1/2:القيمة الواردة في الكائن المشار إليها بواسطة LVALUE هي نتيجة RVALUE.

خاتمة

باستخدام الدلالات المذكورة أعلاه، فمن الواضح الآن لماذا i++ ليست قيمة بل قيمة.لأن التعبير الذي تم إرجاعه غير موجود في i بعد الآن (لقد زادت!)، إنها القيمة التي يمكن أن تكون ذات أهمية.تعديل تلك القيمة التي تم إرجاعها بواسطة i++ لن يكون ذلك منطقيًا، لأنه ليس لدينا موقع يمكننا من خلاله قراءة هذه القيمة مرة أخرى.ولذلك يقول المعيار إنها قيمة r، وبالتالي لا يمكن ربطها إلا بمرجع إلى ثابت.

ومع ذلك، في المقابل، تم إرجاع التعبير بواسطة ++i هو موقع (lvalue) من i.إثارة تحويل من lvalue إلى rvalue، كما هو الحال في int a = ++i; سوف تقرأ القيمة منه.بدلًا من ذلك، يمكننا أن نجعلها نقطة مرجعية، ونقرأ القيمة لاحقًا: int &a = ++i;.

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

سيتضمن الإصدار C++ التالي ما يسمى rvalue references والتي، على الرغم من أنها تشير إلى غير ثابتة، يمكن أن ترتبط بقيمة r.الأساس المنطقي هو أن تكون قادرًا على "سرقة" الموارد من تلك الكائنات المجهولة، وتجنب النسخ التي تفعل ذلك.بافتراض أن نوع الفصل يحتوي على بادئة مثقلة ++ (إرجاع Object&) و postfix ++ (إرجاع Object)، فإن ما يلي قد يتسبب في حدوث نسخة أولاً، وفي الحالة الثانية سوف يسرق الموارد من rvalue:

Object o1(++a); // lvalue => can't steal. It will deep copy.
Object o2(a++); // rvalue => steal resources (like just swapping pointers)

نصائح أخرى

لقد تناول أشخاص آخرون الفرق الوظيفي بين الزيادة اللاحقة والزيادة المسبقة.

بقدر ما يكون lvalue تشعر بالقلق، i++ لا يمكن تعيينه لأنه لا يشير إلى متغير.يشير إلى قيمة محسوبة.

من حيث التعيين، كلا الأمرين التاليين لا معنى لهما بنفس الطريقة:

i++   = 5;
i + 0 = 5;

نظرًا لأن الزيادة المسبقة تُرجع مرجعًا إلى المتغير المتزايد بدلاً من نسخة مؤقتة، ++i هي قيمة.

يصبح تفضيل الزيادة المسبقة لأسباب تتعلق بالأداء فكرة جيدة بشكل خاص عندما تقوم بزيادة شيء مثل كائن مكرر (على سبيل المثال في STL) والذي قد يكون وزنه ثقيلًا أكثر من int.

ويبدو أن الكثير من الناس يشرح كيف ++i هو lvalue، ولكن ليس على لماذا ، أو كما هو الحال في، <م> لماذا لم تضع لجنة المعايير C ++ هذه الميزة في ، لا سيما في ضوء حقيقة أن C لا يسمح إما lvalues. من هذه المناقشة على comp.std.c ++ ، فإنه يبدو أنه حتى تتمكن من اتخاذ عنوانه أو تعيين إلى مرجع. A نموذج التعليمات البرمجية مقتبسة من آخر مسيحي باو:

<اقتباس فقرة>
   int i;
   extern void f (int* p);
   extern void g (int& p);

   f (&++i);   /* Would be illegal C, but C programmers
                  havent missed this feature */
   g (++i);    /* C++ programmers would like this to be legal */
   g (i++);    /* Not legal C++, and it would be difficult to
                  give this meaningful semantics */

وبالمناسبة، إذا حدث i أن تكون مدمجة في نوع، ثم البيانات المهمة مثل ++i = 10 الاحتجاج على سلوك غير معرف ، أو لأن يتم تعديل i مرتين بين نقاط التسلسل.

وأنا الحصول على الخطأ lvalue عندما أحاول تجميع

i++ = 2;

ولكن ليس عندما أقوم بتغيير ل

++i = 2;

وذلك لأن المشغل بادئة (++ ط) تغيير القيمة في الاول، ثم يعود ط، لذلك لا يزال من الممكن المخصصة ل. المشغل لواحق (ط ++) بتغيير قيمة في الاول، ولكن بإرجاع نسخة مؤقتة من العمر <م> قيمة ، والتي لا يمكن تعديلها من قبل عامل التعيين.


<القوي> الإجابة على السؤال الأصلي : ل

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

for(int i=0; i<limit; i++)
...

وهو نفس

for(int i=0; i<limit; ++i)
...

والامور قليلا أكثر تعقيدا عندما كنت تستخدم قيمة الإرجاع العملية كجزء من بيان أوسع.

وحتى بيانين بسيطة

int i = 0;
int a = i++;

و

int i = 0;
int a = ++i;

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

POD قبل الزيادة

ولما قبل الزيادة يجب أن يتصرف كما لو كان يتزايد الكائن قبل التعبير وتكون قابلة للاستخدام في هذا التعبير كما لو حدث ذلك. وهكذا فإن comitee ++ C معايير قررت أنها يمكن أن تستخدم أيضا بوصفها القيمة لتر.

POD المشاركة الزيادة

وبعد الزيادة يجب فسيزيد الكائن POD والعودة نسخة للاستخدام في التعبير (انظر القسم n2521 5.2.6). كنسخة ليست في الواقع المتغير مما يجعلها ذات قيمة ل لا يجعل أي معنى.

كائنات:

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

والأمر متروك منفذة من هذه الطرق لجعل سلوك هذه الكائنات تعكس سلوك الكائنات POD (ليس مطلوبا ولكن من المتوقع).

كائنات ما قبل الزيادة

والشرط (السلوك المتوقع) هنا هو أن الكائنات يتم زيادة (يعني تعتمد على وجوه) وطريقة إرجاع قيمة للتعديل ويشبه الكائن الأصلي بعد وقوع الزيادة (كما لو الزيادة لم يحدث قبل ذلك البيان).

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

كائنات بعد الزيادة

والشرط (السلوك المتوقع) هنا هو أن الكائن يتم زيادة (في بنفس الطريقة قبل الزيادة) وقيمة عاد يشبه القيمة القديمة وغير قابلة للقابلة للتغيير (بحيث لا تتصرف مثل لتر -value).

وعدم قابلية التحويل:
للقيام بذلك يجب إرجاع كائن. في حالة استخدام الكائن ضمن تعبير سيتم نسخ شيدت في متغير مؤقت. المتغيرات المؤقتة CONST وبالتالي فإنه سيتم غير قابلة للتغيير وتتصرف كما هو متوقع.

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

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

class Node // Simple Example
{
     /*
      * Pre-Increment:
      * To make the result non-mutable return an object
      */
     Node operator++(int)
     {
         Node result(*this);   // Make a copy
         operator++();         // Define Post increment in terms of Pre-Increment

         return result;        // return the copy (which looks like the original)
     }

     /*
      * Post-Increment:
      * To make the result an l-value return a reference to this object
      */
     Node& operator++()
     {
         /*
          * Update the state appropriatetly */
         return *this;
     }
};

فيما يتعلق بالقيمة

  • في C (وبيرل على سبيل المثال)، لا ++i ولا i++ هي القيم L.

  • في C++, i++ ليس وLValue ولكن ++i يكون.

    ++i يعادل i += 1, ، وهو ما يعادل i = i + 1.
    والنتيجة هي أننا لا نزال نتعامل مع نفس الكائن i.
    يمكن النظر إليها على النحو التالي:

    int i = 0;
    ++i = 3;  
    // is understood as
    i = i + 1;  // i now equals 1
    i = 3;
    

    i++ ومن ناحية أخرى يمكن النظر إلى:
    أولا نستخدم قيمة ل i, ، ثم قم بزيادة هدف i.

    int i = 0;
    i++ = 3;  
    // would be understood as 
    0 = 3  // Wrong!
    i = i + 1;
    

(يحرر:تم تحديثه بعد محاولة أولى ملطخة).

والفرق الرئيسي هو أن أكون ++ بإرجاع قيمة ما قبل الزيادة في حين ++ ط بإرجاع قيمة بعد الزيادة. وعادة ما تستخدم ++ أنا ما لم يكن لدي سبب مقنع جدا للاستخدام ط ++ - وهي، إذا كنت حقا <م> لا في حاجة إلى قيمة ما قبل الزيادة

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

وبالمناسبة - تجنب استخدام مشغلي زيادة متعددة على نفس المتغير في البيان نفسه. تحصل في حالة من الفوضى "أين هي نقاط التسلسل" والنظام غير محدد من العمليات، على الأقل في C. وأعتقد أن بعض من تم تنظيف في جاوة الثانية C #.

ربما يكون لهذا علاقة بالطريقة التي يتم بها تنفيذ الزيادة اللاحقة.ربما هو شيء من هذا القبيل:

  • إنشاء نسخة من القيمة الأصلية في الذاكرة
  • زيادة المتغير الأصلي
  • إرجاع النسخة

نظرًا لأن النسخة ليست متغيرًا ولا مرجعًا للذاكرة المخصصة ديناميكيًا، فلا يمكن أن تكون قيمة l.

كيف يترجم المترجم هذا التعبير؟ a++

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

القيمة التي تم إرجاعها هي نسخة من a الذي يتم وضعه في يسجل.ثم يتم زيادة المتغير.إذن، هنا لا تقوم بإرجاع المتغير نفسه، ولكنك تقوم بإرجاع نسخة وهي a متفرق كيان!يتم تخزين هذه النسخة مؤقتًا داخل السجل ثم يتم إعادتها.تذكر أن القيمة l في لغة C++ هي كائن له موقع محدد في الذاكرة.لكن النسخة مخزنة بالداخل سجل في وحدة المعالجة المركزية، وليس في الذاكرة. جميع قيم r هي كائنات ليس لها موقع يمكن تحديده في الذاكرة.وهذا ما يفسر سبب نسخة النسخة القديمة من a هي قيمة r، لأنه يتم تخزينها مؤقتًا في السجل.بشكل عام، أي نسخ أو قيم مؤقتة أو نتائج تعبيرات طويلة مثل (5 + a) * b يتم تخزينها في السجلات، ثم يتم تعيينها في المتغير، وهو lvalue.

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

for (int i = 0; i != 5; i++) {...}

يصل عدد هذه الحلقة إلى خمسة، لكن i++ هو الجزء الأكثر إثارة للاهتمام.إنها في الواقع تعليماتان في 1.أولا علينا أن ننقل القيمة القديمة لـ i في السجل، ثم نقوم بزيادة i.في رمز التجميع الزائف:

mov i, eax
inc i

eax التسجيل يحتوي الآن على النسخة القديمة من i كنسخة.إذا كان المتغير i موجودة في الذاكرة الرئيسية، فقد تستغرق وحدة المعالجة المركزية وقتًا طويلاً للحصول على النسخة من الذاكرة الرئيسية ونقلها إلى السجل.عادةً ما يكون ذلك سريعًا جدًا بالنسبة لأنظمة الكمبيوتر الحديثة، ولكن إذا تم تكرار حلقة for-loop الخاصة بك مائة ألف مرة، فستبدأ كل هذه العمليات الإضافية في التراكم!ستكون عقوبة أداء كبيرة.

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

ماذا عن زيادة البادئة ++a?

نريد إرجاع زيادة نسخة من a, ، الإصدار الجديد من a بعد الزيادة.النسخة الجديدة من a يمثل الوضع الحالي لل a, لأنه المتغير نفسه.

أولاً a يتم زيادة.نظرًا لأننا نريد الحصول على الإصدار المحدث من a, ، لماذا لا نعيد فقط عامل a بحد ذاتها؟لا نحتاج إلى إنشاء نسخة مؤقتة في السجل لإنشاء قيمة r.وهذا يتطلب عملا إضافيا غير ضروري.لذلك نعيد المتغير نفسه كقيمة l.

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

ميزة أخرى للاستخدام ++a هو أنه يعبر عن نية البرنامج بشكل مباشر أكثر:أريد فقط أن أزيد a!ومع ذلك، عندما أرى a++ في كود شخص آخر، أتساءل لماذا يريدون إرجاع القيمة القديمة؟لما هذا؟

وC #:

public void test(int n)
{
  Console.WriteLine(n++);
  Console.WriteLine(++n);
}

/* Output:
n
n+2
*/
مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top