книги / Программирование на языке Си
..pdf222 |
Программирование на языке Си |
Пример обращения к функции 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), или "таблицы передачи управления". С помощью таблицы перехо дов удобно организовывать ветвления с возвратом по результа