یادداشت های برنامه نویس آماتور | Amateur programmer notes
61 subscribers
92 photos
10 videos
34 files
88 links
اندازه ی آرزو هات مهم نیست
مهم اینه تو چقد میبینیش
آرزو هاتو بزرگ ببین


</create> by <ashkan\>
Download Telegram
#آموزش ابتدایی دستورات پیش پردازنده و ماکرو ها
اول از همه اشاره کنم تمام پیش پردازنده ها هنگام کامپایل عمل میکنن نه هنگام اجرای برنامه
پس هیچوقت اون ها رو با متغیر های معمولی . توابع معمولی اشتباه نگیرید

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


#include
خودتون از من هم بهتر بلدید
کارش اینه که محتویات یه فایل(هدر) رو به کدتون اضافه میکنه
مثال
اگر محتویات
header.h
این باشه
void helloworld();
و فایل
cpp
این باشه
#include “header.h”

int main(){
return 0;
}

مثل اینه که بنویسید
void helloworld();

int main(){
return 0;
}
عیننا کپی ش میکنه


#define نام
با استفاده از این میتونیم
یه ثابت داشته باشیم
که مقداری نداره
پس اگه هر جای کد اون رو بذارید مثل این میمونه چیزی اضافه نکردید
#define amir

و داخل کد

cout«”Hello!”«endl; amir

امیر رو اون انتها گذاشتم و انگار نه انگار

#define مقدار نام
این فرقش با بالایی اینه که هر جای کد بذارین
چیزی که جلوش هست رو مثل یه قالب میذاره همون جا
مثال:
#define pi 3.1415

cout«pi«endl;

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

cout«3.1415«endl;

#define Fname(Args...) blo blo

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

#define MYMACRO() cout«

حالا کد
یادتون باشه این آرگومان نداره پس میتونید این کار رو مثل حالت بالا مثل عدد پی تعریف کنید
کد

MYMACRO() "Hello";

زیاد نترسید
هیچ چیز ترسناکی نیست
مثل حالت بالا کامپایلر محتویات جلوی اون ماکرو رو جایگزین میکنه
و دقیقا مثل اینه که بنویسید
cout«”Hello”«endl;

یه مثال دیگه
#define MYMACRO(A1) cout«A1«endl;
تو مثال بالا
A1
آرگومانش هست
تنها کاری که باید انجام میده اینه
شما بعدا هر چیزی رو و باز تاکید میکنم هر چیزی رو که به عنوان آرگومان بهش بدید عیننا اون رو داخل محتویات قرار میده

مثال کد
MYMACRO(25)

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

این کد مثل دقیقا مثل اینه که بنویسید
cout«25«endl;

حالا سایر دستورات
#ifdef
این دستور مشخص میکنه آیا بالای اون چیزی رو تعریف کردیم یا نه

یادتون نره حتما بالاش
چون کامپایلر از بالا به پایین کد رو کامپایل میکنه
ترتیب بالا و پایین خیلی مهمه
پایان این دستور وتمام حالت های شرطی با
#endif
مشخص میشه

یه مثال سطحی

#ifdef L
این قسمت رو به کدام اضافه کن
#endif

این یعنی اگر
L
تعریف شده بود
اون قسمت توی کد هامون موجود باشه
وگرنه موجود نباشه و کامپایل نشه
یه مثال

#ifdef L
void helloworld()
{
cout«"Hello World!"«endl;
}
#endif

سعی کنید تابع رو فراخوانی کنید
میبینید که نمیشه
فقط کافیه
L
رو بالاش تعریف کنین
مثلا
#define L

به همین راحتی مشخص میکنید داخل شرط به کدهاتون برای کامپایل اضافه شه.

دیگر ماکرو ها
#ifndef
یعنی اگر تعریف نشده بود اون قسمت تا
endif
کامپایل شه

#else
شبیه
else
هست یعنی اگر شرط برقرار بود که هیچ
درغیر اینصورت این قسمت به خروجی اضافه شه
پایانش هم با endif
مشخص میشه

مثال
#ifdef world
void helloworld()
{
cout«"Hello World!"«endl;
}
#else
void hellocity()
{
cout«"Hello city!"«endl;
}
#endif // city

این یعنی اگر
world
تعریف شده بود اون قسمت کامپایل شه
در غیر اینصورت
اون یکی قسمت کامپایل بشه

سعی کنید تعریف ها رو اضافه کنید و تست کنید

نکته اینه که
#else
برای سایر شروط هم عمل میکنه

#if
این یعنی اگر شرطی برقرار بود کامپایل شه

مثال
#if value==10
void helloworld()
{
cout«"Hello World!"«endl;
}
#endif // world

این یعنی اگه
value
تعریف شده بود و مقدار مساوی10 بود انوقت اون قسمت کامپایل شه

#elif
مثل
else if
میمونه
مثال
#if value==10
void helloworld()
{
cout«"Hello World!"«endl;
}
#elif value==20
void hellocity()
{
cout«"Hello city!"«endl;
}
#endif // world

این یعنی اگه مقدار برابر 10 بود اون قسمت اضافه شه در غیر اینصورت اگر مساوی بیست بود اون یکی قسمت اضافه شه
یه مقدار بدید و تست کنید حتما
#define value 10
مثلا


#error
این یعنی با استفاده از اون شرط ها میتونیم ارور درست کنیم
مثال

#if value!=10
#error value is not 10!
#endif // world
این یعنی اگر مقدار چیزی غیر از 10 باشه
ارور میده
اون چیزی که جلوی اروره پیامیه که نمایش داده میشه

#undef
از بین بردن یک تعریف که از قبل تعریف شده

مثال
#define L

#ifndef L
#error L is not defined
#endif // L

#undef L

#ifndef L
#error L is not defined
#endif // L

در ارور دوم ارور میده
چرا؟
چون
L
تعریفش رو پاک کردیم
اما بعدش اون رو از بین بردیم
همونطور که قبلا گفتم کد از بالا به پایین کامپایل میشه

یه مثال دیگه
#define L 10

#if L==10
void a(){cout«"hello a!"«endl;}
#endif // L

#undef L
#define L 20

#if L==20
void b(){cout«"hello b!"«endl;}
#endif // L

در مثال بالا هر دو کامپایل میشن
نکته
اون
undef
رو هم نذارید فرقی نمیکنه
ولی اگه

#define L 10

#if L==10
void a(){cout«"hello a!"«endl;}
#endif // L

#undef L

#ifndef L
void b(){cout«"hello b!"«endl;}
#endif // L

تو مثال بالا برای قسمت دوم وجودش مهمه


این یه آموزش ساده از تعدادی از دستورات پیش پردازنده بود
#بیشتر_بدانید
اهمیت هدر گارد ها
header guards

#ifndef HEADER_H

#define HEADER_H


#endif


حتما همتون تا حالا هدر گاردهایی مثل بالا رو دیدید
اما اهمیتشون چیه؟
خیلی خلاصه جلوگیری از تعریف چندبار سهوی و ارور های ناخواسته

در مثال زیر خیلی ساده بهتون میگم چرا مفیدن
یه نکته » من برای جلوگیری از تکرار اسم هدر گارد ها از روشی استفاده میکنم که تقریبا اکثر برنامه نویس ها از اون روش استفاده میکنن
#define HEADERNAME_H

که
HEADERNAME
همون نام فایل هدر هست

حالا مثال:
محتویات فایل header2.h


#include <iostream>

using namespace std;


void helloworld()

{

    cout<<"Hello World!"<<endl;

}


محتویات فایل header1.h
#include "header2.h"


void sayhello()

{

    helloworld();

}

محتویات فایل اصلی سی پلاس پلاس
#include "header1.h"



int main(){

   sayhello();

   return 0;

}


میبینیدکه ارور نمیده و اجرا میشه
حالا
#include "header2.h"

`
رو به فایل سی پلاس پلاس اضافه کنین


#include "header1.h"

#include "header2.h"


int main(){

   sayhello();

   return 0;

}


چه قبل اون header1.h بذارید چه بعدش ارور میده چون
`void helloworld()

دوبار تعریف میشه

حالا بیایید هدرگارد برای هر دو هدر تعریف کنید
محتویات جدید header2.h
#ifndef HEADER2_H

#define HEADER2_H


#include <iostream>

using namespace std;


void helloworld()

{

    cout<<"Hello World!"<<endl;

}


#endif


محتویات جدید header1.h
#ifndef HEADER1_H

#define HEADER1_H


#include "header2.h"


void sayhello()

{

    helloworld();

}


#endif


و محتوای فایل اصلی
#include "header1.h"

#include "header2.h"


int main(){

   sayhello();

   return 0;

}


دیگه ارور نمیده
چند بار هم اینکلاد کنم خیالتون راحت شه
#include "header1.h"

#include "header2.h"

#include "header1.h"

#include "header2.h"

#include "header1.h"

#include "header2.h"

#include "header1.h"

#include "header2.h"


int main(){

   sayhello();

   return 0;

}


امیدوارم پی برده به باشید به اهمیتشون
نکته آخر: نوشته اشکان♻️
در بعضی کامپایلر ها لازم نیست
این همه تایپ
کنیم
#pragma once
دقیقا همین کار رو انجام می
ده
بازم میگم فق
ط
بعضی کامپایلر ها


موفق باشید
کدهاتون هم بی باگ
#بیشتر_بدانید
اهمیت هدر گارد ها
header guards

#ifndef HEADER_H

#define HEADER_H


#endif


حتما همتون تا حالا هدر گاردهایی مثل بالا رو دیدید
اما اهمیتشون چیه؟
خیلی خلاصه جلوگیری از تعریف چندبار سهوی و ارور های ناخواسته

در مثال زیر خیلی ساده بهتون میگم چرا مفیدن
یه نکته » من برای جلوگیری از تکرار اسم هدر گارد ها از روشی استفاده میکنم که تقریبا اکثر برنامه نویس ها از اون روش استفاده میکنن
#define HEADERNAME_H

که
HEADERNAME
همون نام فایل هدر هست

حالا مثال:
محتویات فایل header2.h


#include <iostream>

using namespace std;


void helloworld()

{

    cout<<"Hello World!"<<endl;

}


محتویات فایل header1.h
#include "header2.h"


void sayhello()

{

    helloworld();

}

محتویات فایل اصلی سی پلاس پلاس
#include "header1.h"



int main(){

   sayhello();

   return 0;

}


میبینیدکه ارور نمیده و اجرا میشه
حالا
#include "header2.h"

`
رو به فایل سی پلاس پلاس اضافه کنین


#include "header1.h"

#include "header2.h"


int main(){

   sayhello();

   return 0;

}


چه قبل اون header1.h بذارید چه بعدش ارور میده چون
`void helloworld()

دوبار تعریف میشه

حالا بیایید هدرگارد برای هر دو هدر تعریف کنید
محتویات جدید header2.h
#ifndef HEADER2_H

#define HEADER2_H


#include <iostream>

using namespace std;


void helloworld()

{

    cout<<"Hello World!"<<endl;

}


#endif


محتویات جدید header1.h
#ifndef HEADER1_H

#define HEADER1_H


#include "header2.h"


void sayhello()

{

    helloworld();

}


#endif


و محتوای فایل اصلی
#include "header1.h"

#include "header2.h"


int main(){

   sayhello();

   return 0;

}


دیگه ارور نمیده
چند بار هم اینکلاد کنم خیالتون راحت شه
#include "header1.h"

#include "header2.h"

#include "header1.h"

#include "header2.h"

#include "header1.h"

#include "header2.h"

#include "header1.h"

#include "header2.h"


int main(){

   sayhello();

   return 0;

}


امیدوارم پی برده به باشید به اهمیتشون
نکته آخر: نوشته اشکان♻️
در بعضی کامپایلر ها لازم نیست
این همه تایپ
کنیم
#pragma once
دقیقا همین کار رو انجام می
ده
بازم میگم فق
ط
بعضی کامپایلر ها


موفق باشید
کدهاتون هم بی باگ
#بیشتر_بدانید
تا حالا فکر کردید چجوری بی نهایت آرگومان داشته باشید؟
دونوعش رو بهتون معرفی میکنم

variadic arguments
بچه ها

بچه ها
variadic arguments
😄
اینم معرفی

یه تابع میخوام باهاش بنویسم که هرتعداد براش عدد بفرستیم جمع کلشون رو برامون برگردونه
#include <iostream>

#include <stdarg.h>

using namespace std;


//! جمع اعدادی که براش میفرستیم رو برمیگردونه

//! count همون تعداد آرگومان هایی که براش میفرستیم

//! ... همون آرگومان بی نهایت

int FOO(int count,...)

{

    int out=0;//خروجی

    va_list args;// باید آرگومان ها رو توی این بریزیم

    va_start(args,count);// آرگومان ها رو توی اون میریزیم

    //count

    // نقطه شروع هست نه تعداد اشتباه نکنید


    for(int i=0;i<count;i++)//این حلقه به تعداد آرگومان ها اجرا میشه

    {

        int a = va_arg(args,int);//متغیر بعدی رو به عنوان عدد صحیح لود میکنیم

        out+=a;// به خروجی اضافه میکنیم

    }


    va_end(args);//تموم شد

    return out;

}


int main(){

   cout<<FOO(6,2,3,2,3,4,5)<<endl;//2+3+2+3+4+5=19

   return 0;

}


جالب بود نه؟
بیایید ازقصد اشتباه کنیم و آرگومان اول که تعداد هست رو اشتباه بفرستیم
FOO(8,2,3,2,3,4,5)

اوه
عجب وضعیت باقالی شد😂

برای اینکه دیگه اشتباه نکنیم با استفاده از یه ماکرو آرگومان ها رو میفرستیم که آرگومان اولش رو خودش برامون حساب کنه
جالبه بهتون بگم اونم با آرگومان های بی نهایت کار میکنه😄
#define CountArgs(...)

اگه
__VA_ARGS__رو جلوش بذاریم تمام آرگومان بی نهایت ها رو عیننا مثل قالب میذاره جلوش
اول یه ماکرو مینویسیم تا تعدادش رو بشماریم

#define CountArgs(...) CountArgs2( (__VA_ARGS__,10,9,8,7,6,5,4,3,2,1) )

#define CountArgs2(args) CountArgs3 args

#define CountArgs3(_1,_2,_3,_4,_5,_6,_7,_8,_9,_10,argsc,...) argsc

کد بالا تا حداکثر 10 تا آرگومان رو میتونه بشماره اگه بیشتر میخواهید
بیشتر بنویسید
بیایید تستش کنیم
cout<<CountArgs(1,1,1,1,1)<<endl;//5

خروجی 5 میده
بذارید اول تحلیلش کنیم
هر خط یه مرحله از ماکرو رو نشون میده
اگه نمیدونید ماکرو چیه
چند تا پست بالاتر یه توضیح ساده ازش آماده کردم
CountArgs(1,1,1,1,1)


CountArgs2( (1,1,1,1,10,9,8,7,6,5,4,3,2,1) ) // دوتا پرانتز


CountArgs2(args) CountArgs3 args


CountArgs2(args) CountArgs3 (1,1,1,1,10,9,8,7,6,5,4,3,2,1) //args جابه جا میشه


CountArgs3(_1,_2,_3,_4,_5,_6,_7,_8,_9,_10,argsc,...) argsc


CountArgs3(1,1,1,1,1,10,9,8,7,6,5 ,...) argsc  // غیره ش رو ننوشتم دیگه احتیاجی نیست


argc نیتجه ی 5 رو تحویلمون میده

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

#define MYARGCOUNTER(...) CountArgs(__VA_ARGS__),__VA_ARGS__

حالا کدمون رو تغییر میدیم

cout<<FOO(MYARGCOUNTER(2,3,2,3,4,5))<<endl;

MYARGCOUNTER
کاری که میکنه اینه که
CountArgs(2,3,2,3,4,5) , 2,3,2,3,4,5

این رو برامون میسازه و
CountArgs
هم که میشمره پس میشه
6,2,3,2,3,4,5

که مثل همون کد اولمون میشه

نکته اشکانی😐
CountArgs
به خاطر اینکه در بعضی کامپایلر ها درست کامپایل نمیشه سه بار بازخوانی کردم وگرنه مثلا با 2 بار هم روی جی سی سی عم
ل میکنه
#define CountArgs(...) CountArgs2(__VA_ARGS__,10,9,8,7,6,5,
4
,3,2,1)
#define CountArgs2(_1,_2,_3,_4,_5,_6,_7,_8,_9,_10,argsc,..
.) argsc
ولی کد بالا در ویژوال
2012
درست کامپایل نخواهد شد

نکته دو:
تابع بالا با نام
variadic function
و ماکرو بالا با نام
variadic macro
شناخته میشن

نکته سه:
توابعی مثل
printf
نوعی
variadic function
هستن

اینم یه مبحثی بود هزار ماشالا مخ درگیر کن
امیدوارم لذت برده باشید

♥️تک تک ارور های زندگی تون پرپر
#لازمه_بدانید !

لازمه بدانید 😄 که خاصیت استفاده مجدد از کد چیه
خیلی هم لازمه

من در پایین با روش شی گرایی خیلی ابتدایی 😅 این کار رو انجام خواهم داد و با دو روش مختلف ران تایم یا کامپایل تایم اون ها رو لود میکنم

مثالی که میزنم ساده هست
یه استراکت که کارش لود کتاب خانه ریاضی هست
البته نترسید قرار نیست همه چیز داشته باشه😄
برای مثال ما فقط یه تابع برای محاسبه قدر مطلق داره
یه بار با استفاده از کتاب خانه استاندارد سی لود میکنیم و بار دیگه یه تابع که خودمون نوشتیم و مثلا کتابخانه خودمونه (الکی مثلا😅)

یه ماکرو برای کامپایل تایم
#define USING_MY_MATH_LIB

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

#include <iostream>
#include <cmath>
using namespace std;

#define USING_MY_MATH_LIB 0 // الان مثلا میخوایم از کتاب خانه استاندارد استفاده شه

#if USING_MY_MATH_LIB == 1
int myabs(int in) // این همون کتابخونه الکیه هستش
{
cout<<"myabs called!"<<endl;// مطمئن شیم فراخوانی شده
if(in<0)
return -in;
return in;
}
#endif // USING_MY_MATH_LIB

struct themathlib // استراکت بیس برای شی گرایی
{
virtual int Abs(int in)=0;// virtual برای شی گرایی راحت تر شه
};

#if USING_MY_MATH_LIB == 1 // از کتاب خانه خودمون استفاده میکنیم
struct mymathlib: themathlib // یه استراکت فرزند مشتق از بیس کار برای کتاب خانه خودمون
{
int Abs(int in){ return myabs(in); } // همون تابع خودمون رو صدا زدیم
};
#endif // USING_MY_MATH_LIB

#if USING_MY_MATH_LIB == 0// از کتابخانه استاندارد استفاده میکنیم
struct stdmathlib: themathlib
{
int Abs(int in){ return abs(in); }// تابع کتابخانه استاندارد
};
#endif // USING_MY_MATH_LIB

themathlib* intializemathlib()// برای ایجاد کردن یه شی جدید
{
themathlib* out=NULL;// خروجی
#if USING_MY_MATH_LIB ==1
out = new mymathlib; // کتابخانه خودمون
#endif // USING_MY_MATH_LIB

#if USING_MY_MATH_LIB ==0
out = new stdmathlib; // کتاب خانه استاندارد
#endif // USING_MY_MATH_LIB
return out;
}

int main()
{
themathlib* obj = intializemathlib();// یه شی ایجاد میکنیم
int in;
cout<<"Enter Integer:"<<endl;
cin>>in;
cout<<obj->Abs(in)<<endl;// به همین راحتی استفاده میکنیم
delete obj;
return 0;
}



حالا فرض کنیم تصمیمون عوض شد و خواستیم از کتابخانه خودمون استفاده کنیم
چی کار باید کنیم؟
خب خیلی راحته فقط کافیه
#define USING_MY_MATH_LIB 1
باشه

هر دو حالت رو حتما تست کنید

همونطور که میدونید ماکرو ها برای موقع کامپایل هستن و تکلیفشون رو برنامه نویس مشخص میکنه
حالا فرض کنیم کاربر نهایی قراره تکلیف رو مشخص کنه
و به اصطلاح کارها همون موقع اجرا انجام شه
#include <iostream>
#include <cmath>
using namespace std;

int myabs(int in)
{
cout<<"myabs called!"<<endl;
if(in<0)
return -in;
return in;
}

struct themathlib
{
virtual int Abs(int in)=0;
};

struct mymathlib: themathlib
{
int Abs(int in){ return myabs(in); }
};

struct stdmathlib: themathlib
{
int Abs(int in){ return abs(in); }
};

themathlib* intializemathlib(bool usingmylib)// اگه صحیح بود از کتابخانه خودمون استفاده میشه وگرنه از استاندارد
{
themathlib* out=NULL;// خروجی
if(usingmylib)
out = new mymathlib; // کتابخانه خودمون
else
out = new stdmathlib; // کتاب خانه استاندارد
return out;
}

int main()
{
bool type;
int in;
cout<<"Enter the Math library type:\n 0: standard library\n 1: my library\n";// پیام راهنمایی
cin>>type; //نوع کتابخانه
cout<<"Enter Integer:"<<endl;// پیام راهنمایی
cin>>in;//ورودی
themathlib* obj = intializemathlib(type);// ایجاد یک شی
cout<<obj->Abs(in)<<endl;//اجرای تابع
delete obj;
return 0;
}


یه مقایسه سطحی از حالت اول و دوم:
در حالت اول فقط وقتی کلاس ایجاد میشه و کامپایل میشه که استفاده شده باشه ولی حالت دوم اینطور نیست پس مسلما حجم برنامه زیاد خواهد شد و چون شرط ها موقع اجرا استفاده میشن پس سرعت اجرا یه ذره کم میشه
اما در عوض کاربر نهایی تکلیف رو مشخص میکنه و دستش بازتره
در صورتی که تکلیف حالت اول رو فقط برنامه نویس میتونه مشخص کنه

یه چیزی برای اونایی که میخوان یاد بگیرن :
بد نیست خودتون هم یه کتابخانه سوم بنویسید و اضافه کنید تا خوب استفاده مجدد رو بشناسین
1396/5/10
امیدوارم مفید بوده باشه
دنیای بی باگی رو براتون
#بدون_شرح

امروز یه ماکرو که برای پیدا کردن سریع تر محل ارور های ران تایم و دلیلش مینویسم و باهاتون به اشتراک میذارم

یه ماکرو که پیام ارور رو میگیره و به همراه پیام ارور
نام فایل و نام تابع و خط ارور رو هم به همراه ارور مشخص میکنه

برای تست هم یه متغیر قلابی داریم که اگه غلط بود ارور بده

#include <stdexcept>
using namespace std;

#define MYERROR( errormessage ) {char _errormessage[1000];sprintf(_errormessage,"\n\tError!!!\n\tFile=\"%s\"\n\tFunction=\"%s\"\n\tLine=\"%d\"\n\tProblem=\"%s\"",__FILE__,__FUNCTION__,__LINE__,errormessage); throw std::runtime_error(_errormessage);}

int main()
{
bool fakecheck = false;
if(!fakecheck)
MYERROR("fake check is false!");

return 0;
}


کدهاتون بی باگ
#بیشتر_بدانید

این بار میخوام با کمک تکنیک
SFINAE
ماکرویی رو آماده کنم که تابع یه شی رو صدا بزنه به شرط اینکه وجود داشته باشه!!!

یعنی اگه وجود نداشت بی خیال صدا زدن اون بشه!!!

کامپایلری که من با استفاده از اون خروجی گرفتم:GCC v.5.1.0

حالا اولین قسمت کار رو مینویسیم
استراکتی که یه مقدار استاتیک داره که مشخص میکنه آیا یه تابع در اون نوع وجود داره یا نه

پس حتما با
SFINAE
آشنایی داشته باشید

این ها استراکت های مثال هستن:

struct MyStruct1
{
void sayhello(){cout<<"Hello!\n"<<endl;}
};

struct MyStruct2
{

};

استراکتی که کار چک کردن اینکه تابع وجود داره رو انجام میده:
//چک کنیم آیا تابع "سی هلو" داره یا نه
//T: همون نوع کلاس یا استراکتی که میخواییم چک کنیم
//FuncType : نوع اشاره گر به تابع عضو که میشه نوع خود تابع
template<typename T,typename FuncType>
struct has_sayhello
{
//یه استراکت تست که دو تا تمپلیت آرگومان میگیره . اولیش نوع و دومیش یه چیزی از نوعش
template <typename U,U> struct AAA;
// یه تابع چک کردن با استراکت بالا . اگه نوع آ دارای اون تابع بود این تابع قابل فراخوانیه. نوع بازگشتیش هم یه چیزی هست که سایزش با تابع بعدی یکی نباشه
// sayhello همون تابعیه که میخوایم چک کنیم وجود داره یا نه
template <typename A> static char Check(AAA<FuncType,&A::sayhello >* in);
// اگه تابع بالا فراخوانی نشه این یکی فراخوانی میشه
template <typename A> static int Check(...);

// اگه نوع بازگشتی کاراکتر بود یعنی تابع وجود داره . چک کردن رو با سایزش انجام میدیم
const static bool value = sizeof(Check<T>(0))==sizeof(char);
};


یه مثال استفاده ازش:
    MyStruct1 a;
MyStruct2 b;
//void(MyStruct1::*)() نوع اشاره گر به سی هلو هست
cout<<has_sayhello<MyStruct1,void(MyStruct1::*)()>::value<<endl;
cout<<has_sayhello<MyStruct2,void(MyStruct2::*)()>::value<<endl;

به همین راحتی چک کردن رو انجام دادیم
ولی در پایان تبدیلش میکنیم به یه ماکرو که زیاد تایپ نکنیم
#define _HAS_MEM(NAME,FUNCNAME)\
template<typename T,typename Sign>\
struct NAME\
{\
template <typename U,U> struct AAA;\
template <typename A> static char Check(AAA<Sign,&A::FUNCNAME >* in);\
template <typename A> static int Check(...);\
const static bool value = sizeof(Check<T>(0))==sizeof(char);\
};

و حالا کافیه به جای اینکه اون همه تایپ کنیم فقط ماکرو زیر رو فراخونی کنیم

_HAS_MEM(has_sayhello,sayhello);
int main()
{
MyStruct1 a;
MyStruct2 b;

cout<<has_sayhello<MyStruct1,void(MyStruct1::*)()>::value<<endl;
cout<<has_sayhello<MyStruct2,void(MyStruct2::*)()>::value<<endl;

return 0;
}

به همین راحتی
حالا میریم سراغ قسمت دوم کار
استراکتی که یه تابع داره که تابع مارو فراخونی میکنه به شرط اینکه مقدار بولین ورودیش صحیح باشه
//یه استراکت که یه تابع داره که فقط وقتی فراخوانی رو انجام میده که مقدار دوم تمپلیت صحیح باشه
// مقدار دوم همون استراکت بالایی هست
//T: نوعی که باید به تابع بدیم
//value: باید صحیح باشه تا فراخوانی شه . اور لودش میکنیم و تکلیف مقدار غلط رو هم مشخص میکنیم
template<typename T,bool value>
struct call_if_say_hello{
// ARGS همون نوع آرگومان هایین که باید به تابع بفرستیم
// از این بعدا استفاده میکنیم
template<typename ...ARGS>
// args اگه مقداری نداشته باشه چیزی به تابع نمی فرسته
// sayhello هم که تابعمونه دیگه
static void call(T in,ARGS... args){in.sayhello(args...) ;}
};
// اورلود استراکت بالا وقتی که مقدار دومی صحیح نیست
template<typename T>
struct call_if_say_hello <T,false>{
// چیزی وجود نداره که فراخوانی شه
template<typename ...ARGS>
static void call(T in,ARGS... args){}
};


یه مثال استفاده ازش:
    MyStruct1 a;
MyStruct2 b;

call_if_say_hello<MyStruct1,has_sayhello<MyStruct1,void(MyStruct1::*)()>::value>::call(a);
call_if_say_hello<MyStruct2,has_sayhello<MyStruct2,void(MyStruct2::*)()>::value>::call(b);


پس تا اینجا تقریبا کار رو انجام دادیم😊

ولی خب اگه باز دقت کنین استراکت بالا هم میتونه تبدیل بشه به ماکرو تا این همه تایپ نکنیم هر بار
#define _CALL_IF(NAME,FUNCNAME)\
template<typename T,bool value>\
struct NAME{\
template<typename ...ARGS>\
static void call(T in,ARGS... args){in.FUNCNAME(args...) ;}\
};\
template<typename T>\
struct NAME <T,false>{\
template<typename ...ARGS>\
static void call(T in,ARGS... args){}\
};
یادداشت های برنامه نویس آماتور | Amateur programmer notes
#بیشتر_بدانید این بار میخوام با کمک تکنیک SFINAE ماکرویی رو آماده کنم که تابع یه شی رو صدا بزنه به شرط اینکه وجود داشته باشه!!! یعنی اگه وجود نداشت بی خیال صدا زدن اون بشه!!! کامپایلری که من با استفاده از اون خروجی گرفتم:GCC v.5.1.0 حالا اولین قسمت کار…
مثال استفاده ازش:

_HAS_MEM(has_sayhello,sayhello);
_CALL_IF(call_if_say_hello,sayhello);


int main()
{
MyStruct1 a;
MyStruct2 b;

call_if_say_hello<MyStruct1,has_sayhello<MyStruct1,void(MyStruct1::*)()>::value>::call(a);
call_if_say_hello<MyStruct2,has_sayhello<MyStruct2,void(MyStruct2::*)()>::value>::call(b);

return 0;
}

به همین راحتی

حالا بریم سراغ قسمت سوم کار:
اگه نگاه کنین تو تابع هم زیاده نویسی کردیم .
یه تابع دیگه مینویسیم اینکار رو انجام بده
البته نام تابع بالایی حتما باید مشخص باشه
_HAS_MEM(has_sayhello,sayhello);
_CALL_IF(call_ifsay_hello,sayhello);

// فقط یه تابع که مقدار میگیره و اگه وجود داشت فراخاونی میکنه
template<typename T>
void caller_sayhello(T in)
{
call_ifsay_hello<T,has_sayhello<T,void(T::*)()>::value>::call(in);
}

int main()
{
MyStruct1 a;
MyStruct2 b;

caller_sayhello(a);
caller_sayhello(b);

return 0;
}

آقا اینم میشه تبدیل به ماکرو شه خیلی راحت:
#define _CALLER(NAME,FUNCNAME)\
template<typename T>\
void NAME(T in)\
{\
_call_if_##FUNCNAME<T,_has_##FUNCNAME<T,void(T::*)()>::value>::call(in);\
}
نکته: ## برای چسبوندن
FUNCNAME
به
_call_if_
هست

مثال استفاده ازش:
_HAS_MEM(_has_sayhello,sayhello);
_CALL_IF(_call_if_sayhello,sayhello);
_CALLER(_caller_sayhello,sayhello);

int main()
{
MyStruct1 a;
MyStruct2 b;

_caller_sayhello(a);
_caller_sayhello(b);

return 0;
}

حالا بیایید یه ماکرو بنویسیم که اون سه تا ماکرو رو اضافه کنه و یه ماکرو دیگه که کار فراخوانی رو راحت تر کنه
#define CONFIG_CALL_IF_EXIST(FUNCNAME )\
_HAS_MEM(_has_##FUNCNAME,FUNCNAME)\
_CALL_IF(_call_if_##FUNCNAME,FUNCNAME )\
_CALLER(_caller_##FUNCNAME,FUNCNAME )

#define CALL_IF_EXIST(OBJ,FUNCNAME)\
_caller_##FUNCNAME(OBJ );

و حالا دیگه لازم نیست اووووووووووون همممممه تایپ کنیم😂

و این هم مثال استفاده ازش
CONFIG_CALL_IF_EXIST(sayhello)

int main()
{
MyStruct1 a;
MyStruct2 b;

CALL_IF_EXIST(a,sayhello);
CALL_IF_EXIST(b,sayhello);

return 0;
}

حالا فرض کنین یه تابع آرگومان دار باشه
اونوقت چی؟🤔

یه گله ماکرو ی دیگه برای آرگومان دارهاش مینویسیم
// پرانتز باز
#define PAROPEN (
// پرانتز بسته
#define PARCLOSE )

//این کالر هست و برای این یکی باید نوع آرگومان ها رو هم بفرستید
// ARGS هم یه ماکرو دیگه هست این شکلی فرستاده میشه
// ( آرگومان ها )
// اگه گیج تون کرد پایین تر رو نگاه کنید
#define _CALLER_WITH_ARGS(NAME,FUNCNAME,ARGS)\
template<typename T , typename... args>\
void NAME(T in,args... _args)\
{\
call_if_##FUNCNAME<T,__has_##FUNCNAME<T,void(T::*) ARGS >::value>::call(in,___args...);\
}

// فراخوانی با آرگومان
#define CALL_IF_EXIST_WITH_ARGS(OBJ,FUNCNAME,...)\
caller_##FUNCNAME(OBJ, VA_ARGS__ );

// کانفیگ با آرگومان
// اون آرگومان های بینهایت فقط نوع آرگومان های تابعمون هست
#define CONFIG_CALL_IF_EXIST_WITH_ARGS(FUNCNAME,...)\
_HAS_MEM(__has_##FUNCNAME,FUNCNAME)\
_CALL_IF(__call_if_##FUNCNAME,FUNCNAME )\
_CALLER_WITH_ARGS(__caller_##FUNCNAME,FUNCNAME, PAROPEN VA_ARGS PARCLOSE )
// PAROPEN VA_ARGS PARCLOSE
// تبدیل میشه به
// ( آرگومان هامون)

//_CALL_IF
// هم که قبلا گفتم میتونیم آرگومان هم براش بفرستیم
//_HAS_MEM هم که اصن به این چیزا کاری نداره


تمومه!!!
یه مثال مینویسم و از هر دوتاش استفاده میکنم

struct MyStruct1
{
void sayhello(){cout<<"Hello!\n"<<endl;}
};

struct MyStruct2
{
void shownumber(int in){cout<<in<<endl;}
};


CONFIG_CALL_IF_EXIST(sayhello)
CONFIG_CALL_IF_EXIST_WITH_ARGS(shownumber,int)

int main()
{
MyStruct1 a;
MyStruct2 b;

CALL_IF_EXIST(a,sayhello);
CALL_IF_EXIST(b,sayhello);
CALL_IF_EXIST_WITH_ARGS(a,shownumber,int(50));
CALL_IF_EXIST_WITH_ARGS(b,shownumber,int(100));

return 0;
}

این هم ماشالا از توانایی های دیگه سی پلاس پلاس بود که شاید در همین کانال در آینده ازش استفاده کنم

امیدوارم لذت برده باشید♥️
@Simpleprogramming