بسم الله الرحمن الرحيم.

Slides:



Advertisements
Similar presentations
Lecture Computer Science I - Martin Hardwick The Programming Process rUse an editor to create a program file (source file). l contains the text of.
Advertisements

Modular Programming With Functions
C++ Basics Variables, Identifiers, Assignments, Input/Output.
Week 4 Selections This week shows how to use selection statements for more flexible programs. It also describes the various integral types that are available.
C Programming Basics Lecture 5 Engineering H192 Winter 2005 Lecture 05
Iteration This week we will learn how to use iteration in C++ Iteration is the repetition of a statement or block of statements in a program. C++ has three.
C++ Basics. COMP104 C++ Basics / Slide 2 Introduction to C++ * C++ is a programming language for manipulating numbers and user-defined objects. * C++
SECTION 2 C++ Language Basics. Strategies for learning C++ Focus on concepts and programming techniques. (Don’t get lost in language features) Learn C++
KEAN UNIVERSITY Visual C++ Dr. K. Shahrabi. Developer studio Is a self-contain environment for creating, compiling, linking and testing windows program.
C++ Workshop Mark Hennessy Dept. Computer Science 18 th – 22 nd September 2006.
CS 192 Lecture 3 Winter 2003 December 5, 2003 Dr. Shafay Shamail.
© Janice Regan, CMPT 128, Jan CMPT 128: Introduction to Computing Science for Engineering Students Data representation and Data Types Variables.
COMPUTER PROGRAMMING. Data Types “Hello world” program Does it do a useful work? Writing several lines of code. Compiling the program. Executing the program.
Function. Introduction Library function New defined function Random number generator Scope Inline function Function overload Function Function.
1 TAC2000/ Protocol Engineering and Application Research Laboratory (PEARL) MATH Functions in C Language.
 Programming Languages  First Generation Languages (Machine Language)  We Actually have to do a few things. First we have to find the operating code,
M. Taimoor Khan #include void main() { //This is my first C++ Program /* This program will display a string message on.
Chapter 3 Expressions and Interactivity Department of Computer Science Missouri State Univeristy.
1 More C++ Basics Chapter 3 Lecture CSIS 10A. 2 Agenda Review  C++ Standard Numeric Types Arithmetic–way more than you want! Character (char) data type.
C++ Programming, Namiq Sultan1 Chapter 2 Introduction to C++ Namiq Sultan University of Duhok Department of Electrical and Computer Engineerin Reference:
C++ Loose ends from last time. Variable initialization You can do the usual things int x; x = 10; int y = 20; And you can do an unusual thing int x(10);
Chapter 8 Iteration Dept of Computer Engineering Khon Kaen University.
COMPUTER PROGRAMMING. variable What is variable? a portion of memory to store a determined value. Each variable needs an identifier that distinguishes.
Engineering H192 - Computer Programming The Ohio State University Gateway Engineering Education Coalition Lect 5P. 1Winter Quarter C Programming Basics.
Chapter 7 Selection Dept of Computer Engineering Khon Kaen University.
Department of Electrical and Computer Engineering Introduction to C++: Primitive Data Types, Libraries and Operations By Hector M Lugo-Cordero August 27,
Variables and Data Types.  Variable: Portion of memory for storing a determined value.  Could be numerical, could be character or sequence of characters.
Engineering H192 - Computer Programming Gateway Engineering Education Coalition Lect 5P. 1Winter Quarter C Programming Basics Lecture 5.
Chapter 9 Functions Dept of Computer Engineering Khon Kaen University.
Data Structure and c K.S.Prabhu Lecturer All Deaf Educational Technology.
Data Types, Primitive Types in C++, Variables – Declaration, Initialization, Scope Telerik Software Academy academy.telerik.com Learning and Development.
2/19/2016IT 279, Chung-Chih Li1 Branching Condition Statement list 1 T F Statement list 2 Condition Statement list T F.
C++ Basics Programming. COMP104 Lecture 5 / Slide 2 Introduction to C++ l C is a programming language developed in the 1970s with the UNIX operating system.
1 CSC 1111 Introduction to Computing using C++ C++ Basics (Part 1)
Basic Types, Variables, Literals, Constants. What is in a Word? A byte is the basic addressable unit of memory in RAM Typically it is 8 bits (octet)
Functions and Libraries. Reference parameters void setToZero(int var) { var = 0; } int main() { int var = 0; setToZero(var); cout
Chapter INTRODUCTION Data Types and Arithmetic Calculations.
C++ Lesson 1.
Asst.Prof.Dr. Tayfun ÖZGÜR
Lecture 3 Expressions, Type Conversion, Math and String
Variables, Identifiers, Assignments, Input/Output
Chapter 1.2 Introduction to C++ Programming
LESSON 06.
Chapter 1.2 Introduction to C++ Programming
Basics (Variables, Assignments, I/O)
Chapter 1.2 Introduction to C++ Programming
Data types Data types Basic types
Chapter 3 - Functions Outline 3.1 Introduction
LESSON 3 IO, Variables and Operators
Compiler Construction
C Short Overview Lembit Jürimägi.
CSC113: Computer Programming (Theory = 03, Lab = 01)
مبانی کامپیوتر و برنامه سازی
Reserved Words.
توابع در C++ قسمت اول اصول كامپيوتر 1.
مبانی برنامه‌سازی با C++ جلسه دوم
Random Number Generation
Basics (Variables, Assignments, I/O)
درس برنامه‌سازي کامپيوتر
بسم الله الرحمن الرحيم.
Keywords.
Govt. Polytechnic,Dhangar
Variables, Identifiers, Assignments, Input/Output
Prof. Bhushan Trivedi Director GLS Institute of Computer Technology
Seoul National University
2. Second Step for Learning C++ Programming • Data Type • Char • Float
Programming Language C Language.
Module 2 - Part 1 Variables, Assignment, and Data Types
Seoul National University
Chapter 3 - Functions Outline 3.1 Introduction
Presentation transcript:

بسم الله الرحمن الرحيم

دانشكده فناوري اطلاعات دانشگاه پيام نور دانشكده فناوري اطلاعات

تهيه كننده: دكتر احمد فراهي برنامه سازي پيشرفته تهيه كننده: دكتر احمد فراهي

مقدمه: زبان C يک زبان همه منظوره است. دستورالعمل‌هاي اين زبان بسيار شبيه عبارات جبري و نحو آن شبيه جملات انگليسي مي باشد. اين امر سبب مي‌شود که C يک زبان سطح بالا باشد که برنامه‌نويسي در آن آسان است ›››

++C که از نسل C است، تمام ويژگي‌هاي C را به ارث برده است ++C که از نسل C است، تمام ويژگي‌هاي C را به ارث برده است. اما برتري فني ديگري هم دارد: C++ اکنون «شي‌گرا» است. مي‌توان با استفاده از اين خاصيت، برنامه‌هاي شي‌گرا توليد نمود. برنامه‌هاي شي‌گرا منظم و ساخت‌يافته‌اند، قابل روزآمد کردن‌اند، به سهولت تغيير و بهبود مي‌يابند و قابليت اطمينان و پايداري بيشتري دارند.

اهم مطالب اين كتاب : جلسه اول: «مقدمات برنامه‌نويسي با C++» جلسه دوم: «انواع اصلي» جلسه سوم: «انتخاب» جلسه چهارم: ‹‹تكرار» جلسه پنجم: «توابع» جلسه‌ ششم: « آرايه‌ها»

جلسه هفتم: «اشاره‌گرها و ارجاع‌ها» جلسه‌ هشتم: «رشته‌هاي‌ كاراكتري و فايل‌ها در ++Cاستاندارد» جلسه نهم: «شيئ‌گرايي» جلسه‌ دهم: «سربارگذاري عملگرها» جلسه يازدهم: «تركيب و وراثت»

مقدمات برنامه‌نويسي با C++ جلسه اول مقدمات برنامه‌نويسي با C++

آنچه در اين جلسه مي خوانيد: 1- چرا C++ ؟ 2- تاريخچۀ C++ 3- آماده‌سازي مقدمات 4- شروع کار با C++ 5- عملگر خروجي 6- ليترال‌ها و کاراکترها 7- متغيرها و تعريف آن‌ها 8- مقداردهي اوليه به متغيرها 9- ثابت‌ها 10- عملگر ورودي

هدف کلي: آشنايي با تاريخچه و مزاياي زبان برنامه‌نويسي C++ و بيان مفاهيم بنيادي شي‌گرايي و عناصر مهم برنامه‌هاي C++

هدف‌هاي رفتاري: انتظار مي‌رود پس از پايان اين جلسه بتوانيد: - مزاياي زبان C++ را بر زبان‌هاي مشابه ذکر کرده و تفاوت آن را با زبان C بيان کنيد. - شرح مختصري از روند پيشرفت زبان‌هاي برنامه‌نويسي را بيان کرده و مشکلات هر دوره را به اختصار شرح دهيد. - مزاياي شي‌گرايي در توليد نرم‌افزار را برشماريد. - اصول سه‌گانۀ شي‌گرايي را نام برده و هر يک را به اختصار شرح دهيد. >>

- قالب کلي برنامه‌هاي C++ را بشناسيد و بتوانيد برنامه‌هاي کوچک را نوشته و آزمايش کنيد. - نحوۀ اعلان متغيرها و شيوۀ مقداردهي به آن‌ها را بدانيد. - سه موجوديت «ليترال»، «کاراکتر» و «عدد» را شناخته و فرق بين آن‌ها را شرح دهيد. - علت و شيوه‌هاي افزودن توضيح به کد برنامه را شرح دهيد. - علت و شيوۀ معرفي ثابت‌ها در برنامه را شرح دهيد.

مقدمه در دهه 1970 در آزمايشگاه‌هاي بل زباني به نام C ايجاد شد. انحصار اين زبان در اختيار شرکت بل بود تا اين که در سال 1978 توسط Kernighan و Richie شرح کاملي از اين زبان منتشر شد و به سرعت نظر برنامه‌نويسان حرفه‌اي را جلب نمود. هنگامي که بحث شي‌گرايي و مزاياي آن در جهان نرم‌افزار رونق يافت، زبان C که قابليت شي‌گرايي نداشت ناقص به نظر مي‌رسيد تا اين که در اوايل دهۀ 1980 دوباره شرکت بل دست به کار شد و Bjarne Stroustrup زبان C++ را طراحي نمود

C++ ترکيبي از دو زبان C و Simula بود و قابليت‌هاي شي‌گرايي نيز داشت C++ ترکيبي از دو زبان C و Simula بود و قابليت‌هاي شي‌گرايي نيز داشت. از آن زمان به بعد شرکت‌هاي زيادي کامپايلرهايي براي C++ طراحي کردند. اين امر سبب شد تفاوت‌هايي بين نسخه‌هاي مختلف اين زبان به وجود بيايد و از قابليت سازگاري و انتقال آن کاسته شود. به همين دليل در سال 1998 زبان C++ توسط موسسۀ استانداردهاي ملي آمريکا (ANSI) به شکل استاندارد و يک‌پارچه در‌آمد.

1- چرا C++ ؟ زبان C يک زبان همه منظوره است در اين زبان عملگر‌هايي تعبيه شده که برنامه‌نويسي سطح پايين و به زبان ماشين را نيز امکان‌پذير مي‌سازد چون C عملگرهاي فراواني دارد، کد منبع برنامه‌ها در اين زبان بسيار کوتاه است

- زبان C براي اجراي بسياري از دستوراتش از توابع کتابخانه‌اي استفاده مي‌کند و بيشتر خصوصيات وابسته به سخت‌افزار را به اين توابع واگذار مي‌نمايد. برنامۀ مقصدي که توسط کامپايلرهاي C ساخته مي‌شود بسيار فشرده‌تر و کم‌حجم‌تر از برنامه‌هاي مشابه در ساير زبان‌ها است. C++ که از نسل C است، تمام ويژگي‌هاي جذاب C را به ارث برده است . و سرانجام آخرين دليل استفاده از C++ ورود به دنياي C# است.

2- تاريخچۀ C++ در دهه 1970 در آزمايشگاه‌هاي بل زباني به نام C ايجاد شد. انحصار اين زبان در اختيار شرکت بل بود تا اين که در سال 1978 توسط Kernighan و Richie شرح کاملي از اين زبان منتشر شد و به سرعت نظر برنامه‌نويسان حرفه‌اي را جلب نمود. هنگامي که بحث شي‌گرايي و مزاياي آن در جهان نرم‌افزار رونق يافت، زبان C که قابليت شي‌گرايي نداشت ناقص به نظر مي‌رسيد تا اين که در اوايل دهۀ 1980 دوباره شرکت بل دست به کار شد و Bjarne Stroustrup زبان C++ را طراحي نمود.

C++ ترکيبي از دو زبان C و Simula بود و قابليت‌هاي شي‌گرايي نيز داشت از آن زمان به بعد شرکت‌هاي زيادي کامپايلرهايي براي C++ طراحي کردند. اين امر سبب شد تفاوت‌هايي بين نسخه‌هاي مختلف اين زبان به وجود بيايد و از قابليت سازگاري و انتقال آن کاسته شود. به همين دليل در سال 1998 زبان C++ توسط موسسۀ استانداردهاي ملي آمريکا (ANSI) به شکل استاندارد و يک‌پارچه در‌آمد. کامپايلرهاي کنوني به اين استاندارد پايبندند. کتاب حاضر نيز بر مبناي همين استاندارد نگارش يافته است.

3- آماده‌سازي مقدمات يک «برنامه» دستورالعمل‌هاي متوالي است که مي‌تواند توسط يک رايانه اجرا شود. براي نوشتن و اجراي هر برنامه به يک «ويرايش‌گر متن» و يک «کامپايلر» احتياج داريم.  بستۀ Visual C++ محصول شرکت ميکروسافت و بستۀ C++ Builder محصول شرکت بورلند نمونه‌هاي جالبي از محيط مجتمع توليد براي زبان C++ به شمار مي‌روند.

4- شروع کار با C++ #include <iostream> int main() C++ نسبت به حروف «حساس به حالت» است يعني A و a را يکي نمي‌داند مثال : اولين برنامه اولين برنامه‌اي که مي‌نويسيم به محض تولد، به شما سلام مي‌کند و عبارت "Hello, my programmer!" را نمايش مي‌دهد: #include <iostream> int main() { std::cout << "Hello, my programmer!\n" ; return 0; }

اولين خط از کد بالا يک «راهنماي پيش‌پردازنده» است اولين خط از کد بالا يک «راهنماي پيش‌پردازنده» است. راهنماي پيش‌پردازنده شامل اجزاي زير است: 1- کاراکتر # که نشان مي‌دهد اين خط، يک راهنماي پيش‌پردازنده است. اين کاراکتر بايد در ابتداي همۀ خطوط راهنماي پيش‌پردازنده باشد. 2- عبارت include 3- نام يک «فايل کتابخانه‌اي» که ميان دو علامت <> محصور شده است.

خط دوم برنامه نيز بايد در همه برنامه‌هاي C++ وجود داشته باشد. اين خط به کامپايلر مي‌گويد که «بدنۀ اصلي برنامه» از کجا شروع مي‌شود. اين خط داراي اجزاي زير است: 1 – عبارت int که يک نوع عددي در C++ است. 2 – عبارت main که به آن «تابع اصلي» در C++ مي‌گويند. 3 – دو پرانتز () که نشان مي‌دهد عبارت main يک «تابع» است. هر برنامه فقط بايد يک تابع main() داشته باشد .

سه خط آخر برنامه، «بدنۀ اصلي برنامه» را تشکيل مي‌دهند. دستورات برنامه از خط سوم شروع شده است. دستور خط سوم با علامت سميکولن ; پايان يافته است.

توضيح توضيح، متني است که به منظور راهنمايي و درک بهتر به برنامه اضافه مي‌شود و تاثيري در اجراي برنامه ندارد. . کامپايلر توضيحات برنامه را قبل از اجرا حذف مي‌کند. استفاده از توضيح سبب مي‌شود که ساير افراد کد برنامۀ شما را راحت‌تر درک کنند.

به دو صورت مي‌توانيم به برنامه‌هاي C++ توضيحات اضافه کنيم: 1 – با استفاده از دو علامت اسلش // : هر متني که بعد از دو علامت اسلش بيايد تا پايان همان سطر يک توضيح تلقي مي‌شود . 2 – با استفاده از حالت C : هر متني که با علامت /* شروع شود و با علامت */ پايان يابد يک توضيح تلقي مي‌شود.

5- عملگر خروجي علامت << عملگر خروجي در C++ نام دارد (به آن عملگر درج نيز مي‌گويند). يک «عملگر» چيزي است که عملياتي را روي يک يا چند شي انجام مي‌دهد. عملگر خروجي، مقادير موجود در سمت راستش را به خروجي سمت چپش مي‌فرستد. به اين ترتيب دستور cout<< 66 ; مقدار 66 را به خروجي cout مي‌فرستد که cout معمولا به صفحه‌نمايش اشاره دارد. در نتيجه مقدار 66 روي صفحه نمايش درج مي‌شود.

6 -ليترال‌ها و کاراکترها يک «ليترال» رشته‌اي از حروف، ارقام يا علايم چاپي است که ميان دو علامت نقل قول " " محصور شده باشد. يک «کاراکتر» يک حرف، رقم يا علامت قابل چاپ است که ميان دونشانۀ ' ' محصور شده باشد. پس 'w' و '!' و '1' هر کدام يک کاراکتر است. به تفاوت سه موجوديت «عدد» و «کاراکتر» و «ليترال رشته‌اي» دقت کنيد: 6 يک عدد است، '6' يک کاراکتر است و "6" يک ليترال رشته‌اي است.

7 - متغيرها و تعريف آن‌ها: «متغير» مکاني در حافظه است که چهار مشخصه دارد: نام، نوع، مقدار، آدرس. وقتي متغيري را تعريف مي‌کنيم، ابتدا با توجه به نوع متغير، آدرسي از حافظه در نظر گرفته مي‌شود، سپس به آن آدرس يک نام تعلق مي‌گيرد.

نحو اعلان يک متغير type name initializer در C++ قبل از اين که بتوانيم از متغيري استفاده کنيم، بايد آن را اعلان نماييم. نحو اعلان يک متغير type name initializer عبارت type نوع متغير را مشخص مي‌کند. نوع متغير به کامپايلر اطلاع مي‌دهد که اين متغير چه مقاديري مي‌تواند داشته باشد و چه اعمالي مي‌توان روي آن انجام داد.

name \ initializer مقداردهي اوليه دستور زير تعريف يک متغير صحيح را نشان مي‌دهد: int n = 50;

8 - مقداردهي اوليه به متغيرها در بسياري از موارد بهتر است متغيرها را در همان محلي که اعلان مي‌شوند مقداردهي کنيم. استفاده از متغيرهاي مقداردهي نشده ممکن است باعث ايجاد دردسرهايي شود. دردسر متغيرهاي مقداردهي نشده وقتي بزرگ‌تر مي‌شود که سعي کنيم متغير مقداردهي نشده را در يک محاسبه به کار ببريم. مثلا اگر x را که مقداردهي نشده در عبارت y = x + 5; به کار ببريم، حاصل y غير قابل پيش‌بيني خواهد بود. براي اجتناب از چنين مشکلاتي عاقلانه است که متغيرها را هميشه هنگام تعريف، مقداردهي کنيم. مثال: int x=45; int y=0;

9- ثابت‌ها در بعضي از برنامه‌ها از متغيري استفاده مي‌کنيم که فقط يک بار لازم است آن را مقداردهي کنيم و سپس مقدار آن متغير در سراسر برنامه بدون تغيير باقي مي‌ماند. مثلا در يک برنامۀ محاسبات رياضي، متغيري به نام PI تعريف مي‌کنيم و آن را با 3.14 مقداردهي مي‌کنيم و مي‌خواهيم که مقدار اين متغير در سراسر برنامه ثابت بماند. در چنين حالاتي از «ثابت‌ها» استفاده مي‌کنيم. يک ثابت، يک نوع متغير است که فقط يک بار مقداردهي مي‌شود و سپس تغيير دادن مقدار آن در ادامۀ برنامه ممکن نيست. تعريف ثابت‌ها مانند تعريف متغيرهاست با اين تفاوت که کلمه کليدي const به ابتداي تعريف اضافه مي‌شود.

{ // defines constants; has no output: const char BEEP ='\b'; مثال تعريف ثابت‌ها: int main() { // defines constants; has no output: const char BEEP ='\b'; const int MAXINT=2147483647; const float DEGREE=23.53; const double PI=3.14159265358979323846 return 0; } برنامه فوق خروجي ندارد:

10 - عملگر ورودي براي اين که بتوانيم هنگام اجراي برنامه مقاديري را وارد کنيم از عملگر ورودي >> استفاده مي‌کنيم. استفاده از دستور ورودي به شکل زير است: cin >> variable; variable نام يک متغير است.

{ // reads an integer from input: int m; مثال 10 – 1 استفاده از عملگر ورودي برنامۀ زير يک عدد از کاربر گرفته و همان عدد را دوباره در خروجي نمايش مي‌دهد: int main() { // reads an integer from input: int m; cout << "Enter a number: "; cin >> m; cout << "your number is: " << m << endl; return 0; } Enter a number: 52 your number is: 52

cin >> x >> y >> z; عملگر ورودي نيز مانند عملگر خروجي به شکل جرياني رفتار مي‌کند. يعني همان طور که در عملگر خروجي مي‌توانستيم چند عبارت را با استفاده از چند عملگر << به صورت پشت سر هم چاپ کنيم، در عملگر ورودي نيز مي‌توانيم با استفاده از چند عملگر >> چند مقدار را به صورت پشت سر هم دريافت کنيم. مثلا با استفاده از دستور: cin >> x >> y >> z; سه مقدار x و y و z به ترتيب از ورودي دريافت مي‌شوند. براي اين کار بايد بين هر ورودي يک فضاي خالي (space) بگذاريد و پس از تايپ کردن همۀ ورودي‌ها، کليد enter را بفشاريد. آخرين مثال جلسه، اين موضوع را بهتر نشان مي‌دهد.

مثال 11 – 1 چند ورودي روي يک خط برنامۀ زير مانند مثال 10 – 2 است با اين تفاوت که سه عدد را از ورودي گرفته و همان اعداد را دوباره در خروجي نمايش مي‌دهد: int main() { // reads 3 integers from input: int q, r, s; cout << "Enter three numbers: "; cin >> q >> r >> s; cout << "your numbers are: << q << ", " << r << ", " << s << endl; return 0; } Enter three numbers: 35 70 9 your numbers are: 35, 70, 9

پايان جلسه اول

جلسه دوم «انواع اصلي»

آنچه در اين جلسه مي خوانيد: 1- انواع دادۀ عددي 2- متغير عدد صحيح 3- محاسبات اعداد صحيح 4- عملگرهاي افزايشي و کاهشي 5- عملگرهاي مقدارگذاري مرکب 6- انواع مميز شناور ›››

››› 7- تعريف متغير مميز شناور 8 - شکل علمي مقادير مميز شناور 9- نوع بولين bool 10- نوع کاراکتري char 11- نوع شمارشي enum 12- تبديل نوع، گسترش نوع ›››

13- برخي از خطاهاي برنامه‌نويسي 14 - سرريزي عددي 15- خطاي گرد کردن 16- حوزۀ متغيرها

هدف کلي: معرفي انواع متغييرها و نحوۀ به‌کارگيري آن‌ها در برنامه‌هاي C++ هدف‌هاي رفتاري: انتظار مي‌رود پس از پايان اين جلسه بتوانيد: - انواع عددي صحيح در C++ را نام ببريد و متغيرهايي از اين نوع‌ها را در برنامه‌ها به کار ببريد. - انواع عددي مميز شناور در C++ را نام ببريد و متغيرهايي از اين نوع‌ها را در برنامه‌ها به کار ببريد. - نوع بولين را تعريف کرده و متغيرهايي از اين نوع را در برنامه‌ها به کار ببريد. >>>

- نوع شمارشي را شناخته و متغيرهايي از اين نوع را در برنامه‌ها به کار ببريد. - مفاهيم «تبديل نوع» و «گسترش نوع» را شناخته و انواع مختلف را به يکديگر تبديل نماييد. - علت خطاهاي «سرريزي عددي» و «گردکردن» را دانسته و بتوانيد محل وقوع آن‌ها را کشف کنيد. - عملگرهاي حسابي و افزايشي و کاهشي و مقدارگذاري مرکب را در برنامه‌ها به کار ببريد.

مقدمه ما در زندگي روزمره از داده‌هاي مختلفي استفاده مي‌کنيم: اعداد ، تصاوير، نوشته‌ها يا حروف الفبا، صداها، بوها و ... . با پردازش اين داده‌ها مي‌توانيم تصميماتي اتخاذ کنيم، عکس‌العمل‌هايي نشان دهيم و مساله‌اي را حل کنيم. رايانه‌ها نيز قرار است همين کار را انجام دهند. يعني داده‌هايي را بگيرند، آن‌ها را به شکلي که ما تعيين مي‌کنيم پردازش کنند و در نتيجه اطلاعات مورد نيازمان را استخراج کنند.

1- انواع دادۀ عددي در C++ دو نوع اصلي داده وجود دارد: «نوع صحيح» و «نوع مميز شناور». همۀ انواع ديگر از روي اين دو ساخته مي‌شوند (به شکل زير دقت کنيد).

نوع صحيح نوع صحيح براي نگهداري اعداد صحيح (اعداد 0 و 1 و 2 و ...) استفاده مي‌شود. اين اعداد بيشتر براي شمارش به کار مي‌روند و دامنه محدودي دارند.

نوع مميز شناور براي نگهداري اعداد اعشاري استفاده مي‌شود نوع مميز شناور براي نگهداري اعداد اعشاري استفاده مي‌شود. اعداد اعشاري بيشتر براي اندازه‌گيري دقيق به کار مي‌روند و دامنۀ بزرگ‌تري دارند. يک عدد اعشاري مثل 352/187 را مي‌توان به شکل 10×7352/18 يا 102×87352/1 يا1-10×52/1873يا2-10×2/18735 و يا ... نوشت. به اين ترتيب با کم و زياد کردن توان عدد 10 مميز عدد نيز جابه‌جا مي‌شود. به همين دليل است که به اعداد اعشاري «اعداد مميز شناور» مي‌گويند.

2- متغير عدد صحيح C++ شش نوع متغير عدد صحيح دارد تفاوت اين شش نوع مربوط به ميزان حافظۀ مورد استفاده و محدودۀ مقاديري است که هر کدام مي‌توانند داشته باشند. اين ميزان حافظۀ مورد استفاده و محدودۀ مقادير، بستگي زيادي به سخت‌افزار و همچنين سيستم عامل دارد. يعني ممکن است روي يک رايانه، نوع int دو بايت از حافظه را اشغال کند در حالي که روي رايانه‌اي از نوع ديگر نوع int به چهار بايت حافظه نياز داشته باشد.

حداكثر مقدار قابل پذيرش نوع متغيير حداقل مقدار قابل پذيرش حداكثر مقدار قابل پذيرش short -32768 32767 unsigned short 65535 int -2147483648 2147483647 unsigned int 4294967295 long unsigned long وقتي برنامه‌اي مي‌نويسيد، توجه داشته باشيد که از نوع صحيح مناسب استفاده کنيد تا هم برنامه دچار خطا نشود و هم حافظۀ سيستم را هدر ندهيد.

3 -محاسبات اعداد صحيح C++ مانند اغلب زبان‌هاي برنامه‌نويسي براي محاسبات از عملگرهاي جمع (+) ، تفريق (-) ، ضرب (*) ، تقسيم (/) و باقيمانده (%) استفاده مي‌کند.

4 - عملگرهاي افزايشي و کاهشي C++ براي دستکاري مقدار متغيرهاي صحيح، دو عملگر جالب ديگر دارد: عملگر ++ : مقدار يک متغير را يک واحد افزايش مي‌دهد. عملگر -- : مقدار يک متغير را يک واحد کاهش مي‌دهد. اما هر کدام از اين عملگرها دو شکل متفاوت دارند: شکل «پيشوندي» و شکل «پسوندي».

در شکل پيشوندي ابتدا متغير، متناسب با عملگر، افزايش يا کاهش مي‌يابد و پس از آن مقدار متغير براي محاسبات ديگر استفاده مي‌شود. در شکل پسوندي ابتدا مقدار متغير در محاسبات به کار مي‌رود و پس از آن مقدار متغير يک واحد افزايش يا کاهش مي‌يابد. در شکل پيشوندي، عملگر قبل از نام متغير مي‌آيد مثل ++m يا --n . در شکل پسوندي، عملگر بعد از نام متغير مي‌آيد مثل m++ يا n-- .

5 – عملگرهاي مقدارگذاري مرکب 5 – عملگرهاي مقدارگذاري مرکب C++ عملگرهاي ديگري دارد که مقدارگذاري در متغيرها را تسهيل مي‌نمايند. مثلا با استفاده از عملگر += مي‌توانيم هشت واحد به m اضافه کنيم اما با دستور کوتاه‌تر: m += 8; دستور بالا معادل دستور m = m + 8; است با اين تفاوت که کوتاه‌تر است. به عملگر += «عملگر مرکب» مي‌گويند زيرا ترکيبي از عملگرهاي + و = مي‌باشد

5- عملگرهاي مقدارگذاري مرکب قبلا از عملگر = براي مقدارگذاري در متغيرها استفاده کرديم. C++ عملگرهاي ديگري دارد که مقدارگذاري در متغيرها را تسهيل مي‌نمايند. عملگر مرکب در C++ عبارتند از: += و -= و *= و /= و =%

نحوۀ عمل اين عملگرها به شکل زير است: m += 8; → m = m + 8; m -= 8; → m = m - 8; m *= 8; → m = m * 8; m /= 8; →m = m / 8; m %= 8; →m = m % 8;

6 – انواع مميز شناور عدد مميز شناور به بيان ساده همان عدد اعشاري است. عددي مثل 123.45 يک عدد اعشاري است. براي اين که مقدار اين عدد در رايانه ذخيره شود، ابتدا بايد به شکل دودويي تبديل شود: 123.45 = 1111011.01110012 اکنون براي مشخص نمودن محل اعشار در عدد، تمام رقم‌ها را به سمت راست مميز منتقل مي‌کنيم. البته با هر جابجايي مميز، عدد حاصل بايد در تواني از 2 ضرب شود: 123.45 = 0.11110110111001× 27 به مقدار 11110110111001 «مانتيس عدد» و به 7 که توان روي دو است، «نماي عدد» گفته مي‌شود.

درC++ سه نوع مميز شناور وجود دارد: نوع double از هشت بايت براي نگهداري عدد استفاده مي‌کند. نوع long double از هشت يا ده يا دوازده يا شانزده بايت براي نگهداري عدد استفاده مي‌کند. معمولا نوع float از چهار بايت براي نگهداري عدد استفاده مي‌کند.

23 8 1 52 11 مانتيس نما علامت عدد float 32 بيتي double 64 بيتي جدول تخصيص حافظه براي متغيير هاي مميز شناور نوع متغير تعداد بيت براي ذخيره‌سازيِ مانتيس نما علامت عدد float 32 بيتي 23 8 1 double 64 بيتي 52 11

7 – تعريف متغير مميز شناور 7 – تعريف متغير مميز شناور تعريف متغير مميز شناور مانند تعريف متغير صحيح است. با اين تفاوت که از کلمۀ کليدي float يا double براي مشخص نمودن نوع متغير استفاده مي‌کنيم. مثال: float x; double x,y=0; تفاوت نوع float با نوع double در اين است که نوع double دو برابر float از حافظه استفاده مي‌کند. پس نوع double دقتي بسيار بيشتر از float دارد. به همين دليل محاسبات double وقت‌گيرتر از محاسبات float است.

8- شکل علمي مقادير مميز شناور اعداد مميز شناور به دو صورت در ورودي و خروجي نشان داده مي‌شوند: به شکل «ساده» و به شکل «علمي». 2- علمي 1.234567×104 1- ساده 12345.67 مشخص است که شکل علمي براي نشان دادن اعداد خيلي کوچک و همچنين اعداد خيلي بزرگ، کارآيي بيشتري دارد.

9 – نوع بولين bool نوع bool يک نوع صحيح است که متغيرهاي اين نوع فقط مي‌توانند مقدار true يا false داشته باشند. true به معني درست و false به معني نادرست است. اما اين مقادير در اصل به صورت 1 و 0 درون رايانه ذخيره مي‌شوند: 1 براي true و 0 براي false.

10- نوع کاراکتري char يک کاراکتر يک حرف، رقم يا نشانه است که يک شمارۀ منحصر به فرد دارد. به عبارت عاميانه، هر کليدي که روي صفحه‌کليد خود مي‌بينيد يک کاراکتر را نشان مي‌دهد. مثلا هر يک از حروف 'A' تا 'Z' و 'a' تا 'z' و هر يک از اعداد '0' تا '9' و يا نشانه‌هاي '~' تا '+' روي صفحه‌کليد را يک کاراکتر مي‌نامند.

براي تعريف متغيري از نوع کاراکتر از کلمه کليدي char استفاده مي‌کنيم براي تعريف متغيري از نوع کاراکتر از کلمه کليدي char استفاده مي‌کنيم. يک کاراکتر بايد درون دو علامت آپستروف (') محصور شده باشد. پس 'A' يک کاراکتر است؛ همچنين'8' يک کاراکتر است اما 8 يک کاراکتر نيست بلکه يک عدد صحيح است . مثال: char c ='A';

11 – نوع شمارشي enum enum typename{enumerator-list} يک نوع شمارشي يک نوع صحيح است که توسط کاربر مشخص مي‌شود. نحو تعريف يک نوع شمارشي به شکل زير است: enum typename{enumerator-list} که enum کلمه‌اي کليدي است، typename نام نوع جديد است که کاربر مشخص مي‌کند و enumerator-list مجموعه مقاديري است که اين نوع جديد مي‌تواند داشته باشد.

به عنوان مثال به تعريف زير دقت کنيد: enum Day{SAT,SUN,MON,TUE,WED,THU,FRI} حالا Day يک نوع جديد است و متغيرهايي که از اين نوع تعريف مي‌شوند مي‌توانند يکي از مقادير SAT و SUN و MON و TUE و WED و THU و FRI را داشته باشند: Day day1,day2; day1 = MON; day2 = THU; وقتي نوع جديد Day و محدودۀ مقاديرش را تعيين کرديم، مي‌توانيم متغيرهايي از اين نوع جديد بسازيم. در کد بالا متغيرهاي day1 و day2 از نوع Day تعريف شده‌اند. آنگاه day1 با مقدار MON و day2 با مقدار THU مقداردهي شده است.

enum Answer{NO=0,FALSE=0,YES=1,TRUE=1,OK=1} مقادير SAT و SUN و ... هر چند که به همين شکل به کار مي‌روند اما در رايانه به شکل اعداد صحيح 0 و 1 و 2 و ... ذخيره مي‌شوند. به همين دليل است که به هر يک از مقادير SAT و SUN و ... يک شمارشگر مي‌گويند. مي‌توان مقادير صحيح دلخواهي را به شمارشگرها نسبت داد: enum Day{SAT=1,SUN=2,MON=4,TUE=8,WED=16,THU=32,FRI=64} اگر فقط بعضي از شمارشگرها مقداردهي شوند، آنگاه ساير شمارشگرها که مقداردهي نشده‌اند مقادير متوالي بعدي را خواهند گرفت: enum Day{SAT=1,SUN,MON,TUE,WED,THU,FRI} دستور بالا مقادير 1 تا 7 را به ترتيب به روزهاي هفته تخصيص خواهد داد. همچنين دو يا چند شمارشگر در يک فهرست مي‌توانند مقادير يکساني داشته باشند: enum Answer{NO=0,FALSE=0,YES=1,TRUE=1,OK=1}

نام شمارشگر بايد معتبر باشد: يعني: 1- کلمۀ کليدي نباشد. نحوۀ انتخاب نام‌شمارشگرها آزاد است اما بيشتر برنامه‌نويسان از توافق زير در برنامه‌هايشان استفاده مي‌کنند: 1 – براي نام ثابت‌ها از حروف بزرگ استفاده کنيد 2 – اولين حرف از نام نوع شمارشي را با حرف بزرگ بنويسيد. 3 – در هر جاي ديگر از حروف کوچک استفاده کنيد. نام شمارشگر بايد معتبر باشد: يعني: 1- کلمۀ کليدي نباشد. 2- با عدد شروع نشود. 3- نشانه‌هاي رياضي نيز نداشته باشد.

enum Score{A,B,C,D} float B; char c; آخر اين که نام شمارشگرها نبايد به عنوان نام متغيرهاي ديگر در جاهاي ديگر برنامه استفاده شود. مثلا: enum Score{A,B,C,D} float B; char c; در تعريف‌هاي بالا B و C را نبايد به عنوان نام متغيرهاي ديگر به کار برد زيرا اين نام‌ها در نوع شمارشي Score به کار رفته است . شمارشگرهاي هم‌نام نبايد در محدوده‌هاي مشترک استفاده شوند. براي مثال تعريف‌هاي زير را در نظر بگيريد: enum Score{A,B,C,D} enum Group{AB,B,BC} دو تعريف بالا غيرمجاز است زيرا شمارشگر B در هر دو تعريف Score و Group آمده است.

انواع شمارشي براي توليد کد «خود مستند» به کار مي‌روند، يعني کدي که به راحتي درک شود و نياز به توضيحات اضافي نداشته باشد. مثلا تعاريف زير خودمستند هستند زيرا به راحتي نام و نوع کاربرد و محدودۀ مقاديرشان درک مي‌شود: enum Color{RED,GREEN,BLUE,BLACK,ORANGE} enum Time{SECOND,MINUTE,HOUR} enum Date{DAY,MONTH,YEAR} enum Language{C,DELPHI,JAVA,PERL} enum Gender{MALE,FEMALE}

12 – تبديل نوع، گسترش نوع در محاسباتي که چند نوع متغير وجود دارد، جواب هميشه به شکل متغيري است که دقت بالاتري دارد. يعني اگر يک عدد صحيح را با يک عدد مميز شناور جمع ببنديم، پاسخ به شکل مميز شناور است به اين عمل گسترش نوع مي‌گويند. براي اين که مقدار يک متغير از نوع مميز شناور را به نوع صحيح تبديل کنيم از عبارت int() استفاده مي‌کنيم به اين عمل تبديل نوع گفته مي شود

مثال‌هاي زير تبديل نوع و گسترش نوع را نشان مي‌دهند. مثال گسترش نوع برنامۀ زير يک عدد صحيح را با يک عدد مميز شناور جمع مي‌کند: int main() { // adds an int value with a double value: int n = 22; double p = 3.1415; p += n; cout << "p = " << p << ", n = " << n << endl; return 0; } مثال تبديل نوع: اين برنامه، يک نوع double را به نوع int تبديل مي‌کند: int main() { // casts a double value as an int: double v=1234.987; int n; n = int(v); cout << "v = " << v << ", n = " << n << endl; return 0; }

13 – برخي از خطاهاي برنامه‌نويسي 13 – برخي از خطاهاي برنامه‌نويسي ‌«خطاي زمان کامپايل» اين قبيل خطاها که اغلب خطاهاي نحوي هستند ، توسط کامپايلر کشف مي‌شوند و به راحتي مي‌توان آن‌ها را رفع نمود. «خطاي زمان اجرا» کشف اينگونه خطاها به راحتي ممکن نيست و کامپايلر نيز چيزي راجع به آن نمي‌داند. برخي از خطاهاي زمان اجرا سبب مي‌شوند که برنامه به طور کامل متوقف شود و از کار بيفتد.

14- سرريزي عددي يک متغير هر قدر هم که گنجايش داشته باشد، بالاخره مقداري هست که از گنجايش آن متغير بيشتر باشد. اگر سعي کنيم در يک متغير مقداري قرار دهيم که از گنجايش آن متغير فراتر باشد، متغير «سرريز» مي‌شود،در چنين حالتي مي‌گوييم که خطاي سرريزي رخ داده است.

مثال 12 – 2 سرريزي عدد صحيح اين‌ برنامه‌ به طور مكرر n را در 1000 ضرب‌ مي‌كند تا سرانجام سرريز شود: int main() { //prints n until it overflows: int n =1000; cout << "n = " << n << endl; n *= 1000; // multiplies n by 1000 cout << " n = " << n << endl; return 0; } وقتي يک عدد صحيح سرريز شود، عدد سرريز شده به يک مقدار منفي «گردانيده» مي‌شود اما وقتي يک عدد مميز شناور سرريز شود، نماد inf به معناي بي‌نهايت را به دست مي‌دهد.

15 – خطاي گرد کردن خطاي‌ گرد كردن‌ نوع‌ ديگري‌ از خطاست‌ كه‌ اغلب‌ وقتي‌ رايانه‌ها روي‌ اعداد حقيقي‌ محاسبه‌ مي‌كنند، رخ‌ مي‌دهد. براي‌ مثال‌ عدد 1/3ممكن‌ است‌ به‌ صورت‌ 0.333333 ذخيره‌ شود كه‌ دقيقا معادل‌ 1/3 نيست‌. اين خطا از آن‌جا ناشي مي‌شود که اعدادي مثل 1/3 مقدار دقيق ندارند و رايانه نمي‌تواند اين مقدار را پيدا کند، پس نزديک‌ترين عدد قابل محاسبه را به جاي چنين اعدادي منظور مي‌کند. «هيچ‌گاه از متغير مميز شناور براي مقايسه برابري استفاده نکنيد» زيرا در متغيرهاي مميز شناور خطاي گرد کردن سبب مي‌شود که پاسخ با آن چه مورد نظر شماست متفاوت باشد.

16 – حوزۀ متغيرها اصطلاح «بلوک» در C++ واژه مناسبي است که مي‌توان به وسيلۀ آن حوزۀ متغير را مشخص نمود. يک بلوک برنامه، قسمتي از برنامه است که درون يک جفت علامت کروشه { } محدود شده است. انتخاب نام‌هاي نامفهوم يا ناقص سبب کاهش خوانايي برنامه و افزايش خطاهاي برنامه‌نويسي مي‌شود. استفاده از متغيرها در حوزۀ نامناسب هم سبب بروز خطاهايي مي‌شود. «حوزه متغير» محدوده‌اي است که يک متغير خاص اجازه دارد در آن محدوده به کار رود يا فراخواني شود.

حوزۀ يک متغير از محل اعلان آن شروع مي‌شود و تا پايان همان بلوک ادامه مي‌يابد. خارج از آن بلوک نمي‌توان به متغير دسترسي داشت. همچنين قبل از اين که متغير اعلان شود نمي‌توان آن را استفاده نمود. مي‌توانيم در يک برنامه، چند متغير متفاوت با يک نام داشته باشيم به شرطي که در حوزه‌هاي مشترک نباشند.

پايان جلسه دوم

جلسه سوم «انتخاب»

آنچه در اين جلسه مي خوانيد: 1- دستور‌ if 2- دستور if..else 3- عملگرهاي مقايسه‌اي 4- بلوك‌هاي دستورالعمل 5- شرط‌هاي مركب 6- ارزيابي ميانبري ›››

7- عبارات منطقي 8 - دستور‌هاي انتخاب تودرتو 9- ساختار else if 10- دستورالعمل switch 11- عملگر عبارت شرطي 12- كلمات كليدي

شناخت انواع دستورالعمل‌هاي انتخاب و شيوۀ به‌کارگيري هر يک هدف‌هاي رفتاري: انتظار مي‌رود پس از پايان اين جلسه بتوانيد: - نحو دستور if را شناخته و آن را در برنامه‌ها به کار ببريد. - نحو دستور if..else را شناخته و آن را در برنامه‌ها به کار ببريد. - از ساختار else..if در تصميم‌گيري‌هاي پيچيده استفاده کنيد. - نحو دستور switch را شناخته و خطاي «تلۀ سقوط» را تشخيص دهيد. - بلوک دستورالعمل را تعريف کنيد. - عملگرهاي مقايسه‌اي و عملگر عبارت شرطي را در دستورات شرطي به کار ببريد. - از شرط‌هاي مرکب استفاده کرده و ارزيابي ميانبري را شرح دهيد. - «کلمۀ کليدي» را تعريف کنيد. هدف کلي: شناخت انواع دستورالعمل‌هاي انتخاب و شيوۀ به‌کارگيري هر يک >>>

مقدمه همۀ برنامه‌هايي که در دو جلسه اول بيان شد، به شکل ترتيبي ‌اجرا مي‌شوند، يعني دستورات برنامه به ترتيب از بالا به پايين و هر کدام دقيقا يک بار اجرا مي‌شوند. در اين‌ جلسه‌ نشان داده مي‌شود چگونه از دستورالعمل‌هاي انتخاب1 جهت انعطاف‌پذيري بيشتر برنامه استفاده کنيم. همچنين در اين جلسه انواع صحيح كه در C++ وجود دارد بيشتر بررسي مي‌گردد.

دستور if If (condition) statement; Condition که شرط ناميده مي‌شود يك عبارت صحيح است (عبارتي که با يک مقدار صحيح برآورد مي‌شود) و statement‌ مي‌تواند هر فرمان قابل اجرا باشد. Statement وقتي اجرا خواهد شد كه condition‌ مقدار غير صفر داشته باشد. دقت كنيد كه شرط بايد درون پرانتز قرار داده شود.

2- دستور if..else دستور if..else موجب مي‌شود بسته به اين که شرط درست باشد يا خير، يكي از دو دستورالعمل فرعي اجرا گردد. نحو اين دستور به شکل زير است: if (condition) statement1; else statement2; condition همان شرط مساله است که يك عبارت صحيح مي‌باشد و statement1 و statement2 فرمان‌هاي قابل اجرا هستند. اگر مقدار شرط، غير صفر باشد، statement1 اجرا خواهد شد وگرنه statement2 اجرا مي‌شود.

مثال يک آزمون قابليت تقسيم مثال يک آزمون قابليت تقسيم int main() { int n, d; cout << " Enter two positive integers: "; cin >> n >> d; if (n%d) cout << n << " is not divisible by " << d << endl; else cout << n << " is divisible by " << d << endl; }

4- عملگرهاي مقايسه‌اي در C++ شش عملگر مقايسه‌اي وجود دارد: < و > و <= و >= و == و != . هر يک از اين شش عملگر به شکل زير به کار مي‌روند: x < y // است y ‌کوچکتر از x x > y // است y بزرگتر از x x <= y // است y ‌کوچکتر يا مساوي x x >= y // است y بزرگ‌تر يا مساوي x x == y // است y مساوي با x x != y // نيست y مساوي با x

اين‌ها مي‌توانند براي مقايسۀ مقدار عبارات با هر نوع ترتيبي استفاده شوند. عبارت حاصل به عنوان يك‌ شرط تفسير مي‌شود. مقدار اين شرط صفر است اگر شرط نادرست باشد و غير صفر است اگر شرط درست باشد. براي نمونه، عبارت 7*8<6*5 برابر با صفر ارزيابي مي‌شود، به اين معني كه اين شرط نادرست است.

2- متغير عدد صحيح C++ شش نوع متغير عدد صحيح دارد تفاوت اين شش نوع مربوط به ميزان حافظۀ مورد استفاده و محدودۀ مقاديري است که هر کدام مي‌توانند داشته باشند. اين ميزان حافظۀ مورد استفاده و محدودۀ مقادير، بستگي زيادي به سخت‌افزار و همچنين سيستم عامل دارد. يعني ممکن است روي يک رايانه، نوع int دو بايت از حافظه را اشغال کند در حالي که روي رايانه‌اي از نوع ديگر نوع int به چهار بايت حافظه نياز داشته باشد.

دقت کنيد كه در ++C عملگر جايگزيني با عملگر برابري فرق دارد مثلا دستور x = 33; مقدار 33 را در x قرار مي‌دهد ولي دستور x == 33; بررسي مي‌کند که آيا مقدار x با 33 برابر است يا خير. درک اين تفاوت اهميت زيادي دارد.

4- بلوك‌هاي دستورالعمل يك بلوك دستورالعمل زنجيره‌اي از دستورالعمل‌هاست كه درون براكت {} محصور شده، مانند : { int temp=x; x = y; y = temp; } در برنامه‌هاي ++C يک بلوک دستورالعمل مانند يک دستورالعمل تکي است.

int main() { int x, y; cout << "Enter two integers: "; cin >> x >> y; if (x > y) { int temp = x; x = y; y = temp; } //swap x and y cout << x << " <= " << y << endl; } مثال : يك بلوك دستورالعمل درون يك دستور if اين برنامه دو عدد صحيح را گرفته و به ترتيب بزرگ‌تري، آن‌ها را چاپ مي‌كند:

{ int n=44; int main() cout << "n = " << n << endl; cout << "Enter an integer: "; cin >> n; cout << "n = " << n << endl; } { cout << " n = " << n << endl; }

5 – شرط‌هاي مركب شرط‌هايي مانند n%d و x>=y مي‌توانند به صورت يك شرط مركب با هم تركيب شوند. اين كار با استفاده ازعملگرهاي منطقي && (and) و || (or) و ! (not) صورت مي‌پذيرد. اين عملگرها به شکل زير تعريف مي‌شوند: p && q درست است اگر و تنها اگر هم p و هم q هر دو درست باشند p || q نادرست است اگر و تنها اگر هم p و هم q هر دو نادرست باشند !pدرست است اگر و تنها اگر p نادرست باشد براي مثال(n%d || x>=y) نادرست است اگر و تنها اگر n%d برابر صفر و x كوچك‌تر از y باشد.

P&&q q p T F P||q q p T F P F T !P سه عملگر منطقي && (and) و || (or) و ! (not) معمولا با استفاده از جداول درستي به گونۀ زير بيان‌ مي‌شوند: P&&q q p T F P||q q p T F !P P F T طبق جدول‌هاي فوق اگر p درست و q نادرست باشد، عبارت p&&q نادرست و عبارت p||q درست است.

6- ارزيابي ميانبري عملگرهاي && و || به دو عملوند نياز دارندتا مقايسه را روي آن دو انجام دهند. جداول درستي نشان مي‌دهد که p&&q نادرست است اگر p نادرست باشد. در اين حالت ديگر نيازي نيست که q بررسي شود. همچنين p||q درست است اگر p درست باشد و در اين حالت هم نيازي نيست که q بررسي شود. در هر دو حالت گفته شده، با ارزيابي عملوند اول به سرعت نتيجه معلوم مي‌شود. اين كار ارزيابي ميانبري ناميده مي‌شود. شرط‌هاي مركب كه از && و || استفاده مي‌كنند عملوند دوم را بررسي نمي‌كنند مگر اين كه لازم باشد.

7- عبارات منطقي يك عبارت منطقي شرطي است كه يا درست است يا نادرست. قبلا ديديم که عبارات منطقي با مقادير صحيح ارزيابي مي‌شوند. مقدار صفر به معناي نادرست و هر مقدار غير صفر به معناي درست است. به عبارات منطقي «عبارات بولي» هم مي‌گويند.

چون همۀ مقادير صحيح ناصفر به معناي درست تفسير مي‌شوند، عبارات منطقي اغلب تغيير قيافه مي‌دهند. براي مثال دستور if (n) cout << "n is not zero"; وقتي n غير صفر است عبارت ‌n is not zero را چاپ مي‌كند زيرا عبارت منطقي (n) وقتي مقدار n غير صفر است به عنوان درست تفسير مي‌گردد.

کد زير را نگاه کنيد: if (n%d) cout << "n is not a multiple of d"; دستور خروجي فقط وقتي كه n%d ناصفر است اجرا مي‌گردد و n%d وقتي ناصفر است که n بر d بخش‌پذير نباشد. گاهي ممکن است فراموش کنيم که عبارات منطقي مقادير صحيح دارند و اين فراموشي باعث ايجاد نتايج غير منتظره و نامتعارف شود.

cout << "Enter three integers: "; يك خطاي منطقي ديگر، اين برنامه خطادار است: int main() { int n1, n2, n3; cout << "Enter three integers: "; cin >> n1 >> n2 >> n3; if (n1 >= n2 >= n3) cout << "max = " << n1; } منشأ خطا در برنامۀ بالا اين اصل است كه عبارات منطقي مقدارهاي عددي دارند.

8- دستور‌هاي انتخاب تودرتو دستورهاي انتخاب مي‌توانند مانند دستورالعمل‌هاي مركب به كار روند. به اين صورت که يك دستور انتخاب مي‌تواند درون دستور انتخاب ديگر استفاده شود. به اين روش، جملات تودرتو مي‌گويند.

مثال 12-3 دستور‌هاي انتخاب تودرتو اين برنامه همان اثر مثال 10-3 را دارد: int main() { int n, d; cout << "Enter two positive integers: "; cin >> n >> d; if (d != 0) if (n%d = = 0) cout << d << " divides " << n << endl; else cout << d << " does not divide " << n << endl; } در برنامۀ بالا، دستور if..else دوم درون دستور if..else اول قرار گرفته است‌. وقتي دستور if..else به شکل تو در تو به کار مي‌رود، كامپايلر از قانون زير جهت تجزيه اين دستورالعمل مركب استفاده مي‌كند: « هر else با آخرين if تنها جفت مي‌شود.»

9- ساختار else if دستور if..else تودرتو، اغلب براي بررسي مجموعه‌اي از حالت‌هاي متناوب يا موازي به كار مي‌رود. در اين حالات فقط عبارت else شامل دستور if بعدي خواهد بود. اين قبيل کدها را معمولا با ساختار else ifمي‌سازند.

استفاده از ساختار else if براي مشخص کردن محدودۀ نمره برنامۀ زير يك نمرۀ امتحان را به درجۀ حرفي معادل تبديل مي‌كند: int main() { int score; cout << "Enter your test score: "; cin >> score; if (score > 100) cout << "Error: that score is out of range."; else if (score >= 90) cout << "Your grade is an A." << endl; else if (score >= 80) cout << "Your grade is a B." << endl; else if (score >= 70) cout << "Your grade is a C." << endl; else if (score >= 60) cout << "Your grade is a D." << endl; else if (score >= 0) cout << "Your grade is an F." << endl; else cout << "Error: that score is out of range."; }

10- دستورالعمل switch دستور switch مي‌تواند به جاي ساختار else if براي بررسي مجموعه‌اي از حالت‌هاي متناوب و موازي به كار رود. نحو دستور switch به شکل زير است: switch (expression) { case constant1: statementlist1; case constant2: statementlist2; case constant3: statementlist3; : case constantN: statementlistN; default: statementlist0; }

اين دستور ابتدا expression را برآورد مي‌كند و سپس ميان ثابت‌هاي case به دنبال مقدار آن مي‌گردد. اگر مقدار مربوطه از ميان ثابت‌هاي فهرست‌شده يافت شد، دستور statementlist مقابل آن case اجرا مي‌شود. اگر مقدار مورد نظر ميان caseها يافت نشد و عبارت default وجود داشت، دستور statementlist مقابل آن اجرا مي‌شود. عبارتdefault يک عبارت اختياري است. يعني مي‌توانيم در دستور switch آن را قيد نکنيم. expression بايد به شکل يك نوع صحيح ارزيابي شود و constantها بايد ثابت‌هاي صحيح باشند.

لازم است در انتهاي هر case دستور‌ break قرار بگيرد لازم است در انتهاي هر case دستور‌ break قرار بگيرد. بدون اين دستور، اجراي برنامه پس از اين كه case مربوطه را اجرا کرد از دستور switch خارج نمي‌شود، بلکه همۀ caseهاي زيرين را هم خط به خط مي‌پيمايد و دستورات مقابل آن‌ها را اجرا مي‌کند. به اين اتفاق، تلۀ سقوط مي‌گويند. case constant1: statementlist1;break;

11- عملگر عبارت شرطي عملگر عبارت شرطي يکي از امکاناتي است که جهت اختصار در کدنويسي تدارک ديده شده است. اين عملگر را مي‌توانيم به جاي دستور if..else به کار ببريم. اين عملگر از نشانه‌هاي ? و : به شکل زير استفاده مي‌كند: condition ? expression1 : expression2; در اين عملگر ابتدا شرط condition بررسي مي‌شود. اگر اين شرط درست بود، حاصل کل عبارت برابر با expression1 مي‌شود و اگر شرط نادرست بود، حاصل کل عبارت برابر با expression2 مي‌شود.

مثلا در دستور انتساب زير: min = ( x<y ? x : y ); اگر x<y باشد مقدار x را درون min قرار مي‌دهد و اگر x<y نباشد مقدار y را درون min قرار مي‌دهد. يعني به همين سادگي و اختصار، مقدار کمينۀ x و y درون متغير min قرار مي‌گيرد.

12- كلمات كليدي‌ اکنون با کلماتي مثل if و case و float آشنا شديم. دانستيم که اين کلمات براي C++ معاني خاصي دارند. از اين کلمات نمي‌توان به عنوان نام يک متغير يا هر منظور ديگري استفاده کرد و فقط بايد براي انجام همان کار خاص استفاده شوند. مثلا کلمۀ float فقط بايد براي معرفي يک نوع اعشاري به کار رود. يك‌ كلمۀ كليدي در يك زبان برنامه‌نويسي كلمه‌اي است كه از قبل تعريف شده و براي هدف مشخصي منظور شده است.

C++ استاندارد اكنون شامل 74 كلمۀ كليدي است: asm and_eq and Bitor bitand auto case break bool class char catch const_cast const compl delete default continue else dynamic_cast dodouble export explicit enum float dfalse extern goto friend for C++ استاندارد اكنون شامل 74 كلمۀ كليدي است:

int inline if namespace mutable long not_eq not new or_eq or operator public eprotected privat return reinterpret_cast register sizeof signed short struct static_cast static this template swich try TRUE throw

typename typoid typedef unsigned union using volatile void virtual xor while wchar_t xor_eq

دو نوع كلمۀ كليدي وجود دارد: 1- كلمه‌هاي رزرو شده 2- شناسه‌هاي استاندارد. يك كلمۀ رزرو شده كلمه‌اي است که يک دستور خاص از آن زبان را نشان مي‌دهد. كلمۀ كليدي if و else كلمات رزرو شده هستند. يك شناسۀ استاندارد كلمه‌اي است كه يك نوع دادۀ استاندارد از زبان را مشخص مي‌كند. كلمات كليدي bool و int شناسه‌هاي استاندارد هستند

پايان جلسه سوم

جلسه چهارم «تكرار»

آنچه در اين جلسه مي خوانيد: 1- دستور while 2- خاتمه دادن به يك حلقه 3- دستور do..while 4- دستور for 5- دستور break 6- دستور continue 7- دستور goto 8- توليد اعداد شبه تصادفي

شناخت انواع ساختارهاي تکرار و نحو آن‌ها و تبديل آن‌ها به يکديگر. هدف‌هاي رفتاري: انتظار مي‌رود پس از مطالعۀ اين جلسه بتوانيد: - نحو دستورwhile را شناخته و از آن براي ايجاد حلقه استفاده کنيد. - نحو دستور do..while را شناخته و تفاوت آن با دستور while را بيان کنيد. - نحو دستور for را شناخته و با استفاده از آن حلقه‌هاي گوناگون بسازيد. - حلقه‌هاي فوق را به يکديگر تبديل کنيد. - علت استفاده از «دستورات پرش» را ذکر کرده و تفاوت سه دستور break و continue و goto را بيان کنيد. - اهميت اعداد تصادفي را بيان کرده و نحوۀ توليد «اعداد شبه تصادفي» را بدانيد. هدف کلي: شناخت انواع ساختارهاي تکرار و نحو آن‌ها و تبديل آن‌ها به يکديگر.

مقدمه تكرار، اجراي پي در پي يك دستور يا بلوكي از دستورالعمل‌ها در يك برنامه است. با استفاده از تکرار مي‌توانيم کنترل برنامه را مجبور کنيم تا به خطوط قبلي برگردد و آن‌ها را دوباره اجرا نمايد. C++ داراي سه دستور تكرار است: دستور while، دستور do_while و دستور for. دستور‌هاي تکرار به علت طبيعت چرخه‌مانندشان‌، حلقه‌ نيز ناميده مي‌شوند.

1- دستور while نحو دستور while به شکل زير است: while (condition) statement; به جاي condition، يك شرط قرار مي‌گيرد و به جاي statement دستوري که بايد تکرار شود قرار مي‌گيرد. اگر مقدار شرط، صفر(يعني نادرست) باشد، statement ناديده گرفته مي‌شود و برنامه به اولين دستور بعد از while پرش مي‌كند. اگر مقدار شرط ناصفر(يعني‌ درست) باشد، statement اجرا ‌شده و دوباره مقدار شرط بررسي مي‌شود. اين تکرار آن قدر ادامه مي‌يابد تا اين که مقدار شرط صفر شود.

مثال 1-4 محاسبۀ حاصل جمع اعداد صحيح متوالي با حلقۀ while اين برنامه مقدار 1 + 2 + 3 + … + n را براي عدد ورودي n محاسبه مي‌كند: int main() { int n, i=1; cout << "Enter a positive integer: "; cin >> n; long sum=0; while (i <= n) sum += i++; cout << "The sum of the first " << n << " integers is " << sum; }

2- خاتمه دادن به يك حلقه int main() { int n, i=1; cout << "Enter a positive integer: "; cin >> n; long sum=0; while (true) { if (i > n) break; sum += i++; } cout << "The sum of the first " << n << " integers is " << sum; قبلا‌ ديديم كه چگونه دستور break براي كنترل دستورالعمل switch استفاده مي‌شود (به مثال 17-4 نگاه كنيد). از دستور break براي پايان دادن به حلقه‌ها نيز مي‌توان استفاده کرد. يكي از‌ مزيت‌هاي دستور break اين است كه فورا حلقه را خاتمه مي‌دهد بدون اين که مابقي دستورهاي درون حلقه اجرا شوند.

* مثال‌ 4-4 اعداد فيبوناچي اعداد فيبوناچي F0, F1, F2, F3, … به شکل بازگشتي توسط معادله‌هاي زير تعريف مي‌شوند: F0 = 0 , F1 = 1 , Fn = Fn-1 + Fn-2 مثلا براي n=2 داريم: F2 = F2-1 + F2-2 = F1 + F0 = 0 + 1 = 1 يا براي n=3 داريم: F3 = F3-1 + F3-2 = F2 + F1 = 1 + 1 = 2 و براي n=4 داريم: F4 = F4-1 + F4-2 = F3 + F2 = 2 + 1 = 3

cout << "Enter a positive integer: "; cin >> bound; برنامۀ زير، همۀ اعداد فيبوناچي را تا يك محدودۀ مشخص که از ورودي دريافت مي‌شود، محاسبه و چاپ مي‌كند: int main() { long bound; cout << "Enter a positive integer: "; cin >> bound; cout << "Fibonacci numbers < " << bound << ":\n0, 1"; long f0=0, f1=1; while (true) { long f2 = f0 + f1; if (f2 > bound) break; cout << ", " << f2; f0 = f1; f1 = f2;} } Enter a positive integer: 1000 Fibonacci numbers < 1000: 0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377, 610, 987

مثال5-4 استفاده از تابع exit(0) int main() { long bound; cout << "Enter a positive integer: "; cin >> bound; cout << "Fibonacci numbers < " << bound << ":\n0, 1"; long f0=0, f1=1; while (true) { long f2 = f0 + f1; if (f2 > bound) exit(0); cout << ", " << f2; f0 = f1; f1 = f2; } } برنامه‌نويسان ترجيح مي‌دهند از break براي خاتمه دادن به حلقه‌هاي نامتناهي استفاده کنند زيرا قابليت انعطاف بيشتري دارد.

متوقف کردن يك حلقۀ نامتناهي : با فشردن کليدهاي Ctrl+C سيستم عامل يک برنامه را به اجبار خاتمه مي‌دهد. كليد Ctrl را پايين نگه داشته و كليد C روي صفحه‌كليد خود را فشار دهيد تا برنامۀ فعلي خاتمه پيدا کند.

3- دستور do..while do statement while (condition); به جاي condition يك شرط قرار مي‌گيرد و به جاي statement‌ دستور يا بلوکي قرار مي‌گيرد که قرار است تکرار شود. اين دستور ابتدا statement‌ را اجرا مي‌كند و سپس شرط condition را بررسي مي‌كند. اگر شرط درست بود حلقه دوباره تکرار مي‌شود وگرنه حلقه پايان مي‌يابد.

دستور‌ do. while مانند دستور while است يعني هر متغير كنترلي به جاي اين كه قبل از شروع حلقه تنظيم شود، مي‌تواند درون آن تنظيم گردد. نتيجۀ ديگر اين است كه حلقۀ do..while هميشه بدون توجه به مقدار شرط كنترل، لااقل يك بار اجرا مي‌شود اما حلقۀ while مي‌تواند اصلا اجرا نشود.

مثال 7-4 محاسبۀ حاصل جمع اعداد صحيح ‌متوالي با حلقۀ do..while‌ اين برنامه همان تأثير مثال 1-5 را دارد: int main() { int n, i=0; cout << "Enter a positive integer: "; cin >> n; long sum=0; do sum += i++; while (i <= n); cout << "The sum of the first " << n << " integers is " << sum; }

0! = 1 , n! = n(n-1)! 1! = 1((1-1)!) = 1(0!) = 1(1) = 1 * مثال 8-4 اعداد فاكتوريال اعداد فاكتوريال 0! و 1! و 2! و 3! و … با استفاده از رابطه‌هاي بازگشتي زير تعريف مي‌شوند: 0! = 1 , n! = n(n-1)! براي مثال، به ازاي n = 1 در معادلۀ دوم داريم: 1! = 1((1-1)!) = 1(0!) = 1(1) = 1 همچنين براي n = 2 داريم: 2! = 2((2-1)!) = 2(1!) = 2(1) = 2 و به ازاي n = 3 داريم: 3! = 3((3-1)!) = 3(2!) = 3(2) = 6

برنامۀ زير همۀ‌ اعداد فاكتوريال را که از عدد داده شده کوچک‌ترند، چاپ مي‌کند: int main() { long bound; cout << "Enter a positive integer: "; cin >> bound; cout << "Factorial numbers < " << bound << ":\n1"; long f=1, i=1; do { cout << ", " << f; f *= ++i; } while (f < bound);

4 - دستور for نحو دستورالعمل for به صورت زير است: for (initialization; condition; update) statement; سه قسمت داخل پرانتز، حلقه را کنترل مي‌کنند. عبارت initialization براي اعلان يا مقداردهي اوليه به متغير کنترل حلقه استفاده مي‌شود.اين عبارت اولين عبارتي است که ارزيابي مي‌شود پيش از اين که نوبت به تکرارها برسد. عبارت updateبراي پيش‌بردن متغير کنترل حلقه به کار مي‌رود. اين عبارت پس از اجراي statement ارزيابي مي‌گردد. عبارت condition براي تعيين اين که آيا حلقه بايد تکرار شود يا خير به کار مي‌رود. يعني اين عبارت، شرط کنترل حلقه است. اگر اين شرط درست باشد دستور statement اجرا مي‌شود.

بنابراين زنجيرۀ وقايعي که تکرار را ايجاد مي‌کنند عبارتند از: 1 – ارزيابي عبارت initialization 2 – بررسي شرط condition . اگر نادرست باشد، حلقه خاتمه مي‌يابد. 3 – اجراي statement 4 – ارزيابي عبارت update 5 – تکرار گام‌هاي 2 تا 4 عبارت‌هاي initialization و condition و updateعبارت‌هاي اختياري هستند. يعني مي‌توانيم آن‌ها را در حلقه ذکر نکنيم.

cout << "Enter a positive integer: "; cin >> n; مثال 9-4 استفاده از حلقۀ for براي محاسبۀ مجموع اعداد صحيح متوالي اين برنامه همان تأثير مثال 1-5 را دارد: int main() { int n; cout << "Enter a positive integer: "; cin >> n; long sum=0; for (int i=1; i <= n; i++) sum += I; cout << "The sum of the first " << n << " integers is " << sum; } در C++ استاندارد وقتي يك متغير كنترل درون يك حلقۀ for اعلان مي‌شود (مانند i در مثال بالا) حوزۀ آن متغير به همان حلقۀ for محدود مي‌گردد. يعني آن متغير نمي‌تواند بيرون از آن حلقه استفاده شود. نتيجۀ ديگر اين است که مي‌توان از نام مشابهي در خارج از حلقۀ for براي يك متغير ديگر استفاده نمود.

{ for (int i=10; i > 0; i--) cout << " " << i; } برنامۀ زير‌ ده عدد صحيح مثبت را به ترتيب نزولي چاپ مي‌كند: int main() { for (int i=10; i > 0; i--) cout << " " << i; }

مثال 15-4 بيشتر از يك متغير كنترل در حلقۀ for int main() { for (int m=95, n=11, m%n > 0; m -= 3, n++) cout << m << "%" << n << " = " << m%n << endl; }

#include <iomanip> #include <iostream> int main() مثال 16-4 حلقه‌هاي for تودرتو برنامۀ زير يك جدول ضرب چاپ مي‌كند: #include <iomanip> #include <iostream> int main() { for (int x=1; x <= 10; x++) { for (int y=1; y <= 10; y++) cout << setw(4) << x*y; cout << endl; }

5- دستور break وقتي دستور break درون حلقه‌هاي تودرتو استفاده شود، فقط روي حلقه‌اي که مستقيما درون آن قرار گرفته تاثير مي‌گذارد. حلقه‌هاي بيروني بدون هيچ تغييري ادامه مي‌يابند. دستور break يک دستور آشناست. قبلا از آن براي خاتمه دادن به دستور switch و همچنين حلقه‌هاي while و do..while استفاده کرده‌ايم. از اين دستور براي خاتمه دادن به حلقۀ for نيز مي‌توانيم استفاده کنيم. دستور break در هر جايي درون حلقه مي‌تواند جا بگيرد و در همان جا حلقه را خاتمه دهد.

6- دستور continue دستور break بقيۀ دستورهاي درون بلوك حلقه را ناديده گرفته و به اولين ‌‌دستور بيرون حلقه پرش مي‌كند. دستور continue نيز شبيه همين است اما به جاي اين که حلقه را خاتمه دهد، اجرا را به تكرار بعدي حلقه منتقل مي‌كند. اين دستور، ادامۀ چرخۀ فعلي را لغو کرده و اجراي دور بعدي حلقه را آغاز مي‌کند.

{ cout << "\nLoop no: " << n << endl; مثال 19-4 استفاده از دستورهاي break و continue اين برنامۀ كوچك،‌ دستورهاي break و continue را شرح مي‌دهد: int main() { int n = 1; char c; for( ; ;n++ ) { cout << "\nLoop no: " << n << endl; cout << "Continue? <y|n> "; cin >> c; if (c = = 'y') continue; break; } cout << "\nTotal of loops: " << n;

7- دستور goto دستورgoto نوع ديگري از دستورهاي پرش است. مقصد اين پرش توسط يك برچسب معين مي‌شود. برچسب شناسه‌اي است كه جلوي آن علامت كولن( : ) مي‌آيد و جلوي يك دستور ديگر قرار مي‌گيرد. يک مزيت دستور goto اين است که با استفاده از آن مي‌توان از همۀ حلقه‌هاي تودرتو خارج شد و به مکان دلخواهي در برنامه پرش نمود.

for (int i=0; i<N; i++) { for (int j=0; j<N; j++) مثال 20-4 استفاده از دستور goto براي خارج شدن از حلقه‌هاي تودرتو int main() { const int N=5; for (int i=0; i<N; i++) { for (int j=0; j<N; j++) { for (int k=0; k<N; k++) if (i+j+k>N) goto esc; else cout << i+j+k << " "; cout << "* "; } esc: cout << "." << endl;

8- توليد اعداد شبه تصادفي يكي از كاربردهاي بسيار مهم رايانه‌ها، «شبيه‌سازي»‌ سيستم‌هاي دنياي واقعي است. تحقيقات و توسعه‌هاي بسيار پيشرفته به اين راهکار خيلي وابسته است. به وسيلۀ شبيه‌سازي مي‌توانيم رفتار سيستم‌هاي مختلف را مطالعه کنيم بدون اين که لازم باشد واقعا آن‌ها را پياده‌سازي نماييم. در شبيه‌سازي نياز است «اعداد تصادفي» توسط رايانه‌ها توليد شود تا نادانسته‌هاي دنياي واقعي مدل‌سازي شود.

رايانه‌ها «ثابت‌کار» هستند يعني با دادن داده‌هاي مشابه به رايانه‌هاي مشابه، هميشه خروجي يکسان توليد مي‌شود. با وجود اين مي‌توان اعدادي توليد کرد که به ظاهر تصادفي هستند؛ اعدادي که به طور يکنواخت در يک محدودۀ خاص گسترده‌اند و براي هيچ‌کدام الگوي مشخصي وجود ندارد. چنين اعدادي را «اعداد شبه‌تصادفي» مي‌ناميم.

#include<cstdlib>//defines the rand() and RAND_MAX مثال 22-4 توليد اعداد شبه تصادفي اين برنامه از تابع rand() براي توليد اعداد شبه‌تصادفي استفاده مي‌كند: #include<cstdlib>//defines the rand() and RAND_MAX #include <iostream> int main() { // prints pseudo-random numbers: for (int i = 0; i < 8; i++) cout << rand() << endl; cout << "RAND_MAX = " << RAND_MAX << endl; } هر بار که برنامۀ بالا اجرا شود، رايانه هشت عدد صحيح unsigned‌ توليد مي‌کند که به طور يکنواخت‌ در فاصلۀ 0 تا RAND_MAX گسترده شده‌اند.‌ RAND_MAX در اين رايانه برابر با 2,147,483,647 است.

هر عدد شبه‌تصادفي از روي عدد قبلي خود ساخته مي‌شود. اولين عدد شبه‌تصادفي از روي يك مقدار داخلي که «هسته» گفته مي‌شود ايجاد مي‌گردد. هر دفعه که برنامه اجرا شود، هسته با يک مقدار پيش‌فرض بارگذاري مي‌شود. براي حذف اين اثر نامطلوب که از تصادفي بودن اعداد مي‌کاهد، مي‌توانيم با استفاده از تابع ()srand خودمان مقدار هسته را انتخاب کنيم.

مثال 23-4 كارگذاري هسته به طور محاوره‌اي #include <cstdlib> // defines the rand() and srand() #include <iostream> int main() { // prints pseudo-random numbers: unsigned seed; cout << "Enter seed: "; cin >> seed; srand(seed); // initializes the seed for (int i = 0; i < 8; i++) cout << rand() << endl; } مثال 23-4 كارگذاري هسته به طور محاوره‌اي اين برنامه مانند برنامۀ مثال 22-4 است بجز اين كه مي‌توان هستۀ توليدکنندۀ اعداد تصادفي را به شکل محاوره‌اي وارد نمود:

پايان جلسه چهارم

جلسه پنجم « توابع»

آنچه در اين جلسه مي خوانيد: 1- توابع كتابخانه‌اي C++ استاندارد 2- توابع ساخت كاربر 3- برنامۀ آزمون 4- اعلان‌ها و تعاريف تابع 5- كامپايل جداگانۀ توابع 6- متغيرهاي محلي، توابع محلي ›››

››› 7- تابع void 8 - توابع بولي 9- توابع ورودي/خروجي (I/O) 10- ارسال به طريق ارجاع (آدرس) 11- ارسال‌ از طريق‌ ارجاع‌ ثابت‌ 12-توابع‌ بي‌واسطه ›››

13- چندشکلي توابع‌ 14- تابع‌ main() 15- آرگومان‌هاي‌ پيش‌فرض

››› هدف کلي: شناخت و معرفي توابع و مزاياي استفاده از تابع در برنامه‌ها هدف‌هاي رفتاري: انتظار مي‌رود پس از پايان اين جلسه بتوانيد: - اهميت توابع و مزيت استفاده از آن‌ها را بيان کنيد. - «اعلان» و «تعريف» تابع را بدانيد و خودتان توابعي را ايجاد کنيد. - «برنامۀ آزمون» را تعريف کرده و دليل استفاده از آن را بيان نماييد. - مفهوم «آرگومان» را بدانيد. - تفاوت ارسال به طريق «ارجاع» و ارسال به طريق «مقدار» و ارسال به طريق «ارجاع ثابت» را بيان کنيد و شکل استفاده از هر يک را بدانيد. هدف کلي: شناخت و معرفي توابع و مزاياي استفاده از تابع در برنامه‌ها ›››

- «تابع بي‌واسطه» را شناخته و نحوۀ معرفي آن را بدانيد. - چندشکلي توابع را تعريف کنيد و شيوۀ آن را بدانيد. - طريقۀ به‌کارگيري آرگومان‌هاي پيش‌فرض را بدانيد. - فرق بين تابع void با ساير توابع را بدانيد.

1-مقدمه برنامه‌هاي واقعي و تجاري بسيار بزرگ‌تر از برنامه‌هايي هستند که تاکنون بررسي کرديم. براي اين که برنامه‌هاي بزرگ قابل مديريت باشند، برنامه‌نويسان اين برنامه‌ها را به زيربرنامه‌هايي بخش‌بندي مي‌کنند. اين زيربرنامه‌ها «تابع» ناميده مي‌شوند. توابع را مي‌توان به طور جداگانه کامپايل و آزمايش نمود و در برنامه‌هاي مختلف دوباره از آن‌ها استفاده کرد.

2- توابع كتابخانه‌اي C++ استاندارد قبلا برخي از آن‌ها را استفاده كرده‌ايم‌: ثابت INT_MAX که در <climits> تعريف شده ، تابع ()sqrt که در <cmath> تعريف شده است و... .

تابع جذر sqrt() ريشۀ دوم يك عدد مثبت‌، جذر آن عدد است. تابع مانند يک برنامۀ کامل، داراي ‌روند ورودي - پردازش - خروجي است هرچند که پردازش، مرحله‌اي پنهان است. يعني نمي‌دانيم که تابع روي عدد 2 چه اعمالي انجام مي‌دهد که 41421/1 حاصل مي‌شود.

برنامۀ سادۀ زير، تابع از پيش تعريف شدۀ جذر را به کار مي‌گيرد: #include <cmath> // defines the sqrt() function #include <iostream> // defines the cout object using namespace std; int main() { //tests the sqrt() function: for (int x=0; x < 6; x++) cout << "\t" << x << "\t" << sqrt(x) << endl; } براي اجراي يك تابع مانند تابع sqrt() کافي است نام آن تابع به صورت يک متغير در دستورالعمل مورد نظر استفاده شود، مانند زير: y=sqrt(x);

اين کار «فراخواني تابع» يا «احضار تابع» گفته مي‌شود اين کار «فراخواني تابع» يا «احضار تابع» گفته مي‌شود. بنابراين وقتي كد sqrt(x) اجرا شود، تابع sqrt() فراخواني مي‌گردد. عبارت x درون پرانتز «آرگومان» يا «پارامتر واقعي» فراخواني ناميده مي‌شود. در چنين حالتي مي‌گوييم كه x توسط «مقدار» به تابع فرستاده مي‌شود. لذا وقتي x=3 است، با اجراي کد sqrt(x) تابع sqrt() فراخواني شده و مقدار 3 به آن فرستاده مي‌شود. تابع مذکور نيز حاصل 1.73205 را به عنوان پاسخ برمي‌گرداند…

Main() … اين فرايند در نمودار زير نشان داده شده. Sqrt() int 3 x 3 y 1.73205 1.73205 double متغيرهاي x و y در تابع main() تعريف شده‌اند. مقدار x که برابر با 3 است به تابع sqrt() فرستاده مي‌شود و اين تابع مقدار 1.73205 را به تابع main() برمي‌گرداند. جعبه‌اي كه تابع sqrt() را نشان مي‌دهد به رنگ تيره است، به اين معنا كه فرايند داخلي و نحوۀ کار آن قابل رويت نيست.

{ for (float x=0; x < 2; x += 0.2) مثال 2-5 آزمايش يك رابطۀ مثلثاتي اين برنامه هم از سرفايل <cmath> استفاده‌ مي‌كند. هدف اين است که صحت رابطۀ Sin2x=2SinxCosx به شکل تجربي بررسي شود. int main() { for (float x=0; x < 2; x += 0.2) cout << x << "\t\t" << sin(2*x) << "\t“ << 2*sin(x)*cos(x) << endl; }

خروجي برنامه: 0 0 0 0.2 0.389418 0.389418 0.4 0.717356 0.717356 0.6 0.932039 0.932039 0.8 0.999574 0.999574 1 0.909297 0.909297 1.2 0.675463 0.675463 1.4 0.334988 0.334988 1.6 -0.0583744 -0.0583744 1.8 -0.442521 -0.442521 برنامۀ مقدار x را در ستون اول، مقدار Sin2x را در ستون دوم و مقدار 2SinxCosx را در ستون سوم چاپ‌ مي‌كند. خروجي نشان مي‌دهد که براي هر مقدار آزمايشي x، مقدار Sin2x با مقدار 2SinxCosx برابر است.

بيشتر توابع معروف رياضي كه در ماشين‌حساب‌ها هم وجود دارد در سرفايل <cmath> تعريف شده است. بعضي از اين توابع در جدول زير نشان داده شده: مثال شرح تابع acos(0.2) مقدار 1.36944 را برمي‌گرداند کسينوس معکوسx (به راديان) acos(x) asin(0.2) مقدار 0.201358 را برمي‌گرداند سينوس معکوس x (به راديان) asin(x) atan(0.2) مقدار 0.197396 را برمي‌گرداند تانژانت معکوس x (به راديان) atan(x) ceil(3.141593) مقدار 4.0 را برمي‌گرداند مقدار سقف x (گرد شده) ceil(x) cos(2) مقدار -0.416147 را برمي‌گرداند کسينوس x (به راديان) cos(x) exp(2) مقدار 7.38906 را برمي‌گرداند تابع نمايي x (در پايه e) exp(x) fabs(-2) مقدار 2.0 را برمي‌گرداند قدر مطلق x fabs(x)

floor(3.141593) مقدار 3.0 را برمي‌گرداند مقدار کف x (گرد شده) floor(x) log(2) مقدار 0.693147 را برمي‌گرداند لگاريتم طبيعي x (در پايه e) log(x) log10(2) مقدار 0.30103 را برمي‌گرداند لگاريتم عمومي x (در پايه 10) log10(x) pow(2,3) مقدار 8.0 را برمي‌گرداند x به توان p pow(x,p) sin(2) مقدار 0.909297 را برمي‌گرداند سينوس x (به راديان) sin(x) sqrt(2) مقدار 1.41421 را برمي‌گرداند جذر x sqrt(x) tan(2) مقدار -2.18504 را برمي‌گرداند تانژانت x (به راديان) tan(x)

توجه داشته باشيد که هر تابع رياضي يک مقدار از نوع double را برمي‌گرداند. اگر يك نوع صحيح به تابع فرستاده شود، قبل از اين كه تابع آن را پردازش کند، مقدارش را به نوع double‌ ارتقا مي‌دهد.

#include <cstdlib> اين سرفايل‌ها از كتابخانۀ‌ C استاندارد گرفته شده‌اند. استفاده از آن‌ها شبيه استفاده از سرفايل‌هاي C++ استاندارد (مانند <iostream> ) است. براي مثال اگر بخواهيم تابع اعداد تصادفي rand() را از سرفايل <cstdlib> به كار ببريم، بايد دستور پيش‌پردازندۀ زير را به ابتداي فايل برنامۀ‌ اصلي اضافه کنيم: #include <cstdlib> بعضي از سرفايل‌هاي كتابخانۀ C++ استاندارد که کاربرد بيشتري دارند در جدول زير آمده است: شرح سرفايل تابع <assert> را تعريف مي‌کند <assert> توابعي را براي بررسي کاراکترها تعريف مي‌کند <ctype> ثابت‌هاي مربوط به اعداد مميز شناور را تعريف مي‌کند <cfloat> محدودۀ اعداد صحيح را روي سيستم موجود تعريف مي‌کند <climits> توابع رياضي را تعريف مي‌کند <cmath> توابعي را براي ورودي و خروجي استاندارد تعريف مي‌کند <cstdio> توابع کاربردي را تعريف مي‌کند <cstdlib> توابعي را براي پردازش رشته‌ها تعريف مي‌کند <cstring> توابع تاريخ و ساعت را تعريف مي‌کند <ctime>

3- توابع ساخت كاربر گرچه توابع بسيار متنوعي در کتابخانۀ‌ C++ استاندارد وجود دارد ولي اين توابع براي بيشتر وظايف‌ برنامه‌نويسي كافي نيستند. علاوه بر اين برنامه‌نويسان دوست دارند خودشان بتوانند توابعي را بسازند و استفاده نمايند.

مثال 3-5 تابع cube() int cube(int x) { // returns cube of x: يك مثال ساده از توابع ساخت كاربر: int cube(int x) { // returns cube of x: return x*x*x; } اين تابع، مكعب يك عدد صحيح ارسالي به آن را برمي‌گرداند. بنابراين فراخواني cube(2) مقدار 8 را برمي‌گرداند.

يك تابع ساخت كاربر دو قسمت دارد: 1-عنوان 2- بدنه. عنوان يك تابع به صورت زير است: (فهرست‌ پارامترها) نام‌ نوع‌ بازگشتي‌ مثال: int cube(int x) { … بدنه تابع } نوع بازگشتي تابع cube() که در بالا تعريف شد، int است. نام آن cube مي‌باشد و يک پارامتر از نوع int به نام x دارد. يعني تابع cube() يک مقدار از نوع int مي‌گيرد و پاسخي از نوع int تحويل مي‌دهد. بدنۀ تابع، يك بلوك كد است كه در ادامۀ عنوان آن مي‌آيد. بدنه شامل دستوراتي است كه بايد انجام شود تا نتيجۀ مورد نظر به دست آيد. بدنه شامل دستور return است كه پاسخ نهايي را به مكان فراخواني تابع برمي‌گرداند.

دستور return دو وظيفۀ عمده دارد دستور return دو وظيفۀ عمده دارد. اول اين که اجراي تابع را خاتمه مي‌دهد و دوم اين که مقدار نهايي را به برنامۀ فراخوان باز مي‌گرداند. دستور return به شکل زير استفاده مي‌شود: return expression; به جاي expression هر عبارتي قرار مي‌گيرد که بتوان مقدار آن را به يک متغير تخصيص داد. نوع آن عبارت بايد با نوع بازگشتي تابع يکي باشد. عبارت int main() که در همۀ برنامه‌ها استفاده کرده‌ايم يک تابع به نام «تابع اصلي» را تعريف مي‌کند. نوع بازگشتي اين تابع از نوع int است. نام آن main است و فهرست پارامترهاي آن خالي است؛ يعني هيچ پارامتري ندارد.

تنها هدف اين برنامه، امتحان کردن تابع و بررسي صحت کار آن است. 4- برنامۀ آزمون تنها هدف اين برنامه، امتحان کردن تابع و بررسي صحت کار آن است. وقتي يک تابع مورد نياز را ايجاد کرديد، فورا بايد آن تابع را با يک برنامۀ ساده امتحان کنيد. چنين برنامه‌اي برنامۀ آزمون ناميده مي‌شود. برنامۀ آزمون يک برنامۀ موقتي است که بايد «سريع و کثيف» باشد؛ يعني: لازم نيست در آن تمام ظرافت‌هاي برنامه‌نويسي – مثل پيغام‌هاي خروجي، برچسب‌ها و راهنماهاي خوانا – را لحاظ کنيد.

مثال 4-5 يك برنامۀ آزمون براي تابع cube() int cube(int x) { // returns cube of x: return x*x*x; } int main() { // tests the cube() function: int n=1; while (n != 0) { cin >> n; cout << "\tcube(" << n << ") = " << cube(n) << endl; }} برنامۀ حاضر اعداد صحيح را از ورودي مي‌گيرد و مكعب آن‌ها را چاپ مي‌كند تا اين كه كاربر مقدار 0 را وارد كند.

مي‌توان رابطۀ بين تابع main() و تابع cube() را شبيه اين شکل تصور نمود: هر عدد صحيحي که خوانده مي‌شود، با استفاده از کد cube(n) به تابع cube() فرستاده مي‌شود. مقدار بازگشتي از تابع، جايگزين عبارت cube(n) گشته و با استفاده از cout در خروجي چاپ مي‌شود. دقت كنيد كه تابع cube() در بالاي تابع main() تعريف شده زيرا قبل از اين كه تابعcube() در تابع main() به كار رود، كامپايلر C++ بايد در بارۀ‌ آن اطلاع حاصل كند. 5 n int 125 cube() main() x مي‌توان رابطۀ بين تابع main() و تابع cube() را شبيه اين شکل تصور نمود:

مثال 5-5 يك برنامۀ آزمون براي تابع max() تابع زير دو پارامتر دارد. اين تابع از دو مقدار فرستاده شده به آن، مقدار بزرگ‌تر را برمي‌گرداند: int max(int x, int y) { // returns larger of the two given integers: int z; z = (x > y) ? x : y ; return z; } int main() { int m, n; do { cin >> m >> n; cout << "\tmax(" << m << "," << n << ") = " << max(m,n) << endl; } while (m != 0);}

{ // returns larger of the two given integers: if (x < y) return y; توابع مي‌توانند بيش از يک دستور return داشته باشند. مثلا تابع max() را مانند اين نيز مي‌توانستيم بنويسيم: int max(int x, int y) { // returns larger of the two given integers: if (x < y) return y; else return x; } دستور return نوعي دستور پرش است (شبيه دستور break ) زيرا اجرا را به بيرون از تابع هدايت مي‌کند. اگرچه معمولا return در انتهاي تابع قرار مي‌گيرد، مي‌توان آن را در هر نقطۀ ديگري از تابع قرار داد. در اين کد هر دستور return که زودتر اجرا شود مقدار مربوطه‌اش را بازگشت داده و تابع را خاتمه مي‌دهد.

5- اعلان‌ها و تعاريف تابع به دو روش ميتوان توابع را تعريف نمود: 1-توابع قبل از تابع main() به طور كامل با بدنه مربوطه آورده شوند. 2-راه ديگري که بيشتر رواج دارد اين گونه است که ابتدا تابع اعلان شود، سپس متن برنامۀ اصليmain() بيايد، پس از آن تعريف کامل تابع قرار بگيرد.

اعلان تابع شبيه اعلان متغيرهاست. اعلان تابع با تعريف تابع تفاوت دارد. اعلان تابع، فقط عنوان تابع است که يک سميکولن در انتهاي آن قرار دارد. تعريف تابع، متن کامل تابع است که هم شامل عنوان است و هم شامل بدنه. اعلان تابع شبيه اعلان متغيرهاست. يک متغير قبل از اين که به کار گرفته شود بايد اعلان شود. تابع هم همين طور است با اين فرق که متغير را در هر جايي از برنامه مي‌توان اعلان کرد اما تابع را بايد قبل از برنامۀ اصلي اعلان نمود.

در اعلان تابع فقط بيان مي‌شود که نوع بازگشتي تابع چيست، نام تابع چيست و نوع پارامترهاي تابع چيست. همين‌ها براي کامپايلر کافي است تا بتواند کامپايل برنامه را آغاز کند. سپس در زمان اجرا به تعريف بدنۀ تابع نيز احتياج مي‌شود که اين بدنه در انتهاي برنامه و پس از تابع main() قرار مي‌گيرد.

فرق بين «آرگومان» و «پارامتر» : پارامترها متغيرهايي هستند که در فهرست پارامتر يک تابع نام برده مي‌شوند. پارامترها متغيرهاي محلي براي تابع محسوب مي‌شوند؛ يعني فقط در طول اجراي تابع وجود دارند. آرگومان‌ها متغيرهايي هستند که از برنامۀ اصلي به تابع فرستاده مي‌شوند.

int max(int,int); int main() { int m, n; do { cin >> m >> n; cout << "\tmax(" << m << "," << n << ") = " << max(m,n) << endl; } while (m != 0);} int max(int x, int y) { if (x < y) return y; else return x;} مثال 6-5 تابعmax() با اعلان‌ جدا از تعريف آن اين برنامه همان برنامۀ آزمون تابع max() در مثال 5-6 است. اما اين‌جا اعلان تابع بالاي تابع اصلي ظاهر ‌شده و تعريف تابع بعد از برنامۀ اصلي آمده است: توجه كنيد كه پارامترهاي x و y در بخش عنوان تعريف تابع آمده‌اند (طبق معمول) ولي در اعلان تابع وجود ندارند.

6- كامپايل جداگانۀ توابع اغلب اين طور است که تعريف و بدنۀ توابع در فايل‌هاي جداگانه‌اي قرار مي‌گيرد. اين فايل‌ها به طور مستقل کامپايل1 مي‌شوند و سپس به برنامۀ اصلي که آن توابع را به کار مي‌گيرد الصاق2 مي‌شوند. توابع کتابخانۀ C++ استاندارد به همين شکل پياده‌سازي شده‌اند و هنگامي که يکي از آن توابع را در برنامه‌هايتان به کار مي‌بريد بايد با دستور راهنماي پيش‌پردازنده، فايل آن توابع را به برنامه‌تان ضميمه کنيد. اين کار چند مزيت دارد:

1- اولين مزيت «مخفي‌سازي اطلاعات» است. 2-مزيت ديگر اين است که توابع مورد نياز را مي‌توان قبل از اين که برنامۀ اصلي نوشته شود، جداگانه آزمايش نمود. 3-سومين مزيت اين است که در هر زماني به راحتي مي‌توان تعريف توابع را عوض کرد بدون اين که لازم باشد برنامۀ اصلي تغيير يابد. 4-چهارمين مزيت هم اين است که مي‌توانيد يک بار يک تابع را کامپايل و ذخيره کنيد و از آن پس در برنامه‌هاي مختلفي از همان تابع استفاده ببريد.

تابع max() را به خاطر بياوريد تابع max() را به خاطر بياوريد. براي اين که اين تابع را در فايل جداگانه‌اي قرار دهيم، تعريف آن را در فايلي به نام max.cpp ذخيره مي‌کنيم. فايل max.cpp شامل کد زير است: int max(int x, int y) { if (x < y) return y; else return x; } max.cpp

حال كافي است عبارت:#include <test حال كافي است عبارت:#include <test.cpp> را به اول برنامه اصلي وقبل ازmain() اضافه كنيم: #include <test.cpp> int main() { // tests the max() function: int m, n; do { cin >> m >> n; cout << "\tmax(" << m << "," << n << ") = " << max(m,n) << endl; } while (m != 0);}

نحوۀ کامپايل کردن فايل‌ها و الصاق آن‌ها به يکديگر به نوع سيستم عامل و نوع کامپايلر بستگي دارد. در سيستم عامل ويندوز معمولا توابع را در فايل‌هايي از نوع DLL کامپايل و ذخيره مي‌کنند و سپس اين فايل را در برنامۀ اصلي احضار مي‌نمايند. فايل‌هاي DLL را به دو طريق ايستا و پويا مي‌توان مورد استفاده قرار داد. براي آشنايي بيشتر با فايل‌هاي DLL به مرجع ويندوز و کامپايلرهاي C++ مراجعه کنيد.

6- متغيرهاي محلي، توابع محلي متغير محلي، متغيري است که در داخل يک بلوک اعلان گردد. اين گونه متغيرها فقط در داخل همان بلوکي که اعلان مي‌شوند قابل دستيابي هستند. چون بدنۀ تابع، خودش يک بلوک است پس متغيرهاي اعلان شده در يک تابع متغيرهاي محلي براي آن تابع هستند. اين متغيرها فقط تا وقتي که تابع در حال کار است وجود دارند. پارامترهاي تابع نيز متغيرهاي محلي محسوب مي‌شوند.

* مثال 7-5 تابع فاكتوريل اعداد فاكتوريل را در مثال 8-5 ديديم. فاكتوريل عدد صحيح n برابر است با: n! = n(n-1)(n-2)..(3)(2)(1) تابع زير، فاکتوريل عدد n را محاسبه مي‌کند‌: long fact(int n) { //returns n! = n*(n-1)*(n-2)*...*(2)*(1) if (n < 0) return 0; int f = 1; while (n > 1) f *= n--; return f; } اين تابع دو متغير محلي دارد: n و f پارامتر n يک متغير محلي است زيرا در فهرست پارامترهاي تابع اعلان شده و متغير f نيز محلي است زيرا درون بدنۀ تابع اعلان شده است.

همان گونه که متغيرها مي‌توانند محلي باشند، توابع نيز مي‌توانند محلي باشند. يک تابع محلي تابعي است که درون يک تابع ديگر به کار رود. با استفاده از چند تابع ساده و ترکيب آن‌ها مي‌توان توابع پيچيده‌تري ساخت. به مثال زير نگاه کنيد. تابع محلي در رياضيات، تابع جايگشت را با p(n,k) نشان مي‌دهند. اين تابع بيان مي‌کند که به چند طريق مي‌توان k عنصر دلخواه از يک مجموعۀ n عنصري را کنار يکديگر قرار داد. براي اين محاسبه از رابطۀ زير استفاده مي‌شود:

اين تابع، خود از تابع ديگري که همان تابع فاکتوريل است استفاده کرده است. شرط به کار رفته در دستور if براي محدود کردن حالت‌هاي غير ممکن استفاده شده است. در اين حالت‌ها، تابع مقدار 0 را برمي‌گرداند تا نشان دهد که يک ورودي اشتباه وجود داشته است. پس به 12 طريق مي‌توانيم دو عنصر دلخواه از يک مجموعۀ چهار عنصري را کنار هم بچينيم. براي دو عنصر از مجموعۀ {1, 2, 3, 4} حالت‌هاي ممکن عبارت است از: 12, 13, 14, 21, 23, 24, 31, 32, 34, 41, 42, 43 كد زير تابع جايگشت را پياده‌سازي‌ مي‌كند: long perm(int n, int k) {// returns P(n,k), the number of the permutations of k from n: if (n < 0) || k < 0 || k > n) return 0; return fact(n)/fact(n-k); }

برنامۀ آزمون براي تابع perm() در ادامه آمده است: long perm(int,int); // returns P(n,k), the number of permutations of k from n: int main() { // tests the perm() function: for (int i = -1; i < 8; i++) { for (int j= -1; j <= i+1; j++) cout << " " << perm(i,j); cout << endl; } } 0 0 0 1 0 0 1 1 0 0 1 2 2 0 0 1 3 6 6 0 0 1 4 12 24 24 0 0 1 5 20 60 120 120 0 0 1 6 30 120 360 720 720 0 0 1 7 42 210 840 2520 5040 5040 0

7- تابع void لازم نيست يك‌ تابع‌ حتما مقداري را برگرداند. در C++ براي مشخص کردن چنين توابعي از کلمۀ کليدي void به عنوان نوع بازگشتي تابع استفاده مي‌کنند يک تابع void تابعي است که هيچ مقدار بازگشتي ندارد. از آن‌جا كه يك تابع void مقداري را برنمي‌گرداند، نيازي به دستور return نيست ولي اگر قرار باشد اين دستور را در تابع void قرار دهيم، بايد آن را به شکل تنها استفاده کنيم بدون اين که بعد از کلمۀ return هيچ چيز ديگري بيايد: return; در اين حالت دستور return فقط تابع را خاتمه مي‌دهد.

توابع بولي فقط دو مقدار را برمي‌گردانند: true يا false . 8- توابع بولي در بسياري از اوقات لازم است در برنامه، شرطي بررسي شود. اگر بررسي اين شرط به دستورات زيادي نياز داشته باشد، بهتر است که يک تابع اين بررسي را انجام دهد. اين کار مخصوصا هنگامي که از حلقه‌ها استفاده مي‌شود بسيار مفيد است. توابع بولي فقط دو مقدار را برمي‌گردانند: true يا false . اسم توابع بولي را معمولا به شکل سوالي انتخاب مي‌کنند زيرا توابع بولي هميشه به يک سوال مفروض پاسخ بلي يا خير مي‌دهند.

{ // returns true if n is prime, false otherwise: مثال 10-5 تابعي‌ كه‌ اول بودن اعداد را بررسي مي‌كند کد زير يك تابع بولي است كه تشخيص مي‌دهد آيا عدد صحيح ارسال شده به آن، اول است يا خير: bool isPrime(int n) { // returns true if n is prime, false otherwise: float sqrtn = sqrt(n); if (n < 2) return false; // 0 and 1 are not primes if (n < 4) return true; // 2 and 3 are the first primes if (n%2 == 0) return false; // 2 is the only even prime for (int d=3; d <= sqrtn; d += 2) if (n%d == 0) return false; // n has a nontrivial divisor return true; // n has no nontrivial divisors }

9- توابع ورودي/خروجي (I/O) بخش‌هايي از برنامه که به جزييات دست و پا گير مي‌پردازد و خيلي به هدف اصلي برنامه مربوط نيست را مي‌توان به توابع سپرد. در چنين شرايطي سودمندي توابع محسوس‌تر مي‌شود. فرض کنيد نرم‌افزاري براي سيستم آموزشي دانشگاه طراحي کرده‌ايد که سوابق تحصيلي دانشجويان را نگه مي‌دارد. در اين نرم‌افزار لازم است که سن دانشجو به عنوان يکي از اطلاعات پروندۀ دانشجو وارد شود. اگر وظيفۀ دريافت سن را به عهدۀ يک تابع بگذاريد، مي‌توانيد جزيياتي از قبيل کنترل ورودي معتبر، يافتن سن از روي تاريخ تولد و ... را در اين تابع پياده‌سازي کنيد بدون اين که از مسير برنامۀ اصلي منحرف شويد.

مثال بعد يک تابع ورودي را نشان مي‌دهد. قبلا نمونه‌اي از توابع خروجي را ديديم. تابع PrintDate() در مثال 9-5 هيچ چيزي به برنامۀ اصلي برنمي‌گرداند و فقط براي چاپ نتايج به کار مي‌رود. اين تابع نمونه‌اي از توابع خروجي است؛ يعني توابعي که فقط براي چاپ نتايج به کار مي‌روند و هيچ مقدار بازگشتي ندارند. توابع ورودي نيز به همين روش کار مي‌کنند اما در جهت معکوس. يعني توابع ورودي فقط براي دريافت ورودي و ارسال آن به برنامۀ اصلي به کار مي‌روند و هيچ پارامتري ندارند. مثال بعد يک تابع ورودي را نشان مي‌دهد.

مثال 11-5 تابعي براي دريافت سن كاربر تابع سادۀ زير، سن کاربر را درخواست مي‌کند و مقدار دريافت شده را به برنامۀ اصلي مي‌فرستد. اين تابع تقريبا هوشمند است و هر عدد صحيح ورودي غير منطقي را رد مي‌کند و به طور مکرر درخواست ورودي معتبر مي‌کند تا اين که يک عدد صحيح در محدودۀ 7 تا 120 دريافت دارد: int age() { // prompts the user to input his/her age and returns that value: int n; while (true) { cout << "How old are you: "; cin >> n; if (n < 0) cout << "\a\tYour age could not be negative."; else if (n > 120) cout << "\a\tYou could not be over 120."; else return n; cout << "\n\tTry again.\n"; }

{ // tests the age() function: int a = age(); يك برنامۀ آزمون و خروجي حاصل از آن در ادامه آمده است: int age() int main() { // tests the age() function: int a = age(); cout << "\nYou are " << a << " years old.\n"; } How old are you? 125 You could not be over 120 Try again. How old are you? -3 Your age could not be negative How old are you? 99 You are 99 years old.

تا اين‌ لحظه‌ تمام‌ پارامترهايي كه‌ در توابع‌ ديديم‌ به‌ طريق‌ مقدار ارسال‌ شده‌اند. يعني‌ ابتدا مقدار متغيري که در فراخواني تابع ذکر شده برآورد مي‌شود و سپس اين مقدار به پارامترهاي محلي تابع فرستاده مي‌شود. مثلا در فراخواني cube(x) ابتدا مقدار x برآورد شده و سپس اين مقدار به متغير محلي n در تابع فرستاده مي‌شود و پس از آن تابع کار خويش را آغاز مي‌کند. در طي اجراي تابع ممکن است مقدار n تغيير کند اما چون n محلي است هيچ تغييري روي مقدار x نمي‌گذارد.

پس خود x به تابع نمي‌رود بلکه مقدار آن درون تابع کپي مي‌شود. تغيير دادن اين مقدار کپي شده درون تابع هيچ تاثيري بر x اصلي ندارد. به اين ترتيب تابع مي‌تواند مقدار x را بخواند اما نمي‌تواند مقدار x را تغيير دهد. به همين دليل به x يک پارامتر «فقط خواندني» مي‌گويند. وقتي ارسال به وسيلۀ مقدار باشد، هنگام فراخواني تابع مي‌توان از عبارات استفاده کرد. مثلا تابع cube() را مي‌توان به صورتcube(2*x-3) فراخواني کرد يا به شکل cube(2*sqrt(x)-cube(3)) فراخواني نمود. در هر يک از اين حالات، عبارت درون پرانتز به شکل يک مقدار تکي برآورد شده و حاصل آن مقدار به تابع فرستاده مي‌شود.

10- ارسال به طريق ارجاع‌ (آدرس) ارسال به طريق مقدار باعث مي‌شود که متغيرهاي برنامۀ اصلي از تغييرات ناخواسته در توابع مصون بمانند. اما گاهي اوقات عمدا مي‌خواهيم اين اتفاق رخ دهد. يعني مي‌خواهيم که تابع بتواند محتويات متغير فرستاده شده به آن را دست‌کاري کند. در اين حالت از ارسال به طريق ارجاع ‌استفاده مي‌کنيم.

براي اين که مشخص کنيم يک پارامتر به طريق ارجاع ارسال مي‌شود، علامت را به نوع پارامتر در فهرست پارامترهاي تابع اضافه مي‌کنيم. اين باعث مي‌شود که تابع به جاي اين که يک کپي محلي از آن آرگومان ايجاد کند، خود آرگومان محلي را به کار بگيرد. به اين ترتيب تابع هم مي‌تواند مقدار آرگومان فرستاده شده را بخواند و هم مي‌تواند مقدار آن را تغيير دهد. در اين حالت آن پارامتر يک پارامتر «خواندني-نوشتني» خواهد بود. &

هر تغييري که روي پارامتر خواندني-نوشتني در تابع صورت گيرد به طور مستقيم روي متغير برنامۀ اصلي اعمال مي‌شود. به مثال زير نگاه کنيد. * مثال 12-5 تابع‌ swap() تابع‌ كوچك‌ زير در مرتب کردن داده‌ها کاربرد فراوان دارد: void swap(float& x, float& y) { // exchanges the values of x and y: float temp = x; x = y; y = temp; } هدف‌ اين تابع جابجا کردن دو عنصري است که به آن فرستاده مي‌شوند. براي اين منظور پارامترهاي x و y به صورت پارامترهاي ارجاع تعريف شده‌اند: float& x, float& y

void swap(float&, float&) // exchanges the values of x and y: عملگر ارجاع‌ & موجب‌ مي‌شود كه‌ به جاي x و y آرگومان‌هاي ارسالي قرار بگيرند. برنامۀ آزمون و اجراي آزمايشي آن در زير آمده است: void swap(float&, float&) // exchanges the values of x and y: int main() { // tests the swap() function: float a = 55.5, b = 88.8; cout << "a = " << a << ", b = " << b << endl; swap(a,b); } a = 55.5, b = 88.8 a = 88.8, b = 55.5

هنگام فراخواني تابع swap(a,b) وقتي‌ فراخواني swap(a,b) اجرا مي‌شود، x به a اشاره مي‌کند و y به b. سپس متغير محلي temp اعلان مي‌شود و مقدار x (که همان a است) درون آن قرار مي‌گيرد. پس از آن مقدار y (که همان b است) درون x (يعني a) قرار مي‌گيرد و آنگاه مقدار temp درون y (يعني b) قرار داده مي‌شود. نتيجۀ نهايي اين است که مقادير a و b با يکديگر جابجا مي شوند. شکل مقابل نشان مي‌دهد که چطور اين جابجايي رخ مي‌دهد: هنگام فراخواني تابع swap(a,b) 55.5 a float main() x float& 88.8 b y swap() 88.8 a float swap() main() x float& 55.5 b y temp بعد از بازگشت

به‌ اعلان‌ تابع‌ swap() دقت کنيد: void swap(float&, float&) اين اعلان شامل عملگر ارجاع‌ & براي‌ هر پارامتر است‌. برنامه‌نويسان c عادت دارند که عملگر ارجاع & را به عنوان پيشوند نام متغير استفاده کنند (مثلfloat &x) در C++ فرض مي‌کنيم عملگر ارجاع & پسوند نوع است (مثل float& x) به هر حال کامپايلر هيچ فرقي بين اين دو اعلان نمي‌گذارد و شکل نوشتن عملگر ارجاع کاملا اختياري و سليقه‌اي است.

مثال‌ 13-5 ارسال‌ به‌ طريق‌ مقدار و ارسال‌ به‌ طريق‌ ارجاع‌ اين‌ برنامه، تفاوت‌ بين‌ ارسال‌ به طريق‌ مقدار و ارسال‌ به طريق‌ ارجاع‌ را نشان‌ مي‌دهد: void f(int,int&); int main() { int a = 22, b = 44; cout << "a = " << a << ", b = " << b << endl; f(a,b); f(2*a-3,b); cout << "a = " << a << ", b = " << b << endl;} void f(int x , int& y) { x = 88; y = 99;} تابع f() دو پارامتر دارد که اولي به طريق مقدار و دومي به طريق ارجاع ارسال مي‌شود. فراخواني f(a,b) باعث مي‌شود که a از طريق‌ مقدار به‌ x ارسال شود و b از طريق‌ ارجاع‌ به‌ y فرستاده شود. a = 22, b = 44 a = 22, b = 99

شکل زير نحوۀ کار تابع f() را نشان مي‌دهد. 22 a int main() x 44 b y int& f() هنگام فراخواني تابع f(a,b) 22 a int main() 88 xx 99 b y int& f() بعد از بازگشت

در جدول‌ زير خلاصۀ تفاوت‌هاي بين ارسال از طريق مقدار و ارسال از طريق ارجاع آمده است. int& x; int x; پارامتر x يک ارجاع است پارامتر x يک متغير محلي است x مترادف با آرگومان است x يک کپي از آرگومان است مي‌تواند محتويات آرگومان را تغيير دهد تغيير محتويات آرگومان ممکن نيست آرگومان ارسال شده از طريق ارجاع فقط بايد يک متغير باشد آرگومان ارسال شده از طريق مقدار مي‌تواند يک ثابت، يک متغير يا يک عبارت باشد آرگومان خواندني-نوشتني است آرگومان فقط خواندني است

يكي‌ از مواقعي‌ كه‌ پارامترهاي‌ ارجاع‌ مورد نياز هستند جايي‌ است‌ كه‌ تابع‌ بايد بيش از يك‌ مقدار را بازگرداند. دستور return فقط مي‌تواند يك‌ مقدار را برگرداند. بنابراين‌ اگر بايد بيش از يك‌ مقدار برگشت داده‌ شود، اين‌ كار را پارامترهاي‌ ارجاع‌ انجام‌ مي‌دهند.

* مثال‌ 14-5 بازگشت‌ بيشتر از يك‌ مقدار تابع‌ زير از طريق دو پارامتر ارجاع، دو مقدار را بازمي‌گرداند: area و circumference (محيط و مساحت‌) براي دايره‌اي که شعاع آن عدد مفروض r است: void ComputeCircle(double& area, double& circumference, double r) { // returns the area and circumference of a circle with radius r: const double PI = 3.141592653589793; area = PI*r*r; circumference = 2*PI*r; }

برنامۀ آزمون تابع فوق و يک اجراي آزمايشي آن در شکل زير نشان داده شده است: void ComputerCircle(double&, double&, double); // returns the area and circumference of a circle with radius r; int main() { // tests the ComputeCircle() function: double r, a, c; cout << "Enter radius: "; cin >> r; ComputeCircle(a, c, r); cout << "area = " << a << ", circumference = " << c << endl;}

12- ارسال‌ از طريق‌ ارجاع‌ ثابت‌ ارسال پارامترها به طريق ارجاع دو خاصيت مهم دارد: اول اين که تابع مي‌تواند روي آرگومان واقعي تغييراتي بدهد دوم اين که از اشغال بي‌مورد حافظه جلوگيري مي‌شود. روش ديگري نيز براي ارسال آرگومان وجود دارد: ارسال از طريق ارجاع ثابت. اين روش مانند ارسال از طريق ارجاع است با اين فرق که تابع نمي‌تواند محتويات پارامتر ارجاع را دست‌کاري نمايد و فقط اجازۀ خواندن آن را دارد. براي اين که پارامتري را از نوع ارجاع ثابت اعلان کنيم بايد عبارت const را به ابتداي اعلان آن اضافه نماييم.

مثال‌ 15-5 ارسال‌ از طريق‌ ارجاع‌ ثابت‌ سه طريقه ارسال پارامتر در تابع زير به کار رفته است: void f(int x, int& y, const int& z) { x += z; y += z; cout << "x = " << x << ", y = " << y << ", z = " << z << endl; } در تابع فوق اولين‌ پارامتر يعني x از طريق مقدار ارسال مي‌شود، دومين پارامتر يعني y از طريق ارجاع و سومين پارامتر نيز از طريق ارجاع ثابت.

برنامۀ آزمون و يک اجراي آزمايشي از مثال قبل: void f(int, int&, const int&); int main() { // tests the f() function: int a = 22, b = 33, c = 44; cout << "a = " << a << ", b = " << b << ", c = " << c << endl; f(a,b,c); f(2*a-3,b,c); } a = 22, b = 33, c = 44 x = 66, y = 77, z = 44 a = 22, b = 77, c = 44 x = 85, y = 121, z = 44 a = 22, b = 121, c = 44 تابع‌ فوق پارامترهاي‌ x و y را مي‌تواند تغيير دهد ولي‌ قادر نيست پارامتر z را تغيير دهد. تغييراتي که روي x صورت مي‌گيرد اثري روي آرگومان a نخواهد داشت زيرا a از طريق مقدار به تابع ارسال شده. تغييراتي که روي y صورت مي‌گيرد روي آرگومان b هم تاثير مي‌گذارد زيرا b از طريق ارجاع به تابع فرستاده شده.

ارسال به طريق ارجاع ثابت بيشتر براي توابعي استفاده مي‌شود که عناصر بزرگ را ويرايش مي‌کنند مثل آرايه‌ها يا نمونۀ کلاس‌ها که در جلسه‌‌هاي بعدي توضيح آن‌ها آمده است. عناصري که از انواع اصلي هستند (مثل int يا float) به طريق مقدار ارسال مي‌شوند به شرطي که قرار نباشد تابع محتويات آن‌ها را دست‌کاري کند.

13- توابع‌ بي‌واسطه‌ تابعي که به شکل بي‌واسطه تعريف مي‌شود، ظاهري شبيه به توابع معمولي دارد با اين فرق که عبارت inline در اعلان و تعريف آن قيد شده است. مثال‌ 16-5 تابع‌ cube() به شکل بي‌واسطه‌ اين‌ همان‌ تابع‌ cube() مثال‌ 3-5 است‌: inline int cube(int x) { // returns cube of x: return x*x*x; } تنها تفاوت‌ اين‌ است‌ كه‌ كلمۀ‌ كليدي‌ inline در ابتداي عنوان تابع ذکر شده. اين‌ عبارت به‌ كامپايلر مي‌گويد كه‌ در برنامه به جاي cube(n) کد واقعي (n)*(n)*(n) را قرار دهد.

. به برنامۀ آزمون زير نگاه کنيد: int main() { // tests the cube() function: cout << cube(4) << endl; int x, y; cin >> x; y = cube(2*x-3);} اين برنامه هنگام کامپايل به شکل زير درمي‌آيد، گويي اصلا تابعي وجود نداشته: cout << (4) * (4) * (4) << endl; y = (2*x-3) * (2*x-3) * (2*x-3);} احتياط: استفاده از توابع بي‌واسطه مي‌تواند اثرات منفي داشته باشد. مثلا اگر يک تابع بي‌واسطه داراي 40 خط کد باشد و اين تابع در 26 نقطه مختلف از برنامۀ اصلي فراخواني شود، هنگام کامپايل بيش از هزار خط کد به برنامۀ اصلي افزوده مي‌شود. همچنين تابع بي‌واسطه مي‌تواند قابليت انتقال برنامۀ شما را روي سيستم‌هاي مختلف کاهش دهد. وقتي‌ كامپايلر کد واقعي تابع را جايگزين فراخواني آن مي‌کند، مي‌گوييم که تابع بي‌واسطه، باز مي‌شود.

14- چندشکلي توابع‌ در C++ مي‌توانيم چند تابع داشته باشيم که همگي يک نام دارند. در اين حالت مي‌گوييم که تابع مذکور، چندشکلي دارد. شرط اين کار آن است که فهرست پارامترهاي اين توابع با يکديگر تفاوت داشته باشد. يعني تعداد پارامترها متفاوت باشد يا دست کم يکي از پارامترهاي متناظر هم نوع نباشند.

مثال‌ 17-5 چندشکلي تابع max()‌ int max(int, int); int max(int, int, int); int max(double, double); int main() { cout << max(99,77) << " " << max(55,66,33) << " " << max(44.4,88.8); }

int max(int x, int y) { // returns the maximum of the two given integers: return (x > y ? x : y); } int max(int x, int y, int z) { // returns the maximum of the three given integers: int m = (x > y ? x : y); // m = max(x , y) return ( z > m ? z : m); int max(double x, double y) { // return the maximum of the two given doubles: return (x>y ? x : y);

در اين برنامه سه تابع با نام max() تعريف شده است. وقتي تابع max() در جايي از برنامه فراخواني مي‌شود، کامپايلر فهرست آرگومان آن را بررسي مي‌کند تا بفهمد که کدام نسخه از max بايد احضار شود. مثلا در اولين فراخواني تابع max() دو آرگومان int ارسال شده، پس نسخه‌اي که دو پارامتر int در فهرست پارامترهايش دارد فراخواني مي‌شود. اگر اين نسخه وجود نداشته باشد، کامپايلر intها را به double ارتقا مي‌دهد و سپس نسخه‌اي که دو پارامتر double دارد را فرا مي‌خواند.

14- تابع‌ main() برنامه‌هايي که تا کنون نوشتيم همه داراي تابعي به نام main() هستند. منطق C++ اين طور است که هر برنامه بايد داراي تابعي به نام main() باشد. در حقيقت هر برنامه کامل، از يک تابع main() به همراه توابع ديگر تشکيل شده است که هر يک از اين توابع به شکل مستقيم يا غير مستقيم از درون تابع main() فراخواني مي‌شوند.

خود برنامه با فراخواني تابع main() شروع مي‌شود. چون اين تابع يک نوع بازگشتي int دارد، منطقي است که بلوک تابع main() شامل دستور return 0; باشد هرچند که در برخي از کامپايلرهاي C++ اين خط اجباري نيست و مي‌توان آن را ذکر نکرد. مقدار صحيحي که با دستور return به سيستم عامل برمي‌گردد بايد تعداد خطاها را شمارش کند. مقدار پيش‌فرض آن 0 است به اين معنا که برنامه بدون خطا پايان گرفته است. با استفاده از دستور return مي‌توانيم برنامه را به طور غيرمعمول خاتمه دهيم.

مثال‌ 18-5 استفاده‌ از دستور return براي‌ پايان دادن به‌ يك‌ برنامه‌ int main() { // prints the quotient of two input integers: int n, d; cout << "Enter two integers: "; cin >> n >> d; if (d = = 0) return 0; cout << n << "/" << d << " = " << n/d << endl; } دستور return تابع فعلي را خاتمه مي‌دهد و کنترل را به فراخواننده بازمي‌گرداند. به همين دليل است که اجراي دستور return در تابع main() کل برنامه را خاتمه مي‌دهد. Enter two integers: 99 17 99/17 = 5

1 - استفاده‌ از دستور return 2 - فراخواني‌ تابع‌ exit() چهار روش وجود دارد که بتوانيم برنامه را به شکل غيرمعمول (يعني قبل از اين که اجرا به پايان بلوک اصلي برسد) خاتمه دهيم: 1 - استفاده‌ از دستور return 2 - فراخواني‌ تابع‌ exit() 3 - فراخواني‌ تابع‌ abort() 4 – ايجاد يک حالت استثنا اين تابع در سرفايل <cstdlib> تعريف شده است. تابع exit() براي خاتمه دادن به کل برنامه در هر تابعي غير از تابع main() مفيد است.

مثال‌ 19-5 استفاده‌ از تابع‌ exit() براي‌ پايان‌ دادن به برنامه‌ #include <cstdlib> // defines the exit() function #include <iostream> // defines thi cin and cout objects using namespace std; double reciprocal(double x); int main() { double x; cin >> x; cout << reciprocal(x); } double reciprocal(double x)1 – Exception { // returns the reciprocal of x: if (x = = 0) exit(1); // terminate the program return 1.0/x; } دراين برنامۀ اگر كاربر عدد 0 را وارد کند، تابع reciprocal() خاتمه مي‌يابد و برنامه بدون هيچ مقدار چاپي به پايان مي‌رسد

15- آرگومان‌هاي‌ پيش‌فرض‌ در C++ مي‌توان تعداد آرگومان‌هاي يک تابع را در زمان اجرا به دلخواه تغيير داد. اين امر با استفاده از آرگومان‌هاي اختياري و مقادير پيش‌فرض امکان‌پذير است. براي اين که به يک پارامتر مقدار پيش‌فرض بدهيم بايد آن مقدار را در فهرست پارامترهاي تابع و جلوي پارامتر مربوطه به همراه علامت مساوي درج کنيم. به اين ترتيب اگر هنگام فراخواني تابع، آن آرگومان را ذکر نکنيم، مقدار پيش‌فرض آن در محاسبات تابع استفاده مي‌شود. به همين خاطر به اين گونه آرگومان‌ها، آرگومان اختياري مي‌گويند.

مثال‌ 20-5 آرگومان‌هاي ‌پيش‌فرض‌ double p(double, double, double=0, double=0, double=0); int main() { // tests the p() function: double x = 2.0003; cout << "p(x,7) = " << p(x,7) << endl; cout << "p(x,7,6) = " << p(x,7,6) << endl; cout << "p(x,7,6,5) = " << p(x,7,6,5) << endl; cout << "p(x,7,6,5,4) = " << p(x,7,6,5,4) << endl; } double p(double x, double a0, double a1=0, double a2=0, double a3=0) { // returns a0 + a1*x + a2*x^2 + a3*x^3: return a0 + (a1 + (a2 + a3*x)*x)*x; مثال‌ 20-5 آرگومان‌هاي ‌پيش‌فرض‌ برنامۀ زير حاصل چند جمله‌اي درجه سوم را پيدا مي‌کند. براي محاسبۀ اين مقدار از الگوريتم هورنر استفاده شده. به اين شکل که براي کارايي بيشتر، محاسبه به صورت دسته‌بندي مي‌شود: p(x,7) = 7 p(x,7,6) = 19.0018 p(x,7,6,5) = 39.00781 – Default p(x,7,6,5,4) = 71.0222

دقت کنيد که پارامترهايي که مقدار پيش‌فرض دارند بايد در فهرست پارامترهاي تابع بعد از همۀ پارامترهاي اجباري قيد شوند مثل: void f( int a, int b, int c=4, int d=7, int e=3); // OK void g(int a, int b=2, int c=4, int d, int e=3); // ERROR همچنين هنگام فراخواني تابع، آرگومان‌هاي ذکر شده به ترتيب از چپ به راست تخصيص مي‌يابند و پارامترهاي بعدي با مقدار پيش‌فرض پر مي‌شوند. مثلا در تابع p() که در بالا قيد شد، فراخواني p(8.0,7,6) باعث مي‌شود که پارامتر x مقدار 8.0 را بگيرد سپس پارامتر a0 مقدار 7 را بگيرد و سپس پارامتر a1 مقدار 6 را بگيرد. پارامترهاي a2 و a3 مقدار پيش‌فرض‌شان را خواهند داشت. اين ترتيب را نمي‌توانيم به هم بزنيم. مثلا نمي‌توانيم تابع را طوري فرا بخوانيم که پارامترهاي x و a0 و a3 مستقيما مقدار بگيرند ولي پارامترهاي a1 و a2 مقدار پيش‌فرض‌شان را داشته باشند.

پايان جلسه پنجم

جلسه ششم «آرايه‌ها»

آنچه در اين جلسه مي خوانيد: 1- پردازش‌ آرايه‌ها 2- مقداردهي آرايه‌ها‌ 3- ايندكس بيرون از حدود آرايه‌ 4- ارسال آرايه به تابع 5- الگوريتم جستجوي خطي 6- مرتب‌سازي حبابي 7- الگوريتم جستجوي دودويي ›››

8- استفاده از انواع شمارشي در آرايه 9- تعريف‌ انواع‌ 10 -آرايه‌هاي چند بعدي

شناخت و معرفي آرايه‌ها و مزيت و طريقۀ به‌کارگيري آن‌ها هدف کلي: شناخت و معرفي آرايه‌ها و مزيت و طريقۀ به‌کارگيري آن‌ها هدف‌هاي رفتاري: انتظار مي‌رود پس از پايان اين جلسه بتوانيد: - علت استفاده از آرايه‌ها را بدانيد و بتوانيد آن‌ها را در برنامه‌ها به کار ببريد. - آرايه‌هاي «يک‌بعدي» و «چندبعدي» را تعريف کنيد. - مفهوم «ايندکس» را بدانيد و خطاي «اثر همسايگي» را تعريف و شناسايي کنيد. - طريقۀ ارسال آرايه به توابع را بدانيد. - «جستجوي خطي» و «جستجوي دودويي» را به اختصار شرح دهيد. - «مرتب‌سازي حبابي» را به اختصار شرح دهيد.

مقدمه: در برنامه‌هايي که داده‌هاي فراواني را پردازش مي‌کنند استفاده از متغيرهاي معمولي کار عاقلانه‌اي نيست زيرا در بسياري از اين برنامه‌ها «پردازش دسته‌اي» صورت مي‌گيرد به اين معني که مجموعه‌اي از داده‌هاي مرتبط با هم در حافظه قرار داده مي‌شود و پس از پردازش، کل اين مجموعه از حافظه خارج مي‌شود و مجموعۀ بعدي در حافظه بارگذاري مي‌شود. اگر قرار باشد براي اين کار از متغيرهاي معمولي استفاده شود بيشتر وقت برنامه‌نويس صرف پر و خالي کردن انبوهي از متغيرها مي‌شود. به همين دليل در بيشتر زبان‌هاي برنامه‌نويسي «آرايه‌ها» تدارک ديده شده‌اند. آرايه را مي‌توان متغيري تصور کرد که يک نام دارد ولي چندين مقدار را به طور هم‌زمان نگهداري مي‌نمايد.

يک آرايه، يك زنجيره از متغيرهايي است كه همه از يك نوع هستند. به اين متغيرها «اعضاي آرايه» مي‌گويند. هر عضو آرايه با يک شماره مشخص مي‌شود که به اين شماره «ايندکس» يا «زيرنويس» مي‌گويند عناصر يک آرايه در خانه‌هاي پشت سر هم در حافظه ذخيره مي‌شوند. به اين ترتيب آرايه را مي‌توان بخشي از حافظه تصور کرد که اين بخش خود به قسمت‌هاي مساوي تقسيم شده و هر قسمت به يک عنصر تعلق دارد.

شکل مقابل آرايۀ a که پنج عنصر دارد را نشان مي‌دهد. عنصر a[0] حاوي مقدار 17.5 و عنصر a[1] حاوي 19.0 و عنصر a[4] حاوي مقدار 18.0 است. 17.50 1 19.00 2 16.75 3 15.00 4 18.00

2- پردازش‌ آرايه‌ها مثال 1-6 دستيابي مستقيم به عناصر آرايه برنامۀ سادۀ زير يک آرايۀ سه عنصري را تعريف مي‌کند و سپس مقاديري را در آن قرار داده و سرانجام اين مقادير را چاپ مي‌کند: int main() { int a[3]; a[2] = 55; a[0] = 11; a[1] = 33; cout << "a[0] = " << a[0] << endl; cout << "a[1] = " << a[1] << andl; cout << "a[2] = " << a[2] << endl; } آرايه‌ها را مي‌توان مثل متغيرهاي معمولي تعريف و استفاده کرد. با اين تفاوت که آرايه يک متغير مرکب است و براي دستيابي به هر يک از خانه‌هاي آن بايد از ايندکس استفاده نمود. a[0] = 11 a[1] = 33 a[2] = 55

عبارت type نوع عناصر آرايه را مشخص مي‌کند. array_name نام آرايه است . نحو کلي براي اعلان آرايه به شکل زير است: type array_name[array_size]; عبارت type نوع عناصر آرايه را مشخص مي‌کند. array_name نام آرايه است . array_size تعداد عناصر آرايه را نشان مي‌دهد. اين مقدار بايد يک عدد ثابت صحيح باشد و حتما بايد داخل کروشه [] قرار بگيرد.

3- مقداردهي آرايه‌ها float a[] = {22.2,44.4,66.6}; در C++ مي‌توانيم يک آرايه را با استفاده از فهرست مقداردهي، اعلان و مقدارگذاري کنيم: float a[] = {22.2,44.4,66.6}; به اين ترتيب مقادير داخل فهرست به همان ترتيبي که چيده شده‌اند درون عناصر آرايه قرار مي‌گيرند. اندازه آرايه نيز برابر با تعداد عناصر موجود در فهرست خواهد بود. پس همين خط مختصر، آرايه‌اي از نوع float و با نام a و با تعداد سه عنصر اعلان کرده و هر سه عنصر را با مقدارهاي درون فهرست، مقداردهي مي‌کند. a 22.2 1 44.4 2 66.6

int size = sizeof(a)/sizeof(float); for (int i=0; i<size; i++) مثال‌ 3-6 مقداردهي آرايه با استفاده از فهرست مقداردهي برنامۀ زير، آرايۀ a را مقداردهي کرده و سپس مقدار هر عنصر را چاپ مي‌كند: int main() { float a[] = { 22.2, 44.4, 66.6 }; int size = sizeof(a)/sizeof(float); for (int i=0; i<size; i++) cout << "\ta[" << i << "] = " << a[i] << endl; } a[0] = 22.2 a[1] = 44.4 a[2] = 66.6

هنگام استفاده از فهرست مقداردهي براي اعلان آرايه، مي‌توانيم تعداد عناصر آرايه را هم به طور صريح ذکر کنيم. در اين صورت اگر تعداد عناصر ذکر شده از تعداد عناصر موجود در فهرست مقداردهي بيشتر باشد، خانه‌هاي بعدي با مقدار صفر پر مي‌شوند: float a[7] = { 55.5, 66.6, 77.7 }; a 55.5 1 66.6 2 77.7 3 0.0 4 5 6 دقت کنيد که تعداد مقادير موجود در فهرست مقداردهي نبايد از تعداد عناصر آرايه بيشتر باشد: float a[3] = { 22.2, 44.4, 66.6, 88.8 }; // ERROR: too many values!

يك‌ آرايه‌ را مي‌توانيم به طور کامل با صفر مقداردهي اوليه کنيم يك‌ آرايه‌ را مي‌توانيم به طور کامل با صفر مقداردهي اوليه کنيم. براي مثال سه اعلان زير با هم برابرند: float a[ ] = { 0, 0, 0, 0, 0, 0, 0, 0, 0 }; float a[9] = { 0, 0 }; float a[9] = { 0, 0, 0, 0, 0, 0, 0, 0, 0 }; اما مطلب فوق اصلا به اين معني نيست که از فهرست مقداردهي استفاده نشود. درست مثل يک متغير معمولي، اگر يک آرايه مقداردهي اوليه نشود، عناصر آن حاوي مقادير زباله خواهد بود.

مثال‌ 5-6 يك آرايۀ مقداردهي نشده برنامۀ زير، آرايۀ‌ a را اعلان مي‌کند ولي مقداردهي نمي‌كند. با وجود اين، مقادير موجود در آن را چاپ مي‌كند: int main() { const int SIZE=4; // defines the size N for 4 elements float a[SIZE]; // declares the array's elements as float for (int i=0; i<SIZE; i++) cout << "\ta[" << i << "] = " << a[i] << endl; } a[0] = 6.01838e-39 a[1] = 9.36651e-39 a[2] = 6.00363e-39 a[3] = 0

b = a; // ERROR: arrays cannot be assigned! آرايه‌ها را مي‌توان با استفاده از عملگر جايگزيني مقداردهي کرد اما نمي‌توان مقدار آن‌ها را به يکديگر تخصيص داد: float a[7] = { 22.2, 44.4, 66.6 }; float b[7] = { 33.3, 55.5, 77.7 }; b = a; // ERROR: arrays cannot be assigned! همچنين‌ نمي‌توانيم‌ يك‌ آرايه را به طور مستقيم براي‌ مقداردهي به آرايۀ ديگر استفاده كنيم‌: float b[7] = a; // ERROR: arrays cannot be used as nitializers!

4- ايندكس بيرون از حدود آرايه‌ در بعضي از زبان‌هاي برنامه‌نويسي‌، ايندکس آرايه نمي‌تواند از محدودۀ تعريف شده براي آن بيشتر باشد. براي مثال در پاسکال اگر آرايۀ a با تعداد پنج عنصر تعريف شده باشد و آنگاه a[7] دستيابي شود، برنامه از کار مي‌افتد. اين سيستم حفاظتي در C++ وجود ندارد. مثال بعدي نشان مي‌دهد که ايندکس يک آرايه هنگام دستيابي مي‌تواند بيشتر از عناصر تعريف شده براي آن باشد و باز هم بدون اين که خطايي گرفته شود، برنامه ادامه يابد.

for (int i=0; i<7; i++) //ERROR: index is out of bounds! مثال‌ 6-6 تجاوز ايندکس آرايه از محدودۀ تعريف شده براي آن برنامۀ زير يک خطاي زمان اجرا دارد؛ به بخشي از حافظه دستيابي مي‌کند که از محدودۀ آرايه بيرون است: in main() { const int SIZE=4; float a[SIZE} = { 33.3, 44.4, 55.5, 66.6 }; for (int i=0; i<7; i++) //ERROR: index is out of bounds! cout << "\ta[" << i << "] = " << a[i] << endl; } آرايه‌اي که در اين برنامه تعريف شده، چهار عنصر دارد ولي تلاش مي‌شود به هفت عنصر دستيابي شود. سه مقدار آخر واقعا جزو آرايه نيستند و فقط سلول‌هايي از حافظه‌اند که دقيقا بعد از عنصر چهارم آرايه قرار گرفته‌اند. اين سلول‌ها داراي مقدار زباله هستند. a[0] = 33.3 a[1] = 44.4 a[2] = 55.5 a[3] = 66.6 a[4] = 5.60519e-45 a[5] = 6.01888e-39 a[6] = 6.01889e-39

cout << "x = " << x << endl; * مثال‌ 7-6 اثر همسايگي برنامۀ زير از ايندکس خارج از محدوده استفاده مي‌کند و اين باعث مي‌شود که مقدار يک متغير به طور ناخواسته تغيير کند: int main() { const int SIZE=4; float a[] = { 22.2, 44.4, 66.6 }; float x=11.1; cout << "x = " << x << endl; a[3] = 88.8; // ERROR: index is out of bounds! } x = 88.8

متغير x بعد از آرايۀ a اعلان‌ شده، پس يک سلول چهاربايتي بلافاصله بعد از دوازده بايت آرايه به آن تخصيص مي‌يابد. بنابراين وقتي برنامه تلاش مي‌کند مقدار 88.8 را در a[3] قرار دهد (که جزو آرايه نيست) اين مقدار به شکل ناخواسته در x قرار مي‌گيرد. شکل مقابل نشان مي‌دهد چطور اين اتفاق در حافظه رخ مي‌دهد. مثال بعدي نوع ديگري از خطاي زمان اجرا را نشان مي‌دهد: وقتي ايندکس آرايه بيش از حد بزرگ باشد. اين خطا يکي از وحشت‌ناک‌ترين خطاهاي زمان اجراست زيرا ممکن است اصلا نتوانيم منبع خطا را کشف کنيم. حتي ممکن است به اين روش داده‌هاي برنامه‌هاي ديگري که در حال کارند را خراب کنيم و اين باعث ايجاد اختلال در کل سيستم شود. به اين خطا «اثر همسايگي» مي‌گويند. اين وظيفۀ برنامه‌نويس است که تضمين کند ايندکس آرايه هيچ‌گاه از محدودۀ آن خارج نشود. a 0 22.2 1 44.4 2 66.6 x 88.8 22.2 44.4 66.6 88.8

cout << "x = " << x << endl; مثال‌ 8-6 ايجاد استثناي مديريت نشده برنامۀ زير از كار مي‌افتد زيرا ايندكس آرايه خيلي بزرگ است: int main() { const int SIZE=4; float a[] = { 22.2, 44.4, 66.6 }; float x=11.1; cout << "x = " << x << endl; a[3333] =88.8;//ERROR: index is out of bounds! }

وقتي اين برنامه روي رايانه‌اي با سيستم عامل ويندوز اجرا شود، يک صفحۀ هشدار که در شکل نشان داده شده روي صفحه ظاهر مي‌شود. اين پنجره بيان مي‌کند که برنامه تلاش دارد به نشاني 0040108e از حافظه دستيابي کند. اين مکان خارج از حافظۀ تخصيصي است که براي اين برنامه منظور شده، بنابراين سيستم عامل برنامه را متوقف مي‌کند.

پردازش‌گر استثنا خطايي که در مثال 8-6 بيان شده يک «استثناي مديريت نشده» ناميده مي‌شود زيرا کدي وجود ندارد که به اين استثنا پاسخ دهد. در C++ مي‌توانيم کدهايي به برنامه اضافه کنيم که هنگام رخ دادن حالت‌هاي استثنا، از توقف برنامه جلوگيري کند. به اين کدها «پردازش‌گر استثنا» مي‌گويند.

5- ارسال آرايه به تابع‌ كد float a[]; كه آرايه a را اعلان مي‌كند دو چيز را به كامپايلر مي‌گويد: 1- اين که نام آرايه a است 2- عناصر آرايه از نوع float هستند. سمبل a نشاني حافظۀ آرايه را ذخيره مي‌کند. لازم نيست تعداد عناصر آرايه به کامپايلر گفته شود زيرا از روي نشاني موجود در a مي‌توان عناصر را بازيابي نمود. به همين طريق مي‌توان يک آرايه را به تابع ارسال کرد. يعني فقط نوع آرايه و نشاني حافظۀ آن به عنوان پارامتر به تابع فرستاده مي‌شود.

مثال‌ 9-6 ارسال آرايه به تابعي كه مجموع عناصر آرايه را برمي‌گرداند int sum(int[],int); int main() { int a[] = { 11, 33, 55, 77 }; int size = sizeof(a)/sizeof(int); cout << "sum(a,size) = " << sum(a,size) << endl;} int sum(int a[], int n) { int sum=0; for (int i=0; i<n; i++) sum += a[i]; return sum; } فهرست پارامتر تابع فوق به شکل (int a[], int n) است‌ به اين معنا که اين تابع يک آرايه از نوع int و يک متغير از نوع int دريافت مي‌کند. به اعلان اين تابع در بالاي تابع main() نگاه کنيد. نام پارامترها حذف شده است.

هنگام فراخواني تابع نيز از عبارت sum(a,size) استفاده شده که فقط نام آرايه به تابع ارسال شده. تابع از اين نشاني براي دستيابي به عناصر آرايه استفاده مي‌کند. همچنين تابع مي‌تواند با استفاده از اين نشاني، محتويات عناصر آرايه را دست‌کاري کند. پس ارسال آرايه به تابع شبيه ارسال متغير به طريق ارجاع است. به مثال بعدي دقت کنيد.

مثال‌ 10-6 توابع ورودي و خروجي براي يک آرايه در اين برنامه از تابع read() استفاده مي‌شود تا مقاديري به داخل آرايه وارد شود. سپس با استفاده از تابع print() مقادير داخل آرايه چاپ مي‌شوند: void read(int[],int&;) void print(int[],int); int main() { const int MAXSIZE=100; int a[MAXSIZE]={0}, size; read(a,size); cout << "The array has " << size << " elements: "; print(a,size); } Enter integers. Terminate with 0: a[0]: 11 a[1]: 22 a[2]: 33 a[3]: 44 a[4]: 0 The array has 4 elements: 11 22 33 44

void read(int a[], int& n) { cout << "Enter integers. Terminate with 0:\n"; n = 0; do { cout << "a[" << n << "]: "; cin >> a[n]; { while (a[n++] !=0 && n < MAXSIZE); --n; // don't count the 0 }

void print(int a[], int n) { for (int i=0; i<n; i++) cout << a[i] << " "; } چون n يك متغير است، براي اين که تابع read() بتواند مقدار آن را تغيير دهد اين متغير بايد به شکل ارجاع ارسال شود. همچنين براي اين که تابع مذکور بتواند مقادير داخل آرايه a را تغيير دهد، آرايه نيز بايد به طريق ارجاع ارسال شود، اما ارجاع آرايه‌ها کمي متفاوت است.

1 – آدرس اولين خانۀ آرايه 2 – تعداد عناصر آرايه 3 – نوع عناصر آرايه در C++ توابع قادر نيستند تعداد عناصر آرايۀ ارسالي را تشخيص دهند. بنابراين به منظور ارسال آرايه‌ها به تابع از سه مشخصه استفاده مي‌شود: 1 – آدرس اولين خانۀ آرايه 2 – تعداد عناصر آرايه 3 – نوع عناصر آرايه تابع با استفاده از اين سه عنصر مي‌تواند به تک تک اعضاي آرايه دستيابي کند.

آدرس اولين خانۀ آرايه، همان نام آرايه است. پس وقتي نام آرايه را به تابع بفرستيم آدرس اولين خانه را به تابع فرستاده‌ايم. نوع آرايه نيز در تعريف تابع اعلان مي‌شود. بنابراين با اين دو مقدار، تابع مي‌تواند به آرايه دسترسي داشته باشد.

مثال‌ 11-6 آدرس اولين خانۀ آرايه و مقدار درون آن برنامۀ زير، آدرس ذخيره شده در نام آرايه و مقدار موجود در آن خانه را چاپ مي‌کند: int main() { int a[] = { 22, 44, 66, 88 }; cout << "a = " << a << endl; // the address of a[0] cout << "a[0] = " << a[0]; // the value of a[0] } a = 0x0064fdec a[0] = 22 اين برنامه تلاش مي‌کند که به طور مستقيم مقدار a را چاپ کند. نتيجۀ چاپ a اين است که يک آدرس به شکل شانزده دهي چاپ مي‌شود. اين همان آدرس اولين خانۀ آرايه است. يعني درون نام a آدرس اولين عنصر آرايه قرار گرفته. خروجي نيز نشان مي‌دهد که a آدرس اولين عنصر را دارد و a[0] مقدار اولين عنصر را.

6- الگوريتم جستجوي خطي آرايه‌ها بيشتر براي پردازش يک زنجيره از داده‌ها به کار مي‌روند. اغلب لازم است که بررسي شود آيا يک مقدار خاص درون يک آرايه موجود است يا خير. ساده‌ترين راه اين است که از اولين عنصر آرايه شروع کنيم و يکي يکي همۀ عناصر آرايه را جستجو نماييم تا بفهميم که مقدار مورد نظر در کدام عنصر قرار گرفته. به اين روش «جستجوي خطي» مي‌گويند.

مثال‌ 12-6 جستجوي خطي برنامۀ زير تابعي را آزمايش مي‌کند که در اين تابع از روش جستجوي خطي براي يافتن يک مقدار خاص استفاده شده: int index(int,int[],int); int main() { int a[] = { 22, 44, 66, 88, 44, 66, 55}; cout << "index(44,a,7) = " << index(44,a,7) << endl; cout << "index(50,a,7) = " << index(50,a,7) << endl; } int index(int x, int a[], int n) { for (int i=0; i<n; i++) if (a[i] == x) return i; return n; // x not found index(44,a,7) = 1 index(40,a,7) = 7

تابع index() سه پارامتر دارد: پارامتر a آرايه‌اي است که بايد در آن جستجو صورت گيرد و پارامتر n هم ايندکس عنصري است که مقدار مورد نظر در آن پيدا شده است. در اين تابع با استفاده از حلقۀ for عناصر آرايه a پيمايش شده و مقدار هر عنصر با x مقايسه مي‌شود. اگر اين مقدار با x برابر باشد، ايندکس آن عنصر بازگردانده شده و تابع خاتمه مي‌يابد.

اگر مقدار x در هيچ يک از عناصر آرايه موجود نباشد، مقداري خارج از ايندکس آرايه بازگردانده مي‌شود که به اين معناست که مقدار x در آرايۀ a موجود نيست. در اولين اجراي آزمايشي، مشخص شده که مقدار 44 در a[1] واقع است و در اجراي آزمايشي دوم مشخص شده که مقدار 40 در آرايۀ a موجود نيست (يعني مقدار 44 در a[7] واقع است و از آن‌جا که آرايۀ a فقط تا a[6] عنصر دارد، مقدار 7 نشان مي‌دهد که 40 در آرايه موجود نيست).

7- مرتب‌سازي حبابي «مرتب‌سازي حبابي» يکي از ساده‌ترين الگوريتم‌هاي مرتب‌سازي است. در اين روش، آرايه چندين مرتبه پويش مي‌شود و در هر مرتبه بزرگ‌ترين عنصر موجود به سمت بالا هدايت مي‌شود و سپس محدودۀ مرتب‌سازي براي مرتبۀ بعدي يکي کاسته مي‌شود. در پايان همۀ پويش‌ها، آرايه مرتب شده است.

اولين عنصر آرايه با عنصر دوم مقايسه مي‌شود. طريقۀ يافتن بزرگ‌ترين عنصر و انتقال آن به بالاي عناصر ديگر به اين شکل است اولين عنصر آرايه با عنصر دوم مقايسه مي‌شود. اگر عنصر اول بزرگ‌تر بود، جاي اين دو با هم عوض مي‌شود. سپس عنصر دوم با عنصر سوم مقايسه مي‌شود. اگر عنصر دوم بزرگ‌تر بود، جاي اين دو با هم عوض مي‌شود و به همين ترتيب مقايسه و جابجايي زوج‌هاي همسايه ادامه مي‌يابد تا وقتي به انتهاي آرايه رسيديم، بزرگ‌ترين عضو آرايه در خانۀ انتهايي قرار خواهد گرفت. در اين حالت محدودۀ جستجو يکي کاسته مي‌شود و دوباره زوج‌هاي کناري يکي يکي مقايسه مي‌شوند تا عدد بزرگ‌تر بعدي به مکان بالاي محدوده منتقل شود. اين پويش ادامه مي‌يابد تا اين که وقتي محدوده جستجو به عنصر اول محدود شد، آرايه مرتب شده است.

مثال‌ 13-6 مرتب‌سازي برنامۀ زير تابعي را آزمايش مي‌کند که اين تابع با استفاده از مرتب‌سازي حبابي يک آرايه را مرتب مي‌نمايد: void print(float[],int); void sort(float[],int); int main() {float a[]={55.5,22.2,99.9,66.6,44.4,88.8,33.3, 77.7}; print(a,8); sort(a,8); } 55.5, 22.2, 99.9, 66.6, 44.4, 88.8, 33.3, 77.7 22.2, 33.3, 44.4, 55.5, 66.6, 77.7, 88.8, 99.9

void sort(float a[], int n) { // bubble sort: for (int i=1; i<n; i++) // bubble up max{a[0..n-i]}: for (int j=0; j<n-i; j++) if (a[j] > a[j+1]) swap (a[j],a[j+1]); //INVARIANT: a[n-1-i..n-1] is sorted }

تابع sort() از دو حلقۀ تودرتو استفاده مي‌كند. 1- حلقه for داخلي زوج‌هاي همسايه را با هم مقايسه مي‌كند و اگر آن‌ها خارج از ترتيب باشند، جاي آن دو را با هم عوض مي‌کند. وقتي for داخلي به پايان رسيد، بزرگ‌ترين عنصر موجود در محدودۀ فعلي به انتهاي آن هدايت شده است. 2-سپس حلقۀ for بيروني محدودۀ جستجو را يکي کم مي‌کند و دوباره for داخلي را راه مي‌اندازد تا بزرگ‌ترين عنصر بعدي به سمت بالاي آرايه هدايت شود.

8- الگوريتم جستجوي دودويي در روش جستجوي دودويي به يک آرايۀ مرتب نياز است. هنگام جستجو آرايه از وسط به دو بخش بالايي و پاييني تقسيم مي‌شود. مقدار مورد جستجو با آخرين عنصر بخش پاييني مقايسه مي‌شود. اگر اين عنصر کوچک‌تر از مقدار جستجو بود، مورد جستجو در بخش پاييني وجود ندارد و بايد در بخش بالايي به دنبال آن گشت.

دوباره بخش بالايي به دو بخش تقسيم مي‌گردد و گام‌هاي بالا تکرار مي‌شود. سرانجام محدودۀ جستجو به يک عنصر محدود مي‌شود که يا آن عنصر با مورد جستجو برابر است و عنصر مذکور يافت شده و يا اين که آن عنصر با مورد جستجو برابر نيست و لذا مورد جستجو در آرايه وجود ندارد. اين روش پيچيده‌تر از روش جستجوي خطي است اما در عوض بسيار سريع‌تر به جواب مي‌رسيم.

int index(int, int[],int); int main() مثال‌ 14-6 جستجوي دودويي برنامۀ آزمون زير با برنامۀ آزمون مثال 12-6 يکي است اما تابعي که در زير آمده از روش جستجوي دودويي براي يافتن مقدار درون آرايه استفاده مي‌کند: int index(int, int[],int); int main() { int a[] = { 22, 33, 44, 55, 66, 77, 88 }; cout << "index(44,a,7) = " << index(44,a,7) << endl; cout << "index(60,a,7) = " << index(60,a,7) << endl; }

int index(int x, int a[], int n) { // PRECONDITION: a[0] <= a[1] <= ... <= a[n-1]; // binary search: int lo=0, hi=n-1, i; while (lo <= hi) { i = (lo + hi)/2; // the average of lo and hi if (a[i] == x) return i; if (a[i] < x) lo = i+1; // continue search in a[i+1..hi] else hi = i-1; // continue search in a[0..i-1] } return n; // x was not found in a[0..n-1] index(44,a,7) = 2 index(60,a,7) = 7

براي اين که بفهميم تابع چطور کار مي‌کند، فراخواني index(44,a,7) را دنبال مي‌کنيم. وقتي حلقه شروع مي‌شود، x=44 و n=7 و lo=0 و hi=6 است. ابتدا i مقدار (0+6)/2 = 3 را مي‌گيرد.پس عنصر a[i] عنصر وسط آرايۀ a[0..6] است. مقدار a[3] برابر با 55 است که از مقدار x بزرگ‌تر است. پس x در نيمۀ بالايي نيست و جستجو در نيمۀ پاييني ادامه مي‌يابد. لذا hi با i-1 يعني 2 مقداردهي مي‌شود و حلقه تکرار مي‌گردد.

حالا hi=2 و lo=0 است و دوباره عنصر وسط آرايۀ a[0 حالا hi=2 و lo=0 است و دوباره عنصر وسط آرايۀ a[0..2] يعني a[1] با x مقايسه مي‌شود. a[1] برابر با 33 است که کوچک‌تر از x مي‌باشد. پس اين دفعه lo برابر با i+1 يعني 2 مي‌شود. در سومين دور حلقه، hi=2 و lo=2 است. پس عنصر وسط آرايۀ a[2..2] که همان a[2] است با x مقايسه مي‌شود. a[2] برابر با 44 است که با x برابر است. پس مقدار 2 بازگشت داده مي‌شود؛ يعني x مورد نظر در a[2] وجود دارد.

lo hi i a[i] ?? x 6 3 55 > 44 2 1 33 < ==

حال فراخواني index(60,a,7) را دنبال مي‌کنيم حال فراخواني index(60,a,7) را دنبال مي‌کنيم. وقتي حلقه شروع مي‌شود، x=60 و n=7 و lo=0 و hi=6 است. عنصر وسط آرايۀ a[0..6] عنصر a[3]=55 است که از x کوچک‌تر است. پس lo برابر با i+1=4 مي‌شود و حلقه دوباره تکرار مي‌شود. اين دفعه hi=6 و lo=4 است . عنصر وسط آرايۀ a[4..6] عنصر a[5]=77 است که بزرگ‌تر از x مي‌باشد. پس hi به i-1=4 تغيير مي‌يابد و دوباره حلقه تکرار مي‌شود. اين بار hi=4 و lo=4 است و عنصر وسط آرايۀ a[4..4] عنصر a[4]=66 است که بزرگ‌تر از x مي‌باشد. لذا hi به i-1=3 کاهش مي‌يابد.

lo hi i a[i] ?? x 6 3 55 < 60 4 5 77 > 66 اکنون شرط حلقه غلط مي‌شود زيرا hi<lo است. بنابراين تابع مقدار 7 را برمي‌گرداند يعني عنصر مورد نظر در آرايه موجود نيست.

در تابع فوق هر بار که حلقه تکرار مي‌شود، محدودۀ جستجو 50% کوچک‌تر مي‌شود. در آرايۀ n عنصري، روش جستجوي دودويي حداکثر به مقايسه نياز دارد تا به پاسخ برسد. حال آن که در روش جستجوي خطي به n مقايسه نياز است.

تفاوتهاي جستجوي دودويي و خطي جستجوي دودويي سريع‌تر از جستجوي خطي است. دومين تفاوت در اين است که اگر چند عنصر داراي مقادير يکساني باشند، آنگاه جستجوي خطي هميشه کوچک‌ترين ايندکس را برمي‌گرداند ولي در مورد جستجوي دودويي نمي‌توان گفت که کدام ايندکس بازگردانده مي‌شود. سومين فرق در اين است که جستجوي دودويي فقط روي آرايه‌هاي مرتب کارايي دارد و اگر آرايه‌اي مرتب نباشد، جستجوي دودويي پاسخ غلط مي‌دهد ولي جستجوي خطي هميشه پاسخ صحيح خواهد داد.

int main() { int a[] = { 22, 44, 66, 88, 44, 66, 55 }; } * مثال‌ 15-6 مشخص كردن اين كه آيا آرايه مرتب است يا خير برنامۀ زير يک تابع بولي را آزمايش مي‌کند. اين تابع مشخص مي‌نمايد که آيا آرايۀ داده شده غير نزولي است يا خير: bool isNondecreasing(int a[], int n); int main() { int a[] = { 22, 44, 66, 88, 44, 66, 55 }; cout<<"isNondecreasing(a,4) = " << isNondecreasing(a,4)<< endl; cout<<"isNondecreasing(a,7) = " << isNondecreasing(a,7) << endl; }

bool isNondecreasing(int a[], int n) { // returns true iff a[0] <= a[1] <= ... <= a[n-1]: for (int i=1; i<n; i++) if (a[i]<a[i-1]) return false; return true; } isNondecreasing(a,4) = 1 isNondecreasing(a,7) = 0

اين تابع يک بار کل آرايه را پيمايش کرده و زوج‌هاي a[i-1] و a[i] را مقايسه مي‌کند. اگر زوجي يافت شود که در آن a[i]<a[i-1] باشد، مقدار false را بر مي‌گرداند به اين معني که آرايه مرتب نيست. ببينيد که مقادير true و false به شکل اعداد 1 و 0 در خروجي چاپ مي‌شوند زيرا مقادير بولي در حقيقت به شکل اعداد صحيح در حافظه ذخيره مي‌شوند.

اگر پيش‌شرط مثال 14-6 يعني مرتب بودن آرايه رعايت نشود، جستجوي دودويي پاسخ درستي نمي‌دهد. به اين منظور ابتدا بايد اين پيش‌شرط بررسي شود. با استفاده از تابع assert() مي‌توان اجراي يک برنامه را به يک شرط وابسته کرد. اين تابع يک آرگومان بولي مي‌پذيرد. اگر مقدار آرگومان false باشد، برنامه را خاتمه داده و موضوع را به سيستم عامل گزارش مي‌کند. اگر مقدار آرگومان true باشد، برنامه بدون تغيير ادامه مي‌يابد. تابع asset() در سرفايل <cassert> تعريف شده است.

مثال‌ 16-6 استفاده از تابع assert() براي رعايت كردن يك‌ پيش‌شرط برنامۀ زير نسخۀ بهبوديافته‌اي از تابع search() مثال 14-6 را آزمايش مي‌کند. در اين نسخه، از تابع isNonDecreasing() مثال 15-6 استفاده شده تا مشخص شود آرايه مرتب است يا خير. نتيجه اين تابع به تابع assert() ارسال مي‌گردد تا اگر آرايه مرتب نباشد برنامه به بيراهه نرود.

int main() using namespace std; int index(int x, int a[], int n); #include <cassert> // defines the assert() function #include <iostream> // defines the cout object using namespace std; int index(int x, int a[], int n); int main() { int a[] = { 22, 33, 44, 55, 66, 77, 88, 60 }; cout<<"index(44,a,7) = " << index(44,a,7) << endl; cout<<"index(44,a,8) = " << index(44,a,8) << endl; cout<<"index(60,a,8) = " << index(60,a,8) << endl; }

bool isNondecreasing(int a[], int n); int index(int x, int a[], int n) { assert(isNondecreasing(a,n)); int lo=0, hi=n-1, i; while (lo <= hi) { i = (lo + hi)/2; if (a[i] == x) return i; if (a[i] < x) lo = i+1; else hi = i-1; } return n; ‌‌} index(44,a,7) = 2

آرايۀ a[] که در اين برنامه استفاده شده كاملا مرتب‌ نيست‌ اما هفت‌ عنصر اول‌ آن‌ مرتب‌ است. بنابراين‌ در فراخواني‌index(44,a,7) تابع بولي مقدار true را به assert() ارسال مي‌کند و برنامه ادمه مي‌يابد. اما در دومين فراخواني index(44,a,8) باعث مي‌شود که تابع ‌isNondecreasing() مقدار false را به تابع assert() ارسال کند كه در اين صورت برنامه متوقف مي‌شود و ويندوز پنجرۀ هشدار مقابل را نمايش مي‌دهد.

با استفاده از انواع شمارشي نيز مي‌توان آرايه‌ها را پردازش نمود. 9- استفاده از انواع شمارشي در آرايه انواع‌ شمارشي‌ در جلسه‌ دوم‌ توضيح‌ داده‌ شده‌اند. با استفاده از انواع شمارشي نيز مي‌توان آرايه‌ها را پردازش نمود. مثال‌ 17-7 شمارش با استفاده از روزهاي هفته اين‌ برنامه‌ يك‌ آرايه به نام‌ high[] با هفت‌ عنصرازنوعfloat تعريف‌ مي‌كند كه‌ هر عنصر حداکثر دما در يک روز هفته را نشان مي‌دهد: int main() { enum Day { SUN, MON, TUE, WED, THU, FRI, SAT }; float high[SAT+1] = {28.6, 29.1, 29.9, 31.3, 30.4, 32.0, 30.7}; for (int day = SUN; day <= SAT; day++) cout << "The high temperature for day " << day << " was "<< high[day] << endl; } The high temperature for day 0 was 28.6 The high temperature for day 1 was 29.1 The high temperature for day 2 was 29.9 The high temperature for day 3 was 31.3 The high temperature for day 4 was 30.4 The high temperature for day 5 was 32.0 The high temperature for day 6 was 30.7

for (int day = SUN; day <= SAT; day++) به خاطر بياوريد که انواع شمارشي به شکل مقادير عددي ذخيره مي‌شوند. اندازۀ آرايه، SAT+1 است زيرا SAT مقدار صحيح 6 را دارد و آرايه به هفت عنصر نيازمند است. متغير day از نوع int است‌ پس مي‌توان مقادير Day را به آن تخصيص داد. استفاده از انواع شمارشي در برخي از برنامه‌ها باعث مي‌شود که کد برنامه «خود استناد» شود. مثلا در مثال 17-6 کنترل حلقه به شکل for (int day = SUN; day <= SAT; day++) باعث مي‌شود که هر بيننده‌اي حلقۀ for بالا را به خوبي درک کند.

10- تعريف‌ انواع‌ انواع شمارشي يكي از راه‌هايي است که کاربر مي‌تواند نوع ساخت خودش را تعريف کند. براي مثال دستور زير : enum Color{ RED,ORANGE,YELLOW, GREEN, BLUE, VIOLET }; يک نوع جديد به نام Color تعريف مي‌کند که متغيرهايي از اين نوع مي‌توانند مقادير RED يا ORANGE يا YELLOW يا GREEN يا BLUE يا VIOLET را داشته باشند. پس با استفاده از اين نوع مي‌توان متغيرهايي به شکل زير تعريف نمود: Color shirt = BLUE; Color car[] = { GREEN, RED, BLUE, RED }; Floatwavelength[VIOLET+1]={420,480,530,570,600,620}; در اين‌جا shirt متغيري‌ از نوع Color است و با مقدار BLUE مقداردهي‌ شده. car يک آرايۀ چهار عنصري است و مقدار عناصر آن به ترتيب GREEN و RED و BLUE و RED مي‌باشد. همچنين wavelength آرايه‌اي از نوع float است که داراي VIOLET+1 عنصر يعني 5+1=6 عنصر است.

در C++ مي‌توان نام انواع استاندارد را تغيير داد. کلمۀ کليدي typedef يک نام مستعار براي يک نوع استاندارد موجود تعريف مي‌کند. نحو استفاده از آن به شکل زير است: typedef type alias; كه type يک نوع استاندارد و alias نام مستعار براي آن است‌.

typedef element-type alias[]; براي‌ مثال‌ کساني که با پاسکال برنامه مي‌نويسند به جاي نوع long از عبارت Integer استفاده مي‌کنند و به جاي نوع double از عبارت Real استفاده مي‌نمايند. اين افراد مي‌توانند به شکل زير از نام مستعار استفاده کنند: typedef long Integer; typedef double Real; و پس از آن کدهاي زير معتبر خواهند بود: Integer n = 22; const Real PI = 3.141592653589793; Integer frequency[64]; اگر دستور typedef را به شکل زير بکار ببريم مي‌توانيم آرايه‌ها را بدون علامت براکت تعريف کنيم: typedef element-type alias[]; مثل تعريف زير : typedef float sequence[]; سپس مي‌توانيم آرايۀ a را به شکل زير اعلان کنيم: sequence a = {55.5, 22.2, 99.9};

دستور typedef نوع جديدي را اعلان نمي‌کند، بلکه فقط به يک نوع موجود نام مستعاري را نسبت مي‌دهد. برنامۀ زير همان‌ برنامۀ‌ مثال 13-6 است‌ با اين فرق که از typedef استفاده شده تا بتوان از نام مستعار sequrnce به عنوان يک نوع استفاده کرد. سپس اين نوع در فهرست پارامترها و اعلان a در تابع main() به کار رفته است:

typedef float Sequence[]; void sort(Sequence,int); void print(Sequence,int); int main() { Sequence a = {55.5, 22.2, 99.9, 66.6, 44.4, 88.8, 33.3, 77.7}; print(a,8); sort(a,8); }

void sort(Sequence a, int n) { for (int i=n-1; i>0; i--) for (int j=0; j<i; j++) if (a[j] > a[j+1]) swap(a[j],a[j+1]); } دوباره به دستور typedef نگاه کنيد: typedef float Seguence[]; علامت براكت‌ها [] نشان مي‌دهند که هر چيزي که از نوع Sequence تعريف شود، يک آرايه است و عبارت float نيز بيان مي‌کند که اين آرايه از نوع float است.

يک آرايۀ سه بعدي آرايه‌اي است که هر خانه از آن يک آرايۀ دو بعدي باشد. 11- آرايه‌هاي چند بعدي همۀ آرايه‌هايي كه تاکنون تعريف کرديم، يک بعدي هستند، خطي هستند، رشته‌اي هستند. مي‌توانيم آرايه‌اي تعريف کنيم که از نوع آرايه باشد، يعني هر خانه از آن آرايه، خود يک آرايه باشد. به اين قبيل آرايه‌ها، آرايه‌هاي چندبعدي مي‌گوييم. يک آرايۀ دو بعدي آرايه‌اي است که هر خانه از آن، خود يک آرايۀ يک بعدي باشد. يک آرايۀ سه بعدي آرايه‌اي است که هر خانه از آن يک آرايۀ دو بعدي باشد.

مقدار 99 را در عنصري قرار مي‌دهد که ايندکس آن عنصر(1,2,3) است. شکل دستيابي به عناصر در آرايه‌هاي چند بعدي مانند آرايه‌هاي يک بعدي است. مثلا دستور a[1][2][3] = 99; مقدار 99 را در عنصري قرار مي‌دهد که ايندکس آن عنصر(1,2,3) است. دستور int a[5]; آرايه‌اي با پنج عنصر از نوع int تعريف مي‌کند. اين يک آرايۀ يک بعدي است. دستور int a[3][5]; آرايه‌اي با سه عنصر تعريف مي‌کند که هر عنصر، خود يک آرايۀ پنج عنصري از نوع int است. اين يک آرايۀ دو بعدي است که در مجموع پانزده عضو دارد. دستور int a[2][3][5]; آرايه‌اي با دو عنصر تعريف مي‌کند که هر عنصر، سه آرايه است که هر آرايه پنج عضو از نوع int دارد. اين يک آرايۀ سه بعدي است که در مجموع سي عضو دارد. آرايه‌هاي چند بعدي مثل آرايه‌هاي يک بعدي به توابع فرستاده مي‌شوند با اين تفاوت که هنگام اعلان و تعريف تابع مربوطه، بايد تعداد عناصر بُعد دوم تا بُعد آخر حتما ذکر شود.

void print(int a[][5]); int main() { int a[3][5]; read(a); print(a); } مثال‌ 19-6 نوشتن و خواندن يك آرايۀ دو بعدي برنامۀ زير نشان مي‌دهد که يک آرايۀ دوبعدي چگونه پردازش مي‌شود: void read(int a[][5]); void print(int a[][5]); int main() { int a[3][5]; read(a); print(a); }

void read(int a[][5]) { cout << "Enter 15 integers, 5 per row:\n"; for (int i=0; i<3; i++) { cout << "ROW " << i << ": "; for (int j=0; j<5; j++) cin >> a[i][j]; }

void print(const int a[][5]) { for (int i=0; i<3; i++) { for (int j=0; j<5; j++) cout << " " << a[i][j]; cout << endl; }

Enter 15 integers, 5 per row: 44 77 33 11 44 60 50 30 90 70 65 25 45 45 55 دقت کنيد که در فهرست پارامترهاي توابع بالا، بعد اول نامشخص است اما بعد دوم مشخص شده. علت هم اين است که آرايۀ دو بعدي a[][] در حقيقت آرايه‌اي يک‌بعدي از سه آرايۀ پنج عنصري است. کامپايلر نياز ندارد بداند که چه تعداد از اين آرايه‌هاي پنج عنصري موجود است، اما بايد بداند که آن‌ها پنج عنصري هستند.

void printQuizAverages(Score); void printClassAverages(Score); وقتي يک آرايۀ چند بعدي به تابع ارسال مي‌شود، بُعد اول مشخص نيست اما همۀ ابعاد ديگر بايد مشخص باشند. مثال‌ 20-6 پردازش يك آرايۀ دوبعدي از نمرات امتحاني const NUM_STUDENTS = 3; const NUM_QUIZZES = 5; typedef int Score[NUM_STUDENTS][NUM_QUIZZES]; void read(Score); void printQuizAverages(Score); void printClassAverages(Score);

int main() { Score score; cout << "Enter " << NUM_QUIZZES << " quiz scores for each student:\n"; read(score); cout << "The quiz averages are:\n"; printQuizAverages(score); cout << "The class averages are:\n"; printClassAverages(score);}

void read(Score score) { for (int s=0; s<NUM_STUDENTS; s++) { cout << "Student " << s << ": "; for(int q=0; q<NUM_QUIZZES; q++) cin >> score[s][q]; }

void printQuizAverages(Score score) { for (int s=0; s<NUM_STUDENTS; s++) { float sum = 0.0; for (int q=0; q<NUM_QUIZZES; q++) sum += score[s][q]; cout << "\tStudent " << s << ": " << sum/NUM_QUIZZES << endl; }}

void printClassAverages(Score score) { for (int q=0; q<NUM_QUIZZES; q++) { float sum = 0.0; for (int s=0; s<NUM_STUDENTS; s++) sum += score[s][q]; cout << "\tQuiz " << q << ": " << sum/NUM_STUDENTS << endl; }

Enter 5 quiz scores for each student: The quize averages are: student 0: 8.2 student 1: 8.8 student 2: 7 The class averages are: Quiz 0: 7.33333 Quiz 1: 7.33333 Quiz 2: 8.33333 Quiz 3: 8.33333 Quiz 4: 8.66667 در برنامۀ فوق با استفاده از دستور typedef براي آرايه‌هاي دوبعدي 3*5 نام مستعار Score انتخاب شده. اين باعث مي‌شود که توابع خواناتر باشند. هر تابع از دو حلقۀ for تودرتو استفاده کرده که حلقۀ بيروني، بعد اول را پيمايش مي‌کند و حلقۀ دروني بعد دوم را پيمايش مي نمايد. تابع printQuizAverages() ميانگين‌ هر سطر از نمرات را محاسبه و چاپ مي‌نمايد و تابع printClassAverages() ميانگين هر ستون از نمره‌ها را چاپ مي‌كند.

cout << "This array has " << numZeros(a,2,4,3) مثال‌ 21-6 پردازش يك آرايۀ سه بعدي اين‌ برنامه‌ تعداد صفرها را در يك آرايۀ سه بعدي مي‌شمارد: int numZeros(int a[][4][3], int n1, int n2, int n3); int main() { int a[2][4][3]={{{5,0,2}, {0,0,9},{4,1,0},{7,7,7} }, { {3,0,0}, {8,5,0}, {0,0,0}, {2,0,9} } }; cout << "This array has " << numZeros(a,2,4,3) << " zeros:\n"; }

int numZeros(int a[][4][3], int n1, int n2, int n3) { int count = 0; for (int i = 0; i < n1; i++) for (int j = 0; j < n2; j++) for (int k = 0; k < n3; k++) if (a[i][j][k] == 0) ++count; return count; } This array has 11 zeros:

توجه‌ كنيد كه آرايه چگونه مقداردهي شده است توجه‌ كنيد كه آرايه چگونه مقداردهي شده است. اين قالب مقداردهي به خوبي نمايان مي‌کند که آرايۀ مذکور يک آرايه دو عنصري است که هر عنصر، خود يک آرايۀ چهار عضوي است که هر عضو شامل آرايه‌اي سه عنصري مي‌باشد. پس اين آرايه در مجموع 24 عنصر دارد. آرايۀ مذکور را به شکل زير نيز مي‌توانيم مقداردهي کنيم: int a[2][4][3]={5,0,2,0,0,9,4,1,0,7,7,7,3,0,0,8,5,0,0,0,0,2,0,9}; و يا مانند اين‌: int a[2][4][3] = {{5,0,2,0,0,9,4,1,0,7,7,7},{3,0,0,8,5,0,0,0,0,2,0,9}}; هر سۀ اين قالب‌ها براي کامپايلر يک مفهوم را دارند اما با نگاه کردن به دو قالب اخير به سختي مي‌توان فهميد که کدام عنصر از آرايه، کدام مقدار را خواهد داشت.

پايان جلسه ششم

«اشاره‌گرها و ارجاع‌ها » جلسه هفتم «اشاره‌گرها و ارجاع‌ها »

آنچه در اين جلسه مي خوانيد: 1- عملگر ارجاع 2- ارجاع‌ها 3- اشاره‌گرها 4- مقداريابي 5- چپ مقدارها، راست مقداره 6- بازگشت از نوع ارجاع 7- آرايه‌ها و اشاره‌گرها ›››

8- عملگر new 9- عملگر delete 10- آرايه‌هاي‌ پويا 11- اشاره‌گر ثابت 12- آرايه‌اي‌ از اشاره‌گرها 13- اشاره‌گري به اشاره‌گر ديگر 14- اشاره‌گر به توابع 15- NUL و NULL

»»» هدف کلي: آشنايي با اشاره‌گرها و نحوۀ کار با آدرس‌هاي حافظه هدف‌هاي رفتاري: انتظار مي‌رود پس از پايان اين جلسه بتوانيد: - «ارجاع» را تعريف کنيد و با استفاده از عملگر ارجاع به متغيرها دستيابي کنيد. - «اشاره‌گر» را بشناسيد و بتوانيد اشاره‌گرهايي به انواع مختلف ايجاد کرده و آن‌ها را مقداريابي کنيد. »»»

- «چپ‌مقدارها» و «راست‌مقدارها» را تعريف کرده و آن‌ها را از يکديگر تميز دهيد. - بازگشت‌هايي از نوع ارجاع ايجاد نماييد. - طريقۀ استفاده از عملگرهاي new و delete و وظيفۀ هر يک را بدانيد. - «آرايه‌هاي پويا» را تعريف کرده و مزيت آن‌ها را نسبت به آرايه‌هاي ايستا ذکر کنيد. - آرايه‌هاي پويا را در برنامه‌هايتان ايجاد کرده و مديريت نماييد. - تفاوت بين NUL و NULL را توضيح دهيد.

1- مقدمه حافظۀ رايانه را مي‌توان به صورت يک آرايۀ بزرگ در نظر گرفت. براي مثال رايانه‌اي با 256 مگابايت RAM در حقيقت حاوي آرايه‌اي به اندازۀ 268،435،456 (=228) خانه است که اندازۀ هر خانه يک بايت است. اين خانه‌ها داراي ايندکس صفر تا 268،435،455 هستند. به ايندکس هر بايت، آدرس حافظۀ آن مي‌گويند.

آدرس‌هاي حافظه را با اعداد شانزده‌دهي نشان مي‌دهند آدرس‌هاي حافظه را با اعداد شانزده‌دهي نشان مي‌دهند. پس رايانۀ مذکور داراي محدوده آدرس 0x00000000 تا 0x0fffffff مي‌باشد. هر وقت که متغيري را اعلان مي‌کنيم، سه ويژگي اساسي به آن متغير نسبت داده مي‌شود: «نوع متغير» و «نام متغير» و «آدرس حافظه» آن. مثلا اعلان int n; نوع int و نام n و آدرس چند خانه از حافظه که مقدار n در آن قرار مي‌گيرد را به يکديگر مرتبط مي‌سازد. فرض کنيد آدرس اين متغير 0x0050cdc0 است. بنابراين مي‌توانيم n را مانند شکل مقابل مجسم کنيم:

0x0050cdc0 n int خود متغير به شکل جعبه نمايش داده شده. نام متغير، n، در بالاي جعبه است و آدرس متغير در سمت چپ جعبه و نوع متغير، int، در زير جعبه نشان داده شده. در بيشتر رايانه‌ها نوع int چهار بايت از حافظه را اشغال مي‌نمايد. بنابراين همان طور که در شکل مقابل نشان داده شده است، متغير n يک بلوک چهاربايتي از حافظه را اشغال مي‌کند که شامل بايت‌هاي 0x0050cdc0 تا 0x0050cdc3 است. توجه کنيد که آدرس شي، آدرس اولين بايت از بلوکي است که شي در آن جا ذخيره شده.

اگر متغير فوق به شکل int n=32; مقداردهي اوليه شود، آنگاه بلوک حافظه به شکل زير خواهد بود. مقدار 32 در چهار بايتي که براي آن متغير منظور شده ذخيره مي‌شود. 0x0050cdb8 0x0050cdb9 0x0050cdc0 0x0050cdc1 0x0050cdc2 0x0050cdc3 0x0050cdc4 0x0050cdc5 n 32 0x0050cdc0 32 int

2- عملگر ارجاع‌ در C++ براي بدست آوردن آدرس يک متغير مي‌توان از عملگر ارجاع1 & استفاده نمود. به اين عملگر «علمگر آدرس» نيز مي‌گويند. عبارت &n آدرس متغير n را به دست مي‌دهد. int main() { int n=44; cout << " n = " << n << endl; cout << "&n = " << &n << endl; } n = 44 &n = 0x00c9fdc3

خروجي‌ نشان‌ مي‌دهد كه‌ آدرس‌ n در اين اجرا برابر با 0x00c9fdc3 است خروجي‌ نشان‌ مي‌دهد كه‌ آدرس‌ n در اين اجرا برابر با 0x00c9fdc3 است. مي‌توان فهميد که اين مقدار بايد يک آدرس باشد زيرا به شکل شانزده‌دهي نمايش داده شده. اعداد شانزده‌دهي را از روي علامت 0x مي‌توان تشخيص داد. معادل دهدهي عدد بالا مقدار 13,237,699 مي‌باشد.

3- ارجاع‌ها نحو اعلان يک ارجاع به شکل زير است: يك «ارجاع» يك اسم مستعار يا واژۀ مترادف براي متغير ديگر است. نحو اعلان يک ارجاع به شکل زير است: type& ref_name = var_name; type نوع متغير است، ref_name نام مستعار است و var_name نام متغيري است که مي‌خواهيم براي آن نام مستعار بسازيم. براي مثال در اعلان : int& rn=n; // r is a synonym for n rn يک ارجاع يا نام مستعار براي n است. البته n بايد قبلا اعلان شده باشد.

مثال‌ 2-7 استفاده از ارجاع‌ها در برنامۀ زير rn به عنوان يک ارجاع به n اعلان مي‌شود: int main() { int n=44; int& rn=n; // rn is a synonym for n cout << "n = " << n << ", rn = " << rn << endl; --n; rn *= 2; } n = 44, rn = 44 n = 43, rn = 43 n = 86, rn = 86 n و rn نام‌هاي متفاوتي براي يک متغير است. اين دو هميشه مقدار يکساني دارند. اگر n کاسته شود، rn نيز کاسته شده و اگر rn افزايش يابد، n نيز افزايش يافته است.

همانند ثابت‌ها، ارجاع‌ها بايد هنگام اعلان مقداردهي اوليه شوند با اين تفاوت که مقدار اوليۀ يک ارجاع، يک متغير است نه يک ليترال. بنابراين کد زير اشتباه است: int& rn=44; // ERROR: 44 is not a variable; گرچه برخي از کامپايلرها ممکن است دستور بالا را مجاز بدانند ولي با نشان دادن يک هشدار اعلام مي‌کنند که يک متغير موقتي ايجاد شده تا rn به حافظۀ آن متغير، ارجاع داشته باشد.

درست است که ارجاع با يک متغير مقداردهي مي‌شود، اما ارجاع به خودي خود يک متغير نيست. يک متغير، فضاي ذخيره‌سازي و نشاني مستقل دارد، حال آن که ارجاع از فضاي ذخيره‌سازي و نشاني متغير ديگري بهره مي‌برد.

* مثال‌ 3-7 ارجاع‌ها متغيرهاي مستقل نيستند int main() { int n=44; int& rn=n; // rn is a synonym for n cout << " &n = " << &n << ", &rn = " << &rn << endl; int& rn2=n; // rn2 is another synonym for n int& rn3=rn; // rn3 is another synonym for n cout << "&rn2 = " << &rn2 << ", &rn3 = " << &rn3 << endl; } &n = 0x0064fde4, &rn = 0x0064fde4 &rn2 = 0x0064fde4, &rn3 = 0x0064fde4

تنها فرق اين است که دامنۀ پارامتر ارجاع به همان تابع محدود شده است. در برنامۀ فوق فقط يک شي وجود دارد و آن هم n است. rn و rn2 و rn3 ارجاع‌هايي به n هستند. خروجي نيز تاييد مي‌کند که آدرس rn و rn2 و rn3 با آدرس n يکي است. يک شي مي‌تواند چند ارجاع داشته باشد. ارجاع‌ها بيشتر براي ساختن پارامترهاي ارجاع در توابع به کار مي‌روند. تابع مي‌تواند مقدار يک آرگومان را که به طريق ارجاع ارسال شده تغيير دهد زيرا آرگومان اصلي و پارامتر ارجاع هر دو يک شي هستند. تنها فرق اين است که دامنۀ پارامتر ارجاع به همان تابع محدود شده است.

اما آدرس حافظه را در چه نوع متغيري بايد قرار دهيم؟ 4- اشاره‌گرها مي‌دانيم که اعداد صحيح را بايد در متغيري از نوع int نگهداري کنيم و اعداد اعشاري را در متغيرهايي از نوع float. به همين ترتيب کاراکترها را بايد در متغيرهايي از نوع char نگهداريم و مقدارهاي منطقي را در متغيرهايي از نوع bool. اما آدرس حافظه را در چه نوع متغيري بايد قرار دهيم؟

متغيري که يک آدرس در آن ذخيره مي‌شود اشاره‌گر ناميده مي‌شود. عملگر ارجاع & آدرس حافظۀ يک متغير موجود را به دست مي‌دهد. مي‌توان اين آدرس را در متغير ديگري ذخيره نمود. متغيري که يک آدرس در آن ذخيره مي‌شود اشاره‌گر ناميده مي‌شود. براي اين که يک اشاره‌گر اعلان کنيم، ابتدا بايد مشخص کنيم که آدرس چه نوع داده‌اي قرار است در آن ذخيره شود. سپس از عملگر اشاره * استفاده مي‌کنيم تا اشاره‌گر را اعلان کنيم.

براي مثال دستور : float* px; اشاره‌گري به نام px اعلان مي‌کند که اين اشاره‌گر، آدرس متغيرهايي از نوع float را نگهداري مي‌نمايد. به طور کلي براي اعلان يک اشاره‌گر از نحو زير استفاده مي‌کنيم: type* pointername; که type نوع متغيرهايي است که اين اشاره‌گر آدرس آن‌ها را نگهداري مي‌کند و pointername نام اشاره‌گر است. آدرس يک شي از نوع int را فقط مي‌توان در اشاره‌گري از نوع int* ذخيره کرد و آدرس يک شي از نوع float را فقط مي‌توان در اشاره‌گري از نوع float* ذخيره نمود. دقت کنيد که يک اشاره‌گر، يک متغير مستقل است.

int* pn=&n; // pn holds the address of n * مثال 4-7 به کارگيري اشاره‌گرها برنامۀ زير يک متغير از نوع int به نام n و يک اشاره‌گر از نوع int* به نام pn را اعلان مي‌کند: int main() { int n=44; cout << "n = " << n << ", &n = " << &n << endl; int* pn=&n; // pn holds the address of n cout << " pn = " << pn << endl; cout << "&pn = " << &pn << endl;} n = 44, &n = 0x0064fddc pn = 0x0064fddc &pn = 0x0064fde0

متغير n با مقدار 44 مقداردهي‌ شده و آدرس آن 0x0064fddc مي‌باشد متغير n با مقدار 44 مقداردهي‌ شده و آدرس آن 0x0064fddc مي‌باشد. اشاره‌گر pn با مقدار &n يعني آدرس n مقداردهي شده. پس مقدار درون pn برابر با 0x0064fddc است‌ (خط دوم خروجي اين موضوع را تاييد مي‌کند) . 44 n int 0x0064fddc pn int* 0x0064fde0

وقتي مي‌گوييم «pn به n اشاره مي‌کند» يعني درون pn آدرس n قرار دارد. اما pn يک متغير مستقل است و آدرس مستقلي دارد. &pn آدرس pn را به دست مي‌دهد. خط سوم خروجي ثابت مي‌کند که متغير pn مستقل از متغير n است. تصوير زير به درک بهتر اين موضوع کمک مي‌کند. در اين تصوير ويژگي‌هاي مهم n و pn نشان داده شده. pn يک اشاره‌گر به n است و n مقدار 44 دارد. وقتي مي‌گوييم «pn به n اشاره مي‌کند» يعني درون pn آدرس n قرار دارد. وقتي مي‌گوييم «pn به n اشاره مي‌کند» يعني درون pn آدرس n قرار دارد. n 44 int int* pn

به اين کار مقداريابي اشاره‌گر مي‌گوييم. 5-مقداريابي فرض کنيد n داراي مقدار 22 باشد و pn اشاره‌گري به n باشد. با اين حساب بايد بتوان از طريق pn به مقدار 22 رسيد. با استفاده از * مي‌توان مقداري که اشاره‌گر به آن اشاره دارد را به دست آورد. به اين کار مقداريابي اشاره‌گر مي‌گوييم.

ظاهرا *pn يک اسم مستعار براي n است زيرا هر دو يک مقدار دارند. مثال‌ 5-7 مقداريابي يك اشاره‌گر اين‌ برنامه‌ همان‌ برنامۀ‌ مثال 4-7 است. فقط يک خط کد بيشتر دارد: int main() { int n=44; cout << "n = " << n << ", &n = " << &n << endl; int* pn=&n; // pn holds the address of n cout << " pn = " << pn << endl; cout << "&pn = " << &pn << endl; cout << "*pn = " << *pn << endl; } n = 44, &n = 0x0064fdcc pn = 0x0064fdcc &pn = 0x0064fdd0 *pn = 44 ظاهرا *pn يک اسم مستعار براي n است زيرا هر دو يک مقدار دارند.

* مثال 6-7 اشاره‌گري به اشاره‌گرها اين کد ادامۀ ساختار برنامۀ مثال 4-7 است: int main() { int n=44; cout << " n = " << n << endl; cout << " &n = " << &n << endl; int* pn=&n; // pn holds the address of n cout << " pn = " << pn << endl; cout << " &pn = " << &pn << endl; cout << " *pn = " << *pn << endl; int** ppn=&pn; // ppn holds the address of pn cout << " ppn = " << ppn << endl; cout << " &ppn = " << &ppn << endl; cout << " *ppn = " << *ppn << endl; cout << "**ppn = " << **ppn << endl; } يک اشاره‌گر به هر چيزي مي‌تواند اشاره کند، حتي به يک اشاره‌گر ديگر. به مثال زير دقت کنيد.

pn int* ppn int** n = 44 n &n = 0x0064fd78 int pn = 0x0064fd78 &pn = 0x0064fd7c *pn = 44 ppn = 0x0064fd7c &ppn = 0x0064fd80 *ppn = 0x0064fd78 **ppn = 44 n 44 int int* pn ppn int** در برنامۀ بالا متغير n از نوع int تعريف شده. pn اشاره‌گري است که به n اشاره دارد. پس نوع pn بايد int* باشد. ppn اشاره‌گري است که به pn اشاره مي‌کند. پس نوع ppn بايد int** باشد. همچنين چون ppn به pn اشاره دارد، پس *ppn مقدار pn را نشان مي‌دهد و چون pn به n اشاره دارد، پس *pn مقدار n را مي‌دهد.

عملگر مقداريابي. و عملگر ارجاع & معکوس يکديگر رفتار مي‌کنند عملگر مقداريابي * و عملگر ارجاع & معکوس يکديگر رفتار مي‌کنند. اگر اين دو را با هم ترکيب کنيم، يکديگر را خنثي مي‌نمايند. اگر n يک متغير باشد، &n آدرس آن متغير است. از طرفي با استفاده از عملگر * مي‌توان مقداري که در آدرس &n قرار گرفته را به دست آورد. بنابراين *&n برابر با خود n خواهد بود. همچنين اگر p يک اشاره‌گر باشد، *p مقداري که p به آن اشاره دارد را مي‌دهد. از طرفي با استفاده از عملگر & مي‌توانيم آدرس چيزي که در *p قرار گرفته را بدست آوريم. پس &*p برابر با خود p خواهد بود. ترتيب قرارگرفتن اين عملگرها مهم است. يعني *&n با &*n برابر نيست. علت اين امر را توضيح دهيد.

عملگر. دو کاربرد دارد. اگر پسوندِ يک نوع باشد (مثل int عملگر * دو کاربرد دارد. اگر پسوندِ يک نوع باشد (مثل int*) يک اشاره‌گر به آن نوع را تعريف مي‌کند و اگر پيشوندِ يک اشاره‌گر باشد (مثل *p) آنگاه مقداري که p به آن اشاره مي‌کند را برمي‌گرداند. عملگر & نيز دو کاربرد دارد. اگر پسوند يک نوع باشد (مثل int&) يک نام مستعار تعريف مي‌کند و اگر پيشوند يک متغير باشد (مثل &n) آدرس آن متغير را مي‌دهد.

6- چپ مقدارها، راست مقدارها يک دستور جايگزيني دو بخش دارد: بخشي که در سمت چپ علامت جايگزيني قرار مي‌گيرد و بخشي که در سمت راست علامت جايگزيني قرار مي‌گيرد. مثلا دستور n = 55; متغير n در سمت چپ قرار گرفته و مقدار 55 در سمت راست. اين دستور را نمي‌توان به شکل 55 = n; نوشت زيرا مقدار 55 يک ثابت است و نمي‌تواند مقدار بگيرد. پس هنگام استفاده از عملگر جايگزيني بايد دقت کنيم که چه چيزي را در سمت چپ قرار بدهيم و چه چيزي را در سمت راست.

چيزهايي که مي‌توانند در سمت چپ جايگزيني قرار بگيرند «چپ‌مقدار» خوانده مي‌شوند و چيزهايي که مي‌توانند در سمت راست جايگزيني قرار بگيرند «راست‌مقدار» ناميده مي‌شوند. متغيرها (و به طور کلي اشيا) چپ‌مقدار هستند و ليترال‌ها (مثل 15 و "ABC") راست مقدار هستند.

يک ثابت در ابتدا به شکل يک چپ‌مقدار نمايان مي‌شود: const int MAX = 65535; // MAX is an lvalue اما از آن پس ديگر نمي‌توان به عنوان چپ مقدار از آن‌ها استفاده کرد: MAX = 21024; // ERROR: MAX is constant به اين گونه چپ‌مقدارها، چپ‌مقدارهاي «تغيير ناپذير» گفته مي‌شود. مثل آرايه‌ها: int a[] = {1,2,3}; // O.K a[] = {1,2,3}; // ERROR

مابقي چپ‌مقدارها که مي‌توان آن‌ها را تغيير داد، چپ‌مقدارهاي «تغيير پذير» ناميده مي‌شوند. هنگام اعلان يک ارجاع به يک چپ‌مقدار نياز داريم: int& r = n; // O.K. n is an lvalue اما اعلان‌هاي زير غيرمعتبرند زيرا هيچ کدام چپ‌مقدار نيستند: int& r = 44; // ERROR: 44 is not an lvalue int& r = n++; // ERROR: n++ is not an lvalue int& r = cube(n); // ERROR: cube(n) is not an lvalue1 – L_values 2- R_values يک تابع، چپ‌مقدار نيست اما اگر نوع بازگشتي آن يک ارجاع باشد، مي‌توان تابع را به يک چپ‌مقدار تبديل کرد.

7- بازگشت از نوع ارجاع در بحث توابع، ارسال از طريق مقدار و ارسال از طريق ارجاع را ديديم. اين دو شيوۀ تبادل در مورد بازگشت از تابع نيز صدق مي‌کند: بازگشت از طريق مقدار و بازگشت از طريق ارجاع. توابعي که تاکنون ديديم بازگشت به طريق مقدار داشتند. يعني هميشه يک مقدار به فراخواننده برمي‌گشت. مي‌توانيم تابع را طوري تعريف کنيم که به جاي مقدار، يک ارجاع را بازگشت دهد. مثلا به جاي اين که مقدار m را بازگشت دهد، يک ارجاع به m را بازگشت دهد.

وقتي بازگشت به طريق مقدار باشد، تابع يک راست‌مقدار خواهد بود زيرا مقدارها ليترال هستند و ليترال‌ها راست‌مقدارند. به اين ترتيب تابع را فقط در سمت راست يک جايگزيني مي‌توان به کار برد مثل: m = f(); وقتي بازگشت به طريق ارجاع باشد، تابع يک چپ‌مقدار خواهد بود زيرا ارجاع‌ها چپ‌مقدار هستند. در اين حالت تابع را مي‌توان در سمت چپ يک جايگزيني قرار داد مثل : f() = m;

* مثال‌ 7-8 بازگشت از نوع ارجاع int& max(int& m, int& n) براي اين که نوع بازگشتي تابع را به ارجاع تبديل کنيم کافي است عملگر ارجاع را به عنوان پسوند نوع بازگشتي درج کنيم. * مثال‌ 7-8 بازگشت از نوع ارجاع int& max(int& m, int& n) { return (m > n ? m : n);} int main() { int m = 44, n = 22; cout << m << ", " << n << ", " << max(m,n) << endl; max(m,n) = 55; } 44, 22, 44 55, 22, 55

تابع max() از بين m و n مقدار بزرگ‌تر را پيدا کرده و سپس ارجاعي به آن را باز مي‌گرداند. بنابراين اگر m از n بزرگ‌تر باشد، تابع max(m,n) آدرس m را برمي‌گرداند. پس وقتي مي‌نويسيم max(m,n) = 55; مقدار 55 در حقيقت درون متغير m قرار مي‌گيرد (اگر m>n باشد). به بياني ساده، فراخواني max(m,n) خود m را بر مي‌گرداند نه مقدار آن را.

اخطار: وقتي يک تابع پايان مي‌يابد، متغيرهاي محلي آن نابود مي‌شوند. پس هيچ وقت ارجاعي به يک متغير محلي بازگشت ندهيد زيرا وقتي کار تابع تمام شد، آدرس متغيرهاي محلي‌اش غير معتبر مي‌شود و ارجاع بازگشت داده شده ممکن است به يک مقدار غير معتبر اشاره داشته باشد. تابع max() در مثال بالا يک ارجاع به m يا n را بر مي‌گرداند. چون m و n خودشان به طريق ارجاع ارسال شده‌اند، پس محلي نيستند و بازگرداندن ارجاعي به آن‌ها خللي در برنامه وارد نمي‌کند.

به اعلان تابع max() دقت کنيد: int& max(int& m, int& n) نوع بازگشتي آن با استفاده از عملگر ارجاع & به شکل يک ارجاع درآمده است. مثال‌ 9-7 به کارگيري يك تابع به عنوان عملگر زيرنويس آرايه

float& component(float* v, int k) { return v[k-1];} int main() for (int k = 1; k <= 4; k++) component(v,k) = 1.0/k; for (int i = 0; i < 4; i++) cout << "v[" << i << "] = " << v[i] << endl; } v[0] = 1 v[1] = 0.5 v[2] = 0.333333 v[3] = 0.25

تابع‌ component() باعث مي‌شود که ايندکس آرايه v از «شماره‌گذاري از صفر» به «شماره‌گذاري از يک» تغيير کند. بنابراين component(v,3) معادل v[2] است. اين کار از طريق بازگشت از طريق ارجاع ممکن شده است.

8- آرايه‌ها و اشاره‌گرها گرچه اشاره‌گرها از انواع‌ عددي صحيح‌ نيستند اما بعضي از اعمال حسابي را مي‌توان روي اشاره‌گرها انجام داد. حاصل اين مي‌شود که اشاره‌گر به خانۀ ديگري از حافظه اشاره مي‌کند. اشاره‌گرها را مي‌توان مثل اعداد صحيح افزايش و يا کاهش داد و مي‌توان يک عدد صحيح را به آن‌ها اضافه نمود يا از آن کم کرد. البته ميزان افزايش يا کاهش اشاره‌گر بستگي به نوع داده‌اي دارد که اشاره‌گر به آن اشاره دارد. مثال‌ 10-7 پيمايش آرايه با استفاده از اشاره‌گر اين‌ مثال‌ نشان‌ مي‌دهد كه‌ چگونه‌ مي‌توان از اشاره‌گر براي پيمايش يک آرايه استفاده نمود:

cout << "a = " << a << endl; int main() { const int SIZE = 3; short a[SIZE] = {22, 33, 44}; cout << "a = " << a << endl; cout << "sizeof(short) = " << sizeof(short) << endl; short* end = a + SIZE; // converts SIZE to offset 6 short sum = 0; for (short* p = a; p < end; p++) { sum += *p; cout << "\t p = " << p; cout << "\t *p = " << *p; cout << "\t sum = " << sum << endl; } cout << "end = " << end << endl; a = 0x3fffd1a sizeof(short) = 2 p = 0x3fffd1a *p = 22 sum = 22 p = 0x3fffd1c *p = 33 sum = 55 p = 0x3fffd1e *p = 44 sum = 99 end = 0x3fffd20

اين مثال نشان مي‌دهد که هر گاه يک اشاره‌گر افزايش يابد، مقدار آن به اندازۀ تعداد بايت‌هاي شيئي که به آن اشاره مي‌کند، افزايش مي‌يابد. مثلا اگر p اشاره‌گري به double باشد و sizeof(double) برابر با هشت بايت باشد، هر گاه که p يک واحد افزايش يابد، اشاره‌گر p هشت بايت به پيش مي‌رود.

مثلا کد زير : float a[8]; float* p = a; // p points to a[0] ++p; // increases the value of p by sizeof(float)

اگر floatها 4 بايت را اشغال‌ كنند آنگاه ++p مقدار درون p را 4 بايت افزايش مي‌دهد و p += 5; مقدار درون p را 20 بايت افزايش مي‌دهد. با استفاده از خاصيت مذکور مي‌توان آرايه را پيمايش نمود: يک اشاره‌گر را با آدرس اولين عنصر آرايه مقداردهي کنيد، سپس اشاره‌گر را پي در پي افزايش دهيد. هر افزايش سبب مي‌شود که اشاره‌گر به عنصر بعدي آرايه اشاره کند. يعني اشاره‌گري که به اين نحو به کار گرفته شود مثل ايندکس آرايه عمل مي‌کند.

float* p = a; // p points to a[0] p += 5; // now p points to a[5] همچنين با استفاده از اشاره‌گر مي‌توانيم مستقيما به عنصر مورد نظر در آرايه دستيابي کنيم: float* p = a; // p points to a[0] p += 5; // now p points to a[5] يک نکتۀ ظريف در ارتباط با آرايه‌ها و اشاره‌گرها وجود دارد: اگر اشاره‌گر را بيش از ايندکس آرايه افزايش دهيم، ممکن است به بخش‌هايي از حافظه برويم که هنوز تخصيص داده نشده‌اند يا براي کارهاي ديگر تخصيص يافته‌اند. تغيير دادن مقدار اين بخش‌ها باعث بروز خطا در برنامه و کل سيستم مي‌شود. هميشه بايد مراقب اين خطر باشيد.

float* p = a[7]; // points to last element in the array کد زير نشان مي‌دهد که چطور اين اتفاق رخ مي‌دهد. float a[8]; float* p = a[7]; // points to last element in the array ++p; //now p points to memory past last element! *p = 22.2; // TROUBLE! مثال‌ بعدي‌ نشان‌ مي‌دهد كه‌ ارتباط تنگاتنگي‌ بين‌ آرايه‌ها و اشاره‌گرها وجود دارد. نام آرايه در حقيقت يک اشاره‌گر ثابت (const) به اولين عنصر آرايه است. همچنين خواهيم ديد که اشاره‌گرها را مانند هر متغير ديگري مي‌توان با هم مقايسه نمود.

* مثال‌ 11-7 پيمايش عناصر آرايه از طريق آدرس‌ int main() { short a[] = {22, 33, 44, 55, 66}; cout << "a = " << a << ", *a = " << *a << endl; for (short* p = a; p < a +5; p++) cout << "p = " << p << ", *p = " << *p << endl; } a = 0x3fffd08, *a = 22 p = 0x3fffd08, *p = 22 p = 0x3fffd0a, *p = 33 p = 0x3fffd0c, *p = 44 p = 0x3fffd0e, *p = 55 p = 0x3fffd10, *p = 66 p = 0x3fffd12, *p = 77

پس حلقه تا زماني که p < 0x3fffd12 باشد ادامه مي‌يابد. در نگاه اول‌، a و p مانند هم هستند: هر دو به نوع short اشاره مي‌کنند و هر دو داراي مقدار 0x3fffd08 هستند. اما a يک اشاره‌گر ثابت است و نمي‌تواند افزايش يابد تا آرايه پيمايش شود. پس به جاي آن p را افزايش مي‌دهيم تا آرايه را پيمايش کنيم. شرط (p < a+5) حلقه را خاتمه مي‌دهد. a+5 به شکل زير ارزيابي مي‌شود: 0x3fffd08 + 5*sizeof(short) = 0x3fffd08 + 5*2 = 0x3fffd08 + 0xa = 0x3fffd12 پس حلقه تا زماني که p < 0x3fffd12 باشد ادامه مي‌يابد.

عملگر زيرنويس ‌[] مثل عملگر مقداريابي. رفتار مي‌کند عملگر زيرنويس ‌[] مثل عملگر مقداريابي * رفتار مي‌کند. هر دوي اين‌ها مي‌توانند به عناصر آرايه دسترسي مستقيم داشته باشند. a[0] == *a a[1] == *(a + 1) a[2] == *(a + 2) ... پس با استفاده از کد زير نيز مي‌توان آرايه را پيمايش نمود: for (int i = 0; i < 8; i++) cout << *(a + i) << endl;

short* loc(short* a1, short* a2, int n1, int n2) مثال‌ 12-7 مقايسۀ الگو در اين مثال، تابع loc() در ميان n1 عنصر اول آرايۀ a1 به دنبال n2 عنصر اول‌ آرايۀ‌ a2 مي‌گردد. اگر پيدا شد، يک اشاره‌گر به درون a1 برمي‌گرداند که a2 از آن‌جا شروع مي‌شود وگرنه اشاره‌گر NULL را برمي‌گرداند. short* loc(short* a1, short* a2, int n1, int n2) { short* end1 = a1 + n1; for (short* p1 = a1; p1 <end1; p1++) if (*p1 == *a2) { for (int j = 0; j < n2; j++) if (p1[j] != a2[j]) break; if (j == n2) return p1; } return 0;

int main() { short a1[9] = {11, 11, 11, 11, 11, 22, 33, 44, 55}; short a2[5] = {11, 11, 11, 22, 33}; cout << "Array a1 begins at location\t" << a1 << endl; cout << "Array a2 begins at location\t" << a2 << endl; short* p = loc(a1, a2, 9, 5); if (p) { cout << "Array a2 found at location\t" << p << endl; for (int i = 0; i < 5; i++) cout << "\t" << &p[i] << ": " << p[i] << "\t" << &a2[i] << ": " << a2[i] << endl; } else cout << "Not found.\n";}

Array a1 begins at location 0x3fffd12 Array a2 found at location 0x3fffd16 0x3fffd16: 11 0x3fffd08: 11 0x3fffd18: 11 0x3fffd0a: 11 0x3fffd1a: 11 0x3fffd0c: 11 0x3fffd1c: 22 0x3fffd0e: 22 0x3fffd1e: 33 0x3fffd10: 33

9-7 عملگر new وقتي‌ يك‌ اشاره‌گر شبيه‌ اين‌ اعلان‌ شود: float* p; // p is a pointer to a float يک فضاي چهاربايتي به p تخصيص داده مي‌شود (معمولا sizeof(float) چهار بايت است). حالا p ايجاد شده است اما به هيچ جايي اشاره نمي‌کند زيرا هنوز آدرسي درون آن قرار نگرفته. به چنين اشاره‌گري اشاره‌گر سرگردان مي‌گويند. اگر سعي کنيم يک اشاره‌گر سرگردان را مقداريابي يا ارجاع کنيم با خطا مواجه مي‌شويم.

مثلا دستور: *p = 3.14159; // ERROR: no storage has been allocated for *P خطاست. زيرا p به هيچ آدرسي اشاره نمي‌کند و سيستم عامل نمي‌داند که مقدار 3.14159 را کجا ذخيره کند. براي رفع اين مشکل مي‌توان اشاره‌گرها را هنگام اعلان، مقداردهي کرد: float x = 0; // x cintains the value 0 float* p = &x // now p points to x *p = 3.14159; // O.K. assigns this value to address that p points to

در اين حالت مي‌توان به *p دستيابي داشت زيرا حالا p به x اشاره مي‌کند و آدرس آن را دارد. راه حل ديگر اين است که يک آدرس اختصاصي ايجاد شود و درون p قرار بگيرد. بدين ترتيب p از سرگرداني خارج مي‌شود. اين کار با استفاده از عملگر new صورت مي‌پذيرد: float* p; p = new float; // allocates storage for 1 float *p = 3.14159; // O.K. assigns this value to that storage دقت کنيد که عملگر new فقط خود p را مقداردهي مي‌کند نه آدرسي که p به آن اشاره مي‌کند. مي‌توانيم سه خط فوق را با هم ترکيب کرده و به شکل يک دستور بنويسيم: float* p = new float(3.141459);

با اين دستور، اشاره‌گر p از نوع float با اين دستور، اشاره‌گر p از نوع float* تعريف مي‌شود و سپس يک بلوک خالي از نوع float منظور شده و آدرس آن به p تخصيص مي‌يابد و همچنين مقدار 3.14159 در آن آدرس قرار مي‌گيرد. اگر عملگر new نتواند خانۀ خالي در حافظه پيدا کند، مقدار صفر را برمي‌گرداند. اشاره‌گري که اين چنين باشد، «اشاره‌گر تهي» يا NULL مي‌نامند.

با استفاده از کد هوشمند زير مي‌توانيم مراقب باشيم که اشاره‌گر تهي ايجاد نشود: double* p = new double; if (p == 0) abort(); // allocator failed: insufficent memory else *p = 3.141592658979324; در اين قطعه کد، هرگاه اشاره‌گري تهي ايجاد شد، تابع abort() فراخواني شده و اين دستور لغو مي‌شود.

تاکنون دانستيم که به دو طريق مي‌توان يک متغير را ايجاد و مقداردهي کرد تاکنون دانستيم که به دو طريق مي‌توان يک متغير را ايجاد و مقداردهي کرد. روش اول: float x = 3.14159; // allocates named memory و روش دوم: float* p = new float(3.14159); // allocates unnamed memory در حالت اول، حافظۀ مورد نياز براي x هنگام کامپايل تخصيص مي‌يابد. در حالت دوم حافظۀ مورد نياز در زمان اجرا و به يک شيء بي‌نام تخصيص مي‌يابد که با استفاده از *p قابل دستيابي است.

10- عملگر delete عملگر delete عملي‌ برخلاف عملگر new دارد. کارش اين است که حافظۀ اشغال شده را آزاد کند. وقتي حافظه‌اي آزاد شود، سيستم عامل مي‌تواند از آن براي کارهاي ديگر يا حتي تخصيص‌هاي جديد استفاده کند. عملگر delete را تنها روي اشاره‌گرهايي مي‌توان به کار برد که با دستور new ايجاد شده‌اند. وقتي حافظۀ يک اشاره‌گر آزاد شد، ديگر نمي‌توان به آن دستيابي نمود مگر اين که دوباره اين حافظه تخصيص يابد: float* p = new float(3.14159); delete p; // deallocates q *p = 2.71828; // ERROR: q has been deallocated

وقتي اشاره گر p در کد بالا آزاد شود، حافظه‌اي که توسط new به آن تخصيص يافته بود، آزاد شده و به ميزان sizeof(float) به حافظۀ آزاد اضافه مي‌شود. وقتي اشاره‌گري آزاد شد، به هيچ چيزي اشاره نمي‌کند؛ مثل متغيري که مقداردهي نشده. به اين اشاره‌گر، اشاره‌گر سرگردان مي‌گويند. اشاره‌گر به يک شيء ثابت را نمي‌توان آزاد کرد: const int* p = new int; delete p; // ERROR: cannot delete pointer to const objects علت اين است که «ثابت‌ها نمي‌توانند تغيير کنند».

اگر متغيري را صريحا اعلان کرده‌ايد و سپس اشاره‌گري به آن نسبت داده‌ايد، از عملگر delete استفاده نکنيد. اين کار باعث اشتباه غير عمدي زير مي‌شود: float x =3.14159; // x contains the value 3.14159 float* p = &x; // p contains the address of x delete p; // WARNING: this will make x free کد بالا باعث مي‌شود که حافظۀ تخصيص‌يافته براي x آزاد شود. اين اشتباه را به سختي مي‌توان تشخيص داد و اشکال‌زدايي کرد.

11- آرايه‌هاي‌ پويا نام آرايه در حقيقت يك اشاره‌گر ثابت است كه‌ در زمان‌ كامپايل‌، ايجاد و تخصيص‌ داده‌ مي‌شود: float a[20]; //a is a const pointer to a block of 20 floats float* const p = new float[20]; // so is p هم a و هم p اشاره‌گرهاي ثابتي هستند که به بلوکي حاوي 20 متغير float اشاره دارند. به اعلان a بسته‌بندي ايستا1 مي‌گويند زيرا اين کد باعث مي‌شود که حافظۀ مورد نياز براي a در زمان کامپايل تخصيص داده شود. وقي برنامه اجرا شود، به هر حال حافظۀ مربوطه تخصيص خواهد يافت حتي اگر از آن هيچ استفاده‌اي نشود.

مي‌توانيم با استفاده از اشاره‌گر، آرايۀ فوق را طوري تعريف کنيم که حافظه مورد نياز آن فقط در زمان اجرا تخصيص يابد: float* p = new float[20]; دستور بالا، 20 خانۀ خالي حافظه از نوع float را در اختيار گذاشته و اشاره‌گر p را به خانۀ اول آن نسبت مي‌دهد. به اين آرايه، «آرايۀ پويا2» مي‌گويند. به اين طرز ايجاد اشيا بسته‌بندي پويا3 يا «بسته‌بندي زمان جرا» مي‌گويند.

آرايۀ ايستاي a و آرايۀ پوياي p را با يکديگر مقايسه کنيد آرايۀ ايستاي a و آرايۀ پوياي p را با يکديگر مقايسه کنيد. آرايۀ ايستاي a در زمان کامپايل ايجاد مي‌شود و تا پايان اجراي برنامه، حافظۀ تخصيصي به آن مشغول مي‌ماند. ولي آرايۀ پوياي p در زمان اجرا و هر جا که لازم شد ايجاد مي‌شود و پس از اتمام کار نيز مي‌توان با عملگر delete حافظۀ تخصيصي به آن را آزاد کرد: delete [] p; براي آزاد کردن آرايۀ پوياي p براکت‌ها [] قبل از نام p بايد حتما قيد شوند زيرا p به يک آرايه اشاره دارد.

void get(double*& a, int& n) مثال‌ 15-7 استفاده‌ از آرايه‌هاي‌ پويا تابع‌ get() در برنامۀ زير يک آرايۀ پويا ايجاد مي‌كند: void get(double*& a, int& n) { cout << "Enter number of items: "; cin >> n; a = new double[n]; cout << "Enter " << n << " items, one per line:\n"; for (int i = 0; i < n; i++) { cout << "\t" << i+1 << ": "; cin >> a[i]; }} void print(double* a, int n) { for (int i = 0; i < n; i++) cout << a[i] << " " ; cout << endl; }

int main() { double* a;// a is simply an unallocated pointer int n; get(a,n); // now a is an array of n doubles print(a,n); delete [] a;// now a is simply an unallocated pointer again get(a,n); // now a is an array of n doubles print(a,n); }

Enter number of items: 4 Enter 4 items, one per line: 1: 44.4 2: 77.7 3: 22.2 4: 88.8 44.4 77.7 22.2 88.8 Enter number of items: 2 Enter 2 items, one per line: 1: 3.33 2: 9.99 3.33 9.99

12- اشاره‌گر ثابت «اشاره‌گر به يک ثابت» با «اشاره‌گر ثابت» تفاوت دارد. اين تفاوت در قالب مثال زير نشان داده شده است. مثال‌ 16-7 اشاره‌گرهاي ثابت و اشاره‌گرهايي به ثابت‌ها در اين کد چهار اشاره‌گر اعلان شده. اشاره‌گر p، اشاره‌گر ثابت cp، اشاره به يک ثابت pc، اشاره‌گر ثابت به يک ثابت cpc :

int n = 44; // an int int* p = &n; // a pointer to an int ++(*p); // OK: increments int *p ++p; // OK: increments pointer p int* const cp = &n; // a const pointer to an int ++(*cp); // OK: increments int *cp ++cp; // illegal: pointer cp is const const int k = 88; // a const int const int * pc = &k; // a pointer to a const int ++(*pc); // illegal: int *pc is const ++pc; // OK: increments pointer pc const int* const cpc = &k; // a const pointer to a const int ++(*cpc); // illegal: int *pc is const ++cpc; // illegal: pointer cpc is const

اشاره‌گر p اشاره‌گري به متغير n است اشاره‌گر p اشاره‌گري به متغير n است. هم خود p قابل افزايش است (++p) و هم مقداري که p به آن اشاره مي‌کند قابل افزايش است (++(*P)). اشاره گر cp يک اشاره‌گر ثابت است. يعني آدرسي که در cp است قابل تغيير نيست ولي مقداري که در آن آدرس است را مي‌توان دست‌کاري کرد. اشاره‌گر pc اشاره‌گري است که به آدرس يک ثابت اشاره دارد. خود pc را مي‌توان تغيير داد ولي مقداري که pc به آن اشاره دارد قابل تغيير نيست. در آخر هم cpc يک اشاره‌گر ثابت به يک شيء ثابت است. نه مقدار cpc قابل تغيير است و نه مقداري که آدرس آن در cpc است.

13- آرايه‌اي‌ از اشاره‌گرها مي‌توانيم آرايه‌اي تعريف کنيم که اعضاي آن از نوع اشاره‌گر باشند. مثلا دستور: float* p[4]; آرايۀ p را با چهار عنصر از نوع float* (يعني اشاره‌گري به float) اعلان مي‌کند. عناصر اين آرايه را مثل اشاره‌گر‌هاي معمولي مي‌توان مقداردهي کرد: p[0] = new float(3.14159); p[1] = new float(1.19);

اين آرايه را مي توانيم شبيه شکل مقابل مجسم کنيم: مثال بعد نشان مي‌دهد که آرايه‌اي از اشاره‌گرها به چه دردي مي‌خورد. از اين آرايه مي‌توان براي مرتب‌کردن يک فهرست نامرتب به روش حبابي استفاده کرد. به جاي اين که خود عناصر جابجا شوند، اشاره‌گرهاي آن‌ها جابجا مي‌شوند. p 3.14159 1 2 3 double 1.19 double

مثال‌ 17-7 مرتب‌سازي‌ حبابي‌ غيرمستقيم void sort(float* p[], int n) { float* temp; for (int i = 1; i < n; i++) for (int j = 0; j < n-i; j++) if (*p[j] > *p[j+1]) { temp = p[j]; p[j] = p[j+1]; p[j+1] = temp; }

تابع sort() آرايه‌اي از اشاره‌گرها را مي‌گيرد تابع sort() آرايه‌اي از اشاره‌گرها را مي‌گيرد. سپس درون حلقه‌هاي تودرتوي for بررسي مي‌کند که آيا مقاديري که اشاره‌گرهاي مجاور به آن‌ها اشاره دارند، مرتب هستند يا نه. اگر مرتب نبودند، جاي اشاره‌گرهاي آن‌ها را با هم عوض مي‌کند. در پايان به جاي اين که يک فهرست مرتب داشته باشيم، آرايه‌اي داريم که اشاره‌گرهاي درون آن به ترتيب قرار گرفته اند.

14-7 اشاره‌گري به اشاره‌گر ديگر يك اشاره‌گر مي‌تواند به اشاره‌گر ديگري اشاره کند. مثلا: char c = 't'; char* pc = &c; char** ppc = &pc; char*** pppc = &ppc; ***pppc = 'w'; // changes value of c to 'w' حالا pc اشاره‌گري به متغير کاراکتري c است. ppc اشاره‌گري به اشاره‌گر pc است و اشاره‌گر pppc هم به اشاره‌گر ppc اشاره دارد. مثل شکل مقابل:

با اين وجود مي‌توان با اشاره‌گر pppc مستقيما به متغير c رسيد. '\t' c با اين وجود مي‌توان با اشاره‌گر pppc مستقيما به متغير c رسيد.

15- اشاره‌گر به توابع اين بخش ممکن است کمي عجيب به نظر برسد. حقيقت اين است که نام يک تابع مثل نام يک آرايه، يک اشاره‌گر ثابت است. نام تابع، آدرسي از حافظه را نشان مي‌دهد که کدهاي درون تابع در آن قسمت جاي گرفته‌اند. پس بنابر قسمت قبل اگر اشاره‌گري به تابع اعلان کنيم، در اصل اشاره‌گري به اشاره‌گر ديگر تعريف کرده‌ايم. اما اين تعريف، نحو متفاوتي دارد: int f(int); // declares function f int (*pf)(int); // declares function pointer pf pf = &f; // assigns address of f to pf

اشاره‌گر pf همراه با * درون پرانتز قرار گرفته، يعني اين که pf اشاره‌گري به يک تابع است. بعد از آن يک int هم درون پرانتز آمده است، به اين معني که تابعي که pf به آن اشاره مي‌نمايد، پارامتري از نوع int دارد. اشاره‌گر pf را مي‌توانيم به شکل زير تصور کنيم:

فايدۀ اشاره‌گر به توابع اين است که به اين طريق مي‌توانيم توابع مرکب بسازيم. يعني مي‌توانيم يک تابع را به عنوان آرگومان به تابع ديگر ارسال کنيم! اين کار با استفاده از اشاره‌گر به تابع امکان پذير است. pf f int f(int n) { ... }

int sum(int (*)(int), int); int square(int); int cube(int); int main() مثال‌ 18-7 تابع مرکب جمع تابع‌ sum() در اين مثال دو پارامتر دارد: اشاره‌گر تابع pf و عدد صحيح n : int sum(int (*)(int), int); int square(int); int cube(int); int main() { cout << sum(square,4) << endl; // 1 + 4 + 9 + 16 cout << sum(cube,4) << endl; //1 + 8 + 27 + 64 }

تابع sum() يک پارامتر غير معمول دارد تابع sum() يک پارامتر غير معمول دارد. نام تابع ديگري به عنوان آرگومان به آن ارسال شده. هنگامي که ‌ sum(square,4) فراخواني شود، مقدار square(1)+square(2)+square(3)+square(4) بازگشت داده مي‌شود. چونsquare(k) مقدار k*k را برمي‌گرداند، فراخواني sum(square,4) مقدار 1+4+9+16=30 را محاسبه نموده و بازمي‌گرداند. تعريف توابع و خروجي آزمايشي به شکل زير است:

int sum(int (*pf)(int k), int n) { // returns the sum f(0) + f(1) + f(2) + ... + f(n-1): int s = 0; for (int i = 1; i <= n; i++) s += (*pf)(i); return s; } int square(int k) { return k*k; int cube(int k) { return k*k*k; 30 100

pf در فهرست پارامترهاي تابع sum() يک اشاره‌گر به تابع است pf در فهرست پارامترهاي تابع sum() يک اشاره‌گر به تابع است. اشاره‌گر به تابعي که آن تابع پارامتري از نوع int دارد و مقداري از نوع int را برمي‌گرداند. k در تابع sum اصلا استفاده نشده اما حتما بايد قيد شود تا کامپايلر بفهمد که pf به تابعي اشاره دارد که پارامتري از نوع int دارد. عبارت (*pf)(i) معادل با square(i) يا cube(i) خواهد بود، بسته به اين که کدام يک از اين دو تابع به عنوان آرگومان به sum() ارسال شوند.

نام تابع، آدرس شروع تابع را دارد نام تابع، آدرس شروع تابع را دارد. پس square آدرس شروع تابع square() را دارد. بنابراين وقتي تابع sum() به شکل sum(square,4) فراخواني شود، آدرسي که درون square است به اشاره‌گر pf فرستاده مي‌شود. با استفاده از عبارت (*pf)(i) مقدار i به آرگومان تابعي فرستاده مي‌شود که pf به آن اشاره دارد.

16- NUL و NULL ثابت‌ صفر (0) از نوع‌ int است اما اين مقدار را به هر نوع بنيادي ديگر مي‌توان تخصيص داد: char c = 0; // initializes c to the char '\0' short d = 0; // initializes d to the short int 0 int n = 0; // initializes n to the int 0 unsigned u = 0; // initializes u to the unsigned int 0 float x = 0; // initializes x to the float 0.0 double z = 0; // initializes z to the double 0.0

مقدار صفر معناهاي گوناگوني دارد مقدار صفر معناهاي گوناگوني دارد. وقتي براي اشياي عددي به کار رود، به معناي عدد صفر است. وقتي براي اشياي کاراکتري به کار رود، به معناي کاراکتر تهي يا NUL است. NUL معادل کاراکتر '\0' نيز هست. وقتي مقدار صفر براي اشاره‌گر‌ها به کار رود، به معناي «هيچ چيز» يا NULL است. NULL يک کلمۀ کليدي است و کامپايلر آن را مي‌شناسد. هنگامي که مقدار NULL يا صفر در يک اشاره‌گر قرار مي‌گيرد، آن اشاره‌گر به خانه 0x0 در حافظه اشاره دارد. اين خانۀ حافظه، يک خانۀ استثنايي است که قابل پردازش نيست. نه مي‌توان آن خانه را مقداريابي کرد و نه مي‌توان مقداري را درون آن قرار داد. به همين دليل به NULL «هيچ چيز» مي‌گويند.

وقتي اشاره‌گري را بدون استفاده از new اعلان مي‌کنيم، خوب است که ابتدا آن را NULL کنيم تا مقدار زبالۀ آن پاک شود. اما هميشه بايد به خاطر داشته باشيم که اشاره‌گر NULL را نبايد مقداريابي نماييم: int* p = 0; // p points to NULL *p = 22; // ERROR: cannot dereference the NULL pointer پس خوب است هنگام مقداريابي اشاره‌گرها، احتياط کرده و بررسي کنيم که آن اشاره‌گر NULL نباشد: if (p) *p = 22; // O.K. حالا دستور *p=22; وقتي اجرا مي‌شود که p صفر نباشد. مي‌دانيد که شرط بالا معادل شرط زير است: if (p != NULL) *p = 22;

اشاره‌گر‌ها را نمي‌توان ناديده گرفت. آن‌ها سرعت پردازش را زياد مي‌کنند و کدنويسي را کم. با استفاده از اشاره‌گرها مي‌توان به بهترين شکل از حافظه استفاده کرد. با به کارگيري اشاره‌گرها مي‌توان اشيايي پيچيده‌تر و کارآمدتر ساخت.

پايان جلسه هفتم

« رشته‌هاي‌ كاراكتري و فايل‌ها در ++C استاندارد» جلسه هشتم « رشته‌هاي‌ كاراكتري و فايل‌ها در ++C استاندارد»

آنچه در اين جلسه مي خوانيد مروري‌ بر اشاره‌گرها رشته‌هاي كاراكتري در C ورودي‌/خروجي رشته‌هاي کاراکتري چند تابع‌ عضو cin و cout توابع‌ كاراكتري‌ C استاندارد آرايه‌اي از رشته‌ها توابع استاندارد رشته‌هاي کاراکتري ›››

رشته‌هاي کاراکتري در C++ استاندارد‌ نگاهي دقيق‌تر به تبادل داده‌ها ورودي‌ قالب‌بندي نشده‌ نوع‌ string در ++C استاندارد فايل‌ها هدف کلي: آشنايي با کلاس‌ها و اصول اوليۀ به‌کارگيري آن‌ها.

هدف کلي: معرفي رشته‌هاي کاراکتري به سبک c و c++ و نحوۀ ايجاد و دست‌کاري آن‌ها و همچنين نحوۀ استفاده از فايل‌هاي متني براي ذخيره‌سازي و بازيابي اطلاعات.

هدف‌هاي رفتاري: انتظار مي‌رود پس از پايان اين جلسه بتوانيد: - رشته‌هاي کاراکتري به سبک C استاندارد را ايجاد نماييد. - توابع معرفي شده عضو cin و cout را شناخته و وظيفۀ هر يک را شرح دهيد. - رشته‌هاي کاراکتري به سبک C++ استاندارد را ايجاد نماييد. - مفهوم «ورودي قالب‌بندي شده» و «ورودي قالب‌بندي نشده» را دانسته و هر کدام را در مکان‌هاي مناسب به کار ببريد. - نوع string را شناخته و رشته‌هايي از اين نوع ايجاد کنيد و با استفاده از توابع خاص، اين رشته‌ها را دست‌کاري نماييد. - اطلاعات کاراکتري و رشته‌اي را در يک فايل متني نوشته يا از آن بخوانيد.

مقدمه: داده‌هايي که در رايانه‌ها پردازش مي‌شوند هميشه عدد نيستند. معمولا لازم است که اطلاعات کاراکتري مثل نام افراد – نشاني‌ها – متون – توضيحات – کلمات و ... نيز پردازش گردند، جستجو شوند، مقايسه شوند، به يکديگر الصاق شوند يا از هم‌ تفکيک گردند. در اين جلسه بررسي مي‌کنيم که چطور اطلاعات کاراکتري را از ورودي دريافت کنيم و يا آن‌ها را به شکل دلخواه به خروجي بفرستيم. در همين راستا توابعي معرفي مي‌کنيم که انجام اين کارها را آسان مي‌کنند.

مروري‌ بر اشاره‌گرها: يك‌ اشاره‌گر متغيري است که حاوي يک آدرس از حافظه مي‌باشد. نوع اين متغير از نوع مقداري است که در آن آدرس ذخيره شده. با استفاده از عملگر ارجاع & مي‌توان آدرس يک شي را پيدا کرد. همچنين با استفاده از عملگر مقداريابي * مي‌توانيم مقداري که در يک آدرس قرار دارد را مشخص کنيم. به تعاريف زير نگاه کنيد: int n = 44; int* p = &n;

رشته‌هاي كاراكتري در C در زبان C++ يك «رشتۀ کاراکتري» آرايه‌اي از کاراکترهاست که اين آرايه داراي ويژگي مهم زير است: 1- يك‌ بخش‌ اضافي‌ در انتهاي آرايه وجود دارد که مقدار آن، کاراکتر NUL يعني '\0‘ است. پس تعداد کل کاراکترها در آرايه هميشه يکي بيشتر از طول رشته است. 2 – رشتۀ کاراکتري را مي‌توان با ليترال رشته‌اي به طور مستقيم مقدارگذاري کرد مثل: char str[] = "string"; توجه‌ كنيد كه‌ اين‌ آرايه‌ هفت‌ عنصر دارد: 's' و 't' و 'r' و 'i' و 'n' و 'g' و '\0'

3– کل يک رشتۀ کاراکتري را مي‌توان مثل يک متغير معمولي چاپ کرد. مثل: cout << str; در اين صورت، همۀ کاراکترهاي درون رشتۀ کاراکتري str يکي يکي به خروجي مي‌روند تا وقتي که به کاراکتر انتهايي NUL برخورد شود. 4 – يک رشتۀ کاراکتري را مي‌توان مثل يک متغير معمولي از ورودي دريافت کرد مثل: cin >> str; در اين صورت، همۀ کاراکترهاي وارد شده يکي يکي درون str جاي مي‌گيرند تا وقتي که به يک فضاي خالي در کاراکترهاي ورودي برخورد شود. برنامه‌نويس بايد مطمئن باشد که آرايۀ str براي دريافت همۀ کاراکترهاي وارد شده جا دارد.

5 – توابع تعريف شده در سرفايل <cstring> را مي‌توانيم براي دست‌کاري رشته‌هاي کاراکتري به کار بگيريم. اين توابع عبارتند از: تابع طول رشته strlen() توابع کپي رشته strcpy() و strncpy() توابع الصاق رشته‌ها strcat() و strncat() توابع مقايسۀ رشته‌ها strcmp() و strncmp() و تابع استخراج نشانه strtok() .

for (int i = 0; i < 5; i++) رشته‌هاي کاراکتري با كاراكتر NUL خاتمه‌ مي‌يابند برنامۀ کوچک زير نشان مي‌دهد که کاراکتر '\0' به رشته‌هاي کاراکتري الصاق مي‌شود: int main() { char s[] = "ABCD"; for (int i = 0; i < 5; i++) cout << "s[" << i << "] = '" << s[i] << "'\n"; }

رشتۀ کاراکتري s داراي پنج عضو است که عضو پنجم، کاراکتر '\0' مي‌باشد وقتي کاراکتر '\0' به cout فرستاده مي‌شود، هيچ چيز چاپ نمي‌شود. حتي جاي خالي هم چاپ نمي‌شود. خط آخر خروجي، عضو پنجم را نشان مي دهد که ميان دو علامت آپستروف هيچ چيزي چاپ نشده. S A 1 B 2 C 3 D 4 Ø

ورودي‌/خروجي رشته‌هاي کاراکتري: در C++ به چند روش مي‌توان رشته‌هاي کاراکتري را دريافت کرده يا نمايش داد. يک راه استفاده از عملگرهاي کلاس string است که در بخش‌هاي بعدي به آن خواهيم پرداخت. روش ديگر، استفاده از توابع کمکي است که آن را در ادامه شرح مي‌دهيم.

if (*word) cout << "\t\"" << word << "\"\n"; مثال‌ 2-8 روش سادۀ دريافت و نمايش رشته‌هاي کاراکتري: در برنامۀ زير يک رشتۀ کاراکتري به طول 79 کاراکتر اعلان شده و کلماتي که از ورودي خوانده مي‌شود در آن رشته قرار مي‌گيرد: int main() { char word[80]; do { cin >> word; if (*word) cout << "\t\"" << word << "\"\n"; } while (*word); }

چند تابع‌ عضو cin و cout cin.getline() cin.get() cin.ignore() cin.putback() cin.peek() همۀ اين توابع شامل پيشوند cin هستند زيرا آن‌ها عضوي از cin مي‌باشند. به cout شيء فرآيند خروجي مي‌گويند. اين شي نيز شامل تابع cout.put() است. نحوۀ کاربرد هر يک از اين توابع عضو را در ادامه خواهيم ديد. فراخواني cin.getline(str,n); باعث مي‌شود که n کاراکتر به درون str خوانده شود و مابقي کاراکترهاي وارد شده ناديده گرفته مي‌شوند.

با دو پارامتر ‌ cin.getline() تابع اين‌ برنامه‌ ورودي‌ را خط به‌ خط به خروجي مي‌فرستد: int main() { char line[80]; do { cin.getline(line,80); if (*line) cout << "\t[" << line << "]\n"; } while (*line); }

با سه پارامتر cin.getlineتابع()‌ برنامه زير، متن ورودي را جمله به جمله تفکيک مي‌نمايد: int main() { char clause[20]; do { cin.getline(clause, 20, ','); if (*clause) cout << "\t[" << clause << "]\n"; } while (*clause); }

cout << count << " e's were counted.\n"; } تابع‌ cin.get() اين برنامه‌ تعداد حرف‌ 'e' در جريان‌ ورودي‌ را شمارش‌ مي‌كند. تا وقتي cin.get(ch) کاراکترها را با موفقيت به درون ch مي‌خواند، حلقه ادامه مي‌يابد: int main() { char ch; int count = 0; while (cin.get(ch)) if (ch = = 'e') ++count; cout << count << " e's were counted.\n"; }

تابع‌ cout.put() برنامۀ زير، اولين حرف از هر کلمۀ ورودي را به حرف بزرگ تبديل کرده و آن را مجددا در خروجي چاپ مي‌کند: int main() { char ch, pre = '\0'; while (cin.get(ch)) { if (pre = = ' ' || pre = = '\n') cout.put(char(toupper(ch))); else cout.put(ch); pre = ch; }

cin.ignore() و cin.putback() توابع‌ با استفاده از برنامۀ زير، تابعي آزمايش مي‌شود که اين تابع اعداد صحيح را از ورودي استخراج مي‌کند: int nextInt(); int main() { int m = nextInt(), n = nextInt(); cin.ignore(80,'\n'); // ignore rest of input line cout << m << " + " << n << " = " << m+n << endl; } int nextInt() { char ch; int n; while (cin.get(ch)) if (ch >= '0' && ch <= '9') // next character is a digit { cin.putback(ch); // put it back so it can be cin >> n; // read as a complite int break; return n; ‌

تابع‌ cin.peek() اين‌ نسخه‌ از تابع‌ nextInt() معادل‌ آن‌ است‌ كه‌ در مثال‌ قبلي‌ بود: int nextInt() { char ch; int n; while (ch = cin.peek()) if (ch >= '0' && ch <= '9') { cin >> n; break; } else cin.get(ch); return n;

توابع‌ كاراكتري‌ C استاندارد در مثال 6-8 به تابعtoupper() اشاره شد. اين فقط يکي از توابعي است که براي دست‌کاري کاراکترها استفاده مي‌شود. ساير توابعي که در سرفايل <ctype.h> يا <cctype> تعريف شده به شرح زير است: شرح نام تابع int isalnum(int c); اگر c کاراکتر الفبايي يا عددي باشد مقدار غيرصفر وگرنه صفر را برمي‌گرداند isalnum() int isalpha(int c); اگر c کاراکتر الفبايي باشد مقدار غيرصفر و در غير آن، صفر را برمي‌گرداند isalpha()

شرح نام تابع int iscntrl(int c); اگر c کاراکتر کنترلي باشد مقدار غيرصفر و در غير آن، صفر را برمي‌گرداند iscntrl() int isdigit(int c); اگر c کاراکتر عددي باشد، مقدار غيرصفر و در غير آن، صفر را برمي‌گرداند isdigit() int isgraph(int c); اگر c کاراکتر چاپي و غيرخالي باشد مقدار غيرصفر وگرنه صفر را برمي‌گرداند isgraph() int islower(int c); اگر c حرف کوچک باشد مقدار غيرصفر و در غير آن، صفر را برمي‌گرداند islower() int isprint(int c); اگر c کاراکتر قابل چاپ باشد مقدار غيرصفر و در غير آن، صفر را برمي‌گرداند isprint()

ispunct() isspace() isupper() isxdigit() tolower() toupper() شرح نام تابع int ispunct(int c); اگر c کاراکتر چاپي به غير از حروف و اعداد و فضاي خالي باشد، مقدار غيرصفر برمي‌گرداند وگرنه مقدار صفر را برمي‌گرداند ispunct() int isspace(int c); اگر c کاراکتر فضاي سفيد شامل فضاي خالي ' ' و عبور فرم '\f' و خط جديد '\n' و بازگشت نورد '\r' و پرش افقي '\t' و پرش عمودي '\v' باشد، مقدار غيرصفر را برمي‌گرداند وگرنه صفر را برمي‌گرداند isspace() int isupper(int c); اگر c حرف بزرگ باشد، مقدار غيرصفر برمي‌گرداند وگرنه صفر را برمي‌گرداند isupper() int isxdigit(int c); اگر c يکي از ده کاراکتر عددي يا يکي از دوازده حرف عدد شانزده‌دهي شامل 'a' و 'b' و 'c' و 'd' و 'e' و 'f' و 'A' و 'B' و 'C' و 'D' و 'E' و 'F' باشد، مقدار غيرصفر برمي‌گرداند وگرنه مقدار صفر را برمي‌گرداند isxdigit() int tolower(int c); اگر c حرف بزرگ باشد، کاراکتر کوچک معادل آن را برمي‌گرداند وگرنه خود c را برمي‌گرداند tolower() int toupper(int c); اگر c حرف کوچک باشد، کاراکتر بزرگ معادل آن را برمي‌گرداند وگرنه خود c را برمي‌گرداند toupper()

توجه کنيد که همۀ توابع فوق يک پارامتر از نوع int دريافت مي‌کنند و يک مقدار int را برمي‌گردانند. علت اين است که نوع char در اصل يک نوع صحيح است. در عمل وقتي توابع فوق را به کار مي‌برند، يک مقدار char به تابع مي‌فرستند و مقدار بازگشتي را نيز در يک char ذخيره مي‌کنند. به همين خاطر اين توابع را به عنوان «توابع کاراکتري» در نظر مي‌گيريم.

آرايه‌اي از رشته‌ها به خاطر داريد که گفتيم يک آرايۀ دوبعدي در حقيقت آرايه‌اي يک بعدي است که هر کدام از اعضاي آن يک آرايۀ يک بعدي ديگر است. مثلا در آرايۀ دو بعدي که به شکل مقابل اعلان شده باشد: char name[5][20]; اين آرايه در اصل پنج عضو دارد که هر عضو مي‌تواند بيست کاراکتر داشته باشد. اگر آرايۀ فوق را با تعريف رشته‌هاي کاراکتري مقايسه کنيم، نتيجه اين مي‌شود که آرايۀ بالا يک آرايۀ پنج عنصري است که هر عنصر آن يک رشتۀ کاراکتري بيست حرفي است. اين آرايه را مي‌توانيم به شکل مقابل تصور کنيم.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 از طريق name[0] و name[1] و name[2] و name[3] و name[4] مي‌توانيم به هر يک از رشته‌هاي کاراکتري در آرايۀ بالا دسترسي داشته باشيم. يعني آرايۀ name گرچه به صورت يک آرايۀ دوبعدي اعلان شده ليکن به صورت يک آرايۀ يک بعدي با آن رفتار مي‌شود.

آرايه‌اي از رشته‌هاي کاراکتري برنامۀ زير چند رشتۀ کاراکتري را از ورودي مي‌خواند و آن‌ها را در يک آرايه ذخيره کرده و سپس مقادير آن آرايه را چاپ مي‌کند: int main() { char name[5][20]; int count=0; cout << "Enter at most 4 names with at most 19 characters:\n"; while (cin.getline(name[count++], 20)) ; --count; cout << "The names are:\n"; for (int i=0; i<count; i++) cout << "\t" << i << ". [" << name[i] << "]" << endl; }

يك‌ آرايۀ رشته‌اي‌ پويا اين برنامه نشان مي‌دهد که چگونه مي‌توان از کاراکتر '$' به عنوان کاراکتر نگهبان در تابع getline() استفاده کرد. مثال زير تقريبا معادل مثال 9-9 است. برنامۀ زير مجموعه‌اي از اسامي را مي‌خواند، طوري که هر اسم روي يک خط نوشته مي‌شود و هر اسم با کاراکتر '\n' پايان مي‌يابد. اين اسامي در آرايۀ name ذخيره مي‌شوند. سپس نام‌هاي ذخيره شده در آرايۀ name چاپ مي‌شوند:

int main() { char buffer[80]; cin.getline(buffer,80,'$'); char* name[4]; name[0] = buffer; int count = 0; for (char* p=buffer; *p ! '\0'; p++) if (*p == '\n') { *p = '\0'; // end name[count] name[++count] = p+1; // begin next name } cout << "The names are:\n"; for (int i=0; i<count; i++) cout << "\t" << i << . [" << name[i] << "]" << endl;

مقداردهي‌ يك‌ آرايۀ‌ رشته‌اي‌ اين برنامه هم آرايۀ رشته‌اي name را مقداردهي کرده و سپس مقادير آن را چاپ مي‌نمايد: int main() {char* name[]= { "Mostafa Chamran", "Mehdi Zeinoddin", "Ebrahim Hemmat" }; cout << "The names are:\n"; for (int i = 0; i < 3; i++) cout << "\t" << i << ". [" << name[i] << "]" << endl; }

توابع استاندارد رشته‌هاي کاراکتري: سرفايل‌ <cstring> که به آن «کتابخانۀ رشته‌هاي کاراکتري» هم مي‌گويند، شامل خانوادۀ توابعي است که براي دست‌کاري رشته‌هاي کاراکتري خيلي مفيدند. مثال بعدي ساده‌ترين آن‌ها يعني تابع طول رشته را نشان مي‌دهد. اين تابع، طول يک رشتۀ کاراکتري ارسال شده به آن (يعني تعداد کاراکترهاي آن رشته) را برمي‌گرداند.

تابع‌ strlen(): برنامۀ زير يک برنامۀ آزمون ساده براي تابع strlen() است. وقتي strlen(s) فراخواني مي‌شود، تعداد کاراکترهاي درون رشتۀ s که قبل از کاراکتر NUL قرار گرفته‌اند، بازگشت داده مي‌شود: #include <cstring> int main() { char s[] = "ABCDEFG"; cout << "strlen(" << s << ") = " << strlen(s) << endl; cout << "strlen(\"\") = " << strlen("") << endl; char buffer[80]; cout << "Enter string: "; cin >> buffer; cout << "strlen(" << buffer << ") = " << strlen(buffer) << endl; }

توابع‌ strrchr(), strchr(), strstr(): #include <cstring> int main() { char s[] = "The Mississippi is a long river."; cout << "s = \"" << s << "\"\n"; char* p = strchr(s, ' '); cout << "strchr(s, ' ') points to s[" << p - s << "].\n"; p = strchr(s, 'e'); cout << "strchr(s, 'e') points to s[" << p - s << "].\n"; p = strrchr(s, 'e'); cout << "strrchr(s, 'e') points to s[" << p - s << "].\n"; p = strstr(s, "is"); cout << "strstr(s, \"is\") points to s[" << p – s << "].\n"; p = strstr(s, "isi"); if (p == NULL) cout << "strstr(s, \"isi\") returns NULL\n"; }

تابع‌ strcpy(): برنامۀ زير نشان مي‌دهد که فراخواني strcpy(s1, s2) چه تاثيري دارد: #include <iostream> #include <cstring> int main() { char s1[] = "ABCDEFG"; char s2[] = "XYZ"; cout << "Before strcpy(s1,s2):\n"; cout << "\ts1 = [" << s1 << "], length = " << strlen(s1) << endl; cout << "\ts2 = [" << s2 << "], length = " << strlen(s2) strcpy(s1,s2); cout << "After strcpy(s1,s2):\n"; }

تابع‌ :strncpy() برنامۀ زير بررسي مي‌کند که فراخوانيstrncpy(s1, s2, n) چه اثري دارد: int main() { char s1[] = "ABCDEFG"; char s2[] = "XYZ"; cout << "Before strncpy(s1,s2,2):\n"; cout << "\ts1 = [" << s1 << "], length = " << strlen(s1) << endl; cout << "\ts2 = [" << s2 << "], length = " << strlen(s2) strncpy(s1,s2,2); cout << "After strncpy(s1,s2,2):\n"; }

تابع الصاق رشته :strcat() برنامۀ زير بررسي مي‌کند که فراخواني strcat(s1, s2) چه تاثيري دارد: int main() { char s1[] = "ABCDEFG"; char s2[] = "XYZ"; cout << "Before strcat(s1,s2):\n"; cout << "\ts1 = [" << s1 << "], length = " << strlen(s1) << endl; cout << "\ts2 = [" << s2 << "], length = " << strlen(s2) strcat(s1,s2); cout << "After strcat(s1,s2):\n"; }

رشته‌هاي کاراکتري در C++ استاندارد‌ : هستند. اما اين سرعت پردازش، هزينه‌اي هم دارد: خطر خطاهاي زمان اجرا. ين خطاها معمولا از اين ناشي مي‌شوند که فقط بر کاراکتر NUL به عنوان پايان رشته تکيه مي‌شود. C++ رشته‌هاي کاراکتري خاصي نيز دارد که امن‌تر و مطمئن‌تر هستند. در اين رشته‌ها، طول رشته نيز درون رشته ذخيره مي‌شود و لذا فقط به کاراکتر NUL براي مشخص نمودن انتهاي رشته اکتفا نمي‌شود.

نگاهي دقيق‌تر به تبادل داده‌ها وقتي مي‌خواهيم داده‌هايي را وارد کنيم، اين داده‌ها را در قالب مجموعه‌اي از کاراکترها تايپ مي‌کنيم. همچنين وقتي مي‌خواهيم نتايجي را به خارج از برنامه بفرستيم، اين نتايج در قالب مجموعه‌اي از کاراکترها نمايش داده مي‌شوند. لازم است که اين کاراکترها به نحوي براي برنامه تفسير شوند. مثلا وقتي قصد داريم يک عدد صحيح را وارد کنيم، چند کاراکتر عددي تايپ مي‌کنيم

حالا ساز و کاري لازم است که از اين کاراکترها يک مقدار صحيح بسازد و به برنامه تحويل دهد. همچنين وقتي قصد داريم يک عدد اعشاري را به خروجي بفرستيم، بايد با استفاده از راه‌کاري، آن عدد اعشاري به کاراکترهايي تبديل شود تا در خروجي نمايش يابد. C++ بر عهده دارند.

جريان‌ها اين وظايف را در C++ بر عهده دارند جريان‌ها اين وظايف را در C++ بر عهده دارند. جريان‌ها شبيه پالايه‌اي هستند که داده‌ها را به کاراکتر تبديل مي‌کنند و کاراکترها را به داده‌هايي از يک نوع بنيادي تبديل مي‌نمايند. به طور کلي، ورودي‌ها و خروجي‌ها را يک کلاس جريان به نام stream کنترل مي‌کند. اين کلاس خود به زيرکلاس‌هايي تقسيم مي‌شود:

شيء istream جرياني است که داده‌هاي مورد نياز را از کاراکترهاي وارد شده از صفحه کليد، فراهم مي‌کند. شيء ostream جرياني است که داده‌هاي حاصل را به کاراکترهاي خروجي قابل نمايش روي صفحۀ نمايش‌گر تبديل مي‌نمايد. شيء ifstream جرياني است که داده‌هاي مورد نياز را از داده‌هاي داخل يک فايل، فراهم مي‌کند. شيء ofstream جرياني است که داده‌هاي حاصل را درون يک فايل ذخيره مي‌نمايد. اين جريان‌ها و طريقۀ استفاده از آن‌ها را در ادامه خواهيم ديد.

cout << "n = " << n << endl; } استفاده‌ از عملگر بيرون‌كشي‌ براي‌ كنترل کردن يك‌ حلقه‌ : int main() { int n; while (cin >> n) cout << "n = " << n << endl; }

ورودي‌ قالب‌بندي نشده‌: سرفايل <iostream> توابع مختلفي براي ورودي دارد. اين توابع براي وارد کردن کاراکترها و رشته‌هاي کاراکتري به کار مي‌روند که کاراکترهاي فضاي سفيد را ناديده نمي‌گيرند. رايج‌ترين آن‌ها، تابع cin.get() براي دريافت يک کاراکتر تکي و تابع cin.getline() براي دريافت يک رشتۀ کاراکتري است.

{ if (c >= 'a' && c <= 'z') c += 'A' - 'a'; // capitalize c دريافت كاراكترها با استفاده از تابع :cin.get() while (cin.get(c)) { if (c >= 'a' && c <= 'z') c += 'A' - 'a'; // capitalize c cout.put(c); }

وارد كردن يک رشتۀ کاراکتري به وسيلۀ تابع :cin.getline() برنامۀ زير نشان مي‌دهد که چطور مي‌توان داده‌هاي متني را خط به خط از ورودي خوانده و درون يک آرايۀ رشته‌اي قرار داد::: const int LEN=32; // maximum word length const int SIZE=10; // array size typedef char Name[LEN]; // defines Name to be a C_string type int main() { Name martyr[SIZE]; // defines martyr to be an array of 10 names int n=0; while(cin.getline(martyr[n++], LEN) && n<SIZE) ; --n; for (int i=0; i<n; i++) cout << '\t' << i+1 << ". " << martyr[i] << endl; }

نوع‌ string در ++C استاندارد: در C++ استاندارد نوع داده‌اي خاصي به نام string وجود دارد که مشخصات اين نوع در سرفايل <string> تعريف شده است. براي آشنايي با اين نوع جديد، از طريقۀ اعلان و مقداردهي آن شروع مي‌کنيم. اشيايي که از نوع string هستند به چند طريق مي‌توانند اعلان و مقداردهي شوند: string s1; // s1 contains 0 characters string s2 = "PNU University"; // s2 contains 14 characters string s3(60, '*'); // s3 contains 60 asterisks string s4 = s3; // s4 contains 60 asterisks string s5(s2, 4, 2); // s5 is the 2-character string "Un"

استفاده‌ از نوع‌ string کد زير يک مجموعه کاراکتر را از ورودي مي‌گيرد و سپس بعد از هر کاراکتر "E" يک علامت ويرگول ',' اضافه مي‌نمايد. مثلا اگر عبارت "The SOFTWARE MOVEMENT is began" وارد شود، برنامۀ زير، آن را به جملۀ زير تبديل مي‌کند: The SOFTWARE, MOVE,ME,NT is began متن برنامه اين چنين است: string word; int k; while (cin >> word) { k = word.find("E") + 1; if (k < word.length()) word.relace(k, 0, ","); cout << word << ' '; }

فايل‌ها يکي از مزيت‌هاي رايانه، قدرت نگهداري اطلاعات حجيم است. فايل‌ها اين قدرت را به رايانه مي‌دهند. اگر چيزي به نام فايل وجود نمي‌داشت، شايد رايانه‌ها به شکل امروزي توسعه و کاربرد پيدا نمي‌کردند. چون اغلب برنامه‌هاي امروزي با فايل‌ها سر و کار دارند، يک برنامه‌نويس لازم است که با فايل آشنا باشد و بتواند با استفاده از اين امکان ذخيره و بازيابي، کارايي برنامه‌هايش را ارتقا دهد.

پردازش‌ فايل‌ در C++ بسيار شبيه‌ تراکنش‌هاي معمولي‌ ورودي‌ و خروجي‌ است زيرا اين‌ها همه از اشياي جريان مشابهي بهره مي‌برند. جريان fstream براي تراکنش برنامه با فايل‌ها به کار مي‌رود. fstream نيز به دو زيرشاخۀ ifstream و ofstream تقسيم مي‌شود. جريان ifstream براي خواندن اطلاعات از يک فايل به کار مي‌رود و جريان ofstream براي نوشتن اطلاعات درون يک فايل استفاده مي‌شود.

ifstream readfile("INPUT.TXT"); ofstream writefile("OUTPUT.TXT"); پس بايد دستور پيش‌پردازندۀ #include <fstream> را به ابتداي برنامه بيافزاييد. سپس مي‌توانيد عناصري از نوع جريان فايل به شکل زير تعريف کنيد: ifstream readfile("INPUT.TXT"); ofstream writefile("OUTPUT.TXT"); طبق کدهاي فوق، readfile عنصري است که داده‌ها را از فايلي به نام INPUT.TXT مي‌خواند و writefile نيز عنصري است که اطلاعاتي را در فايلي به نام OUTPUT.TXT مي‌نويسد. اکنون مي‌توان با استفاده از عملگر >> داده‌ها را به درون readfile خواند و با عملگر << اطلاعات را درون writefile نوشت. به مثال زير توجه کنيد.

يک دفتر تلفن‌ برنامۀ زير، چند نام و تلفن مربوط به هر يک را به ترتيب از کاربر دريافت کرده و در فايلي به نام PHONE.TXT ذخيره مي‌کند. کاربر براي پايان دادن به ورودي بايد عدد 0 را تايپ کند. #include <fstream> #include <iostream> using namespace std; int main() { ofstream phonefile("PHONE.TXT"); long number; string name; cout << "Enter a number for each name. (0 for quit): "; for ( ; ; ) { cout << "Number: "; cin >> number; if (number == 0) break; phonefile << number << ' '; cout << "Name: "; cin >> name; phonefile << name << ' '; cout << endl; }

جستجوي يک شماره در دفتر تلفن‌ اين برنامه، فايل توليد شده توسط برنامۀ قبل را به کار مي‌گيرد و درون آن به دنبال يک شماره تلفن مي‌گردد: #include <fstream> #include <iostream> using namespace std; int main() { ifstream phonefile("PHONE.TXT"); long number; string name, searchname; bool found=false; cout << "Enter a name for findind it's phone number: "; cin >> searchname; cout << endl; while (phonefile >> number) { phonefile >> name; if (searchname == name) { cout << name << ' ' << number << endl; found = true; } if (!found) cout << searchname << " is not in this phonebook." << endl; }

پايان جلسه هشتم

جلسه نهم «شي‌گرايي»

آنچه در اين جلسه مي خوانيد: 1- اعلان كلاس‌ها 2- سازنده‌ها 3- فهرست مقداردهي در سازنده‌ها 4- توابع‌ دستيابي‌ 5- توابع‌ عضو خصوصي‌ 6- سازندۀ كپي‌ ›››

7- نابود کننده 8 - اشياي ثابت‌ 9- اشاره‌گر به اشيا 10- اعضاي‌ داده‌اي ايستا‌ 11- توابع عضو ايستا

آشنايي با کلاس‌ها و اصول اوليۀ به‌کارگيري آن‌ها. هدف کلي : آشنايي با کلاس‌ها و اصول اوليۀ به‌کارگيري آن‌ها.

هدف‌هاي رفتاري: انتظار مي‌رود پس از پايان اين جلسه بتوانيد: - نحوۀ اعلان «کلاس‌ها» را بدانيد و اعضاي يک کلاس را برشماريد. - تفاوت بين اعضاي «عمومي» و «خصوصي» را شرح داده و نحوۀ اعلان هر کدام را بيان کنيد. - «تابع سازنده» را شناخته و وظيفۀ آن را شرح دهيد. - روش‌هاي گوناگون مقداردهي با استفاده از فهرست مقداردهي را بدانيد. - «تابع سازندۀ کپي» را معرفي کرده و وظيفۀ آن را شرح دهيد. - اعضاي «ايستا» را تعريف کرده و نحوۀ اعلان و استفاده از آن‌ها را بدانيد.

اشيا را مي‌توان با توجه به مشخصات ورفتار آن‌ها دسته بندي کرد. مقدمه‌ «شي‌گرايي» رهيافت جديدي بود که براي پاره اي از مشکلات برنامه نويسي راه حل داشت. اين مضمون از دنياي فلسفه به جهان برنامه‌نويسي آمد و کمک کرد تا معضلات توليد و پشتيباني نرم‌افزار کم‌تر شود. اشيا را مي‌توان با توجه به مشخصات ورفتار آن‌ها دسته بندي کرد.

در بحث شي‌گرايي به دسته‌ها «کلاس» مي‌گويند و به نمونه‌هاي هر کلاس «شي» گفته مي‌شود. مشخصات هر شي را «صفت» مي‌نامند و به رفتارهاي هر شي «متد» مي‌گويند.

برنامه‌نويسي شي‌گرا بر سه ستون استوار است: الف. بسته‌بندي: يعني اين که داده‌هاي مرتبط، با هم ترکيب شوند و جزييات پياده‌سازي مخفي شود. ب. وراثت: در دنياي واقعي، وراثت به اين معناست که يک شي وقتي متولد مي‌شود، خصوصيات و ويژگي‌هايي را از والد خود به همراه دارد .

امتياز وراثت در اين است که از کدهاي مشترک استفاده مي‌شود و علاوه بر اين که مي‌توان از کدهاي قبلي استفاده مجدد کرد، در زمان نيز صرفه‌جويي شده و استحکام منطقي برنامه هم افزايش مي‌يابد.

ج. چند ريختي: که به آن چندشکلي هم مي‌گويند به معناي يک چيز بودن و چند شکل داشتن است. چندريختي بيشتر در وراثت معنا پيدا مي‌کند.

اعلان کلاس با کلمۀ کليدي class شروع مي‌شودسپس نام کلاس مي‌آيد. اعلان كلاس‌ها کد زير اعلان يک کلاس را نشان مي‌دهد. class Ratio { public: void assign(int, int); viod print(); private: int num, den; }; اعلان کلاس با کلمۀ کليدي class شروع مي‌شودسپس نام کلاس مي‌آيد.

اعلان اعضاي کلاس درون يک بلوک انجام مي‌شود و سرانجام يک سميکولن بعد از بلوک نشان مي‌دهد که اعلان کلاس پايان يافته است. عبارت public و عبارت private . هر عضوي که ذيل عبارت public اعلان شود، يک «عضو عمومي» محسوب مي‌شود و هر عضوي که ذيل عبارت private اعلان شود، يک «عضو خصوصي» محسوب مي‌شود.

سازنده‌ها وظيفۀ تابع سازنده اين است که حافظۀ لازم را براي شيء جديد تخصيص داده و آن را مقداردهي نمايد و با اجراي وظايفي که در تابع سازنده منظور شده، شيء جديد را براي استفاده آماده کند. هر کلاس مي‌تواند چندين سازنده داشته باشد. در حقيقت تابع سازنده مي‌تواند چندشکلي داشته باشد.

سازنده‌ها، از طريق فهرست پارامترهاي متفاوت از يکديگر تفکيک مي‌شوند سازنده‌ها، از طريق فهرست پارامترهاي متفاوت از يکديگر تفکيک مي‌شوند. به مثال بعدي نگاه کنيد. مثال‌ 5-9 افزودن چند تابع سازندۀ ديگر به كلاس Ratio class Ratio { public: Ratio() { num = 0; den = 1; } Ratio(int n) { num = n; den = 1; } Ratio(int n, int d) { num = n; den = d; } void print() { cout << num << '/' << den; } private: int num, den; };

سومين سازنده نيز همان سازندۀ مثال 4-2 است. اين نسخه از كلاس Ratio سه سازنده دارد: اولي هيچ پارامتري ندارد و شيء اعلان شده را با مقدار پيش‌فرض 0 و 1 مقداردهي مي‌کند. دومين سازنده يک پارامتر از نوع int دارد و شيء اعلان شده را طوري مقداردهي مي‌کند که حاصل کسر با مقدار آن پارامتر برابر باشد. سومين سازنده نيز همان سازندۀ مثال 4-2 است.

يک کلاس مي‌تواند سازنده‌هاي مختلفي داشته باشد يک کلاس مي‌تواند سازنده‌هاي مختلفي داشته باشد. ساده‌ترين آن‌ها، سازنده‌اي است که هيچ پارامتري ندارد. به اين سازنده سازندۀ پيش‌فرض مي‌گويند. اگر در يک کلاس، سازندۀ پيش‌فرض ذکر نشود، کامپايلر به طور خودکار آن را براي کلاس مذکور ايجاد مي‌کند.

فهرست مقداردهي در سازنده‌ها سازنده‌ها اغلب به غير از مقداردهي داده‌هاي عضو يک شي، کار ديگري انجام نمي‌دهند. به همين دليل در C++ يک واحد دستوري مخصوص پيش‌بيني شده که توليد سازنده را تسهيل مي‌نمايد. اين واحد دستوري فهرست مقداردهي نام دارد.

به سومين سازنده در مثال 5-9 دقت کنيد به سومين سازنده در مثال 5-9 دقت کنيد. اين سازنده را مي‌توانيم با استفاده از فهرست مقداردهي به شکل زير خلاصه کنيم: Ratio(int n, int d) : num(n), den(d) { } مثال‌ 6-9 استفاده‌ از فهرست مقداردهي در كلاس Ratio class Ratio { public: Ratio() : num(0) , den(1) { } Ratio(int n) : num(n) , den(1) { } Ratio(int n, int d) : num(n), den(d) { } private: int num, den; };

توابع‌ دستيابي‌ داده‌هاي عضو يک کلاس معمولا به صورت خصوصي (private) اعلان مي‌شوند تا دستيابي به آن‌ها محدود باشد اما همين امر باعث مي‌شود که نتوانيم در مواقع لزوم به اين داده‌ها دسترسي داشته باشيم. براي حل اين مشکل از توابعي با عنوان توابع دستيابي استفاده مي‌کنيم.

تابع دستيابي يک تابع عمومي عضو کلاس است و به همين دليل اجازۀ دسترسي به اعضاي داده‌اي خصوصي را دارد. با استفاده از توابع دستيابي فقط مي‌توان اعضاي داده‌اي خصوصي را خواند ولي نمي‌توان آن‌ها را دست‌کاري کرد.

مثال‌ 8-9 افزودن توابع دستيابي به كلاس Ratio class Ratio { public: Ratio(int n=0, int d=1) : num(n) , den(d) { } int numerator() { return num; } int denomerator() { return den; } private: int num, den; }; در اين‌جا توابع numerator() و denumerator() مقادير موجود در داده‌هاي عضو خصوصي را نشان مي‌دهند.

توابع‌ عضو خصوصي‌ توابع عضو را گاهي مي‌توانيم به شکل يک عضو خصوصي کلاس معرفي کنيم. واضح است که چنين تابعي از داخل برنامۀ اصلي به هيچ عنوان قابل دستيابي نيست. اين تابع فقط مي‌تواند توسط ساير توابع عضو کلاس دستيابي شود. به چنين تابعي يک تابع سودمند محلي مي‌گوييم.

در برنامه زير، کلاس Ratio داراي يک تابع عضو خصوصي به نام toFloat() است مثال 9-9 استفاده از توابع عضو خصوصي class Ratio { public: Ratio(int n=0, int d=1) : num(n), den(d) { } void print() { cout << num << '/' << den << endl; } void printconv() { cout << toFloat() << endl; } private: int num, den; double toFloat(); }; اين تابع فقط درون بدنۀ تابع عضو printconv() استفاده شده و به انجام وظيفۀ آن کمک مي‌نمايد و هيچ نقشي در برنامۀ اصلي ندارد.

سازندۀ‌ كپي‌ int x; int x=k; مي‌دانيم که به دو شيوه مي‌توانيم متغير جديدي تعريف نماييم: int x; int x=k; در روش اول متغيري به نام x از نوع int ايجاد مي‌شود. در روش دوم هم همين کار انجام مي‌گيرد با اين تفاوت که پس از ايجاد x مقدار موجود در متغير k که از قبل وجود داشته درون x کپي مي‌شود. اصطلاحا x يک کپي از k است.

Ratio y(x); کد بالا يک شي به نام y از نوع Ratio ايجاد مي‌کند و تمام مشخصات شيء x را درون آن قرار مي‌دهد. اگر در تعريف کلاس، سازندۀ کپي ذکر نشود (مثل همۀ کلاس‌هاي قبلي) به طور خودکار يک سازندۀ کپي پيش‌فرض به کلاس افزوده خواهد شد.

مثال 10-9 افزودن يك سازندۀ كپي به كلاس Ratio { public: Ratio(int n=0, int d=1) : num(n), den(d) { } Ratio(const Ratio& r) : num(r.num), den(r.den) { } void print() { cout << num << '/' << den; } private: int num, den; }; در مثال بالا، تابع سازندۀ کپي طوري تعريف شده که عنصرهاي num و den از پارامتر r به درون عنصرهاي متناظر در شيء جديد کپي شوند.

1 – وقتي که يک شي هنگام اعلان از روي شيء ديگر کپي شود. سازندۀ کپي در سه وضعيت فرا خوانده مي‌شود: 1 – وقتي که يک شي هنگام اعلان از روي شيء ديگر کپي شود. 2 – وقتي که يک شي به وسيلۀ مقدار به يک تابع ارسال شود. 3 – وقتي که يک شي به وسيلۀ مقدار از يک تابع بازگشت داده شود .

هر کلاس فقط يک نابودکننده دارد. نابود کننده هر کلاس فقط يک نابودکننده دارد. وقتي که يک شي ايجاد مي‌شود، تابع سازنده به طور خودکار براي ساختن آن فراخواني مي‌شود. وقتي که شي به پايان زندگي‌اش برسد، تابع عضو ديگري به طور خودکار فراخواني مي‌شود تا نابودکردن آن شي را مديريت کند. اين تابع عضو، نابودکننده ناميده مي‌شود . سازنده وظيفه دارد تا منابع لازم را براي شي تخصيص دهد و نابودکننده وظيفه دارد آن منابع را آزاد کند.

مثال‌ 12-9 افزودن يك نابودكننده به كلاس Ratio class Ratio { public: Ratio() { cout << "OBJECT IS BORN.\n"; } ~Ratio() { cout << "OBJECT DIES.\n"; } private: int num, den; };

اشياي ثابت‌ اگر قرار است شيئي بسازيد که در طول اجراي برنامه هيچ‌گاه تغيير نمي‌کند، بهتر است منطقي رفتار کنيد و آن شي را به شکل ثابت اعلان نماييد. اعلان‌هاي زير چند ثابت آشنا را نشان مي‌دهند: اشيا را نيز مي‌توان با استفاده از عبارت const به صورت يک شيء ثابت اعلان کرد: const Ratio PI(22,7); const char BLANK = ' '; const int MAX_INT = 2147483647; const double PI = 3.141592653589793; void int(float a[], const int SIZE);

اشاره‌گر به اشيا مي‌توانيم اشاره‌گر به اشياي کلاس نيز داشته باشيم. از آن‌جا که يک کلاس مي‌تواند اشياي داده‌اي متنوع و متفاوتي داشته باشد، اشاره‌گر به اشيا بسيار سودمند و مفيد است. اشاره‌گر به اشيا براي ساختن فهرست‌هاي پيوندي و درخت‌هاي داده‌اي به کار مي‌رود.

مثال‌ 13-9 استفاده از اشاره‌گر به اشيا class X { public: int data; }; main() { X* p = new X; (*p).data = 22; // equivalent to: p->data = 22; cout << "(*p).data = " << (*p).data << " = " << p->data << endl; p->data = 44; cout << " p->data = " << (*p).data << " = " << p->data << endl; }

در اين مثال، p اشاره‌گري به شيء x است. پس. p يک شيء x است و (. p) در اين مثال، p اشاره‌گري به شيء x است. پس *p يک شيء x است و (*p).data دادۀ عضو آن شي را دستيابي مي‌کند. حتما بايد هنگام استفاده از *p آن را درون پرانتز قرار دهيد زيرا عملگر انتخاب عضو (.) تقدم بالاتري نسبت به عملگر مقداريابي (*) دارد. اگر پرانتزها قيد نشوند و فقط *p.data نوشته شود، کامپايلر اين خط را به صورت *(p.data) تفسير خواهد کرد که اين باعث خطا مي‌شود.

Node(int d, Node* p=0) : data(d), next(p) { } int data; Node* next; }; مثال بعدي اهميت بيشتري دارد و کاربرد اشاره‌گر به اشيا را بهتر نشان مي‌دهد. مثال 14-9 فهرست‌هاي پيوندي با استفاده از كلاس Node‌ به کلاسي که در زير اعلان شده دقت کنيد: class Node { public: Node(int d, Node* p=0) : data(d), next(p) { } int data; Node* next; };

عبارت بالا کلاسي به نام Node تعريف مي‌کند که اشياي اين کلاس داراي دو عضو داده‌اي هستند که يکي متغيري از نوع int است و ديگري يک اشاره‌گر از نوع همين کلاس. اين کار واقعا ممکن است و باعث مي‌شود بتوانيم يک شي را با استفاده از همين اشاره‌گر به شيء ديگر پيوند دهيم و يک زنجيره بسازيم.

اگر اشياي q و r و s از نوع Node باشند، مي‌توانيم پيوند اين سه شي را به صورت زير مجسم کنيم: int data q Node* next r s

for ( ; p->next; p = p->next) به تابع سازنده نيز دقت کنيد که چطور هر دو عضو داده‌اي شيء جديد را مقداردهي مي‌کند. int main() { int n; Node* p; Node* q=0; while (cin >> n) { p = new Node(n, q); q = p; } for ( ; p->next; p = p->next) cout << p->data << " -> "; cout << "*\n";

ج - پس از دومين تکرار حلقه int data q شکل زير روند اجراي برنامه را نشان مي‌دهد. Node* next الف - قبل از شروع حلقه int data q ب - پس از اولين تکرار حلقه int data p Node* next Node* next int data q int data int data p Node* next Node* next Node* next ج - پس از دومين تکرار حلقه

اعضاي‌ داده‌اي ايستا‌ هر وقت که شيئي از روي يک کلاس ساخته مي‌شود، آن شي مستقل از اشياي ديگر، داده‌هاي عضو خاص خودش را دارد. گاهي لازم است که مقدار يک عضو داده‌اي در همۀ اشيا يکسان باشد. اگر اين عضو مفروض در همۀ اشيا تکرار شود، هم از کارايي برنامه مي‌کاهد و هم حافظه را تلف مي‌کند. در چنين مواقعي بهتر است آن عضو را به عنوان يک عضو ايستا اعلان کنيم.

static int n; // declaration of n as a static data member class X { public: static int n; // declaration of n as a static data member }; int X::n = 0; // definition of n خط آخر نشان مي‌دهد که متغيرهاي ايستا را بايد به طور مستقيم و مستقل از اشيا مقداردهي کرد.

متغيرهاي ايستا به طور پيش‌فرض با صفر مقداردهي اوليه مي‌شوند متغيرهاي ايستا به طور پيش‌فرض با صفر مقداردهي اوليه مي‌شوند. بنابراين مقداردهي صريح به اين گونه متغيرها ضروري نيست مگر اين که بخواهيد يک مقدار اوليۀ غير صفر داشته باشيد. مثال 15-9 يك عضو داده‌اي ايستا کد زير، کلاسي به نام widget اعلان مي‌کند که اين کلاس يک عضو داده‌اي ايستا به نام count دارد. اين عضو، تعداد اشياي widget که موجود هستند را نگه مي‌دارد. هر وقت که يک شيء widget ساخته مي‌شود، از طريق سازنده مقدار count يک واحد افزايش مي‌يابد و هر زمان که يک شيء widget نابود مي‌شود، از طريق نابودکننده مقدار count يک واحد کاهش مي‌يابد:

class Widget { public: Widget() { ++count; } ~Widget() { --count; } static int count; }; int Widget::count = 0; main() { Widget w, x; cout << "Now there are " << w.count << " widgets.\n"; { Widget w, x, y, z; } Widget y; Now there are 2 widgets. Now there are 6 widgets. Now there are 3 widgets.

توجه کنيد که چگونه چهار شيء widget درون بلوک داخلي ايجاد شده است توجه کنيد که چگونه چهار شيء widget درون بلوک داخلي ايجاد شده است. هنگامي که اجراي برنامه از آن بلوک خارج مي‌شود، اين اشيا نابود مي‌شوند و لذا تعداد کل widgetها از 6 به 2 تقليل مي‌يابد. يک عضو داده‌اي ايستا مثل يک متغير معمولي است: فقط يک نمونه از آن موجود است بدون توجه به اين که چه تعداد شي از آن کلاس موجود باشد. از آن‌جا که عضو داده‌اي ايستا عضوي از کلاس است، مي‌توانيم آن را به شکل يک عضو خصوصي نيز اعلان کنيم.

* مثال 16-9 يك عضو داده‌اي ايستا و خصوصي class Widget { public: Widget() { ++count; } ~Widget() { --count; } int numWidgets() { return count; } private: static int count; };

int Widget::count = 0; main() { Widget w, x; cout << "Now there are " << w.numWidgets() << " widgets.\n"; { Widget w, x, y, z; } Widget y;

اين برنامه مانند مثال 15-9 کار مي‌کند با اين تفاوت که متغير ايستاي count به شکل يک عضو خصوصي اعلان شده و به همين دليل به تابع دستيابي numWidgets() نياز داريم تا بتوانيم درون برنامۀ اصلي به متغير count دسترسي داشته باشيم. مي‌توانيم کلاس Widget و اشياي x و y و w را مانند مقابل تصور کنيم: Widget() ~Widget() numWidgets() count 3 Widget x y w

12- توابع عضو ايستا با دقت در مثال قبلي به دو ايراد بر مي‌خوريم: اول اين که گرچه متغير count يک عضو ايستا است ولي براي خواندن آن حتما بايد از يک شيء موجود استفاده کنيم. در مثال قبلي از شيء w براي خواندن آن استفاده کرده‌ايم. اين باعث مي‌شود که مجبور شويم هميشه مواظب باشيم عضو ايستاي مفروض از طريق يک شي که الان موجود است فراخواني شود.

static int num() { return count; } private: static int count; }; * مثال 17-9 يك تابع عضو ايستا کد زير همان کد مثال قبلي است با اين فرق که در اين کد، تابع دستيابي کننده نيز به شکل ايستا اعلان شده است: class Widget { public: Widget() { ++count; } ~Widget() { --count; } static int num() { return count; } private: static int count; };

int Widget::count = 0; int main() { cout << "Now there are " << Widget::num() << " widgets.\n"; Widget w, x; cout << "Now there are " << Widget::num() << " widgets.\n"; { Widget w, x, y, z; } Widget y;

وقتي تابع num() به صورت ايستا تعريف شود، از اشياي کلاس مستقل مي‌شود و براي فراخواني آن نيازي به يک شيء موجود نيست و مي‌توان با کد Widget::num() به شکل مستقيم آن را فراخواني کرد.

پايان جلسه نهم

جلسه دهم «سربارگذاري عملگرها »

آنچه در اين جلسه مي خوانيد: 1- توابع دوست 2- سربارگذاري عملگر جايگزيني (=)‌ 3- اشاره‌گر this 4- سربارگذاري عملگرهاي حسابي 5- سربارگذاري عملگرهاي جايگزيني حسابي 6- سربارگذاري عملگرهاي رابطه‌اي 7- سربارگذاري عملگرهاي افزايشي و كاهشي

هدف کلي: بيان اهميت سربارگذاري عملگرها براي يک کلاس و نحوۀ انجام اين کار.

هدف‌هاي رفتاري: انتظار مي‌رود پس از پايان اين جلسه بتوانيد: - «سربارگذاري» را تعريف کرده و اهميت آن را شرح دهيد. - «تابع دوست» را تعريف کنيد و علت و اهميت استفاده از چنين توابعي را بيان نماييد. - اشاره‌گر this را بشناسيد و علت استفاده از چنين اشاره‌گري را بيان نماييد. - نحوۀ سربارگذاري عملگر جايگزيني را بيان کنيد. - نحوۀ سربارگذاري عملگرهاي حسابي را بيان کنيد. - نحوۀ سربارگذاري عملگرهاي جايگزيني حسابي را بيان کنيد. - نحوۀ سربارگذاري عملگرهاي رابطه‌اي را بيان کنيد. - نحوۀ سربارگذاري عملگرهاي افزايشي و کاهشي را بيان کنيد.

مقدمه: در C++ مجموعه‌اي از 45 عملگر مختلف وجود دارد که براي کارهاي متنوعي استفاده مي‌شوند. همۀ اين عملگرها براي کار کردن با انواع بنيادي (مثل int و float و char) سازگاري دارند. هنگامي که کلاسي را تعريف مي‌کنيم، در حقيقت يک نوع جديد را به انواع موجود اضافه کرده‌ايم. ممکن است بخواهيم اشياي اين کلاس را در محاسبات رياضي به کار ببريم. اما چون عملگرهاي رياضي (مثل + يا = يا *= ) چيزي راجع به اشياي کلاس جديد نمي‌دانند، نمي‌توانند به درستي کار کنند. C++ براي رفع اين مشکل چاره انديشيده و امکان سربارگذاري عملگرها را تدارک ديده است. سربارگذاري عملگرها به اين معناست که به عملگرها تعاريف جديدي اضافه کنيم تا بتوانند با اشياي کلاس مورد نظر به درستي کار کنند.

1- توابع دوست: class Ratio { friend int numReturn(Ratio); public: Ratio(); ~Ratio(); private: int num, den; } int numReturn(Ratio r) { return r.num; int main() { Ratio x(22, 7);1 – Friend function cout << numReturn(x) << endl; به کد زير نگاه کنيد: اعضايي از کلاس که به شکل خصوصي (private) اعلان مي‌شوند فقط از داخل همان کلاس قابل دستيابي‌اند و از بيرون کلاس (درون بدنۀ اصلي) امکان دسترسي به آن‌ها نيست. اما يک استثنا وجود دارد. تابع دوست تابعي است که عضو يک کلاس نيست اما اجازه دارد به اعضاي خصوصي آن دسترسي داشته باشد.

2-سربارگذاري عملگر جايگزيني(=): در بين عملگرهاي گوناگون، عملگر جايگزيني شايد بيشترين کاربرد را داشته باشد. هدف اين عملگر، کپي کردن يک شي در شيء ديگر است. مانند سازندۀ پيش‌فرض، سازندۀ کپي و نابودکننده، عملگر جايگزيني نيز به طور خودکار براي يک کلاس ايجاد مي‌شود اما اين تابع را مي‌توانيم به شکل صريح درون کلاس اعلان نماييم.

مثال‌: افزودن عملگر جايگزيني به كلاس: کد زير يک رابط کلاس براي Ratio است که شامل سازندۀ پيش‌فرض، سازندۀ کپي و عملگر جايگزيني مي‌باشد: class Ratio { public: Ratio(int = 0, int = 1); Ratio(const Ratio&); void operator=(const Ratio&); private: int num, den; };

به نحو اعلان عملگر جايگزيني دقت نماييد. نام اين تابع عضو، operator= است و فهرست آرگومان آن مانند سازندۀ کپي مي‌باشد يعني يک آرگومان منفرد دارد که از نوع همان کلاس است که به طريقۀ ارجاع ثابت ارسال مي‌شود. عملگر جايگزيني را مي‌توانيم به شکل زير تعريف کنيم: void Ratio::operator=(const Ratio& r) { num = r.num; den = r.den; } کد فوق اعضاي داده‌اي شيء r را به درون اعضاي داده‌اي شيئي که مالک فراخواني اين عملگر است، کپي مي‌کند.

3-اشاره‌گر :this در C++ مي‌توانيم عملگر جايگزيني را به شکل زنجيره‌اي مثل زير به کار ببريم: x = y = z = 3.14; اجراي کد بالا از راست به چپ صورت مي‌گيرد. يعني ابتدا مقدار 3.14 درون z قرار مي‌گيرد و سپس مقدار z درون y کپي مي‌شود و سرانجام مقدار y درون x قرار داده مي‌شود. عملگر جايگزيني که در مثال قبل ذکر شد، نمي‌تواند به شکل زنجيره‌اي به کار رود.

مثال‌ 2-10 سربارگذاري عملگر جايگزيني به شکل صحيح‌: class Ratio { public: Ratio(int =0, int =1); // default constructor Ratio(const Ratio&); // copy constructor Ratio& operator=(const Ratio&); // assignment operator // other declarations go here private: int num, den; }; Ratio& Ratio::operator=(const Ratio& r) { num = r.num; den = r.den; return *this; }

توجه داشته باشيد که عمل جايگزيني با عمل مقداردهي تفاوت دارد، هر چند هر دو از عملگر يکساني استفاده مي‌کنند. مثلا در کد زير: Ratio x(22,7); // this is an initialization Ratio y(x); // this is an initialization Ratio z = x; // this is an initialization Ratio w; w = x; // this is an assignment سه دستور اول، دستورات مقداردهي هستند ولي دستور آخر يک دستور جايگزيني است. دستور مقداردهي، سازندۀ کپي را فرا مي‌خواند ولي دستور جايگزيني عملگر جايگزيني را فراخواني مي‌کند.

4-سربارگذاري عملگرهاي حسابي: چهار عملگر حسابي + و – و * و / در همۀ زبان‌هاي برنامه‌نويسي وجود دارند و با همۀ انواع بنيادي به کار گرفته مي‌شوند. قصد داريم سرباري را به اين عملگرها اضافه کنيم تا بتوانيم با استفاده از آن‌ها، اشياي ساخت خودمان را در محاسبات رياضي به کار ببريم. عملگرهاي حسابي به دو عملوند نياز دارند. مثلا عملگر ضرب (*) در رابطۀ زير: z = x*y; با توجه به رابطۀ فوق و آنچه در بخش قبلي گفتيم، عملگر ضرب سربارگذاري شده بايد دو پارامتر از نوع يک کلاس و به طريق ارجاع ثابت بگيرد و يک مقدار بازگشتي از نوع همان کلاس داشته باشد. پس انتظار داريم قالب سربارگذاري عملگر ضرب براي کلاس Ratio به شکل زير باشد: Ratio operator*(Ratio x, Ratio y) { Ratio z(x.num*y.num, x.den*y.den); return z; }

اگر تابعي عضو کلاس نباشد، نمي‌تواند به اعضاي خصوصي آن کلاس دستيابد اگر تابعي عضو کلاس نباشد، نمي‌تواند به اعضاي خصوصي آن کلاس دستيابد. براي رفع اين محدوديت‌ها، تابع سربارگذاري عملگر ضرب را بايد به عنوان تابع دوست کلاس معرفي کنيم. لذا قالب کلي براي سربارگذاري عملگر ضرب درون کلاس مفروض T به شکل زير است: Class T { friend T operator*(const T&, const T&); public: // public members private: // private members }

T operator*(const T& x, const T& y) { T z; در سربارگذاري عملگرهاي حسابي + و – و / نيز از قالب‌هاي کلي فوق استفاده مي‌کنيم با اين تفاوت که در نام تابع سربارگذاري، به جاي علامت ضرب * بايد علامت عملگر مربوطه را قرار دهيم و دستورات بدنۀ تابع را نيز طبق نياز تغيير دهيم. و از آن‌جا که تابع دوست عضوي از کلاس نيست، تعريف بدنۀ آن بايد خارج از کلاس صورت پذيرد. در تعريف بدنۀ تابع دوست به کلمۀ کليدي friend نيازي نيست و عملگر جداسازي حوزه :: نيز استفاده نمي‌شود: T operator*(const T& x, const T& y) { T z; // required operations for z = x*y return z; }

مثال 3-10 سربارگذاري عملگر ضرب براي کلاس :Ratio class Ratio { friend Ratio operator*(const Ratio&, const Ratio&); public: Ratio(int = 0, int = 1); Ratio(const Ratio&); Ratio& operator=(const Ratio&); // other declarations go here private: int num, den; }; Ratio operator*(const Ratio& x, const Ratio& y) { Ratio z(x.num * y.num , x.den * y.den); return z; } int main() { Ratio x(22,7) ,y(-3,8) ,z; z = x; // assignment operator is called z.print(); cout << endl; x = y*z; // multiplication operator is called x.print(); cout << endl;

5-سربارگذاري عملگرهاي جايگزيني حسابي: به خاطر بياوريد که عملگرهاي جايگزيني حسابي، ترکيبي از عملگر جايگزيني و يک عملگر حسابي ديگر است. مثلا عملگر *= ترکيبي از دو عمل ضرب * و سپس جايگزيني = است. نکتۀ قابل توجه در عملگرهاي جايگزيني حسابي اين است که اين عملگرها بر خلاف عملگرهاي حسابي ساده، فقط يک عملوند دارند. پس تابع سربارگذاري عملگرهاي جايگزيني حسابي بر خلاف عملگرهاي حسابي، مي‌تواند عضو کلاس باشد. سربارگذاري عملگرهاي جايگزيني حسابي بسيار شبيه سربارگذاري عملگر جايگزيني است. قالب کلي براي سربارگذاري عملگر *= براي کلاس مفروض T به صورت زير است: class T { public: T& operator*=(const T&); // other public members private: // private members };

T& T::operator*=(const T& x) { // required operations return *this; } بدنۀ تابع سربارگذاري به قالب زير است: T& T::operator*=(const T& x) { // required operations return *this; } استفاده از اشاره‌گر *this باعث مي‌شود که بتوانيم عملگر *= را در يک رابطۀ زنجيره‌اي به کار ببريم. در C++ چهار عملگر جايگزيني حسابي += و -= و *= و /= وجود دارد. قالب کلي براي سربارگذاري همۀ اين عملگرها به شکل قالب بالا است فقط در نام تابع به جاي *= بايد علامت عملگر مربوطه را ذکر کرد و دستورات بدنۀ تابع را نيز به تناسب، تغيير داد. مثال بعدي نشان مي‌دهد که عملگر *= چگونه براي کلاس Ratio سربارگذاري شده است.

مثال 4-10 كلاس Ratio با عملگر *= سربارگذاري شده:‌ class Ratio { public: Ratio(int = 0, int = 1); Ratio& operator=(const Ratio&); Ratio& operator*=(const Ratio&); // other declarations go here private: int num, den; }; Ratio& Ratio::operator*=(const Ratio& r) { num = num*r.num; den = den*r.den; return *this; } بديهي است که عملگر سربارگذاري شدۀ جايگزيني حسابي، بايد با عملگر سربارگذاري شدۀ حسابي معادلش، نتيجۀ يکساني داشته باشد. مثلا اگر x و y هر دو از کلاس Ratio باشند، آنگاه دو خط کد زير بايد نتيجۀ مشابهي داشته باشند: x *= y; x = x*y;

6-سربارگذاري عملگرهاي رابطه‌اي: شش عملگر رابطه‌اي در C++ وجود دارد که عبارتند از: > و < و => و <= و == و != . اين عملگرها به همان روش عملگرهاي حسابي،يعني به شکل توابع دوست سربارگذاري مي‌شوند. اما نوع بازگشتي‌شان فرق مي‌کند.

حاصل عبارتي که شامل عملگر رابطه‌اي باشد، همواره يک مقدار بولين است حاصل عبارتي که شامل عملگر رابطه‌اي باشد، همواره يک مقدار بولين است. يعني اگر آن عبارت درست باشد، حاصل true است و اگر آن عبارت نادرست باشد، حاصل false است. چون نوع بولين در حقيقت يک نوع عددي صحيح است، مي‌توان به جاي true مقدار 1 و به جاي false مقدار 0 را قرار داد. به همين جهت نوع بازگشتي را براي توابع سربارگذاري عملگرهاي رابطه‌اي، از نوع int قرار داده‌اند.

{ friend int operator==(const T&, const T&); public: // public members قالب کلي براي سربارگذاري عملگر رابطه‌اي == به شکل زير است: class T { friend int operator==(const T&, const T&); public: // public members private: // private members }

int operator==(const T& x,const T& y) همچنين قالب کلي تعريف بدنۀ اين تابع به صورت زير مي‌باشد: int operator==(const T& x,const T& y) { // required operations to finding result return result; } که به جاي result يک مقدار بولين يا يک عدد صحيح قرار مي‌گيرد. ساير عملگرهاي رابطه‌اي نيز از قالب بالا پيروي مي‌کنند.

مثال 5-10 سربارگذاري عملگر تساوي (==) براي كلاس :Ratio class Ratio { friend int operator==(const Ratio&, const Ratio&); frined Ratio operator*(const Ratio&, const Ratio&); // other declarations go here public: Ratio(int = 0, int = 1); Ratio(const Ratio&); Ratio& operator=(const Ratio&); private: int num, den; }; int operator==(const Ratio& x, const Ratio& y) { return (x.num * y.den == y.num * x.den); } چون اشياي کلاس Ratio به صورت کسر هستند، بررسي تساوي x==y معادل بررسي است که براي بررسي اين تساوي مي‌توانيم مقدار (a*d==b*c) را بررسي کنيم. بدنۀ تابع سربارگذاري در مثال همين رابطه را بررسي مي‌کند.

7-سربارگذاري عملگرهاي افزايشي و كاهشي: عملگر افزايشي ++ و کاهشي -- هر کدام دو شکل دارند: 1- شکل پيشوندي. 2-شکل پسوندي. هر کدام از اين حالت‌ها را مي‌توان سربارگذاري کرد.

قالب کلي براي سربارگذاري عملگر پيش‌افزايشي به شکل زير است: T T::operator++() { // required operations return *this; } اين‌جا هم از اشاره‌گر *this استفاده شده. علت هم اين است که مشخص نيست چه چيزي بايد بازگشت داده شود. به همين دليل اشاره‌گر *this به کار رفته تا شيئي که عمل پيش‌افزايش روي آن صورت گرفته، بازگشت داده شود.

افزودن عملگر پيش‌افزايشي به كلاس :Ratio اگر y يک شي از کلاس Ratio باشد و عبارت ++y ارزيابي گردد، مقدار 1 به y افزوده مي‌شود اما چون y يک عدد کسري است، افزودن مقدار 1 به اين کسر اثر متفاوتي دارد. فرض کنيد y=22/7 باشد. حالا داريم:

افزودن عملگر پس‌افزايشي به كلاس Ratio: در عبارت y = x++; از عملگر پس‌افزايشي استفاده کرده‌ايم. تابع اين عملگر بايد طوري تعريف شود که مقدار x را قبل از اين که درون y قرار بگيرد، تغيير ندهد. مي‌دانيم که اشاره‌گر *this به شيء جاري (مالک فراخواني) اشاره دارد. کافي است مقدار اين اشاره‌گر را در يک محل موقتي ذخيره کنيم و عمل افزايش را روي آن مقدار موقتي انجام داده و حاصل آن را بازگشت دهيم. به اين ترتيب مقدار *this تغييري نمي‌کند و پس از شرکت در عمل جايگزيني، درون y قرار مي‌گيرد: int main() { Ratio x(22,7) , y = x++; cout << "y = "; y.print(); cout << ", x = "; x.print();} Ratio Ratio::operator++(int) { Ratio temp = *this; num += den; return temp; } class Ratio { public: Ratio(int n=0, int d=1) : num(n) , den(d) { } Ratio operator++(); //pre-increment Ratio operator++(int); //post-increment void print() { cout << num << '/' << den << endl; } private: int num, den; };

عملگرهاي پيش‌کاهشي و پس‌کاهشي نيز به همين شيوۀ عملگر‌هاي پيش‌افزايشي و پس‌افزايشي سربارگذاري مي‌شوند. غير از اين‌ها، عملگرهاي ديگري نيز مثل عملگر خروجي (<<) ، عملگر ورودي (>>) ، عملگر انديس ([]) و عملگر تبديل نيز وجود دارند که مي‌توان آن‌ها را براي سازگاري براي کلاس‌هاي جديد سربارگذاري کرد.

پايان جلسه دهم

جلسه يازدهم «تركيب و وراثت»

«تركيب و وراثت» مقدمه تركيب وراثت اعضاي حفاظت شد غلبه کردن بر وراثت اشاره‌گرها در وراثت توابع مجازي و چندريختي‌ نابودكنندۀ مجازي <<<

كلاس‌هاي پايۀ انتزاعي پرسش‌هاي گزينه‌اي پرسش‌هاي تشريحي تمرين‌هاي برنامه‌نويسي‌ ضميمه الف : پاسخ‌نامۀ پرسش‌هاي گزينه‌اي ضميمه ب:جدول اسکي ضميمه ج : کلمات کليدي C++ استاندارد ضميمه د : عملگرهاي C++ استاندارد ضميمه هـ : فهرست منابع و مأخذ

هدف کلي: بيان اهميت ترکيب و وراثت در شي‌گرايي و چگونگي انجام اين کارها. هدف‌هاي رفتاري: انتظار مي‌رود پس از پايان اين جلسه بتوانيد: - علت استفاده از «ترکيب» و «وراثت» را در برنامه‌هاي شي‌گرا توضيح دهيد. - نحوۀ ترکيب دو يا چند کلاس را براي ايجاد کلاس جديد، بدانيد. - وراثت را تعريف کنيد.

- «اعضاي حفاظت شدۀ کلاس» را تعريف کنيد و تفاوت اين اعضا با اعضاي عمومي و خصوصي کلاس را شرح دهيد. - نحوۀ غلبه کردن بر وراثت را شرح دهيد. - «تابع مجازي» را تعريف کنيد و علت استفاده از توابع مجازي را بدانيد. - «چندريختي» را تعريف کنيد و شيوۀ پياده‌سازي چندريختي در کلاس‌ها را بدانيد. - «کلاس پايۀ انتزاعي» را تعريف کنيد و علت تعريف اين کلاس‌ها را ذکر کنيد.

مقدمه اغلب اوقات براي ايجاد يک کلاس جديد، نيازي نيست که همه چيز از اول طراحي شود. مي‌توانيم براي ايجاد کلاس مورد نظر، از تعاريف کلاس‌هايي که قبلا ساخته‌ايم، استفاده نماييم. اين باعث صرفه‌جويي در وقت و استحکام منطق برنامه مي‌شود. در شي‌گرايي به دو شيوه مي‌توان اين کار را انجام داد: ترکيب1 و وراثت2. در اين جلسه خواهيم ديد که چگونه و چه مواقعي مي‌توانيم از اين دو شيوه بهره ببريم.

تركيب: ترکيب کلاس‌ها (يا تجميع کلاس‌ها) يعني استفاده از يک يا چند کلاس ديگر در داخل تعريف يک کلاس جديد. هنگامي که عضو داده‌اي کلاس جديد، شيئي از کلاس ديگر باشد، مي‌گوييم که اين کلاس جديد ترکيبي از ساير کلاس‌هاست. به تعريف دو کلاس زير نگاه کنيد.

كلاس Date کد زير، کلاس Date را نشان مي‌دهد که اشياي اين کلاس براي نگهداري تاريخ استفاده مي‌شوند. class Date { public: Date(int y=0, int m=0, int d=0) : year(y), month(m), day(d) {}; void setDate(int y, int m, int d) { year = y; month = m; day = d; } void getDate() { cin >> year >> month >> day ; } void showDate() { cout << year << '/' << month << '/' << day ; } private: int year, month, day; }

كلاس Book کد زير، کلاس Book را نشان مي‌دهد که اشياي اين کلاس برخي از مشخصات يک کتاب را نگهداري مي‌کنند: class Book { public: Book(char* n = " ", int i = 0, int p = 0) : name(n), id(i), page(p) { } void printName() { cout << name; } void printId() { cout << id; } void printPage() { cout << page; } private: string name, author; int id, page; }

بهبود دادن کلاس Book #include "Date.h" class Book { public: Book(char* n = " ", int i = 0, int p = 0) : name(n), id(i), page(p) { } void printName() { cout << name; } void printId() { cout << id; } void printPage() { cout << page; } void setDOP(int y, int m, int d) { publish.setDate(y, m, d) ; } void showDOP() { publish.showDate(); } private: string name, author; int id, page; Date publish; }

وراثت : وراثت روش ديگري براي ايجاد کلاس جديد از روي کلاس قبلي است. گاهي به وراثت «اشتقاق» نيز مي‌گويند. اگر از قبل با برنامه‌نويسي مبتني بر پنجره‌ها آشنايي مختصر داشته باشيد، احتمالا عبارت «کلاس مشتق‌شده» را فراوان ديده‌ايد. اين موضوع به خوبي اهميت وراثت را آشکار مي‌نمايد.

اعضاي حفاظت شده: گرچه کلاس Ebook در مثال قبل نمي‌تواند مستقيما به اعضاي خصوصي کلاس والدش دسترسي داشته باشد، اما با استفاده از توابع عضو عمومي که از کلاس والد به ارث برده، مي‌تواند به اعضاي خصوصي آن کلاس دستيابي کند. اين محدوديت بزرگي محسوب مي‌شود. اگر توابع عضو عمومي کلاس والد انتظارات کلاس فرزند را برآورده نسازند، کلاس فرزند ناکارآمد مي‌شود. اوضاع زماني وخيم‌تر مي‌شود که هيچ تابع عمومي براي دسترسي به يک دادۀ خصوصي در کلاس والد وجود نداشته باشد.

غلبه کردن بر وراثت : اگر Y زير کلاسي از X باشد، آنگاه اشياي Y همۀ اعضاي عمومي و حفاظت شدۀ کلاس X را ارث مي‌برند. مثلا تمامي اشياي Ebook تابع دستيابي printName() از کلاس Book را به ارث مي‌برند. به تابع printName() يک «عضو موروثي» مي‌گوييم. گاهي لازم است يک نسخۀ محلي از عضو موروثي داشته باشيم. يعني کلاس فرزند، عضوي هم نام با عضو موروثي داشته باشد که مخصوص به خودش باشد و ارثي نباشد. براي مثال فرض کنيد کلاس X يک عضو عمومي به نام p داشته باشد و کلاس Y زير کلاس X باشد.

در اين حالت اشياي کلاس Y عضو موروثي p را خواهند داشت در اين حالت اشياي کلاس Y عضو موروثي p را خواهند داشت. حال اگر يک عضو به همان نام p در زيرکلاس Y به شکل صريح اعلان کنيم، اين عضو جديد، عضو موروثي هم‌نامش را مغلوب مي‌کند. به اين عضو جديد، «عضو غالب» مي‌گوييم. بنابراين اگر y1 يک شي از کلاس Y باشد، y1.p به عضو p غالب اشاره دارد نه به p موروثي. البته هنوز هم مي‌توان به p موروثي دسترسي داشت. عبارت y1.X::p به p موروثي دستيابي دارد.

هم مي‌توان اعضاي داده‌اي موروثي را مغلوب کرد و هم اعضاي تابعي موروثي را. يعني اگر کلاس X داراي يک عضو تابعي عمومي به نام f() باشد و در زيرکلاس Y نيز تابع f() را به شکل صريح اعلان کنيم، آنگاه y1.f() به تابع غالب اشاره دارد و y1.X::f() به تابع موروثي اشاره دارد. در برخي از مراجع به توابع غالب override مي‌گويند و داده‌هاي غالب را dominate مي‌نامند. ما در اين کتاب هر دو مفهوم را به عنوان اعضاي غالب به کار مي‌بريم. به مثال زير نگاه کنيد.

اعضاي داده‌اي و تابعي غالب : class X { public: void f() { cout << "Now X::f() is running\n"; } int a; }; class Y : public X void f() { cout << "Now Y::f() is running\n"; } // this f() overrides X::f() // this a dominates X::a

سازنده‌ها و نابودکننده‌هاي والد: class X { public: X() { cout << "X::X() constructor executing\n"; } ~X() { cout << "X::X() destructor executing\n"; } }; clas Y : public X Y() { cout << "Y::Y() constructor executing\n"; } ~Y() { cout << "Y::Y() destructor executing\n"; } clas Z : public Y Z(int n) {cout << "Z::Z(int) constructor executing\n";} ~Z() { cout << "Z::Z() destructor executing\n"; } int main() { Z z(44); }

اشاره‌گرها در وراثت : ‌در شي‌گرايي خاصيت جالبي وجود دارد و آن اين است که اگر p اشاره‌گري از نوع کلاس والد باشد، آنگاه p را مي‌توان به هر فرزندي از آن کلاس نيز اشاره داد. به کد زير نگاه کنيد: class X { public: void f(); } class Y : public X // Y is a subclass of X } int main() { X* p; // p is a pointer to objects of base class X Y y; p = &y; // p can also point to objects of subclass Y

اشاره‌گري از کلاس والد به شيئي از کلاس فرزند: در برنامۀ زير، کلاس Y زيرکلاسي از X است. هر دوي اين کلاس‌ها داراي يک عضو تابعي به نام f() هستند و p اشاره‌گري از نوع X* تعريف شده: class X { public: void f() { cout << "X::f() executing\n"; } }; class Y : public X void f() { cout << "Y::f() executing\n"; } } int main() { X x; Y y; X* p = &x; p->f(); // invokes X::f() because p has type X* p = &y;

توابع مجازي و چندريختي‌ : تابع مجازي تابعي است که با کلمۀ کليدي virtual مشخص مي‌شود. وقتي يک تابع به شکل مجازي اعلان مي‌شود، يعني در حداقل يکي از کلاس‌هاي فرزند نيز تابعي با همين نام وجود دارد. توابع مجازي امکان مي‌دهند که هنگام استفاده از اشاره‌گرها، بتوانيم بدون در نظر گرفتن نوع اشاره‌گر، به توابع شيء جاري دستيابي کنيم. به مثال زير دقت کنيد.

استفاده از توابع مجازي: class X { public:1 – Virtual function virtual void f() { cout << "X::f() executing\n"; } }; class Y : public X { public: void f() { cout << "Y::f() executing\n"; } } int main() { X x; Y y; X* p = &x; p->f(); // invokes X::f() p = &y; p->f(); // invokes Y::f()

چندريختي از طريق توابع مجازي : سه کلاس زير را در نظر بگيريد. بدون استفاده از توابع‌مجازي، برنامه آن طور که مورد انتظار است کار نمي‌کند: class Ebook : public Book { public: Ebook(char* s, float g) : Book(s), size(g) {} void print() { cout << "Here is an Ebook with name " << name << " and size " << size << " MB.\n"; } private: float size; } class Book { public: Book(char* s) { name = new char[strlen(s+1)]; strcpy(name, s); } void print() { cout << "Here is a book with name " << name << ".\n"; protected: char* name; }; class Notebook : public Book { public: Notebook(char* s, int n) : Book(s) , pages(n) {} void print() { cout << "Here is a Notebook with name " << name << " and " << pages << " pages.\n"; } private: int pages; };

نابودكنندۀ مجازي: با توجه به تعريف توابع مجازي، به نظر مي‌رسد که نمي‌توان توابع سازنده و نابودکننده را به شکل مجازي تعريف نمود زيرا سازنده‌ها و نابودگرها در کلاس‌هاي والد و فرزند، هم‌نام نيستند. در اصل، سازنده‌ها را نمي‌توان به شکل مجازي تعريف کرد اما نابودگرها قصۀ ديگري دارند. مثال بعدي ايراد مهلکي را نشان مي‌دهد که با مجازي کردن نابودگر، برطرف مي‌شود.

حافظۀ گم شده : به برنامۀ زير دقت کنيد: class X { public: x() { p = new int[2]; cout << "X(). "; } ~X() { delete [] p; cout << "~X().\n" } private: int* p; }; class Y : public X Y() { q = new int[1023]; cout << "Y() : Y::q = " << q << ". "; } ~Y() { delete [] q; cout << "~Y(). "; } int* q; int main() { for (int i=0; i<8; i++) { X* r = new Y; delete r; }

كلاس‌هاي پايۀ انتزاعي : در شي‌گرايي رسم بر اين است که ساختار برنامه و کلاس‌ها را طوري طراحي کنند که بتوان آن‌ها را به شکل يک نمودار درختي شبيه زير نشان داد: BOOK Paper BOOK EBOOK REFERENCE MAGAZINE COOKBOOK PDF CHM HLP HTML