ЛЕКЦИИ И МАТЕРИАЛ ДЛЯ ЭЭБ и ИСИТвЭ / Методичка по С
.pdfprintf(“ var = %d\n”,++var); /* var = 14 */
}
void ff( void )
{
int var=55;
printf(“ var = %d\n”,var); /* var = 55 */
}
void fl( void )
{
printf(“ var = %d\n”,var); /* var = 5 */
}
5.5.Параметры и аргументы функций
Вязыке С все аргументы функции передаются по значению. При вызове функции в стеке выделяется место для формальных параметров функции, и в это место заносится значение фактического параметра, т.
е.значение параметра при вызове функции. Далее функция использует это значение, при этом она может изменить значение параметра. При выходе из функции измененные значения параметров теряются. Таким образом, в языке С вызванная функция не может изменить значения переменных, указанных в качестве фактических параметров при обращении к ней. Например, функция swap(), которая должна менять значение параметров местами, не будет этого делать:
/* Пример 39 */ void swap(int a, int b)
{
int tmp=a; a=b; b=tmp;
}
Но тем не менее функцию можно приспособить для изменения аргументов. Для этого необходимо в качестве параметра передавать адрес переменной, которую надо изменять, т. е. передавать указатель на переменную. Такой прием в языке С называется передачей параметра по ссылке. Вызванная функция должна описывать аргумент как ссылку и обращаться к фактической переменной косвенно, через эту ссылку. Если
61
в качестве аргумента берется имя массива, то передаваемое функции значение фактически есть адрес первого элемента массива.
Теперь функцию swap() можно описать следующим образом:
/* Пример 40 */
void swap(int *a, int *b)
{
int tmp=*a; *a = *b; *b=tmp;
}
Для иллюстрации использования этих двух способов передачи параметров приводим текст следующей программы:
# include < stdio.h > /* Пример 41 */
void swap( int a, int b); void swap1( int *a, int *b); void main( void )
{
int x=10, y=20;
printf(“ Сначала x = %d y = %d\n”,x,y); swap(x,y);
printf(“ Теперь x = %d y = %d\n”,x,y); printf(“ Ничего не изменилось \n”); swap1(&x,&y);
printf(“ Теперь x = %d y = %d\n”,x,y); printf(“ Значения поменялись \n“);
}
void swap(int a, int b)
{
int tmp=a; a=b; b=tmp;
}
void swap1(int *a, int *b)
{
int tmp=*a;
62
*a = *b; *b=tmp;
}
Результатом работы этой программы будет следующее: сначала x=10 y=20, потом x=20 y=10. Значения переменных x и y изменились, так как были переданы по ссылке. Еще одна особенность состоит в том, что при вызове функции swap() можно задать конкретные значения, например swap(10,20). Вызвать функцию swap1() в виде swap1(&10,&20) нельзя.
Если в качестве аргумента функции используется массив, есть лишь один способ – передача параметра по ссылке. Сделать это можно тремя способами:
func(int ar[10]); func(int ar[]); func(int *ar);
Все три способа дадут один и тот же результат.
Рассмотрим программу, где используется функция сортировки массива по возрастанию значений:
# include < stdio.h > /* Пример 42 */
void sort(int arr[], int n); void main( void )
{
int mass[10]={1,-6,21,3,-7,4,-12,9,5,17}; int i, size=10;
printf(“ до сортировки: \n“); for ( i=0; i<10; i++) printf(“%d”,mass[i]); printf(“\n”);
sort(mass,size);
printf(“ после сортировки: \n“); for ( i=0; i<10; i++) printf(“%d”,mass[i]);
}
void sort(int arr[], int n);
{
63
int i,j,tmp;
for ( i=0; i<n-2; i++) for ( j=i+1; j<n-1; j++) if ( arr[j] < arr[i] )
{
tmp=arr[i];
arr[i]=arr[j];
arr[j]=tmp;
}
}
В случае использования в качестве аргумента функции многомерного массива лучше всего указывать массив со всеми его размерами, например:
void mult( int a[10][10], int b[10][10], int c[10][10]);
5.6. Рекурсивные функции
Рекурсивная функция – это функция, в теле которой имеется вызов самой себя. Использование рекурсивных функций бывает удобным при программировании ряда задач, например вычислении факториала некоторого числа N
/* Пример 43 */ Int factorial(int n)
{
int a;
if ( n==1) return 1; a = factorial(n-1)*n; return a
}
Вызов функции при рекурсии не создает новую копию функции в памяти, а создает новые копии локальных переменных и параметров. Из рекурсивной функции необходимо предусмотреть выход, иначе это вызовет “зависание” вычислительной системы. Необходимо помнить, что при большом числе рекурсивных вызовов будет происходить быстрое заполнение стека, размер которого ограничен. Это может вызвать остановку программы. Поэтому, использование рекурсии как метода программирования, должно быть осторожным.
64
5.7. Указатель на функцию
На функцию, как и на другие объекты языка С можно создать указатель. Указатель pfunc на функцию, на функцию возвращающую значение типа type и имеющую параметры типа type1, type2, объявляется следующим образом:
type (*pfunc)(type1 t1,type2 t2);
По определению указатель на функцию содержит адрес первого байта или слова выполняемого кода функции. Над указателями на функцию запрещены арифметические операции. Использование указателя на функцию имеет несколько применений, в частности он используется, если необходимо передать функцию как параметр другой функции. Рассмотрим пример использования указателя на функцию:
#include < stdio.h >
#include < math.h > /* Пример 44 */ double f(double x); double f1(double x); double f2(double x);
double ff(double (*pf)(double x), double x); /* указатель на функцию в качестве аргумента*/
void main(void)
{
double z=2.3, y;
double (*ptrf)(double x); /* указатель на функцию */ ptrf=f; /* инициализация указателя */
y=(*ptrf)(z); /* вызов функции через указатель – 1-й способ*/ printf(“z=%f y=%f\n”,z,y);
y=ptrf(z); /* вызов функции через указатель – 2-й способ*/ printf(“z=%f y=%f\n”,z,y);
ptrf=sin; /* использование стандартной функции*/ y=ptrf(z);
printf(“z=%f sin(z)=%f\n”,z,y); y=(*ptrf)(4.6) /* можно таким образом */ printf(“z=%f y=%f\n”,z,y);
y=ff(f1,z); /* вызов ff с параметром f1*/ printf(“z=%f y=%f\n”,z,y);
65
y=ff(cos,z); /* другой вызов с использованием стандартной функции*/
printf(“z=%f y=%f\n”,z,y);
y=ff(ptrf,z); /* то же через указатель на функцию*/ printf(“z=%f y=%f\n”,z,y);
}
double f(double x)
{
puts(“ In f()”); return x;
}
double f1(double x)
{
puts(“ In f1()”); return x*x;
}
double f2(double x)
{
puts(“ In f2()”); return x*x*x;
}
double ff(double (*pf)(double x), double x)
/* Функция с указателем на функцию в качестве параметра*/
{
puts(“ In ff() %f\n”,pf(x)); return pf(x);
}
При использовании указателей на функции можно образовывать массивы указателей. Такая структура данных называется jump table.
Если определены объявления
int f0(void); int f1(void); int f2(void); int f3(void);
int(*jtable[](void)=(f0,f1,f2,f4); Соответствующую функцию можно вызвать val = (*jtable[i])(); или val = jtable[i]();
66
6. ТИПЫ ДАННЫХ, ОПРЕДЕЛЯЕМЫЕ ПОЛЬЗОВАТЕЛЕМ
6.1. Структура
Пользователь при написании программы на языке С может создать пять типов данных:
–структуры (structure),
–объединения (union),
–перечисляемый тип (enumeration),
–поля битов (bit fields),
–с помощью оператора typedef создать новое имя (псевдоним) для уже существующего типа.
Структура объединяет несколько переменных, возможно разного типа. Переменные, которые объединены структурой, называются членами, элементами или полями структуры. Пример определения структуры:
/* Пример 45 */ struct student{ char name[30]; int kurs;
char group[3]; int stip;
};
Объявлениеструктурыявляетсяоператором,ипоэтомупослетакогообъявления должна состоять точка с запятой. При этом надо понимать, что пока никакая переменная не объявлена, так как выделения памяти под переменную не произошло. Здесь под именем student задан вид структуры, иначе говоря, ее шаблон, и определен новый тип struct student. Для того чтобы объявить конкретные переменные типа struct student, можно записать
struct student stud1, stud2;
Теперь объявлены две переменные – stud1 и stud2. Компилятор автоматически выделит под них место в памяти компьютора. Под каждую
67
из переменных типа структуры выделяется непрерывный участок памяти. Задание шаблона структуры и объявление переменных может производится и в одном операторе
/* Пример 46 */ struct student{ char name[30]; int kurs;
char group[3]; int stip;
} stud1, stud2;
Здесь одновременно задается структура с именем student и объявляются переменные stud1 и stud2.
Доступ к конкретному элементу(полю) структуры осуществляется с помощью операции ‘точка’(dot). Например:
strcpy(stud1.name, “Петров И.И”);
Если необходимо содержимое третьего поля переменной stud2 структуры student напечатать, то надо записать:
printf(“%s”,stud2.group);
Структуры, как и переменные других типов, могут объединяться в массивы структур. Чтобы объявить массив структур, надо сначала задать шаблон структуры( используем имеющийся шаблон student), а затем объявить массив:
Struct student stud1kurs[200];
Этот оператор создаст в памяти 200 переменных типа структуры с шаблоном student и именами stud1kurs[0], stud1kurs[1] и т.д.
Для доступа к полю kurs 35-го элемента массива используем stud1kurs[34].kurs
Если объявлены две переменные типа структуры с одним шаблоном, можно сделать присваивание
stud1 = stud2;
При этом произойдет побитовое копирование каждого поля одной переменной в соответствующее поле другой переменной. В то же время нельзя использовать операцию присваивания переменных типа структуры, шаблоны которых описаны под разными именами, пусть даже совсем идентично. Тем не менее в этом случае присваивание возможно для отдельных полей, имеющих один и тот же тип данных.
68
Переменная типа структуры может быть глобальной, локальной переменной и формальным параметром. Можно, естественно, использовать структуру или ее поле как любую другую переменную в качестве параметра функции.
Например:
func1(right.a); или func2(&left.b);
Заметим, что & ставится перед именем структуры, а не перед именем поля. Можно в качестве формального параметра передать по значению всю структуру
#include <stdio.h> /* Пример 47 */ struct stru{
int x; char y; };
void ff( struct stru param); main(void)
{
struct stru arg; arg.x=3; arg.y=’a’; ff(arg);
}
void ff(struct stru param)
{
printf(“%d %c\n”,param.x,param.y);
}
Можно также создать указатель на структуру и передавать аргумент типа структуры по ссылке. Объявить указатель на структуру можно следующим образом:
struct stru *adr_pointer;
Здесь adr_pointer – переменная указатель на структуру struct stru. Если структура передается по значению, то все поля структуры заносят-
ся в стек. Если структура простая и содержит мало элементов, то это не так страшно. Если же структура в качестве своего поля содержит массив, то
69
стек может переполниться. При передаче по ссылке в стек заносится только адрес структуры. При этом копирования структуры не происходит, а также появляется возможность изменять содержимое полей структуры
/* Пример 48 */ struct complex{ float x;
float y; } c1,c2;
struct complex *a; a = &c1;
указателю а присваивается адрес переменной с1. Получить значение поля х переменной с1 можно так:
(*a).x;
Использование указателей на структуру встречается достаточно часто, например, при программировании задач по созданию и обработке динамических связанных структур данных( очереди, стеки, деревья). Поэтому, кроме способа получить значение поля структуры, используя (*a).x можно использовать другой. В языке С вводится специальная операция -> ( стрелка, arrow).
Операция стрелка употребляется вместо операции точка, когда мы хотим использовать значение поля структуры с применением переменной указателя. Вместо (*a).x можно использовать a-> x. Этот способ чаще всего и применяется.
Завершая разговор о структурах необходимо сказать, что в качестве полей структуры можно использовать массивы, структуры и массивы структур.
Пусть объявления переменных имеют вид
Struct addr{ Char city[34]; Char street[76]; Int house;
};
struct fulladdr{ struct addr addres; int room;
char name[48]; } a,b;
70
