Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
lesson_8 функции.doc
Скачиваний:
0
Добавлен:
01.07.2025
Размер:
147.46 Кб
Скачать

Передача аргументов по значению.

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

1. В функцию передаются не a и b, а их точные копии.

2. Все изменения происходят с копиями (Digit и Pow), при этом сами a и b остаются неизменными.

3. При выходе из функции временные копии уничтожаются.

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

Кое-что о массивах...

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

int summa (int array[ ], int size){

int res=0;

for (int i = 0; i < size; i++)

res+ = array[i];

return res;

}

Прототипы функций или второй способ объявления.

При втором способе объявления функции необходимо сообщить компилятору о том, что функция существует. Для этого до main предоставляется имя функции, ее аргументы, а также тип возвращаемого значения. Такую конструкцию называют прототипом функции. Когда компилятор встречает прототип функции он точно знает о том, что функция существует в программе после main - и, она должна там быть.

библиотеки

возвращаемое_значение имя_функции(аргументы);

void main(){

тело main;

}

возвращаемое_значение имя_функции(аргументы){

тело функции;

}

Зачем нужны прототипы?

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

Например,

double volume = cube(side);

Во-первых, прототип сообщает компилятору, что функция cube () должна принимать один аргумент типа double. Если программа не предоставит этот аргумент, то прототипирование позволит компилятору перехватить такую ошибку. Во-вторых, когда функция cube () завершает вычисление, она помещает возвращаемое значение в некоторое определенное место — возможно, в регистр центрального процессора, а может быть и в память. Затем вызывающая функция — main () в данном случае — извлекает значение из этого места. Поскольку прототип устанавливает, что cube () имеет тип double, компилятор знает, сколько байт следует извлечь и как их интерпретировать. Без этой информации он может только предполагать, а это то, чем заниматься он не должен.

Но вы все еще можете задаваться вопросом: зачем компилятору нужен прототип? Не может ли он просто заглянуть дальше в файл и увидеть, как определена функция? Одна из проблем такого подхода в том, что он не слишком эффективен. Компилятору пришлось бы приостановить компиляцию main () на то время, пока он прочитает остаток файла. Однако имеется еще более серьезная проблема: функция может и не находиться в том же самом файле. Компилятор C++ позволяет разбивать программу на множество файлов, которые компилируются независимо друг от друга и позднее собираются вместе. В таком случае компилятор может вообще не иметь доступа к коду функции во время компиляции main (). То же самое справедливо и в ситуации, когда функция является частью библиотеки. Единственный способ избежать применения прототипа функции — поместить ее определение перед первым использованием. Это не всегда возможно. Кроме того, стиль программирования на C++ предусматривает размещение функции main () первой, поскольку это в общем случае предоставляет структуру программы в целом.

Синтаксис прототипа

Прототип функции является оператором, поэтому он должен завершаться точкой с запятой. Простейший способ получить прототип — скопировать заголовок функции из ее определения и добавить точку с запятой. Это, собственно, и делает программа из листинга с функцией cube ():

double cube(double x) ; // добавление ; к заголовку для получения прототипа

Однако прототип функции не требует предоставления имен переменных-параметров; достаточно списка типов. Программа из листинга строит прототип cheers (), используя только тип аргумента:

void cheers(int); // в прототипе можно опустить имена параметров

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

Что обеспечивают прототипы

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

• Компилятор корректно обрабатывает возвращаемое значение.

• Компилятор проверяет, указано ли правильное количество аргументов.

• Компилятор проверяет правильность типов аргументов. Если тип не подходит, компилятор преобразует его в правильный, когда это возможно.

C++ автоматически преобразует переданное значение к типу, указанному в прототипе, предполагая, что оба типа арифметические. Например, в листинге присутствуют два несоответствия типа в одном операторе:

cheers(cube(2));

Программа передает целое значение 2 функции cube () , которая ожидает тип double. Компилятор, замечая, что прототип cube () указывает тип аргумента double, преобразует 2 в 2 . О, т.е. в значение типа double. Затем cube () возвращает значение 8,0 типа double, которое должно быть использовано в качестве аргумента cheers (). Опять-таки, компилятор проверяет прототип и замечает, что cheers () требует аргумента int. Он преобразует возвращенное значение в целочисленное 8. В общем случае прототипирование позволяет выполнять автоматическое приведение к ожидаемым типам. (Однако перегрузки функций могут породить неоднозначные ситуации, которые предотвращают выполнение определенных автоматических приведений типов.)

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

Аргументы функций и передача по значению

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

Например, в листинге присутствует следующий вызов функции:

double volume = cube(side);

Здесь side — переменная, которая в примере запуска получает значение 5. Вспомним, что заголовок функции cube () был таким:

double cube(double x)

Когда эта функция вызывается, она создает новую переменную типа double по имени х и инициализирует ее значением 5. Это позволяет изолировать данные в main () от того, что происходит в cube (), т.к. cube () работает с копией side, а не с исходными данными.

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

double cube(double х) ;

int main()

{

double side = 5; //создает переменную side и присваивается значение 5  исходное значение 5

double volume = cube (side);  передает значение 5 функции cube ( )

}

double cube (double x) создает переменную х и присваивает

{ ей переданное значение 5  скопированное значение 5

return x*x*x;

}

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

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

Множественные аргументы

Функция может принимать более одного аргумента. При вызове функции такие аргументы просто отделяются друг от друга запятыми:

n_chars('R', 25) ;

Это передает два аргумента функции n_chars (), определение которой будет приведено чуть позже. Аналогично, при определении функции используется разделенный запятыми список параметров в ее заголовке:

void n_chars(char с, int n) // два параметра

Этот заголовок устанавливает, что функция n_chars () принимает один параметр типа char; и один типа int. Параметры сип инициализируются значениями, переданными функции. Если функция имеет два параметра одного и того же типа, то типы каждого параметра должны указываться по отдельности. Комбинирование объявлений аргументов, как это делается с обычными переменными, не допускается:

void fifi(float a, float b) // объявляет каждую переменную отдельно

void fufu(float a, b) // не допускается

Как и в случае других функций, для получения прототипа нужно просто добавитьсточку с запятой:

void n_chars(char с, int n) ; // прототип, стиль 1

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

Также можно опускать имена переменных в прототипе:

void n_chars(char, int); // прототип, стиль 2

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

double melon_density(double weight, double volume);

Вывод заданных пользователем количества и вида символов в виде горизонтальной строки.

#include <iostream>

using namespace std;

void n_chars(char, int);

void main()

{

setlocale (LC_CTYPE, "russian");

int times;

char ch;

cout << "Введите символ: "; // ввод символа

cin >> ch;

while (ch != 'q') // q для завершения

{

cout << "Ведите число повторов вывода: "; // ввод целого числа

cin >> times;

n_chars(ch, times); // функция с двумя аргументами

cout << "\nВведите другой символ"

" или q для выхода: "; // ввод другого символа или q для завершения

cin >> ch;

}

cout << "Значение количества повторов: " << times << ".\n"; // вывод значения переменной times

cout << "Bye\n";

cin.get();

cin.get();

}

void n_chars(char c, int n) // вывод значения с п раз

{

while (n-- > 0) // продолжение, пока п не достигнет 0

cout << c;

}

Пример запуска:

Введите символ: &

Ведите число повторов вывода: 5

&&&&&

Введите другой символ или q для выхода: @

Ведите число повторов вывода: 8

@@@@@@@@

Введите другой символ или q для выхода: q

Значение количества повторов: 8.

Bye

Для продолжения нажмите любую клавишу . . .

Вспомните, что две функции cin.get () читают все входные символы, включая пробелы и символы новой строки, в то время как cin » пропускает пробелы и символы новой строки. Когда вы отвечаете на приглашение к вводу в программе, то должны нажимать <Enter> в конце каждой строки, генерируя тем самым символ новой строки. Подход cin » ch пропускает эти лишние символы, тогда как оба варианта сin.get() читают символ новой строки, следующий за каждым введенным числом, как очередной символ для отображения. Этот нюанс можно обойти программным путем, но проще использовать cin, как это делается в программе из предыдущего листинга.

Обратите внимание, что программа выполняет подсчет, уменьшая на каждом шаге значение переменной п, которая является формальным параметром из списка аргументов. Этой переменной присваивается значение переменной times в main(). Цикл while уменьшает п до 0, но, как демонстрирует пример выполнения, изменение значения п никак не отражается на значении times. Даже если в main () вместо имени times использовать имя n, значение n в main() не затрагивается изменениями значения n в n_chars().

Вывод «Привет!» в квадрате введенного числа. Используются 2 функции, причем одна вызывает другую.

#include <iostream>

using namespace std;

void name(int); // прототип: нет значения возврата

double size (double x); // прототип: возвращает double

void main()

{

setlocale (LC_CTYPE, "russian");

name(5); // вызов функции

cout << "Введите число повторов вывода: ";

double side;

cin >> side;

double volume = size (side); // вызов функции

cout << "Число повторов в квадрате: " << volume << endl;

name(size(side)); // защита прототипа в действии

cin.get();

cin.get();

}

void name (int n)

{

for (int i = 0; i < n; i++)

cout << "Привет!";

cout << endl;

}

double size (double x)

{

return x * x;

}

Пример запуска:

Привет! Привет! Привет! Привет! Привет!

Введите число повторов вывода: 3

Число повторов в квадрате: 9

Привет! Привет! Привет! Привет! Привет! Привет! Привет! Привет! Привет!

Для продолжения нажмите любую клавишу . . .

Функции

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

Использование функции, имеющей возвращаемое значение.

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

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

х = sqrt(6.25); // возвращает значение 2.5 и присваивает его переменной х

Значение в круглых скобках (в нашем случае — 6.25) — это информация, которая отправляется функции; говорят, что это значение передается функции. Значение, которое отсылается функции подобным образом, называется аргументом или параметром.

Синтаксис вызова функции:

Аргумент – информация,

передаваемая функции

И мя функции

Точка с запятой, отмечает конец оператора

x = sqrt (6,25);

закрывающая скобка

открывающая скобка

возвращаемое функцией

значение присваивается переменной х

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

На заметку!

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

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

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

этого типа используется имя double.

Прототип функции sqrt () выглядит следующим образом:

double sqrt(double); // прототип функции

Завершающая точка с запятой в прототипе идентифицирует его как оператор и поэтому назначает ему статус прототипа, а не заголовка функции. Если точку с запятой опустить, то компилятор будет интерпретировать строку как заголовок функции, и ожидать за ним тела функции, определяющего ее.

Таким образом, если в программе используется функция sqrt (), значит, для нее должен быть предоставлен прототип. Это можно сделать двумя способами:

• ввести самостоятельно прототип функции в файле исходного кода;

• воспользоваться заголовочным файлом cmath (math.h в старых системах), который содержит прототип функции.

Второй вариант лучше, поскольку заголовочный файл всегда предпочтительнее самостоятельного ввода прототипа.

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

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

#include <iostream>

#include <cmath>

using namespace std;

void main()

{

setlocale (LC_CTYPE, "Russian");

double area;

cout << "Введите площадь комнаты: " << endl;

cin >> area;

cin.get();

double side;

side = sqrt(area);

cout << "Длина комнаты = " << side << endl;

cin.get();

}

Язык C++ позволяет объявлять новую переменную в любом месте программы, поэтому в sqrt.cpp переменная side объявлена непосредственно перед ее использованием. Кроме того, C++ позволяет присваивать значение переменной при ее создании, поэтому допускается следующая форма записи:

double side = sqrt(area);

Такая форма называется инициализацией.

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

double pow(double, double); // прототип функции с двумя аргументами

Если, например, необходимо вычислить 58 (5 в степени 8), можно воспользоваться этой функцией, как показано ниже:

answer = pow (5.0, 8.0); // вызов функции со списком аргументов

Есть функции, которые вообще не принимают аргументов. Например, одна из библиотек С (связанная с заголовочным файлом cstdlib или stdlib.h) содержит функцию rand(), не принимающую аргументов и возвращающую случайное целое число. Ее прототип выглядит так:

int rand(void); // прототип функции, не принимающей аргументов

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

myGuess = rand(); // вызов функции без аргументов

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

Задача:

Пример программы преобразования английских стоунов в американские фунты. В Великобритании используются весы, мерой которых, являются стоуны (1 стоун равен 14 фунтам, или приблизительно 6,34 кг), а в США применяются фунты или килограммы.

#include <iostream>

using namespace std;

int stountolb(int);

void main()

{

setlocale (LC_CTYPE, "Russian");

cout << "Введите вес в стоунах: ";

int stone;

cin >> stone;

cin.get();

int pound = stountolb (stone);

cout << stone << " стоунов = " << pound << " фунтов" << endl;

cin.get();

}

int stountolb (int sts)

{

return 14*sts;

}

В функции main () программа использует объект сіп для ввода значения целочисленной переменной stone. Это значение передается функции stonetolb() в качестве аргумента и присваивается переменной sts в этой функции. Функция stonetolb () использует ключевое слово return для возврата значения 14*sts в функцию main (). Это пример того, что после оператора return не обязательно должно следовать простое число. В этом случае с помощью выражения вы избавляетесь от необходимости создавать новую переменную, которой должно быть присвоено значение, прежде чем оно будет возвращено. Программа вычисляет значение этого выражения (в нашем примере — 210) и возвращает его.

Область видимости.

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

int a=5;

if(a==5){

int b=3;

}

cout<<b; // ошибка! b не существует

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

Согласно правилам области видимости - переменные делятся на два вида – локальные и глобальные.

Локальные переменные создаются внутри какого – нибудь отрезка кода, что это значит для программы, мы уже знаем.

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

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

int a=23; // глобальная a

void main(){

int a=7;// локальная a

cout<<a; // 7, используется локальная

}

Соседние файлы в предмете [НЕСОРТИРОВАННОЕ]