Вызов функций с помощью массивов




Вызовы по значению и по ссылке

В языках программирования имеется два способа передачи значений подпрограмме. Первый из них — вызов по значению. При его применении в формальный параметр подпрограммы копируется значение аргумента. В таком случае изменения параметра на аргумент не влияют.

Вторым способом передачи аргументов подпрограмме является вызов по ссылке. При его применении в параметр копируется адрес аргумента. Это значит, что, в отличие от вызова по значению, изменения значения параметра приводят к точно таким же изменениям значения аргумента.

За небольшим количеством исключений, в языке С для передачи аргументов используется вызов по значению. Обычно это означает, что код, находящийся внутри функции, не может изменять значений аргументов, которые использовались при вызове функции.

Проанализируйте следующую программу:

#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]Ведь при вызове по значению пришлось бы копировать весь массив!

Иногда при запуске программы бывает полезно передать ей какую-либо информацию. Обычно такая информация передается функции main() с помощью аргументов командной строки. Аргумент командной строки — это информация, которая вводится в командной строке операционной системы вслед за именем программы. Например, чтобы запустить компиляцию программы, необходимо в командной строке после подсказки набрать примерно следующее:

cc имя_программы

имя_программы представляет собой аргумент командной строки, он указывает имя той программы, которую вы собираетесь компилировать.

Чтобы принять аргументы командной строки, используются два специальных встроенных аргумента: argc и argv. Параметр argc содержит количество аргументов в командной строке и является целым числом, причем он всегда не меньше 1, потому что первым аргументом считается имя программы. А параметр argv является указателем на массив указателей на строки. В этом массиве каждый элемент указывает на какой-либо аргумент командной строки. Все аргументы командной строки являются строковыми, поэтому преобразование каких бы то ни было чисел в нужный двоичный формат должно быть предусмотрено в программе при ее разработке.

Вот простой пример использования аргумента командной строки. На экран выводятся слово Привет и ваше имя, которое надо указать в виде аргумента командной строки.

#include <stdio.h>#include <stdlib.h> int main(int argc, char *argv[]){ if(argc!=2) { printf("Вы забыли ввести свое имя.\n"); exit(1); } printf("Привет %s", argv[1]); return 0;}

Если вы назвали эту программу name (имя) и ваше имя Том, то для запуска программы следует в командную строку ввести name Том. В результате выполнения программы на экране появится сообщение Привет, Том.

Во многих средах все аргументы командной строки необходимо отделять друг от друга пробелом или табуляцией. Запятые, точки с запятой и тому подобные символы разделителями не считаются. Например,

run Spot, run

состоит из трех символьных строк, в то время как

Эрик, Рик, Фред

представляет собой одну символьную строку — запятые, как правило, разделителями не считаются.

Если в строке имеются пробелы, то, чтобы из нее не получилось несколько аргументов, в некоторых средах эту строку можно заключать в двойные кавычки. В результате вся строка будет считаться одним аргументом. Чтобы подробнее узнать, как в вашей операционной системе задаются параметры командной строки, изучите документацию этой системы.

Очень важно правильно объявлять argv. Вот как это делают чаще всего:

char *argv[];

Пустые квадратные скобки указывают на то, что у массива неопределенная длина. Теперь получить доступ к отдельным аргументам можно с помощью индексации массива argv. Например, argv[0] указывает на первую символьную строку, которой всегда является имя программы; argv[1] указывает на первый аргумент и так далее.

Другим небольшим примером использования аргументов командной строки является приведенная далее программа countdown (счет в обратном порядке). Эта программа считает в обратном порядке, начиная с какого-либо значения (указанного в командной строке), и подает звуковой сигнал, когда доходит до 0. Обратите внимание, что первый аргумент, содержащий начальное значение, преобразуется в целое значение с помощью стандартной функции atoi(). Если вторым аргументом командной строки (а если считать аргументом имя программы, то третьим) является строка "display" (вывод на экран), то результат отсчета (в обратном порядке) будет выводиться на экран.

/* Программа счета в обратном порядке. */#include <stdio.h>#include <stdlib.h>#include <ctype.h>#include <string.h> int main(int argc, char *argv[]){ int disp, count; if(argc<2) { printf("В командной строке необходимо ввести число, с которого\n"); printf("начинается отсчет. Попробуйте снова.\n"); exit(1); } if(argc==3 &&!strcmp(argv[2], "display")) disp = 1; else disp = 0; for(count=atoi(argv[1]); count; --count) if(disp) printf("%d\n", count); putchar('\a'); /* здесь подается звуковой сигнал */ printf("Счет закончен"); return 0;}

Обратите внимание, если аргументы командной строки не будут указаны, то будет выведено сообщение об ошибке. В программах с аргументами командной строки часто делается следующее: в случае, когда пользователь запускает эти программы без ввода нужной информации, выводятся инструкции о том, как правильно указывать аргументы.

Чтобы получить доступ к отдельному символу одного из аргументов командной строки, введите в argv второй индекс. Например, следующая программа посимвольно выводит все аргументы, с которыми ее вызвали:

#include <stdio.h> int main(int argc, char *argv[]){ int t, i; for(t=0; t<argc; ++t) { i = 0; while(argv[t][i]) { putchar(argv[t][i]); ++i; } printf("\n"); } return 0;}

Помните, первый индекс argv обеспечивает доступ к строке, а второй индекс — доступ к ее отдельным символам.

Обычно argc и argv используют для того, чтобы передать программе начальные команды, которые понадобятся ей при запуске. Например, аргументы командной строки часто указывают такие данные, как имя файла, параметр или альтернативное поведение. Использование аргументов командной строки придает вашей программе "профессиональный внешний вид" и облегчает ее использование в пакетных файлах.

Имена argc и argv являются традиционными, но не обязательными. Эти два параметра в функции main() вы можете назвать как угодно. Кроме того, в некоторых компиляторах для main() могут поддерживаться-дополнительные аргументы, поэтому обязательно изучите документацию к вашему компилятору.

Когда для программы не требуются параметры командной строки, то чаще всего явно декларируют функцию main() как не имеющую параметров. В таком случае в списке параметров этой функции используют ключевое слово void.

Механизм использования return описан в главе 3. Как вы помните, там говорится, что этот оператор имеет два важных применения. Во-первых, он обеспечивает немедленный выход из функции, т.е. заставляет выполняющуюся программу передать управление коду, вызвавшему функцию. Во-вторых, этот оператор можно использовать для того, чтобы возвратить значение. В следующих разделах рассказывается, каким именно образом можно использовать оператор return.

Возврат из функции

Функция может завершать выполнение и осуществлять возврат в вызывающую программу двумя способами. Первый способ используется тогда, когда после выполнения последнего оператора в функции встречается закрывающая фигурная скобка (}). (Конечно, это просто жаргон, ведь в настоящем объектном коде фигурной скобки нет!) Например, функция pr_reverse() в приведенной ниже программе просто выводит на экран в обратном порядке строку Мне нравится С, а затем возвращает управление вызывающей программе.

#include <string.h>#include <stdio.h> void pr_reverse(char *s); int main(void){ pr_reverse("Мне нравится C"); return 0;} void pr_reverse(char *s){ register int t; for(t=strlen(s)-1; t>=0; t--) putchar(s[t]);}

Как только строка выведена на экран, функции pr_reverse() "делать больше нечего", поэтому она возвращает управление туда, откуда она была вызвана.

Но на практике не так уж много функций используют именно такой способ завершения выполнения. В большинстве функций для завершения выполнения используется оператор return — или потому, что необходимо вернуть значение, или чтобы сделать код функции проще и эффективнее.

В функции может быть несколько операторов return. Например, в следующей программе функция find_substr() возвращает начальную позицию подстроки в строке или же возвращает —1, если подстрока, наоборот, не найдена. В этой функции для упрощения кодирования используются два оператора return.

#include <stdio.h> int find_substr(char *s1, char *s2); int main(void){ if(find_substr("C - это забавно", "is")!= -1) printf("Подстрока найдена."); return 0;} /* Вернуть позицию первого, вхождения s2 в s1. */int find_substr(char *s1, char *s2){ register int t; char *p, *p2; for(t=0; s1[t]; t++) { p = &s1[t]; p2 = s2; while(*p2 && *p2==*p) { p++; p2++; } if(!*p2) return t; /* 1-й оператор return */ } return -1; /* 2-й оператор return */}

Возврат значений

Все функции, кроме тех, которые относятся к типу void, возвращают значение. Это значение указывается выражением в операторе return. Стандарт С89 допускает выполнение оператора return без указания выражения внутри функции, тип которой отличен от void. В этом случае все равно происходит возврат какого-нибудь произвольного значения. Но такое положение дел, мягко говоря, никуда не годится! Поэтому в Стандарте С99 (да и в C++) предусмотрено, что в функции, тип которой отличен от void, в операторе return необходимо обязательно указать возвращаемое значение. То есть, согласно С99, если для какой-либо функции указано, что она возвращает значение, то внутри этой функции у любого оператора return должно быть свое выражение. Однако если функция, тип которой отличен от void, выполняется до самого конца (то есть до закрывающей ее фигурной скобки), то возвращается произвольное (непредсказуемое с точки зрения разработчика программы!) значение. Хотя здесь нет синтаксической ошибки, это является серьезным упущением и таких ситуаций необходимо избегать.

Если функция не объявлена как имеющая тип void, она может использоваться как операнд в выражении. Поэтому каждое из следующих выражений является правильным:

x = power(y);if(max(x,y) > 100) printf("больше");for(ch=getchar(); isdigit(ch);)...;

Общепринятое правило гласит, что вызов функции не может находиться в левой части оператора присваивания. Выражение

swap(x,y) = 100; /* неправильное выражение */

является неправильным. Если компилятор С в какой-либо программе найдет такое выражение, то пометит его как ошибочное и программу компилировать не будет.

В программе можно использовать функции трех видов. Первый вид — простые вычисления. Эти функции предназначены для выполнения операций над своими аргументами и возвращают полученное в результате этих операций значение. Вычислительная функция является функцией "в чистом виде". В качестве примеров можно назвать стандартные библиотечные функции sqrt() и sin(), которые вычисляют квадратный корень и синус своего аргумента соответственно.

Второй вид включает в себя функции, которые обрабатывают информацию и возвращают значение, которое показывает, успешно ли была выполнена эта обработка. Примером является библиотечная функция fclose(), которая закрывает файл. Если операция закрытия была завершена успешно, функция возвращает 0, а в случае ошибки она возвращает EOF.

У функций последнего, третьего вида нет явно возвращаемых значений. В сущности, такие функции являются чисто процедурными и никаких значений выдавать не должны. Примером является exit(), которая прекращает выполнение программы. Все функции, которые не возвращают значение, должны объявляться как возвращающие значение типа void. Объявляя функцию как возвращающую значение типа void, вы запрещаете ее применение в выражениях, предотвращая таким образом случайное использование этой функции не по назначению.

Иногда функции, которые, казалось бы, фактически не выдают содержательный результат, все же возвращают какое-то значение. Например, printf() возвращает количество выведенных символов. Если бы нашлась такая программа, которая на самом деле проверяла бы это значение, то это было бы что-то необычное... Другими словами, хотя все функции, за исключением относящихся к типу void, возвращают значения, вовсе не нужно стремиться использовать эти значения во что бы то ни стало. Часто при обсуждении значений, возвращаемых функциями, возникает такой довольно распространенный вопрос: "Неужели не обязательно присваивать возвращенное значение какой-либо переменной? Не повиснет ли оно где-нибудь и не приведет ли это в дальнейшем к каким-либо неприятностям?" Отвечая на этот вопрос, повторим, что присваивание отнюдь не является обязательным, причем отсутствие его не станет причиной каких-либо неприятностей. Если возвращаемое значение не входит ни в один из операторов присваивания, то это значение будет просто отброшено. Отметим также, что такое отбрасывание значения встречается очень часто. Проанализируйте следующую программу, в которой используется функция mul():

#include <stdio.h> int mul(int a, int b); int main(void){ int x, y, z; x = 10; y = 20; z = mul(x, y); /* 1 */ printf("%d", mul(x,y)); /* 2 */ mul(x, y); /* 3 */ return 0;} int mul(int a, int b){ return a*b;}

В строке 1 значение, возвращаемое функцией mul(), присваивается переменной z. В строке 2 возвращаемое значение не присваивается, но используется функцией printf(). И наконец, в строке 3 возвращаемое значение теряется, потому что не присваивается никакой из переменных и не используется как часть какого-либо выражения.

Возвращаемые указатели

Хотя с функциями, которые возвращают указатели, обращаются так же, как и с любыми другими функциями, все же будет полезно познакомиться с некоторыми основными понятиями и рассмотреть соответствующий пример. Указатели не являются ни целыми, ни целыми без знака. Они являются адресами в памяти и относятся к особому типу данных. Такая особенность указателей определяется тем, что арифметика указателей (адресная арифметика) работает с учетом параметров базового типа. Например, если указателю на целое придать минимальное (ненулевое) приращение, то его текущее значение станет на четыре больше, чем предыдущее (при условии, что целые значения занимают 4 байта). Вообще говоря, каждый раз, когда значение указателя увеличивается (уменьшается) на минимальную величину, то он указывает на последующий (предыдущий) элемент, имеющий базовый тип указателя. Так как размеры разных типов данных могут быть разными, то компилятор должен знать тип данных, на которые может указывать указатель. Поэтому в объявлении функции, которая возвращает указатель, тип возвращаемого указателя должен декларироваться явно. Например, нельзя объявлять возвращаемый тип как int *, если возвращается указатель типа char *! Иногда (правда, крайне редко!) требуется, чтобы функция возвращала "универсальный" указатель, т.е. указатель, который может указывать на данные любого типа. Тогда тип результата функции следует определить как void *.

Чтобы функция могла возвратить указатель, она должна быть объявлена как возвращающая указатель на нужный тип. Например, следующая функция возвращает указатель на первое вхождение символа, присвоенного переменной с, в строку s. Если этого символа в строке нет, то возвращается указатель на символ конца строки ('0').

/* Возвращает указатель на первое вхождение c в s. */char *match(char c, char *s){ while(c!=*s && *s) s++; return(s);}

Вот небольшая программа, в которой используется функция match():

#include <stdio.h> char *match(char c, char *s); /* прототип */ int main(void){ char s[80], *p, ch; gets(s); ch = getchar(); p = match(ch, s); if(*p) /* символ найден */ printf("%s ", p); else printf("Символа нет."); return 0;}

Эта программа сначала считывает строку, а затем символ. Потом проводится поиск местонахождения символа в строке. При наличии символа в строке переменная p укажет на него, и программа выведет строку, начиная с найденного символа. Если символ в строке не найден, то p укажет на символ конца строки ('0'), причем *p будет представлять логическое значение ЛОЖЬ (false). В таком случае программа выведет сообщение Символа нет.

Функция типа void

Одним из применений ключевого слова void является явное объявление функций, которые не возвращают значений. Мы уже знаем, что такие функции не могут применяться в выражениях, и указание ключевого слова void предотвращает их случайное использование не по назначению. Например, функция print_vertical() выводит в боковой части экрана свой строчный аргумент по вертикали сверху вниз.

void print_vertical(char *str){ while(*str) printf("%c\n", *str++);}

Вот пример использования функции print_vertical():

#include <stdio.h> void print_vertical(char *str); /* прототип */ int main(int argc, char *argv[]){ if(argc > 1) print_vertical(argv[1]); return 0;} void print_vertical(char *str){ while(*str) printf("%c\n", *str++);}

И еще одно замечание: в ранних версиях С ключевое слово void не определялось. Таким образом, в программах, написанных на этих версиях С, функции, которые не возвращали значений, просто имели по умолчанию тип int — и это несмотря на то, что они не возвращали никаких значений!

Механизм использования return описан в главе 3. Как вы помните, там говорится, что этот оператор имеет два важных применения. Во-первых, он обеспечивает немедленный выход из функции, т.е. заставляет выполняющуюся программу передать управление коду, вызвавшему функцию. Во-вторых, этот оператор можно использовать для того, чтобы возвратить значение. В следующих разделах рассказывается, каким именно образом можно использовать оператор return.

Возврат из функции

Функция может завершать выполнение и осуществлять возврат в вызывающую программу двумя способами. Первый способ используется тогда, когда после выполнения последнего оператора в функции встречается закрывающая фигурная скобка (}). (Конечно, это просто жаргон, ведь в настоящем объектном коде фигурной скобки нет!) Например, функция pr_reverse() в приведенной ниже программе просто выводит на экран в обратном порядке строку Мне нравится С, а затем возвращает управление вызывающей программе.

#include <string.h>#include <stdio.h> void pr_reverse(char *s); int main(void){ pr_reverse("Мне нравится C"); return 0;} void pr_reverse(char *s){ register int t; for(t=strlen(s)-1; t>=0; t--) putchar(s[t]);}

Как только строка выведена на экран, функции pr_reverse() "делать больше нечего", поэтому она возвращает управление туда, откуда она была вызвана.

Но на практике не так уж много функций используют именно такой способ завершения выполнения. В большинстве функций для завершения выполнения используется оператор return — или потому, что необходимо вернуть значение, или чтобы сделать код функции проще и эффективнее.

В функции может быть несколько операторов return. Например, в следующей программе функция find_substr() возвращает начальную позицию подстроки в строке или же возвращает —1, если подстрока, наоборот, не найдена. В этой функции для упрощения кодирования используются два оператора return.

#include <stdio.h> int find_substr(char *s1, char *s2); int main(void){ if(find_substr("C - это забавно", "is")!= -1) printf("Подстрока найдена."); return 0;} /* Вернуть позицию первого, вхождения s2 в s1. */int find_substr(char *s1, char *s2){ register int t; char *p, *p2; for(t=0; s1[t]; t++) { p = &s1[t]; p2 = s2; while(*p2 && *p2==*p) { p++; p2++; } if(!*p2) return t; /* 1-й оператор return */ } return -1; /* 2-й оператор return */}

Возврат значений

Все функции, кроме тех, которые относятся к типу void, возвращают значение. Это значение указывается выражением в операторе return. Стандарт С89 допускает выполнение оператора return без указания выражения внутри функции, тип которой отличен от void. В этом случае все равно происходит возврат какого-нибудь произвольного значения. Но такое положение дел, мягко говоря, никуда не годится! Поэтому в Стандарте С99 (да и в C++) предусмотрено, что в функции, тип которой отличен от void, в операторе return необходимо обязательно указать возвращаемое значение. То есть, согласно С99, если для какой-либо функции указано, что она возвращает значение, то внутри этой функции у любого оператора return должно быть свое выражение. Однако если функция, тип которой отличен от void, выполняется до самого конца (то есть до закрывающей ее фигурной скобки), то возвращается произвольное (непредсказуемое с точки зрения разработчика программы!) значение. Хотя здесь нет синтаксической ошибки, это является серьезным упущением и таких ситуаций необходимо избегать.

Если функция не объявлена как имеющая тип void, она может использоваться как операнд в выражении. Поэтому каждое из следующих выражений является правильным:

x = power(y);if(max(x,y) > 100) printf("больше");for(ch=getchar(); isdigit(ch);)...;

Общепринятое правило гласит, что вызов функции не может находиться в левой части оператора присваивания. Выражение

swap(x,y) = 100; /* неправильное выражение */

является неправильным. Если компилятор С в какой-либо программе найдет такое выражение, то пометит его как ошибочное и программу компилировать не будет.

В программе можно использовать функции трех видов. Первый вид — простые вычисления. Эти функции предназначены для выполнения операций над своими аргументами и возвращают полученное в результате этих операций значение. Вычислительная функция является функцией "в чистом виде". В качестве примеров можно назвать стандартные библиотечные функции sqrt() и sin(), которые вычисляют квадратный корень и синус своего аргумента соответственно.

Второй вид включает в себя функции, которые обрабатывают информацию и возвращают значение, которое показывает, успешно ли была выполнена эта обработка. Примером является библиотечная функция fclose(), которая закрывает файл. Если операция закрытия была завершена успешно, функция возвращает 0, а в случае ошибки она возвращает EOF.

У функций последнего, третьего вида нет явно возвращаемых значений. В сущности, такие функции являются чисто процедурными и никаких значений выдавать не должны. Примером является exit(), которая прекращает выполнение программы. Все функции, которые не возвращают значение, должны объявляться как возвращающие значение типа void. Объявляя функцию как возвращающую значение типа void, вы запрещаете ее применение в выражениях, предотвращая таким образом случайное использование этой функции не по назначению.

Иногда функции, которые, казалось бы, фактически не выдают содержательный результат, все же возвращают какое-то значение. Например, printf() возвращает количество выведенных символов. Если бы нашлась такая программа, которая на самом деле проверяла бы это значение, то это было бы что-то необычное... Другими словами, хотя все функции, за исключением относящихся к типу void, возвращают значения, вовсе не нужно стремиться использовать эти значения во что бы то ни стало. Часто при обсуждении значений, возвращаемых функциями, возникает такой довольно распространенный вопрос: "Неужели не обязательно присваивать возвращенное значение какой-либо переменной? Не повиснет ли оно где-нибудь и не приведет ли это в дальнейшем к каким-либо неприятностям?" Отвечая на этот вопрос, повторим, что присваивание отнюдь не является обязательным, причем отсутствие его не станет причиной каких-либо неприятностей. Если возвращаемое значение не входит ни в один из операторов присваивания, то это значение будет просто отброшено. Отметим также, что такое отбрасывание значения встречается очень часто. Проанализируйте следующую программу, в которой используется функция mul():

#include <stdio.h> int mul(int a, int b); int main(void){ int x, y, z; x = 10; y = 20; z = mul(x, y); /* 1 */ printf("%d", mul(x,y)); /* 2 */ mul(x, y); /* 3 */ return 0;} int mul(int a, int b){ return a*b;}

В строке 1 значение, возвращаемое функцией mul(), присваивается переменной z. В строке 2 возвращаемое значение не присваивается, но используется функцией printf(). И наконец, в строке 3 возвращаемое значение теряется, потому что не присваивается никакой из переменных и не используется как часть какого-либо выражения.

Возвращаемые указатели

Хотя с функциями, которые возвращают указатели, обращаются так же, как и с любыми другими функциями, все же будет полезно познакомиться с некоторыми основными понятиями и рассмотреть соответствующий пример. Указатели не являются ни целыми, ни целыми без знака. Они являются адресами в памяти и относятся к особому типу данных. Такая особенность указателей определяется тем, что арифметика указателей (адресная арифметика) работает с учетом параметров базового типа. Например, если указателю на целое придать минимальное (ненулевое) приращение, то его текущее значение станет на четыре больше, чем предыдущее (при условии, что целые значения занимают 4 байта). Вообще говоря, каждый раз, когда значение указателя увеличивается (уменьшается) на минимальную величину, то он указывает на последующий (предыдущий) элемент, имеющий базовый тип указателя. Так как размеры разных типов данных могут быть разными, то компилятор должен знать тип данных, на которые может указывать указатель. Поэтому в объявлении функции, которая возвращает указатель, тип возвращаемого указателя должен декларироваться явно. Например, нельзя объявлять возвращаемый тип как int *, если возвращается указатель типа char *! Иногда (правда, крайне редко!) требуется, чтобы функция возвращала "универсальный" указатель, т.е. указатель, который может указывать на данные любого типа. Тогда тип результата функции следует определить как void *.

Чтобы функция могла возвратить указатель, она должна быть объявлена как возвращающая указатель на нужный тип. Например, следующая функция возвращает указатель на первое вхождение символа, присвоенного переменной с, в строку s. Если этого символа в строке нет, то возвращается указатель на символ конца строки ('0').

/* Возвращает указатель на первое вхождение c в s. */char *match(char c, char *s){ while(c!=*s && *s) s++; return(s);}

Вот небольшая программа, в которой используется функция match():

#include <stdio.h> char *match(char c, char *s); /* прототип */ int main(void){ char s[80], *p, ch; gets(s); ch = getchar(); p = match(ch, s); if(*p) /* символ найден */ printf("%s ", p); else printf("Символа нет."); return 0;}

Эта программа сначала считывает строку, а затем символ. Потом проводится поиск местонахождения символа в строке. При наличии символа в строке переменная p укажет на него, и программа выведет строку, начиная с найденного символа. Если символ в строке не найден, то p укажет на символ конца строки ('0'), причем *p будет представлять логическое значение ЛОЖЬ (false). В таком случае программа выведет сообщение Символа нет.

Функция типа void

Одним из применений ключевого слова void является явное объявление функций, которые не возвращают значений. Мы уже знаем, что такие функции не могут применяться в выражениях, и указание ключевого слова void предотвращает их случайное использование не по назначению. Например, функция print_vertical() выводит в боковой части экрана свой строчный аргумент по вертикали сверху вниз.

void print_vertical(char *str){ while(*str) printf("%c\n", *str++);}

Вот пример использования функции print_vertical():

#include <stdio.h> void print_vertical(char *str); /* прототип */ int main(int argc, char *argv[]){ if(argc > 1) print_vertical(argv[1]); return 0;} void print_vertical(char *str){ while(*str) printf("%c\n", *str++);}

И еще одно замечание: в ранних версиях С ключевое слово void не определялось. Таким образом, в программах, написанных на этих версиях С, функции, которые не возвращали значений, просто имели по умолча



Поделиться:




Поиск по сайту

©2015-2024 poisk-ru.ru
Все права принадлежать их авторам. Данный сайт не претендует на авторства, а предоставляет бесплатное использование.
Дата создания страницы: 2017-12-12 Нарушение авторских прав и Нарушение персональных данных


Поиск по сайту: