Программирование на С/С++. Функции

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

Рубрика Программирование, компьютеры и кибернетика
Вид реферат
Язык русский
Дата добавления 13.09.2015
Размер файла 59,1 K

Отправить свою хорошую работу в базу знаний просто. Используйте форму, расположенную ниже

Студенты, аспиранты, молодые ученые, использующие базу знаний в своей учебе и работе, будут вам очень благодарны.

Размещено на http://www.allbest.ru/

Информатика:

Программирование на С/С++. Функции

1. Основные понятия, общее описание функции

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

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

Формальное описание функции имеет вид:

«тип» «имя_функции»(«список_формальных_параметров»)

{«тело_функции»}

Приведем расшифровку элементов, входящих в формальное описание:

«тип» - это тип возвращаемого функцией значения. Другими словами, это тип результата некоторых действий, производящихся внутри функции. Отметим, что могут быть функции, которые не возвращают никакого значения. В таком случае, в качестве типа указывается зарезервированное слово void;

«имя_функции» - это название функции. Именно по имени функции происходит обращение к ней;

«список_формальных_параметров» - это набор идентификаторов, значения которых необходимо задать в момент обращения к функции. Элементы списка параметров отделяются друг от друга символом «запятая». Для каждого такого идентификатора необходимо задать тип. Даже в том случае, если необходимо задать несколько параметров одного типа, обязательно для каждого из них указать тип. Отметим, что могут быть функции, у которых нет параметров. В таком случае, в описании функции обязательно останутся круглые скобки, а внутри них либо ничего, либо зарезервированное слово void. Любая из этих записей говорит о том, что данная функция не имеет параметров;

«тело_функции» - это последовательность операторов, т.е. как раз те действия блока алгоритма, которые выделили в отдельную функцию. Важным оператором тела функции является оператор return «выражение» - оператор возврата в точку вызова. По этому оператору функция прекращает свою работу и возвращает значение выражения, указанного после зарезервированного слова return. Заметим, что если функция не возвращает никакого значения (в качестве типа возвращаемого значении указано слово void), то элемент «выражение» для оператора return становится лишним и его можно не писать. Отметим тот факт, что даже если функция не выполняет никаких действий, фигурные скобки (ограничивающие тело функции) обязательны.

Приведем несколько примеров описания функций.

Пример 1

double f1(double x)

{x=x*x;

return x;}

Здесь приведена запись функции с именем f1 одного вещественного параметра x, возвращающей вещественное значение. Это значение является квадратом значения параметра x.

Пример 2

double f2(double x)

{if(x<0)return (-x);

return x;}

Здесь приведена запись функции с именем f2 одного вещественного параметра x, возвращающей вещественное значение. Это значение равно модулю значения параметра x. Заметим, что в данном случае в операторе условия отсутствует слово else. Возникает вопрос: а будет ли функция возвращать правильное значение, в случае если значение параметра отрицательно? Ответ на этот вопрос - да, будет. Ведь если условие истинно, то будет выполнен оператор return (-x), который завершит выполнение функции (и не важно, сколько еще операторов записано после него), а если условие ложно, то выполнение программы продолжится оператором, который записан после условного. Таким образом, функция вернет значение модуля параметра x.

Пример 3

void f3()

{cout<<”Пример”;}

Функция с именем f3 без параметров и не возвращающая значений. Эта функция выводит на экран слово «Пример».

Пример 4

void f4(char c)

{cout<<c;}

Функция с именем f4 от одной символьной переменной c, не возвращающая значений. Эта функция выводит на экран значение параметра c, то есть символ, который подали на вход функции.

Пример 5

void f5(char c)

{if(c==' ')return;

cout<<c;}

В последнем примере приведена модификация функции f4, которая выводит на экран любой символ, за исключением пробела. Заметим, что для оператора return не указано выражение, значение которого необходимо вернуть. Но поскольку функция не должна возвращать значение, то выражение для оператора return является лишним. Таким образом, если значение параметра c равно пробелу, то функция завершает свою работу, не возвращая значение.

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

Пример 6

f3();

f4(`ф');

f2(-2);

Здесь в первой строке происходит вызов функции f3, и хотя функция не имеет ни одного параметра, при ее вызове необходимо указывать круглые скобки. Во второй строке происходит вызов функции f4. Поскольку у этой функции должен быть один параметр символьного типа, его указали в явном виде (константой `ф'). В третьей строке происходит вызов функции f2 здесь значения ее вещественного параметра также как и в предыдущем случае указали в явном виде (константой 2). Но при этом, функция f2 возвращает значение. В связи с этим возникает вопрос: куда же девается это возвращенное значение? Ответ на этот вопрос - никуда, оно «повисает в воздухе» и если его не использовать (присвоив переменной или в качестве элемента некоторого выражения), то это значение потеряется. Однако, следует заметить, что такой вариант вызова функции не запрещен, а это означает, что программисту необходимо самому следить за тем как использовать возвращаемые значения функций.

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

Пример 7

double x=-5;

f1(f2(x));

В данном участке описана вещественная переменная x, ей присвоено значение -5 после этого происходит вызов функции f1, у которой в качестве параметра указано значение, возвращаемое функцией f2. Отметим, что параметром является именно возвращаемое значение, а не сама функция f2!!!

Таким образом, вначале будет вызвана функция f2, после завершения ее работы возвращенное значение будет принято функцией f1 в качестве фактического параметра. Только после этого начинает работу функция f1.

Рассмотрим некоторые основные правила языка С++ для оформления программ, состоящих из нескольких функций:

Программа на языках С и С++ должна содержать функцию с именем main.

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

Описание функции должно находиться выше по тексту, чем вызов этой функции.

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

Прототип функции очень похож на ее заголовок, отличие состоит в символе «точка с запятой», завершающей описание прототипа функции:

«тип» «имя_функции»(«список_формальных_параметров»);

Примеры прототипов функций приведенных выше:

double f1(double x);

double f2(double);

void f3();

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

Пример 8

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

int Mult5(int);

void main()

{int x=-5;

x=(Mult5(x)+11)/2;

cout<<x;

}

int Mult5(int a)

{return (a*5);}

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

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

Последнее правило означает, что хотя в рассмотренных функциях f1 и f2 встречается параметр x, эти параметры различны и могут быть использованы только на уровне функции, в которой они описаны. Различие переменных с одинаковыми функциями в разных функциях можно понимать точно так же, как и различие двух людей с одинаковыми именами. Так, например, могут быть два Александра, - один носит фамилию «Первый», другой - фамилию «Второй». Оба они Александры, но каждый из них может даже не подозревать о существовании другого (например, живут на разных континентах). С переменными в различных функциях аналогичная ситуация, если считать имя функций, в которой описаны эти переменные в качестве их «фамилий». Таким образом, переменные в одной функции никак не связаны со своими «тезками» в других функциях. Т.е. можно сказать, что переменная x (параметр) функции f1 и переменная x (параметр) функции f2 - это две совершенно разные переменные (у них разные «фамилии»).

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

double cube(double x)

{double a=x*x*x;

return a;}

void main()

{double a=0;

cout<<cube(2)<<” :::: ”<<a;

getch();

}

Рассмотрим пошагово выполнение этой программы. Выполнение любой программы начинается с функции main, не смотря на то, что в большинстве случаев она идет в конце текста программы (см. правило 3).

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

После того, как отработает вывод, на экране появится следующая строка:

8 :::: 0

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

2. Передача параметров в функцию: по значению, по ссылке, как ссылки

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

Пример 9

#include<iostream.h>

#include<conio.h>

void Swap(int a, int b)

{int c=a;

a=b;

b=c;}

void main()

{int x=0, y=1;

cout<<”x=”<<x<<”, y=”<<y<<endl;

Swap(x,y);

cout<<”После того, как отработала функция Swap:\n”;

cout<<”x=”<<x<<”, y=”<<y;

getch();

}

Запустив приведенную программу, можем видеть на экране следующие строки:

x=0, y=1

После того, как отработала функция Swap.

x=0, y=1

Здесь, внутри функции Swap происходит обмен значениями ее параметров. Однако к обмену значений переменных x и y функции main это не имеет никакого отношения, поскольку при вызове функции Swap их значения были скопированы во вновь образованные переменные a и b. Обмен происходит именно для копий и исходных переменных не касается.

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

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

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

Пример 10

#include<iostream.h>

#include<conio.h>

void Swap(int* a, int* b)

{int c=*a;

*a=*b;

*b=c;}

void main()

{int x=0, y=1;

cout<<”x=”<<x<<”, y=”<<y<<endl;

Swap(&x,&y);

cout<<”После того, как отработала функция Swap:\n”;

cout<<”x=”<<x<<”, y=”<<y;

getch();

}

Здесь параметрами функции Swap являются два указателя на целые числа. Сама функция Swap работает следующим образом: значение содержимого ячейки, находящейся по адресу в переменной a присваивается переменной c, затем, значение содержимого ячейки, находящейся по адресу в переменной b записывается по адресу в переменной a, а на его место записывается значение переменной c. Таким образом, происходит обмен содержимого ячеек, имеющих адреса, записанные в переменных a и b. В функции main происходит вызов функции Swap, для которой в качестве параметров, указываются адреса переменных x и y. Следовательно, запустив программу, можем видеть на экране следующие строки:

x=0, y=1

После того, как отработала функция Swap.

x=1, y=0

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

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

Пример 11

#include<iostream.h>

#include<conio.h>

void Swap(int& a, int& b)

{int c=a;

a=b;

b=c;}

void main()

{int x=0, y=1;

cout<<”x=”<<x<<”, y=”<<y<<endl;

Swap(x,y);

cout<<”После того, как отработала функция Swap:\n”;

cout<<”x=”<<x<<”, y=”<<y;

getch();

}

Как и в предыдущем примере, запустив приведенную программу, можем видеть на экране следующие строки:

x=0, y=1

После того, как отработала функция Swap.

x=1, y=0

3. Массивы как параметры функции

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

3.1 Одномерные массивы как параметры функции

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

Пример 12

Запишем программу для задачи: «Составить функцию вывода одномерного массива на экран. При помощи этой функции вывести два массива различной длины».

#include<iostream.h>

#include<conio.h>

void OutputMas(int* a, int n)//параметр - целочис-

// ленный массив

// длины n

{cout<<”\nМассив:\n”;//информационная строка

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

cout<<a[i]<<”\t”;//вывод элементов массива

//на экран через отступ в

//виде табуляции

сout<<”\n его длина = ”<<n;// сообщение о длине

}

void main()

{int a[]={1,2,3,4,5,6,7},

b[]={5,4,3,2,1};

OutputMas(a,7);// вывод массива a

OutputMas(b,5); // вывод массива b

getch();

}

Запустив приведенную программу, можем видеть на экране следующие строки:

Массив:

1234567

его длина = 7

Массив:

54321

его длина = 5

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

Пример 13

Запишем программу для задачи: «Составить функцию объединения двух массивов A и B одинакового размера (результат поместить в массив C - выходной параметр) по следующему правилу: C - A[0], B[0], A[1], B[1],…».

#include<iostream.h>

#include<conio.h>

void OutputMas(int* a, int n)// функция вывода

// массива

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

cout<<a[i]<<”\t”;

}

void ConcatMas(int* a, int* b, int n, int* c)

// функция объединения массивов

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

{c[2*i]=a[i];

c[2*i+1]=b[i];

}

}

void main()

{int a[]={1,3,5,7},

b[]={2,4,6,8};

cout<<”\nМассив A:\n”;

OutputMas(a,4);// вывод массива a

cout<<”\nМассив B:\n”;

OutputMas(b,4); // вывод массива b

ConcatMas(a,b,4,c);// объединение массивов

cout<<”\nМассив C:\n”;

OutputMas(c,8); // вывод массива b

getch();

}

Запустив приведенную программу, можем видеть на экране следующие строки:

Массив A:

1357

Массив B:

2468

Массив C:

1234567

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

Пример 14

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

#include<iostream.h>

#include<conio.h>

#include<stdlib.h>

void OutputMas(int* a, int n)// функция вывода

// массива

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

cout<<a[i]<<”\t”;

сout<<”\n его длина = ”<<n; }

int* CreateCon(int n)// функция создания массива

{int*a=new int[n];// выделяем память под

//элементы массива целого

//типа в количестве n штук

for(int i=0;i<n;i++)// считываем элементы

{cout<<”Введите ”<<i+1<<”элемент: ”

cin<<a[i];

}

return a;//возвращаем указатель на

//первый элемент созданно-

//го массива

}

int* CreateRand(int n)//функция создания массива

{int*a=new int[n];// выделяем память

randomize();// создаем новую последовательность псевдослучайных чисел

for(int i=0;i<n;i++)// считываем элементы

{a[i]=random(20);

cout<<a[i]<<”\t”;

cin<<a[i];

}

return a;//возвращаем указатель на

//первый элемент созданно-

//го массива

}

void main()

{int* a,*b,n;

a=CreateCon(8); // создание массива a

b=CreateCon(5); // создание массива b

delete[] a;

delete[] b;

getch();

}

Пример 15

Рассмотрим следующую задачу: «Дана строка (массив символов) в том случае, если ее содержимое является натуральным числом в строчной записи найти:

1. сумму всех цифр этого числа (функция Sum);

2. произведение (функция Prod);

3. остаток от деления квадрата данного числа на 25 (функция Mod25).» На основе предложенной задачи приведем основной принцип проектирования программ, состоящих из нескольких функций.

Начнем, хоть это может показаться несколько странно, с основной функции - функции main. Дело в том, что любую задачу удобно решать начиная с ее конца. Такой принцип особенно часто используют физики при решении своих задач. Итак, функция main:

void main()

{char* in_str;

int tmp;

gets(in_str);// считывание строки

if (IsNumber(in_str))// проверка, является ли она

// числом. Если да, то

{t=Sum(in_str);// вычисляем его сумму цифр

printf(“Сумма цифр равна: %d\n”, t);

t=Prod(in_str);// вычисляем произведение цифр

printf(“Произведение цифр равно: %d\n”, t);

t=Мod25(in_str);// находим остаток от деления

// квадрата этого числа на 25

printf(“Остаток от деления квадрата на 25 равен: %d”, t);

}

else// если числом не является, то

// выдаем соответствующее

// сообщение

printf(“Данная запись не является числом!”);

getch();

}

Функция main была составлена исходя из предположения, что необходимые для ее работы функции IsNumber, Sum, Prod и Mod25 уже написаны и работают корректно. Таким образом, чтобы программа начала работать, необходимо записать эти функции.

Функция IsNumber:

int IsNumber(char* str)

{int i=0;

while(str[i]!='\0')

if(StrToNum(str[i++])==10)

return 0;

return 1;

}

Здесь до конца строки (str[i]!='\0'), если встречается символ, который не является цифрой (условие StrToNum(str[i++])==10), то возвращается значение, равное нулю. Это говорит о том, что вся строка не является записью целого числа. Если же таких символов нет (т.е все символы - цифры), то функция возвращает значение, равное единице.

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

Функция StrToNum имеет вид:

int StrToNum(char c)

{switch(c)

{case '0': return 0;

case '1': return 1;

case '2': return 2;

case '3': return 3;

case '4': return 4;

case '5': return 5;

case '6': return 6;

case '7': return 7;

case '8': return 8;

case '9': return 9;

default: return 10;

}

}

Таким образом, если записать приведенные функции в следующем порядке:

StrToNum,

IsNumber,

main,

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

Продолжим описание основных функций для нашей задачи.

Функции Sum и Prod:

int Sum(char* str)

{int i=0, s=0;

while(str[i]!='0')

s+=StrToNum(str[i++]);

return s;

}

int Prod(char* str)

{int i=0, p=1;

while(str[i]!='0')

p*=StrToNum(str[i++]);

return p;

}

Функции Sum и Prod практически идентичны, только в одной вычисляется сумма, а в другой произведение. Отметим, что в этих функциях мы использовали функцию StrToNum, которая была написана для функции IsNumber. Таким образом, мы можем пользоваться уже написанными функциями для других частей программы, если их результат их работы нас устраивает.

Осталась последняя функция - Mod25:

int Mod25(char* str)

{int i=0, n=0;

while(str[i]!='0')

n=n*10+StrToNum(str[i++]);// составляем число по его

// цифрам в соответствующих

// разрядах

n=(n*n)%25;// остаток от деления квадрата

// числа на 25

return n;

}

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

#include<stdio.h>

#include<conio.h>

int StrToNum(char c)

{…}

int IsNumber(char* str)

{…}

int Sum(char* str)

{…}

int Prod(char* str)

{…}

int Mod25(char* str)

{…}

void main()

{…}

3.2 Двумерные массивы как параметры функции

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

Рисунок 1.

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

Пример 16

Запишем программу для задачи: «Составить функцию возвращающую значение суммы всех элементов двумерного массива (с заполнением элементов при помощи генератора случайных чисел). При помощи этой функции вычислить значение суммы двух массивов одинакового размера».

#include<iostream.h>

#include<conio.h>

int SumElem(int** a, int m, int n)

{int v=0;

for(int i=0;i<m;i++)// проходим по всем строкам,

for(int j=0;j<n;j++)// всем столбцам

v+=a[i][j];// и суммируем элементы

return v;// возвращаем результат

}

void main()

{int** a, **b, m=5, n=7,s;

a=new int*[m];

b=new int*[m];// выделяем память под

for(int i=0;i<m;i++)// двумерные массивы a и b

{a[i]=new int[n];

b[i]=new int[n];

}

randomize();// генерируем новую последо-

// вательность случайных чисел

cout<<”\nМассив А:\n”;// информационная строка

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

{for(int j=0;j<n;j++)

{a[i][j]=random(20);//заполнение и вывод элементов

cout<<a[i][j]<<”\t”;// массива a на экран через

}// отступ в виде табуляции

cout<<”\n”;

}

cout<<”\nМассив B:\n”;// аналогичные действия по

for(int i=0;i<m;i++)// заполнению массива b

{for(int j=0;j<n;j++)

{b[i][j]=random(20);

cout<<b[i][j]<<”\t”;

}

cout<<”\n”;

}

s=SumElem(a,m,n);// вычисляем сумму элементов

cout<<”\n Сумма элементов массива A рана: ”<<s;

s=SumElem(b,m,n);

cout<<”\n Сумма элементов массива A рана: ”<<s;

//освобождение памяти

for(int i=0;i<m;i++)// вначале, освобождаем память

{delete[]a[i];// занимаемую строками матрицы

delete[]b[i];

}

delete[]a;// затем, занимаемую вспомога-

delete[]b;// тельным массивом

}

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

Пример 17

Запишем программу для задачи: «Составить функции создания двумерного массива (с заполнением элементов при помощи генератора случайных чисел) и вывода его на экран. При помощи этой функции создать и вывести на экран два массива различных размеров».

#include<iostream.h>

#include<conio.h>

// функция вывода двумерного массива на экран

void OutputMatr(int** a, int m, int n)//параметр - цело-

//численный массив

// размера mxn

{cout<<”\nМассив:\n”;//информационная строка

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

{for(int j=0;j<n;j++)

cout<<a[i][j]<<”\t”;//вывод элементов массива

//на экран через отступ в

//виде табуляции

cout<<”\n”;

}

}

// функция создания двумерного массива

int** CreateMatr(int m, int n)

{int**a=new int*[m];// выделяем память под

for(int i=0;i<m;i++)// двумерный массив

a[i]=new int[n];

randomize();// генерируем новую последо-

// вательность случайных чисел

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

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

a[i][j]=random(20);// заполняем матрицу случай-

// ными числами

return a;// возвращаем указатель на

// матрицу

}

// функция удаления двумерного массива (освобождение памяти)

// ее параметрами будут указатель и количество строк

// количество столбцов не нужно

void DeleteMatr(int** a, int m)

{for(int i=0;i<m;i++)// вначале, освобождаем память

delete[]a[i];// занимаемую строками матрицы

delete[]a;// затем, занимаемую вспомога-

// тельным массивом

}

void main()

{int** a, **b;

a=CreateMatr(3,5);// создание массива a

b=CreateMatr(5,8);// создание массива b

OutputMatr(a,3,5);// вывод массива a

OutputMatr(b,5,8); // вывод массива b

DeleteMatr(a,3,5);// удаление массива a

DeleteMatr(b,5,8); // удаление массива b

getch();

}

Пример 18

Рассмотрим задачу: «Даны целые числа m и n (m,n>0), матрица A размера mxn и вектор B размера n. Найти произведение C=AB.».

Проектирование программы начнем, как и в примере 15, с функции main:

void main()

{int **a,*b,*c, m, n;

cout<<”Введите числа m и n: ”;

cin<<m<<n;// считывание чисел m и n

a=CreateMatr(m,n);// создание матрицы a

b=CreateVect(n);// создание вектора b

// вывод на экран матрицы и вектора

OutputMatr(a,m,n);

OutputVect(b,n);

c=Multiply(a,b,m,n);// вычисление произведения

cout<<”\nРезультат:\n”;

OutputVect(c,m);// вывод на экран результата

DeleteMas(a,b,c,m);// освобождение памяти

getch();

}

Функция main была составлена исходя из предположения, что необходимые для ее работы функции CreateMatr, CreateVect, OutputMatr, OutputVect, Multiply и DeleteMas уже написаны и работают корректно. Таким образом, чтобы программа начала работать, необходимо записать эти функции.

Функция CreateMatr:

int** CreateMatr(int m, int n)

{int** a=new int*[m];// выделяем память под

for(int i=0;i<m;i++)// двумерный массив

a[i]=new int[n];//

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

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

{cout<<"Введите a(”<<i+1<<”,”<<j+1<<”): ”;

cin<<a[i][j];//считывание элементов массива

}

return a;

}

Функция CreateVect:

int* CreateVect(int n)

{int* a=new int[n];// выделяем память

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

{cout<<" Введите b(”<<i+1”): ”;

cin<<a[i];//считывание элементов массива

}

return a;

}

Текст функции OutputMatr возьмем из программы предыдущего примера.

Функция OutputVect:

void OutputVect(int* a, int n)

{cout<<”\nВектор:\n”;

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

cout<<a[i]<<"\t”;

cout<<”\n”;

}

Функция DeleteMas:

void DeleteMas(int** a, int* b, int* c, int m)

{for(int i=0;i<m;i++)// освобождаем память,

delete[]a[i];// занимаемую маcсивом a

delete[]a;

delete[]b;// освобождаем память,

delete[]c; // занимаемую массивами b и c

}

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

Функция Multiply:

int* Multiply(int** a, int* b, int m, int n)

{int* c=new int[m];// выделяем память под массив

// результата

// умножение матрицы на вектор

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

{c[i]=0;

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

c[i]+=a[i][j]*b[j];

}

return c;

}

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

#include<iostream.h>

#include<conio.h>

int** CreateMatr(int m, int n)

{…}

int* CreateVect(int n)

{…}

void OutputMatr(int** a, int m, int n)

{…}

void OutputVect(int* a, int n)

{…}

void DeleteMas(int** a, int* b, int* c, int m)

{…}

int* Multiply(int** a, int* b, int m, int n)

{…}

void main()

{…}

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

функция параметр одномерный умолчание

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

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

Пример 19

void Prt(int a, int n=0, double x=10, int k=1)

{…}

Здесь мы описали функцию Prt от четырех параметров, три из которых (n, x и k) являются параметрами по умолчанию.

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

Пример 20

void Prt(int, int =0, double =10, int =1);

void Prt(int a, int n, double x, int k)

{…}

Здесь мы описали функцию Prt из предыдущего примера с использованием ее прототипа.

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

Проиллюстрируем последнее правило следующим примером:

Пример 21

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

Prt(1,3.5);// неверный вызов! Будет выдано

// сообщение о несовпадении типов при

// определении второго параметра

Prt(1, ,3.5);// также неверный вызов!

Prt(1,0,3.5);// вызов верный. Четвертый параметр

// можно не указывать

Для следующей задачи покажем использование параметров по умолчанию.

Пример 22

Рассмотрим функцию OutputVect из примера 18. Заметим, что она используется и для вывода начальных данных (вектор b), и для вывода результата (вектор c). При этом, в функции main приходится дополнительно сообщать на экран о том, что далее следует результат. Модифицируем функцию таким образом, чтобы она сама выводила требуемое сообщение (модификация выделена).

Функция OutputVect:

void OutputVect(int* a, int n, int k=0)

{if (!k)// если k==0, т.е. не задано,

cout<<”\nВектор:\n”;// то сообщение о начальных данных

else// иначе - о результате

cout<<”\nВ результате получим вектор:\n”;

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

cout<<a[i]<<"\t”;

cout<<”\n”;

}

Функции main, в таком случае, будет иметь вид:

void main()

{int **a,*b,*c, m, n;

cout<<”Введите числа m и n: ”;

cin<<m<<n;

a=CreateMatr(m,n);

b=CreateVect(n);

// вывод на экран матрицы и вектора

OutputMatr(a,m,n);

OutputVect(b,n);// третий параметр не задан,

// значит, он будет равен нулю

// т.е. вывод начальных данных

c=Multiply(a,b,m,n);

// cout<<”\nРезультат:\n”; эта строка уже не нужна

OutputVect(c,m,1);// вывод на экран результата,

// т.к. параметр k = 1

DeleteMas(a,b,c,m);

getch();

}

5. Рекурсия

После всего приведенного выше материала может возникнуть вполне закономерный вопрос: «А можно ли некоторой функции вызывать саму себя?» Ответ на такой вопрос - да, можно. В том случае, если в теле функции имеется вызов самой себя, то говорят, что имеет место прямая рекурсия (мы будем рассматривать только прямую рекурсию), а функция называется рекурсивной. В качестве примера приведем две функции вычисления факториала натурального числа (n!=n*(n-1)*(n-2)*…*2*1): без использования рекурсии и с ее использованием.

Пример 23

Функция Factorial без рекурсии:

double Factorial(int n)// тип double используется для

// исключения переполнения при

{double a=1;// умножении числел

for(int i=n;i>1;i--)

a*=i;

return a;

}

Функции FactorialR (рекурсивная) будет иметь вид:

double FactorialR(int n)

{if(n>1)

return n*FactorialR(n-1);

return 1;

}

Теперь обратим внимание на тот факт, что в функции Factorial мы просто перемножили числа от единицы до n в порядке убывания. Функция FactorialR устроена совершенно по-другому. Мы воспользовались рекуррентным соотношением для факториала (n!=n*(n-1)!) и начальным значением (1!=1). Таким образом, если параметр n больше единицы, то мы должны вернуть его значение (n), умноженное на факториал предшествующего натурального числа ((n-1)!). А какая функция может вычислить факториал числа n-1? Конечно же, функция FactorialR (мы ее создаем именно для вычисления факториалов). Следовательно, мы возвращаем значение, равное n*FactorialR(n-1). В противном случае (т.е. n=1 - мы считаем, что только корректные данные поступают в функцию) вернуть значение, равное единице.

Приведем схему работы последней функции для вычисления значения, равного 4! (Рис.2):

Рисунок 2

Первым действием программа вызывает и вызывает функцию FactorialR, каждый раз с меньшим аргументом, чем предыдущий. Такие вызовы происходят до тех пор, пока передаваемый аргумент не станет равным единице. После этого, программа начинает возвращаться назад на места вызова и умножать старый аргумент на значение, которое вернулось после вызова. Вначале 2*1, затем 3*(2*1) и, наконец, 4*(3*2*1) - значение, которое требовалось вычислить.

В качестве другого примера рекурсии приведем функцию NODe(a,b), вычисляющую наибольший общий делитель натуральных чисел a и b при помощи алгоритма Евклида.

Пример 24

Алгоритм Евклида нахождения наибольшего общего делителя натуральных чисел состоит в том, что если число a не делится без остатка на число b, то наибольшим общим делителем a и b является нод(b, c), где c - это остаток от деления числа a на число b. В противном случае, наибольшим общим делителем является число b.

Функция NODe:

int NODe(int a, int b)

{int c = a%b;

if(c==0)

return b;

return NODe(b,c);

}

6. Подставляемые (inline) функции

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

Пример 25

inline double cube(double* x)

{ return x*x*x;

}

void ff()

{cout<<cube(5);// то же самое, что cout<<(5*5*5)

int a=-3;

int b=cube(a);// то же самое, что int b=a*a*a

}

Функция не может быть описана со спецификатором inline, если

а) она является рекурсивной;

б) она содержит операторы цикла (for, while, do), переключатели (switch) и оператор безусловного перехода (goto).

7. Функция как параметр функции

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

Пример 26

void gg(char* p)

{ /* некоторая последовательность операторов */

}

void ff()

{void (*fct)(char*);// описание указателя на функцию

// одного параметра типа char*

fct = &gg;// переменной fct присваиваем адрес

// функции gg (она подходит!)

(*efct)("текст");// вызов gg через указатель fct

}

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

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

Пример 27

void (*pf)(char*);// указатель на функцию void(char*)

void f1(char*);

int f2(char*);

void f3(int*);

void f()

{pf = &f1; // корректно

// pf = &f2; // ошибка: неверный тип

//возвращаемого значения

// pf = &f3; // ошибка: несоответствие типов

//списка параметров

(*pf)("текст1");// корректно

// (*pf)(1); // ошибка: неверный тип параметра

// должен быть char*, не int

// int i = (*pf)("текст");// ошибка: void присваивается int

}

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

typedef int (*ptr)(int, double);

После того, как мы определили возможность создания такого типа как указатель на функцию, возникает закономерный вопрос: «А можно ли некоторую функцию передавать в качестве параметра в функцию?» Проиллюстрируем такую ситуацию следующим примером.

Пример 28

Рассмотрим следующую задачу: «Даны целые числа N и K (N>0) и два одномерных массива целых чисел A и B размера . Сформировать массив C вещественного типа, элементы которого равны:

а) , если K=2;

б) , если K=3;

в) , если K=4;

г) , если K=5;

д) , во всех остальных случаях.»

Решение:

#include<iostream.h>

#include<conio.h>

typedef double (*ptr)(int, int);// определили тип для

// функций, которые будут выполнять

// основные действия задачи

// функция сложения:

double Plus(int a, int b)

{double c=a+b;

return c;

}

// предоставим читателю возможность самостоятельно

// определить функции разности и произведения:

double Minus(int a, int b){ /*...*/}

double Mult(int a, int b){/*...*/}

// функция частного:

double Div(int a, int b)

{double c;

if(b==0)

return -1;

c=(a*1.0)/b; // умножение на единицу нужно для того, чтобы

return c;// привести частное к вещественному числу

}

// функция степени (удобно организовать рекурсивной):

double ExpAB(int a, int b)

{double c;

if(a==0)

return 0;

if(b==0)

return 1;

if(b>0)// если b>0, то соотношение

return ExpAB(a, b-1)*a;// вида ab=ab-1*a

else// иначе

return ExpAB(a, b+1)/a;// ab=ab+1/a

}

// функция ввода массива:

void InputMas(int* a, int n)

{cout<<”\nВвод данных:\n”;

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

{cout<<”Введите элемент массива с номером ”<<i+1<<”: ”;

cin>>a[i];

}

}

// функция вывода массива-результата:

void OutputMas(double* a, int n)

{cout<<”\nРезультат:\n”;

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

cout<<a[i]<<”\t”;

}

// основная функция действий над массивами

void Calculate(int*a, int*b, double*c, int n, ptr gg)

{for(int i=0;i<n;i++)// вычисляем результат операции по

c[i]=gg(a[i],b[i]);// адресу в gg для пары a[i] и b[i]

}

// функция main

void main()

{int* a,*b, n, k;// описываем начальные данные

ptr fg;// и указатель на функцию

// ввод начальных данных

cout<<”Введите n:”; cin>>n;

cout<<”Введите k:”; cin>>k;

a=new int[n];

b=new int[n];

InputMas(a,n);

InputMas(b,n);

// подготовка к выполнению указанной пользователем

// операции

double*c=new double[n];

switch(k)

{case 2: fg=&Minus;

break;

case 3: fg=&Mult;

break;

case 4: fg=&Div;

break;

case 5: fg=&ExpAB;

break;

default: fg=&Plus;

}

// выполнение действий по формированию массива-

// результата

Calculate(a,b,c,n,fg);

// вывод результата

OutputMas(c,n);

getch();

// освобождение памяти

delete []a;

delete []b;

delete []c;

}

Отметим, что функция Calculate, в которой осуществляются основные действия с массивами сама «не знает», какая операция будет выполнена. Она только получает адрес функции, выполняющей некоторую операцию, и обращается по этому адресу.

Список литературы

1. Подбельский В.В. Язык Си++. -М.: Финансы и статистика, 2003. -560с.

2. Страуструп Б. Язык программирования Си++: Пер. с англ. - М.: Радио и связь, 1991. -352с.

3. Шилдт Г. Самоучитель С++: Пер. с англ. - СПб.: БХВ-Петербург, 2003. -688с.

Размещено на Allbest.ru


Подобные документы

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

    курсовая работа [1,1 M], добавлен 24.11.2013

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

    презентация [83,4 K], добавлен 19.10.2014

  • Выбор параметров и структурой схемы. Программирование скрипта (m-файла) для задания исходных параметров. Расчет параметров регулятора, проектирование его S-функции. Программирование GUI-интерфейса: разработка внешнего вида и проектирование обработчиков.

    курсовая работа [721,5 K], добавлен 18.05.2013

  • Понятие массива и правила описания массивов в программах на языке С. Рассмотрение основных алгоритмов обработки одномерных массивов. Примеры программ на языке С для всех рассмотренных алгоритмов. Примеры решения задач по обработке одномерных массивов.

    учебное пособие [1,1 M], добавлен 22.02.2011

  • Обеспечение безопасности сайта; значение правильной обработки данных, получаемых из формы. Вызов и условное объявление функции. Передача параметров по значению и ссылке. Обработка HTML-форм; протокол GET. Доступ к полям формы через ассоциированный массив.

    презентация [112,7 K], добавлен 21.06.2014

  • Назначение и механизмы подпрограмм в программировании, их описание и вызов. Характеристика формальных и фактических параметров. Разновидности параметров в программе Паскаль. Способы передачи информации в подпрограммы, их виды. Отличие процедур от функций.

    реферат [20,9 K], добавлен 06.03.2014

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

    отчет по практике [2,9 M], добавлен 01.12.2015

  • Характеристика модульного программирования: процедуры и функции, модули и их структура, открытые массивы и строки, нетипизированные параметры. Способы передачи параметров в подпрограммы в Borland Pascal. Объектно-ориентированное программирование.

    контрольная работа [28,9 K], добавлен 28.04.2009

  • Программирование скрипта (m-файла) для задания исходных параметров, m-функции для задающего воздействия. Программирование блока "Signal Builder" для возмущающего воздействия. Расчет параметров регулятора. Проектирование Simulink-модели структурной схемы.

    контрольная работа [769,0 K], добавлен 28.05.2013

  • Характеристика управляющих структур программирования, компонентов и возможностей Delphi, путем решения задач. Особенности расчета значений функции и числового ряда, построение графика функции с помощью TChart. Анализ методов преобразования массивов.

    курсовая работа [191,3 K], добавлен 07.02.2010

Работы в архивах красиво оформлены согласно требованиям ВУЗов и содержат рисунки, диаграммы, формулы и т.д.
PPT, PPTX и PDF-файлы представлены только в архивах.
Рекомендуем скачать работу.