You are on page 1of 8

თავი 10: რეფერენსი.

რეფერენსი ფუნქციის პარამეტრად


 მეტსახელი, ანუ რეფერენსი
 არგუმენტის გადაცემა რეფერენსით ( Pass-by-reference , Pass-by-const reference) >>>
 ფუნქციისთვის პარამეტრების გადაცემის სხვადასხვა გზის შედარება მაგალითებზე >>>
 ფუნქციის დასაბრუნებელი მნიშვნელობა- რეფერენსი და კლასის ობიექტი >>>
 დიაპაზონიანი for -ის გამოყენება რეფერენსებთან ერთად >>>

რეფერენსი
reference არის ობიექტის ფსევდონიმი (alias), ანუ, მარტივად რომ ვთქვათ, იმ ობიექტის
მეტსახელი, რომელსაც იგი მიუთითებს. მაგალითად,
int j = 10, k;
int &i = j; // განაცხადი i მითითებაზე (ფსევდონიმზე)
განაცხადი აღნიშნავს, რომ i წარმოადგენს j-ს კიდევ ერთ სახელს, ანუ მთელი ტიპის i ცვლადის
მისამართი იგივეა, რაც მთელი ტიპის j ცვლადისა ( ე.ი. i და j - ერთი და იგივე მეხსიერების
სახელებია). ცხადია, რომ რეფერენსზე (ფსევდონიმზე) განაცხადისთანავე აუცილებლად უნდა
მოხდეს მისი ინიციალიზება.
შემდეგი ფრაგმენტი
int j = 10, k;
int &i = j; // განაცხადი i რეფერენსზე (მითითებაზე, ფსევდონიმზე)
cout << j << " " << i; // დაიბეჭდება 10 10
k = 121;
i = k;
// i-ს მიენიჭა k-ს მნიშვნელობა, ე.ი. j-ც გახდება k-ს ტოლი
cout << j<< " " << i; // დაიბეჭდება 121 121
i++;
cout << j<< " " << i; // დაიბეჭდება 122 122

გვარწმუნებს, რომ მითითება i არის j ცვლადის (რომელსაც i მიუთითებს) მეტსახელი.

<<< არგუმენტის გადაცემა რეფერენსით (Pass-by-reference )


ფუნქციის გამოძახების დროს მის პარამეტრებს და ლოკალურ ცვლადებს გამოეყოფათ ადგილი
ოპერატიული მეხსიერების სპეციალურ არეში – სტეკში. როდესაც არგუმენტი გადაეცემა ფუნქციას
მნიშვნელობით, ფუნქციის პარამეტრს ენიჭება ამ არგუმენტის ასლი. ამიტომ, თუ ფუნქციის ტანში
მისი პარამეტრი იცვლის მნიშვნელობას, შესაბამისი არგუმენტი არ იცვლება.
ხშირად არგუმენტის ასლის გადაცემა არახელსაყრელია. მაგალითად, როდესაც:
 ფუნქციიდან ერთზე მეტი მნიშვნელობის დაბრუნება გვჭირდება. მაშინ ერთ მნიშვნელობას
დააბრუნებს return შეტყობინება, დანარჩენი მნიშვნელობების დაბრუნება კი უნდა
მოხერხდეს პარამეტრების მეშვეობით.
 პროგრამაში გვჭირდება ფუნქციის მიერ არგუმენტის შეცვლილი მნიშვნელობა.
 ფუნქციას დასამუშავებლად გადაეცემა მონაცემთა დიდი ობიექტი. ასეთი არგუმენტის
გადაცემა მნიშვნელობით (მისი კოპირება პარამეტრში) მოითხოვს გარკვეულ დროს, რაც
ანელებს პროგრამის შესრულებას.
როდესაც ფუნქციას გადაეწოდება არა არგუმენტის ასლი, არამედ რეფერენსი, მაშინ ყველა
ცვლილება, რომელსაც ფუნქციაში განიცდის პარამეტრი, სინამდვილეში ხორციელდება
არგუმენტზე. ასეთ პარამეტრებს ეწოდებათ ცვლადი პარამეტრები.
მაგალითად, პროტოტიპში
void f(int & i);
78
i არის int ტიპის reference-პარამეტრი (ან: i არის მითითება int ტიპზე).
ვთქვათ, k – მთელი ტიპის ცვლადია. f ფუნქციის
f(k);
გამოძახების დროს სრულდება მინიჭება
int &i = k;
ანუ i ხდება k ცვლადის მეტსახელი. ამიტომ i პარამეტრზე მიმართვა ფუნქციის ტანში
ფაქტობრივად ნიშნავს k არგუმენტზე მიმართვას, და i –ს შეცვლა იგივე k –ს შეცვლას ნიშნავს.
არგუმენტის ასეთ გადაცემას უწოდებენ Pass-by-reference.
შემდეგ საილუსტრაციო მაგალითში ნაჩვენებია reference-პარამეტრის გამოყენება
#include <iostream>
using namespace std;
void f(int &);
int main(){
int value = 1;
cout << "value –s sawyisi mnishvneloba: "
<< value << '\n';
f(value); //value –ს გადავცემთ მითითებით
cout << "value –s axali mnishvneloba: "
<< value << '\n';
}
void f(int &i){
i = 10; // არგუმენტის შეცვლა
} value –s sawyisi mnishvneloba: 1
პროგრამის შესრულების შედეგია: value –s axali mnishvneloba: 10
Press any key to continue . . .
შემდეგ მაგალითში reference–პარამეტრს გამოვიყენებთ ვექტორის კლავიატურიდან შევსების
ფუნქციაში, ხოლო ვექტორის ელემენტების ბეჭდვის ფუნქციაში – const reference–პარამეტრს:
#include <iostream>
#include <vector>
using namespace std;
void fillVector(vector<int>& );
void printVector(const vector<int>& );
int main(){
vector<int> a;
fillVector(a);
cout<<"Vectori:\n";
printVector(a);
}
void fillVector(vector<int>& x){
int number;
while( cin >> number )
x.push_back(number);
}
void printVector(const vector<int>& x){
for(int i = 0; i < x.size(); i++)
cout << x[i] << ' ';
cout << endl; 3 -7 19 231 0 25 -87 ^Z
} Vectori:
პროგრამის შესრულების შედეგია: 3 -7 19 231 0 25 -87
Press any key to continue . . .

void fillVector(vector<int>& x);

79
ფუნქციის პარამეტრი წარმოადგენს reference–პარამეტრს. ეს აუცილებელია, რადგან ფუნქციის
დანიშნულებაა ჩაწეროს ვექტორში კლავიატურიდან შემოსული მთელი რიცხვები, ანუ ფუნქციის
fillVector(a);
გამოძახების შედეგად a ვექტორი უნდა შეიცვალოს.
ფუნქცია printVector ბეჭდავს ვექტორს. რადგან a ვექტორის ზომა შეიძლება იყოს დიდი, ამ
ფუნქციის პარამეტრი მიზანშეწონილია გამოვიყენოთ reference სახით, მიუხედავად იმისა რომ
ვექტორის შეცვლას არ ვგეგმავთ. ასეთ შემთხვევებში უნდა ვიყოთ ფრთხილად – თუ ფუნქციის
ტანში შემთხვევით შეიცვლება x ვექტორის ელემენტი (ელემენტები), ეს შეგვიცვლის a ვექტორს.
ენაში გვაქვს დაცვის მექანიზმი, რომელიც მსგავსი შეცდომებისგან გვიცავს –
const reference პარამეტრის გამოყენება:
void printVector(const vector<int>& x);
ახლა თუ ნებით ან უნებლიეთ შევეცდებით a ვექტორის შეცვლას, კომპილერი გამოიტანს
შეცდომის შესახებ გზავნილს: 'x' : you cannot assign to a variable that is const.

<<< ფუნქციისთვის პარამეტრების გადაცემის სხვადასხვა გზის. შედარება მაგალითებზე


დასაწყისისთვის განვიხილოთ ვეტორის ბეჭდვის ფუნქცია, როგორც საკმაოდ მარტივი და კარგად
ცნობილი მაგალითი. თავიდან, ჩვენ ამ ფუნქციას პარამეტრს გადავცემდით ასლის სახით. შემდეგ
პროგრამაში:
#include <iostream>
#include <vector>
using namespace std;
void printVector(const vector<int> );
int main()
{
vector <int> a;
int m;
while(cin >> m)
a.push_back(m);
printVector(a);
}
void printVector(const vector<int> v)
{
for(int i{}; i < v.size(); i++)
cout << v[i] << '\t';
cout << endl;
}
ფუნქციის გამოძახება
printVector(a);
იწვევს ახალი მუდმივი
const vector<int> v;
ვექტორის შექმნას, რომელშიც კოპირდება მთავარ პროგრამაში შექმნილი a ვექტორი. ეს ახალი v
ვექტორი (ანუ a-ს ასლი) დაიბეჭდება, ფუნქცია დაასრულებს მუშაობას და v ვექტორი გაუქმდება,
რადგან იგი წარმოადგენს ფუნქციის ლოკალურ ცვლადს და წყვეტს არსებობას ფუნქციის
დასრულებასთან ერთად.
თუ ამ პროგრამაში გამოვიყენებთ ბეჭდვის ფუნქციას, რომელსაც პარამეტრს გადავცემთ
რეფერენსით:
void printVector(const vector<int> & v)
{
for(int i{}; i < v.size(); i++)
80
cout << v[i] << '\t';
cout << endl;
}
მაშინ ფუნქციის printVector(a); გამოძახების შედეგად არავითარი ახალი ვექტორი არ იქმნება.
უკვე არსებულ a ვექტორს ფუნქციის ტანში დაერქმევა ახალი სახელი v, და ამიტომ v -ს ბეჭდვა
ნიშნავს a -ს ბეჭდვას. ფუნქციის დასრულების შემდეგ, a-ს ახალი სახელი (რომელიც ფუნქციის
ლოკალური ცვლადია) გაუქმდება.
თუ ბეჭდვის ფუნქციას პარამეტრს გადავცემთ პოინტერით (შესამაბისი ცვლილებებით კოდში):
void printVector(const vector<int> * const v)
{
for (int i{}; i < v->size(); i++)
cout << v->at(i) << '\t';
cout << endl;
}
ისევ ავიცილებთ თავიდან ვექტორის ასლის შექმნას.
თუ შევაჯამებთ, ვხედავთ რომ პარამეტრად ვექტორის ასლის გადაწოდება ზრდის გამოყენებულ
მეხსიერებას და შესრულების დროს. ამიტომ, უპირატესობა უნდა მივანიჭოთ მეთოდებს,
რომლებიც უფრო სწრაფად და ეკონომიურად ასრულებებნ იგივე საქმეს.
მომდევნო მაგალითში, გავაკეთოთ ფუნქცია, რომელიც პარამეტრად მიიღებს ინფორმაციას
კვადრატის გვერდის შესახებ და გამოთვლის მის ფართობს და პერიმეტრს.
პირველი ვარიანტი, როდესაც პარამეტრად გადავცემთ არგუმენტის ასლს, არამარტო დროისა და
მეხსიერების თვალსაზრისით არის არახელსაყრელი, არამედ საკმაოდ რთულიცაა, რადგან ჩვენ
მოგვიწევს ვიზრუნოთ ისეთი კონტეინერის გამოყენებაზე, რომელიც ორ ნამდვილ რიცხვს
ერთდროულად შეინახავს და დაბრუნებს. ამ საკითხს ამავე ამოცანის მაგალითზე განვიხილავთ
შემდეგ პუნქტში, როდესაც ფუნქციების დასაბრუნებელ მნიშვნელობებზე უფრო დაწვრილებით
ვისაუბრებთ.
ამოცანის ელემენტარულ დონეზე გადასაჭრელად, შეგვიძლია გამოვიყენოთ ან პოინტერი, ან
რეფერენსი. ვნახოთ ორივე შემთხვევა.
თუ გამოვიყენებთ პოინტერს, მაშინ:
#include <iostream>
#include <vector>
using namespace std;
void kvadrati(const double side, double *sAddress, double *pAddress)
{
*sAddress = side * side;
*pAddress = 4 * side;
}
int main()
{
double area, perimeter;
kvadrati(11, &area, &perimeter);
cout << "tu kvadratis gverdi aris 11-is toli, fartobi = " << area
<< ", perimetri = " << perimeter << endl;
}
გამოძახება
kvadrati(11, &area, &perimeter);

პირველ რიგში იწვევს ფუნქციის ლოკალური ცვლადების შექმნას და ინიციალიზებას:


side = 11;
sAddress = &area;
pAddress = &perimeter;

ამის საფუძველზე, სტრიქონი


81
*sAddress = side*side;

იგივეა რაც
*&area = 11*11;

როგორც ვიცით, *&area იგივეა რაც area. ამგვარად, ფუნქციის ტანის პირველმა სტრიქონმა
გადაცემულ მისამართზე, მთავარ პროგრამაში შექმნილ area ცვლადში ჩაწერა ამ კვადრატის
ფართობი. ანალოგიურად სრულდება ფუნქციის მეორე სტრიქონი.
რეფერენსების საშუალებით შექმნილი კოდი უფრო მარტივი გასაგებია (ფორმალურად მაინც):
void kvadrati(const double side, double &s, double &p)
{
s = side * side;
p = 4 * side;
}
int main()
{
double area, perimeter;
kvadrati(11, area, perimeter);
cout << "tu kvadratis gverdi aris 11-is toli, fartobi = "
<< area << ", perimetri = " << perimeter << endl;
}
აქაც, გამოძახება
kvadrati(11, area, perimeter);

პირველ რიგში იწვევს ფუნქციის ლოკალური ცვლადების შექმნას და ინიციალიზებას:


side = 11;
double &s = area;
double &p = perimeter;

კერძოდ, area ცვლადს დაექვა მეორე სახელი s. ამის საფუძველზე, სტრიქონი


s = side * side;

იგივეა რაც
area = 11 * 11;

მივაღწიეთ იგივე შედეგს რასაც პოინტერების შემთხვევაში.


მესამე მაგალითად შეგვიძლია განვიხილოთ ორი ცვლადის მნიშვნელობის გაცვლის ამოცანა,
რომელშიც უბრალოდ შეუძლებელია პარამეტრების ასლის გადაცემის გზით რაიმე შედეგის
მიღწევა.

<<< რეფერენსი და კლასის ობიექტი - ფუნქციის დასაბრუნებელი მნიშვნელობები


ზოგადად, ფუნქციას შეუძლია დააბრუნოს ნებისმიერი ტიპის მონაცემი, რომელიც მისაწვდომია
პროგრამისთვის. მათ შორისაა როგორც პრიმიტიული ტიპის ცვლადები (მთელი, ნამდვილი და
სხვა), ასევე სხვადასხხვა კლასის ობიექტები და მათზე რეფერენსები ან პოინტერები.
პირველ მაგალითში, ჩვენ მოკლედ შევეხებით რეფერენსის დაბრუნების საკითხს. განვიხილოთ
კოდი:
void printVector(const vector<string> &s)
{
for(int i{}; i < s.size(); i++)
cout << s[i] << '\t';
cout << endl;
}
int main()
{
vector<string> a;

82
string w;
while(cin >> w)
a.push_back(w);
printVector(a);
cout << "a.back() = " << a.back() << endl;
a.back() = "runrunrun";
printVector(a);
}
vector კლასის მეთოდი .back()საშუალებას გვაძლევს, რომ გავსინჯოთ და შევცვალოთ
ვექტორის ბოლო ელემენტი. ამ პროგრამის შესრულების ერთი შესაძლო ვარიანტი შემდეგია:

one one one litle dog run


^Z
one one one litle dog run
a.back() = run
one one one litle dog runrunrun
Press any key to continue . . .

ეს მეთოდი (ანუ კლასის წევრი ფუნქცია) ამ შედეგს აღწევს იმის წყალობით რომ აბრუნებს
რეფერენსს ცვლადზე.
ვცადოთ იგივე შედეგის მიღწევა გლობალური ფუნქციის შექმნის საშუალებით:
void printVector(const vector<string> &s)
{
for(int i{}; i < s.size(); i++)
cout << s[i] << '\t';
cout << endl;
}
string& back(vector<string> &s)
{
return s[s.size()-1];
}

int main()
{
vector<string> a;
string w;
while(cin >> w)
a.push_back(w);
printVector(a);
cout << "back(a)= " << back(a) << endl;
back(a) = "runrunrun";
printVector(a);
}
მისი შედეგია:

one one one litle dog run


^Z
one one one litle dog run
back(a)=run
one one one litle dog runrunrun
Press any key to continue . . .

გამოძახება
back(a) = "runrunrun";

აკეთებს შემდეგ საქმეს: მარჯვენა მხარეში ბრუნდება რეფერენსი ვექტორის ბოლო ელემენტზე,
ანუ მარჯვენა მხარე არის ვექტორის ბოლო ელემენტის ახალი სახელი. შედეგად, მას შეგვიძლია
მივანიჭოთ იგივე ტიპის ახალი მნიშვნელობა.
მეორე მაგალითად, განვიხილოთ საკითხი, თუ როგორ დავაბრუნებინოთ ერთ ფუნქციას ერთზე
მეტი მნიშვნელობა. მაგალითად, კვადრატის ფართობი და პერიმეტრი. აქ ერთი გამოსავალი
83
ასეთია: ასეთი წყვილი, შედგენილი ორი ნამდვილი რიცხვისგან, უნდა განვიხილოთ ერთ
ობიექტად. მაშინ მისი დაბრუნება შეგვიძლია return -ის საშუალებით.
ჩვენ უკვე განვიხილეთ მონაცემთა რამდენიმე ტიპი (ვექტორი, სტრინგი), რომლებიც შექმნილია
კლასის საშუალებით. C++ ენაში არის კლასი pair <ტიპი1, ტიპი2>, რომელსაც შეუძლია შექმნას
ორი სხვადასხვა ტიპისგან წყვილი. წყვილის პირველ და მეორე ელემენტზე წვდომა ხორციელდება
სახელის და არა ინდექსის საშუალებით. შემდეგი კოდი გვიჩვენებს მცირე ნაწილს იმისა, თუ
როგორ შეიძლება ამ კლასის გამოყენება:
#include <iostream>
#include <vector>
using namespace std;
pair<double,double> kvadrati(double side)
{
pair<double,double> point;
point.first = side * side;
point.second = 4 * side;
return point;
}
int main()
{
auto k = kvadrati(11);
cout << "tu gverdi = 11, fartobi = " << k.first
<< ", perimetri = " << k.second << endl;
}
შედეგი იგივეა, რაც რეფერენსის და პოინტერის გამოყენების შემთხვევაში:
tu gverdi = 11, fartobi = 121, perimetri = 44
Press any key to continue . . .

<<< დიაპაზონიანი for -ის გამოყენება რეფერენსებთან ერთად


თუ მთავარ ფუნქციაში ვართ, დიაპაზონიანი for-ის გამოყენება, როდესაც დიაპაზონის ცვლადის
ტიპი რეფერენსით განისაზღვრება, შეგვიძლია ორი სახით, იმის მიხედვით, გვინდა დიაპაზონში
რამის შეცვლა, თუ არა. მაგალითად:
#include <iostream>
#include <vector>
using namespace std;
int main()
{
int x[10] = { 1, -2, 3, -4, 5, 6, -7, 8, 9, -10 };
for (const auto &y : x){ // y ეფექტურია, როდესაც კონტეინერის ცვლილება არ გვჭირდება
cout << y << " ";
}
cout << endl;
for (auto &y : x) { // ეფექტურია, როდესაც კონტეინერის ცვლილება გვჭირდება
y = abs(y);
cout << y << " ";
}
cout << endl;
}

შევქმნათ ვექტორის ელემენტების ბეჭდვის და შეცვლის ფუნქციები, რომლებშიც გამოვიყენებთ


დიაპაზონიან for-ს:

#include <iostream>
#include <vector>
using namespace std;
void printVec(const vector<int>& );
84
void changeVec(vector<int>& ){
int main()
{
vector<int> a { 1, -2, 3, -4, 5, 6, -7, 8, 9, -10 };
printVec(a);
changeVec(a);
cout << "After change ";
printVec(a);
}
void printVec(const vector<int>& x)
{
cout << "Vector is\n";
for (const int& elem : x) // ან for (const auto& elem : x)
cout << elem << ' ';
cout << endl;
}
void changeVec(vector<int>& x)
{
for (auto &y : x) // ან for (int &y : x)
{
y = pow(y, 3.);
}
}
პროგრამის შესრულების შედეგია: Vector is
1 -2 3 -4 5 6 -7 8 9 -10
After change Vector is
1 -8 27 -64 125 216 -343 512 729 -1000
Press any key to continue . . .

85

You might also like