Язык Си - Уэйт, Прата, Мартин. M. уэит с. Прата д. Мартин
Скачать 4.69 Mb.
|
printf( ) довольно основательно. Подобно puts( ), она использует указатель строки в качестве аргумента. Функция printf( ) менее удобна, чем puts( ), но более гибка. Разница заключается в том, что printf( ) не выводит автоматически каждую строку текста с новой строки. Вы должны указать, что хотите выводить с новых строк. Так, printf(" %s\n" , string); 262 дает то же самое, что и puts(string); Вы можете видеть, что первый оператор требует ввода большего числа символов и большего времени при выполнении на компьютере. С другой стороны, printf( ) позволяет легко объединять строки для печати их в одной строке. Например: printf(" Хорошо, %s, %s \n", name, MSG); объединяет " Хорошо" с именем пользователя и c символьной строкой MSG в одну строку. СОЗДАНИЕ СОБСТВЕННЫХ ФУНКЦИЙ Далее Содержание Не ограничивайте себя при вводе и выводе только этими библиотечными функциями. Если у вас нет нужной функции, или она вам не нравится, можно создавать свои собственные версии, используя для этого getchar( ) и putchar( ). Предположим, у вас нет функции puts( ). Вот один из путей ее создания: /* put1 - печатает строку */ put1(string); char *string; { while(*string != '\0') putchar(*string++); putchar('\n'); } Символьный указатель string вначале ссылается на первый элемент вызванного аргумента. После печати его содержимого указатель увеличивается и ссылается уже на следующий элемент. Это продолжается до тех пор, пока указатель не дойдет до элемента, содержащего нуль-символ. Затем в конце строки будет поставлен символ новой строки. Предположим, у вас есть puts( ), но вам нужна функция, которая, кроме того, сообщает, сколько напечатано символов. Эту возможность легко добавить: /* put2- - печатает строку и считывает символы */ put2 (string); char *string; { int count = 0; while(*string != '\0') { putchar(* string++); count++; putchar('\n'); return(count); } Вызов: put2(" пицца" ); печатает строку пицца, в то время как оператор num = puts(" пицца"); передаст, кроме того, количество символов в num; в данном случае это число 5. Вот несколько более сложный вариант, показывающий вложенные функции: 263 /* вложенные функции */ #include main( ) { put1("Если бы я имел столько денег, сколько могу потратить,"); рrintf("Я считаю %d символа.\n", put2(" Я никогда бы нe жаловался, что приходится чинить старые стулья."); } (Мы включили в программу при помощи директивы #include файл stdio.h, потому что в нашей системе в нем определена функция putchar( ), а она используется в нашей новой функции.) Да-а, мы используем функцию printf( ) для печати значения put2( ), но в процессе нахождения значения put2( ) компьютер должен сначала заставить ее поработать - напечатать строку. Вот что получается при этом: Если бы я имел столько денег, сколько могу потратить, Я никогда бы нe жаловался, что приходится чинить старые стулья. Я считаю 63 символа. Теперь вы можете построить работающую версию функции gets( ); она должна быть похожа на нашу функцию getint( ) из гл. 10, но гораздо проще ее. ФУНКЦИИ, РАБОТАЮЩИЕ СО СТРОКАМИ Далее Содержание Большинство библиотек языка Си снабжено функциями, работающими со строками. Рассмотрим четыре наиболее полезных и распространенных: strlen( ), strcat( ), strcmp( ) и strcpy( ). Мы уже применяли функцию strlen( ), которая находит длину строки. Используем ее в нижеследующем примере функции, укорачивающей длинные строки. Функция strlen( ) Далее Содержание /* Функция Прокруста */ fit(string, size) char *string; int size; { if(strlen(string) > size) *(string + size) = '\0'; } Проверьте ее в "деле" в этой тестовой программе: /* тест */ main( ) { static char mesg[ ] = "Ну, теперь держитесь, компьютероманы."; puts(mesg); fit(mesg, 10); puts(mesg); } Программа выдает: Ну, теперь держитесь, компьютероманы. 264 Ну, теперь Наша функция помещает символ '\0' в одиннадцатый элемент массива, заменяя символ пробела. Остаток массива остается на старом месте, но puts( ) прекращает работу на первом нуль-символе и игнорирует остаток массива. Функция strcat( ) Далее Содержание Вот что умеет делать функция strcat( ): /* объединение двух строк */ #include < stdio.h> main( ) { static char flower [80]; static char addon[ ] = "ы пахнут старыми ботинками."; puts(" Назовите ваш любимый цветок." ); gets(flower); strcat (flower, addon); puts(flower); puts(addon); } Получаем на экране: Назовите ваш любимый цветок. Ирис Ирисы пахнут старыми ботинками. ы пахнут старыми ботинками. Очевидно, что strcat( ) (string concatenation) использует в качестве аргументов две строки. Копия второй строки присоединяется к концу первой, и это объединение становится новой первой строкой. Вторая строка не изменяется. Внимание! Эта функция не проверяет, умещается ли вторая строка в первом массиве. Если вы ошиблись при выделении памяти для первого массива, то у вас возникнут проблемы. Конечно, можно использовать strlen( ) для определения размера строки до объединения. /* Объединение двух строк, проверка размера первой */ #include #define SIZE 80 main( ) { static char flower[SIZE]; static char addon[ ] = " ы пахнут старыми ботинками." ; puts(" Назовите ваш любимый цветок. "); gets(flower); if((strlen(addon) + strlen(flower) + 1) < SIZE) strcat (flower, addon); puts(flower); } Мы добавляем 1 к объединенной длине для размещения нуль-символа. Функция strcmp( ) Далее Содержание Предположим, что вы хотите сравнить чей-то ответ со строкой, находящейся в 265 памяти: /* Будет ли это работать? */ #include #define ANSWER " Грант" main( ) { char try [40]; puts(" Кто похоронен в могиле Гранта?" ); gets(try); while(try != ANSWER) puts(" Нет, неверно. Попытайтесь еще раз." ); gets(try); } puts(" Правильно."); } Хотя эта программа и смотрится неплохо, она не будет работать правильно, try и ANSWER на самом деле являются указателями, поэтому сравнение (try != ANSWER) спрашивает не о том, одинаковы ли эти две строки, а одинаковы ли два адреса, на которые ссылаются try и ANSWER. Так как ANSWER и try запоминаются в разных ячейках, эти два указателя никогда не могут быть одним и тем же, и пользователю всегда сообщается, что программа неверна. Такие программы обескураживают людей. Нам нужна функция, которая сравнивает содержимое строк, а не их адреса. Можно было бы придумать ее, но это уже сделала за нас функция strcmp( ) (string comparision). Теперь исправим нашу программу: /* это будет работать */ #includе #define ANSWER " Грант" main( ) { char try [40]; puts(" Кто похоронен в могиле Гранта?" ); gets(try); while(strcmp(try, ANSWER) != 0) { puts(" Нет, неверно. Попытайтесь еще раз."); gets(try); } puts(" Правильно!"); } Так как ненулевые значения интерпретируются всегда как "true", мы можем сократить оператор while do while(strcmp(try, ANSWER)). Из этого примера можно сделать вывод, что strcmp( ) использует два указателя строк в качестве аргументов и возвращает значение 0, если эти две строки одинаковы. Прекрасно, если вы придете к такому выводу. Хорошо, что Strcmp( ) сравнивает строки, а не массивы. Поэтому, хотя массив try занимает 40 ячеек памяти, а " Грант" - только 6 (не забывайте, что одна нужна для нуль-символа), сравнение выполняется только с частью try, до его первого нуль-символа. Такую функцию strcmp( ) можно использовать для сравнения строк, находящихся в массивах разной длины. А что если пользователь ответил " ГРАНТ" или " грант" или "Улиссес С. Грант" ? Хорошо, если пользователю сказали, что он ошибся? Чтобы сделать программу гибкой, вы должны предусмотреть несколько допустимых правильных ответов. Здесь есть некоторые тонкости. Вы могли бы в операторе #define определить в качестве ответа " ГРАНТ" и написать функцию, которая превращает любой ответ только в это слово. Это 266 устраняет проблему накопления, но остаются другие поводы для беспокойства. Между прочим, какое значение возвращает strcmp( ), если строки не одинаковы? Вот пример: /* возвраты функции strcmp */ #include main( ) { printf(" %d \n" , strcmp( "A" , " A" )); printf(" %d \n" , strcmp( "A" , " B" )); printf(" %d \n" , strcmp( "B" , " A" )); printf(" %d \n" , strcmp( "C" , "A" )); printf(" %d \n" , strcmp(" apples", " apple")); } В результате получаем 0 -1 1 2 115 Как мы и предполагали, сравнение "А" с самим собой возвращает 0. Сравнение "А" с "В" дает -1, а "В" с "А" дает 1. Это наводит на мысль, что strcmp( ) возвращает отрицательное число, если первая строка предшествует второй в алфавитном порядке, или положительное число, если порядок иной. Кроме того, сравнение "С" с "А" дает 2 вместо 1. Картина проясняется: функция возвращает разницу между двумя символами в коде ASCII. В более общем смысле strcmp() передвигается вдоль строк до тех пор, пока не находит первую пару несовпадающих символов; затем она возвращает разницу в кодах ASCII. Например, в самом последнем примере "apples" и "apple" совпадают, кроме последнего символа 's', в первой строке. Он сопоставляется с шестым символом в "apple", который является нуль-символом (0 в ASCII). Возвращается значение 's' - '\0' = 115 - 0 = 115, где 115 является кодом буквы 's' в ASCII. Обычно вам не нужно точно знать возвращаемое значение. Чаще всего вы только хотите знать, нуль это или нет, т. е. было ли совпадение. Или, может быть, вы пытаетесь отсортировать строки в алфавитном порядке и хотите узнать, в каком случае сравнение дает положительный, отрицательный или нулевой результат. Можно использовать эту функцию, чтобы проверить, остановится ли программа, читая вводимую информацию: /* Начало какой-то программы */ #include &stdio.h& #define SIZE 81 #define LIM 100 #define STOP " " /* нулевая строка */ main( ) { static char input[LIM][SIZE]; int ct = 0; while(gets(input[ct]) != NUL && strcmp(input[ct].STOP) !=0 && ct++ < LIM) } 267 Программа прекращает чтение вводимой строки, если встречает символ EOF [в этом случае gets( ) возвращает NULL], или если вы нажимаете клавишу [ввод] в начале строки (т.e. введете пустую строку), или если вы достигли предела LIM. Чтение пустой строки дает пользователю простой способ прекращения ввода. Давайте перейдем к последней из обсуждаемых нами функций, работающих со строками. Функция strcpy( ) Далее Содержание Мы уже говорили, что если pts1 и pts2 являются указателями строк, то выражение pts2 = ptsl; копирует только адрес строки, а не саму строку. Предположим, что вы все же хотите скопировать строку. В этом случае можно использовать функцию strcpy( ). Она работает примерно так: /* демонстрация strcpy( ) */ #include #define WORDS "Проверьте, пожалуйста, вашу последнюю чапись." main( ) { static char *orig = WORDS; static char copy [40]; puts(orig); puts(copy); strcpy(copy, orig); puts(orig); puts(copy); } Вот результат: Проверьте, пожалуйста, вашу последнюю запись. Проперьтe, пожалуйста, пашу последнюю запись. Проверьте. пожалуйста, пашу последнюю запись. Очевидно, что строка, на которую указывает второй аргумент (orig) функции strcpy( ), скопирована в массив, на который указывает первый аргумент (copy). Порядок аргументов функции такой же, как в операторе присваивания: строка, получающая значение, стоит слева. (Пустая строка является результатом печати массива copy до копирования, и она говорит о том, что статические массивы .инициализируются нулями, т. е. нуль-символами в символьном виде.) Нужно обеспечить, чтобы размер массива, принимающего строку, был достаточен для ее размещения. Поэтому мы используем описание static char copy [40]; а не static char *copy; /* не выделяет память для строки */ Короче говоря, strcpy() требует два указателя строк в качестве аргументов. Второй указатель, ссылающийся на исходную строку, может быть объявленным указателем, именем массива или строковой константой. А первый указатель, ссылающийся на копию, должен ссылаться на массив или часть массива, имеющего размер, достаточный для размещения строки. 268 Теперь, когда мы описали несколько функций, работающих со строками, рассмотрим целую программу, работающую со строками. ПРИМЕР: СОРТИРОВКА СТРОК Далее Содержание Возьмем реальную задачу сортировки строк в алфавитном порядке. Эта задача может возникнуть при подготовке списка фамилий, при создании алфавитного указателя и во многих других ситуациях. В такой программе одним из главных инструментов является функция strcmp( ), так как ее можно использовать для определения старшинства двух строк. Последовательность наших действий будет состоять из считывания массива строк, их сортировки и последующего вывода. Совсем недавно мы показали последовательность действий для считывания строк, и сейчас мы начнем программу таким же образом. /* считывает строки и сортирует их */ #include #define SIZE 81 /* предельная длина строки, включая \0 */ #define LIM 20 /* максимальное количество считываемых строк */ #define HALT " " /* нулевая строка для прекращения ввода */ main( ) { static char input[LIM][SIZE]; /* массив для запоминания вводимых строк */ char *ptstr[LIM]; /* массив переменных типа указатель */ int ct = 0; /* счетчик вводимых строк */ int k; /* счетчик выводимых строк */ printf(" Введите до %d строк и я их отсортирую.\n" , LIM); printf(" Для прекращения ввода нажмите клавишу [ввод] в начале строки.\n"); while((gets(input[ct])!= NULL) && strcmp(input[ct], HALT) != 0 && ct++ < LIM) ptstr[ct - 1] = input[ct - 1]; /*указывает на еще не отсортированный ввод */ stsrt(ptstr, ct); /* сортировка строк */ puts(" \n Вот отсортированный список строк:\n"); for(k = 0; k < ct; k++) puts(ptstr[k]); /* указатели на отсортированные строки */ } /* функция сортировки-строк-с-использованиeм-указатeлeй */ stsrt(strings, num) char *strings[ ]; int num; { char *temp; int top, seek; for(top = 0; top < num-1; top++) for(seek = top + 1; seek < num; seek++) if(strcmp(strings[top], strings[seek]) > 0) { temp = strings [top]; strings [top] = strings [seek]; strings [seek] = temp; } } РИС. 13.4. Программа чтения и сортировки строк. Вывод строк на печать не составляет проблемы, а для сортировки можно взять тот же алгоритм, который использовался раньше для чисел. Сейчас мы применим один хитрый трюк: посмотрим, сможете ли вы его заметить. Для проверки возьмем детский стишок. Введите 20 строк, и я их отсортирую. Для прекращения ввода нажмите клавишу [ввод] в начале строки. 269 Жил на свете человек Скрюченные ножки И гулял он целый век По скрюченной дорожке Вот отсортированный список строк Жил на свете человек И гулял он целый век По скрюченной дорожке Скрюченные ножки Детские стишки не кажутся слишком искаженными после сортировки их по алфавиту. Трюк состоит в том что вместо перегруппировки самих строк мы перегруппировали их указатели. Разберемся в этом. В начале ptrst[0] ссылается на input[0] и т. д. Каждый input[ ] является массивом из 81 элемента, а каждый элемент ptrst[ ] является отдельной переменной. Процедура сортировки перегруппировывает ptrst, нe трогая input. Если, например, input[l] стоит перед input[0] по алфавиту, то программа переключает указатели ptrst, в результате чего ptrst[0] ссылается на input[1], a ptrst[1] на input[0]. Это гораздо легче, чем, используя strcpy( ), менять местами две введенные строки. Просмотрите еще раз этот процесс на рисунке. И наконец, давайте попытаемся заполнить пробелы, оставшиеся в нашем описании, а именно "пустоту" между скобками в функции main( ). АРГУМЕНТЫ КОМАНДНОЙ СТРОКИ Далее Содержание Командная строка - это строка, которую вы печатаете на клавиатуре, чтобы запустить вашу программу. Это нетрудно. Предположим, у нас есть программа в файле с именем fuss. В этом случае командная строка выглядела бы так: 270 РИС. 13.5. Указатели сортируемых строк. или, может быть, А > fuss с использованием двух системных приглашений. Аргументы командной строки являются дополнительными элементами в той же самой строке: %fuss - r Ginger Следует заметить, что программа на языке Си может вводить информацию в эти элементы и применять их для собственных нужд. Этот механизм предназначен для использования аргументов функции main( ). Вот типичный пример: /* main( ) с аргументами */ main(argc, argv) int argc; char *argv[ ]; { int count; for(count = 1; count < argc; count++) printf(" %s", argv[count]); printf("\n"); 271 } Поместите эту программу в выполняющий файл, названный echo, и вот что произойдет: А > echo Я мог бы воспользоваться небольшой помощью. Я мог бы воспользоваться небольшой помощью. Вероятно, вы видите, почему функция называется echo, но еще не можете понять, как она работает. Может быть, это объяснение поможет вам (мы надеемся). Компиляторы Си предполагают наличие у main( ) двух аргументов. Первый аргумент представляет количество строк, следующих за командным словом. Обычно (но не обязательно) этот аргумент типа |