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

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

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

параметрами-указателями должна "уметь" обрабатывать аргументы любых типов (08_10.с).

!Массив-параметр – это указатель на начало массива-аргумента

(08_11.с).

!Массив-параметр не может передать в функцию длину массивааргумента, т.е. количество его элементов или пределы изменений индекса элементов (08_11.с).

!Имя массива-параметра в теле функции является НЕКОНСТАНТНЫМ указателем на начало массива-аргумента

(08_11.с).

!К имени массива-параметра в теле функции применимы операции, изменяющие его значение (08_11_1.с).

!К имени массива в месте его определения не применимы операции, изменяющие его значение (08_11_2.с).

!При спецификации массива-параметра равноправны две конст-

рукции (08_11.с – 08_12.с):

тип имя_массива-параметра [] тип * имя_массива-параметра

!Для подчеркивания роли параметра как массива (а не обычного указателя) рекомендуется его специфицировать в определении функции с квадратными скобками.

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

(08_12.с, 08_13.с).

!Используя в качестве параметров функции имя массива и индекс, можно получить полный доступ к соответствующему элементу массива-аргумента, не используя операций получения адреса и разыменования (функция swap() в 08-13.с).

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

!Вычитая из адреса элемента массива адрес начала массива, получим индекс элемента (08_14_2.с, 08_09.с).

!Чтобы передать из функции с помощью параметра в вызывающую программу элемент массива-аргумента с нужными свойствами, можно использовать либо указатель и передавать с его помощью индекс элемента (08_15.с), либо указатель на указатель и передавать адрес элемента (08_16.с).

331

!Адрес массива-параметра может быть значением, возвращаемым функцией (08_17.с).

!Если возвращаемое функцией значение есть указатель на массив, то к вызову функции можно применить операцию индексирования (операцию "квадратные скобки") (08_17_1.с).

!Правила построения рекурсивных функций: при самовызовах функции должны изменяться ее параметры; в теле функции должны проверяться условия, обеспечивающие завершение последовательности рекурсивных обращений (08_18.с –

08_21.с).

!Итерационные алгоритмы реализуются с помощью циклов, рекурсивные – с помощью самовызовов функций (08_18.с,

08_18_1.с, 08_19.с, 08_20.с).

!Итерационный алгоритм можно заменить на адекватный ему ре-

курсивный (08_18.с, 08_18_1.с).

!Разрабатывая функции с переменным числом аргументов, всегда используйте препроцессорные средства из заголовочного файла stdarg.h.

!Применение типа va_list и макросов va_start(), va_arg(), va_end() обеспечивает мобильность текста функции с переменным числом аргументов.

!Функция с переменным числом аргументов при вызове всегда должна получать сведения об их реальном количестве.

!Для указания реального числа аргументов используется один из них. Он либо задает их конкретное количество (08_21.с, 08_23.с), либо служит признаком окончания списка аргументов

(08_22.с, 08_24.с, 08_25.с).

!В библиотечных функциях printf() и scanf() количество аргументов определяется количеством спецификаций преобразования, использованных в форматной строке.

!Список аргументов, количество которых не фиксировано, можно в теле функции "перебирать" многократно, возвращаясь к его началу с помощью макроса va_start() (08_25.с).

!Используя несколько указателей типа va_list, можно получить доступ к переменному списку аргументов функции в произвольном порядке (08_26.с).

!Только зная тип аргумента, адресованного указателем типа va_list, можно с помощью макроса va_arg() получить его значение и перейти к следующему аргументу.

332

Тема 9

Строки

Если написать последовательность символов, располагая их один за другим, то получится цепочка символов… Термины "слово" и "строка" часто используются как синонимы термина цепочка.

А. Ахо, Д. Ульман. Теория синтаксического анализа, перевода и компиляции

Основные вопросы темы

!Представление в памяти строковых констант.

!"Превращение" символьного массива в строку в стиле Си.

!Символьный массив как параметр функции.

!Ввод символьных строк из входного потока.

!Строки как параметры функций.

!Перекодировка символов русского алфавита из кода MS-DOS в код

Windows и обратно.

!Типовые задачи обработки символьных данных.

!Применение библиотечных функций для обработки символьной информации.

!Символьное представление арифметических значений.

!Статическая и динамическая память в обработке строк.

!"Упаковка" и "распаковка" разнотипных данных в строках.

!Передача данных исполняемой программе через аргументы командной строки.

!Регулярные выражения.

!Инициализация массивов указателей на строки.

!Сортировка строк с использованием массивов указателей.

9.1. Представление строк

всимвольных массивах

Вязыке Си нет строковых переменных. Известно, что строковая константа из программы на Си хранится в памяти ЭВМ как последовательность символов, завершаемая кодом символа '\0'. Этот символ

333

отсутствует в исходном тексте программы и автоматически добавляется к коду каждой строковой константы компилятором. Строковая константа может быть использована непосредственно в соответствующих выражениях. Кроме того, доступ к строковой константе может осуществляться с помощью указателя типа char *. Здесь существуют особенности, которые следует хорошо понимать.

ЗАДАЧА 09-01. Определите указатель типа char * и инициализируйте его строковой константой. С помощью операции sizeof определите размер строковой константы и размер указателя. Выведите на печать с помощью функции puts() собственно строковую константу и значение, адресуемое указателем.

/* 09_01.c - строковые константы и адресующие их указатели */

#include <stdio.h>

#define PRINTI(EXPRESSION) \ printf(#EXPRESSION"=%d\n",EXPRESSION)

int main ()

{

char * pch = "string"; PRINTI(sizeof("string")); PRINTI(sizeof(pch)); printf("puts(\"string\")="); puts("string"); printf("puts(pch)="); puts(pch); printf("puts(pch+2)="); puts(pch+2);

return 0;

}

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

sizeof("string")=7

sizeof(pch)=4

puts("string")=string

puts(pch)=string

puts(pch+2)=ring

Как видно из результатов, строковая константа "string", состоящая из шести символов, заняла в памяти 7 байт. Операция sizeof, применяемая к указателю, вычисляет размер занимаемой им памяти (в данной реализации 4 байта), а не размеры адресуемого указателем объекта (строковой константы). В противоположность этому функ-

334

ция puts() выводит не значение указателя char *, а значение адресуемой им строки с той позиции, на которую он "настроен". Иллюстрацией последнего из утверждений в приведенной программе служит puts(pch+2);.

ЗАДАНИЕ. Используйте для вывода значения строковой константы, адресуемой указателем, функцию printf() со спецификацией %s.

ЗАДАЧА 09-02. Инициализируйте указатель типа char * строковой константой. Последовательно увеличивая значение указателя, напечатайте адресуемые им символы строковой константы и их числовые коды.

/* 09_02.c - символы строковых констант и их коды */ #include <stdio.h>

int main ()

{

char * pch = "string"; do

printf("\nsymbol: %c, code: %d",*pch,*pch); while(*pch++ != '\0');

return 0;

}

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

symbol: s, code: 115 symbol: t, code: 116 symbol: r, code: 114 symbol: i, code: 105 symbol: n, code: 110 symbol: g, code: 103 symbol: , code: 0

В конце каждой итерации цикла печати символ строки, адресуемый указателем pch, сравнивается с признаком конца строки '\0'. Затем постфиксная операция ++ изменяет значение указателя. Для вывода символов и их кодов в функции printf() использованы, соответ-

335

ственно, спецификации %c и %d. Символ-ограничитель строки '\0' выведен как пробел.

Массив типа char[] – это не символьная строка, однако его можно заставить представлять символьные строки, и не только строковые константы, но и строковые переменные.

ЗАДАЧА 09-03. Определите символьный массив и инициализируйте его списком символьных констант, среди которых имеются символы '\0'. Определите указатели типа char * и присвойте им адреса разных элементов массива. Используйте имя массива и имена указателей в качестве аргументов функции puts(). Замените символы '\0', расположенные в середине массива, символом '-' (дефис) и напечатайте измененный массив функцией puts().

// 09_03.c - ограничители строк в символьном массиве

#include <stdio.h> int main ()

{

char line[] = {'N','o','t','\0','s','t','r','i','n','g','\0'};

char

*

pbegin = line;

 

 

 

char

*

ptail = &line[6];

puts(line);

printf("puts(line) =

");

printf("sizeof(line)

=

%d\n",sizeof(line));

printf("puts(pbegin)

=

");

puts(pbegin);

printf("sizeof(pbegin) = %d\n",sizeof(pbegin)); printf("puts(ptail) = "); puts(ptail);

line[3]='-';

puts(line);

printf("puts(line) = ");

return 0;

 

}

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

puts(line) = Not sizeof(line) = 11 puts(pbegin) = Not

336

sizeof(pbegin) = 4 puts(ptail) = ring puts(line) = Not-string

Как видно из результатов, функция puts() выводит последовательность символов массива типа char[], начиная от переданного ей адреса и до ближайшего символа-ограничителя '\0'. При этом не делается различия между именем массива (line) и указателем (pbegin) на первый элемент массива. Операция sizeof, применяемая к имени символьного массива, вычисляет его длину независимо от наличия ограничителя '\0'. В то же время операция sizeof строго различает имя массива (line) от указателя-переменной, адресующей начало массива (pbegin). В последнем случае вычисляется размер указателя. Обратите внимание на операторы puts(line) в начале программы и в конце, когда равный '\0' элемент line[3] сменил значение.

ЗАДАНИЕ. Сделайте попытку обойтись в программе 09_03.с без указателя ptail, "настраивая" имя массива line на line[6].

Попытка будет неудачной – компилятор выдаст сообщение об ошибке, так как имя массива нельзя изменить. Сравните этот пример с программой 09_02.с, где указатель, инициализированный адресом начала символьной константы, изменяется в процессе выполнения программы.

ЗАДАЧА 09-04. Инициализируйте строковой константой массив с элементами типа char, вычислите и напечатайте размер массива, затем выведите на печать строку из массива в обратном порядке (перебирая символы справа налево). В определении массива явно не указывайте его размер.

В программе 09_01.с мы убедились, что применение операции sizeof к указателю, адресующему строковую константу, позволяет вычислить не размер строки, а размер указателя. В противоположность этому размер (количество элементов) массива char[], инициализированного строковой константой, можно вычислить, применяя операцию sizeof к имени массива. Воспользуемся этой возможностью в следующей программе, решающей сформулированную задачу:

337

/* 09_04.c - символьные массивы и представление строк */

#include <stdio.h> int main ()

{

int i,

arraySize;

 

char

array[] = "123456789";

 

char

*

ps;

puts(array);

printf("puts(array) = ");

arraySize = sizeof(array); printf("sizeof(array) = %d\n", arraySize); for( ps = (array + arraySize - 2);

ps != array - 1; ps--) printf("%c", *ps);

return 0;

}

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

puts(array) = 123456789 sizeof(array) = 10 987654321

Массив char array[] занял в памяти 10 байт, представляя строку из девяти символов. Указатель ps в заголовке цикла настраивается на последний значащий символ строки 9 и последовательно уменьшается до значения (array-1), при котором цикл завершается.

ЗАДАЧА 09-05. По аналогии с рекурсивной функцией быстрой сортировки массива с элементами типа int (тема 8, программа 08_20.с) напишите функцию упорядочения элементов символьного массива по возрастанию числовых значений их кодов. В основной программе определите массив типа char[] и инициализируйте его символьной константой, затем, используя функцию упорядочения, обработайте массив и выведите на печать его символы и их коды.

В функциях из программы 08_20.с изменения будут простейшими – нужно заменить тип элементов сортируемого массива. Прототипы функций станут такими:

void swap(char v[], int i, int j); void quicksort(char v[], int n);

338

Тексты определений функции после знакомства с функциями из 08_20.с приводить здесь излишне (см. программу 09_05.с), но основную функцию стоит рассмотреть. Она может быть такой:

int main()

{

int i;

char charArray[] = "1234554321"; int

sizeArray=sizeof(charArray)/sizeof(charArray[0]); quicksort(charArray, sizeArray-1);

printf("Sort Array:\n"); for (i=0; i<sizeArray; i++)

printf("[%d]=%c(%d)%c", i,charArray[i],charArray[i], (i+1)%5?'\t':'\n');

return 0;

}

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

Sort Array:

[0]=1(49) [1]=1(49) [2]=2(50) [3]=2(50) [4]=3(51) [5]=3(51) [6]=4(52) [7]=4(52) [8]=5(53) [9]=5(53) [10]= (0)

В программе определен и инициализирован символьный массив из 11 элементов, последний из которых (с индексом 10) вводится компилятором и имеет значение '\0'. Этот элемент приходится исключать из сортировки по возрастанию значений кодов, иначе он будет перемещен из конца строки. Поэтому обращение к функции быстрой сортировки имеет вид:

quicksort(charArray, sizeArray-1);

После сортировки печатаются значения элементов массива (символы) и их десятичные коды (в круглых скобках). Элемент массива с индексом 10 остался без изменений. В нем находится ограничитель строки – символ '\0' (с кодом 10), изображаемый пробелом.

339

ЗАДАНИЕ. Включите в программу 09_05.с печать массива charArray[] функцией puts() до сортировки и после нее.

(Результат очевиден.)

ЗАДАНИЕ. Замените обращение к функции сортировки на такое:

quicksort(charArray, sizeArray);

Напечатайте функцией puts() массив до сортировки и после нее.

(См. программу 09_05_1.с.) Символ '\0' с наименьшим кодом переместится в начало массива. Поэтому после такого упорядочения массива функция puts(charArray) ничего не напечатает, ибо "найдет" окончание строки в начале массива.

Одну строку можно адресовать несколькими указателями и гибко выполнять обработку символов строки. В качестве примера рассмотрите следующую программу для распознавания палиндромов. Известно, что палиндром – это слово или фраза, одинаково читающаяся слева направо и обратно. Например, "кабак", "Аргентина манит негра" (если не учитывать пробелы и прописную букву А) и т.д.

ЗАДАЧА 09-06. Определите в программе символьный массив фиксированных размеров. Введя с клавиатуры в этот массив предложение, проверьте, является ли оно палиндромом. При решении задачи учтите, что предложение в ряде случаев воспринимается как палиндром, когда не учитывается наличие пробелов между словами.

Для ввода строк в программах на языке Си обычно используются функция scanf() со спецификацией преобразования %s и функция gets(). Первая прочитывает символы из стандартного входного потока до ближайшего обобщенного пробела, т.е. из набираемого на клавиатуре предложения с пробелами вводит только первое слово. Функция gets() позволяет вводить пробелы, т.е. из входного потока выбираются все символы до появления кода '\n', формируемого при нажатии клавиши ENTER. И функция scanf() со спецификацией %s, и функция gets() добавляют в конец введенной последовательности символов код '\0', т.е. формируют правильную строку в стиле Си.

340