На главную
  Статьи
  Статьи Gentee



  Написать мне
 
 

Язык программирования Gentee.
Том 1. Программирование для начинающих

Перепечатка. Оригинал статьи опубликован по адресу http://www.gentee.ru/vol1.htm

Основные понятия

Введение

Данное пособие находится в стадии написания. Я буду стараться каждую неделю публиковать новые статьи. Ваши замечания и предложения можете присылать на info@gentee.com с темой письма Gentee.

Последнее обновление: 26.04.2006.

Что такое программа

Подобно тому, как человечество разговаривает на разных языках, мы можем общаться с компьютерем с помощью различных языков программирования. Выбор их велик, начиная от широко известных С/С++,C#,VB,Java,Pascal,PHP,Perl и заканчивая многочисленными экзотическими языками. Каждый из языков имеет свои особенности и сферы применения.Как правило, если человек освоил хотя бы один язык программирования, то изучение других языков будет проходить гораздо быстрее. Gentee по своему синтаксису и философии находится ближе к С/С++,C#,Java, хотя и не является в чистом виде объектно-ориентированным языком. Изучив Gentee, вы создадите хороший задел на будущее и, в случае необходимости сможете освоить и более сложные языки.

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

func myprog<main>{}
      

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

Для запуска программы ее надо сохранить в файл с расширение .g (например, myprog.g). В Gentee Studio запуск производится при нажатии кнопки Execute или при нажатии комбинации клавиш Ctrl-F2. Из Проводника или любой файловой оболочки программа запускается при двойном нажатии мыши или при нажатии Enter.

Наша программа ничего не делает, поэтому вы не увидите результата работы.Для вывода данных на экран можно использовать @"Строка вывода" или print("Строка вывода").

func myprog<main>
{   
     @"Hello, World!"
}
      

Если вы запустите эту программу, то вы просто не успеете прочитать текст, потому что она сразу закроется. Поставим в конце вызов функции getch(), которая ожидает нажатия клавиши от пользователя.

func myprog<main>
{   
       print( "Hello, World!" )   
       getch()
}

Откуда взялись функции print и getch? Практически любой язык, в том числе и Gentee, имеет стандартную библиотеку, которая содержит набор часто употребляемых функций, типов и объектов.

Упражнение 1. Выведите на экран любой текст по вашему желанию.

Знакомство с арифметическими выражениями

Все мы знаем, что 2 + 2 = 4, а сможет ли вычислить это компьютер.
func sum<main>
{   
        print("2 + 2 = \( 2 + 2 )\n")   
        getch()
}

Первые '2 + 2' являются частью строки, а '2 + 2' в круглых скобках уже арифметическое выражение. Примерно также мы считаем на калькуляторе. Аналогично мы можем использовать '-' для вычитания, '*' для умножения и '/' для деления двух чисел. Символ '\' внутри строки является специальным символом.

  • \(выражение) - используется для вывода результата выражения.
  • \n - перевод строки.
  • \" - кавычки внутри строки.
Для подсчета 25*(387-113)/2 мы можем написать
@"Result = \(25 * (387 - 113) / 2)\n"

Работая с выражениями вы можете столкнуться со одной странной проблемой. Например

@"\( 1/2 ) \( 2 - 3 )\n"

выведет в качестве результата '0 4294967295'. Ошибки тут никакой нет. В математике есть разные типы чисел: натуральные, целые, действительные, комплексные и т.д. В языках программирования также имеются определенные типы чисел. Рассмотрим пока только тип uint (аналог натуральных чисел), который имеют числа по умолчанию. На бумаге мы можем написать очень большое число, но компьютеру для каждого числа необходимо отвести свое место. Поэтому существуют определенные ограничения. Число типа uint может меняться от 0 до 4294967295, причем этот диапазон зациклен. '4294967295 + 1' даст снова 0, а 0 - 1 даст 4294967295. Сейчас разберемся с '1/2'. В результате арифметического выражения тип не меняется, а так как uint не может иметь дробной части, то она отбрасывается и мы в результате получаем 0.

Оформление исходных текстов

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

  • Число. Например: 25, 426, -1327. Числа вида 0xFF31, 345L, 567D, -5.32, 89E-23 будем рассматривать позднее, в процессе изучения.

  • Ключевое слово. Вы уже знаете одно ключевое слово func. Оно служит для описания функции. Ключевых слов не очень много и их легко запомнить. Например: import, operator, method, foreach, while, define.

  • Имя(идентификатор). Имена будут целиком находиться в вашей власти. Чем понятнее вы будете давать имена, тем легче будет искать ошибки и модифицировать вашу программу. Имена могут состоять из букв и цифр, но должны начинаться с буквы. Регистр символов также имеет значение. Желательно придерживаться одного стиля для имен. Вот три наиболее распространных стиля: getvalue, get_value, GetValue.

  • Операторы. Вы уже знакомы с простейшими арифметическими операторами ('+', '-', '*', '/' ). Есть также операторы сравнения ('==', '>', '<', '>=', '<='), присваивания '=' и т.п.

  • Служебные символы. К служебным символам относятся раздличные скобки, ',', ':', ';', '.'. Каждый из этих символов используется в определенных ситуациях и нельзя, например, вместо фигурных скобок использовать круглые.

Я не буду сейчас останавливаться на синтаксических правилах языка. Если вы где-то забудете поставить скобку или разместите не там запятую, то компилятор сообщит вам об ошибке с указанием строки и позиции.

Большую роль при написании программ имеет стиль форматирования исходного текста. Следующие две функции вычисляют сумму четных чисел в массиве. Они обе работоспособны и делают одно и тоже. Просто сравните два варианта.

func uint evensum( arr array )
{
   uint i result

   fornum i, *array
   {
      if !( array[i] & 1 ) 
      {
         result += array[i]
      }    
   }
   return result   
}
func uint f1( arr a )
{uint I l;fornum I, *a{if !(a[I]&1){l += 
a[I]}}return l}

Правый вариант короче, но понять его очень сложно. Есть даже специальный класс программ (обфускаторы), которые делают исходные тексты непригодными для чтения, при этом оставляя их работоспособными.

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

if !( array[i] & 1 ) { result += array[i] }

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

if !( array[i] & 1 ) : result += array[i]

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

  • Вложенные. Начинаются с '/*' и заканчиваются '*/'.
  • Строчные. Начинаются с '//' и заканчиваются концом строки
/*--------------- 
   Пример
   многострочного  
   комментария
---------------*/
   i = i + 20  // Однострочный комментарий  

Функция

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

func myfunc
func uint my( uint i j k ) // Три параметра i j k типа uint  
func sum( uint left, int right ) 

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

Если переменные или параметры одного типа, то они разделяются пробелами. Если они имеют разные типы, то параметры разделяются запятыми, а переменные разных типов начинаются с новой строки.

Рассмотрим пример функции выводящей сумму, разность, произведение и частное двух целых чисел. Я использовал локальную переменную для хранения произведения двух чисел в качестве простейшего примера.

func myout( int i j )
{
   int multi

   multi = i * j

   @"\(i) + \(j) = \( i + j )
\(i) - \(j) = \( i - j )
\(i) * \(j) = \( multi )
\(i) / \(j) = \( i / j )\n"
}

func main<main>
{
   myout( 20, 10 )
   myout( 567, 35 )
   myout( 16, 4 )
}

В зависимости от различных значений параметров i и j функция myout будет выдавать различные результаты. В данном примере у нас имеется одна операция присваивания '='. Она позволяет устанавливать у переменных новые значения. При вызове функции локальные переменные равны нулю, но можно присваивать значение локальным переменным сразу после их описания.

uint i = 10
i = i * 5 + 2
После выполнения этого фрагмента значение i будет равно 52.

Метод

Метод отличается от функции только описанием и способом вызова. Использование метода лучше дает понять, что над определенным объектом мы производим какое-то действие.

// Описание
method [возвращаемый_тип] тип_переменной.имя_метода( параметры )
// Использование
переменная.имя_метода( параметры ) 
Попробуем сделать фунцию print методом.
method str.myprint : print( this )

func main<main>
{
   str  hello = "Hello!"
 
   hello.myprint()
   getch()  
}

this обозначает переменную с которой вызывается метод. В данном случае можно обойтись без дополнительной переменной hello. Строка в двойных кавычках является обыкновенной переменной типа str, только без имени. Вы просто не можете к ней обратиться в дальнейшем.

"Hello!".myprint()

В объектно-оиентированных языках методы жестко привязаны к конкретному классу и для добавления своих методов требуется определение нового класса на основе существующего. Gentee позволяет добавлять новые методы к типам в любой момент и в любом месте.

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

method str.myprint : print( this )

method str.myprint( uint count )
{
   str  s = this + "\n"  
   print( s.repeat( count ))
}

func main<main>
{
   "Hello!".myprint()
   "-----".myprint( 5 )
   getch()  
}

Консольный ввод и вывод

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

Так как я уже много раз использовал вывод данных с помощью функции print и операции @"строка вывода", то перейдем сразу к вводу данных.

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

print("Привет!")

даст непонятный набор символов. Никакой ошибки тут нет, это наследие операционной системы MS-DOS. У каждой системы есть своя кодировка, и не одна. Консольные приложения Windows используют кодировку из MS-DOS, а вы набиваете текст в кодировке Windows. И если кодировка латинских букв алфавита совпадают в обоих случаях, то расположение русских символов абсолютно разное. Поэтому английский текст выводится правильно, а русский текст нечитаем. Попробуйте вот этот фрагмент:

print("?аЁў?в!")

Он выведет именно то, что вы и ожидали увидеть раньше. Решить эту проблему можно с помощью методов oem2char и char2oem. Первый переводит строку из DOS кодировки в Windows, а второй осуществляет обратную операция.

print( "Привет!".char2oem())

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

func myprint( str out )
{
   str  dos = out
   print( dos.char2oem())
}

Одну функцию для ввода данных вы уже знаете. Это getch, которая используется для получения нажатой клавиши. Для получение целой строки можно использовать функцию congetstr( str outtext intext ). В первом параметре можно указать выводимы текст, а вторая строка служит для получения результата. Вывод заканчивается при нажатии клавиши Enter.

str in
congetstr( "Enter your name: ", in )
print("Your name: \(in)\n")

Конструкция условия

Оператор условия позволяет изменять порядок выполнения программы в зависимости от истинности условного выражения. Условный оператор присутствует в любом языке программирования и является одним из фундаментальных понятий. В Gentee оператор условия выглядит так:

if условное_выражение1
{ 
   // Выполняется если условное_выражение1 не 0 
}
elif условное_выражение2
{
   // Выполняется если условное_выражение2 не 0
}
else
{
   // Выполняется если все предыдущие условия ложь.
}

Дополнительные конструкции elif и else не являеются обязательными. Может быть указано несколько конструкций elif. Переход к ним происходит если ни одно из предыдущих условий не было выполнено.

Условным выражением является любое выражение возвращающее число. Нулевое значение означает, что выражение ЛОЖНО, в противном случае выражение является ИСТИННЫМ. В простейшем случае выражением может быть переменная.

uint i

if i { ... }
Имеются специальные операции сравнения.

a < b - возвратит 1 если a меньше b и 0 в остальных случаях.
a > b - возвратит 1 если a больше b и 0 в остальных случаях.
a <= b - возвратит 1 если a меньше или равно b и 0 в остальных случаях.
a >= b - возвратит 1 если a больше или равно b и 0 в остальных случаях.
a == b - возвратит 1 если a равно b и 0 в остальных случаях.
a != b - возвратит 1 если a не равно b и 0 в остальных случаях.<

Следует заметить, что для равенства используется два знака '=', в отличии от операции присваивания. В приведенном ниже примере тело условного оператора будет выполнятся в любом случае, так как оператор присваивания сделает значение переменной i равным 1 и возвратит его.

if i = 1 {...}
Правильный вариант
if i == 1 {...}

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

func int mymin( int left right )
{
   if left < right : return left
   return right
}

Мы не указали else потому что при left меньше right мы выходим из функции.

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

a && b - возвратит 1 если a и b не ноль. В остальных случаях возвратит 0.
a || b - возвратит 1 если a или b не ноль. Возвращает 0, если и a и b равны нулю.
!a - возвратит 1 если a равно 0, и возвращает 0 если а не равно 0.

Усложним предыдущий пример и напишем функцию нахождения суммы двух минимальных чисел из трех переданных в параметре. Рассмотрим три варианта решения.

// Пример 1
func int mymin( int left right ) : return ?( left < right, left, right )
func int mymin( int x y z ) : return mymin( x + y, mymin( x + z, y + z ))

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

Оператор
 
?( условное выражение, выражение1, выражение2 )

является аналогом конструкции условия для использования внутри выражений. В зависимости от результата условия будет вычислено и возвращено выражение1 или выражение2.

Рассмотрим второй пример
// Пример 2
func int mymin( int x y z )
{ 
   if x < z 
   {
      if y < z : return x + y
      return x + z
   }
   if x < y : return x + z 
   return y + z
}

Смотрим, если x меньше z, то считаем, что одно минимально число мы уже нашли. Остается сравнить y и z и возвратить соответствующую сумму. Если x не меньше z, то тогда остается только сравнить x и y и возвратить сумму меньшего из них и z.

В третьем варианте мы будем искать максимальное число и возвращать сумму двух остальных чисел.

// Пример 3
func int mymin( int x y z )
{ 
   if x > y && x > z : return y + z
   if y > z : return x + z
   return x + y
}

После первой условной конструкции мы выяснили, что x не является максимальным. Значит будем возвращать или x + y или x + z. Нам достаточно только определить наименьшее из y и z.

Для тестирования можно определить ниже функцию main и запускать ее с разными параметрами при вызове mymin.

func main<main>
{
   print("MIN( x, y, z ) = \( mymin( 12, 45, 34 ))")
   getch()
}

Циклы

Циклы позволяют повторить необходимое количество раз определенную последовательность действий. В Gentee имеется несколько видов циклов и сейчас я познакомлю с тремя из них. Попробуем найти сумму чисел от 1 до 100. Для решения этой задачи лучше всего подойдет цикл fornum.

fornum переменная_счетчик [ = начальное_значение ], конечное_значение 
{
   // тело цикла
}

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

func main<main>
{
   uint i sum

   fornum i, 101 : sum += i
   @"SUM (1 - 100) = \(sum)"
   getch() 
}

Я думаю вы не забыли, что все переменные в начале равны нулю, поэтому их можно использовать без дополнительной инициализации. Операция a += b равнозначна операции a = a + b. Есть аналогичные операции для вычитания, умножения и деления -=, *=, /= . Например, в результате i *= 3 значение переменной i увеличится в три раза.

Вторым простым циклом является while, кторый исполняет тело цикла пока условное выражение не равно 0.

while условное_выражение  
{
   // тело цикла
}

Попробуйте решить предыдущую задачу используя while вместо fornum. Учтите, что вы должны сами изменять значение переменной i. Я в качестве иллюстрации использования while приведу пример получения и вывода символа с клавиатуры.

func main<main>
{
   uint ch

   while (ch = getch()) != 'q'
   {
      print( "Key: ".appendch( ch ) += "\n" )
   }
}

Цикл будет работать до тех пор, пока мы не нажмем клавишу 'q'. Метод appendch добавляет к строке символ с данным значением.

Если мы напишем так
print( "Key: \( ch ) \n" )
то у нас будут выводиться числовые значение нажатых клавиш.

Аналогично while работает цикл do while. Единственное отличие в том, что в do while проверка производится после выполнения тела цикла. Цикл выполнится как минимум один раз.

do  
{
   // тело цикла
} while условное_выражение

В следующем примере цикл do while в отличии от while выполнится один раз.

i = 10
while i < 10 : @"While: \( i )"

do
{
   @"Do While: \( i )"
} while i < 10 

Определение типа

Все вещи вокруг нас можно классифицировать и разбить на различные группы. Все животные и растения разбиваются на классы и виды. Возьмем строения. Есть офисные здания, магазины, жилые многоквартирные дома, коттеджи и т.д. Принадлежность к какой-то группе подразумевает определенные свойства данного объекта. Тоже самое с типами в программировании. Вы уже немного знакомы с натуральными числами uint и строками str. Нам встречался еще тип int - это целое число от -2147483648 до +2147483647. Есть типы float и double для работы с действительными числами. Есть long и ulong представляющие очень большие числа. Самое главное то, что вы сами можете определять свои типы и работать с ними. Рассмотрим конкретный пример создания типа ( или структуры ).

type person
{
   str  firstname
   str  lastname
   str  email
   uint age          // Возраст
}

Наш тип person имеет четыре поля для хранения имени, email и возраста. При описании структуры для каждого поля необходимо указать его тип и имя, по которому мы будем обращаться к данному полю. Желательно, чтобы имена полей указывали на ту информацию, которую они будут хранить или имели соответствующие комментарии. Определим методы для установки значений полей и для их вывода на экран.

method person person.set( str fname lname email, uint age )
{
   this.firstname = fname 
   this.lastname = lname
   this.email = email
   this.age = age
   return this
}

method person.print
{
   print( "Name: \(this.firstname) \(this.lastname)
Email: \(this.email)
Age: \(this.age)\n" )
}

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

func main<main>
{
   person myf

   myf.set( "Alexey", "Krivonogov", "info@gentee.com", 34 ).print()
   getch()
}

Благодаря тому, что метод set возвращает сам объект типа person мы можем сразу же применить метод print. Такой последовательный вызов методов сокращает программу, но оставляет ее по-прежнему понятной.




Источник: http://www.gentee.ru/vol1.htm

Автор: Алексей Кривоногов











Hosted by uCoz