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

Замяна на функции
или т.н.
Презаредени функции
(Overloaded Functions)

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

Замяната на функции или т.н. презаредени функции е едно превъзходно удобство в C++. Тази концепция позволява да се използва едно и също име за различни функции, чрез различаването им чрез набора от параметри, с които функциите се извикват.

1. Необходимост от презаредени функции (Need for Function Overloading). Нека разгледаме следния фрагмент от C код, който използва две функции за абсолютна стойност от стандартната библиотека на C (C standard library ).

  int a = -2;
  double b = -3.14;

  cout << abs(a) << endl;
  cout << abs(b) << endl;
  cout << fabs(b) << endl;

Както и очаквахме предпоследният ред дава грешен резултат (отрязана е дробната част на числото b) - вижте изходът по-долу.

2
3
3.14

За съжаление в C за абсолютна стойност има няколко функции, които оперират с различни аргументи. Функцията abs() работи с целочислени стойности, а функцията fabs() - има аргументи, които са числа с плаваща запетая. (Има и специална функция cabs(), която връща абсолютната стойност (модула) на комплексно число.) Декларацията на тези няколко функции (и още една -  fabsl) в хедър файла math.h е следната:

int abs(int x);
double fabs(double x);
long double fabsl(long double x);
double cabs(struct complex z);

Това използване на няколко функции прави програмистките грешки вероятни и е в пълно противоречие с дефиницията на абсолютна стойност в математиката. Както е известно от математиката абсолютна стойност на едно реално число (целочислените също са реални числа) е противоположното число, ако числото е отрицателно, или същото число, ако числото е положително. Т.е.

          м -x за x < 0
    |x| = н
          о x за x >= 0

Също така, използването на няколко функции (това се нарича name proliferation - разпространение на имената) не само способства за повече програмистки грешки, но затруднява програмиста, който трябва да помни и да се съобразява с различните имена. Допълнително, описването на тези няколко функции в помощния файл или в ръководството ще заеме повече място и труд.

Много по-удобно е използването на презаредени функции, които да са дефинирани с едно и също име (например Abs), но които работят с различни по тип параметри и връщат различни по тип резултати. Например предният пример може да бъде решен по следния начин със следната кратка програма:

// abs_ovlo.cpp
// function overloading: applied for absolte value function

#include <iostream.h>          // for <<
#include <conio.h>             // for getch();

int Abs(int x)
{
  if (x < 0)
    return -x;
  else
    return x;
};

double Abs(double x)
{
  if (x < 0)
    return -x;
  else
    return x;
};

long double Abs(long double x)
{
  if (x < 0)
    return -x;
  else
    return x;
};

void main()
{
  int a = -2;
  double b = -3.14;
  long double c = 2.1234567890123456789E4931L;

  // call with int argument
  cout << Abs(a) << endl;

  // call with double argument
  cout << Abs(b) << endl;

  // call with long double argument
  cout << Abs(c) << endl;

  getch();
}  // main()

Виждаме, че функцията за намиране на модул на едно число, Abs(), е дефинирана три пъти, като всеки път тя има различен тип на аргумента. Именно този различен тип позволява на компилатора да различава различните дефиниции и да знае коя точно да използва при извикване на функцията. При първото извикване тя се използва с целочислен аргумент, при второто с такъв с двойна точност и накрая с аргумент с най-голяма точност (long double).

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

2. Декларация на презаредени функции. Както видяхме С++ дава възможност няколко функции да имат едно и също име, но различен брой и тип аргументи и когато биват извиквани да знаят коя от тях ще се изпълни в зависимост от типа на параметрите, с които се извиква функцията. Но как компилаторът разпознава, например в горната кратка програма, че това са две различни функции и коя от тях трябва да бъде използвана? За да "наименова" дадена функция компилаторът гледа не само името на функцията, но и типа и броя на параметрите в нея. Това се нарича name mangling - изопачаване на името [на функцията]. Точното пълно име варира от компилатор на компилатор, но без да влизаме в подробности межем да си представим, че първата функция има име (за компилатора) Abs_int(), а втората - Abs_double().

!Обърнете внимание, че промяната само на типа на връщаната стойност не прави презаредени функции. Т.е. тези две декларации по-долу, които се различават по типа на връщаната стойност не са презаредени функции и компилаторът на Борланд Билдер дава съобщението "Type mismatch in redeclaration of 'Abs(int)'."

 int Abs(int x);
 double Abs(int x);  // not an overloaded function!

3. Друг пример за презаредени функции (A Member Function Example). Този пример е взет от превъзходния курс на  Лафоор [1], В кратката програма по-долу има дефинирани три функции на класа с едно и също име. Целта е класът да показва текст по три различни начини.

// overfun.cpp
// overloaded  functions

#include <iostream.h>
#include <string.h>                 // for strcpy()
#include <conio.h>                  // for getch()

const int MAX_LENGTH = 40;          // maximum length of text

class fancy_text                    // class displays text
{
   private:
      char text[MAX_LENGTH];        // text to be displayed

   public:
      void set_text(char tx[])      // set the text
      {
         strcpy(text, tx);
      }

      void box_display();           // line of dashes

      void box_display(char);       // line of characters

      void box_display(char, int);  // line of n characters
};

void fancy_text::box_display()      // line of 40 dashes
{
   cout << "----------------------------------------";
   cout << endl << text << endl;
   cout << "----------------------------------------";
}

// line of 40 characters
void fancy_text::box_display(char ch)
{
   int j;

   for (j = 0; j < MAX_LENGTH; j++)
      cout << ch;

   cout << endl << text << endl;

   for (j = 0; j < MAX_LENGTH; j++)
      cout << ch;
}

// line of n characters

void fancy_text::box_display(char ch, int n)
{
   int j;

   for (j = 0; j < n; j++)
      cout << ch;

   cout << endl << text << endl;

   for (j = 0; j < n; j++)
      cout << ch;
}

void main()
{
   fancy_text ft1;

   ft1.set_text("Gone with the Wind");

   ft1.box_display();               // display text with default lines
   cout << endl << endl;

   ft1.box_display('+');            // default length and equal signs
   cout << endl << endl;

   ft1.box_display('=', 18);        // equal signs; length matches text
   cout << endl << endl;

   getch();

}   // main()

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

----------------------------------------
Gone with the Wind
----------------------------------------

++++++++++++++++++++++++++++++++++++++++
Gone with the Wind
++++++++++++++++++++++++++++++++++++++++

==================
Gone with the Wind
==================

Трите презаредени функции са съответно void box_display(), void box_display(char ch) и void box_display(char ch, int n). Функциите са дефинирани извън класа - вижте използването на оператора за за решение за обхвата ::. Първата функция отпечатва текста от променливата на класа char text[MAX_LENGTH], заградена от два реда от тирета с дължина 40 символа, втората позволява избиране на символ за заграждащите редове (в случая това е е знакът плюс, който е предаден като аргумент на параметъра на функцията ch), а третата презаредена функция позволява не само избор на символ за, но и дължината на, заграждащите редове. В програмата третата функция е извикана по следния начин ft1.box_display('=', 18);,  което отпечатва два заграждащи реда с дължина от 18 символа и със символи знака равно.

Обърнете внимание, че едно грешно извикване като това по-долу, би дало крайно учудващи резултати. Компилаторът не издава предупреждение и тълкува числото 18 като 18-тия поред символ в кодовата таблица и изписва два реда с него.

   ft1.box_display(18);             // erroneous call !!!
   cout << endl << endl;

В следващата лекция ще се занимаем с т.н. аргументи по подразбиране (Default Arguments), които също ни спестяват програмен код и улесняват програмистите.

.
(съдържание)
.
Литература
.
[1] Robert Lafore; C++ Interactive Course. Waite Group Press, Macmillan Computer Publishing, 1996.

Автор: Проф. Процесор Аритметиков

.
[ това е материал от брой 26 от декември 2008 г - януари 2009 г на списание "Коснос" www.kosnos.com ]

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