Програмен език C++
Част 29

Предаване на аргументи по връзка
(Reference Arguments)

(съдържание)

В една от предишните лекции разгледахме аргументите на функциите и наблегнахме на т.н. предаване на аргументи по стойности (call-by-value). В тази лекция ще бъде разгледано предаването им по връзка (отпратка) - call-by-reference. Изложението в тази лекция плътно следва съответния материал в прекрасната книга на Лафоор [1], а програмните примери са изцяло взети от там.

При предаването на аргументите по стойност, в самата функция се създават нови променливи, които наследяват стойностите на аргументите и във функцията програмата работи с тези нови променливи, при което не променя стойностите на оригиналните аргументи. Споменахме, че има изключение от това правило и то е при предаване на масиви като аргументи на функция - при тях се предава адресът на първия елемент на масива и той не може да се променя (т.е. прави се негово копие във функцията), но пък стойностите на отделните елементи могат да се променят.

В тази лекция ще научим как да променяме оригиналните стойности на аргументите, които се предават към една функция.

! Тъй като има проблеми с буквалния превод на български, то следните твърдения или имена ще се раглеждат еквивалентни

референт = връзка = променлива-връзка

предаване по референт = предаване по връзка

създаване на връзка към променлива = рефериране на променлива

1. Предаване по стойност (Passing by Value). Да рагледаме следния пример за размяна на две целочислени стойности alpha и beta.

void swap_by_val(int a, int b)  // function swaps values???
{
  int temp = a;
  a = b;
  b = temp;
}

Аргументите се предават по стойност на две нови променливи a и b, чиито стойности се разменят в тялото на функцията. Но оригиналните стойности не се засягат! Т.е.функцията е неизползваема в този си вид.

2. Предаване по връзка (Passing by Reference). Нека променим горната функция като добавим знака амперсанд, &, след типа на аргументите int. (Също така ще променим името на функцията, за да я различаваме от предишната.) Ако използваме този код в програма ще видим, че той наистина прави размяна на стойностите на аргументите alpha и beta. Ето една кратка програма, която илюстрира разликата между двете функции.

// swap.cpp
// ilustrates the Passing by Reference
// function swap_by_ref swaps int values

#include <iostream.h>
#include <conio.h>
using namespace std;

void swap_by_val(int a, int b)  // function swaps values???
{
  int temp = a;
  a = b;
  b = temp;
}

void swap_by_ref(int & a, int & b)  // function does really swap values!!!
{
  int temp = a;
  a = b;
  b = temp;
}

void main()
{
  int alpha = 2;          // initialize two ints
  int beta = 7;

  cout << "original alpha = " << alpha << endl;
  cout << "original beta = " << beta << endl;

  swap_by_val(alpha, beta);

  cout << "alpha = " << alpha << endl;
  cout << "beta = " << beta << endl;

  swap_by_ref(alpha, beta);

  cout << "alpha = " << alpha << endl;
  cout << "beta = " << beta << endl;

  // added to stop the screen
  getch();

}  // end main()

Изходът от програмата е следният:

original alpha = 2
original beta = 7
alpha = 2
beta = 7
alpha = 7
beta = 2

което показва, че втората функция действително разменя аргументи за разлика от първата. Причината за това е, че вече a и b са псевдоними (референти, връзки към) оригиналните стойности и по този начин в тялото на функцията се работи с оригиналните стойности alpha и beta. На практика a указва към същото място в паметта, където указва alpha, както и b  указва към същото място в паметта, където указва beta.

3. Размяна на обекти (Swapping Objects). Следната кратка програма илюстрира използването на предаването на аргументи по връзка когато аргументите са обектите на даден клас. Обърнете внимание, че разменящата функция swap първо е декларирана и след това е дефинирана (чак след главната функция). Не е необходимо тя да бъде функция на класа, защото не се занимава с променливите на класа, а с обектите като цяло. При декларацията се изписва типа на аргументите, последван от символа амперсенд &.

// swapobj.cpp
// function swaps employee objects

#include <iostream.h>
#include <conio.h>

////////////////////////////////////////////////////////////////

const int SMAX = 21;           // maximum length of strings

class employee
{
   private:
      char name[SMAX];         // name (20 chars max)
      int serial_number;

   public:
      void input()             // get data from user
      {
         cout << "   Enter name: ";
         cin >> name;
         cout << "   Enter serial number: ";
         cin >> serial_number;
      }

      void output()            // display employee data
      {
         cout << "   Name = " << name;
         cout << "\n   Serial number = " << serial_number;
      }
};

void main()
{
   void swap(employee &, employee &);           // declaration

   employee e1, e2;

   cout << "Enter data for employee 1" << endl;
   e1.input();

   cout << "Enter data for employee 2" << endl;
   e2.input();

   cout << "Swapping employee data";
   swap(e1, e2);                               // swap employees

   cout << "\nData for employee 1" << endl;
   e1.output();

   cout << "\nData for employee 2" << endl;
   e2.output();

  // added to stop the screen
  getch();

}  // end main()

void swap(employee & emp1, employee & emp2)      // definition
{
   employee temp = emp1;
   emp1 = emp2;
   emp2 = temp;
}

4. Връзка към променливи, които не са аргументи (Standalone References). За да вникнем по-добре във връзките към променливите (references) ще покажем, че можем да създадем връзка и към променлива, която не е аргумент. Нека разгледаме следната кратка програма:

// standalone.cpp
// demonstrates standalone reference

#include <iostream.h>
#include <iomanip.h>
#include <conio.h>

////////////////////////////////////////////////////////////////

void main()
{
  int intvar = 7;
  int & refvar = intvar;

  cout << "intvar = " << intvar << endl;
  cout << "refvar = " << refvar << endl;

  intvar = 10;

  cout << "intvar = " << intvar << endl;
  cout << "refvar = " << refvar << endl;

  // added to stop the screen
  getch();
}

В нея е дефинирана променлива-връзка (reference) refvar към целочислената променлива intvar. Ще отбележим, че още със създаванета на променлива-връзка тя трябва да се инициализира да сочи към някаква променлива от същия тип - иначе компилаторът ще издаде съобщение за грешка: например само твърдението

  int & refvar;

ще даде следното съобщение на компилатора:

Reference variable 'refvar' must be initialized.

Оттук нататък всяка една промяна на променливата intvar ще води и до промяна на стойността, към която сочи и нашата променлива-връзка refvar. В горната програма стойността на intvar се променя от първоначалната 7 на 10. Ето защо изходът от горната програма ще е:

intvar = 7
refvar = 7
intvar = 10
refvar = 10

Също така може да променяте и променливата-връзка, refvar, като при това се засяга и целочислената променлива intvar. Например, ако добавите в горната програма следните три реда точно преди getch();

  refvar = 17;
  cout << "intvar = " << intvar << endl;
  cout << "refvar = " << refvar << endl;

те ще доведат до следния изход:

intvar = 7
refvar = 7
intvar = 10
refvar = 10
intvar = 17
refvar = 17

След като променливата-връзка е създадена с инициализация, тя не може да се променя, в смисъл да стане връзка към друга променлива. Това означава, че следните редове (добавени в програмата standalone.cpp):

  int newint = 101;
  refvar = newint;

просто променят стойността на refvar на 101, и съответно на intvar на 101, а не правят refvar да е референт към newint! Затова добавянето в горната програма на следните четири реда точно преди getch();

  int newint = 11;
  refvar = newint;

  cout << "intvar = " << intvar << endl;
  cout << "refvar = " << refvar << endl;

ще доведат до следния изход от нея:

intvar = 7
refvar = 7
intvar = 10
refvar = 10
intvar = 101
refvar = 101

Това горе означава, че ако променим стойността на newint, то стойността на refvar и intvar не се променя. За да проверите това, добавете още два реда в програмата standalone.cpp.

  newint = 1001;

  cout << "intvar = " << intvar << endl;
  cout << "refvar = " << refvar << endl;

На практика при създаване на променлива-връзка програмата работи с адреса на оригиналната променлива, която е реферирана и както разбрахме този адрес не може да се промени, а само в него може да се записват нови стойности. Ето и пълната версия на променената програма standalone.cpp.

// standalone1.cpp
// demonstrates standalone reference

#include <iostream.h>
#include <iomanip.h>
#include <conio.h>

////////////////////////////////////////////////////////////////

void main()
{
//  int intvar = 7;
  int intvar;
  int & refvar = intvar;
 

  cout << "intvar = " << intvar << endl;
  cout << "refvar = " << refvar << endl;

  intvar = 7;

  cout << "intvar = " << intvar << endl;
  cout << "refvar = " << refvar << endl;

  intvar = 10;

  cout << "intvar = " << intvar << endl;
  cout << "refvar = " << refvar << endl;

  refvar = 17;

  cout << "intvar = " << intvar << endl;
  cout << "refvar = " << refvar << endl;

  cout << "++++++++++++++++++++++++" << endl;

  int newint = 101;
  refvar = newint;

  cout << "intvar = " << intvar << endl;
  cout << "refvar = " << refvar << endl;

  newint = 1001;

  cout << "newint = " << newint << endl;
  cout << "intvar = " << intvar << endl;
  cout << "refvar = " << refvar << endl;

  // added to stop the screen
  getch();
}

В заключение ще споменем, че предаването на аргументи по стойност си има своето предимство да предпазва оригиналните стойности на аргументите от неволна промяна в тялото на функцията. Но от друга страна, това предаване по стойност има два големи недостатъка. Първо, създава се ново копие на аргумента и това може да е свързано, например при аргументи-обекти, с изхабяване на голямо количество памет, и второ, създаването на копие на обект не е толкова елементарен процес - той е свързан с извикване на конструктора на обекта и някои други инициализации, а тези процеси водят до забавяне на програмата, а понякога и до нежелани промени с обектите (или други променливи).

.
(съдържание)
.
Литература
.
[1] Robert Lafore; C++ Interactive Course. Waite Group Press, Macmillan Computer Publishing, 1996.
.
Автор: Проф. Процесор Аритметиков

[ това е материал от брой 30 от май 2009 г на списание "Коснос" www.kosnos.com ]

Keywords: С++,  OOP programming , C++ , Classes , Inheritance , Reusability , Creating New Data Types , Polymorphism and Overloading
Ключови думи: клас , обект, обектно ориентирано програмиране , полиморфизъм switch if else ?