Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Подбельский Фомин_Программирование на языке СИ_...doc
Скачиваний:
356
Добавлен:
10.08.2019
Размер:
53.81 Mб
Скачать

5.3. Массивы и строки как параметры функций Массивы в параметрах.

Массивы в параметрах. Если в качестве параметра функции используется обозначение массива, то на самом деле внутрь функции передается только адрес начала массива. Применяя массивы в качестве параметров для функций из главы 2, мы не отмечали этой особенности. Например, заголовок функции для вычисления скалярного произведения векторов выглядел так:

float ScaIar_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).

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

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

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

void quart (int n, float x [ ])

В теле функции разыменовано выражение, содержащее имя массива-параметра, т.е. вместо индексированной переменной x[i] используется *(x+i). Эту возможность мы уже неоднократно отмечали. Более интересная возможность состоит в том, что можно изменять внутри тела функции значение указателя на массив, т.е. в теле цикла записать, например, такой оператор:

Обратите внимание, что неверным для нашей задачи будет такой вариант этого оператора:

В этом ошибочном случае "смещение" указателя х вдоль массива будет при каждой итерации цикла не на один элемент массива, а на 2, так как х изменяется и в левом, и в правом операндах операции присваивания.

Следует отметить, что имя массива внутри тела функции не воспринимается как константный (не допускающий изменений) указатель, однако такая возможность отсутствует в основной программе, где определен соответствующий массив (фактический параметр). Если в цикле основной программы вместо значения z[j] попытаться использовать в функции printf( ) выражение *z++, то получим сообщение об ошибке, т.е. следующие операторы не верны:

Сообщение об ошибке при компиляции выглядит так:

Подводя итоги, отметим, что, с одной стороны, имя массива является константным указателем со значением, равным адресу нулевого элемента массива. С другой стороны, имя массива, использованное в качестве параметра, играет в теле функции роль обычного (неконстантного) указателя, т.е. может использоваться в качестве леводопустимого выражения. Именно поэтому в спецификациях формальных параметров эквивалентны, как указано выше, например, такие формы:

double x[ ] и double *x

Строки как параметры функций. Строки в качестве фактических параметров могут быть специфицированы либо как одномерные массивы типа char [ ], либо как указатели типа char *. В обоих случаях с помощью параметра в функцию передается адрес начала символьного массива, содержащего строку. В отличие от обычных массивов для параметров-строк нет необходимости явно указывать их длину. Признак '\0', размещаемый в конце каждой строки, позволяет всегда определить ее длину, точнее, позволяет перебирать символы строки и не выйти за ее пределы. Как примеры использования строк в качестве параметров функций рассмотрим несколько функций, решающих типовые задачи обработки строк. Аналоги большинства из приводимых ниже функций имеются в библиотеке стандартных функций компилятора (см. Приложение 3). Их прототипы и другие средства связи с этими функциями находятся в заголовочных файлах string.h и stdlib.h. Однако для целей темы, посвященной использованию строк в качестве параметров, удобно заглянуть "внутрь" таких функций.

Итак, в иллюстративных целях приведем определения функций для решения некоторых типовых задач обработки строк. Будем для полноты картины использовать различные способы задания строк-параметров.

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

В соответствии с ANSI-стандартом и со стандартом ISO заголовок должен иметь, например, такой вид:

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

Для формального параметра-указателя s внутри функции выделяется участок памяти, куда записывается значение фактического параметра. Так как s не константа, то значение этого указателя может изменяться. Именно поэтому допустимо выражение S++.

Функция инвертирования строки-аргумента с параметром-массивом (заголовок соответствует стандарту):

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

В качестве упражнения можно переписать функцию invert( ), заменив параметр-массив параметром-указателем типа char*.

При выполнении функции invert( ) строка - фактический параметр, например, "сироп" превратится в строку "порис". При этом символ '\0' остается на своем месте в конце строки. Пример использования функции invert( ):

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

Функция поиска в строке ближайшего слева вхождения другой строки (в стандартной библиотеке имеется подобная функция strstr( ), см. Приложение 3):

Функция index( ) возвращает номер позиции, начиная с которой СТ2 полностью совпадает с частью строки СТ1. Если строка СТ2 не входит в СТ1, то возвращается значение -1.

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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