سؤال

أنا جديد على برمجة C ++ ، لكن لدي خبرة في Java. أحتاج إلى إرشادات حول كيفية تمرير الكائنات إلى الوظائف في C ++.

هل أحتاج إلى تمرير مؤشرات أو مراجع أو قيم غير مؤشر وغير مرجعية؟ أتذكر في Java أنه لا توجد مثل هذه المشكلات لأننا نمر فقط المتغير الذي يحمل الإشارة إلى الكائنات.

سيكون من الرائع أن تشرح أيضًا مكان استخدام كل من هذه الخيارات.

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

المحلول

قواعد الإبهام C ++ 11:

يمر بالقيمة, ، باستثناء متى

  1. لا تحتاج إلى ملكية الكائن وسيقوم بالاسم المستعار البسيط ، وفي هذه الحالة يمر بجانب const المرجعي,
  2. يجب أن تحور الكائن ، وفي هذه الحالة ، استخدام تمر من قبل غيرconst مرجع Lvalue,
  3. تقوم بتمرير كائنات من الفئات المشتقة كطبقات أساسية ، وفي هذه الحالة تحتاج إلى ذلك تمرير بالرجوع إليه. (استخدم القواعد السابقة لتحديد ما إذا كنت ستمرير const مرجع أم لا.)

لا يُنصح بالمرور عن طريق المؤشر أبدًا. من الأفضل التعبير عن المعلمات الاختيارية على أنها أ std::optional (boost::optional ل std libs الأقدم) ، والمستعار يتم بشكل جيد بالرجوع إليها.

تحرك C ++ 11 من الدلالات تجعل المرور والعودة بالقيمة أكثر جاذبية حتى بالنسبة للكائنات المعقدة.


قواعد الإبهام C ++ 03:

تمرير الحجج بواسطة const المرجعي, ، باستثناء متى

  1. يجب تغييرها داخل الوظيفة ويجب أن تنعكس مثل هذه التغييرات في الخارج ، وفي هذه الحالة تمرير من غيرconst المرجعي
  2. يجب أن تكون الوظيفة قابلة للاستدعاء دون أي وسيطة ، وفي هذه الحالة تمر حسب المؤشر ، بحيث يمكن للمستخدمين المرور NULL/0/nullptr في حين أن؛ قم بتطبيق القاعدة السابقة لتحديد ما إذا كان يجب عليك تمر بواسطة مؤشر إلى const جدال
  3. إنها من أنواع مدمجة ، والتي يمكن أن تكون مرت بواسطة نسخة
  4. يجب تغييرها داخل الوظيفة وينبغي هذه التغييرات ليس تنعكس في الخارج ، وفي هذه الحالة يمكنك مرر بنسخ (سيكون البديل هو تمرير وفقًا للقواعد السابقة وإعداد نسخة داخل الوظيفة)

(هنا ، يسمى "Pass by Value" "Pass by Copy" ، لأن المرور حسب القيمة ينشئ دائمًا نسخة في C ++ 03)


هناك ما هو أكثر من ذلك ، لكن قواعد المبتدئين هذه ستجعلك بعيدًا.

نصائح أخرى

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

تمرير بالرجوع إليه

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

تمرير بالقيمة (والمؤشر بتمرير)

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

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

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

إضافة const إلى المعادلة

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

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

من البديهيات

هذه بعض القواعد الأساسية التي يجب اتباعها:

  • تفضل قيمة التمرير على أساس الأنواع البدائية
  • تفضل مرجع مرار على حدة مع إشارات إلى ثابت لأنواع أخرى
  • إذا كانت الوظيفة تحتاج إلى تعديل الوسيطة ، فاستخدم مرجعًا مرجعًا
  • إذا كانت الوسيطة اختيارية ، فاستخدم مرورًا (إلى ثابت إذا لم يتم تعديل القيمة الاختيارية)

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

ملاحظة جانبية

من المهم الإصرار على أهمية الفرق بين مراجع C ++ و Java. في المراجع C ++ هي مثيل الكائن من الناحية المفاهيمية ، وليس ملحقًا به. أبسط مثال هو تنفيذ وظيفة المبادلة:

// C++
class Type; // defined somewhere before, with the appropriate operations
void swap( Type & a, Type & b ) {
   Type tmp = a;
   a = b;
   b = tmp;
}
int main() {
   Type a, b;
   Type old_a = a, old_b = b;
   swap( a, b );
   assert( a == old_b );
   assert( b == old_a ); 
}

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

public class C {
   // ...
   public static void swap( C a, C b ) {
      C tmp = a;
      a = b;
      b = tmp;
   }
   public static void main( String args[] ) {
      C a = new C();
      C b = new C();
      C old_a = a;
      C old_b = b;
      swap( a, b ); 
      // a and b remain unchanged a==old_a, and b==old_b
   }
}

سيقوم إصدار Java من الكود بتعديل نسخ المراجع داخليًا ، ولكنه لن يعدل الكائنات الفعلية خارجيًا. مراجع Java هي مؤشرات C دون حساب مؤشر يتم تمريرها بواسطة القيمة إلى وظائف.

هناك عدة حالات يجب مراعاتها.

المعلمة المعلمين ("Out" و "In/Out")

void modifies(T &param);
// vs
void modifies(T *param);

هذه الحالة تدور حول الأناقة: هل تريد أن تبدو الكود استدعاء (OBJ) أو استدعاء (و OBJ)؟ ومع ذلك ، هناك نقطتان يهم الفرق: الحالة الاختيارية أدناه ، وتريد استخدام مرجع عند الحمل الزائد.

... واختيار

void modifies(T *param=0);  // default value optional, too
// vs
void modifies();
void modifies(T &param);

المعلمة لم يتم تعديلها

void uses(T const &param);
// vs
void uses(T param);

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

... واختيار

void uses(T const *param=0);  // default value optional, too
// vs
void uses();
void uses(T const &param);  // or optional(T param)

هناك اختلاف أقل هنا بين جميع المواقف ، لذلك اختر أيهما يجعل حياتك أسهل.

const حسب القيمة هو تفاصيل التنفيذ

void f(T);
void f(T const);

هذه التصريحات هي في الواقع نفس الوظيفة بالضبط! عند المرور حسب القيمة ، تكون Const مجرد تفاصيل تنفيذ. حاول:

void f(int);
void f(int const) { /* implements above function, not an overload */ }

typedef void NC(int);       // typedefing function types
typedef void C(int const);

NC *nc = &f;  // nc is a function pointer
C *c = nc;    // C and NC are identical types

تمرير بالقيمة:

void func (vector v)

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

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

تمرير عن طريق مرجع const:

void func (const vector& v);

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

الجانب السلبي هو سلامة مؤشرات الترابط: أي تغيير تم إجراؤه على الكائن الأصلي بواسطة مؤشر ترابط آخر سيظهر داخل الوظيفة بينما لا يزال ينفذ.

تمرير عن طريق المرجع غير المؤلف:

void func (vector& v)

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

تماما مثل حالة مرجع const ، هذا ليس آمن مؤشر ترابط.

تمرير بواسطة Const Pointer:

void func (const vector* vp);

من الناحية الوظيفية مثل التمرير بواسطة const-reference باستثناء بناء الجملة المختلفة ، بالإضافة إلى حقيقة أن وظيفة الاتصال يمكن أن تمر مؤشر فارغ للإشارة إلى أنه لا يوجد لديه بيانات صالحة تمريرها.

ليس آمن الخيط.

تمرير بواسطة المؤشر غير المؤشر:

void func (vector* vp);

على غرار المرجع غير المتواصل. عادةً ما يقوم المتصل بتعيين المتغير إلى NULL عندما لا يُفترض أن تكتب الوظيفة قيمة. وينظر إلى هذه الاتفاقية في العديد من واجهات برمجة التطبيقات GLIBC. مثال:

void func (string* str, /* ... */) {
    if (str != NULL) {
        *str = some_value; // assign to *str only if it's non-null
    }
}

تماما مثل كل تمرير بالرجوع/المؤشر ، وليس آمن مؤشر الترابط.

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

Class CPlusPlusJavaFunctionality {
    public:
       CPlusPlusJavaFunctionality(){
         attribute = new int;
         *attribute = value;
       }

       void setValue(int value){
           *attribute = value;
       }

       void getValue(){
          return *attribute;
       }

       ~ CPlusPlusJavaFuncitonality(){
          delete(attribute);
       }

    private:
       int *attribute;
}

void changeObjectAttribute(CPlusPlusJavaFunctionality obj, int value){
   int* prt = obj.attribute;
   *ptr = value;
}

int main(){

   CPlusPlusJavaFunctionality obj;

   obj.setValue(10);

   cout<< obj.getValue();  //output: 10

   changeObjectAttribute(obj, 15);

   cout<< obj.getValue();  //output: 15
}

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

هناك ثلاث طرق لتمرير كائن إلى دالة كمعلمة:

  1. تمرير بالرجوع إليه
  2. تمرير بالقيمة
  3. إضافة ثابت في المعلمة

اذهب من خلال المثال التالي:

class Sample
{
public:
    int *ptr;
    int mVar;

    Sample(int i)
    {
        mVar = 4;
        ptr = new int(i);
    }

    ~Sample()
    {
        delete ptr;
    }

    void PrintVal()
    {
        cout << "The value of the pointer is " << *ptr << endl
             << "The value of the variable is " << mVar;
   }
};

void SomeFunc(Sample x)
{
cout << "Say i am in someFunc " << endl;
}


int main()
{

  Sample s1= 10;
  SomeFunc(s1);
  s1.PrintVal();
  char ch;
  cin >> ch;
}

انتاج:

قل أنا في بعض
قيمة المؤشر هي -17891602
قيمة المتغير 4

فيما يلي طرق تمرير الوسائط/المعلمات للعمل في C ++.

1. بالقيمة.

// passing parameters by value . . .

void foo(int x) 
{
    x = 6;  
}

2. بالرجوع.

// passing parameters by reference . . .

void foo(const int &x) // x is a const reference
{
    x = 6;  
}

// passing parameters by const reference . . .

void foo(const int &x) // x is a const reference
{
    x = 6;  // compile error: a const reference cannot have its value changed!
}

3. عن طريق الكائن.

class abc
{
    display()
    {
        cout<<"Class abc";
    }
}


// pass object by value
void show(abc S)
{
    cout<<S.display();
}

// pass object by reference
void show(abc& S)
{
    cout<<S.display();
}
مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top