درک مفهوم ارسال آرایه به تابع و چرا آرایهها بهصورت پیشفرض با ارجاع ارسال میشوند
آشنایی با متغیرهای ارجاعی (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());
تابعی بهنام `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 به آن بروید
1#include<iostream>2usingnamespace std;34doubleavg(int a[],int n){5double sum =0;6for(int i =0; i < n; i++)7 sum += a[i];8return sum / n;9}1011intmain(){12int n;13 cout <<"tedad adad: ";14 cin >> n;15int a[100];16for(int i =0; i < n; i++)17 cin >> a[i];18 cout <<"miyangin = "<<avg(a, n)<< endl;19return0;20}
voidf(int&x){ x =0;}
1#include<iostream>2usingnamespace std;34voidbyValue(int x){// کپی از n میسازد5 x =0;6}78voidbyReference(int&x){// ارجاع به n اصلی9 x =0;10}1112intmain(){13int a =10, b =10;14byValue(a);15byReference(b);16 cout <<"a ba'd az byValue = "<< a << endl;// تغییری نکرده17 cout <<"b ba'd az byReference = "<< b << endl;// صفر شده18return0;19}
1#include<iostream>2usingnamespace std;34voidsw(int&a,int&b){5int t = a;6 a = b;7 b = t;8}910intmain(){11int x =10, y =5;12 cout <<"qabl: x="<< x <<" y="<< y << endl;13sw(x, y);14 cout <<"ba'd: x="<< x <<" y="<< y << endl;15return0;16}
1voidsw(int a,int b){// بدون &2int t = a;3 a = b;4 b = t;5}6// در main: sw(x, y) تغییری در x,y ایجاد نمیکند
1voidsw(int&a,int&b){// با &2int t = a;3 a = b;4 b = t;5}6// در main: sw(x, y) مقادیر را واقعاً جابجا میکند
1#include<iostream>2usingnamespace std;34voidSORT(int a[],int n){// مرتبسازی حبابی صعودی5for(int i = n -1; i >0; i--)6for(int j =0; j < i; j++)7if(a[j]> a[j +1])8swap(a[j], a[j +1]);9}1011intmedian(int a[],int n){// برای n فرد12SORT(a, n);// آرایهی اصلی مرتب میشود!13return a[n /2];// عنصر وسط14}1516intmain(){17int a[5]={9,1,5,3,7};18 cout <<"miyane = "<<median(a,5)<< endl;19 cout <<"araye ba'd az SORT: ";20for(int i =0; i <5; i++)21 cout << a[i]<<' ';22 cout << endl;23return0;24}
1#include<iostream>2#include<string>3usingnamespace std;45string reverse(string s){6 string r = s;// هماندازه با s7int n = s.length();8for(int i =0; i < n; i++)9 r[i]= s[n -1- i];10return r;11}1213intmain(){14 string str;15 cout <<"reshte vared konid: ";16 cin >> str;17 cout <<"makoose: "<<reverse(str)<< endl;18return0;19}
1#include<iostream>2#include<string>3usingnamespace std;45voidreverseInPlace(string &s){6int n = s.length();7for(int i =0; i < n /2; i++)8swap(s[i], s[n -1- i]);9}1011intmain(){12 string str;13 cout <<"reshte vared konid: ";14 cin >> str;15reverseInPlace(str);16 cout <<"makoose: "<< str << endl;17return0;18}
1// روش اول: آرایه کمکی2// مزیت: رشته اصلی دستنخورده میماند3// معیب: حافظه O(n) اضافی مصرف میکند4string reverse(string s){5 string r = s;6for(int i =0; i <(int)s.length(); i++)7 r[i]= s[s.length()-1- i];8return r;9}
1// روش دوم: جابجایی درجا2// مزیت: حافظه O(1)، سریعتر3// معیب: رشته اصلی تغییر میکند4voidreverseInPlace(string &s){5int n = s.length();6for(int i =0; i < n /2; i++)7swap(s[i], s[n -1- i]);8}
flow.svg100%
Press enter or space to select a node. You can then use the arrow keys to move the node around. Press delete to remove it and escape to cancel.
Press enter or space to select an edge. You can then press delete to remove it or escape to cancel.