Внимание! Прочитайте, пожалуйста, текст в правой колонке (внизу).
Внимание! Прочитайте, пожалуйста, текст в правой колонке (внизу). Внимание! Прочитайте, пожалуйста, текст в правой колонке (внизу). Homepage Карта сайта Версия для печати

Джентльменский набор Web-разработчика   Ларри Уолл о Perl6   Наблы Система Orphus
 

22. Разыменование ссылок в Perl,  или просто о сложном

[5 апреля 2002 г.]

  TIMTOWTDI: There Is More Than One Way To Do It
(всегда есть несколько способов сделать это)

Принцип самоубийцы

В предыдущей набле я писал, как обычно создаются ссылки в программах на Perl. Конечно, от ссылок мало проку, если мы не умеем их разыменовывать — то есть, получать доступ к объекту, на который ссылка указывает. Сейчас мы рассмотрим этот механизм очень подробно.

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

Предположим, мы в программе имеем некоторую ссылку (я буду дальше обозначать ее вот так: ССЫЛКА). Это может быть обычный скаляр: $r, а может — некоторое выражение, результатом вычисления которого будет ссылка. Например, вызов функции, возвращающей ссылку: subReturningRef(). В зависимости от того, какой тип имеет ссылка, применяют различные способы ее разыменования. Если вы, к примеру, попытаетесь разыменовать ссылку на массив при помощи синтаксиса разыменования хэшей, Perl выдаст ошибку. Так что в момент получения объекта вы должны уже представлять себе, какого он типа. Если это все же неизвестно, всегда можно вызвать ref и узнать, что же за фрукт у нас имеется.

Разыменование ссылки на массив

Вообще говоря, существует 2 типа разыменования ссылки на массив.

  • Получение массива целиком — например, для последующего его использования в списковом контексте и преобразования в список.
  • Доступ к отдельному элементу массива по ссылке на массив и индексу элемента. При этом всегда получается скаляр (потому что элемент массива может содержать лишь его, но не более сложный объект).

В первом случае применяется следующий синтаксис:

Листинг 1
# получение <b>массива целиком</b> по ссылке
@{ССЫЛКА}

К сожалению, отказаться от фигурных скобок в большинстве случаев нельзя — конструкция @{} неразделима.

Лирическое отступление 
В конце наблы я расскажу о некоторых альтернативных приемах разыменования, которые иногда помогают сократить программу. Но не сейчас.

Более того: выражение @{...} воспринимается интерпретатором точно так же, как и обычное @A, а потому может быть использовано везде, где допустимо последнее. Например:

Листинг 2
# цикл по всем элементам массива
foreach my $e (@{ССЫЛКА}) { print $_ }
# присваивание одного массива другому
@A = @{ССЫЛКА};
# присваиваем массиву по ссылке новое 
# содержимое; при этом его адрес <i>не меняется</i>
@{ССЫЛКА} = @A;
# это <i>не то же самое</i>, что:
# ССЫЛКА = \@A;

# взятие ссылки от массива
$r = \ @{ССЫЛКА};
# что полностью эквивалентно следующему:
$r = ССЫЛКА; # зачем городить сложности?

Для получения отдельного элемента массива этот синтаксис совершенно непригоден.

Лирическое отступление 
Непригоден концептуально. Практически же можно написать что-то вроде @{ССЫЛКА}[1] — получение среза, — но в следующих версиях Perl эта возможность, по словам Ларри Уолла, будет отключена.

Для того, чтобы добраться до отдельного элемента массива, следует применить вот такой причудливый синтаксис:

Листинг 3
# получение отдельного элемента массива
(ССЫЛКА)->[номер];

В отличие от предыдущих рассуждений, здесь использовать круглые скобки не обязательно (если выражение ССЫЛКА очень простое, как чаще всего и бывает). Однако их наличие не повредит. Даже @{ССЫЛКА} можно было бы записать как @{(ССЫЛКА)}, однако здесь нагромождение скобок кажется явно излишним.

Итак, вот примеры кода:

Листинг 4
# вывод второго элемента массива
print ССЫЛКА->[2];
# перебор массива (массив в скалярном
# контексте возвращает число элементов)
for(my $i=0; $i<@{ССЫЛКА}; $i++) {
    print ССЫЛКА->[$i];
}

В общем-то это все, что достаточно знать про разыменование массивов в Perl. Существуют и другие способы, более лаконичные, но более «капризные», об этом — в конце наблы. А сейчас — несколько реальных примеров, так сказать, «из жизни»:

Листинг 5
# Создаем двумерный массив.
@A = (
    [1, 2, 3],
    [4, 5, 6],
    [7, 8, 9],
);
# Обходим его и печатаем по строкам
for(my $y=0; $y<@A; $y++) {
    # текущая строка - @{$A[$y]}
    for(my $x=0; $x<@{$A[$y]}; $x++) {
        print(($A[$y])->[$x]." ");
        # можно было бы записать и так:
        # print "$A[$y]->[$x] ";
    }
    print "\n";
}
# Другой способ
foreach (@A) {
    foreach (@{$_}) {
        print "$_ ";
    }
    print "\n";
}
# И еще один (короче не бывает!)
print join "\n", map { join " ", @$_ } @A;

# Обнуляем массив
for(my $y=0; $y<@A; $y++) {
    for(my $x=0; $x<@{$A[$y]}; $x++) {
        $A[$y]->[$x]=0;
    }
}
# Или так (пользуемся, что foreach создает
# $_ как <i>синоним</i> для очередного элемента)
foreach (@A) {
    foreach (@$_) {
        $_=0;
    }
}
# Или с помощью map
map { map { $_=0 } @$_ } @A;

# А если бы @A сама была ссылкой...
$r = \@A;
# ...то доступ к элементу (2,1):
$e = (($r)->[2])->[1];
# или чуть короче:
$e = $r->[2]->[1];
# или даже так (см. конец наблы):
$e = $r->[2][1];

Разыменование ссылки на хэш

Может показаться странным, но хэши разыменовываются точно так же, как и массивы, только вместо @ нужно указывать %, а вместо []{}. Итак, у нас есть следующие типы работы с хэшем:

Листинг 6
# получения <b>хэша целиком</b>
%{ССЫЛКА};
# получение <b>отдельного элемента</b> хэша
# по его ключу
(ССЫЛКА)->{ключ};

Используя эти 2 типа разыменований, можно написать программу, работающую с хэшами, как повар с картошкой. Вот некоторые примеры:

Листинг 7
# Создаем хэш ссылок на массивы
my $missingTeeth = {
    me     => [],
    boxer  => [4,5,8],
    granny => [1..7,10..32],
    comb   => [10,11,12,13],
    saw    => [38],
};
# добавляем новый массив в хэш
($missingTeeth)->{mom}=[18];
# еще один элемент
$missingTeeth->{dad} = 
  [@{$missingTeeth->{mom}}, 26];
# здесь сначала @{...} разворачивается в список,
# к которому добаляется 26, а затем создается
# анонимный массив.

# Перебор и печать всего хэша
while(my ($k,$v)=each(%$missingTeeth)) {
    print "$k: ";
    foreach (@$v) { print "$_ " }
    print "\n";
}
# доступ к отдельному элементу
print $missingTeeth->{dad}->[2];

В данном примере я намеренно не стал создавать обычную хэш-переменную, а сделал сразу ссылку. Теперь вы должны окончательно убедиться, что %{ССЫЛКА} и @{ССЫЛКА} иногда можно сократить до %ССЫЛКА и @ССЫЛКА, но только в том случае, если ССЫЛКА представляет собой обычную скалярную переменную, как, например, $missingTeeth. В остальных случаях фигурные скобки обязательны, и, если вы не уверены, можно ли их опустить — просто не опускайте.

Разыменование ссылки на скаляр

Коль скоро скаляр — это неделимая сущность, для его разыменования существует всего один способ:

Листинг 8
# получение значения скаляра по ссылке
${ССЫЛКА};

Опускать здесь фигурные скобки можно в тех же самых случаях, что и для массивов с хэшами. Вот несколько примеров:

Листинг 9
# создаем ссылку на скаляр
$a = "test";
$r = $a;
# выводим значение скаляра
print ${$r};
# или так:
print $$r;
# а если ссылка на скаляр есть в массиве...
$A[10] = $a;
# ...то для ее разыменования:
print ${$A[10]};

Довольно интересно обстоят дела со ссылками на константы (в примере выше была создана ссылка на скалярную переменную):

Листинг 10
# ссылка на константу
$r = \ "test";
# мы можем ее вывести...
print $$r;
# но не способны изменить:
$$r = "error"; # - генерируется ошибка!

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

Листинг 11
# создаем readonly-переменную
*a = \"test";
# теперь ее можно читать...
print $a;
# но нельзя - писать:
$a = 10; # - ошибка!

Однако это затрагивает технологию работы с таблицами символов Perl (тип-глобами — TYPEGLOBS), о чем будет рассказано как-нибудь в другой раз.

Разыменование ссылки на функцию

Ссылка на анонимную функцию — один из самых интересных объектов в Perl. Я уже упоминал, что в этом языке можно создавать функции прямо «на лету», не испытывая особых неудобств. Теперь добавлю сюда, что любая переменная в программе — это в действительности ссылка на анонимный скаляр из таблицы символов, но с более удобным синтаксисом доступа, а функция — точно такая же переменная, только ссылка там идет на код, а не на скаляр. Раз так, должен быть способ вызвать функцию по ссылке на нее, иными словами — разыменовать функцию. Таких способов даже два, и вот примеры их использования:

Листинг 12
# создаем анонимную функцию
$f = sub { print "Hello\n" };
# обращаемся к ней и передаем в 
# параметрах числа 10, 20
($f)->(10,20);
# или короче:
$f->(10,20);
# второй способ:
&{$f}(10,20);
# или короче:
&$f(10,20);

Итак, если символика хэша — это %,{}, а массива — @,[], то «герб» функции должен содержать символы &,(), что мы и наблюдаем. Если систематизировать два способа разыменования, как я это делал раньше, то получится:

Листинг 13
# первый способ:
(ССЫЛКА)->(параметры);
# второй способ:
&{ССЫЛКА}(параметры);

«Хэшемассивы»

Расскажу один случай, который произошел со мной некоторое время назад. Возникла необходимость хранить в программе нечто вроде хэша, но только чтобы порядок элементов в нем был не произвольный, а заданный. Было принято решение использовать для этих целей обычный массив, но с условием, что четные элементы в нем обозначают ключи, а нечетные — значения. Преимуществ у такого подхода два:

Листинг 14
# Этот массив создается точно так же,
# как и обычный хэш - никакой разницы:
@H = (
    a => 10,
    b => 20,
    c => 30,
);
# При случае его легко можно преобразовать
# в хэш для работы с ключами:
%H = @H;
print $H{b}; # имеется в виду %H

В общем, практически беспроигрышный вариант, не правда ли?.. Однако в последней строчке меня смущало использование временной переменной %H — хотелось бы обойтись без нее. И я начал пробовать разные варианты синтаксиса:

Листинг 15
# Внимание - неверный код!
# я пробовал так:
print %{@H}{b};
# и так:
print (\%{@H})->{b};
# и вот так:
print (\%@H)->{b};
# и даже вот так:
print (\%{\@H})->{b};
# а результат один...

Разумеется, ни один из этих вариантов не хотел работать, и я уже начал думать, что решения не существует. В те времена я еще не подозревал о систематизации методов разыменования хэшей и массивов, которую я привел выше, а предпочитал делать все «методом тыка». Но, как только я задумался о том, почему же Perl так упорно не хочет принимать мои варианты, я вдруг понял: чудес не бывает, и преобразовать массив в хэш напрямую (без участия списков) невозможно! То есть, я не могу вот так взять и к той памяти, которая используется для массива, разом обратиться как к хэшу. В любом случае необходимо создание нового объекта, что, кстати, и происходило в примере с временной переменной.

Но как же создать новый объект в программе, не задействовав временные переменные? Такой способ только один — использовать анонимный хэш. В результате родился вот такой простой код, который работает просто замечательно:

Листинг 16
# А вот так - работает!
$s = {@H}->{b};
# И даже вот так:
print scalar {@H}->{b};

Лирическое отступление 
Обратите внимание на то, что в последней строчке обязательно требуется оператор scalar, без этого выдается странное сообщение об ошибке. Странное оно потому, что, если использовать вместо print любую собственноручно написанную функцию, программа прекрасно работает и без scalar. Это — одна из загадок Perl, которая, похоже, лишний раз свидетельствует: print — не совсем функция.

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

Прием с созданием «хэшемассива» оказался настолько удобным, что я до сих пор его использую. Для упрощения работы даже была написана специальная функция eachEx, работающая как each для ссылок на хэши, и как цикл перебора упорядоченных элементов — для «хэшемассивов». Ее можно применять вот в таком контексте:

Листинг 17
# Пусть $rH - ссылка на хэш или на
# "хэшемассив", заранее неизвестно.
# Тогда следующий код перебирает 
# пары ключ=>значение в хэше или
# "хэшемассиве", причем для последнего -
# в порядке их "настоящей" очередности
while(my ($k,$v)=eachEx($rH)) {
    print "$k => $v\n";
}

Получается, что мы используем дополнительные преимущества «хэшемассива», если работа ведется именно с ним, а для обычного хэша... Что же, хоть как-то его перебрать — и то счастье. Вот код функции eachEx. Я пытался максимально оптимизировать ее быстродействие для самых распространенных случаев (хэш и «хэшемассив»).

Листинг 18
# ($k,$v) eachEx(\@%Hash)
# Если этой функции передана ссылка на 
# хэш, то ее вызов эквивалентен вызову each().
# Если же передана ссылка на массив, то 
# осуществляется перебор всех пар в массиве.
sub eachEx($)
{   # Если ссылка на хэш, работаем максимально быстро   
    if(ref($_[0]) eq "HASH") { return each(%{$_[0]}) }
    # Иначе анализируем ситуацию.
    my ($r)=@_;
    if(ref($r) eq "ARRAY" || index("$r","=ARRAY")>=0) {
        my $i=$eachExBuffer{$r};
        if(($$i||=0)>scalar(@$r)-1) { 
            $$i=0; return () 
        }
        return ($$r[$$i++],$$r[$$i++]);
    }
    # Может, это bless-хэш...
    if(index("$r","=HASH")) { return each(%{$_[0]}) }
    # Все совсем плохо.
    require Carp; 
    Carp::croak("Argument must be an array or hash reference");
}

Многоуровневые ссылки

Теперь, вооруженные систематизированным аппаратом разыменования, вы без труда сможете работать даже со ссылками на ссылки. Нужно заметить, что необходимость в двойных (тройных и т. д.) косвенных ссылках возникает крайне редко, обычно хватает и одного уровня вложенности. Однако, чтобы поставить точку в вопросе разыменования, приведу примеры кода.

Лирическое отступление 
Следует помнить, что ссылка — это обычный скаляр, а значит, ссылка на ссылку на массив вначале разыменовывается по правилу скаляра, а уж затем — по правилу массива. Если глубина вложенности еще больше, то скалярное разыменование производится несколько раз.

Листинг 19
# создаем ссылку на ссылку на число
$tS = \\10;
# выводим это число
print ${${$tS}}."\n";
# или короче:
print $$$tS."\n";
# ссылка на ссылку на ссылку на массив
$tA = \\[10,20];
# выводим первый элемент массива
print ${${$tA}}->[1]."\n";
# или короче:
print $$$tA->[1]."\n";

Но, в общем-то, это все экзотика.

Альтернативные способы разыменования

До этого я описывал приемы, которые работают всегда и везде. Если вы сомневаетесь, как лучше написать, то пишите по правилам — и программа будет работать. Однако иногда все-таки хочется сократить письмо там где это возможно, поэтому Perl предлагает нам несколько способов упрощения процесса разыменования.

С самым простым из наипростейших упрощений мы уже сталкивались — это возможность опустить фигурные скобки, если в них стоит обычная скалярная переменная. Итак,

Листинг 20
# этот код:
@a = @{$r}; %a = %{$r};
# работает точно так же, как:
@a = @$r;   %a = %$r;

Второе упрощение — можно опускать -> (стрелочку) между парами открывающих и закрывающих скобок:

Листинг 21
# можно писать и так...
$a = $A->[10]->{abc}->[0]->{d};
# но гораздо короче - так:
$a = $A->[10]{abc}[0]{d};

Заметьте, что стрелочку в самом начале мы пропустить не можем — она говорит интерпретатору о том, что в действительности работа идет с $A, а не с @A.

Третье упрощение позволяет избавиться от ведущей стрелочки за счет еще одного доллара:

Листинг 22
# Билл Гейтс в детстве писал так:
$a = $A->[10]{abc}[0]{d};
# но Ларри Уолл - иногда и так:
$a = $$A[10]{abc}[0]{d};
# а если со скобками - то так:
$a = $${A[10]{abc}[0]{d}};

Лирическое отступление 
Последний способ, хотя и экономит один символ (стрелочка занимает 2 позиции, а доллар — всего одну), выглядит довольно диким исключением из общего правила. Честно говоря, я до сих пор не понимаю, почему он вообще работает (особенно в последнем варианте). Видимо, конструкция $${...} воспринимается как неделимый оператор — вроде «разыменовать дважды».

Наконец, стоит сказать еще о возможности разыменования ссылки, которую вернула функция. Делается это обычным способом:

Листинг 23
# функция возвращает ссылку на массив
sub func { return [1,$_[0],3] }
# обращаемся к нулевому элементу
# (вызываем без параметров)
$a = func->[1];
# обращаемся к первому элементу
$a = func(8)->[1];

В первом случае скобки у функции можно пропустить, во втором — ни в коем случае.

Автосоздание ссылок

До сих пор мы присваивали значения ссылкам явно, однако Perl настолько услужлив, что временами делает это самостоятельно. Давайте рассмотрим такой пример кода:

Листинг 24
# вначале $r не определена
print $r; # выводит, что $r не определена
# теперь работаем, как будто в $r
# содержится ссылка на массив
@$r = (1,2,3);
# выводим $r вновь
print $r; 
# и получаем ARRAY(0x1abf008), то есть,
# ссылка создалась сама собой!

Лирическое отступление 
Когда ссылка преобразуется в строку, как раз и получается что-то типа ARRAY(0x1abf008). Конечно, адрес каждый раз может быть разным, и сейчас он приведен здесь только для примера.

Таким образом, попытка разыменовать скаляр с undef-ссылкой в левой части оператора присваивания всегда ведет к созданию нового объекта, ссылка на которой и присваивается переменной.

Давайте теперь побезумствуем и напишем такой код:

Листинг 25
# работаем, как будто в $r содержится 
# ссылка на ссылку на ссылку на массив
@$$$r = (1,2,3);
# выводим $r по всякому
print $$$r; # получаем ARRAY(0x1abf0c8)
print $$r;  # SCALAR(0x1abf0bc)
print $r;   # SCALAR(0x1abf008)

Мы убедились в одной интересной вещи: автосоздание ссылок работает на любом уровне вложенности, хоть даже пятидесятикратном (напишите 50 долларов в этом примере и проверьте сами). Представляете, как пришлось помучаться Ларри Уоллу, чтобы добиться такого эффекта?..

Хотя автосоздание — это обычно «есть гут», но случается, что оно мешает:

Листинг 26
# создаем хэш ссылок на хэши
%H = (
    a => { x=>1 },
    b => { x=>2 },
);
# всего-то - проверили существование...
print "ya-ya!" if exists $H{c}{x};
# а теперь вывели ключи хэша через запятую
print join ",", keys %H;

Несмотря на то, что мы ничего в коде не присваивали, в хэше %H после невинной проверки вдруг появится новый ключ — c, и соответствующее ему значение — ссылка на пустой хэш. Конечно, мы не ожидали такого побочного эффекта. А возник он потому, что, заметив выражение $H{c}{x}, Perl запустил процесс автосоздания хэш-ссылки, чтобы затем проверить, есть в нем элемент x или нет. Чтобы этого избежать, придется написать:

Листинг 27
# вот так правильно
print "ya-ya!" 
    if exists $H{c} && exists $H{c}{x};

Все это чревато лишь созданием лишних ключей в массиве, что само по себе не так страшно.

Какие же цели преследовал Ларри Уолл, когда разрабатывал механизм автосоздания ссылок?.. Нетрудно догадаться: он бежал от неудобного формализма языков со строгой типизацией (типа Java), чтобы можно было писать вот так:

Листинг 28
# вначале $A не определена
# присваиваем, попутно создавая ссылки
$A[10]{a}[20]{b} = 100;
# а теперь выводим 100
print $A[10]{a}[20]{b};

Этот код выполняется без единого предупреждения и уж тем более без ошибок. В общем-то, в точности, как мы и предполагали: если мы присвоили чему-то 100, а затем сразу вывели это «что-то», то и получить должны 100. Теперь представьте, во что бы это все вылилось, если бы нас обязали создавать все промежуточные ссылки...

Общая Теория Всего

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

Листинг 29
# первый способ
ССЫЛКА->/параметры\;
# второй способ
!{ССЫЛКА};

Здесь метасимволы !/\ — это некоторые значки, характерные для данного типа объекта, причем / и \ — парные скобки (квадратные, круглые и фигурные). Речь сейчас пойдет о втором способе разыменования, когда мы получаем прямой доступ к объекту по его ссылке.

Что будет, если сделать еретический шаг и вместо выражения ССЫЛКА подставить... строку? Например:

Листинг 30
# что же выведется?..
print join ",", @{"INC"};

Оказывается, в этом случае происходит весьма любопытная вещь: Perl обращается к глобальной переменной, чье имя указано в строке. Это правило действует для скаляров, хэшей, массивов, функций и тип-глобов — одним словом, для всего. В такой строке можно даже указывать имя пакета, если переменная расположена в другом модуле.

Пусть, например, нам необходимо обратиться к переменной, имя которой содержится в $v, расположенной в пакете $p. В свете Общей Теории Всего это будет выглядеть весьма просто:

Листинг 31
# $v - содержит имя переменной
# $p - содержит имя пакета
# Выводим содержимое указанной 
# переменной из указанного пакета.
print ${$p."::".$v};
# заметьте, что нельзя написать
#   ${"$p::$v"}
# потому что символ :: управляющий.
# Зато можно его экранировать:
#   ${"$p\::$v"}

Конечно, можно было бы написать и @{$p."::".$v} или &{$p."::".$v}(10) для вызова функции. Это открывает перед программистом весьма впечатляющие возможности — можно управлять любой переменной в любой части программы. Более того, любое привычное нам обращение вида $A или %A с точки зрения Perl выглядит как ${"A"} и %{"A"}. Стоит ли теперь удивляться, что пример выше и правда работает?..

Лирическое отступление 
Напоследок и в качестве отступления: все глобальные переменные в действительности хранятся в хэше со странным именем %пакет:: (например, %main::). Получив ключи этого хэша (keys %main::), вы можете определить, какие переменные в настоящий момент существуют. О том, где хранится сам этот хэш, наука умалчивает (но доступен он отовсюду).

 
Рекламный блок
   

На странице:
    22. Разыменование ссылок в Perl,  или просто о сложном
Разыменование ссылки на массив
Разыменование ссылки на хэш
Разыменование ссылки на скаляр
Разыменование ссылки на функцию
«Хэшемассивы»
Многоуровневые ссылки
Альтернативные способы разыменования
Автосоздание ссылок
Общая Теория Всего

Важное объявление:
    автор категорически против копирования и распространения в Интернете всех статей «Куроводства» с возрастом, меньшим 6 месяцев. Печальный опыт «расползания» чрезвычайно устаревших ошибочных версий статьи про Apache действительно объясняет такое решение.

Орфография на «Куроводстве»:
    если вы заметили орфографическую, стилистическую или другую ошибку на этой странице, просто выделите ошибку мышью и нажмите Ctrl+Enter. Выделенный текст будет немедленно отослан вебмастеру, а Вы даже ничего и не заметите — настолько быстро все произойдет.

На заметку:
    если вы уже вскипели насчет дизайна этой страницы, то присмотритесь повнимательнее к названию, почитайте FAQ, сходите по лебедевским местам, как это уже предлагалось выше. Можно ли считать пародию плагиатом? Надеюсь, что нет.

Параметры этой страницы
   
GZip

Ссылки от спонсоров
   


Дмитрий Котеров | 5 апреля 2002 г. ©1999-2017 | Генеральный спонсор: Хостинг «Джино» | Контакт Вернуться к оглавлению