Язык Си - Уэйт, Прата, Мартин. M. уэит с. Прата д. Мартин
Скачать 4.69 Mb.
|
part2; в этом случае запись оператора начинается с метки, за которой следует двоеточие. part2: printf(" Уточненный анализ:\n"); Избегайте использовать goto Далее Содержание В принципе вы никогда не обязаны пользоваться оператором goto при программировании на Си. Но если ваш предыдущий опыт связан с работой на Фортране или Бейсике, в каждом из которых требуется его использовать, то у вас могли выработаться навыки программирования, основанные на применении данного оператора. Чтобы помочь вам преодолеть эту привычку, ниже вкратце приводится несколько знакомых вам ситуации, реализуемых с помощью goto, а затем показывается, как это можно осуществить другими средствами, в большей степени соответствующими духу языка Си. 1. Работа в ситуации, когда в операторе if требуется выполнить более одного оператора: if(size > 12) goto a; goto b; a: cost = cost * 1.05; flag = 2; b: bill = cost * flag; (В стандартных Бейсике и Фортране только один оператор, непосредственно следующий за if-условием, считается относящимся к оператору if. Мы выразим это с помощью эквивалентного фрагмента на Си.) Обычный подход, применяемый в языке Си и заключающийся в использовании составного оператора, или блока, упрощает понимание смысла программы: if(size > 12); { cost = cost * 1.05; flag = 2; } bill = cost * flag; 2. Осуществление выбора из двух вариантов: if(size > 14) goto a; sheds = 2; goto b; a: sheds = 3; b: help = 2 * sheds; Наличие в языке Си структуры if-else позволяет реализовать такой выбор более наглядно: if(ibex > 14) sheds = 3; else sheds = 2; help = 2 * sheds; 3. Реализация бесконечного цикла: readin: scanf(" %d", &score); if(score < 0) goto stagc2; 166 большое количество операторов; goto readin: stagc2: дополнительная чепуха; } Эквивалентный фрагмент, в котором используется цикл while, выглядит так: scanf(" %d", &score); whilet(score >= 0) { большое количество операторов; scanf("%d", &score); } дополнительная чепуха; 4. Пропуск операторов до конца тела цикла: используйте оператор continue. 5. Выход из цикла: используйте оператор break. Фактически break и continue являются специальными формами оператора goto. Преимущество их использования заключается в том, что, во-первых, названия этих операторов говорят об осуществляемых ими функциях, а во-вторых, поскольку они нe используют меток, отсутствует опасность пометить не тот оператор программы. 6. Выполнение переходов к различным частям программы непредсказуемым образом: так программировать нельзя! Существует один случай, когда использование оператора goto допускается опытными программистами, работающими на языке Си,- это выход из вложенного набора циклов при обнаружении каких-то ошибок. (Применение оператора break даст возможность осуществить выход только из самого внутреннего цикла.) while(funct > 0) { for(i = 1; i < 100; i++) { for(j = 1; j<= 50; j++) { большое число операторов; if(большая ошибка) goto help; операторы; } еще некоторое количество операторов; } и еще некоторое количество операторов; } и еще операторы; help: устранение ошибки; Как вы можете заметить из наших примеров, альтернативные формы представления программ более понятны, чем формы, использующие оператор goto. Эти различия станут еще большими, если вы объедините некоторые из рассмотренных случаев. Какие операторы goto используются при реализации операторов if, какие моделируют конструкции if-else, какие управляют работой циклов, а какие появляются лишь потому, что пользователь написал программу так, что не может без них обойтись? Чрезмерное увеличение оператором goto приводит к созданию лабиринта в логике программы. Дадим вам совет: если вы совсем не знакомы с оператором goto, то не применяйте его вовсе; если вы привыкли пользоваться им, попытайтесь отучить себя от этого. Ирония заключается в том, что в языке Си, который вовсе не нуждается в операторе goto, его структура оказывается лучшей, чем в большинстве других языков программирования, поскольку в качестве меток можно использовать смысловые имена, а не числа. Резюме: переходы в программах I. Ключевые слова: break, continue, goto II. Общие замечания 167 Выполнение каждого из этих трех операторов языка вызывает скачкообразное изменение процесса выполнения программы, т. е. переход от одной команды программы к другой (не следующий за ней непосредственно). III. break Оператор break можно использовать внутри любой из трех форм цикла и конструкции switch. Его выполнение приводит к тому, что управление программой, минуя оставшуюся часть тела цикла или конструкцию switch, содержащую данный оператор, передается на следующую (за этим циклом или за конструкцией switch) команду. Пример: switch(number) { case 4: printf(" Это хороший выбор.\n"); break; case 5: printf(" Это неплохой выбор.\n"); break; default: рrintf(" Это плохой выбор.\n"); } IV. Continue Оператор continue может использоваться в любой из трех форм циклов, но нe в операторе switch. Его выполнение приводит к такому изменению логики программы, что остальные операторы тела цикла пропускаются. Для циклов while или for вслед за этим начинается новый шаг, а для цикла do while проверяется условие на выходе, и затем, если оно оказывается истинным, выполняется следующая итерация. Пример while((ch = gctchar( ))!= EOF) { if(ch == ' ') continue; putchar(ch); chcount++; } В этом фрагменте осуществляется эхо-печать читаемых символов и подсчитывается число символов, отличных от пробела. V. goto Выполнение оператора goto вызывает передачу управления в программе оператору, помеченному указанной меткой. Для отделения оператора от соответствующей ему метки используется двоеточие. Имена меток образуются но тем же правилам, что и имена переменных. Помеченный оператор может появиться в программе текстуально до или после goto. Форма: goto метка; метка: оператор Пример toр : ch = getchar( ); if (ch != 'y') goto top; 168 МАССИВЫ Далее Содержание Массивы являются важнейшим средством языка, применяемым во многих программах. Их использование позволяет удобным способом размещать в памяти большое количество необходимой информации. Позже мы посвятим массивам целую главу, но, поскольку они очень тесно связаны с циклами, мы хотим начать их использовать уже сейчас. Массив - это набор переменных, имеющих одно и то же базовое имя и отличающихся одна от другой числовым признаком. Например, с помощью описания float debts [20]; объявляется, что debts - массив, состоящий из двадцати членов, или "элементов". Первый элемент массива называется debts[0], второй - debts[l], и т. д. вплоть до debts[19]. Заметим, что перечисление элементов массива начинается с 0, а не с 1. Поскольку мы объявили, что массив имеет тип float, каждому его элементу можно присвоить величину типа float. К примеру, можно писать так: debts[5] = 32.54; debts[6] = 1.2е+21; Массивы могут быть образованы из данных любого тина: int nannies[22]; /* масснв, содержащий 22 целых чнсла */ char alpha[26]; /* масснв, содержащий 26 символов */ long big[500]; /*массив, содержащий 500 целых чисел типа long */ Раньше, например, мы говорили о строках, являющихся частным случаем массива типа char. (В общем массив типа char - это массив, элементами которого являются величины типа char. Строка - массив типа char, в котором нуль-символ '\0' используется для того, чтобы отмсчать конец строки.) 169 РИС. 8.6. Массивы символов и строки Числа, используемые для идентификации элементов массива, называются "подстрочными индексами" или просто "индексами". Индексами должны быть целые числа, и, как уже упоминалось, индексирование начинается с 0. Элеметы массива размешаются в памяти последовательно, друг за другом, как показано на рис. 8.6. РИС. 8.7. Размещение в памяти массивов с элементамн типа char и int Существует огромное количество возможностей применения массивов. Ниже приводится сравнительно простой пример. Предположим, вы хотите написать программу, осуществляющую ввод 10 различных результатов спортивной игры, которые будут обрабатываться позже. Используя понятие массива, вы сможете избежать придумывания 10 различных имен переменных - по одной для каждого счета. Кроме того, для чтения данных вы можете воспользоваться циклом for: /* ввод счета */ main( ) { int i, score[10]; for (i = 0; i <= 9; i++) scanf(" %d", &a[i]); /* ввод десяти результатов */ printf(" Введены следующие результаты :\n" ); for (i = 0; i <= 9; i++) printf(" %5d", a[i]); /* проверка правильности ввода */ printf("\n"); } В понятие хорошего стиля программирования входит эхо-печать только что введенных величин. Она даст уверенность, что в программе будут обрабатываться те данные, для которых она предназначена. 170 Применяемый здесь способ гораздо более удобен, чем использование 10 различных операторов scanf() и 10 отдельных операторов printf() для ввода и проверки десяти результатов, определяющих число набранных очков. Цикл for обеспечивает очень простои и удобный способ использования индексов массивов. Какого типа операции могли бы мы выполнить над этими данными? Мы могли бы найти их среднее, величину стандартного отклонения (мы знаем даже, как это сделать) и максимальное значение счета, а также произвести их сортировку в некотором порядке. Займемся двумя простейшими задачами: нахождением среднего и максимального результатов. Чтобы вычислить среднее, мы можем добавить к нашей программе приведенный ниже фрагмент: int sum, average; for(i = 0, sum = 0; i <= 9; i++) /* две инициализации */ sum + = a[i]; /* суммирование элементов массива */ average = sum/10; /*классический метод усреднения */ printf(" Средний результат равен %d.\n", average); Для нахождения максимального результата к программе можно добавить следующий фрагмент: int highest; for(highest = а[0], i = 1; i <= 9; i++) if(a[i] > highest) highest = a[i]; printf ("Максимальный результат равен %d.\n", highest); Здесь мы начинаем с того, что полагаем переменную highest равной величине а[0]. Затем производится сравнение значения highest с каждым элементом массива. Когда обнаруживается, что некоторая величина больше текущего значения переменной highest, ей присваивается эта новая большая величина. Теперь объединим все части программы. На псевдокоде алгоритм ее работы можно записать следующим образом: ввод результатов. эхо-печать результатов. вычисление и печать среднего значения. вычисление и печать максимального значения. Кроме того, мы несколько обобщим нашу программу: /* результаты */ #define NUM 10 main( ) { int i, sum, average, highest, score [NUM]; printf(" Укажите 10 результатов. \n"); for(i = 0; i < NUM; i++) scanf(" %d" , &scorc[i]); /* ввод десяти результатов */ printf(" Введены следующие результаты:\n"); for(i = 0; i printf("\n"); tor(i = 0, sum = 0; i < NUM; i++) sum + = score[i]; /* суммирование элементов массива */ average = sum/NUM; /* классический метод усреднения */ printf(" Средний результат равен %d.\n", average); for(highest = score[0], i = 1; i < NUM; i++) if(score[i] > highest) /* какая из величин больше */ highest = score[i]; printf(" Максимальный результат равен %d.\n", highest); 171 } Мы заменили число 10 символической константой и воспользовались тем, что выражения i <=(NUM-1) и i < NUM эквивалентны. Давайте сначала посмотрим, как это программа работает, а затем сделаем несколько замечаний. Укажите 10 результатов: 76 85 62 48 98 71 66 89 70 77 Введены следующие результаты: 76 85 62 48 98 71 66 89 70 77 Средний результат равен 74. Максимальный результат равен 98. Первый момент, который необходимо отметить, состоит в том, что мы использовали четыре различных цикла for. Вас может заинтересовать вопрос: является ли это на самом деле необходимым или мы можем некоторые из данных операций объединить в одном цикле? Такая возможность существует, и она позволила бы сделать программу более компактной. Однако мы побоялись следовать такому подходу (видите, какие мы впечатлительные люди!), поскольку это противоречит принципу модульности. Смысл, заключенный в данной фразе, состоит в том, что программа должна быть разбита на отдельные единицы, или "модули", причем каждый из них должен выполнять одну задачу. (Наша запись на псевдокоде отражает деление программы на четыре модуля.) Такое разбиение облегчает чтение текста программы. Возможно, еще более важным является то, что если отдельные части программы не перемешаны, ее коррекция или модификация упрощаются. Для этого необходимо только исключить из программы требуемый модуль, заменить его новым, а оставшуюся часть программы не изменять. Второй момент, на который необходимо обратить внимание, состоит в том, что не очень удобно иметь программу, которая обрабатывает ровно 10 чисел. Что произойдет, если кто-то выйдет из игры и будет получено только 9 результатов? Используя символическую константу для обозначения числа 10, мы упростили внесение изменении в программу, по все равно должны произвести ее компиляцию заново. Существуют ли для этого другие возможности? Мы рассмотрим их ниже. ПРОБЛЕМА ВВОДА Далее Содержание Существует несколько способов последовательного ввода набора данных, скажем чисел. Мы обсудим здесь некоторые из них, переходя от менее удобных к более удобным. Вообще говоря, наименее удобный способ - это тот, который мы только что использовали; написание программы, допускающей ввод фиксированного числа элементов данных. (Такой способ, однако, прекрасно подходит для тех ситуаций, когда число входных данных никогда не изменяется.) Если число входных элементов данных изменяется, необходимо осуществить повторную компиляцию программы. Следующий шаг состоит в том, чтобы спросить у пользователя, сколько элементов данных будет введено. Так как размер массива в программе фиксирован, она должна проверить, не превышает ли величина, содержащаяся в ответе пользователя, размер массива. Затем пользователь может начать ввод данных. Тогда начало нашей программы можно переписать следующим образом: printf(" Сколько элементов данных вы будете вводить ?\n"); scanf(" %d", &nbr); while(nbr > NUM) { printf("Я смогу обрабатывать не больше %d элементов; пожалуйста, укажите"); printf("меньшую величину.\n", NUM); scanf("%d", &nbr); } /* гарантирует, что nbr <= NUM - максимального размера массива */ for(i = 0; i 172 Мы можем продолжить движение в этом направлении, заменяя в каждом случае символическую константу NUM в программе (исключая наличие ее в директиве #define и в описании массива) переменной nbr. При таком способе различные операции будут выполняться только над теми элементами массива, в которые введены данные. Недостатком указанного подхода является лежащее в его основе предположение, что пользователь не ошибается при подсчете элементов; если же при программировании полагаться на то, что пользователь всегда все делает правильно, программы оказываются ненадежными. Это подводит нас к следующему методу, при котором в программе осуществляется подсчет количества вводимых чисел. После всего сказанного выше очевидно, что у компьютеров имеются для этого вес возможности. Основная проблема здесь состоит в том, как сообщить компьютеру о завершении ввода чисел. Один из методов - дать пользователю возможность вводить специальный признак, указывающий на конец ввода. Признак должен принадлежать к данным того же типа, что и остальные вводимые данные, так как он должен быть прочитан тем же оператором программы. Но при этом он должен отличаться от обычных данных. К примеру, если бы мы вводили результаты игры, чтобы узнать, кто набрал от 0 до 100 очков, мы не могли бы выбрать число 74 в качестве такого признака, потому что оно может соответствовать некоторому возможному результату. С другой стороны, например, число 999 или - 3 вполне могло бы подойти в качестве такого признака, поскольку оно не соответствует требуемому результату. Ниже приводится программа, являющаяся реализацией этого метода: #define STOP 999 /* признак завершения ввода */ #define NUM 50 main( ) { int i, count, temp, score [NUM]; printf(" Начните ввод результатов. Введите 999 для указания \n"); printf(" конца ввода. Максимальное число результатов, которое вы\n"); printf(" можете ввести.- это %d.\n", NUM); count = 0; scanf(" %d", &temp); /* ввод величины */ while(temp != STOP && count <= NUM) /* проверка наличия признака STOP */ { /* и проверка, не произошло ли превышения размера массива */ score[count++] = temp; /* запись величины в память и коррекция счетчика */ if(count < NUM + 1) scanf("%d", &temp); /* ввод очередного результата */ else printf("Я не могу принять больше данных.\n"); } printf("Bы ввели %d результатов, а именно:\n", count); for(i = 0; i < count; i++) printf("%5d\n", scorc[i]); } Мы вводим данные во временную переменную temp и присваиваем ее значение соответствующему элементу массива только в том случае, если оно не является признаком конца ввода. Совершенно не обязательно реализовывать все именно так; мы просто считаем, что указанный способ делает процесс проверки несколько более наглядным. Обратите внимание на то, что проверяется выполнение двух условий: прочитан ли признак конца ввода и есть ли место в массиве для следующего числа. Если мы заполнили массив данными до того, как указали признак конца ввода, программа вежливо сообщает нам об этом и прекращает ввод данных. Заметьте также, что мы воспользовались постфиксной формой операции увеличения. Поэтому, когда значение |