Глава 6. Инструкции

Чтобы приступить к созданию первых программ на языках C/C++, вам следует познакомиться с некоторыми дополнительными средствами программирования. В этой главе будут рассмотрены базовые инструкции, составляющие основу любой программы. Большинство из них вам должно быть хорошо знакомо по другим языкам программирования высокого уровня. Это такие инструкции, как if, if /else и switch/case, а также циклы for, while и do/while. В то же время ряд инструкций уникален для C/C++, например условный оператор ?:, ключевые слова break и continue. Они не имеют аналогов в более ранних языках программирования, таких как FORTRAN, COBOL или Pascal. По этой причине начинающие программисты часто оставляют их без внимания и не используют в своих программах. В результате остаются невостребованными некоторые уникальные возможности, предоставляемые языками C/C++. Кроме того, это выдает в вас новичка, которому вряд ли доверят работу над серьезным проектом.

Инструкции выбора

В языках C/C++ имеются четыре базовые инструкции выбора: if,if /else, switch/case и оператор ?:. Прежде чем приступить к изучению каждой из них, следует упомянуть об общих принципах построения условных выражений. Инструкции выбора используются для выборочного выполнения определенных блоков программы, состоящих либо из одной строки, либо из нескольких. В первом случае строка не выделяется фигурными скобками, во втором — выделяется весь блок.

Инструкция if

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

if (условие) выражение;

Обратите внимание, что условие заключается в круглые скобки. Если в результате проверки условия возвращается значение true, то выполняется выражение, после чего управление передается следующей строке программы. Если же результатом условия будет false, то выражение будет пропущено. Перейдем к конкретному примеру. В следующем фрагменте на экран выводится приветствие "Добрый день!" всякий раз, когда значение переменной ioutside_temp оказывается большим или равным 72:

if(ioutside_temp >= 72)
printf("
Добрыйдень!");

В более сложном случае, когда инструкция if охватывает несколько выполняе­мых команд, синтаксис немного меняется:

if(условие) {. выражение1; выражение2;
...
выражение-
n; )

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

/*
*       if.с 
*  В этой программе на языке С демонстрируется использование инструкции if.
*/

#include  <stdio.h>

int main ()

{

int  inum_As,   inum_Bs,   inum_Cs;

float   fGPA;

printf("\nВведите число предметов,  по которым вы получили    оценку ОТЛИЧНО:   ");

scanf("%d",   &inum_As);

printf("\nВведите число предметов, по которым вы получили оценку ХОРОШО: ");

scanf("%d",&inum_Bs);

printf("\nВведите число предметов, по которым вы получили оценку УДОВЛЕТВОРИТЕЛЬНО: '") ;

scanf("%d",SinumCs);

fGPA = (inum_As*5 + inum_Bs*4 + inum_Cs*3)/(float)(inum_As + inum_Bs + inum_Cs);
printf("\n
Ваш средний балл: %5.2f\n",fGPA);

if (fGPA >= 4.5){

printf("\nПОЗДРАВЛЯЕМ!\n");
printf ("\
nВы прошли по конкурсу.");

return(0); }

Обратите внимание, что инструкция if контролирует вывод только поздрави­тельного сообщения, которое отображается, если значение переменной fGPA больше или равно 4,5.

Инструкция if/else

Инструкция if /else позволяет выборочно выполнять одно из двух действий в зависимости от условия. Ниже показан синтаксис данной инструкции:

if (условие)

выражение1; else

выражение2;

Если проверка условия дает результат true, то выполняется выражение1, в противном случае — выражение2. Рассмотримпример:

if (ckeypressed — UP)

iy_pixel_coord++; else

iy_pixel_coord--;

В этом фрагменте выполняется увеличение или уменьшение текущей горизонтальной координаты указателя мыши в зависимости от значения переменной ckeypressed (предполагается, что она содержит код нажатой клавиши).

Если операторная часть ветви if или else содержит не одно выражение, а несколько, необходимо заключать их в фигурные скобки, как показано в следующих примерах:

if(условие) {

выражение1; 

выражение.?;

выражениеЗ;

}        .
else

выражение 4;

if (условие) (

выражение1; else {

выражение2;

выражениеЗ; выражение4 ;

if (условие) {  

выражение1;

выражение2;

выражениеЗ;

}
else {

выражение 4;

выражение5;

выражение 6; }

Обратите внимание, что после закрывающей фигурной скобки точка с запятой не ставится.

В следующем примере демонстрируется использование инструкции if /else:

/*
*       crapif.c
*  В этой программе на языке С демонстрируется использование
*  инструкции if /else.
*/

#include <stdio.h>

int main ()

{

char c;

int ihow_many, i, imore;

while (imore == 1) {

printf ("Введите название товара: ");
if (scanf ("%c",&c)!= EOF)

{ while (c != '\n') scanf ("%c",&c) ;

printf ("Сколько заказано? ");
scanf ("%d",&ihow_many) ;
scanf ("%c",&c) ;

for(i= 1; i <= ihow_many; i++)

 printf ("*");

printf ("\n"); } else

imore = 0 ;}

return(0); }

Эта программа предлагает пользователю ввести название товара, и, если не получен признак конца файла [Ctrl+C] (EOF), то название будет прочитано буква за буквой, пока не встретится символ конца строки (генерируется при нажатии клавиши [Enter]). В следующей строке отобразится надпись "Сколько заказано?", после которой необходимо ввести число заказанных единиц товара. Затем в цикле for на экран выводится ряд звездочек, количество которых соответствует введенному числу. При обнаружении признака конца файла программный блок ветви if пропускается и выполняется строка программы в ветви else. В этой строке устанавливается нулевое значение флага imore, что служит признаком завершения программы.

Вложенные инструкции if/else

Если используется несколько вложенных инструкций if, следует тщательно следить, какой из них соответствует ветвь else. Взгляните на следующий пример и попытайтесь определить порядок выполнения команд:

if(itemperature < 50)
if(itemperature < 30)
printf("
Наденьте меховую куртку.");
else printf("
Наденьте шубу!");

В этом примере мы специально не делали отступов с помощью табуляции, чтобы не давать вам подсказок. Что произойдет, если значение переменной itemperature будет 55? Появится ли сообщение "Наденьте шубу!"? Конечно же, нет. В данном примере ветвь else связана со второй инструкцией if. Дело в том, что компилятор всегда связывает инструкцию else с ближайшей к ней несвязанной инструкцией if.

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

if(itemperature  <  50)
if (itemperature  <  30)  
printf ("
Наденьте меховую куртку.");
else printf("
Наденьте шубу!");

Еще нагляднее следующая нотация:

if(itemperature < 50) if(itemperature < 30)
printf("
Наденьте меховую куртку."); else
printf("
Наденьте шубу!");

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

Рассмотрим пример:

if(условие1)

if(условие2)

выражение2; else

выражение1 ;

Нетрудно заметить, что выделения отступами в этом примере ошибочны, и данный код ничем не отличается от предыдущего примера. Многие начинающие программисты часто пытаются устанавливать связи внутри блоков путем изменения отступов. Но ведь компилятор не учитывает отступы! Как же сделать так, чтобы выражение1 действительно было связано с условием1, а не условием2?

Чтобы исправить ошибку, необходимо использовать фигурные скобки:

if(условие1)   {

if (условие.2)

выражение2;
}
else

выражение1;

С помощью фигурных скобок мы объединили условие2 и выражение2 в единый блок, выполняемый в том случае, если условие1 равно true. В результате ветвь else связывается с, первой, а не со второй инструкцией if.

Конструкция if/else/if

Конструкция if/else/if используется для того, чтобы последовательно прове­рить несколько условий. Ее синтаксис выглядит следующим образом:

if(условие!)

выражение1; else if(условие2)

выражение2; else if (условие3)

выражениеЗ;

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

Теперь рассмотрим слегка видоизмененную версию предыдущего примера:

if(условие!)

выражение1; else if(условие2)

выражение2; else if(условиеЗ)

выражениеЗ; else

действие_по_умолчанию;

В данном случае какое-то действие обязательно будет выполнено. Если ни одно из условий не равно true, выполняется действие_по_умолчанию. Чтобы понять, в каких случаях лучше применять эту конструкцию, рассмотрим следующий пример. Здесь переменная fconverted_value содержит значение длины в футах, а переменная econvert_to — признак преобразования. Если значение последней не соответствует ни одному из известных типов преобразования, отображается соответствующее сообщение.

if (econvert_to == YARDS)

fconverted_value = length/3; else if (econvert_to == INCHES)

fconverted_value = length * 12; else if (econvert_to == CENTIMETERS)

fconverted_value = length * 30.48; else if (econvert_to == METERS)

fconverted_value = length * 30'. 48/100; else

printf ("Преобразование невозможно");

Условный оператор ?:

Оператор ? : позволяет создавать простые однострочные условные выражения, в которых выполняется одно из двух действий в зависимости от значения условия. Данный оператор можно использовать вместо инструкции if /else, а синтаксис его таков:

условие ? выражение1 : выражение2;

Оператор ? : называют тернарным, поскольку он требует наличия трех операн­дов. Рассмотрим пример, в котором определяется модуль числа:

if (f value >= 0.0)

fvalue = fvalue; else

fvalue = -fvalue;

С помощью условного оператора его можно записать в одну строку:

fvalue = (fvalue >= 0.0)? fvalue : -fvalue;

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

//
//   condit.cpp
//   В этой программе на языке C++ демонстрируется использование
//
   условного оператора.

#include <math.h>
#include <iostream.h>

int main() <

float fbalance, fpayment;

cout << "Введите сумму заема: ";
cin >> fbalance;

cout << "\nВведите сумму погашения: ";
cin >> fpayment;

cout << "\n\nВаш баланс: ";

cout << ((fpayment > fbalance) ? "переплачено на $" : "уплачено $");
cout << ((fpayment > fbalance) ?
(fpayment - fbalance) : fpayment);
cout << "
по заему на сумму $" << fbalance << ".";

return(0);

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

cout << ((fpayment > fbalance) ? (fpayment - fbalance) : fpayment);

Конструкция switch/case

Часто возникает необходимость проверить переменную на равенство целому ряду значений. Это можно выполнить либо с помощью конструкции if/else/if, либо с помощью похожей конструкции switch/case. Обратим ваше внимание на то, что инструкция switch в языке С имеет ряд особенностей. Ее синтаксис таков:

switch(целочисленное_выражение) {    ,
caseконстанта!:

выражение1;

break; 
саsе константа2:

выражение2;

break;
.
.
.

case 
константа-n:

выражение-п;

break;

default: 
  действие_по_умолчанию; }

Заметьте: инструкция break повторяется во всех ветвях, кроме самой последней. Если, скажем, из первой ветви удалить эту инструкцию, то после выражения1 будет выполняться выражение2, что не всегда желательно. Таким образом, инструкция break отвечает за то, что после выполнения одной из ветвей case все остальные ветви будут пропущены. Рассмотрим несколько примеров.

Предположим, имеется такой фрагмент:

if(emove == SMALL_CHANGE_DP)

fycoord = 5; else iftemove == SMALL_CHANGE_DOWN)

fycoord = -5; else if(emove == LARGE_CHANGE_DP)

fycoord = ,10;

else

fycoord = -10;

Его можно переписать с использованием инструкции switch:

switch(emove) {

case SMALL_CHANGE_UP:

fycoord = 5;

break; case SMALL_CHANGE_DOWN:

fycoord = -5;    

break; case LARGE_CHANGE_UP:

fycoord = 10;

break; default:

fycoord = -10;
}

Здесь значение переменной emove последовательно сравнивается с рядом констант в ветвях case. Если обнаруживается совпадение, переменной fycoord присваивается нужное приращение. Затем выполняется инструкция break, которая передает управление строке, следующей после закрывающей фигурной скобки. Если же ни одно из сравнений не дало результата, то переменной fycoord присваивается значение по умолчанию (-10). Поскольку это последняя строка всего блока, нет необходимости ставить после нее инструкцию break. Следует также упомянуть о том, что ветвь default является необязательной.

Умелое манипулирование инструкциями break позволяет рациональнее строить блоки switch/case, как показано в следующем примере:

/*
*   switch.с
*  В этой программе на языке С демонстрируется, как рационально
*  строить блоки switch/case.
*/

int main () {

char с = 'a' ;

int ivowelct = 0, iconstantct = 0;

switch(c)
{
case 'a'
case 'A'
case 'e'
case 'E'
case 'i'
case 'I'
case 'o'
case '0'
case 'u'
case 'U'
ivowelct++;

break; default : iconstantct++;

return(0); 

} 

Если переменная с содержит ASCII-код любой гласной буквы, увеличивается счетчик гласных ivowelct, в противном случае осуществляется приращение счетчика согласных iconstantct.

В других языках высокого уровня допускается объединение в одной проверке всех констант, приводящих к выполнению одного и того же действия. Но в C/C++ требуется, чтобы каждая константа была представлена отдельной ветвью case. Эффект объединения достигается за счет того, что связанные ветви не разделяются инструкциями break.

В следующей программе на языке С инструкция switch используется для организации вызова правильной функции:

/*
*       fnswitch.c
*  В этой программе на языке С демонстрируются другие возможности
*
   инструкции switch.
*/

#include <stdio.h>
#define QUIT 0
#define BLANK ' '

double      fadd(float fx,float fy) ;

double      fsub(float fx,float fy) ;

double      fmul(float fx,float fy) ;

double      fdiv(float fx,float fy) ;

int main() {

float fx, fy;

char cblank, coperator = BLANK;

while(coperator != QUIT) {
printf("\n
Введите выражение вида (а (оператор) b): ");
scanf("%f%c%c%f", &fx, &cblank, &coperator, &fy) ;

switch (coperator) {

case '+':printf("ответ= %8.2f\n",fadd(fx, fy)); break;

case '-':printf("ответ= %8.2f\n",fsub(fx, fy)); break;

case '*':printf("ответ= %8.2f\n",fmul(fx, fy)); break;

case '/':printf("ответ= %8.2f\n",fdiv(fx, fy)); break;

case 'x': coperator = QUIT;

­          break;

default : printf("\nОператор не распознан.");}

     }

   return (0);
}

double fadd (float fx, float fy)
{ return (fx+ fy) ; }

double fsub (float fx, float fy)
{ return (fx - fy) ; }

double fmul (float fx, float fy)
{ return (fx* fy) ; }

double fdiv (float fx, float fy)
{ return (fx/ fy) ; }

Хотя синтаксис описания и вызова функций может быть еще непонятен вам (с функциями нам предстоит познакомиться в следующей главе), в целом использование инструкции switch в данном примере позволяет сделать текст программы максимально компактным и наглядным. Если пользователь введет какое-нибудь математическое выражение, скажем 10+10 или 23*15, соответствующий оператор (+ или *) будет сохранен в переменной coperator, значение которой затем сравнивается в блоке switch с набором стандартных арифметических операторов. Если же вместо арифметического оператора введен символ х, то программа присвоит пере­менной coperator значение quit, что является признаком завершения цикла, а с ним и всей программы. В случае, когда пользователь случайно ввел какой-нибудь не распознаваемый программой символ, например %, управление передается ветви default, в которой на экран выводится предупреждающее сообщение.

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

//
//  calendar. Срр
//  В этой программе на языке C++ демонстрируется использование
//  инструкции switch для создания ежегодного календаря.

#include <fstream.h>

int main() {

int jan_l_start_day, num_days_per_month, month, date, year;

bool leap_year_flag;

ofstream fout("output.dat");

cout << "Укажите, на какой день недели приходится 1-е января\n";

cout << "\n(0— понедельник,";

cout << "\n 1 — вторник и т.д.): ";

cin >> jan_l_start_day;

cout << "\nВведите год, для которого вы хотите построить календарь:”;

cin >> year;

fout << "\n Календарь на " << year << " год";

if(!(year % 4) && (year % 100) || !(year % 400))

leap_year_flag = true; else

leap_year_flag = false;

for(month = 1; month <= 12;month++) {
switch(month) {   

case 1:

cout << "\n\n\n Январь\n";
num_days_per_month = 31;
break;

case 2:

cout << "\n\n\n Февраль\n";
num_days_per_month = leap_year_flag ? 29 : 28;
break;
  
case 3: 

cout << "\n\n\n Март\n";
num_days_per_month = 31;.
break;

case 4:

cout << "\n\n\n Апрель\n";
num_days_per_month =30;
break;

case 5:

cout << "\n\n\n Май\n";
num_days_per_month =31;
break;

case 6:

cout << "\n\n\n Июнь\n";
num_days_per_month = 30;
break;

case 7: 

cout << "\n\n\n Июль\n";
num_days_per_month = 31;
break; 

case 8:

cout << "\n\n\n Август\n";
num_days__per_month = 31;
break;

case 9:

cout << "\n\n\n Сентябрь\n";
num_days_per_month =30;
break;

case 10:

cout << "\n\n\n Октябрь\n";
num_days_per_month = 31;
break;

case 11:

cout << "\n\n\n
Ноябрь\n";
num_days_per_month = 30;
break;

case 12:

cout << "\n\n\n Декабрь\n";
num_days_per_month =31;
break;
}
 fout << "\n
Пон Вто Сре Чет Пят Суб Вос\n";
 fout << " —-— —-— —-— —-— —-— —-— —-— \n";

for (date = 1; date < jan_l_start_day*4; date++)
fout << " ";

for (date = 1; date <= num_days_per_month; date++)
{ fout.width.(3) ;
fout << date;
if ((date+ jan_l_start_day) % 7 > 0)

fout << " ";
else

fout << "\n"; }
 jan_l_start_day = (jan_l_start_day + num_days_per_month) % 7;
}
fout.close () ;
return (0);
}

Программа начинает свою работу с того, что предлагает пользователю указать день недели, на который приходится 1-е января (0 соответствует понедельнику, 1 — вторнику и т.д.) Далее программа просит указать год, для которого вы. хотите построить календарь. Введенное значение отображается в виде заголовка календаря и сохраняется в переменной year. Выполнив ряд проверок, программа определяет, является ли введенный год високосным или нет, и сохраняет результат в переменной leap_year_flag (високосным является год, номер которого делится на 4, но не делится на 100, а также год, номер которого делится на 400).

Затем запускается цикл for, выполняющийся 12 раз — по одному разу для каждого месяца. Сначала выводится название месяца, а в переменную num_days_per_month записывается количество дней в данном месяце. Это осуществляется в блоке switch/case, состоящем из 12-ти ветвей. Вслед за этим отобра­жаются заголовки дней недели и в отдельной строке — ряд пробелов, чтобы начать выводить числа с того дня, на который приходится 1-е число данного месяца. В последнем цикле for выводятся собственно номера дней месяца. В последней строке внешнего цикла for вычисляется порядковый номер дня недели первого числа следующего месяца.

Совместное использование конструкций if/else/if и switch/case

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

/*
*         ifelsw.c
*   В этой программе на языке С демонстрируется использование
*   конструкции if /else/if в сочетании с конструкцией switch/ case.

*/

#include <stdio.h>

enum conversion_type {YARDS, INCHES, CENTIMETERS, METERS} C_Tconversion;

int main()    
{

int iuser_response;      
 float fmeasurement, ffoot; 
 printf("\n
Введите значение длины в футах: "); scanf("%f",&ffoot) ;

printf("\nВозможные единицы измерений: \
\n\t\t0 -
ЯРДЫ           \
\n\t\t1 –
ДЮЙМЫ          \
\n\t\t2 -
САНТИМЕТРЫ     \
\n\t\t3 -
МЕТРЫ          \
\n\n\t\tBa
ш выбор — >> ");
scanf("%d",&iuser_response) ;

switch (iuser_response) (

case 0 :  C_Tconversion = YARDS;

break; case 1 :  C_Tconversion = INCHES;

break; case 2 :  C_Tconversion = CENTIMETERS;

break; default : C_Tconversion = METERS;

if (C_Tconversion == YARDS)

fmeasurement = ffoot/3; else if (C_Tconversion == INCHES)

fmeasurement = ffoot * 12; else if (C_Tconversion == CENTIMETERS)

fmeasurement = ffoot * 30.48; else

fmeasurement = ffoot * 30.48/100;

switch (C Tconversion) {

case YARDS      : printf("\n\t\t%4.2f ярдов", fmeasurement); break;

case INCHES     : printf("\n\t\t%4.2f дюймов", fmeasurement);

break;

case CENTIMETERS :

printf("\n\t\t%4.2f
сантиметров", fmeasure'ment) ;

break;

default :

printf("\n\t\t%4.2f
метров", fmeasurement); }

return (0);
}

В данном примере константы единиц измерений представлены перечислением conversion_type. (Подробнее о перечислениях см. в главе "Дополнительные типы данных".) Первый блок switch/case предназначен для того, чтобы на основании введенного пользователем значения проинициализировать переменную C_Tconversion типа conversion_type. Затем в блоке вложенных инструкций if/else/if выполняется соответствующее преобразование. Наконец, в последнем блоке switch/case полученное значение выводится на экран.

Циклы

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

Отличие цикла for от циклов while и do/while состоит в том, что в нем, как правило, число повторений заранее известно. Таким образом, цикл for обычно используется в тех случаях, когда можно точно определить необходимое количество повторов. Циклы while и do/while применяются, когда число повторений неиз­вестно, но имеется некоторое условие, которое необходимо выполнить.

Цикл for

Цикл for имеет следующий синтаксис:

for(инициализирующее_выражение;. условное_выражение; модифицирующее_выражение) выражение;

При обнаружении в программе цикла for первым выполняется инициализирующее_выражение, в котором обычно устанавливается счетчик цикла. Это происходит только один раз перед запуском цикла. Затем анализируется условное_выражение, которое также называется условием прекращения цикла. Пока оно равно true, цикл не прекращается. Каждый раз после всех строк тела цикла выполняется модифицирующее_выражение, в котором происходит изменение счетчика цикла. Как только проверка условного_выражения даст результат false, все строки тела цикла и модифицирующее_выражение будут пропущены и управление будет передано первому выражению, следующему за телом цикла. Если тело цикла содержит более одной команды, следует использовать фигурные скобки и руководствоваться определенны­ми правилами оформления, чтобы сделать текст программы более понятным: for (инициализирующее_выражение;   условное_выражение;  модифицирукщее_выражение)   { выражение1;

выражение2; 
выражениеЗ; 

выражение-n; }

Давайте рассмотрим некоторые примеры использования цикла for. В следующем фрагменте вычисляется сумма ряда целых чисел от 1 до 5. Предполагается, что переменные isum и ivalue имеют тип int:

isum = 0;
for (ivalue = .1; ivalue <= 5; ivalue++)
isum += ivalue;

Сначала переменной isum присваивается нулевое значение, после чего запускается цикл for, который начинается с присваивания переменной ivalue значения 1. Эта операция выполняется только один раз. Затем проверяется условие ivalue<= 5. Поскольку на первом шаге цикла это выражение равно true, к переменной isum прибавляется текущее значение переменной ivalue — единица. По завершении выполнения последней строки цикла (в данном случае единственной) переменная ivalue увеличивается на единицу. Этот процесс будет повторяться до тех пор, пока значение переменной ivalue не достигнет 6, что приведет к завершению цикла.

В программе на языке C++ приведенный фрагмент будет записан следующим образом (найдите одно отличие):

isum = 0;

for (intivalue = 1; ivalue <= 5; ivalue-м-) isum += ivalue;

В C++ допускается объявление переменных прямо в строке инициализации цикла for. Тут затрагивается достаточно важный вопрос: где вообще следует размещать все объявления переменных? В C++ переменные можно создавать непосредственно перед той строкой, где они впервые используются. Поскольку в нашем случае переменная ivalue используется только внутри цикла, ее объявление в цикле не нарушает стройности программы. Но рассмотрим такой пример:

int isum = 0;

for(intivalue = i; ivalue <= 5; ivalue++) isum += ivalue;

Подобное объявление переменной isum можно назвать плохим стилем программирования, если ее применение не ограничивается данным циклом. Желательно все объявления локальных переменных собирать сразу после заголовка функции, к которой они относятся, так как это повышает удобочитаемость программы и облегчает ее отладку. Значение счетчика цикла for вовсе не обязательно должно меняться только на единицу. В следующем примере вычисляется сумма ряда нечетных чисел от 1 до 9:

iodd_sum = 0;

for(iodd_value = 1;   iodd_value <— 9; 

  iodd_value += 2); iodd_sum +=  iodd_value;

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

/*
*       forloop.cpp
*       В этой программе на языке C++ демонстрируется использование
*       цикла  for для  работы с массивом символов.
*/

#include <stdio.h>
#define CARRAY_SIZE 10

int main()

{
int ioffset;
char carray[CARRAY_SIZE];

for(ioffset = 0; ioffset < CARRAY_SIZE; ioffset++)

carray[ioffset] = getchar(); for(ioffset = CARRAY_SIZE - 1; ioffset >= 0; ioffset—)

putchar(carray[ioffset]);

return(0); }

В первом цикле for переменной ioffset присваивается начальное значение 0, поскольку адресация элементов массива начинается с нуля. Затем происходит считывание символов по одному до тех пор, пока значение переменной ioffset не станет равным размеру массива. Во втором цикле for, в котором элементы массива выводятся на экран в обратном порядке, переменная ioffset инициализируется номером последнего элемента массива и по ходу цикла уменьшается на единицу. Цикл будет выполняться до тех пор, пока переменная ioffset не примет значение 0.

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

/* 
*       nsloopl.с
*       В этой программе на языке С демонстрируется важность
*       правильной расстановки фигурных скобок во вложенных циклах.
*/

#include <stdio.h>

int main()

{
int iouter_val, iinner_val;

for (iouter_val = 1; iouter_val <= 4; iouter val++) ( printf ("\n%3d--",iouter_val) ;

for (iinner_val = 1; iinner_val <= 5; iinner_val++) printf ("%3d",iouter_val * iinner_val) ;

return(0); }

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

1   —   12345..

2   —  2     4     6     8   10

3   —  3     б     9   12   15

4   —   4      8   12   16   20

А теперь представим, что внешний цикл записан без фигурных скобок:

/*
*       nsloop2.c
*  В этой программе на языке С демонстрируется, что произойдет, если при
*  задании внешнего цикла не поставить фигурные скобки.
*/

#include  <stdio.h>

int main ()

int  iouter_val,   iinner_val;

for(iouter_val  =  1;   iouter_val  <= 4;   iouter_val++) printf("\n%3d-",   iouter_val);

for(iinner_val = >>1;   iinner_val  <=  5;   iinner_val++) printf("%3d",   iouter_val  *  iinner_val);

return(0);

Выводимые данные будут выглядеть совершенно иначе:

1  --

2  --

3  --

4  --  5  10  15  20  25

Если тело внешнего цикла не будет выделено фигурными скобками, то к первому циклу будет отнесена только следующая за ним строка с функцией printf(). Только после того как функция printf() будет вызвана четыре раза подряд, начнется выполнение следующего цикла for. Во внутреннем цикле будет использовано самое последнее значение переменной iouter_val, т.е. 5, на основе которого будет сгенерирован и выведен очередной функцией printf() соответствующий ряд чисел.

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

Вот первый пример:

/*
*         nsloop3.c
*      Еще одна программа на языке С,   демонстрирующая использование фигурных скобок
*      в конструкциях со вложенными циклами.
*/

#include  <stclio.h>

int main () {

int iouter_val, iinner_val;

for(iouter_val = 1;   iouter_val <= 4; iouter_val++)

 { for(iinner_val =  1; iinner_val <= 5; iinner_val++)

printf("%3d",    iouter_val * iinner_val);

}
return(0);
 }

Запишем эту же программу несколько иначе:

/*
*       nsloop4.c
*  Видоизмененный вариант предыдущей программы.
*/

#include <stdio.h>

int main() {

int iouter_yal, iinner_val;

for(iouter_val = 1;  iouter_val <= 4; iouter_val++)

for(iinner_val = 1; iinner_val <= 5; iinner_val++)

printf("%3d", iouter_val * iinner_val);

return(0); }

В обоих случаях на экран будет выводиться одна и та же последовательность:

1    2     3    4     5    2     4     6    8     10    3    6    9    12    15    4     8    12    16    20

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

Цикл while

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

while(условие) выражение;

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

while(условие) ( выражение1; выражение2; выражениеЗ;

выражение-п; }

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

/*
*  while.с
*  В этой программе на языке С демонстрируется использование цикла while.
*/

#include <stdio.h>
#define WORD 16
#define ONE_BYTE 8

int main()

{

int ivalue = 256,ibit_position = 1;

unsigned int umask = 1;

printf("Число%d\n", ivalue);

printf("имеет следующий двоичный эквивалент: ");

while(ibit_position <= WORD) {

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

& umask)    /* сдвигаем каждый     */

printf("l");/* бит на нулевую      */
else        /* позицию и сравниваем */

printf("0");/* число с константой  */
if(ibit_position == ONE_BYTE)   /* umask    */

printf (" "); ibit_position++;

return(0);

}

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

В следующей программе пользователю предлагается задать имена файлов для ввода и вывода данных. Цикл whilе используется для чтения данных из одного файла с одновременной записью их в другой файл, причем размер файлов изначально не известен.

/* 
*     fwhile.с
*  В этой программе на языке С цикл while используется
*  при работе с файлами. В программе демонстрируются
*   дополнительные возможности файлового ввода/вывода.
*/

#include <stdio.h> 
#define MAX_CHARS 30

int main() {

int c;

FILE *ifile, *ofile;

char szi_file_name [MAX_CHARS] , 
       szo_file_name[MAX_CHARS] ;

printf("Введите имя исходного файла: "); 
scanf("%s",szi_file_name);

if((ifile= fopen(szi_file_name, "r">>== NULL) 
{

printf("\nФайл%s не может быть открыт", szi_file_name);
 return(0);

printf ("Введите имя выходного файла:   "); 
scanf("%s",   szo_f ile_name) ;

if ((oflie  =  fopen(szp_file_name,   "w"))   == NULL)  
  {

printf ("\nФайл %s не может быть открыт",   szo_f ile_name) ; 
return (0);

while ((с= fgetc(ifile)) != EOF) 
fputc(c,of ile) ;

return(0); }

В программе также показано использование функций файлового ввода-вывода данных, таких как fopen ( ) , fgetc( ) и fputc( ) (более подробно об этих функциях см. в главе "Ввод-вывод в языке С").

Цикл do/while

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

Цикл do/while имеет следующий синтаксис:

do              .

выражение; while(условие) ;

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

do{

выражение1; выражение2; выражениеЗ;

выражение-n ; }
while(условие);

В следующем примере цикл do/while используется для получения от пользователя строки текста:

//
//   dowhile.cpp
//  В этой программе на языке C++ демонстрируется
//  использование цикла do/while.
//

#include <iostream.h>

#define LENGTH 80

int main()

{

char cSentence [LENGTH] ;

int iNumChars = 0, iNumWords = 1;

do{

cout << "Введите предложение : ";

cin.getline (cSentence, LENGTH); } while (cSentence [0]== '\0');

while (cSentence [iNumChars] != '\0')
{ if (cSentence [iNumChars] !=''&& 
   cSentence [iNumChars] != '\t'&& 
  (cSentence [iNumChars+1] == ' ' || 
   cSentence [iNumChars+1] == '\t'||
  cSentence [iNumChars+1] == '\0'))
 iNumWords++; 
iNumChars++;

cout<< "Вы ввели " << iNumChars<< " символов\n";

cout<< "Вы ввели " << iNumWords<< " слов";

return(0); }

В цикле do/while запрос будет повторяться до тех пор, пока пользователь не введет хотя бы один символ. При простом нажатии клавиши [Enter] функция getline( ) возвращает символ null, в таком случае цикл повторяется. Как только в массив будет записана строка, цикл завершится и программа произведет подсчет количества введенных символов и слов.

Инструкции перехода

В языках C/C++ имеется четыре инструкции перехода: goto, break, continue и return. Инструкция goto(или ее аналог) существует во многих языках высокого уровня и предназначена для принудительного перехода по указанному адресу, определяемому меткой. Ее синтаксис таков:

goto метка;

В отличие от языка типа Basic, где данная инструкция находит широкое применение, в C/C++ наличие в программе команды goto рассматривается как плохой стиль программирования, и считается, что в четко структурированной и грамотно написанной программе этой инструкции быть не должно.

Инструкция break

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

/* 
*         break. с
*   В этой программе на языке С демонстрируется использование
*   инструкции break.
*/

int main ()

{  int itimes = 1, isum = 0;

while (itimes < 10) {
isum += itimes;
if (isum > 20)

break;
itimes++; 

return (0); }

Проанализируйте работу этой программы с помощью отладчика, контролируя значения переменных isum и itimes. Обратите внимание на то, что происходит, когда переменная isum достигает значения 21. Вы увидите, что в этом случае выполняется инструкция break, в результате чего цикл прекращается и выполняется первая строка, следующая за телом цикла. В нашем примере это инструкция return, которая завершает программу.

Инструкция continue

Инструкция continue заставляет программу пропустить все оставшиеся строки цикла, но сам цикл не завершается. Действие этой инструкции аналогично выполнению команды goto, указывающей на строку условия цикла. Если условное выражение возвратит true, цикл будет продолжен.

Ниже показан текст программы, реализующей игру в угадывание чисел и использующей возможности инструкции continue:

/*
*       continue.с
*  В этой программе на языке С демонстрируется использование
*  инструкции continue.
*/

#include <stdio.h>
#define TRUE 1
#define FALSE 0

int main ()

{
int ilucky_number = 77,

iinput_val,

inumber_of_tries = 0,

iam_lucky = FALSE;

while(!iam_lucky) {

printf("Введите число: ");

scanf("%d",&iinput_val);

inumber_of_tries++;

if(iinput_val == ilucky_number)

iam_lucky = TRUE; else {

if(iinput_val > ilucky_number)

printf("Ваше число больше!\n"); else

printf("Ваше число меньше!\n"); continue; } printf("Вам потребовалось всего %d попыток, чтобы

отгадать счастливое число!", inumber_of_tries); }

return(0);  }

В цикле while пользователю предлагается ввести свой вариант числа, после него значение переменной inumber_of_tries, хранящей число попыток, увеличивается на единицу. Если попытка оказалась неудачной, программа переходит в ветвь else, в которой пользователю дается подсказка и выполняется инструкция continue, подавляющая вывод поздравительного сообщения функцией printf(). Тем не менее, выполнение цикла продолжается. Как только будет получено соответствие введенного и счастливого чисел, переменной iam_lucky будет присвоено значение true, в результате чего будет выполнена функция printf( ) .

Совместное использование инструкций break и continue

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

/*
*       breakcon.c
*  В этой программе на языке С используются инструкции break и continue.
*/

#include <stdio.h>
#include <ctype.h>
#define NEWLINE '\n'

int main()

{
int c;

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

if(isascii(с) == 0) {

printf("Введенный символ не является ASCII-символом;  ") ;

printf("продолжение невозможно\n");

break;
if(ispunct(c)    II   isspace(c))   {

putchar(NEWLINE);

continue;   
}
if(isprint(c)   ==  0)

continue;

putchar(c); }

return(0);

}   

На вход программы поступают такие данные:

word control < exclamation! apostrophe' period. 
^Z

Вот что будет получено в результате:

word control

exclamation

apostrophe

period

Данная программа считывает поступающие символы до тех пор, пока не будет введен признак конца файла ^Z [Ctrl+Z]. Затем производится анализ введенных символов, удаляются непечатаемые знаки, а все слова разделяются разрывами строк. Все эти действия выполняются с помощью функций, описанных в файле CTYPE.H: isascii(), ispunct(),isspace() и isprint(}.Каждая функция получает в качестве аргумента символ и возвращает ноль или единицу в зависимости от принад­лежности этого символа соответствующей категории.

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

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

Если формат входных данных подходящий, то проверяется, является ли введенный символ пробелом или знаком препинания. Если это так, выводится пустая строка и выполняется инструкция continue, инициирующая следующую итерацию цикла.

Далее проверяется, является ли введенный символ печатаемым. Обратите внимание, что во входных данных содержится символ < ([Ctri+Q]). Поскольку это непечатаемый символ, он не отображается вовсе, и выполняется инструкция continue, завершающая текущую итерацию цикла.

В случае, если анализируемый символ является текстовым, выполняется функция putchar(), выводящая его на экран.

Инструкция return

Иногда необходимо прервать выполнение программы задолго до Того, как будут выполнены все ее строки. Для этой цели в языках C/C++ существует уже знакомая вам инструкция return, которая завершает выполнение той функции, в которой она была вызвана. Если вызов произошел в функции main() , то завершается сама программа. В этом случае инструкция return принимает единственный целочислен­ный аргумент, называемый кодом завершения. В операционных системах UNIX и MS-DOS нулевой код воспринимается как нормальное завершение программы, тогда как любое другое значение является признаком ошибки.

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

Следующая программа вычисляет среднее арифметическое ряда чисел, содержа­щего не более 30-ти значений, а также находит минимальное и максимальное значение ряда. Если пользователь хочет задать ряд, размер которого превышает максимально допустимый, выдается предупреждающее сообщение и выполнение программы прерывается с помощью инструкции return.

//
//   return.cpp
//  В этой программе на языке C++ демонстрируется использование
//  инструкции return.
//

#include <iostream.h> 
#define LIMIT 30

int main() {

int irow, irequested_qty, iscores[LIMIT], imin_score, imax_score;

float fsum = 0, faverage;

cout<< "\nВведите число значений ряда: ";

cin >> irequested_qty;

if(irequested_qty > LIMIT) {

cout<< "\nВы можете ввести не более " << LIMIT << " значений.\n" cout<< "\n>>> Программа завершена. <<<\n";

return(0);
}    .

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

cout << "\nВведите "'<<irow+1 << "-иэлементряда: cin >> iscores[irow];

}

for(irow = 0; irow < irequested_qty; irow++) fsum = fsum + iscores[irow];

faverage = fsum/(float)irequested_qty; imax^score = imin_score = iscores[0];

for(irow =1;irow < irequested_qty; irow++) {
if(iscores[irow] > imax_score)
imax_score = iscores[irow];
if(iscores[irow] < imin_score)
imin_score = iscores[irow];
}

cout<<    "\nМаксимальное значение    =  "    <<    imax_score;

cout<<    "\nМинимальное значение    =  "    <<    imin_score;

cout<<    "\nСреднее значение    =  "    <<    faverage;

return(0); }

Функция exit( )

Существует библиотечная функция exit(),которая аналогична инструкции return и объявлена в файле STDLIB.H. В этом файле также описаны две дополнительные константы, которые могут быть переданы в качестве аргументов этой функции: exit_success(сигнализирует об успешном завершении программы) и exit_failure(сигнализирует об ошибке). Использование этих констант позволяет сделать текст программы чуть более понятным, хотя компилятор выдает предупреж­дение, если не встречает в функции main() ни одной инструкции return.

Рассмотрим слегка видоизмененный вариант предыдущей программы.

//
//  exit2.cpp
//  Вариант предыдущей программы, в котором вместо
//  инструкции return применяется функция exit() .
//

#include <iostream.h>
#include <stdlib.h>
#define LIMIT 30

int main () {

int irow, irequested_qty, iscores [LIMIT];

float fsum =0,imin_score, imax_score, faverage;

cout << "\nВведите число значений ряда: ";
 cin >> irequested_qty;

if(irequested_qty > LIMIT) {

cout<< "\nВы можете ввести не более " << LIMIT<< " значений.\n" cout<< "\n >>> Программа завершена. <<<\n"; exit(EXIT FAILURE);

}

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

cout << "\nВведите " << irow+1 << "-и элемент ряда: cin >> iscores[irow];

}

for(irow = 0; irow < irequested_qty; irow++) fsum = fsum + iscores[irow];

faverage = fsum/(float)irequested_qty; imin_score = imax_score = iscores[0];

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<<     "\nМаксимальное значение =     "  <<     imax_score;

cout<<     "\nМинимальное значение =     "  <<     imin_score;

cout<<     "\nСреднее значение     =    "  <<     faverage;

exit(EXIT_SUCCESS); )

Функция atexit( )

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

/* 
*      atexit.с
  В этой программе на языке С демонстрируется способ задания процедур,
*  выполняемых при завершении программы, а также показывается, как влияет
*  порядок их регистрации на очередность выполнения.
*/

#include <stdio.h> 
#include <stdlib.h>

void atexit f nl (void) ;  

void atexit_fn2(void); void atexit_fn3(void) ;

int main()

{

atexit(atexit_fnl);

atexit(atexit_fn2);

atexit(atexit_fn3);

printf("Программа atexit запущена.\n");
 printf("Программа atexit завершена.\n\n") printf (">>>>>>>>>>> <<<<<<<<<<<\n\n") ;

return (0);

}

void atexit_fnl(void) {

printf("Запущена функция atexit_fnl.\n"); }

void atexit_fn2(void) (

printf("Запущена функция atexit_fn2.\n"); 1

void atexit_fn3(void) {

printf ("Запущена функция atexit_fn3An") ; )

При выполнении программы на экран будет выведена следующая информация:

Программа atexit запущена. Программа atexit завершена.

>>>>>>>>>>> <<<<<<<<<<<    

Запущена функция atexit_fn3. Запущена функция atexit_fn2. Запущена функция atexit_fnl.

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

В действительности функция atexit() ведет список функций, которые будут выполнены при завершении программы. Причём порядок выполнения функций противоположен порядку их регистрации. То есть первая зарегистрированная функция будет выполняться последней, а последняя — первой. Вот почему сообщение о запуске функции atexit_fnЗ () появилось раньше, чем сообщение о запуске функции atexit_fnl (). Как правило, "финальные" функции используются для удаления из памяти динамически созданных объектов. Поскольку один объект мог быть создан на основе другого, удаление их из памяти осуществляется в обратном порядке.