Роботландский Университет © А.А.Дуванов

ЗНАКОМСТВО С ЯЗЫКОМ

i | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | ?
урок 4 | зачет | задания | дополнения | урок 6

урок 5: функции

В программировании часто бывает нужно одну и ту же группу команд повторять несколько раз. Если повторение происходит “на месте” — используют цикл (while, for). Если код нужно повторять в разных местах программы — его оформляют в виде функции.

описание функции и ее вызов

В самом деле, если уж нужно вычислить длину (количество цифр) целого положительного числа, то логичнее проделать это один раз, а затем спокойно писать как в математике F(num), не задумываясь над “анатомией” функции F и не повторяя ее кода заново. Пусть, например, пользователь последовательно вводит три числа, а программа вычисляет общее количество цифр:

<SCRIPT language=JavaScript>
<!--
  // Количество цифр в целом положительном числе num.
  // Вход:  num (целое положительное число).
  // Выход: количество цифр в num.
  function F(num)
  {
    var len = 0;
    if (num < 10) len = 1;
    else
      while(num)
      {
        num = (num - num%10)/10;
        len++;
      }
    return len;
  }

  var sum  = 0;
  var num1, num2, num3;

  num1 = prompt("Введите первое число", "");
  if (num1) sum += F(num1);

  num2 = prompt("Введите второе число", "");
  if (num2) sum += F(num2);

  num3 = prompt("Введите третье число", "");
  if (num3) sum += F(num3);

  alert("Общее число введенных цифр: " + sum);
//-->
</SCRIPT>

Браузер выполняет этот скрипт так. Запись:

function F(num)
{
  var len = 0;
  if (num < 10) len = 1;
  else
    while(num)
    {
      num = (num - num%10)/10;
      len++;
    }
  return len;
}

принимается во внимание, но команды в ней не выполняются. Браузер делает пометку: “было описание функции с именем F”.

Так происходит потому, что ключевое слово function и следующий за ним блок {...} являются не командой, а декларацией (описанием).

Команды, следующие за описанием функции, выполняются как обычно. При этом каждый раз, когда в коде попадается обращение к F, браузер возвращается к описанию функции и выполняет ее команды, подставляя вместо формального аргумента num фактические переменные num1, num2, num3.

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

num=num1; — при обращении F(num1);
num=num2; — при обращении F(num2);
num=num3; — при обращении F(num3).

Посмотреть код этой программы без использования функции F можно здесь.

В общем случае описание функции на языке JavaScript выглядит так:

function ИмяФункции (список формальных аргументов через запятые)
{
  ...
  тело функции
  ...
  return значение;
}

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

Команда вызова функции имеет вид:

ИмяФункции(список фактических аргументов через запятые)

Фактическим аргументом функции может быть константа, переменная, выражение и, в частности, вызов другой функции.

Имена в программировании играют значительную роль. Они позволяют сделать код более понятным и, наоборот, способны запутать читателя, если выбраны как попало.

Имя F нельзя считать хорошим для описанной выше функции. В примере это имя использовано для “смягчения” перехода от математики к программированию. Ведь в математике функции обозначаются, как правило, одной буквой. Для программирования однобуквенное имя — редкое исключение. По одной букве трудно догадаться о назначении функции. В нашем случае имя LenOfNumber (длина числа) вместо F было бы гораздо лучше.

Длина имен функций и переменных в JavaScript не ограничивается, но имя должно быть одним словом (без пробелов). Имя строится из латинских букв, арабских цифр, знаков подчеркивания и некоторых других (не рекомендуемых) специальных знаков. Русские буквы использовать нельзя. Первым символом имени должна быть латинская буква или знак подчеркивания. Регистр букв имеет значение. Например, имена “LenOfNumber” и “LenOfnumber” считаются разными.

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

проектирование “сверху вниз”

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

В программировании популярны наборы библиотечных функций. Ввод и вывод данных, математические вычисления, создание интерфейсных окон, обработка мышиных щелчков и перемещений — все это требуется практически в любой прикладной программе. Ясно, что есть смысл хорошо запрограммировать эти функции один раз. Так и делают. Некоторые наиболее популярные функции становятся даже элементами систем программирования (как функции alert и prompt — они закодированы в самом браузере).

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

Такой способ проектирования называется “нисходящей технологией” или методом “сверху вниз”. Он следует за особенностью человеческого мышления: мы не можем продумать проект сразу во всех тонкостях и деталях.

задача

Написать программу, которая формирует HTML-код клетчатого поля, в котором чередуются темные и светлые клетки:

решение методом “сверху вниз”

Построим изображение поля при помощи HTML-таблицы. Код таблицы будем формировать как значение строковой переменной.

этап 1

Сначала опишем функцию, которая должна решать поставленную задачу целиком.

// Функция field(row, column, bcolor, wcolor)
// возвращает HTML-код таблицы, изображающей
// клетчатое поле.
// Аргументы:
//             row     - число строк
//             column  - число столбцов
//             bcolor  - цвет темной клетки
//             wcolor  - цвет светлой клетки
function field(row, column, bcolor, wcolor)
{
  // Формируем код начала таблицы.
  var str = "<TABLE border=1 cellspacing=0 cellpadding=10>";

  // Добавляем коды табличных строк.
  str += lines(row, column, bcolor, wcolor);

  // Добавляем код конца таблицы.
  str += "</TABLE>";

  return str;
}
этап 2

Подумаем о том, как должен быть устроен формирователь табличных строк.

// Формирователь табличных строк.
// Функция lines(row, column, bcolor, wcolor)
// возвращает HTML-код всех табличных строк.
// Аргументы:
//             row     - число строк
//             column  - число столбцов
//             bcolor  - цвет темной клетки
//             wcolor  - цвет светлой клетки
function lines(row, column, bcolor, wcolor)
{
  var str     = ""; // Накопитель кода.
  var numpair = Math.floor(row/2); // Число пар строк.

  // Строим коды строк таблицы парами.
  for(var i = 0; i < numpair; i++)
  {
    // Формируем код нечетной табличной строки.
    str += lineOdd(column, bcolor, wcolor);
    // Формируем код четной табличной строки.
    str += lineEven(column, bcolor, wcolor);
  }

  // Строим дополнительно "нечетную" строку,
  // если число строк нечетно.
  if(row%2) str += lineOdd(column, bcolor, wcolor);

  return str;
}
этап 3

Теперь можно заняться проектированием функций lineOdd и lineEven, которые строят соответственно нечетную и четную табличные строки. Функция lineOdd должна начинать строку с темной клетки, а функция lineEven — со светлой.

// Функция lineOdd(column, bcolor, wcolor)
// возвращает HTML-код нечетной строки таблицы.
// Аргументы:
//               column - число ячеек в строке
//               bcolor - цвет темной клетки
//               wcolor - цвет светлой клетки
function lineOdd(column, bcolor, wcolor)
{
  // Формируем код начала табличной строки.
  var str = "<TR>";
  var numpair = Math.floor(column/2); // Число пар клеток.

  // Формируем коды клеток парами.
  for(var i = 0; i < numpair; i++)
  {
    // Строим код нечетной табличной клетки.
    str += cell(bcolor);
    // Строим код четной табличной клетки.
    str += cell(wcolor);
  }

  // Строим дополнительную клетку, если число клеток нечетно.
  if(column%2) str +=cell(bcolor);

  // Формируем код конца табличной строки.
  str += "</TR>";

  return str;
}

// Функция lineEven(column, bcolor, wcolor)
// возвращает HTML-код четной строки таблицы.
// Аргументы:
//               column - число ячеек в строке
//               bcolor - цвет темной клетки
//               wcolor - цвет светлой клетки
function lineEven(column, bcolor, wcolor)
{
  // Формируем код начала табличной строки.
  var str = "<TR>";
  var numpair = Math.floor(column/2); // Число пар клеток.

  // Формируем коды клеток таблицы парами.
  for(var i = 0; i < numpair; i++)
  {
    // Строим код нечетной табличной клетки.
    str += cell(wcolor);
    // Строим код четной табличной клетки.
    str += cell(bcolor);
  }
  // Строим дополнительную клетку, если число клеток нечетно.
  if(column%2) str +=cell(wcolor);

  // Формируем код конца табличной строки.
  str += "</TR>";

  return str;
}
этап 4

Остается только описать функцию cell, которая строит HTML-код отдельной табличной клетки.

// Функция cell(color)
// возвращает HTML-код табличной клетки.
// Аргументы:
//            color - цвет клетки
function cell(color)
{
  return "<TD bgcolor="+color+"> </TD>";
}

Замечание. Функции lineOdd и lineEven, формирующие код цепочки табличных клеток, легко заменяются одной универсальной функцией line. Читателю предлагается получить удовольствие от такого улучшения кода в рамках первой задачи блока заданий урока. Задачи 2, 3 и 4 также посвящены разным модификациям этого упражнения.

Построенная программа описывается иерархической схемой, которая соответствует этапам нисходящей разработки:

Решение построено, и его можно использовать. Например, вызов

var kodField = field(3, 2, "black", "white");

запишет в переменную kodField HTML-код такой таблицы:

Построенный код будет записан в одну строку и без лишних пробелов, но браузер, конечно, на это не обидится!

Остается интересным вопрос: а как показать изображение, соответствующее этому коду на гипертекстовой странице?

Как будет показано в уроке 7, это можно сделать при помощи функции document.write (метод объекта document).

Если описанный выше скрипт записан в файле field.js, то изображение двух таблиц на экране можно получить при помощи такого HTML-документа:

оборонительное программирование

Любая система, рассчитанная на реальную эксплуатацию должна иметь защиту от “дурака”. Этим обидным словом программисты обозначают потребителей своей продукции. На самом деле, употребляется это слово в шутку. Пользователь имеет право на любую ошибку, и про грамма обязана сохранять работоспособность и правильно реагировать на любые действия потребителя, даже на такие, которые не предполагались постановкой решаемой задачи.

угадайка

Петр Мячиков с удовольствием запрограммировал Угадайку (смотрите урок 4) и принес показать работу Ивану Сидорову.

— А заботится ли твоя программа о правильности данных, которые ввел пользователь? — спросил его Сидоров.

Загрузили программу в браузер и стали проверять.

— Первое, что может сделать пользователь, — сказал Сидоров, — это нажать Enter, не введя никакого числа. Как реагирует программа?

— Угадайка “говорит”, что теперь число находится в интервале [,50]. Странное заявление! Так кто здесь больше не прав? Пользователь или программист?

Запуск Угадайки (досрочный выход из игры — Esc)

Можно ввести оборонительную проверку на ввод пустой строки:

if(answer == "") alert ("Ошибка при вводе числа");

Понятно, что пользователь может также:

Все это надо проверить и не пропустить неверные данные в алгоритм, который их обрабатывает.

Напишем специальную функцию для проверки правильности числа, полученного от пользователя:

// Проверка num на целое число из [a,b].
// Возвращает: true  - num хорошее
//             false - num плохое
function IsNum(num, a, b)
{
  var ret = true; // возвращаемое значение
  // проверка на пустую строку
  if (num == "") ret = false;
  else // проверка на цифры
    for(var i=0; i<num.length; i++)
      if(num.charAt(i) < "0" || num.charAt(i) > "9")
         {ret = false; break;}
  // проверка на интервал
  ret = ret && (a <= parseInt(num,10) && parseInt(num,10) <= b);
  return ret;
}

Запуск исправленной Угадайки (досрочный выход из игры — Esc)

Программа исправленной Угадайки

Замечание. Стандартная функция parseInt(num,10) преобразует явным образом значение переменной num в десятичное число. Второй аргумент этой функции указывает основание системы счисления: 2, 8, 10 или 16. Так, вызов parseInt(num,16) будет преобразовывать значение переменной num в 16-ричное число. Если функция parseInt сталкивается с неверным символом, она возвращает числовое значение, основанное на части строки до этого символа. Например, вызов parseInt("48 попугаев",10) вернет значение 48. Если неверный символ в строке самый первый — функция возвращает NaN — специальную константу (от Not a Number).

как браузер разбирается в типах переменных

Плохо разбирается, очень плохо! Не полагайтесь на автоматическое преобразование типов. Используйте функцию parseInt или аналогичную ей функцию parseFloat (преобразование в десятичную дробь).

Петр написал первую Угадайку без использования явных преобразований типа. Сначала он дал этой программе на пробу число 49. Угадайка сообщила, что теперь задуманное число находится в интервале [1,49]. Петр ввел число 222. К его удивлению Угадайка не выдала сообщения об ошибке, а вместо этого “сказала”, что, задуманное число находится в интервале [1,222]!

Ниже приводится программа Угадайки без использования явных преобразований типа. Попробуйте ввести первым числом 49, а затем 222. Что у Вас получится?

Запуск Угадайки без parseInt (досрочный выход из игры — Esc)

Программа Угадайки без parseInt

Почему так произошло? Почему число 222 было пропущено, и интервал из [1,49] превратился в [1,222]?

Дело в том, что проверка "222" < "49" привела к значению true!

Такое странное поведение браузера объясняется очень просто: он сравнивал не числа, а строки. Строка "49" действительно “больше” строки "222" в смысле лексикографического порядка.

Проверяя строки, браузер сравнивает их посимвольно. Сначала сравнивает первые символы, потом — вторые и так далее. Если при очередном сравнении символ из первой строки располагается в кодовой таблице дальше символа из второй — проверка заканчивается, и браузер считает первую строку “больше” второй.

Так, у него и получилось, что "49" > "222". Ведь символ "4" располагается в кодовой таблице “дальше” символа "2".

Ниже предлагается небольшой испытатель. Проделайте опыты по сравнению числа с числом, строки со строкой и числа со строкой.

alert(   );


Дополнительный материал

зачетный класс

В каждом задании Зачетных классов нужно записать сообщение, которое появится в окошке alert после выполнения скрипта.

Зачетный класс 1

Сначала нажмите кнопку Сброс, затем приступайте к решению.

Чтобы увидеть результат работы, нажмите кнопку Оценка.

Зачетный класс 2

Сначала нажмите кнопку Сброс, затем приступайте к решению.

Чтобы увидеть результат работы, нажмите кнопку Оценка.

задания

  1. В программе построения кода клетчатого поля замените отдельные функции для построения нечетных и четных строк на одну универсальную функцию.

  2. Напишите программу, которая строит HTML-код клетчатого поля, с указанием внутри каждой клетки ее координат в формате “строка, столбец”:



  3. Добавьте в решение предыдущей задачи стилевые указания, которые позволят выводить на экран клетки правильной квадратной формы (а не вытянутые по вертикали).

  4. Клетчатое поля без HTML-таблиц. Напишите функцию:

    // Функция field(row, column, bcolor, wcolor, size)
    // возвращает HTML-код клетчатого поля.
    // Аргументы:
    //             row     - число строк
    //             column  - число столбцов
    //             bcolor  - цвет темной клетки
    //             wcolor  - цвет светлой клетки
    //             size    - размер клетки в пикселах
    function field(row, column, bcolor, wcolor, size)
    {
      ...
    }
    

    Функция должна строить код клетчатого поля по следующей схеме:

    Границы поля задаются блоком <DIV>...</DIV> а каждая клетка — блоком <SPAN>...</SPAN>, позиционированным в нужное место родительского блока.

  5. Проверьте вашу программу обучения малышей устному счету (задание прошлого урока) на устойчивость к ошибкам пользователя. Включите в ее состав функцию проверки данных.

  6. Напишите оборонительную Угадайку.

  7. Напишите диалоговую программу, которая просит пользователя:

    Затем программа “отгадывает” задуманное число. На малышей это производит большое впечатление.

 

содержание урок 4 урок 6 письмо автору об авторах