Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

книги / Программирование на языке Си

..pdf
Скачиваний:
15
Добавлен:
12.11.2023
Размер:
17.16 Mб
Скачать

222

Программирование на языке Си

Пример обращения к функции index():

#include <stdio.h> void main( )

{

char Cl[ ]="сумма масс"; /* Прототип функции: */

int index(char [ ], char ( ]); char C2( ]="иа";

char C3[ ]="ам";

printf("\пДля %s mmeKC=%d" ,C2 ,index (Cl ,C2)); printf("\пДля %s HHfleKc=%d",C3,index(Cl,C3));

}

Результат выполнения программы:

Для ма индекс=3

Для ам индекс=-1

Вфункции index() параметры специфицированы как указа­ тели на тип char, а в теле функции обращение к символам строк выполняется с помощью индексированных переменных. Вместо параметров-указателей подставляют в качестве фактических

параметров имена символьных массивов С1[], С2[ ], С3[ ]. Ни­ какой неточности здесь нет: к массивам допустимы обе формы обращения - и с помощью индексированных переменных, и с использованием разыменования указателей.

Функция сравнения строк. Для сравнения двух строк можно написать следующую функцию (в стандартной библиотеке име­ ется близкая к этой функция strcm p(), см. Приложение 3):

int row(char Cl( ], char C2( ])

{

int i,ml,m2; /* ml,m2 - длины строк C1,C2 */ for (ml=0;*(Cl+ml)= '\0'; ml++);

for (m2=0;*(C2+m2)= '\0'; m2++); if (ml!=m2) return -1;

for (i=0; i<ml; i++)

if (*C1++ != *C2++) return (i+1); return 0;

Глава 5. Функции

223

В теле функции обращение к элементам массивов-строк реа­ лизовано через разыменование указателей. Функция row() воз­ вращает: значение -1, если длины строк-аргументов Cl, С2 раз­ личны; 0 - если все символы строк совпадают. Если длины строк одинаковы, но символы не совпадают, то возвращается порядковый номер (слева) первых несовпадающих символов.

Особенность функции row() - спецификация параметров как массивов и обращение к элементам массивов внутри тела функ­ ции с помощью разыменования. При этом за счет операций С1++ и С2++ изменяются начальные "настройки" указателей на массивы. Одновременно в той же функции к тем же массивампараметрам выполняется обращение и с помощью выражений *(С1+ш1) и *(С2+ш2), при вычислении которых значения С1 и С2 не меняются.

Функция соединения строк. Следующая функция позволяет "присоединить" к первой строке-аргументу вторую строкуаргумент (в стандартной библиотеке есть подобная функция" strncat(), см. Приложение 3):

/* Соединение (конкатенация) двух строк: */ void cone(char *Cl, char *C2) ■

{

int

i,m;

/* m - длина 1-й строки

*/

for

(m=0;

* (Cl+m)!=1\0'; m++);

 

for

(i=0;

* (C2+i)?= •\0•; i++)

 

*(Cl+m+i)= * (C2+i) ;

*(Cl+m+i)='\0';

}

Результат возвращается как значение первого аргумента С1. Второй аргумент С2 не изменяется. Обратите внимание на то, что при использовании функции сопс() длина строки, заме­ няющей параметр С 1, должна быть достаточной для приема ре­ зультирующей строки.

Функция выделения подстроки. Для выделения из строки С1 фрагмента заданной длины (подстроки) можно предложить такую функцию:

224

 

 

 

Программирование на языке Си

void substr(char *С1, char *С2, int n, int k)

/*

Cl

- исходная строка

 

*/

/* C2 - выделяемая подстрока */

 

/*

n

- начало выделяемой подстроки */

/*

к

- длина выделяемой подстроки

*/

{

int

i ,m ; /* m - длина исходной

строки */

 

 

for

(m=0; С1[т]!='\0';

т++);

 

 

 

if (n<0 || n>m

||

k<0 || k>m-n)

 

 

{

;

 

 

 

 

C 2[0] = ' \ 0 '

 

 

return;

>

for (i=n; i<k+n; i++) C2[i-n]=Cl[i-1];

C2[i-n]='\0'; return;

}

Результат выполнения функции - строка С2[ ] из к символов, выделенных из строки С 1 [ ], начиная с символа, имеющего но­ мер п. При неверном сочетании значений параметров возвраща­ ется пустая строка, использованная в качестве параметра С2.

Функция копирования содержимого строки. Так как в язы­ ке Си отсутствует оператор присваивания для строк, то полезно иметь специальную функцию, позволяющую "переносить” со­ держимое строки в другую строку (такая функция strcpy() есть в стандартной библиотеке, но она имеет другое возвращаемое значение):.

/* Копирование содержимого строки С2 в С1 */ void copy(char *С1, char *С2)

'/* С2 - оригинал, С1 - копия */

{

int i ;

for (i=0; C2[i]!=’\0'; i++) Cl[i]=C2[i];

Cl[i]='\0';

>

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

Глава 5. Функции

 

225

#include <stdio.h>

 

void main()

 

 

{

TRANSIT GLORIA MUNDI!";

char X[ ]="SIC

/* Прототип функции: */

]);

void copy(char[ ], char[

char В [100];

 

*/

/* Обращение к функции:

copy (В,Х) ;

В);

 

printf("%s\n",

 

}

Результат выполнения тестовой программы :

SIC TRANSIT GLORIA MUNDI!

Другой вариант функции копирования строки:

void copy(char Cl[ ], char С2[ ])

(

int i=0; do

{

Cl[i] = C2[i];

}

while (Cl[i++]!*'\0');

>

В той же функции операция присваивания может быть пере­ несена в выражение-условие продолжения цикла. Ранги опера­ ций требуют заключения выражения-присваивания в скобки:

void copy(char *С1, char *С2)

{

int i=0;

while ((Cl[i] * C2[i]) != '\0’)

i++;

}

Так как ненулевое значение в выражении после while счита­ ется истинным, то явное сравнение с '\0' необязательно и воз­ можно следующее упрощение функции:

15~Э124

226

 

Программирование на языке Си

void

copy(char

* Cl, char * С2)

{

i=0;

 

int

= C2[i])

while (Cl[i]

}

i++;

 

 

 

И, наконец, наиболее короткий вариант:

void copy (char * Cl, char * C2)

{

while (*C1++ = *C2++);

}

Резюме по строкам-параметрам. Часть из приведенных функций для работы со строками, а именно функции: конкате­ нации строк сопс(), 'инвертирования строки invert( ), копирова­ ния одной строки в другую сору(), выделения подстроки stibstr(); предусматривают изменение объектов вызывающей программы. Это возможно и допустимо только потому, что па­ раметрами являются указатели. Это либо имена массивов, либо указатели на строки. Как уже говорилось, параметры-указатели позволяют функции получить непосредственный доступ к объ­ ектам той программы, из которой функция вызвана. В приве­ денных определениях функций этот доступ осуществляется как с помощью индексированных переменных, так и с помощью разыменования указателей. На самом деле компилятор языка Си, встречая, например, такое обращение к элементу массива s[i], всегда заменяет его выражением *(s+i), т.е. указателем s со смещением i.

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

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

Глава 5. Функции

227

Функция для сцепления (конкатенации) строк:

#include <string.h>

void concat(char * Cl, char * C2)

{

int m , i ;

m=strlen (Cl); i=0;

while ((*(Cl+m+i) * * (C2+i))!= '\0') i++;

)

Вместо функции strlen( ) можно было бы использовать опре­ деленную выше функцию 1еп(). При обращении к функции concat(), а также при использовании похожей на нее библио­ течной функции strcat() нужно, чтобы длина строки, использо­ ванной в качестве первого параметра, была достаточной для размещения результирующей (объединенной) строки. В против­ ном случае результат выполнения непредсказуем.

5.4. У казатели на ф ункции .

Указатели при вызове функций. До сих пор мы рассматри­ вали функцию как минимальный исполняемый модуль про­ граммы, обмен данными с которым происходит через набор параметров функции и с помощью значений, возвращаемых функцией в точку вызова. Теперь перейдем к вопросу о том, по­ чему в языке Си функция введена как один из производных ти­ пов. Необходимость в таком типе связана, например, с задачами, в которых функция (или ее адрес) должна выступать в качестве параметра другой функции или в качестве значения, возвращаемого другой функцией. Обратимся к уже рассмотрен­ ному ранее выражению "вызов функции" (точнее, к более об­ щему варианту вызова):

обозначение функции (список фактических параметров)

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

15*

228

Программирование на языке Си

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

Самый употребительный указатель на функцию - это ее имя (идентификатор). Именно так указатель на функцию вводится в

ее определении:

тип имяфункции (спецификация параметров) телофункции

Прототип

тип имя функции (спецификация_параметров);

также описывает имя функции именно как указатель на функ­ цию, возвращающую значение конкретного типа.

Имя функции в ее определении и в ее прототипе - указательконстанта. Он навсегда связан с определяемой функцией и не может быть "настроен" на что-либо иное, чем ее адрес. Для идентификатора имя_функции термин "указатель" обычно не используют, а говорят об имени функции.

Указатель на функцию как переменная вводится отдельно от определения и прототипа какой-либо функции. Для этих целей используется конструкция:

тип (*имя_указателя) (спецификация параметров);

где тип - определяет тип возвращаемого функцией значения; имя_указателя - идентификатор, произвольно выбранный

программистом;

спецификация параметров - определяет состав и типы параметров функции.

Например, запись

int (*point) (void);

определяет указатель-переменную с именем point на функции без параметров, возвращающие значения типа int.

Глава 5. Функции

229

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

int * funct (void);

то это будет не определением указателя, а прототипом функции без параметров с именем funct, возвращающей значения типа int*.

В отличие от имени функции (например, funct) указатель point является переменной, т.е. ему можно присваивать значения других указателей, определяющих адреса функций программы. Принципиальное требование - тип указателя-переменной дол­ жен полностью соответствовать типу функции, адрес которой ему присваивается.

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

point=funct; /* Ошибка - несоответствие типов */

будет ошибочной, так как типы возвращаемых значений для point и funct различны.

Неконстантный указатель на функцию, настроенный на адрес конкретной функции, может быть использован для вызова этой функции. Таким образом, при обращении к функции перед круглыми скобками со списком фактических параметров можно помещать: имя функции (т.е. константный указатель); указа­ тель-переменную того же типа, значение которого равно адресу функции; выражение разыменования такого же указателя с та­ ким же значением. Следующая программа иллюстрирует три способа вызова функций.

#include <stdio.h> void fl (void)

{

printf ("\n Выполняется f1( )") ;

)

void f2 (void)

{.

printf ("\n Выполняется f2( )");

}

230

 

 

 

Программирование на языке Си

void main ()

 

 

 

 

{

 

 

(void);

 

 

void (*point)

 

 

/♦point - указатель-переменная на функцию */

f2 ();

/* Явный вызов

функции f2()*/

 

point=f2; /*

Настройка

указателя на f2()*/

 

(♦point)();

/♦

Вызов f2() по ее адресу

♦/

 

 

 

с разыменованием указателя

point=f1; /* Настройка указателя на fl()*/

 

(♦point)();

/* Вызов f1() по ее адресу

♦/

point

(); /♦

 

с разыменованием указателя

Вызов fl() без разыменования

 

Результат выполнения программы:

Выполняется f2( ) Выполняется f2( ) Выполняется f1( ) Выполняется f1( )

Все варианты обращения к функциям с использованием ука­ зателей-констант (имен функций) и указателей-переменных (неконстантных указателей на функции) в программе продемон­ стрированы и снабжены комментариями. Приведем общий вид двух операций вызова функций с помощью неконстантных ука­ зателей:

(*имяуказателя) (список фактических параметров) имя_указателя (список фактическихпараметров)

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

♦point (); /♦ Ошибочный вызов ♦/

Это полностью соответствует синтаксису языка. Операция '()' - "круглые скобки" имеет более высокий приоритет, чем

Глава 5. Функции

231

операция разыменования

В этом ошибочном вызове вначале

выполнится вызов point(), а уж к результату будет применена операция разыменования.

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

int fic (char); /* Прототип функции */ int (*pfic) (char)=fic;

/* pfic - указатель на функцию */

Массивы указателей на функции. Такие массивы по смыс­ лу ничем не отличаются от массивов других объектов, однако форма их определения несколько необычна:

тип ( *имя массива [размер] ) {спецификация параметров);

где тип определяет тип возвращаемых функциями значений;

имя массива - произвольный идентификатор;

*

размер - количество элементов В массиве;

 

спецификация параметров - определяет состав

и типы

параметров функций.

 

П рим ер:

 

int (*parray [4]) (char);

 

где parray - массив указателей на функции, каждому из которых можно присвоить адрес определенной выше функции int fic (char) и адрес любой функции с прототипом вида

int имя-функции (char);

Массив в соответствии с синтаксисом языка является произ­ водным типом наряду с указателями и функциями. Массив функций создать нельзя, однако, как мы показали, можно опре­ делить массив указателей на функции. Тем самым появляется возможность создавать "таблицы переходов" (jump tables), или "таблицы передачи управления". С помощью таблицы перехо­ дов удобно организовывать ветвления с возвратом по результа­

Соседние файлы в папке книги