- •Министерство образования Российской Федерации
- •Контрольные вопросы
- •Задания для выполнения
- •Варианты задания
- •Классы памяти. Массивы, операция индексации Цель работы
- •Контрольные вопросы
- •Варианты заданий
- •Массивы. Связь массивов и указателей Цель работы
- •Методические указания
- •Контрольные вопросы
- •Варианты заданий
- •Символьные строки
- •Контрольные вопросы
- •Варианты заданий
- •Функции. Основные правила использования функций
- •Контрольные вопросы
- •Варианты заданий
- •Функции. Использование массивов и функций в качестве формальных параметров
- •Контрольные вопросы
- •Варианты заданий
- •Контрольные вопросы
- •Варианты заданий
- •Файлы Цель работы
- •Методические указания
- •Функция
- •Контрольные вопросы
- •Варианты заданий
Функции. Использование массивов и функций в качестве формальных параметров
Цель работы
Изучение способов обработки массивов данных с помощью функций.
Методические указания
Передача массивов в качестве аргументов функции. Простой способ передачи массива функции демонстрирует пример 1:
#include <stdio.h>
void print(int* massiv); /*Прототип функции*/ void main(void) { intv[3],i; for (i=0; i<3; i++) v[i]=i;
print(v); /*Вызов функции и передача адреса массива*/ for (i-0; i<3; i++) printf("%d ",v[i]);
} void print(int mass[3]
{ int i; for(i=0;i<3;i++) printf("%d ",mass[i]++); printf("\n");}
В результате работы этой программы на экране увидим:
012 123.
До вызова функции print массив v содержал элементы 0, 1 и 2. Функция print приняла адрес массива v и присвоила этот адрес переменной mass. После вывода каждое значение массива mass (первая строка результатов) увеличивается на единицу. Поскольку переменная mass указывает на массив v, на единицу увеличились элементы массива v (вторая строка результатов). Другими словами, массивы v и mass расположены в одной и той же области памяти, и изменение в mass означает изменение в v.
В этом примере размер массива, обрабатываемого функцией print, должен быть известен до компиляции. Такая реализация необязательна: функции print нужен лишь начальный адрес массива, а количество элементов можно передать еще одним аргументом, как в примере 2:
void print(int* massiv, int lines); /*Прототип функции*/
void main(void)
{ int v[3],i; for (i=0; i<3; i++) v[i]=i;
print(v,3); /*Передаем адрес массива и количество элементов*/ for(i=0;i<3;i++)printf(“%d",v[i]);
}
void print(int mass[], int m)
{ int i; for (i = 0); i<m; i++) printf("%d ",mass[i]++); printf("\n");}
Результаты, выводимые на экран, не изменились. Но теперь, при изменении в функции main размера массива v нет необходимости что-либо менять в функции print. Как и в примере 1, массивы v и mass расположены в одной и той же области памяти, и изменение в mass означает изменение в v.
Общее в примерах 1 и 2 в том, что массив в функцию можно передать и как ссылку на его первый элемент. Например, в примере 2 массив в функцию print можно передать так:
Пример 3.
void print(int* mass, int m)
{ int i; for (int i = 0; i<m; i++) printf("%d ",mass[i]++); printf("\n");}
В случае многомерных массивов многое совпадает со случаем одномерного массива. Рассмотрим примеры с двумерным массивом:
Пример 4.
#include <stdio.h>
void print(int matrix[2][3]); /*Прототип функции*/
void main(void)
{ int v[2][3], i, j; for (i=0; i<2; i++) for (j=0; j<3; j++) v[i][j]=i+j;
print(v); /*Вызов функции и передача адреса массива*/
for (i=0; i<2; i++)
{ for (j=0;j<3;j++) printf("%d",v[i] [j]); printf("\n");}
} void print(int mass[2][3])
{ inti.j;
for (i=0; i<2; i++) { for (J=0;j<3;j++) printf("%d ",mass[i][j]++); printf("\n");}
}
Функция print выводит на экран элементы массива mass (первые две строки) и увеличивает их значения на единицу (последние две строки).
В следующих примерах размеры двумерного массива в функции print определяют с помощью аргументов.
Можно поступить так: сообщить компилятору, что первый параметр -массив строк по 3 элемента, а второй - количество строк.
Пример 5.
void print(int matrix[][3], int lines); /*Прототип функции*/
void main(void)
{ int v[2][3], i, j; for (i=0; i<2; i++) for (j=0; j<3; j++) v[i] [j] = i+j;
print(v,2); /*Передаем адрес массива и количество строк*/'
for (i=0; i<2; i++)
{ for (j=0; j<3; j++) printf("%d ",v[i][j]); printf("\n");}
}
void print(int mass[][3], int m) { int i, j;
for (i=0; i<m; i++)
{ for (j=0; j<3; j++) printf("%d ",mass[i]|j]++); printf("\n");}.
Результаты не изменились. Но теперь при изменении количества строк
матрицы v в функции main не нужно изменять что-либо в функции print.
Этот пример можно развивать далее. Если функция работает лишь с одной строкой двумерного массива, то можно передать адрес этой строки.
Пример 6. void print(int* massiv, int columns);
void main(void) { int v[2][3], i, j; for (i=0; i<2; i++) for (j=0; j<3; j++) v[i][j]=i+j;
for (H); i<2; i++)
print(v[i],3); /"'Передаем адрес строки и количество элементов*/ for (i=0; i<2; i++) { for (j=0; j<3; j++) printf("%d ",v[i][j]); printf("\n");}}
void print(int* mass, int n)
( int i; for (i-0; i<n; i++) printf("%d ",mass[i]++); printf("\n");}
Результаты, выводимые на экран, не изменились. Но теперь, при изменении любого размера матрицы v нет необходимости что-либо менять в функции print. При таком подходе мы накладываем ограничения на работу функции print: она работает только с одной строкой матрицы.
В примере 7 функция print свободна от таких ограничений и в качестве аргументов принимает адрес первого элемента, количество строк и столбцов матрицы:
Пример 7. void print(int** matrix, int lines, int columns);
void main(void) { int v[2][3], i, j; for (i=0; i<2; i++) for 0=0; j<3; j++) v[i](j]=i+J;
print ((int**)v, 2, 3);
for (i=0; i<2; i++) { for j=0;j<3;j++) printf(“%d",v[i][j]); printf("\n");}}
void print(int** mass, int m, int n)
{ int *p=(int*)mass, i, j;
for (i=0; i<m; i++) { for (j=0;j<n;j++) printf(“%d", р[i*п+j]++); printf("\n");}}
Здесь переменная р является указателем на первый элемент двумерного
массива. Элементы двумерного массива в памяти расположены построчно и непрерывно. То есть, с точки зрения размещения в памяти, двумерный массив представляет собой одномерный массив. Для доступа kj-му элементу i-ой строки достаточно вычислить относительный адрес i*n+j и отсчитать его от первого элемента p[i*n+j].
Результаты, выводимые на экран, не изменились. Но теперь, при изменении любого размера матрицы v нет необходимости что-либо менять в функции print, и эта функция свободна от предыдущих ограничений.
Описание имени типа директивой typedef
Директива typedef позволяет связать любой основной или производный тип с некоторым именем, а затем использовать это имя типа. Такое описание используется для создания более коротких или осмысленных имен типов.
Синтаксис описания имени типа:
typedef <описание_типа> <имя1>[,<имя2>...]:
typedef- это ключевое слово компилятора Си.
<описание_типа> может быть именем известного типа (даже из списка <имя1>[,<имя2>...]) или описанием типа. Например:
typedef int MyInt;
typedef MyInt MyInt;
Имена <имя1>[,<имя2>...] становятся синтаксически эквивалентными ключевому слову и именуют тип, указанный в <описание_типа>. Они не могут совпадать с именами переменных, формальных параметров, перечислимых констант и функций. Имена могут быть переопределены внутри блоков программы. Имена для типов указателя, структуры или совмещения могут быть объявлены прежде чем эти типы будут описаны. Имена типов можно использовать в описаниях переменных и функций как ссылки на этот тип.
Примеры:
typedef int WHOLE;
typedef void DRAW(int, int);
typedef WHOLE MASSIV[10];
В первом примере объявляется WHOLE как синоним для int.
Во втором примере представлен тип DRAW для функции не возвращающей значения и требующей два аргумента типа int. Это означает, например, что объявление DRAW box; эквивалентно объявлению void box(int, int);
В третьем примере описывается тип массива десяти целых элементов с именем MASSIV, которое можно использовать так:
MASSIV myList;
Указатели на функцию, их использование в качестве параметра функции
Любую функцию можно вызвать или получить ее адрес. Имя "функции, возвращающей ...", всегда преобразуется в "указатель на функцию, возвращающую ..." (т.е. имя функции это то же, что и указатель на функцию), если только оно не является аргументом вызова другой функции. Можно явно описать «указатель на функцию». Синтаксис описания такой переменной следующий:
<тип> (*<имя>)(<список_формальных_параметров>);
<тип> - это тип возвращаемого значения функций, на которые может затем указывать <имя>.
<список_формальных_параметров> - это список формальных параметров функций, на которые может затем указывать <имя>. Список задается по правилам объявления функций.
Пример.
int myfunc1( double*, int, int); /*Первая функция*/
int myfunc2( double*, int, int); /*Вторая функция*/
int (*myfunc)( double*, int, int); /*Указатель на такие функции*/
void main(void)
{ double x=2.3; int i=4, k=5;
myfunc=&myfunc1; /*Берем адрес вызываемой функции*/ i=(*myfunc)(&x, i, k); /*Вызываем эту функцию (myfunci)*/
}
Для вызова функции по указателю нужно выполнить операцию обратной ссылки *<имя>. Поскольку круглые скобки справа от имени имеют больший приоритет, чем звездочка слева, то операцию обратной ссылки нужно заключить в круглые скобки: (*<имя>). Правила передачи аргументов одинаковы для вызовов по имени функции и по указателю на функцию.
Удобно описать имя типа "указатель на функцию". Например,
typedef int (*MYFUNC)( double*, int, int);
Это имя типа можно использовать различным образом. Например,
MYFUNC myfunc, myfuncs [] =
{ myfunc1, myfunc2, myfunc3, myfunc4 };
означает, что myfunc является указателем на функции, a myfuncs – массивом указателей на функции. Можно описать и инициализировать указатели:
MYFUNC* funcs= myfuncs.
Переменная funcs необязательна для вызова функций. Для вызова можно пользоваться и массивом myfuncs. Но удобно одновременно хранить адрес вызываемой функции и адрес массива указателей на функции.
Вызов функции по индексу тогда выглядит таким образом.
(*funcs[i]) (&x, i, k); /*Вызываем функцию myfunci*/
Указатель на функции может быть формальным параметром функции. Например,
int func( char* с, int sz, MYFUNC f)
{…
if((*f)(&x,i,k)>0)...}
void main(void)
{ …
к=func("Вызов с первой функцией", 15, myfunc1);
}
Массив указателей на функции также может быть формальным параметром функции. Например,
typedef MYFUNC funcs[4];
int func( funcs);
функция func принимает массив из 4 указателей на функции и возвращает целое. Функции могут возвращать указатели на функции.
Например,
typedef int F(void);
F*g(...) {/*..*/} или MYFUNC fnctn(...);
Пример выполнения задания
Разработать программу вычисления суммы и разности двух чисел с помощью функций сложения и вычитания и указателей на эти функции. Решение может иметь следующий вид:
#include <stdio.h> /*Прототипы функций*/ double Add(double, double);
double Sub(double, double);
/"'Описание имени типа указателя на функции*/ typedef double (*MYFUNC)(double, double);
/*Прототип функции, которая вызывает указанную параметром функцию*/ void function(char*, double, double, MYFUNC f);
void main(void) { function("Сумма 4 и 2 равна:", 4., 2., Add);
function("Разность 4 и 2 равна:", 4., 2., Sub);
} double Add(double x, double y) { return x+y;}
double Sub(double x, double y) { return x-y;}
void function(char* str, double x, double y, MYFUNC f)
{ printf("%s %lf\n", str, (*f)(x,y));}
Результаты работы программы на экране отобразятся так:
Сумма 4 и 2 равна: 6 Разность 4 и 2 равна: 2.
