به نام خدا

 سلام! قصد دارم که در مورد نحوه پیاده سازی برنامه در زبان C++ توسط کامپایلر g++ رو توضیح بدم.

پیاده سازی کلاس ها:

در زبان C یک ساختار (struct) داشتیم که میتوانستیم نوع های مختلفی که به یکدیگر مربوط بودند را در کنار هم داشته باشیم! (مثلا Student یا Point یا ComplexCLS)

برای اینکه روی این ساختار ها عملی رو انجام بدیم معمولا تابعی مینوشتیم که آن ساختار رو به عنوان ورودی میگرفت و عمل رو روی اون انجام میداد! همچنین میتونستیم اشاره گر رو به عنوان آرگومان تابع بفرستیم! به مثال زیر دقت کنید

ابتدا ساختار نقطه را تعریف میکنم:

 

struct point
{
int x;
int y;
};

حال میخواهیم تابعی برای چاپ مختضات نقطه بنویسیم:

print_point(struct point* p)
{
    printf("%d",p->x);
    printf("%d",p->y); 
}

همین برنامه در زبان C++ به صورت زیر پیاده سازی میشود:

struct point
{
    int x,y;
    void print ()
    {
        printf("%d",x);
        printf("%d",y); 
    }

};

حال فرض کنید میخواهیم یک نقطه بسازیم و مختصات آنرا چاپ کنیم.

در C داریم:

struct point p;
print_point (&p);

‍و در C++ داریم:

point p;
p.print();

حال سوال این است که C++ چگونه این موضوع را پیاده سازی کرده؟

پاسخ:

خیلی جالب است اگر بدانید که در مثال فوق کامپایلر C++ دقیقا همان کاری را که ما در زبان C انجام دادیم را انجام میدهد! به عبارت دیگر کامپایلر متغیر های درون کلاس را جدا میکند و درون یک struct قرار میدهد همچنین توابع آن را جدا کرده و مانند تابع معمولی پیاده سازی میکند! و در نهایت خط زیر در زبان C++ به خط زیرین آن تبدیل میشود

p.print();
print(&p);

در واقع C++ آدرس آن struct را (که حاصل تفکیک متغیر های درون کلاس بود) برای تابع print میفرستد.

این اشاره گر در بدنه تابع print توسط this قابل دسترسی یعنی میتوانیم بگویم C++ تابع print را به صورت زیر پیاده سازی میکند:

print (point* this)
{
    printf("%d",this->x);
    printf("%d",this->y); 
}

شاید برایتان سوال پیش بیاید که ممکن است ما درون دو کلاس متفاوت (مثلا کلاس point و کلاس complexCLS) تابعی با نام print داشته باشیم آیا برای این دو تابع به دلیل اینکه نامشان یکسان هست تداخل ایجاد نمیشود؟

باید بگویم که نام تابع که ما مینویسیم در زمان کامپایل تغییر میکند و اینگونه از تداخل جلوگیری میشود! کامپایلر نام جدید تابع را بر اساس نام اصلی تابع، کلاسی که تابع مربوط به آن است و آرگومان های ورودی آن تعیین میکند!

به طور مثال فرض کنید که کامپایلر میخواهد برای تابع print درون کلاس point اسم جدیدی انتخاب کند که با تابع های هم اسم آن در کلاس های دیگر مشکل ایجاد نکند! به طور ساده میتوان گفت که کامپایلر میتواند نام تابع را print_point بگذارد. (البته نام گذاری پیچیده تر از این هست اما ما برای سادگی این مثال را زدیم ولی میتوان گفت که کامپایلر از سه مورد 1. نام تابع 2. نام کلاس 3 .آرگومان ها برای نام گذاری جدید توابع استفاده میکند و دلیل اینکه در زبان C++ میتوانیم توابع با نام های مشابه و آرگومان های متفاوت داشته باشیم هم همین است)

 

پیاده سازی مقادیر پیشفرض در توابع:

اینکار در زبان c++ خیلی ساده پیاده سازی شده

فرض کنید تابع زیر را نوشته ایم

void foo (int  a=3)
{
..
}

 

در هنگام فراخوانی این تابع میتوانیم به دو صورت زیر عمل کنیم

foo();

foo(7);

 

که به طور خودکار در حالت اول مقدار آرگومان a برابر 3 در نظر گرفته میشود.

پیاده سازی این موضوع در زبان c++ خیلی ساده است.

تنها اتفاقی که میافتد این است که کامپایلر در زمان کامپایل برنامه در صورتی که به فراخوانی نوع اول برخورد کند آنرا به صورت زیر مینویسد

foo(3);

یعنی در این حالت هیچ اتفاقی درون تابع رخ نمیدهد و همه چیز مثل c پیاده سازی شده

 

 

تابع سازنده پیشفرض

اسم این تابع را زیاد شنیده ایم و میدانیم در هنگام ساختن یک شی از روی یک کلاس تابع سازنده آن اجرا میشود

تصور ما از تابع سازنده پیشفرض این است که تابع سازنده پیشفرض یک تابع خالی است که اگر ما تابع سازنده نداشته باشیم آن تابع اجرا میشود اما اینطور نیست.

یادآوری میکنم که در زبان c ما نمیتواستیم به متغیر های درون struct مقدار اولیه بدهیم یعنی کد زیر در زبان C قابل پیاده سازی نیست:

struct point
{
    int x = 0;
    int y = 0;
};

و برای آنکه اینکار را انجام دهیم میتوانیم یک تابع بنویسیم که مقدار مختصات نقطه را مقدار دهی اولیه کند. به صورت زیر:

void init_point (struct point* p)
{
    p->x = 0;
    p->y = 0;
}

ولی در زبان c++ میتوان به متغیر های درون کلاس (یا struct) مقدار اولیه داد! کد آن در c++ به صورت زیر است:

class point
{
    int x = 0;
    int y = 0;
};

 

حال نکته اینجاست که مقدار دهی اولیه در C++ توسط تابع سازنده پیشفرض صورت میگیرد. کامپایلر یک تابع دقیقا مشابه تابعی که ما برای c نوشتیم ایجاد میکند و از آن برای مقدار دهی اولیه استفاده میکند.

نکته دیگر آنکه چه ما تابع سازنده برای کلاسمان نوشته باشیم چه ننوشته باشیم این تابع (تابع سازنده پیشفرض) برای مقدار دهی اولیه اجرا میشود.

 

آیا هر Template یک کلاس است؟

سوال این است که هنگامی که ما از template استفاده میکنیم آیا در زمان اجرا اتفاقی خاصی میفتد که ما میتوانیم هر نوع کلاسی ایجاد کنیم یا در زمان کامپایل برای هر نوع، یک کلاس ساخته میشود!؟

پاسخ آن است که در زمان کامپایل برای هر نوع، یک کلاس ساخته میشود.

حدس میزنم که کامپایلر بسته به اینکه یک کلاس با چه نوع هایی ساخته شده کلاس های متفاوت میسازد!

مثلا داریم:

 

myArray<int> r;

 

پس کامپایلر کلاس myArray را تنها با نوع int خواهد ساخت و اگر نوع دیگر هم در برنامه باشد مثلا:

myArray<double> rd;

کامپایلر هم نوع double و هم نوع int برای این کلاس میسازد.

یعنی این نیست که کامپایلر برای تمام نوع های موجود (مثلا int , double , char و...) یک کلاس بسازد! و دلیل حدس ام هم اینست که چون در C++ میتوان کلاس را به عنوان یک نوع دید پس کامپایلر باید همه نوع کلاس رو هم در نظر بگیرد که اینکار باعث افزایش حجم برنامه خواهد شد.

 

پیاده سازی استثنا ها:

پیاده سازی این مورد پیچیده بود متوجه نشدم :(

 

فعلا همین ها بود اگه چیز جدید دیگه ای هم یاد گرفتم اضافه میکنم! اگه هم چیزه دیگه ای در C++ بود که خواستید بدونید چطوری پیاده سازی شده بپرسید اگه سوادم رسید بررسی میکنم و به این مطلب اضافه میکنم!

امیدوارم مفید بوده باشه!

یا علی مدد..!