c++ - ما هي الاختلافات بين متغير المؤشر ومتغير مرجعي في C ++؟


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

ولكن ما هي الاختلافات؟

ملخص من الإجابات والروابط أدناه:

  1. يمكن إعادة تعيين المؤشر أي عدد من المرات في حين أن مرجع لا يمكن إعادة الجلوس بعد ملزمة.
  2. يمكن أن تشير النقاط إلى أي مكان ( NULL )، في حين تشير الإشارة دائما إلى كائن.
  3. لا يمكنك أن تأخذ عنوان مرجع كما يمكنك مع مؤشرات.
  4. ليس هناك "علم الحساب المرجعية" (ولكن يمكنك أن تأخذ عنوان كائن أشار من قبل مرجع والقيام الحساب الحساب على ذلك كما في &obj + 5 ).

لتوضيح مفهوم خاطئ:

معيار C ++ حذر جدا لتجنب إملاء كيف يجب على مترجم تنفيذ المراجع، ولكن كل مترجم C ++ تنفذ مراجع كمؤشرات. أي إعلان مثل:

int &ri = i;

إذا لم يتم تحسينه بعيدا تماما ، يخصص نفس كمية التخزين كمؤشر، ويضع عنوان i في تلك السعة التخزينية.

لذلك، مؤشر ومرجعية على حد سواء تحتل نفس الكمية من الذاكرة.

كقاعدة عامة،

  • استخدم المراجع في معلمات الوظیفة وأنواع العودة لتعرف الواجھات المفیدة والمستندات ذاتیة التوثیق.
  • استخدام مؤشرات لتنفيذ الخوارزميات وهياكل البيانات.

قراءة مثيرة للاهتمام:



Answers


  1. يمكن إعادة تعيين المؤشر:

    int x = 5;
    int y = 6;
    int *p;
    p =  &x;
    p = &y;
    *p = 10;
    assert(x == 5);
    assert(y == 10);

    لا يمكن الإشارة، ويجب تعيينها عند التهيئة:

    int x = 5;
    int y = 6;
    int &r = x;
  2. مؤشر لديه عنوان الذاكرة الخاصة بها والحجم على المكدس (4 بايت على x86)، في حين أن مرجع يشارك نفس عنوان الذاكرة (مع المتغير الأصلي) ولكن أيضا يستغرق بعض المساحة على المكدس. وبما أن المرجع له نفس عنوان المتغير الأصلي نفسه، فمن الآمن التفكير في مرجع كاسم آخر لنفس المتغير. ملاحظة: ما يشير المؤشر إلى يمكن أن تكون على كومة أو كومة. سبق الإشارة. ادعاءي في هذا البيان ليس أن المؤشر يجب أن يشير إلى المكدس. المؤشر هو مجرد متغير يحمل عنوان الذاكرة. هذا المتغير موجود على المكدس. وبما أن المرجع له مساحة خاصة به على المكدس، وبما أن العنوان هو نفسه المتغير الذي يشير إليه. المزيد عن كومة مقابل كومة الذاكرة المؤقتة . وهذا يعني أن هناك عنوانا حقيقيا للمرجعية التي لن يخبرك بها معدو البيانات.

    int x = 0;
    int &r = x;
    int *p = &x;
    int *p2 = &r;
    assert(p == p2);
  3. يمكن أن يكون لديك مؤشرات إلى مؤشرات إلى مؤشرات تقدم مستويات إضافية من غير المباشرة. في حين أن المراجع لا تقدم سوى مستوى واحد من غير المباشرة.

    int x = 0;
    int y = 0;
    int *p = &x;
    int *q = &y;
    int **pp = &p;
    pp = &q;//*pp = q
    **pp = 4;
    assert(y == 4);
    assert(x == 0);
  4. يمكن تعيين مؤشر nullptr مباشرة، في حين لا يمكن الإشارة. إذا حاولت بجد بما فيه الكفاية، وأنت تعرف كيف، يمكنك جعل عنوان nullptr مرجع. وبالمثل، إذا حاولت بجد بما فيه الكفاية يمكن أن يكون إشارة إلى مؤشر، ومن ثم يمكن أن تحتوي على مرجع nullptr .

    int *p = nullptr;
    int &r = nullptr; <--- compiling error
  5. يمكن للمؤشرات التكرار عبر مصفوفة، يمكنك استخدام ++ للانتقال إلى العنصر التالي الذي يشير إليه المؤشر، و + 4 للانتقال إلى العنصر 5. هذا بغض النظر عن حجم الكائن الذي يشير إليه المؤشر.

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

  7. المؤشر هو متغير يحمل عنوان ذاكرة. بغض النظر عن كيفية تنفيذ مرجع، مرجع لها نفس عنوان الذاكرة مثل العنصر الذي يشير.

  8. لا يمكن أن تكون محشوة المراجع في مصفوفة، في حين يمكن أن تكون المؤشرات (المذكورة من قبل المستخدمlitb)

  9. يمكن أن تكون ملزمة إشارات كونست إلى المؤقتين. لا يمكن للمؤشرات (بدون بعض الإتجاهات):

    const int &x = int(12); //legal C++
    int *y = &int(12); //illegal to dereference a temporary.

    وهذا يجعل const& أمنا للاستخدام في قوائم الوسيطة وهكذا دواليك.




ما هو مرجع C ++ ( للمبرمجين C )

ويمكن اعتبار إشارة كمؤشر ثابت (لا ينبغي الخلط بينه وبين مؤشر إلى قيمة ثابتة!) مع التلقائية غير المباشرة، أي سوف مترجم تطبيق * عامل بالنسبة لك.

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

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

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

النظر في البيان التالي من C ++ التعليمات :

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

ولكن إذا كان المرجع حقا هو الكائن، كيف يمكن أن يكون هناك المراجع التعلق؟ في اللغات غير المدارة، فإنه من المستحيل للإشارات إلى أن تكون أي 'أكثر أمانا' من المؤشرات - هناك عموما ليست مجرد وسيلة لقيم مستعار موثوق بها عبر حدود النطاق!

لماذا أعتبر C ++ مراجع مفيدة

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

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




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

std::string s1 = "123";
std::string s2 = "456";

std::string s3_copy = s1 + s2;
const std::string& s3_reference = s1 + s2;

في هذا المثال s3_copy نسخ الكائن المؤقت الذي هو نتيجة تسلسل. في حين أن s3_reference في جوهرها يصبح كائن مؤقت. انها حقا إشارة إلى كائن مؤقت أن لديها الآن نفس العمر كمرجع.

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




لقد نسيت الجزء الأكثر أهمية:

عضو الوصول مع مؤشرات يستخدم ->
عضو الوصول مع المراجع يستخدم .

foo.bar متفوقة بشكل واضح على foo->bar في نفس الطريقة التي في هو واضح بشكل واضح على إماكس :-)




خلافا للرأي الشعبي، فمن الممكن أن يكون مرجعا هو نول.

int * p = NULL;
int & r = *p;
r = 1;  // crash! (if you're lucky)

منح، فإنه من الصعب أن تفعل مع مرجع - ولكن إذا كنت تدير ذلك، عليك المسيل للدموع شعرك من محاولة للعثور عليه. المراجع ليست آمنة بطبيعتها في C ++!

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

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

مثالي أعلاه قصير ومفتعل. وهنا مثال أكثر واقعية.

class MyClass
{
    ...
    virtual void DoSomething(int,int,int,int,int);
};

void Foo(const MyClass & bar)
{
    ...
    bar.DoSomething(i1,i2,i3,i4,i5);  // crash occurs here due to memory access violation - obvious why?
}

MyClass * GetInstance()
{
    if (somecondition)
        return NULL;
    ...
}

MyClass * p = GetInstance();
Foo(*p);

أريد أن أكرر أن الطريقة الوحيدة للحصول على مرجع فارغ هي من خلال التعليمات البرمجية غير صحيح، وبمجرد أن يكون لديك تحصل على سلوك غير معروف. فإنه ليس من المنطقي للتحقق من وجود إشارة فارغة؛ على سبيل المثال يمكنك محاولة if(&bar==NULL)... ولكن المترجم قد تحسين البيان من الوجود! مرجع صالح لا يمكن أبدا أن يكون نول حتى من وجهة نظر المترجم في المقارنة هو دائما كاذبة، وأنه حر في القضاء على شرط if رمز ميت - وهذا هو جوهر السلوك غير معروف.

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

template<typename T>
T& ref(T* p)
{
    if (p == NULL)
        throw std::invalid_argument(std::string("NULL reference"));
    return *p;
}

MyClass * p = GetInstance();
Foo(ref(p));



وبصرف النظر عن السكر النحوي، والمرجعية هو مؤشر const ( ليس مؤشر إلى const ). يجب تحديد ما يشير إليه عند تعريف المتغير المرجعي، ولا يمكنك تغييره لاحقا.




في الواقع، مرجع ليس حقا مثل مؤشر.

يحتفظ مترجم "المراجع" للمتغيرات، ربط اسم مع عنوان الذاكرة. هذا هو وظيفتها لترجمة أي اسم متغير إلى عنوان الذاكرة عند تجميع.

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

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

الآن بعض تفسير رمز حقيقي:

int a = 0;
int& b = a;

هنا أنت لا خلق متغير آخر يشير إلى a ؛ كنت مجرد إضافة اسم آخر لمحتوى الذاكرة عقد قيمة a . هذه الذاكرة لديها الآن اسمين، a و b ، ويمكن معالجتها باستخدام أي من الاسم.

void increment(int& n)
{
    n = n + 1;
}

int a;
increment(a);

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

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




المراجع هي مشابهة جدا للمؤشرات، ولكنها وضعت خصيصا لتكون مفيدة لتحسين المجمعين.

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

كمثال:

void maybeModify(int& x); // may modify x in some way

void hurtTheCompilersOptimizer(short size, int array[])
{
    // This function is designed to do something particularly troublesome
    // for optimizers. It will constantly call maybeModify on array[0] while
    // adding array[1] to array[2]..array[size-1]. There's no real reason to
    // do this, other than to demonstrate the power of references.
    for (int i = 2; i < (int)size; i++) {
        maybeModify(array[0]);
        array[i] += array[1];
    }
}

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

void hurtTheCompilersOptimizer(short size, int array[])
{
    // Do the same thing as above, but instead of accessing array[1]
    // all the time, access it once and store the result in a register,
    // which is much faster to do arithmetic with.
    register int a0 = a[0];
    register int a1 = a[1]; // access a[1] once
    for (int i = 2; i < (int)size; i++) {
        maybeModify(a0); // Give maybeModify a reference to a register
        array[i] += a1;  // Use the saved register value over and over
    }
    a[0] = a0; // Store the modified a[0] back into the array
}

لجعل هذا التحسين، فإنه يحتاج إلى إثبات أن لا شيء يمكن تغيير صفيف [1] أثناء المكالمة. هذا سهل إلى حد ما. أنا لا أقل من 2، لذلك مجموعة [i] لا يمكن أبدا الرجوع إلى مجموعة [1]. يعطى مايبيموديفي () a0 كمرجع (صفيف التسمية [0]). ولأنه لا يوجد حساب "مرجعي"، يتعين على المترجم أن يثبت أن مايبموديفي لا يحصل على عنوان x، وقد ثبت أن لا شيء يغير المصفوفة [1].

كما يجب أن تثبت أنه لا توجد طرق يمكن للمكالمة المستقبلية قراءة / كتابة [0] في حين لدينا نسخة سجل مؤقت منه في a0. هذا هو في كثير من الأحيان تافهة لإثبات، لأنه في كثير من الحالات من الواضح أن إشارة أبدا تخزينها في هيكل دائم مثل مثيل الطبقة.

الآن تفعل الشيء نفسه مع مؤشرات

void maybeModify(int* x); // May modify x in some way

void hurtTheCompilersOptimizer(short size, int array[])
{
    // Same operation, only now with pointers, making the
    // optimization trickier.
    for (int i = 2; i < (int)size; i++) {
        maybeModify(&(array[0]));
        array[i] += array[1];
    }
}

السلوك هو نفسه. فقط الآن هو أصعب بكثير لإثبات أن مايبيموديفي لا تعديل أي وقت مضى صفيف [1]، لأننا بالفعل أعطاه مؤشر. القط هو من الحقيبة. الآن يجب أن تفعل دليل أكثر صعوبة: تحليل ثابت من مايبيموديفي لإثبات أنه يكتب أبدا ل & س + 1. كما أن لديها لإثبات أنه أبدا يحفظ مؤشر التي يمكن أن تشير إلى مجموعة [0]، الذي هو مجرد كما صعبة.

المترجمين الحديثة هي الحصول على أفضل وأفضل في تحليل ثابت، ولكن من الجميل دائما لمساعدتهم على واستخدام المراجع.

وبطبيعة الحال، وبغض النظر عن هذه التحسينات الذكية، فإن المترجمين سيحولون الإشارات إلى مؤشرات عند الحاجة.




مرجع لا يمكن أبدا NULL .




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

النظر في هذين شظايا البرنامج. في البداية، نقوم بتعيين مؤشر واحد إلى آخر:

int ival = 1024, ival2 = 2048;
int *pi = &ival, *pi2 = &ival2;
pi = pi2;    // pi now points to ival2

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

int &ri = ival, &ri2 = ival2;
ri = ri2;    // assigns ival2 to ival

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




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

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

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




المرجع هو اسم مستعار لمتغير آخر بينما يحمل المؤشر عنوان الذاكرة لمتغير. وتستخدم إشارات عموما كمعلمات وظيفة بحيث الكائن الذي تم تمريره ليس نسخة ولكن الكائن نفسه.

    void fun(int &a, int &b); // A common usage of references.
    int a = 0;
    int &b = a; // b is an alias for a. Not so common to use. 



ويستند هذا على البرنامج التعليمي . ما هو مكتوب يجعل الأمر أكثر وضوحا:

>>> The address that locates a variable within memory is
    what we call a reference to that variable. (5th paragraph at page 63)

>>> The variable that stores the reference to another
    variable is what we call a pointer. (3rd paragraph at page 64)

ببساطة أن نتذكر أنه،

>>> reference stands for memory location
>>> pointer is a reference container (Maybe because we will use it for
several times, it is better to remember that reference.)

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

نظرة على البيان التالي

int Tom(0);
int & alias_Tom = Tom;

alias_Tomيمكن أن يفهم على أنه alias of a variable(مختلفة مع typedefالذي هو alias of a type) Tom. فلا بأس أيضا أن ننسى المصطلحات مثل هذا البيان هو خلق مرجعية Tom.




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

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

فمثلا:

class scope_test
{
public:
    ~scope_test() { printf("scope_test done!\n"); }
};

...

{
    const scope_test &test= scope_test();
    printf("in scope\n");
}

ستطبع:

in scope
scope_test done!

هذه هي الآلية التي تسمح لغة ScopeGuard للعمل.




إشارة إلى مؤشر ممكنة في C ++، ولكن العكس غير ممكن يعني مؤشر إلى مرجع غير ممكن. إشارة إلى مؤشر يوفر جملة أنظف لتعديل المؤشر. انظروا إلى هذا المثال:

#include<iostream>
using namespace std;

void swap(char * &str1, char * &str2)
{
  char *temp = str1;
  str1 = str2;
  str2 = temp;
}

int main()
{
  char *str1 = "Hi";
  char *str2 = "Hello";
  swap(str1, str2);
  cout<<"str1 is "<<str1<<endl;
  cout<<"str2 is "<<str2<<endl;
  return 0;
}

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

#include<stdio.h>
/* Swaps strings by swapping pointers */
void swap1(char **str1_ptr, char **str2_ptr)
{
  char *temp = *str1_ptr;
  *str1_ptr = *str2_ptr;
  *str2_ptr = temp;
}

int main()
{
  char *str1 = "Hi";
  char *str2 = "Hello";
  swap1(&str1, &str2);
  printf("str1 is %s, str2 is %s", str1, str2);
  return 0;
}

زيارة التالية لمزيد من المعلومات حول الإشارة إلى المؤشر:

كما قلت، وهو مؤشر إلى مرجع غير ممكن. جرب البرنامج التالي:

#include <iostream>
using namespace std;

int main()
{
   int x = 10;
   int *ptr = &x;
   int &*ptr1 = ptr;
}



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

int& j = i;

يصبح داخليا

int* const j = &i;



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

التحيات، وrzej




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

#include <iostream>
int main(int argc, char** argv) {
    // Create a string on the heap
    std::string *str_ptr = new std::string("THIS IS A STRING");
    // Dereference the string on the heap, and assign it to the reference
    std::string &str_ref = *str_ptr;
    // Not even a compiler warning! At least with gcc
    // Now lets try to print it's value!
    std::cout << str_ref << std::endl;
    // It works! Now lets print and compare actual memory addresses
    std::cout << str_ptr << " : " << &str_ref << std::endl;
    // Exactly the same, now remember to free the memory on the heap
    delete str_ptr;
}

التي مخرجات هذا:

THIS IS A STRING
0xbb2070 : 0xbb2070

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

int main(int argc, char** argv) {
    // In the actual new declaration let immediately de-reference and assign it to the reference
    std::string &str_ref = *(new std::string("THIS IS A STRING"));
    // Once again, it works! (at least in gcc)
    std::cout << str_ref;
    // Once again it prints fine, however we have no pointer to the heap allocation, right? So how do we free the space we just ignorantly created?
    delete &str_ref;
    /*And, it works, because we are taking the memory address that the reference is
    storing, and deleting it, which is all a pointer is doing, just we have to specify
    the address with '&' whereas a pointer does that implicitly, this is sort of like
    calling delete &(*str_ptr); (which also compiles and runs fine).*/
}

التي مخرجات هذا:

THIS IS A STRING

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

وبعبارة أخرى، في إشارة ليست سوى مؤشر لديها ميكانيكا مؤشر المستخرجة بعيدا، مما يجعله أكثر أمانا وأسهل للاستخدام (لا مؤشر الرياضيات عرضي، لا الخلط بين و'.' '->'، وما إلى ذلك)، على افتراض انك لا تحاول أي هراء مثل الأمثلة التي ذكرتها أعلاه؛)

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

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

تذكر، الأمثلة التي ذكرتها ليست سوى ذلك، الأمثلة التي تبين ما هي إشارة أعلاه، وكنت لا تريد أن استخدام مرجع في هذه الطرق! للاستخدام السليم للإشارة هناك الكثير من الأجوبة هنا بالفعل أن يضرب المسمار على رأسه




يمكنني استخدام الإشارات ما لم أحتاج أي من هذه:

  • مؤشرات فارغة يمكن استخدام كقيمة الحارس، في كثير من الأحيان وسيلة رخيصة لتجنب الحمولة الزائدة وظيفة أو استخدام منطقي.

  • يمكنك القيام بعملية حسابية على المؤشر. فمثلا،p += offset;




قد يساعد هذا البرنامج في فهم الجواب من السؤال. هذا هو برنامج بسيط إشارة "ي" ومؤشر "PTR"، مشيرا إلى المتغير "س".

#include<iostream>

using namespace std;

int main()
{
int *ptr=0, x=9; // pointer and variable declaration
ptr=&x; // pointer to variable "x"
int & j=x; // reference declaration; reference to variable "x"

cout << "x=" << x << endl;

cout << "&x=" << &x << endl;

cout << "j=" << j << endl;

cout << "&j=" << &j << endl;

cout << "*ptr=" << *ptr << endl;

cout << "ptr=" << ptr << endl;

cout << "&ptr=" << &ptr << endl;
    getch();
}

تشغيل البرنامج وإلقاء نظرة على الانتاج وعليك أن نفهم.

أيضا، تجنيب 10 دقيقة ومشاهدة هذا الفيديو: https://www.youtube.com/watch؟v=rlJrrGV0iOg




وثمة فرق آخر هو أنه يمكن أن يكون المؤشرات إلى نوع الفراغ (وهذا يعني المؤشر إلى أي شيء) ولكن يحظر على كل ما يشير إلى باطلا.

int a;
void * p = &a; // ok
void & p = a;  //  forbidden

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




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

المراجع هي أقل قوة من مؤشرات ل

1) بمجرد إنشاء إشارة، فإنه لا يمكن أن يتم في وقت لاحق إلى مرجع كائن آخر. لا يمكن reseated. ويتم ذلك غالبا مع مؤشرات.

2) المراجع لا يمكن أن يكون NULL. وغالبا ما تكون مؤشرات NULL للإشارة إلى أنها لا يشير إلى أي شيء صالح.

3) يجب تهيئة إشارة عندما أعلن. لا يوجد مثل هذا التقييد مع مؤشرات

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

مراجع أكثر أمانا وأسهل للاستخدام:

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

2) أسهل للاستخدام: المراجع لا تحتاج مشغل dereferencing للوصول إلى القيمة. أنها يمكن أن تستخدم مثل المتغيرات العادية. وهناك حاجة المشغل '&' فقط في وقت الإعلان. أيضا، يمكن الوصول إليها من أعضاء مرجع كائن مع مشغل نقطة ( '.')، على عكس المؤشرات حيث المشغل السهم - هو مطلوب (>) لأعضاء الوصول.

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




أيضا، في إشارة أن معلمة إلى الدالة التي يتم inlined يمكن التعامل معها بشكل مختلف من المؤشر.

void increment(int *ptrint) { (*ptrint)++; }
void increment(int &refint) { refint++; }
void incptrtest()
{
    int testptr=0;
    increment(&testptr);
}
void increftest()
{
    int testref=0;
    increment(testref);
}

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

وبطبيعة الحال، للوظائف التي لم يتم inlined المؤشر والمرجعية تولد نفس رمز وانها دائما أفضل لتمرير intrinsics من حيث القيمة من بالرجوع إذا لم يتم تعديلها وإرجاعها بواسطة الدالة.




ربما بعض الاستعارات سوف تساعد. في سياق screenspace سطح المكتب -

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



آخر استخدام للاهتمام من المراجع هو توفير الوسيطة الافتراضية من نوع المعرفة من قبل المستخدم:

class UDT
{
public:
   UDT() : val_d(33) {};
   UDT(int val) : val_d(val) {};
   virtual ~UDT() {};
private:
   int val_d;
};

class UDT_Derived : public UDT
{
public:
   UDT_Derived() : UDT() {};
   virtual ~UDT_Derived() {};
};

class Behavior
{
public:
   Behavior(
      const UDT &udt = UDT()
   )  {};
};

int main()
{
   Behavior b; // take default

   UDT u(88);
   Behavior c(u);

   UDT_Derived ud;
   Behavior d(ud);

   return 1;
}

تستخدم نكهة الافتراضية "إشارة CONST ربط مؤقت" الجانب المراجع.




والفرق هو أن غير ثابت مؤشر متغير (وينبغي عدم الخلط بينه وبين مؤشر ثابت) قد يتغير في وقت ما أثناء تنفيذ البرنامج، يتطلب دلالات المؤشر لاستخدامها (و، *) المشغلين، في حين يمكن تعيين مراجع على التهيئة فقط (هذا هو السبب الذي يمكن أن يحدد لها في قائمة مهيئ المنشئ فقط، ولكن ليس بطريقة أو بأخرى آخر) واستخدام دلالات قيمة الوصول العادية. في الأساس أدخلت مراجع للسماح الدعم لمشغلي الحمولة الزائدة كما قرأت في بعض الكتب القديمة جدا. كما ذكر شخص ما في هذا الموضوع - مؤشر يمكن تعيين إلى 0 أو ما القيمة التي تريدها. 0 (NULL، nullptr) يعني أن المؤشر تتم تهيئة مع أي شيء. ومن خطأ لdereference مؤشر فارغة. ولكن في الواقع قد يحتوي على مؤشر قيمة لا يشير إلى موقع الذاكرة الصحيح.المراجع بدورها ليس محاولة للسماح للمستخدم لتهيئة إشارة إلى شيء لا يمكن أن يكون مرجعا يرجع ذلك إلى حقيقة أن كنت دائما على تقديم rvalue من النوع الصحيح لذلك. على الرغم من أن هناك الكثير من الطرق لجعل تتم تهيئة متغير إشارة إلى موقع ذاكرة خطأ - أنه من الأفضل بالنسبة لك لا لحفر هذا في عمق التفاصيل. على مستوى الجهاز على حد سواء المؤشر وعمل مرجعي موحد - عن طريق المؤشرات. دعنا نقول في المراجع الأساسية هي نحوي السكر. مراجع rvalue مختلفة لهذا - أنها كومة طبيعي / الأجسام الكومة.على الرغم من أن هناك الكثير من الطرق لجعل تتم تهيئة متغير إشارة إلى موقع ذاكرة خطأ - أنه من الأفضل بالنسبة لك لا لحفر هذا في عمق التفاصيل. على مستوى الجهاز على حد سواء المؤشر وعمل مرجعي موحد - عن طريق المؤشرات. دعنا نقول في المراجع الأساسية هي نحوي السكر. مراجع rvalue مختلفة لهذا - أنها كومة طبيعي / الأجسام الكومة.على الرغم من أن هناك الكثير من الطرق لجعل تتم تهيئة متغير إشارة إلى موقع ذاكرة خطأ - أنه من الأفضل بالنسبة لك لا لحفر هذا في عمق التفاصيل. على مستوى الجهاز على حد سواء المؤشر وعمل مرجعي موحد - عن طريق المؤشرات. دعنا نقول في المراجع الأساسية هي نحوي السكر. مراجع rvalue مختلفة لهذا - أنها كومة طبيعي / الأجسام الكومة.




الفرق بين المؤشر والمرجعية

مؤشر يمكن تهيئة 0 وإشارة لا. في الواقع، يجب الإشارة أيضا يشير إلى كائن، ولكن مؤشر يمكن أن يكون مؤشر فارغة:

int* p = 0;

ولكننا لا يمكن أن يكون لها int& p = 0;وأيضاint& p=5 ; .

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

Int x = 0;
Int y = 5;
Int& p = x;
Int& p1 = y;

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

وثمة فرق آخر هو أن المؤشر يمكن أن نشير إلى كائن آخر ولكن إشارة يتم الرجوع دائما إلى نفس الكائن، دعونا نلقي هذا المثال:

Int a = 6, b = 5;
Int& rf = a;

Cout << rf << endl; // The result we will get is 6, because rf is referencing to the value of a.

rf = b;
cout << a << endl; // The result will be 5 because the value of b now will be stored into the address of a so the former value of a will be erased

نقطة أخرى: عندما يكون لدينا قالب مثل هذا النوع القالب STL من قالب فئة سيعود دائما مرجعا، وليس مؤشر، لجعل القراءة سهلة أو إسناد قيمة جديدة باستخدام مشغل []:

Std ::vector<int>v(10); // Initialize a vector with 10 elements
V[5] = 5; // Writing the value 5 into the 6 element of our vector, so if the returned type of operator [] was a pointer and not a reference we should write this *v[5]=5, by making a reference we overwrite the element by using the assignment "="



أشعر أن هناك نقطة اخرى لم يتم تغطيتها هنا.

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

في حين أن هذا قد يبدو سطحيا، وأعتقد أن هذا العقار حاسمة لعدد من الميزات C ++، على سبيل المثال:

  • القوالب . منذ المعلمات القالب هي كتبته بطة، والخصائص النحوية من نوع هو كل ما يهم، لذلك غالبا ما يكون نفس القالب يمكن استخدامها مع كل من Tو T&.
    (أو std::reference_wrapper<T>التي لا تزال تعتمد على الزهر ضمني T&)
    قوالب التي تغطي كلا T&و T&&، بل هي أكثر شيوعا.

  • Lvalues . النظر في بيان str[0] = 'X';دون مراجع انها ستعمل فقط لج سلاسل ( char* str). إعادة الطابع بالرجوع يسمح الطبقات المعرفة لديهم نفس التدوين.

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

  • الزائدة المشغل . مع الإشارة أنه من الممكن إدخال المراوغة لنداء مشغل - ويقول، operator+(const T& a, const T& b)مع الإبقاء على نفس أقحم التدوين. هذا وتعمل أيضا من أجل وظائف طاقتها العادية.

تمكن هذه النقاط جزءا كبيرا من C ++ والمكتبة القياسية ولذلك فإن هذا لا بأس به عقارية ضخمة من المراجع.




أنا دائما تقرر من قبل هذا الحكم من المبادئ التوجيهية الأساسية C ++:

تفضل T * على T & عندما "لا حجة" هو خيار صحيح




هناك مهم جدا مختلفة غير التقنية بين المؤشرات والمراجع: حجة التي تم تمريرها إلى وظيفة من قبل مؤشر هو أكثر وضوحا بكثير من حجة التي تم تمريرها إلى وظيفة بالرجوع غير CONST. فمثلا:

void fn1(std::string s);
void fn2(const std::string& s);
void fn3(std::string& s);
void fn4(std::string* s);

void bar() {
    std::string x;
    fn1(x);  // Cannot modify x
    fn2(x);  // Cannot modify x (without const_cast)
    fn3(x);  // CAN modify x!
    fn4(&x); // Can modify x (but is obvious about it)
}

مرة أخرى في C، وهي الدعوة التي تبدو وكأنها fn(x)لا يمكن إلا أن تمر من حيث القيمة، لذلك بالتأكيد لا يمكن تعديل x. لتعديل حجة كنت بحاجة لتمرير مؤشر fn(&x). حتى إذا لم يكن سبقتها مشادة قبل &كنت أعرف أنه لن يتم تعديل. (والعكس، &يعني تعديل، لم يكن صحيحا لكنت في بعض الأحيان لتمرير الهياكل الكبيرة للقراءة فقط من constالمؤشر.)

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