Главная страница
Навигация по странице:

  • Функция инвертирования строки-аргумента с параметром- массивом

  • Функция поиска в строке ближайшего слева вхождения дру­гой строки

  • Функция сравнения строк.

  • Функция соединения строк.

  • Функция выделения подстроки.

  • Функция копирования содержимого строки.

  • Резюме по строкам-параметрам.

  • Курс на Си. Подбельский. Курс программирования на Си. В., Фомин С. С. Курс программирования на языке Си Учебник


    Скачать 1.57 Mb.
    НазваниеВ., Фомин С. С. Курс программирования на языке Си Учебник
    АнкорКурс на Си
    Дата18.02.2023
    Размер1.57 Mb.
    Формат файлаdocx
    Имя файлаПодбельский. Курс программирования на Си.docx
    ТипУчебник
    #943863
    страница15 из 42
    1   ...   11   12   13   14   15   16   17   18   ...   42

    Массивы в параметрах. Если в качестве параметра функции ис­пользуется обозначение массива, то на самом деле внутрь функции передается только адрес начала массива. Применяя массивы в ка­честве параметров для функций из главы 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).

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

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

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

    #include

    /* Определение функции: */

    void quart(int n, float * x)

    {

    int i;

    for(i=0;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[%d]=%f",j,z[j]);

    }

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

    z[0]=1.000000

    z[1[=4.000000

    z[2]=9.000000

    z[3]=16.000000

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

    void quart (int n, float x [ ])

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

    *x*=*x++;

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

    *x++*=*x++; /*ошибка!*/

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

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

    for (j=0; j<4; j++)

    printf(“\nz[%d]=%f”, j, *z++);

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

    Error. . . Lvalue required

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

    double x[ ] и double *x

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

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

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

    int len (char e[ ]) {

    int m;

    for (m=0; e[m]!='\0'; m++); return m;

    }

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

    int len (char *s)

    {

    int m;

    for (m=0; *s++!='\0'; m++) return m;

    }

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

    Функция инвертирования строки-аргумента с параметром- массивом:

    void invert(char e[ ]) {

    char s;

    int i, j, m;

    /*m - номер позиции символа '\0' в строке е */

    for (m=0; e[m]!='\0'; m++);

    for (i=0, j=m-1; i
    {

    s=e[i];

    e[i]=e[j];

    e[j]=s;

    } }

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

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

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

    #include void main( )

    {

    char ct[ ]="0123456789";

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

    void invert(char [ ]);

    /* Вызов функции: */

    invert(ct);

    printf("\n%s",ct);

    }

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

    9876543210

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

    /*Поиск строки СТ2 в строке СТ1 */

    int index (char * СТ1, char * СТ2) {

    int i, j, m1, m2;

    /* Вычисляются m1 и m2 - длины строк */

    for (m1=0; CT1[m1] !='\0'; m1++);

    for (m2=0; CT2[m2] !='\0'; m2++);

    if (m2>m1)

    return -1;

    for (i=0; i<=m1-m2; i++)

    {

    for (j=0; j
    if (CT2[j] !=CT1[i+j])

    break;

    if (j==m2) return i;

    } /* Конец цикла по i */ return -1;

    }

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

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

    #include

    void main( )

    {

    char C1[ ]="сумма масс";

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

    int index(char [ ], char [ ]);

    char C2[ ]="ма";

    char C3[ ]="ам";

    ргШТСДпДля %s индекс=%б", C2, index(C1, C2));

    ргШТСДпДля %s индекс=%б", C3, index(C1, C3)); }

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

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

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

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

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

    int row(char С1[ ], char С2[ ]) {

    int i,m1,m2; /* m1,m2 - длины строк C1,C2 */

    for (m1=0;*(C1+m1)= '\0'; m1++);

    for (m2=0;*(C2+m2)= '\0'; m2++);

    if (m1!=m2) return -1;

    for (i=0; i
    if (*С1++ != *С2++) return (i+1);

    return 0;

    }

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

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

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

    /* Соединение (конкатенация) двух строк: */ void conc(char *С1, char *С2)

    int

    i,m;

    /* m -

    длина

    1-й строки

    */

    for

    (m=0;

    *(C1+m)!

    ='\0';

    m++);




    for

    (i=0;

    *(C2+i)!

    ='\0';

    i++)




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

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

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

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

    void substr(char *C1, char *C2, int n, int k)

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

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

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

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

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

    for (m=0; C1[m]!='\0'; m++);

    if (n<0 || n>m || k<0 || k>m-n)

    {

    C2[0]='\0';

    return;

    }

    for (i=n; i
    C2[i-n]=C1[i-1];

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

    return;

    }

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

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

    /* Копирование содержимого строки С2 в С1 */

    void copy(char *C1, char *C2)

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

    {

    int i;

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

    C1[i]=C2[i];

    C1[i]='\0';

    }

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

    #include

    void main()

    {

    char X[ ]="SIC TRANSIT GLORIA MUNDI!";

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

    void copy(char[ ], char[ ]);

    char B[100];

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

    copy (B,X);

    printf("%s\n", B);

    }

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

    SIC TRANSIT GLORIA MUNDI!

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

    void copy(char C1[ ], char C2[ ]) {

    int i=0;

    do

    {

    C1[i] = C2[i];

    }

    while (C1[i++]!='\0');

    }

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

    void copy(char *C1, char *C2) {

    int i=0;

    while ((C1[i] = C2[i]) != '\0') i++;

    }

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

    void copy(char * C1, char * C2)

    {

    int i=0;

    while (C1[i] = C2[i]) i++;

    }

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

    void copy (char * C1, char * C2) {

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

    }

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

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

    #include

    void concat(char * C1, char * C2)

    {

    int m, i;

    m=strlen (C1); i=0;

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

    }

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

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

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

      1. Указатели на функции
    1   ...   11   12   13   14   15   16   17   18   ...   42


    написать администратору сайта