#تتضمن #تتضمن "complex.h"
باستخدام مساحة الاسم الأمراض المنقولة جنسيا؛
ostream& عامل التشغيل << (ostream& os, const complex& x) { return os << '(' << real (x) << ',' << imag (x) << ')' }
int main() { مجمع c1(2, 1);
cout << c1 << endl;
cout << c1+c2 << cout << c1-c2 << endl;
cout << conj(c1) << endl;
cout << (c1 += c2) << endl;
cout << (c1 == c2) << cout << (c1!= c2) << cout << +c2 << endl;
cout << (c2 - 2) << cout << (5 + c2) << endl;
العودة 0 }
كما هو موضح في الشكل أعلاه، يجب التحقق من التعيين الذاتي عند التحميل الزائد على عامل التعيين "="، للأسباب التالية:
إذا لم يكن هناك اكتشاف للتخصيص الذاتي، فسيتم إصدار m_data للكائن الخاص، ولن يكون المحتوى المشار إليه بواسطة m_data موجودًا، لذلك ستكون هناك مشاكل في النسخة.
وهنا فصلان للتفسير:
class complex
{
public:
complex (double r = 0, double i = 0)
: re (r), im (i)
{ }
complex& operator += (const complex&);
double real () const { return re; }
double imag () const { return im; }
private:
double re, im;
friend complex& __doapl (complex*,
const complex&);
};
class String
{
public:
String(const char* cstr = 0);
String(const String& str);
String& operator=(const String& str);
~String();
char* get_c_str() const { return m_data; }
private:
char* m_data;
};
بعد إنشاء هذين الكائنين، يقوم المترجم (VC) بتخصيص الذاكرة للكائنين على النحو التالي:
الاثنان الموجودان على اليسار هما تخصيص ذاكرة المترجم لمجمع الفئات في وضع التصحيح ووضع الإصدار. في وضع التصحيح، يقوم المترجم بإدراج الرأس والذيل (الجزء الأحمر)، وجزء معلومات الحجم 4*8 + 4 (الجزء الرمادي) في ذاكرة الكائن المعقد. الجزء الأخضر هو المساحة التي يشغلها الكائن المعقد بالفعل. يوجد قسم مكون من 52 كلمة فقط، ولكن VC محاذٍ لـ 16 بايت، لذا فإن أقرب 16 مضاعفًا للرقم 52 هو 64، ويجب ملء الفجوة البالغة 12 بايت (جزء اللوحة السماوية). بالنسبة للكائن المعقد في جزء الإصدار، تتم إضافة رأس المعلومات وجزء الرأس فقط. تحليل فئة السلسلة هو نفسه في الأساس.
بعد ذلك، دعونا نلقي نظرة على كيفية قيام مترجم VC بتخصيص كائنات المصفوفة:
وبالمثل، يضيف المترجم بعض المعلومات الزائدة إلى الكائن. بالنسبة لكائنات الفئة المعقدة، نظرًا لأن المصفوفة تحتوي على ثلاثة كائنات، فهناك 8 كائنات مزدوجة، ثم يقوم المترجم بإدراج "3" أمام الكائنات الثلاثة المعقدة لتحديد رقم الكائنات الفردية . طريقة التحليل لفئة السلسلة متشابهة أيضًا.
توضح الصورة أدناه لماذا يتطلب حذف كائن مصفوفة استخدام طريقة الحذف[]:
سلسلة.ح
#ifndef __MYSTRING__
#define __MYSTRING__
class String
{
public:
String(const char* cstr=0);
String(const String& str);
String& operator=(const String& str);
~String();
char* get_c_str() const { return m_data; }
private:
char* m_data;
};
#include <cstring>
inline
String::String(const char* cstr)
{
if (cstr) {
m_data = new char[strlen(cstr)+1];
strcpy(m_data, cstr);
}
else {
m_data = new char[1];
*m_data = ' ';
}
}
inline
String::~String()
{
delete[] m_data;
}
inline
String& String::operator=(const String& str)
{
if (this == &str)
return *this;
delete[] m_data;
m_data = new char[ strlen(str.m_data) + 1 ];
strcpy(m_data, str.m_data);
return *this;
}
inline
String::String(const String& str)
{
m_data = new char[ strlen(str.m_data) + 1 ];
strcpy(m_data, str.m_data);
}
#include <iostream>
using namespace std;
ostream& operator<<(ostream& os, const String& str)
{
os << str.get_c_str();
return os;
}
#endif
string_test.cpp
#include "string.h"
#include <iostream>
using namespace std;
int main()
{
String s1("hello");
String s2("world");
String s3(s2);
cout << s3 << endl;
s3 = s1;
cout << s3 << endl;
cout << s2 << endl;
cout << s1 << endl;
}
في لغة C++، يلعب المُنشئ ذو المعلمة الواحدة (أو المُنشئ متعدد المعلمات بقيم افتراضية لجميع المعلمات باستثناء المعلمة الأولى) دورين. 1 هو منشئ، 2 هو عامل تحويل النوع الافتراضي والضمني.
لذلك، في بعض الأحيان عندما نكتب تعليمات برمجية مثل AAA = XXX، ويكون نوع XXX هو نوع المعلمة لمنشئ AAA أحادي المعلمة، فسيقوم المترجم تلقائيًا باستدعاء هذا المنشئ لإنشاء كائن AAA.
هذا يبدو رائعًا ومريحًا للغاية. لكن في بعض الحالات (انظر المثال الرسمي أدناه)، يتعارض ذلك مع نوايانا الأصلية (المبرمجين). في هذا الوقت، تحتاج إلى إضافة تعديل صريح أمام المُنشئ لتحديد أنه لا يمكن استدعاء/استخدام هذا المُنشئ إلا بشكل صريح ولا يمكن استخدامه ضمنيًا كمشغل تحويل النوع.
يتم استخدام المنشئ الصريح لمنع التحويلات الضمنية. يرجى إلقاء نظرة على الكود أدناه:
class Test1
{
public:
Test1(int n)
{
num=n;
}//普通构造函数
private:
int num;
};
class Test2
{
public:
explicit Test2(int n)
{
num=n;
}//explicit(显式)构造函数
private:
int num;
};
int main()
{
Test1 t1=12;//隐式调用其构造函数,成功
Test2 t2=12;//编译错误,不能隐式调用其构造函数
Test2 t2(12);//显式调用成功
return 0;
}
يأخذ منشئ Test1 معلمة int، وسيتم تحويل السطر 23 من التعليمات البرمجية ضمنيًا إلى استدعاء منشئ Test1. تم الإعلان عن مُنشئ Test2 على أنه صريح، مما يعني أنه لا يمكن استدعاء المُنشئ من خلال التحويل الضمني، لذلك سيحدث خطأ في الترجمة في السطر 24 من التعليمات البرمجية.
يمكن استدعاء المنشئين العاديين ضمنيًا. لا يمكن استدعاء المنشئ الصريح إلا بشكل صريح.
عندما يلزم تحويل أي جزء إلى نوع مزدوج، يتم استدعاء الدالة double() تلقائيًا للتحويل. كما هو موضح في الشكل أعلاه، يحدد المترجم أن 4 هو عدد صحيح أثناء تحليل d = 4 + f، ثم يستمر في تحديد f. ويلاحظ أن f يوفر وظيفة double()، ثم يقوم بتنفيذ المضاعفة () على f، وحساب 0.6، ثم إضافتها إلى 4، وأخيرا الحصول على النوع المزدوج 4.6.
يتم تعريف فئة في الشكل أعلاه، تسمى Fraction، يتم تحميل عامل التشغيل "+" بشكل زائد في الفئة أثناء عملية f+4، يتم تحويل "4" ضمنيًا (المنشئ) بواسطة المترجم إلى كائن Fraction، ثم يتم تمريره. من خلال الكسر، يشارك عامل التشغيل "+" المثقل في العملية.
كما هو موضح في الشكل أعلاه، تتم إضافة الدالة double() إلى Fraction، والتي تقسم متغيري العضوين في Fraction، ثم تحولهما بالقوة إلى نوع مزدوج وترجع النتيجة. أثناء عملية التحميل الزائد f+4، سيقوم المترجم بذلك الإبلاغ عن خطأ يمكنك إجراء التحليل التالي:
1. أولاً، يتم تحويل 4 ضمنيًا (المنشئ) إلى كائن كسر، ثم يتم تشغيل عامل التشغيل "+" المحمل بشكل زائد باستخدام "f" لإرجاع كائن كسر؛
2. أولاً، يتم تحويل 4 ضمنيًا (المنشئ) إلى كائن كسر، ثم يتم تنفيذ عملية مزدوجة على الزوج من خلال عامل التشغيل "+" والعملية "f" المحملة بشكل زائد، وأخيرًا يتم إرجاع كائن كسر؛
3. . .
لذا فإن لدى المترجم طريقتين على الأقل، مما يخلق الغموض ويبلغ عن خطأ. كما هو موضح في الشكل أعلاه، إذا تمت إضافة الكلمة الأساسية الصريحة قبل المنشئ Franction، فسيتم إلغاء التحويل الضمني، لذلك أثناء تنفيذ d2 = f + 4، ستستدعي f الدالة المزدوجة للتحويل إلى 0.6. أضفه إلى 4 ليصبح 4.6 نظرًا لأن المنشئ يلغي تحويل الصيغة الضمنية، فلا يمكن تحويل 4.6 إلى كسر، لذلك سيتم الإبلاغ عن خطأ.
يوضح الشكل التالي تطبيق التحميل الزائد للمشغل ووظائف التحويل في C++ stl:
توضح الصورة أدناه البنية الداخلية واستخدام المؤشرات الذكية بشكل جيد للغاية: هناك ثلاث نقاط رئيسية في بناء جملة المؤشرات الذكية، الأولى هي المؤشر الخارجي المحفوظ، والذي يتوافق مع T* px في الصورة أعلاه، هذا المؤشر سيتم تنفيذ المؤشر الوارد بدلاً من المؤشر الوارد؛ والثاني هو التحميل الزائد على عامل التشغيل "*"، وإلغاء الإشارة، وإرجاع الكائن الذي يشير إليه المؤشر؛ والثالث هو التحميل الزائد على عامل التشغيل "->"، إلى return المؤشر المطابق للصورة أعلاه هو px.
التكرارات هي أيضًا نوع من المؤشرات الذكية. هناك أيضًا العناصر الثلاثة للمؤشرات الذكية المذكورة أعلاه، والتي تتوافق مع الخط الأحمر والأجزاء المميزة باللون الأصفر في الشكل أدناه:
سيتم تحليل أحرف التحميل الزائد "*" و"->" الخاصة بالتحميل الزائد للمكرر بعناية أدناه:
قم بإنشاء كائن مكرر القائمة، list::iterator ite؛ يتم استخدام القائمة هنا لحفظ كائن Foo، وهو الفئة في تعريف قالب القائمة T، عامل التشغيل*() يُرجع كائن بيانات (*node)، العقدة من نوع __link_type، لكن __link_type من نوع __list_node<T>* T هنا هو Foo، لذا فإن العقدة من نوع __list_node<Foo >*، لذا ( *node).data تحصل على كائن من النوع Foo، ويحصل &(operator*()) أخيرًا على عنوان كائن Foo، أي إرجاع Foo* مؤشر إلى النوع.
كما ترون من الشكل أعلاه، كل عامل هو فئة تقوم بتحميل عامل التشغيل "()" بشكل زائد، ثم تصبح "عاملًا". إنها في الواقع فئة، ولكن يبدو أنها تحتوي على خصائص دالة. يدمج كل عامل في الواقع فئة غريبة خلفه، كما هو موضح في الشكل أدناه، ولا تحتاج هذه الفئة إلى التصريح عنها يدويًا بواسطة المبرمج. يرث العامل في المكتبة القياسية أيضًا فئة غريبة: يظهر محتوى هذه الفئة في الشكل أدناه، وهو يعلن فقط عن بعض الأشياء، ولا توجد متغيرات أو وظائف فعلية فيها، وستتم مناقشة المحتوى المحدد في STL.
تختلف قوالب الوظائف عن قوالب الفئة، ولا تحتاج إلى الإعلان بشكل صريح عن نوع المعلمات الواردة عند استخدامها، وسيقوم المترجم تلقائيًا باستنتاج النوع.
غالبًا ما تُستخدم قوالب الأعضاء في البرمجة العامة للحصول على قابلية توسع أفضل، خذ الشكل أعلاه كمثال. غالبًا ما تكون T1 هي الفئة الأساسية لـ U1، وغالبًا ما تكون T2 هي الفئة الأساسية لـ U2. من خلال هذا بهذه الطريقة، طالما أن فئات الأصل أو الأسلاف U1 وU2 التي تم تمريرها هي T1 وT2، يمكن استخدام الميراث وتعدد الأشكال بذكاء من خلال هذه الطريقة، ولكن ليس العكس. تُستخدم هذه الطريقة كثيرًا في STL:
كما يوحي الاسم، يشير تقسيم القالب إلى تحديد أنواع بيانات محددة في القالب، وهو يختلف عن التعميم: بالطبع، تقسيم القالب له درجات أيضًا، ويمكن تحديد الأنواع الجزئية، وهو ما يسمى التخصص الجزئي:
سيتم شرح الكثير من المحتوى في دورة C++ 11، لذا سأقدمه هنا فقط في الوقت الحالي.
سيتم شرح الكثير من المحتوى في دورة C++ 11 وسأقدمه هنا فقط في الوقت الحالي.
يمكن رؤية المرجع كاسم مستعار للمتغير المشار إليه.
كما هو موضح في الشكل أعلاه، تم تحديد ثلاث فئات، A وB وC. يرث B من A، ويرث C من B. هناك وظيفتان افتراضيتان في A، وواحدة في B، وواحدة في C. يخصص المترجم الكائن a من A في الذاكرة كما هو موضح في الشكل أعلاه. لا يوجد سوى متغيرين للعضو m_data1 وm_data2. في نفس الوقت، نظرًا لأن الفئة A لها وظائف افتراضية، فسيقوم المترجم بتخصيص مساحة للكائن a لحفظ جدول الوظائف الافتراضية، يحتفظ هذا الجدول بعنوان الوظيفة الافتراضية (الوظيفة الديناميكية) لهذه الفئة ربط الحالة)، نظرًا لأن الفئة A لها وظيفتان افتراضيتان، فهناك مسافتان (مسافات صفراء وزرقاء) في جدول الوظائف الافتراضية للإشارة إلى A::vfunc1() وA::vfunc2() على التوالي؛ هو كائن من الفئة B، لأن الفئة B تتجاوز وظيفة vfunc1() من الفئة A. ، لذا فإن جدول الوظائف الافتراضية لـ B (الجزء الأزرق) سيشير إلى B::vfunc1()، ويرث B vfunc2() من الفئة A، لذلك سيشير جدول الوظائف الافتراضية لـ B (الجزء الأزرق) إلى A من الفئة الأصلية A: :vfunc2 () بالمثل، c هو كائن من الفئة C، ويمثله تعيد الفئة C كتابة وظيفة vfunc1() للفئة الأصلية، لذلك سيشير جدول الوظائف الافتراضية لـ C (الجزء الأصفر) إلى C::vfunc1() في نفس الوقت، يرث C vfunc2() من الفئة الفائقة A، وبالتالي فإن الوظيفة الافتراضية لـ B سيشير الجدول (الجزء الأزرق) إلى الدالة A::vfunc2(). في الوقت نفسه، يستخدم الشكل أعلاه أيضًا رمز لغة C لتوضيح كيفية استدعاء الطبقة السفلية للمترجم لهذه الوظائف. وهذا هو جوهر تعدد الأشكال الميراث الموجه للكائنات.
يمكن اعتبار هذا المؤشر في الواقع مؤشرًا يشير إلى عنوان الذاكرة للكائن الحالي، كما هو موضح في الشكل أعلاه، نظرًا لوجود وظائف افتراضية في الفئة الأساسية والفئة الفرعية، سيتم ربط هذا->Serialize() ديناميكيًا. وهو ما يعادل (*(this- >vptr)[n])(this). يمكن فهم ذلك من خلال الجمع بين المؤشر الظاهري وجدول الوظائف الافتراضية في القسم السابق. أما لماذا من الصحيح الكتابة بهذه الطريقة في النهاية، فسوف يوضح الملخص التالي.