1- اجتناب از بررسی تساوی در اعداد اعشاری
اعداد اعشاری در محاسبات ریاضی - مانند عمل تقسیم یا محاسبه توابع مثلثاتی و غیره - ممکن است حاوی مقدار بسیار ناچیزی خطا باشند، که عموما ناشی از عملیات گرد کردن و قطع کردن نتایج مراحل میانی محاسبات هستند. در چنین حالتی به کار بردن عملگر تساوی لزوما به نتیجه درست ختم نمیشود. به عنوان مثال:
کد:
float f
/* f عملیات محاسباتی روی متغیر */
if( f == 0.0 )
{
/*
عملیات بلاک شرط
*/
}
زمانی که تصور میکنید پس از انجام محاسبات مقدار f برابر صفر خواهد شد، ممکن است مقدار آن عدد بسیار کوچکی مانند 0.000000019 باشد، که با عدد صفر برابر نیست. بنابراین عملکرد قطعه کد فوق با شکست مواجه خواهد شد.
در چنین حالتی بهتر است قرار گرفتن متغیر اعشاری در یک بازه را مورد بررسی قرار دهید:
کد:
float f
/* f عملیات محاسباتی روی متغیر */
if( f < 0.0001 && f > - 0.0001 )
{
/*
عملیات بلاک شرط
*/
}
2- استفاده از اشارهگر به جای اندیس در پیمایش آرایه
گاهی نیاز به پیمایش یک آرایه از ابتدا تا انتها وجود دارد. این پیمایش ممکن است برای مقداردهی عناصر آرایه، چاپ آنها، یا جستجوی مقدار خاصی انجام گیرد. چنین عملی در روش مختلف دارد.
اولین روش استفاده از اندیس آرایه است:
کد:
int array[ 10 ];
int i;
for( i = 0 ; i < 10 ; i++ )
{
array[ i ] = 0;
}
در هر تکرار این حالت، کامپایلر برای یافتن محل [ array[ i یک عمل ضرب عدد i در اندازه هر عنصر آرایه (در اینجا int) و جمع آن با آدرس شروع آرایه انجام میدهد.
در روش دوم که روش سریعتری نسبت به روش اول است، از اشارهگر استفاده میشود:
کد:
int array[ 10 ];
int *pInt, *pEnd = &array[9];
for( pInt = array ; pInt <= pEnd; pInt++ )
{
*pInt = 0;
}
در این حالت اشارهگر pInt جایگزین شمارنده i شده است. اما دیگر نیازی به عملیات ضرب و جمع برای محاسبه محل عناصر وجود ندارد. بنابراین سرعت پردازش بالا میرود.
توجه: برای آشنایی با مفهوم اشارهگر و عملیات ریاضی روی آنها به مطلب با عنوان اشارهگرها در زبان ++C مراجعه نمایید.
3- دقت در استفاده از اشارهگر به عنوان مقدار خروجی تابع
یکی از اشتباهات رایج در میان برنامهنویسان ++C، برگشت یک اشارهگر تعریف شده درون تابع است:
کد:
char* HexToAscii( unsigned nHex )
{
char AsciiValue[10];
/*
عملیات تولید خروجی تابع
*/
return AsciiValue;
}
آرایه AsciiValue درون تابع تعریف شده است. بر اساس قطعه کد فوق، مقدار خروجی تابع اشارهگر به ابتدای این آرایه است. اما در زبان برنامهنویسی ++C، زمانی که یک متغیر در یک تابع تعریف میشود، با خروج از تابع ارزش آن از بین رفته، و حافظه آزاد میشود. بنابراین آدرسی که این تابع بازمیگرداند، آدرسی است که فضای آن آزاد شده، و کامپایلر میتواند از آن فضا برای مقاصد دیگر استفاده کند!
روش سنتی برای رفع این مشکل، تعریف آرایه به صورت static است:
کد:
char* HexToAscii( unsigned nHex )
{
static char AsciiValue[10];
/*
عملیات تولید خروجی تابع
*/
return AsciiValue;
}
در این حالت آرایه فضای AsciiValue هرگز آزاد نشده، و از بیرون تابع نیز در دسترس خواهد بود. اما این روش همیشه جوابگو نبوده، و ممکن است مشکلاتی به همراه داشته باشد.
راه حل بهتر آن است که آدرس یک بافر به تابع ارسال شود، و تابع خروجی خود را درون آن تولید کرده، و آدرس همان بافر را بازگرداند:
کد:
char* HexToAscii( unsigned nHex, char *pBuffer, int nBufferSize)
{
/*
عملیات پر کردن بافر با مقدار مناسب
*/
return pBuffer;
}
در این حالت pBuffer آدرس شروع بافر، و nBufferSize اندازه بافر را مشخص میکند. تابع به جای استفاده از یک آرایه داخلی، خروجی را در بافر ریخته، و آدرس آن را باز میگرداند.
توجه: با توجه به ثابت بودن آدرس بافر، میتوان از مقدار بازگشتی تابع نظر کرد. انتقال آدرس ثابت از ورودی به خروجی کاربردهای خاص خود را دارد.
4- کاربرد کلمه کلیدی volatile
متغیرهایی از نوع volatile متغیرهایی هستند که ممکن است مقدار آنها توسط یک پردازش خارجی تغییر یابد. این پردازش میتواند وقوع یک وقفه، یا تغییر از طریق یک پردازش موازی باشد.
کامپایلرهای زبان برنامهنویسی ++C اغلب از روشهای بهینهسازی برای دستیابی به متغیرهای تعریف شده در طول برنامه استفاده میکنند. به عنوان مثال، آخرین تغییر هر متغیر در طول پردازش را به عنوان مقدار آن متغیر در نظر گرفته، و اگر دستوری مقدار متغیر را تغییر ندهد، همان مقدار نهایی را در پردازش مدنظر قرار میدهد. اما متغیرهایی از نوع volatile متغیرهایی هستند که لزوما از طریق پردازش جاری تغییر نمیکنند. بنابراین ممکن است مقدار آنها توسط پردازش دیگر یا یک وقفه عوض شود، بدون آنکه کامپایلر متوجه باشد. برای جلوگیری از چنین خطایی، کلمه کلیدی volatile در ابتدای تعریف متغیر استفاده میشود، تا کامپایلر عملیات بهینهسازی روی آن انجام نداده، و در هر بار فراخوانی اسم متغیر در طول برنامه، مقدار آن را از محل حافظه بخواند.
کد:
volatile int Item;
int WaitForChange()
{
Item = 0;
while(!Item); // حلقه با بدنه تهی
return Item;
}
این تابع تا زمانی که Item صفر باشد درون حلقه باقی خواهد ماند. متغیر Item به صورت volatile معرفی شده است تا با هر بررسی شرط، مقدار Item از حافظه واکشی شود. این مقدار ممکن است توسط یک پردازش موازی دیگر، یا یک وقفه تغییر کند.
5- دقت در تعریف متغیرها به صورت یک عبارت
همانطور که میدانید در زبان ++C میتوان چند متغیر همنوع را با یک عبارت تعریف کرد:
اینطور به نظر میرسد که عبارتی مانند
سه اشارهگر به عدد صحیح را مشخص کند. اما اینگونه نیست! در واقع اشارهگر p، و اعداد صحیح q و r معرفی شدهاند. عملگرهایی مانند * (برای اشارهگر) و & (برای متغیر مرجع) تنها روی اولین عبارت بعد از خود عمل میکنند.
شکل صحیح عبارت فوق به این ترتیب است:
به بیان دیگر، هیچ تفاوتی نمیکند عملگری مانند * چسبیده به int باشد، یا نام متغیر. تمام عبارتهای زیر متغیر p را به عنوان اشارهگر به متغیر صحیح، و q را عنوان متغیر صحیح معرفی میکنند:
کد:
int* p, q;
int * p, q;
int *p, q;
int*p,q;
منبع کمکی: کتاب The Best C/C++ Tips Ever، نوشته Anthony Porter، انتشارات Osborne McGraw-H