
مرحبا. في إحدى مراجعات الكود ، صادفت فكرة أن الكثيرين ، وما الذي أخفيه ، لا نفهم جيدًا متى نستخدم الكلمة الأساسية الثابتة . في هذه المقالة ، أود مشاركة معرفتي ومعلوماتي المتعلقة بالكلمة الأساسية الثابتة . , , ++. /++ . , static ++, . ++, , , , .
static?
Static هي كلمة أساسية C ++ تستخدم لإعطاء عنصر خاصية مميزة. بالنسبة للعناصر الثابتة ، يتم تخصيص الذاكرة مرة واحدة فقط وتوجد هذه العناصر حتى ينتهي البرنامج. لا يتم تخزين كل هذه العناصر في الكومة أو في المكدس ، ولكن في مقاطع ذاكرة خاصة تسمى .data و .bss (اعتمادًا على ما إذا كانت البيانات الثابتة قد تمت تهيئتها أم لا). تُظهر الصورة أدناه تخطيطًا نموذجيًا لذاكرة البرنامج.

أين تستخدم؟
يوجد أدناه رسم تخطيطي لكيفية ومكان استخدام ثابت في البرنامج.

والآن سأحاول أن أصف بالتفصيل كل ما يظهر في الرسم التخطيطي. اذهب!
المتغيرات الثابتة داخل دالة
المتغيرات الثابتة ، عند استخدامها داخل دالة ، تتم تهيئتها مرة واحدة فقط ، ثم تحتفظ بقيمتها. يتم تخزين هذه المتغيرات الثابتة في منطقة ذاكرة ثابتة ( .data أو .bss ) بدلاً من المكدس ، مما يسمح بتخزين قيمة المتغير واستخدامها طوال عمر البرنامج. دعنا نلقي نظرة على برنامجين متطابقين تقريبًا وسلوكهما. الفرق الوحيد هو أن أحدهما يستخدم متغيرًا ثابتًا ، والآخر لا يستخدمه.
البرنامج الأول:
#include <iostream>
void counter() {
static int count = 0; // 4
std::cout << count++;
}
int main() {
for (int i = 0; i < 10; ++i) {
counter();
}
return 0;
}
مخرجات البرنامج:
0123456789
البرنامج الثاني:
#include <iostream>
void counter() {
int count = 0; // 4
std::cout << count++;
}
int main() {
for (int i = 0; i < 10; ++i) {
counter();
}
return 0;
}
مخرجات البرنامج:
000000000
إذا لم تستخدم ثابتًا في السطر 4 ، فسيحدث تخصيص الذاكرة وتهيئة العد المتغير في كل مرة يتم استدعاء عداد () ، ويتم إتلافه في كل مرة يتم فيها إنهاء الوظيفة. ولكن إذا جعلنا المتغير ثابتًا ، بعد التهيئة (في المرة الأولى التي يتم فيها استدعاء وظيفة العداد () ) ، فسيتم تحديد نطاق العد حتى نهاية الوظيفة الرئيسية () ، وسيحتفظ المتغير بقيمته بين الاستدعاءات للعداد () .
كائنات فئة ثابتة
الكائن الثابت للفئة له نفس خصائص المتغير الثابت العادي الموصوف أعلاه ، أي مخزنة في مقطع من الذاكرة .data أو .bss ، تم إنشاؤه عند بدء التشغيل وإتلافه عند إنهاء البرنامج ، وتهيئته مرة واحدة فقط. يتم تهيئة الكائن كالمعتاد - من خلال مُنشئ الفئة. لنفكر في مثال مع كائن فئة ثابت.
#include <iostream>
class Base { // 3
public:
Base() { // 5
std::cout << "Constructor" << std::endl;
}
~Base() { // 8
std::cout << "Destructor" << std::endl;
}
};
void foo() {
static Base obj; // 14
} // 15
int main() {
foo(); // 18
std::cout << "End of main()" << std::endl;
return 0;
}
مخرجات البرنامج:
المُنشئ
نهاية
المُدمر الرئيسي ()
3 Base ( 5) ( 8). . 14 obj Base. foo() 18.
- , , foo() 15, , .. . , , .
#include <iostream>
class Base {
public:
Base() {
std::cout << "Constructor" << std::endl;
}
~Base() {
std::cout << "Destructor" << std::endl;
}
};
void foo() {
Base obj;
} // 15
int main() {
foo();
std::cout << "End of main()" << std::endl;
return 0;
}
إذا أزلنا الثابت عند إنشاء متغير في دالة foo () ، فسيحدث تدمير الكائن في السطر 15 في كل مرة يتم استدعاء الوظيفة. في هذه الحالة ، سيكون إخراج البرنامج متوقعًا تمامًا لمتغير محلي بذاكرة مخصصة على المكدس:
منشئ
المدمر
نهاية الرئيسية ()
أعضاء الفصل الثابت
مقارنةً بحالات الاستخدام السابقة ، يصعب فهم أعضاء الفصل الثابت قليلاً. دعنا نرى لماذا. افترض أن لدينا البرنامج التالي:
#include <iostream>
class A { // 3
public:
A() { std::cout << "Constructor A" << std::endl; }
~A() { std::cout << "Destructor A" << std::endl; }
};
class B { // 9
public:
B() { std::cout << "Constructor B" << std::endl; }
~B() { std::cout << "Destructor B" << std::endl; }
private:
static A a; // 15 ()
};
int main() {
B b; // 19
return 0;
}
في مثالنا ، أنشأنا الفئة A (السطر 3) والفئة B (السطر 9) بأعضاء فئة ثابتة ( السطر 15 ). ونحن نفترض أن خلق الكائن ب على خط 19 وإنشاء كائن من على خط 15 . سيكون هذا هو الحال إذا استخدمنا أعضاء غير ثابتة في الفصل. لكن ناتج البرنامج سيكون كالتالي:
المنشئ ب
المدمر ب
والسبب في هذا السلوك هو أن الأعضاء الثابتة للفئة لم تتم تهيئتها باستخدام مُنشئ لأنها لا تعتمد على تهيئة الكائن. أولئك. في السطر 15 ، نحن نعلن فقط عن الكائن ، وليس تعريفه ، لأن التعريف يجب أن يحدث خارج الفئة باستخدام عامل تحليل النطاق (: :) . دعونا نحدد أعضاء الفئة ب .
#include <iostream>
class A {
public:
A() { std::cout << "Constructor A" << std::endl; }
~A() { std::cout << "Destructor A" << std::endl; }
};
class B {
public:
B() { std::cout << "Constructor B" << std::endl; }
~B() { std::cout << "Destructor B" << std::endl; }
private:
static A a; // 15 ()
};
A B::a; // 18 ()
int main() {
B b;
return 0;
}
الآن ، بعد أن حددنا عضو الفصل الثابت لدينا في السطر 18 ، يمكننا أن نرى ناتج البرنامج التالي:
المنشئ أ
المنشئ ب
المدمر ب
المدمر أ
يجب أن نتذكر أن عضو الفصل سيكون هو نفسه لجميع حالات الفئة B ، أي إذا أنشأنا ثلاثة كائنات من الفئة B ، فسيتم استدعاء مُنشئ العضو الثابت للفئة مرة واحدة فقط. هذا مثال على ما أتحدث عنه:
#include <iostream>
class A {
public:
A() { std::cout << "Constructor A" << std::endl; }
~A() { std::cout << "Destructor A" << std::endl; }
};
class B {
public:
B() { std::cout << "Constructor B" << count++ << std::endl; }
~B() { std::cout << "Destructor B" << --count << std::endl; }
private:
static A a; //
static int count; //
};
A B::a; //
int B::count = 1; //
int main() {
B b1, b2, b3;
return 0;
}
مخرجات البرنامج:
مُنشئ A
مُنشئ B1
مُنشئ B2
مُنشئ B3
مُدمر B3
مُدمر B2
مُدمر B1
مُدمر أ
وظائف ثابتة
أتت الوظائف الثابتة إلى C ++ من C. بشكل افتراضي ، تكون جميع الوظائف في C عامة ، وإذا كنت تريد إنشاء وظيفتين بنفس الاسم في ملفين مختلفين .c (.cpp) من نفس المشروع ، فستتلقى خطأ يفيد بأن هذه الوظيفة تم تعريفه بالفعل ( خطأ فادح LNK1169: تم العثور على رمز أو أكثر من الرموز المحددة المضاعفة ). يوجد أدناه قائمة بثلاثة ملفات لبرنامج واحد.
// extend_math.cpp
int sum(int a, int b) {
int some_coefficient = 1;
return a + b + some_coefficient;
}
// math.cpp
int sum(int a, int b) {
return a + b;
}
// main.cpp
int sum(int, int); // declaration
int main() {
int result = sum(1, 2);
return 0;
}
لحل هذه المشكلة ، سنعلن أن إحدى الوظائف ثابتة. على سبيل المثال هذا:
// extend_math.cpp
static int sum(int a, int b) {
int some_coefficient = 1;
return a + b + some_coefficient;
}
, , . sum() math.cpp . , static , , , , , (.h).
, inline static, , . (.cpp), #include , . , , .. include .cpp .
- ()
يمكنك استخدام دالة عضو ثابتة بدون إنشاء كائن للفئة. يتم الوصول إلى الوظائف الثابتة باستخدام اسم الفئة ومعامل دقة النطاق (: :) . عند استخدام وظيفة عضو ثابتة ، هناك قيود مثل:
- ضمن إحدى الوظائف ، يمكنك فقط الوصول إلى أعضاء البيانات الثابتة ووظائف الأعضاء الثابتة الأخرى وأي وظيفة أخرى من خارج الفصل الدراسي.
- وظائف الأعضاء الثابتة لها نطاق الفئة التي يقيمون فيها.
- لا يمكنك الوصول إلى مؤشر الفئة هذا ، لأننا لا ننشئ أي كائن لاستدعاء هذه الوظيفة.
دعنا نلقي نظرة على المثال التالي:
#include <iostream>
class A {
public:
A() { std::cout << "Constructor A" << std::endl; }
~A() { std::cout << "Destructor A" << std::endl; }
static void foo() { // 8
std::cout << "static foo()" << std::endl;
}
};
int main() {
A::foo(); // 14
return 0;
}
في الفئة A ، في السطر 8 ، لدينا دالة عضو ثابتة foo () . في السطر 14 ، نسمي الوظيفة باستخدام اسم الفئة ومشغل دقة النطاق ، ونحصل على مخرجات البرنامج التالية:
ثابت foo ()
من الإخراج ، يمكنك أن ترى أنه لا يوجد إنشاء كائن ولا يتم استدعاء مُنشئ / مُدمر.
إذا كانت طريقة foo () غير ثابتة ، فسيرمي المترجم خطأ على التعبير الموجود في السطر 14 ، لأن تحتاج إلى إنشاء كائن للوصول إلى طرقه غير الثابتة.
خاتمة
– « static , ». , , .
:
- , . , , , . , , .
- , , .. , . , , .. .
- static Singleton, , . , -. Singleton , , .
- في بعض الأحيان ، لكي تعمل الوظيفة مرة واحدة فقط دون تخزين الحالة السابقة في مكان ما في الكائن ، يتم استخدام المتغيرات الثابتة. يمكنك مشاهدة مثال في قسم "المتغيرات الثابتة داخل دالة". لكن هذا ليس أسلوبًا جيدًا للغاية ، ويمكن أن يؤدي إلى ساعات طويلة من البحث عن الأخطاء إذا كنت تستخدم تعدد مؤشرات الترابط.
- من الناحية العملية ، غالبًا ما يستخدم مبرمجو C ++ وظائف الأعضاء الثابتة كبديل للوظائف العادية التي لا تتطلب إنشاء كائن لتنفيذه.
آمل أن تكون قد استمتعت بمقالتي حول الكلمة الأساسية الثابتة في C ++. سأكون سعيدًا لأي نقد أو نصيحة. شكرا للجميع!