Програмен език C++
(съдържание)

Част 14. Връщане на стойност от функциите

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

1. Намиране модула на комплексно число. В част 10 въведохме клас, който представя комплексните числа. Също така, в уводния материал на лекцията се запознахме с комплексните числа и някои операции с тях, както и какво е това модул на комплексно число, който още се нарича големина или абсолютна стойност на комплексното число. Ето една кратка програма, която пресмята модула на комплексни числа:

// complex1.cpp
// a class that models a complex number data type

#include <iostream.h>

class Complex_number
{
  private:
    float real;          // real part
    float imaginary;     // imaginary part
  public:
    void set()
    {
      char dummy;        // for comma

      cout << "Enter number (format Re, Im): ";
      cin >> real >> dummy >> imaginary;
    }

    void display()
    {
      cout << '('
           << real       // real part
           << ','
           << imaginary  // imaginary part
           << ')';
    }

    float module()
    {
      float m = real*real + imaginary*imaginary;
      return sqrt(m);
    }
};  // class Complex_number

void main()
{
  Complex_number c1;  // create a complex_number variable
  char choice;

  do
  {
    cout << "For c1, ";
    c1.set();       // set c1

    // calculate module of c1 and assign it to mod
    float mod = c1.module();

    cout << "Module of ";
    c1.display();         // display t3
    cout << " =  " << mod;

    cout << "\nDo another (y/n)? ";
    cin >> choice;
  } while(choice != 'n');

}  //end main

Функцията module() пресмята модула на обекта (комплексното число), за който се извиква. Тя се дефинира да връща стойност от реален тип float като за целта пред името се изписва типът на връщаната стойност: float module(). В първия ред на тялото на функцията се изчислява сума от квадратите на съставните части на комплексното число. Обърнете внимание на ключовата дума (keyword) return в следващия ред - на този ред директно се изчислява корен квадратен от спомената сума с функцията sqrt() и този корен директно се връща от функцията. В програмата (в main()) съответното присвояване на резултата от функцията става с твърдението float mod = c1.module();, което показва че се изчислява модулът на комплексното число c1 и този модул се присвоява на променливата от реален тип mod.

2. Твърдението (statement) return. Всяка една функция като бъде извикана изпълнява кода в своето тяло и след свършване на изпълнението се връща на реда, намиращ се след реда, който я извиква (казва се  - изпълнява се реда - понеже обичайна практика е да се пише едно програмно твърдение (statement) на един програмен ред). При функциите, които връщат стойност е необходимо присъствието на твърдението return в тялото на функцията. То започва с ключовата дума return и след нея следва връщаната стойност, например return m;. Вместо конкретна стойност след return може да стои израз, точно както е във функцията module().

 return sqrt(m);

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

1 вариант

    float module()
    {
      float m = real*real + imaginary*imaginary;
      cout << "+++++++++++++++++++++++";
      return sqrt(m);
    }

2 вариант

    float module()
    {
      float m = real*real + imaginary*imaginary;
      return sqrt(m);
      cout << "+++++++++++++++++++++++";
    }

Съотвенто съобщенията на екрана ще са следните:

1 вариант

For c1, Enter number (format Re, Im): 2,3
+++++++++++++++++++++++Module of (2,3) =  3.60555
Do another (y/n)?

2 вариант

For c1, Enter number (format Re, Im): 2,3
Module of (2,3) =  3.60555
Do another (y/n)?

И функциите, които не връщат стойност, могат да имат твърдението return, но ключовата дума не е последвана от някаква стойност или израз, а просто служи за реализиране на логиката на функцията. Например, функцията за делене на дадено комплексно число на друго, която беше оставена за домашна работа в част 11 би изглеждала по този начин:

  // divide the complex number by another one (compl_num)
  void div(complex_number compl_num)
  {
    complex_number cn;

    cn.real = real;
    cn.imaginary = imaginary;

    float sqr_mod = compl_num.module();
    if (sqr_mod == 0)
    {
      cout << "Devision by zero!" << endl;
      return;
    }

    sqr_mod *= sqr_mod;

    real = (cn.real*compl_num.real + compl_num.imaginary*cn.imaginary)/sqr_mod;
    imaginary = (compl_num.real*cn.imaginary - cn.real*compl_num.imaginary)/sqr_mod;
  }

При проверката на равенство на модула на комплексното число на нула, ако променливата sqr_mod (която е квадрат на модула на комплексното число, но по време на проверката е модулът) е нула, програмата изписва Devision by zero! и излиза от функцията div(complex_number compl_num). Тук отново една функция на класа, div(complex_number compl_num), извиква друга функция на класа - module().

3. Автоматични променливи и стек. Променливите cn и sqr_mod са така наречените автоматични променливи (Automatic Variables). Те се създават автоматично от програмата когато функцията се извиква и след изпълнението на програмата те се разрушават и освобождават използваната от тях памет. Първата променлива cn се декларира и след това се инициализа със стойност, докато sqr_mod се декларира и инициализира със стойност, която се изчислява от функцията module(). Автоматичните променливи се съхраняват в стека (Stack).

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

Безименни автоматични променливи (Nameless Automatic Variables): във функцията module() двата програмни реда могат да се сбият на един по следния начин:

    float module()
    {
      return sqrt(real*real + imaginary*imaginary);
    }

Изчисленията в скобите ще се проведат и присвоят на безименна променлива, която ще бъде коренувана и резултатът ще бъде върнат на променлива от тип float, защото функцията връща такъв тип резултат. Безименната автоматична променлива ще бъде унищожена при излизане от функцията.

4. Програма за аритметични действия с комплексни числа. Пълният код на примерната програма е следният:

// complex2.cpp
// a class that models a complex number data type

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

class complex_number
{
  private:
    float real;          // real part
    float imaginary;     // imaginery part
  public:
    void set()
    {
      char dummy;        // for comma

      cout << "Enter number (format Re, Im): ";
      cin >> real >> dummy >> imaginary;
    }

    void display()
    {
      cout << '('
           << showpoint
           << setprecision(6) << real       // real part
           << ','
           << setprecision(6) << imaginary  // imaginary part
           << ')';
    }

    // add to the complex number another one (compl_num)
    void add(complex_number compl_num)
    {
      real += compl_num.real;
      imaginary += compl_num.imaginary;
    }

    // subtract from the complex number another one (compl_num)
    void sub(complex_number compl_num)
    {
      real -= compl_num.real;
      imaginary -= compl_num.imaginary;
    }

   // multiply the complex number by another one (compl_num)
    void mult(complex_number compl_num)
    {
      complex_number cn;
      cn.real = real;
      cn.imaginary = imaginary;

      real = cn.real*compl_num.real - compl_num.imaginary*cn.imaginary;
      imaginary = cn.real*compl_num.imaginary + compl_num.real*cn.imaginary;
    }

    // divide the complex number by another one (compl_num)
    void div(complex_number compl_num)
    {
      complex_number cn;

      cn.real = real;
      cn.imaginary = imaginary;

      float sqr_mod = compl_num.module();
      if (sqr_mod == 0)
      {
        cout << "Devision by zero!" << endl;
        return;
      }

      sqr_mod *= sqr_mod;

      real = (cn.real*compl_num.real + compl_num.imaginary*cn.imaginary)/sqr_mod;
      imaginary = (compl_num.real*cn.imaginary - cn.real*compl_num.imaginary)/sqr_mod;
    }

    // calculate the module of the complex number
    float module()
    {
      float m = real*real + imaginary*imaginary;
      return sqrt(m);
    }

};  // class complex_number

void main()
{
  complex_number c1, c2; // create two complex_number variables
  char choice;

  do
  {
    // enter c1
    cout << "For c1, ";
    c1.set();       // set c1

    // enter c1
    cout << "For c2, ";
    c2.set();       // set c2

    cout << "\nAdd, Subtract, Multiply or Divide (a/s/m/d)? ";
    cin >> choice;

    switch (choice)
    {
      // add c2 to c1 and save the result into c1
      case 'a':
      {
        c1.add(c2);
        c1.display();         // display c1
        break;
      }
      // subtract c2 from c1 and save the result into c1
      case 's':
      {
        c1.sub(c2);
        c1.display();         // display c1
        break;
      }
      // multiply c1 by c2 and save the result into c1
      case 'm':
      {
        c1.mult(c2);
        c1.display();         // display c1
        break;
      }
      // divide c1 by c2 and save the result into c1
      case 'd':
      {
        c1.div(c2);
        c1.display();         // display c1
      }

    }  // switch choice

    cout << "\nDo another (y/n)? ";
    cin >> choice;
  } while(choice != 'n');

}  //end main

Във функцията display() има два нови манипулатора showpoint и setprecision. Първият принуждава програта да генерира винаги десетична точка за числата с плаваща запетая, а вторият определя точността с която ще се показват тези числа.

В главната функция на програмата се декларират две променливи c1 и c2 от тип complex_number. В един цикъл се вкарват стойностите на двете променливи и потребителят се пита за типа на аритметичното действие, което трябва да се извърши. В конструкцията switch се прави избор коя функция да се изпълни.

5. Връщане на стойност от типа complex_number . Четирите функции за четирите аритметични действия са дефинирани с ключовата дума void и се извикват по следния начин:

c1.add(c2);
c1.sub(c2);
c1.mult(c2);
c1.div(c2);

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

c3 = c1.add(c2);
c3 = c1.sub(c2);
c3 = c1.mult(c2);
c3 = c1.div(c2);

Този начин е много по-разбираем и самообяснителен. Ето например дефиницията на функцията mult(c2) в класа complex_number.

 // multiply two complex numbers and assign the result to third one
 complex_number mult(complex_number compl_num)
 {
   complex_number cn;

   cn.real = real*compl_num.real - imaginary*compl_num.imaginary;
   cn.imaginary = real*compl_num.imaginary + compl_num.real*imaginary;

   return (cn);
 }

В този случай cn е автоматична променлива, която съхранява временно резултата. В последния ред тя го предава на функцията. Другите три функции ги напишете сами по аналогия с тази функция. Извикването на функции в главната функция на програмата от т. 4 трябва да бъде променена по следния начин, за да може да се използват:

c3 = c1.add(c2);
c3 = c1.sub(c2);
c3 = c1.mult(c2);
c3 = c1.div(c2);

Опитайте се сами да промените прогарамата от т. 4. Ето един вариант с използване на новия вид на функциите - complex3.cpp

5. Други примери за функции. По традиция ще завършим с две програми, взети от книгата на Лафоор. Те разширяват манипулациите с обектите от класа airtime - вижте примерната програма timecnv1 от предишния материал.

В първата примерна програма, timecnv2.cpp, обект от тип airtime се превръща в обект от тип int с функцията AirtimeToMins.

  int AirtimeToMins()   // convert airtime to minutes
  {
     int imins = hours*60 + minutes;
     return imins;
  }

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

  int AirtimeToMins()   // convert airtime to minutes
  {
     return hours*60 + minutes;
  }

Във втория пример, timeret.cpp, се събират две времена (обекта от клас airtime) и сумата се присвоява на трети обект. Съответната функция е:

  airtime add(airtime at2)
  {
     airtime temp;
     temp.minutes = minutes + at2.minutes;   // add minutes
     temp.hours = hours + at2.hours;         // add hours

     if(temp.minutes > 59)                   // if carry,
     {
        temp.minutes = temp.minutes - 60;    // adjust minutes
        temp.hours = temp.hours + 1;         // and hours
     }

     if(temp.hours > 23)                     // if carry,
        temp.hours = temp.hours - 24;        // adjust hours

     return temp;
  }

Разбира се, в тялото на главната функция извикването на функцията add е:

t3 = t1.add(t2);      // add t1 and t2, result in t3

С това завършихме увода във функциите на класовете и в следващата лекция ще се занимаем с масивите (arrays) и низовете (strings).

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

Литература

[1] Robert Lafore; C++ Interactive Course. Waite Group Press, Macmillan Computer Publishing, 1996.

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

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

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