فصل اول
حل مسئله و برنامهنویسی
مقدمه
زبان ++C یکی از معروفترین زبانهای برنامهنویسی به شمار میآید که در کاربردهای بسیاری در سالیان اخیر مورداستفاده قرار گرفته است. اگرچه این کتاب به آموزش زبان ++C اختصاص دارد، اما هدف اصلی آن آموزش مفاهیم برنامهنویسی به دانشجویان و بهویژه دانشجویان رشتههای مهندسی است، بهگونهای که بتوانند مسائل مختلف مهندسی را به کمک برنامههای کامپیوتری حل نمایند. بنابراین سعی شده است تا صرفنظر از دستورات خاص زبان، مفاهیم عمومی بیشتر مورد تأکید قرار گیرد و در کنار آن گرامر و ساختار این زبان بهطور خاص و با تمرکز بیشتری موردبحث قرار گیرد.
پیشینه زبان ++C
++C یک زبان برنامهنویسی همهمنظوره، سطح بالا و شیءگرا است. در اواخر دهة 60 و اوایل دهة 70 ميلادي دنيس ريچي، زبان برنامهنويسي C را در آزمایشگاههای بل، At & T ابداع كرد. در آن زمان افرادي در آزمایشگاههای بل مشغول طراحي سیستمعامل يونيكس بودند و براي نوشتن اين سیستمعامل از زبان اسمبلي استفاده ميكردند. براي رهایی از مشكلات برنامهنويسي به زبان اسمبلي، ريچي زبان C را اختراع كرد. C ويژگيهاي زبان سطح پايين اسمبلي و تسهيلات زبانهاي برنامهنويسي سطح بالا را با هم درآميخت، لذا حدوداً 90% یونیکس با زبان C و 10% آن به زبان اسمبلي نوشته شد.
اغلب اين سؤال به ذهن ميآيد كه وجهتسمیه C چيست. در دهة 60 میلادی، زبان برنامهنويسي BCPL طرفداراني بهخصوص در اروپا داشت. زبان ديگري از اين زبان به نام B ایجاد شد. ريچي ويژگيهاي زبان B را در طراحي زبان جديد حفظ کرد و ازآنجاکه اين را نسل بعدي B ميدانست، نام C را براي زبان جديد خود انتخاب كرد. در سال 1985 استروستراپ زبان ++C را اختراع كرد. ++C همان C است با ويژگيهاي تجرد داده و برنامهنويسي شيءگرا (در اين مورد بعداً بحث خواهيم كرد). به جاي انتخاب نام D، براي اين زبان جديد نام ++C انتخاب شد. زيرا علامت ++ در زبان C نشاندهنده افزايش است.
اگرچه C و ++C ابتدا بهعنوان زبان¬هاي برنامهنويسي در سطح سيستم (مثلاً نوشتن سیستمعامل) در نظر گرفته شده بودند، اما طی این سال¬ها بهطور گسترده در صنعت نرمافزار و در کاربردهای مختلف مورداستفاده قرار گرفتهاند. در اين كتاب سعي شده است اكثر ویژگیهای مهم و کاربردی ++C مطرح شوند.
از کجا شروع کنیم؟
برای برنامهنویسی با ++C نیاز به نصب یک برنامه نرمافزاری است که امکان نوشتن برنامه و اجرای آن را فراهم آورد. محیط¬های برنامهنویسی متعددی برای این منظور وجود دارد که بهآسانی میتوان آنها را دانلود و نصب کرد یکی از این برنامهها را میتوانید از https://sourceforge.net/projects/orwelldevcpp/ دانلود و نصب نمایید. همچنین هر یک از محیطهای دیگر برنامهنویسی این زبان نیز قابلاستفاده خواهد بود.
نحوه فراگیری برنامهنویسی
برنامهنویسی تنها یک روش یادگیری دارد: تمرین زیاد. برای آموختن برنامهنویسی باید پس از مطالعه مطالب نسبت به نوشتن برنامه در کامپیوتر اقدام کنید و نتیجه آن را مشاهده کنید. هر یک از مثالها را در صورت لزوم چند بار حل کنید تا از فراگیری تکنیکها مطمئن شوید. راه میانبر دیگری برای آموختن برنامهنویسی بهغیراز تمرین، وجود ندارد. سعی کنید وقتی مهارت کافی پیدا کردید، بدون نگاه کردن به حل مثالها، آنها را حل کنید. تمرینهای آخر فصلهای کتاب نیز مجموعه خوبی را برای تمرین بیشتر فراهم آورده است.
اولین برنامه ++C
مثال زیر یک برنامه ساده به زبان ++C را نشان میدهد. هر برنامه از یک تابع اصلی به نام main تشکیل میشود که در خط 8 نشان داده شده است.
هر تابع میتواند یک مقدار خروجی داشته باشد که نوع آن قبل از نام تابع نوشته میشود. در این مثال، خروجی تابع یک عدد است که نوع آن عدد صحیح است و در C++ با int نشان داده میشود. در پایان بدنه تابع یعنی خط 11 با دستور return خروجی تابع بازگردانده میشود. طبق یک قرار نانوشته، درصورتیکه برنامه با موفقیت به پایان برسد و هیچ مشکلی پیش نیاید، مقدار 0 برمیگردانیم. 0 به معنی اجرای موفق برنامه است. جزئیات این دستورات در فصلهای بعدی توضیح داده خواهد شد. خطوط 5 و 6 برنامه شرایط استفاده از دستور cout را در برنامه فراهم میآورد. این دستورات به سيستم ++C دستور ميدهد كه محتويات فايلي به نام iostream.h را به برنامه اضافه كند. اين فايل شامل اطلاعاتي است كه ++C براي نمایش خروجيهاي نياز دارد. دقت كنيد كه cout متغيري از نوع داده stream است. بعداً راجع به خط #include بيشتر صحبت خواهيم كرد. فعلاً این دستورات را به همین صورت استفاده نمایید.
تنها دستور اصلی این برنامه مربوط به خط 10 است که باعث میشود عبارت “Welcome to C++.” را عیناً به همین صورت به کاربر نشان دهد. سه خط ابتدایی برنامه که به رنگ سبز نشان دادهشدهاند، توضیحات برنامه هستند. معمولاً در ابتدای برنامه در چند خط یک معرفی اجمالی از برنامه انجام میشود و نام برنامهنویس و تاریخ نوشتن برنامه نیز نوشته میشود. هر عبارتی که بهعنوان توضیح نوشته شود، اجرا نمیشود و صرفاً بهعنوان راهنما برای برنامهنویس مورداستفاده قرار میگیرد. توضیحات به برنامهنویس کمک میکند تا درصورتیکه بعد از مدتی به برنامه مراجعه کرد بتواند به یاد بیاورد که هر بخش برنامه به چه کاری اختصاص داشته است و درصورتیکه از محاسبات پیچیدهای استفاده کرده باشد بتواند بفهمد که هر بخش از برنامه به چه محاسباتی اختصاص داشته است. علاوه بر این، در کارهایی که به صورت تیمی انجام میشود، استفاده از توضیحات مناسب به اعضای تیم کمک میکند تا در جریان کار هم قرار گیرند و به مستندسازی برنامهها کمک میکند. در غیر این صورت نیاز است که هر یک از برنامهنویسان حضوری برنامه خود را برای دیگران توضیح دهند. برنامهنویس میتواند علاوه بر خطوط اولیه برنامه، هرجایی که احساس کند نوشتن توضیحات در آینده کمک میکند، از آن استفاده کند و این توضیحات محدود به خطوط اول برنامه نیستند.
در ++C نتايج محاسبات را با استفاده از دستور cout با عملگر (<<) به نام "اضافهگر" روي صفحهنمایش نشان ميدهيم. مثلاً عبارت:
عبارت “Hello” را که رشتهاي از كاراكترها است و اصطلاحاً رشته نامیده میشود، روي صفحهنمایش نشان ميدهد. همچنین دستور زیر
نتيجه عبارت 3 *2 يا 6 را توسط عملگر << روي صفحهنمایش نشان ميدهد. علامت * در زبان C++ برای ضرب به کار میرود.
متغير cout در ++C از پیش بهعنوان جرياني از داده خروجي كه به سمت صفحهنمایش در حال حركت است تعريف شده است. توجه كنيد كه cout يك متغير است و لازم است كه تعریف شود. تعریف آن از طریق همان اعلان include ابتدای برنامه انجام میشود. به جهت << كه جهت حركت جريان داده را مشخص ميكند دقت كنيد.
به دستورات زیر توجه کنید:
خروجی این دو دستور به صورت زیر است:
همانگونه که ملاحظه میکنید، اینکه دستورات خروجی در دو خط متفاوت نوشته شود، تأثیری در خروجی ندارد و کماکان خروجی پشت سر هم و در یک خط چاپ میشود.
چنانچه بخواهيم چاپ یک عبارت یا جمله از جای خاصی در خط بعدی انجام شود، از کاراکتر خاص \n که به معنی خط جدید است، استفاده میکنیم. مثال زیر نشان میدهد که چگونه این کار انجام میشود:
همانگونه که مشاهده میکنید، سه جا در این عبارت از کاراکتر خط جدید استفاده شده است و دقیقاً در همان سه نقطه در هنگام چاپ خروجی، ادامه کار از خط جدید انجام میشود. نمونه خروجی این دستور به شکل زیر است:
میتوان چندین عبارت را پشت سر هم با یک cout نوشت. این عبارتها به ترتیب در خروجی چاپ میشوند. در چنین حالتی لازم است بین هر جمله یا عبارت از یک << استفاده شود. به مثال زیر توجه کنید:
خروجی این مثال به صورت زیر است:
همچنین استفاده از عبارت endl (بخوانيد end line) به معنای پایان خط نیز همان نتیجه \n را خواهد داشت. به مثال زیر توجه کنید:
نتیجه این دستور به صورت زیر است:
شناسه endl يك عملگر است كه كارش قطع يك خط است و خط بعدي با cout بعدي شروع ميشود.
یک برنامه چگونه اجرا میشود؟
زبان برنامهنویسی ++C یک زبان برنامهنویسی سطح بالا تلقی میشود. زبانهای برنامهنویسی سطح بالا به زبان ریاضیات نزدیکتر بوده و بهگونهای طراحی میشوند که فهم و استفاده از آن برای برنامهنویسان آسانتر باشد. در عوض زبانهای سطح بالا برای کامپیوتر قابلفهم نمیباشند و برای اجرا باید به زبان سطح پایین یا اصطلاحاً به زبان ماشین ترجمه شوند.
نوشتن برنامه به زبان سطح بالا مزایای مختلفی به دنبال دارد. اولاً نوشتن برنامه با زبان سطح بالا آسانتر بوده و رفع خطاهای احتمالی نیز راحتتر است. همچنین این برنامهها خواناتر از برنامههای سطح پایین میباشند. از طرف دیگر استفاده از زبان سطح بالا، امکان اجرای آن را روی سیستمعاملهای مختلف فراهم می¬آورد. به عبارتی کد نوشتهشده به زبان سطح بالا میتواند به زبان کامپیوترهای مختلف ترجمه شود و لذا امکان اجرای آن در سیستمعاملها و کامپیوترهای مختلف با تغییرات حداقلی فراهم میگردد. درحالیکه برنامههای سطح پایین فقط روی یک کامپیوتر خاص یا سیستمعامل خاص قابل اجرا میباشند.
با توجه به مزایای فوق تقریباً تمامی برنامهها به زبان سطح بالا نوشته میشوند و زبانهای سطح پایین صرفاً برای کارهای بسیار تخصصی و خاص استفاده میشوند.
بهطورکلی روند تبدیل و ترجمه برنامه سطح بالا به برنامه سطح پایین به دو صورت انجام میپذیرد: ترجمه و تفسیر . در روش تفسیر، هر بار یک دستور از برنامه خوانده میشود و اجرا میگردد (شکل 1-1 الف). در مقابل مترجم در ابتدا کل برنامه را خوانده و ترجمه میکند و سپس اجرای آن را آغاز میکند (شکل 1-1 ب). اصطلاحاً به برنامه سطح بالا کد منبع گفته میشود و به برنامه ترجمهشده کد شیء یا کد قابلاجرا گفته میشود. وقتی یک برنامه یکبار ترجمه شد، برای اجرای مجدد نیازی به ترجمه دوباره ندارد و بلافاصله قابلاجرا است.
برنامه چیست؟
برنامه دنبالهای از دستورات است که مشخص میکند چگونه یک پردازش یا محاسبات انجام شود. بهعبارتدیگر مشخص می¬کند که چه دستوراتی باید به چه ترتیبی اجرا شوند تا نتیجه موردنظر حاصل شود. این پردازش میتواند نظیر حل دستگاه معادلات یا پیداکرده مشتق یک تابع، از جنس محاسبات ریاضی و یا نظیر جستجو در متن یا صفحات وب از نوع پردازش نمادین باشد.
محیطهای دیگر برنامهنویسی
محیط¬های متعددی برای برنامه¬نویسی با زبانهای مختلف به وجود آمده و قابلاستفاده است. علاوه بر محیط DevCPP که در ابتدای این فصل معرفی شد، محیط نرمافزار ویژوال استودیو از شرکت مایکروسافت نیز امکانات حرفهای و گستردهای را برای برنامهنویسی با زبانهای مختلف، منجمله ++C فراهم آورده است که دانشجویان میتوانند در مراحل حرفهایتر از آن استفاده نمایند. با توجه به پیچیدهتر بودن محیط این برنامه، جهت فراگیری مقدماتی این زبان توصیه نمیشود.
چگونه يك برنامه بنويسيم؟
تولید یک برنامه از مراحل مشخصی تبعیت میکند که اصطلاحاً فرایند تولید نرمافزار گفته میشود. فرایندهای متنوعی جهت تولید سیستمهای نرمافزاری ارائه شده است و در این صنعت مورداستفاده قرار میگیرد، اما با توجه به اینکه تمرکز ما بر برنامههای مهندسی است، چارچوب سادهای را برای این منظور معرفی میکنیم. طبیعی است دانشجویان در صورت نیاز در کارهای حرفهایتر میتوانند به مطالعه بیشتری در خصوص فرایندهای تولید نرمافزار بپردازند. البته فرایند حل مسئله و تولید نرمافزار که در اینجا به آن خواهیم پرداخت، بهنوعی سنگ بنا و نقطه مشترک همه فرایندها نیز محسوب میشود. بهطورکلی، دوره زندگی یک نرمافزار شامل دو مرحله تولید و نگهداری است. در مرحله تولید، یک سیستم نرمافزاری تهیه و آماده بهرهبرداری میشود و کدهای برنامه نوشته میشود و در دوره نگهداری مورد استفاده قرار میگیرد و تغییرات احتمالی بسته به نیاز در آن انجام میشود. خود مرحله تولید نیز به مراحل جزئیتری تقسیم میشود. این مراحل در شکل 1-2 نشان داده شدهاند. در ادامه به شرح هر یک از این گامها خواهیم پرداخت. برای درک بهتر این مراحل، مسئله زیر را در نظر بگیرید.
فرض کنید یک شرکت تولیدکننده لوازمخانگی قصد دارد دستگاه تخممرغپز تولید کند و سفارش طراحی این دستگاه را به واحد مهندسی و طراحی محصول خود داده است. مدیر این واحد میخواهد تخممرغهایی که توسط این دستگاه طبخ میشود، از طعم ایدئال برخوردار باشند. ازاینرو از شما خواسته است تا برنامهای تولید کنید تا زمان پخت تخممرغ را برای دو حالت عسلی و سفت (آب پز) محاسبه کند. این زمان به دمای اولیه تخممرغها وابسته است و بهعنوانمثال برای تخممرغی که در یخچال نگهداری شده یا در فضای آزاد قرار داشته متفاوت است. از طرفی برای تخممرغهای کوچک و بزرگ هم فرق دارد.
گام 1: درک صورتمسئله و شناخت دامنه مسئله
در اولین قدم باید شناخت کافی نسبت به موضوع حاصل شود. صورتمسئله بهصورت واضح تعریف شود. ورودیها و خروجیهای مسئله مشخص شود. فرمولها و روابطی که برای حل مسئله موردنیاز هستند استخراج شود. در خصوص موضوع مسئله مطالعه کافی صورت پذیرد و زوایای آن بهدقت موشکافی شود. بهعنوانمثال چنانچه قرار است برنامهای برای پیشبینی پیوندهای شیمیایی نوشته شود، باید تسلط کامل به این موضوع ایجاد شود و به اندازه کافی راجع به آن مطالعه صورت پذیرد و مطالب موردنیاز گردآوری شود. همچنین، چنانچه قرار است برنامهای برای محاسبه مقاومت یک سازه نوشته شود، باید مطالب و فرمولهای مربوطه گرداوری شود. اصطلاحاً به این گام، شناخت دامنه مسئله گفته میشود. شناخت دامنه، از مهمترین گامهای حل مسئله به شمار میآید و نیازمند توجه و تمرکز بسیار زیادی است. همه گامهای بعدی تحت تأثیر کیفیت این مرحله قرار میگیرند.
اجازه دهید به مثال پخت تخممرغ برگردیم. احتمالاً همه ما تجربه پخت تخممرغ را داریم، اما شاید هیچگاه به این مسئله بهعنوان یک موضوع علمی که میتواند از فرمولهای ریاضی و مهندسی بهره گیرد، نگاه نکرده باشیم. قطعاً تخممرغ خدمت بزرگی به جامعه بشریت کرده، پس جا دارد کمی در مورد آن تحقیق کنیم. با کمی جستوجو متوجه میشویم که مسئله پخت تخممرغ چندان هم ساده نیست. سفیده تخممرغ باید به دمای بالاتر از 63 درجه سلسیوس برسد تا سفت شود و زرده آن باید به دمایی بالاتر از 70 درجه برسد تا بهطور کامل پخته و سفت شود. در طبخ تخممرغ عسلی، تخممرغ باید به اندازه کافی حرارت داده شود، بهنحویکه سفیده آن دمای بالای 63 درجه را دریافت نماید ولی زرده به دمای 70 درجه نرسد. برای طبخ تخممرغ آب پز (سفت) باید مرکز زرده تخممرغ به دمایی بالاتر از 70 درجه برسد. فرمول زیر زمانی را که لازم است تا مرکز زرده تخممرغ به دمای Ty برحسب سلسیوس برسد را محاسبه میکند (برحسب ثانیه). M جرم تخممرغ، ρ چگالی، c ظرفیت گرمایی و K هدایت گرمایی تخممرغ است.
T0 دمای اولیه تخممرغ قبل از قرارگرفتن در آب و Tw دمای آب جوش است. متوسط دمای یخچال حدود 3 درجه و متوسط دمای اتاق حدود 22 درجه است. متوسط جرم تخممرغ کوچک 47 گرم، و تخممرغ بزرگ 67 گرم است. مقادیر سایر پارامترها بهصورت زیر است:
جمعآوری اطلاعات بیشتر در مورد یک مسئله
اگر مسئلهای بهطور شفاهي داده شده است، سؤالات متعددي مانند با كي؟ چرا؟ و كجا؟ را مطرح ميكنيم تا بهطور دقيق متوجه شويم چهکاری را بايد انجام دهيم. اگر مسئله بهصورت نوشتاري داده شده است، در حاشيه متن علامت سؤال قرار داده و مشخص ميكنيم كه کدامیک از كلمات يا عبارات نوشتهشده روشن نيستند و احتياج به توضيح دارند. ممكن است سؤالات در پاراگرافهاي بعدي جواب داده شده باشند يا در بحث حضوري با شخصي كه كار را به ما ارجاع كرده است، جواب سؤالات را دريابيم. برخی از سؤالاتي را كه از ديد برنامهنويسي ميتوان پرسيد، به شرح زیر است:
- دادههايي كه قرار است با آن كار كنيم چه هستند؟
- دادهها چه شکلی هستند و چه ساختاری دارند؟
- تعداد یا ميزان دادهها چقدر است؟
- شكل خروجي چگونه بايد باشد؟
- چگونه دريابيم كه همه دادهها بهدرستی پردازش شدهاند؟
- شرايط استثنا یا خطاهاي خاصي كه ممكن است اتفاق بيافتد كدامند؟
گام 2: تحلیل و طراحی عمومي (الگوریتم)
در این مرحله بر اساس شناختی که در مرحله قبل پیدا شده طرح کلی سیستم و الگوریتم موردنیاز برای حل مسئله طراحی میشود. الگوریتم شامل روال مرحلهبهمرحله حل مسئله در یکزمان معين است. به عبارتی الگوریتم توصیف گامبهگام و مکتوب بهصورت دنبالهای منطقی از عملیاتی است که منجر به حل یک مسئله خاص شود.
در زندگي روزمره با الگوریتمها سروکار زيادي داريم. بازي فوتبال الگوریتمی دارد كه توسط بازيكنان دنبال ميشود. نسخهاي كه پزشك براي بهبود بيمار مينويسد الگوریتمی است كه بيمار آن را دنبال ميكند. دستورالعملهای پختن كيك، الگوریتمی است كه قناد با دنبال كردن آن كيك را ميپزد. برنامهنويس در مواجهه با يك مسئله ابتدا آن را حل ميكند و يك الگوریتم ايجاد ميكند و سپس الگوریتم را به برنامه تبديل ميكند. كامپيوتر با دنبال كردن دستورالعملهای برنامه نتايج حل مسئله را نشان ميدهد. توجه كنيد كه در مثالهای فوق بازيكنان ـ بيماران و … الگوریتمی را دنبال ميكنند.
به خاطر داشته باشيد كه كامپيوتر تنها قادر به انجام بعضي امور است (دستورات متوالی ـ انتخاب ـ حلقه و زير برنامه). لذا وظيفه اوليه برنامهنويس اين است كه ببيند چگونه ميتواند كامپيوتر را براي تبديل و پردازش داده به خدمت بگيرد. اگر دستورالعملهاي مجاز را در زبان برنامهنويسي بدانيم، الگوریتمی را طراحي خواهيم كرد كه بهآسانی به برنامه (کد) تبدیل شود.
اگرچه حل مسئله پخت تخممرغ الگوریتم پیچیدهای ندارد و در صورت شناخت صحیح دامنه مسئله بهراحتی قابلحل است، اما در اینجا برای نشان دادن مفهوم طراحی الگوریتم، الگوریتم حل این مسئله را ذکر میکنیم:
اگر تعريف برنامه كامپيوتري و الگوریتم شبيه به نظر ميرسند، به اين دليل است كه در اصل برنامهها پيادهسازي الگوریتمها هستند. به بيان سادهتر برنامه الگوریتمی است كه براي كامپيوتر نوشته شده است.
در برخی موارد برنامهنویسان از نمودار فلوچارت برای مشخص کردن الگوریتم موردنظر خود استفاده میکنند. برای روشنتر شدن موضوع در نظر بگیرید که میخواهیم مسئلهای را حل کنیم که با دریافت 3 عدد مشخص کند که آیا این اعداد میتوانند اندازههای اضلاع یک مثلث باشند یا خیر؟
شرط اینکه سه عدد بتوانند اضلاع یک مثلث باشند این است که مجموع هر دو عدد از عدد سوم بیشتر باشد. فلوچارت این الگوریتم در شکل 1-3 نشان داده شده است. به نمادهایی که برای مراحل مختلف شامل پردازشها، شرطها و نمایش خروجی استفاده شده است توجه نمایید.
گام 3: بازبيني: پس از شناخت دامنه مسئله و طراحی الگوریتم لازم است طرح و الگوریتم به دست آمده مورد بازبینی قرار گیرد و کنترل شود تا اطمینان حاصل کنیم همه جوانب در آن دیده شده و در همه حالتهای ممکن پاسخ صحیح را پیدا میکند.
مرحله پیادهسازی:
گام 4: برنامهنویسی: تبديل الگوریتم به زبان برنامهنويسي: در این مرحله عملاً کار نوشتن برنامه به زبانی که انتخاب شده (در این کتاب C++) انجام میشود. تمام مراحل الگوریتم باید به کد C++ تبدیل شوند.
گام 5: آزمون: پس از نوشتن برنامه باید اطمينان حاصل شود که درست کار میکند و محاسبات را آنگونه که انتظار میرفته به انجام میرساند. تکنیکهای متعددی در این گام مورد استفاده قرار میگیرد که جزئیات آن در فصلهای آتی توضیح داده شده است. نکته مهم اینکه در روشهای جدید، آزمون همزمان با برنامهنویسی یا حتی گاهی پیش از آن انجام میشود. به عبارتی همزمان با تولید برنامه تست آن مرحلهبهمرحله انجام میشود و در صورت موفقیتآمیز بودن وارد مرحله بعدی میشود.
مرحله نگهداري:
بعد از آنكه برنامه نوشته شد، مرحله نگهداري برنامه شروع ميشود.
گام 6: استفاده: استفاده از برنامه.
گام 7: نگهداري (افزودن قابلیتهای جدید): اِعمال تغييرات لازم جهت برآوردن نيازهاي جديد.
خطاهای برنامهنویسی
یکی از مسائل مهم در برنامهنویسی، خطاهایی است که ممکن است در مراحل مختلف کار پیش آیند. این مراحل در شکل 1-1 ب نشان داده شدهاند. در این بخش به معرفی اجمالی انواع خطاها میپردازیم. دانشجویان باید توجه داشته باشند که بسیاری از این خطاها اجتنابناپذیر هستند و بنابراین باید با توجه به این توضیحات مهارت لازم جهت تشخیص و رفع آنها را پیدا کنند.
خطاهای زمان کامپایل
بخش عمدهای از خطاهای برنامه از نوع خطاهای سینتکس یا دستور زبان است که به خطاهای کامپایل نیز شناخته میشوند. این خطاها در زمان کامپایل و توسط کامپایلر تشخیص داده میشوند. دلیل بروز این خطاها عدم رعایت قالب و ساختار دستورات طبق تعریف و استانداردهای C++ است. بهعنوانمثال تمام دستورات اجرایی در C++ باید با یک نقطه ویرگول یا ؛ خاتمه یابند. درصورتیکه در پایان یکی از دستورات ؛ فراموش شده باشد، توسط کامپایلر خطا گرفته میشود. درواقع ؛ به کامپایلر میفهماند که یک دستور خاتمه پیدا کرده و دستور دیگری شروع شده است، دقیقاً شبیه کاربرد نقطه در متنهای فارسی. بهعنوانمثال دیگر در همان برنامه ساده ابتدای این فصل، چنانچه اعلان #include فراموش شود، کامپایلر نمیتواند دستور خروجی cout را درک کند و لذا خطا میدهد. دقت کنید که کامپایلر در پیغام خطا، شماره خطی را که در آن متوجه خطا شده را اعلام میکند که میتواند به برنامهنویس در رفع خطا کمک کند. در مثال فراموشی ؛ خطی که خطا رخ داده دقیقاً همانجایی است که کامپایلر اعلام میکند. اما در صورت فراموش include کامپایلر خطا را در خط 10 تشخیص میدهد درحالیکه واقعاً مربوط به خط 5 است. برنامهنویسان بهتدریج با اینگونه خطاها آشنا میشوند و بهسرعت میتوانند دلیل آن را پیدا کنند و نسبت به رفع آن اقدام نمایند.
تقریباً اکثر برنامههای بزرگ زمانی که نوشته میشوند با تعدادی خطای کامپایل همراه هستند. معمولاً تعداد این خطاها زیاد است و نباید باعث نگرانی برنامهنویس شود. گاهی ممکن است دهها خطا از یک برنامه نهچندان بزرگ گرفته شود. اما رفع این خطاها معمولاً بسیار آسان است و زمان زیادی نمیبرد. کافی است پیغام خطا بهدقت خوانده شود تا متوجه شوید چه عاملی باعث بروز خطا شده است. از طرف دیگر گاهی با اصلاح یک مورد بهیکباره تعداد زیادی از خطاها کم میشود.
خطاهای زمان اجرا
نوع دیگری از خطاها در زمان کامپایل شناسایی نمیشوند و در هنگامیکه برنامه اجرا میشود نمایان میشوند. معمولاً این خطاها در همه سناریوهای اجرای برنامه ظاهر نمیشوند و در مواقع خاص و با توجه به ورودیهای خاصی ظهور مییابند. بهعنوانمثال فرض کنید در یک برنامه دو عدد از کاربر گرفته شده و در یک فرمول یکی بر دیگری تقسیم شده باشد. ممکن است این برنامه با مقادیر مختلف درست کار کند ولی وقتی کاربر عدد دوم را 0 وارد کند، خطای تقسیم بر 0 رخ دهد. معمولاً در صورت بروز چنین خطاهایی برنامه بهصورت خودکار بسته میشود. حتماً شما هم گاهی با چنین شرایطی مواجه شدهاید که در هنگام کار کردن با یک برنامه مثلاً ویندوز یا یک برنامه تلفن همراه مثل یک بازی، بهیکباره سیستم قفل کرده باشد و از برنامه خارج شده باشد. اینها نمونههایی از خطاهای زمان اجرا هستند که تا وقتی برنامه اجرا نشود دیده نمیشوند. مثال دیگری از خطاهای زمان اجرا، گرفتن ریشه دوم (جذر) از اعداد منفی است.
معمولاً تعداد خطاهای زمان اجرا خیلی کمتر از خطاهای زمان کامپایل است ولی در عوض رفع آنها سختتر است. در عالم واقع، در برخی از سیستمهای بزرگ ممکن است خطاهایی وجود داشته باشد که حتی پس از سالها ریشه آن بهدرستی شناسایی نشده باشد و موفق به رفع آن نشده باشند.
خطاهای منطقی
نوع سوم از خطاها زمانی اتفاق میافتد که برنامه بهدرستی کامپایل و اجرا میشود و هیچ خطایی هم در ظاهر نمایان نمیشود، ولی در عمل پاسخی که توسط برنامه ارائه میشود با آنچه انتظار داریم، متفاوت است. بهعنوانمثال برنامهای برای پیدا کردن ریشههای یک معادله درجه 2 نوشته شده و هنگام اجرا ریشههای معادله x^2 - 1 = 0 را به اشتباه 2 و 1 محاسبه میکند. شناسایی این خطاها نیز نیازمند حوصله و دقت بیشتری است. برای جلوگیری از وقوع چنین خطاهایی و رفع آنها راهکارهای زیر توصیه میشود:
- جلوگیری از بروز خطا: چنانچه از سبک درست برنامهنویسی تبعیت کنید و پیش از برنامهنویسی راهحل خود را بررسی کنید و الگوریتم خود را روی کاغذ بیاورید و سپس برنامه را به تدریج تکمیل کنید و در هر مرحله آزمون کنید که آیا برنامه مطابق خواسته شما عمل میکند یا خیر، تا حد زیادی از این خطاها جلوگیری میشود. شیوه برنامهنویسی تدریجی در فصلهای مختلف توضیح داده شده است. در این شیوه برنامه بهیکباره تولید نمیشود بلکه هر بار قسمت کوچکی از آن نوشته میشود و سپس آزمون (تست) میشود تا از صحت آن اطمینان حاصل شود. رعایت نکاتی که در خصوص افزایش خوانایی برنامه ذکر شد نیز به جلوگیری از بروز خطاها و شناسایی سریعتر آنها کمک میکند.
- آزمون برنامه بهمنظور پیدا کردن خطاها: حتماً برنامهنویسان باید برنامههای خود را آزمایش کنند تا از صحت آن مطمئن شوند. برای این منظور لازم است همه حالتهای ممکن در نظر گرفته شوند و مورد آزمایش قرار گیرند. برای روشنتر شدن موضوع از مثال حل معادله درجه 2 استفاده میکنیم. در اصل برای اینکه اطمینان حاصل شود این برنامه درست کار میکند باید به ازای همه معادلات درجه 2 مورد آزمایش قرار گیرد. ازآنجاکه تعداد این معادلات نامحدود است و عملاً امکان آزمودن همه آنها وجود ندارد، ناگزیرم تنها حالتهای خاصی را آزمایش کنیم. هنر برنامهنویس این است که بتواند حالتهایی را پیدا کند که درصورتیکه برنامه در آن حالتها درست کار کند، بتوان با اطمینان نزدیک بهیقین گفت که برای همه حالتها درست کار خواهد کرد. برای این مثال باید حالتهایی که دلتا مثبت، منفی و مساوی 0 است در نظر گرفته شود و مورد آزمون قرار گیرد. همچنین میتوان حالتهایی که ضریب جمله x^2 یا x یا جمله ثابت صفر باشد نیز مورد آزمون قرار داد.
- اشکالزدایی از برنامه: در مرحله بعد، زمانی که متوجه شدیم برنامه عملکرد درستی ندارد و برخی از آزمونها را بهدرستی نمیگذراند، باید نسبت به پیدا کردن ریشه مشکل اقدام کنیم. معمولاً محیطهای برنامهنویسی امکاناتی را برای خطایابی و اشکالزدایی از برنامه فراهم میآورند. بهاینترتیب امکان اجرای خط به خط برنامه فراهم میشود تا برنامهنویس بتواند اثر هر یک از دستورات را ملاحظه کند و ببیند در کدام قسمت از برنامه، روند کار از مسیر موردنظر خارج میشود و نهایتاً منجر به نتیجه اشتباه میشود. انتظار میرود دانشجویان با تمرین زیاد بتوانند به خوب از این امکانات استفاده کنند.
روشهای حل مسئله
برای حل مسئله و طراحی الگوریتم مناسبی که بتواند مسئله را حل کند، لازم است برنامهنويس با تکنیکها و استراتژي¬هایي كه براي به دست آوردن الگوریتم يا حل مسئله بكار ميروند آشنا باشد تا با بهکارگیری آنها براي مسائل مختلف، برنامهنويسي كند. در ادامه بهاجمال به اين تکنیکها میپردازیم.
شکستن مسئله و غلبه بر آن
این تکنیک تقریباً در همه رشتههای مهندسی مورداستفاده قرار میگیرد و در برنامهنویسی هم بسیار کلیدی و پرکاربرد است. يك مسئله بزرگ را به قسمتهاي کوچکتری میشکنیم كه حل آنها آسانتر است، بهاینترتیب حل مسئله راحتتر ميشود (شكل 1-15 را ببينيد). همانگونه که ذکر شد این کار در اکثر رشتههای مهندسی مرسوم است. بهعنوانمثال، مسئله طراحی یک خودرو به زیر مسئلههایی نظیر طراحی موتور، طراحی سیستم انتقال نیرو، طراحی بدنه، طراحی تزئینات داخلی، طراحی سیستم ترمز و نظایر آن شکسته میشود. طراحی یک هتل به مسائل کوچکتری نظیر طراحی سازه، طراحی نما، طراحی سیستم گرمایشی و سرمایشی، دکوراسیون داخلی و نظایر آن تبدیل میشود. طراحی یک سیستم سفارش تاکسی اینترنتی از طریق تلفن همراه (شبیه اسنپ و تپسی) به زیر مسئلههای کوچکتری نظیر پیدا کردن موقعیت مکانی از طریق جیپیاس، نمایش اطلاعات روی نقشه، محاسبه قیمت مسیر، پیدا کردن نزدیکترین رانندهها، و نظایر آن تبدیل میشود. حل هر یک از این زیر مسئلهها بهتنهایی بهمراتب راحتتر از حل مسئله اصلی است. به این شیوه حل مسئله، روش بالا به پائين گفته میشود. رویکرد شئ گرایی هم که در فصل .. توضیح داده شده است بر این تکنیک استوار است.
طبيعي است كه نبايد چرخ را دوباره اختراع كرد. برای بسیاری از مسائل راهحلهای آماده وجود دارد که با کمی جستوجو میتوان آن را پیدا کرد و گاهی با تغییرات اندک میتوان از آنها استفاده کرد. بهعنوانمثال بسیاری از زیر مسئلههای پایه مهندسی قبلاً حل شده و کد آماده آن به شکلهای مختلف نظیر کتابخانههای آماده در دسترس است. بهعنوانمثال کدهای زیادی برای حل دستگاه معادلات وجود دارد و درصورتیکه در یک مسئله خاص نیاز به حل دستگاه معادلات پیش آید، میتوان از این کدها استفاده کرد. بهعنوانمثال دیگر، بسیاری از برنامههای مربوط به وبسایتها نیاز به تقویم فارسی دارند و مؤلفههای زیادی برای این منظور تاکنون طراحی شده که میتوان از آنها استفاده کرد. همچنین ممکن است خود ما در کارهای قبلی خود با مسئله مشابهی مواجه شده باشیم یا بخشهایی از آن مسئله را قبلاً حل کرده باشیم که میتوانیم از همان مجدداً استفاده کنیم. استفاده از کارهای گذشته اصطلاحاً استفاده مجدد نامیده میشود. برنامهنویسان موفق سعی میکنند بهگونهای کد نویسی کنند که بعداً هم بتوانند از کدهای خود در کارهای مشابه استفاده کنند.
استفاده از الگوها
بسیاری از مسائل اگرچه ممکن است در ظاهر با هم تفاوتهای زیادی داشته باشند، اما در باطن از الگوهای یکسانی تبعیت میکنند. همانگونه که بسیاری از خانههایی که در یک دوره زمانی خاص و در یک جغرافیای خاص ساخته میشوند، اگرچه توسط افراد مختلفی طراحی و ساخته میشوند اما مشابهتهای ساختاری زیادی دارند و معمولاً طراحان آنها از راهکارهای مشابهی برای حل مسائل خود بهره میگیرند، بهعنوانمثال مردم یزد از بادگیر برای خنک کردن خانههای خود در قدیم استفاده میکردند یا مردم شمال کشور به دلیل بارندگی زیاد سقف خانهها را بهصورت شیروانی میسازند. این الگوها در جاهای مختلف مشاهده میشود. همین موضوع در برنامهنویسی هم وجود دارد. بهعنوانمثال، الگو و تکنیک سانتریفیوژ که در نیروگاههای اتمی نیز مورد استفاده قرار میگیرد، در کارخانههای لبنیات برای تولید کره استفاده میشود، اما جزئیات و نحوه استفاده از آن کاملاً متفاوت است. یا همان الگوی موتورهای الکتریکی در پنکه، دینام کولر، همزن برقی و خیلی از دستگاههای دیگر مشاهده میشود. در برنامهنویسی نیز همین اتفاق رخ میدهد و شایسته است از تجربیات حوزههای مشابه استفاده شود. یکی از اقدامات مؤثر در حل یک مسئله، شناسایی الگوهایی است که میتواند در حل آن کمک کند. این الگوها معمولاً در سطوح مختلف تحلیل، معماری، طراحی و پیادهسازی موجود است و بخشهای عمدهای از آن مستند شده است. ازآنجاکه مسائل مشابه قبلاً حل شدهاند و راهحلهای آنها هم در طول زمان مورد آزمون قرار گرفته و امتحان خود را پس داده است، با خیال راحت میتوان از آنها استفاده نمود. بهعنوانمثال مسئله رزرواسیون در مورد صندلیهای هواپیما، صندلیهای یک سالن تئاتر، سالن سینما، سالن همایش و کنسرت، اتاقهای یک هتل و صندلیهای استادیوم مشابه هم است و از تجربیات هر یک میتوان در مسائل مشابه بهره گرفت.
وقتي دنبال حل يك مسئله هستيد، خود را محدود به راهحل همان مسئله خاص نکنید، بلكه ديد وسيعتري از مسئله پيدا كنيد و سعی کنید مسائل مشابه را پیدا کنید. هرچند معمولاً هیچگاه این مسائل بهطور کامل باهم تطبیق ندارند، اما مشابهتهای آنها کمک میکند تا از تجربیات گذشته استفاده کنید و کار خود را از مرحله جلوتری ادامه دهید.
بنچمارک و مهندسی مجدد
بسیاری از مسائلی که ممکن است به شما ارجاع شود، احتمالاً نمونههای داخلی و خارجی متعددی داشته باشد. پیش از اینکه شروع به طراحی سیستم و پیدا کردن راهحل مناسب کنید، نمونههای موجود را بررسی کنید. ببینید قبلاً چه سیستمهایی برای این موضوع طراحی شده است و این سیستمها چه قابلیتهایی دارند. در صورت امکان چند نمونه را دانلود و نصب کنید و محیط کاری آنها را بررسی کنید تا با نحوه کارکرد آنها آشنا شوید. نقاط ضعف و قوت هر یک را شناسایی کنید. این کار ایدههای بسیار خوبی به شما منتقل خواهد کرد. همچنین باید از خود سؤال کنید که با وجود این نرمافزارهای مشابه آیا واقعاً تولید یک سیستم جدید ضرورتی دارد؟
استقرا
همانگونه که در ریاضیات خواندید، استقرا یکی از تکنیکهای کارآمد در حل بسیاری از مسائل است و برخی از مسائل و قضایای ریاضی که اثبات آن به شیوه معمول بسیار مشکل است، به کمک استقرا به آسانی حل میشوند. در روش استقرایی کافی است بتوانیم یک مسئله را برای ابعاد خیلی کوچک که همان پایه استقرا است (معادل n=1 در بسیاری از مسائل) حل کنیم و سپس نشان دهیم که اگر مسئله برای اندازه-1 n قابل حل باشد، برای اندازه n هم قابل حل است. به عبارتی حل مسئله با ابعاد n به n – 1 یا اندازه دیگری کوچکتر از n کاهش پیدا میکند. این تکنیک، از تکنیکهای کارآمد و بسیار مهم حل مسئله به شمار میآید که در فصل 6 به طور مفصل به آن پرداخته شده است.
تلفيق راهحلها
بسیاری از مسائل دنیای واقعی، به یکی از روشها و راهحلهایی که در اینجا مطرح شدند محدود نیستند و معمولاً ترکیبی از این راهکارها میتواند مورد استفاده قرار گیرد. طبیعی است که در ابتدای راه، پیدا کردن راهحل مناسب چندان ساده نباشد، اما بهتدریج با تمرین و ممارست میتوانید تواناییهای حل مسئله را در خود پرورش دهید. در اینجا صرفاً بهروشهایی پرداختیم که در حوزه برنامهنویسی پرکاربردتر هستند، اما روشهای حل مسئله به این موارد محدود نیست و میتوانید با مطالعه بیشتر، تکنیکهای سیستماتیک حل مسائل را فراگیرید.
پیدا کردن راهحل يك مسئله همیشه خيلي شستهورفته نيست، در حقيقت حل يك مسئله فرآيندي مبتني بر سعي و خطا است كه تلاش، تصحيح و پالايشهاي متعددي را ميطلبد. در هر مرحله ممکن است ایدهای به ذهنتان برسد و تلاش کنید تا آن ایده را محک بزنید تا ببینید آیا میتواند مسئله را حل کند یا خیر؟ اگر جواب مثبت باشد، كه كار انجام شده است وگرنه مجدداً تلاش صورت ميپذيرد و معمولاً با تركيبي از تکنیکهای فوق و هوش برنامهنويس مسئله حل ميشود.
بيواهمه شروع كنيد
همه ما تجربه نوشتن انشاء را در سالهای اوليه تحصيل داريم و تصور اين تجربه را داريم كه كاغذ سفيدي روبروي ماست و نميدانيم چگونه شروع كنيم. آنچه بهعنوان راهنمایی گفته ميشد اين بود كه هر چه در ذهن داريد و فكر ميكنيد به موضوع مربوط است و در روشنتر كردن موضوع كمك ميكند، روي كاغذ بياوريد و نهايتاً به تصحيح آن بپردازيد. اين توصيه اكنون نيز در نوشتن برنامه تكرار ميشود. مسئله را به اجزاء کوچکتر تقسيم كنيد (روش تقسيم و غلبه). با توصيف مسئله براي خودتان ممكن است به مواردي برخورد كنيد كه ورودي و خروجي بهطور مشخص معلوم باشند، بهطوریکه بتوانید آن بخش از مسئله را حل کنید. نوشتن مسئله به زبان خودتان سبب ميشود كه روي قسمتهای مختلف مسئله تمركز كرده و دريابيد كه چه چيزهايي براي حل مسئله مورد نياز است.
بسیاری از دانشجویان در مواجهه با یک زبان جدید که قدری متفاوت از آموختههای پیشین آنها است و با مجموعه قواعدی متفاوت از زبانهای نوشتاری سروکار دارد، دچار سردرگمی و استرس میشوند. در این کتاب سعی شده مطالب به زبان ساده و گامبهگام مطرح شود. لذا چنانچه با حوصله این مطالب را دنبال کنید و در کنار آن با تمرین آموختههای خود را تثبیت کنید، خواهید توانست بهآسانی این زبان را فراگیرید. توصیه میشود از مثالهای ساده شروع کنید و همگام با کتاب سعی کنید مثالهای متنوعتری را حل کنید.