Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
билеты информатика.rtf
Скачиваний:
39
Добавлен:
02.05.2015
Размер:
6.9 Mб
Скачать

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

Рас­смот­рим не­сколь­ко при­ме­ров. Пусть у нас опи­са­ны 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 == 1b = 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, на экране по прежнему 123b = 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); //Выведет 0012for(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 де­ся­тич­ных цифр чис­ла  (по­дроб­но­сти здесь):

#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);}