ЭЛЕКТРОННАЯ БИБЛИОТЕКА КОАПП
Сборники Художественной, Технической, Справочной, Английской, Нормативной, Исторической, и др. литературы.



 

Часть 6

                                                                       
                   ГЛАВА 5. ОСНОВНЫЕ СВЕДЕНИЯ О ЯЗЫКЕ СИ
          
          Приятно видеть, что вы добрались и до этой главы. В главе 4 мы
     показали вам только малую часть возможностей Си, только для возбуж-
     дения  любопытства.  Теперь  вы  готовы погрузиться в глубины этого
     языка, в чем мы вам и поможем в этой главе.
                        Краткое содержание главы 5
          В данной главе освещаются следующие вопросы:
          - структуры данных, включая ссылки, массивы и структуры;
          - оператор switch;
          - команды потока  управления,  включая  return,  break,  goto,
            continue и условный оператор (?:);
          - стиль программирования на Си, особенно по отношению к неко-
            торым расширениям этого языка;
          - некоторые "ловушки", в которые часто попадают прораммисты;
                           Обзор структур данных
          В предыдущей главе мы рассмотрели основные типы данных,  такие
     как числа целые и с плавающей точкой, символы и их варианты. В этой
     главе мы расскажем, как строить при помощи этих элементов структуры
     данных  или  наборы  элементов  данных. Но вначале рассмотрим более
     подробно одну из основных концепций языка Си - ссылки или указатели.
                                 Указатели
          Большинство переменных, рассматривавшихся до сих пор, содержа-
     ли  данные, т.е. ту самую информацию, которую обрабатывала програм-
     ма. Иногда бывает необходимо знать, где находится некоторое  значе-
     ние, а не само значение. Именно для этого и нужны указатели.
          Если вы не уверены в своих знаниях об адресах и памяти, приво-
     димые ниже сведения пригодятся вам. Для хранения программ и  данных
     в  компьютере имеется память, называемая оперативной памятью. Самой
     мельчайшей единицей памяти является  бит,  микроэлектронная  схема,
     которая  может  хранить в себе /пока включено питание/ одно из двух
     возможных значений, которое обычно интерпретируется как ноль и еди-
     ница.
          Восемь  битов  составляют  один  байт. Именуются также и более
     крупные группы бит; обычно, два  байта  называются  словом,  четыре
     байта  называются  длинным словом, на IBM PC 16 байт называются па-
     раграфом.
          Каждый байт в памяти компьютера имеет свой собственный  адрес,
     так же как имеет свой адрес любой дом на любой улице. Но, в отличие
     от домов, последовательные байты имеют последовательные адреса. Ес-
     ли  данный  байт  имеет  адрес n, тогда предыдущий байт имеет адрес
     n-1, а последующий -  n+1.
          Указатель - это переменная, содержащая адрес некоторого данно-
     го, а не само данное. Как его можно использовать? Во-первых, указа-
     тель может указывать на различные данные и на  различные  структуры
     даных.  Изменением адреса, содержащегося в указателе, можно манипу-
     лировать /присваивать, считывать, изменять/ информацией в различных
     ячейках.  Таким  образом  можно,  например, пройти связанный список
     структур при помощи одного указателя.
          Во-вторых, при помощи указателей можно создавать  новые  пере-
     менные  в процессе выполнения программы. Язык Си позволяет резерви-
     ровать некоторое количество памяти /в байтах/, возвращая адрес, ко-
     торый  можно хранить в указателе. Этот процесс известен как динами-
     ческое резервирование. С его помощью программа  может  использовать
     всю имеющуюся на компьютере память.
 
          В-третьих,  указатель  может  быть использован для обращения к
     различным ячейкам некоторой структуры данных, как например,  масси-
     ва, строки или структуры. На самом деле, указатель указывает только
     на одну ячейку памяти /сегмент:  смещение/,  индексируя  указатель,
     можно обращаться к любому из последующих байт.
          Теперь  вы, без сомнения, убеждены, что указатели удобны в ис-
     пользовании. Как же их использовать в Си? Во-первых, их надо  объя-
     вить. Рассмотрим следующую программу.
          
          1-адрес  ivar; 2-содержимое ivar; 3- содержимое iptr; 4-указы-
     ваемое значение.
          В данной программе объявлены две переменные: ivar и iptr. Пер-
     вая,  ivar,  является  целой переменной, т.е. она содержит значения
     типа int. Вторая, iptr, является указателем  на  целую  переменную,
     т.е. она содержит адрес значения типа int. Можно сразу сказать, что
     iptr - это указатель, потому что при объявлении этой переменной пе-
     ред  ней  была поставлена звездочка. Звездочка в Си называется кос-
     венным оператором /*/.
          В данной программе сделаны следующие присвоения:
          - адрес переменной ivar присвоен переменной iprt;
          - целое значение 421 присвоено переменной ivar.
          Адрес переменной ivar получается при помощи адресного операто-
     ра /&/, упомянутого в предыдущей главе.
          Если  вы напечатаете и запустите эту программу, ее вывод будет
     выглядеть примерно так:
          
          Первые две строки сообщают нам адрес и  содержимое  переменной
     ivar.  Третья показывает адрес, содержащийся в переменной iptr. Как
     видно, это адрес переменной ivar, т.е. той ячейки памяти, в которой
     ваша  программа  решила создать переменную ivar. Последнее распеча-
     танное значение - это данные, хранящиеся по этому адресу, т.е. зна-
     чение уже присвоенное переменной ivar.
          Обратите  внимание  на  третий вызов функции printf, в котором
     выражение iptr используется для получения своего содержимого,  т.е.
     адреса  переменной  ivar.  В  последнем вызове printf для получения
     данных, записанных по этому адресу, используется выражение *iptr.
          Приведем эту же программу в несколько измененном виде:
          
          В этой программе адрес переменной ivar точно так же присваива-
     ется  переменной  iptr,  но  значение  421 присваивается переменной
     *iptr, а не ivar. Результат при этом получится тот же самый.  Поче-
     му? Потому что выражение *iptr=421 абсолютно эквивалентно выражению
     ivar=421, а это, в свою очередь, верно потому,  что  идентификаторы
     ivar  и iptr относятся к одной и той же ячейке памяти, следователь-
     но, оба эти выражения записывают значение 421 в эту ячейку.
                       Динамическое резервирование
          Изменим нашу программу еще раз:
          
          В этой версии опущено объявление переменной ivar. Вместо этого
     появилось присвоение переменной iptr некоторого значения, возвраща-
     емого функцией malloc, объявленной в файле ALLOC.H /отсюда  появле-
     ние  директивы #include в начале программы/. Затем переменной *iptr
     присваивается значение 421, адресом которого является iptr. При за-
     пуске этой программы значение iptr будет отличаться от получавшего-
     ся ранее, а значение *iptr будет тем же - 421.
 
          Какие действия выполняет следующий оператор:
                    iptr = (int*)malloc(sizeof(int))
          Разберем его по порядку.
          Выражение sizeof(int) возвращает  количество  байт,  требуемых
     для хранения переменной типа int. Для компилятора Турбо-Си, работа-
     ющего на IBM PC, это значение будет равно 2.
          Функция malloc(num) резервирует num последовательных  байт  из
     свободной  /неиспользуемой/памяти компьютера и возвращает адрес на-
     чального байта.
          Выражение (int*) означает, что этот адрес будет рассматривать-
     ся  как  указатель  на переменную типа int. Такая операция известна
     как приведение типов. В данном случае Турбо-Си не требует ее приме-
     нения, но многие компиляторы Си используют ее в подобных ситуациях,
     и  поэтому  ее  отсутствие  приведет   к   сообщению   об   ошибке:
     Non-portable  pointer assignment /непереносимое присвоение указате-
     ля/.
          И, наконец, этот адрес записывается в переменную iptr. Все эти
     действия  означают,  что вы динамически создали целую переменную, к
     которой можно обращаться при помощи идентификатора *iptr.
          Учитывая  все  вышеизложенное,  наш оператор может быть описан
     следующим образом: "зарезервировать в памяти компьютера  достаточно
     места  для хранения переменной целого типа, затем присвоить началь-
     ный адрес зарезервированной памяти переменной iptr, которая являет-
     ся указателем на переменную типа int."
          Была  ли процедура необходимой? Да. Почему? Потому что без нее
     не было бы никакой гарантии, что iptr будет указывать на  свободный
     участок  памяти. В переменной iptr будет находиться какое-то значе-
     ние, и вы используете его в качестве адреса, но ячейка, на  которую
     будет  указывать  этот  адрес, может быть уже занята для каких-либо
     других целей. При использовании указателей  всегда  придерживайтесь
     простого  правила:  обязательно  присваивайте адрес указателю перед
     его использованием. Другими словами, не присваивайте целое значение
     переменной *iptr, не присвоив предварительно адрес переменной iptr.
                           Указатели и функции
          В  предыдущей  главе  рассказывалось,  как объявлять параметры
     функций.  Возможно,  теперь стало более понятным, почему в качестве
     формальных параметров, значения которых  необходимо  изменить,  ис-
     пользуются указатели. Для примера рассмотрим следующую функцию:
          
          В данной функции swap, объявлены два формальных параметра: а и
     b, являющиеся указателями на тип int. Это значит, что должен  пере-
     даваться  адрес  целой  переменной, а не ее значение. Все изменения
     действуют на значения, находящиеся по передаваемым в функцию  адре-
     сам. Пример программы, вызывающей функцию swap:
          
          1-до; 2-после.
          
          Вы можете убедиться, что эта программа действительно перестав-
     ляет местами значения i и j. Эта  программа  может  рассматриваться
     эквивалентом следующей программы:
          
          В  данной  программе  результаты получаются, конечно же, те же
     самые. Вызов swap(&i,&j) присваивает значения  двух  действительных
     параметров /i и j/ двум формальным параметрам /а и b/, затем выпол-
     няет операторы процедуры swap.
 
                           Адресная арифметика
          Что делать, если вы хотите изменить программу  таким  образом,
     чтобы переменная iptr указывала на три целых переменые, а не на од-
     ну? Ниже приведено одно из возможных решений.
          
          Вместо использования функции malloc, в этой программе  исполь-
     зована  функция  calloc, требующая двух параметров: количество эле-
     ментов, для которых необходимо резервировать память, и размер  каж-
     дого элемента в байтах. Теперь переменная list указывает на участок
     памяти размером 3х2 байта, этого достаточно для содержания трех це-
     лых переменных.
          Обратите  внимание на три следующих оператора: Первый оператор
     вам знаком - это *list = 421.  Он  означает  следующее:  "сохранить
     значение 421 в переменной типа int, расположенной по адресу, храня-
     щемуся в переменной list".
          Важно понять следующий оператор - *(list+1)=53. На первый взг-
     ляд  его можно интерпретировать следующим образом:"сохранить значе-
     ние 53 в целой переменной, чей адрес записан  в  переменной  list."
     Если  бы  это было так, вам пришлось бы туго, т.к. этот адрес попал
     бы прямо в середину предыдущей целой переменной /которая занимает 2
     байта/. Предыдущее значение было бы при этом безвозвратно утеряно.
          Но не беспокойтесь, ваш компилятор не сделает такой ошибки. Он
     знает, что list - это  указатель  на  тип  int,  поэтому  выражение
     list+1  относится  к  байтовому  адресу,  определяемому  выражением
     list+(1*sizeof(int)), поэтому значение 53 никоим образом не  испор-
     тит значения 421.
          Точно  так же, выражение (list+1) - этол адрес в байтах, кото-
     рый может быть развернут таким образом: list+2*sizof(int)), поэтому
     число 1806 запишется в память, не испортив предыдущих значений.
          В   общем   случае,  выражение  ptr+1  означает  адрес  памяти
     ptr+(i*sizof(int)).
          Введите и запустите предыдущую программу. Результат должен по-
     лучиться приблизительно следующим:
          List of addresses:  06AA   06AC   06AE
          List of values:      421     53   1806
          Адреса  отстоят  друг от друга на 2 байта, а не на один, и все
     три значения сохранены в различных ячейках.
          Подведем некоторые итоги: Если вы используете указатель ptr на
     тип  type,  тогда  выражение (ptr+i) обозначает адрес памяти (ptr +
     (i*sizeof(type))), где  sizeof(type)  возвращает  количество  байт,
     требуемых для хранения переменной типа type.
                                 Массивы
          Большинство  языков высокого уровня, включая Си, позволяют оп-
     ределять массивы, т.е. индексированные списки данных  определенного
     типа.  Например, предыдущая программа может быть переписана следую-
     щим образом:
          
          Выражение int list[NUMINTS] объявляет list массивом переменных
     типа int, резервируя место для хранения трех таких переменных. Пер-
     вая переменная обозначается list[0], вторая  -  list[1],  третья  -
     list[2].
 
          Общий вид объявления любого массива:
          type name[size];
     где  type - это тип данных, name - имя массива, а size - количество
     элементов типа type, содержащихся в массиве  name.  Первый  элемент
     массива   будет   name[0],   второй   -   name[1],  а  последний  -
     name[size-1]; общий размер массива в байтах определяется выражением
     size*(sizeof(type)).
                            Массивы и ссылки
          Скорее  всего,  вы  уже догадались, что массивы и ссылки очень
     тесно связаны между собой. Если вы запустите предыдущую  программу,
     ее вывод покажется вам знакомым:
          List of addresses:  163A   163C   163E
          List of values:      421     53   1806
          Единственное различие заключается в начальном адресе. Все дело
     в том, что имя массива можно использовать как ссылку; точно так  же
     можно индексировать ссылку, как массив. Посмотрите на следующие два
     равенства:
          (list+i) == &(list[i])
         *(list+i) == list[i]
          В обоих случаях выражение слева эквивалентно выражению справа,
     одно может использоваться вместо другого, независимо от того, опре-
     делена переменная list как ссылка или как массив.
          Единственное различие, наблюдаемое  при  объявлении  переменой
     ссылкой  или  массивом,  заключается  в резервировании памяти. Если
     list определена как массив, программа автоматически резервирует для
     нее  требуемое количество памяти. Если же list определена как ссыл-
     ка, для нее необходимо явно создать участок памяти, пользуясь функ-
     цией  calloc  или  ей аналогичной, или же присвоить этой переменной
     адрес ранее зарезервированного участка.
                            Массивы и строки
          Строки обсуждались в предыдущей главе, мы  объявили  их  двумя
     слегка  различными  способами: как указатель на символ и как массив
     символов. Теперь вы можете лучше понять, в чем тут разница.
          Если строка определена как массив типа char, то место для этой
     строки  будет зарезервировано. Если строку определить как указатель
     на тип char, место для нее не резервируется. Его нужно зарезервиро-
     вать самому, используя функцию malloc или ей подобную, или же прис-
     воить указателю адрес существующей строки. Пример на эту тему  при-
     веден  ниже  в данной главе, в разделе "Распространенные ошибки при
     программировании на Си".
                           Многомерные массивы
          В языке Си имеется возможность объявления  многомерных  масси-
     вов, что делается следующим образом:
          type name[size1][size2]...[sizeN];
          Рассмотрим  следующую  программу,  которая инициализирует нес-
     колько двумерных массивов, а затем производит их матричное  умноже-
     ние.
          Обратите  ваше внимание на два момента. Для инициализации дву-
     мерных массивов используются вложенные  списки  {...},  разделенные
     запятыми. Каждая индексная переменная берется в квадратные скобки.
 
          В  некоторых языках для обозначения массивов применяются выра-
     жения вида: [i,j]. Такое обозначение допустимо в  Си,  но  это  все
     равно,  что  написать просто [j], т.к. запятая интерпретируется как
     оператор, означающий: "вычислить i, затем вычислить j, затем  прис-
     воить  всему выражению значение j". Не забывайте ставить квадратные
     скобки вокруг каждой индексной переменной.
          Многомерные массивы храняться в памяти  слева  напрааво,  т.е.
     последний  индекс изменяется быстрее первого. Другими словами, если
     есть массив arr[3][2], тогда элементы этого массива будут храниться
     следующим образом:
           
          Этот же принцип применим и к трех-, четырех- и к N-мерным мас-
     сивам.
                            Массивы и функции
          Что получится, если мы захотим передать массив в функцию? Рас-
     смотрим  следующую функцию, возвращаемую индекс наименьшегго значе-
     ния в масииве целых чисел:
          
          На примере этой функции можно увидеть одно из преимуществ язы-
     ка Си. Нет необходимости знать размер массива list[] во время тран-
     сляции. Почему? Потому что компилятор считает list[] начальным  ад-
     ресом  массива,  а  конечный адрес массива его не интересует. Вызов
     функции Lmin может выглядеть следующим образом:
          
          Вопрос: Что передано функции lmin? Ответ: Начальный адрес мас-
     сива  vector.  Это  значит,  что если вы хотите изменить переменную
     list в функции lmin, точно таким же изменениям подвергнется и пере-
     менная vector. Можно, например, написать следующую функцию:
          
          Теперь  для  инициализации массива vector в функции main можно
     сделать вызов: setrand(vector,VSIZE).
          Обладает ли язык Си достаточной  гибкостью,  чтобы  передавать
     функции  многомерные  массивы?  Предположим,  что мы хотим изменить
     функцию setrand для работы с двумерным массивом.  Необходимо  напи-
     сать приблизительно следующее:
          
          Здесь  CSIZE  -  это глобальная константа, определяющая размер
     второго измерения массива. Другими словами, любой массив передавае-
     мый в функцию setrand, должен будет иметь второе измерение размером
     с CSIZE.
          Есть, однако, и другое решение. Предположим, что имеется  мас-
     сив  matrix[15][7],  который необходимо передать в функцию setrand.
     Если мы воспользуемся  нашим  первоначальным  определением  функции
     setrand(int list[],int size), можно будет написать так:
                          setrand(matrix,15*7);
          В таком случае функция setrand воспримет массив matrix как од-
     номерный массив размерностью 105 /15*7/ и выполнит все, что нам не-
     обходимо.
                                Структуры
          Массивы и ссылки позволяют нам строить списки элементов одного
     и того же типа. Что делать, если  необходимо  создать  что-либо  из
     элементов различного типа? Объявить структуру.
          Структура представляет из себя сборную конструкцию, включающую
     в себя данные различных типов. Предположим, например, что нам необ-
     ходимо  хранить  информацию  о  некоторой звезде: имя, спектральный
     класс, координаты и т.д. Тогда можно объявить следующее:
 
          Таким образом мы объявили структуру /struct/ типа star.  Опре-
     делив  тип  star,  т.е. поместив его объявление в начале программы,
     можно написать следующую программу:
          
          1-продолжение функции main.
          Для обращения к каждому элементу переменной структурного  типа
     перед  ним ставится имя переменной с точкой. Следующая конструкция:
     varname.memname считается эквивалентной имени  переменной  того  же
     типа, что и memname, и с ней можно производить те же самые операции.
                           Структуры и ссылки
          Ссылки  на  структуры  можно  объявлять точно так же, как и на
     данные других типов. В этом  кроется  основа  возможности  создания
     связных списков и других динамических структур данных. На самом де-
     ле, указатели на структуры используются в языке Си так  часто,  что
     имеется  даже специальный символ, позволяющий обращаться к элементу
     структуры, на который указывает указатель. Рассмотрим следующую ре-
     дакцию предыдущей программы:
          
          В данной редакции программы переменная mystar является ссылкой
     на тип star, а не переменной типа star. Память для переменной  star
     резервируется  вызовом функции malloc. Теперь, при обращении к эле-
     ментам структурной  переменной  mystar  мы  используем  обозначение
     ptrname->memname. Символ -> означает указываемый элемент структуры.
     Это сокращенная запись обозначения: (*ptrname).memname
                             Оператор switch
          В определенный момент вы  можете  начать  строить  бесконечные
     конструкторы  типа if...else if...else и т.д. Посмотрите на следую-
     щую функцию:
          
          Конструкции такого рода так часто встречаются в программирова-
     нии,  что  в языке Си предусмотрен специальный оператор switch, об-
     легчающий их запись. Ниже приведена та же функция,  переписанная  с
     применением оператора switch:
          
          Данная  функция  в  цикле  считывает символ, преобразует его в
     символ верхнего регистра, затем сохраняет его в переменной cmd. За-
     тем,  на  основании  значения  переменной cmd, выполняется оператор
     switch. Цикл продолжается до тех пор, пока переменной done не будет
     присвоено  значение  ноль  /вероятнее всего в функциях do_file_menu
     или handle_others/.
          Оператор  switch  сравнивает  значение переменной cmd с каждым
     значением метки case. Если соответствие находится, с этой метки на-
     чинается  выполнение,  которое продолжается или до оператора break,
     или до конца оператора switch. Если соотвествие  не  найдено,  а  в
     оператор switch включена метка default, тогда выполнение начинается
     с этой метки, если же такой метки нет, весь оператор switch пропус-
     кается.
          Значение value, используемое в выражении switch(value), должно
     быть значением, совместимым с типом int. Другими словами, это  зна-
     чение  должно легко преобразовываться в целое, как, например, char,
     разновидности перечисляемого типа и, конечно, тип int и все его ва-
     рианты. Здесь не могут использоваться числа с плавающей точкой, та-
     кие как float и double, указатели, строки или структурные  перемен-
     ные  /хотя  элементы  структур, совместимые с целым, могут быть ис-
     пользованы/.
 
          Значение value может быть любым выражением  /константа,  пере-
     менная,  вызов функции или любая их комбинация/, тогда как вариант-
     ные метки должны быть константами. Кроме того, на  каждое  ключевое
     слово case можно использовать только одно значение. Если бы в функ-
     ции do_main_menu не использовалась функция toupper для преобразова-
     ния переменной cmd в верхний регистр, оператор switch мог бы выгля-
     деть таким образом:
          switch (cmd) {
             case 'f':
             case 'F': do file menu(done);
               break;
             case 'r':
             case 'R': run_program();
               break;
             ...
          В данном варианте функция do_file_menu выполняется вне зависи-
     мости от регистровой принадлежности переменной cmd, то же относится
     и к другим меткам.
          Для окончанния обработки данной метки необходимо  использовать
     оператор break. В противном случае будут исполнены операторы, отно-
     сящиеся к другим меткам, до тех пор, пока, конечно,  не  встретится
     оператор  break,  следующий за вызовом функции do_file_menu, то пе-
     чать буквы F вызовет выполнение этой функции, а за  ней  и  функции
     run_program.
          Бывают  случаи, когда так и следует делать. Рассмотрим следую-
     щий пример:
          typdef enum (sun,mon,tues,wed,thur,fri,sat} days;
          main()
          {`days today;
          ...
          
          В этом операторе switch значения от mon до fri приведут к  ис-
     полнению функции puts, после чего оператор break прекратит выполне-
     ние оператора. Однако, если значение переменной  today  равно  sat,
     выполнится  функция  printf,  за  которой  последует  оператор puts
     ("relax!"); если же значение переменной today равно sun, выполнится
     только последняя функция puts.
                        Команды потока управления
          Существуют дополнительные команды, использующиеся  внутри  уп-
     равляющих  структур  или  для имитации других управляющих структур.
     Оператор return позволяет прерывать выполнение  функций.  Операторы
     break  и continue предназначены для использования внутри циклов для
     пропуска определенных выражений. Оператор goto позволяет передавать
     управление в различные места программы. И последний условный опера-
     тор (?:) позволяет сжать определенные выражения типа if...else все-
     голишь в одну строку.
          Маленький совет: Не спешите использовать эти операторы /за ис-
     ключением, конечно, оператора return/. Конечно, встречаются  ситуа-
     ции, в которых использование этих операторов обеспечивает наилучшее
     решение, но гораздо чаще, и с большей ясностью, задача  может  быть
     решена без их использования. Особенно избегайте применения операто-
     ра goto. Имея  в  своем  распоряжении  операторы  return,  break  и
     continue, вы легко можете обойтись без него.
 
                             Оператор return
         Оператор  return  используется в двух основных случаях. Во-пер-
     вых, если функция возвращает значение, он должен  быть  использован
     для передачи этого значения в вызывавшую функцию. Например:
          
          В данном случае оператор return использован для передачи в вы-
     зывавшую программу максимального значения.
          Во-вторых, оператор return используется для выхода из  функции
     до  окончания ее работы. Например, в начале своего выполнения функ-
     ция может обнаружить условие, требующее немедленного прекращения ее
     работы.  Вместо того, чтобы помещать все основные операторы функции
     внутрь оператора if, для выхода можно использовать оператор return.
     Если  это  функция  типа void, можно использовать return вообще без
     аргументов, т.е. без передаваемого значения. Рассмотрим модификацию
     ранее приведенной функции lmin:
          
          В  данном случае, если параметр size меньше или равен нулю, то
     в массиве list ничего нет, следовательно немедленно вызывается  для
     выхода  из функции оператор return. Обратите внимание на возвращае-
     мое значение /-1/. Так как -1 не может быть индексом  массива,  для
     вызывающей программы это будет свидетельством об ошибке.
                             Оператор break
          Иногда  возникает  необходимость выйти из цикла до его оконча-
     ния. Рассмотрим следующую программу:
          
          Обратите внимание на оператор if(score<0)break;. Он  означает,
     что  если  пользователь введет в качестве значения переменной score
     отрицательное значение, цикл while прекратит выполнение. Переменная
     j  используется одновременно для индексирования массива score и для
     подсчета общего количества элементов в каждом  ряду  массива;  этот
     счетчик сохраняется в первом элементе ряда.
          Вы  можете вспомнить использование оператора break в операторе
     switch в последнем разделе. Тогда он использовался  для  выхода  из
     оператора  switch,  в  данном  случае он используется для выхода из
     цикла и продолжения работы программы. Оператор break может  исполь-
     зоваться  со  всеми тремя циклами /for, while, do...while/, а также
     вместе с оператором switch, однако, он не может быть использован  в
     операторе if...else или в основном теле функции.
                            Оператор continue
          Иногда бывает необходимо, не выходя из цикла, пропустить неко-
     торое выражение, или выражения, и вернуться к началу цикла. В таких
     ситуациях следует использовать специально для этого предназначенный
     оператор continue. Рассмотрим следующую программу:
          
          При выполнении оператора continue программа пропускает  остав-
     шуюся часть цикла и снова вычисляет условие выполнения цикла. В ре-
     зультате, работа этой программы отличается от той, которую мы расс-
     матривали  перед  этим.  Вместо  выполнения внутреннего цикла после
     ввода пользователем значения -1 программа  считает,  что  произошла
     ошибка, и начинает снова выполнять цикл while. Так как переменная j
     в этом случае не была увеличена, программа снова просит ввести зна-
     чение.
 
                              Оператор goto
          Да,  в  языке  Си  существует оператор goto. Его формат прост:
     goto метка, где метка представляет из себя  конкретный  идентифика-
     тор, связанный с определенным выражением. Однако, все разумные слу-
     чаи использования оператора  goto  предусмотрены  имеющимися  тремя
     циклами, поэтому подумайте, а нужен ли вам этот оператор.
                         Условный оператор /?:/
          При  необходимости выбора между двумя выражениями /и их значе-
     ниями/ на основе какого-либо условия обячно  используется  оператор
     if...else, как, например, ниже:
          
          Эта ситуация настолько распространена, что имеется специальная
     конструкция для выполнения такого рода выбора. Она имеет  следующий
     вид:
          exp1? exp2: exp3
          Это  читается  следующим  образом: "Если выражение ехр1 верно,
     тогда вычислять выражение ехр2 и присвоить его значение всему выра-
     жению,  в противном случае вычислять выражение ехр3 и присвоить его
     значение всему выражению". Функцию imin можно переписать, использо-
     вав такую конструкцию, следующим образом:
          int imin(int a, int b)
          {
               return((a




Яндекс цитирования