Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

Лабы 1 курс 2 семестр / ЛР 6 Информатика 2 сем 2020

.pdf
Скачиваний:
10
Добавлен:
15.01.2021
Размер:
674.9 Кб
Скачать

7Лабораторная работа №6. Функции и ссылки

7.1Цели и задачи работы:

Цель данной лабораторной работы — научиться создавать свои функции.

Задача данной лабораторной работы — выполнить выданные типовые задания, создав и протестировав свои функции.

7.2Теоретическая часть

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

Для того, что бы использовать функции, реализованные другими программистами, нужно включить в программу с помощью директивы #include заголовочные файлы с описаниями этих функций. Описание функции, включающее имя функции, перечень еѐ аргументов с их типами и тип возвращаемого значения, называется сигнатурой функции.

Некоторые библиотечные функции нам использовать уже приходилось, например, функции printf() и scanf(), описанные в заголовочном файле stdio.h, или функцию _getch(), описанную в заголовочном файле conio.h. Так же мы использовали конструкции std::cin>> и std::cout<<, описания которых находятся в заголовочном файле iostream.

Когда при компиляции программы компилятор «встречает» #include <имя_файла>, например, #include <iostream>, он копирует в это место в нашу прогамму всѐ содержимое указанного файла (в данном случае файла iostream). Если имя файла заключено в угловые скобки (< >), компилятор будет его искать среди остальных стандартных библиотек, а если имя файла заключено в кавычки (" "), компилятор сперва будет его искать в папке с текущим проектом.

Как правило, в заголовочных файлах записываются только объявления, без определений. Например, в файле iostream находятся только объявления конструкций std::cin>> и std::cout<<, а реализуются они в стандартной библиотеке С++, которая автоматически подключается к проекту на этапе линковки (связывания).

Если бы заголовочного файла iostream не было бы, каждый раз, при использовании cout, приходилось бы вручную копировать все предварительные объявления, связанные с cout в верхнюю часть вашего файла с программой.

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

107

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

int m = max(5, 7);

unsigned int a = nod(b, c);

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

В качестве примера библиотечных функций можно упомянуть математические функции, описанные в заголовочном файле <cmath> (или math.h при написании программы на «чистом» Си). Среди них есть такие функции, как:

Имя функции

Описание функции

 

 

cos

Вычисление косинуса угла, переведенного в радианы

sin

Вычисление синуса угла, переведенного в радианы.

 

 

tan

Вычисление тангенса угла, переведенного в радианы.

 

 

exp

Вычисление экспоненты

 

 

log

Натуральный логарифм

 

 

log10

Десятичный логарифм

 

 

modf

Разделение вещественного значения на дробную и целую части

 

 

pow

Возведение числа в степень

 

 

sqrt

Корень квадратный

 

 

fabs

Вычислить модуль значения

 

 

ceil

Округление до наименьшего целого значения

 

 

floor

Округление до наибольшего целого значения

 

 

Примеры использования:

double x = -5.7;

double y = pow(x, 3); // Возведение значения x в степень 3.2

double z = sqrt(fabs(x)); // Нахождение квадратного корня от модуля значения x

Рассмотрим ещѐ одну полезную функцию rand() для генерации псевдослучайных чисел из файла <cstdlib> (или stdlib.h при написании программы на «чистом» Си). Эта функция возвращает псевдослучайное целое число в диапазоне от нуля до RAND_MAX. RAND_MAX – константа, так же определѐнная в файле stdlib.h и означающая максимальное число, которое может быть возвращено функцией rand()

unsigned

int r

=

rand();

//

Получение

случайного числа

unsigned

int p

=

rand();

//

Получение

другого числа

 

 

 

 

 

108

Нужно обратить внимание, что при каждом запуске программы последовательность сгенерированных чисел будет одинаковая. Это было сделано специально для удобства отладки программы. Для получения другой последовательности чисел нужно перед началом генерации проинициализировать генератор случайных чисел начальным значением при помощи функции srand() из файла cstdlib (stdlib.h). При указании различных значений аргумента функции srand() мы получим различные последовательности чисел.

Но каждый раз вручную прописывать значеня аргумента для srand() и перекомпилировать программу неудобно. Начальные значения для генератора случайных чисел лучше определять автоматически. Это сделать можно, например, при помощи функции time(), которая определена в заголовочном файле <ctime> (или time.h при написании программы на «чистом» Си). Функция time () считывает и возвращает текущие показания системных часов компьютера (показание системных часов – это время в секундах, истекшее с 0 часов 1 января 1970 года). Естественно, при каждом запуске программы time() будет возвращать другое значение.

//Задаём начальное значение для "генератора"

//псевдослучайных чисел (текущее время в секундах) srand(time(NULL));

unsigned int r = rand(); // Получение нового числа

Теперь рассмотрим создание собственных (пользовательских функций). Одну такую функцию (правда, заданной сигнатуры) мы уже несколько раз создавали – это функция main. Обычно в каждой программе на языках «C/C++» присутствует функция с названием "main", с которой начинается выполнение этой программы. Некоторые компиляторы позволяют задать произвольное имя для начальной функции, но делать этого не рекомендуется.

Сигнатура функции main в общем случае выглядит следующим

образом: type main (int argc, char *argv[], other_parameters) Имена

argc и argv могут быть произвольными.

type – тип значения, возвращаемого функцией, допускается указывать ключевое слово void, если не предполагается, что функция main() будет возвращать какие-либо значения (на самом деле в этом случае компилятор будет считать, что функция main() всегда возвращает 0).

argc - количество аргументов, переданных программе из окружения (например, операционной системы), в котором запустили программу.

argv - указатель на первый элемент массива указателей на многобайтовые строки с завершающим нулем, аргументы, переданных программе из окружения, в котором запустили программу (с argv[0] по argv[argc-1]). Гарантируется, что последний элемент массива argv[argc] — нулевой указатель (0).

109

other_parameters - различные реализации допускают дополнительные формы функции main, но при этом тип возвращаемого значения всегда int. Часто третьим аргументом передают массив char*[] указателей на значения переменных окружения.

При выполнении заданий лабораторных работ мы в основном записывали программный код в теле функции main() (внутри фигурных скобок), т.е. реализовывали эту функцию.

Для создания собственной функции нужно указать еѐ имя, список аргументов функции и тип возвращаемого значения следующим образом:

ТипВЗ Функ(ТипА1 Арг1, ТипА2 Арг2, …)

{

return …;

}

где ТипВЗ – тип возвращаемого значения. Если функция не будет возвращать никаких значений, здесь указывается «void».

Функ – имя функции.

ТипА1 – тип аргумента 1, Арг1 - имя аргумента 1. ТипА2 – тип аргумента 2, Арг2 - имя аргумента 2 и т.д. Имена аргументов не могут повторяться.

return – ключевое слово, за которым следует выражение, значение которого вернѐт функция. Если в качестве типа возвращаемого значения указан «void», return может отсутствовать или использоваться без значения, исключительно для завершения работы функции.

Примеры:

//Функция для выбора большей из двух целых величин int max(int A, int B)

{

return A > B ? A : B;

}

//Функция для нахождения факториала числа

unsigned int f(unsigned int N)

{

unsigned int result = 1;

for(unsigned int i = 2; i <= N; i++)

{

result *= i;

110

}

return result;

}

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

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

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

Например, можно определить функции с одинаковыми именами:

void myfunc(char a)

{

std::cout<<"Simvol \""<< a <<"\". Eto kod ASCII: " << (int)a <<".\n";

}

void myfunc(int a)

{

std::cout<<"Chislo "<< a <<".";

if(a >= -128 && a < 128)

{

111

std::cout<< " Eto kod ASCII simvola \"" << (char)a <<"\".";

}

std::cout<<"\n";

}

Тогда в тексте программы можно будет записать:

...

void main()

{

...

 

char cv = '1';

// На экран будет выведено

myfunc(cv);

// Simvol "1". Eto kod ASCII: 49.

int iv = 1;

// На экран будет выведено

myfunc(iv);

// Chislo 1. Eto kod ASCII simvola "☺".

...

 

}

 

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

//Функция для вычисления длины вектора,

//выходящего из точки с координатами (0, 0, 0)

//x, y, и z – координаты конца вектора

float len(float x = 0.0, float y = 0.0, float z = 0.0)

{

//sqrt() – функция для вычисления квадратного корня,

//описанная в заголовочном файле <cmath> или <math.h> return sqrt(x * x + y * y + z * z);

}

float a = len(3, 4, 5); // Длина вектора (3, 4, 5) float b = len(3, 4); // Длина вектора (3, 4, 0) float c = len(3); // Длина вектора (3, 0, 0)

112

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

Стоит обратить внимание ещѐ на одну особенность описания функций. Рассмотрим следующий пример.

void swap(int a, int b) // аргументы передаются по значению

{

int tmp = a; a = b;

b = tmp

}

...

int x = 5; int y = 10;

swap(x, y); // В функцию передаются значения переменных

На первый взгляд, после вызова функции swap() переменные x и y должны обменяться значениями. Но на самом деле этого не произойдѐт. Дело в том, что «вовнутрь» функции передаются не сами переменные x и y, а их копии. Изменение значений этих копий на переменные вне функции никак не влияет.

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

При объявлении ссылки перед еѐ именем ставится символ амперсанда &, сама же ссылка должна быть проинициализирована именем переменной, на которую она ссылается.

Ссылка создаѐтся и используется следующим образом:

int x = 5;

// Переменная x

int &y = x;

// Переменная-ссылка y на переменную x

int z = y;

// Записываем в переменную z

 

// значение переменной x

y = 7;

// Записываем в переменную x число 7

113

С использованием указателей это выглядело бы так:

int x;

// Переменная x

int *y = &x;

// Переменная-указатель y на переменную x

int z = *y;

// Записываем в переменную z

// значение переменной, на которую указывает y

*y = 7; // Записываем в переменную, на которую указывает y, // число 7

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

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

сними самими.

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

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

// Функция меняет значения целочисленных аргументов

void swap(int &a, int &b) // аргументы передаются по ссылке

{

int tmp = a; // tmp – обычная переменная, не ссылка a = b;

b = tmp

}

...

int x = 5; int y = 10;

swap(x, y); // В функцию передаются ссылки на x и y

//Теперь переменная x имеет значение 10,

//а переменная y – значение 5

А так бы выглядела реализация через указатели

void swap(int *a, int *b) // передаются указатели на x и y

{

int tmp = *a; *a = *b;

114

*b = tmp

}

...

int x = 5; int y = 10;

swap(&x, &y); // В функцию передаются адреса x и y

//Теперь переменная x имеет значение 10,

//а переменная y – значение 5

Иещѐ следует рассказать про использование ключевого слова static применительно к локальным переменным, объявленным внутри функции. Статическая переменная (или ещѐ «переменная со статической продолжительностью») сохраняет свое значение даже после выхода из блока (завершения выполнения функции), в котором она определена. Она создается (и инициализируется) только один раз (при первом вызове функции), а затем сохраняется на протяжении выполнения всей программы.

//Пример использования статической локальной переменной void test()

{

static unsigned int primer = 0; primer++;

printf("This function call %d times", primer);

//Правильным будет ещё добавить проверку,

//что бы не было переполнения

}

При каждом вызове функции test() значение переменной primer будет увеличиваться на 1.

.

7.3Примеры решения задач

Задача А Ввести с клавиатуры натуральное число N и вещественное число х.

N

2i

Вычислить S

x

 

. Для нахождения степени и факториала написать

 

 

i 1 i!

собственные функци. Результат вывести на экран.

Решение

115

Составим блок-схемы функций.

Возведение числа в степень:

Начало

Вещественное число osnovanie, Целое неотрицательное число pokazatel

Передача значений osnovanie, pokazatel

Вещественное число result = 1 Целое неотрицательное число i = 0

да

нет

 

i < pokazatel

result *= osnovanie

Возвращение

result

 

i++

Конец

Нахождение факториала числа:

Начало

Целое неотрицательное число chislo

Передача значения chislo

Целое неотрицательное число result = 1 Целое неотрицательное число i = 2

да

нет

 

i <= chislo

result *= i

Возвращение

result

 

i++

Конец

 

116