زبان برنامه نویسی سی/علائم و عملگرهای دیگر و اولویتها
جز پیشپردازندهها ، شناسهها ( که شناسهها ، خود میتوانند ماکروها یا تابعهای کتابخانهای باشند ) ، یادداشتها و کلیدواژهها هر آنچه که در زبان C باقی میماند ، عملگر است ؛ جز اینکه کلیدواژه sizeof خود یک عملگر است که در همین موضوع آن را تشریح میکنیم . ابتدا عملگرهای دیگری جز عملگرهای یاد شده در سه موضوع پیشین را فهرست میکنیم و توضیح میدهیم که چه عملی را انجام میدهند و سپس عملگرهای مقدار دهی را بازگو مینمائیم و بعد از آن به نحوه خوانده شدن عبارتها توسط کامپایلر میپردازیم که اولویت عملگرها را که علاوه بر از چپ-به-راست خوانده شدن عبارتها میباشد بیان خواهیم نمود و در نهایت عملگر sizeof را تعریف و تشریح مینمائیم
عملگرهای جانبی
ویرایشعملگر کروشه : عملگرهای کروشه به صورت جفت باز و بسته در مقابل یک شناسه باعث ایجاد یک آرایه به تعداد عددی که داخل جفت کروشههای باز و بسته نوشته شده است میشوند . مثلاً
int a[6];
که باعث ایجاد یک آرایه ۶ عنصری میشود و عنصرها از طریق شناسه آن قابل دسترسی هستند ( رجوع کنید به موضوع آرایه )
عملگر پرانتز و آکولاد : عملگر پرانتز به صورت جفت باز و بسته در مقابل یک شناسه باعث میشود تا شناسه به عنوان تابع تعریف شود که سپس باید بعد از پرانتز بسته یک جفت آکولاد به صورت باز و بسته نوشته شوند که بدنه تابع را تشکیل میدهد . ضمناً میتوان تابع را اعلان نمود ( prototype ) سپس تعریف کرد و هر جا لازم شد احضار و یا همان فراخوانی نمود که باز هم با کمک عملگر پرانتز امکان پذیر است ( رجوع کنید به فصل تابع )
عملگر سمی کالن : عملگر ; که نام آن سِمی کالُن ( semi colon ) است پس از هر حکم باعث به اجرا گذاشته شدن آن توسط کامپایلر میشود . هر حکم میتواند اعلان و یا تعریف متغیرهای پایه ، ترکیبی و یا اعلان و فراخوانی تابع باشد ( که تابع ممکن است یک تابع کتابخانهای باشد ) و البته برخی دستورها مثل continue یا return . مثال : ;float g
عملگر نقطه و اشارهگر به عضو : عملگر « . » یا همان دات و یا نقطه ، اگر پس از شناسه ی نمونه ی یک ساختمان و یا اجتماع بیاید که عملگر نقطه ، پیش از نام یکی از اعضای آن ساختمان یا اجتماع است ، دسترسی به آن شی ( عضو ساختمان یا اجتماع ) را فراهم میکند . همچنین عملگر <- باعث میشود تا با نوشتن نام یک نمونه اشارهگر به ساختمان و یا اجتماع پیش از عملگر و نوشتن عضوی از آن ساختمان یا اجتماع ، پس از عملگر ، نمونه اشارهگر ما به عضو اشاره شده دسترسی پیدا کند . دقت کنید که در نوشتن هیچ کدام از نام نمونه و یا نام نمونه اشارهگر و همچنین عضو ساختمان یا اجتماع نباید فاصلهای بگذارید ( رجوع کنید به موضوع ساختمان و اجتماع و اشارهگر )
عملگر اشاره و آدرس دهی : عملگر * که نام آن استریسک یا همان ستاره است اگر پس از کلیدواژههای سازنده دادهها بیاید ، داده را به عنوان اشارهگر تعریف میکند . سپس برای مقدار دهی آن باید از عملگر آدرس دهی یعنی & که نام امپرسند است ، استفاده نمود تا با قرار دادن شناسه یک داده دیگر در مقابل آن ، اشارهگر به آن داده دیگر اشاره نماید . سپس برای دسترسی به آن داده ، دوباره باید از عملگر استریسک اما این بار بدون نوشتن کلیدواژههای ایجاد کننده دادهها استفاده نمود ( رجوع کنید به موضوع اشارهگر )
عملگر پرانتز برای کست کردن : اگر عملگر پرانتز را به صورت یک جفت پرانتز باز و بسته که تعیین کننده یک نوع داده پیش از آن قرار داشته باشد را بنویسم و داخل پرانتزها ، یک نوع داده را بنویسیم ، کامپایلر با شناسه به عنوان نوع داده داخل پرانتز ، رفتار خواهد نمود مثل : ( رجوع کنید به موضوع تبدیل و جایگزینی دادهها )
(int)a
عملگرهای شرطی : عملگر ? و : به عنوان عملگرهای شرطی در زبان C عمل میکنند مثلاً a ? b : c که اگر a درست true یا غیر 0 باشد b به اجرا در میآید و اگر a غلط false یا 0 باشد ، c به اجرا در میآید . مثل a = (x == 5) ? y; : z که در اینجا اگر مقدار x برابر با 5 باشد متغیر a مقدار y را می گیرد و در غیر این صورت مقدار z را . به مثال دیگری دقت کنید :
a ? b : c ? d : e
در مثال بالا کامپایلر ارزیابی میکند که اگر a درست true یا غیر 0 باشد b را به اجرا میگذارد و اگر نبود c را ارزبابی میکند اگر درست true یا غیر 0 بود d را به اجرا میگذارد و اگر نبود e را به اجرا میگذارد . دقت کنید که کدنویسیهای پیچیده آگاهی بالایی میخواهند و ممکن است شما را به خطا بیاندازند . در ابتدای یاد گیری با کدهای ساده کار شروع کنید و سپس به سراغ کدهای پیچیدهتر و پیچیدهتر بروید
دقت کنید :
در عملگرهای شرطی ، اگر عملوندها از یک نوع داده باشند ، خروجیای که عملگرهای شرطی باز میگردانند ، از همان نوع عملوندها است و شما نمیتوانید از انواع مختلف را استفاده کنید مگر اینکه قابل تبدیل به یکدیگر بوده و یا : اگر هر یک از عملوندها ، اشارهگری به یک داده پوچ باشد ، نتیجه نهایی که عملگرهای شرطی باز میگردانند اشارهگر به پوچ خواهد بود . اگر عملوندها یکی اشارهگر باشد و دیگری اشارهگر تهی باشد ، نتیجه نهایی که باز گردانده میشود از نوع اشارهگری که غیر تهی است خواهد بود . اگر عملوندها نوع داده پوچ دارند ، خروجی از نوع پوچ خواهد بود ؛ به غیر از این ، تبدیلهای غیر مجاز انجام نمیشوند و یا با خطای دیباگر مواجه خواهید و یا برنامه شما معیوب خواهد شد
عملگر ویرگول : عملگر « , » که نام آن کاما است و در پارسی ، معنی ویرگول را میدهد دو نوع عمل را انجام میدهد یکی نقش جدا کننده شناسهها را دارد که جایی که شناسهها را اعلان و یا تعریف میکنید ، میتوانید با کمک عملگر کاما آنها را از یکدیگر جدا کنید . مثلاً
int a = 2 , b = 5 , c = 8;
در لیست پارامترهای یک تابع نیز برای تعیین چند پارامتر ، باید نام پارامترها را که شناسه هستند با عملگر کاما از یکدیگر جدا کنید ؛ همچنین در هنگام فراخوانی تابع نیز برای پاس دادن آرگومانها ، باید برای جدا کردن آنها از عملگر کاما استفاده کنید ( رجوع کنید به فصل تابع ) ؛ عملکرد دیگر عملگر کاما یعنی « , » در نادیده گرفتن عملوند اولی است که این کاربرد کاما به ندرت به کار میآید . در جایی که آرگومانی را میخواهید به یک تابع پاس بدهید یا مقداری را در یک متغیر ذخیره کنید ، اگر از عملگر کاما در مقادیر استفاده کنید ، عملوند اول نادیده گرفته میشود و نتیجه ارزیابی ، مقدار و نوع داده عملوند دوم خواهد بود . همچنین اگر بیش از یک بار از آن استفاده کنید ، تمام عملوندهای ابتدایی نادیده گرفته میشوند و در نهایت عملوند آخری احتساب میشود . مثلاً :
#include<stdio.h>
int main()
{
int x = 10;
int y = 15;
printf("%d", (x, y));
getchar();
return 0;
}
در مثال بالا مقداری که printf چاپ خواهد نمود مقدار y است و x نادیده گرفته میشود . این کاربرد عملگر کاما به ندرت به کار میآید
- دقت کنید : در تمام عملگرهایی که چند عملوند دارند ، عملوندها باید یک نوع داده داشته باشند ( البته برخی از آنها علاوه بر این قانون ، تنها عملوندهای صحیح و یا مثبت و یا صحیح مثبت را میپذیرند ) ؛ در غیر این صورت یا کامپایلر آنها را به همدیگر تبدیل میکند ( رجوع کنید به تبدیل و جایگزینی دادهها ) و یا اخطار میدهد ؛ البته به غیر از استثناهایی که در هر موضوع و برای هر عملگر استثناء نوشتیم
اگر قسمت ابتدایی موضوع را به یاد داشته باشید ، میدانید که عملگرهای زیادی در C وجود دارند که در ابتدای فصل هم بدان اشاره نمودیم که این عملگرها در زبان C به عنوان عملگر بیان نمیشوند اما علائم زیادی نظیر " و # وجود دارند که در واقع عملگر هستند . اما فراموش نکنید که شما نمیتوانید از آنها در عبارتها استفاده کنید و فقط به شکل و نحوه خاص خودشان مورد استفاده هستند که با مطالعه کامل کتاب نقش هر کدام را خواهید فهمید و اینکه کجا مجاز هستید تا از آنها استفاده کنید ( که در مورد دو مثال نوشته شده ، «"» برای ذخیره یک رشته به کار میرود و «#» برای دادن یک دستور مستقیم )
عملگرهای مقداردهی
ویرایشAssignment Operators یا عملگرهای گماشتن و یا عملگرهای مقداردهی ، عملگرهایی هستند که باعث میشوند تا ما بتوانیم در یک شناسه ، مقدار و موجودیای را قرار بدهیم و یا با کمک آنها به مقدار و موجودی آن دسترسی پیدا کرده و آن را تغییر دهیم . همگی آنها در خود علامت مساوی « = » را دارند اما علاوه بر عملگر مقداردهی ساده که همان « = » میباشد ، عملگرهای دیگری وجود دارند که ترکیبی هستند . مثال عملگر مقداردهی ساده را در فصل دادهها بارها دیدهاید ؛ مثل :
- int a = 6 یا ;float y = 53.84795
در تمامی مقداردهیها عملوند سمت چپ باید یک شناسه باشد ولی عملوند سمت راست میتواند یک مقدار عددی یا حرفی ، شناسهای که مقدار دارد و یا مقداری را باز میگرداند ( تابع ) و یا یک ماکرو باشد . بدین ترتیب مقدار و موجودی عملوند سمت راست در عملوند سمت چپ که یک شناسه است قرار میگیرد . اما علاوه بر عملگر مقداردهی ساده ، عملگرهای مقداردهی ترکیبی نیز وجود دارند که شامل :
+=
-=
*=
/=
%=
&=
^=
|=
<<=
>>=
میشوند . عملگرهای مقداردهی ترکیبی با عملگرهای منطقی و بیتی و یا حسابی به همراه عملگر مقداردهی ساده بدین گونه عمل را بر روی عملوند راست و چپ اعمال میکنند ( عمل عملگر را انجام میدهند ) که عملگر غیر مقدار دهی را بر روی عملوند چپ و سپس راست اعمال کرده و نتیجه را در عملوند سمت چپ ، ذخیره میکنند . مثلاً ;a += 5 هر مقداری که a داشته باشد را با 5 جمع میکند و حاصل را در a ذخیره میکند . اگر a در همین مثال مقدار 4 میداشت بعد از حکم بالا مقدار 9 را مییافت . یا مثلاً در مورد عملگر =- ابتدا عملوند سمت راست از عملوند سمت چپ کم میشود و سپس حاصل به دست آمده ، در عملوند سمت چپ ذخیره میشود ؛ در مورد عملگرهای دیگر نیز عملیات به همین شکل انجام میشود ؛ یعنی =* عملوند سمت چپ را در عملوند سمت راست ، ضرب میکند و مقدار حاصل را در عملوند سمت چپ ذخیره میکند و =/ عملوند سمت چپ را بر عملوند سمت راست ، تقسیم میکند و مقدار حاصل را در عملوند سمت چپ ذخیره میکند . به همین ترتیب =% عملوند سمت چپ را بر عملوند سمت راست ، تقسیم میکند و باقیمانده را در عملوند سمت چپ ذخیره میکند ، =& عملوند سمت راست را با عمل « و بیتی » بر روی عملوند سمت چپ اعمال میکند و مقدار حاصل را در عملوند سمت چپ ذخیره میکند ؛ به همین شکل =^ و =| به ترتیب عملیات « زور بیتی » و « یا بیتی » را انجام میدهند . موارد بالا با توجه به اولویتشان از یکدیگر جدا شدهاند که آخرین اولویت در عملگرهای مقدار دهی متعلق به =>> و =<< یعنی به ترتیب مقدار دهی انتقال به چپ بیتی و مقدار دهی انتقال به راست بیتی میباشند که اولی عملیات انتقال به چپ را به اندازه عملوند سمت راست بر روی عملوند سمت چپ انجام میدهد و حاصل را عملوند سمت چپ ذخیره میکند و دومی عملیات انتقال به راست را به اندازه عملوند سمت راست بر روی عملوند سمت چپ انجام میدهد و حاصل را در عملوند سمت چپ ذخیره میکند
دقت کنید : عملگرهای مقداردهی ترکیبی فقط بر روی مقادیر عمل میکنند ( و نه کاراکترها و رشتهها ) و هر کدام ، قوانین عملگر ترکیب شده را در مورد عملوندهای خود انتظار دارند و اگر جز این باشد یا کامپایلر خطا میگیرد یا برنامه شما دارای باگ ( Bug ) خواهد بود
اولویتها و خوانده شدن عبارتها
ویرایشعبارتها ، توسط کامپایلر با توجه به الویت عملگرها و البته از چپ به راست خوانده میشوند اما برای اینکه الویتها را و از چپ-به-راست خوانده شدن را تغییر دهیم و کاری کنیم تا کامپایلر آنگونه که ما میخواهیم ، عبارت را بخواند باید از پرانتزها استفاده کنیم . برای این منظور زیرعبارتها را آنگونه که میخواهیم لحاظ شوند داخل حفت پرانتزهای باز و بسته میگذاریم که این عمل میتواند تو در تو نیز انجام شود . مثال :
((( 3 * 7 ) * (2 + 5))/8) + 6 * 2
همان طور که از علم ریاضی به یاد دارید ، عمل ضرب و تقسیم بر عمل جمع و تفریق ، اولویت دارند و اگر از هم جدا نشده باشند هر کدام که در سمت چپتر قرار داشته باشد زودتر مورد ارزیابی قرار میگیرد . در مثال بالا با کمک پرانتزها کل عبارت را به چند زیر عبارت تقسیم کردهایم که اگر عبارت مقدار ذخیره شده در یک float باشد ، مقدار بازگردانده شده 30.375 خواهد بود چرا که ابتدا 7 * 3 و 5 + 2 مورد ارزیابی قرار میگیرند که حاصل اولی 21 و حاصل دومی 7 است که در هم ضرب میشوند و عدد 147 به دست میآید که سپس تقسیم بر 8 میشود که 18.375 به دست خواهد آمد که جمع میشود با 6 * 2 ( که میشود 12 ) و در نهایت عدد 30.375 به دست میآید . اما اگر از پرانتزها استفاده نمیکردیم پاسخ ما 54.625 میشد
بنابراین « اولویت » یا Precedence به دیرتر یا زودتر لحاظ شدن یک عنصر زبان C میگویند . بالاترین اولویت در یک برنامه C با یادداشتها (Comments) میباشد که هر آنچه بعد از دو اسلش / به صورت // در آن خط قرار گرفته باشد یا هر آنچه بین */ و /* قرار گرفته باشد نادیده گرفته خواهد شد . اولویت بعدی با پیشپردازندهها Preprocessors که با علامت شارپ (Sharp) # آغاز میشوند است که زودتر از عنصرهای دیگر سازنده متن برنامه C مورد ارزیابی و لحاظ شدن قرار میگیرند . سپس توابع و حکمها در اولویتها هستند که در آنها ممکن است بخشهایی از عملوندهایی به همراه عملگرهای بازگو شده در این فصل ، استفاده شده باشند که به آنها عبارت میگوئیم . عبارتها میتوانند ترکیبی باشند و یا توسط پرانتزها به چند یا چندین زیر عبارت تقسیم شده باشند که در هر زیر عبارت نیز کامپایلر در نهایت باید با توجه به اولویت عملگرها ؛ زیر عبارتها و در نهایت کل عبارت را ارزیابی نماید
در یک عبارت و یا حتی زیر عبارت ، اولویتها بدین ترتیب میباشند ( که اگر چند عملگر اولویت یکسانی داشته باشند ، آنکه در سمت چپتر قرار دارد زودتر مورد ارزیابی قرار خواهد گرفت ) :
۱ − شناسهها
۲ − رشتهها
۳ − پرانتزهای تابع ، کروشههای آرایه ، عملگرهای انتخاب عضو ساختمان یا اجتماع ( . و <- ) ، عملگرهای افزایش و کاهش پس از شناسه ( ++a و --a )
۴ − عملگر sizeof ، عملگر اشارهگر و آدرس دهی ( * و & ) ، افزایش و کاهش پیش از شناسه ( a++ و a-- ) ، نه منطقی (!) ، نه بیتی (~)
۵ − پرانتزهای کستکردن int(a)
۶ − ضرب ، تقسیم و باقیمانده ( * و / و % )
۷ − جمع و تفریق ( + و - )
۸ − انتقال بیتی به چپ و راست ( 5>>a و 2<<a )
۹ − عملگرهای مقایسهای ( a < b و a > b و a <= b و a >= b )
۱۰ − عملگرهای تساوی ( a == b a != b )
۱۱ − و بیتی ( a & b )
۱۲ − زور بیتی ( a ^ b )
۱۳ − یا بیتی ( a | b )
۱۴ − و منطقی ( a && b )
۱۵ − یا منطقی ( a || b )
۱۶ − عملگرهای شرطی ( a ? b : c )
۱۷ − عملگرهای مقداردهی ( a = b و a += b و a -= b و a *= b و a /= b و a %= b و a &= b و a ^= b و a |= b و a <<= b و a >>= b )
۱۸ − عملگر کاما برای نادیده گرفتن (a,b)
شما میتوانید با کمک پرانتزهای باز و بسته که میتواند تو در تو نیز باشد ، عبارت خود را به زیر عبارتهایی تقسیم کنید تا زودتر مورد ارزیابی قرار بگیرند و از اینکه کامپایلر کد شما را به گونهای جز آنکه مد نظر شماست بخواند ، جلوگیری کنید
عملگر sizeof
ویرایشعملگر sizeof که از اسم آن نیز پیداست ، اندازه و حجم اشغال شده توسط دادهای که به عنوان عملوند به آن ارجاع داده میشود را در مبنای دهدهی و واحد بایت (Byte) باز میگرداند . دقت کنید که sizeof نمیتواند اندازه اشغال شده توسط تابعها ، bit-field ها و یا دادههای ناقص و ناتمام (incomplete types) را بازگرداند ( علیرغم اینکه داده هستند ) ؛ پیش از این به انواع داده ناقص و ناتمام اشاره نمودیم که شامل دادههای پوچ (void) ، آرایههایی با تعداد عنصرهای نامعین و ساختمان (structure) و یا اجتماعی (union) که دادههای تعریف نشده دارند و یا در خود ، ساختمان یا اجتماعی دارند که دادههای تعریف نشده دارند میشود
عملگر sizeof را به چند توع میتوان به کار برد :
۱ − نام نوع داده را داخل یک جفت پرانتز باز و بسته در مقابل sizeof مینویسید : ;sizeof (float)
۲ − نام یک شناسه را که جز شناسه تابع و یا bit-field و یا دادهای که ناقص و ناتمام است در مقابل sizeof مینویسید : ;sizeof a
۳ − یک عبارت را داخل یک جفت پرانتز باز و بسته در مقابل sizeof مینویسید ؛ دقت کنید که در این فرایند ، عبارت ، خروجی نمیدهد بلکه فقط در همین حد ارزیابی میشود که نوع داده نهایی چه خواهد بود و اندازه آن نوع داده را sizeof باز میگرداند مثلاً مینویسید :
int a = 5, b = 0;
double h = 59.85647;
b = sizeof(a+b)
در مثال بالا یک متغیر صحیح که ۴ بایت دارد با یک متغیر اعشاری با دقت دوبرابر که ۸ بایت دارد جمع شده است ، مهم نیست که نتیجه چه میشود ، چون طی تبدیل ، double از int بزرگتر است sizeof مقدار اشغال شده توسط double را باز میگرداند ( یعنی عدد ۸ )
در دادههای ترکیبی مثل ساختمان ، sizeof اندازه اشغال شده توسط کل ساختمان را که شامل فضاهای خالی اضافه نیز میشود ، باز میگرداند . مقدار بازگردانده شده توسط sizeof یک عدد صحیح بدون علامت است که در اکثر کامپایلرها از نوع داده size_t که در فایل سرآیند stddef تعریف شده است میباشد . اینکه size_t چه نوع دادهای دارد توسط استاندارد تعریف نشده است اما در اکثر کامپایلرها صحیح بدون علامت unsigned int و یا بلند بدون علامت unsigned long int است
با کمک عملگر sizeof شما میتوانید پی ببرید که در هر سیستم ، اندازه اشغال شده توسط هر نوع داده چه قدر است ؛ اندازه اشغال شده توسط ساختمان یا اجتماع شما چه قدر است و با آرایهای که اندیس آن را ننوشتهاید اما آن را با مقداردهی تعریف کردهاید ( مثلاً یک رشته ) ، چند عنصر دارد . بدین ترتیب شما خواهید دانست که چه قدر باید حافظه کامپیوتر را مصرف کنید . مثال :
#include <stddef.h>
#include <stdio.h>
static const int values[] = { 1, 2, 48, 681 };
#define ARRAYSIZE(x) (sizeof x/sizeof x[0])
int main (int argc, char *argv[])
{
size_t i;
for (i = 0; i < ARRAYSIZE(values); i++)
{
printf("%d\n", values[i]);
}
return 0;
}
در مثال بالا فایل سرآیند stddef که سرنام standard definitions میباشد را به علت اینکه میخواستیم در برنامه از نوع داده size_t استفاده کنیم و البته stdio که سرنام standard Input/Output میباشد را جهت استفاده از تابع کتابخانهای printf که سرنام print formatted میباشد توسط پیشپردازنده include ضمیمه برنامه خود نمودیم . ابتدا یک آرایه صحیح ثابت مختص فایل برنامه فعلی با نام values تعریف نمودیم که چهار عنصر دارد . سپس با کمک پیشپردازنده define یک تابع با نام ARRAYSIZE تعریف نمودیم که یک پارامتر دارد با نام x که این x اندازه x است تقسیم بر اندازه یکی از عنصرهای x که تعریف کردهایم اولی باشد ( یعنی عنصر اول که میشود اندیس 0 ) سپس در تابع main که تابع اصلی برنامه است یک متغیر از نوع داده size_t با نام i تعریف نمودهایم که یک صحیح بدون علامت است . حالا از i که مقدار آن 0 باشد تا زمانی که تابع ARRAYSIZE آرگومان values را میپذیرد و مقدار 0 ندارد ، i یک واحد یک واحد افزایش مییابد و در هر دفعه ، بلوک خود را به اجرا میگذارد که این بلوک چاپ مقدار عنصرهای آرایه values میباشد که در خروجی خطدستوری نمایش داده خواهد شد