ЛЕКЦИЯ 4. МАССИВЫ, УКАЗАТЕЛИ И ФУНКЦИИ МАССИВЫ МАССИВЫ Массив – это последовательность однотипных элементов, имеющих единый идентификатор и хранящихся в смежных ячейках памяти. • Единственный оператор для массива – индексация []. • Имеет наивысший приоритет (1). • Начинается с 0. • Индекс – всегда целое выражение. ОБЪЯВЛЕНИЕ МАССИВОВ int massiv[5]; float d[4]; Размер массива при объявлении – всегда целое константное выражение. Нельзя объявить массив int i =9; int m[i]; // нельзя Внутри квадратных скобок может стоять только константа, но не имя переменной, чтобы компилятор точно знал, какой объем памяти резервировать. Размер массива должен быть известен заранее и не может быть изменен в ходе выполнения программы. const int MAX_ARRAY = 30; int massiv[MAX_ARRAY]; ОБЪЯВЛЕНИЕ МАССИВОВ Когда массив объявлен, к его элементам можно обращаться как к отдельным переменным, не забывая указывать индекс. int massiv[5]; massiv[0] = 14; massiv[1] = massiv[0]*2; cout << massiv[1]; // 28 cout << massiv[2]; // ерунда ОБЪЯВЛЕНИЕ МАССИВОВ • заполним массив нулями: for (int i = 0; i < 5; i++) massiv[i] = 0; • заполним массив от 1 до 5: for (int i = 0; i < 5; i++) massiv[i] = i+1; • выведем массив на экран: for (int i = 0; i < 5; i++) cout << massiv[i] << '\t'; ИНИЦИАЛИЗАЦИЯ МАССИВОВ Массив можно инициализировать одним из трех способов: • при создании массива — используя инициализацию по умолчанию (этот метод применяется только для глобальных и статических массивов); • при создании массива — явно указывая начальные константные значения. • в процессе выполнения программы — путем записи данных в массив. При создании в массив могут быть занесены только константные выражения. В процессе выполнения программы в массив можно записывать и значения переменных. Инициализация по умолчанию – нулями (только для глобальных и статических). ИНИЦИАЛИЗАЦИЯ МАССИВОВ Массив можно проинициализировать при объявлении (явная инициализация): int massiv[5] = {2,4,6,8,10}; float d[4] = {2.4, 5.8, 1.347, 3.14}; Можно инициализировать не все элементы: int massiv[5] = {2,4}; В этом случае остальные элементы будут проинициализированы 0. Чтобы это сработало, хотя бы один элемент должен быть проинициализирован. int massiv[5] = {0}; ИНИЦИАЛИЗАЦИЯ МАССИВОВ Если массив сразу инициализируется, его размер можно не задавать: int massiv[] = {1,2,3,4,5,6,7,8,9,10,11,12}; Размер будет автоматически вычислен компилятором. Контроль выхода за пределы массива возложен на программиста. int massiv[5] = {0}; for(int i = 0; i < 10; i++) cout << massiv[i] << "\t"; Увидите 5 нулей и 5 порций ерунды. Размер массива sizeof(massiv) •Размер элемента массива sizeof(massiv[0]) sizeof(int) массива //это предпочтительнее //если не предполагаете менять тип •К-во элементов в массиве: sizeof(massiv)/ sizeof(massiv[0]) •№ последнего элемента в массиве: sizeof(massiv)/ sizeof(massiv[0])-1 РАЗМЕРЫ COUT << SIZEOF(MASSIV); //РАЗМЕРА МАССИВА В БАЙТАХ COUT << SIZEOF(MASSIV[0]);// РАЗМЕР ЭЛЕМЕНТА // К-ВО ЭЛЕМЕНТОВ COUT << SIZEOF(MASSIV) / SIZEOF(MASSIV[0]); // НОМЕР ПОСЛЕДНЕГО ЭЛЕМЕНТА COUT << SIZEOF(MASSIV) / SIZEOF(MASSIV[0]) - 1; И.М.Желакович БГУИР ОСНОВНЫЕ ЗАДАЧИ С МАССИВАМИ сумма элементов массива int massiv[5] = {6, -4, 17, 139, 0}; int sum = 0; for (int i = 0; i < 5; i++) sum += massiv[i]; 0+6+(-4)+17+139+0 cout << sum << endl; 6 -4 17 2 19 139 0 0 6 И.М.ЖЕЛАКОВИЧ 158 БГУИР 158 ОСНОВНЫЕ ЗАДАЧИ С МАССИВАМИ среднее арифметическое: int massiv[5] = {6, -4, 17, 139, 0}; double sum = 0; for (int i = 0; i < 5; i++) sum += massiv[i]; cout << sum/5 << endl; 6 -4 17 139 (6+(-4)+17+139+0)/5 И.М.ЖЕЛАКОВИЧ БГУИР 0 ОСНОВНЫЕ ЗАДАЧИ С МАССИВАМИ максимальный элемент в массиве: int massiv[5] = {6, -4, 17, 139, 0}; int max = massiv[0]; // считаем максимальным первый for (int i = 1; i < 5; i++) if(massiv[i] > max) max = massiv[i]; cout << max<< endl; 6 6 -4 17 139 0 6 17 139 139 И.М.ЖЕЛАКОВИЧ БГУИР МНОГОМЕРНЫЕ МАССИВЫ. Двумерный массив рассматриваются как массив элементов, каждый из которых является одномерным массивом. Трехмерный - как массив, элементами которого являются двумерные массивы и т.д. int matrix[3][4]; // Массив из 3-х элементов, каждый из которых является // int matrix[3][4][2]; массивом из 4-х целых чисел. МНОГОМЕРНЫЕ МАССИВЫ. Двумерные массивы в памяти хранятся по строкам, т.е. при обращении к элементам в порядке их размещения в памяти быстрее всего меняется самый правый индекс. Так, для массива c[2][3] его шесть элементов расположены в памяти так: c[0][0] c[0][1] c[0][2] c[1][0] c[1][1] c[1][2]. Многомерные массивы также можно инициализировать при описании: int d[2][3]={ 1, 2, 0, 5 }; В этом случае первые 4 элемента массива получат указанные значения, а остальные два инициализируются нулями. МНОГОМЕРНЫЕ МАССИВЫ. Если инициализируется многомерный массив, то самую первую размерность можно не задавать (и только ее). В этом случае компилятор сам вычисляет размер массива: int f[][2] = {2, 4, 6, 1}; // массив f[2][2] int a[][2][2] = {1, 2, 3, 4, 5, 6, 7, 8}; //массив a[2][2][2] МНОГОМЕРНЫЕ МАССИВЫ. Инициализирующее выражение может иметь вид, отражающий факт, что массив является, например, двумерным: int c[2][3]={{1, 7},{-5, 3}}; В этом случае первая и вторая строки инициализируются не до конца. Элементы c[0][2] и c[1][2](3-й столбец), - инициализируется нулями. 1 7 0 -5 3 0 МНОГОМЕРНЫЕ МАССИВЫ. Трехмерный массив можно рассматривать как 3 страницы 2х2: int massiv[3][2][2] = {{1,2,3},{5,6,7,8},{9,10,11,12}}; на месте 4 будет 0 int massiv[3][2][2] = {{0},{5,6,7,8},{9,10,11,12}}; пустые скобки оставлять нельзя. ПРИМЕРЫ Для примера – вывод одномерного массива int massiv[] = {1,2,3,4,5,6,7,8,9,10,11,12}; for(int i=0;i<12; i++) { cout << massiv[i] << "\t"; } cout << "\n"; ПРИМЕРЫ Вывод всех элементов для двумерного массива: int massiv[3][4] = {1,2,3,4,5,6,7,8,9,10,11,12}; for(int i=0;i<3; i++) { for(int j=0;j<4; j++) cout << massiv[i][j] << "\t"; cout << "\n"; } ПРИМЕРЫ Если массив трехмерный int massiv[3][2][2] = {1,2,3,4,5,6,7,8,9,10,11,12}; for(int k = 0; k < 3; k++) { for(int i = 0; i < 2; i++) { for(int j = 0; j < 2; j++) cout << massiv[k][i][j] << "\t"; cout << "\n"; } cout << "*******************\n"; } ПРИМЕРЫ Подсчет суммы элементов int massiv[3][4] = {1,2,3,4,5,6,7,8,9,10,11,12}; int sum = 0; for(int i = 0; i < 3; i++) { for(int j = 0; j < 4; j++) sum += massiv[i][j]; } cout << sum << "\n"; ПРИМЕРЫ Подсчет суммы по строкам int massiv[3][4] = {1,2,3,4,5,6,7,8,9,10,11,12}; int sum; for(int i = 0; i < 3; i++) { sum = 0; for(int j = 0; j < 4; j++) sum += massiv[i][j]; cout << sum << "\n"; } ПРИМЕРЫ Поиск максимума int massiv[3][4] = {1,2,3,4,5,6,7,8,9,10,11,12}; int max = massiv[0][0]; for(int i = 0; i < 3; i++) { for(int j = 0; j < 4; j++) if (max < massiv[i][j]) max = massiv[i][j]; } cout << max << "\n"; ПРИМЕРЫ вывод главной диагонали int massiv[3][3] = {1,2,3,4,5,6,7,8,9}; for(int i = 0; i < 3; i++) { cout << massiv[i][i]; } СТРОКОВЫЕ МАССИВЫ В C отсутствует тип данных для символьных строк. Строка представляется в виде массива символов (char). Каждый символ хранится в отдельной ячейке массива, а в последней ячейке содержится null-символ ‘\0’. Длина массива должна учитывать ‘\0’. char s[5]; s[0] = 's'; s[1]='h'; s[2] = 'i'; s[3]='p'; s[4]='\0'; СТРОКОВЫЕ МАССИВЫ Или сразу char s[5] = {'s','h','i','p','\0'}; char s[] = {'s','h','i','p','\0'}; // можно не задавать размер СТРОКОВЫЕ МАССИВЫ Можно проще: char s[5] = "ship"; // в этом случае null-символ будем добавлен автоматически Или char s[] = "ship"; СТРОКОВЫЕ МАССИВЫ Массив можно заполнить при помощи функции scanf: scanf("%s",s); Обратите внимание, что взятие адреса (&) здесь не требуется. Имя массива – это адрес самого первого элемента. Это константа, которая не может быть изменена программным путем. СТРОКОВЫЕ МАССИВЫ Для работы со строками используются специальные функции: gets(stroka); получает символы из стандартного устройства в/в (клавиатура) и помещает их в строковый массив. Когда будет нажата клавиша ENTER, в функцию передается символ перевода строки. Функция заменяет его на null-символ. Это гарантирует, что в символьном массиве окажется именно строка, а не просто набор значений. Никаких проверок размера массива не производится. СТРОКОВЫЕ МАССИВЫ Функция puts(stroka); отображает строку на экране. При этом null-символ заменяется на символ перевода строки. Эти функции симметричны. СТРОКОВЫЕ МАССИВЫ При использовании функции fgets() можно задавать максимальное число вводимых символов. Функция прекращает считывание из заданного файлового потока в тот момент, когда число считанных символов на 1 меньше заданного аргумента. fputs() выводит строку, не добавляя никаких переводов строки. char stroka[20]; fgets(stroka,20,stdin); fputs(stroka,stdout); qwerty qwerty 123456789012345678901234567890 1234567890123456789 СТРОКОВЫЕ МАССИВЫ Функция sprintf() – помещает форматированный вывод в строку. char str1[4] = "one"; char str2[10]; sprintf(str2,"***%s***",str1); СТРОКОВЫЕ МАССИВЫ Множество функций для работы со строками содержит библиотека <string.h>. strcpy(str1,"Good "); // длина строки не контролируется strcat(str1," morning!"); int i = strlen(str1); //без учета null-символа СТРОКОВЫЕ МАССИВЫ strncmp() char szstringA[] = "Вита", szstringB[]= "Вика"; int istringA_length, iresult = 0; istringA_length = strlen(szstringA); if(strlen(szstringB) >= strlen(szstringA)) iresult = strncmp(szstringA, szstringB, istringA_length); printf ("Строки %s совпадают",, iresult == 0 ? "" : "не "); return(0); } УКАЗАТЕЛИ УКАЗАТЕЛИ Указатель (pointer) - это переменная, содержащая адрес другой переменной, функции или объекта. Точнее - адрес первого байта. Это дает возможность косвенного доступа к этому объекту через указатель. int *pa, *pb, *pc; // объявление нескольких указателей одного типа Тип указателя должен совпадать с типом переменной, адрес которой он хранит. УКАЗАТЕЛИ Контроль за типом указателя в С более строгий, чем за типом переменной: int a = 5; float b = 3.4; a = b; // это возможно ----------------------------------------------- int *pa; float *pb; ... pa = pb; // ошибка УКАЗАТЕЛИ Нельзя использовать в программе указатель, значение которого не определено (но ошибки это не вызовет). Можно проинициализировать при объявлении: double *px = 0; // это указатель в никуда double *px = NULL; // тоже самое, но более грамотно double y; double *py = &y; // здесь адрес y УКАЗАТЕЛИ Операция & применима только к адресным выражениям (ℓ-выражения), так что конструкции вида &(x-1) и &3 незаконны. УКАЗАТЕЛИ Унарная операция * называется операцией разыменования (разадресации, операцией разрешения адреса). Эта операция извлекает значение по указанному адресу. z = *px + *py; ОПЕРАЦИИ С УКАЗАТЕЛЯМИ. Название Взятие адреса Разыменование Присваивание Инкремент Декремент Сложение Знак & * = ++ -+ Сложение с замещением Вычитание += – Вычитание с замещением –= Пояснение Получить адрес переменной Получить значение переменной по адресу Присвоить указателю адрес переменной или 0 Увеличить указатель на целое значение (на след. элемент массива) Уменьшить указатель на целое значение (на пред. элемент массива) Увеличить указатель на целое значение и присвоить другому указателю Увеличить существующий указатель на целое значение Уменьшить указатель на целое значение или на значение другого указателя, если оба указывают на один и тот же массив, и присвоить третьему указателю. Уменьшить указатель на целое значение. ОПЕРАЦИИ С УКАЗАТЕЛЯМИ. Название Отношения Выделение памяти Освобождение памяти Знак == != < <= > >= calloc, malloc, new free, delete Пояснение Сравнение указателей – истина или ложь Получить указатель на начало выделенного блока памяти Освободить выделенный недоступным блок памяти и сделать указатель УКАЗАТЕЛИ И МАССИВЫ Имя одномерного массива само по себе является указателем. int x[]= {1,2,3,4,5,6,7,8,9,10,11,12}; cout << x; // выведет какой-то адрес 0x0012FF50 int *px1 = &x[0]; int *px2 = x; // берем адрес первого элемента // не требуется использовать & УКАЗАТЕЛИ И МАССИВЫ Но это работает только с одномерными массивами int y[][2]= {1,2,3,4,5,6,7,8,9,10,11,12}; int *py = y; // так делать нельзя А вот так можно: int *py1 = &y[0][0]; int *py2 = y[0]; А это уже другой адрес: int y[][2]= {1,2,3,4,5,6,7,8,9,10,11,12}; int *py3 = y[1]; А что вернет *py3? (3) – нулевой элемент первой строки УКАЗАТЕЛЬ МОЖНО СКЛАДЫВАТЬ С ЦЕЛЫМ int x[]= {1,2,3,4,5,6,7,8,9,10,11,12}; cout << x + 5 << &x[5]; // один и тот же адрес cout << x[5] << *(x+5); // увидим 6 px++; // следующий элемент x 0x0012FF78 py--; // предыдущий элемент y 0x0012FF88 ОПЕРАЦИИ С УКАЗАТЕЛЯМИ Указатели можно сравнивать. px = &x[9]; py = &x[4]; px > py Указатели можно вычитать. px – py даст 5 py – px даст - 5 РАБОТА С МНОГОМЕРНЫМИ МАССИВАМИ int x[][3][2]= {1,2,3,4,5,6,7,8,9,10,11,12}; int *px = &x[0][0][0]; // вывод всех элементов массива for (int i = 0; i < 12; i++) cout << *(px++) << "\t"; РАБОТА СО СТРОКАМИ Строки интерпретируются как обычные массивы с размером каждого элемента в 1 символ (1 байт). Имя строки – это адрес. Вывод обычного массива: int s[10] = {1,2,3,4,5,6,7,8,9,10}; int *p = s; int i = 10; while (i--) cout << *(p++) << endl; 1 2 3 4 5 6 7 8 9 10 РАБОТА СО СТРОКАМИ Вывод такого же строкового массива: i char s[10] = "irina"; r char *p = s; i while (*p) n cout << *(p++) << endl; a РАБОТА СО СТРОКАМИ Но если убрать разыменование: char s[10] = "irina"; irina char *p = s; rina while (*p) ina cout << p++ << endl; na a УКАЗАТЕЛИ НА УКАЗАТЕЛИ В языке С есть возможность описать переменные-указатели, указывающие на другие указатели. int i = 10; int *pi = &i; int **ppi = &pi; int ***pppi = &ppi; УКАЗАТЕЛИ НА УКАЗАТЕЛИ Но нельзя написать &&i &(&i) Взять адрес можно только у переменной, а &i не является переменной. УКАЗАТЕЛИ НА КОНСТАНТУ Допустим, в программе имеется константа const double PI = 3.1415; Ясно дело, ее нельзя менять – на то и константа. Но что, если double *pPI = &PI; // так нельзя ++ *pPI; Отследить такое любому компилятору непросто. УКАЗАТЕЛИ НА КОНСТАНТУ Поэтому компилятор запрещает присваивать адреса констант обычным указателям. const double *pPI = &PI; //указатель на константу обратите внимание – const стоит перед типом double (именно значение double нельзя менять) *pPI = 3.14159; // ошибка УКАЗАТЕЛИ НА КОНСТАНТУ Но сам указатель – не константа, его можно менять. const double E = 2.71828; pPI = &E; double g = 9.8; // можно и без const pPI = &g; УКАЗАТЕЛИ НА КОНСТАНТУ Адрес константного объекта присваивается только указателю на константу. Вместе с тем, такому указателю может быть присвоен и адрес обычной переменной: Хотя g в примере выше и не является константой, компилятор не допустит изменения переменной g через pPI. (Опять-таки потому, что он не в состоянии определить, адрес какого объекта может содержать указатель в произвольный момент выполнения программы.) КОНСТАНТНЫЕ УКАЗАТЕЛИ Существуют и константные указатели. (Обратите внимание на разницу между константным указателем и указателем на константу!). Константный указатель может адресовать как константу, так и переменную. Например: int Nomer = 0; int *const pNomer = &Nomer; Здесь pNomer – константный указатель на неконстантный объект. Это значит, что мы не можем присвоить ему адрес другого объекта, хотя сам объект допускает модификацию. (Квалификатор const стоит перед именем pNomer – именно эту переменную нельзя изменять) КОНСТАНТНЫЕ УКАЗАТЕЛИ Вот как мог бы быть использован указатель pNomer: pNomer = &PI; // ошибка – нельзя изменять сам указатель *pNomer = 100; // все в порядке – можно изменять значение по адресу КОНСТАНТНЫЙ УКАЗАТЕЛЬ НА КОНСТАНТУ Константный указатель на константу является объединением двух рассмотренных случаев. const double *const pi_ptr = &pi; Ни значение объекта, на который указывает pi_ptr, ни значение самого указателя не может быть изменено в программе. ПРИМЕРЫ int i; int j = -1; const int I1; const int J1 = j; J1 = 5; int * const pI1; int * const pJ2 = &j; int * const pJ1 = &J1; pJ2 = &i; *pJ2 = 5; ПРИМЕРЫ int i; int j = -1; // переменная i // переменная j проинициализир. const int I1; // ошибка!! константа – нужно инициализировать const int J1 = j; // ошибка!! константа иниц. переменной J1 = 5; //ошибка!! константу менять нельзя int * const pI1; // ошибка!!константный указатель- нет инициал. int * const pJ2 = &j; // иниц.адресом обычной переменной int * const pJ1 = &J1; //ошибка!! иниц.адресом константы (константа - только сам указатель) pJ2 = &i; // ошибка!! сам указатель менять нельзя *pJ2 = 5; // а значение по нему - менять можно ПРИМЕРЫ const int *pJ3; pJ3 = &J1; pJ3 = &i; *pJ3 = 8; const int * const pI4; const int * const pI5= &i; const int * const pJ4= &J1; pJ4 = &J1; *pJ4 = 8; ПРИМЕРЫ const int *pJ3; // указатель на константу – можно инициализировать позже pJ3 = &J1; // сам указатель можно менять pJ3 = &i; // не обязательно на адрес константы *pJ3 = 8; //ошибка! значение по указателю менять нельзя (даже если это просто i) const int * const pI4; // ошибка! конст. указатель на константу (нужно инициализ.) const int * const pI5= &i; // ошибка! и не чем-нибудь, а адресом константы const int * const pJ4= &J1; // pJ4 = &J1; // ошибка! менять нельзя *pJ4 = 8; // ошибка! тоже нельзя УКАЗАТЕЛИ НА VOID void *pv = NULL; int a = 5,*pa =&a; pa = pv; // ошибка pv = pa; pv++; // ошибка cout << pa; //адрес cout << pv; // адрес cout << *pa; //5 cout << *pv; // ошибка ДИНАМИЧЕСКОЕ РАСПРЕДЕЛЕНИЕ ПАМЯТИ При компиляции программы на С память компьютера, выделенная программе, разбивается на 4 области: • область кода; • область глобальных данных; • область стека (stack); • динамическая область (heap – куча). ДИНАМИЧЕСКОЕ РАСПРЕДЕЛЕНИЕ ПАМЯТИ Когда объявляются локальные переменные, они создаются в стеке, и при этом указатель стека двигается вниз. Когда область действия переменных заканчивается (выход из функции - локальная переменная больше не нужна), память автоматически освобождается путем смещения указателя стека вверх. Размер стековой памяти должен быть известен при компиляции. Стек занимает нижнюю часть области программы и растет вниз. ДИНАМИЧЕСКОЕ РАСПРЕДЕЛЕНИЕ ПАМЯТИ В программе возможно существование переменных, размер которых при компиляции неизвестен. Для них необходимо самостоятельно выделять память из свободной области. Свободная область занимает верхнюю часть памяти программы и растет вверх. Эти данные не освобождаются автоматически (вопрос – видны ли указатели?). Поэтому не забывайте освобождать ненужную динамическую память. ДИНАМИЧЕСКОЕ РАСПРЕДЕЛЕНИЕ ПАМЯТИ Функции С по захвату и освобождению памяти void *calloc(size_t nmemb, size_t size); void *malloc(size_t size); calloc() распределяет память для массива размером nmemb, каждый элемент которого равен size байтов, и возвращает указатель на распределенную память. Память при этом "очищается". malloc() распределяет size байтов и возвращает указатель на распределенную память. Память при этом не "очищается". ДИНАМИЧЕСКОЕ РАСПРЕДЕЛЕНИЕ ПАМЯТИ Функции С по захвату и освобождению памяти void free(void *ptr); free() освобождает место в памяти, на которое указывает ptr, возвращенный, по-видимому, предшествующим вызовом функций malloc(), calloc(). Иначе (или если уже вызывался free(ptr)) дальнейший ход событий непредсказуем. Если ptr равен NULL, то не выполняется никаких действий. ДИНАМИЧЕСКОЕ РАСПРЕДЕЛЕНИЕ ПАМЯТИ Функции С++: c помощью операции new : int *n = new int; int *m = new int (10); float *pf = new float (-3.5); Освобождение памяти: delete pf; ДИНАМИЧЕСКОЕ РАСПРЕДЕЛЕНИЕ ПАМЯТИ Становится возможным создавать массивы, размер которых становится известен лишь в процессе выполнения программы. указатель = new тип_массива [размер]; delete [] указатель; int i = 10; int m[i]; // так нельзя int *pm; pm = new int[i]; // так можно delete [] pm; // когда массив уже не нужен ДИНАМИЧЕСКОЕ РАСПРЕДЕЛЕНИЕ ПАМЯТИ //пример работы с динамическим массивом int size; cout << "Введите размер массива "; cin >> size; int *pmassiv = new int[size]; for (int i=0; i < size; i++) { cout << "Введите элемент "; cin >> pmassiv[i]; } ФУНКЦИИ СТАНДАРТНЫЕ МАТЕМАТИЧЕСКИЕ ФУНКЦИИ Подключение мат. библиотеки #define _USE_MATH_DEFINES // for C++ #include <cmath> #define _USE_MATH_DEFINES // for C #include <math.h> ИСПОЛЬЗОВАНИЕ КОНСТАНТ #define _USE_MATH_DEFINES // for C #include <math.h> #include <iostream> using namespace std; void main() { cout << M_PI << endl; cout << M_E << endl; cout << M_SQRT2 << endl; double r = 1.; cout << 4./3 * M_PI * pow(r,3) << endl; } ПОНЯТИЕ ФУНКЦИИ Функция —это поименованная часть программы, которая может вызываться из других частей программы столько раз, сколько необходимо. Функция может возвращать некоторое значение. Функции – это основные единицы построения программ при процедурном программировании на языке Си++. Функции используются для того, чтобы организовать программу в виде совокупности небольших и не зависящих друг от друга частей. Она инкапсулирует алгоритм или набор алгоритмов, применяемых к некоторому набору данных. ПОНЯТИЕ ФУНКЦИИ В языке "C" функции эквивалентны подпрограммам или процедурам в других языках программирования. Функции дают удобный способ заключения некоторой части вычислений в черный ящик, который в дальнейшем можно использовать, не интересуясь его внутренним содержанием. Использование функций является фактически единственным способом справиться с потенциальной сложностью больших программ. ПОНЯТИЕ ФУНКЦИИ Если функции организованы должным образом, то можно игнорировать то, как делается работа; достаточно знание того, что делается. Язык "C" разработан таким образом, чтобы сделать использование функций легким, удобным и эффективным. Вам будут часто встречаться функции длиной всего в несколько строчек, вызываемые только один раз, и они используются только потому, что это проясняет некоторую часть программы. ПОНЯТИЕ ФУНКЦИИ Функция не может быть определена в другой функции. ПОНЯТИЕ ФУНКЦИИ С использованием функции связаны 3 понятия - определение функции, объявление функции (прототип) и вызов функции. ПРОТОТИП ФУНКЦИИ Прежде всего, функцию необходимо объявить. Объявление функции, аналогично объявлению переменной, определяет имя функции и ее тип – типы и количество ее аргументов и тип возвращаемого значения. double sqrt(double x); //функция sqrt с аргументом – double и результатом double int sum(int a, int b, int c); // функция от 3 целых аргументов возвращает целое ПРОТОТИП Имена параметров в прототипе функции не обязательны, их область видимости – только сам прототип: double sqrt(double); //функция sqrt с аргументом – double и результатом double int sum(int, int, int); // функция от 3 целых аргументов возвращает целое ПРОТОТИП Прототип функции аналогичен заголовку (первой строке в определении), но заканчивается точкой с запятой. После того, как функция объявлена, ее можно использовать в выражениях. Разные компиляторы по-разному относятся к необходимости прототипов. Часто в литературе можно встретить утверждение, что функцию ОБЯЗАТЕЛЬНО нужно объявлять. Но на практике часто этого не делают. ПРОТОТИП Если функция описана после вызова, ее нужно предварительно объявить (обычно, в начале программы). Если функция описана раньше – можно обойтись без объявления. Однако, хорошее правило – объявлять все используемые функции в начале. (Речь пока идет об одномодульной программе). ОПРЕДЕЛЕНИЕ И ВЫЗОВ ФУНКЦИИ тип имя ( список описаний аргументов ){ операторы } Здесь имя - это имя функции (придуманный вами идентификатор); тип - тип возвращаемого функцией значения; операторы в фигурных скобках { } – тело функции. Аргументы в списке описаний называют формальными параметрами. ОПРЕДЕЛЕНИЕ Например, функция, находящая и возвращающая максимальное значение из двух целых величин a и b определяется так: int max(int a, int b) { return(a >= b) ? a : b; } Это определение говорит о том, что функция с именем max имеет два целых аргумента и возвращает целое значение. Если функция действительно должна возвращать значение какого-либо типа, то в ее теле обязательно должен присутствовать оператор return выражение; при выполнении этого оператора выполнение функции прекращается, управление передается в функцию, вызывающую данную функцию, а значением функции будет значение выражения. ОПРЕДЕЛЕНИЕ int max(int a, int b) { return (a >= b) ? a : b; } void main( ) { int i = 2, j = 3; int c = max( i, j ); cout<<" max= "<< c<< "\n"; c = max(i * i, j) * max(5, i - j); cout << " max= " << c << "\n"; } В этой программе приведено определение функции max и 3 обращения к ней. При обращении указывается имя функции и в круглых скобках список фактических параметров. ОПРЕДЕЛЕНИЕ Если у функции нет формальных параметров, то она определяется, например, так: double f(void){тело функции}; или, эквивалентно, double f() {тело функции}; Обращаются в программе к этой функции, например, так (скобки обязательны): a = b*f() + c; ФУНКЦИЯ, НЕ ВОЗВРАЩАЮЩАЯ ЗНАЧЕНИЕ Функция может и не возвращать никакого значения. В этом случае ее определение таково: void имя (список описаний аргументов){операторы} Вызов такой функции имеет вид: имя (список фактических аргументов); ФУНКЦИЯ, НЕ ВОЗВРАЩАЮЩАЯ ЗНАЧЕНИЕ Выполнение функции, не возвращающей никакого значения, прекращается оператором return без следующего за ним выражения. Выполнение такой функции и возврат из нее в вызывающую функцию происходит также и в случае, если при выполнении тела функции произошел переход на самую последнюю закрывающую фигурную скобку этой функции. ФУНКЦИЯ, НЕ ВОЗВРАЩАЮЩАЯ ЗНАЧЕНИЕ В качестве примера приведем функцию печати целого числа. void pr (int a) { printf("a = %d",a); } void main() { int i = 5; pr(i); }