книги / Программирование на языке Си
..pdf212 |
Программирование на языке Си |
*т = *т>0 ? *т :- *т
все действия выполняются над значениями той переменной ос новной программы (int к), адрес которой (&к) использован в качестве фактического параметра.
Графически иллюстрирует взаимосвязь функции positive() и основной программы рис. 5.1. При обращении к функции positive() она присваивает абсолютное значение той перемен ной, адрес которой использован в качестве ее параметра.
Функция positive () |
Основная программа |
*(&к) - переменная к
Р и с . 5 .1 . С х е м а " н а с т р о й к и " п а р а м е т р а - у к а з а т е л я
Пояснив основные принципы воздействия с помощью указа телей из тела функции на значения объектов вызывающей про граммы, напомним, что этой возможностью мы уже неодно кратно пользовались, обращаясь к функции scanf(). Функция scanf() имеет один обязательный параметр - форматную строку и некоторое количество необязательных параметров, каждый из которых должен быть адресом того объекта, значение которого вводится с помощью функции scanf(). Например:
long 1; double d;
scanf("%ld%le", &1, &d);
Глава 5.Функции |
213 |
Здесь в форматной строке спецификаторы преобразования %ld и %1е обеспечивают ввод (чтение) от клавиатуры соответ ственно значений типов long int и double. Передача этих значе ний переменным long 1 и double d обеспечивается с помощью их адресов &I и &d, которые используются в качестве фактиче ских параметров функции scanf().
Имитация подпрограмм. Подпрограммы отсутствуют в языке Си, однако если использовать обращение к функции в виде оператора-выражения, то получим аналог оператора вызо ва подпрограммы. (Напомним, что выражение приобретает ста тус оператора, если после него стоит точка с запятой.) Выше были приведены в качестве примеров функции
void print(int gg, int mm, int) |
и |
void RealTime(void), |
каждая из которых очень похожа на подпрограммы других язы ков программирования.
К сожалению, в языке Си имеется еще одно препятствие для непосредственного использования функции в роли подпрограм мы - это рассмотренная выше передача параметров только зна чениями, т.е. передаются значения переменных, а не их адреса. Другими словами, в результате выполнения функции нельзя из менить значения ее фактических параметров. Таким образом, если Z () - функция для вычисления периметра и площади тре угольника по длинам сторон Е, F, G, то невозможно, записав оператор-выражение Z(E, F, G, РР, SS); получить результаты (периметр и площадь) в виде значений переменных РР и SS. Так как параметры передаются только значениями, то после вы полнения функции Z () значения переменных РР и SS останутся прежними.
Возможно следующее решение задачи. В определении функ ции параметры, с помощью которых результаты должны пере даваться из функции в точку вызова, описываются как ука затели. Тогда с помощью этих указателей может быть реализо ван доступ из тела функции к тем объектам вызывающей про граммы, которые адресуются параметрами-указателями. Для пояснения сказанного рассмотрим следующую программу:
214 |
|
Программирование на языке Си |
|
#include <stdio.h> |
|
|
|
void main () |
|
|
|
{ |
|
|
|
float x,y; |
|
|
|
/* Прототип функции */ |
|
||
void aa(float *, float *); |
|
||
printf("\n Введите: x="); |
|
||
scanf("%f",&x); |
|
|
|
printf(" |
Введите: y="); |
|
|
scanf("%f",&y); |
*/ |
|
|
/* Вызов |
функции |
|
|
a a (& x ,&y) ; |
|
x, y) ; |
|
printf(" \n Результат: x=%f y=%f", |
|||
) |
меняющая местами |
|
|
/* Функция, |
|
||
значения переменных, на которые указывают |
|||
фактические параметры: |
*/ |
||
void aa(float * b, float * с) |
|
||
/* Ь и с - указатели |
*/ |
|
|
{ |
|
|
|
float е; /* Вспомогательная переменная */ е=*Ь; *Ь=*с; *с=е ;
}
В основной программе описаны переменные х, у, значения которых пользователь сводит с клавиатуры. Формальными па раметрами функции аа() служат указатели типа float *. Задача функции - поменять местами значения тех переменных, на ко торые указывают ее параметры-указатели. При обращении к функции аа() адреса переменных х, у используются в качестве фактических параметров. Поэтому после выполнения програм мы возможен, например, такой результат:
Введите: х=33.3 Введите: у=6б.б
Результат: х=66.600000 у=33.300000
Имитация подпрограммы (функция) для вычисления пери метра и площади треугольника:
Глава 5. Функции |
215 |
#include <math.h> /* для функции sqrt( ) */ finclude <stdio.h>
void main( )
{
float x,y,z,pp,ss; /* Прототип: */
int triangle(float, float, float, float *, float *);
printf("\n Введите: x=") ; scanf("%f",fix); printf("\t y="); scanf("%f",£y); printf("\t z="); scanf("%f",£z);
if (triangle(x,y,z,£pp,£ss) = 1)
(
printf(" Периметр = %f",pp); printf(", площадь = %f",ss);
}
else
printf("\n Ошибка в данных ");
}
/* Определение функции: */ int triangle(float a,float b,
float c,float * perimeter, float * area)
(
float e; *perimeter=*area=0.0;
if (a+b<=c || a+c<=b || b+c<=a) return 0;
*perimeter=a+b+c;
e=*perimeter/2; *area=sqrt(e*(e-a)* (e-b)* (e-c)); return 1 ;
Пример результатов выполнения программы:
Введите х=3 у=4 z=5
Периметр=12.000000 площадь=б.000000
216 |
Программирование на языке Си |
5.3. М ассивы и строки к ак парам етры
ф ункций
Массивы в параметрах. Если в качестве параметра функции используется обозначение массива, то на самом деле внутрь функции передается только адрес начала массива. Применяя массивы в качестве параметров для функций из главы 2, мы не отмечали этой особенности. Например, заголовок функции для вычисления скалярного произведения векторов выглядел так:
float Scalar_Product(int n, float a[ ], float b[ ])...
Но заголовок той же функции можно записать и следующим образом:
float Scalar_Product(int n, float *a, float *b)...
Конструкции
float b[ ]; и float *b;
совершенно равноправны в спецификациях параметров. Однако в первом случае роль имени b как указателя не так явственна. Во втором варианте все более очевидно - b явно определяется как указатель типа float *. .
В теле функции Scalar_Product() из главы 2 обращение к элементам массивов-параметров выполнялось с помощью ин дексированных элементов a[i] и b[i]. Однако можно обращаться к элементам массивов, разыменовывая соответствующие значе ния адресов, т.е. используя выражения *(a+i) и *(i+b).
Так как массив всегда передается в функцию как указатель, то внутри функции можно изменять значения элементов масси- ва-фактического параметра, определенного в вызывающей про грамме. Это возможно и при использовании индексирования, и при разыменовании указателей на элементы массива.
Для иллюстрации указанных возможностей рассмотрим функцию, возводящую в квадрат значения элементов одномер ного массива, и вызывающую ее программу:
Глава 5.Функции |
217 |
#include <stdio.h> |
|
/* Определение |
функции: */ |
void quart(int |
n, float * x) |
(
int i ; for(i=0;i<n;i++)
/* Присваивание после умножения: */ * (x+i)*=*(x+i) ;
}
void main()
{
/* Определение массива: */ float z[ ]={1.0, 2.0, 3.0, 4.0}; int j ;
quart(4,z); /* Обращение к функции */ /* Печать измененного массива */ for(j=0;j<4;j++)
printf ("\nz t%d] =%f j ,z[ j]);
}
Результат выполнения программы:
z[0]=l.000000
z[l[=4.000000
z[2]=9.000000
z[3]=16.000000
Чтобы еще раз обратить внимание на равноправие парамет ров в виде массива и указателя того же типа, отметим, что заго ловок функции в нашей программе может быть и таким:
void quart (int n, float x [ ])
В теле функции разыменовано выражение, содержащее имя массива-параметра, т.е. вместо индексированной переменной x[i] используется *(x+i). Эту возможность мы уже неоднократно отмечали. Более интересная возможность состоит в том, что можно изменять внутри тела функции значение указателя на массив, т.е. в теле цикла записать, например, такой оператор:
*х*=*х++;
Обратите внимание, что неверным для нашей задачи будет такой вариант этого оператора:
218 |
Программирование на языке Си |
*х++*=*х++; /*ошибка!*/
В этом ошибочном случае "смещение1' указателя х вдоль массива будет при каждой итерации цикла не на один элемент массива, а на 2, так как х изменяется и в левом, и в правом опе рандах операции присваивания.
Следует отметить, что имя массива внутри тела функции не воспринимается как константный (не допускающий изменений) указатель, однако такая возможность отсутствует в основной программе, где определен соответствующий массив (факти ческий параметр). Если в цикле основной программы вместо значения z[j] попытаться использовать в функции printf() вы ражение *z++, то получим сообщение об ошибке, т.е. следую щие операторы не верны:
f o r ( j = 0 ; j < 4 ; j + + )
printf("\nz[%d]=%f", j, *z++);
Сообщение об ошибке при компиляции выглядит так:
Error. . . Lvalue required
Подводя итоги, отметим, что, с одной стороны, имя массива является константным указателем со значением, равным адресу нулевого элемента массива. С другой стороны, имя массива, ис пользованное в качестве параметра, играет в теле функции роль обычного (неконстантного) указателя, т.е. может использовать ся в качестве леводопустимого выражения. Именно поэтому в спецификациях формальных параметров эквивалентны, как ука зано выше, например, такие формы:
double х[] |
и |
double *х |
Строки как параметры функций. Строки в качестве фак тических параметров могут быть специфицированы либо как одномерные массивы типа char [ ], либо как указатели типа char *. В обоих случаях с помощью параметра в функцию пере дается адрес начала символьного массива, содержащего строку. В отличие от обычных массивов для параметров-строк нет не
Глава 5. Функции |
219 |
обходимости явно указывать их длину. Признак '\0', размещае мый в конце каждой строки, позволяет всегда определить ее длину, точнее, позволяет перебирать символы строки и не вый ти за ее пределы. Как примеры использования строк в качестве параметров функций рассмотрим несколько функций, решаю щих типовые задачи обработки строк. Аналоги большинства из приводимых ниже функций имеются в библиотеке стандартных функций компилятора (см. Приложение 3). Их прототипы и другие средства связи с этими функциями находятся в заголо вочных файлах string.h и stdlib.h. Однако для целей темы, по священной использованию строк в качестве параметров, удобно заглянуть "внутрь" таких функций.
Итак, в иллюстративных целях приведем определения функ ций для решения некоторых типовых задач обработки строк. Будем для полноты картины использовать различные способы задания строк-параметров.
Функция вычисления длины строки. Следующая ниже функция имеет самую старомодную и не рекомендуемую стан дартом форму заголовка (в стандартной библиотеке ей соответ ствует функция strlen(), см. Приложение 3):
int len(e) char е [ ];
{
int m;
for (m=0; e[m]!='\0'; m++); return m;
)
В соответствии с ANSI-стандартом и со стандартом ISO заго ловок должен иметь, например, такой вид:
int len (char е[ ])
В этом примере и в заголовке, и в теле функции нет даже упоминаний о родстве массивов и указателей. Однако компиля тор всегда воспринимает массив как указатель на его начало, а индекс как смещение относительно начала массива. Следующий
220 |
Программирование на языке Си |
вариант той же функции (теперь заголовок соответствует стан дартам языка) явно реализует механизм работы с указателями:
int len (char *s)
{
int m;
fox (m=0; *s++!='\0'; m++) return m;
}
Для формального параметра-указателя s внутри функции вы деляется участок памяти, куда записывается значение фактиче ского параметра. Так как s не константа, то значение этого указателя может изменяться. Именно поэтому допустимо выра жение
Функция инвертирования строки-аргумента с парамет ром-массивом (заголовок соответствует стандарту):
void invert(char е[ ])
{
char s;
int i , j , m;
/*m - номер позиции символа '\0' в строке е */ for (m=0; e[m]!='\0'; m++) ;
for (i=0, j=m-l; i<j; i++, j--)
(
s = e [ i ] ;
e ( i ] = e ( j ] ; e t j ]= s ;
}
}
Вопределении функции invert() с помощью ключевого слова void указано, что функция не возвращает значения.
Вкачестве упражнения можно переписать функцию invert(),
заменив параметр-массив параметром-указателем типа char*. При выполнении функции invert() строка - фактический па
раметр, например, "сироп" превратится в строку "порис". При этом символ 40’ остается на своем месте в конце строки. Пример использования функции invert():
Глава 5. Функции |
221 |
#include <stdio.h> void main( )
{
char ct[ ]="0123456789"; /* Прототип функции: */ void invert(char ( ]); /* Вызов функции: */ invert(ct); printf("\n%s",ct);
)
Результат выполнения программы:
9876543210
Функция поиска в строке ближайшего слева вхождения другой строки (в стандартной библиотеке имеется подобная функция strstr(), см. Приложение 3):
/♦Поиск строки СТ2 в строке СТ1 */ int index (char * СТ1, char * CT2)
{
int |
i , j , ml, m2; |
|
|
||
/* Вычисляются ml и m2 - длины строк */ |
|||||
for |
(ml=0; |
CT1[ml] |
!='\0'; |
ml++); |
|
for |
(m2=0; CT2[m2] |
!='\0'; |
m2++) ; |
||
if |
(m2>ml) |
-1; |
|
|
|
for |
return |
|
|
||
(i=0; |
i<=ml-m2; i++) |
|
|||
( |
|
(j=0; |
j<m2; j++) /*Цикл сравнивания */ |
||
for |
|||||
|
if |
(CT2[j] !=CT1[i+j]) |
|
||
if |
|
break; |
|
|
|
(j==m2) |
|
|
|||
|
return |
i ; |
|
|
|
} /* Конец цикла no i */ |
|
||||
return -1; |
|
|
|
}
Функция index() возвращает номер позиции, начиная с кото рой СТ2 полностью совпадает с частью строки СТ1. Если стро ка СТ2 не входит в СТ1, то возвращается значение -1.