مبانی کامپیوتر و برنامه سازی - نصب و راه اندازی شبکه
Download
Report
Transcript مبانی کامپیوتر و برنامه سازی - نصب و راه اندازی شبکه
مراجع
« برنامهنویسیبهزبان،»Cسعیدابریشمی،انتشاراتجهاد
دانشگاهی
« مبانیکامپیوتروبرنامهنویسی بهزبان، »Cمحمدداورپناه
جزی،انتشاراتدانشگاهصنعتیاصفهان
C How to program, Deitel and Deitel, 6th ed
مبانیرایانه،فنیوحرفهای،محمدرضاموحدی،محمدرضا
یمقانی
ابزار)Visual Studio – C++ (6, 2003, 2005, 2008, 2010, 2012
ارزیابیدرس
10
امتحان پایان ترم:
امتحان میان ترم:
پروژه کالسی و نهایی 5 :نمره
حضور و غیاب ،فعالیت کالسی
5
نمره
نمره
مبانی کامپیوتر و برنامه سازی
فصلاول :مبانیکامپیوترها
مدرس :رضارمضانی
1-1تعاریف اولیه
کامپیوتر
الگوریتم
وسیلهای است که دادهها و دستورالعملها را از انسان دریافت
کرده و پس از اجرای دستورالعملها برروی داده ها ،دادههای
حاصل را به انسانها باز میگرداند .به اجرای دستورالعملها برروی
دادهها پردازش گفته میشود.
دستورالعملهایی که برای کامپیوتر نوشته میشود را الگوریتم
گوییم .
برنامه کامپیوتری
به تشریح الگوریتمها برای کامپیوتر با استفاده از یک زبان برنامه
سازی گفته میشود.
1-1تعاریف اولیه
زبان برنامه سازی
زبانی است که برای کامپیوتر قابل فهم بوده و الگوریتمها با استفاده از آن به کامپیوتر داده
میشوند .این زبانها به سه دسته تقسیم میگردند :
زبانهای سطح پایین :که به آن زبان ماشین نیز گفته میشود ،مستقیما به زبان خود کامپیوتر
(یعنی زبان صفر و یک) نوشته میشود و توسط کامپیوتر قابل اجرا میباشد.
زبانهای سطح باال :این زبانها بسیار نزدیک به زبان انسان هستند .مثال :
;If (a > b) then c = c + 1
برای تبدیل این زبان به زبان ماشین نیاز به مترجم داریم :
کامپایلر ) : (Compilerابتدا کل برنامه زبان سطح باال را بررسی کرده و درصورت نبود خطا کل آن را به زبان
ماشین تبدیل میکند .اکنون برنامه آماده اجرا است.
مفسر ) : (Interpreterبرنامه زبان سطح باال را دستور به دستور به زبان ماشین تبدیل و همزمان آن را اجرا
میکند.
زبانهای بسیار سطح باال :زبانهای خاص منظورهای که برای عملیات خاص طراحی شدهاند همانند
زبان PROLOGبرای هوش مصنوعی و یا SQLبرای پایگاه داده ها.
1-2تاریخچه کامپیوتر
کامپیوترهای نسل اول
کامپیوترهای نسل دوم
در سال 1964با ابداع مدارات مجتمع ICکه صدها ترانزیستور را در یک فضای کوچک
جای میداد ،ایجاد شدند.
نسل چهارم کامپیوترها
ابداع در اوایل دهه 1960و ویژگی مهم آنها استفاده از ترانزیستور بود.
کامپیوترهای نسل سوم
ابداع در اوایل دهه 1950و از المپ خالء بعنوان جزو اصلی خود استفاده میکردند.
در اواسط دهه 1970با ابداع مدارات مجتمع با فشردگی باال ایجاد شدند.
نسل پنجم کامپیوترها
یا نسل کامپیوترهای هوشمند که قادر به انجام اعمالی همانند استنتاج و استدالل
مانند انسانها باشند.
1-3انواع کامپیوتر
کامپیوترهای بزرگ )(mainframe
این کامپیوترها از سرعت و قدرت باالیی برخوردارند و معموال در
سازمانهای بزرگ و برای محاسبات سنگین استفاده میشوند .دستهای
از این کامپیوترها که دارای توان بسیار باالی محاسباتی هستند به
ابرکامپیوتر ) (supercomputerموسومند.
کامپیوترهای کوچک )(minicomputer
در اواخر دهه 1950کامپیوترهای کوچک وارد بازاز شدند که توان
محاسباتی کمتری داشتند و توسط سازمانهای کوچکتر مورد استفاده
قرار میگرفتند.
ریزکامپیوتر )(microcomputer
در آغاز دهه 1980ریزکامپیوترها یا کامپیوترهای شخصی با قیمت پایین
و حجم بسیار کوچک وارد بازار شدند و مورد استقبال مردم و افراد عادی
قرار گرفتند.
1-4اجزای کامپیوتر
کامپیوتر از دو قسمت اصلی تشکیل شده است
سخت افزار ) : (Hardwareکلیه دستگاههای الکتریکی،
الکترونیکی و مکانیکی تشکیل دهنده یک کامپیوتر را
سخت افزار آن میگوییم.
نرم افزار ) : (Softwareمجموعه برنامههایی هستند که
برای یک کاربرد خاص نوشته شدهاند و بدون آنها سخت
افزار قادر به کاری نیست.
1-4-1سخت افزار کامپیوتر
کنترل
واحد خروجی
حافظه اصلی
محاسبه و منطق
حافظه جانبی
واحد ورودی
1-4-1سخت افزار کامپیوتر
واحد ورودی
واحد خروجی
این بخش وظیفه انتقال اطالعات از کامپیوتر به محیط خارج را بعهده دارد و مهمترین
دستگاههای خروجی عبارتند از :
صفحه نمایش) ،(Monitorچاپگر ،رسام ،بلندگو و ...
واحد محاسبه و منطق
وظیفه این بخش دریافت دادهها از محیط خارج و انتقال آنها به کامپیوتر میباشد.
دستگاههای ورودی مهم عبارتند از :
صفحه کلید ،ماوس ،صفحه لمسی ) ،(touch screenقلم نوری ،اسکنر ،دیجیتایزر و ...
مغز اصلی کامپیوتر است که اعمال اصلی همچون جمع ،ضرب ،تفریق ،تقسیم،
مقایسه دو مقدار و ...در آن انجام میپذیرد.
واحد کنترل
این بخش و ظیفه کنترل سایر بخشها را بعهده دارد و تصمیم میگیرد کدام عمل در
چه زمانی صورت پذیرد این بخش بهمراه واحد محاسبه و منطق تشکیل واحد
پردازش مرکزی ) CPU (Central Processing Unitرا میدهند.
1-4-1سخت افزار کامپیوتر
واحد حافظه اصلی
این واحد وظیفه نگهداری اطالعات (شامل دادهها و برنامه ها) را بعهده دارد .در واقع
هر برنامهای برای اجرا ،ابتدا باید بهمراه دادههای مورد نیاز وارد حافظه اصلی
گردد.حافظه اصلی به دو دسته اصلی تقسیم میگردد :
حافظه با دستیابی تصادفی ) (RAM Random Access Memoryاین حافظه قابلخواندن و نوشتن میباشد و برای ذخیره اطالعات کاربران بکار میرود.
حافظه فقط خواندنی ) (ROM Read Only Memoryاین حافظه فقط قابل خواندناست و محتویات آن قابل تغییر نیست .این حافظه معموال در کارخانه سازنده پر شده و
حاوی دستورالعملهای الزم برای راه اندازی اولیه کامپیوتر میباشد.
حافظه از واحدهای کوچکی بنام بیت ) (Bitتشکیل شده است که هر بیت قابلیت
نگاهداری یک 0یا 1را در خود دارد .به هر 8بیت یک بایت ) (Byteگفته میشود که
واحد اندازه گیری حافظه است .به هر 2یا 4بایت ،یک کلمه ) (Wordمیگوییم.
عالوه براین داریم :
= 1024 Byte
= 1024 KiloByte = 1048576 Byte
= 1024 MegaByte = 1073741824 Byte
= 1024 GigaByte
1 KiloByte or 1K
1 MegaByte or 1M
1 GigaByte or 1G
1 TeraByte or 1T
1-4-1سخت افزار کامپیوتر
حافظه جانبی
از آنجا که با خاموش شدن کامپیوتر اطالعات حافظه اصلی پاک
میگردد ،نیاز به حافظهای داریم که بتواند دادهها را مدت
طوالنیتری در خود نگاه دارد .حافظه جانبی برای نگاهداری
طوالنی مدت اطالعات و همچنین جابجایی آنها بکار میرود.
عالوه براین بدلیل سرعت پایینتر نسبت به حافظه اصلی ،ارزانتر
بوده و درنتیجه معموال حجم آن باال تر میباشد .اما نکته مهم آن
است که اطالعات برای پردازش ابتدا باید وارد حافظه اصلی
گردند.در حال حاضر حافظههای جانبی مهم عبارتند از :
دیسکهای مغناطیسی )(Hard Disk and Floppy Disk
دیسکهای نوری )(CD and DVD
Flash Disk
1-4-2نرم افزار کامپیوتر
نرم افزار کامپیوتر به دو دسته اصلی تقسیم میگردد :
نرم افزارهای کاربردی :نرم افزارهایی هستند که برای یک کاربرد خاص
و رفع یک نیاز مشخص کاربران نوشته شده اند .مانند سیستمهای
حسابداری ،دبیرخانه ،سیستم انتخاب واحد دانشگاهی ،انواع بازیها
نرم افزارهای سیستمی :نرم افزارهایی هستند که برای ایجاد و یا
اجرای برنامههای کاربردی نوشته میشوند .مهمترین برنامه
سیستمی ،سیستم عامل است .سیستم عامل نرم افزاری است که
ارتباط بین سخت افزار و کاربران (یا برنامههای کاربردی کاربران) را
فراهم میسازد .در حقیقت سیستم عامل مدیریت منابع سخت افزاری
یک کامپیوتر را بعهده دارد.
: Windowsبیشتر در منازل و محیطهای اداری مورد استفاده قرار میگیرد
: Linuxبیشتر در محیطهای دانشگاهی و بعنوان سرور استفاده میشود.
: Unixنیز بیشتر در کامپیوترهای بزرگ نصب میشود.
مبانی کامپیوتر و برنامه سازی
فصلدوم :نحوهنمایشاطالعاتدرکامپیوتر
مدرس :رضارمضانی
2نحوه نمایش اطالعات در کامپیوتر
اطالعات در کامپیوتر به دو دسته اصلی تقسیم میگردند:
اطالعات کاراکتری (حرفی) :مانند A B …Z $ # @ ! :
اطالعات عددی که خود به دو دسته اعداد صحیح و اعداد اعشاری
تقسیم میگردند.
برای نمایش اطالعات در کامپیوتر از مبنای 2استفاده میگردد
2-1سیستم اعداد
مبنای ، 10مبنای مورد استفاده انسانها در ریاضیات
در ریاضیات متداول هر عدد Nبصورت زیر تفسیر میگردد :
N = (an-1 an-2 … a2 a1 a0 )10 = a0 × 100 + a1 × 101 + a2 × 102 + … an-1 × 10n-1
بعنوان مثال عدد 3482بصورت زیر تفسیر میگردد :
(3482 )10 = 2 × 100 + 8 × 101 + 4 × 102 + 3 × 103
در سیستم دهدهی نیاز به 10رقم (از 0تا )9داریم.
می توان اعداد را در هر مبنای دلخواه دیگری مانند bنیز نشان داد در اینصورت هر عدد
مانند Nدر مبنای bبصورت زیر تفسیر میگردد :
N = (an-1 an-2 … a2 a1 a0 )b = a0 × b0 + a1 × b1 + a2 × b2 + … an-1 × bn-1
کامال واضح است که در مبنای bنیاز به bرقم (از 0تا ) b-1خواهیم داشت.
بعنوان مثال یک عدد در مبنای 6از ارقام 0..5تشکیل میگردد ،بنابراین (341)6یک عدد درست
است اما (592)6غیر قابل قبول میباشد.
2-2تبدیل مبناها
برای تبدیل یک عدد از مبنای 10به هر مبنای دلخواه ،bاز روش تقسیمات
متوالی استفاده میگردد
(941)10 = (?)6
6
0
6
4
0
4
941 6
936 156 6
26
156
5
0 24
2
(941)10 = (4205)6
2-2تبدیل مبناها
برای تبدیل از مبنای bبه مبنای 10کافی است ارقام عدد مورد نظر را در
ارزش مکانی آنها ضرب و سپس با یکدیگر جمع کنیم .
(4205)6 = (?)10
(4205)6 = 5 × 60 + 0 × 61 + 2 × 62 + 4 × 63 = 5 + 0 + 72 + 864 = (941)10
2-3مبنای 2و اهمیت آن
مبنای 2اهمیت بسیار زیادی در کامپیوترهای دیجیتال دارد .چراکه :
در مبنای 2تنها به 2رقم نیاز داریم،یعنی 0و 1
آین دو رقم را میتوان توسط هر ابزاری که دارای دو حالت باشد نشان داد .مثال
یک المپ که خاموش بودن المپ به معنای 0و روشن بودن آن به معنای 1
میباشد.
این همان ایدهای است که کامپیوترهای دیجیتال از آن استفاده میکنند.
همانطور که قبال نیز گفته شد واحد نگهداری اطالعات در کامپیوتر بیت میباشد
که هر بیت قادر به نگهداری 0و یا 1است .با کنار هم قرار دادن بیتها ،بایتها
تشکیل میگردند و بدینوسیله اطالعات مورد نظر در قالب بایتها تشکیل
میگردند.
2-3مبنای 2و اهمیت آن
تبدیل اعداد از مبنای 10به 2و بالعکس بسیار ساده و همانند سایر مبناها
است.
تبدیل از مبنای 2به 10
(11001001)2 = (?)10
(11001001)2 = 1 × 20 + 0 × 21 + 0 × 22 + 1 × 23 + 0 × 24 + 0 × 25 +
1 × 26 + 1 × 27
= 1 + 0 + 0 + 8 + 0 + 0 + 64 + 128 = (201)10
تبدیل از مبنای 10به مبنای 2
(486)10 = (?)2
2-3مبنای 2و اهمیت آن
2
0
2
1
0
1
2
3
2
1
486 2
486 243 2
121 2
0 242
1 120 60 2
2
1 60 30
30 15 2
0
0 14 7
1 6
1
(486)10 = (111100110)2
2-4مبناهای 8و 16و کاربرد آنها
مشکل اصلی در مبنای 2اندازه بزرگ اعداد است .بعنوان مثال عدد 486که در
مبنای 10تنها 3رقم دارد ،تبدیل به یک عدد 9رقمی در مبنای 2شده است.
این مسئله باعث میشود که محاسبه در مبنای 2برای انسانها بسیار مشکل
شود و معموال برنامهنویسان عالقه چندانی به مبنای 2ندارند.
مبنای 8نیز همانند سایر مبناها میتواند مورد استفاده قرار گیرد و در ظاهر
تفاوتی با سایر مبناها ندارد .اما ویژگی جالب این مبنا در تبدیل ساده آن به
مبنای 2و بالعکس است.
همانطور که میدانیم در مبنای 8تنها ارقام 0تا 7استفاده میشوند .از طرف
دیگر اگر یک عدد در مبنای 2با حداکثر 3رقم را درنظر بگیریم ،در مییابیم که
میتوان
000 = 0, 001=1, 010=2, … , 111=7
را با آن نشان داد .بنابراین میتوان نتیجه گرفت که هر 3رقم در مبنای ،2برابر
است با 1رقم در مبنای 8و بالعکس .این نتیجه گیری تبدیل این دو مبنا را به
یکدیگر ساده میکند.
2-4مبناهای 8و 16و کاربرد آنها
تبدیل از مبنای 2به 8
تبدیل از مبنای 8به 2
1 0 1 0 1 1 1 0 = (256)8
6
= (10111001)2
5
2
( 2 7 1 )8
010 111 001
2-4مبناهای 8و 16و کاربرد آنها
اکثر برنامهنویسان کامپیوتر ترجیح میدهند از مبنای دیگری بنام مبنای 16
استفاده نمایند.
این مبنا نیز همانند مبنای 8بسادگی قابل تبدیل به مبنای 2است ،اما
اعداد آن به ارقام کمتری نیاز دارند.
در این مبنا نیاز به 16رقم داریم درحالیکه ارقام موجود فقط 10تا است.
بهمین دلیل از حروف Aتا Fبرای ارقام 10تا 15استفاده میگردد.یعنی
ارقام عبارتند از :
0 1 2 3 4 5 6 7 8 9 A B C D E F
تبدیل اعداد از مبنای 2به 16و بالعکس ازهمان روش گفته شده برای
مبنای 8استفاده مینماییم با این تفاوت که هر رقم در مبنای 16معادل 4
رقم در مبنای 2است.
2-4مبناهای 8و 16و کاربرد آنها
تبدیل از مبنای 2به 16
تبدیل از مبنای 16به 2
1 1 0 1 0 1 1 1 0 0 1 = (6B9)16
9
B
6
( A 3 E )16 = (101000111110)2
101000111110
2-5نمایش اعداد صحیح
اعداد صحیح در کامپیوتر با استفاده از مبنای 2نمایش داده میشوند.
برای نمایش اعداد صحیح از 1یا 2بایت و یا بیشتر (بسته به اندازه
عدد) استفاده میگردد.
چنانچه قصد ذخیره اعداد صحیح مثبت را داشته باشیم ،با استفاده از 1
بایت میتوان اعداد 0تا 255را ذخیره کرد .بنابراین برای 1بایت،
بزرگترین عدد قابل ذخیره برابر است با .28 – 1 = 255
با استدالل مشابهی چنانچه از 2بایت یا 16بیت استفاده گردد،
بزرگترین عدد قابل ذخیره برابر 216 – 1 = 65535خواهد بود.
اما مشکل آنستکه اعداد منفی را چگونه ذخیره نماییم؟
برای این کار چندین روش وجود دارد که هریک را جداگانه بررسی
مینماییم.
2-5-1استفاده از بیت عالمت
در این روش سمت چپ ترین بیت برای عالمت عدد درنظر گرفته
میشود و سایر بیتها مقدار عدد رانشان میدهند.
0بودن بیت عالمت بمعنای مثبت بودن و 1بودن آن به معنای منفی
بودن عدد میباشد.
با داشتن 8بیت میتوان اعداد بین -127 … +127را نمایش داد.
1 1010011
-83
0 1010011
+83
این روش دو مشکل اصلی دارد:
دو مقدار متفاوت +0و -0وجود دارد.
برای عمل جمع و تفریق نیاز به دو مدار جداگانه داریم.
2-5-2استفاده از متمم 1
در این روش اعداد مثبت بصورت معمولی نمایش داده میشوند .اما برای
نمایش اعداد منفی ،ابتدا قدر مطلق آن را (بصورت عدد مثبت) نمایش داده
و سپس کلیه 0ها را به 1و بالعکس تبدیل مینماییم.
10101100
01010011
+ 83
- 83
با توجه به محدودیت تعداد بیتها و جلوگیری از تداخل اعداد مثبت و منفی ،برای اعداد مثبت
باید بیت سمت چپ 0باشد و بهمین دلیل بازه اعداد مجاز از -127تا +127است.
خوشبختانه اکنون تنها نیاز به یک مدار برای عمل جمع و تفریق است اما هنوز هم دو نمایش
مختلف برای +0و -0وجود دارد.
11111111
-0
00000000
+0
2-5-3استفاده از متمم 2
در این روش برای نمایش اعداد منفی ابتدا متمم 1آنها را محاسبه و سپس
آن را با 1جمع میکنیم.
10100110
-90
10100101
01011010
+90
بازه اعداد مجاز در این روش از -128تا +127است.
2-5-3استفاده از متمم 2
این روش هردو مشکل روشهای قبل را حل میکند .چرا که :
تنها یک نمایش برای +0و -0وجود دارد.
1 00000000
-0
11111111
00000000
+0
برای عمل تفریق میتوان از همان مدار جمع استفاده
کرد .بدین ترتیب که ابتدا عدد دوم را به روش متمم
2منفی کرده و با عدد اول جمع میکنیم.
53 – 22 = 53 + (-22) = 31
)(00110101) – (00010110) = (00110101) + (11101010
)= 1(00011111
38 – 60 = 38 + (-60) = -22
)(00100110) – (00111100) = (00100110) + (11000100
2-6نمایش مقادیر اعشاری
نمایش اعداد اعشاری در کامپیوتر مشکلتر است.
ما معموال اعداد اعشاری را به شکل ممیز ثابت نشان میدهیم 53.648 :
اما شکل دیگری نیز وجود دارد که به آن نماد علمی یا ممیز شناور گفته
میشود و به شکل زیر است :
53.648 × 100
5.3648 × 101
0.53648 × 102
5364.8 × 10-2
همانطور که دیده میشود ،مکان ممیز در این نمایش شناور است و میتواند در
هر نقطهای قرار گیرد و البته توان نیز باید متناسب با آن تنظیم گردد .نماد
علمی نرمال به حالتی گفته میشود که قسمت صحیح فقط دارای یک رقم
غیر صفر باشد.
برای نمایش اعداد اعشاری در کامپیوتر استانداردهای مختلفی وجود دارد که
همگی در اصول مشترکند و تنها تفاوتهایی در جزئیات دارند.
2-6نمایش مقادیر اعشاری
ما در اینجا از یک روش استاندارد متداول که توسط انجمن معتبر IEEEارائه
شده است ،استفاده میکنیم .این استاندارد بنام
IEEE Standard 754 Floating Point Numbers
شناخته شده است.
برای اطالعات بیشتر به سایت http://standards.ieee.orgمراجعه نمایید.
در استاندارد مورد نظر هر عدد اعشاری در یک کلمه 32بیتی ذخیره
میگردد .ساختار هر عدد بصورت زیر است:
8
23
m
مانتیس
e
توان
1
s
عالمت
2-6نمایش مقادیر اعشاری
برای ذخیره یک عدد اعشاری باید ابتدا آن را به نماد علمی نرمال در
مبنای 2تبدیل کنیم .سپس آن را به شکل زیر ذخیره میکنیم :
عالمت عدد را در sقرار میدهیم (مثبت = 0و منفی = )1
توان را در قسمت eقرار میدهیم که یک عدد مثبت بدون عالمت
است(بین 0تا .)255
نکته مهم آنکه از آنجا که ممکن است توان مثبت یا منفی باشد ،ابتدا 127
واحد به توان اضافه میکنیم و سپس آن را ذخیره میکنیم.
بنابراین e = 131باشد بدین معناست که توان برابر 4بوده است و e=120به
معنای توان برابر -7است.
البته توانهای e=255و e=0برای منظورهای خاصی در نظر گرفته شدهاند که
بعدا توضیح داده خواهند شد.
قسمت پایه عدد را در مانتیس قرار میدهیم.
نکته جالب آن است که چون در نمایش نرمال قسمت صحیح تنها یک رقم
غیرصفر دارد و در نمایش دودویی نیز تنها رقم غیر صفر ،رقم 1میباشد؛
بنابراین تنها قسمت اعشاری در مانتیس ذخیره میگردد و قسمت صحیح
بطور پیش فرض 1درنظر گرفته میشود .این باعث میشود که یک بیت در
ذخیره سازی صرفه جویی گردد.
2-6نمایش مقادیر اعشاری
مثال :عدد 486را بصورت دودویی اعشاری ذخیره نمایید.
ابتدا آن را به مبنای 2تبدیل میکنیم.
(486)10 = (111100110)2
اکنون داریم :
بنابراین جواب نهایی بصورت زیر خواهد شد :
111100110 = 1.11100110 × 28
m : 11100110
e : 8+127 = 135 = 10000111
11100110000000000000000
10000111
0
s:0
2-6نمایش مقادیر اعشاری
مثال :2عدد -121.640625را بصورت دودویی اعشاری ذخیره نمایید.
ابتدا عدد 121را به مبنای 2میبریم:
(121)10 = (1111001)2
واما برای تبدیل قسمت اعشاری باید از روش ضربهای متوالی استفاده نماییم .بدین
صورت که ابتدا آن را در 2ضرب کرده و قسمت صحیح حاصلضرب را ذخیره میکنیم.
همین عمل را مجددا برروی قسمت اعشاری حاصلضرب انجام میدهیم و اینکار را تا
صفر شدن قسمت اعشاری و یا پر شدن تعداد بیتهای کلمه موردنظر ( 23بیت) تکرار
میکنیم .در پایان ارقام ذخیره شده را از اول به آخر به ترتیب پس از ممیز قرار
میدهیم .داریم :
0.640625 × 2 = 1.28125
= 1قسمت صحیح
0.28125 × 2 = 0.5625
= 0قسمت صحیح
0.5625 × 2 = 1.125
= 1قسمت صحیح
0.125 × 2 = 0.25
= 0قسمت صحیح
0.25 × 2 = 0.5
= 0قسمت صحیح
0.5 × 2 = 1.00
= 1قسمت صحیح
(0.640625)10 = (0.101001)2
2-6نمایش مقادیر اعشاری
بنابراین عدد نهایی بصورت زیر درخواهد آمد :
(121.640625)10 = (1111001.101001)2 = (1.111001101001 × 26)2
m : 111001101001
e : 6 + 127 = 133 = 10000011
11100110100100000000000
10000011
s:1
1
نکته مهم :توجه داشته باشید که به دلیل محدود بودن اندازه مانتیس ( 23بیت) ،در هنگام
تبدیل اعداد از مبنای 10به مبنای 2مجبور هستیم عمل ضرب را تا زمانیکه بیتها پر شوند
ادامه دهیم .این مسئله باعث میشود که مقدار تقریبی اعداد در کلمه 32بیتی ذخیره گردد.
بنابراین در هنگام کار با اعداد اعشاری به این مسئله توجه کنید
2-6نمایش مقادیر اعشاری
یک عدد اعشاری ذخیره شده به فرم استاندارد موردنظر به شکل زیر تفسیر
میگردد :
اگر 0<e<255آنگاه
اگر e=0و mغیر صفر باشد آنگاه
= (-1)s × (0.m) × 2e-126عدد
که به آن عدد غیرنرمال ) (unnormalizedگفته میشود .دلیل این مسئله آنست که با
کوچکترین توان ممکن یعنی -126بتوان کوچکترین مانتیس ممکن (بدون اضافه شدن )1
را برای نمایش اعداد کوچک داشت.
اگر e=0و m=0آنگاه = 0عدد
= (-1)s × (1.m) × 2e-127عدد
که البته بسته به میزان sمقدار آن +0یا -0خواهد بود.
اگر e=255و mغیرصفر باشد آنگاه حاصل یک عدد نیست ( NaNیا )Not a Number
اگر e=255و m=0و s=0آنگاه عدد برابر مثبت بینهایت است.
اگر e=255و m=0و s=1آنگاه عدد برابر منفی بینهایت است.
2-7نمایش کاراکترها در کامپیوتر
در کامپیوترها عالوه بر اطالعات عددی ،گاهی الزم است که حروف و عالئم نیز ذخیره
گردد که به آنها کاراکتر میگوییم.
برای ذخیره سازی کاراکترها به هریک از آنها یک کد عددی نسبت داده شده است و در
حقیقت کد عددی هر کاراکتر در کامپیوتر ذخیره میگردد.
در گذشته پر کاربردترین کد مورد استفاده ،کد ASCIIبود که برای نمایش هر کاراکتر از
یک بایت استفاده میکرد .از آنجا که هر بایت میتواند بین 0تا 255تغییر کند ،بنابراین تا
256کاراکتر قابل تعریف است .از این بین کدهای بین 0تا 127بصورت استاندارد برای
عالئم و حروف انگلیسی تعریف شده است و کدهای باالتر از 127برای هر کشور خالی
گذاشته شده است تا بتوانند حروف خاص زبان خود را تعریف کنند .بعنوان مثال به
کدهای ASCIIزیر دقت کنید:
A=65
B=66
C=67 … 0=48
… 1=49
اما امروزه و بدلیل ارتباطات گسترده جهانی از طریق اینترنت ،نیاز به تعریف یک کد بین
المللی میباشد که کلیه زبانهای جهانی را دربرگیرد .چراکه متنی که در کشور دیگری
به زبان فارسی نوشته میشود باید در ایران هم قابل خواندن باشد و الزمه این مسئله
یکسان بودن کدها است.
بهمین دلیل اخیرا کد بین المللی بنام Unicodeابداع شده است که تقریبا تمام زبانهای
زنده دنیا (از جمله زبان فارسی) را دربر میگیرد .البته این کد از 2بایت برای نمایش هر
کاراکتر استفاده میکند و سیستم عاملهای جدید همگی از آن حمایت میکنند.
مبانی کامپیوتر و برنامه سازی
فصلسوم:الگوریتم
مدرس :رضارمضانی
3الگوریتم
الگوریتم
مجموعه محدودی از دستورالعملها است که اگر به ترتیب دنبال
شوند موجب انجام کار خاصی میگردند.
هر الگوریتم باید دارای شرایط زیر باشد
ورودی :یک الگوریتم میتواند صفر یا چند ورودی داشته باشد که
از محیط خارج تامین میگردد.
خروجی :الگوریتم باید یک یا چند کمیت خروجی داشته باشد.
قطعیت :هر دستورالعمل باید واضح و بدون ابهام باشد.
کارایی :هر دستورالعمل باید قابل اجرا باشد.
محدودیت :در تمام حاالت ،الگوریتم باید پس از طی مراحل
محدودی خاتمه یابد.
3الگوریتم
در علم کامپیوتر ،ما معموال با یک مسئله مواجهیم که باید آن را حل
کنیم .این مسئله میتواند در زمینههای مختلفی همچون علمی،
اقتصادی ،ریاضی ،فنی و ...باشد.
معموال برای حل یک مسئله ،مراحل زیر طی میگردد
تعریف مسئله بصورت جامع و دقیق (شامل تعریف ورودیها و خروجیها)
بررسی راه حلهای مختلف برای حل مسئله
انتخاب مناسبترین راه حل و تهیه یک الگوریتم برای آن
آزمایش الگوریتم با دادههای ورودی و اشکالزدایی آن
تبدیل الگوریتم به یک زبان برنامهنویسی کامپیوتری (مانند Cیا )Pascal
وارد کردن برنامه به کامپیوتر و تست و اشکالزدایی آن
استفاده از برنامه
3-1نحوه بیان الگوریتمها
چگونه میتوانیم الگوریتمها را بیان کنیم؟
الگوریتمها باید برای انسانها قابل فهم و درک باشند و همه بتوانند به
راحتی منظور نویسنده الگوریتم را درک کنند
معموال الگوریتمها به یک زبان طبیعی مانند فارسی یا انگلیسی نوشته
میشود.
این مسئله باعث میشود که بعضی ابهامات در درک الگوریتمها پیش
آید.
معموال یکسری از توافقات و تعریفها از قبل بین طراح و خواننده الگوریتم
برقرار میگردد.
از آنجا که زبانهای برنامهنویسی مانند Cبه زبان انگلیسی خیلی
نزدیک هستند ،بعض از طراحان از ترکیب زبان Cو انگلیسی (که به آن
کد شبه Cمیگویند) برای بیان الگوریتم استفاده میکنند
الزم بذکر است که در گذشته از نمودار گردشی ) (Flowchartنیز برای
بیان الگوریتمها با استفاده از شکلهای استاندارد استفاده میشد ،که
تنها برای الگوریتمهای کوچک مناسب بود.
3-2شروع به کار با الگوریتمها
الگوریتمی بنویسید که ضرایب یک معادله درجه دوم بصورت زیر را دریافت و
ریشههای آن را محاسبه و چاپ کند.
ax2 + bx + c = 0
برای حل این مسئله ابتدا باید ضرائب b ، aو cاز کاربر دریافت و در خانههای حافظه
ذخیره گردند.
برای اینکه بتوانیم بعدا به این خانههای حافظه مراجعه کنیم ،به هریک از آنها یک
نام نسبت میدهیم .به هریک از این نامها یک متغیر گفته میشود.
دلیل این نامگذاری آنستکه مقادیر ذخیره شده در هریک از این خانههای حافظه
میتواند تغییر کند.
گرچه انتخاب نام بعهده خودشماست و میتواند هر چیزی باشد ،ولی توصیه
میگردد از اسامی بامعنی و متناسب استفاده گردد .این کار سبب میشود که
خواندن و درک الگوریتم شما برای سایر افراد نیز ساده گردد.
اکنون به قراردادهای زیر توجه کنید :
برای دریافت اطالعات از کاربر از دستور بخوان استفاده میگردد.
برای نوشتن اطالعات در خروجی از دستور چاپ کن استفاده میگردد.
برای انتساب یک مقدار به یک متغیر از عالمت ← استفاده میشود.
3-2شروع به کار با الگوریتمها
(1
(2
(3
aو bو cرا بخوان
delta ← b2 – 4ac
x1 b deltaو
(4
(5
x1و x2را چاپ کن
توقف کن
2a
b delta
2a
x2
3-3مکانیزم شرط
مکانیزم شرط هنگامی استفاده میشود که قصد داریم درستی یا
نادرستی یک عبارت رابررسی کرده و متناسب با نتیجه بررسی شرط،
عملیات خاصی را انجام دهیم و یا از انجام بعضی عملیات صرفنظر کنیم.
شکل کلی این دستور به شکل زیر است
اگر (عبارت شرطی) آنگاه دستورات
این دستور به شکل زیر نیز استفاده میشود:
اگر (عبارت شرطی) آنگاه دستورات1
درغیر اینصورت دستورات 2
3-3مکانیزم شرط
(1
(2
(3
(4
aو bو cرا بخوان
اگر ) ( a = 0آنگاه چاپ کن ”معادله درجه 2نیست“ و توقف
کن
delta ← b2 – 4ac
اگر ) ( delta < 0آنگاه چاپ کن ”معادله جواب ندارد“
درغیراینصورت b delta
و b delta
x
x
2a
1
و x1و x2را چاپ کن
(5
توقف کن
2a
2
3-4مکانیزم حلقه تکرار
در بعضی الگوریتمها الزم است که عملیات مشخصی چندین بار تکرار
شوند .بعنوان مثال فرض کنید قصد داریم میانگین معدلهای 100دانشجو را
محاسبه کنیم .قطعا 100بار دستور خواندن و جمع کردن راه حل چندان
مناسبی نیست.
راه حل بهتر آنستکه بگونهای به مجری الگوریتم بگوییم بنحوی عمل
خواندن معدل و جمع زدن آنها را 100بار تکرار کند.
حلقه تکرار مکانیزمی است که مجموعهای از دستوزات را تا زمانیکه شرط
خاصی برقرار باشد تکرار میکند.
حلقه تکرار به دو شکل مورد استفاده قرار میگیرد
3-4مکانیزم حلقه تکرار
شرط در ابتدای حلقه که مکانیزم کلی آن به شکل زیر است :
تا زمانیکه (شرط مورد نظر) دستورات aتا bرا تکرارکن
… (a
.
.
.
… (b
در این حالت ابتدا شرط موردنظر بررسی میگردد؛ درصورتیکه شرط
برقرار نباشد به اولین دستور پس از bمیرود .اما در صورتیکه شرط
درست ارزیابی شود ،دستورات شماره aتا bانجام میشوند و سپس
مجددا به ابتدای حلقه بازگشته و عملیات فوق را مجددا تکرار میکند.
3-4مکانیزم حلقه تکرار
شرط در انتهای حلقه که مکانیزم کلی آن به شکل زیر است :
تکرار کن
…(a
.
.
.
… (b
تا زمانیکه (شرط مورد نظر)
در این روش ابتدا دستورات حلقه یکبار انجام میشوند و در پایان حلقه
شرط بررسی میگردد .چنانچه شرط برقرار نبود به دستور بعدی
میرود و در صورت برقرار بودن شرط ،مجددا به ابتدای حلقه باز
میگردد.
3-4مکانیزم حلقه تکرار
(1
(2
(3
(4
(5
(6
(7
الگوریتمی بنویسید که یک عدد را دریافت و فاکتوریال آن را محاسبه و
چاپ کند.
N! = 1 × 2 × 3 × … × (N-1) × N
الگوریتم
nرا بخوان
i ←1و fact ←1
تا زمانیکه ) ( i ≤ nدستورات 4-5را تکرار کن
fact ← fact × i
i←i+1
factرا چاپ کن
توقف کن
3-5آزمایش الگوریتم
(1
(2
(3
(4
(5
(6
(7
nرا بخوان
i ← 1و fact ← 1
تا زمانیکه ) ( i ≤ nدستورات 4-5را تکرار کن
fact ←fact × i
i ←i + 1
factرا چاپ کن
n=4
توقف کن
اجرای الگوریتم ←
i= 4
1
2
3
5
fact =1
2
6
24
= 24خروجی
3-6چند الگوریتم نمونه
الگوریتمی بنویسید که nعدد را دریافت و حداکثر و حداقل آنها را چاپ
کند.
n (1را بخوان
adad (2را بخوان
min ← adad (3و max ← adad
i ← 2 (4
(5تا زمانیکه ) ( i ≤ nدستورات 6تا 8را تکرار کن
adad (6را بخوان
(7اگر ) ( adad > maxآنگاه max ← adad
در غیر اینصورت اگر ) ( adad < minآنگاه min ← adad
i←i+1
(8
max (9و minرا چاپ کن
(10توقف کن
3-6چند الگوریتم نمونه
n (1را بخوان
adad (2را بخوان
min ← adad (3و max ← adad
i ← 2 (4
(5تا زمانیکه ) ( i ≤ nدستورات 6تا 8را تکرار کن
adad (6را بخوان
(7اگر ) ( adad > maxآنگاه max ← adad
در غیر اینصورت اگر ) ( adad < minآنگاه ← min
adad
n=5
i ← i + 1 (8
2
4
3
i=6
5
max (9و minرا چاپ کن
39
27
21
6
adad = 18
(10توقف کن
27
39
max = 18
اجرای الگوریتم ←
6
min = 18
= 39 6خروجی
3-6چند الگوریتم نمونه
الگوریتمی بنویسید که یک عدد را دریافت و وارون آن را محاسبه و بهمراه
خود عدد چاپ کند .مثال وارون عدد 3872برابر 2783میباشد.
adad (1را بخوان
(2اگر ) ( adad < 0آنگاه a ← -adad
در غیر اینصورت a ← adad
varoon ← 0 (3
(4تازمانیکه ) ( a > 0آنگاه دستورات 5تا 7را تکرار کن
remain ← a mod 10 (5
a ← a / 10 (6
varoon ← varoon × 10 + remain (7
(8اگر ) ( adad < 0آنگاه varoon ← -varoon
adad (9و varoonرا چاپ کن
(10توقف کن
3-6چند الگوریتم نمونه
adad (1را بخوان
(2اگر ) ( adad < 0آنگاه a ← -adad
در غیر اینصورت a ← adad
varoon ← 0 (3
(4تازمانیکه ) ( a > 0آنگاه دستورات 5تا 7را تکرار کن
remain ← a mod 10 (5
a ← a / 10 (6
varoon ← varoon × 10 + remain (7
(8اگر ) ( adad < 0آنگاه varoon ← -varoon
adad = -275
adad (9و varoonرا چاپ کن
275
27
0
a=2
(10توقف کن
0
57
-572
5
varoon =572
اجرای الگوریتم ←
remain = 2
5
7
= -275 -572خروجی
3-6چند الگوریتم نمونه
الگوریتمی بنویسید که مقدار xو تعداد جمالت را دریافت و سپس ) sin(xرا
با استفاده از فرمول زیر تخمین بزند:
3
5
7
x
x
x
sin(x) x ...
!3! 5! 7
x (1و nرا بخوان
sin ← x (2و fact ← 1و xPower ← xوsign ← 1
i ← 1 (3
(4تازمانیکه ) ( i < nدستورات 5تا 9را تکرار کن
fact ← fact × 2i × (2i+1) (5
xPower ← xPower × x2 (6
sign ← -1 × sign (7
sin ← sin + sign × (xPower / fact) (8
i ← i + 1 (9
sin (10را چاپ کن
(11توقف کن
3-6چند الگوریتم نمونه
(1
(2
(3
(4
(5
(6
(7
(8
الگوریتمی بنویسید که یک عدد را دریافت و تعیین کند که آیا اول است یا
خیر؟
adadرابخوان
adad
root
i ← 2و primeSw ← 1
تازمانیکه ) ( i ≤ root and primeSw=1دستورات 5تا 6را تکرار کن
اگر ) ( adad mod i = 0آنگاه primeSw ← 0
i←i+1
اگر ) ( primeSw = 1آنگاه چاپ کن "عدد اول است“
درغیراینصورت چاپ کن "عدد اول نیست"
توقف کن
3-6چند الگوریتم نمونه
(1
(2
(3
(4
(5
(6
(7
(8
(9
الگوریتمی بنویسید که یک عدد صحیح مثبت در مبنای 10را دریافت و
سپس آن را به مبنای bببرد .مبنای bنیز از کاربر دریافت میگردد.
adadو bرا بخوان
hasel ← 0و i ← 0
تازمانیکه ) ( adad > 0دستورات 4تا 7را تکرار کن
remain ← adad mod b
hasel ← hasel + remain × 10 i
( adad ← adad / bتقسیم صحیح بر صحیح)
i←i+1
haselرا چاپ کن
توقف کن
3-6چند الگوریتم نمونه
(1
(2
(3
(4
(5
(6
(7
(8
(9
(10
(11
(12
الگوریتمی بنویسید که برای تعدادی مشترک این اطالعات را بخواند :شماره حساب ،موجودی اولیه،
تعداد برداشت یا واریز و سپس برای هر برداشت یا واریز این اطالعات را دریافت کند:
کد عمل ( = 1برداشت = 2 ،واریز) و مبلغ.
در نهایت برای هرمشترک ،شماره حساب و موجودی نهایی را چاپ کند.
nرا بخوان
i←1
تا زمانیکه ) ( i ≤ nدستورات 4تا 11را تکرار کن
shomareو mojodiو tedadرا بخوان
j←1
تا زمانیکه ) ( j ≤ tedadدستورات 7تا 9را تکرار کن
codeو mablaghرا بخوان
اگر ) ( code = 1آنگاه mojodi ← mojodi – mablagh
درغیراینصورت اگر ) ( code = 2آنگاه mojodi ← mojodi + mablagh
درغیراینصورت چاپ کن "کد اشتباه است"
j←j+1
shomareو mojodiرا چاپ کن
i←i+1
توقف کن
3-6چند الگوریتم نمونه
(1
(2
(3
(4
(5
(6
(7
(8
(9
(10
(11
(12
الگوریتمی بنویسید که معدل و کد جنسیت دانشجویان (پسران= mو دختران= )fیک کالس را
دریافت و در پایان میانگین معدل پسران و میانگین معدل دختران و میانگین معدل کل کالس را چاپ
کند.
nرا بخوان
mSum ← 0و mCount ← 0و fSum ← 0و fCount ← 0
i←1
تا زمانیکه ) ( i ≤ nدستورات 5تا 7را تکرار کن
codeو averageرا بخوان
اگر ) ( code = fآنگاه
fSum ← fSum + averageو fCount ← fCount + 1
در غیراینصورت اگر ) ( code = mآنگاه
mSum ← mSum + averageو mCount ← mCount + 1
در غیراینصورت چاپ کن "کد اشتباه است" و i ← i – 1
i←i+1
اگر ) ( fCount > 0آنگاه fAve ← fSum / fCount
اگر ) ( mCount > 0آنگاه mAve ← mSum / mCount
totalAverage ← (mSum + fSum) / n
mAveو fAveو totalAverageرا چاپ کن
توقف کن
3-6چند الگوریتم نمونه
(1
(2
(3
(4
(5
(6
(7
(8
(9
(10
(11
(12
(13
(14
(15
(16
(17
الگوریتمی بنویسید که برای تعدادی دانشجو ،نام و شماره دانشجویی و تعداد دروس را دریافت کند و سپس برای
هر دانشجو نمره و تعداد واحد هر درس وی را دریافت و معدل وی را محاسبه نماید .در پایان شماره دانشجویی ،نام
و معدل دو دانشجوی برتر کالس را چاپ کند.
nرا بخوان
maxAve1 ← -1و maxAve2 ← -1
i←1
تازمانیکه ) ( i ≤ nدستورات 5تا 15را تکرار کن
nameو idو tedadرا بخوان
sum ← 0و sumVahed ← 0
j←1
تازمانیکه ) ( j ≤ tedadدستورات 9تا 12را تکرار کن
nomreو vahedرا بخوان
sum ← sum + nomre × vahed
sumVahed ← sumVahed + vahed
j←j+1
average ← sum / sumVahed
اگر ) ( average > maxAve1آنگاه
maxName2 ← maxName1و maxId2 ← maxId1
maxAve2 ← maxAve1و maxName1 ← name
maxId1 ← idو maxAve1 ← average
درغیراینصورت اگر ) ( average > maxAve2آنگاه
maxName2 ← nameو maxId2 ← idو maxAve2 ← average
i←i+1
maxId1و maxName1و maxAve1و maxId2و maxName2و maxAveرا چاپ کن
توقف کن
3-6چند الگوریتم نمونه
(1
(2
(3
(4
(5
(6
(7
(8
(9
(10
(11
(12
(13
(14
(15
(16
(17
الگوریتمی بنویسید که برای تعدادی شهر ابتدا نام شهر را دریافت و سپس دمای هوا در 24ساعت گذشته دریافت نماید و میانگین دما
را برای هر شهر محاسبه و چاپ نماید .درپایان موارد زیر را چاپ نماید :
شهری که از همه سردتر بوده است
شهری که از همه گرمتر بوده است
تعداد شهرهای سرد (کمتر از ، )10معتدل (بین 10تا )30و گرم (بیشتر از )30به تفکیک
درضمن تعداد شهرها مشخص نیست و در پایان هر شهر سوال میشود که آیا ادامه بدهیم یا خیر؟
warmDegree ← -100و coldDegree ← 100
coldCount ← 0و warmCount ← 0و mediumCount ← 0
تکرار کن
cityرا بخوان
sum ← 0و hour ← 1
تازمانیکه ) ( hour ≤ 24دستورات 7تا 9را تکرار کن
degreeرا بخوان
sum ← sum + degree
hour ← hour + 1
sum ← sum / 24
اگر ) ( sum > warmDegreeآنگاه
warmDegree ← sumو warmCity ← city
درغیراینصورت اگر ) ( sum < coldDegreeآنگاه
coldDegree ← sumو coldCity ← city
اگر ) ( sum < 10آنگاه coldCount ← coldCount +1
در غیر اینصورت اگر ) ( sum < 30آنگاه
mediumCount ← mediumCount + 1
درغیراینصورت warmCount ← warmCount + 1
چاپ کن "آیا مایل به ادامه هستید ؟"
answerرا بخوان
تا زمانیکه ) ’( answer = ‘yes
اطالعات الزم را چاپ کن
توقف کن
مبانی کامپیوتر و برنامه سازی
فصلچهارم :آرایهها
مدرس :رضارمضانی
4متغیرهای اندیسدار یا آرایه ها
در مثالهایی که در فصل قبل بیان گردید ،از متغیرهای معمولی استفاده گردید.
اما گاهی نیاز به تعداد زیادی متغیر برای نگاهداری دادهها داریم .درچنین
مواردی نه تنها برای نامگذاری این متغیرها دچار مشکل میشویم ،بلکه
دسترسی به تک تک آنها نیز مشکل است.
مثال ) الگوریتمی بنویسید که شماره دانشجویی ،نام و معدل تعدادی دانشجو
را بخواند و مشخصات دانشجویانی را که معدل آنها از میانگین معدل کالس
بیشتر است را چاپ کند.
برای حل این مثال ابتدا باید مشخصات و معدل کلیه دانشجویان دریافت شود تا
بتوانیم میانگین معدلهای آنان را محاسبه کنیم .سپس باید معدل تک تک
دانشجویان با میانگین کالس مقایسه گردد .مسلما نمیتوانیم مجددا از کاربر
بخواهیم که همان اطالعات قبلی را مجددا وارد کند ،بلکه باید از قبل آنها را در
درون متغیرهایی ذخیره کرده باشیم تا اکنون بتوانیم مقایسه را انجام دهیم.
برای اینکار نیاز به تعدادی متغیر (به تعداد دانشجویان مثال 100عدد) داریم که
ما را دچار 2مشکل اساسی میکند :
این متغیرها را چگونه نامگذاری کنیم ؟
بر فرض نامگذاری متغیرها برطبق یک روش خاص ،چگونه تک تک آنها را با میانگین
کل مقایسه کنیم؟ آیا باید برای مقایسه هر کدام یک دستور مجزا بنویسیم ؟
4-1آرایههای یک بعدی
تعریف آرایه :مجموعهای از دادههای همنوع است که تحت یک نام
مشترک ذخیره میگردند.
برای دسترسی به هریک از اعضا یا عناصر آرایه از نام آرایه بعالوه یک
اندیس استفاده میشود .بنابراین هر عنصر آرایه درحقیقت یک متغیر
مستقل از همان نوع مورد نظر است.
یک آرایه پیش از آنکه استفاده گردد باید اعالن شود .اعالن آرایه شامل نام
آرایه و اندازه آن است .عناصر آرایه برای سهولت در دسترسی (معموال) در
خانههای پشت سرهم حافظه ذخیره میگردند.
مثال) آرایه Aرا با 100عضو درنظر بگیر
.....
.....
)A(100
)A(i
)A(1) A(2) A(3) A(4
4-1آرایههای یک بعدی
(1
(2
(3
(4
(5
(6
(7
(8
(9
(10
(11
(12
(13
(14
مثال) الگوریتمی بنویسید که شماره دانشجویی ،نام و معدل تعدادی دانشجو را
بخواند و مشخصات دانشجویانی را که معدل آنها از میانگین معدل کالس بیشتر
است را چاپ کند
nرا بخوان
آرایههای idListو nameListو aveListرا با nعنصر درنظر بگیر.
i ← 1و sum ← 0
تازمانیکه ) ( i ≤ nدستورات 5تا 8را تکرار کن
چاپ کن "مشخصات دانشجوی " " ، i ،را وارد کنید"
) idList(iو ) nameList(iو ) aveList(iرا بخوان
)sum ← sum + aveList(i
i←i+1
totalAve ← sum / n
i←1
تازمانیکه ) ( i ≤ nدستورات 12تا 13را تکرار کن
اگر ) ( aveList(i) ≥ totalAveآنگاه
) idList(iو ) nameList(iو ) aveList(iرا چا پ کن
i ← i +1
توقف کن
4-1آرایههای یک بعدی
(1
(2
(3
(4
(5
(6
(7
(8
(9
(10
(11
(12
(13
(14
(15
(16
(17
(18
الگوریتمی بنویسید که تعدادی عدد را دریافت و سپس ابتدا اعداد مثبت و سپس اعداد منفی را بطور جداگانه چاپ
کند.
nرا بخوان
آرایههای positiveو negativeرا با nعنصر درنظر بگیر
i ← 1و posCount ← 0و negCount ← 0
تازمانیکه ) ( i ≤ nدستورات 5تا 7را تکرار کن
adadرا بخوان
اگر ) ( adad ≥ 0آنگاه
posCount ← posCount + 1و positive(posCount) ← adad
در غیراینصورت negCount ← negCount + 1و negative(negCount) ← adad
i←i+1
چاپ کن "لیست اعداد مثبت"
i←1
تازمانیکه ) ( i ≤ posCountدستورات 11تا 12را تکرار کن
چاپ کن )positive(i
i←i+1
چاپ کن "لیست اعداد منفی"
i←1
تازمانیکه ) ( i ≤ negCountدستورات 16تا 17را تکرار کن
چاپ کن )negative(i
i←i+1
توقف کن
4-1آرایههای یک بعدی
(1
(2
(3
(4
(5
(6
(7
(8
(9
(10
(11
(12
(13
(14
(15
(16
(17
الگوریتمی بنویسید که دو مجموعه از اعداد را خوانده و در دو آرایه قرار دهد .سپس اشتراک
آن دو را محاسبه و در یک آرایه دیگر قرار دهد .در پایان اشتراک حاصل را چاپ کند.
nو mرا بخوان
آرایه Aرا با nعنصر و آرایه Bرا با mعنصر در نظر بگیر.
آرایه Cرا با ) min(n,mعنصر درنظر بگیر
آرایههای Aبا nعنصر و Bبا mعنصر را بخوان (البته نیاز به حلقه دارد)
aCount ← 1و cCount ← 0
تا زمانیکه ) ( aCount ≤ nدستورات 7تا 11را تکرار کن
bCount ← 1و sw ← 1
تازمانیکه ) ( bCount ≤ m and sw = 1دستورات 9تا 10را تکرار کن
اگر ) ) ( A(aCount) = B(bCountآنگاه
cCount ← cCount + 1و ) C(cCount) ← A(aCountو sw ← 0
bCount ← bCount + 1
aCount ← aCount + 1
چاپ کن "اشتراک دو مجموعه برابر است با ":
i←1
تازمانیکه ) ( i ≤ cCountدستورات 15تا 16را تکرار کن
) C(iرا چاپ کن
i←i+1
توقف کن
4-1آرایههای یک بعدی
آزمایش الگوریتم اشتراک
C
23
10
35
17
A
B
cCount
19
35
23
33
17
21
10
bCount
23
45
10
35
8
17
26
12
aCount
4-1آرایههای یک بعدی
(1
(2
(3
(4
(5
(6
(7
(8
(9
(10
(11
(12
(13
(14
(15
(16
(17
(18
(19
(20
(21
(22
الگوریتمی مثال قبل را برای اجتماع دو مجموعه تکرار کنید.
nو mرا بخوان
آرایه Aرا با nعنصر و آرایه Bرا با mعنصر در نظر بگیر.
آرایه Cرا با n+mعنصر درنظر بگیر
آرایههای Aبا nعنصر و Bبا mعنصر را بخوان (البته نیاز به حلقه دارد)
i←1
تا زمانیکه ) ( i ≤ nدستورات 7تا 8را تکرار کن
)C(i) ← A(i
i←i+1
bCount ← 1و cCount ← n
تا زمانیکه ) ( bCount ≤ mدستورات 11تا 16را تکرار کن
aCount ← 1و sw ← 1
تازمانیکه ) ( aCount ≤ n and sw = 1دستورات 13تا 14را تکرار کن
اگر ) ) ( A(aCount) = B(bCountآنگاه sw ← 0
aCount ← aCount + 1
اگر ) (sw = 1آنگاه
cCount ← cCount + 1و )C(cCount) ← B(bCount
bCount ← bCount + 1
چاپ کن "اجتماع دو مجموعه برابر است با ":
i←1
تازمانیکه ) ( i ≤ cCountدستورات 20تا 21را تکرار کن
) C(iرا چاپ کن
i←i+1
توقف کن
4-1آرایههای یک بعدی
الگوریتمی بنویسید که یک لیست را گرفته و پس از حذف اعداد تکراری آن ،حاصل را
در یک لیست دیگر قرار دهد.
n (1را بخوان
(2آرایه Aو Bرا با nعنصر درنظر بگیر
(3آرایه Aرا با nعنصر بخوان
B(1) ← A(1) (4
i ← 2 (5و k ← 1
(6تا زمانیکه ) ( i ≤ nدستورات 7تا 11را تکرار کن
j ← 1 (7
(8تازمانیکه ) ) ( j ≤ k and A(i) ≠ B(jدستور 9را تکرار کن
j ← j + 1 (9
(10اگر ) ( j > kآنگاه
k ← k + 1و )B(k) ← A(i
i ← i + 1 (11
(12لیست Bرا با kعضو چاپ کن (نیاز به حلقه دارد)
(13توقف کن
4-1آرایههای یک بعدی
الگوریتم مثال قبل را بگونهای تغییر دهید که عملیات حذف دادههای تکراری را
درخود آرایه اصلی (بدون کمک آرایه دیگر) انجام دهد.
n (1را بخوان
(2لیست Aرا با nعنصر در نظر بگیر
(3لیست Aرا با nعنصر بخوان (نیاز به حلقه دارد)
i ← 1 (4
(5تازمانیکه ) ( i ≤ nدستورات 6تا 9را تکرار کن
j ← i + 1 (6
(7تازمانیکه ) ( j ≤ nدستور 8را تکرار کن
(8اگر ) ) ( A(i) = A(jآنگاه
) A(j) ← A(nو n ← n – 1
درغیراینصورت j ← j + 1
i ← i + 1 (9
(10لیست Aرا با nعضو چاپ کن
(11توقف کن
4-1آرایههای یک بعدی
الگوریتمی بنویسید که یک لیست مرتب بصورت صعودی از اعداد و یک عدد را از
کاربر دریافت و سپس بدنبال داده در لیست جستجو کند و آن را حذف نماید.
n (1را بخوان
(2آرایه Aرا با nعنصر درنظر بگیر
(3آرایه Aرا با nعنصر بخوان (بصورت مرتب شده)
x (4را بخوان
i ← 1 (5
(6تازمانیکه ) ) ( i ≤ n and x≠A(iدستور 7را تکرار کن
i ← i + 1 (7
(8اگر ) ( i > nچاپ کن "عدد پیدا نشد" و توقف کن
(9تازمانیکه ) ( i < nدستورات 10تا 11را تکرار کن
A(i) ← A(i + 1) (10
i ← i + 1 (11
n ← n -1 (12
(13آرایه Aرا با nعضو چاپ کن
(14توقف کن
4-1آرایههای یک بعدی
آزمایش الگوریتم حذف از لیست مرتب
A
n=9
10
x = 38
12
18
21
35
38
42
44
48
52
58
i
4-1آرایههای یک بعدی
الگوریتمی بنویسید که یک لیست مرتب بصورت صعودی از اعداد را از کاربر
دریافت و سپس با دریافت یک عدد جدید از کاربر آن را درمکان مناسب لیست
درج کند بطوریکه ترتیب حفظ شود.
n (1را بخوان
(2آرایه Aرا با n + 1عنصر درنظر بگیر
(3آرایه Aرا با nعنصر بخوان (بصورت مرتب شده)
x (4را بخوان
i ← n (5
(6تازمانیکه ) ) ( i ≥ 1 and x < A(iدستورات 7تا 8را تکرار کن
A(i + 1) ← A(i) (7
i ← i – 1 (8
A(i + 1) ← x (9
n ← n + 1 (10
(11آرایه Aرا با nعضو چاپ کن
(12توقف کن
4-1آرایههای یک بعدی
آزمایش الگوریتم درج در لیست مرتب
A
n = 10
11
x = 39
12
18
21
35
38
39
42
44
48
52
58
i
4-1آرایههای یک بعدی
الگوریتمی بنویسید که یک لیست از اعداد را دریافت و عددی که بیشترین تکرار را دارد چاپ کند.
n (1را بخوان
(2آرایههای Aو swرا با nعضو درنظر بگیر
(3آرایه Aرا با nعضو بخوان
(4تمام عناصر آرایه swرا برابر 0قرار بده
mod ← 0 (5و modNo ← 0
i ← 1 (6
(7تازمانیکه ) ( i ≤ nدستورات 8تا 9را تکرار کن
(8اگر ) ( sw(i) = 0آنگاه
sw(i) ← 1 .a
j ← i + 1 .bو sum ← 1
.cتازمانیکه ) ( j ≤ nدستورات dتا eرا تکرار کن
.dاگر ) ) ( A(i) = A(jآنگاه
sum ← sum + 1و sw(j) ← 1
j ← j + 1 .e
.fاگر ) ( sum > modNoآنگاه
) mod ← A(iو modNo ← sum
i ← i + 1 (9
mod (10و modNoرا چاپ کن
(11توقف کن
4-1آرایههای یک بعدی
الگوریتمی بنویسید که برای تعداد دانشجو ،کد رشته (از 1تا )15را خوانده
و سپس تعداد دانشجویان هررشته را به تفکیک چاپ کند.
(1آرایه countرا با 15عضو درنظر بگیر
(2تمام عناصر آرایه countرا برابر 0قرار بده
n (3را بخوان
i ← 1 (4
(5تازمانیکه ) ( i ≤ nدستورات 6تا 8را تکرار کن
code (6را بخوان
count(code) ← count(code) + 1 (7
i ← i + 1 (8
(9آرایه countرا با 15عضو چاپ کن
(10توقف کن
4-2آرایههای چندبعدی
مسائلی که تاکنون حل شدند نیاز به آرایههای یک بعدی داشتند .هر عنصر
از این آرایهها تنها با یک اندیس مشخص میگردد.
اما گاهی در مسائل پیچیده تر نیاز به آرایههایی است که هر عضو آنها نیاز
به بیش از یک اندیس دارد ،که به آنها آرایههای چند بعدی گفته میشود.
چنانچه هر عنصر آرایه به 2اندیس نیاز داشته باشد ،به آن آرایه دو بعدی
میگوییم.
برای تعریف یک آرایه دوبعدی باید تعداد سطرها و ستونهای آن را مشخص
کنیم .معموال یک آرایه دو بعدی بصورت m × nاعالن میگردد که mتعداد
سطرها و nتعداد ستونها است.
4-2آرایههای چندبعدی
بعنوان مثال چنانچه آرایه Aرا بعنوان یک آرایه دوبعدی به ابعاد 5 × 8تعریف
کنیم ،آنگاه داریم :
ستون
8
7
6
5
4
3
2
1
1
)A(2,7
2
3
4
5
)A(3,4
سطر
4-2آرایههای چندبعدی
برای آرایههای سه بعدی نیز مفاهیم مشابهی قابل طرح است .در این آرایهها هر
عنصر نیاز به 3اندیس دارد و برای تعریف آنها را بصورت p × m × nاعالن میکنیم
که pعمق m ،تعداد سطرها و nتعداد ستونها است.
بعنوان مثال چنانچه آرایه Bبعنوان یک آرایه سه بعدی به ابعاد 3 × 4 × 6تعریف
شود ،خواهیم داشت :
)B(2,1,4
عمق
)B(1,2,6
سطر
ستون
4-2آرایههای چندبعدی
(1
(2
(3
(4
(5
(6
(7
(8
(9
(10
(11
(12
(13
(14
(15
(16
(17
(18
(19
الگوریتمی بنویسید که برای تعدادی دانشجو ،شماره دانشجویی و کد رشته (از 1تا )15را خوانده و سپس
دانشجویان هررشته را به تفکیک چاپ کند.
nرا بخوان
آرایه دوبعدی studentرا به ابعاد 15 × nدر نظر بگیر
آرایه countرا با 15عنصر در نظر بگیر و تمام عناصر آن را برابر 0قرار بده
i←1
تازمانیکه ) ( i ≤ nدستورات 6تا 9را تکرار کن
idو codeرا بخوان
count(code) ← count(code) + 1
student( code , count(code) ) ← id
i←i+1
چاپ کن "لیست دانشجویان"
i←1
تازمانیکه ) ( i ≤ 15دستورات 13تا 18را تکرار کن
چاپ کن "رشته "i ،
j←1
تازمانیکه ) ) ( j ≤ count(iدستورات 16تا 17را تکرار کن
) student( i , jرا چاپ کن
j←j+1
i←i+1
توقف کن
4-2آرایههای چندبعدی
(1
(2
(3
(4
(5
(6
(7
(8
(9
(10
(11
(12
(13
(14
(15
(16
(17
(18
(19
(20
(21
(22
(23
الگوریتمی بنویسید که برای تعدادی دانشجو ،شماره دانشجویی و کد محل تحصیل (از 1تا )10و کد رشته (از 1تا )15را
خوانده و سپس دانشجویان هر رشته را به تفکیک چاپ کند.
nرا بخوان
آرایه سه بعدی studentرا به ابعاد 10 × 15 × nدر نظر بگیر
آرایه countرا با 10 × 15عنصر در نظر بگیر و تمام عناصر آن را برابر 0قرار بده
i←1
تازمانیکه ) ( i ≤ nدستورات 6تا 9را تکرار کن
idو cityCodeو studyCodeرا بخوان
count(cityCode,studyCode) ← count(cityCode,studyCode) + 1
student( cityCode , studyCode , count(cityCode , studyCode) ) ← id
i←i+1
چاپ کن "لیست دانشجویان"
i←1
تازمانیکه ) ( i ≤ 10دستورات 13تا 22را تکرار کن
چاپ کن "محل "i ،
j←1
تازمانیکه ) ( j ≤ 15دستورات 16تا 21را تکرار کن
چاپ کن "کد رشته "j ،
k←1
تازمانیکه ) ) ( k ≤ count(i,jدستورات 19تا 20را چاپ کن
) student( i , j , kرا چاپ کن
k←k+1
j←j+1
i←i+1
توقف کن
4-2آرایههای چندبعدی
50موضوع مختلف را به رای گذاشته ایم و هرکس میتواند آرای خود را بشرح زیر اعالم
کند:
-1موافق -2مخالف -3ممتنع -4بی اطالع
الگوریتمی بنویسید که تعداد افراد رای دهنده را دریافت و سپس پس از دریافت آرای
هریک از آنان برای هر 50موضوع ،تعداد آرای مختلف هر موضوع را به تفکیک چاپ کند.
nرا بخوان
i←1
تازمانیکه ) ( i ≤ nدستورات را 5تا 11تکرار کن
j←1
تازمانیکه ) ( j ≤ 50دستورات 7تا 10را تکرار کن
چاپ کن "موضوع " j ،
voteرا بخوان
count(j , vote) ← count(j , vote) + 1
j←j+1
i←i+1
آرایه countرا به ابعاد 50 × 4چاپ کن (نیاز به حلقه دارد)
توقف کن
4-2آرایههای چندبعدی
(1
(2
(3
(4
(5
(6
(7
(8
(9
(10
(11
(12
(13
(14
(15
(16
(17
(18
(19
(20
الگوریتمی بنویسید که دو ماتریس را از کاربر دریافت و حاصلضرب آن دو را محاسبه و چاپ کند.
mو nرا بخوان (ابعاد ماتریس اول )m × n
pو qرا بخوان (ابعاد ماتریس دوم )p × q
اگر ) ( n ≠ pآنگاه
چاپ کن "ماتریسها قابل ضرب نیستند" و توقف کن
آرایه Aرا به ابعاد m × nو آرایه Bرا به ابعاد p × qدرنظر بگیر
آرایه Cرا به ابعاد m × qدرنظر بگیر
آرایه Aرا به ابعاد m × nبخوان
آرایه Bرا به ابعاد p × qبخوان
i←1
تازمانیکه ) ( i ≤ mدستورات 10تا 18را تکرار کن
j←1
تازمانیکه ) ( j ≤ qدستورات 12تا 17را تکرار کن
sum ← 0و k ← 1
تازمانیکه ) (k ≤ nدستورات 14تا 15را تکرار کن
)sum ← sum + A(i,k) × B(k,j
k←k+1
C(i,j) ← sum
j←j+1
i←i+1
آرایه Cرا به ابعاد m × qچاپ کن
توقف کن
4-2آرایههای چندبعدی
(1
(2
(3
(4
(5
(6
(7
(8
(9
(10
(11
(12
(13
(14
(15
(16
(17
(18
(19
(20
(21
(22
(23
دریک آزمون دانشجویان باید به 20سوال 4گزینهای جواب بدهند .الگوریتمی بنویسید که ابتدا جواب درست سواالت را دریافت و سپس
برای تعدادی دانشجو برگه پاسخنامه را دریافت و نمره آنها را محاسبه و چاپ کند .پاسخنامهها در یک آرایه دوبعدی 20 × 4است که در
جاهایی که دانشجو عالمت زده است کاراکتر Xقرار گرفته است و سایر مکانها خالی است .هر پاسخ غلط ⅓ نمره منفی دارد.
آرایه correctرا با 20عضو درنظر بگیر
آرایه answersرا به ابعاد 20 × 4در نظر بگیر
i←1
تازمانیکه ) (i ≤ 20دستورات 5تا 7را تکرار کن
چاپ کن "پاسخ درست سوال"i ،
) correct(iرا بخوان
i←i+1
nرا بخوان
i←1
تازمانیکه ) ( i ≤ nدستورات 11تا 22را تکرار کن
آرایه answersرا بخوان (از داخل فایل یا ورودی صفحه کلید)
grade ← 0
j←1
تازمانیکه ) ( j ≤ 20دستورات 15تا 20را تکرار کن
k ← 1و count ← 0
تازمانیکه ) (k ≤ 4دستورات 17تا 18را تکرار کن
اگر ) ’ ( answers(j , k) = ‘Xآنگاه count ← count + 1
k←k+1
اگر ) ( count > 1آنگاه grade ← grade – 1/3
درغیراینصورت اگر ) (count = 1آنگاه
اگر )’grade ← grade + 1 ( answers(j , correct(j) ) = ‘X
درغیراینصورت grade ← grade – 1/3
j←j+1
چاپ کن "نمره این دانشجو برابر است با "grade ،
i←i+1
توقف کن
مبانی کامپیوتر و برنامه سازی
فصلپنجم :زیرالگوریتمها
مدرس :رضارمضانی
5زیرالگوریتمها
در الگوریتمهای نوشته شده تا کنون ،بعضی اعمال مانند خواندن یک آرایه دوبعدی و یا
چاپ آن مرتبا مورد استفاده قرار میگرفتند.
این قبیل الگوریتمها را میتوان یکبار نوشت و چندین بار مورد استفاده قرار داد ،که به آنها
زیرالگوریتم گفته میشود.
درحقیقت زیرالگوریتم یک قطعه الگوریتم کمکی است که دادههایی را بعنوان ورودی از
الگوریتم اصلی دریافت و پس از انجام پردازش برروی آنها ،داده یا دادههایی را بعنوان
خروجی باز میگرداند .هر زیرالگوریتم دارای یک نام است که الگوریتم اصلی میتواند آن
را توسط نامش فراخوانی نماید.
زیرالگوریتم دارای مزایای متعدی است که اهم آنها عبارتند از :
جلوگیری از تکرار الگوریتمهایی که مرتبا مورد استفاده قرار میگیرند.
ساده شدن عیب یابی و اشکالزدایی الگوریتم
باال رفتن خوانایی برنامه
امکان تقسیم کار به چند بخش و واگذاری آن به افراد مختلف
هر الگوریتم میتواند برای تبادل اطالعات با زیرالگوریتم ،تعدادی از متغیرهای خود را به
زیرالگوریتم ارسال کند و یا دادههایی را از آن دریافت کند .متغیرهایی را که برای تبادل
اطالعات بین الگوریتم و زیرالگوریتم بکار میروند را پارامتر میگوییم .بنابراین پارامترها
میتوانند ورودی (به زیرالگوریتم) یا خروجی (از زیرالگوریتم) باشند .هر زیرالگوریتم
میتواند هر تعداد پارامتر ورودی یا خروجی داشته باشد و یا میتواند اصال پارامتر
نداشته باشد.
5-1نحوه استفاده از زیرالگوریتم
استفاده از یک زیرالگوریتم دارای دو مرحله است :
تعریف زیرالگوریتم :برای تعریف زیرالگوریتم ابتدا نام آن و سپس در داخل پرانتز
لیست پارامترهای آن را مشخص مینماییم .پس از آن نیز دستورات تشکیل
دهنده زیرالگوریتم را مینویسیم .الزم به ذکر است که زیرالگوریتم حتما باید
دارای دستور برگشت باشد .این دستور باعث میشود که کنترل اجرا به
الگوریتم اصلی بازگردد.
فراخوانی زبرالگوریتم :پس از تعریف یک زیرالگوریتم ،میتوان آن را از هر
نقطهای از الگوریتم اصلی فراخوانی کرد .فراخوانی شامل نام زیرالگوریتم و
سپس در داخل پرانتز لیست پارامترهای ارسالی و دریافتی به زیرالگوریتم
میباشد.
5-2انواع زیرالگوریتم
زیرالگوریتمها به سه دسته اصلی تقسیم میگردند :
زیرالگوریتمهایی که مقدار خروجی ندارند.
زیرالگوریتمهایی که یک مقدار خروجی دارند .معموال در این قبیل
زیرالگوریتمها ،بجای اینکه از یک پارامتر خروجی استفاده شود ،نام خود
زیرالگوریتم نماینده مقدار بازگشتی است .اینکار در ریاضیات معمولی نیز
رایج است .مثال وقتی از عبارت ) a = 2 × sin(30استفاده میکنیم ،خود نام
تابع یعنی ) sin(30جایگزین مقدار حاصل یعنی 2/1شده است.
زیر الگوریتمهایی که چندین مقدار باز میگردانند .این زیر الگوریتمها از
پارامترهای خروجی برای بازگرداندن مقادیر استفاده میکنند .البته برای
بازگرداندن یک مقدار نیز میتوان از پارامترها استفاده کرد ولی اینکار متداول
نیست.
5-3چند نمونه از زیرالگوریتمها
الگوریتمی بنویسید که مقدار ترکیب nبه kرا محاسبه کند.
n
!n
!) k k!(n k
زیرالگوریتم )Factorial(x
i ← 1 (1وF ← 1
(2تازمانیکه( ) i ≤ xدستورات 3تا 4راتکرارکن
F←F×i
(3
i ← i + 1 (4
Factorial ← F (5
(6برگشت
5-3چند نمونه از زیرالگوریتمها
الگوریتم اصلی
(1
(2
(3
(4
(5
(6
nو kرا بخوان
اگر ) ( n < kچاپ کن "مسئله جواب ندارد" و توقف کن
اگر ) ( n=k or k=0چاپ کن "جواب = "1و توقف کن
) )result ← Factorial(n) / ( Factorial(k) × Factorial(n-k
resultرا چاپ کن
توقف کن
5-3چند نمونه از زیرالگوریتمها
روش دیگر حل این مسئله با استفاده از یک پارامتر خروجی
زیرالگوریتم )Factorial(x,f
i ← 1 (1و f ← 1
(2تازمانیکه ) ( i ≤ xدستورات 3تا 4را تکرار کن
f←f×i
(3
i ← i + 1 (4
(5برگشت
5-3چند نمونه از زیرالگوریتمها
الگوریتم اصلی
(1
(2
(3
(4
(5
(6
(7
(8
(9
nو kرا بخوان
اگر ) ( n < kچاپ کن "مسئله جواب ندارد" و توقف کن
اگر ) ( n=k or k=1چاپ کن "جواب = "1و توقف کن
)Factorial(n,fn
)Factorial(k,fk
)Factorial(n-k,fnk
)result ← fn / (fk × fnk
resultرا چاپ کن
توقف کن
5-3چند نمونه از زیرالگوریتمها
الگوریتمی بنویسید که تعدادی عدد را دریافت و هربار با فراخوانی یک
زیرالگوریتم عدد را در مکان مناسب یک آرایه درج کند بطوریکه در انتها
آرایه مرتب باشد.
زیرالگوریتم )insert(A,t,x
i ← t (1
(2تازمانیکه ) ) ( i ≥ 1 and x < A(iدستورات 3تا 4را تکرار کن
A(i + 1) ← A(i) (3
i ← i – 1 (4
A(i + 1) ← x (5
(6برگشت
5-3چند نمونه از زیرالگوریتمها
الگوریتم اصلی
(1
(2
(3
(4
(5
(6
(7
(8
(9
nرابخوان
آرایه Listرا با nعضو درنظر بگیر
i←1
تازمانیکه ) ( i ≤ nدستورات 5تا 7را تکرار کن
adadرا بخوان
)insert(List, i-1, adad
i←i+1
آرایه Listرا با nعضو چاپ کن
توقف کن
5-3چند نمونه از زیرالگوریتمها
الگوریتمی بنویسید که لیستی از اعداد را دریافت و در یک آرایه قرار دهد.
سپس با دریافت هرعدد از کاربر تعیین کند که آیا این عدد در لیست
موجود بوده یا خیر و درصورت وجود مکان آن را چاپ کند .در پایان اعداد،
عدد صفر قرار گرفته است.
زیرالگوریتم )Search(A, t, x
i ← 1 (1
(2تازمانیکه ) ( i ≤ tدستورات 3تا 4را تکرار کن
(3اگر ) ( A(i) = xآنگاه Search ← iو برگشت
i ← i + 1 (4
Search ← 0 (5
(6برگشت
5-3چند نمونه از زیرالگوریتمها
الگوریتم اصلی
(1
(2
(3
(4
(5
(6
(7
(8
(9
nرا بخوان
آرایه Listرا با nعضو درنظر بگیر
آرایه Listرا با nعضو بخوان
adadرا بخوان
تازمانیکه ) ( adad ≠ 0دستورات 6تا 8را تکرار کن
)place = Search(List, n , adad
اگر ) ( place = 0آنگاه چاپ کن "عدد در لیست موجود نیست"
درغیراینصورت چاپ کن "مکان عدد = "place ،
adadرا بخوان
توقف کن
مبانی کامپیوتر و برنامه سازی
فصلششم :مقدمهای برزبانC
مدرس :رضارمضانی
6مقدمهای بر زبان C
زبانهای برنامه سازی به سه دسته کلی تقسیم میگردند :
زبان ماشین (سطح پایین) :این زبان مستقیما با صفر و یک نوشته میشود و بدون هیچ
واسطهای برروی کامپیوتر قابل اجرا است .هر برنامهای که به زبان ماشین نوشته شود،
فقط برروی همان ماشین خاص کار میکند ،بهمین دلیل برنامههای نوشته شده به زبان
ماشین را غیر قابل حمل مینامند .از طرف دیگر یادگیری این زبان بسیار مشکل بوده و
برنامهنویسی با آن نیز بسیار سخت است و همچنین احتمال بروز خطا نیز در آن زیاد
است.
زبان اسمبلی :این زبان شکل ساده تر زبان ماشین است ،بدین صورت که برای هر
دستورالعمل زبان ماشین ،یک اسم نمادین انتخاب شده است (مانند دستور ADDبجای
کد دودویی دستورالعمل جمع) که بخاطر سپردن و برنامهنویسی با آنها برای انسانها
ساده تر است .اما این برنامهها برای ماشین قابل فهم نیست و باید قبل از اجرا شدن
توسط برنامه مترجمی بنام اسمبلر به زبان ماشین تبدیل شود.
زبانهای سطح باال :دستورالعملهای این زبانها بسیار نزدیک به زبان انسانها (بطور
مشخص زبان انگلیسی) میباشد و بهمین دلیل برنامهنویسی به آنها بسیار ساده تر
بوده و میتوان الگوریتمها را به راحتی به این زبانها تبدیل کرد.
بیسیک ) :(Basicبرای کاربردهای آموزشی
فرترن ) : (Fortranبرای کاربردهای علمی و مهندسی
پاسکال ) :(Pascalبرای کاربردهای آموزشی و علمی
زبان C
6-1تاریخچه زبان C
در سال 1967مارتین ریچاردز زبان BCPLرا برای نوشتن نرم افزارهای سیستم عامل و کامپایلر در
دانشگاه کمبریج ابداع کرد.
در سال 1970کن تامپسون زبان Bرا بر مبنای ویژگیهای زبان BCPLنوشت و از آن برای ایجاد اولین
نسخههای سیستم عامل Unixدر آزمایشگاههای بل استفاده کرد.
زبان Cدر سال 1972توسط دنیس ریچی از روی زبان Bو BCPLدر آزمایشگاه بل ساخته شد و
ویژگیهای جدیدی همچون نظارت بر نوع دادهها نیز به آن اضافه شد .ریچی از این زبان برای ایجاد
سیستم عامل Unixاستفاده کرد اما بعدها اکثر سیستم عاملهای دیگر نیز با همین زبان نوشته
شدند .این زبان با سرعت بسیاری گسترش یافت و چاپ کتاب ” “The C Programming Languageدر
سال 1978توسط کرنیگان و ریچی باعث رشد روزافزون این زبان در جهان شد.
در سال 1983انستیتوی ملی استاندارد آمریکا ) (ANSIکمیتهای موسوم به X3J11را را مامور کرد تا
یک تعریف فاقد ابهام و مستقل از ماشین را از این زبان تدوین نماید.در سال 1989این استاندارد
تحت عنوان ANSI Cبه تصویب رسید و سپس در سال ،1990سازمان استانداردهای بین المللی
) (ISOنیز این استاندارد را پذیرفت و مستندات مشترک آنها تحت عنوان ANSI/ISO Cمنتشر گردید.
در سالهای بعد و با ظهور روشهای برنامهنویسی شی گرا نسخه جدیدی از زبان Cبنام C++
توسط بیارنه استراوستروپ در اوایل 1980در آزمایشگاه بل توسعه یافت .در C++عالوه بر امکانات
جدیدی که به زبان Cاضافه شده است ،خاصیت شی گرایی را نیز به آن اضافه کرده است.
شرکت سان مایکروسیستمز در سال 1995میالدی زبان Javaرا برمبنای Cو C++ایجاد کرد که هم
اکنون از آن در سطح وسیعی استفاده میشود و برنامههای نوشته شده به آن برروی هر کامپیوتری
که از Javaپشتیبانی کند(تقریبا تمام سیستمهای شناخته شده) قابل اجرا میباشد .شرکت
مایکروسافت در رقابت با شرکت سان ،در سال 2002زبان جدیدی بنام ( C#سی شارپ) را ارائه داد
که رقیبی برای Javaبشمار میرود.
6-2برنامهنویسی ساختیافته
در دهه 1960میالدی توسعه نرم افزار دچار مشکالت عدیدهای شد .در آن زمان سبک خاصی برای
برنامهنویسی وجود نداشت و برنامهها بدون هیچگونه ساختار خاصی نوشته میشدند.
فعالیتهای پژوهشی در این دهه باعث بوجود آمدن سبک جدیدی از برنامهنویسی بنام روش ساختیافته
گردید؛ روش منظمی که باعث ایجاد برنامههایی کامال واضح و خوانا گردید که اشکال زدایی و خطایابی آنها
نیز بسیار ساده تر بود.
اصلی ترین نکته در این روش عدم استفاده از دستور پرش ) (gotoاست .تحقیقات بوهم و ژاکوپینی نشان داد
که میتوان هر برنامهای را بدون دستور پرش و فقط با استفاده از 3ساختار کنترلی ترتیب ،انتخاب و تکرار
نوشت.
ساختار ترتیب ،همان اجرای دستورات بصورت متوالی (یکی پس از دیگری) است.
ساختار انتخاب به برنامهنویس اجازه میدهد که براساس درستی یا نادرستی یک شرط ،تصمیم بگیرد کدام مجموعه از
دستورات اجرا شود.
ساختار تکرار نیز به برنامهنویسان اجازه میدهد مجموعه خاصی از دستورات را تا زمانیکه شرط خاصی برقرار باشد ،تکرار
نماید.
هر برنامه ساختیافته از تعدادی بلوک تشکیل میشود که این بلوکها به ترتیب اجرا میشوند تا برنامه خاتمه
یابد(ساختار ترتیب) .هر بلوک میتواند یک دستورساده مانند خواندن ،نوشتن یا تخصیص مقدار به یک متغیر
باشد و یا اینکه شامل دستوراتی باشد که یکی از 3ساختار فوق را پیاده سازی کنند .نکته مهم اینجاست که
درمورد دستورات داخل هر بلوک نیز همین قوانین برقرار است و این دستورات میتوانند از تعدادی بلوک به
شرح فوق ایجاد شوند و تشکیل ساختارهایی مانند حلقههای تودرتو را دهند .نکته مهم اینجاست که طبق
قوانین فوق یک حلقه تکرار یا بطور کامل داخل حلقه تکرار دیگر است و یا بطور کامل خارج آن قرار میگیرد و
هیچگاه حلقههای روی هم افتاده نخواهیم داشت.
از جمله اولین تالشها در زمینه ساخت زبانهای برنامهنویسی ساختیافته ،زبان پاسکال بود که توسط پروفسور
نیکالس ویرث در سال 1971برای آموزش برنامهنویسی ساختیافته در محیطهای آموزشی ساخته شد و
بسرعت در دانشگاهها رواج یافت.
کمی بعد زبان Cارائه گردید که عالوه بر دارا بودن ویژگیهای برنامهنویسی ساختیافته بدلیل سرعت و کارایی
باال مقبولیتی همه گیر یافت و هم اکنون سالهاست که بعنوان بزرگترین زبان برنامهنویسی دنیا شناخته شده
است.
6-3مراحل اجرای یک برنامه C
ورودیها
اجرای برنامه
خروجیها
فایل قابل اجرا
A.exe
پیوند دهنده
توابع آماده
برنامه مقصد
A.obj
کامپایلر
برنامه مبدا
A.C
ویرایشگر
6-3مراحل اجرای یک برنامه C
مسلما طی این مراحل برای برنامهنویسان بسیار زمانبر است.
راه حل این مشکل استفاده از محیط مجتمع توسعه نرم افزار یا IDEاست.
)IDE (Integrated Development Environment
یک IDEشامل مواردی همچون
ویرایشگر متن با امکانات ویژه برای زبان
امکان کامپایل و پیوند برنامه
امکان اشکال زدایی ) (Debugبرنامه
چند محیط معروف برنامهنویسی عبارتند از :
Borland C++ 3.1برای محیط DOS
Borland C++از نسخه 4به باال برای Windows
Microsoft Visual C++برای محیط Windows
Borland C++ Builderبرای محیط Windows
6-3مراحل اجرای یک برنامه C
Borland C++ 3.1
6-4خطاهای برنامهنویسی
بنظر میرسد خطاها جزء جداناپذیر برنامهها هستند .بندرت میتوان برنامهای نوشت که در
همان بار اول بدرستی و بدون هیچگونه خطایی اجرا شود.
بطور کلی خطاها به دو دسته تقسیم میشوند:
خطاهای نحوی (خطاهای زمان کامپایل) :این خطاها در اثر رعایت نکردن قواعد دستورات زبان Cو یا
تایپ اشتباه یک دستور بوجود میآیند و در همان ابتدا توسط کامپایلر به برنامهنویس اعالم میگردد.
معموال این قبیل خطاها خطر کمتری را در بردارند.
خطاهای منطقی (خطاهای زمان اجرا) :این دسته خطاها در اثر اشتباه برنامهنویس در طراحی الگوریتم
درست برای برنامه و یا گاهی در اثر درنظر نگرفتن بعضی شرایط خاص در برنامه ایجاد میشوند.
متاسفانه این دسته خطاها در زمان کامپایل اعالم نمیشوند و در زمان اجرای برنامه خود را نشان
میدهند .ممکن است یک برنامهنویس خطای منطقی برنامه خود را تشخیص ندهد و این خطا پس از
مدتها و تحت یک شرایط خاص توسط کاربر برنامه کشف شود .بهمین دلیل این دسته از خطاها
خطرناکتر هستند .خود این خطاها به دو دسته تقسیم میگردند:
خطاهای مهلک :در این دسته خطاها کامپیوتر بالفاصله اجرای برنامه را متوقف کرده و خطا را به
کاربر گزارش میکند .مثال معروف این خطاها ،خطای تقسیم بر صفر میباشد.
خطاهای غیرمهلک :در این دسته خطا ،اجرای برنامه ادامه مییابد ولی برنامه نتایج اشتباه تولید
مینماید .بعنوان مثال ممکن است دراثر وجود یک خطای منطقی در یک برنامه حقوق و دستمزد،
حقوق کارمندان اشتباه محاسبه شود و تا مدتها نیز کسی متوجه این خطا نشود!
بسیار مهم است که در ابتدا سعی کنید برنامهای بنویسید که حداقل خطاها را داشته باشد،
در گام دوم با آزمایش دقیق برنامه خود هرگونه خطای احتمالی را پیدا کنید و در گام سوم
بتوانید دلیل بروز خطا را پیدا کرده و آنرا رفع نمایید .هر سه عمل فوق کار سختی بوده و نیاز به
تجربه و مهارت دارد .در اصطالح برنامهنویسی به هر گونه خطا bug ،و به رفع خطا debugگفته
میشود.
6-5یک برنامه نمونه C
// This Program Computes the Area of a Circle
توضیحات
دستور پیش پردازنده
تابع اصلی
اجرای برنامه از این تابع آغاز میگردد
برای افزودن توابع کتابخانهای به برنامه
>#include <stdio.h
{ )(void main
اعالن متغیرهای برنامه
; int radius
عالمت شروع بلوک
اطالعات
تابع چاپ
; float area
beginدر پاسکال
معادل دستور
; )" printf("please enter radius :
اطالعات
تابع دریافت
انتهای بلوک
عالمت
; )scanf("%d",&radius
معادل دستور endدر پاسکال
; area = 2 * 3.14 * radius
; )printf("Area is %f",area
عالمت پایان دهنده دستورات
}
نتیجه اجرای برنامه
please enter radius : 10
Area is 62.8
دندانه گذاری
مبانی کامپیوتر و برنامه سازی
فصلهفتم :مفاهیماولیهزبانC
مدرس :رضارمضانی
7-1شناسهها در C
شناسه ) (identifierنامی است که به یک قسمت از برنامه مانند متغیر ،تابع ،ثابت و یا
...داده میشود.
در زبان Cبرای انتخاب شناسهها فقط میتوان از عالئم زیر استفاده کرد:
حروف انگلیسی کوچک و بزرگ )(A…Z a…z
ارقام )(0…9
عالمت خط پایین یا _
البته یک شناسه نمیتواند با یک رقم شروع شود.
چند شناسه مجاز name2 ، average ، sum :و student_average
چند شناسه غیرمجاز 2nameو یا student average
در برنامهنویسی امروزی پیشنهاد میشود بجای شناسههایی همانند student_average
از studentAverageاستفاده گردد.
نکته مهم دیگری که باید به آن اشاره کرد آنستکه زبان Cبرخالف بسیاری از زبانهای
دیگر به کوچک و بزرگی حروف حساس است ) .(case sensitiveدر نتیجه شناسههای زیر
با یکدیگر متفاوتند :
Sum ≠ sum ≠ SUM
آخرین نکته اینستکه در هنگام انتخاب شناسه نمیتوانید از کلمات کلیدی که برای
منظورهای خاص در زبان Cرزرو شدهاند استفاده کنید.
C شناسهها در7-1
: C کلمات کلیدی در زبان
auto
break
case
char
const
continue
default
do
double
else
enum
extern
float
for
goto
if
int
long
register
return
short
signed
sizeof
static
struct
switch
typedef
union
unsigned
void
volatile
while
7-2انواع دادهها در C
محدوده
اندازه (بیت)
توضیح
نوع داده
-128 to +127
8
کاراکتر
char
-32768 to +32767
16
عدد صحیح
int
3.4e-38 to 3.4e+38
32
عدد اعشاری
float
1.7e-308 to 1.7e+308
64
عدد اعشاری با
دقت مضاعف
double
7-2انواع دادهها در C
البته چند نکته مهم درمورد این جدول قابل ذکر است :
اندازه intدر محیطهای 16بیتی مانند DOSبرابر 16بیت است .اما در
محیطهای 32بیتی همانند Windowsاندازه آن 32بیت میباشد که در
اینصورت محدودهای برابر -2,147,483,648تا +2,147,483,647را پوشش
میدهد.
در بعضی از کامپایلرهای ،Cنوع داده boolنیز وجود دارد که میتواند
یکی از مقادیر (trueدرست) یا ( falseغلط) را نشان دهد .اما در
نسخههای اولیه Cاز همان نوع داده صحیح یا intبرای اینکار استفاده
میشد .بدین صورت که 0نشاندهنده falseو هر عدد غیر صفر (معموال
)1نشاندهنده trueاست.
از آنجا که برای ذخیره سازی کاراکترها کد اسکی آنها ذخیره میگردد،
از یک متغیر کاراکتری یا charمیتوان بعنوان یک عدد صحیح کوچک نیز
استفاده کرد و اعمال ریاضی برروی آنها نیز مجاز است.
7-2انواع دادهها در C
عالوه براین چندین اصالح کننده نیز وجود دارد که به ما اجازه میدهد نوع داده مورد
نظر را با دقت بیشتری برای نیازهای مختلف استفاده نماییم.
این اصالح کنندهها عبارتند از short, long, signed, unsigned :
تمام این اصالح کنندهها میتوانند به نوع داده intاعمال شوند و اثر آنها بستگی به
محیط دارد.
در یک محیط 16بیتیshort int ،بازهم برابر 16بیت است ولی long intبرابر32بیت
میباشد.
unsigned intباعث میشود که یک عدد 16بیتی بدون عالمت داشته باشیم که بازه بین
0تا 65535را پوشش میدهد signed int .نیز همانند intمعمولی بوده و تفاوتی ندارد.
ترکیب این اصالح کنندهها نیز ممکن است .مثال unsigned long intیک عدد 32بیتی بدون
عالمت است که بازه 0تا 4,294,967,295را پوشش میدهد.
برروی نوع داده charفقط اصالح کنندههای signedو unsignedرا میتوان اعمال
کرد .معموال از اصالح کننده unsignedوقتی استفاده میشود که قصد داشته
باشیم از charبعنوان یک عدد صحیح مثبت بین 0تا 255استفاده کنیم.
برروی نوع داده doubleتنها اصالح کننده longقابل اعمال است و دراینصورت عدد
اعشاری با 80بیت را خواهیم داشت که قادر است هر عددی در بازه 3.4e-4932تا
1.1e+4932را در خود نگاه دارد.
7-3تعریف متغیرها
برای تعریف متغیرها به شکل زیر عمل میکنیم:
;><type> <variable-list
که typeیکی از نوع دادههای گفته شده و variable-listلیستی از متغیرها است که
با کاما از یکدیگر جدا شده اند .بعنوان مثال :
;int sum
;float average
; long int a, b, c
; unsigned long int i, j, k
عالوه براین میتوان در هنگام تعریف متغیر به آن مقدار اولیه نیز داد .مثال :
; int d = 0
نکته مهم آنکه زبان Cبه متغیرها مقدار اولیه نمی دهد (حتی )0و برنامهنویس خود
باید اینکار را صریحا انجام دهد ،درغیر اینصورت مقدار اولیه متغیر ،نامعین خواهد بود.
تعریف متغیرها طبق اصول زبان Cمیتواند درهرجایی از برنامه صورت پذیرد ،و
متغیرهای تعریف شده از همان خط به بعد قابل استفاده خواهد بود .اما معموال
توصیه میگردد که تعریف متغیرها در همان خط ابتدایی تابع و بالفاصله پس از {
صورت پذیرد.
7-4ثوابت
ثابتها مقادیر ثابتی هستند که مقدار آنها در حین اجرای برنامه تغییر نمییابد.
ثابتها میتوانند از هریک از نوع دادههای اصلی باشند.
ثوابت عددی صحیح :
برای نمایش این دسته از ثوابت ،از دنبالهای از ارقام بعالوه عالمت +یا -استفاده
میکنیم .بعنوان مثال -45و یا 3489ثوابت صحیح هستند.
در حالت عادی Cهر عدد را در کوچکترین نوع دادهای که میتواند قرار میدهد .مثال
عدد 85در یک intقرار میگیرد ،اما عدد 145398در یک long intقرار خواهد گرفت.
اگر قصد دارید یک عدد کوچک بعنوان longمحسوب گردد ،میتوانید از پسوند Lدر
انتهای آن استفاده میکنیم .مثال 245Lیک عدد longمحسوب میشود.
;long int a = 20L
پسوند Uدر انتهای عدد نیز نشانه بدون عالمت بودن آن است.
نکته دیگر آنکه Cبه شما اجازه میدهد درصورت لزوم ثوابت صحیح خود را در مبنای
8یا 16نیز که از مبناهای متداول در برنامهنویسی هستند ،بنویسید.
برای نوشتن عدد در مبنای 8باید آن را با 0آغاز کنید ،مثال 0342یک عدد در منای 8
محسوب میگردد.
برای نوشتن یک عدد در مبنای 16باید آن را با 0xآغاز نمایید ،مانند .0x27A4
7-4ثوابت
ثوابت عددی اعشاری
برای نمایش اعداد اعشاری ،باید از نقطه اعشار استفاده کنیم ،مانند 237.45
اما نکته جالب آنستکه میتوانید از نماد علمی نیز برای نمایش اعداد اعشاری
استفاده کنید .برای اینکار کافی است از حرف eبرای نمایش قسمت توان
استفاده نمایید .بعنوان مثال :
-23.47 × 10 5 = -23.47e5
42.389 × 10 -3 = 42.389e-3
دقت کنید که قسمت توان ،حتما یک عدد صحیح است.
نکته جالب اینجاست که برخالف مورد قبل ،کامپایلر بطور پیش فرض دادههای
اعشاری را از نوع doubleفرض میکند.چنانچه دوست دارید ثابت شما ازنوع
floatدرنظر گرفته شود ،در انتهای آن Fقرار دهید .ضمنا پسوند Lنیز داده
اعشاری را از نوع long doubleدرنظر میگیرد.
7-4ثوابت
ثوابت کاراکتری :
برای نشان دادن ثوابت کاراکتری ،آنها را در داخل ' قرار میدهیم .بعنوان مثال
''Aیک ثابت کاراکتری است.
; 'char ch = 'S
دقت کنید که همانطور که قبال گفته شد ،کد اسکی کاراکترها در متغیر ذخیره
میگردد ،بنابراین میتوان از آن بعنوان یک عدد صحیح نیز استفاده کرد .نکته
مهم دیگر آنستکه به تفاوت عدد 5و کاراکتر ' '5دقت داشته باشید .در حقیقت
' '5برابر است با عدد 53که همان کد اسکی آن است.
روش فوق ،برای نمایش کاراکترهای قابل چاپ مناسب است ،اما بعضی
کاراکترها مانند enterقابل نمایش نبوده و برای آنها شکل خاصی وجود ندارد.
در چنین مواردی ،زبان Cاز ترکیب عالمت \ به همراه یک کاراکتر دیگر ،برای
نمایش این قبیل کاراکترها استفاده مینماید .بعنوان مثال ،کاراکتر ' '\nنشان
دهنده خط جدید یا همان enterمیباشد.
جدول صفحه بعد این قبیل کاراکترها و معادل آنها را نشان میدهد.
7-4ثوابت
نحوه نمایش در C
کد اسکی
صدای بوق کامپیوتر
\a
7
حرکت به عقب backspace
\b
8
شروع صفحه form feed
\f
12
سطر جدید line feed
\n
10
برگشت به ابتدای سطر carriage return
\r
13
فاصله افقی horizontal tab
\t
9
فاصله عمودی vertical tab
\v
11
عالمت سوال
?\
63
عالمت '
'\
39
عالمت "
"\
34
عالمت \
\\
92
عالمت تهی
\0
0
نام کاراکتر
7-4ثوابت
ثوابت رشتهای C :عالوه بر ثوابت فوق ،از یک ثابت دیگر بنام رشته نیز
حمایت میکند .رشته ،دنبالهای از کاراکترها است که در داخل " قرار
میگیرند .بعنوان مثال " "this is a testیک رشته است .دقت کنید که ''a
یک کاراکتر است ،اما " "aیک رشته است که فقط شامل یک کاراکتر
میباشد.
7-5عملگرها
عملگر ،نمادی است که به کامپایلر میگوید تا عملیات محاسباتی یا
منطقی خاصی را برروی یک یا چند عملوند ،انجام دهد.
به عملگرهایی که فقط یک عملوند دارند ،عملگر یکانی میگوییم و
همواره عملگر در سمت چپ عملوند قرار میگیرد(مانند عدد .)-125
اما عملگرهایی که برروی دو عملوند اثر میکنند را عملگر دودویی
نامیده و عملگر را بین دو عملوند قرار میدهیم (مانند .)23+86
هر ترکیب درستی از عملگرها و عملوندها را یک عبارت مینامیم.
Cاز نقطه نظر عملگرها یک زبان بسیار قوی است .این عملگرها به چند
دسته اصلی تقسیم میگردند که آنها را به ترتیب بررسی میکنیم.
7-5-1عملگرهای محاسباتی
این عملگرها ،همان اعمال متداول ریاضی هستند که در زبان Cمورد استفاده
قرار میگیرند .این اعمال عبارتند از :
عمل
نوع
عملگر
منفی کردن عملوند سمت راست
یکانی
-
جمع دو عملوند
دودویی
+
تفریق دو عملوند
دودویی
-
ضرب دو عملوند
دودویی
*
تقسیم دو عملوند
دودویی
/
محاسبه باقیمانده تقسیم دو عملوند
دودویی
%
7-5-1عملگرهای محاسباتی
این عملگرهای برروی همه انواع دادههای Cعمل میکنند ،بجز عملگر %که فقط برروی
نوع دادههای صحیح عمل میکند.
اما نکته مهمی که باید به آن اشاره کرد ،نحوه کار عملگر تقسیم برروی نوع دادههای
مختلف است.
درصورتیکه هردو عملوند صحیح باشند ،تقسیم بصورت صحیح بر صحیح انجام خواهد شد.
اگر یکی یا هر دو عملوند اعشاری باشند ،تقسیم بصورت اعشاری انجام خواهد پذیرفت.
فرض کنید تعاریف زیر را داریم :
; int a,b
; float c,d
; a = 10 ; b = 4
;c = 8.2; d = 4.0
اکنون به نتایج عملیات زیر دقت کنید :
a / b => 2
c / d => 2.05
a / d => 2.5
a / 4 => 2
چنانچه به آخرین مورد توجه کنید ،متوجه میشوید که از آنجا که aیک متغیر صحیح است و 4
نیز یک ثابت صحیح در نظر گرفته شده ،درنتیجه تقسیم بصورت صحیح بر صحیح انجام گرفته
است .چنانچه بخواهیم تقسیم بصورت اعشاری صورت پذیرد ،به دو شکل میتوانیم عمل کنیم.
7-5-1عملگرهای محاسباتی
برای اینکه عبارت تقسیم صفحه قبل بصورت اعشاری انجام شود ،به دو روش
میتوان عمل کرد:
این عبارت را بصورت a / 4.0بنویسیم.
از عملگر قالب ریزی استفاده کنیم.
عملگر قالب ریزی میتواند یک نوع را به نوع دیگری تبدیل نماید .شکل کلی آن
به شکل زیر است :
>(<type>) <expression
که typeنوع مورد نظر است که قصد تبدیل عبارت expressionبه آن نوع را داریم.
بعنوان مثال در مورد قبلی میتوان به شکل زیر عمل کرد :
(float) a / 4
در این حالت از کامپایلر خواسته ایم که ابتدا عدد aرا به اعشاری تبدیل کند
(البته بصورت موقت) و سپس آن را بر 4تقسیم نماید ،که مسلما حاصل
اعشاری خواهد بود.
بطورکلی هرگاه ثابتها و متغیرهایی از انواع مختلف در یک عبارت محاسباتی
داشته باشیم ،کامپایلر Cهمه آنها را به یک نوع یکسان که همان بزرگترین
عملوند موجود است تبدیل خواهد کرد .بعنوان مثال به مورد صفحه بعد دقت
کنید :
عملگرهای محاسباتی7-5-1
char ch;
int i;
float f;
double d;
result = (ch / i)
int
+
(f *d)
(f+i)
float
double
double
int
-
double
double
float
7-5-1عملگرهای محاسباتی
اولویت عملگرها :در عبارتی که شامل چندین عملگر است ،کدامیک در
ابتدا اعمال خواهد گردید.
اولویت عملگرهای محاسباتی از باال به پایین بشرح زیر است:
عملگر یکانی –
عملگرهای * و /و %
عملگرهای +و –
چنانچه اولویت دو عملگر یکسان باشد ،این عملگرها از چپ به راست
محاسبه خواهند شد.
چنانچه بخواهیم یک عمل با اولویت پایین زودتر انجام شود ،باید از پرانتز
استفاده کنیم .بنابراین اولویت عملگر پرانتز از همه موارد فوق
بیشتراست .در مورد پرانتزهای متداخل ،ابتدا پرانتز داخلی محاسبه
میشود؛ اما در مورد پرانتزهای هم سطح ،ابتدا پرانتز سمت چپتر
محاسبه میگردد.
7-5-1عملگرهای محاسباتی
چند مثال در مورد اولویت عملگرها
ترتیب اجرای عملگرها
عبارت زبان C
ابتدا عمل * و سپس عمل +
b+c*d
ابتدا عمل +و سپس عمل *
(b + c) * d
ابتدا عمل /سپس عمل * و در انتها عمل +
b+c/d*e
ابتدا عمل * سپس عمل /و در انتها عمل +
)b + c / (d * e
7-5-1عملگرهای محاسباتی
یک مثال کامل در مورد اولویت عملگرها
result = a + b * (f – (g + b) / d) – c * (a – d) / e
6
1
7
2
8
3
4
5
9
7-5-2عملگرهای انتساب
در زبان Cبرای انتساب چندین عملگر وجود دارد .ساده ترین عملگر انتساب ،همان عملگر =
است که در بسیاری از زبانها استفاده میشود .بعنوان مثال :
;a = 5
;b = c + 2 * d
این عملگر باعث میشود که عبارت سمت راست در عبارت سمت چپ قرار گیرد.
توجه کنید که مقدار سمت چپ باید عبارتی باشد که بتوان به آن یک مقدار را نسبت داد
(مانند یک متغیر) که به آن Lvalueگفته میشود ،بنابراین یک ثابت نمیتواند در سمت چپ
قرار گیرد.
نکته دیگر اینکه اولویت عملگر = از عملگرهای ریاضی کمتر است و درنتیجه ابتدا آن عملیات
انجام شده و در پایان حاصل در عبارت سمت چپ ریخته میشود.
الزم به ذکر است که در هنگام انتساب ،درصورت لزوم نوع عبارت سمت راست به نوع عبارت
سمت چپ تبدیل میشود .مثال:
;int a
;a = 2.5 * 5.0
که دراینصورت عدد 12در aذخیره خواهد شد.
شرکت پذیری این عملگر از راست به چپ میباشد ،بدین معنا که چنانچه چندین عملگر
نسبت دهی داشته باشیم ،این عملگرها از راست به چپ محاسبه میشوند .مثال پس از
اجرای دستور زیر ،مقدار هر 3متغیر برابر 10خواهد شد.
;a = b = c = 10
7-5-2عملگرهای انتساب
نکته جالب درمورد زبان Cآنستکه دارای یک سری عملگرهای انتساب
خالصه شده است که باعث میشوند در بعضی موارد بتوانیم عبارات
کوتاهتری را بنویسیم .این عملگرها عبارتند از :
عملگرد
مثال
عبارت انتساب معادل
++
;a ++
;a = a + 1
--
;a --
;a = a – 1
=+
;a += 5
;a = a + 5
=-
;a -= 8
;a= a – 8
=*
;a *= 10
;a= a * 10
=/
;a /= 2
;a = a / 2
=%
;a %= 10
;a = a % 10
7-5-2عملگرهای انتساب
عملگر اول و دوم ،عملگرهای یکانی هستند که بترتیب عملگر افزایش و عملگر کاهش
نامیده میشوند.
نکته جالب آنستکه این دو عملگر به دوصورت مورد استفاده قرار میگیرند.
در حالتی که از دستور ; a ++استفاده شود به آن پس افزایش میگویند و بدین معناست که
ابتدا از مقدار فعلی aدر عبارت موردنظر استفاده کن و سپس آن را افزایش بده.
اما دستور ; ++ aکه به آن پیش افزایش گفته میشود ،ابتدا aرا افزایش داده و سپس از آن در
عبارت استفاده میکند.
به برنامه زیر دقت کنید:
>#include <stdio.h
{ )(void main
; int a ,b
;a=5
;b = a ++
;)printf(“a=%d and b=%d \n”,a,b
;a=5
;b = ++ a
;)printf(“a=%d and b=%d \n”,a,b
}
a=6 b=5
a=6 b=6
7-5-3عملگرهای مقایسه ای
این عملگرها دو عبارت را بایکدیگر مقایسه کرده و نتیجه را باز میگردانند.
نتیجه میتواند درست ) (trueیا غلط ) (falseباشد.
نتیجه این عملگرها یک عدد صحیح است که درصورت درست بودن 1و
درصورت غلط بودن 0باز میگردانند .این عملگرها عبارتند از:
عملگر
مفهوم عملگر
مثال
<
بزرگتر (<)
a>b
>
کوچکتر (>)
a<b
=<
بزرگتر یا مساوی (≤)
a >= b
=>
کوچکتر یا مساوی (≤)
a <= b
==
مساوی (=)
a == b
=!
نامساوی (≠)
a != b
7-5-3عملگرهای مقایسه ای
نکته مهمی که باید به آن دقت کرد عملگر مساوی (==) است ،چرا که یک
اشتباه بسیار متداول برنامهنویسان Cاستفاده اشتباه از عملگر انتساب
(=) بجای عملگر تساوی (==) است که باعث ایجاد خطا در برنامه
میشود.
اولویت این عملگرها از باال به پایین بشرح زیر است:
عملگرهای < <= ، > ،و =>
عملگرهای == و =!
الزم بذکر است که در هنگام مساوی بودن اولویت چند عملگر ،این عملگرها
از چپ به راست محاسبه میگردند.
7-5-4عملگرهای منطقی
این عملگرها به شما اجازه میدهند که شرطهای سادهای را که با
استفاده از عملگرهای مقایسهای ایجاد شدهاند را با یکدیگر ترکیب نموده و
شرطهای پیچیده تری را بسازید .این عملگرها عبارتند از :
مثال
عملگر
مفهوم عملگر
نحوه کار
&&
ANDمنطقی
اگر هر دو عملوند درست باشند,
درست و در غیر اینصورت نادرست باز
میگرداند.
a>0 && sw==1
||
ORمنطقی
اگر هر دو عملوند نادرست باشند,
نادرست و در غیر اینصورت درست باز
میگرداند.
a<=100 || b!=0
!
NOTمنطقی
اگر عملوند درست باشد ،نادرست و
اگر نادرست باشد ،درست برمی
گرداند.
)! (a==1 || b<10
7-5-5عملگر شرطی
گاهی الزم است که ابتدا یک شرط بررسی شده و سپس برمبنای نتیجه (درست یا نادرست بودن آن) یکی
از دو عبارت ممکن بازگردانده شود .برای اینکار میتوان از یک عملگر 3تایی (با 3عملوند) بنام عملگر
شرطی استفاده نمود.
شکل کلی این عملگر بصورت زیر است:
>عبارت> :<2عبارت> ?<1شرط<
نحوه کار بدینصورت است که درصورت درست بودن شرط ،عبارت 1و در غیراینصورت عبارت 2بازگردانده
میشود.
به عنوان مثال به دستور زیر توجه کنید:
که این عبارت معادل دستور زیر است:
;a = (k<10) ? 100 : 50
;if (k<10) a=100
;else a=50
البته این عبارت به شکلهای پیچیده نیز میتواند مورد استفاده قرار گیرد .مثال :
;c += (a>0 && a<10) ? ++a : a/b
که معادل است با :
{ )if (a>0 && a<10
;a= a + 1
;c = c + a
}
;else c = c + a/b
7-5-6اولویت عملگرها
عملگر
اولویت
شرکت پذیری
1
()
از چپ به راست
2
- + ! -- ++
از راست به چپ
3
* / %
از چپ به راست
4
+ -
از چپ به راست
5
>> <<
از چپ به راست
6
>= > <= <
از چپ به راست
7
=! ==
از چپ به راست
8
&&
از چپ به راست
9
||
از چپ به راست
10
?:
از راست به چپ
11
== += -= *= /= %
از راست به چپ
12
,
از چپ به راست
7-6خواندن و نمایش اطالعات
همانطور که قبال نیز گفته شد ،یکی از اهداف زبان Cقابل حمل بودن آن
است .بهمین منظور سعی شده است که از دستوراتی که ممکن است
وابسته به ماشین خاصی باشد ،اجتناب گردد.
بهمین دلیل زبان Cبرخالف سایر زبانها دارای هیچ دستوری برای
خواندن از ورودی و یا نوشتن در خروجی نیست.
اما در عوض دارای تعدادی تابع (زیربرنامه) استاندارد میباشد که تقریبا
تمامی کامپایلرها از آنها حمایت میکنند.
این کار به کامپایلر این امکان را میدهد که بر حسب نوع سخت افزار
موردنظر ،توابع ورودی و خروجی را طراحی نماید.
این توابع در یک فایل سرآمد بنام stdio.hتعریف شدهاند و بنابراین قبل
ازاینکه بتوانید از این توابع استفاده نمایید ،باید این فایل را نیز با استفاده
از #includeدر برنامه خود بگنجانید.
7-6-1تابع نمایش در خروجی
برای نمایش اطالعات در خروجی از تابع printfاستفاده میشود.
این تابع رشته موردنظر شما را به خروجی استاندارد (که در حالت
عادی همان صفحه نمایش یا مانیتور است) میفرستد .شکل کلی این
تابع بصورت زیر است:
; )>لیست متغیرها< > ,رشته کنترلی<(printf
رشته کنترلی همان متنی است که قصد چاپ آن را داریم ،با ذکر این
نکته که در قسمتهایی که باید مقدار یک متغیر چاپ شود ،از یک
مشخصه تبدیل استفاده میشود .هر مشخصه تبدیل از یک عالمت %
بعالوه یک کاراکتر که نوع متغیر مورد نظر را نشان میدهد تشکیل
میگردد.
لیست متغیرها نیز همان متغیرهایی هستند که قصد چاپ آنها را داریم.
این متغیرها باید بترتیب قرار گرفته و با کاما ) (,از یکدیگر جدا شوند.
7-6-1تابع نمایش در خروجی
مشخصه تبدیل
مفهوم
%c
کاراکتر
%d
عدد صحیح در مبنای 10
%f
عدد اعشاری بدون نماد علمی
%e
عدد اعشاری با نماد علمی
%g
عدد اعشاری با حالت کوتاهتر بین eو f
%s
رشته
%ld
عدد صحیح بزرگ
%lf %le %lg
عدد اعشاری بزرگ
%o
عدد صحیح در مبنای 8
%x
عدد صحیحی در مبنای 16
%u
عدد صحیح بدون عالمت
تابع نمایش در خروجی7-6-1
#include <stdio.h>
void main() {
int age = 20;
float average = 18.23;
printf("You are %d years old and your average is %f \n",age,average);
}
You are 20 years old and your average is 18.230000
7-6-1تابع نمایش در خروجی
برای کنترل نحوه چاپ اعداد میتوانید از مشخصه طول میدان استفاده
کنید.
مشخصه طول میدان برای اعداد به شکل زیر استفاده میشود:
برای اعداد صحیح از %ndاستفاده میکنیم که nتعداد ارقام را نشان
میدهد (مثال .)%3dدر اینصورت برای هر متغیر nرقم درنظر گرفته
میشود .اگر اندازه عدد از nکوچکتر باشد ،به سمت چپ آن فضای
خالی اضافه میشود و اگر اندازه عدد بیش از nرقم باشد ،طول میدان
نادیده گرفته شده و عدد بطور کامل چاپ میشود.
برای اعداد اعشاری از %n.mfاستفاده میکنیم که nاندازه کل عدد
(شامل عالمت ممیز) و mتعداد ارقام اعشار است (مثال .)%5.2fدر
صورتیکه تعداد ارقام اعشاری عدد موردنظر از mبیشتر باشد ،عدد به m
عدد اعشار گرد میشود و در صورتیکه از mکمتر باشد ،در سمت
راست آن 0قرار داده میشود.
7-6-2تابع خواندن از ورودی
برای خواندن اطالعات از ورودی از تابع scanfاستفاده میشود.
این تابع اطالعات را ازورودی استاندارد (معموال صفحه کلید) خوانده و در
متغیرهای تعیین شده قرار میدهد.
شکل کلی این تابع بصورت زیر است:
; )>لیست آدرسهای متغیرها< > ,رشته کنترلی<(scanf
همانطور که میبینید نحوه احضار تابع scanfنیز مشابه printfاست .تنها تفاوت
در آن است که در scanfباید لیست آدرسهای متغیرها ارسال شود.
مبحث مربوط به آدرسها در فصول بعدی بررسی خواهد شد ولی در حال حاضر
بخاطر بسپارید که برای بدست آوردن آدرس یک متغیر از عالمت & استفاده
میکنیم .بعنوان مثال &ageبمعنای آدرس متغیر ageاست.
بطور کلی در Cقدیمی هرگاه که یک تابع دارای پارامترهای خروجی بود (یعنی
پارامترهایی که یک مقدار را باز میگرداندند) از آدرس متغیرها استفاده میشد
که امروزه این مسئله وجود ندارد.
تابع خواندن از ورودی7-6-2
#include <stdio.h>
void main() {
int age;
float average ;
printf("Please enter your age and average : ");
scanf("%d %f",&age,&average);
printf("You are %d years old and your average is %5.2f \n",age,average);
}
Please enter your age and average : 19 16.72
You are 19 years old and your average is 16.72
7-6-3ورودی و خروجی اطالعات در C++
زبان C++یک زبان شی گرا است ،به همین دلیل در این زبان برای
ورودی و خروجی از اشیاء بجای توابع استفاده میگردد.
از آنجا که امروزه معموال برنامهنویسان Cاز کامپایلرهای C++استفاده
میکنند ،میتوانند از اشیای ورودی و خروجی آن نیز استفاده کنند.
اینکار در بین بسیاری از برنامهنویسان Cمتداول است ،بهمین دلیل ما
در اینجا نحوه کار با اشیای خواندن و نوشتن در C++را بطور مقدماتی
توضیح میدهیم؛ گرچه توضیح کامل این موارد نیاز به آشنایی با شی
گرایی و زبان C++دارد.
قبل از هرچیز الزم به ذکر است که کلیه اشیای مربوط به ورودی و
خروجی در فایل سرآمدی بنام iostream.hتعیریف شده اند ،بنابراین ابتدا
باید این فایل به برنامه توسط دستور #includeالحاق گردد.
7-6-3ورودی و خروجی اطالعات در C++
زبان C++برای نمایش اطالعات از یک شیئ بنام coutاستفاده مینماید.
برای ارسال اطالعات مورد نظر برای چاپ به coutباید از عملگر درج در جریان
یا >> استفاده نماییم .بعنوان مثال :
; ” cout << “Please enter your name:
و یا مثال دیگر :
;int a = 10
;float b = 2.86
;cout << a
;cout << b
نکته جالبی که در این مثالها دیده میشود ،آنستکه برخالف تابع printfهیچ
نیازی به مشخص کردن نوع متغیری که قصد چاپ آن را داریم نیست و خود
شئ coutنوع آن را تشخیص میدهد.
عالوه براین میتوان چندین عملگر درج در جریان را با یکدیگر الحاق کرد و چندین
متغیر را با یک دستور چاپ کرد.
C++ ورودی و خروجی اطالعات در7-6-3
#include <iostream.h>
void main() {
int age = 20;
floate average = 18.23;
cout << “You are ” << age << “ years old and your average is ” << average ;
}
You are 20 years old and your average is 18.230000
7-6-3ورودی و خروجی اطالعات در C++
برای دریافت اطالعات از کاربر ،از شئ دیگری بنام cinاستفاده میشود.
برای ارسال متغیر مورد نظر به cinباید از عملگر استخراج از جریان یا <<
استفاده نماییم .بعنوان مثال:
;int a
;cin >> a
بازهم همانطور که میبینید نیازی به تعیین نوع متغیر موردنظر نیست و خود
شئ cinنوع متغیر را بطور اتوماتیک تشخیص داده و دادهای از همان نوع را از
کاربر دریافت و در متغیر مورد نظر قرار میدهد.
عملگرهای استخراج از جریان را نیز میتوان با یکدیگر الحاق کرد.
برای رفتن به خط بعد در شئ coutمیتوان از دستکاری کننده endlاستفاده
کرد .مثال در دستور زیر پس از چاپ پیغام ،مکان نما به خط بعد منتقل میشود:
;cout << “List of students : “ << endl
البته دستکاری کنندههای متعدد دیگری نیز ازجمله موارد مربوط به تعیین طول
میدان و نحوه چاپ مقادیر وجود دارد که توضیح آنها نیاز به یک مبحث مستقل
دارد.
C++ ورودی و خروجی اطالعات در7-6-3
#include <iostream.h>
void main() {
int age;
float average ;
cout << "Please enter your age and average : " ;
cin >> age >> average ;
cout << "You are " << age << "years old and your average is " << average;
}
Please enter your age and average : 19 16.72
You are 19 years old and your average is 16.72
7-7توابع کتابخانهای
همانطور که قبال نیز گفته شد ،زبان Cاز زیر برنامهها نیز حمایت میکند .هر زیر برنامه
در Cیک تابع نامیده میشود .تا کنون با توابعی همچون mainو یا printfو scanfآشنا
شده ایم.
معموال عرضه کنندگان کامپایلرها و یا سایر فروشندگان نرم افزار ،برخی از توابع عمومی
را که ممکن است مورد نیاز جمع کثیری از برنامهنویسان مختلف باشد را در قالب
کتابخانهای از توابع در اختیار برنامهنویسان میگذارند .بعضی از این توابع کتابخانهای
مانند printfو scanfبصورت استاندارد درآمده و توسط عرضه کنندگان مختلف ارائه
میشوند.
در کامپایلر عرضه شده توسط شرکت بورلند ) (Borland C++ 3.1نیز کتابخانههای متعددی
از توابع برای شما عرضه شدهاند که بتدریج با آنها و کاربردشان آشنا خواهید شد.
نکته مهم آنستکه برای استفاده از این توابع ابتدا باید فایل سرآمد مربوط به آنها را نیز در
ابتدای برنامه خود اضافه نمایید ( با استفاده از .)#includeهر فایل سرآمد شامل تعاریف
اولیه گروهی از توابع مرتبط با هم و دادههای مربوط به آنها بوده و در استاندارد قدیمی تر
دارای پسوند .hمیباشد(در استاندارد جدید پسوند این فایلها حذف شده است).
برخی از این فایلهای سرآمد عبارتند از:
: stdio.hتوابع ورودی و خروجی استاندارد
: math.hتوابع ریاضی
: graphics.hتوابع مربوط به عملیات گرافیکی
: string.hتوابع مربوط به کار با رشته ها
مبانی کامپیوتر و برنامه سازی
فصلهشتم :ساختارهایکنترلی
مدرس :رضارمضانی
8ساختارهای کنترلی
ساختارها در برنامهنویسی ساختیافته
ساختار ترتیب
ساختار انتخاب
ساختار تکرار
زبان Cدارای 7نوع ساختار کنترلی است
ساختار ترتیب :دستورهای زبان Cدر حالت عادی به همان ترتیبی که
نوشته شده اند ،یکی پس از دیگری اجرا میشوند.
3 نوع ساختار انتخاب:
ساختار ifیا ساختار تک انتخابی
ساختار if / elseیا ساختار دو انتخابی
ساختار switchیا ساختار چند انتخابی
3نوع ساختار تکرار
while
for
do / while
8-1ساختار انتخاب if
این دستور به شکل زیر استفاده میشود:
;>if (<expresion>) <statement
نحوه کار بدینصورت است که ابتدا عبارت موجود در قسمت > <expressionارزیابی
میشود .در صورتیکه درست ارزیابی گردد ،دستور قسمت > <statementاجرا خواهد شد
و در صورتیکه نادرست باشد ،بدون اینکه دستور قسمت > <statementرا اجرا کند به
دستور بعدی خواهد رفت.
این دستور میتواند بصورت زیر نیز استفاده گردد:
;>if (<expresion>) <statement 1
;>else <statement 2
در اینصورت ابتدا عبارت موجود در قسمت > <expressionارزیابی میشود .در صورتیکه
درست ارزیابی گردد ،دستور قسمت > <statement 1اجرا خواهد شد ،و در صورتیکه
نادرست باشد ،دستور قسمت > <statement 2اجرا خواهد شد .در هر حال فقط یکی از
این دو قسمت اجرا خواهد گردید.
8-1ساختار انتخاب if
بعنوان مثال چنانچه متغیر gradeحاوی نمره دانشجو باشد و بخواهیم بر
مبنای نمره وی ،پیغام مناسبی چاپ کنیم ،میتوانیم از دستور زیر
استفاده کنیم:
;)”! if (grade >= 10) printf(“Passed
;)”!else printf(“Failed
در حالت عادی دستور ifمنتظر یک دستور در بدنه خود میباشد ،اما
چنانچه میخواهید چندین دستور را در بدنه یک دستور ifدهید ،باید
آنها را در داخل آکوالد باز وبسته { } قرار دهید .این مجموعه دستورات
را یک دستور مرکب میگویند.
بطور کلی در زبان Cهرجا که میتوان یک دستور قرار داد ،میتوان از
یک دستور مرکب نیز استفاده کرد .به یک دستور مرکب ،بلوک نیز گفته
میشود.
if ساختار انتخاب8-1
: به شکل زیر استif بنابراین صورت کلی دستور
if (<expression>) {
<statement 1> ;
<statement 2> ;
….
<statement n> ;
}
else {
<statement 1> ;
<statement 2> ;
…….
<statement m> ;
}
if ساختار انتخاب8-1
را دریافت و ریشههای آن را2 ) برنامهای بنویسید که ضرایب یک معادله درجه1 برنامه
.محاسبه و چاپ نماید
#include <stdio.h>
#include <stdlib.h>
#include <conio.h>
#include <math.h>
void main() {
int a, b, c;
float x1, x2, delta;
clrscr();
printf(“Please enter a, b and c : “);
scanf(“%d %d %d”, &a, &b, &c);
if (a==0) {
printf(“wrong equation!”);
exit(1) ;
}
if (!a)
if ساختار انتخاب8-1
delta = b*b – 4*a*c;
if (delta < 0)
printf(“No answer !”);
else if (delta == 0) {
x1 = -b / (2*a);
printf(“There is one answer, x = %8.2f”,x1);
}
else {
delta = sqrt(delta);
x1 = (-b+delta) / (2*a);
x2 = (-b-delta) / (2*a);
printf(“There are two answers, x1= %8.2f and x2 = %8.2f”, x1, x2);
}
}
8-1ساختار انتخاب if
یک روش متداول استفاده از دستور ،ifاستفاده از ifهای تودرتو می
باشد.
;)"!if (grade >= 18) printf("good
;)"!else if (grade >= 15) printf("medium
;)"!else if (grade >= 12) printf("rather weak
;)"else if (grade >= 10) printf("weak
;)"!else printf("failed
درچنین حالتی توصیه می گردد که شرطهای نادر را که امکان وقوع آنها
کم است ،در انتهای کار بررسی نمایید ،تا تعداد مقایسه کمتری صورت
پذیرد.
8-1ساختار انتخاب if
مشکل ifهای تودرتو :در دستور زیر else ،به کدام ifتعلق
دارد؟
)if (a < b
;>if (c < d) <statement 1
;>else <statement 2
بطور کلی طبق قوانین گرامری ،هر elseمربوط به نزدیکترین if
قبل از خود می باشد.
8-1ساختار انتخاب if
اما سوال این است که اگر بخواهیم elseبه ifاول بازگردد از
چه روشی استفاده نماییم .دراینصورت می توان از یکی از دو
روش زیر استفاده کرد:
{ )if (a < b
;>if (c < d) <statement 1
}
;>else <statement 2
)if (a < b
;>if (c < d) <statement 1
; else
;>else <statement 2
if ساختار انتخاب8-1
. عدد را دریافت و حداکثر آنها را چاپ کند3 ) برنامه ای بنویسید که2 برنامه
#include <stdio.h>
void main() {
int a, b, c, max;
printf("Please enter 3 numbers :");
scanf("%d %d %d",&a, &b, &c);
if (a > b)
if (a > c) max = a;
else max= c;
else if (b > c) max = b;
else max = c;
printf("Maximum is %d",max);
}
while ساختار تکرار8-2
false
true ?)(≠=00) )
while (<expression>) {
<statement 1>
<statement 2>
…
<statement n>
}
<next statement>
while ساختار تکرار8-2
) برنامه ای بنویسید که یک عدد را دریافت و فاکتوریال آن را محاسبه و چاپ3 برنامه
.نماید
#include <stdio.h>
void main() {
int i,number;
long int factorial;
printf("Please enter number :");
scanf("%d",&number);
factorial = 1;
i = 1;
while (i <= number) {
factorial *= i;
i ++;
}
printf("Factorial of %d is %ld“,number,factorial);
}
8-2ساختار تکرار while
برنامه )4برنامه ای بنویسید که یک متن را از کاربر دریافت و آن را با حروف
بزرگ چاپ کند.
>#include <stdio.h
{ )(void main
;char ch
; )(ch = getch
{ )while (ch != 13
)'if (ch >= 'a' && ch <= 'z
;ch -= 32
;)putch(ch
;)(ch = getch
}
}
8-3ساختار تکرار for
همانگونه که در مثال مربوط به حل مسئله فاکتوریال دیده می شود،
گاهی نیاز به حلقه تکراری داریم که به تعداد دفعات مشخصی تکرار
گردد.
در چنین مواقعی با استفاده از یک متغیر شمارنده ،تعداد تکرارها را تا
رسیدن به مقدار مورد نظر میشماریم و سپس به حلقه پایان
میدهیم .به چنین حلقه هایی ،تکرار تحت کنترل شمارنده یا تکرار
معین میگوییم ،چرا که تعداد تکرارها از قبل مشخص است.
چنین حلقهای دارای 3جزء اصلی میباشد:
مقداردهی اولیه به متغیر شمارنده حلقه
شرط پایان حلقه (پایان شمارش)
نحوه افزایش متغیر شمارنده
از آنجا که در تمام حلقههایی که تکرار معین دارند ،همین ساختار
استفاده میشود؛ در اکثر زبانهای برنامه سازی یک ساختار تکرار ویژه،
بنام حلقه ،forبرای اینکار در نظر گرفته شده است.
8-3ساختار تکرار for
;>for (<expression1> ; <expression2> ; <expression3>) <statement
نحوه افزایش متغیر حلقه
شرط تکرار حلقه
مقداردهی اولیه
for ساختار تکرار8-3
)≠
)بعد0)اجرا ( از اجرای دوم به
) فقط در شروع حلقهfalse
(true
=(?اجرا
for (<exp1>;<exp2>;<exp3>) {
<statement 1>
<statement 2>
…
<statement n>
}
<next statement>
8-3ساختار تکرار for
درحقیقت هر حلقه forمعادل با حلقه whileزیر
است:
; ><exp1
{ )>while (<exp2
;><statement
;><exp3
}
8-3ساختار تکرار for
بعنوان یک مثال ساده ،تکه برنامه زیر اعداد بین 0تا 100را چاپ مینماید:
;int count
)for (count = 0; count <= 100; count ++
;)printf(“%d “,count
اگر بخواهیم تنها مضارب 5چاپ شوند ،حلقه را به شکل زیر تغییر می دهیم:
)for (count = 0; count <= 100; count += 5
حتی می توان مضارب 5را از آخر به اول چاپ کرد:
)for (count = 100; count >= 0; count -= 5
قسمت شرط می تواند یک شرط مرکب نیز باشد.
)for (count = 0; count < 100 && sw==1; count ++
نکته آخر اینکه قسمت مقدار دهی اولیه و افزایش متغیر نیز می توانند شامل چند عبارت
باشند که در اینصورت با کاما از یکدیگر جدا می شوند.
)for (a = 0, b = 100; b – a > 50; a++, b--
for ساختار تکرار8-3
. عدد بزرگتر و مجموع کل اعداد را محاسبه و چاپ نماید2 ) برنامهای بنویسید که تعدادی عدد را از کاربر دریافت و5 برنامه
#include <stdio.h>
void main() {
int i, n, number;
int sum, max1, max2;
printf(“please enter n : “);
scanf(“%d”,&n);
sum = 0;
max1 = max2 = -1;
for (i=0 ; i<n ; i++) {
printf(“enter number : “);
scanf(“%d”,&number);
sum += number;
if (number > max1) {
max2 = max1;
max1 = number;
}
else if (number > max2)
max2 = number;
} //end for
printf(“Sum = %d, Maximum 1=%d, Maximum 2= %d”, sum, max1, max2);
}
8-3ساختار تکرار for
نکته جالب در مورد حلقه forآنستکه می توان هریک از 3عبارت آن را
حذف کرد.
)for (; i<100; i++
);for (i=0; i<100
);for (; i<100
)for (i=0; ;i++
در مورد آخر حتما باید در داخل حلقه با استفاده از دستور ( breakکه در
قسمتهای بعدی توضیح داده خواهد شد) ،راهی برای خروج از حلقه
قرار داده شود.
do / while حلقه8-4
do {
<statement 1> ;
<statement 2> ;
…
<statement n> ;
} while (<expression>);
<next statement> ;
true )≠
false
(=0)
0)
?
8-4حلقه do / while
یک مثال کوچک:
فرض کنید از کاربر خواسته اید که اعالم کند آیا مایل به ادامه هست یا
خیر؟ وی باید پاسخ yیا nبدهد ،اما ممکن است یک حرف اشتباه
(مانند )mوارد کند.
قصد داریم تکه برنامه ای بنویسیم که عمل دریافت پاسخ را تا زمانیکه
یک حرف درست وارد شود ،تکرار کند.
مسلم است که باید ابتدا یک پاسخ وارد شود و سپس درستی آن
بررسی گردد.
;char answer
{ do
;)"? )printf("Do you want to continue (y/n
;)(answer = getch
; )'} while (answer != 'y' && answer != 'n
do / while حلقه8-4
برنامه ای بنویسید.( آماده شده استA, B, C and D) ) فرض کنید نمرات یک گروه از دانشجویان بصورت درجه بندی6 برنامه
در ضمن از آنجا که تعداد دانشجویان.که نمرات دانشجویان را دریافت و در پایان درصد هریک از نمرات را محاسبه و چاپ نماید
.) را وارد می نمایدQuit (مخففQ حرف، کاربر در انتهای نمرات،از قبل مشخص نیست
#include <stdio.h>
void main() {
int aCount, bCount, cCount, dCount, n;
char grade;
aCount = bCount = cCount = dCount = n = 0;
do {
printf("Enter grade (Q for Quit) : ");
grade = getch() ;
n ++;
if (grade == 'A') aCount ++;
else if (grade == 'B') bCount ++;
else if (grade == 'C') cCount ++;
else if (grade == 'D') dCount ++;
else if (grade == 'Q') n --;
else {
printf("Wrong grade, try again.\n");
n --;
}
} while (grade != 'Q' ) ;
do / while حلقه8-4
printf("Statistics :\n");
printf("Grade A : %f percent\n", float(aCount)/float(n));
printf("Grade B : %f percent\n", float(bCount)/float(n));
printf("Grade C : %f percent\n", float(cCount)/float(n));
printf("Grade D : %f percent\n", float(dCount)/float(n));
} // end main
switch / case ساختار8-5
<expression> == <exp1> ?
false
switch (<expression>) {
case <exp1> : <statement 1> ;
<statement 2> ;
…
<statement n> ;
<expression> == <exp2> ?
case <exp2> : <statement 1> ;
<statement 2> ;
…
<statement n> ;
…
true
if there is no match
default : <statement 1> ;
<statement 2> ;
…
<statement n> ;
}
8-5ساختار switch / case
توجه کنید که قسمت defaultاختیاری بوده و میتوان از آن استفاده نکرد.
این ساختار فقط برای عبارات کاراکتری و صحیح معتبر بوده و نمیتوان در آن از عبارات
اعشاری استفاده نمود.
نکته مهم دیگر در مورد این ساختار این است که چنانچه عبارت > <expressionبا یک ثابت
مانند > <constant iبرابر باشد ،آنگاه پس از اینکه دستورات مربوط به این حالت اجرا
گردید ،اجرا ادامه یافته و دستورات مربوط حالتهای بعدی تا انتهای switchانجام خواهد
شد!
به عنوان مثال چنانچه عبارت > <expressionبا ثابت > <constant 2برابر باشد ،پس از اجرای
دسترات مربوط به این حالت ،دستورات حالتهای > <constant 3و ...تا > <constant mو حتی
قسمت defaultنیز اجرا خواهد گردید.
برای جلوگیری از این وضعیت که معموال دلخواه برنامهنویسان نیست ،میتوان از دستور
breakاستفاده کرد .این دستور که بعدا در مورد آن توضیح بیشتری خواهیم داد ،باعث
میشود که از ساختار switchخارج شده و به دستور پس از آن برویم.
بنابراین معموال برنامهنویسان در پایان دستورات هر ،caseاز یک دستور breakاستفاده
میکنند .این کار باعث میشود که پس از اجرای دستورات مربوط به هر ،caseبا رسیدن
به دستور breakبالفاصله از ساختار switchخارج شده و دستورات مربوط به caseبعدی
اجرا نشوند.
8-5ساختار switch / case
اما چرا در زبان Cاز این روش استفاده شده است
بطوریکه برنامهنویسان مجبور به استفاده از دستور break
شوند؟
جواب این است که میتوان با استفاده از این خاصیت ،چندین
caseمختلف را با یکدیگر یای منطقی ) (orکرد.
فرض کنید چند caseمختلف دارید که قصد دارید با وقوع هریک از
آنها ،مجموعه دستورات مشترکی انجام شوند.
کافی است این caseها را بصورت پشت سرهم قرار داده و
دستورات همگی آنها بجز caseآخر را خالی قرار دهید.
حال دستورات مشترک را در caseآخر قرار داده و در انتها نیز یک
دستور breakبگذارید.
اکنون چنانچه عبارت با هریک از این caseها برابر باشد ،از آنجا که
هیچیک دارای دستور breakنیستند ،اجرا تا caseآخر ادامه خواهد
یافت و در پایان دستورات مشترک اجرا خواهد شد.
8-5ساختار switch / case
بعنوان مثال فرض کنید یک متغیر صحیح بنام pointداریم که امتیاز یک ورزشکار را بین 1تا
5مشخص مینماید .اکنون قصد داریم بسته به امتیاز ورزشکار ،پیام مناسبی را برای وی
چاپ نماییم .امتیاز 1یا 2ضعیف ،امتیاز 3متوسط ،و امتیاز 4یا 5خوب ارزیابی میگردد.
ساختار زیر این کار را انجام میدهد.
{ )switch (point
case 1 :
;)"case 2 : printf("weak!\n
;break
;)"case 3 : printf("medium!\n
;break
case 4 :
;)"case 5 : printf("good!\n
;break
;)"!default : printf("out of range
}
switch / case ساختار8-5
برنامه را. بازنویسی نماییدswitch / case را با استفاده از دستور6 ) برنامه7 برنامه
.بگونه ای بنویسید که حروف بزرگ و کوچک هردو مورد قبول واقع شود
#include <stdio.h>
void main() {
int aCount, bCount, cCount, dCount, n;
char grade;
aCount = bCount = cCount = dCount = n = 0;
do {
printf("Enter grade (Q for Quit) : ");
grade = getch() ;
n ++;
switch / case ساختار8-5
switch (grade) {
case 'A' :
case 'a' : aCount ++;
break ;
case 'B' :
case 'b' : bCount ++;
break ;
case 'C' :
case 'c' : cCount ++;
break ;
case 'D' :
case 'd' : dCount ++;
break ;
case 'Q' :
case 'q' : n--;
break ;
default : printf("Wrong grade, try again.\n");
n --;
} //end switch
} while (grade != 'Q' && grade!=‘q’) ;
printf("Statistics :\n");
printf("Grade A : %f percent\n", float(aCount)/float(n));
printf("Grade B : %f percent\n", float(bCount)/float(n));
printf("Grade C : %f percent\n", float(cCount)/float(n));
printf("Grade D : %f percent\n", float(dCount)/float(n));
} // end main
switch / case ساختار8-5
حاصل را، یک عملگر و یک عدد دیگر را از کاربر دریافت و پس از اعمال عملگر برروی دو عدد،) برنامه ای بنویسید که یک عدد8 برنامه
.چاپ نماید
#include <stdio.h>
void main() {
int number1, number2, result;
char op ;
printf("Please enter number1 operator number2 : ");
scanf("%d %c %d",&number1, &op, &number2);
result = 0;
switch (op) {
case '+' : result = number1 + number2 ; break;
case '-' : result = number1 - number2 ; break;
case '*' : result = number1 * number2 ; break;
case '/' : if (number2 != 0) result = number1 / number2 ;
else printf("There is no answer!\n");
break;
case '%' : if (number2 != 0) result = number1 % number2 ;
else printf("There is no answer!\n");
break;
default : printf("invalid operator!\n");
}
printf("Result = %d",result);
}
break دستور8-6
while (<expression>) {
>statements … <
true ?) ≠ 0 )
…
if (<exp1>) break ;
…
}
<next statement>
break دستور8-6
بالفاصله به عملیات خاتمه، و درصورتیکه عدد منفی وارد شد، را بگونه ای تغییر دهید که فقط اعداد مثبت را بپذیرد5 ) برنامه9 برنامه
.داده و نتایج تا همین نقطه را چاپ نماید
#include <stdio.h>
void main() {
int i, n, number;
int sum, max1, max2;
printf(“please enter n : “);
scanf(“%d”,&n);
sum = 0;
max1 = max2 = -1;
for (i=1 ; i<n ; i++) {
printf(“enter number : “);
scanf(“%d”,&number);
if (number < 0) break; // this is the difference
sum += number;
if (number > max1) {
max2 = max1;
max1 = number;
}
else if (number > max2)
max2 = number;
} //end for
printf(“Sum = %d, Maximum 1=%d, Maximum 2= d”, sum, max1, max2);
}
continue دستور8-6
while (<expression>) {
>statements … <
true ?) ≠ 0 )
…
if (<exp1>) continue ;
…
}
<next statement>
8-6دستور continue
بعنوان مثال ،چنانچه بخواهیم برنامه 9را بگونه ای تغییر
دهیم که از اعداد منفی صرفنظر کند و آنها را در محاسبات
لحاظ نکند ،کافیست دستور
را به دستور زیر تبدیل کنیم:
;if (number < 0) break
;if (number < 0) continue
دراینصورت ،چنانچه عدد منفی باشد ،بدون اینکه
محاسبات بعدی انجام شوند ،کنترل به ابتدای حلقه
بازگشته و عدد بعدی را دریافت می کند.
مبانی کامپیوتر و برنامه سازی
فصلنهم :توابع
مدرس :رضارمضانی
9توابع
در مبحث الگوریتمها راجع به مزایای استفاده از زیرالگوریتمها صحبت
کردیم .در برنامهنویسی نیز مباحث مشابهی در مورد زیربرنامهها وجود
دارد.
هنگامی که یک برنامه بیش از حد بزرگ می شود ،برنامهنویسی و
مدیریت آن مشکل می گردد و ممکن است از عهده یک نفر خارج باشد.
در چنین شرایطی ،برنامه را به چندین بخش کوچکتر (زیربرنامه) تقسیم
کرده و هریک را بعهده یک برنامهنویس می گذاریم .هر قسمت به
تنهایی نوشته شده و رفع اشکال می گردد .سپس این زیربرنامهها در
کنار یکدیگر قرار گرفته و تشکیل برنامه اصلی را می دهند.
اینکار نه تنها در تقسیم کار به ما کمک می کند ،بلکه باعث می شود
خوانایی برنامه نیز باالتر رفته و اشکالزدایی آن ساده تر گردد .عالوه
براین می توان یک زیربرنامه را یکبار نوشت ،و سپس در برنامه های
مختلف از آن استفاده کرد که به این خاصیت ،قابلیت استفاده مجدد از
نرم افزار گفته می شود.
9توابع
در زبان Cبه هر زیربرنامه ،یک تابع گفته می شود.
یک تابع ،تکه برنامه ای است که داده یا داده هایی را بعنوان ورودی دریافت ،و داده یا
داده هایی را بعنوان خروجی باز می گرداند.
در زبان ،Cهر برنامه از یک یا چند تابع تشکیل می گردد ،که یکی از آنها باید بنام main
نامیده گردد و برنامه از این تابع شروع خواهد گردید.
تابع mainمی تواند سایر توابع را فراخوانی نماید و هر یک از این توابع نیز می توانند به
نوبه خود ،توابع دیگر را فراخوانی نمایند.
نکته جالب اینجاست که تابع فراخواننده نیازی به دانستن نحوه کار تابعی که فراخوانی
می کند ،ندارد و تنها باید از نحوه فراخوانی و مقدار خروجی آن آگاه باشد .این نحوه
پنهانسازی جزئیات پیاده سازی ،نقش بسیار مهمی در مهندسی نرم افزار دارد.
تاکنون از توابع کتابخانه های استاندارد Cاستفاده نموده ایم .کتابخانه استاندارد C
مجموعه ای از توابع و نوع دادهها است که برای انجام عملیاتی که عموما مورد نیاز
برنامهنویسان است ،طراحی شده و همراه کامپایلر در اختیار برنامهنویسان قرار داده
شده است.
بعنوان مثال توابع ورودی/خروجی مانند printfو scanfو یا توابع ریاضی مانند sqrtو sin
که توسط بسیاری از برنامهنویسان مورد استفاده قرار می گیرند.
اما ازطرف دیگر ،برنامهنویس نیز می تواند توابع مورد نیاز خود را تعریف کرده و از آنها در
برنامه خود استفاده نماید .به این دسته از توابع ،توابع کاربر می گوییم.
9-1توابع کاربر
بکار گیری توابع شامل دو قسمت است:
تعریف تابع
استفاده از تابع (فراخوانی تابع)
ما در مثالهای قبلی تابع mainرا در برنامه های خود تعریف می کردیم.
تعریف توابع دیگر نیز بطور مشابه می باشد ،که جزئیات آن را بررسی
خواهیم کرد.
البته تابع اصلی بطور اتوماتیک در ابتدای اجرای برنامه فراخوانی می
گردد و برنامهنویس صریحا آن را احضار نمی کند ،ولی سایر توابع باید از
داخل تابع دیگری (از جمله )mainبطور صریح فراخوانی گردند.
البته ما قبال توابعی همچون scanfو printfرا فراخوانی کرده ایم ،اما
جزئیات مربوط به نحوه فراخوانی را در قسمت بعدی بررسی می
نماییم.
9-1-1تعریف تابع
قالب کلی تعریف تابع بصورت زیر است:
{ ) … <return-type> <function-name> (<param-type param-name> ,
; ><local variable definitions
; ><statements
در این قسمت هرگونه متغیری که برای انجام
وظایف محوله به تابع مورد نیاز باشد،
}
تعریف می گردد .این متغیرها نیز همانند
پارامترهای تابع ،محلی محسوب می
شوند و در سایر توابع شناخته شده
پس از نام تابع و در داخل پرانتز ،لیست
ها
نیستند .با شروع هر تابع ،این متغیر
پارامترهای تابع قرار می گیرد .این
در این قسمت ،دستورات
بطور اتوماتیک ایجاد شده و پس از خاتمه
تعدادی پارامتر
تعریف
مقدار شامل
نوعلیست
نوع داده
بازگشتی :
دهنده بدنه تابع که
تشکیل
درمی
بین
آن از
روند.یک شناسه است که از
حقیقت
تابع
نام
موردشده
جدا
از یکدیگر
توسط '',
مقداری که با کاما
است
بازگشت
تابع
نظر
وظایف
به
مربوط
نامگذاری
قوانین
همان
و
نوع
شامل
پارامتر
هر
تعریف
.
اند
می دهد
داده می شود را نشان
نویس .را انجام می
برنامه
شناسهها تبعیت می کند .یک تابع
ا
پارامتره
.
باشد
می
سپس نام پارامتر دهند ،قرار می گیرند.
.
گردد
حقیقت رابط بین تابع احضار کننده و
توسط نام خود فراخوانی می در
تابع احضار شونده هستند.
9-1-1تعریف تابع
مقدار بازگشتی تابع توسط دستور returnبه تابع فراخواننده برگشت
داده می شود .برای این کار کافی است به شکل زیر عمل نماییم:
;> return <expو یا ; )>return (<exp
چنانچه تابع مقدار بازگشتی نداشته باشد ،از کلمه کلیدی voidبجای
نوع مقدار بازگشتی استفاده می نماییم .دراینصورت ،دستور return
نیاز به مقدار بازگشتی ندارد.
البته چنانچه تابع بیش از یک مقدار بازگشتی داشته باشد ،باید از
تکنیکهای گفته شده در قسمت بعد استفاده نماییم.
الزم به ذکر است که در قسمت دستورات تابع باید یک (یا چند)
دستور returnداشته باشیم که کنترل را به تابع فراخواننده باز گرداند.
البته درمورد توابعی که مقداری را باز نمی گردانند ،درصورت عدم
وجود دستور ،returnکنترل پس از رسیدن به انتهای تابع یعنی } بطور
خودکار به تابع فراخواننده باز می گردد.
9-1-1تعریف تابع
نکته مهم دیگر مکان تعریف توابع در یک برنامه Cاست.
یک برنامه Cمی تواند دارای یک یا چند تابع باشد که
یکی از آنها باید حتما mainنامیده شود و همانطور که قبال
نیز گفته شد ،اجرای برنامه از این تابع آغاز می گردد.
توابع می توانند به هر ترتیبی تعریف شوند ،اما معموال تابع
mainدر آخر توابع دیگر تعریف می گردد؛ گرچه این مسئله
اجباری نیست.
توابع باید بصورت پشت سر هم تعریف گردند و برخالف
بعضی از زبانهای دیگر ،نمی توان یک تابع را در داخل تابع
دیگر تعریف کرد .بعبارت دیگر ،کلیه توابع در یک سطح قرار
دارند و هیچ تابعی ،شامل تابع دیگر نمی باشد.
9-1-2فراخوانی توابع
برای فراخوانی یک تابع باید از نام آن بعالوه لیست
آرگومانهای متناسب با پارامترهای تابع استفاده کرد.
نکته مهم آنستکه باید تعداد ،ترتیب و نوع آرگومانهای
ارسالی با پارامترهای متناظرشان در تعریف تابع ،منطبق
باشد .در غیراینصورت ممکن است خطای نحوی و یا حتی
خطای منطقی رخ دهد.
هنگامیکه یک تابع فراخوانی می گردد ،اجرای تابع
فراخواننده بطور موقت متوقف شده و کنترل اجرا به تابع
فراخوانی شده منتقل می گردد .پس از اتمام تابع
فراخوانی شده و اجرای دستور returnتوسط آن ،کنترل
اجرا به تابع فراخواننده بازگشته و اجرا را از دستور بعدی،
از سر می گیرد.
9-1-2فراخوانی توابع
چنانچه تابع هیچ مقداری را بازنگرداند ،می توان آن را بصورت یک دستور
مستقل فراخوانی کرد .بعنوان مثال :
; )(clrscr
اما توابعی که یک مقدار خروجی را باز می گردانند ،می توان در یک عبارت
نسبت دهی یا محاسباتی نیز بکاربرد .بعنوان مثال می توان تابع ( sqrtکه یک
عدد را دریافت و جذر آن را باز می گرداند) را بصورت زیر استفاده کرد :
; )a = sqrt(10
; a = 2 * sqrt(b) + c
نکته مهم آنستکه چنانچه فراخوانی تابع توسط مقدار باشد ،آنگاه می توان
بجای یک متغیر یا یک ثابت ،یک عبارت محاسباتی را نیز به تابع ارسال کرد.
بعنوان مثال فراخوانی زیر مجاز است:
; )a = sqrt(2*b+8
در اینحالت ،ابتدا عبارت محاسباتی ارزیابی شده و سپس مقدار آن بعنوان
آرگومان به تابع ارسال می گردد .البته درصورتیکه فراخوانی توسط ارجاع باشد،
فقط یک متغیر می تواند به تابع ارسال گردد و عبارت محاسباتی و یا حتی یک
ثابت به تنهایی نیز مورد قبول نخواهد بود.
فراخوانی توابع9-1-2
void main() {
int a, b;
a=5;
b = square(a) ;
a:5
x:5
b : 25
return 25
printf(“a=%d and b=%d”,a,b)
}
a = 5 and b = 25
int square(int x) {
return(x * x) ;
}
چند نمونه از توابع9-2
. را محاسبه نمایدk بهn ) برنامه ای بنویسید که مقدار ترکیب1 مثال
#include <stdio.h>
long int factorial(int number) {
int i;
long int f = 1L ;
for (i=1; i<=number; i++)
f *= i ;
return(f) ;
}
void main() {
int n, k ;
long int result;
printf("please enter n and k : ");
scanf("%d %d", &n, &k) ;
result = factorial(n) / ( factorial(k) * factorial(n-k) ) ;
printf("result = %ld", result) ;
}
چند نمونه از توابع9-2
برای اینکار از.) برنامه ای بنویسید که تعدادی عدد را دریافت و سپس حداکثر آنها را محاسبه و چاپ نماید2 مثال
. استفاده نمایید، که دو عدد را دریافت و حداکثر آنها را باز می گرداندgetMax تابعی بنام
#include <stdio.h>
#include <values.h>
int getMax(int a, int b) {
if (a>b) return(a);
else return(b) ;
}
void main() {
int i, n, max, number ;
printf("Please enter n: ");
scanf("%d",&n);
max = -MAXINT-1;
for (i=0; i<n; i++) {
printf("Please enter number: ");
scanf("%d", &number);
max = getMax(number, max);
}
printf("Maximum is : %d", max);
}
9-2چند نمونه از توابع
مثال )3تابعی بنویسید که یک خط 40تایی از عالمت * را چاپ نماید .سپس با استفاده
از آن برنامهای بنویسید که ابتدا پیام Helloو سپس خط جداکننده فوق را چاپ کند.
>#include <stdio.h
{ )(void starLine
;int i
)for (i=0; i<40; i++
;)"*"(printf
;)"printf("\n
}
{ )(void main
;)"printf("Hello\n
; )(starLine
}
9-2چند نمونه از توابع
البته درچنین مواردی ،بهتر است برنامهنویس تابع رسم خط را بصورت کلی تری بنویسد؛ بطوریکه در
سایر برنامهها نیز بتواند از آن استفاده نماید .مثال چنانچه تابع را بگونهای بنویسیم که کاراکتر
جداکننده و تعداد آن را بعنوان ورودی دریافت کند ،حالت کلی تری پیدا خواهد کرد.
>#include <stdio.h
{ )void separatorLine(char sep,int n
;int i
)for (i=0; i<n; i++
;)printf("%c",sep
;)”printf(“\n
}
{ )(void main
;)"printf("Hello\n
; )separatorLine('-' ,60
}
9-3نمونه اولیه توابع
همانطور که قبال گفته شد ،توابع میتوانند به هر ترتیبی تعریف شوند ،ولی معموال تابع
mainدر انتها قرار میگیرد.
اما اگر بخواهیم دقیقتر صحبت کنیم ،تعریف هر تابع باید قبل از فراخوانی آن صورت پذیرد.
چنانچه تابعی قبل از آنکه تعریف شود ،فراخوانی گردد؛ یک خطای کامپایل رخ خواهد داد.
اما میتوان این خطا را با استفاده از نمونه اولیه تابع ) (prototypeکه به آن پیش تعریف
نیز گفته میشود ،از بین برد.
یک نمونه اولیه تابع ،شامل نام تابع ،نوع دادهای که باز میگرداند و همچنین تعداد و نوع
پارامترهای تابع است .برای مثال ،نمونه اولیه تابع factorialبصورت زیر است:
; )long int factorial(int
این نمونه اولیه میگوید که تابع factorialیک آرگومان از نوع intدریافت و یک مقدار از نوع
long intباز میگرداند .عالمت ; انتهای دستور نشان میدهد که این فقط یک نمونه اولیه
بوده و شامل تعریف بدنه تابع نمیباشد.
نمونه اولیه یک تابع حتما باید با تعریف آن (که در قسمتهای بعدی برنامه آمده است)
یکسان باشد و گرنه یک خطای کامپایل رخ خواهد داد.
کامپایلر از نمونه اولیه تابع برای بررسی درستی نحوه فراخوانی تابع (از نظر تعداد و نوع
آرگومانها) استفاده میکند.
9-4انواع فراخوانی توابع
بطورکلی ،به دو روش میتوان یک تابع را فراخوانی کرد:
فراخوانی توسط مقدار )(Call by Value
فراخوانی توسط ارجاع )(Call by Reference
9-4-1فراخوانی توسط مقدار
در فراخوانی توسط مقدار ،آرگومانهای ارسالی توسط تابع فراخواننده،
در پارامترهای متناظر تابع فراخوانی شده ،کپی میگردند.
بنابراین تابع فراخوانی شده عملیات خود را برروی یک کپی از
آرگومانهای ارسالی انجام میدهد .درنتیجه ،در صورت انجام هرگونه
تغییری برروی این کپی توسط تابع فراخوانی شده ،متغیر اصلی در تابع
فراخواننده تغییر نخواهد کرد.
مزیت این نوع فراخوانی در این است که میتوان بدون هیچ نگرانی از
تغییر ناخواسته متغیرها ،آنها را به هر تابعی ارسال کرد ،چرا که فقط
یک کپی از آنها به تابع ارسال میشود.
در زبان ،Cدر حالت عادی فراخوانی توسط مقدار صورت میپذیرد.
برای روشن شدن موضوع به مثال اسالید بعدی توجه کنید.
فراخوانی توسط مقدار9-4-1
void test(int a, int b) {
printf("Function test : a=%d and b=%d \n",a,b);
a ++ ;
b *= 2;
printf("Function test : a=%d and b=%d \n",a,b);
}
void main() {
int x, y;
x = 10;
y = 8;
printf("Function main : x=%d and y=%d \n",x,y);
test(x,y) ;
printf("Function main : x=%d and y=%d \n",x,y);
}
Function main : x=10 and y=8
Function test : a=10 and b=8
Function test : a=11 and b=16
Function main : x=10 and y=8
9-4-1فراخوانی توسط مقدار
{ )void test(int a, int b
…
a 10 11
…
…
b 8 16
…
}
10
8
aو bتغییرکردهاند،
امامتغیرهایاصلی xو yبدونتغییرماندهاند.
{ )(void main
…
…
x
…
y
…
…
; )test(x,y
…
}
9-4-2فراخوانی توسط ارجاع
در این روش فراخوانی ،خود آرگومانهای اصلی (بجای یک کپی از آنها) به تابع
فراخوانی شده ارسال می گردد.
در نتیجه ،هرگونه تغییری در پارامترهای تابع فراخوانی شده ،مقدار آرگومانهای
اصلی در تابع فراخوانی کننده را نیز تغییر خواهد داد.
در Cاولیه ،تنها راه فراخوانی توابع توسط ارجاع ،استفاده از متغیرهای اشاره
گر بود که در فصول بعدی این روش بررسی خواهد گردید .اما در استاندارد جدید
،Cمی توان این کار را به روش ساده تر ی و با استفاده از متغیرهای
ارجاعی انجام داد.
یک متغیر ارجاعی ،در حقیقت یک نام مترادف و یا یک جایگزین برای یک
متغیر دیگر است.
برای تعریف یک متغیر ارجاعی از عالمت & پس از نوع متغیر مورد ارجاع
استفاده می کنیم .بعنوان مثال به نمونه زیر توجه کنید:
;int a = 10
;int &r = a
از این پس متغیر ،rنام دیگری برای متغیر aمحسوب می شود و هرگونه
تغییری در ،rباعث تغییر aنیز خواهد شد.
فراخوانی توسط ارجاع9-4-2
#include <stdio.h>
void main() {
int a = 10;
int &r = a;
r ++;
printf("a=%d and r=%d \n",a,r);
a ++;
printf("a=%d and r=%d \n",a,r);
}
a = 11 and r = 11
a = 12 and r = 12
9-4-2فراخوانی توسط ارجاع
برای ارسال آرگومانها توسط ارجاع ،کافی است پارامترهای تابع مورد
نظر را بصورت متغیر ارجاعی تعریف نماییم.
در اینصورت ،این پارامترها در حقیقت یک ارجاع به آرگومانهای ارسالی
خواهند بود و در نتیجه هرگونه تغییری در آنها ،آرگومانهای اصلی در تابع
فراخوانی کننده را نیز تغییر خواهد داد.
بعبارت بهتر ،در این روش بجای آنکه یک کپی از آرگومانها در پارامترها
قرار گیرد ،خود آرگومانها به تابع فراخوانی شده ارسال خواهند شد.
بعنوان مثال ،همان تابع قسمت قبلی را که بصورت فراخوانی توسط
مقدار عمل می کرد ،مجددا به روش فراخوانی با ارجاع باز نویسی می
کنیم تا تفاوت این دو مشخص گردد.
فراخوانی توسط ارجاع9-4-2
void test(int &a, int &b) {
printf("Function test : a=%d and b=%d \n",a,b);
a ++ ;
b *= 2;
printf("Function test : a=%d and b=%d \n",a,b);
}
void main() {
int x, y;
x = 10;
y = 8;
printf("Function main : x=%d and y=%d \n",x,y);
test(x,y) ;
printf("Function main : x=%d and y=%d \n",x,y);
}
Function main : x=10 and y=8
Function test : a=10 and b=8
Function test : a=11 and b=16
Function main : x=11 and y=16
فراخوانی توسط ارجاع9-4-2
void main() {
…
x 10 11
…
…
y 8 16
…
…
test(x,y) ;
…
}
void test(int &a, int &b) {
…
a
…
…
b
…
}
9-4-2فراخوانی توسط ارجاع
اکنون سوال اصلی این است که در چه مواردی از فراخوانی توسط
ارجاع استفاده کنیم؟
فراخوانی توسط ارجاع در دو مورد کاربرد دارد ،که هریک را با ذکر یک
مثال توضیح می دهیم.
می دانیم که هر تابع فقط می تواند یک مقدار را توسط دستور returnباز
گرداند .در مواردی که تابع باید بیش از یک مقدار را باز گرداند ،تنها راه
استفاده از فراخوانی توسط ارجاع است.
بدین صورت که تعدادی پارامتر خروجی را بصورت ارجاعی به تابع
ارسال می کنیم ،و تابع پس از انجام محاسبات ،خروجی را در این
متغیرها قرار داده و باز می گرداند.
از آنجا که پارامترها توسط ارجاع ارسال شده اند ،تغییرات اعمال شده
توسط تابع (قرار دادن مقدار خروجی در آنها) به تابع فراخواننده منتقل
خواهد شد.
فراخوانی توسط ارجاع9-4-2
) برنامه ای بنویسید که ضرایب یک معادله درجه دوم را دریافت و ریشه های آن را محاسبه وچاپ5 مثال
.نماید
#include <stdio.h>
#include <math.h>
int equation(int a, int b, int c, float &x1, float &x2) {
float delta;
if (a==0) return(0) ; //معادله جواب ندارد
delta = b*b – 4*a*c ;
if (delta < 0) return(0) ; //معادله جواب ندارد
else if (delta == 0) {
//معادله یک جواب دارد
x1 = -b / (2*a) ;
return(1) ;
}
else {
//معادله دو جواب دارد
delta = sqrt(delta) ;
x1 = (-b + delta) / (2*a) ;
x2 = (-b – delta) / (2*a) ;
return(2) ;
}
}
فراخوانی توسط ارجاع9-4-2
void main() {
int resultNo, coef1, coef2, coef3;
float result1,result2 ;
printf("Please enter coefficients: ");
scanf("%d %d %d", &coef1, &coef2, &coef3);
resultNo = equation(coef1,coef2,coef3,result1,result2);
if (resultNo == 0)
printf("There is no answer! \n");
else if (resultNo==1)
printf("There is 1 answer, x=%f \n", result1);
else printf("There are 2 answers, x1=%f and x2=%f \n",
result1, result2);
}
Please enter coefficients : 3 -7 2
There are two answers, x1 = 2.000000 and x2 = 0.333333
9-4-2فراخوانی توسط ارجاع
کاربرد دوم در مواردی است که آرگومان ارسالی به تابع،
همزمان نقش ورودی و خروجی را دارا باشد.
یعنی آرگومان عالوه بر آنکه مقداری را به تابع ارسال
می کند ،ممکن است مقداری را به تابع فراخواننده نیز
بازگرداند .مثال زیر موضوع را روشن تر میکند.
مثال )6برنامه ای بنویسید که دو عدد را از کاربر دریافت
و با استفاده از یک تابع ،مقدار آن دو را جابجا نموده و
حاصل را چاپ نماید.
به حل این مثال در اسالید بعدی توجه کنید.
فراخوانی توسط ارجاع9-4-2
#include <stdio.h>
void swap(int &a, int &b) {
int temp;
temp = a;
a = b;
b = temp;
}
void main() {
int x, y;
printf("enter x : ");
scanf("%d", &x);
printf("enter y : ");
scanf("%d", &y);
printf("x = %d and y = %d \n", x, y);
swap(x,y);
printf("now x = %d and y = %d \n", x, y);
}
enter x : 8
enter y : 12
x = 8 and y = 12
now x = 12 and y = 8
9-5حوزه شناخت متغیر
یکی از مسائل مهم در مورد متغیرها ،حوزه شناخت ) (Scopeمتغیر است که تعیین می
نماید متغیر در چه قسمتهایی از برنامه شناخته شده است و در چه قسمتهایی قابل
استفاده نیست.
بطور کلی متغیرها به دو دسته تقسیم می شوند:
متغیرهای محلی ) :(local variableمتغیرهایی هستند که در داخل یک بالک }{ تعریف شدهاند و
فقط در محدوده همان بالک شناخته شده هستند.
نمونه این دسته از متغیرها ،متغیرهای محلی توابع هستند که در داخل بالک مربوط به تابع تعریف
می شوند و فقط در همان تابع شناخته شده هستند.
البته دو تابع مختلف می توانند دارای متغیرهای همنام باشند ،که در اینصورت این دو متغیر مجزا
بوده و هیچ ارتباطی به یکدیگر ندارند.
الزم به ذکر است که پارامترهای یک تابع نیز جزو متغیرهای محلی آن تابع محسوب می گردند.
نکته دیگر اینکه گرچه معموال متغیرهای محلی در داخل تابع تعریف می شوند ،اما هر بلوک دلخواه
می تواند دارای متغیرهای محلی باشد .مثال یک دستور ifمرکب می تواند در داخل بلوک خود،
متغیرهای محلی را تعریف کند که فقط در داخل همان بلوک شناخته شده باشند (به مثال بعدی
مراجعه کنید).
متغیرهای سراسری ) :(global variableمتغیرهای هستند که در خارج کلیه بلوکها (و توابع از
جمله )mainتعریف شدهاند و در کل توابع برنامه (در حقیقت کل فایل مربوط به برنامه) شناخته
شده و قابل استفاده می باشند.
از آنجا که کلیه توابع برنامه به این متغیرها دسترسی دارند ،هرگونه تغییری در این متغیرها توسط
یکی از توابع ،در سایر توابع نیز قابل رویت خواهد بود.
9-5حوزه شناخت متغیر
متغیر سراسری
متغیر محلی برای main
متغیر سراسری aدر اینجا قابل
دسترسی است
به متغیر محلی bنیز میتوان
بلوک if
برای
محلی
متغیر
عالوه بر متغیر
قسمت
در این
داشت
دسترسی
محلی ،cبه متغیرهای aو bنیز
میتوان دسترسی پیدا کرد.
در این قسمت به متغیرهای aو bدسترسی
داریم ،اما دیگر نمیتوان به متغیر محلی c
مربوط به بلوک ifدسترسی پیدا کرد.
متغیر محلی برای test
این متغیر ارتباطی با متغیر cدر
تابع mainندارد
در این قسمت نه تنها میتوان به
متغیر محلی cدسترسی داشت،
بلکه متغیر سراسری aنیز قابل
استفاده است .اما به متغیرهای
محلی سایر توابع ،مانند متغیر b
دسترسی نداریم.
; int a
{ )(void main
;int b
; a = 10
;b=5
{ )if (a>0
; int c
;c = 8
….
}
}
{ )(void test
; int c
;c = 5
;a = 20
}
9-5حوزه شناخت متغیر
نکته مهم آنستکه درصورتیکه یک تابع دارای یک متغیر محلی همنام با
یک متغیر سراسری باشد ،در اینصورت هرگونه ارجاع به این نام
مشترک ،به متغیر محلی رجوع خواهد کرد (به مثال قسمت بعدی
رجوع شود).
چه موقع از متغیر های سراسری استفاده کنیم؟
متغیرهای سراسری هنگامی مفید هستند که یک داده بین چندین تابع
بصورت مشترک استفاده شود .در این حالت نیازی به ارسال متغیرهای
مشترک از طریق پارامترها نمی باشد.
اما متاسفانه از آنجا که متغیرهای سراسری در کلیه توابع در دسترس
هستند ،ممکن است بصورت ناخواسته دچار تغییر شوند.
عالوه براین اشکال زدایی آنها نیز بسیار مشکل است ،چرا که محل بروز
خطا مشخص نیست و هریک از توابع ممکن است مقدار متغیر را تغییر
داده باشند.
بنابراین در برنامهنویسی ساختیافته توصیه می گردد تا حد ممکن از
متغیرهای سراسری استفاده نکنید.
9-6ردههای ذخیره سازی
رده ذخیره سازی یک متغیر ،مدت زمان حضور آن را در برنامه تعیین
مینماید .بعبارت دیگر ،رده ذخیره سازی تعیین مینماید یک متغیر چه
موقع بوجود میآید و چه زمانی از بین میرود.
در هنگام تعریف یک متغیر ،باید به همراه نوع آن ،رده ذخیره سازی آن را
نیز مشخص کرد.
چنانچه اینکار صورت نپذیرد ،کامپایلر از رده ذخیره سازی پیش فرض
استفاده خواهد کرد .از آنجایی که در کلیه مثالهایی که تاکنون ذکر
شده اند ،رده ذخیره سازی بطور صریح مشخص نشده بود ،همگی از
رده پیش فرض در نظر گرفته شده اند.
بطور کلی دو رده ذخیره سازی برای متغیرها وجود دارد:
رده ذخیره سازی اتوماتیک
رده ذخیره سازی ایستا
9-6-1رده ذخیره سازی اتوماتیک
متغیرهای متعلق به رده ذخیره سازی اتوماتیک ،هنگام ورود به بلوکی که این متغیرها در
آن اعالن شده اند ،ایجاد شده و در طول اجرای این بلوک در حافظه وجود دارند؛ به محض
خاتمه بلوک ،این متغیرها نیز از بین رفته و حافظه آنها پس گرفته میشود .متغیرهای
محلی (شامل پارامترهای توابع) ،معموال از این رده میباشند .بعنوان مثال ،متغیرهای
محلی یک تابع ،به محض فراخوانی تابع ایجاد میشوند و در حین اجرای تابع در حافظه
حضور دارند .با پایان یافتن اجرای تابع ،این متغیرها نیز از بین میروند .این مسئله باعث
میشود که صرفه جویی قابل توجهی در حافظه داشته باشیم .چرا که هرگاه به متغیری
نیاز داریم ایجاد شده و با پایان یافتن کار نیز حافظه آن آزاد میشود.
برای تعریف یک متغیر اتوماتیک ،باید از کلمه کلیدی ،autoقبل از مشخصه نوع متغیر،
استفاده نماییم .بعنوان مثال به نمونه زیر دقت نمایید :
{ )void test(int a, int b
;auto int i
;float k
…
دستورات تابع //
}
9-6-1رده ذخیره سازی اتوماتیک
نوع دیگری از متغیرهای اتوماتیک نیز وجود دارد .همانطور که میدانید کلیه متغیرهای
برنامه در حافظه اصلی کامپیوتر ) (RAMذخیره میگردند .اما پردازنده اصلی ) (CPUبرای
پردازش دادهها باید ابتدا آنها را از حافظه اصلی به یک حافظه داخلی بنام ثبات یا
Registerمنتقل نماید.
گرچه سرعت دسترسی به حافظه اصلی بسیار باال است ،اما بهرحال مدتی زمان صرف
این انتقال میشود .پس از آن پردازنده میتواند با سرعت بسیار باال عملیات را برروی
ثباتهای داخلی خود انجام داده و حاصل را مجددا به حافظه اصلی منتقل نماید.
حال اگر متغیری داشته باشیم که عملیات زیادی بر روی آن انجام شود ،مانند متغیرهای
شمارنده حلقه ،میتوانیم از کامپایلر بخواهیم که آن را بجای حافظه اصلی ،بطور
مستقیم در داخل یکی از ثباتهای پردازنده ذخیره نماید .با این کار دیگر نیازی به عملیات
انتقال نیست و سرعت بنحو قابل مالحظهای افزایش مییابد.
برای اینکار کافی است که از کلمه کلیدی registerاستفاده نماییم .بعنوان مثال در تابع
قبلی میتوانستیم متغیر iرا بصورت زیر تعریف نماییم:
;register int i
با این وجود دقت کنید که تعداد ثباتهای پردازنده محدود است و کامپایلر فقط درصورتی
این درخواست را قبول مینماید که ثبات خالی وجود داشته باشد؛ در غیراینصورت متغیر
را در حافظه اصلی ذخیره خواهد کرد(اما هیچ خطایی اعالم نخواهد کرد).
همانطور که گفته شد فقط از متغیرهای بسیار پرکاربرد ،بخصوص متغیرهایی که در داخل
یک حلقه استفاده میشوند ،بعنوان متغیر ثبات استفاده نمایید.
9-6-2رده ذخیره سازی ایستا
متغیرهای متعلق به رده ذخیره سازی ایستا ،از ابتدای آغاز برنامه ایجاد میشوند و تا پایان برنامه
نیز در حافظه حضور دارند.
متغیرهای سراسری به این دسته متعلق هستند .با شروع اجرای برنامه به متغیرهای سراسری
حافظه تخصیص داده میشود .پس از آن کلیه توابع قادر به دیدن و تغییر مقدار آنها هستند ،اما
نمیتوانند حافظه تخصیص یافته به این متغیرها را بازپس بگیرند .در پایان و پس از خاتمه تابع ،main
حافظه تخصیص یافته به این متغیرها بازپس گرفته میشود.
اما عالوه بر متغیرهای سراسری ،متغیرهای محلی نیز میتوانند از رده ذخیره سازی ایستا تعریف
شوند.
اگر یک متغیر محلی ،بصورت ایستا تعریف گردد؛ فقط یکبار و آن هم در شروع اجرای برنامه ایجاد
شده و مقدار اولیه خواهد گرفت(البته درصورتیکه به آن مقدار اولیه داده باشیم) .پس از آن ،با هر بار
اجرای تابع ،قادر به دسترسی به این متغیر خواهیم بود(چرا که یک متغیر محلی است) ؛ اما با
خاتمه تابع این متغیر از بین نرفته و مقدار آن تا فراخوانی بعدی تابع حفظ خواهد شد .متغیرهای
محلی ایستا هنگامی از بین میروند که برنامه اصلی خاتمه یابد.
از این متغیرها هنگامی استفاده میشود که بخواهیم مقدار یک متغیر محلی در فراخوانیهای متوالی
یک تابع حفظ شود(البته بدون اینکه سایر توابع به آن دسترسی داشته باشند ،درغیراینصورت آن را
بصورت سراسری تعریف میکردیم).
برای تعریف یک متغیر محلی از رده ذخیره سازی ایستا ،از کلمه کلیدی staticاستفاده میشود.
البته استفاده از این کلمه الزامی است ،چرا که متغیرهای محلی بطور پیش فرض از رده ذخیره
سازی اتوماتیک درنظر گرفته میشوند.
رده ذخیره سازی ایستا9-6-2
#include <stdio.h>
number
2
4
31
5
void computeSum(int number) {
4
autoSum 1
3
0
5
2
int autoSum = 0;
static int staticSum = 0;
15
3
6
1
0
staticSum 10
autoSum += number;
staticSum += number ;
printf("autoSum = %d and staticSum = %d \n",autoSum,staticSum);
}
void main() {
int i;
for (i=1; i<=5; i++)
computeSum(i) ;
}
i
3
4
1
2
5
9-6-2رده ذخیره سازی ایستا
نوع دیگری از رده ذخیره سازی ایستا ،رده externاست .این رده هنگامی بکار می رود که برنامه در
چندین فایل مختلف قرار داشته باشد.
در برنامه های بزرگ که دارای توابع متعددی هستند ،برای جلوگیری از بزرگ و پیچیده شدن بیش از
حد فایل برنامه ،آن را بطور منطقی به چندین فایل تقسیم می کنند .بدین صورت که کلیه توابع و
داده های مرتبط با یکدیگر را در یک فایل قرار می دهند .سپس هر فایل بصورت مجزا کامپایل شده و
درنهایت تمامی آنها با یکدیگر پیوند ) (linkخورده و تشکیل فایل اجرایی نهایی را می دهند .این روش
باعث مدیریت بهتر پروژه های بزرگ می شود.
اما مشکل هنگامی است که بخواهیم یک متغیر سراسری را در توابع موجود در چند فایل مختلف،
مورد استفاده قرار دهیم .مسلما این متغیر سراسری باید در یکی از فایلها تعریف شود .اما در مورد
سایر فایلها چه باید بکنیم؟ متاسفانه هریک از دو راه زیر منجر به شکست می شود:
اگر متغیر را در سایر فایلها بدون تعریف مجدد استفاده نماییم ،کامپایلر اعالم خطا کرده و متغیر را نخواهد
شناخت.
اگر متغیر را در سایر فایلها نیز تعریف مجدد نماییم ،کامپایلر اعالم خطا نخواهد کرد ،اما پیوند زننده ) (linkerدر
هنگام ترکیب فایلها با هم متوجه تعریف چند متغیر با نام یکسان شده و اعالم خطا خواهد کرد.
تنها راه حل آن است که این متغیر را در یک فایل تعریف کرده و سپس در سایر فایلها آن را بعنوان یک
متغیر خارجی ) (externاعالن (و نه تعریف) نماییم.
مشخصه externبه کامپایلر اعالن می کند که این متغیر در جای دیگری (معموال یک فایل دیگر)
تعریف شده است و بنابراین می تواند بدون اعالن خطا از آن استفاده کند؛ اما این مشخصه باعث
تعریف مجدد متغیر نمی گردد .برای تعریف متغیر به شکل خارجی بصورت زیر عمل می کنیم:
;extern int a
9-6-2رده ذخیره سازی ایستا
File2.C
File1.C
;extern int k
;int k = 0
{ )(void F2
…
دسترسی به متغیر سراسری مشترک k -- ; //
…
}
{ )(void F1
….
دسترسی به متغیر سراسری k ++; // k
…
}
{ )(void F3
…
}
{ )(void main
….
}
رده ذخیره سازی ایستا9-6-2
#include <stdio.h>
int x = 1; // global variable x
void a() {
int x = 10 ; // local automatic variable x
printf("x in function a is %d \n",x);
x ++;
printf("x in function a is %d \n",x);
}
void b() {
static int x = 20 ; // local static variable x
printf("x in function b is %d \n",x);
x ++;
printf("x in function b is %d \n",x);
}
void c() {
printf("x in function c is %d \n",x);
x ++;
printf("x in function c is %d \n",x);
}
void main() {
printf("x in function main is %d \n",x);
a() ;
b() ;
c() ;
printf("x in function main is %d \n",x);
a() ;
b() ;
c() ;
printf("x in function main is %d \n",x);
}
x
x
x
x
x
x
x
x
x
x
x
x
x
x
x
in
in
in
in
in
in
in
in
in
in
in
in
in
in
in
function main is 1
function a is 10
function a is 11
function b is 20
function b is 21
function c is 1
function c is 2
function main is 2
function a is 10
function a is 11
function b is 21
function b is 22
function c is 2
function c is 3
function main is 3
9-7مقادیر پیش فرض برای پارامترها
در بسیاری از موارد ،توابعی داریم که دارای تعداد زیادی پارامتر هستند که در هربار
فراخوانی باید آرگومانهای متناظر با هریک را به تابع ارسال کرد .چنانچه تعداد آرگومانهای
ارسالی ،با تعداد پارامترها یکسان نباشد ،یک خطای کامپایل ایجاد می گردد.
اما در بعضی موارد ،مقادیر بعضی از این پارامترها در اکثر موارد مشخص است و فقط در
شرایط خاص تغییر می کند.
بعنوان مثال فرض کنید تابعی نوشته اید که یک پنجره را مکان موردنظر ترسیم میکند.
پارامترهای متداول برای چنین تابعی عبارتند از:
مختصات شروع ،رنگ زمینه ،رنگ متن ،نوع حاشیه (یک خطی ،دوخطی و )...
اما فرض کنیم پنجره های متداول در برنامه ما دارای رنگ زمینه آبی و رنگ متن سفید با حاشیه
دوخطی هستند .در اینصورت در اکثر موارد بجز اطالعات مربوط به مختصات پنجره ،بقیه اطالعات
بصورت تکراری ارسال می گردند.
در چنین مواردی می توان از پارامترهای پیش فرض استفاده نمود.
چنانچه یک پارامتر از تابع دارای مقدار پیش فرض باشد ،آنگاه تابع فراخواننده می تواند
هیچ آرگومانی متناظر با این پارامتر ارسال ننماید .در اینصورت تابع فراخوانده شده از
مقدار پیش فرض برای آن پارامتر استفاده می نماید.
برای تعیین مقدار پیش فرض برای پارامتر ،کافی است که در هنگام تعریف پارامتر با
استفاده از عملگر نسبت دهی )=( مقدار پیش فرض را به پارامتر نسبت دهیم.
مقادیر پیش فرض برای پارامترها9-7
#include <stdio.h>
int sum(int a, int b=0, int c=0) {
return(a+b+c);
}
a
b 10
0
c 15
0
void main() {
printf("sum(5,10,20) = %d \n", sum(5,10,20) );
printf("sum(5,10) = %d \n", sum(5,10) );
printf("sum(5) = %d \n", sum(5) );
}
sum(5,10,20) = 35
sum(5,10) = 15
sum(5) = 5
5
9-7مقادیر پیش فرض برای پارامترها
البته در نحوه استفاده از پارامترهای پیش فرض دو محدودیت وجود دارد:
تعریف پارامترهای پیش فرض حتما باید از سمت راست ترین پارامتر آغاز
شده و به سمت چپ ادامه یابد.
بعنوان نمونه در مثال قبل چنانچه پارامتر cمقدار پیش فرض نداشته باشد،
پارامتر bنیز قادر به اعالم مقدار پیش فرض نیست .عالوه براین پارامتر aنیز
فقط درصورتی می تواند مقدار پیش فرض داشته باشد که پارامترهای bو c
هر دو دارای مقدار پیش فرض باشند .یعنی اعالن زیر خطا می باشد:
int sum(int a=0, int b, int c=0) // compile error
در هنگام فراخوانی یک تابع که دارای پارامترهای پیش فرض است،
آرگومانهای ارسالی از چپ به راست به پارامترها اختصاص می یابند.
یعنی در صورتیکه هنگام فراخوانی تابع sumدو آرگومان به آن ارسال
شود ،اولی به aو دومی به bتخصیص خواهد یافت .تحت هیچ
شرایطی نمی توان بدون اینکه مقداری برای bارسال شود ،آرگومانی را
برای cارسال نمود.
9-8سربارگذاری توابع
در برخی موارد ،تابعی داریم که یک وظیفه خاص را برای چندین نوع داده مختلف انجام
می دهد.
بعنوان مثال تابع maxرا درنظر بگیرید که دو داده را بعنوان ورودی دریافت و حداکثر آنها را
باز می گرداند .چنانچه این تابع را برای دو ورودی از نوع عدد صحیح بنویسیم ،آنگاه برای
ورودی های اعشاری درست عمل نخواهد کرد .بنابراین ممکن است برنامهنویس ترجیح
دهد دو نسخه از این تابع داشته باشد :یکی برای اعداد صحیح و یکی برای اعداد
اعشاری.
اما از آنجا که نمی توان دو شناسه همنام تعریف کرد ،بنابراین ممکن است مجبور شویم
از دو نام مجزا استفاده نماییم ،مانند intMaxبرای اعداد صحیح و doubleMaxبرای اعداد
اعشاری.
خوشبختانه با استفاده از سربار گذاری توابع ،می توان این مشکل را حل کرد .با استفاده
از سربار گذاری ،می توان توابعی تعریف کرد که دارای نام یکسان باشند ،ولی لیست
پارامترهای ورودی آنها متفاوت باشد.
در اینصورت در هنگام فراخوانی تابع ،زبان Cبا توجه به نوع آرگومانهای ارسالی ،تابع
مناسب را انتخاب کرده و فراخوانی می نماید .تفاوت در لیست پارامترهای ورودی توابع
سربارگذاری شده ،می تواند شامل یک یا هر دو مورد زیر باشد:
تفاوت در تعداد پارامترها
تفاوت در نوع داده یک یا چند پارامتر
نکته آخر این که سربار گذاری نمی تواند بر اساس نوع داده خروجی انجام شود.
سربارگذاری توابع9-8
int max(int a, int b) {
if (a > b) return(a);
else return(b);
}
double max(double a, double b) {
if (a > b) return(a);
else return(b);
}
int max(int a, int b, int c) {
if (a > b)
if (a > c) return(a);
else return(c);
else
if (b > c) return(b);
else return(c);
}
void main() {
printf("max(8, 10) = %d \n", max(8, 10) );
printf("max(4.23, 3.712) = %f \n", max(4.23, 3.712) );
printf("max(15, 3, 20) = %d \n", max(15, 3, 20) );
}
max(8, 10) = 10
max(4.23, 3.712) = 4.23
max(15, 3, 20) = 20
9-9الگوهای تابعی
الگوهای تابعی ،در حقیقت شکل پیشرفته تری از سربارگذاری هستند.
اگر به تابع maxدر قسمت قبل دقت کنید ،این تابع یک بار برای نوع داده intو یک بار نیز
برای نوع داده doubleنوشته شده است ،اما الگوریتم هر دو تابع یکسان است .در چنین
مواردی می توان بجای نوشتن چندین تابع سربارگذاری شده با الگوریتم یکسان ،تنها یک
الگوی تابعی نوشت و ساخت توابع با نوع داده های مختلف را بعهده کامپایلر نهاد.
برای روشن شدن موضوع ،بحث را با یک مثال ادامه می دهیم.
تابع swapرا در نظر بگیرید که یک دو متغیر را دریافت و مقادیر آنها را جابجا می کند .این تابع
دارای کاربردهای متعددی بوده و ممکن است با نوع داده های مختلفی مورد استفاده قرار گیرد.
یک روش سربارگذاری تابع swapبرای انواع داده های مورد نیاز است .اما روش بهتر استفاده از
یک الگو است.
تعریف الگو با استفاده از کلمه کلیدی templateصورت می پذیرد و پس از آن لیست
پارامترهای الگو در داخل عالمت >< قرار می گیرند.
پارامترهای الگو ،مشابه پارامترهای تابع هستند ،با این تفاوت که بجای اینکه حاوی
مقدار باشند ،حاوی نوع داده هستند .در هنگام استفاده از یک الگو ،نوع داده مورد نظر
از طریق این پارامترها به الگو ارسال می شود.
برای تعریف هر پارامتر الگو ،از کلمه کلیدی classبعالوه یک شناسه که نماینده نوع داده
است ،استفاده می شود .در اینجا classبه معنای هر نوع داده دلخواه است.
پس از کلمه کلیدی templateو لیست پارامترها ،تعریف تابع مربوط به الگو شروع می
شود که همانند سایر توابع است؛ با این تفاوت که می توان از پارامترهای الگو ،همانند
نوع داده های عادی در تعریف متغیرهای تابع استفاده نمود.
الگوهای تابعی9-9
template <class T>
void swap(T &a, T &b) {
T temp;
temp = a;
a = b;
b = temp ;
}
void main() {
int a=5 , b=10;
double c = 1.1 , d = 2.2 ;
char e = 'a' , f = 'b' ;
swap(a,b);
swap(c,d);
swap(e,f) ;
printf("a = %d and b = %d \n", a, b);
printf("c = %f and d = %f \n", c, d);
printf("e = %c and f = %c \n", e, f);
}
swap(double
swap(char
&a,
&a,
char
double
&b)
void swap(int
&a,
int
&b)
{ { &b) {
double
char
temp;
temp;
int
temp;
temp = a;
a = b;
b = temp ;
}
9-10توابع درون برنامه ای
فراخوانی یک تابع دارای سربار زمانی و حافظه ای می باشد .با فراخوانی هر
تابع باید برای متغیرهای محلی و پارامترهای آن حافظه تخصیص داده شود،
آرگومانهای ارسالی در پارامترهای تابع کپی شوند و ...که همگی این موارد
باعث صرف زمان و حافظه می شوند.
به همین دلیل برای انجام عملیات کوتاه ،استفاده از تابع ایده چندان خوبی
نیست؛ چراکه گرچه خوانایی برنامه باال می رود ،اما برای انجام یک کار کوچک،
هزینه زیادی به سیستم تحمیل می گردد.
برای رفع این مشکل ،زبان Cاز توابع درون برنامه ای یا inlineاستفاده می کند.
اگر قبل از شروع تعریف تابع (یعنی قبل از نوع مقدار بازگشتی) از کلمه کلیدی
inlineاستفاده شود ،به کامپایلر توصیه می شود که هر فراخوانی تابع مورد
نظر را با یک کپی صریح از کد تابع جایگزین نماید ،تا دیگر نیازی به صرف زمان
برای فراخوانی تابع نباشد.
البته استفاده از ،inlineگرچه باعث باال رفتن سرعت اجرای برنامه می شود،
اما حجم آن را باال می برد؛ چرا که بجای هر فراخوانی تابع ،یک کپی از آن را
قرار می دهد.
در نتیجه استفاده از این مشخصه ،فقط در توابع کوچک قابل قبول است .در
حقیقت Cاز این مشخصه برای توابع بزرگ صرفنظر می کند.
توابع درون برنامه ای9-10
inline int square(int n) {
return (n*n) ;
}
void main() {
int a, b ;
کامپایلر
void main() {
int a, b ;
a = (5) * (5) ;
b = (2*a+3) * (2*a+3) ;
a = square(5) ;
b = square(2*a+3) ;
}
}
9-11توابع بازگشتی
یکی از مهمترین مفاهیم علم کامپیوتر ،توابع بازگشتی هستند که کاربرد بسیار زیادی در حل
مسائل دارند.
توابع بازگشتی ،توابعی هستند که خود را مجددا فراخوانی می کنند .این توابع به دو دسته تقسیم
می شوند:
توابع بازگشتی مستقیم :تابعی مانند ،Aخودش را فراخوانی نماید.
توابع بازگشتی غیر مستقیم :تابعی مانند ،Aتابع دیگری مانند Bرا فراخوانی نماید و سپس Bمجددا تابع A
را فراخوانی نماید.
اما چرا یک تابع باید خودش را فراخوانی نماید؟
در بعضی مسائل (که عموما مسائل پیچیده ای نیز هستند) ،می توان یک نمونه از مسئله را با
استفاده از نمونه ساده تر یا کوچکتری از همان مسئله حل کرد.
بنابراین ،تابع می تواند برای حل مسئله بزرگتر ،نسخه جدیدی از خودش را برای حل مسئله کوچکتر
فراخوانی نماید و سپس با استفاده از نتایج بدست آمده ،مسئله بزرگ را حل نماید.
مسلم است که تابعی که برای حل مسئله کوچک فراخوانی شده است نیز از همین مکانیزم
استفاده کرده و مجددا خودش را برای حل یک مسئله کوچکتر فراخوانی خواهد کرد.
اما این فراخوانیها تا چه زمانی ادامه پیدا می کند؟
هر مسئله بازگشتی دارای یک حالت پایه است که حل آن بدیهی بوده و نیازی به حل یک مسئله
کوچکتر ندارد .بنابراین تابع بازگشتی حتما باید دارای یک شرط برای بررسی حالت پایه باشد ،که به
آن شرط توقف گفته می شود.
هنگامی که تابع به حالت پایه می رسد ،فراخوانی بازگشتی را متوقف کرده و حل بدیهی مسئله را
برای فراخوانی قبلی تابع باز می گرداند .سپس این بازگرداندن مقادیر رو به عقب تکرار می شود تا
هنگامی که به نسخه اولیه تابع برسد و در نهایت جواب نهایی به تابع اصلی بازگردانده شود.
9-11توابع بازگشتی
اکنون برای روشن شدن موضوع به بررسی یک مثال کالسیک در زمینه توابع
بازگشتی ،یعنی محاسبه فاکتوریال می پردازیم.
قبال نحوه محاسبه فاکتوریال یک عدد را بصورت زیر بیان کردیم :
n! = 1 × 2 × … × n
تابع مربوط به فاکتوریال نیز با استفاده از یک حلقه تکرار که اعداد 1تا nرا در یکدیگر
ضرب می کرد ،نوشته شد.
اما اگر به تعریف فاکتوریال دقت کنیم ،درمی یابیم که فاکتوریال عددی مانند 5را می
توان از ضرب عدد 5در فاکتوریال 4بدست آورد .بعبارت بهتر
!5! = 5 × 4
استدالل مشابهی برای ! 4برقرار است .بطور کلی می توان گفت:
n0
!)n (n 1
n!
n0
1
همانطور که دیده می شود ،حالت پایه در این مسئله ،حالت n=0است؛ چرا که در
این حالت حل مسئله بدیهی بوده و جواب برابر 1است.
9-11توابع بازگشتی
تابع بازگشتی فاکتوریال بصورت زیر نوشته می شود:
{ )long int factorial(int n
; )if (n ==0) return(1
;))else return(n * factorial(n-1
}
همانطور که دیده می شود ،توابع باز گشتی بسیار ساده و کوچک می باشند،
اما درک نحوه کار آنها کمی مشکل است.
قبل از توضیح نحوه کار این تابع ،توجه کنید که هنگامی که یک تابع خودش را
فراخوانی می کند ،یک نسخه جدید از آن تابع بوجود می آید .بدین معنا که یک
نسخه جدید از کلیه پارامترها و متغیرهای محلی تابع ایجاد می شود و عملیات
برروی این متغیرهای جدید انجام می شود(بدون اینکه تغییری در متغیرهای
فراخوانی قبلی ایجاد شود).
البته برای صرفه جویی در حافظه ،نسخه جدیدی از دستورات برنامه ایجاد نمی
شود.
توابع بازگشتی9-11
long int factorial (4) {
if (4 ==0) return(1) ;
else return(4 * factorial(3));
}
6
long int factorial (3) {
if (3 ==0) return(1) ;
else return(3 * factorial(2));
}
2
long int factorial (2) {
if (2 ==0) return(1) ;
else return(2 * factorial(1));
}
factorial(4) = ?
24
1
long int factorial (0) {
if (0 ==0) return(1) ;
else return(0 * factorial(-1));
}
1
long int factorial (1) {
if (1 ==0) return(1) ;
else return(1 * factorial(0));
}
توابع بازگشتی9-11
factorial(4)
n=4
return (24)
factorial(3)
n=3
return (6)
factorial(2)
n=2
return (2)
factorial(1)
n=1
return (1)
factorial(0)
n=0
return (1)
توابع بازگشتی9-11
. را محاسبه نمایدk بهn ) تابعی بنویسید که ترکیب7 مثال
k 0 or n k
1
n
n 1 n 1
otherwise
k
k
1
k
int combination(int n, int k) {
if (k==0 || n==k) return(1);
else return( combination(n-1,k-1) + combination(n-1,k) ) ;
}
9-11توابع بازگشتی
مثال )8تابعی بنویسید که nامین عدد فیبوناچی را باز گرداند.
دنباله فیبو ناچی بصورت زیر تعریف می شود:
… 1 1 2 3 5 8 13 21
{ )int fibonacci(int n
;)if (n==1 || n==2) return(1
;) )else return( fibonacci(n-1) + fibonacci(n-2
}
9-11توابع بازگشتی
مثال )9تابعی بنویسید که دو عدد را دریافت و بزرگترین مقسوم علیه
مشترک آن دو را بازگرداند.
y0
x
gcd ( x, y)
gcd ( y , x % y) y 0
{ )int gcd(int x, int y
;)if (y == 0) return(x
;) )else return( gcd(y, x%y
}
9-11-1مقایسه توابع بازگشتی و تکراری
همانطور که قبال دیده شد ،الگوریتم فاکتوریال را می توان به دوصورت تکراری (با حلقه
تکرار) و بازگشتی نوشت .آیا این مسئله برای سایر الگوریتمهای بازگشتی نیز برقرار
است؟
جواب مثبت است .هر الگوریتم بازگشتی را می توان بصورت غیر بازگشتی یا تکراری نیز
نوشت ،گرچه ممکن است راه حل آن پیچیده تر و وضوح آن نیز کمتر باشد.
اما کدام روش بهتر است؟
با توجه به این نکات منفی ،چرا از توابع بازگشتی استفاده کنیم؟
در الگوریتمهای بازگشتی ،با هربار فراخوانی تابع یک نسخه جدید از متغیرها ایجاد می شود .به
همین دلیل معموال الگوریتمهای بازگشتی حافظه بیشتری را نسبت به الگوریتمهای تکراری
مصرف می نمایند.
عالوه براین الگوریتمهای بازگشتی معموال از نظر زمان اجرا نیز کندتر هستند .دلیل این مسئله
سربار ناشی از فراخوانی مکرر تابع است.
تنها هنگامی از توابع بازگشتی استفاده نمایید که طبیعت مساله بگونه ای باشد که توسط
روشهای بازگشتی راحتتر حل شود و الگوریتم حاصل ساده تر و واضحتر باشد.
در بسیاری از مسائل ،روش بازگشتی بسیار ساده و آسان بوده و درک و اشکالزدایی آن بسیار
ساده است ،درحالیکه راه حل غیربازگشتی به راحتی به ذهن نمی رسد و یا بسیار پیچیده
است.
اما در مواردی که کارایی اهمیت زیادی دارد ،هرگز از بازگشت استفاده ننمایید.
مبانی کامپیوتر و برنامه سازی
فصلدهم :آرایهها
مدرس :رضارمضانی
10آرایه ها
در مبحث الگوریتمها با یک ساختمان داده مهم ،یعنی
آرایهها آشنا شدیم.
البته در Cساختمان داده های دیگر ی همچون ساختارها
نیز وجود دارند ،اما آرایهها همچنین مهمترین ساختمان
داده موجود در این زبان هستند.
آرایه در Cعبارتست از مجموعه ای از داده های همنوع
که تحت یک نام مشترک و در خانه های متوالی حافظه
ذخیره می گردند.
برای دسترسی به عناصر آرایه ،باید از نام آرایه بعالوه
اندیس استفاده کرد.
در قسمتهای بعدی ،نحوه تعریف و استفاده از آرایهها را
تشریح خواهیم کرد.
10-1آرایه های یک بعدی
پیش از آنکه بتوان از یک آرایه یک بعدی استفاده کرد ،باید آن را اعالن
کرد.
اعالن آرایهها بصورت زیر انجام می گردد:
; ]><type> <var-name>[<size
بعنوان مثال:
;]int A[10
خط باال یک آرایه 10تایی از اعداد صحیح بنام Aایجاد می نماید.
هر کدام از عناصر این آرایه می توانند بعنوان یک متغیر مستقل مورد
استفاده قرار گیرد.
برای دسترسی به عناصر این آرایه باید از اندیس استفاده نمود .در زبان
Cاندیسها در داخل کروشه ][ قرار می گیرند.
نکته بسیار مهمی که باید بدان توجه کرد آنستکه در Cاندیس یک عدد
صحیح است که از 0آغاز می گردد.
10-1آرایه های یک بعدی
; ]int A[10
8
]A[9
]A[0] A[1] A[2] A[3
; A[2] = 8
10-1آرایه های یک بعدی
مثال )1برنامه ای بنویسید که شماره دانشجویی و معدل تعدادی دانشجو را دریافت ،و سپس
چنانچه معدل دانشجو از میانگین کالس :
بیش از یک نمره بیشتر باشد ،چاپ کند :عالی
حداکثر یک نمره بیشتر یا کمتر باشد ،چاپ کند :خوب
بیش از یک نمره کمتر باشد ،چاپ کند :ضعیف
>#include <stdio.h
{ )(void main
; ]float average[100
آرایه نگهداری معدل دانشجویان//
; ]long int id[100
آرایه نگهداری شماره دانشجویان//
میانگین کل دانشجویانfloat totalAverage; //
; int i, n
;)" printf("enter number of students :
;)scanf("%d", &n
دریافت تعداد دانشجویان//
آرایه های یک بعدی10-1
//حلقه دریافت مشخصات دانشجویان و محاسبه میانگین معدلها
for (i = 0; i < n ; i ++) {
printf("enter id and average : ");
scanf("%ld %f", &id[i], &average[i]);
totalAverage += average[i];
}
totalAverage /= n;
//حلقه مقایسه معدل هر دانشجو با میانگین کالس و چاپ پیام مناسب
for (i = 0; i < n ; i ++) {
if (average[i] >= totalAverage + 1)
printf("%ld : excellent !\n", id[i]);
else if (average[i] >= totalAverage – 1)
printf("%ld : good !\n", id[i]);
else printf("%ld : weak !\n", id[i]);
}
}
10-1آرایه های یک بعدی
چند نکته مهم راجع به آرایه در Cوجود دارد که حتما باید به آنها دقت کنید:
اندازه آرایهها در Cثابت بوده و حتما باید توسط یک مقدار ثابت صحیح تعیین گردد .بعنوان مثال
اعالن زیر خطای نحوی محسوب می گردد:
; int n
; n=100
;]int A[n
اما می توان با استفاده از متغیر های ثابت (ثابتهای دارای نام) ،اندازه آرایه را تعیین کرد ،که در
قسمتهای بعدی به آن اشاره خواهد شد.
اندیس آرایهها در Cعدد صحیح بوده و همیشه از 0شروع می شود .لذا به تفاوت "عنصر چهار
آرایه" یعنی ] A[4و "چهارمین عنصر آرایه" یعنی ] A[3دقت کنید .این مسئله معموال باعث بروز
خطاهای منطقی می گردد.
در Cمرز آرایهها بررسی نمی گردد .بدین معنا که چنانچه اندیسی خارج از محدوده مجاز یک
آرایه استفاده شود ،باعث ایجاد خطا توسط کامپایلر نمی گردد ،اما مسلما برنامه را دچار یک
خطای منطقی خواهد کرد .بعنوان مثال:
; ]int A[10
A[12] = 20 ; //this is not a syntax error but a logical error
لذا بررسی مرزهای آرایه بعهده خود برنامهنویس است و باید از درستی برنامه خود و خارج
نشدن از محدوده مجاز مطمئن گردد.
10-1آرایه های یک بعدی
مقداردهی اولیه به آرایه های یک بعدی بصورت زیر انجام می پذیرد:
;}int A[3] = {5, 2, 8
که در اینجا ] A[0برابر A[1] ، 5برابر 2و ] A[2برابر 8خواهد شد.
عالوه براین می توان فقط به تعدادی از عناصر آرایه مقدار داد ،دراینصورت مقدار
عناصر باقیمانده آرایه اتوماتیک 0خواهد شد.
; }int B[10] = {5, 8
در اینجا عناصر ] B[2به بعد مقدار 0خواهند گرفت .بنابراین می توان برای 0کردن
کلیه عناصر یک آرایه به شکل زیر عمل کرد :
;}int C[10] = {0
چنانچه به آرایه مقدار دهی اولیه کرده باشیم ،می توان تعداد عناصر آرایه را نیز
ذکر نکرد ،دراینصورت اندازه آرایه بطور اتوماتیک برابر تعداد مقادیر مشخص شده
خواهد شد.
;}int C[] = {10, 15, 20
در مثال فوق آرایه Cبا 3عضو درنظر گرفته می شود.
10-2متغیرهای ثابت
همانطور که در قسمت قبل گفته شد ،گرچه اندازه یک آرایه باید ثابت صحیح
باشد؛ اما می توان از متغیرهای ثابت نیز استفاده کرد.
یک متغیر ثابت ،متغیری است که فقط می تواند در هنگام اعالن مقدار اولیه
بگیرد و این مقدار دیگر قابل تغییر نیست .برای اعالن متغیرهای ثابت ،از کلمه
کلیدی constقبل از نوع متغیر استفاده می گردد .بعنوان مثال:
;const int k = 10
اکنون هرگونه تالش برای تغییر مقدار ،kباعث ایجاد یک خطای نحوی توسط
کامپایلر خواهد شد .به این نوع متغیرها ،ثابتهای نام دار نیز گفته می شود.
این متغیرها در تعریف مقادیر ثابتی که مقدار آنها در طول برنامه تغییر نمیکند،
بکار میروند .بعنوان مثال :
;const float pi = 3.14
این کار نه تنها خوانایی برنامه را باال میبرد (بدلیل استفاده از کلمه piکه برای
همه شناخته شده است) ،بلکه باعث میشود تغییر پذیری برنامه نیز باال برود.
بدین معنا که در صورتیکه برنامهنویس تصمیم گرفت مقدار ثابت را عوض کند،
نیازی به تغییر کل برنامه نیست و فقط کافی است مقدار اولیه متغیر را عوض
نماید.
10-2متغیرهای ثابت
از این مسئله میتوان در تعریف آرایهها نیز استفاده کرد.
بدین صورت که بجای آنکه اندازه آرایه را با یک ثابت صحیح
مشخص نماییم ،آن را با یک متغیر ثابت تعریف میکنیم.
با اینکار ،درصورتیکه نیازی به تغییر اندازه آرایه (یا آرایه ها)
گردد ،فقط کافی است مقدار اولیه متغیر ثابت خود را تغییر
دهیم.
مثال )2برنامه ای بنویسید که سال ورود تعدادی دانشجو را
دریافت و سپس تعداد ورودی های سالهای 75تا 84را
محاسبه و چاپ نماید.
متغیرهای ثابت10-2
#include <stdio.h>
void main() {
const int startYear = 75;
const int yearNo = 10;
int count[yearNo] = {0};
int i, n, year;
printf("enter student no :");
scanf("%d",&n);
for (i=0; i<n ; i++) {
printf("enter entrance year :");
scanf("%d",&year);
count [year – startYear] ++;
}
for (i=0 ; i<yearNo ; i++)
printf("year = %d count = %d \n",startYear + i , count[i]);
}
10-3آرایههای چندبعدی
همانطور که در بخش الگوریتمها گفته شد ،آرایهها می توانند
دارای ابعاد بیشتری نیز باشند.
در زبان Cنیز می توان یک آرایه چند بعدی را بصورت زیر اعالن
کرد:
; ]><type> <var-name>[<size 1>][<size 2>] … [<size n
بعنوان مثال ،اعالن زیر یک آرایه دوبعدی را معرفی می نماید:
; ]int A[5][8
برای دسترسی به هر عنصر از این آرایه باید از دو عالمت ][
استفاده کرد .توجه کنید که اندیس سطرها و ستونها هر دو از
0آغاز می گردند.
10-3آرایههای چندبعدی
; ]int A[5][8
ستون
7
6
5
4
3
2
1
0
0
]A[1][6
1
24
2
3
4
سطر
]A[2][3
; A[3][1] = 24
10-3آرایههای چندبعدی
البته در مورد آرایه های با ابعاد باالتر نیز به شکل مشابهی عمل می
گردد.
بعنوان مثال به نحوه استفاده از یک آرایه سه بعدی در مثال زیر دقت
کنید:
; ]int B[5][8][6
;B[2][4][0] = 12
مثال )3یکی از اساتید قصد دارد آماری از نمرات دانشجویان خود در
کوییزهای بین ترم تهیه نماید .وی 5کوییز در طول ترم برگذار نموده
است و اکنون نیاز به اطالعات زیر دارد:
میانگین نمرات هر دانشجو برای کل کوییزها
میانگین نمرات کل دانشجویان برای هر کوییز
برنامه ای بنویسید که نمرات دانشجویان را برای 5کوییز دریافت و
اطالعات خواسته شده را چاپ نماید.
10-3آرایههای چندبعدی
کوییز کوییز کوییز
پنجم چهارم سوم
کوییز
دوم
کوییز
اول
دانشجوی اول
دانشجوی دوم
دانشجوی سوم
...
آرایههای چندبعدی10-3
#include <stdio.h>
const int maxStudent = 100;
const int quizNo = 5;
//حداکثر تعداد دانشجویان
//تعداد کوئیزها
void main() {
float grades[maxStudent][quizNo] ;
//آرایه نگهداری نمرات دانشجویان
float quizAverage,
//میانگین نمرات هر کوئیز
studentAverage;
//میانگین نمرات هر دانشجو
int i, j, n;
printf("enter number of students: ");
scanf("%d",&n);
//دریافت تعداد دانشجویان
//حلقه دریافت اطالعات دانشجویان
for (i=0 ; i<n ; i++) {
printf("student no %d:\n",i+1);
for (j=0 ; j<quizNo ; j++) {
printf("enter grade of quiz %d: ", j+1);
scanf("%f", &grades[i][j]);
}
}
آرایههای چندبعدی10-3
//حلقههای متداخل برای محاسبه میانگین نمرات هر دانشجو
printf("Student's averages:\n");
for (i=0 ; i<n ; i++) {
studentAverage = 0.0;
for (j=0; j<quizNo; j++)
studentAverage += grades[i][j];
studentAverage /= quizNo ;
printf("student no %d: average=%f \n",i+1, studentAverage);
}
//حلقههای متداخل برای محاسبه میانگین نمرات هر کوئیز
printf("Average of each quiz:\n");
for (j=0 ; j<quizNo ; j++) {
quizAverage = 0.0;
for (i=0; i<n; i++)
quizAverage += grades[i][j];
quizAverage /= n ;
printf("quiz no %d: average=%f \n",j+1, quizAverage);
}
}
10-3آرایههای چندبعدی
و نکته آخر اینکه مقداردهی اولیه به آرایه های چندبعدی
امکان پذیر است و بصورت زیر انجام می پذیرد:
;} }int A[3][4] = { {12, 5, 3, 8} , {-3, 7, -9, 2}, {4, 22, 18, 6
یعنی یک عالمت }{ برای کل مقداردهی قرار می گیرد،
سپس هر ردیف از آرایه در داخل یک }{ مجزا قرار می
گیرد.
برای ابعاد باالتر نیز به روش مشابهی عمل می گردد.
بعنوان مثال برای آرایه های سه بعدی داریم:
int A[2][3][4] = { { {12, 5, 3, 8} , {-3, 7, -9, 2}, {4, 22, 18, 6} } ,
;} } }{ {8, 1, -3, 4} , {-2, 8, 11, 21} , {7, 3, -15, -8
10-4ارسال آرایههای یک بعدی به توابع
آرایهها را نیز همچون سایر نوع دادهها میتوان به یک تابع ارسال کرد.
برای اینکار ابتدا باید تابع را بگونهای تعریف کنیم که یک پارامتر از نوع
آرایه را دریافت کند.
فرض کنید تابعی بنام sumArrayداریم که یک آرایه یک بعدی از اعداد
صحیح را بعنوان ورودی دریافت مینماید و مجموع عناصر آن را باز
میگرداند.
تعریف این تابع بصورت زیر است:
{ )int sumArray(int A[], int size
;int i , sum = 0
)for (i=0; i<size; i++
;]sum += A[i
; )return(sum
}
10-4ارسال آرایههای یک بعدی به توابع
همانطور که میبینید ،اندازه آرایه مشخص نشده است و این یک نکته مثبت است؛ چرا
که تابع sumArrayمیتواند هر آرایه صحیحی را با هر اندازهای دریافت نماید.
درواقع حتی اگر اندازه آرایه را نیز مشخص نمایید ،کامپایلر از آن صرفنظر خواهد کرد.
دومین پارامتر ،اندازه واقعی آرایه Aرا مشخص مینماید .معموال توابع بگونهای نوشته
میشوند که هنگام ارسال یک آرایه به یک تابع ،اندازه آن نیز بعنوان یک پارامتر ارسال
گردد.
در هنگام فراخوانی تابع ،sumArrayبرای ارسال آرایه موردنظر کافی است که تنها نام
آرایه را بدون کروشه استفاده نماییم.
{ )(void main
;}int data1[3] = {5, 10, 15
; }int data2[5] = {1, 6, 4, 12, 5
;int sum1, sum2
;)sum1 = sumArray(data1, 3
;)sum2 = sumArray(data2, 5
;)printf("sum1 = %d\n",sum1
;)printf("sum2 = %d\n",sum2
}
10-4ارسال آرایههای یک بعدی به توابع
نکته بسیار مهم ،نحوه ارسال آرایهها به توابع است.
زبان Cآرایهها را توسط ارجاع به تابع ارسال مینماید ،بدین معنا که در هنگام ارسال
یک آرایه به تابع ،بجای یک کپی از آرایه ،خود آرایه ارسال میشود.
در حقیقت در فصلهای بعدی خواهید دید که برای ارسال یک آرایه ،آدرس اولین عنصر آن
ارسال میگردد .لذا تابع میتواند از طریق این آدرس ،به کلیه دادههای آرایه اصلی
دسترسی پیدا کند.
اما چرا Cدر مورد آرایهها به روش متفاوتی عمل مینماید؟
اما آیا میتوان یک آرایه را توسط مقدار به یک تابع ارسال کرد؟
دلیل این مسئله آن است که معموال یک آرایه حافظه بسیار زیادی را اشغال میکند ،لذا تهیه
یک کپی کردن از آن ،نه تنها باعث اشغال حافظه میشود بلکه زمان زیادی را نیز صرف خواهد
کرد.
متاسفانه خیر.
اما اگر نگران تغییر سهوی آرایه ارسالی به یک تابع هستید میتوانید آن را بگونهای به
تابع ارسال نمایید که تغییر آن در تابع ممکن نباشد.
زبان Cیک نحوه دیگر ارسال دادهها به توابع بنام ارسال توسط ارجاع ثابت میباشد.
چنانچه در هنگام تعریف یک پارامتر از یک تابع ،از کلمه کلیدی constاستفاده شود،
کامپایلر اجازه تغییر مقادیر آن پارامتر را در حین اجرای تابع نخواهد داد .با این ارسال
آرایهها بصورت ارجاع ثابت ،میتوانیم مانع از انجام تغییرات ناخواسته در آرایه شویم.
ارسال آرایههای یک بعدی به توابع10-4
. اشتراک دو مجموعه را محاسبه و چاپ نماید،) برنامهای بنویسید که با استفاده از یک تابع4 مثال
void intersection(const int A[], int na, const int B[], int nb, int C[], int &nc) {
int i,k,j,sw;
k = 0;
for (i=0; i<na; i++) {
sw = 1;
for (j=0; j<nb && sw; j++)
if (A[i] == B[j]) {
C[k] = A[i] ;
k ++;
sw = 0;
}
}
nc = k;
}
void printSet(int set[], int size) {
int i;
printf("{ ") ;
for (i=0; i<size; i++)
printf("%d ",set[i]) ;
printf("}\n");
}
ارسال آرایههای یک بعدی به توابع10-4
void
int
int
int
main() {
set1[5] = {5, 8, 3, 12, 20};
set2[3] = {12, 16, 8} ;
result[3] , resultSize ;
intersection(set1, 5, set2, 3, result, resultSize);
printf("set 1 = ");
printSet(set1,5) ;
printf("set 2 = ");
printSet(set2,3) ;
printf("intersection = ");
printSet(result,resultSize) ;
}
set1 = { 5 8 3 12 20 }
set2 = { 12 16 8 }
intersection = { 8 12 }
10-5ارسال آرایههای چندبعدی به توابع
در این قسمت ،ابتدا به نحوه ارسال آرایههای دو بعدی به توابع میپردازیم و
سپس آرایههای با ابعاد باالتر را بررسی خواهیم کرد.
شاید تصور کنید که برای تعریف یک آرایه دوبعدی بعنوان پارامتری از یک تابع،
تنها قرار دادن دو عالمت ][ کافی است و نیازی به ذکر ابعاد آن نیست.
اما متاسفانه اینگونه نیست ،بلکه برنامهنویس باید تعداد ستونهای آرایه دوبعدی را
صریحا مشخص نماید ،اما نیازی به تعیین تعداد ردیفهای آن نیست.
بعنوان مثال فرض کنید تابعی مانند testداریم که بعنوان ورودی یک آرایه دو
بعدی و تعدادی پارامتر دیگر دریافت میکند .تعریف تابع بصورت زیر اشتباه
است:
{ )… void test(int A[][],
تعریف درست ،تعریفی مانند زیر است:
{ )… void test(int A[][10] ,
در هنگام فراخوانی تابع ،testمیتوان هر آرایه دوبعدی 10ستونی را به آن
ارسال کرد .آرایه ارسالی به تابع میتواند 5×10و یا 20×10باشد ،اما
نمیتواند مثال 5×20باشد.
10-5ارسال آرایههای چندبعدی به توابع
مثال )5تابعی بنویسید که میزان فروش تعدادی شرکت در 12ماه سال را بعنوان ورودی دریافت ،و
میانگین فروش شرکتی را که بیشترین میانگین فروش را داشته است ،بازگرداند.
{ )float maxSales(const long int sales[][12], int companyNo
;int i,j
;float average , max
;max = 0.0
{ )for (i=0 ;i<companyNo; i++
;average = 0
)for (j=0; j<12; j++
; ]average += sales[i][j
;average /= 12
)if (average > max
; max = average
}
;)return(max
}
ارسال آرایههای چندبعدی به توابع10-5
.) برنامهای بنویسید که حاصلضرب دو ماتریس را با استفاده از یک تابع محاسبه نماید6 مثال
#include <stdio.h>
const int maxCol = 10;
void multiply(const int A[][maxCol], const int B[][maxCol], int m,int p, int n, int C[][maxCol] ) {
int i,j,k,sum;
for (i=0; i<m; i++)
for (j=0; j<n; j++) {
sum = 0;
for (k=0; k<p; k++)
sum += A[i][k] * B[k][j] ;
C[i][j] = sum;
}
}
void printMatrix(int matrix[][maxCol], int row,int col) {
int i,j;
for (i=0; i<row; i++) {
for (j=0; j<col ;j++)
printf("%d ",matrix[i][j]);
printf("\n");
}
}
ارسال آرایههای چندبعدی به توابع10-5
void main() {
int matrix1[2][maxCol] = { {7 ,3 , 2} , {-2, 6, 1} };
int matrix2[3][maxCol] = { {2 , 7 , -4, -1} , { 3 ,-3, 5, -8} , {6, -7, 2, 3} };
int result[2][maxCol] ;
multiply(matrix1, matrix2, 2, 3, 4, result);
printf("matrix1 is :\n");
printMatrix(matrix1,2,3) ;
printf("\nmatrix2 is :\n");
printMatrix(matrix2,3,4) ;
printf("\nmultiply of matrix1 and matrix2 is :\n");
printMatrix(result,2,4) ;
}
matrix1 is :
7 3 2
-2 6 1
matrix2
2 7 -4
3 -3 5
6 -7 2
is :
-1
-8
3
multiply of matrix1 and matrix 2 is :
35 26 -9 -25
20 -39 40 -43
10-5ارسال آرایههای چندبعدی به توابع
اما ارسال آرایه های با ابعاد باالتر به توابع نیز مشابه آرایه های دوبعدی
است.
به این صورت که در هنگام تعریف یک پارامتر از تابع بعنوان یک آرایه
چندبعدی ،مشخص کردن بعد اول لزومی ندارد ،اما اندازه کلیه ابعاد بعدی
باید حتما مشخص گردد.
بعنوان مثال به تابع زیر دقت کنید :
{ ) … void test(int A[][5][10],
این تابع بعنوان ورودی یک آرایه سه بعدی دریافت می نماید که حتما باید
بعد دوم آن 5و بعد سوم آن 10باشند ،اما اندازه بعد اول هر مقداری می
تواند باشد.
10-6برخی عملیات مهم برروی آرایه ها
همانطور که قبال گفته شد ،آرایهها از ساختمان داده های
بسیار مهم برنامهنویسی بوده و تقریبا می توان گفت هیچ
برنامه ای وجود ندارد که به نحوی از آرایهها استفاده نکند.
به همین دلیل برای انجام بعضی از عملیات مهم بر روی آرایه
ها ،الگوریتمهای کارا و موثری توسط محققین ابداع شده
است ،که چند نمونه از مهمترین آنها را بررسی می نماییم.
10-6-1الگوریتمهای مرتب سازی
شاید مهمترین عملی که برروی یک آرایه یک بعدی انجام
می شود ،مرتب کردن آن بصورت صعودی یا نزولی است.
بدلیل اهمیت این کار ،الگوریتمهای متعددی برای آن ابداع
شده است که برخی از آنها بسیار پیچیده هستند.
در اینجا 3الگوریتم ساده مورد بررسی قرار گرفتهاند که
برای آرایه های کوچک بسیار کارا هستند .اما برای مرتب
سازی آرایه های بزرگ ،باید از روشهای پیچیده تری
استفاده شود که در کتابهای پیشرفته تر مورد بررسی
قرار گرفته اند.
در الگوریتمهای زیر فرض شده است که قصد داریم آرایه را
بصورت صعودی مرتب نماییم ،گرچه با یک تغییر کوچک
می توان آن را به نزولی تبدیل نمود.
10-6-1-1الگوریتم مرتب سازی انتخابی
...
8
8
16
12
22
22
22
16
12
35
35
35
27
27
27
16
12
8
41
41
41
19
19
19
مرحله دوم
مرحله اول
مرحله سوم
الگوریتم مرتب سازی انتخابی10-6-1-1
void selectionSort(int A[], int n) {
int i,j;
for (i=0; i<n; i++)
for (j=i+1; j<n; j++)
if (A[i] > A[j]) swap(A[i],A[j]) ;
}
10-6-1-2الگوریتم مرتب سازی حبابی
...
12
16
16
16
12
22
22
22
12
8
27
35
27
8
27
19
35
8
35
19
41
41
41
19
مرحله دوم
مرحله اول
مرحله سوم
الگوریتم مرتب سازی حبابی10-6-1-2
void bubbleSort(int A[], int n) {
int i, contSw;
do {
contSw = 0;
n --;
for (i=0; i<n; i++)
if (A[i] > A[i+1]) {
swap(A[i], A[i+1]) ;
contSw = 1;
}
} while (contSw) ;
}
10-6-1-3الگوریتم مرتب سازی درجی
در این روش مرتب سازی از عمل درج استفاده می شود.
این روش را می توان بصورت زیر مرحله بندی کرد:
مرحله -1فرض می کنیم آرایه فقط دارای عنصر اول است و سایر عناصر
را درنظر نمی گیریم .در اینصورت یک آرایه یک عنصری مرتب داریم
مرحله – 2عنصر دوم را در آرایه مرتب مرحله قبل درج می نماییم .اکنون
یک آرایه دو عنصری مرتب داریم.
مرحله -3عنصر سوم را در آرایه مرتب مرحله قبل درج می نماییم .اکنون
یک آرایه سه عنصری مرتب داریم.
...
مرحله –kعنصر kام را در آرایه مرتب k-1عنصری مرحله قبل درج می
نماییم تا یک آرایه kعنصری مرتب بدست آوریم.
...
مرحله –nعنصر nرا در آرایه مرتب مرحله قبل درج کرده تا آرایه مرتب
نهایی حاصل شود.
10-6-1-3الگوریتم مرتب سازی درجی
8
8
12
12
12
16
16
12
12
16
16
16
22
22
16
16
22
22
22
12
12
22
22
27
35
35
35
35
27
27
35
27
27
27
27
35
35
8
8
8
8
8
41
41
41
41
41
41
41
19
19
19
19
19
19
19
مرحله هفتم مرحله ششم
مرحله پنجم
مرحله چهارم
مرحله سوم
مرحله دوم
مرحله اول
الگوریتم مرتب سازی درجی10-6-1-3
void insertionSort(int A[], int n) {
int i, j, cur;
for (i=1; i<n; i++) {
cur = A[i] ;
for (j=i-1; j>=0 && cur<A[j]; j--)
A[j+1] = A[j] ;
A[j+1] = cur ;
}
}
10-6-1-3الگوریتم مرتب سازی درجی
در مورد سه الگوریتم مرتب سازی ذکرشده چند نکته قابل
ذکر است:
هر 3الگوریتم برای مرتب سازی یک آرایه از اعداد صحیح )(int
نوشته شده اند ،اما روش بهتر آن است که آنها را بصورت یک
الگو بنویسیم که قادر به مرتب سازی هر نوع آرایهای باشند.
الگوها و نحوه پیاده سازی آنها در فصل گذشته مورد بحث قرار
گرفتند.
هر 3الگوریتم برای مرتب سازی بصورت صعودی نوشته شده اند،
اما میتوان آنها را با یک تغییر کوچک به مرتب سازی نزولی تبدیل
کرد.
حتی میتوان تابع را بگونهای نوشت که یک پارامتر دیگر برای
تعیین نوع مرتب سازی نیز بعنوان ورودی دریافت و براساس مقدار
آن ،مرتب سازی را بصورت صعودی یا نزولی انجام دهد.
10-6-2الگوریتمهای جستجو
یکی دیگر از الگوریتمهای بسیار مهم برای آرایه ها،
الگوریتمهای جستجو هستند.
موارد زیادی پیش میآیند که قصد داریم به دنبال یک داده در
یک آرایه جستجو کرده و مکان آن را پیدا کنیم.
در این قسمت دو روش متداول جستجو را مورد بررسی قرار
میدهیم .البته معموال برای عمل جستجو از ساختمان
دادههای پیچیده تری همچون درختهای جستجوی دودویی
استفاده میگردد ،که از بحث ما خارج است.
10-6-2-1الگوریتم جستجوی خطی
جستجوی خطی ،ساده ترین نوع جستجو است که در آرایههای نامرتب
استفاده میشود .در این روش داده مورد نظر به ترتیب با تک تک عناصر آرایه
مقایسه میشود تا مکان آن پیدا شود.
{ )int linearSearch(int A[], int n, int x
;int i
)for (i=0; i<n; i++
;)if (x == A[i]) return(i
; )return(-1
}
اگر آرایه دارای nعنصر باشد ،در بدترین حالت نیاز به nمقایسه برای پیدا کردن
داده مورد نظر داریم و این در صورتی است که داده در آخرین مکان آرایه قرار
داشته باشد.
اما از آنجا که احتمال قرار گرفتن داده در هریک از مکانهای آرایه یکسان است،
بطور متوسط نیاز به n/2مقایسه خواهیم داشت.
10-6-2-2الگوریتم جستجوی دودویی
چنانچه آرایه مورد جستجو مرتب شده باشد ،روش بسیار کاراتری برای جستجو وجود
دارد .این روش که به جستجوی دودویی موسوم است ،با هربار مقایسه ،نیمی از عناصر
آرایه را از بازه جستجو حذف مینماید .در نتیجه جستجو در یک آرایه بزرگ با سرعت
بسیار زیادی صورت میپذیرد.
برای تشریح الگوریتم ،فرض کنید آرایه موردنظر بصورت صعودی مرتب شده است .ابتدا
عنصر وسط آرایه را پیدا کرده و داده مورد جستجو را با آن مقایسه میکنیم .سه حالت
ممکن است رخ دهد:
اگر داده مورد جستجو با عنصر وسط آرایه مساوی باشد ،داده پیدا شده و مکان آن را باز
میگردانیم.
اگر داده مورد جستجو از عنصر وسط آرایه کوچکتر باشد ،بنابراین باید در نیمه اول آرایه به دنبال
آن جستجو نماییم.
اگر داده مورد جستجو از عنصر وسط آرایه بزرگتر باشد ،بنابراین باید در نیمه دوم آرایه به دنبال
آن جستجو نماییم.
چنانچه حالت اول رخ دهد ،جستجو پایان یافته و مکان داده بازگردانده میشود .اما اگر
حالت دوم یا سوم رخ دهد ،عملیات جستجو به روش فوق مجددا برای نیمه اول یا نیمه
دوم آرایه تکرار میشود .بدین ترتیب بازه مورد جستجو به نیمی از آرایه کاهش مییابد.
عملیات تا زمانی ادامه مییابد که یا داده مورد نظر پیدا شود و یا بازه مورد جستجو آنقدر
کوچک شود که دادهای باقی نماند (یعنی طول بازه مورد جستجو به صفر برسد) ،که در
اینصورت داده در آرایه وجود ندارد.
10-6-2-2الگوریتم جستجوی دودویی
…
0
<
0
23
=
داده پیدا شد
…
0
24
x
<
25
48
>
48
=
داده پیدا شد
…
49
50
<
50
73
=
داده پیدا شد
…
74
75
>
99
99
x
>
99
x
الگوریتم جستجوی دودویی10-6-2-2
int binarySearch(int A[], int n, int x) {
int low, high, mid;
low = 0;
high = n-1;
while (low <= high) {
mid = (low + high) / 2;
if (x == A[mid]) return(mid);
else if (x < A[mid]) high = mid-1;
else low = mid + 1;
}
return(-1);
}
10-6-2-2الگوریتم جستجوی دودویی
حد پایین
حد باال
8
0
11
1
23
2
35
3
42
4
53
5
58
6
62
7
71
8
78
9
23
الگوریتم جستجوی دودویی10-6-2-2
int recBinarySearch(int A[], int low, int high, int x) {
int mid;
if (low > high) return(-1);
mid = (low + high) / 2;
if (x == A[mid]) return(mid);
else if (x < A[mid]) return(recBinarySearch(A, low, mid-1, x));
else return(recBinarySearch(A, mid+1, high, x)) ;
}
100 در آرایه52 نحوه فراخوانی اولیه این تابع برای جستجوی داده
: بصورت استdata عنصری
recBinarySearch(data, 0, 99, 52)
10-6-2-2الگوریتم جستجوی دودویی
برای محاسبه زمان مورد نیاز یک جستجو ،باید به این نکته
توجه کرد که با هر مقایسه ،بازه جستجو نصف می شود.
بدترین حالت زمانی است که داده اصال پیدا نشود و یا در
آخرین مقایسه (زمانی که تنها یک عنصر باقی مانده است)
پیدا شود.
سوال اینجاست که چند بار می توان اندازه آرایه را نصف کرد تا
سرانجام به یک عنصر رسید؟
n
2
log
بعنوان مثال در یک آرایه با 1.000.000عنصر ،تنها به 20
مقایسه نیاز است .به همین دلیل جستجوی دودویی یکی از
بهترین روشهای جستجو محسوب می گردد.
10-6-3الگوریتم ادغام
یکی دیگر از الگوریتمهای مفید ،ادغام دو آرایه مرتب است .ادغام دو آرایه
مرتب به معنای ایجاد یک آرایه مرتب دیگر است که حاوی تمام عناصر دو
آرایه اولیه باشد.
یک روش ضعیف برای این کار آن است که ابتدا عناصر هر دو آرایه را در آرایه
جواب کپی کنیم و سپس آرایه جواب را مرتب نماییم .اما مرتب سازی آرایه
جواب زمان زیادی را صرف خواهد کرد.
خوشبختانه الگوریتم بهتری وجود دارد که در آن نیازی به مرتب سازی
نهایی آرایه جواب نیست.
طرح کلی این الگوریتم به شرح زیر است:
یک شمارنده برای هریک از دو آرایه در نظر بگیرید و آنها را برابر صفر قرار دهید.
در هر مرحله ،دو عنصری را که شمارندهها برروی آنها قرار دارند ،با یکدیگر
مقایسه نمایید؛ عنصر کوچکتر را به آرایه جواب منتقل کرده و شمارنده مربوط به
آن را یک واحد افزایش دهید .این مرحله را تا پایان یافتن یکی از دو آرایه تکرار
کنید.
در پایان ،تمام عناصر باقیمانده از آرایه ناتمام را به ترتیب به آرایه جواب منتقل
نمایید.
10-6-3الگوریتم ادغام
8
8
17
14
14
32
17
43
49
32
52
56
43
64
49
73
52
56
64
73
الگوریتم ادغام10-6-3
void merge(int A[], int na, int B[], int nb, int C[], int &nc) {
int i=0, j=0, k=0;
while (i<na && j<nb) {
if (A[i] < B[j]) {
C[k] = A[i] ;
i++ ;
}
else if (A[i] > B[j]) {
C[k] = B[j] ;
j++ ;
}
else {
C[k] = A[i] ;
i++ ;
j ++ ;
}
k++ ;
}
for (; i<na; i++, k++) C[k] = A[i];
for (; j<nb; j++, k++) C[k] = B[j];
nc = k;
}
مبانی کامپیوتر و برنامه سازی
فصلیازدهم :رشتهها
مدرس :رضارمضانی
11رشته ها
رشتهها یکی از مهمترین انواع دادهها در زبانهای برنامه سازی هستند.
یک رشته به یک توالی از صفر یا چند کاراکتر گفته می شود.
بعنوان مثال Aliیک رشته کاراکتری است.
از رشتهها در موارد بسیاری همچون ذخیره اسم و آدرس استفاده می شود.
در زبان Cنوع داده مشخصی برای رشته وجود ندارد ،بلکه یک رشته بصورت
آرایه ای از کاراکترها تعریف می گردد .بعنوان مثال:
;]char name[10
متغیر nameمی تواند یک رشته با حداکثر 10کاراکتر را در خود نگاه دارد.
اما فرض کنید قصد داریم رشته ای مانند Aliرا در این متغیر ذخیره نماییم که
کمتر از 10کاراکتر دارد .دراینصورت زبان Cچگونه دریابد که در هنگام انجام
عملیات مختلف برروی این رشته ،مثال در هنگام چاپ آن ،فقط باید 3حرف اول
رشته را چاپ نماید؟
11رشته ها
برای حل این مشکل ،طراحان زبان Cاز یک کاراکتر خاص بنام null
استفاده کردند.
کلیه رشتهها در زبان Cباید به کاراکتر nullختم گردند .در حقیقت در
زبان Cیک رشته هنگامی که به nullبرسد ،خاتمه می یابد و نه
زمانیکه به انتهای آرایه برسد.
کاراکتر ،nullدارای کد اسکی 0می باشد و با ' '\0نشان داده می شود.
بعنوان مثال به نمونه زیر دقت کنید:
;} 'char name[10] = {'A', 'l', 'i', '\0
دقت کنید که در حقیقت 4عضو از آرایه پر شده است و عضو آخر به
nullتخصص داده شده است.
مقادیر عناصر بعدی آرایه مهم نیست ،چرا که در هنگام انجام عملیات
برروی رشته nameاز آنها صرفنظر می گردد.
بنابراین دقت کنید که در هنگام تعریف یک رشته ،یک عنصر اضافه برای
کاراکتر nullدر نظر بگیرید.
11رشته ها
همانطور که در فصول قبل گفتیم ،در زبان Cامکان تعریف ثابت رشته ای نیز
وجود دارد .یک ثابت رشته ای ،دنباله ای از کاراکترها است که در داخل "
قرار می گیرد.
بعنوان مثال " "Aliنشاندهنده یک ثابت رشته ای است.
توجه کنید که هنگامی که از " برای یک ثابت رشته ای استفاده می کنید،
کامپایلر یک عالمت nullدر انتهای رشته اضافه می کند.
از ثوابت رشته ای برای مقداردهی اولیه به متغیرهای رشته ای نیز می
توان استفاده کرد .بعنوان مثال:
; "char name[10] = "Ahmad
; "char address[50] = "No. 20 Azadi Street
d \0
m a
h
A
name
11-1خواندن و نوشتن رشته ها
برای خواندن و نوشتن رشتهها می توان از توابع scanfو printfاستفاده
کرد .تنها نکته این است که در داخل رشته کنترلی باید از مشخصه تبدیل
%sاستفاده نمایید.
بعنوان مثال به نمونه زیر دقت کنید:
>#include <stdio.h
{ )(void main
;]char name[20
;)" ? printf("what is your name
;)scanf("%s" ,name
;)printf("Hello %s !", name
}
what is your name ? Mohamad
Ali
reza
Hello Mohamad
! Ali
!
11-1خواندن و نوشتن رشته ها
برای رفع این مشکل می توان از تابع دیگری بنام getsاستفاده کرد.
این تابع یک متغیر رشته ای را بعنوان پارامتر ورودی دریافت و پس از خواندن یک رشته از
صفحه کلید ،آن را در پارامتر ورودی قرار داده و باز می گرداند.
نکته مهم این است که تابع getsتا زمانیکه کلید Enterفشرده نشده است ،به خواندن
دادهها از صفحه کلید ادامه می دهد .بنابراین رشته می تواند دارای فضای خالی
)(spaceنیز باشد.
به بازنویسی مثال قبل با تابع getsدقت کنید:
>#include <stdio.h
{ )(void main
;]char name[20
;)" ? printf("what is your name
;)gets(name
;)printf("Hello %s !", name
}
الزم به ذکر است که برای چاپ رشتهها نیز تابعی بنام putsوجود دارد .بعنوان مثال بجای
آخرین تابع printfدر مثال باال می توانستید به شکل زیر عمل کنید:
;)" puts("Hello
; )puts(name
11-2توابع کتابخانه ای رشته ای
زبان Cدارای یک کتابخانه غنی از توابع کار با رشتهها است.
پیش تعریف این توابع در فایل سرآمد string.hآمده است.
در این قسمت چندین تابع از کتابخانه Cبه همراه الگوریتم آنها بررسی
می گردند .هدف از بررسی الگوریتم این توابع ،آشنایی بیشتر شما با
نحوه کار با رشتهها می باشد.
دقت کنید که ممکن است تعریف دقیق تابع کتابخانه ای در Cبا آنچه که
ما در اینجا آورده ایم ،کمی متفاوت باشد.
نکته بسیار مهمی که در هنگام بررسی این توابع باید بدان توجه داشته
باشید ،این است که هیچیک از آنها حدود آرایه (اندازه آرایه) را بررسی
نمی کنند.
بنابراین این وظیفه خود برنامهنویس است که آرایه هایی با اندازه
مناسب را به توابع ارسال کند.
(string length) strlen تابع
int strlen(const char string[]) {
int i;
for (i=0; string[i]; i++) ;
return(i);
}
string
A
h
i=0
i=1
m a
i=2
i=3
d \0
i=4 i=5
(string length) strlen تابع
: را نشان می دهدstrlen برنامه زیر نحوه استفاده از
void main() {
char text[100];
int len;
printf("enter a text : ");
gets(text);
len = strlen(text);
printf("length of your text is %d",len);
}
enter a text : Hello
length of your text is 5
(string copy) strcpy تابع
void strcpy(char dest[], const char source[]) {
int i;
for (i=0; source[i]; i++)
dest[i] = source[i] ;
dest[i] = '\0' ;
}
source
dest
A
h
i=0
i=1
A
h
i=0
i=1
m a
i=2
i=3
m a
i=2
i=3
d \0
i=4 i=5
d \0
i=4 i=5
(string copy) strcpy تابع
: را نشان می دهدstrcpy برنامه زیر نحوه استفاده از
void main() {
char string1[20], string2[20] ;
printf("Please enter string1 : ");
gets(string1) ;
strcpy(string2, string1) ;
printf("copy string1 into string2\n");
printf("now string1 = %s and string2 = %s\n", string1, string2);
strcpy(string1,"new") ;
printf("copy new into string1\n");
printf("now string1 = %s", string1);
}
Please enter string1 : Hello
copy string1 into string2
now string1 = Hello and string2 = Hello
copy new into string1
now string1 = new
(string concat) strcat تابع
void strcat(char str1[], const char str2[]) {
int i, j ;
for (i=0; str1[i]; i++) ;
for (j=0; str2[j]; j++)
str1[i+j] = str2[j] ;
str1[i+j] = '\0' ;
}
str1
str2
i+j
i+j
i+j
i+j
i+j
e
z
a
\0
a
l
i
\0r
i=0
i=1
i=2
i=3
r
e
z
a
\0
j=3
j=4
j=0
j=1
j=2
(string concat) strcat تابع
: را نشان می دهدstrcat برنامه زیر نحوه استفاده از
void main() {
char string1[20], string2[20] ;
printf("please enter string1 : ");
gets(string1);
printf("please enter string2 : ");
gets(string2);
strcat(string1,string2);
printf("concatenate of string1 and string2 is : %s", string1) ;
}
please enter string1 : Hello
please enter string2 : everybody!
concatenate of string1 and string2 is : Hello everybody!
تابع (string compare) strcmp
این تابع دو رشته را دریافت و پس از مقایسه آنها یکی از 3مقدار زیر را باز می
گرداند:
در صورتیکه مساوی باشند 0 :
در صورتیکه رشته اول بزرگتر باشد +1 :
درصورتیکه رشته اول کوچکتر باشد -1 :
{ )][int strcmp(const char str1[], const char str2
;int i
;i = -1
{ do
;i ++
;)if (str1[i] > str2[i]) return(1
; )if (str1[i] < str2[i]) return(-1
; )]} while (str1[i
;)return(0
}
(string compare) strcmp تابع
: را نشان می دهدstrcmp برنامه زیر نحوه استفاده از
void main() {
char string1[20], string2[20] ;
int result;
printf("please enter string1 : ");
gets(string1);
printf("please enter string2 : ");
gets(string2);
result = strcmp(string1,string2);
if (result == 0)
printf("%s equals %s\n", string1, string2) ;
else if (result == 1)
printf("%s is grater than %s\n", string1, string2) ;
else printf("%s is less than %s\n", string1, string2) ;
}
please
please enter
enter string1
string1 :: ali
ali
please enter
enter string2
string2 :: ali
ahmad
alireza
please
ali equals
is
is grater
less ali
than
than
alireza
ahmad
ali
(string in string) strstr تابع
int strstr(const char str1[], const char str2[]) {
int i, j ;
for (i=0; str1[i]; i++)
if (str1[i] == str2[0]) {
for (j=1; str2[j] && str1[i+j] == str2[j] ; j++) ;
if (!str2[j]) return(i);
}
return(-1) ;
}
i+j
str1
t h i
s
i+j
i
s
i+j i+j i+j i+j i+j i+j
a
s a m p l
i=0 i=1 i=2 i=3 i=4i=5 i=6 i=7 i=8 i=9 i=10
str2
s a m p l
e \0
J=1 J=2J=3J=4 J=5 J=6
e
t
e x t \0
(string in string) strstr تابع
: را نشان می دهدstrstr برنامه زیر نحوه استفاده از
void main() {
char text[100], word[20];
int i, n, result ;
printf("enter a text : ");
gets(text) ;
printf("how many words do you have : ");
scanf("%d",&n) ;
for (i=0; i<n; i++) {
printf("enter a word to search : ") ;
scanf("%s",word);
result = strstr(text, word) ;
if (result == -1)
printf("(%s) not found\n",word);
else printf("(%s) is founded in position %d\n", word, result);
}
}
enter a text : this is a sample text!
how many words do you have : 3
enter a word to search : sample
(sample) is founded in position 10
enter a word to search : is
(is) is founded in position 2
enter a word to search : test
(test) not found
(string reverse) strrev تابع
void strrev(char string[]) {
int i, j, l;
char temp;
for (l=0; string[l]; l++) ;
for (i=0, j=l-1; i<j; i++, j--) {
temp = string[i] ;
string[i] = string[j] ;
string[j] = temp ;
}
}
string
c
l=0
i=0
o
i=1
l=1
u
n
i=2 i=3
l=2
l=3
J=3
t
e
r
j=4 l=5
j=5 j=6
l=4
l=6
\0
l=7
(ascii to integer) atoi تابع
int atoi(const char string[]) {
int i=0 , sign=1, number=0;
if (string[0] == '-') {
sign = -1; i = 1;
}
else if (string[0] == '+')
i = 1;
for (; string[i]; i++)
if (string[i]>='0' && string[i]<='9')
number = number * 10 + (string[i] – 48) ; // or (string[i] – '0')
else return(0);
return(sign * number);
}
sign = 1
string
+
7
3
5
\0
7
10++)‘3’
)‘5’––48)
48)
0 * *10
)‘7’
number = 73
= 70
0
++7+3=
730
5=7=73
735
i=0
i=1
i=2
i=3
i=4
11-3چند تابع رشته ای مفید دیگر
همانطور که گفته شد ،توابع مذکور همگی در کتابخانه C
موجود هستند و نیازی به نوشتن آنها نیست.
اما بعضی اعمال مفید دیگر نیز بر روی رشتهها وجود دارند که
در توابع کتابخانه ای تعریف نشده اند .در اینجا چند نمونه از این
توابع برای آشنایی بیشتر شما آمده است .در نوشتن این
توابع ،از توابع کتابخانه ای استفاده شده است.
strDelete تابع
int strDelete(char str1[], const char str2[]) {
int i, position, length;
position = strstr(str1, str2);
if (position == -1) return(0);
length = strlen(str2) ;
for (i= position+length ; str1[i]; i++)
str1[i – length] = str1[i] ;
str1[i-length] = '\0' ;
return(1);
}
i
str1
t h i
s
i
s
a
e xp tl \0
s at m
e
position = 10
str2 s a m p l e \0
i
i
t
e x t \0
i
i
i
length = 6
strInsert تابع
void strInsert(char str1[], const char str2[], int k) {
int i, length ;
length = strlen(str2);
for (i=strlen(str1); i >= k ; i --)
str1[i+length] = str1[i] ;
for (i=0 ;i<length; i++)
str1[k + i] = str2[i] ;
}
i i+4 i+4 i+4 i+4
i i+4
i i+4
k+i
i k+i
i k+i
i k+i
k+i
str1
t h i
i
s
s
a
nt e xw t t! \0
e x
k = 10
str2 n e w
\0
i=0 i=1 i=2 i=3 i=4
t
! \0
length = 4
11-4آرایهای از رشتهها
در بعضی موارد الزم است که تعدادی رشته کاراکتری را
ذخیره نماییم.
بعنوان مثال فرض کنید قصد ذخیره سازی نام 5تن از
دانشجویان را داریم .اگر برای هر نام 10کاراکتر در نظر
بگیریم ،احتیاج به یک آرایه 5تایی از رشته های 10
کاراکتری خواهیم داشت.
برای تعریف آرایه ای از رشته ها ،باید از یک آرایه دو بعدی
از کاراکترها استفاده کرد.
دستور زیر ،آرایه مورد نظر برای نگاهداری نام دانشجویان
را ایجاد و به آن مقدار اولیه می دهد:
;} "char nameList[5][10] = {"Ali", "Reza", "Ahmad", "Babak", "Hamid
آرایهای از رشته ها11-4
nameList[0]
nameList[1]
nameList[2]
nameList[3]
nameList[4]
A
R
l
e
i
z
\0
a
\0
A
h
m
a
d
\0
B
a
b
a
k
\0
H
a
m
i
d
\0
nameListآرایه
11-4آرایهای از رشته ها
آرایه ،nameListیک آرایه دو بعدی از نوع کاراکتر است و همانند تمام آرایه های دو بعدی
دیگر می توان از طریق دو اندیس به اعضای آن دسترسی پیدا کرد.
بعنوان مثال عنصر ] nameList[0][0در مثال فوق برابر ' 'Aمی باشد.
اما در یک آرایه از رشته ها ،معموال برنامهنویس عملیات خود را بر روی رشته های آن
آرایه انجام می دهد و بطور مستقیم با تک تک کاراکترها کار نمی کند.
بعنوان مثال ممکن است بخواهد که رشته اول یعنی " "Aliرا چاپ نماید .برای اینکار
کافی است از نام آرایه بعالوه یک اندیس استفاده نماییم.
بعنوان مثال ] nameList[0یک متغیر رشته ای است که مقدار " "Aliرا در خود جای داده
است .برای دسترسی به سایر رشتهها نیز کافی است از نام آرایه بعالوه اندیس ردیفی
که رشته در آن قرار دارد ،استفاده نماییم.
حتی می توان با این روش ،یک رشته را به یک تابع نیز ارسال کرد .بعنوان مثال دستور
زیر مقداری را برای سومین رشته از کاربر دریافت می نماید :
; )]gets(nameList[2
مثال )8برنامه ای بنویسید که ابتدا شماره دانشجویی ،نام ،معدل تعدادی دانشجو را
دریافت و ذخیره نماید .سپس با دریافت شماره دانشجویی هر فرد به دنبال اطالعات وی
جستجو کرده و در صورت پیدا شدن ،مشخصات وی را چاپ نماید .برنامه با در یافت
شماره دانشجویی 0پایان می یابد.
آرایهای از رشته ها11-4
#include <stdio.h>
const int maxStudent = 100;
//حداکثر تعداد دانشجویان
const int maxNameLen = 20;
//حداکثر طول نام هر دانشجو
void main() {
long int idList[maxStudent] ;
float averageList[maxStudent] ;
char nameList[maxStudent][maxNameLen] ;
long int searchId;
int i, n;
printf("enter student number : ");
scanf("%d",&n);
//دریافت اطالعات دانشجویان
for (i=0; i<n; i++) {
printf("student #%d :\n", i+1) ;
printf("enter id : ");
scanf("%ld", &idList[i]);
printf("enter name : ");
scanf("%s", nameList[i]);
printf("enter average : ");
scanf("%f", &averageList[i]);
}
آرایهای از رشته ها11-4
//حلقه جستجو به دنبال اطالعات دانشجو
while(1) {
printf("enter student id (0 to exit) : ");
scanf("%ld", &searchId);
if (!searchId) break ;
for (i=0 ; i<n; i++)
if (idList[i] == searchId) break;
if (i<n) {
printf("Student specifications : \n");
printf("id=%ld name=%s average=%f\n", idList[i], nameList[i],
averageList[i]);
}
else printf("student not found !\n");
}
}
مبانی کامپیوتر و برنامه سازی
فصلدوازدهم :ساختارها
مدرس :رضارمضانی
12ساختارها
گاهی مجموعهای از دادههای غیر همنوع داریم که به نحوی با یکدیگر
دارای ارتباط منطقی هستند ،به همین دلیل مایلیم آنها را تحت یک نام
مشترک ذخیره نماییم تا بتوانیم کل آنها را به صورت یکجا پردازش نماییم
(مثال آنها را به یک تابع ارسال نماییم).
برای این کار باید از ساختمان داده قدرتمند دیگری به نام ساختار یا
structureاستفاده نماییم.
ساختارها به برنامهنویس اجازه میدهند نوع دادههای جدیدی را به
وجود آورد که همانند نوع دادههای اولیه خود زبان (مانند )intمیتوانند
در تعریف متغیرها به کار روند.
خواهید دید که این قابلیت (امکان ایجاد نوع دادههای موردنظر) یکی از
ویژگیهای بسیار قوی زبان Cاست که نوع پیشرفته تر آن یعنی کالس
) ،(classپایه برنامهنویسی شئ گرا در زبان C++است.
12-1نحوه تعریف ساختار
همانطور که گفته شد ،ساختارها مجموعهای از دادههای غیر همنوع
میباشند که به نحوی با یکدیگر در ارتباط هستند.
به این داده ها ،عناصر ساختار (یا اعضای ساختار و یا فیلد) گفته
میشود.
هر عضو ساختار ،در حقیقت یک متغیر است که در تعریف ساختار باید
نوع و نام آن تعیین شود.
برای تعریف یک ساختار از کلمه کلیدی structاستفاده میشود و شکل
کلی آن به صورت زیر است:
{ >struct <struct-name
; ><type> <element-name 1
; ><type> <element-name 2
نام ساختار
…
نوع عنصر
; ><type> <element-name n
;}
نام عنصر
12-1نحوه تعریف ساختار
فرض کنید قصد داریم ساختاری تعریف کنیم که اطالعات یک دانشجو را
در خود ذخیره نماید.
این اطالعات شامل شماره دانشجویی ،نام ،معدل کل ،سن و جنسیت
دانشجو میباشد .تعریف این ساختار در زیر آمده است:
{ struct student
;long int id
شماره دانشجویی (حداکثر 8رقم) //
; ]char name[20
نام دانشجو //
معدل کلfloat average; //
; int age
سن دانشجو //
زن :جنسیت دانشجو = ' 'Fمرد = 'char gender ; // 'M
;}
12-1نحوه تعریف ساختار
همانطور که قبال نیز گفته شد ،با تعریف یک ساختار در حقیقت یک نوع
داده جدید را تعریف میکنید.
اکنون میتوانید متغیری را از نوع studentتعریف نمایید .نحوه انجام این
کار همانند تعریف متغیرهای عادی است .به عنوان مثال:
;student s
اکنون متغیر sشامل کلیه عناصر تعریف شده در ساختار میباشد و
زبان Cبه طور خودکار حافظه الزم برای این ساختار را تخصیص
میدهد.
معموال کامپایلر این فضا را بصورت پشت سرهم تخصیص میدهد .به
عنوان مثال متغیر sبه صورت شکل زیر در حافظه ذخیره میگردد.
2 1
4
average age
20
4
name
id
12-1نحوه تعریف ساختار
اکنون سوال این است که چگونه میتوان به اجزای متغیر sدسترسی پیدا کرد؟
برای این کار از عملگر نقطه (یا )dotاستفاده میشود .به این صورت که ابتدا نام
متغیر ساختاری ،به دنبال آن یک نقطه و سپس نام عنصر موردنظر ذکر میگردند.
به عنوان مثال برای مقداردهی به عناصر متغیر sمیتوان به شکل زیر عمل کرد:
; s.id = 84122030
;)"strcpy(s.name, "reza
; s.average = 17.64
; s.age = 18
; 's.gender = 'M
بنابراین میتوان از عناصر یک متغیر ساختاری ،دقیقا همانند یک متغیر معمولی
استفاده کرد .مثال میتوان برای دریافت نام و معدل دانشجوی sاز دستورات زیر
استفاده کرد:
; )gets(s.name
;)scanf("%f", &s.average
و یا میتوان سن دانشجوی sرا به صورت زیر چاپ کرد:
;)printf("age = %d", s.age
12-1نحوه تعریف ساختار
در پایان به چند نکته مهم در مورد ساختارها توجه کنید:
مقدار دهی اولیه به ساختارها امکان پذیر است و تقریبا مشابه مقداردهی اولیه به
آرایهها میباشد .بعنوان نمونه به مثال زیر توجه کنید:
;} 'student s = {83201012, "ali", 16.90, 19, 'M
می توان در هنگام تعریف یک ساختار ،به طور همزمان یک یا چند متغیر از نوع آن ساختار
تعریف کرد .به عنوان مثال به نمونه زیر دقت کنید:
{ struct rectangle
int x,y ; // start coordinates
;int length, width
;} r1, r2
جالب آن است که اگر فقط نیاز به همین دو متغیر از نوع rectangleدارید و دیگر در
قسمتهای بعدی برنامه از این ساختار استفاده نمیکنید ،میتوانید برای ساختار خود
نامی تعیین نکنید.
{ struct
int x,y ; // start coordinates
;int length, width
;} r1, r2
12-1نحوه تعریف ساختار
استفاده از عملگر نسبت دهی یا = برای دو متغیر ساختاری مجاز است،
البته به شرطی که هر دو از یک نوع ساختار باشند .در این حالت محتویات
هر عنصر از متغیر ساختاری مبدا ،به عنصر متناظر در متغیر مقصد منتقل
میشود.
به عنوان مثال ساختار rectangleرا در نظر بگیرید:
;rectangle r1, r2
;r1.x = r1.y = 0
;r1.length = 10
;r1.width = 20
; r2 = r1
چنانچه تعریف ساختار را در خارج کلیه توابع انجام دهید ،ساختار به صورت
سراسری تعریف شده است و در نتیجه در کلیه توابع فایل شناخته شده
خواهد بود.
البته میتوان تعریف ساختار را در داخل یک تابع خاص نیز انجام داد که در این
صورت ساختار مورد نظر فقط در داخل همان تابع شناخته شده خواهد بود.
البته تعریف ساختار در داخل یک تابع متداول نمیباشد.
12-2آرایهای از ساختارها
فرض کنید نیاز به نگاهداری اطالعات تعدادی دانشجو دارید ،در این
صورت میتوانید آرایهای از ساختار دانشجو تعریف نمایید.
در حقیقت آرایهای از ساختار بسیار متداول بوده و کاربرد زیادی دارد .به
عنوان مثال ،یک بار دیگر ساختار دانشجو را درنظر بگیرید:
{ struct student
;long int id
; ]char name[20
;float average
; int age
; char gender
;}
دستور زیر ،متغیر listرا به عنوان یک آرایه 100عنصری از نوع student
تعریف مینماید:
; ]student list[100
12-2آرایهای از ساختارها
همانند سایر آرایه ها ،برای دسترسی به عناصر یک آرایه از ساختارها
نیز باید از اندیس استفاده نمود.
به عنوان مثال ] ،list[0نشان دهنده اطالعات مربوط به اولین دانشجو
میباشد.
حال برای دسترسی به نام مربوط به اولین دانشجو ،میتوان از
list[0].nameاستفاده نمود .به عنوان نمونه دستورات زیر اطالعات مربوط
به دهمین دانشجو را مقداردهی میکند:
; list[9].id = 83102012
;)"strcpy(list[9].name, "ahmad
;list[9].average = 17.34
;list[9].age = 21
; 'list[9].gender = 'M
همان طور که گفته شد استفاده از آرایهای از ساختارها ،عملی متداول
برای ذخیره و بازیابی اطالعات است.
12-3ساختارها و توابع
یک متغیر ساختاری را میتوان همانند سایر متغیرها به یک تابع ارسال کرد.
به عنوان مثال ساختار rectangleاز قسمت قبل را در نظر بگیرید ،قصد داریم
تابعی بنویسیم که یک مستطیل را دریافت و محیط آن را بازگرداند.
برنامه زیر نحوه تعریف و فراخوانی این تابع را نشان میدهد.
>#include <stdio.h
{ struct rectangle
; int x,y
;int length, width
;}
{ )int rectangleArea(rectangle r
; ) )return (2 * (r.width+r.length
}
12-3ساختارها و توابع
نکته مهمی که باید به آن توجه کرد ،این است که ارسال ساختارها به
توابع در حالت عادی توسط مقدار ) (call by valueصورت میپذیرد.
از آنجا که هر ساختار ،خود از چندین متغیر دیگر تشکیل شده است،
این نحوه ارسال باعث ایجاد سربار میگردد؛ چرا که باید ساختار
ارسالی در پارامتر تابع کپی گردد و این عمل باعث هدر رفتن زمان و
حافظه میگردد.
به همین دلیل معموال ساختارها توسط ارجاع به توابع ارسال
میگردند .حتی در مواردی که قصد تغییر متغیر ساختاری را در تابع
نداریم نیز آنها را به صورت ارجاع ثابت به تابع ارسال مینماییم.
بنابراین بهتر است تابع قبلی را به صورت زیر بازنویسی نماییم:
{ )int rectangleArea(const rectangle &r
; ) )return (2 * (r.width+r.length
}
12-3ساختارها و توابع
یک ساختار میتواند به عنوان خروجی یک تابع نیز تعریف گردد.
به عنوان مثال فرض کنید قصد داریم تابعی بنویسیم که مشخصات یک مستطیل را از کاربر دریافت و
آن را باز گرداند .این تابع ،به همراه نحوه فراخوانی آن در برنامه زیر نشان داده شده است.
>#include <stdio.h
{ struct rectangle
; int x,y
;int length, width
;}
{ )(rectangle getRectangle
;rectangle r
;)" printf("enter coordinates of rectangle (x,y) :
;)scanf("%d %d", &r.x, &r.y
;)" printf("enter length :
;)scanf("%d", &r.length
;)" printf("enter width :
;)scanf("%d", &r.width
;)return (r
}
ساختارها و توابع12-3
void main() {
rectangle rect1;
rect1 = getRectangle() ;
printf("rectangle 1 : x=%d y=%d length=%d width=%d\n",
rect1.x, rect1.y, rect1.length,rect1.width) ;
}
enter coordinates of rectangle (x,y) : 5 5
enter length : 20
enter width : 8
rectangle 1 : x=5 y=5 length=20 width=8
به، به این صورت که مستطیل بازگشتی،می توان این تابع را به شکل دیگری نیز نوشت
باید تعریف تابع را به، برای این کار.عنوان یک پارامتر ارجاعی (خروجی) به تابع ارسال شود
:صورت زیر تغییر داد
void getRectangle)rectangle &r( } …
12-4ساختارهای تودرتو
ساختارهایی که تاکنون طراحی کرده ایم ،حاوی نوع دادههای سادهای مانند
عدد صحیح ،عدد اعشاری و یا رشته بوده اند.
اما هر عضو ساختار میتواند یک آرایه و یا حتی یک ساختار دیگر نیز باشد.
هنگامی که یک ساختار ،دارای عضوی از نوع یک ساختار دیگر باشد ،به آن
ساختار تودرتو یا متداخل میگوییم .به مثال زیر توجه کنید.
مثال )7-1ساختاری برای نگهداری اطالعات دانشجویان طراحی کنید که شامل
اطالعات زیر باشد:
شماره دانشجویی
نام
شماره ترم جاری
تعداد دروس ترم جاری
مشخصات دروس ترم جاری (نام درس ،تعداد واحد درس ،نمره دانشجو)
معدل کل
لیست معدل ترمهای گذشته
تاریخ تولد به طور کامل (روز ،ماه ،سال)
ساختارهای تودرتو12-4
const int maxCourse = 10;
const int maxTerm = 12;
// حداکثر دروس دانشجو در یک ترم
// حداکثر ترمهای تحصیلی دانشجو
struct date {
int day, month, year ;
};
struct course {
char name[30] ;
int unit;
float grade;
};
// نام درس
// تعداد واحد
// نمره درس
struct student {
long int id ;
// شماره دانشجویی
char name[20] ;
// نام دانشجو
int termNo;
// شماره ترم جاری
int courseNo ;
// تعداد دروس ترم جاری
course courseList[maxCourse] ;
// لیست دروس ترم جاری
float gpa ;
// معدل کل
float averageList[maxTerm] ;
// لیست معدل ترمهای گذشته
date birthDate ;
// تاریخ تولد
};
student s ;
12-4ساختارهای تودرتو
اولین مورد ،یک آرایه از اعداد اعشاری به نام averageListمیباشد که
لیست معدل ترمهای گذشته دانشجو را نگاه میدارد.
به عنوان مثال چنانچه بخواهیم معدل ترم اول دانشجوی sرا مقداردهی
کنیم ،میتوانیم از دستور زیر استفاده نماییم:
; s.averageList[0] = 18.2
مورد بعدی ،متغیر birthDateاست که تاریخ تولد دانشجو را نگاه میدارد
و خود از نوع ساختار dateمیباشد.
دستورات زیر تاریخ تولد دانشجوی sرا برابر 1/12/1360قرار میدهند :
; s.birthDate.day = 1
; s.birthDate.month = 12
; s.birthDate.year = 1360
همان طور که میبینید برای دسترسی به عناصر ساختار متداخل ،date
از دو عالمت نقطه استفاده شده است.
12-4ساختارهای تودرتو
آخرین مورد ،متغیر courseListاست که لیست دروس ترم جاری دانشجو را نگاه
میدارد.
این متغیر خود آرایهای از یک ساختار دیگر به نام courseاست .دستورات زیر،
اولین درس دانشجوی sرا مقدار دهی مینمایند:
;)"strcpy(s.courseList[0].name, "Computer Programming
;s.courseList[0].unit = 4
; s.courseList[0].grade = 18.5
مقداردهی به سایر دروس دانشجوی sنیز همانند دستورات فوق است ،با این
تفاوت که باید از اندیس مناسب برای آرایه courseListاستفاده شود.
البته ساختارها میتوانند پیچیده تر نیز باشند .به عنوان مثال میتوان ساختاری
برای یک گروه آموزشی تعریف کرد که شامل نام گروه ،نام مدیر گروه و لیست
دانشجویان ،که آرایهای از ساختار دانشجو است ،باشد .به همین ترتیب
میتوان ساختار دانشکده و دانشگاه را نیز طراحی کرد .البته تعریف
ساختارهای پیچیدهای که دارای چندین ساختار تودرتو باشند ،باعث میشود
که استفاده از آنها نیز مشکل گردد.
12-5چند جمله ایها
به دلیل اهمیت ساختارها ،در این قسمت یک مثال جامع برای نشان دادن نحوه
طراحی و کار با آنها ارائه میگردد.
این مثال ،نحوه پیاده سازی چند جمله ایها را نشان می دهد .حتما از دروس ریاضی
به یاد دارید که یک چند جمله ای به صورت زیر تعریف می گردد:
A( x) an x n an1 x n1 ... a2 x 2 a1 x a0
به عنوان مثال به چند جمله ای زیر توجه کنید:
A( x) 7 x 8 4 x 5 8 x 4 3 x 9
و اما اعمال مختلفی همچون جمع دو چند جمله ای ،ضرب دو چند جمله ای ،ضرب
یک جمله در چند جمله ای و ...نیز برروی چند جمله ایها تعریف شده است ،که
تعاریف آنها را در کتابهای ریاضی خوانده اید.
12-5چند جمله ایها
اکنون فرض کنید از ما خواسته شده است یک مجموعه از ساختارها و
توابع برای کار با چند جمله ایها طراحی نماییم .موارد خواسته شده
عبارتند از:
نوع داده چند جمله ای به عنوان یک ساختار
یک مجموعه از توابع برای
اضافه کردن یک جمله به چند جمله ای
حذف یک جمله از چند جمله ای
دریافت یک چند جمله ای از کاربر
چاپ یک چند جمله ای
جمع دو چند جمله ای
ضرب دو چند جمله ای
ضرب یک جمله در چند جمله ای
محاسبه ضریب یک توان خاص از چند جمله ای
محاسبه درجه یک چند جمله ای
تعیین مقدار چند جمله ای به ازای یک مقدار مشخص برای x
12-5چند جمله ایها
قبل از هر چیز ،باید ساختاری برای نگهداری یک چند جمله ای طراحی نماییم.
همان طور که می بینید ،هر چند جمله ای از تعدادی جمله مانند 5x3تشکیل شده است.
هر جمله شامل دو قسمت است :ضریب و توان جمله (متغیر xمهم نیست ،چرا که می توان
از هر حرف دیگری مانند yنیز استفاده کرد).
بنابراین می توان هر چند جمله ای را به صورت آرایه ای از جملهها پیاده سازی کرد ،که هر
جمله شامل یک ضریب و یک توان است.
این پیاده سازی در زیر نشان داده شده است.
حداکثر تعداد جمالت در یک چندجمله ای //
;const int maxTerm = 100
ضریب جمله //
توان جمله //
آرایه ذخیره سازی جمالت //
تعداد جمالت چند جمله ای //
{ struct term
; float coef
; int exp
;}
{ struct polynomial
; ]term terms[maxTerm
; int termNo
;}
12-5چند جمله ایها
اکنون می توان متغیری از نوع چند جمله ای را به صورت زیر تعریف کرد:
;polynomial A
در شکل زیر ،نحوه ذخیره سازی یک چند جمله ای در متغیر Aنشان داده شده
است.
A( x) 7 x 8 4 x 5 8 x 4 3 x 9
…
…
4 8 3 9
5 4 1 0
coef = 7
exp = 8
= terms
termNo = 5
A
چند جمله ایها12-5
تابع، اولین تابعی که بررسی می نماییم.اکنون به پیاده سازی توابع می پردازیم
و جمله را، است که یک چند جمله ای و یک جمله را به عنوان ورودی دریافتinsertTerm
.در مکان مناسب چند جمله ای (بر حسب توان) درج می نماید
int insertTerm(polynomial &A, term newTerm) {
int i;
if (A.termNo == maxTerm) return(1);
if ( searchExponent(A, newTerm.exp) != -1) return(2) ;
for (i=A.termNo-1; i>=0; i--)
if (newTerm.exp > A.terms[i].exp)
A.terms[i+1] = A.terms[i] ;
else break ;
A.terms[i+1] = newTerm ;
A.termNo ++;
return(0) ;
}
12-5چند جمله ایها
قبل از اینکه به سراغ تابع بعدی برویم ،ابتدا تابع searchExponentرا که جزو
توابع استفاده شده بود ،پیاده سازی می نماییم.
{ )int searchExponent(const polynomial &A, int searchExp
;int i
)for (i=0; i<A.termNo; i++
; )if (A.terms[i].exp == searchExp) return(i
;)return(-1
}
اگرچه به دلیل مرتب بودن چندجمله ای برحسب توان ،جستجو می تواند به صورت
دودویی نیز انجام پذیرد ،اما از آنجا که معموال تعداد جمالت بسیار کم است ،از
جستجوی خطی استفاده شده است .
12-5چند جمله ایها
تابع بعدی ،تابع deleteTermاست که یک چند جمله ای و یک توان را دریافت ،و در
صورت وجود توان موردنظر در چندجمله ای ،جمله مربوط به آن را حذف می نماید.
از آنجا که جمله های چندجمله ای برحسب توان مرتب هستند ،حذف یک جمله
نیاز به شیفت دادن جمله های پس از آن دارد .این تابع درصورت موفقیت در حذف
مقدار ،1و در صورت عدم موفقیت 0باز می گرداند.
{ )int deleteTerm(polynomial &A, int deleteExp
;int i, position
;)position = searchExponent(A, deleteExp
; )if (position == -1) return(0
)for (i=position; i<A.termNo -1; i++
; ]A.terms[i] = A.terms[i+1
;A.termNo --
; )return(1
}
12-5چند جمله ایها
اکنون به تابع getPolynomialمیپردازیم که یک چند جمله ای را از کاربر دریافت کرده و
به عنوان خروجی باز میگرداند.
این تابع را می توان به دو صورت نوشت .در حالت اول چندجمله ای دریافتی به صورت
خروجی تابع بازگردانده می شود .یعنی تعریف تابع به صورت زیر است:
… { )(polynomial getPolynomial
و اما در حالت دوم ،چند جمله ای به صورت یک پارامتر ارجاعی به تابع ارسال می شود،
و سپس در داخل تابع مقادیر آن از کاربر دریافت می گردد .در این حالت تعریف تابع به
صورت زیر خواهد بود:
… { )void getPolynomial(polynomial &A
تفاوت این دو در نحوه فرا خوانی است .فرض کنید قصد داریم چند جمله ای به نام pرا از
کاربر دریافت کنیم .دستورات زیر نحوه انجام این کار در هر دو حالت را نشان می دهند:
;polynomial p
حالت اول p = getPolynomial() ; //
;)getPolynomial(p
حالت دوم //
البته حالت دوم از لحاظ فضا و زمان بهینه تر است .چرا که در حالت اول ،پس از بازگشت
از تابع ،باید مقدار بازگشتی در داخل متغیر pکپی گردد .در حالیکه در حالت دوم خود
متغیر pبه تابع ارسال و در آنجا مقدار می گیرد.
چند جمله ایها12-5
void getPolynomial(polynomial &A) {
int i, n;
term newTerm;
printf("enter number of terms : " );
scanf("%d", &n) ;
if (n > maxTerm) {
printf("sorry! we can save only %d terms.\n", maxTerm);
n = maxTerm ;
}
A.termNo = 0;
for (i=0; i<n; i++) {
printf("enter coefficient and exponent : ");
scanf("%f %d", &newTerm.coef, &newTerm.exp) ;
if ( insertTerm(A, newTerm) == 2 ) {
printf("repeated exponent! try again.\n");
i --;
}
}
}
12-5چند جمله ایها
تابع بعدی printPolynomial ،است که یک چند جمله ای را دریافت و آن
را چاپ می کند.
در این تابع برای نشان دادن توان از عالمت ^ استفاده شده است.
عالمت +در مشخصه تبدیل %+7.2fباعث میشود که عالمت عدد
(مثبت یا منفی) نیز چاپ گردد.
{ )void printPolynomial(const polynomial &A
;int i
)for (i=0; i<A.termNo; i++
; )printf("%+7.2f x^%d",A.terms[i].coef,A.terms[i].exp
}
12-5چند جمله ایها
اکنون نوبت به یکی از مهمترین توابع برنامه ،یعنی تابع جمع دو چند جمله ای
رسیده است .همان طور که می دانید در ریاضیات برای جمع دو چند جمله ای )A(x
و ) B(xبه صورت زیر عمل می شود:
برای جمله های مشترک (جمله هایی که توانهای مشابه دارند) ضرایب دو جمله با
یکدیگر جمع شده و ضریب حاصل با همان توان مشترک به چند جمله ای حاصلجمع
اضافه می شود.
جمله های غیر مشترک ،عینا به چند جمله ای حاصلجمع اضافه می شوند.
به مثال زیر توجه کنید:
A( x) 7 x 8 4 x 6 8 x 4 3 x 9
B( x) 8 x 5 2 x 4 2 x 2 7 x
C ( x) A( x) B( x) 7 x 8 4 x 6 8 x 5 10 x 4 2 x 2 10 x 9
12-5چند جمله ایها
برای پیاده سازی تابع جمع ،باید جمله های مشترک بین دو چند جمله ای که
دارای توان یکسان هستند را پیدا کنیم.
اما خوشبختانه از آنجا که چندجمله ایها برحسب توان مرتب هستند ،نیازی به
جستجوی هر جمله از یک چندجمله ای در دیگری نیست .راه ساده تر آن است
که مشابه عمل ادغام دو لیست مرتب عمل نماییم (به فصل آرایهها مراجعه
نمایید).
طرح کلی روش به صورت زیر است:
یک شمارنده برای آرایه termsهریک از دو چند جمله ای در نظر بگیرید و آنها را برابر
صفر قرار دهید.
در هر مرحله ،توان دو جمله ای را که شمارندهها برروی آنها قرار دارند ،با یکدیگر
مقایسه نمایید .در صورت مساوی بودن توانها ،ضرایب را با هم جمع و حاصل را به
چند جمله ای جواب اضافه کنید ،سپس هر دو شمارنده را یک واحد افزایش دهید؛
در غیر اینصورت جمله ای را که توان بزرگتری دارد به چندجمله ای جواب اضافه کرده
و شمارنده مربوط به آن را یک واحد افزایش دهید .این عملیات را تا پایان یافتن یکی
از دو چند جمله ای تکرار کنید.
در پایان ،تمام عناصر باقیمانده از چندجمله ای ناتمام را به ترتیب به چندجمله ای
جواب منتقل نمایید.
12-5چند جمله ایها
…
…
C
7 4 8 10 2 10 9
8 6 5 4 2 1 0
B
… 7
… 1
8 2 2
5 4 2
A
… 7 4 8 3 9
… 8 6 4 1 0
چند جمله ایها12-5
polynomial addPolynomial(const polynomial &A, const polynomial &B) {
polynomial C ; // چندجمله ای حاصلجمع
int i, j, k;
float newCoef;
i = j = k = 0;
while (i < A.termNo && j < B.termNo) {
if (A.terms[i].exp > B.terms[j].exp) {
C.terms[k] = A.terms[i] ;
i++; k++;
}
else if (A.terms[i].exp < B.terms[j].exp) {
C.terms[k] = B.terms[j] ;
j ++; k ++;
}
else {
newCoef = A.terms[i].coef + B.terms[j].coef ;
if (newCoef != 0) {
C.terms[k].coef = newCoef ;
C.terms[k].exp = A.terms[i].exp;
k ++;
}
i ++; j ++;
}
} //end while
for (; i<A.termNo; i++, k++)
C.terms[k] = A.terms[i] ;
for (; j<B.termNo; j++, k++)
C.terms[k] = B.terms[j] ;
C.termNo = k;
return(C);
}
12-5چند جمله ایها
نوشتن تابع ضرب کمی مشکلتر است.
برای ضرب دو چندجمله ای در یکدیگر ،باید هریک از جمله های چند جمله ای اول،
در هر یک از جمله های چند جمله ای دوم ضرب شود .برای ضرب دو جمله نیز ،ابتدا
ضرایب آن دو را در هم ضرب و سپس توانهای آنها را با هم جمع می کنیم.
به مثال زیر دقت کنید:
A( x) 4 x 6 2 x 4 3 x
B( x) 8 x 5 2 x 2 7 x
C ( x) A( x) B( x) 32 x11 16 x 9 8 x 8 28 x 7 28 x 6 14 x 5 6 x 3 21x 2
چند جمله ایها12-5
polynomial multiplyPolynomial(const polynomial &A, const polynomial &B) {
polynomial C;
//چندجملهای حاصلضرب
term multTerm ;
int i, j, position;
C.termNo = 0;
for (i=0; i<A.termNo; i++)
for (j=0; j<B.termNo; j++) {
//محاسبه حاصلضرب دو جمله
multTerm.coef = A.terms[i].coef * B.terms[j].coef ;
multTerm.exp = A.terms[i].exp + B.terms[j].exp ;
//جستجوی توان جمله جدید در چندجملهای حاصلضرب
position = searchExponent(C, multTerm.exp) ;
if (position == -1)
//جمله جدید در چندجملهای حاصلضرب وجود ندارد
insertTerm(C, multTerm);
else {
//جمله جدید در چندجملهای حاصلضرب وجود دارد
C.terms[position].coef += multTerm.coef ;
if (C.terms[position].coef == 0)
deleteTerm(C, C.terms[position].exp) ;
}
}
return(C);
}
چند جمله ایها12-5
برای ضرب یک جمله در یک چند جمله ای نیز می توان به صورت باال
با این تفاوت که تنها یک جمله باید در چند جمله ای ضرب،عمل کرد
. بنابراین ترتیب جملهها به هم نخواهد خورد،شود
polynomial multiplyTerm(const polynomial &A, term T) {
polynomial B ;
int i;
for (i=0; i<A.termNo; i++) {
B.terms[i].coef = A.terms[i].coef * T.coef ;
B.terms[i].exp = A.terms[i].exp + T.exp ;
}
B.termNo = A.termNo ;
return (B);
}
12-5چند جمله ایها
تابع بعدی getCoefficientنام دارد.
این تابع ،یک چندجمله ای و یک توان را دریافت ،و ضریب مربوط به آن
توان در چند جمله ای را باز می گرداند.
به یاد داشته باشید که چنانچه یک توان در چند جملهای وجود نداشته
باشد ،ضریب مربوط به آن برابر صفر است.
{ )float getCoefficient(const polynomial &A, int exponent
;int position
; )position = searchExponent(A, exponent
;)if (position == -1) return(0
; )else return(A.terms[position].coef
}
12-5چند جمله ایها
تابع بعدی polynomialDegree ،نام داشته و درجه چند جمله ای را باز می
گرداند.
از آنجا که چند جمله ای بر حسب توان مرتب است ،این تابع بسیار ساده
بوده و تنها توان اولین جمله را باز می گرداند.
{ )int polynomialDegree(const polynomial &A
;)if (A.termNo == 0) return(0
;)else return(A.terms[0].exp
}
چند جمله ایها12-5
است که مقدار چند جمله ای را به ازای یک مقدارevaluatePolynomial ،و آخرین تابع
. محاسبه می کندx مشخص برای
float evaluatePolynomial(const polynomial &A, float x) {
float result = 0;
int i;
for (i=0; i<A.termNo; i++)
result += A.terms[i].coef * power(x, A.terms[i].exp) ;
return(result) ;
}
float power(float base, int exp) {
int i;
float result = 1;
for (i=0; i<exp; i++)
result *= base ;
return(result) ;
}
12-5چند جمله ایها
حال که کلیه ساختارها و توابع مورد نیاز را تعریف کردیم ،می
توانیم آنها را در داخل یک فایل جداگانه مانند poly.hقرار داده و
سپس در برنامه هایی که نیاز به کار با چندجمله ایها دارند ،از
این فایل استفاده نماییم.
برای استفاده از این فایل ،ابتدا باید با استفاده از دستور
#includeآن را در برنامه موردنظر گنجانید.
در این قسمت فقط تعریف توابع و نحوه کار آنها تشریح گردید،
نوشتن برنامه ای که از این توابع استفاده نماید را به عنوان
تمرین به خود شما واگذار می نماییم.
7-6اتحادها
اتحاد یا ،unionمحلی از حافظه است که توسط دو یا چند متغیر مختلف
به طور مشترک استفاده می شود.
کاربرد اتحادها در مواردی است که می خواهید از یک مکان حافظه،
تحت شرایط مختلف ،با نوعهای متفاوتی استفاده نمایید.
به عنوان مثال فرض کنید در یک کارخانه برای یک قطعه تولیدی دانستن
نوع قطعه (یک کاراکتر) ،یا وزن قطعه (عدد اعشاری) و یا شماره رنگ آن
(عدد صحیح) کافی است ،و با داشتن یکی از این سه مورد می توان
قطعه را شناسایی کرد .اکنون قصد داریم متغیری تعریف کنیم که بتواند
هریک از این سه داده را نگهداری کند.
مسلم است که تعریف این متغیر به صورت ساختار باعث اتالف حافظه
می گردد ،چرا که فضای الزم برای هر سه داده ایجاد خواهد شد .راه
حل درست ،استفاده از یک اتحاد است.
7-6اتحادها
تعریف اتحادها تقریبا همانند ساختارها صورت می پذیرد ،با این تفاوت
که به جای کلمه کلیدی ،structاز کلمه unionاستفاده می شود.
به عنوان مثال می توان برای قطعه تولیدی موردنظر ،اتحاد زیر را تعریف
کرد :
{ union product
; char kind
; int color
; float weight
;}
; product p
7-6اتحادها
برای دسترسی به عناصر متغیر ،pمی توان از همان عالمت نقطه استفاده
کرد.
اما نکته مهم آن است که در هر لحظه فقط از یک عضو pمی توان استفاده
نمود.
دلیل این امر آن است که هر سه متغیر color ،kindو weightاز محل حافظه
مشترکی استفاده می نمایند ،در نتیجه نوشتن بر روی یکی از آنها ،مقادیر
سایرین را از بین خواهد برد.
در حقیقت هنگامی که متغیری از نوع یک اتحاد تعریف می کنید ،کامپایلر
فضای حافظه ای به اندازه بزرگترین نوع موجود در اتحاد را به آن تخصیص می
دهد.
به عنوان مثال ،نحوه ذخیره متغیر pدر حافظه در شکل زیر نشان داده شده
است.
color
byte 3
byte 2
byte 1
byte 0
kind
weight
p
اتحادها7-6
. به برنامه زیر و خروجیهای آن توجه نمایید،برای آشنایی بیشتر با اتحادها
#include <stdio.h>
union product {
char kind ;
int color ;
float weight ;
};
void main() {
product p;
p.weight = 4200.500 ;
printf("p.weight = %f \n\n", p.weight);
p.color = 10;
printf("p.color = %d \n", p.color);
printf("now p.weight = %f \n\n", p.weight);
p.kind = 'M';
printf("p.kind = %c \n", p.kind);
printf("now p.color = %d and p.weight = %f \n\n", p.color, p.weight);
}
7-6اتحادها
p.weight = 4200.500000
p.color = 10
now p.weight = 4192.004883
p.kind = M
now p.color = 77 and p.weight = 4192.037598
همان گونه که می بینید ،با تغییر هر عضو از متغیر ،pمقدار اعضای دیگر آن
نیز تغییر می کند که دلیل آن استفاده مشترک از یک مکان حافظه است.
بار دیگر یادآوری می شود که مسئولیت استفاده درست از یک متغیر اتحاد
به عهده برنامهنویس است ،و خود وی باید از عضو مناسب متغیر استفاده
نماید.
مبانی کامپیوتر و برنامه سازی
فصلسیزدهم :فایلها
مدرس :رضارمضانی
13فایلها
همانگونه که میدانید در زبانهای برنامه سازی از جمله ،Cکلیه متغیرها در
حافظه اصلی ) (RAMذخیره میگردند.
اگرچه حافظه اصلی دارای سرعت بسیار باالیی است ،اما با پایان یافتن اجرای
برنامه اطالعات آن از بین میروند.
با این وجود در بسیاری از برنامه ها ،باید اطالعات برای اجرای بعدی برنامه
حفظ شوند .به عنوان مثال در یک سیستم دانشجویی ،باید اطالعات
دانشجویان و دروس آنها بر روی یک حافظه پایدار ذخیره گردد ،تا با اتمام برنامه
اطالعات آنها از بین نرود.
راه حل این مشکالت استفاده از حافظه پایداری به نام حافظه جانبی است.
اگرچه حافظههای جانبی سرعت بسیار کمتری نسبت به حافظه اصلی دارند،
اما قادرند اطالعات خود را حتی پس از قطع جریان برق نیز حفظ نمایند.
انواع مختلفی از حافظههای جانبی وجود دارد ،مانند :دیسک سخت و نرم،
نوار ،دیسکهای نوری ) (CDو اخیرا نیز Flash Diskها.
واحد ذخیره سازی اطالعات بر روی حافظههای جانبی فایل است .فایل
مجموعهای از دادهها است که به نحوی با یکدیگر در ارتباط هستند.
برای ذخیره اطالعات یک برنامه ،کافی است یک فایل بر روی حافظه جانبی
ایجاد کرده و دادهها را در آن ذخیره نماییم .در این فصل با نحوه انجام این کار
آشنا خواهیم شد.
13-1انواع فایلها
فایلها به دو دسته اصلی تقسیم میگردند:
فایلهای متنی )(text
فایلهای دودویی )(binary
درک تفاوت بین این دو بسیار مهم است .برای روشن شدن موضوع ،به یک
مثال ساده توجه کنید .فرض کنید یک متغیر صحیح به صورت زیر تعریف کرده
ایم:
;int a = 9281
همانگونه که میدانید این متغیر در حافظه به صورت یک عدد دودویی ذخیره
میگردد.
اگر برنامه در محیط DOSباشد ،برای این متغیر دو بایت حافظه به صورت شکل
زیر در نظر گرفته شده و عدد 9281به صورت دودویی در آن ذخیره میگردد.
a 00100100 01000001
13-1انواع فایلها
حال فرض کنیم یک فایل دودویی به نام ،test.datو یک فایل متنی بنام
test.txtایجاد کرده و متغیر aرا در آنها نوشته ایم.
در فایل دودویی ،test.datمتغیر aدقیقا به همان شکلی که در حافظه
قرار دارد ،ذخیره میگردد
البته در هنگام ذخیره در فایل ،ابتدا بایت ارزش پایینتر و سپس بایت با
ارزش باالتر ذخیره میگردد.
بنابراین این متغیر دو بایت فضا را در فایل اشغال خواهد کرد.
اما در فایل متنی ،test.txtکلیه اطالعات به صورت رشتهای از کاراکترها
ذخیره میگردند.
بنابراین متغیر صحیح ،aابتدا به رشته " "9281تبدیل شده و سپس این
چهار کاراکتر به ترتیب در فایل نوشته میشوند.
در حقیقت در این فایل متنی ،به ترتیب کدهای اسکی مربوط به
کاراکترهای ' '8' ،'2' ،'9و سرانجام ' '1نوشته خواهد شد.
درنتیجه این متغیر ،چهار بایت فضا را در فایل اشغال خواهد کرد.
13-1انواع فایلها
مزیت فایلهای متنی نسبت به فایلهای دودویی آن است که میتوان آنها را
توسط هر ویرایشگر متنی (مانند Editو یا )Notepadباز کرد و محتوای آنها را
دید.
توجه کنید که یک ویرایشگر متنی ،فایل را به صورت رشتهای از کاراکترها
میبیند ،و آن را به صورت کاراکتر به کاراکتر میخواند.
چنانچه یک فایل دودویی توسط یک ویرایشگر متنی باز شود ،اطالعات به شکل
نادرستی نمایش داده خواهند شد و کاربر فقط مجموعهای از کاراکترهای
نامفهوم را خواهد دید.
به عنوان مثال شکل زیر نحوه نمایش دو فایل test.datو test.txtرا توسط یک
ویرایشگر متنی نشان میدهد.
A$
test.dat
9281
test.txt
13-1انواع فایلها
اگر به نحوه نمایش فایل test.datدقت کنید ،میبینید که این فایل حاوی دو
کاراکتر (بایت) میباشد.
کاراکتر ،Aدارای کد اسکی 65است که در مبنای دو برابر 01000001میباشد.
کاراکتر $نیز دارای کد اسکی 36است که در مبنای دو برابر 00100100خواهد شد.
حال اگر این دو عدد را با نحوه ذخیره متغیر aدر حافظه مقایسه کنید ،متوجه خواهید
شد که این دو همان بایتهای تشکیل دهنده عدد 9281هستند.
به طور کلی ،یک فایل دودویی توسط ویرایشگر متنی قابل خواندن نیست و
فقط توسط خود برنامه میتواند خوانده شده و تفسیر گردد.
از آنجا که فایلهای متنی توسط هر ویرایشگری قابل خواندن هستند ،معموال از
آنها برای ذخیره گزارش و یا اطالعاتی که باید توسط کاربر قابل مشاهده و چاپ
باشد ،استفاده میگردد.
به فایلهای متنی ،فایلهای قالب بندی شده نیز گفته میشود ،چرا که
برنامهنویس میتواند دادهها را در یک قالب دلخواه و با شکل مناسب برای کاربر
در فایل قرار دهد.
مثال در صورت لزوم میتوان دادهها را در داخل یک جدول با عنوانهای مناسب
قرار داد ،تا کاربر راحت تر بتواند از آنها استفاده نماید.
13-1انواع فایلها
اما در مقابل ،فایلهای دودویی دنبالهای از بایتهای خام هستند که فقط
توسط برنامه میتوانند خوانده شده و تفسیر گردند.
یک فایل دودویی از خارج برنامه قابل استفاده نمیباشد ،چرا که یک
فایل دودویی 200بایتی ،میتواند 100عدد صحیح (دو بایتی) و یا 50
عدد اعشاری ( 4بایتی) باشد.
بنابراین تنها خود برنامهنویس میداند اطالعات را چگونه ذخیره کرده
است ،و به چه نحوی باید آنها را خوانده و تفسیر نماید.
توجه کنید که در فایلهای دودویی مواردی همچون فاصله گذاری بین
اطالعات ،رفتن به خط بعد ،جدول کشی و ...معنا ندارد ،چرا که این
فایلها از خارج برنامه قابل مشاهده و چاپ نیستند.
اما فایلهای دودویی دارای مزایای متعدی نسبت به فایلهای متنی
هستند که باعث شده است برنامهنویسان در اغلب موارد از این دسته
فایلها برای ذخیره و بازیابی اطالعات استفاده نمایند.
13-1انواع فایلها
این مزایا عبارتند از:
سرعت باال :
این فایلها دادهها را به همان نحوی که در حافظه ذخیره میشوند ،نگهداری
میکنند.
بنابراین در هنگام نوشتن یا خواندن دادهها به/از فایل ،نیازی به تبدیل دادهها
به رشته کاراکتری و بالعکس نمیباشد .درحالیکه در فایلهای متنی ،انجام
این تبدیل ها ،باعث پایین آمدن سرعت میگردد.
حجم کمتر:
فایلهای دودویی حجم کمتری را نسبت به فایلهای متنی اشغال میکنند،
چرا که معموال تبدیل اعداد صحیح و یا اعشاری به رشته ،باعث میشود که
تعداد بایتهای بیشتری اشغال گردد.
به عنوان مثال عدد اعشاری 1456.321که در فایل دودویی به صورت یک داده
چهار بایتی ذخیره میگردد ،دریک فایل متنی احتیاج به یک رشته هشت
بایتی دارد (برای ذخیره رشته ".)"1456.321
13-1انواع فایلها
امکان دسترسی تصادفی:
فرض کنید یک فایل متنی حاوی تعدادی عدد صحیح داریم .حال قصد داریم
دهمین عدد این فایل را بخوانیم .برای این کار باید از ابتدای فایل شروع
کرده و نه عدد اول را خوانده و کنار بگذاریم تا به عدد دهم برسیم.
چرا که تعداد بایتهای تخصیص داده شده به هر عدد مشخص نیست و
بستگی به تعداد ارقام آن دارد (مثال 12نیاز به دو بایت و 6714نیاز به چهار
بایت دارند).
به این روش دسترسی به داده ها ،دسترسی ترتیبی گفته میشود.
اما در یک فایل دودویی مشابه ،میتوان بالفاصله به ابتدای دهمین عدد
پرش کرده و آن را خواند؛ چرا که هر عدد صحیح دقیقا دو بایت را اشغال
میکند ،بنابراین چنانچه 18بایت از ابتدای فایل جلو برویم ،به عدد دهم
خواهیم رسید.
به این روش دسترسی به داده ها ،دسترسی تصادفی یا مستقیم
گفته میشود .در قسمتهای بعدی ،روش دسترسی تصادفی را بررسی
خواهیم کرد.
اطالعات از بیرون برنامه قابل خواندن و بروزرسانی نیستند.
این خاصیت در مواردی که دادهها مهم بوده و نمیخواهیم سایر افراد از
بیرون برنامه به اطالعات دسترسی داشته باشند ،مفید است.
13-2نحوه دستیابی به فایلها
برای دستیابی به یک فایل ،باید مراحل زیر انجام گردد:
تعریف یک متغیر برای فایل
باز کردن فایل
خواندن و نوشتن در فایل
بستن فایل
کلیه دادهها و توابع الزم برای عملیات فوق در فایل سرآمد
stdio.hتعریف شده اند ،بنابراین برای کار با فایلها باید این
فایل را در برنامه گنجاند.
در ادامه ،هریک از موارد فوق مورد بررسی قرار گرفته اند.
13-2-1تعریف فایل
برای تعریف یک فایل ،باید از نوع دادهای بنام FILEاستفاده نمایید.
در حقیقت نوع داده ،FILEیک ساختار است که در فایل stdio.hتعریف
شده است .این ساختار حاوی اطالعاتی همچون :
نام خارجی فایل بر روی دیسک :نام واقعی فایل در سیستم عامل
نوع فایل :ورودی ،خروجی ،دودویی ،متنی
آدرس محل بافر اطالعات :مکانی از حافظه است که در هنگام عملیات
ورودی و خروجی ،به عنوان واسط بین برنامه و فایل استفاده میشود.
اشاره گر به مکان فعلی فایل :شماره بایتی از فایل را نشان میدهد که
در اولین عمل خواندن یا نوشتن بعدی ،استفاده خواهد شد .از این
اشاره گر در قسمتهای بعدی برای دسترسی تصادفی استفاده خواهیم
کرد.
وضعیت خطاها مانند رسیدن به آخر فایل ،خطا در خواندن یا نوشتن و ...
توجه کنید که در حقیقت نیازی به دانستن عناصر ساختار FILEو نام
آنها نیست ،چرا که کلیه عملیات توسط توابع زبان Cانجام خواهد شد و
موارد گفته شده تنها برای آشنایی بیشتر شما با این ساختار بود.
13-2-1تعریف فایل
برای استفاده از یک فایل در برنامه ،ابتدا باید متغیری از نوع اشاره گر به
ساختار FILEتعریف نمایید .به عنوان مثال به تعریف زیر توجه کنید:
; FILE *inputFile
این تعریف ،متغیر inputFileرا از نوع اشاره گر به FILEتعریف مینماید.
در مورد اشاره گرها و نحوه تعریف آنها در فصل آینده بیشتر توضیح خواهیم
داد .در حال حاضر فقط کافی است بدانیم از عملگر * برای تعریف یک اشاره
گر استفاده میشود.
13-2-2باز کردن فایل
برای استفاده از یک فایل ،ابتدا باید آن را باز کنید و یا به عبارت بهتر ،آن را به
برنامه خود مربوط کنید.
برای این کار از تابعی بنام fopenاستفاده میشود .اعالن این تابع به صورت
زیر است:
; )][FILE* fopen(char fileName[], char mode
پارامتر ،fileNameرشتهای است که نام خارجی فایل در سیستم عامل را
مشخص میکند.
این رشته میتواند عالوه بر نام فایل ،حاوی مسیر فایل نیز باشد.
به عنوان مثال چنانچه این رشته به صورت ""letter.txtمشخص شود ،در فهرست
جاری به دنبال این فایل جستجو میشود.
اما رشتهای مانند " ،"C:\\myLetters\\letter.txtآدرس فیزیکی فایل را به طور کامل
مشخص مینماید.
پارامتر دوم یا ،modeرشتهای است که نحوه باز شدن فایل را نشان میدهد.
این رشته از دو قسمت تشکیل میگردد.
قسمت اول ،عملیات قابل اجرا بر روی فایل را نشان میدهد و یکی از موارد
مشخص شده در جدول صفجه بعد است
13-2-2باز کردن فایل
توضیحات
رشته
عمل قابل اجرا
r
خواندن
فایل باید موجود باشد ،در غیر اینصورت NULLباز
گردانده میشود.
w
نوشتن
یک فایل جدید ایجاد میشود ،اگر فایل از قبل موجود
باشد ،اطالعات آن از بین خواهد رفت.
a
افزودن به انتها
r+
خواندن و نوشتن
فایل باید موجود باشد ،در غیر اینصورت NULLباز
گردانده میشود.
w+
نوشتن و خواندن
یک فایل جدید ایجاد میشود ،اگر فایل از قبل موجود
باشد ،اطالعات آن از بین خواهد رفت.
a+
افزودن و خواندن
اگر فایل موجود باشد ،اطالعات به انتهای آن اضافه
میگردد ،درغیراینصورت ایجاد میگردد.
اگر فایل موجود باشد ،اطالعات به انتهای آن اضافه
میگردد ،درغیراینصورت ایجاد میگردد.
13-2-2باز کردن فایل
و اما قسمت دوم رشته ،modeنوع فایل را نشان میدهد و یکی از موارد
مشخص شده در شکل زیر است.
رشته
نوع فایل
t
متنی
b
دودویی
توضیحات
فایل بصورت متنی باز شود.
فایل بصورت دودویی باز شود.
این دو قسمت به یکدیگر متصل شده و تشکیل رشته نحوه باز شدن فایل را
میدهند.
به عنوان نمونه ،رشته " "wbبه معنای نوشتن بر روی یک فایل دودویی
است.
الزم به ذکر است که اگر قسمت دوم ذکر نشود ،به طور پیش فرض از نوع
متنی ) (tدر نظر گرفته میشود .به عنوان مثال " "r+به معنای خواندن و
نوشتن بر روی یک فایل متنی است.
13-2-2باز کردن فایل
تابع ،fopenابتدا حافظه الزم برای یک ساختار از نوع FILEرا تخصیص داده و
سپس با توجه به آرگومانهای ارسالی (نام واقعی فایل و نحوه باز شدن آن)،
عناصر آن را مقدار دهی اولیه میکند ،و اشاره گری به آن را باز میگرداند.
اکنون میتوان این اشاره گر بازگشتی را به یک متغیر از نوع اشاره گر به FILE
نسبت داده و از آن برای عملیات خواندن و نوشتن در فایل استفاده کرد.
به مثال زیر دقت کنید:
;FILE *inputFile
;)"inputFile = fopen("letter.txt", "rt
در این مثال ،تابع fopenفایلی بنام letter.txtرا به صورت متنی و برای خواندن
باز کرده و یک اشاره گر به FILEباز میگرداند ،که آن را در متغیری به نام
inputFileقرار داده ایم.
به عنوان یک مثال دیگر ،به نمونه زیر دقت کنید:
;FILE *itemFile
;)"itemFile = fopen("C:\\data\\items.dat","w+b
این دستور ،فایلی از آدرس C:\data\items.datرا برای خواندن و نوشتن ،به
صورت دودویی باز کرده و اشاره گر FILEآن را در متغیر dataFileقرار میدهد.
13-2-2باز کردن فایل
توجه کنید که چنانچه تابع fopenبه هر دلیلی نتواند فایل مورد نظر را باز
کند (مثال به دلیل موجود نبودن فایل یا فهرست مورد نظر ،یا پر بودن
دیسک و ،)...مقدار ( NULLکه برابر با 0است) را باز میگرداند.
بنابراین بهتر است پس از باز کردن فایل ،ابتدا درستی باز شدن آن
بررسی گردد .به مثال زیر توجه کنید:
;FILE *inputFile
;)"inputFile = fopen("letter.txt", "rt
{ )if (inputFile == NULL
;)"printf("can't open file!\n
; )exit(1
}
البته معموال برنامهنویسان قسمت شرط ifرا بصورت زیر مینویسند:
… { )if (!inputFile
13-2-3بستن فایل
پس از آنکه عملیات ما بر روی فایل خاتمه پیدا کرد ،باید آن را توسط تابع
fcloseببندیم.
اعالن این تابع به شکل زیر است:
)int fclose(FILE *f
این تابع ،یک اشاره گر به فایل را به عنوان ورودی دریافت و آن را میبندد.
در صورت موفقیت در بستن فایل مقدار ،0و در صورت عدم موفقیت مقدار
EOFبازگردانده میشود EOF .یک ثابت نمادی است که برابر مقدار -1
تعریف شده است.
به عنوان مثال دستور زیر ،فایلی که توسط متغیر inputFileبه آن اشاره
میشود را میبندد:
; )fclose(inputFile
13-2-3بستن فایل
با بستن یک فایل ،ارتباط آن با برنامه قطع میشود .چندین دلیل برای
بستن فایل وجود دارد:
تعداد فایلهایی که میتوانند به طور همزمان باز باشند ،محدود است و
در نتیجه هرگاه به فایلی نیاز نداریم ،بهتر است آن را ببندیم.
هنگام عملیات نوشتن در فایل ،دادهها ابتدا در یک بافر در حافظه نوشته
میشوند (به قسمت تعریف فایل مراجعه کنید) ،سپس هنگامی که
بافر پر شد ،به طور یکجا به فایل ارسال میشوند.با بستن فایل ،چنانچه
هنوز دادهای در بافر باقی مانده باشد ،به فایل ارسال میشود.
تابع fcloseفضای تخصیص یافته به متغیر اشاره گر فایل را آزاد میکند
که باعث صرفه جویی در حافظه میشود .عالوه براین میتوان از این
متغیر مجددا برای باز کردن یک فایل دیگر استفاده کرد.
البته چنانچه اجرای یک برنامه به طور عادی خاتمه پیدا کند ،خود
کامپایلر تمام فایلهای باز آن را میبندد؛ ولی بهتر است این کار توسط
خود برنامهنویس انجام شود.
13-2-3بستن فایل
برای بستن فایل تابع دیگری نیز وجود دارد .تابع fcloseallتمام فایلهای
باز برنامه را میبندد.
این تابع هیچ آرگومانی دریافت نی کند و اعالن آن به صورت زیر است:
;)(int fcloseall
این تابع در صورت موفقیت ،تعداد فایلهای بسته شده و در صورت عدم
موفقیت ،مقدار EOFرا باز میگرداند.
آخرین نکتهای که به آن اشاره میکنیم ،در مورد بافر است .همانطور که
گفته شد ،با بسته شدن یک فایل ،محتوای بافر آن خالی شده و به
فایل اصلی ارسال میگردد .اما چنانچه پیش از بستن فایل بخواهیم
بافر را خالی کنیم ،میتوانیم از تابع fflushاستفاده نماییم.
اعالن این تابع به صورت زیر است:
)int fflush(FILE *f
این تابع ،بافر فایلی را که توسط پارامتر fبه آن ارسال کرده ایم ،خالی
مینماید .در صورت موفقیت مقدار ،0و درصورت عدم موفقیت مقدار
( EOFیا )-1باز گردانده میشود.
13-2-4ورودی و خروجی در فایلها
از آنجا که نحوه خواندن و نوشتن در فایلهای متنی با فایلهای دودویی متفاوت است ،هریک از آنها را
در یک بخش جداگانه بررسی مینماییم.
قبل از اینکه این مبحث را آغاز نماییم ،باید اشارهای به یک متغیر بسیار مهم ،یعنی اشاره گر به
مکان فعلی فایل داشته باشیم.
همان طور که در قسمتهای قبلی گفته شد ،ساختار FILEدارای عضوی است که به مکان فعلی
فایل اشاره میکند .این عضو ،مکانی از فایل را نشان میدهد که عمل خواندن یا نوشتن بعدی از/به
آنجا انجام خواهد شد.
هنگامی که یک فایل را باز میکنیم ،این اشاره گر به اولین بایت فایل اشاره میکند ،مگر اینکه فایل
را برای اضافه کردن به انتها (با گزینه )aباز کرده باشیم ،که در این صورت به انتهای فایل اشاره
خواهد کرد.
با هر عمل خواندن یا نوشتن ،این اشاره گر به تعداد بایتهای خوانده یا نوشته شده ،جلو میرود.
حال فرض کنید فایلی را برای خواندن باز کرده ایم .با اولین عمل خواندن یک کاراکتر ،اولین بایت آن
خوانده خواهد شد .اما از آنجا که اشاره گر مکان فعلی فایل نیز به اندازه یک بایت جلو میرود ،در
دومین عمل خواندن کاراکتر ،دومین بایت آن فایل خوانده خواهد شد (و نه اولین بایت) و عملیات به
همین شکل ادامه مییابد.
الزم به ذکر است که کلیه این عملیات توسط خود توابع زبان Cانجام خواهد شد ،و نیازی نیست که
برنامهنویس نگران محل اشاره گر مکان فعلی فایل باشد .البته میتوانیم مکان این اشاره گر را
توسط توابعی که معرفی خواهیم کرد ،تغییر دهیم.
روال نوشتن در فایل نیز مشابه خواندن است؛ به این معنا که با هر بار عمل نوشتن یک داده ،اشاره
گر مکان فعلی به تعداد بایتهای آن داده جلو خواهد رفت .به این ترتیب هر داده جدید ،به طور خودکار
در مکان پس از داده قبلی نوشته خواهد شد (و نه بر روی آن).
13-2-4-1ورودی و خروجی در فایلهای متنی
همانطور که قبال نیز گفته شد ،فایلهای متنی توسط هر ویرایشگری
قابل خواندن هستند .لذا برنامهنویس باید بتواند دادهها را در قالب
دلخواه خود در فایل قرار دهد تا توسط کاربران قابل استفاده باشد.
به همین دلیل به فایلهای متنی ،فایلهای قالب بندی شده نیز
میگویند.
اگر به یاد داشته باشید ،عملیات ورودی و خروجی در زبان Cاز طریق دو
تابع scanfو printfانجام میگرفت .برای عملیات ورودی و خروجی در
فایلهای متنی نیز دو تابع مشابه وجود دارد fscanf :و .fprintf
تابع fprintfکه برای نوشتن دادههای خروجی در یک فایل استفاده
میشود ،دارای شکل کلی زیر است:
; )>fprintf(<file-pointer>,<control-string>, <variable-list
ورودی و خروجی در فایلهای متنی13-2-4-1
یا365( ) برنامهای بنویسید که میزان فروش روزانه یک شرکت را برای تمام روزهای سال13-1 مثال
میزان فروش روزانه یک عدد. قرار دهدsales.txt و آنها را دریک فایل متنی به نام، روز) دریافت366
.صحیح است
#include <stdio.h>
void main() {
FILE *outFile ;
int i, day, sales;
outFile = fopen("sales.txt", "wt");
if (!outFile) {
printf("can't open file!");
return ;
}
printf("enter number of days : ");
scanf("%d", &day);
for (i=0; i<day; i++) {
printf("enter daily sales (day %d) : ", i+1);
scanf("%d", &sales) ;
fprintf(outFile, "%d\n", sales);
}
fclose(outFile);
}
13-2-4-1ورودی و خروجی در فایلهای متنی
برای خواندن اطالعات از یک فایل متنی ،از تابع fscanfاستفاده میگردد.
شکل کلی این تابع بصورت زیر است:
; )>fscanf(<file-pointer>,<control-string>, <variable-address-list
تعریف این تابع نیز تقریبا همانند تابع scanfاست ،با این تفاوت که به عنوان
اولین آرگومان ،یک اشاره گر به فایل را دریافت میکند که مشخص کننده
فایلی است که دادهها باید از آن خوانده شوند.
دو آرگومان بعدی دقیقا همانند تابع scanfبوده و نحوه استفاده از آنها نیز
کامال مشابه است .به عنوان نمونه ،به یک مثال توجه کنید.
مثال )13-2برنامهای بنویسید که فایل متنی ،sales.txtمربوط به مثال قبلی
را خوانده و میانگین فروش روزانه شرکت را بر روی نمایشگر چاپ نماید.
13-2-4-1ورودی و خروجی در فایلهای متنی
نکته مهم در حل این مسئله آن است که تعداد اعداد فایل sales.txtاز قبل مشخص نیست (ممکن
است 365یا 366باشد) ،بنابراین برنامه باید عمل خواندن را تا زمانی که به انتهای فایل برسد،
ادامه دهد.
حتما از مطالب مطرح شده در اول این قسمت به یاد دارید که با هر بار عمل خواندن از فایل ،اشاره
گر مکان جاری فایل به اندازه تعداد بایتهای خوانده شده جلو میرود .بنابراین باید عمل خواندن را تا
زمانی که این اشاره گر به انتهای فایل برسد ،ادامه دهیم.
اما چگونه میتوان فهمید که این اشاره گر به انتهای فایل رسیده است؟ خوشبختانه برای این کار
تابعی به نام feofوجود دارد که اعالن آن به صورت زیر است:
;)int feof(FILE *f
این تابع یک اشاره گر به فایل را دریافت کرده ،و در صورتی که اشاره گر مکان فعلی آن به انتهای
فایل رسیده باشد ،مقدار درست (غیر صفر) و درغیر این صورت مقدار نادرست (صفر) را باز
میگرداند.
اما در استفاده از این تابع باید دقت کرد .نکته مهم در اینجا است که این تابع هنگامی مقدار درست
باز میگرداند که در آخرین عمل خواندن با شکست مواجه شده و به پایان فایل رسیده باشد.
برای روشن شدن موضوع ،فرض کنید فایلی دارای 10عدد صحیح است .چنانچه پس از 10بار عمل
خواندن از فایل ،تابع feofرا فراخوانی کنید ،مقدار صفر را باز خواهد گرداند که نشان میدهد هنوز
انتهای فایل را ندیده است .اما چنانچه یک عمل خواندن دیگر (یازدهمین عمل خواندن) انجام دهید،
گرچه این عمل ناموفق خواهد بود چرا که دادهای برای خواندن وجود ندارد ،اما تابع متوجه میگردد
که به انتهای فایل رسیده است .این بار فراخوانی تابع feofمقدار غیرصفر باز خواهد گرداند.
ورودی و خروجی در فایلهای متنی13-2-4-1
#include <stdio.h>
void main() {
FILE *inFile ;
int sales, counter=0 ;
float average = 0.0;
inFile = fopen("sales.txt", "rt");
if (!inFile) {
printf("can't open file!");
return ;
}
fscanf(inFile, "%d",&sales) ;
while ( ! feof(inFile) ) {
average += sales ;
counter ++;
fscanf(inFile, "%d",&sales) ;
}
fclose(inFile);
average /= counter ;
printf("average = %f",average);
}
13-2-4-2ورودی و خروجی در فایلهای دودویی
همانطور که گفته شد ،در فایلهای دودویی اطالعات به صورت بایتهای
خام ذخیره میگردند و به همین دلیل توسط ویرایشگرها قابل خوانده
شدن نیستند.
در این نوع فایلها ،دادهها به صورت پشت سرهم ذخیره میگردند و از
آنجا که اندازه (تعداد بایتهای) هر داده مشخص است ،احتیاجی به
استفاده از کاراکترهای جدا کننده مانند فضای خالی نیست.
به طور کلی برای خواندن و نوشتن در این فایلها ،احتیاج به
ورودی/خروجی قالب بندی شده نداریم ،بلکه به توابعی نیاز داریم که
بتوانند دادهها را به صورت دنبالهای از بایتهای خام به فایل ارسال کنند و
یا از آن بخوانند.
به همین دلیل به این فایلها ،فایلهای قالب بندی نشده نیز گفته
میشود.
برای خواندن و نوشتن در فایلهای دودویی ،دو تابع داریم :
fread
fwrite
13-2-4-2ورودی و خروجی در فایلهای دودویی
اعالن تابع fwriteکه برای نوشتن در فایلهای دودویی به کار میرود ،به شکل
زیر است:
;)size_t fwrite(void *data, size_t size, size_t n, FILE *f
ابتدا توجه کنید که نوع داده ،size_tنام دیگری برای عدد صحیح بدون عالمت
) (unsigned intمیباشد .این نوع داده توسط دستور typedefدر فایل stdio.h
تعریف شده است.
پارامتر ،dataآدرس دادهای است که قصد نوشتن آن در فایل را داریم .همانطور
که پیشتر نیز گفته شد ،برای به دست آوردن آدرس یک متغیر ،از عالمت &
پیش از نام متغیر استفاده مینماییم.
پارامتر ،sizeاندازه دادهای که باید نوشته شود را بر حسب بایت مشخص می
نماید.
پارامتر nنیز تعداد دادههایی که باید نوشته شوند را مشخص میکند .در
قسمتهای بعدی خواهید دید که این تابع می تواند تعدادی داده را در قالب یک
آرایه به طور یکجا در فایل بنویسد.
آخرین پارامتر نیز اشاره گر به فایلی است که قصد نوشتن دادهها در آن را
داریم .این تابع به عنوان خروجی ،تعداد دادههای (نه تعداد بایتهای) نوشته
شده را باز میگرداند.
ورودی و خروجی در فایلهای دودویی13-2-4-2
روز) دریافت366 یا365( ) برنامهای بنویسید که میزان فروش روزانه یک شرکت را برای تمام روزهای سال13-3 مثال
. میزان فروش روزانه یک عدد صحیح است. قرار دهدsales.dat و آنها را دریک فایل دودویی بنام
#include <stdio.h>
void main() {
FILE *outFile ;
int i, day, sales ;
outFile = fopen("sales.dat", "wb");
if (!outFile) {
printf("can't open file!");
return ;
}
printf("enter number of days : ");
scanf("%d", &day);
for (i=0; i<day; i++) {
printf("enter daily sales (day %d): ", i+1);
scanf("%d", &sales) ;
fwrite(&sales, sizeof(int), 1, outFile) ;
}
fclose(outFile);
}
13-2-4-2ورودی و خروجی در فایلهای دودویی
برای خواندن دادهها از یک فایل دودویی ،از تابع freadاستفاده می شود.
اعالن این تابع بصورت زیر است:
;)size_t fread(void *data, size_t size, size_t n, FILE *f
پارامترهای این تابع همانند تابع fwriteمیباشند ،با این تفاوت که اولین
پارامتر ،آدرس داده ای است که باید از فایل خوانده شود.
در ضمن مقدار خروجی این تابع ،تعداد دادههای (و نه بایتهای) خوانده شده
میباشد .به مثال زیر توجه کنید.
مثال )13-4برنامهای بنویسید که فایل دودویی ،sales.datمربوط به مثال
قبلی را خوانده و میانگین فروش روزانه شرکت را بر روی نمایشگر چاپ
نماید.
ورودی و خروجی در فایلهای دودویی13-2-4-2
#include <stdio.h>
void main() {
FILE *inFile ;
int sales, counter=0 ;
float average = 0.0;
inFile = fopen("sales.dat", "rb");
if (!inFile) {
printf("can't open file!");
return ;
}
fread(&sales , sizeof(int), 1, inFile) ;
while ( ! feof(inFile) ) {
average += sales ;
counter ++;
fread(&sales, sizeof(int), 1, inFile) ;
}
fclose(inFile);
average /= counter ;
printf("average = %f",average);
}
13-2-4-3سایر توابع ورودی و خروجی فایل
به جز توابع گفته شده ،توابع دیگری نیز برای ورودی/خروجی در فایلها وجود دارند.
توابع fgetsو fputsبرای خواندن و نوشتن رشتهها در فایل بکار میروند.
اعالن تابع fgetsبه شکل زیر است:
; )char* fgets(char string[], int n, FILE *f
این تابع یک رشته کاراکتری را از فایل fخوانده و در رشته stringقرار میدهد .خواندن
رشته تا زمانی ادامه می یابد که یا در فایل به خط جدید ) (new lineبرسیم ،و یا به تعداد
n-1کاراکتر خوانده شود .یک کاراکتر باقیمانده نیز برای ' '\0درنظر گرفته شده است.
مقدار بازگشتی این تابع ،آدرس رشته خوانده شده است.
اعالن تابع putsنیز به شکل زیر است:
; )int fputs(char string[], FILE *f
دسته دیگر ،توابع fgetcو fputcهستند که به ترتیب برای خواندن و نوشتن یک کاراکتر در
فایل به کار می روند .این توابع هنگامی استفاده می شوند که قصد دارید یک فایل را به
صورت بایت به بایت (کاراکتر به کاراکتر) پردازش نمایید .اگرچه این دو تابع معموال در
فایلهای دودویی استفاده می شوند ،اما گاهی از آنها برای پردازش فایلهای متنی نیز
استفاده می شود.
اعالن این دو تابع بصورت زیر است:
; )int fgetc(FILE *f
; )int fputc(int c, FILE *f
13-3دسترسی مستقیم به فایلها
به دو شکل می توان فایلها را مورد پردازش قرار داد:
پردازش ترتیبی :در این روش ،پردازش از ابتدای فایل شروع
می شود و دادهها یک به یک پردازش می گردند تا به انتهای فایل
برسیم .این روش در مواقعی مناسب است که بخواهیم عملیاتی
را بر روی کل فایل و یا قسمت بزرگی از آن انجام دهیم.
پردازش مستقیم )تصادفی( :در این روش می توان به طور
مستقیم به یک مکان (آدرس) مشخص از فایل دسترسی پیدا
کرد .این روش در مواردی موثر است که بخواهیم عملیاتی را بر
روی برخی داده های مشخص فایل انجام دهیم.
13-3دسترسی مستقیم به فایلها
با توجه به مطالب گفته شده قبلی ،دسترسی مستقیم فقط در مورد
فایلهای دودویی معنا دارد.
چرا که در این فایلها با توجه به ثابت بودن اندازه هر داده ،مکان هر یک
از دادهها کامال مشخص است.
مثال چنانچه فایلی از اعداد صحیح به صورت دودویی داشته باشیم و
فرض کنیم هر عدد صحیح 2بایت باشد ،برای خواندن دهمین عنصر
کافی است اشاره گر مکان جاری فایل را به بایت شماره 18برده
(ابتدای دهمین عدد) و سپس عمل خواندن را انجام دهیم.
اما آیا می توان همین روش را در مورد یک فایل متنی از اعداد صحیح نیز
بکار برد؟ با توجه به اینکه در چنین فایلی ،تعداد بایتهای تخصیص یافته
به هر عدد بستگی به تعداد ارقام آن دارد ،چگونه می توان مکان
(آدرس) دهمین عدد را تعیین کرد؟ مسلما تنها روش آن است که از اول
فایل شروع کرده و عدد اول را بخوانیم تا به جداکننده برسیم ،سپس
عدد دوم و ...تا به دهمین عدد برسیم .بنابراین معموال در فایلهای
متنی از پردازش ترتیبی استفاده می شود.
13-3دسترسی مستقیم به فایلها
به یاد دارید که هر فایل دارای یک اشاره گر به مکان فعلی فایل
است .این اشاره گر به مکانی از فایل اشاره می نماید که
عمل خواندن یا نوشتن بعدی در آن انجام خواهد شد .برای
دسترسی مستقیم به داده های فایل ،نیاز به تابعی داریم که
به ما امکان تغییر مکان این اشاره گر را بدهد.
این تابع fseekنام دارد و اعالن آن به صورت زیر است:
; )int fseek(FILE *f, long int offset, int place
مقادیر مجازی برای پارامتر placeدر جدول صفحه بعد آمده
است:
13-3دسترسی مستقیم به فایلها
مقدار ثابت نمادی
مفهوم
مقادیر مجاز تعداد بایت
0
SEEK_SET
1
عدد مثبت :حرکت رو به جلو
SEEK_CURجابجایی از مکان فعلی
اشاره گر شروع شود عدد منفی :حرکت رو به عقب
2
SEEK_ENDجابجایی از انتهای فایل
شروع شود
جابجایی از ابتدای فایل
شروع شود
0یا یک عدد مثبت برای حرکت
رو به جلو
0یا یک عدد منفی برای حرکت
رو به عقب
توجه کنید که دومین پارامتر ،یعنی offsetمی تواند منفی نیز باشد که به
معنای حرکت رو به عقب است.
این تابع در صورت موفقیت مقدار ،0و در غیر اینصورت یک مقدار غیر صفر باز
می گرداند .به عنوان نمونه به مثال زیر توجه نمایید.
دسترسی مستقیم به فایلها13-3
) برنامه ای بنویسید که میانگین فروش ماه مهر و همچنین میزان فروش در آخرین13-5 مثال
.) استخراج کرده و چاپ نماید13-3 (مربوط به مثالsales.dat روز سال را از فایل
#include <stdio.h>
void main() {
FILE *inFile ;
int i, sales, lastDaySales ;
float averageMehr = 0.0;
inFile = fopen("sales.dat", "rb");
if (!inFile) {
printf("can't open file!");
return ;
}
fseek(inFile, 186 * sizeof(int), SEEK_SET);
//پرش به ابتدای اطالعات ماه مهر
//حلقه مربوط به خواندن میزان فروش ماه مهر و محاسبه میانگین آنها
for (i=0; i<30; i++) {
fread(&sales , sizeof(int), 1, inFile) ;
averageMehr += sales ;
}
دسترسی مستقیم به فایلها13-3
averageMehr /= 30 ;
fseek(inFile, -1L * sizeof(int), SEEK_END);
آخرین روز سال
fread(&lastDaySales , sizeof(int), 1, inFile) ;
printf("average = %f\n",averageMehr);
printf("last day sales = %d",lastDaySales) ;
fclose(inFile);
}
// پرش به میزان فروش
13-3دسترسی مستقیم به فایلها
یکی از مهمترین کاربردهای تابع ،fseekبروز رسانی (انجام تغییرات) فایلهای
دودویی است.
به عنوان مثال فرض کنید اطالع پیدا کرده ایم که در فایل ،sales.datیکی از
فروشهای روزانه به طور اشتباه زیر 100عدد وارد شده است ،و باید به آن 100
واحد اضافه شود.
برای انجام این کار باید از ابتدای فایل شروع به خواندن نماییم تا به اولین داده
کوچکتر از 100برسیم .اما پس از پیدا کردن داده موردنظر ،ابتدا باید اشاره گر
مکان فعلی فایل را به اندازه یک عدد صحیح به عقب بازگردانده و سپس عدد
جدید را بنویسیم ،در غیراینصورت عدد جدید بر روی فروش روز بعد نوشته
خواهد شد.
شکل زیر نحوه انجام کار را بر روی یک فایل نمونه نشان می دهد.
اشاره گر
های
دادهاشاره
بازگرداندن
:4خواندن
3
مرحله 2
جدیدگربر
نوشتن
کردن
داده 1
مرحله
فایلاده
داده
اولین د
ابتدای
بازبه
رسیدن
جاری :به
مکانتا
فایل
نظر
مورد
روی
اینجا )75
fseek
تابع (در
موردنظرازبا100
کوچکتر
…
4231
اشاره گر
اشاره گر
175
75
567
2320
دسترسی مستقیم به فایلها13-3
#include <stdio.h>
void main() {
FILE *updateFile ;
int sales ;
updateFile = fopen("sales.dat", "r+b");
if (!updateFile) {
printf("can't open file!");
return ;
}
fread(&sales , sizeof(int), 1, updateFile) ;
while ( ! feof(updateFile) ) {
if (sales < 100) {
sales += 100 ;
//بازگرداندن اشاره گر به روی داده قبلی
fseek(updateFile, -1L * sizeof(int), SEEK_CUR) ;
//نوشتن مقدار جدید بر روی داده قبلی
fwrite(&sales, sizeof(int), 1, updateFile);
break ;
//خروج از حلقه در صورت پیدا شدن عدد موردنظر
}
fread(&sales, sizeof(int), 1, updateFile) ;
}
fclose(updateFile);
}
13-4خواندن و نوشتن ساختارها در فایل
در بسیاری از موارد ممکن است بخواهید یک ساختار را در یک فایل بنویسید ،یا
آن را از یک فایل بخوانید .نحوه انجام این کار در فایلهای متنی و دودویی کامال
متفاوت است.
برای خواندن یا نوشتن یک ساختار در یک فایل متنی ،باید هریک از اعضای آن
را به طور جداگانه از فایل خواند و یا در آن نوشت.
دلیل این مسئله نیز کامال مشخص است .برای ارسال یک ساختار به یک فایل
متنی ،باید نحوه و قالب نوشتن هر عضو ،به طور جداگانه مشخص شود.
مثال آیا هر عضو در یک ردیف جدا چاپ شود و یا با یک فاصله از عضو بعدی جدا
گردد.
همین مسئله در هنگام خواندن نیز باید رعایت گردد .بنابراین نمیتوان کل یک
ساختار را به طور یکجا در فایل نوشت یا از آن خواند.
مثال )13-6برنامهای بنویسید که مشخصات تعدادی دانشجو را از کاربر دریافت
و آنها را دریک فایل متنی بنام student.txtقرار دهد .اطالعات هر دانشجو شامل
شماره دانشجویی ،نام ،سن و معدل دانشجو میباشد.
خواندن و نوشتن ساختارها در فایل13-4
#include <stdio.h>
struct student {
long int id;
char name[40] ;
int age;
float average ;
};
void main() {
student s;
FILE *studentFile;
int i, n;
studentFile = fopen("student.txt","wt");
if (! studentFile) {
printf("can't open file.");
return ;
}
printf("please enter number of students: ");
scanf("%d", &n);
for (i=0; i<n; i++) {
printf("student %d: enter id, name, age and average: ", i+1);
scanf("%ld %s %d %f", &s.id, s.name, &s.age, &s.average);
fprintf(studentFile,"%ld %s %d %5.2f\n",s.id, s.name, s.age, s.average);
}
fclose(studentFile);
}
13-4خواندن و نوشتن ساختارها در فایل
اما خوشبختانه روش کار در فایلهای دودویی بسیار ساده تر است.
برای خواندن یا نوشتن یک ساختار در یک فایل دودویی ،میتوان آن
ساختار را به صورت یکجا از فایل خواند یا در آن نوشت.
هنگامی که یک ساختار را در یک فایل دودویی مینویسیم ،کلیه
بایتهای عناصر آن بصورت پشت سرهم و بدون هیچ فاصلهای در فایل
نوشته میشوند.
همانطور که قبال نیز گفتیم ،از آنجا که تعداد بایتهای هر یک از عناصر
ساختار کامال مشخص است ،نیازی به استفاده از یک جداکننده نیست.
در هنگام خواندن یک ساختار از فایل نیز ،همان عناصر (با تعداد بایتهای
مشخص) به صورت پشت سرهم از فایل خوانده میشوند.
مثال )13-7برنامهای بنویسید که مشخصات تعدادی دانشجو را از کاربر
دریافت و آنها را دریک فایل دودویی بنام student.datقرار دهد .اطالعات
هر دانشجو شامل شماره دانشجویی ،نام ،سن و معدل دانشجو
میباشد.
خواندن و نوشتن ساختارها در فایل13-4
#include <stdio.h>
struct student {
long int id;
char name[40] ;
int age;
float average ;
};
void main() {
student s;
FILE *studentFile;
int i, n;
studentFile = fopen("student.dat","wb");
if (! studentFile) {
printf("can't open file.");
return ;
}
printf("please enter student number : ");
scanf("%d", &n);
for (i=0; i<n; i++) {
printf("student %d: enter id, name, age and average: ", i+1);
scanf("%ld %s %d %f", &s.id, s.name, &s.age, &s.average);
fwrite(&s, sizeof(student), 1, studentFile) ;
}
fclose(studentFile);
}
13-4خواندن و نوشتن ساختارها در فایل
مثال )13-8برنامهای برای بروزرسانی اطالعات دانشجویان بنویسید .این برنامه باید یک شماره
دانشجویی را دریافت و در فایل دودویی دانشجویان ) (student.datبه دنبال آن جستجو نماید .سپس
در صورت پیدا شدن دانشجو ،اطالعات قبلی شامل نام ،سن و معدل وی را با اطالعات جدیدی که
از کاربر دریافت میکند ،جایگزین نماید.
>#include <stdio.h
تعریف ساختار studentهمانند برنامه مثال 13-7میباشد//
{ )(void main
;student s
;FILE *studentFile
;int i
; long int searchId
;)"studentFile = fopen("student.dat","r+b
{ )if (! studentFile
;)"printf("can't open file.
; return
}
;)" printf("please enter student id :
;)scanf("%ld", &searchId
دریافت شماره دانشجویی موردنظر//
خواندن و نوشتن ساختارها در فایل13-4
//جستجو به دنبال شماره دانشجویی موردنظر در فایل دانشجویان
do
fread(&s, sizeof(student), 1, studentFile) ;
while (!feof(studentFile) && s.id != searchId) ;
if (feof(studentFile)) //دانشجو پیدا نشده است
printf("student not found!\n");
else {
//دانشجو پیدا شده است
printf("student found!\n");
printf("enter new name, age and average : ");
scanf("%s %d %f", s.name, &s.age, &s.average);
//پرش بر روی اطالعات دانشجوی قبلی
fseek(studentFile, -1L * sizeof(student), SEEK_CUR);
//نوشتن اطالعات دانشجوی جدید بر روی اطالعات دانشجوی قبلی
fwrite(&s, sizeof(student), 1, studentFile) ;
}
fclose(studentFile);
}
13-5خواندن و نوشتن آرایهها در فایل
یکی دیگر از مسائلی که ممکن است با آن مواجه شوید ،خواندن یا نوشتن کل
محتویات یک آرایه در یک فایل است.
به عنوان یک مثال ساده فرض کنید یک آرایه از اعداد اعشاری به صورت زیر
تعریف شده است:
; ]float data[100
پس از آنکه در برنامه به نحوی به این آرایه مقداردهی کرده ایم ،قصد داریم آن
را به طور کامل در یک فایل ذخیره کنیم.
در اینجا نیز نحوه ذخیره آرایه دریک فایل متنی با یک فایل دودویی متفاوت
است.
برای ذخیره یک آرایه در یک فایل متنی ،باید هریک از عناصر آرایه را به طور
جداگانه در فایل نوشت .بنابراین برای این کار نیاز به استفاده از یک حلقه داریم.
قطعه برنامه زیر نحوه انجام این کار را نشان میدهد:
;)"outFile = fopen("data.txt", "wt
)for (i=0; i<100; i++
; )]fprintf(outFile, "%f ", data[i
13-5خواندن و نوشتن آرایهها در فایل
خواندن آرایه از یک فایل متنی نیز به همین صورت انجام میپذیرد .یعنی عناصر آرایه باید
به صورت تک به تک از آرایه خوانده شوند.
به عنوان مثال قطعه برنامه زیر مجددا آرایه dataرا از همان فایل میخواند:
;)"inFile = fopen("data.txt", "rt
)for (i=0; i<100; i++
; )]fscanf(inFile, "%f", &data[i
اما برای نوشتن یک آرایه در یک فایل دودویی ،میتوان آن را با استفاده از تابع fwriteبه
صورت یکجا در فایل نوشت و نیازی به استفاده از حلقه نمیباشد.
برای این کار کافی است در هنگام فراخوانی تابع ،fwriteنام آرایه را به تنهایی (بدون
استفاده از عالمت &) به عنوان آرگومان اول و تعداد عناصر آرایه را به عنوان آرگومان سوم
(که مشخص کننده تعداد دادهها میباشد) ارسال نماییم.
به عنوان مثال ،قطعه برنامه زیر آرایه dataرا در یک فایل دودویی مینویسد:
;)"outFile = fopen("data.dat", "wb
;)fwrite(data, sizeof(float), 100, outFile
13-5خواندن و نوشتن آرایهها در فایل
خواندن یک آرایه از یک فایل دودویی نیز به طور مشابه و فقط با یک بار
فراخوانی تابع freadانجام میشود.
قطعه برنامه زیر آرایه dataرا از همان فایل دودویی باال میخواند:
;)"inFile = fopen("data.dat", "rb
;)fread(data, sizeof(float), 100, inFile
موارد فوق برای سایر نوع داده ها ،از جمله نوع دادههای تعریف شده
توسط کاربر (مانند ساختارها) نیز برقرار است.
به عنوان مثال اگر ساختاری مانند ساختار دانشجو را تعریف کرده
باشید ،میتوانید یک آرایه از نوع دانشجو را به طور یکجا در یک فایل
دودویی بنویسید و یا از آن بخوانید.
مثال سیستم تصحیح آزمون از سایت مطالعه شود.
مبانی کامپیوتر و برنامه سازی
فصلچهاردهم :اشارهگرها
مدرس :رضارمضانی
14اشاره گرها
اشاره گرها یکی از قدرتمند ترین جنبههای زبان Cهستند.
اشاره گرها امکان پشتیبانی از مواردی همچون تخصیص حافظه پویا،
ایجاد لیستهای پیوندی و ...را به برنامهنویس میدهند.
اما همیشه از مبحث اشاره گرها بعنوان یک موضوع پیچیده و مشکل
یاد میگردد .البته این مسئله تا حدی درست است چرا که کار با
اشاره گرها مستلزم دقت بسیار باالیی است.
معموال امکان وجود خطاهای منطقی در برنامههایی که از اشاره گر
استفاده میکنند ،باال است و پیدا کردن محل خطا و دلیل آن نیز
بسیار مشکل است.
بنابراین در هنگام استفاده از اشاره گرها ،برنامه باید با دقت نوشته
شده و سپس اشکال زدایی گردد.
در این فصل ،ابتدا مفاهیم آدرس و اشاره گر را توضیح داده و سپس
به بررسی کاربردهای اشاره گرها خواهیم پرداخت.
14-1آدرسهای حافظه
همانطور که میدانید یک برنامه به همراه متغیرهای آن برای اجرا باید وارد
حافظه اصلی گردند.
حافظه اصلی از تعدادی بایت تشکیل شده است که هر بایت دارای یک آدرس
منحصر بفرد است .این آدرسها از صفر شروع شده و به ترتیب تا آخرین بایت
حافظه ادامه مییابد.
بعنوان مثال اگر حافظه کامپیوتر شما 64 KBباشد ،آدرس بایتهای حافظه از 0
تا 65535خواهد بود.
هنگامی که برنامه خود را اجرا میکنید ،قسمتی از حافظه اصلی به برنامه
شما تخصیص پیدا میکند .بخشی از این حافظه به توابع برنامه و بخش دیگر
به متغیرها تخصیص مییابد.
در حقیقت با تعریف هر متغیر در برنامه ،بسته به نوع آن ،قسمتی از حافظه به
آن تخصیص مییابد که آدرس اولین بایت تخصیص یافته به آن را ،آدرس آن متغیر
میگوییم.
در زبانهای سطح پایین (زبان ماشین) ،از آدرس یک متغیر برای دسترسی به آن
استفاده میگردد .اما از آنجا که کار با آدرسها بسیار مشکل است ،در زبانهای
سطح باال مانند ،Cبه هر متغیر یک نام نمادین نسبت داده شده و سپس از آن
نام برای دسترسی به متغیر استفاده میگردد.
14-1آدرسهای حافظه
بعنوان مثال دستور زیر را در نظر بگیرید:
;float average
این دستور 4بایت از حافظه را به متغیر averageتخصیص میدهد.
بعنوان مثال فرض کنید بایتهای موجود در آدرسهای 102 ،101 ،100و 103به این
متغیر نسبت داده شوند .در این حالت آدرس متغیر averageبرابر ( 100اولین
بایت تخصیص یافته به آن) درنظر گرفته میشود.
کامپایلر زبان Cدر یک جدول ویژه ،نام ،نوع و آدرس کلیه متغیرها را نگهداری
میکند .اکنون برنامهنویس میتواند در برنامه خود فقط از نام متغیر استفاده
نماید .در هنگام تبدیل برنامه به زبان ماشین ،کامپایلر با مراجعه به جدول
مذکور ،آدرس هر متغیر را استخراج کرده و از آن برای دسترسی به متغیر
استفاده مینماید.
بنابراین در حالت عادی ،یک برنامهنویس زبان ،Cنیازی به دانستن آدرس
متغیرها ندارد و میتواند عملیات خود را تنها از طریق نام نمادین متغیرها انجام
دهد.
اما در برخی موارد و برای نوشتن برنامههای پیشرفته ،نیاز به دانستن آدرس
یک متغیر در حافظه داریم .در این صورت میتوان از عملگر ویژهای که برای
بدست آوردن آدرس یک متغیر در زبان Cدر نظر گرفته شده است ،استفاده
نمود.
14-2عملگر آدرس )&(
برنامهنویسان میتوانن با استفاده از عملگر آدرس یعنی & قبل از نام متغیر،
آدرس آن متغیر را به دست آورند.
بعنوان مثال &averageمشخص کننده آدرس متغیر averageاست.
می توان با استفاده از تابع printfآدرس یک متغیر را نیز چاپ کرد .برای این کار
باید از مشخصه تبدیل %pدر رشته تبدیل استفاده نمود .به مثال زیر توجه
نمایید:
{ )(void main
;int a = 25
;float b = 110.46
; )printf("a=%d and address of a=%p\n",a, &a
;)printf("b=%f and address of b = %p", b, &b
}
a=25 and address of a = FFF4
b=110.460000 and address of b = FFF0
(&) عملگر آدرس14-2
Memory
0
1
…
FFEF
FFF0
FFF1
b
FFF2
FFF3
FFF4
FFF5
a
…
14-3متغیرهای اشاره گر
یک متغیر اشاره گر یا بطور خالصه اشاره گر ،متغیری است که در خود یک
آدرس از حافظه را نگهداری میکند.معموال این آدرس ،موقعیت یک متغیر دیگر
در حافظه است.
شکل کلی تعریف یک اشاره گر بصورت زیر است:
; ><type> *<var-name
بعنوان مثال دستور زیر :
;int *ptr
متغیر ptrرا از نوع اشاره گر به یک عدد صحیح معرفی مینماید.
بعبارت بهتر ،متغیر ptrبه مکانی از حافظه اشاره میکند که در آن یک عدد
صحیح ذخیره شده است.
البته از آنجا که هنوز متغیر ptrمقداردهی نشده است ،حاوی یک آدرس بی
معنی است .پیش از آنکه از این متغیر استفاده شود ،باید آن را مقدار دهی
نمود.
مقدار متغیر ptrمیتواند آدرس هر متغیر صحیحی باشد .بعنوان مثال تکه برنامه
زیر این متغیر را مقدار دهی مینماید:
; int a
;ptr = &a
متغیرهای اشاره گر14-3
:به برنامه زیر توجه کنید
void main() {
int *ptr;
int a;
a = 20;
ptr = &a ;
printf("a=%d and address of a=%p", a, &a) ;
printf("ptr=%p and address of ptr=%p", ptr, &ptr);
}
a=20 and address of a=FFF2
ptr=FFF2 and address of ptr=FFF4
متغیرهای اشاره گر14-3
0
…
FFF2
20
FFF3
FFF4
FFF2
FFF5
…
a
ptr
ptr
a
20
14-3متغیرهای اشاره گر
اکنون این سوال مطرح میگردد که آیا میتوان از طریق آدرس یک
متغیر ،به مقدار آن دسترسی پیدا کرد؟
بعنوان مثال ،در برنامه باال ،متغیر ptrبه متغیر aاشاره میکند ،بنابراین
آیا میتوان از طریق اشاره گر ptrبه مقدار متغیر aدسترسی پیدا نمود؟
برای دسترسی به محتوای یک متغیر از طریق آدرس آن میتوان از
عملگر یکانی * که به آن عملگر دسترسی غیر مستقیم یا عملگر
محتوا نیز گفته میشود ،استفاده نمود.
این عملگر ،قبل از یک اشاره گر استفاده میشود و مقدار متغیری را که
آن اشاره گر به آن اشاره میکند ،باز میگرداند.
بعنوان مثال در برنامه فوق ،برای دسترسی به مقدار متغیر aمیتوان از
عبارت *ptrاستفاده نمود .بعبارت بهتر *ptr ،برابر 20خواهد بود.
متغیرهای اشاره گر14-3
void main() {
int *ptr;
int a, b;
a =10;
b = 20;
ptr = &a ;
printf("a=%d b=%d and *ptr=%d\n", a, b, *ptr) ;
*ptr = 50;
printf("a=%d b=%d and *ptr=%d\n", a, b, *ptr) ;
ptr = &b;
(*ptr) ++ ;
printf("a=%d b=%d and *ptr=%d\n", a, b, *ptr) ;
}
a=10 b=20 and *ptr=10
a=50 b=20 and *ptr=50
a=50 b=21 and *ptr=21
14-3متغیرهای اشاره گر
اندازه یک متغیر اشاره گر بستگی به اندازه آدرسها در کامپیوتر و سیستم
عاملی دارد که از آن استفاده میکنید و ممکن است 2یا 4بایت باشد.
ممکن است این نکته به ذهن شما برسد که با توجه به اینکه در هرحال اندازه
آدرسها در یک کامپیوتر و سیستم عامل خاص ثابت است ،بنابراین اندازه کلیه
اشاره گرها با یکدیگر برابر است.
بعبارت بهتر تفاوتی بین اندازه یک اشاره گر به کاراکتر )* (charبا یک اشاره گر
به عدد اعشاری )* (floatنیست و اندازه هردو برابر است.
بنابراین چرا فقط از یک نوع داده مشترک برای تعریف کلیه اشاره گرها (مانند
نوع داده )pointerاستفاده نکنیم؟
جواب این است که کامپایلر باید از نوع دادهای که اشاره گر به آن اشاره
میکند ،مطلع باشد .بعنوان نمونه اگر به برنامه فوق دقت کنیم ،در مییابیم
هنگامی که کامپایلر به دستوری مانند ; (*ptr) ++میرسد ،باید از تعداد بایتهای
متغیری که ptrبه آن اشاره میکند مطلع باشد تا بتواند عملیات الزم را انجام
دهد.
از آنجا که ptrبصورت اشاره گر به عدد صحیح معرفی شده است ،بنابراین
کامپایلر متوجه میگردد که در آدرسی که ptrبه آن اشاره میکند ،یک عدد
صحیح قرار دارد و به تعداد بایتهای یک عدد صحیح عملیات الزم را انجام
میدهد.
14-3متغیرهای اشاره گر
متغیرهایی که تاکنون استفاده نمودیم ،از نوع اشاره گر به عدد صحیح
بودند .اما میتوان اشاره گرهایی به سایر نوع دادههای زبان Cنیز
تعریف کرد.
بعنوان مثال به موارد زیر توجه کنید:
اشاره گر به کاراکتر char *charPtr; //
اشاره گر به عدد اعشاری float *floatPtr; //
اشاره گر به ساختار دانشجو student *stdPtr; //
توجه کنید که فضای تخصیص یافته به هریک از اشاره گرهای فوق با
یکدیگر برابر میباشد.
14-3متغیرهای اشاره گر
در هنگام کار با اشاره گرها به نکات زیر توجه کنید:
قبل از کار با یک متغیر اشاره گر مطمئن شوید که آن را
مقداردهی کرده اید .چنانچه یک اشاره گر مقداردهی نشده
باشد ،دارای یک مقدار بی معنی است که ممکن است آدرس
یک مکان حساس از حافظه باشد.
در چنین حالتی ممکن است استفاده از این اشاره گر باعث ایجاد
مشکل در اجرای برنامه گردد.
معموال به برنامهنویسان توصیه میگردد که به محض تعریف یک
اشاره گر ،آن را برابر NULLقرار دهند NULL .یک ثابت تعریف
شده در زبان Cاست که برابر 0میباشد .این کار باعث میشود
که اشاره گر مورد نظر به مکان خاصی از حافظه اشاره نکند.
بنابراین چنانچه اشتباها پیش از مقداردهی ،استفاده گردد
مشکل خاصی پیش نخواهد آمد.
14-3متغیرهای اشاره گر
برای تعریف یک متغیر اشاره گر ،دو روش وجود دارد .برخی عالمت * را
چسبیده به نوع داده مینویسند و عدهای دیگر آن را به نام متغیر
میچسبانند .به تفاوت این دو توجه کنید:
;int* ptr
;int *ptr
هردو روش از نظر نحوی درست هستند ،اما پیشنهاد میگردد که از
روش دوم استفاده نمایید ،یعنی عالمت * به نام متغیر بچسبد .دلیل
این مسئله آن است که هنگامیکه قصد تعریف چندین اشاره گر در یک
دستور را دارید ،باید برای هریک بطور جداگانه از عالمت * استفاده
نمایید.
بعنوان مثال فرض کنید قصد داریم 3متغیر b ،aو cرا از نوع اشاره گر به
عدد صحیح تعریف نماییم .دستور زیر نحوه انجام این کار بصورت صحیح را
نشان میدهد:
; int *a, *b, *c
// this is correct
اما ممکن است یک برنامهنویس به اشتباه از دستور زیر استفاده نماید:
int* a, b, c; //this is incorrect
14-3متغیرهای اشاره گر
به تفاوت معنای عملگر * در قسمتهای مختلف برنامه توجه کنید.
اگر از عملگر * در هنگام تعریف یک متغیر استفاده شود ،به این
معنا است که متغیر از نوع اشاره گر است.
اما در سایر قسمتهای برنامه ممکن است به معنای عملگر محتوا
(اگر قبل از یک اشاره گر استفاده شود) و یا عملگر ضرب باشد.
متاسفانه یکسان بودن عملگر ضرب و محتوا گاهی باعث
سردرگمی برنامهنویسان مبتدی میگردد.
ممکن است این سوال به ذهنتان رسیده باشد که آیا یک متغیر
میتواند آدرس یک متغیر اشاره گر را در خود جای دهد؟ پاسخ
مثبت است و به چنین متغیری اشاره گر به اشاره گر گفته
میشود! به مثال زیر توجه کنید:
;int a = 10
;int *p = &a
;int **q = &p
14-4اعمال مجاز بر روی اشاره گرها
اشاره گرها میتوانند در یک عبارت محاسباتی استفاده گردند.
با این وجود فقط دو عمل محاسباتی برای آنها تعریف شده است :اعمال جمع و
تفریق .برنامهنویس میتواند یک اشاره گر را با یک عدد صحیح ،جمع یا تفریق
نماید.
اما معنای عمل جمع و تفریق بر روی اشاره گرها کمی متفاوت با سایر نوع
دادهها است.
فرض کنید متغیر pیک اشاره گر به اعداد اعشاری )* (floatاست که حاوی
مقدار 100است (یا به عبارت بهتر به آدرس 100حافظه اشاره میکند) .دستور
زیر ،یک دستور مجاز در زبان Cاست:
;p = p + 1; // or p++
اما نتیجه آن ممکن است برخالف انتظار شما باشد ،چرا که پس از اجرای این
دستور مقدار متغیر pبرابر 104خواهد شد!
چرا که در زبان Cاین دستور به این معنا است که اشاره گر باید به عدد
اعشاری بعدی اشاره نماید .از آنجا که اندازه هر عدد اعشاری 4بایت است،
بنابراین با هربار افزایش متغیر ،pمقدار آن 4بایت افزایش خواهد یافت.
14-4اعمال مجاز بر روی اشاره گرها
همین مسئله برای کاهش نیز برقرار است .بعنوان مثال چنانچه مقدار متغیر p
برابر 100باشد ،با اجرای دستور زیر:
;p = p - 5
مقدار pبرابر 80خواهد شد .
موارد فوق در مورد سایر نوع دادهها نیز برقرار است .به این معنا که با هربار
افزایش یک متغیر اشاره گر به یک نوع داده خاص ،اشاره گر مزبور به ابتدای
عنصر بعدی از همان نوع داده در حافظه اشاره خواهد کرد.
بنابراین باید مقدار اشاره گر مورد نظر به اندازه تعداد بایتهای نوع داده مورد نظر
افزایش یابد.
خوشبختانه این عمل توسط خود زبان ،Cبه طرز هوشمندانهای صورت
میپذیرد.
در مورد کاهش یک متغیر اشاره گر نیز عملیات مشابهی صورت میپذیرد.
البته در مورد اشاره گرهایی که از نوع اشاره گر به کاراکتر باشند ،اعمال جمع و
تفریق بصورت عادی انجام میگردند ،چرا که اندازه هر کاراکتر برابر 1بایت
است.
14-4اعمال مجاز بر روی اشاره گرها
ممکن است این سوال به ذهن برسد که این عملیات چه کاربردی
دارند؟
کاربرد آنها در مواردی است که برنامهنویس یک مجموعه از دادههای
همنوع بصورت پشت سرهم در حافظه داشته باشد (همانند یک آرایه از
اعداد صحیح).
در این صورت کافی است یک اشاره گر به آدرس ابتدای این دادهها
داشته باشیم .اکنون با هربار افزایش مقدار این اشاره گر ،آن را بر روی
داده بعدی منتقل نماییم.
بجز جمع و تفریق یک اشاره گر با یک عدد صحیح ،هیچ عمل دیگری بر
روی اشاره گرها تعریف نشده است .بعنوان مثال شما اجازه جمع یک
اشاره گر با یک عدد اعشاری و یا ضرب و تقسیم آن بر یک عدد دلخواه
دیگر را ندارید.
البته میتوانید مقدار یک اشاره گر را از یک اشاره گر همنوع آن تفریق
نمایید .در اینصورت حاصل برابر تعداد عناصری از همان نوع خواهد بود
که بین این دو اشاره گر قرار دارد .اما توجه کنید که اجازه جمع دو اشاره
گر را با یکدیگر ندارید.
14-4اعمال مجاز بر روی اشاره گرها
عمل مجاز دیگر بر روی اشاره گرها ،مقایسه آنها با یکدیگر است.
کلیه عملگر های مقایسه ای مانند < > ،و == بر روی اشاره گرها عمل
می کنند.
در حقیقت این عملگرها ،آدرسهای ذخیره شده در اشاره گرها را با
یکدیگر مقایسه می کنند.
بعنوان مثال اگر اشاره گر pحاوی آدرس 100و اشاره گر qحاوی آدرس
200باشد ،آنگاه عبارت ) (p < qبرابر درست ارزیابی خواهد شد.
اما مسلما مقایسه دو متغیر اشاره گر وقتی منطقی است که آن دو به
نحوی با یکدیگر در ارتباط باشند.
بعنوان مثال هنگامی که دو اشاره گر به عناصر یک آرایه اشاره کنند ،با
توجه به اینکه عناصر آرایه در مکانهای پشت سرهم حافظه ذخیره می
گردند ،مقایسه آنها منطقی خواهد بود .در قسمت بعدی کاربرد
مقایسه اشاره گرها بررسی خواهد شد.
14-5کاربردهای اشاره گرها
تاکنون در یافته ایم که یک اشاره گر می تواند آدرس یک
متغیر دیگر را در خود جای دهد ،و بنابراین می توان از
طریق آن اشاره گر ،مقدار متغیر مورد اشاره را تغییر داد.
اما این موارد چه کاربردی برای برنامهنویس دارند؟
در این قسمت سعی می شود کاربردهای اشاره گرها در
برنامهنویسی پیشرفته مورد بررسی قرار گیرد.
14-5-1اشاره گرها و ارسال متغیرها به توابع
همانگونه که در فصل مربوط به توابع گفته شد ،دو روش برای ارسال آرگومانها
به توابع وجود دارد :فراخوانی توسط مقدار و فراخوانی توسط ارجاع.
اما حقیقت آن است که در Cاستاندارد ،فقط روش فراخوانی توسط مقدار وجود
دارد و روش فراخوانی توسط ارجاع تنها در نسخه های جدیدتر ( Cدر واقع در
زبان )C++پیاده سازی شده است.
بنابراین سوال اینجا است که در نسخه های قدیمی زبان ،Cچگونه می
توانستیم مقدار آرگومان ارسالی به یک تابع را تغییر دهیم؟
گفتیم که در نسخه های اولیه زبان ،Cتنها روش فراخوانی توسط مقدار وجود
داشت .به این معنا که برای ارسال آرگومانها به یک تابع ،یک کپی از آنها تهیه
شده و به تابع ارسال می شد .در نتیجه چنانچه تغییری بر روی آرگومانها در
داخل تابع صورت بپذیرد ،این تغییر به تابع فراخواننده منتقل نمی گردد.
به همین دلیل برنامهنویسان ،هنگامی که قصد داشتند مقدار یک آرگومان را در
داخل یک تابع تغییر دهند ،آدرس آن آرگومان را بجای خودش به تابع ارسال می
نمودند.
همانطور که قبال نیز گفته شد ،چنانچه آدرس یک متغیر را داشته باشیم ،می
توانیم از طریق عملگر دسترسی غیر مستقیم یا * به مقدار آن دسترسی پیدا
کنیم .بنابراین برنامهنویس می توانست در داخل تابع ،از طریق آدرس آرگومان،
به مقدار اصلی آن دسترسی پیدا کرده و آن را تغییر دهد.
14-5-1اشاره گرها و ارسال متغیرها به توابع
برای روشن شدن موضوع ،به پیاده سازی تابع swapبا استفاده از
این روش دقت کنید .اگر به یاد داشته باشید ،این تابع دو متغیر صحیح
را بعنوان ورودی دریافت کرده و جای آن دو را با یکدیگر عوض می کرد.
>#include <stdio.h
{ )void swap(int *a, int *b
;int temp
;temp = *a
;*a = *b
;*b = temp
}
اشاره گرها و ارسال متغیرها به توابع14-5-1
void main() {
int x, y;
x = 10;
y = 20;
printf("now x=%d and y=%d\n", x, y);
swap(&x, &y) ;
printf("after swapping x=%d and y=%d\n", x, y);
}
now x=10 and y=20
after swapping x=20 and y=10
14-5-1اشاره گرها و ارسال متغیرها به توابع
)(swap
a
*a = x
FFF4
b
*b = y
FFF2
)(main
x
FFF4 10
y
FFF2 20
14-5-1اشاره گرها و ارسال متغیرها به توابع
همانگونه که می بینید این روش نسبتا پیچیده است و برنامهنویس را مجبور
می کند عالوه بر ارسال آدرس متغیرها در هنگام فراخوانی تابع ،در داخل تابع
نیز مرتبا از عملگر * استفاده نماید.
به همین دلیل در نسخه جدید زبان Cیعنی ،C++روش فراخوانی توسط ارجاع
به زبان اضافه گردید تا از پیچیدگی آن کاسته شود.
اما توجه کنید که در تمامی کتابخانه های استاندارد زبان Cاز همان روش قبلی
استفاده می گردد .بعنوان مثال اگر به یاد داشته باشید ،در هنگام فراخوانی
تابع scanfباید آدرس متغیرهایی که قصد خواندن آنها را داریم ،به تابع ارسال
شوند.
بعنوان مثال برای دریافت یک عدد صحیح مانند kاز کاربر ،این تابع بصورت زیر
فراخوانی میگردید:
;)scanf("%d", &k
اکنون بهتر میتوانید دلیل این مسئله را درک نمایید .از آنجا که آرگومانهای
ارسالی به این تابع ،در داخل آن تغییر داده میشوند (چراکه مقدار دریافتی از
کاربر در آنها قرار داده میشود) ،بنابراین آدرس آرگومانها به تابع ارسال میگردد
تا بتوان مقدار آنها را تغییر داد.
سایر توابع کتابخانهای دیگری نیز که آدرس متغیرها را بعنوان ورودی دریافت
میکنند ،دلیل مشابهی برای این کار دارند.
14-5-2اشاره گرها و آرایههای یک بعدی
اشاره گرها رابطه بسیار نزدیکی با آرایههای یک بعدی دارند .میدانید
که در زبان ،Cآرایه مجموعهای از داده همنوع است که در خانههای
متوالی حافظه ذخیره میگردند.
اما نکته جالب اینجاست که نام هر آرایه به تنهایی ،اشاره گری به اولین
عنصر آرایه است .بعنوان مثال تعریف زیر را در نظر بگیرید:
;]int data[10
کامپایلر زبان Cبه محض برخورد به چنین دستوری 10 ،مکان متوالی
حافظه از نوع عدد صحیح را به این آرایه تخصیص داده و آدرس اولین
مکان را در متغیر dataقرار میدهد.
بنابراین در حقیقت متغیر dataیک اشاره گر به عدد صحیح )* (int
میباشد.
FFE2
data
FFE2
14-5-2اشاره گرها و آرایههای یک بعدی
از این ویژگی میتوان برای دسترسی به عناصر آرایه استفاده کرد.
تا کنون از کروشه و اندیس برای دسترسی به هر عنصر آرایه استفاده
میکردیم .بعنوان مثال برای دسترسی به ششمین عنصر آرایه ،از
] data[5استفاده میکردیم.
اما میتوان به طریق دیگری نیز به این عنصر دسترسی پیدا کرد .از آنجا
که dataبه اولین عنصر آرایه اشاره میکند ،بنابراین با توجه به آنچه که
راجع به جمع اشاره گرها و اعداد صحیح گفتیم data + 1 ،به دومین
عنصر آرایه اشاره خواهد کرد.
فراموش نکنید که زبان Cدر هنگام افزایش یک اشاره گر به اعداد
صحیح ،بطور خودکار آن را به اندازه تعداد بایتهای یک عدد صحیح افزایش
میدهد.
با یک استدالل مشابه data + 5 ،به ششمین عنصر آرایه اشاره خواهد
کرد.
اشاره گرها و آرایههای یک بعدی14-5-2
data
data + 1
data + 2
data + 3
data + 4
data + 5
FFE2
FFE3
FFE4
FFE5
FFE6
FFE7
FFE8
FFE9
FFEA
data[0]
data[1]
data[2]
data[3]
data[4]
FFEB
FFEC
FFED
data[5]
…
data + 9
FFF4
FFF5
data[9]
14-5-2اشاره گرها و آرایههای یک بعدی
حال که توانستیم آدرس عنصر مورد نظر خود را پیدا کنیم ،میتوانیم به راحتی
با استفاده از عملگر دسترسی غیرمستقیم یا * ،مقدار آن را به دست آوریم.
بعبارت بهتر عبارت ) *(data + 5معادل با ] data[5بوده و هر دو مقدار عنصر
ششم آرایه را به ما باز میگردانند.
به مثال زیر توجه کنید:
{ )(void main
; }int data[10] = {8, 12, 6, -3, 5, 16, -7, 14, -20, 6
;int i , sum=0
)for (i=0; i<10; i++
; )sum += *(data + i
;)printf("sum = %d",sum
}
sum = 37
14-5-2اشاره گرها و آرایههای یک بعدی
برخی از برنامهنویسان ترجیح میدهند در هنگام کار با آرایهها از اندیس استفاده نکنند،
بلکه کلیه عملیات خود را منحصرا با اشاره گرها انجام دهند.
برای این کار کافی است یک متغیر اشاره گر تعریف کرده و آدرس ابتدای آرایه را در آن
قرار دهیم ،سپس این اشاره گر را بر روی آرایه به سمت جلو حرکت داده و عملیات مورد
نظر را انجام دهیم.
برنامه زیر از این روش برای محاسبه مجموع عناصر آرایه dataاستفاده میکند:
{ )(void main
; }int data[10] = {8, 12, 6, -3, 5, 16, -7, 14, -20, 6
;int sum=0
; int *ptr
)for (ptr = data; ptr < data+10; ptr++
; sum += *ptr
;)printf("sum = %d",sum
}
sum = 37
14-5-3ارسال آرایهها به توابع
اجازه دهید بحث را با یک مثال ساده شروع نماییم.
فرض کنید از ما خواسته شده است تابعی بنویسیم که یک آرایه را
دریافت و میانگین عناصر آن را بازگرداند .باتوجه به آنچه در مباحث قبلی
داشتیه ایم ،این تابع بصورت زیر نوشته میشود:
{ )float computeaverage(int A[], int n
;float average = 0.0
;int i
)for (i=0; i<n; i++
;]average += A[i
;average /= n
;)return ( average
}
14-5-3ارسال آرایهها به توابع
اکنون فرض کنید قصد داریم تابعی بنویسیم که با استفاده از این تابع میانگین
آرایهای بنام dataرا محاسبه نماید .به این برنامه توجه کنید:
>#include <stdio.h
{ )(void main
; }int data[10] = {8, 12, 6, -3, 5, 16, -7, 14, -20, 6
;float average
;)average = computeAverage(data, 10
;)printf("average = %f", average
}
همانطور که قبال نیز گفته شده است ،برای ارسال یک آرایه به یک تابع باید از
نام آن آرایه به تنهایی استفاده نماییم.
اما میدانیم که نام هر آرایه به تنهایی ،اشاره گری به اولین عنصر آرایه است.
بنابراین در هنگام فراخوانی تابع ،computeAverageدر حقیقت آدرس اولین عنصر
آرایه dataرا به آن ارسال مینماییم.
بطور کلی ،زبان Cدر هنگام ارسال آرایهها به توابع ،آدرس اولین عنصر آنها را
ارسال مینماید.
14-5-3ارسال آرایهها به توابع
از آنجا که اولین پارامتر تابع computeAverageدر حقیقت آدرس شروع
یک آرایه را بعنوان ورودی دریافت میکند ،بنابراین میتوان تعریف این
تابع را بصورت زیر نیز نوشت:
… { )float computeAverage(int *A, int n
البته بسیاری از برنامهنویسان ترجیح میدهند از همان روش قبلی
برای تعریف پارامتر Aاستفاده نمایند.
اگر به یاد داشته باشید ،در فصل آرایهها بیان کردیم که برخالف
متغیرهای عادی ،آرایهها توسط ارجاع به توابع ارسال میگردند .اکنون
دلیل این موضوع برای شما روشنتر شده است.
از آنجا که آدرس ابتدای آرایه به تابع ارسال میشود ،تابع مورد نظر
میتواند از طریق این آدرس به کلیه عناصر آرایه اصلی دسترسی پیدا
نماید و هر گونه انجام تغییرات در آرایه اصلی نیز اعمال خواهد گردید.
اما چرا طراحان زبان Cتصمیم گرفتند از این روش برای ارسال آرایهها به
توابع استفاده نمایند؟
دلیل این مسئله باال رفتن کارایی برنامهها بود.
14-5-3ارسال آرایهها به توابع
اما بعنوان نکته آخر ،یک کاربرد بسیار جالب دیگر از اشاره گرها را بررسی
مینماییم.
همانطور که دیدید توابعی مانند computeAverageکه یک آرایه را بعنوان ورودی
دریافت میکنند ،در حقیقت یک اشاره گر به ابتدای آرایه را دریافت میکنند.
حال اگر به جای آدرس ابتدای آرایه اصلی ،آدرس مکان دیگری از آن را به این
توابع ارسال کنیم ،چه اتفاقی خواهد افتاد؟
پاسخ این است که تابع ،آدرس دریافتی را بعنوان آدرس ابتدای آرایه فرض کرده
و عملیات را از همان جا آغاز مینماید.
از این ویژگی جالب میتوان برای انجام عملیات موردنظر بر روی قسمتی از
یک آرایه استفاده کرد .بعنوان مثال فرض کنید قصد دارید در برنامه مثال قبل،
تنها میانگین دادههای موجود در مکان 3تا 7آرایه dataرا محاسبه نمایید .برای
اینکار کافی است از دستور زیر استفاده کنید:
;)average = computeAverage(data+3, 5
می توانستیم این تابع را بصورت زیر نیز فراخوانی نماییم:
; )average = computeAverage(&data[3], 5
می توانید از این خاصیت جالب در فراخوانی کلیه توابعی که بعنوان ورودی یک
آرایه را دریافت میکنند ،استفاده نموده و فقط قسمتی از آرایه را به تابع ارسال
نمایید.
14-5-4اشاره گرها و رشته ها
می دانید که در زبان Cهر رشته توسط آرایهای از کاراکترها تعریف میشود.
بنابراین هر متغیر رشتهای نیز در حقیقت اشاره گری به آدرس شروع آن رشته
است.
بعنوان مثال اگر متغیر nameبصورت زیر تعریف شده باشد:
; "char name[10] = "ali
آنگاه متغیر nameیک اشاره گر به کاراکتر است که به مکان عنصر اول رشته
یعنی ' 'aاشاره میکند.
بنابراین میتوان برای پردازش رشته ها ،بجای اندیس از اشاره گرها استفاده
کرد.
استفاده از اشاره گرها برای پردازش رشته ها ،در بین برنامهنویسان حرفهای
بسیار متداول است و به همین دلیل بسیاری از توابع رشتهای با استفاده از
اشاره گرها نوشته شده اند.
در اینجا برای آشنایی بیشتر شما ،دو تابع رشتهای که قبال در فصل مربوط به
رشتهها با استفاده از اندیس نوشته شده اند ،با استفاده از اشاره گرها
بازنویسی میگردند تا بتوانید این دو روش را با یکدیگر مقایسه نمایید.
14-5-4اشاره گرها و رشته ها
اولین تابع strcpy ،نام دارد که دو رشته را بعنوان ورودی دریافت و
رشته دوم را در رشته اول کپی مینماید.
نسخه جدید این تابع بصورت زیر است:
{ )void strcpy(char *str1, char *str2
)for (; *str2; str1++, str2++
;*str1 = *str2
;'*str1 = '\0
}
14-5-4اشاره گرها و رشته ها
تابع بعدی که مورد بررسی قرار میدهیم ،تابع strrevاست .این تابع یک رشته
را دریافت و آن را در داخل خودش معکوس مینماید.
پیاده سازی این تابع با اشاره گرها بصورت زیر است:
{ )void strrev(char *str
;char *end
; char temp
; )for (end=str, *end, end++
;end --
{ )for (; str<end; str++, end--
;temp = *str
;*str = *end
; *end = temp
}
}
14-5-5تخصیص حافظه پویا به آرایه ها
در این قسمت به بررسی راه حل یکی از بزرگترین مشکالت آرایه ها،
یعنی ثابت بودن اندازه آنها میپردازیم.
حتما به یاد دارید که اندازه آرایهها باید حتما توسط یک مقدار ثابت
مشخص گردد و این مقدار در زمان اجرا قابل تغییر نیست .به همین
دلیل به این آرایه ها ،آرایههای ایستا گفته میشود.
راه حل این مشکل ،استفاده از آرایههای پویا است .برای شروع به
تعریف آرایه ایستای زیر زیر دقت کنید:
;]int data[10
همانطور که گفته شده ،متغیر dataدر حقیقت یک اشاره گر به اعداد
صحیح )* (intاست که به اولین عنصر آرایه اشاره مینماید.
هنگامی که یک آرایه مانند dataتعریف میگردد ،ابتدا فضای مورد نظر
برای آرایه در حافظه تخصیص داده شده و سپس آدرس ابتدای این فضا
در متغیر dataقرار داده میشود.
14-5-5تخصیص حافظه پویا به آرایه ها
اما برای تعریف یک آرایه پویا ،ابتدا باید یک اشاره گر به نوع مورد نظر
آرایه تعریف نمود.
بعنوان مثال چنانچه بخواهیم آرایه dataرا بصورت پویا تعریف کنیم ،از
دستور زیر استفاده مینماییم :
;int *data
همانطور که میدانید این دستور تنها یک اشاره گر تعریف مینماید که
در حال حاضر به مکان مشخصی اشاره نمیکند.
اکنون میتوانیم در حین اجرای برنامه و پس از مشخص شدن اندازه
مورد نیاز آرایه ،فضای مورد نظر را در حافظه تخصیص داده و سپس
آدرس ابتدای آن را در متغیر dataقرار دهیم.
در این حالت متغیر dataتبدیل به یک آرایه معمولی با اندازه موردنظر
خواهد شد که حتی میتوان با استفاده از اندیس به اعضای آن
دسترسی پیدا کرد!
14-5-5تخصیص حافظه پویا به آرایه ها
اما برای تخصیص فضای مورد نیاز در حافظه اصلی دو روش وجود دارد.
روش اول که در کامپایلرهای جدید ( Cو در حقیقت کامپایلرهای )C++استفاده
میگردد ،از عملگری بنام newبرای تخصیص حافظه استفاده مینماید.
اما روش قدیمی تر از یک تابع کتابخانهای بنام mallocبرای این کار استفاده
مینماید.
کامپایلرهای جدید ،Cدارای یک عملگر یکانی بنام newهستند که یک نوع داده
را دریافت و فضایی به اندازه همان نوع داده در حافظه تخصیص داده و آدرس آن
را باز میگرداند .نحوه استفاده از این عملگر بصورت زیر است:
>new <type
از آنجا که این عملگر آدرس فضای تخصیص داده شده را باز میگرداند ،باید
مقدار بازگشتی آن را به یک متغیر اشاره گر نسبت داد .درغیراینصورت حافظه
تخصیص داده شده گم خواهد شد و دیگر نمیتوان به هیچ صورتی به آن
دسترسی پیدا کرد .چنانچه عملگر newنتواند فضای کافی را تخصیص دهد،
مقدار NULLبازخواهد گرداند.
بعنوان مثال به دستورات زیر دقت کنید:
;int *p
;p = new int
;*p = 100
14-5-5تخصیص حافظه پویا به آرایه ها
نکته بسیار مهم دیگری که باید به آن اشاره کرد ،این است که هر
فضایی که توسط خود برنامهنویس و با عملگر newتخصیص داده شود،
باید توسط عملگر دیگری بنام deleteباز پس گرفته شود.
در صورتیکه این عمل انجام نشود ،حافظه تخصیص یافته آزاد نخواهد
گردید و باعث اتالف حافظه میگردد .نحوه استفاده از این عملگر بصورت
زیر است:
>delete <pointer
بنابراین برنامهنویس میتواند در هر نقطه از برنامه ،در صورت عدم نیاز
به حافظه تخصیص یافته آن را با استفاده از عملگر deleteحذف نماید.
بعنوان مثال ،در تکه برنامه فوق ،پس از اتمام کار میتوانیم از دستور زیر
استفاده نماییم:
;delete p
14-5-5تخصیص حافظه پویا به آرایه ها
اما قدرت واقعی عملگر newدر اینجا است که میتواند فضای الزم برای
تعدادی داده را نیز تخصیص دهد.
برای این کار کافی است از دستور زیر استفاده کنید:
; ]>new <type> [<size
در این حالت عملگر newفضای الزم برای ذخیره > <sizeعدد داده از نوع
مورد نظر را بصورت پشت سرهم در حافظه تخصیص داده و آدرس اولین
عنصر آن را باز میگرداند.
حال میتوان آدرس بازگردانده شده را دریک اشاره گر قرار داد و آن
اشاره گر را تبدیل به یک آرایه پویا نمود .بعنوان مثال به دستورات زیر
توجه کنید:
;int *data
;]data = new int[10
;data[2] = 34
14-5-5تخصیص حافظه پویا به آرایه ها
الزم به یاد آوری است که پس از اتمام کار با آرایه ،dataباید حافظه
تخصیص داده شده به آن را آزاد کرد .اما برای این کار باید عملگر delete
را بصورت زیر استفاده کرد:
;delete[] data
عملگر ][ پس از ،deleteمشخص میکند که فضای تخصیص داده شده
به اشاره گر ،dataبصورت آرایهای (یعنی بیش از یک داده) بوده است.
اما نکته مهمی که در مورد عملگر newوجود دارد این است که الزم
نیست تعداد دادهها یا همان اندازه آرایه ثابت باشد ،بلکه میتوان از یک
متغیر صحیح نیز برای مشخص نمودن ><sizeاستفاده کرد.
با استفاده از این خاصیت ،میتوان اندازه آرایه را در زمان اجرا تعیین
نمود و به همین دلیل به آنها آرایههای پویا گفته میشود.
بعنوان مثال اگر متغیر ،nتعداد عناصر آرایه باشد که از کاربر دریافت
شده باشد (و یا به هر شکل دیگری محاسبه شده باشد) ،میتوان
نوشت:
;]data = new int[n
تخصیص حافظه پویا به آرایه ها14-5-5
، مثال) برنامهای بنویسید که شماره دانشجویی و معدل تعدادی دانشجو را دریافت
.سپس شماره دانشجویانی را که از میانگین کالس بیشتر است را چاپ نماید
#include <stdio.h>
void main() {
float *averageList;
long int *idList;
int i, n;
float totalAverage;
printf("please enter number of student : ");
scanf("%d",&n) ;
averageList= new float[n];
idList = new long int[n];
if (!idList || !averageList) {
printf("not enough memory!");
return ;
}
تخصیص حافظه پویا به آرایه ها14-5-5
totalAverage = 0;
for (i=0; i<n; i++) {
printf("enter id and average : ");
scanf("%ld %f",&idList[i], &averageList[i]);
// or scanf("%ld %f", idList + i, averageList + i);
totalAverage += averageList[i];
}
totalAverag /= n;
printf("good students :\n");
for (i=0; i<n; i++)
if (averageList[i] > totalAverage)
printf("%ld\n", idList[i]);
delete[] averageList;
delete[] idList;
}
14-5-5تخصیص حافظه پویا به آرایه ها
دو نکته مهم دیگر درمورد آرایههای پویا قابل بررسی است.
نکته اول این است که از آنجا که نام یک آرایه پویا ،در حقیقت یک اشاره
گر عادی (غیر ثابت) به ابتدای آرایه است ،میتوان مکان آن را تغییر داد
و یا بطور کلی آن را به جای دیگری اشاره داد.
اما پیش از انجام هر تغییری ،ابتدا مطمئن شوید که اشاره دیگری مکان
شروع آرایه را در خود داشته باشد ،در غیراینصورت مکان شروع آرایه گم
خواهد گردید.
توجه کنید که برای آزاد سازی حافظه تخصیص داده شده ،باید مکان شروع
آن به عملگر deleteداده شود .بنابراین چنانچه مکان شروع آرایه گم شود ،نه
تنها دیگر نمیتوان از آن آرایه استفاده نمود ،بلکه حتی نمیتوان آن را آزاد
کرد؛ در نتیجه این فضا بصورت تخصیص یافته در حافظه باقی خواهد ماند،
درحالیکه هیچکس از آن استفاده مفیدی نمیکند.
به این فضاهای بدون استفاده ،فضاهای گم شده گفته میشود و یک
خطای منطقی بسیار متداول در برنامهها میباشد.
نکته دیگر این است که میتوانید در صورت لزوم ،اندازه یک آرایه پویا را
در حین اجرای برنامه تغییر دهید .بعنوان مثال فرض کنید آرایه data
بصورت پویا و با 100عنصر بصورت زیر تعریف شده است:
;]int *data = new int[100
14-5-5تخصیص حافظه پویا به آرایه ها
اکنون فرض کنید در حین اجرای برنامه متوجه شده ایم که اندازه آرایه dataکوچک بوده
و نیاز به 20عنصر دیگر نیز داریم.
توجه کنید که متاسفانه نمیتوان به سادگی اندازه آرایه dataرا 20عنصر افزایش داد؛
چرا که همانطور که میدانید فضای تخصیص یافته به یک آرایه باید بصورت پشت
سرهم باشد ،اما به هیچ صورتی نمیتوان مطمئن شد که 20آدرس پس از انتهای
آرایه dataآزاد باشند.
به همین دلیل باید ابتدا 120فضای آزاد از نوع عدد صحیح در مکان دیگری از حافظه
تخصیص داد و کلیه 100عنصر موجود در dataرا به آنجا منتقل نمود.
اکنون میتوانیم فضای تخصیص یافته به آرایه اولیه را آزاد نموده و اشاره گر dataرا به
اولین عنصر از فضای تخصیص یافته جدید ،اشاره دهیم .البته برای انجام این عملیات،
نیاز به یک اشاره گر کمکی نیز داریم .تکه برنامه زیر نحوه انجام این عملیات را نشان
میدهد:
; ]int *temp = new int[120
)for (i=0; i<100; i++
; ]temp[i] = data[i
;delete[] data
;data = temp
; temp = NULL
14-5-5تخصیص حافظه پویا به آرایه ها
اما روش دیگر تخصیص حافظه پویا که در کامپایلرهای قدیمی زبان Cاستفاده میگردید،
استفاده از دوتابع mallocبرای تخصیص حافظه و freeبرای آزادسازی آن است.
این دو تابع در فایل سرآمد alloc.hتعریف شدهاند و برای استفاده از آنها باید این فایل را
در برنامه با استفاده از #includeگنجانید .شکل کلی تابع mallocبصورت زیر است:
)>void *malloc(<num-bytes
در فراخوانی این تابع > <num-bytesمشخص کننده تعداد بایتهای مورد نیاز است که باید
تخصیص داده شود.
این تابع بعنوان خروجی یک اشاره گر به فضای تخصیص داده شده از نوع )* (voidباز
میگرداند.
(void *) یک اشاره گر عمومی است که میتواند به هر نوع دادهای اشاره کند .در
حقیقت چنانچه اشاره گری از نوع )* (voidتعریف کنید ،قادر خواهید بود آن را به هر
متغیری از هر نوع دادهای اشاره دهید.
از آنجا که تابع mallocتنها تعداد بایتهای مورد نیاز را بعنوان ورودی دریافت مینماید و از
نوع دادهها آگاه نیست (این یکی از مشکالت این تابع است) ،بنابراین یک اشاره گر
عمومی به فضای تخصیص یافته را باز میگرداند .اما برنامهنویس باید قبل از انتساب
اشاره گر بازگشتی تابع خود ،آن را به نوع داده مورد نظر تغییر قالب دهد.
چنانچه تابع mallocنتواند حافظه موردنظر را تخصیص دهد ،مقدار NULLرا باز خواهد
گرداند.
14-5-5تخصیص حافظه پویا به آرایه ها
مثال فرض کنید قصد داریم یک آرایه از اعداد صحیح با nعنصر ایجاد کنیم که n
یک متغیر صحیح است.تکه برنامه زیر نحوه انجام این کار را نشان میدهد:
;int *data
;))data = (int *)malloc(n * sizeof(int
می بینید که بعنوان تعداد بایتها از ) n*sizeof(intاستفاده شده است.
در ضمن اشاره گر بازگشتی تابع قبل از انتساب به متغیر dataتوسط عملگر
)* (intبه نوع مناسب تغییر قالب داده شده است.
پس از تخصیص حافظه توسط تابع ،mallocمیتوانید حافظه تخصیص داده شده
را توسط تابع freeباز پس بگیرید .شکل کلی این تابع بصورت زیر است:
)>void free(<pointer
که در آن > <pointerاشاره گر به مکانی از حافظه است که قصد آزاد سازی آن
مکان را داریم .بعنوان مثال برای آزاد سازی حافظه تخصیص یافته به dataداریم:
;)free(data
14-5-5تخصیص حافظه پویا به آرایه ها
به دو دلیل بهتر است از عملگرهای newبرای تخصیص
حافظه پویا استفاده نمایید.
این عملگر از نوع داده آگاه است و خودش بطور خودکار اندازه
حافظه الزم را محاسبه مینماید.
اشاره گر بازگشتی آن از نوع مناسب بوده و نیازی به تغییر قالب
آن نیست.
اما در هرصورت تنها از یکی از موارد فوق ،یعنی
عملگرهای newو deleteو یا توایع mallocو freeاستفاده
نمایید.
به دلیل عدم سازگاری این دو ،استفاده همزمان از هر دو
روش موجب بروز مشکل خواهد شد.
14-5-6اشاره گرها و آرایههای چندبعدی
رابطه اشاره گرها و آرایههای چند بعدی کمی پیچیده است .اجازه دهید
بحث را از آرایههای دوبعدی آغاز نماییم.
برای درک رابطه بین اشاره گرها و آرایههای دوبعدی ،ابتدا باید نحوه
ذخیره سازی این آرایهها در زبان Cرا بررسی کنیم .آرایه دوبعدی زیر را
در نظر بگیرید:
;]int A[4][5
ما تا کنون این آرایه را بصورت یک ماتریس دوبعدی تصور مینمودیم .اما
زبان Cچگونه میتواند یک آرایه دوبعدی را در حافظه اصلی که بصورت
خطی (یک بعدی) است ذخیره نماید؟
برای این کار Cباید آرایه دوبعدی را بصورت یک بعدی درآورد .برای این
کار دو روش وجود دارد :روش سطری و روش ستونی.
در روش سطری ،سطرهای آرایه دوبعدی به ترتیب پشت سرهم قرار
داده میشوند تا یک آرایه یک بعدی بوجود آید.
زبان Cاز این روش استفاده مینماید.
14-5-6اشاره گرها و آرایههای چندبعدی
4
3
2
1
0
0
1
A
2
3
نحوه تجسم آرایه Aتوسط برنامهنویس
A
3ردیف
2ردیف
1ردیف
نحوه ذخیره آرایه Aدر حافظه اصلی
0ردیف
14-5-6اشاره گرها و آرایههای چندبعدی
همانگونه که در شکل میبینید ،متغیر Aدر حقیقت اشاره گری به عنصر اول
آرایه است .اما سوال این است که Aبه چه نوع دادهای اشاره میکند؟
این متغیر اشاره گری به آرایههای 5عنصری از نوع عدد صحیح است که
میتوان آن را با )* ] (int[5نشان داد!
می دانید که برای دسترسی به عناصر یک آرایه دوبعدی باید از دو اندیس
استفاده نماییم .بعنوان مثال برای دسترسی به عنصر موجود در ردیف دوم،
ستون سوم از ] A[2][3استفاده میکنیم.
هنگامیکه کامپایلر Cبه این عبارت میرسد ،باید عنصر معادل آن در حافظه
اصلی را محاسبه کند.
برای انجام این کار ،کامپایلر ابتدا اندیس اول یعنی ] A[2آغاز مینماید.
در قسمت قبل گفتیم که ] A[2معادل ) *(A+2میباشد .اما برای محاسبه
مقدار ) *(A+2ابتدا توجه کنید که با هربار افزایش یک اشاره گر ،مقدار آن به
اندازه تعداد بایتهای دادهای که به آن اشاره میکند افزایش خواهد یافت.
از آنجا که Aاشاره گری به آرایههای 5عنصری از نوع عدد صحیح است؛ با
هربار افزایش ،مقدار آن به اندازه طول 5عدد صحیح (یعنی 10بایت در
سیستمهای 16بیتی) افزایش خواهد یافت .بنابراین توجه کنید که A+1به
ابتدای ردیف 1اشاره خواهد کرد و A+2به ابتدای ردیف 2و ...
14-5-6اشاره گرها و آرایههای چندبعدی
A+3
A+2
A+1
A
گرچه A+2به ابتدای ردیف 2اشاره میکند ،اما هنوز هم
از نوع اشاره گر به آرایههای 5عنصری یا )* ] (int[5است.
اما ) *(A+2یک اشاره گر به ابتدای ردیف 2است که از نوع
اشاره گر به عدد صحیح یا )* (intاست.
بنابراین ] A[2یا معادل آن ) *(A+2در حقیقت همانند یک
اشاره گر به آرایه یک بعدی موجود در ردیف 2هستند.
14-5-6اشاره گرها و آرایههای چندبعدی
حال نوبت به اندیس دوم عبارت میرسد .گفتیم که ] A[2معادل ) *(A+2است،
با استدالل مشابهی عبارت ] A[2][3معادل ) *(*(A+2)+3میباشد.
دیدید که ) *(A+2یک اشاره گر به ابتدای ردیف دوم میباشد که از نوع )* (int
است ،بنابراین جمع آن با 3یعنی *(A+2)+3باعث میشود که 3مکان دیگر از
نوع عدد صحیح جلوتر رفته و دقیقا به آدرس داده موردنظر برسیم.
حال کافی است که محتوای این آدرس را با استفاده از عملگر * بدست آوریم
که برابر ) *(*(A+2)+3میگردد.
در حقیقت کامپایلر ،عبارت ] A[2][3را تبدیل به عبارت معادل )*(*(A+2)+3
نموده و با استفاده از آن مکان داده را به دست میآورد .بطور کلی داریم:
)A[i][j] = *(*(A+i) + j
اما مطلب جالب دیگری که در مباحث باال وجود داشت ،این نکته بود که
)*(A+2و ] A[2در حقیقت اشاره گری به ردیف 2هستند ،و یا بعبارت دیگر
نماینده آرایه صحیح ردیف 2هستند.
بنابراین چنانچه یک آرایه دوبعدی مانند Aداشته باشیم ،نام آرایه به همراه تنها
یک اندیس مانند ( iیعنی ] )A[iنشاندهنده آرایه یک بعدی موجود در ردیف iام
است.
14-5-6اشاره گرها و آرایههای چندبعدی
از این خاصیت میتوانید برای ارسال یک ردیف خاص از یک آرایه
دوبعدی ،بعنوان یک آرایه یک بعدی به یک تابع استفاده نمایید.
بعنوان مثال چنانچه بخواهید میانگین اعداد ردیف 2از آرایه Aرا بدست
آورده و در متغیری مانند averageقرار دهید ،میتوانید تابع
computeAverageرا به شکل زیر فراخوانی نمایید:
;)average = computeAverage(A[2], 5
برای آرایه های با ابعاد باالتر نیز مفاهیم مشابهی وجود دارد .بعنوان
مثال چنانچه آرایه Bبصورت زیر تعریف شده باشد:
;]int B[3][5][8
آنگاه آرایه Bیک اشاره گر به آرایه های دوبعدی 5×8یا )* ](int[5][8
است.
برای طوالنی نشدن موضوع از ذکر جزئیات بیشتر خودداری می گردد و
بررسی بیشتر را به خود شما واگذار می نماییم.
14-5-6اشاره گرها و آرایههای چندبعدی
فرض کنید قصد داریم یک آرایه دوبعدی از اعداد به ابعاد 5×8ایجاد نماییم.
همانطور که در بخش قبل گفتیم ،نام چنین آرایه ای یک اشاره گر به آرایه های
8عنصری یا )* ] (int[8است .برای تعریف چنین اشاره گری می توان بصورت زیر
عمل کرد:
;]int (*ptr)[8
توجه کنید که قرار دادن پرانتزها الزامی است ،چرا که چنانچه از پرانتز استفاده
نشود (یعنی بصورت ] int ptr[8نوشته شود) ،آنگاه متغیر ptrیک آرایه 8عنصری
از نوع اشاره گر به عدد صحیح خواهد بود.
حال که اشاره گر مورد نظر خود را تعریف نمودیم ،می توانیم بصورت برای
تخصیص حافظه پویا به آن از عملگر newاستفاده نماییم .به دستور زیر توجه
کنید:
;]ptr = new int[5][8
نکته بسیار مهم در این است که بعد اول آرایه دوبعدی پویا ،می تواند یک متغیر
صحیح نیز باشد .بعنوان مثال به دستورات زیر توجه کنید:
;]int (*ptr)[8
; int n
تعیین مقدار// n
;]ptr = new int[n][8
14-5-6اشاره گرها و آرایههای چندبعدی
آخرین نکته نیز آن است که برای آزاد کردن حافظه یک
آرایه دوبعدی ،از همان عملگر ][ deleteاستفاده نمایید.
بعنوان مثال :
;delete[] ptr
چنانچه بخواهیم آرایه n×8فوق را با استفاده از تابع
mallocایجاد کنیم ،می توانیم بصورت زیر عمل نماییم:
;]int (*ptr)[8
;))ptr = (int[8] *) malloc(n*8*sizeof(int
برای آزاد سازی این حافظه نیز می توان تابع freeرا
بصورت عادی فراخوانی نمود:
;)free(ptr
14-5-6اشاره گرها و آرایههای چندبعدی
اما روش دیگری نیز برای ایجاد آرایه های دوبعدی بصورت پویا وجود دارد.
مجددا یک آرایه دوبعدی از اعداد صحیح به ابعاد 5×8را در نظر بگیرید .در
روش جدید ،ابتدا یک آرایه یک بعدی از اشاره گر به اعداد صحیح با 5
عضو ایجاد می گردد .سپس بازای هر عضو این آرایه ،یک آرایه یک بعدی
از اعداد صحیح با 8عنصر ایجاد شده و آدرس شروع آن در عضو مورد
نظر قرار می گیرد.
شکل زیر نحوه انجام کار را نشان می دهد:
14-5-6اشاره گرها و آرایههای چندبعدی
با توجه به روش گفته شده ،دستورات الزم برای انجام این کار به شرح زیر می
باشد:
;]int *ptr[5
)for (i=0 ;i<5; i++
;]ptr[i] = new int[8
توجه کنید که در این روش ،برخالف روش قبل تعداد ردیفها ثابت بوده و باید در
هنگام تعریف متغیر ptrبصورت ایستا تعیین گردد ،اما تعداد ستونها می تواند
بصورت پویا و در زمان اجرا تعیین گردد.
بعنوان مثال چنانچه متغیر صحیح mحاوی تعداد ستونها باشد ،می توان حلقه
forدر تکه برنامه باال را بصورت زیر نوشت:
)for (i=0 ;i<5; i++
;]ptr[i] = new int[m
توجه داشته باشید که برای آزاد سازی فضای تخصیص یافته به این آرایه ،باید
فضای تخصیص یافته به هر اشاره گر را جداگانه آزاد نمایید .تکه برنامه زیر این
عمل را انجام می دهد:
)for (i=0; i<5; i++
;]delete[] ptr[i
14-5-6اشاره گرها و آرایههای چندبعدی
اما این روش دارای چندین مزیت عمده نسبت به روش قبلی می باشد.
اولین مزیت این است که حتما الزم نیست آرایه دوبعدی شما بصورت
مستطیل تعریف گردد! بعبارت دیگر می توانید برای هر ردیف ،آرایه ای با
اندازه متفاوت ایجاد کنید.
از این حالت معموال برای ذخیره سازی آرایه ای از رشتهها استفاده می
شود .بعنوان مثال فرض کنید می خواهیم آدرس کلیه دانشجویان یک کالس
را در یک آرایه نگهداری کنیم .از آنجا که طول آدرس هر دانشجو با دیگری
متفاوت است ،می توان با استفاده از این روش برای هر دانشجو یک رشته
کاراکتری به اندازه آدرس وی ایجاد کرده و به وی نسبت داد.
مزیت دوم ،سادگی و سرعت جابجایی دو ردیف از آرایه است.
در مواردی که نیاز زیادی به جابجایی دو ردیف از آرایه است ،بهتر است از این
روش استفاده شود .چرا که در این روش فقط اشاره گرهای دو ردیف با
یکدیگر جابجا می شوند ،ولی در روش قبلی باید کل عناصر دو ردیف با
یکدیگر جابجا شوند.
14-5-6اشاره گرها و آرایههای چندبعدی
و اما آخرین مزیت این روش در این است که می توان تعداد ردیفها و ستونها را
بصورت پویا تعیین نمود.
;]int *ptr[5
بار دیگر نگاهی به تعریف متغیر ptrدر این روش بیندازید.
ptrیک آرایه 5عنصری از نوع اشاره گر به عدد صحیح است .بنابراین خود ptrبه
تنهایی ،اشاره گری به اولین عنصر این آرایه است .از آنجا که اولین عنصر خود یک
اشاره گر است ،بنابراین ptrیک اشاره گر به اشاره گر به عدد صحیح است .بنابراین
چنانچه بخواهیم آرایه ptrرا بصورت پویا تعریف کنیم داریم:
;int **ptr
;]ptr = new int*[5
حال می توانیم با استفاده از یک حلقه ،forفضای الزم برای هر ردیف را به آن تخصیص
دهیم.
بطور کلی چنانچه بخواهیم یک آرایه دوبعدی کامال پویا به ابعاد n×mایجاد نماییم،
بصورت زیر عمل می کنیم:
; int n,m
تعیین مقدار mو// n
;int **ptr
;]ptr = new int*[n
)for (i=0; i<n; i++
;]ptr[i] = new int[m
14-5-6اشاره گرها و آرایههای چندبعدی
حتما به یاد دارید که برای تعریف یک پارامتر از تابع بعنوان یک آرایه دوبعدی ،باید
حتما تعداد ستونهای آن مشخص گردد اما نیازی به تعیین تعداد ردیفهای آن
نیست.
بعنوان مثال اگر تابعی مانند testیک آرایه دوبعدی با 5ستون را بعنوان و رودی
دریافت میکرد ،تعریف آن بصورت زیر بود:
… { )]void test(int A[][5
با توجه به مطالبی که در مورد اشاره گرها و رابطه آنها با آرایههای دوبعدی ذکر
شد ،دلیل این نحوه تعریف کامال روشن میگردد.
گفتیم که نام یک آرایه دوبعدی با 5ستون (و هر تعداد ردیف دلخواه) اشاره گری
به آرایههای 5عنصری است ،بنابراین تابع testنیز یک )* ] (int[5را بعنوان ورودی
دریافت میکند .میتوان این تابع را بصورت زیر نیز تعریف کرد:
… { )]void test(int (*A)[5
که معادل با همان تعریف قبلی است.
هنگامی که در داخل تابع از اندیس برای دسترسی به یک عنصر از آرایه
دوبعدی استفاده میکنیم ،کامپایلر Cاز تعداد ستونهای مشخص شده برای
محاسبه مکان عنصر در حافظه اصلی استفاده میکند.
14-6اشاره گرها و ساختارها
از آنجا که هر ساختار همانند یک نوع داده جدید میباشد ،میتوان متغیری از نوع اشاره گر به ساختار تعریف کرد.
بعنوان مثال ساختار دانشجو را در نظر بگیرید.
{ struct student
;long int id
;]char name[30
;float average
;int age
;}
برای تعریف متغیری که به این ساختار اشاره نماید ،همانند اشاره گرهای دیگر از عالمت * استفاده میکنیم.
;student *stdPtr
حال میتوان این اشاره گر را به یک متغیر دیگر از نوع studentاشاره داد .به مثال زیر توجه کنید:
; }student s = {82121020, "reza", 17.2, 20
;stdPtr = &s
حال میتوان از طریق stdPtrبه اجزای sدسترسی پیدا کرد .برای دسترسی به idاز طریق اشاره گر stdPtrباید
ابتدا از عملگر محتوا یا * بصورت (*stdPtr).idاستفاده نماییم.
دستور زیر باعث میشود که شماره دانشجویی sبه 83101010تغییر کند:
;(*stdPtr).id = 83101010
از آنجا که نوشتن این عبارت کمی سخت است ،در زبان Cاز عملگر دیگری بنام فلش یا > -برای دسترسی به
اجزای یک اشاره گر به ساختار استفاده میشود .این عملگر در حقیقت متشکل از یک عالمت منها -و یک عالمت
بزرگتر < است که در کنار یکدیگر قرار گرفته اند .بعنوان مثال دستور فوق را میتوان بصورت زیر نیز نوشت:
;stdPtr->id = 83101010
14-6اشاره گرها و ساختارها
می توان از عملگر newبرای تخصیص حافظه به یک اشاره گر به ساختار استفاده کرد.
در تکه برنامه زیر پس از تخصیص حافظه الزم به یک دانشجو ،آن را مقداردهی نیز نموده
ایم:
; student *stdPtr
;stdPtr = new student
;stdPtr->id = 84102010
;)"strcpy(stdPtr->name, "reza
;stdPtr->average = 18.0
;stdPtr->age = 19
برای آزاد سازی فضای تخصیص یافته نیز میتوان از عملگر deleteاستفاده نمود.
;delete stdPtr
تخصیص حافظه و باز پس گیری آن به ساختارها با استفاده از توابع mallocو freeنیز
امکان پذیر است .نحوه انجام این کار بصورت زیر است:
;))stdPtr = (student *) malloc(sizeof(student
;)free(stdPtr