Бьерн Страуструп (947334), страница 20
Текст из файла (страница 20)
начальный-оператор-for:
описание
выражение opt ;
----------------------------------------------------------------------
Обратите внимание, что описание является оператором, но нет операторов
присваивания или вызова функции (они относятся к выражениям).
3.3.1 Выбирающие операторы
Значение можно проверить с помощью операторов if или switch:
if ( выражение ) оператор
if ( выражение ) оператор else оператор
switch ( выражение ) оператор
В языке С++ среди основных типов нет отдельного булевского (тип
со значениями истина, ложь). Все операции отношений:
== != < > <= >=
дают в результате целое 1, если отношение выполняется, и 0 в противном
случае. Обычно определяют константы TRUE как 1 и FALSE как 0.
В операторе if, если выражение имеет ненулевое значение,
выполняется первый оператор, а иначе выполняется второй (если
он указан). Таким образом, в качестве условия допускается любое выражение
типа целое или указатель. Пусть a целое, тогда
if (a) // ...
эквивалентно
if (a != 0) ...
Логические операции
&& || !
обычно используются в условиях. В операциях && и || второй операнд
не вычисляется, если результат определяется значением первого
операнда. Например, в выражении
if (p && l<p->count) // ...
сначала проверяется значение p, и только если оно не равно нулю, то
проверяется отношение l<p->count.
Некоторые простые операторы if удобно заменять выражениями
условия. Например, вместо оператора
if (a <= b)
max = b;
else
max = a;
лучше использовать выражение
max = (a<=b) ? b : a;
Условие в выражении условия не обязательно окружать скобками, но
если их использовать, то выражение становится понятнее.
Простой переключатель (switch) можно записать с помощью
серии операторов if. Например,
switch (val) {
case 1:
f();
break;
case 2:
g();
break;
default:
h();
break;
}
можно эквивалентно задать так:
if (val == 1)
f();
else if (val == 2)
g();
else
h();
Смысл обеих конструкций совпадает, но все же первая предпочтительнее,
поскольку в ней нагляднее показана суть операции: проверка на
совпадение значения val со значением из множества констант. Поэтому в
нетривиальных случаях запись, использующая переключатель, понятнее.
Нужно позаботиться о каком-то завершении оператора, указанного
в варианте переключателя, если только вы не хотите, чтобы стали
выполняться операторы из следующего варианта. Например,
переключатель
switch (val) { // возможна ошибка
case 1:
cout << "case 1\n";
case 2:
cout << "case 2\n";
default:
cout << "default: case not found\n";
}
при val==1 напечатает к большому удивлению непосвященных:
case 1
case 2
default: case not found
Имеет смысл отметить в комментариях те редкие случаи, когда стандартный
переход на следующий вариант оставлен намеренно. Тогда этот переход
во всех остальных случаях можно смело считать ошибкой. Для
завершения оператора в варианте чаще всего используется break, но
иногда используются return и даже goto. Приведем пример:
switch (val) { // возможна ошибка
case 0:
cout << "case 0\n";
case1:
case 1:
cout << "case 1\n";
return;
case 2:
cout << "case 2\n";
goto case1;
default:
cout << "default: case not found\n";
return;
}
Здесь при значении val равном 2 мы получим:
case 2
case 1
Отметим, что метку варианта нельзя использовать в операторе goto:
goto case 2; // синтаксическая ошибка
3.3.2 Оператор goto
Презираемый оператор goto все-таки есть в С++:
goto идентификатор;
идентификатор: оператор
Вообще говоря, он мало используется в языках высокого уровня, но
может быть очень полезен, если текст на С++ создается не человеком,
а автоматически, т.е. с помощью программы. Например,
операторы goto используются при создании анализатора по заданной
грамматике языка с помощью программных средств.
Кроме того, операторы goto могут пригодиться в тех случаях,
когда на первый план выходит скорость работы программы. Один из
них - когда в реальном времени происходят какие-то вычисления во
внутреннем цикле программы.
Есть немногие ситуации и в обычных программах, когда применение
goto оправдано. Одна из них - выход из вложенного цикла или
переключателя. Дело в том, что оператор break во вложенных циклах
или переключателях позволяет перейти только на один уровень выше.
Приведем пример:
void f()
{
int i;
int j;
for ( i = 0; i < n; i++)
for (j = 0; j<m; j++)
if (nm[i][j] == a) goto found;
// здесь a не найдено
// ...
found:
// nm[i][j] == a
}
Есть еще оператор continue, который позволяет перейти на конец
цикла. Что это значит, объяснено в $$3.1.5.
3.4 Комментарии и расположение текста
Программу гораздо легче читать, и она становится намного понятнее, если
разумно использовать комментарии и систематически выделять текст
программы пробелами. Есть несколько способов расположения текста
программы, но нет причин считать, что один из них - наилучший. Хотя
у каждого свой вкус. То же можно сказать и о комментариях.
Однако можно заполнить программу такими комментариями, что читать
и понимать ее будет только труднее. Транслятор не в силах понять
комментарий, поэтому он не может убедиться в том, что комментарий:
[1] осмысленный,
[2] действительно описывает программу,
[3] не устарел.
Во многих программах попадаются непостижимые, двусмысленные и просто
неверные комментарии. Лучше вообще обходиться без них, чем давать
такие комментарии.
Если некий факт можно прямо выразить в языке, то так и следует
делать, и не надо считать, что достаточно упомянуть его в комментарии.
Последнее замечание относится к комментариям, подобным приведенным
ниже:
// переменную "v" необходимо инициализировать.
// переменная "v" может использоваться только в функции "f()".
// до вызова любой функции из этого файла
// необходимо вызвать функцию "init()".
// в конце своей программы вызовите функцию "cleanup()".
// не используйте функцию "weird()".
// функция "f()" имеет два параметра.
При правильном программировании на С++ такие комментарии обычно
оказываются излишними. Чтобы именно эти комментарии стали ненужными,
можно воспользоваться правилами связывания ($$4.2) и областей
видимости, а также правилами инициализации и уничтожения объектов
класса ($$5.5).
Если некоторое утверждение выражается самой программой, не нужно
повторять его в комментарии. Например:
a = b + c; // a принимает значение b+c
count++; // увеличим счетчик count
Такие комментарии хуже, чем избыточные. Они раздувают объем текста,
затуманивают программу и могут быть даже ложными. В то же время
комментарии именно такого рода используют для примеров в учебниках
по языкам программирования, подобных этой книге. Это одна из
многих причин, по которой учебная программа отличается от настоящей.
Можно рекомендовать такой стиль введения комментариев в
программу:
[1] начинать с комментария каждый файл программы: указать в
общих чертах, что в ней определяется, дать ссылки на
справочные руководства, общие идеи по сопровождению
программы и т.д.;
[2] снабжать комментарием каждое определение класса или шаблона
типа;
[3] комментировать каждую нетривиальную функцию, указав: ее
назначение, используемый алгоритм (если только он неочевиден)
и, возможно, предположения об окружении, в котором работает
функция;
[4] комментировать определение каждой глобальной переменной;
[5] давать некоторое число комментариев в тех местах, где
алгоритм неочевиден или непереносим;
[6] больше практически ничего.
Приведем пример:
// tbl.c: Реализация таблицы имен.
/*
Использован метод Гаусса
см. Ральстон "Начальный курс по ..." стр. 411.
*/
// в swap() предполагается, что стек AT&T начинается с 3B20.
/************************************
Авторские права (c) 1991 AT&T, Inc
Все права сохранены
**************************************/
Правильно подобранные и хорошо составленные комментарии играют в
программе важную роль. Написать хорошие комментарии не менее
трудно, чем саму программу, и это - искусство, в котором стоит
совершенствоваться.
Заметим, что если в функции используются только комментарии
вида //, то любую ее часть можно сделать комментарием с помощью
/* */, и наоборот.
3.5 Упражнения
1. (*1) Следующий цикл for перепишите с помощью оператора while:
for (i=0; i<max_length; i++)
if (input_line[i] == '?') quest_count++;
Запишите цикл, используя в качестве его управляющей переменной
указатель так, чтобы условие имело вид *p=='?'.
2. (*1) Укажите порядок вычисления следующих выражений, задав полную
скобочную структуру:
a = b + c * d << 2 & 8
a & 077 != 3
a == b || a == c && c < 5
c = x != 0
0 <= i < 7
f(1,2) + 3
a = - 1 + + b -- - 5
a = b == c ++
a = b = c = 0
a[4][2] *= * b ? c : * d * 2
a-b, c=d
3. (*2) Укажите 5 различных конструкций на С++, значение которых
неопределено.
4. (*2) Приведите 10 разных примеров непереносимых конструкций
на С++.
5. (*1) Что произойдет при делении на нуль в вашей программе на С++?
Что будет в случае переполнения или потери значимости?
6. (*1) Укажите порядок вычисления следующих выражений, задав их
полную скобочную структуру:
*p++
*--p
++a--
(int*)p->m
*p.m
*a[i]
7. (*2) Напишите такие функции: strlen() - подсчет длины строки,
strcpy() - копирование строк и strcmp() - сравнение строк. Какими
должны быть типы параметров и результатов функций? Сравните их
со стандартными версиями, имеющимися в <string.h> и в вашем
руководстве.
8. (*1) Выясните, как ваш транслятор отреагирует на такие ошибки:
void f(int a, int b)
{
if (a = 3) // ...
if (a&077 == 0) // ...
a := b+1;
}
Посмотрите, какова будет реакция на более простые ошибки.
9. (*2) Напишите функцию cat(), которая получает два параметра-строки
и возвращает строку, являющуюся их конкатенацией. Для
результирующей строки используйте память, отведенную с помощью
new. Напишите функцию rev() для перевертывания строки, переданной
ей в качестве параметра. Это означает, что после вызова rev(p)
последний символ p станет первым и т.д.
10. (*2) Что делает следующая функция?
void send(register* to, register* from, register count)
// Псевдоустройство. Все комментарии сознательно удалены
{
register n=(count+7)/8;
switch (count%8) {
case 0: do { *to++ = *from++;
case 7: *to++ = *from++;
case 6: *to++ = *from++;
case 5: *to++ = *from++;
case 4: *to++ = *from++;
case 3: *to++ = *from++;
case 2: *to++ = *from++;
case 1: *to++ = *from++;
} while (--n>0);
}
}
Каков может быть смысл этой функции?
11. (*2) Напишите функцию atoi(), которая имеет параметр - строку цифр
и возвращает соответствующее ей целое. Например, atoi("123")
равно 123. Измените функцию atoi() так, чтобы она могла
переводить в число последовательность цифр не только в десятичной,
но и в восьмеричной и шестнадцатеричной записи, принятой в С++.
Добавьте возможность перевода символьных констант С++. Напишите
функцию itoa() для перевода целого значения в строковое
представление.
12. (*2) Перепишите функцию get_token() ($$3.12) так, чтобы она читала
целую строку в буфер, а затем выдавала лексемы, читая по