برای روشن کردن ترمینال، کلیک کن یا هر کلیدی را بزن

پرش به محتوای اصلی
>_
درس ۲۷متوسط

ارجاع، ارسال آرایه به تابع و معکوس رشته

متغیر‌های ارجاعی، تابع swap و ارسال آرایه‌های یک و دو بُعدی

معمولی
توابع و اشاره‌گر‌ها•صفحات ۴۴-۴۵•۴۵ دقیقه•۱٬۶۳۰ کلمهزمان مطالعه: ۰ دقیقه
فهرست این درس
// اهداف۵
  • درک مفهوم ارسال آرایه به تابع و چرا آرایه‌ها به‌صورت پیش‌فرض با ارجاع ارسال می‌شوند
  • آشنایی با متغیر‌های ارجاعی (Reference) و تفاوت call by value و call by reference
  • پیاده‌سازی تابع swap با ارجاع
  • یادگیری نحوه ارسال آرایه‌های یک‌بُعدی و دو‌بُعدی به تابع
  • پیاده‌سازی معکوس کردن رشته به دو روش: آرایه کمکی و جابجایی درجا

ارسال آرایه به تابع

وقتی یک آرایه را به تابع می‌فرستیم، در واقع آدرس اولین خانه آن آرایه به تابع داده می‌شود (تبدیل به اشاره‌گر). این یعنی اگر داخل تابع عنصری از آرایه را تغییر دهیم، این تغییر در آرایه اصلیِ تابع main هم اعمال می‌شود.

به این حالت می‌گویند آرایه‌ها به‌صورت پیش‌فرض با ارجاع (by reference) ارسال می‌شوند، برخلاف متغیر‌های ساده‌ی int که به‌صورت مقداری ارسال می‌شوند.

برای پاس دادن آرایه به تابع معمولاً دو پارامتر لازم است:

  • خود آرایه: int a[]
  • تعداد عناصر: int n

تابع محاسبه میانگین آرایه

تابع avg یک آرایه و طول آن را می‌گیرد و میانگین عناصر را برمی‌گرداند. دقت کنید که در پارامتر int a[] اندازه داخل براکت اختیاری است و کامپایلر آن را نادیده می‌گیرد؛ بنابراین حتماً باید n را جداگانه بفرستید.

ارسال آرایه یک‌بُعدی به تابعcpp
خروجی برنامه
tedad adad: 4
12 5 8 15
miyangin = 10

متغیر‌های ارجاعی (Reference Variables)

گاهی می‌خواهیم تابع بتواند متغیر اصلی را در main تغییر دهد، نه فقط یک کپی از آن را. راه‌حل، استفاده از ارجاع (reference) است. کافی است قبل از نام پارامتر علامت & بگذاریم:

main.cppcpp

حالا وقتی f(n) صدا زده می‌شود، x به‌جای کپی گرفتن، همان متغیر n می‌شود و هر تغییری روی x مستقیماً روی n اعمال می‌گردد.

تفاوت call by value و call by reference

دو تابع byValue و byReference هر دو می‌خواهند x را صفر کنند. اما اولی تأثیری روی متغیر اصلی ندارد و دومی آن را تغییر می‌دهد.

نشان دادن تأثیر & در پارامترcpp
خروجی برنامه
a ba'd az byValue    = 10
b ba'd az byReference = 0

مثال معروف: تابع swap با ارجاع

یکی از کلاسیک‌ترین مثال‌های استفاده از ارجاع، تابع swap است که مقادیر دو متغیر را با هم عوض می‌کند. بدون & این کار غیرممکن است، زیرا تابع با کپی‌ها کار می‌کند و تغییرش روی اصلی اعمال نمی‌شود.

روش کار: یک متغیر موقت t می‌سازیم، مقدار a را در t ذخیره می‌کنیم، سپس a = b و b = t قرار می‌دهیم.

تابع swap با ارجاع

توجه کنید که در فراخوانی swap(x, y) هیچ علامت اضافه‌ای لازم نیست؛ خود کامپایلر آدرس را به تابع می‌فرستد چون پارامتر‌ها & دارند.

جابجایی مقادیر دو متغیرcpp
خروجی برنامه
qabl:  x=10  y=5
ba'd:  x=5  y=10

چرا swap بدون & کار نمی‌کند؟

اگر & را از پارامتر‌های sw حذف کنیم، تابع فقط کپی‌های محلی را جابجا می‌کند و متغیر‌های اصلی x و y در main هیچ تغییری نمی‌کنند. این یک خطای منطقی بسیار رایج است.

نسخه اشتباه جزوه
کد دارای باگcpp
نسخه اصلاح‌شده و استاندارد
کد صحیحcpp

مرتب‌سازی در تابع و میانه‌ی آرایه

در جزوه (صفحه ۴۴) بعد از تابع SORT، فرمول میانه آمده بود: آرایه را مرتب کن، بعد برای n فرد، میانه همان عنصر وسط یعنی a[n/2] است — تقسیم صحیح 5/2 برابر ۲ می‌شود و a[2] عنصر سوم است.

تابع median دو کار می‌کند: اول آرایه را با SORT مرتب می‌کند و بعد a[n/2] را برمی‌گرداند. به خروجی دقت کنید: چون آرایه با آدرس اولین خانه ارسال می‌شود، آرایه‌ی main هم بعد از فراخوانی median مرتب شده است — همان نکته‌ی اول همین درس. (برای n زوج، میانه میانگین دو عنصر وسط a[n/2 - 1] و a[n/2] است.)

SORT + میانه = a[n/2] برای n فرد (صفحه ۴۴ جزوه)cpp
خروجی برنامه
miyane = 5
araye ba'd az SORT: 1 3 5 7 9 

ارسال آرایه دو بُعدی به تابع

هنگام ارسال آرایه دو بُعدی به تابع، باید حتماً تعداد ستون‌ها (بُعد دوم) را در پارامتر مشخص کنید. تعداد سطر‌ها (بُعد اول) اختیاری است. دلیل: کامپایلر برای محاسبه آدرس عنصر a[i][j] نیاز به دانستن تعداد ستون‌ها دارد تا فرمول i * cols + j را به‌درستی به کار ببرد.

شکل صحیح پارامتر: void f(int a[][3], int n)

ارسال آرایه دو بُعدی به تابع

تعداد ستون‌ها (۳) باید در تعریف پارامتر قید شود. تعداد سطر‌ها از طریق پارامتر n به تابع داده می‌شود.

چاپ یک ماتریس ۲×۳cpp
خروجی برنامه
1  2  3  
4  5  6

معکوس کردن رشته — روش اول: آرایه کمکی

در این روش یک رشته جدید می‌سازیم و کاراکتر‌ها را به ترتیب معکوس از رشته اصلی کپی می‌کنیم. ساده و واضح است اما به اندازه‌ی رشته اصلی، حافظه اضافی مصرف می‌کند.

رابطه‌ی اندیس‌ها: کاراکتر iام رشته جدید = کاراکتر length - 1 - iام رشته اصلی.

معکوس رشته — روش آرایه کمکی

تابع reverse یک string می‌گیرد و رشته معکوس را برمی‌گرداند. رشته اصلی دست‌نخورده باقی می‌ماند.

ساخت رشته معکوس در یک متغیر جداگانهcpp
خروجی برنامه
reshte vared konid: salam
makoose: malas

معکوس رشته — روش دوم: جابجایی درجا (In-place)

در این روش بدون ساخت آرایه کمکی، کاراکتر‌ها را در همان رشته اصلی جابجا می‌کنیم: کاراکتر اول با آخر، دوم با یکی مانده به آخر، و الی آخر. حلقه فقط تا نصف طول رشته می‌رود.

مزیت: بدون مصرف حافظه‌ی اضافی. نکته: برای اینکه تغییر روی رشته اصلی اعمال شود، پارامتر را به‌صورت ارجاعی string &s تعریف می‌کنیم.

معکوس رشته — روش swap درجا

حلقه از i = 0 تا n/2 می‌رود و s[i] را با s[n-1-i] جابجا می‌کند. تابع std::swap از هدر <utility> در دسترس است (با using namespace std; لازم به include جداگانه نیست).

معکوس کردن بدون حافظه اضافی با swapcpp
خروجی برنامه
reshte vared konid: algoritm
makoose: mtirogla

مقایسه دو روش معکوس رشته

هر دو روش به یک نتیجه می‌رسند اما روش‌های متفاوتی دارند. روش دوم بهینه‌تر است زیرا حافظه اضافی مصرف نمی‌کند.

نسخه اشتباه جزوه
کد دارای باگcpp
نسخه اصلاح‌شده و استاندارد
کد صحیحcpp

الگوریتم معکوس درجا با swap

این نمودار، مراحل حلقه معکوس درجا را برای رشته‌ای به طول n نشان می‌دهد.

flow.svg

مدیریت خطا در تابع‌های آرایه‌ای

وقتی آرایه را به تابع می‌فرستید، چند نکته‌ی ایمنی مهم دارد:

  • ارسال n به‌عنوان پارامتر جداگانه: تابع نمی‌تواند اندازه آرایه را خودش تشخیص دهد (مگر با استفاده از template یا span در ⁦C++⁩ مدرن). اگر n اشتباه باشد، ممکن است به حافظه خارج از آرایه دسترسی پیدا کنید (Buffer Overflow).
  • بررسی n >= 0 قبل از کار با آرایه.
  • محدوده‌ی اندیس: همیشه 0 <= i < n باشد.
  • در توابعی که آرایه را تغییر می‌دهند، مطمئن شوید کاری که انجام می‌دهید با هدف تابع سازگار است (در جزوه اصلی، در تابع میانگین به‌اشتباه حلقه‌ی مرتب‌سازی حبابی هم گنجانده شده بود که باعث سردرگمی می‌شد).

کدام روش معکوس را انتخاب کنیم؟

  • اگر می‌خواهید رشته اصلی حفظ شود → روش اول (آرایه کمکی)
  • اگر می‌خواهید حافظه‌ی بهینه باشد و رشته اصلی دیگر لازم نیست → روش دوم (swap درجا)
  • در مسائل رقابتی (Competitive Programming) معمولاً روش دوم ترجیح داده می‌شود.
  • در پروژه‌های واقعی می‌توانید از std::reverse در هدر <algorithm> استفاده کنید: reverse(s.begin(), s.end());

مرور سریع مطالب

۱۸ بخش در این درس

تمرین‌ها

01
تمرین•حل کامل دارد

تابعی به‌نام `int findMax(int a[], int n)` بنویسید که بزرگ‌ترین عنصر آرایه را برگرداند. در `main` یک آرایه ۵تایی بسازید و بیشینه را چاپ کنید.

نمایش راهنما
یک متغیر `m = a[0]` بسازید و در حلقه اگر `a[i] > m` آن‌گاه `m = a[i]`.
02
تمرین•حل کامل دارد

تابع `int countValue(int a[], int n, int v)` بنویسید که تعداد تکرار مقدار `v` در آرایه را بشمارد و برگرداند.

نمایش راهنما
یک شمارنده از صفر بسازید و هر بار `a[i] == v` یک واحد به آن اضافه کنید.
03
تمرین•حل کامل دارد

تابع `void reverseArray(int a[], int n)` بنویسید که یک آرایه صحیح را درجا معکوس کند. در `main` آرایه‌ای به طول ۵ بسازید، معکوس کنید و چاپ کنید.

نمایش راهنما
از همان روش swap درجا استفاده کنید: `for (i=0; i<n/2; i++) swap(a[i], a[n-1-i]);`
04
تمرین•حل کامل دارد

تابع `void printTranspose(int a[][3], int n)` بنویسید که ترانهاده یک ماتریس n×۳ را چاپ کند. در `main` یک ماتریس ۲×۳ بسازید و ترانهاده آن را چاپ کنید.

نمایش راهنما
ترانهاده یعنی سطر‌ها به ستون و ستون‌ها به سطر تبدیل شوند. دو حلقه تودرتو بنویسید که ابتدا روی ستون‌ها (j) و سپس سطر‌ها (i) بچرخد.
05
تمرین•حل کامل دارد

تابع `bool isPalindrome(string s)` بنویسید که اگر رشته متقارن (Palindrome) باشد `true` برگرداند. مثلاً "radar" و "level" متقارن‌اند. در `main` آن را برای کلمه‌ای از ورودی تست کنید.

نمایش راهنما
مقایسه `s[i]` با `s[n-1-i]` برای i از ۰ تا n/2. اگر حتی یک جفت متفاوت بود، `false` برگردانید.
// نکات-کلیدی۶
  • 01آرایه‌ها به‌صورت پیش‌فرض با ارجاع (در واقع با آدرس اولین خانه) به تابع ارسال می‌شوند، در نتیجه تغییرات در تابع روی آرایه اصلی هم اعمال می‌شود.
  • 02علامت `&` در پارامتر تابع، آن را به ارجاع تبدیل می‌کند و اجازه می‌دهد تابع متغیر اصلی را تغییر دهد (مثل تابع swap).
  • 03برای ارسال آرایه یک‌بُعدی، پارامتر را `int a[]` می‌نویسیم و اندازه را به‌صورت پارامتر جداگانه `int n` می‌فرستیم.
  • 04در ارسال آرایه دو بُعدی حتماً باید تعداد ستون‌ها در پارامتر قید شود (مثل `int a[][3]`)، در غیر این صورت کامپایلر خطا می‌دهد.
  • 05معکوس رشته به دو روش ممکن است: با آرایه کمکی (ساده، حافظه‌ی بیشتر) یا با swap درجا (بهینه، بدون حافظه اضافی).
  • 06برای جلوگیری از خطای Buffer Overflow همیشه اندازه آرایه را به‌صورت جداگانه به تابع بفرستید و محدوده اندیس را بررسی کنید.
گام بعدی پیشنهادی

اشاره‌گر‌ها و حافظه پویا

از مفهوم اشاره‌گر تا تخصیص پویا و اشاره‌گر به اشاره‌گر

۶۰ دقیقهپیشرفته۲۱ بخش

شروع درس

جستجوی سریع در درس‌ها

نام درس، موضوع یا کلمه کلیدی را بنویسید و با Enter به آن بروید