ВУЗ: Не указан

Категория: Не указан

Дисциплина: Не указана

Добавлен: 30.03.2021

Просмотров: 215

Скачиваний: 2

ВНИМАНИЕ! Если данный файл нарушает Ваши авторские права, то обязательно сообщите нам.

При выполнении этой инструкции присваивания значение переменной val становится равным числу 99,1. И вот почему: поскольку функция f() возвращает ссылку на переменную val, эта ссылка и является приемником инструкции присваивания. Таким образом, значение 99,1 присваивается переменной val косвенно, через ссылку на нее, которую возвращает функция f().

Наконец, при выполнении строки


cout << f() << '\n'; // Отображаем новое значение val.

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


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


#include <iostream>

using namespace std;

double &change_it(int i);

// Функция возвращает ссылку.

double vals[] = {1.1, 2.2, 3.3, 4.4, 5.5};

int main()

{

 int i;

 cout << "Вот исходные значения: ";

 for(i=0; i<5; i++)

  cout << vals[i] << ' ';

 cout << '\n'

 change_it(1) = 5298.23; // Изменяем 2-й элемент.

 change_it(3) = -98.8; // Изменяем 4-й элемент.

 cout << "Вот измененные значения: ";

 for(i=0; i<5; i++)

  cout << vals[i] << ' ';

 cout << '\n';

 return 0;

}

double &change_it(int i)

{

 return vals[i]; // Возвращаем ссылку на i-й элемент.

}


Эта программа изменяет значения второго и четвертого элементов массива vals. Результаты ее выполнения таковы.


Вот исходные значения: 1.1 2.2 3.3 4.4 5.5


Вот измененные значения: 1.1 5298.23 3.3 -98.8 5.5


Давайте разберемся, как они были получены. Функция change_it() объявлена как возвращающая ссылку на значение типа double. Говоря более конкретно, она возвращает ссылку на элемент массива vals, который задан ей в качестве параметра i. Таким образом, при выполнении следующей инструкции функции main()


change_it(1) = 5298.23; // Изменяем 2-й элемент.

функция change_it() возвращает ссылку на элемент vals[1]. Через эту ссылку элементу vals[1] теперь присваивается значение 5298,23. Аналогичные события происходят при выполнении и этой инструкции.


change_it(3) = -98.8; // Изменяем 4-й элемент.

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


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


// Здесь ошибка: нельзя возвращать ссылку

// на локальную переменную.

int &f()

{

 int i=10;

 return i;

}


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


Создание ограниченного массива

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


Один из способов создания ограниченного массива иллюстрируется в следующей программе.


// Простой способ организации безопасного массива.

#include <iostream>

using namespace std;

int &put(int i); // Помещаем значение в массив.

int get(int i); // Считываем значение из массива.

int vals[10];

int error = -1;

int main()

{

 put(0) = 10; // Помещаем значения в массив

 put(1) = 20;

 put(9) = 30;

 cout << get(0) << ' ';

 cout << get(1) << ' ';

 cout << get(9) << ' ';

 // А теперь специально генерируем ошибку.

 put(12) =1; // Индекс за пределами границ массива.

return 0;

}


// Функция занесения значения в массив.

int &put(int i)

{

 if(i>=0 && i<10)

   return vals[i]; // Возвращаем ссылку на i-й элемент.

  else {

   cout << "Ошибка нарушения границ!\n";

   return error; // Возвращаем ссылку на error.

  }

}


// Функция считывания значения из массива.

int get(int i)

{

 if(i>=0 && i<10)

   return vals[i]; // Возвращаем значение i-го элемента.

  else {

   cout << "Ошибка нарушения границ!\n";

   return error; // Возвращаем значение переменной error.

  }

}


Результат, полученный при выполнении этой программы, выглядит так.


10 20 30 Ошибка нарушения границ!


В этой программе создается безопасный массив, предназначенный для хранения десяти целочисленных значений. Чтобы поместить в него значение, используйте функцию put(), а чтобы прочитать нужный элемент массива, вызовите функцию get(). При использовании обеих функций индекс интересующего вас элемента задается в виде аргумента. Как видно из текста программы, функции get() и put() не допускают выход за границы области памяти, выделенной для массива. Обратите внимание на то, что функция put() возвращает ссылку на заданный элемент и поэтому законно используется в левой части инструкции присваивания.


Независимые ссылки


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


Независимая ссылкаэто просто еще одно название для переменных некоторого иного типа.


Независимая ссылка должна указывать на некоторый объект. Следовательно, независимая ссылка должна быть инициализирована при ее объявлении. В общем случае это означает, что ей будет присвоен адрес некоторой ранее объявленной переменной. После этого имя такой ссылочной переменной можно применять везде, где может быть использована переменная, на которую она ссылается. И в самом деле, между ссылкой и переменной, на которую она ссылается, практически нет никакой разницы. Рассмотрим, например, следующую программу.



#include <iostream>

using namespace std;

int main()

{

 int j, k;

 int &i = j; // независимая ссылка

 j = 10;

 cout << j << " " << i; // Выводится: 10 10

 k = 121;

 i = k; // Копирует в переменную j значение переменной k, а не адрес переменной k.

 cout << "\n" << j; // Выводится: 121

 return 0;

}


При выполнении эта программа выводит следующие результаты.


10 10


121


Адрес, который содержит ссылочная переменная, фиксирован и его нельзя изменить. Следовательно, при выполнении инструкции i = k в переменную j (адресуемую ссылкой i) копируется значение переменной k, а не ее адрес.

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


Ограничения при использовании ссылок

На применение ссылочных переменных накладывается ряд следующих ограничений.

Нельзя ссылаться на ссылочную переменную.

Нельзя создавать массивы ссылок.

Нельзя создавать указатель на ссылку, т.е. нельзя к ссылке применять оператор "&"


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


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

В этом разделе мы узнаем об одной из самых удивительных возможностей языка C++ — перегрузке функций. В C++ несколько функций могут иметь одинаковые имена, но при условии, что их параметры будут различными. Такую ситуацию называют перегрузкой функций (function overloading), а функции, которые в ней задействованы, — перегруженными (overloaded). Перегрузка функций — один из способов реализации полиморфизма в C++.


Рассмотрим простой пример перегрузки функций.


// "Трехкратная" перегрузка функции f().

#include <iostream>

using namespace std;

void f(int i); // один целочисленный параметр

void f(int i, int j); // два целочисленных параметра

void f(double k); // один параметр типа double

int main()

{

 f (10); // вызов функции f(int)

 f(10, 20); // вызов функции f (int, int)

 f(12.23); // вызов функции f(double)

 return 0;

}

void f(int i)

{

 cout << "В функции f(int), i равно " << i << '\n';

}

void f(int i, int j)

{

 cout << "В функции f(int, int), i равно " << i;

 cout << ", j равно " << j << '\n';

}

void f(double k)

{

 cout << "В функции f(double), k равно " << k << ' \n';

}


При выполнении эта программа генерирует следующие результаты.


В функции f(int), i равно 10


В функции f(int, int), i равно 10, j равно 20


В функции f(double), к равно 12.23


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

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


Чтобы лучше понять выигрыш от перегрузки функций, рассмотрим три функции из стандартной библиотеки: abs(), labs() и fabs(). Они были впервые определены в языке С, а затем ради совместимости включены в C++. Функция abs() возвращает абсолютное значение (модуль) целого числа, функция labs() возвращает модуль длинного целочисленного значения (типа long), a fabs() — модуль значения с плавающей точкой (типа double). Поскольку язык С не поддерживает перегрузку функций, каждая функция должна иметь собственное имя, несмотря на то, что все три функции выполняют, по сути, одно и то же действие. Это делает ситуацию сложнее, чем она есть на самом деле. Другими словами, при одних и тех же действиях программисту необходимо помнить имена всех трех (в данном случае) функций вместо одного. Но в C++, как показано в следующем примере, можно использовать только одно имя для всех трех функций.


// Создание функций myabs() — перегруженной версии функции abs().

#include <iostream>

using namespace std;

// Функция myabs() перегружается тремя способами.

int myabs(int i);

double myabs(double d);

long myabs(long l);


int main()

{

 cout << myabs(-10) << "\n";

 cout << myabs(-11.0) << "\n";

 cout << myabs(-9L) << "\n";

 return 0;

}


int myabs(int i)

{

 cout << "Использование int-функции myabs(): ";

 if(i<0) return -i;

 else return i;

}


double myabs(double d)

{

 cout << "Использование double-функции myabs(): ";

 if(d<0.0) return -d;

 else return d;

}


long myabs(long l)

{

 cout << "Использование long-функции myabs(): ";

 if(1<0) return -1;

 else return 1;

}


Результаты выполнения этой программы таковы.


Использование int-функции myabs(): 10


Использование double-функции myabs(): 11


Использование long-функции myabs(): 9


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

Каждая версия перегруженной функции может выполнять любые действия. Другими словами, не существует правила, которое бы обязывало программиста связывать перегруженные функции общими действиями. Однако с точки зрения стилистики перегрузка функций все-таки подразумевает определенное "родство" его версий. Таким образом, несмотря на то, что одно и то же имя можно использовать для перегрузки не связанных общими действиями функций, этого делать не стоит. Например, в принципе можно использовать имя sqr для создания функции, которая возвращает квадрат целого числа, и функции, которая возвращает значение квадратного корня из вещественного числа (типа double). Но, поскольку эти операции фундаментально различны, применение механизма перегрузки методов в этом случае сводит на нет его первоначальную цель. (Такой стиль программирования, наверное, подошел бы лишь для того, чтобы ввести в заблуждение конкурента.) На практике перегружать имеет смысл только тесно связанные операции.



Анахронизм в виде ключевого слова overload


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

Общая форма использования ключевого слова overload такова.


overload func_name;


Здесь элемент func_name представляет собой имя перегружаемой функции. Эта инструкция должна предшествовать объявлениям перегруженных функций. (В общем случае оно встречается в начале программы.) Например, если функция Counter() является перегруженной, то в программу могла быть включена такая строка.


overload Counter;


Если вы встретите overload-объявления при работе со старыми программами, их можно просто удалить: они больше не нужны. Поскольку ключевое слово overload — анахронизм, его не следует использовать в новых С++-программах. На самом деле большинство компиляторов его попросту не воспримет.


Аргументы, передаваемые функции по умолчанию 


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

Задание аргументов, передаваемых функции по умолчанию, синтаксически аналогично инициализации переменных. Рассмотрим следующий пример, в котором объявляется функция myfunc(), принимающая один аргумент типа double с действующим по умолчанию значением 0.0 и один символьный аргумент с действующим по умолчанию значением 'Х'.


void myfunc(double num = 0.0, char ch = 'Х')

{

}


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


myfunc(198.234, 'A'); // Передаем явно заданные значения.


myfunc(10.1); // Передаем для параметра num значение 10.1, а для параметра ch позволяем применить  аргумент, задаваемый по умолчанию ('Х').


myfunc(); // Для обоих параметров num и ch позволяем применить аргументы, задаваемые по умолчанию.


При первом вызове параметру num передается значение 198.234, а параметру ch — символ 'А'. Во время второго вызова параметру num передается значение 10.1, а параметр ch по умолчанию устанавливается равным символу 'Х'. Наконец, в результате третьего вызова как параметр num, так и параметр ch по умолчанию устанавливаются равными значениям, заданным в объявлении функции.

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