facebook LinkedIn
Google Book Search
Прегледи

Увод в програмирането на Perl Още управляващи конструкции

от Курс за ССОК

Увод в програмирането на Perl
Лекция #5 Още управляващи конструкции

В тази лекция ще завършим дискусията за управляващите конструкции в Perl. Ще разгледаме конструкцията за повторение foreach и ще я сравним с конструкцията for. Ще разгледаме и логическите оператори в Perl и ще обсъдим как могат да бъдат използвани те за контролиране на потока на изпълнение на програмите.

Съдържание

Структура за повторение foreach

Структурата за повторение foreach позволява на програмиста да обхожда даден списък, достъпвайки и манипулирайки всеки елемент в него. Предимството на структурата е че не се налага използването на отделна променлива брояч за нейната употреба. Така тя на практика елиминира грешките от типа разлика с единица(off-by-one), които са често срещани при използването на брояч за обхождането на елементите в масив.

Пример:

#!/usr/bin/perl -w

@array = (1 .. 10);

foreach $number (@array) {
   $number **= 2;
}

print "@array\n";

Тази проста програма използва foreach за да повдигне елементите в един масив на квадрат. При всяко изпълнение на цикъла на променливата $number се присвоява стойността на поредния елемент от масива като се започва от първия. После в тялото на цикъла тази стойност се повдига на втора степен. Цикъла се изпълнява всички елементи в масива не бъдат обработени. Важно е да се отбележи че $number във всеки един момент представлява конкретна стойност в масива @array и промяната на стойността в $number променя и съответния елемент от @array. Това демонстрираме като отпечатваме стойността на @array в края на програмата.

Общия формат на foreach е следния:

foreach controlVariable (list) {
   statements
}

А сега нека сравним for и foreach със следния пример:

#!/usr/bin/perl

foreach (1..10) {
   print "*";
}

print "\n";

foreach $letter ('A' .. 'G') {
   print "$letter";
}

print "\n";

for $letter ('A' .. 'G') {
   print "$letter";
}

print "\n";

for ($number = 0; $number <= 20; $number += 5) {
   print "$number ";
}

print "\n";

foreach ($number = 0; $number <= 20; $number += 5) {
   print "$number ";
}

print "\n";

Примерът показва че ключовите думи for и foreach са на практика взаимно-заменяеми. За да се избегне объркване, обаче, се препоръчва for да бъде използвана само за цикли с брояч, а foreach само за обхождане на списък със стойности.

Специална променлива $_

Езикът Perl дефинира набор от специални променливи, които извършват множество функции. Тези променливи са създадени с цел да улеснят програмиста на Perl и често се изпозлват като стойности по подразбиране. Например специалната променлива $_ е аргумент по подразбиране на много функции в Perl и също е управляващата променлива по подразбиране на много управляващи конструкции в Perl, включително foreach. С други думи $_ ще заеме мястото на липсващ параметър в извикване на функция или липсваща управляваща променлива в структура за повторение foreach.

Например това е коректна употреба на foreach:

foreach (list) {
   statements
}

Пример:

#!/usr/bin/perl -w

foreach $name ('Amanda ', 'Jeff ', 'Sarah ', 'David ') {
   print "$name";
}

print "\n";

foreach ('Amanda ', 'Jeff ', 'Sarah ', 'David ') {
   print "$_";
}

print "\n";

foreach ('Amanda ', 'Jeff ', 'Sarah ', 'David ') {
   print;
}

print "\n";

Управление на цикли с grep и map

Някои операции се изпълняват толкова често при обхождането на елементите в списък, че Perl предлага вградени функции за улеснение на тези операции. Тези функции са преки пътища, които позволяват на програмиста да улесни структурата и да намали размера на програмите си. Това пък от своя страна улеснява разчитането на програмите, които е от изключително голямо значение при работа над голям проект.

Функциите grep и map представляват капсулирани форми на често срещани операции при обхождането на списък. Следвайки основите на конструкцията foreach и двете обхождат списък и присвояват всяка стойност в него на променливата $_.

Функцията grep е кръстена на програмата в Unix със същото име, която търси текст във файл. Подобно функцията grep в Perl претърсва списък и създава нов съдържащ само елементи отговарящи на определено условие. Функцията grep приема като параметри израз условие и списък:

newList = grep( conditionalExpression, list);

Функцията grep на практика представлява по-кратка форма на:

foreach (list) {
   push(newList, $_) if conditionalExpression;
}

Функцията map позволява на програмиста да създаде списък отразяващ прилагането на функция върху всеки елемент от даден списък. Функцията map се извиква със два аргумента както следва:

newList = map(function, list);

Функцията map на практика е еквивалентна на:

foreach (list) {
   push(newList, function applied to $_);
}

Важно е да се отбележи че map може да промени стойностите в оригиналния списък, ако в кода някъде се модифицира стойността на $_;

Функциите grep и map са оптимизирани да изпълняват техните задачи по-ефективно от еквивалентните им конструкции за повторение имплементирани от програмиста.

Пример:

#!/usr/bin/perl -w

@numbers = (1 .. 10);
print "\@numbers: @numbers\n\n";

foreach (@numbers) {
   push(@smallNumbers, $_) if $_ < 6;
}

print "Numbers less than 6 are:\n",
"foreach: @smallNumbers\n";

@smallNumbers2 = grep($_ < 6, @numbers);
print "grep: @smallNumbers2\n\n";

foreach (@numbers) {
   push (@doubledNumbers, $_ * 2);
}

print "Double each number:\n",
"foreach: @doubledNumbers\n";

@doubledNumbers2 = map($_ * 2, @numbers);
print "map: @doubledNumbers2\n";

Управление на цикли – next, last и redo

Твърдението next

Твърдението next изпълнено в конструкция за повторение while, until, for и foreach пропуска останалите твърдения в тялото на конструкцията и изпълнява следващото повторение на цикъла. В while и until условието за продължаване на цикъла се проверява веднага след next, в конструкцията for първо се изпълнява израза за инкрементация на управляващата променлива и след това се тества условието за продължаване на цикъла, а в конструкцията foreach управляващата променлива приема стойността на следващия елемент в списъка.

В предишната лекция споменахме че конструкцията while може да бъде използвана в повечето случаи за представяне на конструкцията for с едно изключение. Изключението се случва, когато изразът за инкрементация в while е след next. В такъв случай той не се изпълнява и цикъла while не функционира като цикъла for. Ако цикъла не инкрементира автоматични управляващата си променлива(като при for и foreach) допълнителни мерки трябва да бъдат взети за предотвратяване на безкрайни цикли.

Пример:

#!/usr/bin/perl -w

foreach (1 .. 10) {
   if ( $_ == 5 ) {
      $skipped = $_;
      next;
   }
   print "$_ ";
}

print "\n\nUsed 'next' to skip the value $skipped.\n";

Твърдението last

Твърдението last изпълнено в while, until, for или foreach конструкция за повторение предизвиква незабавен изход от тази конструкция. Изпълнението на програмата продължава веднага след конструкцията. Твърдението last е обичайната практика за преждевременен изход от цикъл.


Пример:

#!/usr/bin/perl - w

foreach (1 .. 10) {
   if ( $_ == 5) {
      $number = $_;
      last;
   }
   print "$_ ";
}

print "\n\nUsed 'last' to terminate loop at $number.\n";

Твърдението redo

Твърдението redo изпълнено в while, until, for или foreach конструкция връща изпълнението при първото твърдение в тялото на конструкцията без да изчислява условието за продължаване на цикъла. Командата е полезна, когато е необходимо да се повтори определена итерация на цикъл например поради въведен невалиден вход.

Пример:

#!/usr/bin/perl -w

$number = 1;

while ( $number <= 5 ) {
   if ( $number <= 10 ) {
      print "$number ";
      ++$number;
      redo;
   }
}

print "\nStopped when \$number became $number.\n";

Етикети на блокове

Всеки цикъл освен do/while и do/until може да има етикет. Този етикет, освен че играе ролята на описание на блока от код, също така е и цел за командите за управление на цикли next, last и redo. Тези команди могат да приемат етикет на блок като параметър за да се отнесат към специфичен блок от код. Когато тези команди са използвани без параметри те оперират върху цикъла в който се появяват.

Добра практика е да се използват за имена на етикети само големи букви. Тази практика предотвратява конфликтите между имена на етикети и ключови думи в Perl и увеличава четливостта на програмите.

Пример:

#!/usr/bin/perl -w

OUTER: foreach $row (1 .. 10) {
   INNER: foreach $column (1 .. 10) {
      if ( $row < $column ) {
         print "\n";
         next OUTER;
      }
      print "$column ";
   }
}

Голи блокове

Гол блок е блок от код заграден от фигурни скоби, с или без етикет, но без придружаваща ключова дума за управляваща конструкция. Такъв блок може да бъде използван с управляващите твърдения за цикли last и redo. Забележете че next е еквивалентен на redo в контекста на голия блок.

Пример:

#!/usr/bin/perl -w

print "Enter your guess(1 - 3): ";
$guess = <STDIN>;

BLOCK: {
   if ($guess == 1) {
      print "Right!\n";
      last BLOCK;
   }

   if ($guess == 2) {
      print "Close!\n";
      last BLOCK;
   }

   if ($guess == 3) {
      print "Wrong!\n";
      last BLOCK;
   }

   {
      print "Please re-enter your guess(1 - 3): ";
      $guess = <STDIN>;
      redo BLOCK;
    }
}

Логически оператори

Досега в програмите, които разглеждахме използвахме само просто условия. Perl предоставя логически оператори, с които могат са се формират комплексни условия формирани от прости. Логическите оператори са &&(логическо И), ||(логическо ИЛИ) и !(логическо НЕ, наричано още логическо отрицание).

Например ако искаме да сме сигурни че две условия са верни преди да изберем определена пътека на изпълнение можем да използваме логическия оператор &&:

if ( $name eq 'Ivan' && $age == 21 ) {
   print "We found one 21 year old Ivan.\n";
}

Простото условие вляво се тества първо. Забележете, че тъй като приоритета на eq е по-висок от този на && няма нужда от скоби. Ако условието от ляво не е изпълнено, условието от дясно въобще не се изчислява. Това е известно като short-circuit изчисление и заради него е добра практика условието, което е по-вероятно да е невярно да е отляво(в случая за логическо И, за логическо ИЛИ е добре условието, което е по-вероятно да е истина да е отляво) за да се повиши бързодействието на програмата.

Логическо И се изчислява до истина(true), ако всички условия, които го съставят са истина, а логическото ИЛИ, ако поне от всички условия е истина. Логическо НЕ обръща логическата истина(true) в неистина(false) и неистината в истина. Действието на логическите оператори често се илюстрира с таблици за истинност.

Perl предлага три допълнителни логически оператора – and, or и not. Тяхната единствена разлика с представените досега логически оператори е че and, or и not имат по-нисък приоритет от всички останали оператори в Perl. Този факт позволява тези оператори да участват в комплексни условия без нужда от употреба на скоби, които да налагат реда на изчисление.

Пример:

#!/usr/bin/perl -w

print "Please enter a numerator: ";
chomp($numerator = <STDIN>);

{
   print "Please enter a denominator: ";
   chomp($denominator = <STDIN>);
   $denominator != 0 or print "Cannot divide by zero\n" and redo;
}

print "\nThe result is ", $numerator / $denominator, "\n";

Функции за грешки: die и warn

Има моменти, когато някои програмни елементи са критични за да продължи нормалното изпълнение на програмата. Ако тези елементи не са обработени или въведени коректно програмата не бива да продължи. В такива ситуации програмиста може да поиска да изпрати съобщение показващо какво се е объркало в програмата.

Функцията die позволява на програмиста да терминира програмното изпълнение и да отпечета съобщение, като по този начин спомага за откриването на грешки и за получаването на по-четлив код. Вместо да пишете:

if (essential condition) {
   rest of program
} else {
   print "Error: Essential condition not met\n";
}

може да използвате die:

unless (essential condition) {
   die "Error: Essential condition not met\n";
}

Може да напишем това по още един начин:

essential condition or die "Error: Essential condition not met\n";

Пример:

#!/usr/bin/perl -w

print "Enter a numerator: ";
chomp($numerator = <STDIN>);

print "Enter a denominator: ";
chomp($denominator = <STDIN>);

$denominator != 0 or die "Cannot divide by zero\n";

print "\nThe result is ", $numerator / $denominator, "\n";

За удобство Perl съхранява най-скорошното съобщение за системна грешка в променливата $!.

Функцията warn произвежда същия изход като die с разликата че изпълнението на програмата продължава. Тази функция предоставя възможност да се предупредят потребителите за не фатални грешки в програма.

Задачи

Задача 1 Да се напише програма, която изчислява факториела на дадено положително число, прието на входа (като се знае, че 0! = 1).

Задача 2 Питагорова тройка се наричат целочислените стойности на трите страни на правоъгълен триъгълник. Да се намерят всички питагорови тройки, при които нито една от страните на триъгълника не надвишава 500.

Задача 3 Да се напише програма, която има следния изход:

*          ********** **********          *
**         *********   *********         **
***        ********     ********        ***
****       *******       *******       ****
*****      ******         ******      *****
******     *****           *****     ******
*******    ****             ****    *******
********   ***               ***   ********
*********  **                 **  *********
********** *                   * **********

Може да се ползва само един foreach със само един print в него, който извежда един ред.

Решения

Локални линукс групи RSS
Дискусии