Принципы программирования на языке С основаны на понятии функции. Например, к системным функциям относятся printf(), scanf(), gets(), putchar() и др. Функции – это строительные элементы языка С и то место, в котором выполняется вся работа программы [10.1].
Большие программы обычно состоят из нескольких пользовательских функций и ряда системных функций. Функция – самостоятельная единица программы. Функции повышают уровень модульности программы, облегчают ее чтение, внесение изменений и коррекцию ошибок.
В основе всех программ на языке программирования С лежат одни и те же фундаментальные элементы – функции [10.2]. В частности, функция main() является обязательной для любой программы. Во всех программах С определяется единая main(), служащая точкой входа в программу, то есть первой функцией, выполняемой после запуска программы [10.3].
Ни одна программа в языке С не может обойтись без функций.
Функция в языке С играет ту же роль, что и подпрограммы или процедуры в других языках [10.4]. Каждая функция языка С имеет имя и список аргументов. По соглашению, принятому в языке С, при записи имени функции после него ставятся круглые скобки [10.4]. Это соглашение позволяет легко отличить имена переменных от имен функций.
Рассмотрим модельный пример программы, в которой, кроме функции main(), содержатся еще три функции [10.4].
#include <stdio.h>
int main(void) /* Главная функция */
{ /* Начало тела функции */
function1(); /* вызов первой функции */
function2(); /* вызов второй функции */
function3(); /* вызов третьей функции */
} /* Конец тела функции main() */
/* Начало определения первой функции */
function1() { /* Начало тела первой функции */
/* Операторы первой функции */
/* Конец тела первой функции */
}
/* Начало определения второй функции */
function2()
{ /* Начало тела второй функции*/
/* Операторы второй функции */
/* Конец тела второй функции*/
}
/* Начало определения третьей функции */
function3()
{ /* Начало тела третьей функции*/
/* Операторы третьей функции */
/* Конец тела третьей функции*/
}
В условной (модельной) программе имеются четыре функции: main(), function1(), function2(), function3(). Эти функции не имеют аргументов. Позднее рассмотрим функции, которые имеют аргументы. Аргументы функции – это величины, которые передаются функции во время ее вызова. Аргумент, стоящий в операторе вызова функции, называется фактическим параметром. Аргументы, стоящие в заголовке функции, называются формальными параметрами. В языке С функция может возвращать значение в вызывающую программу посредством оператора return. Оператор возврата из функции в точку вызова имеет две формы [10.5]:
return; return выражение;
В общем виде функция выглядит следующим образом [10.1]:
возвр-тип имя-функции(список параметров)
{
Тело_функции
}
Тело_функции – это часть определения функции, ограниченная фигурными скобками и непосредственно размещенная вслед за заголовком функции. Тело функции может быть либо
Возвращаемый тип возвр-тип функции определяет тип данного, возвращаемого функцией. Например, это могут быть int, float, double и т.д. В случае, когда функция ничего не возвращает, ей присваивается тип void.
Функция может возвращать любой тип данных, за исключением массивов. Список параметров – это список, элементы которого отделяются друг от друга запятыми [10.1]. При вызове функции параметры принимают значения аргументов. Если функция без параметров, то такой пустой список можно указать в явном виде, поместив для этого внутри скобок ключевое слово void. Все параметры функции (входящие в список параметров) должны объявляться отдельно, причем для каждого из них надо указывать и тип, и имя. В общем виде список объявлений параметров должен выглядеть следующим образом [10.1]:
fun(тип имя_перем1, тип имя_перем2,..., тип имя_перем N)
Например:
fun(int i, int j, float k, char str1, char str2)
Рассмотрим пример программы с выводом сообщения не в главной функции main(), а в другой:
#include <stdio.h>
#include <conio.h>
void printMessage (void)
{
printf("\n\t hello, world\n");
return;
printf("\n\t 123\n");
}
int main(void)
{
printMessage();
printf("\n Press any key: ");
_getch();
return 0;
}
Результат выполнения программы показан на рис 10.1.
(рис 10.1) Вывод сообщения с помощью двух функцийПрограмма состоит из двух функций: printMessage() и main(). Выполнение программы всегда начинается с функции main(), которую называют еще главной. Внутри функции main() происходит вызов функции printMessage() без параметров. Когда происходит вызов функции, выполнение программы передается непосредственно вызванной функции. Внутри функции printMessage() выполняется только утверждение
printf("\n\t hello, world\n");
Несмотря на то, что в функции printMessage() есть еще одно утверждение printf("\n\t 123\n"), которое не выполняется, поскольку используется утверждение возврата ( return ) из функции.
В языке С функция введена как один из
Формальные параметры в определениях функций могут объявляться в форме прототипа [10.3]. Прототипы дают компилятору возможность тщательнее выполнять проверку типов аргументов [10.1]. Если используются прототипы, то компилятор может обнаружить любые сомнительные преобразования типов аргументов, необходимые при вызове функции, если тип ее параметров отличается от типов аргументов. Компилятор также обнаружит различия в количестве аргументов, использованных при вызове функции, и в количестве параметров функции.
В общем случае прототип функции должен выглядеть таким образом [10.1]:
тип имя_функции(тип имя_парам1, тип имя_парам2,..., тип им_парамN);
В приведенной выше программе прототип функции printMessage() не использовался, так как сама функция была объявлена до главной функции main(). Для переносимости С -кода в С ++ использование прототипа функции обязательно. Поэтому к хорошему стилю программирования относится использование прототипов функций, поскольку большие программы обычно состоят из нескольких функций, часто расположенных в различных файлах.
Вышеприведенная программа с использованием прототипа функции printMessage() будет выглядеть следующим образом:
#include <stdio.h>
#include <conio.h>
//void printMessage (void);//Прототип функции
int main(void) {
void printMessage (void); //Прототип функции
printMessage(); // Вызов функции
printf("\n Press any key: ");
_getch();
return 0;
}
// Определение функции
void printMessage (void)
{
printf("\n\t hello, world\n");
return;
printf("\n\t 123\n");
}
В листинге программы показаны две возможности использования прототипа функции printMessage(). При этом, сама функция printMessage() объявлена после функции main().
Формальные параметры функции определены в прототипе функции. При обращении к функции используются фактические параметры, называемые аргументами функции.
Список фактических параметров – это список выражений, количество которых равно количеству формальных параметров функции (исключение составляют функции с переменным числом параметров). Соответствие между формальными и фактическими параметрами устанавливается по их взаимному расположению в списках. Между формальными и фактическими параметрами должно быть соответствие по типам.
Синтаксис языка С предусматривает только один способ передачи параметров – передачу по значениям. Это означает, что формальные параметры функции локализованы в ней, т.е. недоступны вне определения функции и никакие операции над формальными параметрами в теле функции не изменяют значений фактических параметров [10.4].
Передача параметров по значению предусматривает следующие шаги [10.4]:
float формируются объекты типа double, а для параметров типов char и short int создаются объекты типа int. Если параметром является массив, то формируется указатель на начало этого массива, и он служит представлением массива-параметра в теле функции.Важным является момент, что объект вызывающей программы, использованный в качестве фактического параметра, не может быть изменен из тела функции. Для подобного изменения существует косвенная возможность изменять значения объектов вызывающей программы действиями в вызванной функции. Это становится возможным с помощью указателя (указателей), когда в вызываемую функцию передается адрес любого объекта из вызывающей программы. С помощью выполняемого в тексте функции
Массивы и строки также могут быть параметрами функции. В этом случае внутрь функции передается только адрес начала массива. Тогда можно в качестве параметра использовать указатель. Приведем два равноправных прототипа функций:
float fun(int n, float A[ ], float B[ ]); float fun(int n, float *a, float *b);
Поскольку массив передается в функцию как указатель, внутри функции можно изменять значения элементов массива–фактического параметра, определенного в вызывающей программе. Это возможно и при использовании индексирования, и при
В языке С существует возможность создавать функции, число аргументов которых не определено – функции с переменным числом аргументов [1]. При этом следует указать только количество аргументов. Пример прототипа функции с переменным числом аргументов:
int fun(int n, ј);
Многоточие (ј) в прототипе функции означает, что функция получает переменное число аргументов любого типа. Многоточие должно всегда находиться в конце списка параметров [1].
Макросы и определения заголовочного файла переменных аргументов stdarg.h (табл. 10.1) предоставляют программисту средства, необходимые для построения функций со списком аргументов переменной длины [1].
| Идентификатор | Объяснение |
|---|---|
va_list |
Тип, предназначающийся для хранения информации, необходимой макросам va_start, va_arg и va_end. Чтобы получить доступ к аргументам в списке переменной длины, необходимо объявить объект типа va_list |
va_start |
Макрос, который вызывается перед обращением к аргументам списка переменной длины. Он инициализирует объект, объявленный с помощью va_list, для использования макросами va_arg и va_end |
va_arg |
Макрос, расширяющийся до выражения со значением и типом следующего аргументов списке переменной длины. Каждый вызов его изменяет объект, объявленный с помощью va_list так, что объект указывает на следующий аргумент списка |
va_end |
Макрос обеспечивает нормальный возврат из функции, на список аргументов которой ссылается макрос va_start |
Примеры обращений к функции с
double k;
double v1 = 1.5,
v2 = 2.5,
v3 = 3.5;
// Первый вариант, где 3 – количество аргументов
k = fun(3,v1, v2, v3);
// Второй вариант, где 0.0 – завершающий нуль списка аргументов
k = fun(v1, v2, v3, 0.0);
Пример 1. Напишите программу сортировки по возрастанию заданного массива случайных чисел, равномерно распределенных в интервале [–6;6], с помощью вспомогательной функции.
Программный код решения примера:
#include <stdio.h>
#include <conio.h>
#include <stdlib.h>
#include <time.h>
#define MAX 10
// Прототип функции с формальными параметрами
void sort(double arr[], int n);
int main (void) {
double M[MAX];
int i, size = MAX;
long int L;
unsigned int some;
L = (long) time(NULL);
srand((unsigned)L);
for (i = 0; i < MAX; ++i)
M[i] = 12.0*rand()/RAND_MAX - 6.0;
printf("\n\t The original array:\n");
for (i = 0; i < MAX; ++i)
printf("\t%8.4f\n", M[i]);
// Обращение к функции с фактическими параметрами
sort(M, size);
// Распечатка отсортированного массива
printf("\n\t After sorting: \n");
for (i = 0; i < MAX; ++i) printf("\t%8.4f\n", M[i]);
printf("\n Press any key: ");
_getch();
return 0; }
// Вспомогательная функция сортировки
void sort(double Array[], int m) {
int i, j;
double tmp;
for (i = 0; i < m-1; ++i)
for (j = 0; j < m-i-1; ++j)
if (Array[j+1] < Array[j]) {
tmp = Array[j];
Array[j] = Array[j+1];
Array[j+1] = tmp;
}
}
Следует обратить внимание на имена формальных параметров в самой функции sort() и в ее прототипе: они имеют разные имена, но одинаковые типы. Фактические параметры или аргументы функции sort() в вызывающей программе (в теле функции main() ) имеют свои имена, не связанные с именами формальных параметров.
Заполнение массива случайными числами производится с помощью библиотечной функции rand() и RAND_MAX. Для рандомизации массива случайных чисел при каждом новом запуске программы используется библиотечная функция srand(), аргументом которой является системное время, формируемое библиотечной функцией time().
Возможный результат выполнения программы показан на рис 10.2.
(рис 10.2) Пример сортировки числового массиваЗадание 1
sort().Пример 2. Напишите программу вычисления квадратного корня числа по
При вычислении квадратного корня из числа следует помнить, что подкоренное выражение не должно быть отрицательным.
Алгоритм метода Ньютона–Рафсона для вычисления квадратного корня числа:
Шаг 1. Выбрать приблизительное значение 1.
Шаг 2. Если |
Шаг 3. Установить приблизительное значение, равное (x/
Шаг 4. Считать приблизительное значение квадратным корнем числа [7].
Программный код решения примера:
#include <stdio.h>
#include <conio.h>
// Функция абсолютного значения числа
double absValue(double x)
{
if (x < 0) x = -x;
return (x);
}
// Функция расчета квадратного корня из числа
double squareRoot (double x) {
const double epsilon = 0.000001;
double guess = 1.0; //Начальное приближение
while (absValue(guess*guess - x) >= epsilon)
guess = (x/guess + guess)/2.0;
return (guess);
}
int main (void)
{
double result, X;
printf("\n\t Enter a number: ");
scanf_s("%lf", X);
// Обращение к функции с фактическим параметром
result = squareRoot(X);
printf("\n\t Square root of \"%1.4f\" is: %1.8f\n", X, result);
printf("\n Press any key: ");
_getch();
return 0;
}
В программе используются три функции: main(), absValue(), squareRoot().
Вспомогательные функции расположены в определенном порядке: сначала функция absValue(), а потом функция squareRoot(). В функцию absValue() передается значение guess, которое вычисляется в функции squareRoot(), находящейся в условии оператора цикла while. Когда условие для оператора цикла будет ложным, т.е. когда значение корня будет меньше заданного числа epsilon, то полученное значение возвращается в вызывающую функцию main().
В программе использована переменная epsilon, определенная с помощью спецификатора const, что делает переменную неизменной. При таком объявлении компилятор определяет ее как константное значение. Таким переменным нельзя присваивать значения в программе, нельзя их инкрементировать или декрементировать [10.2].
Возможный результат выполнения программы показан на рис 10.3.
(рис 10.3) Результат итерационного извлечения квадратного корня из двухЗадание 2
squareRoot() – месяц своего рождения.main() введите проверку ввода неотрицательного числа.Пример 3. Напишите программу поиска максимального элемента среди минимальных элементов строк двухмерного целочисленного массива.
Условие примера соответствует поиску максимина в двухмерном массиве, т.е.
$$q_{ki}=\max_i\min_ja_{ij},$$где $$A_{ij}$$ – элементы матрицы $$А$$.
Алгоритм поиска максмина заключается в следующем [10.6]. Сначала предполагаем, что максимальным элементом является начальный элемент первой строки A[0][0], затем заменяем его минимальным элементом этой же строки, т.е. теперь минимальный элемент первой строки принимается за искомый максимум. Последовательно просматривая остальные строки, находим в каждой из них минимальный элемент. Если окажется, что минимальный элемент текущей строки больше текущего максимума, то он принимается за максимальный. Результатом поиска являются значение максимального элемента и его индексы (номер строки и номер столбца).
Программный код решения примера:
#include <stdio.h>
#include <conio.h>
#include <stdlib.h>
#include <time.h>
#define n 6
#define m 7
const int N = 100;
int main (void)
{
//Прототип функции
int MaxMin(int A[][m], int nn, int mm, int *imax, int *jmax);
int i, j, A[n][m], max, imax, jmax;
long int L;
L = (long) time(NULL);
srand((unsigned)L);
//Заполнение матрицы целыми случайными числами
for (i = 0; i < n; ++i)
for (j = 0; j < m; ++j)
A[i][j] = 2*N*rand()/RAND_MAX - N;
// Распечатка матрицы
printf("\n\t The original matrix A(%d*%d):\n\n", n, m);
for (i = 0; i < n; ++i){
printf("\t");
for (j = 0; j < m; ++j)
printf("%4d", A[i][j]);
printf("\n");
}
max = MaxMin(A, n, m, imax, jmax);
printf("\n\t Result: MaxMin = A[%d][%d] = %d\n", imax+1, jmax+1, max);
printf("\n Press any key: ");
_getch();
return 0;
}
// Функция поиска максмина
int MaxMin(int A[][m], int nn, int mm, int *imax, int *jmax)
{
int i, j, min, max, imin, jmin;
max = A[0][0];
*imax = 0; *jmax = 0;
for (j = 1; j < mm; ++j)
if (A[0][j] < max)
{max = A[0][j]; *imax = 0; *jmax = j;}
for (i = 1; i < nn; ++i) {
min = A[i][0]; imin = i; jmin = 0;
for (j = 1; j < mm; ++j)
if (A[i][j] < min)
{min = A[i][j]; imin = i; jmin = j;}
if (max < min)
{max = min; *imax = imin; *jmax = jmin;}
}
return max;
}
В программе поиск максмина выполняется на основе обычной индексации двухмерного массива. В объявлении функции MaxMin() для матрицы явно указывается только второй размер – количество столбцов. Указатели *imax, *jmax используются для передачи в вызывающую функцию индексов найденного return, т.е. return max. Вывод значений индексов максмина сделан традиционно в соответствии с принятой обычной математической индексацией.
Возможный результат выполнения программы показан на рис 10.4.
(рис 10.4) Максмин двухмерного массиваЗадание 3
MaxMin() (и ее прототипа) примените указатель на одномерный массив, например, int *a вместо int A[ ][m].Пример 4. Напишите программу поиска одного элемента в линейной неупорядоченной таблице по совпадению ключа на основе заграждающего элемента. Таблицу опишите в виде одномерного массива целых чисел [10.6].
В линейной таблице элементы располагаются друг за другом, т.е. для каждого элемента таблицы существуют отношения порядка [10.6]. Линейные таблицы в оперативной памяти компьютера отображаются в массивы или линейные связанные списки.
Поиск одного элемента в неупорядоченной таблице по заданному условию осуществляется последовательным просмотром элементов или до нахождения искомого элемента, т.е. до выполнения условия поиска, или до конца таблицы, если искомый элемент не найден. Возвращаемыми значениями являются адрес (индекс) элемента, или значение элемента, либо признак отсутствия элемента [10.6].
В случае применения заграждающего элемента последняя запись таблицы запоминается, а после завершения поиска восстанавливается в таблице. В последний элемент массива (когда таблица представляется в виде одномерного массива данных) заносится ключ поиска, и образуется так называемый заграждающий элемент. Теперь на каждом шаге поиска осуществляется только одно сравнение, а сам поиск продолжается до нахождения элемента с заданным ключом. Если искомого элемента в исходной таблице не было, то поиск закончится на заграждающем элементе. Использование заграждающего элемента в случае числовых ключей существенно сокращает количество сравнений.
Программный код решения примера:
#include <stdio.h>
#include <conio.h>
#define size 10
// Таблица числовых элементов
int A[size] = {1,-7,3,4,8,-5,-2,6,0,9};
int main (void) {
int i, ind, key;
int search(int A[], int n, int key);
printf("\n\t The original array:\n\t");
for (i = 0; i < size; ++i)
printf("%3d", A[i]);
// Поиск ключа соответствия
printf("\n\n\t Search for key matches.\n");
// Ввод ключа поиска
printf("\t Key in your search: ");
scanf_s("%i", key);;
ind = search(A, size, key);
if (ind == -1)
printf("\n\t Element \"%d\" could not be found\n", key);
else
printf("\n\t Element \"%d\" index is %d\n", key, ind+1);
printf("\n Press any key: ");
_getch();
return 0;
}
int search(int A[], int n, int key) {
int i = 0, r;
r = A[n-1];
A[n-1] = key;// Заграждающий элемент
while (A[i] != key)
i++;
A[n-1] = r; //Восстановление последнего элемента
if ((i == n-1) (r != key))
return -1; // Отсутствие элемента
elseF
return i; // Успешный поиск
}
В программе использовано внешнее объявление одномерного массива с инициализацией. Термин "внешний" здесь используется, чтобы подчеркнуть расположение объявление вне функций; напрямую с ключевым словом extern он не связан [10.4].
Возможный результат выполнения программы показан на рис 10.5.
(рис 10.5) Пример поиска элемента в массиве по заданному ключу Задание 4
Пример 5. Напишите программу выделения слов из символьной строки, когда слова в ней разделены пробелами, и поместите каждое слово в отдельной строке свободного (вспомогательного массива) [10.4].
Программный код решения примера:
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <conio.h>
#include <stdlib.h>
#include <string.h>
#define N 123
#define M 40
int wordstr(char *c, char **FA) {
int Ln, n = 0, i = 0;
char *wr;
char *tempstr;
Ln = strlen(c);
tempstr = (char *)calloc(Ln + 1, sizeof(char));
strcpy(tempstr, c);
wr = strtok(tempstr, " ");
Ln = strlen(wr);
FA[i] = (char *)calloc(Ln + 1, sizeof(char));
strcpy(FA[i++], wr);
n++;
while (wr = strtok(NULL, " ")) {
Ln = strlen(wr);
FA[i] = (char *) calloc(Ln + 1, sizeof(char));
strcpy(FA[i++], wr);
n++;
}
free(tempstr);
return n;
}
int main (void)
{
int i, n;
char *Farr[M];
char str[N];
printf("\n\t Enter the string of characters:\n >> ");
gets_s(str, N-1);
printf("\n\t The original string of characters:\n");
printf(" %s\n", str);
//Обращение к функции обработки строки
n = wordstr(str, Farr);
//Распечатка слов, помещенных в свободный массив
printf("\n\t The characters in a free array:");
for (i = 0; i < n; ++i)
printf("\n\t %s", Farr[i]);
//Освобождение памяти, занятую выделенными словами
for (i = 0; i < n; ++i)
{free(Farr[i]); Farr[i] = NULL;}
printf("\n\n Press any key: ");
_getch();
return 0;
}
В программе использована библиотечная функция strtok(), которая выделяет слова из строки. При этом для каждого выделенного слова получаем динамическую память с помощью функции calloc(). Адрес выделенной памяти помещаем в соответствующий указатель массива свободных строк FA[].
Обратите внимание, что в функции calloc() вводится число на единицу больше, чем длина строки. Это сделано для учета символа завершения строки (или одного слова), т.е. для символа "\0"
Для исключения предупреждений о безопасной работе с функциями , strtok() в MS Visual Studio в программе используется директива #define _CRT_SECURE_NO_WARNINGS.
Результат выполнения программы показан на рис 10.6.
(рис 10.6) Пример выделения слов из строкиЗадание 5
";" и точки "." в качестве разделителей слов.wordstr().Пример 6. Написать программу расчета суммы и среднего арифметического произвольного количества данных. В качестве ключевых слов для выбора варианта расчета принять mean (среднее) и sum (сумма).
В данном примере необходимо использовать функцию с переменным числом аргументов. Формула расчета среднего арифметического (m):
$$m=\frac{1}{n}\sum^n_{i=1}x_i,$$где $$n$$ – количество числовых данных, $$x_i$$ – текущее число.
Программный код решения примера
#include <conio.h>
#include <string.h>
#include <stdarg.h>
#include <float.h>
/* Прототип функции с переменным числом аргументов*/
double varfun(char str[], double v1, ...);
int main(void)
{
double v1 = 10.0,
v2 = 2.5,
control;
char str[121];
printf("\n Enter one of the key words 'mean' or 'sum': ");
gets_s(str, 120);
control = varfun(str, v1, v2, 7.5, 0.0);
if (control < DBL_MAX)
printf("\n Result: %lf\n", control);
else
printf("\n Invalid input keyword.\n");
printf("\n\n ... Press any key: ");
_getch();
return 0;
}
/* Определение функции с переменным числом аргументов */
double varfun( char str[], double v1, ...) {
/* Указатель на переменные списка аргументов */
va_list parg;
double sum = v1;
double value = 0.0; /* значение аргумента */
int count = 1; /* начальное количество аргументов */
int k, p;
char *ch = "mean";
char *ch2 = "sum";
// Лексиграфическое сравнение строк
k = strcmp(str, ch);
if ( !k ) // k == 0
p = 1;
else if ( k ) // k != 0
{
k = strcmp(str, ch2);
if ( !k )
p = 2;
else
p = 0;
}
if ( p == 1 )
{
va_start(parg,v1); /* инициализация указателя parg */
/* Просмотр списка аргументов*/
while( (value = va_arg(parg, double)) != 0.0)
{
// Суммирование числовых аргументов функции varfun()
sum += value;
count++;
}
/* Завершение процесса счтывания аргументов */
va_end(parg);
return sum/count;
}
if ( p == 2 )
{
va_start(parg,v1);
while( (value = va_arg(parg, double)) != 0.0)
{
sum += value;
count++;
}
va_end(parg);
return sum;
}
return DBL_MAX;
}
Возможный вариант выполнения программы показан на рис. 10.7.
(рис 10.7) Пример выполнения программы
Задание 6
void? К каким последствиям это может привести?Принципы программирования на языке С основаны на понятии функции. Например, к системным функциям относятся printf(), scanf(), gets(), putchar() и др. Функции – это строительные элементы языка С и то место, в котором выполняется вся работа программы [10.1].
Большие программы обычно состоят из нескольких пользовательских функций и ряда системных функций. Функция – самостоятельная единица программы. Функции повышают уровень модульности программы, облегчают ее чтение, внесение изменений и коррекцию ошибок.
В основе всех программ на языке программирования С лежат одни и те же фундаментальные элементы – функции [10.2]. В частности, функция main() является обязательной для любой программы. Во всех программах С определяется единая main(), служащая точкой входа в программу, то есть первой функцией, выполняемой после запуска программы [10.3].
Ни одна программа в языке С не может обойтись без функций.
Функция в языке С играет ту же роль, что и подпрограммы или процедуры в других языках [10.4]. Каждая функция языка С имеет имя и список аргументов. По соглашению, принятому в языке С, при записи имени функции после него ставятся круглые скобки [10.4]. Это соглашение позволяет легко отличить имена переменных от имен функций.
Рассмотрим модельный пример программы, в которой, кроме функции main(), содержатся еще три функции [10.4].
#include <stdio.h>
int main(void) /* Главная функция */
{ /* Начало тела функции */
function1(); /* вызов первой функции */
function2(); /* вызов второй функции */
function3(); /* вызов третьей функции */
} /* Конец тела функции main() */
/* Начало определения первой функции */
function1() { /* Начало тела первой функции */
/* Операторы первой функции */
/* Конец тела первой функции */
}
/* Начало определения второй функции */
function2()
{ /* Начало тела второй функции*/
/* Операторы второй функции */
/* Конец тела второй функции*/
}
/* Начало определения третьей функции */
function3()
{ /* Начало тела третьей функции*/
/* Операторы третьей функции */
/* Конец тела третьей функции*/
}
В условной (модельной) программе имеются четыре функции: main(), function1(), function2(), function3(). Эти функции не имеют аргументов. Позднее рассмотрим функции, которые имеют аргументы. Аргументы функции – это величины, которые передаются функции во время ее вызова. Аргумент, стоящий в операторе вызова функции, называется фактическим параметром. Аргументы, стоящие в заголовке функции, называются формальными параметрами. В языке С функция может возвращать значение в вызывающую программу посредством оператора return. Оператор возврата из функции в точку вызова имеет две формы [10.5]:
return; return выражение;
В общем виде функция выглядит следующим образом [10.1]:
возвр-тип имя-функции(список параметров)
{
Тело_функции
}
Тело_функции – это часть определения функции, ограниченная фигурными скобками и непосредственно размещенная вслед за заголовком функции. Тело функции может быть либо
Возвращаемый тип возвр-тип функции определяет тип данного, возвращаемого функцией. Например, это могут быть int, float, double и т.д. В случае, когда функция ничего не возвращает, ей присваивается тип void.
Функция может возвращать любой тип данных, за исключением массивов. Список параметров – это список, элементы которого отделяются друг от друга запятыми [10.1]. При вызове функции параметры принимают значения аргументов. Если функция без параметров, то такой пустой список можно указать в явном виде, поместив для этого внутри скобок ключевое слово void. Все параметры функции (входящие в список параметров) должны объявляться отдельно, причем для каждого из них надо указывать и тип, и имя. В общем виде список объявлений параметров должен выглядеть следующим образом [10.1]:
fun(тип имя_перем1, тип имя_перем2,..., тип имя_перем N)
Например:
fun(int i, int j, float k, char str1, char str2)
Рассмотрим пример программы с выводом сообщения не в главной функции main(), а в другой:
#include <stdio.h>
#include <conio.h>
void printMessage (void)
{
printf("\n\t hello, world\n");
return;
printf("\n\t 123\n");
}
int main(void)
{
printMessage();
printf("\n Press any key: ");
_getch();
return 0;
}
Результат выполнения программы показан на рис 10.1.
(рис 10.1) Вывод сообщения с помощью двух функцийПрограмма состоит из двух функций: printMessage() и main(). Выполнение программы всегда начинается с функции main(), которую называют еще главной. Внутри функции main() происходит вызов функции printMessage() без параметров. Когда происходит вызов функции, выполнение программы передается непосредственно вызванной функции. Внутри функции printMessage() выполняется только утверждение
printf("\n\t hello, world\n");
Несмотря на то, что в функции printMessage() есть еще одно утверждение printf("\n\t 123\n"), которое не выполняется, поскольку используется утверждение возврата ( return ) из функции.
В языке С функция введена как один из
Формальные параметры в определениях функций могут объявляться в форме прототипа [10.3]. Прототипы дают компилятору возможность тщательнее выполнять проверку типов аргументов [10.1]. Если используются прототипы, то компилятор может обнаружить любые сомнительные преобразования типов аргументов, необходимые при вызове функции, если тип ее параметров отличается от типов аргументов. Компилятор также обнаружит различия в количестве аргументов, использованных при вызове функции, и в количестве параметров функции.
В общем случае прототип функции должен выглядеть таким образом [10.1]:
тип имя_функции(тип имя_парам1, тип имя_парам2,..., тип им_парамN);
В приведенной выше программе прототип функции printMessage() не использовался, так как сама функция была объявлена до главной функции main(). Для переносимости С -кода в С ++ использование прототипа функции обязательно. Поэтому к хорошему стилю программирования относится использование прототипов функций, поскольку большие программы обычно состоят из нескольких функций, часто расположенных в различных файлах.
Вышеприведенная программа с использованием прототипа функции printMessage() будет выглядеть следующим образом:
#include <stdio.h>
#include <conio.h>
//void printMessage (void);//Прототип функции
int main(void) {
void printMessage (void); //Прототип функции
printMessage(); // Вызов функции
printf("\n Press any key: ");
_getch();
return 0;
}
// Определение функции
void printMessage (void)
{
printf("\n\t hello, world\n");
return;
printf("\n\t 123\n");
}
В листинге программы показаны две возможности использования прототипа функции printMessage(). При этом, сама функция printMessage() объявлена после функции main().
Формальные параметры функции определены в прототипе функции. При обращении к функции используются фактические параметры, называемые аргументами функции.
Список фактических параметров – это список выражений, количество которых равно количеству формальных параметров функции (исключение составляют функции с переменным числом параметров). Соответствие между формальными и фактическими параметрами устанавливается по их взаимному расположению в списках. Между формальными и фактическими параметрами должно быть соответствие по типам.
Синтаксис языка С предусматривает только один способ передачи параметров – передачу по значениям. Это означает, что формальные параметры функции локализованы в ней, т.е. недоступны вне определения функции и никакие операции над формальными параметрами в теле функции не изменяют значений фактических параметров [10.4].
Передача параметров по значению предусматривает следующие шаги [10.4]:
float формируются объекты типа double, а для параметров типов char и short int создаются объекты типа int. Если параметром является массив, то формируется указатель на начало этого массива, и он служит представлением массива-параметра в теле функции.Важным является момент, что объект вызывающей программы, использованный в качестве фактического параметра, не может быть изменен из тела функции. Для подобного изменения существует косвенная возможность изменять значения объектов вызывающей программы действиями в вызванной функции. Это становится возможным с помощью указателя (указателей), когда в вызываемую функцию передается адрес любого объекта из вызывающей программы. С помощью выполняемого в тексте функции
Массивы и строки также могут быть параметрами функции. В этом случае внутрь функции передается только адрес начала массива. Тогда можно в качестве параметра использовать указатель. Приведем два равноправных прототипа функций:
float fun(int n, float A[ ], float B[ ]); float fun(int n, float *a, float *b);
Поскольку массив передается в функцию как указатель, внутри функции можно изменять значения элементов массива–фактического параметра, определенного в вызывающей программе. Это возможно и при использовании индексирования, и при
В языке С существует возможность создавать функции, число аргументов которых не определено – функции с переменным числом аргументов [1]. При этом следует указать только количество аргументов. Пример прототипа функции с переменным числом аргументов:
int fun(int n, ј);
Многоточие (ј) в прототипе функции означает, что функция получает переменное число аргументов любого типа. Многоточие должно всегда находиться в конце списка параметров [1].
Макросы и определения заголовочного файла переменных аргументов stdarg.h (табл. 10.1) предоставляют программисту средства, необходимые для построения функций со списком аргументов переменной длины [1].
| Идентификатор | Объяснение |
|---|---|
va_list |
Тип, предназначающийся для хранения информации, необходимой макросам va_start, va_arg и va_end. Чтобы получить доступ к аргументам в списке переменной длины, необходимо объявить объект типа va_list |
va_start |
Макрос, который вызывается перед обращением к аргументам списка переменной длины. Он инициализирует объект, объявленный с помощью va_list, для использования макросами va_arg и va_end |
va_arg |
Макрос, расширяющийся до выражения со значением и типом следующего аргументов списке переменной длины. Каждый вызов его изменяет объект, объявленный с помощью va_list так, что объект указывает на следующий аргумент списка |
va_end |
Макрос обеспечивает нормальный возврат из функции, на список аргументов которой ссылается макрос va_start |
Примеры обращений к функции с
double k;
double v1 = 1.5,
v2 = 2.5,
v3 = 3.5;
// Первый вариант, где 3 – количество аргументов
k = fun(3,v1, v2, v3);
// Второй вариант, где 0.0 – завершающий нуль списка аргументов
k = fun(v1, v2, v3, 0.0);
Пример 1. Напишите программу сортировки по возрастанию заданного массива случайных чисел, равномерно распределенных в интервале [–6;6], с помощью вспомогательной функции.
Программный код решения примера:
#include <stdio.h>
#include <conio.h>
#include <stdlib.h>
#include <time.h>
#define MAX 10
// Прототип функции с формальными параметрами
void sort(double arr[], int n);
int main (void) {
double M[MAX];
int i, size = MAX;
long int L;
unsigned int some;
L = (long) time(NULL);
srand((unsigned)L);
for (i = 0; i < MAX; ++i)
M[i] = 12.0*rand()/RAND_MAX - 6.0;
printf("\n\t The original array:\n");
for (i = 0; i < MAX; ++i)
printf("\t%8.4f\n", M[i]);
// Обращение к функции с фактическими параметрами
sort(M, size);
// Распечатка отсортированного массива
printf("\n\t After sorting: \n");
for (i = 0; i < MAX; ++i) printf("\t%8.4f\n", M[i]);
printf("\n Press any key: ");
_getch();
return 0; }
// Вспомогательная функция сортировки
void sort(double Array[], int m) {
int i, j;
double tmp;
for (i = 0; i < m-1; ++i)
for (j = 0; j < m-i-1; ++j)
if (Array[j+1] < Array[j]) {
tmp = Array[j];
Array[j] = Array[j+1];
Array[j+1] = tmp;
}
}
Следует обратить внимание на имена формальных параметров в самой функции sort() и в ее прототипе: они имеют разные имена, но одинаковые типы. Фактические параметры или аргументы функции sort() в вызывающей программе (в теле функции main() ) имеют свои имена, не связанные с именами формальных параметров.
Заполнение массива случайными числами производится с помощью библиотечной функции rand() и RAND_MAX. Для рандомизации массива случайных чисел при каждом новом запуске программы используется библиотечная функция srand(), аргументом которой является системное время, формируемое библиотечной функцией time().
Возможный результат выполнения программы показан на рис 10.2.
(рис 10.2) Пример сортировки числового массиваЗадание 1
sort().Пример 2. Напишите программу вычисления квадратного корня числа по
При вычислении квадратного корня из числа следует помнить, что подкоренное выражение не должно быть отрицательным.
Алгоритм метода Ньютона–Рафсона для вычисления квадратного корня числа:
Шаг 1. Выбрать приблизительное значение 1.
Шаг 2. Если |
Шаг 3. Установить приблизительное значение, равное (x/
Шаг 4. Считать приблизительное значение квадратным корнем числа [7].
Программный код решения примера:
#include <stdio.h>
#include <conio.h>
// Функция абсолютного значения числа
double absValue(double x)
{
if (x < 0) x = -x;
return (x);
}
// Функция расчета квадратного корня из числа
double squareRoot (double x) {
const double epsilon = 0.000001;
double guess = 1.0; //Начальное приближение
while (absValue(guess*guess - x) >= epsilon)
guess = (x/guess + guess)/2.0;
return (guess);
}
int main (void)
{
double result, X;
printf("\n\t Enter a number: ");
scanf_s("%lf", X);
// Обращение к функции с фактическим параметром
result = squareRoot(X);
printf("\n\t Square root of \"%1.4f\" is: %1.8f\n", X, result);
printf("\n Press any key: ");
_getch();
return 0;
}
В программе используются три функции: main(), absValue(), squareRoot().
Вспомогательные функции расположены в определенном порядке: сначала функция absValue(), а потом функция squareRoot(). В функцию absValue() передается значение guess, которое вычисляется в функции squareRoot(), находящейся в условии оператора цикла while. Когда условие для оператора цикла будет ложным, т.е. когда значение корня будет меньше заданного числа epsilon, то полученное значение возвращается в вызывающую функцию main().
В программе использована переменная epsilon, определенная с помощью спецификатора const, что делает переменную неизменной. При таком объявлении компилятор определяет ее как константное значение. Таким переменным нельзя присваивать значения в программе, нельзя их инкрементировать или декрементировать [10.2].
Возможный результат выполнения программы показан на рис 10.3.
(рис 10.3) Результат итерационного извлечения квадратного корня из двухЗадание 2
squareRoot() – месяц своего рождения.main() введите проверку ввода неотрицательного числа.Пример 3. Напишите программу поиска максимального элемента среди минимальных элементов строк двухмерного целочисленного массива.
Условие примера соответствует поиску максимина в двухмерном массиве, т.е.
$$q_{ki}=\max_i\min_ja_{ij},$$где $$A_{ij}$$ – элементы матрицы $$А$$.
Алгоритм поиска максмина заключается в следующем [10.6]. Сначала предполагаем, что максимальным элементом является начальный элемент первой строки A[0][0], затем заменяем его минимальным элементом этой же строки, т.е. теперь минимальный элемент первой строки принимается за искомый максимум. Последовательно просматривая остальные строки, находим в каждой из них минимальный элемент. Если окажется, что минимальный элемент текущей строки больше текущего максимума, то он принимается за максимальный. Результатом поиска являются значение максимального элемента и его индексы (номер строки и номер столбца).
Программный код решения примера:
#include <stdio.h>
#include <conio.h>
#include <stdlib.h>
#include <time.h>
#define n 6
#define m 7
const int N = 100;
int main (void)
{
//Прототип функции
int MaxMin(int A[][m], int nn, int mm, int *imax, int *jmax);
int i, j, A[n][m], max, imax, jmax;
long int L;
L = (long) time(NULL);
srand((unsigned)L);
//Заполнение матрицы целыми случайными числами
for (i = 0; i < n; ++i)
for (j = 0; j < m; ++j)
A[i][j] = 2*N*rand()/RAND_MAX - N;
// Распечатка матрицы
printf("\n\t The original matrix A(%d*%d):\n\n", n, m);
for (i = 0; i < n; ++i){
printf("\t");
for (j = 0; j < m; ++j)
printf("%4d", A[i][j]);
printf("\n");
}
max = MaxMin(A, n, m, imax, jmax);
printf("\n\t Result: MaxMin = A[%d][%d] = %d\n", imax+1, jmax+1, max);
printf("\n Press any key: ");
_getch();
return 0;
}
// Функция поиска максмина
int MaxMin(int A[][m], int nn, int mm, int *imax, int *jmax)
{
int i, j, min, max, imin, jmin;
max = A[0][0];
*imax = 0; *jmax = 0;
for (j = 1; j < mm; ++j)
if (A[0][j] < max)
{max = A[0][j]; *imax = 0; *jmax = j;}
for (i = 1; i < nn; ++i) {
min = A[i][0]; imin = i; jmin = 0;
for (j = 1; j < mm; ++j)
if (A[i][j] < min)
{min = A[i][j]; imin = i; jmin = j;}
if (max < min)
{max = min; *imax = imin; *jmax = jmin;}
}
return max;
}
В программе поиск максмина выполняется на основе обычной индексации двухмерного массива. В объявлении функции MaxMin() для матрицы явно указывается только второй размер – количество столбцов. Указатели *imax, *jmax используются для передачи в вызывающую функцию индексов найденного return, т.е. return max. Вывод значений индексов максмина сделан традиционно в соответствии с принятой обычной математической индексацией.
Возможный результат выполнения программы показан на рис 10.4.
(рис 10.4) Максмин двухмерного массиваЗадание 3
MaxMin() (и ее прототипа) примените указатель на одномерный массив, например, int *a вместо int A[ ][m].Пример 4. Напишите программу поиска одного элемента в линейной неупорядоченной таблице по совпадению ключа на основе заграждающего элемента. Таблицу опишите в виде одномерного массива целых чисел [10.6].
В линейной таблице элементы располагаются друг за другом, т.е. для каждого элемента таблицы существуют отношения порядка [10.6]. Линейные таблицы в оперативной памяти компьютера отображаются в массивы или линейные связанные списки.
Поиск одного элемента в неупорядоченной таблице по заданному условию осуществляется последовательным просмотром элементов или до нахождения искомого элемента, т.е. до выполнения условия поиска, или до конца таблицы, если искомый элемент не найден. Возвращаемыми значениями являются адрес (индекс) элемента, или значение элемента, либо признак отсутствия элемента [10.6].
В случае применения заграждающего элемента последняя запись таблицы запоминается, а после завершения поиска восстанавливается в таблице. В последний элемент массива (когда таблица представляется в виде одномерного массива данных) заносится ключ поиска, и образуется так называемый заграждающий элемент. Теперь на каждом шаге поиска осуществляется только одно сравнение, а сам поиск продолжается до нахождения элемента с заданным ключом. Если искомого элемента в исходной таблице не было, то поиск закончится на заграждающем элементе. Использование заграждающего элемента в случае числовых ключей существенно сокращает количество сравнений.
Программный код решения примера:
#include <stdio.h>
#include <conio.h>
#define size 10
// Таблица числовых элементов
int A[size] = {1,-7,3,4,8,-5,-2,6,0,9};
int main (void) {
int i, ind, key;
int search(int A[], int n, int key);
printf("\n\t The original array:\n\t");
for (i = 0; i < size; ++i)
printf("%3d", A[i]);
// Поиск ключа соответствия
printf("\n\n\t Search for key matches.\n");
// Ввод ключа поиска
printf("\t Key in your search: ");
scanf_s("%i", key);;
ind = search(A, size, key);
if (ind == -1)
printf("\n\t Element \"%d\" could not be found\n", key);
else
printf("\n\t Element \"%d\" index is %d\n", key, ind+1);
printf("\n Press any key: ");
_getch();
return 0;
}
int search(int A[], int n, int key) {
int i = 0, r;
r = A[n-1];
A[n-1] = key;// Заграждающий элемент
while (A[i] != key)
i++;
A[n-1] = r; //Восстановление последнего элемента
if ((i == n-1) (r != key))
return -1; // Отсутствие элемента
elseF
return i; // Успешный поиск
}
В программе использовано внешнее объявление одномерного массива с инициализацией. Термин "внешний" здесь используется, чтобы подчеркнуть расположение объявление вне функций; напрямую с ключевым словом extern он не связан [10.4].
Возможный результат выполнения программы показан на рис 10.5.
(рис 10.5) Пример поиска элемента в массиве по заданному ключуЗадание 4
Пример 5. Напишите программу выделения слов из символьной строки, когда слова в ней разделены пробелами, и поместите каждое слово в отдельной строке свободного (вспомогательного массива) [10.4].
Программный код решения примера:
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <conio.h>
#include <stdlib.h>
#include <string.h>
#define N 123
#define M 40
int wordstr(char *c, char **FA) {
int Ln, n = 0, i = 0;
char *wr;
char *tempstr;
Ln = strlen(c);
tempstr = (char *)calloc(Ln + 1, sizeof(char));
strcpy(tempstr, c);
wr = strtok(tempstr, " ");
Ln = strlen(wr);
FA[i] = (char *)calloc(Ln + 1, sizeof(char));
strcpy(FA[i++], wr);
n++;
while (wr = strtok(NULL, " ")) {
Ln = strlen(wr);
FA[i] = (char *) calloc(Ln + 1, sizeof(char));
strcpy(FA[i++], wr);
n++;
}
free(tempstr);
return n;
}
int main (void)
{
int i, n;
char *Farr[M];
char str[N];
printf("\n\t Enter the string of characters:\n >> ");
gets_s(str, N-1);
printf("\n\t The original string of characters:\n");
printf(" %s\n", str);
//Обращение к функции обработки строки
n = wordstr(str, Farr);
//Распечатка слов, помещенных в свободный массив
printf("\n\t The characters in a free array:");
for (i = 0; i < n; ++i)
printf("\n\t %s", Farr[i]);
//Освобождение памяти, занятую выделенными словами
for (i = 0; i < n; ++i)
{free(Farr[i]); Farr[i] = NULL;}
printf("\n\n Press any key: ");
_getch();
return 0;
}
В программе использована библиотечная функция strtok(), которая выделяет слова из строки. При этом для каждого выделенного слова получаем динамическую память с помощью функции calloc(). Адрес выделенной памяти помещаем в соответствующий указатель массива свободных строк FA[].
Обратите внимание, что в функции calloc() вводится число на единицу больше, чем длина строки. Это сделано для учета символа завершения строки (или одного слова), т.е. для символа "\0"
Для исключения предупреждений о безопасной работе с функциями , strtok() в MS Visual Studio в программе используется директива #define _CRT_SECURE_NO_WARNINGS.
Результат выполнения программы показан на рис 10.6.
(рис 10.6) Пример выделения слов из строкиЗадание 5
";" и точки "." в качестве разделителей слов.wordstr().Пример 6. Написать программу расчета суммы и среднего арифметического произвольного количества данных. В качестве ключевых слов для выбора варианта расчета принять mean (среднее) и sum (сумма).
В данном примере необходимо использовать функцию с переменным числом аргументов. Формула расчета среднего арифметического (m):
$$m=\frac{1}{n}\sum^n_{i=1}x_i,$$где $$n$$ – количество числовых данных, $$x_i$$ – текущее число.
Программный код решения примера
#include <conio.h>
#include <string.h>
#include <stdarg.h>
#include <float.h>
/* Прототип функции с переменным числом аргументов*/
double varfun(char str[], double v1, ...);
int main(void)
{
double v1 = 10.0,
v2 = 2.5,
control;
char str[121];
printf("\n Enter one of the key words 'mean' or 'sum': ");
gets_s(str, 120);
control = varfun(str, v1, v2, 7.5, 0.0);
if (control < DBL_MAX)
printf("\n Result: %lf\n", control);
else
printf("\n Invalid input keyword.\n");
printf("\n\n ... Press any key: ");
_getch();
return 0;
}
/* Определение функции с переменным числом аргументов */
double varfun( char str[], double v1, ...) {
/* Указатель на переменные списка аргументов */
va_list parg;
double sum = v1;
double value = 0.0; /* значение аргумента */
int count = 1; /* начальное количество аргументов */
int k, p;
char *ch = "mean";
char *ch2 = "sum";
// Лексиграфическое сравнение строк
k = strcmp(str, ch);
if ( !k ) // k == 0
p = 1;
else if ( k ) // k != 0
{
k = strcmp(str, ch2);
if ( !k )
p = 2;
else
p = 0;
}
if ( p == 1 )
{
va_start(parg,v1); /* инициализация указателя parg */
/* Просмотр списка аргументов*/
while( (value = va_arg(parg, double)) != 0.0)
{
// Суммирование числовых аргументов функции varfun()
sum += value;
count++;
}
/* Завершение процесса счтывания аргументов */
va_end(parg);
return sum/count;
}
if ( p == 2 )
{
va_start(parg,v1);
while( (value = va_arg(parg, double)) != 0.0)
{
sum += value;
count++;
}
va_end(parg);
return sum;
}
return DBL_MAX;
}
Возможный вариант выполнения программы показан на рис. 10.7.
(рис 10.7) Пример выполнения программы
Задание 6
void? К каким последствиям это может привести?Для получения официальных документов о завершении программы дополнительного профессионального образования (удостоверения о повышении квалификации, дипломов о профессиональной переподготовке и MBA) необходимо предоставить:
Внимание! Вы можете не заказывать доставку бумажной версии официального документы, а скачать его в электронном виде и распечатать самостоятельно. Информация о выданном документе в течение 1 месяца загружается в Федеральную информационную систему «Федеральный реестр сведений о документах об образовании и (или) о квалификации, документах об обучении» - ФИС ФРДО.
Доступ на новый сайт осуществляется с использованием адреса электронной почты, который был указан вами при регистрации на "старом". Мы постарались перенести все ваши данные с прежнего ресурса, однако не исключена вероятность потери части информации.
При возникновении проблемы со входом, воспользуйтесь функцией сброса пароля
Если вы обнаружите несоответствия, пожалуйста, сообщите нам.