Основы функций в C++

C++Beginner
Практиковаться сейчас

Введение

В этом лабе вы изучите функции на C++. Вы научитесь определять и вызывать функции, а также передавать аргументы в функции.

Предварительный просмотр содержания

Иногда определенная часть кода приходится использовать многократно. Лучше поместить их в "подпрограмму" - функцию, и многократно "вызывать" эту функцию - для удобства сопровождения и понимания.

При использовании функции участвуют две стороны: вызывающий, который вызывает функцию, и функция, которая вызывается. Вызывающий передает аргумент(ы) функции. Функция получает эти аргумент(ы), выполняет запрограммированные операции внутри тела функции и возвращает результат обратно вызывающему.

Использование функций

Предположим, что нам нужно много раз вычислять площадь круга. Лучше написать функцию под названием getArea() и повторно использовать ее по мере необходимости.

/* Test Function */
#include <iostream>
using namespace std;
const int PI = 3.14159265;

// Прототип функции (Объявление функции)
double getArea(double radius);

int main() {
   double radius1 = 1.1, area1, area2;
   // вызов функции getArea()
   area1 = getArea(radius1);
   cout << "area 1 is " << area1 << endl;
   // вызов функции getArea()
   area2 = getArea(2.2);
   cout << "area 2 is " << area2 << endl;
   // вызов функции getArea()
   cout << "area 3 is " << getArea(3.3) << endl;
}

// Определение функции
// Возвращает площадь круга по заданному радиусу
double getArea(double radius) {
   return radius * radius * PI;
}

Вывод:

area 1 is 3.63
area 2 is 14.52
area 3 is 32.67

image desc

image desc

В C++ вам нужно объявить прототип функции (перед использованием функции), и предоставить определение функции, с телом, содержащим запрограммированные операции.

Синтаксис определения функции следующий:

returnValueType functionName ( parameterList ) {
   functionBody ;
}

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

// Прототип функции - размещен перед использованием функции.
double getArea(double);  // без имени параметра
double getArea(double radius);  // имена параметров игнорируются, но служат в качестве документации

Возвращаемый тип "void"

Если не нужно возвращать значение вызывающему, вы можете объявить тип возвращаемого значения как void. В теле функции вы можете использовать оператор "return;" без возвращаемого значения, чтобы вернуть управление вызывающему.

Фактические параметры vs. формальные параметры

В приведенном выше примере переменная (double radius), объявленная в сигнатуре getArea(double radius), называется формальным параметром. Его область видимости находится внутри тела функции. Когда функция вызывается вызывающим, вызывающий должен предоставить так называемые фактические параметры (или аргументы), значение которых затем используется для фактических вычислений. Например, когда функция вызывается с помощью "area1 = getArea(radius1)", radius1 является фактическим параметром, со значением 1.1.

Область видимости локальных переменных и параметров функции

Все переменные, включая параметры функции, объявленные внутри функции, доступны только для функции. Они создаются при вызове функции и освобождаются (уничтожаются) после возврата функции. Они называются локальными переменными, потому что они локальны для функции и недоступны вне функции.

Параметры по умолчанию

В C++ для функций вводятся так называемые стандартные аргументы. Эти стандартные значения будут использоваться, если вызывающий не указывает соответствующий фактический аргумент при вызове функции. Стандартные аргументы задаются в прототипе функции и не могут повторяться в определении функции. Стандартные аргументы разрешаются в зависимости от их позиций. Следовательно, они могут использоваться только для замены последних аргументов, чтобы избежать неоднозначности. Например,

/* Test Function default arguments */
#include <iostream>
using namespace std;

// Прототип функции - Указывайте стандартные аргументы здесь
int fun1(int = 1, int = 2, int = 3);
int fun2(int, int, int = 3);

int main() {
   cout << fun1(4, 5, 6) << endl; // Без стандартных
   cout << fun1(4, 5) << endl;    // 4, 5, 3(стандартный)
   cout << fun1(4) << endl;       // 4, 2(стандартный), 3(стандартный)
   cout << fun1() << endl;        // 1(стандартный), 2(стандартный), 3(стандартный)

   cout << fun2(4, 5, 6) << endl; // Без стандартных
   cout << fun2(4, 5) << endl;    // 4, 5, 3(стандартный)
   // cout << fun2(4) << endl;
   // ошибка: слишком мало аргументов для функции 'int fun2(int, int, int)'
}

int fun1(int n1, int n2, int n3) {
   // не могут повторяться стандартные аргументы в определении функции
   return n1 + n2 + n3;
}

int fun2(int n1, int n2, int n3) {
   return n1 + n2 + n3;
}

Вывод:

15
12
9
6
15
12

image desc

Перегрузка функций

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

/* Test Function Overloading */
#include <iostream>
using namespace std;

void fun(int, int, int);  // Версия 1
void fun(double, int);          // Версия 2
void fun(int, double);          // Версия 3

int main() {
   fun(1, 2, 3);   // версия 1
   fun(1.0, 2);    // версия 2
   fun(1, 2.0);    // версия 3
   fun(1.1, 2, 3); // версия 1 - double 1.1 приведен к int 1 (без предупреждения)

   // fun(1, 2, 3, 4);
      // ошибка: нет соответствующей функции для вызова 'fun(int, int, int, int)'
   // fun(1, 2);
      // ошибка: вызов перегруженной функции 'fun(int, int)' неоднозначный
      // примечание: возможные варианты:
      //    void fun(double, int)
      //    void fun(int, double)
   // fun(1.0, 2.0);
      // ошибка: вызов перегруженной функции 'fun(double, double)' неоднозначный
}

void fun(int n1, int n2, int n3) {  // версия 1
   cout << "версия 1" << endl;
}

void fun(double n1, int n2) { // версия 2
   cout << "версия 2" << endl;
}

void fun(int n1, double n2) { // версия 3
   cout << "версия 3" << endl;
}

Вывод:

версия 1
версия 2
версия 3
версия 1

image desc

Функции и массивы

Вы также можете передавать массивы в функцию. Однако, вам также нужно передавать размер массива в функцию. Это потому, что невозможно определить размер массива по аргументу массива внутри вызываемой функции. Например,

/* Function to compute the sum of an array */
#include <iostream>
using namespace std;

// Прототип функции
int sum(int array[], int size);    // Также нужно передать размер массива
void print(int array[], int size);

// Тестовый драйвер
int main() {
   int a1[] = {8, 4, 5, 3, 2};
   print(a1, 5);   // {8,4,5,3,2}
   cout << "sum is " << sum(a1, 5) << endl;  // sum is 22
}

// Определение функции
// Возвращает сумму заданного массива
int sum(int array[], int size) {
   int sum = 0;
   for (int i = 0; i < size; ++i) {
      sum += array[i];
   }
   return sum;
}

// Выводит содержимое заданного массива
void print(int array[], int size) {
   cout << "{";
   for (int i = 0; i < size; ++i) {
      cout << array[i];
      if (i < size - 1) {
         cout << ",";
      }
   }
   cout << "}" << endl;
}

Вывод:

{8,4,5,3,2}
sum is 22

image desc

Передача по значению против передачи по ссылке

Есть два способа передачи параметра в функцию: передача по значению и передача по ссылке.

Передача по значению

При передаче по значению создается "копия" аргумента и передается в функцию. Вызываемая функция работает с "клоном" и не может изменить исходную копию. В C/C++ фундаментальные типы (например, int и double) передаются по значению.

/* Fundamental types are passed by value into Function */
#include <iostream>
using namespace std;

// Прототипы функций
int inc(int number);

// Тестовый драйвер
int main() {
   int n = 8;
   cout << "Before calling function, n is " << n << endl; // 8
   int result = inc(n);
   cout << "After calling function, n is " << n << endl;  // 8
   cout << "result is " << result << endl;                // 9
}

// Определения функций
// Возвращает number+1
int inc(int number) {
   ++number;  // Изменить параметр, не оказывает влияния на вызывающего
   return number;
}

Вывод:

Before calling function, n is 8
After calling function, n is 8
result is 9

image desc

Передача по ссылке

С другой стороны, при передаче по ссылке передается ссылка на переменную вызывающего. Другими словами, вызываемая функция работает с теми же данными. Если вызываемая функция изменяет параметр, то и исходная копия у вызывающего изменится. В C/C++ массивы передаются по ссылке. C/C++ не позволяет функциям возвращать массив.

/* Function to increment each element of an array */
#include <iostream>
using namespace std;

// Прототипы функций
void inc(int array[], int size);
void print(int array[], int size);

// Тестовый драйвер
int main() {
   int a1[] = {8, 4, 5, 3, 2};

   // До инкремента
   print(a1, 5);   // {8,4,5,3,2}
   // Выполнить инкремент
   inc(a1, 5);     // Массив передается по ссылке (имеет побочный эффект)
   // После инкремента
   print(a1, 5);   // {9,5,6,4,3}
}

// Определения функций

// Инкрементировать каждый элемент заданного массива
void inc(int array[], int size) {  // array[] не является const
   for (int i = 0; i < size; ++i) {
      array[i]++;  // побочный эффект
   }
}

// Вывести содержимое заданного массива
void print(int array[], int size) {
   cout << "{";
   for (int i = 0; i < size; ++i) {
      cout << array[i];
      if (i < size - 1) {
         cout << ",";
      }
   }
   cout << "}" << endl;
}

Вывод:

{8,4,5,3,2}
{9,5,6,4,3}

image desc

Константные параметры функции

Используйте const, когда это возможно, при передаче ссылок, так как это предотвращает случайное изменение параметров и защищает вас от многих ошибок программирования.

В линейном поиске ключ поиска сравнивается с каждым элементом массива линейно. Если найдено совпадение, возвращается индекс совпавшего элемента; в противном случае возвращается -1. Линейный поиск имеет сложность O(n).

/* Search an array for the given key using Linear Search */
#include <iostream>
using namespace std;

int linearSearch(const int a[], int size, int key);

int main() {
   const int SIZE = 8;
   int a1[SIZE] = {8, 4, 5, 3, 2, 9, 4, 1};

   cout << linearSearch(a1, SIZE, 8) << endl;  // 0
   cout << linearSearch(a1, SIZE, 4) << endl;  // 1
   cout << linearSearch(a1, SIZE, 99) << endl; // 8 (not found)
}

// Search the array for the given key
// If found, return array index [0, size-1]; otherwise, return size
int linearSearch(const int a[], int size, int key) {
   for (int i = 0; i < size; ++i) {
      if (a[i] == key) return i;
   }
   // a[0] = 1;
   // It will result in an error,because a[] is const, means read only
   return -1;
}

Output:

0
1
-1

image desc

Передача по ссылке с использованием параметров 'Reference'

Вы можете передавать параметр фундаментального типа по ссылке с помощью параметра ссылки, обозначенного &.

/* Test Pass-by-reference for fundamental-type parameter
   via reference declaration */
#include <iostream>
using namespace std;

int squareByValue (int number);        // Передача по значению
void squareByReference (int &number); // Передача по ссылке

int main() {
   int n1 = 8;
   cout << "Before call, value is " << n1 << endl;  // 8
   cout << squareByValue(n1) << endl;  // без побочного эффекта
   cout << "After call, value is " << n1 << endl;   // 8

   int n2 = 9;
   cout << "Before call, value is " << n2 << endl;  // 9
   squareByReference(n2);  // побочный эффект
   cout << "After call, value is " << n2 << endl;   // 81
}

// Передача параметра по значению - без побочного эффекта
int squareByValue (int number) {
   return number * number;
}

// Передача параметра по ссылке, объявляя его как ссылку (&)
// - с побочным эффектом для вызывающего
void squareByReference (int &number) {
   number = number * number;
}

Output:

Before call, value is 8
64
After call, value is 8
Before call, value is 9
After call, value is 81

image desc

2.8 Математические функции

C++ предоставляет многие часто используемые математические функции в библиотеке <cmath>

sin(x), cos(x), tan(x), asin(x), acos(x), atan(x):
   Принимают тип аргумента и возвращают тип float, double, long double.
sinh(x), cosh(x), tanh(x):
   Гиперболические тригонометрические функции.
pow(x, y), sqrt(x):
   Возведение в степень и квадратный корень.
ceil(x), floor(x):
   Возвращают потолок и пол целого числа с плавающей точкой.
fabs(x), fmod(x, y):
   Абсолютное значение и модуль для чисел с плавающей точкой.
exp(x), log(x), log10(x):
   Функции экспоненты и логарифма.

Заголовочный файл cstdlib (портированный из C's stdlib.h) предоставляет функцию rand(), которая генерирует псевдослучайное целое число в диапазоне от 0 до RAND_MAX (включительно).

/* Test Random Number Generation */
#include <iostream>
#include <cstdlib>  // для rand(), srand()
#include <ctime>    // для time()
using namespace std;

int main() {
   // rand() генерирует случайное число в диапазоне [0, RAND_MAX]
   cout << "RAND_MAX is " << RAND_MAX << endl;   // 32767

   // Генерируем 10 псевдослучайных чисел в диапазоне от 0 до 99
   //   без инициализации генератора.
   // Вы получите ту же последовательность, каждый раз, когда запускаете эту программу
   for (int i = 0; i < 10; ++i) {
      cout << rand() % 100 << " ";   // требуется заголовочный файл <cstdlib>
   }
   cout << endl;

   // Инициализируем генератор псевдослучайных чисел текущим временем
   srand(time(0));   // требуются заголовочные файлы <cstdlib> и <ctime>
   // Генерируем 10 псевдослучайных чисел
   // Вы получите разные последовательности при разных запусках,
   //   потому что текущее время различается
   for (int i = 0; i < 10; ++i) {
      cout << rand() % 100 << " ";   // требуется заголовочный файл <cstdlib>
   }
   cout << endl;
}

Output:

RAND_MAX is 2147483647
83 86 77 15 93 35 86 92 49 21
29 0 83 60 22 55 97 80 68 87

image desc

- name: check if keyword exist
  script: |
    #!/bin/bash
    grep -i 'rand' /home/labex/Code/test.cpp
  error: Oops! We find that you didn't use "rand()" method in "test.cpp".
  timeout: 3

Резюме

Преимущества использования функций следующие:

  1. Разделяй и властвуй: строить программу из простых, мелких частей или компонентов. Модульно структурировать программу на самостоятельные задачи.
  2. Избегать повторения кода: легче копировать и вставлять код, но сложнее поддерживать и синхронизировать все копии.
  3. Повторное использование программного обеспечения: вы можете использовать функции в других программах, упаковав их в библиотечный код.