учебники, программирование, основы, введение в,

 

Управляющие конструкции языка Си. Представление программ в виде функций. Работа с памятью. Структуры

http://localhost:3232/img/empty.gifУправляющие конструкции

Управляющие конструкции позволяют организовывать циклы и ветвления в программах. В Си всего несколько конструкций, причем половину из них можно не использовать (они реализуются через остальные).
Фигурные скобки
Фигурные скобки позволяют объединить несколько элементарных операторов в один составной оператор, или блок. Во всех синтаксических конструкциях составной оператор можно использовать вместо простого.
В Си в начало блока можно помещать описания локальных переменных. Локальные переменные, описанные внутри блока, создаются при входе в блок и уничтожаются при выходе из него.
В C++ локальные переменные можно описывать где угодно, а не только в начале блока. Тем не менее, они, так же как и в Си, автоматически уничтожаются при выходе из блока.
Приведем фрагмент программы, обменивающий значения двух вещественных переменных:
double x, y;
. . .
{
double tmp = x;
x = y;
y = tmp;
}
Здесь, чтобы обменять значения двух переменных x и y, мы сначала запоминаем значение x во вспомогательной переменной tmp. Затем в x записывается значение y, а в y - сохраненное в tmp предыдущее значение x. Поскольку переменная tmp нужна только внутри этого фрагмента, мы заключили его в блок и описали переменную tmp внутри этого блока. По выходу из блока память, занятая переменной tmp, будет освобождена.
Оператор if
Оператор if ("если") позволяет организовать ветвление в программе. Он имеет две формы: оператор "если" и оператор "если...иначе". Оператор "если" имеет вид
if (условие)
действие;
оператор "если...иначе" имеет вид
if (условие)
действие1;
else
действие2;
В качестве условия можно использовать любое выражение логического или целого типа. Напомним, что при использовании целочисленного выражения значению "истина" соответствует любое ненулевое значение. При выполнении оператора "если" сначала вычисляется условное выражение после if. Если оно истинно, то выполняется действие, если ложно, то ничего не происходит. Например, в следующем фрагменте в переменную m записывается максимальное из значений переменных x и y:
double x, y, m;
. . .
m = x;
if (y > x)
m = y;
При выполнении оператора "если...иначе" в случае, когда условие истинно, выполняется действие, записанное после if; в противном случае выполняется действие после else. Например, предыдущий фрагмент переписывается следующим образом:
double x, y, m;
. . .
if (x > y)
m = x;
else
m = y;
Когда надо выполнить несколько действий в зависимости от истинности условия, следует использовать фигурные скобки, объединяя несколько операторов в блок, например,
double x, y, d;
. . .
if (d > 1.0) {
x /= d;
y /= d;
}
Здесь переменные x и y делятся на d только в том случае, когда значение d больше единицы.
Фигурные скобки можно использовать даже, когда после if или else стоит только один оператор. Они улучшают структуру текста программы и облегчают ее возможную модификацию. Пример:
double x, y;
. . .
if (x != 0.0) {
y = 1.0;
}
Если нужно будет добавить еще одно действие, выполняемое при условии "x отлично от нуля", то мы просто добавим строку внутри фигурных скобок.
Выбор из нескольких возможностей: if...else if...
Несколько условных операторов типа "если...иначе" можно записывать последовательно (т.е. действие после else может снова представлять собой условный оператор). В результате реализуется выбор из нескольких возможностей. Конструкция выбора используется в программировании очень часто. Пример: дана вещественная переменная x, требуется записать в вещественную переменную y значение функции sign(x):
sign(x) = -1, при x < 0
sign(x) = 1,  при x > 0
sign(x) = 0,  при x = 0
Это делается с использованием конструкции выбора:
double x, s;
. . .
if (x < 0.0) {
s = (-1.0);
}
else if (x > 0.0) {
s = 1.0;
}
else {
s = 0.0;
}
При выполнении этого фрагмента сперва проверяется условие x < 0.0. Если оно истинно, то выполняется оператор s = (-1.0); иначе проверяется второе условие x < 0.0. В случае его истинности выполняется оператор s = 1.0, иначе выполняется оператор s = 0.0. Фигурные скобки здесь добавлены для улучшения структурности текста программы.
В любом случае, в результате выполнения конструкции выбора исполняется лишь один из операторов (возможно, составных). Условия проверяются последовательно сверху вниз. Как только находится истинное условие, то производится соответствующее действие и выбор заканчивается.

http://localhost:3232/img/empty.gifПример: решение квадратного уравнения

Рассмотрим простой пример, в котором применяется конструкция "если...иначе": требуется решить квадратное уравнение
ax2+bx+c = 0
Программа должна ввести с клавиатуры терминала числа a, b, c и затем напечатать ответ. После ввода надо проверить корректность введенных чисел - коэффициент a должен быть отличен от нуля (иначе уравнение перестает быть квадратным, тогда формула решения квадратного уравнения неприменима). В зависимости от знака дискриминанта уравнение может не иметь решений. Программа должна напечатать либо сообщение об отсутствии решений, либо два корня уравнения (возможно, совпадающие в случае нулевого дискриминанта).
Для печати на экран терминала и ввода информации с клавиатуры используются функции ввода-вывода из стандартной библиотеки Си. Отметим, что функции стандартного ввода-вывода не являются частью языка Си: Си не содержит средств ввода-вывода. Однако любой компилятор обычно предоставляет набор библиотек, в который входит стандартный ввод-вывод. Описания функций ввода-вывода содержатся в заголовочном файле stdio.h, который подключается с помощью строки
#include <stdio.h>
Мы используем две функции: функцию printf вывода по формату и функцию scanf ввода по формату. У обеих этих функций число аргументов переменное, первым аргументом всегда является форматная строка. В случае функции printf обычные символы форматной строки просто выводятся на экран терминала. Например, в рассмотренном ранее примере "Hello, World!" текст выводился на экран с помощью строки прoграммы
printf("Hello, World!\n");
(Здесь '\n' - символ конца строки, т.е. перевода курсора в начало следующей строки.) Единственным аргументом функции printf в данном случае служит форматная строка.
Кроме обычных символов, форматная строка может включать символы формата, которые при выводе заменяются значениями остальных аргументов функции printf, начиная со второго аргумента. Для каждого типа данных Си имеются свои форматы. Формат начинается с символа процента '%'. После процента идет необязательный числовой аргумент, управляющий представлением данных. Наконец, далее идет одна или несколько букв, задающих тип выводимых на печать данных. Для вывода чисел можно использовать следующие форматы:
%d  вывод целого числа типа int (d - от decimal)
%lf вывод вещ. числа типа double (lf - от long float)
Например, для печати целого числа n можно использовать строку
printf("n = %d\n", n);
Здесь формат "%d" будет заменен на значение переменной n. Пусть, к примеру, n = 15. Тогда при выполнении функции printf будет напечатана строка
n = 15
При печати вещественного числа компьютер сам решает, сколько знаков после десятичной точки следует напечатать. Если нужно повлиять на представление числа, следует использовать необязательную часть формата. Например, формат
%.3lf
применяется для печати значения вещественного числа в форме с тремя цифрами после десятичной точки. Пусть значение вещественной переменной x равно единице. Тогда при выполнении функции
printf("ответ = %.3lf\n", x);
будет напечатана строка
ответ = 1.000
При вызове функции форматного ввода scanf форматная строка должна содержать только форматы. Этим функция scanf отличается от printf. Вместо значений печатаемых переменных или выражений, как в функции printf, функция scanf должна содержать указатели на вводимые переменные! Для начинающих это постоянный источник ошибок. Необходимо запомнить: функции scanf нужно передавать адреса переменных, в которые надо записать введенные значения. Если вместо адресов переменных передать их значения, то функция scanf все равно проинтерпретирует полученные значения как адреса, что при выполнении вызовет попытку записи по некорректным адресам памяти и, скорее всего, приведет к ошибке типа Segmentation fault. Пример: пусть нужно ввести значения трех вещественных переменных a, b, c. Тогда следует использовать фрагмент
scanf("%lf%lf%lf", &a, &b, &c);
Ошибка, которую часто совершают начинающие: передача функции scanf значений переменных вместо адресов:
scanf("%lf%lf%lf", a, b, c); // Ошибка! Передаются
// значения вместо указателей
Помимо стандартной библиотеки ввода-вывода, в Си-программах широко используется стандартная библиотека математических функций. Ее описания содержатся в стандартном заголовочном файле math.h, который подключается строкой
#include <math.h>
Стандартная математическая библиотека содержит математические функции sin, cos, exp, log (натуральный логарифм), fabs (абсолютная величина вещ. числа) и многие другие. Нам необходима функция sqrt, вычисляющая квадратный корень вещественного числа.
Итак, приведем полный текст программы, решающей квадратное уравнение; он содержится в файле "squareEq.cpp".
#include <stdio.h>  // Описания стандартного ввода-вывода
#include <math.h>   // Описания математической библиотеки

int main() {
double a, b, c; // Коэффициенты уравнения
double d;       // Дискриминант
double x1, x2;  // Корни уравнения

    printf("Введите коэффициенты a, b, c:\n");
scanf("%lf%lf%lf", &a, &b, &c);

    if (a == 0.0) {
printf("Коэффициент a должен быть ненулевым.\n");
return 1;   // Возвращаем код некорректного
}               //                завершения

    d = b*b - 4.0*a*c;  // Вычисляем дискриминант
if (d < 0.0) {
printf("Решений нет.\n");
}
else {
d = sqrt(d); // Квадр. корень из дискриминанта
x1 = (-b + d) / (2.0 * a); // Первый корень ур-я
x2 = (-b - d) / (2.0 * a); // Второй корень ур-я

        // Печатаем ответ
printf(
"Решения уравнения: x1 = %lf, x2 = %lf\n",
x1, x2
);
}
return 0; // Возвращаем код успешного завершения
}
Приведем пример выполнения программы:
Введите коэффициенты a, b, c:
1 2 -3
Решения уравнения: x1 = 1.000000, x2 = -3.000000
Здесь первая и третья строчки напечатаны компьютером, вторая строчка напечатана человеком (ввод чисел заканчивается клавишей перевода строки Enter).
Цикл while
Конструкция цикла "пока" соответствует циклу while в Си:
while (условие)
действие;
Цикл while называют циклом с предусловием, поскольку условие проверяется перед выполнением тела цикла.
Цикл while выполняется следующим образом: сначала проверяется условие. Если оно истинно, то выполняется действие. Затем снова проверяется условие; если оно истинно, то снова повторяется действие, и так до бесконечности. Цикл завершается, когда условие становится ложным. Пример:
int n, p;
. . .
p = 1;
while (2*p <= n)
p *= 2;
В результате выполнения этого фрагмента в переменной p будет вычислена максимальная степень двойки, не превосходящая целого положительного числа n.
Если условие ложно с самого начала, то действие не выполняется ни разу. Это очень облегчает программирование и делает программу более надежной, поскольку исключительные ситуации автоматически правильно обрабатываются. Так, приведенный выше фрагмент работает корректно при n = 1 (цикл не выполняется ни разу).
При ошибке программирования цикл может никогда не кончиться. Чтобы избежать этого, следует составлять программу таким образом, чтобы некоторая ограниченная величина, от которой прямо или косвенно зависит условие в заголовке цикла, монотонно убывала или возрастала после каждого выполнения тела цикла. Это обеспечивает завершение цикла. В приведенном выше фрагменте такой величиной является значение p, которое возрастает вдвое после каждого выполнения тела цикла.
Тело цикла может состоять из одного или нескольких операторов. В последнем случае их надо заключить в фигурные скобки. Советуем заключать тело цикла в фигурные скобки даже в том случае, когда оно состоит всего из одного оператора, - это делает текст программы более наглядным и облегчает его возможную модификацию. Например, приведенный выше фрагмент лучше было бы записать так:
int n, p;
. . .
p = 1;
while (2*p <= n) {
p *= 2;
}
Сознательное применение цикла "пока" всегда связано с явной формулировкой инварианта цикла, см. раздел 1.5.2.
Рассмотрим построение цикла "пока" на примере программы вычисления квадратного корня методом деления отрезка пополам.
Пример: вычисление квадратного корня методом деления отрезка пополам
Метод вычисления корня функции с помощью деления отрезка пополам в общем случае уже был рассмотрен в разделе 1.5.2. Пусть надо найти квадратный корень из неотрицательного вещественного числа a с заданной точностью ε. Задача сводится к нахождению корня функции
y = x2-a
на отрезке [0,b], где b = max(1,a). На этом отрезке функция имеет ровно один корень, поcкольку она монотонно возрастает и на концах отрезка принимает значения разных знаков (или нулевое значение при a = 0 или a = 1).
Идея алгоритма состоит в том, что отрезок делится пополам и выбирается та половина, на которой функция принимает значения разных знаков. Эта операция повторяется до тех пор, пока длина отрезка не станет меньше, чем ε. Концы текущего отрезка содержатся в переменных x0, x1. В данном случае функция монотонно возрастает при x http://localhost:3232/img/symbols/ge.gif0. Инвариантом цикла является утверждение о том, что функция принимает отрицательное или нулевое значение в точке x0 и положительное или нулевое значение в точке x1. Цикл рано или поздно завершается, поскольку после каждого выполнения тела цикла длина отрезка [x0,x1] уменьшается в два раза.
Приведем полный текст программы:
#include <stdio.h> // Описания стандартного ввода-вывода

int main() {
double a;   // Число, из которого извлекается корень
double x, x0, x1;      // [x0, x1] - текущий отрезок
double y;              // Значение ф-ции в точке x
double eps = 0.000001; // Точность вычисления корня

    printf("Введите число a:\n");
scanf("%lf", &a);

    if (a < 0.0) {
printf("Число должно быть неотрицательным.\n");
return 1; // Возвращаем код
}             //     некорректного завершения

    // Задаем концы отрезка
x0 = 0.0;
x1 = a;
if (a < 1.0) {
x1 = 1.0;
}

    // Утверждение: x0 * x0 - a <= 0,
//              x1 * x1 - a >= 0

    while (x1 - x0 > eps) {
// Инвариант: x0 * x0 - a <= 0,
//            x1 * x1 - a >= 0
x = (x0 + x1) / 2.0; // середина отрезка [x0,x1]
y = x * x - a;       // значение ф-ции в точке x

        if (y >= 0.0) {
x1 = x; // выбираем левую половину отрезка
}
else {
x0 = x; // выбираем правую половину отрезка
}
}

    // Утверждение: x0 * x0 - a <= 0,
//              x1 * x1 - a >= 0,
//              x1 - x0 <= eps
x = (x0 + x1) / 2.0; // Корень := середина отрезка

    // Печатаем ответ
printf("Квадратный корень = %lf\n", x);

    return 0; // Возвращаем код успешного завершения
}
Отметим, что существует более быстрый способ вычисления квадратного корня числа - метод итераций Ньютона, или метод касательных к графику функции, но здесь мы его не рассматриваем.

http://localhost:3232/img/empty.gifВыход из цикла break, переход на конец цикла continue

Если необходимо прервать выполнение цикла, следует использовать оператор
break;
Оператор break применяется внутри тела цикла, заключенного в фигурные скобки. Пример: требуется найти корень целочисленной функции f(x), определенной для целочисленных аргументов.
int f(int x);   // Описание прототипа функции
. . .
int x;
. . .
// Ищем корень функции f(x)
x = 0;
while (true) {
if (f(x) == 0) {
break;  // Нашли корень
}
// Переходим к следующему целому значению x
//    в порядке 0, -1, 1, -2, 2, -3, 3, ...
if (x >= 0) {
x = (-x - 1);
}
else {
x = (-x);
}
}
// Утверждение: f(x) == 0
Здесь используется бесконечный цикл "while (true)". Выход из цикла осуществляется с помощью оператора "break".
Иногда требуется пропустить выполнение тела цикла при каких-либо значениях изменяющихся в цикле переменных, переходя к следующему набору значений и очередной итерации. Для этого используется оператор
continue;
Оператор continue, так же, как и break, используется лишь в том случае, когда тело цикла состоит более чем из одного оператора и заключено в фигурные скобки. Его следует понимать как переход на фигурную скобку, закрывающую тело цикла. Пример: пусть задана n+1 точка на вещественной прямой xi, i = 0, 1,..., n; точки xi будут называться узлами интерполяции. Элементарный интерполяционный многочлен Лагранжа Lk(x) - это многочлен степени n, который принимает нулевые значения во всех узлах xi, кроме xk. В k-ом узле xk многочлен Lk(x) принимает значение 1. Многочлен Lk(x) вычисляется по следующей формуле:
Пусть требуется вычислить значение элементарного интерполяционного многочлена Lk(x) в заданной точке x = t. Это делается с помощью следующего фрагмента программы:
double x[100]; // Узлы интерполяции (не более 100)
int n;    // Количество узлов интерполяции
int k;    // Номер узла
double t; // Точка, в которой вычисляется значение
double L; // Значение многочлена L_k(x) в точке t
int i;
. . .
L = 1.0;  // Начальное значение произведения
i = 0;
while (i <= n) {
if (i == k) {
++i;      // К следующему узлу
continue; // Пропустить k-й множитель
}

    // Вычисляем произведение
L *= (t - x[i]) / (x[k] - x[i]);
++i; // К следующему узлу
}
// Ответ в переменной L
Здесь оператор continue используется для того, чтобы пропустить вычисление произведения при i = k.
Оператор перехода на метку goto
Оператор перехода goto позволяет изменить естественный порядок выполнения программы и осуществить переход на другой участок программы, обозначенный меткой. Переход может осуществляться только внутри функции, т.е. оператор goto не может ни выйти из функции, ни войти внутрь другой функции. Оператор goto выглядит следующим образом:
L: ...;
. . .
goto L;
В качестве метки можно использовать любое имя, допустимое в Си (т.е. последовательность букв, цифр и знаков подчеркивания "_", начинающуюся не с цифры). Метка может стоять до или после оператора goto. Метка выделяется символом двоеточия ":". Лучше после него сразу ставить точку с запятой ";", помечая таким образом пустой оператор - это общепринятая программистская практика, согласно которой метки ставятся между операторами, а не на операторах.
Не следует увлекаться использованием оператора goto - это всегда запутывает программу. Большинство программистов считают применение оператора goto дурным стилем программирования. Вместо goto при необходимости можно использовать операторы выхода из цикла break и пропуска итерации цикла continue (см. раздел 3.5.7). Единственная ситуация, в которой использование goto оправдано, - это выход из нескольких вложенных друг в друга циклов:
while (...) {
. . .
while (...) {
. . .
if (...) {
goto LExit; // Выход из двух
// вложенных циклов
}
. . .
}
}
LExit: ;

  • В объектно-ориентированном языке Java, синтаксис которого построен на основе языка Си, использование оператора goto запрещено. Вместо него для выхода из нескольких вложенных друг в друга циклов применяется форма оператора break с меткой. Меткой помечается цикл, из которого надо выйти:

Loop1:
while (...) {
. . .
while (...) {
. . .
break Loop1; // Выход из цикла,
// помеченного меткой Loop1
. . .
}
. . .
}

Цикл for

Популярный в других языках программирования арифметический цикл (см. с. 41) в языке Си реализуется с помощью цикла for. Он выглядит следующим образом:

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

Инициализация выполняется один раз перед первой проверкой условия продолжения и первым выполнением тела цикла. Условие продолжения проверяется перед каждым выполнением тела цикла. Если условие истинно, то выполняется тело цикла, иначе цикл завершается. Итератор выполняется после каждого выполнения тела цикла (перед следующей проверкой условия продолжения).
Поскольку условие продолжения проверяется перед выполнением тела цикла, цикл for является, подобно циклу while, циклом с предусловием. Если условие продолжения не выполняется изначально, то тело цикла не выполняется ни разу, а это хорошо как с точки зрения надежности программы, так и с точки зрения простоты и эстетики (поскольку не нужно отдельно рассматривать исключительные случаи).
Рассмотрим пример суммирования массива с использованием цикла for:

double a[100]; // Массив a содержит не более 100 эл-тов
int n;         // Реальная длина массива a (n <= 100)
double sum;    // Переменная для суммы эл-тов массива
int i;         // Переменная цикла
. . .
sum = 0.0;
for (i = 0; i < n; ++i) {
    sum += a[i]; // Увеличиваем сумму на a[i]
}

Здесь целочисленная переменная i используется в качестве переменной цикла. В операторе инициализации переменной i присваивается значение 0. Условием продолжения цикла является условие i<n. Итератор ++i увеличивает переменную i на единицу. Таким образом, переменная i последовательно принимает значения 0, 1, 2,..., n-1. Для каждого значения i выполняется тело цикла.
В большинстве других языков программирования арифметический цикл жестко связан с использованием переменной цикла, которая должна принимать значения из арифметической прогрессии. В Си это не так, здесь инициализация, условие продолжения и итератор могут быть произвольными выражениями, что обеспечивает гораздо большую гибкость программы. Конструкцию цикла for можно реализовать с помощью цикла while:

for (
    инициализация;          инициализация;
    условие;                while (условие) {
    итератор         ~          тело цикла;
) {                             итератор;
    тело цикла;             }
}

Например, фрагмент с суммированием массива реализуется с использованием цикла while следующим образом:

                                i = 0;
for (i=0; i < n; ++i) {  while (i < n) {
    sum += a[i];           ~        sum += a[i];
}                                   ++i;
                                }

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

Операция "запятая" и цикл for

В цикле for

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

в качестве инициализации и итератора можно использовать любые выражения, в частности, операцию присваивания = и операцию увеличения значения переменной на единицу ++. Как быть, если необходимо выполнить несколько действий при инициализации или в итераторе? Можно, конечно, использовать цикл while, но любители цикла for поступают другим образом. Для этого язык Си предоставляет операцию "запятая", которая позволяет объединить несколько выражений в одно. У операции "запятая" два аргумента, которые вычисляются последовательно слева направо. Результатом операции является последнее вычисленное, т.е. правое, значение. Пример:

int x, y, z;
x = 5;
z = (y = x + 10, ++x);  // y = 15, x = 6, z = 6

Здесь при вычислении выражения в скобках сначала вычисляется первое подвыражение y = x+10, в результате которого в y записывается значение 15, значение первого подвыражения также равно 15. Затем вычисляется стоящее после запятой второе подвыражение ++x, в результате чего значение x увеличивается и становится равным 6, значение второго подвыражения также равно 6. Значением операции "запятая" является значение второго подвыражения, т.е. 6. В результате значение 6 присваивается переменной z.
Наличие операции "запятая" отражает эстетскую сторону первоначального варианта языка Cи 70-х годов XX века: в нем почти любая запись имела какой-то смысл. Позже программисты пришли к пониманию того, что надежность программы важнее краткости и изящества, и приняли более строгий ANSI-стандарт языка Си 1989 г., который несколько ограничил свободу творчества в области Си-программ.
Тем не менее, операцию "запятая" по-прежнему можно использовать в заголовке цикла for, когда нужно выполнить несколько действий при инициализации или в итераторе. Например, фрагмент суммирования массива

sum = 0.0;
for (i = 0; i < n; ++i) {
    sum += a[i];
}

можно переписать следующим "эстетским" образом:

for (sum = 0.0, i = 0; i < n; sum += a[i], ++i);

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

Конструкции, которые лучше не использовать

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

Цикл do...while

Цикл do...while имеет вид

do
    действие;
while (условие);

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

do {
    x *= 2;
} while (x < n);

Цикл do...while является циклом с постусловием. Сначала выполняется тело цикла и только после этого проверяется условие продолжения цикла. Если условие истинно, то тело цикла повторяется, и так до бесконечности, пока условие не станет ложным. Таким образом, тело цикла выполняется всегда, даже если условие ложно с самого начала. Это является потенциальным источником ошибок. Лучше всегда использовать цикл с предусловием while (прежде чем прыгнуть, лучше сначала посмотреть, куда прыгаешь!).
Приведем пример ошибочного использования цикла do...while. Пусть переменная n содержит целое положительное число. Надо записать в целочисленную переменную p максимальную степень двойки, не превосходящую n. Ранее этот фрагмент уже был реализован с помощью цикла while (раздел 3.5.5):

int n, p;
. . .
p = 1;
while (2*p <= n) {
    p *= 2;
}

Попытка использовать цикл do...while может привести к ошибке:

 
int n, p;
. . .
p = 1;
do {
    p *= 2;
} while (2*p <= n);

Программа работает неверно при n = 1 (в переменную p записывается двойка вместо единицы), поскольку тело цикла do...while всегда выполняется один раз независимо от истинности условия, которое проверяется лишь после выполнения тела цикла. Такого рода ошибки в "крайних" ситуациях наиболее опасны в программировании: программа правильно работает почти во всех ситуациях, кроме нескольких исключений. Но известно, что большинство катастроф происходит как раз в результате исключительного стечения обстоятельств!

Оператор switch (вычисляемый goto)

Оператор switch имеет следующий вид:

switch (выражение) {
    case значение_1:
        фрагмент_1;
    case значение_2:
        фрагмент_2;
    case значение_3:
        фрагмент_3;
    . . .
    default:        // Необязательный фрагмент
        фрагмент_N;
}

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

  1. сначала вычисляется значение выражения в заголовке switch;
  2. затем осуществляется переход на метку "case L:", где константа L совпадает с вычисленным значением выражения в заголовке;
  3. если такого значения нет среди меток внутри тела switch, то
    • если есть метка "default:", то осуществляется переход на нее;
    • если метка "default:" отсутствует, то ничего не происходит.

Подчеркнем, что после перехода на метку "case L:" текст программы выполняется последовательно. Например, при выполнении фрагмента программы

int n, k;
n = 2;
switch (n) {
    case 1:
        k = 2;
    case 2:
        k = 4;
    case 3:
        k = 8;
}

переменной k будет присвоено значение 8, а не 4. Дело в том, что при переходе на метку "case 2:" будут выполнена сначала строка

k = 4;

и затем строка

 
k = 8;

что делает приведенный фрагмент совершенно бессмысленным (оптимизирующий компилятор вообще исключит строки "k = 2;" и "k = 4;" из кода готовой программы!). Чтобы исправить этот фрагмент, следует использовать оператор

break;

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

int n, k;
n = 2;
switch (n) {
    case 1:
        k = 2;
        break;
    case 2:
        k = 4;
        break;
    case 3:
        k = 8;
        break;
}

В результате выполнения этого фрагмента переменной k будет присвоено значение 4. Если бы значение n равнялось 1, то k было бы присвоено значение 2, если n равнялось бы 3, то 8. Если n не равно ни 1, ни 2, ни 3, то ничего не происходит.
Оператор switch иногда совершенно необосновано называют оператором выбора. На самом деле, для выбора следует использовать конструкцию if...else if..., см. раздел 3.5.3. Например, приведенный фрагмент лучше реализовать следующим образом:

if (n == 1) {
    k = 2;
}
else if (n == 2) {
    k = 4;
}
else if (n == 3) {
    k = 8;
}

Оператор switch по сути своей является оператором перехода goto с вычисляемой меткой. Ему присущи многие недостатки goto, например, проблемы с инициализацией локальных переменных при входе в блок. Кроме того, switch не позволяет записывать условия в виде логических выражений, что ограничивает сферу его применения. Рекомендуется никогда не использовать оператор switch: выбор в стиле if...else if... во всех отношениях лучше!

http://localhost:3232/img/empty.gifПредставление программы в виде функций

Прототипы функций
Перед использованием или реализацией функции необходимо описать ее прототип. Прототип функции сообщает информацию об имени функции, типе возвращаемого значения, количестве и типах ее аргументов. Пример:
int gcd(int x, int y);
Описан прототип функции gcd, возвращающей целое значение, с двумя целыми аргументами. Имена аргументов x и y здесь являются лишь комментариями, не несущими никакой информации для компилятора. Их можно опускать, например, описание

int gcd(int, int);
является вполне допустимым.
Описания прототипов функций обычно выносятся в заголовочные файлы, см. раздел 3.1. Для коротких программ, которые помещаются в одном файле, описания прототипов располагают в начале программы. Рассмотрим пример такой короткой программы.

 

 
На главную | Содержание | < Назад....Вперёд >
С вопросами и предложениями можно обращаться по nicivas@bk.ru. 2013 г.Яндекс.Метрика