سؤال

هذا السؤال لديه بالفعل إجابة هنا:

يمكنك رؤيته مستخدمًا في عبارات الحلقة، ولكنه بناء جملة قانوني في أي مكان.ما هي الاستخدامات التي وجدتها لها في مكان آخر، إن وجدت؟

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

المحلول

تعد لغة C (وكذلك C++) تاريخيًا مزيجًا من نمطين مختلفين تمامًا من البرمجة، والتي يمكن للمرء أن يشير إليها باسم "برمجة البيان" و"برمجة التعبير".كما تعلم، تدعم كل لغة برمجة إجرائية عادةً بنيات أساسية مثل التسلسل و المتفرعة (يرى برمجة منظمة).هذه التركيبات الأساسية موجودة في لغات C/C++ في شكلين:واحد للبرمجة البيانية، والآخر للبرمجة التعبيرية.

على سبيل المثال، عندما تكتب برنامجك من حيث العبارات، يمكنك استخدام سلسلة من العبارات مفصولة بـ ;.عندما تريد القيام ببعض التفرع، يمكنك استخدام if صياغات.يمكنك أيضًا استخدام الدورات وأنواع أخرى من بيانات نقل التحكم.

في البرمجة التعبيرية تتوفر لك نفس التركيبات أيضًا.هذا هو في الواقع حيث , المشغل يأتي في اللعب.المشغل أو العامل , في شيء آخر غير فاصل التعبيرات المتسلسلة في لغة C، أي.المشغل أو العامل , في برمجة التعبير يخدم نفس الدور ; يفعل في برمجة البيان.التفرع في البرمجة التعبيرية يتم من خلال ?: المشغل، وبدلاً من ذلك، من خلال خصائص تقييم ماس كهربائى && و || العاملين.(رغم أن برمجة التعبير لا تحتوي على دورات.ولاستبدالها بالتكرار، سيتعين عليك تطبيق برمجة البيانات.)

على سبيل المثال، التعليمات البرمجية التالية

a = rand();
++a;
b = rand();
c = a + b / 2;
if (a < c - 5)
  d = a;
else
  d = b;

وهو مثال على برمجة البيانات التقليدية، ويمكن إعادة كتابته من حيث البرمجة التعبيرية مثل

a = rand(), ++a, b = rand(), c = a + b / 2, a < c - 5 ? d = a : d = b;

أو كما

a = rand(), ++a, b = rand(), c = a + b / 2, d = a < c - 5 ? a : b;

أو

d = (a = rand(), ++a, b = rand(), c = a + b / 2, a < c - 5 ? a : b);

أو

a = rand(), ++a, b = rand(), c = a + b / 2, (a < c - 5 && (d = a, 1)) || (d = b);

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

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

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

نصائح أخرى

أعتقد بشكل عام أن فاصلة لغة C ليست أسلوبًا جيدًا للاستخدام ببساطة لأنه من السهل جدًا تفويتها - إما عن طريق شخص آخر يحاول قراءة/فهم/إصلاح الكود الخاص بك، أو أنت نفسك بعد شهر.خارج نطاق الإعلانات المتغيرة والحلقات، بالطبع، حيث يكون ذلك اصطلاحيًا.

يمكنك استخدامه، على سبيل المثال، لتعبئة بيانات متعددة في عامل تشغيل ثلاثي (؟:)، ala:

int x = some_bool ? printf("WTF"), 5 : fprintf(stderr, "No, really, WTF"), 117;

ولكن يا إلهي لماذا؟!؟(لقد رأيته مستخدمًا بهذه الطريقة في التعليمات البرمجية الحقيقية، ولكن لا يمكنني الوصول إليه لعرضه للأسف)

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

مثال مبسط:

#define SomeMacro(A) ( DoWork(A), Permute(A) )

هنا B=SomeMacro(A) "إرجاع" نتيجة Permute(A) وتخصيصها إلى "B".

ميزتان قاتلتان لمشغل الفاصلة في C++:

أ) القراءة من الدفق حتى تتم مواجهة سلسلة معينة (يساعد في الحفاظ على الكود جافًا):

 while (cin >> str, str != "STOP") {
   //process str
 }

ب) اكتب تعليمات برمجية معقدة في مُهيئات المُنشئ:

class X : public A {
  X() : A( (global_function(), global_result) ) {};
};

اضطررت إلى استخدام فاصلة لتصحيح أخطاء أقفال كائن المزامنة (mutex) لوضع رسالة قبل يبدأ القفل في الانتظار.

لم أستطع إلا أن أضع رسالة السجل في نص مُنشئ القفل المشتق، لذلك اضطررت إلى وضعها في وسيطات مُنشئ الفئة الأساسية باستخدام:baseclass( (log( "message" ) , act_arg )) في قائمة التهيئة.لاحظ الأقواس الإضافية.

هنا مقتطف من الفصول:

class NamedMutex : public boost::timed_mutex
{
public:
    ...

private:
    std::string name_ ;
};

void log( NamedMutex & ref__ , std::string const& name__ )
{
    LOG( name__ << " waits for " << ref__.name_ );
}

class NamedUniqueLock : public boost::unique_lock< NamedMutex >
{
public:

    NamedUniqueLock::NamedUniqueLock(
        NamedMutex & ref__ ,
        std::string const& name__ ,
        size_t const& nbmilliseconds )
    :
        boost::unique_lock< NamedMutex >( ( log( ref__ , name__ ) , ref__ ) ,
            boost::get_system_time() + boost::posix_time::milliseconds( nbmilliseconds ) ),
            ref_( ref__ ),
            name_( name__ )
    {
    }

  ....

};

ال تعزيز التعيين تعد المكتبة مثالًا جيدًا على التحميل الزائد على عامل الفاصلة بطريقة مفيدة وسهلة القراءة.على سبيل المثال:

using namespace boost::assign;

vector<int> v; 
v += 1,2,3,4,5,6,7,8,9;

من المعيار C:

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

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

لاحظ أن:

int a, b, c;

ليس عامل الفاصلة، بل هو قائمة من المُصرِّحين.

يتم استخدامه أحيانًا في وحدات الماكرو، مثل تصحيح وحدات الماكرو مثل هذا:

#define malloc(size) (printf("malloc(%d)\n", (int)(size)), malloc((size)))

(ولكن أنظر هذا الفشل الذريع, ، بحقك، لما يمكن أن يحدث عندما تبالغ في ذلك.)

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

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

MyVector foo = 2, 3, 4, 5, 6;

خارج حلقة for، وحتى هناك يمكن أن يكون لها رائحة رائحة الكود، المكان الوحيد الذي رأيته كاستخدام جيد لمشغل الفاصلة هو كجزء من الحذف:

 delete p, p = 0;

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

يعجبني أيضًا لأنه إذا قمت بذلك بدافع العادة، فلن تنسى أبدًا المهمة الصفرية.(بالطبع، لماذا لا يكون p داخل أحد أنواع الأغلفة auto_ptr، وsmart_ptr، وshared_ptr، وما إلى ذلك، فهو سؤال مختلف.)

نظرًا لاقتباس @Nicolas Goy من المعيار، يبدو أنه يمكنك كتابة سطر واحد للحلقات مثل:

int a, b, c;
for(a = 0, b = 10; c += 2*a+b, a <= b; a++, b--);
printf("%d", c);

لكن يا إلهي، هل تريد حقًا إنشاء كود C الخاص بك أكثر غامضة بهذه الطريقة؟

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

foo=bar*2, plugh=hoo+7;

لا يقدم أي ميزة واضحة على:

foo=bar*2;
plugh=hoo+7;

المكان الوحيد بجانب الحلقات حيث استخدمته في بنيات if/else، مثل:

if (a==1)
... do something ...
else if (function_with_side_effects_including_setting_b(), b==2)
... do something that relies on the side effects ...

يمكنك وضع الدالة قبل IF، ولكن إذا استغرق تشغيل الدالة وقتًا طويلاً، فقد ترغب في تجنب القيام بها إذا لم تكن ضرورية، وإذا لم يكن من الضروري تنفيذ الدالة إلا إذا كانت a!=1، فهذا ليس أمرًا خيار.البديل هو تداخل طبقة IF الإضافية.هذا في الواقع ما أفعله عادةً لأن الكود أعلاه غامض بعض الشيء.لكنني فعلت ذلك بطريقة الفاصلة بين الحين والآخر لأن التداخل غامض أيضًا.

إنه مفيد جدًا في إضافة بعض التعليقات إلى ASSERT وحدات الماكرو:

ASSERT(("This value must be true.", x));

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

غالبًا ما أستخدمه لتشغيل وظيفة التهيئة الثابتة في بعض ملفات cpp، لتجنب مشكلات التهيئة البطيئة مع المفردات الكلاسيكية:

void* s_static_pointer = 0;

void init() {
    configureLib(); 
    s_static_pointer = calculateFancyStuff(x,y,z);
    regptr(s_static_pointer);
}

bool s_init = init(), true; // just run init() before anything else

Foo::Foo() {
  s_static_pointer->doStuff(); // works properly
}

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

  if (something) dothis(), dothat(), x++;

هذا يعادل

  if (something) { dothis(); dothat(); x++; }

لا يتعلق الأمر بـ "الكتابة بشكل أقل"، بل يبدو الأمر واضحًا جدًا في بعض الأحيان.

الحلقات أيضًا هي هكذا:

while(true) x++, y += 5;

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

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

int ans = isRunning() ? total += 10, newAnswer(total) : 0;

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

كان الكود الخاص به سريعًا جدًا ولكن لا يمكن صيانته، ويسعدني أنني لم أعد مضطرًا للعمل معه بعد الآن.

لقد استخدمته للماكرو "لتعيين قيمة من أي نوع لمخزن مؤقت للإخراج يُشار إليه بحرف*، ثم قم بزيادة المؤشر بالعدد المطلوب من البايتات"، مثل هذا:

#define ASSIGN_INCR(p, val, type)  ((*((type) *)(p) = (val)), (p) += sizeof(type))

استخدام عامل الفاصلة يعني أنه يمكن استخدام الماكرو في التعبيرات أو كعبارات حسب الرغبة:

if (need_to_output_short)
    ASSIGN_INCR(ptr, short_value, short);

latest_pos = ASSIGN_INCR(ptr, int_value, int);

send_buff(outbuff, (int)(ASSIGN_INCR(ptr, last_value, int) - outbuff));

لقد قلل من بعض الكتابة المتكررة ولكن عليك توخي الحذر حتى لا تصبح غير قابلة للقراءة.

يرجى الاطلاع على نسختي الطويلة جدًا من هذه الإجابة هنا.

يمكن أن يكون مفيدًا لـ "Code Golf":

كود جولف:لعب المكعبات

ال , في if(i>0)t=i,i=0; يحفظ حرفين.

يحتوي qemu على بعض التعليمات البرمجية التي تستخدم عامل الفاصلة داخل الجزء الشرطي من حلقة for (انظر QTAILQ_FOREACH_SAFE في qemu-queue.h).وما فعلوه يتلخص في ما يلي:

#include <stdio.h>

int main( int argc, char* argv[] ){
  int x = 0, y = 0;

  for( x = 0; x < 3 && (y = x+1,1); x = y ){
    printf( "%d, %d\n", x, y );
  }

  printf( "\n%d, %d\n\n", x, y );

  for( x = 0, y = x+1; x < 3; x = y, y = x+1 ){
    printf( "%d, %d\n", x, y );
  }

  printf( "\n%d, %d\n", x, y );
  return 0;
}

...مع الإخراج التالي:

0, 1
1, 2
2, 3

3, 3

0, 1
1, 2
2, 3

3, 4

الإصدار الأول من هذه الحلقة له التأثيرات التالية:

  • إنه يتجنب القيام بمهمتين، وبالتالي تقل فرص خروج التعليمات البرمجية من المزامنة
  • منذ أن يستخدم &&, ، لا يتم تقييم المهمة بعد التكرار الأخير
  • نظرًا لعدم تقييم المهمة، فلن تحاول إلغاء العنصر التالي في قائمة الانتظار عندما يكون في النهاية (في كود qemu، وليس في الكود أعلاه).
  • داخل الحلقة، لديك حق الوصول إلى العنصر الحالي والتالي

وجدته في تهيئة الصفيف:

في C ماذا يحدث بالضبط إذا استخدمت () لتهيئة مصفوفة ذات بعد مزدوج بدلاً من {}؟

عندما أقوم بتهيئة مصفوفة a[][]:

int a[2][5]={(8,9,7,67,11),(7,8,9,199,89)};

ثم قم بعرض عناصر المصفوفة.

انا حصلت:

11 89 0 0 0 
0 0 0 0 0
مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top