Внимание! Прочитайте, пожалуйста, текст в правой колонке (внизу).
Внимание! Прочитайте, пожалуйста, текст в правой колонке (внизу). Внимание! Прочитайте, пожалуйста, текст в правой колонке (внизу). 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-2016 | Генеральный спонсор: Хостинг «Джино» | Контакт Вернуться к оглавлению