functions2, : Top


31 Передача параметров функции

Локальные и глобальные переменные

Внутри функции могут быть объявлены переменные, как, например, переменные p и i в функции power. Такие переменные называются локальными и они определены только внутри этой функции. Переменные, определенные вне тела любой функции (ранее мы такие переменные вообще не рассматривали) называются глобальными и они определены всюду начиная с места определения и до конца файла. Глобальными переменными можно пользоваться во всех функциях, но с современной точки зрения использовать глобальные переменные рекомендуется как можно реже.

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

В различных функциях можно определять переменные с одним и тем же именем. Это будут различные переменные. Также можно объявлять в функциях переменные, имена которых совпадают с именами глобальных переменных. Тогда будет создана новая локальная переменная, а изменить значение глобальной переменной с таким именем будет невозможно. Пример:

     #include<iostream>
     using namespace std;
     int i;                // i - глобальная переменная
     void f1() {
         int i;            // Определена локальная переменная i
         i=5;              // Изменяется значение локальной переменной
         cout<<i<<endl;    // Будет напечатано 5
     }
     void f2() {
         i=3;              // Изменяется значение глобальной переменной
     }
     int main(){
         i=1;              // Изменяется значение глобальной переменной
         f1();             // f1() не меняет значение глобальной переменной
         cout<<i<<endl;    // Будет напечатано 1
         f2();             // f2() меняет значение глобальной переменной
         cout<<i<<endl;    // Будет напечатано 3
         return 0;
     }

Вопрос: как будет работать программа, если добавить в функцию main определение переменной i, как локальной? А если при этом убрать объявление глобальной переменной i?

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

Переменные, в которых сохраняются параметры, передаваемые функции, также являются локальными для этой функции. Эти переменные создаются при вызове функции и в них копируются значения, передаваемые функции в качестве параметров. Эти переменные можно изменять, но все изменения этих переменных будут "забыты" после выхода из функции. Рассмотрим это на примере следующей функции, "меняющей" значения двух переданных ей переменных:

     #include<iostream>
     using namespace std;
     void swap(int a, int b)
     {
         int t;
         t=b;
         b=a;
         a=t;
     }
     int main()
     {
         int p=3,q=5;
         swap(p,q);
         cout<<p<<" "<<q<<endl;
         return 0;
     }

При вызове функции swap создаются новые переменные a и b, им присваиваются значения 3 и 5. Эти переменные никак не связаны с переменными p и q и их изменение не изменяет значения p и q. Такой способ передачи параметров называется передачей параметров по значению.

Чтобы функция могла изменять значения переменных, объявленных в других функциях, необходимо указать, что передаваемый параметр является не просто константной величиной, а переменной, необходимо передавать значения по ссылке. Для этого функцию swap следовало бы объявить следующим образом:

     void swap(int & a, int & b)

Амперсанды перед именем переменной означают, что эта переменная является не локальной переменной, а ссылкой на переменную, указанную в качестве параметра при вызове функции. Теперь при вызове swap(p,q) переменные a и b являются синонимами для переменных p и q, и изменение их значений влечет изменение значений p и q. А вот вызывать функцию в виде swap(3,5) уже нельзя, поскольку 3 и 5 — это константы, и сделать переменные синонимами констант нельзя.

Однако в языке C (не C++) вообще не было такого понятия, как передача параметров по ссылке. Для того, чтобы реализовать функцию, аналогичную swap в рассмотренном примере, необходимо было передавать адреса переменных p и q, а сама функция при этом должна быть объявлена, как

     void swap(int * a, int * b)

Поскольку в этом случае функция swap знает физические адреса в оперативной памяти переменных p и q, то разыменовав эти адреса функция swap сможет изменить значения самих переменных p и q.

Теперь вспомним, что в C++ массивы и указатели — это почти одно и то же. А поскольку передача массива по значению, то есть создание копии всего массива является трудоемкой операцией (особенно для больших массивов), то вместо массива всегда передается указатель на его начало. То есть два объявления функции void f(int A[10]) и void f(int * A) абсолютно идентичны. В любом случае функция f получит указатель на начало массива, а, значит, будет способна изменять значения его элементов.

Упражнения

Функции — удобный способ структурирования программы. Существует мнение, что если фрагмент программы реализует некоторую законченную алгоритмическую идею и состоит более, чем из 10 строк, то его обязательно надо оформить в виде отдельной функции.

Упражнения к этому листку объединены темой генерирования комбинаторных объектов (перестановок, подмножеств и т.д.) Такие объекты следует хранить в массивах, например, перестановка чисел от 1 до n — это массив типа int[n], заполненный числами от 1 до n.

В программе должны быть следующие функции (p — массив для хранения комбинаторного объекта, n — число элементов в нем):

     int * Init (int n);          // Создание и инициализация массива
     bool  Next (int * p, int n); // Построение следующего комбинаторного объекта
     void  Print(int * p, int n); // Печать комбинаторного объекта
     void  Done (int * p);        // Освобождение памяти

а функция main должна иметь следующую структуру:

     cin>>n;
     p=Init(n);
     Print(p,n);
     while( Next(p,n) )
         Print(p,n);
     Done(p);

Кроме того, программа должны работать за оптимальное время.

  1. По данным числам n и k выведите на экран все строки длины n, состоящие из чисел 1, ..., k в лексикографическом (алфавитном) порядке.
  2. По данному n выведите на экран все подмножества множества {1, ...,n}.
  3. По данным n и k выведите на экран все двоичные строки длины n, содержащие ровно k единиц в лексикографическом порядке.
  4. По данному n напечатайте все перестановки чисел от 1 до n в лексикографическом порядке.
  5. По данному n напечатайте все способы размещения из n элементов k элементов (количество способов выбрать из чисел от 1 до n ровно k чисел с учетом порядка выбора).
  6. По данному числу n напечатайте всевозможные его представления в виде суммы натуральных слагаемых. Представления, различающиеся порядком слагаемых, считать за одно.
  7. По данному слову (или последовательности цифр) напечатайте всевозможные перестановки его букв. Учтите, что буквы исходного слова могут совпадать.