مقدمه
:
1- هدف
به
آموزش GNU C خوش آمديد . هدف از نوشتن اين کتاب آموزش نوشتن نرم افزار به
کمک زبان برنامه نويسي C به
کاربران سيستم هاي GNU است.
اصولا اين کتاب به عنوان خود آموز براي افراد مبتدي نوشته شده امابراي
استفاده به عنوان مرجع توسط کاربران با تجربه نيز کامل است.
مطالب پايه اي در چند
فصل اول شرح داده شده اند. کاربران مبتدي بايد به دقت آنها را مطالعه کنند اما
افراد با تجربه مي توانند به صورت سطحي از کنار آنها بگذرند.تمام اطلاعات در اينجا
وجود دارد و به هيچ گونه اطلاعات قبلي در مورد برنامه نويسي نياز نيست.
فرض بر اين است که
خواننده به يک سيستم Gnu دسترسي
دارد.اگر چه هدف اصلي کاربران Gnu هستند
اما مطالب اين کتاب تا 98 درصد براي کاربران - Open
BSD
Free BSD يا
Net BSD قابل
استفاده است. اگر چه تمام فرمان ها در کنار مثال هاي برنامه نويسي ذکر شده اند اما
آشنايي مختصر با پوسته (سطر فرمان) مفيد خواهد بود . شما تنها به مهارت در استفاده
از يک ويرايشگر متن احتياج داريد. هر ويرايشگري قابل استفاده خواهد
بود.
Gnu Emacs يک
نمونه خيلي خوب براي برنامه نويسان است. اين برنامه به مدت بيش از 20 سال است
که توسعه مي يابد و داراي صدها خصيصه مفيد مي باشد. Gnu Nanoيک ويرايشگر متن ساده است که
شما مي توانيد از آن استفاده کنيد . برخي برنامه نويسان مايل به استفاده از
VI هستند.
اگر شما از قبل يک ويرايشگر محبوب داريد مي توانيد از آن استفاده کنيد. همچنين
ويرايشگر هاي گرافيکي نظير Anjuta
يا KDevelop براي
برنامه نويسان وجود دارند اما اکثر برنامه نويسان ويرايشگر هاي محيط هاي متني را
ترجيح مي دهند.
2- مفاد اين
متن
مطالب اين کتاب مي
توانند به دو بخش تقسيم شوند : هسته زبان سي و توابع استاندارد ارايه
شده براي برنامه نويسان. اين توابع توسط کتابخانه توابع سي ياGNU
Libc فراهم شده اند که بخشي از هر سيستم Gnu مي
باشد. هيچ کدام يک از اين دو بخش اغلب بدون ديگري استفاده نمي شود اما در اين
کتاب در شروع تمر کز بر روي هسته زبان C و در
بخشهاي آخر بحث هاي بيشتري در مورد LibC خواهد
بود. سازمان دهي مطالب به گونه ايست تا آموزش برنامه نويسي C در يک
حالت صعودي انجام شود به گونه اي که مطالب هر فصل بر روي فصل قبلي بنا مي
شود. برخي جنبه هاي هسته زبان براي کاربران با تجربه کاربرد دارند
بنابراين در فصل هاي پاياني مي آيند.
زبان C به خودي
خود مي تواند ساختار هاي تصميم گيري و تکرار دستورات را ايجاد کند و
ذخيره سازي داده ها و محاسبات رياضي را انجام دهد. با همين اهميت يک روش براي
استفاده از توابع خارجي نظير LibC را
فراهم ميکند.
LibC توابعي براي کارهاي نظير خواندن و نوشتن فايل ها - دسته بندي
و جست و جوي داده - دريافت ورودي براي کاربر و نمايش داده براي او - ارتياط از طريق
شبکه - ساخت برنامه هاي قابل ترجمه و غيره فراهم مي کند.
3-چرا آموزش
C؟
C استاندارد است.آن زبان برنامه نويسي برنامه نويسان است.آن
زبان استاندارد برنامه نويسي سيستم هاي بر پايه Gnu
و BSD
است.اکثر اين سيستم ها به همراه برنامه هاي کاربردي که بر روي آنها اجرا مي شوند به
زبان C نوشته
شده اند. C بيش از
سي سال پيش براي نوشتن سيستم هاي عامل و برنامه هاي کاربردي توسعه يافت. آن کوچک
است . طراحي قايل بسط به آن اين امکان را داد تا به همراه صنعت کامپيوتر
توسعه پيدا کند. به دليل قدمت و محبوبيت آن C
زباني داراي پشتيباني بسيار خوب است. ابزارهاي زيادي به وجود آمده اند تا برنامه
نويسي C را آسان
تر کنند . و اين ابزار ها غالبا بسيار کامل و استاندارد هستند. تمام نرم
افزارهايي که ما در اين کتاب از آنها استفاده خواهيم کرد با C نوشته
شده اند.
4- چرا استفاده
از GNU
؟
Gnu يک
سيستم عامل کامل شبه Unix
بوده که به مدت بيش از بيست سال در حال توسعه است. نرم افزار
هاي Gnu به دليل
پايداري و انطباقشان با استاندارد ها شناخته شده اند. اغلب سيستم
هاي Gnu
از Linux به
عنوان هسته استفاده مي کنند. اين سيستم ها غالبا به عنوان سيستم هاي
Gnu/Linux شناخته
مي شوند.
5- چرا نرم افزار
آزاد؟
مهمترين مطلب در مورد
Gnu اينست
که تمام سيستم چيزي است که به عنوان نرم افزار آزاد شناخته مي شود. يک نرم
افزار زماني يک نرم افزار آزاد است که مردم آزاد باشند تا :از نرم
افزار در هر زمينه اي استفاده کنند. در نرم افزار تغيير ايجاد کنند. نرم افزار را
با ديگران به اشتراک بگذارند. و نسخه هاي اصلاح شده از نرم افزار را پخش
کنند.
نرم افزار هايي که نرم
افزار آزاد نيستند نرم افزار داراي حق مالکيت (انحصاري) ناميده مي شوند. زيرا
يک نفر ادعاي مالکيت آنها را مي کند و ديگران را از به اشتراگ گذاري و ايجاد تغيير
در آن منع مي نمايد.
از ديدگاه اخلاقي
نوشتن نرم افزار آزاد راهي اجتماعي تر است. نرم افزار آزاد با اجازه دادن به
کاربرانش براي کمک به خود از طريق ايجاد تغييرات دلخواه در نرم افزار (يا
استفاده از فردي ديگر براي ايجاد تغييرات ) آنها را قدرتمند مي سازند. آن به
افراد اجازه مي دهد تا از طريق به اشتراک گذاردن نرم افزار با همسايگانشان به آنها
کمک کنند. نرم افرار انحصاري بر عکس عمل مي کند : آن با گفتن اين نکته به افراد که
پاسخ مثبت دادن به تقاضاي کمک مردم جرم است به اشتراک گذاري نرم افزار را غير
قانوني مي سازد. در حاليکه نرم افزار آزاد به افراد اجازه مي دهد تا با پخش
نسخه هاي بهبود يافته نرم افزار به اجتماع خود کمک کنند.
نرم افزار آزاد همچنين
بر ضد افراد فقير يا افراد کشور هاي در حال توسعه موضع نمي گيرد.با اعطاي تمام
آزاديهاي بالا به آنها اجازه مي دهدتا بدون اجبار براي پرداخت مبالغ غير ممکن پول
براي حق امتياز نرم افزار از کامپيوتر استفاده کنند.
در نهايت برتري هاي
فني نيز وجود دارد. نرم افزار آزاد فارغ از دسيسه هاي بازاريابي است.آن خود را براي
مجبور کردن کاربران به خريد قطعه هاي اضافي برنامه محدود نمي کند. هر قطعه از
Gnu طوري
طراحي شده که تا حد ممکن مفيد باشد. به عنوان يک برنامه نويس شما از همان نرم افزار
برنامه نويسي C استفاده
مي کنيد که در پروژه هاي بزرگ استفاده مي شود.
نر افزار هاي انحصاري
تنها در يک شکل قابل خواندن براي ماشين توزيع مي شوند.اين يعني کاربر نمي تواند از
آنچه درون نرم افزار اتفاق مي افتد اطلاع يابد.در مقابل نرم افزار آزاد بايد به
همراه کد منبع خود در يک فرمت قابل خواندن براي انسان منتشر شود. به عنوان يک
برنامه نويس شما مي توانيد کدهاي هر قطعه از نرم افزار ازاد را که مايل باشيد
مطالعه کنيد.اگر خطايي در برنامه وجود داشته باشد شما مي توانيد خودتان آنرا
رفع کنيد.
اين آزادي در رفع
خطاها و اضافه ساختن امکانات چيزي است که نرم افزار Gnu را عالي
مي سازد. همه کدها براي بازبيني در دسترس اند.
نرم افزار آزاد قصد
تغيير جهان براي بهتر شدن را دارد.
فصل اول : ديباچه
اي بر C
1.1 زبان
برنامه نويسي چيست؟
يک زبان برنامه نويسي
قالبي براي طرح ريزي براي اجراي موارد سفارش داده شده توسط کامپيوتر تعريف مي کند.
زبان هاي برنامه نويسي به سه دسته تقسيم مي شوند. زبان هاي ترجمه اي - زبان هاي
کامپايل شده و زبان ماشين. از اين ميان تنها زبان ماشين به طور مستقيم توسط
کامپيوتر قابل فهم است.
يک زبان ماشين مجموعه
اي از راهنماييها(instructions) ست که CPU
کامپيوتر (واحد پردازشگر مرکزي) مي فهمد. تمام راهنمايي ها و اطلاعات توسط اعداد
نمايش داده مي شوند. خيلي سريع براي کامپيوتر و خيلي سخت براي مغز انسان براي
خواندن و نوشتن آنها. براي آسان تر ساختن وظيفه برنامه نويسي کامپيوتر مردم زبان
آسانتري را ساختند که Assembly ناميده
مي شد.يک زبان Assembly نامهاي
متني براي دستورات در دسترس زبان ماشين فراهم ميکند. اين مطلب در کنار اين واقعيت
که زبان اسمبلي به برنامه نويسان اجازه اضافه کرن فاصله ها و تب ها را در
کدها مي دهد زبان Assembly را براي
برنامه نويسي بسيار راحت مي سازد. برنامه هاي Assembly مي
توانند براي ترجمه به زبان ماشين به منظور فهم CPU به يک
Assembler سپرده
شوند.
استفاده از زبان
هاي Assembly خيلي
سريع گسترش يافت.آنها به عنوان زبانهاي نسل دوم شناخته مي شدند. اما
هنوز هم دو مشک وجود داشت . اولا هر دستور Assembly تنها يک
وظيفه خيلي ابتدايي نظير جمع دو عدد و يا بارگذاري يک مقدار از حافظه را انجام مي
داد. استفاده از اين دستورات کوچک واقعا ملالت آور بود. مشکل دوم خيلي بزرگتر بود.
هر کدام از برنامه هاي نوشته شده به Assembly به نوع
خاصي از CPUوابسته بود. هر نوع
از CPU زبان
ماشين مخصوص به خودو بنابر اين زبان Assembly مخصوص
به خود را داشت. کار مهم بعدي طراحي زباني بود که به زبان ماشين انواع زيادي
از CPU
قابل ترجمه باشد.
اين زبانهاي جديد
وابسته به ماشين زبانهاي نسل سوم يا زبانهاي سطح بالا ناميده مي شوند. اين
زبانها که براي آسان خوانده شدن طراحي شده بودند از کلمات انگليسي - سمبل هاي
پايه اي رياضي و علايم نقطه گذاري تشکيل مي شدند. اين زبانها به عبارات ساده اجازه
مي دهند تا به طور مختصر بيان شوند به عنوام مثال جمع دو عدد و ذخيره سازي
حاصل در حافظه مي تواند به صورت زير بيان شود:
;data = 10 + 200
که بر عبارت زير ارجحيت
دارد:
Load R1, 10
Load R2, 200
Addi R1, R2
Store R2, L1
1.2 C
چيست ؟
سپس ابزاري که کامپايلر ناميده
مي شود براي تبديل کدهاي سطح بالا به زبان ماشين استفاده مي شود. يک برنامه
مي تواند به زبان C نوشته
شده و سپس براي هر کامپيوتري کامپايل شود.درگيري با جزييات سخت افزاري بر
عهده کامپايلر است.
براي مقايسه برتري
زبان C بر
Assembly به يک
برنامه کوچک که در هر دو زبان نوشته شده نگاه کنيد:
مثال
1.1 C
در مقايسه با Assembly : |
.section
.rodata .LC0:
.string "Tax Due: %d "
.text
.align 2 .globl main
.type main,@function main:
pushl %ebp
movl %esp, %ebp
subl $24, %esp
andl $-16, %esp
movl $0, %eax
subl %eax, %esp
movl $1000, %eax
movl $400, %edx
movl $0x3e6147ae, -12(%ebp)
subl %edx, %eax
pushl %eax
fildl (%esp)
leal 4(%esp), %esp
fmuls -12(%ebp)
fnstcw -18(%ebp)
movw -18(%ebp), %ax
movb $12, %ah
movw %ax, -20(%ebp)
fldcw -20(%ebp)
fistpl -16(%ebp)
fldcw
-18(%ebp)
subl $8, %esp
pushl -16(%ebp)
pushl $.LC0
call printf
addl $16, %esp
movl $1, %eax
leave
ret .Lfe1: .size main,.Lfe1-main |
برنامه در
زبان C
: |
#include <stdio.h> int main() { int wages = 1000; int tax_allowance = 400; float tax_rate = 0.22; int tax_due; tax_due = (wages - tax_allowance) * tax_rate; printf("Tax Due: %d euro ", tax_due); return 0; } |
بدون شناخت
از C
شما کدام برنامه را براي خواندن آسانتر مي دانيد؟ خروجي هر دو برنامه مشابه است
:
"Tax Due: 131 euro"
کد Assembly نشان
داده شده که در مجموعه راهنمايي 80386 نوشته شده در ماشيني که از مجموعه
راهنمايي ديگري استفاده مي کند کار نخواهد کرد .اما کد C مي
تواند عملا براي هر کامپيوتري کامپايل شود.
1.3 ابزارهاي برنامه
نويسي
Gnu به
همراه کامپايلري به نام gccارايه مي
شود. gcc از
ابتدا مخفف Gnu
C Compiler بود ولي از زماني که توانست زبانهاي ديگري غير
از C را
کامپايل کند بهGnu Compiler
Colection تغيير نام
داد.
پدید آورنده اصلی GCC ریچارد استالمن است
کسی که بناینگذار پروژه Gnu
محسوب می شود.
نخستین
نسخه GCC در سال
1987 انتشار یافت که یک پیشرفت مهم محسوب می شد زیرا محصول جدید اولین کامپایلر
بهینه سازی شده قابل حمل ANSI C به
عنوان یک نرم افزار آزاد محسوب می شد.
در سال 1992 نسخه 2.0 کامپایلر
GCC عرضه
شد. نسخه جدید قابلیت کامپایل کدهای C++ را نیز
داشت.
در سال 1997 یک انشعاب آزمایشی
در GCC به نام
EGCC به
منظور بهینه سازی کامپیایلر و پشتیبانی کامل تر از C++ ایجاد
شد. در ادامه EGCC به
عنوان نسل بعدی کامپایلر GCC پذیرفته شد و تکامل آن باعث انتشار نسخه
سوم GCC در سال
2004 گردید.
چهارمین نسخه از کامپایلر
GCC در سال
2005 عرضه شد.
براي اطمينان از اينکه
gccرا نصب کرده ايد يا خير
دستورات زير را وارد کنيد :
مشاهده نسخه
gcc |
ciaran@pooh:~/book$ gcc --version
4.1.1 $ciaran@pooh:~/book |
نسخه اي
از gcc که شما
استفاده مي کنيد ممکن است متفاوت باشد. هر چيزي شبيه اين از قبيل 3.4.6 يا 4.1.1 خوب
است. در صودتي که يک پيام خطابه صورت :
command not found
دريافت مي کنيد
شما gcc را نصب
نکرده ايد.اگر Gnu را از
روي cd نصب
کرده ايد مي توانيد gcc را در
آن پيدا کنيد.اگر شما نمي دانيد چگونه يک برنامه را از روي cd نصب
کنيد آنگاه از يک دوست يا فردي کهGnu را بر
روي سيستم شما نصب کرده بخواهيد اين کار را براي شما انجام دهد .
1.4 معرفي
gcc
حالا ما مي خواهيم يک
تکه کوچک کد c و طرز
کامپايل آنرا به شما نشان بدهيم.دراينجاهدف آموزش نحوه استفاده
از gcc است
بنابر اين هنوز به تشريح کد c
نمي پردازيم. در زير کوچکترين قطعه کد c
که gcc قادر به
کامپايل آن است را مي بينيد. اين کد هيچ کاري انجام نمي دهد.
مثال
1.3
tiny.c |
main() { } |
اين قطعه کد را در ويرايشگر
متن خود تايپ و با نامtiny.c ذخيره
کنيد.شما مي توانيد هر نام ديگري را نيز که به پسوند c. ختم مي
شود انتخاب کنيد.اين پسوندي است که توسط برنامه هاي c استفاده
مي شود. gcc
براي
کامپايل يک برنامه صحت اين پسوند را چک مي کند. با در اختيار داشتن يک فايل ذخيره
شده حالا مي توانيد به وسيله دستور زير آنرا کامپايل کنيد:
کامپايل برنامه |
ciaran@pooh:~/book$ gcc tiny.c $ciaran@pooh:~/book |
در صورتيکه
که اين فرمان با موفقيت انجام شود بدون پيغام خروجي خواهد بود.در صورتيکه هر گونه
پيغام خطايي را دريافت کرديد بايد صحت برنامه خود را چک کنيد. در صورتيکه فرض کنيم
شما اين برنامه 8 کاراکتري را درست نوشته باشيد بايد فايلي به
نامa.outدر دايرکتوري شما ايجاد
شود.اين برنامه زبان ماشيني است که از کدهاي بالا ساخته شده است.در صورتيکه آنرا
اجرا کنيد خواهيد ديد که در واقع هيچ کار خاصي را انجام نمي دهد.
نامگذاريa.out دلايل
تاريخي دارد. آن مخفف output Assembler مي
باشد.
اگر چه gcc اين
کدها را کامپايل کرد اما اين کدها کامل نيستند اگر شما اخطار هاي جي سي سي را
فعال کنيد به شما گفته خواهد شد چه کمبود هايي وجود دارد. با اضافه کردن سوئيچ
Wall- به
فرمان کامپايل اخطار هاي gcc را فعال
مي کنيم:
فعال کردن نمايش اخطارها |
ciaran@pooh:~/book$ gcc -Wall tiny.c tiny.c:2: warning: return-type defaults to `int' tiny.c: In function `main': tiny.c:3: warning: control reaches end of non-void function ciaran@pooh:~/book$ |
دليل ظهور اين
پيغام ها اينست که برنامه ما به طور دقيق کامل نيست. براي رهايي از اين پيغام ها
بايد دو سطر جديد به برنامه خود اضافه کنيم . بتابر اين کوتاهترين برنامه
معتبر C به صورت
زير است:
مثال
2.3
tiny2.c |
int main() { return 0; } |
حالا برنامه را با
گزينه –Wall کامپايل مي کنيم ولي هيچ پيام خطايي دريافت نمي کنيم. گزينه
ديگر –o Filename است که نام فايل خروجي را ( به
جاي a.out) مشخص
مي کند.
کامپايل برنامه
در حال فعال بودن اخطارها |
ciaran@pooh:~/book$ gcc -Wall -o tiny-program
tiny2.c ciaran@pooh:~/book$ ls tiny2.c
tiny-program ciaran@pooh:~/book$
./tiny-program ciaran@pooh:~/book$ |
1.5 چه کسي
c معتبر
را تعريف مي کند؟
براي شما به عنوان يک
برنامه نويس C معتبر
توسط کامپايلر تعريف مي شود. لهجه هاي مختلف زيادي از C وجود
دارد. خوشبختانه آنها خيلي به هم شبيه هستند. همچنين زبانهاي ديگري نيز وجود دارند
که بر پايه C ساخته
شده اند مثل Objective
C و
C++ .اين
زبانها در ظاهر بسيار شبيه C هستند
اما کاربردي متمايز دارند.
gcc خيلي از
لهجه هاي زبان C را به
خوبي خيلي از زبانهاي ديگر از قبيل C++ و Obcetive
C مي
فهمد.
1.5.1 سي کرنيگان و
ريچي
C توسط
Dennis Ritchie
بين
سالهاي 1969 تا 1973 ساخته شد.در سال 1978دنيس ريچي به همراه
Brian Kernighan يک کتاب آموزشي بسيار خوب براي C با نام
"The C programming
language"منتشر
کرد.اين کتاب در واقع اولين تعريف رسمي از اين زبان بود و از آنجا که لهجه
اصلي C محسوب
مي شود گاهي اوقات به آن C سنتي مي
گويند. متاسفانه اين کتاب بسياري از جنبه هاي اين زبان را تعريف نشده باقي
گذاشت.بنابر اين افرادي که کامپايلر ها را مي نوشتند مجبور بودند خود در مورد
چگونگي رفتار با اين جنبه ها تصميم گيري کنند.نتيجه اين شد که اجراي يک قطعه کد
بسته به نوع کامپايلر مورد استفاده نتايج متفاوتي را در بر داشت. استفاده از اين
لهجه مدت زيادي به طول نينجاميد . gcc تنها
براي کامپايل برنامه هاي خيلي قديمي از آن پشتيباني مي کندو ما تنها به خاطر مقاصد
تاريخي به آن اشاره کرديم.
1.5.2
ISO C
در سال 1983 موسسه
استاندارد ملي آمريکا کميته اي را به منظور تدوين يک استاندارد صحيح و رفع برخي
کاستي ها ي زبان C تشکيل
داد.کار تدوين اين استاندارد در سال 1989 به پايان رسيد و مورد پذيرش سازمان بين
المللي استاندارد نيز قرار گرفت. اين لهجه جديد با نام C89 شناخته
شد. همچنين از آن به عنوان ISO
C و
ANSI C نيز ياد
مي شود. gcc يکي از سازگارترين کامپايلر هاي موجود با اين لهجه
است.
1.5.3
C99
کميته آنسي سي جلسات
بسيار کمي را براي ارتقائ اين زبان تشکيل مي دهد . آخرين استاندارد ارتقا
يافته در سال 1999 عر ضه شد که با نام سي 99 شناخته مي شود.تاکنون کامپايلر
هاي بسيار کمي به طور کامل از سي 99 پشتيباني مي کنند زيرا ايجاد تغييرات در يکي از
مهمترين نرم افزار هاي سيستم عامل کار زمان گيري است. تا زمان نگارش اين کتاب
پشتيباني جي سي سي از سي 99 تا حد زيادي کامل شده است اما توسعه دهندگان هنوز مشغول
کار بر روي آن هستند.
1.5.4
Gnu C
Gnu C
بسيار شبيه C89 است اما
داراي برخي خصيصه هاي جديد از C99 و برخي
امکانات فرعي اضافه(extension) ديگر است. امکانات فرعي به صورت محتاطانه براي
رفع برخي اشکالاتي که C99 راه حل
مناسبي براي آنها ارايه نکرده توسط توسعه دهندگان ارايه شده اند.Gnu C
لهجه پيش فرض gcc و نيز
لهجه ايست که ما در اين کتاب به کار خواهيم برد. ما نهايت تلاش خود را خواهيم کرد
تا در مواقعي که از امکانات فرعي Gnu C استفاده
مي کنيم آنها را مشخص کنيم هر چند به طور کلي استفاده کامل از Gnu C
بهتر است زيرا استفاده از ISO
C باعث
محدود شدن برنامه هاي شما به استفاده از جنبه هاي مشترک مي گرددو کابرد آنها را
محدود به موارد معيني مي سازد.
1.5.5 انتخاب يک
لهجه
در صورتيکه مي خواهيد
لهجه اي غير از لهجه پيش فرض انتخاب کنيد مي توانيد با سوييچ –std = انتخاب
خود را مشخص کنيد.به دنبال اين سوييچ مي توانيد يکي از گزينه هاي c89 – c99 – gnu89 و
gnu99 را وارد
کنيد. gnu89 در حال
حاظر گزينه پيش فرض است تا زماني که پشتيباني از c99 تکميل
شود . در آن صورت gnu99 گزينه
پيش فرض خواهد شد. در هر صورت تغييرات چندان قابل توجه نخواهد بود.
1.5.6 آينده
استانداردها
امکانات فرعي اضافه از
قبيل آنهايي که توسط gcc اضافه
مي شوند منابع الهام اصلي ISO براي
استاندارد هاي جديد C هستند.
زماني که گروه ANSI
C مشاهده
مي کنند تعداد زيادي از کامپايلر ها يکي از امکانات فرعي اضافه را به کار مي گيرند
آنها لزوم وجود آن امکان را بررسي مي نمايند و در صورتيکه آنرا مفيد تشخيص دهند يک
را استاندارد براي به کار گيري آن ارايه مي دهند. برخي از امکانات فرعي اضافه شده
توسط gcc ممکن
است در آينده به صورت استاندارد در آيند و برخي ديگر ممکن است اينگونه
نباشند.
1.6
خاتمه
در اين جا معرفي ما
خاتمه مي يابد . با اميد به اينکه ديد صحيحي در باره برنامه نويسي بدست آورده ايد
در فصل بعدي به نوشتن برنامه هاي پايه
اي که
عملا کارهايي را انجام مي دهند و تشريح اينکه چگونه اين کار ها را انجام مي دهند
خواهيم پرداخت.
فصل
دوم :خيره شدن به توابع
2.1
توابع چه چيزهايي هستند؟
توابع بلوک هاي
ساختماني برنامه هاي C
هستند.اکثريت برنامه هاي C از بلوک
هاي از کد که تابع(function) ناميده
مي شوند ساخته مي شوند.وقتي يک برنامه را مي نويسيد شما توابع زيادي را براي انجام
وظايفي که احتياج داريد ايجاد خواهيد کرد. علاوه بر اين تعداد زيادي از وظايف عمومي
از قبيل نمايش يک متن بر روي صفحه نمايش وجود دارند که مورد نياز اگثر برنامه
نويسان هستند. به جاي اينکه هر برنامه نويس مجبور باشد در اين زمينه سرمايه گذاري
مجددي صورت دهد سيستم هاي Gnu به
همراه کتابخانه(library) هايي از
توابع پيش ساخته براي انجام بسياري از اين وظايف عرضه مي شوند.
در طول
سالها هزاران مورد از چنين توابعي بر روي هم جمع شده اند. در غير اين صورت اگر شما
قصد نوشتن برنامه اي براي بازي BINGO داشتيد مجبور بوديد شخصا
توابع مخصوص برنامه را بنويسيد امااکنون در مي يابيد که افراد ديگر توابعي براي
توليد اعداد تصادفي – نمايش اعداد بر روي صفحه نمايش – دريافت اعداد ورودي از
بازيکن و موارد ديگر نوشته اند.
هر برنامه C ملزم به
داشتن تابعي به نام main() است.
اجراي برنامه از اين تابع شروع مي شود. مي توان تمامي کدهاي برنامه را در تابع
main() قرار
داد اما روش مرسوم تر تقسيم برنامه به چندين تابع کوچک است.
به عنوان اولين قطعه
کد مفيد در اين کتاب به يک نمونه کلاسيک نگاه مي اندازيم.بعد از کامپايل و اجرا
برنامه يک پيام سلام ساده روي صفحه نمايش شما چاپ مي کند. برنامه تابعي به نام
main() را
تعريف (define) و تابعي
به نام printf() را
فراخواني (call) مي
کند.prinf() تابعي
است که توسط کتابخانه ابزار ورودي/خروجي استاندارد (Standard Device Input/Output
library) فراهم مي شود که اين کتابخانه
به همراه هر سيستم Gnu وجود
دارد. متن برنامه کوچک ما به صورت زير است:
مثال
2.1
hello.c |
#include <stdio.h> int main() { printf("hello, world"); return 0; }
|
حالا برنامه را
کامپايل و اجرا کنيد. اگر همه چيز درست باشد عبارت “hello, world” بر روي
ترمينال شما (ابزار خروجي استاندارد ) نمايش داده خواهد شد. در صورتيکه فرامين
مربوط به کامپايل و اجراي برنامه را فراموش کرده ايد آنها به صورت زير هستند
:
فرامين کامپايل
و اجراي برنامه |
ciaran@pooh:~/book$ gcc -Wall -o hello hello.c ciaran@pooh:~/book$ ./hello hello, world ciaran@pooh:~/book$ |
اگر شما هرگونه پيام
خطا و يا اخطار را مشاهده مي کنيد دقت کنيد که کدهاي شما دقيقا مطابق کدهاي اين
کتاب باشد. هر پيام خطايي که دريافت کنيد سطري را که در آن مرتکب اشتباه شده ايد به
شما اعلام مي کند. در صورتيکه کدها را صحيح تايپ کرده باشيد چنين پيام هايي را
دريافت نخواهيد کرد.
2.2 يک تشريح سطر به
سطر
حالا به طور سريع کاري
را که هر سطر انجام مي دهد تشريح مي کنيم.در صورتيکه برخي از سطر ها براي شما مبهم
است نگران نباشيد . در آينده مثال هاي بسيار زيادي را انجام خواهيم
داد.
#include <stdio.h>
اين سطر به
gcc مي گويد
از کتابخانه ابزار ورودي/خروجي استاندارد اطلاعاتي را در مورد چگونگي استفاده از
توابع برداشت کند. به طور معمول ابزار ورودي استاندارد صفحه کليد و ابزار خروجي
استاندارد صفحه نمايش است. اين کتابخانه به طور گسترده مورد استفاده قرار مي گيرد و
ما در اين کتاب با توابع بسيار زيادي از آن سروکار خواهيم داشت.
int
main()
اين دو سطر تعريف تابع
main() را آغاز
مي کنند. در مورد سطر اول از اين دو سطر بعدا توضيحات بيشتري خواهيم
داد.
{
آکولاد باز نشانه شروع
يک بلوک کد است. تمام کدهاي بين آکولاد باز و بسته جزيي از تابع main()
هستند.
printf("hello, world
");
اين سطر در واقع يک
فراخواني تابع (function call) است.
تابع از قبل براي شما تعريف شده است. وقتي شما تابع printf() را فرا
مي خوانيد بايد يک نشانوند(Argument)براي آن ذکر کنيد تا به آن
بگوييد چه چيزي را نمايش دهد.
return 0;
دستور return
به اجراي تابع main() خاتمه
مي دهد. هيچ دستور ديگري بعد از اين سطر اجرا نخواهد شد. با پايان يافتن تابع
main() برنامه
شما خاتمه مي يابد. زماني که يک تابع به پايان مي رسد مي تواند مقداري را به محلي
که از آنجا فراخواني شده بازگرداند.اين کار با قراردادن يک مقدار پس از
return حاصل مي
شود.تابع main() همواره
يک مقدار صحيح (يک عدد مثبت يا منفي بدون اعشار) را باز مي گرداند.ما با مقدم ساختن
تعريف main() با
int به
کامپايلر مي فهمانيم که منتظر چنين چيزي باشد. قرار بر اين است که درصورت اجراي
بدون اشکال تابع main() مقدار
بازگشتي برابر صفر باشد.
{
آکولاد بسته نشانه
پايان بلوک کدي است که تابع main() را مي
سازد.
دو سطري که بدنه تابع
main() را مي
سازند با نام دستور (statement) شناخته
مي شوند. به صورت دقيقتر اينها دستورات ساده (simple
statements) هستند
(در مقابل دستورات مرکب (compound
statements) که در
فصل چهار به آنها مي رسيم).دستورات در زبان C به
منزله جملات براي زبانهاي گفتاري هستند. در انتهاي هر دستور ساده يک نقطه-ويرگول
(semi-colon) قرار مي
گيرد.اضافه نمودن خطوط خالي در برنامه اختياري است.C هيچگونه
نيازي به آنها ندارد اما استفاده از آنها باعث خوانايي برنامه مي شود.
متذکر شديم که تابع
main() ما
مقدار صفر را باز مي گرداند.اغلب توابع مقدار بازگشتي خود را به برنامه بازمي
گردانند در حاليکه تابع main()مقدار بازگشتي خود را که نشانه
پايان برنامه است براي پوسته ارسال مي کند. مقدار بازگشتي برنامه توسط پوسته ذخيره
مي شود. درصورتيکه مايل به مشاهده آن هستيد از دستورات زير استفاده
کنيد:
مشاهده مقدار
بازگشتي برنامه به پوسته |
ciaran@pooh:~/book$ gcc -Wall -o hello hello.c ciaran@pooh:~/book$ ./hello hello, world ciaran@pooh:~/book$ echo $? 0 ciaran@pooh:~/book$ |
2.3
توضيحات
Comment
ها راهي براي اضافه نمودن متون توضيحي در برنامه هستند.آنها توسط کامپايلر ناديده
گرفته مي شوند بنابراين هيچ تاثيري بر روي برنامه شما ندارند. هر چنداين کار حجم
برنامه شما را افزايش مي دهد اما شما پي خواهيد برد که استفاده از Comment
ها وسيله مفيدي است تا به شما يادآوري کند مشغول انجام چه کاري هستيد.در مثال هاي
اين کتاب ما Comment ها را
براي توضيح آنچه در حال انجام است به کار مي بريم.با دو روش ميتوانيد يک
Comment را وارد
برنامه خود سازيد:عادي ترين روش اينست که در شروع و انتهاي متن توضيحي خود به ترتيب
از /* و
*/ استفاده
کنيد. توضيحات در اين روش مي توانند از چندين سطر تشکيل شوند. روش دوم استفاده از
//
درابتداي سطر است که محتويات بعد از خود تا پايان سطر جاري را به متن توضيحي مبدل
مي سازد.
در اين جا برنامه
hello, world ما به
همراه متون توضيحي آورده شده است:
مثال 2.2
hello2.c |
/* The purpose of this program is to * display some text to the screen * and then exit. */ #include <stdio.h> int main() { /* printf() displays a text string */ printf("hello, world"); return 0; //zero indicates there were no errors } |
پس از کامپايل از لحاظ
اجرايي اين کدها مشابه کدهاي مثال قبل هستند. خطوط 2 و 3 توضيحات بالا با يک ستاره
آغاز مي شوند .اين کار اگر چه لازم نيست اما اين نکته که توضيحات در چهار سطر ادامه
دارد را روشن مي سازد.
2.1 ساختن توابع
شخصي
در مثال قبلي ما تنها
بک تابع معرفي کرديم.براي اضافه کردن توابع ديگر به طور کلي شما بايد دو کار انجام
دهيد:ابتدا بايد به همان صورت که براي تابع main() انجام
داديم آنها را تعريف کنيم. کار
ديگري که بايد انجام دهيم اعلان (declare)توابع است. اعلان يک تابع به
منزله اينست که به gcc بگوييم
تا منتظر آن تابع باشد.ما مجبور به اعلان تابع main() نيستسم
زيرا آن يک تابع مخصوص است و gcc مي داند
که بايد منتظر آن باشد.نامي که به تابع مي دهيم بايد در هر دو جنبه اعلان و تعريف
ظاهر شود.
نام تابع مي تواند از
حروف الفبا يعني کاراکترهاي A تا
Z و
a تا
z و نيز
ارقام 0 تا 9 و کاراکتر خط زير(underscore) يعني "_" تشکيل شود. اين کاراکتر ها به هر ترتيبي مي
توانند کنار هم قرار بگيرند اما حرف نخست نام تابع نمي تواند عدد باشد.زبان
C حساس به
متن (case-sensitive) است
بنابرين My_Function کاملا
با my_function متمايز
است. نام هر تابع بايد متمايز بوده و طول آن از يک تا 36 کاراکتر مجاز
است.
به همراه نام بايد به
هرتابع يک نوع و يک بلوک کد اختصاص دهيد.نوع تابع براي کامپايلر مشخص مي سازد که
مقدار بازگشتي تابع چگونه است. تابع مي تواند بدون مقدار بازگشتي باشد.مقدار
بازگشتي تابع printf() يک عدد صحيح و برابر تعداد کاراکترهاي است که بر روي ترمينال نمايش داده
است. اين اطلاعات براي ما مهم نيستند بنابرين آنها را در برنامه خود ناديده مي
گيريم.
در فصل بعدي در مورد
جزييات انواع داده اي صحبت خواهيم کرد و سپس به تفصيل مقادير بازگشتي توابع خواهيم
پرداخت.
در اينجا برنامه اي را
داريم که 3 تابع را تعريف مي کند:
مثال 2.3
three_functions.c |
#include <stdio.h> /* function declarations
*/ int
first_function(void); int goodbye(void); int main()
// function definition { printf("the program begins...
"); first_function(); goodbye(); return 0; } int first_function() // function
definition { /* this function does nothing
*/ return 0; } int goodbye()
// function definition { printf("...and the program ends.
"); return 0; }
|
در مثال بالا ما تابع
()first_function را
نوشتيم که هيچ کاري انجام نمي دهد و نيز تابع ()goodbye که يک
پيغام را در صفحه نمايش چاپ مي کند. اعلان توابع بايد قبل ار فراخواني آنها صورت
بگيرد يعني در مثال ما بايد توابع قبل از تابع main() اعلان
مي شدند. به طور کلي مرسوم است که اعلان توابع بعد از سطر هاي شامل عبارت
include# و قبل
ار آغاز تعريف توابع صورت مي گيرد.
2.5 فايلهاي
چندگانه
اجباري براي اينکه
برنامه ها تنها در يک فايل معين نوشته شوند وجود ندارد. کدهاي شما ميتوانند در هر
تعداد فايل که بخواهيد پخش شوند. به عنوان مثال اگر برنامه اي از 40 تابع تشکيل شده
باشد شما مي توانيد هر تابع را در يک فايل جداگانه قرار دهيداگر چند اين کار يک
افراط در تکه تکه کردن برنامه است. غالبا توابع بر حسب موضوع در گروه هايي دسته
بندي شده و در فايلهاي جداگانه قرار مي گيرند. فرضا در صورتيکه وظيفه يک برنامه
محاسبه قيمت يک پيتزا و نمايش نتيجه است شما مي توانيد توابع مربوط به محاسبه قيمت
را در يک فايل و توابع نمايش نتايج را در فايل ديگري قرار داده و از فايل سوم براي
نگهداري تابع main() استفاده
کنيد. حال مي توانيد از فرمان زير براي کامپايل برنامه استفاده کنيد:
کامپايل برنامه
تشکيل شده از 3 فايل |
ciaran@pooh:~/book$ gcc -o pizza_program
main.c prices.c display.c |
ياد آوري مي کنيم در
صورتيکه تابعي را در prices.c تعريف و
قصد فراخواني آن را در main.c داريد
بايد آنرا در main.c
اعلان
کنيد.
2.6 فايلهاي
سرآيند
ادامه پيگيري
اعلان توابع مي تواند موجب شلوغ کاري شود. به همين دليل فايلهاي سرآيند براي جا
دادن کدهاي C که شما
قصد به کارگيري آنها در فايلهاي چندگانه را داريد استفاده مي شوند. قبل از اين شما
به طور عملي از يک فايل سرآيند به نام stdio.h استفاده
کرديد.اين فايل سرآيند شامل اعلان توابع بسيار زيادي است. از جمله شامل تابع
printf() و نيز
اعلان آن است. شما نيز مي توانيد يک فايل سرآيند شامل اعلان توابعي که قصد به
اشتراک گذاري آنها را داريد ايجاد کنيد و سپس با استفاده از #include آن را
در اختيار هر فايل C که به اطلاعات آن احتياج دارد قرار دهيد. تنها تفاوت
موجود در اين است که شما بايد به جاي علائم < و > نام فايل خود را بين دو "
(کوتيشن) قرار دهيد . يعني از "my_header.h" به جاي
<system_header.h>
استفاده کنيد. براي توضيح اين نکته برنامه پيتزا که قبلا به آن اشاره کرديم را
خواهيم نوشت
2.7 يک (غير) برنامه
بزرگتر
نکات کوچک برنامه
نويسي که تا کنون بررسي کرديم براي نوشتن يک برنامه آراسته و تاثير گذار کافي
نيستند.براي حفظ سادگي برنامه ما تنها يک اسکلت کلي از برنامه را مي نويسيم تا
بدينوسيله ساختار و طرز استفاده از فايلهاي سرآيند را تشريح کنيم بدون اينکه
شما در باتلاق مفاهيم جديد فرو رويد.در فصل 3 ما يک نسخه کامل از اين برنامه را
خواهيم نوشت.کدهاي زير مي توانند کامپايل و اجرا شود اما عملياتي براي محاسبه قيمت
و يا سوال از کاربر براي وارد کردن اطلاعات انجام نخواهد داد.
نخست با فايل
main.c سرو کار
داريم که تنها شامل تابع main() مي
شود.main() برخي از
توابعي که در فايلهاي ديگر تعريف شده اند را فراخواني خواهد نمود. متذکر مي شويم که
فايل main.c فاقد
سطر #include <stdio.h> مي باشد
زيرا از هيچ يک از توابع عضو کتابخانه ابزار ورودي/خروجي استاندارد استفاده نمي
کند.
مثال 2.4
main.c |
#include "display.h" #include "prices.h" int main() { display_options(); calculate_price(); display_price(); return 0; } |
در مرحله بعد با
display.c سر و
کار داريم.اين فايل شامل دو تابع است که هر دو از طريق تابع main()
فراخواني مي شوند و ما اعلان آنها را در يک فايل سرآيند با نام display.h
قرار داده ايم.
مثال 2.5
display.c |
#include <stdio.h> int display_options() { printf("Welcome to the pizza parlor "); printf("What size pizza would you like? (in inches)"); return 0; } int display_price() { printf("Your pizza will cost 0.00 "); return 0; } |
مثال 2.6
display.h |
/* header file just contains function declarations, an file that wants * to use either of these functions just has to #include this file */ int display_options(void); int display_price(void); |
و در نهايت
prices.c را
داريم که شامل توابعي براي دريافت داده از کاربر و محاسبه قيمت مجموع هزينه هاي
پيتزاست. تنها يکي از اين توابع از طريق main()
فراخواني مي شود بنابر اين اعلان دو تابع ديگر در بالاي همين فايل صورت مي گيرد.
کدهاي کامل اين توابع را در فصل سوم خواهيم نوشت.
مثال 2.7
prices.c |
int get_size(void); int get_toppings(void); int calculate_price() { /* insert code here. Will call get_size() and get_toppings(). */ return 0; } int get_size() { /* insert code here */ return 0; } int get_toppings() { /* insert code here */ return 0; } |
مثال 2.8
prices.h |
int
calculate_price(void); |
حالا مي توانيد
gcc را با
دستور زير براي کامپايل فايل هاي بالا به کار گيريد:
gcc -Wall -o pizza_program main.c prices.c
display.c
اين برنامه در زمان
اجرا پس از نمايش يک پيغام خوش آمدگويي اعلام مي کند که قيمت پيتزاي شما برابر
£0.00 است.
2.8 يک تابع جديد
ديگر
قبل از ادامه بياييد
نگاهي به يکي ديگر از توابع عضو کتابخانه ابزارهاي ورودي/خروجي استاندارد
بياندازيم:printf() . دستور
چاپ قالبدار يکي از شکل هاي پيشرفته تابع printf() است.
رشته اي که براي prinf() ارسال
مي شود مي تواند شامل کاراکترهاي کنترلي که داراي معاني مخصوصي هستند باشد.رفتن به
سطر جديد به طور خودکار توسط printf() صورت
نمي گيرد و براي اينکار بايد کاراکترهاي را اضافه نماييد.
2.9 خلاصه اصول
اوليه
مطالبي را که تا کنون از
آنها عبور کرديم نبايد چندان سخت باشند. اگر قصد کسب تجربه را داريد برنامه هايي
مشابه که داراي چندين سطر خروجي هستند ايجاد کنيد. برنامه خود را به دو تابع تقسيم
کرده و آنها را در دو فايل مجزا قرار دهيد.
همواره در هنگام کامپايل
اخطارهاي gcc را فعال
کنيد.اخطارها خبر از مبهم بودن و يا ناکامل بودن کدهاي شما مي دهند.در اين حالت
gcc
معناي صحيح آنها را حدس مي زند و اغلب اين کار را به درستي
انجام مي دهد اما شما نبايد به آنها اعتماد کنيد. جست و جو براي تصحيح پيغام هاي
خطا به مهارت شما در استفاده از زبان کمک ميکند.اغلب پيغام هاي خطا به همراه شماره
سطري که مشکل در آن وجود دارد مي آيند. اگر شما در آن سطر هيچگونه خطايي مشاهده نمي
کنيد سطر فوقاني آنرا چک کنيد. در صورتيکه يک دستورالعمل ناکامل باشد
gcc تا
زماني که به ابتداي دستورالعمل بعدي نرسيده متوجه وجود خطا نخواهد شد.نقطه ويرگول
هاي خود را فراموش نکنيد.
فصل سوم:داده
ها و عبارت ها
برنامه هاي مفيد واقعي شامل
داده ها، انجام عمليات بر روي آنها و نمايش آنها در خروجي هستند.در زبان
C شما از
قطعات نامگذاري شده حافظه که متغير (variables) ناميده
مي شود براي نگهداري داده ها استفاده مي کنيد. برنامه هاي C مي
توانند با کمک نام متغير ها داده هاي درون آنها را در هر زمان تغيير دهند. هر
متغيير يک نام منحصر به فرد (identifier )
دارد که شما براي استفاده يا
تغيير مقدارش به آن رجوع مي کنيد. يک عبارت (expression) هر
چيزيست که بتواند مورد ارزيابي قرار گيرد. براي مثال 1+1 عبارتي با مقدار 2 است. در
اين عبارت علامت بعلاوه يک عملگر دوتايي (binary operator) است
زيرا بر روي دو مقدار(value)
براي ساخت يک مقدار عمل مي کند.
قواعد نامگذاري متغير ها مشابه
نامگذاري توابع است.شما مي توانيد از اعداد،ارقام و کاراکتر زير خط در نامگذاري
آنها استفاده کنيد به شرطي که کاراکتر اول عدد نباشد. همچنين به مانند توابع متغير
ها نيز بايد قبل از به کارگيري اعلان شوند. ناميکه براي متغير در نظر مي گيريد بهتر
است گوياي هدفي باشد که متغير را براي آن به کار مي گيريم. اين عمل مطالعه کد هاي
شما را آسانتر مي سازد. شما ميتوانيد متغير هاي خود را شخصا تعريف کرده و يا از
انواعي (types) از قبل
تعريف شده اند استفاده کنيد.
قبل
از اينکه در باتلاق اصطلاحات فني فرو رويم بياييد با هم نگاهي به يک قطعه کد کوچک
بياندازيم تا نشان دهيم همه اينها چقدر ساده است . در اين مثال ما دو متغيير از نوع
پيش تعريف شده int را به
کار مي گيريم:
مثال 3.1
bicycles.c |
include
<stdio.h># printf("So I have %d
wheels ", number_of_wheels);
|
3.1 تشريح برنامه
دوچرخه
مطالب جديد کمي براي بررسي
وجود دارد که براي تشريح آنها برنامه را به چند بخش تقسيم مي کنيم
:
int number_of_bicycles;
int number_of_wheels;
هر
يک از اين دو سطر يک متغير را تعريف مي کند.Int يکي از انواع داده است که به صورت توکار در زبان
C وجود
دارد. متغيير هايي از نوع int توانايي نگهداري مقادير مثبت و يا منفي صحيح را دارا
هستند.
number_of_bicycles = 6;
اين
سطر مقدار 6 را در متغيير number_of_bicycles
ذخيره مي کند. علامت مساوي به عنوان عملگر تخصيص (the assignment operator) شناخته
مي شود. اين عملگر مقدار عبارت سمت راست خود را به به متغيير سمت چپش اختصاص مي
دهد.
number_of_wheels = number_of_bicycles *
2;
در
اين سطر علاوه بر عملگر تخصيص از عملگر ضرب (multiplication operator) نيز
استفاده شده است. ستاره (asterisk) نيز يک
عملگر دوتايي است زيرا دو مقدار را براي ايجاد يک مقدار در هم ضرب مي کند. در اينجا
اين عملگر مقدار 12 را ايجاد مي کند که درون متغير number_of_wheels
ذخيره مي شود.
printf("I have %d bicycles ",
number_of_bicycles);
printf("So I have %d wheels ",
number_of_wheels);
در اينجا نيز باز با
printf() سر و
کار داريم که البته در شکلي متفاوت از آنچه قبلا ديده بوديم به کار رفته است. در
اين مثال printf() دو
نشانوند (arguments)
پذيرفته است که توسط يک کاما از هم جدا شده اند. نشانوند اول printf() به عنوان رشته قالب
(format string) شناخته
مي شود. وقتي يک %d در رشته
قالب وجود داشته باشد printf() متوجه مي شود که بايد منتظر يک
نشانوند اضافي باشد تا مقدار آن جايگزين %d
گردد. به
ازاي هر %d
بايد يک
نشانوند اضافي وجود داشته باشد.
با دانستن اين مطلب
جديد از اينکه پس از کامپايل و اجراي اين قطعه کد سطور زير نمايش داده شود غافلگير
نخواهيد شد :
خروجي برنامه
|
I have 6 bicycles So I have 12
wheels |
مثل هميشه در صورتيکه برخي قسمت ها ها خوب متوجه نشده ايد
نگران نباشيد زيرا مثال هاي بيشتري را انجام خواهيم داد.
3.2انواع داده اي
تمام
انواع داده اي که
در C تعريف شده
اند از قسمت هايي از حافظه که بايت ناميده مي شوند ساخته شده اند. در اکثر
معماري هاي کامپيوتر يک بايت از هشت بيت تشکيل شده است. هر بيت يک مقدار
صفر يا يک را نگهداري مي کند. هشت بيت که هر کدام داراي دو حالت باشند در
کنار هم ۲۵۶ ترکيب مختلف را ايجاد مي کنند. ( ۲ به توان ۸ حالت ) . بنابر اين
يک مقدار صحيح که از دو بايت تشکيل شده است مي توانند عددي بين ۰ تا ۶۵۵۳۵ را در
خود نگه دارد.( از صفر تا ۲ به توان ۱۶) . اما معمولا متغيير هاي نوع صحيح از
اولين بيت خود براي نگهداري علامت مثبت و يا منفي عدد
استفاده مي کنند بنابراين مي
توانند عددي بين مثبت و منفي
۳۲۷۶۸ را در خود نگه دارند. اما چنانچه اين متغيير ها از
نوع بدون علامت باشند تنها قادر به نگهداري اعداد بزرگتر يا مساوي صفر خواهند بود . نوع داده اي متغيير هاي بدون
علامت در ابتدا داراي عبارت unsigned مي
باشد.
همانطور که قبلا اشاره
کرديم ۸ نوع داده پايه اي در زبان C تعريف شده اند. ۵ نوع براي نگهداري
اعداد صحيح با مقادير گوناگون و ۳ نوع براي نگهداري مقادير گوناگون
اعداد با مميز شناور ( اعداد اعشاري) . زبان C هيچگونه نوع داده
پايه اي را براي متن فراهم نکرده است. متن ها از کاراکتر هاي تکي ساخته مي
شوند و کاراکتر ها توسط اعداد نمايندگي مي شوند. در مثال قبلي از يکي
از انواع داده اي به نام int استفاده کرديم که داراي بيشترين کاربرد در زبان C است.
بيشتر اعدادي که در
برنامه هاي کامپيوتري استفاده مي شوند اعداد صحيح هستند. ما
کمي بعد درباره انواع با مميز شناور صحبت خواهيم کرد. به
ترتيب اندازه مقادير صحيح علامت دار و بدون علامت از كوچك به بزرگ عبارتند
از:
محدوده اعداد
قابل نگهداري در سيستم 32 بيتي |
اندازه در سيستم
32 بيتي |
نوع داده
اي |
ترتيب |
-127 تا
127 |
8 |
char |
1 |
0 تا
255 |
8 |
|
2 |
-32,767 تا
32,767 |
16 |
short |
3 |
0 تا
65,535 |
16 |
|
4 |
-2,147,483,647 تا 2,147,483,647 |
32 |
int |
5 |
0 تا
4,294,967,295 |
32 |
|
6 |
-2,147,483,647 تا 2,147,483,647 |
32 |
|
7 |
0 تا
4,294,967,295 |
32 |
|
8 |
-9,223,372,036,854,775,807 تا 9,223,372,036,854,775,807 |
64 |
|
9 |
0 تا
18,446,744,073,709,551,615 |
64 |
|
10 |
انواع داده اي كوچكتر داراي
اين مزيت هستند كه مقدار كمتري از حافظه را اشغال مي كنند. انواع داده اي بزرگ باعث ايجاد خطاي اجرايي (performance
penalty) مي
شوند.متغيير هايي از نوع داده اي int بزرگترين عدد صحيح ممكن را كه باعث ايجاد
خطاي اجرايي
(performance
penalty) نمي شود
نگهداري مي كنند به همين دليل مقدار متغيير هاي نوع Int بسته به نوع كامپيوتري كه شما استفاده مي
كنيد متفاوت است.
نوع داده اي char معمولا يك بايت است.
وجه تسميه آن به اين دليل است كه اين نوع داده اي معمولا براي نگهداري كاراكترهاي
تنها استفاده مي شود.اندازه ساير انواع داده اي به نوع سخت افزار كامپيوتري كه شما
استفاده مي كنيد بستگي دارد. اغلب رايانه اي روميزي 32 بيتي هستند كه اشاره به
اندازه داده اي كه آنها براي پردازش آن طراحي شده اند دارد.در رايانه هاي 32 بيتي
نوع داده اي int از 4 بايت تشكيل شده است (2 به توان 32
حالت).نوع داده اي short معمولا كوچكتر از int و نوع داده اي long از نوع داده اي int بزرگتر و يا با آن مساوي است و در نهايت
نوع داده اي long long براي
نگهداري مقادير عددي خيلي خيلي بزرگ است.
نوع داده اي متغييري كه شما
استفاده مي كنيد تاثير زيادي بر روي كاهش
سرعت اجرا يا اشغال حافظه برنامه شما ندارد.جز در موارد خاص شما مي توانيد
تنها از متغيير هاي نوع int استفاده كنيد. در دهه گذشته اغلب رايانه ها
پردازنده هاي 16 بيتي داشتند كه اندازه متغيير هاي نوع Int را به 2 بايت محدود مي كرد. در حال حاضر
متغيير هاي از نوع دادهاي short از 2 بايت و متغيير هاي از نوع داده اي
long از 4
بايت تشكيل شده اند. در حال حاضر با پردازنده هاي 32 بيتي نوع داده اي پيش فرض يعني
int
به اندازه كافي براي نگهداري متغيير هاي كه
سابقا از نوع داده اي long براي نگهداري آنها استفاده
مي شد گنجايش دارد. براي اطلاع از اندازه هر يك از انواع داده اي بر روي رايانه خود
قطعه كد زير را كامپايل و اجرا كنيد. در اين برنامه از عملگر جديدي به نام
()sizeof تشكيل
شده كهمقدار حافظه اي را كه توسط هر نوع داده اي اشغال مي گردد بيان مي
كند.
مثال 3.2
sizeof_types.c |
int main() { printf("sizeof(char) == %d\n", sizeof(char)); printf("sizeof(short) == %d\n", sizeof(short)); printf("sizeof(int) == %d\n", sizeof(int)); printf("sizeof(long) == %d\n", sizeof(long)); printf("sizeof(long long) == %d\n", sizeof(long long));
return 0; }
|
3.3 مثال ديگري از
انتساب
وقت آن رسيده كه مثال ديگري را
بررسي كنيم. در اين مثال هم چند مطلب تازه وجود دارد كه در يك دقيقه آنرا توضيح
خواهيم داد.
مثال 3.3
displaying_variables.c |
#include <stdio.h>
int main() { short first_number = -5; long second_number, third_number;
second_number = 20000 + 10000;
printf("the value of first_number is %hd\n", first_number); printf("the value of second_number is %ld\n", second_number); printf("the value of third_number is %ld\n", third_number);
return 0; }
|
در مثال بالا از يك متغيير نوع
short
و دو
متغيير نوع longاستفاده كرده ايم. مي
توانستيم هر سه متغيير را از نوع Int در نظر بگيريم اما از انواع داده اي ديگر
استفاده كرديم تا نشان دهيم اين انواع داده اي چقدر به هم شبيه اند. در نخستين سطر
تابع main ما متغييري را اعلان و همزمان آن را مقدار
دهي كرده ايم كه اين كار بسيار متداول است. در سطر دوم دو متغيير را با جدا كردن
آنها از هم به وسيله كاما اعلان نموده ايم. اين كار ممكن است نشانه مهارت ما باشد
ولي بهتر آنست كه هر متغيير را در سطري جدا گانه اعلان كنيم تا به خوانا بودن
برنامه كمك شده باشد.سطر سوم بسيار شبيه به برخي كدهاي مثال اول است. عملگر جمع
مقدار 30000 را توليد و سپس اين مقدار در متغيير second_number قرار مي گيرد. آخرين
مطلب قابل اشاره اينست كه در رشته قالب بندي تابع printf() به جاي %d از %hd براي متغيير هاي از نوع داده اي
short
و از %ld براي
متغيير هاي از نوع داده اي long استفاده شده است. به
اين عبارت هاي كوچك كاراكتري اصطلاحا شاخص هاي تبديل (conversion specifiers) گفته مي شود. هر نوع از انواع داده اي نصريح كننده
تبديل مختص خود را دارد. در صورتيكه خواسته باشيد كاراكتر درصد (%)
را چاپ كنيد بايد از %% استفاده
نماييد.
هنگامييكه برنامه بالا را
كامپايل و اجرا كنيد مقدار متغيير هاي خود را مشاهده خواهيد كرد. مقدار متغيير
third_number عجيب
خواهد بود زيرا در طول برنامه هيچ مقداري به آن اختصاص داده نشده است. وقتي كه شما
يك متغيير را اعلان مي نماييد سيستم عامل مقداري از حافظه را به آن اختصاص مي دهد.
شما راهي براي فهميدن اينكه اين قسمت از حافظه قبلا براي چه كاري استفاده شده
نخواهيد داشت .تا زمانيكه مقداري را به متغيير خود انتساب نداده ايد مقدار ي كه از
قبل در خانه هاي حافظه اختصاص يافته به متغيير وجود دارد به متغيير اختصاص خواهد
داشت. بنابر اين مقدار متغيير ها تا قبل از انتساب مقدار مشخص به انها ضرورتا عددي
تصادفي و غير قابل پيش بيني خواهد بود. فراموش كردن انتساب مقدار به متغيير ها يك
اشتباه رايج در ميان برنامه نويسان مبتدي است.
3.4 توضيح سريع تابع printf()
شما احتمالا متوجه عبارت دو
حرفي \n
در مواقعي كه از تابع printf() استفاده مي كنيم شده ايد.
اين عبارت در خروجي چاپ نمي شود بلكه printf() را متوجه مي كند كه بايد به
سطر بعد برود. به طور كلي اسلاش وارونه (\) كاراكتر
گريز (escape character) زبان سي
محسوب مي شود و هرگاه در داخل 2 كوتيشن قرار گيرد كاراكتر بعد از آن معناي مخصوصي
را خواهد داد.مثال ديگر \t
است كه به اندازه يك tab فاصله خالي در خروجي ايجاد مي
كند.
كاراكتر مخصوص ديگري كه
printf()آنرا مراعات مي كند درصد
(%)
است كه از printf() ميخواهد كاراكترهاي بعد از خود را در هنگام
چاپ با مقدار يك متغيير مشخص جايگزين نمايد.%d
رشته كاراكتري است كه يك متغيير از نوع داده اي Int را براي چاپ با استفاده
از سيستم شمارش ده دهي ( مبناي ده )
نمايندگي مي كند. به ازاي هر %d
در رشته قالب بندي شما بايد به تابع
printf()
بفهمانيد كه چه متغييري را قصد جايگزين كردن با آن
داريد. در مثال زير برخي
استفاده هاي بيشتر تابع printf() را مشاهده مي
كنيد:
مثال 3.4
more_printf.c |
Int main() { int one =
1; int two =
2; int three = 4; /* the values are unimportant here
*/ printf( "one ==\t%d\ntwo
==\t%d\nthree ==\t%d\n", one, two, three ); return
0; } |
3.5 عمليات رياضي
ساده
در ابتداي اين فصل اشاره كرديم
كه يك برنامه معمولا با انجام عمليات بر روي داده ها سر و كار دارد. با به كار گيري
علايم استاندارد رياضي انجام عمليات رياضي در زبان c به صورت
بسيار آسان قابل فهم و خواندن است. به مثال زير توجه كنيد:
مثال 3.5
wages.c |
int main() { int hours_per_day; int days_per_week;
hours_per_day = 8; days_per_week = 5;
printf("I work %d hours a week.\n", (days_per_week * hours_per_day) );
printf("%d %d hour days\n", days_per_week, hours_per_day);
return 0; } |
3.6
متغيير هاي عمومي و
محلي
متغيير هايي كه تا به حال از
آنها استفاده كرديم متغييرهاي محلي (local
variables) بودند
اين متغيير ها در همان تابعي كه قصد استفاده از آنها را داريم تعريف شده پس از
اجراي تابع نابود مي شوند و همانطور كه گفتيم تا قبل از انتساب مقدار آنها غير قابل
پيش بيني است.
دسته ديگري از متغيير ها كه
متغيير هاي عمومي (global
variables) نام
دارند در خارج از توابع و معمولا بالاي تابع main() تعريف
مي شوند. اين متغيير ها از داخل تمام توابعي بعد از اعلان آنها تعريف شوند قابل
استفاده بوده
در ابتداي اجراي برنامه ايجاد و تا پايان
اجراي برنامه در حافظه باقي مي مانند. بر خلاف متغيير هاي محلي به محض اعلان اين متغيير ها مقدار اوليه صفر به آنها
تعلق مي گيرد.
مثال 3.6
global_variable.c |
#include <stdio.h> int
global_varialble; int
first_function(void); int main()
{ printf("The first value of global_varialbles is %d
\n",global_varialble); first_function(); return 0; } int first_function() { global_varialble =
5; printf("Now the value of global_varialble is %d
\n",global_varialble); return 0; } |
3.7 حوضه
متغيير
منظور از حوضه (scope) يك
متغيير محدوده ايست كه متغيير در آن قابل دسترسي و استفاده مي باشد. به عنوان مثال
همانطور كه در بالا ديديم حوضه متغيير هاي محلي همان تابعي بود كه در آن تعريف شده
بودند و حوضه متغيير هاي عمومي تمام توابعي بود كه بعد از اعلان آن متغيير ها تعريف
مي شدند.
3.8 كلاسهاي
حافظه
كلاس حافظه خصوصيتي از متغيير
است كه طول عمر و حوضه متغيير را مشخص مي كند.منظور از طول عمر زمان ايجاد و نابود
شدن متغيير مي باشد. كلاسهاي حافظه انواع گوناگوني دارد كه در اين جا دو مورد از
پركاربرد ترين آنها يعني كلاس حافظه ايستا (static) و كلاس
حافظه خارجي (external) را
توضيح مي دهيم:
3.8.1 كلاس حافظه
خارجي
بعضي اوقات كدهاي برنامه ما در
بيش از يك فايل قرار دارد.شما ميتوانيد از متغيير هاي عمومي كه در يكي از فايلها
اعلان شده است در فايل ديگري استفاده كنيد به شرط اينكه آن متغيير را درفايل دوم
نيز اعلان نماييد . دستور اعلان متغيير در فايل دوم بايد با كلمه كليدي
extern آغاز
گردد.در مثال زير كدهاي برنامه از دو فايل تشكيل شده است:
مثال 3.7
main.c |
int main() { extern int my_var; my_var = 500; print_value(); return 0; }
|
مثال 3.8
secondary.c |
#include <stdio.h> int my_var; void print_value() { printf("my_var = %d\n", my_var); }
|
3.8.2 كلاس
حافظه ايستا
در زبان C هرگاه
يك تابع را بيش از يك بار فراخواني كنيم با هر بار فراخواني تابع متغيير هاي محلي
تعريف شده در تابع از ابتدا ايجاد شده سپس مقدار اوليه گرفته و در انتهاي اجراي
تابع نابود مي شوند اما اگر اين متغيير ها از نوع كلاس حافظه ايستا باشند تنها يك
بار مقدار اوليه گرفته در هنگام خروج از تابع آخرين مقدار خود را حفظ كرده و تا
انتهاي اجراي برنامه در حافظه باقي مي مانند. براي اينكه متغيير هاي محلي يك تابع
را از نوع ايستا تعريف كنيم بايد در دستور اعلان آنها ابتدا كلمه كليدي
static را ذكر
نماييم تا آن متغيير از نوع محلي ايستا تعريف شود. اما متغيير هاي عمومي به طور پيش
فرض از نوع ايستا بوده و ذكر يا عدم ذكر كلمه كليدي static در
ابتداي فرمان اعلان آنها بي تاثير است.
در
صورتيكه متغيير محلي از نوع ايستا تعريف نشود كلاس حافظه آن از نوع اتوماتيك
(automatic) خواهد
بود.كلاس حافظه اتوماتيك با آوردن كلمه كليدي
auto در ابتداي دستور اعلان
متغيير مشخص مي شود.البته چون متغيير هاي محلي در صورتيكه كلاس حافظه ديگري براي
آنها بيان نشود به طور پيش فرض از نوع اتوماتيك هستند آوردن و يا نياوردن كلمه
كليدي autoدر ابتداي دستور اعلان آنها
يكسان است.
مثال 3.9
static_variable.c |
#include <stdio.h> int
first_function(void); int main()
{
first_function();
first_function(); first_function();
return 0; } int first_function() { int
x=0; static int
y=0; printf(“automatic x =
%d static y = %d
\n”,x,y); x=x+1; y=y+1;
return 0; } |
3.9 متغيير هاي غير قابل
تغيير
يك عادت خوب برنامه نويس اينست
كه هيچ گاه عددي غير از صفر يا يك را در خلال كدهايتان به كار نبريد!. اگر به ثابت
عددي ديگري غير از صفر و يك احتياج داشتيد آنرا تبديل به يك متغيير از نوع غيرقابل
تغيير (constant) نماييد.
براي اين كار در ابتداي دستور اعلان متغيير از كلمه كليدي const استفاده
نماييد. مثال :
Const int my_age = 20;
مقدار اين متغيير در تمام طول
برنامه 20 باقي
خواهد ماند و هر كجا كه قصد تغيير آن را داشته باشيد با خطاي كامپايلر مواجه خواهيد
شد. عدد 20 معناي مختصري مي دهد در حاليكه مشخصه اي مانند my_age اطلاعات
بيشتري در مورد عملي كه تابع انجام مي دهد در اختيار ما قرار مي دهد. مزيت ديگر
استفاده از متغيير هاي غير قابل تغيير در اينست كه براي تغيير مقدار ثابت عددي بايد
در تمام برنامه به دنبال آن ثابت گشته و آن را تغيير دهيم ولي براي تغيير مقدار
متغيير هاي غير قابل تغيير تنها فرمان اعلان آنها را تغيير مي دهيم.
فصل
چهار : كنترل جريان
زبان برنامه نويسي
c دو سبك
براي تصميم گيري فراهم نموده است : حلقه ها (looping) انشعاب
ها(Branching). انشعاب
يعني تصميم گيري در مورد اينكه چه عملياتي انجام شود و حلقه يعني اينكه يك عمليات
خاص چند مرتبه تكرار شود.
4.1
انشعاب
وجه تسميه انشعاب
به اين دليل است كه برنامه در مواجه با دو
انشعاب در كد خود انتخاب مي كند كه كدام يك را ادامه دهد. دستور if يكي از
ساده ترين ساختار هاي انشعاب است.اين دستور از يك شرط (expression)كه داخل پرانتز قرار گرفته و
نيز يك دستور(statement) و يا بلوكي از دستورات(block
of statements) كه توسط دو آكولاد باز و بسته يعني { و }
محاصره شده اند تشكيل شده است.در صورتيكه شرط صحيح باشد ( ارزشي غير صفر داشته
باشد) در آنصورت دستور و يا بلوك دستورات اجرا خواهند شد و در غير اين صورت دستورات
ناديده گرفته خواهند شد. دستور if داراي يكي از دو شكل كلي زير
است:
فرم كلي اول با
يك دستور |
if (expression)
statement; |
فرم كلي دوم با
بلوك دستورات |
if (expression)
{ statement1;
statement2;
statement3; } |
در زير مثالي ساده از كاربرد
دستور if
را مشاهده مي كنيد:
مثال 4.1
using_if.c |
#include <stdio.h>
int main() { int cows = 6;
if (cows > 1) printf("We have cows\n");
if (cows > 10) printf("loads of them!\n");
return 0; } |
كامپايل – اجرا و خروجي برنامه
بالا به صورت زير است:
كامپايل – اجرا
و خروجي برنامه |
ciaran@pooh:~/book$ gcc
-Wall -Werror -o cows using_if.c ciaran@pooh:~/book$
./cows We have
cows ciaran@pooh:~/book$ |
دستور printf() دوم در
كد بالا به دليل اشتباه بودن شرط آن ( با ارزش صفر) اجرا نمي گردد.
4.2if…else
شكل دومي از دستور
if نيز وجود دارد كه به شما
امكان مي دهد تا بلوكي از كدها را مشخص نموده تا در صورت اشتباه بودن شرط اجرا
شوند. اين ساختار با نام دستور if ... else شهرت
دارد و به وسيله قرار دادن كلمه كليدي reserved word))
else و يك
بلوك كد ديگر در انتهاي ساختار معمولي if شكل مي گيرد.
پس ار تست شرط دستر
if يكي از
دو بلوك كد بسته به صحيح يا غير صحيح بودن شرط اجرا خواهند شد. به مثال زير توجه
كنيد:
مثال 4.2 cows2.c |
int main() { int cows =
0; if (cows >
1)
{ printf("We
have cows\n"); printf("%d
cows to be precise\n", cows);
} else
{ if (cows
== 0)
printf("We have no cows at all\n"); else
printf("We have only one cow\n");
} if (cows >
10) printf("Maybe too many
cows.\n"); return 0; }
|
شما اكنون بايد قادر به حدس
زدن خروجي برنامه باشيد:
خروجي
برنامه |
ciaran@pooh:~/book$
./cows2 We have no cows at
all ciaran@pooh:~/book$ |
در مثال بالا يك دستور
if …
else در درون
يك دستور if … else ديگر
وجو داشت. اين ساختار در زبان c كاملا منطقي و پركاربرد است.
4.3
دستور switch
اين ساختار انشعاب نسبت ساختار
هاي قبلي قدري پيچيده تر است.اگر چه دستور switch بسيار
انعاط پذير است اما تنها براي تست كردن داده هاي صحيح و كاراكتري كاربرد دارد. فرم
كلي آن به صورت زير است:
فرم كلي ساختار
switch |
switch (integer or character expression) { case constant1 : statement1; break;
case constant2 : statement2; break;
case constant3 : statement3; break; }
|
با اجراي دستور switch ابتدا
عبارت داخل پرانتز با تمام مقاديري كه جلوي آنها عبارتهاي case نوشته
شده مقايسه شده و با هركدام از آنها كه برابر بود دستورات بعد از آن تا رسيدن به
عبارت break اجرا مي
شود.سپس با اجراي دستور break
كامپايلر از حلقه خارج مي شود.
مثال 4.3
morse.c |
#include
<stdio.h> int main (); void morse (int); int main () { int
digit; printf
("Enter any digit in the range 0 to 9: "); scanf ("%d",
&digit); if ((digit
< 0) || (digit > 9)) {
printf ("Your number was not in the range 0 to 9.\n"); } else {
printf ("The Morse code of that digit is ");
morse (digit); } return
0; } void morse (int
digit)
/* print out Morse code */ { switch
(digit) {
case 0 : printf ("-----");
break;
case 1 : printf (".----");
break;
case 2 : printf ("..---");
break;
case 3 : printf ("...--");
break;
case 4 : printf ("....-");
break;
case 5 : printf (".....");
break;
case 6 : printf ("-....");
break;
case 7 : printf ("--...");
break;
case 8 : printf ("---..");
break;
case 9 : printf ("----."); } printf
("\n\n"); } |
مثال
4.4
fs.c |
#include
<stdio.h> int main () { printf ("Will
you join the Free Software movement? "); if
(yes()) {
printf("Great! The
price of freedom is eternal vigilance!\n\n"); } else {
printf("Too bad. Maybe
next life...\n\n"); } return
0; } int yes() { switch
(getchar()) {
case 'y' :
case 'Y' : return 1;
default : return
0; } } |
4.3 حلقه
ها
حلقه ها راهي را براي تكرار
فرمان ها و نيز كنترل اينكه فرمان ها چند مرتبه انجام شود فراهم مي كنند.فرضا شما
قصد داريد حروف الفبا را بر روي صفحه نمايش چاپ كنيد. مي توانيد اين كار را با يك
بار فراخواني تابع printf() انجام
دهيد. اين يك راه حل براي انجام اين كار است اما به نظر نمي رسد كه بهترين راه
باشد.حال بر فرض اگر خواسته باشيد اعداد يك تا هزار را در يك ستون چاپ كنيد چه مي
كنيد؟ آيا باز هم از يك تابع printf() بسيار
طولاني استفاده مي كنيد و يا هزار بار تابع printf() را در
كدهايتان تايپ مي كنيد؟!. البته مي توانيد راه حل هاي ذكر شده را به كار بريد ولي
بهتر است انجام كارهاي تكراري را به كامپيوتر سپرده و وقت خود را صرف پرداختن به
قسمت هاي جذاب تر برنامه خود كنيد.
4.4 حلقه
while
اساسي ترين حلقه در زبان
c حلقه
while
است.دستور while كاري شبيه تكرار دستور
if را
انجام مي دهد. اگر شرط دستور صحيح باشد دستورات انجام مي شوند با اين تفاوت كه پس
از اجراي دستورات شرط مجددا بررسي مي شود تا در صورتيكه هنوز صحيح باشد دستورات بار
ديگر انجام شوند. و اين حلقه تا زماني تكرار مي شود كه شرط مقدار غلط پيدا كند. در
صورتيكه در همان مرتبه اول بررسي شرط مقدار ناصحيح داشته باشد دستورات به هيچ وجه
اجرا نمي گردند. به عبارت ديگر درصورتيكه شرط هرگز مقدار غلط پيدا نكند حلقه به طور
بي نهايت تكرار مي شود.
براي كنترل تعداد دفعاتي كه يك
حلقه كدهايش را اجرا مي كند شما حداقل يك متغيير در جمله شرط داريد كه در بلوك كد
متعاقب تغيير مي يابد. اين موضوع به جمله شرط امكان مي دهد در برخي مراحل نادرست
گردد.
مثالي را كه پيش رو داريد يك
بازي ساده حدس زدن عدد است:
مثال 4.5
guess_my_number.c |
#include
<stdio.h> int main() { const int MAGIC_NUMBER =
6; int
guessed_number; printf("Try to guess what number
I'm thinking of\n"); printf("HINT: It's a number
between 1 and 10\n"); printf("enter your guess:
"); scanf("%d",
&guessed_number); while (guessed_number !=
MAGIC_NUMBER);
{
printf("enter your guess: ");
scanf("%d", &guessed_number);
} printf("you
win.\n") return 0; } |
بلوك كد حلقه while در مثال بالا تا
زماني كه بازيكن عدد 6 را حدس بزند مرتب تكرار مي شود.
4.5 حلقه
for
معمولا در موارديكه تعداد
دفعات تكرار حلقه از قبل معيين است از حلقه for استفاده مي كنيم. در ساختار حلقه
for
متغييري وجود دارد كه تعداد دفعات تكرار
حلقه را تعيين مي كند و اصطلاحا شمارنده ناميده مي شود. در پرانتز بعد از كلمه
for سه
عبارت وجود دارند كه توسط سمي كالون (;) از
يكديگر جدا شده اند. اولين عبارت مقدار اوليه متغيير شمارنده را مشخص مي كند. آخرين
عبارت معيين مي كند كه با هر بار اجراي دستورات حلقه مقدار متغيير شمارنده به چه
ميزان اضافه يا كم مي شود و بالاخره عبارت وسط كه شرط حلقه است مشخص مي كند حلقه تا
چه زماني بايد اجرا شود.
فرم كلي حلقه
if |
for (initialization;
expression; action ) {
statement1;
statement2;
statement3;
} |
در مثال زير متغيير
i متغيير
شمارنده حلقه for مي باشد:
مثال 4.6
for_ten.c |
#include <stdio.h>
int main() { int i;
/* display the numbers from 0 to 9 */ for (i = 0; i < 10; i++) printf("%d\n", i);
return 0; }
|
4.6 حلقه
do… while
حلقه do … while شبيه
حلقه while عمل مي
كند با اين تفاوت كه شرط حلقه به جاي ابتدا در انتهاي حلقه بررسي مي شود. پس حتي
اگر شرط حلقه مقدار نادرست داشته باشد دستورات حلقه حداقل يك بار اجرا مي شوند. در
ذيل مثالي از كاربرد اين حلقه آمده است:
مثال 4.7
guess_my_number.c |
#include <stdio.h>
int main() { const int MAGIC_NUMBER = 6; int guessed_number;
printf("Try to guess what number I'm thinking of\n"); printf("HINT: It's a number between 1 and 10\n");
do { printf("enter your guess: "); scanf("%d", &guessed_number); } while (guessed_number != MAGIC_NUMBER);
printf("you win.\n")
return 0; } |
4.7 عملگر
شرطی
?...:...
عملگر ... ?... : شبیه
دستور if … else عمل می
کند با این تفاوت که چون یک عملگر است ( و نه یک دستور )در میان عبارت ها نیز کار
برد دارد.
مثال 4.8
apples.c |
#include <stdio.h> int main() { apples = 6; printf("I have %d apple%s ", apples,
(apples == 1) ? "" : "s"); return 0; } |
?
… : … تنها
عملگر سه گانه در زبان c است.در
مثال بالا عبارت (apples == 1) شرط
ماست که درستی آن بررسی می شود. در صورتی که شرط صحیح باشد ( که در مثال بالا نیست
) در خروجی کلمه apple بدون
s و در
صورت اشتباه بودن شرط کلمه apple همراه
با s چاپ
خواهد شد.
4.9
break و
continue
زبان c راه
بسیار ساده ای برای خارج شدن از حلقه های تکرار در مواقع لزوم فراهم می کند. برای
این کار باید از دستور break که پیش
از این به منظور بیرون جهیدن از ساختار switch از آن
بهره برده بودیم استفاده کرد.
مثال زیر اعداد یک تا 12 را
چاپ می کند:
مثال 4.9
break.c |
#include <stdio.h> int main() { for (i = 1; i <= 20;
i++) { if (i ==
12)
{
break;
} printf ( “%d “ ,i);
} return 0; } |
به کار بردن دستور
continue در یک
حلقه تکرار باعث می شود تا دستورات بعدی نادیده گرفته شده و مفسر به ابتدای حلقه
باز گردد. مثال زیر حاصل تقسیم عدد بیست را بر اعداد محدوده منفی ده تا مثبت ده چاپ
می کند اما برای جلوگیری از تقسیم شدن عدد بیست بر عدد صفر از یک شرط و دستور
continue استفاده
شده است.
مثال 4.10
continue.c |
#include <stdio.h> int main() { for (i = -10; i <= 10;
i++) { if (i == 0)
{
continue;
} printf ("%d",
20/i); } return 0; } |
فصل
پنجم : اشاره گر ها
5.1 اصول
اولیه
محدودیتی که ممکن است شما نیز
متوجه آن شده باشید این است که توابع تنها از طریق مقادیر بازگشتی خود در برنامه
شما تاثیر گذراند. با این وصف اگر بخواهید تا یک تابع با بیش از یک متغییر سر و کار
داشته باشد چه خواهید کرد؟ پاسخ استفاده از اشاره گر هاست.
یک اشاره گر نوع خاصی از
متغییر هاست که برای نگهداری آدرس های حافظه کاربرد دارد یعنی یک اشاره گر
آدرس متغییر دیگری را به عنوان مقدار خود نگهداری می کند. اعلان اشاره گر ها
به مانند اعلان متغییر های عادی است با این تفاوت که شما باید یک کاراکتر ستاره
“*” را به
ابتدای نام اشاره گر اضافه کنید. دو عملگر جدید برای کار با اشاره گر ها وجود دارد
که شما باید آنها را بشناسید:عملگر & و عملگر
* که هر
دو جزء عملگر های یگانی پیشوندی محسوب می شوند .
زمانی که شما یک علامت آمپرسند
"&" را در آغاز نام یک متغییر اضافه می کنید شما آدرس آن متغییر را دریافت
خواهید کرد که می تواند در یک اشاره گر نگهداری شود. اما زمانیکه علامت ستاره را در
آغاز نام یک اشاره گر به کار می برید شما مقدار نگهداری شده در آدرس حافظه ای را که
اشاره گر به آن اشاره می کند دریافت خواهید کرد. مثل همیشه بحث را با یک مثال دنبال
می کنیم :
مثال 5.1 pointers_are_simple.c
|
#include
<stdio.h> int main() { int
my_variable = 6, other_variable = 10; int
*my_pointer; printf("the
address of my_variable is : %p\n",
&my_variable); printf("the
address of other_variable is : %p\n", &other_variable); my_pointer =
&my_variable;
printf("\nafter \"my_pointer = &my_variable\":\n"); printf("\tthe
value of my_pointer is %p\n", my_pointer); printf("\tthe
value at that address is %d\n", *my_pointer); my_pointer =
&other_variable;
printf("\nafter \"my_pointer = &other_variable\":\n"); printf("\tthe
value of my_pointer is %p\n", my_pointer); printf("\tthe
value at that address is %d\n", *my_pointer); return
0; } |
خروجی برنامه آدرس حافظه دو
متغییر را به شما نشان می دهد. مقادیر در رایانه من با رایانه شما متفاوت خواهند
بود. در تابع printf() همانطور
که شما هم متوجه شده اید از %p برای
نمایش آدرس ها بهره بردیم
که شاخص تبدیل برای تمام اشاره گر هاست. به
هر حال خروجی برنامه در رایانه من به صورت زیر بود :
خروجی
برنامه |
the address of
my_variable is :
0xbffffa18 the address of
other_variable is : 0xbffffa14 after "my_pointer =
&my_variable":
the value of my_pointer is 0xbffffa18
the value at that address is 6 after "my_pointer =
&other_variable":
the value of my_pointer is 0xbffffa14
the value at that address is
10 |
5.2 آدرس یک
متغییر
برنامه ها در زمان اجرا ، وقتی
به اعلان یک متغییر می رسند از سیستم عامل درخواست تخصیص مقداری حافظه برای آن
متغییر را می نمایند. سیتم عامل قطعه ای از حافظه را که اندازه آن برای نگهداری
مقادیر متغییر مناسب است انتخاب و آدرس آن را به برنامه اعلام می کند. هر زمان که
برنامه قصد خواندن داده هایی را که درون آن متغییر نگهداری می شوند داشته باشد ، به
آدرس حافظه آن متغییر مراجعه کرده و
به برابر با اندازه آن متغییر بایت های حافظه را می خواند.
در صورتی که شما یک بار دیگر
برنامه ای را که در اغاز این فصل ذکر
کردیم اجرا کنید ممکن است نتایج مشابه یا متفاوتی را نسبت به بار اول در مورد آدرس
های حافظه بدست آورید که این امر بستگی به شرایط سیستم شما دارد اما حتی اگر در بار
دوم نتایج یکسانی را نسبت به یار اول بدست آورید هیچ تضمینی برای اینکه اجرای
برنامه در فردا نیز همین نتایج را داشته باشد وجود نخواهد داشت. در واقع این احتمال
که فردا نیز برنامه همین نتایج را داشته باشد تقریبا محال
است.
نکته : متغییر های کلاس حافظه
ثبات فاقد آدرس حافظه بوده و اشاره گری قادر به اشاره به آن ها نمی
باشد.
5.3 اشاره گر ها در جایگاه آرگومان (نشانوند ) های تابع
یکی از بهترین مزیت های اشاره
گر ها این است که آن ها به توابع این امکان را می دهند تا متغییر هایی در خارج از
حوزه خود را تغییر دهند. با ارسال یک اشاره گر برای یک تابع ، شما به آن تابع امکان
خواندن و نوشتن بر روی داده های نگهداری شده در متغییر مربوط به آن اشاره گر را می
دهید.
مثلا شما قصد نوشتن تابعی را
دارید که مقادیر دو متغییر را جا به
جا می کند. بدون بهره گیری از اشاره گر ها نوشتن این برنامه غیر ممکن است. در زیر
کدهای برنامه مورد نظر را می بینید:
مثال 5.2
swap_ints.c |
#include
<stdio.h> int swap_ints(int
*first_number, int *second_number); int main() { int a = 4, b
= 7;
printf("pre-swap values are: a == %d, b == %d\n", a, b)
swap_ints(&a, &b);
printf("post-swap values are: a == %d, b == %d\n", a, b) return
0; } int swap_ints(int
*first_number, int *second_number) { int
temp; /* temp =
"what is pointed to by" first_number; etc... */ temp =
*first_number; *first_number
= *second_number;
*second_number = temp; return
0; }
|
همانطور که مشاهده می کنید
اعلان تابع () swap_ints به
کامپایلر gcc می گوید
که انتظار دو اشاره گر ( آدرس متغییر
)را داشته باشد. همچنین عملگر &
به منظور ارسال آدرس دو متغییر به جای مقدار آن ها به کار رفته
است.
5.4
عملیات حسابی بر روی اشاره گر ها
اگر چه انجام عملیات حسابی بر
روی اشاره گر ها مانند سایر متغییر هاست اما این عمل تنها در موارد نادری مفید واقع
خواهد شد. بر فرض اگر شما یک اشاره گر را بر دو تقسیم کنید حاصل به آدرس قسمتی از
حافظه اشاره خواهد کرد که ممکن است متعلق به برنامه شما نباشد. حال اگر برنامه شما
برای خواند یا نوشتن بر روی آن ناحیه از حافظه تلاش کند پیام خطایی با مضمون
segmentation
fault نمایش
داده شده و برنامه شما قطع خواهد شد. خطای segmentation
fault زمانی
روی می دهد که برنامه ای برای دسترسی به قطعه ای از حافظه که اجازه دسترسی به آن را
ندارد تلاش کند.
با این حال مواردی وجود دارد
که جمع مقادیر ساده به اشاره گر ها مفید واقع می شود. در فصل بعدی زمانیکه در مورد
آرایه ها بحث کنیم این نمونه را بررسی خواهیم کرد. در این مثال ها مقداری برابر
اندازه متغییری که اشاره گر به آن اشاره می کند به آن اضافه ( یا از آن کاسته ) می
شود.
5.5 اشاره گر های
عمومی (تهی )
یک اشاره گر عمومی متغییری است
که به عنوان اشاره گر به نوع داده ای
void تعریف
می شود. از آنجا که شما نمی توانید متغییری با نوع داده ای void داشته
باشید اشاره گر به هیچ نوع داده ای اشاره نخواهد کرد و بنابر این نخواهد توانست
محتویات آدرسی از حافظه را مشخص کند.
برای استفاده از چنین اشاره
گری شما باید ابتدا آن را در قالب یکی دیگر از انواع اشاره گر ها قرار دهید. برای
این کار باید از عملگر قالب ((Cast
Operator استفاده
نمایید .
فرم کلی عملگر قالب که مقدار
یک متغییر را در قالب یک نوع داده ای دیگر تغییر می دهد به صورت زیر
است:
فرک کلی عملگر
قالب |
(type) variable |
این روش زمانیکه قصد دارید تا یک اشاره گر
در مواقع مختلف به انواع داده ای مختلفی اشاره کند بسیار مفید خواهد بود . مثال زیر
بسیار ابهام زداست:
مثال 5.3
generic_pointer.c |
#include
<stdio.h> int main() { int i; char c; void
*the_data; i = 6; c = 'a'; the_data =
&i; printf("the_data points to the
integer value %d\n", *(int*) the_data); the_data =
&c; printf("the_data now points to the
character %c\n", *(char*) the_data); return 0; }
|
برای آشنایی بیشتر با عملگر
قالب به مثال زیر توجه کنید :
مثال 5.4
cast_operator.c |
#include
<stdio.h> int main() { float
first_num; float second_num
; first_num =
3.37; second_num = (int)
exact_length; printf("first_num is %f \n",
first_num); printf("second_num is %f \n" ,
second_num); return 0; }
|
خروجی برنامه بالا به صورت زیر
خواهد بود :
خروجی برنامه
|
first_num
is
3.370000 second_num is
3.000000 |
فصل
ششم : انواع داده ای ساخت یافته
6.1 داده ساخت یافته
چیست ؟
زبان c دو روش
برای تعریف انواع داده ای ساخت یافته یا متراکم فراهم کرده است : آرایه ها و ساختار ها .
هر دو روش می تواند شامل انواع مختلف داده ای استاندارد ازقبیل اشاره گر ها و یا سایر آرایه ها و ساختار ها
باشد.
آرایه ها می توانند تنها شامل
تعدادی از متغییر ها از یک نوع داده ای خاص باشند در حالیکه ساختار ها قادرند هر ترکیبی از انواع داده ای مختلف را شامل
شوند.
6.2 آرایه
ها
یک آرایه در حقیقت یک نوع داده
ایست که شامل تعدادی از متغییر ها از یک نوع داده ای خاص می باشد. هر عنصر در آرایه
دارای شماره ایست که شما از طریق آن می توانید به آن عنصر دسترسی پیدا
کنید.
برای آرایه ای با 100 عنصر
اولین عنصر دارای شماره صفر و آخرین
عنصر دارای شماره 99 می باشد.
این نوع دستیابی شاخص دار
(Indexed
access ) گردش در
میان عناصر آرایه را بسیار آسان می سازد.
6.3 اعلان و دستیابی
به آرایه ها
اعلان یک آرایه بسیار شبیه به
اعلان سایر انواع داده ایست با این تفاوت که شما باید اندازه آرایه ( تعداد عناصر
آرایه ) را نیز معین کنید. اندازه آرایه یک مقدار صحیح است که
در میان
دو براکت [] بعد از
نام آرایه قرار می گیرد.
مثال 6.1 first_arrays.c |
int main() { int
person[10]; float hourly_wage[4] = {2, 4.9,
10, 123.456}; int index; index = 4; person[index] =
56; printf("the %dth person is number
%d and earns $%f an hour\n",
(index + 1), person[index],
hourly_wage[index]); return 0; }
|
نکته : این خود شما هستید که
باید مراقب باشید تا اشتباها از
برنامه نخواهید به عنصری که در آرایه وجود ندارد دسترسی پیدا کند. مثلا اگر شما
درخواست خواندن یا نوشتن بر روی
یازدهمین عنصر یک آرایه را که تنها شامل ده عنصر است صادر کنید احتمالا برنامه شما
crash خواهد
کرد و یا برنامه نتایج اشتباهی را به بار خواهد آورد بدون اینکه شما را از انجام اشتباه آگاه کند.
6.4 مقدار دهی اولیه
به آرایه
ها
در مثال بالا ما برای عناصر
آرایه hourly_wage را از
طریق اعدادی که داخل دو کروشه قرار گرفته و با کاما از هم جدا شده اند مقدار دهی
اولیه انجام دادیم. اشکال روش بالا در این است که برای مقدار دهی به یک عنصر شما را مجبور به مقداردهی اولیه به تمام
عناصر ما قبل آن عنصر می سازد و در
صورتی که مقدار اولیه برخی عناصر را مشخص نکنید به صورت خودکار مقدار اولیه آنها برابر
صفر قرار می گیرد.
برای رفع این مشکل یک امکان
الحاقی لهجه Gnu به زبان
C مفید
واقع می شود. با استفاده از این امکان برای مقدار دهی به عناصر یک آرایه از فرم کلی
[index]=value استفاده
می شود. در مثال زیر دقت کنید :
مثال 6.2 initialise_array.c |
#include
<stdio.h> int main() { int i; int first_array[100] = { [90]=4,
[0]=5, [98]=6 }; double second_array[5] = { [3] =
1.01, [4] = 1.02 }; printf("sure enough,
first_array[90] == %d\n\n", first_array[90]); printf("sure enough,
first_array[99] == %d\n\n", first_array[99]); for (i = 0; i < 5;
i++} printf("value of
second_array[%d] is %f\n", i, second_array[i]); return 0; } |
در مثال بالا اولین ، نود و
یکمین و نود و نهمین عنصر آرایه first_array که
اندیس آنها به ترتیب صفر، نود و نود و هشت است مقداریر اولیه ای برابر 4 ، 5 و 6 پذیرفته
اند.
6.5 آرایه های چند
بعدی
آرایه های که در مثال های قبل
به کار بردیم آرایه های تک بعدی بودند اما آرایه ها می توانند بیش
از یک بعد داشته باشند. آرایه های چند بعدی در حقیقت آرایه ای از آرایه ها هستند.
این آرایه ها بسیار شبیه به آرایه های استاندارد بوده با این تفاوت که در هنگام
اعلان آرایه پس از نام آرایه به جای یک براکت بسته چند براکت بسته قرار می گیرد. یک
آرایه دو بعدی را می توان به صورت شبکه ای متشکل از ردیف ها و ستون های افقی و عمودی فرض کرد. در مثال زیر با یک
آرایه دو بعدی با نام box سرو کار
داریم:
مثال 6.3 number_square.c |
#include
<stdio.h> const int num_rows =
7; const int num_columns =
5; int main() { int
box[num_rows][num_columns]; int row,
column; for(row = 0; row < num_rows;
row++) for(column = 0; column
< num_columns; column++)
box[row][column] = column + (row *
num_columns); for(row = 0; row < num_rows;
row++) { for(column
= 0; column < num_columns; column++)
{
printf("%4d", box[row][column]);
}
printf("\n");
} return 0; } |
در صورتی که کد های مثال بالا
را کامپایل و اجرا کنید خروجی آن جعبه ای از اعداد به صورت زیر خواهد بود :
خروجی برنامه
|
0 1
2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 |
Gcc از
آرایه های تک بعدی تا آرایه های 29
بعدی را پشتیبانی می کند
هرچند استفاده از آرایه هایی با بیش ار سه بعد بسیار نادر
است.
6.6 آرایه ای از
کاراکتر ها ( متن )
رشته های متنی در زبان
C توسط
آرایه هایی از متغییر های نوع char که به
یک کاراکتر تهی (null) یا “\0” منتهی
می شوند ایجاد می شوند.
مثال 6.4 text_string.c |
#include
<stdio.h> int main() { Char hello [] = “Hello World!”
; printf ("%s\n",
hello); return
0; } |
6.7 تعرف انواع داده
ای جدید
زبان c تنها انواع داده ای رایج و پایه ای
را فراهم می کند. بسیاری از زبان های
برنامه نویسی مجموعه بزرگتری از انواع داده ای را فراهم می کنند اما در زبان
C شما می
توانید انواع داده ای مورد نیاز خود را خلق کنید.
6.8
داده های ساخت یافته
در زبان c شما می
توانید انواع داده ای جدیدی را خلق کنید . به عنوان مثال یک نوع داده ای جدید به
نام person که می
تواند یک مقدار صحیح را که age نامیده
می شود و نیز یک رشته متنی با نام name و همچنین یک مقدار صحیح دیگر به عنوان
height_in_cm را در خود نگهداری
کند.
کد های زیر برای ایجاد نوع
داده
ای person کافیست
:
تعریف یک نوع
داده ای جدید |
struct Person { Char * name[40]; int age; int height_in_cm; };
|
کدهای بالا متغییری را می سازد
که struct Person نامیده
می شود. شما به طور عادی می توانید متغییر هایی از این نوع و نیز اشاره گر هایی به
متغییر های این نوع را اعلان نمایید.
برای نمونه شما متغییری با نام john از نوع
struct
Person می
سازید. برا ی دسترسی به فیلد age شما
باید john.age را به
کار ببرید.
به مثال زیر توجه کنید :
مثال 7.4 person_struct.c |
#include
<stdio.h> int
main() { struct Person { char
*name[40]; int age; int
height_in_cm; }; struct Person
john;
*john.name = " John
Little";
john.age =
31;
john.height_in_cm =
237;
printf("%s is %d years old and stands %dcm tall in his
socks\n",
*john.name, john.age,john.height_in_cm); return 0
; } |
6.9 آنیون
ها
زبان C همچنین
از متغییر هایی که نوع داده ای آنها به صورت پویا تغییر می کند پشتیبانی می کند .
چنین متغییر هایی می تواند در یک نقطه از برنامه به عنوان یک متغییر از نوع
int و در
جایی دیگر از برنامه به عنوان متغییری از نوع double عمل کرده و در نقطه سوم متغییری از نوع
unsigned
long long
باشند.
برای تعریف این نوع متغییر ها
به همان شیوه تعریف متغییر های ساخت یافته عمل می کنیم با این تفاوت که به جای
عبارت struct این بار
کلمه کلیدی union را به
کار می بریم. در عمل آنیون ها رفتاری کاملا متفاوت از ساختار ها دارند. با استفاده
از کد های زیر یک آنیون با نام int_or_float تعریف
شده است:
تعریف یک
آنیون |
union int_or_float { int int_member; float float_member; }; |
پس از تعریف آنیون می توانیم
متغییر هایی از نوع آن را اعلان نماییم :
union int_or_float my_union1,
my_union2;
فصل
هفتم : تخصیص حافظه در زمان اجرا
7.1 چه نیازی به آن
دارید؟
زمانی که برنامه ای را
می نویسید غالبا اطلاع دقیقی از اینکه برنامه مجبور به نگهداری یا پردازش چه حجمی
از داده هاست ندارید. در مثال های قبلی که برنامه متونی را از طریق ورودی توسط
کاربر دریافت می کرد ما از آرایه های
بزرگ کاراکتری برای نگهداری این متون استفاده می کردیم. اما در صورتیکه کاربر متنی
بزرگتر از گنجایش آرایه ای که توسط ما تعریف شده بود را وارد می کرد چه اتفاقی می
افتاد؟ متاسفانه برنامه شما crash می
کرد.
اما همانطور که در
قسمتهای بعدی این فصل خواهیم دید در زمان اجرا یک برنامه می تواند درخواست مقدار بیشتری از حافظه
را بنماید.
7.2
توابع حافظه
پویا
Glibc یا همان کنابخانه توابع گنو برای زبان
c توابعی
را برای درخواست تخصیص حافظه اضافی فراهم نموده است.
یکی از مهمترین این
توابع ، تابع malloc () است که
نام آن مخفف memory allocation می
باشد.این تابع به منظور تخصیص مقدار مشخصی از حافظه به کار می رود. تابع دیگر
realloc
() است که
به منظور تغییر اندازه و تخصیص مجدد مقدار بیشتری از حافظه کاربرد دارد.در نهایت از
تابع free () برای
آزاد سازی حافظه اختصاص داده شده به برنامه استفاده می شود.
7.2.1
تابع malloc()
تابع malloc() یک آرگومان (نشانوند) دارد که تعداد بایت های حافظه
مورد نیاز برای اختصاص یافتن به برنامه را مشخص می کند. از آنجا که در زمان تخصیص
بلوک حافظه توسط این تابع مشخص نیست که این بلوک برای نگهداری داده هایی از کدام یک
از انواع داده ای استفاده خواهد شد و برای اینکه بلوک حافظه تخصیص یافته توسط داده
هایی از انواع داده ای مختلف قابل اشغال باشد، مقدار برگشتی تابع
malloc () یک اشاره گر عمومی (تهی) خواهد بود. این اشاره گر به
آدرس حافظه اولین بایت بلوک حافظه تخصیص یافته اشاره می کند . در قطعه کد زیر دقت
کنید:
تخصیص یک بلوک
حافظه |
char *my_string; my_string = (char *) malloc(10+1);
|
قطعه کد بالا یک بلوک
حافظه 11 بایتی برای نگهداری یک رشته متنی 10(آخرین بایت توسط کاراکتر null اشغال
می شود و نشانگر پایان رشته است. ) حرفی را به برنامه اختصاص می دهد. همانطور که می
بینید به منظور ارجاع بلوک حافظه به اشاره گر my_string از
عملگر قالب به صورت (char *) استفاده شده است.
7.2.2
تابع realloc
()
همانطور که گفتیم این
تابع به منظور تخصیص مجدد حافظه به کار می رود. تابع realloc () دارای
دو پارامتر است که اولین پارامتر آن اشاره گریست که به بلوک حافظه ای که اندازه آن
تغییر خواهد یافت اشاره می کند و دومین پارامتر عددی است که اندازه جدید بلوک حافظه
را بر حسب بایت مشخص می کند. مقدار برگشتی این تابع نیز یک اشاره گر عمومی (تهی )
خواهد بود که به بلوک حافظه مورد نظر اشاره می کند. با استفاده از دستور زیر می
توان بلوک حافظه تخصیص یافته به اشاره گر my_string در قطعه
کد قبلی را به 1000 بایت افزایش داد:
تغییر اندازه یک
بلوک حافظه |
my_string = (char *) realloc (my_string, 1001); |
بلوک حافظه جدید شامل
تمامی داده های بلوک حافظه قبلی می باشد اما 990 بایت اضافه شده شامل داده های
آشغال (garbage) و
نامربوط است.
7.2.3
تابع free()
بعد از اینکه استفاده
لازم از بلوک حافظه تخصیص داده شده صورت گرفت باید حافظه آن را آزاد کنیم بدین
منظور از تابع free() استفاده
می کنیم . تنها آرگومان این تابع اشاره گریست که به بلوک حافظه مورد نظر اشاره می
کند. این تابع هیچ مقداری را بر نمی گرداند.
آزاد سازی حافظه
یک بلوک حافظه |
free (my_string); |
فصل
هشتم : پیش پردازنده های زبان C
8.1 پیش پردازنده چیست؟
برخی از دستورات زبان
C
که به دستورات توسعه یافته ( یا دستورات
پیش پردازنده ) معروفند توسط کامپایلر قابل فهم نیستند . پیش پردازنده نوعی مترجم
است که دستورات توسعه یافته را به دستورات قابل فهم برای کامپایلر تبدیل می کند.
دستورات پیش پردازنده با کاراکتر
# آغاز می شوند. تا به حال در آغاز
برنامه ها از عبارت #include
استفاده می کردیم که یکی از رایج ترین
دستورات پیش پردازنده است. یعنی دستور include# توسط کامپایلر gcc غیر
قابل فهم بوده و توسط پیش پردازنده به دستورات قابل فهم برای کامپایلر تبدیل می
شود.
8.2
ماکرو
ها
ماکرو نامی است که
برای یک عبارت در زبان c قرار
داده شده و می توان به جای آن عبارت نام انتخابی برای آن که همان ماکرو می باشد را
به کار برد.برای تعریف یک ماکرو از دستور پیش پردازنده #define استفاده
می کنیم. توضیح را با یک مثال پیش می بریم :
مثال 10.1
box_of_stars.c |
#define SIZE_OF_SQUARE 4
int main() { int i, j;
for(i = 0; i < SIZE_OF_SQUARE; i++) { for(j = 0; j < SIZE_OF_SQUARE; j++) { printf("*"); // print an asterisk for each column }
printf("\n"); // and a newline at the end of each row } } |
در مثال بالا ماکروی
SIZE_OF_SQUARE برای
عبارت 4 در نظر
گرفته شده است به همین دلیل پیش پردازنده قبل از کامپایل برنامه توسط کامپایلر
تمامی عبارات SIZE_OF_SQUARE درون تابع
main() را بع
عبارت 4 تغییر می دهد . خروجی برنامه به صورت زیر است:
خروجی برنامه
|
**** **** **** **** |
دو نمونه از کاربرد
ماکرو ها در برنامه نویسی c عبارتند
از :
1)
در مثال
بالا اگر از ماکرو استفاده نکرده بودیم و فرضا مجبور می شدیم مقدار 4 را به 5 تغییر
دهیم باید در تمام برنامه به دنبال مقادیر 4 گشته و آنها را اصلاح می کردیم اما با
استفاده از ماکرو تنها مقدار 4 در دستورdefine # را تغییر می دهیم.
2)
استفاده
از نام هایی معنی دار به جای اعداد گنگ به خوانایی برنامه کمک می کند مثلا در آغاز
برنامه ای برای انجام محاسبات مثلثاتی می توان از دستور زیر استفاده
کرد:
#define PI
3.142857143
8.3
دستور #if
...
#endif
در صورتیکه عبارت شرط
بعد از if# صحیح
باشد دستورات بین if# و
endif# اجرا
خواهند شد. اگر مابین if# و
endif# یک
دستور else# نیز
وجود داشته باشد در صورت صحیح بودن عبارت شرط دستورات بین if# و
else# و در
صورت نا صحیح بودن شرط دستورات بین else# و
endif# اجرا
خواهند شد. مثال زیر در این زمینه گویا خواهد بود:
مثال 10.2 if_endif.c |
#include <stdio.h>
#define CHOICE 500
int my_int = 0;
#if (CHOICE == 500) void set_my_int() { my_int = 23; } #else void set_my_int() { my_int = 17; } #endif
int main () { set_my_int(); printf("%d\n", my_int);
return 0; } |
خروجی برنامه بالا عدد
23 خواهد بود.
8.4
سایر دستورات ماکرو
ها
علاوه بر #define دستورات
پیش پردازنده دیگری نیز برای کار با ماکرو ها وجود دارد:
دستور undef #
ماکروی تعریف شده توسط define # را
پاک می کند.
دستور ifdef # نوعی
از دستور if # است و
بعد از آن نام یک ماکرو قرار می گیرد. در صورتیکه ماکرویی با آن نام تعریف شده باشد
شرط صحیح خواهد داشت.
دستور ifndef # دقیقا
بر عکس دستور ifdef # عمل
می کند.
مثال زیر استفاده از
این دستورات را به شما نشان خواهد داد:
مثال 10.3
m_directives.c |
#include <stdio.h>
#define CHOICE 500
int my_int = 0;
#undef CHOICE #ifdef CHOICE void set_my_int() { my_int = 23; } #else void set_my_int() { my_int = 17; } #endif
int main () { set_my_int(); printf("%d\n", my_int);
return 0; } |
همانطور که متوجه شده
اید خروجی برنامه بالا عدد 17 است.
8.5
توابع
ماکرو
ماکرو ها نیز به مانند
توابع می توانند پارامتر هایی را پذیرفته و مقادیری را برگشت دهند. چنین ماکروهایی
را توابع ماکرو (macro
functions) می
نامند. برای ساخت یک تابع ماکرو به طور خیلی ساده با استفاده از دستور define # یک
ماکرو به همراه یک پارمتری با هر نامی که مایل هستید ( مثلا my_val ) تعریف
کنید.
برای نمونه یکی از
ماکروهایی که در کتابخانه استاندارد زبان C تعریف شده abs نام
دارد. مقدار برگشتی این ماکرو قدر مطلق (absolute value) پارامتر
آن است. اجازه بدهید یک نسخه شخصی از این تابع ماکرو با نام ABS را با هم ایجاد کنیم. انتخاب حروف بزرگ
برای نام ماکرو تنها به دلیل جلوگیری از ناسازگاری آن با abs نیست
بلکه علاوه بر آن در شیوه کد نویسی gnu معمولا
نام ماکرو ها را با حروف بزرگ انتخاب می کنند. به قطعه کد زیر توجه کنید
:
تعریف یک تابع
ماکرو برای محاسبه قدر مطلق |
#define ABS(my_val) ((my_val) < 0) ? -(my_val) : (my_val) |
در قطعه کد بالا برای
تعریف تابع ماکروی قدر مطلق از عملگر شرطی ? ... : ...
استفاده
شده است . در صورتیکه مقدار پارمتر کوچکتر از صفر باشد ( شرط صحیح باشد ) ماکرو
مقداری برابر منفی مقدار پارمتر را برمی گرداند و در صورتی مقدار پارمتر مثبت و شرط
غیر صحیح باشد ماکرو همان مقدار پارامتر را باز می گرداند.
یکی از مزیت های توابع
ماکرو نسبت به توابع واقعی اینست که استفاده از توابع ماکرو سرعت اجرای برنامه را
بالاتر می برد زیرا تعریف آنها قبل
از کامپایل جایگزین نام آنها در سراسر برنامه شده و بر خلاف توابع واقعی کنترل برنامه از
تابع اصلی به توابع فرعی فرستاده نمی شود. در زیر مثالی شامل تعدادی از توابع ماکرو
را مشاهده می کنید:
مثال 10.4
m_func.c |
#include <stdio.h>
#define STRING1 "A macro definition\n" #define STRING2 "must be all on one line!\n" #define EXPRESSION1 1 + 2 + 3 + 4 #define EXPRESSION2 EXPRESSION1 + 10 #define ABS(x) ((x) < 0) ? -(x) : (x) #define MAX(a,b) (a < b) ? (b) : (a) #define BIGGEST(a,b,c) (MAX(a,b) < c) ? (c) : (MAX(a,b))
int main () { printf (STRING1); printf (STRING2); printf ("%d\n", EXPRESSION1); printf ("%d\n", EXPRESSION2); printf ("%d\n", ABS(-5)); printf ("Biggest of 1, 2, and 3 is %d\n", BIGGEST(1,2,3));
return 0; }
|
خروجی برنامه بالا پس
از کامپایل و اجرا به صورت زیر خواهد بود :
خروجی
برنامه |
A macro definition must be all on one line! 10 20 5 Biggest of 1, 2, and 3 is 3
|
8.6
جنبه های احتیاطی در کاربرد
ماکرو ها
احتمال دارد ماکرو ها
مورد استفاده ناصحیح قرار گرفته و اشکالات (bug)
ناخواسته ای را در برنامه ایجاد کنند. برای جلوگیری از این اشکالات همیشه باید مد
نظر داشت که در زمان کامپایل برنامه ماکرو ها دیگر در میان کدها وجود ندارند. یکی
از شایع ترین اشتباهات استفاده از پارامتر (آرگومان) هایی است که همراه با نوعی
واکنش و عملیات ثانوی هستند. در دو مثال زیر دقت کنید:
مثال 10.5 max_macro.c |
#include <stdio.h> #define MAX(a, b) (a > b ? a : b)
int main() { int cows = 10, sheep = 12;
printf("we have %d of our most common animal\n", MAX(cows, sheep));
return 0; }
|
بعد از کامپایل برنامه
را اجرا می کنیم:
اجرای برنامه و
خروجی آن |
ciaran@pooh:~/book$ ./a.out we have 12 of our most common animal ciaran@pooh:~/book$ |
در مثال بالا همه چیز
درست کار می کند . اما در مثال دوم داریم :
مثال 10.6
max_macro_problem.c |
#define MAX(a, b) (a > b ? a : b)
int main() { int cows = 10, sheep = 12;
printf("We have %d of our most common animal\n", MAX(cows, sheep));
printf("Hang on, we just bought another one.\n"); printf("Now we have %d.\n", MAX(cows, ++sheep));
return 0; } |
پس از کامپایل این بار
برنامه دوم را اجرا می کنیم که آرگومان دوم
آن شامل عملگر ++ می
باشد:
اجرای برنامه و
خروجی آن |
ciaran@pooh:~/book$ ./a.out We have 12 of our most common animal Hang on, we just bought another one. Now we have 14. ciaran@pooh:~/book$
|
پس از اینکه پیش
پردازنده ماکرو را با کد های مناسب جایگزین می کند در هنگام کامپایل دو عبارت
++sheep در کد
برنامه قرار می گیرد و باعث می شود در حالیکه انتظار عدد 13 را داریم در خروجی با
عدد 14 مواجه شویم. اشکال رایج دیگری که ممکن است بروز کند زمانیست که شما تصمیم به استفاده از یک تابع به
عنوان آرگومان ماکرو می گیرید. اگر تایع مورد نظر بر روی یک متغییر عمومی
(global) یا ایستا (static) عملیاتی
را انجام دهد ممکن است این عملیات به جای یک بار چند بار صورت گیرد. یافتن این نوع
اشکالات (Bug) در
برنامه بسیار دشوار است. کد ها از لحاظ ظاهری صحیح و بدون عیب هستند به همین دلیل
کامپایلر از آنها اشکال نمی گیرد. این گونه اشکالات تنها در حین اجرای برنامه ظاهر
می شوند .
شما می توانید از
ماکرو های ساده که تنها به اعلان ثوابت می پردازند صرف نظر کرده و به جای آنها از
دستور const استفاده
کنید . استفاده از const این میت
را بر ماکرو ها دارد که شما میتوانید با استفاده از آدرس حافظه آنها از قابلیت های
اشاره گر ها در مورد آنها استفاده کنید.
برای ماکروهای پیچیده
تر ( توابع ماکرو ) نیز جایگزین مناسبی مانند توابع inline وجود
دارد.
8.7
توابع inline
در صورتیکه در آغاز
دستور اعلان تابعی از کلمه کلیدی inline استفاده
شود یک نسخه از آن تابع در جاهایی از برنامه که آن تابع فراخوانی شده است کپی می
شود . استفاده از توابع به صورت inline در
زمانهایی که تعداد دستورات تابع و نیز دفعات فراخوانی آن اندک است توصیه می
شود.
مثال 10.7
inline_func.c |
#include <stdio.h> inline int max(int a,int b) { return a>b?a:b; } int main() { printf ("Max of 10,20 is %d .\n",max(10,20) ); return 0; }
|