Пишем простой калькулятор

Однажды один мой знакомый ну просто достал меня той идеей, будто я не смогу написать калькулятор на каком-нибудь языке… Но зря он так думал — ведь я смог написать калькулятор на Icon (с учетом того, что я и визуальный интерфейс выравнивал сам, а большинство кнопок просто вручную добавлял !).

Итак, как же действует калькулятор?

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

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

А теперь обработка кнопок. Проще всего обработать нажатие на числовые клавиши (а также нажатие на клавишу смены знака и на клавишу с десятичной точкой) для чего создадим процедуру, выполняющуюся после нажатия перечисленных клавиш и назовем ее button_cb_dig (и в коде для каждой кнопки в конце списка исправим процедуру вызова на button_cb_dig):

procedure button_cb_dig(vidget, value)
case vidget.id of {
   "0" : curr||:="0"
   "1" : curr||:="1"
   "2" : curr||:="2"
   "3" : curr||:="3"
   "4" : curr||:="4"
   "5" : curr||:="5"
   "6" : curr||:="6"
   "7" : curr||:="7"
   "8" : curr||:="8"
   "9" : curr||:="9"
   "." : curr||:="."
   "neg" : {
     Fg("white")
     FillRectangle(20,20,160,60)
     Fg("black")
     if curr=="" then {
       Fg("gray")
       FillRectangle(20,20,160,60)
       Fg("white")
       FillRectangle(22,22,156,56)
       Fg("black")
       fail
     }
  }
}
Fg("gray")
FillRectangle(20,20,160,60)
Fg("white")
FillRectangle(22,22,156,56)
Fg("black")
DrawString(80,70,curr)
end

Процедура работает вполне просто: накапливает разряды числа в переменной curr и по завершении накопления выдает число на «дисплей». Если потребуется изменение знака числа, то сначала происходит сравнение curr с пустой строкой, и если строка не пуста, то дальше идет ее преобразование в тип real и изменение знака и дальше процедура выводит само число на «дисплей».

Теперь сделаем обработку операций. После введения числа, нажатия кнопки с операцией — само число и операция будут помещены в список cop, а переменная curr будет установлена в изначальное положение (т.е. в пустую строку) — и за это будет отвечать процедура обработки нажатия кнопок button_cb_op.

А теперь самое веселое — после нажатия на кнопку «Calculate!» должен вычислиться результат введенного выражения.

Как это сделать? Для этого в процедуре button_cb_op создадим локальную переменную res, в которую будем помещать результат. После нажатия кнопки «Calculate!» сначала происходит сравнение размера cop с 1 и если размер меньше 1, то ничего не произойдет (так как нечего будет вычислять, ибо аргументов в этом случае либо нет, либо их недостаточно), затем нужно проверить равна ли curr пустой строке и если да, то приравняем curr к cop[1] — это необходимо для обработки операций с результатом (т.е. проведение вычислений с готовым результатом).

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

if /res then fail

но добавление нужно производить только после получения результата и помещения второго аргумента операций в список cop, т.е. после вот таких действий:

put(cop,curr)
res:=calc()

Ну, а далее все относительно просто: «дисплей» очищается и выводиться результат, после чего переменные curr и сop возвращаются в привычное состояние.

Весь код процедуры button_cb_op выглядит так:

procedure button_cb_op(vidget, value)
local res
case vidget.id of {
   "+" : {
      put(cop,curr,"+")
      curr:=""
   }
   "-" : {
      put(cop,curr,"-")
      curr:=""
   }
   "*" : {
      put(cop,curr,"*")
      curr:=""
   }
   "/" : {
      put(cop,curr,"/")
      curr:=""
   }
   "calc" : {
      if *cop

Но самое интересное это процедура calc(), которая выглядит так :

procedure calc()
case cop[2] of {
   "+" : return real(cop[1])+real(cop[3])
   "-" : return real(cop[1])-real(cop[3])
   "*" : return real(cop[1])*real(cop[3])
   "/" : {
      if cop[3]=0 then {
      Fg("red")
      DrawString(30,32,"Division by zero !!!")
      Fg("black")
      curr:=""
      cop:=list()
      }
      else return real(cop[1])/real(cop[3])
   }
}
end

Суть простая: по индексу 2 в списке cop размещена строка с операцией (т.е. строки «+», «-«, «*» и «/») и при равенстве индекса списка определенной операции, происходит возврат результата самой операции над крайними элементами списка cop (т.е. первым введенным числом и вторым введенным числом). Однако, для деления необходимо предусмотреть вывод ошибки, если второй аргумент операции будет нулевым — что в принципе и сделано в процедуре calc().

В принципе основные функции обычного калькулятора реализованы, за исключением сброса (он тут на фиг не нужен, даже в случае ошибки), однако есть простор для усовершенствования!

Спойлер: полный код калькулятора.

############################################################################
#
#	File:     calc.icn
#
#	Subject:  Simple calculator without clear
#
#	Author:   Oleg Baharew
#
#	Date:     July 28, 2012
#
############################################################################
#
#
#
############################################################################
#
#  Requires:
#
############################################################################
#
#  Links: vsetup
#
############################################################################

#  This vib interface specification is a working program that responds
#  to vidget events by printing messages.  Use a text editor to replace
#  this skeletal program with your own code.  Retain the vib section at
#  the end and use vib to make any changes to the interface.

link vsetup
global curr,cop
procedure main(args)
   local vidgets, root, paused
   cop:=list()
   curr:=""
   (WOpen ! ui_atts()) | stop("can't open window")
   vidgets := ui()				# set up vidgets
   root := vidgets["root"]
   Fg("gray")
   FillRectangle(20,20,160,60)
   Fg("white")
   FillRectangle(22,22,156,56)
   Fg("black")

   paused := 1					# flag no work to do
   repeat {
      # handle any events that are available, or
      # wait for events if there is no other work to do
      while (*Pending() > 0) | \paused do {
         ProcessEvent(root, QuitCheck)
         }
      # if  is set null, code can be added here
      # to perform useful work between checks for input
      }
end

procedure button_cb_dig(vidget, value)
case vidget.id of {
 "0" : curr||:="0"
 "1" : curr||:="1"
 "2" : curr||:="2"
 "3" : curr||:="3"
 "4" : curr||:="4"
 "5" : curr||:="5"
 "6" : curr||:="6"
 "7" : curr||:="7"
 "8" : curr||:="8"
 "9" : curr||:="9"
 "." : curr||:="."
 "neg" :  {
  Fg("white")
  FillRectangle(20,20,160,60)
  Fg("black")
  if curr=="" then {
   Fg("gray")
   FillRectangle(20,20,160,60)
   Fg("white")
   FillRectangle(22,22,156,56)
   Fg("black")
  fail
  }
}
}
Fg("gray")
   FillRectangle(20,20,160,60)
   Fg("white")
   FillRectangle(22,22,156,56)
   Fg("black")
DrawString(80,70,curr)
end

procedure button_cb_op(vidget, value)
local res
case vidget.id of {
	"+" : {
		put(cop,curr,"+")
		curr:=""
	}
	"-" : {
	        put(cop,curr,"-")
		curr:=""	
	}
	"*" : {
		put(cop,curr,"*")
		curr:=""
	}
	"/" : {
		put(cop,curr,"/")
		curr:=""
	}
 "calc" : {
   if *cop>===	modify using vib; do not remove this marker line
procedure ui_atts()
   return ["size=200,250", "bg=white" , "label=IconCalc"]
end

procedure ui(win, cbk)
return vsetup(win, cbk,
   [":Sizer:::0,0,600,401:",],
   ["1:Button:regular::20,100,30,20:1",button_cb_dig],
   ["6:Button:regular::100,130,30,20:6",button_cb_dig],
   ["9:Button:regular::100,160,30,20:9",button_cb_dig],
   [".:Button:regular::100,190,30,20:.",button_cb_dig],
   ["/:Button:regular::140,100,35,20:/",button_cb_op],
   ["*:Button:regular::140,130,35,20:*",button_cb_op],
   ["-:Button:regular::140,160,35,20:-",button_cb_op],
   ["+:Button:regular::140,190,35,20:+",button_cb_op],
   ["calc:Button:regular::20,220,155,20:calculate !",button_cb_op],
   ["4:Button:regular::20,130,30,20:4",button_cb_dig],
   ["7:Button:regular::20,160,30,20:7",button_cb_dig],
   ["neg:Button:regular::20,190,30,20:-",button_cb_dig],
   ["2:Button:regular::60,100,30,20:2",button_cb_dig],
   ["5:Button:regular::60,130,30,20:5",button_cb_dig],
   ["8:Button:regular::60,160,30,20:8",button_cb_dig],
   ["0:Button:regular::60,190,30,20:0",button_cb_dig],
   ["3:Button:regular::100,100,30,20:3",button_cb_dig],
   )
end
#===<>===	end of section maintained by vib

А теперь скриншоты этого чуда:
calc1_0_oИ скриншот с ошибкой:
calc2_0_oP.S: Естественно, кое-какую ошибку я не исправил (хотя могу это сделать) — но она не существенна, и к тому же — это своеобразная задача.
P.P.S: Ну и конечно, я делаю более навороченную версию программы, вот как она выглядит:
calc3_0_o

aquaratixc

Программист-самоучка и программист-любитель

Добавить комментарий