Язык программирования C++. Пятое издание - Стенли Липпман
Шрифт:
Интервал:
Закладка:
Однако возможны ситуации, когда необходимо именно стандартное поведение оператора switch. У каждой метки case может быть только одно значение, однако две или более метки могут совместно использовать единый набор действий. В таких ситуациях достаточно пропустить оператор break и позволить программе пройти несколько меток case.
Например, можно было бы посчитать общее количество гласных так:
unsigned vowelCnt = 0;
// ...
switch (ch) {
// для инкремента vowelCnt подойдет любая буква а, е, i, о или u
case 'a':
case 'e':
case 'i':
case 'o':
case 'u':
++vowelCnt;
break;
}
Здесь расположено несколько меток case подряд без оператора break. Теперь при любой гласной в переменной ch будет выполняться тот же код.
Поскольку язык С++ не требует обязательно располагать метки case в отдельной строке, весь диапазон значений можно указать в одной строке:
switch (ch) {
// альтернативный допустимый синтаксис
case 'a': case 'e': case 'i': case 'o': case 'u':
++vowelCnt;
break;
}
Случаи, когда оператор break пропускают преднамеренно, довольно редки, поэтому их следует обязательно комментировать, объясняя логику действий.
Пропуск оператора break — весьма распространенная ошибкаВесьма распространено заблуждение, что выполняются только те операторы, которые связаны с совпавшей меткой case. Вот пример неправильной реализации подсчета гласных в операторе switch:
// внимание: преднамеренно неправильный код!
switch (ch) {
case 'a' :
++aCnt; // Упс! Необходим оператор break
case 'e':
++eCnt; // Упс! Необходим оператор break
case 'i':
++iCnt; // Упс! Необходим оператор break
case 'o':
++oCnt; // Упс! Необходим оператор break
case 'u':
++uCnt;
}
Чтобы понять происходящее, предположим, что значением переменной ch является 'e'. Выполнение переходит к коду после метки case 'e', где происходит инкремент переменной eCnt. Выполнение продолжается далее через метки case, увеличивая также значения переменных iCnt, oCnt и uCnt.
Несмотря на то что оператор break и не обязателен после последней метки оператора switch, использовать его все же рекомендуется. Ведь если впоследствии оператор switch будет дополнен еще одной меткой case, отсутствие оператора break после прежней последней метки не создаст проблем.
Метка defaultОператоры после метки default выполняются, если ни одна из меток case не соответствует значению выражения оператора switch. Например, в рассматриваемый код можно добавить счетчик негласных букв. Значение этого счетчика по имени otherCnt будет увеличиваться в случае default:
// если ch гласная, увеличить соответствующий счетчик
switch (ch) {
case 'a': case 'e': case 'i': case 'o': case 'u':
++vowelCnt;
break;
default:
++otherCnt;
break;
}
В этой версии, если переменная ch не содержит гласную букву, управление перейдет к метке default и увеличится значение счетчика otherCnt.
Раздел default имеет смысл создавать всегда, даже если в нем не происходит никаких действий. Впоследствии это однозначно укажет читателю кода, что случай default не был забыт, т.е. для остальных случаев никаких действий предпринимать не нужно.
Метка не может быть автономной; она должна предшествовать оператору или другой метке case. Если оператор switch заканчивается разделом default, в котором не осуществляется никаких действий, за меткой default должен следовать пустой оператор или пустой блок.
Определение переменной в операторе switchКак уже упоминалось, выполнение оператора switch способно переходить через метки case. Когда выполнение переходит к некой метке case, весь расположенный выше код оператора switch будет проигнорирован. Факт игнорирования кода поднимает интересный вопрос: что будет, если пропущенный код содержит определение переменной?
Ответ прост: недопустим переход с места, где переменная с инициализатором уже вышла из области видимости к месту, где эта переменная находится в области видимости.
case true:
// этот оператор switch недопустим, поскольку инициализацию
// можно обойти
string file_name; // ошибка: выполнение обходит неявно
// инициализированную переменную
int ival = 0; // ошибка: выполнение обходит неявно
// инициализированную переменную
int jval; // ok: поскольку jval не инициализирована
break;
case false:
// ok: jval находится в области видимости, но она не инициализирована
jval = next_num(); // ok: присвоить значение jval
if (file_name.empty()) // file_name находится в области видимости, но
// она не инициализирована
// ...
Если бы этот код был допустим, то любой переход к случаю false обходил бы инициализацию переменных file_name и ival, но они оставались бы в области видимости и код вполне мог бы использовать их. Однако эти переменные не были бы инициализированы. В результате язык не позволяет перепрыгивать через инициализацию, если инициализированная переменная находится в области видимости в пункте, к которому переходит управление.
Если необходимо определить и инициализировать переменную для некоего случая case, то сделать это следует в блоке, гарантируя таким образом, что переменная выйдет из области видимости перед любой последующей меткой.
case true:
{
// ok: оператор объявления в пределах операторного блока
string file_name = get_file_name();
// ...
}
break;
case false:
if (file_name.empty()) // ошибка: file_name вне области видимости
Упражнения раздела 5.3.2Упражнение 5.9. Напишите программу, использующую серию операторов if для подсчета количества гласных букв в тексте, прочитанном из потока cin.
Упражнение 5.10. Программа подсчета гласных имеет одну проблему: она не учитывает заглавные буквы как гласные. Напишите программу, которая подсчитывает гласные буквы как в верхнем, так и в нижнем регистре. То есть значение счетчика aCnt должно увеличиваться при встрече как символа 'a', так и символа 'A' (аналогично для остальных гласных букв).
Упражнение 5.11. Измените рассматриваемую программу так, чтобы она подсчитывала также количество пробелов, символов табуляции и новой строки.
Упражнение 5.12. Измените рассматриваемую программу так, чтобы она подсчитывала количество встреченных двухсимвольных последовательностей: ff, fl и fi.
Упражнение 5.13. Каждая из приведенных ниже программ содержит распространенную ошибку. Выявите и исправьте каждую из них.
Код для упражнения 5.13
(a) unsigned aCnt = 0, eCnt = 0, iouCnt = 0;
char ch = next_text();
switch (ch) {
case 'a': aCnt++;
case 'e': eCnt++;
default: iouCnt++;
}
(b) unsigned index = some_value();
switch (index) {
case 1:
int ix = get_value();
ivec[ix] = index;
break;
default:
ix = ivec.size()-1;
ivec[ix] = index;
(c) unsigned evenCnt = 0, oddCnt = 0;
int digit = get_num() % 10;
switch (digit) {
case 1, 3, 5, 7, 9:
oddcnt++;
break;
case 2, 4, 6, 8, 10:
evencnt++;
break;
}
(d) unsigned ival=512, jval=1024, kval=4096;
unsigned bufsize;
unsigned swt = get_bufCnt();
switch(swt) {
case ival:
bufsize = ival * sizeof (int);
break;
case jval:
bufsize = jval * sizeof(int);
break;