Если функция должна принимать аргументы, то в ее объявлении следует декларировать параметры, которые примут значения этих аргументов. Как видно из объявления следующей функции, объявления параметров стоят после имени функции.
/* Возвращает 1, если символ c входит в строку s; и 0 в противном случае. */ int is_in(char *s, char c) { while(*s) if(*s==c) return 1; else s++; return 0; }
Функция is_in() имеет два параметра: s и d. Если символ c входит в строку s, то эта функция возвращает 1, в противном случае она возвращает 0.
Хотя параметры выполняют специальную задачу, — принимают значения аргументов, передаваемых функции, — они все равно ведут себя так, как и другие локальные переменные. Формальным параметрам функции, например, можно присваивать какие-либо значения или использовать эти параметры в каких-либо выражениях.
В языках программирования имеется два способа передачи значений подпрограмме. Первый из них — вызов по значению. При его применении в формальный параметр подпрограммы копируется значение аргумента. В таком случае изменения параметра на аргумент не влияют.
Вторым способом передачи аргументов подпрограмме является вызов по ссылке. При его применении в параметр копируется адрес аргумента. Это значит, что, в отличие от вызова по значению, изменения значения параметра приводят к точно таким же изменениям значения аргумента.
За небольшим количеством исключений, в языке С для передачи аргументов используется вызов по значению. Обычно это означает, что код, находящийся внутри функции, не может изменять значений аргументов, которые использовались при вызове функции.
Проанализируйте следующую программу:
#include <stdio.h> int sqr(int x); int main(void) { int t=10; printf("%d %d", sqr(t), t); return 0; } int sqr(int x) { x = x*x; return(x); }
В этом примере в параметр х копируется 10 — значение аргумента для sqr(). Когда выполняется присваивание х=х*х, модифицируется только локальная переменная х. А значение переменной t, использованной в качестве аргумента при вызове sqr(), по-прежнему остается равным 10. Поэтому выведено будет следующее: 100.10.
Помните, что именно копия значения аргумента передается в функцию. А то, что происходит внутри функции, не влияет на значение переменной, которая была использована при вызове в качестве аргумента.
Хотя в С для передачи параметров применяется вызов по значению, можно создать вызов и по ссылке, передавая не сам аргумент, а указатель на него[1]. Так как функции передается адрес аргумента, то ее внутренний код в состоянии изменить значение этого аргумента, находящегося, между прочим, за пределами самой функции.
Указатель передается функции так, как и любой другой аргумент. Конечно, в таком случае параметр следует декларировать как один из типов указателей. Это можно увидеть на примере функции swap(), которая меняет местами значения двух целых переменных, на которые указывают аргументы этой функции:
void swap(int *x, int *y) { int temp; temp = *x; /* сохранить значение по адресу x */ *x = *y; /* поместить y в x */ *y = temp; /* поместить x в y */ }
Функция swap() может выполнять обмен значениями двух переменных, на которые указывают х и y, потому что передаются их адреса, а не значения. Внутри функции, используя стандартные операции с указателями, можно получить доступ к содержимому переменных и провести обмен их значений[2].
Помните, что swap() (или любую другую функцию, в которой используются параметры в виде указателей) необходимо вызывать вместе с адресами аргументов[3]. Следующая программа показывает, как надо правильно вызывать swap():
#include <stdio.h> void swap(int *x, int *y); int main(void) { int i, j; i = 10; j = 20; printf("i и j перед обменом значениями: %d %d\n", i, j); swap(&i, &j); /* передать адреса переменных i и j */ printf("i и j после обмена значениями: %d %d\n", i, j); return 0; } void swap(int *x, int *y) { int temp; temp = *x; /* сохранить значение по адресу x */ *x = *y; /* поместить y в x */ *y = temp; /* поместить x в y */ }
И вот что вывела эта программа:
i и j перед обменом значениями: 10 20 i и j после обмена значениями: 20 10
В программе переменной i присваивается значение 10, а переменной j — значение 20. Затем вызывается функция swap() с адресами этих переменных. (Для получения адреса каждой из переменных используется унарный оператор &.) Поэтому в swap() передаются адреса переменных i и j, а не их значения.
На заметку | Язык C++ при помощи параметров-ссылок дает возможность полностью автоматизировать вызов по ссылке. А в языке С параметры-ссылки не поддерживается |
Подробно о массивах рассказывалось в главе 4. В настоящем же разделе рассказывается о передаче массивов функциям в качестве аргументов. Этот вопрос рассматривается потому, что эта операция является исключением по отношению к обычной передаче параметров, выполняемой путем вызова по значению[4].
Когда в качестве аргумента функции используется массив, то функции передается его адрес. В этом и состоит исключение по отношению к правилу, которое гласит, что при передаче параметров используется вызов по значению. В случае передачи массива функции ее внутренний код работает с реальным содержимым этого массива и вполне может изменить это содержимое. Проанализируйте, например, функцию print_upper(), которая печатает свой строковый аргумент на верхнем регистре:
#include <stdio.h> #include <ctype.h> void print_upper(char *string); int main(void) { char s[80]; printf("Введите строку символов: "); gets(s); print_upper(s); printf("\ns теперь на верхнем регистре: %s", s); return 0; } /* Печатать строку на верхнем регистре. */ void print_upper(char *string) { register int t; for(t=0; string[t]; ++t) { string[t] = toupper(string[t]); putchar(string[t]); } }
Вот что будет выведено в случае фразы "This is a test." (это тест):
Введите строку символов: This is a test. THIS IS A TEST. s теперь в верхнем регистре: THIS IS A TEST.
Правда, эта программа не работает с символами кириллицы.
После вызова print_upper() содержимое массива s в main() переводится в символы верхнего регистра. Если вам это не нужно, программу можно написать следующим образом:
#include <stdio.h> #include <ctype.h> void print_upper(char *string); int main(void) { char s[80]; printf("Введите строку символов: "); gets(s); print_upper(s); printf("\ns не изменялась: %s", s); return 0; } void print_upper(char *string) { register int t; for(t=0; string[t]; ++t) putchar(toupper(string[t])); }
Вот какой на этот раз получится фраза "This is a test.":
Введите строку символов: This is a test. THIS IS A TEST. s не изменилась: This is a test.
На этот раз содержимое массива не изменилось, потому что внутри print_upper() не изменялись его значения.
Классическим примером передачи массивов в функции является стандартная библиотечная функция gets(). Хотя gets(), которая находится в вашей стандартной библиотеке, и более сложная, чем предлагаемая вам версия xgets(), но с помощью функции xgets() вы сможете получить представление о том, как работает gets().
/* Упрощенная версия стандартной библиотечной функции gets(). */ char *xgets(char *s) { char ch, *p; int t; p = s; /* xgets() возвращает указатель s */ for(t=0; t<80; ++t){ ch = getchar(); switch(ch) { case '\n': s[t] = '\0'; /* завершает строку */ return p; case '\b': if(t>0) t--; break; default: s[t] = ch; } } s[79] = '\0'; return p; }
Функцию xgets() следует вызывать с указателем char *. Им, конечно же, может быть имя символьного массива, которое по определению является указателем char *. В самом начале программы xgets() выполняется цикл for от 0 до 80. Это не даст вводить с клавиатуры строки, содержащие более 80 символов. При попытке ввода большего количества символов происходит возврат из функции. (В настоящей функции gets() такого ограничения нет.) Так как в языке С нет встроенной проверки границ, программист должен сам позаботиться, чтобы в любом массиве, используемом при вызове xgets(), помещалось не менее 80 символов. Когда символы вводятся с клавиатуры, они сразу записываются в строку. Если пользователь нажимает клавишу <Backspase>, то счетчик t уменьшается на 1, а из массива удаляется последний символ, введенный перед нажатием этой клавиши. Когда пользователь нажмет <ENTER>, в конец строки запишется нуль, т.е. признак конца строки. Так как массив, использованный для вызова xgets(), модифицируется, то при возврате из функции в нем будут находиться введенные пользователем символы.
[1]Конечно, при передаче указателя будет применен вызов по значению, и сам указатель внутри функции вы изменить не сможете. Однако для того объекта, на который указывает этот указатель, все произойдет так, будто этот объект был передан по ссылке. В некоторых языках программирования (например, в Алголе-60) имелись специальные средства, позволяющие уточнить, как следует передавать аргументы: по ссылке или по значению. Благодаря наличию указателей в С механизм передачи параметров удалось унифицировать. Параметры, не являющиеся массивами, в С всегда вызываются только по значению, но все, что в других языках вы можете сделать с объектом, получив ссылку на него (т.е. его адрес), вы можете сделать, получив значение указателя на этот объект (т.е. опять же, его адрес). Так что в языке С благодаря свойственной ему унификации передачи параметров никаких проблем не возникает. А вот в других языках трудности, связанные с отсутствием эффективных средств работы с указателями, встречаются довольно часто.
[2]Конечно, задача, решаемая этой программой, кажется тривиальной. Ну разве представляет трудность написать на каком-либо процедурном языке, например, на Алголе-60, процедуру, которая обменивает значения своих параметров. Ведь так просто написать: procedure swap(x, y); integer х, y; begin integer t; t:= x; x:=y; y:=t end. Но эта процедура работает неправильно, хотя вызов значений здесь происходит по ссылке! Причем сразу найти тестовый пример, демонстрирующий ошибочность этой процедуры, удается далеко не всем. Ведь в случае вызова swap(i, j) все работает правильно! А что будет в случае вызова swap(i, a[i])? Да и можно ли на Алголе-60 вообще написать требуемую процедуру? Если вы склоняетесь к отрицательному ответу, то это показывает, насколько все-таки необходимы указатели в развитых языках программирования. Если все же вы знаете правильный ответ, то обратите внимание на то, что требуемая процедура, хотя и не длинная, но все же содержит своего рода программистский фокус!
[3]Конечно, это просто программистский жаргон. На самом деле, конечно, аргументами являются именно адреса переменных, а не сами переменные. Просто в этом случае для краткости изложения программисты "делают вид", что вроде бы и в самом деле происходит передача значений по ссылке.
[4]Ведь при вызове по значению пришлось бы копировать весь массив!