Отправляет email-рассылки с помощью сервиса Sendsay
  Все выпуски  

Точка, точка, запятая...



Точка, точка, запятая…
2012-04-15 02:32 Алексей Захаренков

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

Точкой с запятой в С++ должны завершаться многие команды. Среди них:

  1. объявления переменных:
    int a, b, arr[10], *p;
  2. определение типа структуры/класса/объединения/перечисления. Например:
    struct Point { int x; int y; };

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

    struct Point { int x; int y; } p1, p2, points[15], *pPointer;

    Здесь, кроме того что вводится новый тип данных Point, ещё определяются четыре переменные: две переменные типа Point, один массив элементов типа Point и один указатель на Point. Сравните это объявление с примером из пункта 1. Теперь вы должны увидеть, что пункт 2 – это усложнённый вариант объявления переменных. Т.е., на самом деле, последняя точка с запятой в определении типа структуры (или класса) завершает не определение типа структуры, а список переменных этого типа, который, как частный случай, может оказаться пустым. Правда, на практике такой «частный случай» чаще всего и реализуется, поэтому многие программисты давно забыли, откуда происходит необходимость ставить точку с запятой в конце определения класса.

  3. Объявление элементов данных структуры/класса. Это две точки с запятой в определении структуры Point из пункта 2, стоящие после определения полей x и y этой структуры.
  4. Директивы using:
    using namespace std;
    using std::cin;
  5. Определения typedef:
    typedef unsigned short USHORT;
  6. Простые операторы:
    x = y + 5;
    f(x);
  7. Прототип автономной функции (т.е. заголовок функции без тела):
    int sum(int, int);
  8. Объявление методов класса
    class A {
        bool isEven(int a);
    };
  9. После определения методов класса непосредственно внутри определения класса (inline-определение) точка с запятой может ставиться, а может не ставиться. Это значит, что, определяя тело метода isEven( ), а не только давая его прототип, внутри определения класса A, в конце не возбраняется поставить точку с запятой, хотя её можно и не ставить:
    class A {
        bool isEven(int a) { return !(a%2); };
    };

Из-за особой роли точки с запятой в С++ как символа-ограничителя многие новички начинают машинально ставить её где нужно и где не нужно. Поэтому полезно будет вспомнить случаи, когда точка с запятой является лишней и приводит к неприятностям разной степени тяжести.

После блока операторов, например после такого цикла:

while (x < 10)
{
    cout <<; x << ‘\t’;
    ++x;
};

Здесь последняя точка с запятой никаким образом не относится к циклу. Это совершенно отдельный оператор, точнее сказать – пустой оператор. Более ясное представление о её роли дала бы такая запись этого кусочка кода:

while (x < 10)
{
    cout << x << ‘\t’;
    ++x;
}
;

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

while (x < 10)
{
    cout << x << ‘\t’;
    ++x;
};;;;;;;;;;

просто заставив оптимизатор выгребать за вами лишние пустые операторы.

Но лишние точки с запятой не после любого блока операторов могут оказаться некритичными и никак не испортить программу. Рассмотрим такой пример, в котором автор переборщил с символами-ограничителями:

int n, m;
int x;
cin >> x;
 
if (x < 3)
{
    n = 5;
    m = 10;
};
else
{
    n = 10;
    m = 5;
};

Точка с запятой после блока в ветке else, как мы видели чуть выше, не приведёт ни к чему плохому, однако она же после блока в ветке if нарушит всю конструкцию оператора if-else, так как теперь после if-а стоят два оператора – составной оператор (блок операторов) и пустой оператор. Второй из них прерывает последовательность if – else if – … – else, так что else оказывается оторванным от условного оператора. Всё это приведёт к ошибке компиляции – и слава богу. Это синтаксическая ошибка. Гораздо труднее отловить логические ошибки – неверный текст программы, который, тем не менее, валиден с точки зрения компилятора. Рассмотрим такой цикл:

int i, a[10];
for (i = 0; i < 10; ++i);
    a[i] = i*i;

Очень коварная ошибка, т.к. глазом заметить её очень трудно, компилятор же её пропускает, а программист долго не может понять, почему в массиве a[] оказывается мусор. Если отформатировать текст этого куска программы ближе к структуре выполняемых действий, то мы получим

int i, a[10];
for (i = 0; i < 10; ++i)
    ;
a[i] = i*i;

т.е. тело цикла for составляет пустой оператор, а присваивание элементу a[i] делается один раз – в самом конце, причём когда индекс i уже вышел за пределы массива.

В связи с этим примером уместно обратить внимание на принцип минимальной видимости. Банальное объявление счётчика i в заголовке цикла for сделало бы код с лишней точкой с запятой некомпилируемым, т.к. переменная i тогда была бы видна только в пределах цикла

for (int i = 0; i < 10; ++i)
    ;

но не в операторе

a[i] = i*i;

(конечно, если переменная с именем i не объявлена где-то выше в пределах текущей области видимости).

Подобная ошибка может случайно возникнуть при написании оператора if:

if (a%2 == 0);
   a++;

Оператор a++; будет выполняться в любом случае, т.к. он не подпадает под условие if. Подобные ошибки возникают даже у программистов, которые хорошо владеют синтаксисом, из-за того, что пальцы иногда сами машинально набирают точку с запятой в конце строки. Товарищи, слепая печать вам с помощь! Если вы сразу видите, что печатаете, то вряд ли пропустите такую опечатку, что сэкономит вам часы отладки и километры нервов. Многие компиляторы генерируют предупреждение (warning), если видят одиночную точку с запятой в качестве тела цикла или условия, поэтому советую также устанавливать повыше уровень предупреждений и внимательно их прочитывать.



Точка, точка, запятая…
2012-04-15 02:32 Алексей Захаренков

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

Точкой с запятой в С++ должны завершаться многие команды. Среди них:

  1. объявления переменных:
    int a, b, arr[10], *p;
  2. определение типа структуры/класса/объединения/перечисления. Например:
    struct Point { int x; int y; };

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

    struct Point { int x; int y; } p1, p2, points[15], *pPointer;

    Здесь, кроме того что вводится новый тип данных Point, ещё определяются четыре переменные: две переменные типа Point, один массив элементов типа Point и один указатель на Point. Сравните это объявление с примером из пункта 1. Теперь вы должны увидеть, что пункт 2 – это усложнённый вариант объявления переменных. Т.е., на самом деле, последняя точка с запятой в определении типа структуры (или класса) завершает не определение типа структуры, а список переменных этого типа, который, как частный случай, может оказаться пустым. Правда, на практике такой «частный случай» чаще всего и реализуется, поэтому многие программисты давно забыли, откуда происходит необходимость ставить точку с запятой в конце определения класса.

  3. Объявление элементов данных структуры/класса. Это две точки с запятой в определении структуры Point из пункта 2, стоящие после определения полей x и y этой структуры.
  4. Директивы using:
    using namespace std;
    using std::cin;
  5. Определения typedef:
    typedef unsigned short USHORT;
  6. Простые операторы:
    x = y + 5;
    f(x);
  7. Прототип автономной функции (т.е. заголовок функции без тела):
    int sum(int, int);
  8. Объявление методов класса
    class A {
        bool isEven(int a);
    };
  9. После определения методов класса непосредственно внутри определения класса (inline-определение) точка с запятой может ставиться, а может не ставиться. Это значит, что, определяя тело метода isEven( ), а не только давая его прототип, внутри определения класса A, в конце не возбраняется поставить точку с запятой, хотя её можно и не ставить:
    class A {
        bool isEven(int a) { return !(a%2); };
    };

Из-за особой роли точки с запятой в С++ как символа-ограничителя многие новички начинают машинально ставить её где нужно и где не нужно. Поэтому полезно будет вспомнить случаи, когда точка с запятой является лишней и приводит к неприятностям разной степени тяжести.

После блока операторов, например после такого цикла:

while (x < 10)
{
    cout <<; x << ‘\t’;
    ++x;
};

Здесь последняя точка с запятой никаким образом не относится к циклу. Это совершенно отдельный оператор, точнее сказать – пустой оператор. Более ясное представление о её роли дала бы такая запись этого кусочка кода:

while (x < 10)
{
    cout << x << ‘\t’;
    ++x;
}
;

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

while (x < 10)
{
    cout << x << ‘\t’;
    ++x;
};;;;;;;;;;

просто заставив оптимизатор выгребать за вами лишние пустые операторы.

Но лишние точки с запятой не после любого блока операторов могут оказаться некритичными и никак не испортить программу. Рассмотрим такой пример, в котором автор переборщил с символами-ограничителями:

int n, m;
int x;
cin >> x;
 
if (x < 3)
{
    n = 5;
    m = 10;
};
else
{
    n = 10;
    m = 5;
};

Точка с запятой после блока в ветке else, как мы видели чуть выше, не приведёт ни к чему плохому, однако она же после блока в ветке if нарушит всю конструкцию оператора if-else, так как теперь после if-а стоят два оператора – составной оператор (блок операторов) и пустой оператор. Второй из них прерывает последовательность if – else if – … – else, так что else оказывается оторванным от условного оператора. Всё это приведёт к ошибке компиляции – и слава богу. Это синтаксическая ошибка. Гораздо труднее отловить логические ошибки – неверный текст программы, который, тем не менее, валиден с точки зрения компилятора. Рассмотрим такой цикл:

int i, a[10];
for (i = 0; i < 10; ++i);
    a[i] = i*i;

Очень коварная ошибка, т.к. глазом заметить её очень трудно, компилятор же её пропускает, а программист долго не может понять, почему в массиве a[] оказывается мусор. Если отформатировать текст этого куска программы ближе к структуре выполняемых действий, то мы получим

int i, a[10];
for (i = 0; i < 10; ++i)
    ;
a[i] = i*i;

т.е. тело цикла for составляет пустой оператор, а присваивание элементу a[i] делается один раз – в самом конце, причём когда индекс i уже вышел за пределы массива.

В связи с этим примером уместно обратить внимание на принцип минимальной видимости. Банальное объявление счётчика i в заголовке цикла for сделало бы код с лишней точкой с запятой некомпилируемым, т.к. переменная i тогда была бы видна только в пределах цикла

for (int i = 0; i < 10; ++i)
    ;

но не в операторе

a[i] = i*i;

(конечно, если переменная с именем i не объявлена где-то выше в пределах текущей области видимости).

Подобная ошибка может случайно возникнуть при написании оператора if:

if (a%2 == 0);
   a++;

Оператор a++; будет выполняться в любом случае, т.к. он не подпадает под условие if. Подобные ошибки возникают даже у программистов, которые хорошо владеют синтаксисом, из-за того, что пальцы иногда сами машинально набирают точку с запятой в конце строки. Товарищи, слепая печать вам с помощь! Если вы сразу видите, что печатаете, то вряд ли пропустите такую опечатку, что сэкономит вам часы отладки и километры нервов. Многие компиляторы генерируют предупреждение (warning), если видят одиночную точку с запятой в качестве тела цикла или условия, поэтому советую также устанавливать повыше уровень предупреждений и внимательно их прочитывать.



В избранное