целое, то
if (a) // ...
эквивалентно
if (a != 0) // ...
Логические операции
&& || !
наиболее часто используются в условиях. Операции && и || не будут
вычислять второй аргумент, если это ненужно. Например:
if (p && 1count) // ...
вначале проверяет, является ли p не нулем, и только если это так,
то проверяет 1count.
Некоторые простые операторы if могут быть с удобством заменены
выражениями арифметического if. Например:
if (a <= d)
max = b;
else
max = a;
лучше выражается так:
max = (a<=b) ? b : a;
Скобки вокруг условия необязательны, но я считаю, что когда они
используются, программу легче читать.
Некоторые простые операторы switch можно по-другому записать в
виде набора операторов if. Например:
- стр 102 -
switch (val) {
case 1:
f();
break;
case 2;
g();
break;
default:
h();
break;
}
иначе можно было бы записать так:
if (val == 1)
f();
else if (val == 2)
g();
else
h();
Смысл тот же, однако первый вариант (switch) предпочтительнее,
поскольку в этом случае явно выражается сущность действия
(сопоставление значения с рядом констант). Поэтому в нетривиальных
случаях оператор switch читается легче.
Заботьтесь о том, что switch должен как-то завершаться, если
только вы не хотите, чтобы выполнялся следующий case. Например:
switch (val) { // осторожно
case 1:
cout << "case 1\n";
case 2;
cout << "case 2\n";
default:
cout << "default: case не найден\n";
}
при val==1 напечатает
case 1
case 2
default: case не найден
к великому изумлению непосвященного. Самый обычный способ завершить
случай - это break, иногда можно даже использовать goto. Например:
- стр 103 -
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 не найден\n";
return;
}
При обращении к нему с val==2 выдаст
case 2
case 1
Заметьте, что метка case не подходит как метка для употребления в
операторе goto:
goto case 1; // синтаксическая ошибка
3.3.2 Goto
C++ снабжен имеющим дурную репутацию оператором goto.
goto идентификатор;
идентификатор : оператор
В общем, в программировании высокого уровня он имеет очень мало
применений, но он может быть очень полезен, когда C++ программа
генерируется программой, а не пишется непосредственно человеком.
Например, операторы goto можно использовать в синтаксическом
анализаторе, порождаемом генератором синтаксических анализаторов.
Оператор goto может быть также важен в тех редких случаях, когда
важна наилучшая эффективность, например, во внутреннем цикле какой-
нибудь программы, работающей в реальном времени.
Одно из немногих разумных применений состоит в выходе из
вложенного цикла или переключателя (break лишь прекращает
выполнение самого внутреннего охватывающего его цикла или
переключателя). Например:
for (int i = 0; i
3.4 Комментарии и Выравнивание
Продуманное использование комментариев и согласованное
использование отступов может сделать чтение и понимание программы
намного более приятным. Существует несколько различных стилей
согласованного использования отступов. Автор не видит никаких
серьезных оснований предпочесть один другому (хотя как и у
большинства, у меня есть свои предпочтения). Сказанное относится
также и к стилю комментариев.
Неправильное использование комментариев может серьезно повлиять
на удобочитаемость программы, Компилятор не понимает содержание
комментария, поэтому он никаким способом не может убедиться в том,
что комментарий
[1] осмыслен;
[2] описывает программу; и
[3] не устарел.
Непонятные, двусмыленные и просто неправильные комментарии
содержатся в большинстве программ. Плохой комментарий может быть
хуже, чем никакой.
Если что-то можно сформулировать средствами самого языка, следует
это сделать, а не просто отметить в комментарии. Данное замечание
относится к комментариям вроде:
// переменная "v" должна быть инициализирована.
// переменная "v" должна использоваться только функцией "f()".
// вызвать функцию init() перед вызовом
// любой другой функции в этом файле.
// вызовите функцию очиститки "cleanup()" в конце вашей
программы.
// не используйте функцию "wierd()".
// функция "f()" получает два параметра.
При правильном использовании C++ подобные комментарии как правило
становятся ненужными. Чтобы предыдущие комментарии стали излишними,
можно, например, использовать правила компоновки (#4.2) и
видимость, инициализацию и правила очистки для классов (см.
#5.5.2).
Если что-то было ясно сформулировано на языке, второй раз
упоминать это в комментарии не следует. Например:
a = b+c; // a становится b+c
count++; // увеличить счетчик
Такие комментарии хуже чем просто излишни, они увеличивают объем
текса, который надо прочитать, они часто затуманивают структуру
программы, и они могут быть неправильными.
Автор предпочитает:
- стр 105 -
[1] Комментарий для каждого исходного файла, сообщающий, для чего
в целом предназначены находящиеся в нем комментарии, дающий
ссылки на справочники и руководства, общие рекомендации по
использованию и т.д.;
[2] Комментарий для каждой нетривиальной функции, в котором
сформулировано ее назначение, используемый алгоритм (если он
неочевиден) и, быть может, что-то о принимаемых в ней
предположениях относительно среды выполнения;
[3] Небольшое число комментариев в тех местах, где программа
неочевидна и/или непереносима; и
[4] Очень мало что еще.
Например:
// tbl.c: Реализация таблицы имен
/*
Гауссовское исключение с частичным
См. Ralston: "A first course ..." стр. 411.
*/
// swap() предполагает размещение стека AT&T sB20.
/**************************************
Copyright (c) 1984 AT&T, Inc.
All rights reserved
****************************************/
Удачно подобранные и хорошо написанные комментарии - существенная
часть программы. Написание хороших комментариев может быть столь же
сложным, сколь и написание самой программы.
Заметьте также, что если в функции используются исключительно
комментарии //, то любую часть этой функции можно закомментировать
с помощью комментариев /* */, и наоборот.
3.5 Упражнения
1. (*1) Перепишите следующий оператор for в виде эквивалентного
оператора while:
for (i=0; im
*p.m
*a[i]
7. (*2) Напишите функции: strlen(), которая возвращает длину
строки, strcpy(), которая копирует одну строку в другую, и
strcmp(), которая сравнивает две строки. Разберитесь, какие
должны быть типы параметров и типы возвращаемых значений, а
потом сравните их со стандартными версиями, которые описаны в
и в вашем руководстве.
8. (*1) Посмотрите, как ваш компилятор реагирует на ошибки:
a := b+1;
if (a = 3) // ...
if (a&077 == 0) // ...
Придумайте ошибки попроще, и посмотрите, как компилятор на них
реагирует.
9. (*2) Напишите функцию cat(), получающую два строковых
параметра и возвращающую строку, которая является
конкатенацией параметров. Используйте new, чтобы найти память
для результата. Напишите функцию rev(), которая получает
строку и переставляет в ней символы в обратном порядке. То
есть, после вызова rev(p) последний символ p становится
первым.
10. (*2) Что делает следующая программа?
- стр 107 -
void send(register* to, register* from, register count)
// Полезные комментарии несомненно уничтожены.
{
register n=(count+7)/8;
switch (count%8) {
case 0: do { *to++ = *from++;
case 7: do { *to++ = *from++;
case 6: do { *to++ = *from++;
case 5: do { *to++ = *from++;
case 4: do { *to++ = *from++;
case 3: do { *to++ = *from++;
case 2: do { *to++ = *from++;
case 1: do { *to++ = *from++;
while (--n>0);
}
}
Зачем кто-то мог написать нечто похожее?
11. (*2) Напишите функцию atoi(), которая получает строку,
содержащую цифры, и возвращает соответствующее int. Например,
atoi("123") - это 123. Модифицируйте atoi() так, чтобы помимо
обычной десятичной она обрабатывала еще восьмеричную и
шестнадцатиричную записи C++. Модифицируйте atoi() так, чтобы
обрабатывать запись символьной константы. Напишите функцию
itoa(), которая строит представление целого параметра в виде
строки.
12. (*2) Перепишите get_token() (#3.1.2), чтобы она за один раз
читала строку в буфер, а затем составляла лексемы, читая
символы из буфера.
13. (*2) Добавьте в настрольный калькулятор из #3.1 такие
функции, как sqrt(), log() и sin(). Подсказка: предопределите
имена и вызывайте функции с помощью вектора указателей на
функции. Не забывайте проверять параметры в вызове функции.
14. (*3) Дайте пользователю возможность определять функции в
настольном калькуляторе. Подсказка: определяйте функции как
последовательность действий, прямо так, как их набрал
пользователь. Такую последовательность можно хранить или как
символьную строку, или как список лексем. После этого, когда
функция вызывается, читайте и выполняйте эти действия. Если вы
хотите, чтобы пользовательская функция получала параметры, вы
должны придумать форму записи этого.
15. (*1.5) Преобразуйте настольный калькулятор так, чтобы вместо
статических переменных name_string и number_value
использовалась структура символа symbol:
struct symbol {
token_value tok;
union {
double number_value;
char* name_string;
};
};
16. (*2.5) Напишите программу, которая выбрасывает комментарии из
C++ программы. То есть, читает из cin, удаляет // и /* */
комментарии и пишет результат в cout. Не заботьтесь о приятном
- стр 108 -
виде выходного текста (это могло бы быть другим, более сложным
упражнением). Не беспокойтесь о правильности программ.
Остерегайтесь // и /* и */ внутри комментариев, строк и
символьных констант.
17. (*2) Посмотрите какие-нибудь программы, чтобы понять принцип
различных стилей комментирования и выравнивания, которые
используются на практике.
Глава 4
Функции и Файлы
Итерация свойственна человеку,
рекусия божественна.
- Л. Питер Дойч
Все нетривиальные программы собираются из нескольких раздельно
компилируемых единиц (их принято называть просто файлами). В этой
главе описано, как раздельно откомпилированные функции могут
обращаться друг к другу, как такие функции могут совместно
пользоваться данными (разделять данные), и как можно обеспечить