Download as pdf or txt
Download as pdf or txt
You are on page 1of 6

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

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


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

რეფერენსი, ანუ გადამისამართება


მეორე სტრიქონში მოყვანილი განაცხადი
int j = 10, k;
int &i{ j }; // განაცხადი i მითითებაზე (ფსევდონიმზე)
აღნიშნავს, რომ 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 ცვლადის მეორე მეტსახელი. ასეთი


გადამისამართებები ძალიან სასარგებლოა სწრაფი და უსაფრთხო ფუნქციების შესაქმნელად.
თუ გადამისამართება მუდმივი არაა, მარჯვენა მხარეში უნდა იყოს მისამართის მქონე
ობიექტი/(ცვლადი (გადამისამართება ხდება ცვლადზე), - ეს ყველაზე ეკონომიური გზაა ახალი
ცვლადის შექმნის, რადგან მეხსიერებაში ადგილის გამოყოფა არ ხდება ამ დროს (მეხსერების
გამოყოფისა და გაუქმების ოპერაციები შედარებით დიდხანს გრძელდება). გამონაკლისს
წარმოადგენს მუდმივზე რეფრენსი, რაც მართლაც ახალ ცვლადს შექმნის და მას მიანიჭებს
მარჯვენა მხარეში მდგარ მუდმივს. მაგალითად:
const char& c{'?'};
ეს გამონაკლისი გაკეთბულია იმისთვის, რომ გაადვილდეს და კიდევ უფრო მოქნილი გახდეს
ფუნქციების შექმნა გადამისამართებების გამოყენებით.
ქართულ წყაროებში რეფერენსს, ანუ გადამისამართებას რამდენიმე განსხვავებული ტერმინით
მოიხსენებენ (მაგალითად, მითითება).
<<< არგუმენტის გადაცემა გადამისამართებით (Pass-by-reference )
ფუნქციის გამოძახების დროს მის პარამეტრებს და ლოკალურ ცვლადებს გამოეყოფათ ადგილი
ოპერატიული მეხსიერების განსაკუთრებულ ნაწილში – სტეკში. როდესაც არგუმენტი
გადაეცემა ფუნქციას მნიშვნელობით, ფუნქციის პარამეტრს ენიჭება ამ არგუმენტის ასლი.
ამიტომ, თუ ფუნქციის ტანში მისი პარამეტრი იცვლის მნიშვნელობას, შესაბამისი არგუმენტი
არ იცვლება. მაგრამ,
ხშირად არგუმენტის ასლის გადაცემა არახელსაყრელია. მაგალითად, როდესაც:
Page 1 of 6
 ფუნქციიდან ერთზე მეტი მნიშვნელობის დაბრუნება გვჭირდება. მაშინ ერთ
მნიშვნელობას დააბრუნებს return შეტყობინება, დანარჩენი მნიშვნელობების დაბრუნება
კი უნდა მოხერხდეს პარამეტრების მეშვეობით.
 პროგრამაში გვჭირდება ფუნქციის მიერ არგუმენტის მნიშვნელობის შეცვლა.
 ფუნქციას დასამუშავებლად გადაეცემა დიდი ობიექტები. ასეთი არგუმენტის გადაცემა
მნიშვნელობით (მისი გაასლება პარამეტრში) მოითხოვს გარკვეულ დროს და ადგილს
მეხსიერებაში, რაც ანელებს პროგრამის შესრულებას დარისკთანაა დაკავშირებული,
რადგან შესაძლოა შეიქმნას გამონაკლისი შემთხვევა.
როდესაც ფუნქციის პარამეტრს გადაეწოდება არა არგუმენტის ასლი, არამედ არგუმენტზე
გადამისამართება, მაშინ ყველა ცვლილება, რომელსაც ფუნქციაში განიცდის პარამეტრი,
გადამისამართდება და სინამდვილეში ხორციელდება არგუმენტზე. ასეთ პარამეტრებს
ეწოდებათ ცვლადი პარამეტრები. მაგალითად, პროტოტიპში
void f(int & i);
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 m = 1;
cout << "The initial value of m: "
<< m << '\n';
f(m); //value –ს გადავცემთ მითითებით
cout << "The new value of m: "
<< m << '\n';
}
void f(int &i){
i = 10; // არგუმენტის შეცვლა The initial value of m: 1
} The new value of m: 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 << "Vector: ";
printVector(a);
}
void fillVector(vector<int>& a)
Page 2 of 6
{
std::cout << "Enter integer numbers, plz" << std::endl;
int n;
while (std::cin >> n)
a.push_back(n);
}
void printVector(const vector<int>& a)
{
for (auto m:a) std::cout << m << “ “;
std::cout << std::endl; Enter integer numbers, plz
} 3 -7 19 231 0 25 -87 ^Z
Vector: 3 -7 19 231 0 25 -87
პროგრამის შედეგია: Press any key to continue . . .
void fillVector(vector<int>& a);
ფუნქციის პარამეტრი წარმოადგენს reference–პარამეტრს. ეს აუცილებელია, რადგან
ფუნქციის დანიშნულებაა ჩაწეროს ვექტორში კლავიატურიდან შემოსული მთელი რიცხვები,
ანუ ფუნქციის
fillVector(a);
გამოძახების შედეგად a ვექტორი უნდა შეიცვალოს.
ფუნქცია printVector ბეჭდავს ვექტორს. რადგან a ვექტორის ზომა შეიძლება იყოს დიდი, ამ
ფუნქციის პარამეტრი მიზანშეწონილია გადავამისამართოთ არგუმენტზე. რადგან ვექტორის
შეცვლას არ ვგეგმავთ, გამოვიყენოთ const reference პარამეტრი
void printVector(const vector<int>& a);
ახლა თუ ნებით ან უნებლიეთ შევეცდებით a ვექტორის შეცვლას, კომპილერი გამოიტანს
შეცდომის გზავნილს: 'a' : 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;
cout << "Enter integer numbers, plz" << endl;
while (cin >> m)
a.push_back(m);
printVector(a);
}
void printVector(const vector<int> v)
{
for (auto m : v) std::cout << m << '\t';
std::cout << std::endl;
}
ფუნქციის გამოძახება
printVector(a);
იწვევს ახალი მუდმივი
const vector<int> v;

Page 3 of 6
ვექტორის შექმნას, რომელშიც გაასლდება მთავარ პროგრამაში შექმნილი a ვექტორი. ეს ახალი
v ვექტორი (ანუ a-ს ასლი) დაიბეჭდება, ფუნქცია დაასრულებს მუშაობას და v ვექტორი
გაუქმდება, რადგან იგი წარმოადგენს ფუნქციის ლოკალურ ცვლადს და წყვეტს არსებობას
ფუნქციის დასრულებასთან ერთად.
თუ ამ პროგრამაში გამოვიყენებთ ბეჭდვის ფუნქციას, რომელსაც პარამეტრს გადავცემთ
გადამისამართებით (რეფერენსით):
void printVector(const vector<int> &v)
{
for (auto m : v) std::cout << m << '\t';
std::cout << std::endl;
}
მაშინ ფუნქციის printVector(a); გამოძახების შედეგად არავითარი ახალი ვექტორი არ
იქმნება. უკვე არსებულ a ვექტორს დაერქმევა ახალი სახელი v, და ამიტომ v -ს ბეჭდვა ნიშნავს
a-ს ბეჭდვას. ფუნქციის დასრულების შემდეგ, a-ს ახალი სახელი (რომელიც ფუნქციის
ლოკალური ცვლადია) გაუქმდება.
როგორც ვხედავთ, პარამეტრად ვექტორის ასლის გადაწოდება ზრდის გამოყენებულ
მეხსიერებას და შესრულების დროს. ამიტომ, უპირატესობა უნდა მივანიჭოთ მეთოდებს,
რომლებიც უფრო სწრაფად და ეკონომიურად ასრულებებნ იგივე საქმეს.
მომდევნო მაგალითში, გავაკეთოთ ფუნქცია, რომელიც პარამეტრად მიიღებს ინფორმაციას
კვადრატის გვერდის შესახებ და გამოთვლის მის ფართობს და პერიმეტრს.
პირველი ვარიანტი, როდესაც პარამეტრად გადავცემთ არგუმენტის ასლს, არამარტო დროისა
და მეხსიერების თვალსაზრისით არის არახელსაყრელი, არამედ საკმაოდ რთულიცაა, რადგან
ჩვენ მოგვიწევს ვიზრუნოთ ისეთი კონტეინერის გამოყენებაზე, რომელიც ორ ნამდვილ რიცხვს
ერთდროულად შეინახავს და დაბრუნებს. ამ საკითხს ამავე ამოცანის მაგალითზე განვიხილავთ
შემდეგ პუნქტში, როდესაც ფუნქციების დასაბრუნებელ მნიშვნელობებზე უფრო დაწვრილებით
ვისაუბრებთ. რეფერენსების საშუალებით შექმნილი კოდი მარტივი გასაგებია:
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 << "Side is 11, area - "
<< area << ", perimeter = " << perimeter << endl;
}
აქაც, გამოძახება
kvadrati(11, area, perimeter);
პირველ რიგში იწვევს ფუნქციის ლოკალური ცვლადების შექმნას და ინიციალიზებას:
side = 11;
double &s = area;
double &p = perimeter;
კერძოდ, area ცვლადს დაექვა მეორე სახელი s. ამის საფუძველზე, სტრიქონი
s = side * side;
იგივეა რაც
area = 11 * 11;

Page 4 of 6
მივაღწიეთ იგივე შედეგს რასაც პოინტერების შემთხვევაში.
მესამე მაგალითად შეგვიძლია განვიხილოთ ორი ცვლადის მნიშვნელობის გაცვლის ამოცანა,
რომელშიც უბრალოდ შეუძლებელია პარამეტრების ასლის გადაცემის გზით რაიმე შედეგის
მიღწევა.
<<< რეფერენსი და კლასის ობიექტი - ფუნქციის დასაბრუნებელი მნიშვნელობები
ზოგადად, ფუნქციას შეუძლია დააბრუნოს ნებისმიერი ტიპის მონაცემი, რომელიც
მისაწვდომია პროგრამისთვის. მათ შორისაა როგორც პრიმიტიული ტიპის ცვლადები (მთელი,
ნამდვილი და სხვა), ასევე სხვადასხვა კლასის ობიექტები, მათზე გადამისამართებები
(რეფერენსები), პოინტერები (იხ. შემდეგ თემებში).
პირველ მაგალითში, ჩვენ მოკლედ შევეხებით რეფერენსის დაბრუნების საკითხს. განვიხილოთ
კოდი:
void printVector(const vector< string > &v)
{
for (auto m : v) std::cout << m << '\t';
std::cout << std::endl;
}
int main()
{
vector<string> a;
string w;
cout << "Enter stirings, plz" << endl;
while(cin >> w)
a.push_back(w);
printVector(a);
cout << "a.back() = " << a.back() << endl;
a.back() = "runrunrun";
printVector(a);
} one one one litle dog run
vector კლასის მეთოდი .back()საშუალებას ^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 > &v)
{
for (auto m : v) std::cout << m << '\t';
std::cout << std::endl;
}
string& back(vector<string> &s)
{
return s[s.size() - 1];
}
int main()
{
vector<string> a;
string w;
cout << "Enter stirings, plz" << endl;
while (cin >> w)
a.push_back(w);
printVector(a);
Page 5 of 6
cout << "a.back() = " << back(a) << endl;
back(a) = "runrunrun";
printVector(a);
}
მისი შედეგი იგივე შედეგს გვაძლევს რაც ზემოთ იყო, რადგან back(a) გადამისამართდება და
გაიგივდება a ვექტორის ბოლო ელემენტთან. გამოძახება
back(a) = "runrunrun";
აკეთებს შემდეგ საქმეს: მარჯვენა მხარეში ბრუნდება რეფერენსი ვექტორის ბოლო ელემენტზე,
ანუ მარჯვენა მხარე არის ვექტორის ბოლო ელემენტის ახალი სახელი. შედეგად, მას შეგვიძლია
მივანიჭოთ იგივე ტიპის ახალი მნიშვნელობა.
მეორე მაგალითად, განვიხილოთ საკითხი, თუ როგორ დავაბრუნებინოთ ერთ ფუნქციას ერთზე
მეტი მნიშვნელობა. მაგალითად, კვადრატის ფართობი და პერიმეტრი. აქ ერთი გამოსავალი
ასეთია: ასეთი წყვილი, შედგენილი ორი ნამდვილი რიცხვისგან, უნდა განვიხილოთ ერთ
ობიექტად. მაშინ მისი დაბრუნება შეგვიძლია 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 << "Side is 11, area - " << k.first
<< ", perimeter - " << k.second << endl;
}
შედეგი იგივეა, რაც რეფერენსის და პოინტერის გამოყენების შემთხვევაში:

<<< დიაპაზონიანი 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;
}
Page 6 of 6

You might also like