Опе­ра­ция за­пя­тая в C++

Как ни стран­но, про­грам­ми­сты ред­ко уде­ля­ют вни­ма­ние изу­че­нию и ис­поль­зо­ва­нию опе­ра­ции за­пя­тая (comma operator) в язы­ке C++. Да­вай­те посмот­рим, что это та­кое.

Пре­жде все­го, сле­ду­ет знать, что не вся­кая за­пя­тая в тек­сте ва­шей про­грам­мы яв­ля­етcя опе­ра­ци­ей. Пе­ре­чис­лим за­пя­тые, ко­то­рые яв­ля­ют­ся не опе­ра­ци­я­ми, а раз­де­ли­те­ля­ми:

  • За­пя­тые, раз­де­ляю­щие ар­гу­мен­ты мак­ро­под­ста­но­вок. При­мер:
    #define MIN(a, b) (((a) < (b)) ? (a) : (b))
  • За­пя­тые, раз­де­ляю­щие ар­гу­мен­ты шаб­ло­нов. При­мер:
    template<class A, class B> class C;
  • За­пя­тые, раз­де­ляю­щие ар­гу­мен­ты функ­ции (при её опре­де­ле­нии, опи­са­нии, и вы­зо­ве);
  • За­пя­тые, раз­де­ляю­щие пе­ре­мен­ные при опи­са­нии не­сколь­ких пе­ре­мен­ных:
    int a, *b, c;
  • За­пя­тые, раз­де­ляю­щие вы­зо­вы кон­ст­рук­то­ров внут­рен­них объ­ек­тов. При­мер:
    class C{int x; float y; public: C(void): x(9), y(2.5) { ; } };
  • За­пя­тые, раз­де­ляю­щие эле­мен­ты ини­циа­ли­за­то­ров мас­си­вов и ст­рук­тур:
    int a[] = {1, 2, 3};

Осталь­ные за­пя­тые яв­ля­ют­ся опе­ра­ци­я­ми. Опе­ра­ция за­пя­тая име­ет са­мый низ­кий при­о­ри­тет сре­ди всех опе­ра­ций язы­ка C++. У этой опе­ра­ции есть 2 опе­ран­да (ле­вый и пра­вый). Вна­ча­ле вы­чис­ля­ет­ся ле­вый опе­ранд, за­тем пра­вый, а в ка­че­стве ре­зуль­та­та воз­вра­ща­ет­ся пра­вый опе­ранд. Три­ви­аль­ный при­мер:

int a = (1 + 2, 3 + 4); //В результате a == 3+4 == 7. Заключёно в
                        //  скобки, так как иначе запятая была бы
                        //  воспринята, как разделитель переменных

Ес­ли пра­вый опе­ранд опе­ра­ции за­пя­тая яв­ля­ет­ся ле­вос­то­рон­ним вы­ра­же­ни­ем (име­ет ад­рес и не яв­ля­ет­ся кон­стан­той), то ре­зуль­та­том то­же яв­ля­ет­ся ле­вос­то­рон­нее вы­ра­же­ние. В этом слу­чае ре­зуль­та­ту мож­но при­сваи­вать зна­че­ние или вы­чис­лять ад­рес:

int a = 1, b = 2;
(a, b) = 3; //В результате a==1, b==3

По­боч­ные эф­фек­ты

Пусть вы­ра­же­ние «по­боч­ный эф­фект» вас не пу­га­ет, — это дей­ствие, со­вер­шен­но осо­знан­но за­про­грам­ми­ро­ван­ное раз­ра­бот­чи­ком про­грам­мы. «По­боч­ный» — не обя­за­тель­но «пло­хой»; сло­во при­об­ре­ло от­ри­ца­тель­ный от­те­нок из-за ле­карств, ко­то­рые обыч­но име­ют один те­ра­пев­ти­че­ский эф­фект и мно­го по­боч­ных.

За­чем же вы­чис­лять зна­че­ния обо­их опе­ран­дов, ес­ли зна­че­ние ле­во­го всё рав­но не ис­поль­зу­ет­ся? Де­ло в том, что в про­цес­се вы­чис­ле­ния опе­ран­да мо­гут быть про­из­ве­де­ны до­пол­ни­тель­ные дей­ствия, на­зы­вае­мые по­боч­ны­ми эф­фек­та­ми. На­при­мер, мо­гут быть мо­дифи­ци­ро­ва­ны ка­кие-ли­бо пе­ре­мен­ные в про­грам­ме, или вы­пол­не­ны не­ко­то­рые внеш­ние дей­ствия (вы­вод на экран, за­пись на диск).

Опе­ра­ция за­пя­тая пред­на­зна­че­на для то­го, что­бы впи­хи­вать дей­ствия с по­боч­ны­ми эф­фек­та­ми в те ме­ста про­грам­мы, где ком­пи­ля­тор ожи­да­ет один опе­ра­тор. Как вы уви­ди­те да­лее, от этой воз­мож­но­сти язы­ка про­грам­ми­ро­ва­ния нет ни­ка­кой поль­зы — по­нят­ность про­грам­мы сни­жа­ет­ся, а до­пол­ни­тель­ные дей­ствия мож­но вы­пол­нить и без за­пя­тых.

При­тя­ну­тые за уши при­ме­ры ис­поль­зо­ва­ния

Рас­смот­рим не­сколь­ко при­ме­ров. Пусть у нас опи­са­ны 4 функ­ции и 3 пе­ре­мен­ных:

//Определяем функции нахождения минимума для разного числа аргументов:
int min(int a) { return a; }
//Запятая разделяет аргументы функции. Это не операция:
int min(int a, int b) { return a < b ? a : b; }
int min(int a, int b, int c) { return min(min(a, b), c); }
//Вывод аргумента на экран (побочный эффект) и возврат значения аргумента:
int out(int x) { cout << x; return x; }

int a, b, c; //Это не операция запятая. Это перечисление переменных

По­иг­ра­ем­ся с опе­ра­ци­ей за­пя­тая. Пре­жде все­го, про­ве­рим её при­о­ри­тет:

a = (1, 2, 3); //В итоге a == 3, так как запятая возвращает
               //  правый аргумент

b = 1, 2, 3; //Операция запятая имеет самый низкий приоритет,
             //  поэтому строка эквивалентна (b = 1), 2, 3;
             //  В итоге b == 1, а числа 2 и 3 не используются.

Об­ра­ти­те вни­ма­ние на то, как скоб­ки из­ме­ни­ли смысл вы­ра­же­ния. В сле­дую­щем при­ме­ре из­ме­не­ние смыс­ла ещё бо­лее не­ожи­дан­но, так как ка­жет­ся, что ((вы­ра­же­ние)) и (вы­ра­же­ние) — это од­но и то же:

a = min(1, 2, 3); //Запятые разделяют аргументы функции. В итоге a == 1

b = min((1, 2, 3)); //Операции запятая. В итоге b = min(3) == 3.

Частая ошиб­ка «пас­ка­ли­стов» — на­пи­са­ние че­рез за­пя­тую ин­дек­сов мно­го­мер­но­го мас­си­ва. До­пу­стим, у нас есть два дву­мер­ных мас­си­ва, каж­дый из ко­то­рый сде­лан, как мас­сив ука­за­те­лей на од­но­мер­ные мас­си­вы:

int const width = 10, height = 10;
int **a = new int*[height];
int **b = new int*[height];
for(int y=0; y<height; ++y)
{
    a[y] = new int[width];
    b[y] = new int[width];
    for(int x=0; x<width; ++x)
    {
        a[y][x] = 1; //Массив a заполнен единицами
        b[y][x] = 0; //Массив b заполнен нулями
    }
}

А те­перь мы хо­тим ско­пи­ро­вать мас­сив a в b по­эле­мент­но, но слу­чай­но на­пи­са­ли ин­дек­сы мас­си­ва че­рез за­пя­тую:

for(int y=0; y<height; ++y)
{
    for(int x=0; x<width; ++x)
        b[y,x] = a[y,x]; //Какой ужас!
}

Ком­пи­ля­тор в этом слу­чае не вы­даст ни оши­бок, ни пре­ду­пре­жде­ний. Ин­те­рес­но то, что при пр­осмот­ре со­дер­жи­мо­го мас­си­ва b он бу­дет со­сто­ять из еди­ниц, как мы и ожи­да­ем. Вот толь­ко при мо­дифи­ка­ции мас­си­ва b «сам со­бой» те­перь бу­дет мо­дифи­ци­ро­вать­ся и мас­сив a, что мо­жет при­ве­сти к стран­ным глю­кам в дру­гих ме­стах про­грам­мы:

b[0][0] = 3;
cout << a[0][0] <<endl; //Выведет 3

Для тех, кто не по­нял, от­че­го так про­ис­хо­дит, по­яс­ню. В вы­ра­же­нии b[y,x] = a[y,x]; у нас на­пи­са­ны опе­ра­ции за­пя­тая, ко­то­рые «иг­но­ри­ру­ют» ле­вый ар­гу­мент. По­это­му вы­ра­же­ние эк­ви­ва­лент­но вы­ра­же­нию b[x] = a[x];. То есть ука­за­те­ли на стро́ки мас­си­ва b мы за­ме­ня­ем ука­за­те­ля­ми на стро́ки мас­си­ва a; по­сле это­го стро­ка­ми обо­их мас­си­вов яв­ля­ют­ся од­ни и те же бу­фе­ры па­мя­ти. Не­за­мет­но­сти ошиб­ки по­спо­соб­ство­ва­ло так­же то, что вы­со­та мас­си­ва сов­па­да­ет с ши­ри­ной (в вы­ра­же­нии b[x] = a[x] мы ин­дек­си­ру­ем мас­сив, имею­щий раз­мер height, ин­дек­сом, ме­няю­щим­ся от 0 до width-1).

За­пя­тая как точ­ка сле­до­ва­ния

Важ­но, что опе­ра­ция за­пя­тая опре­де­ля­ет в про­грам­ме точ­ку сле­до­ва­ния. Это озна­ча­ет, что сна­ча­ла пол­но­стью вы­чис­ля­ет­ся вы­ра­же­ние сле­ва от за­пя­той, а за­тем — вы­ра­же­ние спра­ва. Для срав­не­ния, опе­ра­ция сло­же­ния точ­ку сле­до­ва­ния не опре­де­ля­ет, по­это­му:

a = out(1), out(2), out(3); //В итоге a == 1, а на экран выведется 123, так как
                            //  аргументы операции запятая вычисляются слева-
                            //  направо (значит, в этом же порядке происходят
                            //  вызовы функций)

a = ( out(1), out(2), out(3) ); //В результате a == 3, на экране по прежнему 123

b = out(1) + out(2) + out(3); //На экране выведутся цифры 123 в неопределённом
                              //  порядке. (123 или 321 или ещё как-нибудь)

c = min( out(1), out(2), out(3) ); //Аргументы функции. Порядок не определён

В мо­ём слу­чае (Visual C++ 2010, Release) про­грам­ма вы­ве­ла 123123123321 при вы­пол­не­нии преды­ду­ще­го ко­да.

Ес­ли вам ин­те­рес­но, то в про­грам­мах на C++ есть ещё не­сколь­ко мест, в ко­то­рых опре­де­ле­ны точ­ки сле­до­ва­ния: это ло­ги­че­ские опе­ра­ции &&, || и ?:, раз­де­ли­тель опе­ра­то­ров ;, точ­ки вхо­да и вы­хо­да из функ­ций.

Кро­ме то­го, точ­ки сле­до­ва­ния опре­де­ле­ны на ме­сте за­пя­тых, раз­де­ляю­щих пе­ре­мен­ные при их опи­са­нии. По­это­му мо­же­те сме­ло пи­сать int x = 1, y = x + 1; не бо­ясь, что y = x + 1 вы­чис­лит­ся до то­го, как ик­су при­сво­ит­ся еди­ни­ца. Будь­те вни­ма­тель­ны, не спу­тай­те эти за­пя­тые с те­ми, что раз­де­ля­ют ар­гу­мен­ты функ­ции, — там то­чек сле­до­ва­ния нет.

За­пя­тая в опе­ра­то­рах цик­лов

Те­перь рас­смот­рим воз­мож­но­сти при­ме­не­ния за­пя­той в опе­ра­то­ре цик­ла for. Вна­ча­ле при­мер, где за­пя­тая раз­де­ля­ет объ­яв­ле­ния пе­ре­мен­ных (не яв­ля­ет­ся опе­ра­ци­ей):

for(int x=0, y=3; x<y; ++x) out(x); //Выведет 012

Ес­ли в пер­вом вы­ра­же­нии цик­ла for опи­сы­ва­ют­ся пе­ре­мен­ные (как в при­ве­дён­ном при­ме­ре), то они вы­нуж­де­ны иметь оди­на­ко­вый тип. Кро­ме вы­бо­ра ини­циа­ли­зи­рую­щих зна­че­ний пе­ре­мен­ных ни­ка­кой сво­бо­ды у нас нет. Един­ствен­ный спо­соб впих­нуть в это вы­ра­же­ние ка­кое-ли­бо до­пол­ни­тель­ное дей­ствие — это до­ба­вить вы­зов функ­ции и/или опе­ра­цию за­пя­тая спра­ва от опе­ра­ции ини­циа­ли­за­ции:

for(int x=out(0), y=3; x<y; ++x) out(x); //Выведет 0012
for(int x=( out(9), 0 ), y=3; x<y; ++x) out(x); //Выведет 9012

По­след­ний при­мер — со­вер­шен­но бес­по­лез­ная вещь, ибо out(9) мож­но на­пи­сать и пе­ред цик­лом. Ещё ма­ло­по­лез­ные при­ме­не­ния:

for(int x=0, y=9; x<y; ++x, --y) out(x), out(y); //Выведет 0918273645

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

for(int x=0, y=9; x<y; ) { out(x); out(y); ++x; --y; }

Воз­ни­ка­ет впе­чат­ле­ние, что поль­за есть толь­ко от раз­де­ли­тель­ных за­пя­тых, а за­пя­тая-опе­ра­ция лишь услож­ня­ет по­ни­ма­ние про­грам­мы, не да­вая ни­ка­ких но­вых воз­мож­но­стей про­грам­ми­сту. За­чем эта опе­ра­ция во­об­ще бы­ла вве­де­на в C и C++?

Мне всё же уда­лось най­ти два по­лез­ных при­ме­не­ния опе­ра­ции за­пя­тая. Пер­вое — это вы­пол­не­ние до­пол­ни­тель­ных дей­ствий в услов­ных вы­ра­же­ни­ях цик­лов for и while. Вто­рое — это пе­ре­груз­ка опе­ра­ции за­пя­тая для то­го, что­бы она слу­жи­ла со­вер­шен­но иным це­лям.

Вна­ча­ле рас­смот­рим ис­поль­зо­ва­ние в услов­ных вы­ра­же­ни­ях цик­лов. До­пу­стим, пе­ред про­вер­кой усло­вия цик­ла тре­бу­ет­ся вы­пол­нить не­ко­то­рые дей­ствия (на­при­мер, вы­чис­лить зна­че­ние функ­ции), и вы­чис­лен­ное зна­че­ние тре­бу­ет­ся нам внут­ри цик­ла. То­гда есть смысл на­пи­сать та­кой код:

//Перебираем значения x. Условие продолжения работы цикла --
//  положительность некоторой функции f(x)
for(float x=0.0, y; y=f(x), y>0.0; x+=0.1) cout << x << ", " << y << endl;

Ана­ло­гич­но для цик­ла while:

float x = 0.0, y;
while(y=f(x), y>0.0) { cout << x << ", " << y << endl; x+=0.1; }

Об­ра­ти­те вни­ма­ние, что опи­са­ние пе­ре­мен­ных мы мо­жем по­ме­стить пе­ред цик­лом, на­ра­щи­ва­ние зна­че­ний этих пе­ре­мен­ных мо­жем по­ме­стить в кон­це те­ла цик­ла, а вот вы­зов функ­ции f(x) мы не мо­жем ни­ку­да пе­ре­не­сти, ина­че нам при­дёт­ся на­пи­сать его два­жды: пе­ред цик­лом и в кон­це те­ла цик­ла.

При­ве­дён­ные при­ме­ры мож­но реа­ли­зо­вать и без за­пя­тых, ис­поль­зуя воз­вра­ща­е­мое зна­че­ние опе­ра­ции при­сваи­ва­ния:

for(float x=0.0, y; (y=f(x)) > 0.0; x+=0.1) cout << x << ", " << y << endl;

float x = 0.0, y;
while( (y=f(x)) > 0.0 ) { cout << x << ", " << y << endl; x+=0.1; }

Но мы смог­ли обой­тись без за­пя­той лишь по­то­му, что за­да­ча про­стая. Бы­ло бы у нас, на­при­мер, две функ­ции вме­сто од­ной — и за­пя­тая бы очень при­го­ди­лась.

Твор­че­ское при­ме­не­ние опе­ра­ции за­пя­тая опи­сан­ным вы­ше спо­со­бом мож­но най­ти в сле­дую­щей про­грам­ме, вы­чис­ляю­щей 800 де­ся­тич­ных цифр чис­ла \pi (по­дроб­но­сти здесь):

#include<stdio.h>
int a=10000,b,c=2800,d,e,f[2801],g;int main(){for(;b-c;)f[b++]=a/5;for(;d=0,g=c*2;
c-=14,printf("%.4d",e+d/a),e=d%a)for(b=c;d+=f[b]*a,f[b]=d%--g,d/=g--,--b;d*=b);}

Пе­ре­груз­ка опе­ра­ции за­пя­тая

Мы по­дроб­но изу­чи­ли ис­поль­зо­ва­ние опе­ра­ции за­пя­тая, и по­ка об­на­ру­жи­ли лишь од­но по­лез­ное при­ме­не­ние. К сча­стью, язык про­грам­ми­ро­ва­ния C++ поз­во­ля­ет пе­ре­гру­зить опе­ра­цию за­пя­тая для то­го, что­бы она слу­жи­ла ка­ким угод­но це­лям. Син­так­сис пе­ре­груз­ки оче­ви­ден:

возвр_тип operator,(тип1 арг1, тип2 арг2) { ... }

Сле­ду­ет знать о том, что точ­ка сле­до­ва­ния, опре­де­лён­ная на ме­сте опе­ра­ции за­пя­тая, ис­че­за­ет при пе­ре­груз­ке этой опе­ра­ции. Этот эф­фект вы­зван тем, что за­пя­тая в слу­чае пе­ре­груз­ки пре­вра­ща­ет­ся в обыч­ный вы­зов функ­ции, а по­ря­док вы­чис­ле­ния ар­гу­мен­тов при вы­зо­ве функ­ции не опре­де­лён.

Опе­ра­ция мо­жет быть шаб­лон­ной, а ар­гу­мен­ты мо­гут быть пе­ре­да­ны по ссыл­кам. В об­щем, про­сто­ра для твор­че­ства мно­го (этот про­стор огра­ни­чен толь­ко тем, что хо­тя бы один из ар­гу­мен­тов пе­ре­опре­де­ля­е­мой опе­ра­ции дол­жен быть поль­зо­ватель­ским клас­сом).

Но и тут за­пя­тая по­ка­зы­ва­ет свой сквер­ный ха­рак­тер. Не­осто­рож­но пе­ре­опре­де­лив эту опе­ра­цию для ка­ких-ли­бо ши­ро­ко ис­поль­зуе­мых в про­грам­ме ти­пов дан­ных, мы мо­жем на­ру­шить про­грам­му в со­вер­шен­но не­ожи­дан­ных ме­стах (в том чис­ле мо­гут на­чать не­вер­но ра­бо­тать стан­дарт­ные кон­тей­не­ры).

Рас­смот­рим до­воль­но ча­стое при­ме­не­ние пе­ре­гру­жен­ной опе­ра­ции за­пя­тая — ком­пакт­ную за­пись ини­циа­ли­за­ции кон­тей­не­ров. На­при­мер, вме­сто та­ко­го ко­да:

vector<int> c;
c.push_back(10); c.push_back(20); c.push_back(30); c.push_back(40);

по­яв­ля­ет­ся воз­мож­ность пи­сать та­кой код:

vector<int> c;
c << 10, 20, 30, 40;

До­бить­ся опи­сан­ной функ­цио­наль­но­сти мож­но дву­мя спо­со­ба­ми:

  1. Сде­лать так, что­бы обе опе­ра­ции (за­пя­тая и <<) до­бав­ля­ли эле­мент (пра­вый ар­гу­мент) в кон­тей­нер (ле­вый ар­гу­мент), и воз­вра­ща­ли ссыл­ку на кон­тей­нер. По при­чи­не, опи­сан­ной вы­ше, это пло­хой спо­соб, так как опе­ра­ция за­пя­тая нач­нёт сра­ба­ты­вать в не­ожи­дан­ных ме­стах про­грам­мы. Да и смыс­ла не бу­дет в за­пя­той, ибо мож­но бу­дет пи­сать так: c << 10 << 20 << 30 << 40;
  2. Сде­лать так, что­бы опе­ра­ция << воз­вра­ща­ла объ­ект спе­ци­аль­но­го клас­са, и опре­де­лить за­пя­тую имен­но для это­го клас­са.

Реа­ли­зу­ем вто­рой ва­ри­ант. Пре­жде все­го, со­зда­дим шаб­лон­ный класс CommaInserter, ко­то­рый со­дер­жит ссыл­ку на кон­тей­нер. При кон­струи­ро­ва­нии объ­ект это­го клас­са бу­дет за­по­ми­нать ссыл­ку на кон­тей­нер и до­бав­лять в не­го пер­вый эле­мент. Кро­ме то­го, CommaInserter бу­дет иметь соб­ствен­ную опе­ра­цию за­пя­тая, ко­то­рая бу­дет до­бав­лять оче­ред­ной эле­мент:

template<class C> class CommaInserter
{
private: C &container;
public:
    template<class V> inline CommaInserter(C &container, V const &value):
        container(container) { container.insert(container.end(), value); }
    template<class V> inline CommaInserter<C> operator,(V const &value)
        { container.insert(container.end(), value); return *this; }
};

Те­перь до­опре­де­лим опе­ра­ции << и < для то­го, что­бы они со­зда­ва­ли эк­зем­пляр CommaInserter. Вто­рая опе­ра­ция от­ли­ча­ет­ся от пер­вой тем, что вна­ча­ле очи­ща­ет кон­тей­нер:

template<class C, class V>
    inline CommaInserter<C> operator<<(C &container, V const &value)
{ //Начинает добавление элементов в контейнер, создавая CommaInserter
    return CommaInserter<C>(container, value);
}

template<class C, class V>
    inline CommaInserter<C> operator<(C &container, V const &value)
{ //Очищает контейнер и начинает добавление элементов
    container.clear();
    return CommaInserter<C>(container, value);
}

Пред­став­лен­ные опе­ра­ции << и < для ком­пи­ля­то­ра име­ют ми­ни­маль­ный при­о­ри­тет (при­о­ри­тет не в смыс­ле по­ряд­ка вы­пол­не­ния опе­ра­ций, а в смыс­ле то­го, ка­кая имен­но реа­ли­за­ция опе­ра­ции бу­дет вы­бра­на ком­пи­ля­то­ром), так как эти опе­ра­ции шаб­лон­ные, и все ар­гу­мен­ты у них шаб­лон­ные. По­это­му не сто­ит пе­ре­жи­вать, что опе­ра­ции сра­бо­та­ют в «лиш­них» ме­стах: ес­ли уж про­грам­мист пи­шет << при­ме­ни­тель­но к ка­ко­му-ли­бо объ­ек­ту, то про­грам­мист яв­но хо­чет вы­пол­нить с объ­ек­том ка­кое-ли­бо дей­ствие (а не про­сто раз­де­лить два дей­ствия, как в слу­чае с за­пя­той).

По­иг­ра­ем­ся с на­ши­ми но­вы­ми опе­ра­ци­я­ми:

//Вектор из стандартной библиотеки (#include <vector>)
vector<int> v; //Пустой вектор
v << 1, 2, 3;  //Добавляем элементы: [1, 2, 3]
v << 2, 3, 4;  //Добавляем ещё элементы: [1, 2, 3, 2, 3, 4]
v < 3, 4, 5;   //Очищаем вектор и добавляем элементы: [3, 4, 5]

//Множество из стандартной библиотеки (#include <set>)
set<int> s;
s << 3, 2, 1, 3; //Добавляем элементы в множество. Множество всегда
                 //  отсортировано, и не содержит повторений,
                 //  поэтому s = {1, 2, 3}
s << 2, 5; //Ещё пара элементов: s = {1, 2, 3, 5}
s < 1, 2;  //Очищаем и добавляем: s = {1, 2}

Хо­чет­ся ве­рить, что пред­став­лен­ные опе­ра­ции бу­дут ра­бо­тать со все­ми кон­тей­не­ра­ми из стан­дарт­ной биб­лио­те­ки и с боль­шин­ством поль­зо­ватель­ских кон­тей­не­ров, под­дер­жи­ваю­щих ите­ра­то­ры.

За­клю­че­ние

Мы уви­де­ли, что C++ — не столь про­стой язык, как ка­жет­ся. Да­же ба­наль­ные за­пя­тые в нём яв­ля­ют­ся слож­ны­ми и мно­го­гран­ны­ми сущ­но­стя­ми (мо­гут яв­лять­ся раз­де­ли­те­ля­ми или опе­ра­ци­я­ми, мо­гут опре­де­лять ли­бо не опре­де­лять точ­ку сле­до­ва­ния, мо­гут быть пе­ре­гру­же­ны...). Ес­ли вы зна­е­те ещё о ка­кой-ли­бо осо­бен­но­сти за­пя­тых, не от­ра­жён­ной в этом ру­ко­вод­стве, про­шу пи­сать в ком­мен­та­ри­ях.

19 отзывов на запись «Опе­ра­ция за­пя­тая в C++»

Весь пост не про­чи­тал, но по­хо­же для for не ука­зан та­кой слу­чай, где все-та­ки за­пя­тая удоб­на:
for ( int i = 0, j = 0; i < i_max; ++i, j = i * i) {
    if ( condition ) {
        continue;
        // после continue будет выполнено ++i, j = i * i,
        // иначе пришлось бы писать j = i * i для каждого continue;
    }
    // код, который не нужен при condition,
    // но j = i * i все равно должно выполнятся;
}
В ре­зуль­та­те при ис­поль­зо­ва­нии continue вы­пол­ня­ет­ся код, ко­то­рый мож­но по­том ме­нять при не­об­хо­ди­мо­сти толь­ко в од­ном ме­сте, а не пе­ред каж­дым continue. Т. е. все-та­ки удоб­ство есть, а вы пи­ши­те "Воз­ни­ка­ет впе­чат­ле­ние, что поль­за есть толь­ко от раз­де­ли­тель­ных за­пя­тых, а за­пя­тая-опе­ра­ция лишь услож­ня­ет по­ни­ма­ние про­грам­мы, не да­вая ни­ка­ких но­вых воз­мож­но­стей про­грам­ми­сту".
Спа­си­бо, хо­ро­ший при­мер.
А ну­жен ли здесь continue?
for ( int i = 0, j = 0; i &lt; i_max; ++i, j = i * i) {
    if ( !condition ) {
        // код, который не нужен при condition
    }
}
По идее ни­чем не ху­же. Опе­ра­ции ++i и j = i * i мож­но без ущер­ба вы­не­сти в ко­нец цик­ла (хоть я бы не стал это­го де­лать), и они бу­дут ра­бо­тать так же. .
Вот веч­но так — ка­кой-нить при­ду­рок вче­ра про­чи­тал ка­ко­го-нить Рих­те­ра, а зав­тра уже стро­чит гов­но­ста­тьи в ин­тер­не­те. >>> Пусть вы­ра­же­ние «по­боч­ный эф­фект» вас не пу­га­ет, — это дей­ствие, со­вер­шен­но осо­знан­но за­про­грам­ми­ро­ван­ное раз­ра­бот­чи­ком про­грам­мы. «По­боч­ный» — не обя­за­тель­но «пло­хой»; Ты ещё ска­жи вез­де void * пе­ре­да­вать, а в нуж­ном ме­сте пи­сать что-то вро­де *((double *)m). Ну а че­го та­ко­го? Это же бу­дет со­вер­шен­но осо­знан­но за­про­грам­ми­ро­ва­но! А то что от­ла­дить та­кое гов­но слож­нее так ко­го это ебёт, да? А то что разо­брать­ся в по­боч­ных эф­фек­тах тво­их за­пя­тых дру­гим лю­дям по­том при­дёт­ся это ни­хе­ра страш­но­го, да? Им ведь так хо­чет­ся из-за та­ко­го му­да­ка в оче­ред­ной раз до­ста­вать стан­дарт или K&R и пе­ре­чи­ты­вать их, тра­тя своё ра­бо­чее или лич­ное вре­мя, про­сто что­бы разо­брать­ся как про­грам­ма бу­дет ве­сти се­бя, при том, что всё это гов­но мож­но бы­ло рас­пи­хать по от­дель­ным опе­ра­то­рам, и ни­ка­ких про­блем бы не воз­ник­ло! >>>Сле­ду­ет знать о том, что точ­ка сле­до­ва­ния, оп­ре­де­лён­ная на ме­сте опе­ра­ции за­пя­тая, ис­че­за­ет при пе­ре­груз­ке этой опе­ра­ции. Этот эф­фект вы­зван тем, что за­пя­тая в слу­чае пе­ре­груз­ки пре­вра­ща­ет­ся в обыч­ный вы­зов функ­ции, а по­ря­док вы­чис­ле­ния ар­гу­мен­тов при вы­зо­ве функ­ции не оп­ре­де­лён. Зае­бись! У нор­маль­но­го че­ло­ве­ка в этом ме­сте долж­но в баш­ке щёлк­нуть, что нихуя хо­ро­ше­го из та­ких ве­сё­лых ве­щей в прин­ци­пе не мо­жет по­лу­чить­ся. Эта ста­тья — перл упо­ро­то­го кре­ти­на. Нор­маль­ные про­грам­ми­сты стре­мят­ся из­бе­гать по­боч­ных эф­фек­тов там где они мо­гут и в осо­бен­но­сти там где эти по­боч­ные эф­фек­ты не да­ют ни­че­го. При­мер:
int c;
while((c = getc(stdio)) != EOF)
{
    /* do something */
}
Это свое­об­раз­ная иди­о­ма в язы­ке Си. В по­доб­ном кон­тек­сте ис­поль­зо­ва­ние по­боч­но­го эф­фек­та оправ­да­но — это удоб­но, про­грам­ма ве­дёт се­бя пред­ска­зу­е­мо и «вы­лез­ти» где-ни­будь этот по­боч­ный эф­фект не мо­жет. Дру­гое де­ло опе­ра­ция за­пя­тая. На­хе­ра она во­об­ще нуж­на кро­ме по­рож­де­ния по­боч­ных эф­фек­тов? Нор­маль­ные про­грам­ми­сты дав­но взя­ли се­бе за пра­ви­ло — од­но дей­ствие в про­грам­ме од­на строч­ка ко­да. За­пя­тая поз­во­ля­ет за­пи­хать в од­ну строч­ку сколь­ко угод­но дей­ствий и при том ещё и сде­лать всё это дерь­мо од­ним опе­ра­то­ром! Где во­об­ще это мо­жет при­го­дит­ся? Это как из «Вой­ны и мир» все точ­ки, про­бе­лы и за­пя­тые убрать, сде­лать все бук­вы строч­ны­ми и за­ста­вить по­том ко­го-то это чи­тать. Я бы на ме­сте кол­лег та­ких гов­но­пи­са­те­лей еб­ло за та­кое бил. Го­луб в «Ве­рёв­ке до­ста­точ­ной длин­ны» во­об­ще со­ве­ту­ет не ис­поль­зо­вать за­пя­тую. Поль­зы от неё ни­ка­кой по­чти все­гда. Лиш­няя строч­ка-две в про­грам­ме ни­ко­му не ме­ша­ет, ско­рость вы­пол­не­ния про­грам­мы за­пя­ты­ми не по­вы­сишь. Ко­неч­но ино­гда код на­пи­сан­ный с за­пя­ты­ми вы­гля­дит изящ­нее, но эта про­сто­та толь­ко ка­жу­щая­ся, и ко­гда вы ко­гда-ни­будь столк­нё­тесь с труд­но­уло­ви­мой ошиб­кой, по­рож­дён­ной по­боч­ным эф­фек­том за­пя­той вы пой­мё­те на­сколь­ко до­ро­го об­хо­дит­ся эта ка­жу­щая­ся «изящ­ность». Кста­те, как пра­ви­ло та­кая ошиб­ка об­на­ру­жи­ва­ет­ся прак­ти­че­ски мгно­вен­но, по­сле то­го как вы раз­би­ва­е­те за­пя­тую на два нор­маль­ных опе­ра­то­ра. На­по­сле­док при­ве­ду ци­та­ту Waldi Ravens: «Про­грам­ми­ро­ва­ние на С по­хо­же на быст­рые тан­цы на толь­ко что от­по­ли­ро­ван­ном по­лу лю­дей с ост­ры­ми брит­ва­ми в ру­ках.» Нуж­но быть не очень ум­ным че­ло­ве­ком что­бы на­ме­рен­но «на­хе­ра­чи­вать» по­тен­ци­аль­ных опас­но­стей се­бе же са­мо­му в та­ких не слиш­ком «безопас­ных» для «тан­цев» язы­ках как С или С++.
Я с ва­шим ком­мен­та­ри­ем аб­со­лют­но со­гла­сен, ес­ли не брать в рас­чёт фра­зу «эта ста­тья — перл упо­ро­то­го кре­ти­на». (Бук­валь­но вче­ра я по­лу­чил справ­ку в нар­ко­ло­ги­че­ском дис­пан­се­ре, что я не упо­ро­тый, и справ­ку в пси­хо­нев­ро­ло­ги­че­ском дис­пан­се­ре, что я не кре­тин. Так что на этот счёт я спо­ко­ен.) Те­перь ка­са­тель­но ва­шей агрес­сии. Пред­ставь­те се­бе, я то­же по­ни­маю, что код нуж­но не толь­ко на­пи­сать, но и до­ку­мен­ти­ро­вать, и под­дер­жи­вать, и от­ла­жи­вать, и по­мо­гать кол­ле­гам этим ко­дом поль­зо­вать­ся. Я что, где-то аги­ти­ро­вал за ис­поль­зо­ва­ние опе­ра­ции за­пя­тая? Ней­траль­ный стиль из­ло­же­ния дан­ной ста­тьи не озна­ча­ет то­го, что я счи­таю за­пя­тую чем-то хо­ро­шим. Это дан­ность. Вы­ступ­лю лишь в за­щи­ту пе­ре­груз­ки за­пя­той для ини­циа­ли­за­ции кон­тей­не­ров: в биб­лио­те­ках Eigen и OpenCV та­ким спо­со­бом мож­но за­пол­нять мат­ри­цы. Учи­ты­вая, что эти биб­лио­те­ки до­ста­точ­но ши­ро­ко рас­про­стра­не­ны, и на­пи­са­ны лю­дь­ми от­нюдь не глу­пы­ми, я счёл важ­ным упо­мя­нуть это при­ме­не­ние за­пя­той, как впол­не удоб­ное и оправ­дан­ное. Вот веч­но так — ка­кой-нить при­ду­рок вче­ра про­чи­тал ка­ко­го-нить Рих­те­ра, а зав­тра уже стро­чит гов­но­ста­тьи в ин­тер­не­те. Мне это яв­ле­ние то­же не нра­вит­ся.
Не по­ни­маю ва­ше­го не­га­ти­ва. Че­ло­век по­дроб­но и по­нят­но рас­ска­зал о не слиш­ком ис­поль­зу­е­мом опе­ра­то­ре. У K&R, кста­ти, на­пи­са­но го­раз­до мень­ше. То, что зло­упо­треб­ле­ние за­пя­той мо­жет сде­лать про­грам­му бо­лее за­пу­тан­ной, от­нюдь не озна­ча­ет, что вы с ней ни­ко­гда не столк­нё­тесь. Мне один раз на со­бе­се­до­ва­нии по­па­лась за­дач­ка на за­пя­тую (на со­бе­се­до­ва­ни­ях во­об­ще очень лю­бят та­кие ве­щи).
Но есть у та­ких на­деж­ных бро­не­кон­ст­рук­ций и боль­шой не­до­ста­ток — они се­рьез­ная пре­гра­да не толь­ко для зло­умыш­лен­ни­ков, но и для по­жар­ных. При на­гре­ве зам­ки мо­гут за­бло­ки­ро­вать­ся.
Зам­ки для ме­тал­ли­че­ских две­рей тра­ди­ци­он­но до­ро­же и слож­нее в уста­нов­ке и за­ме­не. Бы­ва­ют да­же та­кие, на ко­то­рых за­ме­на зам­ка в прин­ци­пе не преду­смот­ре­на про­из­во­ди­те­лем.
На­деж­ная ме­тал­ли­че­ская дверь, ко­неч­но, по­вы­ша­ет безопас­ность Ва­ше­го жи­ли­ща, но ни­как не га­ран­ти­ру­ет пол­ной не­при­кос­но­вен­но­сти. Взлом­щи­ки поль­зу­ют­ся раз­лич­ны­ми хит­ры­ми улов­ка­ми и при­бо­ра­ми (на­при­мер, уг­ло­вой шли­фо­валь­ной ма­ши­ной). Воз­дей­ствию та­ких ин­стру­мен­тов под­вер­жен бой бе­то­на бой кир­пи­ча лю­бой ме­талл, но на­деж­ная дверь мак­си­маль­но за­дер­жит пре­ступ­ни­ка и смо­жет его от­влечь. По­это­му мы со­ве­ту­ем не пре­не­бре­гать креп­кой и ка­че­ствен­ной ме­тал­ли­че­ской две­рью на вхо­де сво­ей квар­ти­ры или до­ма.
Et ce n est qu un debut. http://lenitsky.com/semki-klipa-obnimi-menya-zavershenyi-backstage/ La telemedecine a en effet sa place en geriatrie.
propranolol cost
generic for strattera
atenolol
Hello!
По­зна­ком­люсь:
с Пар­нем
в воз­расте 26-30 лет
Цель зна­ком­ства:
Брак, со­зда­ние се­мьи
Ин­те­ре­сы
Fitness
Ти­паж
Те­ло­сло­же­ние:
Обыч­ное
Рост:
168 см
Вес:
70 кг
Во­ло­сы на го­ло­ве:
Тем­ные
Цвет глаз:
Ка­рие
Ре­ли­гия:
Хри­сти­ан­ство
Со­цио­ни­че­ский тип:
Есе­нин (ли­рик)
Зна­ние язы­ков:
Рус­ский
Тип внеш­но­сти:
Кав­каз­ская
Про­фес­сия:
До­мо­хо­зяй­ка не ра­бо­таю в де­кре­те
Ма­те­ри­аль­ное по­ло­же­ние:
Не­по­сто­ян­ные за­ра­бот­ки
Про­жи­ва­ние:
Жи­ву с ро­ди­те­ля­ми
Де­ти:
Есть, жи­вем вме­сте
Де­ти парт­нё­ра:
Не про­тив де­тей парт­не­ра
Cов­ме­ст­ные де­ти:
По­ка не знаю, как по­лу­чит­ся
Сек­су­аль­ные пред­по­чте­ния
loveawake
Как ча­сто Вы хо­те­ли бы за­ни­мать­ся сек­сом?
Для ме­ня секс не очень ва­жен
Раз­мер гру­ди
Сред­няя
Ме­ня воз­буж­да­ет:
Ин­тим­ная об­ста­нов­ка
Normally I do not read post on blogs, but I wish to say that this writeup very compelled me to try and do it! Your writing style has been surprised me. Thanks, quite great article.
brand viagra
buy eurax
cheap vermox
erythromycin 500 mg
please check out my pics

Оставить отзыв

Жёлтые поля обязательны к заполнению

   

Можете использовать теги <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong> <pre lang=""> <div class=""> <span class=""> <br>