Студопедия.Орг Главная | Случайная страница | Контакты | Мы поможем в написании вашей работы!  
 

Использование goto



11.6. Оператор exit().

11.7. Оператор atexit().

7. Циклы и другие управляющие средства.

Здесь рассматриваются принципы работы управляющих структур и даются рекомендации, каким образом лучше всего применять каждую из них. Мы обсудим операторы break, continue, goto и операцию «запятая»: все они могут использоваться для управления ходом выполнения программы. Кроме того, мы расскажем вам еще немного о массивах, которые часто используются вместе с циклами.

11.1. Оператор цикла.

В языке С имеется стандартный набор операторов цикла: for, while и do-while (называемый в некоторый других языках высокого уровня циклом repeat-until). Вас может, однако, удивить то, каким способом программа выходит из цикла. В С можно изменить порядок выполнения цикла четырьмя способами. Естественно, все циклы заканчиваются при выполнении заданного проверочного условия. Однако, в С цикл может также закончиться по некоторому заданному условию ошибки при помощи операторов break или exit. Кроме этого, в циклах может быть собственная управляющая логика, изменяемая при помощи оператора break или оператора continue.

11.1.1. Цикл while.

Так же как и цикл for, в С цикл while является циклом с предусловием. Это означает, что в программе проверка_условия осуществляется до выполнения оператора или операторов, входящих в тело цикла. Благодаря этому, циклы с предусловием могут либо не выполняться вообще, либо выполняться множество раз. В С синтаксис цикла while следующий:

while(проверка_условия)

оператор;

В циклах while с несколькими операторами необходимы фигурные скобки:

while(проверка_условия) {

оператор1;

оператор2;

оператор3;

операторn;

}

В следующей программе на С цикл while используется для определения того, сколько раз переменную ivalue сдвигать вправо. Программа печатает двоичное представление целого числа со знаком.

/*07WHILE.C

Программа на С, в которой используется цикл while с предусловием

и флагом*/

#include "stdafx.h"

#include <iostream>

#include <conio.h>

#include <stdio.h>

//#include <iostream.h>

using namespace std;

#define WORD 16

#define ONE_BYTE 8

main()

{

int ivalue = 256, ibit_position=1;

unsigned int umask = 1;

/* Следуицее значение... */

printf("The following value %d,\n",ivalue);

/* в двоичном виде выглядит так: */

printf("in binary form looks like: ");

while(ibit_position <= WORD) {

if ((ivalue >> (WORD - ibit_position)) & umask)

/*сдвинуть каждый */

printf ("1"); /*разряд в 0-ю */

else /* позицию и */

printf("0"); /* сравнить с umask k*/

if(ibit_position == ONE_BYTE)

printf (" ");

ibit_position++;

}

printf ("\n\nPress any key to finish\n"); /* Конец работы */

_getch();

return(0);

}

В начале программы определяются две константы, WORD и ONE_BYTE, которые можно легко изменить для различных архитектур. Константа WORD используется как флаг, определяющий окончание цикла while. В цикле while переменная ivalue сдвигается, сравнивается с umask, и печатается следующий старший разряд числа. Такой алгоритм позволяет использовать простой оператор printf() для вывода результата.

11.1.1.1. Завершение цикла while.

Мы подошли к самому существенному моменту рассмотрения циклов while. При построении цикла while вы должны включить в него какие-то конструкции, изменяющие величину проверяемого выражения так, чтобы в конце концов оно стало ложным. В противном случае выполнение цикла никогда не завершится. Рассмотрим следующий пример:

index = 1;

while(index < 5)

printf("Доброе утро!\n");

Данный фрагмент программы печатает это радостное сообщение бесконечное число раз, поскольку в цикле отсутствуют конструкции, изменяющие величину переменной index, которой было присвоено значение 1.

index = 1;

while(-- index < 5)

printf(" Как колеблются старые атомы!\n");

И этот фрагмент программы работает ненамного лучше. Значение переменной index в нем изменяется, но в «неправильном» направлении! Единственным утешением здесь служит тот факт, что выполнение данного куска программы в конце концов завершится. Это произойдет, когда величина переменной index станет меньше наименьшего отрицательного числа, допустимого в системе.

В следующем примере тело цикла не выполнится ни разу, потому что используемое условие с самого начала является ложным.

index = 3;

while(index++ < 5)

printf(" Желаю хорошо провести день\n");

Измените первую строку на

index = 3;

и вы получите работающую программу.

11.1.1.2. Программа подсчета слов.

Теперь у нас есть возможности для написания программы подсчета числа слов в тексте. (Она может также подсчитывать символы и строки.)

#include <stdio.h>

#define YES 1

#define NO 0

main()

{

int ch; /* введенный символ */

long nc = 0L; /* число символов */

int nl = 0; /* число строк */

int nw = 0; /* число слов */

int word = NO; /* = = YES, если содержимое ch — часть слова */

while((ch = getchar())!= EOF)

{

nc++; /* подсчет символов */

if (ch = = ' \n')

nl ++; /* подсчет строк */

if (ch!= ' ' && ch!= '\n' && ch!= ' \t' && word = = NO)

{

word = YES; /* начало нового слова */

nw++; /* подсчет слов */

}

if ((ch = = ' ' || ch = = ' \n' || ch = = ' \t') && word == YES)

word = NO; /* достигнут конец слова */

}

printf(" символов = %ld, слов = %d, строк = %d\n", nc, nw, nl);

}

Поскольку существуют три различных «пустых символа», мы должны использовать логические операции для проверки всех трех возможностей. Рассмотрим, например, следующую строку:

if(ch!= ' '&& ch!= '\n' && ch!= ' \t' && word == NO)

В ней говорится: «если содержимое ch — не пробел, и не новая строка, и не табуляция, и не первый символ слова». (Первые три условия эквивалентны проверке, не является ли содержимое ch пустым символом.) Выполнение всех четырех условий служит признаком начала нового слова, и значение переменной nw увеличивается. Если мы в середине слова, то первые три условия оказываются выполненными, но значением переменной word окажется признак YES, и значение переменной nw не увеличивается. Когда в процессе ввода встретится очередной «пустой» символ, переменной word будет вновь присвоен признак NO.

11.1.1.3. Программа, «рисующая» символами.

Нашей целью является создание программы, с помощью которой вы сможете рисовать на экране геометрические фигуры, заполненные символами. Каждая выводимая строка представляет собой сплошной ряд одинаковых символов. Нам предоставляется возможность выбора символа, длины строки, а также начальной позиции, с которой она выводится на печать. Программа читает указываемые параметры до тех пор, пока не встретит признак EOF. Текст программы представлен.

/* художник-график */

/* рисует сплошные фигуры */

#include <stdio.h>

#define MAXLENGTH 80

main()

{

int ch; /* печатаемый символ */

int start, stop; /* начальная и конечная позиции */

int count;

/* счетчик позиций */

while((ch = getchar())!= EOF) /* ввод символа */

{

if (ch!= '\n') /* пропуск символа «новая строка» */

{

scanf(" %d %d", &start, &stop); /* ввод граничных значений */

if (start > stop || start < 1 || stop > MAXLENGTH)

printf(" Введены неправильные граничные значения. \n");

else

{

count = 0;

while(++count < start)

putchar(' '); /* печать пробелов вплоть до

начальной позиции */

while (count++ < = stop)

putchar(ch); /"печать символа до конечной позиции */

putchar('\n'); /* закончить печать строки и

начать новую */

} /* конец части else */

} /* конец проверки содержимого ch */

} /* конец цикла while */

} /* конец программы */

Программа вывела на экран символ В в позициях с 10 по 20, а символ Y — с 12 по 18. К сожалению, при диалоговой работе с программой на экране наши команды перемежаются выводимыми строками. Гораздо более удобным способом использования программы является создание файла, содержащего подходящий набор данных, а затем применение операции переключения для ввода (из него) параметров в программу. Предположим, например, что в файле с именем fig содержатся следующие данные:

-30 50

| 30 50

| 30 50

| 30 50

| 30 50

| 30 50

= 20 60

: 31 49

: 30 49

: 29 49

: 27 49

: 25 49

: 30 49

: 30 49

/ 30 49

: 35 48

: 15 48

После ввода команды sketcher < fig вы увидите результат работы программы.

Замечание: на устройствах печати и экранах дисплеев отношение высоты символа к его ширине может быть различным; в результате эти фигуры при печати выглядят более сжатыми по вертикали, чем изображенные на экране.

Анализ программы. Это короткая программа, но она оказалась сложнее тех примеров, которые мы обсуждали до сих пор. Рассмотрим некоторые ее элементы.

Длина строки. Мы ввели в программу ограничение на длину печатаемой строки (она не может быть больше 80 позиций), поскольку 80 символов — это стандартный формат (по ширине) экрана дисплеев различных типов, а также число символов при нормальной ширине листа бумаги на устройстве для печати. Вы можете, однако, переопределить величину константы MAXLENGTH, если при работе с программой захотите воспользоваться устройством, имеющим другую ширину строки.

Структура программы. В нашей программе имеются три цикла while, один оператор if и один оператор if-else. Посмотрим, чтобы каждый из них делает: while((ch = getchar())!= EOF)

Функция getchar() читает первый встретившийся символ независимо от того, является ли он алфавитным символом, пробелом, символом «новая строка» или еще чем-нибудь. Функция scanf() делает то же самое, если чтение производится в формате %с (символ). Но, когда scanf() осуществляет ввод данных в формате %d (целые), пробелы и символы «новая строка» пропускаются. Поэтому символы «новая строка» или любые пробелы между символом, считываемым функцией getchar(), и следующим целым числом, считываемым функцией scanf(), игнорируются. Функция scanf() читает цифры до тех пор, пока не встретит нецифровой символ — пробел, символ «новая строка» или букву. Следовательно, между первым и вторым целыми числами необходимо помещать пробел или символ «новая строка», чтобы функция scanf() могла распознать, где кончается одно и начинается другое.

Этим объясняется, почему между символом и следующим целым числом может стоять пробел или символ «новая строка», и почему между двумя целыми числами обязательно должен быть разделитель такого вида. Но почему между целым числом, стоящим в конце набора данных, и следующим символом не может стоять пробел? Потому что в следующий раз на очередном шаге выполнения цикла while функция getchar() осуществляет ввод символа из той позиции, где «остановилась» функция scanf(). Поэтому она прочтет любой следующий символ, стоящий после целого числа,— пробел, символ «новая строка» и т. п.

Если бы мы следовали требованиям функции getchar(), структуру данных необходимо было бы организовать так:

W10 50a20 60у10 30

где между последним целым числом, стоящим в конце группы, и следующим символом разделитель отсутствует. Но такая структура выглядит неуклюже, поскольку число 50 при этом выглядит так, как будто оно помещено в одну группу с а, а не с w. Поэтому введен оператор

if(ch!= '\n')

чтобы иметь возможность обнаружить, когда значение ch равно символу «новая строка». Вместо этого можно использовать данные, вводимые в виде

w10 50

а20 60

у10 30

где между числом 50 и а помещен символ «новая строка». Программа читает этот символ, игнорирует его и затем переходит к чтению следующего символа.

Контроль ошибок. Существует широко распространенная проблема, связанная с вводом в машину данных, которые должны использоваться определенным образом. Один из методов ее решения состоит в «контроле ошибок». Это означает, что, перед тем как приступить к обработке данных, программа должна проверить их правильность. В нашей программе мы сделали первую попытку осуществить такой контроль ошибок с помощью операторов:

if(start > stop || start < 1 || stop > MAXLENGTH)

printf(" Введены неправильные граничные значения.\n");

Они входят в структуру if-else, которая определяет, что основная часть программы будет выполняться только в том случае, если ни один из трех if-тестов не окажется истинным.

С какой целью мы принимаем все эти меры предосторожности? Во-первых, совершенно неправильно размещать начальную позицию после конечной, поскольку обычно на терминал данные выводятся слева направо, а не наоборот. Поэтому с помощью выражения start > stop проверяется наличие такой потенциальной ошибки. Во-вторых, при выводе на экран первый столбец имеет номер 1; мы не можем выводить данные левее левого края. Выражение start < 1 служит средством обнаружения такой ошибки. И наконец, с помощью выражения stop > MAXLENGTH проверяется, не попытаемся ли мы вывести на печать данные правее правого края. Существуют ли еще какие-нибудь ошибочные значения, которые мы можем присвоить переменным start и stop? Можно было бы, конечно, попробовать присвоить переменной start значение большее, чем MAXLENGTH. Может ли этот вариант успешно пройти наш тест? Нет, хотя наличие подобной ошибки мы и не проверяем непосредственно. Предположим, что величина Start больше константы MAXLENGTH. Тогда либо значение stop тоже превышает величину MAXLENGTH, что обязательно приведет к обнаружению ошибки, либо stop окажется меньшей или равной MAXLENGTH. Но тогда ее значение должно быть меньше величины Start, что приведет к обнаружению этой ошибки первым тестом. Другая вероятная ошибка может состоять в том, что значение stop окажется меньше 1. Мы оставляем читателям в качестве самостоятельного упражнения проверку того, что данная ошибка также не останется незамеченной.

В нашей программе контроль ошибок выглядит весьма простым. Если вы проектируете программу для серьезных целей, вы обязаны обратить на этот вопрос больше внимания, чем мы. Например, выводимые сообщения об ошибках могли бы указывать, какие величины неверны и почему; кроме того, вы могли бы привнести в сообщения что-то от себя и придать им большую эмоциональную окраску. Приведем несколько примеров:

Указанное вами значение stop — 897654 превышает ширину экрана.

Вот эта да! У вас START больше, чем STOP. Попробуйте, пожалуйста, еще раз.

ВЕЛИЧИНА START ДОЛЖНА БЫТЬ БОЛЬШЕ 0, ИНДЮК.

Привносимые личностные моменты относятся, конечно, и к вам самим.

11.1.2. Цикл do-while.

Цикл do-while отличается от циклов for и while тем, что это — цикл с постусловием. Другими словами: цикл всегда выполняется хотя бы один раз, после чего в конце первого прохода проверяется условие продолжения цикла. В отличие от этого циклы for и while могут не выполняться вообще, или выполняться множество раз в зависимости от значения переменной управления циклом. Поскольку циклы do-while выполняются, по меньшей мере, один раз, их лучше использовать тогда, когда нет сомнений о вхождении в определенный цикл.

Синтаксис цикла do-while следующий:

do

действие;

while(проверка_условия);

При множестве действий в операторе do-while необходимы фигурные скобки:

do {

действие1;

действие2;

действиеЗ;

действиеn;

} while(проверка_условия);

Тело цикла do while всегда выполняется по крайней мере один раз, поскольку проверка осуществляется только после его завершения. Тело цикла for или while, возможно, не будет выполнено ни разу, поскольку проверка осуществляется перед началом его выполнения. Использовать цикл do while лучше всего в тех случаях, когда должна быть выполнена по крайней мере одна итерация. К примеру, мы могли бы применить цикл do while в нашей программе угадывания числа. На псевдокоде алгоритм работы программы можно тогда записать следующим образом:

do

{

выдвиньте предположение

получите ответ вида д, б, или м

} while (ответ не совпадает с д)

Вы должны избегать использования цикла do while, структура которого аналогична представленной ниже в записи на псевдокоде:

спросите пользователя, хочет ли он продолжать

do

некоторый умный вздор

while (oтвет будет да)

В данном случае, после того как пользователь ответит «нет», «некоторый умный вздор» будет выполнен, поскольку проверка осуществляется слишком поздно.

11.1.3. Цикл for.

В цикле for распространен для «математических повторений. Вот пример его записи:

for(count = 1; count <= NUMBER; count ++)

printf(" Будь моим Валентином!\n");

В круглых скобках содержатся три выражения, разделенные символом «точка с запятой». Первое из них служит для инициализации счетчика. Она осуществляется только один раз — когда цикл for начинает выполняться. Второе выражение — для проверки условия; она производится перед каждым возможным выполнением тела цикла. Когда выражение становится ложным (или в общем случае равным нулю), цикл завершается. Третье выражение вычисляется в конце каждого выполнения тела цикла. Ранее мы использовали его для увеличения значения счетчика count, но, вообще говоря, его использование этим не ограничивается. За заголовком цикла for следует простой или составной оператор.

Сейчас мы продемонстрируем, как цикл for используется в программе, печатающей таблицу кубов целых чисел:

/* таблица кубов */

main()

{

int num;

for(num = 1; num < = 6; num++)

printf(" %5d %5d \n", num, num*num*num);

}

Эта программа выводит на печать числа от 1 до 6 и их кубы:

1 1

2 8

3 27

4 64

5 125

6 216

Инициализация выполняется один раз перед началом работы цикла.

Из первой строки цикла for мы сразу можем узнать всю информацию о параметрах цикла: начальное значение переменной num, ее конечное значение, а также насколько увеличивается значение переменной num при каждом выполнении тела цикла.

Цикл for часто используется для реализации в программе временной задержки с целью согласования скорости реагирования (в данном случае замедления) машины с возможностями восприятия человека.

for(n = 1; n <= 10000; n++)

;

Этот цикл заставляет машину считать до 10000. Единственный символ «точка с запятой», расположенный во второй строке, информирует нас о том, что никаких других действий в этом цикле не производится. Такой уединенный символ «точка с запятой» можно представлять себе как «пустой» оператор, т. е. оператор, который не выполняет никаких действий.

Синтаксис цикла for следующий:

for(инициализация; проверка_условия; коррекция)

оператор;

При выполнении цикла for сначала осуществляется инициализация. Она выполняется в начале цикла и больше никогда не повторяется.

В следующем примере выполняется суммирование первых пяти целых чисел. Подразумевается, что переменные isum и ivalue предварительно описаны как целые:

isum = 0;

for(ivalue=1; ivalue <= 5: ivalue++)

isum += ivalue;

После инициализации переменной isum значением 0, выполняется цикл for. Во-первых, переменная ivalue устанавливается в 1 (это осуществляется только один раз); во-вторых, значение ivalue сравнивается с условием окончания цикла <=5. Поскольку результат сравнения — "истина", к переменной isum добавляется 1. Как только оператор выполнился, значение переменной управления циклом (ivalue) увеличивается на 1. Этот процесс повторяется еще четыре раза до тех пор, пока значение ivalue не увеличится до 6 и цикл не закончится.

11.1.3.1. Операция «запятая» в цикле for.

Операция «запятая» увеличивает гибкость использования цикла for, позволяя включать в его спецификацию несколько инициализирующих или корректирующих выражений. Например, ниже приводится программа, которая выводит на печать величины почтовых тарифов первого класса обслуживания. (Предположим, что почтовые тарифы: 20 центов за первую унцию и по 17 центов за каждую следующую.)

/* почтовые тарифы */

#define FIRST 20

#define NEXT 17

main()

{

int ounces, cost;

printf(" унции стоимость\n");

for(ounces = 1, cost = FIRST; ounces < - 16; ounces++, cost + = NEXT)

printf(" %3d %7d\n", ounces, cost);

}

Первые четыре строки результата работы программы будут выглядеть следующим образом:

Мы воспользовались операцией «запятая» в первом и третьих выражениях: в первом случае она позволяет инициализировать переменные ounces и cost; во втором — на каждой итерации увеличивать значение ounces на 1, a cost на 17 (величину константы NEXT). Все вычисления осуществляются в спецификации цикла for.

Применение операции «запятая» не ограничено только циклами for, но именно в них она используется особенно часто. Операция обладает одним дополнительным свойством: при ее использовании гарантируется, что выражения, к которым она применяется (т. е. выражения, разделенные запятой), будут вычисляться слева направо.

11.1.3.2. Гибкость конструкции for.

Хотя цикл for на первый взгляд очень похож на цикл DO в Фортране, цикл FOR в Паскале и цикл FOR... NEXT в Бейсике, for в Си является гораздо более гибким средством, чем любой из упомянутых. Эта гибкость — следствие способа использования упомянутых выше трех выражений в спецификации цикла for. До сих пор первое выражение применялось для инициализации счетчика, второе — для задания его граничного значения, а третье — для увеличения его текущего значения на 1. Использованный таким образом, оператор for в языке Си совершенно аналогичен упомянутым выше соответствующим операторам в других языках. Но, кроме описанной, существует еще и много других возможностей его применения, девять из которых мы приводим ниже.

1. Можно применять операцию уменьшения для счета в порядке убывания вместо счета в порядке возрастания.

for(n = 10; n > 0; n--)

printf(" %d секунд!\n", n);

printf(" Пуск!\n");

2. При желании вы можете вести счет двойками, десятками и т. д.

for(n = 2; n < 60; n = n + 13)

printf(" %d\n", n);

3. Можно вести подсчет с помощью символов, а не только чисел:

for(ch = 'а'; ch <= 'z'; ch++)

printf(" Величина кода ASCII для %с равна %d.\n", ch, ch);

4. Можно проверить выполнение некоторого произвольного условия, отличного от условия, налагаемого на число итераций. В нашей программе таблица кубов вы могли бы заменить спецификацию

for(num = 1; num < = 6; num++)

на

for(num = 1; num*num*num <= 216; num ++)

Это было бы целесообразно в случае, если бы нас больше занимало ограничение максимального значения диапазона кубов чисел, а не количества итераций.

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

for(debt = 100.0; debt < 150.0; debt = debt* 1.1)

printf(" Ваш долг теперь $%.2f.\n", debt);

6. В качестве третьего выражения можно использовать любое правильно составленное выражение. Какое бы выражение вы ни указали, его значение будет меняться при каждой итерации.

for(x = 1; у <= 75; у = 5*х+++ 10);

printf("%10d %10d\n", x, у);

7. Можно даже опустить одно или более выражений (но при этом нельзя опустить символы «точка с запятой»). Необходимо только включить в тело цикла несколько операторов, которые в конце концов приведут к завершению его работы.

ans = 2;

for(n = 3; ans < = 25;)

ans = ans*n;

8. Первое выражение не обязательно должно инициализировать переменную. Вместо этого, например, там мог бы стоять оператор printf() некоторого специального вида. Необходимо помнить только, что первое выражение вычисляется только один раз перед тем, как остальные части цикла начнут выполняться.

for(printf(" Запоминайте введенные числа!\n"); num = = 6;)

scanf(" %d", &num);

printf("Эro как раз то, что я хочу!\n");

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

for(n = 1; n < 1000; n += delta)

И если после нескольких итераций ваша программа решает, что величина параметра delta слишком мала или велика, оператор if внутри цикла может изменить значение параметра. В диалоговой программе пользователь может изменить этот параметр в процессе выполнения цикла.

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

11.1.3.3. Философ Зенон и цикл for.

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

Применим количественный подход и предположим, что за одну секунду полета стрела пролетает первую половину расстояния. Тогда за последующую 1/2 секунды она пролетит половину того, что осталось от половины, за 1/4 — половину того, что осталось после этого, и т д. Полное время полета представляется в виде суммы бесконечного ряда 1 + 1/2 + 1/4 + 1/8 + 1/16 +.... Мы можем написать короткую программу для нахождения суммы первых нескольких членов.

/* Зенон */

#define LIMIT 15

main ()

{

int count;

float sum, x;

for (sum = 0.0, x = 1.0, count = 1; count <= LIMIT; count++, x *= 2.0)

{

sum + = 1.0/x;

printf("sum = %f когда count = %d.\n", sum, count);

}

}

В результате выполнения программы получим значения сумм, соответствующих первым 15 членам ряда:

Можно видеть, что, хотя мы и добавляем новые члены, сумма, по-видимому, стремится к какому-то пределу. И действительно, математики показали, что при стремлении числа членов к бесконечности сумма ряда сходится к 2,0, что и демонстрируется нашей программой.

11.1.4. Сравнение циклов.

После того как вы решили, что вам необходимо использовать оператор цикла, возникает вопрос: циклом какого вида лучше всего воспользоваться? Во-первых, решите, нужен ли вам цикл с предусловием или же с постусловием. Чаще вам нужен будет цикл с предусловием. По оценкам Кернигана и Ритчи, в среднем циклы с постусловием (do while) составляют только 5% общего числа используемых циклов. Существует несколько причин, по которым программисты предпочитают пользоваться циклами с предусловием; в их числе один общий принцип, согласно которому лучше посмотреть, куда вы прыгаете, до прыжка, а не после. Вторым моментом является то, что программу легче читать, если проверяемое условие находится в начале цикла. И наконец, во многих случаях важно, чтобы тело цикла игнорировалось полностью, если условие вначале не выполняется.

Для превращения цикла while в цикл for необходимо предварительно осуществить инициализацию некоторых выбранных переменных и включить в тело цикла операторы, корректирующие их значения:

инициализация;

while (проверка условия)

{

тело;

коррекция;

}

Данная запись по своим функциональным возможностям эквивалентна следующей:

for(инициализация; проверка условия, коррекция)

тело;

Исходя из соображений стиля программирования, применение цикла for представляется более предпочтительным в случае, когда в цикле используется инициализация и коррекция переменной, а применение цикла while — в случае, когда этого нет. Поэтому использование цикла while вполне оправданно в случае

while((ch = getchar())!= EOF)

Применение цикла for представляется более естественным в случаях, когда в циклах осуществляется счет прохождений с обновлением индекса:

for(count = 1; count <= 100; count ++)

11.1.5. Вложенные циклы.

Вложенным называется цикл, находящийся внутри другого цикла.

Начнем с программы, проверяющей делимость одного числа; в ней имеется всего один оператор цикла.

/* простое число1 */

main()

{

int number, divisor;

printf(" О каком числе вы хотите знать, простое ли оно?\n");

scanf(" %d", &number); /* получение ответа */

while (number < 2) /* число отвергается */

{

printf(" Извините, мы не принимаем чисел меньше 2.\n");

printf(" Пожалуйста, попробуйте еще раз.\n");

scanf(" %d", &number);

}

for (divisor = 2; number % divisor!= 0; divisor ++)

; /* проверка, простое число или нет, осуществляется

внутри спецификации цикла */

if (divisor = = number) /* выполняется после завершения цикла */

printf(" %d — простое число.\n", number);

else

printf(" %d — не простое число\n", number);

}

Мы воспользовались структурой цикла while, чтобы избежать ввода значений, которые могли бы привести к аварийному завершению программы.

Обратите внимание, что все вычисления выполняются внутри спецификации цикла for. Величина переменной number последовательно делится на возрастающие значения делителей до тех пор, пока не произойдет деление нацело (т. е. number % divisor станет равным 0). Если первым делителем, который приведет к такому результату окажется само это число, то значение переменной number — простое число. В противном случае данное число будет иметь меньший делитель, и это приведет к тому, что цикл завершится раньше.

Для нахождения всех простых чисел, меньших некоторой задан-ной величины, нам нужно будет заключить наш цикл for в некоторый другой цикл. На псевдокоде это будет выглядеть следующим образом:

для числа (number) = 1 до верхнего предела limit проверять, является ли число простым

Вторая строка представляет собой нашу предыдущую программу. Переводя эту запись на язык Си, получим_программу:

/* простые числа2 */

main()

{

int number, divisor, limit;

int count = 0;

printf(" Укажите, пожалуйста, верхний предел для поиска простых чисел.\n");

printf(" Верхний предел должен быть 2 или больше.\n");

scanf(" %d", &limit);

while(limit < 2) /* вторая попытка, если ошибка при вводе */

{

printf(" Вы были невнимательны! Попробуйте еще раз.\n");

scanf(" %d", &limit);

}

printf(" Сейчас будут печататься простые числа!\n");

for (number = 2; number < = limit; number++) /* внешний цикл */

{

for (divisor =2; number % divisor!= 0; divisor++)

;

if(divisor = = number)

{

printf(" %5d", number);

if (++count % 10 = = 0)

printf(" \n"); /* новая строка начинается через каждые 10 простых чисел */

}

}

printf(" \n Вот и все!\n");

}

Во внешнем цикле каждое число, начиная с 2 и кончая величиной limit, последовательно берется для проверки. Указанная проверка осуществляется во внутреннем цикле. Мы использовали переменную count для хранения счетчика получаемых простых чисел. При печати каждое одиннадцатое простое число мы начинаем с новой строки. Ниже приводится пример результатов, получаемых с помощью такой программы:

Этот способ решения довольно прост, но он не является самым эффективным. Например, если вы хотите узнать, является ли число 121 простым или нет, нет необходимости проверять, существуют ли у него делители, превышающие 11. Дело в том, что если у данного числа существует делитель, больший 11, то результатом деления будет число, меньшее 11; тогда этот делитель был бы обнаружен раньше. Поэтому требуется проверять только делители, не превышающие величину квадратного корня из числа, но в данном случае программа будет несколько сложнее.

11.1.6. Алгоритмы и псевдокод.

А теперь вернемся к нашей «тупоумной» программе, угадывающей число. Недостаток этой программы кроется не в программировании самом по себе, а в «алгоритме», т. е. методе, используемом для отгадывания числа. Этот метод можно описать следующим образом:

попросите пользователя задумать число

компьютер начинает угадывание с 1

до тех пор пока догадка неверна, предлагаемое значение увеличивается на 1

Эта запись, между прочим, служит примером «псевдокода», представляющего собой способ выражения смысла программ на упрощенном языке и являющегося некоторым аналогом языка машины. Псевдокод очень эффективен при разработке логики программы.

Если мы хотим улучшить программу, нам в первую очередь необходимо улучшить алгоритм. Один из методов заключается в том, чтобы выбрать число где-нибудь посередине между 1 и 100 (число 50 нам вполне подходит) и попросить пользователя ответить, больше ли это число задуманного, меньше его или равно ему. Если он сообщает, что данное число слишком велико, то тем самым из рассмотрения немедленно исключаются все числа между 50 и 100. Следующей догадкой программы является число, выбранное где-то посередине между 1 и 49. И снова ответ на вопрос, велико или мало это число, позволит исключить из рассмотрения половину оставшихся возможных чисел; программа продолжает указанный процесс, быстро сужая поле поиска до тех пор, пока задуманное число не будет угадано. Давайте запишем эти логические рассуждения на псевдокоде. Пусть highest — максимально возможная величина отгадываемого числа, a lowest — его минимально возможное значение. Вначале этими величинами будут соответственно 100 и 1, поэтому алгоритм запишется следующим образом:

установить highest равным 100

установить lowest равным 1

попросить пользователя задумать число

предложенное значение (guess) равно

(highest + lowest)/2

пока догадка неверна, делать следующее:

{если предложенное значение велико, установить highest равным этому предложенному значению минус 1

если предложенное значение мало, установить lowest равным этому предложенному значению плюс 1

новое предложенное значение равно (highest + lowest)/2}

Обратите внимание на логику алгоритма: если предложенное значение, равное 50, велико, то максимально возможная величина задуманного числа будет равна 49. Если же значение 50 мало, то минимально возможная величина числа будет равна 51.

Сейчас мы переведем текст, указанный выше, на язык Си. Полученная программа представлена ниже.

/* угадывание числа2 */

/* более эффективный способ угадывания */

#include <stdio.h>

#define HIGH 100

#define LOW 1

main()

{

int guess = (HIGH + LOW)/2;

int highest = HIGH;

int lowest = LOW;

char response;

printf(" Задумайте число от %d до %d. Я попробую", LOW, HIGH);

printf(" угадать его.\n Отвечайте д, если моя догадка правильна,");

printf(" б, если \n больше, и м, если");

printf(" меньше. \n);

printf("Итак... ваше число %d?\n", guess);

while((response = getchar())!= 'д')

{

if(response!= ' \n')

{

if (response == ' 6')

{/* уменьшение верхнего предела. Если предло-

женное значение слишком велико */

highest = guess - 1;

guess = (highest + lowest)/2;

printf(" Гм... слишком велико. Ваше число %d?\n", guess);

}

else

if (response == ' м')

{/* увеличение нижнего предела, если предложенное значение слишком мало */

lowest = guess + 1;

guess = (highest + lowest)/2;

printf('Гм... слишком мало. Ваше число %d?\n", guess);

}

else

{/* подводите пользователя к правильному ответу */

printf("Я не понимаю; введите, пожалуйста, д, б");

printf(" или м.\n");

}

}

printf("Я знала, что смогу сделать это!\n");

}

11.2. Оператор break.

В С оператор break может использоваться для выхода из некоторого цикла до того, как условие проверки получит значение ЛОЖЬ. Во многом оператор break похож на оператор goto; различие в том, что точка перехода точно не известна. При выходе из цикла по команде break программа продолжается с оператора, следующего за циклом. Взгляните на очень простой пример:

/*07BREAK.C

Программа на С, иллюстрирующая использование оператора break*/

#include "stdafx.h"

#include <iostream>

#include <conio.h>

#include <stdio.h>

#include <process.h>

using namespace std;

main ()

{

int itimes = 1, isum = 0;

while(itimes < 10){

isum += isum + itimes;

if(isum > 20)

break;

itimes++;

}

printf ("\n\nPress any key to finish\n"); /* Конец работы */

_getch();

return(0);

}

Бывает, что break используется для выхода из цикла в тех случаях, когда заданы два разных условия прекращения его работы. Ниже приводится цикл, реализующий эхо-печать символов и завершающийся при чтении либо признака EOF, либо символа «новая строка».

11.3. Оператор continue.

В С существует небольшое различие между операторами break и continue. Как вы уже видели в последнем примере, break полностью прекращает выполнение цикла. В отличие от этого, оператор continue приводит к игнорированию всех следующих за ним операторов, однако не препятствует инкременту переменной управления циклом или выполнению проверки условия продолжения цикла. Другими словами: если переменная управления циклом продолжает отвечать условию выполнения цикла, то цикл повторяется.

Эта мысль иллюстрируется следующей программой, представляющей собой игру "угадай число":

/*07CONTNU.C

Программа на С, иллюстрирующая использование оператора continue*/

#include "stdafx.h"

#include <iostream>

#include <conio.h>

#include <stdio.h>

#include <process.h>

using namespace std;

#define TRUE 1

#define FALSE 0

main()

{

int ilucky_number=77, iinput_val, inumber_of_tries=0,

iam_lucky=FALSE;

while(!iam_lucky){

/* Введите предполагаемое число: */

printf("Please enter your lucky guess: ");

scanf("%d",&iinput_val);

inumber_of_tries++;

if(iinput_val == ilucky_number)

iam_lucky=TRUE;

else

continue;

/* Для угадывания вам понадобилось всего... попыток! */

printf("It only took you %d tries to get lucky!", inumber_of_tries);

}

printf ("\n\nPress any key to finish\n");

_getch();

return(0);

}

Этот оператор может использоваться во всех трех типах циклов, но не в операторе switch.

11.4. Совместное использование операторов break и continue.

Операторы break и continue можно использовать вместе для решения некоторых интересных задач программирования. Посмотрите на следующий пример на C++:

// 07BRACNTG.CPP

// Программа на C++, показывающая преимущества от совместного

// использования операторов break и continue

#include "stdafx.h"

#include <iostream>

#include <conio.h>

#include <stdio.h>

#include <process.h>

#include <ctype.h>

using namespace std;

#define NEWLINE '\n'

main ()

{

int c;

while((c=getchar())!= EOF)

{

if (isascii(c) == 0)

{

cout << "Not an ASCII character; "; // He ASCII-символ;

cout << "not going to continue/n"; // продолжения нет

break;

}

if(ispunct(c) || isspace(c)) {

putchar(NEWLINE);

continue;

}

if (isprint(c) == 0) {

c = getchar();

continue;

}

putchar(c);

}

printf ("\n\nPress any key to finish\n");

_getch();

return(0);

}

До того, как рассмотреть принцип работы этой программы, взгляните на ее входные значения:

word control ^В exclamation' apostrophe' period.

^Z

Также посмотрите на символы, полученные на выходе:

word

control

В

exclamation

apostrophe

period

Программа считывает символы до тех пор, пока не введен символ EOF (end-of-file — конец файла) ^Z. Затем введенные символы анализируются: удаляются все непечатаемые символы и каждое "слово" помещается на отдельной строке. Для этого используются некоторые очень интересные функции, описанные в файле ctype.h, в том числе isascii(), ispunct(), isspace() и isprint(). Каждой функции в качестве параметра передается символ, а возвращается ноль или некоторое другое значение, показывающее результат сравнения.

Функция isascii() показывает, относится ли указанный символ к набору допустимых ASCII-символов; функция ispunct() — является ли символ знаком пунктуации; функция isspace() — не является ли этот символ пробелом; и наконец функция isprint() — относится ли он к печатаемым символам.

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

11.5. Оператор goto.

Оператор goto — одно из важнейших средств Бейсика и Фортрана — также реализован и в Си. Однако на этом языке в отличие от двух других можно программировать, совершенно не используя указанное средство. Керниган и Ритчи считают оператор goto «чрезвычайно плохим» средством и предлагают «применять его как можно реже или не применять совсем».

Оператор goto состоит из двух частей — ключевого слова goto и имени метки. Имена меток образуются по тем же правилам, что и имена переменных. Приведем пример записи оператора

goto part2;

Чтобы этот оператор выполнился правильно, необходимо наличие другого оператора, имеющего метку part2; в этом случае запись оператора начинается с метки, за которой следует двоеточие.

part2: printf(" Уточненный анализ:\n");

11.5.1. Использование goto.

В принципе вы никогда не обязаны пользоваться оператором goto при программировании на Си. Но если ваш предыдущий опыт связан с работой на Фортране или Бейсике, в каждом из которых требуется его использовать, то у вас могли выработаться навыки программирования, основанные на применении данного оператора. Чтобы помочь вам преодолеть эту привычку, ниже вкратце приводится несколько знакомых вам ситуаций, реализуемых с помощью goto, а затем показывается, как это можно осуществить другими средствами, в большей степени соответствующими духу языка Си.

1. Работа в ситуации, когда в операторе if требуется выполнить более одного оператора:

if (size > 12)

goto a;

goto b;

a: cost = cost * 1.05;

flag - 2;

b: bill = cost * flag;

Обычный подход, применяемый в языке Си и заключающийся в использовании составного оператора, или блока, упрощает понимание смысла программы:

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 stage2;

большое количество операторов;

goto readin:

stage2: дополнительная чепуха;

Эквивалентный фрагмент, в котором используется цикл while, выглядит так:

scanf("%d", &score);

while(score > = 0)

{

большое количество операторов;

scanf(" %d", &score);

дополнительная чепуха;

4. Пропуск операторов до конца тела цикла: используйте оператор continue.

5. Выход из цикла: используйте оператор break.

11.6. Оператор exit().

В некоторых случаях программу необходимо закончить до того, как выполнились все ее операторы или условия. Для таких особых случаев в С имеется библиотечная функция exit(). Эта функция может иметь один целочисленный аргумент, называемый статусом. Операционные системы UNIX и MS-DOS интерпретируют значение статуса, равное нулю, как успешное завершение программы, а все ненулевые значения статуса говорят о различного вида ошибках.

В следующей программе на C++ выполняется поиск среднего значения из списка, имеющего до 30 чисел. Выход из программы происходит, если пользователь просит усреднить большее, чем LIMIT, количество цифр.

// 07EXIT1.CPP

// Программа на C++, иллюстрирующая использование функции exit

#include "stdafx.h"

#include <iostream>

#include <conio.h>

#include <stdio.h>

#include <process.h>

#include <ctype.h>

using namespace std;

#define LIMIT 30

main()

{

int irow,irequested_qty,iscores[LIMIT];

float fsum=0,imax_score=0,imin_score=100, faverage;

// Введите количество усредняемых величин:

cout << "\nEnter the number of scores to be averaged: ";

cin >> irequested_qty;

if(irequested_qty > LIMIT) {

// Вы можете задать только... чисел для усреднения,

cout << "\nYou can only enter up to " << LIMIT << \

" scores" << " to be averaged.\n";

// Программа окончена.

cout << "\n >>> Program was exited. <<<\n";

exit;

}

for(irow = 0; irow < irequested_qty; irow++) {

// Введите число

cout << "\nPlease enter a grade " << irow+1 << ": ";

cin >> iscores[irow];

}

for(irow = 0; irow < irequested_qty; irow++)

fsum = fsum + iscores[irow];

faverage = fsum/(float)irequested_qty;

for(irow = 0; irow < irequested_qty; irow++) {

if(iscores[irow] > imax_score)

imax_score = iscores[irow];

if(iscores[irow] < imin_score)

imin_score = iscores[irow];

}

cout << "\nThe maximum grade is " << imax_score; // Максимум

cout << "\nThe minimum grade is " << imin_score; // Минимум

cout << "\nThe average grade is " << faverage; // Среднее значение

printf ("\n\nPress any key to finish\n");

_getch();

return(0);

}

11.7. Оператор atexit().

Всякий раз, когда программа вызывает функцию exit() или происходит нормальное завершение программы, можно также вызывать любую зарегистрированную "функцию выхода", занесенную в atexit(). Следующая программа на С иллюстрирует эту возможность:

/*07ATEXIT.C

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

atexit и порядком выполнения объявленных функций*/

#include "stdafx.h"

#include <iostream>

#include <conio.h>

#include <stdio.h>

#include <process.h>

#include <ctype.h>

#include <stdlib.h>

using namespace std;

void atexit_fn1(void);

void atexit_fn2(void);

void atexit_fn3(void);

main()

{

atexit(atexit_fn1);

atexit(atexit_fn2);

atexit(atexit_fn3);

printf("Atexit program entered.\n"); /* Вход в программу Atexit.*/

printf("Atexit program exited.\n\n"); /* Выход из программы Atexit.*/

printf (">>>>>>>>>> <<<<<<<<,\n\n");

printf ("\n\nPress any key to finish\n");

_getch();

return(0);

}

void atexit_fn1(void)

{

printf("atexit_fn1 entered.\n"); /* Вход в функцию atexit_fn1.*/

}

void atexit_fn2(void)

{

printf("atexit_fn2 entered.\n"); /* Вход в функцию atexit_fn2.*/

}

void atexit_fn3(void)

{

printf("atexit_fn3 entered.\n"); /* Вход в функцию atexit_fn3.*/

}

На выходе программы получается следующее:

Функция atexit() использует имя некоторой функции в качестве единственного параметра и регистрирует эту функцию как функцию выхода. Если программа завершается нормально, как в предыдущем примере, или она вызывает функцию exit(), то выполняются все функции, объявленные в atexit().

12. Массивы.

12.1. Понятие массив.

12.2. Массивы в С.

12.3. Объявление массивов.

12.4. Проблема ввода.

12.5. Инициализация массивов.





Дата публикования: 2015-01-13; Прочитано: 444 | Нарушение авторского права страницы | Мы поможем в написании вашей работы!



studopedia.org - Студопедия.Орг - 2014-2024 год. Студопедия не является автором материалов, которые размещены. Но предоставляет возможность бесплатного использования (0.156 с)...