فصل 5: آرایه ها
مقدمه
در فصل های گذشته مفهوم نوع داده و چگونگي تعريف نوع دادههاي ساده را بررسي كرديم. در اين فصل تعريف نوع دادههاي ساخت يافته كه شامل مجموعهاي از عناصر با نام مشترك هستند را توضيح ميدهيم.
گاهي اوقات با گروهی از متغيرها سروکار داریم که نامگذاري جداگانة هريك از آن¬ها مشکل است. به عنوان مثال اگر بخواهيم لیستی از اعداد را از کاربر دریافت کرده و در جهت عكس چاپ كنيم، باید همه مقادير را تك تك بخوانيم و ذخيره كنيم. سپس از آخرين مقدار شروع كنيم و چاپ آنها را به انجام برسانيم.
اگر تعداد مقادير 1000 باشد، بايد هزار متغير جداگانه را براي نگهداري اين مقادير تعریف كنيم و هزار بار از دستور خواندن اطلاعات از کاربر استفاده کنیم. اين كار كسلكننده و غیرمنطقی است. در حالي كه با استفاده از يك آراية يك بعدي، از نوع دادههاي ساختيافته، اين كار به سادگي انجام ميشود.
در اين فصل نوع داده ساختيافته را به صورت كلي و آرايهها را با جزئيات شرح ميدهيم.
نوع دادههاي ساختيافته
در فصل 2 نوع دادههاي ساده را بررسي كرديم. مقدار یک متغیر با نوع داده ساده، نمی تواند شامل اجزایی باشد. به عنوان مثال مقدار یک متغیر با نوع عدد صحيح، حالت اتمي دارد و نميتواند به اجزاءٍ كوچكتر تقسيم شود. در حالي كه در نوع داده ساختيافته، هر مقدار مجموعهاي از اجزاء ميباشد و كل مجموعه داراي يك نام است و هر جزئي از آن به طور جداگانه قابل دسترسي است. دنباله ها را در ریاضیات در نظر بگیرید، چنانچه بخواهیم یک دنباله را در برنامه نویسی مدل کنیم، از یک داده نوع ساختیافته به نام آرایه استفاده می کنیم.
نوع داده ساختيافته (Structured Data Type) مجموعهاي از عناصر است که می توان به هریک به صورت مجزا دستيابي پیدا کرد. آنچه نوع داده های مختلف را از هم متمايز ميكند، همين روش دستيابي است.
نوعهاي داده ساده چه از قبل در زبان تعریف شده باشند و چه توسط كاربر تعريف شوند، پایه ساخت نوع داده¬های ساختيافته هستند. يك نوع داده ساختيافته مجموعهاي از مقادير را دور هم جمع ميكند و ترتيب خاصي را بر آنها تحميل ميكند (شکل5-1 را ببينيد). روش دسترسي به عناصر مجموعه يك نوع داده ساختيافته به نحوه سازماندهي عناصر و ترتيب قرارگرفتن آنها بستگي دارد. همزمان با بحث روي روشهاي مختلف سازماندهي دادهها به روش دسترسي متناظر با آن خواهيم پرداخت.
شکل5-2 انواع داده های ساختيافتة موجود در C++ را نشان ميدهد. در اين فصل نوع دادة آرايه را بررسي ميكنيم. نوع دادههاي struct و union در فصل 7 مطرح ميشوند و در فصل 9 نوع داده ساختيافته بسيار قدرتمندي به نام كلاس (class) را بررسي ميكنيم.
آرايههاي يكبعدي
برای نشان دادن مفهوم آرایه ها از یک مثال ساده شروع می کنیم. فرض کنید بخواهيم ليستي شامل 1000 مقدار را بخوانيم و سپس آنها را برعكس چاپ كنيم. در صورتی که با مفهوم آرایه آشنا نباشیم و تنها بخواهیم از مفاهیم ساده استفاده کنیم، مجبوریم 1000 متغیر مجزا تعریف کنیم و برنامهاي به صورت زير بنويسيم:
اين برنامه شامل بيش از 3000 خط است و به علاوه بايد هزار متغير جداگانه تعریف کنیم که نام همة اين متغيرها شبیه است و عدد انتهای نام آنها با هم فرق دارد. همانگونه که ذکر شد این روش، منطقی به نظر نمی رسد. راهکاری که C++ برای حل این مشکل ارائه می دهد، استفاده از آرایه ها است.
آرایه مجموعه ای از داده ها را در کنار هم نگه می دارد. شکل 5-3 نمونه ای از یک آرایه را نشان می دهد. همانگونه که مشاهده می شود هر آرایه می تواند شامل چندین خانه باشد. در مثال مذکور، آرایه ما باید 1000 خانه داشته باشد. در هر یک از خانه های آرایه می توان مقداری را ذخیره کرد. این مقدار می تواند از هر یک از نوع های ساده نظیر اعداد صحیح یا اعشاری باشد. باید توجه داشت که الزاما باید نوع همه خانه های آرایه یکسان باشد.
برای دسترسی آسان به خانه های آرایه برای هر کدام یک شماره یا زیرنویس در نظر گرفته می شود. شماره زیرنویس های آرایه از 0 شماره گذاری می شوند. بنابراین چنانچه آرایه ای با 10 خانه داشته باشیم، شماره اخرین خانه آن 9 خواهد بود. به هر یک از خانه های آرایه یک درایه نیز گفته می شود.
تعریف يك متغیر از نو آرايه يكبعدي مشابه تعریف يك متغير ساده است (متغيري از نوع داده ساده). با اين تفاوت كه اندازة آرايه نیز در تعریف بايد مشخص شود. براي اين كار تعداد اعضاي آرايه در كروشه مشخص ميشود:
در این مثال x نام آرایه و 10 تعداد خانه های آرایه را مشخص می کند. نوع هر یک از خانه ها نیز از جنس عدد صحیح یا همان int است.
حال که با مفهوم آرایه آشنا شدیم، مثال قبل را به کمک آرایه بازنویسی می کنیم. برای این منظور ابتدا آرایه ای با 1000 خانه از نوع اعداد صحیح تعریف می کنیم.
معمولا برای خواندن اطلاعات یک آرایه از حلقه for استفاده می شود. شمارنده حلقه باید از 0 تا آخرین زیرنویس آرایه که یک واحد کمتر از تعداد کل خانه های آرایه است، حرکت کند. همچنین برای چاپ لیست اعداد به صورت معکوس می توانیم از یک حلقه دیگر استفاده کنیم و این بار شمارنده را از شماره آخرین خانه تا 0 حرکت دهیم. قطعه کد زیر این برنامه را نشان می دهد. همانگونه که مشاهده می شود به جای 3000 خط کد، می توان این کار را به کمک آرایه و حلقه در 4 خط انجام داد. در این مثال نام متغیر حلقه value و نام شمارنده i انتخاب شده است.
در این مثال، آرايهاي با هزار درایه كه همه از نوع داده صحيح هستند، استفاده شده است. زيرنويس اولين درایه صفر، زيرنويس دومين درایه يك و زيرنويس آخرين درایه 999 است.
در ادامه برنامه ReverseNumbers با استفاده از آرايهها به طور كامل آورده شده است كه مطمئناً كوتاه¬تر از برنامة نخست است.
اكنون كه مفيد بودن آرايههاي يكبعدي را نشان داديم آنها را به طور رسمي تعريف ميكنيم و نشان ميدهيم كه چگونه ميتوان به عناصر آرايه دسترسي يافت.
تعریف يك آرايه
آراية يكبعدي مجموعة ساختيافتهاي از اعضاء (كه غالباً عنصر نيز ناميده ميشوند) است كه به کمک یک زیرنویس می توان هر یک از خانه ها یا درایه های آن را مشخص کرد و به آن دسترسی پیدا کرد ( در فصل 8 آرايههاي چندبعدي را كه بيش از يك زيرنويس دارند معرفي ميكنيم).
آراية يكبعدي مجموعة ساختيافته از عناصري است كه همگي از يك نوع داده هستند و با يك نام خوانده ميشوند. هر عنصر آرايه با زيرنويسي كه موقعيت عنصر را در مجموعه مشخص ميكند قابل دسترسي است.
قالب تعریف يك آرايه يكبعدي چنين است:
كه در آن Datatype نوع دادهاي كه در هر عنصر از آرايه ذخيره ميشود را مشخص ميكند. عناصر آرايه ميتوانند از هر نوعي باشند ليكن اكنون بحث خود را به عناصر اتمي محدود ميكنيم. ConstIntExpression عبارتي است صحيح كه ميتواند ثابت نامدار يا ثابت معين باشد. اين عبارت كه نشان دهندة تعداد عناصر آرايه است بايستي مقدارش از صفر بيشتر باشد. اگر مقدار اين عبارت n باشد زيرنويس از صفر تا n-1 تغيير ميكند، نه از يك تا n . به عنوان مثال قطعه کد ذيل:
آرايهاي را كه در شکل5-4 نشان داده شده است، ايجاد ميكند. آراية angle چهار عضو دارد كه در هر كدام يك مقدار اعشاري نگهداري ميشود. آراية testScore ده تا عضو دارد كه همه از نوع صحيح هستند.
دسترسي به اعضا
براي دسترسي به يك عضو آرايه، نام آرايه را مينويسيم و متعاقب آن عبارتي را داخل دو كروشه قرار ميدهيم. اين عبارت مشخص ميكند كه كدام عضو مورد نظر است. چارچوب دستوري زير نحوة دسترسي به اعضا يك آرايه را بيان ميكند.
عبارت زيرنويس (Index Expression) ممكن است ساده يا ثابت يا نام متغير يا تركيبي پيچيده از متغيرها يا عملگرها يا توابع باشد. ولي به هر حال نتيجة نهايي عبارت بايد مقداري صحيح باشد.
عبارت زيرنويس ميتواند از نوع long ، int ، short ، char يا enum باشد زيرا همة اينها ازنوع دادههاي صحيح هستند.
سادهترين شکل عبارت زيرنويس اين است كه يك مقدار صحيح باشد. مثلاً دنبالة انتسابهاي ذيل عناصر آرايه angle فوقالذكر را، يكي بعد از ديگري مقداردهي ميكنند. شکل5-5 را ببينيد.
هر عضو آرايه مثلاً angle[2] دقيقاً ميتواند نظير يك متغير ساده مورد استفاده قرار گيرد. ميتوانيم به آن مقدار نسبت دهيم
يا با cin مقداري را براي آن بخوانيم:
يا با cout محتويات آن را روي صفحه نمايش دهيم:
يا آن را همانند پارامتر به يك تابع ارسال كنيم:
يا از آن در يك عبارت محاسباتي استفاده كنيم:
اكنون به عبارت زيرنويسي كه پيچيدهتر از ثابتهاست ميپردازيم. تصور كنيد آرايهاي با 1000 عنصر با مقادير صحيحي در دستور زير تعریف شده باشد:
دو دستور ذيل را اجرا ميكنيم.
در اولين دستور، بسته به مقدار counter عدد 5 در یکی از خانه های آرایه ذخیره می شود. اگر counter صفر باشد عدد 5 در اولين عنصر آرايه ذخيره ميشود و اگر counter برابر 1 باشد 5 در دومين عنصر آرايه ذخيره ميشود و الي آخر.
دستور دوم عنصري از آرايه به وسيلة number+1 انتخاب شده است و سپس بر 10 تقسيم شده و باقيماندهاش مورد آزمايش قرار ميگيرد كه آيا غير صفر ميباشد يا خير. اگر number+1 يك باشد دومين عنصر تحت آزمايش قرار ميگيرد و همينطور الي آخر. شکل 5-6 عبارت زيرنويس را به صورت ثابت، متغير و عبارات پيچيده نشان ميدهد.
مقداردهي اوليه آرايه در تعریف
هنگام تعریف آرايه ميتوان عناصر آن را مقداردهي كرد. ليست مقادير را پشت سرهم جدا شده با ویرگول مينويسيم و آنها را در يك جفت آكولاد محصور ميكنيم:
در اين تعریف age[0] با عدد 23 مقداردهي اوليه ميشود و age[1] با عدد 10 و همينطور الي آخر. حداقل يك مقدار اوليه بين آكولادها بايد وجود داشته باشد، اگر بيش از اندازه آرايه مقادير اوليه داده شود خطاي دستوري پيش خواهد آمد و اگر كمتر از اندازه آرايه مقدار اوليه داده شود باقيماندة عناصر آرايه با صفر مقداردهي اوليه ميشود.
در اينصورت مطابق زير ميتوان به كليه عناصر آرايه مقدار اوليه صفر داده شود.
آرايهها از قانون مشابه متغيرهاي ساده در مورد مقداردهي پيروي ميكنند. يك آراية استاتيك (چه به صورت جهاني يا به صورت static تعریف شده باشد) تنها يك بار مقداردهي ميشود و آن هم وقتي است كه كنترل به تعریفش ميرسد: ولي يك آراية اتوماتيك (كه به صورت محلي تعریف ميشود و نه static) هر دفعه كه كنترل به تعریفش ميرسد مقداردهي اوليه ميشود. يكي از ويژگيهاي قابل توجه C++ اين است كه امكان حذف اندازه آرايه داده ميشود، البته در صورتي كه هنگام تعریف مقداردهي اوليه نيز انجام شود.
كمپايلر براساس شمارش عناصر ليست محصور شده در آكولاد اندازه آرايه را درمييابد. اين ويژگي عموماً مفيد نيست، اگرچه در انتهای این فصل خواهيم ديد كه اين ويژگي سهولت زيادي را در مقداردهي آرايههاي كاراكتري كه رشته string ناميده ميشوند فراهم ميكند.
عملگرهاي جمعي در آرايهها امكان ندارد
برخي زبانهاي برنامهنويسي اعمال عملگرهاي جمعي روي آرايهها را دارند. يك عملگر جمعي آرايه را به صورت يك واحد مورد پردازش قرار ميدهد.
عملگر جمعي (Aggregate Operation) عملگري است که به یک باره روي كل ساختمان داده کار می کند، برخلاف عملگري كه روي تك تك عناصر ساختمان داده عمل ميكند.
C++ عملگرهاي جمعي روي آرايه را در اختيار قرار نميدهد. اگر x و y به صورت زير تعریف شده باشند:
انتساب جمعي y به x امكان ندارد.
براي كپي كردن آرايه y در x بايد تك تك عناصر y را در تك تك عناصر x كپي كرد:
به طور مشابه مقايسه جمعي آرايه وجود ندارد.
همينطور نميتوان عملگر جمعي ورودي و خروجي را روي آرايهها انجام داد.
يا محاسبة عمليات جمع روي آرايهها امكان ندارد.
C++ براي ورودي و خروجي جمعي يك استثناء دارد كه ما در انتهای فصل آن را شرح ميدهيم. ورودي و خروجي جمعي براي رشتهها (String) كه نوع خاصي از آرايههاي كاراكتريهستندمجازاست). نهايتاً دريك تابع با مقدار برگشتي نميتوان يك آرايه را باز گرداند:
در عين حال آرايه را به عنوان پارامتر تابع ميتوان به آن فرستاد.
فرستادن يك آرايه به عنوان پارامتر تابع به آن سبب ميشود كه همة آرايه در دسترسي تابع باشد. اين موضوع را در همين فصل بررسي ميكنيم.
پیمایش آرایه
معمولا پردازش آرايهها به کمک حلقه for انجام می شود. برای این منظور شمارنده حلقه از 0 تا n – 1 که n اندازه آرایه است، تغییر می کند. به مثال زیر توجه کنید:
اولين خط را به صورت زير نيز ميتوان نوشت:
عموماً برنامهنويس¬هاC++ نوع اول را به كار ميبرند، به طوري كه عدد 100 كه نشاندهندة اندازة آرايه است در حلقه نمايان باشد.
مثالهايي از تعریف و دسترسي به آرايهها
اكنون برخي از مثالهاي مربوط به تعریف و دسترسي به آرايهها را با شرح بیشتر بررسي ميكنيم. فرض کنید می خواهیم اطلاعات واحدهای یک مجتمع مسکونی را نگهداری کنیم. تعریفهاي موردنياز به شرح ذیل است:
آراية occupants آرايهاي 350 عنصري است كه از نوع صحيحاند و هركدام نشاندهنده تعداد ساكنين يكي از آپارتمان¬هاست (شکل5- 7 را ببينيد). اگر در آپارتمان شماره يك 3 نفر ساكن باشند آنگاه occupants[0]=3 و اگر در آپارتمان دوم پنج نفر ساكن باشند آنگاه occupants[1]=5 و الي آخر. اگر اطلاعات ساكنين مجتمع را تا آخر در آرايه وارد كنيم، قطعه كد زير تعداد ساكنين مجتمع را حساب خواهد كرد.
در ابتداي حلقه i صفر است و به محتواي totalOccupants كه صفر است مقدار occupants[0] اضافه ميگردد، و نتيجة اين جمع در totalOccupants ذخيره ميشود. سپس i يك ميشود و آزمون شرط حلقه انجام ميگيرد. در دومين تكرار حلقه محتواي totalOccupants با مقدار occupants[1] جمع شده و نتيجه در totalOccupants ذخيره ميشود. حالا i ، 2 ميشود و آزمون شرط حلقه صورت ميگيرد. با توجه به اينكه i هنوز از 350 كوچكتر است حلقه تا اضافه كردن محتويات occupants [349] به مجموع ادامه خواهد داشت. زماني كه i به 350 برسد، شرط حلقه نقض ميشود، و حلقه پايان ميپذيرد.
توجه كنيد كه چگونه ثابت BUILDING_SIZE را هم در تعریف آرايه و هم در داخل حلقه به كار برديم. اگر تعداد آپارتمانها مثلاً از 350 به 400 تغيير كند فقط تعریف ثابت BUILDING SIZE تغيير ميكند و اين تغيير به راحتي صورت ميگيرد. اگر تعریف به شکل فوق صورت نميگرفت. به خاطر يك تغيير كوچك در برنامه بالا مقدار بيشتري از برنامه احتياج به تغيير داشت.
به خاطر اينكه زيرنويس آرايه مقداري صحيح است عناصر توسط موقعيتشان كه در اول يا دوم يا سوم الي آخر قرار گرفتهاند قابل دسترسياند. اگرچه استفاده زيرنويس int براي يك آرايه معمولترين روش است، با اين حال C++ شرايطي را فراهم ميكند كه زيرنويس يكي از نوعهاي صحيح باشد. (عبارت زيرنويسدار بايد از نوع صحيح و بين صفر تا SIZE-1 تغيير كند كه در آن SIZE مقدار عناصر آرايه است).
مثال بعدي آرايههاي كه زيرنويسهايشان از نوع enum ميباشند، نشان ميدهد.
Drink
از نوع enum است كه شامل LEMON و… ORANGE, COLA و از صفر تا 5 عنصر را نمايش ميدهد. SaleAmt يك مجموعة شش عضوي ميباشد كه فروش هر نوشيدني را به ریال نمايش ميدهد. (شکل 5-8 را ببينيد). كد زير مقادير موجود در آرايه را چاپ ميكند. (در فصل 3 نحوة افزايش متغيرهاي شمارش در حلقههاي for را مطالعه كنيد).
يك مثال ديگر: فرض كنيد آرايه grade نمرات 10 دانشجو را نگهداري ميكند.
آراية grade در شکل 5-9 تصوير شده است. مقادير عناصر آرايه در شکل نشان داده شده است، كه نشاندهنده اين است كه قبلاً پردازشهاي روي آرايه انجام گرفته است.
مثال¬هاي سادهاي از نحوة استفاده از آرايه در زیر آورده شده است،
كاراكتري را از جريان ورودي ميخواند و آن را در سومين عنصر آراية grade با زيرنويس 2 ذخيره ميكند.
كاراكتر A به عنصر آراية grade كه زيرنويس 3 دارد نسبت داده ميشود.
5 به متغير زيرنويس idNumber نسبت داده ميشود. idNumber = 5;
كاراكتر 'C' به عنصري از آرايه كه زيرنويس آن به وسيلة idNumber مشخص ميشود نسبت داده ميشود. (در اينجا عنصر ششم آرايه)
حلقه زير عناصر آرايه را بصورت FBCAFCAACB چاپ ميكند.
خروجي را به صورت خواناتري چاپ ميكند:
خروجي در اين حالت چنين خواهد بود:
فرستادن آرايهها به عنوان پارامتر به تابع
در فصل 4 گفتيم اگر بخواهيم متغيري به تابع ارسال كنيم به طوري كه مقدار آن به وسيله تابع تغيير نكند، آن را با روش ارسال با مقدار ميفرستيم و نه با روش ارسال با عطف. آرایه ها از این موضوع استثنا هستند.
پيشفرض C++ براي ارسال متغيرهاي ساده ارسال با مقدار است. براي ارسال متغيرهاي ساده با عطف بايد علامت & را به نوع داده در ليست پارامترهاي صوري اضافه كنيم:
ارسال آرايهها با مقدار غيرممكن است و آنها همواره با عطف ارسال ميشوند. بنابراين ضرورتي بر استفاده از علامت & براي آرايهها در ليست پارامترهاي صوري وجود ندارد. وقتي آرايهاي به تابعي ارسال ميشود، آدرس پايه آن كه آدرس اولين عنصر آرايه ميباشد به تابع فرستاده ميشود. تابع با در دست داشتن آدرس اولين عنصر به آن و همچنين به كليه عناصر بعدي آرايه دسترسي خواهد داشت. زيرا عناصر آرايه در حافظه به ترتيب پشت سرهم قرار گرفتهاند.
آدرس پايه (Base Address) آدرس اولين عنصر آرايه
در تابع ذيل به همة عناصر آرايهاي كه اندازة آن دلخواه و از نوع اعشاري ميباشد مقدار صفر داده ميشود:
توجه كنيد كه تعریف arr در ليست پارامترهاي صوري، شامل اندازه آن نیست. به عبارت ديگر داخل كروشه بعد از arr هيچ عددي ذكر نشده است. بهرحال، اگر در اين تعریف اندازه ذكر شود، كمپايلر از آن صرفنظر ميكند. كمپايلر تنها ميخواهد بداند كه نوع آرايه چيست (كه در اينجا float ميباشد). لذا براي اينكه تابع بدرستي كار كند، اندازه آرايه بايد توسط عدد ديگري كه به تابع ارسال ميشود معين گردد.
كد فراخواننده ميتواند تابع zeroOut را براي يك آرايه با عناصر اعشاري منتها با هر اندازه دلخواهي فراخواني كند. قطعه كد زير تابع zeroOut را براي دو آرايه با اندازههاي متفاوت فراخواني ميكند. توجه كنيد كه آرايه و اندازه آن در الگو تابع، چگونه تعریف شدهاند.
آرايههاي موازي
در بسياري از مسائل، با دادههايي سروكار داريم كه همراه يكديگرند. مثلاً ميتوان شماره دانشجوئي و نمره دانشجويان را در يك درس نام برد. ميتوان يك آرايه int يا long به نام idNum براي شماره دانشجويي و يك آرايه از نوع char به نام grade براي نمرات كه از A تا F ارزيابي شدهاند درنظر گرفت.
بنابراين با يك زيرنويس خاص، شماره دانشجويي و نمره با هم در دسترس قرار ميگيرند. شکل5-10 را در نظر بگيريد.